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