Bug 759885 - Add support for running mochitest-plain on B2G emulators, r=jmaher, DONTBUILD because NPOTB

This commit is contained in:
Jonathan Griffin 2012-06-05 15:17:26 -07:00
parent 97a0230af4
commit 36e0617209
2 changed files with 116 additions and 47 deletions

View File

@ -3,7 +3,9 @@
# You can obtain one at http://mozilla.org/MPL/2.0/.
import automationutils
import threading
import os
import Queue
import re
import socket
import shutil
@ -13,6 +15,21 @@ import time
from automation import Automation
from devicemanager import DeviceManager, NetworkTools
from mozprocess import ProcessHandlerMixin
class LogcatProc(ProcessHandlerMixin):
"""Process handler for logcat which puts all output in a Queue.
"""
def __init__(self, cmd, queue, **kwargs):
self.queue = queue
kwargs.setdefault('processOutputLine', []).append(self.handle_output)
ProcessHandlerMixin.__init__(self, cmd, **kwargs)
def handle_output(self, line):
self.queue.put_nowait(line)
class B2GRemoteAutomation(Automation):
_devicemanager = None
@ -24,11 +41,15 @@ class B2GRemoteAutomation(Automation):
self._remoteProfile = None
self._remoteLog = remoteLog
self.marionette = marionette
self._is_emulator = False
# Default our product to b2g
self._product = "b2g"
Automation.__init__(self)
def setEmulator(self, is_emulator):
self._is_emulator = is_emulator
def setDeviceManager(self, deviceManager):
self._devicemanager = deviceManager
@ -51,6 +72,8 @@ class B2GRemoteAutomation(Automation):
if env is None:
env = {}
# We always hide the results table in B2G; it's much slower if we don't.
env['MOZ_HIDE_RESULTS_TABLE'] = '1'
return env
def checkForCrashes(self, directory, symbolsPath):
@ -119,6 +142,14 @@ class B2GRemoteAutomation(Automation):
return (serial, status)
def restartB2G(self):
self._devicemanager.checkCmd(['shell', 'stop', 'b2g'])
# Wait for a bit to make sure B2G has completely shut down.
time.sleep(5)
self._devicemanager.checkCmd(['shell', 'start', 'b2g'])
if self._is_emulator:
self.marionette.emulator.wait_for_port()
def rebootDevice(self):
# find device's current status and serial number
serial, status = self.getDeviceStatus()
@ -153,7 +184,10 @@ class B2GRemoteAutomation(Automation):
# XXX: We could potentially use 'stop b2g' + 'start b2g' to achieve
# a similar effect; will see which is more stable while attempting
# to bring up the continuous integration.
self.rebootDevice()
if self._is_emulator:
self.restartB2G()
else:
self.rebootDevice()
# Infrequently, gecko comes up before networking does, so wait a little
# bit to give the network time to become available.
@ -162,9 +196,10 @@ class B2GRemoteAutomation(Automation):
# Set up port forwarding again for Marionette, since any that
# existed previously got wiped out by the reboot.
self._devicemanager.checkCmd(['forward',
'tcp:%s' % self.marionette.port,
'tcp:%s' % self.marionette.port])
if not self._is_emulator:
self._devicemanager.checkCmd(['forward',
'tcp:%s' % self.marionette.port,
'tcp:%s' % self.marionette.port])
# start a marionette session
session = self.marionette.start_session()
@ -185,7 +220,26 @@ class B2GRemoteAutomation(Automation):
def __init__(self, dm):
self.dm = dm
self.lastloglines = []
self.logcat_proc = None
self.queue = Queue.Queue()
# Launch logcat in a separate thread, and dump all output lines
# into a queue. The lines in this queue are
# retrieved and returned by accessing the stdout property of
# this class.
cmd = [self.dm.adbPath]
if self.dm.deviceSerial:
cmd.extend(['-s', self.dm.deviceSerial])
cmd.append('logcat')
proc = threading.Thread(target=self._save_logcat_proc, args=(cmd, self.queue))
proc.daemon = True
proc.start()
def _save_logcat_proc(self, cmd, queue):
self.logcat_proc = LogcatProc(cmd, queue)
self.logcat_proc.run()
self.logcat_proc.waitForFinish()
self.logcat_proc = None
@property
def pid(self):
@ -194,33 +248,15 @@ class B2GRemoteAutomation(Automation):
@property
def stdout(self):
# Return any part of logcat output that wasn't returned
# previously. This is done by fetching about the last 50
# lines of the log (logcat -t 50 generally fetches 50-58 lines),
# and comparing against the previous set to find new lines.
t = self.dm.runCmd(['logcat', '-t', '50']).stdout.read()
if t == None: return ''
t = t.strip('\n').strip()
loglines = t.split('\n')
line_index = 0
# If there are more than 50 lines, we skip the first 20; this
# is because the number of lines returned
# by logcat -t 50 varies (usually between 50 and 58).
log_index = 20 if len(loglines) > 50 else 0
for index, line in enumerate(loglines[log_index:]):
line_index = index + log_index + 1
# Return any lines in the queue used by the
# logcat process handler.
lines = []
while True:
try:
self.lastloglines.index(line)
except ValueError:
lines.append(self.queue.get_nowait())
except Queue.Empty:
break
newoutput = '\n'.join(loglines[line_index:])
self.lastloglines = loglines
return newoutput
return '\n'.join(lines)
def wait(self, timeout = None):
# this should never happen

View File

@ -40,10 +40,10 @@ class B2GOptions(MochitestOptions):
help = "host:port to use when connecting to Marionette")
defaults["marionette"] = None
self.add_option("--emulator", action="store_true",
dest = "emulator",
help = "True if using a b2g emulator")
defaults["emulator"] = False
self.add_option("--emulator", action="store",
type="string", dest = "emulator",
help = "Architecture of emulator to use: x86 or arm")
defaults["emulator"] = None
self.add_option("--adbpath", action="store",
type = "string", dest = "adbPath",
@ -186,21 +186,38 @@ class B2GMochitest(Mochitest):
self.remoteProfile = options.remoteTestRoot + '/profile'
self._automation.setRemoteProfile(self.remoteProfile)
self.remoteLog = options.remoteLogFile
self.remoteProfilesIniPath = '/data/b2g/mozilla/profiles.ini'
self.userJS = '/data/local/user.js'
self.testDir = '/data/local/tests'
self.remoteMozillaPath = '/data/b2g/mozilla'
self.remoteProfilesIniPath = os.path.join(self.remoteMozillaPath, 'profiles.ini')
self.originalProfilesIni = None
def cleanup(self, manifest, options):
self._dm.getFile(self.remoteLog, self.localLog)
self._dm.removeFile(self.remoteLog)
self._dm.removeDir(self.remoteProfile)
# Restore the original profiles.ini.
if self.originalProfilesIni:
try:
self.restoreProfilesIni()
if not options.emulator:
self.restoreProfilesIni()
os.remove(self.originalProfilesIni)
except:
pass
if not options.emulator:
self._dm.getFile(self.remoteLog, self.localLog)
self._dm.removeFile(self.remoteLog)
self._dm.removeDir(self.remoteProfile)
# Restore the original user.js.
self._dm.checkCmdAs(['shell', 'rm', '-f', self.userJS])
if self._dm.useDDCopy:
self._dm.checkCmdAs(['shell', 'dd', 'if=%s.orig' % self.userJS, 'of=%s' % self.userJS])
else:
self._dm.checkCmdAs(['shell', 'cp', '%s.orig' % self.userJS, self.userJS])
# We've restored the original profile, so reboot the device so that
# it gets picked up.
self._automation.rebootDevice()
if options.pidFile != "":
try:
os.remove(options.pidFile)
@ -208,10 +225,6 @@ class B2GMochitest(Mochitest):
except:
print "Warning: cleaning up pidfile '%s' was unsuccessful from the test harness" % options.pidFile
# We've restored the original profile, so reboot the device so that
# it gets picked up.
self._automation.rebootDevice()
def findPath(self, paths, filename = None):
for path in paths:
p = path
@ -283,7 +296,6 @@ class B2GMochitest(Mochitest):
def buildProfile(self, options):
if self.localProfile:
options.profilePath = self.localProfile
print 'buildProfile', repr(options)
manifest = Mochitest.buildProfile(self, options)
self.localProfile = options.profilePath
@ -318,6 +330,10 @@ class B2GMochitest(Mochitest):
config.write(configfile)
self._dm.pushFile(newProfilesIni, self.remoteProfilesIniPath)
try:
os.remove(newProfilesIni)
except:
pass
def buildURLOptions(self, options, env):
self.localLog = options.logFile
@ -334,13 +350,23 @@ class B2GMochitest(Mochitest):
# Set the B2G homepage as a static local page, since wi-fi generally
# isn't available as soon as the device boots.
f = open(os.path.join(options.profilePath, "user.js"), "a")
f.write('user_pref("browser.homescreenURL", "data:text/html,mochitest-plain should start soon");\n')
f.write('user_pref("browser.homescreenURL", "data:text/html,<h1>mochitest-plain should start soon</h1>");\n')
f.close()
# Copy the profile to the device.
self._dm.removeDir(self.remoteProfile)
if self._dm.pushDir(options.profilePath, self.remoteProfile) == None:
raise devicemanager.FileError("Unable to copy profile to device.")
# In B2G, user.js is always read from /data/local, not the profile
# directory. Backup the original user.js first so we can restore it.
self._dm.checkCmdAs(['shell', 'rm', '-f', '%s.orig' % self.userJS])
if self._dm.useDDCopy:
self._dm.checkCmdAs(['shell', 'dd', 'if=%s' % self.userJS, 'of=%s.orig' % self.userJS])
else:
self._dm.checkCmdAs(['shell', 'cp', self.userJS, '%s.orig' % self.userJS])
self._dm.pushFile(os.path.join(options.profilePath, "user.js"), self.userJS)
self.updateProfilesIni(self.remoteProfile)
options.profilePath = self.remoteProfile
@ -355,7 +381,10 @@ def main():
options, args = parser.parse_args()
# create our Marionette instance
kwargs = {'emulator': options.emulator}
kwargs = {}
if options.emulator:
kwargs['emulator'] = options.emulator
auto.setEmulator(True)
if options.b2gPath:
kwargs['homedir'] = options.b2gPath
if options.marionette:
@ -383,6 +412,10 @@ def main():
mochitest = B2GMochitest(auto, dm, options)
# Create /data/local/tests, to force its use by DeviceManagerADB;
# B2G won't run correctly with the profile installed to /mnt/sdcard.
dm.mkDirs(mochitest.testDir)
options = parser.verifyOptions(options, mochitest)
if (options == None):
sys.exit(1)