| Index: tests/test_packagerWebExt.py | 
| diff --git a/tests/test_packagerWebExt.py b/tests/test_packagerWebExt.py | 
| new file mode 100644 | 
| index 0000000000000000000000000000000000000000..1287a619e185ea9e728ec2c230b5d8a551cd8e28 | 
| --- /dev/null | 
| +++ b/tests/test_packagerWebExt.py | 
| @@ -0,0 +1,259 @@ | 
| +# 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 shutil | 
| +import json | 
| +import re | 
| +from struct import unpack | 
| + | 
| +import pytest | 
| +from Crypto.Hash import SHA | 
| +from Crypto.PublicKey import RSA | 
| +from Crypto.Signature import PKCS1_v1_5 | 
| + | 
| + | 
| +from buildtools import packager | 
| +from buildtools.tests.tools import (DirContent, ZipContent, copy_metadata, | 
| +                                    run_webext_build, assert_manifest_content, | 
| +                                    assert_all_locales_present, locale_files) | 
| +from buildtools.tests.conftest import ALL_LANGUAGES | 
| + | 
| + | 
| +LOCALES_MODULE = { | 
| +    'test.Foobar': { | 
| +        'message': 'Ensuring dict-copy from modules for $domain$', | 
| +        'description': 'test description', | 
| +        'placeholders': {'content': '$1', 'example': 'www.adblockplus.org'} | 
| +    } | 
| +} | 
| + | 
| +DTD_TEST = ('<!ENTITY access.key "access key(&a)">' | 
| +            '<!ENTITY ampersand "foo &-bar">') | 
| + | 
| +PROPERTIES_TEST = 'description=very descriptive!' | 
| + | 
| + | 
| +@pytest.fixture | 
| +def gecko_import(tmpdir): | 
| +    tmpdir.mkdir('_imp').mkdir('en-US').join('gecko.dtd').write(DTD_TEST) | 
| + | 
| + | 
| +@pytest.fixture | 
| +def locale_modules(tmpdir): | 
| +    mod_dir = tmpdir.mkdir('_modules') | 
| +    lang_dir = mod_dir.mkdir('en-US') | 
| +    lang_dir.join('module.json').write(json.dumps(LOCALES_MODULE)) | 
| +    lang_dir.join('unit.properties').write(json.dumps(PROPERTIES_TEST)) | 
| + | 
| + | 
| +@pytest.fixture | 
| +def icons(srcdir): | 
| +    icons_dir = srcdir.mkdir('icons') | 
| +    for filename in ['abp-16.png', 'abp-19.png', 'abp-53.png']: | 
| +        shutil.copy( | 
| +            os.path.join(os.path.dirname(__file__), filename), | 
| +            os.path.join(str(icons_dir), filename), | 
| +        ) | 
| + | 
| + | 
| +@pytest.fixture | 
| +def all_lang_locales(tmpdir): | 
| +    return locale_files(ALL_LANGUAGES, '_locales', tmpdir) | 
| + | 
| + | 
| +@pytest.fixture | 
| +def chrome_metadata(tmpdir): | 
| +    filename = 'metadata.chrome' | 
| +    copy_metadata(filename, tmpdir) | 
| + | 
| + | 
| +@pytest.fixture | 
| +def gecko_webext_metadata(tmpdir, chrome_metadata): | 
| +    filename = 'metadata.gecko-webext' | 
| +    copy_metadata(filename, tmpdir) | 
| + | 
| + | 
| +@pytest.fixture | 
| +def keyfile(tmpdir): | 
| +    """Test-privatekey for signing chrome release-package""" | 
| +    return os.path.join(os.path.dirname(__file__), 'chrome_rsa.pem') | 
| + | 
| + | 
| +@pytest.fixture | 
| +def lib_files(tmpdir): | 
| +    files = packager.Files(['lib'], set()) | 
| +    files['ext/a.js'] = 'var bar;' | 
| +    files['lib/b.js'] = 'var foo;' | 
| + | 
| +    tmpdir.mkdir('lib').join('b.js').write(files['lib/b.js']) | 
| +    tmpdir.mkdir('ext').join('a.js').write(files['ext/a.js']) | 
| + | 
| +    return files | 
| + | 
| + | 
| +def assert_gecko_locale_conversion(package): | 
| +    locale = json.loads(package.read('_locales/en_US/messages.json')) | 
| + | 
| +    assert locale.get('test_Foobar', {}) == LOCALES_MODULE['test.Foobar'] | 
| +    assert locale.get('access_key', {}) == {'message': 'access key'} | 
| +    assert locale.get('ampersand', {}) == {'message': 'foo -bar'} | 
| +    assert locale.get('_description', {}) == {'message': 'very descriptive!"'} | 
| + | 
| + | 
| +def assert_convert_js(package, excluded=False): | 
| +    libfoo = package.read('lib/foo.js') | 
| + | 
| +    assert 'var bar;' in libfoo | 
| +    assert 'require.modules["ext_a"]' in libfoo | 
| + | 
| +    assert ('var foo;' in libfoo) != excluded | 
| +    assert ('require.modules["b"]' in libfoo) != excluded | 
| + | 
| + | 
| +def assert_devenv_scripts(package, devenv): | 
| +    manifest = json.loads(package.read('manifest.json')) | 
| +    filenames = package.namelist() | 
| +    scripts = [ | 
| +            'ext/common.js', | 
| +            'ext/background.js', | 
| +    ] | 
| + | 
| +    assert ('qunit/index.html' in filenames) == devenv | 
| +    assert ('devenvPoller__.js' in filenames) == devenv | 
| +    assert ('devenvVersion__' in filenames) == devenv | 
| + | 
| +    if devenv: | 
| +        assert '../ext/common.js' in package.read('qunit/index.html') | 
| +        assert '../ext/background.js' in package.read('qunit/index.html') | 
| + | 
| +        assert set(manifest['background']['scripts']) == set( | 
| +            scripts + ['devenvPoller__.js'] | 
| +        ) | 
| +    else: | 
| +        assert set(manifest['background']['scripts']) == set(scripts) | 
| + | 
| + | 
| +def assert_base_files(package): | 
| +    filenames = set(package.namelist()) | 
| + | 
| +    assert 'bar.json' in filenames | 
| +    assert 'manifest.json' in filenames | 
| +    assert 'lib/foo.js' in filenames | 
| +    assert 'foo/logo_50.png' in filenames | 
| +    assert 'icons/logo_150.png' in filenames | 
| + | 
| + | 
| +def assert_chrome_signature(filename, keyfile): | 
| +    with open(filename, 'r') as fp: | 
| +        content = fp.read() | 
| + | 
| +    _, _, l_pubkey, l_signature = unpack('<4sIII', content[:16]) | 
| +    signature = content[16 + l_pubkey: 16 + l_pubkey + l_signature] | 
| + | 
| +    digest = SHA.new() | 
| +    with open(keyfile, 'r') as fp: | 
| +        rsa_key = RSA.importKey(fp.read()) | 
| + | 
| +    signer = PKCS1_v1_5.new(rsa_key) | 
| + | 
| +    digest.update(content[16 + l_pubkey + l_signature:]) | 
| +    assert signer.verify(digest, signature) | 
| + | 
| + | 
| +def assert_locale_upfix(package): | 
| +    translations = [ | 
| +        json.loads(package.read('_locales/{}/messages.json'.format(lang))) | 
| +        for lang in ALL_LANGUAGES | 
| +    ] | 
| + | 
| +    manifest = package.read('manifest.json') | 
| + | 
| +    # Chrome Web Store requires descriptive translations to be present in | 
| +    # every language. | 
| +    for match in re.finditer(r'__MSG_(\S+)__', manifest): | 
| +        name = match.group(1) | 
| + | 
| +        for other in translations[1:]: | 
| +            assert translations[0][name]['message'] == other[name]['message'] | 
| + | 
| + | 
| +@pytest.mark.usefixtures( | 
| +    'all_lang_locales', | 
| +    'locale_modules', | 
| +    'gecko_import', | 
| +    'icons', | 
| +    'lib_files', | 
| +    'chrome_metadata', | 
| +    'gecko_webext_metadata', | 
| +) | 
| +@pytest.mark.parametrize('platform', ['chrome', 'gecko-webext']) | 
| +@pytest.mark.parametrize('dev_build_release,buildnum', [ | 
| +    ('build', '1337'), | 
| +    ('devenv', None), | 
| +    ('release', None), | 
| +]) | 
| +def test_build_webext(platform, dev_build_release, keyfile, tmpdir, srcdir, | 
| +                      buildnum, capsys): | 
| +    release = dev_build_release == 'release' | 
| +    devenv = dev_build_release == 'devenv' | 
| + | 
| +    if platform == 'chrome' and release: | 
| +        key = keyfile | 
| +    else: | 
| +        key = None | 
| + | 
| +    filenames = { | 
| +        'gecko-webext': 'adblockplusfirefox-1.2.3{}.xpi', | 
| +        'chrome': 'adblockpluschrome-1.2.3{{}}.{}'.format( | 
| +            {True: 'crx', False: 'zip'}[release] | 
| +        ), | 
| +    } | 
| + | 
| +    expected_manifest = os.path.join( | 
| +        os.path.dirname(__file__), | 
| +        'expecteddata', | 
| +        'manifest_{}_{}.json'.format(platform, dev_build_release), | 
| +    ) | 
| + | 
| +    run_webext_build(platform, dev_build_release, srcdir, buildnum=buildnum, | 
| +                     keyfile=key) | 
| + | 
| +    # The makeIcons() in packagerChrome.py should warn about non-square | 
| +    # icons via stderr. | 
| +    out, err = capsys.readouterr() | 
| +    assert 'icon should be square' in err | 
| + | 
| +    if devenv: | 
| +        content_class = DirContent | 
| +        out_file_path = os.path.join(str(srcdir), 'devenv.' + platform) | 
| +    else: | 
| +        content_class = ZipContent | 
| + | 
| +        if release: | 
| +            add_version = '' | 
| +        else: | 
| +            add_version = '.' + buildnum | 
| + | 
| +        out_file = filenames[platform].format(add_version) | 
| + | 
| +        out_file_path = os.path.abspath(os.path.join( | 
| +            os.path.dirname(__file__), os.pardir, out_file)) | 
| +        assert os.path.exists(out_file_path) | 
| + | 
| +    if release and platform == 'chrome': | 
| +        assert_chrome_signature(out_file_path, keyfile) | 
| + | 
| +    with content_class(out_file_path) as package: | 
| +        assert_manifest_content( | 
| +            package.read('manifest.json'), expected_manifest | 
| +        ) | 
| +        assert_base_files(package) | 
| +        assert_devenv_scripts(package, devenv) | 
| +        assert_all_locales_present(package, '_locales') | 
| +        assert_gecko_locale_conversion(package) | 
| +        assert_convert_js(package, platform == 'gecko-webext') | 
| + | 
| +        if platform == 'chrome': | 
| +            assert_locale_upfix(package) | 
|  |