Skip to content
Merged
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
15 changes: 15 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -574,6 +574,21 @@ Important notes:
* Marked with the ``@diff_cover_hookimpl`` decorator
* Named ``diff_cover_report_quality``. (This distinguishes it from any other
plugin types ``diff_cover`` may support.)
* The function may declare either, both, or neither of the hook's arguments:

* ``reports`` - the list of open file handles for the pre-generated reports
passed on the command line.
* ``options`` - the string given to ``--options``.

Only the arguments you declare are passed, so the zero-argument form above
keeps working. A plugin that wants the user's input would be written as:

.. code:: python

@diff_cover_hookimpl
def diff_cover_report_quality(reports, options):
return SQLFluffViolationReporter(reports=reports, options=options)

* The function should return an object with the following properties and methods:

* ``supported_extensions`` property with a list of supported file extensions
Expand Down
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 @@ -299,6 +300,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 @@ -368,8 +390,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 393 in diff_cover/diff_quality_tool.py

View workflow job for this annotation

GitHub Actions / coverage

Missing Coverage

Line 393 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): # pylint: disable=unused-argument
"""
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.
"""
36 changes: 35 additions & 1 deletion tests/test_diff_quality_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,15 @@

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

import pluggy
import pytest

from diff_cover.diff_quality_tool import main, parse_quality_args
from diff_cover import hookspecs
from diff_cover.diff_quality_tool import (
_call_reporter_factory,
main,
parse_quality_args,
)


def test_parse_with_html_report():
Expand Down Expand Up @@ -158,3 +164,31 @@ def test_parse_format_from_config_file(tmp_path):
)

assert arg_dict.get("format") == {"html": "report.html"}


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):
assert _call_reporter_factory(factory, ["report"], "--foobar") == expected
Loading