449 lines
11 KiB
Python
449 lines
11 KiB
Python
# 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()
|