Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 24 additions & 2 deletions diff_cover/diff_quality_tool.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import argparse
import contextlib
import inspect
import io
import logging
import os
Expand Down Expand Up @@ -297,6 +298,27 @@
return reporter.total_percent_covered()


def _call_reporter_factory(factory_fn, reports, options):
"""
Call a plugin's ``diff_cover_report_quality`` implementation.

Plugins are free to declare only the arguments they need (including none
at all, as documented in the README), so pass only what the function
actually accepts.
"""
available = {"reports": reports, "options": options}
try:
parameters = inspect.signature(factory_fn).parameters
except (TypeError, ValueError): # pragma: no cover - builtins/C callables
return factory_fn(**available)

if any(p.kind is inspect.Parameter.VAR_KEYWORD for p in parameters.values()):
return factory_fn(**available)

kwargs = {name: value for name, value in available.items() if name in parameters}
return factory_fn(**kwargs)


def main(argv=None, directory=None):
"""
Main entry point for the tool, script installed via pyproject.toml
Expand Down Expand Up @@ -366,8 +388,8 @@

reporter = QualityReporter(driver, input_reports, user_options)
elif reporter_factory_fn:
reporter = reporter_factory_fn(
reports=input_reports, options=user_options
reporter = _call_reporter_factory(

Check warning on line 391 in diff_cover/diff_quality_tool.py

View workflow job for this annotation

GitHub Actions / coverage

Missing Coverage

Line 391 missing coverage
reporter_factory_fn, input_reports, user_options
)

percent_passing = generate_quality_report(
Expand Down
6 changes: 5 additions & 1 deletion diff_cover/hookspecs.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,13 @@


@hookspec
def diff_cover_report_quality():
def diff_cover_report_quality(reports, options):
"""
Return a 2-part tuple:
- Quality plugin name
- Object that implements the BaseViolationReporter protocol

``reports`` is the list of open pre-generated report file handles and
``options`` the user options string; both are passed by ``diff-quality``.
A plugin may declare either or neither argument.
"""
32 changes: 32 additions & 0 deletions tests/test_diff_quality_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@

"""Test for diff_cover.diff_quality - main"""

import pluggy
import pytest

from diff_cover import hookspecs
from diff_cover.diff_quality_tool import main, parse_quality_args


Expand Down Expand Up @@ -147,3 +149,33 @@ def _run_main(report, argv):
quality_reporter = report.call_args[0][0]
assert quality_reporter.driver.name == "pylint"
assert quality_reporter.options == "--foobar"


def test_plugin_may_declare_hook_arguments():
"""A plugin declaring reports/options must validate against the hookspec."""
hookimpl = pluggy.HookimplMarker("diff_cover")

class Plugin:
@hookimpl
def diff_cover_report_quality(self, reports, options):
return (reports, options)

plugin_manager = pluggy.PluginManager("diff_cover")
plugin_manager.add_hookspecs(hookspecs)
plugin_manager.register(Plugin(), name="myplugin")


@pytest.mark.parametrize(
"factory,expected",
[
(lambda: "none", "none"),
(lambda options: options, "--foobar"),
(lambda reports: reports, ["report"]),
(lambda reports, options: (reports, options), (["report"], "--foobar")),
(lambda **kwargs: kwargs, {"reports": ["report"], "options": "--foobar"}),
],
)
def test_call_reporter_factory_passes_declared_arguments(factory, expected):
from diff_cover.diff_quality_tool import _call_reporter_factory

assert _call_reporter_factory(factory, ["report"], "--foobar") == expected
Loading