123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615 |
- #!/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 posixpath
- import sys, os
- import subprocess
- import runxpcshelltests as xpcshell
- import tempfile
- import time
- from zipfile import ZipFile
- from mozlog import commandline
- import shutil
- import mozdevice
- import mozfile
- import mozinfo
- from xpcshellcommandline import parser_remote
- here = os.path.dirname(os.path.abspath(__file__))
- def remoteJoin(path1, path2):
- return posixpath.join(path1, path2)
- class RemoteXPCShellTestThread(xpcshell.XPCShellTestThread):
- def __init__(self, *args, **kwargs):
- xpcshell.XPCShellTestThread.__init__(self, *args, **kwargs)
- self.shellReturnCode = None
- # embed the mobile params from the harness into the TestThread
- mobileArgs = kwargs.get('mobileArgs')
- for key in mobileArgs:
- setattr(self, key, mobileArgs[key])
- def buildCmdTestFile(self, name):
- remoteDir = self.remoteForLocal(os.path.dirname(name))
- if remoteDir == self.remoteHere:
- remoteName = os.path.basename(name)
- else:
- remoteName = remoteJoin(remoteDir, os.path.basename(name))
- return ['-e', 'const _TEST_FILE = ["%s"];' %
- remoteName.replace('\\', '/')]
- def remoteForLocal(self, local):
- for mapping in self.pathMapping:
- if (os.path.abspath(mapping.local) == os.path.abspath(local)):
- return mapping.remote
- return local
- def setupTempDir(self):
- # make sure the temp dir exists
- self.clearRemoteDir(self.remoteTmpDir)
- # env var is set in buildEnvironment
- return self.remoteTmpDir
- def setupPluginsDir(self):
- if not os.path.isdir(self.pluginsPath):
- return None
- # making sure tmp dir is set up
- self.setupTempDir()
- pluginsDir = remoteJoin(self.remoteTmpDir, "plugins")
- self.device.pushDir(self.pluginsPath, pluginsDir)
- if self.interactive:
- self.log.info("plugins dir is %s" % pluginsDir)
- return pluginsDir
- def setupProfileDir(self):
- self.clearRemoteDir(self.profileDir)
- if self.interactive or self.singleFile:
- self.log.info("profile dir is %s" % self.profileDir)
- return self.profileDir
- def setupMozinfoJS(self):
- local = tempfile.mktemp()
- mozinfo.output_to_file(local)
- mozInfoJSPath = remoteJoin(self.profileDir, "mozinfo.json")
- self.device.pushFile(local, mozInfoJSPath)
- os.remove(local)
- return mozInfoJSPath
- def logCommand(self, name, completeCmd, testdir):
- self.log.info("%s | full command: %r" % (name, completeCmd))
- self.log.info("%s | current directory: %r" % (name, self.remoteHere))
- self.log.info("%s | environment: %s" % (name, self.env))
- def getHeadAndTailFiles(self, test):
- """Override parent method to find files on remote device.
- Obtains lists of head- and tail files. Returns a tuple containing
- a list of head files and a list of tail files.
- """
- def sanitize_list(s, kind):
- for f in s.strip().split(' '):
- f = f.strip()
- if len(f) < 1:
- continue
- path = remoteJoin(self.remoteHere, f)
- # skip check for file existence: the convenience of discovering
- # a missing file does not justify the time cost of the round trip
- # to the device
- yield path
- self.remoteHere = self.remoteForLocal(test['here'])
- headlist = test.get('head', '')
- taillist = test.get('tail', '')
- return (list(sanitize_list(headlist, 'head')),
- list(sanitize_list(taillist, 'tail')))
- def buildXpcsCmd(self):
- # change base class' paths to remote paths and use base class to build command
- self.xpcshell = remoteJoin(self.remoteBinDir, "xpcw")
- self.headJSPath = remoteJoin(self.remoteScriptsDir, 'head.js')
- self.httpdJSPath = remoteJoin(self.remoteComponentsDir, 'httpd.js')
- self.httpdManifest = remoteJoin(self.remoteComponentsDir, 'httpd.manifest')
- self.testingModulesDir = self.remoteModulesDir
- self.testharnessdir = self.remoteScriptsDir
- xpcshell.XPCShellTestThread.buildXpcsCmd(self)
- # remove "-g <dir> -a <dir>" and add "--greomni <apk>"
- del(self.xpcsCmd[1:5])
- if self.options.localAPK:
- self.xpcsCmd.insert(3, '--greomni')
- self.xpcsCmd.insert(4, self.remoteAPK)
- if self.remoteDebugger:
- # for example, "/data/local/gdbserver" "localhost:12345"
- self.xpcsCmd = [
- self.remoteDebugger,
- self.remoteDebuggerArgs,
- self.xpcsCmd]
- def killTimeout(self, proc):
- self.kill(proc)
- def launchProcess(self, cmd, stdout, stderr, env, cwd, timeout=None):
- self.timedout = False
- cmd.insert(1, self.remoteHere)
- outputFile = "xpcshelloutput"
- with open(outputFile, 'w+') as f:
- try:
- self.shellReturnCode = self.device.shell(cmd, f, timeout=timeout+10)
- except mozdevice.DMError as e:
- if self.timedout:
- # If the test timed out, there is a good chance the SUTagent also
- # timed out and failed to return a return code, generating a
- # DMError. Ignore the DMError to simplify the error report.
- self.shellReturnCode = None
- pass
- else:
- raise e
- # The device manager may have timed out waiting for xpcshell.
- # Guard against an accumulation of hung processes by killing
- # them here. Note also that IPC tests may spawn new instances
- # of xpcshell.
- self.device.killProcess("xpcshell")
- return outputFile
- def checkForCrashes(self,
- dump_directory,
- symbols_path,
- test_name=None):
- if not self.device.dirExists(self.remoteMinidumpDir):
- # The minidumps directory is automatically created when Fennec
- # (first) starts, so its lack of presence is a hint that
- # something went wrong.
- print "Automation Error: No crash directory (%s) found on remote device" % self.remoteMinidumpDir
- # Whilst no crash was found, the run should still display as a failure
- return True
- with mozfile.TemporaryDirectory() as dumpDir:
- self.device.getDirectory(self.remoteMinidumpDir, dumpDir)
- crashed = xpcshell.XPCShellTestThread.checkForCrashes(self, dumpDir, symbols_path, test_name)
- self.clearRemoteDir(self.remoteMinidumpDir)
- return crashed
- def communicate(self, proc):
- f = open(proc, "r")
- contents = f.read()
- f.close()
- os.remove(proc)
- return contents, ""
- def poll(self, proc):
- if self.device.processExist("xpcshell") is None:
- return self.getReturnCode(proc)
- # Process is still running
- return None
- def kill(self, proc):
- return self.device.killProcess("xpcshell", True)
- def getReturnCode(self, proc):
- if self.shellReturnCode is not None:
- return self.shellReturnCode
- else:
- return -1
- def removeDir(self, dirname):
- self.device.removeDir(dirname)
- def clearRemoteDir(self, remoteDir):
- out = ""
- try:
- out = self.device.shellCheckOutput([self.remoteClearDirScript, remoteDir])
- except mozdevice.DMError:
- self.log.info("unable to delete %s: '%s'" % (remoteDir, str(out)))
- self.log.info("retrying after 10 seconds...")
- time.sleep(10)
- try:
- out = self.device.shellCheckOutput([self.remoteClearDirScript, remoteDir])
- except mozdevice.DMError:
- self.log.error("failed to delete %s: '%s'" % (remoteDir, str(out)))
- #TODO: consider creating a separate log dir. We don't have the test file structure,
- # so we use filename.log. Would rather see ./logs/filename.log
- def createLogFile(self, test, stdout):
- try:
- f = None
- filename = test.replace('\\', '/').split('/')[-1] + ".log"
- f = open(filename, "w")
- f.write(stdout)
- finally:
- if f is not None:
- f.close()
- # A specialization of XPCShellTests that runs tests on an Android device
- # via devicemanager.
- class XPCShellRemote(xpcshell.XPCShellTests, object):
- def __init__(self, devmgr, options, log):
- xpcshell.XPCShellTests.__init__(self, log)
- # Add Android version (SDK level) to mozinfo so that manifest entries
- # can be conditional on android_version.
- androidVersion = devmgr.shellCheckOutput(['getprop', 'ro.build.version.sdk'])
- mozinfo.info['android_version'] = androidVersion
- self.localLib = options.localLib
- self.localBin = options.localBin
- self.options = options
- self.device = devmgr
- self.pathMapping = []
- self.remoteTestRoot = "%s/xpc" % self.device.deviceRoot
- # remoteBinDir contains xpcshell and its wrapper script, both of which must
- # be executable. Since +x permissions cannot usually be set on /mnt/sdcard,
- # and the test root may be on /mnt/sdcard, remoteBinDir is set to be on
- # /data/local, always.
- self.remoteBinDir = "/data/local/xpcb"
- # Terse directory names are used here ("c" for the components directory)
- # to minimize the length of the command line used to execute
- # xpcshell on the remote device. adb has a limit to the number
- # of characters used in a shell command, and the xpcshell command
- # line can be quite complex.
- self.remoteTmpDir = remoteJoin(self.remoteTestRoot, "tmp")
- self.remoteScriptsDir = self.remoteTestRoot
- self.remoteComponentsDir = remoteJoin(self.remoteTestRoot, "c")
- self.remoteModulesDir = remoteJoin(self.remoteTestRoot, "m")
- self.remoteMinidumpDir = remoteJoin(self.remoteTestRoot, "minidumps")
- self.remoteClearDirScript = remoteJoin(self.remoteBinDir, "cleardir")
- self.profileDir = remoteJoin(self.remoteTestRoot, "p")
- self.remoteDebugger = options.debugger
- self.remoteDebuggerArgs = options.debuggerArgs
- self.testingModulesDir = options.testingModulesDir
- self.env = {}
- if self.options.objdir:
- self.xpcDir = os.path.join(self.options.objdir, "_tests/xpcshell")
- elif os.path.isdir(os.path.join(here, 'tests')):
- self.xpcDir = os.path.join(here, 'tests')
- else:
- print >> sys.stderr, "Couldn't find local xpcshell test directory"
- sys.exit(1)
- if options.localAPK:
- self.localAPKContents = ZipFile(options.localAPK)
- if options.setup:
- self.setupTestDir()
- self.setupUtilities()
- self.setupModules()
- self.setupMinidumpDir()
- self.remoteAPK = None
- if options.localAPK:
- self.remoteAPK = remoteJoin(self.remoteBinDir, os.path.basename(options.localAPK))
- self.setAppRoot()
- # data that needs to be passed to the RemoteXPCShellTestThread
- self.mobileArgs = {
- 'device': self.device,
- 'remoteBinDir': self.remoteBinDir,
- 'remoteScriptsDir': self.remoteScriptsDir,
- 'remoteComponentsDir': self.remoteComponentsDir,
- 'remoteModulesDir': self.remoteModulesDir,
- 'options': self.options,
- 'remoteDebugger': self.remoteDebugger,
- 'pathMapping': self.pathMapping,
- 'profileDir': self.profileDir,
- 'remoteTmpDir': self.remoteTmpDir,
- 'remoteMinidumpDir': self.remoteMinidumpDir,
- 'remoteClearDirScript': self.remoteClearDirScript,
- }
- if self.remoteAPK:
- self.mobileArgs['remoteAPK'] = self.remoteAPK
- def setLD_LIBRARY_PATH(self):
- self.env["LD_LIBRARY_PATH"] = self.remoteBinDir
- def pushWrapper(self):
- # Rather than executing xpcshell directly, this wrapper script is
- # used. By setting environment variables and the cwd in the script,
- # the length of the per-test command line is shortened. This is
- # often important when using ADB, as there is a limit to the length
- # of the ADB command line.
- localWrapper = tempfile.mktemp()
- f = open(localWrapper, "w")
- f.write("#!/system/bin/sh\n")
- for envkey, envval in self.env.iteritems():
- f.write("export %s=%s\n" % (envkey, envval))
- f.writelines([
- "cd $1\n",
- "echo xpcw: cd $1\n",
- "shift\n",
- "echo xpcw: xpcshell \"$@\"\n",
- "%s/xpcshell \"$@\"\n" % self.remoteBinDir])
- f.close()
- remoteWrapper = remoteJoin(self.remoteBinDir, "xpcw")
- self.device.pushFile(localWrapper, remoteWrapper)
- os.remove(localWrapper)
- # Removing and re-creating a directory is a common operation which
- # can be implemented more efficiently with a shell script.
- localWrapper = tempfile.mktemp()
- f = open(localWrapper, "w")
- # The directory may not exist initially, so rm may fail. 'rm -f' is not
- # supported on some Androids. Similarly, 'test' and 'if [ -d ]' are not
- # universally available, so we just ignore errors from rm.
- f.writelines([
- "#!/system/bin/sh\n",
- "rm -r \"$1\"\n",
- "mkdir \"$1\"\n"])
- f.close()
- self.device.pushFile(localWrapper, self.remoteClearDirScript)
- os.remove(localWrapper)
- self.device.chmodDir(self.remoteBinDir)
- def buildEnvironment(self):
- self.buildCoreEnvironment()
- self.setLD_LIBRARY_PATH()
- self.env["MOZ_LINKER_CACHE"] = self.remoteBinDir
- if self.options.localAPK and self.appRoot:
- self.env["GRE_HOME"] = self.appRoot
- self.env["XPCSHELL_TEST_PROFILE_DIR"] = self.profileDir
- self.env["TMPDIR"] = self.remoteTmpDir
- self.env["HOME"] = self.profileDir
- self.env["XPCSHELL_TEST_TEMP_DIR"] = self.remoteTmpDir
- self.env["XPCSHELL_MINIDUMP_DIR"] = self.remoteMinidumpDir
- if self.options.setup:
- self.pushWrapper()
- def setAppRoot(self):
- # Determine the application root directory associated with the package
- # name used by the Fennec APK.
- self.appRoot = None
- packageName = None
- if self.options.localAPK:
- try:
- packageName = self.localAPKContents.read("package-name.txt")
- if packageName:
- self.appRoot = self.device.getAppRoot(packageName.strip())
- except Exception as detail:
- print "unable to determine app root: " + str(detail)
- pass
- return None
- def setupUtilities(self):
- if (not self.device.dirExists(self.remoteBinDir)):
- # device.mkDir may fail here where shellCheckOutput may succeed -- see bug 817235
- try:
- self.device.shellCheckOutput(["mkdir", self.remoteBinDir]);
- except mozdevice.DMError:
- # Might get a permission error; try again as root, if available
- self.device.shellCheckOutput(["mkdir", self.remoteBinDir], root=True);
- self.device.shellCheckOutput(["chmod", "777", self.remoteBinDir], root=True);
- remotePrefDir = remoteJoin(self.remoteBinDir, "defaults/pref")
- if (self.device.dirExists(self.remoteTmpDir)):
- self.device.removeDir(self.remoteTmpDir)
- self.device.mkDir(self.remoteTmpDir)
- if (not self.device.dirExists(remotePrefDir)):
- self.device.mkDirs(remoteJoin(remotePrefDir, "extra"))
- if (not self.device.dirExists(self.remoteScriptsDir)):
- self.device.mkDir(self.remoteScriptsDir)
- if (not self.device.dirExists(self.remoteComponentsDir)):
- self.device.mkDir(self.remoteComponentsDir)
- local = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'head.js')
- remoteFile = remoteJoin(self.remoteScriptsDir, "head.js")
- self.device.pushFile(local, remoteFile)
- # The xpcshell binary is required for all tests. Additional binaries
- # are required for some tests. This list should be similar to
- # TEST_HARNESS_BINS in testing/mochitest/Makefile.in.
- binaries = ["xpcshell",
- "ssltunnel",
- "certutil",
- "pk12util",
- "BadCertServer",
- "OCSPStaplingServer",
- "GenerateOCSPResponse"]
- for fname in binaries:
- local = os.path.join(self.localBin, fname)
- if os.path.isfile(local):
- print >> sys.stderr, "Pushing %s.." % fname
- remoteFile = remoteJoin(self.remoteBinDir, fname)
- self.device.pushFile(local, remoteFile)
- else:
- print >> sys.stderr, "*** Expected binary %s not found in %s!" % (fname, self.localBin)
- local = os.path.join(self.localBin, "components/httpd.js")
- remoteFile = remoteJoin(self.remoteComponentsDir, "httpd.js")
- self.device.pushFile(local, remoteFile)
- local = os.path.join(self.localBin, "components/httpd.manifest")
- remoteFile = remoteJoin(self.remoteComponentsDir, "httpd.manifest")
- self.device.pushFile(local, remoteFile)
- local = os.path.join(self.localBin, "components/test_necko.xpt")
- remoteFile = remoteJoin(self.remoteComponentsDir, "test_necko.xpt")
- self.device.pushFile(local, remoteFile)
- if self.options.localAPK:
- remoteFile = remoteJoin(self.remoteBinDir, os.path.basename(self.options.localAPK))
- self.device.pushFile(self.options.localAPK, remoteFile)
- self.pushLibs()
- def pushLibs(self):
- pushed_libs_count = 0
- if self.options.localAPK:
- try:
- dir = tempfile.mkdtemp()
- for info in self.localAPKContents.infolist():
- if info.filename.endswith(".so"):
- print >> sys.stderr, "Pushing %s.." % info.filename
- remoteFile = remoteJoin(self.remoteBinDir, os.path.basename(info.filename))
- self.localAPKContents.extract(info, dir)
- localFile = os.path.join(dir, info.filename)
- with open(localFile) as f:
- # Decompress xz-compressed file.
- if f.read(5)[1:] == '7zXZ':
- cmd = ['xz', '-df', '--suffix', '.so', localFile]
- subprocess.check_output(cmd)
- # xz strips the ".so" file suffix.
- os.rename(localFile[:-3], localFile)
- self.device.pushFile(localFile, remoteFile)
- pushed_libs_count += 1
- finally:
- shutil.rmtree(dir)
- return pushed_libs_count
- for file in os.listdir(self.localLib):
- if (file.endswith(".so")):
- print >> sys.stderr, "Pushing %s.." % file
- if 'libxul' in file:
- print >> sys.stderr, "This is a big file, it could take a while."
- localFile = os.path.join(self.localLib, file)
- remoteFile = remoteJoin(self.remoteBinDir, file)
- self.device.pushFile(localFile, remoteFile)
- pushed_libs_count += 1
- # Additional libraries may be found in a sub-directory such as "lib/armeabi-v7a"
- localArmLib = os.path.join(self.localLib, "lib")
- if os.path.exists(localArmLib):
- for root, dirs, files in os.walk(localArmLib):
- for file in files:
- if (file.endswith(".so")):
- print >> sys.stderr, "Pushing %s.." % file
- localFile = os.path.join(root, file)
- remoteFile = remoteJoin(self.remoteBinDir, file)
- self.device.pushFile(localFile, remoteFile)
- pushed_libs_count += 1
- return pushed_libs_count
- def setupModules(self):
- if self.testingModulesDir:
- self.device.pushDir(self.testingModulesDir, self.remoteModulesDir)
- def setupTestDir(self):
- print 'pushing %s' % self.xpcDir
- try:
- # The tests directory can be quite large: 5000 files and growing!
- # Sometimes - like on a low-end aws instance running an emulator - the push
- # may exceed the default 5 minute timeout, so we increase it here to 10 minutes.
- self.device.pushDir(self.xpcDir, self.remoteScriptsDir, timeout=600, retryLimit=10)
- except TypeError:
- # Foopies have an older mozdevice ver without retryLimit
- self.device.pushDir(self.xpcDir, self.remoteScriptsDir)
- def setupMinidumpDir(self):
- if self.device.dirExists(self.remoteMinidumpDir):
- self.device.removeDir(self.remoteMinidumpDir)
- self.device.mkDir(self.remoteMinidumpDir)
- def buildTestList(self, test_tags=None, test_paths=None):
- xpcshell.XPCShellTests.buildTestList(self, test_tags=test_tags, test_paths=test_paths)
- uniqueTestPaths = set([])
- for test in self.alltests:
- uniqueTestPaths.add(test['here'])
- for testdir in uniqueTestPaths:
- abbrevTestDir = os.path.relpath(testdir, self.xpcDir)
- remoteScriptDir = remoteJoin(self.remoteScriptsDir, abbrevTestDir)
- self.pathMapping.append(PathMapping(testdir, remoteScriptDir))
- def verifyRemoteOptions(parser, options):
- if options.localLib is None:
- if options.localAPK and options.objdir:
- for path in ['dist/fennec', 'fennec/lib']:
- options.localLib = os.path.join(options.objdir, path)
- if os.path.isdir(options.localLib):
- break
- else:
- parser.error("Couldn't find local library dir, specify --local-lib-dir")
- elif options.objdir:
- options.localLib = os.path.join(options.objdir, 'dist/bin')
- elif os.path.isfile(os.path.join(here, '..', 'bin', 'xpcshell')):
- # assume tests are being run from a tests.zip
- options.localLib = os.path.abspath(os.path.join(here, '..', 'bin'))
- else:
- parser.error("Couldn't find local library dir, specify --local-lib-dir")
- if options.localBin is None:
- if options.objdir:
- for path in ['dist/bin', 'bin']:
- options.localBin = os.path.join(options.objdir, path)
- if os.path.isdir(options.localBin):
- break
- else:
- parser.error("Couldn't find local binary dir, specify --local-bin-dir")
- elif os.path.isfile(os.path.join(here, '..', 'bin', 'xpcshell')):
- # assume tests are being run from a tests.zip
- options.localBin = os.path.abspath(os.path.join(here, '..', 'bin'))
- else:
- parser.error("Couldn't find local binary dir, specify --local-bin-dir")
- return options
- class PathMapping:
- def __init__(self, localDir, remoteDir):
- self.local = localDir
- self.remote = remoteDir
- def main():
- if sys.version_info < (2,7):
- print >>sys.stderr, "Error: You must use python version 2.7 or newer but less than 3.0"
- sys.exit(1)
- parser = parser_remote()
- options = parser.parse_args()
- if not options.localAPK:
- for file in os.listdir(os.path.join(options.objdir, "dist")):
- if (file.endswith(".apk") and file.startswith("fennec")):
- options.localAPK = os.path.join(options.objdir, "dist")
- options.localAPK = os.path.join(options.localAPK, file)
- print >>sys.stderr, "using APK: " + options.localAPK
- break
- else:
- print >>sys.stderr, "Error: please specify an APK"
- sys.exit(1)
- options = verifyRemoteOptions(parser, options)
- log = commandline.setup_logging("Remote XPCShell",
- options,
- {"tbpl": sys.stdout})
- if options.dm_trans == "adb":
- if options.deviceIP:
- dm = mozdevice.DroidADB(options.deviceIP, options.devicePort, packageName=None, deviceRoot=options.remoteTestRoot)
- else:
- dm = mozdevice.DroidADB(packageName=None, deviceRoot=options.remoteTestRoot)
- else:
- if not options.deviceIP:
- print "Error: you must provide a device IP to connect to via the --device option"
- sys.exit(1)
- dm = mozdevice.DroidSUT(options.deviceIP, options.devicePort, deviceRoot=options.remoteTestRoot)
- if options.interactive and not options.testPath:
- print >>sys.stderr, "Error: You must specify a test filename in interactive mode!"
- sys.exit(1)
- if options.xpcshell is None:
- options.xpcshell = "xpcshell"
- xpcsh = XPCShellRemote(dm, options, log)
- # we don't run concurrent tests on mobile
- options.sequential = True
- if not xpcsh.runTests(testClass=RemoteXPCShellTestThread,
- mobileArgs=xpcsh.mobileArgs,
- **vars(options)):
- sys.exit(1)
- if __name__ == '__main__':
- main()
|