blob: b8d98b6b158c59e44610e9df935b93364d31b94b [file] [log] [blame]
# 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/.
from __future__ import unicode_literals
import itertools
import hashlib
import os
import unittest
import shutil
import string
import sys
import tempfile
from mozfile.mozfile import NamedTemporaryFile
from mozunit import (
main,
MockedOpen,
)
from mozbuild.util import (
expand_variables,
FileAvoidWrite,
group_unified_files,
hash_file,
memoize,
memoized_property,
pair,
resolve_target_to_make,
MozbuildDeletionError,
HierarchicalStringList,
StrictOrderingOnAppendList,
StrictOrderingOnAppendListWithFlagsFactory,
TypedList,
TypedNamedTuple,
UnsortedError,
)
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 TestFileAvoidWrite(unittest.TestCase):
def test_file_avoid_write(self):
with MockedOpen({'file': 'content'}):
# Overwriting an existing file replaces its content
faw = FileAvoidWrite('file')
faw.write('bazqux')
self.assertEqual(faw.close(), (True, True))
self.assertEqual(open('file', 'r').read(), 'bazqux')
# Creating a new file (obviously) stores its content
faw = FileAvoidWrite('file2')
faw.write('content')
self.assertEqual(faw.close(), (False, True))
self.assertEqual(open('file2').read(), 'content')
with MockedOpen({'file': 'content'}):
with FileAvoidWrite('file') as file:
file.write('foobar')
self.assertEqual(open('file', 'r').read(), 'foobar')
class MyMockedOpen(MockedOpen):
'''MockedOpen extension to raise an exception if something
attempts to write in an opened file.
'''
def __call__(self, name, mode):
if 'w' in mode:
raise Exception, 'Unexpected open with write mode'
return MockedOpen.__call__(self, name, mode)
with MyMockedOpen({'file': 'content'}):
# Validate that MyMockedOpen works as intended
file = FileAvoidWrite('file')
file.write('foobar')
self.assertRaises(Exception, file.close)
# Check that no write actually happens when writing the
# same content as what already is in the file
faw = FileAvoidWrite('file')
faw.write('content')
self.assertEqual(faw.close(), (True, False))
def test_diff_not_default(self):
"""Diffs are not produced by default."""
with MockedOpen({'file': 'old'}):
faw = FileAvoidWrite('file')
faw.write('dummy')
faw.close()
self.assertIsNone(faw.diff)
def test_diff_update(self):
"""Diffs are produced on file update."""
with MockedOpen({'file': 'old'}):
faw = FileAvoidWrite('file', capture_diff=True)
faw.write('new')
faw.close()
diff = '\n'.join(faw.diff)
self.assertIn('-old', diff)
self.assertIn('+new', diff)
def test_diff_create(self):
"""Diffs are produced when files are created."""
tmpdir = tempfile.mkdtemp()
try:
path = os.path.join(tmpdir, 'file')
faw = FileAvoidWrite(path, capture_diff=True)
faw.write('new')
faw.close()
diff = '\n'.join(faw.diff)
self.assertIn('+new', diff)
finally:
shutil.rmtree(tmpdir)
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"]
self.assertItemsEqual(self.EXPORTS._children, {"foo" : True})
self.assertEqual(self.EXPORTS.foo._strings, ["foo.h"])
self.EXPORTS.bar += ["bar.h"]
self.assertItemsEqual(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"]
self.assertItemsEqual(self.EXPORTS._children, {"foo" : True})
self.assertItemsEqual(self.EXPORTS.foo._children, {"bar" : True})
self.assertItemsEqual(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"
self.assertEqual(str(ve.exception),
"Expected a list of strings, not <type '%s'>" % str_type)
def test_invalid_exports_set(self):
with self.assertRaises(ValueError) as ve:
self.EXPORTS.foo = "foo.h"
self.assertEqual(str(ve.exception),
"Expected a list of strings, not <type '%s'>" % str_type)
def test_invalid_exports_append_base(self):
with self.assertRaises(ValueError) as ve:
self.EXPORTS += "foo.h"
self.assertEqual(str(ve.exception),
"Expected a list of strings, not <type '%s'>" % str_type)
def test_invalid_exports_bool(self):
with self.assertRaises(ValueError) as ve:
self.EXPORTS += [True]
self.assertEqual(str(ve.exception),
"Expected a list of strings, not an element of "
"<type 'bool'>")
def test_del_exports(self):
with self.assertRaises(MozbuildDeletionError) as mde:
self.EXPORTS.foo += ['bar.h']
del self.EXPORTS.foo
def test_unsorted(self):
with self.assertRaises(UnsortedError) as ee:
self.EXPORTS += ['foo.h', 'bar.h']
with self.assertRaises(UnsortedError) as ee:
self.EXPORTS.foo = ['foo.h', 'bar.h']
with self.assertRaises(UnsortedError) as ee:
self.EXPORTS.foo += ['foo.h', 'bar.h']
def test_reassign(self):
self.EXPORTS.foo = ['foo.h']
with self.assertRaises(KeyError) as ee:
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 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):
c = 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)
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.__setslice__(l, 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(unicode):
def __init__(self, other):
if not isinstance(other, unicode):
raise ValueError()
super(Unicode, self).__init__(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', unicode), ('bar', int)])
t = FooBar(foo='foo', bar=2)
self.assertEquals(type(t), FooBar)
self.assertEquals(t.foo, 'foo')
self.assertEquals(t.bar, 2)
self.assertEquals(t[0], 'foo')
self.assertEquals(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.assertEquals(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)
def test_unsorted_files(self):
unsorted_files = ['a%d.cpp' % i for i in range(11)]
sorted_files = sorted(unsorted_files)
mapping = list(group_unified_files(unsorted_files, 'Unified', 'cpp', 5))
self.assertEqual(mapping[0][1], sorted_files[0:5])
self.assertEqual(mapping[1][1], sorted_files[5:10])
self.assertEqual(mapping[2][1], sorted_files[10:])
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'
)
if __name__ == '__main__':
main()