summaryrefslogtreecommitdiffstats
path: root/testing/mozbase/mozrunner/mozrunner/devices/emulator.py
diff options
context:
space:
mode:
Diffstat (limited to 'testing/mozbase/mozrunner/mozrunner/devices/emulator.py')
-rw-r--r--testing/mozbase/mozrunner/mozrunner/devices/emulator.py224
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()