Files
Artificial-Sweetener-Simple…/tests/test_text_encoder_lora_workflow.py
T

197 lines
6.7 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 exact public graph construction for P9.4 LoRA cases."""
from __future__ import annotations
import hashlib
from pathlib import Path
from tools.anima_attention_coupling_integration.matrix import (
PUBLIC_NODE_ID as FULL_NODE_ID,
)
from tools.anima_contextual_attention_coupling_integration.matrix import (
PUBLIC_NODE_ID as CONTEXTUAL_NODE_ID,
)
from tools.anima_tiled_attention_coupling_integration.matrix import (
PUBLIC_NODE_ID as TILED_NODE_ID,
)
from tools.comfy_api import JsonObject
from tools.text_encoder_lora_integration.fixture import (
TextEncoderLoraFixtureIdentity,
)
from tools.text_encoder_lora_integration.matrix import cases
from tools.text_encoder_lora_integration.workflow import (
TextEncoderLoraWorkflowBuilder,
)
def test_workflows_use_explicit_global_and_regional_clip_strengths(
tmp_path: Path,
) -> None:
"""Keep encoder adapter model strength zero on both supported encoding paths."""
definitions = {case.case_id: case for case in cases()}
fixture = _fixture(tmp_path)
builder = TextEncoderLoraWorkflowBuilder(fixture)
global_workflow = builder.build(
definitions["full-global-encoder_adapter-text"],
run_id="global",
mask_names=("left.png", "right.png"),
)
regional_workflow = builder.build(
definitions["full-regional-encoder_adapter-text"],
run_id="regional",
mask_names=("left.png", "right.png"),
)
mixed_workflow = builder.build(
definitions["full-regional-encoder_adapter-text-primary_adapter-model"],
run_id="mixed",
mask_names=("left.png", "right.png"),
)
lora_name = fixture.lora_name
global_loader = _single_node(global_workflow.prompt, "LoraLoader")
assert global_loader["inputs"] == {
"model": _node_reference(
global_workflow.prompt, "SimpleSyrup.SimpleLoadAnima", 0
),
"clip": _node_reference(
global_workflow.prompt, "SimpleSyrup.SimpleLoadAnima", 1
),
"lora_name": lora_name,
"strength_model": 0.0,
"strength_clip": 0.75,
}
regional_prompt = _positive_prompt(regional_workflow.prompt)
assert f"<lora:{lora_name}:0:0.75>" in regional_prompt
assert not _nodes(regional_workflow.prompt, "LoraLoader")
mixed_prompt = _positive_prompt(mixed_workflow.prompt)
assert f"<lora:{lora_name}:0:0.75>" in mixed_prompt
assert "<lora:Anima\\style\\adapter-a.safetensors:0.8:0>" in mixed_prompt
def test_workflows_bind_each_case_to_its_public_spatial_sampler(tmp_path: Path) -> None:
"""Use full 1024 generation and accepted 1024-to-1536 refinement graphs."""
builder = TextEncoderLoraWorkflowBuilder(_fixture(tmp_path))
expected_nodes = {
"full-baseline": FULL_NODE_ID,
"tiled-regional-primary_adapter-model": TILED_NODE_ID,
"contextual-regional-primary_adapter-model": CONTEXTUAL_NODE_ID,
}
definitions = {case.case_id: case for case in cases()}
for case_id, expected_node in expected_nodes.items():
workflow = builder.build(
definitions[case_id],
run_id=case_id,
mask_names=("left.png", "right.png"),
)
sampler = _single_node(workflow.prompt, expected_node)
assert expected_node in workflow.required_node_ids
if expected_node == FULL_NODE_ID:
assert not _nodes(workflow.prompt, "ImageScale")
continue
assert _nodes(workflow.prompt, "ImageScale")
sampler_inputs = sampler["inputs"]
assert isinstance(sampler_inputs, dict)
assert sampler_inputs["diffusion_mode"] == "multidiffusion"
def test_workflows_snapshot_the_exact_public_conditioning_batches(
tmp_path: Path,
) -> None:
"""Bind one output-only snapshot to the unique Schedule-and-Encode outputs."""
case = next(
item for item in cases() if item.case_id == "full-regional-encoder_adapter-text"
)
workflow = TextEncoderLoraWorkflowBuilder(_fixture(tmp_path)).build(
case,
run_id="snapshot-run",
mask_names=("left.png", "right.png"),
)
encoder_id = next(
node_id
for node_id, node in workflow.prompt.items()
if node.get("class_type")
== "SimpleSyrup.ScheduleAndEncodePromptsWithPromptControl"
)
snapshot = _single_node(
workflow.prompt,
"SimpleSyrupBenchmark.SnapshotConditioningBatch",
)
assert workflow.conditioning_batch_snapshot_node_id is not None
assert workflow.conditioning_batch_snapshot_run_id == (
"snapshot-run:full-regional-encoder_adapter-text:conditioning-batch"
)
assert snapshot["inputs"] == {
"positive": [encoder_id, 1],
"negative": [encoder_id, 2],
"run_id": workflow.conditioning_batch_snapshot_run_id,
}
assert "SimpleSyrupBenchmark.SnapshotConditioningBatch" in (
workflow.required_node_ids
)
def _nodes(prompt: dict[str, JsonObject], class_type: str) -> list[JsonObject]:
"""Return graph nodes with one exact public class identity."""
return [node for node in prompt.values() if node.get("class_type") == class_type]
def _single_node(prompt: dict[str, JsonObject], class_type: str) -> JsonObject:
"""Return one uniquely matching graph node."""
matches = _nodes(prompt, class_type)
assert len(matches) == 1
return matches[0]
def _node_reference(
prompt: dict[str, JsonObject],
class_type: str,
output_index: int,
) -> list[object]:
"""Return one graph node's output reference."""
node_id = next(
node_id
for node_id, node in prompt.items()
if node.get("class_type") == class_type
)
return [node_id, output_index]
def _positive_prompt(prompt: dict[str, JsonObject]) -> str:
"""Return the exact lazy regional encoder prompt."""
node = _single_node(
prompt,
"SimpleSyrup.ScheduleAndEncodePromptsWithPromptControl",
)
inputs = node["inputs"]
assert isinstance(inputs, dict)
value = inputs["positive_prompt"]
assert isinstance(value, str)
return value
def _fixture(root: Path) -> TextEncoderLoraFixtureIdentity:
"""Build an anonymous external adapter identity for graph tests."""
return TextEncoderLoraFixtureIdentity(
lora_name=r"evidence\text-adapter.safetensors",
path=(root / "text-adapter.safetensors").resolve(),
sha256=hashlib.sha256(b"adapter").hexdigest(),
size_bytes=7,
tensor_count=1,
source_sha256=hashlib.sha256(b"source").hexdigest(),
transformation="test fixture",
)