2009-03-11 08:56:58 -07:00
|
|
|
#!/usr/bin/env python
|
|
|
|
#
|
2012-05-21 04:12:37 -07:00
|
|
|
# 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/.
|
2009-03-11 08:56:58 -07:00
|
|
|
|
2012-10-07 13:21:52 -07:00
|
|
|
import re, sys, os, os.path, logging, shutil, signal, math, time, traceback
|
2012-02-27 19:53:00 -08:00
|
|
|
import xml.dom.minidom
|
2009-03-11 08:56:58 -07:00
|
|
|
from glob import glob
|
|
|
|
from optparse import OptionParser
|
|
|
|
from subprocess import Popen, PIPE, STDOUT
|
2010-04-27 10:28:56 -07:00
|
|
|
from tempfile import mkdtemp, gettempdir
|
2011-05-20 08:54:01 -07:00
|
|
|
import manifestparser
|
2011-06-21 05:12:40 -07:00
|
|
|
import mozinfo
|
2012-02-14 11:49:55 -08:00
|
|
|
import random
|
2012-09-10 11:15:00 -07:00
|
|
|
import socket
|
|
|
|
import time
|
2009-03-11 08:56:58 -07:00
|
|
|
|
2009-10-19 16:12:09 -07:00
|
|
|
from automationutils import *
|
2009-05-11 12:54:39 -07:00
|
|
|
|
2011-06-21 05:12:40 -07:00
|
|
|
#TODO: replace this with json.loads when Python 2.6 is required.
|
|
|
|
def parse_json(j):
|
|
|
|
"""
|
|
|
|
Awful hack to parse a restricted subset of JSON strings into Python dicts.
|
|
|
|
"""
|
|
|
|
return eval(j, {'true':True,'false':False,'null':None})
|
2012-09-10 11:15:00 -07:00
|
|
|
|
2010-09-10 10:20:38 -07:00
|
|
|
""" Control-C handling """
|
|
|
|
gotSIGINT = False
|
|
|
|
def markGotSIGINT(signum, stackFrame):
|
2012-09-10 11:15:00 -07:00
|
|
|
global gotSIGINT
|
2010-09-10 10:20:38 -07:00
|
|
|
gotSIGINT = True
|
|
|
|
|
2010-01-15 09:22:54 -08:00
|
|
|
class XPCShellTests(object):
|
|
|
|
|
|
|
|
log = logging.getLogger()
|
|
|
|
oldcwd = os.getcwd()
|
|
|
|
|
2011-06-21 05:12:40 -07:00
|
|
|
def __init__(self, log=sys.stdout):
|
2012-04-25 17:12:33 -07:00
|
|
|
""" Init logging and node status """
|
2011-06-21 05:12:40 -07:00
|
|
|
handler = logging.StreamHandler(log)
|
2010-01-15 09:22:54 -08:00
|
|
|
self.log.setLevel(logging.INFO)
|
|
|
|
self.log.addHandler(handler)
|
2012-04-25 17:12:33 -07:00
|
|
|
self.nodeProc = None
|
2010-01-15 09:22:54 -08:00
|
|
|
|
2010-04-01 12:34:10 -07:00
|
|
|
def buildTestList(self):
|
|
|
|
"""
|
2012-09-10 11:15:00 -07:00
|
|
|
read the xpcshell.ini manifest and set self.alltests to be
|
2011-05-20 08:54:01 -07:00
|
|
|
an array of test objects.
|
|
|
|
|
|
|
|
if we are chunking tests, it will be done here as well
|
2010-04-01 12:34:10 -07:00
|
|
|
"""
|
2011-06-21 05:12:40 -07:00
|
|
|
mp = manifestparser.TestManifest(strict=False)
|
2011-05-20 08:54:01 -07:00
|
|
|
if self.manifest is None:
|
|
|
|
for testdir in self.testdirs:
|
|
|
|
if testdir:
|
|
|
|
mp.read(os.path.join(testdir, 'xpcshell.ini'))
|
|
|
|
else:
|
|
|
|
mp.read(self.manifest)
|
2010-04-01 12:34:10 -07:00
|
|
|
self.buildTestPath()
|
|
|
|
|
2011-06-21 05:12:40 -07:00
|
|
|
self.alltests = mp.active_tests(**mozinfo.info)
|
2010-04-01 12:34:10 -07:00
|
|
|
|
|
|
|
if self.singleFile is None and self.totalChunks > 1:
|
|
|
|
self.chunkTests()
|
|
|
|
|
|
|
|
def chunkTests(self):
|
|
|
|
"""
|
|
|
|
Split the list of tests up into [totalChunks] pieces and filter the
|
|
|
|
self.alltests based on thisChunk, so we only run a subset.
|
|
|
|
"""
|
|
|
|
totalTests = 0
|
|
|
|
for dir in self.alltests:
|
|
|
|
totalTests += len(self.alltests[dir])
|
|
|
|
|
|
|
|
testsPerChunk = math.ceil(totalTests / float(self.totalChunks))
|
|
|
|
start = int(round((self.thisChunk-1) * testsPerChunk))
|
|
|
|
end = start + testsPerChunk
|
|
|
|
currentCount = 0
|
|
|
|
|
|
|
|
templist = {}
|
|
|
|
for dir in self.alltests:
|
|
|
|
startPosition = 0
|
|
|
|
dirCount = len(self.alltests[dir])
|
|
|
|
endPosition = dirCount
|
|
|
|
if currentCount < start and currentCount + dirCount >= start:
|
2012-09-10 11:15:00 -07:00
|
|
|
startPosition = int(start - currentCount)
|
2010-04-01 12:34:10 -07:00
|
|
|
if currentCount + dirCount > end:
|
|
|
|
endPosition = int(end - currentCount)
|
|
|
|
if end - currentCount < 0 or (currentCount + dirCount < start):
|
|
|
|
endPosition = 0
|
|
|
|
|
|
|
|
if startPosition is not endPosition:
|
|
|
|
templist[dir] = self.alltests[dir][startPosition:endPosition]
|
|
|
|
currentCount += dirCount
|
|
|
|
|
|
|
|
self.alltests = templist
|
2010-01-15 09:22:54 -08:00
|
|
|
|
2010-03-12 14:57:29 -08:00
|
|
|
def setAbsPath(self):
|
2010-01-15 09:22:54 -08:00
|
|
|
"""
|
2010-03-12 14:57:29 -08:00
|
|
|
Set the absolute path for xpcshell, httpdjspath and xrepath.
|
|
|
|
These 3 variables depend on input from the command line and we need to allow for absolute paths.
|
|
|
|
This function is overloaded for a remote solution as os.path* won't work remotely.
|
|
|
|
"""
|
|
|
|
self.testharnessdir = os.path.dirname(os.path.abspath(__file__))
|
2010-03-16 19:40:36 -07:00
|
|
|
self.headJSPath = self.testharnessdir.replace("\\", "/") + "/head.js"
|
2010-03-12 14:57:29 -08:00
|
|
|
self.xpcshell = os.path.abspath(self.xpcshell)
|
2010-01-15 09:22:54 -08:00
|
|
|
|
|
|
|
# we assume that httpd.js lives in components/ relative to xpcshell
|
2010-03-12 14:57:29 -08:00
|
|
|
self.httpdJSPath = os.path.join(os.path.dirname(self.xpcshell), 'components', 'httpd.js')
|
|
|
|
self.httpdJSPath = replaceBackSlashes(self.httpdJSPath)
|
2010-01-15 09:22:54 -08:00
|
|
|
|
2010-07-22 07:42:43 -07:00
|
|
|
self.httpdManifest = os.path.join(os.path.dirname(self.xpcshell), 'components', 'httpd.manifest')
|
|
|
|
self.httpdManifest = replaceBackSlashes(self.httpdManifest)
|
|
|
|
|
2010-03-12 14:57:29 -08:00
|
|
|
if self.xrePath is None:
|
|
|
|
self.xrePath = os.path.dirname(self.xpcshell)
|
|
|
|
else:
|
|
|
|
self.xrePath = os.path.abspath(self.xrePath)
|
2011-06-21 05:12:40 -07:00
|
|
|
|
|
|
|
if self.mozInfo is None:
|
|
|
|
self.mozInfo = os.path.join(self.testharnessdir, "mozinfo.json")
|
|
|
|
|
2010-03-12 14:57:29 -08:00
|
|
|
def buildEnvironment(self):
|
|
|
|
"""
|
|
|
|
Create and returns a dictionary of self.env to include all the appropriate env variables and values.
|
|
|
|
On a remote system, we overload this to set different values and are missing things like os.environ and PATH.
|
|
|
|
"""
|
|
|
|
self.env = dict(os.environ)
|
2010-01-15 09:22:54 -08:00
|
|
|
# Make assertions fatal
|
2010-03-12 14:57:29 -08:00
|
|
|
self.env["XPCOM_DEBUG_BREAK"] = "stack-and-abort"
|
2010-01-15 09:22:54 -08:00
|
|
|
# Don't launch the crash reporter client
|
2010-03-12 14:57:29 -08:00
|
|
|
self.env["MOZ_CRASHREPORTER_NO_REPORT"] = "1"
|
Rollup of bug 645263 and bug 646259: Switch to mozilla:: sync primitives. r=cjones,dbaron,doublec,ehsan src=bsmedberg
Bug 645263, part 0: Count sync primitive ctor/dtors. r=dbaron
Bug 645263, part 1: Migrate content/media to mozilla:: sync primitives. r=doublec
Bug 645263, part 2: Migrate modules/plugin to mozilla:: sync primitives. sr=bsmedberg
Bug 645263, part 3: Migrate nsComponentManagerImpl to mozilla:: sync primitives. sr=bsmedberg
Bug 645263, part 4: Migrate everything else to mozilla:: sync primitives. r=dbaron
Bug 645263, part 5: Remove nsAutoLock.*. sr=bsmedberg
Bug 645263, part 6: Make editor test be nicer to deadlock detector. r=ehsan
Bug 645263, part 7: Disable tracemalloc backtraces for xpcshell tests. r=dbaron
Bug 646259: Fix nsCacheService to use a CondVar for notifying. r=cjones
2011-03-31 21:29:02 -07:00
|
|
|
# Capturing backtraces is very slow on some platforms, and it's
|
|
|
|
# disabled by automation.py too
|
2011-10-28 15:43:49 -07:00
|
|
|
self.env["NS_TRACE_MALLOC_DISABLE_STACKS"] = "1"
|
2010-01-15 09:22:54 -08:00
|
|
|
|
|
|
|
if sys.platform == 'win32':
|
2010-03-12 14:57:29 -08:00
|
|
|
self.env["PATH"] = self.env["PATH"] + ";" + self.xrePath
|
2010-01-15 09:22:54 -08:00
|
|
|
elif sys.platform in ('os2emx', 'os2knix'):
|
2010-03-12 14:57:29 -08:00
|
|
|
os.environ["BEGINLIBPATH"] = self.xrePath + ";" + self.env["BEGINLIBPATH"]
|
2010-01-15 09:22:54 -08:00
|
|
|
os.environ["LIBPATHSTRICT"] = "T"
|
2010-07-19 21:17:45 -07:00
|
|
|
elif sys.platform == 'osx' or sys.platform == "darwin":
|
2010-03-12 14:57:29 -08:00
|
|
|
self.env["DYLD_LIBRARY_PATH"] = self.xrePath
|
2010-01-15 09:22:54 -08:00
|
|
|
else: # unix or linux?
|
2011-01-01 21:58:29 -08:00
|
|
|
if not "LD_LIBRARY_PATH" in self.env or self.env["LD_LIBRARY_PATH"] is None:
|
|
|
|
self.env["LD_LIBRARY_PATH"] = self.xrePath
|
|
|
|
else:
|
|
|
|
self.env["LD_LIBRARY_PATH"] = ":".join([self.xrePath, self.env["LD_LIBRARY_PATH"]])
|
|
|
|
|
2010-03-12 14:57:29 -08:00
|
|
|
return self.env
|
2010-01-15 09:22:54 -08:00
|
|
|
|
2010-03-12 14:57:29 -08:00
|
|
|
def buildXpcsRunArgs(self):
|
|
|
|
"""
|
|
|
|
Add arguments to run the test or make it interactive.
|
|
|
|
"""
|
|
|
|
if self.interactive:
|
|
|
|
self.xpcsRunArgs = [
|
2009-06-13 06:11:00 -07:00
|
|
|
'-e', 'print("To start the test, type |_execute_test();|.");',
|
|
|
|
'-i']
|
2010-03-12 14:57:29 -08:00
|
|
|
else:
|
2010-07-08 09:40:07 -07:00
|
|
|
self.xpcsRunArgs = ['-e', '_execute_test(); quit(0);']
|
2010-03-12 14:57:29 -08:00
|
|
|
|
|
|
|
def getPipes(self):
|
|
|
|
"""
|
|
|
|
Determine the value of the stdout and stderr for the test.
|
|
|
|
Return value is a list (pStdout, pStderr).
|
|
|
|
"""
|
2010-09-10 10:20:38 -07:00
|
|
|
if self.interactive:
|
2009-09-21 09:19:21 -07:00
|
|
|
pStdout = None
|
2009-10-19 16:12:09 -07:00
|
|
|
pStderr = None
|
2009-06-13 06:11:00 -07:00
|
|
|
else:
|
2010-03-12 14:57:29 -08:00
|
|
|
if (self.debuggerInfo and self.debuggerInfo["interactive"]):
|
2009-10-19 16:12:09 -07:00
|
|
|
pStdout = None
|
2010-01-15 09:22:54 -08:00
|
|
|
pStderr = None
|
2009-10-19 16:12:09 -07:00
|
|
|
else:
|
2010-01-15 09:22:54 -08:00
|
|
|
if sys.platform == 'os2emx':
|
|
|
|
pStdout = None
|
|
|
|
else:
|
|
|
|
pStdout = PIPE
|
|
|
|
pStderr = STDOUT
|
2010-03-12 14:57:29 -08:00
|
|
|
return pStdout, pStderr
|
2010-01-15 09:22:54 -08:00
|
|
|
|
2010-03-12 14:57:29 -08:00
|
|
|
def buildXpcsCmd(self, testdir):
|
|
|
|
"""
|
|
|
|
Load the root head.js file as the first file in our test path, before other head, test, and tail files.
|
|
|
|
On a remote system, we overload this to add additional command line arguments, so this gets overloaded.
|
|
|
|
"""
|
2010-03-16 19:40:36 -07:00
|
|
|
# - NOTE: if you rename/add any of the constants set here, update
|
|
|
|
# do_load_child_test_harness() in head.js
|
2011-07-21 23:48:01 -07:00
|
|
|
if not self.appPath:
|
|
|
|
self.appPath = self.xrePath
|
2012-05-10 10:10:14 -07:00
|
|
|
|
|
|
|
self.xpcsCmd = [
|
|
|
|
self.xpcshell,
|
|
|
|
'-g', self.xrePath,
|
|
|
|
'-a', self.appPath,
|
|
|
|
'-r', self.httpdManifest,
|
|
|
|
'-m',
|
|
|
|
'-n',
|
|
|
|
'-s',
|
|
|
|
'-e', 'const _HTTPD_JS_PATH = "%s";' % self.httpdJSPath,
|
|
|
|
'-e', 'const _HEAD_JS_PATH = "%s";' % self.headJSPath
|
|
|
|
]
|
|
|
|
|
2012-05-10 10:19:16 -07:00
|
|
|
if self.testingModulesDir:
|
2012-06-29 13:27:11 -07:00
|
|
|
# Escape backslashes in string literal.
|
|
|
|
sanitized = self.testingModulesDir.replace('\\', '\\\\')
|
2012-05-10 10:10:14 -07:00
|
|
|
self.xpcsCmd.extend([
|
|
|
|
'-e',
|
2012-06-29 13:27:11 -07:00
|
|
|
'const _TESTING_MODULES_DIR = "%s";' % sanitized
|
2012-05-10 10:10:14 -07:00
|
|
|
])
|
|
|
|
|
|
|
|
self.xpcsCmd.extend(['-f', os.path.join(self.testharnessdir, 'head.js')])
|
2010-03-12 14:57:29 -08:00
|
|
|
|
|
|
|
if self.debuggerInfo:
|
|
|
|
self.xpcsCmd = [self.debuggerInfo["path"]] + self.debuggerInfo["args"] + self.xpcsCmd
|
2010-01-15 09:22:54 -08:00
|
|
|
|
2010-03-12 14:57:29 -08:00
|
|
|
def buildTestPath(self):
|
|
|
|
"""
|
|
|
|
If we specifiy a testpath, set the self.testPath variable to be the given directory or file.
|
2010-01-15 09:22:54 -08:00
|
|
|
|
2010-03-12 14:57:29 -08:00
|
|
|
|testPath| will be the optional path only, or |None|.
|
|
|
|
|singleFile| will be the optional test only, or |None|.
|
|
|
|
"""
|
|
|
|
self.singleFile = None
|
2010-04-01 12:34:10 -07:00
|
|
|
if self.testPath is not None:
|
2010-03-12 14:57:29 -08:00
|
|
|
if self.testPath.endswith('.js'):
|
2010-01-15 09:22:54 -08:00
|
|
|
# Split into path and file.
|
2010-03-12 14:57:29 -08:00
|
|
|
if self.testPath.find('/') == -1:
|
2010-01-15 09:22:54 -08:00
|
|
|
# Test only.
|
2010-03-12 14:57:29 -08:00
|
|
|
self.singleFile = self.testPath
|
2010-01-15 09:22:54 -08:00
|
|
|
else:
|
|
|
|
# Both path and test.
|
|
|
|
# Reuse |testPath| temporarily.
|
2010-03-12 14:57:29 -08:00
|
|
|
self.testPath = self.testPath.rsplit('/', 1)
|
|
|
|
self.singleFile = self.testPath[1]
|
|
|
|
self.testPath = self.testPath[0]
|
2009-04-27 16:37:02 -07:00
|
|
|
else:
|
2010-01-15 09:22:54 -08:00
|
|
|
# Path only.
|
|
|
|
# Simply remove optional ending separator.
|
2010-03-12 14:57:29 -08:00
|
|
|
self.testPath = self.testPath.rstrip("/")
|
|
|
|
|
2012-03-28 16:05:22 -07:00
|
|
|
def getHeadAndTailFiles(self, test):
|
|
|
|
"""Obtain the list of head and tail files.
|
2010-03-12 14:57:29 -08:00
|
|
|
|
2012-03-28 16:05:22 -07:00
|
|
|
Returns a 2-tuple. The first element is a list of head files. The second
|
|
|
|
is a list of tail files.
|
|
|
|
"""
|
|
|
|
def sanitize_list(s, kind):
|
|
|
|
for f in s.strip().split(' '):
|
|
|
|
f = f.strip()
|
|
|
|
if len(f) < 1:
|
|
|
|
continue
|
2010-03-12 14:57:29 -08:00
|
|
|
|
2012-03-28 16:05:22 -07:00
|
|
|
path = os.path.normpath(os.path.join(test['here'], f))
|
|
|
|
if not os.path.exists(path):
|
|
|
|
raise Exception('%s file does not exist: %s' % (kind, path))
|
2010-03-12 14:57:29 -08:00
|
|
|
|
2012-03-28 16:05:22 -07:00
|
|
|
if not os.path.isfile(path):
|
|
|
|
raise Exception('%s file is not a file: %s' % (kind, path))
|
2010-03-12 14:57:29 -08:00
|
|
|
|
2012-03-28 16:05:22 -07:00
|
|
|
yield path
|
|
|
|
|
|
|
|
return (list(sanitize_list(test['head'], 'head')),
|
|
|
|
list(sanitize_list(test['tail'], 'tail')))
|
2010-03-12 14:57:29 -08:00
|
|
|
|
|
|
|
def setupProfileDir(self):
|
|
|
|
"""
|
|
|
|
Create a temporary folder for the profile and set appropriate environment variables.
|
2010-04-27 10:28:56 -07:00
|
|
|
When running check-interactive and check-one, the directory is well-defined and
|
|
|
|
retained for inspection once the tests complete.
|
2010-03-12 14:57:29 -08:00
|
|
|
|
2011-08-22 01:00:50 -07:00
|
|
|
On a remote system, this may be overloaded to use a remote path structure.
|
2010-03-12 14:57:29 -08:00
|
|
|
"""
|
2010-04-27 10:28:56 -07:00
|
|
|
if self.interactive or self.singleFile:
|
|
|
|
profileDir = os.path.join(gettempdir(), self.profileName, "xpcshellprofile")
|
|
|
|
try:
|
|
|
|
# This could be left over from previous runs
|
|
|
|
self.removeDir(profileDir)
|
|
|
|
except:
|
|
|
|
pass
|
|
|
|
os.makedirs(profileDir)
|
|
|
|
else:
|
|
|
|
profileDir = mkdtemp()
|
2010-03-12 14:57:29 -08:00
|
|
|
self.env["XPCSHELL_TEST_PROFILE_DIR"] = profileDir
|
2010-04-27 10:28:56 -07:00
|
|
|
if self.interactive or self.singleFile:
|
2011-06-21 05:12:40 -07:00
|
|
|
self.log.info("TEST-INFO | profile dir is %s" % profileDir)
|
2010-03-12 14:57:29 -08:00
|
|
|
return profileDir
|
|
|
|
|
|
|
|
def setupLeakLogging(self):
|
|
|
|
"""
|
|
|
|
Enable leaks (only) detection to its own log file and set environment variables.
|
|
|
|
|
2011-08-22 01:00:50 -07:00
|
|
|
On a remote system, this may be overloaded to use a remote filename and path structure
|
2010-03-12 14:57:29 -08:00
|
|
|
"""
|
|
|
|
filename = "runxpcshelltests_leaks.log"
|
|
|
|
|
|
|
|
leakLogFile = os.path.join(self.profileDir, filename)
|
|
|
|
self.env["XPCOM_MEM_LEAK_LOG"] = leakLogFile
|
|
|
|
return leakLogFile
|
|
|
|
|
|
|
|
def launchProcess(self, cmd, stdout, stderr, env, cwd):
|
|
|
|
"""
|
|
|
|
Simple wrapper to launch a process.
|
|
|
|
On a remote system, this is more complex and we need to overload this function.
|
|
|
|
"""
|
2010-09-23 09:19:31 -07:00
|
|
|
cmd = wrapCommand(cmd)
|
2012-09-10 11:15:00 -07:00
|
|
|
proc = Popen(cmd, stdout=stdout, stderr=stderr,
|
2010-03-12 14:57:29 -08:00
|
|
|
env=env, cwd=cwd)
|
|
|
|
return proc
|
|
|
|
|
|
|
|
def communicate(self, proc):
|
|
|
|
"""
|
|
|
|
Simple wrapper to communicate with a process.
|
|
|
|
On a remote system, this is overloaded to handle remote process communication.
|
|
|
|
"""
|
|
|
|
return proc.communicate()
|
2012-09-10 11:15:00 -07:00
|
|
|
|
2012-10-07 13:21:30 -07:00
|
|
|
def poll(self, proc):
|
|
|
|
"""
|
|
|
|
Simple wrapper to check if a process has terminated.
|
|
|
|
On a remote system, this is overloaded to handle remote process communication.
|
|
|
|
"""
|
|
|
|
return proc.poll()
|
|
|
|
|
|
|
|
def kill(self, proc):
|
|
|
|
"""
|
|
|
|
Simple wrapper to kill a process.
|
|
|
|
On a remote system, this is overloaded to handle remote process communication.
|
|
|
|
"""
|
|
|
|
return proc.kill()
|
|
|
|
|
2010-03-12 14:57:29 -08:00
|
|
|
def removeDir(self, dirname):
|
|
|
|
"""
|
|
|
|
Simple wrapper to remove (recursively) a given directory.
|
|
|
|
On a remote system, we need to overload this to work on the remote filesystem.
|
|
|
|
"""
|
|
|
|
shutil.rmtree(dirname)
|
|
|
|
|
|
|
|
def verifyDirPath(self, dirname):
|
|
|
|
"""
|
|
|
|
Simple wrapper to get the absolute path for a given directory name.
|
|
|
|
On a remote system, we need to overload this to work on the remote filesystem.
|
|
|
|
"""
|
|
|
|
return os.path.abspath(dirname)
|
2012-09-10 11:15:00 -07:00
|
|
|
|
2010-03-12 14:57:29 -08:00
|
|
|
def getReturnCode(self, proc):
|
|
|
|
"""
|
|
|
|
Simple wrapper to get the return code for a given process.
|
|
|
|
On a remote system we overload this to work with the remote process management.
|
|
|
|
"""
|
|
|
|
return proc.returncode
|
|
|
|
|
2009-12-03 17:12:33 -08:00
|
|
|
def createLogFile(self, test, stdout, leakLogs):
|
2010-03-12 14:57:29 -08:00
|
|
|
"""
|
|
|
|
For a given test and stdout buffer, create a log file. also log any found leaks.
|
|
|
|
On a remote system we have to fix the test name since it can contain directories.
|
|
|
|
"""
|
|
|
|
try:
|
|
|
|
f = open(test + ".log", "w")
|
|
|
|
f.write(stdout)
|
|
|
|
|
2012-09-10 11:15:00 -07:00
|
|
|
for leakLog in leakLogs:
|
2009-12-03 17:12:33 -08:00
|
|
|
if os.path.exists(leakLog):
|
|
|
|
leaks = open(leakLog, "r")
|
|
|
|
f.write(leaks.read())
|
|
|
|
leaks.close()
|
2010-03-12 14:57:29 -08:00
|
|
|
finally:
|
|
|
|
if f:
|
|
|
|
f.close()
|
|
|
|
|
|
|
|
def buildCmdHead(self, headfiles, tailfiles, xpcscmd):
|
|
|
|
"""
|
|
|
|
Build the command line arguments for the head and tail files,
|
|
|
|
along with the address of the webserver which some tests require.
|
|
|
|
|
|
|
|
On a remote system, this is overloaded to resolve quoting issues over a secondary command line.
|
|
|
|
"""
|
|
|
|
cmdH = ", ".join(['"' + replaceBackSlashes(f) + '"'
|
|
|
|
for f in headfiles])
|
|
|
|
cmdT = ", ".join(['"' + replaceBackSlashes(f) + '"'
|
|
|
|
for f in tailfiles])
|
|
|
|
return xpcscmd + \
|
|
|
|
['-e', 'const _SERVER_ADDR = "localhost"',
|
|
|
|
'-e', 'const _HEAD_FILES = [%s];' % cmdH,
|
|
|
|
'-e', 'const _TAIL_FILES = [%s];' % cmdT]
|
|
|
|
|
2011-08-22 01:00:50 -07:00
|
|
|
def buildCmdTestFile(self, name):
|
|
|
|
"""
|
|
|
|
Build the command line arguments for the test file.
|
|
|
|
On a remote system, this may be overloaded to use a remote path structure.
|
|
|
|
"""
|
|
|
|
return ['-e', 'const _TEST_FILE = ["%s"];' %
|
|
|
|
replaceBackSlashes(name)]
|
|
|
|
|
2012-04-25 17:12:33 -07:00
|
|
|
def trySetupNode(self):
|
|
|
|
"""
|
|
|
|
Run node for SPDY tests, if available, and updates mozinfo as appropriate.
|
|
|
|
"""
|
|
|
|
nodeMozInfo = {'hasNode': False} # Assume the worst
|
|
|
|
nodeBin = None
|
|
|
|
|
|
|
|
# We try to find the node executable in the path given to us by the user in
|
|
|
|
# the MOZ_NODE_PATH environment variable
|
|
|
|
localPath = os.getenv('MOZ_NODE_PATH', None)
|
|
|
|
if localPath and os.path.exists(localPath) and os.path.isfile(localPath):
|
|
|
|
nodeBin = localPath
|
|
|
|
|
|
|
|
if nodeBin:
|
|
|
|
self.log.info('Found node at %s' % (nodeBin,))
|
|
|
|
myDir = os.path.split(os.path.abspath(__file__))[0]
|
|
|
|
mozSpdyJs = os.path.join(myDir, 'moz-spdy', 'moz-spdy.js')
|
|
|
|
|
|
|
|
if os.path.exists(mozSpdyJs):
|
|
|
|
# OK, we found our SPDY server, let's try to get it running
|
|
|
|
self.log.info('Found moz-spdy at %s' % (mozSpdyJs,))
|
|
|
|
stdout, stderr = self.getPipes()
|
|
|
|
try:
|
|
|
|
# We pipe stdin to node because the spdy server will exit when its
|
|
|
|
# stdin reaches EOF
|
|
|
|
self.nodeProc = Popen([nodeBin, mozSpdyJs], stdin=PIPE, stdout=PIPE,
|
|
|
|
stderr=STDOUT, env=self.env, cwd=os.getcwd())
|
|
|
|
|
|
|
|
# Check to make sure the server starts properly by waiting for it to
|
|
|
|
# tell us it's started
|
|
|
|
msg = self.nodeProc.stdout.readline()
|
|
|
|
if msg.startswith('SPDY server listening'):
|
|
|
|
nodeMozInfo['hasNode'] = True
|
|
|
|
except OSError, e:
|
|
|
|
# This occurs if the subprocess couldn't be started
|
|
|
|
self.log.error('Could not run node SPDY server: %s' % (str(e),))
|
|
|
|
|
|
|
|
mozinfo.update(nodeMozInfo)
|
|
|
|
|
|
|
|
def shutdownNode(self):
|
|
|
|
"""
|
|
|
|
Shut down our node process, if it exists
|
|
|
|
"""
|
|
|
|
if self.nodeProc:
|
|
|
|
self.log.info('Node SPDY server shutting down ...')
|
|
|
|
# moz-spdy exits when its stdin reaches EOF, so force that to happen here
|
|
|
|
self.nodeProc.communicate()
|
|
|
|
|
2012-02-27 19:53:00 -08:00
|
|
|
def writeXunitResults(self, results, name=None, filename=None, fh=None):
|
|
|
|
"""
|
|
|
|
Write Xunit XML from results.
|
|
|
|
|
|
|
|
The function receives an iterable of results dicts. Each dict must have
|
|
|
|
the following keys:
|
|
|
|
|
|
|
|
classname - The "class" name of the test.
|
|
|
|
name - The simple name of the test.
|
|
|
|
|
|
|
|
In addition, it must have one of the following saying how the test
|
|
|
|
executed:
|
|
|
|
|
|
|
|
passed - Boolean indicating whether the test passed. False if it
|
|
|
|
failed.
|
|
|
|
skipped - True if the test was skipped.
|
|
|
|
|
|
|
|
The following keys are optional:
|
|
|
|
|
|
|
|
time - Execution time of the test in decimal seconds.
|
|
|
|
failure - Dict describing test failure. Requires keys:
|
|
|
|
type - String type of failure.
|
|
|
|
message - String describing basic failure.
|
|
|
|
text - Verbose string describing failure.
|
|
|
|
|
|
|
|
Arguments:
|
|
|
|
|
|
|
|
|name|, Name of the test suite. Many tools expect Java class dot notation
|
|
|
|
e.g. dom.simple.foo. A directory with '/' converted to '.' is a good
|
|
|
|
choice.
|
|
|
|
|fh|, File handle to write XML to.
|
|
|
|
|filename|, File name to write XML to.
|
|
|
|
|results|, Iterable of tuples describing the results.
|
|
|
|
"""
|
|
|
|
if filename is None and fh is None:
|
|
|
|
raise Exception("One of filename or fh must be defined.")
|
|
|
|
|
|
|
|
if name is None:
|
|
|
|
name = "xpcshell"
|
|
|
|
else:
|
2012-10-31 10:29:26 -07:00
|
|
|
assert isinstance(name, basestring)
|
2012-02-27 19:53:00 -08:00
|
|
|
|
|
|
|
if filename is not None:
|
|
|
|
fh = open(filename, 'wb')
|
|
|
|
|
|
|
|
doc = xml.dom.minidom.Document()
|
|
|
|
testsuite = doc.createElement("testsuite")
|
|
|
|
testsuite.setAttribute("name", name)
|
|
|
|
doc.appendChild(testsuite)
|
|
|
|
|
|
|
|
total = 0
|
|
|
|
passed = 0
|
|
|
|
failed = 0
|
|
|
|
skipped = 0
|
|
|
|
|
|
|
|
for result in results:
|
|
|
|
total += 1
|
|
|
|
|
|
|
|
if result.get("skipped", None):
|
|
|
|
skipped += 1
|
|
|
|
elif result["passed"]:
|
|
|
|
passed += 1
|
|
|
|
else:
|
|
|
|
failed += 1
|
|
|
|
|
|
|
|
testcase = doc.createElement("testcase")
|
|
|
|
testcase.setAttribute("classname", result["classname"])
|
|
|
|
testcase.setAttribute("name", result["name"])
|
|
|
|
|
|
|
|
if "time" in result:
|
|
|
|
testcase.setAttribute("time", str(result["time"]))
|
|
|
|
else:
|
|
|
|
# It appears most tools expect the time attribute to be present.
|
|
|
|
testcase.setAttribute("time", "0")
|
|
|
|
|
|
|
|
if "failure" in result:
|
|
|
|
failure = doc.createElement("failure")
|
|
|
|
failure.setAttribute("type", str(result["failure"]["type"]))
|
|
|
|
failure.setAttribute("message", result["failure"]["message"])
|
|
|
|
|
|
|
|
# Lossy translation but required to not break CDATA. Also, text could
|
|
|
|
# be None and Python 2.5's minidom doesn't accept None. Later versions
|
|
|
|
# do, however.
|
|
|
|
cdata = result["failure"]["text"]
|
|
|
|
if not isinstance(cdata, str):
|
|
|
|
cdata = ""
|
|
|
|
|
|
|
|
cdata = cdata.replace("]]>", "]] >")
|
|
|
|
text = doc.createCDATASection(cdata)
|
|
|
|
failure.appendChild(text)
|
|
|
|
testcase.appendChild(failure)
|
|
|
|
|
|
|
|
if result.get("skipped", None):
|
|
|
|
e = doc.createElement("skipped")
|
|
|
|
testcase.appendChild(e)
|
|
|
|
|
|
|
|
testsuite.appendChild(testcase)
|
|
|
|
|
|
|
|
testsuite.setAttribute("tests", str(total))
|
|
|
|
testsuite.setAttribute("failures", str(failed))
|
|
|
|
testsuite.setAttribute("skip", str(skipped))
|
|
|
|
|
|
|
|
doc.writexml(fh, addindent=" ", newl="\n", encoding="utf-8")
|
|
|
|
|
2012-09-10 11:15:00 -07:00
|
|
|
def post_to_autolog(self, results, name):
|
|
|
|
from moztest.results import TestContext, TestResult, TestResultCollection
|
|
|
|
from moztest.output.autolog import AutologOutput
|
|
|
|
|
|
|
|
context = TestContext(
|
|
|
|
testgroup='b2g xpcshell testsuite',
|
|
|
|
operating_system='android',
|
|
|
|
arch='emulator',
|
|
|
|
harness='xpcshell',
|
|
|
|
hostname=socket.gethostname(),
|
|
|
|
tree='b2g',
|
|
|
|
buildtype='opt',
|
|
|
|
)
|
|
|
|
|
|
|
|
collection = TestResultCollection('b2g emulator testsuite')
|
|
|
|
|
|
|
|
for result in results:
|
|
|
|
duration = result.get('time', 0)
|
|
|
|
|
|
|
|
if 'skipped' in result:
|
|
|
|
outcome = 'SKIPPED'
|
|
|
|
elif 'todo' in result:
|
|
|
|
outcome = 'KNOWN-FAIL'
|
|
|
|
elif result['passed']:
|
|
|
|
outcome = 'PASS'
|
|
|
|
else:
|
|
|
|
outcome = 'UNEXPECTED-FAIL'
|
|
|
|
|
|
|
|
output = None
|
|
|
|
if 'failure' in result:
|
|
|
|
output = result['failure']['text']
|
|
|
|
|
|
|
|
t = TestResult(name=result['name'], test_class=name,
|
|
|
|
time_start=0, context=context)
|
|
|
|
t.finish(result=outcome, time_end=duration, output=output)
|
|
|
|
|
|
|
|
collection.append(t)
|
|
|
|
collection.time_taken += duration
|
|
|
|
|
|
|
|
out = AutologOutput()
|
|
|
|
out.post(out.make_testgroups(collection))
|
|
|
|
|
2011-07-21 23:48:01 -07:00
|
|
|
def runTests(self, xpcshell, xrePath=None, appPath=None, symbolsPath=None,
|
2012-02-27 19:53:00 -08:00
|
|
|
manifest=None, testdirs=None, testPath=None,
|
2010-09-10 10:20:38 -07:00
|
|
|
interactive=False, verbose=False, keepGoing=False, logfiles=True,
|
2010-04-01 12:34:10 -07:00
|
|
|
thisChunk=1, totalChunks=1, debugger=None,
|
2010-04-27 10:28:56 -07:00
|
|
|
debuggerArgs=None, debuggerInteractive=False,
|
2012-02-27 19:53:00 -08:00
|
|
|
profileName=None, mozInfo=None, shuffle=False,
|
2012-03-06 15:03:34 -08:00
|
|
|
testsRootDir=None, xunitFilename=None, xunitName=None,
|
2012-09-10 11:15:00 -07:00
|
|
|
testingModulesDir=None, autolog=False, **otherOptions):
|
2010-03-12 14:57:29 -08:00
|
|
|
"""Run xpcshell tests.
|
|
|
|
|
|
|
|
|xpcshell|, is the xpcshell executable to use to run the tests.
|
|
|
|
|xrePath|, if provided, is the path to the XRE to use.
|
2011-07-21 23:48:01 -07:00
|
|
|
|appPath|, if provided, is the path to an application directory.
|
2010-03-12 14:57:29 -08:00
|
|
|
|symbolsPath|, if provided is the path to a directory containing
|
|
|
|
breakpad symbols for processing crashes in tests.
|
|
|
|
|manifest|, if provided, is a file containing a list of
|
|
|
|
test directories to run.
|
|
|
|
|testdirs|, if provided, is a list of absolute paths of test directories.
|
|
|
|
No-manifest only option.
|
|
|
|
|testPath|, if provided, indicates a single path and/or test to run.
|
|
|
|
|interactive|, if set to True, indicates to provide an xpcshell prompt
|
|
|
|
instead of automatically executing the test.
|
2010-02-10 14:52:16 -08:00
|
|
|
|verbose|, if set to True, will cause stdout/stderr from tests to
|
|
|
|
be printed always
|
2010-03-12 14:57:29 -08:00
|
|
|
|logfiles|, if set to False, indicates not to save output to log files.
|
|
|
|
Non-interactive only option.
|
|
|
|
|debuggerInfo|, if set, specifies the debugger and debugger arguments
|
|
|
|
that will be used to launch xpcshell.
|
2010-04-27 10:28:56 -07:00
|
|
|
|profileName|, if set, specifies the name of the application for the profile
|
2011-06-21 05:12:40 -07:00
|
|
|
directory if running only a subset of tests.
|
|
|
|
|mozInfo|, if set, specifies specifies build configuration information, either as a filename containing JSON, or a dict.
|
2012-02-14 11:49:55 -08:00
|
|
|
|shuffle|, if True, execute tests in random order.
|
2012-03-06 15:03:34 -08:00
|
|
|
|testsRootDir|, absolute path to root directory of all tests. This is used
|
|
|
|
by xUnit generation to determine the package name of the tests.
|
2012-02-27 19:53:00 -08:00
|
|
|
|xunitFilename|, if set, specifies the filename to which to write xUnit XML
|
|
|
|
results.
|
|
|
|
|xunitName|, if outputting an xUnit XML file, the str value to use for the
|
|
|
|
testsuite name.
|
2012-05-10 10:10:14 -07:00
|
|
|
|testingModulesDir|, if provided, specifies where JS modules reside.
|
|
|
|
xpcshell will register a resource handler mapping this path.
|
2011-08-22 01:00:50 -07:00
|
|
|
|otherOptions| may be present for the convenience of subclasses
|
2010-03-12 14:57:29 -08:00
|
|
|
"""
|
|
|
|
|
2012-02-27 19:53:00 -08:00
|
|
|
global gotSIGINT
|
|
|
|
|
|
|
|
if testdirs is None:
|
|
|
|
testdirs = []
|
2010-09-10 10:20:38 -07:00
|
|
|
|
2012-03-06 15:03:34 -08:00
|
|
|
if xunitFilename is not None or xunitName is not None:
|
2012-10-31 10:29:26 -07:00
|
|
|
if not isinstance(testsRootDir, basestring):
|
2012-03-06 15:03:34 -08:00
|
|
|
raise Exception("testsRootDir must be a str when outputting xUnit.")
|
|
|
|
|
|
|
|
if not os.path.isabs(testsRootDir):
|
|
|
|
testsRootDir = os.path.abspath(testsRootDir)
|
|
|
|
|
|
|
|
if not os.path.exists(testsRootDir):
|
|
|
|
raise Exception("testsRootDir path does not exists: %s" %
|
|
|
|
testsRootDir)
|
|
|
|
|
2012-06-29 13:27:11 -07:00
|
|
|
# Try to guess modules directory.
|
|
|
|
# This somewhat grotesque hack allows the buildbot machines to find the
|
|
|
|
# modules directory without having to configure the buildbot hosts. This
|
|
|
|
# code path should never be executed in local runs because the build system
|
|
|
|
# should always set this argument.
|
|
|
|
if not testingModulesDir:
|
|
|
|
ourDir = os.path.dirname(__file__)
|
|
|
|
possible = os.path.join(ourDir, os.path.pardir, 'modules')
|
|
|
|
|
|
|
|
if os.path.isdir(possible):
|
|
|
|
testingModulesDir = possible
|
|
|
|
|
2012-05-10 10:19:16 -07:00
|
|
|
if testingModulesDir:
|
2012-06-29 13:27:11 -07:00
|
|
|
# The resource loader expects native paths. Depending on how we were
|
|
|
|
# invoked, a UNIX style path may sneak in on Windows. We try to
|
|
|
|
# normalize that.
|
|
|
|
testingModulesDir = os.path.normpath(testingModulesDir)
|
|
|
|
|
2012-05-10 10:10:14 -07:00
|
|
|
if not os.path.isabs(testingModulesDir):
|
|
|
|
testingModulesDir = os.path.abspath(testingModulesDir)
|
|
|
|
|
2012-06-29 13:27:11 -07:00
|
|
|
if not testingModulesDir.endswith(os.path.sep):
|
|
|
|
testingModulesDir += os.path.sep
|
|
|
|
|
2010-03-12 14:57:29 -08:00
|
|
|
self.xpcshell = xpcshell
|
|
|
|
self.xrePath = xrePath
|
2011-07-21 23:48:01 -07:00
|
|
|
self.appPath = appPath
|
2010-03-12 14:57:29 -08:00
|
|
|
self.symbolsPath = symbolsPath
|
|
|
|
self.manifest = manifest
|
|
|
|
self.testdirs = testdirs
|
|
|
|
self.testPath = testPath
|
|
|
|
self.interactive = interactive
|
2010-02-10 14:52:16 -08:00
|
|
|
self.verbose = verbose
|
2010-09-10 10:20:38 -07:00
|
|
|
self.keepGoing = keepGoing
|
2010-03-12 14:57:29 -08:00
|
|
|
self.logfiles = logfiles
|
2010-04-01 12:34:10 -07:00
|
|
|
self.totalChunks = totalChunks
|
|
|
|
self.thisChunk = thisChunk
|
|
|
|
self.debuggerInfo = getDebuggerInfo(self.oldcwd, debugger, debuggerArgs, debuggerInteractive)
|
2010-04-27 10:28:56 -07:00
|
|
|
self.profileName = profileName or "xpcshell"
|
2011-06-21 05:12:40 -07:00
|
|
|
self.mozInfo = mozInfo
|
2012-05-10 10:10:14 -07:00
|
|
|
self.testingModulesDir = testingModulesDir
|
2010-03-12 14:57:29 -08:00
|
|
|
|
2010-08-31 18:03:38 -07:00
|
|
|
# If we have an interactive debugger, disable ctrl-c.
|
|
|
|
if self.debuggerInfo and self.debuggerInfo["interactive"]:
|
|
|
|
signal.signal(signal.SIGINT, lambda signum, frame: None)
|
|
|
|
|
2010-03-12 14:57:29 -08:00
|
|
|
if not testdirs and not manifest:
|
|
|
|
# nothing to test!
|
2011-06-21 05:12:40 -07:00
|
|
|
self.log.error("Error: No test dirs or test manifest specified!")
|
2010-03-12 14:57:29 -08:00
|
|
|
return False
|
|
|
|
|
2011-06-21 05:12:40 -07:00
|
|
|
self.testCount = 0
|
|
|
|
self.passCount = 0
|
|
|
|
self.failCount = 0
|
|
|
|
self.todoCount = 0
|
2010-03-12 14:57:29 -08:00
|
|
|
|
|
|
|
self.setAbsPath()
|
|
|
|
self.buildXpcsRunArgs()
|
|
|
|
self.buildEnvironment()
|
2011-06-21 05:12:40 -07:00
|
|
|
|
|
|
|
# Handle filenames in mozInfo
|
|
|
|
if not isinstance(self.mozInfo, dict):
|
|
|
|
mozInfoFile = self.mozInfo
|
|
|
|
if not os.path.isfile(mozInfoFile):
|
|
|
|
self.log.error("Error: couldn't find mozinfo.json at '%s'. Perhaps you need to use --build-info-json?" % mozInfoFile)
|
|
|
|
return False
|
|
|
|
self.mozInfo = parse_json(open(mozInfoFile).read())
|
|
|
|
mozinfo.update(self.mozInfo)
|
2012-04-25 17:12:33 -07:00
|
|
|
|
|
|
|
# We have to do this before we build the test list so we know whether or
|
|
|
|
# not to run tests that depend on having the node spdy server
|
|
|
|
self.trySetupNode()
|
2012-09-10 11:15:00 -07:00
|
|
|
|
2010-03-12 14:57:29 -08:00
|
|
|
pStdout, pStderr = self.getPipes()
|
2010-01-15 09:22:54 -08:00
|
|
|
|
2010-04-01 12:34:10 -07:00
|
|
|
self.buildTestList()
|
2010-03-12 14:57:29 -08:00
|
|
|
|
2012-02-14 11:49:55 -08:00
|
|
|
if shuffle:
|
|
|
|
random.shuffle(self.alltests)
|
|
|
|
|
2012-02-27 19:53:00 -08:00
|
|
|
xunitResults = []
|
|
|
|
|
2011-05-20 08:54:01 -07:00
|
|
|
for test in self.alltests:
|
|
|
|
name = test['path']
|
|
|
|
if self.singleFile and not name.endswith(self.singleFile):
|
2009-03-11 08:56:58 -07:00
|
|
|
continue
|
2009-03-27 10:58:11 -07:00
|
|
|
|
2011-05-20 08:54:01 -07:00
|
|
|
if self.testPath and name.find(self.testPath) == -1:
|
|
|
|
continue
|
|
|
|
|
2011-06-21 05:12:40 -07:00
|
|
|
self.testCount += 1
|
|
|
|
|
2012-03-06 15:03:34 -08:00
|
|
|
xunitResult = {"name": test["name"], "classname": "xpcshell"}
|
|
|
|
# The xUnit package is defined as the path component between the root
|
|
|
|
# dir and the test with path characters replaced with '.' (using Java
|
|
|
|
# class notation).
|
|
|
|
if testsRootDir is not None:
|
2012-06-29 13:27:11 -07:00
|
|
|
testsRootDir = os.path.normpath(testsRootDir)
|
2012-03-06 15:03:34 -08:00
|
|
|
if test["here"].find(testsRootDir) != 0:
|
|
|
|
raise Exception("testsRootDir is not a parent path of %s" %
|
|
|
|
test["here"])
|
|
|
|
relpath = test["here"][len(testsRootDir):].lstrip("/\\")
|
|
|
|
xunitResult["classname"] = relpath.replace("/", ".").replace("\\", ".")
|
2012-02-27 19:53:00 -08:00
|
|
|
|
2011-06-21 05:12:40 -07:00
|
|
|
# Check for skipped tests
|
|
|
|
if 'disabled' in test:
|
|
|
|
self.log.info("TEST-INFO | skipping %s | %s" %
|
|
|
|
(name, test['disabled']))
|
2012-02-27 19:53:00 -08:00
|
|
|
|
|
|
|
xunitResult["skipped"] = True
|
|
|
|
xunitResults.append(xunitResult)
|
2011-06-21 05:12:40 -07:00
|
|
|
continue
|
2012-02-27 19:53:00 -08:00
|
|
|
|
2011-06-21 05:12:40 -07:00
|
|
|
# Check for known-fail tests
|
|
|
|
expected = test['expected'] == 'pass'
|
|
|
|
|
2011-05-20 08:54:01 -07:00
|
|
|
testdir = os.path.dirname(name)
|
2010-04-01 12:34:10 -07:00
|
|
|
self.buildXpcsCmd(testdir)
|
2012-03-28 16:05:22 -07:00
|
|
|
testHeadFiles, testTailFiles = self.getHeadAndTailFiles(test)
|
2010-03-12 14:57:29 -08:00
|
|
|
cmdH = self.buildCmdHead(testHeadFiles, testTailFiles, self.xpcsCmd)
|
2010-01-15 09:22:54 -08:00
|
|
|
|
2011-05-20 08:54:01 -07:00
|
|
|
# create a temp dir that the JS harness can stick a profile in
|
|
|
|
self.profileDir = self.setupProfileDir()
|
|
|
|
self.leakLogFile = self.setupLeakLogging()
|
|
|
|
|
|
|
|
# The test file will have to be loaded after the head files.
|
2011-08-22 01:00:50 -07:00
|
|
|
cmdT = self.buildCmdTestFile(name)
|
2011-05-20 08:54:01 -07:00
|
|
|
|
2012-05-03 12:32:31 -07:00
|
|
|
args = self.xpcsRunArgs[:]
|
2011-10-10 15:06:28 -07:00
|
|
|
if 'debug' in test:
|
|
|
|
args.insert(0, '-d')
|
|
|
|
|
2012-04-16 14:19:07 -07:00
|
|
|
completeCmd = cmdH + cmdT + args
|
|
|
|
|
2012-10-16 13:32:33 -07:00
|
|
|
proc = None
|
2011-05-20 08:54:01 -07:00
|
|
|
try:
|
2011-06-21 05:12:40 -07:00
|
|
|
self.log.info("TEST-INFO | %s | running test ..." % name)
|
2012-04-16 14:19:07 -07:00
|
|
|
if verbose:
|
|
|
|
self.log.info("TEST-INFO | %s | full command: %r" % (name, completeCmd))
|
|
|
|
self.log.info("TEST-INFO | %s | current directory: %r" % (name, testdir))
|
|
|
|
# Show only those environment variables that are changed from
|
|
|
|
# the ambient environment.
|
|
|
|
changedEnv = (set("%s=%s" % i for i in self.env.iteritems())
|
|
|
|
- set("%s=%s" % i for i in os.environ.iteritems()))
|
|
|
|
self.log.info("TEST-INFO | %s | environment: %s" % (name, list(changedEnv)))
|
2011-08-10 11:34:14 -07:00
|
|
|
startTime = time.time()
|
2011-05-20 08:54:01 -07:00
|
|
|
|
2012-04-16 14:19:07 -07:00
|
|
|
proc = self.launchProcess(completeCmd,
|
2011-05-20 08:54:01 -07:00
|
|
|
stdout=pStdout, stderr=pStderr, env=self.env, cwd=testdir)
|
|
|
|
|
2012-08-13 10:19:11 -07:00
|
|
|
if interactive:
|
|
|
|
self.log.info("TEST-INFO | %s | Process ID: %d" % (name, proc.pid))
|
|
|
|
|
2011-05-20 08:54:01 -07:00
|
|
|
# Allow user to kill hung subprocess with SIGINT w/o killing this script
|
|
|
|
# - don't move this line above launchProcess, or child will inherit the SIG_IGN
|
|
|
|
signal.signal(signal.SIGINT, markGotSIGINT)
|
|
|
|
# |stderr == None| as |pStderr| was either |None| or redirected to |stdout|.
|
|
|
|
stdout, stderr = self.communicate(proc)
|
|
|
|
signal.signal(signal.SIGINT, signal.SIG_DFL)
|
|
|
|
|
|
|
|
if interactive:
|
|
|
|
# Not sure what else to do here...
|
|
|
|
return True
|
|
|
|
|
|
|
|
def print_stdout(stdout):
|
|
|
|
"""Print stdout line-by-line to avoid overflowing buffers."""
|
2011-06-21 05:12:40 -07:00
|
|
|
self.log.info(">>>>>>>")
|
2011-08-22 01:00:50 -07:00
|
|
|
if (stdout):
|
|
|
|
for line in stdout.splitlines():
|
|
|
|
self.log.info(line)
|
2011-06-21 05:12:40 -07:00
|
|
|
self.log.info("<<<<<<<")
|
|
|
|
|
|
|
|
result = not ((self.getReturnCode(proc) != 0) or
|
2012-03-08 15:00:05 -08:00
|
|
|
# if do_throw or do_check failed
|
2011-06-21 05:12:40 -07:00
|
|
|
(stdout and re.search("^((parent|child): )?TEST-UNEXPECTED-",
|
|
|
|
stdout, re.MULTILINE)) or
|
2012-03-08 15:00:05 -08:00
|
|
|
# if syntax error in xpcshell file
|
2011-06-21 05:12:40 -07:00
|
|
|
(stdout and re.search(": SyntaxError:", stdout,
|
2012-03-08 15:00:05 -08:00
|
|
|
re.MULTILINE)) or
|
|
|
|
# if e10s test started but never finished (child process crash)
|
2012-09-10 11:15:00 -07:00
|
|
|
(stdout and re.search("^child: CHILD-TEST-STARTED",
|
|
|
|
stdout, re.MULTILINE)
|
2012-03-08 15:00:05 -08:00
|
|
|
and not re.search("^child: CHILD-TEST-COMPLETED",
|
|
|
|
stdout, re.MULTILINE)))
|
2011-05-20 08:54:01 -07:00
|
|
|
|
2011-06-21 05:12:40 -07:00
|
|
|
if result != expected:
|
2012-02-27 19:53:00 -08:00
|
|
|
failureType = "TEST-UNEXPECTED-%s" % ("FAIL" if expected else "PASS")
|
|
|
|
message = "%s | %s | test failed (with xpcshell return code: %d), see following log:" % (
|
|
|
|
failureType, name, self.getReturnCode(proc))
|
|
|
|
self.log.error(message)
|
2011-05-20 08:54:01 -07:00
|
|
|
print_stdout(stdout)
|
2011-06-21 05:12:40 -07:00
|
|
|
self.failCount += 1
|
2012-02-27 19:53:00 -08:00
|
|
|
xunitResult["passed"] = False
|
|
|
|
|
|
|
|
xunitResult["failure"] = {
|
|
|
|
"type": failureType,
|
|
|
|
"message": message,
|
|
|
|
"text": stdout
|
|
|
|
}
|
2011-05-20 08:54:01 -07:00
|
|
|
else:
|
2012-02-27 19:53:00 -08:00
|
|
|
now = time.time()
|
|
|
|
timeTaken = (now - startTime) * 1000
|
|
|
|
xunitResult["time"] = now - startTime
|
2011-08-10 11:34:14 -07:00
|
|
|
self.log.info("TEST-%s | %s | test passed (time: %.3fms)" % ("PASS" if expected else "KNOWN-FAIL", name, timeTaken))
|
2011-05-20 08:54:01 -07:00
|
|
|
if verbose:
|
2010-10-20 12:13:54 -07:00
|
|
|
print_stdout(stdout)
|
2012-02-27 19:53:00 -08:00
|
|
|
|
|
|
|
xunitResult["passed"] = True
|
|
|
|
|
2011-06-21 05:12:40 -07:00
|
|
|
if expected:
|
|
|
|
self.passCount += 1
|
|
|
|
else:
|
|
|
|
self.todoCount += 1
|
2012-09-10 11:15:00 -07:00
|
|
|
xunitResult["todo"] = True
|
2011-05-20 08:54:01 -07:00
|
|
|
|
|
|
|
checkForCrashes(testdir, self.symbolsPath, testName=name)
|
|
|
|
# Find child process(es) leak log(s), if any: See InitLog() in
|
|
|
|
# xpcom/base/nsTraceRefcntImpl.cpp for logfile naming logic
|
|
|
|
leakLogs = [self.leakLogFile]
|
|
|
|
for childLog in glob(os.path.join(self.profileDir, "runxpcshelltests_leaks_*_pid*.log")):
|
|
|
|
if os.path.isfile(childLog):
|
|
|
|
leakLogs += [childLog]
|
|
|
|
for log in leakLogs:
|
|
|
|
dumpLeakLog(log, True)
|
|
|
|
|
|
|
|
if self.logfiles and stdout:
|
|
|
|
self.createLogFile(name, stdout, leakLogs)
|
|
|
|
finally:
|
2012-10-07 13:21:30 -07:00
|
|
|
# We can sometimes get here before the process has terminated, which would
|
|
|
|
# cause removeDir() to fail - so check for the process & kill it it needed.
|
2012-10-16 13:32:33 -07:00
|
|
|
if proc and self.poll(proc) is None:
|
2012-10-07 13:21:30 -07:00
|
|
|
message = "TEST-UNEXPECTED-FAIL | %s | Process still running after test!" % name
|
|
|
|
self.log.error(message)
|
|
|
|
print_stdout(stdout)
|
|
|
|
self.failCount += 1
|
|
|
|
xunitResult["passed"] = False
|
|
|
|
xunitResult["failure"] = {
|
|
|
|
"type": "TEST-UNEXPECTED-FAIL",
|
|
|
|
"message": message,
|
|
|
|
"text": stdout
|
|
|
|
}
|
|
|
|
self.kill(proc)
|
2011-05-20 08:54:01 -07:00
|
|
|
# We don't want to delete the profile when running check-interactive
|
|
|
|
# or check-one.
|
|
|
|
if self.profileDir and not self.interactive and not self.singleFile:
|
2012-10-07 13:21:52 -07:00
|
|
|
try:
|
|
|
|
self.removeDir(self.profileDir)
|
|
|
|
except Exception:
|
|
|
|
message = "TEST-UNEXPECTED-FAIL | %s | Failed to clean up the test profile directory: %s" % (name, sys.exc_info()[0])
|
|
|
|
self.log.error(message)
|
|
|
|
print_stdout(stdout)
|
|
|
|
print_stdout(traceback.format_exc())
|
2012-10-17 11:17:49 -07:00
|
|
|
|
|
|
|
# What follows is code to dump the directory listing similar to ls.
|
|
|
|
# This should only be needed until we track down the source of
|
|
|
|
# failures on the buildbot machines.
|
|
|
|
try:
|
|
|
|
import pwd
|
|
|
|
import grp
|
|
|
|
except ImportError:
|
|
|
|
pwd = None
|
|
|
|
grp = None
|
|
|
|
|
|
|
|
def get_username(uid):
|
|
|
|
if pwd is None:
|
|
|
|
return None
|
|
|
|
|
|
|
|
try:
|
|
|
|
return pwd.getpwuid(uid).pw_name
|
|
|
|
except KeyError:
|
|
|
|
return '%d missing' % uid
|
|
|
|
|
|
|
|
def get_groupname(gid):
|
|
|
|
if grp is None:
|
|
|
|
return None
|
|
|
|
|
|
|
|
try:
|
|
|
|
return grp.getgrgid(gid).gr_name
|
|
|
|
except KeyError:
|
|
|
|
return '%d missing' % gid
|
|
|
|
|
|
|
|
self.log.info('Files in profile directory:')
|
|
|
|
def on_error(error):
|
|
|
|
self.log.info('OS Error while performing os.walk!')
|
|
|
|
self.log.info(traceback.format_exc())
|
|
|
|
|
|
|
|
for d, dirs, files in os.walk(self.profileDir, onerror=on_error):
|
|
|
|
try:
|
|
|
|
d_stat = os.stat(d)
|
|
|
|
except Exception:
|
|
|
|
self.log.info('Could not stat directory %s' % d)
|
|
|
|
self.log.info(traceback.format_exc())
|
|
|
|
else:
|
|
|
|
self.log.info('%o %s %s %s/' % (d_stat.st_mode,
|
|
|
|
get_username(d_stat.st_uid),
|
|
|
|
get_groupname(d_stat.st_gid), d))
|
|
|
|
|
|
|
|
for f in files:
|
|
|
|
path = os.path.join(d, f)
|
|
|
|
|
|
|
|
try:
|
|
|
|
f_stat = os.stat(path)
|
|
|
|
except Exception:
|
|
|
|
self.log.info('Could not stat file %s' % path)
|
|
|
|
self.log.info(traceback.format_exc())
|
|
|
|
else:
|
|
|
|
self.log.info('%o %s %s %s' % (f_stat.st_mode,
|
|
|
|
get_username(f_stat.st_uid),
|
|
|
|
get_groupname(f_stat.st_gid), path))
|
|
|
|
|
2012-10-07 13:21:52 -07:00
|
|
|
self.failCount += 1
|
|
|
|
xunitResult["passed"] = False
|
|
|
|
xunitResult["failure"] = {
|
|
|
|
"type": "TEST-UNEXPECTED-FAIL",
|
|
|
|
"message": message,
|
|
|
|
"text": "%s\n%s" % (stdout, traceback.format_exc())
|
|
|
|
}
|
|
|
|
|
2011-05-20 08:54:01 -07:00
|
|
|
if gotSIGINT:
|
2012-02-27 19:53:00 -08:00
|
|
|
xunitResult["passed"] = False
|
|
|
|
xunitResult["time"] = "0.0"
|
|
|
|
xunitResult["failure"] = {
|
|
|
|
"type": "SIGINT",
|
|
|
|
"message": "Received SIGINT",
|
|
|
|
"text": "Received SIGINT (control-C) during test execution."
|
|
|
|
}
|
|
|
|
|
2011-06-21 05:12:40 -07:00
|
|
|
self.log.error("TEST-UNEXPECTED-FAIL | Received SIGINT (control-C) during test execution")
|
2011-05-20 08:54:01 -07:00
|
|
|
if (keepGoing):
|
|
|
|
gotSIGINT = False
|
|
|
|
else:
|
2012-02-27 19:53:00 -08:00
|
|
|
xunitResults.append(xunitResult)
|
2011-05-20 08:54:01 -07:00
|
|
|
break
|
2012-02-27 19:53:00 -08:00
|
|
|
|
|
|
|
xunitResults.append(xunitResult)
|
|
|
|
|
2012-04-25 17:12:33 -07:00
|
|
|
self.shutdownNode()
|
|
|
|
|
2011-06-21 05:12:40 -07:00
|
|
|
if self.testCount == 0:
|
|
|
|
self.log.error("TEST-UNEXPECTED-FAIL | runxpcshelltests.py | No tests run. Did you pass an invalid --test-path?")
|
|
|
|
self.failCount = 1
|
2010-01-15 09:22:54 -08:00
|
|
|
|
2011-06-21 05:12:40 -07:00
|
|
|
self.log.info("""INFO | Result summary:
|
2009-07-31 12:58:42 -07:00
|
|
|
INFO | Passed: %d
|
2011-06-21 05:12:40 -07:00
|
|
|
INFO | Failed: %d
|
|
|
|
INFO | Todo: %d""" % (self.passCount, self.failCount, self.todoCount))
|
2009-07-31 12:58:42 -07:00
|
|
|
|
2012-09-10 11:15:00 -07:00
|
|
|
if autolog:
|
|
|
|
self.post_to_autolog(xunitResults, xunitName)
|
|
|
|
|
2012-02-27 19:53:00 -08:00
|
|
|
if xunitFilename is not None:
|
|
|
|
self.writeXunitResults(filename=xunitFilename, results=xunitResults,
|
|
|
|
name=xunitName)
|
|
|
|
|
2010-09-10 10:20:38 -07:00
|
|
|
if gotSIGINT and not keepGoing:
|
2011-07-26 08:52:35 -07:00
|
|
|
self.log.error("TEST-UNEXPECTED-FAIL | Received SIGINT (control-C), so stopped run. " \
|
|
|
|
"(Use --keep-going to keep running tests after killing one with SIGINT)")
|
2010-09-10 10:20:38 -07:00
|
|
|
return False
|
2012-02-27 19:53:00 -08:00
|
|
|
|
2011-06-21 05:12:40 -07:00
|
|
|
return self.failCount == 0
|
2009-03-11 08:56:58 -07:00
|
|
|
|
2010-03-12 14:57:29 -08:00
|
|
|
class XPCShellOptions(OptionParser):
|
|
|
|
def __init__(self):
|
|
|
|
"""Process command line arguments and call runTests() to do the real work."""
|
|
|
|
OptionParser.__init__(self)
|
2009-05-11 12:54:39 -07:00
|
|
|
|
2010-03-12 14:57:29 -08:00
|
|
|
addCommonOptions(self)
|
2011-07-21 23:48:01 -07:00
|
|
|
self.add_option("--app-path",
|
|
|
|
type="string", dest="appPath", default=None,
|
|
|
|
help="application directory (as opposed to XRE directory)")
|
2012-09-10 11:15:00 -07:00
|
|
|
self.add_option("--autolog",
|
|
|
|
action="store_true", dest="autolog", default=False,
|
|
|
|
help="post to autolog")
|
2010-03-12 14:57:29 -08:00
|
|
|
self.add_option("--interactive",
|
2009-03-11 08:56:58 -07:00
|
|
|
action="store_true", dest="interactive", default=False,
|
|
|
|
help="don't automatically run tests, drop to an xpcshell prompt")
|
2010-02-10 14:52:16 -08:00
|
|
|
self.add_option("--verbose",
|
|
|
|
action="store_true", dest="verbose", default=False,
|
|
|
|
help="always print stdout and stderr from tests")
|
2010-09-10 10:20:38 -07:00
|
|
|
self.add_option("--keep-going",
|
|
|
|
action="store_true", dest="keepGoing", default=False,
|
|
|
|
help="continue running tests after test killed with control-C (SIGINT)")
|
2010-03-12 14:57:29 -08:00
|
|
|
self.add_option("--logfiles",
|
2009-09-21 09:19:21 -07:00
|
|
|
action="store_true", dest="logfiles", default=True,
|
|
|
|
help="create log files (default, only used to override --no-logfiles)")
|
2010-03-12 14:57:29 -08:00
|
|
|
self.add_option("--manifest",
|
2009-09-21 09:19:21 -07:00
|
|
|
type="string", dest="manifest", default=None,
|
|
|
|
help="Manifest of test directories to use")
|
2010-03-12 14:57:29 -08:00
|
|
|
self.add_option("--no-logfiles",
|
2009-09-21 09:19:21 -07:00
|
|
|
action="store_false", dest="logfiles",
|
|
|
|
help="don't create log files")
|
2010-03-12 14:57:29 -08:00
|
|
|
self.add_option("--test-path",
|
2009-09-21 09:19:21 -07:00
|
|
|
type="string", dest="testPath", default=None,
|
|
|
|
help="single path and/or test filename to test")
|
2012-03-06 15:03:34 -08:00
|
|
|
self.add_option("--tests-root-dir",
|
|
|
|
type="string", dest="testsRootDir", default=None,
|
|
|
|
help="absolute path to directory where all tests are located. this is typically $(objdir)/_tests")
|
2012-05-10 10:19:16 -07:00
|
|
|
self.add_option("--testing-modules-dir",
|
2012-05-10 10:10:14 -07:00
|
|
|
dest="testingModulesDir", default=None,
|
|
|
|
help="Directory where testing modules are located.")
|
2010-04-01 12:34:10 -07:00
|
|
|
self.add_option("--total-chunks",
|
|
|
|
type = "int", dest = "totalChunks", default=1,
|
|
|
|
help = "how many chunks to split the tests up into")
|
|
|
|
self.add_option("--this-chunk",
|
|
|
|
type = "int", dest = "thisChunk", default=1,
|
|
|
|
help = "which chunk to run between 1 and --total-chunks")
|
2010-04-27 10:28:56 -07:00
|
|
|
self.add_option("--profile-name",
|
|
|
|
type = "string", dest="profileName", default=None,
|
|
|
|
help="name of application profile being tested")
|
2011-06-21 05:12:40 -07:00
|
|
|
self.add_option("--build-info-json",
|
|
|
|
type = "string", dest="mozInfo", default=None,
|
|
|
|
help="path to a mozinfo.json including information about the build configuration. defaults to looking for mozinfo.json next to the script.")
|
2012-02-14 11:49:55 -08:00
|
|
|
self.add_option("--shuffle",
|
|
|
|
action="store_true", dest="shuffle", default=False,
|
|
|
|
help="Execute tests in random order")
|
2012-02-27 19:53:00 -08:00
|
|
|
self.add_option("--xunit-file", dest="xunitFilename",
|
|
|
|
help="path to file where xUnit results will be written.")
|
|
|
|
self.add_option("--xunit-suite-name", dest="xunitName",
|
|
|
|
help="name to record for this xUnit test suite. Many "
|
|
|
|
"tools expect Java class notation, e.g. "
|
|
|
|
"dom.basic.foo")
|
2010-03-12 14:57:29 -08:00
|
|
|
|
|
|
|
def main():
|
|
|
|
parser = XPCShellOptions()
|
2009-03-11 08:56:58 -07:00
|
|
|
options, args = parser.parse_args()
|
|
|
|
|
2009-03-21 08:20:00 -07:00
|
|
|
if len(args) < 2 and options.manifest is None or \
|
|
|
|
(len(args) < 1 and options.manifest is not None):
|
2010-01-15 09:22:54 -08:00
|
|
|
print >>sys.stderr, """Usage: %s <path to xpcshell> <test dirs>
|
|
|
|
or: %s --manifest=test.manifest <path to xpcshell>""" % (sys.argv[0],
|
2009-03-21 08:20:00 -07:00
|
|
|
sys.argv[0])
|
2010-01-15 09:22:54 -08:00
|
|
|
sys.exit(1)
|
2009-03-11 08:56:58 -07:00
|
|
|
|
2010-01-15 09:22:54 -08:00
|
|
|
xpcsh = XPCShellTests()
|
2009-10-19 16:12:09 -07:00
|
|
|
|
2009-04-27 16:37:02 -07:00
|
|
|
if options.interactive and not options.testPath:
|
2009-03-11 08:56:58 -07:00
|
|
|
print >>sys.stderr, "Error: You must specify a test filename in interactive mode!"
|
|
|
|
sys.exit(1)
|
|
|
|
|
2010-04-02 06:29:35 -07:00
|
|
|
if not xpcsh.runTests(args[0], testdirs=args[1:], **options.__dict__):
|
2009-03-11 08:56:58 -07:00
|
|
|
sys.exit(1)
|
|
|
|
|
|
|
|
if __name__ == '__main__':
|
|
|
|
main()
|