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
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
import unittest
import mozpack.path as mozpath
from mozunit import main
from mozbuild.frontend.context import (
FUNCTIONS,
SPECIAL_VARIABLES,
VARIABLES,
Context,
SourcePath,
)
from mozbuild.frontend.reader import MozbuildSandbox, SandboxCalledError
from mozbuild.frontend.sandbox import Sandbox, SandboxExecutionError, SandboxLoadError
from mozbuild.test.common import MockConfig
test_data_path = mozpath.abspath(mozpath.dirname(__file__))
test_data_path = mozpath.join(test_data_path, "data")
class TestSandbox(unittest.TestCase):
def sandbox(self):
return Sandbox(
Context(
{
"DIRS": (list, list, None),
}
)
)
def test_exec_source_success(self):
sandbox = self.sandbox()
context = sandbox._context
sandbox.exec_source("foo = True", mozpath.abspath("foo.py"))
self.assertNotIn("foo", context)
self.assertEqual(context.main_path, mozpath.abspath("foo.py"))
self.assertEqual(context.all_paths, set([mozpath.abspath("foo.py")]))
def test_exec_compile_error(self):
sandbox = self.sandbox()
with self.assertRaises(SandboxExecutionError) as se:
sandbox.exec_source("2f23;k;asfj", mozpath.abspath("foo.py"))
self.assertEqual(se.exception.file_stack, [mozpath.abspath("foo.py")])
self.assertIsInstance(se.exception.exc_value, SyntaxError)
self.assertEqual(sandbox._context.main_path, mozpath.abspath("foo.py"))
def test_exec_import_denied(self):
sandbox = self.sandbox()
with self.assertRaises(SandboxExecutionError) as se:
sandbox.exec_source("import sys")
self.assertIsInstance(se.exception, SandboxExecutionError)
self.assertEqual(se.exception.exc_type, ImportError)
def test_exec_source_multiple(self):
sandbox = self.sandbox()
sandbox.exec_source('DIRS = ["foo"]')
sandbox.exec_source('DIRS += ["bar"]')
self.assertEqual(sandbox["DIRS"], ["foo", "bar"])
def test_exec_source_illegal_key_set(self):
sandbox = self.sandbox()
with self.assertRaises(SandboxExecutionError) as se:
sandbox.exec_source("ILLEGAL = True")
e = se.exception
self.assertIsInstance(e.exc_value, KeyError)
e = se.exception.exc_value
self.assertEqual(e.args[0], "global_ns")
self.assertEqual(e.args[1], "set_unknown")
def test_exec_source_reassign(self):
sandbox = self.sandbox()
sandbox.exec_source('DIRS = ["foo"]')
with self.assertRaises(SandboxExecutionError) as se:
sandbox.exec_source('DIRS = ["bar"]')
self.assertEqual(sandbox["DIRS"], ["foo"])
e = se.exception
self.assertIsInstance(e.exc_value, KeyError)
e = se.exception.exc_value
self.assertEqual(e.args[0], "global_ns")
self.assertEqual(e.args[1], "reassign")
self.assertEqual(e.args[2], "DIRS")
def test_exec_source_reassign_builtin(self):
sandbox = self.sandbox()
with self.assertRaises(SandboxExecutionError) as se:
sandbox.exec_source("sorted = 1")
e = se.exception
self.assertIsInstance(e.exc_value, KeyError)
e = se.exception.exc_value
self.assertEqual(e.args[0], "Cannot reassign builtins")
class TestedSandbox(MozbuildSandbox):
"""Version of MozbuildSandbox with a little more convenience for testing.
It automatically normalizes paths given to exec_file and exec_source. This
helps simplify the test code.
"""
def normalize_path(self, path):
return mozpath.normpath(mozpath.join(self._context.config.topsrcdir, path))
def source_path(self, path):
return SourcePath(self._context, path)
def exec_file(self, path):
super(TestedSandbox, self).exec_file(self.normalize_path(path))
def exec_source(self, source, path=""):
super(TestedSandbox, self).exec_source(
source, self.normalize_path(path) if path else ""
)
class TestMozbuildSandbox(unittest.TestCase):
def sandbox(self, data_path=None, metadata={}):
config = None
if data_path is not None:
config = MockConfig(mozpath.join(test_data_path, data_path))
else:
config = MockConfig()
return TestedSandbox(Context(VARIABLES, config), metadata)
def test_default_state(self):
sandbox = self.sandbox()
sandbox._context.add_source(sandbox.normalize_path("moz.build"))
config = sandbox._context.config
self.assertEqual(sandbox["TOPSRCDIR"], config.topsrcdir)
self.assertEqual(sandbox["TOPOBJDIR"], config.topobjdir)
self.assertEqual(sandbox["RELATIVEDIR"], "")
self.assertEqual(sandbox["SRCDIR"], config.topsrcdir)
self.assertEqual(sandbox["OBJDIR"], config.topobjdir)
def test_symbol_presence(self):
# Ensure no discrepancies between the master symbol table and what's in
# the sandbox.
sandbox = self.sandbox()
sandbox._context.add_source(sandbox.normalize_path("moz.build"))
all_symbols = set()
all_symbols |= set(FUNCTIONS.keys())
all_symbols |= set(SPECIAL_VARIABLES.keys())
for symbol in all_symbols:
self.assertIsNotNone(sandbox[symbol])
def test_path_calculation(self):
sandbox = self.sandbox()
sandbox._context.add_source(sandbox.normalize_path("foo/bar/moz.build"))
config = sandbox._context.config
self.assertEqual(sandbox["TOPSRCDIR"], config.topsrcdir)
self.assertEqual(sandbox["TOPOBJDIR"], config.topobjdir)
self.assertEqual(sandbox["RELATIVEDIR"], "foo/bar")
self.assertEqual(sandbox["SRCDIR"], mozpath.join(config.topsrcdir, "foo/bar"))
self.assertEqual(sandbox["OBJDIR"], mozpath.join(config.topobjdir, "foo/bar"))
def test_config_access(self):
sandbox = self.sandbox()
config = sandbox._context.config
self.assertEqual(sandbox["CONFIG"]["MOZ_TRUE"], "1")
self.assertEqual(sandbox["CONFIG"]["MOZ_FOO"], config.substs["MOZ_FOO"])
# Access to an undefined substitution should return None.
self.assertNotIn("MISSING", sandbox["CONFIG"])
self.assertIsNone(sandbox["CONFIG"]["MISSING"])
# Should shouldn't be allowed to assign to the config.
with self.assertRaises(Exception):
sandbox["CONFIG"]["FOO"] = ""
def test_special_variables(self):
sandbox = self.sandbox()
sandbox._context.add_source(sandbox.normalize_path("moz.build"))
for k in SPECIAL_VARIABLES:
with self.assertRaises(KeyError):
sandbox[k] = 0
def test_exec_source_reassign_exported(self):
template_sandbox = self.sandbox(data_path="templates")
# Templates need to be defined in actual files because of
# inspect.getsourcelines.
template_sandbox.exec_file("templates.mozbuild")
config = MockConfig()
exports = {"DIST_SUBDIR": "browser"}
sandbox = TestedSandbox(
Context(VARIABLES, config),
metadata={
"exports": exports,
"templates": template_sandbox.templates,
},
)
self.assertEqual(sandbox["DIST_SUBDIR"], "browser")
# Templates should not interfere
sandbox.exec_source("Template([])", "foo.mozbuild")
sandbox.exec_source('DIST_SUBDIR = "foo"')
with self.assertRaises(SandboxExecutionError) as se:
sandbox.exec_source('DIST_SUBDIR = "bar"')
self.assertEqual(sandbox["DIST_SUBDIR"], "foo")
e = se.exception
self.assertIsInstance(e.exc_value, KeyError)
e = se.exception.exc_value
self.assertEqual(e.args[0], "global_ns")
self.assertEqual(e.args[1], "reassign")
self.assertEqual(e.args[2], "DIST_SUBDIR")
def test_include_basic(self):
sandbox = self.sandbox(data_path="include-basic")
sandbox.exec_file("moz.build")
self.assertEqual(
sandbox["DIRS"],
[
sandbox.source_path("foo"),
sandbox.source_path("bar"),
],
)
self.assertEqual(
sandbox._context.main_path, sandbox.normalize_path("moz.build")
)
self.assertEqual(len(sandbox._context.all_paths), 2)
def test_include_outside_topsrcdir(self):
sandbox = self.sandbox(data_path="include-outside-topsrcdir")
with self.assertRaises(SandboxLoadError) as se:
sandbox.exec_file("relative.build")
self.assertEqual(
se.exception.illegal_path, sandbox.normalize_path("../moz.build")
)
def test_include_error_stack(self):
# Ensure the path stack is reported properly in exceptions.
sandbox = self.sandbox(data_path="include-file-stack")
with self.assertRaises(SandboxExecutionError) as se:
sandbox.exec_file("moz.build")
e = se.exception
self.assertIsInstance(e.exc_value, KeyError)
args = e.exc_value.args
self.assertEqual(args[0], "global_ns")
self.assertEqual(args[1], "set_unknown")
self.assertEqual(args[2], "ILLEGAL")
expected_stack = [
mozpath.join(sandbox._context.config.topsrcdir, p)
for p in ["moz.build", "included-1.build", "included-2.build"]
]
self.assertEqual(e.file_stack, expected_stack)
def test_include_missing(self):
sandbox = self.sandbox(data_path="include-missing")
with self.assertRaises(SandboxLoadError) as sle:
sandbox.exec_file("moz.build")
self.assertIsNotNone(sle.exception.read_error)
def test_include_relative_from_child_dir(self):
# A relative path from a subdirectory should be relative from that
# child directory.
sandbox = self.sandbox(data_path="include-relative-from-child")
sandbox.exec_file("child/child.build")
self.assertEqual(sandbox["DIRS"], [sandbox.source_path("../foo")])
sandbox = self.sandbox(data_path="include-relative-from-child")
sandbox.exec_file("child/child2.build")
self.assertEqual(sandbox["DIRS"], [sandbox.source_path("../foo")])
def test_include_topsrcdir_relative(self):
# An absolute path for include() is relative to topsrcdir.
sandbox = self.sandbox(data_path="include-topsrcdir-relative")
sandbox.exec_file("moz.build")
self.assertEqual(sandbox["DIRS"], [sandbox.source_path("foo")])
def test_error(self):
sandbox = self.sandbox()
with self.assertRaises(SandboxCalledError) as sce:
sandbox.exec_source('error("This is an error.")')
e = sce.exception.message
self.assertIn("This is an error.", str(e))
def test_substitute_config_files(self):
sandbox = self.sandbox()
sandbox._context.add_source(sandbox.normalize_path("moz.build"))
sandbox.exec_source('CONFIGURE_SUBST_FILES += ["bar", "foo"]')
self.assertEqual(sandbox["CONFIGURE_SUBST_FILES"], ["bar", "foo"])
for item in sandbox["CONFIGURE_SUBST_FILES"]:
self.assertIsInstance(item, SourcePath)
def test_invalid_exports_set_base(self):
sandbox = self.sandbox()
with self.assertRaises(SandboxExecutionError) as se:
sandbox.exec_source('EXPORTS = "foo.h"')
self.assertEqual(se.exception.exc_type, ValueError)
def test_templates(self):
sandbox = self.sandbox(data_path="templates")
# Templates need to be defined in actual files because of
# inspect.getsourcelines.
sandbox.exec_file("templates.mozbuild")
sandbox2 = self.sandbox(metadata={"templates": sandbox.templates})
source = """
Template([
'foo.cpp',
])
"""
sandbox2.exec_source(source, "foo.mozbuild")
self.assertEqual(
sandbox2._context,
{
"SOURCES": ["foo.cpp"],
"DIRS": [],
},
)
sandbox2 = self.sandbox(metadata={"templates": sandbox.templates})
source = """
SOURCES += ['qux.cpp']
Template([
'bar.cpp',
'foo.cpp',
],[
'foo',
])
SOURCES += ['hoge.cpp']
"""
sandbox2.exec_source(source, "foo.mozbuild")
self.assertEqual(
sandbox2._context,
{
"SOURCES": ["qux.cpp", "bar.cpp", "foo.cpp", "hoge.cpp"],
"DIRS": [sandbox2.source_path("foo")],
},
)
sandbox2 = self.sandbox(metadata={"templates": sandbox.templates})
source = """
TemplateError([
'foo.cpp',
])
"""
with self.assertRaises(SandboxExecutionError) as se:
sandbox2.exec_source(source, "foo.mozbuild")
e = se.exception
self.assertIsInstance(e.exc_value, KeyError)
e = se.exception.exc_value
self.assertEqual(e.args[0], "global_ns")
self.assertEqual(e.args[1], "set_unknown")
# TemplateGlobalVariable tries to access 'illegal' but that is expected
# to throw.
sandbox2 = self.sandbox(metadata={"templates": sandbox.templates})
source = """
illegal = True
TemplateGlobalVariable()
"""
with self.assertRaises(SandboxExecutionError) as se:
sandbox2.exec_source(source, "foo.mozbuild")
e = se.exception
self.assertIsInstance(e.exc_value, NameError)
# TemplateGlobalUPPERVariable sets SOURCES with DIRS, but the context
# used when running the template is not expected to access variables
# from the global context.
sandbox2 = self.sandbox(metadata={"templates": sandbox.templates})
source = """
DIRS += ['foo']
TemplateGlobalUPPERVariable()
"""
sandbox2.exec_source(source, "foo.mozbuild")
self.assertEqual(
sandbox2._context,
{
"SOURCES": [],
"DIRS": [sandbox2.source_path("foo")],
},
)
# However, the result of the template is mixed with the global
# context.
sandbox2 = self.sandbox(metadata={"templates": sandbox.templates})
source = """
SOURCES += ['qux.cpp']
TemplateInherit([
'bar.cpp',
'foo.cpp',
])
SOURCES += ['hoge.cpp']
"""
sandbox2.exec_source(source, "foo.mozbuild")
self.assertEqual(
sandbox2._context,
{
"SOURCES": ["qux.cpp", "bar.cpp", "foo.cpp", "hoge.cpp"],
"USE_LIBS": ["foo"],
"DIRS": [],
},
)
# Template names must be CamelCase. Here, we can define the template
# inline because the error happens before inspect.getsourcelines.
sandbox2 = self.sandbox(metadata={"templates": sandbox.templates})
source = """
@template
def foo():
pass
"""
with self.assertRaises(SandboxExecutionError) as se:
sandbox2.exec_source(source, "foo.mozbuild")
e = se.exception
self.assertIsInstance(e.exc_value, NameError)
e = se.exception.exc_value
self.assertIn("Template function names must be CamelCase.", str(e))
# Template names must not already be registered.
sandbox2 = self.sandbox(metadata={"templates": sandbox.templates})
source = """
@template
def Template():
pass
"""
with self.assertRaises(SandboxExecutionError) as se:
sandbox2.exec_source(source, "foo.mozbuild")
e = se.exception
self.assertIsInstance(e.exc_value, KeyError)
e = se.exception.exc_value
self.assertIn(
'A template named "Template" was already declared in %s.'
% sandbox.normalize_path("templates.mozbuild"),
str(e),
)
def test_function_args(self):
class Foo(int):
pass
def foo(a, b):
return type(a), type(b)
FUNCTIONS.update(
{
"foo": (lambda self: foo, (Foo, int), ""),
}
)
try:
sandbox = self.sandbox()
source = 'foo("a", "b")'
with self.assertRaises(SandboxExecutionError) as se:
sandbox.exec_source(source, "foo.mozbuild")
e = se.exception
self.assertIsInstance(e.exc_value, ValueError)
sandbox = self.sandbox()
source = 'foo(1, "b")'
with self.assertRaises(SandboxExecutionError) as se:
sandbox.exec_source(source, "foo.mozbuild")
e = se.exception
self.assertIsInstance(e.exc_value, ValueError)
sandbox = self.sandbox()
source = "a = foo(1, 2)"
sandbox.exec_source(source, "foo.mozbuild")
self.assertEqual(sandbox["a"], (Foo, int))
finally:
del FUNCTIONS["foo"]
if __name__ == "__main__":
main()