Wed, 31 Dec 2014 06:09:35 +0100
Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.
michael@0 | 1 | # This Source Code Form is subject to the terms of the Mozilla Public |
michael@0 | 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this |
michael@0 | 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. |
michael@0 | 4 | |
michael@0 | 5 | import json |
michael@0 | 6 | import os |
michael@0 | 7 | import subprocess |
michael@0 | 8 | import sys |
michael@0 | 9 | import tempfile |
michael@0 | 10 | import threading |
michael@0 | 11 | import zipfile |
michael@0 | 12 | |
michael@0 | 13 | from ConfigParser import ConfigParser |
michael@0 | 14 | |
michael@0 | 15 | this_dir = os.path.abspath(os.path.dirname(__file__)) |
michael@0 | 16 | marionette_dir = os.path.dirname(this_dir) |
michael@0 | 17 | marionette_client_dir = os.path.join(marionette_dir, 'client', 'marionette') |
michael@0 | 18 | |
michael@0 | 19 | def find_b2g(): |
michael@0 | 20 | sys.path.append(marionette_client_dir) |
michael@0 | 21 | from b2gbuild import B2GBuild |
michael@0 | 22 | return B2GBuild() |
michael@0 | 23 | |
michael@0 | 24 | class DictObject(dict): |
michael@0 | 25 | def __getattr__(self, item): |
michael@0 | 26 | try: |
michael@0 | 27 | return self.__getitem__(item) |
michael@0 | 28 | except KeyError: |
michael@0 | 29 | raise AttributeError(item) |
michael@0 | 30 | |
michael@0 | 31 | def __getitem__(self, item): |
michael@0 | 32 | value = dict.__getitem__(self, item) |
michael@0 | 33 | if isinstance(value, dict): |
michael@0 | 34 | return DictObject(value) |
michael@0 | 35 | return value |
michael@0 | 36 | |
michael@0 | 37 | class SmokeTestError(Exception): |
michael@0 | 38 | pass |
michael@0 | 39 | |
michael@0 | 40 | class SmokeTestConfigError(SmokeTestError): |
michael@0 | 41 | def __init__(self, message): |
michael@0 | 42 | SmokeTestError.__init__(self, 'smoketest-config.json: ' + message) |
michael@0 | 43 | |
michael@0 | 44 | class SmokeTestConfig(DictObject): |
michael@0 | 45 | TOP_LEVEL_REQUIRED = ('devices', 'public_key', 'private_key') |
michael@0 | 46 | DEVICE_REQUIRED = ('system_fs_type', 'system_location', 'data_fs_type', |
michael@0 | 47 | 'data_location', 'sdcard', 'sdcard_recovery', |
michael@0 | 48 | 'serials') |
michael@0 | 49 | |
michael@0 | 50 | def __init__(self, build_dir): |
michael@0 | 51 | self.top_dir = build_dir |
michael@0 | 52 | self.build_data = {} |
michael@0 | 53 | self.flash_template = None |
michael@0 | 54 | |
michael@0 | 55 | with open(os.path.join(build_dir, 'smoketest-config.json')) as f: |
michael@0 | 56 | DictObject.__init__(self, json.loads(f.read())) |
michael@0 | 57 | |
michael@0 | 58 | for required in self.TOP_LEVEL_REQUIRED: |
michael@0 | 59 | if required not in self: |
michael@0 | 60 | raise SmokeTestConfigError('No "%s" found' % required) |
michael@0 | 61 | |
michael@0 | 62 | if len(self.devices) == 0: |
michael@0 | 63 | raise SmokeTestConfigError('No devices found') |
michael@0 | 64 | |
michael@0 | 65 | for name, device in self.devices.iteritems(): |
michael@0 | 66 | for required in self.DEVICE_REQUIRED: |
michael@0 | 67 | if required not in device: |
michael@0 | 68 | raise SmokeTestConfigError('No "%s" found in device "%s"' % (required, name)) |
michael@0 | 69 | |
michael@0 | 70 | def get_build_data(self, device, build_id): |
michael@0 | 71 | if device in self.build_data: |
michael@0 | 72 | if build_id in self.build_data[device]: |
michael@0 | 73 | return self.build_data[device][build_id] |
michael@0 | 74 | else: |
michael@0 | 75 | self.build_data[device] = {} |
michael@0 | 76 | |
michael@0 | 77 | build_dir = os.path.join(self.top_dir, device, build_id) |
michael@0 | 78 | flash_zip = os.path.join(build_dir, 'flash.zip') |
michael@0 | 79 | with zipfile.ZipFile(flash_zip) as zip: |
michael@0 | 80 | app_ini = ConfigParser() |
michael@0 | 81 | app_ini.readfp(zip.open('system/b2g/application.ini')) |
michael@0 | 82 | platform_ini = ConfigParser() |
michael@0 | 83 | platform_ini.readfp(zip.open('system/b2g/platform.ini')) |
michael@0 | 84 | |
michael@0 | 85 | build_data = self.build_data[device][build_id] = DictObject({ |
michael@0 | 86 | 'app_version': app_ini.get('App', 'version'), |
michael@0 | 87 | 'app_build_id': app_ini.get('App', 'buildid'), |
michael@0 | 88 | 'platform_build_id': platform_ini.get('Build', 'buildid'), |
michael@0 | 89 | 'platform_milestone': platform_ini.get('Build', 'milestone'), |
michael@0 | 90 | 'complete_mar': os.path.join(build_dir, 'complete.mar'), |
michael@0 | 91 | 'flash_script': os.path.join(build_dir, 'flash.sh') |
michael@0 | 92 | }) |
michael@0 | 93 | |
michael@0 | 94 | return build_data |
michael@0 | 95 | |
michael@0 | 96 | class SmokeTestRunner(object): |
michael@0 | 97 | DEVICE_TIMEOUT = 30 |
michael@0 | 98 | |
michael@0 | 99 | def __init__(self, config, b2g, run_dir=None): |
michael@0 | 100 | self.config = config |
michael@0 | 101 | self.b2g = b2g |
michael@0 | 102 | self.run_dir = run_dir or tempfile.mkdtemp() |
michael@0 | 103 | |
michael@0 | 104 | update_tools = self.b2g.import_update_tools() |
michael@0 | 105 | self.b2g_config = update_tools.B2GConfig() |
michael@0 | 106 | |
michael@0 | 107 | def run_b2g_update_test(self, serial, testvars, tests): |
michael@0 | 108 | b2g_update_test = os.path.join(marionette_client_dir, |
michael@0 | 109 | 'venv_b2g_update_test.sh') |
michael@0 | 110 | |
michael@0 | 111 | if not tests: |
michael@0 | 112 | tests = [os.path.join(marionette_client_dir, 'tests', |
michael@0 | 113 | 'update-tests.ini')] |
michael@0 | 114 | |
michael@0 | 115 | args = ['bash', b2g_update_test, sys.executable, |
michael@0 | 116 | '--homedir', self.b2g.homedir, |
michael@0 | 117 | '--address', 'localhost:2828', |
michael@0 | 118 | '--type', 'b2g+smoketest', |
michael@0 | 119 | '--device', serial, |
michael@0 | 120 | '--testvars', testvars] |
michael@0 | 121 | args.extend(tests) |
michael@0 | 122 | |
michael@0 | 123 | print ' '.join(args) |
michael@0 | 124 | subprocess.check_call(args) |
michael@0 | 125 | |
michael@0 | 126 | def build_testvars(self, device, start_id, finish_id): |
michael@0 | 127 | run_dir = os.path.join(self.run_dir, device, start_id, finish_id) |
michael@0 | 128 | if not os.path.exists(run_dir): |
michael@0 | 129 | os.makedirs(run_dir) |
michael@0 | 130 | |
michael@0 | 131 | start_data = self.config.get_build_data(device, start_id) |
michael@0 | 132 | finish_data = self.config.get_build_data(device, finish_id) |
michael@0 | 133 | |
michael@0 | 134 | partial_mar = os.path.join(run_dir, 'partial.mar') |
michael@0 | 135 | if not os.path.exists(partial_mar): |
michael@0 | 136 | build_gecko_mar = os.path.join(self.b2g.update_tools, |
michael@0 | 137 | 'build-gecko-mar.py') |
michael@0 | 138 | subprocess.check_call([sys.executable, build_gecko_mar, |
michael@0 | 139 | '--from', start_data.complete_mar, |
michael@0 | 140 | '--to', finish_data.complete_mar, |
michael@0 | 141 | partial_mar]) |
michael@0 | 142 | finish_data['partial_mar'] = partial_mar |
michael@0 | 143 | |
michael@0 | 144 | testvars = os.path.join(run_dir, 'testvars.json') |
michael@0 | 145 | if not os.path.exists(testvars): |
michael@0 | 146 | open(testvars, 'w').write(json.dumps({ |
michael@0 | 147 | 'start': start_data, |
michael@0 | 148 | 'finish': finish_data |
michael@0 | 149 | })) |
michael@0 | 150 | |
michael@0 | 151 | return testvars |
michael@0 | 152 | |
michael@0 | 153 | def wait_for_device(self, device): |
michael@0 | 154 | for serial in self.config.devices[device].serials: |
michael@0 | 155 | proc = subprocess.Popen([self.b2g.adb_path, '-s', serial, |
michael@0 | 156 | 'wait-for-device']) |
michael@0 | 157 | def wait_for_adb(): |
michael@0 | 158 | proc.communicate() |
michael@0 | 159 | |
michael@0 | 160 | thread = threading.Thread(target=wait_for_adb) |
michael@0 | 161 | thread.start() |
michael@0 | 162 | thread.join(self.DEVICE_TIMEOUT) |
michael@0 | 163 | |
michael@0 | 164 | if thread.isAlive(): |
michael@0 | 165 | print >>sys.stderr, '%s device %s is not recognized by ADB, ' \ |
michael@0 | 166 | 'trying next device' % (device, serial) |
michael@0 | 167 | proc.kill() |
michael@0 | 168 | thread.join() |
michael@0 | 169 | continue |
michael@0 | 170 | |
michael@0 | 171 | return serial |
michael@0 | 172 | return None |
michael@0 | 173 | |
michael@0 | 174 | def run_smoketests_for_device(self, device, start_id, finish_id, tests): |
michael@0 | 175 | testvars = self.build_testvars(device, start_id, finish_id) |
michael@0 | 176 | serial = self.wait_for_device(device) |
michael@0 | 177 | if not serial: |
michael@0 | 178 | raise SmokeTestError('No connected serials for device "%s" could ' \ |
michael@0 | 179 | 'be found' % device) |
michael@0 | 180 | |
michael@0 | 181 | try: |
michael@0 | 182 | self.run_b2g_update_test(serial, testvars, tests) |
michael@0 | 183 | except subprocess.CalledProcessError: |
michael@0 | 184 | print >>sys.stderr, 'SMOKETEST-FAIL | START=%s | FINISH=%s | ' \ |
michael@0 | 185 | 'DEVICE=%s/%s | %s' % (start_id, finish_id, |
michael@0 | 186 | device, serial, testvars) |
michael@0 | 187 | |
michael@0 | 188 | def run_smoketests(self, build_ids, tests): |
michael@0 | 189 | build_ids.sort() |
michael@0 | 190 | |
michael@0 | 191 | latest_build_id = build_ids.pop(-1) |
michael@0 | 192 | for build_id in build_ids: |
michael@0 | 193 | for device in self.config.devices: |
michael@0 | 194 | self.run_smoketests_for_device(device, build_id, |
michael@0 | 195 | latest_build_id, tests) |