3 # ***** BEGIN LICENSE BLOCK *****
4 # Version: MPL 1.1/GPL 2.0/LGPL 2.1
6 # The contents of this file are subject to the Mozilla Public License Version
7 # 1.1 (the "License"); you may not use this file except in compliance with
8 # the License. You may obtain a copy of the License at
9 # http://www.mozilla.org/MPL/
11 # Software distributed under the License is distributed on an "AS IS" basis,
12 # WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
13 # for the specific language governing rights and limitations under the
16 # The Original Code is mozilla.org code.
18 # The Initial Developer of the Original Code is The Mozilla Foundation
19 # Portions created by the Initial Developer are Copyright (C) 2009
20 # the Initial Developer. All Rights Reserved.
23 # Serge Gautherie <sgautherie.bz@free.fr>
24 # Ted Mielczarek <ted.mielczarek@gmail.com>
26 # Alternatively, the contents of this file may be used under the terms of
27 # either the GNU General Public License Version 2 or later (the "GPL"), or
28 # the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
29 # in which case the provisions of the GPL or the LGPL are applicable instead
30 # of those above. If you wish to allow use of your version of this file only
31 # under the terms of either the GPL or the LGPL, and not to allow others to
32 # use your version of this file under the terms of the MPL, indicate your
33 # decision by deleting the provisions above and replace them with the notice
34 # and other provisions required by the GPL or the LGPL. If you do not delete
35 # the provisions above, a recipient may use your version of this file under
36 # the terms of any one of the MPL, the GPL or the LGPL.
38 # ***** END LICENSE BLOCK ***** */
40 import re
, sys
, os
, os
.path
, logging
, shutil
43 from optparse
import OptionParser
44 from subprocess
import Popen
, PIPE
, STDOUT
45 from tempfile
import mkdtemp
47 from automationutils
import addCommonOptions
, checkForCrashes
, dumpLeakLog
50 log
= logging
.getLogger()
51 handler
= logging
.StreamHandler(sys
.stdout
)
52 log
.setLevel(logging
.INFO
)
53 log
.addHandler(handler
)
55 def readManifest(manifest
):
56 """Given a manifest file containing a list of test directories,
57 return a list of absolute paths to the directories contained within."""
58 manifestdir
= os
.path
.dirname(manifest
)
61 f
= open(manifest
, "r")
64 path
= os
.path
.join(manifestdir
, dir)
65 if os
.path
.isdir(path
):
69 pass # just eat exceptions
72 def runTests(xpcshell
, testdirs
=[], xrePath
=None, testPath
=None,
73 manifest
=None, interactive
=False, symbolsPath
=None):
74 """Run the tests in |testdirs| using the |xpcshell| executable.
76 |xrePath|, if provided, is the path to the XRE to use.
77 |testPath|, if provided, indicates a single path and/or test to run.
78 |manifest|, if provided, is a file containing a list of
79 test directories to run.
80 |interactive|, if set to True, indicates to provide an xpcshell prompt
81 instead of automatically executing the test.
82 |symbolsPath|, if provided is the path to a directory containing
83 breakpad symbols for processing crashes in tests.
86 if not testdirs
and not manifest
:
88 print >>sys
.stderr
, "Error: No test dirs or test manifest specified!"
94 testharnessdir
= os
.path
.dirname(os
.path
.abspath(__file__
))
95 xpcshell
= os
.path
.abspath(xpcshell
)
96 # we assume that httpd.js lives in components/ relative to xpcshell
97 httpdJSPath
= os
.path
.join(os
.path
.dirname(xpcshell
), "components", "httpd.js").replace("\\", "/");
99 env
= dict(os
.environ
)
100 # Make assertions fatal
101 env
["XPCOM_DEBUG_BREAK"] = "stack-and-abort"
102 # Don't launch the crash reporter client
103 env
["MOZ_CRASHREPORTER_NO_REPORT"] = "1"
105 # Enable leaks (only) detection to its own log file.
106 # Each test will overwrite it.
107 leakLogFile
= os
.path
.join(tempfile
.gettempdir(), "runxpcshelltests_leaks.log")
108 env
["XPCOM_MEM_LEAK_LOG"] = leakLogFile
111 xrePath
= os
.path
.dirname(xpcshell
)
113 xrePath
= os
.path
.abspath(xrePath
)
114 if sys
.platform
== 'win32':
115 env
["PATH"] = env
["PATH"] + ";" + xrePath
116 elif sys
.platform
in ('os2emx', 'os2knix'):
117 os
.environ
["BEGINLIBPATH"] = xrePath
+ ";" + env
["BEGINLIBPATH"]
118 os
.environ
["LIBPATHSTRICT"] = "T"
119 elif sys
.platform
== 'osx':
120 env
["DYLD_LIBRARY_PATH"] = xrePath
121 else: # unix or linux?
122 env
["LD_LIBRARY_PATH"] = xrePath
124 # xpcsRunArgs: <head.js> function to call to run the test.
125 # pStdout, pStderr: Parameter values for later |Popen()| call.
128 '-e', 'print("To start the test, type |_execute_test();|.");',
133 xpcsRunArgs
= ['-e', '_execute_test();']
134 if sys
.platform
== 'os2emx':
140 # <head.js> has to be loaded by xpchell: it can't load itself.
141 xpcsCmd
= [xpcshell
, '-g', xrePath
, '-j', '-s'] + \
142 ['-e', 'const _HTTPD_JS_PATH = "%s";' % httpdJSPath
,
143 '-f', os
.path
.join(testharnessdir
, 'head.js')]
145 # |testPath| will be the optional path only, or |None|.
146 # |singleFile| will be the optional test only, or |None|.
149 if testPath
.endswith('.js'):
150 # Split into path and file.
151 if testPath
.find('/') == -1:
153 singleFile
= testPath
156 # Both path and test.
157 # Reuse |testPath| temporarily.
158 testPath
= testPath
.rsplit('/', 1)
159 singleFile
= testPath
[1]
160 testPath
= testPath
[0]
163 # Simply remove optional ending separator.
164 testPath
= testPath
.rstrip("/")
166 if manifest
is not None:
167 testdirs
= readManifest(os
.path
.abspath(manifest
))
169 # Process each test directory individually.
170 for testdir
in testdirs
:
171 if testPath
and not testdir
.endswith(testPath
):
174 testdir
= os
.path
.abspath(testdir
)
176 # get the list of head and tail files from the directory
178 for f
in sorted(glob(os
.path
.join(testdir
, "head_*.js"))):
179 if os
.path
.isfile(f
):
182 # Tails are executed in the reverse order, to "match" heads order,
183 # as in "h1-h2-h3 then t3-t2-t1".
184 for f
in reversed(sorted(glob(os
.path
.join(testdir
, "tail_*.js")))):
185 if os
.path
.isfile(f
):
188 # if a single test file was specified, we only want to execute that test
189 testfiles
= sorted(glob(os
.path
.join(testdir
, "test_*.js")))
191 if singleFile
in [os
.path
.basename(x
) for x
in testfiles
]:
192 testfiles
= [os
.path
.join(testdir
, singleFile
)]
193 else: # not in this dir? skip it
196 cmdH
= ", ".join(['"' + f
.replace('\\', '/') + '"'
197 for f
in testHeadFiles
])
198 cmdT
= ", ".join(['"' + f
.replace('\\', '/') + '"'
199 for f
in testTailFiles
])
201 ['-e', 'const _HEAD_FILES = [%s];' % cmdH
] + \
202 ['-e', 'const _TAIL_FILES = [%s];' % cmdT
]
204 # Now execute each test individually.
205 for test
in testfiles
:
206 # The test file will have to be loaded after the head files.
207 cmdT
= ['-e', 'const _TEST_FILE = ["%s"];' %
208 os
.path
.join(testdir
, test
).replace('\\', '/')]
209 # create a temp dir that the JS harness can stick a profile in
211 env
["XPCSHELL_TEST_PROFILE_DIR"] = profd
213 proc
= Popen(cmdH
+ cmdT
+ xpcsRunArgs
,
214 stdout
=pStdout
, stderr
=pStderr
, env
=env
, cwd
=testdir
)
215 # |stderr == None| as |pStderr| was either |None| or redirected to |stdout|.
216 stdout
, stderr
= proc
.communicate()
218 shutil
.rmtree(profd
, True)
221 # not sure what else to do here...
224 if proc
.returncode
!= 0 or (stdout
is not None and re
.search("^TEST-UNEXPECTED-FAIL", stdout
, re
.MULTILINE
)):
225 print """TEST-UNEXPECTED-FAIL | %s | test failed (with xpcshell return code: %d), see following log:
228 <<<<<<<""" % (test
, proc
.returncode
, stdout
)
229 checkForCrashes(testdir
, symbolsPath
, testName
=test
)
232 print "TEST-PASS | %s | test passed" % test
235 dumpLeakLog(leakLogFile
, True)
237 if stdout
is not None:
239 f
= open(test
+ '.log', 'w')
242 if os
.path
.exists(leakLogFile
):
243 leaks
= open(leakLogFile
, "r")
244 f
.write(leaks
.read())
250 # Remove the leak detection file (here) so it can't "leak" to the next test.
251 # The file is not there if leak logging was not enabled in the xpcshell build.
252 if os
.path
.exists(leakLogFile
):
253 os
.remove(leakLogFile
)
255 if passCount
== 0 and failCount
== 0:
256 print "TEST-UNEXPECTED-FAIL | runxpcshelltests.py | No tests run. Did you pass an invalid --test-path?"
259 print """INFO | Result summary:
261 INFO | Failed: %d""" % (passCount
, failCount
)
263 return failCount
== 0
266 """Process command line arguments and call runTests() to do the real work."""
267 parser
= OptionParser()
269 addCommonOptions(parser
)
270 parser
.add_option("--test-path",
271 action
="store", type="string", dest
="testPath",
272 default
=None, help="single path and/or test filename to test")
273 parser
.add_option("--interactive",
274 action
="store_true", dest
="interactive", default
=False,
275 help="don't automatically run tests, drop to an xpcshell prompt")
276 parser
.add_option("--manifest",
277 action
="store", type="string", dest
="manifest",
278 default
=None, help="Manifest of test directories to use")
279 options
, args
= parser
.parse_args()
281 if len(args
) < 2 and options
.manifest
is None or \
282 (len(args
) < 1 and options
.manifest
is not None):
283 print >>sys
.stderr
, """Usage: %s <path to xpcshell> <test dirs>
284 or: %s --manifest=test.manifest <path to xpcshell>""" % (sys
.argv
[0],
288 if options
.interactive
and not options
.testPath
:
289 print >>sys
.stderr
, "Error: You must specify a test filename in interactive mode!"
292 if not runTests(args
[0], testdirs
=args
[1:],
293 xrePath
=options
.xrePath
,
294 testPath
=options
.testPath
,
295 interactive
=options
.interactive
,
296 manifest
=options
.manifest
,
297 symbolsPath
=options
.symbolsPath
):
300 if __name__
== '__main__':