Source code
Revision control
Copy as Markdown
Other Tools
Test Info:
# 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
import os
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 AssertRaisesFromLine:
def __init__(self, test_case, expected, path, line):
self.test_case = test_case
self.expected = expected
self.path = path
self.line = line
def __enter__(self):
return self
def __exit__(self, exc_type, exc_value, tb):
if exc_type is None:
raise Exception(f"{self.expected.__name__} not raised")
if not issubclass(exc_type, self.expected):
return False
self.exception = exc_value
self.test_case.assertEqual(
traceback.extract_tb(tb)[-1][:2], (self.path, self.line)
)
return True
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)}
)
def assertRaisesFromLine(self, exc_type, line):
return AssertRaisesFromLine(
self, exc_type, 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_dual_help(self):
# Help text for an option that can be both disabled and enabled with an
# optional value should contain {enable|disable} rule.
with self.moz_configure(
"""
option('--disable-bar', nargs="*", choices=("a", "b"),
help='{Enable|Disable} bar')
"""
):
self.lint_test()
with self.assertRaisesFromLine(ConfigureError, 2) as e:
with self.moz_configure(
"""
option('--disable-bar', nargs="*", choices=("a", "b"), help='Enable bar')
"""
):
self.lint_test()
self.assertEqual(
str(e.exception),
'`help` should contain "{Enable|Disable}" because it '
"can be both disabled and enabled with an optional value",
)
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()