xref: /openbmc/openbmc/poky/bitbake/lib/bb/tests/utils.py (revision 96e4b4e121e0e2da1535d7d537d6a982a6ff5bc0)
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
133    def test_goh1(self):
134        import hashlib
135        with tempfile.NamedTemporaryFile() as f:
136            f.write(self.filler)
137            f.flush()
138            checksum = bb.utils.goh1_file(f.name)
139            self.assertEqual(checksum, "81191f04d4abf413e5badd234814e4202d9efa73e6f9437e9ddd6b8165b569ef")
140
141class EditMetadataFile(unittest.TestCase):
142    _origfile = """
143# A comment
144HELLO = "oldvalue"
145
146THIS = "that"
147
148# Another comment
149NOCHANGE = "samevalue"
150OTHER = 'anothervalue'
151
152MULTILINE = "a1 \\
153             a2 \\
154             a3"
155
156MULTILINE2 := " \\
157               b1 \\
158               b2 \\
159               b3 \\
160               "
161
162
163MULTILINE3 = " \\
164              c1 \\
165              c2 \\
166              c3 \\
167"
168
169do_functionname() {
170    command1 ${VAL1} ${VAL2}
171    command2 ${VAL3} ${VAL4}
172}
173"""
174    def _testeditfile(self, varvalues, compareto, dummyvars=None):
175        if dummyvars is None:
176            dummyvars = []
177        with tempfile.NamedTemporaryFile('w', delete=False) as tf:
178            tf.write(self._origfile)
179            tf.close()
180            try:
181                varcalls = []
182                def handle_file(varname, origvalue, op, newlines):
183                    self.assertIn(varname, varvalues, 'Callback called for variable %s not in the list!' % varname)
184                    self.assertNotIn(varname, dummyvars, 'Callback called for variable %s in dummy list!' % varname)
185                    varcalls.append(varname)
186                    return varvalues[varname]
187
188                bb.utils.edit_metadata_file(tf.name, varvalues.keys(), handle_file)
189                with open(tf.name) as f:
190                    modfile = f.readlines()
191                # Ensure the output matches the expected output
192                self.assertEqual(compareto.splitlines(True), modfile)
193                # Ensure the callback function was called for every variable we asked for
194                # (plus allow testing behaviour when a requested variable is not present)
195                self.assertEqual(sorted(varvalues.keys()), sorted(varcalls + dummyvars))
196            finally:
197                os.remove(tf.name)
198
199
200    def test_edit_metadata_file_nochange(self):
201        # Test file doesn't get modified with nothing to do
202        self._testeditfile({}, self._origfile)
203        # Test file doesn't get modified with only dummy variables
204        self._testeditfile({'DUMMY1': ('should_not_set', None, 0, True),
205                        'DUMMY2': ('should_not_set_again', None, 0, True)}, self._origfile, dummyvars=['DUMMY1', 'DUMMY2'])
206        # Test file doesn't get modified with some the same values
207        self._testeditfile({'THIS': ('that', None, 0, True),
208                        'OTHER': ('anothervalue', None, 0, True),
209                        'MULTILINE3': ('               c1               c2               c3 ', None, 4, False)}, self._origfile)
210
211    def test_edit_metadata_file_1(self):
212
213        newfile1 = """
214# A comment
215HELLO = "newvalue"
216
217THIS = "that"
218
219# Another comment
220NOCHANGE = "samevalue"
221OTHER = 'anothervalue'
222
223MULTILINE = "a1 \\
224             a2 \\
225             a3"
226
227MULTILINE2 := " \\
228               b1 \\
229               b2 \\
230               b3 \\
231               "
232
233
234MULTILINE3 = " \\
235              c1 \\
236              c2 \\
237              c3 \\
238"
239
240do_functionname() {
241    command1 ${VAL1} ${VAL2}
242    command2 ${VAL3} ${VAL4}
243}
244"""
245        self._testeditfile({'HELLO': ('newvalue', None, 4, True)}, newfile1)
246
247
248    def test_edit_metadata_file_2(self):
249
250        newfile2 = """
251# A comment
252HELLO = "oldvalue"
253
254THIS = "that"
255
256# Another comment
257NOCHANGE = "samevalue"
258OTHER = 'anothervalue'
259
260MULTILINE = " \\
261    d1 \\
262    d2 \\
263    d3 \\
264    "
265
266MULTILINE2 := " \\
267               b1 \\
268               b2 \\
269               b3 \\
270               "
271
272
273MULTILINE3 = "nowsingle"
274
275do_functionname() {
276    command1 ${VAL1} ${VAL2}
277    command2 ${VAL3} ${VAL4}
278}
279"""
280        self._testeditfile({'MULTILINE': (['d1','d2','d3'], None, 4, False),
281                        'MULTILINE3': ('nowsingle', None, 4, True),
282                        'NOTPRESENT': (['a', 'b'], None, 4, False)}, newfile2, dummyvars=['NOTPRESENT'])
283
284
285    def test_edit_metadata_file_3(self):
286
287        newfile3 = """
288# A comment
289HELLO = "oldvalue"
290
291# Another comment
292NOCHANGE = "samevalue"
293OTHER = "yetanothervalue"
294
295MULTILINE = "e1 \\
296             e2 \\
297             e3 \\
298             "
299
300MULTILINE2 := "f1 \\
301\tf2 \\
302\t"
303
304
305MULTILINE3 = " \\
306              c1 \\
307              c2 \\
308              c3 \\
309"
310
311do_functionname() {
312    othercommand_one a b c
313    othercommand_two d e f
314}
315"""
316
317        self._testeditfile({'do_functionname()': (['othercommand_one a b c', 'othercommand_two d e f'], None, 4, False),
318                        'MULTILINE2': (['f1', 'f2'], None, '\t', True),
319                        'MULTILINE': (['e1', 'e2', 'e3'], None, -1, True),
320                        'THIS': (None, None, 0, False),
321                        'OTHER': ('yetanothervalue', None, 0, True)}, newfile3)
322
323
324    def test_edit_metadata_file_4(self):
325
326        newfile4 = """
327# A comment
328HELLO = "oldvalue"
329
330THIS = "that"
331
332# Another comment
333OTHER = 'anothervalue'
334
335MULTILINE = "a1 \\
336             a2 \\
337             a3"
338
339MULTILINE2 := " \\
340               b1 \\
341               b2 \\
342               b3 \\
343               "
344
345
346"""
347
348        self._testeditfile({'NOCHANGE': (None, None, 0, False),
349                        'MULTILINE3': (None, None, 0, False),
350                        'THIS': ('that', None, 0, False),
351                        'do_functionname()': (None, None, 0, False)}, newfile4)
352
353
354    def test_edit_metadata(self):
355        newfile5 = """
356# A comment
357HELLO = "hithere"
358
359# A new comment
360THIS += "that"
361
362# Another comment
363NOCHANGE = "samevalue"
364OTHER = 'anothervalue'
365
366MULTILINE = "a1 \\
367             a2 \\
368             a3"
369
370MULTILINE2 := " \\
371               b1 \\
372               b2 \\
373               b3 \\
374               "
375
376
377MULTILINE3 = " \\
378              c1 \\
379              c2 \\
380              c3 \\
381"
382
383NEWVAR = "value"
384
385do_functionname() {
386    command1 ${VAL1} ${VAL2}
387    command2 ${VAL3} ${VAL4}
388}
389"""
390
391
392        def handle_var(varname, origvalue, op, newlines):
393            if varname == 'THIS':
394                newlines.append('# A new comment\n')
395            elif varname == 'do_functionname()':
396                newlines.append('NEWVAR = "value"\n')
397                newlines.append('\n')
398            valueitem = varvalues.get(varname, None)
399            if valueitem:
400                return valueitem
401            else:
402                return (origvalue, op, 0, True)
403
404        varvalues = {'HELLO': ('hithere', None, 0, True), 'THIS': ('that', '+=', 0, True)}
405        varlist = ['HELLO', 'THIS', 'do_functionname()']
406        (updated, newlines) = bb.utils.edit_metadata(self._origfile.splitlines(True), varlist, handle_var)
407        self.assertTrue(updated, 'List should be updated but isn\'t')
408        self.assertEqual(newlines, newfile5.splitlines(True))
409
410    # Make sure the orig value matches what we expect it to be
411    def test_edit_metadata_origvalue(self):
412        origfile = """
413MULTILINE = "  stuff \\
414    morestuff"
415"""
416        expected_value = "stuff morestuff"
417        global value_in_callback
418        value_in_callback = ""
419
420        def handle_var(varname, origvalue, op, newlines):
421            global value_in_callback
422            value_in_callback = origvalue
423            return (origvalue, op, -1, False)
424
425        bb.utils.edit_metadata(origfile.splitlines(True),
426                               ['MULTILINE'],
427                               handle_var)
428
429        testvalue = re.sub(r'\s+', ' ', value_in_callback.strip())
430        self.assertEqual(expected_value, testvalue)
431
432class EditBbLayersConf(unittest.TestCase):
433
434    def _test_bblayers_edit(self, before, after, add, remove, notadded, notremoved):
435        with tempfile.NamedTemporaryFile('w', delete=False) as tf:
436            tf.write(before)
437            tf.close()
438            try:
439                actual_notadded, actual_notremoved = bb.utils.edit_bblayers_conf(tf.name, add, remove)
440                with open(tf.name) as f:
441                    actual_after = f.readlines()
442                self.assertEqual(after.splitlines(True), actual_after)
443                self.assertEqual(notadded, actual_notadded)
444                self.assertEqual(notremoved, actual_notremoved)
445            finally:
446                os.remove(tf.name)
447
448
449    def test_bblayers_remove(self):
450        before = r"""
451# A comment
452
453BBPATH = "${TOPDIR}"
454BBFILES ?= ""
455BBLAYERS = " \
456  /home/user/path/layer1 \
457  /home/user/path/layer2 \
458  /home/user/path/subpath/layer3 \
459  /home/user/path/layer4 \
460  "
461"""
462        after = r"""
463# A comment
464
465BBPATH = "${TOPDIR}"
466BBFILES ?= ""
467BBLAYERS = " \
468  /home/user/path/layer1 \
469  /home/user/path/subpath/layer3 \
470  /home/user/path/layer4 \
471  "
472"""
473        self._test_bblayers_edit(before, after,
474                                 None,
475                                 '/home/user/path/layer2',
476                                 [],
477                                 [])
478
479
480    def test_bblayers_add(self):
481        before = r"""
482# A comment
483
484BBPATH = "${TOPDIR}"
485BBFILES ?= ""
486BBLAYERS = " \
487  /home/user/path/layer1 \
488  /home/user/path/layer2 \
489  /home/user/path/subpath/layer3 \
490  /home/user/path/layer4 \
491  "
492"""
493        after = r"""
494# A comment
495
496BBPATH = "${TOPDIR}"
497BBFILES ?= ""
498BBLAYERS = " \
499  /home/user/path/layer1 \
500  /home/user/path/layer2 \
501  /home/user/path/subpath/layer3 \
502  /home/user/path/layer4 \
503  /other/path/to/layer5 \
504  "
505"""
506        self._test_bblayers_edit(before, after,
507                                 '/other/path/to/layer5/',
508                                 None,
509                                 [],
510                                 [])
511
512
513    def test_bblayers_add_remove(self):
514        before = r"""
515# A comment
516
517BBPATH = "${TOPDIR}"
518BBFILES ?= ""
519BBLAYERS = " \
520  /home/user/path/layer1 \
521  /home/user/path/layer2 \
522  /home/user/path/subpath/layer3 \
523  /home/user/path/layer4 \
524  "
525"""
526        after = r"""
527# A comment
528
529BBPATH = "${TOPDIR}"
530BBFILES ?= ""
531BBLAYERS = " \
532  /home/user/path/layer1 \
533  /home/user/path/layer2 \
534  /home/user/path/layer4 \
535  /other/path/to/layer5 \
536  "
537"""
538        self._test_bblayers_edit(before, after,
539                                 ['/other/path/to/layer5', '/home/user/path/layer2/'], '/home/user/path/subpath/layer3/',
540                                 ['/home/user/path/layer2'],
541                                 [])
542
543
544    def test_bblayers_add_remove_home(self):
545        before = r"""
546# A comment
547
548BBPATH = "${TOPDIR}"
549BBFILES ?= ""
550BBLAYERS = " \
551  ~/path/layer1 \
552  ~/path/layer2 \
553  ~/otherpath/layer3 \
554  ~/path/layer4 \
555  "
556"""
557        after = r"""
558# A comment
559
560BBPATH = "${TOPDIR}"
561BBFILES ?= ""
562BBLAYERS = " \
563  ~/path/layer2 \
564  ~/path/layer4 \
565  ~/path2/layer5 \
566  "
567"""
568        self._test_bblayers_edit(before, after,
569                                 [os.environ['HOME'] + '/path/layer4', '~/path2/layer5'],
570                                 [os.environ['HOME'] + '/otherpath/layer3', '~/path/layer1', '~/path/notinlist'],
571                                 [os.environ['HOME'] + '/path/layer4'],
572                                 ['~/path/notinlist'])
573
574
575    def test_bblayers_add_remove_plusequals(self):
576        before = r"""
577# A comment
578
579BBPATH = "${TOPDIR}"
580BBFILES ?= ""
581BBLAYERS += " \
582  /home/user/path/layer1 \
583  /home/user/path/layer2 \
584  "
585"""
586        after = r"""
587# A comment
588
589BBPATH = "${TOPDIR}"
590BBFILES ?= ""
591BBLAYERS += " \
592  /home/user/path/layer2 \
593  /home/user/path/layer3 \
594  "
595"""
596        self._test_bblayers_edit(before, after,
597                                 '/home/user/path/layer3',
598                                 '/home/user/path/layer1',
599                                 [],
600                                 [])
601
602
603    def test_bblayers_add_remove_plusequals2(self):
604        before = r"""
605# A comment
606
607BBPATH = "${TOPDIR}"
608BBFILES ?= ""
609BBLAYERS += " \
610  /home/user/path/layer1 \
611  /home/user/path/layer2 \
612  /home/user/path/layer3 \
613  "
614BBLAYERS += "/home/user/path/layer4"
615BBLAYERS += "/home/user/path/layer5"
616"""
617        after = r"""
618# A comment
619
620BBPATH = "${TOPDIR}"
621BBFILES ?= ""
622BBLAYERS += " \
623  /home/user/path/layer2 \
624  /home/user/path/layer3 \
625  "
626BBLAYERS += "/home/user/path/layer5"
627BBLAYERS += "/home/user/otherpath/layer6"
628"""
629        self._test_bblayers_edit(before, after,
630                                 ['/home/user/otherpath/layer6', '/home/user/path/layer3'], ['/home/user/path/layer1', '/home/user/path/layer4', '/home/user/path/layer7'],
631                                 ['/home/user/path/layer3'],
632                                 ['/home/user/path/layer7'])
633
634
635class GetReferencedVars(unittest.TestCase):
636    def setUp(self):
637        self.d = bb.data.init()
638
639    def check_referenced(self, expression, expected_layers):
640        vars = bb.utils.get_referenced_vars(expression, self.d)
641
642        # Do the easy check first - is every variable accounted for?
643        expected_vars = set.union(set(), *expected_layers)
644        got_vars = set(vars)
645        self.assertSetEqual(got_vars, expected_vars)
646
647        # Now test the order of the layers
648        start = 0
649        for i, expected_layer in enumerate(expected_layers):
650            got_layer = set(vars[start:len(expected_layer)+start])
651            start += len(expected_layer)
652            self.assertSetEqual(got_layer, expected_layer)
653
654    def test_no_vars(self):
655        self.check_referenced("", [])
656        self.check_referenced(" ", [])
657        self.check_referenced(" no vars here! ", [])
658
659    def test_single_layer(self):
660        self.check_referenced("${VAR}", [{"VAR"}])
661        self.check_referenced("${VAR} ${VAR}", [{"VAR"}])
662
663    def test_two_layer(self):
664        self.d.setVar("VAR", "${B}")
665        self.check_referenced("${VAR}", [{"VAR"}, {"B"}])
666        self.check_referenced("${@d.getVar('VAR')}", [{"VAR"}, {"B"}])
667
668    def test_more_complicated(self):
669        self.d["SRC_URI"] = "${QT_GIT}/${QT_MODULE}.git;name=${QT_MODULE};${QT_MODULE_BRANCH_PARAM};protocol=${QT_GIT_PROTOCOL}"
670        self.d["QT_GIT"] = "git://code.qt.io/${QT_GIT_PROJECT}"
671        self.d["QT_MODULE_BRANCH_PARAM"] = "branch=${QT_MODULE_BRANCH}"
672        self.d["QT_MODULE"] = "${BPN}"
673        self.d["BPN"] = "something to do with ${PN} and ${SPECIAL_PKGSUFFIX}"
674
675        layers = [{"SRC_URI"}, {"QT_GIT", "QT_MODULE", "QT_MODULE_BRANCH_PARAM", "QT_GIT_PROTOCOL"}, {"QT_GIT_PROJECT", "QT_MODULE_BRANCH", "BPN"}, {"PN", "SPECIAL_PKGSUFFIX"}]
676        self.check_referenced("${SRC_URI}", layers)
677
678
679class EnvironmentTests(unittest.TestCase):
680    def test_environment(self):
681        os.environ["A"] = "this is A"
682        self.assertIn("A", os.environ)
683        self.assertEqual(os.environ["A"], "this is A")
684        self.assertNotIn("B", os.environ)
685
686        with bb.utils.environment(B="this is B"):
687            self.assertIn("A", os.environ)
688            self.assertEqual(os.environ["A"], "this is A")
689            self.assertIn("B", os.environ)
690            self.assertEqual(os.environ["B"], "this is B")
691
692        self.assertIn("A", os.environ)
693        self.assertEqual(os.environ["A"], "this is A")
694        self.assertNotIn("B", os.environ)
695