2 # ***** BEGIN LICENSE BLOCK *****
3 # Version: MPL 1.1/GPL 2.0/LGPL 2.1
5 # The contents of this file are subject to the Mozilla Public License Version
6 # 1.1 (the "License"); you may not use this file except in compliance with
7 # the License. You may obtain a copy of the License at
8 # http://www.mozilla.org/MPL/
10 # Software distributed under the License is distributed on an "AS IS" basis,
11 # WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 # for the specific language governing rights and limitations under the
15 # The Original Code is mozilla.org code.
17 # The Initial Developer of the Original Code is
19 # Portions created by the Initial Developer are Copyright (C) 1998
20 # the Initial Developer. All Rights Reserved.
23 # Robert Sayre <sayrer@gmail.com>
24 # Jeff Walden <jwalden+bmo@mit.edu>
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 *****
41 Runs the Mochitest test harness.
44 from datetime
import datetime
52 from urllib
import quote_plus
as encodeURIComponent
57 # Path to the test script on the server
58 TEST_SERVER_HOST
= "localhost:8888"
60 CHROME_PATH
= "/redirect.html";
61 A11Y_PATH
= "/redirect-a11y.html"
62 TESTS_URL
= "http://" + TEST_SERVER_HOST
+ TEST_PATH
63 CHROMETESTS_URL
= "http://" + TEST_SERVER_HOST
+ CHROME_PATH
64 A11YTESTS_URL
= "http://" + TEST_SERVER_HOST
+ A11Y_PATH
65 SERVER_SHUTDOWN_URL
= "http://" + TEST_SERVER_HOST
+ "/server/shutdown"
66 # main browser chrome URL, same as browser.chromeURL pref
68 BROWSER_CHROME_URL
= "chrome://navigator/content/navigator.xul"
70 BROWSER_CHROME_URL
= "chrome://browser/content/browser.xul"
73 # Max time in seconds to wait for server startup before tests will fail -- if
74 # this seems big, it's mostly for debug machines where cold startup
75 # (particularly after a build) takes forever.
76 SERVER_STARTUP_TIMEOUT
= 45
81 SCRIPT_DIRECTORY
= os
.path
.abspath(os
.path
.realpath(os
.path
.dirname(sys
.argv
[0])))
82 os
.chdir(SCRIPT_DIRECTORY
)
84 PROFILE_DIRECTORY
= os
.path
.abspath("./mochitesttestingprofile")
86 LEAK_REPORT_FILE
= PROFILE_DIRECTORY
+ "/" + "leaks-report.log"
88 log
= logging
.getLogger()
91 #######################
92 # COMMANDLINE OPTIONS #
93 #######################
95 class MochitestOptions(optparse
.OptionParser
):
96 """Parses Mochitest commandline options."""
97 def __init__(self
, **kwargs
):
98 optparse
.OptionParser
.__init
__(self
, **kwargs
)
101 self
.add_option("--close-when-done",
102 action
= "store_true", dest
= "closeWhenDone",
103 help = "close the application when tests are done running")
104 defaults
["closeWhenDone"] = False
106 self
.add_option("--appname",
107 action
= "store", type = "string", dest
= "app",
108 help = "absolute path to application, overriding default")
109 defaults
["app"] = automation
.DEFAULT_APP
111 self
.add_option("--log-file",
112 action
= "store", type = "string",
113 dest
= "logFile", metavar
= "FILE",
114 help = "file to which logging occurs")
115 defaults
["logFile"] = ""
117 self
.add_option("--autorun",
118 action
= "store_true", dest
= "autorun",
119 help = "start running tests when the application starts")
120 defaults
["autorun"] = False
122 LOG_LEVELS
= ("DEBUG", "INFO", "WARNING", "ERROR", "FATAL")
123 LEVEL_STRING
= ", ".join(LOG_LEVELS
)
125 self
.add_option("--console-level",
126 action
= "store", type = "choice", dest
= "consoleLevel",
127 choices
= LOG_LEVELS
, metavar
= "LEVEL",
128 help = "one of %s to determine the level of console "
129 "logging" % LEVEL_STRING
)
130 defaults
["consoleLevel"] = None
132 self
.add_option("--file-level",
133 action
= "store", type = "choice", dest
= "fileLevel",
134 choices
= LOG_LEVELS
, metavar
= "LEVEL",
135 help = "one of %s to determine the level of file "
136 "logging if a file has been specified, defaulting "
137 "to INFO" % LEVEL_STRING
)
138 defaults
["fileLevel"] = "INFO"
140 self
.add_option("--chrome",
141 action
= "store_true", dest
= "chrome",
142 help = "run chrome Mochitests")
143 defaults
["chrome"] = False
145 self
.add_option("--test-path",
146 action
= "store", type = "string", dest
= "testPath",
147 help = "start in the given directory's tests")
148 defaults
["testPath"] = ""
150 self
.add_option("--browser-chrome",
151 action
= "store_true", dest
= "browserChrome",
152 help = "run browser chrome Mochitests")
153 defaults
["browserChrome"] = False
155 self
.add_option("--a11y",
156 action
= "store_true", dest
= "a11y",
157 help = "run accessibility Mochitests");
159 self
.add_option("--setenv",
160 action
= "append", type = "string",
161 dest
= "environment", metavar
= "NAME=VALUE",
162 help = "sets the given variable in the application's "
164 defaults
["environment"] = []
166 self
.add_option("--browser-arg",
167 action
= "append", type = "string",
168 dest
= "browserArgs", metavar
= "ARG",
169 help = "provides an argument to the test application")
170 defaults
["browserArgs"] = []
172 self
.add_option("--leak-threshold",
173 action
= "store", type = "int",
174 dest
= "leakThreshold", metavar
= "THRESHOLD",
175 help = "fail if the number of bytes leaked through "
176 "refcounted objects (or bytes in classes with "
177 "MOZ_COUNT_CTOR and MOZ_COUNT_DTOR) is greater "
178 "than the given number")
179 defaults
["leakThreshold"] = INFINITY
181 self
.add_option("--fatal-assertions",
182 action
= "store_true", dest
= "fatalAssertions",
183 help = "abort testing whenever an assertion is hit "
184 "(requires a debug build to be effective)")
185 defaults
["fatalAssertions"] = False
187 # -h, --help are automatically handled by OptionParser
189 self
.set_defaults(**defaults
)
192 Usage instructions for runtests.py.
193 All arguments are optional.
194 If --chrome is specified, chrome tests will be run instead of web content tests.
195 If --browser-chrome is specified, browser-chrome tests will be run instead of web content tests.
196 See <http://mochikit.com/doc/html/MochiKit/Logging.html> for details on the logging levels."""
197 self
.set_usage(usage
)
201 #######################
202 # HTTP SERVER SUPPORT #
203 #######################
205 class MochitestServer
:
206 "Web server used to serve Mochitests, for closer fidelity to the real web."
208 def __init__(self
, options
):
209 self
._closeWhenDone
= options
.closeWhenDone
212 "Run the Mochitest server, returning the process ID of the server."
214 env
= dict(os
.environ
)
215 if automation
.UNIXISH
:
216 env
["LD_LIBRARY_PATH"] = automation
.DIST_BIN
217 env
["MOZILLA_FIVE_HOME"] = automation
.DIST_BIN
218 env
["XPCOM_DEBUG_BREAK"] = "warn"
221 "-f", "./" + "httpd.js",
222 "-f", "./" + "server.js"]
224 xpcshell
= automation
.DIST_BIN
+ "/" + "xpcshell";
225 self
._process
= automation
.Process(xpcshell
, args
, env
= env
)
226 pid
= self
._process
.pid
228 print "Error starting server."
230 log
.info("Server pid: %d", pid
)
233 def ensureReady(self
, timeout
):
236 aliveFile
= PROFILE_DIRECTORY
+ "/" + "server_alive.txt"
239 if os
.path
.exists(aliveFile
):
244 print "Timed out while waiting for server startup."
250 c
= urllib2
.urlopen(SERVER_SHUTDOWN_URL
)
263 parser
= MochitestOptions()
264 options
, args
= parser
.parse_args()
266 # If the leak threshold wasn't explicitly set, we override the default of
267 # infinity when the set of tests we're running are known to leak only a
268 # particular amount. If for some reason you don't want a new leak threshold
269 # enforced, just pass an explicit --leak-threshold=N to prevent the override.
270 maybeForceLeakThreshold(options
)
272 if not os
.path
.exists(options
.app
):
274 Error: Path %(app)s doesn't exist.
275 Are you executing $objdir/_tests/testing/mochitest/runtests.py?"""
276 print msg
% {"app": options
.app
}
279 # browser environment
280 browserEnv
= dict(os
.environ
)
282 # These variables are necessary for correct application startup; change
283 # via the commandline at your own risk.
284 browserEnv
["NO_EM_RESTART"] = "1"
285 browserEnv
["XPCOM_DEBUG_BREAK"] = "warn"
286 if automation
.UNIXISH
:
287 browserEnv
["LD_LIBRARY_PATH"] = automation
.DIST_BIN
288 browserEnv
["MOZILLA_FIVE_HOME"] = automation
.DIST_BIN
289 browserEnv
["GNOME_DISABLE_CRASH_DIALOG"] = "1"
291 for v
in options
.environment
:
294 print "Error: syntax error in --setenv=" + v
296 browserEnv
[v
[:ix
]] = v
[ix
+ 1:]
298 automation
.initializeProfile(PROFILE_DIRECTORY
)
299 manifest
= addChromeToProfile(options
)
300 server
= MochitestServer(options
)
303 # If we're lucky, the server has fully started by now, and all paths are
304 # ready, etc. However, xpcshell cold start times suck, at least for debug
305 # builds. We'll try to connect to the server for awhile, and if we fail,
306 # we'll try to kill the server and exit with an error.
307 server
.ensureReady(SERVER_STARTUP_TIMEOUT
)
310 # URL parameters to test URL:
312 # autorun -- kick off tests automatically
313 # closeWhenDone -- runs quit.js after tests
314 # logFile -- logs test run to an absolute path
317 # consoleLevel, fileLevel: set the logging level of the console and
318 # file logs, if activated.
319 # <http://mochikit.com/doc/html/MochiKit/Logging.html>
321 testURL
= TESTS_URL
+ options
.testPath
324 testURL
= CHROMETESTS_URL
326 urlOpts
.append("testPath=" + encodeURIComponent(options
.testPath
))
328 testURL
= A11YTESTS_URL
330 urlOpts
.append("testPath=" + encodeURIComponent(options
.testPath
))
331 elif options
.browserChrome
:
332 testURL
= "about:blank"
334 # allow relative paths for logFile
336 options
.logFile
= os
.path
.normpath(os
.path
.join(oldcwd
, options
.logFile
))
337 if options
.browserChrome
:
338 makeTestConfig(options
)
341 urlOpts
.append("autorun=1")
342 if options
.closeWhenDone
:
343 urlOpts
.append("closeWhenDone=1")
345 urlOpts
.append("logFile=" + encodeURIComponent(options
.logFile
))
346 urlOpts
.append("fileLevel=" + encodeURIComponent(options
.fileLevel
))
347 if options
.consoleLevel
:
348 urlOpts
.append("consoleLevel=" + encodeURIComponent(options
.consoleLevel
))
350 testURL
+= "?" + "&".join(urlOpts
)
352 browserEnv
["XPCOM_MEM_BLOAT_LOG"] = LEAK_REPORT_FILE
354 if options
.fatalAssertions
:
355 browserEnv
["XPCOM_DEBUG_BREAK"] = "stack-and-abort"
357 start
= automation
.runApp(testURL
, browserEnv
, options
.app
, PROFILE_DIRECTORY
,
360 # Server's no longer needed, and perhaps more importantly, anything it might
361 # spew to console shouldn't disrupt the leak information table we print next.
364 if not os
.path
.exists(LEAK_REPORT_FILE
):
365 log
.info("WARNING refcount logging is off, so leaks can't be detected!")
367 leaks
= open(LEAK_REPORT_FILE
, "r")
369 log
.info(line
.rstrip())
372 threshold
= options
.leakThreshold
373 leaks
= open(LEAK_REPORT_FILE
, "r")
374 # Per-Inst Leaked Total Rem ...
375 # 0 TOTAL 17 192 419115886 2 ...
376 # 833 nsTimerImpl 60 120 24726 2 ...
377 lineRe
= re
.compile(r
"^\s*\d+\s+(?P<name>\S+)\s+"
378 r
"(?P<size>-?\d+)\s+(?P<bytesLeaked>-?\d+)\s+"
379 r
"\d+\s+(?P<numLeaked>-?\d+)")
383 matches
= lineRe
.match(line
)
386 name
= matches
.group("name")
387 size
= int(matches
.group("size"))
388 bytesLeaked
= int(matches
.group("bytesLeaked"))
389 numLeaked
= int(matches
.group("numLeaked"))
390 if size
< 0 or bytesLeaked
< 0 or numLeaked
< 0:
391 log
.info("TEST-UNEXPECTED-FAIL | runtests-leaks | negative leaks caught!")
395 if bytesLeaked
< 0 or bytesLeaked
> threshold
:
396 prefix
= "TEST-UNEXPECTED-FAIL"
397 leakLog
= "TEST-UNEXPECTED-FAIL | runtests-leaks | leaked" \
398 " %d bytes during test execution" % bytesLeaked
399 elif bytesLeaked
> 0:
400 leakLog
= "TEST-PASS | runtests-leaks | WARNING leaked" \
401 " %d bytes during test execution" % bytesLeaked
403 leakLog
= "TEST-PASS | runtests-leaks | no leaks detected!"
404 # Remind the threshold if it is not 0, which is the goal.
406 leakLog
+= (threshold
== INFINITY
) \
407 and (" (no threshold set)") \
408 or (" (threshold set at %d bytes)" % threshold
)
409 # Log the information.
413 if abs(numLeaked
) > 1:
414 instance
= "instances"
415 rest
= " each (%s bytes total)" % matches
.group("bytesLeaked")
417 instance
= "instance"
419 log
.info("%(prefix)s | runtests-leaks | leaked %(numLeaked)d %(instance)s of %(name)s "
420 "with size %(size)s bytes%(rest)s" %
422 "numLeaked": numLeaked
,
423 "instance": instance
,
425 "size": matches
.group("size"),
428 log
.info("TEST-UNEXPECTED-FAIL | runtests-leaks | missing output line for total leaks!")
432 # print test run times
433 finish
= datetime
.now()
434 log
.info(" started: %s", str(start
))
435 log
.info("finished: %s", str(finish
))
437 # delete the profile and manifest
440 # hanging due to non-halting threads is no fun; assume we hit the errors we
441 # were going to hit already and exit with a success code
446 #######################
447 # CONFIGURATION SETUP #
448 #######################
450 def maybeForceLeakThreshold(options
):
452 Modifies an unset leak threshold if it is known that a particular leak
453 threshold can be successfully forced for the particular Mochitest type
456 if options
.leakThreshold
== INFINITY
:
458 # We don't leak running the --chrome tests.
459 options
.leakThreshold
= 0
460 elif options
.browserChrome
:
461 # We still leak a nondeterministic amount running browser-chrome tests.
462 # But we are close to 0 (bug), so start to prevent/detect regressions. (Bug 460548)
463 options
.leakThreshold
= 75000
465 # We don't leak running the --a11y tests.
466 options
.leakThreshold
= 0
468 # Normal Mochitests: no leaks.
469 options
.leakThreshold
= 0
471 def makeTestConfig(options
):
472 "Creates a test configuration file for customizing test execution."
478 logFile
= options
.logFile
.replace("\\", "\\\\")
479 testPath
= options
.testPath
.replace("\\", "\\\\")
482 autoRun: %(autorun)s,
483 closeWhenDone: %(closeWhenDone)s,
484 logPath: "%(logPath)s",
485 testPath: "%(testPath)s"
486 })""" % {"autorun": boolString(options
.autorun
),
487 "closeWhenDone": boolString(options
.closeWhenDone
),
489 "testPath": testPath
}
491 config
= open(PROFILE_DIRECTORY
+ "/" + "testConfig.js", "w")
492 config
.write(content
)
496 def addChromeToProfile(options
):
497 "Adds MochiKit chrome tests to the profile."
499 chromedir
= PROFILE_DIRECTORY
+ "/" + "chrome"
505 @namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"); /* set default namespace to XUL */
508 background-color: rgb(235, 235, 235) !important;
511 background-image: none !important;
518 # write userChrome.css
519 chromeFile
= open(PROFILE_DIRECTORY
+ "/" + "userChrome.css", "a")
520 chromeFile
.write("".join(chrome
))
524 # register our chrome dir
525 chrometestDir
= os
.path
.abspath(".") + "/"
526 if automation
.IS_WIN32
:
527 chrometestDir
= "file:///" + chrometestDir
.replace("\\", "/")
530 (path
, leaf
) = os
.path
.split(options
.app
)
531 manifest
= path
+ "/" + "chrome/mochikit.manifest"
532 manifestFile
= open(manifest
, "w")
533 manifestFile
.write("content mochikit " + chrometestDir
+ " contentaccessible=yes\n")
534 if options
.browserChrome
:
535 overlayLine
= "overlay " + BROWSER_CHROME_URL
+ " " \
536 "chrome://mochikit/content/browser-test-overlay.xul\n"
537 manifestFile
.write(overlayLine
)
546 if __name__
== "__main__":