1*4882a593Smuzhiyun# Copyright (C) 2017 Intel Corporation 2*4882a593Smuzhiyun# 3*4882a593Smuzhiyun# SPDX-License-Identifier: MIT 4*4882a593Smuzhiyun# 5*4882a593Smuzhiyun 6*4882a593Smuzhiyunimport unittest 7*4882a593Smuzhiyun 8*4882a593Smuzhiyunfrom checklayer import LayerType, get_signatures, check_command, get_depgraph 9*4882a593Smuzhiyunfrom checklayer.case import OECheckLayerTestCase 10*4882a593Smuzhiyun 11*4882a593Smuzhiyunclass BSPCheckLayer(OECheckLayerTestCase): 12*4882a593Smuzhiyun @classmethod 13*4882a593Smuzhiyun def setUpClass(self): 14*4882a593Smuzhiyun if self.tc.layer['type'] not in (LayerType.BSP, LayerType.CORE): 15*4882a593Smuzhiyun raise unittest.SkipTest("BSPCheckLayer: Layer %s isn't BSP one." %\ 16*4882a593Smuzhiyun self.tc.layer['name']) 17*4882a593Smuzhiyun 18*4882a593Smuzhiyun def test_bsp_defines_machines(self): 19*4882a593Smuzhiyun self.assertTrue(self.tc.layer['conf']['machines'], 20*4882a593Smuzhiyun "Layer is BSP but doesn't defines machines.") 21*4882a593Smuzhiyun 22*4882a593Smuzhiyun def test_bsp_no_set_machine(self): 23*4882a593Smuzhiyun from oeqa.utils.commands import get_bb_var 24*4882a593Smuzhiyun 25*4882a593Smuzhiyun machine = get_bb_var('MACHINE') 26*4882a593Smuzhiyun self.assertEqual(self.td['bbvars']['MACHINE'], machine, 27*4882a593Smuzhiyun msg="Layer %s modified machine %s -> %s" % \ 28*4882a593Smuzhiyun (self.tc.layer['name'], self.td['bbvars']['MACHINE'], machine)) 29*4882a593Smuzhiyun 30*4882a593Smuzhiyun 31*4882a593Smuzhiyun def test_machine_world(self): 32*4882a593Smuzhiyun ''' 33*4882a593Smuzhiyun "bitbake world" is expected to work regardless which machine is selected. 34*4882a593Smuzhiyun BSP layers sometimes break that by enabling a recipe for a certain machine 35*4882a593Smuzhiyun without checking whether that recipe actually can be built in the current 36*4882a593Smuzhiyun distro configuration (for example, OpenGL might not enabled). 37*4882a593Smuzhiyun 38*4882a593Smuzhiyun This test iterates over all machines. It would be nicer to instantiate 39*4882a593Smuzhiyun it once per machine. It merely checks for errors during parse 40*4882a593Smuzhiyun time. It does not actually attempt to build anything. 41*4882a593Smuzhiyun ''' 42*4882a593Smuzhiyun 43*4882a593Smuzhiyun if not self.td['machines']: 44*4882a593Smuzhiyun self.skipTest('No machines set with --machines.') 45*4882a593Smuzhiyun msg = [] 46*4882a593Smuzhiyun for machine in self.td['machines']: 47*4882a593Smuzhiyun # In contrast to test_machine_signatures() below, errors are fatal here. 48*4882a593Smuzhiyun try: 49*4882a593Smuzhiyun get_signatures(self.td['builddir'], failsafe=False, machine=machine) 50*4882a593Smuzhiyun except RuntimeError as ex: 51*4882a593Smuzhiyun msg.append(str(ex)) 52*4882a593Smuzhiyun if msg: 53*4882a593Smuzhiyun msg.insert(0, 'The following machines broke a world build:') 54*4882a593Smuzhiyun self.fail('\n'.join(msg)) 55*4882a593Smuzhiyun 56*4882a593Smuzhiyun def test_machine_signatures(self): 57*4882a593Smuzhiyun ''' 58*4882a593Smuzhiyun Selecting a machine may only affect the signature of tasks that are specific 59*4882a593Smuzhiyun to that machine. In other words, when MACHINE=A and MACHINE=B share a recipe 60*4882a593Smuzhiyun foo and the output of foo, then both machine configurations must build foo 61*4882a593Smuzhiyun in exactly the same way. Otherwise it is not possible to use both machines 62*4882a593Smuzhiyun in the same distribution. 63*4882a593Smuzhiyun 64*4882a593Smuzhiyun This criteria can only be tested by testing different machines in combination, 65*4882a593Smuzhiyun i.e. one main layer, potentially several additional BSP layers and an explicit 66*4882a593Smuzhiyun choice of machines: 67*4882a593Smuzhiyun yocto-check-layer --additional-layers .../meta-intel --machines intel-corei7-64 imx6slevk -- .../meta-freescale 68*4882a593Smuzhiyun ''' 69*4882a593Smuzhiyun 70*4882a593Smuzhiyun if not self.td['machines']: 71*4882a593Smuzhiyun self.skipTest('No machines set with --machines.') 72*4882a593Smuzhiyun 73*4882a593Smuzhiyun # Collect signatures for all machines that we are testing 74*4882a593Smuzhiyun # and merge that into a hash: 75*4882a593Smuzhiyun # tune -> task -> signature -> list of machines with that combination 76*4882a593Smuzhiyun # 77*4882a593Smuzhiyun # It is an error if any tune/task pair has more than one signature, 78*4882a593Smuzhiyun # because that implies that the machines that caused those different 79*4882a593Smuzhiyun # signatures do not agree on how to execute the task. 80*4882a593Smuzhiyun tunes = {} 81*4882a593Smuzhiyun # Preserve ordering of machines as chosen by the user. 82*4882a593Smuzhiyun for machine in self.td['machines']: 83*4882a593Smuzhiyun curr_sigs, tune2tasks = get_signatures(self.td['builddir'], failsafe=True, machine=machine) 84*4882a593Smuzhiyun # Invert the tune -> [tasks] mapping. 85*4882a593Smuzhiyun tasks2tune = {} 86*4882a593Smuzhiyun for tune, tasks in tune2tasks.items(): 87*4882a593Smuzhiyun for task in tasks: 88*4882a593Smuzhiyun tasks2tune[task] = tune 89*4882a593Smuzhiyun for task, sighash in curr_sigs.items(): 90*4882a593Smuzhiyun tunes.setdefault(tasks2tune[task], {}).setdefault(task, {}).setdefault(sighash, []).append(machine) 91*4882a593Smuzhiyun 92*4882a593Smuzhiyun msg = [] 93*4882a593Smuzhiyun pruned = 0 94*4882a593Smuzhiyun last_line_key = None 95*4882a593Smuzhiyun # do_fetch, do_unpack, ..., do_build 96*4882a593Smuzhiyun taskname_list = [] 97*4882a593Smuzhiyun if tunes: 98*4882a593Smuzhiyun # The output below is most useful when we start with tasks that are at 99*4882a593Smuzhiyun # the bottom of the dependency chain, i.e. those that run first. If 100*4882a593Smuzhiyun # those tasks differ, the rest also does. 101*4882a593Smuzhiyun # 102*4882a593Smuzhiyun # To get an ordering of tasks, we do a topological sort of the entire 103*4882a593Smuzhiyun # depgraph for the base configuration, then on-the-fly flatten that list by stripping 104*4882a593Smuzhiyun # out the recipe names and removing duplicates. The base configuration 105*4882a593Smuzhiyun # is not necessarily representative, but should be close enough. Tasks 106*4882a593Smuzhiyun # that were not encountered get a default priority. 107*4882a593Smuzhiyun depgraph = get_depgraph() 108*4882a593Smuzhiyun depends = depgraph['tdepends'] 109*4882a593Smuzhiyun WHITE = 1 110*4882a593Smuzhiyun GRAY = 2 111*4882a593Smuzhiyun BLACK = 3 112*4882a593Smuzhiyun color = {} 113*4882a593Smuzhiyun found = set() 114*4882a593Smuzhiyun def visit(task): 115*4882a593Smuzhiyun color[task] = GRAY 116*4882a593Smuzhiyun for dep in depends.get(task, ()): 117*4882a593Smuzhiyun if color.setdefault(dep, WHITE) == WHITE: 118*4882a593Smuzhiyun visit(dep) 119*4882a593Smuzhiyun color[task] = BLACK 120*4882a593Smuzhiyun pn, taskname = task.rsplit('.', 1) 121*4882a593Smuzhiyun if taskname not in found: 122*4882a593Smuzhiyun taskname_list.append(taskname) 123*4882a593Smuzhiyun found.add(taskname) 124*4882a593Smuzhiyun for task in depends.keys(): 125*4882a593Smuzhiyun if color.setdefault(task, WHITE) == WHITE: 126*4882a593Smuzhiyun visit(task) 127*4882a593Smuzhiyun 128*4882a593Smuzhiyun taskname_order = dict([(task, index) for index, task in enumerate(taskname_list) ]) 129*4882a593Smuzhiyun def task_key(task): 130*4882a593Smuzhiyun pn, taskname = task.rsplit(':', 1) 131*4882a593Smuzhiyun return (pn, taskname_order.get(taskname, len(taskname_list)), taskname) 132*4882a593Smuzhiyun 133*4882a593Smuzhiyun for tune in sorted(tunes.keys()): 134*4882a593Smuzhiyun tasks = tunes[tune] 135*4882a593Smuzhiyun # As for test_signatures it would be nicer to sort tasks 136*4882a593Smuzhiyun # by dependencies here, but that is harder because we have 137*4882a593Smuzhiyun # to report on tasks from different machines, which might 138*4882a593Smuzhiyun # have different dependencies. We resort to pruning the 139*4882a593Smuzhiyun # output by reporting only one task per recipe if the set 140*4882a593Smuzhiyun # of machines matches. 141*4882a593Smuzhiyun # 142*4882a593Smuzhiyun # "bitbake-diffsigs -t -s" is intelligent enough to print 143*4882a593Smuzhiyun # diffs recursively, so often it does not matter that much 144*4882a593Smuzhiyun # if we don't pick the underlying difference 145*4882a593Smuzhiyun # here. However, sometimes recursion fails 146*4882a593Smuzhiyun # (https://bugzilla.yoctoproject.org/show_bug.cgi?id=6428). 147*4882a593Smuzhiyun # 148*4882a593Smuzhiyun # To mitigate that a bit, we use a hard-coded ordering of 149*4882a593Smuzhiyun # tasks that represents how they normally run and prefer 150*4882a593Smuzhiyun # to print the ones that run first. 151*4882a593Smuzhiyun for task in sorted(tasks.keys(), key=task_key): 152*4882a593Smuzhiyun signatures = tasks[task] 153*4882a593Smuzhiyun # do_build can be ignored: it is know to have 154*4882a593Smuzhiyun # different signatures in some cases, for example in 155*4882a593Smuzhiyun # the allarch ca-certificates due to RDEPENDS=openssl. 156*4882a593Smuzhiyun # That particular dependency is marked via 157*4882a593Smuzhiyun # SIGGEN_EXCLUDE_SAFE_RECIPE_DEPS, but still shows up 158*4882a593Smuzhiyun # in the sstate signature hash because filtering it 159*4882a593Smuzhiyun # out would be hard and running do_build multiple 160*4882a593Smuzhiyun # times doesn't really matter. 161*4882a593Smuzhiyun if len(signatures.keys()) > 1 and \ 162*4882a593Smuzhiyun not task.endswith(':do_build'): 163*4882a593Smuzhiyun # Error! 164*4882a593Smuzhiyun # 165*4882a593Smuzhiyun # Sort signatures by machines, because the hex values don't mean anything. 166*4882a593Smuzhiyun # => all-arch adwaita-icon-theme:do_build: 1234... (beaglebone, qemux86) != abcdf... (qemux86-64) 167*4882a593Smuzhiyun # 168*4882a593Smuzhiyun # Skip the line if it is covered already by the predecessor (same pn, same sets of machines). 169*4882a593Smuzhiyun pn, taskname = task.rsplit(':', 1) 170*4882a593Smuzhiyun next_line_key = (pn, sorted(signatures.values())) 171*4882a593Smuzhiyun if next_line_key != last_line_key: 172*4882a593Smuzhiyun line = ' %s %s: ' % (tune, task) 173*4882a593Smuzhiyun line += ' != '.join(['%s (%s)' % (signature, ', '.join([m for m in signatures[signature]])) for 174*4882a593Smuzhiyun signature in sorted(signatures.keys(), key=lambda s: signatures[s])]) 175*4882a593Smuzhiyun last_line_key = next_line_key 176*4882a593Smuzhiyun msg.append(line) 177*4882a593Smuzhiyun # Randomly pick two mismatched signatures and remember how to invoke 178*4882a593Smuzhiyun # bitbake-diffsigs for them. 179*4882a593Smuzhiyun iterator = iter(signatures.items()) 180*4882a593Smuzhiyun a = next(iterator) 181*4882a593Smuzhiyun b = next(iterator) 182*4882a593Smuzhiyun diffsig_machines = '(%s) != (%s)' % (', '.join(a[1]), ', '.join(b[1])) 183*4882a593Smuzhiyun diffsig_params = '-t %s %s -s %s %s' % (pn, taskname, a[0], b[0]) 184*4882a593Smuzhiyun else: 185*4882a593Smuzhiyun pruned += 1 186*4882a593Smuzhiyun 187*4882a593Smuzhiyun if msg: 188*4882a593Smuzhiyun msg.insert(0, 'The machines have conflicting signatures for some shared tasks:') 189*4882a593Smuzhiyun if pruned > 0: 190*4882a593Smuzhiyun msg.append('') 191*4882a593Smuzhiyun msg.append('%d tasks where not listed because some other task of the recipe already differed.' % pruned) 192*4882a593Smuzhiyun msg.append('It is likely that differences from different recipes also have the same root cause.') 193*4882a593Smuzhiyun msg.append('') 194*4882a593Smuzhiyun # Explain how to investigate... 195*4882a593Smuzhiyun msg.append('To investigate, run bitbake-diffsigs -t recipename taskname -s fromsig tosig.') 196*4882a593Smuzhiyun cmd = 'bitbake-diffsigs %s' % diffsig_params 197*4882a593Smuzhiyun msg.append('Example: %s in the last line' % diffsig_machines) 198*4882a593Smuzhiyun msg.append('Command: %s' % cmd) 199*4882a593Smuzhiyun # ... and actually do it automatically for that example, but without aborting 200*4882a593Smuzhiyun # when that fails. 201*4882a593Smuzhiyun try: 202*4882a593Smuzhiyun output = check_command('Comparing signatures failed.', cmd).decode('utf-8') 203*4882a593Smuzhiyun except RuntimeError as ex: 204*4882a593Smuzhiyun output = str(ex) 205*4882a593Smuzhiyun msg.extend([' ' + line for line in output.splitlines()]) 206*4882a593Smuzhiyun self.fail('\n'.join(msg)) 207