Revert "feat(lvl5): P1 — 5-rung ladder (L5=abra recipe lint) + de-capped level semantics"
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
This reverts commit e219a7891d.
This commit is contained in:
@ -1,14 +1,8 @@
|
||||
"""Unit tests for the level ladder (harness.level) — phase lvl5 semantics.
|
||||
"""Unit tests for the Phase-3 level ladder (harness.level), plan-phase3-results-ux.md §4.1 / R1.
|
||||
|
||||
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
|
||||
Pure function — no I/O. Proves the YunoHost gap-caps-the-level semantics, including the U0 gate
|
||||
acceptance: a recipe that climbs through L4 reports 4, and one that fails at L2 is capped at 1
|
||||
(the level just below the failed rung). Run cold with: cc-ci-run -m pytest tests/unit/test_level.py -q
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
@ -25,115 +19,69 @@ def _rungs(
|
||||
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) ----
|
||||
# ---- the ladder: four essential rungs, top is L4 (functional) ----
|
||||
|
||||
|
||||
def test_full_clean_climb_is_L5():
|
||||
assert L.compute_level(_rungs()) == 5
|
||||
def test_full_clean_climb_to_L4():
|
||||
# All four essential rungs pass → L4 (the top; integration/recipe-local are optional, not leveled).
|
||||
lvl, reason = L.compute_level(_rungs())
|
||||
assert lvl == 4
|
||||
assert reason == ""
|
||||
|
||||
|
||||
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]
|
||||
def test_fails_at_L2_capped_at_L1():
|
||||
# GATE: upgrade fails → capped at L1 even though higher rungs would pass.
|
||||
lvl, reason = L.compute_level(_rungs(upgrade="fail", backup_restore="pass", functional="pass"))
|
||||
assert lvl == 1
|
||||
assert "L2" in reason and "FAILED" in reason
|
||||
|
||||
|
||||
# ---- 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 ----
|
||||
# ---- L0 / install ----
|
||||
|
||||
|
||||
def test_install_fail_is_L0():
|
||||
assert L.compute_level(_rungs(install="fail")) == 0
|
||||
lvl, reason = L.compute_level(_rungs(install="fail"))
|
||||
assert lvl == 0
|
||||
assert "L1" in reason and "FAILED" in reason
|
||||
|
||||
|
||||
def test_install_unver_is_L0():
|
||||
assert L.compute_level(_rungs(install="unver")) == 0
|
||||
# ---- gap-caps semantics: a higher pass can't rescue a lower gap ----
|
||||
|
||||
|
||||
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_higher_pass_does_not_rescue_lower_na():
|
||||
# backup/restore N/A (stateless app) caps at L2 even though functional would pass.
|
||||
lvl, reason = L.compute_level(_rungs(backup_restore="na", functional="pass"))
|
||||
assert lvl == 2
|
||||
assert "L3" in reason and "N/A" in reason
|
||||
|
||||
|
||||
def test_lint_fail_blocks_at_4():
|
||||
assert L.compute_level(_rungs(lint="fail")) == 4
|
||||
def test_upgrade_na_caps_at_L1():
|
||||
# only one published version → no upgrade possible → N/A caps at L1 (upgrade is essential).
|
||||
lvl, reason = L.compute_level(_rungs(upgrade="na"))
|
||||
assert lvl == 1
|
||||
assert "L2" in reason and "N/A" in reason
|
||||
|
||||
|
||||
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
|
||||
def test_functional_na_caps_at_L3():
|
||||
# no recipe-specific functional tests → functional N/A caps at L3.
|
||||
lvl, reason = L.compute_level(_rungs(functional="na"))
|
||||
assert lvl == 3
|
||||
assert "L4" in reason and "N/A" in reason
|
||||
|
||||
|
||||
# ---- 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
|
||||
def test_functional_fail_caps_at_L3():
|
||||
lvl, reason = L.compute_level(_rungs(functional="fail"))
|
||||
assert lvl == 3
|
||||
assert "L4" in reason and "FAILED" in reason
|
||||
|
||||
|
||||
# ---- input validation ----
|
||||
@ -141,7 +89,7 @@ def test_trailing_skip_keeps_last_pass():
|
||||
|
||||
def test_invalid_status_raises():
|
||||
bad = _rungs()
|
||||
bad["functional"] = "na" # the OLD vocabulary is no longer valid — every N/A is classified
|
||||
bad["functional"] = "passed" # not in the vocabulary
|
||||
try:
|
||||
L.compute_level(bad)
|
||||
except ValueError:
|
||||
@ -149,16 +97,6 @@ def test_invalid_status_raises():
|
||||
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 ----
|
||||
|
||||
|
||||
@ -166,8 +104,8 @@ 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_not_capable_is_na():
|
||||
assert L.backup_restore_status("skip", "skip", False) == "na"
|
||||
|
||||
|
||||
def test_backup_restore_status_fail_on_either():
|
||||
@ -175,20 +113,16 @@ def test_backup_restore_status_fail_on_either():
|
||||
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"
|
||||
def test_backup_restore_partial_is_na():
|
||||
# backup-capable but restore didn't run cleanly (not pass, not fail) → cannot claim L3
|
||||
assert L.backup_restore_status("pass", "skip", True) == "na"
|
||||
|
||||
|
||||
# ---- helpers: tier_to_rung ----
|
||||
|
||||
|
||||
def test_tier_to_rung_mapping_defaults_unverified():
|
||||
def test_tier_to_rung_mapping():
|
||||
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"
|
||||
assert L.tier_to_rung("skip") == "na"
|
||||
assert L.tier_to_rung(None) == "na"
|
||||
|
||||
Reference in New Issue
Block a user