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