diff options
Diffstat (limited to 'testing/mozbase/mozrunner/mozrunner/devices/emulator.py')
-rw-r--r-- | testing/mozbase/mozrunner/mozrunner/devices/emulator.py | 224 |
1 files changed, 224 insertions, 0 deletions
diff --git a/testing/mozbase/mozrunner/mozrunner/devices/emulator.py b/testing/mozbase/mozrunner/mozrunner/devices/emulator.py new file mode 100644 index 0000000000..4a2aa81733 --- /dev/null +++ b/testing/mozbase/mozrunner/mozrunner/devices/emulator.py @@ -0,0 +1,224 @@ +# 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 datetime +import os +import shutil +import subprocess +import tempfile +import time +from telnetlib import Telnet + +from mozdevice import ADBHost +from mozprocess import ProcessHandler + +from ..errors import TimeoutException +from .base import Device +from .emulator_battery import EmulatorBattery +from .emulator_geo import EmulatorGeo +from .emulator_screen import EmulatorScreen + + +class ArchContext(object): + def __init__(self, arch, context, binary=None, avd=None, extra_args=None): + homedir = getattr(context, "homedir", "") + kernel = os.path.join(homedir, "prebuilts", "qemu-kernel", "%s", "%s") + sysdir = os.path.join(homedir, "out", "target", "product", "%s") + self.extra_args = [] + self.binary = os.path.join(context.bindir or "", "emulator") + if arch == "x86": + self.binary = os.path.join(context.bindir or "", "emulator-x86") + self.kernel = kernel % ("x86", "kernel-qemu") + self.sysdir = sysdir % "generic_x86" + elif avd: + self.avd = avd + self.extra_args = [ + "-show-kernel", + "-debug", + "init,console,gles,memcheck,adbserver,adbclient,adb,avd_config,socket", + ] + else: + self.kernel = kernel % ("arm", "kernel-qemu-armv7") + self.sysdir = sysdir % "generic" + self.extra_args = ["-cpu", "cortex-a8"] + + if binary: + self.binary = binary + + if extra_args: + self.extra_args.extend(extra_args) + + +class SDCard(object): + def __init__(self, emulator, size): + self.emulator = emulator + self.path = self.create_sdcard(size) + + def create_sdcard(self, sdcard_size): + """ + Creates an sdcard partition in the emulator. + + :param sdcard_size: Size of partition to create, e.g '10MB'. + """ + mksdcard = self.emulator.app_ctx.which("mksdcard") + path = tempfile.mktemp(prefix="sdcard", dir=self.emulator.tmpdir) + sdargs = [mksdcard, "-l", "mySdCard", sdcard_size, path] + sd = subprocess.Popen(sdargs, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + retcode = sd.wait() + if retcode: + raise Exception( + "unable to create sdcard: exit code %d: %s" + % (retcode, sd.stdout.read()) + ) + return path + + +class BaseEmulator(Device): + port = None + proc = None + telnet = None + + def __init__(self, app_ctx, **kwargs): + self.arch = ArchContext( + kwargs.pop("arch", "arm"), + app_ctx, + binary=kwargs.pop("binary", None), + avd=kwargs.pop("avd", None), + ) + super(BaseEmulator, self).__init__(app_ctx, **kwargs) + self.tmpdir = tempfile.mkdtemp() + # These rely on telnet + self.battery = EmulatorBattery(self) + self.geo = EmulatorGeo(self) + self.screen = EmulatorScreen(self) + + @property + def args(self): + """ + Arguments to pass into the emulator binary. + """ + return [self.arch.binary] + + def start(self): + """ + Starts a new emulator. + """ + if self.proc: + return + + original_devices = set(self._get_online_devices()) + + # QEMU relies on atexit() to remove temporary files, which does not + # work since mozprocess uses SIGKILL to kill the emulator process. + # Use a customized temporary directory so we can clean it up. + os.environ["ANDROID_TMP"] = self.tmpdir + + qemu_log = None + qemu_proc_args = {} + if self.logdir: + # save output from qemu to logfile + qemu_log = os.path.join(self.logdir, "qemu.log") + if os.path.isfile(qemu_log): + self._rotate_log(qemu_log) + qemu_proc_args["logfile"] = qemu_log + else: + qemu_proc_args["processOutputLine"] = lambda line: None + self.proc = ProcessHandler(self.args, **qemu_proc_args) + self.proc.run() + + devices = set(self._get_online_devices()) + now = datetime.datetime.now() + while (devices - original_devices) == set([]): + time.sleep(1) + # Sometimes it takes more than 60s to launch emulator, so we + # increase timeout value to 180s. Please see bug 1143380. + if datetime.datetime.now() - now > datetime.timedelta(seconds=180): + raise TimeoutException("timed out waiting for emulator to start") + devices = set(self._get_online_devices()) + devices = devices - original_devices + self.serial = devices.pop() + self.connect() + + def _get_online_devices(self): + adbhost = ADBHost(adb=self.app_ctx.adb) + return [ + d["device_serial"] + for d in adbhost.devices() + if d["state"] != "offline" + if d["device_serial"].startswith("emulator") + ] + + def connect(self): + """ + Connects to a running device. If no serial was specified in the + constructor, defaults to the first entry in `adb devices`. + """ + if self.connected: + return + + super(BaseEmulator, self).connect() + self.port = int(self.serial[self.serial.rindex("-") + 1 :]) + + def cleanup(self): + """ + Cleans up and kills the emulator, if it was started by mozrunner. + """ + super(BaseEmulator, self).cleanup() + if self.proc: + self.proc.kill() + self.proc = None + self.connected = False + + # Remove temporary files + if os.path.isdir(self.tmpdir): + shutil.rmtree(self.tmpdir) + + def _get_telnet_response(self, command=None): + output = [] + assert self.telnet + if command is not None: + self.telnet.write("%s\n" % command) + while True: + line = self.telnet.read_until("\n") + output.append(line.rstrip()) + if line.startswith("OK"): + return output + elif line.startswith("KO:"): + raise Exception("bad telnet response: %s" % line) + + def _run_telnet(self, command): + if not self.telnet: + self.telnet = Telnet("localhost", self.port) + self._get_telnet_response() + return self._get_telnet_response(command) + + def __del__(self): + if self.telnet: + self.telnet.write("exit\n") + self.telnet.read_all() + + +class EmulatorAVD(BaseEmulator): + def __init__(self, app_ctx, binary, avd, port=5554, **kwargs): + super(EmulatorAVD, self).__init__(app_ctx, binary=binary, avd=avd, **kwargs) + self.port = port + + @property + def args(self): + """ + Arguments to pass into the emulator binary. + """ + qemu_args = super(EmulatorAVD, self).args + qemu_args.extend(["-avd", self.arch.avd, "-port", str(self.port)]) + qemu_args.extend(self.arch.extra_args) + return qemu_args + + def start(self): + if self.proc: + return + + env = os.environ + env["ANDROID_AVD_HOME"] = self.app_ctx.avd_home + + super(EmulatorAVD, self).start() |