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