diff options
Diffstat (limited to 'testing/mozharness/scripts/android_emulator_unittest.py')
-rw-r--r-- | testing/mozharness/scripts/android_emulator_unittest.py | 755 |
1 files changed, 755 insertions, 0 deletions
diff --git a/testing/mozharness/scripts/android_emulator_unittest.py b/testing/mozharness/scripts/android_emulator_unittest.py new file mode 100644 index 000000000..2d17b9cb6 --- /dev/null +++ b/testing/mozharness/scripts/android_emulator_unittest.py @@ -0,0 +1,755 @@ +#!/usr/bin/env python +# ***** BEGIN LICENSE BLOCK ***** +# 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/. +# ***** END LICENSE BLOCK ***** + +import copy +import datetime +import glob +import os +import re +import sys +import signal +import socket +import subprocess +import telnetlib +import time +import tempfile + +# load modules from parent dir +sys.path.insert(1, os.path.dirname(sys.path[0])) + +from mozprocess import ProcessHandler + +from mozharness.base.log import FATAL +from mozharness.base.script import BaseScript, PreScriptAction, PostScriptAction +from mozharness.base.vcs.vcsbase import VCSMixin +from mozharness.mozilla.blob_upload import BlobUploadMixin, blobupload_config_options +from mozharness.mozilla.mozbase import MozbaseMixin +from mozharness.mozilla.testing.testbase import TestingMixin, testing_config_options +from mozharness.mozilla.testing.unittest import EmulatorMixin + + +class AndroidEmulatorTest(BlobUploadMixin, TestingMixin, EmulatorMixin, VCSMixin, BaseScript, MozbaseMixin): + config_options = [[ + ["--test-suite"], + {"action": "store", + "dest": "test_suite", + } + ], [ + ["--adb-path"], + {"action": "store", + "dest": "adb_path", + "default": None, + "help": "Path to adb", + } + ], [ + ["--total-chunk"], + {"action": "store", + "dest": "total_chunks", + "default": None, + "help": "Number of total chunks", + } + ], [ + ["--this-chunk"], + {"action": "store", + "dest": "this_chunk", + "default": None, + "help": "Number of this chunk", + } + ]] + copy.deepcopy(testing_config_options) + \ + copy.deepcopy(blobupload_config_options) + + error_list = [ + ] + + virtualenv_requirements = [ + ] + + virtualenv_modules = [ + ] + + app_name = None + + def __init__(self, require_config_file=False): + super(AndroidEmulatorTest, self).__init__( + config_options=self.config_options, + all_actions=['clobber', + 'read-buildbot-config', + 'setup-avds', + 'start-emulator', + 'download-and-extract', + 'create-virtualenv', + 'verify-emulator', + 'install', + 'run-tests', + ], + default_actions=['clobber', + 'start-emulator', + 'download-and-extract', + 'create-virtualenv', + 'verify-emulator', + 'install', + 'run-tests', + ], + require_config_file=require_config_file, + config={ + 'virtualenv_modules': self.virtualenv_modules, + 'virtualenv_requirements': self.virtualenv_requirements, + 'require_test_zip': True, + # IP address of the host as seen from the emulator + 'remote_webserver': '10.0.2.2', + } + ) + + # these are necessary since self.config is read only + c = self.config + abs_dirs = self.query_abs_dirs() + self.adb_path = self.query_exe('adb') + self.installer_url = c.get('installer_url') + self.installer_path = c.get('installer_path') + self.test_url = c.get('test_url') + self.test_packages_url = c.get('test_packages_url') + self.test_manifest = c.get('test_manifest') + self.robocop_path = os.path.join(abs_dirs['abs_work_dir'], "robocop.apk") + self.minidump_stackwalk_path = c.get("minidump_stackwalk_path") + self.emulator = c.get('emulator') + self.test_suite = c.get('test_suite') + self.this_chunk = c.get('this_chunk') + self.total_chunks = c.get('total_chunks') + if self.test_suite not in self.config["suite_definitions"]: + # accept old-style test suite name like "mochitest-3" + m = re.match("(.*)-(\d*)", self.test_suite) + if m: + self.test_suite = m.group(1) + if self.this_chunk is None: + self.this_chunk = m.group(2) + self.sdk_level = None + self.xre_path = None + + def _query_tests_dir(self): + dirs = self.query_abs_dirs() + try: + test_dir = self.config["suite_definitions"][self.test_suite]["testsdir"] + except: + test_dir = self.test_suite + return os.path.join(dirs['abs_test_install_dir'], test_dir) + + def query_abs_dirs(self): + if self.abs_dirs: + return self.abs_dirs + abs_dirs = super(AndroidEmulatorTest, self).query_abs_dirs() + dirs = {} + dirs['abs_test_install_dir'] = os.path.join( + abs_dirs['abs_work_dir'], 'tests') + dirs['abs_xre_dir'] = os.path.join( + abs_dirs['abs_work_dir'], 'hostutils') + dirs['abs_modules_dir'] = os.path.join( + dirs['abs_test_install_dir'], 'modules') + dirs['abs_blob_upload_dir'] = os.path.join( + abs_dirs['abs_work_dir'], 'blobber_upload_dir') + dirs['abs_emulator_dir'] = abs_dirs['abs_work_dir'] + dirs['abs_mochitest_dir'] = os.path.join( + dirs['abs_test_install_dir'], 'mochitest') + dirs['abs_marionette_dir'] = os.path.join( + dirs['abs_test_install_dir'], 'marionette', 'harness', 'marionette_harness') + dirs['abs_marionette_tests_dir'] = os.path.join( + dirs['abs_test_install_dir'], 'marionette', 'tests', 'testing', + 'marionette', 'harness', 'marionette_harness', 'tests') + dirs['abs_avds_dir'] = self.config.get("avds_dir", "/home/cltbld/.android") + + for key in dirs.keys(): + if key not in abs_dirs: + abs_dirs[key] = dirs[key] + self.abs_dirs = abs_dirs + return self.abs_dirs + + @PreScriptAction('create-virtualenv') + def _pre_create_virtualenv(self, action): + dirs = self.query_abs_dirs() + requirements = None + if os.path.isdir(dirs['abs_mochitest_dir']): + # mochitest is the only thing that needs this + requirements = os.path.join(dirs['abs_mochitest_dir'], + 'websocketprocessbridge', + 'websocketprocessbridge_requirements.txt') + elif self.test_suite == 'marionette': + requirements = os.path.join(dirs['abs_test_install_dir'], + 'config', 'marionette_requirements.txt') + if requirements: + self.register_virtualenv_module(requirements=[requirements], + two_pass=True) + + def _launch_emulator(self): + env = self.query_env() + + # Set $LD_LIBRARY_PATH to self.dirs['abs_work_dir'] so that + # the emulator picks up the symlink to libGL.so.1 that we + # constructed in start_emulator. + env['LD_LIBRARY_PATH'] = self.abs_dirs['abs_work_dir'] + + # Set environment variables to help emulator find the AVD. + # In newer versions of the emulator, ANDROID_AVD_HOME should + # point to the 'avd' directory. + # For older versions of the emulator, ANDROID_SDK_HOME should + # point to the directory containing the '.android' directory + # containing the 'avd' directory. + avd_home_dir = self.abs_dirs['abs_avds_dir'] + env['ANDROID_AVD_HOME'] = os.path.join(avd_home_dir, 'avd') + env['ANDROID_SDK_HOME'] = os.path.abspath(os.path.join(avd_home_dir, '..')) + + command = [ + "emulator", "-avd", self.emulator["name"], + "-port", str(self.emulator["emulator_port"]), + ] + if "emulator_extra_args" in self.config: + command += self.config["emulator_extra_args"].split() + + tmp_file = tempfile.NamedTemporaryFile(mode='w') + tmp_stdout = open(tmp_file.name, 'w') + self.info("Created temp file %s." % tmp_file.name) + self.info("Trying to start the emulator with this command: %s" % ' '.join(command)) + proc = subprocess.Popen(command, stdout=tmp_stdout, stderr=tmp_stdout, env=env) + return { + "process": proc, + "tmp_file": tmp_file, + } + + def _retry(self, max_attempts, interval, func, description, max_time = 0): + ''' + Execute func until it returns True, up to max_attempts times, waiting for + interval seconds between each attempt. description is logged on each attempt. + If max_time is specified, no further attempts will be made once max_time + seconds have elapsed; this provides some protection for the case where + the run-time for func is long or highly variable. + ''' + status = False + attempts = 0 + if max_time > 0: + end_time = datetime.datetime.now() + datetime.timedelta(seconds = max_time) + else: + end_time = None + while attempts < max_attempts and not status: + if (end_time is not None) and (datetime.datetime.now() > end_time): + self.info("Maximum retry run-time of %d seconds exceeded; remaining attempts abandoned" % max_time) + break + if attempts != 0: + self.info("Sleeping %d seconds" % interval) + time.sleep(interval) + attempts += 1 + self.info(">> %s: Attempt #%d of %d" % (description, attempts, max_attempts)) + status = func() + return status + + def _run_with_timeout(self, timeout, cmd): + timeout_cmd = ['timeout', '%s' % timeout] + cmd + return self._run_proc(timeout_cmd) + + def _run_proc(self, cmd): + self.info('Running %s' % subprocess.list2cmdline(cmd)) + p = subprocess.Popen(cmd, stdout=subprocess.PIPE) + out, err = p.communicate() + if out: + self.info('%s' % str(out.strip())) + if err: + self.info('stderr: %s' % str(err.strip())) + return out + + def _telnet_cmd(self, telnet, command): + telnet.write('%s\n' % command) + result = telnet.read_until('OK', 10) + self.info('%s: %s' % (command, result)) + return result + + def _verify_adb(self): + self.info('Verifying adb connectivity') + self._run_with_timeout(180, [self.adb_path, 'wait-for-device']) + return True + + def _verify_adb_device(self): + out = self._run_with_timeout(30, [self.adb_path, 'devices']) + if (self.emulator['device_id'] in out) and ("device" in out): + return True + return False + + def _is_boot_completed(self): + boot_cmd = [self.adb_path, '-s', self.emulator['device_id'], + 'shell', 'getprop', 'sys.boot_completed'] + out = self._run_with_timeout(30, boot_cmd) + if out.strip() == '1': + return True + return False + + def _telnet_to_emulator(self): + port = self.emulator["emulator_port"] + telnet_ok = False + try: + tn = telnetlib.Telnet('localhost', port, 10) + if tn is not None: + self.info('Connected to port %d' % port) + res = tn.read_until('OK', 10) + self.info(res) + self._telnet_cmd(tn, 'avd status') + self._telnet_cmd(tn, 'redir list') + self._telnet_cmd(tn, 'network status') + tn.write('quit\n') + tn.read_all() + telnet_ok = True + else: + self.warning('Unable to connect to port %d' % port) + except socket.error, e: + self.info('Trying again after socket error: %s' % str(e)) + pass + except EOFError: + self.info('Trying again after EOF') + pass + except: + self.info('Trying again after unexpected exception') + pass + finally: + if tn is not None: + tn.close() + return telnet_ok + + def _verify_emulator(self): + adb_ok = self._verify_adb() + if not adb_ok: + self.warning('Unable to communicate with adb') + return False + adb_device_ok = self._retry(4, 30, self._verify_adb_device, "Verify emulator visible to adb") + if not adb_device_ok: + self.warning('Unable to communicate with emulator via adb') + return False + boot_ok = self._retry(30, 10, self._is_boot_completed, "Verify Android boot completed", max_time = 330) + if not boot_ok: + self.warning('Unable to verify Android boot completion') + return False + telnet_ok = self._retry(4, 30, self._telnet_to_emulator, "Verify telnet to emulator") + if not telnet_ok: + self.warning('Unable to telnet to emulator on port %d' % self.emulator["emulator_port"]) + return False + return True + + def _verify_emulator_and_restart_on_fail(self): + emulator_ok = self._verify_emulator() + if not emulator_ok: + self._dump_host_state() + self._screenshot("emulator-startup-screenshot-") + self._kill_processes(self.config["emulator_process_name"]) + self._run_proc(['ps', '-ef']) + self._dump_emulator_log() + # remove emulator tmp files + for dir in glob.glob("/tmp/android-*"): + self.rmtree(dir) + self._restart_adbd() + time.sleep(5) + self.emulator_proc = self._launch_emulator() + return emulator_ok + + def _install_fennec_apk(self): + install_ok = False + if int(self.sdk_level) >= 23: + cmd = [self.adb_path, '-s', self.emulator['device_id'], 'install', '-r', '-g', self.installer_path] + else: + cmd = [self.adb_path, '-s', self.emulator['device_id'], 'install', '-r', self.installer_path] + out = self._run_with_timeout(300, cmd) + if 'Success' in out: + install_ok = True + return install_ok + + def _install_robocop_apk(self): + install_ok = False + if int(self.sdk_level) >= 23: + cmd = [self.adb_path, '-s', self.emulator['device_id'], 'install', '-r', '-g', self.robocop_path] + else: + cmd = [self.adb_path, '-s', self.emulator['device_id'], 'install', '-r', self.robocop_path] + out = self._run_with_timeout(300, cmd) + if 'Success' in out: + install_ok = True + return install_ok + + def _dump_host_state(self): + self._run_proc(['ps', '-ef']) + self._run_proc(['netstat', '-a', '-p', '-n', '-t', '-u']) + + def _dump_emulator_log(self): + self.info("##### %s emulator log begins" % self.emulator["name"]) + output = self.read_from_file(self.emulator_proc["tmp_file"].name, verbose=False) + if output: + self.info(output) + self.info("##### %s emulator log ends" % self.emulator["name"]) + + def _kill_processes(self, process_name): + p = subprocess.Popen(['ps', '-A'], stdout=subprocess.PIPE) + out, err = p.communicate() + self.info("Let's kill every process called %s" % process_name) + for line in out.splitlines(): + if process_name in line: + pid = int(line.split(None, 1)[0]) + self.info("Killing pid %d." % pid) + os.kill(pid, signal.SIGKILL) + + def _restart_adbd(self): + self._run_with_timeout(30, [self.adb_path, 'kill-server']) + self._run_with_timeout(30, [self.adb_path, 'start-server']) + + def _screenshot(self, prefix): + """ + Save a screenshot of the entire screen to the blob upload directory. + """ + dirs = self.query_abs_dirs() + utility = os.path.join(self.xre_path, "screentopng") + if not os.path.exists(utility): + self.warning("Unable to take screenshot: %s does not exist" % utility) + return + try: + tmpfd, filename = tempfile.mkstemp(prefix=prefix, suffix='.png', + dir=dirs['abs_blob_upload_dir']) + os.close(tmpfd) + self.info("Taking screenshot with %s; saving to %s" % (utility, filename)) + subprocess.call([utility, filename], env=self.query_env()) + except OSError, err: + self.warning("Failed to take screenshot: %s" % err.strerror) + + def _query_package_name(self): + if self.app_name is None: + #find appname from package-name.txt - assumes download-and-extract has completed successfully + apk_dir = self.abs_dirs['abs_work_dir'] + self.apk_path = os.path.join(apk_dir, self.installer_path) + unzip = self.query_exe("unzip") + package_path = os.path.join(apk_dir, 'package-name.txt') + unzip_cmd = [unzip, '-q', '-o', self.apk_path] + self.run_command(unzip_cmd, cwd=apk_dir, halt_on_failure=True) + self.app_name = str(self.read_from_file(package_path, verbose=True)).rstrip() + return self.app_name + + def preflight_install(self): + # in the base class, this checks for mozinstall, but we don't use it + pass + + def _build_command(self): + c = self.config + dirs = self.query_abs_dirs() + + if self.test_suite not in self.config["suite_definitions"]: + self.fatal("Key '%s' not defined in the config!" % self.test_suite) + + cmd = [ + self.query_python_path('python'), + '-u', + os.path.join( + self._query_tests_dir(), + self.config["suite_definitions"][self.test_suite]["run_filename"] + ), + ] + + raw_log_file = os.path.join(dirs['abs_blob_upload_dir'], + '%s_raw.log' % self.test_suite) + + error_summary_file = os.path.join(dirs['abs_blob_upload_dir'], + '%s_errorsummary.log' % self.test_suite) + str_format_values = { + 'app': self._query_package_name(), + 'remote_webserver': c['remote_webserver'], + 'xre_path': self.xre_path, + 'utility_path': self.xre_path, + 'http_port': self.emulator['http_port'], + 'ssl_port': self.emulator['ssl_port'], + 'certs_path': os.path.join(dirs['abs_work_dir'], 'tests/certs'), + # TestingMixin._download_and_extract_symbols() will set + # self.symbols_path when downloading/extracting. + 'symbols_path': self.symbols_path, + 'modules_dir': dirs['abs_modules_dir'], + 'installer_path': self.installer_path, + 'raw_log_file': raw_log_file, + 'error_summary_file': error_summary_file, + 'dm_trans': c['device_manager'], + # marionette options + 'address': c.get('marionette_address'), + 'gecko_log': os.path.join(dirs["abs_blob_upload_dir"], 'gecko.log'), + 'test_manifest': os.path.join( + dirs['abs_marionette_tests_dir'], + self.config.get('marionette_test_manifest', '') + ), + } + for option in self.config["suite_definitions"][self.test_suite]["options"]: + opt = option.split('=')[0] + # override configured chunk options with script args, if specified + if opt == '--this-chunk' and self.this_chunk is not None: + continue + if opt == '--total-chunks' and self.total_chunks is not None: + continue + cmd.extend([option % str_format_values]) + + if self.this_chunk is not None: + cmd.extend(['--this-chunk', self.this_chunk]) + if self.total_chunks is not None: + cmd.extend(['--total-chunks', self.total_chunks]) + + try_options, try_tests = self.try_args(self.test_suite) + cmd.extend(try_options) + cmd.extend(self.query_tests_args( + self.config["suite_definitions"][self.test_suite].get("tests"), + None, + try_tests)) + + return cmd + + def _get_repo_url(self, path): + """ + Return a url for a file (typically a tooltool manifest) in this hg repo + and using this revision (or mozilla-central/default if repo/rev cannot + be determined). + + :param path specifies the directory path to the file of interest. + """ + if 'GECKO_HEAD_REPOSITORY' in os.environ and 'GECKO_HEAD_REV' in os.environ: + # probably taskcluster + repo = os.environ['GECKO_HEAD_REPOSITORY'] + revision = os.environ['GECKO_HEAD_REV'] + elif self.buildbot_config and 'properties' in self.buildbot_config: + # probably buildbot + repo = 'https://hg.mozilla.org/%s' % self.buildbot_config['properties']['repo_path'] + revision = self.buildbot_config['properties']['revision'] + else: + # something unexpected! + repo = 'https://hg.mozilla.org/mozilla-central' + revision = 'default' + self.warning('Unable to find repo/revision for manifest; using mozilla-central/default') + url = '%s/raw-file/%s/%s' % ( + repo, + revision, + path) + return url + + def _tooltool_fetch(self, url, dir): + c = self.config + + manifest_path = self.download_file( + url, + file_name='releng.manifest', + parent_dir=dir + ) + + if not os.path.exists(manifest_path): + self.fatal("Could not retrieve manifest needed to retrieve " + "artifacts from %s" % manifest_path) + + self.tooltool_fetch(manifest_path, + output_dir=dir, + cache=c.get("tooltool_cache", None)) + + ########################################## + ### Actions for AndroidEmulatorTest ### + ########################################## + def setup_avds(self): + ''' + If tooltool cache mechanism is enabled, the cached version is used by + the fetch command. If the manifest includes an "unpack" field, tooltool + will unpack all compressed archives mentioned in the manifest. + ''' + c = self.config + dirs = self.query_abs_dirs() + + # FIXME + # Clobbering and re-unpacking would not be needed if we had a way to + # check whether the unpacked content already present match the + # contents of the tar ball + self.rmtree(dirs['abs_avds_dir']) + self.mkdir_p(dirs['abs_avds_dir']) + if 'avd_url' in c: + # Intended for experimental setups to evaluate an avd prior to + # tooltool deployment. + url = c['avd_url'] + self.download_unpack(url, dirs['abs_avds_dir']) + else: + url = self._get_repo_url(c["tooltool_manifest_path"]) + self._tooltool_fetch(url, dirs['abs_avds_dir']) + + avd_home_dir = self.abs_dirs['abs_avds_dir'] + if avd_home_dir != "/home/cltbld/.android": + # Modify the downloaded avds to point to the right directory. + cmd = [ + 'bash', '-c', + 'sed -i "s|/home/cltbld/.android|%s|" %s/test-*.ini' % + (avd_home_dir, os.path.join(avd_home_dir, 'avd')) + ] + proc = ProcessHandler(cmd) + proc.run() + proc.wait() + + def start_emulator(self): + ''' + Starts the emulator + ''' + if 'emulator_url' in self.config or 'emulator_manifest' in self.config or 'tools_manifest' in self.config: + self.install_emulator() + + if not os.path.isfile(self.adb_path): + self.fatal("The adb binary '%s' is not a valid file!" % self.adb_path) + self._restart_adbd() + + if not self.config.get("developer_mode"): + # We kill compiz because it sometimes prevents us from starting the emulator + self._kill_processes("compiz") + self._kill_processes("xpcshell") + + # We add a symlink for libGL.so because the emulator dlopen()s it by that name + # even though the installed library on most systems without dev packages is + # libGL.so.1 + linkfile = os.path.join(self.abs_dirs['abs_work_dir'], "libGL.so") + self.info("Attempting to establish symlink for %s" % linkfile) + try: + os.unlink(linkfile) + except OSError: + pass + for libdir in ["/usr/lib/x86_64-linux-gnu/mesa", + "/usr/lib/i386-linux-gnu/mesa", + "/usr/lib/mesa"]: + libfile = os.path.join(libdir, "libGL.so.1") + if os.path.exists(libfile): + self.info("Symlinking %s -> %s" % (linkfile, libfile)) + self.mkdir_p(self.abs_dirs['abs_work_dir']) + os.symlink(libfile, linkfile) + break + self.emulator_proc = self._launch_emulator() + + def verify_emulator(self): + ''' + Check to see if the emulator can be contacted via adb and telnet. + If any communication attempt fails, kill the emulator, re-launch, and re-check. + ''' + self.mkdir_p(self.query_abs_dirs()['abs_blob_upload_dir']) + max_restarts = 5 + emulator_ok = self._retry(max_restarts, 10, self._verify_emulator_and_restart_on_fail, "Check emulator") + if not emulator_ok: + self.fatal('INFRA-ERROR: Unable to start emulator after %d attempts' % max_restarts) + # Start logcat for the emulator. The adb process runs until the + # corresponding emulator is killed. Output is written directly to + # the blobber upload directory so that it is uploaded automatically + # at the end of the job. + logcat_filename = 'logcat-%s.log' % self.emulator["device_id"] + logcat_path = os.path.join(self.abs_dirs['abs_blob_upload_dir'], logcat_filename) + logcat_cmd = '%s -s %s logcat -v threadtime Trace:S StrictMode:S ExchangeService:S > %s &' % \ + (self.adb_path, self.emulator["device_id"], logcat_path) + self.info(logcat_cmd) + os.system(logcat_cmd) + # Get a post-boot emulator process list for diagnostics + ps_cmd = [self.adb_path, '-s', self.emulator["device_id"], 'shell', 'ps'] + self._run_with_timeout(30, ps_cmd) + + def download_and_extract(self): + """ + Download and extract fennec APK, tests.zip, host utils, and robocop (if required). + """ + super(AndroidEmulatorTest, self).download_and_extract(suite_categories=[self.test_suite]) + dirs = self.query_abs_dirs() + if self.test_suite.startswith('robocop'): + robocop_url = self.installer_url[:self.installer_url.rfind('/')] + '/robocop.apk' + self.info("Downloading robocop...") + self.download_file(robocop_url, 'robocop.apk', dirs['abs_work_dir'], error_level=FATAL) + self.rmtree(dirs['abs_xre_dir']) + self.mkdir_p(dirs['abs_xre_dir']) + if self.config["hostutils_manifest_path"]: + url = self._get_repo_url(self.config["hostutils_manifest_path"]) + self._tooltool_fetch(url, dirs['abs_xre_dir']) + for p in glob.glob(os.path.join(dirs['abs_xre_dir'], 'host-utils-*')): + if os.path.isdir(p) and os.path.isfile(os.path.join(p, 'xpcshell')): + self.xre_path = p + if not self.xre_path: + self.fatal("xre path not found in %s" % dirs['abs_xre_dir']) + else: + self.fatal("configure hostutils_manifest_path!") + + def install(self): + """ + Install APKs on the emulator + """ + assert self.installer_path is not None, \ + "Either add installer_path to the config or use --installer-path." + install_needed = self.config["suite_definitions"][self.test_suite].get("install") + if install_needed == False: + self.info("Skipping apk installation for %s" % self.test_suite) + return + + self.sdk_level = self._run_with_timeout(30, [self.adb_path, '-s', self.emulator['device_id'], + 'shell', 'getprop', 'ro.build.version.sdk']) + + # Install Fennec + install_ok = self._retry(3, 30, self._install_fennec_apk, "Install Fennec APK") + if not install_ok: + self.fatal('INFRA-ERROR: Failed to install %s on %s' % (self.installer_path, self.emulator["name"])) + + # Install Robocop if required + if self.test_suite.startswith('robocop'): + install_ok = self._retry(3, 30, self._install_robocop_apk, "Install Robocop APK") + if not install_ok: + self.fatal('INFRA-ERROR: Failed to install %s on %s' % (self.robocop_path, self.emulator["name"])) + + self.info("Finished installing apps for %s" % self.emulator["name"]) + + def run_tests(self): + """ + Run the tests + """ + cmd = self._build_command() + + try: + cwd = self._query_tests_dir() + except: + self.fatal("Don't know how to run --test-suite '%s'!" % self.test_suite) + env = self.query_env() + if self.query_minidump_stackwalk(): + env['MINIDUMP_STACKWALK'] = self.minidump_stackwalk_path + env['MOZ_UPLOAD_DIR'] = self.query_abs_dirs()['abs_blob_upload_dir'] + env['MINIDUMP_SAVE_PATH'] = self.query_abs_dirs()['abs_blob_upload_dir'] + + self.info("Running on %s the command %s" % (self.emulator["name"], subprocess.list2cmdline(cmd))) + self.info("##### %s log begins" % self.test_suite) + + # TinderBoxPrintRe does not know about the '-debug' categories + aliases = { + 'reftest-debug': 'reftest', + 'jsreftest-debug': 'jsreftest', + 'crashtest-debug': 'crashtest', + } + suite_category = aliases.get(self.test_suite, self.test_suite) + parser = self.get_test_output_parser( + suite_category, + config=self.config, + log_obj=self.log_obj, + error_list=self.error_list) + self.run_command(cmd, cwd=cwd, env=env, output_parser=parser) + tbpl_status, log_level = parser.evaluate_parser(0) + parser.append_tinderboxprint_line(self.test_suite) + + self.info("##### %s log ends" % self.test_suite) + self._dump_emulator_log() + self.buildbot_status(tbpl_status, level=log_level) + + @PostScriptAction('run-tests') + def stop_emulator(self, action, success=None): + ''' + Report emulator health, then make sure that the emulator has been stopped + ''' + self._verify_emulator() + self._kill_processes(self.config["emulator_process_name"]) + + def upload_blobber_files(self): + ''' + Override BlobUploadMixin.upload_blobber_files to ensure emulator is killed + first (if the emulator is still running, logcat may still be running, which + may lock the blob upload directory, causing a hang). + ''' + if self.config.get('blob_upload_branch'): + # Except on interactive workers, we want the emulator to keep running + # after the script is finished. So only kill it if blobber would otherwise + # have run anyway (it doesn't get run on interactive workers). + self._kill_processes(self.config["emulator_process_name"]) + super(AndroidEmulatorTest, self).upload_blobber_files() + +if __name__ == '__main__': + emulatorTest = AndroidEmulatorTest() + emulatorTest.run_and_exit() |