Bug 1311087 - Allow developer override of avd and emulator package in Android mozharness tests; r=kmoir
authorGeoff Brown <gbrown@mozilla.com>
Wed, 19 Oct 2016 08:41:23 -0600
changeset 318643 e6d49c56eac16d019be8036900919b22e66fbccb
parent 318642 d56e9b123ed63993592c477540b64ec24a18002b
child 318644 971940ade41462ecbea585293987cf9c58ac697f
push id20725
push userphilringnalda@gmail.com
push dateThu, 20 Oct 2016 01:36:01 +0000
treeherderfx-team@998ad5a74da8 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskmoir
bugs1311087
milestone52.0a1
Bug 1311087 - Allow developer override of avd and emulator package in Android mozharness tests; r=kmoir
testing/mozharness/scripts/android_emulator_unittest.py
testing/mozharness/scripts/androidx86_emulator_unittest.py
--- a/testing/mozharness/scripts/android_emulator_unittest.py
+++ b/testing/mozharness/scripts/android_emulator_unittest.py
@@ -145,18 +145,17 @@ class AndroidEmulatorTest(BlobUploadMixi
         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'] = os.path.join(
-            abs_dirs['abs_work_dir'], 'emulator')
+        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', 'marionette')
         dirs['abs_marionette_tests_dir'] = os.path.join(
             dirs['abs_test_install_dir'], 'marionette', 'tests', 'testing',
             'marionette', 'harness', 'marionette', 'tests')
         dirs['abs_avds_dir'] = self.config.get("avds_dir", "/home/cltbld/.android")
@@ -555,18 +554,24 @@ class AndroidEmulatorTest(BlobUploadMixi
         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'])
-        url = self._get_repo_url(c["tooltool_manifest_path"])
-        self._tooltool_fetch(url, 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'))
deleted file mode 100644
--- a/testing/mozharness/scripts/androidx86_emulator_unittest.py
+++ /dev/null
@@ -1,839 +0,0 @@
-#!/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 glob
-import os
-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, PostScriptRun, 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.buildbot import TBPL_WORST_LEVEL_TUPLE
-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 = [[
-        ["--robocop-url"],
-        {"action": "store",
-         "dest": "robocop_url",
-         "default": None,
-         "help": "URL to the robocop apk",
-         }
-    ], [
-        ["--test-suite"],
-        {"action": "append",
-         "dest": "test_suites",
-         }
-    ], [
-        ["--adb-path"],
-        {"action": "store",
-         "dest": "adb_path",
-         "default": None,
-         "help": "Path to adb",
-         }
-    ]] + 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-emulators',
-                         'download-and-extract',
-                         'create-virtualenv',
-                         'install',
-                         'run-tests',
-                        ],
-            default_actions=['clobber',
-                             'start-emulators',
-                             'download-and-extract',
-                             'create-virtualenv',
-                             '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_manifest = c.get('test_manifest')
-        self.robocop_url = c.get('robocop_url')
-        self.robocop_path = os.path.join(abs_dirs['abs_work_dir'], "robocop.apk")
-        self.minidump_stackwalk_path = c.get("minidump_stackwalk_path")
-        self.emulators = c.get('emulators')
-        self.test_suite_definitions = c['test_suite_definitions']
-        self.test_suites = c.get('test_suites')
-        for suite in self.test_suites:
-            assert suite in self.test_suite_definitions
-        self.xre_path = None
-
-    def _query_tests_dir(self, suite_name):
-        dirs = self.query_abs_dirs()
-        suite_category = self.test_suite_definitions[suite_name]["category"]
-        try:
-            test_dir = self.config["suite_definitions"][suite_category]["testsdir"]
-        except:
-            test_dir = suite_category
-
-        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'] = os.path.join(
-            abs_dirs['abs_work_dir'], 'emulator')
-        dirs['abs_mochitest_dir'] = os.path.join(
-            dirs['abs_test_install_dir'], 'mochitest')
-        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()
-
-        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')
-
-            self.register_virtualenv_module(requirements=[requirements],
-                                            two_pass=True)
-
-    def _build_arg(self, option, value):
-        """
-        Build a command line argument
-        """
-        if not value:
-            return []
-        return [str(option), str(value)]
-
-    def _redirectSUT(self, emulator_index):
-        '''
-        This redirects the default SUT ports for a given emulator.
-        This is needed if more than one emulator is started.
-        '''
-        emulator = self.emulators[emulator_index]
-        emuport = emulator["emulator_port"]
-        sutport1 = emulator["sut_port1"]
-        sutport2 = emulator["sut_port2"]
-        attempts = 0
-        tn = None
-        redirect_completed = False
-        while attempts < 5:
-            if attempts == 0:
-                self.info("Sleeping 10 seconds")
-                time.sleep(10)
-            else:
-                self.info("Sleeping 30 seconds")
-                time.sleep(30)
-            attempts += 1
-            self.info("  Attempt #%d to redirect ports: (%d, %d, %d)" %
-                      (attempts, emuport, sutport1, sutport2))
-            try:
-                tn = telnetlib.Telnet('localhost', emuport, 300)
-                break
-            except socket.error, e:
-                self.info("Trying again after exception: %s" % str(e))
-                pass
-
-        if tn is not None:
-            res = tn.read_until('OK')
-            if res.find('OK') == -1:
-                self.warning('initial OK prompt not received from emulator: ' + str(res))
-            tn.write('redir add tcp:' + str(sutport1) + ':' + str(self.config["default_sut_port1"]) + '\n')
-            tn.write('redir add tcp:' + str(sutport2) + ':' + str(self.config["default_sut_port2"]) + '\n')
-            tn.write('quit\n')
-            res = tn.read_all()
-            if res.find('OK') == -1:
-                self.warning('error adding redirect: ' + str(res))
-            else:
-                redirect_completed = True
-        else:
-            self.warning('failed to establish a telnet connection with the emulator')
-        return redirect_completed
-
-    def _launch_emulator(self, emulator_index):
-        emulator = self.emulators[emulator_index]
-        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_emulators.
-        env['LD_LIBRARY_PATH'] = self.abs_dirs['abs_work_dir']
-
-        avd_home_dir = self.abs_dirs['abs_avds_dir']
-        env['ANDROID_AVD_HOME'] = os.path.join(avd_home_dir, 'avd')
-
-        command = [
-            "emulator", "-avd", emulator["name"],
-            "-port", str(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,
-            "tmp_stdout": tmp_stdout
-        }
-
-    def _check_emulator(self, emulator):
-        self.info('Checking emulator %s' % emulator["name"])
-
-        if self.config["device_manager"] == "sut":
-            attempts = 0
-            tn = None
-            contacted_sut = False
-            while attempts < 8 and not contacted_sut:
-                if attempts != 0:
-                    self.info("Sleeping 30 seconds")
-                    time.sleep(30)
-                attempts += 1
-                self.info("  Attempt #%d to connect to SUT on port %d" %
-                          (attempts, emulator["sut_port1"]))
-                try:
-                    tn = telnetlib.Telnet('localhost', emulator["sut_port1"], 10)
-                    if tn is not None:
-                        self.info('Connected to port %d' % emulator["sut_port1"])
-                        res = tn.read_until('$>', 10)
-                        tn.write('quit\n')
-                        if res.find('$>') == -1:
-                            self.warning('Unexpected SUT response: %s' % res)
-                        else:
-                            self.info('SUT response: %s' % res)
-                            contacted_sut = True
-                        tn.read_all()
-                    else:
-                        self.warning('Unable to connect to the SUT agent on port %d' % emulator["sut_port1"])
-                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()
-            if not contacted_sut:
-                self.warning('Unable to communicate with SUT agent on port %d' % emulator["sut_port1"])
-
-        attempts = 0
-        tn = None
-        contacted_emu = False
-        while attempts < 4:
-            if attempts != 0:
-                self.info("Sleeping 30 seconds")
-                time.sleep(30)
-            attempts += 1
-            self.info("  Attempt #%d to connect to emulator on port %d" %
-                      (attempts, emulator["emulator_port"]))
-            try:
-                tn = telnetlib.Telnet('localhost', emulator["emulator_port"], 10)
-                if tn is not None:
-                    self.info('Connected to port %d' % emulator["emulator_port"])
-                    res = tn.read_until('OK', 10)
-                    self.info(res)
-                    tn.write('avd status\n')
-                    res = tn.read_until('OK', 10)
-                    self.info('avd status: %s' % res)
-                    tn.write('redir list\n')
-                    res = tn.read_until('OK', 10)
-                    self.info('redir list: %s' % res)
-                    tn.write('network status\n')
-                    res = tn.read_until('OK', 10)
-                    self.info('network status: %s' % res)
-                    tn.write('quit\n')
-                    tn.read_all()
-                    tn.close()
-                    contacted_emu = True
-                    break
-                else:
-                    self.warning('Unable to connect to the emulator on port %d' % emulator["emulator_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()
-        if not contacted_emu:
-            self.warning('Unable to communicate with emulator on port %d' % emulator["emulator_port"])
-
-        ps_cmd = [self.adb_path, '-s', emulator["device_id"], 'shell', 'ps']
-        p = subprocess.Popen(ps_cmd, stdout=subprocess.PIPE)
-        out, err = p.communicate()
-        self.info('%s:\n%s\n%s' % (ps_cmd, out, err))
-
-    def _dump_host_state(self):
-        p = subprocess.Popen(['ps', '-ef'], stdout=subprocess.PIPE)
-        out, err = p.communicate()
-        self.info("output from ps -ef follows...")
-        if out:
-            self.info(out)
-        if err:
-            self.info(err)
-        p = subprocess.Popen(['netstat', '-a', '-p', '-n', '-t', '-u'], stdout=subprocess.PIPE)
-        out, err = p.communicate()
-        self.info("output from netstat -a -p -n -t -u follows...")
-        if out:
-            self.info(out)
-        if err:
-            self.info(err)
-
-    def _dump_emulator_log(self, emulator_index):
-        emulator = self.emulators[emulator_index]
-        self.info("##### %s emulator log begins" % emulator["name"])
-        output = self.read_from_file(self.emulator_procs[emulator_index]["tmp_file"].name, verbose=False)
-        if output:
-            self.info(output)
-        self.info("##### %s emulator log ends" % 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)
-
-    @PostScriptRun
-    def _post_script(self):
-        self._kill_processes(self.config["emulator_process_name"])
-
-    # XXX: This and android_panda.py's function might make sense to take higher up
-    def _download_robocop_apk(self):
-        dirs = self.query_abs_dirs()
-        self.apk_url = self.installer_url[:self.installer_url.rfind('/')]
-        robocop_url = self.apk_url + '/robocop.apk'
-        self.info("Downloading robocop...")
-        self.download_file(robocop_url, 'robocop.apk', dirs['abs_work_dir'], error_level=FATAL)
-
-    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, emulator, suite_name):
-        c = self.config
-        dirs = self.query_abs_dirs()
-        suite_category = self.test_suite_definitions[suite_name]["category"]
-
-        if suite_category not in c["suite_definitions"]:
-            self.fatal("Key '%s' not defined in the config!" % suite_category)
-
-        cmd = [
-            self.query_python_path('python'),
-            '-u',
-            os.path.join(
-                self._query_tests_dir(suite_name),
-                c["suite_definitions"][suite_category]["run_filename"]
-            ),
-        ]
-
-        raw_log_file = os.path.join(dirs['abs_blob_upload_dir'],
-                                    '%s_raw.log' % suite_name)
-        error_summary_file = os.path.join(dirs['abs_blob_upload_dir'],
-                                          '%s_errorsummary.log' % suite_name)
-        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': emulator['http_port'],
-            'ssl_port': 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'],
-        }
-        if self.config["device_manager"] == "sut":
-            str_format_values.update({
-                'device_ip': c['device_ip'],
-                'device_port': str(emulator['sut_port1']),
-            })
-        for option in c["suite_definitions"][suite_category]["options"]:
-            cmd.extend([option % str_format_values])
-
-        for arg in self.test_suite_definitions[suite_name]["extra_args"]:
-            argname = arg.split('=')[0]
-            # only add the extra arg if it wasn't already defined by in-tree configs
-            if any(a.split('=')[0] == argname for a in cmd):
-                continue
-            cmd.append(arg)
-
-        try_options, try_tests = self.try_args(suite_category)
-        cmd.extend(try_options)
-        cmd.extend(self.query_tests_args(
-            self.config["suite_definitions"][suite_category].get("tests"),
-            self.test_suite_definitions[suite_name].get("tests"),
-            try_tests))
-
-        return cmd
-
-    def preflight_run_tests(self):
-        super(AndroidEmulatorTest, self).preflight_run_tests()
-
-        if not os.path.isfile(self.adb_path):
-            self.fatal("The adb binary '%s' is not a valid file!" % self.adb_path)
-
-    def _trigger_test(self, suite_name, emulator_index):
-        """
-        Run a test suite on an emulator
-
-        We return a dictionary with the following information:
-         - subprocess object that is running the test on the emulator
-         - the filename where the stdout is going to
-         - the stdout where the output is going to
-         - the suite name that is associated
-        """
-        cmd = self._build_command(self.emulators[emulator_index], suite_name)
-
-        try:
-            cwd = self._query_tests_dir(suite_name)
-        except:
-            self.fatal("Don't know how to run --test-suite '%s'!" % suite_name)
-
-        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.emulators[emulator_index]["name"], subprocess.list2cmdline(cmd)))
-        tmp_file = tempfile.NamedTemporaryFile(mode='w')
-        tmp_stdout = open(tmp_file.name, 'w')
-        self.info("Created temp file %s." % tmp_file.name)
-        return {
-            "process": subprocess.Popen(cmd, cwd=cwd, stdout=tmp_stdout, stderr=tmp_stdout, env=env),
-            "tmp_file": tmp_file,
-            "tmp_stdout": tmp_stdout,
-            "suite_name": suite_name,
-            "emulator_index": emulator_index
-        }
-
-    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'])
-        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_emulators(self):
-        '''
-        This action starts the emulators and redirects the two SUT ports for each one of them
-        '''
-        assert len(self.test_suites) <= len(self.emulators), \
-            "We can't run more tests that the number of emulators we start"
-
-        if 'emulator_url' in self.config or 'emulator_manifest' in self.config or 'tools_manifest' in self.config:
-            self.install_emulator()
-
-        if not self.config.get("developer_mode"):
-            # We kill compiz because it sometimes prevents us from starting the emulators
-            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
-
-        attempts = 0
-        redirect_failed = True
-        # Launch the required emulators and redirect the SUT ports for each. If unable
-        # to redirect the SUT ports, kill the emulators and try starting them again.
-        # The wait-and-retry logic is necessary because the emulators intermittently fail
-        # to respond to telnet connections immediately after startup: bug 949740. In this
-        # case, the emulator log shows "ioctl(KVM_CREATE_VM) failed: Interrupted system call".
-        # We do not know how to avoid this error and the only way we have found to
-        # recover is to kill the emulator and start again.
-        while attempts < 3 and redirect_failed:
-            if attempts > 0:
-                self.info("Sleeping 30 seconds before retry")
-                time.sleep(30)
-            attempts += 1
-            self.info('Attempt #%d to launch emulators...' % attempts)
-            self._dump_host_state()
-            self.emulator_procs = []
-            emulator_index = 0
-            redirect_failed = False
-            for test in self.test_suites:
-                emulator_proc = self._launch_emulator(emulator_index)
-                self.emulator_procs.append(emulator_proc)
-                if self.config["device_manager"] == "sut":
-                    if self._redirectSUT(emulator_index):
-                        emulator = self.emulators[emulator_index]
-                        self.info("%s: %s; sut port: %s/%s" %
-                                  (emulator["name"], emulator["emulator_port"], emulator["sut_port1"], emulator["sut_port2"]))
-                        emulator_index += 1
-                    else:
-                        self._dump_emulator_log(emulator_index)
-                        self._kill_processes(self.config["emulator_process_name"])
-                        redirect_failed = True
-                        break
-        if redirect_failed:
-            self.fatal('We have not been able to establish a telnet connection with the emulator')
-
-        # Verify that we can communicate with each emulator
-        emulator_index = 0
-        for test in self.test_suites:
-            emulator = self.emulators[emulator_index]
-            emulator_index += 1
-            self._check_emulator(emulator)
-        # Start logcat for each emulator. Each 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.
-        self.mkdir_p(self.abs_dirs['abs_blob_upload_dir'])
-        emulator_index = 0
-        for test in self.test_suites:
-            emulator = self.emulators[emulator_index]
-            emulator_index += 1
-            logcat_filename = 'logcat-%s.log' % emulator["device_id"]
-            logcat_path = os.path.join(self.abs_dirs['abs_blob_upload_dir'], logcat_filename)
-            logcat_cmd = '%s -s %s logcat -v time Trace:S StrictMode:S ExchangeService:S > %s &' % \
-                (self.adb_path, emulator["device_id"], logcat_path)
-            self.info(logcat_cmd)
-            os.system(logcat_cmd)
-        # Create the /data/anr directory on each emulator image.
-        emulator_index = 0
-        for test in self.test_suites:
-            emulator = self.emulators[emulator_index]
-            emulator_index += 1
-            mkdir_cmd = [self.adb_path, '-s', emulator["device_id"], 'shell', 'mkdir', '/data/anr']
-            p = subprocess.Popen(mkdir_cmd, stdout=subprocess.PIPE)
-            out, err = p.communicate()
-            self.info('%s:\n%s\n%s' % (mkdir_cmd, out, err))
-
-    def download_and_extract(self):
-        # This will download and extract the fennec.apk and tests.zip
-        suite_categories = set([self.test_suite_definitions[c]['category']
-                                for c in self.test_suites])
-        super(AndroidEmulatorTest, self).download_and_extract(suite_categories=suite_categories)
-        dirs = self.query_abs_dirs()
-
-        for suite_name in self.test_suites:
-            if suite_name.startswith('robocop'):
-                self._download_robocop_apk()
-                break
-
-        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):
-        assert self.installer_path is not None, \
-            "Either add installer_path to the config or use --installer-path."
-
-        emulator_index = 0
-        for suite_name in self.test_suites:
-            emulator = self.emulators[emulator_index]
-            emulator_index += 1
-
-            config = {
-                'device-id': emulator["device_id"],
-                'enable_automation': True,
-                'device_package_name': self._query_package_name()
-            }
-            config = dict(config.items() + self.config.items())
-
-            # Wait for Android to finish booting
-            completed = None
-            retries = 0
-            while retries < 30:
-                completed = self.get_output_from_command([self.adb_path,
-                    "-s", emulator['device_id'], "shell",
-                    "getprop", "sys.boot_completed"])
-                if completed == '1':
-                    break
-                time.sleep(10)
-                retries = retries + 1
-            if completed != '1':
-                self.warning('Retries exhausted waiting for Android boot.')
-
-            cmd = [self.adb_path, '-s', emulator['device_id'], 'shell', 'getprop', 'ro.build.version.sdk']
-            p = subprocess.Popen(cmd, stdout=subprocess.PIPE)
-            out, err = p.communicate()
-            sdk_level = out
-
-            # Install Fennec
-            self.info("Installing Fennec for %s" % emulator["name"])
-            if int(sdk_level) >= 23:
-                cmd = [self.adb_path, '-s', emulator['device_id'], 'install', '-r', '-g', self.installer_path]
-            else:
-                cmd = [self.adb_path, '-s', emulator['device_id'], 'install', '-r', self.installer_path]
-            p = subprocess.Popen(cmd, stdout=subprocess.PIPE)
-            out, err = p.communicate()
-
-            # Install the robocop apk if required
-            if suite_name.startswith('robocop'):
-                self.info("Installing Robocop for %s" % emulator["name"])
-                if int(sdk_level) >= 23:
-                    cmd = [self.adb_path, '-s', emulator['device_id'], 'install', '-r', '-g', self.robocop_path]
-                else:
-                    cmd = [self.adb_path, '-s', emulator['device_id'], 'install', '-r', self.robocop_path]
-                p = subprocess.Popen(cmd, stdout=subprocess.PIPE)
-                out, err = p.communicate()
-
-            self.info("Finished installing apps for %s" % emulator["name"])
-
-    def run_tests(self):
-        """
-        Run the tests
-        """
-        procs = []
-
-        emulator_index = 0
-        for suite_name in self.test_suites:
-            procs.append(self._trigger_test(suite_name, emulator_index))
-            emulator_index += 1
-
-        joint_tbpl_status = None
-        joint_log_level = None
-        start_time = int(time.time())
-        while True:
-            for p in procs:
-                emulator_index = p["emulator_index"]
-                return_code = p["process"].poll()
-                if return_code is not None:
-                    suite_name = p["suite_name"]
-                    # To make reading the log of the suite not mix with the previous line
-                    sys.stdout.write('\n')
-                    self.info("##### %s log begins" % p["suite_name"])
-                    # Let's close the stdout
-                    p["tmp_stdout"].close()
-                    # Let's read the file that now has the output
-                    output = self.read_from_file(p["tmp_file"].name, verbose=False)
-                    # Let's parse the output (which also prints it)
-                    # and determine what the results should be
-                    parser = self.get_test_output_parser(
-                        self.test_suite_definitions[p["suite_name"]]["category"],
-                        config=self.config,
-                        log_obj=self.log_obj,
-                        error_list=self.error_list)
-                    for line in output.splitlines():
-                        parser.parse_single_line(line)
-
-                    # After parsing each line we should know what the summary for this suite should be
-                    tbpl_status, log_level = parser.evaluate_parser(0)
-                    parser.append_tinderboxprint_line(p["suite_name"])
-                    # After running all jobs we will report the worst status of all emulator runs
-                    joint_tbpl_status = self.worst_level(tbpl_status, joint_tbpl_status, TBPL_WORST_LEVEL_TUPLE)
-                    joint_log_level = self.worst_level(log_level, joint_log_level)
-
-                    self.info("##### %s log ends" % p["suite_name"])
-                    self._dump_emulator_log(emulator_index)
-                    procs.remove(p)
-            if procs == []:
-                break
-            else:
-                # Every 5 minutes let's print something to stdout
-                # so buildbot won't kill the process due to lack of output
-                if int(time.time()) - start_time > 5 * 60:
-                    self.info('#')
-                    start_time = int(time.time())
-                time.sleep(30)
-
-        self.buildbot_status(joint_tbpl_status, level=joint_log_level)
-
-    @PostScriptAction('run-tests')
-    def stop_emulators(self, action, success=None):
-        '''
-        Report emulator health, then make sure that every emulator has been stopped
-        '''
-        emulator_index = 0
-        for test in self.test_suites:
-            emulator = self.emulators[emulator_index]
-            emulator_index += 1
-            self._check_emulator(emulator)
-        self._kill_processes(self.config["emulator_process_name"])
-
-if __name__ == '__main__':
-    emulatorTest = AndroidEmulatorTest()
-    emulatorTest.run_and_exit()