summaryrefslogtreecommitdiffstats
path: root/scripts/lib/checklayer/cases/common.py
blob: ddead69a7be8529a72bf165a52623164422fea4e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
# Copyright (C) 2017 Intel Corporation
#
# SPDX-License-Identifier: MIT
#

import glob
import os
import unittest
import re
from checklayer import get_signatures, LayerType, check_command, compare_signatures, get_git_toplevel
from checklayer.case import OECheckLayerTestCase

class CommonCheckLayer(OECheckLayerTestCase):
    def test_readme(self):
        if self.tc.layer['type'] == LayerType.CORE:
            raise unittest.SkipTest("Core layer's README is top level")

        # The top-level README file may have a suffix (like README.rst or README.txt).
        readme_files = glob.glob(os.path.join(self.tc.layer['path'], '[Rr][Ee][Aa][Dd][Mm][Ee]*'))
        self.assertTrue(len(readme_files) > 0,
                        msg="Layer doesn't contain a README file.")

        # There might be more than one file matching the file pattern above
        # (for example, README.rst and README-COPYING.rst). The one with the shortest
        # name is considered the "main" one.
        readme_file = sorted(readme_files)[0]
        data = ''
        with open(readme_file, 'r') as f:
            data = f.read()
        self.assertTrue(data,
                msg="Layer contains a README file but it is empty.")

        # If a layer's README references another README, then the checks below are not valid
        if re.search('README', data, re.IGNORECASE):
            return

        self.assertIn('maintainer', data.lower())
        self.assertIn('patch', data.lower())
        # Check that there is an email address in the README
        email_regex = re.compile(r"[^@]+@[^@]+")
        self.assertTrue(email_regex.match(data))

    def find_file_by_name(self, globs):
        """
        Utility function to find a file that matches the specified list of
        globs, in either the layer directory itself or the repository top-level
        directory.
        """
        directories = [self.tc.layer["path"]]
        toplevel = get_git_toplevel(directories[0])
        if toplevel:
            directories.append(toplevel)

        for path in directories:
            for name in globs:
                files = glob.glob(os.path.join(path, name))
                if files:
                    return sorted(files)[0]
        return None

    def test_security(self):
        """
        Test that the layer has a SECURITY.md (or similar) file, either in the
        layer itself or at the top of the containing git repository.
        """
        if self.tc.layer["type"] == LayerType.CORE:
            raise unittest.SkipTest("Core layer's SECURITY is top level")

        filename = self.find_file_by_name(("SECURITY", "SECURITY.*"))
        self.assertTrue(filename, msg="Layer doesn't contain a SECURITY.md file.")

        size = os.path.getsize(filename)
        self.assertGreater(size, 0, msg=f"{filename} has no content.")

    def test_parse(self):
        check_command('Layer %s failed to parse.' % self.tc.layer['name'],
                      'bitbake -p')

    def test_show_environment(self):
        check_command('Layer %s failed to show environment.' % self.tc.layer['name'],
                      'bitbake -e')

    def test_world(self):
        '''
        "bitbake world" is expected to work. test_signatures does not cover that
        because it is more lenient and ignores recipes in a world build that
        are not actually buildable, so here we fail when "bitbake -S none world"
        fails.
        '''
        get_signatures(self.td['builddir'], failsafe=False)

    def test_world_inherit_class(self):
        '''
        This also does "bitbake -S none world" along with inheriting "yocto-check-layer"
        class, which can do additional per-recipe test cases.
        '''
        msg = []
        try:
            get_signatures(self.td['builddir'], failsafe=False, machine=None, extravars='BB_ENV_PASSTHROUGH_ADDITIONS="$BB_ENV_PASSTHROUGH_ADDITIONS INHERIT" INHERIT="yocto-check-layer"')
        except RuntimeError as ex:
            msg.append(str(ex))
        if msg:
            msg.insert(0, 'Layer %s failed additional checks from yocto-check-layer.bbclass\nSee below log for specific recipe parsing errors:\n' % \
                self.tc.layer['name'])
            self.fail('\n'.join(msg))

    def test_patches_upstream_status(self):
        import sys
        sys.path.append(os.path.join(sys.path[0], '../../../../meta/lib/'))
        import oe.qa
        patches = []
        for dirpath, dirs, files in os.walk(self.tc.layer['path']):
            for filename in files:
                if filename.endswith(".patch"):
                    ppath = os.path.join(dirpath, filename)
                    if oe.qa.check_upstream_status(ppath):
                        patches.append(ppath)
        self.assertEqual(len(patches), 0 , \
                msg="Found following patches with malformed or missing upstream status:\n%s" % '\n'.join([str(patch) for patch in patches]))

    def test_signatures(self):
        if self.tc.layer['type'] == LayerType.SOFTWARE and \
           not self.tc.test_software_layer_signatures:
            raise unittest.SkipTest("Not testing for signature changes in a software layer %s." \
                     % self.tc.layer['name'])

        curr_sigs, _ = get_signatures(self.td['builddir'], failsafe=True)
        msg = compare_signatures(self.td['sigs'], curr_sigs)
        if msg is not None:
            self.fail('Adding layer %s changed signatures.\n%s' % (self.tc.layer['name'], msg))

    def test_layerseries_compat(self):
        for collection_name, collection_data in self.tc.layer['collections'].items():
            self.assertTrue(collection_data['compat'], "Collection %s from layer %s does not set compatible oe-core versions via LAYERSERIES_COMPAT_collection." \
                 % (collection_name, self.tc.layer['name']))