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