Chih-Hung Hsieh | aa48555 | 2020-08-20 15:45:09 -0700 | [diff] [blame] | 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 | |
| 16 | from typing import List, Mapping, Set |
| 17 | import unittest |
| 18 | |
| 19 | import reviewers |
| 20 | |
| 21 | |
| 22 | class 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 | |
Thiébaud Weksteen | 4ac289b | 2020-09-28 15:23:29 +0200 | [diff] [blame] | 43 | # pylint: disable=no-self-use |
Chih-Hung Hsieh | aa48555 | 2020-08-20 15:45:09 -0700 | [diff] [blame] | 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): |
Thiébaud Weksteen | 4ac289b | 2020-09-28 15:23:29 +0200 | [diff] [blame] | 55 | """Check the types of PROJ_REVIEWERS and RUST_REVIEWERS.""" |
Chih-Hung Hsieh | aa48555 | 2020-08-20 15:45:09 -0700 | [diff] [blame] | 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) |
Thiébaud Weksteen | 4ac289b | 2020-09-28 15:23:29 +0200 | [diff] [blame] | 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) |
Chih-Hung Hsieh | aa48555 | 2020-08-20 15:45:09 -0700 | [diff] [blame] | 65 | else: |
| 66 | self.assertIsInstance(value, str) |
| 67 | # Check element types of the reviewers list and map. |
| 68 | self.assertIsInstance(reviewers.RUST_REVIEWERS, Mapping) |
Thiébaud Weksteen | 4ac289b | 2020-09-28 15:23:29 +0200 | [diff] [blame] | 69 | for (name, quota) in reviewers.RUST_REVIEWERS.items(): |
| 70 | self.assertIsInstance(name, str) |
| 71 | self.assertIsInstance(quota, int) |
Chih-Hung Hsieh | aa48555 | 2020-08-20 15:45:09 -0700 | [diff] [blame] | 72 | |
| 73 | def test_reviewers_constants(self): |
Thiébaud Weksteen | 4ac289b | 2020-09-28 15:23:29 +0200 | [diff] [blame] | 74 | """Check the constants associated to the reviewers.""" |
Chih-Hung Hsieh | aa48555 | 2020-08-20 15:45:09 -0700 | [diff] [blame] | 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)) |
Chih-Hung Hsieh | aa48555 | 2020-08-20 15:45:09 -0700 | [diff] [blame] | 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): |
Thiébaud Weksteen | 4ac289b | 2020-09-28 15:23:29 +0200 | [diff] [blame] | 89 | """Check random selection of reviewers.""" |
Chih-Hung Hsieh | aa48555 | 2020-08-20 15:45:09 -0700 | [diff] [blame] | 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)) |
Thiébaud Weksteen | 4ac289b | 2020-09-28 15:23:29 +0200 | [diff] [blame] | 98 | for n in counters.values(): |
Joel Galenson | df54065 | 2021-05-05 10:16:45 -0700 | [diff] [blame] | 99 | self.assertGreaterEqual(n, 5) |
Chih-Hung Hsieh | aa48555 | 2020-08-20 15:45:09 -0700 | [diff] [blame] | 100 | self.assertEqual(sum(counters.values()), num_tries) |
| 101 | |
| 102 | def test_project_reviewers(self): |
Thiébaud Weksteen | 4ac289b | 2020-09-28 15:23:29 +0200 | [diff] [blame] | 103 | """For specific projects, select only the specified reviewers.""" |
Chih-Hung Hsieh | aa48555 | 2020-08-20 15:45:09 -0700 | [diff] [blame] | 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): |
Thiébaud Weksteen | 4ac289b | 2020-09-28 15:23:29 +0200 | [diff] [blame] | 122 | """Test create_rust_reviewer_list.""" |
Chih-Hung Hsieh | aa48555 | 2020-08-20 15:45:09 -0700 | [diff] [blame] | 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 | |
| 143 | if __name__ == "__main__": |
| 144 | unittest.main(verbosity=2) |