summaryrefslogtreecommitdiffstats
path: root/scripts/lib/checklayer/cases
diff options
context:
space:
mode:
Diffstat (limited to 'scripts/lib/checklayer/cases')
-rw-r--r--scripts/lib/checklayer/cases/__init__.py0
-rw-r--r--scripts/lib/checklayer/cases/bsp.py206
-rw-r--r--scripts/lib/checklayer/cases/common.py135
-rw-r--r--scripts/lib/checklayer/cases/distro.py28
4 files changed, 0 insertions, 369 deletions
diff --git a/scripts/lib/checklayer/cases/__init__.py b/scripts/lib/checklayer/cases/__init__.py
deleted file mode 100644
index e69de29bb2..0000000000
--- a/scripts/lib/checklayer/cases/__init__.py
+++ /dev/null
diff --git a/scripts/lib/checklayer/cases/bsp.py b/scripts/lib/checklayer/cases/bsp.py
deleted file mode 100644
index b76163fb56..0000000000
--- a/scripts/lib/checklayer/cases/bsp.py
+++ /dev/null
@@ -1,206 +0,0 @@
1# Copyright (C) 2017 Intel Corporation
2#
3# SPDX-License-Identifier: MIT
4#
5
6import unittest
7
8from checklayer import LayerType, get_signatures, check_command, get_depgraph
9from checklayer.case import OECheckLayerTestCase
10
11class BSPCheckLayer(OECheckLayerTestCase):
12 @classmethod
13 def setUpClass(self):
14 if self.tc.layer['type'] not in (LayerType.BSP, LayerType.CORE):
15 raise unittest.SkipTest("BSPCheckLayer: Layer %s isn't BSP one." %\
16 self.tc.layer['name'])
17
18 def test_bsp_defines_machines(self):
19 self.assertTrue(self.tc.layer['conf']['machines'],
20 "Layer is BSP but doesn't defines machines.")
21
22 def test_bsp_no_set_machine(self):
23 from oeqa.utils.commands import get_bb_var
24
25 machine = get_bb_var('MACHINE')
26 self.assertEqual(self.td['bbvars']['MACHINE'], machine,
27 msg="Layer %s modified machine %s -> %s" % \
28 (self.tc.layer['name'], self.td['bbvars']['MACHINE'], machine))
29
30
31 def test_machine_world(self):
32 '''
33 "bitbake world" is expected to work regardless which machine is selected.
34 BSP layers sometimes break that by enabling a recipe for a certain machine
35 without checking whether that recipe actually can be built in the current
36 distro configuration (for example, OpenGL might not enabled).
37
38 This test iterates over all machines. It would be nicer to instantiate
39 it once per machine. It merely checks for errors during parse
40 time. It does not actually attempt to build anything.
41 '''
42
43 if not self.td['machines']:
44 self.skipTest('No machines set with --machines.')
45 msg = []
46 for machine in self.td['machines']:
47 # In contrast to test_machine_signatures() below, errors are fatal here.
48 try:
49 get_signatures(self.td['builddir'], failsafe=False, machine=machine)
50 except RuntimeError as ex:
51 msg.append(str(ex))
52 if msg:
53 msg.insert(0, 'The following machines broke a world build:')
54 self.fail('\n'.join(msg))
55
56 def test_machine_signatures(self):
57 '''
58 Selecting a machine may only affect the signature of tasks that are specific
59 to that machine. In other words, when MACHINE=A and MACHINE=B share a recipe
60 foo and the output of foo, then both machine configurations must build foo
61 in exactly the same way. Otherwise it is not possible to use both machines
62 in the same distribution.
63
64 This criteria can only be tested by testing different machines in combination,
65 i.e. one main layer, potentially several additional BSP layers and an explicit
66 choice of machines:
67 yocto-check-layer --additional-layers .../meta-intel --machines intel-corei7-64 imx6slevk -- .../meta-freescale
68 '''
69
70 if not self.td['machines']:
71 self.skipTest('No machines set with --machines.')
72
73 # Collect signatures for all machines that we are testing
74 # and merge that into a hash:
75 # tune -> task -> signature -> list of machines with that combination
76 #
77 # It is an error if any tune/task pair has more than one signature,
78 # because that implies that the machines that caused those different
79 # signatures do not agree on how to execute the task.
80 tunes = {}
81 # Preserve ordering of machines as chosen by the user.
82 for machine in self.td['machines']:
83 curr_sigs, tune2tasks = get_signatures(self.td['builddir'], failsafe=True, machine=machine)
84 # Invert the tune -> [tasks] mapping.
85 tasks2tune = {}
86 for tune, tasks in tune2tasks.items():
87 for task in tasks:
88 tasks2tune[task] = tune
89 for task, sighash in curr_sigs.items():
90 tunes.setdefault(tasks2tune[task], {}).setdefault(task, {}).setdefault(sighash, []).append(machine)
91
92 msg = []
93 pruned = 0
94 last_line_key = None
95 # do_fetch, do_unpack, ..., do_build
96 taskname_list = []
97 if tunes:
98 # The output below is most useful when we start with tasks that are at
99 # the bottom of the dependency chain, i.e. those that run first. If
100 # those tasks differ, the rest also does.
101 #
102 # To get an ordering of tasks, we do a topological sort of the entire
103 # depgraph for the base configuration, then on-the-fly flatten that list by stripping
104 # out the recipe names and removing duplicates. The base configuration
105 # is not necessarily representative, but should be close enough. Tasks
106 # that were not encountered get a default priority.
107 depgraph = get_depgraph()
108 depends = depgraph['tdepends']
109 WHITE = 1
110 GRAY = 2
111 BLACK = 3
112 color = {}
113 found = set()
114 def visit(task):
115 color[task] = GRAY
116 for dep in depends.get(task, ()):
117 if color.setdefault(dep, WHITE) == WHITE:
118 visit(dep)
119 color[task] = BLACK
120 pn, taskname = task.rsplit('.', 1)
121 if taskname not in found:
122 taskname_list.append(taskname)
123 found.add(taskname)
124 for task in depends.keys():
125 if color.setdefault(task, WHITE) == WHITE:
126 visit(task)
127
128 taskname_order = dict([(task, index) for index, task in enumerate(taskname_list) ])
129 def task_key(task):
130 pn, taskname = task.rsplit(':', 1)
131 return (pn, taskname_order.get(taskname, len(taskname_list)), taskname)
132
133 for tune in sorted(tunes.keys()):
134 tasks = tunes[tune]
135 # As for test_signatures it would be nicer to sort tasks
136 # by dependencies here, but that is harder because we have
137 # to report on tasks from different machines, which might
138 # have different dependencies. We resort to pruning the
139 # output by reporting only one task per recipe if the set
140 # of machines matches.
141 #
142 # "bitbake-diffsigs -t -s" is intelligent enough to print
143 # diffs recursively, so often it does not matter that much
144 # if we don't pick the underlying difference
145 # here. However, sometimes recursion fails
146 # (https://bugzilla.yoctoproject.org/show_bug.cgi?id=6428).
147 #
148 # To mitigate that a bit, we use a hard-coded ordering of
149 # tasks that represents how they normally run and prefer
150 # to print the ones that run first.
151 for task in sorted(tasks.keys(), key=task_key):
152 signatures = tasks[task]
153 # do_build can be ignored: it is know to have
154 # different signatures in some cases, for example in
155 # the allarch ca-certificates due to RDEPENDS=openssl.
156 # That particular dependency is marked via
157 # SIGGEN_EXCLUDE_SAFE_RECIPE_DEPS, but still shows up
158 # in the sstate signature hash because filtering it
159 # out would be hard and running do_build multiple
160 # times doesn't really matter.
161 if len(signatures.keys()) > 1 and \
162 not task.endswith(':do_build'):
163 # Error!
164 #
165 # Sort signatures by machines, because the hex values don't mean anything.
166 # => all-arch adwaita-icon-theme:do_build: 1234... (beaglebone, qemux86) != abcdf... (qemux86-64)
167 #
168 # Skip the line if it is covered already by the predecessor (same pn, same sets of machines).
169 pn, taskname = task.rsplit(':', 1)
170 next_line_key = (pn, sorted(signatures.values()))
171 if next_line_key != last_line_key:
172 line = ' %s %s: ' % (tune, task)
173 line += ' != '.join(['%s (%s)' % (signature, ', '.join([m for m in signatures[signature]])) for
174 signature in sorted(signatures.keys(), key=lambda s: signatures[s])])
175 last_line_key = next_line_key
176 msg.append(line)
177 # Randomly pick two mismatched signatures and remember how to invoke
178 # bitbake-diffsigs for them.
179 iterator = iter(signatures.items())
180 a = next(iterator)
181 b = next(iterator)
182 diffsig_machines = '(%s) != (%s)' % (', '.join(a[1]), ', '.join(b[1]))
183 diffsig_params = '-t %s %s -s %s %s' % (pn, taskname, a[0], b[0])
184 else:
185 pruned += 1
186
187 if msg:
188 msg.insert(0, 'The machines have conflicting signatures for some shared tasks:')
189 if pruned > 0:
190 msg.append('')
191 msg.append('%d tasks where not listed because some other task of the recipe already differed.' % pruned)
192 msg.append('It is likely that differences from different recipes also have the same root cause.')
193 msg.append('')
194 # Explain how to investigate...
195 msg.append('To investigate, run bitbake-diffsigs -t recipename taskname -s fromsig tosig.')
196 cmd = 'bitbake-diffsigs %s' % diffsig_params
197 msg.append('Example: %s in the last line' % diffsig_machines)
198 msg.append('Command: %s' % cmd)
199 # ... and actually do it automatically for that example, but without aborting
200 # when that fails.
201 try:
202 output = check_command('Comparing signatures failed.', cmd).decode('utf-8')
203 except RuntimeError as ex:
204 output = str(ex)
205 msg.extend([' ' + line for line in output.splitlines()])
206 self.fail('\n'.join(msg))
diff --git a/scripts/lib/checklayer/cases/common.py b/scripts/lib/checklayer/cases/common.py
deleted file mode 100644
index ddead69a7b..0000000000
--- a/scripts/lib/checklayer/cases/common.py
+++ /dev/null
@@ -1,135 +0,0 @@
1# Copyright (C) 2017 Intel Corporation
2#
3# SPDX-License-Identifier: MIT
4#
5
6import glob
7import os
8import unittest
9import re
10from checklayer import get_signatures, LayerType, check_command, compare_signatures, get_git_toplevel
11from checklayer.case import OECheckLayerTestCase
12
13class CommonCheckLayer(OECheckLayerTestCase):
14 def test_readme(self):
15 if self.tc.layer['type'] == LayerType.CORE:
16 raise unittest.SkipTest("Core layer's README is top level")
17
18 # The top-level README file may have a suffix (like README.rst or README.txt).
19 readme_files = glob.glob(os.path.join(self.tc.layer['path'], '[Rr][Ee][Aa][Dd][Mm][Ee]*'))
20 self.assertTrue(len(readme_files) > 0,
21 msg="Layer doesn't contain a README file.")
22
23 # There might be more than one file matching the file pattern above
24 # (for example, README.rst and README-COPYING.rst). The one with the shortest
25 # name is considered the "main" one.
26 readme_file = sorted(readme_files)[0]
27 data = ''
28 with open(readme_file, 'r') as f:
29 data = f.read()
30 self.assertTrue(data,
31 msg="Layer contains a README file but it is empty.")
32
33 # If a layer's README references another README, then the checks below are not valid
34 if re.search('README', data, re.IGNORECASE):
35 return
36
37 self.assertIn('maintainer', data.lower())
38 self.assertIn('patch', data.lower())
39 # Check that there is an email address in the README
40 email_regex = re.compile(r"[^@]+@[^@]+")
41 self.assertTrue(email_regex.match(data))
42
43 def find_file_by_name(self, globs):
44 """
45 Utility function to find a file that matches the specified list of
46 globs, in either the layer directory itself or the repository top-level
47 directory.
48 """
49 directories = [self.tc.layer["path"]]
50 toplevel = get_git_toplevel(directories[0])
51 if toplevel:
52 directories.append(toplevel)
53
54 for path in directories:
55 for name in globs:
56 files = glob.glob(os.path.join(path, name))
57 if files:
58 return sorted(files)[0]
59 return None
60
61 def test_security(self):
62 """
63 Test that the layer has a SECURITY.md (or similar) file, either in the
64 layer itself or at the top of the containing git repository.
65 """
66 if self.tc.layer["type"] == LayerType.CORE:
67 raise unittest.SkipTest("Core layer's SECURITY is top level")
68
69 filename = self.find_file_by_name(("SECURITY", "SECURITY.*"))
70 self.assertTrue(filename, msg="Layer doesn't contain a SECURITY.md file.")
71
72 size = os.path.getsize(filename)
73 self.assertGreater(size, 0, msg=f"{filename} has no content.")
74
75 def test_parse(self):
76 check_command('Layer %s failed to parse.' % self.tc.layer['name'],
77 'bitbake -p')
78
79 def test_show_environment(self):
80 check_command('Layer %s failed to show environment.' % self.tc.layer['name'],
81 'bitbake -e')
82
83 def test_world(self):
84 '''
85 "bitbake world" is expected to work. test_signatures does not cover that
86 because it is more lenient and ignores recipes in a world build that
87 are not actually buildable, so here we fail when "bitbake -S none world"
88 fails.
89 '''
90 get_signatures(self.td['builddir'], failsafe=False)
91
92 def test_world_inherit_class(self):
93 '''
94 This also does "bitbake -S none world" along with inheriting "yocto-check-layer"
95 class, which can do additional per-recipe test cases.
96 '''
97 msg = []
98 try:
99 get_signatures(self.td['builddir'], failsafe=False, machine=None, extravars='BB_ENV_PASSTHROUGH_ADDITIONS="$BB_ENV_PASSTHROUGH_ADDITIONS INHERIT" INHERIT="yocto-check-layer"')
100 except RuntimeError as ex:
101 msg.append(str(ex))
102 if msg:
103 msg.insert(0, 'Layer %s failed additional checks from yocto-check-layer.bbclass\nSee below log for specific recipe parsing errors:\n' % \
104 self.tc.layer['name'])
105 self.fail('\n'.join(msg))
106
107 def test_patches_upstream_status(self):
108 import sys
109 sys.path.append(os.path.join(sys.path[0], '../../../../meta/lib/'))
110 import oe.qa
111 patches = []
112 for dirpath, dirs, files in os.walk(self.tc.layer['path']):
113 for filename in files:
114 if filename.endswith(".patch"):
115 ppath = os.path.join(dirpath, filename)
116 if oe.qa.check_upstream_status(ppath):
117 patches.append(ppath)
118 self.assertEqual(len(patches), 0 , \
119 msg="Found following patches with malformed or missing upstream status:\n%s" % '\n'.join([str(patch) for patch in patches]))
120
121 def test_signatures(self):
122 if self.tc.layer['type'] == LayerType.SOFTWARE and \
123 not self.tc.test_software_layer_signatures:
124 raise unittest.SkipTest("Not testing for signature changes in a software layer %s." \
125 % self.tc.layer['name'])
126
127 curr_sigs, _ = get_signatures(self.td['builddir'], failsafe=True)
128 msg = compare_signatures(self.td['sigs'], curr_sigs)
129 if msg is not None:
130 self.fail('Adding layer %s changed signatures.\n%s' % (self.tc.layer['name'], msg))
131
132 def test_layerseries_compat(self):
133 for collection_name, collection_data in self.tc.layer['collections'].items():
134 self.assertTrue(collection_data['compat'], "Collection %s from layer %s does not set compatible oe-core versions via LAYERSERIES_COMPAT_collection." \
135 % (collection_name, self.tc.layer['name']))
diff --git a/scripts/lib/checklayer/cases/distro.py b/scripts/lib/checklayer/cases/distro.py
deleted file mode 100644
index a35332451c..0000000000
--- a/scripts/lib/checklayer/cases/distro.py
+++ /dev/null
@@ -1,28 +0,0 @@
1# Copyright (C) 2017 Intel Corporation
2#
3# SPDX-License-Identifier: MIT
4#
5
6import unittest
7
8from checklayer import LayerType
9from checklayer.case import OECheckLayerTestCase
10
11class DistroCheckLayer(OECheckLayerTestCase):
12 @classmethod
13 def setUpClass(self):
14 if self.tc.layer['type'] not in (LayerType.DISTRO, LayerType.CORE):
15 raise unittest.SkipTest("DistroCheckLayer: Layer %s isn't Distro one." %\
16 self.tc.layer['name'])
17
18 def test_distro_defines_distros(self):
19 self.assertTrue(self.tc.layer['conf']['distros'],
20 "Layer is BSP but doesn't defines machines.")
21
22 def test_distro_no_set_distros(self):
23 from oeqa.utils.commands import get_bb_var
24
25 distro = get_bb_var('DISTRO')
26 self.assertEqual(self.td['bbvars']['DISTRO'], distro,
27 msg="Layer %s modified distro %s -> %s" % \
28 (self.tc.layer['name'], self.td['bbvars']['DISTRO'], distro))