style: repo-wide lint pass — make the lint gate green again
Push builds have been RED on the lint step since ~build 209 from accumulated formatting drift. This is the mechanical cleanup: ruff format + ruff --fix (UP038 isinstance unions, SIM105 contextlib.suppress, UP031 f-strings, SIM115 tempfile context manager), shfmt -i 2 -ci, nixpkgs-fmt/statix/deadnix (merged attrsets, dropped unused lib args), yamllint, and shell quoting fixes in tests/lasuite-docs/setup_custom_tests.sh. No behaviour changes intended; lint: PASS, unit tests: 138 passed.
This commit is contained in:
@ -29,9 +29,9 @@ def test_quick_form():
|
||||
|
||||
def test_non_trigger_forms_rejected():
|
||||
for body in (
|
||||
"!testmexyz", # the Adversary's classic negative
|
||||
"!testmexyz", # the Adversary's classic negative
|
||||
"!testme xyz",
|
||||
"!testme--quick", # no space → not the quick form
|
||||
"!testme--quick", # no space → not the quick form
|
||||
"!testme --quick", # double space → not an exact match (conservative)
|
||||
"please !testme",
|
||||
"testme",
|
||||
@ -43,10 +43,11 @@ def test_non_trigger_forms_rejected():
|
||||
|
||||
# --- Phase 3 U3: YunoHost-style PR comment builders (R2) -----------------------------------------
|
||||
|
||||
|
||||
def test_start_comment_is_yunohost_shaped():
|
||||
b = bridge.start_comment_body("uptime-kuma", "dfed87a39f8a", "https://drone.x/cc-ci/42")
|
||||
assert bridge.COMMENT_MARKER in b # re-!testme updates the same comment
|
||||
assert "🌻" in b and "⏳" in b # marker + in-progress
|
||||
assert bridge.COMMENT_MARKER in b # re-!testme updates the same comment
|
||||
assert "🌻" in b and "⏳" in b # marker + in-progress
|
||||
assert "uptime-kuma" in b and "dfed87a3" in b
|
||||
assert "https://drone.x/cc-ci/42" in b
|
||||
|
||||
@ -54,30 +55,38 @@ def test_start_comment_is_yunohost_shaped():
|
||||
def test_result_comment_image_forward_when_card_available(monkeypatch):
|
||||
monkeypatch.setattr(bridge, "artifact_available", lambda url: True)
|
||||
monkeypatch.setattr(bridge, "DASH_URL", "https://ci.example")
|
||||
b = bridge.result_comment_body("uptime-kuma", "dfed87a39f8a", "42", "https://drone.x/cc-ci/42", "success")
|
||||
b = bridge.result_comment_body(
|
||||
"uptime-kuma", "dfed87a39f8a", "42", "https://drone.x/cc-ci/42", "success"
|
||||
)
|
||||
assert bridge.COMMENT_MARKER in b
|
||||
assert "✅" in b and "passed" in b
|
||||
# the card + badge are embedded as linked images at the stable /runs/<num>/ URLs
|
||||
assert "" in b
|
||||
assert "https://ci.example/runs/42/badge.svg" in b
|
||||
assert "(https://drone.x/cc-ci/42)" in b # links to the run
|
||||
assert "(https://drone.x/cc-ci/42)" in b # links to the run
|
||||
|
||||
|
||||
def test_result_comment_text_fallback_when_card_missing(monkeypatch):
|
||||
# Render failed / not served → MUST degrade to text, never a broken image (R7).
|
||||
monkeypatch.setattr(bridge, "artifact_available", lambda url: False)
|
||||
b = bridge.result_comment_body("hedgedoc", "abc1234def", "9", "https://drone.x/cc-ci/9", "failure")
|
||||
assert "summary.png" not in b # no image embed
|
||||
assert "![" not in b # no markdown image at all
|
||||
b = bridge.result_comment_body(
|
||||
"hedgedoc", "abc1234def", "9", "https://drone.x/cc-ci/9", "failure"
|
||||
)
|
||||
assert "summary.png" not in b # no image embed
|
||||
assert "![" not in b # no markdown image at all
|
||||
assert "❌" in b and "failure" in b
|
||||
assert "https://drone.x/cc-ci/9" in b
|
||||
|
||||
|
||||
def test_find_existing_comment_matches_marker(monkeypatch):
|
||||
monkeypatch.setattr(bridge, "list_comments", lambda fn, n: [
|
||||
{"id": 1, "body": "just a normal comment"},
|
||||
{"id": 2, "body": bridge.COMMENT_MARKER + "\n🌻 old run"},
|
||||
])
|
||||
monkeypatch.setattr(
|
||||
bridge,
|
||||
"list_comments",
|
||||
lambda fn, n: [
|
||||
{"id": 1, "body": "just a normal comment"},
|
||||
{"id": 2, "body": bridge.COMMENT_MARKER + "\n🌻 old run"},
|
||||
],
|
||||
)
|
||||
assert bridge.find_existing_comment("org/repo", 5) == 2
|
||||
|
||||
|
||||
|
||||
@ -48,7 +48,9 @@ def test_is_enrolled_reads_flag(tmp_path, monkeypatch):
|
||||
def test_registry_roundtrip(tmp_path, monkeypatch):
|
||||
monkeypatch.setenv("CCCI_WARM_ROOT", str(tmp_path))
|
||||
assert canonical.read_registry("custom-html") is None
|
||||
rec = canonical.write_registry("custom-html", version="1.10.0+x", commit="abc123", status="idle")
|
||||
rec = canonical.write_registry(
|
||||
"custom-html", version="1.10.0+x", commit="abc123", status="idle"
|
||||
)
|
||||
assert rec["domain"] == "warm-custom-html.ci.commoninternet.net"
|
||||
assert rec["version"] == "1.10.0+x" and rec["commit"] == "abc123" and rec["status"] == "idle"
|
||||
back = canonical.read_registry("custom-html")
|
||||
@ -66,9 +68,11 @@ def test_enrolled_recipes_scans_meta(tmp_path, monkeypatch):
|
||||
fake_harness = tmp_path / "runner" / "harness"
|
||||
fake_harness.mkdir(parents=True)
|
||||
monkeypatch.setattr(canonical, "__file__", str(fake_harness / "canonical.py"))
|
||||
for name, body in (("aaa", "WARM_CANONICAL = True\n"),
|
||||
("bbb", "DEPS=['x']\n"),
|
||||
("ccc", "WARM_CANONICAL = True\n")):
|
||||
for name, body in (
|
||||
("aaa", "WARM_CANONICAL = True\n"),
|
||||
("bbb", "DEPS=['x']\n"),
|
||||
("ccc", "WARM_CANONICAL = True\n"),
|
||||
):
|
||||
d = tmp_path / "tests" / name
|
||||
d.mkdir(parents=True)
|
||||
(d / "recipe_meta.py").write_text(body)
|
||||
@ -85,12 +89,13 @@ def test_prune_stale_drops_deenrolled_only(tmp_path, monkeypatch):
|
||||
# enrolled canonical (keep), de-enrolled canonical (prune), reconciler dir (keep), alerts (keep)
|
||||
for name in ("keepme", "gone"):
|
||||
(tmp_path / name).mkdir()
|
||||
(tmp_path / name / "canonical.json").write_text('{"recipe":"%s"}' % name)
|
||||
(tmp_path / "keycloak").mkdir(); (tmp_path / "keycloak" / "last_good").write_text("v1") # reconciler
|
||||
(tmp_path / name / "canonical.json").write_text(f'{{"recipe":"{name}"}}')
|
||||
(tmp_path / "keycloak").mkdir()
|
||||
(tmp_path / "keycloak" / "last_good").write_text("v1") # reconciler
|
||||
(tmp_path / "alerts").mkdir()
|
||||
pruned = canonical.prune_stale()
|
||||
assert pruned == ["gone"]
|
||||
assert not (tmp_path / "gone").exists()
|
||||
assert (tmp_path / "keepme").exists()
|
||||
assert (tmp_path / "keycloak").exists() # no canonical.json → not a canonical → kept
|
||||
assert (tmp_path / "keycloak").exists() # no canonical.json → not a canonical → kept
|
||||
assert (tmp_path / "alerts").exists()
|
||||
|
||||
@ -12,9 +12,8 @@ import os
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
_tok = tempfile.NamedTemporaryFile("w", delete=False, suffix=".tok")
|
||||
_tok.write("test-token")
|
||||
_tok.close()
|
||||
with tempfile.NamedTemporaryFile("w", delete=False, suffix=".tok") as _tok:
|
||||
_tok.write("test-token")
|
||||
os.environ["DRONE_TOKEN_FILE"] = _tok.name
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "..", "dashboard"))
|
||||
|
||||
@ -23,10 +22,17 @@ import dashboard # noqa: E402
|
||||
|
||||
def _row(**kw):
|
||||
base = {
|
||||
"recipe": "custom-html", "status": "success", "number": 4, "ref": "db9a9502",
|
||||
"version": "db9a95024e9d", "level": 4, "level_cap_reason": "",
|
||||
"has_screenshot": True, "flags": {"clean_teardown": True, "no_secret_leak": True},
|
||||
"finished": 0, "url": "https://drone.x/cc-ci/4",
|
||||
"recipe": "custom-html",
|
||||
"status": "success",
|
||||
"number": 4,
|
||||
"ref": "db9a9502",
|
||||
"version": "db9a95024e9d",
|
||||
"level": 4,
|
||||
"level_cap_reason": "",
|
||||
"has_screenshot": True,
|
||||
"flags": {"clean_teardown": True, "no_secret_leak": True},
|
||||
"finished": 0,
|
||||
"url": "https://drone.x/cc-ci/4",
|
||||
}
|
||||
base.update(kw)
|
||||
return base
|
||||
@ -43,24 +49,33 @@ def test_level_color_ramp_and_fallback():
|
||||
def test_overview_grid_mirrors_results():
|
||||
out = dashboard.render_overview([_row()])
|
||||
assert "custom-html" in out
|
||||
assert "level 4" in out # the corner level pill
|
||||
assert dashboard.level_color(4) in out # coloured by level
|
||||
assert "db9a95024e9d" in out # version from results.json
|
||||
assert "/runs/4/screenshot.png" in out # thumbnail
|
||||
assert "/runs/4/summary.png" in out # links to full card
|
||||
assert "/recipe/custom-html" in out # history link
|
||||
assert "level 4" in out # the corner level pill
|
||||
assert dashboard.level_color(4) in out # coloured by level
|
||||
assert "db9a95024e9d" in out # version from results.json
|
||||
assert "/runs/4/screenshot.png" in out # thumbnail
|
||||
assert "/runs/4/summary.png" in out # links to full card
|
||||
assert "/recipe/custom-html" in out # history link
|
||||
assert "✔ teardown" in out and "✔ no-leak" in out
|
||||
|
||||
|
||||
def test_overview_never_greener_than_data():
|
||||
# A failed run at level 0 must show level 0 + the failure pill — never a green/high level.
|
||||
out = dashboard.render_overview([_row(status="failure", level=0, has_screenshot=False,
|
||||
flags={}, level_cap_reason="L1 install FAILED")])
|
||||
out = dashboard.render_overview(
|
||||
[
|
||||
_row(
|
||||
status="failure",
|
||||
level=0,
|
||||
has_screenshot=False,
|
||||
flags={},
|
||||
level_cap_reason="L1 install FAILED",
|
||||
)
|
||||
]
|
||||
)
|
||||
assert "level 0" in out
|
||||
assert dashboard.level_color(0) in out # red
|
||||
assert dashboard.level_color(0) in out # red
|
||||
assert dashboard._COLORS["failure"] in out
|
||||
assert "level 4" not in out and "level 5" not in out and "level 6" not in out
|
||||
assert "no screenshot" in out # placeholder, no broken image
|
||||
assert "no screenshot" in out # placeholder, no broken image
|
||||
|
||||
|
||||
def test_level_pill_unknown_when_no_results():
|
||||
@ -74,7 +89,7 @@ def test_history_table_lists_runs():
|
||||
assert "#4" in out and "#3" in out
|
||||
assert "L4" in out and "L2" in out
|
||||
assert "← all recipes" in out
|
||||
assert "/runs/4/summary.png" in out # per-run card link
|
||||
assert "/runs/4/summary.png" in out # per-run card link
|
||||
|
||||
|
||||
def test_history_empty():
|
||||
@ -83,12 +98,24 @@ def test_history_empty():
|
||||
|
||||
|
||||
def test_build_row_projects_results(monkeypatch):
|
||||
monkeypatch.setattr(dashboard, "_results_for", lambda n: {
|
||||
"version": "1.2.3", "level": 2, "level_cap_reason": "cap",
|
||||
"screenshot": "screenshot.png", "flags": {"clean_teardown": True},
|
||||
})
|
||||
b = {"number": 7, "status": "success", "event": "custom",
|
||||
"params": {"RECIPE": "n8n", "REF": "abcdef1234567890"}, "finished": 10}
|
||||
monkeypatch.setattr(
|
||||
dashboard,
|
||||
"_results_for",
|
||||
lambda n: {
|
||||
"version": "1.2.3",
|
||||
"level": 2,
|
||||
"level_cap_reason": "cap",
|
||||
"screenshot": "screenshot.png",
|
||||
"flags": {"clean_teardown": True},
|
||||
},
|
||||
)
|
||||
b = {
|
||||
"number": 7,
|
||||
"status": "success",
|
||||
"event": "custom",
|
||||
"params": {"RECIPE": "n8n", "REF": "abcdef1234567890"},
|
||||
"finished": 10,
|
||||
}
|
||||
r = dashboard._build_row(b)
|
||||
assert r["recipe"] == "n8n" and r["number"] == 7
|
||||
assert r["level"] == 2 and r["version"] == "1.2.3"
|
||||
@ -99,11 +126,16 @@ def test_build_row_projects_results(monkeypatch):
|
||||
def test_build_row_degrades_without_results(monkeypatch):
|
||||
# No results.json (e.g. an old run): grid still renders from Drone fields, level absent.
|
||||
monkeypatch.setattr(dashboard, "_results_for", lambda n: {})
|
||||
b = {"number": 9, "status": "running", "event": "custom",
|
||||
"params": {"RECIPE": "ghost", "REF": "deadbeefcafe1234567890"}, "finished": 0}
|
||||
b = {
|
||||
"number": 9,
|
||||
"status": "running",
|
||||
"event": "custom",
|
||||
"params": {"RECIPE": "ghost", "REF": "deadbeefcafe1234567890"},
|
||||
"finished": 0,
|
||||
}
|
||||
r = dashboard._build_row(b)
|
||||
assert r["level"] is None and r["has_screenshot"] is False
|
||||
assert r["version"] == "deadbeefcafe" # ref[:12] fallback
|
||||
assert r["version"] == "deadbeefcafe" # ref[:12] fallback
|
||||
# render must not crash or claim a level
|
||||
assert "level —" in dashboard.render_overview([r])
|
||||
|
||||
@ -111,7 +143,7 @@ def test_build_row_degrades_without_results(monkeypatch):
|
||||
def test_level_badge_shows_level_coloured(monkeypatch):
|
||||
svg = dashboard.render_level_badge("custom-html", 4)
|
||||
assert "custom-html" in svg and "level 4" in svg
|
||||
assert dashboard.level_color(4) in svg # coloured by level
|
||||
assert dashboard.level_color(4) in svg # coloured by level
|
||||
assert svg.startswith("<svg") and "image" not in svg # plain SVG
|
||||
# A higher displayed level than earned would be inflation — badge shows exactly the given level.
|
||||
assert "level 5" not in svg and "level 6" not in svg
|
||||
@ -133,8 +165,8 @@ def test_results_for_traversal_guarded():
|
||||
dashboard.CCCI_RUNS_DIR = d
|
||||
try:
|
||||
assert dashboard._results_for("5") == {"level": 3}
|
||||
assert dashboard._results_for("../../etc") == {} # traversal rejected
|
||||
assert dashboard._results_for("nonexist") == {} # missing → {}
|
||||
assert dashboard._results_for("../../etc") == {} # traversal rejected
|
||||
assert dashboard._results_for("nonexist") == {} # missing → {}
|
||||
assert dashboard._results_for("") == {}
|
||||
finally:
|
||||
dashboard.CCCI_RUNS_DIR = orig
|
||||
|
||||
@ -10,7 +10,6 @@ from __future__ import annotations
|
||||
|
||||
import os
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "..", "runner"))
|
||||
from harness import deps # noqa: E402
|
||||
@ -30,9 +29,7 @@ def test_declared_deps_reads_DEPS_list(tmp_path, monkeypatch):
|
||||
# Build a fake repo layout under tmp_path
|
||||
recipe_dir = tmp_path / "tests" / fake_recipe
|
||||
recipe_dir.mkdir(parents=True)
|
||||
(recipe_dir / "recipe_meta.py").write_text(
|
||||
'HEALTH_PATH = "/"\nDEPS = ["keycloak", "redis"]\n'
|
||||
)
|
||||
(recipe_dir / "recipe_meta.py").write_text('HEALTH_PATH = "/"\nDEPS = ["keycloak", "redis"]\n')
|
||||
# Patch the deps module's idea of "where the repo is" by monkey-patching __file__ for the
|
||||
# function indirectly: declared_deps uses `os.path.dirname(__file__), "..", "..", "tests"` —
|
||||
# which resolves to the real repo's `tests/`. So instead, override that with a symlink/dir
|
||||
|
||||
@ -20,9 +20,9 @@ import sys
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "..", "runner"))
|
||||
import run_recipe_ci # noqa: E402
|
||||
|
||||
|
||||
# ---- 1. the pure gate predicate ----
|
||||
|
||||
|
||||
def test_sso_dep_unverified_true_when_declared_notready_and_skipped():
|
||||
"""declares DEPS + deps not ready + ≥1 requires_deps test skipped → run must FAIL (F2-11)."""
|
||||
assert run_recipe_ci.sso_dep_unverified(["keycloak"], deps_ready=False, requires_deps_skipped=1)
|
||||
@ -31,7 +31,9 @@ def test_sso_dep_unverified_true_when_declared_notready_and_skipped():
|
||||
|
||||
def test_sso_dep_unverified_false_when_deps_ready():
|
||||
"""deps ready (setup_custom_tests succeeded) → SSO tests actually ran → not a failure."""
|
||||
assert not run_recipe_ci.sso_dep_unverified(["keycloak"], deps_ready=True, requires_deps_skipped=0)
|
||||
assert not run_recipe_ci.sso_dep_unverified(
|
||||
["keycloak"], deps_ready=True, requires_deps_skipped=0
|
||||
)
|
||||
|
||||
|
||||
def test_sso_dep_unverified_false_when_no_deps_declared():
|
||||
@ -43,11 +45,14 @@ def test_sso_dep_unverified_false_when_no_deps_declared():
|
||||
def test_sso_dep_unverified_false_when_nothing_skipped():
|
||||
"""Deps declared + not ready but ZERO requires_deps tests skipped → don't false-fail
|
||||
(the recipe has no SSO-marked tests to have been masked)."""
|
||||
assert not run_recipe_ci.sso_dep_unverified(["keycloak"], deps_ready=False, requires_deps_skipped=0)
|
||||
assert not run_recipe_ci.sso_dep_unverified(
|
||||
["keycloak"], deps_ready=False, requires_deps_skipped=0
|
||||
)
|
||||
|
||||
|
||||
# ---- 2. conftest skip + record behavior ----
|
||||
|
||||
|
||||
def _load_conftest():
|
||||
"""Load tests/conftest.py under a private module name (avoid clashing with pytest's own
|
||||
loaded `conftest`), so we can call pytest_collection_modifyitems directly with fakes."""
|
||||
@ -95,7 +100,9 @@ def test_conftest_appends_across_invocations(tmp_path, monkeypatch):
|
||||
monkeypatch.setenv("CCCI_DEPS_SKIP_REPORT", str(report))
|
||||
|
||||
conftest.pytest_collection_modifyitems(None, [_FakeItem(["requires_deps"])])
|
||||
conftest.pytest_collection_modifyitems(None, [_FakeItem(["requires_deps"]), _FakeItem(["requires_deps"])])
|
||||
conftest.pytest_collection_modifyitems(
|
||||
None, [_FakeItem(["requires_deps"]), _FakeItem(["requires_deps"])]
|
||||
)
|
||||
|
||||
total = sum(int(x) for x in report.read_text().split())
|
||||
assert total == 3
|
||||
|
||||
@ -32,7 +32,9 @@ def _fake_clock(monkeypatch):
|
||||
|
||||
|
||||
_DRIVE_META = {
|
||||
"READY_PROBE": lambda d: [{"host": f"collabora-{d}", "path": "/hosting/discovery", "ok": (200,)}]
|
||||
"READY_PROBE": lambda d: [
|
||||
{"host": f"collabora-{d}", "path": "/hosting/discovery", "ok": (200,)}
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
@ -73,4 +75,6 @@ def test_wait_healthy_raises_when_converged_but_never_serves(monkeypatch):
|
||||
monkeypatch.setattr(lc, "services_converged", lambda domain: True)
|
||||
monkeypatch.setattr(lc, "http_get", lambda *a, **k: 502)
|
||||
with pytest.raises(TimeoutError):
|
||||
lc.wait_healthy("x.ci.commoninternet.net", ok_codes=(200,), deploy_timeout=60, http_timeout=60)
|
||||
lc.wait_healthy(
|
||||
"x.ci.commoninternet.net", ok_codes=(200,), deploy_timeout=60, http_timeout=60
|
||||
)
|
||||
|
||||
@ -126,9 +126,7 @@ def test_http_get_transport_failure_returns_status_zero():
|
||||
|
||||
|
||||
def test_http_get_sends_headers(server):
|
||||
status, body = harness_http.http_get(
|
||||
f"{server}/echo-headers", headers={"X-Auth": "token-123"}
|
||||
)
|
||||
status, body = harness_http.http_get(f"{server}/echo-headers", headers={"X-Auth": "token-123"})
|
||||
assert status == 200
|
||||
assert body["X-Auth"] == "token-123"
|
||||
|
||||
|
||||
@ -275,7 +275,9 @@ def test_build_results_threads_expected_na(tmp_path):
|
||||
assert data["level_cap_rung"] == "backup_restore"
|
||||
assert data["rungs"]["functional"] == "pass"
|
||||
assert data["skips"]["intentional"]["backup_restore"] == "stateless static file server"
|
||||
assert data["skips"]["unintentional"] == [] # backup_restore declared; functional passed → clean
|
||||
assert (
|
||||
data["skips"]["unintentional"] == []
|
||||
) # backup_restore declared; functional passed → clean
|
||||
|
||||
|
||||
def test_write_results_roundtrip(tmp_path):
|
||||
|
||||
Reference in New Issue
Block a user