mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
419217bc82
--HG-- extra : rebase_source : 4c260e11c51c5015d2ded66f4b8cfc94a9969298
233 lines
8.5 KiB
Python
233 lines
8.5 KiB
Python
# 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 automationutils
|
|
import os
|
|
import re
|
|
import socket
|
|
import shutil
|
|
import sys
|
|
import tempfile
|
|
import time
|
|
|
|
from automation import Automation
|
|
from devicemanager import DeviceManager, NetworkTools
|
|
|
|
class B2GRemoteAutomation(Automation):
|
|
_devicemanager = None
|
|
|
|
def __init__(self, deviceManager, appName='', remoteLog=None,
|
|
marionette=None):
|
|
self._devicemanager = deviceManager
|
|
self._appName = appName
|
|
self._remoteProfile = None
|
|
self._remoteLog = remoteLog
|
|
self.marionette = marionette
|
|
|
|
# Default our product to b2g
|
|
self._product = "b2g"
|
|
Automation.__init__(self)
|
|
|
|
def setDeviceManager(self, deviceManager):
|
|
self._devicemanager = deviceManager
|
|
|
|
def setAppName(self, appName):
|
|
self._appName = appName
|
|
|
|
def setRemoteProfile(self, remoteProfile):
|
|
self._remoteProfile = remoteProfile
|
|
|
|
def setProduct(self, product):
|
|
self._product = product
|
|
|
|
def setRemoteLog(self, logfile):
|
|
self._remoteLog = logfile
|
|
|
|
# Set up what we need for the remote environment
|
|
def environment(self, env=None, xrePath=None, crashreporter=True):
|
|
# Because we are running remote, we don't want to mimic the local env
|
|
# so no copying of os.environ
|
|
if env is None:
|
|
env = {}
|
|
|
|
return env
|
|
|
|
def checkForCrashes(self, directory, symbolsPath):
|
|
# XXX: This will have to be updated after crash reporting on b2g
|
|
# is in place.
|
|
dumpDir = tempfile.mkdtemp()
|
|
self._devicemanager.getDirectory(self._remoteProfile + '/minidumps/', dumpDir)
|
|
automationutils.checkForCrashes(dumpDir, symbolsPath, self.lastTestSeen)
|
|
try:
|
|
shutil.rmtree(dumpDir)
|
|
except:
|
|
print "WARNING: unable to remove directory: %s" % (dumpDir)
|
|
|
|
def buildCommandLine(self, app, debuggerInfo, profileDir, testURL, extraArgs):
|
|
# if remote profile is specified, use that instead
|
|
if (self._remoteProfile):
|
|
profileDir = self._remoteProfile
|
|
|
|
cmd, args = Automation.buildCommandLine(self, app, debuggerInfo, profileDir, testURL, extraArgs)
|
|
|
|
return app, args
|
|
|
|
def getLanIp(self):
|
|
nettools = NetworkTools()
|
|
return nettools.getLanIp()
|
|
|
|
def waitForFinish(self, proc, utilityPath, timeout, maxTime, startTime,
|
|
debuggerInfo, symbolsPath, logger):
|
|
""" Wait for mochitest to finish (as evidenced by a signature string
|
|
in logcat), or for a given amount of time to elapse with no
|
|
output.
|
|
"""
|
|
timeout = timeout or 120
|
|
|
|
didTimeout = False
|
|
|
|
done = time.time() + timeout
|
|
while True:
|
|
currentlog = proc.stdout
|
|
if currentlog:
|
|
done = time.time() + timeout
|
|
print currentlog
|
|
if 'INFO SimpleTest FINISHED' in currentlog:
|
|
return 0
|
|
else:
|
|
if time.time() > done:
|
|
self.log.info("TEST-UNEXPECTED-FAIL | %s | application timed "
|
|
"out after %d seconds with no output",
|
|
self.lastTestSeen, int(timeout))
|
|
return 1
|
|
|
|
def getDeviceStatus(self, serial=None):
|
|
# Get the current status of the device. If we know the device
|
|
# serial number, we look for that, otherwise we use the (presumably
|
|
# only) device shown in 'adb devices'.
|
|
serial = serial or self._devicemanager.deviceSerial
|
|
status = 'unknown'
|
|
|
|
for line in self._devicemanager.runCmd(['devices']).stdout.readlines():
|
|
result = re.match('(.*?)\t(.*)', line)
|
|
if result:
|
|
thisSerial = result.group(1)
|
|
if not serial or thisSerial == serial:
|
|
serial = thisSerial
|
|
status = result.group(2)
|
|
|
|
return (serial, status)
|
|
|
|
def rebootDevice(self):
|
|
# find device's current status and serial number
|
|
serial, status = self.getDeviceStatus()
|
|
|
|
# reboot!
|
|
self._devicemanager.checkCmd(['reboot'])
|
|
|
|
# wait for device to come back to previous status
|
|
print 'waiting for device to come back online after reboot'
|
|
start = time.time()
|
|
rserial, rstatus = self.getDeviceStatus(serial)
|
|
while rstatus != 'device':
|
|
if time.time() - start > 120:
|
|
# device hasn't come back online in 2 minutes, something's wrong
|
|
raise Exception("Device %s (status: %s) not back online after reboot" % (serial, rstatus))
|
|
time.sleep(5)
|
|
rserial, rstatus = self.getDeviceStatus(serial)
|
|
print 'device:', serial, 'status:', rstatus
|
|
|
|
def Process(self, cmd, stdout=None, stderr=None, env=None, cwd=None):
|
|
# On a desktop or fennec run, the Process method invokes a gecko
|
|
# process in which to run mochitests. For B2G, we simply
|
|
# reboot the device (which was configured with a test profile
|
|
# already), wait for B2G to start up, and then navigate to the
|
|
# test url using Marionette. There doesn't seem to be any way
|
|
# to pass env variables into the B2G process, but this doesn't
|
|
# seem to matter.
|
|
|
|
instance = self.B2GInstance(self._devicemanager)
|
|
|
|
# reboot device so it starts up with the mochitest profile
|
|
# 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()
|
|
|
|
# Infrequently, gecko comes up before networking does, so wait a little
|
|
# bit to give the network time to become available.
|
|
# XXX: need a more robust mechanism for this
|
|
time.sleep(20)
|
|
|
|
# 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])
|
|
|
|
# start a marionette session
|
|
session = self.marionette.start_session()
|
|
if 'b2g' not in session:
|
|
raise Exception("bad session value %s returned by start_session" % session)
|
|
|
|
# start the tests by navigating to the mochitest url
|
|
self.marionette.execute_script("window.location.href='%s';" % self.testURL)
|
|
|
|
return instance
|
|
|
|
# be careful here as this inner class doesn't have access to outer class members
|
|
class B2GInstance(object):
|
|
"""Represents a B2G instance running on a device, and exposes
|
|
some process-like methods/properties that are expected by the
|
|
automation.
|
|
"""
|
|
|
|
def __init__(self, dm):
|
|
self.dm = dm
|
|
self.lastloglines = []
|
|
|
|
@property
|
|
def pid(self):
|
|
# a dummy value to make the automation happy
|
|
return 0
|
|
|
|
@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
|
|
try:
|
|
self.lastloglines.index(line)
|
|
except ValueError:
|
|
break
|
|
|
|
newoutput = '\n'.join(loglines[line_index:])
|
|
self.lastloglines = loglines
|
|
|
|
return newoutput
|
|
|
|
def wait(self, timeout = None):
|
|
# this should never happen
|
|
raise Exception("'wait' called on B2GInstance")
|
|
|
|
def kill(self):
|
|
# this should never happen
|
|
raise Exception("'kill' called on B2GInstance")
|
|
|