All checks were successful
continuous-integration/drone/push Build is passing
level.py: RUNGS += lint; statuses {pass,fail,skip,unver}; compute_level = max passed
rung with all below pass-or-skip (fail/unver block); cap_reason/capped DELETED.
harness/lint.py: lint executor — pristine scratch clone of the per-run tree at the
exact tested ref (mirror-origin + untracked-overlay pollution solved by context, no
rule filtered), PTY via script -qec, 60s hard budget, lint.txt artifact, table-parse
classifier (rc only signals FATA), unver on any non-run (never silent pass).
results.py: derive_rungs classifies every N/A source (structural/declared → skip,
else unver), lint rung + synthetic lint stage + lint block in results.json, schema 2,
cap fields removed. run_recipe_ci.py: lint call before tiers (double-wrapped,
verdict-neutral), badge = level only. card/dashboard: 0-5 ramp, cap line → 'level N
of {4|5}', unverified rows, badge number+colour only, lint.txt servable, old schema-1
artifacts render untouched. Unit suite rewritten: 245 passed on cc-ci venv.
195 lines
6.0 KiB
Python
195 lines
6.0 KiB
Python
"""Unit tests for the level ladder (harness.level) — phase lvl5 semantics.
|
|
|
|
Pure function — no I/O. Proves the operator-decided rule (plan-phase-lvl5-lint-rung.md,
|
|
DECISIONS.md phase lvl5):
|
|
|
|
level = max i such that rung_i == "pass" and every rung j < i is "pass" or "skip"
|
|
|
|
— a real FAIL blocks, an UNVERIFIED rung blocks exactly like a fail, an INTENTIONAL skip is
|
|
climbed past. Includes the mission's four worked examples verbatim, and the old N/A cases
|
|
(single-published-version recipe, non-backup-capable recipe) now climbing past their former
|
|
caps. Run cold with: cc-ci-run -m pytest tests/unit/test_level.py -q
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
import sys
|
|
|
|
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "..", "runner"))
|
|
from harness import level as L # noqa: E402
|
|
|
|
|
|
def _rungs(
|
|
install="pass",
|
|
upgrade="pass",
|
|
backup_restore="pass",
|
|
functional="pass",
|
|
lint="pass",
|
|
):
|
|
return {
|
|
"install": install,
|
|
"upgrade": upgrade,
|
|
"backup_restore": backup_restore,
|
|
"functional": functional,
|
|
"lint": lint,
|
|
}
|
|
|
|
|
|
# ---- the ladder: five essential rungs, top is L5 (lint) ----
|
|
|
|
|
|
def test_full_clean_climb_is_L5():
|
|
assert L.compute_level(_rungs()) == 5
|
|
|
|
|
|
def test_ladder_is_five_rungs_lint_on_top():
|
|
assert L.RUNGS == ("install", "upgrade", "backup_restore", "functional", "lint")
|
|
assert "lint" in L.RUNG_LABEL[5]
|
|
|
|
|
|
# ---- mission worked examples (operator Q&A 2026-06-11, verbatim) ----
|
|
|
|
|
|
def test_mission_example_fail_blocks():
|
|
# install ✔, upgrade ✘, backup ✔, functional ✔, lint ✔ → level 1 (fail blocks).
|
|
assert L.compute_level(_rungs(upgrade="fail")) == 1
|
|
|
|
|
|
def test_mission_example_intentional_skip_climbs():
|
|
# install ✔, upgrade ✔, backup skip (not capable), functional ✔, lint ✔ → level 5
|
|
# (previously capped at 2 — the confusing part the operator removed).
|
|
assert L.compute_level(_rungs(backup_restore="skip")) == 5
|
|
|
|
|
|
def test_mission_example_unverified_blocks():
|
|
# install ✔, upgrade ✔, backup UNVER (harness error), functional ✔, lint ✔ → level 2
|
|
# (we cannot claim what we didn't check).
|
|
assert L.compute_level(_rungs(backup_restore="unver")) == 2
|
|
|
|
|
|
def test_mission_example_unverified_top_rung_not_earned():
|
|
# all four ✔, lint unver (abra missing) → level 4.
|
|
assert L.compute_level(_rungs(lint="unver")) == 4
|
|
|
|
|
|
# ---- blocking semantics ----
|
|
|
|
|
|
def test_install_fail_is_L0():
|
|
assert L.compute_level(_rungs(install="fail")) == 0
|
|
|
|
|
|
def test_install_unver_is_L0():
|
|
assert L.compute_level(_rungs(install="unver")) == 0
|
|
|
|
|
|
def test_higher_pass_never_rescues_a_fail():
|
|
# everything above a failed rung is dead, however green.
|
|
assert L.compute_level(_rungs(upgrade="fail", backup_restore="pass", functional="pass")) == 1
|
|
|
|
|
|
def test_lint_fail_blocks_at_4():
|
|
assert L.compute_level(_rungs(lint="fail")) == 4
|
|
|
|
|
|
def test_unver_blocks_even_after_a_skip():
|
|
# skip at L2 is climbed past, but the unver at L3 still blocks → level 1.
|
|
assert L.compute_level(_rungs(upgrade="skip", backup_restore="unver")) == 1
|
|
|
|
|
|
# ---- intentional-skip climbing (the de-cap) ----
|
|
|
|
|
|
def test_single_version_recipe_climbs_past_upgrade_skip():
|
|
# old rule: upgrade N/A capped at L1. New rule: skip is climbed past → full climb 5.
|
|
assert L.compute_level(_rungs(upgrade="skip")) == 5
|
|
|
|
|
|
def test_stateless_recipe_climbs_past_backup_skip_to_lint():
|
|
assert L.compute_level(_rungs(upgrade="skip", backup_restore="skip")) == 5
|
|
|
|
|
|
def test_skip_does_not_count_as_pass():
|
|
# ALL skips → nothing passed → level 0 (a skip climbs, but never earns).
|
|
assert (
|
|
L.compute_level(
|
|
_rungs(
|
|
install="skip",
|
|
upgrade="skip",
|
|
backup_restore="skip",
|
|
functional="skip",
|
|
lint="skip",
|
|
)
|
|
)
|
|
== 0
|
|
)
|
|
|
|
|
|
def test_skip_then_pass_earns_the_higher_rung():
|
|
# skip at L4, pass at L5 → level 5 (the skip below doesn't stop the climb).
|
|
assert L.compute_level(_rungs(functional="skip")) == 5
|
|
|
|
|
|
def test_trailing_skip_keeps_last_pass():
|
|
# passes up to L3, skips above → level stays 3 (skips never raise).
|
|
assert L.compute_level(_rungs(functional="skip", lint="skip")) == 3
|
|
|
|
|
|
# ---- input validation ----
|
|
|
|
|
|
def test_invalid_status_raises():
|
|
bad = _rungs()
|
|
bad["functional"] = "na" # the OLD vocabulary is no longer valid — every N/A is classified
|
|
try:
|
|
L.compute_level(bad)
|
|
except ValueError:
|
|
return
|
|
raise AssertionError("expected ValueError on invalid rung status")
|
|
|
|
|
|
def test_missing_rung_raises():
|
|
bad = _rungs()
|
|
del bad["lint"]
|
|
try:
|
|
L.compute_level(bad)
|
|
except ValueError:
|
|
return
|
|
raise AssertionError("expected ValueError on missing rung")
|
|
|
|
|
|
# ---- helpers: backup_restore_status ----
|
|
|
|
|
|
def test_backup_restore_status_pass():
|
|
assert L.backup_restore_status("pass", "pass", True) == "pass"
|
|
|
|
|
|
def test_backup_restore_status_not_capable_is_intentional_skip():
|
|
assert L.backup_restore_status("skip", "skip", False) == "skip"
|
|
|
|
|
|
def test_backup_restore_status_fail_on_either():
|
|
assert L.backup_restore_status("pass", "fail", True) == "fail"
|
|
assert L.backup_restore_status("fail", "pass", True) == "fail"
|
|
|
|
|
|
def test_backup_restore_partial_is_unverified():
|
|
# backup-capable but restore didn't run cleanly (not pass, not fail) → cannot claim L3,
|
|
# and the non-run is NOT intentional → unver (blocks the level above it).
|
|
assert L.backup_restore_status("pass", "skip", True) == "unver"
|
|
assert L.backup_restore_status(None, None, True) == "unver"
|
|
|
|
|
|
# ---- helpers: tier_to_rung ----
|
|
|
|
|
|
def test_tier_to_rung_mapping_defaults_unverified():
|
|
assert L.tier_to_rung("pass") == "pass"
|
|
assert L.tier_to_rung("fail") == "fail"
|
|
# no intentionality information here — a non-run is unver; derive_rungs upgrades to "skip"
|
|
# only on a declared/structural fact, never the other way.
|
|
assert L.tier_to_rung("skip") == "unver"
|
|
assert L.tier_to_rung(None) == "unver"
|