owners_client_test.py 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293
  1. #!/usr/bin/env vpython3
  2. # Copyright (c) 2020 The Chromium Authors. All rights reserved.
  3. # Use of this source code is governed by a BSD-style license that can be
  4. # found in the LICENSE file.
  5. import os
  6. import sys
  7. import unittest
  8. from unittest import mock
  9. sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
  10. import gerrit_util
  11. import owners_client
  12. alice = 'alice@example.com'
  13. bob = 'bob@example.com'
  14. chris = 'chris@example.com'
  15. dave = 'dave@example.com'
  16. emily = 'emily@example.com'
  17. class GerritClientTest(unittest.TestCase):
  18. def setUp(self):
  19. self.client = owners_client.GerritClient('host', 'project', 'branch')
  20. self.addCleanup(mock.patch.stopall)
  21. def testListOwners(self):
  22. mock.patch('gerrit_util.GetOwnersForFile',
  23. return_value={
  24. "code_owners": [{
  25. "account": {
  26. "email": 'approver@example.com'
  27. }
  28. }, {
  29. "account": {
  30. "email": 'reviewer@example.com'
  31. },
  32. }, {
  33. "account": {
  34. "email": 'missing@example.com'
  35. },
  36. }, {
  37. "account": {},
  38. }]
  39. }).start()
  40. self.assertEqual([
  41. 'approver@example.com', 'reviewer@example.com',
  42. 'missing@example.com'
  43. ], self.client.ListOwners(os.path.join('bar', 'everyone', 'foo.txt')))
  44. # Result should be cached.
  45. self.assertEqual([
  46. 'approver@example.com', 'reviewer@example.com',
  47. 'missing@example.com'
  48. ], self.client.ListOwners(os.path.join('bar', 'everyone', 'foo.txt')))
  49. # Always use slashes as separators.
  50. gerrit_util.GetOwnersForFile.assert_called_once_with(
  51. 'host',
  52. 'project',
  53. 'branch',
  54. 'bar/everyone/foo.txt',
  55. resolve_all_users=False,
  56. highest_score_only=False,
  57. seed=mock.ANY)
  58. def testListOwnersOwnedByAll(self):
  59. mock.patch('gerrit_util.GetOwnersForFile',
  60. side_effect=[
  61. {
  62. "code_owners": [
  63. {
  64. "account": {
  65. "email": 'foo@example.com'
  66. },
  67. },
  68. ],
  69. "owned_by_all_users":
  70. True,
  71. },
  72. {
  73. "code_owners": [
  74. {
  75. "account": {
  76. "email": 'bar@example.com'
  77. },
  78. },
  79. ],
  80. "owned_by_all_users":
  81. False,
  82. },
  83. ]).start()
  84. self.assertEqual(['foo@example.com', self.client.EVERYONE],
  85. self.client.ListOwners('foo.txt'))
  86. self.assertEqual(['bar@example.com'], self.client.ListOwners('bar.txt'))
  87. class TestClient(owners_client.OwnersClient):
  88. def __init__(self, owners_by_path):
  89. super(TestClient, self).__init__()
  90. self.owners_by_path = owners_by_path
  91. def ListOwners(self, path):
  92. return self.owners_by_path[path]
  93. class OwnersClientTest(unittest.TestCase):
  94. def setUp(self):
  95. self.owners = {}
  96. self.client = TestClient(self.owners)
  97. def testGetFilesApprovalStatus(self):
  98. self.client.owners_by_path = {
  99. 'approved': ['approver@example.com'],
  100. 'pending': ['reviewer@example.com'],
  101. 'insufficient': ['insufficient@example.com'],
  102. 'everyone': [owners_client.OwnersClient.EVERYONE],
  103. }
  104. self.assertEqual(
  105. self.client.GetFilesApprovalStatus(
  106. ['approved', 'pending', 'insufficient'],
  107. ['approver@example.com'], ['reviewer@example.com']), {
  108. 'approved': owners_client.OwnersClient.APPROVED,
  109. 'pending': owners_client.OwnersClient.PENDING,
  110. 'insufficient':
  111. owners_client.OwnersClient.INSUFFICIENT_REVIEWERS,
  112. })
  113. self.assertEqual(
  114. self.client.GetFilesApprovalStatus(['everyone'],
  115. ['anyone@example.com'], []),
  116. {'everyone': owners_client.OwnersClient.APPROVED})
  117. self.assertEqual(
  118. self.client.GetFilesApprovalStatus(['everyone'], [],
  119. ['anyone@example.com']),
  120. {'everyone': owners_client.OwnersClient.PENDING})
  121. self.assertEqual(
  122. self.client.GetFilesApprovalStatus(['everyone'], [], []),
  123. {'everyone': owners_client.OwnersClient.INSUFFICIENT_REVIEWERS})
  124. def testScoreOwners(self):
  125. self.client.owners_by_path = {'a': [alice, bob, chris]}
  126. self.assertEqual(
  127. self.client.ScoreOwners(self.client.owners_by_path.keys()),
  128. [alice, bob, chris])
  129. self.client.owners_by_path = {
  130. 'a': [alice, bob],
  131. 'b': [bob],
  132. 'c': [bob, chris]
  133. }
  134. self.assertEqual(
  135. self.client.ScoreOwners(self.client.owners_by_path.keys()),
  136. [alice, bob, chris])
  137. self.client.owners_by_path = {
  138. 'a': [alice, bob],
  139. 'b': [bob],
  140. 'c': [bob, chris]
  141. }
  142. self.assertEqual(
  143. self.client.ScoreOwners(self.client.owners_by_path.keys(),
  144. exclude=[chris]),
  145. [alice, bob],
  146. )
  147. self.client.owners_by_path = {
  148. 'a': [alice, bob, chris, dave],
  149. 'b': [chris, bob, dave],
  150. 'c': [chris, dave],
  151. 'd': [alice, chris, dave]
  152. }
  153. self.assertEqual(
  154. self.client.ScoreOwners(self.client.owners_by_path.keys()),
  155. [alice, chris, bob, dave])
  156. def assertSuggestsOwners(self, owners_by_path, exclude=None):
  157. self.client.owners_by_path = owners_by_path
  158. suggested = self.client.SuggestOwners(owners_by_path.keys(),
  159. exclude=exclude)
  160. # Owners should appear only once
  161. self.assertEqual(len(suggested), len(set(suggested)))
  162. # All paths should be covered.
  163. suggested = set(suggested)
  164. for owners in owners_by_path.values():
  165. self.assertTrue(suggested & set(owners))
  166. # No excluded owners should be present.
  167. if exclude:
  168. for owner in suggested:
  169. self.assertNotIn(owner, exclude)
  170. def testSuggestOwners(self):
  171. self.assertSuggestsOwners({})
  172. self.assertSuggestsOwners({'a': [alice]})
  173. self.assertSuggestsOwners({'abcd': [alice, bob, chris, dave]})
  174. self.assertSuggestsOwners({'abcd': [alice, bob, chris, dave]},
  175. exclude=[alice, bob])
  176. self.assertSuggestsOwners({
  177. 'ae': [alice, emily],
  178. 'be': [bob, emily],
  179. 'ce': [chris, emily],
  180. 'de': [dave, emily]
  181. })
  182. self.assertSuggestsOwners({
  183. 'ad': [alice, dave],
  184. 'cad': [chris, alice, dave],
  185. 'ead': [emily, alice, dave],
  186. 'bd': [bob, dave]
  187. })
  188. self.assertSuggestsOwners({
  189. 'a': [alice],
  190. 'b': [bob],
  191. 'c': [chris],
  192. 'ad': [alice, dave]
  193. })
  194. self.assertSuggestsOwners({
  195. 'abc': [alice, bob, chris],
  196. 'acb': [alice, chris, bob],
  197. 'bac': [bob, alice, chris],
  198. 'bca': [bob, chris, alice],
  199. 'cab': [chris, alice, bob],
  200. 'cba': [chris, bob, alice]
  201. })
  202. # Check that we can handle a large amount of files with unrelated
  203. # owners.
  204. self.assertSuggestsOwners({str(x): [str(x)] for x in range(100)})
  205. def testBatchListOwners(self):
  206. self.client.owners_by_path = {
  207. 'bar/everyone/foo.txt': [alice, bob],
  208. 'bar/everyone/bar.txt': [bob],
  209. 'bar/foo/': [bob, chris]
  210. }
  211. self.assertEqual(
  212. {
  213. 'bar/everyone/foo.txt': [alice, bob],
  214. 'bar/everyone/bar.txt': [bob],
  215. 'bar/foo/': [bob, chris]
  216. },
  217. self.client.BatchListOwners(
  218. ['bar/everyone/foo.txt', 'bar/everyone/bar.txt', 'bar/foo/']))
  219. def testSuggestMinimalOwners(self):
  220. self.client.owners_by_path = {
  221. 'bar/everyone/foo.txt': [alice, bob, emily],
  222. 'bar/everyone/bar.txt': [bob, emily],
  223. 'bar/foo/': [bob, chris, emily],
  224. 'baz/baz/baz': [chris, dave, emily]
  225. }
  226. self.assertEqual([bob],
  227. self.client.SuggestMinimalOwners([
  228. 'bar/everyone/foo.txt', 'bar/everyone/bar.txt',
  229. 'bar/foo/'
  230. ]))
  231. self.assertEqual([chris],
  232. self.client.SuggestMinimalOwners(
  233. ['bar/foo/', 'baz/baz/baz']))
  234. # If no common owner exists, fallback to returning multiple owners
  235. self.assertEqual([alice, bob, chris],
  236. self.client.SuggestMinimalOwners([
  237. 'bar/everyone/foo.txt', 'bar/everyone/bar.txt',
  238. 'bar/foo/', 'baz/baz/baz'
  239. ],
  240. exclude=[emily]))
  241. class GetCodeOwnersClientTest(unittest.TestCase):
  242. def setUp(self):
  243. mock.patch('gerrit_util.IsCodeOwnersEnabledOnHost').start()
  244. self.addCleanup(mock.patch.stopall)
  245. def testGetCodeOwnersClient_CodeOwnersEnabled(self):
  246. gerrit_util.IsCodeOwnersEnabledOnHost.return_value = True
  247. self.assertIsInstance(
  248. owners_client.GetCodeOwnersClient('host', 'project', 'branch'),
  249. owners_client.GerritClient)
  250. def testGetCodeOwnersClient_CodeOwnersDisabled(self):
  251. gerrit_util.IsCodeOwnersEnabledOnHost.return_value = False
  252. with self.assertRaises(Exception):
  253. owners_client.GetCodeOwnersClient('', '', '')
  254. if __name__ == '__main__':
  255. unittest.main()