• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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