# 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 unittest
import six
from mozpack
import path
as mozpath
from mozunit
import main
from mozbuild.frontend.context
import (
FUNCTIONS,
SPECIAL_VARIABLES,
SUBCONTEXTS,
VARIABLES,
AbsolutePath,
Context,
ContextDerivedTypedHierarchicalStringList,
ContextDerivedTypedList,
ContextDerivedTypedListWithItems,
ContextDerivedTypedRecord,
Files,
ObjDirPath,
Path,
SourcePath,
)
from mozbuild.util
import StrictOrderingOnAppendListWithFlagsFactory
class TestContext(unittest.TestCase):
def test_defaults(self):
test = Context(
{
"foo": (int, int,
""),
"bar": (bool, bool,
""),
"baz": (dict, dict,
""),
}
)
self.assertEqual(list(test), [])
self.assertEqual(test[
"foo"], 0)
self.assertEqual(set(test.keys()), {
"foo"})
self.assertEqual(test[
"bar"],
False)
self.assertEqual(set(test.keys()), {
"foo",
"bar"})
self.assertEqual(test[
"baz"], {})
self.assertEqual(set(test.keys()), {
"foo",
"bar",
"baz"})
with self.assertRaises(KeyError):
test[
"qux"]
self.assertEqual(set(test.keys()), {
"foo",
"bar",
"baz"})
def test_type_check(self):
test = Context(
{
"foo": (int, int,
""),
"baz": (dict, list,
""),
}
)
test[
"foo"] = 5
self.assertEqual(test[
"foo"], 5)
with self.assertRaises(ValueError):
test[
"foo"] = {}
self.assertEqual(test[
"foo"], 5)
with self.assertRaises(KeyError):
test[
"bar"] =
True
test[
"baz"] = [(
"a", 1), (
"b", 2)]
self.assertEqual(test[
"baz"], {
"a": 1,
"b": 2})
def test_update(self):
test = Context(
{
"foo": (int, int,
""),
"bar": (bool, bool,
""),
"baz": (dict, list,
""),
}
)
self.assertEqual(list(test), [])
with self.assertRaises(ValueError):
test.update(bar=
True, foo={})
self.assertEqual(list(test), [])
test.update(bar=
True, foo=1)
self.assertEqual(set(test.keys()), {
"foo",
"bar"})
self.assertEqual(test[
"foo"], 1)
self.assertEqual(test[
"bar"],
True)
test.update([(
"bar",
False), (
"foo", 2)])
self.assertEqual(test[
"foo"], 2)
self.assertEqual(test[
"bar"],
False)
test.update([(
"foo", 0), (
"baz", {
"a": 1,
"b": 2})])
self.assertEqual(test[
"foo"], 0)
self.assertEqual(test[
"baz"], {
"a": 1,
"b": 2})
test.update([(
"foo", 42), (
"baz", [(
"c", 3), (
"d", 4)])])
self.assertEqual(test[
"foo"], 42)
self.assertEqual(test[
"baz"], {
"c": 3,
"d": 4})
def test_context_paths(self):
test = Context()
# Newly created context has no paths.
self.assertIsNone(test.main_path)
self.assertIsNone(test.current_path)
self.assertEqual(test.all_paths, set())
self.assertEqual(test.source_stack, [])
foo = os.path.abspath(
"foo")
test.add_source(foo)
# Adding the first source makes it the main and current path.
self.assertEqual(test.main_path, foo)
self.assertEqual(test.current_path, foo)
self.assertEqual(test.all_paths, set([foo]))
self.assertEqual(test.source_stack, [foo])
bar = os.path.abspath(
"bar")
test.add_source(bar)
# Adding the second source makes leaves main and current paths alone.
self.assertEqual(test.main_path, foo)
self.assertEqual(test.current_path, foo)
self.assertEqual(test.all_paths, set([bar, foo]))
self.assertEqual(test.source_stack, [foo])
qux = os.path.abspath(
"qux")
test.push_source(qux)
# Pushing a source makes it the current path
self.assertEqual(test.main_path, foo)
self.assertEqual(test.current_path, qux)
self.assertEqual(test.all_paths, set([bar, foo, qux]))
self.assertEqual(test.source_stack, [foo, qux])
hoge = os.path.abspath(
"hoge")
test.push_source(hoge)
self.assertEqual(test.main_path, foo)
self.assertEqual(test.current_path, hoge)
self.assertEqual(test.all_paths, set([bar, foo, hoge, qux]))
self.assertEqual(test.source_stack, [foo, qux, hoge])
fuga = os.path.abspath(
"fuga")
# Adding a source after pushing doesn't change the source stack
test.add_source(fuga)
self.assertEqual(test.main_path, foo)
self.assertEqual(test.current_path, hoge)
self.assertEqual(test.all_paths, set([bar, foo, fuga, hoge, qux]))
self.assertEqual(test.source_stack, [foo, qux, hoge])
# Adding a source twice doesn't change anything
test.add_source(qux)
self.assertEqual(test.main_path, foo)
self.assertEqual(test.current_path, hoge)
self.assertEqual(test.all_paths, set([bar, foo, fuga, hoge, qux]))
self.assertEqual(test.source_stack, [foo, qux, hoge])
last = test.pop_source()
# Popping a source returns the last pushed one, not the last added one.
self.assertEqual(last, hoge)
self.assertEqual(test.main_path, foo)
self.assertEqual(test.current_path, qux)
self.assertEqual(test.all_paths, set([bar, foo, fuga, hoge, qux]))
self.assertEqual(test.source_stack, [foo, qux])
last = test.pop_source()
self.assertEqual(last, qux)
self.assertEqual(test.main_path, foo)
self.assertEqual(test.current_path, foo)
self.assertEqual(test.all_paths, set([bar, foo, fuga, hoge, qux]))
self.assertEqual(test.source_stack, [foo])
# Popping the main path is allowed.
last = test.pop_source()
self.assertEqual(last, foo)
self.assertEqual(test.main_path, foo)
self.assertIsNone(test.current_path)
self.assertEqual(test.all_paths, set([bar, foo, fuga, hoge, qux]))
self.assertEqual(test.source_stack, [])
# Popping past the main path asserts.
with self.assertRaises(AssertionError):
test.pop_source()
# Pushing after the main path was popped asserts.
with self.assertRaises(AssertionError):
test.push_source(foo)
test = Context()
test.push_source(foo)
test.push_source(bar)
# Pushing the same file twice is allowed.
test.push_source(bar)
test.push_source(foo)
self.assertEqual(last, foo)
self.assertEqual(test.main_path, foo)
self.assertEqual(test.current_path, foo)
self.assertEqual(test.all_paths, set([bar, foo]))
self.assertEqual(test.source_stack, [foo, bar, bar, foo])
def test_context_dirs(self):
class Config(object):
pass
config = Config()
config.topsrcdir = mozpath.abspath(os.curdir)
config.topobjdir = mozpath.abspath(
"obj")
test = Context(config=config)
foo = mozpath.abspath(
"foo")
test.push_source(foo)
self.assertEqual(test.srcdir, config.topsrcdir)
self.assertEqual(test.relsrcdir,
"")
self.assertEqual(test.objdir, config.topobjdir)
self.assertEqual(test.relobjdir,
"")
foobar = os.path.abspath(
"foo/bar")
test.push_source(foobar)
self.assertEqual(test.srcdir, mozpath.join(config.topsrcdir,
"foo"))
self.assertEqual(test.relsrcdir,
"foo")
self.assertEqual(test.objdir, config.topobjdir)
self.assertEqual(test.relobjdir,
"")
class TestSymbols(unittest.TestCase):
def _verify_doc(self, doc):
# Documentation should be of the format:
# """SUMMARY LINE
#
# EXTRA PARAGRAPHS
# """
self.assertNotIn(
"\r", doc)
lines = doc.split(
"\n")
# No trailing whitespace.
for line
in lines[0:-1]:
self.assertEqual(line, line.rstrip())
self.assertGreater(len(lines), 0)
self.assertGreater(len(lines[0].strip()), 0)
# Last line should be empty.
self.assertEqual(lines[-1].strip(),
"")
def test_documentation_formatting(self):
for typ, inp, doc
in VARIABLES.values():
self._verify_doc(doc)
for attr, args, doc
in FUNCTIONS.values():
self._verify_doc(doc)
for func, typ, doc
in SPECIAL_VARIABLES.values():
self._verify_doc(doc)
for name, cls
in SUBCONTEXTS.items():
self._verify_doc(cls.__doc__)
for name, v
in cls.VARIABLES.items():
self._verify_doc(v[2])
class TestPaths(unittest.TestCase):
@classmethod
def setUpClass(cls):
class Config(object):
pass
cls.config = config = Config()
config.topsrcdir = mozpath.abspath(os.curdir)
config.topobjdir = mozpath.abspath(
"obj")
def test_path(self):
config = self.config
ctxt1 = Context(config=config)
ctxt1.push_source(mozpath.join(config.topsrcdir,
"foo",
"moz.build"))
ctxt2 = Context(config=config)
ctxt2.push_source(mozpath.join(config.topsrcdir,
"bar",
"moz.build"))
path1 = Path(ctxt1,
"qux")
self.assertIsInstance(path1, SourcePath)
self.assertEqual(path1,
"qux")
self.assertEqual(path1.full_path, mozpath.join(config.topsrcdir,
"foo",
"qux"))
path2 = Path(ctxt2,
"../foo/qux")
self.assertIsInstance(path2, SourcePath)
self.assertEqual(path2,
"../foo/qux")
self.assertEqual(path2.full_path, mozpath.join(config.topsrcdir,
"foo",
"qux"))
self.assertEqual(path1, path2)
self.assertEqual(
path1.join(
"../../bar/qux").full_path,
mozpath.join(config.topsrcdir,
"bar",
"qux"),
)
path1 = Path(ctxt1,
"/qux/qux")
self.assertIsInstance(path1, SourcePath)
self.assertEqual(path1,
"/qux/qux")
self.assertEqual(path1.full_path, mozpath.join(config.topsrcdir,
"qux",
"qux"))
path2 = Path(ctxt2,
"/qux/qux")
self.assertIsInstance(path2, SourcePath)
self.assertEqual(path2,
"/qux/qux")
self.assertEqual(path2.full_path, mozpath.join(config.topsrcdir,
"qux",
"qux"))
self.assertEqual(path1, path2)
path1 = Path(ctxt1,
"!qux")
self.assertIsInstance(path1, ObjDirPath)
self.assertEqual(path1,
"!qux")
self.assertEqual(path1.full_path, mozpath.join(config.topobjdir,
"foo",
"qux"))
path2 = Path(ctxt2,
"!../foo/qux")
self.assertIsInstance(path2, ObjDirPath)
self.assertEqual(path2,
"!../foo/qux")
self.assertEqual(path2.full_path, mozpath.join(config.topobjdir,
"foo",
"qux"))
self.assertEqual(path1, path2)
path1 = Path(ctxt1,
"!/qux/qux")
self.assertIsInstance(path1, ObjDirPath)
self.assertEqual(path1,
"!/qux/qux")
self.assertEqual(path1.full_path, mozpath.join(config.topobjdir,
"qux",
"qux"))
path2 = Path(ctxt2,
"!/qux/qux")
self.assertIsInstance(path2, ObjDirPath)
self.assertEqual(path2,
"!/qux/qux")
self.assertEqual(path2.full_path, mozpath.join(config.topobjdir,
"qux",
"qux"))
self.assertEqual(path1, path2)
path1 = Path(ctxt1, path1)
self.assertIsInstance(path1, ObjDirPath)
self.assertEqual(path1,
"!/qux/qux")
self.assertEqual(path1.full_path, mozpath.join(config.topobjdir,
"qux",
"qux"))
path2 = Path(ctxt2, path2)
self.assertIsInstance(path2, ObjDirPath)
self.assertEqual(path2,
"!/qux/qux")
self.assertEqual(path2.full_path, mozpath.join(config.topobjdir,
"qux",
"qux"))
self.assertEqual(path1, path2)
path1 = Path(path1)
self.assertIsInstance(path1, ObjDirPath)
self.assertEqual(path1,
"!/qux/qux")
self.assertEqual(path1.full_path, mozpath.join(config.topobjdir,
"qux",
"qux"))
self.assertEqual(path1, path2)
path2 = Path(path2)
self.assertIsInstance(path2, ObjDirPath)
self.assertEqual(path2,
"!/qux/qux")
self.assertEqual(path2.full_path, mozpath.join(config.topobjdir,
"qux",
"qux"))
self.assertEqual(path1, path2)
def test_source_path(self):
config = self.config
ctxt = Context(config=config)
ctxt.push_source(mozpath.join(config.topsrcdir,
"foo",
"moz.build"))
path = SourcePath(ctxt,
"qux")
self.assertEqual(path,
"qux")
self.assertEqual(path.full_path, mozpath.join(config.topsrcdir,
"foo",
"qux"))
self.assertEqual(path.translated, mozpath.join(config.topobjdir,
"foo",
"qux"))
path = SourcePath(ctxt,
"../bar/qux")
self.assertEqual(path,
"../bar/qux")
self.assertEqual(path.full_path, mozpath.join(config.topsrcdir,
"bar",
"qux"))
self.assertEqual(path.translated, mozpath.join(config.topobjdir,
"bar",
"qux"))
path = SourcePath(ctxt,
"/qux/qux")
self.assertEqual(path,
"/qux/qux")
self.assertEqual(path.full_path, mozpath.join(config.topsrcdir,
"qux",
"qux"))
self.assertEqual(path.translated, mozpath.join(config.topobjdir,
"qux",
"qux"))
with self.assertRaises(ValueError):
SourcePath(ctxt,
"!../bar/qux")
with self.assertRaises(ValueError):
SourcePath(ctxt,
"!/qux/qux")
path = SourcePath(path)
self.assertIsInstance(path, SourcePath)
self.assertEqual(path,
"/qux/qux")
self.assertEqual(path.full_path, mozpath.join(config.topsrcdir,
"qux",
"qux"))
self.assertEqual(path.translated, mozpath.join(config.topobjdir,
"qux",
"qux"))
path = Path(path)
self.assertIsInstance(path, SourcePath)
def test_objdir_path(self):
config = self.config
ctxt = Context(config=config)
ctxt.push_source(mozpath.join(config.topsrcdir,
"foo",
"moz.build"))
path = ObjDirPath(ctxt,
"!qux")
self.assertEqual(path,
"!qux")
self.assertEqual(path.full_path, mozpath.join(config.topobjdir,
"foo",
"qux"))
path = ObjDirPath(ctxt,
"!../bar/qux")
self.assertEqual(path,
"!../bar/qux")
self.assertEqual(path.full_path, mozpath.join(config.topobjdir,
"bar",
"qux"))
path = ObjDirPath(ctxt,
"!/qux/qux")
self.assertEqual(path,
"!/qux/qux")
self.assertEqual(path.full_path, mozpath.join(config.topobjdir,
"qux",
"qux"))
with self.assertRaises(ValueError):
path = ObjDirPath(ctxt,
"../bar/qux")
with self.assertRaises(ValueError):
path = ObjDirPath(ctxt,
"/qux/qux")
path = ObjDirPath(path)
self.assertIsInstance(path, ObjDirPath)
self.assertEqual(path,
"!/qux/qux")
self.assertEqual(path.full_path, mozpath.join(config.topobjdir,
"qux",
"qux"))
path = Path(path)
self.assertIsInstance(path, ObjDirPath)
def test_absolute_path(self):
config = self.config
ctxt = Context(config=config)
ctxt.push_source(mozpath.join(config.topsrcdir,
"foo",
"moz.build"))
path = AbsolutePath(ctxt,
"%/qux")
self.assertEqual(path,
"%/qux")
self.assertEqual(path.full_path,
"/qux")
with self.assertRaises(ValueError):
path = AbsolutePath(ctxt,
"%qux")
def test_path_with_mixed_contexts(self):
config = self.config
ctxt1 = Context(config=config)
ctxt1.push_source(mozpath.join(config.topsrcdir,
"foo",
"moz.build"))
ctxt2 = Context(config=config)
ctxt2.push_source(mozpath.join(config.topsrcdir,
"bar",
"moz.build"))
path1 = Path(ctxt1,
"qux")
path2 = Path(ctxt2, path1)
self.assertEqual(path2, path1)
self.assertEqual(path2,
"qux")
self.assertEqual(path2.context, ctxt1)
self.assertEqual(path2.full_path, mozpath.join(config.topsrcdir,
"foo",
"qux"))
path1 = Path(ctxt1,
"../bar/qux")
path2 = Path(ctxt2, path1)
self.assertEqual(path2, path1)
self.assertEqual(path2,
"../bar/qux")
self.assertEqual(path2.context, ctxt1)
self.assertEqual(path2.full_path, mozpath.join(config.topsrcdir,
"bar",
"qux"))
path1 = Path(ctxt1,
"/qux/qux")
path2 = Path(ctxt2, path1)
self.assertEqual(path2, path1)
self.assertEqual(path2,
"/qux/qux")
self.assertEqual(path2.context, ctxt1)
self.assertEqual(path2.full_path, mozpath.join(config.topsrcdir,
"qux",
"qux"))
path1 = Path(ctxt1,
"!qux")
path2 = Path(ctxt2, path1)
self.assertEqual(path2, path1)
self.assertEqual(path2,
"!qux")
self.assertEqual(path2.context, ctxt1)
self.assertEqual(path2.full_path, mozpath.join(config.topobjdir,
"foo",
"qux"))
path1 = Path(ctxt1,
"!../bar/qux")
path2 = Path(ctxt2, path1)
self.assertEqual(path2, path1)
self.assertEqual(path2,
"!../bar/qux")
self.assertEqual(path2.context, ctxt1)
self.assertEqual(path2.full_path, mozpath.join(config.topobjdir,
"bar",
"qux"))
path1 = Path(ctxt1,
"!/qux/qux")
path2 = Path(ctxt2, path1)
self.assertEqual(path2, path1)
self.assertEqual(path2,
"!/qux/qux")
self.assertEqual(path2.context, ctxt1)
self.assertEqual(path2.full_path, mozpath.join(config.topobjdir,
"qux",
"qux"))
def test_path_typed_list(self):
config = self.config
ctxt1 = Context(config=config)
ctxt1.push_source(mozpath.join(config.topsrcdir,
"foo",
"moz.build"))
ctxt2 = Context(config=config)
ctxt2.push_source(mozpath.join(config.topsrcdir,
"bar",
"moz.build"))
paths = [
"!../bar/qux",
"!/qux/qux",
"!qux",
"../bar/qux",
"/qux/qux",
"qux",
]
MyList = ContextDerivedTypedList(Path)
l = MyList(ctxt1)
l += paths
for p_str, p_path
in zip(paths, l):
self.assertEqual(p_str, p_path)
self.assertEqual(p_path, Path(ctxt1, p_str))
self.assertEqual(
p_path.join(
"foo"), Path(ctxt1, mozpath.join(p_str,
"foo"))
)
l2 = MyList(ctxt2)
l2 += paths
for p_str, p_path
in zip(paths, l2):
self.assertEqual(p_str, p_path)
self.assertEqual(p_path, Path(ctxt2, p_str))
# Assigning with Paths from another context doesn't rebase them
l2 = MyList(ctxt2)
l2 += l
for p_str, p_path
in zip(paths, l2):
self.assertEqual(p_str, p_path)
self.assertEqual(p_path, Path(ctxt1, p_str))
MyListWithFlags = ContextDerivedTypedListWithItems(
Path,
StrictOrderingOnAppendListWithFlagsFactory(
{
"foo": bool,
}
),
)
l = MyListWithFlags(ctxt1)
l += paths
for p
in paths:
l[p].foo =
True
for p_str, p_path
in zip(paths, l):
self.assertEqual(p_str, p_path)
self.assertEqual(p_path, Path(ctxt1, p_str))
self.assertEqual(l[p_str].foo,
True)
self.assertEqual(l[p_path].foo,
True)
def test_path_typed_hierarchy_list(self):
config = self.config
ctxt1 = Context(config=config)
ctxt1.push_source(mozpath.join(config.topsrcdir,
"foo",
"moz.build"))
ctxt2 = Context(config=config)
ctxt2.push_source(mozpath.join(config.topsrcdir,
"bar",
"moz.build"))
paths = [
"!../bar/qux",
"!/qux/qux",
"!qux",
"../bar/qux",
"/qux/qux",
"qux",
]
MyList = ContextDerivedTypedHierarchicalStringList(Path)
l = MyList(ctxt1)
l += paths
l.subdir += paths
for _, files
in l.walk():
for p_str, p_path
in zip(paths, files):
self.assertEqual(p_str, p_path)
self.assertEqual(p_path, Path(ctxt1, p_str))
self.assertEqual(
p_path.join(
"foo"), Path(ctxt1, mozpath.join(p_str,
"foo"))
)
l2 = MyList(ctxt2)
l2 += paths
l2.subdir += paths
for _, files
in l2.walk():
for p_str, p_path
in zip(paths, files):
self.assertEqual(p_str, p_path)
self.assertEqual(p_path, Path(ctxt2, p_str))
# Assigning with Paths from another context doesn't rebase them
l2 = MyList(ctxt2)
l2 += l
for _, files
in l2.walk():
for p_str, p_path
in zip(paths, files):
self.assertEqual(p_str, p_path)
self.assertEqual(p_path, Path(ctxt1, p_str))
class TestTypedRecord(unittest.TestCase):
def test_fields(self):
T = ContextDerivedTypedRecord((
"field1", six.text_type), (
"field2", list))
inst = T(
None)
self.assertEqual(inst.field1,
"")
self.assertEqual(inst.field2, [])
inst.field1 =
"foo"
inst.field2 += [
"bar"]
self.assertEqual(inst.field1,
"foo")
self.assertEqual(inst.field2, [
"bar"])
with self.assertRaises(AttributeError):
inst.field3 = []
def test_coercion(self):
T = ContextDerivedTypedRecord((
"field1", six.text_type), (
"field2", list))
inst = T(
None)
inst.field1 = 3
inst.field2 += (
"bar",)
self.assertEqual(inst.field1,
"3")
self.assertEqual(inst.field2, [
"bar"])
with self.assertRaises(TypeError):
inst.field2 = object()
class TestFiles(unittest.TestCase):
def test_aggregate_empty(self):
c = Context({})
files = {
"moz.build": Files(c,
"**")}
self.assertEqual(
Files.aggregate(files),
{
"bug_component_counts": [],
"recommended_bug_component":
None,
},
)
def test_single_bug_component(self):
c = Context({})
f = Files(c,
"**")
f[
"BUG_COMPONENT"] = (
"Product1",
"Component1")
files = {
"moz.build": f}
self.assertEqual(
Files.aggregate(files),
{
"bug_component_counts": [((
"Product1",
"Component1"), 1)],
"recommended_bug_component": (
"Product1",
"Component1"),
},
)
def test_multiple_bug_components(self):
c = Context({})
f1 = Files(c,
"**")
f1[
"BUG_COMPONENT"] = (
"Product1",
"Component1")
f2 = Files(c,
"**")
f2[
"BUG_COMPONENT"] = (
"Product2",
"Component2")
files = {
"a": f1,
"b": f2,
"c": f1}
self.assertEqual(
Files.aggregate(files),
{
"bug_component_counts": [
((
"Product1",
"Component1"), 2),
((
"Product2",
"Component2"), 1),
],
"recommended_bug_component": (
"Product1",
"Component1"),
},
)
def test_no_recommended_bug_component(self):
"""If there is no clear count winner, we don't recommend a bug component."""
c = Context({})
f1 = Files(c,
"**")
f1[
"BUG_COMPONENT"] = (
"Product1",
"Component1")
f2 = Files(c,
"**")
f2[
"BUG_COMPONENT"] = (
"Product2",
"Component2")
files = {
"a": f1,
"b": f2}
self.assertEqual(
Files.aggregate(files),
{
"bug_component_counts": [
((
"Product1",
"Component1"), 1),
((
"Product2",
"Component2"), 1),
],
"recommended_bug_component":
None,
},
)
def test_multiple_patterns(self):
c = Context({})
f1 = Files(c,
"a/**")
f1[
"BUG_COMPONENT"] = (
"Product1",
"Component1")
f2 = Files(c,
"b/**",
"a/bar")
f2[
"BUG_COMPONENT"] = (
"Product2",
"Component2")
files = {
"a/foo": f1,
"a/bar": f2,
"b/foo": f2}
self.assertEqual(
Files.aggregate(files),
{
"bug_component_counts": [
((
"Product2",
"Component2"), 2),
((
"Product1",
"Component1"), 1),
],
"recommended_bug_component": (
"Product2",
"Component2"),
},
)
if __name__ ==
"__main__":
main()