sched: extract typed preparer/dispatcher seams; test impl out of production (F6)

This commit is contained in:
lda
2026-09-08 11:19:48 +07:00 Verified
parent 7f6c832b0b
commit 15bd472bdc
8 changed files with 564 additions and 162 deletions
+26 -28
View File
@@ -4,12 +4,19 @@ from __future__ import annotations
from datetime import UTC, datetime, timedelta
from pathlib import Path
from typing import Any
from typing import Any, cast
from tests.scheduling.controlled import (
DictDeployments,
ScriptedDispatcher,
fixture_environment,
)
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.poll import SCAN_CAP, Scheduler
from wf_scheduling.prepare import SchedulePreparer
from wf_scheduling.store import FileScheduleStore
UTC_TZ = UTC
@@ -41,21 +48,6 @@ class PeriodicSource:
return self.start + n * self.period
class OneShotSource:
def __init__(self, at: datetime) -> None:
self.at = at
self.next_calls = 0
self.prev_calls = 0
def next_after(self, instant: datetime) -> datetime | None:
self.next_calls += 1
return self.at if instant < self.at else None
def prev_before(self, instant: datetime) -> datetime | None:
self.prev_calls += 1
return self.at if instant > self.at else None
def _sched_model(sid: str, start_hint: str = "cron", **kw: Any) -> Schedule:
now = ts(2026, 9, 8, 12, 0)
base: dict[str, Any] = {
@@ -74,21 +66,27 @@ def _harness(
tmp_path: Path,
*,
capacity: int = 4,
outcomes: dict[str, str] | None = None,
script: dict | None = None,
deployments: dict[str, dict] | None = None,
) -> tuple[Scheduler, FileScheduleStore, FileRunStore, dict]:
sched_store = FileScheduleStore(tmp_path / "sched")
run_store = FileRunStore(tmp_path / "runs")
sources: dict = {}
preparer = SchedulePreparer(
DictDeployments(
deployments
if deployments is not None
else {"dep-1": {"rev": 1, "required": []}}
),
fixture_environment,
)
sched = Scheduler(
schedule_store=sched_store,
run_store=run_store,
sources=sources,
capacity=capacity,
outcomes=outcomes,
deployments=deployments
if deployments is not None
else {"dep-1": {"rev": 1, "required": []}},
preparer=preparer,
dispatcher=ScriptedDispatcher(script),
)
return sched, sched_store, run_store, sources
@@ -119,7 +117,7 @@ def test_overlap_skip_blocks_and_late_drops() -> None:
with tempfile.TemporaryDirectory() as tmp:
root = Path(tmp)
sched, store, runs, sources = _harness(root, capacity=4, outcomes={"*": "hang"})
sched, store, runs, sources = _harness(root, capacity=4, script={"*": "hang"})
t0 = ts(2026, 9, 8, 12, 0)
_add(
sched,
@@ -161,7 +159,7 @@ def test_latest_coalesces_to_one_candidate_and_no_double_admit() -> None:
# Capacity returns at 13:00 while 13:00 is also due: exactly one
# admission for 13:00, 12:00 superseded, never both.
sched.capacity = 4
sched.outcomes = {"*": "complete"}
cast(ScriptedDispatcher, sched.dispatcher).script = {"*": "complete"}
sched.poll(ts(2026, 9, 8, 13, 0))
admitted = [r for r in _history(store, "h") if r["kind"] == "admitted"]
assert len(admitted) == 1
@@ -176,7 +174,7 @@ def test_parallel_limits_and_interrupted_slots() -> None:
with tempfile.TemporaryDirectory() as tmp:
root = Path(tmp)
sched, store, runs, sources = _harness(root, capacity=4, outcomes={"*": "hang"})
sched, store, runs, sources = _harness(root, capacity=4, script={"*": "hang"})
t0 = ts(2026, 9, 8, 12, 0)
_add(
sched,
@@ -216,7 +214,7 @@ def test_pause_is_not_downtime() -> None:
with tempfile.TemporaryDirectory() as tmp:
root = Path(tmp)
sched, store, runs, sources = _harness(
root, capacity=4, outcomes={"*": "complete"}
root, capacity=4, script={"*": "complete"}
)
_add(
sched,
@@ -244,7 +242,7 @@ def test_pause_is_not_downtime() -> None:
def test_long_downtime_is_bounded(tmp_path: Path) -> None:
sched, store, runs, sources = _harness(
tmp_path, capacity=4, outcomes={"*": "complete"}
tmp_path, capacity=4, script={"*": "complete"}
)
src = PeriodicSource(timedelta(minutes=1), ts(2023, 9, 8, 12, 0))
_add(sched, store, sources, "m", src, ts(2023, 9, 8, 12, 0), misfire="latest")
@@ -259,7 +257,7 @@ def test_long_downtime_is_bounded(tmp_path: Path) -> None:
def test_fairness_slow_schedule_not_starved(tmp_path: Path) -> None:
sched, store, runs, sources = _harness(tmp_path, capacity=1, outcomes={"*": "hang"})
sched, store, runs, sources = _harness(tmp_path, capacity=1, script={"*": "hang"})
t0 = ts(2026, 9, 8, 12, 0)
_add(
sched,
@@ -288,6 +286,6 @@ def test_capacity_wait_then_expire_for_skip(tmp_path: Path) -> None:
assert sched.poll(t0) == {"a": "admit:held-undecided"}
assert store.get_candidate("a") is None
sched.capacity = 1
sched.outcomes = {"*": "complete"}
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