diff --git a/coverage/control.py b/coverage/control.py index b052d4b4c..6378af1e4 100644 --- a/coverage/control.py +++ b/coverage/control.py @@ -30,7 +30,11 @@ from coverage.core import Core, HAS_CTRACER from coverage.data import CoverageData, combine_parallel_data from coverage.debug import ( - DebugControl, NoDebugging, short_stack, write_formatted_info, relevant_environment_display, + DebugControl, + NoDebugging, + short_stack, + write_formatted_info, + relevant_environment_display, ) from coverage.disposition import disposition_debug_msg from coverage.exceptions import ConfigError, CoverageException, CoverageWarning, PluginError @@ -49,13 +53,20 @@ from coverage.report_core import render_report from coverage.results import Analysis, analysis_from_file_reporter from coverage.types import ( - FilePath, TConfigurable, TConfigSectionIn, TConfigValueIn, TConfigValueOut, - TFileDisposition, TLineNo, TMorf, + FilePath, + TConfigurable, + TConfigSectionIn, + TConfigValueIn, + TConfigValueOut, + TFileDisposition, + TLineNo, + TMorf, ) from coverage.xmlreport import XmlReporter os = isolate_module(os) + @contextlib.contextmanager def override_config(cov: Coverage, **kwargs: TConfigValueIn) -> Iterator[None]: """Temporarily tweak the configuration of `cov`. @@ -75,6 +86,7 @@ def override_config(cov: Coverage, **kwargs: TConfigValueIn) -> Iterator[None]: DEFAULT_DATAFILE = DefaultValue("MISSING") _DEFAULT_DATAFILE = DEFAULT_DATAFILE # Just in case, for backwards compatibility + class Coverage(TConfigurable): """Programmatic access to coverage.py. @@ -120,7 +132,7 @@ def current(cls) -> Coverage | None: else: return None - def __init__( # pylint: disable=too-many-arguments + def __init__( # pylint: disable=too-many-arguments self, data_file: FilePath | DefaultValue | None = DEFAULT_DATAFILE, data_suffix: str | bool | None = None, @@ -571,8 +583,7 @@ def _init_for_start(self) -> None: self._warn( "Plugin file tracers ({}) aren't supported with {}".format( ", ".join( - plugin._coverage_plugin_name - for plugin in self._plugins.file_tracers + plugin._coverage_plugin_name for plugin in self._plugins.file_tracers ), self._collector.tracer_name(), ), @@ -596,13 +607,14 @@ def _init_for_start(self) -> None: # Register our clean-up handlers. atexit.register(self._atexit) if self.config.sigterm: - is_main = (threading.current_thread() == threading.main_thread()) + is_main = threading.current_thread() == threading.main_thread() if is_main and not env.WINDOWS: # The Python docs seem to imply that SIGTERM works uniformly even # on Windows, but that's not my experience, and this agrees: # https://stackoverflow.com/questions/35772001/x/35792192#35792192 - self._old_sigterm = signal.signal( # type: ignore[assignment] - signal.SIGTERM, self._on_sigterm, + self._old_sigterm = signal.signal( # type: ignore[assignment] + signal.SIGTERM, + self._on_sigterm, ) def _init_data(self, suffix: str | bool | None) -> None: @@ -679,7 +691,7 @@ def collect(self) -> Iterator[None]: try: yield finally: - self.stop() # pragma: nested + self.stop() # pragma: nested def _atexit(self, event: str = "atexit") -> None: """Clean up on process shutdown.""" @@ -695,8 +707,8 @@ def _on_sigterm(self, signum_unused: int, frame_unused: FrameType | None) -> Non self._atexit("sigterm") # Statements after here won't be seen by metacov because we just wrote # the data, and are about to kill the process. - signal.signal(signal.SIGTERM, self._old_sigterm) # pragma: not covered - os.kill(os.getpid(), signal.SIGTERM) # pragma: not covered + signal.signal(signal.SIGTERM, self._old_sigterm) # pragma: not covered + os.kill(os.getpid(), signal.SIGTERM) # pragma: not covered def erase(self) -> None: """Erase previously collected coverage data. @@ -728,7 +740,7 @@ def switch_context(self, new_context: str) -> None: .. versionadded:: 5.0 """ - if not self._started: # pragma: part started + if not self._started: # pragma: part started raise CoverageException("Cannot switch context, coverage is not started") assert self._collector is not None @@ -926,7 +938,7 @@ def analysis2( coverage data. """ - analysis = self._analyze(morf) + analysis = self.analyze(morf) return ( analysis.filename, sorted(analysis.statements), @@ -935,8 +947,8 @@ def analysis2( analysis.missing_formatted(), ) - def _analyze(self, morf: TMorf) -> Analysis: - """Analyze a module or file. Private for now.""" + def analyze(self, morf: TMorf) -> Analysis: + """Analyze a module or file.""" self._init() self._post_init() @@ -963,7 +975,8 @@ def _get_file_reporter(self, morf: TMorf) -> FileReporter: if file_reporter is None: raise PluginError( "Plugin {!r} did not provide a file reporter for {!r}.".format( - plugin._coverage_plugin_name, morf, + plugin._coverage_plugin_name, + morf, ), ) @@ -993,7 +1006,7 @@ def _get_file_reporters( # Be sure we have a collection. if not isinstance(morfs, (list, tuple, set)): - morfs = [morfs] # type: ignore[list-item] + morfs = [morfs] # type: ignore[list-item] return [(self._get_file_reporter(morf), morf) for morf in morfs] @@ -1305,14 +1318,15 @@ def plugin_info(plugins: list[Any]) -> list[str]: ("configs_attempted", self.config.config_files_attempted), ("configs_read", self.config.config_files_read), ("config_file", self.config.config_file), - ("config_contents", + ( + "config_contents", repr(self.config._config_contents) if self.config._config_contents else "-none-", ), ("data_file", self._data.data_filename() if self._data is not None else "-none-"), ("python", sys.version.replace("\n", "")), ("platform", platform.platform()), ("implementation", platform.python_implementation()), - ("gil_enabled", getattr(sys, '_is_gil_enabled', lambda: True)()), + ("gil_enabled", getattr(sys, "_is_gil_enabled", lambda: True)()), ("executable", sys.executable), ("def_encoding", sys.getdefaultencoding()), ("fs_encoding", sys.getfilesystemencoding()), @@ -1333,10 +1347,10 @@ def plugin_info(plugins: list[Any]) -> list[str]: # Mega debugging... # $set_env.py: COVERAGE_DEBUG_CALLS - Lots and lots of output about calls to Coverage. -if int(os.getenv("COVERAGE_DEBUG_CALLS", 0)): # pragma: debugging +if int(os.getenv("COVERAGE_DEBUG_CALLS", 0)): # pragma: debugging from coverage.debug import decorate_methods, show_calls - Coverage = decorate_methods( # type: ignore[misc] + Coverage = decorate_methods( # type: ignore[misc] show_calls(show_args=True), butnot=["get_data"], )(Coverage) @@ -1385,7 +1399,7 @@ def process_startup() -> Coverage | None: return None cov = Coverage(config_file=cps) - process_startup.coverage = cov # type: ignore[attr-defined] + process_startup.coverage = cov # type: ignore[attr-defined] cov._warn_no_data = False cov._warn_unimported_source = False cov._warn_preimported_source = False diff --git a/coverage/report_core.py b/coverage/report_core.py index 477034bae..3ae647e5e 100644 --- a/coverage/report_core.py +++ b/coverage/report_core.py @@ -8,7 +8,10 @@ import sys from typing import ( - Callable, IO, Protocol, TYPE_CHECKING, + Callable, + IO, + Protocol, + TYPE_CHECKING, ) from collections.abc import Iterable, Iterator @@ -68,7 +71,7 @@ def render_report( if file_to_close is not None: file_to_close.close() if delete_file: - file_be_gone(output_path) # pragma: part covered (doesn't return) + file_be_gone(output_path) # pragma: part covered (doesn't return) def get_analysis_to_report( @@ -98,13 +101,13 @@ def get_analysis_to_report( for fr, morf in sorted(fr_morfs): try: - analysis = coverage._analyze(morf) + analysis = coverage.analyze(morf) except NotPython: # Only report errors for .py files, and only if we didn't # explicitly suppress those errors. # NotPython is only raised by PythonFileReporter, which has a # should_be_python() method. - if fr.should_be_python(): # type: ignore[attr-defined] + if fr.should_be_python(): # type: ignore[attr-defined] if config.ignore_errors: msg = f"Couldn't parse Python file '{fr.filename}'" coverage._warn(msg, slug="couldnt-parse") diff --git a/doc/api.rst b/doc/api.rst index 7d04f03ee..a39ffad30 100644 --- a/doc/api.rst +++ b/doc/api.rst @@ -55,4 +55,5 @@ only. :ref:`dbschema` explains more. api_module api_plugin api_coveragedata + api_analysis dbschema diff --git a/doc/api_analysis.rst b/doc/api_analysis.rst new file mode 100644 index 000000000..9424c2c95 --- /dev/null +++ b/doc/api_analysis.rst @@ -0,0 +1,10 @@ +.. Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 +.. For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt + +.. _api_analysis: + +The Analysis class +------------------ + +.. autoclass:: coverage.results.Analysis + :members: branch_stats diff --git a/tests/coveragetest.py b/tests/coveragetest.py index 244a68a29..d7bfb177b 100644 --- a/tests/coveragetest.py +++ b/tests/coveragetest.py @@ -119,10 +119,10 @@ def start_import_stop( # understand the difference. cov.start() - try: # pragma: nested + try: # pragma: nested # Import the Python file, executing it. mod = import_local_file(modname, modfile) - finally: # pragma: nested + finally: # pragma: nested # Stop coverage.py. cov.stop() return mod @@ -132,15 +132,15 @@ def get_report(self, cov: Coverage, squeeze: bool = True, **kwargs: Any) -> str: repout = io.StringIO() kwargs.setdefault("show_missing", False) cov.report(file=repout, **kwargs) - report = repout.getvalue().replace('\\', '/') - print(report) # When tests fail, it's helpful to see the output + report = repout.getvalue().replace("\\", "/") + print(report) # When tests fail, it's helpful to see the output if squeeze: report = re.sub(r" +", " ", report) return report def get_module_name(self) -> str: """Return a random module name to use for this test run.""" - self.last_module_name = 'coverage_test_' + str(random.random())[2:] + self.last_module_name = "coverage_test_" + str(random.random())[2:] return self.last_module_name def check_coverage( @@ -169,7 +169,7 @@ def check_coverage( Returns the Coverage object, in case you want to poke at it some more. """ - __tracebackhide__ = True # pytest, please don't show me this function. + __tracebackhide__ = True # pytest, please don't show me this function. # We write the code into a file so that we can import it. # Coverage.py wants to deal with things as modules with file names. @@ -189,7 +189,7 @@ def check_coverage( for exc in excludes or []: cov.exclude(exc) for par in partials or []: - cov.exclude(par, which='partial') + cov.exclude(par, which="partial") mod = self.start_import_stop(cov, modname) @@ -197,7 +197,7 @@ def check_coverage( del sys.modules[modname] # Get the analysis results, and check that they are right. - analysis = cov._analyze(mod) + analysis = cov.analyze(mod) statements = sorted(analysis.statements) if lines: if isinstance(lines[0], int): @@ -219,17 +219,17 @@ def check_coverage( if branches is not None: trimmed_arcs = branches_to_arcs(arcs_to_branches(analysis.arc_possibilities)) - assert branches == trimmed_arcs, ( - f"Wrong possible branches: {branches} != {trimmed_arcs}" - ) + assert ( + branches == trimmed_arcs + ), f"Wrong possible branches: {branches} != {trimmed_arcs}" if branches_missing is not None: - assert set(branches_missing) <= set(branches), ( - f"{branches_missing = }, has non-branches in it." - ) + assert set(branches_missing) <= set( + branches + ), f"{branches_missing = }, has non-branches in it." analysis_missing = branches_to_arcs(analysis.missing_branch_arcs()) - assert branches_missing == analysis_missing, ( - f"Wrong missing branches: {branches_missing} != {analysis_missing}" - ) + assert ( + branches_missing == analysis_missing + ), f"Wrong missing branches: {branches_missing} != {analysis_missing}" if report: frep = io.StringIO() @@ -284,10 +284,11 @@ def assert_warnings( """ __tracebackhide__ = True saved_warnings = [] + def capture_warning( msg: str, slug: str | None = None, - once: bool = False, # pylint: disable=unused-argument + once: bool = False, # pylint: disable=unused-argument ) -> None: """A fake implementation of Coverage._warn, to capture warnings.""" # NOTE: we don't implement `once`. @@ -296,11 +297,11 @@ def capture_warning( saved_warnings.append(msg) original_warn = cov._warn - cov._warn = capture_warning # type: ignore[method-assign] + cov._warn = capture_warning # type: ignore[method-assign] try: yield - except: # pylint: disable=try-except-raise + except: # pylint: disable=try-except-raise raise else: if warnings: @@ -321,7 +322,7 @@ def capture_warning( if saved_warnings: assert False, f"Unexpected warnings: {saved_warnings!r}" finally: - cov._warn = original_warn # type: ignore[method-assign] + cov._warn = original_warn # type: ignore[method-assign] def assert_same_files(self, flist1: Iterable[str], flist2: Iterable[str]) -> None: """Assert that `flist1` and `flist2` are the same set of file names.""" @@ -455,13 +456,13 @@ def working_root(self) -> str: def report_from_command(self, cmd: str) -> str: """Return the report from the `cmd`, with some convenience added.""" - report = self.run_command(cmd).replace('\\', '/') + report = self.run_command(cmd).replace("\\", "/") assert "error" not in report.lower() return report def report_lines(self, report: str) -> list[str]: """Return the lines of the report, as a list.""" - lines = report.split('\n') + lines = report.split("\n") assert lines[-1] == "" return lines[:-1] @@ -484,8 +485,7 @@ def get_measured_filenames(self, coverage_data: CoverageData) -> dict[str, str]: Returns a dict of {filename: absolute path to file} for given CoverageData. """ - return {os.path.basename(filename): filename - for filename in coverage_data.measured_files()} + return {os.path.basename(filename): filename for filename in coverage_data.measured_files()} def get_missing_arc_description(self, cov: Coverage, start: TLineNo, end: TLineNo) -> str: """Get the missing-arc description for a line arc in a coverage run.""" @@ -493,7 +493,7 @@ def get_missing_arc_description(self, cov: Coverage, start: TLineNo, end: TLineN assert self.last_module_name is not None filename = self.last_module_name + ".py" fr = cov._get_file_reporter(filename) - arcs_executed = cov._analyze(filename).arcs_executed + arcs_executed = cov.analyze(filename).arcs_executed return fr.missing_arc_description(start, end, arcs_executed) @@ -501,7 +501,7 @@ class UsingModulesMixin: """A mixin for importing modules from tests/modules and tests/moremodules.""" def setUp(self) -> None: - super().setUp() # type: ignore[misc] + super().setUp() # type: ignore[misc] # Parent class saves and restores sys.path, we can just modify it. sys.path.append(nice_file(TESTS_DIR, "modules")) diff --git a/tests/test_api.py b/tests/test_api.py index ab738e3c3..d066ab3ff 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -35,6 +35,7 @@ BAD_SQLITE_REGEX = r"file( is encrypted or)? is not a database" + class ApiTest(CoverageTest): """Api-oriented tests for coverage.py.""" @@ -58,17 +59,23 @@ def assertFiles(self, files: list[str]) -> None: def test_unexecuted_file(self) -> None: cov = coverage.Coverage() - self.make_file("mycode.py", """\ + self.make_file( + "mycode.py", + """\ a = 1 b = 2 if b == 3: c = 4 d = 5 - """) + """, + ) - self.make_file("not_run.py", """\ + self.make_file( + "not_run.py", + """\ fooey = 17 - """) + """, + ) # Import the Python file, executing it. self.start_import_stop(cov, "mycode") @@ -78,14 +85,20 @@ def test_unexecuted_file(self) -> None: assert missing == [1] def test_filenames(self) -> None: - self.make_file("mymain.py", """\ + self.make_file( + "mymain.py", + """\ import mymod a = 1 - """) + """, + ) - self.make_file("mymod.py", """\ + self.make_file( + "mymod.py", + """\ fooey = 17 - """) + """, + ) # Import the Python file, executing it. cov = coverage.Coverage() @@ -118,11 +131,14 @@ def test_filenames(self) -> None: @pytest.mark.parametrize("cover_pylib", [False, True]) def test_stdlib(self, cover_pylib: bool) -> None: - self.make_file("mymain.py", """\ + self.make_file( + "mymain.py", + """\ import colorsys a = 1 hls = colorsys.rgb_to_hls(1.0, 0.5, 0.0) - """) + """, + ) # Measure without the stdlib. cov1 = coverage.Coverage(cover_pylib=cover_pylib) @@ -139,12 +155,15 @@ def test_stdlib(self, cover_pylib: bool) -> None: assert statements == missing def test_include_can_measure_stdlib(self) -> None: - self.make_file("mymain.py", """\ + self.make_file( + "mymain.py", + """\ import colorsys, random a = 1 r, g, b = [random.random() for _ in range(3)] hls = colorsys.rgb_to_hls(r, g, b) - """) + """, + ) # Measure without the stdlib, but include colorsys. cov1 = coverage.Coverage(cover_pylib=False, include=["*/colorsys.py"]) @@ -165,48 +184,51 @@ def test_exclude_list(self) -> None: assert cov.get_exclude_list() == ["foo"] cov.exclude("bar") assert cov.get_exclude_list() == ["foo", "bar"] - assert cov._exclude_regex('exclude') == "(?:foo)|(?:bar)" + assert cov._exclude_regex("exclude") == "(?:foo)|(?:bar)" cov.clear_exclude() assert cov.get_exclude_list() == [] def test_exclude_partial_list(self) -> None: cov = coverage.Coverage() - cov.clear_exclude(which='partial') - assert cov.get_exclude_list(which='partial') == [] - cov.exclude("foo", which='partial') - assert cov.get_exclude_list(which='partial') == ["foo"] - cov.exclude("bar", which='partial') - assert cov.get_exclude_list(which='partial') == ["foo", "bar"] - assert cov._exclude_regex(which='partial') == "(?:foo)|(?:bar)" - cov.clear_exclude(which='partial') - assert cov.get_exclude_list(which='partial') == [] + cov.clear_exclude(which="partial") + assert cov.get_exclude_list(which="partial") == [] + cov.exclude("foo", which="partial") + assert cov.get_exclude_list(which="partial") == ["foo"] + cov.exclude("bar", which="partial") + assert cov.get_exclude_list(which="partial") == ["foo", "bar"] + assert cov._exclude_regex(which="partial") == "(?:foo)|(?:bar)" + cov.clear_exclude(which="partial") + assert cov.get_exclude_list(which="partial") == [] def test_exclude_and_partial_are_separate_lists(self) -> None: cov = coverage.Coverage() - cov.clear_exclude(which='partial') - cov.clear_exclude(which='exclude') - cov.exclude("foo", which='partial') - assert cov.get_exclude_list(which='partial') == ['foo'] - assert cov.get_exclude_list(which='exclude') == [] - cov.exclude("bar", which='exclude') - assert cov.get_exclude_list(which='partial') == ['foo'] - assert cov.get_exclude_list(which='exclude') == ['bar'] - cov.exclude("p2", which='partial') - cov.exclude("e2", which='exclude') - assert cov.get_exclude_list(which='partial') == ['foo', 'p2'] - assert cov.get_exclude_list(which='exclude') == ['bar', 'e2'] - cov.clear_exclude(which='partial') - assert cov.get_exclude_list(which='partial') == [] - assert cov.get_exclude_list(which='exclude') == ['bar', 'e2'] - cov.clear_exclude(which='exclude') - assert cov.get_exclude_list(which='partial') == [] - assert cov.get_exclude_list(which='exclude') == [] + cov.clear_exclude(which="partial") + cov.clear_exclude(which="exclude") + cov.exclude("foo", which="partial") + assert cov.get_exclude_list(which="partial") == ["foo"] + assert cov.get_exclude_list(which="exclude") == [] + cov.exclude("bar", which="exclude") + assert cov.get_exclude_list(which="partial") == ["foo"] + assert cov.get_exclude_list(which="exclude") == ["bar"] + cov.exclude("p2", which="partial") + cov.exclude("e2", which="exclude") + assert cov.get_exclude_list(which="partial") == ["foo", "p2"] + assert cov.get_exclude_list(which="exclude") == ["bar", "e2"] + cov.clear_exclude(which="partial") + assert cov.get_exclude_list(which="partial") == [] + assert cov.get_exclude_list(which="exclude") == ["bar", "e2"] + cov.clear_exclude(which="exclude") + assert cov.get_exclude_list(which="partial") == [] + assert cov.get_exclude_list(which="exclude") == [] def test_datafile_default(self) -> None: # Default data file behavior: it's .coverage - self.make_file("datatest1.py", """\ + self.make_file( + "datatest1.py", + """\ fooey = 17 - """) + """, + ) self.assertFiles(["datatest1.py"]) cov = coverage.Coverage() @@ -217,9 +239,12 @@ def test_datafile_default(self) -> None: @pytest.mark.parametrize("file_class", FilePathClasses) def test_datafile_specified(self, file_class: FilePathType) -> None: # You can specify the data file name. - self.make_file("datatest2.py", """\ + self.make_file( + "datatest2.py", + """\ fooey = 17 - """) + """, + ) self.assertFiles(["datatest2.py"]) cov = coverage.Coverage(data_file=file_class("cov.data")) @@ -230,9 +255,12 @@ def test_datafile_specified(self, file_class: FilePathType) -> None: @pytest.mark.parametrize("file_class", FilePathClasses) def test_datafile_and_suffix_specified(self, file_class: FilePathType) -> None: # You can specify the data file name and suffix. - self.make_file("datatest3.py", """\ + self.make_file( + "datatest3.py", + """\ fooey = 17 - """) + """, + ) self.assertFiles(["datatest3.py"]) cov = coverage.Coverage(data_file=file_class("cov.data"), data_suffix="14") @@ -242,13 +270,19 @@ def test_datafile_and_suffix_specified(self, file_class: FilePathType) -> None: def test_datafile_from_rcfile(self) -> None: # You can specify the data file name in the .coveragerc file - self.make_file("datatest4.py", """\ + self.make_file( + "datatest4.py", + """\ fooey = 17 - """) - self.make_file(".coveragerc", """\ + """, + ) + self.make_file( + ".coveragerc", + """\ [run] data_file = mydata.dat - """) + """, + ) self.assertFiles(["datatest4.py", ".coveragerc"]) cov = coverage.Coverage() @@ -268,8 +302,8 @@ def test_deep_datafile(self) -> None: def test_datafile_none(self) -> None: cov = coverage.Coverage(data_file=None) - def f1() -> None: # pragma: nested - a = 1 # pylint: disable=unused-variable + def f1() -> None: # pragma: nested + a = 1 # pylint: disable=unused-variable one_line_number = f1.__code__.co_firstlineno + 1 lines = [] @@ -317,8 +351,8 @@ def test_completely_zero_reporting(self) -> None: def test_cov4_data_file(self) -> None: cov4_data = ( - "!coverage.py: This is a private format, don't read it directly!" + - '{"lines":{"/private/tmp/foo.py":[1,5,2,3]}}' + "!coverage.py: This is a private format, don't read it directly!" + + '{"lines":{"/private/tmp/foo.py":[1,5,2,3]}}' ) self.make_file(".coverage", cov4_data) cov = coverage.Coverage() @@ -328,13 +362,19 @@ def test_cov4_data_file(self) -> None: def make_code1_code2(self) -> None: """Create the code1.py and code2.py files.""" - self.make_file("code1.py", """\ + self.make_file( + "code1.py", + """\ code1 = 1 - """) - self.make_file("code2.py", """\ + """, + ) + self.make_file( + "code2.py", + """\ code2 = 1 code2 = 2 - """) + """, + ) def check_code1_code2(self, cov: Coverage) -> None: """Check the analysis is correct for code1.py and code2.py.""" @@ -415,9 +455,7 @@ def test_combining_corrupt_data(self) -> None: self.make_good_data_files() self.make_file(".coverage.foo", """La la la, this isn't coverage data!""") cov = coverage.Coverage() - warning_regex = ( - r"Couldn't use data file '.*\.coverage\.foo': " + BAD_SQLITE_REGEX - ) + warning_regex = r"Couldn't use data file '.*\.coverage\.foo': " + BAD_SQLITE_REGEX with self.assert_warnings(cov, [warning_regex]): cov.combine() @@ -479,8 +517,8 @@ def make_files() -> None: self.make_data_file( basename=".coverage.1", lines={ - abs_file('ci/girder/g1.py'): range(10), - abs_file('ci/girder/plugins/p1.py'): range(10), + abs_file("ci/girder/g1.py"): range(10), + abs_file("ci/girder/plugins/p1.py"): range(10), }, ) @@ -495,7 +533,9 @@ def get_combined_filenames() -> set[str]: # Case 1: get the order right. make_files() - self.make_file(".coveragerc", """\ + self.make_file( + ".coveragerc", + """\ [paths] plugins = plugins/ @@ -503,12 +543,15 @@ def get_combined_filenames() -> set[str]: girder = girder/ ci/girder/ - """) - assert get_combined_filenames() == {'girder/g1.py', 'plugins/p1.py'} + """, + ) + assert get_combined_filenames() == {"girder/g1.py", "plugins/p1.py"} # Case 2: get the order "wrong". make_files() - self.make_file(".coveragerc", """\ + self.make_file( + ".coveragerc", + """\ [paths] girder = girder/ @@ -516,14 +559,18 @@ def get_combined_filenames() -> set[str]: plugins = plugins/ ci/girder/plugins/ - """) - assert get_combined_filenames() == {'girder/g1.py', 'plugins/p1.py'} + """, + ) + assert get_combined_filenames() == {"girder/g1.py", "plugins/p1.py"} def test_warnings(self) -> None: - self.make_file("hello.py", """\ + self.make_file( + "hello.py", + """\ import sys, os print("Hello") - """) + """, + ) with pytest.warns(Warning) as warns: cov = coverage.Coverage(source=["sys", "xyzzy", "quux"]) self.start_import_stop(cov, "hello") @@ -539,14 +586,20 @@ def test_warnings(self) -> None: ) def test_warnings_suppressed(self) -> None: - self.make_file("hello.py", """\ + self.make_file( + "hello.py", + """\ import sys, os print("Hello") - """) - self.make_file(".coveragerc", """\ + """, + ) + self.make_file( + ".coveragerc", + """\ [run] disable_warnings = no-data-collected, module-not-imported - """) + """, + ) with pytest.warns(Warning) as warns: cov = coverage.Coverage(source=["sys", "xyzzy", "quux"]) self.start_import_stop(cov, "hello") @@ -571,10 +624,13 @@ def test_source_and_include_dont_conflict(self) -> None: # A bad fix made this case fail: https://github.com/nedbat/coveragepy/issues/541 self.make_file("a.py", "import b\na = 1") self.make_file("b.py", "b = 1") - self.make_file(".coveragerc", """\ + self.make_file( + ".coveragerc", + """\ [run] source = . - """) + """, + ) # Just like: coverage run a.py cov = coverage.Coverage() @@ -616,7 +672,7 @@ def test_run_debug_sys(self) -> None: cov = coverage.Coverage() with cov.collect(): d = dict(cov.sys_info()) - assert cast(str, d['data_file']).endswith(".coverage") + assert cast(str, d["data_file"]).endswith(".coverage") @pytest.mark.skipif(not testenv.DYN_CONTEXTS, reason="No dynamic contexts with this core.") @@ -625,7 +681,9 @@ class SwitchContextTest(CoverageTest): def make_test_files(self) -> None: """Create a simple file representing a method with two tests.""" - self.make_file("testsuite.py", """\ + self.make_file( + "testsuite.py", + """\ def timestwo(x): return x*2 @@ -634,7 +692,8 @@ def test_multiply_zero(): def test_multiply_six(): assert timestwo(6) == 12 - """) + """, + ) def test_switch_context_testrunner(self) -> None: # This test simulates a coverage-aware test runner, @@ -648,11 +707,11 @@ def test_switch_context_testrunner(self) -> None: suite = import_local_file("testsuite") # Measures test case 1 - cov.switch_context('multiply_zero') + cov.switch_context("multiply_zero") suite.test_multiply_zero() # Measures test case 2 - cov.switch_context('multiply_six') + cov.switch_context("multiply_six") suite.test_multiply_six() # Runner finishes @@ -660,10 +719,10 @@ def test_switch_context_testrunner(self) -> None: # Labeled data is collected data = cov.get_data() - assert ['', 'multiply_six', 'multiply_zero'] == sorted(data.measured_contexts()) + assert ["", "multiply_six", "multiply_zero"] == sorted(data.measured_contexts()) filenames = self.get_measured_filenames(data) - suite_filename = filenames['testsuite.py'] + suite_filename = filenames["testsuite.py"] data.set_query_context("multiply_six") assert [2, 8] == sorted_lines(data, suite_filename) @@ -683,11 +742,11 @@ def test_switch_context_with_static(self) -> None: suite = import_local_file("testsuite") # Measures test case 1 - cov.switch_context('multiply_zero') + cov.switch_context("multiply_zero") suite.test_multiply_zero() # Measures test case 2 - cov.switch_context('multiply_six') + cov.switch_context("multiply_six") suite.test_multiply_six() # Runner finishes @@ -695,11 +754,11 @@ def test_switch_context_with_static(self) -> None: # Labeled data is collected data = cov.get_data() - expected = ['mysuite', 'mysuite|multiply_six', 'mysuite|multiply_zero'] + expected = ["mysuite", "mysuite|multiply_six", "mysuite|multiply_zero"] assert expected == sorted(data.measured_contexts()) filenames = self.get_measured_filenames(data) - suite_filename = filenames['testsuite.py'] + suite_filename = filenames["testsuite.py"] data.set_query_context("mysuite|multiply_six") assert [2, 8] == sorted_lines(data, suite_filename) @@ -779,7 +838,7 @@ def test_explicit_namespace_module(self) -> None: self.start_import_stop(cov, "main") with pytest.raises(CoverageException, match=r"Module .* has no file"): - cov.analysis(sys.modules['namespace_420']) + cov.analysis(sys.modules["namespace_420"]) def test_bug_572(self) -> None: self.make_file("main.py", "import namespace_420\n") @@ -798,7 +857,7 @@ class IncludeOmitTestsMixin(UsingModulesMixin, CoverageTest): # An abstract method for subclasses to define, to appease mypy. def coverage_usepkgs(self, **kwargs_unused: TCovKwargs) -> Iterable[str]: """Run coverage on usepkgs, return a line summary. kwargs are for Coverage(**kwargs).""" - raise NotImplementedError() # pragma: not covered + raise NotImplementedError() # pragma: not covered def filenames_in(self, summary: Iterable[str], filenames: str) -> None: """Assert the `filenames` are in the `summary`.""" @@ -904,7 +963,7 @@ def test_source_package_as_package(self) -> None: self.filenames_in(list(lines), "p1a p1b") self.filenames_not_in(list(lines), "p2a p2b othera otherb osa osb") # Because source= was specified, we do search for un-executed files. - assert lines['p1c'] == 0 + assert lines["p1c"] == 0 def test_source_package_as_dir(self) -> None: os.chdir("tests_dir_modules") @@ -913,13 +972,13 @@ def test_source_package_as_dir(self) -> None: self.filenames_in(list(lines), "p1a p1b") self.filenames_not_in(list(lines), "p2a p2b othera otherb osa osb") # Because source= was specified, we do search for un-executed files. - assert lines['p1c'] == 0 + assert lines["p1c"] == 0 def test_source_package_dotted_sub(self) -> None: lines = self.coverage_usepkgs_counts(source=["pkg1.sub"]) self.filenames_not_in(list(lines), "p2a p2b othera otherb osa osb") # Because source= was specified, we do search for un-executed files. - assert lines['runmod3'] == 0 + assert lines["runmod3"] == 0 def test_source_package_dotted_p1b(self) -> None: lines = self.coverage_usepkgs_counts(source=["pkg1.p1b"]) @@ -937,14 +996,14 @@ def test_source_package_part_omitted(self) -> None: lines = self.coverage_usepkgs_counts(source=["pkg1"], omit=["pkg1/p1b.py"]) self.filenames_in(list(lines), "p1a") self.filenames_not_in(list(lines), "p1b") - assert lines['p1c'] == 0 + assert lines["p1c"] == 0 def test_source_package_as_package_part_omitted(self) -> None: # https://github.com/nedbat/coveragepy/issues/638 lines = self.coverage_usepkgs_counts(source=["pkg1"], omit=["*/p1b.py"]) self.filenames_in(list(lines), "p1a") self.filenames_not_in(list(lines), "p1b") - assert lines['p1c'] == 0 + assert lines["p1c"] == 0 def test_ambiguous_source_package_as_dir(self) -> None: # pkg1 is a directory and a pkg, since we cd into tests_dir_modules/ambiguous @@ -961,7 +1020,7 @@ def test_ambiguous_source_package_as_package(self) -> None: self.filenames_in(list(lines), "p1a p1b") self.filenames_not_in(list(lines), "p2a p2b othera otherb osa osb ambiguous") # Because source= was specified, we do search for un-executed files. - assert lines['p1c'] == 0 + assert lines["p1c"] == 0 class ReportIncludeOmitTest(IncludeOmitTestsMixin, CoverageTest): @@ -996,10 +1055,13 @@ def coverage_usepkgs(self, **kwargs: TCovKwargs) -> Iterable[str]: class AnalysisTest(CoverageTest): """Test the numerical analysis of results.""" + def test_many_missing_branches(self) -> None: cov = coverage.Coverage(branch=True) - self.make_file("missing.py", """\ + self.make_file( + "missing.py", + """\ def fun1(x): if x == 1: print("one") @@ -1011,12 +1073,13 @@ def fun2(x): print("x") fun2(3) - """) + """, + ) # Import the Python file, executing it. self.start_import_stop(cov, "missing") - nums = cov._analyze("missing.py").numbers + nums = cov.analyze("missing.py").numbers assert nums.n_files == 1 assert nums.n_statements == 7 assert nums.n_excluded == 1 @@ -1033,14 +1096,18 @@ class TestRunnerPluginTest(CoverageTest): way they do. """ + def pretend_to_be_nose_with_cover(self, erase: bool = False, cd: bool = False) -> None: """This is what the nose --with-cover plugin does.""" - self.make_file("no_biggie.py", """\ + self.make_file( + "no_biggie.py", + """\ a = 1 b = 2 if b == 1: c = 4 - """) + """, + ) self.make_file("sub/hold.txt", "") cov = coverage.Coverage() @@ -1076,19 +1143,25 @@ def test_nose_plugin_with_cd(self) -> None: def pretend_to_be_pytestcov(self, append: bool) -> None: """Act like pytest-cov.""" - self.make_file("prog.py", """\ + self.make_file( + "prog.py", + """\ a = 1 b = 2 if b == 1: c = 4 - """) - self.make_file(".coveragerc", """\ + """, + ) + self.make_file( + ".coveragerc", + """\ [run] parallel = True source = . - """) + """, + ) - cov = coverage.Coverage(source=None, branch=None, config_file='.coveragerc') + cov = coverage.Coverage(source=None, branch=None, config_file=".coveragerc") if append: cov.load() else: @@ -1097,8 +1170,9 @@ def pretend_to_be_pytestcov(self, append: bool) -> None: cov.combine() cov.save() report = io.StringIO() - cov.report(show_missing=None, ignore_errors=True, file=report, skip_covered=None, - skip_empty=None) + cov.report( + show_missing=None, ignore_errors=True, file=report, skip_covered=None, skip_empty=None + ) assert report.getvalue() == textwrap.dedent("""\ Name Stmts Miss Cover ----------------------------- @@ -1153,10 +1227,13 @@ def test_moving_stuff(self) -> None: def test_moving_stuff_with_relative(self) -> None: # When using relative file names, moving the source around is fine. self.make_file("foo.py", "a = 1") - self.make_file(".coveragerc", """\ + self.make_file( + ".coveragerc", + """\ [run] relative_files = true - """) + """, + ) cov = coverage.Coverage(source=["."]) self.start_import_stop(cov, "foo") res = cov.report() @@ -1173,25 +1250,34 @@ def test_moving_stuff_with_relative(self) -> None: assert res == 100 def test_combine_relative(self) -> None: - self.make_file("foo.py", """\ + self.make_file( + "foo.py", + """\ import mod a = 1 - """) + """, + ) self.make_file("lib/mod/__init__.py", "x = 1") - self.make_file(".coveragerc", """\ + self.make_file( + ".coveragerc", + """\ [run] relative_files = true - """) + """, + ) sys.path.append("lib") cov = coverage.Coverage(source=["."], data_suffix=True) self.start_import_stop(cov, "foo") cov.save() self.make_file("dir2/bar.py", "a = 1") - self.make_file("dir2/.coveragerc", """\ + self.make_file( + "dir2/.coveragerc", + """\ [run] relative_files = true - """) + """, + ) with change_dir("dir2"): cov = coverage.Coverage(source=["."], data_suffix=True) self.start_import_stop(cov, "bar") @@ -1202,14 +1288,17 @@ def test_combine_relative(self) -> None: self.make_file("bar.py", "a = 1") self.make_file("modsrc/__init__.py", "x = 1") - self.make_file(".coveragerc", """\ + self.make_file( + ".coveragerc", + """\ [run] relative_files = true [paths] source = modsrc */mod - """) + """, + ) cov = coverage.Coverage() cov.combine() cov.save() @@ -1217,15 +1306,18 @@ def test_combine_relative(self) -> None: cov = coverage.Coverage() cov.load() files = cov.get_data().measured_files() - assert files == {'foo.py', 'bar.py', os_sep('modsrc/__init__.py')} + assert files == {"foo.py", "bar.py", os_sep("modsrc/__init__.py")} res = cov.report() assert res == 100 def test_combine_no_suffix_multiprocessing(self) -> None: - self.make_file(".coveragerc", """\ + self.make_file( + ".coveragerc", + """\ [run] branch = True - """) + """, + ) cov = coverage.Coverage( config_file=".coveragerc", concurrency="multiprocessing", @@ -1243,15 +1335,23 @@ def test_combine_no_suffix_multiprocessing(self) -> None: def test_files_up_one_level(self) -> None: # https://github.com/nedbat/coveragepy/issues/1280 - self.make_file("src/mycode.py", """\ + self.make_file( + "src/mycode.py", + """\ def foo(): return 17 - """) - self.make_file("test/test_it.py", """\ + """, + ) + self.make_file( + "test/test_it.py", + """\ from src.mycode import foo assert foo() == 17 - """) - self.make_file("test/.coveragerc", """\ + """, + ) + self.make_file( + "test/.coveragerc", + """\ [run] parallel = True relative_files = True @@ -1260,7 +1360,8 @@ def foo(): source = ../src/ */src - """) + """, + ) os.chdir("test") sys.path.insert(0, "..") cov1 = coverage.Coverage() @@ -1285,7 +1386,9 @@ def make_b_or_c_py(self) -> None: # "b_or_c.py b" will run 6 lines. # "b_or_c.py c" will run 7 lines. # Together, they run 8 lines. - self.make_file("b_or_c.py", """\ + self.make_file( + "b_or_c.py", + """\ import sys a = 2 if sys.argv[1] == 'b': @@ -1295,7 +1398,8 @@ def make_b_or_c_py(self) -> None: c2 = 7 d = 8 print('done') - """) + """, + ) def test_combine_parallel_data(self) -> None: self.make_b_or_c_py() @@ -1314,7 +1418,7 @@ def test_combine_parallel_data(self) -> None: # executed. data = coverage.CoverageData() data.read() - assert line_counts(data)['b_or_c.py'] == 8 + assert line_counts(data)["b_or_c.py"] == 8 # Running combine again should fail, because there are no parallel data # files to combine. @@ -1325,7 +1429,7 @@ def test_combine_parallel_data(self) -> None: # And the originally combined data is still there. data = coverage.CoverageData() data.read() - assert line_counts(data)['b_or_c.py'] == 8 + assert line_counts(data)["b_or_c.py"] == 8 def test_combine_parallel_data_with_a_corrupt_file(self) -> None: self.make_b_or_c_py() @@ -1355,7 +1459,7 @@ def test_combine_parallel_data_with_a_corrupt_file(self) -> None: # executed. data = coverage.CoverageData() data.read() - assert line_counts(data)['b_or_c.py'] == 8 + assert line_counts(data)["b_or_c.py"] == 8 def test_combine_no_usable_files(self) -> None: # https://github.com/nedbat/coveragepy/issues/629 @@ -1387,7 +1491,7 @@ def test_combine_no_usable_files(self) -> None: # executed (we only did b, not c). data = coverage.CoverageData() data.read() - assert line_counts(data)['b_or_c.py'] == 6 + assert line_counts(data)["b_or_c.py"] == 6 def test_combine_parallel_data_in_two_steps(self) -> None: self.make_b_or_c_py() @@ -1417,7 +1521,7 @@ def test_combine_parallel_data_in_two_steps(self) -> None: # executed. data = coverage.CoverageData() data.read() - assert line_counts(data)['b_or_c.py'] == 8 + assert line_counts(data)["b_or_c.py"] == 8 def test_combine_parallel_data_no_append(self) -> None: self.make_b_or_c_py() @@ -1444,7 +1548,7 @@ def test_combine_parallel_data_no_append(self) -> None: # because we didn't keep the data from running b. data = coverage.CoverageData() data.read() - assert line_counts(data)['b_or_c.py'] == 7 + assert line_counts(data)["b_or_c.py"] == 7 def test_combine_parallel_data_keep(self) -> None: self.make_b_or_c_py() diff --git a/tests/test_plugins.py b/tests/test_plugins.py index b3c8cd6f6..724638a01 100644 --- a/tests/test_plugins.py +++ b/tests/test_plugins.py @@ -32,8 +32,9 @@ class NullConfig(TPluginConfig): """A plugin configure thing when we don't really need one.""" + def get_plugin_options(self, plugin: str) -> TConfigSectionOut: - return {} # pragma: never called + return {} # pragma: never called class FakeConfig(TPluginConfig): @@ -57,7 +58,9 @@ class LoadPluginsTest(CoverageTest): """Test Plugins.load_plugins directly.""" def test_implicit_boolean(self) -> None: - self.make_file("plugin1.py", """\ + self.make_file( + "plugin1.py", + """\ from coverage import CoveragePlugin class Plugin(CoveragePlugin): @@ -65,7 +68,8 @@ class Plugin(CoveragePlugin): def coverage_init(reg, options): reg.add_file_tracer(Plugin()) - """) + """, + ) config = FakeConfig("plugin1", {}) plugins = Plugins.load_plugins([], config) @@ -75,7 +79,9 @@ def coverage_init(reg, options): assert plugins def test_importing_and_configuring(self) -> None: - self.make_file("plugin1.py", """\ + self.make_file( + "plugin1.py", + """\ from coverage import CoveragePlugin class Plugin(CoveragePlugin): @@ -85,18 +91,21 @@ def __init__(self, options): def coverage_init(reg, options): reg.add_file_tracer(Plugin(options)) - """) + """, + ) - config = FakeConfig("plugin1", {'a': 'hello'}) + config = FakeConfig("plugin1", {"a": "hello"}) plugins = list(Plugins.load_plugins(["plugin1"], config)) assert len(plugins) == 1 - assert plugins[0].this_is == "me" # type: ignore - assert plugins[0].options == {'a': 'hello'} # type: ignore - assert config.asked_for == ['plugin1'] + assert plugins[0].this_is == "me" # type: ignore + assert plugins[0].options == {"a": "hello"} # type: ignore + assert config.asked_for == ["plugin1"] def test_importing_and_configuring_more_than_one(self) -> None: - self.make_file("plugin1.py", """\ + self.make_file( + "plugin1.py", + """\ from coverage import CoveragePlugin class Plugin(CoveragePlugin): @@ -106,8 +115,11 @@ def __init__(self, options): def coverage_init(reg, options): reg.add_file_tracer(Plugin(options)) - """) - self.make_file("plugin2.py", """\ + """, + ) + self.make_file( + "plugin2.py", + """\ from coverage import CoveragePlugin class Plugin(CoveragePlugin): @@ -116,35 +128,39 @@ def __init__(self, options): def coverage_init(reg, options): reg.add_file_tracer(Plugin(options)) - """) + """, + ) - config = FakeConfig("plugin1", {'a': 'hello'}) + config = FakeConfig("plugin1", {"a": "hello"}) plugins = list(Plugins.load_plugins(["plugin1", "plugin2"], config)) assert len(plugins) == 2 - assert plugins[0].this_is == "me" # type: ignore - assert plugins[0].options == {'a': 'hello'} # type: ignore - assert plugins[1].options == {} # type: ignore - assert config.asked_for == ['plugin1', 'plugin2'] + assert plugins[0].this_is == "me" # type: ignore + assert plugins[0].options == {"a": "hello"} # type: ignore + assert plugins[1].options == {} # type: ignore + assert config.asked_for == ["plugin1", "plugin2"] # The order matters... - config = FakeConfig("plugin1", {'a': 'second'}) + config = FakeConfig("plugin1", {"a": "second"}) plugins = list(Plugins.load_plugins(["plugin2", "plugin1"], config)) assert len(plugins) == 2 - assert plugins[0].options == {} # type: ignore - assert plugins[1].this_is == "me" # type: ignore - assert plugins[1].options == {'a': 'second'} # type: ignore + assert plugins[0].options == {} # type: ignore + assert plugins[1].this_is == "me" # type: ignore + assert plugins[1].options == {"a": "second"} # type: ignore def test_cant_import(self) -> None: with pytest.raises(ImportError, match="No module named '?plugin_not_there'?"): _ = Plugins.load_plugins(["plugin_not_there"], NullConfig()) def test_plugin_must_define_coverage_init(self) -> None: - self.make_file("no_plugin.py", """\ + self.make_file( + "no_plugin.py", + """\ from coverage import CoveragePlugin Nothing = 0 - """) + """, + ) msg_pat = "Plugin module 'no_plugin' didn't define a coverage_init function" with pytest.raises(PluginError, match=msg_pat): list(Plugins.load_plugins(["no_plugin"], NullConfig())) @@ -155,7 +171,9 @@ class PluginTest(CoverageTest): def test_plugin_imported(self) -> None: # Prove that a plugin will be imported. - self.make_file("my_plugin.py", """\ + self.make_file( + "my_plugin.py", + """\ from coverage import CoveragePlugin class Plugin(CoveragePlugin): pass @@ -163,13 +181,14 @@ def coverage_init(reg, options): reg.add_noop(Plugin()) with open("evidence.out", "w") as f: f.write("we are here!") - """) + """, + ) self.assert_doesnt_exist("evidence.out") cov = coverage.Coverage() cov.set_option("run:plugins", ["my_plugin"]) cov.start() - cov.stop() # pragma: nested + cov.stop() # pragma: nested with open("evidence.out") as f: assert f.read() == "we are here!" @@ -192,7 +211,9 @@ def test_bad_plugin_isnt_hidden(self) -> None: cov.stop() def test_plugin_sys_info(self) -> None: - self.make_file("plugin_sys_info.py", """\ + self.make_file( + "plugin_sys_info.py", + """\ import coverage class Plugin(coverage.CoveragePlugin): @@ -201,7 +222,8 @@ def sys_info(self): def coverage_init(reg, options): reg.add_file_tracer(Plugin()) - """) + """, + ) debug_out = io.StringIO() cov = coverage.Coverage(debug=["sys"]) cov._debug_file = debug_out @@ -210,23 +232,25 @@ def coverage_init(reg, options): r"Plugin file tracers \(plugin_sys_info.Plugin\) aren't supported with .*", ): cov.start() - cov.stop() # pragma: nested + cov.stop() # pragma: nested out_lines = [line.strip() for line in debug_out.getvalue().splitlines()] if testenv.C_TRACER: - assert 'plugins.file_tracers: plugin_sys_info.Plugin' in out_lines + assert "plugins.file_tracers: plugin_sys_info.Plugin" in out_lines else: - assert 'plugins.file_tracers: plugin_sys_info.Plugin (disabled)' in out_lines - assert 'plugins.configurers: -none-' in out_lines + assert "plugins.file_tracers: plugin_sys_info.Plugin (disabled)" in out_lines + assert "plugins.configurers: -none-" in out_lines expected_end = [ "-- sys: plugin_sys_info.Plugin -------------------------------", "hello: world", "-- end -------------------------------------------------------", ] - assert expected_end == out_lines[-len(expected_end):] + assert expected_end == out_lines[-len(expected_end) :] def test_plugin_with_no_sys_info(self) -> None: - self.make_file("plugin_no_sys_info.py", """\ + self.make_file( + "plugin_no_sys_info.py", + """\ import coverage class Plugin(coverage.CoveragePlugin): @@ -234,37 +258,44 @@ class Plugin(coverage.CoveragePlugin): def coverage_init(reg, options): reg.add_configurer(Plugin()) - """) + """, + ) debug_out = io.StringIO() cov = coverage.Coverage(debug=["sys"]) cov._debug_file = debug_out cov.set_option("run:plugins", ["plugin_no_sys_info"]) cov.start() - cov.stop() # pragma: nested + cov.stop() # pragma: nested out_lines = [line.strip() for line in debug_out.getvalue().splitlines()] - assert 'plugins.file_tracers: -none-' in out_lines - assert 'plugins.configurers: plugin_no_sys_info.Plugin' in out_lines + assert "plugins.file_tracers: -none-" in out_lines + assert "plugins.configurers: plugin_no_sys_info.Plugin" in out_lines expected_end = [ "-- sys: plugin_no_sys_info.Plugin ----------------------------", "-- end -------------------------------------------------------", ] - assert expected_end == out_lines[-len(expected_end):] + assert expected_end == out_lines[-len(expected_end) :] def test_local_files_are_importable(self) -> None: - self.make_file("importing_plugin.py", """\ + self.make_file( + "importing_plugin.py", + """\ from coverage import CoveragePlugin import local_module class MyPlugin(CoveragePlugin): pass def coverage_init(reg, options): reg.add_noop(MyPlugin()) - """) + """, + ) self.make_file("local_module.py", "CONST = 1") - self.make_file(".coveragerc", """\ + self.make_file( + ".coveragerc", + """\ [run] plugins = importing_plugin - """) + """, + ) self.make_file("main_file.py", "print('MAIN')") out = self.run_command("coverage run main_file.py") @@ -276,6 +307,7 @@ def coverage_init(reg, options): @pytest.mark.skipif(testenv.PLUGINS, reason="This core doesn't support plugins.") class PluginWarningOnPyTracerTest(CoverageTest): """Test that we get a controlled exception when plugins aren't supported.""" + def test_exception_if_plugins_on_pytracer(self) -> None: self.make_file("simple.py", "a = 1") @@ -289,7 +321,7 @@ def test_exception_if_plugins_on_pytracer(self) -> None: core = "SysMonitor" expected_warnings = [ - fr"Plugin file tracers \(tests.plugin1.Plugin\) aren't supported with {core}", + rf"Plugin file tracers \(tests.plugin1.Plugin\) aren't supported with {core}", ] with self.assert_warnings(cov, expected_warnings): self.start_import_stop(cov, "simple") @@ -304,19 +336,25 @@ class GoodFileTracerTest(FileTracerTest): """Tests of file tracer plugin happy paths.""" def test_plugin1(self) -> None: - self.make_file("simple.py", """\ + self.make_file( + "simple.py", + """\ import try_xyz a = 1 b = 2 - """) - self.make_file("try_xyz.py", """\ + """, + ) + self.make_file( + "try_xyz.py", + """\ c = 3 d = 4 - """) + """, + ) cov = coverage.Coverage() - CheckUniqueFilenames.hook(cov, '_should_trace') - CheckUniqueFilenames.hook(cov, '_check_include_omit_etc') + CheckUniqueFilenames.hook(cov, "_should_trace") + CheckUniqueFilenames.hook(cov, "_check_include_omit_etc") cov.set_option("run:plugins", ["tests.plugin1"]) # Import the Python file, executing it. @@ -334,7 +372,9 @@ def make_render_and_caller(self) -> None: # plugin2 emulates a dynamic tracing plugin: the caller's locals # are examined to determine the source file and line number. # The plugin is in tests/plugin2.py. - self.make_file("render.py", """\ + self.make_file( + "render.py", + """\ def render(filename, linenum): # This function emulates a template renderer. The plugin # will examine the `filename` and `linenum` locals to @@ -346,8 +386,11 @@ def helper(x): # This function is here just to show that not all code in # this file will be part of the dynamic tracing. return x+1 - """) - self.make_file("caller.py", """\ + """, + ) + self.make_file( + "caller.py", + """\ import sys from render import helper, render @@ -361,7 +404,8 @@ def helper(x): # quux_5.html will be omitted from the results. assert render("quux_5.html", 3) == "[quux_5.html @ 3]" - """) + """, + ) # will try to read the actual source files, so make some # source files. @@ -376,8 +420,8 @@ def test_plugin2(self) -> None: self.make_render_and_caller() cov = coverage.Coverage(omit=["*quux*"]) - CheckUniqueFilenames.hook(cov, '_should_trace') - CheckUniqueFilenames.hook(cov, '_check_include_omit_etc') + CheckUniqueFilenames.hook(cov, "_should_trace") + CheckUniqueFilenames.hook(cov, "_check_include_omit_etc") cov.set_option("run:plugins", ["tests.plugin2"]) self.start_import_stop(cov, "caller") @@ -401,8 +445,8 @@ def test_plugin2_with_branch(self) -> None: self.make_render_and_caller() cov = coverage.Coverage(branch=True, omit=["*quux*"]) - CheckUniqueFilenames.hook(cov, '_should_trace') - CheckUniqueFilenames.hook(cov, '_check_include_omit_etc') + CheckUniqueFilenames.hook(cov, "_should_trace") + CheckUniqueFilenames.hook(cov, "_check_include_omit_etc") cov.set_option("run:plugins", ["tests.plugin2"]) self.start_import_stop(cov, "caller") @@ -410,7 +454,7 @@ def test_plugin2_with_branch(self) -> None: # The way plugin2 works, a file named foo_7.html will be claimed to # have 7 lines in it. If render() was called with line number 4, # then the plugin will claim that lines 4 and 5 were executed. - analysis = cov._analyze("foo_7.html") + analysis = cov.analyze("foo_7.html") assert analysis.statements == {1, 2, 3, 4, 5, 6, 7} # Plugins don't do branch coverage yet. assert analysis.has_arcs is True @@ -430,12 +474,12 @@ def test_plugin2_with_text_report(self) -> None: total = cov.report(file=repout, include=["*.html"], omit=["uni*.html"], show_missing=True) report = repout.getvalue().splitlines() expected = [ - 'Name Stmts Miss Branch BrPart Cover Missing', - '--------------------------------------------------------', - 'bar_4.html 4 2 0 0 50% 1, 4', - 'foo_7.html 7 5 0 0 29% 1-3, 6-7', - '--------------------------------------------------------', - 'TOTAL 11 7 0 0 36%', + "Name Stmts Miss Branch BrPart Cover Missing", + "--------------------------------------------------------", + "bar_4.html 4 2 0 0 50% 1, 4", + "foo_7.html 7 5 0 0 29% 1-3, 6-7", + "--------------------------------------------------------", + "TOTAL 11 7 0 0 36%", ] assert expected == report assert math.isclose(total, 4 / 11 * 100) @@ -469,26 +513,28 @@ def test_plugin2_with_xml_report(self) -> None: dom = ElementTree.parse("coverage.xml") classes = {} for elt in dom.findall(".//class"): - classes[elt.get('name')] = elt - - assert classes['bar_4.html'].attrib == { - 'branch-rate': '1', - 'complexity': '0', - 'filename': 'bar_4.html', - 'line-rate': '0.5', - 'name': 'bar_4.html', + classes[elt.get("name")] = elt + + assert classes["bar_4.html"].attrib == { + "branch-rate": "1", + "complexity": "0", + "filename": "bar_4.html", + "line-rate": "0.5", + "name": "bar_4.html", } - assert classes['foo_7.html'].attrib == { - 'branch-rate': '1', - 'complexity': '0', - 'filename': 'foo_7.html', - 'line-rate': '0.2857', - 'name': 'foo_7.html', + assert classes["foo_7.html"].attrib == { + "branch-rate": "1", + "complexity": "0", + "filename": "foo_7.html", + "line-rate": "0.2857", + "name": "foo_7.html", } 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", """\ + self.make_file( + "fairly_odd_plugin.py", + """\ # A plugin that claims all the odd lines are executed, and none of # the even lines, and then punts reporting off to the built-in # Python reporting. @@ -513,15 +559,19 @@ def line_number_range(self, frame): def coverage_init(reg, options): reg.add_file_tracer(Plugin()) - """) - self.make_file("unsuspecting.py", """\ + """, + ) + self.make_file( + "unsuspecting.py", + """\ a = 1 b = 2 c = 3 d = 4 e = 5 f = 6 - """) + """, + ) cov = coverage.Coverage(include=["unsuspecting.py"]) cov.set_option("run:plugins", ["fairly_odd_plugin"]) self.start_import_stop(cov, "unsuspecting") @@ -530,17 +580,19 @@ def coverage_init(reg, options): total = cov.report(file=repout, show_missing=True) report = repout.getvalue().splitlines() expected = [ - 'Name Stmts Miss Cover Missing', - '-----------------------------------------------', - 'unsuspecting.py 6 3 50% 2, 4, 6', - '-----------------------------------------------', - 'TOTAL 6 3 50%', + "Name Stmts Miss Cover Missing", + "-----------------------------------------------", + "unsuspecting.py 6 3 50% 2, 4, 6", + "-----------------------------------------------", + "TOTAL 6 3 50%", ] assert expected == report assert total == 50 def test_find_unexecuted(self) -> None: - self.make_file("unexecuted_plugin.py", """\ + self.make_file( + "unexecuted_plugin.py", + """\ import os import coverage.plugin class Plugin(coverage.CoveragePlugin): @@ -570,9 +622,10 @@ def lines(self): def coverage_init(reg, options): reg.add_file_tracer(Plugin()) - """) + """, + ) self.make_file("foo.py", "a = 1") - cov = coverage.Coverage(source=['.']) + cov = coverage.Coverage(source=["."]) cov.set_option("run:plugins", ["unexecuted_plugin"]) self.start_import_stop(cov, "foo") @@ -603,23 +656,32 @@ def run_plugin(self, module_name: str) -> Coverage: Returns the Coverage object. """ - self.make_file("simple.py", """\ + self.make_file( + "simple.py", + """\ import other, another a = other.f(2) b = other.f(3) c = another.g(4) d = another.g(5) - """) + """, + ) # The names of these files are important: some plugins apply themselves # to "*other.py". - self.make_file("other.py", """\ + self.make_file( + "other.py", + """\ def f(x): return x+1 - """) - self.make_file("another.py", """\ + """, + ) + self.make_file( + "another.py", + """\ def g(x): return x-1 - """) + """, + ) cov = coverage.Coverage() cov.set_option("run:plugins", [module_name]) @@ -675,21 +737,26 @@ def run_bad_plugin( if excmsg: assert excmsg in stderr if excmsgs: - found_exc = any(em in stderr for em in excmsgs) # pragma: part covered + 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) -> None: - self.make_file("bad_plugin.py", """\ + self.make_file( + "bad_plugin.py", + """\ class Plugin(object): pass def coverage_init(reg, options): reg.add_file_tracer(Plugin()) - """) + """, + ) self.run_bad_plugin("bad_plugin", "Plugin", our_error=False) def test_file_tracer_has_inherited_sourcefilename_method(self) -> None: - self.make_file("bad_plugin.py", """\ + self.make_file( + "bad_plugin.py", + """\ import coverage class Plugin(coverage.CoveragePlugin): def file_tracer(self, filename): @@ -701,14 +768,19 @@ class FileTracer(coverage.FileTracer): def coverage_init(reg, options): reg.add_file_tracer(Plugin()) - """) + """, + ) self.run_bad_plugin( - "bad_plugin", "Plugin", our_error=False, + "bad_plugin", + "Plugin", + our_error=False, excmsg="Class 'bad_plugin.FileTracer' needs to implement source_filename()", ) def test_plugin_has_inherited_filereporter_method(self) -> None: - self.make_file("bad_plugin.py", """\ + self.make_file( + "bad_plugin.py", + """\ import coverage class Plugin(coverage.CoveragePlugin): def file_tracer(self, filename): @@ -721,14 +793,17 @@ def source_filename(self): def coverage_init(reg, options): reg.add_file_tracer(Plugin()) - """) + """, + ) cov = self.run_plugin("bad_plugin") expected_msg = "Plugin 'bad_plugin.Plugin' needs to implement file_reporter()" with pytest.raises(NotImplementedError, match=expected_msg): cov.report() def test_file_tracer_fails(self) -> None: - self.make_file("bad_plugin.py", """\ + self.make_file( + "bad_plugin.py", + """\ import coverage.plugin class Plugin(coverage.plugin.CoveragePlugin): def file_tracer(self, filename): @@ -736,13 +811,16 @@ def file_tracer(self, filename): def coverage_init(reg, options): reg.add_file_tracer(Plugin()) - """) + """, + ) self.run_bad_plugin("bad_plugin", "Plugin") 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", """\ + self.make_file( + "bad_plugin.py", + """\ import os.path import coverage.plugin class Plugin(coverage.plugin.CoveragePlugin): @@ -767,11 +845,14 @@ def line_number_range(self, frame): def coverage_init(reg, options): reg.add_file_tracer(Plugin()) - """) + """, + ) self.run_bad_plugin("bad_plugin", "Plugin") def test_file_tracer_returns_wrong(self) -> None: - self.make_file("bad_plugin.py", """\ + self.make_file( + "bad_plugin.py", + """\ import coverage.plugin class Plugin(coverage.plugin.CoveragePlugin): def file_tracer(self, filename): @@ -779,13 +860,19 @@ def file_tracer(self, filename): def coverage_init(reg, options): reg.add_file_tracer(Plugin()) - """) + """, + ) self.run_bad_plugin( - "bad_plugin", "Plugin", our_error=False, excmsg="'float' object has no attribute", + "bad_plugin", + "Plugin", + our_error=False, + excmsg="'float' object has no attribute", ) def test_has_dynamic_source_filename_fails(self) -> None: - self.make_file("bad_plugin.py", """\ + self.make_file( + "bad_plugin.py", + """\ import coverage.plugin class Plugin(coverage.plugin.CoveragePlugin): def file_tracer(self, filename): @@ -797,11 +884,14 @@ def has_dynamic_source_filename(self): def coverage_init(reg, options): reg.add_file_tracer(Plugin()) - """) + """, + ) self.run_bad_plugin("bad_plugin", "Plugin") def test_source_filename_fails(self) -> None: - self.make_file("bad_plugin.py", """\ + self.make_file( + "bad_plugin.py", + """\ import coverage.plugin class Plugin(coverage.plugin.CoveragePlugin): def file_tracer(self, filename): @@ -813,11 +903,14 @@ def source_filename(self): def coverage_init(reg, options): reg.add_file_tracer(Plugin()) - """) + """, + ) self.run_bad_plugin("bad_plugin", "Plugin") def test_source_filename_returns_wrong(self) -> None: - self.make_file("bad_plugin.py", """\ + self.make_file( + "bad_plugin.py", + """\ import coverage.plugin class Plugin(coverage.plugin.CoveragePlugin): def file_tracer(self, filename): @@ -829,9 +922,12 @@ def source_filename(self): def coverage_init(reg, options): reg.add_file_tracer(Plugin()) - """) + """, + ) self.run_bad_plugin( - "bad_plugin", "Plugin", our_error=False, + "bad_plugin", + "Plugin", + our_error=False, excmsgs=[ "expected str, bytes or os.PathLike object, not float", "'float' object has no attribute", @@ -841,7 +937,9 @@ def coverage_init(reg, options): ) def test_dynamic_source_filename_fails(self) -> None: - self.make_file("bad_plugin.py", """\ + self.make_file( + "bad_plugin.py", + """\ import coverage.plugin class Plugin(coverage.plugin.CoveragePlugin): def file_tracer(self, filename): @@ -856,11 +954,14 @@ def dynamic_source_filename(self, filename, frame): def coverage_init(reg, options): reg.add_file_tracer(Plugin()) - """) + """, + ) self.run_bad_plugin("bad_plugin", "Plugin") def test_line_number_range_raises_error(self) -> None: - self.make_file("bad_plugin.py", """\ + self.make_file( + "bad_plugin.py", + """\ import coverage.plugin class Plugin(coverage.plugin.CoveragePlugin): def file_tracer(self, filename): @@ -876,13 +977,19 @@ def line_number_range(self, frame): def coverage_init(reg, options): reg.add_file_tracer(Plugin()) - """) + """, + ) self.run_bad_plugin( - "bad_plugin", "Plugin", our_error=False, excmsg="borked!", + "bad_plugin", + "Plugin", + our_error=False, + excmsg="borked!", ) def test_line_number_range_returns_non_tuple(self) -> None: - self.make_file("bad_plugin.py", """\ + self.make_file( + "bad_plugin.py", + """\ import coverage.plugin class Plugin(coverage.plugin.CoveragePlugin): def file_tracer(self, filename): @@ -898,13 +1005,19 @@ def line_number_range(self, frame): def coverage_init(reg, options): reg.add_file_tracer(Plugin()) - """) + """, + ) self.run_bad_plugin( - "bad_plugin", "Plugin", our_error=False, excmsg="line_number_range must return 2-tuple", + "bad_plugin", + "Plugin", + our_error=False, + excmsg="line_number_range must return 2-tuple", ) def test_line_number_range_returns_triple(self) -> None: - self.make_file("bad_plugin.py", """\ + self.make_file( + "bad_plugin.py", + """\ import coverage.plugin class Plugin(coverage.plugin.CoveragePlugin): def file_tracer(self, filename): @@ -920,13 +1033,19 @@ def line_number_range(self, frame): def coverage_init(reg, options): reg.add_file_tracer(Plugin()) - """) + """, + ) self.run_bad_plugin( - "bad_plugin", "Plugin", our_error=False, excmsg="line_number_range must return 2-tuple", + "bad_plugin", + "Plugin", + our_error=False, + excmsg="line_number_range must return 2-tuple", ) def test_line_number_range_returns_pair_of_strings(self) -> None: - self.make_file("bad_plugin.py", """\ + self.make_file( + "bad_plugin.py", + """\ import coverage.plugin class Plugin(coverage.plugin.CoveragePlugin): def file_tracer(self, filename): @@ -942,9 +1061,12 @@ def line_number_range(self, frame): def coverage_init(reg, options): reg.add_file_tracer(Plugin()) - """) + """, + ) self.run_bad_plugin( - "bad_plugin", "Plugin", our_error=False, + "bad_plugin", + "Plugin", + our_error=False, excmsgs=[ "an integer is required", "cannot be interpreted as an integer", @@ -961,7 +1083,7 @@ def test_configurer_plugin(self) -> None: cov = coverage.Coverage() cov.set_option("run:plugins", ["tests.plugin_config"]) cov.start() - cov.stop() # pragma: nested + cov.stop() # pragma: nested excluded = cov.get_option("report:exclude_lines") assert isinstance(excluded, list) assert "pragma: custom" in excluded @@ -974,7 +1096,9 @@ class DynamicContextPluginTest(CoverageTest): def make_plugin_capitalized_testnames(self, filename: str) -> None: """Create a dynamic context plugin that capitalizes the part after 'test_'.""" - self.make_file(filename, """\ + self.make_file( + filename, + """\ from coverage import CoveragePlugin class Plugin(CoveragePlugin): @@ -987,11 +1111,14 @@ def dynamic_context(self, frame): def coverage_init(reg, options): reg.add_dynamic_context(Plugin()) - """) + """, + ) def make_plugin_track_render(self, filename: str) -> None: """Make a dynamic context plugin that tracks 'render_' functions.""" - self.make_file(filename, """\ + self.make_file( + filename, + """\ from coverage import CoveragePlugin class Plugin(CoveragePlugin): @@ -1003,11 +1130,14 @@ def dynamic_context(self, frame): def coverage_init(reg, options): reg.add_dynamic_context(Plugin()) - """) + """, + ) def make_test_files(self) -> None: """Make some files to use while testing dynamic context plugins.""" - self.make_file("rendering.py", """\ + self.make_file( + "rendering.py", + """\ def html_tag(tag, content): return f'<{tag}>{content}' @@ -1019,9 +1149,12 @@ def render_span(text): def render_bold(text): return html_tag('b', text) - """) + """, + ) - self.make_file("testsuite.py", """\ + self.make_file( + "testsuite.py", + """\ import rendering def test_html_tag() -> None: @@ -1042,9 +1175,10 @@ def build_full_html(): rendering.render_paragraph( rendering.render_span('hello'))) return html - """) + """, + ) - def run_all_functions(self, cov: Coverage, suite_name: str) -> None: # pragma: nested + def run_all_functions(self, cov: Coverage, suite_name: str) -> None: # pragma: nested """Run all functions in `suite_name` under coverage.""" cov.start() suite = import_local_file(suite_name) @@ -1058,60 +1192,60 @@ def run_all_functions(self, cov: Coverage, suite_name: str) -> None: # pragma cov.stop() def test_plugin_standalone(self) -> None: - self.make_plugin_capitalized_testnames('plugin_tests.py') + self.make_plugin_capitalized_testnames("plugin_tests.py") self.make_test_files() # Enable dynamic context plugin cov = coverage.Coverage() - cov.set_option("run:plugins", ['plugin_tests']) + cov.set_option("run:plugins", ["plugin_tests"]) # Run the tests - self.run_all_functions(cov, 'testsuite') + self.run_all_functions(cov, "testsuite") # Labeled coverage is collected data = cov.get_data() filenames = self.get_measured_filenames(data) - expected = ['', 'doctest:HTML_TAG', 'test:HTML_TAG', 'test:RENDERERS'] + expected = ["", "doctest:HTML_TAG", "test:HTML_TAG", "test:RENDERERS"] assert expected == sorted(data.measured_contexts()) data.set_query_context("doctest:HTML_TAG") - assert [2] == sorted_lines(data, filenames['rendering.py']) + assert [2] == sorted_lines(data, filenames["rendering.py"]) data.set_query_context("test:HTML_TAG") - assert [2] == sorted_lines(data, filenames['rendering.py']) + assert [2] == sorted_lines(data, filenames["rendering.py"]) data.set_query_context("test:RENDERERS") - assert [2, 5, 8, 11] == sorted_lines(data, filenames['rendering.py']) + assert [2, 5, 8, 11] == sorted_lines(data, filenames["rendering.py"]) def test_static_context(self) -> None: - self.make_plugin_capitalized_testnames('plugin_tests.py') + self.make_plugin_capitalized_testnames("plugin_tests.py") self.make_test_files() # Enable dynamic context plugin for coverage with named context - cov = coverage.Coverage(context='mytests') - cov.set_option("run:plugins", ['plugin_tests']) + cov = coverage.Coverage(context="mytests") + cov.set_option("run:plugins", ["plugin_tests"]) # Run the tests - self.run_all_functions(cov, 'testsuite') + self.run_all_functions(cov, "testsuite") # Static context prefix is preserved data = cov.get_data() expected = [ - 'mytests', - 'mytests|doctest:HTML_TAG', - 'mytests|test:HTML_TAG', - 'mytests|test:RENDERERS', + "mytests", + "mytests|doctest:HTML_TAG", + "mytests|test:HTML_TAG", + "mytests|test:RENDERERS", ] assert expected == sorted(data.measured_contexts()) def test_plugin_with_test_function(self) -> None: - self.make_plugin_capitalized_testnames('plugin_tests.py') + self.make_plugin_capitalized_testnames("plugin_tests.py") self.make_test_files() # Enable both a plugin and test_function dynamic context cov = coverage.Coverage() - cov.set_option("run:plugins", ['plugin_tests']) + cov.set_option("run:plugins", ["plugin_tests"]) cov.set_option("run:dynamic_context", "test_function") # Run the tests - self.run_all_functions(cov, 'testsuite') + self.run_all_functions(cov, "testsuite") # test_function takes precedence over plugins - only # functions that are not labeled by test_function are @@ -1119,31 +1253,31 @@ def test_plugin_with_test_function(self) -> None: data = cov.get_data() filenames = self.get_measured_filenames(data) expected = [ - '', - 'doctest:HTML_TAG', - 'testsuite.test_html_tag', - 'testsuite.test_renderers', + "", + "doctest:HTML_TAG", + "testsuite.test_html_tag", + "testsuite.test_renderers", ] assert expected == sorted(data.measured_contexts()) def assert_context_lines(context: str, lines: list[TLineNo]) -> None: data.set_query_context(context) - assert lines == sorted_lines(data, filenames['rendering.py']) + assert lines == sorted_lines(data, filenames["rendering.py"]) assert_context_lines("doctest:HTML_TAG", [2]) assert_context_lines("testsuite.test_html_tag", [2]) assert_context_lines("testsuite.test_renderers", [2, 5, 8, 11]) def test_multiple_plugins(self) -> None: - self.make_plugin_capitalized_testnames('plugin_tests.py') - self.make_plugin_track_render('plugin_renderers.py') + self.make_plugin_capitalized_testnames("plugin_tests.py") + self.make_plugin_track_render("plugin_renderers.py") self.make_test_files() # Enable two plugins cov = coverage.Coverage() - cov.set_option("run:plugins", ['plugin_renderers', 'plugin_tests']) + cov.set_option("run:plugins", ["plugin_renderers", "plugin_tests"]) - self.run_all_functions(cov, 'testsuite') + self.run_all_functions(cov, "testsuite") # It is important to note, that line 11 (render_bold function) is never # labeled as renderer:bold context, because it is only called from @@ -1155,18 +1289,18 @@ def test_multiple_plugins(self) -> None: data = cov.get_data() filenames = self.get_measured_filenames(data) expected = [ - '', - 'doctest:HTML_TAG', - 'renderer:paragraph', - 'renderer:span', - 'test:HTML_TAG', - 'test:RENDERERS', + "", + "doctest:HTML_TAG", + "renderer:paragraph", + "renderer:span", + "test:HTML_TAG", + "test:RENDERERS", ] assert expected == sorted(data.measured_contexts()) def assert_context_lines(context: str, lines: list[TLineNo]) -> None: data.set_query_context(context) - assert lines == sorted_lines(data, filenames['rendering.py']) + assert lines == sorted_lines(data, filenames["rendering.py"]) assert_context_lines("test:HTML_TAG", [2]) assert_context_lines("test:RENDERERS", [2, 5, 8, 11])