| # Copyright 2020 The Pigweed Authors |
| # |
| # Licensed under the Apache License, Version 2.0 (the "License"); you may not |
| # use this file except in compliance with the License. You may obtain a copy of |
| # the License at |
| # |
| # https://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
| # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
| # License for the specific language governing permissions and limitations under |
| # the License. |
| # |
| """Unit tests for owners_checks.py.""" |
| from pathlib import Path |
| import tempfile |
| from typing import Iterable, Sequence, Tuple |
| import unittest |
| from unittest import mock |
| from pw_presubmit import owners_checks |
| |
| # ===== Test data ===== |
| |
| bad_duplicate = """\ |
| # Should raise OwnersDuplicateError. |
| set noparent |
| |
| file:/foo/OWNERZ |
| file:../OWNERS |
| file:../OWNERS |
| test1@example.com |
| #Test 2 comment |
| test2@example.com |
| """ |
| |
| bad_duplicate_user = """\ |
| # Should raise OwnersDuplicateError. |
| set noparent |
| |
| file:/foo/OWNERZ |
| file:../OWNERS |
| |
| * |
| test1@example.com |
| #Test 2 comment |
| test2@example.com |
| test1@example.com |
| """ |
| |
| bad_duplicate_wildcard = """\ |
| # Should raise OwnersDuplicateError. |
| set noparent |
| * |
| file:/foo/OWNERZ |
| file:../OWNERS |
| test1@example.com |
| #Test 2 comment |
| test2@example.com |
| * |
| """ |
| |
| bad_email = """\ |
| # Should raise OwnersInvalidLineError. |
| set noparent |
| * |
| file:/foo/OWNERZ |
| file:../OWNERS |
| test1example.com |
| #Test 2 comment |
| test2@example.com |
| * |
| """ |
| bad_grant_combo = """\ |
| # Should raise OwnersUserGrantError. |
| |
| file:/foo/OWNERZ |
| file:../OWNERS |
| |
| test1@example.com |
| #Test noparent comment |
| set noparent |
| test2@example.com |
| |
| * |
| """ |
| |
| bad_ordering1 = """\ |
| # Tests formatter reorders groupings of lines into the right order. |
| file:/foo/OWNERZ |
| file:bar/OWNERZ |
| |
| test1@example.com |
| #Test noparent comment |
| set noparent |
| test2@example.com |
| """ |
| |
| bad_ordering1_fixed = """\ |
| #Test noparent comment |
| set noparent |
| |
| # Tests formatter reorders groupings of lines into the right order. |
| file:/foo/OWNERZ |
| file:bar/OWNERZ |
| |
| test1@example.com |
| test2@example.com |
| """ |
| |
| bad_prohibited1 = """\ |
| # Should raise OwnersProhibitedError. |
| set noparent |
| |
| file:/foo/OWNERZ |
| file:../OWNERS |
| |
| test1@example.com |
| #Test 2 comment |
| test2@example.com |
| |
| include file1.txt |
| |
| per-file foo.txt=test3@example.com |
| """ |
| |
| bad_moving_comments = """\ |
| # Test comments move with the rule that follows them. |
| test2@example.com |
| test1@example.com |
| |
| # foo comment |
| file:/foo/OWNERZ |
| # .. comment |
| file:../OWNERS |
| |
| set noparent |
| """ |
| bad_moving_comments_fixed = """\ |
| set noparent |
| |
| # .. comment |
| file:../OWNERS |
| # foo comment |
| file:/foo/OWNERZ |
| |
| test1@example.com |
| # Test comments move with the rule that follows them. |
| test2@example.com |
| """ |
| |
| bad_whitespace = """\ |
| set noparent |
| |
| |
| |
| file:/foo/OWNERZ |
| |
| file:../OWNERS |
| |
| test1@example.com |
| #Test 2 comment |
| test2@example.com |
| |
| """ |
| |
| bad_whitespace_fixed = """\ |
| set noparent |
| |
| file:../OWNERS |
| file:/foo/OWNERZ |
| |
| test1@example.com |
| #Test 2 comment |
| test2@example.com |
| """ |
| |
| no_dependencies = """\ |
| # Test no imports are found when there are none. |
| set noparent |
| |
| test1@example.com |
| #Test 2 comment |
| test2@example.com |
| """ |
| |
| has_dependencies_file = """\ |
| # Test if owners checks examine file: imports. |
| set noparent |
| |
| file:foo_owners |
| file:bar_owners |
| |
| test1@example.com |
| #Test 2 comment |
| test2@example.com |
| """ |
| |
| has_dependencies_perfile = """\ |
| # Test if owners checks examine per-file imports. |
| set noparent |
| |
| test1@example.com |
| #Test 2 comment |
| test2@example.com |
| |
| per-file *.txt=file:foo_owners |
| per-file *.md=example.google.com |
| """ |
| |
| has_dependencies_include = """\ |
| # Test if owners checks examine per-file imports. |
| set noparent |
| |
| test1@example.com |
| #Test 2 comment |
| test2@example.com |
| |
| per-file *.txt=file:foo_owners |
| per-file *.md=example.google.com |
| """ |
| |
| dependencies_paths_relative = """\ |
| set noparent |
| |
| include foo/bar/../include_owners |
| |
| file:foo/bar/../file_owners |
| |
| * |
| |
| per-file *.txt=file:foo/bar/../perfile_owners |
| """ |
| |
| dependencies_paths_absolute = """\ |
| set noparent |
| |
| include /test/include_owners |
| |
| file:/test/file_owners |
| |
| * |
| |
| per-file *.txt=file:/test/perfile_owners |
| """ |
| |
| good1 = """\ |
| # Checks should fine this formatted correctly |
| set noparent |
| |
| include good1_include |
| |
| file:good1_file |
| |
| test1@example.com |
| #Test 2 comment |
| test2@example.com #{LAST_RESORT_SUGGESTION} |
| # LAST LINE |
| """ |
| |
| good1_include = """\ |
| test1@example.comom |
| """ |
| |
| good1_file = """\ |
| # Checks should fine this formatted correctly. |
| test1@example.com |
| """ |
| |
| foo_owners = """\ |
| test1@example.com |
| """ |
| |
| bar_owners = """\ |
| test1@example.com |
| """ |
| |
| BAD_TEST_FILES = ( |
| ("bad_duplicate", owners_checks.OwnersDuplicateError), |
| ("bad_duplicate_user", owners_checks.OwnersDuplicateError), |
| ("bad_duplicate_wildcard", owners_checks.OwnersDuplicateError), |
| ("bad_email", owners_checks.OwnersInvalidLineError), |
| ("bad_grant_combo", owners_checks.OwnersUserGrantError), |
| ("bad_ordering1", owners_checks.OwnersStyleError), |
| ("bad_prohibited1", owners_checks.OwnersProhibitedError), |
| ) |
| |
| STYLING_CHECKS = ( |
| ("bad_moving_comments", "bad_moving_comments_fixed"), |
| ("bad_ordering1", "bad_ordering1_fixed"), |
| ("bad_whitespace", "bad_whitespace_fixed"), |
| ) |
| |
| DEPENDENCY_TEST_CASES: Iterable[Tuple[str, Iterable[str]]] = ( |
| ("no_dependencies", tuple()), |
| ("has_dependencies_file", ("foo_owners", "bar_owners")), |
| ("has_dependencies_perfile", ("foo_owners",)), |
| ("has_dependencies_include", ("foo_owners",)), |
| ) |
| |
| DEPENDENCY_PATH_TEST_CASES: Iterable[str] = ( |
| "dependencies_paths_relative", |
| "dependencies_paths_absolute", |
| ) |
| |
| GOOD_TEST_CASES = (("good1", "good1_include", "good1_file"),) |
| |
| |
| # ===== Unit Tests ===== |
| class TestOwnersChecks(unittest.TestCase): |
| """Unittest class for owners_checks.py.""" |
| |
| maxDiff = 2000 |
| |
| @staticmethod |
| def _create_temp_files( |
| temp_dir: str, file_list: Sequence[Tuple[str, str]] |
| ) -> Sequence[Path]: |
| real_files = [] |
| temp_dir_path = Path(temp_dir) |
| for name, contents in file_list: |
| file_path = temp_dir_path / name |
| file_path.write_text(contents) |
| real_files.append(file_path) |
| return real_files |
| |
| def test_bad_files(self): |
| # First test_file is the "primary" owners file followed by any needed |
| # "secondary" owners. |
| for test_file, expected_exception in BAD_TEST_FILES: |
| with self.subTest( |
| i=test_file |
| ), tempfile.TemporaryDirectory() as temp_dir, self.assertRaises( |
| expected_exception |
| ): |
| file_contents = globals()[test_file] |
| primary_file = self._create_temp_files( |
| temp_dir=temp_dir, file_list=((test_file, file_contents),) |
| )[0] |
| owners_file = owners_checks.OwnersFile(primary_file) |
| owners_file.look_for_owners_errors() |
| owners_file.check_style() |
| |
| def test_good(self): |
| # First test_file is the "primary" owners file followed by any needed |
| # "secondary" owners. |
| for test_files in GOOD_TEST_CASES: |
| with self.subTest( |
| i=test_files[0] |
| ), tempfile.TemporaryDirectory() as temp_dir: |
| files = [ |
| (file_name, globals()[file_name]) |
| for file_name in test_files |
| ] |
| primary_file = self._create_temp_files( |
| temp_dir=temp_dir, file_list=files |
| )[0] |
| self.assertDictEqual( |
| {}, owners_checks.run_owners_checks(primary_file) |
| ) |
| |
| def test_style_proposals(self): |
| for unstyled_file, styled_file in STYLING_CHECKS: |
| with self.subTest( |
| i=unstyled_file |
| ), tempfile.TemporaryDirectory() as temp_dir: |
| unstyled_contents = globals()[unstyled_file] |
| styled_contents = globals()[styled_file] |
| unstyled_real_file = self._create_temp_files( |
| temp_dir=temp_dir, |
| file_list=((unstyled_file, unstyled_contents),), |
| )[0] |
| owners_file = owners_checks.OwnersFile(unstyled_real_file) |
| formatted_content = "\n".join(owners_file.formatted_lines) |
| self.assertEqual(styled_contents, formatted_content) |
| |
| def test_dependency_discovery(self): |
| for file_under_test, expected_deps in DEPENDENCY_TEST_CASES: |
| # During test make the test file directory the "git root" |
| with tempfile.TemporaryDirectory() as temp_dir: |
| temp_dir_path = Path(temp_dir).resolve() |
| with self.subTest(i=file_under_test), mock.patch( |
| "pw_presubmit.owners_checks.git_repo.root", |
| return_value=temp_dir_path, |
| ): |
| primary_file = (file_under_test, globals()[file_under_test]) |
| deps_files = tuple( |
| (dep, (globals()[dep])) for dep in expected_deps |
| ) |
| |
| primary_file = self._create_temp_files( |
| temp_dir=temp_dir, file_list=(primary_file,) |
| )[0] |
| dep_files = self._create_temp_files( |
| temp_dir=temp_dir, file_list=deps_files |
| ) |
| |
| owners_file = owners_checks.OwnersFile(primary_file) |
| |
| # get_dependencies is expected to resolve() files |
| found_deps = owners_file.get_dependencies() |
| expected_deps_path = [path.resolve() for path in dep_files] |
| expected_deps_path.sort() |
| found_deps.sort() |
| self.assertListEqual(expected_deps_path, found_deps) |
| |
| def test_dependency_path_creation(self): |
| """Confirm paths care constructed for absolute and relative paths.""" |
| for file_under_test in DEPENDENCY_PATH_TEST_CASES: |
| # During test make the test file directory the "git root" |
| with tempfile.TemporaryDirectory() as temp_dir: |
| temp_dir_path = Path(temp_dir).resolve() |
| with self.subTest(i=file_under_test), mock.patch( |
| "pw_presubmit.owners_checks.git_repo.root", |
| return_value=temp_dir_path, |
| ): |
| owners_file_path = ( |
| temp_dir_path / "owners" / file_under_test |
| ) |
| owners_file_path.parent.mkdir(parents=True) |
| owners_file_path.write_text(globals()[file_under_test]) |
| owners_file = owners_checks.OwnersFile(owners_file_path) |
| |
| # get_dependencies is expected to resolve() files |
| found_deps = owners_file.get_dependencies() |
| |
| if "absolute" in file_under_test: |
| # Absolute paths start with the git/project root |
| expected_prefix = temp_dir_path / "test" |
| else: |
| # Relative paths start with owners file dir |
| expected_prefix = owners_file_path.parent / "foo" |
| |
| expected_deps_path = [ |
| (expected_prefix / filename).resolve() |
| for filename in ( |
| "include_owners", |
| "file_owners", |
| "perfile_owners", |
| ) |
| ] |
| expected_deps_path.sort() |
| found_deps.sort() |
| self.assertListEqual(expected_deps_path, found_deps) |
| |
| |
| if __name__ == "__main__": |
| unittest.main() |