Merge branch 'main' into houseme/test/scanner-heal-v2-w20

This commit is contained in:
houseme
2026-09-05 19:10:01 +08:00
committed by GitHub
126 changed files with 13680 additions and 2748 deletions
+292 -106
View File
@@ -1,10 +1,11 @@
#!/usr/bin/env python3
"""Fail when a critical scheduled validation has not started recently."""
"""Require recent scheduled attempts and completed successes on the default branch."""
from __future__ import annotations
import argparse
from datetime import datetime, timedelta, timezone
import io
import json
import os
from pathlib import Path
@@ -13,7 +14,7 @@ import sys
import tempfile
import unittest
from unittest import mock
from urllib.parse import quote, urlencode
from urllib.parse import parse_qs, quote, urlencode, urlsplit
from urllib.request import Request, urlopen
@@ -75,15 +76,8 @@ def stale_reason(
run: dict[str, object] | None,
now: datetime,
max_age_hours: int,
never_ran_grace_until: datetime | None = None,
) -> str | None:
if run is None:
# The grace deadline only covers a workflow whose first scheduled slot
# has not arrived yet (for example a monthly cron enabled mid-month).
# A recorded-but-old run proves the schedule used to fire and stopped,
# so the grace never masks that case.
if never_ran_grace_until is not None and now <= never_ran_grace_until:
return None
return "no scheduled run has been recorded"
created_at = parse_timestamp(run.get("created_at"))
age = now - created_at
@@ -93,14 +87,23 @@ def stale_reason(
def fetch_latest_scheduled_run(
repository: str, workflow: str, token: str, api_url: str
repository: str,
workflow: str,
token: str,
api_url: str,
default_branch: str,
successful: bool = False,
) -> dict[str, object] | None:
owner, repo = repository.split("/", 1)
workflow_name = Path(workflow).name
query = {"event": "schedule", "branch": default_branch, "per_page": 1}
if successful:
# Filter on the server: the last success may be beyond a page of failures.
query["status"] = "success"
endpoint = (
f"{api_url.rstrip('/')}/repos/{quote(owner, safe='')}/{quote(repo, safe='')}"
f"/actions/workflows/{quote(workflow_name, safe='')}/runs?"
+ urlencode({"event": "schedule", "per_page": 1})
+ urlencode(query)
)
request = Request(
endpoint,
@@ -110,57 +113,104 @@ def fetch_latest_scheduled_run(
"X-GitHub-Api-Version": "2022-11-28",
},
)
with urlopen(request, timeout=30) as response:
# Two requests per manifest entry must fit the watchdog's ten-minute job.
with urlopen(request, timeout=15) as response:
payload = json.load(response)
runs = payload.get("workflow_runs")
runs = payload.get("workflow_runs") if isinstance(payload, dict) else None
if not isinstance(runs, list):
raise ValueError(f"GitHub returned no workflow_runs list for {workflow}")
total_count = payload.get("total_count")
if not isinstance(total_count, int) or isinstance(total_count, bool) or total_count < len(runs):
raise ValueError(f"GitHub returned an invalid run count for {workflow}")
if not runs:
if total_count:
raise ValueError(f"GitHub returned an empty first page with recorded runs for {workflow}")
return None
if not isinstance(runs[0], dict):
run = runs[0]
if not isinstance(run, dict):
raise ValueError(f"GitHub returned an invalid workflow run for {workflow}")
return runs[0]
if run.get("event") != "schedule" or run.get("head_branch") != default_branch:
raise ValueError(f"GitHub returned a run outside the scheduled default-branch query for {workflow}")
if not isinstance(run.get("status"), str) or not run["status"]:
raise ValueError(f"GitHub returned no run status for {workflow}")
conclusion = run.get("conclusion")
if (conclusion is not None and not isinstance(conclusion, str)) or (
run["status"] == "completed" and not conclusion
):
raise ValueError(f"GitHub returned an invalid run conclusion for {workflow}")
if successful and (run["status"] != "completed" or conclusion != "success"):
raise ValueError(f"GitHub returned a run without a completed success for {workflow}")
parse_timestamp(run.get("created_at"))
if not isinstance(run.get("html_url"), str) or not run["html_url"]:
raise ValueError(f"GitHub returned no run URL for {workflow}")
return run
def write_report(path: Path, failures: list[tuple[str, int, str, str]]) -> None:
lines = ["## Scheduled validation freshness"]
if not failures:
lines.append("")
lines.append("All critical scheduled validations have a recent scheduled run.")
else:
lines.extend(
[
"",
"The following critical validations are stale or could not be inspected:",
"",
"| Workflow | Limit | Result | Last run |",
"| --- | ---: | --- | --- |",
]
)
for workflow, max_age_hours, reason, run_url in failures:
link = f"[open]({run_url})" if run_url else ""
lines.append(f"| `{workflow}` | {max_age_hours}h | {reason} | {link} |")
def describe_run(run: dict[str, object] | None) -> str:
if run is None:
return "No recorded run"
outcome = run["status"]
if run.get("conclusion"):
outcome = f"{outcome}/{run['conclusion']}"
return f"[{outcome}]({run['html_url']}) — created {run['created_at']}"
def write_report(path: Path, rows: list[tuple[str, int, str, str, str]], default_branch: str) -> None:
lines = [
"## Scheduled validation freshness",
"",
f"Default branch: `{default_branch}`. Ages use scheduled-run creation time; rerunning an old commit does not refresh its evidence.",
"Attempt outcomes are shown independently of successful-run freshness.",
"Success is the GitHub workflow run conclusion; suite completeness remains the responsibility of each workflow.",
"",
"| Workflow | Limit | Freshness | Last attempt | Last completed success |",
"| --- | ---: | --- | --- | --- |",
]
for workflow, max_age_hours, result, attempt, success in rows:
cells = [f"`{workflow}`", f"{max_age_hours}h", result, attempt, success]
lines.append("| " + " | ".join(cell.replace("|", "\\|").replace("\n", " ") for cell in cells) + " |")
path.write_text("\n".join(lines) + "\n")
def check_freshness(
config: Path, report: Path, repository: str, token: str, api_url: str
config: Path, report: Path, repository: str, token: str, api_url: str, default_branch: str
) -> int:
now = datetime.now(timezone.utc)
failures: list[tuple[str, int, str, str]] = []
rows: list[tuple[str, int, str, str, str]] = []
failed = False
for workflow, max_age_hours, never_ran_grace_until in load_validations(config):
try:
run = fetch_latest_scheduled_run(repository, workflow, token, api_url)
reason = stale_reason(run, now, max_age_hours, never_ran_grace_until)
if reason is not None:
run_url = str(run.get("html_url", "")) if run else ""
failures.append((workflow, max_age_hours, reason, run_url))
except Exception as error:
failures.append(
(workflow, max_age_hours, f"inspection failed: {error}", "")
)
write_report(report, failures)
return 1 if failures else 0
runs: dict[str, dict[str, object] | None] = {}
reasons: list[str] = []
for label, successful in (("Last attempt", False), ("Last completed success", True)):
try:
runs[label] = fetch_latest_scheduled_run(
repository, workflow, token, api_url, default_branch, successful
)
except Exception as error:
reasons.append(f"{label}: inspection failed: {error}")
# A failed inspection or any recorded attempt ends first-run grace.
initial_grace = (
len(runs) == 2
and all(run is None for run in runs.values())
and never_ran_grace_until is not None
and now <= never_ran_grace_until
)
if not initial_grace:
for label, run in runs.items():
reason = stale_reason(run, now, max_age_hours)
if reason is not None:
reasons.append(f"{label}: {reason}")
failed |= bool(reasons)
result = "; ".join(reasons) if reasons else "Fresh"
if initial_grace:
result = f"Initial grace until {never_ran_grace_until.isoformat()}"
evidence = [
describe_run(runs[label]) if label in runs else "Inspection failed"
for label in ("Last attempt", "Last completed success")
]
rows.append((workflow, max_age_hours, result, *evidence))
write_report(report, rows, default_branch)
return 1 if failed else 0
class SelfTests(unittest.TestCase):
@@ -173,15 +223,6 @@ class SelfTests(unittest.TestCase):
self.assertIsNotNone(stale_reason(past_limit, self.NOW, 36))
self.assertIsNotNone(stale_reason(None, self.NOW, 36))
def test_never_ran_grace_only_covers_missing_runs(self) -> None:
future_grace = self.NOW + timedelta(hours=1)
past_grace = self.NOW - timedelta(seconds=1)
self.assertIsNone(stale_reason(None, self.NOW, 36, future_grace))
self.assertIsNone(stale_reason(None, self.NOW, 36, self.NOW))
self.assertIsNotNone(stale_reason(None, self.NOW, 36, past_grace))
stale_run = {"created_at": "2026-08-20T23:59:59Z"}
self.assertIsNotNone(stale_reason(stale_run, self.NOW, 36, future_grace))
def test_config_rejects_duplicate_and_invalid_entries(self) -> None:
with tempfile.TemporaryDirectory() as tmp:
path = Path(tmp) / "validations.json"
@@ -238,63 +279,205 @@ class SelfTests(unittest.TestCase):
],
)
def test_check_reports_missing_runs(self) -> None:
@staticmethod
def run_fixture(**overrides: object) -> dict[str, object]:
return {
"status": "completed",
"conclusion": "success",
"event": "schedule",
"head_branch": "release/current",
"created_at": "2026-08-22T00:00:00Z",
"html_url": "https://github.test/rustfs/rustfs/actions/runs/1",
**overrides,
}
def check_payloads(
self, payloads: list[object], *, grace: str | None = None, workflows: int = 1
) -> tuple[int, str, list]:
with tempfile.TemporaryDirectory() as tmp:
root = Path(tmp)
config = root / "validations.json"
report = root / "report.md"
config.write_text(
json.dumps(
[
{"workflow": ".github/workflows/ci.yml", "max_age_hours": 36},
{"workflow": ".github/workflows/fuzz.yml", "max_age_hours": 36},
{"workflow": ".github/workflows/mint.yml", "max_age_hours": 36},
]
)
)
with mock.patch(
__name__ + ".fetch_latest_scheduled_run",
side_effect=[
{"created_at": "2999-01-01T00:00:00Z"},
None,
RuntimeError("API unavailable"),
],
entries = [
{"workflow": f".github/workflows/check-{index}.yml", "max_age_hours": 36}
for index in range(workflows)
]
if grace is not None:
entries[0]["never_ran_grace_until"] = grace
config.write_text(json.dumps(entries))
responses = []
for payload in payloads:
if isinstance(payload, dict) and isinstance(payload.get("workflow_runs"), list):
payload = {"total_count": len(payload["workflow_runs"]), **payload}
responses.append(payload if isinstance(payload, Exception) else io.StringIO(json.dumps(payload)))
with (
mock.patch(__name__ + ".urlopen", side_effect=responses) as request,
mock.patch(__name__ + ".datetime", wraps=datetime) as clock,
):
self.assertEqual(
check_freshness(
config,
report,
"rustfs/rustfs",
"token",
"https://api.github.test",
),
1,
clock.now.return_value = self.NOW
status = check_freshness(
config, report, "rustfs/rustfs", "test-token",
"https://api.github.test", "release/current",
)
contents = report.read_text()
self.assertIn(".github/workflows/fuzz.yml", contents)
self.assertIn("inspection failed: API unavailable", contents)
self.assertNotIn(".github/workflows/ci.yml`", contents)
return status, report.read_text(), request.call_args_list
config.write_text(
json.dumps(
[{"workflow": ".github/workflows/ci.yml", "max_age_hours": 36}]
def test_requests_filter_schedule_default_branch_and_success_on_server(self) -> None:
attempt = self.run_fixture(status="in_progress", conclusion=None)
success = self.run_fixture(html_url="https://github.test/rustfs/rustfs/actions/runs/2")
status, report, calls = self.check_payloads([
{"workflow_runs": [attempt], "total_count": 1001},
{"workflow_runs": [success], "total_count": 1},
])
self.assertEqual(status, 0)
self.assertEqual(len(calls), 2)
for call, successful in zip(calls, (False, True)):
request = call.args[0]
url = urlsplit(request.full_url)
self.assertEqual(url.path, "/repos/rustfs/rustfs/actions/workflows/check-0.yml/runs")
expected = {"event": ["schedule"], "branch": ["release/current"], "per_page": ["1"]}
if successful:
expected["status"] = ["success"]
self.assertEqual(parse_qs(url.query), expected)
self.assertEqual(request.get_header("Authorization"), "Bearer test-token")
self.assertEqual(call.kwargs, {"timeout": 15})
self.assertIn("[in_progress]", report)
self.assertIn(str(attempt["html_url"]), report)
self.assertIn(str(success["html_url"]), report)
def test_cancelled_attempt_cannot_refresh_expired_success(self) -> None:
attempt = self.run_fixture(conclusion="cancelled")
success = self.run_fixture(
created_at="2026-08-20T23:59:59Z", updated_at="2026-08-22T11:59:59Z",
html_url="https://github.test/rustfs/rustfs/actions/runs/2",
)
status, report, _ = self.check_payloads([
{"workflow_runs": [attempt]}, {"workflow_runs": [success]},
])
self.assertEqual(status, 1)
self.assertIn("Last completed success: last scheduled run is", report)
self.assertIn("[completed/cancelled]", report)
for run in (attempt, success):
self.assertIn(str(run["html_url"]), report)
self.assertIn(str(run["created_at"]), report)
def test_attempt_outcome_does_not_replace_recent_success(self) -> None:
success = self.run_fixture(created_at="2026-08-21T00:00:00Z")
for state, conclusion in (
("completed", "failure"), ("completed", "cancelled"),
("completed", "timed_out"), ("completed", "success"),
("queued", None), ("in_progress", None),
):
with self.subTest(state=state, conclusion=conclusion):
status, report, _ = self.check_payloads([
{"workflow_runs": [self.run_fixture(status=state, conclusion=conclusion)]},
{"workflow_runs": [success]},
])
self.assertEqual(status, 0)
self.assertIn(f"[{state}" + (f"/{conclusion}" if conclusion else "") + "]", report)
self.assertIn("Fresh", report)
self.assertNotIn("All critical scheduled validations", report)
def test_grace_requires_two_successful_queries_with_no_history(self) -> None:
for attempt, success, grace, expected in (
(None, None, "2026-08-22T12:00:00Z", 0),
(None, None, "2026-08-22T11:59:59Z", 1),
(self.run_fixture(conclusion="failure"), None, "2026-08-23T00:00:00Z", 1),
(self.run_fixture(status="queued", conclusion=None), None, "2026-08-23T00:00:00Z", 1),
(None, self.run_fixture(), "2026-08-23T00:00:00Z", 1),
):
with self.subTest(attempt=attempt, success=success, grace=grace):
status, report, _ = self.check_payloads([
{"workflow_runs": [] if attempt is None else [attempt]},
{"workflow_runs": [] if success is None else [success]},
], grace=grace)
self.assertEqual(status, expected)
self.assertEqual("Initial grace until" in report, expected == 0)
def test_api_failures_preserve_other_evidence_and_never_enter_grace(self) -> None:
good = {"workflow_runs": [self.run_fixture()]}
for first, second in (
(RuntimeError("API unavailable"), good),
(good, RuntimeError("API unavailable")),
(RuntimeError("API unavailable"), {"workflow_runs": []}),
):
with self.subTest(first=first, second=second):
status, report, calls = self.check_payloads(
[first, second], grace="2026-08-23T00:00:00Z"
)
)
with mock.patch(
__name__ + ".fetch_latest_scheduled_run",
return_value={"created_at": "2999-01-01T00:00:00Z"},
):
self.assertEqual(
check_freshness(
config,
report,
"rustfs/rustfs",
"token",
"https://api.github.test",
),
0,
)
self.assertIn("All critical scheduled validations", report.read_text())
self.assertEqual(status, 1)
self.assertEqual(len(calls), 2)
self.assertIn("inspection failed: API unavailable", report)
self.assertNotIn("Initial grace until", report)
if first is good or second is good:
self.assertIn(str(self.run_fixture()["html_url"]), report)
def test_invalid_api_evidence_fails_closed(self) -> None:
malformed = [
[], {}, {"workflow_runs": {}}, {"workflow_runs": [None]},
{"workflow_runs": [], "total_count": 1},
{"workflow_runs": [], "total_count": -1},
{"workflow_runs": [], "total_count": None},
{"workflow_runs": [], "total_count": True},
*({"workflow_runs": [self.run_fixture(**override)]} for override in (
{"event": "workflow_dispatch"}, {"head_branch": "other"},
{"created_at": "invalid"}, {"created_at": "2026-08-22T00:00:00"},
{"status": None}, {"conclusion": None}, {"conclusion": 1},
{"html_url": ""},
)),
]
for payload in malformed:
for index, label in enumerate(("Last attempt", "Last completed success")):
with self.subTest(payload=payload, label=label):
payloads = [{"workflow_runs": [self.run_fixture()]} for _ in range(2)]
payloads[index] = payload
status, report, _ = self.check_payloads(payloads, grace="2026-08-23T00:00:00Z")
self.assertEqual(status, 1)
self.assertIn(f"{label}: inspection failed", report)
self.assertNotIn("Initial grace until", report)
self.assertIn(str(self.run_fixture()["html_url"]), report)
for state, conclusion in (("in_progress", "success"), ("completed", "failure"), ("completed", "skipped")):
with self.subTest(state=state, conclusion=conclusion):
status, report, _ = self.check_payloads([
{"workflow_runs": [self.run_fixture()]},
{"workflow_runs": [self.run_fixture(status=state, conclusion=conclusion)]},
])
self.assertEqual(status, 1)
self.assertIn("without a completed success", report)
def test_report_retains_every_workflow(self) -> None:
status, report, calls = self.check_payloads([
{"workflow_runs": [self.run_fixture()]}, {"workflow_runs": [self.run_fixture()]},
{"workflow_runs": []}, {"workflow_runs": []},
RuntimeError("API unavailable"), {"workflow_runs": [self.run_fixture()]},
], workflows=3)
self.assertEqual(status, 1)
self.assertEqual(len(calls), 6)
for index in range(3):
self.assertEqual(report.count(f"`.github/workflows/check-{index}.yml`"), 1)
self.assertIn("No recorded run", report)
self.assertIn("Inspection failed", report)
def test_cli_requires_the_repository_default_branch(self) -> None:
from check_test_wiring import yaml_block
workflow = (ROOT / ".github/workflows/scheduled-validation-freshness.yml").read_text().splitlines()
job = yaml_block(workflow, "check-freshness", 2)
self.assertIsNotNone(job)
start = job.index(" - name: Check latest scheduled runs")
end = next((index for index in range(start + 1, len(job)) if job[index].startswith(" - ")), len(job))
environment = yaml_block(job[start:end], "env", 8)
self.assertIsNotNone(environment)
self.assertIn(" RUSTFS_DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}", environment)
with (
mock.patch.dict(os.environ, {"GITHUB_REPOSITORY": "rustfs/rustfs", "GH_TOKEN": "test-token"}, clear=True),
mock.patch.object(sys, "argv", ["checker", "--report", "unused.md"]),
mock.patch("sys.stderr", new=io.StringIO()) as stderr,
self.assertRaises(SystemExit) as error,
):
main()
self.assertEqual(error.exception.code, 2)
self.assertIn("RUSTFS_DEFAULT_BRANCH", stderr.getvalue())
def main() -> int:
@@ -318,11 +501,14 @@ def main() -> int:
repository = os.environ.get("GITHUB_REPOSITORY", "")
token = os.environ.get("GH_TOKEN", "")
api_url = os.environ.get("GITHUB_API_URL", "https://api.github.com")
default_branch = os.environ.get("RUSTFS_DEFAULT_BRANCH", "")
if not re.fullmatch(r"[^/\s]+/[^/\s]+", repository):
parser.error("GITHUB_REPOSITORY must be owner/repository")
if not token:
parser.error("GH_TOKEN is required")
return check_freshness(args.config, args.report, repository, token, api_url)
if not default_branch or any(character.isspace() for character in default_branch):
parser.error("RUSTFS_DEFAULT_BRANCH is required and must name the repository default branch")
return check_freshness(args.config, args.report, repository, token, api_url, default_branch)
if __name__ == "__main__":
+78 -1
View File
@@ -764,8 +764,53 @@ def check_profile_listing(root: Path, profile: str, listing: Path) -> list[str]:
return []
def core_requirements(root: Path) -> dict:
data = json.loads((root / ".config/ecstore-required-tests.json").read_text())
if not data["tests"] or not data["fixtures"]:
raise ValueError("core test and fixture requirements must not be empty")
identities = [(test["suite"], test["name"]) for test in data["tests"]]
if len(set(identities)) != len(identities):
raise ValueError("duplicate core test requirement")
return data
def check_core_fixtures(root: Path) -> list[str]:
try:
fixtures = core_requirements(root)["fixtures"]
errors = []
for fixture in fixtures:
path = (root / fixture["path"]).resolve()
if not path.is_relative_to(root.resolve()):
raise ValueError("core fixture path escapes repository")
if not path.is_file():
errors.append(f"{fixture['path']}: required core fixture missing")
elif hashlib.sha256(path.read_bytes()).hexdigest() != fixture["sha256"]:
errors.append(f"{fixture['path']}: core fixture sha256 mismatch")
return errors
except (OSError, KeyError, TypeError, ValueError) as error:
return [f"cannot validate core fixtures: {error}"]
def check_core_listing(root: Path, listing: Path) -> list[str]:
"""Check the existing CI run's selection, not a second filtered test run."""
try:
required = core_requirements(root)["tests"]
suites = json.loads(listing.read_text())["rust-suites"]
if not isinstance(suites, dict):
raise ValueError("rust-suites must be an object")
errors = check_core_fixtures(root)
for test in required:
testcase = suites.get(test["suite"], {}).get("testcases", {}).get(test["name"], {})
if testcase.get("ignored") is not False or testcase.get("filter-match", {}).get("status") != "matches":
errors.append(f"{test['invariant']}: required test not selected: {test['suite']}::{test['name']}")
return errors
except (OSError, KeyError, TypeError, ValueError) as error:
return [f"cannot read core nextest listing: {error}"]
def validate(root: Path) -> list[str]:
errors: list[str] = []
errors.extend(check_core_fixtures(root))
errors.extend(check_e2e_modules(root))
errors.extend(check_vault_test_groups(root))
errors.extend(check_ilm_build_budget(root))
@@ -779,6 +824,32 @@ def validate(root: Path) -> list[str]:
class SelfTests(unittest.TestCase):
def test_core_gate_rejects_missing_ignored_filtered_and_corrupt_inputs(self) -> None:
with tempfile.TemporaryDirectory() as tmp:
root = Path(tmp)
(root / ".config").mkdir()
fixture = root / "fixture.hex"
fixture.write_text("4142")
requirements = {
"tests": [{"invariant": "commit", "suite": "store", "name": "commit_test"}],
"fixtures": [{"path": "fixture.hex", "sha256": hashlib.sha256(fixture.read_bytes()).hexdigest()}],
}
(root / ".config/ecstore-required-tests.json").write_text(json.dumps(requirements))
listing = root / "listing.json"
good = {"ignored": False, "filter-match": {"status": "matches"}}
for case, testcase in (("selected", good), ("missing", {}), ("ignored", dict(good, ignored=True)),
("filtered", dict(good, **{"filter-match": {"status": "mismatch"}}))):
with self.subTest(case=case):
listing.write_text(json.dumps({"rust-suites": {"store": {"testcases": {"commit_test": testcase}}}}))
self.assertEqual(bool(check_core_listing(root, listing)), case != "selected")
listing.write_text(json.dumps({"rust-suites": {"store": {"testcases": {"commit_test": good}}}}))
fixture.write_text("4143")
self.assertIn("sha256 mismatch", check_core_listing(root, listing)[0])
fixture.unlink()
self.assertIn("fixture missing", check_core_listing(root, listing)[0])
listing.write_text("not json")
self.assertIn("cannot read", check_core_listing(root, listing)[0])
def test_ilm_lane_keeps_the_measured_cargo_build_budget(self) -> None:
with tempfile.TemporaryDirectory() as tmp:
root = Path(tmp)
@@ -981,6 +1052,7 @@ class SelfTests(unittest.TestCase):
mock.patch(__name__ + ".check_e2e_modules", return_value=[]),
mock.patch(__name__ + ".check_vault_test_groups", return_value=[]),
mock.patch(__name__ + ".check_fuzz_targets", return_value=[]),
mock.patch(__name__ + ".check_core_fixtures", return_value=[]),
mock.patch(__name__ + ".check_runner_selection", return_value=[]),
mock.patch(__name__ + ".check_workflow_readiness", return_value=[]),
mock.patch(__name__ + ".check_profile_definitions", return_value=[]),
@@ -1393,6 +1465,11 @@ def main() -> int:
if sys.argv[1:] == ["--self-test"]:
suite = unittest.defaultTestLoader.loadTestsFromTestCase(SelfTests)
return 0 if unittest.TextTestRunner(verbosity=2).run(suite).wasSuccessful() else 1
if len(sys.argv) == 3 and sys.argv[1] == "--check-core":
errors = check_core_listing(ROOT, Path(sys.argv[2]))
for error in errors:
print(f"ERROR: {error}", file=sys.stderr)
return 1 if errors else 0
if len(sys.argv) == 4 and sys.argv[1] == "--check-profile":
errors = check_profile_listing(ROOT, sys.argv[2], Path(sys.argv[3]))
if errors:
@@ -1410,7 +1487,7 @@ def main() -> int:
return 0
if sys.argv[1:]:
print(
"usage: check_test_wiring.py [--self-test | --check-profile PROFILE LISTING | "
"usage: check_test_wiring.py [--self-test | --check-core LISTING | --check-profile PROFILE LISTING | "
"--update-profile PROFILE LISTING PLATFORM]",
file=sys.stderr,
)
-20
View File
@@ -20,8 +20,6 @@
crates/ecstore/src/bucket/lifecycle/tier_last_day_stats.rs|clippy::all
crates/ecstore/src/bucket/lifecycle/tier_last_day_stats.rs|unused_must_use
crates/ecstore/src/bucket/lifecycle/tier_last_day_stats.rs|unused_variables
crates/ecstore/src/bucket/lifecycle/tier_sweeper.rs|clippy::all
crates/ecstore/src/bucket/lifecycle/tier_sweeper.rs|unused_must_use
crates/ecstore/src/bucket/lifecycle/tier_sweeper.rs|unused_variables
crates/s3-client/src/api_error_response.rs|clippy::all
crates/s3-client/src/api_error_response.rs|unused_must_use
@@ -74,36 +72,18 @@ crates/ecstore/src/services/event_notification.rs|unused_variables
crates/ecstore/src/services/tier/tier.rs|clippy::all
crates/ecstore/src/services/tier/tier.rs|unused_must_use
crates/ecstore/src/services/tier/tier.rs|unused_variables
crates/ecstore/src/services/tier/tier_admin.rs|clippy::all
crates/ecstore/src/services/tier/tier_admin.rs|unused_must_use
crates/ecstore/src/services/tier/tier_admin.rs|unused_variables
crates/ecstore/src/services/tier/warm_backend.rs|clippy::all
crates/ecstore/src/services/tier/warm_backend.rs|unused_must_use
crates/ecstore/src/services/tier/warm_backend.rs|unused_variables
crates/ecstore/src/services/tier/warm_backend_aliyun.rs|clippy::all
crates/ecstore/src/services/tier/warm_backend_aliyun.rs|unused_must_use
crates/ecstore/src/services/tier/warm_backend_aliyun.rs|unused_variables
crates/ecstore/src/services/tier/warm_backend_azure.rs|clippy::all
crates/ecstore/src/services/tier/warm_backend_azure.rs|unused_must_use
crates/ecstore/src/services/tier/warm_backend_azure.rs|unused_variables
crates/ecstore/src/services/tier/warm_backend_gcs.rs|clippy::all
crates/ecstore/src/services/tier/warm_backend_gcs.rs|unused_must_use
crates/ecstore/src/services/tier/warm_backend_gcs.rs|unused_variables
crates/ecstore/src/services/tier/warm_backend_huaweicloud.rs|clippy::all
crates/ecstore/src/services/tier/warm_backend_huaweicloud.rs|unused_must_use
crates/ecstore/src/services/tier/warm_backend_huaweicloud.rs|unused_variables
crates/ecstore/src/services/tier/warm_backend_minio.rs|clippy::all
crates/ecstore/src/services/tier/warm_backend_minio.rs|unused_must_use
crates/ecstore/src/services/tier/warm_backend_minio.rs|unused_variables
crates/ecstore/src/services/tier/warm_backend_r2.rs|clippy::all
crates/ecstore/src/services/tier/warm_backend_r2.rs|unused_must_use
crates/ecstore/src/services/tier/warm_backend_r2.rs|unused_variables
crates/ecstore/src/services/tier/warm_backend_rustfs.rs|clippy::all
crates/ecstore/src/services/tier/warm_backend_rustfs.rs|unused_must_use
crates/ecstore/src/services/tier/warm_backend_rustfs.rs|unused_variables
crates/ecstore/src/services/tier/warm_backend_s3.rs|clippy::all
crates/ecstore/src/services/tier/warm_backend_s3.rs|unused_must_use
crates/ecstore/src/services/tier/warm_backend_s3.rs|unused_variables
crates/ecstore/src/services/tier/warm_backend_tencent.rs|clippy::all
crates/ecstore/src/services/tier/warm_backend_tencent.rs|unused_must_use
crates/ecstore/src/services/tier/warm_backend_tencent.rs|unused_variables
+197
View File
@@ -0,0 +1,197 @@
#!/usr/bin/env python3
"""Run the security workflow's evidence and result steps without remote VMs."""
from __future__ import annotations
import os
import re
import subprocess
import tempfile
import unittest
from pathlib import Path
from check_test_wiring import yaml_block
ROOT = Path(__file__).resolve().parents[1]
WORKFLOW = ROOT / ".github/workflows/rustfs-security-test.yml"
CASE_ROW = "| IAM-101 | user CRUD lifecycle | PASS |"
class SecurityWorkflowTests(unittest.TestCase):
def setUp(self) -> None:
self.source = WORKFLOW.read_text()
self.job = yaml_block(self.source.splitlines(), "security-test", 2)
self.assertIsNotNone(self.job)
starts = [i for i, line in enumerate(self.job) if line.startswith(" - name: ")]
self.steps = {
self.job[start].split(": ", 1)[1].strip('"'): self.job[start:end]
for start, end in zip(starts, starts[1:] + [len(self.job)])
}
self.temp = tempfile.TemporaryDirectory()
self.addCleanup(self.temp.cleanup)
self.directory = Path(self.temp.name)
self.context = {
"runner.temp": self.temp.name,
"github.server_url": "https://github.com",
"github.repository": "rustfs/rustfs",
"github.run_id": "314159",
"github.run_attempt": "2",
"github.sha": "0123456789abcdef0123456789abcdef01234567",
"github.event_name": "workflow_dispatch",
"github.workspace": self.temp.name,
"inputs.package_url": "",
"inputs.rustfs_version": "test-version",
"inputs.topology": "all",
"inputs.oidc_live": "false",
"steps.evidence.outcome": "skipped",
"steps.test.outcome": "skipped",
"steps.report.outcome": "skipped",
}
self.env = {
**os.environ, "GITHUB_STEP_SUMMARY": str(self.directory / "summary.md"),
"GITHUB_ENV": str(self.directory / "github-env"), "RUNNER_TEMP": self.temp.name, "TMPDIR": self.temp.name,
}
for key in ("server_url", "repository", "run_id", "run_attempt", "sha", "event_name"):
self.env[f"GITHUB_{key.upper()}"] = self.context[f"github.{key}"]
self.context["env.SECURITY_ARTIFACTS_DIR"] = ""
self.artifacts = self.directory / "rustfs-security-314159-2"
suite = self.directory / "auto-testing/rustfs-security-test.sh"
suite.parent.mkdir()
suite.write_text(
'#!/usr/bin/env bash\nset -euo pipefail\n'
'log_dir=$(mktemp -d "$TMPDIR/rustfs-security.XXXXXX")\n'
'echo "CURRENT SUITE LOG" > "$log_dir/suite.log"\n'
'case "$FAKE_REPORT" in\n'
f' present) printf "%s\\n" "CURRENT SUITE DIAGNOSTIC" "{CASE_ROW}" > "$REPORT_FILE" ;;\n'
' empty) : > "$REPORT_FILE" ;;\n'
'esac\n'
'echo "UNWRAPPED SUITE SUMMARY" >> "$GITHUB_STEP_SUMMARY"\n'
'exit "$FAKE_EXIT"\n'
)
def render(self, value: str) -> str:
return re.sub(r"\$\{\{\s*(.*?)\s*\}\}", lambda match: self.context[match[1]], value)
def step_env(self, lines: list[str], indent: int = 8) -> dict[str, str]:
result = {}
for line in yaml_block(lines, "env", indent) or []:
if line.strip() and not line.lstrip().startswith("#"):
key, value = line.strip().split(": ", 1)
result[key] = self.render(value.strip("'\""))
return result
def run_step(self, name: str) -> subprocess.CompletedProcess[str]:
lines = self.steps[name]
start = lines.index(" run: |") + 1
shell_lines = []
for line in lines[start:]:
if line.strip() and not line.startswith(" "):
break
shell_lines.append(line[10:])
self.assertTrue(shell_lines, f"missing literal shell body: {name}")
result = subprocess.run(
["bash", "--noprofile", "--norc", "-e", "-o", "pipefail", "-c", self.render("\n".join(shell_lines))],
cwd=self.directory, env={**self.env, **self.step_env(lines)}, capture_output=True, text=True,
)
for line in lines:
if line.startswith(" id: "):
self.context[f"steps.{line.split(': ', 1)[1]}.outcome"] = "failure" if result.returncode else "success"
if Path(self.env["GITHUB_ENV"]).exists():
for line in Path(self.env["GITHUB_ENV"]).read_text().splitlines():
key, value = line.split("=", 1)
self.env[key] = value
self.context[f"env.{key}"] = value
return result
def test_workflow_wiring(self) -> None:
names = list(self.steps)
self.assertLess(names.index("Checkout repository (for the OIDC live gate script)"), names.index("Checkout auto-testing scripts (with retry)"))
self.assertNotIn(" continue-on-error: true", self.job)
self.assertIn(" continue-on-error: true", self.steps["Run security suite"])
for name in ("Initialize security evidence", "Generate report"):
self.assertNotIn(" continue-on-error: true", self.steps[name])
self.assertIn(" if: ${{ always() && steps.evidence.outcome == 'success' }}", self.steps["Generate report"])
self.assertNotIn("/tmp/rustfs-security", self.source)
for name in ("Upload functional report to dashboard", "File failure issue in rustfs/backlog"):
report = next(line for line in self.steps[name] if line.strip().startswith("REPORT_FILE:"))
self.assertIn("${{ env.SECURITY_ARTIFACTS_DIR }}/report.md", report)
for name in ("Upload functional report to dashboard", "Upload report and logs"):
self.assertIn(" if: ${{ always() && steps.evidence.outcome == 'success' }}", self.steps[name])
artifact_settings = yaml_block(self.steps["Upload report and logs"], "with", 8)
self.assertIn(" path: ${{ env.SECURITY_ARTIFACTS_DIR }}/", artifact_settings)
self.assertIn(" if-no-files-found: error", artifact_settings)
def test_suite_report_and_result_matrix(self) -> None:
for outcome, mode, exit_code in (
("success", "present", 0), ("failure", "present", 7), ("failure", "missing", 7),
("success", "missing", 0), ("success", "empty", 0),
("skipped", "missing", 0), ("skipped", "present", 0),
("cancelled", "missing", 0), ("cancelled", "present", 0),
):
with self.subTest(outcome=outcome, report=mode):
self.setUp()
initialized = self.run_step("Initialize security evidence")
self.assertEqual(initialized.returncode, 0, initialized.stderr)
self.assertEqual(self.env["SECURITY_ARTIFACTS_DIR"], str(self.artifacts))
self.env.update(FAKE_REPORT=mode, FAKE_EXIT=str(exit_code))
if outcome != "skipped" or mode == "present":
suite = self.run_step("Run security suite")
self.assertEqual(suite.returncode, exit_code, suite.stderr)
logs = list(self.artifacts.glob("rustfs-security.*/suite.log"))
self.assertEqual(len(logs), 1)
self.assertEqual(logs[0].read_text(), "CURRENT SUITE LOG\n")
self.context["steps.test.outcome"] = outcome
report = self.run_step("Generate report")
success = outcome == "success" and mode == "present"
self.assertEqual(report.returncode == 0, success, report.stderr)
contents = (self.artifacts / "report.md").read_text()
for expected in (
"https://github.com/rustfs/rustfs/actions/runs/314159", "Attempt: 2",
f"Workflow Commit: {self.context['github.sha']}", "Trigger: workflow_dispatch",
f"Test Step Outcome: {'success' if success else 'failure'}", f"Suite Step Outcome: {outcome}",
):
self.assertIn(expected, contents)
self.assertEqual(CASE_ROW in contents, success)
self.assertEqual("CURRENT SUITE DIAGNOSTIC" in contents, success)
if mode == "present":
raw = (self.artifacts / "suite-report.md").read_text()
self.assertEqual(raw, f"CURRENT SUITE DIAGNOSTIC\n{CASE_ROW}\n")
summary = Path(self.env["GITHUB_STEP_SUMMARY"]).read_text()
self.assertEqual(summary, contents)
self.assertNotIn("UNWRAPPED SUITE SUMMARY", summary)
def test_existing_evidence_directory_is_rejected(self) -> None:
self.artifacts.mkdir()
stale = self.artifacts / "suite-report.md"
stale.write_text("OLD RUN REPORT")
self.assertNotEqual(self.run_step("Initialize security evidence").returncode, 0)
self.assertEqual(stale.read_text(), "OLD RUN REPORT")
self.assertFalse(Path(self.env["GITHUB_ENV"]).exists())
(self.artifacts / "report.md").write_text("OLD RUN REPORT")
self.context.update({
"env.SECURITY_ARTIFACTS_DIR": str(self.artifacts), "secrets.PF_TESTING_GH_TOKEN": "fake-local-token",
})
fake_bin = self.directory / "bin"
fake_bin.mkdir()
gh = fake_bin / "gh"
gh.write_text(
'#!/usr/bin/env bash\nset -euo pipefail\n'
'if [ "$1 $2" = "issue create" ]; then\n'
' while [ "$#" -gt 0 ]; do\n'
' if [ "$1" = "--body-file" ]; then cat "$2" > "$CAPTURE_BODY"; fi\n'
' shift\n'
' done\n'
'fi\n'
)
gh.chmod(0o755)
body = self.directory / "issue-body.md"
self.env.update(PATH=f"{fake_bin}{os.pathsep}{os.environ['PATH']}", CAPTURE_BODY=str(body))
result = self.run_step("File failure issue in rustfs/backlog")
self.assertEqual(result.returncode, 0, result.stderr)
self.assertNotIn("OLD RUN REPORT", body.read_text())
self.assertIn("https://github.com/rustfs/rustfs/actions/runs/314159", body.read_text())
if __name__ == "__main__":
unittest.main()