1# Copyright (C) 2020 The Android Open Source Project 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6# 7# http://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14"""Unit tests for external updater reviewers.""" 15 16from typing import List, Mapping, Set 17import unittest 18 19import reviewers 20 21 22class ExternalUpdaterReviewersTest(unittest.TestCase): 23 """Unit tests for external updater reviewers.""" 24 25 def setUp(self): 26 super().setUp() 27 # save constants in reviewers 28 self.saved_proj_reviewers = reviewers.PROJ_REVIEWERS 29 self.saved_rust_reviewers = reviewers.RUST_REVIEWERS 30 self.saved_rust_reviewer_list = reviewers.RUST_REVIEWER_LIST 31 self.saved_num_rust_projects = reviewers.NUM_RUST_PROJECTS 32 self.saved_rust_crate_owners = reviewers.RUST_CRATE_OWNERS 33 34 def tearDown(self): 35 super().tearDown() 36 # restore constants in reviewers 37 reviewers.PROJ_REVIEWERS = self.saved_proj_reviewers 38 reviewers.RUST_REVIEWERS = self.saved_rust_reviewers 39 reviewers.RUST_REVIEWER_LIST = self.saved_rust_reviewer_list 40 reviewers.NUM_RUST_PROJECTS = self.saved_num_rust_projects 41 reviewers.RUST_CRATE_OWNERS = self.saved_rust_crate_owners 42 43 # pylint: disable=no-self-use 44 def _collect_reviewers(self, num_runs, proj_path): 45 counters = {} 46 for _ in range(num_runs): 47 name = reviewers.find_reviewers(proj_path) 48 if name in counters: 49 counters[name] += 1 50 else: 51 counters[name] = 1 52 return counters 53 54 def test_reviewers_types(self): 55 """Check the types of PROJ_REVIEWERS and RUST_REVIEWERS.""" 56 # Check type of PROJ_REVIEWERS 57 self.assertIsInstance(reviewers.PROJ_REVIEWERS, Mapping) 58 for key, value in reviewers.PROJ_REVIEWERS.items(): 59 self.assertIsInstance(key, str) 60 # pylint: disable=isinstance-second-argument-not-valid-type 61 # https://github.com/PyCQA/pylint/issues/3507 62 if isinstance(value, (List, Set)): 63 for x in value: 64 self.assertIsInstance(x, str) 65 else: 66 self.assertIsInstance(value, str) 67 # Check element types of the reviewers list and map. 68 self.assertIsInstance(reviewers.RUST_REVIEWERS, Mapping) 69 for (name, quota) in reviewers.RUST_REVIEWERS.items(): 70 self.assertIsInstance(name, str) 71 self.assertIsInstance(quota, int) 72 73 def test_reviewers_constants(self): 74 """Check the constants associated to the reviewers.""" 75 # There should be enough people in the reviewers pool. 76 self.assertGreaterEqual(len(reviewers.RUST_REVIEWERS), 3) 77 # The NUM_RUST_PROJECTS should not be too small. 78 self.assertGreaterEqual(reviewers.NUM_RUST_PROJECTS, 50) 79 self.assertGreaterEqual(reviewers.NUM_RUST_PROJECTS, 80 len(reviewers.RUST_CRATE_OWNERS)) 81 # Assume no project reviewers and recreate RUST_REVIEWER_LIST 82 reviewers.PROJ_REVIEWERS = {} 83 reviewers.RUST_REVIEWER_LIST = reviewers.create_rust_reviewer_list() 84 sum_projects = sum(reviewers.RUST_REVIEWERS.values()) 85 self.assertEqual(sum_projects, len(reviewers.RUST_REVIEWER_LIST)) 86 self.assertGreaterEqual(sum_projects, reviewers.NUM_RUST_PROJECTS) 87 88 def test_reviewers_randomness(self): 89 """Check random selection of reviewers.""" 90 # This might fail when the random.choice function is extremely unfair. 91 # With N * 20 tries, each reviewer should be picked at least twice. 92 # Assume no project reviewers and recreate RUST_REVIEWER_LIST 93 reviewers.PROJ_REVIEWERS = {} 94 reviewers.RUST_REVIEWER_LIST = reviewers.create_rust_reviewer_list() 95 num_tries = len(reviewers.RUST_REVIEWERS) * 20 96 counters = self._collect_reviewers(num_tries, "rust/crates/libc") 97 self.assertEqual(len(counters), len(reviewers.RUST_REVIEWERS)) 98 for n in counters.values(): 99 self.assertGreaterEqual(n, 5) 100 self.assertEqual(sum(counters.values()), num_tries) 101 102 def test_project_reviewers(self): 103 """For specific projects, select only the specified reviewers.""" 104 reviewers.PROJ_REVIEWERS = { 105 "rust/crates/p1": "x@g.com", 106 "rust/crates/p_any": ["x@g.com", "y@g.com"], 107 "rust/crates/p_all": {"z@g", "x@g.com", "y@g.com"}, 108 } 109 counters = self._collect_reviewers(20, "external/rust/crates/p1") 110 self.assertEqual(len(counters), 1) 111 self.assertTrue(counters["r=x@g.com"], 20) 112 counters = self._collect_reviewers(20, "external/rust/crates/p_any") 113 self.assertEqual(len(counters), 2) 114 self.assertGreater(counters["r=x@g.com"], 2) 115 self.assertGreater(counters["r=y@g.com"], 2) 116 self.assertTrue(counters["r=x@g.com"] + counters["r=y@g.com"], 20) 117 counters = self._collect_reviewers(20, "external/rust/crates/p_all") 118 # {x, y, z} reviewers should be sorted 119 self.assertEqual(counters["r=x@g.com,r=y@g.com,r=z@g"], 20) 120 121 def test_weighted_reviewers(self): 122 """Test create_rust_reviewer_list.""" 123 reviewers.PROJ_REVIEWERS = { 124 "any_p1": "x@g", # 1 for x@g 125 "any_p2": {"xyz", "x@g"}, # 1 for x@g, xyz is not a rust reviewer 126 "any_p3": {"abc", "x@g"}, # 0.5 for "abc" and "x@g" 127 } 128 reviewers.RUST_REVIEWERS = { 129 "x@g": 5, # ceil(5 - 2.5) = 3 130 "abc": 2, # ceil(2 - 0.5) = 2 131 } 132 reviewer_list = reviewers.create_rust_reviewer_list() 133 self.assertEqual(reviewer_list, ["x@g", "x@g", "x@g", "abc", "abc"]) 134 # Error case: if nobody has project quota, reset everyone to 1. 135 reviewers.RUST_REVIEWERS = { 136 "x@g": 1, # ceil(1 - 2.5) = -1 137 "abc": 0, # ceil(0 - 0.5) = 0 138 } 139 reviewer_list = reviewers.create_rust_reviewer_list() 140 self.assertEqual(reviewer_list, ["x@g", "abc"]) # everyone got 1 141 142 143if __name__ == "__main__": 144 unittest.main(verbosity=2) 145