196 lines
6.2 KiB
Python
196 lines
6.2 KiB
Python
"""Contracts for schedule ownership, domains, and transform ordering."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from dataclasses import FrozenInstanceError
|
|
from typing import cast
|
|
|
|
import pytest
|
|
from comfyui_sigmax.core import (
|
|
ScheduleContractError,
|
|
ScheduleOwnership,
|
|
SigmaDomain,
|
|
TransformContract,
|
|
TransformStage,
|
|
require_single_ownership,
|
|
validate_transform_chain,
|
|
)
|
|
|
|
|
|
def _transform(
|
|
name: str,
|
|
stage: TransformStage,
|
|
input_domain: SigmaDomain = SigmaDomain.UNIT_FLOW,
|
|
output_domain: SigmaDomain = SigmaDomain.UNIT_FLOW,
|
|
) -> TransformContract:
|
|
return TransformContract(
|
|
name=name,
|
|
stage=stage,
|
|
input_domain=input_domain,
|
|
output_domain=output_domain,
|
|
)
|
|
|
|
|
|
def test_contract_wire_values_are_explicit_and_complete() -> None:
|
|
assert {mode.value for mode in ScheduleOwnership} == {
|
|
"MODEL_NATIVE",
|
|
"EXTERNAL_SIGMAS",
|
|
"MODEL_PATCH",
|
|
}
|
|
assert {domain.value for domain in SigmaDomain} == {
|
|
"UNIT_FLOW",
|
|
"MODEL_NATIVE",
|
|
"CONTINUOUS_EDM",
|
|
"DISCRETE_TRAINING_INDEX",
|
|
}
|
|
assert [stage.value for stage in TransformStage] == [
|
|
"PRIMARY_TIME_SHIFT",
|
|
"OPTIONAL_SPACING",
|
|
"TERMINAL",
|
|
"SLICE",
|
|
]
|
|
|
|
|
|
@pytest.mark.parametrize("mode", list(ScheduleOwnership))
|
|
def test_each_single_ownership_mode_resolves(mode: ScheduleOwnership) -> None:
|
|
assert require_single_ownership(mode) is mode
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"modes",
|
|
[
|
|
(),
|
|
(ScheduleOwnership.MODEL_NATIVE, ScheduleOwnership.EXTERNAL_SIGMAS),
|
|
(ScheduleOwnership.MODEL_PATCH, ScheduleOwnership.MODEL_PATCH),
|
|
],
|
|
)
|
|
def test_ownership_requires_exactly_one_mode(modes: tuple[ScheduleOwnership, ...]) -> None:
|
|
with pytest.raises(ScheduleContractError, match="exactly one"):
|
|
require_single_ownership(*modes)
|
|
|
|
|
|
def test_ownership_rejects_untyped_wire_value() -> None:
|
|
with pytest.raises(ScheduleContractError, match="unsupported"):
|
|
require_single_ownership(cast(ScheduleOwnership, "MODEL_NATIVE"))
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"ownership",
|
|
[ScheduleOwnership.MODEL_NATIVE, ScheduleOwnership.MODEL_PATCH],
|
|
)
|
|
def test_native_or_patched_ownership_rejects_external_shift_before_execution(
|
|
ownership: ScheduleOwnership,
|
|
) -> None:
|
|
execution_reached = False
|
|
|
|
with pytest.raises(ScheduleContractError, match="double shifting"):
|
|
validate_transform_chain(
|
|
ownership,
|
|
SigmaDomain.MODEL_NATIVE,
|
|
[_transform("krea exponential mu", TransformStage.PRIMARY_TIME_SHIFT)],
|
|
)
|
|
execution_reached = True
|
|
|
|
assert execution_reached is False
|
|
|
|
|
|
def test_native_and_patch_ownership_accept_only_opaque_native_domain() -> None:
|
|
for ownership in (ScheduleOwnership.MODEL_NATIVE, ScheduleOwnership.MODEL_PATCH):
|
|
assert (
|
|
validate_transform_chain(ownership, SigmaDomain.MODEL_NATIVE, [])
|
|
is SigmaDomain.MODEL_NATIVE
|
|
)
|
|
with pytest.raises(ScheduleContractError, match="MODEL_NATIVE sigma domain"):
|
|
validate_transform_chain(ownership, SigmaDomain.UNIT_FLOW, [])
|
|
|
|
|
|
def test_external_ownership_rejects_opaque_model_native_domain() -> None:
|
|
with pytest.raises(ScheduleContractError, match="opaque MODEL_NATIVE"):
|
|
validate_transform_chain(
|
|
ScheduleOwnership.EXTERNAL_SIGMAS,
|
|
SigmaDomain.MODEL_NATIVE,
|
|
[],
|
|
)
|
|
|
|
|
|
def test_transform_validation_rejects_untyped_ownership_value() -> None:
|
|
with pytest.raises(ScheduleContractError, match="unsupported"):
|
|
validate_transform_chain(
|
|
cast(ScheduleOwnership, "EXTERNAL_SIGMAS"),
|
|
SigmaDomain.UNIT_FLOW,
|
|
[],
|
|
)
|
|
|
|
|
|
def test_valid_external_chain_returns_explicit_final_domain() -> None:
|
|
final_domain = validate_transform_chain(
|
|
ScheduleOwnership.EXTERNAL_SIGMAS,
|
|
SigmaDomain.DISCRETE_TRAINING_INDEX,
|
|
[
|
|
_transform(
|
|
"normalize flow time",
|
|
TransformStage.PRIMARY_TIME_SHIFT,
|
|
SigmaDomain.DISCRETE_TRAINING_INDEX,
|
|
SigmaDomain.UNIT_FLOW,
|
|
),
|
|
_transform("profile spacing", TransformStage.OPTIONAL_SPACING),
|
|
_transform("append zero", TransformStage.TERMINAL),
|
|
_transform("partial denoise", TransformStage.SLICE),
|
|
],
|
|
)
|
|
|
|
assert final_domain is SigmaDomain.UNIT_FLOW
|
|
|
|
|
|
def test_external_chain_rejects_domain_discontinuity() -> None:
|
|
with pytest.raises(ScheduleContractError, match="expects CONTINUOUS_EDM"):
|
|
validate_transform_chain(
|
|
ScheduleOwnership.EXTERNAL_SIGMAS,
|
|
SigmaDomain.UNIT_FLOW,
|
|
[
|
|
_transform(
|
|
"wrong-domain shift",
|
|
TransformStage.PRIMARY_TIME_SHIFT,
|
|
SigmaDomain.CONTINUOUS_EDM,
|
|
SigmaDomain.UNIT_FLOW,
|
|
)
|
|
],
|
|
)
|
|
|
|
|
|
def test_external_chain_rejects_stage_regression() -> None:
|
|
with pytest.raises(ScheduleContractError, match="stage order"):
|
|
validate_transform_chain(
|
|
ScheduleOwnership.EXTERNAL_SIGMAS,
|
|
SigmaDomain.UNIT_FLOW,
|
|
[
|
|
_transform("append zero", TransformStage.TERMINAL),
|
|
_transform("late shift", TransformStage.PRIMARY_TIME_SHIFT),
|
|
],
|
|
)
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"stage",
|
|
[TransformStage.PRIMARY_TIME_SHIFT, TransformStage.OPTIONAL_SPACING],
|
|
)
|
|
def test_external_chain_rejects_duplicate_singleton_stage(stage: TransformStage) -> None:
|
|
with pytest.raises(ScheduleContractError, match="at most one"):
|
|
validate_transform_chain(
|
|
ScheduleOwnership.EXTERNAL_SIGMAS,
|
|
SigmaDomain.UNIT_FLOW,
|
|
[_transform("first", stage), _transform("second", stage)],
|
|
)
|
|
|
|
|
|
def test_transform_contract_is_immutable() -> None:
|
|
contract = _transform("fixed shift", TransformStage.PRIMARY_TIME_SHIFT)
|
|
|
|
with pytest.raises(FrozenInstanceError):
|
|
contract.name = "changed" # type: ignore[misc]
|
|
|
|
|
|
def test_transform_contract_rejects_empty_name() -> None:
|
|
with pytest.raises(ScheduleContractError, match="name must not be empty"):
|
|
_transform(" ", TransformStage.TERMINAL)
|