summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNed Batchelder <ned@nedbatchelder.com>2022-12-31 23:33:31 -0500
committerNed Batchelder <ned@nedbatchelder.com>2022-12-31 23:34:12 -0500
commita3f3841b746a1789ff8f7fea0cc0715c45770996 (patch)
tree36850d9d8b3252db847e0fd874601f3e59ae9055
parent09f9188e826f900198d638ee3c42b27bca29597d (diff)
downloadpython-coveragepy-git-a3f3841b746a1789ff8f7fea0cc0715c45770996.tar.gz
mypy: add cmdline.py and test_cmdline.py
-rw-r--r--coverage/cmdline.py84
-rw-r--r--coverage/control.py4
-rw-r--r--coverage/types.py2
-rw-r--r--tests/coveragetest.py4
-rw-r--r--tests/test_cmdline.py176
-rw-r--r--tox.ini11
6 files changed, 166 insertions, 115 deletions
diff --git a/coverage/cmdline.py b/coverage/cmdline.py
index b15a66f7..4a00105a 100644
--- a/coverage/cmdline.py
+++ b/coverage/cmdline.py
@@ -12,6 +12,8 @@ import sys
import textwrap
import traceback
+from typing import cast, Any, List, NoReturn, Optional, Tuple
+
import coverage
from coverage import Coverage
from coverage import env
@@ -235,8 +237,9 @@ class CoverageOptionParser(optparse.OptionParser):
"""
- def __init__(self, *args, **kwargs):
- super().__init__(add_help_option=False, *args, **kwargs)
+ def __init__(self, *args: Any, **kwargs: Any) -> None:
+ kwargs["add_help_option"] = False
+ super().__init__(*args, **kwargs)
self.set_defaults(
# Keep these arguments alphabetized by their names.
action=None,
@@ -278,19 +281,19 @@ class CoverageOptionParser(optparse.OptionParser):
"""Used to stop the optparse error handler ending the process."""
pass
- def parse_args_ok(self, args=None, options=None):
+ def parse_args_ok(self, args: List[str]) -> Tuple[bool, Optional[optparse.Values], List[str]]:
"""Call optparse.parse_args, but return a triple:
(ok, options, args)
"""
try:
- options, args = super().parse_args(args, options)
+ options, args = super().parse_args(args)
except self.OptionParserError:
- return False, None, None
+ return False, None, []
return True, options, args
- def error(self, msg):
+ def error(self, msg: str) -> NoReturn:
"""Override optparse.error so sys.exit doesn't get called."""
show_help(msg)
raise self.OptionParserError
@@ -299,7 +302,7 @@ class CoverageOptionParser(optparse.OptionParser):
class GlobalOptionParser(CoverageOptionParser):
"""Command-line parser for coverage.py global option arguments."""
- def __init__(self):
+ def __init__(self) -> None:
super().__init__()
self.add_options([
@@ -311,14 +314,19 @@ class GlobalOptionParser(CoverageOptionParser):
class CmdOptionParser(CoverageOptionParser):
"""Parse one of the new-style commands for coverage.py."""
- def __init__(self, action, options, defaults=None, usage=None, description=None):
+ def __init__(
+ self,
+ action: str,
+ options: List[optparse.Option],
+ description: str,
+ usage: Optional[str]=None,
+ ):
"""Create an OptionParser for a coverage.py command.
`action` is the slug to put into `options.action`.
`options` is a list of Option's for the command.
- `defaults` is a dict of default value for options.
- `usage` is the usage string to display in help.
`description` is the description of the command, for the help text.
+ `usage` is the usage string to display in help.
"""
if usage:
@@ -327,18 +335,18 @@ class CmdOptionParser(CoverageOptionParser):
usage=usage,
description=description,
)
- self.set_defaults(action=action, **(defaults or {}))
+ self.set_defaults(action=action)
self.add_options(options)
self.cmd = action
- def __eq__(self, other):
+ def __eq__(self, other: str) -> bool: # type: ignore[override]
# A convenience equality, so that I can put strings in unit test
# results, and they will compare equal to objects.
return (other == f"<CmdOptionParser:{self.cmd}>")
- __hash__ = None # This object doesn't need to be hashed.
+ __hash__ = None # type: ignore[assignment]
- def get_prog_name(self):
+ def get_prog_name(self) -> str:
"""Override of an undocumented function in optparse.OptionParser."""
program_name = super().get_prog_name()
@@ -540,7 +548,11 @@ COMMANDS = {
}
-def show_help(error=None, topic=None, parser=None):
+def show_help(
+ error: Optional[str]=None,
+ topic: Optional[str]=None,
+ parser: Optional[optparse.OptionParser]=None,
+) -> None:
"""Display an error message, or the named topic."""
assert error or topic or parser
@@ -573,6 +585,7 @@ def show_help(error=None, topic=None, parser=None):
print(parser.format_help().strip())
print()
else:
+ assert topic is not None
help_msg = textwrap.dedent(HELP_TOPICS.get(topic, '')).strip()
if help_msg:
print(help_msg.format(**help_params))
@@ -587,11 +600,11 @@ OK, ERR, FAIL_UNDER = 0, 1, 2
class CoverageScript:
"""The command-line interface to coverage.py."""
- def __init__(self):
+ def __init__(self) -> None:
self.global_option = False
- self.coverage = None
+ self.coverage: Coverage
- def command_line(self, argv):
+ def command_line(self, argv: List[str]) -> int:
"""The bulk of the command line interface to coverage.py.
`argv` is the argument list to process.
@@ -606,6 +619,7 @@ class CoverageScript:
# The command syntax we parse depends on the first argument. Global
# switch syntax always starts with an option.
+ parser: Optional[optparse.OptionParser]
self.global_option = argv[0].startswith('-')
if self.global_option:
parser = GlobalOptionParser()
@@ -619,6 +633,7 @@ class CoverageScript:
ok, options, args = parser.parse_args_ok(argv)
if not ok:
return ERR
+ assert options is not None
# Handle help and version.
if self.do_help(options, args, parser):
@@ -740,8 +755,8 @@ class CoverageScript:
if options.precision is not None:
self.coverage.set_option("report:precision", options.precision)
- fail_under = self.coverage.get_option("report:fail_under")
- precision = self.coverage.get_option("report:precision")
+ fail_under = cast(float, self.coverage.get_option("report:fail_under"))
+ precision = cast(int, self.coverage.get_option("report:precision"))
if should_fail_under(total, fail_under, precision):
msg = "total of {total} is less than fail-under={fail_under:.{p}f}".format(
total=Numbers(precision=precision).display_covered(total),
@@ -753,7 +768,12 @@ class CoverageScript:
return OK
- def do_help(self, options, args, parser):
+ def do_help(
+ self,
+ options: optparse.Values,
+ args: List[str],
+ parser: optparse.OptionParser,
+ ) -> bool:
"""Deal with help requests.
Return True if it handled the request, False if not.
@@ -770,9 +790,9 @@ class CoverageScript:
if options.action == "help":
if args:
for a in args:
- parser = COMMANDS.get(a)
- if parser:
- show_help(parser=parser)
+ parser_maybe = COMMANDS.get(a)
+ if parser_maybe is not None:
+ show_help(parser=parser_maybe)
else:
show_help(topic=a)
else:
@@ -786,7 +806,7 @@ class CoverageScript:
return False
- def do_run(self, options, args):
+ def do_run(self, options: optparse.Values, args: List[str]) -> int:
"""Implementation of 'coverage run'."""
if not args:
@@ -794,7 +814,7 @@ class CoverageScript:
# Specified -m with nothing else.
show_help("No module specified for -m")
return ERR
- command_line = self.coverage.get_option("run:command_line")
+ command_line = cast(str, self.coverage.get_option("run:command_line"))
if command_line is not None:
args = shlex.split(command_line)
if args and args[0] in {"-m", "--module"}:
@@ -845,7 +865,7 @@ class CoverageScript:
return OK
- def do_debug(self, args):
+ def do_debug(self, args: List[str]) -> int:
"""Implementation of 'coverage debug'."""
if not args:
@@ -878,7 +898,7 @@ class CoverageScript:
return OK
-def unshell_list(s):
+def unshell_list(s: str) -> Optional[List[str]]:
"""Turn a command-line argument into a list."""
if not s:
return None
@@ -892,7 +912,7 @@ def unshell_list(s):
return s.split(',')
-def unglob_args(args):
+def unglob_args(args: List[str]) -> List[str]:
"""Interpret shell wildcards for platforms that need it."""
if env.WINDOWS:
globbed = []
@@ -938,7 +958,7 @@ HELP_TOPICS = {
}
-def main(argv=None):
+def main(argv: Optional[List[str]]=None) -> Optional[int]:
"""The main entry point to coverage.py.
This is installed as the script entry point.
@@ -976,7 +996,9 @@ if _profile: # pragma: debugging
from ox_profile.core.launchers import SimpleLauncher # pylint: disable=import-error
original_main = main
- def main(argv=None): # pylint: disable=function-redefined
+ def main( # pylint: disable=function-redefined
+ argv: Optional[List[str]]=None,
+ ) -> Optional[int]:
"""A wrapper around main that profiles."""
profiler = SimpleLauncher.launch()
try:
diff --git a/coverage/control.py b/coverage/control.py
index 5aa312d3..4306fea7 100644
--- a/coverage/control.py
+++ b/coverage/control.py
@@ -111,7 +111,7 @@ class Coverage(TConfigurable):
def __init__( # pylint: disable=too-many-arguments
self,
- data_file: Optional[str]=DEFAULT_DATAFILE, # type: ignore[assignment]
+ data_file: Optional[Union[str, DefaultValue]]=DEFAULT_DATAFILE,
data_suffix: Optional[Union[str, bool]]=None,
cover_pylib: Optional[bool]=None,
auto_data: bool=False,
@@ -219,7 +219,7 @@ class Coverage(TConfigurable):
# data_file=None means no disk file at all. data_file missing means
# use the value from the config file.
self._no_disk = data_file is None
- if data_file is DEFAULT_DATAFILE:
+ if isinstance(data_file, DefaultValue):
data_file = None
# This is injectable by tests.
diff --git a/coverage/types.py b/coverage/types.py
index 416b0b5d..79cf5d3a 100644
--- a/coverage/types.py
+++ b/coverage/types.py
@@ -25,7 +25,7 @@ TCovKwargs = Any
## Configuration
# One value read from a config file.
-TConfigValue = Optional[Union[bool, int, str, List[str]]]
+TConfigValue = Optional[Union[bool, int, float, str, List[str]]]
# An entire config section, mapping option names to values.
TConfigSection = Dict[str, TConfigValue]
diff --git a/tests/coveragetest.py b/tests/coveragetest.py
index e718dd31..47a124c1 100644
--- a/tests/coveragetest.py
+++ b/tests/coveragetest.py
@@ -310,7 +310,7 @@ class CoverageTest(
assert age.total_seconds() >= 0, msg
assert age.total_seconds() <= seconds, msg
- def command_line(self, args, ret=OK):
+ def command_line(self, args: str, ret: int=OK) -> None:
"""Run `args` through the command line.
Use this when you want to run the full coverage machinery, but in the
@@ -467,7 +467,7 @@ class UsingModulesMixin:
sys.path.append(nice_file(TESTS_DIR, "zipmods.zip"))
-def command_line(args):
+def command_line(args: str) -> int:
"""Run `args` through the CoverageScript command line.
Returns the return code from CoverageScript.command_line.
diff --git a/tests/test_cmdline.py b/tests/test_cmdline.py
index 67899b75..85e99ad5 100644
--- a/tests/test_cmdline.py
+++ b/tests/test_cmdline.py
@@ -10,6 +10,8 @@ import sys
import textwrap
from unittest import mock
+from typing import Any, List, Mapping, Optional, Tuple
+
import pytest
import coverage
@@ -18,6 +20,7 @@ from coverage import env
from coverage.control import DEFAULT_DATAFILE
from coverage.config import CoverageConfig
from coverage.exceptions import _ExceptionDuringRun
+from coverage.types import TConfigValue
from coverage.version import __url__
from tests.coveragetest import CoverageTest, OK, ERR, command_line
@@ -67,7 +70,7 @@ class BaseCmdLineTest(CoverageTest):
DEFAULT_KWARGS = {name: kw for name, _, kw in _defaults.mock_calls}
- def model_object(self):
+ def model_object(self) -> mock.Mock:
"""Return a Mock suitable for use in CoverageScript."""
mk = mock.Mock()
@@ -90,7 +93,11 @@ class BaseCmdLineTest(CoverageTest):
# Global names in cmdline.py that will be mocked during the tests.
MOCK_GLOBALS = ['Coverage', 'PyRunner', 'show_help']
- def mock_command_line(self, args, options=None):
+ def mock_command_line(
+ self,
+ args: str,
+ options: Optional[Mapping[str, TConfigValue]]=None,
+ ) -> Tuple[mock.Mock, int]:
"""Run `args` through the command line, with a Mock.
`options` is a dict of names and values to pass to `set_option`.
@@ -118,7 +125,13 @@ class BaseCmdLineTest(CoverageTest):
return mk, ret
- def cmd_executes(self, args, code, ret=OK, options=None):
+ def cmd_executes(
+ self,
+ args: str,
+ code: str,
+ ret: int=OK,
+ options: Optional[Mapping[str, TConfigValue]]=None,
+ ) -> None:
"""Assert that the `args` end up executing the sequence in `code`."""
called, status = self.mock_command_line(args, options=options)
assert status == ret, f"Wrong status: got {status!r}, wanted {ret!r}"
@@ -140,14 +153,14 @@ class BaseCmdLineTest(CoverageTest):
self.assert_same_mock_calls(expected, called)
- def cmd_executes_same(self, args1, args2):
+ def cmd_executes_same(self, args1: str, args2: str) -> None:
"""Assert that the `args1` executes the same as `args2`."""
m1, r1 = self.mock_command_line(args1)
m2, r2 = self.mock_command_line(args2)
assert r1 == r2
self.assert_same_mock_calls(m1, m2)
- def assert_same_mock_calls(self, m1, m2):
+ def assert_same_mock_calls(self, m1: mock.Mock, m2: mock.Mock) -> None:
"""Assert that `m1.mock_calls` and `m2.mock_calls` are the same."""
# Use a real equality comparison, but if it fails, use a nicer assert
# so we can tell what's going on. We have to use the real == first due
@@ -157,7 +170,13 @@ class BaseCmdLineTest(CoverageTest):
pp2 = pprint.pformat(m2.mock_calls)
assert pp1+'\n' == pp2+'\n'
- def cmd_help(self, args, help_msg=None, topic=None, ret=ERR):
+ def cmd_help(
+ self,
+ args: str,
+ help_msg: Optional[str]=None,
+ topic: Optional[str]=None,
+ ret: int=ERR,
+ ) -> None:
"""Run a command line, and check that it prints the right help.
Only the last function call in the mock is checked, which should be the
@@ -174,7 +193,7 @@ class BaseCmdLineTest(CoverageTest):
class BaseCmdLineTestTest(BaseCmdLineTest):
"""Tests that our BaseCmdLineTest helpers work."""
- def test_cmd_executes_same(self):
+ def test_cmd_executes_same(self) -> None:
# All the other tests here use self.cmd_executes_same in successful
# ways, so here we just check that it fails.
with pytest.raises(AssertionError):
@@ -184,7 +203,7 @@ class BaseCmdLineTestTest(BaseCmdLineTest):
class CmdLineTest(BaseCmdLineTest):
"""Tests of the coverage.py command line."""
- def test_annotate(self):
+ def test_annotate(self) -> None:
# coverage annotate [-d DIR] [-i] [--omit DIR,...] [FILE1 FILE2 ...]
self.cmd_executes("annotate", """\
cov = Coverage()
@@ -222,7 +241,7 @@ class CmdLineTest(BaseCmdLineTest):
cov.annotate(morfs=["mod1", "mod2", "mod3"])
""")
- def test_combine(self):
+ def test_combine(self) -> None:
# coverage combine with args
self.cmd_executes("combine datadir1", """\
cov = Coverage()
@@ -259,7 +278,7 @@ class CmdLineTest(BaseCmdLineTest):
cov.save()
""")
- def test_combine_doesnt_confuse_options_with_args(self):
+ def test_combine_doesnt_confuse_options_with_args(self) -> None:
# https://github.com/nedbat/coveragepy/issues/385
self.cmd_executes("combine --rcfile cov.ini", """\
cov = Coverage(config_file='cov.ini')
@@ -277,23 +296,23 @@ class CmdLineTest(BaseCmdLineTest):
("debug foo", "Don't know what you mean by 'foo'"),
("debug sys config", "Only one topic at a time, please"),
])
- def test_debug(self, cmd, output):
+ def test_debug(self, cmd: str, output: str) -> None:
self.cmd_help(cmd, output)
- def test_debug_sys(self):
+ def test_debug_sys(self) -> None:
self.command_line("debug sys")
out = self.stdout()
assert "version:" in out
assert "data_file:" in out
- def test_debug_config(self):
+ def test_debug_config(self) -> None:
self.command_line("debug config")
out = self.stdout()
assert "cover_pylib:" in out
assert "skip_covered:" in out
assert "skip_empty:" in out
- def test_debug_pybehave(self):
+ def test_debug_pybehave(self) -> None:
self.command_line("debug pybehave")
out = self.stdout()
assert " CPYTHON:" in out
@@ -303,7 +322,7 @@ class CmdLineTest(BaseCmdLineTest):
vtuple = ast.literal_eval(pyversion.partition(":")[-1].strip())
assert vtuple[:5] == sys.version_info
- def test_debug_premain(self):
+ def test_debug_premain(self) -> None:
self.command_line("debug premain")
out = self.stdout()
# ... many lines ...
@@ -317,7 +336,7 @@ class CmdLineTest(BaseCmdLineTest):
assert re.search(r"(?m)^\s+command_line : .*[/\\]coverage[/\\]cmdline.py:\d+$", out)
assert re.search(r"(?m)^\s+do_debug : .*[/\\]coverage[/\\]cmdline.py:\d+$", out)
- def test_erase(self):
+ def test_erase(self) -> None:
# coverage erase
self.cmd_executes("erase", """\
cov = Coverage()
@@ -328,23 +347,23 @@ class CmdLineTest(BaseCmdLineTest):
cov.erase()
""")
- def test_version(self):
+ def test_version(self) -> None:
# coverage --version
self.cmd_help("--version", topic="version", ret=OK)
- def test_help_option(self):
+ def test_help_option(self) -> None:
# coverage -h
self.cmd_help("-h", topic="help", ret=OK)
self.cmd_help("--help", topic="help", ret=OK)
- def test_help_command(self):
+ def test_help_command(self) -> None:
self.cmd_executes("help", "show_help(topic='help')")
- def test_cmd_help(self):
+ def test_cmd_help(self) -> None:
self.cmd_executes("run --help", "show_help(parser='<CmdOptionParser:run>')")
self.cmd_executes_same("help run", "run --help")
- def test_html(self):
+ def test_html(self) -> None:
# coverage html -d DIR [-i] [--omit DIR,...] [FILE1 FILE2 ...]
self.cmd_executes("html", """\
cov = Coverage()
@@ -402,7 +421,7 @@ class CmdLineTest(BaseCmdLineTest):
cov.html_report()
""")
- def test_json(self):
+ def test_json(self) -> None:
# coverage json [-i] [--omit DIR,...] [FILE1 FILE2 ...]
self.cmd_executes("json", """\
cov = Coverage()
@@ -465,7 +484,7 @@ class CmdLineTest(BaseCmdLineTest):
cov.json_report()
""")
- def test_lcov(self):
+ def test_lcov(self) -> None:
# coverage lcov [-i] [--omit DIR,...] [FILE1 FILE2 ...]
self.cmd_executes("lcov", """\
cov = Coverage()
@@ -508,7 +527,7 @@ class CmdLineTest(BaseCmdLineTest):
cov.lcov_report()
""")
- def test_report(self):
+ def test_report(self) -> None:
# coverage report [-m] [-i] [-o DIR,...] [FILE1 FILE2 ...]
self.cmd_executes("report", """\
cov = Coverage()
@@ -591,7 +610,7 @@ class CmdLineTest(BaseCmdLineTest):
cov.report(output_format="markdown")
""")
- def test_run(self):
+ def test_run(self) -> None:
# coverage run [-p] [-L] [--timid] MODULE.py [ARG1 ARG2 ...]
# run calls coverage.erase first.
@@ -726,7 +745,7 @@ class CmdLineTest(BaseCmdLineTest):
cov.save()
""")
- def test_multiprocessing_needs_config_file(self):
+ def test_multiprocessing_needs_config_file(self) -> None:
# You can't use command-line args to add options to multiprocessing
# runs, since they won't make it to the subprocesses. You need to use a
# config file.
@@ -736,7 +755,7 @@ class CmdLineTest(BaseCmdLineTest):
assert msg in err
assert "Remove --branch from the command line." in err
- def test_run_debug(self):
+ def test_run_debug(self) -> None:
self.cmd_executes("run --debug=opt1 foo.py", """\
cov = Coverage(debug=["opt1"])
runner = PyRunner(['foo.py'], as_module=False)
@@ -756,7 +775,7 @@ class CmdLineTest(BaseCmdLineTest):
cov.save()
""")
- def test_run_module(self):
+ def test_run_module(self) -> None:
self.cmd_executes("run -m mymodule", """\
cov = Coverage()
runner = PyRunner(['mymodule'], as_module=True)
@@ -786,11 +805,11 @@ class CmdLineTest(BaseCmdLineTest):
""")
self.cmd_executes_same("run -m mymodule", "run --module mymodule")
- def test_run_nothing(self):
+ def test_run_nothing(self) -> None:
self.command_line("run", ret=ERR)
assert "Nothing to do" in self.stderr()
- def test_run_from_config(self):
+ def test_run_from_config(self) -> None:
options = {"run:command_line": "myprog.py a 123 'a quoted thing' xyz"}
self.cmd_executes("run", """\
cov = Coverage()
@@ -804,7 +823,7 @@ class CmdLineTest(BaseCmdLineTest):
options=options,
)
- def test_run_module_from_config(self):
+ def test_run_module_from_config(self) -> None:
self.cmd_executes("run", """\
cov = Coverage()
runner = PyRunner(['mymodule', 'thing1', 'thing2'], as_module=True)
@@ -817,7 +836,7 @@ class CmdLineTest(BaseCmdLineTest):
options={"run:command_line": "-m mymodule thing1 thing2"},
)
- def test_run_from_config_but_empty(self):
+ def test_run_from_config_but_empty(self) -> None:
self.cmd_executes("run", """\
cov = Coverage()
show_help('Nothing to do.')
@@ -826,7 +845,7 @@ class CmdLineTest(BaseCmdLineTest):
options={"run:command_line": ""},
)
- def test_run_dashm_only(self):
+ def test_run_dashm_only(self) -> None:
self.cmd_executes("run -m", """\
cov = Coverage()
show_help('No module specified for -m')
@@ -841,11 +860,11 @@ class CmdLineTest(BaseCmdLineTest):
options={"run:command_line": "myprog.py"}
)
- def test_cant_append_parallel(self):
+ def test_cant_append_parallel(self) -> None:
self.command_line("run --append --parallel-mode foo.py", ret=ERR)
assert "Can't append to data files in parallel mode." in self.stderr()
- def test_xml(self):
+ def test_xml(self) -> None:
# coverage xml [-i] [--omit DIR,...] [FILE1 FILE2 ...]
self.cmd_executes("xml", """\
cov = Coverage()
@@ -898,10 +917,10 @@ class CmdLineTest(BaseCmdLineTest):
cov.xml_report()
""")
- def test_no_arguments_at_all(self):
+ def test_no_arguments_at_all(self) -> None:
self.cmd_help("", topic="minimum_help", ret=OK)
- def test_bad_command(self):
+ def test_bad_command(self) -> None:
self.cmd_help("xyzzy", "Unknown command: 'xyzzy'")
@@ -910,7 +929,7 @@ class CmdLineWithFilesTest(BaseCmdLineTest):
run_in_temp_dir = True
- def test_debug_data(self):
+ def test_debug_data(self) -> None:
data = self.make_data_file(
lines={
"file1.py": range(1, 18),
@@ -929,7 +948,7 @@ class CmdLineWithFilesTest(BaseCmdLineTest):
file2.py: 23 lines
""")
- def test_debug_data_with_no_data_file(self):
+ def test_debug_data_with_no_data_file(self) -> None:
data = self.make_data_file()
self.command_line("debug data")
assert self.stdout() == textwrap.dedent(f"""\
@@ -938,7 +957,7 @@ class CmdLineWithFilesTest(BaseCmdLineTest):
No data collected: file doesn't exist
""")
- def test_debug_combinable_data(self):
+ def test_debug_combinable_data(self) -> None:
data1 = self.make_data_file(lines={"file1.py": range(1, 18), "file2.py": [1]})
data2 = self.make_data_file(suffix="123", lines={"file2.py": range(1, 10)})
@@ -961,13 +980,13 @@ class CmdLineWithFilesTest(BaseCmdLineTest):
class CmdLineStdoutTest(BaseCmdLineTest):
"""Test the command line with real stdout output."""
- def test_minimum_help(self):
+ def test_minimum_help(self) -> None:
self.command_line("")
out = self.stdout()
assert "Code coverage for Python" in out
assert out.count("\n") < 4
- def test_version(self):
+ def test_version(self) -> None:
self.command_line("--version")
out = self.stdout()
assert "ersion " in out
@@ -977,7 +996,7 @@ class CmdLineStdoutTest(BaseCmdLineTest):
assert "without C extension" in out
assert out.count("\n") < 4
- def test_help_contains_command_name(self):
+ def test_help_contains_command_name(self) -> None:
# Command name should be present in help output.
fake_command_path = os_sep("lorem/ipsum/dolor")
expected_command_name = "dolor"
@@ -987,7 +1006,7 @@ class CmdLineStdoutTest(BaseCmdLineTest):
out = self.stdout()
assert expected_command_name in out
- def test_help_contains_command_name_from_package(self):
+ def test_help_contains_command_name_from_package(self) -> None:
# Command package name should be present in help output.
#
# When the main module is actually a package's `__main__` module, the resulting command line
@@ -1002,13 +1021,13 @@ class CmdLineStdoutTest(BaseCmdLineTest):
out = self.stdout()
assert expected_command_name in out
- def test_help(self):
+ def test_help(self) -> None:
self.command_line("help")
lines = self.stdout().splitlines()
assert len(lines) > 10
assert lines[-1] == f"Full documentation is at {__url__}"
- def test_cmd_help(self):
+ def test_cmd_help(self) -> None:
self.command_line("help run")
out = self.stdout()
lines = out.splitlines()
@@ -1017,26 +1036,26 @@ class CmdLineStdoutTest(BaseCmdLineTest):
assert len(lines) > 20
assert lines[-1] == f"Full documentation is at {__url__}"
- def test_unknown_topic(self):
+ def test_unknown_topic(self) -> None:
# Should probably be an ERR return, but meh.
self.command_line("help foobar")
lines = self.stdout().splitlines()
assert lines[0] == "Don't know topic 'foobar'"
assert lines[-1] == f"Full documentation is at {__url__}"
- def test_error(self):
+ def test_error(self) -> None:
self.command_line("fooey kablooey", ret=ERR)
err = self.stderr()
assert "fooey" in err
assert "help" in err
- def test_option_error(self):
+ def test_option_error(self) -> None:
self.command_line("run --fooey", ret=ERR)
err = self.stderr()
assert "fooey" in err
assert "help" in err
- def test_doc_url(self):
+ def test_doc_url(self) -> None:
assert __url__.startswith("https://coverage.readthedocs.io")
@@ -1048,7 +1067,7 @@ class CmdMainTest(CoverageTest):
class CoverageScriptStub:
"""A stub for coverage.cmdline.CoverageScript, used by CmdMainTest."""
- def command_line(self, argv):
+ def command_line(self, argv: List[str]) -> int:
"""Stub for command_line, the arg determines what it will do."""
if argv[0] == 'hello':
print("Hello, world!")
@@ -1065,33 +1084,33 @@ class CmdMainTest(CoverageTest):
raise AssertionError(f"Bad CoverageScriptStub: {argv!r}")
return 0
- def setUp(self):
+ def setUp(self) -> None:
super().setUp()
old_CoverageScript = coverage.cmdline.CoverageScript
- coverage.cmdline.CoverageScript = self.CoverageScriptStub
+ coverage.cmdline.CoverageScript = self.CoverageScriptStub # type: ignore
self.addCleanup(setattr, coverage.cmdline, 'CoverageScript', old_CoverageScript)
- def test_normal(self):
+ def test_normal(self) -> None:
ret = coverage.cmdline.main(['hello'])
assert ret == 0
assert self.stdout() == "Hello, world!\n"
- def test_raise(self):
+ def test_raise(self) -> None:
ret = coverage.cmdline.main(['raise'])
assert ret == 1
out, err = self.stdouterr()
assert out == ""
print(err)
- err = err.splitlines(keepends=True)
- assert err[0] == 'Traceback (most recent call last):\n'
- assert ' raise Exception("oh noes!")\n' in err
- assert err[-1] == 'Exception: oh noes!\n'
+ err_parts = err.splitlines(keepends=True)
+ assert err_parts[0] == 'Traceback (most recent call last):\n'
+ assert ' raise Exception("oh noes!")\n' in err_parts
+ assert err_parts[-1] == 'Exception: oh noes!\n'
- def test_internalraise(self):
+ def test_internalraise(self) -> None:
with pytest.raises(ValueError, match="coverage is broken"):
coverage.cmdline.main(['internalraise'])
- def test_exit(self):
+ def test_exit(self) -> None:
ret = coverage.cmdline.main(['exit'])
assert ret == 23
@@ -1099,7 +1118,14 @@ class CmdMainTest(CoverageTest):
class CoverageReportingFake:
"""A fake Coverage.coverage test double for FailUnderTest methods."""
# pylint: disable=missing-function-docstring
- def __init__(self, report_result, html_result=0, xml_result=0, json_report=0, lcov_result=0):
+ def __init__(
+ self,
+ report_result: float,
+ html_result: float=0,
+ xml_result: float=0,
+ json_report: float=0,
+ lcov_result: float=0,
+ ) -> None:
self.config = CoverageConfig()
self.report_result = report_result
self.html_result = html_result
@@ -1107,28 +1133,28 @@ class CoverageReportingFake:
self.json_result = json_report
self.lcov_result = lcov_result
- def set_option(self, optname, optvalue):
+ def set_option(self, optname: str, optvalue: TConfigValue) -> None:
self.config.set_option(optname, optvalue)
- def get_option(self, optname):
+ def get_option(self, optname: str) -> TConfigValue:
return self.config.get_option(optname)
- def load(self):
+ def load(self) -> None:
pass
- def report(self, *args_unused, **kwargs_unused):
+ def report(self, *args_unused: Any, **kwargs_unused: Any) -> float:
return self.report_result
- def html_report(self, *args_unused, **kwargs_unused):
+ def html_report(self, *args_unused: Any, **kwargs_unused: Any) -> float:
return self.html_result
- def xml_report(self, *args_unused, **kwargs_unused):
+ def xml_report(self, *args_unused: Any, **kwargs_unused: Any) -> float:
return self.xml_result
- def json_report(self, *args_unused, **kwargs_unused):
+ def json_report(self, *args_unused: Any, **kwargs_unused: Any) -> float:
return self.json_result
- def lcov_report(self, *args_unused, **kwargs_unused):
+ def lcov_report(self, *args_unused: Any, **kwargs_unused: Any) -> float:
return self.lcov_result
@@ -1161,7 +1187,13 @@ class FailUnderTest(CoverageTest):
# Command-line overrides configuration.
((20, 30, 40, 50, 60), 19, "report --fail-under=21", 2),
])
- def test_fail_under(self, results, fail_under, cmd, ret):
+ def test_fail_under(
+ self,
+ results: Tuple[float, float, float, float, float],
+ fail_under: Optional[float],
+ cmd: str,
+ ret: int,
+ ) -> None:
cov = CoverageReportingFake(*results)
if fail_under is not None:
cov.set_option("report:fail_under", fail_under)
@@ -1175,7 +1207,7 @@ class FailUnderTest(CoverageTest):
(20.12345, "report --fail-under=20.1235 --precision=5", 2,
"Coverage failure: total of 20.12345 is less than fail-under=20.12350\n"),
])
- def test_fail_under_with_precision(self, result, cmd, ret, msg):
+ def test_fail_under_with_precision(self, result: float, cmd: str, ret: int, msg: str) -> None:
cov = CoverageReportingFake(report_result=result)
with mock.patch("coverage.cmdline.Coverage", lambda *a,**kw: cov):
self.command_line(cmd, ret)
diff --git a/tox.ini b/tox.ini
index 10bdf6d3..882b38f9 100644
--- a/tox.ini
+++ b/tox.ini
@@ -96,20 +96,17 @@ deps =
setenv =
{[testenv]setenv}
C__B=coverage/__init__.py coverage/__main__.py coverage/annotate.py coverage/bytecode.py
- C_CE=coverage/config.py coverage/context.py coverage/control.py coverage/data.py coverage/disposition.py coverage/env.py coverage/exceptions.py
+ C_CC=coverage/cmdline.py coverage/config.py coverage/context.py coverage/control.py
+ C_DE=coverage/data.py coverage/disposition.py coverage/env.py coverage/exceptions.py
C_FN=coverage/files.py coverage/inorout.py coverage/jsonreport.py coverage/lcovreport.py coverage/multiproc.py coverage/numbits.py
C_OP=coverage/parser.py coverage/phystokens.py coverage/plugin.py coverage/python.py
C_QZ=coverage/report.py coverage/results.py coverage/sqldata.py coverage/tomlconfig.py coverage/types.py coverage/version.py
- T_AN=tests/test_api.py tests/goldtest.py tests/helpers.py tests/test_html.py
- TYPEABLE={env:C__B} {env:C_CE} {env:C_FN} {env:C_OP} {env:C_QZ} {env:T_AN}
+ T_AN=tests/test_api.py tests/test_cmdline.py tests/goldtest.py tests/helpers.py tests/test_html.py
+ TYPEABLE={env:C__B} {env:C_CC} {env:C_DE} {env:C_FN} {env:C_OP} {env:C_QZ} {env:T_AN}
commands =
# PYVERSIONS
mypy --python-version=3.7 {env:TYPEABLE}
- mypy --python-version=3.8 {env:TYPEABLE}
- mypy --python-version=3.9 {env:TYPEABLE}
- mypy --python-version=3.10 {env:TYPEABLE}
- mypy --python-version=3.11 {env:TYPEABLE}
mypy --python-version=3.12 {env:TYPEABLE}
[gh-actions]