python/mozbuild/mozpack/test/test_packager_formats.py
author Mike Hommey <mh+mozilla@glandium.org>
Tue, 23 May 2017 07:51:22 +0900
changeset 360181 aec720895e5e79d126283c118cb4d26744c3750a
parent 359275 68cdf62dd865766db12969c8869fdbaade1784c6
child 360182 249495cc2ea52fb0fa0f8f4db6894070789953bd
permissions -rw-r--r--
Bug 1366729 - Properly handle "multi-content" manifest entries after bug 1366169. r=gps Some manifest entries (e.g. skin or locale) have an attached identifier, and there can be different entries with different identifiers for the same chrome name. The change from bug 1366169 would consider those as errors, while they are the expected configuration.

# 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 mozunit
import unittest
from mozpack.packager.formats import (
    FlatFormatter,
    JarFormatter,
    OmniJarFormatter,
)
from mozpack.copier import FileRegistry
from mozpack.files import (
    GeneratedFile,
    ManifestFile,
)
from mozpack.chrome.manifest import (
    ManifestContent,
    ManifestComponent,
    ManifestResource,
    ManifestBinaryComponent,
    ManifestSkin,
    ManifestLocale,
)
from mozpack.errors import (
    errors,
    ErrorMessage,
)
from mozpack.test.test_files import (
    MockDest,
    foo_xpt,
    foo2_xpt,
    bar_xpt,
    read_interfaces,
)
import mozpack.path as mozpath


CONTENTS = {
    'bases': {
        # base_path: is_addon?
        '': False,
        'app': False,
        'addon0': 'unpacked',
        'addon1': True,
    },
    'manifests': [
        ManifestContent('chrome/f', 'oo', 'oo/'),
        ManifestContent('chrome/f', 'bar', 'oo/bar/'),
        ManifestResource('chrome/f', 'foo', 'resource://bar/'),
        ManifestBinaryComponent('components', 'foo.so'),
        ManifestContent('app/chrome', 'content', 'foo/'),
        ManifestComponent('app/components', '{foo-id}', 'foo.js'),
        ManifestContent('addon0/chrome', 'content', 'foo/bar/'),
        ManifestContent('addon1/chrome', 'content', 'foo/bar/'),
    ],
    'files': {
        'chrome/f/oo/bar/baz': GeneratedFile('foobarbaz'),
        'chrome/f/oo/baz': GeneratedFile('foobaz'),
        'chrome/f/oo/qux': GeneratedFile('fooqux'),
        'components/foo.so': GeneratedFile('foo.so'),
        'components/foo.xpt': foo_xpt,
        'components/bar.xpt': bar_xpt,
        'foo': GeneratedFile('foo'),
        'app/chrome/foo/foo': GeneratedFile('appfoo'),
        'app/components/foo.js': GeneratedFile('foo.js'),
        'addon0/chrome/foo/bar/baz': GeneratedFile('foobarbaz'),
        'addon0/components/foo.xpt': foo2_xpt,
        'addon0/components/bar.xpt': bar_xpt,
        'addon1/chrome/foo/bar/baz': GeneratedFile('foobarbaz'),
        'addon1/components/foo.xpt': foo2_xpt,
        'addon1/components/bar.xpt': bar_xpt,
    },
}

FILES = CONTENTS['files']

RESULT_FLAT = {
    'chrome.manifest': [
        'manifest chrome/chrome.manifest',
        'manifest components/components.manifest',
    ],
    'chrome/chrome.manifest': [
        'manifest f/f.manifest',
    ],
    'chrome/f/f.manifest': [
        'content oo oo/',
        'content bar oo/bar/',
        'resource foo resource://bar/',
    ],
    'chrome/f/oo/bar/baz': FILES['chrome/f/oo/bar/baz'],
    'chrome/f/oo/baz': FILES['chrome/f/oo/baz'],
    'chrome/f/oo/qux': FILES['chrome/f/oo/qux'],
    'components/components.manifest': [
        'binary-component foo.so',
        'interfaces interfaces.xpt',
    ],
    'components/foo.so': FILES['components/foo.so'],
    'components/interfaces.xpt': {
        'foo': read_interfaces(foo_xpt.open())['foo'],
        'bar': read_interfaces(bar_xpt.open())['bar'],
    },
    'foo': FILES['foo'],
    'app/chrome.manifest': [
        'manifest chrome/chrome.manifest',
        'manifest components/components.manifest',
    ],
    'app/chrome/chrome.manifest': [
        'content content foo/',
    ],
    'app/chrome/foo/foo': FILES['app/chrome/foo/foo'],
    'app/components/components.manifest': [
        'component {foo-id} foo.js',
    ],
    'app/components/foo.js': FILES['app/components/foo.js'],
}

for addon in ('addon0', 'addon1'):
    RESULT_FLAT.update({
        mozpath.join(addon, p): f
        for p, f in {
            'chrome.manifest': [
                'manifest chrome/chrome.manifest',
                'manifest components/components.manifest',
            ],
            'chrome/chrome.manifest': [
                'content content foo/bar/',
            ],
            'chrome/foo/bar/baz': FILES[mozpath.join(addon, 'chrome/foo/bar/baz')],
            'components/components.manifest': [
                'interfaces interfaces.xpt',
            ],
            'components/interfaces.xpt': {
                'foo': read_interfaces(foo2_xpt.open())['foo'],
                'bar': read_interfaces(bar_xpt.open())['bar'],
            },
        }.iteritems()
    })

RESULT_JAR = {
    p: RESULT_FLAT[p]
    for p in (
        'chrome.manifest',
        'chrome/chrome.manifest',
        'components/components.manifest',
        'components/foo.so',
        'components/interfaces.xpt',
        'foo',
        'app/chrome.manifest',
        'app/components/components.manifest',
        'app/components/foo.js',
        'addon0/chrome.manifest',
        'addon0/components/components.manifest',
        'addon0/components/interfaces.xpt',
    )
}

RESULT_JAR.update({
    'chrome/f/f.manifest': [
        'content oo jar:oo.jar!/',
        'content bar jar:oo.jar!/bar/',
        'resource foo resource://bar/',
    ],
    'chrome/f/oo.jar': {
        'bar/baz': FILES['chrome/f/oo/bar/baz'],
        'baz': FILES['chrome/f/oo/baz'],
        'qux': FILES['chrome/f/oo/qux'],
    },
    'app/chrome/chrome.manifest': [
        'content content jar:foo.jar!/',
    ],
    'app/chrome/foo.jar': {
        'foo': FILES['app/chrome/foo/foo'],
    },
    'addon0/chrome/chrome.manifest': [
        'content content jar:foo.jar!/bar/',
    ],
    'addon0/chrome/foo.jar': {
        'bar/baz': FILES['addon0/chrome/foo/bar/baz'],
    },
    'addon1.xpi': {
        mozpath.relpath(p, 'addon1'): f
        for p, f in RESULT_FLAT.iteritems()
        if p.startswith('addon1/')
    },
})

RESULT_OMNIJAR = {
    p: RESULT_FLAT[p]
    for p in (
        'components/foo.so',
        'foo',
    )
}

RESULT_OMNIJAR.update({
    p: RESULT_JAR[p]
    for p in RESULT_JAR
    if p.startswith('addon')
})

RESULT_OMNIJAR.update({
    'omni.foo': {
        'components/components.manifest': [
            'interfaces interfaces.xpt',
        ],
    },
    'chrome.manifest': [
        'manifest components/components.manifest',
    ],
    'components/components.manifest': [
        'binary-component foo.so',
    ],
    'app/omni.foo': {
        p: RESULT_FLAT['app/' + p]
        for p in (
            'chrome.manifest',
            'chrome/chrome.manifest',
            'chrome/foo/foo',
            'components/components.manifest',
            'components/foo.js',
        )
    },
    'app/chrome.manifest': [],
})

RESULT_OMNIJAR['omni.foo'].update({
    p: RESULT_FLAT[p]
    for p in (
        'chrome.manifest',
        'chrome/chrome.manifest',
        'chrome/f/f.manifest',
        'chrome/f/oo/bar/baz',
        'chrome/f/oo/baz',
        'chrome/f/oo/qux',
        'components/interfaces.xpt',
    )
})

CONTENTS_WITH_BASE = {
    'bases': {
        mozpath.join('base/root', b) if b else 'base/root': a
        for b, a in CONTENTS['bases'].iteritems()
    },
    'manifests': [
        m.move(mozpath.join('base/root', m.base))
        for m in CONTENTS['manifests']
    ],
    'files': {
        mozpath.join('base/root', p): f
        for p, f in CONTENTS['files'].iteritems()
    },
}

EXTRA_CONTENTS = {
    'extra/file': GeneratedFile('extra file'),
}

CONTENTS_WITH_BASE['files'].update(EXTRA_CONTENTS)

def result_with_base(results):
    result = {
        mozpath.join('base/root', p): v
        for p, v in results.iteritems()
    }
    result.update(EXTRA_CONTENTS)
    return result

RESULT_FLAT_WITH_BASE = result_with_base(RESULT_FLAT)
RESULT_JAR_WITH_BASE = result_with_base(RESULT_JAR)
RESULT_OMNIJAR_WITH_BASE = result_with_base(RESULT_OMNIJAR)


class MockDest(MockDest):
    def exists(self):
        return False


def fill_formatter(formatter, contents):
    for base, is_addon in contents['bases'].items():
        formatter.add_base(base, is_addon)

    for manifest in contents['manifests']:
        formatter.add_manifest(manifest)

    for k, v in contents['files'].iteritems():
        if k.endswith('.xpt'):
            formatter.add_interfaces(k, v)
        else:
            formatter.add(k, v)


def get_contents(registry, read_all=False):
    result = {}
    for k, v in registry:
        if k.endswith('.xpt'):
            tmpfile = MockDest()
            registry[k].copy(tmpfile)
            result[k] = read_interfaces(tmpfile)
        elif isinstance(v, FileRegistry):
            result[k] = get_contents(v)
        elif isinstance(v, ManifestFile) or read_all:
            result[k] = v.open().read().splitlines()
        else:
            result[k] = v
    return result


class TestFormatters(unittest.TestCase):
    maxDiff = None

    def test_bases(self):
        formatter = FlatFormatter(FileRegistry())
        formatter.add_base('')
        formatter.add_base('browser')
        formatter.add_base('addon0', addon=True)
        self.assertEqual(formatter._get_base('platform.ini'),
                         ('', 'platform.ini'))
        self.assertEqual(formatter._get_base('browser/application.ini'),
                         ('browser', 'application.ini'))
        self.assertEqual(formatter._get_base('addon0/install.rdf'),
                         ('addon0', 'install.rdf'))

    def do_test_contents(self, formatter, contents):
        for f in contents['files']:
            # .xpt files are merged, so skip them.
            if not f.endswith('.xpt'):
                self.assertTrue(formatter.contains(f))

    def test_flat_formatter(self):
        registry = FileRegistry()
        formatter = FlatFormatter(registry)

        fill_formatter(formatter, CONTENTS)
        self.assertEqual(get_contents(registry), RESULT_FLAT)
        self.do_test_contents(formatter, CONTENTS)

    def test_jar_formatter(self):
        registry = FileRegistry()
        formatter = JarFormatter(registry)

        fill_formatter(formatter, CONTENTS)
        self.assertEqual(get_contents(registry), RESULT_JAR)
        self.do_test_contents(formatter, CONTENTS)

    def test_omnijar_formatter(self):
        registry = FileRegistry()
        formatter = OmniJarFormatter(registry, 'omni.foo')

        fill_formatter(formatter, CONTENTS)
        self.assertEqual(get_contents(registry), RESULT_OMNIJAR)
        self.do_test_contents(formatter, CONTENTS)

    def test_flat_formatter_with_base(self):
        registry = FileRegistry()
        formatter = FlatFormatter(registry)

        fill_formatter(formatter, CONTENTS_WITH_BASE)
        self.assertEqual(get_contents(registry), RESULT_FLAT_WITH_BASE)
        self.do_test_contents(formatter, CONTENTS_WITH_BASE)

    def test_jar_formatter_with_base(self):
        registry = FileRegistry()
        formatter = JarFormatter(registry)

        fill_formatter(formatter, CONTENTS_WITH_BASE)
        self.assertEqual(get_contents(registry), RESULT_JAR_WITH_BASE)
        self.do_test_contents(formatter, CONTENTS_WITH_BASE)

    def test_omnijar_formatter_with_base(self):
        registry = FileRegistry()
        formatter = OmniJarFormatter(registry, 'omni.foo')

        fill_formatter(formatter, CONTENTS_WITH_BASE)
        self.assertEqual(get_contents(registry), RESULT_OMNIJAR_WITH_BASE)
        self.do_test_contents(formatter, CONTENTS_WITH_BASE)

    def test_omnijar_is_resource(self):
        def is_resource(base, path):
            registry = FileRegistry()
            f = OmniJarFormatter(registry, 'omni.foo', non_resources=[
                'defaults/messenger/mailViews.dat',
                'defaults/foo/*',
                '*/dummy',
            ])
            f.add_base('')
            f.add_base('app')
            f.add(mozpath.join(base, path), GeneratedFile(''))
            if f.copier.contains(mozpath.join(base, path)):
                return False
            self.assertTrue(f.copier.contains(mozpath.join(base, 'omni.foo')))
            self.assertTrue(f.copier[mozpath.join(base, 'omni.foo')]
                            .contains(path))
            return True

        for base in ['', 'app/']:
            self.assertTrue(is_resource(base, 'chrome'))
            self.assertTrue(
                is_resource(base, 'chrome/foo/bar/baz.properties'))
            self.assertFalse(is_resource(base, 'chrome/icons/foo.png'))
            self.assertTrue(is_resource(base, 'components/foo.js'))
            self.assertFalse(is_resource(base, 'components/foo.so'))
            self.assertTrue(is_resource(base, 'res/foo.css'))
            self.assertFalse(is_resource(base, 'res/cursors/foo.png'))
            self.assertFalse(is_resource(base, 'res/MainMenu.nib/foo'))
            self.assertTrue(is_resource(base, 'defaults/pref/foo.js'))
            self.assertFalse(
                is_resource(base, 'defaults/pref/channel-prefs.js'))
            self.assertTrue(
                is_resource(base, 'defaults/preferences/foo.js'))
            self.assertFalse(
                is_resource(base, 'defaults/preferences/channel-prefs.js'))
            self.assertTrue(is_resource(base, 'modules/foo.jsm'))
            self.assertTrue(is_resource(base, 'greprefs.js'))
            self.assertTrue(is_resource(base, 'hyphenation/foo'))
            self.assertTrue(is_resource(base, 'update.locale'))
            self.assertFalse(is_resource(base, 'foo'))
            self.assertFalse(is_resource(base, 'foo/bar/greprefs.js'))
            self.assertTrue(is_resource(base, 'defaults/messenger/foo.dat'))
            self.assertFalse(
                is_resource(base, 'defaults/messenger/mailViews.dat'))
            self.assertTrue(is_resource(base, 'defaults/pref/foo.js'))
            self.assertFalse(is_resource(base, 'defaults/foo/bar.dat'))
            self.assertFalse(is_resource(base, 'defaults/foo/bar/baz.dat'))
            self.assertTrue(is_resource(base, 'chrome/foo/bar/baz/dummy_'))
            self.assertFalse(is_resource(base, 'chrome/foo/bar/baz/dummy'))
            self.assertTrue(is_resource(base, 'chrome/foo/bar/dummy_'))
            self.assertFalse(is_resource(base, 'chrome/foo/bar/dummy'))

    def test_chrome_override(self):
        registry = FileRegistry()
        f = FlatFormatter(registry)
        f.add_base('')
        f.add_manifest(ManifestContent('chrome', 'foo', 'foo/unix'))
        # A more specific entry for a given chrome name can override a more
        # generic one.
        f.add_manifest(ManifestContent('chrome', 'foo', 'foo/win', 'os=WINNT'))
        f.add_manifest(ManifestContent('chrome', 'foo', 'foo/osx', 'os=Darwin'))

        # Chrome with the same name overrides the previous registration.
        with self.assertRaises(ErrorMessage) as e:
            f.add_manifest(ManifestContent('chrome', 'foo', 'foo/'))

        self.assertEqual(e.exception.message,
            'Error: "content foo foo/" overrides '
            '"content foo foo/unix"')

        # Chrome with the same name and same flags overrides the previous
        # registration.
        with self.assertRaises(ErrorMessage) as e:
            f.add_manifest(ManifestContent('chrome', 'foo', 'foo/', 'os=WINNT'))

        self.assertEqual(e.exception.message,
            'Error: "content foo foo/ os=WINNT" overrides '
            '"content foo foo/win os=WINNT"')

        # We may start with the more specific entry first
        f.add_manifest(ManifestContent('chrome', 'bar', 'bar/win', 'os=WINNT'))
        # Then adding a more generic one overrides it.
        with self.assertRaises(ErrorMessage) as e:
            f.add_manifest(ManifestContent('chrome', 'bar', 'bar/unix'))

        self.assertEqual(e.exception.message,
            'Error: "content bar bar/unix" overrides '
            '"content bar bar/win os=WINNT"')

        # Adding something more specific still works.
        f.add_manifest(ManifestContent('chrome', 'bar', 'bar/win',
                                       'os=WINNT osversion>=7.0'))

        # Variations of skin/locales are allowed.
        f.add_manifest(ManifestSkin('chrome', 'foo', 'classic/1.0',
                                    'foo/skin/classic/'))
        f.add_manifest(ManifestSkin('chrome', 'foo', 'modern/1.0',
                                    'foo/skin/modern/'))

        f.add_manifest(ManifestLocale('chrome', 'foo', 'en-US',
                                    'foo/locale/en-US/'))
        f.add_manifest(ManifestLocale('chrome', 'foo', 'ja-JP',
                                    'foo/locale/ja-JP/'))

        # But same-skin/locale still error out.
        with self.assertRaises(ErrorMessage) as e:
            f.add_manifest(ManifestSkin('chrome', 'foo', 'classic/1.0',
                                        'foo/skin/classic/foo'))

        self.assertEqual(e.exception.message,
            'Error: "skin foo classic/1.0 foo/skin/classic/foo" overrides '
            '"skin foo classic/1.0 foo/skin/classic/"')

        with self.assertRaises(ErrorMessage) as e:
            f.add_manifest(ManifestLocale('chrome', 'foo', 'en-US',
                                         'foo/locale/en-US/foo'))

        self.assertEqual(e.exception.message,
            'Error: "locale foo en-US foo/locale/en-US/foo" overrides '
            '"locale foo en-US foo/locale/en-US/"')


if __name__ == '__main__':
    mozunit.main()