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 exporttest = """ 79A = "a" 80export B = "b" 81export C 82exportD = "d" 83""" 84 85 def test_parse_exports(self): 86 f = self.parsehelper(self.exporttest) 87 d = bb.parse.handle(f.name, self.d)[''] 88 self.assertEqual(d.getVar("A"), "a") 89 self.assertIsNone(d.getVarFlag("A", "export")) 90 self.assertEqual(d.getVar("B"), "b") 91 self.assertEqual(d.getVarFlag("B", "export"), 1) 92 self.assertIsNone(d.getVar("C")) 93 self.assertEqual(d.getVarFlag("C", "export"), 1) 94 self.assertIsNone(d.getVar("D")) 95 self.assertIsNone(d.getVarFlag("D", "export")) 96 self.assertEqual(d.getVar("exportD"), "d") 97 self.assertIsNone(d.getVarFlag("exportD", "export")) 98 99 100 overridetest = """ 101RRECOMMENDS:${PN} = "a" 102RRECOMMENDS:${PN}:libc = "b" 103OVERRIDES = "libc:${PN}" 104PN = "gtk+" 105""" 106 107 def test_parse_overrides(self): 108 f = self.parsehelper(self.overridetest) 109 d = bb.parse.handle(f.name, self.d)[''] 110 self.assertEqual(d.getVar("RRECOMMENDS"), "b") 111 bb.data.expandKeys(d) 112 self.assertEqual(d.getVar("RRECOMMENDS"), "b") 113 d.setVar("RRECOMMENDS:gtk+", "c") 114 self.assertEqual(d.getVar("RRECOMMENDS"), "c") 115 116 overridetest2 = """ 117EXTRA_OECONF = "" 118EXTRA_OECONF:class-target = "b" 119EXTRA_OECONF:append = " c" 120""" 121 122 def test_parse_overrides2(self): 123 f = self.parsehelper(self.overridetest2) 124 d = bb.parse.handle(f.name, self.d)[''] 125 d.appendVar("EXTRA_OECONF", " d") 126 d.setVar("OVERRIDES", "class-target") 127 self.assertEqual(d.getVar("EXTRA_OECONF"), "b c d") 128 129 overridetest3 = """ 130DESCRIPTION = "A" 131DESCRIPTION:${PN}-dev = "${DESCRIPTION} B" 132PN = "bc" 133""" 134 135 def test_parse_combinations(self): 136 f = self.parsehelper(self.overridetest3) 137 d = bb.parse.handle(f.name, self.d)[''] 138 bb.data.expandKeys(d) 139 self.assertEqual(d.getVar("DESCRIPTION:bc-dev"), "A B") 140 d.setVar("DESCRIPTION", "E") 141 d.setVar("DESCRIPTION:bc-dev", "C D") 142 d.setVar("OVERRIDES", "bc-dev") 143 self.assertEqual(d.getVar("DESCRIPTION"), "C D") 144 145 146 classextend = """ 147VAR_var:override1 = "B" 148EXTRA = ":override1" 149OVERRIDES = "nothing${EXTRA}" 150 151BBCLASSEXTEND = "###CLASS###" 152""" 153 classextend_bbclass = """ 154EXTRA = "" 155python () { 156 d.renameVar("VAR_var", "VAR_var2") 157} 158""" 159 160 # 161 # Test based upon a real world data corruption issue. One 162 # data store changing a variable poked through into a different data 163 # store. This test case replicates that issue where the value 'B' would 164 # become unset/disappear. 165 # 166 def test_parse_classextend_contamination(self): 167 self.d.setVar("__bbclasstype", "recipe") 168 cls = self.parsehelper(self.classextend_bbclass, suffix=".bbclass") 169 #clsname = os.path.basename(cls.name).replace(".bbclass", "") 170 self.classextend = self.classextend.replace("###CLASS###", cls.name) 171 f = self.parsehelper(self.classextend) 172 alldata = bb.parse.handle(f.name, self.d) 173 d1 = alldata[''] 174 d2 = alldata[cls.name] 175 self.assertEqual(d1.getVar("VAR_var"), "B") 176 self.assertEqual(d2.getVar("VAR_var"), None) 177 178 addtask_deltask = """ 179addtask do_patch after do_foo after do_unpack before do_configure before do_compile 180addtask do_fetch do_patch 181 182MYVAR = "do_patch" 183EMPTYVAR = "" 184deltask do_fetch ${MYVAR} ${EMPTYVAR} 185deltask ${EMPTYVAR} 186""" 187 def test_parse_addtask_deltask(self): 188 import sys 189 190 with self.assertLogs() as logs: 191 f = self.parsehelper(self.addtask_deltask) 192 d = bb.parse.handle(f.name, self.d)[''] 193 194 output = "".join(logs.output) 195 self.assertTrue("addtask contained multiple 'before' keywords" in output) 196 self.assertTrue("addtask contained multiple 'after' keywords" in output) 197 self.assertTrue('addtask ignored: " do_patch"' in output) 198 #self.assertTrue('dependent task do_foo for do_patch does not exist' in output) 199 200 broken_multiline_comment = """ 201# First line of comment \\ 202# Second line of comment \\ 203 204""" 205 def test_parse_broken_multiline_comment(self): 206 f = self.parsehelper(self.broken_multiline_comment) 207 with self.assertRaises(bb.BBHandledException): 208 d = bb.parse.handle(f.name, self.d)[''] 209 210 211 comment_in_var = """ 212VAR = " \\ 213 SOMEVAL \\ 214# some comment \\ 215 SOMEOTHERVAL \\ 216" 217""" 218 def test_parse_comment_in_var(self): 219 f = self.parsehelper(self.comment_in_var) 220 with self.assertRaises(bb.BBHandledException): 221 d = bb.parse.handle(f.name, self.d)[''] 222 223 224 at_sign_in_var_flag = """ 225A[flag@.service] = "nonet" 226B[flag@.target] = "ntb" 227C[f] = "flag" 228 229unset A[flag@.service] 230""" 231 def test_parse_at_sign_in_var_flag(self): 232 f = self.parsehelper(self.at_sign_in_var_flag) 233 d = bb.parse.handle(f.name, self.d)[''] 234 self.assertEqual(d.getVar("A"), None) 235 self.assertEqual(d.getVar("B"), None) 236 self.assertEqual(d.getVarFlag("A","flag@.service"), None) 237 self.assertEqual(d.getVarFlag("B","flag@.target"), "ntb") 238 self.assertEqual(d.getVarFlag("C","f"), "flag") 239 240 def test_parse_invalid_at_sign_in_var_flag(self): 241 invalid_at_sign = self.at_sign_in_var_flag.replace("B[f", "B[@f") 242 f = self.parsehelper(invalid_at_sign) 243 with self.assertRaises(bb.parse.ParseError): 244 d = bb.parse.handle(f.name, self.d)[''] 245 246 export_function_recipe = """ 247inherit someclass 248""" 249 250 export_function_recipe2 = """ 251inherit someclass 252 253do_compile () { 254 false 255} 256 257python do_compilepython () { 258 bb.note("Something else") 259} 260 261""" 262 export_function_class = """ 263someclass_do_compile() { 264 true 265} 266 267python someclass_do_compilepython () { 268 bb.note("Something") 269} 270 271EXPORT_FUNCTIONS do_compile do_compilepython 272""" 273 274 export_function_class2 = """ 275secondclass_do_compile() { 276 true 277} 278 279python secondclass_do_compilepython () { 280 bb.note("Something") 281} 282 283EXPORT_FUNCTIONS do_compile do_compilepython 284""" 285 286 def test_parse_export_functions(self): 287 def check_function_flags(d): 288 self.assertEqual(d.getVarFlag("do_compile", "func"), 1) 289 self.assertEqual(d.getVarFlag("do_compilepython", "func"), 1) 290 self.assertEqual(d.getVarFlag("do_compile", "python"), None) 291 self.assertEqual(d.getVarFlag("do_compilepython", "python"), "1") 292 293 with tempfile.TemporaryDirectory() as tempdir: 294 self.d.setVar("__bbclasstype", "recipe") 295 recipename = tempdir + "/recipe.bb" 296 os.makedirs(tempdir + "/classes") 297 with open(tempdir + "/classes/someclass.bbclass", "w") as f: 298 f.write(self.export_function_class) 299 f.flush() 300 with open(tempdir + "/classes/secondclass.bbclass", "w") as f: 301 f.write(self.export_function_class2) 302 f.flush() 303 304 with open(recipename, "w") as f: 305 f.write(self.export_function_recipe) 306 f.flush() 307 os.chdir(tempdir) 308 d = bb.parse.handle(recipename, bb.data.createCopy(self.d))[''] 309 self.assertIn("someclass_do_compile", d.getVar("do_compile")) 310 self.assertIn("someclass_do_compilepython", d.getVar("do_compilepython")) 311 check_function_flags(d) 312 313 recipename2 = tempdir + "/recipe2.bb" 314 with open(recipename2, "w") as f: 315 f.write(self.export_function_recipe2) 316 f.flush() 317 318 d = bb.parse.handle(recipename2, bb.data.createCopy(self.d))[''] 319 self.assertNotIn("someclass_do_compile", d.getVar("do_compile")) 320 self.assertNotIn("someclass_do_compilepython", d.getVar("do_compilepython")) 321 self.assertIn("false", d.getVar("do_compile")) 322 self.assertIn("else", d.getVar("do_compilepython")) 323 check_function_flags(d) 324 325 with open(recipename, "a+") as f: 326 f.write("\ninherit secondclass\n") 327 f.flush() 328 with open(recipename2, "a+") as f: 329 f.write("\ninherit secondclass\n") 330 f.flush() 331 332 d = bb.parse.handle(recipename, bb.data.createCopy(self.d))[''] 333 self.assertIn("secondclass_do_compile", d.getVar("do_compile")) 334 self.assertIn("secondclass_do_compilepython", d.getVar("do_compilepython")) 335 check_function_flags(d) 336 337 d = bb.parse.handle(recipename2, bb.data.createCopy(self.d))[''] 338 self.assertNotIn("someclass_do_compile", d.getVar("do_compile")) 339 self.assertNotIn("someclass_do_compilepython", d.getVar("do_compilepython")) 340 self.assertIn("false", d.getVar("do_compile")) 341 self.assertIn("else", d.getVar("do_compilepython")) 342 check_function_flags(d) 343 344