forked from ChelseaKR/ctdl-validate
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtest_action_runner.py
More file actions
114 lines (88 loc) · 4.26 KB
/
Copy pathtest_action_runner.py
File metadata and controls
114 lines (88 loc) · 4.26 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
"""Break the action's gate on purpose, before anyone depends on it.
`action.yml` is a gate other repositories will put in front of their own
publication step, and a gate that cannot fail is worse than no gate at all.
These tests run the action's entry point exactly as the composite step runs
it, same interpreter and same environment variables, and assert the exit code
it hands back to GitHub: 0 clean, 1 gated findings, 2 unusable input.
"""
from __future__ import annotations
import os
import subprocess
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parent.parent
RUNNER = ROOT / "tools" / "action_runner.py"
FIXTURES = Path("tests") / "fixtures"
def _run(tmp_path: Path, **inputs: str) -> tuple[int, str, dict[str, str]]:
"""Invoke the runner the way the composite step does, from the repo root."""
written = tmp_path / "outputs.txt"
environment = {
**os.environ,
"PYTHONPATH": str(ROOT / "src"),
"GITHUB_OUTPUT": str(written),
**inputs,
}
completed = subprocess.run(
[sys.executable, str(RUNNER)],
capture_output=True,
text=True,
cwd=ROOT,
env=environment,
check=False,
)
# Nothing is written when the run rejects its inputs before validating.
raw = written.read_text(encoding="utf-8") if written.exists() else ""
outputs = dict(line.split("=", 1) for line in raw.splitlines() if "=" in line)
return completed.returncode, completed.stdout, outputs
def test_a_clean_document_passes(tmp_path: Path) -> None:
code, _, outputs = _run(tmp_path, CTDL_PATH=str(FIXTURES / "clean_framework.json"))
assert code == 0
assert outputs["error-count"] == "0"
assert outputs["files-validated"] == "1"
def test_an_error_finding_fails_the_job(tmp_path: Path) -> None:
code, stdout, outputs = _run(tmp_path, CTDL_PATH=str(FIXTURES / "domain_violation.json"))
assert code == 1, "an ERROR finding must fail the job"
assert outputs["error-count"] == "1"
assert "::error file=" in stdout, "the failing finding must be annotated on the file"
def test_a_warning_only_document_passes_at_the_default_threshold(tmp_path: Path) -> None:
code, _, outputs = _run(tmp_path, CTDL_PATH=str(FIXTURES / "ctid_warnings.json"))
assert code == 0
assert outputs["warning-count"] == "2"
def test_the_same_document_fails_when_the_threshold_is_lowered(tmp_path: Path) -> None:
code, _, _ = _run(
tmp_path, CTDL_PATH=str(FIXTURES / "ctid_warnings.json"), CTDL_FAIL_ON="warning"
)
assert code == 1
def test_unverifiable_never_gates_at_any_threshold(tmp_path: Path) -> None:
# UNVERIFIABLE is neither a pass nor a fail for the CLI, and the action
# does not quietly promote it into one.
for threshold in ("error", "warning", "info"):
code, _, outputs = _run(
tmp_path,
CTDL_PATH=str(FIXTURES / "external_reference.json"),
CTDL_FAIL_ON=threshold,
)
assert code == 0, threshold
assert outputs["unverifiable-count"] == "1"
def test_resolve_reaches_the_cli_and_can_turn_a_run_red(tmp_path: Path) -> None:
owner = FIXTURES / "resolve" / "variants" / "owner_is_not_an_organization.json"
document = str(FIXTURES / "external_reference.json")
assert _run(tmp_path, CTDL_PATH=document)[0] == 0
assert _run(tmp_path, CTDL_PATH=document, CTDL_RESOLVE=str(owner))[0] == 1
def test_a_document_that_cannot_be_read_is_a_failure_not_a_pass(tmp_path: Path) -> None:
code, _, _ = _run(tmp_path, CTDL_PATH=str(FIXTURES / "no-such-document.json"))
assert code == 2
def test_a_path_matching_nothing_is_a_failure_not_a_pass(tmp_path: Path) -> None:
code, stdout, _ = _run(tmp_path, CTDL_PATH=str(FIXTURES / "*.no-such-suffix"))
assert code == 2
assert "not a pass" in stdout
def test_an_unusable_fail_on_is_rejected_rather_than_ignored(tmp_path: Path) -> None:
code, _, _ = _run(
tmp_path, CTDL_PATH=str(FIXTURES / "clean_framework.json"), CTDL_FAIL_ON="whenever"
)
assert code == 2
def test_a_directory_is_validated_recursively_and_one_bad_file_fails_it(tmp_path: Path) -> None:
code, _, outputs = _run(tmp_path, CTDL_PATH=str(FIXTURES))
assert code == 1
assert int(outputs["files-validated"]) > 1
assert int(outputs["error-count"]) > 0