blob: b95f9d506d6892b42d21ef8deb05ef2468e037f4 [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/.
import os
import unittest
from mozunit import main
from mozbuild.frontend.context import (
AbsolutePath,
Context,
ContextDerivedTypedHierarchicalStringList,
ContextDerivedTypedList,
ContextDerivedTypedListWithItems,
ContextDerivedTypedRecord,
Files,
FUNCTIONS,
ObjDirPath,
Path,
SourcePath,
SPECIAL_VARIABLES,
SUBCONTEXTS,
VARIABLES,
)
from mozbuild.util import StrictOrderingOnAppendListWithFlagsFactory
from mozpack import path as mozpath
class TestContext(unittest.TestCase):
def test_defaults(self):
test = Context({
'foo': (int, int, '', None),
'bar': (bool, bool, '', None),
'baz': (dict, dict, '', None),
})
self.assertEqual(test.keys(), [])
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, '', None),
'baz': (dict, list, '', None),
})
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, '', None),
'bar': (bool, bool, '', None),
'baz': (dict, list, '', None),
})
self.assertEqual(test.keys(), [])
with self.assertRaises(ValueError):
test.update(bar=True, foo={})
self.assertEqual(test.keys(), [])
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, tier 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')
config.external_source_dir = None
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', unicode),
('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', unicode),
('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, pattern='**')}
self.assertEqual(Files.aggregate(files), {
'bug_component_counts': [],
'recommended_bug_component': None,
})
def test_single_bug_component(self):
c = Context({})
f = Files(c, pattern='**')
f['BUG_COMPONENT'] = (u'Product1', u'Component1')
files = {'moz.build': f}
self.assertEqual(Files.aggregate(files), {
'bug_component_counts': [((u'Product1', u'Component1'), 1)],
'recommended_bug_component': (u'Product1', u'Component1'),
})
def test_multiple_bug_components(self):
c = Context({})
f1 = Files(c, pattern='**')
f1['BUG_COMPONENT'] = (u'Product1', u'Component1')
f2 = Files(c, pattern='**')
f2['BUG_COMPONENT'] = (u'Product2', u'Component2')
files = {'a': f1, 'b': f2, 'c': f1}
self.assertEqual(Files.aggregate(files), {
'bug_component_counts': [
((u'Product1', u'Component1'), 2),
((u'Product2', u'Component2'), 1),
],
'recommended_bug_component': (u'Product1', u'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, pattern='**')
f1['BUG_COMPONENT'] = (u'Product1', u'Component1')
f2 = Files(c, pattern='**')
f2['BUG_COMPONENT'] = (u'Product2', u'Component2')
files = {'a': f1, 'b': f2}
self.assertEqual(Files.aggregate(files), {
'bug_component_counts': [
((u'Product1', u'Component1'), 1),
((u'Product2', u'Component2'), 1),
],
'recommended_bug_component': None,
})
if __name__ == '__main__':
main()