diff options
author | Ned Batchelder <ned@nedbatchelder.com> | 2023-01-07 21:25:42 -0500 |
---|---|---|
committer | Ned Batchelder <ned@nedbatchelder.com> | 2023-01-07 21:25:42 -0500 |
commit | 13218037401dc30f05fd3a16a2cd52ee882fd1c4 (patch) | |
tree | c58e4361b3bc81c15196255e3c3a90cb10aa2b04 | |
parent | 2c527825ac0cf394b32d773fd0ca5375dd8c031b (diff) | |
download | python-coveragepy-git-13218037401dc30f05fd3a16a2cd52ee882fd1c4.tar.gz |
mypy: test_parser.py test_phystokens.py test_process.py test_report.py test_results.py test_setup.py
-rw-r--r-- | tests/test_parser.py | 56 | ||||
-rw-r--r-- | tests/test_phystokens.py | 34 | ||||
-rw-r--r-- | tests/test_plugins.py | 86 | ||||
-rw-r--r-- | tests/test_process.py | 140 | ||||
-rw-r--r-- | tests/test_report.py | 31 | ||||
-rw-r--r-- | tests/test_results.py | 38 | ||||
-rw-r--r-- | tests/test_setup.py | 14 | ||||
-rw-r--r-- | tox.ini | 6 |
8 files changed, 221 insertions, 184 deletions
diff --git a/tests/test_parser.py b/tests/test_parser.py index 057b9244..8009ce51 100644 --- a/tests/test_parser.py +++ b/tests/test_parser.py @@ -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 @@ -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: @@ -47,7 +51,7 @@ class PythonParserTest(CoverageTest): 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): @@ -63,7 +67,7 @@ class PythonParserTest(CoverageTest): 5:1, # list -> exit } - def test_try_except(self): + def test_try_except(self) -> None: parser = self.parse_source("""\ try: a = 2 @@ -79,7 +83,7 @@ class PythonParserTest(CoverageTest): 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): @@ -93,7 +97,7 @@ class PythonParserTest(CoverageTest): 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 @@ -121,7 +125,7 @@ class PythonParserTest(CoverageTest): """) 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" @@ -133,7 +137,7 @@ class PythonParserTest(CoverageTest): 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("""\ @@ -141,7 +145,7 @@ class PythonParserTest(CoverageTest): """) @xfail_pypy38 - def test_decorator_pragmas(self): + def test_decorator_pragmas(self) -> None: parser = self.parse_source("""\ # 1 @@ -177,7 +181,7 @@ class PythonParserTest(CoverageTest): 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("""\ @@ -197,7 +201,7 @@ class PythonParserTest(CoverageTest): 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): @@ -211,7 +215,7 @@ class PythonParserTest(CoverageTest): 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 @@ -247,7 +251,7 @@ class PythonParserTest(CoverageTest): 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" @@ -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: @@ -304,7 +308,7 @@ class ParserMissingArcDescriptionTest(CoverageTest): ) 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, @@ -323,7 +327,7 @@ class ParserMissingArcDescriptionTest(CoverageTest): 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 @@ -343,7 +347,7 @@ class ParserMissingArcDescriptionTest(CoverageTest): ) 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): @@ -417,7 +421,7 @@ class ParserMissingArcDescriptionTest(CoverageTest): ) 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 = { @@ -429,7 +433,7 @@ class ParserMissingArcDescriptionTest(CoverageTest): 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(): @@ -450,7 +454,7 @@ class ParserMissingArcDescriptionTest(CoverageTest): 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() @@ -459,7 +463,7 @@ class ParserFileTest(CoverageTest): @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: @@ -478,14 +482,14 @@ class ParserFileTest(CoverageTest): 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 @@ -514,7 +518,7 @@ class ParserFileTest(CoverageTest): 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. @@ -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 diff --git a/tests/test_phystokens.py b/tests/test_phystokens.py index dae1a0ed..5807f00d 100644 --- a/tests/test_phystokens.py +++ b/tests/test_phystokens.py @@ -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): @@ -71,26 +71,26 @@ class PhysTokensTest(CoverageTest): 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) @@ -99,7 +99,7 @@ class PhysTokensTest(CoverageTest): "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(): @@ -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"]: @@ -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) diff --git a/tests/test_plugins.py b/tests/test_plugins.py index d407f748..866fab87 100644 --- a/tests/test_plugins.py +++ b/tests/test_plugins.py @@ -27,7 +27,7 @@ from tests.helpers import CheckUniqueFilenames, swallow_warnings class FakeConfig: """A fake config for use in tests.""" - def __init__(self, plugin, options): + def __init__(self, plugin, options) -> None: self.plugin = plugin self.options = options self.asked_for = [] @@ -44,7 +44,7 @@ class FakeConfig: class LoadPluginsTest(CoverageTest): """Test Plugins.load_plugins directly.""" - def test_implicit_boolean(self): + def test_implicit_boolean(self) -> None: self.make_file("plugin1.py", """\ from coverage import CoveragePlugin @@ -62,7 +62,7 @@ class LoadPluginsTest(CoverageTest): plugins = Plugins.load_plugins(["plugin1"], config) assert plugins - def test_importing_and_configuring(self): + def test_importing_and_configuring(self) -> None: self.make_file("plugin1.py", """\ from coverage import CoveragePlugin @@ -83,7 +83,7 @@ class LoadPluginsTest(CoverageTest): assert plugins[0].options == {'a': 'hello'} assert config.asked_for == ['plugin1'] - def test_importing_and_configuring_more_than_one(self): + def test_importing_and_configuring_more_than_one(self) -> None: self.make_file("plugin1.py", """\ from coverage import CoveragePlugin @@ -124,11 +124,11 @@ class LoadPluginsTest(CoverageTest): assert plugins[1].this_is == "me" assert plugins[1].options == {'a': 'second'} - def test_cant_import(self): + def test_cant_import(self) -> None: with pytest.raises(ImportError, match="No module named '?plugin_not_there'?"): _ = Plugins.load_plugins(["plugin_not_there"], None) - def test_plugin_must_define_coverage_init(self): + def test_plugin_must_define_coverage_init(self) -> None: self.make_file("no_plugin.py", """\ from coverage import CoveragePlugin Nothing = 0 @@ -141,7 +141,7 @@ class LoadPluginsTest(CoverageTest): class PluginTest(CoverageTest): """Test plugins through the Coverage class.""" - def test_plugin_imported(self): + def test_plugin_imported(self) -> None: # Prove that a plugin will be imported. self.make_file("my_plugin.py", """\ from coverage import CoveragePlugin @@ -162,7 +162,7 @@ class PluginTest(CoverageTest): with open("evidence.out") as f: assert f.read() == "we are here!" - def test_missing_plugin_raises_import_error(self): + def test_missing_plugin_raises_import_error(self) -> None: # Prove that a missing plugin will raise an ImportError. with pytest.raises(ImportError, match="No module named '?does_not_exist_woijwoicweo'?"): cov = coverage.Coverage() @@ -170,7 +170,7 @@ class PluginTest(CoverageTest): cov.start() cov.stop() - def test_bad_plugin_isnt_hidden(self): + def test_bad_plugin_isnt_hidden(self) -> None: # Prove that a plugin with an error in it will raise the error. self.make_file("plugin_over_zero.py", "1/0") with pytest.raises(ZeroDivisionError): @@ -179,7 +179,7 @@ class PluginTest(CoverageTest): cov.start() cov.stop() - def test_plugin_sys_info(self): + def test_plugin_sys_info(self) -> None: self.make_file("plugin_sys_info.py", """\ import coverage @@ -213,7 +213,7 @@ class PluginTest(CoverageTest): ] assert expected_end == out_lines[-len(expected_end):] - def test_plugin_with_no_sys_info(self): + def test_plugin_with_no_sys_info(self) -> None: self.make_file("plugin_no_sys_info.py", """\ import coverage @@ -239,7 +239,7 @@ class PluginTest(CoverageTest): ] assert expected_end == out_lines[-len(expected_end):] - def test_local_files_are_importable(self): + def test_local_files_are_importable(self) -> None: self.make_file("importing_plugin.py", """\ from coverage import CoveragePlugin import local_module @@ -264,7 +264,7 @@ class PluginTest(CoverageTest): @pytest.mark.skipif(env.C_TRACER, reason="This test is only about PyTracer.") class PluginWarningOnPyTracerTest(CoverageTest): """Test that we get a controlled exception with plugins on PyTracer.""" - def test_exception_if_plugins_on_pytracer(self): + def test_exception_if_plugins_on_pytracer(self) -> None: self.make_file("simple.py", "a = 1") cov = coverage.Coverage() @@ -285,7 +285,7 @@ class FileTracerTest(CoverageTest): class GoodFileTracerTest(FileTracerTest): """Tests of file tracer plugin happy paths.""" - def test_plugin1(self): + def test_plugin1(self) -> None: self.make_file("simple.py", """\ import try_xyz a = 1 @@ -354,7 +354,7 @@ class GoodFileTracerTest(FileTracerTest): self.make_file("bar_4.html", lines(4)) self.make_file("foo_7.html", lines(7)) - def test_plugin2(self): + def test_plugin2(self) -> None: self.make_render_and_caller() cov = coverage.Coverage(omit=["*quux*"]) @@ -379,7 +379,7 @@ class GoodFileTracerTest(FileTracerTest): assert "quux_5.html" not in line_counts(cov.get_data()) - def test_plugin2_with_branch(self): + def test_plugin2_with_branch(self) -> None: self.make_render_and_caller() cov = coverage.Coverage(branch=True, omit=["*quux*"]) @@ -400,7 +400,7 @@ class GoodFileTracerTest(FileTracerTest): assert analysis.missing == {1, 2, 3, 6, 7} - def test_plugin2_with_text_report(self): + def test_plugin2_with_text_report(self) -> None: self.make_render_and_caller() cov = coverage.Coverage(branch=True, omit=["*quux*"]) @@ -422,7 +422,7 @@ class GoodFileTracerTest(FileTracerTest): assert expected == report assert math.isclose(total, 4 / 11 * 100) - def test_plugin2_with_html_report(self): + def test_plugin2_with_html_report(self) -> None: self.make_render_and_caller() cov = coverage.Coverage(branch=True, omit=["*quux*"]) @@ -437,7 +437,7 @@ class GoodFileTracerTest(FileTracerTest): self.assert_exists("htmlcov/bar_4_html.html") self.assert_exists("htmlcov/foo_7_html.html") - def test_plugin2_with_xml_report(self): + def test_plugin2_with_xml_report(self) -> None: self.make_render_and_caller() cov = coverage.Coverage(branch=True, omit=["*quux*"]) @@ -468,7 +468,7 @@ class GoodFileTracerTest(FileTracerTest): 'name': 'foo_7.html', } - def test_defer_to_python(self): + def test_defer_to_python(self) -> None: # A plugin that measures, but then wants built-in python reporting. self.make_file("fairly_odd_plugin.py", """\ # A plugin that claims all the odd lines are executed, and none of @@ -521,7 +521,7 @@ class GoodFileTracerTest(FileTracerTest): assert expected == report assert total == 50 - def test_find_unexecuted(self): + def test_find_unexecuted(self) -> None: self.make_file("unexecuted_plugin.py", """\ import os import coverage.plugin @@ -653,7 +653,7 @@ class BadFileTracerTest(FileTracerTest): found_exc = any(em in stderr for em in excmsgs) # pragma: part covered assert found_exc, f"expected one of {excmsgs} in stderr" - def test_file_tracer_has_no_file_tracer_method(self): + def test_file_tracer_has_no_file_tracer_method(self) -> None: self.make_file("bad_plugin.py", """\ class Plugin(object): pass @@ -663,7 +663,7 @@ class BadFileTracerTest(FileTracerTest): """) self.run_bad_plugin("bad_plugin", "Plugin", our_error=False) - def test_file_tracer_has_inherited_sourcefilename_method(self): + def test_file_tracer_has_inherited_sourcefilename_method(self) -> None: self.make_file("bad_plugin.py", """\ import coverage class Plugin(coverage.CoveragePlugin): @@ -682,7 +682,7 @@ class BadFileTracerTest(FileTracerTest): excmsg="Class 'bad_plugin.FileTracer' needs to implement source_filename()", ) - def test_plugin_has_inherited_filereporter_method(self): + def test_plugin_has_inherited_filereporter_method(self) -> None: self.make_file("bad_plugin.py", """\ import coverage class Plugin(coverage.CoveragePlugin): @@ -702,7 +702,7 @@ class BadFileTracerTest(FileTracerTest): with pytest.raises(NotImplementedError, match=expected_msg): cov.report() - def test_file_tracer_fails(self): + def test_file_tracer_fails(self) -> None: self.make_file("bad_plugin.py", """\ import coverage.plugin class Plugin(coverage.plugin.CoveragePlugin): @@ -714,7 +714,7 @@ class BadFileTracerTest(FileTracerTest): """) self.run_bad_plugin("bad_plugin", "Plugin") - def test_file_tracer_fails_eventually(self): + def test_file_tracer_fails_eventually(self) -> None: # Django coverage plugin can report on a few files and then fail. # https://github.com/nedbat/coveragepy/issues/1011 self.make_file("bad_plugin.py", """\ @@ -745,7 +745,7 @@ class BadFileTracerTest(FileTracerTest): """) self.run_bad_plugin("bad_plugin", "Plugin") - def test_file_tracer_returns_wrong(self): + def test_file_tracer_returns_wrong(self) -> None: self.make_file("bad_plugin.py", """\ import coverage.plugin class Plugin(coverage.plugin.CoveragePlugin): @@ -759,7 +759,7 @@ class BadFileTracerTest(FileTracerTest): "bad_plugin", "Plugin", our_error=False, excmsg="'float' object has no attribute", ) - def test_has_dynamic_source_filename_fails(self): + def test_has_dynamic_source_filename_fails(self) -> None: self.make_file("bad_plugin.py", """\ import coverage.plugin class Plugin(coverage.plugin.CoveragePlugin): @@ -775,7 +775,7 @@ class BadFileTracerTest(FileTracerTest): """) self.run_bad_plugin("bad_plugin", "Plugin") - def test_source_filename_fails(self): + def test_source_filename_fails(self) -> None: self.make_file("bad_plugin.py", """\ import coverage.plugin class Plugin(coverage.plugin.CoveragePlugin): @@ -791,7 +791,7 @@ class BadFileTracerTest(FileTracerTest): """) self.run_bad_plugin("bad_plugin", "Plugin") - def test_source_filename_returns_wrong(self): + def test_source_filename_returns_wrong(self) -> None: self.make_file("bad_plugin.py", """\ import coverage.plugin class Plugin(coverage.plugin.CoveragePlugin): @@ -815,7 +815,7 @@ class BadFileTracerTest(FileTracerTest): ], ) - def test_dynamic_source_filename_fails(self): + def test_dynamic_source_filename_fails(self) -> None: self.make_file("bad_plugin.py", """\ import coverage.plugin class Plugin(coverage.plugin.CoveragePlugin): @@ -834,7 +834,7 @@ class BadFileTracerTest(FileTracerTest): """) self.run_bad_plugin("bad_plugin", "Plugin") - def test_line_number_range_raises_error(self): + def test_line_number_range_raises_error(self) -> None: self.make_file("bad_plugin.py", """\ import coverage.plugin class Plugin(coverage.plugin.CoveragePlugin): @@ -856,7 +856,7 @@ class BadFileTracerTest(FileTracerTest): "bad_plugin", "Plugin", our_error=False, excmsg="borked!", ) - def test_line_number_range_returns_non_tuple(self): + def test_line_number_range_returns_non_tuple(self) -> None: self.make_file("bad_plugin.py", """\ import coverage.plugin class Plugin(coverage.plugin.CoveragePlugin): @@ -878,7 +878,7 @@ class BadFileTracerTest(FileTracerTest): "bad_plugin", "Plugin", our_error=False, excmsg="line_number_range must return 2-tuple", ) - def test_line_number_range_returns_triple(self): + def test_line_number_range_returns_triple(self) -> None: self.make_file("bad_plugin.py", """\ import coverage.plugin class Plugin(coverage.plugin.CoveragePlugin): @@ -900,7 +900,7 @@ class BadFileTracerTest(FileTracerTest): "bad_plugin", "Plugin", our_error=False, excmsg="line_number_range must return 2-tuple", ) - def test_line_number_range_returns_pair_of_strings(self): + def test_line_number_range_returns_pair_of_strings(self) -> None: self.make_file("bad_plugin.py", """\ import coverage.plugin class Plugin(coverage.plugin.CoveragePlugin): @@ -932,7 +932,7 @@ class ConfigurerPluginTest(CoverageTest): run_in_temp_dir = False - def test_configurer_plugin(self): + def test_configurer_plugin(self) -> None: cov = coverage.Coverage() cov.set_option("run:plugins", ["tests.plugin_config"]) cov.start() @@ -978,7 +978,7 @@ class DynamicContextPluginTest(CoverageTest): reg.add_dynamic_context(Plugin()) """) - def make_test_files(self): + def make_test_files(self) -> None: """Make some files to use while testing dynamic context plugins.""" self.make_file("rendering.py", """\ def html_tag(tag, content): @@ -997,7 +997,7 @@ class DynamicContextPluginTest(CoverageTest): self.make_file("testsuite.py", """\ import rendering - def test_html_tag(): + def test_html_tag() -> None: assert rendering.html_tag('b', 'hello') == '<b>hello</b>' def doctest_html_tag(): @@ -1005,7 +1005,7 @@ class DynamicContextPluginTest(CoverageTest): rendering.html_tag('i', 'text') == '<i>text</i>' '''.strip()) - def test_renderers(): + def test_renderers() -> None: assert rendering.render_paragraph('hello') == '<p>hello</p>' assert rendering.render_bold('wide') == '<b>wide</b>' assert rendering.render_span('world') == '<span>world</span>' @@ -1030,7 +1030,7 @@ class DynamicContextPluginTest(CoverageTest): finally: cov.stop() - def test_plugin_standalone(self): + def test_plugin_standalone(self) -> None: self.make_plugin_capitalized_testnames('plugin_tests.py') self.make_test_files() @@ -1053,7 +1053,7 @@ class DynamicContextPluginTest(CoverageTest): data.set_query_context("test:RENDERERS") assert [2, 5, 8, 11] == sorted_lines(data, filenames['rendering.py']) - def test_static_context(self): + def test_static_context(self) -> None: self.make_plugin_capitalized_testnames('plugin_tests.py') self.make_test_files() @@ -1074,7 +1074,7 @@ class DynamicContextPluginTest(CoverageTest): ] assert expected == sorted(data.measured_contexts()) - def test_plugin_with_test_function(self): + def test_plugin_with_test_function(self) -> None: self.make_plugin_capitalized_testnames('plugin_tests.py') self.make_test_files() @@ -1107,7 +1107,7 @@ class DynamicContextPluginTest(CoverageTest): assert_context_lines("testsuite.test_html_tag", [2]) assert_context_lines("testsuite.test_renderers", [2, 5, 8, 11]) - def test_multiple_plugins(self): + def test_multiple_plugins(self) -> None: self.make_plugin_capitalized_testnames('plugin_tests.py') self.make_plugin_track_render('plugin_renderers.py') self.make_test_files() diff --git a/tests/test_process.py b/tests/test_process.py index 33d52923..bdfa3316 100644 --- a/tests/test_process.py +++ b/tests/test_process.py @@ -3,6 +3,8 @@ """Tests for process behavior of coverage.py.""" +from __future__ import annotations + import glob import os import os.path @@ -11,6 +13,8 @@ import stat import sys import textwrap +from typing import Any + import pytest import coverage @@ -25,7 +29,7 @@ from tests.helpers import re_lines_text class ProcessTest(CoverageTest): """Tests of the per-process behavior of coverage.py.""" - def test_save_on_exit(self): + def test_save_on_exit(self) -> None: self.make_file("mycode.py", """\ h = "Hello" w = "world" @@ -35,7 +39,7 @@ class ProcessTest(CoverageTest): self.run_command("coverage run mycode.py") self.assert_exists(".coverage") - def test_tests_dir_is_importable(self): + def test_tests_dir_is_importable(self) -> None: # Checks that we can import modules from the tests directory at all! self.make_file("mycode.py", """\ import covmod1 @@ -49,7 +53,7 @@ class ProcessTest(CoverageTest): self.assert_exists(".coverage") assert out == 'done\n' - def test_coverage_run_envvar_is_in_coveragerun(self): + def test_coverage_run_envvar_is_in_coveragerun(self) -> None: # Test that we are setting COVERAGE_RUN when we run. self.make_file("envornot.py", """\ import os @@ -64,7 +68,7 @@ class ProcessTest(CoverageTest): out = self.run_command("coverage run envornot.py") assert out == "true\n" - def make_b_or_c_py(self): + def make_b_or_c_py(self) -> None: """Create b_or_c.py, used in a few of these tests.""" # "b_or_c.py b" will run 6 lines. # "b_or_c.py c" will run 7 lines. @@ -81,7 +85,7 @@ class ProcessTest(CoverageTest): print('done') """) - def test_append_data(self): + def test_append_data(self) -> None: self.make_b_or_c_py() out = self.run_command("coverage run b_or_c.py b") @@ -100,7 +104,7 @@ class ProcessTest(CoverageTest): data.read() assert line_counts(data)['b_or_c.py'] == 8 - def test_append_data_with_different_file(self): + def test_append_data_with_different_file(self) -> None: self.make_b_or_c_py() self.make_file(".coveragerc", """\ @@ -124,7 +128,7 @@ class ProcessTest(CoverageTest): data.read() assert line_counts(data)['b_or_c.py'] == 8 - def test_append_can_create_a_data_file(self): + def test_append_can_create_a_data_file(self) -> None: self.make_b_or_c_py() out = self.run_command("coverage run --append b_or_c.py b") @@ -138,7 +142,7 @@ class ProcessTest(CoverageTest): data.read() assert line_counts(data)['b_or_c.py'] == 6 - def test_combine_with_rc(self): + def test_combine_with_rc(self) -> None: self.make_b_or_c_py() self.make_file(".coveragerc", """\ @@ -182,7 +186,7 @@ class ProcessTest(CoverageTest): TOTAL 8 0 100% """) - def test_combine_with_aliases(self): + def test_combine_with_aliases(self) -> None: self.make_file("d1/x.py", """\ a = 1 b = 2 @@ -236,7 +240,7 @@ class ProcessTest(CoverageTest): assert expected == actual assert list(summary.values())[0] == 6 - def test_erase_parallel(self): + def test_erase_parallel(self) -> None: self.make_file(".coveragerc", """\ [run] data_file = data.dat @@ -253,7 +257,7 @@ class ProcessTest(CoverageTest): self.assert_doesnt_exist("data.dat.gooey") self.assert_exists(".coverage") - def test_missing_source_file(self): + def test_missing_source_file(self) -> None: # Check what happens if the source is missing when reporting happens. self.make_file("fleeting.py", """\ s = 'goodbye, cruel world!' @@ -278,14 +282,14 @@ class ProcessTest(CoverageTest): assert "Traceback" not in out assert status == 1 - def test_running_missing_file(self): + def test_running_missing_file(self) -> None: status, out = self.run_command_status("coverage run xyzzy.py") assert re.search("No file to run: .*xyzzy.py", out) assert "raceback" not in out assert "rror" not in out assert status == 1 - def test_code_throws(self): + def test_code_throws(self) -> None: self.make_file("throw.py", """\ class MyException(Exception): pass @@ -315,7 +319,7 @@ class ProcessTest(CoverageTest): assert 'raise MyException("hey!")' in out assert status == 1 - def test_code_exits(self): + def test_code_exits(self) -> None: self.make_file("exit.py", """\ import sys def f1(): @@ -337,7 +341,7 @@ class ProcessTest(CoverageTest): assert status == status2 assert status == 17 - def test_code_exits_no_arg(self): + def test_code_exits_no_arg(self) -> None: self.make_file("exit_none.py", """\ import sys def f1(): @@ -354,7 +358,7 @@ class ProcessTest(CoverageTest): assert status == 0 @pytest.mark.skipif(not hasattr(os, "fork"), reason="Can't test os.fork, it doesn't exist.") - def test_fork(self): + def test_fork(self) -> None: self.make_file("fork.py", """\ import os @@ -397,7 +401,7 @@ class ProcessTest(CoverageTest): data.read() assert line_counts(data)['fork.py'] == 9 - def test_warnings_during_reporting(self): + def test_warnings_during_reporting(self) -> None: # While fixing issue #224, the warnings were being printed far too # often. Make sure they're not any more. self.make_file("hello.py", """\ @@ -418,7 +422,7 @@ class ProcessTest(CoverageTest): out = self.run_command("coverage html") assert out.count("Module xyzzy was never imported.") == 0 - def test_warns_if_never_run(self): + def test_warns_if_never_run(self) -> None: # Note: the name of the function can't have "warning" in it, or the # absolute path of the file will have "warning" in it, and an assertion # will fail. @@ -437,7 +441,7 @@ class ProcessTest(CoverageTest): assert "Exception" not in out @pytest.mark.skipif(env.METACOV, reason="Can't test tracers changing during metacoverage") - def test_warnings_trace_function_changed_with_threads(self): + def test_warnings_trace_function_changed_with_threads(self) -> None: # https://github.com/nedbat/coveragepy/issues/164 self.make_file("bug164.py", """\ @@ -457,7 +461,7 @@ class ProcessTest(CoverageTest): assert "Hello\n" in out assert "warning" not in out - def test_warning_trace_function_changed(self): + def test_warning_trace_function_changed(self) -> None: self.make_file("settrace.py", """\ import sys print("Hello") @@ -473,7 +477,7 @@ class ProcessTest(CoverageTest): # When meta-coverage testing, this test doesn't work, because it finds # coverage.py's own trace function. @pytest.mark.skipif(env.METACOV, reason="Can't test timid during coverage measurement.") - def test_timid(self): + def test_timid(self) -> None: # Test that the --timid command line argument properly swaps the tracer # function for a simpler one. # @@ -527,7 +531,7 @@ class ProcessTest(CoverageTest): timid_out = self.run_command("coverage run --timid showtrace.py") assert timid_out == "PyTracer\n" - def test_warn_preimported(self): + def test_warn_preimported(self) -> None: self.make_file("hello.py", """\ import goodbye import coverage @@ -554,7 +558,7 @@ class ProcessTest(CoverageTest): @pytest.mark.expensive @pytest.mark.skipif(not env.C_TRACER, reason="fullcoverage only works with the C tracer.") @pytest.mark.skipif(env.METACOV, reason="Can't test fullcoverage when measuring ourselves") - def test_fullcoverage(self): + def test_fullcoverage(self) -> None: # fullcoverage is a trick to get stdlib modules measured from # the very beginning of the process. Here we import os and # then check how many lines are measured. @@ -578,7 +582,7 @@ class ProcessTest(CoverageTest): # Pypy passes locally, but fails in CI? Perhaps the version of macOS is # significant? https://foss.heptapod.net/pypy/pypy/-/issues/3074 @pytest.mark.skipif(env.PYPY, reason="PyPy is unreliable with this test") - def test_lang_c(self): + def test_lang_c(self) -> None: # LANG=C forces getfilesystemencoding on Linux to 'ascii', which causes # failures with non-ascii file names. We don't want to make a real file # with strange characters, though, because that gets the test runners @@ -595,7 +599,7 @@ class ProcessTest(CoverageTest): out = self.run_command("coverage run weird_file.py") assert out == "1\n2\n" - def test_deprecation_warnings(self): + def test_deprecation_warnings(self) -> None: # Test that coverage doesn't trigger deprecation warnings. # https://github.com/nedbat/coveragepy/issues/305 self.make_file("allok.py", """\ @@ -612,7 +616,7 @@ class ProcessTest(CoverageTest): out = self.run_command("python allok.py") assert out == "No warnings!\n" - def test_run_twice(self): + def test_run_twice(self) -> None: # https://github.com/nedbat/coveragepy/issues/353 self.make_file("foo.py", """\ def foo(): @@ -643,7 +647,7 @@ class ProcessTest(CoverageTest): ) assert expected == out - def test_module_name(self): + def test_module_name(self) -> None: # https://github.com/nedbat/coveragepy/issues/478 # Make sure help doesn't show a silly command name when run as a # module, like it used to: @@ -658,7 +662,7 @@ TRY_EXECFILE = os.path.join(os.path.dirname(__file__), "modules/process_test/try class EnvironmentTest(CoverageTest): """Tests using try_execfile.py to test the execution environment.""" - def assert_tryexecfile_output(self, expected, actual): + def assert_tryexecfile_output(self, expected: str, actual: str) -> None: """Assert that the output we got is a successful run of try_execfile.py. `expected` and `actual` must be the same, modulo a few slight known @@ -669,27 +673,27 @@ class EnvironmentTest(CoverageTest): assert '"DATA": "xyzzy"' in actual assert actual == expected - def test_coverage_run_is_like_python(self): + def test_coverage_run_is_like_python(self) -> None: with open(TRY_EXECFILE) as f: self.make_file("run_me.py", f.read()) expected = self.run_command("python run_me.py") actual = self.run_command("coverage run run_me.py") self.assert_tryexecfile_output(expected, actual) - def test_coverage_run_far_away_is_like_python(self): + def test_coverage_run_far_away_is_like_python(self) -> None: with open(TRY_EXECFILE) as f: self.make_file("sub/overthere/prog.py", f.read()) expected = self.run_command("python sub/overthere/prog.py") actual = self.run_command("coverage run sub/overthere/prog.py") self.assert_tryexecfile_output(expected, actual) - def test_coverage_run_dashm_is_like_python_dashm(self): + def test_coverage_run_dashm_is_like_python_dashm(self) -> None: # These -m commands assume the coverage tree is on the path. expected = self.run_command("python -m process_test.try_execfile") actual = self.run_command("coverage run -m process_test.try_execfile") self.assert_tryexecfile_output(expected, actual) - def test_coverage_run_dir_is_like_python_dir(self): + def test_coverage_run_dir_is_like_python_dir(self) -> None: with open(TRY_EXECFILE) as f: self.make_file("with_main/__main__.py", f.read()) @@ -697,7 +701,7 @@ class EnvironmentTest(CoverageTest): actual = self.run_command("coverage run with_main") self.assert_tryexecfile_output(expected, actual) - def test_coverage_run_dashm_dir_no_init_is_like_python(self): + def test_coverage_run_dashm_dir_no_init_is_like_python(self) -> None: with open(TRY_EXECFILE) as f: self.make_file("with_main/__main__.py", f.read()) @@ -705,7 +709,7 @@ class EnvironmentTest(CoverageTest): actual = self.run_command("coverage run -m with_main") self.assert_tryexecfile_output(expected, actual) - def test_coverage_run_dashm_dir_with_init_is_like_python(self): + def test_coverage_run_dashm_dir_with_init_is_like_python(self) -> None: with open(TRY_EXECFILE) as f: self.make_file("with_main/__main__.py", f.read()) self.make_file("with_main/__init__.py", "") @@ -714,7 +718,7 @@ class EnvironmentTest(CoverageTest): actual = self.run_command("coverage run -m with_main") self.assert_tryexecfile_output(expected, actual) - def test_coverage_run_dashm_equal_to_doubledashsource(self): + def test_coverage_run_dashm_equal_to_doubledashsource(self) -> None: """regression test for #328 When imported by -m, a module's __name__ is __main__, but we need the @@ -727,7 +731,7 @@ class EnvironmentTest(CoverageTest): ) self.assert_tryexecfile_output(expected, actual) - def test_coverage_run_dashm_superset_of_doubledashsource(self): + def test_coverage_run_dashm_superset_of_doubledashsource(self) -> None: """Edge case: --source foo -m foo.bar""" # Ugh: without this config file, we'll get a warning about # CoverageWarning: Module process_test was previously imported, @@ -751,7 +755,7 @@ class EnvironmentTest(CoverageTest): assert st == 0 assert self.line_count(out) == 6, out - def test_coverage_run_script_imports_doubledashsource(self): + def test_coverage_run_script_imports_doubledashsource(self) -> None: # This file imports try_execfile, which compiles it to .pyc, so the # first run will have __file__ == "try_execfile.py" and the second will # have __file__ == "try_execfile.pyc", which throws off the comparison. @@ -770,7 +774,7 @@ class EnvironmentTest(CoverageTest): assert st == 0 assert self.line_count(out) == 6, out - def test_coverage_run_dashm_is_like_python_dashm_off_path(self): + def test_coverage_run_dashm_is_like_python_dashm_off_path(self) -> None: # https://github.com/nedbat/coveragepy/issues/242 self.make_file("sub/__init__.py", "") with open(TRY_EXECFILE) as f: @@ -780,7 +784,7 @@ class EnvironmentTest(CoverageTest): actual = self.run_command("coverage run -m sub.run_me") self.assert_tryexecfile_output(expected, actual) - def test_coverage_run_dashm_is_like_python_dashm_with__main__207(self): + def test_coverage_run_dashm_is_like_python_dashm_with__main__207(self) -> None: # https://github.com/nedbat/coveragepy/issues/207 self.make_file("package/__init__.py", "print('init')") self.make_file("package/__main__.py", "print('main')") @@ -788,7 +792,7 @@ class EnvironmentTest(CoverageTest): actual = self.run_command("coverage run -m package") assert expected == actual - def test_coverage_zip_is_like_python(self): + def test_coverage_zip_is_like_python(self) -> None: # Test running coverage from a zip file itself. Some environments # (windows?) zip up the coverage main to be used as the coverage # command. @@ -799,7 +803,7 @@ class EnvironmentTest(CoverageTest): actual = self.run_command(f"python {cov_main} run run_me.py") self.assert_tryexecfile_output(expected, actual) - def test_coverage_custom_script(self): + def test_coverage_custom_script(self) -> None: # https://github.com/nedbat/coveragepy/issues/678 # If sys.path[0] isn't the Python default, then coverage.py won't # fiddle with it. @@ -833,7 +837,7 @@ class EnvironmentTest(CoverageTest): assert "hello-xyzzy" in out @pytest.mark.skipif(env.WINDOWS, reason="Windows can't make symlinks") - def test_bug_862(self): + def test_bug_862(self) -> None: # This simulates how pyenv and pyenv-virtualenv end up creating the # coverage executable. self.make_file("elsewhere/bin/fake-coverage", """\ @@ -848,7 +852,7 @@ class EnvironmentTest(CoverageTest): out = self.run_command("somewhere/bin/fake-coverage run bar.py") assert "inside foo\n" == out - def test_bug_909(self): + def test_bug_909(self) -> None: # https://github.com/nedbat/coveragepy/issues/909 # The __init__ files were being imported before measurement started, # so the line in __init__.py was being marked as missed, and there were @@ -882,7 +886,7 @@ class ExcepthookTest(CoverageTest): # TODO: do we need these as process tests if we have test_execfile.py:RunFileTest? - def test_excepthook(self): + def test_excepthook(self) -> None: self.make_file("excepthook.py", """\ import sys @@ -912,7 +916,7 @@ class ExcepthookTest(CoverageTest): @pytest.mark.skipif(not env.CPYTHON, reason="non-CPython handles excepthook exits differently, punt for now." ) - def test_excepthook_exit(self): + def test_excepthook_exit(self) -> None: self.make_file("excepthook_exit.py", """\ import sys @@ -933,7 +937,7 @@ class ExcepthookTest(CoverageTest): assert cov_out == py_out @pytest.mark.skipif(env.PYPY, reason="PyPy handles excepthook throws differently.") - def test_excepthook_throw(self): + def test_excepthook_throw(self) -> None: self.make_file("excepthook_throw.py", """\ import sys @@ -961,20 +965,20 @@ class AliasedCommandTest(CoverageTest): run_in_temp_dir = False - def test_major_version_works(self): + def test_major_version_works(self) -> None: # "coverage3" works on py3 cmd = "coverage%d" % sys.version_info[0] out = self.run_command(cmd) assert "Code coverage for Python" in out - def test_wrong_alias_doesnt_work(self): + def test_wrong_alias_doesnt_work(self) -> None: # "coverage2" doesn't work on py3 assert sys.version_info[0] in [2, 3] # Let us know when Python 4 is out... badcmd = "coverage%d" % (5 - sys.version_info[0]) out = self.run_command(badcmd) assert "Code coverage for Python" not in out - def test_specific_alias_works(self): + def test_specific_alias_works(self) -> None: # "coverage-3.9" works on py3.9 cmd = "coverage-%d.%d" % sys.version_info[:2] out = self.run_command(cmd) @@ -985,7 +989,7 @@ class AliasedCommandTest(CoverageTest): "coverage%d" % sys.version_info[0], "coverage-%d.%d" % sys.version_info[:2], ]) - def test_aliases_used_in_messages(self, cmd): + def test_aliases_used_in_messages(self, cmd: str) -> None: out = self.run_command(f"{cmd} foobar") assert "Unknown command: 'foobar'" in out assert f"Use '{cmd} help' for help" in out @@ -996,7 +1000,7 @@ class PydocTest(CoverageTest): run_in_temp_dir = False - def assert_pydoc_ok(self, name, thing): + def assert_pydoc_ok(self, name: str, thing: Any) -> None: """Check that pydoc of `name` finds the docstring from `thing`.""" # Run pydoc. out = self.run_command("python -m pydoc " + name) @@ -1008,17 +1012,17 @@ class PydocTest(CoverageTest): for line in thing.__doc__.splitlines(): assert line.strip() in out - def test_pydoc_coverage(self): + def test_pydoc_coverage(self) -> None: self.assert_pydoc_ok("coverage", coverage) - def test_pydoc_coverage_coverage(self): + def test_pydoc_coverage_coverage(self) -> None: self.assert_pydoc_ok("coverage.Coverage", coverage.Coverage) class FailUnderTest(CoverageTest): """Tests of the --fail-under switch.""" - def setUp(self): + def setUp(self) -> None: super().setUp() self.make_file("forty_two_plus.py", """\ # I have 42.857% (3/7) coverage! @@ -1032,25 +1036,25 @@ class FailUnderTest(CoverageTest): """) self.make_data_file(lines={abs_file("forty_two_plus.py"): [2, 3, 4]}) - def test_report_43_is_ok(self): + def test_report_43_is_ok(self) -> None: st, out = self.run_command_status("coverage report --fail-under=43") assert st == 0 assert self.last_line_squeezed(out) == "TOTAL 7 4 43%" - def test_report_43_is_not_ok(self): + def test_report_43_is_not_ok(self) -> None: st, out = self.run_command_status("coverage report --fail-under=44") assert st == 2 expected = "Coverage failure: total of 43 is less than fail-under=44" assert expected == self.last_line_squeezed(out) - def test_report_42p86_is_not_ok(self): + def test_report_42p86_is_not_ok(self) -> None: self.make_file(".coveragerc", "[report]\nprecision = 2") st, out = self.run_command_status("coverage report --fail-under=42.88") assert st == 2 expected = "Coverage failure: total of 42.86 is less than fail-under=42.88" assert expected == self.last_line_squeezed(out) - def test_report_99p9_is_not_ok(self): + def test_report_99p9_is_not_ok(self) -> None: # A file with 99.9% coverage: self.make_file("ninety_nine_plus.py", "a = 1\n" + @@ -1067,7 +1071,7 @@ class FailUnderTest(CoverageTest): class FailUnderNoFilesTest(CoverageTest): """Test that nothing to report results in an error exit status.""" - def test_report(self): + def test_report(self) -> None: self.make_file(".coveragerc", "[report]\nfail_under = 99\n") st, out = self.run_command_status("coverage report") assert 'No data to report.' in out @@ -1076,7 +1080,7 @@ class FailUnderNoFilesTest(CoverageTest): class FailUnderEmptyFilesTest(CoverageTest): """Test that empty files produce the proper fail_under exit status.""" - def test_report(self): + def test_report(self) -> None: self.make_file(".coveragerc", "[report]\nfail_under = 99\n") self.make_file("empty.py", "") st, _ = self.run_command_status("coverage run empty.py") @@ -1101,12 +1105,12 @@ class YankedDirectoryTest(CoverageTest): print(sys.argv[1]) """ - def test_removing_directory(self): + def test_removing_directory(self) -> None: self.make_file("bug806.py", self.BUG_806) out = self.run_command("coverage run bug806.py noerror") assert out == "noerror\n" - def test_removing_directory_with_error(self): + def test_removing_directory_with_error(self) -> None: self.make_file("bug806.py", self.BUG_806) out = self.run_command("coverage run bug806.py") path = python_reported_file('bug806.py') @@ -1125,7 +1129,7 @@ class YankedDirectoryTest(CoverageTest): class ProcessStartupTest(CoverageTest): """Test that we can measure coverage in sub-processes.""" - def setUp(self): + def setUp(self) -> None: super().setUp() # Main will run sub.py @@ -1141,7 +1145,7 @@ class ProcessStartupTest(CoverageTest): f.close() """) - def test_subprocess_with_pth_files(self): + def test_subprocess_with_pth_files(self) -> None: # An existing data file should not be read when a subprocess gets # measured automatically. Create the data file here with bogus data in # it. @@ -1165,7 +1169,7 @@ class ProcessStartupTest(CoverageTest): data.read() assert line_counts(data)['sub.py'] == 3 - def test_subprocess_with_pth_files_and_parallel(self): + def test_subprocess_with_pth_files_and_parallel(self) -> None: # https://github.com/nedbat/coveragepy/issues/492 self.make_file("coverage.ini", """\ [run] @@ -1212,7 +1216,7 @@ class ProcessStartupWithSourceTest(CoverageTest): @pytest.mark.parametrize("dashm", ["-m", ""]) @pytest.mark.parametrize("package", ["pkg", ""]) @pytest.mark.parametrize("source", ["main", "sub"]) - def test_pth_and_source_work_together(self, dashm, package, source): + def test_pth_and_source_work_together(self, dashm: str, package: str, source: str) -> None: """Run the test for a particular combination of factors. The arguments are all strings: @@ -1227,14 +1231,14 @@ class ProcessStartupWithSourceTest(CoverageTest): ``--source`` argument. """ - def fullname(modname): + def fullname(modname: str) -> str: """What is the full module name for `modname` for this test?""" if package and dashm: return '.'.join((package, modname)) else: return modname - def path(basename): + def path(basename: str) -> str: """Where should `basename` be created for this test?""" return os.path.join(package, basename) diff --git a/tests/test_report.py b/tests/test_report.py index 1e7c0762..3d87b514 100644 --- a/tests/test_report.py +++ b/tests/test_report.py @@ -3,10 +3,16 @@ """Tests for helpers in report.py""" +from __future__ import annotations + +from typing import IO, Iterable, List, Optional + import pytest from coverage.exceptions import CoverageException from coverage.report import render_report +from coverage.types import TMorf + from tests.coveragetest import CoverageTest @@ -15,42 +21,45 @@ class FakeReporter: report_type = "fake report file" - def __init__(self, output="", error=False): + def __init__(self, output: str = "", error: bool = False) -> None: self.output = output self.error = error - self.morfs = None + self.morfs: Optional[Iterable[TMorf]] = None - def report(self, morfs, outfile): + def report(self, morfs: Optional[Iterable[TMorf]], outfile: IO[str]) -> float: """Fake.""" self.morfs = morfs outfile.write(self.output) if self.error: raise CoverageException("You asked for it!") + return 17.25 class RenderReportTest(CoverageTest): """Tests of render_report.""" - def test_stdout(self): + def test_stdout(self) -> None: fake = FakeReporter(output="Hello!\n") - msgs = [] - render_report("-", fake, [pytest, "coverage"], msgs.append) + msgs: List[str] = [] + res = render_report("-", fake, [pytest, "coverage"], msgs.append) + assert res == 17.25 assert fake.morfs == [pytest, "coverage"] assert self.stdout() == "Hello!\n" assert not msgs - def test_file(self): + def test_file(self) -> None: fake = FakeReporter(output="Gréètings!\n") - msgs = [] - render_report("output.txt", fake, [], msgs.append) + msgs: List[str] = [] + res = render_report("output.txt", fake, [], msgs.append) + assert res == 17.25 assert self.stdout() == "" with open("output.txt", "rb") as f: assert f.read().rstrip() == b"Gr\xc3\xa9\xc3\xa8tings!" assert msgs == ["Wrote fake report file to output.txt"] - def test_exception(self): + def test_exception(self) -> None: fake = FakeReporter(error=True) - msgs = [] + msgs: List[str] = [] with pytest.raises(CoverageException, match="You asked for it!"): render_report("output.txt", fake, [], msgs.append) assert self.stdout() == "" diff --git a/tests/test_results.py b/tests/test_results.py index 41f3dc40..f2a5ae83 100644 --- a/tests/test_results.py +++ b/tests/test_results.py @@ -3,12 +3,17 @@ """Tests for coverage.py's results analysis.""" +from __future__ import annotations + import math +from typing import Dict, Iterable, List, Tuple, cast + import pytest from coverage.exceptions import ConfigError from coverage.results import format_lines, Numbers, should_fail_under +from coverage.types import TLineNo from tests.coveragetest import CoverageTest @@ -18,14 +23,14 @@ class NumbersTest(CoverageTest): run_in_temp_dir = False - def test_basic(self): + def test_basic(self) -> None: n1 = Numbers(n_files=1, n_statements=200, n_missing=20) assert n1.n_statements == 200 assert n1.n_executed == 180 assert n1.n_missing == 20 assert n1.pc_covered == 90 - def test_addition(self): + def test_addition(self) -> None: n1 = Numbers(n_files=1, n_statements=200, n_missing=20) n2 = Numbers(n_files=1, n_statements=10, n_missing=8) n3 = n1 + n2 @@ -35,10 +40,10 @@ class NumbersTest(CoverageTest): assert n3.n_missing == 28 assert math.isclose(n3.pc_covered, 86.666666666) - def test_sum(self): + def test_sum(self) -> None: n1 = Numbers(n_files=1, n_statements=200, n_missing=20) n2 = Numbers(n_files=1, n_statements=10, n_missing=8) - n3 = sum([n1, n2]) + n3 = cast(Numbers, sum([n1, n2])) assert n3.n_files == 2 assert n3.n_statements == 210 assert n3.n_executed == 182 @@ -55,7 +60,7 @@ class NumbersTest(CoverageTest): (dict(precision=1, n_files=1, n_statements=10000, n_missing=9999), "0.1"), (dict(precision=1, n_files=1, n_statements=10000, n_missing=10000), "0.0"), ]) - def test_pc_covered_str(self, kwargs, res): + def test_pc_covered_str(self, kwargs: Dict[str, int], res: str) -> None: assert Numbers(**kwargs).pc_covered_str == res @pytest.mark.parametrize("prec, pc, res", [ @@ -64,7 +69,7 @@ class NumbersTest(CoverageTest): (0, 99.995, "99"), (2, 99.99995, "99.99"), ]) - def test_display_covered(self, prec, pc, res): + def test_display_covered(self, prec: int, pc: float, res: str) -> None: assert Numbers(precision=prec).display_covered(pc) == res @pytest.mark.parametrize("prec, width", [ @@ -72,10 +77,10 @@ class NumbersTest(CoverageTest): (1, 5), # 100.0 (4, 8), # 100.0000 ]) - def test_pc_str_width(self, prec, width): + def test_pc_str_width(self, prec: int, width: int) -> None: assert Numbers(precision=prec).pc_str_width() == width - def test_covered_ratio(self): + def test_covered_ratio(self) -> None: n = Numbers(n_files=1, n_statements=200, n_missing=47) assert n.ratio_covered == (153, 200) @@ -111,11 +116,11 @@ class NumbersTest(CoverageTest): (99.999, 100, 2, True), (99.999, 100, 3, True), ]) -def test_should_fail_under(total, fail_under, precision, result): +def test_should_fail_under(total: float, fail_under: float, precision: int, result: bool) -> None: assert should_fail_under(float(total), float(fail_under), precision) == result -def test_should_fail_under_invalid_value(): +def test_should_fail_under_invalid_value() -> None: with pytest.raises(ConfigError, match=r"fail_under=101"): should_fail_under(100.0, 101, 0) @@ -129,7 +134,11 @@ def test_should_fail_under_invalid_value(): ([1, 2, 3, 4, 5], [], ""), ([1, 2, 3, 4, 5], [4], "4"), ]) -def test_format_lines(statements, lines, result): +def test_format_lines( + statements: Iterable[TLineNo], + lines: Iterable[TLineNo], + result: str, +) -> None: assert format_lines(statements, lines) == result @@ -153,5 +162,10 @@ def test_format_lines(statements, lines, result): "1-2, 3->4, 99, 102-104" ), ]) -def test_format_lines_with_arcs(statements, lines, arcs, result): +def test_format_lines_with_arcs( + statements: Iterable[TLineNo], + lines: Iterable[TLineNo], + arcs: Iterable[Tuple[TLineNo, List[TLineNo]]], + result: str, +) -> None: assert format_lines(statements, lines, arcs) == result diff --git a/tests/test_setup.py b/tests/test_setup.py index 5468e3bf..a7a97d1f 100644 --- a/tests/test_setup.py +++ b/tests/test_setup.py @@ -3,8 +3,12 @@ """Tests of miscellaneous stuff.""" +from __future__ import annotations + import sys +from typing import List, cast + import coverage from tests.coveragetest import CoverageTest @@ -15,12 +19,12 @@ class SetupPyTest(CoverageTest): run_in_temp_dir = False - def setUp(self): + def setUp(self) -> None: super().setUp() # Force the most restrictive interpretation. self.set_environ('LC_ALL', 'C') - def test_metadata(self): + def test_metadata(self) -> None: status, output = self.run_command_status( "python setup.py --description --version --url --author" ) @@ -31,19 +35,19 @@ class SetupPyTest(CoverageTest): assert "github.com/nedbat/coveragepy" in out[2] assert "Ned Batchelder" in out[3] - def test_more_metadata(self): + def test_more_metadata(self) -> None: # Let's be sure we pick up our own setup.py # CoverageTest restores the original sys.path for us. sys.path.insert(0, '') from setup import setup_args - classifiers = setup_args['classifiers'] + classifiers = cast(List[str], setup_args['classifiers']) assert len(classifiers) > 7 assert classifiers[-1].startswith("Development Status ::") assert "Programming Language :: Python :: %d" % sys.version_info[:1] in classifiers assert "Programming Language :: Python :: %d.%d" % sys.version_info[:2] in classifiers - long_description = setup_args['long_description'].splitlines() + long_description = cast(str, setup_args['long_description']).splitlines() assert len(long_description) > 7 assert long_description[0].strip() != "" assert long_description[-1].strip() != "" @@ -105,8 +105,10 @@ setenv = T2=tests/test_annotate.py tests/test_api.py tests/test_arcs.py tests/test_cmdline.py tests/test_collector.py tests/test_concurrency.py T3=tests/test_config.py tests/test_context.py tests/test_coverage.py tests/test_data.py tests/test_debug.py tests/test_execfile.py T4=tests/test_filereporter.py tests/test_files.py tests/test_goldtest.py tests/test_html.py tests/test_json.py tests/test_lcov.py - T5=tests/test_misc.py tests/test_mixins.py tests/test_numbits.py tests/test_oddball.py tests/test_python.py tests/test_summary.py tests/test_xml.py - TYPEABLE={env:C1} {env:C2} {env:C3} {env:C4} {env:C5} {env:C6} {env:T1} {env:T2} {env:T3} {env:T4} {env:T5} + T5=tests/test_misc.py tests/test_mixins.py tests/test_numbits.py tests/test_oddball.py tests/test_parser.py tests/test_phystokens.py + T6=tests/test_process.py tests/test_python.py tests/test_report.py tests/test_results.py tests/test_setup.py tests/test_summary.py tests/test_xml.py + # not done yet: test_plugins.py + TYPEABLE={env:C1} {env:C2} {env:C3} {env:C4} {env:C5} {env:C6} {env:T1} {env:T2} {env:T3} {env:T4} {env:T5} {env:T6} commands = # PYVERSIONS |