Bug 916350 - Make it possible to run reftests on b2g desktop, r=jgriffin,ted

This commit is contained in:
Andrew Halberstadt 2014-01-20 17:44:44 -05:00
parent a0d854f7ae
commit 9464506911
8 changed files with 268 additions and 66 deletions

View File

@ -45,6 +45,7 @@ _HARNESS_FILES = \
$(srcdir)/runreftest.py \
$(srcdir)/remotereftest.py \
$(srcdir)/runreftestb2g.py \
$(srcdir)/b2g_desktop.py \
$(srcdir)/b2g_start_script.js \
automation.py \
$(topsrcdir)/testing/mozbase/mozdevice/mozdevice/devicemanager.py \

View File

@ -0,0 +1,170 @@
# 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 __future__ import print_function, unicode_literals
import json
import os
import signal
import sys
import threading
here = os.path.abspath(os.path.dirname(__file__))
from runreftest import RefTest, ReftestOptions
from marionette import Marionette
from mozprocess import ProcessHandler
from mozrunner import FirefoxRunner
import mozinfo
import mozlog
log = mozlog.getLogger('REFTEST')
class B2GDesktopReftest(RefTest):
def __init__(self, marionette):
RefTest.__init__(self)
self.last_test = os.path.basename(__file__)
self.marionette = marionette
self.profile = None
self.runner = None
self.test_script = os.path.join(here, 'b2g_start_script.js')
self.timeout = None
def run_marionette_script(self):
assert(self.marionette.wait_for_port())
self.marionette.start_session()
self.marionette.set_context(self.marionette.CONTEXT_CHROME)
if os.path.isfile(self.test_script):
f = open(self.test_script, 'r')
self.test_script = f.read()
f.close()
self.marionette.execute_script(self.test_script)
def run_tests(self, test_path, options):
reftestlist = self.getManifestPath(test_path)
if not reftestlist.startswith('file://'):
reftestlist = 'file://%s' % reftestlist
self.profile = self.create_profile(options, reftestlist,
profile_to_clone=options.profile)
env = self.buildBrowserEnv(options, self.profile.profile)
kp_kwargs = { 'processOutputLine': [self._on_output],
'onTimeout': [self._on_timeout],
'kill_on_timeout': False }
if not options.debugger:
if not options.timeout:
if mozinfo.info['debug']:
options.timeout = 420
else:
options.timeout = 300
self.timeout = options.timeout + 30.0
log.info("%s | Running tests: start.", os.path.basename(__file__))
cmd, args = self.build_command_line(options.app,
ignore_window_size=options.ignoreWindowSize)
self.runner = FirefoxRunner(profile=self.profile,
binary=cmd,
cmdargs=args,
env=env,
process_class=ProcessHandler,
symbols_path=options.symbolsPath,
kp_kwargs=kp_kwargs)
status = 0
try:
self.runner.start(outputTimeout=self.timeout)
log.info("%s | Application pid: %d",
os.path.basename(__file__),
self.runner.process_handler.pid)
# kick starts the reftest harness
self.run_marionette_script()
status = self.runner.wait()
finally:
self.runner.check_for_crashes(test_name=self.last_test)
self.runner.cleanup()
if status > 0:
log.testFail("%s | application terminated with exit code %s",
self.last_test, status)
elif status < 0:
log.info("%s | application killed with signal %s",
self.last_test, -status)
log.info("%s | Running tests: end.", os.path.basename(__file__))
return status
def create_profile(self, options, reftestlist, profile_to_clone=None):
profile = RefTest.createReftestProfile(self, options, reftestlist,
profile_to_clone=profile_to_clone)
prefs = {}
# Turn off the locale picker screen
prefs["browser.firstrun.show.localepicker"] = False
prefs["browser.homescreenURL"] = "app://test-container.gaiamobile.org/index.html"
prefs["browser.manifestURL"] = "app://test-container.gaiamobile.org/manifest.webapp"
prefs["browser.tabs.remote"] = False
prefs["dom.ipc.tabs.disabled"] = False
prefs["dom.mozBrowserFramesEnabled"] = True
prefs["font.size.inflation.emPerLine"] = 0
prefs["font.size.inflation.minTwips"] = 0
prefs["network.dns.localDomains"] = "app://test-container.gaiamobile.org"
prefs["reftest.browser.iframe.enabled"] = False
prefs["reftest.remote"] = False
prefs["reftest.uri"] = "%s" % reftestlist
# Set a future policy version to avoid the telemetry prompt.
prefs["toolkit.telemetry.prompted"] = 999
prefs["toolkit.telemetry.notifiedOptOut"] = 999
# Set the extra prefs.
profile.set_preferences(prefs)
return profile
def build_command_line(self, app, ignore_window_size=False):
cmd = os.path.abspath(app)
args = []
if not ignore_window_size:
args.extend(['--screen', '800x1000'])
return cmd, args
def _on_output(self, line):
print(line)
# TODO use structured logging
if "TEST-START" in line and "|" in line:
self.last_test = line.split("|")[1].strip()
def _on_timeout(self):
msg = "%s | application timed out after %s seconds with no output"
log.testFail(msg % (self.last_test, self.timeout))
# kill process to get a stack
self.runner.stop(sig=signal.SIGABRT)
def run_desktop_reftests(parser, options, args):
kwargs = {}
if options.marionette:
host, port = options.marionette.split(':')
kwargs['host'] = host
kwargs['port'] = int(port)
marionette = Marionette.getMarionetteOrExit(**kwargs)
reftest = B2GDesktopReftest(marionette)
options = ReftestOptions.verifyCommonOptions(parser, options, reftest)
if options == None:
sys.exit(1)
# add a -bin suffix if b2g-bin exists, but just b2g was specified
if options.app[-4:] != '-bin':
if os.path.isfile("%s-bin" % options.app):
options.app = "%s-bin" % options.app
if options.desktop and not options.profile:
raise Exception("must specify --profile when specifying --desktop")
sys.exit(reftest.run_tests(args[0], options))

View File

@ -2,9 +2,6 @@
* 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/. */
let serverAddr = __marionetteParams[0];
let serverPort = __marionetteParams[1];
function setDefaultPrefs() {
// This code sets the preferences for extension-based reftest; for
// command-line based reftest they are set in function handler_handle in
@ -44,6 +41,12 @@ function setDefaultPrefs() {
}
function setPermissions() {
if (__marionetteParams.length < 2) {
return;
}
let serverAddr = __marionetteParams[0];
let serverPort = __marionetteParams[1];
let perms = Cc["@mozilla.org/permissionmanager;1"]
.getService(Ci.nsIPermissionManager);
let ioService = Cc["@mozilla.org/network/io-service;1"]

View File

@ -394,6 +394,15 @@ function InitAndStartRefTests()
if (gRemote) {
gServer = null;
} else {
// not all gecko applications autoregister xpcom components
if (CC["@mozilla.org/server/jshttp;1"] === undefined) {
var file = CC["@mozilla.org/file/directory_service;1"].
getService(CI.nsIProperties).get("ProfD", CI.nsIFile);
file.appendRelativePath("extensions/reftest@mozilla.org/chrome.manifest");
registrar = Components.manager.QueryInterface(CI.nsIComponentRegistrar);
registrar.autoRegister(file);
}
gServer = CC["@mozilla.org/server/jshttp;1"].
createInstance(CI.nsIHttpServer);
}

View File

@ -6,23 +6,28 @@
Runs the reftest test harness.
"""
import re
import sys
import shutil
import os
import threading
import subprocess
from optparse import OptionParser
import collections
import json
import mozprofile
import multiprocessing
import os
import re
import shutil
import subprocess
import sys
import threading
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 automationutils import *
from optparse import OptionParser
from tempfile import mkdtemp
import mozprofile
from automationutils import (
addCommonOptions,
getDebuggerInfo,
isURL,
processLeakLog
)
def categoriesToRegex(categoryList):
return "\\(" + ', '.join(["(?P<%s>\\d+) %s" % c for c in categoryList]) + "\\)"
@ -129,11 +134,11 @@ class RefTest(object):
return '"%s"' % re.sub(r'([\\"])', r'\\\1', s)
def createReftestProfile(self, options, manifest, server='localhost',
special_powers=True):
special_powers=True, profile_to_clone=None):
"""
Sets up a profile for reftest.
'manifest' is the path to the reftest.list file we want to test with. This is used in
the remote subclass in remotereftest.py so we can write it to a preference for the
the remote subclass in remotereftest.py so we can write it to a preference for the
bootstrap extension.
"""
@ -184,11 +189,14 @@ class RefTest(object):
for f in options.extensionsToInstall:
addons.append(self.getFullPath(f))
profile = mozprofile.profile.Profile(
addons=addons,
preferences=prefs,
locations=locations,
)
kwargs = { 'addons': addons,
'preferences': prefs,
'locations': locations }
if profile_to_clone:
profile = mozprofile.Profile.clone(profile_to_clone, **kwargs)
else:
profile = mozprofile.Profile(**kwargs)
self.copyExtraFilesToProfile(options, profile)
return profile
@ -201,7 +209,7 @@ class RefTest(object):
if ix <= 0:
print "Error: syntax error in --setenv=" + v
return None
browserEnv[v[:ix]] = v[ix + 1:]
browserEnv[v[:ix]] = v[ix + 1:]
# Enable leaks detection to its own log file.
self.leakLogFile = os.path.join(profileDir, "runreftest_leaks.log")
@ -338,16 +346,16 @@ class RefTest(object):
class ReftestOptions(OptionParser):
def __init__(self, automation):
self._automation = automation
def __init__(self, automation=None):
self.automation = automation or Automation()
OptionParser.__init__(self)
defaults = {}
# we want to pass down everything from automation.__all__
addCommonOptions(self,
defaults=dict(zip(self._automation.__all__,
[getattr(self._automation, x) for x in self._automation.__all__])))
self._automation.addCommonOptions(self)
addCommonOptions(self,
defaults=dict(zip(self.automation.__all__,
[getattr(self.automation, x) for x in self.automation.__all__])))
self.automation.addCommonOptions(self)
self.add_option("--appname",
action = "store", type = "string", dest = "app",
default = os.path.join(SCRIPT_DIRECTORY, automation.DEFAULT_APP),
@ -356,8 +364,8 @@ class ReftestOptions(OptionParser):
action = "append", dest = "extraProfileFiles",
default = [],
help = "copy specified files/dirs to testing profile")
self.add_option("--timeout",
action = "store", dest = "timeout", type = "int",
self.add_option("--timeout",
action = "store", dest = "timeout", type = "int",
default = 5 * 60, # 5 minutes per bug 479518
help = "reftest will timeout in specified number of seconds. [default %default s].")
self.add_option("--leak-threshold",
@ -369,10 +377,10 @@ class ReftestOptions(OptionParser):
"than the given number")
self.add_option("--utility-path",
action = "store", type = "string", dest = "utilityPath",
default = self._automation.DIST_BIN,
default = self.automation.DIST_BIN,
help = "absolute path to directory containing utility "
"programs (xpcshell, ssltunnel, certutil)")
defaults["utilityPath"] = self._automation.DIST_BIN
defaults["utilityPath"] = self.automation.DIST_BIN
self.add_option("--total-chunks",
type = "int", dest = "totalChunks",
@ -389,7 +397,7 @@ class ReftestOptions(OptionParser):
default = None,
help = "file to log output to in addition to stdout")
defaults["logFile"] = None
self.add_option("--skip-slow-tests",
dest = "skipSlowTests", action = "store_true",
help = "skip tests marked as slow when running")

View File

@ -9,11 +9,11 @@ import tempfile
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)
here = os.path.abspath(os.path.dirname(__file__))
from automation import Automation
from b2gautomation import B2GRemoteAutomation
from b2g_desktop import run_desktop_reftests
from runreftest import RefTest
from runreftest import ReftestOptions
from remotereftest import ReftestServer
@ -21,7 +21,6 @@ from remotereftest import ReftestServer
from mozdevice import DeviceManagerADB, DMError
from marionette import Marionette
class B2GOptions(ReftestOptions):
def __init__(self, automation=None, **kwargs):
@ -111,6 +110,15 @@ class B2GOptions(ReftestOptions):
type = "string", dest = "httpdPath",
help = "path to the httpd.js file")
defaults["httpdPath"] = None
self.add_option("--profile", action="store",
type="string", dest="profile",
help="for desktop testing, the path to the "
"gaia profile to use")
defaults["profile"] = None
self.add_option("--desktop", action="store_true",
dest="desktop",
help="Run the tests on a B2G desktop build")
defaults["desktop"] = False
defaults["remoteTestRoot"] = "/data/local/tests"
defaults["logFile"] = "reftest.log"
defaults["autorun"] = True
@ -125,16 +133,16 @@ class B2GOptions(ReftestOptions):
self.error("Cannot run parallel tests here")
if not options.remoteTestRoot:
options.remoteTestRoot = self._automation._devicemanager.getDeviceRoot() + "/reftest"
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:
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()
options.remoteWebServer = self.automation.getLanIp()
else:
print "ERROR: you must specify a --remote-webserver=<ip address>\n"
return None
@ -209,23 +217,20 @@ class ProfileConfigParser(ConfigParser.RawConfigParser):
fp.write("%s\n" % (key))
fp.write("\n")
class B2GRemoteReftest(RefTest):
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)
RefTest.__init__(self, automation)
self._devicemanager = devicemanager
self.runSSLTunnel = False
self.remoteTestRoot = options.remoteTestRoot
self.remoteProfile = options.remoteProfile
self._automation.setRemoteProfile(self.remoteProfile)
self.automation.setRemoteProfile(self.remoteProfile)
self.localLogName = options.localLogName
self.remoteLogFile = options.remoteLogFile
self.bundlesDir = '/system/b2g/distribution/bundles'
@ -235,7 +240,7 @@ class B2GReftest(RefTest):
self.originalProfilesIni = None
self.scriptDir = scriptDir
self.SERVER_STARTUP_TIMEOUT = 90
if self._automation.IS_DEBUG_BUILD:
if self.automation.IS_DEBUG_BUILD:
self.SERVER_STARTUP_TIMEOUT = 180
def cleanup(self, profileDir):
@ -259,13 +264,13 @@ class B2GReftest(RefTest):
# Restore the original profiles.ini.
if self.originalProfilesIni:
try:
if not self._automation._is_emulator:
if not self.automation._is_emulator:
self.restoreProfilesIni()
os.remove(self.originalProfilesIni)
except:
pass
if not self._automation._is_emulator:
if not self.automation._is_emulator:
self._devicemanager.removeFile(self.remoteLogFile)
self._devicemanager.removeDir(self.remoteProfile)
self._devicemanager.removeDir(self.remoteTestRoot)
@ -276,7 +281,7 @@ class B2GReftest(RefTest):
# We've restored the original profile, so reboot the device so that
# it gets picked up.
self._automation.rebootDevice()
self.automation.rebootDevice()
RefTest.cleanup(self, profileDir)
if getattr(self, 'pidFile', '') != '':
@ -317,8 +322,8 @@ class B2GReftest(RefTest):
paths = [options.xrePath,
localAutomation.DIST_BIN,
self._automation._product,
os.path.join('..', self._automation._product)]
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)
@ -338,7 +343,7 @@ class B2GReftest(RefTest):
sys.exit(1)
xpcshell = os.path.join(options.utilityPath, xpcshell)
if self._automation.elf_arm(xpcshell):
if self.automation.elf_arm(xpcshell):
raise Exception('xpcshell at %s is an ARM binary; please use '
'the --utility-path argument to specify the path '
'to a desktop version.' % xpcshell)
@ -367,7 +372,6 @@ class B2GReftest(RefTest):
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):
@ -470,6 +474,7 @@ class B2GReftest(RefTest):
def getManifestPath(self, path):
return path
def run_remote_reftests(parser, options, args):
auto = B2GRemoteAutomation(None, "fennec", context_chrome=True)
@ -525,11 +530,11 @@ def run_remote_reftests(parser, options, args):
return 1
auto.setProduct("b2g")
auto.test_script = os.path.join(SCRIPT_DIRECTORY, 'b2g_start_script.js')
auto.test_script = os.path.join(here, 'b2g_start_script.js')
auto.test_script_args = [options.remoteWebServer, options.httpPort]
auto.logFinish = "REFTEST TEST-START | Shutdown"
reftest = B2GReftest(auto, dm, options, SCRIPT_DIRECTORY)
reftest = B2GRemoteReftest(auto, dm, options, here)
options = parser.verifyCommonOptions(options, reftest)
logParent = os.path.dirname(options.remoteLogFile)
@ -538,14 +543,14 @@ def run_remote_reftests(parser, options, args):
auto.setServerInfo(options.webServer, options.httpPort, options.sslPort)
# Hack in a symbolic link for jsreftest
os.system("ln -s %s %s" % (os.path.join('..', 'jsreftest'), os.path.join(SCRIPT_DIRECTORY, 'jsreftest')))
os.system("ln -s %s %s" % (os.path.join('..', 'jsreftest'), os.path.join(here, 'jsreftest')))
# 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])):
if os.path.exists(os.path.join(here, 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('/')
manifestPath = os.path.abspath(args[0]).split(here)[1].strip('/')
manifest = "http://%s:%s/%s" % (options.remoteWebServer, options.httpPort, manifestPath)
else:
print "ERROR: Could not find test manifest '%s'" % manifest
@ -582,6 +587,9 @@ def run_remote_reftests(parser, options, args):
def main(args=sys.argv[1:]):
parser = B2GOptions()
options, args = parser.parse_args(args)
if options.desktop:
return run_desktop_reftests(parser, options, args)
return run_remote_reftests(parser, options, args)

View File

@ -73,16 +73,16 @@ class LocalRunner(Runner):
@classmethod
def create(cls, binary=None, cmdargs=None, env=None, kp_kwargs=None, profile_args=None,
clean_profile=True, process_class=None):
clean_profile=True, process_class=None, **kwargs):
profile = cls.profile_class(**(profile_args or {}))
return cls(profile, binary=binary, cmdargs=cmdargs, env=env, kp_kwargs=kp_kwargs,
clean_profile=clean_profile, process_class=process_class)
clean_profile=clean_profile, process_class=process_class, **kwargs)
def __init__(self, profile, binary, cmdargs=None, env=None,
kp_kwargs=None, clean_profile=None, process_class=None):
kp_kwargs=None, clean_profile=None, process_class=None, **kwargs):
super(LocalRunner, self).__init__(profile, clean_profile=clean_profile, kp_kwargs=kp_kwargs,
process_class=process_class, env=env)
process_class=process_class, env=env, **kwargs)
# find the binary
self.binary = binary

View File

@ -1,8 +1,9 @@
#!/usr/bin/env 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 os
import subprocess
import traceback
@ -38,7 +39,6 @@ class Runner(object):
self.log = mozlog.getLogger('MozRunner')
self.symbols_path = symbols_path
@abstractmethod
def start(self, *args, **kwargs):
"""
Run the process
@ -112,7 +112,10 @@ class Runner(object):
if getattr(self, 'profile', False):
self.profile.reset()
def check_for_crashes(self, dump_directory, test_name=None):
def check_for_crashes(self, dump_directory=None, test_name=None):
if not dump_directory:
dump_directory = os.path.join(self.profile.profile, 'minidumps')
crashed = False
try:
crashed = mozcrash.check_for_crashes(dump_directory,