Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: testing function #201

Merged
merged 1 commit into from
Apr 19, 2024
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
8 changes: 8 additions & 0 deletions docs/api/repo_review.rst
Original file line number Diff line number Diff line change
Expand Up @@ -72,3 +72,11 @@ repo\_review.schema module
:members:
:undoc-members:
:show-inheritance:

repo\_review.testing module
---------------------------

.. automodule:: repo_review.testing
:members:
:undoc-members:
:show-inheritance:
14 changes: 14 additions & 0 deletions docs/plugins.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,20 @@ You have `processed.results` and `processed.families` from the return of
{func}`~repo_review.processor.collect_all` to get `.fixtures`, `.checks`, and
`.families`.

### Unit testing

You can also run unit tests with the {func}`~repo_review.testing.compute_check` helper. It is used like this:

```python
def test_has_tool_ruff_unit() -> None:
assert repo_review.testing.compute_check("RF001", ruff={}).result
assert not repo_review.testing.compute_check("RF001", ruff=None).result
```

It takes the check name and any fixtures as keyword arguments. It returns a
{class}`~repo_review.checks.Check` instance, so you can see if the `.result` is
`True`/`False`/`None`, or check any of the other properties.

## An existing package

Since writing a plugin does not require depending on repo-review, you can also
Expand Down
23 changes: 23 additions & 0 deletions src/repo_review/checks.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,3 +120,26 @@ def get_check_description(name: str, check: Check) -> str:
.. versionadded:: 0.8
"""
return (check.__doc__ or "").format(self=check, name=name)


def process_result_bool(
result: str | bool | None, check: Check, name: str
) -> str | None:
"""
This converts a bool into a string given a check and name. If the result is a string
or None, it is returned as is.

:param result: The result to process.
:param check: The check instance.
:param name: The name of the check.
:return: The final string or None.

.. versionadded:: 0.11
"""
if isinstance(result, bool):
return (
""
if result
else (check.check.__doc__ or "Check failed").format(name=name, self=check)
)
return result
19 changes: 8 additions & 11 deletions src/repo_review/processor.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,13 @@
import markdown_it

from ._compat.importlib.resources.abc import Traversable
from .checks import Check, collect_checks, get_check_url, is_allowed
from .checks import (
Check,
collect_checks,
get_check_url,
is_allowed,
process_result_bool,
)
from .families import Family, collect_families
from .fixtures import apply_fixtures, collect_fixtures, compute_fixtures, pyproject
from .ghpath import EmptyTraversable
Expand Down Expand Up @@ -211,16 +217,7 @@ def process(
for name in ts.static_order():
if all(completed.get(n, "") == "" for n in graph[name]):
result = apply_fixtures({"name": name, **fixtures}, tasks[name].check)
if isinstance(result, bool):
completed[name] = (
""
if result
else (tasks[name].check.__doc__ or "Check failed").format(
name=name, self=tasks[name]
)
)
else:
completed[name] = result
completed[name] = process_result_bool(result, tasks[name], name)
else:
completed[name] = None

Expand Down
51 changes: 51 additions & 0 deletions src/repo_review/testing.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
"""
Helpers for testing repo-review plugins.
"""

from __future__ import annotations

import importlib.metadata
import textwrap
from typing import Any

from .checks import Check, get_check_url, process_result_bool
from .fixtures import apply_fixtures
from .processor import Result


def compute_check(name: str, /, **fixtures: Any) -> Result:
"""
A helper function to compute a check given fixtures, intended for testing.
Currently, all fixtures are required to be passed in as keyword arguments,
transitive fixtures are not supported.

:param name: The name of the check to compute.
:param fixtures: The fixtures to use when computing the check.
:return: The computed result.

.. versionadded:: 0.10.5
"""

check_functions = (
ep.load() for ep in importlib.metadata.entry_points(group="repo_review.checks")
)
checks = {
k: v
for func in check_functions
for k, v in apply_fixtures(fixtures, func).items()
}
check: Check = checks[name]
completed_raw = apply_fixtures({"name": name, **fixtures}, check.check)
completed = process_result_bool(completed_raw, check, name)
result = None if completed is None else not completed
doc = check.__doc__ or ""
err_msg = completed or ""

return Result(
family=check.family,
name=name,
description=doc.format(self=check, name=name).strip(),
result=result,
err_msg=textwrap.dedent(err_msg),
url=get_check_url(name, check),
)
8 changes: 8 additions & 0 deletions tests/test_package.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import pytest

import repo_review as m
import repo_review.testing
from repo_review.processor import process

DIR = Path(__file__).parent.resolve()
Expand Down Expand Up @@ -43,3 +44,10 @@ def test_broken_validate_pyproject(tmp_path: Path) -> None:
(result,) = (r for r in results.results if r.name == "VPP001")
assert "must match pattern" in result.err_msg
assert not result.result


def test_testing_function():
pytest.importorskip("sp_repo_review")

assert repo_review.testing.compute_check("RF001", ruff={}).result
assert not repo_review.testing.compute_check("RF001", ruff=None).result
Loading