| OLD | NEW |
| (Empty) |
| 1 # Copyright (C) 2009 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 unittest | |
| 30 | |
| 31 from webkitpy.common.net.layouttestresults import LayoutTestResults | |
| 32 from webkitpy.common.net.buildbot import BuildBot, Builder, Build | |
| 33 from webkitpy.layout_tests.models import test_results | |
| 34 from webkitpy.layout_tests.models import test_failures | |
| 35 from webkitpy.thirdparty.BeautifulSoup import BeautifulSoup | |
| 36 | |
| 37 | |
| 38 class BuilderTest(unittest.TestCase): | |
| 39 | |
| 40 def _mock_test_result(self, testname): | |
| 41 return test_results.TestResult(testname, [test_failures.FailureTextMisma
tch()]) | |
| 42 | |
| 43 def _install_fetch_build(self, failure): | |
| 44 def _mock_fetch_build(build_number): | |
| 45 build = Build( | |
| 46 builder=self.builder, | |
| 47 build_number=build_number, | |
| 48 revision=build_number + 1000, | |
| 49 is_green=build_number < 4 | |
| 50 ) | |
| 51 return build | |
| 52 self.builder._fetch_build = _mock_fetch_build | |
| 53 | |
| 54 def setUp(self): | |
| 55 self.buildbot = BuildBot() | |
| 56 self.builder = Builder(u"Test Builder \u2661", self.buildbot) | |
| 57 self._install_fetch_build(lambda build_number: ["test1", "test2"]) | |
| 58 | |
| 59 def test_latest_layout_test_results(self): | |
| 60 self.builder.fetch_layout_test_results = lambda results_url: LayoutTestR
esults(None) | |
| 61 self.builder.accumulated_results_url = lambda: "http://dummy_url.org" | |
| 62 self.assertTrue(self.builder.latest_layout_test_results()) | |
| 63 | |
| 64 def test_build_caching(self): | |
| 65 self.assertEqual(self.builder.build(10), self.builder.build(10)) | |
| 66 | |
| 67 def test_build_and_revision_for_filename(self): | |
| 68 expectations = { | |
| 69 "r47483 (1)/": (47483, 1), | |
| 70 "r47483 (1).zip": (47483, 1), | |
| 71 "random junk": None, | |
| 72 } | |
| 73 for filename, revision_and_build in expectations.items(): | |
| 74 self.assertEqual(self.builder._revision_and_build_for_filename(filen
ame), revision_and_build) | |
| 75 | |
| 76 def test_file_info_list_to_revision_to_build_list(self): | |
| 77 file_info_list = [ | |
| 78 {"filename": "r47483 (1)/"}, | |
| 79 {"filename": "r47483 (1).zip"}, | |
| 80 {"filename": "random junk"}, | |
| 81 ] | |
| 82 builds_and_revisions_list = [(47483, 1), (47483, 1)] | |
| 83 self.assertEqual(self.builder._file_info_list_to_revision_to_build_list(
file_info_list), builds_and_revisions_list) | |
| 84 | |
| 85 def test_fetch_build(self): | |
| 86 buildbot = BuildBot() | |
| 87 builder = Builder(u"Test Builder \u2661", buildbot) | |
| 88 | |
| 89 def mock_fetch_build_dictionary(self, build_number): | |
| 90 build_dictionary = { | |
| 91 "sourceStamp": { | |
| 92 "revision": None, # revision=None means a trunk build start
ed from the force-build button on the builder page. | |
| 93 }, | |
| 94 "number": int(build_number), | |
| 95 # Intentionally missing the 'results' key, meaning it's a "pass"
build. | |
| 96 } | |
| 97 return build_dictionary | |
| 98 buildbot._fetch_build_dictionary = mock_fetch_build_dictionary | |
| 99 self.assertIsNotNone(builder._fetch_build(1)) | |
| 100 | |
| 101 def test_results_url(self): | |
| 102 builder = BuildBot().builder_with_name('WebKit Mac10.8 (dbg)') | |
| 103 self.assertEqual(builder.results_url(), | |
| 104 'https://storage.googleapis.com/chromium-layout-test-ar
chives/WebKit_Mac10_8__dbg_') | |
| 105 | |
| 106 def test_accumulated_results_url(self): | |
| 107 builder = BuildBot().builder_with_name('WebKit Mac10.8 (dbg)') | |
| 108 self.assertEqual(builder.accumulated_results_url(), | |
| 109 'https://storage.googleapis.com/chromium-layout-test-ar
chives/WebKit_Mac10_8__dbg_/results/layout-test-results') | |
| 110 | |
| 111 | |
| 112 class BuildBotTest(unittest.TestCase): | |
| 113 | |
| 114 _example_one_box_status = ''' | |
| 115 <table> | |
| 116 <tr> | |
| 117 <td class="box"><a href="builders/Windows%20Debug%20%28Tests%29">Windows Deb
ug (Tests)</a></td> | |
| 118 <td align="center" class="LastBuild box success"><a href="builders/Windows
%20Debug%20%28Tests%29/builds/3693">47380</a><br />build<br />successful</td> | |
| 119 <td align="center" class="Activity building">building<br />ETA in<br />~ 1
4 mins<br />at 13:40</td> | |
| 120 <tr> | |
| 121 <td class="box"><a href="builders/SnowLeopard%20Intel%20Release">SnowLeopard
Intel Release</a></td> | |
| 122 <td class="LastBuild box" >no build</td> | |
| 123 <td align="center" class="Activity building">building<br />< 1 min</td> | |
| 124 <tr> | |
| 125 <td class="box"><a href="builders/Qt%20Linux%20Release">Qt Linux Release</a>
</td> | |
| 126 <td align="center" class="LastBuild box failure"><a href="builders/Qt%20Li
nux%20Release/builds/654">47383</a><br />failed<br />compile-webkit</td> | |
| 127 <td align="center" class="Activity idle">idle<br />3 pending</td> | |
| 128 <tr> | |
| 129 <td class="box"><a href="builders/Qt%20Windows%2032-bit%20Debug">Qt Windows
32-bit Debug</a></td> | |
| 130 <td align="center" class="LastBuild box failure"><a href="builders/Qt%20Wi
ndows%2032-bit%20Debug/builds/2090">60563</a><br />failed<br />failed<br />slave
<br />lost</td> | |
| 131 <td align="center" class="Activity building">building<br />ETA in<br />~ 5
mins<br />at 08:25</td> | |
| 132 </table> | |
| 133 ''' | |
| 134 _expected_example_one_box_parsings = [ | |
| 135 { | |
| 136 'is_green': True, | |
| 137 'build_number': 3693, | |
| 138 'name': u'Windows Debug (Tests)', | |
| 139 'built_revision': 47380, | |
| 140 'activity': 'building', | |
| 141 'pending_builds': 0, | |
| 142 }, | |
| 143 { | |
| 144 'is_green': False, | |
| 145 'build_number': None, | |
| 146 'name': u'SnowLeopard Intel Release', | |
| 147 'built_revision': None, | |
| 148 'activity': 'building', | |
| 149 'pending_builds': 0, | |
| 150 }, | |
| 151 { | |
| 152 'is_green': False, | |
| 153 'build_number': 654, | |
| 154 'name': u'Qt Linux Release', | |
| 155 'built_revision': 47383, | |
| 156 'activity': 'idle', | |
| 157 'pending_builds': 3, | |
| 158 }, | |
| 159 { | |
| 160 'is_green': True, | |
| 161 'build_number': 2090, | |
| 162 'name': u'Qt Windows 32-bit Debug', | |
| 163 'built_revision': 60563, | |
| 164 'activity': 'building', | |
| 165 'pending_builds': 0, | |
| 166 }, | |
| 167 ] | |
| 168 | |
| 169 def test_status_parsing(self): | |
| 170 buildbot = BuildBot() | |
| 171 | |
| 172 soup = BeautifulSoup(self._example_one_box_status) | |
| 173 status_table = soup.find("table") | |
| 174 input_rows = status_table.findAll('tr') | |
| 175 | |
| 176 for x in range(len(input_rows)): | |
| 177 status_row = input_rows[x] | |
| 178 expected_parsing = self._expected_example_one_box_parsings[x] | |
| 179 | |
| 180 builder = buildbot._parse_builder_status_from_row(status_row) | |
| 181 | |
| 182 # Make sure we aren't parsing more or less than we expect | |
| 183 self.assertEqual(builder.keys(), expected_parsing.keys()) | |
| 184 | |
| 185 for key, expected_value in expected_parsing.items(): | |
| 186 self.assertEqual(builder[key], expected_value, ("Builder %d pars
e failure for key: %s: Actual='%s' Expected='%s'" % ( | |
| 187 x, key, builder[key], expected_value))) | |
| 188 | |
| 189 def test_builder_with_name(self): | |
| 190 buildbot = BuildBot() | |
| 191 | |
| 192 builder = buildbot.builder_with_name("Test Builder") | |
| 193 self.assertEqual(builder.name(), "Test Builder") | |
| 194 self.assertEqual(builder.url(), "http://build.chromium.org/p/chromium.we
bkit/builders/Test%20Builder") | |
| 195 self.assertEqual(builder.url_encoded_name(), "Test%20Builder") | |
| 196 self.assertEqual(builder.results_url(), "https://storage.googleapis.com/
chromium-layout-test-archives/Test_Builder") | |
| 197 | |
| 198 # Override _fetch_build_dictionary function to not touch the network. | |
| 199 def mock_fetch_build_dictionary(self, build_number): | |
| 200 build_dictionary = { | |
| 201 "sourceStamp": { | |
| 202 "revision": 2 * build_number, | |
| 203 }, | |
| 204 "number": int(build_number), | |
| 205 "results": build_number % 2, # 0 means pass | |
| 206 } | |
| 207 return build_dictionary | |
| 208 buildbot._fetch_build_dictionary = mock_fetch_build_dictionary | |
| 209 | |
| 210 build = builder.build(10) | |
| 211 self.assertEqual(build.builder(), builder) | |
| 212 self.assertEqual(build.url(), "http://build.chromium.org/p/chromium.webk
it/builders/Test%20Builder/builds/10") | |
| 213 self.assertEqual(build.results_url(), "https://storage.googleapis.com/ch
romium-layout-test-archives/Test_Builder/r20%20%2810%29") | |
| 214 self.assertEqual(build.revision(), 20) | |
| 215 self.assertTrue(build.is_green()) | |
| 216 | |
| 217 build = build.previous_build() | |
| 218 self.assertEqual(build.builder(), builder) | |
| 219 self.assertEqual(build.url(), "http://build.chromium.org/p/chromium.webk
it/builders/Test%20Builder/builds/9") | |
| 220 self.assertEqual(build.results_url(), "https://storage.googleapis.com/ch
romium-layout-test-archives/Test_Builder/r18%20%289%29") | |
| 221 self.assertEqual(build.revision(), 18) | |
| 222 self.assertFalse(build.is_green()) | |
| 223 | |
| 224 self.assertIsNone(builder.build(None)) | |
| 225 | |
| 226 _example_directory_listing = ''' | |
| 227 <h1>Directory listing for /results/SnowLeopard Intel Leaks/</h1> | |
| 228 | |
| 229 <table> | |
| 230 <tr class="alt"> | |
| 231 <th>Filename</th> | |
| 232 <th>Size</th> | |
| 233 <th>Content type</th> | |
| 234 <th>Content encoding</th> | |
| 235 </tr> | |
| 236 <tr class="directory "> | |
| 237 <td><a href="r47483%20%281%29/"><b>r47483 (1)/</b></a></td> | |
| 238 <td><b></b></td> | |
| 239 <td><b>[Directory]</b></td> | |
| 240 <td><b></b></td> | |
| 241 </tr> | |
| 242 <tr class="file alt"> | |
| 243 <td><a href="r47484%20%282%29.zip">r47484 (2).zip</a></td> | |
| 244 <td>89K</td> | |
| 245 <td>[application/zip]</td> | |
| 246 <td></td> | |
| 247 </tr> | |
| 248 ''' | |
| 249 _expected_files = [ | |
| 250 { | |
| 251 "filename": "r47483 (1)/", | |
| 252 "size": "", | |
| 253 "type": "[Directory]", | |
| 254 "encoding": "", | |
| 255 }, | |
| 256 { | |
| 257 "filename": "r47484 (2).zip", | |
| 258 "size": "89K", | |
| 259 "type": "[application/zip]", | |
| 260 "encoding": "", | |
| 261 }, | |
| 262 ] | |
| 263 | |
| 264 def test_parse_build_to_revision_map(self): | |
| 265 buildbot = BuildBot() | |
| 266 files = buildbot._parse_twisted_directory_listing(self._example_director
y_listing) | |
| 267 self.assertEqual(self._expected_files, files) | |
| 268 | |
| 269 _fake_builder_page = ''' | |
| 270 <body> | |
| 271 <div class="content"> | |
| 272 <h1>Some Builder</h1> | |
| 273 <p>(<a href="../waterfall?show=Some Builder">view in waterfall</a>)</p> | |
| 274 <div class="column"> | |
| 275 <h2>Recent Builds:</h2> | |
| 276 <table class="info"> | |
| 277 <tr> | |
| 278 <th>Time</th> | |
| 279 <th>Revision</th> | |
| 280 <th>Result</th> <th>Build #</th> | |
| 281 <th>Info</th> | |
| 282 </tr> | |
| 283 <tr class="alt"> | |
| 284 <td>Jan 10 15:49</td> | |
| 285 <td><span class="revision" title="Revision 104643"><a href="http://trac.
webkit.org/changeset/104643">104643</a></span></td> | |
| 286 <td class="success">failure</td> <td><a href=".../37604">#37604</a></
td> | |
| 287 <td class="left">Build successful</td> | |
| 288 </tr> | |
| 289 <tr class=""> | |
| 290 <td>Jan 10 15:32</td> | |
| 291 <td><span class="revision" title="Revision 104636"><a href="http://trac.
webkit.org/changeset/104636">104636</a></span></td> | |
| 292 <td class="success">failure</td> <td><a href=".../37603">#37603</a></
td> | |
| 293 <td class="left">Build successful</td> | |
| 294 </tr> | |
| 295 <tr class="alt"> | |
| 296 <td>Jan 10 15:18</td> | |
| 297 <td><span class="revision" title="Revision 104635"><a href="http://trac.
webkit.org/changeset/104635">104635</a></span></td> | |
| 298 <td class="success">success</td> <td><a href=".../37602">#37602</a></
td> | |
| 299 <td class="left">Build successful</td> | |
| 300 </tr> | |
| 301 <tr class=""> | |
| 302 <td>Jan 10 14:51</td> | |
| 303 <td><span class="revision" title="Revision 104633"><a href="http://trac.
webkit.org/changeset/104633">104633</a></span></td> | |
| 304 <td class="failure">failure</td> <td><a href=".../37601">#37601</a></
td> | |
| 305 <td class="left">Failed compile-webkit</td> | |
| 306 </tr> | |
| 307 </table> | |
| 308 </body>''' | |
| 309 _fake_builder_page_without_success = ''' | |
| 310 <body> | |
| 311 <table> | |
| 312 <tr class="alt"> | |
| 313 <td>Jan 10 15:49</td> | |
| 314 <td><span class="revision" title="Revision 104643"><a href="http://trac.
webkit.org/changeset/104643">104643</a></span></td> | |
| 315 <td class="success">failure</td> | |
| 316 </tr> | |
| 317 <tr class=""> | |
| 318 <td>Jan 10 15:32</td> | |
| 319 <td><span class="revision" title="Revision 104636"><a href="http://trac.
webkit.org/changeset/104636">104636</a></span></td> | |
| 320 <td class="success">failure</td> | |
| 321 </tr> | |
| 322 <tr class="alt"> | |
| 323 <td>Jan 10 15:18</td> | |
| 324 <td><span class="revision" title="Revision 104635"><a href="http://trac.
webkit.org/changeset/104635">104635</a></span></td> | |
| 325 <td class="success">failure</td> | |
| 326 </tr> | |
| 327 <tr class=""> | |
| 328 <td>Jan 10 11:58</td> | |
| 329 <td><span class="revision" title="Revision ??"><a href="http://trac.we
bkit.org/changeset/%3F%3F">??</a></span></td> | |
| 330 <td class="retry">retry</td> | |
| 331 </tr> | |
| 332 <tr class=""> | |
| 333 <td>Jan 10 14:51</td> | |
| 334 <td><span class="revision" title="Revision 104633"><a href="http://trac.
webkit.org/changeset/104633">104633</a></span></td> | |
| 335 <td class="failure">failure</td> | |
| 336 </tr> | |
| 337 </table> | |
| 338 </body>''' | |
| 339 | |
| 340 def test_revisions_for_builder(self): | |
| 341 buildbot = BuildBot() | |
| 342 buildbot._fetch_builder_page = lambda builder: builder.page | |
| 343 builder_with_success = Builder('Some builder', None) | |
| 344 builder_with_success.page = self._fake_builder_page | |
| 345 self.assertEqual(buildbot._revisions_for_builder(builder_with_success),
[ | |
| 346 (104643, False), (104636, False), (104635, True), (1046
33, False)]) | |
| 347 | |
| 348 builder_without_success = Builder('Some builder', None) | |
| 349 builder_without_success.page = self._fake_builder_page_without_success | |
| 350 self.assertEqual(buildbot._revisions_for_builder(builder_without_success
), [ | |
| 351 (104643, False), (104636, False), (104635, False), (104
633, False)]) | |
| 352 | |
| 353 def test_find_green_revision(self): | |
| 354 buildbot = BuildBot() | |
| 355 self.assertEqual(buildbot._find_green_revision({ | |
| 356 'Builder 1': [(1, True), (3, True)], | |
| 357 'Builder 2': [(1, True), (3, False)], | |
| 358 'Builder 3': [(1, True), (3, True)], | |
| 359 }), 1) | |
| 360 self.assertEqual(buildbot._find_green_revision({ | |
| 361 'Builder 1': [(1, False), (3, True)], | |
| 362 'Builder 2': [(1, True), (3, True)], | |
| 363 'Builder 3': [(1, True), (3, True)], | |
| 364 }), 3) | |
| 365 self.assertEqual(buildbot._find_green_revision({ | |
| 366 'Builder 1': [(1, True), (2, True)], | |
| 367 'Builder 2': [(1, False), (2, True), (3, True)], | |
| 368 'Builder 3': [(1, True), (3, True)], | |
| 369 }), None) | |
| 370 self.assertEqual(buildbot._find_green_revision({ | |
| 371 'Builder 1': [(1, True), (2, True)], | |
| 372 'Builder 2': [(1, True), (2, True), (3, True)], | |
| 373 'Builder 3': [(1, True), (3, True)], | |
| 374 }), 2) | |
| 375 self.assertEqual(buildbot._find_green_revision({ | |
| 376 'Builder 1': [(1, False), (2, True)], | |
| 377 'Builder 2': [(1, True), (3, True)], | |
| 378 'Builder 3': [(1, True), (3, True)], | |
| 379 }), None) | |
| 380 self.assertEqual(buildbot._find_green_revision({ | |
| 381 'Builder 1': [(1, True), (3, True)], | |
| 382 'Builder 2': [(1, False), (2, True), (3, True), (4, True)], | |
| 383 'Builder 3': [(2, True), (4, True)], | |
| 384 }), 3) | |
| 385 self.assertEqual(buildbot._find_green_revision({ | |
| 386 'Builder 1': [(1, True), (3, True)], | |
| 387 'Builder 2': [(1, False), (2, True), (3, True), (4, False)], | |
| 388 'Builder 3': [(2, True), (4, True)], | |
| 389 }), None) | |
| 390 self.assertEqual(buildbot._find_green_revision({ | |
| 391 'Builder 1': [(1, True), (3, True)], | |
| 392 'Builder 2': [(1, False), (2, True), (3, True), (4, False)], | |
| 393 'Builder 3': [(2, True), (3, True), (4, True)], | |
| 394 }), 3) | |
| 395 self.assertEqual(buildbot._find_green_revision({ | |
| 396 'Builder 1': [(1, True), (2, True)], | |
| 397 'Builder 2': [], | |
| 398 'Builder 3': [(1, True), (2, True)], | |
| 399 }), None) | |
| 400 self.assertEqual(buildbot._find_green_revision({ | |
| 401 'Builder 1': [(1, True), (3, False), (5, True), (10, True), (12, Fal
se)], | |
| 402 'Builder 2': [(1, True), (3, False), (7, True), (9, True), (12, Fals
e)], | |
| 403 'Builder 3': [(1, True), (3, True), (7, True), (11, False), (12, Tru
e)], | |
| 404 }), 7) | |
| 405 | |
| 406 def _fetch_build(self, build_number): | |
| 407 if build_number == 5: | |
| 408 return "correct build" | |
| 409 return "wrong build" | |
| 410 | |
| 411 def _fetch_revision_to_build_map(self): | |
| 412 return {'r5': 5, 'r2': 2, 'r3': 3} | |
| 413 | |
| 414 def test_latest_cached_build(self): | |
| 415 b = Builder('builder', BuildBot()) | |
| 416 b._fetch_build = self._fetch_build | |
| 417 b._fetch_revision_to_build_map = self._fetch_revision_to_build_map | |
| 418 self.assertEqual("correct build", b.latest_cached_build()) | |
| 419 | |
| 420 def results_url(self): | |
| 421 return "some-url" | |
| 422 | |
| 423 def test_results_zip_url(self): | |
| 424 b = Build(None, 123, 123, False) | |
| 425 b.results_url = self.results_url | |
| 426 self.assertEqual("some-url.zip", b.results_zip_url()) | |
| OLD | NEW |