1# Copyright 2020 The Pigweed Authors 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); you may not 4# use this file except in compliance with the License. You may obtain a copy of 5# the License at 6# 7# https://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, WITHOUT 11# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12# License for the specific language governing permissions and limitations under 13# the License. 14# 15"""Unit tests for owners_checks.py.""" 16from pathlib import Path 17import tempfile 18from typing import Iterable, Sequence, Tuple 19import unittest 20from unittest import mock 21from pw_presubmit import owners_checks 22 23# ===== Test data ===== 24 25bad_duplicate = """\ 26# Should raise OwnersDuplicateError. 27set noparent 28 29file:/foo/OWNERZ 30file:../OWNERS 31file:../OWNERS 32test1@example.com 33#Test 2 comment 34test2@example.com 35""" 36 37bad_duplicate_user = """\ 38# Should raise OwnersDuplicateError. 39set noparent 40 41file:/foo/OWNERZ 42file:../OWNERS 43 44* 45test1@example.com 46#Test 2 comment 47test2@example.com 48 test1@example.com 49""" 50 51bad_duplicate_wildcard = """\ 52# Should raise OwnersDuplicateError. 53set noparent 54* 55file:/foo/OWNERZ 56file:../OWNERS 57test1@example.com 58#Test 2 comment 59test2@example.com 60* 61""" 62 63bad_email = """\ 64# Should raise OwnersInvalidLineError. 65set noparent 66* 67file:/foo/OWNERZ 68file:../OWNERS 69test1example.com 70#Test 2 comment 71test2@example.com 72* 73""" 74bad_grant_combo = """\ 75# Should raise OwnersUserGrantError. 76 77file:/foo/OWNERZ 78file:../OWNERS 79 80test1@example.com 81#Test noparent comment 82set noparent 83test2@example.com 84 85* 86""" 87 88bad_ordering1 = """\ 89# Tests formatter reorders groupings of lines into the right order. 90file:/foo/OWNERZ 91file:bar/OWNERZ 92 93test1@example.com 94#Test noparent comment 95set noparent 96test2@example.com 97""" 98 99bad_ordering1_fixed = """\ 100#Test noparent comment 101set noparent 102 103# Tests formatter reorders groupings of lines into the right order. 104file:/foo/OWNERZ 105file:bar/OWNERZ 106 107test1@example.com 108test2@example.com 109""" 110 111bad_prohibited1 = """\ 112# Should raise OwnersProhibitedError. 113set noparent 114 115file:/foo/OWNERZ 116file:../OWNERS 117 118test1@example.com 119#Test 2 comment 120test2@example.com 121 122include file1.txt 123 124per-file foo.txt=test3@example.com 125""" 126 127bad_moving_comments = """\ 128# Test comments move with the rule that follows them. 129test2@example.com 130test1@example.com 131 132# foo comment 133file:/foo/OWNERZ 134# .. comment 135file:../OWNERS 136 137set noparent 138""" 139bad_moving_comments_fixed = """\ 140set noparent 141 142# .. comment 143file:../OWNERS 144# foo comment 145file:/foo/OWNERZ 146 147test1@example.com 148# Test comments move with the rule that follows them. 149test2@example.com 150""" 151 152bad_whitespace = """\ 153 set noparent 154 155 156 157file:/foo/OWNERZ 158 159 file:../OWNERS 160 161test1@example.com 162#Test 2 comment 163 test2@example.com 164 165""" 166 167bad_whitespace_fixed = """\ 168set noparent 169 170file:../OWNERS 171file:/foo/OWNERZ 172 173test1@example.com 174#Test 2 comment 175test2@example.com 176""" 177 178no_dependencies = """\ 179# Test no imports are found when there are none. 180set noparent 181 182test1@example.com 183#Test 2 comment 184test2@example.com 185""" 186 187has_dependencies_file = """\ 188# Test if owners checks examine file: imports. 189set noparent 190 191file:foo_owners 192file:bar_owners 193 194test1@example.com 195#Test 2 comment 196test2@example.com 197""" 198 199has_dependencies_perfile = """\ 200# Test if owners checks examine per-file imports. 201set noparent 202 203test1@example.com 204#Test 2 comment 205test2@example.com 206 207per-file *.txt=file:foo_owners 208per-file *.md=example.google.com 209""" 210 211has_dependencies_include = """\ 212# Test if owners checks examine per-file imports. 213set noparent 214 215test1@example.com 216#Test 2 comment 217test2@example.com 218 219per-file *.txt=file:foo_owners 220per-file *.md=example.google.com 221""" 222 223dependencies_paths_relative = """\ 224set noparent 225 226include foo/bar/../include_owners 227 228file:foo/bar/../file_owners 229 230* 231 232per-file *.txt=file:foo/bar/../perfile_owners 233""" 234 235dependencies_paths_absolute = """\ 236set noparent 237 238include /test/include_owners 239 240file:/test/file_owners 241 242* 243 244per-file *.txt=file:/test/perfile_owners 245""" 246 247good1 = """\ 248# Checks should fine this formatted correctly 249set noparent 250 251include good1_include 252 253file:good1_file 254 255test1@example.com 256#Test 2 comment 257test2@example.com #{LAST_RESORT_SUGGESTION} 258# LAST LINE 259""" 260 261good1_include = """\ 262test1@example.comom 263""" 264 265good1_file = """\ 266# Checks should fine this formatted correctly. 267test1@example.com 268""" 269 270foo_owners = """\ 271test1@example.com 272""" 273 274bar_owners = """\ 275test1@example.com 276""" 277 278BAD_TEST_FILES = ( 279 ("bad_duplicate", owners_checks.OwnersDuplicateError), 280 ("bad_duplicate_user", owners_checks.OwnersDuplicateError), 281 ("bad_duplicate_wildcard", owners_checks.OwnersDuplicateError), 282 ("bad_email", owners_checks.OwnersInvalidLineError), 283 ("bad_grant_combo", owners_checks.OwnersUserGrantError), 284 ("bad_ordering1", owners_checks.OwnersStyleError), 285 ("bad_prohibited1", owners_checks.OwnersProhibitedError), 286) 287 288STYLING_CHECKS = ( 289 ("bad_moving_comments", "bad_moving_comments_fixed"), 290 ("bad_ordering1", "bad_ordering1_fixed"), 291 ("bad_whitespace", "bad_whitespace_fixed"), 292) 293 294DEPENDENCY_TEST_CASES: Iterable[Tuple[str, Iterable[str]]] = ( 295 ("no_dependencies", tuple()), 296 ("has_dependencies_file", ("foo_owners", "bar_owners")), 297 ("has_dependencies_perfile", ("foo_owners",)), 298 ("has_dependencies_include", ("foo_owners",)), 299) 300 301DEPENDENCY_PATH_TEST_CASES: Iterable[str] = ( 302 "dependencies_paths_relative", 303 "dependencies_paths_absolute", 304) 305 306GOOD_TEST_CASES = (("good1", "good1_include", "good1_file"),) 307 308 309# ===== Unit Tests ===== 310class TestOwnersChecks(unittest.TestCase): 311 """Unittest class for owners_checks.py.""" 312 313 maxDiff = 2000 314 315 @staticmethod 316 def _create_temp_files( 317 temp_dir: str, file_list: Sequence[Tuple[str, str]] 318 ) -> Sequence[Path]: 319 real_files = [] 320 temp_dir_path = Path(temp_dir) 321 for name, contents in file_list: 322 file_path = temp_dir_path / name 323 file_path.write_text(contents) 324 real_files.append(file_path) 325 return real_files 326 327 def test_bad_files(self): 328 # First test_file is the "primary" owners file followed by any needed 329 # "secondary" owners. 330 for test_file, expected_exception in BAD_TEST_FILES: 331 with self.subTest( 332 i=test_file 333 ), tempfile.TemporaryDirectory() as temp_dir, self.assertRaises( 334 expected_exception 335 ): 336 file_contents = globals()[test_file] 337 primary_file = self._create_temp_files( 338 temp_dir=temp_dir, file_list=((test_file, file_contents),) 339 )[0] 340 owners_file = owners_checks.OwnersFile(primary_file) 341 owners_file.look_for_owners_errors() 342 owners_file.check_style() 343 344 def test_good(self): 345 # First test_file is the "primary" owners file followed by any needed 346 # "secondary" owners. 347 for test_files in GOOD_TEST_CASES: 348 with self.subTest( 349 i=test_files[0] 350 ), tempfile.TemporaryDirectory() as temp_dir: 351 files = [ 352 (file_name, globals()[file_name]) 353 for file_name in test_files 354 ] 355 primary_file = self._create_temp_files( 356 temp_dir=temp_dir, file_list=files 357 )[0] 358 self.assertDictEqual( 359 {}, owners_checks.run_owners_checks(primary_file) 360 ) 361 362 def test_style_proposals(self): 363 for unstyled_file, styled_file in STYLING_CHECKS: 364 with self.subTest( 365 i=unstyled_file 366 ), tempfile.TemporaryDirectory() as temp_dir: 367 unstyled_contents = globals()[unstyled_file] 368 styled_contents = globals()[styled_file] 369 unstyled_real_file = self._create_temp_files( 370 temp_dir=temp_dir, 371 file_list=((unstyled_file, unstyled_contents),), 372 )[0] 373 owners_file = owners_checks.OwnersFile(unstyled_real_file) 374 formatted_content = "\n".join(owners_file.formatted_lines) 375 self.assertEqual(styled_contents, formatted_content) 376 377 def test_dependency_discovery(self): 378 for file_under_test, expected_deps in DEPENDENCY_TEST_CASES: 379 # During test make the test file directory the "git root" 380 with tempfile.TemporaryDirectory() as temp_dir: 381 temp_dir_path = Path(temp_dir).resolve() 382 with self.subTest(i=file_under_test), mock.patch( 383 "pw_presubmit.owners_checks.git_repo.root", 384 return_value=temp_dir_path, 385 ): 386 primary_file = (file_under_test, globals()[file_under_test]) 387 deps_files = tuple( 388 (dep, (globals()[dep])) for dep in expected_deps 389 ) 390 391 primary_file = self._create_temp_files( 392 temp_dir=temp_dir, file_list=(primary_file,) 393 )[0] 394 dep_files = self._create_temp_files( 395 temp_dir=temp_dir, file_list=deps_files 396 ) 397 398 owners_file = owners_checks.OwnersFile(primary_file) 399 400 # get_dependencies is expected to resolve() files 401 found_deps = owners_file.get_dependencies() 402 expected_deps_path = [path.resolve() for path in dep_files] 403 expected_deps_path.sort() 404 found_deps.sort() 405 self.assertListEqual(expected_deps_path, found_deps) 406 407 def test_dependency_path_creation(self): 408 """Confirm paths care constructed for absolute and relative paths.""" 409 for file_under_test in DEPENDENCY_PATH_TEST_CASES: 410 # During test make the test file directory the "git root" 411 with tempfile.TemporaryDirectory() as temp_dir: 412 temp_dir_path = Path(temp_dir).resolve() 413 with self.subTest(i=file_under_test), mock.patch( 414 "pw_presubmit.owners_checks.git_repo.root", 415 return_value=temp_dir_path, 416 ): 417 owners_file_path = ( 418 temp_dir_path / "owners" / file_under_test 419 ) 420 owners_file_path.parent.mkdir(parents=True) 421 owners_file_path.write_text(globals()[file_under_test]) 422 owners_file = owners_checks.OwnersFile(owners_file_path) 423 424 # get_dependencies is expected to resolve() files 425 found_deps = owners_file.get_dependencies() 426 427 if "absolute" in file_under_test: 428 # Absolute paths start with the git/project root 429 expected_prefix = temp_dir_path / "test" 430 else: 431 # Relative paths start with owners file dir 432 expected_prefix = owners_file_path.parent / "foo" 433 434 expected_deps_path = [ 435 (expected_prefix / filename).resolve() 436 for filename in ( 437 "include_owners", 438 "file_owners", 439 "perfile_owners", 440 ) 441 ] 442 expected_deps_path.sort() 443 found_deps.sort() 444 self.assertListEqual(expected_deps_path, found_deps) 445 446 447if __name__ == "__main__": 448 unittest.main() 449