| # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 |
| # For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt |
| |
| """Tests for files.py""" |
| |
| from __future__ import annotations |
| |
| import itertools |
| import os |
| import os.path |
| import re |
| |
| from typing import Any, Protocol |
| from collections.abc import Iterable |
| from unittest import mock |
| |
| import pytest |
| |
| from coverage import env, files |
| from coverage.exceptions import ConfigError |
| from coverage.files import ( |
| GlobMatcher, |
| ModuleMatcher, |
| PathAliases, |
| TreeMatcher, |
| abs_file, |
| actual_path, |
| find_python_files, |
| flat_rootname, |
| globs_to_regex, |
| ) |
| |
| from tests.coveragetest import CoverageTest |
| from tests.helpers import os_sep |
| |
| |
| class FilesTest(CoverageTest): |
| """Tests of coverage.files.""" |
| |
| def abs_path(self, p: str) -> str: |
| """Return the absolute path for `p`.""" |
| return os.path.join(abs_file(os.getcwd()), os.path.normpath(p)) |
| |
| def test_simple(self) -> None: |
| self.make_file("hello.py") |
| files.set_relative_directory() |
| assert files.relative_filename("hello.py") == "hello.py" |
| a = self.abs_path("hello.py") |
| assert a != "hello.py" |
| assert files.relative_filename(a) == "hello.py" |
| |
| def test_peer_directories(self) -> None: |
| self.make_file("sub/proj1/file1.py") |
| self.make_file("sub/proj2/file2.py") |
| a1 = self.abs_path("sub/proj1/file1.py") |
| a2 = self.abs_path("sub/proj2/file2.py") |
| d = os.path.normpath("sub/proj1") |
| os.chdir(d) |
| files.set_relative_directory() |
| assert files.relative_filename(a1) == "file1.py" |
| assert files.relative_filename(a2) == a2 |
| |
| def test_filepath_contains_absolute_prefix_twice(self) -> None: |
| # https://github.com/nedbat/coveragepy/issues/194 |
| # Build a path that has two pieces matching the absolute path prefix. |
| # Technically, this test doesn't do that on Windows, but drive |
| # letters make that impractical to achieve. |
| files.set_relative_directory() |
| d = abs_file(os.curdir) |
| trick = os.path.splitdrive(d)[1].lstrip(os.path.sep) |
| rel = os.path.join("sub", trick, "file1.py") |
| assert files.relative_filename(abs_file(rel)) == rel |
| |
| def test_canonical_filename_ensure_cache_hit(self) -> None: |
| self.make_file("sub/proj1/file1.py") |
| d = actual_path(self.abs_path("sub/proj1")) |
| os.chdir(d) |
| files.set_relative_directory() |
| canonical_path = files.canonical_filename("sub/proj1/file1.py") |
| assert canonical_path == self.abs_path("file1.py") |
| # After the filename has been converted, it should be in the cache. |
| assert "sub/proj1/file1.py" in files.CANONICAL_FILENAME_CACHE |
| assert files.canonical_filename("sub/proj1/file1.py") == self.abs_path("file1.py") |
| |
| @pytest.mark.parametrize( |
| "curdir, sep", |
| [ |
| ("/", "/"), |
| ("X:\\", "\\"), |
| ], |
| ) |
| def test_relative_dir_for_root(self, curdir: str, sep: str) -> None: |
| with mock.patch.object(files.os, "curdir", new=curdir): # type: ignore[attr-defined] |
| with mock.patch.object(files.os, "sep", new=sep): # type: ignore[attr-defined] |
| with mock.patch("coverage.files.os.path.normcase", return_value=curdir): |
| files.set_relative_directory() |
| assert files.relative_directory() == curdir |
| |
| @pytest.mark.parametrize( |
| "to_make, to_check, answer", |
| [ |
| ("a/b/c/foo.py", "a/b/c/foo.py", True), |
| ("a/b/c/foo.py", "a/b/c/bar.py", False), |
| ("src/files.zip", "src/files.zip/foo.py", True), |
| ("src/files.whl", "src/files.whl/foo.py", True), |
| ("src/files.egg", "src/files.egg/foo.py", True), |
| ("src/files.pex", "src/files.pex/foo.py", True), |
| ("src/files.zip", "src/morefiles.zip/foo.py", False), |
| ("src/files.pex", "src/files.pex/zipfiles/files.zip/foo.py", True), |
| ], |
| ) |
| def test_source_exists(self, to_make: str, to_check: str, answer: bool) -> None: |
| # source_exists won't look inside the zipfile, so it's fine to make |
| # an empty file with the zipfile name. |
| self.make_file(to_make, "") |
| assert files.source_exists(to_check) == answer |
| |
| |
| @pytest.mark.parametrize( |
| "original, flat", |
| [ |
| ("abc.py", "abc_py"), |
| ("hellothere", "hellothere"), |
| ("a/b/c.py", "z_86bbcbe134d28fd2_c_py"), |
| ("a/b/defghi.py", "z_86bbcbe134d28fd2_defghi_py"), |
| ("/a/b/c.py", "z_bb25e0ada04227c6_c_py"), |
| ("/a/b/defghi.py", "z_bb25e0ada04227c6_defghi_py"), |
| (r"c:\foo\bar.html", "z_e7c107482373f299_bar_html"), |
| (r"d:\foo\bar.html", "z_584a05dcebc67b46_bar_html"), |
| ("Montréal/☺/conf.py", "z_c840497a2c647ce0_conf_py"), |
| ( # original: |
| r"c:\lorem\ipsum\quia\dolor\sit\amet\consectetur\adipisci\velit\sed" |
| + r"\quia\non\numquam\eius\modi\tempora\incidunt\ut\labore\et\dolore" |
| + r"\magnam\aliquam\quaerat\voluptatem\ut\enim\ad\minima\veniam\quis" |
| + r"\nostrum\exercitationem\ullam\corporis\suscipit\laboriosam" |
| + r"\Montréal\☺\my_program.py", |
| # flat: |
| "z_e597dfacb73a23d5_my_program_py", |
| ), |
| ], |
| ) |
| def test_flat_rootname(original: str, flat: str) -> None: |
| assert flat_rootname(original) == flat |
| |
| |
| def globs_to_regex_params( |
| patterns: Iterable[str], |
| case_insensitive: bool = False, |
| partial: bool = False, |
| matches: Iterable[str] = (), |
| nomatches: Iterable[str] = (), |
| ) -> Iterable[Any]: |
| """Generate parameters for `test_globs_to_regex`. |
| |
| `patterns`, `case_insensitive`, and `partial` are arguments for |
| `globs_to_regex`. `matches` is a list of strings that should match, and |
| `nomatches` is a list of strings that should not match. |
| |
| Everything is yielded so that `test_globs_to_regex` can call |
| `globs_to_regex` once and check one result. |
| |
| """ |
| pat_id = "|".join(patterns) |
| for text in matches: |
| yield pytest.param( |
| patterns, |
| case_insensitive, |
| partial, |
| text, |
| True, |
| id=f"{pat_id}:ci{case_insensitive}:par{partial}:{text}:match", |
| ) |
| for text in nomatches: |
| yield pytest.param( |
| patterns, |
| case_insensitive, |
| partial, |
| text, |
| False, |
| id=f"{pat_id}:ci{case_insensitive}:par{partial}:{text}:nomatch", |
| ) |
| |
| |
| @pytest.mark.parametrize( |
| "patterns, case_insensitive, partial, text, result", |
| list( |
| itertools.chain.from_iterable( |
| [ |
| globs_to_regex_params( |
| ["abc", "xyz"], |
| matches=["abc", "xyz", "sub/mod/abc"], |
| nomatches=[ |
| "ABC", |
| "xYz", |
| "abcx", |
| "xabc", |
| "axyz", |
| "xyza", |
| "sub/mod/abcd", |
| "sub/abc/more", |
| ], |
| ), |
| globs_to_regex_params( |
| ["abc", "xyz"], |
| case_insensitive=True, |
| matches=["abc", "xyz", "Abc", "XYZ", "AbC"], |
| nomatches=["abcx", "xabc", "axyz", "xyza"], |
| ), |
| globs_to_regex_params( |
| ["a*c", "x*z"], |
| matches=["abc", "xyz", "xYz", "azc", "xaz", "axyzc"], |
| nomatches=["ABC", "abcx", "xabc", "axyz", "xyza", "a/c"], |
| ), |
| globs_to_regex_params( |
| ["a?c", "x?z"], |
| matches=["abc", "xyz", "xYz", "azc", "xaz"], |
| nomatches=["ABC", "abcx", "xabc", "axyz", "xyza", "a/c"], |
| ), |
| globs_to_regex_params( |
| ["a??d"], |
| matches=["abcd", "azcd", "a12d"], |
| nomatches=["ABCD", "abcx", "axyz", "abcde"], |
| ), |
| globs_to_regex_params( |
| ["abc/hi.py"], |
| case_insensitive=True, |
| matches=["abc/hi.py", "ABC/hi.py", r"ABC\hi.py"], |
| nomatches=["abc_hi.py", "abc/hi.pyc"], |
| ), |
| globs_to_regex_params( |
| [r"abc\hi.py"], |
| case_insensitive=True, |
| matches=[r"abc\hi.py", r"ABC\hi.py", "abc/hi.py", "ABC/hi.py"], |
| nomatches=["abc_hi.py", "abc/hi.pyc"], |
| ), |
| globs_to_regex_params( |
| ["abc/*/hi.py"], |
| case_insensitive=True, |
| matches=["abc/foo/hi.py", r"ABC\foo/hi.py"], |
| nomatches=[ |
| "abc/hi.py", |
| "abc/hi.pyc", |
| "ABC/foo/bar/hi.py", |
| r"ABC\foo/bar/hi.py", |
| ], |
| ), |
| globs_to_regex_params( |
| ["abc/**/hi.py"], |
| case_insensitive=True, |
| matches=[ |
| "abc/foo/hi.py", |
| r"ABC\foo/hi.py", |
| "abc/hi.py", |
| "ABC/foo/bar/hi.py", |
| r"ABC\foo/bar/hi.py", |
| ], |
| nomatches=["abc/hi.pyc"], |
| ), |
| globs_to_regex_params( |
| ["abc/[a-f]*/hi.py"], |
| case_insensitive=True, |
| matches=["abc/foo/hi.py", r"ABC\boo/hi.py"], |
| nomatches=[ |
| "abc/zoo/hi.py", |
| "abc/hi.py", |
| "abc/hi.pyc", |
| "abc/foo/bar/hi.py", |
| r"abc\foo/bar/hi.py", |
| ], |
| ), |
| globs_to_regex_params( |
| ["abc/[a-f]/hi.py"], |
| case_insensitive=True, |
| matches=["abc/f/hi.py", r"ABC\b/hi.py"], |
| nomatches=[ |
| "abc/foo/hi.py", |
| "abc/zoo/hi.py", |
| "abc/hi.py", |
| "abc/hi.pyc", |
| "abc/foo/bar/hi.py", |
| r"abc\foo/bar/hi.py", |
| ], |
| ), |
| globs_to_regex_params( |
| ["abc/"], |
| case_insensitive=True, |
| partial=True, |
| matches=["abc/foo/hi.py", "ABC/foo/bar/hi.py", r"ABC\foo/bar/hi.py"], |
| nomatches=["abcd/foo.py", "xabc/hi.py"], |
| ), |
| globs_to_regex_params( |
| ["*/foo"], |
| case_insensitive=False, |
| partial=True, |
| matches=["abc/foo/hi.py", "foo/hi.py", "abc/def/foo/hi.py"], |
| nomatches=["abc/xfoo/hi.py"], |
| ), |
| globs_to_regex_params( |
| ["*c/foo"], |
| case_insensitive=False, |
| partial=True, |
| matches=["abc/foo/hi.py"], |
| nomatches=["abc/xfoo/hi.py", "foo/hi.py", "def/abc/foo/hi.py"], |
| ), |
| globs_to_regex_params( |
| ["foo/x*"], |
| case_insensitive=False, |
| partial=True, |
| matches=["foo/x", "foo/xhi.py", "foo/x/hi.py"], |
| nomatches=[], |
| ), |
| globs_to_regex_params( |
| ["foo/x*"], |
| case_insensitive=False, |
| partial=False, |
| matches=["foo/x", "foo/xhi.py"], |
| nomatches=["foo/x/hi.py"], |
| ), |
| globs_to_regex_params( |
| ["**/foo"], |
| matches=["foo", "hello/foo", "hi/there/foo"], |
| nomatches=["foob", "hello/foob", "hello/Foo"], |
| ), |
| globs_to_regex_params( |
| ["a+b/foo*", "x{y}z/foo*"], |
| matches=["a+b/foo", "a+b/foobar", "x{y}z/foobar"], |
| nomatches=["aab/foo", "ab/foo", "xyz/foo"], |
| ), |
| ] |
| ) |
| ), |
| ) |
| def test_globs_to_regex( |
| patterns: Iterable[str], |
| case_insensitive: bool, |
| partial: bool, |
| text: str, |
| result: bool, |
| ) -> None: |
| regex = globs_to_regex(patterns, case_insensitive=case_insensitive, partial=partial) |
| assert bool(regex.match(text)) == result |
| |
| |
| @pytest.mark.parametrize( |
| "pattern, bad_word", |
| [ |
| ("***/foo.py", "***"), |
| ("bar/***/foo.py", "***"), |
| ("*****/foo.py", "*****"), |
| ("Hello]there", "]"), |
| ("Hello[there", "["), |
| ("x/a**/b.py", "a**"), |
| ("x/abcd**/b.py", "abcd**"), |
| ("x/**a/b.py", "**a"), |
| ("x/**/**/b.py", "**/**"), |
| ], |
| ) |
| def test_invalid_globs(pattern: str, bad_word: str) -> None: |
| msg = f"File pattern can't include {bad_word!r}" |
| with pytest.raises(ConfigError, match=re.escape(msg)): |
| globs_to_regex([pattern]) |
| |
| |
| class TMatcher(Protocol): |
| """The shape all Matchers have.""" |
| |
| def match(self, s: str) -> bool: |
| """Does this string match?""" |
| |
| |
| class MatcherTest(CoverageTest): |
| """Tests of file matchers.""" |
| |
| def setUp(self) -> None: |
| super().setUp() |
| files.set_relative_directory() |
| |
| def assertMatches(self, matcher: TMatcher, filepath: str, matches: bool) -> None: |
| """The `matcher` should agree with `matches` about `filepath`.""" |
| canonical = files.canonical_filename(filepath) |
| msg = f"File {filepath} should have matched as {matches}" |
| assert matches == matcher.match(canonical), msg |
| |
| def test_tree_matcher(self) -> None: |
| case_folding = env.WINDOWS |
| matches_to_try = [ |
| (self.make_file("sub/file1.py"), True), |
| (self.make_file("sub/file2.c"), True), |
| (self.make_file("sub2/file3.h"), False), |
| (self.make_file("sub3/file4.py"), True), |
| (self.make_file("sub3/file5.c"), False), |
| (self.make_file("sub4/File5.py"), case_folding), |
| (self.make_file("sub5/file6.py"), case_folding), |
| ] |
| trees = [ |
| files.canonical_filename("sub"), |
| files.canonical_filename("sub3/file4.py"), |
| files.canonical_filename("sub4/file5.py"), |
| files.canonical_filename("SUB5/file6.py"), |
| ] |
| tm = TreeMatcher(trees) |
| assert tm.info() == sorted(trees) |
| for filepath, matches in matches_to_try: |
| self.assertMatches(tm, filepath, matches) |
| |
| def test_module_matcher(self) -> None: |
| matches_to_try = [ |
| ("test", True), |
| ("trash", False), |
| ("testing", False), |
| ("test.x", True), |
| ("test.x.y.z", True), |
| ("py", False), |
| ("py.t", False), |
| ("py.test", True), |
| ("py.testing", False), |
| ("py.test.buz", True), |
| ("py.test.buz.baz", True), |
| ("__main__", False), |
| ("mymain", True), |
| ("yourmain", False), |
| ] |
| modules = ["test", "py.test", "mymain"] |
| mm = ModuleMatcher(modules) |
| assert mm.info() == modules |
| for modulename, matches in matches_to_try: |
| assert mm.match(modulename) == matches, modulename |
| |
| def test_glob_matcher(self) -> None: |
| matches_to_try = [ |
| (self.make_file("sub/file1.py"), True), |
| (self.make_file("sub/file2.c"), False), |
| (self.make_file("sub2/file3.h"), True), |
| (self.make_file("sub2/sub/file3.h"), True), |
| (self.make_file("sub3/file4.py"), True), |
| (self.make_file("sub3/file5.c"), False), |
| ] |
| fnm = GlobMatcher(["*.py", "*/sub2/*"]) |
| assert fnm.info() == ["*.py", "*/sub2/*"] |
| for filepath, matches in matches_to_try: |
| self.assertMatches(fnm, filepath, matches) |
| |
| def test_glob_matcher_overload(self) -> None: |
| fnm = GlobMatcher(["*x%03d*.txt" % i for i in range(500)]) |
| self.assertMatches(fnm, "x007foo.txt", True) |
| self.assertMatches(fnm, "x123foo.txt", True) |
| self.assertMatches(fnm, "x798bar.txt", False) |
| self.assertMatches(fnm, "x499.txt", True) |
| self.assertMatches(fnm, "x500.txt", False) |
| |
| def test_glob_windows_paths(self) -> None: |
| # We should be able to match Windows paths even if we are running on |
| # a non-Windows OS. |
| fnm = GlobMatcher(["*/foo.py"]) |
| self.assertMatches(fnm, r"dir\foo.py", True) |
| fnm = GlobMatcher([r"*\foo.py"]) |
| self.assertMatches(fnm, r"dir\foo.py", True) |
| |
| |
| @pytest.fixture(params=[False, True], name="rel_yn") |
| def relative_setting(request: pytest.FixtureRequest) -> bool: |
| """Parameterized fixture to choose whether PathAliases is relative or not.""" |
| return request.param # type: ignore[no-any-return] |
| |
| |
| class PathAliasesTest(CoverageTest): |
| """Tests for coverage/files.py:PathAliases""" |
| |
| run_in_temp_dir = False |
| |
| def assert_mapped(self, aliases: PathAliases, inp: str, out: str) -> None: |
| """Assert that `inp` mapped through `aliases` produces `out`. |
| |
| If the aliases are not relative, then `out` is canonicalized first, |
| since aliases produce canonicalized paths by default. |
| |
| """ |
| mapped = aliases.map(inp, exists=lambda p: True) |
| if aliases.relative: |
| expected = out |
| else: |
| expected = files.canonical_filename(out) |
| assert mapped == expected |
| |
| def assert_unchanged(self, aliases: PathAliases, inp: str, exists: bool = True) -> None: |
| """Assert that `inp` mapped through `aliases` is unchanged.""" |
| assert aliases.map(inp, exists=lambda p: exists) == inp |
| |
| def test_noop(self, rel_yn: bool) -> None: |
| aliases = PathAliases(relative=rel_yn) |
| self.assert_unchanged(aliases, "/ned/home/a.py") |
| |
| def test_nomatch(self, rel_yn: bool) -> None: |
| aliases = PathAliases(relative=rel_yn) |
| aliases.add("/home/*/src", "./mysrc") |
| self.assert_unchanged(aliases, "/home/foo/a.py") |
| |
| def test_wildcard(self, rel_yn: bool) -> None: |
| aliases = PathAliases(relative=rel_yn) |
| aliases.add("/ned/home/*/src", "./mysrc") |
| self.assert_mapped(aliases, "/ned/home/foo/src/a.py", "./mysrc/a.py") |
| |
| aliases = PathAliases(relative=rel_yn) |
| aliases.add("/ned/home/*/src/", "./mysrc") |
| self.assert_mapped(aliases, "/ned/home/foo/src/a.py", "./mysrc/a.py") |
| |
| def test_no_accidental_match(self, rel_yn: bool) -> None: |
| aliases = PathAliases(relative=rel_yn) |
| aliases.add("/home/*/src", "./mysrc") |
| self.assert_unchanged(aliases, "/home/foo/srcetc") |
| |
| def test_no_map_if_not_exist(self, rel_yn: bool) -> None: |
| aliases = PathAliases(relative=rel_yn) |
| aliases.add("/ned/home/*/src", "./mysrc") |
| self.assert_unchanged(aliases, "/ned/home/foo/src/a.py", exists=False) |
| self.assert_unchanged(aliases, "foo/src/a.py", exists=False) |
| |
| def test_no_dotslash(self, rel_yn: bool) -> None: |
| # The result shouldn't start with "./" if the map result didn't. |
| aliases = PathAliases(relative=rel_yn) |
| aliases.add("*/project", ".") |
| self.assert_mapped(aliases, "/ned/home/project/src/a.py", os_sep("src/a.py")) |
| |
| def test_relative_pattern(self) -> None: |
| aliases = PathAliases(relative=True) |
| aliases.add(".tox/*/site-packages", "src") |
| self.assert_mapped( |
| aliases, |
| ".tox/py314/site-packages/proj/a.py", |
| os_sep("src/proj/a.py"), |
| ) |
| |
| def test_multiple_patterns(self, rel_yn: bool) -> None: |
| # also test the debugfn... |
| msgs: list[str] = [] |
| aliases = PathAliases(debugfn=msgs.append, relative=rel_yn) |
| aliases.add("/home/*/src", "./mysrc") |
| aliases.add("/lib/*/libsrc", "./mylib") |
| self.assert_mapped(aliases, "/home/foo/src/a.py", "./mysrc/a.py") |
| self.assert_mapped(aliases, "/lib/foo/libsrc/a.py", "./mylib/a.py") |
| if rel_yn: |
| assert msgs == [ |
| "Aliases (relative=True):", |
| " Rule: '/home/*/src' -> './mysrc/' using regex " |
| + "'[/\\\\\\\\]home[/\\\\\\\\][^/\\\\\\\\]*[/\\\\\\\\]src[/\\\\\\\\]'", |
| " Rule: '/lib/*/libsrc' -> './mylib/' using regex " |
| + "'[/\\\\\\\\]lib[/\\\\\\\\][^/\\\\\\\\]*[/\\\\\\\\]libsrc[/\\\\\\\\]'", |
| "Matched path '/home/foo/src/a.py' to rule '/home/*/src' -> './mysrc/', " |
| + "producing './mysrc/a.py'", |
| "Matched path '/lib/foo/libsrc/a.py' to rule '/lib/*/libsrc' -> './mylib/', " |
| + "producing './mylib/a.py'", |
| ] |
| else: |
| assert msgs == [ |
| "Aliases (relative=False):", |
| " Rule: '/home/*/src' -> './mysrc/' using regex " |
| + "'[/\\\\\\\\]home[/\\\\\\\\][^/\\\\\\\\]*[/\\\\\\\\]src[/\\\\\\\\]'", |
| " Rule: '/lib/*/libsrc' -> './mylib/' using regex " |
| + "'[/\\\\\\\\]lib[/\\\\\\\\][^/\\\\\\\\]*[/\\\\\\\\]libsrc[/\\\\\\\\]'", |
| "Matched path '/home/foo/src/a.py' to rule '/home/*/src' -> './mysrc/', " |
| + f"producing {files.canonical_filename('./mysrc/a.py')!r}", |
| "Matched path '/lib/foo/libsrc/a.py' to rule '/lib/*/libsrc' -> './mylib/', " |
| + f"producing {files.canonical_filename('./mylib/a.py')!r}", |
| ] |
| |
| @pytest.mark.parametrize( |
| "badpat", |
| [ |
| "/ned/home/*", |
| "/ned/home/*/", |
| "/ned/home/*/*/", |
| ], |
| ) |
| def test_cant_have_wildcard_at_end(self, badpat: str) -> None: |
| aliases = PathAliases() |
| msg = "Pattern must not end with wildcards." |
| with pytest.raises(ConfigError, match=msg): |
| aliases.add(badpat, "fooey") |
| |
| def test_no_accidental_munging(self) -> None: |
| aliases = PathAliases() |
| aliases.add(r"c:\Zoo\boo", "src/") |
| aliases.add("/home/ned$", "src/") |
| self.assert_mapped(aliases, r"c:\Zoo\boo\foo.py", "src/foo.py") |
| self.assert_mapped(aliases, r"/home/ned$/foo.py", "src/foo.py") |
| |
| def test_paths_are_os_corrected(self, rel_yn: bool) -> None: |
| aliases = PathAliases(relative=rel_yn) |
| aliases.add("/home/ned/*/src", "./mysrc") |
| aliases.add(r"c:\ned\src", "./mysrc") |
| self.assert_mapped(aliases, r"C:\Ned\src\sub\a.py", "./mysrc/sub/a.py") |
| |
| aliases = PathAliases(relative=rel_yn) |
| aliases.add("/home/ned/*/src", r".\mysrc") |
| aliases.add(r"c:\ned\src", r".\mysrc") |
| self.assert_mapped( |
| aliases, |
| r"/home/ned/foo/src/sub/a.py", |
| r".\mysrc\sub\a.py", |
| ) |
| |
| # Try the paths in both orders. |
| lin = "*/project/module/" |
| win = "*\\project\\module\\" |
| lin_win_paths = [[lin, win], [win, lin]] |
| |
| @pytest.mark.parametrize("paths", lin_win_paths) |
| def test_windows_on_linux(self, paths: Iterable[str], rel_yn: bool) -> None: |
| # https://github.com/nedbat/coveragepy/issues/618 |
| aliases = PathAliases(relative=rel_yn) |
| for path in paths: |
| aliases.add(path, "project/module") |
| self.assert_mapped( |
| aliases, |
| "C:\\a\\path\\somewhere\\coveragepy_test\\project\\module\\tests\\file.py", |
| "project/module/tests/file.py", |
| ) |
| |
| @pytest.mark.parametrize("paths", lin_win_paths) |
| def test_linux_on_windows(self, paths: Iterable[str], rel_yn: bool) -> None: |
| # https://github.com/nedbat/coveragepy/issues/618 |
| aliases = PathAliases(relative=rel_yn) |
| for path in paths: |
| aliases.add(path, "project\\module") |
| self.assert_mapped( |
| aliases, |
| "C:/a/path/somewhere/coveragepy_test/project/module/tests/file.py", |
| "project\\module\\tests\\file.py", |
| ) |
| |
| @pytest.mark.parametrize("paths", lin_win_paths) |
| def test_relative_windows_on_linux(self, paths: Iterable[str]) -> None: |
| # https://github.com/nedbat/coveragepy/issues/991 |
| aliases = PathAliases(relative=True) |
| for path in paths: |
| aliases.add(path, "project/module") |
| self.assert_mapped( |
| aliases, |
| r"project\module\tests\file.py", |
| r"project/module/tests/file.py", |
| ) |
| |
| @pytest.mark.parametrize("paths", lin_win_paths) |
| def test_relative_linux_on_windows(self, paths: Iterable[str]) -> None: |
| # https://github.com/nedbat/coveragepy/issues/991 |
| aliases = PathAliases(relative=True) |
| for path in paths: |
| aliases.add(path, r"project\module") |
| self.assert_mapped( |
| aliases, |
| r"project/module/tests/file.py", |
| r"project\module\tests\file.py", |
| ) |
| |
| @pytest.mark.skipif(env.WINDOWS, reason="This test assumes Unix file system") |
| def test_implicit_relative_windows_on_linux(self) -> None: |
| # https://github.com/nedbat/coveragepy/issues/991 |
| aliases = PathAliases(relative=True) |
| self.assert_mapped( |
| aliases, |
| r"project\module\tests\file.py", |
| r"project/module/tests/file.py", |
| ) |
| |
| @pytest.mark.skipif(not env.WINDOWS, reason="This test assumes Windows file system") |
| def test_implicit_relative_linux_on_windows(self) -> None: |
| # https://github.com/nedbat/coveragepy/issues/991 |
| aliases = PathAliases(relative=True) |
| self.assert_mapped( |
| aliases, |
| r"project/module/tests/file.py", |
| r"project\module\tests\file.py", |
| ) |
| |
| def test_multiple_wildcard(self, rel_yn: bool) -> None: |
| aliases = PathAliases(relative=rel_yn) |
| aliases.add("/home/jenkins/*/a/*/b/*/django", "./django") |
| self.assert_mapped( |
| aliases, |
| "/home/jenkins/xx/a/yy/b/zz/django/foo/bar.py", |
| "./django/foo/bar.py", |
| ) |
| |
| def test_windows_root_paths(self, rel_yn: bool) -> None: |
| aliases = PathAliases(relative=rel_yn) |
| aliases.add("X:\\", "/tmp/src") |
| self.assert_mapped( |
| aliases, |
| "X:\\a\\file.py", |
| "/tmp/src/a/file.py", |
| ) |
| self.assert_mapped( |
| aliases, |
| "X:\\file.py", |
| "/tmp/src/file.py", |
| ) |
| |
| def test_leading_wildcard(self, rel_yn: bool) -> None: |
| aliases = PathAliases(relative=rel_yn) |
| aliases.add("*/d1", "./mysrc1") |
| aliases.add("*/d2", "./mysrc2") |
| self.assert_mapped(aliases, "/foo/bar/d1/x.py", "./mysrc1/x.py") |
| self.assert_mapped(aliases, "/foo/bar/d2/y.py", "./mysrc2/y.py") |
| |
| @pytest.mark.parametrize("dirname", [".", "..", "../other", "/"]) |
| def test_dot(self, dirname: str) -> None: |
| if env.WINDOWS and dirname == "/": |
| # The root test case was added for the manylinux Docker images, |
| # and I'm not sure how it should work on Windows, so skip it. |
| pytest.skip("Don't know how to handle root on Windows") |
| aliases = PathAliases() |
| aliases.add(dirname, "/the/source") |
| the_file = os.path.join(dirname, "a.py") |
| the_file = os.path.expanduser(the_file) |
| the_file = os.path.abspath(os.path.realpath(the_file)) |
| |
| assert "~" not in the_file # to be sure the test is pure. |
| self.assert_mapped(aliases, the_file, "/the/source/a.py") |
| |
| |
| class PathAliasesRealFilesTest(CoverageTest): |
| """Tests for coverage/files.py:PathAliases using real files.""" |
| |
| def test_aliasing_zip_files(self) -> None: |
| self.make_file("src/zipfiles/code.zip", "fake zip, doesn't matter") |
| aliases = PathAliases() |
| aliases.add("*/d1", "./src") |
| aliases.add("*/d2", "./src") |
| |
| expected = files.canonical_filename("src/zipfiles/code.zip/p1.py") |
| assert aliases.map("tox/d1/zipfiles/code.zip/p1.py") == expected |
| |
| |
| class FindPythonFilesTest(CoverageTest): |
| """Tests of `find_python_files`.""" |
| |
| def test_find_python_files(self) -> None: |
| self.make_file("sub/a.py") |
| self.make_file("sub/b.py") |
| self.make_file("sub/x.c") # nope: not .py |
| self.make_file("sub/ssub/__init__.py") |
| self.make_file("sub/ssub/s.py") |
| self.make_file("sub/ssub/~s.py") # nope: editor effluvia |
| self.make_file("sub/lab/exp.py") # nope: no __init__.py |
| self.make_file("sub/windows.pyw") |
| py_files = set(find_python_files("sub", include_namespace_packages=False)) |
| self.assert_same_files( |
| py_files, |
| [ |
| "sub/a.py", |
| "sub/b.py", |
| "sub/ssub/__init__.py", |
| "sub/ssub/s.py", |
| "sub/windows.pyw", |
| ], |
| ) |
| |
| def test_find_python_files_include_namespace_packages(self) -> None: |
| self.make_file("sub/a.py") |
| self.make_file("sub/b.py") |
| self.make_file("sub/x.c") # nope: not .py |
| self.make_file("sub/ssub/__init__.py") |
| self.make_file("sub/ssub/s.py") |
| self.make_file("sub/ssub/~s.py") # nope: editor effluvia |
| self.make_file("sub/lab/exp.py") |
| self.make_file("sub/windows.pyw") |
| py_files = set(find_python_files("sub", include_namespace_packages=True)) |
| self.assert_same_files( |
| py_files, |
| [ |
| "sub/a.py", |
| "sub/b.py", |
| "sub/ssub/__init__.py", |
| "sub/ssub/s.py", |
| "sub/lab/exp.py", |
| "sub/windows.pyw", |
| ], |
| ) |
| |
| |
| @pytest.mark.skipif(not env.WINDOWS, reason="Only need to run Windows tests on Windows.") |
| class WindowsFileTest(CoverageTest): |
| """Windows-specific tests of file name handling.""" |
| |
| run_in_temp_dir = False |
| |
| def test_actual_path(self) -> None: |
| assert actual_path(r"c:\Windows") == actual_path(r"C:\wINDOWS") |