зеркало из https://github.com/mozilla/gecko-dev.git
Bug 753605 - Add emulator classes to build/mobile, r=jmaher, DONTBUILD because NPOTB
This commit is contained in:
Родитель
c87728bbb9
Коммит
d8d14d6204
|
@ -0,0 +1,80 @@
|
|||
# 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 os
|
||||
import platform
|
||||
|
||||
from emulator import Emulator
|
||||
|
||||
|
||||
class B2GEmulator(Emulator):
|
||||
|
||||
def __init__(self, homedir=None, noWindow=False, logcat_dir=None, arch="x86",
|
||||
emulatorBinary=None, res='480x800', userdata=None,
|
||||
memory='512', partition_size='512'):
|
||||
super(B2GEmulator, self).__init__(noWindow=noWindow, logcat_dir=logcat_dir,
|
||||
arch=arch, emulatorBinary=emulatorBinary,
|
||||
res=res, userdata=userdata,
|
||||
memory=memory, partition_size=partition_size)
|
||||
self.homedir = homedir
|
||||
if self.homedir is not None:
|
||||
self.homedir = os.path.expanduser(homedir)
|
||||
|
||||
def _check_file(self, filePath):
|
||||
if not os.path.exists(filePath):
|
||||
raise Exception(('File not found: %s; did you pass the B2G home '
|
||||
'directory as the homedir parameter, or set '
|
||||
'B2G_HOME correctly?') % filePath)
|
||||
|
||||
def _check_for_adb(self, host_dir):
|
||||
self.adb = os.path.join(self.homedir, 'out', 'host', host_dir, 'bin', 'adb')
|
||||
if not os.path.exists(self.adb):
|
||||
self.adb = os.path.join(self.homedir, 'bin/adb')
|
||||
super(B2GEmulator, self)._check_for_adb()
|
||||
|
||||
def _locate_files(self):
|
||||
if self.homedir is None:
|
||||
self.homedir = os.getenv('B2G_HOME')
|
||||
if self.homedir is None:
|
||||
raise Exception('Must define B2G_HOME or pass the homedir parameter')
|
||||
self._check_file(self.homedir)
|
||||
|
||||
if self.arch not in ("x86", "arm"):
|
||||
raise Exception("Emulator architecture must be one of x86, arm, got: %s" %
|
||||
self.arch)
|
||||
|
||||
host_dir = "linux-x86"
|
||||
if platform.system() == "Darwin":
|
||||
host_dir = "darwin-x86"
|
||||
|
||||
host_bin_dir = os.path.join("out", "host", host_dir, "bin")
|
||||
|
||||
if self.arch == "x86":
|
||||
binary = os.path.join(host_bin_dir, "emulator-x86")
|
||||
kernel = "prebuilts/qemu-kernel/x86/kernel-qemu"
|
||||
sysdir = "out/target/product/generic_x86"
|
||||
self.tail_args = []
|
||||
else:
|
||||
binary = os.path.join(host_bin_dir, "emulator")
|
||||
kernel = "prebuilts/qemu-kernel/arm/kernel-qemu-armv7"
|
||||
sysdir = "out/target/product/generic"
|
||||
self.tail_args = ["-cpu", "cortex-a8"]
|
||||
|
||||
self._check_for_adb(host_dir)
|
||||
|
||||
if not self.binary:
|
||||
self.binary = os.path.join(self.homedir, binary)
|
||||
|
||||
self._check_file(self.binary)
|
||||
|
||||
self.kernelImg = os.path.join(self.homedir, kernel)
|
||||
self._check_file(self.kernelImg)
|
||||
|
||||
self.sysDir = os.path.join(self.homedir, sysdir)
|
||||
self._check_file(self.sysDir)
|
||||
|
||||
if not self.dataImg:
|
||||
self.dataImg = os.path.join(self.sysDir, 'userdata.img')
|
||||
self._check_file(self.dataImg)
|
|
@ -0,0 +1,295 @@
|
|||
# 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/.
|
||||
|
||||
from abc import abstractmethod
|
||||
import datetime
|
||||
from mozprocess import ProcessHandlerMixin
|
||||
import multiprocessing
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import socket
|
||||
import subprocess
|
||||
from telnetlib import Telnet
|
||||
import tempfile
|
||||
import time
|
||||
|
||||
from emulator_battery import EmulatorBattery
|
||||
|
||||
|
||||
class LogcatProc(ProcessHandlerMixin):
|
||||
"""Process handler for logcat which saves all output to a logfile.
|
||||
"""
|
||||
|
||||
def __init__(self, logfile, cmd, **kwargs):
|
||||
self.logfile = logfile
|
||||
kwargs.setdefault('processOutputLine', []).append(self.log_output)
|
||||
ProcessHandlerMixin.__init__(self, cmd, **kwargs)
|
||||
|
||||
def log_output(self, line):
|
||||
f = open(self.logfile, 'a')
|
||||
f.write(line + "\n")
|
||||
f.flush()
|
||||
|
||||
|
||||
class Emulator(object):
|
||||
|
||||
deviceRe = re.compile(r"^emulator-(\d+)(\s*)(.*)$")
|
||||
|
||||
def __init__(self, noWindow=False, logcat_dir=None, arch="x86",
|
||||
emulatorBinary=None, res='480x800', userdata=None,
|
||||
memory='512', partition_size='512'):
|
||||
self.port = None
|
||||
self._emulator_launched = False
|
||||
self.proc = None
|
||||
self.local_port = None
|
||||
self.telnet = None
|
||||
self._tmp_userdata = None
|
||||
self._adb_started = False
|
||||
self.logcat_dir = logcat_dir
|
||||
self.logcat_proc = None
|
||||
self.arch = arch
|
||||
self.binary = emulatorBinary
|
||||
self.memory = str(memory)
|
||||
self.partition_size = str(partition_size)
|
||||
self.res = res
|
||||
self.battery = EmulatorBattery(self)
|
||||
self.noWindow = noWindow
|
||||
self.dataImg = userdata
|
||||
self.copy_userdata = self.dataImg is None
|
||||
|
||||
def __del__(self):
|
||||
if self.telnet:
|
||||
self.telnet.write('exit\n')
|
||||
self.telnet.read_all()
|
||||
|
||||
@property
|
||||
def args(self):
|
||||
qemuArgs = [self.binary,
|
||||
'-kernel', self.kernelImg,
|
||||
'-sysdir', self.sysDir,
|
||||
'-data', self.dataImg]
|
||||
if self.noWindow:
|
||||
qemuArgs.append('-no-window')
|
||||
qemuArgs.extend(['-memory', self.memory,
|
||||
'-partition-size', self.partition_size,
|
||||
'-verbose',
|
||||
'-skin', self.res,
|
||||
'-gpu', 'on',
|
||||
'-qemu'] + self.tail_args)
|
||||
return qemuArgs
|
||||
|
||||
@property
|
||||
def is_running(self):
|
||||
if self._emulator_launched:
|
||||
return self.proc is not None and self.proc.poll() is None
|
||||
else:
|
||||
return self.port is not None
|
||||
|
||||
def _check_for_adb(self):
|
||||
if not os.path.exists(self.adb):
|
||||
adb = subprocess.Popen(['which', 'adb'],
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.STDOUT)
|
||||
retcode = adb.wait()
|
||||
if retcode:
|
||||
raise Exception('adb not found!')
|
||||
out = adb.stdout.read().strip()
|
||||
if len(out) and out.find('/') > -1:
|
||||
self.adb = out
|
||||
|
||||
def _run_adb(self, args):
|
||||
args.insert(0, self.adb)
|
||||
adb = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
|
||||
retcode = adb.wait()
|
||||
if retcode:
|
||||
raise Exception('adb terminated with exit code %d: %s'
|
||||
% (retcode, adb.stdout.read()))
|
||||
return adb.stdout.read()
|
||||
|
||||
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 close(self):
|
||||
if self.is_running and self._emulator_launched:
|
||||
self.proc.terminate()
|
||||
self.proc.wait()
|
||||
if self._adb_started:
|
||||
self._run_adb(['kill-server'])
|
||||
self._adb_started = False
|
||||
if self.proc:
|
||||
retcode = self.proc.poll()
|
||||
self.proc = None
|
||||
if self._tmp_userdata:
|
||||
os.remove(self._tmp_userdata)
|
||||
self._tmp_userdata = None
|
||||
return retcode
|
||||
if self.logcat_proc:
|
||||
self.logcat_proc.kill()
|
||||
return 0
|
||||
|
||||
def _get_adb_devices(self):
|
||||
offline = set()
|
||||
online = set()
|
||||
output = self._run_adb(['devices'])
|
||||
for line in output.split('\n'):
|
||||
m = self.deviceRe.match(line)
|
||||
if m:
|
||||
if m.group(3) == 'offline':
|
||||
offline.add(m.group(1))
|
||||
else:
|
||||
online.add(m.group(1))
|
||||
return (online, offline)
|
||||
|
||||
def restart(self):
|
||||
if not self._emulator_launched:
|
||||
return
|
||||
self.close()
|
||||
self.start()
|
||||
|
||||
def start_adb(self):
|
||||
result = self._run_adb(['start-server'])
|
||||
# We keep track of whether we've started adb or not, so we know
|
||||
# if we need to kill it.
|
||||
if 'daemon started successfully' in result:
|
||||
self._adb_started = True
|
||||
else:
|
||||
self._adb_started = False
|
||||
|
||||
def connect(self):
|
||||
self._check_for_adb()
|
||||
self.start_adb()
|
||||
|
||||
online, offline = self._get_adb_devices()
|
||||
now = datetime.datetime.now()
|
||||
while online == set([]):
|
||||
time.sleep(1)
|
||||
if datetime.datetime.now() - now > datetime.timedelta(seconds=60):
|
||||
raise Exception('timed out waiting for emulator to be available')
|
||||
online, offline = self._get_adb_devices()
|
||||
self.port = int(list(online)[0])
|
||||
|
||||
@abstractmethod
|
||||
def _locate_files(self):
|
||||
pass
|
||||
|
||||
def start(self):
|
||||
self._locate_files()
|
||||
self.start_adb()
|
||||
|
||||
qemu_args = self.args[:]
|
||||
if self.copy_userdata:
|
||||
# Make a copy of the userdata.img for this instance of the emulator
|
||||
# to use.
|
||||
self._tmp_userdata = tempfile.mktemp(prefix='emulator')
|
||||
shutil.copyfile(self.dataImg, self._tmp_userdata)
|
||||
qemu_args[qemu_args.index('-data') + 1] = self._tmp_userdata
|
||||
|
||||
original_online, original_offline = self._get_adb_devices()
|
||||
|
||||
self.proc = subprocess.Popen(qemu_args,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE)
|
||||
|
||||
online, offline = self._get_adb_devices()
|
||||
now = datetime.datetime.now()
|
||||
while online - original_online == set([]):
|
||||
time.sleep(1)
|
||||
if datetime.datetime.now() - now > datetime.timedelta(seconds=60):
|
||||
raise Exception('timed out waiting for emulator to start')
|
||||
online, offline = self._get_adb_devices()
|
||||
self.port = int(list(online - original_online)[0])
|
||||
self._emulator_launched = True
|
||||
|
||||
if self.logcat_dir:
|
||||
self.save_logcat()
|
||||
|
||||
# setup DNS fix for networking
|
||||
self._run_adb(['-s', 'emulator-%d' % self.port,
|
||||
'shell', 'setprop', 'net.dns1', '10.0.2.3'])
|
||||
|
||||
def _save_logcat_proc(self, filename, cmd):
|
||||
self.logcat_proc = LogcatProc(filename, cmd)
|
||||
self.logcat_proc.run()
|
||||
self.logcat_proc.waitForFinish()
|
||||
self.logcat_proc = None
|
||||
|
||||
def rotate_log(self, srclog, index=1):
|
||||
""" Rotate a logfile, by recursively rotating logs further in the sequence,
|
||||
deleting the last file if necessary.
|
||||
"""
|
||||
destlog = os.path.join(self.logcat_dir, 'emulator-%d.%d.log' % (self.port, index))
|
||||
if os.path.exists(destlog):
|
||||
if index == 3:
|
||||
os.remove(destlog)
|
||||
else:
|
||||
self.rotate_log(destlog, index + 1)
|
||||
shutil.move(srclog, destlog)
|
||||
|
||||
def save_logcat(self):
|
||||
""" Save the output of logcat to a file.
|
||||
"""
|
||||
filename = os.path.join(self.logcat_dir, "emulator-%d.log" % self.port)
|
||||
if os.path.exists(filename):
|
||||
self.rotate_log(filename)
|
||||
cmd = [self.adb, '-s', 'emulator-%d' % self.port, 'logcat']
|
||||
|
||||
# We do this in a separate process because we call mozprocess's
|
||||
# waitForFinish method to process logcat's output, and this method
|
||||
# blocks.
|
||||
proc = multiprocessing.Process(target=self._save_logcat_proc, args=(filename, cmd))
|
||||
proc.daemon = True
|
||||
proc.start()
|
||||
|
||||
def setup_port_forwarding(self, remote_port):
|
||||
""" Set up TCP port forwarding to the specified port on the device,
|
||||
using any availble local port, and return the local port.
|
||||
"""
|
||||
|
||||
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
s.bind(("", 0))
|
||||
local_port = s.getsockname()[1]
|
||||
s.close()
|
||||
|
||||
output = self._run_adb(['-s', 'emulator-%d' % self.port,
|
||||
'forward',
|
||||
'tcp:%d' % local_port,
|
||||
'tcp:%d' % remote_port])
|
||||
|
||||
self.local_port = local_port
|
||||
|
||||
return local_port
|
||||
|
||||
def wait_for_port(self, timeout=300):
|
||||
assert(self.local_port)
|
||||
starttime = datetime.datetime.now()
|
||||
while datetime.datetime.now() - starttime < datetime.timedelta(seconds=timeout):
|
||||
try:
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
sock.connect(('localhost', self.local_port))
|
||||
data = sock.recv(16)
|
||||
sock.close()
|
||||
if '"from"' in data:
|
||||
return True
|
||||
except:
|
||||
import traceback
|
||||
print traceback.format_exc()
|
||||
time.sleep(1)
|
||||
return False
|
|
@ -0,0 +1,52 @@
|
|||
# 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/.
|
||||
|
||||
class EmulatorBattery(object):
|
||||
|
||||
def __init__(self, emulator):
|
||||
self.emulator = emulator
|
||||
|
||||
def get_state(self):
|
||||
status = {}
|
||||
state = {}
|
||||
|
||||
response = self.emulator._run_telnet('power display')
|
||||
for line in response:
|
||||
if ':' in line:
|
||||
field, value = line.split(':')
|
||||
value = value.strip()
|
||||
if value == 'true':
|
||||
value = True
|
||||
elif value == 'false':
|
||||
value = False
|
||||
elif field == 'capacity':
|
||||
value = float(value)
|
||||
status[field] = value
|
||||
|
||||
state['level'] = status.get('capacity', 0.0) / 100
|
||||
if status.get('AC') == 'online':
|
||||
state['charging'] = True
|
||||
else:
|
||||
state['charging'] = False
|
||||
|
||||
return state
|
||||
|
||||
def get_charging(self):
|
||||
return self.get_state()['charging']
|
||||
|
||||
def get_level(self):
|
||||
return self.get_state()['level']
|
||||
|
||||
def set_level(self, level):
|
||||
self.emulator._run_telnet('power capacity %d' % (level * 100))
|
||||
|
||||
def set_charging(self, charging):
|
||||
if charging:
|
||||
cmd = 'power ac on'
|
||||
else:
|
||||
cmd = 'power ac off'
|
||||
self.emulator._run_telnet(cmd)
|
||||
|
||||
charging = property(get_charging, set_charging)
|
||||
level = property(get_level, set_level)
|
Загрузка…
Ссылка в новой задаче