Files
SciMesh/tests/test_sdk_compatibility.py
T

687 lines
23 KiB
Python

"""Compatibility tests for the built-in SDK bridge and scientific reference."""
from __future__ import annotations
import csv
from dataclasses import replace
from pathlib import Path
from uuid import uuid4
import pytest
from scimesh.chemistry.dataset import find_molecule_by_id
from scimesh.sdk import (
ArtifactCollection,
ArtifactSchema,
CheckpointPolicy,
CompatibilityError,
ComponentRef,
DeterminismProfile,
FeatureRequirement,
GangSpec,
JobRequest,
LocalArtifactStore,
LocalCoreBatchExecutor,
LocalPlanningContext,
NetworkPolicy,
PortRef,
ProcessModel,
RetryPolicy,
SchemaRef,
StageKind,
TrustMode,
VerificationDecision,
VerificationStatus,
VersionRange,
WorkloadDefinition,
WorkloadRegistry,
assert_manifest_round_trip,
)
from scimesh.workloads.library import default_sdk_registry, default_sdk_runtime
from scimesh.workloads.search import similarity_search_sdk_definition
from scimesh.workloads.similarity_search import search_similar, write_search_results
def _write_tiny_dataset(path: Path) -> None:
path.write_text(
"chembl_id\tcanonical_smiles\textra\n"
"QUERY\tCCO\tquery\n"
"ALCOHOL\tCCCO\talcohol\n"
"ALKANE\tCCCC\talkane\n"
"BROKEN\tnot-a-smiles\tinvalid\n"
"DUPLICATE\tCCO\tduplicate\n"
"AMINE\tCCN\tamine\n",
encoding="utf-8",
)
def _registered_similarity_search(shard_rows: int = 2):
registry = default_sdk_registry(shard_rows=shard_rows)
runtime = default_sdk_runtime()
descriptions = registry.descriptions()
assert len(descriptions) == 4
description = next(
item for item in descriptions if item.workload.name == "similarity-search"
)
definition, negotiated = registry.require(
description.workload.name,
description.workload.version,
description.package_digest,
runtime=runtime,
)
return registry, runtime, description, definition, negotiated
def _request_for(
dataset: Path,
artifact_store: LocalArtifactStore,
definition: WorkloadDefinition,
) -> JobRequest:
input_port = definition.manifest.inputs["input"]
dataset_artifact = artifact_store.import_file(
dataset,
declaration=input_port.schema,
)
return JobRequest(
workload=definition.manifest.workload,
parameters={"query_id": "QUERY", "top_k": 3, "progress_every": 0},
inputs={"input": ArtifactCollection.single(dataset_artifact)},
)
def test_builtin_similarity_search_manifest_is_registered_and_negotiable() -> None:
_, _, description, definition, negotiated = _registered_similarity_search()
manifest = definition.manifest
assert description.enabled is True
assert manifest.workload.name == "similarity-search"
assert manifest.workload.version == "1.0.0"
assert manifest.determinism is DeterminismProfile.BYTE_EXACT
assert manifest.conformance_profiles == ("core-batch-v1",)
assert [stage.kind for stage in manifest.workflow.stages] == [
StageKind.MAP,
StageKind.REDUCE,
]
assert set(definition.runners) == {manifest.workflow.stages[0].entry_point}
assert set(definition.reducers) == {manifest.workflow.stages[1].entry_point}
assert negotiated is not None
assert negotiated.manifest == manifest
assert_manifest_round_trip(manifest)
def test_local_sdk_executor_matches_similarity_search_reference(tmp_path: Path) -> None:
dataset = tmp_path / "molecules.tsv"
_write_tiny_dataset(dataset)
registry, runtime, description, definition, _ = _registered_similarity_search()
artifact_store = LocalArtifactStore(tmp_path / "artifacts")
request = _request_for(dataset, artifact_store, definition)
result = LocalCoreBatchExecutor(
registry,
runtime,
artifact_store,
tmp_path / "sdk-work",
).execute(request, description.package_digest)
result_artifact = result.outputs["result"].items[0].artifact
reference_path = tmp_path / "reference.csv"
query = find_molecule_by_id(dataset, "QUERY")
reference = search_similar(dataset, query, top_k=3, progress_every=0)
write_search_results(reference_path, reference.matches)
assert (
artifact_store.materialize(result_artifact).read_bytes()
== reference_path.read_bytes()
)
assert result.task_key == "reduce/final"
assert result.metrics == {"matches_emitted": 3, "partial_count": 3}
def test_legacy_adapter_planning_is_deterministic_ordered_and_path_free(
tmp_path: Path,
) -> None:
dataset = tmp_path / "molecules.tsv"
_write_tiny_dataset(dataset)
registry, runtime, description, definition, _ = _registered_similarity_search()
artifact_store = LocalArtifactStore(tmp_path / "artifacts")
request = _request_for(dataset, artifact_store, definition)
input_artifact = request.inputs["input"].items[0].artifact
first = registry.plan(
request,
description.package_digest,
runtime,
LocalPlanningContext(
artifact_store,
artifact_store,
tmp_path / "first-plan",
allowed_artifacts=(input_artifact,),
),
)
second = registry.plan(
request,
description.package_digest,
runtime,
LocalPlanningContext(
artifact_store,
artifact_store,
tmp_path / "second-plan",
allowed_artifacts=(input_artifact,),
),
)
assert first.to_json() == second.to_json()
assert first.digest == second.digest
assert first.package_digest == definition.manifest.package.digest
assert first.manifest_digest == definition.manifest.digest
assert first.trust_mode is request.trust_mode
assert JobRequest.from_json(request.to_json()) == request
assert [task.task_key for task in first.tasks] == [
"map/00000000",
"map/00000001",
"map/00000002",
]
assert all(task.stage_id == "map" for task in first.tasks)
assert all(task.package_digest == first.package_digest for task in first.tasks)
assert all(task.manifest_digest == first.manifest_digest for task in first.tasks)
assert all(task.trust_mode is first.trust_mode for task in first.tasks)
assert all("query_id" not in task.parameters for task in first.tasks)
assert all(task.parameters["query_smiles"] == "CCO" for task in first.tasks)
shard_ids: list[list[str]] = []
for task in first.tasks:
artifact = task.inputs["input"].items[0].artifact
with artifact_store.materialize(artifact).open(
encoding="utf-8", newline=""
) as source:
shard_ids.append(
[row["chembl_id"] for row in csv.DictReader(source, delimiter="\t")]
)
assert set(artifact.to_dict()) == {
"artifact_id",
"sha256",
"schema",
"media_type",
"size_bytes",
"records",
"dimensions",
}
assert shard_ids == [
["QUERY", "ALCOHOL"],
["ALKANE", "BROKEN"],
["DUPLICATE", "AMINE"],
]
wire_payload = first.to_json()
assert str(tmp_path) not in wire_payload
assert "file://" not in wire_payload
assert "worker://" not in wire_payload
assert "workspace" not in wire_payload
def test_local_context_sink_cannot_seal_files_outside_the_attempt(
tmp_path: Path,
) -> None:
_, _, _, definition, _ = _registered_similarity_search()
artifact_store = LocalArtifactStore(tmp_path / "artifacts")
workspace = tmp_path / "attempt"
context = LocalPlanningContext(artifact_store, artifact_store, workspace)
outside = tmp_path / "private.txt"
outside.write_text("private", encoding="utf-8")
schema = definition.manifest.inputs["input"].schema
with pytest.raises(ValueError, match="inside its workspace"):
context.sink.seal(outside, declaration=schema)
workspace.mkdir(parents=True, exist_ok=True)
link = workspace / "result"
link.symlink_to(outside)
with pytest.raises(ValueError, match="real workspace directories"):
context.sink.seal(link, declaration=schema)
def test_local_store_rejects_malformed_content_before_publishing(
tmp_path: Path,
) -> None:
malformed = tmp_path / "malformed.json"
malformed.write_text('{"unfinished":', encoding="utf-8")
declaration = ArtifactSchema(
SchemaRef("json-result", 1),
"application/json",
"utf-8",
max_bytes=1_024,
validator=ComponentRef("json-document", 1),
)
store = LocalArtifactStore(tmp_path / "artifacts")
with pytest.raises(ValueError, match="not a valid bounded document"):
store.import_file(malformed, declaration=declaration)
assert (
tuple(
path for path in store.root.iterdir() if not path.name.startswith(".seal-")
)
== ()
)
def test_delimited_validator_rejects_headerless_data_and_enforces_record_limit(
tmp_path: Path,
) -> None:
declaration = ArtifactSchema(
SchemaRef("bounded-table", 1),
"text/csv",
"utf-8",
max_bytes=1_024,
validator=ComponentRef("delimited-table", 1),
validator_configuration={"columns": ["value"]},
max_records=1,
)
store = LocalArtifactStore(tmp_path / "artifacts")
headerless = tmp_path / "headerless.csv"
headerless.write_text("1\n2\n", encoding="utf-8")
oversized = tmp_path / "oversized.csv"
oversized.write_text("value\n1\n2\n", encoding="utf-8")
with pytest.raises(ValueError, match="header does not match"):
store.import_file(headerless, declaration=declaration)
with pytest.raises(ValueError, match="record limit"):
store.import_file(oversized, declaration=declaration)
def test_custom_artifact_inspector_is_bound_to_schema_and_validator_identity(
tmp_path: Path,
) -> None:
schema_ref = SchemaRef("matrix-result", 1)
validator = ComponentRef("matrix-inspector", 1)
declaration = ArtifactSchema(
schema_ref,
"application/x-matrix",
None,
max_bytes=1_024,
validator=validator,
validator_configuration={"layout": "row-major"},
max_records=1,
max_dimensions=(2, 2),
)
source = tmp_path / "matrix.bin"
source.write_bytes(b"matrix")
wrong = LocalArtifactStore(
tmp_path / "wrong-store",
inspectors={
schema_ref.canonical: (
ComponentRef("other-inspector", 1),
lambda _path, _configuration: (1, (2, 2)),
)
},
)
with pytest.raises(ValueError, match="no matching registered validator"):
wrong.import_file(source, declaration=declaration)
def inspect(_path: Path, configuration):
assert dict(configuration) == {"layout": "row-major"}
return 1, (2, 2)
store = LocalArtifactStore(
tmp_path / "store",
inspectors={schema_ref.canonical: (validator, inspect)},
)
artifact = store.import_file(source, declaration=declaration)
assert artifact.records == 1
assert artifact.dimensions == (2, 2)
@pytest.mark.parametrize(
("forgery", "message"),
(
("artifact", "artifacts sealed by its attempt"),
("provenance", "provenance does not match"),
),
)
def test_local_executor_rejects_handler_forged_outputs(
tmp_path: Path,
forgery: str,
message: str,
) -> None:
dataset = tmp_path / "molecules.tsv"
_write_tiny_dataset(dataset)
_, runtime, description, original, _ = _registered_similarity_search()
map_stage = next(
stage
for stage in original.manifest.workflow.stages
if stage.kind is StageKind.MAP
)
inner = original.runners[map_stage.entry_point]
class ForgingRunner:
def run(self, context):
result = inner.run(context)
if forgery == "provenance":
forged = replace(
result.provenance,
worker_runtime={"kind": "forged-runtime"},
)
return replace(result, provenance=forged)
original_ref = result.outputs["partial"].items[0].artifact
forged_ref = replace(original_ref, artifact_id=str(uuid4()))
return replace(
result,
outputs={"partial": ArtifactCollection.single(forged_ref)},
)
definition = WorkloadDefinition(
original.manifest,
original.planner,
{map_stage.entry_point: ForgingRunner()},
original.reducers,
original.verifiers,
)
registry = WorkloadRegistry()
registry.register(definition, enabled=True)
store = LocalArtifactStore(tmp_path / "artifacts")
request = _request_for(dataset, store, definition)
with pytest.raises(ValueError, match=message):
LocalCoreBatchExecutor(registry, runtime, store, tmp_path / "work").execute(
request,
description.package_digest,
)
def test_local_executor_rejects_profiles_that_claim_network_isolation(
tmp_path: Path,
) -> None:
dataset = tmp_path / "molecules.tsv"
_write_tiny_dataset(dataset)
_, runtime, description, original, _ = _registered_similarity_search()
stages = tuple(
replace(stage, execution=replace(stage.execution, network=NetworkPolicy.NONE))
for stage in original.manifest.workflow.stages
)
workflow = replace(original.manifest.workflow, stages=stages)
manifest = replace(original.manifest, workflow=workflow)
definition = WorkloadDefinition(
manifest,
original.planner,
original.runners,
original.reducers,
original.verifiers,
)
registry = WorkloadRegistry()
registry.register(definition, enabled=True)
store = LocalArtifactStore(tmp_path / "artifacts")
request = _request_for(dataset, store, definition)
with pytest.raises(CompatibilityError) as raised:
LocalCoreBatchExecutor(registry, runtime, store, tmp_path / "work").execute(
request,
description.package_digest,
)
assert raised.value.code == "feature-undeclared"
def test_local_executor_rejects_aliased_terminal_outputs_before_planning(
tmp_path: Path,
) -> None:
dataset = tmp_path / "molecules.tsv"
_write_tiny_dataset(dataset)
_, runtime, description, original, _ = _registered_similarity_search()
reducer = next(
stage
for stage in original.manifest.workflow.stages
if stage.kind is StageKind.REDUCE
)
internal_name = next(iter(reducer.outputs))
workflow = replace(
original.manifest.workflow,
outputs={"aliased": PortRef(internal_name, reducer.stage_id)},
)
manifest = replace(
original.manifest,
workflow=workflow,
outputs={"aliased": reducer.outputs[internal_name]},
)
definition = WorkloadDefinition(
manifest,
original.planner,
original.runners,
original.reducers,
original.verifiers,
)
registry = WorkloadRegistry()
registry.register(definition, enabled=True)
store = LocalArtifactStore(tmp_path / "artifacts")
request = _request_for(dataset, store, definition)
with pytest.raises(ValueError, match="identity-mapped reducer outputs"):
LocalCoreBatchExecutor(registry, runtime, store, tmp_path / "work").execute(
request,
description.package_digest,
)
def test_local_executor_rejects_non_trusted_trust_modes(tmp_path: Path) -> None:
dataset = tmp_path / "molecules.tsv"
_write_tiny_dataset(dataset)
_, runtime, _, original, _ = _registered_similarity_search()
stages = tuple(
replace(stage, trust_modes=("trusted", "verified"))
for stage in original.manifest.workflow.stages
)
manifest = replace(
original.manifest,
workflow=replace(original.manifest.workflow, stages=stages),
trust_modes=(TrustMode.TRUSTED, TrustMode.VERIFIED),
)
definition = WorkloadDefinition(
manifest,
original.planner,
original.runners,
original.reducers,
original.verifiers,
)
registry = WorkloadRegistry()
registry.register(definition, enabled=True)
store = LocalArtifactStore(tmp_path / "artifacts")
request = replace(
_request_for(dataset, store, definition),
trust_mode=TrustMode.VERIFIED,
)
runtime = replace(runtime, trust_modes=(TrustMode.TRUSTED, TrustMode.VERIFIED))
with pytest.raises(ValueError, match="supports only trusted workloads"):
LocalCoreBatchExecutor(registry, runtime, store, tmp_path / "work").execute(
request,
manifest.package.digest,
)
def _advanced_execution_manifest(
original: WorkloadDefinition,
case: str,
) -> tuple[tuple[str, ...], object]:
"""Declare one negotiable advanced profile the local executor cannot enforce."""
stages = original.manifest.workflow.stages
if case == "process-pool":
features = ("process-pools", "multi-process")
changed = tuple(
replace(
stage,
resources=replace(stage.resources, cpu_cores=2),
execution=replace(
stage.execution,
process_model=ProcessModel.PROCESS_POOL,
max_processes=2,
),
)
for stage in stages
)
elif case == "checkpoints":
features = ("checkpoints",)
changed = tuple(
replace(
stage,
execution=replace(
stage.execution,
checkpoint=CheckpointPolicy(
enabled=True,
schema=SchemaRef("task-state", 1),
compatibility_version=1,
),
),
)
for stage in stages
)
elif case == "retries":
features = ("retries",)
changed = tuple(
replace(stage, retry=RetryPolicy(max_attempts=2)) for stage in stages
)
elif case == "secrets":
features = ("secret-injection",)
changed = tuple(
replace(
stage,
execution=replace(stage.execution, secret_handles=("db-credential",)),
)
for stage in stages
)
elif case == "gang":
features = ("gang-leases",)
changed = tuple(
replace(
stage,
gang=GangSpec(replicas=2, per_replica_resources=stage.resources),
)
for stage in stages
)
elif case == "network-isolation":
features = ("network-isolation",)
changed = tuple(
replace(
stage,
execution=replace(stage.execution, network=NetworkPolicy.NONE),
)
for stage in stages
)
else:
assert case == "service-stage"
features = ("services",)
changed = (replace(stages[0], kind=StageKind.SERVICE),) + stages[1:]
manifest = replace(
original.manifest,
workflow=replace(original.manifest.workflow, stages=changed),
required_features=original.manifest.required_features
+ tuple(
FeatureRequirement(feature, VersionRange(">=1,<2")) for feature in features
),
)
return features, manifest
@pytest.mark.parametrize(
("case", "message"),
(
("process-pool", "one non-nested host thread"),
("checkpoints", "cannot enforce this stage profile"),
("retries", "does not implement retries"),
("secrets", "cannot enforce this stage profile"),
("gang", "cannot enforce this stage profile"),
("network-isolation", "cannot enforce a restricted network policy"),
("service-stage", "does not implement advanced stages"),
),
)
def test_local_executor_rejects_profiles_it_cannot_enforce(
tmp_path: Path,
case: str,
message: str,
) -> None:
dataset = tmp_path / "molecules.tsv"
_write_tiny_dataset(dataset)
_, runtime, _, original, _ = _registered_similarity_search()
features, manifest = _advanced_execution_manifest(original, case)
definition = WorkloadDefinition(
manifest,
original.planner,
original.runners,
original.reducers,
original.verifiers,
)
registry = WorkloadRegistry()
registry.register(definition, enabled=True)
store = LocalArtifactStore(tmp_path / "artifacts")
request = _request_for(dataset, store, definition)
runtime = replace(
runtime,
features={**runtime.features, **{feature: "1.0.0" for feature in features}},
)
with pytest.raises(ValueError, match=message):
LocalCoreBatchExecutor(registry, runtime, store, tmp_path / "work").execute(
request,
manifest.package.digest,
)
def test_local_executor_fails_when_the_declared_verifier_rejects(
tmp_path: Path,
) -> None:
dataset = tmp_path / "molecules.tsv"
_write_tiny_dataset(dataset)
_, runtime, _, original, _ = _registered_similarity_search()
class RejectingVerifier:
identity = ComponentRef("exact-artifact", 1)
def verify(self, context, candidates):
return VerificationDecision(
VerificationStatus.REJECTED,
self.identity,
"forced-rejection",
{},
)
definition = WorkloadDefinition(
original.manifest,
original.planner,
original.runners,
original.reducers,
{ComponentRef("exact-artifact", 1).canonical: RejectingVerifier()},
)
registry = WorkloadRegistry()
registry.register(definition, enabled=True)
store = LocalArtifactStore(tmp_path / "artifacts")
request = _request_for(dataset, store, definition)
with pytest.raises(ValueError, match="did not pass its declared verifier"):
LocalCoreBatchExecutor(registry, runtime, store, tmp_path / "work").execute(
request,
original.manifest.package.digest,
)
def test_local_executor_enforces_the_declared_output_byte_budget(
tmp_path: Path,
) -> None:
dataset = tmp_path / "molecules.tsv"
_write_tiny_dataset(dataset)
runtime = default_sdk_runtime()
workload = similarity_search_sdk_definition(shard_rows=2)
# The planner pins its own manifest into every task, so the budget cut must
# be applied to the workload's manifest for plan and definition to agree.
workload.manifest = replace(
workload.manifest,
workflow=replace(workload.manifest.workflow, max_output_bytes=256),
limits=replace(workload.manifest.limits, max_output_bytes=256),
)
definition = workload.definition()
registry = WorkloadRegistry()
registry.register(definition, enabled=True)
store = LocalArtifactStore(tmp_path / "artifacts")
request = _request_for(dataset, store, definition)
with pytest.raises(ValueError, match="bytes exceed their sink limit"):
LocalCoreBatchExecutor(registry, runtime, store, tmp_path / "work").execute(
request,
definition.manifest.package.digest,
)