diff options
Diffstat (limited to 'python/mozbuild/mozbuild/test/test_util.py')
-rw-r--r-- | python/mozbuild/mozbuild/test/test_util.py | 889 |
1 files changed, 889 insertions, 0 deletions
diff --git a/python/mozbuild/mozbuild/test/test_util.py b/python/mozbuild/mozbuild/test/test_util.py new file mode 100644 index 0000000000..9931b338b9 --- /dev/null +++ b/python/mozbuild/mozbuild/test/test_util.py @@ -0,0 +1,889 @@ +# coding: utf-8 +# 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 copy +import hashlib +import itertools +import os +import string +import sys +import unittest + +import pytest +import six +from mozfile.mozfile import NamedTemporaryFile +from mozunit import main + +from mozbuild.util import ( + EnumString, + EnumStringComparisonError, + HierarchicalStringList, + MozbuildDeletionError, + ReadOnlyDict, + StrictOrderingOnAppendList, + StrictOrderingOnAppendListWithAction, + StrictOrderingOnAppendListWithFlagsFactory, + TypedList, + TypedNamedTuple, + UnsortedError, + expand_variables, + group_unified_files, + hash_file, + hexdump, + memoize, + memoized_property, + pair, + resolve_target_to_make, +) + +if sys.version_info[0] == 3: + str_type = "str" +else: + str_type = "unicode" + +data_path = os.path.abspath(os.path.dirname(__file__)) +data_path = os.path.join(data_path, "data") + + +class TestHashing(unittest.TestCase): + def test_hash_file_known_hash(self): + """Ensure a known hash value is recreated.""" + data = b"The quick brown fox jumps over the lazy cog" + expected = "de9f2c7fd25e1b3afad3e85a0bd17d9b100db4b3" + + temp = NamedTemporaryFile() + temp.write(data) + temp.flush() + + actual = hash_file(temp.name) + + self.assertEqual(actual, expected) + + def test_hash_file_large(self): + """Ensure that hash_file seems to work with a large file.""" + data = b"x" * 1048576 + + hasher = hashlib.sha1() + hasher.update(data) + expected = hasher.hexdigest() + + temp = NamedTemporaryFile() + temp.write(data) + temp.flush() + + actual = hash_file(temp.name) + + self.assertEqual(actual, expected) + + +class TestResolveTargetToMake(unittest.TestCase): + def setUp(self): + self.topobjdir = data_path + + def assertResolve(self, path, expected): + # Handle Windows path separators. + (reldir, target) = resolve_target_to_make(self.topobjdir, path) + if reldir is not None: + reldir = reldir.replace(os.sep, "/") + if target is not None: + target = target.replace(os.sep, "/") + self.assertEqual((reldir, target), expected) + + def test_root_path(self): + self.assertResolve("/test-dir", ("test-dir", None)) + self.assertResolve("/test-dir/with", ("test-dir/with", None)) + self.assertResolve("/test-dir/without", ("test-dir", None)) + self.assertResolve("/test-dir/without/with", ("test-dir/without/with", None)) + + def test_dir(self): + self.assertResolve("test-dir", ("test-dir", None)) + self.assertResolve("test-dir/with", ("test-dir/with", None)) + self.assertResolve("test-dir/with", ("test-dir/with", None)) + self.assertResolve("test-dir/without", ("test-dir", None)) + self.assertResolve("test-dir/without/with", ("test-dir/without/with", None)) + + def test_top_level(self): + self.assertResolve("package", (None, "package")) + # Makefile handling shouldn't affect top-level targets. + self.assertResolve("Makefile", (None, "Makefile")) + + def test_regular_file(self): + self.assertResolve("test-dir/with/file", ("test-dir/with", "file")) + self.assertResolve( + "test-dir/with/without/file", ("test-dir/with", "without/file") + ) + self.assertResolve( + "test-dir/with/without/with/file", ("test-dir/with/without/with", "file") + ) + + self.assertResolve("test-dir/without/file", ("test-dir", "without/file")) + self.assertResolve( + "test-dir/without/with/file", ("test-dir/without/with", "file") + ) + self.assertResolve( + "test-dir/without/with/without/file", + ("test-dir/without/with", "without/file"), + ) + + def test_Makefile(self): + self.assertResolve("test-dir/with/Makefile", ("test-dir", "with/Makefile")) + self.assertResolve( + "test-dir/with/without/Makefile", ("test-dir/with", "without/Makefile") + ) + self.assertResolve( + "test-dir/with/without/with/Makefile", + ("test-dir/with", "without/with/Makefile"), + ) + + self.assertResolve( + "test-dir/without/Makefile", ("test-dir", "without/Makefile") + ) + self.assertResolve( + "test-dir/without/with/Makefile", ("test-dir", "without/with/Makefile") + ) + self.assertResolve( + "test-dir/without/with/without/Makefile", + ("test-dir/without/with", "without/Makefile"), + ) + + +class TestHierarchicalStringList(unittest.TestCase): + def setUp(self): + self.EXPORTS = HierarchicalStringList() + + def test_exports_append(self): + self.assertEqual(self.EXPORTS._strings, []) + self.EXPORTS += ["foo.h"] + self.assertEqual(self.EXPORTS._strings, ["foo.h"]) + self.EXPORTS += ["bar.h"] + self.assertEqual(self.EXPORTS._strings, ["foo.h", "bar.h"]) + + def test_exports_subdir(self): + self.assertEqual(self.EXPORTS._children, {}) + self.EXPORTS.foo += ["foo.h"] + six.assertCountEqual(self, self.EXPORTS._children, {"foo": True}) + self.assertEqual(self.EXPORTS.foo._strings, ["foo.h"]) + self.EXPORTS.bar += ["bar.h"] + six.assertCountEqual(self, self.EXPORTS._children, {"foo": True, "bar": True}) + self.assertEqual(self.EXPORTS.foo._strings, ["foo.h"]) + self.assertEqual(self.EXPORTS.bar._strings, ["bar.h"]) + + def test_exports_multiple_subdir(self): + self.EXPORTS.foo.bar = ["foobar.h"] + six.assertCountEqual(self, self.EXPORTS._children, {"foo": True}) + six.assertCountEqual(self, self.EXPORTS.foo._children, {"bar": True}) + six.assertCountEqual(self, self.EXPORTS.foo.bar._children, {}) + self.assertEqual(self.EXPORTS._strings, []) + self.assertEqual(self.EXPORTS.foo._strings, []) + self.assertEqual(self.EXPORTS.foo.bar._strings, ["foobar.h"]) + + def test_invalid_exports_append(self): + with self.assertRaises(ValueError) as ve: + self.EXPORTS += "foo.h" + six.assertRegex( + self, + str(ve.exception), + "Expected a list of strings, not <(?:type|class) '%s'>" % str_type, + ) + + def test_invalid_exports_set(self): + with self.assertRaises(ValueError) as ve: + self.EXPORTS.foo = "foo.h" + + six.assertRegex( + self, + str(ve.exception), + "Expected a list of strings, not <(?:type|class) '%s'>" % str_type, + ) + + def test_invalid_exports_append_base(self): + with self.assertRaises(ValueError) as ve: + self.EXPORTS += "foo.h" + + six.assertRegex( + self, + str(ve.exception), + "Expected a list of strings, not <(?:type|class) '%s'>" % str_type, + ) + + def test_invalid_exports_bool(self): + with self.assertRaises(ValueError) as ve: + self.EXPORTS += [True] + + six.assertRegex( + self, + str(ve.exception), + "Expected a list of strings, not an element of " "<(?:type|class) 'bool'>", + ) + + def test_del_exports(self): + with self.assertRaises(MozbuildDeletionError): + self.EXPORTS.foo += ["bar.h"] + del self.EXPORTS.foo + + def test_unsorted(self): + with self.assertRaises(UnsortedError): + self.EXPORTS += ["foo.h", "bar.h"] + + with self.assertRaises(UnsortedError): + self.EXPORTS.foo = ["foo.h", "bar.h"] + + with self.assertRaises(UnsortedError): + self.EXPORTS.foo += ["foo.h", "bar.h"] + + def test_reassign(self): + self.EXPORTS.foo = ["foo.h"] + + with self.assertRaises(KeyError): + self.EXPORTS.foo = ["bar.h"] + + def test_walk(self): + l = HierarchicalStringList() + l += ["root1", "root2", "root3"] + l.child1 += ["child11", "child12", "child13"] + l.child1.grandchild1 += ["grandchild111", "grandchild112"] + l.child1.grandchild2 += ["grandchild121", "grandchild122"] + l.child2.grandchild1 += ["grandchild211", "grandchild212"] + l.child2.grandchild1 += ["grandchild213", "grandchild214"] + + els = list((path, list(seq)) for path, seq in l.walk()) + self.assertEqual( + els, + [ + ("", ["root1", "root2", "root3"]), + ("child1", ["child11", "child12", "child13"]), + ("child1/grandchild1", ["grandchild111", "grandchild112"]), + ("child1/grandchild2", ["grandchild121", "grandchild122"]), + ( + "child2/grandchild1", + [ + "grandchild211", + "grandchild212", + "grandchild213", + "grandchild214", + ], + ), + ], + ) + + def test_merge(self): + l1 = HierarchicalStringList() + l1 += ["root1", "root2", "root3"] + l1.child1 += ["child11", "child12", "child13"] + l1.child1.grandchild1 += ["grandchild111", "grandchild112"] + l1.child1.grandchild2 += ["grandchild121", "grandchild122"] + l1.child2.grandchild1 += ["grandchild211", "grandchild212"] + l1.child2.grandchild1 += ["grandchild213", "grandchild214"] + l2 = HierarchicalStringList() + l2.child1 += ["child14", "child15"] + l2.child1.grandchild2 += ["grandchild123"] + l2.child3 += ["child31", "child32"] + + l1 += l2 + els = list((path, list(seq)) for path, seq in l1.walk()) + self.assertEqual( + els, + [ + ("", ["root1", "root2", "root3"]), + ("child1", ["child11", "child12", "child13", "child14", "child15"]), + ("child1/grandchild1", ["grandchild111", "grandchild112"]), + ( + "child1/grandchild2", + ["grandchild121", "grandchild122", "grandchild123"], + ), + ( + "child2/grandchild1", + [ + "grandchild211", + "grandchild212", + "grandchild213", + "grandchild214", + ], + ), + ("child3", ["child31", "child32"]), + ], + ) + + +class TestStrictOrderingOnAppendList(unittest.TestCase): + def test_init(self): + l = StrictOrderingOnAppendList() + self.assertEqual(len(l), 0) + + l = StrictOrderingOnAppendList(["a", "b", "c"]) + self.assertEqual(len(l), 3) + + with self.assertRaises(UnsortedError): + StrictOrderingOnAppendList(["c", "b", "a"]) + + self.assertEqual(len(l), 3) + + def test_extend(self): + l = StrictOrderingOnAppendList() + l.extend(["a", "b"]) + self.assertEqual(len(l), 2) + self.assertIsInstance(l, StrictOrderingOnAppendList) + + with self.assertRaises(UnsortedError): + l.extend(["d", "c"]) + + self.assertEqual(len(l), 2) + + def test_slicing(self): + l = StrictOrderingOnAppendList() + l[:] = ["a", "b"] + self.assertEqual(len(l), 2) + self.assertIsInstance(l, StrictOrderingOnAppendList) + + with self.assertRaises(UnsortedError): + l[:] = ["b", "a"] + + self.assertEqual(len(l), 2) + + def test_add(self): + l = StrictOrderingOnAppendList() + l2 = l + ["a", "b"] + self.assertEqual(len(l), 0) + self.assertEqual(len(l2), 2) + self.assertIsInstance(l2, StrictOrderingOnAppendList) + + with self.assertRaises(UnsortedError): + l2 = l + ["b", "a"] + + self.assertEqual(len(l), 0) + + def test_iadd(self): + l = StrictOrderingOnAppendList() + l += ["a", "b"] + self.assertEqual(len(l), 2) + self.assertIsInstance(l, StrictOrderingOnAppendList) + + with self.assertRaises(UnsortedError): + l += ["b", "a"] + + self.assertEqual(len(l), 2) + + def test_add_after_iadd(self): + l = StrictOrderingOnAppendList(["b"]) + l += ["a"] + l2 = l + ["c", "d"] + self.assertEqual(len(l), 2) + self.assertEqual(len(l2), 4) + self.assertIsInstance(l2, StrictOrderingOnAppendList) + with self.assertRaises(UnsortedError): + l2 = l + ["d", "c"] + + self.assertEqual(len(l), 2) + + def test_add_StrictOrderingOnAppendList(self): + l = StrictOrderingOnAppendList() + l += ["c", "d"] + l += ["a", "b"] + l2 = StrictOrderingOnAppendList() + with self.assertRaises(UnsortedError): + l2 += list(l) + # Adding a StrictOrderingOnAppendList to another shouldn't throw + l2 += l + + +class TestStrictOrderingOnAppendListWithAction(unittest.TestCase): + def setUp(self): + self.action = lambda a: (a, id(a)) + + def assertSameList(self, expected, actual): + self.assertEqual(len(expected), len(actual)) + for idx, item in enumerate(actual): + self.assertEqual(item, expected[idx]) + + def test_init(self): + l = StrictOrderingOnAppendListWithAction(action=self.action) + self.assertEqual(len(l), 0) + original = ["a", "b", "c"] + l = StrictOrderingOnAppendListWithAction(["a", "b", "c"], action=self.action) + expected = [self.action(i) for i in original] + self.assertSameList(expected, l) + + with self.assertRaises(ValueError): + StrictOrderingOnAppendListWithAction("abc", action=self.action) + + with self.assertRaises(ValueError): + StrictOrderingOnAppendListWithAction() + + def test_extend(self): + l = StrictOrderingOnAppendListWithAction(action=self.action) + original = ["a", "b"] + l.extend(original) + expected = [self.action(i) for i in original] + self.assertSameList(expected, l) + + with self.assertRaises(ValueError): + l.extend("ab") + + def test_slicing(self): + l = StrictOrderingOnAppendListWithAction(action=self.action) + original = ["a", "b"] + l[:] = original + expected = [self.action(i) for i in original] + self.assertSameList(expected, l) + + with self.assertRaises(ValueError): + l[:] = "ab" + + def test_add(self): + l = StrictOrderingOnAppendListWithAction(action=self.action) + original = ["a", "b"] + l2 = l + original + expected = [self.action(i) for i in original] + self.assertSameList(expected, l2) + + with self.assertRaises(ValueError): + l + "abc" + + def test_iadd(self): + l = StrictOrderingOnAppendListWithAction(action=self.action) + original = ["a", "b"] + l += original + expected = [self.action(i) for i in original] + self.assertSameList(expected, l) + + with self.assertRaises(ValueError): + l += "abc" + + +class TestStrictOrderingOnAppendListWithFlagsFactory(unittest.TestCase): + def test_strict_ordering_on_append_list_with_flags_factory(self): + cls = StrictOrderingOnAppendListWithFlagsFactory( + { + "foo": bool, + "bar": int, + } + ) + + l = cls() + l += ["a", "b"] + + with self.assertRaises(Exception): + l["a"] = "foo" + + with self.assertRaises(Exception): + l["c"] + + self.assertEqual(l["a"].foo, False) + l["a"].foo = True + self.assertEqual(l["a"].foo, True) + + with self.assertRaises(TypeError): + l["a"].bar = "bar" + + self.assertEqual(l["a"].bar, 0) + l["a"].bar = 42 + self.assertEqual(l["a"].bar, 42) + + l["b"].foo = True + self.assertEqual(l["b"].foo, True) + + with self.assertRaises(AttributeError): + l["b"].baz = False + + l["b"].update(foo=False, bar=12) + self.assertEqual(l["b"].foo, False) + self.assertEqual(l["b"].bar, 12) + + with self.assertRaises(AttributeError): + l["b"].update(xyz=1) + + def test_strict_ordering_on_append_list_with_flags_factory_extend(self): + FooList = StrictOrderingOnAppendListWithFlagsFactory( + {"foo": bool, "bar": six.text_type} + ) + foo = FooList(["a", "b", "c"]) + foo["a"].foo = True + foo["b"].bar = "bar" + + # Don't allow extending lists with different flag definitions. + BarList = StrictOrderingOnAppendListWithFlagsFactory( + {"foo": six.text_type, "baz": bool} + ) + bar = BarList(["d", "e", "f"]) + bar["d"].foo = "foo" + bar["e"].baz = True + with self.assertRaises(ValueError): + foo + bar + with self.assertRaises(ValueError): + bar + foo + + # It's not obvious what to do with duplicate list items with possibly + # different flag values, so don't allow that case. + with self.assertRaises(ValueError): + foo + foo + + def assertExtended(l): + self.assertEqual(len(l), 6) + self.assertEqual(l["a"].foo, True) + self.assertEqual(l["b"].bar, "bar") + self.assertTrue("c" in l) + self.assertEqual(l["d"].foo, True) + self.assertEqual(l["e"].bar, "bar") + self.assertTrue("f" in l) + + # Test extend. + zot = FooList(["d", "e", "f"]) + zot["d"].foo = True + zot["e"].bar = "bar" + zot.extend(foo) + assertExtended(zot) + + # Test __add__. + zot = FooList(["d", "e", "f"]) + zot["d"].foo = True + zot["e"].bar = "bar" + assertExtended(foo + zot) + assertExtended(zot + foo) + + # Test __iadd__. + foo += zot + assertExtended(foo) + + # Test __setitem__. + foo[3:] = [] + self.assertEqual(len(foo), 3) + foo[3:] = zot + assertExtended(foo) + + +class TestMemoize(unittest.TestCase): + def test_memoize(self): + self._count = 0 + + @memoize + def wrapped(a, b): + self._count += 1 + return a + b + + self.assertEqual(self._count, 0) + self.assertEqual(wrapped(1, 1), 2) + self.assertEqual(self._count, 1) + self.assertEqual(wrapped(1, 1), 2) + self.assertEqual(self._count, 1) + self.assertEqual(wrapped(2, 1), 3) + self.assertEqual(self._count, 2) + self.assertEqual(wrapped(1, 2), 3) + self.assertEqual(self._count, 3) + self.assertEqual(wrapped(1, 2), 3) + self.assertEqual(self._count, 3) + self.assertEqual(wrapped(1, 1), 2) + self.assertEqual(self._count, 3) + + def test_memoize_method(self): + class foo(object): + def __init__(self): + self._count = 0 + + @memoize + def wrapped(self, a, b): + self._count += 1 + return a + b + + instance = foo() + refcount = sys.getrefcount(instance) + self.assertEqual(instance._count, 0) + self.assertEqual(instance.wrapped(1, 1), 2) + self.assertEqual(instance._count, 1) + self.assertEqual(instance.wrapped(1, 1), 2) + self.assertEqual(instance._count, 1) + self.assertEqual(instance.wrapped(2, 1), 3) + self.assertEqual(instance._count, 2) + self.assertEqual(instance.wrapped(1, 2), 3) + self.assertEqual(instance._count, 3) + self.assertEqual(instance.wrapped(1, 2), 3) + self.assertEqual(instance._count, 3) + self.assertEqual(instance.wrapped(1, 1), 2) + self.assertEqual(instance._count, 3) + + # Memoization of methods is expected to not keep references to + # instances, so the refcount shouldn't have changed after executing the + # memoized method. + self.assertEqual(refcount, sys.getrefcount(instance)) + + def test_memoized_property(self): + class foo(object): + def __init__(self): + self._count = 0 + + @memoized_property + def wrapped(self): + self._count += 1 + return 42 + + instance = foo() + self.assertEqual(instance._count, 0) + self.assertEqual(instance.wrapped, 42) + self.assertEqual(instance._count, 1) + self.assertEqual(instance.wrapped, 42) + self.assertEqual(instance._count, 1) + + +class TestTypedList(unittest.TestCase): + def test_init(self): + cls = TypedList(int) + l = cls() + self.assertEqual(len(l), 0) + + l = cls([1, 2, 3]) + self.assertEqual(len(l), 3) + + with self.assertRaises(ValueError): + cls([1, 2, "c"]) + + def test_extend(self): + cls = TypedList(int) + l = cls() + l.extend([1, 2]) + self.assertEqual(len(l), 2) + self.assertIsInstance(l, cls) + + with self.assertRaises(ValueError): + l.extend([3, "c"]) + + self.assertEqual(len(l), 2) + + def test_slicing(self): + cls = TypedList(int) + l = cls() + l[:] = [1, 2] + self.assertEqual(len(l), 2) + self.assertIsInstance(l, cls) + + with self.assertRaises(ValueError): + l[:] = [3, "c"] + + self.assertEqual(len(l), 2) + + def test_add(self): + cls = TypedList(int) + l = cls() + l2 = l + [1, 2] + self.assertEqual(len(l), 0) + self.assertEqual(len(l2), 2) + self.assertIsInstance(l2, cls) + + with self.assertRaises(ValueError): + l2 = l + [3, "c"] + + self.assertEqual(len(l), 0) + + def test_iadd(self): + cls = TypedList(int) + l = cls() + l += [1, 2] + self.assertEqual(len(l), 2) + self.assertIsInstance(l, cls) + + with self.assertRaises(ValueError): + l += [3, "c"] + + self.assertEqual(len(l), 2) + + def test_add_coercion(self): + objs = [] + + class Foo(object): + def __init__(self, obj): + objs.append(obj) + + cls = TypedList(Foo) + l = cls() + l += [1, 2] + self.assertEqual(len(objs), 2) + self.assertEqual(type(l[0]), Foo) + self.assertEqual(type(l[1]), Foo) + + # Adding a TypedList to a TypedList shouldn't trigger coercion again + l2 = cls() + l2 += l + self.assertEqual(len(objs), 2) + self.assertEqual(type(l2[0]), Foo) + self.assertEqual(type(l2[1]), Foo) + + # Adding a TypedList to a TypedList shouldn't even trigger the code + # that does coercion at all. + l2 = cls() + list.__setitem__(l, slice(0, -1), [1, 2]) + l2 += l + self.assertEqual(len(objs), 2) + self.assertEqual(type(l2[0]), int) + self.assertEqual(type(l2[1]), int) + + def test_memoized(self): + cls = TypedList(int) + cls2 = TypedList(str) + self.assertEqual(TypedList(int), cls) + self.assertNotEqual(cls, cls2) + + +class TypedTestStrictOrderingOnAppendList(unittest.TestCase): + def test_init(self): + class Unicode(six.text_type): + def __new__(cls, other): + if not isinstance(other, six.text_type): + raise ValueError() + return six.text_type.__new__(cls, other) + + cls = TypedList(Unicode, StrictOrderingOnAppendList) + l = cls() + self.assertEqual(len(l), 0) + + l = cls(["a", "b", "c"]) + self.assertEqual(len(l), 3) + + with self.assertRaises(UnsortedError): + cls(["c", "b", "a"]) + + with self.assertRaises(ValueError): + cls(["a", "b", 3]) + + self.assertEqual(len(l), 3) + + +class TestTypedNamedTuple(unittest.TestCase): + def test_simple(self): + FooBar = TypedNamedTuple("FooBar", [("foo", six.text_type), ("bar", int)]) + + t = FooBar(foo="foo", bar=2) + self.assertEqual(type(t), FooBar) + self.assertEqual(t.foo, "foo") + self.assertEqual(t.bar, 2) + self.assertEqual(t[0], "foo") + self.assertEqual(t[1], 2) + + FooBar("foo", 2) + + with self.assertRaises(TypeError): + FooBar("foo", "not integer") + with self.assertRaises(TypeError): + FooBar(2, 4) + + # Passing a tuple as the first argument is the same as passing multiple + # arguments. + t1 = ("foo", 3) + t2 = FooBar(t1) + self.assertEqual(type(t2), FooBar) + self.assertEqual(FooBar(t1), FooBar("foo", 3)) + + +class TestGroupUnifiedFiles(unittest.TestCase): + FILES = ["%s.cpp" % letter for letter in string.ascii_lowercase] + + def test_multiple_files(self): + mapping = list(group_unified_files(self.FILES, "Unified", "cpp", 5)) + + def check_mapping(index, expected_num_source_files): + (unified_file, source_files) = mapping[index] + + self.assertEqual(unified_file, "Unified%d.cpp" % index) + self.assertEqual(len(source_files), expected_num_source_files) + + all_files = list(itertools.chain(*[files for (_, files) in mapping])) + self.assertEqual(len(all_files), len(self.FILES)) + self.assertEqual(set(all_files), set(self.FILES)) + + expected_amounts = [5, 5, 5, 5, 5, 1] + for i, amount in enumerate(expected_amounts): + check_mapping(i, amount) + + +class TestMisc(unittest.TestCase): + def test_pair(self): + self.assertEqual(list(pair([1, 2, 3, 4, 5, 6])), [(1, 2), (3, 4), (5, 6)]) + + self.assertEqual( + list(pair([1, 2, 3, 4, 5, 6, 7])), [(1, 2), (3, 4), (5, 6), (7, None)] + ) + + def test_expand_variables(self): + self.assertEqual(expand_variables("$(var)", {"var": "value"}), "value") + + self.assertEqual( + expand_variables("$(a) and $(b)", {"a": "1", "b": "2"}), "1 and 2" + ) + + self.assertEqual( + expand_variables("$(a) and $(undefined)", {"a": "1", "b": "2"}), "1 and " + ) + + self.assertEqual( + expand_variables( + "before $(string) between $(list) after", + {"string": "abc", "list": ["a", "b", "c"]}, + ), + "before abc between a b c after", + ) + + +class TestEnumString(unittest.TestCase): + def test_string(self): + CompilerType = EnumString.subclass("gcc", "clang", "clang-cl") + + type = CompilerType("gcc") + self.assertEqual(type, "gcc") + self.assertNotEqual(type, "clang") + self.assertNotEqual(type, "clang-cl") + self.assertIn(type, ("gcc", "clang-cl")) + self.assertNotIn(type, ("clang", "clang-cl")) + + with self.assertRaises(EnumStringComparisonError): + self.assertEqual(type, "foo") + + with self.assertRaises(EnumStringComparisonError): + self.assertNotEqual(type, "foo") + + with self.assertRaises(EnumStringComparisonError): + self.assertIn(type, ("foo", "gcc")) + + with self.assertRaises(ValueError): + type = CompilerType("foo") + + +class TestHexDump(unittest.TestCase): + @unittest.skipUnless(six.PY3, "requires Python 3") + def test_hexdump(self): + self.assertEqual( + hexdump("abcdef123💩ZYXWVU".encode("utf-8")), + [ + "00 61 62 63 64 65 66 31 32 33 f0 9f 92 a9 5a 59 58 |abcdef123....ZYX|\n", + "10 57 56 55 |WVU |\n", + ], + ) + + +def test_read_only_dict(): + d = ReadOnlyDict(foo="bar") + with pytest.raises(Exception): + d["foo"] = "baz" + + with pytest.raises(Exception): + d.update({"foo": "baz"}) + + with pytest.raises(Exception): + del d["foo"] + + # ensure copy still works + d_copy = d.copy() + assert d == d_copy + # TODO Returning a dict here feels like a bug, but there are places in-tree + # relying on this behaviour. + assert isinstance(d_copy, dict) + + d_copy = copy.copy(d) + assert d == d_copy + assert isinstance(d_copy, ReadOnlyDict) + + d_copy = copy.deepcopy(d) + assert d == d_copy + assert isinstance(d_copy, ReadOnlyDict) + + +if __name__ == "__main__": + main() |