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(r'\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