1# 2# Copyright OpenEmbedded Contributors 3# 4# SPDX-License-Identifier: MIT 5# 6 7import os 8import re 9import time 10import logging 11import bb.tinfoil 12 13from oeqa.selftest.case import OESelftestTestCase 14 15class TinfoilTests(OESelftestTestCase): 16 """ Basic tests for the tinfoil API """ 17 18 def test_getvar(self): 19 with bb.tinfoil.Tinfoil() as tinfoil: 20 tinfoil.prepare(True) 21 machine = tinfoil.config_data.getVar('MACHINE') 22 if not machine: 23 self.fail('Unable to get MACHINE value - returned %s' % machine) 24 25 def test_expand(self): 26 with bb.tinfoil.Tinfoil() as tinfoil: 27 tinfoil.prepare(True) 28 expr = '${@os.getpid()}' 29 pid = tinfoil.config_data.expand(expr) 30 if not pid: 31 self.fail('Unable to expand "%s" - returned %s' % (expr, pid)) 32 33 def test_getvar_bb_origenv(self): 34 with bb.tinfoil.Tinfoil() as tinfoil: 35 tinfoil.prepare(True) 36 origenv = tinfoil.config_data.getVar('BB_ORIGENV', False) 37 if not origenv: 38 self.fail('Unable to get BB_ORIGENV value - returned %s' % origenv) 39 self.assertEqual(origenv.getVar('HOME', False), os.environ['HOME']) 40 41 def test_parse_recipe(self): 42 with bb.tinfoil.Tinfoil() as tinfoil: 43 tinfoil.prepare(config_only=False, quiet=2) 44 testrecipe = 'mdadm' 45 best = tinfoil.find_best_provider(testrecipe) 46 if not best: 47 self.fail('Unable to find recipe providing %s' % testrecipe) 48 rd = tinfoil.parse_recipe_file(best[3]) 49 self.assertEqual(testrecipe, rd.getVar('PN')) 50 51 def test_parse_virtual_recipe(self): 52 with bb.tinfoil.Tinfoil() as tinfoil: 53 tinfoil.prepare(config_only=False, quiet=2) 54 testrecipe = 'nativesdk-gcc' 55 best = tinfoil.find_best_provider(testrecipe) 56 if not best: 57 self.fail('Unable to find recipe providing %s' % testrecipe) 58 rd = tinfoil.parse_recipe_file(best[3]) 59 self.assertEqual(testrecipe, rd.getVar('PN')) 60 self.assertIsNotNone(rd.getVar('FILE_LAYERNAME')) 61 62 def test_parse_recipe_copy_expand(self): 63 with bb.tinfoil.Tinfoil() as tinfoil: 64 tinfoil.prepare(config_only=False, quiet=2) 65 testrecipe = 'mdadm' 66 best = tinfoil.find_best_provider(testrecipe) 67 if not best: 68 self.fail('Unable to find recipe providing %s' % testrecipe) 69 rd = tinfoil.parse_recipe_file(best[3]) 70 # Check we can get variable values 71 self.assertEqual(testrecipe, rd.getVar('PN')) 72 # Check that expanding a value that includes a variable reference works 73 self.assertEqual(testrecipe, rd.getVar('BPN')) 74 # Now check that changing the referenced variable's value in a copy gives that 75 # value when expanding 76 localdata = bb.data.createCopy(rd) 77 localdata.setVar('PN', 'hello') 78 self.assertEqual('hello', localdata.getVar('BPN')) 79 80 # The config_data API to parse_recipe_file is used by: 81 # layerindex-web layerindex/update_layer.py 82 def test_parse_recipe_custom_data(self): 83 with bb.tinfoil.Tinfoil() as tinfoil: 84 tinfoil.prepare(config_only=False, quiet=2) 85 localdata = bb.data.createCopy(tinfoil.config_data) 86 localdata.setVar("TESTVAR", "testval") 87 testrecipe = 'mdadm' 88 best = tinfoil.find_best_provider(testrecipe) 89 if not best: 90 self.fail('Unable to find recipe providing %s' % testrecipe) 91 rd = tinfoil.parse_recipe_file(best[3], config_data=localdata) 92 self.assertEqual("testval", rd.getVar('TESTVAR')) 93 94 def test_parse_virtual_recipe_custom_data(self): 95 with bb.tinfoil.Tinfoil() as tinfoil: 96 tinfoil.prepare(config_only=False, quiet=2) 97 localdata = bb.data.createCopy(tinfoil.config_data) 98 localdata.setVar("TESTVAR", "testval") 99 testrecipe = 'nativesdk-gcc' 100 best = tinfoil.find_best_provider(testrecipe) 101 if not best: 102 self.fail('Unable to find recipe providing %s' % testrecipe) 103 rd = tinfoil.parse_recipe_file(best[3], config_data=localdata) 104 self.assertEqual("testval", rd.getVar('TESTVAR')) 105 106 def test_list_recipes(self): 107 with bb.tinfoil.Tinfoil() as tinfoil: 108 tinfoil.prepare(config_only=False, quiet=2) 109 # Check pkg_pn 110 checkpns = ['tar', 'automake', 'coreutils', 'm4-native', 'nativesdk-gcc'] 111 pkg_pn = tinfoil.cooker.recipecaches[''].pkg_pn 112 for pn in checkpns: 113 self.assertIn(pn, pkg_pn) 114 # Check pkg_fn 115 checkfns = {'nativesdk-gcc': '^virtual:nativesdk:.*', 'coreutils': '.*/coreutils_.*.bb'} 116 for fn, pn in tinfoil.cooker.recipecaches[''].pkg_fn.items(): 117 if pn in checkpns: 118 if pn in checkfns: 119 self.assertTrue(re.match(checkfns[pn], fn), 'Entry for %s: %s did not match %s' % (pn, fn, checkfns[pn])) 120 checkpns.remove(pn) 121 if checkpns: 122 self.fail('Unable to find pkg_fn entries for: %s' % ', '.join(checkpns)) 123 124 def test_wait_event(self): 125 with bb.tinfoil.Tinfoil() as tinfoil: 126 tinfoil.prepare(config_only=True) 127 128 tinfoil.set_event_mask(['bb.event.FilesMatchingFound', 'bb.command.CommandCompleted', 'bb.command.CommandFailed', 'bb.command.CommandExit']) 129 130 # Need to drain events otherwise events that were masked may still be in the queue 131 while tinfoil.wait_event(): 132 pass 133 134 pattern = 'conf' 135 res = tinfoil.run_command('testCookerCommandEvent', pattern, handle_events=False) 136 self.assertTrue(res) 137 138 eventreceived = False 139 commandcomplete = False 140 start = time.time() 141 # Wait for maximum 60s in total so we'd detect spurious heartbeat events for example 142 while (not (eventreceived == True and commandcomplete == True) 143 and (time.time() - start < 60)): 144 # if we received both events (on let's say a good day), we are done 145 event = tinfoil.wait_event(1) 146 if event: 147 if isinstance(event, bb.command.CommandCompleted): 148 commandcomplete = True 149 elif isinstance(event, bb.event.FilesMatchingFound): 150 self.assertEqual(pattern, event._pattern) 151 self.assertIn('A', event._matches) 152 self.assertIn('B', event._matches) 153 eventreceived = True 154 elif isinstance(event, logging.LogRecord): 155 continue 156 else: 157 self.fail('Unexpected event: %s' % event) 158 159 self.assertTrue(commandcomplete, 'Timed out waiting for CommandCompleted event from bitbake server (Matching event received: %s)' % str(eventreceived)) 160 self.assertTrue(eventreceived, 'Did not receive FilesMatchingFound event from bitbake server') 161 162 def test_setvariable_clean(self): 163 # First check that setVariable affects the datastore 164 with bb.tinfoil.Tinfoil() as tinfoil: 165 tinfoil.prepare(config_only=True) 166 tinfoil.run_command('setVariable', 'TESTVAR', 'specialvalue') 167 self.assertEqual(tinfoil.config_data.getVar('TESTVAR'), 'specialvalue', 'Value set using setVariable is not reflected in client-side getVar()') 168 169 # Now check that the setVariable's effects are no longer present 170 # (this may legitimately break in future if we stop reinitialising 171 # the datastore, in which case we'll have to reconsider use of 172 # setVariable entirely) 173 with bb.tinfoil.Tinfoil() as tinfoil: 174 tinfoil.prepare(config_only=True) 175 self.assertNotEqual(tinfoil.config_data.getVar('TESTVAR'), 'specialvalue', 'Value set using setVariable is still present!') 176 177 # Now check that setVar on the main datastore works (uses setVariable internally) 178 with bb.tinfoil.Tinfoil() as tinfoil: 179 tinfoil.prepare(config_only=True) 180 tinfoil.config_data.setVar('TESTVAR', 'specialvalue') 181 value = tinfoil.run_command('getVariable', 'TESTVAR') 182 self.assertEqual(value, 'specialvalue', 'Value set using config_data.setVar() is not reflected in config_data.getVar()') 183 184 def test_datastore_operations(self): 185 with bb.tinfoil.Tinfoil() as tinfoil: 186 tinfoil.prepare(config_only=True) 187 # Test setVarFlag() / getVarFlag() 188 tinfoil.config_data.setVarFlag('TESTVAR', 'flagname', 'flagval') 189 value = tinfoil.config_data.getVarFlag('TESTVAR', 'flagname') 190 self.assertEqual(value, 'flagval', 'Value set using config_data.setVarFlag() is not reflected in config_data.getVarFlag()') 191 # Test delVarFlag() 192 tinfoil.config_data.setVarFlag('TESTVAR', 'otherflag', 'othervalue') 193 tinfoil.config_data.delVarFlag('TESTVAR', 'flagname') 194 value = tinfoil.config_data.getVarFlag('TESTVAR', 'flagname') 195 self.assertEqual(value, None, 'Varflag deleted using config_data.delVarFlag() is not reflected in config_data.getVarFlag()') 196 value = tinfoil.config_data.getVarFlag('TESTVAR', 'otherflag') 197 self.assertEqual(value, 'othervalue', 'Varflag deleted using config_data.delVarFlag() caused unrelated flag to be removed') 198 # Test delVar() 199 tinfoil.config_data.setVar('TESTVAR', 'varvalue') 200 value = tinfoil.config_data.getVar('TESTVAR') 201 self.assertEqual(value, 'varvalue', 'Value set using config_data.setVar() is not reflected in config_data.getVar()') 202 tinfoil.config_data.delVar('TESTVAR') 203 value = tinfoil.config_data.getVar('TESTVAR') 204 self.assertEqual(value, None, 'Variable deleted using config_data.delVar() appears to still have a value') 205 # Test renameVar() 206 tinfoil.config_data.setVar('TESTVAROLD', 'origvalue') 207 tinfoil.config_data.renameVar('TESTVAROLD', 'TESTVARNEW') 208 value = tinfoil.config_data.getVar('TESTVAROLD') 209 self.assertEqual(value, None, 'Variable renamed using config_data.renameVar() still seems to exist') 210 value = tinfoil.config_data.getVar('TESTVARNEW') 211 self.assertEqual(value, 'origvalue', 'Variable renamed using config_data.renameVar() does not appear with new name') 212 # Test overrides 213 tinfoil.config_data.setVar('TESTVAR', 'original') 214 tinfoil.config_data.setVar('TESTVAR:overrideone', 'one') 215 tinfoil.config_data.setVar('TESTVAR:overridetwo', 'two') 216 tinfoil.config_data.appendVar('OVERRIDES', ':overrideone') 217 value = tinfoil.config_data.getVar('TESTVAR') 218 self.assertEqual(value, 'one', 'Variable overrides not functioning correctly') 219 220 def test_variable_history(self): 221 # Basic test to ensure that variable history works when tracking=True 222 with bb.tinfoil.Tinfoil(tracking=True) as tinfoil: 223 tinfoil.prepare(config_only=False, quiet=2) 224 # Note that _tracking for any datastore we get will be 225 # false here, that's currently expected - so we can't check 226 # for that 227 history = tinfoil.config_data.varhistory.variable('DL_DIR') 228 for entry in history: 229 if entry['file'].endswith('/bitbake.conf'): 230 if entry['op'] in ['set', 'set?']: 231 break 232 else: 233 self.fail('Did not find history entry setting DL_DIR in bitbake.conf. History: %s' % history) 234 # Check it works for recipes as well 235 testrecipe = 'zlib' 236 rd = tinfoil.parse_recipe(testrecipe) 237 history = rd.varhistory.variable('LICENSE') 238 bbfound = -1 239 recipefound = -1 240 for i, entry in enumerate(history): 241 if entry['file'].endswith('/bitbake.conf'): 242 if entry['detail'] == 'INVALID' and entry['op'] in ['set', 'set?']: 243 bbfound = i 244 elif entry['file'].endswith('.bb'): 245 if entry['op'] == 'set': 246 recipefound = i 247 if bbfound == -1: 248 self.fail('Did not find history entry setting LICENSE in bitbake.conf parsing %s recipe. History: %s' % (testrecipe, history)) 249 if recipefound == -1: 250 self.fail('Did not find history entry setting LICENSE in %s recipe. History: %s' % (testrecipe, history)) 251 if bbfound > recipefound: 252 self.fail('History entry setting LICENSE in %s recipe and in bitbake.conf in wrong order. History: %s' % (testrecipe, history)) 253