187 lines
6.4 KiB
Python
187 lines
6.4 KiB
Python
"""Contract tests for the coordinator-independent distributed workload boundary."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
from pathlib import Path
|
|
from typing import Mapping, Sequence
|
|
from uuid import NAMESPACE_URL, uuid5
|
|
|
|
import pytest
|
|
|
|
from scimesh.distributed import (
|
|
ArtifactReference,
|
|
CompletedPartial,
|
|
DistributedPlan,
|
|
DistributedWorkloadRegistry,
|
|
FinalResult,
|
|
PlannedTask,
|
|
PlanningService,
|
|
)
|
|
|
|
|
|
def artifact(seed: str, content_type: str = "text/tab-separated-values") -> ArtifactReference:
|
|
return ArtifactReference(
|
|
artifact_id=str(uuid5(NAMESPACE_URL, seed)),
|
|
sha256=(seed.encode("utf-8").hex() * 64)[:64],
|
|
content_type=content_type,
|
|
)
|
|
|
|
|
|
class DummyWorkload:
|
|
"""A deterministic fake workload used to test the generic CTX-07 bridge."""
|
|
|
|
name = "dummy-workload"
|
|
description = "A deterministic test workload."
|
|
|
|
def __init__(self) -> None:
|
|
self.plan_calls = 0
|
|
self.received_partials: tuple[CompletedPartial, ...] = ()
|
|
|
|
def validate_job(self, parameters: Mapping[str, object]) -> None:
|
|
if parameters != {"mode": "valid"}:
|
|
raise ValueError("mode must be valid")
|
|
|
|
def plan(
|
|
self,
|
|
input_path: Path,
|
|
input_artifact_id: str,
|
|
parameters: Mapping[str, object],
|
|
shard_rows: int,
|
|
workspace: Path,
|
|
) -> DistributedPlan:
|
|
self.plan_calls += 1
|
|
assert input_path.name == "input.tsv"
|
|
assert workspace.name == "workspace"
|
|
return DistributedPlan(
|
|
workload=self.name,
|
|
resolved_parameters={"mode": parameters["mode"], "source": input_artifact_id},
|
|
tasks=(
|
|
PlannedTask(0, artifact(f"{input_artifact_id}:0"), {"mode": "valid"}),
|
|
PlannedTask(1, artifact(f"{input_artifact_id}:1"), {"mode": "valid"}),
|
|
),
|
|
)
|
|
|
|
def reduce(
|
|
self,
|
|
partial_results: Sequence[CompletedPartial],
|
|
parameters: Mapping[str, object],
|
|
workspace: Path,
|
|
) -> FinalResult:
|
|
self.received_partials = tuple(partial_results)
|
|
return FinalResult(artifact("final", "text/csv"), {"partial_count": len(partial_results)})
|
|
|
|
|
|
def service() -> tuple[PlanningService, DummyWorkload]:
|
|
workload = DummyWorkload()
|
|
registry = DistributedWorkloadRegistry()
|
|
registry.register(workload)
|
|
return PlanningService(registry), workload
|
|
|
|
|
|
def test_unknown_workload_is_rejected_before_a_plan_is_written(tmp_path: Path) -> None:
|
|
planner, workload = service()
|
|
|
|
with pytest.raises(ValueError, match="unknown distributed workload"):
|
|
planner.plan(
|
|
"unknown-workload", tmp_path / "input.tsv", artifact("input").artifact_id,
|
|
{"mode": "valid"}, 10, tmp_path / "workspace",
|
|
)
|
|
|
|
assert workload.plan_calls == 0
|
|
|
|
|
|
def test_invalid_job_is_rejected_before_the_planner_runs(tmp_path: Path) -> None:
|
|
planner, workload = service()
|
|
|
|
with pytest.raises(ValueError, match="mode must be valid"):
|
|
planner.plan(
|
|
"dummy-workload", tmp_path / "input.tsv", artifact("input").artifact_id,
|
|
{"mode": "invalid"}, 10, tmp_path / "workspace",
|
|
)
|
|
|
|
assert workload.plan_calls == 0
|
|
|
|
|
|
def test_two_shard_plan_is_deterministic_and_json_serializable(tmp_path: Path) -> None:
|
|
planner, _ = service()
|
|
input_artifact_id = artifact("input").artifact_id
|
|
first = planner.plan(
|
|
"dummy-workload", tmp_path / "input.tsv", input_artifact_id,
|
|
{"mode": "valid"}, 10, tmp_path / "workspace",
|
|
)
|
|
second = planner.plan(
|
|
"dummy-workload", tmp_path / "input.tsv", input_artifact_id,
|
|
{"mode": "valid"}, 10, tmp_path / "workspace",
|
|
)
|
|
|
|
assert first.to_json() == second.to_json()
|
|
payload = json.loads(first.to_json())
|
|
assert [task["chunk_index"] for task in payload["tasks"]] == [0, 1]
|
|
assert all(set(task) == {"chunk_index", "input_artifact", "parameters"} for task in payload["tasks"])
|
|
assert DistributedPlan.from_json(first.to_json()) == first
|
|
|
|
|
|
def test_plan_rejects_unsafe_or_non_deterministic_task_payloads() -> None:
|
|
with pytest.raises(ValueError, match="unique, ascending"):
|
|
DistributedPlan(
|
|
workload="dummy-workload",
|
|
resolved_parameters={},
|
|
tasks=(
|
|
PlannedTask(1, artifact("one"), {}),
|
|
PlannedTask(0, artifact("zero"), {}),
|
|
),
|
|
)
|
|
|
|
with pytest.raises(ValueError, match="JSON-compatible"):
|
|
PlannedTask(0, artifact("bad"), {"path": Path("not-serializable")})
|
|
|
|
with pytest.raises(ValueError, match="URI or local path"):
|
|
PlannedTask(0, artifact("uri"), {"input": "file:///tmp/input.tsv"})
|
|
|
|
with pytest.raises(ValueError, match="canonical hyphenated"):
|
|
DistributedPlan("dummy_workload", {}, (PlannedTask(0, artifact("one"), {}),))
|
|
|
|
|
|
def test_reducer_receives_completed_partials_in_chunk_order(tmp_path: Path) -> None:
|
|
planner, workload = service()
|
|
result = planner.reduce(
|
|
"dummy-workload",
|
|
(
|
|
CompletedPartial(3, artifact("three", "text/csv"), {"scanned_rows": 10}),
|
|
CompletedPartial(1, artifact("one", "text/csv"), {"scanned_rows": 10}),
|
|
),
|
|
{"mode": "valid"},
|
|
tmp_path / "workspace",
|
|
)
|
|
|
|
assert [partial.chunk_index for partial in workload.received_partials] == [1, 3]
|
|
assert result.metrics == {"partial_count": 2}
|
|
|
|
|
|
def test_reducer_rejects_duplicate_chunk_indexes_before_invocation(tmp_path: Path) -> None:
|
|
planner, workload = service()
|
|
duplicate = CompletedPartial(0, artifact("partial", "text/csv"), {"scanned_rows": 1})
|
|
|
|
with pytest.raises(ValueError, match="unique chunk_index"):
|
|
planner.reduce("dummy-workload", (duplicate, duplicate), {"mode": "valid"}, tmp_path)
|
|
|
|
assert workload.received_partials == ()
|
|
|
|
|
|
def test_artifact_references_never_accept_paths_or_uris() -> None:
|
|
with pytest.raises(ValueError, match="UUID"):
|
|
ArtifactReference("file:///tmp/input.tsv", "a" * 64, "text/csv")
|
|
with pytest.raises(ValueError, match="lowercase SHA-256"):
|
|
ArtifactReference(str(uuid5(NAMESPACE_URL, "input")), "A" * 64, "text/csv")
|
|
|
|
|
|
def test_registry_descriptions_are_stable_and_duplicate_names_are_rejected() -> None:
|
|
registry = DistributedWorkloadRegistry()
|
|
first, second = DummyWorkload(), DummyWorkload()
|
|
registry.register(first)
|
|
|
|
assert registry.descriptions()[0].name == "dummy-workload"
|
|
with pytest.raises(ValueError, match="already registered"):
|
|
registry.register(second)
|