xref: /openbmc/openbmc/poky/bitbake/lib/bb/tests/parse.py (revision 96e4b4e121e0e2da1535d7d537d6a982a6ff5bc0)
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