2009-03-11 08:56:58 -07:00
#!/usr/bin/env python
#
# ***** BEGIN LICENSE BLOCK *****
# Version: MPL 1.1/GPL 2.0/LGPL 2.1
#
# The contents of this file are subject to the Mozilla Public License Version
# 1.1 (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
# http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS IS" basis,
# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
# for the specific language governing rights and limitations under the
# License.
#
# The Original Code is mozilla.org code.
#
# The Initial Developer of the Original Code is The Mozilla Foundation
# Portions created by the Initial Developer are Copyright (C) 2009
# the Initial Developer. All Rights Reserved.
#
# Contributor(s):
2009-06-04 15:05:22 -07:00
# Serge Gautherie <sgautherie.bz@free.fr>
2009-03-11 08:56:58 -07:00
# Ted Mielczarek <ted.mielczarek@gmail.com>
2010-01-15 09:22:54 -08:00
# Joel Maher <joel.maher@gmail.com>
2009-03-11 08:56:58 -07:00
#
# Alternatively, the contents of this file may be used under the terms of
# either the GNU General Public License Version 2 or later (the "GPL"), or
# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
# in which case the provisions of the GPL or the LGPL are applicable instead
# of those above. If you wish to allow use of your version of this file only
# under the terms of either the GPL or the LGPL, and not to allow others to
# use your version of this file under the terms of the MPL, indicate your
# decision by deleting the provisions above and replace them with the notice
# and other provisions required by the GPL or the LGPL. If you do not delete
# the provisions above, a recipient may use your version of this file under
# the terms of any one of the MPL, the GPL or the LGPL.
#
# ***** END LICENSE BLOCK ***** */
2011-08-10 11:34:14 -07:00
import re , sys , os , os . path , logging , shutil , signal , math , time
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
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 } )
2010-09-10 10:20:38 -07:00
""" Control-C handling """
gotSIGINT = False
def markGotSIGINT ( signum , stackFrame ) :
global gotSIGINT
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 ) :
2010-03-12 14:57:29 -08:00
""" Init logging """
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 )
2010-04-01 12:34:10 -07:00
def buildTestList ( self ) :
"""
2011-05-20 08:54:01 -07:00
read the xpcshell . ini manifest and set self . alltests to be
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 :
startPosition = int ( start - currentCount )
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
2011-11-28 13:24:18 -08:00
self . xpcsCmd = [ self . xpcshell , ' -g ' , self . xrePath , ' -a ' , self . appPath , ' -r ' , self . httpdManifest , ' -m ' , ' -n ' , ' -s ' ] + \
2010-03-12 14:57:29 -08:00
[ ' -e ' , ' const _HTTPD_JS_PATH = " %s " ; ' % self . httpdJSPath ,
2010-03-16 19:40:36 -07:00
' -e ' , ' const _HEAD_JS_PATH = " %s " ; ' % self . headJSPath ,
' -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 ( " / " )
2011-05-20 08:54:01 -07:00
def getHeadFiles ( self , test ) :
2010-03-12 14:57:29 -08:00
"""
2011-05-20 08:54:01 -07:00
test [ ' head ' ] is a whitespace delimited list of head files .
return the list of head files as paths including the subdir if the head file exists
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 list files in a remote directory structure .
2010-03-12 14:57:29 -08:00
"""
2011-05-20 08:54:01 -07:00
return [ os . path . join ( test [ ' here ' ] , f ) . strip ( ) for f in sorted ( test [ ' head ' ] . split ( ' ' ) ) if os . path . isfile ( os . path . join ( test [ ' here ' ] , f ) ) ]
2010-03-12 14:57:29 -08:00
2011-05-20 08:54:01 -07:00
def getTailFiles ( self , test ) :
2010-03-12 14:57:29 -08:00
"""
2011-05-20 08:54:01 -07:00
test [ ' tail ' ] is a whitespace delimited list of head files .
return the list of tail files as paths including the subdir if the tail file exists
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 list files in a remote directory structure .
2010-03-12 14:57:29 -08:00
"""
2011-05-20 08:54:01 -07:00
return [ os . path . join ( test [ ' here ' ] , f ) . strip ( ) for f in sorted ( test [ ' tail ' ] . split ( ' ' ) ) if os . path . isfile ( os . path . join ( test [ ' here ' ] , f ) ) ]
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 )
2010-03-12 14:57:29 -08:00
proc = Popen ( cmd , stdout = stdout , stderr = stderr ,
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 ( )
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 )
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 )
2009-12-03 17:12:33 -08:00
for leakLog in leakLogs :
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 ) ]
2011-07-21 23:48:01 -07:00
def runTests ( self , xpcshell , xrePath = None , appPath = None , symbolsPath = None ,
2010-03-12 14:57:29 -08:00
manifest = None , testdirs = [ ] , 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 ,
2011-08-22 01:00:50 -07:00
profileName = None , mozInfo = None , * * 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 .
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
"""
2010-09-10 10:20:38 -07:00
global gotSIGINT
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
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 )
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
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
# Check for skipped tests
if ' disabled ' in test :
self . log . info ( " TEST-INFO | skipping %s | %s " %
( name , test [ ' disabled ' ] ) )
continue
# 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 )
2011-05-20 08:54:01 -07:00
testHeadFiles = self . getHeadFiles ( test )
testTailFiles = self . getTailFiles ( 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
2011-10-10 15:06:28 -07:00
args = self . xpcsRunArgs
if ' debug ' in test :
args . insert ( 0 , ' -d ' )
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 )
2011-08-10 11:34:14 -07:00
startTime = time . time ( )
2011-05-20 08:54:01 -07:00
2011-10-10 15:06:28 -07:00
proc = self . launchProcess ( cmdH + cmdT + args ,
2011-05-20 08:54:01 -07:00
stdout = pStdout , stderr = pStderr , env = self . env , cwd = testdir )
# 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
( stdout and re . search ( " ^((parent|child): )?TEST-UNEXPECTED- " ,
stdout , re . MULTILINE ) ) or
( stdout and re . search ( " : SyntaxError: " , stdout ,
re . MULTILINE ) ) )
2011-05-20 08:54:01 -07:00
2011-06-21 05:12:40 -07:00
if result != expected :
self . log . error ( " TEST-UNEXPECTED- %s | %s | test failed (with xpcshell return code: %d ), see following log: " % ( " FAIL " if expected else " PASS " , name , self . getReturnCode ( proc ) ) )
2011-05-20 08:54:01 -07:00
print_stdout ( stdout )
2011-06-21 05:12:40 -07:00
self . failCount + = 1
2011-05-20 08:54:01 -07:00
else :
2011-08-10 11:34:14 -07:00
timeTaken = ( time . time ( ) - startTime ) * 1000
self . log . info ( " TEST- %s | %s | test passed (time: %.3f ms) " % ( " 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 )
2011-06-21 05:12:40 -07:00
if expected :
self . passCount + = 1
else :
self . todoCount + = 1
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 :
# 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 :
self . removeDir ( self . profileDir )
if gotSIGINT :
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 :
break
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
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
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) " )
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 " )
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. " )
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 ( )