Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(307)

Side by Side Diff: tests/owners_unittest.py

Issue 11645009: Try again to land the improved owners algorithm. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/tools/depot_tools
Patch Set: update w/ review feedback Created 8 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « owners.py ('k') | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 #!/usr/bin/env python 1 #!/usr/bin/env python
2 # Copyright (c) 2012 The Chromium Authors. All rights reserved. 2 # Copyright (c) 2012 The Chromium Authors. All rights reserved.
3 # Use of this source code is governed by a BSD-style license that can be 3 # Use of this source code is governed by a BSD-style license that can be
4 # found in the LICENSE file. 4 # found in the LICENSE file.
5 5
6 """Unit tests for owners.py.""" 6 """Unit tests for owners.py."""
7 7
8 import os 8 import os
9 import sys 9 import sys
10 import unittest 10 import unittest
(...skipping 22 matching lines...) Expand all
33 s += '\n'.join(kwargs.get('lines', [])) + '\n' 33 s += '\n'.join(kwargs.get('lines', [])) + '\n'
34 return s + '\n'.join(email_addresses) + '\n' 34 return s + '\n'.join(email_addresses) + '\n'
35 35
36 36
37 def test_repo(): 37 def test_repo():
38 return filesystem_mock.MockFileSystem(files={ 38 return filesystem_mock.MockFileSystem(files={
39 '/DEPS' : '', 39 '/DEPS' : '',
40 '/OWNERS': owners_file(owners.EVERYONE), 40 '/OWNERS': owners_file(owners.EVERYONE),
41 '/base/vlog.h': '', 41 '/base/vlog.h': '',
42 '/chrome/OWNERS': owners_file(ben, brett), 42 '/chrome/OWNERS': owners_file(ben, brett),
43 '/chrome/browser/OWNERS': owners_file(brett),
44 '/chrome/browser/defaults.h': '',
43 '/chrome/gpu/OWNERS': owners_file(ken), 45 '/chrome/gpu/OWNERS': owners_file(ken),
44 '/chrome/gpu/gpu_channel.h': '', 46 '/chrome/gpu/gpu_channel.h': '',
45 '/chrome/renderer/OWNERS': owners_file(peter), 47 '/chrome/renderer/OWNERS': owners_file(peter),
46 '/chrome/renderer/gpu/gpu_channel_host.h': '', 48 '/chrome/renderer/gpu/gpu_channel_host.h': '',
47 '/chrome/renderer/safe_browsing/scorer.h': '', 49 '/chrome/renderer/safe_browsing/scorer.h': '',
48 '/content/OWNERS': owners_file(john, darin, comment='foo', noparent=True), 50 '/content/OWNERS': owners_file(john, darin, comment='foo', noparent=True),
49 '/content/content.gyp': '', 51 '/content/content.gyp': '',
50 '/content/bar/foo.cc': '', 52 '/content/bar/foo.cc': '',
51 '/content/baz/OWNERS': owners_file(brett), 53 '/content/baz/OWNERS': owners_file(brett),
52 '/content/baz/froboz.h': '', 54 '/content/baz/froboz.h': '',
53 '/content/baz/ugly.cc': '', 55 '/content/baz/ugly.cc': '',
54 '/content/baz/ugly.h': '', 56 '/content/baz/ugly.h': '',
55 '/content/views/OWNERS': owners_file(ben, john, owners.EVERYONE, 57 '/content/views/OWNERS': owners_file(ben, john, owners.EVERYONE,
56 noparent=True), 58 noparent=True),
57 '/content/views/pie.h': '', 59 '/content/views/pie.h': '',
58 }) 60 })
59 61
60 62
61 class OwnersDatabaseTest(unittest.TestCase): 63 class _BaseTestCase(unittest.TestCase):
62 def setUp(self): 64 def setUp(self):
63 self.repo = test_repo() 65 self.repo = test_repo()
64 self.files = self.repo.files 66 self.files = self.repo.files
65 self.root = '/' 67 self.root = '/'
66 self.fopen = self.repo.open_for_reading 68 self.fopen = self.repo.open_for_reading
67 self.glob = self.repo.glob 69 self.glob = self.repo.glob
68 70
69 def db(self, root=None, fopen=None, os_path=None, glob=None): 71 def db(self, root=None, fopen=None, os_path=None, glob=None):
70 root = root or self.root 72 root = root or self.root
71 fopen = fopen or self.fopen 73 fopen = fopen or self.fopen
72 os_path = os_path or self.repo 74 os_path = os_path or self.repo
73 glob = glob or self.glob 75 glob = glob or self.glob
74 return owners.Database(root, fopen, os_path, glob) 76 return owners.Database(root, fopen, os_path, glob)
75 77
78
79 class OwnersDatabaseTest(_BaseTestCase):
76 def test_constructor(self): 80 def test_constructor(self):
77 self.assertNotEquals(self.db(), None) 81 self.assertNotEquals(self.db(), None)
78 82
79 def test_dirs_not_covered_by__valid_inputs(self): 83 def test_dirs_not_covered_by__valid_inputs(self):
80 db = self.db() 84 db = self.db()
81 85
82 # Check that we're passed in a sequence that isn't a string. 86 # Check that we're passed in a sequence that isn't a string.
83 self.assertRaises(AssertionError, db.directories_not_covered_by, 'foo', []) 87 self.assertRaises(AssertionError, db.directories_not_covered_by, 'foo', [])
84 if hasattr(owners.collections, 'Iterable'): 88 if hasattr(owners.collections, 'Iterable'):
85 self.assertRaises(AssertionError, db.directories_not_covered_by, 89 self.assertRaises(AssertionError, db.directories_not_covered_by,
(...skipping 112 matching lines...) Expand 10 before | Expand all | Expand 10 after
198 # This test ensures the mock relpath has the arguments in the right 202 # This test ensures the mock relpath has the arguments in the right
199 # order; this should probably live someplace else. 203 # order; this should probably live someplace else.
200 self.assertEquals(self.repo.relpath('foo/bar.c', 'foo/'), 'bar.c') 204 self.assertEquals(self.repo.relpath('foo/bar.c', 'foo/'), 'bar.c')
201 self.assertEquals(self.repo.relpath('/bar.c', '/'), 'bar.c') 205 self.assertEquals(self.repo.relpath('/bar.c', '/'), 'bar.c')
202 206
203 def test_per_file_glob_across_dirs_not_allowed(self): 207 def test_per_file_glob_across_dirs_not_allowed(self):
204 self.files['/OWNERS'] = 'per-file content/*=john@example.org\n' 208 self.files['/OWNERS'] = 'per-file content/*=john@example.org\n'
205 self.assertRaises(owners.SyntaxErrorInOwnersFile, 209 self.assertRaises(owners.SyntaxErrorInOwnersFile,
206 self.db().directories_not_covered_by, ['DEPS'], [brett]) 210 self.db().directories_not_covered_by, ['DEPS'], [brett])
207 211
208 def assert_reviewers_for(self, files, expected_reviewers): 212 def assert_syntax_error(self, owners_file_contents):
209 db = self.db() 213 db = self.db()
210 self.assertEquals(db.reviewers_for(set(files)), set(expected_reviewers)) 214 self.files['/foo/OWNERS'] = owners_file_contents
215 self.files['/foo/DEPS'] = ''
216 try:
217 db.reviewers_for(['foo/DEPS'])
218 self.fail() # pragma: no cover
219 except owners.SyntaxErrorInOwnersFile, e:
220 self.assertTrue(str(e).startswith('/foo/OWNERS:1'))
221
222 def test_syntax_error__unknown_token(self):
223 self.assert_syntax_error('{}\n')
224
225 def test_syntax_error__unknown_set(self):
226 self.assert_syntax_error('set myfatherisbillgates\n')
227
228 def test_syntax_error__bad_email(self):
229 self.assert_syntax_error('ben\n')
230
231
232 class ReviewersForTest(_BaseTestCase):
233 def assert_reviewers_for(self, files, *potential_suggested_reviewers):
234 db = self.db()
235 suggested_reviewers = db.reviewers_for(set(files))
236 self.assertTrue(suggested_reviewers in
237 [set(suggestion) for suggestion in potential_suggested_reviewers])
211 238
212 def test_reviewers_for__basic_functionality(self): 239 def test_reviewers_for__basic_functionality(self):
213 self.assert_reviewers_for(['chrome/gpu/gpu_channel.h'], 240 self.assert_reviewers_for(['chrome/gpu/gpu_channel.h'],
214 [brett]) 241 [ken])
215 242
216 def test_reviewers_for__set_noparent_works(self): 243 def test_reviewers_for__set_noparent_works(self):
217 self.assert_reviewers_for(['content/content.gyp'], [darin]) 244 self.assert_reviewers_for(['content/content.gyp'],
245 [john],
246 [darin])
218 247
219 def test_reviewers_for__valid_inputs(self): 248 def test_reviewers_for__valid_inputs(self):
220 db = self.db() 249 db = self.db()
221 250
222 # Check that we're passed in a sequence that isn't a string. 251 # Check that we're passed in a sequence that isn't a string.
223 self.assertRaises(AssertionError, db.reviewers_for, 'foo') 252 self.assertRaises(AssertionError, db.reviewers_for, 'foo')
224 if hasattr(owners.collections, 'Iterable'): 253 if hasattr(owners.collections, 'Iterable'):
225 self.assertRaises(AssertionError, db.reviewers_for, 254 self.assertRaises(AssertionError, db.reviewers_for,
226 (f for f in ['x', 'y'])) 255 (f for f in ['x', 'y']))
227 256
228 # Check that the files are under the root. 257 # Check that the files are under the root.
229 db.root = '/checkout' 258 db.root = '/checkout'
230 self.assertRaises(AssertionError, db.reviewers_for, ['/OWNERS']) 259 self.assertRaises(AssertionError, db.reviewers_for, ['/OWNERS'])
231 260
232 def test_reviewers_for__wildcard_dir(self): 261 def test_reviewers_for__wildcard_dir(self):
233 self.assert_reviewers_for(['DEPS'], [owners.EVERYONE]) 262 self.assert_reviewers_for(['DEPS'], [owners.EVERYONE])
234 263
235 def test_reviewers_for__one_owner(self): 264 def test_reviewers_for__one_owner(self):
236 self.assert_reviewers_for([ 265 self.assert_reviewers_for([
237 'chrome/gpu/gpu_channel.h', 266 'chrome/gpu/gpu_channel.h',
238 'content/baz/froboz.h', 267 'content/baz/froboz.h',
239 'chrome/renderer/gpu/gpu_channel_host.h'], [brett]) 268 'chrome/renderer/gpu/gpu_channel_host.h'],
269 [brett])
240 270
241 def test_reviewers_for__two_owners(self): 271 def test_reviewers_for__two_owners(self):
242 self.assert_reviewers_for([ 272 self.assert_reviewers_for([
243 'chrome/gpu/gpu_channel.h', 273 'chrome/gpu/gpu_channel.h',
244 'content/content.gyp', 274 'content/content.gyp',
245 'content/baz/froboz.h', 275 'content/baz/froboz.h',
246 'content/views/pie.h' 276 'content/views/pie.h'],
247 ], [john, brett]) 277 [ken, john])
248 278
249 def test_reviewers_for__all_files(self): 279 def test_reviewers_for__all_files(self):
250 self.assert_reviewers_for([ 280 self.assert_reviewers_for([
251 'chrome/gpu/gpu_channel.h', 281 'chrome/gpu/gpu_channel.h',
252 'chrome/renderer/gpu/gpu_channel_host.h', 282 'chrome/renderer/gpu/gpu_channel_host.h',
253 'chrome/renderer/safe_browsing/scorer.h', 283 'chrome/renderer/safe_browsing/scorer.h',
254 'content/content.gyp', 284 'content/content.gyp',
255 'content/bar/foo.cc', 285 'content/bar/foo.cc',
256 'content/baz/froboz.h', 286 'content/baz/froboz.h',
257 'content/views/pie.h'], [john, brett]) 287 'content/views/pie.h'],
288 [peter, ken, john])
258 289
259 def test_reviewers_for__per_file_owners_file(self): 290 def test_reviewers_for__per_file_owners_file(self):
260 self.files['/content/baz/OWNERS'] = owners_file(lines=[ 291 self.files['/content/baz/OWNERS'] = owners_file(lines=[
261 'per-file ugly.*=tom@example.com']) 292 'per-file ugly.*=tom@example.com'])
262 self.assert_reviewers_for(['content/baz/OWNERS'], [darin]) 293 self.assert_reviewers_for(['content/baz/OWNERS'],
294 [john],
295 [darin])
263 296
264 def assert_syntax_error(self, owners_file_contents): 297 def test_reviewers_for__per_file(self):
265 db = self.db() 298 self.files['/content/baz/OWNERS'] = owners_file(lines=[
266 self.files['/foo/OWNERS'] = owners_file_contents 299 'per-file ugly.*=tom@example.com'])
267 self.files['/foo/DEPS'] = '' 300 self.assert_reviewers_for(['content/baz/ugly.cc'],
268 try: 301 [tom])
269 db.reviewers_for(['foo/DEPS'])
270 self.fail() # pragma: no cover
271 except owners.SyntaxErrorInOwnersFile, e:
272 self.assertTrue(str(e).startswith('/foo/OWNERS:1'))
273 302
274 def test_syntax_error__unknown_token(self): 303 def test_reviewers_for__two_nested_dirs(self):
275 self.assert_syntax_error('{}\n') 304 # The same owner is listed in two directories (one above the other)
305 self.assert_reviewers_for(['chrome/browser/defaults.h'],
306 [brett])
276 307
277 def test_syntax_error__unknown_set(self): 308 class LowestCostOwnersTest(_BaseTestCase):
278 self.assert_syntax_error('set myfatherisbillgates\n') 309 # Keep the data in the test_lowest_cost_owner* methods as consistent with
310 # test_repo() where possible to minimize confusion.
279 311
280 def test_syntax_error__bad_email(self): 312 def check(self, possible_owners, dirs, *possible_lowest_cost_owners):
281 self.assert_syntax_error('ben\n') 313 suggested_owner = owners.Database.lowest_cost_owner(possible_owners, dirs)
314 self.assertTrue(suggested_owner in possible_lowest_cost_owners)
282 315
316 def test_one_dir_with_owner(self):
317 # brett is the only immediate owner for stuff in baz; john is also
318 # an owner, but further removed. We should always get brett.
319 self.check({brett: [('content/baz', 1)],
320 john: [('content/baz', 2)]},
321 ['content/baz'],
322 brett)
323
324 # john and darin are owners for content; the suggestion could be either.
325 def test_one_dir_with_two_owners(self):
326 self.check({john: [('content', 1)],
327 darin: [('content', 1)]},
328 ['content'],
329 john, darin)
330
331 def test_one_dir_with_two_owners_in_parent(self):
332 # As long as the distance is the same, it shouldn't matter (brett isn't
333 # listed in this case).
334 self.check({john: [('content/baz', 2)],
335 darin: [('content/baz', 2)]},
336 ['content/baz'],
337 john, darin)
338
339 def test_two_dirs_two_owners(self):
340 # If they both match both dirs, they should be treated equally.
341 self.check({john: [('content/baz', 2), ('content/bar', 2)],
342 darin: [('content/baz', 2), ('content/bar', 2)]},
343 ['content/baz', 'content/bar'],
344 john, darin)
345
346 # Here brett is better since he's closer for one of the two dirs.
347 self.check({brett: [('content/baz', 1), ('content/views', 1)],
348 darin: [('content/baz', 2), ('content/views', 1)]},
349 ['content/baz', 'content/views'],
350 brett)
351
352 def test_hierarchy(self):
353 # the choices in these tests are more arbitrary value judgements;
354 # also, here we drift away from test_repo() to cover more cases.
355
356 # Here ben isn't picked, even though he can review both; we prefer
357 # closer reviewers.
358 self.check({ben: [('chrome/gpu', 2), ('chrome/renderer', 2)],
359 ken: [('chrome/gpu', 1)],
360 peter: [('chrome/renderer', 1)]},
361 ['chrome/gpu', 'chrome/renderer'],
362 ken, peter)
363
364 # Here we always pick ben since he can review either dir as well as
365 # the others but can review both (giving us fewer total reviewers).
366 self.check({ben: [('chrome/gpu', 1), ('chrome/renderer', 1)],
367 ken: [('chrome/gpu', 1)],
368 peter: [('chrome/renderer', 1)]},
369 ['chrome/gpu', 'chrome/renderer'],
370 ben)
371
372 # However, three reviewers is too many, so ben gets this one.
373 self.check({ben: [('chrome/gpu', 2), ('chrome/renderer', 2),
374 ('chrome/browser', 2)],
375 ken: [('chrome/gpu', 1)],
376 peter: [('chrome/renderer', 1)],
377 brett: [('chrome/browser', 1)]},
378 ['chrome/gpu', 'chrome/renderer',
379 'chrome/browser'],
380 ben)
283 381
284 if __name__ == '__main__': 382 if __name__ == '__main__':
285 unittest.main() 383 unittest.main()
OLDNEW
« no previous file with comments | « owners.py ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698