Files
olivv-cs-ComfyUI-FunPack/modules/sampling/tests/test_modifier_reaches_sampling.py
T
digital-garbageandClaude Opus 5 8521bdf899 sampling: modifiers attach to the model, so no sampler can own them
Two nodes. One carries the values a person picked, as a single validated
payload rather than a socket per setting -- a socket per setting means this
node's shape changes whenever any module gains an option, and every saved
workflow refers to sockets that moved.

The other reads what announced itself, filters it by what the model actually
is, orders it by declared relations, and installs each one through ComfyUI's
own patcher_extension wrappers. The consequence is checked against comfy's
real prepare_model_patcher and WrapperExecutor rather than against our own
dict: a model that has been through this node runs every per-step modifier
inside ComfyUI's own SamplerCustomAdvanced. The sampler is not consulted, so
it cannot own them.

Installing happens on a clone, and clone() copies the wrapper lists per key,
so the model everyone else holds is untouched. Fifty runs leave zero
wrappers behind. The accumulation that once degraded output across a hundred
generations and survived every reset short of a restart is structurally
impossible here rather than something to remember to undo.

The payload is validated against the same declaration that renders the
panel: an undeclared key, a wrong type, a value outside its own bounds and a
bool passed as a number are all refused, and all problems are reported at
once. v4 validated none of it, which is why it needed a hand-mirrored table
of every default and a list of keys that no longer meant anything.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-29 12:35:28 +03:00

101 lines
3.5 KiB
Python

"""A modifier installed on the model is reached by ComfyUI's own sampling path.
This is the architectural claim made checkable: modifiers attach to the MODEL, so
no sampler has to cooperate and therefore no sampler can own them. If this test
holds, wiring the modified model into ComfyUI's own SamplerCustomAdvanced runs
every per-step modifier.
It uses comfy's real functions -- prepare_model_patcher, get_all_wrappers,
WrapperExecutor -- rather than asserting on our own dict. Checking that we wrote
a key we chose would prove nothing about whether anything reads it.
"""
import pytest
@pytest.fixture(autouse=True)
def _needs_comfy(comfyui):
"""Imports comfy."""
@pytest.fixture
def registry(monkeypatch):
from core import registry as registry_mod
from core.contract import ModuleSpec
class Fake:
specs = {}
failed = []
fake = Fake()
fake.specs = {}
monkeypatch.setattr(registry_mod, "current", lambda rescan=False: fake)
return fake, ModuleSpec
def test_the_wrapper_a_modifier_installs_is_executed_by_comfys_sampler_path(registry):
import torch
import comfy.patcher_extension as ext
import comfy.sampler_helpers
from comfy.model_patcher import ModelPatcher
from comfy.patcher_extension import WrappersMP
from modules.sampling.modifiers.nodes import FunPackLoadModifiers
fake, ModuleSpec = registry
ran = []
def install(target, values, key):
def wrapper(executor, *args, **kwargs):
ran.append(key)
return executor(*args, **kwargs)
target.add_wrapper_with_key(WrappersMP.SAMPLER_SAMPLE, key, wrapper)
return "on"
fake.specs["m"] = ModuleSpec(id="m", title="m", mount="",
provides={"modifier": install})
class Stub(torch.nn.Module):
pass
patcher = ModelPatcher(Stub(), load_device=torch.device("cpu"),
offload_device=torch.device("cpu"))
patched, _status = FunPackLoadModifiers.execute(patcher).result
# Exactly what CFGGuider.inner_sample does: the patcher's wrappers are merged
# into model_options, then the sampler's own sample() is wrapped in them.
model_options = {"transformer_options": {}}
comfy.sampler_helpers.prepare_model_patcher(patched, {}, model_options)
def sample(*args, **kwargs):
return "sampled"
executor = ext.WrapperExecutor.new_class_executor(
sample, None,
ext.get_all_wrappers(WrappersMP.SAMPLER_SAMPLE, model_options, is_model_options=True))
assert executor.execute() == "sampled"
assert ran == ["funpack.m"], (
"the modifier was installed somewhere ComfyUI's sampling path does not read")
def test_an_unmodified_model_carries_none_of_our_wrappers(registry):
"""The control. Without it the test above could pass on a machine where
something else installs a wrapper under any key at all."""
import torch
import comfy.sampler_helpers
from comfy.model_patcher import ModelPatcher
from comfy.patcher_extension import WrappersMP
class Stub(torch.nn.Module):
pass
patcher = ModelPatcher(Stub(), load_device=torch.device("cpu"),
offload_device=torch.device("cpu"))
model_options = {"transformer_options": {}}
comfy.sampler_helpers.prepare_model_patcher(patcher, {}, model_options)
installed = model_options["transformer_options"].get("wrappers", {}).get(
WrappersMP.SAMPLER_SAMPLE, {})
assert not [k for k in installed if str(k).startswith("funpack.")]