Improve webkit-patch rebaseline to work for more cases
authorojan@chromium.org <ojan@chromium.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 4 Jul 2012 02:13:13 +0000 (02:13 +0000)
committerojan@chromium.org <ojan@chromium.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 4 Jul 2012 02:13:13 +0000 (02:13 +0000)
https://bugs.webkit.org/show_bug.cgi?id=90504

Reviewed by Dirk Pranke.

-Makes it work for the build.chromium.org bots.
-Lets you rebaseline all builders instead of just one.
-Lets you pass in the builder or test to rebaseline.

* Scripts/webkitpy/common/net/buildbot/buildbot.py:
(Build.results_url):
Make this work for build.chromium.org builders as well.

* Scripts/webkitpy/common/net/buildbot/chromiumbuildbot.py:
(ChromiumBuilder):
(ChromiumBuilder.latest_cached_build):
ChromiumBuilder doesn't support large parts of the Builder interface.
This provides the bare minimum for this patch to work. Eventually,
we should create a single interface that can be supported for both
sets of buildbots.

* Scripts/webkitpy/layout_tests/port/builders.py:
(builder_path_from_name):
(all_builder_names):
memoizing here is incorrect because the test override _exact_matches,
so these can return different values. In either case, I'm pretty sure these
are not remotely hot codepaths.

* Scripts/webkitpy/tool/commands/rebaseline.py:
(AbstractParallelRebaselineCommand.__init__):
(Rebaseline):
(Rebaseline.__init__):
(Rebaseline._builders_to_pull_from):
(Rebaseline._builder_with_name):
(Rebaseline._tests_to_update):
(Rebaseline.execute):
* Scripts/webkitpy/tool/commands/rebaseline_unittest.py:
(test_rebaseline.mock_builders_to_pull_from):
(test_rebaseline):
(test_rebaseline_command_line_flags):
(test_rebaseline_multiple_builders):
(test_rebaseline_multiple_builders.mock_builders_to_pull_from):
(test_rebaseline_multiple_builders.mock_tests_to_update):

git-svn-id: http://svn.webkit.org/repository/webkit/trunk@121821 268f45cc-cd09-0410-ab3c-d52691b4dbfc

Tools/ChangeLog
Tools/Scripts/webkitpy/common/net/buildbot/buildbot.py
Tools/Scripts/webkitpy/common/net/buildbot/chromiumbuildbot.py
Tools/Scripts/webkitpy/layout_tests/port/builders.py
Tools/Scripts/webkitpy/tool/commands/rebaseline.py
Tools/Scripts/webkitpy/tool/commands/rebaseline_unittest.py

index 81cac9f..a6820ba 100644 (file)
@@ -1,3 +1,49 @@
+2012-07-03  Ojan Vafai  <ojan@chromium.org>
+
+        Improve webkit-patch rebaseline to work for more cases
+        https://bugs.webkit.org/show_bug.cgi?id=90504
+
+        Reviewed by Dirk Pranke.
+
+        -Makes it work for the build.chromium.org bots.
+        -Lets you rebaseline all builders instead of just one.
+        -Lets you pass in the builder or test to rebaseline.
+
+        * Scripts/webkitpy/common/net/buildbot/buildbot.py:
+        (Build.results_url):
+        Make this work for build.chromium.org builders as well.
+
+        * Scripts/webkitpy/common/net/buildbot/chromiumbuildbot.py:
+        (ChromiumBuilder):
+        (ChromiumBuilder.latest_cached_build):
+        ChromiumBuilder doesn't support large parts of the Builder interface.
+        This provides the bare minimum for this patch to work. Eventually,
+        we should create a single interface that can be supported for both
+        sets of buildbots.
+
+        * Scripts/webkitpy/layout_tests/port/builders.py:
+        (builder_path_from_name):
+        (all_builder_names):
+        memoizing here is incorrect because the test override _exact_matches,
+        so these can return different values. In either case, I'm pretty sure these
+        are not remotely hot codepaths.
+
+        * Scripts/webkitpy/tool/commands/rebaseline.py:
+        (AbstractParallelRebaselineCommand.__init__):
+        (Rebaseline):
+        (Rebaseline.__init__):
+        (Rebaseline._builders_to_pull_from):
+        (Rebaseline._builder_with_name):
+        (Rebaseline._tests_to_update):
+        (Rebaseline.execute):
+        * Scripts/webkitpy/tool/commands/rebaseline_unittest.py:
+        (test_rebaseline.mock_builders_to_pull_from):
+        (test_rebaseline):
+        (test_rebaseline_command_line_flags):
+        (test_rebaseline_multiple_builders):
+        (test_rebaseline_multiple_builders.mock_builders_to_pull_from):
+        (test_rebaseline_multiple_builders.mock_tests_to_update):
+
 2012-07-03  Christophe Dumez  <christophe.dumez@intel.com>
 
         [EFL] Enable CSS variables support at compile time
index 4e820ce..40de8d5 100644 (file)
@@ -239,7 +239,8 @@ class Build(object):
         return self.build_url(self.builder(), self._number)
 
     def results_url(self):
-        results_directory = "r%s (%s)" % (self.revision(), self._number)
+        # FIXME: We should probably have a better way of determining chromium builders than checking if the name starts with 'Webkit'.
+        results_directory = "results/layout-test-results" if self._builder.name().startswith('Webkit') else "r%s (%s)" % (self.revision(), self._number)
         return "%s/%s" % (self._builder.results_url(), urllib.quote(results_directory))
 
     def results_zip_url(self):
index 5030bba..9f9043b 100644 (file)
@@ -41,6 +41,16 @@ class ChromiumBuilder(Builder):
     def accumulated_results_url(self):
         return self.results_url() + "/results/layout-test-results"
 
+    # We override Builder.latest_cached_build because it relies on functions that assume too much about
+    # the information provided on the buildbot pages.
+    # FIXME: Have Builder and ChromiumBuilder actually provide a unified inteface that they both support.
+    def latest_cached_build(self):
+        for builder_status in self._buildbot.builder_statuses():
+            if builder_status["name"] == self.name():
+                build_number = builder_status["build_number"]
+                break
+        return self.build(build_number)
+
 
 class ChromiumBuildBot(BuildBot):
     _builder_factory = ChromiumBuilder
index b289b97..21fcc98 100644 (file)
@@ -96,12 +96,10 @@ def builder_path_from_name(builder_name):
     return re.sub(r'[\s().]', '_', builder_name)
 
 
-@memoized
 def all_builder_names():
     return sorted(set(_exact_matches.keys()))
 
 
-@memoized
 def all_port_names():
     return sorted(set(map(lambda x: x["port_name"], _exact_matches.values()) + _ports_without_builders))
 
index caa5f5b..2cd38e3 100644 (file)
@@ -229,13 +229,13 @@ class AnalyzeBaselines(AbstractRebaseliningCommand):
 
 
 class AbstractParallelRebaselineCommand(AbstractDeclarativeCommand):
-    def __init__(self):
-        options = [
+    def __init__(self, options=None):
+        options = options or []
+        options.extend([
             optparse.make_option('--no-optimize', dest='optimize', action='store_false', default=True,
                 help=('Do not optimize/de-dup the expectations after rebaselining '
                       '(default is to de-dup automatically). '
-                      'You can use "webkit-patch optimize-baselines" to optimize separately.')),
-        ]
+                      'You can use "webkit-patch optimize-baselines" to optimize separately.'))])
         AbstractDeclarativeCommand.__init__(self, options=options)
 
     def _run_webkit_patch(self, args):
@@ -370,29 +370,53 @@ class RebaselineExpectations(AbstractParallelRebaselineCommand):
 class Rebaseline(AbstractParallelRebaselineCommand):
     name = "rebaseline"
     help_text = "Replaces local expected.txt files with new results from build bots"
+    _chromium_prefix = 'Chromium - '
 
-    # FIXME: This should share more code with FailureReason._builder_to_explain
-    def _builder_to_pull_from(self):
-        builder_statuses = self._tool.buildbot.builder_statuses()
-        red_statuses = [status for status in builder_statuses if not status["is_green"]]
-        _log.info("%s failing" % (pluralize("builder", len(red_statuses))))
-        builder_choices = [status["name"] for status in red_statuses]
-        chosen_name = self._tool.user.prompt_with_list("Which builder to pull results from:", builder_choices)
-        # FIXME: prompt_with_list should really take a set of objects and a set of names and then return the object.
-        for status in red_statuses:
-            if status["name"] == chosen_name:
-                return (self._tool.buildbot.builder_with_name(chosen_name), status["build_number"])
-
-    def _tests_to_update(self, build):
-        failing_tests = build.layout_test_results().tests_matching_failure_types([test_failures.FailureTextMismatch])
-        return self._tool.user.prompt_with_list("Which test(s) to rebaseline:", failing_tests, can_choose_multiple=True)
+    def __init__(self):
+        options = [
+            # FIXME: Make this builders/tests and have it take a comma-separated list.
+            optparse.make_option("--builder", default=None, help="Builder to pull new baselines from"),
+            optparse.make_option("--test", help="Test to rebaseline"),
+        ]
+        AbstractParallelRebaselineCommand.__init__(self, options=options)
+
+    def _builders_to_pull_from(self):
+        # FIXME: Instead of prefixing the chromium builder names, show a build.webkit.org list and a build.chromium.org list.
+        builder_names = [self._chromium_prefix + name if name.startswith('Webkit') else name for name in builders.all_builder_names()]
+        chosen_names = self._tool.user.prompt_with_list("Which builder to pull results from:", builder_names, can_choose_multiple=True)
+        return [self._builder_with_name(name) for name in chosen_names]
+
+    def _builder_with_name(self, name):
+        if name.startswith(self._chromium_prefix):
+            name = name[len(self._chromium_prefix):]
+
+        # FIXME: Share this code with the code in RebaselineTest._results_url.
+        port = self._tool.port_factory.get_from_builder_name(name)
+        if port.name().startswith('chromium-'):
+            return self._tool.chromium_buildbot().builder_with_name(name)
+        return self._tool.buildbot.builder_with_name(name)
+
+    def _tests_to_update(self, builder):
+        failing_tests = builder.latest_cached_build().layout_test_results().tests_matching_failure_types([test_failures.FailureTextMismatch])
+        return self._tool.user.prompt_with_list("Which test(s) to rebaseline for %s:" % builder.name(), failing_tests, can_choose_multiple=True)
 
     def execute(self, options, args, tool):
-        builder, build_number = self._builder_to_pull_from()
-        build = builder.build(build_number)
+        if options.builder:
+            builders = [self._builder_with_name(options.builder)]
+        else:
+            builders = self._builders_to_pull_from()
 
-        builder_name = builder.name()
         test_list = {}
-        for test in self._tests_to_update(build):
-            test_list[test] = {builder_name: ['txt']}
+
+        for builder in builders:
+            tests = [options.test] if options.test else self._tests_to_update(builder)
+            for test in tests:
+                if test not in test_list:
+                    test_list[test] = {}
+                # FIXME: Allow for choosing the suffixes.
+                test_list[test][builder.name()] = ['txt']
+
+        if options.verbose:
+            print "rebaseline-json: " + str(test_list)
+
         self._rebaseline(options, test_list)
index 13d03b6..af03413 100644 (file)
@@ -351,20 +351,96 @@ MOCK run_command: ['qmake', '-v'], cwd=None
 
             tool.executive = MockExecutive(should_log=True)
 
-            def mock_builder_to_pull_from():
-                return MockBuilder('MOCK builder'), 1234
+            def mock_builders_to_pull_from():
+                return [MockBuilder('MOCK builder')]
 
             def mock_tests_to_update(build):
                 return ['mock/path/to/test.html']
 
-            command._builder_to_pull_from = mock_builder_to_pull_from
+            command._builders_to_pull_from = mock_builders_to_pull_from
             command._tests_to_update = mock_tests_to_update
 
+            expected_stdout = """rebaseline-json: {'mock/path/to/test.html': {'MOCK builder': ['txt']}}
+"""
+
             expected_stderr = """MOCK run_command: ['echo', 'rebaseline-test-internal', '--suffixes', 'txt', '--builder', 'MOCK builder', '--test', 'mock/path/to/test.html'], cwd=/mock-checkout
 MOCK run_command: ['echo', 'optimize-baselines', '--suffixes', 'txt', 'mock/path/to/test.html'], cwd=/mock-checkout
 """
 
-            OutputCapture().assert_outputs(self, command.execute, [MockOptions(optimize=True), [], tool], expected_stderr=expected_stderr)
+            OutputCapture().assert_outputs(self, command.execute, [MockOptions(optimize=True, builder=None, test=None, verbose=True), [], tool], expected_stdout=expected_stdout, expected_stderr=expected_stderr)
+
+        finally:
+            builders._exact_matches = old_exact_matches
+
+    def test_rebaseline_command_line_flags(self):
+        old_exact_matches = builders._exact_matches
+        try:
+            builders._exact_matches = {
+                "MOCK builder": {"port_name": "test-mac-leopard", "specifiers": set(["mock-specifier"])},
+            }
+
+            command = Rebaseline()
+            tool = MockTool()
+            command.bind_to_tool(tool)
+
+            for port_name in tool.port_factory.all_port_names():
+                port = tool.port_factory.get(port_name)
+                for path in port.expectations_files():
+                    tool.filesystem.write_text_file(path, '')
+
+            tool.executive = MockExecutive(should_log=True)
+
+            expected_stdout = """rebaseline-json: {'mock/path/to/test.html': {'MOCK builder': ['txt']}}
+"""
+
+            expected_stderr = """MOCK run_command: ['echo', 'rebaseline-test-internal', '--suffixes', 'txt', '--builder', 'MOCK builder', '--test', 'mock/path/to/test.html'], cwd=/mock-checkout
+MOCK run_command: ['echo', 'optimize-baselines', '--suffixes', 'txt', 'mock/path/to/test.html'], cwd=/mock-checkout
+"""
+
+            builder = "MOCK builder"
+            test = "mock/path/to/test.html"
+            OutputCapture().assert_outputs(self, command.execute, [MockOptions(optimize=True, builder=builder, test=test, verbose=True), [], tool], expected_stdout=expected_stdout, expected_stderr=expected_stderr)
+
+        finally:
+            builders._exact_matches = old_exact_matches
+
+    def test_rebaseline_multiple_builders(self):
+        old_exact_matches = builders._exact_matches
+        try:
+            builders._exact_matches = {
+                "MOCK builder": {"port_name": "test-mac-leopard", "specifiers": set(["mock-specifier"])},
+                "MOCK builder2": {"port_name": "test-mac-snowleopard", "specifiers": set(["mock-specifier2"])},
+            }
+
+            command = Rebaseline()
+            tool = MockTool()
+            command.bind_to_tool(tool)
+
+            for port_name in tool.port_factory.all_port_names():
+                port = tool.port_factory.get(port_name)
+                for path in port.expectations_files():
+                    tool.filesystem.write_text_file(path, '')
+
+            tool.executive = MockExecutive(should_log=True)
+
+            def mock_builders_to_pull_from():
+                return [MockBuilder('MOCK builder'), MockBuilder('MOCK builder2')]
+
+            def mock_tests_to_update(build):
+                return ['mock/path/to/test.html']
+
+            command._builders_to_pull_from = mock_builders_to_pull_from
+            command._tests_to_update = mock_tests_to_update
+
+            expected_stdout = """rebaseline-json: {'mock/path/to/test.html': {'MOCK builder2': ['txt'], 'MOCK builder': ['txt']}}
+"""
+
+            expected_stderr = """MOCK run_command: ['echo', 'rebaseline-test-internal', '--suffixes', 'txt', '--builder', 'MOCK builder2', '--test', 'mock/path/to/test.html'], cwd=/mock-checkout
+MOCK run_command: ['echo', 'rebaseline-test-internal', '--suffixes', 'txt', '--builder', 'MOCK builder', '--test', 'mock/path/to/test.html'], cwd=/mock-checkout
+MOCK run_command: ['echo', 'optimize-baselines', '--suffixes', 'txt', 'mock/path/to/test.html'], cwd=/mock-checkout
+"""
+
+            OutputCapture().assert_outputs(self, command.execute, [MockOptions(optimize=True, builder=None, test=None, verbose=True), [], tool], expected_stdout=expected_stdout, expected_stderr=expected_stderr)
 
         finally:
             builders._exact_matches = old_exact_matches