109 lines
3.8 KiB
Python
109 lines
3.8 KiB
Python
# SimpleSyrup - workflow-focused ComfyUI extensions for image generation
|
|
# Copyright (C) 2026 Artificial Sweetener and contributors
|
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
|
|
"""Prove graph-order independence, branch isolation, and capability conflicts."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from itertools import permutations
|
|
from typing import Any
|
|
|
|
import pytest
|
|
|
|
from simple_syrup.domain.noise_inversion import NoiseInversionOptions
|
|
from simple_syrup.domain.sampler_options import (
|
|
AttentionCouplingOptions,
|
|
ContextualDiffusionOptions,
|
|
SamplerCapability,
|
|
SamplerOptions,
|
|
TilingOptions,
|
|
append_sampler_capability,
|
|
)
|
|
|
|
|
|
def test_all_distinct_capability_orders_produce_identical_configuration() -> None:
|
|
"""Let users arrange features freely without altering effective settings."""
|
|
capabilities = (
|
|
TilingOptions(),
|
|
ContextualDiffusionOptions(),
|
|
NoiseInversionOptions(),
|
|
AttentionCouplingOptions(),
|
|
)
|
|
expected = SamplerOptions(
|
|
tiling=capabilities[0],
|
|
contextual_diffusion=capabilities[1],
|
|
noise_inversion=capabilities[2],
|
|
attention_coupling=capabilities[3],
|
|
)
|
|
all_capabilities: tuple[SamplerCapability, ...] = capabilities
|
|
for order in permutations(all_capabilities):
|
|
options: SamplerOptions | None = None
|
|
for capability in order:
|
|
options = append_sampler_capability(options, capability)
|
|
assert options == expected
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"capability",
|
|
[
|
|
TilingOptions(),
|
|
ContextualDiffusionOptions(),
|
|
NoiseInversionOptions(),
|
|
AttentionCouplingOptions(),
|
|
],
|
|
)
|
|
def test_every_feature_can_start_a_chain_and_rejects_duplicates(
|
|
capability: SamplerCapability,
|
|
) -> None:
|
|
"""Avoid mandatory empty nodes and order-dependent last-wins overrides."""
|
|
options = append_sampler_capability(None, capability)
|
|
with pytest.raises(ValueError, match="Duplicate"):
|
|
append_sampler_capability(options, capability)
|
|
|
|
|
|
def test_branched_options_do_not_mutate_the_shared_upstream_configuration() -> None:
|
|
"""Allow independent downstream samplers to share a configuration prefix."""
|
|
upstream = SamplerOptions(tiling=TilingOptions())
|
|
contextual = append_sampler_capability(upstream, ContextualDiffusionOptions())
|
|
inverted = append_sampler_capability(upstream, NoiseInversionOptions())
|
|
assert upstream.contextual_diffusion is None
|
|
assert upstream.noise_inversion is None
|
|
assert contextual.noise_inversion is None
|
|
assert inverted.contextual_diffusion is None
|
|
assert contextual.tiling is inverted.tiling is upstream.tiling
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("constructor", "changes"),
|
|
[
|
|
(TilingOptions, {"width": 0}),
|
|
(TilingOptions, {"overlap": 128}),
|
|
(TilingOptions, {"diffusion_mode": "unknown"}),
|
|
(TilingOptions, {"batch_size": 0}),
|
|
(ContextualDiffusionOptions, {"global_weight": float("nan")}),
|
|
(ContextualDiffusionOptions, {"global_decay": 2}),
|
|
(ContextualDiffusionOptions, {"global_steps": -1}),
|
|
(AttentionCouplingOptions, {"region_mask_feather": -1}),
|
|
(
|
|
AttentionCouplingOptions,
|
|
{"regional_prompt_weight": 1.1},
|
|
),
|
|
],
|
|
)
|
|
def test_invalid_feature_settings_fail_at_construction(
|
|
constructor: Any, changes: dict[str, Any]
|
|
) -> None:
|
|
"""Validate capability settings before any model mutation or sampling."""
|
|
with pytest.raises(ValueError):
|
|
constructor(**changes)
|
|
|
|
|
|
def test_malformed_options_connection_is_rejected() -> None:
|
|
"""Fail closed for foreign objects on the custom connection boundary."""
|
|
malformed: Any = object()
|
|
with pytest.raises(TypeError, match="Options input"):
|
|
append_sampler_capability(malformed, TilingOptions())
|
|
with pytest.raises(TypeError, match="tiling"):
|
|
SamplerOptions(tiling=malformed)
|