fdc20cb392edbbd2aa8a907d64bd6b646342c65f
[WebKit-https.git] / Tools / Scripts / webkitpy / layout_tests / run_webkit_tests.py
1 # Copyright (C) 2010 Google Inc. All rights reserved.
2 # Copyright (C) 2010 Gabor Rapcsanyi (rgabor@inf.u-szeged.hu), University of Szeged
3 # Copyright (C) 2011 Apple Inc. All rights reserved.
4 #
5 # Redistribution and use in source and binary forms, with or without
6 # modification, are permitted provided that the following conditions are
7 # met:
8 #
9 #     * Redistributions of source code must retain the above copyright
10 # notice, this list of conditions and the following disclaimer.
11 #     * Redistributions in binary form must reproduce the above
12 # copyright notice, this list of conditions and the following disclaimer
13 # in the documentation and/or other materials provided with the
14 # distribution.
15 #     * Neither the name of Google Inc. nor the names of its
16 # contributors may be used to endorse or promote products derived from
17 # this software without specific prior written permission.
18 #
19 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23 # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30
31 import logging
32 import optparse
33 import os
34 import signal
35 import sys
36 import traceback
37
38 from webkitpy.common.host import Host
39 from webkitpy.layout_tests.controllers.manager import Manager
40 from webkitpy.port import configuration_options, platform_options
41 from webkitpy.layout_tests.views import buildbot_results
42 from webkitpy.layout_tests.views import printing
43
44
45 _log = logging.getLogger(__name__)
46
47
48 # This mirrors what the shell normally does.
49 INTERRUPTED_EXIT_STATUS = signal.SIGINT + 128
50
51 # This is a randomly chosen exit code that can be tested against to
52 # indicate that an unexpected exception occurred.
53 EXCEPTIONAL_EXIT_STATUS = 254
54
55
56 def main(argv, stdout, stderr):
57     options, args = parse_args(argv)
58
59     if options.platform and 'test' in options.platform:
60         # It's a bit lame to import mocks into real code, but this allows the user
61         # to run tests against the test platform interactively, which is useful for
62         # debugging test failures.
63         from webkitpy.common.host_mock import MockHost
64         host = MockHost()
65     else:
66         host = Host()
67
68     if options.lint_test_files:
69         from webkitpy.layout_tests.lint_test_expectations import lint
70         return lint(host, options, stderr)
71
72     try:
73         port = host.port_factory.get(options.platform, options)
74     except NotImplementedError, e:
75         # FIXME: is this the best way to handle unsupported port names?
76         print >> stderr, str(e)
77         return EXCEPTIONAL_EXIT_STATUS
78
79     try:
80         run_details = run(port, options, args, stderr)
81         if run_details.exit_code != -1:
82             bot_printer = buildbot_results.BuildBotPrinter(stdout, options.debug_rwt_logging)
83             bot_printer.print_results(run_details)
84
85         return run_details.exit_code
86     except KeyboardInterrupt:
87         return INTERRUPTED_EXIT_STATUS
88     except BaseException as e:
89         if isinstance(e, Exception):
90             print >> stderr, '\n%s raised: %s' % (e.__class__.__name__, str(e))
91             traceback.print_exc(file=stderr)
92         return EXCEPTIONAL_EXIT_STATUS
93
94
95 def parse_args(args):
96     option_group_definitions = []
97
98     option_group_definitions.append(("Platform options", platform_options()))
99     option_group_definitions.append(("Configuration options", configuration_options()))
100     option_group_definitions.append(("Printing Options", printing.print_options()))
101
102     option_group_definitions.append(("EFL-specific Options", [
103         optparse.make_option("--webprocess-cmd-prefix", type="string",
104             default=False, help="Prefix used when spawning the Web process (Debug mode only)"),
105     ]))
106
107     option_group_definitions.append(("WebKit Options", [
108         optparse.make_option("--gc-between-tests", action="store_true", default=False,
109             help="Force garbage collection between each test"),
110         optparse.make_option("--complex-text", action="store_true", default=False,
111             help="Use the complex text code path for all text (Mac OS X and Windows only)"),
112         optparse.make_option("-l", "--leaks", action="store_true", default=False,
113             help="Enable leaks checking (Mac OS X and Gtk+ only)"),
114         optparse.make_option("-g", "--guard-malloc", action="store_true", default=False,
115             help="Enable Guard Malloc (Mac OS X only)"),
116         optparse.make_option("--threaded", action="store_true", default=False,
117             help="Run a concurrent JavaScript thread with each test"),
118         optparse.make_option("--webkit-test-runner", "-2", action="store_true",
119             help="Use WebKitTestRunner rather than DumpRenderTree."),
120         # FIXME: We should merge this w/ --build-directory and only have one flag.
121         optparse.make_option("--root", action="store",
122             help="Path to a directory containing the executables needed to run tests."),
123     ]))
124
125     option_group_definitions.append(("Results Options", [
126         optparse.make_option("-p", "--pixel", "--pixel-tests", action="store_true",
127             dest="pixel_tests", help="Enable pixel-to-pixel PNG comparisons"),
128         optparse.make_option("--no-pixel", "--no-pixel-tests", action="store_false",
129             dest="pixel_tests", help="Disable pixel-to-pixel PNG comparisons"),
130         optparse.make_option("--no-sample-on-timeout", action="store_false",
131             dest="sample_on_timeout", help="Don't run sample on timeout (Mac OS X only)"),
132         optparse.make_option("--no-ref-tests", action="store_true",
133             dest="no_ref_tests", help="Skip all ref tests"),
134         optparse.make_option("--tolerance",
135             help="Ignore image differences less than this percentage (some "
136                 "ports may ignore this option)", type="float"),
137         optparse.make_option("--results-directory", help="Location of test results"),
138         optparse.make_option("--build-directory",
139             help="Path to the directory under which build files are kept (should not include configuration)"),
140         optparse.make_option("--add-platform-exceptions", action="store_true", default=False,
141             help="Save generated results into the *most-specific-platform* directory rather than the *generic-platform* directory"),
142         optparse.make_option("--new-baseline", action="store_true",
143             default=False, help="Save generated results as new baselines "
144                  "into the *most-specific-platform* directory, overwriting whatever's "
145                  "already there. Equivalent to --reset-results --add-platform-exceptions"),
146         optparse.make_option("--reset-results", action="store_true",
147             default=False, help="Reset expectations to the "
148                  "generated results in their existing location."),
149         optparse.make_option("--no-new-test-results", action="store_false",
150             dest="new_test_results", default=True,
151             help="Don't create new baselines when no expected results exist"),
152
153         #FIXME: we should support a comma separated list with --pixel-test-directory as well.
154         optparse.make_option("--pixel-test-directory", action="append", default=[], dest="pixel_test_directories",
155             help="A directory where it is allowed to execute tests as pixel tests. "
156                  "Specify multiple times to add multiple directories. "
157                  "This option implies --pixel-tests. If specified, only those tests "
158                  "will be executed as pixel tests that are located in one of the "
159                  "directories enumerated with the option. Some ports may ignore this "
160                  "option while others can have a default value that can be overridden here."),
161
162         optparse.make_option("--skip-failing-tests", action="store_true",
163             default=False, help="Skip tests that are expected to fail. "
164                  "Note: When using this option, you might miss new crashes "
165                  "in these tests."),
166         optparse.make_option("--additional-drt-flag", action="append",
167             default=[], help="Additional command line flag to pass to DumpRenderTree "
168                  "Specify multiple times to add multiple flags."),
169         optparse.make_option("--driver-name", type="string",
170             help="Alternative DumpRenderTree binary to use"),
171         optparse.make_option("--additional-platform-directory", action="append",
172             default=[], help="Additional directory where to look for test "
173                  "baselines (will take precendence over platform baselines). "
174                  "Specify multiple times to add multiple search path entries."),
175         optparse.make_option("--additional-expectations", action="append", default=[],
176             help="Path to a test_expectations file that will override previous expectations. "
177                  "Specify multiple times for multiple sets of overrides."),
178         optparse.make_option("--compare-port", action="store", default=None,
179             help="Use the specified port's baselines first"),
180         optparse.make_option("--no-show-results", action="store_false",
181             default=True, dest="show_results",
182             help="Don't launch a browser with results after the tests "
183                  "are done"),
184         optparse.make_option("--full-results-html", action="store_true",
185             default=False,
186             help="Show all failures in results.html, rather than only regressions"),
187         optparse.make_option("--clobber-old-results", action="store_true",
188             default=False, help="Clobbers test results from previous runs."),
189         optparse.make_option("--http", action="store_true", dest="http",
190             default=True, help="Run HTTP and WebSocket tests (default)"),
191         optparse.make_option("--no-http", action="store_false", dest="http",
192             help="Don't run HTTP and WebSocket tests"),
193         optparse.make_option("--ignore-metrics", action="store_true", dest="ignore_metrics",
194             default=False, help="Ignore rendering metrics related information from test "
195             "output, only compare the structure of the rendertree."),
196         optparse.make_option("--nocheck-sys-deps", action="store_true",
197             default=False,
198             help="Don't check the system dependencies (themes)"),
199         optparse.make_option("--nojava", action="store_true",
200             default=False,
201             help="Don't build java support files"),
202     ]))
203
204     option_group_definitions.append(("Testing Options", [
205         optparse.make_option("--build", dest="build",
206             action="store_true", default=True,
207             help="Check to ensure the DumpRenderTree build is up-to-date "
208                  "(default)."),
209         optparse.make_option("--no-build", dest="build",
210             action="store_false", help="Don't check to see if the "
211                                        "DumpRenderTree build is up-to-date."),
212         optparse.make_option("-n", "--dry-run", action="store_true",
213             default=False,
214             help="Do everything but actually run the tests or upload results."),
215         optparse.make_option("--wrapper",
216             help="wrapper command to insert before invocations of "
217                  "DumpRenderTree; option is split on whitespace before "
218                  "running. (Example: --wrapper='valgrind --smc-check=all')"),
219         optparse.make_option("-i", "--ignore-tests", action="append", default=[],
220             help="directories or test to ignore (may specify multiple times)"),
221         optparse.make_option("--test-list", action="append",
222             help="read list of tests to run from file", metavar="FILE"),
223         optparse.make_option("--skipped", action="store", default="default",
224             help=("control how tests marked SKIP are run. "
225                  "'default' == Skip tests unless explicitly listed on the command line, "
226                  "'ignore' == Run them anyway, "
227                  "'only' == only run the SKIP tests, "
228                  "'always' == always skip, even if listed on the command line.")),
229         optparse.make_option("--force", dest="skipped", action="store_const", const='ignore',
230             help="Run all tests, even those marked SKIP in the test list (same as --skipped=ignore)"),
231         optparse.make_option("--time-out-ms",
232             help="Set the timeout for each test"),
233         optparse.make_option("--order", action="store", default="natural",
234             help=("determine the order in which the test cases will be run. "
235                   "'none' == use the order in which the tests were listed either in arguments or test list, "
236                   "'natural' == use the natural order (default), "
237                   "'random' == randomize the test order.")),
238         optparse.make_option("--run-chunk",
239             help=("Run a specified chunk (n:l), the nth of len l, "
240                  "of the layout tests")),
241         optparse.make_option("--run-part", help=("Run a specified part (n:m), "
242                   "the nth of m parts, of the layout tests")),
243         optparse.make_option("--batch-size",
244             help=("Run a the tests in batches (n), after every n tests, "
245                   "DumpRenderTree is relaunched."), type="int", default=None),
246         optparse.make_option("--run-singly", action="store_true",
247             default=False, help="run a separate DumpRenderTree for each test (implies --verbose)"),
248         optparse.make_option("--child-processes",
249             help="Number of DumpRenderTrees to run in parallel."),
250         # FIXME: Display default number of child processes that will run.
251         optparse.make_option("-f", "--fully-parallel", action="store_true",
252             help="run all tests in parallel"),
253         optparse.make_option("--exit-after-n-failures", type="int", default=None,
254             help="Exit after the first N failures instead of running all "
255             "tests"),
256         optparse.make_option("--exit-after-n-crashes-or-timeouts", type="int",
257             default=None, help="Exit after the first N crashes instead of "
258             "running all tests"),
259         optparse.make_option("--iterations", type="int", default=1, help="Number of times to run the set of tests (e.g. ABCABCABC)"),
260         optparse.make_option("--repeat-each", type="int", default=1, help="Number of times to run each test (e.g. AAABBBCCC)"),
261         optparse.make_option("--retry-failures", action="store_true",
262             default=True,
263             help="Re-try any tests that produce unexpected results (default)"),
264         optparse.make_option("--no-retry-failures", action="store_false",
265             dest="retry_failures",
266             help="Don't re-try any tests that produce unexpected results."),
267         optparse.make_option("--max-locked-shards", type="int", default=0,
268             help="Set the maximum number of locked shards"),
269         optparse.make_option("--additional-env-var", type="string", action="append", default=[],
270             help="Passes that environment variable to the tests (--additional-env-var=NAME=VALUE)"),
271         optparse.make_option("--profile", action="store_true",
272             help="Output per-test profile information."),
273         optparse.make_option("--profiler", action="store",
274             help="Output per-test profile information, using the specified profiler."),
275     ]))
276
277     option_group_definitions.append(("Miscellaneous Options", [
278         optparse.make_option("--lint-test-files", action="store_true",
279         default=False, help=("Makes sure the test files parse for all "
280                             "configurations. Does not run any tests.")),
281     ]))
282
283     # FIXME: Move these into json_results_generator.py
284     option_group_definitions.append(("Result JSON Options", [
285         optparse.make_option("--master-name", help="The name of the buildbot master."),
286         optparse.make_option("--builder-name", default="",
287             help=("The name of the builder shown on the waterfall running this script. e.g. Apple MountainLion Release WK2 (Tests).")),
288         optparse.make_option("--build-name", default="DUMMY_BUILD_NAME",
289             help=("The name of the builder used in its path, e.g. webkit-rel.")),
290         optparse.make_option("--build-slave", default="DUMMY_BUILD_SLAVE",
291             help=("The name of the buildslave used. e.g. apple-macpro-6.")),
292         optparse.make_option("--build-number", default="DUMMY_BUILD_NUMBER",
293             help=("The build number of the builder running this script.")),
294         optparse.make_option("--test-results-server", default="",
295             help=("If specified, upload results json files to this appengine server.")),
296         optparse.make_option("--results-server-host", default="",
297             help=("If specified, upload results JSON file to this results server.")),
298         optparse.make_option("--additional-repository-name",
299             help=("The name of an additional subversion or git checkout")),
300         optparse.make_option("--additional-repository-path",
301             help=("The path to an additional subversion or git checkout (requires --additional-repository-name)")),
302     ]))
303
304     option_parser = optparse.OptionParser()
305
306     for group_name, group_options in option_group_definitions:
307         option_group = optparse.OptionGroup(option_parser, group_name)
308         option_group.add_options(group_options)
309         option_parser.add_option_group(option_group)
310
311     return option_parser.parse_args(args)
312
313
314 def _set_up_derived_options(port, options):
315     """Sets the options values that depend on other options values."""
316     if not options.child_processes:
317         options.child_processes = os.environ.get("WEBKIT_TEST_CHILD_PROCESSES",
318                                                  str(port.default_child_processes()))
319     if not options.max_locked_shards:
320         options.max_locked_shards = int(os.environ.get("WEBKIT_TEST_MAX_LOCKED_SHARDS",
321                                                        str(port.default_max_locked_shards())))
322
323     if not options.configuration:
324         options.configuration = port.default_configuration()
325
326     if options.pixel_tests is None:
327         options.pixel_tests = port.default_pixel_tests()
328
329     if not options.time_out_ms:
330         options.time_out_ms = str(port.default_timeout_ms())
331
332     options.slow_time_out_ms = str(5 * int(options.time_out_ms))
333
334     if options.additional_platform_directory:
335         additional_platform_directories = []
336         for path in options.additional_platform_directory:
337             additional_platform_directories.append(port.host.filesystem.abspath(path))
338         options.additional_platform_directory = additional_platform_directories
339
340     if not options.http and options.skipped in ('ignore', 'only'):
341         _log.warning("--force/--skipped=%s overrides --no-http." % (options.skipped))
342         options.http = True
343
344     if options.ignore_metrics and (options.new_baseline or options.reset_results):
345         _log.warning("--ignore-metrics has no effect with --new-baselines or with --reset-results")
346
347     if options.new_baseline:
348         options.reset_results = True
349         options.add_platform_exceptions = True
350
351     if options.pixel_test_directories:
352         options.pixel_tests = True
353         varified_dirs = set()
354         pixel_test_directories = options.pixel_test_directories
355         for directory in pixel_test_directories:
356             # FIXME: we should support specifying the directories all the ways we support it for additional
357             # arguments specifying which tests and directories to run. We should also move the logic for that
358             # to Port.
359             filesystem = port.host.filesystem
360             if not filesystem.isdir(filesystem.join(port.layout_tests_dir(), directory)):
361                 _log.warning("'%s' was passed to --pixel-test-directories, which doesn't seem to be a directory" % str(directory))
362             else:
363                 varified_dirs.add(directory)
364
365         options.pixel_test_directories = list(varified_dirs)
366
367     if options.run_singly:
368         options.verbose = True
369
370
371 def run(port, options, args, logging_stream):
372     logger = logging.getLogger()
373     logger.setLevel(logging.DEBUG if options.debug_rwt_logging else logging.INFO)
374
375     try:
376         printer = printing.Printer(port, options, logging_stream, logger=logger)
377
378         _set_up_derived_options(port, options)
379         manager = Manager(port, options, printer)
380         printer.print_config(port.results_directory())
381
382         run_details = manager.run(args)
383         _log.debug("Testing completed, Exit status: %d" % run_details.exit_code)
384         return run_details
385     finally:
386         printer.cleanup()
387
388 if __name__ == '__main__':
389     sys.exit(main(sys.argv[1:], sys.stdout, sys.stderr))