last-green-revision should give us per-bot information
authorrniwa@webkit.org <rniwa@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 11 Jan 2012 19:53:00 +0000 (19:53 +0000)
committerrniwa@webkit.org <rniwa@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 11 Jan 2012 19:53:00 +0000 (19:53 +0000)
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):

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@104729 268f45cc-cd09-0410-ab3c-d52691b4dbfc

Tools/ChangeLog
Tools/Scripts/webkitpy/common/net/buildbot/buildbot.py
Tools/Scripts/webkitpy/common/net/buildbot/buildbot_mock.py
Tools/Scripts/webkitpy/common/net/buildbot/buildbot_unittest.py
Tools/Scripts/webkitpy/tool/bot/irc_command.py
Tools/Scripts/webkitpy/tool/bot/sheriffircbot_unittest.py
Tools/Scripts/webkitpy/tool/commands/queries.py

index 57871aed88a8b4a64244b860ded5b8df24ecf53c..335adcdebf978ab9c2ea9b3d95639a91737dea04 100644 (file)
@@ -1,3 +1,26 @@
+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
index 644a8219aec46bff85d8c7d85ace4f7a9dc6f25a..8fed4b1b31a2e179bec240a1912658e5b9b10812 100644 (file)
@@ -432,23 +432,39 @@ class BuildBot(object):
                 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
index f9e27b80b224a8adc36d7ac05671f0c09e31bc39..b487728749ffd3fb53c59ba4b6560a50e87403cc 100644 (file)
@@ -90,8 +90,8 @@ class MockBuildBot(object):
             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
index 5b1e57140aaf7c91c49a07a81b7efce6d67ae979..7ce257770ebbd7a0398ab118c1d3d1fd1cbb665f 100644 (file)
@@ -299,37 +299,82 @@ class BuildBotTest(unittest.TestCase):
         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()
@@ -337,8 +382,23 @@ class BuildBotTest(unittest.TestCase):
         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:
index fb2f42fd18be9d2c6fe70b35346c928b05da2c35..2c7f832fb696de3ddc14f0c3805bcd0806b758e6 100644 (file)
@@ -55,8 +55,9 @@ class IRCCommand(object):
 
 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):
index d9303808ca3c3cb5abaf35b72ac701a509413c76..e1501f6027c53ed304fbdfb1c5dcbca8919ce26c 100644 (file)
@@ -86,8 +86,8 @@ class SheriffIRCBotTest(unittest.TestCase):
         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"
index 7d23717a86ece34383c8db8e8fd76d7de4557d65..5a14afde3d34357db7d14c1c2c00ac298ecf5ba8 100644 (file)
@@ -132,7 +132,7 @@ class LastGreenRevision(AbstractDeclarativeCommand):
     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):