+2012-01-11 Ryosuke Niwa <rniwa@webkit.org>
+
+ last-green-revision should give us per-bot information
+ https://bugs.webkit.org/show_bug.cgi?id=76011
+
+ Reviewed by Adam Barth.
+
+ Rewrote last-green-revision command. Instead of finding a revision for which all bots succeeded,
+ we report the latest green run on each bot from the last 100 runs.
+
+ * Scripts/webkitpy/common/net/buildbot/buildbot.py:
+ (BuildBot._fetch_builder_page):
+ (BuildBot):
+ (BuildBot._green_revision_for_builder):
+ (BuildBot.last_green_revision):
+ * Scripts/webkitpy/common/net/buildbot/buildbot_unittest.py:
+ (test_green_revision_for_builder):
+ (test_last_green_revision):
+ * Scripts/webkitpy/tool/bot/irc_command.py:
+ (LastGreenRevision.execute):
+ * Scripts/webkitpy/tool/commands/queries.py:
+ (LastGreenRevision.execute):
+
2012-01-11 Dirk Pranke <dpranke@chromium.org>
webkitpy: clean up version detection in webkitpy.layout_tests.port
return build
build = build.previous_build()
- def last_green_revision(self):
- builds = self._latest_builds_from_builders()
- target_revision = builds[0].revision()
- # An alternate way to do this would be to start at one revision and walk backwards
- # checking builder.build_for_revision, however build_for_revision is very slow on first load.
- while True:
- # Make builds agree on revision
- builds = [self._build_at_or_before_revision(build, target_revision) for build in builds]
- if None in builds: # One of the builds failed to load from the server.
- return None
- min_revision = min(map(lambda build: build.revision(), builds))
- if min_revision != target_revision:
- target_revision = min_revision
- continue # Builds don't all agree on revision, keep searching
- # Check to make sure they're all green
- all_are_green = reduce(operator.and_, map(lambda build: build.is_green(), builds))
- if not all_are_green:
- target_revision -= 1
+ def _fetch_builder_page(self, builder):
+ builder_page_url = "%s/builders/%s?numbuilds=100" % (self.buildbot_url, urllib2.quote(builder.name()))
+ return urllib2.urlopen(builder_page_url)
+
+ def _green_revision_for_builder(self, builder):
+ soup = BeautifulSoup(self._fetch_builder_page(builder))
+ revision = None
+ number_of_revisions = 0
+ for status_row in soup.find('table').findAll('tr'):
+ revision_anchor = status_row.find('a')
+ table_cells = status_row.findAll('td')
+ if not revision_anchor or not table_cells or len(table_cells) < 3 or not table_cells[2].string:
continue
- return min_revision
+ number_of_revisions += 1
+ if revision == None and 'success' in table_cells[2].string:
+ revision = int(revision_anchor.string)
+ return revision, number_of_revisions
+
+ def last_green_revision(self, builder_name_regex):
+ compiled_builder_name_regex = re.compile(builder_name_regex, flags=re.IGNORECASE)
+ builders = [builder for builder in self.builders() if compiled_builder_name_regex.search(builder.name())]
+ if len(builders) > 10:
+ return '"%s" matches too many bots' % builder_name_regex
+ elif not len(builders):
+ return '"%s" doesn\'t match any bot' % builder_name_regex
+
+ result = ''
+ for builder in builders:
+ revision, number_of_revisions = self._green_revision_for_builder(builder)
+ if revision == None:
+ result += '%s has had no green revision in the last %d runs' % (builder.name(), number_of_revisions)
+ else:
+ result += '%s: %d' % (builder.name(), revision)
+ result += "\n"
+
+ return result
self._mock_builder2_status,
]
- def last_green_revision(self):
- return 9479
+ def last_green_revision(self, builder_name):
+ return builder_name + ': ' + str(9479)
def light_tree_on_fire(self):
self._mock_builder2_status["is_green"] = False
files = buildbot._parse_twisted_directory_listing(self._example_directory_listing)
self.assertEqual(self._expected_files, files)
- # Revision, is_green
- # Ordered from newest (highest number) to oldest.
- fake_builder1 = [
- [2, False],
- [1, True],
- ]
- fake_builder2 = [
- [2, False],
- [1, True],
- ]
- fake_builders = [
- fake_builder1,
- fake_builder2,
- ]
- def _build_from_fake(self, fake_builder, index):
- if index >= len(fake_builder):
- return None
- fake_build = fake_builder[index]
- build = Build(
- builder=fake_builder,
- build_number=index,
- revision=fake_build[0],
- is_green=fake_build[1],
- )
- def mock_previous_build():
- return self._build_from_fake(fake_builder, index + 1)
- build.previous_build = mock_previous_build
- return build
-
- def _fake_builds_at_index(self, index):
- return [self._build_from_fake(builder, index) for builder in self.fake_builders]
+ _fake_builder_page = '''
+ <body>
+ <div class="content">
+ <h1>Some Builder</h1>
+ <p>(<a href="../waterfall?show=Some Builder">view in waterfall</a>)</p>
+ <div class="column">
+ <h2>Recent Builds:</h2>
+ <table class="info">
+ <tr>
+ <th>Time</th>
+ <th>Revision</th>
+ <th>Result</th> <th>Build #</th>
+ <th>Info</th>
+ </tr>
+ <tr class="alt">
+ <td>Jan 10 15:49</td>
+ <td><span class="revision" title="Revision 104643"><a href="http://trac.webkit.org/changeset/104643">104643</a></span></td>
+ <td class="success">failure</td> <td><a href=".../37604">#37604</a></td>
+ <td class="left">Build successful</td>
+ </tr>
+ <tr class="">
+ <td>Jan 10 15:32</td>
+ <td><span class="revision" title="Revision 104636"><a href="http://trac.webkit.org/changeset/104636">104636</a></span></td>
+ <td class="success">failure</td> <td><a href=".../37603">#37603</a></td>
+ <td class="left">Build successful</td>
+ </tr>
+ <tr class="alt">
+ <td>Jan 10 15:18</td>
+ <td><span class="revision" title="Revision 104635"><a href="http://trac.webkit.org/changeset/104635">104635</a></span></td>
+ <td class="success">success</td> <td><a href=".../37602">#37602</a></td>
+ <td class="left">Build successful</td>
+ </tr>
+ <tr class="">
+ <td>Jan 10 14:51</td>
+ <td><span class="revision" title="Revision 104633"><a href="http://trac.webkit.org/changeset/104633">104633</a></span></td>
+ <td class="failure">failure</td> <td><a href=".../37601">#37601</a></td>
+ <td class="left">Failed compile-webkit</td>
+ </tr>
+ </table>
+ </body>'''
+ _fake_builder_page_without_success = '''
+ <body>
+ <table>
+ <tr class="alt">
+ <td>Jan 10 15:49</td>
+ <td><span class="revision" title="Revision 104643"><a href="http://trac.webkit.org/changeset/104643">104643</a></span></td>
+ <td class="success">failure</td>
+ </tr>
+ <tr class="">
+ <td>Jan 10 15:32</td>
+ <td><span class="revision" title="Revision 104636"><a href="http://trac.webkit.org/changeset/104636">104636</a></span></td>
+ <td class="success">failure</td>
+ </tr>
+ <tr class="alt">
+ <td>Jan 10 15:18</td>
+ <td><span class="revision" title="Revision 104635"><a href="http://trac.webkit.org/changeset/104635">104635</a></span></td>
+ <td class="success">failure</td>
+ </tr>
+ <tr class="">
+ <td>Jan 10 14:51</td>
+ <td><span class="revision" title="Revision 104633"><a href="http://trac.webkit.org/changeset/104633">104633</a></span></td>
+ <td class="failure">failure</td>
+ </tr>
+ </table>
+ </body>'''
+
+ def test_green_revision_for_builder(self):
+ buildbot = BuildBot()
+ buildbot._fetch_builder_page = lambda builder: builder.page
+ builder_with_success = Builder('Some builder', None)
+ builder_with_success.page = self._fake_builder_page
+ self.assertEqual(buildbot._green_revision_for_builder(builder_with_success), (104635, 4))
+
+ builder_without_success = Builder('Some builder', None)
+ builder_without_success.page = self._fake_builder_page_without_success
+ self.assertEqual(buildbot._green_revision_for_builder(builder_without_success), (None, 4))
def test_last_green_revision(self):
buildbot = BuildBot()
def mock_builds_from_builders():
return self._fake_builds_at_index(0)
+ # Revision, is_green
+ # Ordered from newest (highest number) to oldest.
+ fake_builder1 = Builder("Fake Builder 1", None)
+ fake_builder1.revision = 1234
+ fake_builder1.revision_count = 3
+ fake_builder2 = Builder("Fake Builder 2", None)
+ fake_builder2.revision = 1230
+ fake_builder2.revision_count = 4
+ some_builder = Builder("Some Builder", None)
+ some_builder.revision = None
+ some_builder.revision_count = 3
+
+ buildbot.builders = lambda: [fake_builder1, fake_builder2, some_builder]
+ buildbot._green_revision_for_builder = lambda builder: (builder.revision, builder.revision_count)
buildbot._latest_builds_from_builders = mock_builds_from_builders
- self.assertEqual(buildbot.last_green_revision(), 1)
+ self.assertEqual(buildbot.last_green_revision(''),
+ "Fake Builder 1: 1234\nFake Builder 2: 1230\nSome Builder has had no green revision in the last 3 runs\n")
def _fetch_build(self, build_number):
if build_number == 5:
class LastGreenRevision(IRCCommand):
def execute(self, nick, args, tool, sheriff):
- return "%s: %s" % (nick,
- urls.view_revision_url(tool.buildbot.last_green_revision()))
+ if not args:
+ return "%s: Usage: last-green-revision BUILDER_NAME" % nick
+ return "%s: %s" % (nick, tool.buildbot.last_green_revision(args[0]))
class Restart(IRCCommand):
OutputCapture().assert_outputs(self, run, args=["help"], expected_stderr=expected_stderr)
def test_lgr(self):
- expected_stderr = "MOCK: irc.post: mock_nick: http://trac.webkit.org/changeset/9479\n"
- OutputCapture().assert_outputs(self, run, args=["last-green-revision"], expected_stderr=expected_stderr)
+ expected_stderr = "MOCK: irc.post: mock_nick: Builder: 9479\n"
+ OutputCapture().assert_outputs(self, run, args=["last-green-revision Builder"], expected_stderr=expected_stderr)
def test_restart(self):
expected_stderr = "MOCK: irc.post: Restarting...\n"
help_text = "Prints the last known good revision"
def execute(self, options, args, tool):
- print self._tool.buildbot.last_green_revision()
+ print self._tool.buildbot.last_green_revision(args[0])
class WhatBroke(AbstractDeclarativeCommand):