Commit 37740e2b authored by Bruce Dawson's avatar Bruce Dawson Committed by Commit Bot

Randomize results of git cl owners

git cl owners orders owners by score with alphabetization being the tie
breaker. This leads to some owners being suggested far more often than
others.

Adding a tiny amount of randomization to the scoring leads to an even
distribution of equally qualified reviewers. Less qualified reviewers
will still be sorted into distinct buckets - the randomness is too small
to do anything except break ties.

The tests were updated so that they can tolerate the randomness, but
only for breaking ties.

Bug: 1024083
Change-Id: If7d39d1b3bbd980b80b46ab3f62c65215309bdc8
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/tools/depot_tools/+/1913642
Commit-Queue: Bruce Dawson <brucedawson@chromium.org>
Reviewed-by: 's avatarAnthony Polito <apolito@google.com>
Reviewed-by: 's avatarEdward Lesmes <ehmaldonado@chromium.org>
parent 088977a7
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
# Use of this source code is governed by a BSD-style license that can be # Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file. # found in the LICENSE file.
"""A database of OWNERS files. r"""A database of OWNERS files.
OWNERS files indicate who is allowed to approve changes in a specific directory OWNERS files indicate who is allowed to approve changes in a specific directory
(or who is allowed to make changes without needing approval of another OWNER). (or who is allowed to make changes without needing approval of another OWNER).
...@@ -62,6 +62,12 @@ import fnmatch ...@@ -62,6 +62,12 @@ import fnmatch
import random import random
import re import re
try:
# This fallback applies for all versions of Python before 3.3
import collections.abc as collections_abc
except ImportError:
import collections as collections_abc
# If this is present by itself on a line, this means that everyone can review. # If this is present by itself on a line, this means that everyone can review.
EVERYONE = '*' EVERYONE = '*'
...@@ -80,9 +86,9 @@ def _assert_is_collection(obj): ...@@ -80,9 +86,9 @@ def _assert_is_collection(obj):
assert not isinstance(obj, str) assert not isinstance(obj, str)
# Module 'collections' has no 'Iterable' member # Module 'collections' has no 'Iterable' member
# pylint: disable=no-member # pylint: disable=no-member
if hasattr(collections, 'Iterable') and hasattr(collections, 'Sized'): if hasattr(collections_abc, 'Iterable') and hasattr(collections_abc, 'Sized'):
assert (isinstance(obj, collections.Iterable) and assert (isinstance(obj, collections_abc.Iterable) and
isinstance(obj, collections.Sized)) isinstance(obj, collections_abc.Sized))
class SyntaxErrorInOwnersFile(Exception): class SyntaxErrorInOwnersFile(Exception):
......
...@@ -9,6 +9,7 @@ from __future__ import print_function ...@@ -9,6 +9,7 @@ from __future__ import print_function
import os import os
import copy import copy
import owners as owners_module import owners as owners_module
import random
def first(iterable): def first(iterable):
...@@ -161,10 +162,12 @@ class OwnersFinder(object): ...@@ -161,10 +162,12 @@ class OwnersFinder(object):
self.selected_owners = set() self.selected_owners = set()
self.deselected_owners = set() self.deselected_owners = set()
# Initialize owners queue, sort it by the score and name # Randomize owners' names so that if many reviewers have identical scores
self.owners_queue = sorted( # they will be randomly ordered to avoid bias.
self.owners_to_files.keys(), owners = list(self.owners_to_files.keys())
key=lambda owner: (self.owners_score[owner], owner)) random.shuffle(owners)
self.owners_queue = sorted(owners,
key=lambda owner: self.owners_score[owner])
self.find_mandatory_owners() self.find_mandatory_owners()
def select_owner(self, owner, findMandatoryOwners=True): def select_owner(self, owner, findMandatoryOwners=True):
......
...@@ -153,8 +153,12 @@ class OwnersFinderTests(_BaseTestCase): ...@@ -153,8 +153,12 @@ class OwnersFinderTests(_BaseTestCase):
def test_reset(self): def test_reset(self):
finder = self.defaultFinder() finder = self.defaultFinder()
for _ in range(2): for _ in range(2):
self.assertEqual(finder.owners_queue, expected = [brett, darin, john, peter, ken, ben, tom]
[brett, darin, john, peter, ken, ben, tom]) # darin and john have equal cost, the others have distinct costs.
# If the owners_queue has those two swapped then swap them in expected.
if finder.owners_queue[1] != expected[1]:
expected[1], expected[2] = expected[2], expected[1]
self.assertEqual(finder.owners_queue, expected)
self.assertEqual(finder.unreviewed_files, { self.assertEqual(finder.unreviewed_files, {
'base/vlog.h', 'base/vlog.h',
'chrome/browser/defaults.h', 'chrome/browser/defaults.h',
...@@ -202,7 +206,13 @@ class OwnersFinderTests(_BaseTestCase): ...@@ -202,7 +206,13 @@ class OwnersFinderTests(_BaseTestCase):
finder = self.defaultFinder() finder = self.defaultFinder()
finder.select_owner(brett) finder.select_owner(brett)
self.assertEqual(finder.owners_queue, [darin, john, peter, ken, tom]) expected = [darin, john, peter, ken, tom]
# darin and john have equal cost, the others have distinct costs.
# If the owners_queue has those two swapped then swap them in expected.
if finder.owners_queue[0] == john:
expected[0], expected[1] = expected[1], expected[0]
self.assertEqual(finder.owners_queue, expected)
self.assertEqual(finder.selected_owners, {brett}) self.assertEqual(finder.selected_owners, {brett})
self.assertEqual(finder.deselected_owners, {ben}) self.assertEqual(finder.deselected_owners, {ben})
self.assertEqual(finder.reviewed_by, self.assertEqual(finder.reviewed_by,
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment