Skip to content

Commit

Permalink
mypy: test_parser.py test_phystokens.py test_process.py test_report.p…
Browse files Browse the repository at this point in the history
…y test_results.py test_setup.py
  • Loading branch information
nedbat committed Jan 8, 2023
1 parent 2c52782 commit 1321803
Show file tree
Hide file tree
Showing 8 changed files with 221 additions and 184 deletions.
56 changes: 30 additions & 26 deletions tests/test_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,15 @@

"""Tests for coverage.py's code parsing."""

from __future__ import annotations

import ast
import os.path
import textwrap
import warnings

from typing import List

import pytest

from coverage import env
Expand All @@ -23,14 +27,14 @@ class PythonParserTest(CoverageTest):

run_in_temp_dir = False

def parse_source(self, text):
def parse_source(self, text: str) -> PythonParser:
"""Parse `text` as source, and return the `PythonParser` used."""
text = textwrap.dedent(text)
parser = PythonParser(text=text, exclude="nocover")
parser.parse_source()
return parser

def test_exit_counts(self):
def test_exit_counts(self) -> None:
parser = self.parse_source("""\
# check some basic branch counting
class Foo:
Expand All @@ -47,7 +51,7 @@ class Bar:
2:1, 3:1, 4:2, 5:1, 7:1, 9:1, 10:1
}

def test_generator_exit_counts(self):
def test_generator_exit_counts(self) -> None:
# https://github.com/nedbat/coveragepy/issues/324
parser = self.parse_source("""\
def gen(input):
Expand All @@ -63,7 +67,7 @@ def gen(input):
5:1, # list -> exit
}

def test_try_except(self):
def test_try_except(self) -> None:
parser = self.parse_source("""\
try:
a = 2
Expand All @@ -79,7 +83,7 @@ def test_try_except(self):
1: 1, 2:1, 3:2, 4:1, 5:2, 6:1, 7:1, 8:1, 9:1
}

def test_excluded_classes(self):
def test_excluded_classes(self) -> None:
parser = self.parse_source("""\
class Foo:
def __init__(self):
Expand All @@ -93,7 +97,7 @@ class Bar:
1:0, 2:1, 3:1
}

def test_missing_branch_to_excluded_code(self):
def test_missing_branch_to_excluded_code(self) -> None:
parser = self.parse_source("""\
if fooey:
a = 2
Expand Down Expand Up @@ -121,7 +125,7 @@ def foo():
""")
assert parser.exit_counts() == { 1:1, 2:1, 3:1, 6:1 }

def test_indentation_error(self):
def test_indentation_error(self) -> None:
msg = (
"Couldn't parse '<code>' as Python source: " +
"'unindent does not match any outer indentation level' at line 3"
Expand All @@ -133,15 +137,15 @@ def test_indentation_error(self):
1
""")

def test_token_error(self):
def test_token_error(self) -> None:
msg = "Couldn't parse '<code>' as Python source: 'EOF in multi-line string' at line 1"
with pytest.raises(NotPython, match=msg):
_ = self.parse_source("""\
'''
""")

@xfail_pypy38
def test_decorator_pragmas(self):
def test_decorator_pragmas(self) -> None:
parser = self.parse_source("""\
# 1
Expand Down Expand Up @@ -177,7 +181,7 @@ def func(x=25):
assert parser.statements == {8}

@xfail_pypy38
def test_decorator_pragmas_with_colons(self):
def test_decorator_pragmas_with_colons(self) -> None:
# A colon in a decorator expression would confuse the parser,
# ending the exclusion of the decorated function.
parser = self.parse_source("""\
Expand All @@ -197,7 +201,7 @@ def g():
assert parser.raw_statements == raw_statements
assert parser.statements == set()

def test_class_decorator_pragmas(self):
def test_class_decorator_pragmas(self) -> None:
parser = self.parse_source("""\
class Foo(object):
def __init__(self):
Expand All @@ -211,7 +215,7 @@ def __init__(self):
assert parser.raw_statements == {1, 2, 3, 5, 6, 7, 8}
assert parser.statements == {1, 2, 3}

def test_empty_decorated_function(self):
def test_empty_decorated_function(self) -> None:
parser = self.parse_source("""\
def decorator(func):
return func
Expand Down Expand Up @@ -247,7 +251,7 @@ def bar(self):
assert expected_arcs == parser.arcs()
assert expected_exits == parser.exit_counts()

def test_fuzzed_double_parse(self):
def test_fuzzed_double_parse(self) -> None:
# https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=50381
# The second parse used to raise `TypeError: 'NoneType' object is not iterable`
msg = "EOF in multi-line statement"
Expand All @@ -262,13 +266,13 @@ class ParserMissingArcDescriptionTest(CoverageTest):

run_in_temp_dir = False

def parse_text(self, source):
def parse_text(self, source: str) -> PythonParser:
"""Parse Python source, and return the parser object."""
parser = PythonParser(text=textwrap.dedent(source))
parser.parse_source()
return parser

def test_missing_arc_description(self):
def test_missing_arc_description(self) -> None:
# This code is never run, so the actual values don't matter.
parser = self.parse_text("""\
if x:
Expand Down Expand Up @@ -304,7 +308,7 @@ def func10():
)
assert expected == parser.missing_arc_description(11, 13)

def test_missing_arc_descriptions_for_small_callables(self):
def test_missing_arc_descriptions_for_small_callables(self) -> None:
parser = self.parse_text("""\
callables = [
lambda: 2,
Expand All @@ -323,7 +327,7 @@ def test_missing_arc_descriptions_for_small_callables(self):
expected = "line 5 didn't finish the set comprehension on line 5"
assert expected == parser.missing_arc_description(5, -5)

def test_missing_arc_descriptions_for_exceptions(self):
def test_missing_arc_descriptions_for_exceptions(self) -> None:
parser = self.parse_text("""\
try:
pass
Expand All @@ -343,7 +347,7 @@ def test_missing_arc_descriptions_for_exceptions(self):
)
assert expected == parser.missing_arc_description(5, 6)

def test_missing_arc_descriptions_for_finally(self):
def test_missing_arc_descriptions_for_finally(self) -> None:
parser = self.parse_text("""\
def function():
for i in range(2):
Expand Down Expand Up @@ -417,7 +421,7 @@ def function():
)
assert expected == parser.missing_arc_description(18, -1)

def test_missing_arc_descriptions_bug460(self):
def test_missing_arc_descriptions_bug460(self) -> None:
parser = self.parse_text("""\
x = 1
d = {
Expand All @@ -429,7 +433,7 @@ def test_missing_arc_descriptions_bug460(self):
assert parser.missing_arc_description(2, -3) == "line 3 didn't finish the lambda on line 3"

@pytest.mark.skipif(not env.PYBEHAVIOR.match_case, reason="Match-case is new in 3.10")
def test_match_case_with_default(self):
def test_match_case_with_default(self) -> None:
parser = self.parse_text("""\
for command in ["huh", "go home", "go n"]:
match command.split():
Expand All @@ -450,7 +454,7 @@ def test_match_case_with_default(self):
class ParserFileTest(CoverageTest):
"""Tests for coverage.py's code parsing from files."""

def parse_file(self, filename):
def parse_file(self, filename: str) -> PythonParser:
"""Parse `text` as source, and return the `PythonParser` used."""
parser = PythonParser(filename=filename, exclude="nocover")
parser.parse_source()
Expand All @@ -459,7 +463,7 @@ def parse_file(self, filename):
@pytest.mark.parametrize("slug, newline", [
("unix", "\n"), ("dos", "\r\n"), ("mac", "\r"),
])
def test_line_endings(self, slug, newline):
def test_line_endings(self, slug: str, newline: str) -> None:
text = """\
# check some basic branch counting
class Foo:
Expand All @@ -478,14 +482,14 @@ class Bar:
parser = self.parse_file(fname)
assert parser.exit_counts() == counts, f"Wrong for {fname!r}"

def test_encoding(self):
def test_encoding(self) -> None:
self.make_file("encoded.py", """\
coverage = "\xe7\xf6v\xear\xe3g\xe9"
""")
parser = self.parse_file("encoded.py")
assert parser.exit_counts() == {1: 1}

def test_missing_line_ending(self):
def test_missing_line_ending(self) -> None:
# Test that the set of statements is the same even if a final
# multi-line statement has no final newline.
# https://github.com/nedbat/coveragepy/issues/293
Expand Down Expand Up @@ -514,7 +518,7 @@ def test_missing_line_ending(self):
assert parser.statements == {1}


def test_ast_dump():
def test_ast_dump() -> None:
# Run the AST_DUMP code to make sure it doesn't fail, with some light
# assertions. Use parser.py as the test code since it is the longest file,
# and fitting, since it's the AST_DUMP code.
Expand All @@ -531,7 +535,7 @@ def test_ast_dump():
# stress_phystoken.tok has deprecation warnings, suppress them.
warnings.filterwarnings("ignore", message=r".*invalid escape sequence",)
ast_root = ast.parse(source)
result = []
result: List[str] = []
ast_dump(ast_root, print=result.append)
if num_lines < 100:
continue
Expand Down
34 changes: 17 additions & 17 deletions tests/test_phystokens.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ class PhysTokensTest(CoverageTest):

run_in_temp_dir = False

def check_tokenization(self, source):
def check_tokenization(self, source: str) -> None:
"""Tokenize `source`, then put it back together, should be the same."""
tokenized = ""
for line in source_token_lines(source):
Expand All @@ -71,26 +71,26 @@ def check_tokenization(self, source):
tokenized = re.sub(r"(?m)[ \t]+$", "", tokenized)
assert source == tokenized

def check_file_tokenization(self, fname):
def check_file_tokenization(self, fname: str) -> None:
"""Use the contents of `fname` for `check_tokenization`."""
self.check_tokenization(get_python_source(fname))

def test_simple(self):
def test_simple(self) -> None:
assert list(source_token_lines(SIMPLE)) == SIMPLE_TOKENS
self.check_tokenization(SIMPLE)

def test_missing_final_newline(self):
def test_missing_final_newline(self) -> None:
# We can tokenize source that is missing the final newline.
assert list(source_token_lines(SIMPLE.rstrip())) == SIMPLE_TOKENS

def test_tab_indentation(self):
def test_tab_indentation(self) -> None:
# Mixed tabs and spaces...
assert list(source_token_lines(MIXED_WS)) == MIXED_WS_TOKENS

def test_bug_822(self):
def test_bug_822(self) -> None:
self.check_tokenization(BUG_822)

def test_tokenize_real_file(self):
def test_tokenize_real_file(self) -> None:
# Check the tokenization of a real file (large, btw).
real_file = os.path.join(TESTS_DIR, "test_coverage.py")
self.check_file_tokenization(real_file)
Expand All @@ -99,7 +99,7 @@ def test_tokenize_real_file(self):
"stress_phystoken.tok",
"stress_phystoken_dos.tok",
])
def test_stress(self, fname):
def test_stress(self, fname: str) -> None:
# Check the tokenization of the stress-test files.
# And check that those files haven't been incorrectly "fixed".
with warnings.catch_warnings():
Expand All @@ -116,7 +116,7 @@ class SoftKeywordTest(CoverageTest):

run_in_temp_dir = False

def test_soft_keywords(self):
def test_soft_keywords(self) -> None:
source = textwrap.dedent("""\
match re.match(something):
case ["what"]:
Expand Down Expand Up @@ -168,40 +168,40 @@ class SourceEncodingTest(CoverageTest):

run_in_temp_dir = False

def test_detect_source_encoding(self):
def test_detect_source_encoding(self) -> None:
for _, source, expected in ENCODING_DECLARATION_SOURCES:
assert source_encoding(source) == expected, f"Wrong encoding in {source!r}"

def test_detect_source_encoding_not_in_comment(self):
def test_detect_source_encoding_not_in_comment(self) -> None:
# Should not detect anything here
source = b'def parse(src, encoding=None):\n pass'
assert source_encoding(source) == DEF_ENCODING

def test_dont_detect_source_encoding_on_third_line(self):
def test_dont_detect_source_encoding_on_third_line(self) -> None:
# A coding declaration doesn't count on the third line.
source = b"\n\n# coding=cp850\n\n"
assert source_encoding(source) == DEF_ENCODING

def test_detect_source_encoding_of_empty_file(self):
def test_detect_source_encoding_of_empty_file(self) -> None:
# An important edge case.
assert source_encoding(b"") == DEF_ENCODING

def test_bom(self):
def test_bom(self) -> None:
# A BOM means utf-8.
source = b"\xEF\xBB\xBFtext = 'hello'\n"
assert source_encoding(source) == 'utf-8-sig'

def test_bom_with_encoding(self):
def test_bom_with_encoding(self) -> None:
source = b"\xEF\xBB\xBF# coding: utf-8\ntext = 'hello'\n"
assert source_encoding(source) == 'utf-8-sig'

def test_bom_is_wrong(self):
def test_bom_is_wrong(self) -> None:
# A BOM with an explicit non-utf8 encoding is an error.
source = b"\xEF\xBB\xBF# coding: cp850\n"
with pytest.raises(SyntaxError, match="encoding problem: utf-8"):
source_encoding(source)

def test_unknown_encoding(self):
def test_unknown_encoding(self) -> None:
source = b"# coding: klingon\n"
with pytest.raises(SyntaxError, match="unknown encoding: klingon"):
source_encoding(source)
Loading

0 comments on commit 1321803

Please sign in to comment.