Files
wildminder-ComfyUI-DyPE/tests/test_patch_utils_decision.py
T

83 lines
3.3 KiB
Python

"""Schedule-patch decision hygiene (v2.16.0, plan S5).
_should_patch_schedule must resolve the sampling through the patcher
(patch -> backup -> live) so a leaked *ModelSamplingFlux from a previous run
cannot flip the decision. Characterization: clean-state outcomes are pinned;
history-independence: leak/backup states give the same answer as clean.
"""
from __future__ import annotations
import sys
import types
from pathlib import Path
import pytest
sys.path.insert(0, str(Path(__file__).parent.parent))
from comfy import model_sampling as comfy_ms # noqa: E402
from src.patch_utils import _should_patch_schedule # noqa: E402
class _Patcher:
def __init__(self, live, patches=None, backup=None):
self.model = types.SimpleNamespace(model_sampling=live)
self.object_patches = dict(patches or {})
self.object_patches_backup = dict(backup or {})
def get_model_object(self, name):
if name in self.object_patches:
return self.object_patches[name]
if name in self.object_patches_backup:
return self.object_patches_backup[name]
return getattr(self.model, name)
def _ms(name, *bases):
return type(name, bases or (object,), {})()
FluxLike = comfy_ms.ModelSamplingFlux
ContFlow = type("ModelSamplingContinuousFlow", (), {})
@pytest.mark.unit
class TestShouldPatchSchedule:
@pytest.mark.parametrize("flags", [(False, False), (True, False),
(False, True), (True, True)])
def test_characterization_flux_sampling(self, flags):
# Clean FLUX-style sampling: decision follows the isinstance only.
patcher = _Patcher(_ms("MS", FluxLike))
assert _should_patch_schedule(patcher, *flags) is True
def test_characterization_continuous_flow_not_patched(self):
patcher = _Patcher(_ms("MS", ContFlow))
assert _should_patch_schedule(patcher, False, False) is False
def test_characterization_qwen_and_zimage_force_patch(self):
patcher = _Patcher(_ms("MS", ContFlow))
assert _should_patch_schedule(patcher, True, False) is True
assert _should_patch_schedule(patcher, False, True) is True
def test_leak_does_not_flip_continuous_flow_decision(self):
# A leaked Flux-style sampling on the live attr (empty backup, no
# patch) is the documented KSampler-consistent residual: it IS what
# resolution returns here, so the decision follows it — pinned so a
# future change of this trade-off is explicit.
patcher = _Patcher(_ms("Leaked", FluxLike))
assert _should_patch_schedule(patcher, False, False) is True
def test_own_patch_decides_over_leaked_live(self):
# Graph carries a Flux-style patch over a continuous-flow live attr.
patcher = _Patcher(_ms("Live", ContFlow),
patches={"model_sampling": _ms("P", FluxLike)})
assert _should_patch_schedule(patcher, False, False) is True
def test_backup_original_decides_over_leaked_live(self):
# Unpatched patcher whose lineage backup holds the ORIGINAL
# continuous-flow sampler: the leak must not flip the decision.
patcher = _Patcher(_ms("Leaked", FluxLike),
backup={"model_sampling": _ms("Orig", ContFlow)})
assert _should_patch_schedule(patcher, False, False) is False