diff options
Diffstat (limited to 'python/mozbuild/mozbuild/test/test_preprocessor.py')
-rw-r--r-- | python/mozbuild/mozbuild/test/test_preprocessor.py | 832 |
1 files changed, 832 insertions, 0 deletions
diff --git a/python/mozbuild/mozbuild/test/test_preprocessor.py b/python/mozbuild/mozbuild/test/test_preprocessor.py new file mode 100644 index 0000000000..82039c2bd7 --- /dev/null +++ b/python/mozbuild/mozbuild/test/test_preprocessor.py @@ -0,0 +1,832 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import os +import shutil +import unittest +from tempfile import mkdtemp + +from mozunit import MockedOpen, main +from six import StringIO + +from mozbuild.preprocessor import Preprocessor + + +class TestPreprocessor(unittest.TestCase): + """ + Unit tests for the Context class + """ + + def setUp(self): + self.pp = Preprocessor() + self.pp.out = StringIO() + + def do_include_compare(self, content_lines, expected_lines): + content = "%s" % "\n".join(content_lines) + expected = "%s".rstrip() % "\n".join(expected_lines) + + with MockedOpen({"dummy": content}): + self.pp.do_include("dummy") + self.assertEqual(self.pp.out.getvalue().rstrip("\n"), expected) + + def do_include_pass(self, content_lines): + self.do_include_compare(content_lines, ["PASS"]) + + def test_conditional_if_0(self): + self.do_include_pass( + [ + "#if 0", + "FAIL", + "#else", + "PASS", + "#endif", + ] + ) + + def test_no_marker(self): + lines = [ + "#if 0", + "PASS", + "#endif", + ] + self.pp.setMarker(None) + self.do_include_compare(lines, lines) + + def test_string_value(self): + self.do_include_compare( + [ + "#define FOO STRING", + "#if FOO", + "string value is true", + "#else", + "string value is false", + "#endif", + ], + ["string value is false"], + ) + + def test_number_value(self): + self.do_include_compare( + [ + "#define FOO 1", + "#if FOO", + "number value is true", + "#else", + "number value is false", + "#endif", + ], + ["number value is true"], + ) + + def test_conditional_if_0_elif_1(self): + self.do_include_pass( + [ + "#if 0", + "#elif 1", + "PASS", + "#else", + "FAIL", + "#endif", + ] + ) + + def test_conditional_if_1(self): + self.do_include_pass( + [ + "#if 1", + "PASS", + "#else", + "FAIL", + "#endif", + ] + ) + + def test_conditional_if_0_or_1(self): + self.do_include_pass( + [ + "#if 0 || 1", + "PASS", + "#else", + "FAIL", + "#endif", + ] + ) + + def test_conditional_if_1_elif_1_else(self): + self.do_include_pass( + [ + "#if 1", + "PASS", + "#elif 1", + "FAIL", + "#else", + "FAIL", + "#endif", + ] + ) + + def test_conditional_if_1_if_1(self): + self.do_include_pass( + [ + "#if 1", + "#if 1", + "PASS", + "#else", + "FAIL", + "#endif", + "#else", + "FAIL", + "#endif", + ] + ) + + def test_conditional_not_0(self): + self.do_include_pass( + [ + "#if !0", + "PASS", + "#else", + "FAIL", + "#endif", + ] + ) + + def test_conditional_not_0_and_1(self): + self.do_include_pass( + [ + "#if !0 && !1", + "FAIL", + "#else", + "PASS", + "#endif", + ] + ) + + def test_conditional_not_1(self): + self.do_include_pass( + [ + "#if !1", + "FAIL", + "#else", + "PASS", + "#endif", + ] + ) + + def test_conditional_not_emptyval(self): + self.do_include_compare( + [ + "#define EMPTYVAL", + "#ifndef EMPTYVAL", + "FAIL", + "#else", + "PASS", + "#endif", + "#ifdef EMPTYVAL", + "PASS", + "#else", + "FAIL", + "#endif", + ], + ["PASS", "PASS"], + ) + + def test_conditional_not_nullval(self): + self.do_include_pass( + [ + "#define NULLVAL 0", + "#if !NULLVAL", + "PASS", + "#else", + "FAIL", + "#endif", + ] + ) + + def test_indentation(self): + self.do_include_pass( + [ + " #define NULLVAL 0", + " #if !NULLVAL", + "PASS", + " #else", + "FAIL", + " #endif", + ] + ) + + def test_expand(self): + self.do_include_pass( + [ + "#define ASVAR AS", + "#expand P__ASVAR__S", + ] + ) + + def test_undef_defined(self): + self.do_include_compare( + [ + "#define BAR", + "#undef BAR", + "BAR", + ], + ["BAR"], + ) + + def test_undef_undefined(self): + self.do_include_compare( + [ + "#undef BAR", + ], + [], + ) + + def test_filter_attemptSubstitution(self): + self.do_include_compare( + [ + "#filter attemptSubstitution", + "@PASS@", + "#unfilter attemptSubstitution", + ], + ["@PASS@"], + ) + + def test_filter_emptyLines(self): + self.do_include_compare( + [ + "lines with a", + "", + "blank line", + "#filter emptyLines", + "lines with", + "", + "no blank lines", + "#unfilter emptyLines", + "yet more lines with", + "", + "blank lines", + ], + [ + "lines with a", + "", + "blank line", + "lines with", + "no blank lines", + "yet more lines with", + "", + "blank lines", + ], + ) + + def test_filter_dumbComments(self): + self.do_include_compare( + [ + "#filter dumbComments", + "PASS//PASS // PASS", + " //FAIL", + "// FAIL", + "PASS //", + "PASS // FAIL", + "//", + "", + "#unfilter dumbComments", + "// PASS", + ], + [ + "PASS//PASS // PASS", + "", + "", + "PASS //", + "PASS // FAIL", + "", + "", + "// PASS", + ], + ) + + def test_filter_dumbComments_and_emptyLines(self): + self.do_include_compare( + [ + "#filter dumbComments emptyLines", + "PASS//PASS // PASS", + " //FAIL", + "// FAIL", + "PASS //", + "PASS // FAIL", + "//", + "", + "#unfilter dumbComments emptyLines", + "", + "// PASS", + ], + [ + "PASS//PASS // PASS", + "PASS //", + "PASS // FAIL", + "", + "// PASS", + ], + ) + + def test_filter_substitution(self): + self.do_include_pass( + [ + "#define VAR ASS", + "#filter substitution", + "P@VAR@", + "#unfilter substitution", + ] + ) + + def test_error(self): + with MockedOpen({"f": "#error spit this message out\n"}): + with self.assertRaises(Preprocessor.Error) as e: + self.pp.do_include("f") + self.assertEqual(e.args[0][-1], "spit this message out") + + def test_ambigous_command(self): + comment = "# if I tell you a joke\n" + with MockedOpen({"f": comment}): + with self.assertRaises(Preprocessor.Error) as e: + self.pp.do_include("f") + the_exception = e.exception + self.assertEqual(the_exception.args[0][-1], comment) + + def test_javascript_line(self): + # The preprocessor is reading the filename from somewhere not caught + # by MockedOpen. + tmpdir = mkdtemp() + try: + full = os.path.join(tmpdir, "javascript_line.js.in") + with open(full, "w") as fh: + fh.write( + "\n".join( + [ + "// Line 1", + "#if 0", + "// line 3", + "#endif", + "// line 5", + "# comment", + "// line 7", + "// line 8", + "// line 9", + "# another comment", + "// line 11", + "#define LINE 1", + "// line 13, given line number overwritten with 2", + "", + ] + ) + ) + + self.pp.do_include(full) + out = "\n".join( + [ + "// Line 1", + '//@line 5 "CWDjavascript_line.js.in"', + "// line 5", + '//@line 7 "CWDjavascript_line.js.in"', + "// line 7", + "// line 8", + "// line 9", + '//@line 11 "CWDjavascript_line.js.in"', + "// line 11", + '//@line 2 "CWDjavascript_line.js.in"', + "// line 13, given line number overwritten with 2", + "", + ] + ) + out = out.replace("CWD", tmpdir + os.path.sep) + self.assertEqual(self.pp.out.getvalue(), out) + finally: + shutil.rmtree(tmpdir) + + def test_literal(self): + self.do_include_pass( + [ + "#literal PASS", + ] + ) + + def test_var_directory(self): + self.do_include_pass( + [ + "#ifdef DIRECTORY", + "PASS", + "#else", + "FAIL", + "#endif", + ] + ) + + def test_var_file(self): + self.do_include_pass( + [ + "#ifdef FILE", + "PASS", + "#else", + "FAIL", + "#endif", + ] + ) + + def test_var_if_0(self): + self.do_include_pass( + [ + "#define VAR 0", + "#if VAR", + "FAIL", + "#else", + "PASS", + "#endif", + ] + ) + + def test_var_if_0_elifdef(self): + self.do_include_pass( + [ + "#if 0", + "#elifdef FILE", + "PASS", + "#else", + "FAIL", + "#endif", + ] + ) + + def test_var_if_0_elifndef(self): + self.do_include_pass( + [ + "#if 0", + "#elifndef VAR", + "PASS", + "#else", + "FAIL", + "#endif", + ] + ) + + def test_var_ifdef_0(self): + self.do_include_pass( + [ + "#define VAR 0", + "#ifdef VAR", + "PASS", + "#else", + "FAIL", + "#endif", + ] + ) + + def test_var_ifdef_1_or_undef(self): + self.do_include_pass( + [ + "#define FOO 1", + "#if defined(FOO) || defined(BAR)", + "PASS", + "#else", + "FAIL", + "#endif", + ] + ) + + def test_var_ifdef_undef(self): + self.do_include_pass( + [ + "#define VAR 0", + "#undef VAR", + "#ifdef VAR", + "FAIL", + "#else", + "PASS", + "#endif", + ] + ) + + def test_var_ifndef_0(self): + self.do_include_pass( + [ + "#define VAR 0", + "#ifndef VAR", + "FAIL", + "#else", + "PASS", + "#endif", + ] + ) + + def test_var_ifndef_0_and_undef(self): + self.do_include_pass( + [ + "#define FOO 0", + "#if !defined(FOO) && !defined(BAR)", + "FAIL", + "#else", + "PASS", + "#endif", + ] + ) + + def test_var_ifndef_undef(self): + self.do_include_pass( + [ + "#define VAR 0", + "#undef VAR", + "#ifndef VAR", + "PASS", + "#else", + "FAIL", + "#endif", + ] + ) + + def test_var_line(self): + self.do_include_pass( + [ + "#ifdef LINE", + "PASS", + "#else", + "FAIL", + "#endif", + ] + ) + + def test_filterDefine(self): + self.do_include_pass( + [ + "#filter substitution", + "#define VAR AS", + "#define VAR2 P@VAR@", + "@VAR2@S", + ] + ) + + def test_number_value_equals(self): + self.do_include_pass( + [ + "#define FOO 1000", + "#if FOO == 1000", + "PASS", + "#else", + "FAIL", + "#endif", + ] + ) + + def test_default_defines(self): + self.pp.handleCommandLine(["-DFOO"]) + self.do_include_pass( + [ + "#if FOO == 1", + "PASS", + "#else", + "FAIL", + ] + ) + + def test_number_value_equals_defines(self): + self.pp.handleCommandLine(["-DFOO=1000"]) + self.do_include_pass( + [ + "#if FOO == 1000", + "PASS", + "#else", + "FAIL", + ] + ) + + def test_octal_value_equals(self): + self.do_include_pass( + [ + "#define FOO 0100", + "#if FOO == 0100", + "PASS", + "#else", + "FAIL", + "#endif", + ] + ) + + def test_octal_value_equals_defines(self): + self.pp.handleCommandLine(["-DFOO=0100"]) + self.do_include_pass( + [ + "#if FOO == 0100", + "PASS", + "#else", + "FAIL", + "#endif", + ] + ) + + def test_value_quoted_expansion(self): + """ + Quoted values on the commandline don't currently have quotes stripped. + Pike says this is for compat reasons. + """ + self.pp.handleCommandLine(['-DFOO="ABCD"']) + self.do_include_compare( + [ + "#filter substitution", + "@FOO@", + ], + ['"ABCD"'], + ) + + def test_octal_value_quoted_expansion(self): + self.pp.handleCommandLine(['-DFOO="0100"']) + self.do_include_compare( + [ + "#filter substitution", + "@FOO@", + ], + ['"0100"'], + ) + + def test_number_value_not_equals_quoted_defines(self): + self.pp.handleCommandLine(['-DFOO="1000"']) + self.do_include_pass( + [ + "#if FOO == 1000", + "FAIL", + "#else", + "PASS", + "#endif", + ] + ) + + def test_octal_value_not_equals_quoted_defines(self): + self.pp.handleCommandLine(['-DFOO="0100"']) + self.do_include_pass( + [ + "#if FOO == 0100", + "FAIL", + "#else", + "PASS", + "#endif", + ] + ) + + def test_undefined_variable(self): + with MockedOpen({"f": "#filter substitution\n@foo@"}): + with self.assertRaises(Preprocessor.Error) as e: + self.pp.do_include("f") + self.assertEqual(e.key, "UNDEFINED_VAR") + + def test_include(self): + files = { + "foo/test": "\n".join( + [ + "#define foo foobarbaz", + "#include @inc@", + "@bar@", + "", + ] + ), + "bar": "\n".join( + [ + "#define bar barfoobaz", + "@foo@", + "", + ] + ), + "f": "\n".join( + [ + "#filter substitution", + "#define inc ../bar", + "#include foo/test", + "", + ] + ), + } + + with MockedOpen(files): + self.pp.do_include("f") + self.assertEqual(self.pp.out.getvalue(), "foobarbaz\nbarfoobaz\n") + + def test_include_line(self): + files = { + "srcdir/test.js": "\n".join( + [ + "#define foo foobarbaz", + "#include @inc@", + "@bar@", + "", + ] + ), + "srcdir/bar.js": "\n".join( + [ + "#define bar barfoobaz", + "@foo@", + "", + ] + ), + "srcdir/foo.js": "\n".join( + [ + "bazfoobar", + "#include bar.js", + "bazbarfoo", + "", + ] + ), + "objdir/baz.js": "baz\n", + "srcdir/f.js": "\n".join( + [ + "#include foo.js", + "#filter substitution", + "#define inc bar.js", + "#include test.js", + "#include ../objdir/baz.js", + "fin", + "", + ] + ), + } + + preprocessed = ( + '//@line 1 "$SRCDIR/foo.js"\n' + "bazfoobar\n" + '//@line 2 "$SRCDIR/bar.js"\n' + "@foo@\n" + '//@line 3 "$SRCDIR/foo.js"\n' + "bazbarfoo\n" + '//@line 2 "$SRCDIR/bar.js"\n' + "foobarbaz\n" + '//@line 3 "$SRCDIR/test.js"\n' + "barfoobaz\n" + '//@line 1 "$OBJDIR/baz.js"\n' + "baz\n" + '//@line 6 "$SRCDIR/f.js"\n' + "fin\n" + ) + + # Try with separate srcdir/objdir + with MockedOpen(files): + self.pp.topsrcdir = os.path.abspath("srcdir") + self.pp.topobjdir = os.path.abspath("objdir") + self.pp.do_include("srcdir/f.js") + self.assertEqual(self.pp.out.getvalue(), preprocessed) + + # Try again with relative objdir + self.setUp() + files["srcdir/objdir/baz.js"] = files["objdir/baz.js"] + del files["objdir/baz.js"] + files["srcdir/f.js"] = files["srcdir/f.js"].replace("../", "") + with MockedOpen(files): + self.pp.topsrcdir = os.path.abspath("srcdir") + self.pp.topobjdir = os.path.abspath("srcdir/objdir") + self.pp.do_include("srcdir/f.js") + self.assertEqual(self.pp.out.getvalue(), preprocessed) + + def test_include_missing_file(self): + with MockedOpen({"f": "#include foo\n"}): + with self.assertRaises(Preprocessor.Error) as e: + self.pp.do_include("f") + self.assertEqual(e.exception.key, "FILE_NOT_FOUND") + + def test_include_undefined_variable(self): + with MockedOpen({"f": "#filter substitution\n#include @foo@\n"}): + with self.assertRaises(Preprocessor.Error) as e: + self.pp.do_include("f") + self.assertEqual(e.exception.key, "UNDEFINED_VAR") + + def test_include_literal_at(self): + files = { + "@foo@": "#define foo foobarbaz\n", + "f": "#include @foo@\n#filter substitution\n@foo@\n", + } + + with MockedOpen(files): + self.pp.do_include("f") + self.assertEqual(self.pp.out.getvalue(), "foobarbaz\n") + + def test_command_line_literal_at(self): + with MockedOpen({"@foo@.in": "@foo@\n"}): + self.pp.handleCommandLine(["-Fsubstitution", "-Dfoo=foobarbaz", "@foo@.in"]) + self.assertEqual(self.pp.out.getvalue(), "foobarbaz\n") + + def test_invalid_ifdef(self): + with MockedOpen({"dummy": "#ifdef FOO == BAR\nPASS\n#endif"}): + with self.assertRaises(Preprocessor.Error) as e: + self.pp.do_include("dummy") + self.assertEqual(e.exception.key, "INVALID_VAR") + + with MockedOpen({"dummy": "#ifndef FOO == BAR\nPASS\n#endif"}): + with self.assertRaises(Preprocessor.Error) as e: + self.pp.do_include("dummy") + self.assertEqual(e.exception.key, "INVALID_VAR") + + # Trailing whitespaces, while not nice, shouldn't be an error. + self.do_include_pass( + [ + "#ifndef FOO ", + "PASS", + "#endif", + ] + ) + + +if __name__ == "__main__": + main() |