All checks were successful
continuous-integration/drone/push Build is passing
- resolve_upgrade_base: BasePlan(kind=version|ref|skip); last-green (warm canonical) primary, main-tip fallback, declared skip else. UPGRADE_BASE_VERSION retained as optional override. - deploy_app: base_ref path (chaos-deploy a main-tip/last-green commit) + apply_previous wiring. - lifecycle: previous/ surface (has_previous, previous_target_version, previous_status decision, provide/remove overlay, compose_file add/remove, recipe_branch_commit, stack_service_names). - generic.perform_upgrade: strip previous/ overlay + COMPOSE_FILE entry before head redeploy. - discourse: compose.ccci.yml now environmental-only (order: stop-first); removed bitnamilegacy pins + sidekiq + UPGRADE_BASE_VERSION; test_upgrade.py asserts head image == official 3.5.3 + no sidekiq. - unit tests: resolve_upgrade_base matrix + previous/ apply/skip/stale + COMPOSE_FILE layering.
106 lines
4.4 KiB
Python
106 lines
4.4 KiB
Python
"""Unit tests for `run_recipe_ci.resolve_upgrade_base` — the DYNAMIC upgrade-base decision (phase prevb).
|
|
|
|
Resolution order: upgrade∉stages / EXPECTED_NA[upgrade] (declared skip) → explicit UPGRADE_BASE_VERSION
|
|
override → last-green (warm canonical) → target-branch (`main`) tip → skip (no predecessor). The result
|
|
is a `BasePlan(kind, version, ref, reason)`: kind ∈ {"version", "ref", "skip"}; `.runs` is True for
|
|
version/ref (the upgrade tier runs). Replaces the old static `recipe_versions[-2]` default.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
import sys
|
|
from types import SimpleNamespace
|
|
|
|
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "..", "runner"))
|
|
import run_recipe_ci # noqa: E402
|
|
from harness import canonical, lifecycle # noqa: E402
|
|
|
|
ALL = {"install", "upgrade", "backup", "restore", "custom"}
|
|
HEAD = "aaaa1111head"
|
|
MAIN = "bbbb2222main"
|
|
|
|
|
|
def _meta(expected_na=None, upgrade_base_version=None):
|
|
return SimpleNamespace(EXPECTED_NA=expected_na, UPGRADE_BASE_VERSION=upgrade_base_version)
|
|
|
|
|
|
def _no_canonical(monkeypatch):
|
|
monkeypatch.setattr(canonical, "read_registry", lambda r: None)
|
|
|
|
|
|
def _no_main(monkeypatch):
|
|
monkeypatch.setattr(lifecycle, "recipe_branch_commit", lambda r, b="main": None)
|
|
|
|
|
|
def test_upgrade_not_in_stages_skip(monkeypatch):
|
|
# never consults canonical/main when upgrade isn't requested
|
|
monkeypatch.setattr(
|
|
canonical, "read_registry", lambda r: (_ for _ in ()).throw(AssertionError("not consulted"))
|
|
)
|
|
plan = run_recipe_ci.resolve_upgrade_base(ALL - {"upgrade"}, _meta(), "somerecipe")
|
|
assert plan.kind == "skip" and not plan.runs
|
|
|
|
|
|
def test_expected_na_upgrade_skip_even_with_canonical_and_override(monkeypatch):
|
|
# EXPECTED_NA[upgrade] is the strongest, declared fact — short-circuits before override/canonical.
|
|
monkeypatch.setattr(
|
|
canonical, "read_registry", lambda r: {"version": "9.9.9", "status": "warm"}
|
|
)
|
|
declared = {"upgrade": "no deployable upgrade base (moving tag republished)"}
|
|
plan = run_recipe_ci.resolve_upgrade_base(
|
|
ALL, _meta(expected_na=declared, upgrade_base_version="0.2.0+v0.4"), "bluesky-pds"
|
|
)
|
|
assert plan.kind == "skip" and "EXPECTED_NA" in plan.reason
|
|
|
|
|
|
def test_explicit_override_wins_over_canonical(monkeypatch):
|
|
monkeypatch.setattr(
|
|
canonical, "read_registry", lambda r: {"version": "9.9.9", "status": "warm"}
|
|
)
|
|
monkeypatch.setattr(
|
|
lifecycle,
|
|
"recipe_branch_commit",
|
|
lambda r, b="main": (_ for _ in ()).throw(AssertionError("not consulted")),
|
|
)
|
|
plan = run_recipe_ci.resolve_upgrade_base(
|
|
ALL, _meta(upgrade_base_version="0.7.0+3.3.1"), "discourse"
|
|
)
|
|
assert plan.kind == "version" and plan.version == "0.7.0+3.3.1" and plan.runs
|
|
|
|
|
|
def test_last_green_warm_canonical_is_primary(monkeypatch):
|
|
# no override → last-green (warm canonical version) is the PRIMARY base; main is not consulted.
|
|
monkeypatch.setattr(
|
|
canonical, "read_registry", lambda r: {"version": "0.6.0+3.1.1", "status": "idle"}
|
|
)
|
|
monkeypatch.setattr(
|
|
lifecycle,
|
|
"recipe_branch_commit",
|
|
lambda r, b="main": (_ for _ in ()).throw(AssertionError("not consulted")),
|
|
)
|
|
plan = run_recipe_ci.resolve_upgrade_base(ALL, _meta(), "keycloak", head_ref=HEAD)
|
|
assert plan.kind == "version" and plan.version == "0.6.0+3.1.1" and "last-green" in plan.reason
|
|
|
|
|
|
def test_main_tip_fallback_when_no_last_green(monkeypatch):
|
|
_no_canonical(monkeypatch)
|
|
monkeypatch.setattr(lifecycle, "recipe_branch_commit", lambda r, b="main": MAIN)
|
|
plan = run_recipe_ci.resolve_upgrade_base(ALL, _meta(), "discourse", head_ref=HEAD)
|
|
assert plan.kind == "ref" and plan.ref == MAIN and plan.version is None and plan.runs
|
|
|
|
|
|
def test_head_equals_main_tip_skip(monkeypatch):
|
|
# the PR head IS the main tip (empty PR / LATEST run) → no real predecessor → declared skip.
|
|
_no_canonical(monkeypatch)
|
|
monkeypatch.setattr(lifecycle, "recipe_branch_commit", lambda r, b="main": HEAD)
|
|
plan = run_recipe_ci.resolve_upgrade_base(ALL, _meta(), "discourse", head_ref=HEAD)
|
|
assert plan.kind == "skip" and "main tip" in plan.reason and not plan.runs
|
|
|
|
|
|
def test_no_canonical_no_main_skip(monkeypatch):
|
|
_no_canonical(monkeypatch)
|
|
_no_main(monkeypatch)
|
|
plan = run_recipe_ci.resolve_upgrade_base(ALL, _meta(), "brandnew", head_ref=HEAD)
|
|
assert plan.kind == "skip" and "no predecessor" in plan.reason
|