diff options
Diffstat (limited to 'WebKitTools/Scripts')
34 files changed, 885 insertions, 204 deletions
diff --git a/WebKitTools/Scripts/build-webkit b/WebKitTools/Scripts/build-webkit index 4234905..cd43499 100755 --- a/WebKitTools/Scripts/build-webkit +++ b/WebKitTools/Scripts/build-webkit @@ -50,6 +50,7 @@ chdirWebKit(); my $showHelp = 0; my $clean = 0; my $minimal = 0; +my $v8 = 0; my $installHeaders; my $installLibs; my $prefixPath; @@ -197,9 +198,6 @@ my @features = ( { option => "xslt", desc => "Toggle XSLT support", define => "ENABLE_XSLT", default => 1, value => \$xsltSupport }, - { option => "file-writer", desc => "Toggle FileWriter support", - define => "ENABLE_FILE_WRITER", default => 0, value => \$fileWriterSupport }, - { option => "file-system", desc => "Toggle FileSystem support", define => "ENABLE_FILE_SYSTEM", default => 0, value => \$fileSystemSupport }, @@ -222,7 +220,8 @@ push @ARGV, split(/ /, $ENV{'BUILD_WEBKIT_ARGS'}) if ($ENV{'BUILD_WEBKIT_ARGS'}) foreach (@ARGV) { if ($_ eq '--minimal') { $minimal = 1; - last; + } elsif ($_ eq '--v8') { + $v8 = 1; } } @@ -250,6 +249,7 @@ Usage: $programName [options] [options to pass to build system] --install-headers=<path> Set installation path for the headers (Qt only) --install-libs=<path> Set installation path for the libraries (Qt only) + --v8 Use V8 as JavaScript engine (Qt only) --prefix=<path> Set installation prefix to the given path (Gtk only) --makeargs=<arguments> Optional Makefile flags @@ -266,6 +266,7 @@ my %options = ( 'prefix=s' => \$prefixPath, 'makeargs=s' => \$makeArgs, 'minimal' => \$minimal, + 'v8' => \$v8, ); # Build usage text and options list from features @@ -288,7 +289,7 @@ setConfiguration(); my $productDir = productDir(); # Remove 0 byte sized files from productDir after slave lost for Qt buildbots. -File::Find::find(\&unlinkZeroFiles, $productDir) if isQt(); +File::Find::find(\&unlinkZeroFiles, $productDir) if (isQt() && -e $productDir); sub unlinkZeroFiles () { @@ -395,6 +396,10 @@ if (isGtk()) { if ($minimal) { push @options, "CONFIG+=minimal"; } + + if ($v8) { + push @options, "CONFIG+=v8"; + } } # Force re-link of existing libraries if different than expected diff --git a/WebKitTools/Scripts/update-webgl-conformance-tests b/WebKitTools/Scripts/update-webgl-conformance-tests new file mode 100755 index 0000000..dfe20a1 --- /dev/null +++ b/WebKitTools/Scripts/update-webgl-conformance-tests @@ -0,0 +1,36 @@ +#!/usr/bin/env python +# Copyright (C) 2010 Google Inc. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +"""Wrapper around webkitpy/layout_tests/update-webgl-conformance-tests.py""" + +import webkitpy.layout_tests.update_webgl_conformance_tests +import sys + +if __name__ == '__main__': + sys.exit(webkitpy.layout_tests.update_webgl_conformance_tests.main()) diff --git a/WebKitTools/Scripts/update-webkit-chromium b/WebKitTools/Scripts/update-webkit-chromium index 8458f83..1db1826 100755 --- a/WebKitTools/Scripts/update-webkit-chromium +++ b/WebKitTools/Scripts/update-webkit-chromium @@ -29,13 +29,16 @@ # Update script for the WebKit Chromium Port. use File::Path; +use FindBin; use Getopt::Long; +use lib $FindBin::Bin; +use webkitdirs; chdir("WebKit/chromium") or die $!; # Find gclient or install it. my $gclientPath; -if (`gclient --version`) { +if (commandExists('gclient')) { $gclientPath = 'gclient'; } elsif (-e 'depot_tools/gclient') { $gclientPath = 'depot_tools/gclient'; diff --git a/WebKitTools/Scripts/webkitdirs.pm b/WebKitTools/Scripts/webkitdirs.pm index b4d3f60..2980750 100644 --- a/WebKitTools/Scripts/webkitdirs.pm +++ b/WebKitTools/Scripts/webkitdirs.pm @@ -258,6 +258,7 @@ sub jscPath($) my ($productDir) = @_; my $jscName = "jsc"; $jscName .= "_debug" if (isCygwin() && ($configuration eq "Debug")); + $jscName .= ".exe" if (isWindows() || isCygwin()); return "$productDir/$jscName" if -e "$productDir/$jscName"; return "$productDir/JavaScriptCore.framework/Resources/$jscName"; } diff --git a/WebKitTools/Scripts/webkitperl/VCSUtils_unittest/fixChangeLogPatch.pl b/WebKitTools/Scripts/webkitperl/VCSUtils_unittest/fixChangeLogPatch.pl index 2fd187b..ee258da 100644 --- a/WebKitTools/Scripts/webkitperl/VCSUtils_unittest/fixChangeLogPatch.pl +++ b/WebKitTools/Scripts/webkitperl/VCSUtils_unittest/fixChangeLogPatch.pl @@ -30,7 +30,7 @@ # Unit tests of VCSUtils::fixChangeLogPatch(). -use Test::Simple tests => 8; +use Test::Simple tests => 12; use VCSUtils; # The source ChangeLog for these tests is the following: @@ -58,6 +58,93 @@ my $in; my $out; # New test +$title = "fixChangeLogPatch: [no change] In-place change."; + +$in = <<'END'; +--- ChangeLog ++++ ChangeLog +@@ -1,5 +1,5 @@ + 2010-12-22 Bob <bob@email.address> + +- Reviewed by Sue. ++ Reviewed by Ray. + + Changed some code on 2010-12-22. +END + +ok(fixChangeLogPatch($in) eq $in, $title); + +# New test +$title = "fixChangeLogPatch: [no change] Remove first entry."; + +$in = <<'END'; +--- ChangeLog ++++ ChangeLog +@@ -1,11 +1,3 @@ +-2010-12-22 Bob <bob@email.address> +- +- Reviewed by Ray. +- +- Changed some code on 2010-12-22. +- +- * File: +- + 2010-12-22 Alice <alice@email.address> + + Reviewed by Ray. +END + +ok(fixChangeLogPatch($in) eq $in, $title); + +# New test +$title = "fixChangeLogPatch: [no change] Remove entry in the middle."; + +$in = <<'END'; +--- ChangeLog ++++ ChangeLog +@@@ -7,10 +7,6 @@ + + * File: + +-2010-12-22 Bob <bob@email.address> +- +- Changed some code on 2010-12-22. +- + 2010-12-22 Alice <alice@email.address> + + Reviewed by Ray. +END + +ok(fixChangeLogPatch($in) eq $in, $title); + +# New test +$title = "fixChangeLogPatch: [no change] Far apart changes (i.e. more than one chunk)."; + +$in = <<'END'; +--- ChangeLog ++++ ChangeLog +@@ -7,7 +7,7 @@ + + * File: + +-2010-12-22 Bob <bob@email.address> ++2010-12-22 Bobby <bob@email.address> + + Changed some code on 2010-12-22. + +@@ -21,7 +21,7 @@ + + * File2: + +-2010-12-21 Bob <bob@email.address> ++2010-12-21 Bobby <bob@email.address> + + Changed some code on 2010-12-21. +END + +ok(fixChangeLogPatch($in) eq $in, $title); + +# New test $title = "fixChangeLogPatch: [no change] First line is new line."; $in = <<'END'; diff --git a/WebKitTools/Scripts/webkitpy/common/config/committers.py b/WebKitTools/Scripts/webkitpy/common/config/committers.py index 6a45cab..113131f 100644 --- a/WebKitTools/Scripts/webkitpy/common/config/committers.py +++ b/WebKitTools/Scripts/webkitpy/common/config/committers.py @@ -77,6 +77,7 @@ committers_unable_to_review = [ Committer("Andy Estes", "aestes@apple.com", "estes"), Committer("Anthony Ricaud", "rik@webkit.org", "rik"), Committer("Anton Muhin", "antonm@chromium.org", "antonm"), + Committer("Balazs Kelemen", "kbalazs@webkit.org", "kbalazs"), Committer("Ben Murdoch", "benm@google.com", "benm"), Committer("Benjamin C Meyer", ["ben@meyerhome.net", "ben@webkit.org"], "icefox"), Committer("Benjamin Otte", ["otte@gnome.org", "otte@webkit.org"], "otte"), @@ -92,7 +93,6 @@ committers_unable_to_review = [ Committer("Chris Rogers", "crogers@google.com", "crogers"), Committer("Christian Dywan", ["christian@twotoasts.de", "christian@webkit.org"]), Committer("Collin Jackson", "collinj@webkit.org"), - Committer("Csaba Osztrogonac", "ossy@webkit.org", "ossy"), Committer("David Smith", ["catfish.man@gmail.com", "dsmith@webkit.org"], "catfishman"), Committer("Dean Jackson", "dino@apple.com", "dino"), Committer("Diego Gonzalez", ["diegohcg@webkit.org", "diego.gonzalez@openbossa.org"], "diegohcg"), @@ -148,6 +148,7 @@ committers_unable_to_review = [ Committer("Maxime Simon", ["simon.maxime@gmail.com", "maxime.simon@webkit.org"], "maxime.simon"), Committer("Michael Saboff", "msaboff@apple.com"), Committer("Michelangelo De Simone", "michelangelo@webkit.org", "michelangelo"), + Committer("Mihai Parparita", "mihaip@chromium.org", "mihaip"), Committer("Mike Belshe", ["mbelshe@chromium.org", "mike@belshe.com"]), Committer("Mike Fenton", ["mifenton@rim.com", "mike.fenton@torchmobile.com"], "mfenton"), Committer("Mike Thole", ["mthole@mikethole.com", "mthole@apple.com"]), @@ -156,6 +157,7 @@ committers_unable_to_review = [ Committer("Nico Weber", ["thakis@chromium.org", "thakis@google.com"], "thakis"), Committer("Noam Rosenthal", "noam.rosenthal@nokia.com", "noamr"), Committer("Pam Greene", "pam@chromium.org", "pamg"), + Committer("Patrick Gansterer", ["paroga@paroga.com", "paroga@webkit.org"], "paroga"), Committer("Peter Kasting", ["pkasting@google.com", "pkasting@chromium.org"], "pkasting"), Committer("Philippe Normand", ["pnormand@igalia.com", "philn@webkit.org"], "philn-tp"), Committer("Pierre d'Herbemont", ["pdherbemont@free.fr", "pdherbemont@apple.com"], "pdherbemont"), @@ -211,6 +213,7 @@ reviewers_list = [ Reviewer("Chris Marrin", "cmarrin@apple.com", "cmarrin"), Reviewer("Chris Fleizach", "cfleizach@apple.com", "cfleizach"), Reviewer("Chris Jerdonek", "cjerdonek@webkit.org", "cjerdonek"), + Reviewer(u"Csaba Osztrogon\u00e1c", "ossy@webkit.org", "ossy"), Reviewer("Dan Bernstein", ["mitz@webkit.org", "mitz@apple.com"], "mitzpettel"), Reviewer("Daniel Bates", "dbates@webkit.org", "dydz"), Reviewer("Darin Adler", "darin@apple.com", "darin"), diff --git a/WebKitTools/Scripts/webkitpy/common/net/bugzilla.py b/WebKitTools/Scripts/webkitpy/common/net/bugzilla.py index 2b2258e..cc64fac 100644 --- a/WebKitTools/Scripts/webkitpy/common/net/bugzilla.py +++ b/WebKitTools/Scripts/webkitpy/common/net/bugzilla.py @@ -175,6 +175,20 @@ class Bug(object): def is_unassigned(self): return self.assigned_to_email() in self.unassigned_emails + def status(self): + return self.bug_dictionary["bug_status"] + + # Bugzilla has many status states we don't really use in WebKit: + # https://bugs.webkit.org/page.cgi?id=fields.html#status + _open_states = ["UNCONFIRMED", "NEW", "ASSIGNED", "REOPENED"] + _closed_states = ["RESOLVED", "VERIFIED", "CLOSED"] + + def is_open(self): + return self.status() in self._open_states + + def is_closed(self): + return not self.is_open() + # Rarely do we actually want obsolete attachments def attachments(self, include_obsolete=False): attachments = self.bug_dictionary["attachments"] @@ -357,15 +371,14 @@ class CommitterValidator(object): return False return True + def _reject_patch_if_flags_are_invalid(self, patch): + return (self._validate_setter_email( + patch, "reviewer", self.reject_patch_from_review_queue) + and self._validate_setter_email( + patch, "committer", self.reject_patch_from_commit_queue)) + def patches_after_rejecting_invalid_commiters_and_reviewers(self, patches): - validated_patches = [] - for patch in patches: - if (self._validate_setter_email( - patch, "reviewer", self.reject_patch_from_review_queue) - and self._validate_setter_email( - patch, "committer", self.reject_patch_from_commit_queue)): - validated_patches.append(patch) - return validated_patches + return [patch for patch in patches if self._reject_patch_if_flags_are_invalid(patch)] def reject_patch_from_commit_queue(self, attachment_id, @@ -500,6 +513,7 @@ class Bugzilla(object): bug = {} bug["id"] = int(soup.find("bug_id").string) bug["title"] = self._string_contents(soup.find("short_desc")) + bug["bug_status"] = self._string_contents(soup.find("bug_status")) bug["reporter_email"] = self._string_contents(soup.find("reporter")) bug["assigned_to_email"] = self._string_contents(soup.find("assigned_to")) bug["cc_emails"] = [self._string_contents(element) diff --git a/WebKitTools/Scripts/webkitpy/common/net/bugzilla_unittest.py b/WebKitTools/Scripts/webkitpy/common/net/bugzilla_unittest.py index 280696e..32f23cd 100644 --- a/WebKitTools/Scripts/webkitpy/common/net/bugzilla_unittest.py +++ b/WebKitTools/Scripts/webkitpy/common/net/bugzilla_unittest.py @@ -213,6 +213,7 @@ ZEZpbmlzaExvYWRXaXRoUmVhc29uOnJlYXNvbl07Cit9CisKIEBlbmQKIAogI2VuZGlmCg== "cc_emails" : ["foo@bar.com", "example@example.com"], "reporter_email" : "eric@webkit.org", "assigned_to_email" : "webkit-unassigned@lists.webkit.org", + "bug_status": "NEW", "attachments" : [{ "attach_date": datetime.datetime(2009, 12, 27, 23, 51), 'name': u'Patch', diff --git a/WebKitTools/Scripts/webkitpy/common/net/statusserver.py b/WebKitTools/Scripts/webkitpy/common/net/statusserver.py index c8fced6..57390b8 100644 --- a/WebKitTools/Scripts/webkitpy/common/net/statusserver.py +++ b/WebKitTools/Scripts/webkitpy/common/net/statusserver.py @@ -96,6 +96,11 @@ class StatusServer: self.browser["work_items"] = " ".join(work_items) return self.browser.submit().read() + def next_work_item(self, queue_name): + _log.debug("Fetching next work item for %s" % queue_name) + patch_status_url = "%s/next-patch/%s" % (self.url, queue_name) + return self._fetch_url(patch_status_url) + def update_work_items(self, queue_name, work_items): _log.debug("Recording work items: %s for %s" % (work_items, queue_name)) return NetworkTransaction().run(lambda: self._post_work_items_to_server(queue_name, work_items)) diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/deduplicate_tests.py b/WebKitTools/Scripts/webkitpy/layout_tests/deduplicate_tests.py index c543d91..51dcac8 100644 --- a/WebKitTools/Scripts/webkitpy/layout_tests/deduplicate_tests.py +++ b/WebKitTools/Scripts/webkitpy/layout_tests/deduplicate_tests.py @@ -36,8 +36,10 @@ import os import subprocess import sys import re +import webkitpy.common.checkout.scm as scm import webkitpy.common.system.executive as executive import webkitpy.common.system.logutils as logutils +import webkitpy.common.system.ospath as ospath import webkitpy.layout_tests.port.factory as port_factory _log = logutils.get_logger(__file__) @@ -52,11 +54,14 @@ def port_fallbacks(): back on. All platforms fall back on 'base'. """ fallbacks = {_BASE_PLATFORM: []} - for port_name in os.listdir(os.path.join('LayoutTests', 'platform')): + platform_dir = os.path.join(scm.find_checkout_root(), 'LayoutTests', + 'platform') + for port_name in os.listdir(platform_dir): try: platforms = port_factory.get(port_name).baseline_search_path() except NotImplementedError: - _log.error("'%s' lacks baseline_search_path(), please fix." % port_name) + _log.error("'%s' lacks baseline_search_path(), please fix." + % port_name) fallbacks[port_name] = [_BASE_PLATFORM] continue fallbacks[port_name] = [os.path.basename(p) for p in platforms][1:] @@ -102,7 +107,8 @@ def cluster_file_hashes(glob_pattern): # Fill in the map. cmd = ('git', 'ls-tree', '-r', 'HEAD', 'LayoutTests') try: - git_output = executive.Executive().run_command(cmd) + git_output = executive.Executive().run_command(cmd, + cwd=scm.find_checkout_root()) except OSError, e: if e.errno == 2: # No such file or directory. _log.error("Error: 'No such file' when running git.") @@ -156,11 +162,28 @@ def has_intermediate_results(test, fallbacks, matching_platform, return False -def find_dups(hashes, port_fallbacks): +def get_relative_test_path(filename, relative_to, + checkout_root=scm.find_checkout_root()): + """Constructs a relative path to |filename| from |relative_to|. + Args: + filename: The test file we're trying to get a relative path to. + relative_to: The absolute path we're relative to. + Returns: + A relative path to filename or None if |filename| is not below + |relative_to|. + """ + layout_test_dir = os.path.join(checkout_root, 'LayoutTests') + abs_path = os.path.join(layout_test_dir, filename) + return ospath.relpath(abs_path, relative_to) + + +def find_dups(hashes, port_fallbacks, relative_to): """Yields info about redundant test expectations. Args: hashes: a list of hashes as returned by cluster_file_hashes. - port_fallbacks: a list of fallback information as returned by get_port_fallbacks. + port_fallbacks: a list of fallback information as returned by + get_port_fallbacks. + relative_to: the directory that we want the results relative to Returns: a tuple containing (test, platform, fallback, platforms) """ @@ -176,13 +199,24 @@ def find_dups(hashes, port_fallbacks): # See if any of the platforms are redundant with each other. for platform in platforms.keys(): for fallback in port_fallbacks[platform]: - if fallback in platforms.keys(): - # We have to verify that there isn't an intermediate result - # that causes this duplicate hash to exist. - if not has_intermediate_results(test, - port_fallbacks[platform], fallback): - path = os.path.join('LayoutTests', platforms[platform]) - yield test, platform, fallback, path + if fallback not in platforms.keys(): + continue + # We have to verify that there isn't an intermediate result + # that causes this duplicate hash to exist. + if has_intermediate_results(test, port_fallbacks[platform], + fallback): + continue + # We print the relative path so it's easy to pipe the results + # to xargs rm. + path = get_relative_test_path(platforms[platform], relative_to) + if not path: + continue + yield { + 'test': test, + 'platform': platform, + 'fallback': fallback, + 'path': path, + } def deduplicate(glob_pattern): @@ -194,5 +228,4 @@ def deduplicate(glob_pattern): """ fallbacks = port_fallbacks() hashes = cluster_file_hashes(glob_pattern) - return [{'test': test, 'path': path, 'platform': platform, 'fallback': fallback} - for test, platform, fallback, path in find_dups(hashes, fallbacks)] + return list(find_dups(hashes, fallbacks, os.getcwd())) diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/deduplicate_tests_unittest.py b/WebKitTools/Scripts/webkitpy/layout_tests/deduplicate_tests_unittest.py index be2e381..bb9604f 100644 --- a/WebKitTools/Scripts/webkitpy/layout_tests/deduplicate_tests_unittest.py +++ b/WebKitTools/Scripts/webkitpy/layout_tests/deduplicate_tests_unittest.py @@ -186,3 +186,22 @@ class ListDuplicatesTest(unittest.TestCase): 'fallback': 'chromium-win', 'platform': 'chromium-linux'}, result[0]) + + def test_get_relative_test_path(self): + checkout_root = scm.find_checkout_root() + layout_test_dir = os.path.join(checkout_root, 'LayoutTests') + test_cases = ( + ('platform/mac/test.html', + ('platform/mac/test.html', layout_test_dir)), + ('LayoutTests/platform/mac/test.html', + ('platform/mac/test.html', checkout_root)), + (None, + ('platform/mac/test.html', os.path.join(checkout_root, 'WebCore'))), + ('test.html', + ('platform/mac/test.html', os.path.join(layout_test_dir, 'platform/mac'))), + (None, + ('platform/mac/test.html', os.path.join(layout_test_dir, 'platform/win'))), + ) + for expected, inputs in test_cases: + self.assertEquals(expected, + deduplicate_tests.get_relative_test_path(*inputs)) diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/layout_package/dump_render_tree_thread.py b/WebKitTools/Scripts/webkitpy/layout_tests/layout_package/dump_render_tree_thread.py index 9b963ca..970de60 100644 --- a/WebKitTools/Scripts/webkitpy/layout_tests/layout_package/dump_render_tree_thread.py +++ b/WebKitTools/Scripts/webkitpy/layout_tests/layout_package/dump_render_tree_thread.py @@ -72,20 +72,19 @@ def log_stack(stack): _log.error(' %s' % line.strip()) -def _process_output(port, test_info, test_types, test_args, configuration, - output_dir, crash, timeout, test_run_time, actual_checksum, +def _process_output(port, options, test_info, test_types, test_args, + crash, timeout, test_run_time, actual_checksum, output, error): """Receives the output from a DumpRenderTree process, subjects it to a number of tests, and returns a list of failure types the test produced. Args: port: port-specific hooks + options: command line options argument from optparse proc: an active DumpRenderTree process test_info: Object containing the test filename, uri and timeout test_types: list of test types to subject the output to test_args: arguments to be passed to each test - configuration: Debug or Release - output_dir: directory to put crash stack traces into Returns: a TestResult object """ @@ -106,7 +105,8 @@ def _process_output(port, test_info, test_types, test_args, configuration, _log.debug("Stacktrace for %s:\n%s" % (test_info.filename, error)) # Strip off "file://" since RelativeTestFilename expects # filesystem paths. - filename = os.path.join(output_dir, port.relative_test_filename( + filename = os.path.join(options.results_directory, + port.relative_test_filename( test_info.filename)) filename = os.path.splitext(filename)[0] + "-stack.txt" port.maybe_make_directory(os.path.split(filename)[0]) @@ -122,7 +122,7 @@ def _process_output(port, test_info, test_types, test_args, configuration, start_diff_time = time.time() new_failures = test_type.compare_output(port, test_info.filename, output, local_test_args, - configuration) + options.configuration) # Don't add any more failures if we already have a crash, so we don't # double-report those tests. We do double-report for timeouts since # we still want to see the text and image output. @@ -166,25 +166,23 @@ class TestResult(object): class SingleTestThread(threading.Thread): """Thread wrapper for running a single test file.""" - def __init__(self, port, image_path, shell_args, test_info, - test_types, test_args, configuration, output_dir): + def __init__(self, port, options, test_info, test_types, test_args): """ Args: port: object implementing port-specific hooks + options: command line argument object from optparse test_info: Object containing the test filename, uri and timeout - output_dir: Directory to put crash stacks into. - See TestShellThread for documentation of the remaining arguments. + test_types: A list of TestType objects to run the test output + against. + test_args: A TestArguments object to pass to each TestType. """ threading.Thread.__init__(self) self._port = port - self._image_path = image_path - self._shell_args = shell_args + self._options = options self._test_info = test_info self._test_types = test_types self._test_args = test_args - self._configuration = configuration - self._output_dir = output_dir self._driver = None def run(self): @@ -194,17 +192,17 @@ class SingleTestThread(threading.Thread): # FIXME: this is a separate routine to work around a bug # in coverage: see http://bitbucket.org/ned/coveragepy/issue/85. test_info = self._test_info - self._driver = self._port.create_driver(self._image_path, - self._shell_args) + self._driver = self._port.create_driver(self._test_args.png_path, + self._options) self._driver.start() start = time.time() crash, timeout, actual_checksum, output, error = \ self._driver.run_test(test_info.uri.strip(), test_info.timeout, test_info.image_hash()) end = time.time() - self._test_result = _process_output(self._port, + self._test_result = _process_output(self._port, self._options, test_info, self._test_types, self._test_args, - self._configuration, self._output_dir, crash, timeout, end - start, + crash, timeout, end - start, actual_checksum, output, error) self._driver.stop() @@ -248,12 +246,13 @@ class WatchableThread(threading.Thread): class TestShellThread(WatchableThread): - def __init__(self, port, filename_list_queue, result_queue, - test_types, test_args, image_path, shell_args, options): + def __init__(self, port, options, filename_list_queue, result_queue, + test_types, test_args): """Initialize all the local state for this DumpRenderTree thread. Args: port: interface to port-specific hooks + options: command line options argument from optparse filename_list_queue: A thread safe Queue class that contains lists of tuples of (filename, uri) pairs. result_queue: A thread safe Queue class that will contain tuples of @@ -261,22 +260,17 @@ class TestShellThread(WatchableThread): test_types: A list of TestType objects to run the test output against. test_args: A TestArguments object to pass to each TestType. - shell_args: Any extra arguments to be passed to DumpRenderTree. - options: A property dictionary as produced by optparse. The - command-line options should match those expected by - run_webkit_tests; they are typically passed via the - run_webkit_tests.TestRunner class.""" + + """ WatchableThread.__init__(self) self._port = port + self._options = options self._filename_list_queue = filename_list_queue self._result_queue = result_queue self._filename_list = [] self._test_types = test_types self._test_args = test_args self._driver = None - self._image_path = image_path - self._shell_args = shell_args - self._options = options self._directory_timing_stats = {} self._test_results = [] self._num_tests = 0 @@ -433,13 +427,11 @@ class TestShellThread(WatchableThread): A TestResult """ - worker = SingleTestThread(self._port, self._image_path, - self._shell_args, + worker = SingleTestThread(self._port, + self._options, test_info, self._test_types, - self._test_args, - self._options.configuration, - self._options.results_directory) + self._test_args) worker.start() @@ -503,11 +495,11 @@ class TestShellThread(WatchableThread): self._driver.run_test(test_info.uri, test_info.timeout, image_hash) end = time.time() - result = _process_output(self._port, test_info, self._test_types, - self._test_args, self._options.configuration, - self._options.results_directory, crash, - timeout, end - start, actual_checksum, - output, error) + result = _process_output(self._port, self._options, + test_info, self._test_types, + self._test_args, crash, + timeout, end - start, actual_checksum, + output, error) self._test_results.append(result) return result @@ -521,7 +513,8 @@ class TestShellThread(WatchableThread): # poll() is not threadsafe and can throw OSError due to: # http://bugs.python.org/issue1731717 if (not self._driver or self._driver.poll() is not None): - self._driver = self._port.create_driver(self._image_path, self._shell_args) + self._driver = self._port.create_driver(self._test_args.png_path, + self._options) self._driver.start() def _kill_dump_render_tree(self): diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/port/base.py b/WebKitTools/Scripts/webkitpy/layout_tests/port/base.py index 9125f9e..70beac3 100644 --- a/WebKitTools/Scripts/webkitpy/layout_tests/port/base.py +++ b/WebKitTools/Scripts/webkitpy/layout_tests/port/base.py @@ -152,6 +152,16 @@ class Port(object): While this is a generic routine, we include it in the Port interface so that it can be overriden for testing purposes.""" + + # The filenames show up in the diff output, make sure they're + # raw bytes and not unicode, so that they don't trigger join() + # trying to decode the input. + def to_raw_bytes(str): + if isinstance(str, unicode): + return str.encode('utf-8') + return str + expected_filename = to_raw_bytes(expected_filename) + actual_filename = to_raw_bytes(actual_filename) diff = difflib.unified_diff(expected_text.splitlines(True), actual_text.splitlines(True), expected_filename, @@ -364,7 +374,7 @@ class Port(object): results_filename in a users' browser.""" raise NotImplementedError('Port.show_html_results_file') - def create_driver(self, png_path, options): + def create_driver(self, image_path, options): """Return a newly created base.Driver subclass for starting/stopping the test driver.""" raise NotImplementedError('Port.create_driver') @@ -678,7 +688,7 @@ class Port(object): class Driver: """Abstract interface for the DumpRenderTree interface.""" - def __init__(self, port, png_path, options): + def __init__(self, port, png_path, options, executive): """Initialize a Driver to subsequently run tests. Typically this routine will spawn DumpRenderTree in a config @@ -688,7 +698,10 @@ class Driver: png_path - an absolute path for the driver to write any image data for a test (as a PNG). If no path is provided, that indicates that pixel test results will not be checked. - options - any port-specific driver options.""" + options - command line options argument from optparse + executive - reference to the process-wide Executive object + + """ raise NotImplementedError('Driver.__init__') def run_test(self, uri, timeout, checksum): diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/port/base_unittest.py b/WebKitTools/Scripts/webkitpy/layout_tests/port/base_unittest.py index 1cc426f..780cd22 100644 --- a/WebKitTools/Scripts/webkitpy/layout_tests/port/base_unittest.py +++ b/WebKitTools/Scripts/webkitpy/layout_tests/port/base_unittest.py @@ -66,7 +66,7 @@ class UnitTestPort(base.Port): def _open_configuration_file(self): if self._configuration_contents: return NewStringIO(self._configuration_contents) - return base.Port._open_configuration_file(self) + raise IOError class PortTest(unittest.TestCase): @@ -163,6 +163,33 @@ class PortTest(unittest.TestCase): self.assertFalse(base._wdiff_available) base._wdiff_available = True + def test_diff_text(self): + port = base.Port() + # Make sure that we don't run into decoding exceptions when the + # filenames are unicode, with regular or malformed input (expected or + # actual input is always raw bytes, not unicode). + port.diff_text('exp', 'act', 'exp.txt', 'act.txt') + port.diff_text('exp', 'act', u'exp.txt', 'act.txt') + port.diff_text('exp', 'act', u'a\xac\u1234\u20ac\U00008000', 'act.txt') + + port.diff_text('exp' + chr(255), 'act', 'exp.txt', 'act.txt') + port.diff_text('exp' + chr(255), 'act', u'exp.txt', 'act.txt') + + # Though expected and actual files should always be read in with no + # encoding (and be stored as str objects), test unicode inputs just to + # be safe. + port.diff_text(u'exp', 'act', 'exp.txt', 'act.txt') + port.diff_text( + u'a\xac\u1234\u20ac\U00008000', 'act', 'exp.txt', 'act.txt') + + # And make sure we actually get diff output. + diff = port.diff_text('foo', 'bar', 'exp.txt', 'act.txt') + self.assertTrue('foo' in diff) + self.assertTrue('bar' in diff) + self.assertTrue('exp.txt' in diff) + self.assertTrue('act.txt' in diff) + self.assertFalse('nosuchthing' in diff) + def test_default_configuration_notfound(self): port = UnitTestPort() self.assertEqual(port.default_configuration(), "Release") @@ -223,8 +250,8 @@ class VirtualTest(unittest.TestCase): self.assertVirtual(port._shut_down_http_server, None) def test_virtual_driver_method(self): - self.assertRaises(NotImplementedError, base.Driver, base.Port, "", None) - self.assertVirtual(base.Driver, base.Port, "", None) + self.assertRaises(NotImplementedError, base.Driver, base.Port(), + "", None, None) def test_virtual_driver_methods(self): class VirtualDriver(base.Driver): diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/port/chromium.py b/WebKitTools/Scripts/webkitpy/layout_tests/port/chromium.py index 896eab1..3fc4613 100644 --- a/WebKitTools/Scripts/webkitpy/layout_tests/port/chromium.py +++ b/WebKitTools/Scripts/webkitpy/layout_tests/port/chromium.py @@ -194,13 +194,11 @@ class ChromiumPort(base.Port): def create_driver(self, image_path, options): """Starts a new Driver and returns a handle to it.""" - if self._options.use_drt and sys.platform == 'darwin': - return webkit.WebKitDriver(self, image_path, options, executive=self._executive) - if self._options.use_drt: - options += ['--test-shell'] - else: - options += ['--layout-tests'] - return ChromiumDriver(self, image_path, options, executive=self._executive) + if options.use_drt and sys.platform == 'darwin': + return webkit.WebKitDriver(self, image_path, options, + executive=self._executive) + return ChromiumDriver(self, image_path, options, + executive=self._executive) def start_helper(self): helper_path = self._path_to_helper() @@ -337,22 +335,32 @@ class ChromiumDriver(base.Driver): def __init__(self, port, image_path, options, executive=Executive()): self._port = port - self._configuration = port._options.configuration - # FIXME: _options is very confusing, because it's not an Options() element. - # FIXME: These don't need to be passed into the constructor, but could rather - # be passed into .start() self._options = options self._image_path = image_path self._executive = executive + def _driver_args(self): + driver_args = [] + if self._image_path: + driver_args.append("--pixel-tests=" + self._image_path) + + if self._options.use_drt: + driver_args.append('--test-shell') + else: + driver_args.append('--layout-tests') + + if self._options.startup_dialog: + driver_args.append('--testshell-startup-dialog') + + if self._options.gp_fault_error_box: + driver_args.append('--gp-fault-error-box') + return driver_args + def start(self): # FIXME: Should be an error to call this method twice. - cmd = [] - # FIXME: We should not be grabbing at self._port._options.wrapper directly. - cmd += self._command_wrapper(self._port._options.wrapper) - cmd += [self._port._path_to_driver()] - if self._options: - cmd += self._options + cmd = self._command_wrapper(self._options.wrapper) + cmd.append(self._port._path_to_driver()) + cmd += self._driver_args() # We need to pass close_fds=True to work around Python bug #2320 # (otherwise we can hang when we kill DumpRenderTree when we are running diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/port/dryrun.py b/WebKitTools/Scripts/webkitpy/layout_tests/port/dryrun.py index 1af01ad..4940e4c 100644 --- a/WebKitTools/Scripts/webkitpy/layout_tests/port/dryrun.py +++ b/WebKitTools/Scripts/webkitpy/layout_tests/port/dryrun.py @@ -86,6 +86,7 @@ class DryRunPort(object): port_name = port_name[len(pfx):] else: port_name = None + self._options = options self.__delegate = factory.get(port_name, options) def __getattr__(self, name): @@ -116,16 +117,17 @@ class DryRunPort(object): pass def create_driver(self, image_path, options): - return DryrunDriver(self, image_path, options) + return DryrunDriver(self, image_path, options, executive=None) class DryrunDriver(base.Driver): """Dryrun implementation of the DumpRenderTree / Driver interface.""" - def __init__(self, port, image_path, test_driver_options): + def __init__(self, port, image_path, options, executive): self._port = port - self._driver_options = test_driver_options + self._options = options self._image_path = image_path + self._executive = executive self._layout_tests_dir = None def poll(self): diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/port/test.py b/WebKitTools/Scripts/webkitpy/layout_tests/port/test.py index a3a16c3..2ccddb0 100644 --- a/WebKitTools/Scripts/webkitpy/layout_tests/port/test.py +++ b/WebKitTools/Scripts/webkitpy/layout_tests/port/test.py @@ -97,7 +97,7 @@ class TestPort(base.Port): pass def create_driver(self, image_path, options): - return TestDriver(image_path, options, self) + return TestDriver(self, image_path, options, executive=None) def start_http_server(self): pass @@ -139,10 +139,11 @@ class TestPort(base.Port): class TestDriver(base.Driver): """Test/Dummy implementation of the DumpRenderTree interface.""" - def __init__(self, image_path, test_driver_options, port): - self._driver_options = test_driver_options - self._image_path = image_path + def __init__(self, port, image_path, options, executive): self._port = port + self._image_path = image_path + self._options = options + self._executive = executive self._image_written = False def poll(self): @@ -204,7 +205,7 @@ class TestDriver(base.Driver): crash = False timeout = False output = basename + '-txt\n' - if self._port.options().pixel_tests and ( + if self._options.pixel_tests and ( 'image' in basename or 'check' in basename): checksum = basename + '-checksum\n' with open(self._image_path, "w") as f: diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/port/webkit.py b/WebKitTools/Scripts/webkitpy/layout_tests/port/webkit.py index b085ceb..88c9bdf 100644 --- a/WebKitTools/Scripts/webkitpy/layout_tests/port/webkit.py +++ b/WebKitTools/Scripts/webkitpy/layout_tests/port/webkit.py @@ -199,7 +199,8 @@ class WebKitPort(base.Port): webbrowser.open(uri, new=1) def create_driver(self, image_path, options): - return WebKitDriver(self, image_path, options, executive=self._executive) + return WebKitDriver(self, image_path, options, + executive=self._executive) def test_base_platform_names(self): # At the moment we don't use test platform names, but we have @@ -405,22 +406,31 @@ class WebKitPort(base.Port): class WebKitDriver(base.Driver): """WebKit implementation of the DumpRenderTree interface.""" - def __init__(self, port, image_path, driver_options, executive=Executive()): + def __init__(self, port, image_path, options, executive=Executive()): self._port = port - # FIXME: driver_options is never used. self._image_path = image_path + self._options = options + self._executive = executive self._driver_tempdir = tempfile.mkdtemp(prefix='DumpRenderTree-') def __del__(self): shutil.rmtree(self._driver_tempdir) + def _driver_args(self): + driver_args = [] + if self._image_path: + driver_args.append('--pixel-tests') + + # These are used by the Chromium DRT port + if self._options.use_drt: + driver_args.append('--test-shell') + return driver_args + def start(self): - command = [] - # FIXME: We should not be grabbing at self._port._options.wrapper directly. - command += self._command_wrapper(self._port._options.wrapper) + command = self._command_wrapper(self._options.wrapper) command += [self._port._path_to_driver(), '-'] - if self._image_path: - command.append('--pixel-tests') + command += self._driver_args() + environment = self._port.setup_environ_for_server() environment['DYLD_FRAMEWORK_PATH'] = self._port._build_path() environment['DUMPRENDERTREE_TEMP'] = self._driver_tempdir diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/run_webkit_tests.py b/WebKitTools/Scripts/webkitpy/layout_tests/run_webkit_tests.py index 2e2da6d..14d4f0e 100755 --- a/WebKitTools/Scripts/webkitpy/layout_tests/run_webkit_tests.py +++ b/WebKitTools/Scripts/webkitpy/layout_tests/run_webkit_tests.py @@ -564,27 +564,18 @@ class TestRunner: filename_queue.put(item) return filename_queue - def _get_dump_render_tree_args(self, index): + def _get_test_args(self, index): """Returns the tuple of arguments for tests and for DumpRenderTree.""" - shell_args = [] test_args = test_type_base.TestArguments() - png_path = None + test_args.png_path = None if self._options.pixel_tests: png_path = os.path.join(self._options.results_directory, "png_result%s.png" % index) - shell_args.append("--pixel-tests=" + png_path) test_args.png_path = png_path - test_args.new_baseline = self._options.new_baseline test_args.reset_results = self._options.reset_results - if self._options.startup_dialog: - shell_args.append('--testshell-startup-dialog') - - if self._options.gp_fault_error_box: - shell_args.append('--gp-fault-error-box') - - return test_args, png_path, shell_args + return test_args def _contains_tests(self, subdir): for test_file in self._test_files: @@ -610,11 +601,10 @@ class TestRunner: test_types.append(test_type(self._port, self._options.results_directory)) - test_args, png_path, shell_args = \ - self._get_dump_render_tree_args(i) + test_args = self._get_test_args(i) thread = dump_render_tree_thread.TestShellThread(self._port, - filename_queue, self._result_queue, test_types, test_args, - png_path, shell_args, self._options) + self._options, filename_queue, self._result_queue, + test_types, test_args) if self._is_single_threaded(): thread.run_in_main_thread(self, result_summary) else: diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/update_webgl_conformance_tests.py b/WebKitTools/Scripts/webkitpy/layout_tests/update_webgl_conformance_tests.py new file mode 100755 index 0000000..f4c8098 --- /dev/null +++ b/WebKitTools/Scripts/webkitpy/layout_tests/update_webgl_conformance_tests.py @@ -0,0 +1,160 @@ +#!/usr/bin/env python + +# Copyright (C) 2010 Google Inc. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY +# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR +# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY +# OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +from __future__ import with_statement + +import glob +import logging +import optparse +import os +import re +import sys +import webkitpy.common.checkout.scm as scm + +_log = logging.getLogger("webkitpy.layout_tests." + "update-webgl-conformance-tests") + + +def remove_first_line_comment(text): + return re.compile(r'^<!--.*?-->\s*', re.DOTALL).sub('', text) + + +def translate_includes(text): + # Mapping of single filename to relative path under WebKit root. + # Assumption: these filenames are globally unique. + include_mapping = { + "js-test-style.css": "../../js/resources", + "js-test-pre.js": "../../js/resources", + "js-test-post.js": "../../js/resources", + "desktop-gl-constants.js": "resources", + } + + for filename, path in include_mapping.items(): + search = r'(?:[^"\'= ]*/)?' + re.escape(filename) + replace = os.path.join(path, filename) + text = re.sub(search, replace, text) + + return text + + +def translate_khronos_test(text): + """ + This method translates the contents of a Khronos test to a WebKit test. + """ + + translateFuncs = [ + remove_first_line_comment, + translate_includes, + ] + + for f in translateFuncs: + text = f(text) + + return text + + +def update_file(in_filename, out_dir): + # check in_filename exists + # check out_dir exists + out_filename = os.path.join(out_dir, os.path.basename(in_filename)) + + _log.debug("Processing " + in_filename) + with open(in_filename, 'r') as in_file: + with open(out_filename, 'w') as out_file: + out_file.write(translate_khronos_test(in_file.read())) + + +def update_directory(in_dir, out_dir): + for filename in glob.glob(os.path.join(in_dir, '*.html')): + update_file(os.path.join(in_dir, filename), out_dir) + + +def default_out_dir(): + current_scm = scm.detect_scm_system(os.path.dirname(sys.argv[0])) + if not current_scm: + return os.getcwd() + root_dir = current_scm.checkout_root + if not root_dir: + return os.getcwd() + out_dir = os.path.join(root_dir, "LayoutTests/fast/canvas/webgl") + if os.path.isdir(out_dir): + return out_dir + return os.getcwd() + + +def configure_logging(options): + """Configures the logging system.""" + log_fmt = '%(levelname)s: %(message)s' + log_datefmt = '%y%m%d %H:%M:%S' + log_level = logging.INFO + if options.verbose: + log_fmt = ('%(asctime)s %(filename)s:%(lineno)-4d %(levelname)s ' + '%(message)s') + log_level = logging.DEBUG + logging.basicConfig(level=log_level, format=log_fmt, + datefmt=log_datefmt) + + +def option_parser(): + usage = "usage: %prog [options] (input file or directory)" + parser = optparse.OptionParser(usage=usage) + parser.add_option('-v', '--verbose', + action='store_true', + default=False, + help='include debug-level logging') + parser.add_option('-o', '--output', + action='store', + type='string', + default=default_out_dir(), + metavar='DIR', + help='specify an output directory to place files ' + 'in [default: %default]') + return parser + + +def main(): + parser = option_parser() + (options, args) = parser.parse_args() + configure_logging(options) + + if len(args) == 0: + _log.error("Must specify an input directory or filename.") + parser.print_help() + return 1 + + in_name = args[0] + if os.path.isfile(in_name): + update_file(in_name, options.output) + elif os.path.isdir(in_name): + update_directory(in_name, options.output) + else: + _log.error("'%s' is not a directory or a file.", in_name) + return 2 + + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/update_webgl_conformance_tests_unittest.py b/WebKitTools/Scripts/webkitpy/layout_tests/update_webgl_conformance_tests_unittest.py new file mode 100644 index 0000000..7393b70 --- /dev/null +++ b/WebKitTools/Scripts/webkitpy/layout_tests/update_webgl_conformance_tests_unittest.py @@ -0,0 +1,102 @@ +#!/usr/bin/python +# Copyright (C) 2010 Google Inc. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +"""Unit tests for update_webgl_conformance_tests.""" + +import unittest +from webkitpy.layout_tests import update_webgl_conformance_tests as webgl + + +def construct_script(name): + return "<script src=\"" + name + "\"></script>\n" + + +def construct_style(name): + return "<link rel=\"stylesheet\" href=\"" + name + "\">" + + +class TestTranslation(unittest.TestCase): + def assert_unchanged(self, text): + self.assertEqual(text, webgl.translate_khronos_test(text)) + + def assert_translate(self, input, output): + self.assertEqual(output, webgl.translate_khronos_test(input)) + + def test_simple_unchanged(self): + self.assert_unchanged("") + self.assert_unchanged("<html></html>") + + def test_header_strip(self): + single_line_header = "<!-- single line header. -->" + multi_line_header = """<!-- this is a multi-line + header. it should all be removed too. + -->""" + text = "<html></html>" + self.assert_translate(single_line_header, "") + self.assert_translate(single_line_header + text, text) + self.assert_translate(multi_line_header + text, text) + + def dont_strip_other_headers(self): + self.assert_unchanged("<html>\n<!-- don't remove comments on other lines. -->\n</html>") + + def test_include_rewriting(self): + # Mappings to None are unchanged + styles = { + "../resources/js-test-style.css": "../../js/resources/js-test-style.css", + "fail.css": None, + "resources/stylesheet.css": None, + "../resources/style.css": None, + } + scripts = { + "../resources/js-test-pre.js": "../../js/resources/js-test-pre.js", + "../resources/js-test-post.js": "../../js/resources/js-test-post.js", + "../resources/desktop-gl-constants.js": "resources/desktop-gl-constants.js", + + "resources/shadow-offset.js": None, + "../resources/js-test-post-async.js": None, + } + + input_text = "" + output_text = "" + for input, output in styles.items(): + input_text += construct_style(input) + output_text += construct_style(output if output else input) + for input, output in scripts.items(): + input_text += construct_script(input) + output_text += construct_script(output if output else input) + + head = '<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">\n<html>\n<head>\n' + foot = '</head>\n<body>\n</body>\n</html>' + input_text = head + input_text + foot + output_text = head + output_text + foot + self.assert_translate(input_text, output_text) + + +if __name__ == '__main__': + unittest.main() diff --git a/WebKitTools/Scripts/webkitpy/style/checkers/cpp.py b/WebKitTools/Scripts/webkitpy/style/checkers/cpp.py index 62f40ea..f8ebeff 100644 --- a/WebKitTools/Scripts/webkitpy/style/checkers/cpp.py +++ b/WebKitTools/Scripts/webkitpy/style/checkers/cpp.py @@ -2519,6 +2519,7 @@ def check_identifier_name_in_declaration(filename, line_number, line, error): and not modified_identifier.startswith('NPP_') and not modified_identifier.startswith('NP_') and not modified_identifier.startswith('qt_') + and not modified_identifier.startswith('cairo_') and not modified_identifier.find('::qt_') >= 0 and not modified_identifier == "const_iterator"): error(line_number, 'readability/naming', 4, identifier + " is incorrectly named. Don't use underscores in your identifier names.") diff --git a/WebKitTools/Scripts/webkitpy/style/checkers/cpp_unittest.py b/WebKitTools/Scripts/webkitpy/style/checkers/cpp_unittest.py index 16b1a3c..2f54305 100644 --- a/WebKitTools/Scripts/webkitpy/style/checkers/cpp_unittest.py +++ b/WebKitTools/Scripts/webkitpy/style/checkers/cpp_unittest.py @@ -3703,6 +3703,11 @@ class WebKitStyleTest(CppStyleTestBase): self.assert_lint('void QTFrame::qt_drt_is_awesome(int var1, int var2)', '') self.assert_lint('void qt_drt_is_awesome(int var1, int var2);', '') + # Cairo forward-declarations should not be a failure. + self.assert_lint('typedef struct _cairo cairo_t;', '') + self.assert_lint('typedef struct _cairo_surface cairo_surface_t;', '') + self.assert_lint('typedef struct _cairo_scaled_font cairo_scaled_font_t;', '') + # NPAPI functions that start with NPN_, NPP_ or NP_ are allowed. self.assert_lint('void NPN_Status(NPP, const char*)', '') self.assert_lint('NPError NPP_SetWindow(NPP instance, NPWindow *window)', '') diff --git a/WebKitTools/Scripts/webkitpy/tool/bot/queueengine.py b/WebKitTools/Scripts/webkitpy/tool/bot/queueengine.py index 36cbc5f..289dc4a 100644 --- a/WebKitTools/Scripts/webkitpy/tool/bot/queueengine.py +++ b/WebKitTools/Scripts/webkitpy/tool/bot/queueengine.py @@ -33,7 +33,6 @@ import traceback from datetime import datetime, timedelta -from webkitpy.common.net.statusserver import StatusServer from webkitpy.common.system.executive import ScriptError from webkitpy.common.system.deprecated_logging import log, OutputTee @@ -117,10 +116,10 @@ class QueueEngine: message = "Unexpected failure when processing patch! Please file a bug against webkit-patch.\n%s" % e.message_with_output() self._delegate.handle_unexpected_error(work_item, message) except TerminateQueue, e: - log("\nTerminateQueue exception received.") + self._stopping("TerminateQueue exception received.") return 0 except KeyboardInterrupt, e: - log("\nUser terminated queue.") + self._stopping("User terminated queue.") return 1 except Exception, e: traceback.print_exc() @@ -129,6 +128,13 @@ class QueueEngine: # Never reached. self._ensure_work_log_closed() + def _stopping(self, message): + log("\n%s" % message) + self._delegate.stop_work_queue(message) + # Be careful to shut down our OutputTee or the unit tests will be unhappy. + self._ensure_work_log_closed() + self._output_tee.remove_log(self._queue_log) + def _begin_logging(self): self._queue_log = self._output_tee.add_log(self._delegate.queue_log_path()) self._work_log = None diff --git a/WebKitTools/Scripts/webkitpy/tool/bot/queueengine_unittest.py b/WebKitTools/Scripts/webkitpy/tool/bot/queueengine_unittest.py index ec91bdb..bfec401 100644 --- a/WebKitTools/Scripts/webkitpy/tool/bot/queueengine_unittest.py +++ b/WebKitTools/Scripts/webkitpy/tool/bot/queueengine_unittest.py @@ -34,7 +34,9 @@ import threading import unittest from webkitpy.common.system.executive import ScriptError -from webkitpy.tool.bot.queueengine import QueueEngine, QueueEngineDelegate +from webkitpy.common.system.outputcapture import OutputCapture +from webkitpy.tool.bot.queueengine import QueueEngine, QueueEngineDelegate, TerminateQueue + class LoggingDelegate(QueueEngineDelegate): def __init__(self, test): @@ -94,14 +96,19 @@ class LoggingDelegate(QueueEngineDelegate): self._test.assertEquals(work_item, "work_item") -class ThrowErrorDelegate(LoggingDelegate): - def __init__(self, test, error_code): +class RaisingDelegate(LoggingDelegate): + def __init__(self, test, exception): LoggingDelegate.__init__(self, test) - self.error_code = error_code + self._exception = exception + self.stop_message = None def process_work_item(self, work_item): self.record("process_work_item") - raise ScriptError(exit_code=self.error_code) + raise self._exception + + def stop_work_queue(self, message): + self.record("stop_work_queue") + self.stop_message = message class NotSafeToProceedDelegate(LoggingDelegate): @@ -132,7 +139,7 @@ class QueueEngineTest(unittest.TestCase): self.assertTrue(os.path.exists(os.path.join(self.temp_dir, "work_log_path", "work_item.log"))) def test_unexpected_error(self): - delegate = ThrowErrorDelegate(self, 3) + delegate = RaisingDelegate(self, ScriptError(exit_code=3)) work_queue = QueueEngine("error-queue", delegate, threading.Event()) work_queue.run() expected_callbacks = LoggingDelegate.expected_callbacks[:] @@ -143,11 +150,32 @@ class QueueEngineTest(unittest.TestCase): self.assertEquals(delegate._callbacks, expected_callbacks) def test_handled_error(self): - delegate = ThrowErrorDelegate(self, QueueEngine.handled_error_code) + delegate = RaisingDelegate(self, ScriptError(exit_code=QueueEngine.handled_error_code)) work_queue = QueueEngine("handled-error-queue", delegate, threading.Event()) work_queue.run() self.assertEquals(delegate._callbacks, LoggingDelegate.expected_callbacks) + def _test_terminating_queue(self, exception, expected_message): + work_item_index = LoggingDelegate.expected_callbacks.index('process_work_item') + # The terminating error should be handled right after process_work_item. + # There should be no other callbacks after stop_work_queue. + expected_callbacks = LoggingDelegate.expected_callbacks[:work_item_index + 1] + expected_callbacks.append("stop_work_queue") + + delegate = RaisingDelegate(self, exception) + work_queue = QueueEngine("terminating-queue", delegate, threading.Event()) + + output = OutputCapture() + expected_stderr = "\n%s\n" % expected_message + output.assert_outputs(self, work_queue.run, [], expected_stderr=expected_stderr) + + self.assertEquals(delegate._callbacks, expected_callbacks) + self.assertEquals(delegate.stop_message, expected_message) + + def test_terminating_error(self): + self._test_terminating_queue(KeyboardInterrupt(), "User terminated queue.") + self._test_terminating_queue(TerminateQueue(), "TerminateQueue exception received.") + def test_not_safe_to_proceed(self): delegate = NotSafeToProceedDelegate(self) work_queue = FastQueueEngine(delegate) diff --git a/WebKitTools/Scripts/webkitpy/tool/commands/download.py b/WebKitTools/Scripts/webkitpy/tool/commands/download.py index d27ab0e..ed0e3d6 100644 --- a/WebKitTools/Scripts/webkitpy/tool/commands/download.py +++ b/WebKitTools/Scripts/webkitpy/tool/commands/download.py @@ -194,6 +194,19 @@ class BuildAttachment(AbstractPatchSequencingCommand, ProcessAttachmentsMixin): ] +class BuildAndTestAttachment(AbstractPatchSequencingCommand, ProcessAttachmentsMixin): + name = "build-and-test-attachment" + help_text = "Apply, build, and test patches from bugzilla" + argument_names = "ATTACHMENT_ID [ATTACHMENT_IDS]" + main_steps = [ + steps.CleanWorkingDirectory, + steps.Update, + steps.ApplyPatch, + steps.Build, + steps.RunTests, + ] + + class PostAttachmentToRietveld(AbstractPatchSequencingCommand, ProcessAttachmentsMixin): name = "post-attachment-to-rietveld" help_text = "Uploads a bugzilla attachment to rietveld" diff --git a/WebKitTools/Scripts/webkitpy/tool/commands/earlywarningsystem_unittest.py b/WebKitTools/Scripts/webkitpy/tool/commands/earlywarningsystem_unittest.py index 1f04923..3b0ea47 100644 --- a/WebKitTools/Scripts/webkitpy/tool/commands/earlywarningsystem_unittest.py +++ b/WebKitTools/Scripts/webkitpy/tool/commands/earlywarningsystem_unittest.py @@ -42,16 +42,15 @@ class EarlyWarningSytemTest(QueuesTest): def _default_expected_stderr(self, ews): string_replacemnts = { "name": ews.name, - "checkout_dir": os.getcwd(), # FIXME: Use of os.getcwd() is wrong, should be scm.checkout_root "port": ews.port_name, "watchers": ews.watchers, } expected_stderr = { - "begin_work_queue": "CAUTION: %(name)s will discard all local changes in \"%(checkout_dir)s\"\nRunning WebKit %(name)s.\n" % string_replacemnts, + "begin_work_queue": self._default_begin_work_queue_stderr(ews.name, os.getcwd()), # FIXME: Use of os.getcwd() is wrong, should be scm.checkout_root "handle_unexpected_error": "Mock error message\n", "next_work_item": "MOCK: update_work_items: %(name)s [103]\n" % string_replacemnts, "process_work_item": "MOCK: update_status: %(name)s Pass\n" % string_replacemnts, - "handle_script_error": "MOCK: update_status: %(name)s ScriptError error message\nMOCK bug comment: bug_id=345, cc=%(watchers)s\n--- Begin comment ---\\Attachment 1234 did not build on %(port)s:\nBuild output: http://dummy_url\n--- End comment ---\n\n" % string_replacemnts, + "handle_script_error": "MOCK: update_status: %(name)s ScriptError error message\nMOCK bug comment: bug_id=142, cc=%(watchers)s\n--- Begin comment ---\\Attachment 197 did not build on %(port)s:\nBuild output: http://dummy_url\n--- End comment ---\n\n" % string_replacemnts, } return expected_stderr diff --git a/WebKitTools/Scripts/webkitpy/tool/commands/queues.py b/WebKitTools/Scripts/webkitpy/tool/commands/queues.py index 4d2a9df..bc9ee42 100644 --- a/WebKitTools/Scripts/webkitpy/tool/commands/queues.py +++ b/WebKitTools/Scripts/webkitpy/tool/commands/queues.py @@ -95,6 +95,10 @@ class AbstractQueue(Command, QueueEngineDelegate): if (response != "yes"): error("User declined.") log("Running WebKit %s." % self.name) + self.tool.status_server.update_status(self.name, "Starting Queue") + + def stop_work_queue(self, reason): + self.tool.status_server.update_status(self.name, "Stopping Queue, reason: %s" % reason) def should_continue_work_queue(self): self._iteration_count += 1 @@ -115,8 +119,8 @@ class AbstractQueue(Command, QueueEngineDelegate): # Command methods def execute(self, options, args, tool, engine=QueueEngine): - self.options = options - self.tool = tool + self.options = options # FIXME: This code is wrong. Command.options is a list, this assumes an Options element! + self.tool = tool # FIXME: This code is wrong too! Command.bind_to_tool handles this! return engine(self.name, self, self.tool.wakeup_event).run() @classmethod @@ -144,8 +148,16 @@ class AbstractPatchQueue(AbstractQueue): def _update_status(self, message, patch=None, results_file=None): self.tool.status_server.update_status(self.name, message, patch, results_file) + # Note, eventually this will be done by a separate "feeder" queue + # whose job it is to poll bugzilla and feed work items into the + # status server for other queues to munch on. def _update_work_items(self, patch_ids): self.tool.status_server.update_work_items(self.name, patch_ids) + if patch_ids: + self.log_progress(patch_ids) + + def _fetch_next_work_item(self): + return self.tool.status_server.next_work_item(self.name) def _did_pass(self, patch): self._update_status(self._pass_status, patch) @@ -189,18 +201,22 @@ class CommitQueue(AbstractPatchQueue, StepSequenceErrorHandler): return rollout_cmp return cmp(a.attach_date(), b.attach_date()) - def next_work_item(self): + def _feed_work_items_to_server(self): + # Grab the set of patches from bugzilla, sort them, and update the status server. + # Eventually this will all be done by a separate feeder queue. patches = self._validate_patches_in_commit_queue() patches = sorted(patches, self._patch_cmp) self._update_work_items([patch.id() for patch in patches]) - if not patches: - self._update_status("Empty queue") + + def next_work_item(self): + self._feed_work_items_to_server() + # The grab the next patch to work on back from the status server. + patch_id = self._fetch_next_work_item() + if not patch_id: return None - # Only bother logging if we have patches in the queue. - self.log_progress([patch.id() for patch in patches]) - return patches[0] + return self.tool.bugs.fetch_attachment(patch_id) - def _can_build_and_test(self): + def _can_build_and_test_without_patch(self): try: self.run_webkit_patch([ "build-and-test", @@ -211,25 +227,24 @@ class CommitQueue(AbstractPatchQueue, StepSequenceErrorHandler): "--no-update", "--build-style=both", "--quiet"]) + return True except ScriptError, e: failure_log = self._log_from_script_error_for_upload(e) - self._update_status("Unable to successfully build and test", results_file=failure_log) + self._update_status("Unable to build and test without patch", results_file=failure_log) return False - return True def should_proceed_with_work_item(self, patch): patch_text = "rollout patch" if patch.is_rollout() else "patch" self._update_status("Landing %s" % patch_text, patch) return True - def _land(self, patch, first_run=False): + def _build_and_test_patch(self, patch, first_run=False): try: args = [ - "land-attachment", + "build-and-test-attachment", "--force-clean", "--build", "--non-interactive", - "--ignore-builders", "--build-style=both", "--quiet", patch.id() @@ -249,27 +264,68 @@ class CommitQueue(AbstractPatchQueue, StepSequenceErrorHandler): # built and tested. args.append("--no-update") self.run_webkit_patch(args) - self._did_pass(patch) return True except ScriptError, e: + failure_log = self._log_from_script_error_for_upload(e) + self._update_status("Unable to build and test patch", patch=patch, results_file=failure_log) if first_run: return False self._did_fail(patch) raise + def _revalidate_patch(self, patch): + # Bugs might get closed, or patches might be obsoleted or r-'d while the + # commit-queue is processing. Do one last minute check before landing. + patch = self.tool.bugs.fetch_attachment(patch.id()) + if patch.is_obsolete(): + return None + if patch.bug().is_closed(): + return None + if not patch.committer(): + return None + # Reviewer is not required. Misisng reviewers will be caught during the ChangeLog check during landing. + return patch + + def _land(self, patch): + try: + args = [ + "land-attachment", + "--force-clean", + "--non-interactive", + "--ignore-builders", + "--quiet", + "--parent-command=commit-queue", + patch.id(), + ] + self.run_webkit_patch(args) + self._did_pass(patch) + except ScriptError, e: + failure_log = self._log_from_script_error_for_upload(e) + self._update_status("Unable to land patch", patch=patch, results_file=failure_log) + self._did_fail(patch) + raise + def process_work_item(self, patch): self._cc_watchers(patch.bug_id()) - if not self._land(patch, first_run=True): - # The patch failed to land, but the bots were green. It's possible - # that the bots were behind. To check that case, we try to build and - # test ourselves. - if not self._can_build_and_test(): + if not self._build_and_test_patch(patch, first_run=True): + self._update_status("Building and testing without the patch as a sanity check", patch) + # The patch failed to build and test. It's possible that the + # tree is busted. To check that case, we try to build and test + # without the patch. + if not self._can_build_and_test_without_patch(): return False + self._update_status("Build and test succeeded, trying again with patch", patch) # Hum, looks like the patch is actually bad. Of course, we could # have been bitten by a flaky test the first time around. We try - # to land again. If it fails a second time, we're pretty sure its - # a bad test and re can reject it outright. - self._land(patch) + # to build and test again. If it fails a second time, we're pretty + # sure its a bad test and re can reject it outright. + self._build_and_test_patch(patch) + # Do one last check to catch any bug changes (cq-, closed, reviewer changed, etc.) + # This helps catch races between the bots if locks expire. + patch = self._revalidate_patch(patch) + if not patch: + return False + self._land(patch) return True def handle_unexpected_error(self, patch, message): @@ -291,6 +347,8 @@ class CommitQueue(AbstractPatchQueue, StepSequenceErrorHandler): @classmethod def handle_checkout_needs_update(cls, tool, state, options, error): + message = "Tests passed, but commit failed (checkout out of date). Updating, then landing without building or re-running tests." + tool.status_server.update_status(cls.name, message, state["patch"]) # The only time when we find out that out checkout needs update is # when we were ready to actually pull the trigger and land the patch. # Rather than spinning in the master process, we retry without @@ -377,7 +435,6 @@ class AbstractReviewQueue(AbstractPatchQueue, PersistentPatchCollectionDelegate, patch_id = self._patches.next() if patch_id: return self.tool.bugs.fetch_attachment(patch_id) - self._update_status("Empty queue") def should_proceed_with_work_item(self, patch): raise NotImplementedError, "subclasses must implement" diff --git a/WebKitTools/Scripts/webkitpy/tool/commands/queues_unittest.py b/WebKitTools/Scripts/webkitpy/tool/commands/queues_unittest.py index fd6543c..2deee76 100644 --- a/WebKitTools/Scripts/webkitpy/tool/commands/queues_unittest.py +++ b/WebKitTools/Scripts/webkitpy/tool/commands/queues_unittest.py @@ -34,9 +34,9 @@ from webkitpy.common.system.outputcapture import OutputCapture from webkitpy.thirdparty.mock import Mock from webkitpy.tool.commands.commandtest import CommandsTest from webkitpy.tool.commands.queues import * -from webkitpy.tool.commands.queuestest import QueuesTest +from webkitpy.tool.commands.queuestest import QueuesTest, MockPatch from webkitpy.tool.commands.stepsequence import StepSequence -from webkitpy.tool.mocktool import MockTool, MockSCM +from webkitpy.tool.mocktool import MockTool, MockSCM, MockStatusServer class TestQueue(AbstractPatchQueue): @@ -47,16 +47,10 @@ class TestReviewQueue(AbstractReviewQueue): name = "test-review-queue" -class MockPatch(object): +class MockRolloutPatch(MockPatch): def is_rollout(self): return True - def bug_id(self): - return 12345 - - def id(self): - return 76543 - class AbstractQueueTest(CommandsTest): def _assert_log_progress_output(self, patch_ids, progress_output): @@ -121,6 +115,16 @@ class AbstractQueueTest(CommandsTest): self._assert_log_message(script_error, expected_output) +class AbstractPatchQueueTest(CommandsTest): + def test_fetch_next_work_item(self): + queue = AbstractPatchQueue() + tool = MockTool() + queue.bind_to_tool(tool) + self.assertEquals(queue._fetch_next_work_item(), None) + tool.status_server = MockStatusServer(work_items=[2, 1, 3]) + self.assertEquals(queue._fetch_next_work_item(), 2) + + class AbstractReviewQueueTest(CommandsTest): def test_patch_collection_delegate_methods(self): queue = TestReviewQueue() @@ -141,25 +145,47 @@ class NeedsUpdateSequence(StepSequence): class AlwaysCommitQueueTool(object): + def __init__(self): + self.status_server = MockStatusServer() + def command_by_name(self, name): return CommitQueue +class SecondThoughtsCommitQueue(CommitQueue): + def _build_and_test_patch(self, patch, first_run=True): + attachment_dictionary = { + "id": patch.id(), + "bug_id": patch.bug_id(), + "name": "Rejected", + "is_obsolete": True, + "is_patch": False, + "review": "-", + "reviewer_email": "foo@bar.com", + "commit-queue": "-", + "committer_email": "foo@bar.com", + "attacher_email": "Contributer1", + } + patch = Attachment(attachment_dictionary, None) + self.tool.bugs.set_override_patch(patch) + return True + + class CommitQueueTest(QueuesTest): def test_commit_queue(self): expected_stderr = { - "begin_work_queue" : "CAUTION: commit-queue will discard all local changes in \"%s\"\nRunning WebKit commit-queue.\n" % MockSCM.fake_checkout_root, + "begin_work_queue": self._default_begin_work_queue_stderr("commit-queue", MockSCM.fake_checkout_root), "should_proceed_with_work_item": "MOCK: update_status: commit-queue Landing patch\n", # FIXME: The commit-queue warns about bad committers twice. This is due to the fact that we access Attachment.reviewer() twice and it logs each time. - "next_work_item" : """Warning, attachment 128 on bug 42 has invalid committer (non-committer@example.com) + "next_work_item": """Warning, attachment 128 on bug 42 has invalid committer (non-committer@example.com) Warning, attachment 128 on bug 42 has invalid committer (non-committer@example.com) MOCK setting flag 'commit-queue' to '-' on attachment '128' with comment 'Rejecting patch 128 from commit-queue.' and additional comment 'non-committer@example.com does not have committer permissions according to http://trac.webkit.org/browser/trunk/WebKitTools/Scripts/webkitpy/common/config/committers.py.\n\n- If you do not have committer rights please read http://webkit.org/coding/contributing.html for instructions on how to use bugzilla flags.\n\n- If you have committer rights please correct the error in WebKitTools/Scripts/webkitpy/common/config/committers.py by adding yourself to the file (no review needed). The commit-queue restarts itself every 2 hours. After restart the commit-queue will correctly respect your committer rights.' MOCK: update_work_items: commit-queue [106, 197] 2 patches in commit-queue [106, 197] """, - "process_work_item" : "MOCK: update_status: commit-queue Pass\n", - "handle_unexpected_error" : "MOCK setting flag 'commit-queue' to '-' on attachment '1234' with comment 'Rejecting patch 1234 from commit-queue.' and additional comment 'Mock error message'\n", - "handle_script_error": "MOCK: update_status: commit-queue ScriptError error message\nMOCK setting flag 'commit-queue' to '-' on attachment '1234' with comment 'Rejecting patch 1234 from commit-queue.' and additional comment 'ScriptError error message'\n", + "process_work_item": "MOCK: update_status: commit-queue Pass\n", + "handle_unexpected_error": "MOCK setting flag 'commit-queue' to '-' on attachment '197' with comment 'Rejecting patch 197 from commit-queue.' and additional comment 'Mock error message'\n", + "handle_script_error": "MOCK: update_status: commit-queue ScriptError error message\nMOCK setting flag 'commit-queue' to '-' on attachment '197' with comment 'Rejecting patch 197 from commit-queue.' and additional comment 'ScriptError error message'\n", } self.assert_queue_outputs(CommitQueue(), expected_stderr=expected_stderr) @@ -167,10 +193,10 @@ MOCK: update_work_items: commit-queue [106, 197] tool = MockTool(log_executive=True) tool.buildbot.light_tree_on_fire() expected_stderr = { - "begin_work_queue" : "CAUTION: commit-queue will discard all local changes in \"%s\"\nRunning WebKit commit-queue.\n" % MockSCM.fake_checkout_root, + "begin_work_queue": self._default_begin_work_queue_stderr("commit-queue", MockSCM.fake_checkout_root), "should_proceed_with_work_item": "MOCK: update_status: commit-queue Landing patch\n", # FIXME: The commit-queue warns about bad committers twice. This is due to the fact that we access Attachment.reviewer() twice and it logs each time. - "next_work_item" : """Warning, attachment 128 on bug 42 has invalid committer (non-committer@example.com) + "next_work_item": """Warning, attachment 128 on bug 42 has invalid committer (non-committer@example.com) Warning, attachment 128 on bug 42 has invalid committer (non-committer@example.com) MOCK setting flag 'commit-queue' to '-' on attachment '128' with comment 'Rejecting patch 128 from commit-queue.' and additional comment 'non-committer@example.com does not have committer permissions according to http://trac.webkit.org/browser/trunk/WebKitTools/Scripts/webkitpy/common/config/committers.py. @@ -180,18 +206,18 @@ MOCK setting flag 'commit-queue' to '-' on attachment '128' with comment 'Reject MOCK: update_work_items: commit-queue [106, 197] 2 patches in commit-queue [106, 197] """, - "process_work_item" : "MOCK run_and_throw_if_fail: ['echo', '--status-host=example.com', 'land-attachment', '--force-clean', '--build', '--non-interactive', '--ignore-builders', '--build-style=both', '--quiet', 1234, '--test']\nMOCK: update_status: commit-queue Pass\n", - "handle_unexpected_error" : "MOCK setting flag 'commit-queue' to '-' on attachment '1234' with comment 'Rejecting patch 1234 from commit-queue.' and additional comment 'Mock error message'\n", - "handle_script_error": "MOCK: update_status: commit-queue ScriptError error message\nMOCK setting flag 'commit-queue' to '-' on attachment '1234' with comment 'Rejecting patch 1234 from commit-queue.' and additional comment 'ScriptError error message'\n", + "process_work_item": "MOCK run_and_throw_if_fail: ['echo', '--status-host=example.com', 'build-and-test-attachment', '--force-clean', '--build', '--non-interactive', '--build-style=both', '--quiet', 197, '--test']\nMOCK run_and_throw_if_fail: ['echo', '--status-host=example.com', 'land-attachment', '--force-clean', '--non-interactive', '--ignore-builders', '--quiet', '--parent-command=commit-queue', 197]\nMOCK: update_status: commit-queue Pass\n", + "handle_unexpected_error": "MOCK setting flag 'commit-queue' to '-' on attachment '197' with comment 'Rejecting patch 197 from commit-queue.' and additional comment 'Mock error message'\n", + "handle_script_error": "MOCK: update_status: commit-queue ScriptError error message\nMOCK setting flag 'commit-queue' to '-' on attachment '197' with comment 'Rejecting patch 197 from commit-queue.' and additional comment 'ScriptError error message'\n", } self.assert_queue_outputs(CommitQueue(), tool=tool, expected_stderr=expected_stderr) def test_rollout_lands(self): tool = MockTool(log_executive=True) tool.buildbot.light_tree_on_fire() - rollout_patch = MockPatch() + rollout_patch = MockRolloutPatch() expected_stderr = { - "begin_work_queue": "CAUTION: commit-queue will discard all local changes in \"%s\"\nRunning WebKit commit-queue.\n" % MockSCM.fake_checkout_root, + "begin_work_queue": self._default_begin_work_queue_stderr("commit-queue", MockSCM.fake_checkout_root), "should_proceed_with_work_item": "MOCK: update_status: commit-queue Landing rollout patch\n", # FIXME: The commit-queue warns about bad committers twice. This is due to the fact that we access Attachment.reviewer() twice and it logs each time. "next_work_item": """Warning, attachment 128 on bug 42 has invalid committer (non-committer@example.com) @@ -204,9 +230,9 @@ MOCK setting flag 'commit-queue' to '-' on attachment '128' with comment 'Reject MOCK: update_work_items: commit-queue [106, 197] 2 patches in commit-queue [106, 197] """, - "process_work_item": "MOCK run_and_throw_if_fail: ['echo', '--status-host=example.com', 'land-attachment', '--force-clean', '--build', '--non-interactive', '--ignore-builders', '--build-style=both', '--quiet', 76543]\nMOCK: update_status: commit-queue Pass\n", - "handle_unexpected_error": "MOCK setting flag 'commit-queue' to '-' on attachment '76543' with comment 'Rejecting patch 76543 from commit-queue.' and additional comment 'Mock error message'\n", - "handle_script_error": "MOCK: update_status: commit-queue ScriptError error message\nMOCK setting flag 'commit-queue' to '-' on attachment '1234' with comment 'Rejecting patch 1234 from commit-queue.' and additional comment 'ScriptError error message'\n", + "process_work_item": "MOCK run_and_throw_if_fail: ['echo', '--status-host=example.com', 'build-and-test-attachment', '--force-clean', '--build', '--non-interactive', '--build-style=both', '--quiet', 197]\nMOCK run_and_throw_if_fail: ['echo', '--status-host=example.com', 'land-attachment', '--force-clean', '--non-interactive', '--ignore-builders', '--quiet', '--parent-command=commit-queue', 197]\nMOCK: update_status: commit-queue Pass\n", + "handle_unexpected_error": "MOCK setting flag 'commit-queue' to '-' on attachment '197' with comment 'Rejecting patch 197 from commit-queue.' and additional comment 'Mock error message'\n", + "handle_script_error": "MOCK: update_status: commit-queue ScriptError error message\nMOCK setting flag 'commit-queue' to '-' on attachment '197' with comment 'Rejecting patch 197 from commit-queue.' and additional comment 'ScriptError error message'\n", } self.assert_queue_outputs(CommitQueue(), tool=tool, work_item=rollout_patch, expected_stderr=expected_stderr) @@ -215,7 +241,7 @@ MOCK: update_work_items: commit-queue [106, 197] tool = MockTool() tool.executive = Mock() queue.bind_to_tool(tool) - self.assertTrue(queue._can_build_and_test()) + self.assertTrue(queue._can_build_and_test_without_patch()) expected_run_args = ["echo", "--status-host=example.com", "build-and-test", "--force-clean", "--build", "--test", "--non-interactive", "--no-update", "--build-style=both", "--quiet"] tool.executive.run_and_throw_if_fail.assert_called_with(expected_run_args) @@ -245,22 +271,28 @@ MOCK: update_work_items: commit-queue [106, 197] tool = AlwaysCommitQueueTool() sequence = NeedsUpdateSequence(None) - expected_stderr = "Commit failed because the checkout is out of date. Please update and try again.\n" - OutputCapture().assert_outputs(self, sequence.run_and_handle_errors, [tool, options], expected_exception=TryAgain, expected_stderr=expected_stderr) + expected_stderr = "Commit failed because the checkout is out of date. Please update and try again.\nMOCK: update_status: commit-queue Tests passed, but commit failed (checkout out of date). Updating, then landing without building or re-running tests.\n" + state = {'patch': None} + OutputCapture().assert_outputs(self, sequence.run_and_handle_errors, [tool, options, state], expected_exception=TryAgain, expected_stderr=expected_stderr) self.assertEquals(options.update, True) self.assertEquals(options.build, False) self.assertEquals(options.test, False) + def test_manual_reject_during_processing(self): + queue = SecondThoughtsCommitQueue() + queue.bind_to_tool(MockTool()) + queue.process_work_item(MockPatch()) + class RietveldUploadQueueTest(QueuesTest): def test_rietveld_upload_queue(self): expected_stderr = { - "begin_work_queue": "CAUTION: rietveld-upload-queue will discard all local changes in \"%s\"\nRunning WebKit rietveld-upload-queue.\n" % MockSCM.fake_checkout_root, + "begin_work_queue": self._default_begin_work_queue_stderr("rietveld-upload-queue", MockSCM.fake_checkout_root), "should_proceed_with_work_item": "MOCK: update_status: rietveld-upload-queue Uploading patch\n", "process_work_item": "MOCK: update_status: rietveld-upload-queue Pass\n", - "handle_unexpected_error": "Mock error message\nMOCK setting flag 'in-rietveld' to '-' on attachment '1234' with comment 'None' and additional comment 'None'\n", - "handle_script_error": "ScriptError error message\nMOCK: update_status: rietveld-upload-queue ScriptError error message\nMOCK setting flag 'in-rietveld' to '-' on attachment '1234' with comment 'None' and additional comment 'None'\n", + "handle_unexpected_error": "Mock error message\nMOCK setting flag 'in-rietveld' to '-' on attachment '197' with comment 'None' and additional comment 'None'\n", + "handle_script_error": "ScriptError error message\nMOCK: update_status: rietveld-upload-queue ScriptError error message\nMOCK setting flag 'in-rietveld' to '-' on attachment '197' with comment 'None' and additional comment 'None'\n", } self.assert_queue_outputs(RietveldUploadQueue(), expected_stderr=expected_stderr) @@ -268,12 +300,12 @@ class RietveldUploadQueueTest(QueuesTest): class StyleQueueTest(QueuesTest): def test_style_queue(self): expected_stderr = { - "begin_work_queue" : "CAUTION: style-queue will discard all local changes in \"%s\"\nRunning WebKit style-queue.\n" % MockSCM.fake_checkout_root, + "begin_work_queue": self._default_begin_work_queue_stderr("style-queue", MockSCM.fake_checkout_root), "next_work_item": "MOCK: update_work_items: style-queue [103]\n", "should_proceed_with_work_item": "MOCK: update_status: style-queue Checking style\n", - "process_work_item" : "MOCK: update_status: style-queue Pass\n", - "handle_unexpected_error" : "Mock error message\n", - "handle_script_error": "MOCK: update_status: style-queue ScriptError error message\nMOCK bug comment: bug_id=345, cc=[]\n--- Begin comment ---\\Attachment 1234 did not pass style-queue:\n\nScriptError error message\n\nIf any of these errors are false positives, please file a bug against check-webkit-style.\n--- End comment ---\n\n", + "process_work_item": "MOCK: update_status: style-queue Pass\n", + "handle_unexpected_error": "Mock error message\n", + "handle_script_error": "MOCK: update_status: style-queue ScriptError error message\nMOCK bug comment: bug_id=142, cc=[]\n--- Begin comment ---\\Attachment 197 did not pass style-queue:\n\nScriptError error message\n\nIf any of these errors are false positives, please file a bug against check-webkit-style.\n--- End comment ---\n\n", } expected_exceptions = { "handle_script_error": SystemExit, diff --git a/WebKitTools/Scripts/webkitpy/tool/commands/queuestest.py b/WebKitTools/Scripts/webkitpy/tool/commands/queuestest.py index 9e17c5c..aa3cef4 100644 --- a/WebKitTools/Scripts/webkitpy/tool/commands/queuestest.py +++ b/WebKitTools/Scripts/webkitpy/tool/commands/queuestest.py @@ -45,16 +45,20 @@ class MockQueueEngine(object): class MockPatch(): def id(self): - return 1234 + return 197 def bug_id(self): - return 345 + return 142 + + def is_rollout(self): + return False class QueuesTest(unittest.TestCase): + # Ids match patch1 in mocktool.py mock_work_item = Attachment({ - "id": 1234, - "bug_id": 345, + "id": 197, + "bug_id": 142, "name": "Patch", "attacher_email": "adam@example.com", }, None) @@ -71,6 +75,10 @@ class QueuesTest(unittest.TestCase): expected_stderr=expected_stderr.get(func_name, ""), expected_exception=exception) + def _default_begin_work_queue_stderr(self, name, checkout_dir): + string_replacements = {"name": name, 'checkout_dir': checkout_dir} + return "CAUTION: %(name)s will discard all local changes in \"%(checkout_dir)s\"\nRunning WebKit %(name)s.\nMOCK: update_status: %(name)s Starting Queue\n" % string_replacements + def assert_queue_outputs(self, queue, args=None, work_item=None, expected_stdout=None, expected_stderr=None, expected_exceptions=None, options=Mock(), tool=MockTool()): if not expected_stdout: expected_stdout = {} diff --git a/WebKitTools/Scripts/webkitpy/tool/commands/sheriffbot_unittest.py b/WebKitTools/Scripts/webkitpy/tool/commands/sheriffbot_unittest.py index 4b4b8b6..a63ec24 100644 --- a/WebKitTools/Scripts/webkitpy/tool/commands/sheriffbot_unittest.py +++ b/WebKitTools/Scripts/webkitpy/tool/commands/sheriffbot_unittest.py @@ -42,7 +42,7 @@ class SheriffBotTest(QueuesTest): 29837: [self.builder1], } expected_stderr = { - "begin_work_queue": "CAUTION: sheriff-bot will discard all local changes in \"%s\"\nRunning WebKit sheriff-bot.\n" % os.getcwd(), + "begin_work_queue": self._default_begin_work_queue_stderr("sheriff-bot", os.getcwd()), "next_work_item": "", "process_work_item": "MOCK: irc.post: abarth, darin, eseidel: http://trac.webkit.org/changeset/29837 might have broken Builder1\nMOCK bug comment: bug_id=42, cc=['abarth@webkit.org', 'eric@webkit.org']\n--- Begin comment ---\\http://trac.webkit.org/changeset/29837 might have broken Builder1\n--- End comment ---\n\n", "handle_unexpected_error": "Mock error message\n" diff --git a/WebKitTools/Scripts/webkitpy/tool/mocktool.py b/WebKitTools/Scripts/webkitpy/tool/mocktool.py index e3d36ce..8a6188a 100644 --- a/WebKitTools/Scripts/webkitpy/tool/mocktool.py +++ b/WebKitTools/Scripts/webkitpy/tool/mocktool.py @@ -161,6 +161,7 @@ _bug1 = { "invalid commit-queue setter.", "assigned_to_email": _unassigned_email, "attachments": [_patch1, _patch2], + "bug_status": "UNCONFIRMED", } @@ -169,6 +170,7 @@ _bug2 = { "title": "Bug with a patch needing review.", "assigned_to_email": "foo@foo.com", "attachments": [_patch3], + "bug_status": "ASSIGNED", } @@ -177,6 +179,7 @@ _bug3 = { "title": "The third bug", "assigned_to_email": _unassigned_email, "attachments": [_patch7], + "bug_status": "NEW", } @@ -185,6 +188,7 @@ _bug4 = { "title": "The fourth bug", "assigned_to_email": "foo@foo.com", "attachments": [_patch4, _patch5, _patch6], + "bug_status": "REOPENED", } @@ -254,8 +258,8 @@ class MockBugzilla(Mock): def __init__(self): Mock.__init__(self) self.queries = MockBugzillaQueries(self) - self.committers = CommitterList(reviewers=[Reviewer("Foo Bar", - "foo@bar.com")]) + self.committers = CommitterList(reviewers=[Reviewer("Foo Bar", "foo@bar.com")]) + self._override_patch = None def create_bug(self, bug_title, @@ -277,7 +281,13 @@ class MockBugzilla(Mock): def fetch_bug(self, bug_id): return Bug(self.bug_cache.get(bug_id), self) + def set_override_patch(self, patch): + self._override_patch = patch + def fetch_attachment(self, attachment_id): + if self._override_patch: + return self._override_patch + # This could be changed to .get() if we wish to allow failed lookups. attachment_dictionary = self.attachment_cache[attachment_id] bug = self.fetch_bug(attachment_dictionary["bug_id"]) @@ -497,8 +507,9 @@ class MockIRC(object): class MockStatusServer(object): - def __init__(self): + def __init__(self, work_items=None): self.host = "example.com" + self._work_items = work_items or [] def patch_status(self, queue_name, patch_id): return None @@ -506,7 +517,13 @@ class MockStatusServer(object): def svn_revision(self, svn_revision): return None + def next_work_item(self, queue_name): + if not self._work_items: + return None + return self._work_items[0] + def update_work_items(self, queue_name, work_items): + self._work_items = work_items log("MOCK: update_work_items: %s %s" % (queue_name, work_items)) def update_status(self, queue_name, status, patch=None, results_file=None): diff --git a/WebKitTools/Scripts/webkitpy/tool/steps/applypatch.py b/WebKitTools/Scripts/webkitpy/tool/steps/applypatch.py index 6cded27..327ac09 100644 --- a/WebKitTools/Scripts/webkitpy/tool/steps/applypatch.py +++ b/WebKitTools/Scripts/webkitpy/tool/steps/applypatch.py @@ -35,8 +35,9 @@ class ApplyPatch(AbstractStep): def options(cls): return AbstractStep.options() + [ Options.non_interactive, + Options.force_patch, ] def run(self, state): log("Processing patch %s from bug %s." % (state["patch"].id(), state["patch"].bug_id())) - self._tool.checkout().apply_patch(state["patch"], force=self._options.non_interactive) + self._tool.checkout().apply_patch(state["patch"], force=self._options.non_interactive or self._options.force_patch) diff --git a/WebKitTools/Scripts/webkitpy/tool/steps/options.py b/WebKitTools/Scripts/webkitpy/tool/steps/options.py index e7e3855..3dc1963 100644 --- a/WebKitTools/Scripts/webkitpy/tool/steps/options.py +++ b/WebKitTools/Scripts/webkitpy/tool/steps/options.py @@ -43,6 +43,7 @@ class Options(object): description = make_option("-m", "--description", action="store", type="string", dest="description", help="Description string for the attachment (default: \"patch\")") email = make_option("--email", action="store", type="string", dest="email", help="Email address to use in ChangeLogs.") force_clean = make_option("--force-clean", action="store_true", dest="force_clean", default=False, help="Clean working directory before applying patches (removes local changes and commits)") + force_patch = make_option("--force-patch", action="store_true", dest="force_patch", default=False, help="Forcefully applies the patch, continuing past errors.") git_commit = make_option("-g", "--git-commit", action="store", dest="git_commit", help="Operate on a local commit. If a range, the commits are squashed into one. HEAD.. operates on working copy changes only.") local_commit = make_option("--local-commit", action="store_true", dest="local_commit", default=False, help="Make a local commit for each applied patch") non_interactive = make_option("--non-interactive", action="store_true", dest="non_interactive", default=False, help="Never prompt the user, fail as fast as possible.") |