Title: [100407] trunk/Tools
Revision
100407
Author
[email protected]
Date
2011-11-15 22:41:17 -0800 (Tue, 15 Nov 2011)

Log Message

Implement edit-distance based reviewer recognition algorithm
https://bugs.webkit.org/show_bug.cgi?id=72351

Reviewed by Eric Seidel.

Implement an algorithm to recognize reviewer's name based on its edit distance (or more precisely
its Levenshtein distance) to each reviewer's full name, first, last and middle names, and IRC nicknames.
Furthermore, we cap the maximum edit distance at len(name) - 1 to avoid matching a bogus string like
"build fix" to a reviewer's name (e.g. with with edit distance 9).

This algorithm is implemented in CommitterList.contributors_by_fuzzy_match. The function to compute
the edit distance is implemented in edit_distance.py.

Also moved _has_valid_reviewer from ValidateReviewer to ChangeLogEntry because we can no longer rely
on the presence of ChangeLogEntry.reviewer() to verify that reviewer string is nicely formatted.

* Scripts/webkitpy/common/checkout/changelog.py:
* Scripts/webkitpy/common/checkout/changelog_unittest.py:
* Scripts/webkitpy/common/config/committers.py:
* Scripts/webkitpy/common/config/committers_unittest.py:
* Scripts/webkitpy/common/editdistance.py: Added.
* Scripts/webkitpy/common/editdistance_unittest.py: Added.
* Scripts/webkitpy/tool/steps/validatereviewer.py:
* Scripts/webkitpy/tool/steps/validatereviewer_unittest.py: Removed.

Modified Paths

Added Paths

Removed Paths

Diff

Modified: trunk/Tools/ChangeLog (100406 => 100407)


--- trunk/Tools/ChangeLog	2011-11-16 06:21:01 UTC (rev 100406)
+++ trunk/Tools/ChangeLog	2011-11-16 06:41:17 UTC (rev 100407)
@@ -1,3 +1,30 @@
+2011-11-14  Ryosuke Niwa  <[email protected]>
+
+        Implement edit-distance based reviewer recognition algorithm
+        https://bugs.webkit.org/show_bug.cgi?id=72351
+
+        Reviewed by Eric Seidel.
+
+        Implement an algorithm to recognize reviewer's name based on its edit distance (or more precisely
+        its Levenshtein distance) to each reviewer's full name, first, last and middle names, and IRC nicknames.
+        Furthermore, we cap the maximum edit distance at len(name) - 1 to avoid matching a bogus string like
+        "build fix" to a reviewer's name (e.g. with with edit distance 9).
+
+        This algorithm is implemented in CommitterList.contributors_by_fuzzy_match. The function to compute
+        the edit distance is implemented in edit_distance.py.
+
+        Also moved _has_valid_reviewer from ValidateReviewer to ChangeLogEntry because we can no longer rely
+        on the presence of ChangeLogEntry.reviewer() to verify that reviewer string is nicely formatted.
+
+        * Scripts/webkitpy/common/checkout/changelog.py:
+        * Scripts/webkitpy/common/checkout/changelog_unittest.py:
+        * Scripts/webkitpy/common/config/committers.py:
+        * Scripts/webkitpy/common/config/committers_unittest.py:
+        * Scripts/webkitpy/common/editdistance.py: Added.
+        * Scripts/webkitpy/common/editdistance_unittest.py: Added.
+        * Scripts/webkitpy/tool/steps/validatereviewer.py:
+        * Scripts/webkitpy/tool/steps/validatereviewer_unittest.py: Removed.
+
 2011-11-15  Tony Chang  <[email protected]>
 
         Skip a failing webkitpy test on cygwin.

Modified: trunk/Tools/Scripts/webkitpy/common/checkout/changelog.py (100406 => 100407)


--- trunk/Tools/Scripts/webkitpy/common/checkout/changelog.py	2011-11-16 06:21:01 UTC (rev 100406)
+++ trunk/Tools/Scripts/webkitpy/common/checkout/changelog.py	2011-11-16 06:41:17 UTC (rev 100407)
@@ -146,6 +146,13 @@
 
         return reviewer_text, reviewer_list
 
+    def _fuzz_match_reviewers(self, reviewers_text_list):
+        if not reviewers_text_list:
+            return []
+        list_of_reviewers = [self._committer_list.contributors_by_fuzzy_match(reviewer)[0] for reviewer in reviewers_text_list]
+        # Flatten lists and get rid of any reviewers with more than one candidate.
+        return [reviewers[0] for reviewers in list_of_reviewers if len(reviewers) == 1]
+
     def _parse_entry(self):
         match = re.match(self.date_line_regexp, self._contents, re.MULTILINE)
         if not match:
@@ -155,8 +162,8 @@
         self._author_name = match.group("name") if match else None
         self._author_email = match.group("email") if match else None
 
-        self._reviewer_text, self._reviewer_list = ChangeLogEntry._parse_reviewer_text(self._contents)
-        self._reviewer = self._committer_list.committer_by_name(self._reviewer_list[0]) if self._reviewer_list else None
+        self._reviewer_text, self._reviewers_text_list = ChangeLogEntry._parse_reviewer_text(self._contents)
+        self._reviewers = self._fuzz_match_reviewers(self._reviewers_text_list)
         self._author = self._committer_list.contributor_by_email(self._author_email) or self._committer_list.contributor_by_name(self._author_name)
 
         self._touched_files = re.findall(self.touched_files_regexp, self._contents, re.MULTILINE)
@@ -175,9 +182,21 @@
     def reviewer_text(self):
         return self._reviewer_text
 
+    # Might be None, might also not be a Reviewer!
     def reviewer(self):
-        return self._reviewer  # Might be None, might also not be a Reviewer!
+        return self._reviewers[0] if len(self._reviewers) > 0 else None
 
+    def reviewers(self):
+        return self._reviewers
+
+    def has_valid_reviewer(self):
+        if self._reviewers_text_list:
+            for reviewer in self._reviewers_text_list:
+                reviewer = self._committer_list.committer_by_name(reviewer)
+                if reviewer:
+                    return True
+        return bool(re.search("unreviewed", self._contents, re.IGNORECASE))
+
     def contents(self):
         return self._contents
 

Modified: trunk/Tools/Scripts/webkitpy/common/checkout/changelog_unittest.py (100406 => 100407)


--- trunk/Tools/Scripts/webkitpy/common/checkout/changelog_unittest.py	2011-11-16 06:21:01 UTC (rev 100406)
+++ trunk/Tools/Scripts/webkitpy/common/checkout/changelog_unittest.py	2011-11-16 06:41:17 UTC (rev 100407)
@@ -377,41 +377,16 @@
         reviewer_text, reviewer_list = ChangeLogEntry._parse_reviewer_text('Reviewed by Sam Weinig with no hesitation')
         self.assertEquals(reviewer_list, ['Sam Weinig'])
 
-        # For now, we let unofficial reviewers recognized as reviewers
-        reviewer_text, reviewer_list = ChangeLogEntry._parse_reviewer_text('Reviewed by Sam Weinig, Anders Carlsson, and (unofficially) Adam Barth.')
-        self.assertEquals(reviewer_list, ['Sam Weinig', 'Anders Carlsson', 'Adam Barth'])
-
-        # It's okay to have 'build fix' and 'others', etc... as a reviewer in the following cases because fuzzy-match would reject it
-        reviewer_text, reviewer_list = ChangeLogEntry._parse_reviewer_text('Reviewed by Dimitri Glazkov, build fix')
-        self.assertEquals(reviewer_list, ['Dimitri Glazkov', 'build fix'])
-
-        reviewer_text, reviewer_list = ChangeLogEntry._parse_reviewer_text('Reviewed by BUILD FIX')
-        self.assertEquals(reviewer_list, ['BUILD FIX'])
-
-        reviewer_text, reviewer_list = ChangeLogEntry._parse_reviewer_text('Reviewed by Mac build fix')
-        self.assertEquals(reviewer_list, ['Mac build fix'])
-
-        reviewer_text, reviewer_list = ChangeLogEntry._parse_reviewer_text('Reviewed by Darin Adler, Dan Bernstein, Adele Peterson, and others.')
-        self.assertEquals(reviewer_text, 'Darin Adler, Dan Bernstein, Adele Peterson, and others')
-        self.assertEquals(reviewer_list, ['Darin Adler', 'Dan Bernstein', 'Adele Peterson', 'others'])
-
-        reviewer_text, reviewer_list = ChangeLogEntry._parse_reviewer_text('Reviewed by George Staikos (and others)')
-        self.assertEquals(reviewer_list, ['George Staikos', 'others'])
-
         reviewer_text, reviewer_list = ChangeLogEntry._parse_reviewer_text('Reviewed by Oliver Hunt, okayed by Darin Adler.')
         self.assertEquals(reviewer_list, ['Oliver Hunt'])
 
-        reviewer_text, reviewer_list = ChangeLogEntry._parse_reviewer_text('Reviewed by Mark Rowe, but Dan Bernstein also reviewed and asked thoughtful questions.')
-        self.assertEquals(reviewer_list, ['Mark Rowe', 'but Dan Bernstein also reviewed', 'asked thoughtful questions'])
-
-        # It's okay to have " in" and "by ", etc... in the following cases because we're going to fuzzy-match them later
-        reviewer_text, reviewer_list = ChangeLogEntry._parse_reviewer_text('Reviewed by Darin Adler in <https://bugs.webkit.org/show_bug.cgi?id=47736>.')
-        self.assertEquals(reviewer_text, 'Darin Adler in')
-        reviewer_text, reviewer_list = ChangeLogEntry._parse_reviewer_text('Reviewed by Adam Barth.:w')
-        self.assertEquals(reviewer_text, 'Adam Barth.:w')
         reviewer_text, reviewer_list = ChangeLogEntry._parse_reviewer_text('Reviewed by Darin Adler).')
-        self.assertEquals(reviewer_text, 'Darin Adler')
+        self.assertEquals(reviewer_list, ['Darin Adler'])
 
+        # For now, we let unofficial reviewers recognized as reviewers
+        reviewer_text, reviewer_list = ChangeLogEntry._parse_reviewer_text('Reviewed by Sam Weinig, Anders Carlsson, and (unofficially) Adam Barth.')
+        self.assertEquals(reviewer_list, ['Sam Weinig', 'Anders Carlsson', 'Adam Barth'])
+
         reviewer_text, reviewer_list = ChangeLogEntry._parse_reviewer_text('Reviewed by NOBODY.')
         self.assertEquals(reviewer_text, None)
 
@@ -433,6 +408,48 @@
         reviewer_text, reviewer_list = ChangeLogEntry._parse_reviewer_text('Reviewed by nobody (trivial follow up fix), Joseph Pecoraro LGTM-ed.')
         self.assertEquals(reviewer_text, None)
 
+    def _entry_with_reviewer(self, reviewer_line):
+        return ChangeLogEntry('''2009-08-19  Eric Seidel  <[email protected]>
+
+            REVIEW_LINE
+
+            * Scripts/bugzilla-tool:
+'''.replace("REVIEW_LINE", reviewer_line))
+
+    def _contributors(self, names):
+        return [CommitterList().contributor_by_name(name) for name in names]
+
+    def _assert_fuzzy_reviewer_match(self, reviewer_text, expected_text_list, expected_contributors):
+        unused, reviewer_text_list = ChangeLogEntry._parse_reviewer_text(reviewer_text)
+        self.assertEquals(reviewer_text_list, expected_text_list)
+        self.assertEquals(self._entry_with_reviewer(reviewer_text).reviewers(), self._contributors(expected_contributors))
+
+    def test_fuzzy_reviewer_match(self):
+        self._assert_fuzzy_reviewer_match('Reviewed by Dimitri Glazkov, build fix', ['Dimitri Glazkov', 'build fix'], ['Dimitri Glazkov'])
+        self._assert_fuzzy_reviewer_match('Reviewed by BUILD FIX', ['BUILD FIX'], [])
+        self._assert_fuzzy_reviewer_match('Reviewed by Mac build fix', ['Mac build fix'], [])
+        self._assert_fuzzy_reviewer_match('Reviewed by Darin Adler, Dan Bernstein, Adele Peterson, and others.',
+            ['Darin Adler', 'Dan Bernstein', 'Adele Peterson', 'others'], ['Darin Adler', 'Dan Bernstein', 'Adele Peterson'])
+        self._assert_fuzzy_reviewer_match('Reviewed by George Staikos (and others)', ['George Staikos', 'others'], ['George Staikos'])
+        self._assert_fuzzy_reviewer_match('Reviewed by Mark Rowe, but Dan Bernstein also reviewed and asked thoughtful questions.',
+            ['Mark Rowe', 'but Dan Bernstein also reviewed', 'asked thoughtful questions'], ['Mark Rowe'])
+        self._assert_fuzzy_reviewer_match('Reviewed by Darin Adler in <https://bugs.webkit.org/show_bug.cgi?id=47736>.', ['Darin Adler in'], ['Darin Adler'])
+        self._assert_fuzzy_reviewer_match('Reviewed by Adam Barth.:w', ['Adam Barth.:w'], ['Adam Barth'])
+
+    def _assert_has_valid_reviewer(self, reviewer_line, expected):
+        self.assertEqual(self._entry_with_reviewer(reviewer_line).has_valid_reviewer(), expected)
+
+    def test_has_valid_reviewer(self):
+        self._assert_has_valid_reviewer("Reviewed by Eric Seidel.", True)
+        self._assert_has_valid_reviewer("Reviewed by Eric Seidel", True)  # Not picky about the '.'
+        self._assert_has_valid_reviewer("Reviewed by Eric.", False)
+        self._assert_has_valid_reviewer("Reviewed by Eric C Seidel.", False)
+        self._assert_has_valid_reviewer("Rubber-stamped by Eric.", False)
+        self._assert_has_valid_reviewer("Rubber-stamped by Eric Seidel.", True)
+        self._assert_has_valid_reviewer("Rubber stamped by Eric.", False)
+        self._assert_has_valid_reviewer("Rubber stamped by Eric Seidel.", True)
+        self._assert_has_valid_reviewer("Unreviewed build fix.", True)
+
     def test_latest_entry_parse(self):
         changelog_contents = u"%s\n%s" % (self._example_entry, self._example_changelog)
         changelog_file = StringIO(changelog_contents)

Modified: trunk/Tools/Scripts/webkitpy/common/config/committers.py (100406 => 100407)


--- trunk/Tools/Scripts/webkitpy/common/config/committers.py	2011-11-16 06:21:01 UTC (rev 100406)
+++ trunk/Tools/Scripts/webkitpy/common/config/committers.py	2011-11-16 06:41:17 UTC (rev 100407)
@@ -29,6 +29,7 @@
 #
 # WebKit's Python module for committer and reviewer validation.
 
+from webkitpy.common.editdistance import edit_distance
 
 class Account(object):
     def __init__(self, name, email_or_emails, irc_nickname_or_nicknames=None):
@@ -449,7 +450,6 @@
     Reviewer("Zoltan Herczeg", ["[email protected]", "[email protected]"], "zherczeg"),
 ]
 
-
 class CommitterList(object):
 
     # Committers and reviewers are passed in to allow easy testing
@@ -521,6 +521,7 @@
 
     def contributor_by_irc_nickname(self, irc_nickname):
         for contributor in self.contributors():
+            # FIXME: This should do case-insensitive comparison or assert that all IRC nicknames are in lowercase
             if contributor.irc_nicknames and irc_nickname in contributor.irc_nicknames:
                 return contributor
         return None
@@ -528,6 +529,39 @@
     def contributors_by_search_string(self, string):
         return filter(lambda contributor: contributor.contains_string(string), self.contributors())
 
+    def _tokenize_contributor_name(self, contributor):
+        full_name_in_lowercase = contributor.full_name.lower()
+        tokens = [full_name_in_lowercase] + full_name_in_lowercase.split()
+        if contributor.irc_nicknames:
+            return tokens + [nickname.lower() for nickname in contributor.irc_nicknames if len(nickname) > 5]
+        return tokens
+
+    def contributors_by_fuzzy_match(self, string):
+        string = string.lower()
+
+        # First path, optimitically match for fullname, email and irc_nicknames
+        account = self.account_by_email(string) or self.contributor_by_irc_nickname(string) or self.contributor_by_name(string)
+        if account:
+            return [account], 0
+
+        # Second path, much slower search using edit-distance
+        contributorWithMinDistance = []
+        minDistance = len(string) / 2 - 1
+        for contributor in self.contributors():
+            tokens = self._tokenize_contributor_name(contributor)
+            editdistances = [edit_distance(token, string) for token in tokens if abs(len(token) - len(string)) <= minDistance]
+            if not editdistances:
+                continue
+            distance = min(editdistances)
+            if distance == minDistance:
+                contributorWithMinDistance.append(contributor)
+            elif distance < minDistance:
+                contributorWithMinDistance = [contributor]
+                minDistance = distance
+        if not len(contributorWithMinDistance):
+            return [], len(string)
+        return contributorWithMinDistance, minDistance
+
     def account_by_login(self, login):
         return self._login_to_account_map().get(login.lower())
 

Modified: trunk/Tools/Scripts/webkitpy/common/config/committers_unittest.py (100406 => 100407)


--- trunk/Tools/Scripts/webkitpy/common/config/committers_unittest.py	2011-11-16 06:21:01 UTC (rev 100406)
+++ trunk/Tools/Scripts/webkitpy/common/config/committers_unittest.py	2011-11-16 06:41:17 UTC (rev 100407)
@@ -91,3 +91,35 @@
         self.assertEqual(committer_list.contributors_by_search_string('test'), [contributor, committer, reviewer])
         self.assertEqual(committer_list.contributors_by_search_string('rad'), [reviewer])
         self.assertEqual(committer_list.contributors_by_search_string('Two'), [reviewer])
+
+    def _assert_fuzz_match(self, text, name_of_expected_contributor, expected_distance):
+        committers = CommitterList()
+        expected_contributors = [committers.contributor_by_name(name_of_expected_contributor)] if name_of_expected_contributor else []
+        self.assertEqual(committers.contributors_by_fuzzy_match(text), (expected_contributors, expected_distance))
+
+    def test_contributors_by_fuzzy_match(self):
+        self._assert_fuzz_match('Geoff Garen', 'Geoffrey Garen', 3)
+        self._assert_fuzz_match('Kenneth Christiansen', 'Kenneth Rohde Christiansen', 6)
+        self._assert_fuzz_match('Ken Russell', 'Kenneth Russell', 4)
+        self._assert_fuzz_match('Dave Hyatt', 'David Hyatt', 2)
+        self._assert_fuzz_match('Dave Kilzer', 'David Kilzer', 2)
+        self._assert_fuzz_match('Antti "printf" Koivisto', 'Antti Koivisto', 9)
+        self._assert_fuzz_match('Sammy Weinig', 'Sam Weinig', 2)
+        self._assert_fuzz_match('Andres Kling', 'Andreas Kling', 1)
+        self._assert_fuzz_match('Darin Adler\'', 'Darin Adler', 1)
+        self._assert_fuzz_match('Joe Pecoraro', 'Joseph Pecoraro', 3)
+        self._assert_fuzz_match('Dr Dan Bernstein', 'Dan Bernstein', 3)
+        self._assert_fuzz_match('Mitzpettel', 'Dan Bernstein', 0)
+        self._assert_fuzz_match('[email protected]', 'Ryosuke Niwa', 0)
+        self._assert_fuzz_match('Ap', 'Alexey Proskuryakov', 0)
+        self._assert_fuzz_match('Sam', 'Sam Weinig', 0)
+        self._assert_fuzz_match('darin', 'Darin Adler', 0)
+        self._assert_fuzz_match('harrison', 'David Harrison', 0)
+        self._assert_fuzz_match('others', None, 6)
+        self._assert_fuzz_match('BUILD FIX', None, 9)
+        self._assert_fuzz_match('but Dan Bernstein also reviewed', None, 31)
+        self._assert_fuzz_match('asked thoughtful questions', None, 26)
+        self._assert_fuzz_match('build fix of mac', None, 16)
+        self._assert_fuzz_match('a spell checker', None, 15)
+        self._assert_fuzz_match('nobody, build fix', None, 17)
+        self._assert_fuzz_match('NOBODY (chromium build fix)', None, 27)

Added: trunk/Tools/Scripts/webkitpy/common/editdistance.py (0 => 100407)


--- trunk/Tools/Scripts/webkitpy/common/editdistance.py	                        (rev 0)
+++ trunk/Tools/Scripts/webkitpy/common/editdistance.py	2011-11-16 06:41:17 UTC (rev 100407)
@@ -0,0 +1,47 @@
+# Copyright (c) 2011 Google Inc. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+#     * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+#     * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+#     * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+from array import array
+
+
+def edit_distance(str1, str2):
+    unsignedShort = 'h'
+    distances = [array(unsignedShort, (0,) * (len(str2) + 1)) for i in range(0, len(str1) + 1)]
+    # distances[0][0] = 0 since distance between str1[:0] and str2[:0] is 0
+    for i in range(1, len(str1) + 1):
+        distances[i][0] = i  # Distance between str1[:i] and str2[:0] is i
+
+    for j in range(1, len(str2) + 1):
+        distances[0][j] = j  # Distance between str1[:0] and str2[:j] is j
+
+    for i in range(0, len(str1)):
+        for j in range(0, len(str2)):
+            diff = 0 if str1[i] == str2[j] else 1
+            # Deletion, Insertion, Identical / Replacement
+            distances[i + 1][j + 1] = min(distances[i + 1][j] + 1, distances[i][j + 1] + 1, distances[i][j] + diff)
+    return distances[len(str1)][len(str2)]

Added: trunk/Tools/Scripts/webkitpy/common/editdistance_unittest.py (0 => 100407)


--- trunk/Tools/Scripts/webkitpy/common/editdistance_unittest.py	                        (rev 0)
+++ trunk/Tools/Scripts/webkitpy/common/editdistance_unittest.py	2011-11-16 06:41:17 UTC (rev 100407)
@@ -0,0 +1,43 @@
+# Copyright (c) 2010 Google Inc. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+#     * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+#     * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+#     * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+import unittest
+
+from webkitpy.common.editdistance import edit_distance
+
+
+class EditDistanceTest(unittest.TestCase):
+    def test_edit_distance(self):
+        self.assertEqual(edit_distance('', 'aa'), 2)
+        self.assertEqual(edit_distance('aa', ''), 2)
+        self.assertEqual(edit_distance('a', 'ab'), 1)
+        self.assertEqual(edit_distance('ab', 'a'), 1)
+        self.assertEqual(edit_distance('ab', 'aa'), 1)
+        self.assertEqual(edit_distance('aa', 'ab'), 1)
+        self.assertEqual(edit_distance('abd', 'abcdef'), 3)
+        self.assertEqual(edit_distance('abcdef', 'abd'), 3)

Modified: trunk/Tools/Scripts/webkitpy/tool/steps/validatereviewer.py (100406 => 100407)


--- trunk/Tools/Scripts/webkitpy/tool/steps/validatereviewer.py	2011-11-16 06:21:01 UTC (rev 100406)
+++ trunk/Tools/Scripts/webkitpy/tool/steps/validatereviewer.py	2011-11-16 06:41:17 UTC (rev 100407)
@@ -43,16 +43,6 @@
             Options.non_interactive,
         ]
 
-    # FIXME: This should probably move onto ChangeLogEntry
-    def _has_valid_reviewer(self, changelog_entry):
-        if changelog_entry.reviewer():
-            return True
-        if re.search("unreviewed", changelog_entry.contents(), re.IGNORECASE):
-            return True
-        if re.search("rubber[ -]stamp", changelog_entry.contents(), re.IGNORECASE):
-            return True
-        return False
-
     def run(self, state):
         # FIXME: For now we disable this check when a user is driving the script
         # this check is too draconian (and too poorly tested) to foist upon users.
@@ -60,7 +50,7 @@
             return
         for changelog_path in self.cached_lookup(state, "changelogs"):
             changelog_entry = ChangeLog(changelog_path).latest_entry()
-            if self._has_valid_reviewer(changelog_entry):
+            if changelog_entry.has_valid_reviewer():
                 continue
             reviewer_text = changelog_entry.reviewer_text()
             if reviewer_text:

Deleted: trunk/Tools/Scripts/webkitpy/tool/steps/validatereviewer_unittest.py (100406 => 100407)


--- trunk/Tools/Scripts/webkitpy/tool/steps/validatereviewer_unittest.py	2011-11-16 06:21:01 UTC (rev 100406)
+++ trunk/Tools/Scripts/webkitpy/tool/steps/validatereviewer_unittest.py	2011-11-16 06:41:17 UTC (rev 100407)
@@ -1,57 +0,0 @@
-# Copyright (C) 2010 Google Inc. All rights reserved.
-#
-# Redistribution and use in source and binary forms, with or without
-# modification, are permitted provided that the following conditions are
-# met:
-#
-#    * Redistributions of source code must retain the above copyright
-# notice, this list of conditions and the following disclaimer.
-#    * Redistributions in binary form must reproduce the above
-# copyright notice, this list of conditions and the following disclaimer
-# in the documentation and/or other materials provided with the
-# distribution.
-#    * Neither the name of Google Inc. nor the names of its
-# contributors may be used to endorse or promote products derived from
-# this software without specific prior written permission.
-#
-# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
-# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
-# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
-# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
-# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
-# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
-import unittest
-
-from webkitpy.common.checkout.changelog import ChangeLogEntry
-from webkitpy.common.system.outputcapture import OutputCapture
-from webkitpy.tool.mocktool import MockOptions, MockTool
-from webkitpy.tool.steps.validatereviewer import ValidateReviewer
-
-class ValidateReviewerTest(unittest.TestCase):
-    _boilerplate_entry = '''2009-08-19  Eric Seidel  <[email protected]>
-
-        REVIEW_LINE
-
-        * Scripts/bugzilla-tool:
-'''
-
-    def _test_review_text(self, step, text, expected):
-        contents = self._boilerplate_entry.replace("REVIEW_LINE", text)
-        entry = ChangeLogEntry(contents)
-        self.assertEqual(step._has_valid_reviewer(entry), expected)
-
-    def test_has_valid_reviewer(self):
-        step = ValidateReviewer(MockTool(), MockOptions())
-        self._test_review_text(step, "Reviewed by Eric Seidel.", True)
-        self._test_review_text(step, "Reviewed by Eric Seidel", True) # Not picky about the '.'
-        self._test_review_text(step, "Reviewed by Eric.", False)
-        self._test_review_text(step, "Reviewed by Eric C Seidel.", False)
-        self._test_review_text(step, "Rubber-stamped by Eric.", True)
-        self._test_review_text(step, "Rubber stamped by Eric.", True)
-        self._test_review_text(step, "Unreviewed build fix.", True)
_______________________________________________
webkit-changes mailing list
[email protected]
http://lists.webkit.org/mailman/listinfo.cgi/webkit-changes

Reply via email to