Skip to content

Commit eb345bc

Browse files
authored
feat: testing function (#201)
Signed-off-by: Henry Schreiner <henryschreineriii@gmail.com>
1 parent 0cd33ba commit eb345bc

File tree

6 files changed

+112
-11
lines changed

6 files changed

+112
-11
lines changed

docs/api/repo_review.rst

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,3 +72,11 @@ repo\_review.schema module
7272
:members:
7373
:undoc-members:
7474
:show-inheritance:
75+
76+
repo\_review.testing module
77+
---------------------------
78+
79+
.. automodule:: repo_review.testing
80+
:members:
81+
:undoc-members:
82+
:show-inheritance:

docs/plugins.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,20 @@ You have `processed.results` and `processed.families` from the return of
5353
{func}`~repo_review.processor.collect_all` to get `.fixtures`, `.checks`, and
5454
`.families`.
5555

56+
### Unit testing
57+
58+
You can also run unit tests with the {func}`~repo_review.testing.compute_check` helper. It is used like this:
59+
60+
```python
61+
def test_has_tool_ruff_unit() -> None:
62+
assert repo_review.testing.compute_check("RF001", ruff={}).result
63+
assert not repo_review.testing.compute_check("RF001", ruff=None).result
64+
```
65+
66+
It takes the check name and any fixtures as keyword arguments. It returns a
67+
{class}`~repo_review.checks.Check` instance, so you can see if the `.result` is
68+
`True`/`False`/`None`, or check any of the other properties.
69+
5670
## An existing package
5771

5872
Since writing a plugin does not require depending on repo-review, you can also

src/repo_review/checks.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,3 +120,26 @@ def get_check_description(name: str, check: Check) -> str:
120120
.. versionadded:: 0.8
121121
"""
122122
return (check.__doc__ or "").format(self=check, name=name)
123+
124+
125+
def process_result_bool(
126+
result: str | bool | None, check: Check, name: str
127+
) -> str | None:
128+
"""
129+
This converts a bool into a string given a check and name. If the result is a string
130+
or None, it is returned as is.
131+
132+
:param result: The result to process.
133+
:param check: The check instance.
134+
:param name: The name of the check.
135+
:return: The final string or None.
136+
137+
.. versionadded:: 0.11
138+
"""
139+
if isinstance(result, bool):
140+
return (
141+
""
142+
if result
143+
else (check.check.__doc__ or "Check failed").format(name=name, self=check)
144+
)
145+
return result

src/repo_review/processor.py

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,13 @@
1010
import markdown_it
1111

1212
from ._compat.importlib.resources.abc import Traversable
13-
from .checks import Check, collect_checks, get_check_url, is_allowed
13+
from .checks import (
14+
Check,
15+
collect_checks,
16+
get_check_url,
17+
is_allowed,
18+
process_result_bool,
19+
)
1420
from .families import Family, collect_families
1521
from .fixtures import apply_fixtures, collect_fixtures, compute_fixtures, pyproject
1622
from .ghpath import EmptyTraversable
@@ -211,16 +217,7 @@ def process(
211217
for name in ts.static_order():
212218
if all(completed.get(n, "") == "" for n in graph[name]):
213219
result = apply_fixtures({"name": name, **fixtures}, tasks[name].check)
214-
if isinstance(result, bool):
215-
completed[name] = (
216-
""
217-
if result
218-
else (tasks[name].check.__doc__ or "Check failed").format(
219-
name=name, self=tasks[name]
220-
)
221-
)
222-
else:
223-
completed[name] = result
220+
completed[name] = process_result_bool(result, tasks[name], name)
224221
else:
225222
completed[name] = None
226223

src/repo_review/testing.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
"""
2+
Helpers for testing repo-review plugins.
3+
"""
4+
5+
from __future__ import annotations
6+
7+
import importlib.metadata
8+
import textwrap
9+
from typing import Any
10+
11+
from .checks import Check, get_check_url, process_result_bool
12+
from .fixtures import apply_fixtures
13+
from .processor import Result
14+
15+
16+
def compute_check(name: str, /, **fixtures: Any) -> Result:
17+
"""
18+
A helper function to compute a check given fixtures, intended for testing.
19+
Currently, all fixtures are required to be passed in as keyword arguments,
20+
transitive fixtures are not supported.
21+
22+
:param name: The name of the check to compute.
23+
:param fixtures: The fixtures to use when computing the check.
24+
:return: The computed result.
25+
26+
.. versionadded:: 0.10.5
27+
"""
28+
29+
check_functions = (
30+
ep.load() for ep in importlib.metadata.entry_points(group="repo_review.checks")
31+
)
32+
checks = {
33+
k: v
34+
for func in check_functions
35+
for k, v in apply_fixtures(fixtures, func).items()
36+
}
37+
check: Check = checks[name]
38+
completed_raw = apply_fixtures({"name": name, **fixtures}, check.check)
39+
completed = process_result_bool(completed_raw, check, name)
40+
result = None if completed is None else not completed
41+
doc = check.__doc__ or ""
42+
err_msg = completed or ""
43+
44+
return Result(
45+
family=check.family,
46+
name=name,
47+
description=doc.format(self=check, name=name).strip(),
48+
result=result,
49+
err_msg=textwrap.dedent(err_msg),
50+
url=get_check_url(name, check),
51+
)

tests/test_package.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import pytest
77

88
import repo_review as m
9+
import repo_review.testing
910
from repo_review.processor import process
1011

1112
DIR = Path(__file__).parent.resolve()
@@ -43,3 +44,10 @@ def test_broken_validate_pyproject(tmp_path: Path) -> None:
4344
(result,) = (r for r in results.results if r.name == "VPP001")
4445
assert "must match pattern" in result.err_msg
4546
assert not result.result
47+
48+
49+
def test_testing_function():
50+
pytest.importorskip("sp_repo_review")
51+
52+
assert repo_review.testing.compute_check("RF001", ruff={}).result
53+
assert not repo_review.testing.compute_check("RF001", ruff=None).result

0 commit comments

Comments
 (0)