# 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 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_capitalize_help(self):
with self.moz_configure(
"option('--some', help='Help')"):
self.lint_test()
with self.assertRaisesFromLine(ConfigureError, 1)
as e0:
with self.moz_configure(
"option('--some', help='help')"):
self.lint_test()
self.assertEqual(
str(e0.exception),
'Invalid `help` message for option "--some": `help` is not properly capitalized',
)
with self.assertRaisesFromLine(ConfigureError, 1)
as e1:
with self.moz_configure(
"option('--some', help='Help.')"):
self.lint_test()
self.assertEqual(
str(e1.exception),
"Invalid `help` message for option \"--some\
": `Help.` should not end with a '.'",
)
with self.moz_configure(
"""
option(env=
'SOME', help=
'Foo', default=
'a')
option(
'--enable-some', nargs=
'*', choices=(
'a',
'b'),
help=
'{Enable|Disable} some',
default=depends(
'SOME')(
lambda x: x[0]))
"""
):
self.lint_test()
with self.assertRaisesFromLine(ConfigureError, 3)
as e2:
with self.moz_configure(
"""
option(env=
'SOME', help=
'Foo', default=
'a')
option(
'--enable-some', nargs=
'*', choices=(
'a',
'b'),
help=
'{enable|Disable} some',
default=depends(
'SOME')(
lambda x: x[0]))
"""
):
self.lint_test()
self.assertEqual(
str(e2.exception),
'Invalid `help` message for option "--enable-some": `enable` is not properly capitalized',
)
with self.assertRaisesFromLine(ConfigureError, 3)
as e3:
with self.moz_configure(
"""
option(env=
'SOME', help=
'Foo', default=
'a')
option(
'--enable-some', nargs=
'*', choices=(
'a',
'b'),
help=
'enable some',
default=depends(
'SOME')(
lambda x: x[0]))
"""
):
self.lint_test()
self.assertEqual(
str(e3.exception),
'Invalid `help` message for option "--enable-some": `enable some` is not properly capitalized',
)
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()