Files

195 lines
5.7 KiB
Python

from __future__ import annotations
from collections.abc import Callable
from dataclasses import FrozenInstanceError, replace
import pytest
from comfyui_sigmax.core import ScheduleContractError
from comfyui_sigmax.host_mutation import (
CoInstallationEvaluation,
HostMutationFinding,
HostMutationSnapshot,
MutationVerdict,
NodeRegistryIdentity,
SchedulerRegistryIdentity,
evaluate_host_mutation,
)
HASH_A = "sha256:" + "a" * 64
HASH_B = "sha256:" + "b" * 64
HASH_C = "sha256:" + "c" * 64
HASH_D = "sha256:" + "d" * 64
def _snapshot() -> HostMutationSnapshot:
return HostMutationSnapshot(
nodes=(
NodeRegistryIdentity(
node_id="Sigmax.Krea2SigmaScheduler",
provider="comfyui_sigmax",
definition_fingerprint=HASH_A,
),
),
schedulers=(
SchedulerRegistryIdentity(
name="simple",
provider="comfyui",
handler_fingerprint=HASH_B,
),
),
torch_call_fingerprint=HASH_C,
model_patch_fingerprint=HASH_D,
schedule_ownership="external_sigmas",
construction_shift_count=1,
model_native_shifted=False,
)
def _evaluate(after: HostMutationSnapshot) -> CoInstallationEvaluation:
return evaluate_host_mutation(
before=_snapshot(),
after=after,
pack_id="synthetic.fixture",
)
def test_unrelated_node_and_scheduler_additions_are_compatible() -> None:
before = _snapshot()
after = replace(
before,
nodes=(
NodeRegistryIdentity(
node_id="Other.ExampleNode",
provider="synthetic.other",
definition_fingerprint=HASH_B,
),
*before.nodes,
),
schedulers=(
SchedulerRegistryIdentity(
name="other_scheduler",
provider="synthetic.other",
handler_fingerprint=HASH_A,
),
*before.schedulers,
),
)
report = _evaluate(after)
assert report.verdict is MutationVerdict.ALLOW
assert report.findings == ()
assert report.report_fingerprint.startswith("sha256:")
@pytest.mark.parametrize(
("after", "finding"),
[
(
replace(
_snapshot(),
nodes=(
NodeRegistryIdentity(
node_id="Sigmax.Krea2SigmaScheduler",
provider="synthetic.other",
definition_fingerprint=HASH_B,
),
),
),
HostMutationFinding.NODE_REGISTRY_COLLISION,
),
(
replace(
_snapshot(),
nodes=(
NodeRegistryIdentity(
node_id="Sigmax.ForeignNode",
provider="synthetic.other",
definition_fingerprint=HASH_B,
),
*_snapshot().nodes,
),
),
HostMutationFinding.SIGMAX_NAMESPACE_HIJACK,
),
(
replace(
_snapshot(),
schedulers=(
SchedulerRegistryIdentity(
name="simple",
provider="synthetic.other",
handler_fingerprint=HASH_A,
),
),
),
HostMutationFinding.SCHEDULER_REGISTRY_OVERWRITE,
),
(
replace(_snapshot(), torch_call_fingerprint=HASH_A),
HostMutationFinding.TORCH_CALL_PATH_CHANGED,
),
(
replace(_snapshot(), model_patch_fingerprint=HASH_A),
HostMutationFinding.MODEL_PATCH_STATE_CHANGED,
),
(
replace(_snapshot(), construction_shift_count=2),
HostMutationFinding.CONSTRUCTION_SHIFT_REPEATED,
),
(
replace(
_snapshot(),
schedule_ownership="model_native",
model_native_shifted=True,
),
HostMutationFinding.MODEL_NATIVE_EXTERNAL_DOUBLE_SHIFT,
),
],
)
def test_protected_host_mutations_are_detected_and_rejected(
after: HostMutationSnapshot,
finding: HostMutationFinding,
) -> None:
report = _evaluate(after)
assert report.verdict is MutationVerdict.REJECT
assert finding in report.findings
def test_evaluation_is_deterministic_and_snapshot_is_immutable() -> None:
before = _snapshot()
first = evaluate_host_mutation(before=before, after=before, pack_id="synthetic.reload")
repeat = evaluate_host_mutation(before=before, after=before, pack_id="synthetic.reload")
assert first == repeat
assert first.report_fingerprint == repeat.report_fingerprint
with pytest.raises(FrozenInstanceError):
before.construction_shift_count = 2 # type: ignore[misc]
@pytest.mark.parametrize(
"mutation",
[
lambda snapshot: replace(
snapshot,
nodes=(
*snapshot.nodes,
NodeRegistryIdentity(
node_id="Other.Unsorted",
provider="synthetic.other",
definition_fingerprint=HASH_A,
),
),
),
lambda snapshot: replace(snapshot, torch_call_fingerprint="not-a-fingerprint"),
lambda snapshot: replace(snapshot, construction_shift_count=-1),
lambda snapshot: replace(snapshot, schedule_ownership="implicit"),
],
)
def test_snapshot_rejects_noncanonical_or_invalid_state(
mutation: Callable[[HostMutationSnapshot], HostMutationSnapshot],
) -> None:
with pytest.raises(ScheduleContractError):
mutation(_snapshot())