xref: /openbmc/openbmc/poky/bitbake/lib/bb/tests/utils.py (revision d159c7fb)
1#
2# BitBake Tests for utils.py
3#
4# Copyright (C) 2012 Richard Purdie
5#
6# SPDX-License-Identifier: GPL-2.0-only
7#
8
9import unittest
10import bb
11import os
12import tempfile
13import re
14
15class VerCmpString(unittest.TestCase):
16
17    def test_vercmpstring(self):
18        result = bb.utils.vercmp_string('1', '2')
19        self.assertTrue(result < 0)
20        result = bb.utils.vercmp_string('2', '1')
21        self.assertTrue(result > 0)
22        result = bb.utils.vercmp_string('1', '1.0')
23        self.assertTrue(result < 0)
24        result = bb.utils.vercmp_string('1', '1.1')
25        self.assertTrue(result < 0)
26        result = bb.utils.vercmp_string('1.1', '1_p2')
27        self.assertTrue(result < 0)
28        result = bb.utils.vercmp_string('1.0', '1.0+1.1-beta1')
29        self.assertTrue(result < 0)
30        result = bb.utils.vercmp_string('1.1', '1.0+1.1-beta1')
31        self.assertTrue(result > 0)
32        result = bb.utils.vercmp_string('1a', '1a1')
33        self.assertTrue(result < 0)
34        result = bb.utils.vercmp_string('1a1', '1a')
35        self.assertTrue(result > 0)
36        result = bb.utils.vercmp_string('1.', '1.1')
37        self.assertTrue(result < 0)
38        result = bb.utils.vercmp_string('1.1', '1.')
39        self.assertTrue(result > 0)
40
41    def test_explode_dep_versions(self):
42        correctresult = {"foo" : ["= 1.10"]}
43        result = bb.utils.explode_dep_versions2("foo (= 1.10)")
44        self.assertEqual(result, correctresult)
45        result = bb.utils.explode_dep_versions2("foo (=1.10)")
46        self.assertEqual(result, correctresult)
47        result = bb.utils.explode_dep_versions2("foo ( = 1.10)")
48        self.assertEqual(result, correctresult)
49        result = bb.utils.explode_dep_versions2("foo ( =1.10)")
50        self.assertEqual(result, correctresult)
51        result = bb.utils.explode_dep_versions2("foo ( = 1.10 )")
52        self.assertEqual(result, correctresult)
53        result = bb.utils.explode_dep_versions2("foo ( =1.10 )")
54        self.assertEqual(result, correctresult)
55
56    def test_vercmp_string_op(self):
57        compareops = [('1', '1', '=', True),
58                      ('1', '1', '==', True),
59                      ('1', '1', '!=', False),
60                      ('1', '1', '>', False),
61                      ('1', '1', '<', False),
62                      ('1', '1', '>=', True),
63                      ('1', '1', '<=', True),
64                      ('1', '0', '=', False),
65                      ('1', '0', '==', False),
66                      ('1', '0', '!=', True),
67                      ('1', '0', '>', True),
68                      ('1', '0', '<', False),
69                      ('1', '0', '>>', True),
70                      ('1', '0', '<<', False),
71                      ('1', '0', '>=', True),
72                      ('1', '0', '<=', False),
73                      ('0', '1', '=', False),
74                      ('0', '1', '==', False),
75                      ('0', '1', '!=', True),
76                      ('0', '1', '>', False),
77                      ('0', '1', '<', True),
78                      ('0', '1', '>>', False),
79                      ('0', '1', '<<', True),
80                      ('0', '1', '>=', False),
81                      ('0', '1', '<=', True)]
82
83        for arg1, arg2, op, correctresult in compareops:
84            result = bb.utils.vercmp_string_op(arg1, arg2, op)
85            self.assertEqual(result, correctresult, 'vercmp_string_op("%s", "%s", "%s") != %s' % (arg1, arg2, op, correctresult))
86
87        # Check that clearly invalid operator raises an exception
88        self.assertRaises(bb.utils.VersionStringException, bb.utils.vercmp_string_op, '0', '0', '$')
89
90
91class Path(unittest.TestCase):
92    def test_unsafe_delete_path(self):
93        checkitems = [('/', True),
94                      ('//', True),
95                      ('///', True),
96                      (os.getcwd().count(os.sep) * ('..' + os.sep), True),
97                      (os.environ.get('HOME', '/home/test'), True),
98                      ('/home/someone', True),
99                      ('/home/other/', True),
100                      ('/home/other/subdir', False),
101                      ('', False)]
102        for arg1, correctresult in checkitems:
103            result = bb.utils._check_unsafe_delete_path(arg1)
104            self.assertEqual(result, correctresult, '_check_unsafe_delete_path("%s") != %s' % (arg1, correctresult))
105
106class Checksum(unittest.TestCase):
107    filler = b"Shiver me timbers square-rigged spike Gold Road galleon bilge water boatswain wherry jack pirate. Mizzenmast rum lad Privateer jack salmagundi hang the jib piracy Pieces of Eight Corsair. Parrel marooned black spot yawl provost quarterdeck cable no prey, no pay spirits lateen sail."
108
109    def test_md5(self):
110        import hashlib
111        with tempfile.NamedTemporaryFile() as f:
112            f.write(self.filler)
113            f.flush()
114            checksum = bb.utils.md5_file(f.name)
115            self.assertEqual(checksum, "bd572cd5de30a785f4efcb6eaf5089e3")
116
117    def test_sha1(self):
118        import hashlib
119        with tempfile.NamedTemporaryFile() as f:
120            f.write(self.filler)
121            f.flush()
122            checksum = bb.utils.sha1_file(f.name)
123            self.assertEqual(checksum, "249eb8fd654732ea836d5e702d7aa567898eca71")
124
125    def test_sha256(self):
126        import hashlib
127        with tempfile.NamedTemporaryFile() as f:
128            f.write(self.filler)
129            f.flush()
130            checksum = bb.utils.sha256_file(f.name)
131            self.assertEqual(checksum, "fcfbae8bf6b721dbb9d2dc6a9334a58f2031a9a9b302999243f99da4d7f12d0f")
132
133class EditMetadataFile(unittest.TestCase):
134    _origfile = """
135# A comment
136HELLO = "oldvalue"
137
138THIS = "that"
139
140# Another comment
141NOCHANGE = "samevalue"
142OTHER = 'anothervalue'
143
144MULTILINE = "a1 \\
145             a2 \\
146             a3"
147
148MULTILINE2 := " \\
149               b1 \\
150               b2 \\
151               b3 \\
152               "
153
154
155MULTILINE3 = " \\
156              c1 \\
157              c2 \\
158              c3 \\
159"
160
161do_functionname() {
162    command1 ${VAL1} ${VAL2}
163    command2 ${VAL3} ${VAL4}
164}
165"""
166    def _testeditfile(self, varvalues, compareto, dummyvars=None):
167        if dummyvars is None:
168            dummyvars = []
169        with tempfile.NamedTemporaryFile('w', delete=False) as tf:
170            tf.write(self._origfile)
171            tf.close()
172            try:
173                varcalls = []
174                def handle_file(varname, origvalue, op, newlines):
175                    self.assertIn(varname, varvalues, 'Callback called for variable %s not in the list!' % varname)
176                    self.assertNotIn(varname, dummyvars, 'Callback called for variable %s in dummy list!' % varname)
177                    varcalls.append(varname)
178                    return varvalues[varname]
179
180                bb.utils.edit_metadata_file(tf.name, varvalues.keys(), handle_file)
181                with open(tf.name) as f:
182                    modfile = f.readlines()
183                # Ensure the output matches the expected output
184                self.assertEqual(compareto.splitlines(True), modfile)
185                # Ensure the callback function was called for every variable we asked for
186                # (plus allow testing behaviour when a requested variable is not present)
187                self.assertEqual(sorted(varvalues.keys()), sorted(varcalls + dummyvars))
188            finally:
189                os.remove(tf.name)
190
191
192    def test_edit_metadata_file_nochange(self):
193        # Test file doesn't get modified with nothing to do
194        self._testeditfile({}, self._origfile)
195        # Test file doesn't get modified with only dummy variables
196        self._testeditfile({'DUMMY1': ('should_not_set', None, 0, True),
197                        'DUMMY2': ('should_not_set_again', None, 0, True)}, self._origfile, dummyvars=['DUMMY1', 'DUMMY2'])
198        # Test file doesn't get modified with some the same values
199        self._testeditfile({'THIS': ('that', None, 0, True),
200                        'OTHER': ('anothervalue', None, 0, True),
201                        'MULTILINE3': ('               c1               c2               c3 ', None, 4, False)}, self._origfile)
202
203    def test_edit_metadata_file_1(self):
204
205        newfile1 = """
206# A comment
207HELLO = "newvalue"
208
209THIS = "that"
210
211# Another comment
212NOCHANGE = "samevalue"
213OTHER = 'anothervalue'
214
215MULTILINE = "a1 \\
216             a2 \\
217             a3"
218
219MULTILINE2 := " \\
220               b1 \\
221               b2 \\
222               b3 \\
223               "
224
225
226MULTILINE3 = " \\
227              c1 \\
228              c2 \\
229              c3 \\
230"
231
232do_functionname() {
233    command1 ${VAL1} ${VAL2}
234    command2 ${VAL3} ${VAL4}
235}
236"""
237        self._testeditfile({'HELLO': ('newvalue', None, 4, True)}, newfile1)
238
239
240    def test_edit_metadata_file_2(self):
241
242        newfile2 = """
243# A comment
244HELLO = "oldvalue"
245
246THIS = "that"
247
248# Another comment
249NOCHANGE = "samevalue"
250OTHER = 'anothervalue'
251
252MULTILINE = " \\
253    d1 \\
254    d2 \\
255    d3 \\
256    "
257
258MULTILINE2 := " \\
259               b1 \\
260               b2 \\
261               b3 \\
262               "
263
264
265MULTILINE3 = "nowsingle"
266
267do_functionname() {
268    command1 ${VAL1} ${VAL2}
269    command2 ${VAL3} ${VAL4}
270}
271"""
272        self._testeditfile({'MULTILINE': (['d1','d2','d3'], None, 4, False),
273                        'MULTILINE3': ('nowsingle', None, 4, True),
274                        'NOTPRESENT': (['a', 'b'], None, 4, False)}, newfile2, dummyvars=['NOTPRESENT'])
275
276
277    def test_edit_metadata_file_3(self):
278
279        newfile3 = """
280# A comment
281HELLO = "oldvalue"
282
283# Another comment
284NOCHANGE = "samevalue"
285OTHER = "yetanothervalue"
286
287MULTILINE = "e1 \\
288             e2 \\
289             e3 \\
290             "
291
292MULTILINE2 := "f1 \\
293\tf2 \\
294\t"
295
296
297MULTILINE3 = " \\
298              c1 \\
299              c2 \\
300              c3 \\
301"
302
303do_functionname() {
304    othercommand_one a b c
305    othercommand_two d e f
306}
307"""
308
309        self._testeditfile({'do_functionname()': (['othercommand_one a b c', 'othercommand_two d e f'], None, 4, False),
310                        'MULTILINE2': (['f1', 'f2'], None, '\t', True),
311                        'MULTILINE': (['e1', 'e2', 'e3'], None, -1, True),
312                        'THIS': (None, None, 0, False),
313                        'OTHER': ('yetanothervalue', None, 0, True)}, newfile3)
314
315
316    def test_edit_metadata_file_4(self):
317
318        newfile4 = """
319# A comment
320HELLO = "oldvalue"
321
322THIS = "that"
323
324# Another comment
325OTHER = 'anothervalue'
326
327MULTILINE = "a1 \\
328             a2 \\
329             a3"
330
331MULTILINE2 := " \\
332               b1 \\
333               b2 \\
334               b3 \\
335               "
336
337
338"""
339
340        self._testeditfile({'NOCHANGE': (None, None, 0, False),
341                        'MULTILINE3': (None, None, 0, False),
342                        'THIS': ('that', None, 0, False),
343                        'do_functionname()': (None, None, 0, False)}, newfile4)
344
345
346    def test_edit_metadata(self):
347        newfile5 = """
348# A comment
349HELLO = "hithere"
350
351# A new comment
352THIS += "that"
353
354# Another comment
355NOCHANGE = "samevalue"
356OTHER = 'anothervalue'
357
358MULTILINE = "a1 \\
359             a2 \\
360             a3"
361
362MULTILINE2 := " \\
363               b1 \\
364               b2 \\
365               b3 \\
366               "
367
368
369MULTILINE3 = " \\
370              c1 \\
371              c2 \\
372              c3 \\
373"
374
375NEWVAR = "value"
376
377do_functionname() {
378    command1 ${VAL1} ${VAL2}
379    command2 ${VAL3} ${VAL4}
380}
381"""
382
383
384        def handle_var(varname, origvalue, op, newlines):
385            if varname == 'THIS':
386                newlines.append('# A new comment\n')
387            elif varname == 'do_functionname()':
388                newlines.append('NEWVAR = "value"\n')
389                newlines.append('\n')
390            valueitem = varvalues.get(varname, None)
391            if valueitem:
392                return valueitem
393            else:
394                return (origvalue, op, 0, True)
395
396        varvalues = {'HELLO': ('hithere', None, 0, True), 'THIS': ('that', '+=', 0, True)}
397        varlist = ['HELLO', 'THIS', 'do_functionname()']
398        (updated, newlines) = bb.utils.edit_metadata(self._origfile.splitlines(True), varlist, handle_var)
399        self.assertTrue(updated, 'List should be updated but isn\'t')
400        self.assertEqual(newlines, newfile5.splitlines(True))
401
402    # Make sure the orig value matches what we expect it to be
403    def test_edit_metadata_origvalue(self):
404        origfile = """
405MULTILINE = "  stuff \\
406    morestuff"
407"""
408        expected_value = "stuff morestuff"
409        global value_in_callback
410        value_in_callback = ""
411
412        def handle_var(varname, origvalue, op, newlines):
413            global value_in_callback
414            value_in_callback = origvalue
415            return (origvalue, op, -1, False)
416
417        bb.utils.edit_metadata(origfile.splitlines(True),
418                               ['MULTILINE'],
419                               handle_var)
420
421        testvalue = re.sub('\s+', ' ', value_in_callback.strip())
422        self.assertEqual(expected_value, testvalue)
423
424class EditBbLayersConf(unittest.TestCase):
425
426    def _test_bblayers_edit(self, before, after, add, remove, notadded, notremoved):
427        with tempfile.NamedTemporaryFile('w', delete=False) as tf:
428            tf.write(before)
429            tf.close()
430            try:
431                actual_notadded, actual_notremoved = bb.utils.edit_bblayers_conf(tf.name, add, remove)
432                with open(tf.name) as f:
433                    actual_after = f.readlines()
434                self.assertEqual(after.splitlines(True), actual_after)
435                self.assertEqual(notadded, actual_notadded)
436                self.assertEqual(notremoved, actual_notremoved)
437            finally:
438                os.remove(tf.name)
439
440
441    def test_bblayers_remove(self):
442        before = r"""
443# A comment
444
445BBPATH = "${TOPDIR}"
446BBFILES ?= ""
447BBLAYERS = " \
448  /home/user/path/layer1 \
449  /home/user/path/layer2 \
450  /home/user/path/subpath/layer3 \
451  /home/user/path/layer4 \
452  "
453"""
454        after = r"""
455# A comment
456
457BBPATH = "${TOPDIR}"
458BBFILES ?= ""
459BBLAYERS = " \
460  /home/user/path/layer1 \
461  /home/user/path/subpath/layer3 \
462  /home/user/path/layer4 \
463  "
464"""
465        self._test_bblayers_edit(before, after,
466                                 None,
467                                 '/home/user/path/layer2',
468                                 [],
469                                 [])
470
471
472    def test_bblayers_add(self):
473        before = r"""
474# A comment
475
476BBPATH = "${TOPDIR}"
477BBFILES ?= ""
478BBLAYERS = " \
479  /home/user/path/layer1 \
480  /home/user/path/layer2 \
481  /home/user/path/subpath/layer3 \
482  /home/user/path/layer4 \
483  "
484"""
485        after = r"""
486# A comment
487
488BBPATH = "${TOPDIR}"
489BBFILES ?= ""
490BBLAYERS = " \
491  /home/user/path/layer1 \
492  /home/user/path/layer2 \
493  /home/user/path/subpath/layer3 \
494  /home/user/path/layer4 \
495  /other/path/to/layer5 \
496  "
497"""
498        self._test_bblayers_edit(before, after,
499                                 '/other/path/to/layer5/',
500                                 None,
501                                 [],
502                                 [])
503
504
505    def test_bblayers_add_remove(self):
506        before = r"""
507# A comment
508
509BBPATH = "${TOPDIR}"
510BBFILES ?= ""
511BBLAYERS = " \
512  /home/user/path/layer1 \
513  /home/user/path/layer2 \
514  /home/user/path/subpath/layer3 \
515  /home/user/path/layer4 \
516  "
517"""
518        after = r"""
519# A comment
520
521BBPATH = "${TOPDIR}"
522BBFILES ?= ""
523BBLAYERS = " \
524  /home/user/path/layer1 \
525  /home/user/path/layer2 \
526  /home/user/path/layer4 \
527  /other/path/to/layer5 \
528  "
529"""
530        self._test_bblayers_edit(before, after,
531                                 ['/other/path/to/layer5', '/home/user/path/layer2/'], '/home/user/path/subpath/layer3/',
532                                 ['/home/user/path/layer2'],
533                                 [])
534
535
536    def test_bblayers_add_remove_home(self):
537        before = r"""
538# A comment
539
540BBPATH = "${TOPDIR}"
541BBFILES ?= ""
542BBLAYERS = " \
543  ~/path/layer1 \
544  ~/path/layer2 \
545  ~/otherpath/layer3 \
546  ~/path/layer4 \
547  "
548"""
549        after = r"""
550# A comment
551
552BBPATH = "${TOPDIR}"
553BBFILES ?= ""
554BBLAYERS = " \
555  ~/path/layer2 \
556  ~/path/layer4 \
557  ~/path2/layer5 \
558  "
559"""
560        self._test_bblayers_edit(before, after,
561                                 [os.environ['HOME'] + '/path/layer4', '~/path2/layer5'],
562                                 [os.environ['HOME'] + '/otherpath/layer3', '~/path/layer1', '~/path/notinlist'],
563                                 [os.environ['HOME'] + '/path/layer4'],
564                                 ['~/path/notinlist'])
565
566
567    def test_bblayers_add_remove_plusequals(self):
568        before = r"""
569# A comment
570
571BBPATH = "${TOPDIR}"
572BBFILES ?= ""
573BBLAYERS += " \
574  /home/user/path/layer1 \
575  /home/user/path/layer2 \
576  "
577"""
578        after = r"""
579# A comment
580
581BBPATH = "${TOPDIR}"
582BBFILES ?= ""
583BBLAYERS += " \
584  /home/user/path/layer2 \
585  /home/user/path/layer3 \
586  "
587"""
588        self._test_bblayers_edit(before, after,
589                                 '/home/user/path/layer3',
590                                 '/home/user/path/layer1',
591                                 [],
592                                 [])
593
594
595    def test_bblayers_add_remove_plusequals2(self):
596        before = r"""
597# A comment
598
599BBPATH = "${TOPDIR}"
600BBFILES ?= ""
601BBLAYERS += " \
602  /home/user/path/layer1 \
603  /home/user/path/layer2 \
604  /home/user/path/layer3 \
605  "
606BBLAYERS += "/home/user/path/layer4"
607BBLAYERS += "/home/user/path/layer5"
608"""
609        after = r"""
610# A comment
611
612BBPATH = "${TOPDIR}"
613BBFILES ?= ""
614BBLAYERS += " \
615  /home/user/path/layer2 \
616  /home/user/path/layer3 \
617  "
618BBLAYERS += "/home/user/path/layer5"
619BBLAYERS += "/home/user/otherpath/layer6"
620"""
621        self._test_bblayers_edit(before, after,
622                                 ['/home/user/otherpath/layer6', '/home/user/path/layer3'], ['/home/user/path/layer1', '/home/user/path/layer4', '/home/user/path/layer7'],
623                                 ['/home/user/path/layer3'],
624                                 ['/home/user/path/layer7'])
625
626
627class GetReferencedVars(unittest.TestCase):
628    def setUp(self):
629        self.d = bb.data.init()
630
631    def check_referenced(self, expression, expected_layers):
632        vars = bb.utils.get_referenced_vars(expression, self.d)
633
634        # Do the easy check first - is every variable accounted for?
635        expected_vars = set.union(set(), *expected_layers)
636        got_vars = set(vars)
637        self.assertSetEqual(got_vars, expected_vars)
638
639        # Now test the order of the layers
640        start = 0
641        for i, expected_layer in enumerate(expected_layers):
642            got_layer = set(vars[start:len(expected_layer)+start])
643            start += len(expected_layer)
644            self.assertSetEqual(got_layer, expected_layer)
645
646    def test_no_vars(self):
647        self.check_referenced("", [])
648        self.check_referenced(" ", [])
649        self.check_referenced(" no vars here! ", [])
650
651    def test_single_layer(self):
652        self.check_referenced("${VAR}", [{"VAR"}])
653        self.check_referenced("${VAR} ${VAR}", [{"VAR"}])
654
655    def test_two_layer(self):
656        self.d.setVar("VAR", "${B}")
657        self.check_referenced("${VAR}", [{"VAR"}, {"B"}])
658        self.check_referenced("${@d.getVar('VAR')}", [{"VAR"}, {"B"}])
659
660    def test_more_complicated(self):
661        self.d["SRC_URI"] = "${QT_GIT}/${QT_MODULE}.git;name=${QT_MODULE};${QT_MODULE_BRANCH_PARAM};protocol=${QT_GIT_PROTOCOL}"
662        self.d["QT_GIT"] = "git://code.qt.io/${QT_GIT_PROJECT}"
663        self.d["QT_MODULE_BRANCH_PARAM"] = "branch=${QT_MODULE_BRANCH}"
664        self.d["QT_MODULE"] = "${BPN}"
665        self.d["BPN"] = "something to do with ${PN} and ${SPECIAL_PKGSUFFIX}"
666
667        layers = [{"SRC_URI"}, {"QT_GIT", "QT_MODULE", "QT_MODULE_BRANCH_PARAM", "QT_GIT_PROTOCOL"}, {"QT_GIT_PROJECT", "QT_MODULE_BRANCH", "BPN"}, {"PN", "SPECIAL_PKGSUFFIX"}]
668        self.check_referenced("${SRC_URI}", layers)
669
670
671class EnvironmentTests(unittest.TestCase):
672    def test_environment(self):
673        os.environ["A"] = "this is A"
674        self.assertIn("A", os.environ)
675        self.assertEqual(os.environ["A"], "this is A")
676        self.assertNotIn("B", os.environ)
677
678        with bb.utils.environment(B="this is B"):
679            self.assertIn("A", os.environ)
680            self.assertEqual(os.environ["A"], "this is A")
681            self.assertIn("B", os.environ)
682            self.assertEqual(os.environ["B"], "this is B")
683
684        self.assertIn("A", os.environ)
685        self.assertEqual(os.environ["A"], "this is A")
686        self.assertNotIn("B", os.environ)
687