| # 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 unittest |
| import mozunit |
| import os |
| from mozpack.packager import ( |
| preprocess_manifest, |
| CallDeque, |
| Component, |
| SimplePackager, |
| SimpleManifestSink, |
| ) |
| from mozpack.files import GeneratedFile |
| from mozpack.chrome.manifest import ( |
| ManifestBinaryComponent, |
| ManifestContent, |
| ManifestResource, |
| ) |
| from mozunit import MockedOpen |
| from mozbuild.preprocessor import Preprocessor |
| from mozpack.errors import ( |
| errors, |
| ErrorMessage, |
| ) |
| import mozpack.path as mozpath |
| |
| MANIFEST = ''' |
| bar/* |
| [foo] |
| foo/* |
| -foo/bar |
| chrome.manifest |
| [zot destdir="destdir"] |
| foo/zot |
| ; comment |
| #ifdef baz |
| [baz] |
| baz@SUFFIX@ |
| #endif |
| ''' |
| |
| |
| class TestPreprocessManifest(unittest.TestCase): |
| MANIFEST_PATH = os.path.join(os.path.abspath(os.curdir), 'manifest') |
| |
| EXPECTED_LOG = [ |
| ((MANIFEST_PATH, 2), 'add', '', 'bar/*'), |
| ((MANIFEST_PATH, 4), 'add', 'foo', 'foo/*'), |
| ((MANIFEST_PATH, 5), 'remove', 'foo', 'foo/bar'), |
| ((MANIFEST_PATH, 6), 'add', 'foo', 'chrome.manifest'), |
| ((MANIFEST_PATH, 8), 'add', 'zot destdir="destdir"', 'foo/zot'), |
| ] |
| |
| def setUp(self): |
| class MockSink(object): |
| def __init__(self): |
| self.log = [] |
| |
| def add(self, component, path): |
| self._log(errors.get_context(), 'add', repr(component), path) |
| |
| def remove(self, component, path): |
| self._log(errors.get_context(), 'remove', repr(component), path) |
| |
| def _log(self, *args): |
| self.log.append(args) |
| |
| self.sink = MockSink() |
| |
| def test_preprocess_manifest(self): |
| with MockedOpen({'manifest': MANIFEST}): |
| preprocess_manifest(self.sink, 'manifest') |
| self.assertEqual(self.sink.log, self.EXPECTED_LOG) |
| |
| def test_preprocess_manifest_missing_define(self): |
| with MockedOpen({'manifest': MANIFEST}): |
| self.assertRaises( |
| Preprocessor.Error, |
| preprocess_manifest, |
| self.sink, |
| 'manifest', |
| {'baz': 1} |
| ) |
| |
| def test_preprocess_manifest_defines(self): |
| with MockedOpen({'manifest': MANIFEST}): |
| preprocess_manifest(self.sink, 'manifest', |
| {'baz': 1, 'SUFFIX': '.exe'}) |
| self.assertEqual(self.sink.log, self.EXPECTED_LOG + |
| [((self.MANIFEST_PATH, 12), 'add', 'baz', 'baz.exe')]) |
| |
| |
| class MockFinder(object): |
| def __init__(self, files): |
| self.files = files |
| self.log = [] |
| |
| def find(self, path): |
| self.log.append(path) |
| for f in sorted(self.files): |
| if mozpath.match(f, path): |
| yield f, self.files[f] |
| |
| def __iter__(self): |
| return self.find('') |
| |
| |
| class MockFormatter(object): |
| def __init__(self): |
| self.log = [] |
| |
| def add_base(self, *args): |
| self._log(errors.get_context(), 'add_base', *args) |
| |
| def add_manifest(self, *args): |
| self._log(errors.get_context(), 'add_manifest', *args) |
| |
| def add_interfaces(self, *args): |
| self._log(errors.get_context(), 'add_interfaces', *args) |
| |
| def add(self, *args): |
| self._log(errors.get_context(), 'add', *args) |
| |
| def _log(self, *args): |
| self.log.append(args) |
| |
| |
| class TestSimplePackager(unittest.TestCase): |
| def test_simple_packager(self): |
| class GeneratedFileWithPath(GeneratedFile): |
| def __init__(self, path, content): |
| GeneratedFile.__init__(self, content) |
| self.path = path |
| |
| formatter = MockFormatter() |
| packager = SimplePackager(formatter) |
| curdir = os.path.abspath(os.curdir) |
| file = GeneratedFileWithPath(os.path.join(curdir, 'foo', |
| 'bar.manifest'), |
| 'resource bar bar/\ncontent bar bar/') |
| with errors.context('manifest', 1): |
| packager.add('foo/bar.manifest', file) |
| |
| file = GeneratedFileWithPath(os.path.join(curdir, 'foo', |
| 'baz.manifest'), |
| 'resource baz baz/') |
| with errors.context('manifest', 2): |
| packager.add('bar/baz.manifest', file) |
| |
| with errors.context('manifest', 3): |
| packager.add('qux/qux.manifest', |
| GeneratedFile(''.join([ |
| 'resource qux qux/\n', |
| 'binary-component qux.so\n', |
| ]))) |
| bar_xpt = GeneratedFile('bar.xpt') |
| qux_xpt = GeneratedFile('qux.xpt') |
| foo_html = GeneratedFile('foo_html') |
| bar_html = GeneratedFile('bar_html') |
| with errors.context('manifest', 4): |
| packager.add('foo/bar.xpt', bar_xpt) |
| with errors.context('manifest', 5): |
| packager.add('foo/bar/foo.html', foo_html) |
| packager.add('foo/bar/bar.html', bar_html) |
| |
| file = GeneratedFileWithPath(os.path.join(curdir, 'foo.manifest'), |
| ''.join([ |
| 'manifest foo/bar.manifest\n', |
| 'manifest bar/baz.manifest\n', |
| ])) |
| with errors.context('manifest', 6): |
| packager.add('foo.manifest', file) |
| with errors.context('manifest', 7): |
| packager.add('foo/qux.xpt', qux_xpt) |
| |
| file = GeneratedFileWithPath(os.path.join(curdir, 'addon', |
| 'chrome.manifest'), |
| 'resource hoge hoge/') |
| with errors.context('manifest', 8): |
| packager.add('addon/chrome.manifest', file) |
| |
| install_rdf = GeneratedFile('<RDF></RDF>') |
| with errors.context('manifest', 9): |
| packager.add('addon/install.rdf', install_rdf) |
| |
| with errors.context('manifest', 10): |
| packager.add('addon2/install.rdf', install_rdf) |
| packager.add('addon2/chrome.manifest', |
| GeneratedFile('binary-component addon2.so')) |
| |
| with errors.context('manifest', 11): |
| packager.add('addon3/install.rdf', install_rdf) |
| packager.add('addon3/chrome.manifest', GeneratedFile( |
| 'manifest components/components.manifest')) |
| packager.add('addon3/components/components.manifest', |
| GeneratedFile('binary-component addon3.so')) |
| |
| with errors.context('manifest', 12): |
| install_rdf_addon4 = GeneratedFile( |
| '<RDF>\n<...>\n<em:unpack>true</em:unpack>\n<...>\n</RDF>') |
| packager.add('addon4/install.rdf', install_rdf_addon4) |
| |
| with errors.context('manifest', 13): |
| install_rdf_addon5 = GeneratedFile( |
| '<RDF>\n<...>\n<em:unpack>false</em:unpack>\n<...>\n</RDF>') |
| packager.add('addon5/install.rdf', install_rdf_addon5) |
| |
| with errors.context('manifest', 14): |
| install_rdf_addon6 = GeneratedFile( |
| '<RDF>\n<... em:unpack=true>\n<...>\n</RDF>') |
| packager.add('addon6/install.rdf', install_rdf_addon6) |
| |
| with errors.context('manifest', 15): |
| install_rdf_addon7 = GeneratedFile( |
| '<RDF>\n<... em:unpack=false>\n<...>\n</RDF>') |
| packager.add('addon7/install.rdf', install_rdf_addon7) |
| |
| with errors.context('manifest', 16): |
| install_rdf_addon8 = GeneratedFile( |
| '<RDF>\n<... em:unpack="true">\n<...>\n</RDF>') |
| packager.add('addon8/install.rdf', install_rdf_addon8) |
| |
| with errors.context('manifest', 17): |
| install_rdf_addon9 = GeneratedFile( |
| '<RDF>\n<... em:unpack="false">\n<...>\n</RDF>') |
| packager.add('addon9/install.rdf', install_rdf_addon9) |
| |
| with errors.context('manifest', 18): |
| install_rdf_addon10 = GeneratedFile( |
| '<RDF>\n<... em:unpack=\'true\'>\n<...>\n</RDF>') |
| packager.add('addon10/install.rdf', install_rdf_addon10) |
| |
| with errors.context('manifest', 19): |
| install_rdf_addon11 = GeneratedFile( |
| '<RDF>\n<... em:unpack=\'false\'>\n<...>\n</RDF>') |
| packager.add('addon11/install.rdf', install_rdf_addon11) |
| |
| self.assertEqual(formatter.log, []) |
| |
| with errors.context('dummy', 1): |
| packager.close() |
| self.maxDiff = None |
| # The formatter is expected to reorder the manifest entries so that |
| # chrome entries appear before the others. |
| self.assertEqual(formatter.log, [ |
| (('dummy', 1), 'add_base', '', False), |
| (('dummy', 1), 'add_base', 'addon', True), |
| (('dummy', 1), 'add_base', 'addon10', 'unpacked'), |
| (('dummy', 1), 'add_base', 'addon11', True), |
| (('dummy', 1), 'add_base', 'addon2', 'unpacked'), |
| (('dummy', 1), 'add_base', 'addon3', 'unpacked'), |
| (('dummy', 1), 'add_base', 'addon4', 'unpacked'), |
| (('dummy', 1), 'add_base', 'addon5', True), |
| (('dummy', 1), 'add_base', 'addon6', 'unpacked'), |
| (('dummy', 1), 'add_base', 'addon7', True), |
| (('dummy', 1), 'add_base', 'addon8', 'unpacked'), |
| (('dummy', 1), 'add_base', 'addon9', True), |
| (('dummy', 1), 'add_base', 'qux', False), |
| ((os.path.join(curdir, 'foo', 'bar.manifest'), 2), |
| 'add_manifest', ManifestContent('foo', 'bar', 'bar/')), |
| ((os.path.join(curdir, 'foo', 'bar.manifest'), 1), |
| 'add_manifest', ManifestResource('foo', 'bar', 'bar/')), |
| (('bar/baz.manifest', 1), |
| 'add_manifest', ManifestResource('bar', 'baz', 'baz/')), |
| (('qux/qux.manifest', 1), |
| 'add_manifest', ManifestResource('qux', 'qux', 'qux/')), |
| (('qux/qux.manifest', 2), |
| 'add_manifest', ManifestBinaryComponent('qux', 'qux.so')), |
| (('manifest', 4), 'add_interfaces', 'foo/bar.xpt', bar_xpt), |
| (('manifest', 7), 'add_interfaces', 'foo/qux.xpt', qux_xpt), |
| ((os.path.join(curdir, 'addon', 'chrome.manifest'), 1), |
| 'add_manifest', ManifestResource('addon', 'hoge', 'hoge/')), |
| (('addon2/chrome.manifest', 1), 'add_manifest', |
| ManifestBinaryComponent('addon2', 'addon2.so')), |
| (('addon3/components/components.manifest', 1), 'add_manifest', |
| ManifestBinaryComponent('addon3/components', 'addon3.so')), |
| (('manifest', 5), 'add', 'foo/bar/foo.html', foo_html), |
| (('manifest', 5), 'add', 'foo/bar/bar.html', bar_html), |
| (('manifest', 9), 'add', 'addon/install.rdf', install_rdf), |
| (('manifest', 10), 'add', 'addon2/install.rdf', install_rdf), |
| (('manifest', 11), 'add', 'addon3/install.rdf', install_rdf), |
| (('manifest', 12), 'add', 'addon4/install.rdf', |
| install_rdf_addon4), |
| (('manifest', 13), 'add', 'addon5/install.rdf', |
| install_rdf_addon5), |
| (('manifest', 14), 'add', 'addon6/install.rdf', |
| install_rdf_addon6), |
| (('manifest', 15), 'add', 'addon7/install.rdf', |
| install_rdf_addon7), |
| (('manifest', 16), 'add', 'addon8/install.rdf', |
| install_rdf_addon8), |
| (('manifest', 17), 'add', 'addon9/install.rdf', |
| install_rdf_addon9), |
| (('manifest', 18), 'add', 'addon10/install.rdf', |
| install_rdf_addon10), |
| (('manifest', 19), 'add', 'addon11/install.rdf', |
| install_rdf_addon11), |
| ]) |
| |
| self.assertEqual(packager.get_bases(), |
| set(['', 'addon', 'addon2', 'addon3', 'addon4', |
| 'addon5', 'addon6', 'addon7', 'addon8', |
| 'addon9', 'addon10', 'addon11', 'qux'])) |
| self.assertEqual(packager.get_bases(addons=False), set(['', 'qux'])) |
| |
| def test_simple_packager_manifest_consistency(self): |
| formatter = MockFormatter() |
| # bar/ is detected as an addon because of install.rdf, but top-level |
| # includes a manifest inside bar/. |
| packager = SimplePackager(formatter) |
| packager.add('base.manifest', GeneratedFile( |
| 'manifest foo/bar.manifest\n' |
| 'manifest bar/baz.manifest\n' |
| )) |
| packager.add('foo/bar.manifest', GeneratedFile('resource bar bar')) |
| packager.add('bar/baz.manifest', GeneratedFile('resource baz baz')) |
| packager.add('bar/install.rdf', GeneratedFile('')) |
| |
| with self.assertRaises(ErrorMessage) as e: |
| packager.close() |
| |
| self.assertEqual(e.exception.message, |
| 'Error: "bar/baz.manifest" is included from "base.manifest", ' |
| 'which is outside "bar"') |
| |
| # bar/ is detected as a separate base because of chrome.manifest that |
| # is included nowhere, but top-level includes another manifest inside |
| # bar/. |
| packager = SimplePackager(formatter) |
| packager.add('base.manifest', GeneratedFile( |
| 'manifest foo/bar.manifest\n' |
| 'manifest bar/baz.manifest\n' |
| )) |
| packager.add('foo/bar.manifest', GeneratedFile('resource bar bar')) |
| packager.add('bar/baz.manifest', GeneratedFile('resource baz baz')) |
| packager.add('bar/chrome.manifest', GeneratedFile('resource baz baz')) |
| |
| with self.assertRaises(ErrorMessage) as e: |
| packager.close() |
| |
| self.assertEqual(e.exception.message, |
| 'Error: "bar/baz.manifest" is included from "base.manifest", ' |
| 'which is outside "bar"') |
| |
| # bar/ is detected as a separate base because of chrome.manifest that |
| # is included nowhere, but chrome.manifest includes baz.manifest from |
| # the same directory. This shouldn't error out. |
| packager = SimplePackager(formatter) |
| packager.add('base.manifest', GeneratedFile( |
| 'manifest foo/bar.manifest\n' |
| )) |
| packager.add('foo/bar.manifest', GeneratedFile('resource bar bar')) |
| packager.add('bar/baz.manifest', GeneratedFile('resource baz baz')) |
| packager.add('bar/chrome.manifest', |
| GeneratedFile('manifest baz.manifest')) |
| packager.close() |
| |
| |
| class TestSimpleManifestSink(unittest.TestCase): |
| def test_simple_manifest_parser(self): |
| formatter = MockFormatter() |
| foobar = GeneratedFile('foobar') |
| foobaz = GeneratedFile('foobaz') |
| fooqux = GeneratedFile('fooqux') |
| foozot = GeneratedFile('foozot') |
| finder = MockFinder({ |
| 'bin/foo/bar': foobar, |
| 'bin/foo/baz': foobaz, |
| 'bin/foo/qux': fooqux, |
| 'bin/foo/zot': foozot, |
| 'bin/foo/chrome.manifest': GeneratedFile('resource foo foo/'), |
| 'bin/chrome.manifest': |
| GeneratedFile('manifest foo/chrome.manifest'), |
| }) |
| parser = SimpleManifestSink(finder, formatter) |
| component0 = Component('component0') |
| component1 = Component('component1') |
| component2 = Component('component2', destdir='destdir') |
| parser.add(component0, 'bin/foo/b*') |
| parser.add(component1, 'bin/foo/qux') |
| parser.add(component1, 'bin/foo/chrome.manifest') |
| parser.add(component2, 'bin/foo/zot') |
| self.assertRaises(ErrorMessage, parser.add, 'component1', 'bin/bar') |
| |
| self.assertEqual(formatter.log, []) |
| parser.close() |
| self.assertEqual(formatter.log, [ |
| (None, 'add_base', '', False), |
| (('foo/chrome.manifest', 1), |
| 'add_manifest', ManifestResource('foo', 'foo', 'foo/')), |
| (None, 'add', 'foo/bar', foobar), |
| (None, 'add', 'foo/baz', foobaz), |
| (None, 'add', 'foo/qux', fooqux), |
| (None, 'add', 'destdir/foo/zot', foozot), |
| ]) |
| |
| self.assertEqual(finder.log, [ |
| 'bin/foo/b*', |
| 'bin/foo/qux', |
| 'bin/foo/chrome.manifest', |
| 'bin/foo/zot', |
| 'bin/bar', |
| 'bin/chrome.manifest' |
| ]) |
| |
| |
| class TestCallDeque(unittest.TestCase): |
| def test_call_deque(self): |
| class Logger(object): |
| def __init__(self): |
| self._log = [] |
| |
| def log(self, str): |
| self._log.append(str) |
| |
| @staticmethod |
| def staticlog(logger, str): |
| logger.log(str) |
| |
| def do_log(logger, str): |
| logger.log(str) |
| |
| logger = Logger() |
| d = CallDeque() |
| d.append(logger.log, 'foo') |
| d.append(logger.log, 'bar') |
| d.append(logger.staticlog, logger, 'baz') |
| d.append(do_log, logger, 'qux') |
| self.assertEqual(logger._log, []) |
| d.execute() |
| self.assertEqual(logger._log, ['foo', 'bar', 'baz', 'qux']) |
| |
| |
| class TestComponent(unittest.TestCase): |
| def do_split(self, string, name, options): |
| n, o = Component._split_component_and_options(string) |
| self.assertEqual(name, n) |
| self.assertEqual(options, o) |
| |
| def test_component_split_component_and_options(self): |
| self.do_split('component', 'component', {}) |
| self.do_split('trailingspace ', 'trailingspace', {}) |
| self.do_split(' leadingspace', 'leadingspace', {}) |
| self.do_split(' trim ', 'trim', {}) |
| self.do_split(' trim key="value"', 'trim', {'key':'value'}) |
| self.do_split(' trim empty=""', 'trim', {'empty':''}) |
| self.do_split(' trim space=" "', 'trim', {'space':' '}) |
| self.do_split('component key="value" key2="second" ', |
| 'component', {'key':'value', 'key2':'second'}) |
| self.do_split( 'trim key=" value with spaces " key2="spaces again"', |
| 'trim', {'key':' value with spaces ', 'key2': 'spaces again'}) |
| |
| def do_split_error(self, string): |
| self.assertRaises(ValueError, Component._split_component_and_options, string) |
| |
| def test_component_split_component_and_options_errors(self): |
| self.do_split_error('"component') |
| self.do_split_error('comp"onent') |
| self.do_split_error('component"') |
| self.do_split_error('"component"') |
| self.do_split_error('=component') |
| self.do_split_error('comp=onent') |
| self.do_split_error('component=') |
| self.do_split_error('key="val"') |
| self.do_split_error('component key=') |
| self.do_split_error('component key="val') |
| self.do_split_error('component key=val"') |
| self.do_split_error('component key="val" x') |
| self.do_split_error('component x key="val"') |
| self.do_split_error('component key1="val" x key2="val"') |
| |
| def do_from_string(self, string, name, destdir=''): |
| component = Component.from_string(string) |
| self.assertEqual(name, component.name) |
| self.assertEqual(destdir, component.destdir) |
| |
| def test_component_from_string(self): |
| self.do_from_string('component', 'component') |
| self.do_from_string('component-with-hyphen', 'component-with-hyphen') |
| self.do_from_string('component destdir="foo/bar"', 'component', 'foo/bar') |
| self.do_from_string('component destdir="bar spc"', 'component', 'bar spc') |
| self.assertRaises(ErrorMessage, Component.from_string, '') |
| self.assertRaises(ErrorMessage, Component.from_string, 'component novalue=') |
| self.assertRaises(ErrorMessage, Component.from_string, 'component badoption=badvalue') |
| |
| |
| if __name__ == '__main__': |
| mozunit.main() |