Home | History | Annotate | Download | only in layout_tests
      1 #!/usr/bin/env python
      2 # Copyright (C) 2010 Google Inc. All rights reserved.
      3 # Copyright (C) 2010 Gabor Rapcsanyi (rgabor (at] inf.u-szeged.hu), University of Szeged
      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 """Run layout tests."""
     32 
     33 import errno
     34 import logging
     35 import optparse
     36 import os
     37 import signal
     38 import sys
     39 
     40 from layout_package import json_results_generator
     41 from layout_package import printing
     42 from layout_package import test_runner
     43 from layout_package import test_runner2
     44 
     45 from webkitpy.common.system import user
     46 from webkitpy.thirdparty import simplejson
     47 
     48 import port
     49 
     50 _log = logging.getLogger(__name__)
     51 
     52 
     53 def run(port, options, args, regular_output=sys.stderr,
     54         buildbot_output=sys.stdout):
     55     """Run the tests.
     56 
     57     Args:
     58       port: Port object for port-specific behavior
     59       options: a dictionary of command line options
     60       args: a list of sub directories or files to test
     61       regular_output: a stream-like object that we can send logging/debug
     62           output to
     63       buildbot_output: a stream-like object that we can write all output that
     64           is intended to be parsed by the buildbot to
     65     Returns:
     66       the number of unexpected results that occurred, or -1 if there is an
     67           error.
     68 
     69     """
     70     warnings = _set_up_derived_options(port, options)
     71 
     72     printer = printing.Printer(port, options, regular_output, buildbot_output,
     73         int(options.child_processes), options.experimental_fully_parallel)
     74     for w in warnings:
     75         _log.warning(w)
     76 
     77     if options.help_printing:
     78         printer.help_printing()
     79         printer.cleanup()
     80         return 0
     81 
     82     last_unexpected_results = _gather_unexpected_results(port)
     83     if options.print_last_failures:
     84         printer.write("\n".join(last_unexpected_results) + "\n")
     85         printer.cleanup()
     86         return 0
     87 
     88     # We wrap any parts of the run that are slow or likely to raise exceptions
     89     # in a try/finally to ensure that we clean up the logging configuration.
     90     num_unexpected_results = -1
     91     try:
     92         runner = test_runner2.TestRunner2(port, options, printer)
     93         runner._print_config()
     94 
     95         printer.print_update("Collecting tests ...")
     96         try:
     97             runner.collect_tests(args, last_unexpected_results)
     98         except IOError, e:
     99             if e.errno == errno.ENOENT:
    100                 return -1
    101             raise
    102 
    103         if options.lint_test_files:
    104             return runner.lint()
    105 
    106         printer.print_update("Parsing expectations ...")
    107         runner.parse_expectations()
    108 
    109         printer.print_update("Checking build ...")
    110         if not port.check_build(runner.needs_http()):
    111             _log.error("Build check failed")
    112             return -1
    113 
    114         result_summary = runner.set_up_run()
    115         if result_summary:
    116             num_unexpected_results = runner.run(result_summary)
    117             runner.clean_up_run()
    118             _log.debug("Testing completed, Exit status: %d" %
    119                        num_unexpected_results)
    120     finally:
    121         printer.cleanup()
    122 
    123     return num_unexpected_results
    124 
    125 
    126 def _set_up_derived_options(port_obj, options):
    127     """Sets the options values that depend on other options values."""
    128     # We return a list of warnings to print after the printer is initialized.
    129     warnings = []
    130 
    131     if options.worker_model is None:
    132         options.worker_model = port_obj.default_worker_model()
    133 
    134     if options.worker_model == 'inline':
    135         if options.child_processes and int(options.child_processes) > 1:
    136             warnings.append("--worker-model=inline overrides --child-processes")
    137         options.child_processes = "1"
    138     if not options.child_processes:
    139         options.child_processes = os.environ.get("WEBKIT_TEST_CHILD_PROCESSES",
    140                                                  str(port_obj.default_child_processes()))
    141 
    142     if not options.configuration:
    143         options.configuration = port_obj.default_configuration()
    144 
    145     if options.pixel_tests is None:
    146         options.pixel_tests = True
    147 
    148     if not options.use_apache:
    149         options.use_apache = sys.platform in ('darwin', 'linux2')
    150 
    151     if not options.time_out_ms:
    152         if options.configuration == "Debug":
    153             options.time_out_ms = str(2 * test_runner.TestRunner.DEFAULT_TEST_TIMEOUT_MS)
    154         else:
    155             options.time_out_ms = str(test_runner.TestRunner.DEFAULT_TEST_TIMEOUT_MS)
    156 
    157     options.slow_time_out_ms = str(5 * int(options.time_out_ms))
    158 
    159     if options.additional_platform_directory:
    160         normalized_platform_directories = []
    161         for path in options.additional_platform_directory:
    162             if not port_obj._filesystem.isabs(path):
    163                 warnings.append("--additional-platform-directory=%s is ignored since it is not absolute" % path)
    164                 continue
    165             normalized_platform_directories.append(port_obj._filesystem.normpath(path))
    166         options.additional_platform_directory = normalized_platform_directories
    167 
    168     return warnings
    169 
    170 
    171 def _gather_unexpected_results(port):
    172     """Returns the unexpected results from the previous run, if any."""
    173     filesystem = port._filesystem
    174     results_directory = port.results_directory()
    175     options = port._options
    176     last_unexpected_results = []
    177     if options.print_last_failures or options.retest_last_failures:
    178         unexpected_results_filename = filesystem.join(results_directory, "unexpected_results.json")
    179         if filesystem.exists(unexpected_results_filename):
    180             results = json_results_generator.load_json(filesystem, unexpected_results_filename)
    181             last_unexpected_results = results['tests'].keys()
    182     return last_unexpected_results
    183 
    184 
    185 def _compat_shim_callback(option, opt_str, value, parser):
    186     print "Ignoring unsupported option: %s" % opt_str
    187 
    188 
    189 def _compat_shim_option(option_name, **kwargs):
    190     return optparse.make_option(option_name, action="callback",
    191         callback=_compat_shim_callback,
    192         help="Ignored, for old-run-webkit-tests compat only.", **kwargs)
    193 
    194 
    195 def parse_args(args=None):
    196     """Provides a default set of command line args.
    197 
    198     Returns a tuple of options, args from optparse"""
    199 
    200     # FIXME: All of these options should be stored closer to the code which
    201     # FIXME: actually uses them. configuration_options should move
    202     # FIXME: to WebKitPort and be shared across all scripts.
    203     configuration_options = [
    204         optparse.make_option("-t", "--target", dest="configuration",
    205                              help="(DEPRECATED)"),
    206         # FIXME: --help should display which configuration is default.
    207         optparse.make_option('--debug', action='store_const', const='Debug',
    208                              dest="configuration",
    209                              help='Set the configuration to Debug'),
    210         optparse.make_option('--release', action='store_const',
    211                              const='Release', dest="configuration",
    212                              help='Set the configuration to Release'),
    213         # old-run-webkit-tests also accepts -c, --configuration CONFIGURATION.
    214     ]
    215 
    216     print_options = printing.print_options()
    217 
    218     # FIXME: These options should move onto the ChromiumPort.
    219     chromium_options = [
    220         optparse.make_option("--chromium", action="store_true", default=False,
    221             help="use the Chromium port"),
    222         optparse.make_option("--startup-dialog", action="store_true",
    223             default=False, help="create a dialog on DumpRenderTree startup"),
    224         optparse.make_option("--gp-fault-error-box", action="store_true",
    225             default=False, help="enable Windows GP fault error box"),
    226         optparse.make_option("--js-flags",
    227             type="string", help="JavaScript flags to pass to tests"),
    228         optparse.make_option("--stress-opt", action="store_true",
    229             default=False,
    230             help="Enable additional stress test to JavaScript optimization"),
    231         optparse.make_option("--stress-deopt", action="store_true",
    232             default=False,
    233             help="Enable additional stress test to JavaScript optimization"),
    234         optparse.make_option("--nocheck-sys-deps", action="store_true",
    235             default=False,
    236             help="Don't check the system dependencies (themes)"),
    237         optparse.make_option("--accelerated-compositing",
    238             action="store_true",
    239             help="Use hardware-accelerated compositing for rendering"),
    240         optparse.make_option("--no-accelerated-compositing",
    241             action="store_false",
    242             dest="accelerated_compositing",
    243             help="Don't use hardware-accelerated compositing for rendering"),
    244         optparse.make_option("--accelerated-2d-canvas",
    245             action="store_true",
    246             help="Use hardware-accelerated 2D Canvas calls"),
    247         optparse.make_option("--no-accelerated-2d-canvas",
    248             action="store_false",
    249             dest="accelerated_2d_canvas",
    250             help="Don't use hardware-accelerated 2D Canvas calls"),
    251         optparse.make_option("--enable-hardware-gpu",
    252             action="store_true",
    253             default=False,
    254             help="Run graphics tests on real GPU hardware vs software"),
    255     ]
    256 
    257     # Missing Mac-specific old-run-webkit-tests options:
    258     # FIXME: Need: -g, --guard for guard malloc support on Mac.
    259     # FIXME: Need: -l --leaks    Enable leaks checking.
    260     # FIXME: Need: --sample-on-timeout Run sample on timeout
    261 
    262     old_run_webkit_tests_compat = [
    263         # NRWT doesn't generate results by default anyway.
    264         _compat_shim_option("--no-new-test-results"),
    265         # NRWT doesn't sample on timeout yet anyway.
    266         _compat_shim_option("--no-sample-on-timeout"),
    267         # FIXME: NRWT needs to support remote links eventually.
    268         _compat_shim_option("--use-remote-links-to-tests"),
    269     ]
    270 
    271     results_options = [
    272         # NEED for bots: --use-remote-links-to-tests Link to test files
    273         # within the SVN repository in the results.
    274         optparse.make_option("-p", "--pixel-tests", action="store_true",
    275             dest="pixel_tests", help="Enable pixel-to-pixel PNG comparisons"),
    276         optparse.make_option("--no-pixel-tests", action="store_false",
    277             dest="pixel_tests", help="Disable pixel-to-pixel PNG comparisons"),
    278         optparse.make_option("--tolerance",
    279             help="Ignore image differences less than this percentage (some "
    280                 "ports may ignore this option)", type="float"),
    281         optparse.make_option("--results-directory", help="Location of test results"),
    282         optparse.make_option("--build-directory",
    283             help="Path to the directory under which build files are kept (should not include configuration)"),
    284         optparse.make_option("--new-baseline", action="store_true",
    285             default=False, help="Save all generated results as new baselines "
    286                  "into the platform directory, overwriting whatever's "
    287                  "already there."),
    288         optparse.make_option("--reset-results", action="store_true",
    289             default=False, help="Reset any existing baselines to the "
    290                  "generated results"),
    291         optparse.make_option("--additional-drt-flag", action="append",
    292             default=[], help="Additional command line flag to pass to DumpRenderTree "
    293                  "Specify multiple times to add multiple flags."),
    294         optparse.make_option("--additional-platform-directory", action="append",
    295             default=[], help="Additional directory where to look for test "
    296                  "baselines (will take precendence over platform baselines). "
    297                  "Specify multiple times to add multiple search path entries."),
    298         optparse.make_option("--no-show-results", action="store_false",
    299             default=True, dest="show_results",
    300             help="Don't launch a browser with results after the tests "
    301                  "are done"),
    302         # FIXME: We should have a helper function to do this sort of
    303         # deprectated mapping and automatically log, etc.
    304         optparse.make_option("--noshow-results", action="store_false",
    305             dest="show_results",
    306             help="Deprecated, same as --no-show-results."),
    307         optparse.make_option("--no-launch-safari", action="store_false",
    308             dest="show_results",
    309             help="old-run-webkit-tests compat, same as --noshow-results."),
    310         # old-run-webkit-tests:
    311         # --[no-]launch-safari    Launch (or do not launch) Safari to display
    312         #                         test results (default: launch)
    313         optparse.make_option("--full-results-html", action="store_true",
    314             default=False,
    315             help="Show all failures in results.html, rather than only "
    316                  "regressions"),
    317         optparse.make_option("--clobber-old-results", action="store_true",
    318             default=False, help="Clobbers test results from previous runs."),
    319         optparse.make_option("--platform",
    320             help="Override the platform for expected results"),
    321         optparse.make_option("--no-record-results", action="store_false",
    322             default=True, dest="record_results",
    323             help="Don't record the results."),
    324         # old-run-webkit-tests also has HTTP toggle options:
    325         # --[no-]http                     Run (or do not run) http tests
    326         #                                 (default: run)
    327     ]
    328 
    329     test_options = [
    330         optparse.make_option("--build", dest="build",
    331             action="store_true", default=True,
    332             help="Check to ensure the DumpRenderTree build is up-to-date "
    333                  "(default)."),
    334         optparse.make_option("--no-build", dest="build",
    335             action="store_false", help="Don't check to see if the "
    336                                        "DumpRenderTree build is up-to-date."),
    337         optparse.make_option("-n", "--dry-run", action="store_true",
    338             default=False,
    339             help="Do everything but actually run the tests or upload results."),
    340         # old-run-webkit-tests has --valgrind instead of wrapper.
    341         optparse.make_option("--wrapper",
    342             help="wrapper command to insert before invocations of "
    343                  "DumpRenderTree; option is split on whitespace before "
    344                  "running. (Example: --wrapper='valgrind --smc-check=all')"),
    345         # old-run-webkit-tests:
    346         # -i|--ignore-tests               Comma-separated list of directories
    347         #                                 or tests to ignore
    348         optparse.make_option("--test-list", action="append",
    349             help="read list of tests to run from file", metavar="FILE"),
    350         # old-run-webkit-tests uses --skipped==[default|ignore|only]
    351         # instead of --force:
    352         optparse.make_option("--force", action="store_true", default=False,
    353             help="Run all tests, even those marked SKIP in the test list"),
    354         optparse.make_option("--use-apache", action="store_true",
    355             default=False, help="Whether to use apache instead of lighttpd."),
    356         optparse.make_option("--time-out-ms",
    357             help="Set the timeout for each test"),
    358         # old-run-webkit-tests calls --randomize-order --random:
    359         optparse.make_option("--randomize-order", action="store_true",
    360             default=False, help=("Run tests in random order (useful "
    361                                 "for tracking down corruption)")),
    362         optparse.make_option("--run-chunk",
    363             help=("Run a specified chunk (n:l), the nth of len l, "
    364                  "of the layout tests")),
    365         optparse.make_option("--run-part", help=("Run a specified part (n:m), "
    366                   "the nth of m parts, of the layout tests")),
    367         # old-run-webkit-tests calls --batch-size: --nthly n
    368         #   Restart DumpRenderTree every n tests (default: 1000)
    369         optparse.make_option("--batch-size",
    370             help=("Run a the tests in batches (n), after every n tests, "
    371                   "DumpRenderTree is relaunched."), type="int", default=0),
    372         # old-run-webkit-tests calls --run-singly: -1|--singly
    373         # Isolate each test case run (implies --nthly 1 --verbose)
    374         optparse.make_option("--run-singly", action="store_true",
    375             default=False, help="run a separate DumpRenderTree for each test"),
    376         optparse.make_option("--child-processes",
    377             help="Number of DumpRenderTrees to run in parallel."),
    378         # FIXME: Display default number of child processes that will run.
    379         optparse.make_option("--worker-model", action="store",
    380             default=None, help=("controls worker model. Valid values are "
    381                                 "'inline', 'threads', and 'processes'.")),
    382         optparse.make_option("--experimental-fully-parallel",
    383             action="store_true", default=False,
    384             help="run all tests in parallel"),
    385         optparse.make_option("--exit-after-n-failures", type="int", default=500,
    386             help="Exit after the first N failures instead of running all "
    387             "tests"),
    388         optparse.make_option("--exit-after-n-crashes-or-timeouts", type="int",
    389             default=20, help="Exit after the first N crashes instead of "
    390             "running all tests"),
    391         # FIXME: consider: --iterations n
    392         #      Number of times to run the set of tests (e.g. ABCABCABC)
    393         optparse.make_option("--print-last-failures", action="store_true",
    394             default=False, help="Print the tests in the last run that "
    395             "had unexpected failures (or passes) and then exit."),
    396         optparse.make_option("--retest-last-failures", action="store_true",
    397             default=False, help="re-test the tests in the last run that "
    398             "had unexpected failures (or passes)."),
    399         optparse.make_option("--retry-failures", action="store_true",
    400             default=True,
    401             help="Re-try any tests that produce unexpected results (default)"),
    402         optparse.make_option("--no-retry-failures", action="store_false",
    403             dest="retry_failures",
    404             help="Don't re-try any tests that produce unexpected results."),
    405     ]
    406 
    407     misc_options = [
    408         optparse.make_option("--lint-test-files", action="store_true",
    409         default=False, help=("Makes sure the test files parse for all "
    410                             "configurations. Does not run any tests.")),
    411     ]
    412 
    413     # FIXME: Move these into json_results_generator.py
    414     results_json_options = [
    415         optparse.make_option("--master-name", help="The name of the buildbot master."),
    416         optparse.make_option("--builder-name", default="DUMMY_BUILDER_NAME",
    417             help=("The name of the builder shown on the waterfall running "
    418                   "this script e.g. WebKit.")),
    419         optparse.make_option("--build-name", default="DUMMY_BUILD_NAME",
    420             help=("The name of the builder used in its path, e.g. "
    421                   "webkit-rel.")),
    422         optparse.make_option("--build-number", default="DUMMY_BUILD_NUMBER",
    423             help=("The build number of the builder running this script.")),
    424         optparse.make_option("--test-results-server", default="",
    425             help=("If specified, upload results json files to this appengine "
    426                   "server.")),
    427     ]
    428 
    429     option_list = (configuration_options + print_options +
    430                    chromium_options + results_options + test_options +
    431                    misc_options + results_json_options +
    432                    old_run_webkit_tests_compat)
    433     option_parser = optparse.OptionParser(option_list=option_list)
    434 
    435     return option_parser.parse_args(args)
    436 
    437 
    438 def main():
    439     options, args = parse_args()
    440     port_obj = port.get(options.platform, options)
    441     return run(port_obj, options, args)
    442 
    443 
    444 if '__main__' == __name__:
    445     try:
    446         sys.exit(main())
    447     except KeyboardInterrupt:
    448         # this mirrors what the shell normally does
    449         sys.exit(signal.SIGINT + 128)
    450