# 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()