Bug 770490 - Infrastructure to run reftests on B2G, r=jgriffin

This commit is contained in:
Andrew Halberstadt 2012-08-10 14:25:20 -04:00
parent 42dfe91ce7
commit 7c1ba0c892
10 changed files with 705 additions and 75 deletions

View File

@ -35,16 +35,19 @@ class B2GRemoteAutomation(Automation):
_devicemanager = None
def __init__(self, deviceManager, appName='', remoteLog=None,
marionette=None):
marionette=None, context_chrome=True):
self._devicemanager = deviceManager
self._appName = appName
self._remoteProfile = None
self._remoteLog = remoteLog
self.marionette = marionette
self.context_chrome = context_chrome
self._is_emulator = False
# Default our product to b2g
self._product = "b2g"
# Default log finish to mochitest standard
self.logFinish = 'INFO SimpleTest FINISHED'
Automation.__init__(self)
def setEmulator(self, is_emulator):
@ -121,7 +124,7 @@ class B2GRemoteAutomation(Automation):
def waitForFinish(self, proc, utilityPath, timeout, maxTime, startTime,
debuggerInfo, symbolsPath):
""" Wait for mochitest to finish (as evidenced by a signature string
""" Wait for tests to finish (as evidenced by a signature string
in logcat), or for a given amount of time to elapse with no
output.
"""
@ -135,7 +138,7 @@ class B2GRemoteAutomation(Automation):
if currentlog:
done = time.time() + timeout
print currentlog
if 'INFO SimpleTest FINISHED' in currentlog:
if hasattr(self, 'logFinish') and self.logFinish in currentlog:
return 0
else:
if time.time() > done:
@ -162,6 +165,8 @@ class B2GRemoteAutomation(Automation):
return (serial, status)
def restartB2G(self):
# TODO hangs in subprocess.Popen without this delay
time.sleep(5)
self._devicemanager.checkCmd(['shell', 'stop', 'b2g'])
# Wait for a bit to make sure B2G has completely shut down.
time.sleep(10)
@ -194,11 +199,11 @@ class B2GRemoteAutomation(Automation):
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
# process in which to the tests. 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
# to pass env variables into the B2G process, but this doesn't
# seem to matter.
# reboot device so it starts up with the mochitest profile
@ -239,11 +244,27 @@ class B2GRemoteAutomation(Automation):
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, by setting it
# as the 'src' attribute to the homescreen mozbrowser element
# provided by B2G's shell.js.
self.marionette.set_context("chrome")
self.marionette.execute_script("document.getElementById('homescreen').src='%s';" % self.testURL)
if self.context_chrome:
self.marionette.set_context(self.marionette.CONTEXT_CHROME)
# start the tests
if hasattr(self, 'testURL'):
# Start the tests by navigating to the mochitest url, by setting it
# as the 'src' attribute to the homescreen mozbrowser element
# provided by B2G's shell.js.
self.marionette.execute_script("document.getElementById('homescreen').src='%s';" % self.testURL)
# run the script that starts the tests
elif hasattr(self, 'testScript'):
if os.path.isfile(self.testScript):
script = open(self.testScript, 'r')
self.marionette.execute_script(script.read())
script.close()
else:
# assume testScript is a string
self.marionette.execute_script(self.testScript)
else:
# assumes the tests are started on startup automatically
pass
return instance

View File

@ -24,7 +24,7 @@ include backgrounds/reftest.list
include bidi/reftest.list
# border-image
skip-if(Android) include border-image/reftest.list
skip-if(Android||B2G) include border-image/reftest.list
# border-radius/
include border-radius/reftest.list
@ -39,10 +39,10 @@ include box-ordinal/reftest.list
include box-properties/reftest.list
# box-shadow/
skip-if(Android) include box-shadow/reftest.list
skip-if(Android||B2G) include box-shadow/reftest.list
# bugs/
skip-if(Android) include bugs/reftest.list
skip-if(Android||B2G) include bugs/reftest.list
# canvas 2D
include canvas/reftest.list
@ -57,10 +57,10 @@ include css-charset/reftest.list
include css-default/reftest.list
# css :disable tests
skip-if(Android) include css-disabled/reftest.list
skip-if(Android||B2G) include css-disabled/reftest.list
# css :enable tests
skip-if(Android) include css-enabled/reftest.list
skip-if(Android||B2G) include css-enabled/reftest.list
# css @import tests
include css-import/reftest.list
@ -87,10 +87,10 @@ include css-required/reftest.list
include css-optional/reftest.list
# css valid
skip-if(Android) include css-valid/reftest.list
skip-if(Android||B2G) include css-valid/reftest.list
# css invalid
skip-if(Android) include css-invalid/reftest.list
skip-if(Android||B2G) include css-invalid/reftest.list
# css-submit-invalid
include css-submit-invalid/reftest.list
@ -105,10 +105,10 @@ include css-selectors/reftest.list
include css-transitions/reftest.list
# css :-moz-ui-invalid
skip-if(Android) include css-ui-invalid/reftest.list
skip-if(Android||B2G) include css-ui-invalid/reftest.list
# css :-moz-ui-valid
skip-if(Android) include css-ui-valid/reftest.list
skip-if(Android||B2G) include css-ui-valid/reftest.list
# css values and units
include css-valuesandunits/reftest.list
@ -138,7 +138,7 @@ include dom/reftest.list
include generated-content/reftest.list
# first-letter/
skip-if(Android) include first-letter/reftest.list
skip-if(Android||B2G) include first-letter/reftest.list
# first-line/
include first-line/reftest.list
@ -159,7 +159,7 @@ include font-inflation/reftest.list
include font-matching/reftest.list
# forms
skip-if(Android) include forms/reftest.list
skip-if(Android||B2G) include forms/reftest.list
# gfx
include ../../gfx/tests/reftest/reftest.list
@ -204,7 +204,7 @@ include margin-collapsing/reftest.list
include marquee/reftest.list
# native-theme/
skip-if(Android) include native-theme/reftest.list
skip-if(Android||B2G) include native-theme/reftest.list
# netwerk/
include ../../netwerk/test/reftest/reftest.list
@ -232,7 +232,7 @@ include ../../dom/plugins/test/reftest/reftest.list
# position-dynamic-changes/
# Disabled on Native Fennec for now, because running them takes too long
skip-if(Android&&!browserIsRemote) include position-dynamic-changes/reftest.list # Bug 762181
skip-if((Android||B2G)&&!browserIsRemote) include position-dynamic-changes/reftest.list # Bug 762181
# printing
include printing/reftest.list
@ -275,7 +275,7 @@ include text-decoration/reftest.list
include text-indent/reftest.list
# text-shadow/
skip-if(Android) include text-shadow/reftest.list
skip-if(Android||B2G) include text-shadow/reftest.list
# theme (pinstripe)
include ../../toolkit/themes/pinstripe/reftests/reftest.list

View File

@ -25,6 +25,11 @@ DEFINES += -DBOOTSTRAP
DIST_FILES += bootstrap.js
endif
ifeq ($(MOZ_BUILD_APP),b2g)
DEFINES += -DBOOTSTRAP
DEFINES += -DREFTEST_B2G
endif
# Used in install.rdf
USE_EXTENSION_MANIFEST=1
else
@ -52,10 +57,13 @@ endif
_HARNESS_FILES = \
$(srcdir)/runreftest.py \
$(srcdir)/remotereftest.py \
$(srcdir)/runreftestb2g.py \
$(srcdir)/b2g_start_script.js \
automation.py \
$(topsrcdir)/build/mobile/devicemanager.py \
$(topsrcdir)/build/mobile/devicemanagerADB.py \
$(topsrcdir)/build/mobile/devicemanagerSUT.py \
$(topsrcdir)/build/mobile/b2gautomation.py \
$(topsrcdir)/build/automationutils.py \
$(topsrcdir)/build/mobile/remoteautomation.py \
$(topsrcdir)/testing/mochitest/server.js \

View File

@ -0,0 +1,30 @@
function setDefaultPrefs() {
// This code sets the preferences for extension-based reftest; for
// command-line based reftest they are set in function handler_handle in
// reftest-cmdline.js. These two locations should stay in sync.
//
// FIXME: These should be in only one place.
var prefs = Components.classes["@mozilla.org/preferences-service;1"].
getService(Components.interfaces.nsIPrefService);
var branch = prefs.getDefaultBranch("");
branch.setBoolPref("gfx.color_management.force_srgb", true);
branch.setBoolPref("browser.dom.window.dump.enabled", true);
branch.setIntPref("ui.caretBlinkTime", -1);
branch.setBoolPref("dom.send_after_paint_to_content", true);
// no slow script dialogs
branch.setIntPref("dom.max_script_run_time", 0);
branch.setIntPref("dom.max_chrome_script_run_time", 0);
branch.setIntPref("hangmonitor.timeout", 0);
// Ensure autoplay is enabled for all platforms.
branch.setBoolPref("media.autoplay.enabled", true);
// Disable updates
branch.setBoolPref("app.update.enabled", false);
}
// Load into any existing windows
let wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
.getService(Components.interfaces.nsIWindowMediator);
let win = wm.getMostRecentWindow('');
setDefaultPrefs();
Components.utils.import("chrome://reftest/content/reftest.jsm");
OnRefTestLoad(win);

View File

@ -1,6 +1,6 @@
reftest.jar:
% content reftest %content/
content/reftest-content.js (reftest-content.js)
* content/reftest-content.js (reftest-content.js)
#ifdef BOOTSTRAP
* content/reftest.jsm (reftest.js)
#else

View File

@ -75,7 +75,7 @@ var gClearingForAssertionCheck = false;
const TYPE_SCRIPT = 'script'; // test contains individual test results
function markupDocumentViewer() {
function markupDocumentViewer() {
return docShell.contentViewer.QueryInterface(CI.nsIMarkupDocumentViewer);
}
@ -119,7 +119,9 @@ function PaintWaitFinishedListener(event)
function OnInitialLoad()
{
#ifndef REFTEST_B2G
removeEventListener("load", OnInitialLoad, true);
#endif
gDebug = CC[DEBUG_CONTRACTID].getService(CI.nsIDebug2);
@ -132,7 +134,7 @@ function OnInitialLoad()
addEventListener("MozPaintWait", PaintWaitListener, true);
addEventListener("MozPaintWaitFinished", PaintWaitFinishedListener, true);
LogWarning("Using browser remote="+ gBrowserIsRemote +"\n");
}
@ -170,11 +172,16 @@ function resetZoom() {
}
function doPrintMode(contentRootElement) {
#if REFTEST_B2G
// nsIPrintSettings not available in B2G
return false;
#else
// use getAttribute because className works differently in HTML and SVG
return contentRootElement &&
contentRootElement.hasAttribute('class') &&
contentRootElement.getAttribute('class').split(/\s+/)
.indexOf("reftest-print") != -1;
#endif
}
function setupPrintMode() {
@ -289,7 +296,7 @@ function WaitForTestEnd(contentRootElement, inPrintMode) {
} catch (e) {
LogWarning("flushWindow failed: " + e + "\n");
}
if (!afterPaintWasPending && utils.isMozAfterPaintPending) {
LogInfo("FlushRendering generated paint for window " + win.location.href);
anyPendingPaintsGeneratedInDescendants = true;
@ -380,7 +387,7 @@ function WaitForTestEnd(contentRootElement, inPrintMode) {
}
state = STATE_WAITING_FOR_REFTEST_WAIT_REMOVAL;
var hasReftestWait = shouldWaitForReftestWaitRemoval(contentRootElement);
var hasReftestWait = shouldWaitForReftestWaitRemoval(contentRootElement);
// Notify the test document that now is a good time to test some invalidation
LogInfo("MakeProgress: dispatching MozReftestInvalidate");
if (contentRootElement) {
@ -459,12 +466,18 @@ function WaitForTestEnd(contentRootElement, inPrintMode) {
MakeProgress();
}
#if REFTEST_B2G
function OnDocumentLoad()
{
var currentDoc = content.document;
#else
function OnDocumentLoad(event)
{
var currentDoc = content.document;
if (event.target != currentDoc)
// Ignore load events for subframes.
return;
#endif
if (gClearingForAssertionCheck &&
currentDoc.location.href == BLANK_URL_FOR_CLEARING) {
@ -750,7 +763,7 @@ function SendInitCanvasWithSnapshot()
// browser though, it doesn't wrt correctness whether this request
// is sync or async.
var ret = sendSyncMessage("reftest:InitCanvasWithSnapshot")[0];
gHaveCanvasSnapshot = ret.painted;
return ret.painted;
}
@ -780,7 +793,7 @@ function SendUpdateCanvasForEvent(event)
{
var win = content;
var scale = markupDocumentViewer().fullZoom;
var rects = [ ];
var rectList = event.clientRects;
for (var i = 0; i < rectList.length; ++i) {
@ -803,5 +816,9 @@ function SendUpdateCanvasForEvent(event)
sendAsyncMessage("reftest:UpdateCanvasForInvalidation", { rects: rects });
}
}
#if REFTEST_B2G
OnInitialLoad();
OnDocumentLoad();
#else
addEventListener("load", OnInitialLoad, true);
#endif

View File

@ -35,7 +35,7 @@ const NS_DIRECTORY_SERVICE_CONTRACTID =
const NS_OBSERVER_SERVICE_CONTRACTID =
"@mozilla.org/observer-service;1";
Components.utils.import("resource://gre/modules/FileUtils.jsm");
Components.utils.import("resource://gre/modules/FileUtils.jsm");
var gLoadTimeout = 0;
var gTimeoutHook = null;
@ -109,7 +109,7 @@ const TYPE_SCRIPT = 'script'; // test contains individual test results
// The order of these constants matters, since when we have a status
// listed for a *manifest*, we combine the status with the status for
// the test by using the *larger*.
// the test by using the *larger*.
// FIXME: In the future, we may also want to use this rule for combining
// statuses that are on the same line (rather than making the last one
// win).
@ -206,7 +206,7 @@ function OnRefTestLoad(win)
.getService(CI.nsIProperties)
.get("ProfD", CI.nsIFile);
gCrashDumpDir.append("minidumps");
var env = CC["@mozilla.org/process/environment;1"].
getService(CI.nsIEnvironment);
gVerbose = !!env.get("MOZ_REFTEST_VERBOSE");
@ -218,7 +218,7 @@ function OnRefTestLoad(win)
} catch (e) {
gBrowserIsRemote = false;
}
if (win === undefined || win == null) {
win = window;
}
@ -235,7 +235,11 @@ function OnRefTestLoad(win)
gBrowser.setAttribute("style", "min-width: 800px; min-height: 1000px; max-width: 800px; max-height: 1000px");
#if BOOTSTRAP
#if REFTEST_B2G
var doc = gContainingWindow.document.getElementsByTagName("window")[0];
#else
var doc = gContainingWindow.document.getElementById('main-window');
#endif
while (doc.hasChildNodes()) {
doc.removeChild(doc.firstChild);
}
@ -260,27 +264,31 @@ function InitAndStartRefTests()
} catch(e) {
gDumpLog("REFTEST TEST-UNEXPECTED-FAIL | | EXCEPTION: " + e + "\n");
}
/* set the gLoadTimeout */
try {
gLoadTimeout = prefs.getIntPref("reftest.timeout");
} catch(e) {
} catch(e) {
gLoadTimeout = 5 * 60 * 1000; //5 minutes as per bug 479518
}
/* Get the logfile for android tests */
try {
var logFile = prefs.getCharPref("reftest.logFile");
if (logFile) {
try {
var f = FileUtils.File(logFile);
var mfl = FileUtils.openFileOutputStream(f, FileUtils.MODE_WRONLY | FileUtils.MODE_CREATE);
var mfl = FileUtils.openFileOutputStream(f, FileUtils.MODE_WRONLY | FileUtils.MODE_CREATE);
// Set to mirror to stdout as well as the file
gDumpLog = function (msg) {
#if BOOTSTRAP
//NOTE: on android-xul, we have a libc crash if we do a dump with %7s in the string
#if REFTEST_B2G
dump(msg);
#else
dump(msg);
//NOTE: on android-xul, we have a libc crash if we do a dump with %7s in the string
#endif
#else
dump(msg);
#endif
mfl.write(msg, msg.length);
};
@ -291,10 +299,10 @@ function InitAndStartRefTests()
}
}
} catch(e) {}
try {
gRemote = prefs.getBoolPref("reftest.remote");
} catch(e) {
} catch(e) {
gRemote = false;
}
@ -374,22 +382,22 @@ function StartTests()
} catch(e) {
gDumpLog("REFTEST TEST-UNEXPECTED-FAIL | | EXCEPTION: " + e + "\n");
}
try {
gNoCanvasCache = prefs.getIntPref("reftest.nocache");
} catch(e) {
} catch(e) {
gNoCanvasCache = false;
}
try {
gRunSlowTests = prefs.getIntPref("reftest.skipslowtests");
} catch(e) {
} catch(e) {
gRunSlowTests = false;
}
try {
uri = prefs.getCharPref("reftest.uri");
} catch(e) {
} catch(e) {
uri = "";
}
@ -415,7 +423,6 @@ function StartTests()
DoneTests();
}
#endif
try {
ReadTopManifest(uri);
BuildUseCounts();
@ -480,19 +487,26 @@ function BuildConditionSandbox(aURL) {
}
#if REFTEST_B2G
// XXX nsIGfxInfo isn't available in B2G
sandbox.d2d = false;
sandbox.azureQuartz = false;
sandbox.azureSkia = false;
sandbox.contentSameGfxBackendAsCanvas = false;
#else
var gfxInfo = (NS_GFXINFO_CONTRACTID in CC) && CC[NS_GFXINFO_CONTRACTID].getService(CI.nsIGfxInfo);
try {
sandbox.d2d = gfxInfo.D2DEnabled;
} catch (e) {
sandbox.d2d = false;
}
var info = gfxInfo.getInfo();
sandbox.azureQuartz = info.AzureCanvasBackend == "quartz";
sandbox.azureSkia = info.AzureCanvasBackend == "skia";
// true if we are using the same Azure backend for rendering canvas and content
sandbox.contentSameGfxBackendAsCanvas = info.AzureContentBackend == info.AzureCanvasBackend
|| (info.AzureContentBackend == "none" && info.AzureCanvasBackend == "cairo");
#endif
sandbox.layersGPUAccelerated =
gWindowUtils.layerManagerType != "Basic";
@ -500,7 +514,8 @@ function BuildConditionSandbox(aURL) {
gWindowUtils.layerManagerType == "OpenGL";
// Shortcuts for widget toolkits.
sandbox.Android = xr.OS == "Android";
sandbox.B2G = xr.widgetToolkit == "gonk";
sandbox.Android = xr.OS == "Android" && !sandbox.B2G;
sandbox.cocoaWidget = xr.widgetToolkit == "cocoa";
sandbox.gtk2Widget = xr.widgetToolkit == "gtk2";
sandbox.qtWidget = xr.widgetToolkit == "qt";
@ -599,7 +614,6 @@ function BuildConditionSandbox(aURL) {
dump("REFTEST INFO | " + JSON.stringify(sandbox) + " \n");
gDumpedConditionSandbox = true;
}
return sandbox;
}
@ -624,7 +638,7 @@ function ReadManifest(aURL, inherited_status)
var inputStream = channel.open();
if (channel instanceof Components.interfaces.nsIHttpChannel
&& channel.responseStatus != 200) {
gDumpLog("REFTEST TEST-UNEXPECTED-FAIL | | HTTP ERROR : " +
gDumpLog("REFTEST TEST-UNEXPECTED-FAIL | | HTTP ERROR : " +
channel.responseStatus + "\n");
}
var streamBuf = getStreamContent(inputStream);
@ -633,7 +647,6 @@ function ReadManifest(aURL, inherited_status)
// Build the sandbox for fails-if(), etc., condition evaluation.
var sandbox = BuildConditionSandbox(aURL);
var lineNo = 0;
var urlprefix = "";
for each (var str in lines) {
@ -1180,10 +1193,12 @@ function DoneTests()
let appStartup = CC["@mozilla.org/toolkit/app-startup;1"].getService(CI.nsIAppStartup);
appStartup.quit(CI.nsIAppStartup.eForceQuit);
}
if (gServer)
if (gServer) {
gServer.stop(onStopped);
else
}
else {
onStopped();
}
}
function UpdateCanvasCache(url, canvas)

View File

@ -51,7 +51,7 @@ class RemoteOptions(ReftestOptions):
self.add_option("--remote-webserver", action="store",
type = "string", dest = "remoteWebServer",
help = "IP Address of the webserver hosting the reftest content")
defaults["remoteWebServer"] = automation.getLanIp()
defaults["remoteWebServer"] = automation.getLanIp()
self.add_option("--http-port", action = "store",
type = "string", dest = "httpPort",
@ -114,23 +114,23 @@ class RemoteOptions(ReftestOptions):
# Neither remoteAppPath nor app are set -- error
print "ERROR: You must specify either appPath or app"
return None
if (options.xrePath == None):
print "ERROR: You must specify the path to the controller xre directory"
return None
else:
# Ensure xrepath is a full path
options.xrePath = os.path.abspath(options.xrePath)
# Default to <deviceroot>/reftest/reftest.log
if (options.remoteLogFile == None):
options.remoteLogFile = 'reftest.log'
options.localLogName = options.remoteLogFile
options.remoteLogFile = options.remoteTestRoot + '/' + options.remoteLogFile
# Ensure that the options.logfile (which the base class uses) is set to
# the remote setting when running remote. Also, if the user set the
# the remote setting when running remote. Also, if the user set the
# log file name there, use that instead of reusing the remotelogfile as above.
if (options.logFile):
# If the user specified a local logfile name use that
@ -168,7 +168,7 @@ class ReftestServer:
def start(self):
"Run the Refest server, returning the process ID of the server."
env = self._automation.environment(xrePath = self._xrePath)
env["XPCOM_DEBUG_BREAK"] = "warn"
if self._automation.IS_WIN32:
@ -177,7 +177,7 @@ class ReftestServer:
args = ["-g", self._xrePath,
"-v", "170",
"-f", os.path.join(self.scriptDir, "reftest/components/httpd.js"),
"-e", "const _PROFILE_PATH = '%(profile)s';const _SERVER_PORT = '%(port)s'; const _SERVER_ADDR ='%(server)s';" %
"-e", "const _PROFILE_PATH = '%(profile)s';const _SERVER_PORT = '%(port)s'; const _SERVER_ADDR ='%(server)s';" %
{"profile" : self._profileDir.replace('\\', '\\\\'), "port" : self.httpPort, "server" : self.webServer },
"-f", os.path.join(self.scriptDir, "server.js")]
@ -211,16 +211,17 @@ class ReftestServer:
return 1
def stop(self):
try:
c = urllib2.urlopen(self.shutdownURL)
c.read()
c.close()
if hasattr(self, '_process'):
try:
c = urllib2.urlopen(self.shutdownURL)
c.read()
c.close()
rtncode = self._process.poll()
if (rtncode == None):
self._process.terminate()
except:
self._process.kill()
rtncode = self._process.poll()
if (rtncode == None):
self._process.terminate()
except:
self._process.kill()
class RemoteReftest(RefTest):
remoteApp = ''
@ -279,7 +280,7 @@ class RemoteReftest(RefTest):
xpcshell = "xpcshell"
if (os.name == "nt"):
xpcshell += ".exe"
if (options.utilityPath):
paths.insert(0, options.utilityPath)
options.utilityPath = self.findPath(paths, xpcshell)
@ -299,7 +300,7 @@ class RemoteReftest(RefTest):
options.xrePath = remoteXrePath
options.utilityPath = remoteUtilityPath
return 0
def stopWebServer(self, options):
self.server.stop()
@ -333,7 +334,7 @@ user_pref("capability.principal.codebase.p2.id", "http://%s:%s");
def copyExtraFilesToProfile(self, options, profileDir):
RefTest.copyExtraFilesToProfile(self, options, profileDir)
if (self._devicemanager.pushDir(profileDir, options.remoteProfile) == None):
raise devicemanager.FileError("Failed to copy extra files to device")
raise devicemanager.FileError("Failed to copy extra files to device")
def getManifestPath(self, path):
return path

View File

@ -0,0 +1,517 @@
# 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 ConfigParser
import os
import re
import sys
import tempfile
import time
import urllib
import traceback
# We need to know our current directory so that we can serve our test files from it.
SCRIPT_DIRECTORY = os.path.abspath(os.path.realpath(os.path.dirname(sys.argv[0])))
sys.path.insert(0, SCRIPT_DIRECTORY)
from automation import Automation
from b2gautomation import B2GRemoteAutomation
from runreftest import RefTest
from runreftest import ReftestOptions
from remotereftest import ReftestServer
from mozprofile import Profile
from mozrunner import Runner
import devicemanagerADB
import manifestparser
from marionette import Marionette
class B2GOptions(ReftestOptions):
def __init__(self, automation, **kwargs):
defaults = {}
ReftestOptions.__init__(self, automation)
self.add_option("--b2gpath", action="store",
type = "string", dest = "b2gPath",
help = "path to B2G repo or qemu dir")
defaults["b2gPath"] = None
self.add_option("--marionette", action="store",
type = "string", dest = "marionette",
help = "host:port to use when connecting to Marionette")
defaults["marionette"] = None
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("--emulator-res", action="store",
type="string", dest = "emulator_res",
help = "Emulator resolution of the format '<width>x<height>'")
defaults["emulator_res"] = None
self.add_option("--no-window", action="store_true",
dest = "noWindow",
help = "Pass --no-window to the emulator")
defaults["noWindow"] = False
self.add_option("--adbpath", action="store",
type = "string", dest = "adbPath",
help = "path to adb")
defaults["adbPath"] = "adb"
self.add_option("--deviceIP", action="store",
type = "string", dest = "deviceIP",
help = "ip address of remote device to test")
defaults["deviceIP"] = None
self.add_option("--devicePort", action="store",
type = "string", dest = "devicePort",
help = "port of remote device to test")
defaults["devicePort"] = 20701
self.add_option("--remote-logfile", action="store",
type = "string", dest = "remoteLogFile",
help = "Name of log file on the device relative to the device root. PLEASE ONLY USE A FILENAME.")
defaults["remoteLogFile"] = None
self.add_option("--remote-webserver", action = "store",
type = "string", dest = "remoteWebServer",
help = "ip address where the remote web server is hosted at")
defaults["remoteWebServer"] = None
self.add_option("--http-port", action = "store",
type = "string", dest = "httpPort",
help = "ip address where the remote web server is hosted at")
defaults["httpPort"] = automation.DEFAULT_HTTP_PORT
self.add_option("--ssl-port", action = "store",
type = "string", dest = "sslPort",
help = "ip address where the remote web server is hosted at")
defaults["sslPort"] = automation.DEFAULT_SSL_PORT
self.add_option("--pidfile", action = "store",
type = "string", dest = "pidFile",
help = "name of the pidfile to generate")
defaults["pidFile"] = ""
defaults["remoteTestRoot"] = None
defaults["logFile"] = "reftest.log"
defaults["autorun"] = True
defaults["closeWhenDone"] = True
defaults["testPath"] = ""
self.set_defaults(**defaults)
def verifyRemoteOptions(self, options):
options.remoteTestRoot = self._automation._devicemanager.getDeviceRoot() + "/reftest"
options.remoteProfile = options.remoteTestRoot + "/profile"
productRoot = options.remoteTestRoot + "/" + self._automation._product
if options.utilityPath == self._automation.DIST_BIN:
options.utilityPath = productRoot + "/bin"
if options.remoteWebServer == None:
if os.name != "nt":
options.remoteWebServer = self._automation.getLanIp()
else:
print "ERROR: you must specify a --remote-webserver=<ip address>\n"
return None
options.webServer = options.remoteWebServer
#if not options.emulator and not options.deviceIP:
# print "ERROR: you must provide a device IP"
# return None
if options.remoteLogFile == None:
options.remoteLogFile = "reftest.log"
options.localLogName = options.remoteLogFile
options.remoteLogFile = options.remoteTestRoot + '/' + options.remoteLogFile
# Ensure that the options.logfile (which the base class uses) is set to
# the remote setting when running remote. Also, if the user set the
# log file name there, use that instead of reusing the remotelogfile as above.
if (options.logFile):
# If the user specified a local logfile name use that
options.localLogName = options.logFile
options.logFile = options.remoteLogFile
# Only reset the xrePath if it wasn't provided
if options.xrePath == None:
options.xrePath = options.utilityPath
options.xrePath = os.path.abspath(options.xrePath)
if options.pidFile != "":
f = open(options.pidFile, 'w')
f.write("%s" % os.getpid())
f.close()
return options
class ProfileConfigParser(ConfigParser.RawConfigParser):
"""Subclass of RawConfigParser that outputs .ini files in the exact
format expected for profiles.ini, which is slightly different
than the default format.
"""
def optionxform(self, optionstr):
return optionstr
def write(self, fp):
if self._defaults:
fp.write("[%s]\n" % ConfigParser.DEFAULTSECT)
for (key, value) in self._defaults.items():
fp.write("%s=%s\n" % (key, str(value).replace('\n', '\n\t')))
fp.write("\n")
for section in self._sections:
fp.write("[%s]\n" % section)
for (key, value) in self._sections[section].items():
if key == "__name__":
continue
if (value is not None) or (self._optcre == self.OPTCRE):
key = "=".join((key, str(value).replace('\n', '\n\t')))
fp.write("%s\n" % (key))
fp.write("\n")
class B2GReftest(RefTest):
_automation = None
_devicemanager = None
localProfile = None
remoteApp = ''
profile = None
def __init__(self, automation, devicemanager, options, scriptDir):
self._automation = automation
RefTest.__init__(self, self._automation)
self._devicemanager = devicemanager
self.runSSLTunnel = False
self.remoteTestRoot = options.remoteTestRoot
self.remoteProfile = options.remoteProfile
self._automation.setRemoteProfile(self.remoteProfile)
self.localLogName = options.localLogName
self.remoteLogFile = options.remoteLogFile
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
self.scriptDir = scriptDir
self.SERVER_STARTUP_TIMEOUT = 90
if self._automation.IS_DEBUG_BUILD:
self.SERVER_STARTUP_TIMEOUT = 180
def cleanup(self, profileDir):
# Pull results back from device
if (self.remoteLogFile):
try:
self._devicemanager.getFile(self.remoteLogFile, self.localLogName)
except:
print "ERROR: We were not able to retrieve the info from %s" % self.remoteLogFile
sys.exit(5)
# Restore the original profiles.ini.
if self.originalProfilesIni:
try:
if not self._automation._is_emulator:
self.restoreProfilesIni()
os.remove(self.originalProfilesIni)
except:
pass
if not self._automation._is_emulator:
self._devicemanager.removeFile(self.remoteLogFile)
self._devicemanager.removeDir(self.remoteProfile)
self._devicemanager.removeDir(self.remoteTestRoot)
# Restore the original user.js.
self._devicemanager.checkCmdAs(['shell', 'rm', '-f', self.userJS])
if self._devicemanager.useDDCopy:
self._devicemanager.checkCmdAs(['shell', 'dd', 'if=%s.orig' % self.userJS, 'of=%s' % self.userJS])
else:
self._devicemanager.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()
RefTest.cleanup(self, profileDir)
if getattr(self, 'pidFile', '') != '':
try:
os.remove(self.pidFile)
os.remove(self.pidFile + ".xpcshell.pid")
except:
print "Warning: cleaning up pidfile '%s' was unsuccessful from the test harness" % self.pidFile
def findPath(self, paths, filename = None):
for path in paths:
p = path
if filename:
p = os.path.join(p, filename)
if os.path.exists(self.getFullPath(p)):
return path
return None
def startWebServer(self, options):
""" Create the webserver on the host and start it up """
remoteXrePath = options.xrePath
remoteProfilePath = self.remoteProfile
remoteUtilityPath = options.utilityPath
localAutomation = Automation()
localAutomation.IS_WIN32 = False
localAutomation.IS_LINUX = False
localAutomation.IS_MAC = False
localAutomation.UNIXISH = False
hostos = sys.platform
if hostos in ['mac', 'darwin']:
localAutomation.IS_MAC = True
elif hostos in ['linux', 'linux2']:
localAutomation.IS_LINUX = True
localAutomation.UNIXISH = True
elif hostos in ['win32', 'win64']:
localAutomation.BIN_SUFFIX = ".exe"
localAutomation.IS_WIN32 = True
paths = [options.xrePath,
localAutomation.DIST_BIN,
self._automation._product,
os.path.join('..', self._automation._product)]
options.xrePath = self.findPath(paths)
if options.xrePath == None:
print "ERROR: unable to find xulrunner path for %s, please specify with --xre-path" % (os.name)
sys.exit(1)
paths.append("bin")
paths.append(os.path.join("..", "bin"))
xpcshell = "xpcshell"
if (os.name == "nt"):
xpcshell += ".exe"
if (options.utilityPath):
paths.insert(0, options.utilityPath)
options.utilityPath = self.findPath(paths, xpcshell)
if options.utilityPath == None:
print "ERROR: unable to find utility path for %s, please specify with --utility-path" % (os.name)
sys.exit(1)
options.serverProfilePath = tempfile.mkdtemp()
self.server = ReftestServer(localAutomation, options, self.scriptDir)
retVal = self.server.start()
if retVal:
return retVal
if (options.pidFile != ""):
f = open(options.pidFile + ".xpcshell.pid", 'w')
f.write("%s" % self.server._process.pid)
f.close()
retVal = self.server.ensureReady(self.SERVER_STARTUP_TIMEOUT)
if retVal:
return retVal
options.xrePath = remoteXrePath
options.utilityPath = remoteUtilityPath
options.profilePath = remoteProfilePath
return 0
def stopWebServer(self, options):
if hasattr(self, 'server'):
self.server.stop()
def restoreProfilesIni(self):
# restore profiles.ini on the device to its previous state
if not self.originalProfilesIni or not os.access(self.originalProfilesIni, os.F_OK):
raise DMError('Unable to install original profiles.ini; file not found: %s',
self.originalProfilesIni)
self._devicemanager.pushFile(self.originalProfilesIni, self.remoteProfilesIniPath)
def updateProfilesIni(self, profilePath):
# update profiles.ini on the device to point to the test profile
self.originalProfilesIni = tempfile.mktemp()
self._devicemanager.getFile(self.remoteProfilesIniPath, self.originalProfilesIni)
config = ProfileConfigParser()
config.read(self.originalProfilesIni)
for section in config.sections():
if 'Profile' in section:
config.set(section, 'IsRelative', 0)
config.set(section, 'Path', profilePath)
newProfilesIni = tempfile.mktemp()
with open(newProfilesIni, 'wb') as configfile:
config.write(configfile)
self._devicemanager.pushFile(newProfilesIni, self.remoteProfilesIniPath)
try:
os.remove(newProfilesIni)
except:
pass
def createReftestProfile(self, options, profileDir, reftestlist):
print "profileDir: " + str(profileDir)
retVal = RefTest.createReftestProfile(self, options, profileDir, reftestlist, server=options.remoteWebServer)
# Turn off the locale picker screen
fhandle = open(os.path.join(profileDir, "user.js"), 'a')
fhandle.write("""
user_pref("browser.homescreenURL", "data:text/html,<h1>reftests should start soon</h1>");
user_pref("browser.manifestURL", "dummy (bug 772307)");
user_pref("browser.firstrun.show.localepicker", false);
user_pref("browser.dom.window.dump.enabled", true);
user_pref("font.size.inflation.emPerLine", 0);
user_pref("font.size.inflation.minTwips", 0);
user_pref("reftest.remote", true);
user_pref("toolkit.telemetry.prompted", true);
user_pref("reftest.uri", "%s");
""" % reftestlist)
#workaround for jsreftests.
if getattr(options, 'enablePrivilege', False):
fhandle.write("""
user_pref("capability.principal.codebase.p2.granted", "UniversalXPConnect");
user_pref("capability.principal.codebase.p2.id", "http://%s:%s");
""" % (options.remoteWebServer, options.httpPort))
# Close the file
fhandle.close()
# Copy the profile to the device.
self._devicemanager.removeDir(self.remoteProfile)
if self._devicemanager.pushDir(profileDir, 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._devicemanager.checkCmdAs(['shell', 'rm', '-f', '%s.orig' % self.userJS])
if self._devicemanager.useDDCopy:
self._devicemanager.checkCmdAs(['shell', 'dd', 'if=%s' % self.userJS, 'of=%s.orig' % self.userJS])
else:
self._devicemanager.checkCmdAs(['shell', 'cp', self.userJS, '%s.orig' % self.userJS])
self._devicemanager.pushFile(os.path.join(profileDir, "user.js"), self.userJS)
self.updateProfilesIni(self.remoteProfile)
options.profilePath = self.remoteProfile
return retVal
def copyExtraFilesToProfile(self, options, profileDir):
RefTest.copyExtraFilesToProfile(self, options, profileDir)
if (self._devicemanager.pushDir(profileDir, options.remoteProfile) == None):
raise devicemanager.FileError("Failed to copy extra files to device")
def getManifestPath(self, path):
return path
def main(args=sys.argv[1:]):
auto = B2GRemoteAutomation(None, "fennec", context_chrome=True)
parser = B2GOptions(auto)
options, args = parser.parse_args(args)
# create our Marionette instance
kwargs = {}
if options.emulator:
kwargs['emulator'] = options.emulator
auto.setEmulator(True)
if options.noWindow:
kwargs['noWindow'] = True
if options.emulator_res:
kwargs['emulator_res'] = options.emulator_res
if options.b2gPath:
kwargs['homedir'] = options.b2gPath
if options.marionette:
host,port = options.marionette.split(':')
kwargs['host'] = host
kwargs['port'] = int(port)
marionette = Marionette(**kwargs)
auto.marionette = marionette
# create the DeviceManager
kwargs = {'adbPath': options.adbPath}
if options.deviceIP:
kwargs.update({'host': options.deviceIP,
'port': options.devicePort})
dm = devicemanagerADB.DeviceManagerADB(**kwargs)
auto.setDeviceManager(dm)
options = parser.verifyRemoteOptions(options)
if (options == None):
print "ERROR: Invalid options specified, use --help for a list of valid options"
sys.exit(1)
# TODO fix exception
if not options.ignoreWindowSize:
parts = dm.getInfo('screen')['screen'][0].split()
width = int(parts[0].split(':')[1])
height = int(parts[1].split(':')[1])
if (width < 1366 or height < 1050):
print "ERROR: Invalid screen resolution %sx%s, please adjust to 1366x1050 or higher" % (width, height)
return 1
auto.setProduct("b2g")
auto.testScript = os.path.join(SCRIPT_DIRECTORY, 'b2g_start_script.js')
auto.logFinish = "REFTEST TEST-START | Shutdown"
reftest = B2GReftest(auto, dm, options, SCRIPT_DIRECTORY)
# Create /data/local/tests, to force its use by DeviceManagerADB;
# B2G won't run correctly with the profile installed to /mnt/sdcard.
dm.mkDirs(reftest.testDir)
logParent = os.path.dirname(options.remoteLogFile)
dm.mkDir(logParent);
auto.setRemoteLog(options.remoteLogFile)
auto.setServerInfo(options.webServer, options.httpPort, options.sslPort)
# Dynamically build the reftest URL if possible, beware that args[0] should exist 'inside' the webroot
manifest = args[0]
if os.path.exists(os.path.join(SCRIPT_DIRECTORY, args[0])):
manifest = "http://%s:%s/%s" % (options.remoteWebServer, options.httpPort, args[0])
elif os.path.exists(args[0]):
manifestPath = os.path.abspath(args[0]).split(SCRIPT_DIRECTORY)[1].strip('/')
manifest = "http://%s:%s/%s" % (options.remoteWebServer, options.httpPort, manifestPath)
else:
print "ERROR: Could not find test manifest '%s'" % manifest
return 1
# Start the webserver
retVal = 1
try:
retVal = reftest.startWebServer(options)
if retVal:
return retVal
procName = options.app.split('/')[-1]
if (dm.processExist(procName)):
dm.killProcess(procName)
cmdlineArgs = ["-reftest", manifest]
if getattr(options, 'bootstrap', False):
cmdlineArgs = []
retVal = reftest.runTests(manifest, options, cmdlineArgs)
except:
print "TEST-UNEXPECTED-FAIL | %s | Exception caught while running tests." % sys.exc_info()[1]
traceback.print_exc()
reftest.stopWebServer(options)
try:
reftest.cleanup(None)
except:
pass
return 1
reftest.stopWebServer(options)
return retVal
if __name__ == "__main__":
sys.exit(main())

View File

@ -159,6 +159,11 @@ REMOTE_REFTEST = rm -f ./$@.log && $(PYTHON) _tests/reftest/remotereftest.py \
--app=$(TEST_PACKAGE_NAME) --deviceIP=${TEST_DEVICE} --xre-path=${MOZ_HOST_BIN} \
$(SYMBOLS_PATH) $(EXTRA_TEST_ARGS) $(1) | tee ./$@.log
RUN_REFTEST_B2G = rm -f ./$@.log && $(PYTHON) _tests/reftest/runreftestb2g.py \
--remote-webserver=10.0.2.2 --b2gpath=${B2G_PATH} --adbpath=${ADB_PATH} \
--xre-path=${MOZ_HOST_BIN} $(SYMBOLS_PATH) --ignore-window-size \
$(EXTRA_TEST_ARGS) $(1) | tee ./$@.log
ifeq ($(OS_ARCH),WINNT) #{
# GPU-rendered shadow layers are unsupported here
OOP_CONTENT = --setpref=browser.tabs.remote=true --setpref=layers.acceleration.disabled=true
@ -186,6 +191,22 @@ reftest-remote:
$(CHECK_TEST_ERROR); \
fi
reftest-b2g: TEST_PATH?=layout/reftests/reftest.list
reftest-b2g:
@if [ ! -f ${MOZ_HOST_BIN}/xpcshell ]; then \
echo "please set the MOZ_HOST_BIN environment variable"; \
elif [ "${B2G_PATH}" = "" -o "${ADB_PATH}" = "" ]; then \
echo "please set the B2G_PATH and ADB_PATH environment variables"; \
else \
ln -s $(abspath $(topsrcdir)) _tests/reftest/tests; \
if [ "${REFTEST_PATH}" != "" ]; then \
$(call RUN_REFTEST_B2G,tests/${REFTEST_PATH}); \
else \
$(call RUN_REFTEST_B2G,tests/$(TEST_PATH)); \
fi; \
$(CHECK_TEST_ERROR); \
fi
reftest-ipc: TEST_PATH?=layout/reftests/reftest.list
reftest-ipc:
$(call RUN_REFTEST,$(topsrcdir)/$(TEST_PATH) $(OOP_CONTENT))