| OLD | NEW |
| 1 # Copyright (c) 2009, Google Inc. All rights reserved. | 1 # Copyright (c) 2009, Google Inc. All rights reserved. |
| 2 # | 2 # |
| 3 # Redistribution and use in source and binary forms, with or without | 3 # Redistribution and use in source and binary forms, with or without |
| 4 # modification, are permitted provided that the following conditions are | 4 # modification, are permitted provided that the following conditions are |
| 5 # met: | 5 # met: |
| 6 # | 6 # |
| 7 # * Redistributions of source code must retain the above copyright | 7 # * Redistributions of source code must retain the above copyright |
| 8 # notice, this list of conditions and the following disclaimer. | 8 # notice, this list of conditions and the following disclaimer. |
| 9 # * Redistributions in binary form must reproduce the above | 9 # * Redistributions in binary form must reproduce the above |
| 10 # copyright notice, this list of conditions and the following disclaimer | 10 # copyright notice, this list of conditions and the following disclaimer |
| 11 # in the documentation and/or other materials provided with the | 11 # in the documentation and/or other materials provided with the |
| 12 # distribution. | 12 # distribution. |
| 13 # * Neither the name of Google Inc. nor the names of its | 13 # * Neither the name of Google Inc. nor the names of its |
| 14 # contributors may be used to endorse or promote products derived from | 14 # contributors may be used to endorse or promote products derived from |
| 15 # this software without specific prior written permission. | 15 # this software without specific prior written permission. |
| 16 # | 16 # |
| 17 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | 17 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| 18 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | 18 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| 19 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | 19 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
| 20 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | 20 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
| 21 # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | 21 # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| 22 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | 22 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| 23 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | 23 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| 24 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | 24 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| 25 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | 25 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| 26 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | 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. | 27 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| 28 | 28 |
| 29 import collections | 29 import collections |
| 30 import logging |
| 30 import re | 31 import re |
| 31 import urllib2 | 32 import urllib2 |
| 32 | 33 |
| 33 from webkitpy.common.memoized import memoized | 34 from webkitpy.common.memoized import memoized |
| 34 from webkitpy.common.net.layout_test_results import LayoutTestResults | 35 from webkitpy.common.net.layout_test_results import LayoutTestResults |
| 35 from webkitpy.common.net.network_transaction import NetworkTransaction | 36 from webkitpy.common.net.network_transaction import NetworkTransaction |
| 36 | 37 |
| 38 _log = logging.getLogger(__name__) |
| 37 | 39 |
| 38 RESULTS_URL_BASE = 'https://storage.googleapis.com/chromium-layout-test-archives
' | 40 RESULTS_URL_BASE = 'https://storage.googleapis.com/chromium-layout-test-archives
' |
| 39 | 41 |
| 40 | 42 |
| 41 class Build(collections.namedtuple('Build', ('builder_name', 'build_number'))): | 43 class Build(collections.namedtuple('Build', ('builder_name', 'build_number'))): |
| 42 """Represents a combination of builder and build number. | 44 """Represents a combination of builder and build number. |
| 43 | 45 |
| 44 If build number is None, this represents the latest build | 46 If build number is None, this represents the latest build |
| 45 for a given builder. | 47 for a given builder. |
| 46 """ | 48 """ |
| (...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 81 def fetch_retry_summary_json(self, build): | 83 def fetch_retry_summary_json(self, build): |
| 82 """Fetches and returns the text of the archived retry_summary file. | 84 """Fetches and returns the text of the archived retry_summary file. |
| 83 | 85 |
| 84 This file is expected to contain the results of retrying layout tests | 86 This file is expected to contain the results of retrying layout tests |
| 85 with and without a patch in a try job. It includes lists of tests | 87 with and without a patch in a try job. It includes lists of tests |
| 86 that failed only with the patch ("failures"), and tests that failed | 88 that failed only with the patch ("failures"), and tests that failed |
| 87 both with and without ("ignored"). | 89 both with and without ("ignored"). |
| 88 """ | 90 """ |
| 89 url_base = '%s/%s' % (self.builder_results_url_base(build.builder_name),
build.build_number) | 91 url_base = '%s/%s' % (self.builder_results_url_base(build.builder_name),
build.build_number) |
| 90 return NetworkTransaction(return_none_on_404=True).run( | 92 return NetworkTransaction(return_none_on_404=True).run( |
| 91 lambda: self._fetch_file(url_base, 'retry_summary.json')) | 93 lambda: self.fetch_file(url_base, 'retry_summary.json')) |
| 92 | 94 |
| 93 def accumulated_results_url_base(self, builder_name): | 95 def accumulated_results_url_base(self, builder_name): |
| 94 return self.builder_results_url_base(builder_name) + '/results/layout-te
st-results' | 96 return self.builder_results_url_base(builder_name) + '/results/layout-te
st-results' |
| 95 | 97 |
| 96 @memoized | 98 @memoized |
| 97 def latest_layout_test_results(self, builder_name): | 99 def latest_layout_test_results(self, builder_name): |
| 98 return self.fetch_layout_test_results(self.accumulated_results_url_base(
builder_name)) | 100 return self.fetch_layout_test_results(self.accumulated_results_url_base(
builder_name)) |
| 99 | 101 |
| 100 @memoized | 102 @memoized |
| 101 def fetch_results(self, build): | 103 def fetch_results(self, build): |
| 102 return self.fetch_layout_test_results(self.results_url(build.builder_nam
e, build.build_number)) | 104 return self.fetch_layout_test_results(self.results_url(build.builder_nam
e, build.build_number)) |
| 103 | 105 |
| 104 @memoized | 106 @memoized |
| 105 def fetch_layout_test_results(self, results_url): | 107 def fetch_layout_test_results(self, results_url): |
| 106 """Returns a LayoutTestResults object for results fetched from a given U
RL.""" | 108 """Returns a LayoutTestResults object for results fetched from a given U
RL.""" |
| 107 results_file = NetworkTransaction(return_none_on_404=True).run( | 109 results_file = NetworkTransaction(return_none_on_404=True).run( |
| 108 lambda: self._fetch_file(results_url, 'failing_results.json')) | 110 lambda: self.fetch_file(results_url, 'failing_results.json')) |
| 111 if results_file is None: |
| 112 _log.warning('Got 404 response from:\n%s/failing_results.json', resu
lts_url) |
| 113 return None |
| 109 revision = NetworkTransaction(return_none_on_404=True).run( | 114 revision = NetworkTransaction(return_none_on_404=True).run( |
| 110 lambda: self._fetch_file(results_url, 'LAST_CHANGE')) | 115 lambda: self.fetch_file(results_url, 'LAST_CHANGE')) |
| 111 if not revision: | 116 if revision is None: |
| 112 results_file = None | 117 _log.warning('Got 404 response from:\n%s/LAST_CHANGE', results_url) |
| 118 return None |
| 113 return LayoutTestResults.results_from_string(results_file, revision) | 119 return LayoutTestResults.results_from_string(results_file, revision) |
| 114 | 120 |
| 115 def _fetch_file(self, url_base, file_name): | 121 def fetch_file(self, url_base, filename): |
| 116 # It seems this can return None if the url redirects and then returns 40
4. | 122 # It seems this can return None if the url redirects and then returns 40
4. |
| 117 # FIXME: This could use Web instead of using urllib2 directly. | 123 # FIXME: This could use Web instead of using urllib2 directly. |
| 118 result = urllib2.urlopen('%s/%s' % (url_base, file_name)) | 124 result = urllib2.urlopen('%s/%s' % (url_base, filename)) |
| 119 if not result: | 125 if not result: |
| 120 return None | 126 return None |
| 121 # urlopen returns a file-like object which sometimes works fine with str
() | 127 # urlopen returns a file-like object which sometimes works fine with str
() |
| 122 # but sometimes is a addinfourl object. In either case calling read() i
s correct. | 128 # but sometimes is a addinfourl object. In either case calling read() i
s correct. |
| 123 return result.read() | 129 return result.read() |
| 124 | 130 |
| 125 | 131 |
| 126 def current_build_link(host): | 132 def current_build_link(host): |
| 127 """Returns a link to the current job if running on buildbot, or None.""" | 133 """Returns a link to the current job if running on buildbot, or None.""" |
| 128 master_name = host.environ.get('BUILDBOT_MASTERNAME') | 134 master_name = host.environ.get('BUILDBOT_MASTERNAME') |
| (...skipping 15 matching lines...) Expand all Loading... |
| 144 there are only Builds with no build number, then one is kept; if there | 150 there are only Builds with no build number, then one is kept; if there |
| 145 are Builds with build numbers, then the one with the highest build | 151 are Builds with build numbers, then the one with the highest build |
| 146 number is kept. | 152 number is kept. |
| 147 """ | 153 """ |
| 148 latest_builds = {} | 154 latest_builds = {} |
| 149 for build in builds: | 155 for build in builds: |
| 150 builder = build.builder_name | 156 builder = build.builder_name |
| 151 if builder not in latest_builds or build.build_number > latest_builds[bu
ilder].build_number: | 157 if builder not in latest_builds or build.build_number > latest_builds[bu
ilder].build_number: |
| 152 latest_builds[builder] = build | 158 latest_builds[builder] = build |
| 153 return sorted(latest_builds.values()) | 159 return sorted(latest_builds.values()) |
| OLD | NEW |