diff options
Diffstat (limited to 'python/mozbuild/mozbuild/test/configure/test_lint.py')
-rw-r--r-- | python/mozbuild/mozbuild/test/configure/test_lint.py | 487 |
1 files changed, 487 insertions, 0 deletions
diff --git a/python/mozbuild/mozbuild/test/configure/test_lint.py b/python/mozbuild/mozbuild/test/configure/test_lint.py new file mode 100644 index 0000000000..7ecac769c3 --- /dev/null +++ b/python/mozbuild/mozbuild/test/configure/test_lint.py @@ -0,0 +1,487 @@ +# 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 contextlib +import os +import sys +import textwrap +import traceback +import unittest + +import mozpack.path as mozpath +from mozunit import MockedOpen, main + +from mozbuild.configure import ConfigureError +from mozbuild.configure.lint import LintSandbox + +test_data_path = mozpath.abspath(mozpath.dirname(__file__)) +test_data_path = mozpath.join(test_data_path, "data") + + +class TestLint(unittest.TestCase): + def lint_test(self, options=[], env={}): + sandbox = LintSandbox(env, ["configure"] + options) + + sandbox.run(mozpath.join(test_data_path, "moz.configure")) + + def moz_configure(self, source): + return MockedOpen( + {os.path.join(test_data_path, "moz.configure"): textwrap.dedent(source)} + ) + + @contextlib.contextmanager + def assertRaisesFromLine(self, exc_type, line): + with self.assertRaises(exc_type) as e: + yield e + + _, _, tb = sys.exc_info() + self.assertEqual( + traceback.extract_tb(tb)[-1][:2], + (mozpath.join(test_data_path, "moz.configure"), line), + ) + + def test_configure_testcase(self): + # Lint python/mozbuild/mozbuild/test/configure/data/moz.configure + self.lint_test() + + def test_depends_failures(self): + with self.moz_configure( + """ + option('--foo', help='foo') + @depends('--foo') + def foo(value): + return value + + @depends('--help', foo) + @imports('os') + def bar(help, foo): + return foo + """ + ): + self.lint_test() + + with self.assertRaisesFromLine(ConfigureError, 7) as e: + with self.moz_configure( + """ + option('--foo', help='foo') + @depends('--foo') + def foo(value): + return value + + @depends('--help', foo) + def bar(help, foo): + return foo + """ + ): + self.lint_test() + + self.assertEqual(str(e.exception), "The dependency on `--help` is unused") + + with self.assertRaisesFromLine(ConfigureError, 3) as e: + with self.moz_configure( + """ + option('--foo', help='foo') + @depends('--foo') + @imports('os') + def foo(value): + return value + + @depends('--help', foo) + @imports('os') + def bar(help, foo): + return foo + """ + ): + self.lint_test() + + self.assertEqual( + str(e.exception), + "Missing '--help' dependency because `bar` depends on '--help' and `foo`", + ) + + with self.assertRaisesFromLine(ConfigureError, 7) as e: + with self.moz_configure( + """ + @template + def tmpl(): + qux = 42 + + option('--foo', help='foo') + @depends('--foo') + def foo(value): + qux + return value + + @depends('--help', foo) + @imports('os') + def bar(help, foo): + return foo + tmpl() + """ + ): + self.lint_test() + + self.assertEqual( + str(e.exception), + "Missing '--help' dependency because `bar` depends on '--help' and `foo`", + ) + + with self.moz_configure( + """ + option('--foo', help='foo') + @depends('--foo') + def foo(value): + return value + + include(foo) + """ + ): + self.lint_test() + + with self.assertRaisesFromLine(ConfigureError, 3) as e: + with self.moz_configure( + """ + option('--foo', help='foo') + @depends('--foo') + @imports('os') + def foo(value): + return value + + include(foo) + """ + ): + self.lint_test() + + self.assertEqual(str(e.exception), "Missing '--help' dependency") + + with self.assertRaisesFromLine(ConfigureError, 3) as e: + with self.moz_configure( + """ + option('--foo', help='foo') + @depends('--foo') + @imports('os') + def foo(value): + return value + + @depends(foo) + def bar(value): + return value + + include(bar) + """ + ): + self.lint_test() + + self.assertEqual(str(e.exception), "Missing '--help' dependency") + + with self.assertRaisesFromLine(ConfigureError, 3) as e: + with self.moz_configure( + """ + option('--foo', help='foo') + @depends('--foo') + @imports('os') + def foo(value): + return value + + option('--bar', help='bar', when=foo) + """ + ): + self.lint_test() + + self.assertEqual(str(e.exception), "Missing '--help' dependency") + + # This would have failed with "Missing '--help' dependency" + # in the past, because of the reference to the builtin False. + with self.moz_configure( + """ + option('--foo', help='foo') + @depends('--foo') + def foo(value): + return False or value + + option('--bar', help='bar', when=foo) + """ + ): + self.lint_test() + + # However, when something that is normally a builtin is overridden, + # we should still want the dependency on --help. + with self.assertRaisesFromLine(ConfigureError, 7) as e: + with self.moz_configure( + """ + @template + def tmpl(): + sorted = 42 + + option('--foo', help='foo') + @depends('--foo') + def foo(value): + return sorted + + option('--bar', help='bar', when=foo) + tmpl() + """ + ): + self.lint_test() + + self.assertEqual(str(e.exception), "Missing '--help' dependency") + + # There is a default restricted `os` module when there is no explicit + # @imports, and it's fine to use it without a dependency on --help. + with self.moz_configure( + """ + option('--foo', help='foo') + @depends('--foo') + def foo(value): + os + return value + + include(foo) + """ + ): + self.lint_test() + + with self.assertRaisesFromLine(ConfigureError, 3) as e: + with self.moz_configure( + """ + option('--foo', help='foo') + @depends('--foo') + def foo(value): + return + + include(foo) + """ + ): + self.lint_test() + + self.assertEqual(str(e.exception), "The dependency on `--foo` is unused") + + with self.assertRaisesFromLine(ConfigureError, 5) as e: + with self.moz_configure( + """ + @depends(when=True) + def bar(): + return + @depends(bar) + def foo(value): + return + + include(foo) + """ + ): + self.lint_test() + + self.assertEqual(str(e.exception), "The dependency on `bar` is unused") + + with self.assertRaisesFromLine(ConfigureError, 2) as e: + with self.moz_configure( + """ + @depends(depends(when=True)(lambda: None)) + def foo(value): + return + + include(foo) + """ + ): + self.lint_test() + + self.assertEqual(str(e.exception), "The dependency on `<lambda>` is unused") + + with self.assertRaisesFromLine(ConfigureError, 9) as e: + with self.moz_configure( + """ + @template + def tmpl(): + @depends(when=True) + def bar(): + return + return bar + qux = tmpl() + @depends(qux) + def foo(value): + return + + include(foo) + """ + ): + self.lint_test() + + self.assertEqual(str(e.exception), "The dependency on `qux` is unused") + + def test_default_enable(self): + # --enable-* with default=True is not allowed. + with self.moz_configure( + """ + option('--enable-foo', default=False, help='foo') + """ + ): + self.lint_test() + with self.assertRaisesFromLine(ConfigureError, 2) as e: + with self.moz_configure( + """ + option('--enable-foo', default=True, help='foo') + """ + ): + self.lint_test() + self.assertEqual( + str(e.exception), + "--disable-foo should be used instead of " "--enable-foo with default=True", + ) + + def test_default_disable(self): + # --disable-* with default=False is not allowed. + with self.moz_configure( + """ + option('--disable-foo', default=True, help='foo') + """ + ): + self.lint_test() + with self.assertRaisesFromLine(ConfigureError, 2) as e: + with self.moz_configure( + """ + option('--disable-foo', default=False, help='foo') + """ + ): + self.lint_test() + self.assertEqual( + str(e.exception), + "--enable-foo should be used instead of " + "--disable-foo with default=False", + ) + + def test_default_with(self): + # --with-* with default=True is not allowed. + with self.moz_configure( + """ + option('--with-foo', default=False, help='foo') + """ + ): + self.lint_test() + with self.assertRaisesFromLine(ConfigureError, 2) as e: + with self.moz_configure( + """ + option('--with-foo', default=True, help='foo') + """ + ): + self.lint_test() + self.assertEqual( + str(e.exception), + "--without-foo should be used instead of " "--with-foo with default=True", + ) + + def test_default_without(self): + # --without-* with default=False is not allowed. + with self.moz_configure( + """ + option('--without-foo', default=True, help='foo') + """ + ): + self.lint_test() + with self.assertRaisesFromLine(ConfigureError, 2) as e: + with self.moz_configure( + """ + option('--without-foo', default=False, help='foo') + """ + ): + self.lint_test() + self.assertEqual( + str(e.exception), + "--with-foo should be used instead of " "--without-foo with default=False", + ) + + def test_default_func(self): + # Help text for an option with variable default should contain + # {enable|disable} rule. + with self.moz_configure( + """ + option(env='FOO', help='foo') + option('--enable-bar', default=depends('FOO')(lambda x: bool(x)), + help='{Enable|Disable} bar') + """ + ): + self.lint_test() + with self.assertRaisesFromLine(ConfigureError, 3) as e: + with self.moz_configure( + """ + option(env='FOO', help='foo') + option('--enable-bar', default=depends('FOO')(lambda x: bool(x)),\ + help='Enable bar') + """ + ): + self.lint_test() + self.assertEqual( + str(e.exception), + '`help` should contain "{Enable|Disable}" because of ' + "non-constant default", + ) + + def test_large_offset(self): + with self.assertRaisesFromLine(ConfigureError, 375): + with self.moz_configure( + """ + option(env='FOO', help='foo') + """ + + "\n" * 371 + + """ + option('--enable-bar', default=depends('FOO')(lambda x: bool(x)),\ + help='Enable bar') + """ + ): + self.lint_test() + + def test_undefined_global(self): + with self.assertRaisesFromLine(NameError, 6) as e: + with self.moz_configure( + """ + option(env='FOO', help='foo') + @depends('FOO') + def foo(value): + if value: + return unknown + return value + """ + ): + self.lint_test() + + self.assertEqual(str(e.exception), "global name 'unknown' is not defined") + + # Ideally, this would raise on line 4, where `unknown` is used, but + # python disassembly doesn't give use the information. + with self.assertRaisesFromLine(NameError, 2) as e: + with self.moz_configure( + """ + @template + def tmpl(): + @depends(unknown) + def foo(value): + if value: + return True + return foo + tmpl() + """ + ): + self.lint_test() + + self.assertEqual(str(e.exception), "global name 'unknown' is not defined") + + def test_unnecessary_imports(self): + with self.assertRaisesFromLine(NameError, 3) as e: + with self.moz_configure( + """ + option(env='FOO', help='foo') + @depends('FOO') + @imports(_from='__builtin__', _import='list') + def foo(value): + if value: + return list() + return value + """ + ): + self.lint_test() + + self.assertEqual(str(e.exception), "builtin 'list' doesn't need to be imported") + + +if __name__ == "__main__": + main() |