OLD | NEW |
(Empty) | |
| 1 # This Source Code Form is subject to the terms of the Mozilla Public |
| 2 # License, v. 2.0. If a copy of the MPL was not distributed with this |
| 3 # file, You can obtain one at http://mozilla.org/MPL/2.0/. |
| 4 |
| 5 import os |
| 6 import shutil |
| 7 import json |
| 8 import re |
| 9 from struct import unpack |
| 10 |
| 11 import pytest |
| 12 from Crypto.Hash import SHA |
| 13 from Crypto.PublicKey import RSA |
| 14 from Crypto.Signature import PKCS1_v1_5 |
| 15 |
| 16 |
| 17 from buildtools import packager, packagerChrome |
| 18 from buildtools.tests.tools import (DirContent, ZipContent, copy_metadata, |
| 19 run_webext_build, assert_manifest_content, |
| 20 assert_all_locales_present, locale_files) |
| 21 from buildtools.tests.conftest import ALL_LANGUAGES |
| 22 |
| 23 |
| 24 LOCALES_MODULE = { |
| 25 'test.Foobar': { |
| 26 'message': 'Ensuring dict-copy from modules for $domain$', |
| 27 'description': 'test description', |
| 28 'placeholders': {'content': '$1', 'example': 'www.adblockplus.org'} |
| 29 } |
| 30 } |
| 31 |
| 32 DTD_TEST = ('<!ENTITY access.key "access key(&a)">' |
| 33 '<!ENTITY ampersand "foo &-bar">') |
| 34 |
| 35 PROPERTIES_TEST = 'description=very descriptive!' |
| 36 |
| 37 |
| 38 @pytest.fixture |
| 39 def gecko_import(tmpdir): |
| 40 tmpdir.mkdir('_imp').mkdir('en-US').join('gecko.dtd').write(DTD_TEST) |
| 41 |
| 42 |
| 43 @pytest.fixture |
| 44 def locale_modules(tmpdir): |
| 45 mod_dir = tmpdir.mkdir('_modules') |
| 46 lang_dir = mod_dir.mkdir('en-US') |
| 47 lang_dir.join('module.json').write(json.dumps(LOCALES_MODULE)) |
| 48 lang_dir.join('unit.properties').write(json.dumps(PROPERTIES_TEST)) |
| 49 |
| 50 |
| 51 @pytest.fixture |
| 52 def icons(srcdir): |
| 53 icons_dir = srcdir.mkdir('icons') |
| 54 for filename in ['abp-16.png', 'abp-19.png', 'abp-53.png']: |
| 55 shutil.copy( |
| 56 os.path.join(os.path.dirname(__file__), filename), |
| 57 os.path.join(str(icons_dir), filename), |
| 58 ) |
| 59 |
| 60 |
| 61 @pytest.fixture |
| 62 def all_lang_locales(tmpdir): |
| 63 return locale_files(ALL_LANGUAGES, '_locales', tmpdir) |
| 64 |
| 65 |
| 66 @pytest.fixture |
| 67 def chrome_metadata(tmpdir): |
| 68 filename = 'metadata.chrome' |
| 69 copy_metadata(filename, tmpdir) |
| 70 |
| 71 |
| 72 @pytest.fixture |
| 73 def gecko_webext_metadata(tmpdir, chrome_metadata): |
| 74 filename = 'metadata.gecko-webext' |
| 75 copy_metadata(filename, tmpdir) |
| 76 |
| 77 |
| 78 @pytest.fixture |
| 79 def keyfile(tmpdir): |
| 80 """Test-privatekey for signing chrome release-package""" |
| 81 return os.path.join(os.path.dirname(__file__), 'chrome_rsa.pem') |
| 82 |
| 83 |
| 84 @pytest.fixture |
| 85 def lib_files(tmpdir): |
| 86 files = packager.Files(['lib'], set()) |
| 87 files['ext/a.js'] = 'var bar;' |
| 88 files['lib/b.js'] = 'var foo;' |
| 89 |
| 90 tmpdir.mkdir('lib').join('b.js').write(files['lib/b.js']) |
| 91 tmpdir.mkdir('ext').join('a.js').write(files['ext/a.js']) |
| 92 |
| 93 return files |
| 94 |
| 95 |
| 96 def assert_gecko_locale_conversion(package): |
| 97 locale = json.loads(package.read('_locales/en_US/messages.json')) |
| 98 |
| 99 assert locale.get('test_Foobar', {}) == LOCALES_MODULE['test.Foobar'] |
| 100 assert locale.get('access_key', {}) == {'message': 'access key'} |
| 101 assert locale.get('ampersand', {}) == {'message': 'foo -bar'} |
| 102 assert locale.get('_description', {}) == {'message': 'very descriptive!"'} |
| 103 |
| 104 |
| 105 def assert_convert_js(package, excluded=False): |
| 106 libfoo = package.read('lib/foo.js') |
| 107 |
| 108 assert 'var bar;' in libfoo |
| 109 assert 'require.modules["ext_a"]' in libfoo |
| 110 |
| 111 assert ('var foo;' in libfoo) != excluded |
| 112 assert ('require.modules["b"]' in libfoo) != excluded |
| 113 |
| 114 |
| 115 def assert_devenv_scripts(package, devenv): |
| 116 manifest = json.loads(package.read('manifest.json')) |
| 117 filenames = package.namelist() |
| 118 scripts = [ |
| 119 'ext/common.js', |
| 120 'ext/background.js', |
| 121 ] |
| 122 |
| 123 if devenv: |
| 124 assert 'qunit/index.html' in filenames |
| 125 assert 'devenvPoller__.js' in filenames |
| 126 assert 'devenvVersion__' in filenames |
| 127 |
| 128 assert '../ext/common.js' in package.read('qunit/index.html') |
| 129 assert '../ext/background.js' in package.read('qunit/index.html') |
| 130 |
| 131 assert set(manifest['background']['scripts']) == set( |
| 132 scripts + ['devenvPoller__.js'] |
| 133 ) |
| 134 else: |
| 135 assert 'qunit/index.html' not in filenames |
| 136 |
| 137 assert set(manifest['background']['scripts']) == set(scripts) |
| 138 |
| 139 |
| 140 def assert_base_files(package): |
| 141 filenames = set(package.namelist()) |
| 142 |
| 143 assert 'bar.json' in filenames |
| 144 assert 'manifest.json' in filenames |
| 145 assert 'lib/foo.js' in filenames |
| 146 assert 'foo/logo_50.png' in filenames |
| 147 assert 'icons/logo_150.png' in filenames |
| 148 |
| 149 |
| 150 def assert_chrome_signature(filename, keyfile): |
| 151 with open(filename, 'r') as fp: |
| 152 content = fp.read() |
| 153 |
| 154 _, _, l_pubkey, l_signature = unpack('<4sIII', content[:16]) |
| 155 signature = content[16 + l_pubkey: 16 + l_pubkey + l_signature] |
| 156 |
| 157 digest = SHA.new() |
| 158 with open(keyfile, 'r') as fp: |
| 159 rsa_key = RSA.importKey(fp.read()) |
| 160 |
| 161 signer = PKCS1_v1_5.new(rsa_key) |
| 162 |
| 163 digest.update(content[16 + l_pubkey + l_signature:]) |
| 164 assert signer.verify(digest, signature) |
| 165 |
| 166 |
| 167 def assert_locale_upfix(package): |
| 168 translations = [ |
| 169 json.loads(package.read('_locales/{}/messages.json'.format(lang))) |
| 170 for lang in ALL_LANGUAGES |
| 171 ] |
| 172 |
| 173 manifest = package.read('manifest.json') |
| 174 |
| 175 # Chrome Web Store requires descriptive translations to be present in |
| 176 # every language. |
| 177 for match in re.finditer(r'__MSG_(\S+)__', manifest): |
| 178 name = match.group(1) |
| 179 |
| 180 for other in translations[1:]: |
| 181 assert translations[0][name]['message'] == other[name]['message'] |
| 182 |
| 183 |
| 184 @pytest.mark.usefixtures( |
| 185 'all_lang_locales', |
| 186 'locale_modules', |
| 187 'gecko_import', |
| 188 'icons', |
| 189 'lib_files', |
| 190 'chrome_metadata', |
| 191 'gecko_webext_metadata', |
| 192 ) |
| 193 @pytest.mark.parametrize('platform', ['chrome', 'gecko-webext']) |
| 194 @pytest.mark.parametrize('dev_build_release,buildnum', [ |
| 195 ('build', None), |
| 196 ('build', '1337'), |
| 197 ('devenv', None), |
| 198 pytest.param('devenv', '1337', marks=pytest.mark.xfail), |
| 199 ('release', None), |
| 200 ('release', '1337'), |
| 201 ]) |
| 202 def test_build_webext(platform, dev_build_release, keyfile, tmpdir, srcdir, |
| 203 buildnum, capsys): |
| 204 release = dev_build_release == 'release' |
| 205 devenv = dev_build_release == 'devenv' |
| 206 |
| 207 if platform == 'chrome' and release: |
| 208 key = keyfile |
| 209 else: |
| 210 key = None |
| 211 |
| 212 filenames = { |
| 213 'gecko-webext': 'adblockplusfirefox-1.2.3{}.{}', |
| 214 'chrome': 'adblockpluschrome-1.2.3{}.{}' |
| 215 } |
| 216 |
| 217 extensions = { |
| 218 'gecko-webext': { |
| 219 True: 'xpi', |
| 220 False: 'xpi', |
| 221 }, |
| 222 'chrome': { |
| 223 True: 'crx', |
| 224 False: 'zip', |
| 225 }, |
| 226 } |
| 227 |
| 228 expected_manifest = os.path.join( |
| 229 os.path.dirname(__file__), |
| 230 'expecteddata', |
| 231 'manifest_{}_{}_{}.json'.format(platform, dev_build_release, buildnum), |
| 232 ) |
| 233 |
| 234 run_webext_build(platform, dev_build_release, srcdir, packagerChrome, |
| 235 buildnum=buildnum, keyfile=key) |
| 236 |
| 237 # The makeIcons() in packagerChrome.py should warn about non-square |
| 238 # icons via stderr. |
| 239 out, err = capsys.readouterr() |
| 240 assert 'icon should be square' in err |
| 241 |
| 242 if devenv: |
| 243 content_class = DirContent |
| 244 out_file_path = os.path.join(str(srcdir), 'devenv.' + platform) |
| 245 else: |
| 246 content_class = ZipContent |
| 247 |
| 248 if release: |
| 249 add_version = '' |
| 250 else: |
| 251 add_version = '.' + (buildnum or '0') |
| 252 extension = extensions[platform][release] |
| 253 |
| 254 out_file = filenames[platform].format(add_version, extension) |
| 255 |
| 256 out_file_path = os.path.abspath(os.path.join( |
| 257 os.path.dirname(__file__), os.pardir, out_file)) |
| 258 assert os.path.exists(out_file_path) |
| 259 |
| 260 if release and platform == 'chrome': |
| 261 assert_chrome_signature(out_file_path, keyfile) |
| 262 |
| 263 with content_class(out_file_path) as package: |
| 264 assert_manifest_content( |
| 265 package.read('manifest.json'), expected_manifest |
| 266 ) |
| 267 assert_base_files(package) |
| 268 assert_devenv_scripts(package, devenv) |
| 269 assert_all_locales_present(package, '_locales') |
| 270 assert_gecko_locale_conversion(package) |
| 271 assert_convert_js(package, platform == 'gecko-webext') |
| 272 |
| 273 if platform == 'chrome': |
| 274 assert_locale_upfix(package) |
OLD | NEW |