1# 2# BitBake Test for lib/bb/parse/ 3# 4# Copyright (C) 2015 Richard Purdie 5# 6# SPDX-License-Identifier: GPL-2.0-only 7# 8 9import unittest 10import tempfile 11import logging 12import bb 13import os 14 15logger = logging.getLogger('BitBake.TestParse') 16 17import bb.parse 18import bb.data 19import bb.siggen 20 21class ParseTest(unittest.TestCase): 22 23 testfile = """ 24A = "1" 25B = "2" 26do_install() { 27 echo "hello" 28} 29 30C = "3" 31""" 32 33 def setUp(self): 34 self.origdir = os.getcwd() 35 self.d = bb.data.init() 36 bb.parse.siggen = bb.siggen.init(self.d) 37 38 def tearDown(self): 39 os.chdir(self.origdir) 40 41 def parsehelper(self, content, suffix = ".bb"): 42 43 f = tempfile.NamedTemporaryFile(suffix = suffix) 44 f.write(bytes(content, "utf-8")) 45 f.flush() 46 os.chdir(os.path.dirname(f.name)) 47 return f 48 49 def test_parse_simple(self): 50 f = self.parsehelper(self.testfile) 51 d = bb.parse.handle(f.name, self.d)[''] 52 self.assertEqual(d.getVar("A"), "1") 53 self.assertEqual(d.getVar("B"), "2") 54 self.assertEqual(d.getVar("C"), "3") 55 56 def test_parse_incomplete_function(self): 57 testfileB = self.testfile.replace("}", "") 58 f = self.parsehelper(testfileB) 59 with self.assertRaises(bb.parse.ParseError): 60 d = bb.parse.handle(f.name, self.d)[''] 61 62 unsettest = """ 63A = "1" 64B = "2" 65B[flag] = "3" 66 67unset A 68unset B[flag] 69""" 70 71 def test_parse_unset(self): 72 f = self.parsehelper(self.unsettest) 73 d = bb.parse.handle(f.name, self.d)[''] 74 self.assertEqual(d.getVar("A"), None) 75 self.assertEqual(d.getVarFlag("A","flag"), None) 76 self.assertEqual(d.getVar("B"), "2") 77 78 defaulttest = """ 79A = "set value" 80A ??= "default value" 81 82A[flag_set_vs_question] = "set flag" 83A[flag_set_vs_question] ?= "question flag" 84 85A[flag_set_vs_default] = "set flag" 86A[flag_set_vs_default] ??= "default flag" 87 88A[flag_question] ?= "question flag" 89 90A[flag_default] ??= "default flag" 91 92A[flag_question_vs_default] ?= "question flag" 93A[flag_question_vs_default] ??= "default flag" 94 95A[flag_default_vs_question] ??= "default flag" 96A[flag_default_vs_question] ?= "question flag" 97 98A[flag_set_question_default] = "set flag" 99A[flag_set_question_default] ?= "question flag" 100A[flag_set_question_default] ??= "default flag" 101 102A[flag_set_default_question] = "set flag" 103A[flag_set_default_question] ??= "default flag" 104A[flag_set_default_question] ?= "question flag" 105 106A[flag_set_twice] = "set flag first" 107A[flag_set_twice] = "set flag second" 108 109A[flag_question_twice] ?= "question flag first" 110A[flag_question_twice] ?= "question flag second" 111 112A[flag_default_twice] ??= "default flag first" 113A[flag_default_twice] ??= "default flag second" 114""" 115 def test_parse_defaulttest(self): 116 f = self.parsehelper(self.defaulttest) 117 d = bb.parse.handle(f.name, self.d)[''] 118 self.assertEqual(d.getVar("A"), "set value") 119 self.assertEqual(d.getVarFlag("A","flag_set_vs_question"), "set flag") 120 self.assertEqual(d.getVarFlag("A","flag_set_vs_default"), "set flag") 121 self.assertEqual(d.getVarFlag("A","flag_question"), "question flag") 122 self.assertEqual(d.getVarFlag("A","flag_default"), "default flag") 123 self.assertEqual(d.getVarFlag("A","flag_question_vs_default"), "question flag") 124 self.assertEqual(d.getVarFlag("A","flag_default_vs_question"), "question flag") 125 self.assertEqual(d.getVarFlag("A","flag_set_question_default"), "set flag") 126 self.assertEqual(d.getVarFlag("A","flag_set_default_question"), "set flag") 127 self.assertEqual(d.getVarFlag("A","flag_set_twice"), "set flag second") 128 self.assertEqual(d.getVarFlag("A","flag_question_twice"), "question flag first") 129 self.assertEqual(d.getVarFlag("A","flag_default_twice"), "default flag second") 130 131 exporttest = """ 132A = "a" 133export B = "b" 134export C 135exportD = "d" 136""" 137 138 def test_parse_exports(self): 139 f = self.parsehelper(self.exporttest) 140 d = bb.parse.handle(f.name, self.d)[''] 141 self.assertEqual(d.getVar("A"), "a") 142 self.assertIsNone(d.getVarFlag("A", "export")) 143 self.assertEqual(d.getVar("B"), "b") 144 self.assertEqual(d.getVarFlag("B", "export"), 1) 145 self.assertIsNone(d.getVar("C")) 146 self.assertEqual(d.getVarFlag("C", "export"), 1) 147 self.assertIsNone(d.getVar("D")) 148 self.assertIsNone(d.getVarFlag("D", "export")) 149 self.assertEqual(d.getVar("exportD"), "d") 150 self.assertIsNone(d.getVarFlag("exportD", "export")) 151 152 153 overridetest = """ 154RRECOMMENDS:${PN} = "a" 155RRECOMMENDS:${PN}:libc = "b" 156OVERRIDES = "libc:${PN}" 157PN = "gtk+" 158""" 159 160 def test_parse_overrides(self): 161 f = self.parsehelper(self.overridetest) 162 d = bb.parse.handle(f.name, self.d)[''] 163 self.assertEqual(d.getVar("RRECOMMENDS"), "b") 164 bb.data.expandKeys(d) 165 self.assertEqual(d.getVar("RRECOMMENDS"), "b") 166 d.setVar("RRECOMMENDS:gtk+", "c") 167 self.assertEqual(d.getVar("RRECOMMENDS"), "c") 168 169 overridetest2 = """ 170EXTRA_OECONF = "" 171EXTRA_OECONF:class-target = "b" 172EXTRA_OECONF:append = " c" 173""" 174 175 def test_parse_overrides2(self): 176 f = self.parsehelper(self.overridetest2) 177 d = bb.parse.handle(f.name, self.d)[''] 178 d.appendVar("EXTRA_OECONF", " d") 179 d.setVar("OVERRIDES", "class-target") 180 self.assertEqual(d.getVar("EXTRA_OECONF"), "b c d") 181 182 overridetest3 = """ 183DESCRIPTION = "A" 184DESCRIPTION:${PN}-dev = "${DESCRIPTION} B" 185PN = "bc" 186""" 187 188 def test_parse_combinations(self): 189 f = self.parsehelper(self.overridetest3) 190 d = bb.parse.handle(f.name, self.d)[''] 191 bb.data.expandKeys(d) 192 self.assertEqual(d.getVar("DESCRIPTION:bc-dev"), "A B") 193 d.setVar("DESCRIPTION", "E") 194 d.setVar("DESCRIPTION:bc-dev", "C D") 195 d.setVar("OVERRIDES", "bc-dev") 196 self.assertEqual(d.getVar("DESCRIPTION"), "C D") 197 198 199 classextend = """ 200VAR_var:override1 = "B" 201EXTRA = ":override1" 202OVERRIDES = "nothing${EXTRA}" 203 204BBCLASSEXTEND = "###CLASS###" 205""" 206 classextend_bbclass = """ 207EXTRA = "" 208python () { 209 d.renameVar("VAR_var", "VAR_var2") 210} 211""" 212 213 # 214 # Test based upon a real world data corruption issue. One 215 # data store changing a variable poked through into a different data 216 # store. This test case replicates that issue where the value 'B' would 217 # become unset/disappear. 218 # 219 def test_parse_classextend_contamination(self): 220 self.d.setVar("__bbclasstype", "recipe") 221 cls = self.parsehelper(self.classextend_bbclass, suffix=".bbclass") 222 #clsname = os.path.basename(cls.name).replace(".bbclass", "") 223 self.classextend = self.classextend.replace("###CLASS###", cls.name) 224 f = self.parsehelper(self.classextend) 225 alldata = bb.parse.handle(f.name, self.d) 226 d1 = alldata[''] 227 d2 = alldata[cls.name] 228 self.assertEqual(d1.getVar("VAR_var"), "B") 229 self.assertEqual(d2.getVar("VAR_var"), None) 230 231 addtask_deltask = """ 232addtask do_patch after do_foo after do_unpack before do_configure before do_compile 233addtask do_fetch2 do_patch2 234 235addtask do_myplaintask 236addtask do_myplaintask2 237deltask do_myplaintask2 238addtask do_mytask# comment 239addtask do_mytask2 # comment2 240addtask do_mytask3 241deltask do_mytask3# comment 242deltask do_mytask4 # comment2 243 244# Ensure a missing task prefix on after works 245addtask do_mytask5 after mytask 246 247MYVAR = "do_patch" 248EMPTYVAR = "" 249deltask do_fetch ${MYVAR} ${EMPTYVAR} 250deltask ${EMPTYVAR} 251""" 252 def test_parse_addtask_deltask(self): 253 254 f = self.parsehelper(self.addtask_deltask) 255 d = bb.parse.handle(f.name, self.d)[''] 256 257 self.assertSequenceEqual(['do_fetch2', 'do_patch2', 'do_myplaintask', 'do_mytask', 'do_mytask2', 'do_mytask5'], bb.build.listtasks(d)) 258 self.assertEqual(['do_mytask'], d.getVarFlag("do_mytask5", "deps")) 259 260 broken_multiline_comment = """ 261# First line of comment \\ 262# Second line of comment \\ 263 264""" 265 def test_parse_broken_multiline_comment(self): 266 f = self.parsehelper(self.broken_multiline_comment) 267 with self.assertRaises(bb.BBHandledException): 268 d = bb.parse.handle(f.name, self.d)[''] 269 270 271 comment_in_var = """ 272VAR = " \\ 273 SOMEVAL \\ 274# some comment \\ 275 SOMEOTHERVAL \\ 276" 277""" 278 def test_parse_comment_in_var(self): 279 f = self.parsehelper(self.comment_in_var) 280 with self.assertRaises(bb.BBHandledException): 281 d = bb.parse.handle(f.name, self.d)[''] 282 283 284 at_sign_in_var_flag = """ 285A[flag@.service] = "nonet" 286B[flag@.target] = "ntb" 287C[f] = "flag" 288 289unset A[flag@.service] 290""" 291 def test_parse_at_sign_in_var_flag(self): 292 f = self.parsehelper(self.at_sign_in_var_flag) 293 d = bb.parse.handle(f.name, self.d)[''] 294 self.assertEqual(d.getVar("A"), None) 295 self.assertEqual(d.getVar("B"), None) 296 self.assertEqual(d.getVarFlag("A","flag@.service"), None) 297 self.assertEqual(d.getVarFlag("B","flag@.target"), "ntb") 298 self.assertEqual(d.getVarFlag("C","f"), "flag") 299 300 def test_parse_invalid_at_sign_in_var_flag(self): 301 invalid_at_sign = self.at_sign_in_var_flag.replace("B[f", "B[@f") 302 f = self.parsehelper(invalid_at_sign) 303 with self.assertRaises(bb.parse.ParseError): 304 d = bb.parse.handle(f.name, self.d)[''] 305 306 export_function_recipe = """ 307inherit someclass 308""" 309 310 export_function_recipe2 = """ 311inherit someclass 312 313do_compile () { 314 false 315} 316 317python do_compilepython () { 318 bb.note("Something else") 319} 320 321""" 322 export_function_class = """ 323someclass_do_compile() { 324 true 325} 326 327python someclass_do_compilepython () { 328 bb.note("Something") 329} 330 331EXPORT_FUNCTIONS do_compile do_compilepython 332""" 333 334 export_function_class2 = """ 335secondclass_do_compile() { 336 true 337} 338 339python secondclass_do_compilepython () { 340 bb.note("Something") 341} 342 343EXPORT_FUNCTIONS do_compile do_compilepython 344""" 345 346 def test_parse_export_functions(self): 347 def check_function_flags(d): 348 self.assertEqual(d.getVarFlag("do_compile", "func"), 1) 349 self.assertEqual(d.getVarFlag("do_compilepython", "func"), 1) 350 self.assertEqual(d.getVarFlag("do_compile", "python"), None) 351 self.assertEqual(d.getVarFlag("do_compilepython", "python"), "1") 352 353 with tempfile.TemporaryDirectory() as tempdir: 354 self.d.setVar("__bbclasstype", "recipe") 355 recipename = tempdir + "/recipe.bb" 356 os.makedirs(tempdir + "/classes") 357 with open(tempdir + "/classes/someclass.bbclass", "w") as f: 358 f.write(self.export_function_class) 359 f.flush() 360 with open(tempdir + "/classes/secondclass.bbclass", "w") as f: 361 f.write(self.export_function_class2) 362 f.flush() 363 364 with open(recipename, "w") as f: 365 f.write(self.export_function_recipe) 366 f.flush() 367 os.chdir(tempdir) 368 d = bb.parse.handle(recipename, bb.data.createCopy(self.d))[''] 369 self.assertIn("someclass_do_compile", d.getVar("do_compile")) 370 self.assertIn("someclass_do_compilepython", d.getVar("do_compilepython")) 371 check_function_flags(d) 372 373 recipename2 = tempdir + "/recipe2.bb" 374 with open(recipename2, "w") as f: 375 f.write(self.export_function_recipe2) 376 f.flush() 377 378 d = bb.parse.handle(recipename2, bb.data.createCopy(self.d))[''] 379 self.assertNotIn("someclass_do_compile", d.getVar("do_compile")) 380 self.assertNotIn("someclass_do_compilepython", d.getVar("do_compilepython")) 381 self.assertIn("false", d.getVar("do_compile")) 382 self.assertIn("else", d.getVar("do_compilepython")) 383 check_function_flags(d) 384 385 with open(recipename, "a+") as f: 386 f.write("\ninherit secondclass\n") 387 f.flush() 388 with open(recipename2, "a+") as f: 389 f.write("\ninherit secondclass\n") 390 f.flush() 391 392 d = bb.parse.handle(recipename, bb.data.createCopy(self.d))[''] 393 self.assertIn("secondclass_do_compile", d.getVar("do_compile")) 394 self.assertIn("secondclass_do_compilepython", d.getVar("do_compilepython")) 395 check_function_flags(d) 396 397 d = bb.parse.handle(recipename2, bb.data.createCopy(self.d))[''] 398 self.assertNotIn("someclass_do_compile", d.getVar("do_compile")) 399 self.assertNotIn("someclass_do_compilepython", d.getVar("do_compilepython")) 400 self.assertIn("false", d.getVar("do_compile")) 401 self.assertIn("else", d.getVar("do_compilepython")) 402 check_function_flags(d) 403 404 export_function_unclosed_tab = """ 405do_compile () { 406 bb.note("Something") 407\t} 408""" 409 export_function_unclosed_space = """ 410do_compile () { 411 bb.note("Something") 412 } 413""" 414 export_function_residue = """ 415do_compile () { 416 bb.note("Something") 417} 418 419include \\ 420""" 421 422 def test_unclosed_functions(self): 423 def test_helper(content, expected_error): 424 with tempfile.TemporaryDirectory() as tempdir: 425 recipename = tempdir + "/recipe_unclosed.bb" 426 with open(recipename, "w") as f: 427 f.write(content) 428 f.flush() 429 os.chdir(tempdir) 430 with self.assertRaises(bb.parse.ParseError) as error: 431 bb.parse.handle(recipename, bb.data.createCopy(self.d)) 432 self.assertIn(expected_error, str(error.exception)) 433 434 with tempfile.TemporaryDirectory() as tempdir: 435 test_helper(self.export_function_unclosed_tab, "Unparsed lines from unclosed function") 436 test_helper(self.export_function_unclosed_space, "Unparsed lines from unclosed function") 437 test_helper(self.export_function_residue, "Unparsed lines") 438 439 recipename_closed = tempdir + "/recipe_closed.bb" 440 with open(recipename_closed, "w") as in_file: 441 lines = self.export_function_unclosed_tab.split("\n") 442 lines[3] = "}" 443 in_file.write("\n".join(lines)) 444 in_file.flush() 445 bb.parse.handle(recipename_closed, bb.data.createCopy(self.d)) 446