Files
cc-ci/tests/concurrency/test_run_state.py
notplantsandClaude Fable 5.1 9a80002b37
continuous-integration/drone/push Build is failing
lint: ruff format + one auto-fix so the push self-test is green again
`scripts/lint.sh --fix` from the pinned lint devshell: 90 Python files
reformatted (ruff format, mechanical) and one C420 (dict comprehension →
dict.fromkeys) in tests/unit/test_f211_sso_skip.py. The push self-test had
been failing at the lint stage since build 1313 (2026-08-31) on exactly
these files; nothing else changed.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FqkQq3CDmFWcQ7u1LzoyRz
2026-09-07 21:15:05 +00:00

80 lines
3.7 KiB
Python

"""Run-scoped state files — M2(c) live-verify regression (not one of the 19 plan cases).
The four CCCI state files (deploys countfile, opstate, deps, depskip) must be keyed by
run id + harness pid, NEVER by app domain: a second run of the SAME domain executes its
main() preamble (state-file init, deploy_app's _record_deploy) BEFORE it blocks at the
app lock, so domain-keyed files in the shared tempdir get reset/removed underneath the
live first run. Observed live (builds 279/281): false DG4.1 deploy-count=2 in run 1,
countfile FileNotFoundError crash in run 2. Children never re-derive these paths — they
receive them via the CCCI_*_FILE env vars, so per-process uniqueness is sufficient.
"""
from __future__ import annotations
import os
import sys
import tempfile
sys.path.insert(0, os.path.dirname(__file__))
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "..", "runner"))
import run_recipe_ci # noqa: E402
from concutil import wait_marker # noqa: E402
DOMAIN = "fake-abc123.ci.commoninternet.net"
def test_20_state_paths_keyed_by_run_and_pid_never_by_domain(monkeypatch):
domain = "immi-ad3e33.ci.commoninternet.net"
monkeypatch.setenv("CCCI_APP_DOMAIN", domain)
monkeypatch.setenv("DRONE_BUILD_NUMBER", "279")
p279 = run_recipe_ci._run_state_path("deploys")
monkeypatch.setenv("DRONE_BUILD_NUMBER", "281")
p281 = run_recipe_ci._run_state_path("deploys")
# the double-!testme invariant: two runs (same domain) share NO state file
assert p279 != p281
# keyed by run id + pid, under the tempdir
base = os.path.basename(p279)
assert base == f"ccci-deploys-279-{os.getpid()}"
assert os.path.dirname(p279) == tempfile.gettempdir()
# the app domain must not appear in the path at all
assert domain not in p279 and domain not in p281
def test_20c_same_domain_runs_each_keep_their_own_count(tmp_path, lock_dir, pool):
"""The live CONC-A1 interleaving, with REAL processes + the REAL lock and counter code:
run A holds the app lock; run B (same domain) fires its pre-lock _record_deploy and
blocks; A then reads its counter — must still be 1 (not polluted by B) — and removes
its own file; B acquires and must find ITS file intact (no FileNotFoundError)."""
gate = tmp_path / "gate"
env_a = {"TMPDIR": str(tmp_path), "DRONE_BUILD_NUMBER": "9001"}
env_b = {"TMPDIR": str(tmp_path), "DRONE_BUILD_NUMBER": "9002"}
pa, out_a = pool.spawn("deploy-count-run", DOMAIN, str(gate), env_extra=env_a)
assert wait_marker(out_a, "ACQUIRED")
pb, out_b = pool.spawn("deploy-count-run", DOMAIN, "", env_extra=env_b)
# B's main()-preamble + pre-lock increment have fired; B is now blocked on the app lock
assert wait_marker(out_b, "PRELOCK")
assert wait_marker(out_b, "ACQUIRED", timeout=1.0) is None # still serialised behind A
gate.touch() # let A read its counter only AFTER B's pre-lock work landed
line_a = wait_marker(out_a, "COUNT")
assert line_a is not None and line_a.strip() == "COUNT 1", line_a # not 2: B didn't pollute A
pa.wait(timeout=15)
line_b = wait_marker(out_b, "COUNT")
assert line_b is not None and line_b.strip() == "COUNT 1", (
line_b
) # B's file survived A's remove
pb.wait(timeout=15)
def test_20b_manual_runs_distinct_via_pid(monkeypatch):
# no DRONE_BUILD_NUMBER and no domain/run-id env → run_id() falls back to "manual";
# the pid suffix still separates two concurrent hand-runs of the same domain.
for var in ("DRONE_BUILD_NUMBER", "CCCI_APP_DOMAIN", "CCCI_RUN_ID"):
monkeypatch.delenv(var, raising=False)
p = run_recipe_ci._run_state_path("opstate")
assert os.path.basename(p) == f"ccci-opstate-manual-{os.getpid()}"