diff options
Diffstat (limited to 'tests/test_files.py')
-rw-r--r-- | tests/test_files.py | 209 |
1 files changed, 146 insertions, 63 deletions
diff --git a/tests/test_files.py b/tests/test_files.py index 8fea61d0..9a4cea7f 100644 --- a/tests/test_files.py +++ b/tests/test_files.py @@ -3,8 +3,10 @@ """Tests for files.py""" +import itertools import os import os.path +import re from unittest import mock import pytest @@ -12,8 +14,8 @@ import pytest from coverage import env, files from coverage.exceptions import ConfigError from coverage.files import ( - FnmatchMatcher, ModuleMatcher, PathAliases, TreeMatcher, abs_file, - actual_path, find_python_files, flat_rootname, fnmatches_to_regex, + GlobMatcher, ModuleMatcher, PathAliases, TreeMatcher, abs_file, + actual_path, find_python_files, flat_rootname, globs_to_regex, ) from tests.coveragetest import CoverageTest @@ -104,59 +106,138 @@ def test_flat_rootname(original, flat): assert flat_rootname(original) == flat +def globs_to_regex_params( + patterns, case_insensitive=False, partial=False, matches=(), nomatches=(), +): + """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," + - "matches," + - "nomatches", -[ - ( - ["abc", "xyz"], False, False, + "patterns, case_insensitive, partial, text, result", + list(itertools.chain.from_iterable([ + globs_to_regex_params( ["abc", "xyz"], - ["ABC", "xYz", "abcx", "xabc", "axyz", "xyza"], - ), - ( - ["abc", "xyz"], True, False, - ["abc", "xyz", "Abc", "XYZ", "AbC"], - ["abcx", "xabc", "axyz", "xyza"], - ), - ( - ["abc/hi.py"], True, False, - ["abc/hi.py", "ABC/hi.py", r"ABC\hi.py"], - ["abc_hi.py", "abc/hi.pyc"], - ), - ( - [r"abc\hi.py"], True, False, - [r"abc\hi.py", r"ABC\hi.py"], - ["abc/hi.py", "ABC/hi.py", "abc_hi.py", "abc/hi.pyc"], - ), - ( - ["abc/*/hi.py"], True, False, - ["abc/foo/hi.py", "ABC/foo/bar/hi.py", r"ABC\foo/bar/hi.py"], - ["abc/hi.py", "abc/hi.pyc"], - ), - ( - ["abc/[a-f]*/hi.py"], True, False, - ["abc/foo/hi.py", "ABC/foo/bar/hi.py", r"ABC\foo/bar/hi.py"], - ["abc/zoo/hi.py", "abc/hi.py", "abc/hi.pyc"], - ), - ( - ["abc/"], True, True, - ["abc/foo/hi.py", "ABC/foo/bar/hi.py", r"ABC\foo/bar/hi.py"], - ["abcd/foo.py", "xabc/hi.py"], - ), - ( - ["*/foo"], False, True, - ["abc/foo/hi.py", "foo/hi.py"], - ["abc/xfoo/hi.py"], - ), - + 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"], + nomatches=["abc/xfoo/hi.py"], + ), + globs_to_regex_params( + ["**/foo"], + matches=["foo", "hello/foo", "hi/there/foo"], + nomatches=["foob", "hello/foob", "hello/Foo"], + ), + ])) +) +def test_globs_to_regex(patterns, case_insensitive, partial, text, result): + 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", "["), + ("Hello+there", "+"), + ("{a,b}c", "{"), + ("x/a**/b.py", "a**"), + ("x/abcd**/b.py", "abcd**"), + ("x/**a/b.py", "**a"), + ("x/**/**/b.py", "**/**"), ]) -def test_fnmatches_to_regex(patterns, case_insensitive, partial, matches, nomatches): - regex = fnmatches_to_regex(patterns, case_insensitive=case_insensitive, partial=partial) - for s in matches: - assert regex.match(s) - for s in nomatches: - assert not regex.match(s) +def test_invalid_globs(pattern, bad_word): + msg = f"File pattern can't include {bad_word!r}" + with pytest.raises(ConfigError, match=re.escape(msg)): + globs_to_regex([pattern]) class MatcherTest(CoverageTest): @@ -217,7 +298,7 @@ class MatcherTest(CoverageTest): for modulename, matches in matches_to_try: assert mm.match(modulename) == matches, modulename - def test_fnmatch_matcher(self): + def test_glob_matcher(self): matches_to_try = [ (self.make_file("sub/file1.py"), True), (self.make_file("sub/file2.c"), False), @@ -225,23 +306,25 @@ class MatcherTest(CoverageTest): (self.make_file("sub3/file4.py"), True), (self.make_file("sub3/file5.c"), False), ] - fnm = FnmatchMatcher(["*.py", "*/sub2/*"]) + fnm = GlobMatcher(["*.py", "*/sub2/*"]) assert fnm.info() == ["*.py", "*/sub2/*"] for filepath, matches in matches_to_try: self.assertMatches(fnm, filepath, matches) - def test_fnmatch_matcher_overload(self): - fnm = FnmatchMatcher(["*x%03d*.txt" % i for i in range(500)]) + def test_glob_matcher_overload(self): + 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_fnmatch_windows_paths(self): + def test_glob_windows_paths(self): # We should be able to match Windows paths even if we are running on # a non-Windows OS. - fnm = FnmatchMatcher(["*/foo.py"]) + fnm = GlobMatcher(["*/foo.py"]) self.assertMatches(fnm, r"dir\foo.py", True) - fnm = FnmatchMatcher([r"*\foo.py"]) + fnm = GlobMatcher([r"*\foo.py"]) self.assertMatches(fnm, r"dir\foo.py", True) @@ -309,9 +392,9 @@ class PathAliasesTest(CoverageTest): assert msgs == [ "Aliases (relative=True):", " Rule: '/home/*/src' -> './mysrc/' using regex " + - "'(?s:[\\\\\\\\/]home[\\\\\\\\/].*[\\\\\\\\/]src[\\\\\\\\/])'", + "'[/\\\\\\\\]home[/\\\\\\\\][^/\\\\\\\\]*[/\\\\\\\\]src[/\\\\\\\\]'", " Rule: '/lib/*/libsrc' -> './mylib/' using regex " + - "'(?s:[\\\\\\\\/]lib[\\\\\\\\/].*[\\\\\\\\/]libsrc[\\\\\\\\/])'", + "'[/\\\\\\\\]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/', " + @@ -321,9 +404,9 @@ class PathAliasesTest(CoverageTest): assert msgs == [ "Aliases (relative=False):", " Rule: '/home/*/src' -> './mysrc/' using regex " + - "'(?s:[\\\\\\\\/]home[\\\\\\\\/].*[\\\\\\\\/]src[\\\\\\\\/])'", + "'[/\\\\\\\\]home[/\\\\\\\\][^/\\\\\\\\]*[/\\\\\\\\]src[/\\\\\\\\]'", " Rule: '/lib/*/libsrc' -> './mylib/' using regex " + - "'(?s:[\\\\\\\\/]lib[\\\\\\\\/].*[\\\\\\\\/]libsrc[\\\\\\\\/])'", + "'[/\\\\\\\\]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/', " + |