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