Skip to content

Commit

Permalink
feat: testing function
Browse files Browse the repository at this point in the history
Signed-off-by: Henry Schreiner <henryschreineriii@gmail.com>
  • Loading branch information
henryiii committed Apr 17, 2024
1 parent 0cd33ba commit 530dc03
Show file tree
Hide file tree
Showing 5 changed files with 104 additions and 11 deletions.
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,
transiative 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.11
"""

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

0 comments on commit 530dc03

Please sign in to comment.