An interactive tool to help find owners covering current change list.

BUG=77248

Review URL: https://chromiumcodereview.appspot.com/12712002

git-svn-id: svn://svn.chromium.org/chrome/trunk/tools/depot_tools@224264 0039d316-1c4b-4281-b951-d872f2087c98
experimental/szager/collated-output
ikarienator@chromium.org 12 years ago
parent 8b3cad72ec
commit faf3fdf736

@ -8,6 +8,7 @@
"""A git-command for integrating reviews on Rietveld.""" """A git-command for integrating reviews on Rietveld."""
from distutils.version import LooseVersion from distutils.version import LooseVersion
import glob
import json import json
import logging import logging
import optparse import optparse
@ -38,6 +39,7 @@ import scm
import subcommand import subcommand
import subprocess2 import subprocess2
import watchlists import watchlists
import owners_finder
__version__ = '1.0' __version__ = '1.0'
@ -2126,6 +2128,35 @@ def CMDset_close(parser, args):
return 0 return 0
def CMDowners(parser, args):
"""interactively find the owners for reviewing"""
parser.add_option(
'--no-color',
action='store_true',
help='Use this option to disable color output')
options, args = parser.parse_args(args)
author = RunGit(['config', 'user.email']).strip() or None
cl = Changelist()
if args:
if len(args) > 1:
parser.error('Unknown args')
base_branch = args[0]
else:
# Default to diffing against the common ancestor of the upstream branch.
base_branch = RunGit(['merge-base', cl.GetUpstreamBranch(), 'HEAD']).strip()
change = cl.GetChange(base_branch, None)
return owners_finder.OwnersFinder(
[f.LocalPath() for f in
cl.GetChange(base_branch, None).AffectedFiles()],
change.RepositoryRoot(), author,
fopen=file, os_path=os.path, glob=glob.glob,
disable_color=options.no_color).run()
def CMDformat(parser, args): def CMDformat(parser, args):
"""Runs clang-format on the diff.""" """Runs clang-format on the diff."""
CLANG_EXTS = ['.cc', '.cpp', '.h'] CLANG_EXTS = ['.cc', '.cpp', '.h']

@ -78,7 +78,7 @@ class SyntaxErrorInOwnersFile(Exception):
self.msg = msg self.msg = msg
def __str__(self): def __str__(self):
return "%s:%d syntax error: %s" % (self.path, self.lineno, self.msg) return '%s:%d syntax error: %s' % (self.path, self.lineno, self.msg)
class Database(object): class Database(object):
@ -111,6 +111,9 @@ class Database(object):
# Mapping of paths to authorized owners. # Mapping of paths to authorized owners.
self.owners_for = {} self.owners_for = {}
# Mapping reviewers to the preceding comment per file in the OWNERS files.
self.comments = {}
# Set of paths that stop us from looking above them for owners. # Set of paths that stop us from looking above them for owners.
# (This is implicitly true for the root directory). # (This is implicitly true for the root directory).
self.stop_looking = set(['']) self.stop_looking = set([''])
@ -122,7 +125,7 @@ class Database(object):
If author is nonempty, we ensure it is not included in the set returned If author is nonempty, we ensure it is not included in the set returned
in order avoid suggesting the author as a reviewer for their own changes.""" in order avoid suggesting the author as a reviewer for their own changes."""
self._check_paths(files) self._check_paths(files)
self._load_data_needed_for(files) self.load_data_needed_for(files)
suggested_owners = self._covering_set_of_owners_for(files, author) suggested_owners = self._covering_set_of_owners_for(files, author)
if EVERYONE in suggested_owners: if EVERYONE in suggested_owners:
if len(suggested_owners) > 1: if len(suggested_owners) > 1:
@ -140,7 +143,7 @@ class Database(object):
""" """
self._check_paths(files) self._check_paths(files)
self._check_reviewers(reviewers) self._check_reviewers(reviewers)
self._load_data_needed_for(files) self.load_data_needed_for(files)
covered_objs = self._objs_covered_by(reviewers) covered_objs = self._objs_covered_by(reviewers)
uncovered_files = [f for f in files uncovered_files = [f for f in files
@ -182,7 +185,7 @@ class Database(object):
dirpath = self.os_path.dirname(dirpath) dirpath = self.os_path.dirname(dirpath)
return dirpath return dirpath
def _load_data_needed_for(self, files): def load_data_needed_for(self, files):
for f in files: for f in files:
dirpath = self.os_path.dirname(f) dirpath = self.os_path.dirname(f)
while not dirpath in self.owners_for: while not dirpath in self.owners_for:
@ -195,18 +198,27 @@ class Database(object):
owners_path = self.os_path.join(self.root, dirpath, 'OWNERS') owners_path = self.os_path.join(self.root, dirpath, 'OWNERS')
if not self.os_path.exists(owners_path): if not self.os_path.exists(owners_path):
return return
comment = []
in_comment = False
lineno = 0 lineno = 0
for line in self.fopen(owners_path): for line in self.fopen(owners_path):
lineno += 1 lineno += 1
line = line.strip() line = line.strip()
if line.startswith('#') or line == '': if line.startswith('#'):
if not in_comment:
comment = []
comment.append(line[1:].strip())
in_comment = True
continue continue
if line == '':
continue
in_comment = False
if line == 'set noparent': if line == 'set noparent':
self.stop_looking.add(dirpath) self.stop_looking.add(dirpath)
continue continue
m = re.match("per-file (.+)=(.+)", line) m = re.match('per-file (.+)=(.+)', line)
if m: if m:
glob_string = m.group(1).strip() glob_string = m.group(1).strip()
directive = m.group(2).strip() directive = m.group(2).strip()
@ -217,20 +229,24 @@ class Database(object):
line) line)
baselines = self.glob(full_glob_string) baselines = self.glob(full_glob_string)
for baseline in (self.os_path.relpath(b, self.root) for b in baselines): for baseline in (self.os_path.relpath(b, self.root) for b in baselines):
self._add_entry(baseline, directive, "per-file line", self._add_entry(baseline, directive, 'per-file line',
owners_path, lineno) owners_path, lineno, '\n'.join(comment))
continue continue
if line.startswith('set '): if line.startswith('set '):
raise SyntaxErrorInOwnersFile(owners_path, lineno, raise SyntaxErrorInOwnersFile(owners_path, lineno,
'unknown option: "%s"' % line[4:].strip()) 'unknown option: "%s"' % line[4:].strip())
self._add_entry(dirpath, line, "line", owners_path, lineno) self._add_entry(dirpath, line, 'line', owners_path, lineno,
' '.join(comment))
def _add_entry(self, path, directive, line_type, owners_path, lineno): def _add_entry(self, path, directive,
if directive == "set noparent": line_type, owners_path, lineno, comment):
if directive == 'set noparent':
self.stop_looking.add(path) self.stop_looking.add(path)
elif self.email_regexp.match(directive) or directive == EVERYONE: elif self.email_regexp.match(directive) or directive == EVERYONE:
self.comments.setdefault(directive, {})
self.comments[directive][path] = comment
self.owned_by.setdefault(directive, set()).add(path) self.owned_by.setdefault(directive, set()).add(path)
self.owners_for.setdefault(path, set()).add(directive) self.owners_for.setdefault(path, set()).add(directive)
else: else:
@ -240,7 +256,7 @@ class Database(object):
def _covering_set_of_owners_for(self, files, author): def _covering_set_of_owners_for(self, files, author):
dirs_remaining = set(self._enclosing_dir_with_owners(f) for f in files) dirs_remaining = set(self._enclosing_dir_with_owners(f) for f in files)
all_possible_owners = self._all_possible_owners(dirs_remaining, author) all_possible_owners = self.all_possible_owners(dirs_remaining, author)
suggested_owners = set() suggested_owners = set()
while dirs_remaining: while dirs_remaining:
owner = self.lowest_cost_owner(all_possible_owners, dirs_remaining) owner = self.lowest_cost_owner(all_possible_owners, dirs_remaining)
@ -249,7 +265,7 @@ class Database(object):
dirs_remaining -= dirs_to_remove dirs_remaining -= dirs_to_remove
return suggested_owners return suggested_owners
def _all_possible_owners(self, dirs, author): def all_possible_owners(self, dirs, author):
"""Returns a list of (potential owner, distance-from-dir) tuples; a """Returns a list of (potential owner, distance-from-dir) tuples; a
distance of 1 is the lowest/closest possible distance (which makes the distance of 1 is the lowest/closest possible distance (which makes the
subsequent math easier).""" subsequent math easier)."""
@ -273,13 +289,13 @@ class Database(object):
return all_possible_owners return all_possible_owners
@staticmethod @staticmethod
def lowest_cost_owner(all_possible_owners, dirs): def total_costs_by_owner(all_possible_owners, dirs):
# We want to minimize both the number of reviewers and the distance # We want to minimize both the number of reviewers and the distance
# from the files/dirs needing reviews. The "pow(X, 1.75)" below is # from the files/dirs needing reviews. The "pow(X, 1.75)" below is
# an arbitrarily-selected scaling factor that seems to work well - it # an arbitrarily-selected scaling factor that seems to work well - it
# will select one reviewer in the parent directory over three reviewers # will select one reviewer in the parent directory over three reviewers
# in subdirs, but not one reviewer over just two. # in subdirs, but not one reviewer over just two.
total_costs_by_owner = {} result = {}
for owner in all_possible_owners: for owner in all_possible_owners:
total_distance = 0 total_distance = 0
num_directories_owned = 0 num_directories_owned = 0
@ -287,13 +303,18 @@ class Database(object):
if dirname in dirs: if dirname in dirs:
total_distance += distance total_distance += distance
num_directories_owned += 1 num_directories_owned += 1
if num_directories_owned: if num_directories_owned:
total_costs_by_owner[owner] = (total_distance / result[owner] = (total_distance /
pow(num_directories_owned, 1.75)) pow(num_directories_owned, 1.75))
return result
@staticmethod
def lowest_cost_owner(all_possible_owners, dirs):
total_costs_by_owner = Database.total_costs_by_owner(all_possible_owners,
dirs)
# Return the lowest cost owner. In the case of a tie, pick one randomly. # Return the lowest cost owner. In the case of a tie, pick one randomly.
lowest_cost = min(total_costs_by_owner.itervalues()) lowest_cost = min(total_costs_by_owner.itervalues())
lowest_cost_owners = filter( lowest_cost_owners = filter(
lambda owner: total_costs_by_owner[owner] == lowest_cost, lambda owner: total_costs_by_owner[owner] == lowest_cost,
total_costs_by_owner) total_costs_by_owner)
return random.Random().choice(lowest_cost_owners) return random.Random().choice(lowest_cost_owners)

@ -0,0 +1,365 @@
# Copyright 2013 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Interactive tool for finding reviewers/owners for a change."""
import os
import copy
import owners as owners_module
def first(iterable):
for element in iterable:
return element
class OwnersFinder(object):
COLOR_LINK = '\033[4m'
COLOR_BOLD = '\033[1;32m'
COLOR_GREY = '\033[0;37m'
COLOR_RESET = '\033[0m'
indentation = 0
def __init__(self, files, local_root, author,
fopen, os_path, glob,
email_postfix='@chromium.org',
disable_color=False):
self.email_postfix = email_postfix
if os.name == 'nt' or disable_color:
self.COLOR_LINK = ''
self.COLOR_BOLD = ''
self.COLOR_GREY = ''
self.COLOR_RESET = ''
self.db = owners_module.Database(local_root, fopen, os_path, glob)
self.db.load_data_needed_for(files)
self.os_path = os_path
self.author = author
filtered_files = files
# Eliminate files that author himself can review.
if author:
if author in self.db.owned_by:
for dir_name in self.db.owned_by[author]:
filtered_files = [
file_name for file_name in filtered_files
if not file_name.startswith(dir_name)]
filtered_files = list(filtered_files)
# Eliminate files that everyone can review.
if owners_module.EVERYONE in self.db.owned_by:
for dir_name in self.db.owned_by[owners_module.EVERYONE]:
filtered_files = filter(
lambda file_name: not file_name.startswith(dir_name),
filtered_files)
# If some files are eliminated.
if len(filtered_files) != len(files):
files = filtered_files
# Reload the database.
self.db = owners_module.Database(local_root, fopen, os_path, glob)
self.db.load_data_needed_for(files)
self.all_possible_owners = self.db.all_possible_owners(files, None)
self.owners_to_files = {}
self._map_owners_to_files(files)
self.files_to_owners = {}
self._map_files_to_owners()
self.owners_score = self.db.total_costs_by_owner(
self.all_possible_owners, files)
self.original_files_to_owners = copy.deepcopy(self.files_to_owners)
self.comments = self.db.comments
# This is the queue that will be shown in the interactive questions.
# It is initially sorted by the score in descending order. In the
# interactive questions a user can choose to "defer" its decision, then the
# owner will be put to the end of the queue and shown later.
self.owners_queue = []
self.unreviewed_files = set()
self.reviewed_by = {}
self.selected_owners = set()
self.deselected_owners = set()
self.reset()
def run(self):
self.reset()
while self.owners_queue and self.unreviewed_files:
owner = self.owners_queue[0]
if (owner in self.selected_owners) or (owner in self.deselected_owners):
continue
if not any((file_name in self.unreviewed_files)
for file_name in self.owners_to_files[owner]):
self.deselect_owner(owner)
continue
self.print_info(owner)
while True:
inp = self.input_command(owner)
if inp == 'y' or inp == 'yes':
self.select_owner(owner)
break
elif inp == 'n' or inp == 'no':
self.deselect_owner(owner)
break
elif inp == '' or inp == 'd' or inp == 'defer':
self.owners_queue.append(self.owners_queue.pop(0))
break
elif inp == 'f' or inp == 'files':
self.list_files()
break
elif inp == 'o' or inp == 'owners':
self.list_owners(self.owners_queue)
break
elif inp == 'p' or inp == 'pick':
self.pick_owner(raw_input('Pick an owner: '))
break
elif inp.startswith('p ') or inp.startswith('pick '):
self.pick_owner(inp.split(' ', 2)[1].strip())
break
elif inp == 'r' or inp == 'restart':
self.reset()
break
elif inp == 'q' or inp == 'quit':
# Exit with error
return 1
self.print_result()
return 0
def _map_owners_to_files(self, files):
for owner in self.all_possible_owners:
for dir_name, _ in self.all_possible_owners[owner]:
for file_name in files:
if file_name.startswith(dir_name):
self.owners_to_files.setdefault(owner, set())
self.owners_to_files[owner].add(file_name)
def _map_files_to_owners(self):
for owner in self.owners_to_files:
for file_name in self.owners_to_files[owner]:
self.files_to_owners.setdefault(file_name, set())
self.files_to_owners[file_name].add(owner)
def reset(self):
self.files_to_owners = copy.deepcopy(self.original_files_to_owners)
self.unreviewed_files = set(self.files_to_owners.keys())
self.reviewed_by = {}
self.selected_owners = set()
self.deselected_owners = set()
# Initialize owners queue, sort it by the score
self.owners_queue = list(sorted(self.owners_to_files.keys(),
key=lambda owner: self.owners_score[owner]))
self.find_mandatory_owners()
def select_owner(self, owner, findMandatoryOwners=True):
if owner in self.selected_owners or owner in self.deselected_owners\
or not (owner in self.owners_queue):
return
self.writeln('Selected: ' + owner)
self.owners_queue.remove(owner)
self.selected_owners.add(owner)
for file_name in filter(
lambda file_name: file_name in self.unreviewed_files,
self.owners_to_files[owner]):
self.unreviewed_files.remove(file_name)
self.reviewed_by[file_name] = owner
if findMandatoryOwners:
self.find_mandatory_owners()
def deselect_owner(self, owner, findMandatoryOwners=True):
if owner in self.selected_owners or owner in self.deselected_owners\
or not (owner in self.owners_queue):
return
self.writeln('Deselected: ' + owner)
self.owners_queue.remove(owner)
self.deselected_owners.add(owner)
for file_name in self.owners_to_files[owner] & self.unreviewed_files:
self.files_to_owners[file_name].remove(owner)
if findMandatoryOwners:
self.find_mandatory_owners()
def find_mandatory_owners(self):
continues = True
for owner in self.owners_queue:
if owner in self.selected_owners:
continue
if owner in self.deselected_owners:
continue
if len(self.owners_to_files[owner] & self.unreviewed_files) == 0:
self.deselect_owner(owner, False)
while continues:
continues = False
for file_name in filter(
lambda file_name: len(self.files_to_owners[file_name]) == 1,
self.unreviewed_files):
owner = first(self.files_to_owners[file_name])
self.select_owner(owner, False)
continues = True
break
def print_comments(self, owner):
if owner not in self.comments:
self.writeln(self.bold_name(owner))
else:
self.writeln(self.bold_name(owner) + ' is commented as:')
self.indent()
for path in self.comments[owner]:
if len(self.comments[owner][path]) > 0:
self.writeln(self.greyed(self.comments[owner][path]) +
' (at ' + self.bold(path or '<root>') + ')')
else:
self.writeln(self.greyed('[No comment] ') + ' (at ' +
self.bold(path or '<root>') + ')')
self.unindent()
def print_file_info(self, file_name, except_owner=''):
if file_name not in self.unreviewed_files:
self.writeln(self.greyed(file_name +
' (by ' +
self.bold_name(self.reviewed_by[file_name]) +
')'))
else:
if len(self.files_to_owners[file_name]) <= 3:
other_owners = []
for ow in self.files_to_owners[file_name]:
if ow != except_owner:
other_owners.append(self.bold_name(ow))
self.writeln(file_name +
' [' + (', '.join(other_owners)) + ']')
else:
self.writeln(file_name + ' [' +
self.bold(str(len(self.files_to_owners[file_name]))) +
']')
def print_file_info_detailed(self, file_name):
self.writeln(file_name)
self.indent()
for ow in sorted(self.files_to_owners[file_name]):
if ow in self.deselected_owners:
self.writeln(self.bold_name(self.greyed(ow)))
elif ow in self.selected_owners:
self.writeln(self.bold_name(self.greyed(ow)))
else:
self.writeln(self.bold_name(ow))
self.unindent()
def print_owned_files_for(self, owner):
# Print owned files
self.print_comments(owner)
self.writeln(self.bold_name(owner) + ' owns ' +
str(len(self.owners_to_files[owner])) + ' file(s):')
self.indent()
for file_name in sorted(self.owners_to_files[owner]):
self.print_file_info(file_name, owner)
self.unindent()
self.writeln()
def list_owners(self, owners_queue):
if (len(self.owners_to_files) - len(self.deselected_owners) -
len(self.selected_owners)) > 3:
for ow in owners_queue:
if ow not in self.deselected_owners and ow not in self.selected_owners:
self.print_comments(ow)
else:
for ow in owners_queue:
if ow not in self.deselected_owners and ow not in self.selected_owners:
self.writeln()
self.print_owned_files_for(ow)
def list_files(self):
self.indent()
if len(self.unreviewed_files) > 5:
for file_name in sorted(self.unreviewed_files):
self.print_file_info(file_name)
else:
for file_name in self.unreviewed_files:
self.print_file_info_detailed(file_name)
self.unindent()
def pick_owner(self, ow):
# Allowing to omit domain suffixes
if ow not in self.owners_to_files:
if ow + self.email_postfix in self.owners_to_files:
ow += self.email_postfix
if ow not in self.owners_to_files:
self.writeln('You cannot pick ' + self.bold_name(ow) + ' manually. ' +
'It\'s an invalid name or not related to the change list.')
return False
elif ow in self.selected_owners:
self.writeln('You cannot pick ' + self.bold_name(ow) + ' manually. ' +
'It\'s already selected.')
return False
elif ow in self.deselected_owners:
self.writeln('You cannot pick ' + self.bold_name(ow) + ' manually.' +
'It\'s already unselected.')
return False
self.select_owner(ow)
return True
def print_result(self):
# Print results
self.writeln()
self.writeln()
self.writeln('** You selected these owners **')
self.writeln()
for owner in self.selected_owners:
self.writeln(self.bold_name(owner) + ':')
self.indent()
for file_name in sorted(self.owners_to_files[owner]):
self.writeln(file_name)
self.unindent()
def bold(self, text):
return self.COLOR_BOLD + text + self.COLOR_RESET
def bold_name(self, name):
return (self.COLOR_BOLD +
name.replace(self.email_postfix, '') + self.COLOR_RESET)
def greyed(self, text):
return self.COLOR_GREY + text + self.COLOR_RESET
def indent(self):
self.indentation += 1
def unindent(self):
self.indentation -= 1
def print_indent(self):
return ' ' * self.indentation
def writeln(self, text=''):
print self.print_indent() + text
def hr(self):
self.writeln('=====================')
def print_info(self, owner):
self.hr()
self.writeln(
self.bold(str(len(self.unreviewed_files))) + ' file(s) left.')
self.print_owned_files_for(owner)
def input_command(self, owner):
self.writeln('Add ' + self.bold_name(owner) + ' as your reviewer? ')
return raw_input(
'[yes/no/Defer/pick/files/owners/quit/restart]: ').lower()

@ -0,0 +1,240 @@
#!/usr/bin/env python
# Copyright 2013 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Unit tests for owners_finder.py."""
import os
import sys
import unittest
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from testing_support import filesystem_mock
import owners_finder
import owners
ben = 'ben@example.com'
brett = 'brett@example.com'
darin = 'darin@example.com'
john = 'john@example.com'
ken = 'ken@example.com'
peter = 'peter@example.com'
tom = 'tom@example.com'
def owners_file(*email_addresses, **kwargs):
s = ''
if kwargs.get('comment'):
s += '# %s\n' % kwargs.get('comment')
if kwargs.get('noparent'):
s += 'set noparent\n'
s += '\n'.join(kwargs.get('lines', [])) + '\n'
return s + '\n'.join(email_addresses) + '\n'
def test_repo():
return filesystem_mock.MockFileSystem(files={
'/DEPS': '',
'/OWNERS': owners_file(ken, peter, tom),
'/base/vlog.h': '',
'/chrome/OWNERS': owners_file(ben, brett),
'/chrome/browser/OWNERS': owners_file(brett),
'/chrome/browser/defaults.h': '',
'/chrome/gpu/OWNERS': owners_file(ken),
'/chrome/gpu/gpu_channel.h': '',
'/chrome/renderer/OWNERS': owners_file(peter),
'/chrome/renderer/gpu/gpu_channel_host.h': '',
'/chrome/renderer/safe_browsing/scorer.h': '',
'/content/OWNERS': owners_file(john, darin, comment='foo', noparent=True),
'/content/content.gyp': '',
'/content/bar/foo.cc': '',
'/content/baz/OWNERS': owners_file(brett),
'/content/baz/froboz.h': '',
'/content/baz/ugly.cc': '',
'/content/baz/ugly.h': '',
'/content/views/OWNERS': owners_file(ben, john, owners.EVERYONE,
noparent=True),
'/content/views/pie.h': '',
})
class OutputInterceptedOwnersFinder(owners_finder.OwnersFinder):
def __init__(self, files, local_root,
fopen, os_path, glob,
disable_color=False):
super(OutputInterceptedOwnersFinder, self).__init__(
files, local_root, None,
fopen, os_path, glob, disable_color=disable_color)
self.output = []
self.indentation_stack = []
def resetText(self):
self.output = []
self.indentation_stack = []
def indent(self):
self.indentation_stack.append(self.output)
self.output = []
def unindent(self):
block = self.output
self.output = self.indentation_stack.pop()
self.output.append(block)
def writeln(self, text=''):
self.output.append(text)
class _BaseTestCase(unittest.TestCase):
default_files = [
'base/vlog.h',
'chrome/browser/defaults.h',
'chrome/gpu/gpu_channel.h',
'chrome/renderer/gpu/gpu_channel_host.h',
'chrome/renderer/safe_browsing/scorer.h',
'content/content.gyp',
'content/bar/foo.cc',
'content/baz/ugly.cc',
'content/baz/ugly.h',
'content/views/pie.h'
]
def setUp(self):
self.repo = test_repo()
self.root = '/'
self.fopen = self.repo.open_for_reading
self.glob = self.repo.glob
def ownersFinder(self, files):
finder = OutputInterceptedOwnersFinder(files, self.root,
fopen=self.fopen,
os_path=self.repo,
glob=self.glob,
disable_color=True)
return finder
def defaultFinder(self):
return self.ownersFinder(self.default_files)
class OwnersFinderTests(_BaseTestCase):
def test_constructor(self):
self.assertNotEquals(self.defaultFinder(), None)
def test_reset(self):
finder = self.defaultFinder()
i = 0
while i < 2:
i += 1
self.assertEqual(finder.owners_queue,
[brett, john, darin, peter, ken, ben, tom])
self.assertEqual(finder.unreviewed_files, {
'base/vlog.h',
'chrome/browser/defaults.h',
'chrome/gpu/gpu_channel.h',
'chrome/renderer/gpu/gpu_channel_host.h',
'chrome/renderer/safe_browsing/scorer.h',
'content/content.gyp',
'content/bar/foo.cc',
'content/baz/ugly.cc',
'content/baz/ugly.h'
})
self.assertEqual(finder.selected_owners, set())
self.assertEqual(finder.deselected_owners, set())
self.assertEqual(finder.reviewed_by, {})
self.assertEqual(finder.output, [])
finder.select_owner(john)
finder.reset()
finder.resetText()
def test_select(self):
finder = self.defaultFinder()
finder.select_owner(john)
self.assertEqual(finder.owners_queue, [brett, peter, ken, ben, tom])
self.assertEqual(finder.selected_owners, {john})
self.assertEqual(finder.deselected_owners, {darin})
self.assertEqual(finder.reviewed_by, {'content/bar/foo.cc': john,
'content/baz/ugly.cc': john,
'content/baz/ugly.h': john,
'content/content.gyp': john})
self.assertEqual(finder.output,
['Selected: ' + john, 'Deselected: ' + darin])
finder = self.defaultFinder()
finder.select_owner(darin)
self.assertEqual(finder.owners_queue, [brett, peter, ken, ben, tom])
self.assertEqual(finder.selected_owners, {darin})
self.assertEqual(finder.deselected_owners, {john})
self.assertEqual(finder.reviewed_by, {'content/bar/foo.cc': darin,
'content/baz/ugly.cc': darin,
'content/baz/ugly.h': darin,
'content/content.gyp': darin})
self.assertEqual(finder.output,
['Selected: ' + darin, 'Deselected: ' + john])
finder = self.defaultFinder()
finder.select_owner(brett)
self.assertEqual(finder.owners_queue, [john, darin, peter, ken, tom])
self.assertEqual(finder.selected_owners, {brett})
self.assertEqual(finder.deselected_owners, {ben})
self.assertEqual(finder.reviewed_by,
{'chrome/browser/defaults.h': brett,
'chrome/gpu/gpu_channel.h': brett,
'chrome/renderer/gpu/gpu_channel_host.h': brett,
'chrome/renderer/safe_browsing/scorer.h': brett,
'content/baz/ugly.cc': brett,
'content/baz/ugly.h': brett})
self.assertEqual(finder.output,
['Selected: ' + brett, 'Deselected: ' + ben])
def test_deselect(self):
finder = self.defaultFinder()
finder.deselect_owner(john)
self.assertEqual(finder.owners_queue, [brett, peter, ken, ben, tom])
self.assertEqual(finder.selected_owners, {darin})
self.assertEqual(finder.deselected_owners, {john})
self.assertEqual(finder.reviewed_by, {'content/bar/foo.cc': darin,
'content/baz/ugly.cc': darin,
'content/baz/ugly.h': darin,
'content/content.gyp': darin})
self.assertEqual(finder.output,
['Deselected: ' + john, 'Selected: ' + darin])
def test_print_file_info(self):
finder = self.defaultFinder()
finder.print_file_info('chrome/browser/defaults.h')
self.assertEqual(finder.output, ['chrome/browser/defaults.h [5]'])
finder.resetText()
finder.print_file_info('chrome/renderer/gpu/gpu_channel_host.h')
self.assertEqual(finder.output,
['chrome/renderer/gpu/gpu_channel_host.h [5]'])
def test_print_file_info_detailed(self):
finder = self.defaultFinder()
finder.print_file_info_detailed('chrome/browser/defaults.h')
self.assertEqual(finder.output,
['chrome/browser/defaults.h',
[ben, brett, ken, peter, tom]])
finder.resetText()
finder.print_file_info_detailed('chrome/renderer/gpu/gpu_channel_host.h')
self.assertEqual(finder.output,
['chrome/renderer/gpu/gpu_channel_host.h',
[ben, brett, ken, peter, tom]])
def test_print_comments(self):
finder = self.defaultFinder()
finder.print_comments(darin)
self.assertEqual(finder.output,
[darin + ' is commented as:', ['foo (at content)']])
if __name__ == '__main__':
unittest.main()
Loading…
Cancel
Save