"""Lifetime hardening (concurrency-restructure plan, cases 13-16): the REAL prctl/signal/alarm guards installed by helper subprocesses; tests assert teardown ran, exit was non-zero, and the lock was released.""" from __future__ import annotations import os import signal import sys sys.path.insert(0, os.path.dirname(__file__)) sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "..", "runner")) from concutil import ( # noqa: E402 DOMAIN, wait_lock_state, wait_marker, wait_pid_gone, ) def test_13_pdeathsig_parent_kill_terms_harness(lock_dir, pool): """Case 13: wrapper-parent spawns a guarded harness-child; the parent is SIGKILL'd (the harness gets no courtesy signal) -> the kernel's PDEATHSIG TERMs the child, its teardown funnel runs, it exits, and the lock is released.""" p, out = pool.spawn("wrapper", DOMAIN) line = wait_marker(out, "WRAPPED") assert line, "wrapper never spawned its child" child_pid = int(line.split()[1]) pool.track_pid(child_pid) assert wait_marker(out, "READY"), "guarded child never got ready" p.kill() # parent dies WITHOUT signalling the child — only PDEATHSIG can save us p.wait(timeout=10) assert wait_pid_gone(child_pid), "guarded child must exit on parent death (PDEATHSIG)" assert wait_marker(out, "TEARDOWN", timeout=5), "teardown funnel did not run" assert wait_lock_state(DOMAIN, "free") == "free" def test_14_already_orphaned_helper_refuses_to_run(lock_dir, pool): """Case 14 (ppid race): a helper whose parent died BEFORE the prctl was armed (it starts already reparented to pid 1) must refuse to run — PDEATHSIG would never fire for it.""" # Spawn an intermediate parent that forks orphan-probe and exits immediately. import subprocess out = os.path.join(pool.out_dir, "orphan.out") intermediate = ( "import subprocess, sys, os; " "subprocess.Popen([sys.executable, os.environ['CCCI_HELPERS'], 'orphan-probe']); " ) env = dict( os.environ, CCCI_HELPER_OUT=out, CCCI_HELPERS=os.path.join(os.path.dirname(__file__), "helpers.py"), ) subprocess.run([sys.executable, "-c", intermediate], env=env, timeout=15, check=True) line = wait_marker(out, "REFUSED", timeout=20) assert line, "orphaned helper did not refuse to run (or never reparented to pid 1)" def test_15_deadline_alarm_fires_teardown_and_releases(lock_dir, pool): """Case 15: the self-deadline (alarm). A guarded helper with a 2s deadline tears down via the funnel (finally: ran), exits NON-zero, and its lock is released.""" p, out = pool.spawn("guarded", DOMAIN, "2") assert wait_marker(out, "READY") rc = p.wait(timeout=20) assert rc != 0, f"deadline exit must be non-zero (got {rc})" assert rc == 128 + signal.SIGALRM, f"expected 142 (128+SIGALRM), got {rc}" assert wait_marker(out, "TEARDOWN", timeout=5), "teardown funnel did not run on deadline" assert wait_lock_state(DOMAIN, "free") == "free" def test_16_sigterm_runs_teardown_funnel_and_releases(lock_dir, pool): """Case 16: SIGTERM (drone cancel path) -> the finally: teardown funnel runs, exit is non-zero, lock released.""" p, out = pool.spawn("guarded", DOMAIN, "3600") assert wait_marker(out, "READY") p.send_signal(signal.SIGTERM) rc = p.wait(timeout=20) assert rc != 0, f"SIGTERM exit must be non-zero (got {rc})" assert rc == 128 + signal.SIGTERM, f"expected 143 (128+SIGTERM), got {rc}" assert wait_marker(out, "TEARDOWN", timeout=5), "teardown funnel did not run on SIGTERM" assert wait_lock_state(DOMAIN, "free") == "free"