sched: require proven ownership at poll/admin/recovery entries (F5)
This commit is contained in:
@@ -15,6 +15,7 @@ from wf_artifacts.runs.models import StoredRunStatus
|
||||
from wf_artifacts.runs.store import FileRunStore
|
||||
from wf_scheduling.calendar import OneShotSource
|
||||
from wf_scheduling.models import Schedule
|
||||
from wf_scheduling.ownership import SchedulerOwnership
|
||||
from wf_scheduling.poll import SCAN_CAP, Scheduler
|
||||
from wf_scheduling.prepare import SchedulePreparer
|
||||
from wf_scheduling.store import FileScheduleStore
|
||||
@@ -87,6 +88,7 @@ def _harness(
|
||||
capacity=capacity,
|
||||
preparer=preparer,
|
||||
dispatcher=ScriptedDispatcher(script),
|
||||
ownership=SchedulerOwnership(tmp_path / "sched", owner="test").acquire(),
|
||||
)
|
||||
return sched, sched_store, run_store, sources
|
||||
|
||||
@@ -135,6 +137,7 @@ def test_overlap_skip_blocks_and_late_drops() -> None:
|
||||
sched.poll(t0 + timedelta(minutes=30))
|
||||
kinds = [(r["kind"], r["resolved_at"]) for r in _history(store, "a")]
|
||||
assert any(k == "skipped-misfire" for k, _ in kinds)
|
||||
sched.ownership.release()
|
||||
|
||||
|
||||
def test_latest_coalesces_to_one_candidate_and_no_double_admit() -> None:
|
||||
@@ -167,6 +170,7 @@ def test_latest_coalesces_to_one_candidate_and_no_double_admit() -> None:
|
||||
2026, 9, 8, 13, 0
|
||||
)
|
||||
assert store.get_candidate("h") is None
|
||||
sched.ownership.release()
|
||||
|
||||
|
||||
def test_parallel_limits_and_interrupted_slots() -> None:
|
||||
@@ -206,6 +210,7 @@ def test_parallel_limits_and_interrupted_slots() -> None:
|
||||
r["kind"] == "skipped-overlap" and "12:15" in str(r["resolved_at"])
|
||||
for r in _history(store, "p")
|
||||
)
|
||||
sched.ownership.release()
|
||||
|
||||
|
||||
def test_pause_is_not_downtime() -> None:
|
||||
@@ -238,6 +243,7 @@ def test_pause_is_not_downtime() -> None:
|
||||
]
|
||||
assert "2026-09-08T10:00:00+00:00" not in admitted
|
||||
assert "2026-09-08T11:00:00+00:00" not in admitted
|
||||
sched.ownership.release()
|
||||
|
||||
|
||||
def test_long_downtime_is_bounded(tmp_path: Path) -> None:
|
||||
@@ -254,6 +260,7 @@ def test_long_downtime_is_bounded(tmp_path: Path) -> None:
|
||||
assert (
|
||||
len([r for r in _history(store, "m") if r["kind"] == "interval-summary"]) == 1
|
||||
)
|
||||
sched.ownership.release()
|
||||
|
||||
|
||||
def test_fairness_slow_schedule_not_starved(tmp_path: Path) -> None:
|
||||
@@ -277,6 +284,7 @@ def test_fairness_slow_schedule_not_starved(tmp_path: Path) -> None:
|
||||
runs.save_run(run.model_copy(update={"status": StoredRunStatus.COMPLETED}))
|
||||
sched.poll(t0 + timedelta(seconds=30))
|
||||
assert any(r["kind"] == "admitted" for r in _history(store, "slow"))
|
||||
sched.ownership.release()
|
||||
|
||||
|
||||
def test_capacity_wait_then_expire_for_skip(tmp_path: Path) -> None:
|
||||
@@ -289,3 +297,4 @@ def test_capacity_wait_then_expire_for_skip(tmp_path: Path) -> None:
|
||||
cast(ScriptedDispatcher, sched.dispatcher).script = {"*": "complete"}
|
||||
sched.poll(t0 + timedelta(seconds=30))
|
||||
assert len([r for r in _history(store, "a") if r["kind"] == "admitted"]) == 1
|
||||
sched.ownership.release()
|
||||
|
||||
Reference in New Issue
Block a user