|
1 # Copyright (c) 2010 Google Inc. All rights reserved. |
|
2 # |
|
3 # Redistribution and use in source and binary forms, with or without |
|
4 # modification, are permitted provided that the following conditions are |
|
5 # met: |
|
6 # |
|
7 # * Redistributions of source code must retain the above copyright |
|
8 # notice, this list of conditions and the following disclaimer. |
|
9 # * Redistributions in binary form must reproduce the above |
|
10 # copyright notice, this list of conditions and the following disclaimer |
|
11 # in the documentation and/or other materials provided with the |
|
12 # distribution. |
|
13 # * Neither the name of Google Inc. nor the names of its |
|
14 # contributors may be used to endorse or promote products derived from |
|
15 # this software without specific prior written permission. |
|
16 # |
|
17 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
|
18 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
|
19 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
|
20 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
|
21 # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
|
22 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
|
23 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
|
24 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
|
25 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
|
26 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
|
27 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
|
28 |
|
29 import os.path |
|
30 import re |
|
31 import shutil |
|
32 import urllib |
|
33 |
|
34 from webkitpy.common.net.buildbot import BuildBot, LayoutTestResults |
|
35 from webkitpy.common.system.user import User |
|
36 from webkitpy.layout_tests.port import factory |
|
37 from webkitpy.tool.grammar import pluralize |
|
38 from webkitpy.tool.multicommandtool import AbstractDeclarativeCommand |
|
39 |
|
40 |
|
41 # FIXME: I'm not sure where this logic should go in the end. |
|
42 # For now it's here, until we have a second need for it. |
|
43 class BuilderToPort(object): |
|
44 _builder_name_to_port_name = { |
|
45 r"SnowLeopard": "mac-snowleopard", |
|
46 r"Leopard": "mac-leopard", |
|
47 r"Tiger": "mac-tiger", |
|
48 r"Windows": "win", |
|
49 r"GTK": "gtk", |
|
50 r"Qt": "qt", |
|
51 r"Chromium Mac": "chromium-mac", |
|
52 r"Chromium Linux": "chromium-linux", |
|
53 r"Chromium Win": "chromium-win", |
|
54 } |
|
55 |
|
56 def _port_name_for_builder_name(self, builder_name): |
|
57 for regexp, port_name in self._builder_name_to_port_name.items(): |
|
58 if re.match(regexp, builder_name): |
|
59 return port_name |
|
60 |
|
61 def port_for_builder(self, builder_name): |
|
62 port_name = self._port_name_for_builder_name(builder_name) |
|
63 assert(port_name) # Need to update _builder_name_to_port_name |
|
64 port = factory.get(port_name) |
|
65 assert(port) # Need to update _builder_name_to_port_name |
|
66 return port |
|
67 |
|
68 |
|
69 class Rebaseline(AbstractDeclarativeCommand): |
|
70 name = "rebaseline" |
|
71 help_text = "Replaces local expected.txt files with new results from build bots" |
|
72 |
|
73 # FIXME: This should share more code with FailureReason._builder_to_explain |
|
74 def _builder_to_pull_from(self): |
|
75 builder_statuses = self.tool.buildbot.builder_statuses() |
|
76 red_statuses = [status for status in builder_statuses if not status["is_green"]] |
|
77 print "%s failing" % (pluralize("builder", len(red_statuses))) |
|
78 builder_choices = [status["name"] for status in red_statuses] |
|
79 chosen_name = self.tool.user.prompt_with_list("Which builder to pull results from:", builder_choices) |
|
80 # FIXME: prompt_with_list should really take a set of objects and a set of names and then return the object. |
|
81 for status in red_statuses: |
|
82 if status["name"] == chosen_name: |
|
83 return (self.tool.buildbot.builder_with_name(chosen_name), status["build_number"]) |
|
84 |
|
85 def _replace_expectation_with_remote_result(self, local_file, remote_file): |
|
86 (downloaded_file, headers) = urllib.urlretrieve(remote_file) |
|
87 shutil.move(downloaded_file, local_file) |
|
88 |
|
89 def _tests_to_update(self, build): |
|
90 parsed_results = build.layout_test_results().parsed_results() |
|
91 # FIXME: This probably belongs as API on LayoutTestResults |
|
92 # but .failing_tests() already means something else. |
|
93 return parsed_results[LayoutTestResults.fail_key] |
|
94 |
|
95 def _results_url_for_test(self, build, test): |
|
96 test_base = os.path.splitext(test)[0] |
|
97 actual_path = test_base + "-actual.txt" |
|
98 return build.results_url() + "/" + actual_path |
|
99 |
|
100 def execute(self, options, args, tool): |
|
101 builder, build_number = self._builder_to_pull_from() |
|
102 build = builder.build(build_number) |
|
103 port = BuilderToPort().port_for_builder(builder.name()) |
|
104 |
|
105 for test in self._tests_to_update(build): |
|
106 results_url = self._results_url_for_test(build, test) |
|
107 # Port operates with absolute paths. |
|
108 absolute_path = os.path.join(port.layout_tests_dir(), test) |
|
109 expected_file = port.expected_filename(absolute_path, ".txt") |
|
110 print test |
|
111 self._replace_expectation_with_remote_result(expected_file, results_url) |
|
112 |
|
113 # FIXME: We should handle new results too. |