Skip to content

Commit 8ef4157

Browse files
committed
Provide an API for passing plugins to the Coverage constructor
1 parent bb68f99 commit 8ef4157

File tree

3 files changed

+56
-15
lines changed

3 files changed

+56
-15
lines changed

coverage/control.py

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import sys
1717
import threading
1818
import time
19+
import typing
1920
import warnings
2021

2122
from types import FrameType
@@ -43,14 +44,14 @@
4344
from coverage.misc import DefaultValue, ensure_dir_for_file, isolate_module
4445
from coverage.multiproc import patch_multiprocessing
4546
from coverage.plugin import FileReporter
46-
from coverage.plugin_support import Plugins
47+
from coverage.plugin_support import Plugins, TCoverageInit
4748
from coverage.python import PythonFileReporter
4849
from coverage.report import SummaryReporter
4950
from coverage.report_core import render_report
5051
from coverage.results import Analysis, analysis_from_file_reporter
5152
from coverage.types import (
5253
FilePath, TConfigurable, TConfigSectionIn, TConfigValueIn, TConfigValueOut,
53-
TFileDisposition, TLineNo, TMorf,
54+
TFileDisposition, TLineNo, TMorf
5455
)
5556
from coverage.xmlreport import XmlReporter
5657

@@ -138,6 +139,7 @@ def __init__( # pylint: disable=too-many-arguments
138139
check_preimported: bool = False,
139140
context: str | None = None,
140141
messages: bool = False,
142+
plugins: Iterable[Callable] | None = None,
141143
) -> None:
142144
"""
143145
Many of these arguments duplicate and override values that can be
@@ -211,6 +213,12 @@ def __init__( # pylint: disable=too-many-arguments
211213
If `messages` is true, some messages will be printed to stdout
212214
indicating what is happening.
213215
216+
If `plugins` are passed, they are an iterable of functions with the
217+
`coverage_init` signature (meaning they take a plugin registry and a
218+
dictionary of configuration options as arguments). When they are
219+
provided, they will override the plugins found in the coverage
220+
configuration file.
221+
214222
.. versionadded:: 4.0
215223
The `concurrency` parameter.
216224
@@ -260,6 +268,10 @@ def __init__( # pylint: disable=too-many-arguments
260268
self._debug: DebugControl = NoDebugging()
261269
self._inorout: InOrOut | None = None
262270
self._plugins: Plugins = Plugins()
271+
self._plugin_override = typing.cast(
272+
typing.Union[Iterable[TCoverageInit], None],
273+
plugins
274+
)
263275
self._data: CoverageData | None = None
264276
self._core: Core | None = None
265277
self._collector: Collector | None = None
@@ -340,7 +352,12 @@ def _init(self) -> None:
340352
self._file_mapper = relative_filename
341353

342354
# Load plugins
343-
self._plugins = Plugins.load_plugins(self.config.plugins, self.config, self._debug)
355+
self._plugins = Plugins.load_plugins(
356+
self.config.plugins,
357+
self.config,
358+
self._debug,
359+
plugin_override=self._plugin_override
360+
)
344361

345362
# Run configuring plugins.
346363
for plugin in self._plugins.configurers:

coverage/plugin_support.py

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,14 @@
1111

1212
from types import FrameType
1313
from typing import Any
14+
from typing import Callable
1415
from collections.abc import Iterable, Iterator
1516

1617
from coverage.exceptions import PluginError
1718
from coverage.misc import isolate_module
1819
from coverage.plugin import CoveragePlugin, FileTracer, FileReporter
1920
from coverage.types import (
20-
TArc, TConfigurable, TDebugCtl, TLineNo, TPluginConfig, TSourceTokenLines,
21+
TArc, TConfigurable, TDebugCtl, TLineNo, TPluginConfig, TSourceTokenLines, TConfigSectionOut
2122
)
2223

2324
os = isolate_module(os)
@@ -42,6 +43,7 @@ def load_plugins(
4243
modules: Iterable[str],
4344
config: TPluginConfig,
4445
debug: TDebugCtl | None = None,
46+
plugin_override: Iterable[TCoverageInit] | None = None,
4547
) -> Plugins:
4648
"""Load plugins from `modules`.
4749
@@ -51,19 +53,23 @@ def load_plugins(
5153
plugins = cls()
5254
plugins.debug = debug
5355

54-
for module in modules:
55-
plugins.current_module = module
56-
__import__(module)
57-
mod = sys.modules[module]
56+
if plugin_override is not None:
57+
for override in plugin_override:
58+
override(plugins, {})
59+
else:
60+
for module in modules:
61+
plugins.current_module = module
62+
__import__(module)
63+
mod = sys.modules[module]
5864

59-
coverage_init = getattr(mod, "coverage_init", None)
60-
if not coverage_init:
61-
raise PluginError(
62-
f"Plugin module {module!r} didn't define a coverage_init function",
63-
)
65+
coverage_init = getattr(mod, "coverage_init", None)
66+
if not coverage_init:
67+
raise PluginError(
68+
f"Plugin module {module!r} didn't define a coverage_init function",
69+
)
6470

65-
options = config.get_plugin_options(module)
66-
coverage_init(plugins, options)
71+
options = config.get_plugin_options(module)
72+
coverage_init(plugins, options)
6773

6874
plugins.current_module = None
6975
return plugins
@@ -138,6 +144,9 @@ def get(self, plugin_name: str) -> CoveragePlugin:
138144
return self.names[plugin_name]
139145

140146

147+
TCoverageInit = Callable[[Plugins, TConfigSectionOut], None]
148+
149+
141150
class LabelledDebug:
142151
"""A Debug writer, but with labels for prepending to the messages."""
143152

tests/test_plugins.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,21 @@ def coverage_init(reg, options):
272272
out = self.run_command("coverage html -q") # sneak in a test of -q
273273
assert out == ""
274274

275+
def test_coverage_init_plugins(self) -> None:
276+
called = False
277+
def coverage_init(
278+
reg: Plugins, # pylint: disable=unused-argument
279+
options: TConfigSectionOut # pylint: disable=unused-argument
280+
) -> None:
281+
nonlocal called
282+
called = True
283+
284+
cov = coverage.Coverage(plugins=[coverage_init])
285+
# Calls _init() and loads plugins
286+
cov.sys_info()
287+
288+
assert called
289+
275290

276291
@pytest.mark.skipif(testenv.PLUGINS, reason="This core doesn't support plugins.")
277292
class PluginWarningOnPyTracerTest(CoverageTest):

0 commit comments

Comments
 (0)