mirror of
https://github.com/comfyanonymous/ComfyUI.git
synced 2026-07-18 20:38:15 +08:00
209 lines
6.5 KiB
Python
209 lines
6.5 KiB
Python
"""Unit tests for execution.validate_prompt.
|
|
|
|
Stub node classes are registered via the stub_nodes fixture in conftest.py;
|
|
no server, GPU or model files are required.
|
|
"""
|
|
import pytest
|
|
|
|
import execution
|
|
import nodes
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_missing_class_type():
|
|
"""A node without a class_type key fails fast with missing_node_type."""
|
|
prompt = {"1": {"inputs": {}, "_meta": {"title": "My Node"}}}
|
|
|
|
valid, error, good_outputs, node_errors = await execution.validate_prompt(
|
|
"test", prompt, None
|
|
)
|
|
|
|
assert valid is False
|
|
assert good_outputs == []
|
|
assert node_errors == {}
|
|
assert error["type"] == "missing_node_type"
|
|
assert "My Node" in error["message"]
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_unknown_class_type():
|
|
"""A class_type that isn't registered fails with missing_node_type."""
|
|
prompt = {"1": {"class_type": "VPDoesNotExist", "inputs": {}}}
|
|
|
|
valid, error, good_outputs, node_errors = await execution.validate_prompt(
|
|
"test", prompt, None
|
|
)
|
|
|
|
assert valid is False
|
|
assert good_outputs == []
|
|
assert node_errors == {}
|
|
assert error["type"] == "missing_node_type"
|
|
assert "VPDoesNotExist" in error["message"]
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_prompt_no_outputs(stub_nodes):
|
|
"""A prompt with no OUTPUT_NODE nodes fails with prompt_no_outputs."""
|
|
stub_nodes("VPNoOutput", input_types={"required": {}})
|
|
prompt = {"1": {"class_type": "VPNoOutput", "inputs": {}}}
|
|
|
|
valid, error, good_outputs, node_errors = await execution.validate_prompt(
|
|
"test", prompt, None
|
|
)
|
|
|
|
assert valid is False
|
|
assert error["type"] == "prompt_no_outputs"
|
|
assert good_outputs == []
|
|
assert node_errors == {}
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_partial_execution_list_filters_outputs(stub_nodes):
|
|
"""partial_execution_list restricts good_outputs to the listed node(s)."""
|
|
stub_nodes("VPOutputA", input_types={"required": {}}, output_node=True)
|
|
stub_nodes("VPOutputB", input_types={"required": {}}, output_node=True)
|
|
prompt = {
|
|
"1": {"class_type": "VPOutputA", "inputs": {}},
|
|
"2": {"class_type": "VPOutputB", "inputs": {}},
|
|
}
|
|
|
|
valid, error, good_outputs, node_errors = await execution.validate_prompt(
|
|
"test", prompt, ["1"]
|
|
)
|
|
|
|
assert valid is True
|
|
assert good_outputs == ["1"]
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_partial_execution_list_none_takes_all(stub_nodes):
|
|
"""partial_execution_list=None treats every OUTPUT_NODE as an output."""
|
|
stub_nodes("VPOutputA", input_types={"required": {}}, output_node=True)
|
|
stub_nodes("VPOutputB", input_types={"required": {}}, output_node=True)
|
|
prompt = {
|
|
"1": {"class_type": "VPOutputA", "inputs": {}},
|
|
"2": {"class_type": "VPOutputB", "inputs": {}},
|
|
}
|
|
|
|
valid, error, good_outputs, node_errors = await execution.validate_prompt(
|
|
"test", prompt, None
|
|
)
|
|
|
|
assert valid is True
|
|
assert sorted(good_outputs) == ["1", "2"]
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_all_outputs_invalid(stub_nodes):
|
|
"""The sole output fails validation, so the whole prompt is invalid."""
|
|
stub_nodes(
|
|
"VPRequiresInt",
|
|
input_types={"required": {"amount": ("INT", {})}},
|
|
output_node=True,
|
|
)
|
|
prompt = {"1": {"class_type": "VPRequiresInt", "inputs": {}}}
|
|
|
|
valid, error, good_outputs, node_errors = await execution.validate_prompt(
|
|
"test", prompt, None
|
|
)
|
|
|
|
assert valid is False
|
|
assert error["type"] == "prompt_outputs_failed_validation"
|
|
assert node_errors["1"]["class_type"] == "VPRequiresInt"
|
|
assert node_errors["1"]["dependent_outputs"] == ["1"]
|
|
assert node_errors["1"]["errors"][0]["type"] == "required_input_missing"
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_mixed_valid_and_invalid_outputs(stub_nodes):
|
|
"""One output validates fine while a sibling output fails."""
|
|
stub_nodes("VPOutputValid", input_types={"required": {}}, output_node=True)
|
|
stub_nodes(
|
|
"VPOutputInvalid",
|
|
input_types={"required": {"amount": ("INT", {})}},
|
|
output_node=True,
|
|
)
|
|
prompt = {
|
|
"1": {"class_type": "VPOutputValid", "inputs": {}},
|
|
"2": {"class_type": "VPOutputInvalid", "inputs": {}},
|
|
}
|
|
|
|
valid, error, good_outputs, node_errors = await execution.validate_prompt(
|
|
"test", prompt, None
|
|
)
|
|
|
|
assert valid is True
|
|
assert good_outputs == ["1"]
|
|
assert "2" in node_errors
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_exception_during_validation(monkeypatch):
|
|
"""An exception raised inside INPUT_TYPES is caught and reported."""
|
|
|
|
class _ExplodingOutput:
|
|
"""Stub whose INPUT_TYPES raises during validation recursion."""
|
|
|
|
RETURN_TYPES = ()
|
|
FUNCTION = "run"
|
|
CATEGORY = "_test"
|
|
OUTPUT_NODE = True
|
|
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
raise RuntimeError("boom")
|
|
|
|
monkeypatch.setitem(nodes.NODE_CLASS_MAPPINGS, "VPExploding", _ExplodingOutput)
|
|
prompt = {"1": {"class_type": "VPExploding", "inputs": {}}}
|
|
|
|
valid, error, good_outputs, node_errors = await execution.validate_prompt(
|
|
"test", prompt, None
|
|
)
|
|
|
|
assert valid is False
|
|
assert error["type"] == "prompt_outputs_failed_validation"
|
|
assert node_errors["1"]["errors"][0]["type"] == "exception_during_validation"
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_downstream_nodes_without_reasons_omitted(stub_nodes):
|
|
"""A downstream failure with no reasons of its own is filtered out."""
|
|
stub_nodes(
|
|
"VPUpstreamFails",
|
|
input_types={"required": {"amount": ("INT", {})}},
|
|
return_types=("INT",),
|
|
)
|
|
stub_nodes(
|
|
"VPDownstreamOutput",
|
|
input_types={"required": {"amount": ("INT", {})}},
|
|
output_node=True,
|
|
)
|
|
prompt = {
|
|
"1": {"class_type": "VPUpstreamFails", "inputs": {}},
|
|
"2": {"class_type": "VPDownstreamOutput", "inputs": {"amount": ["1", 0]}},
|
|
}
|
|
|
|
valid, error, good_outputs, node_errors = await execution.validate_prompt(
|
|
"test", prompt, None
|
|
)
|
|
|
|
assert valid is False
|
|
assert "1" in node_errors
|
|
assert "2" not in node_errors
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_fully_valid_prompt(stub_nodes):
|
|
"""A single valid output produces a clean, error-free result."""
|
|
stub_nodes("VPValidOutput", input_types={"required": {}}, output_node=True)
|
|
prompt = {"1": {"class_type": "VPValidOutput", "inputs": {}}}
|
|
|
|
valid, error, good_outputs, node_errors = await execution.validate_prompt(
|
|
"test", prompt, None
|
|
)
|
|
|
|
assert valid is True
|
|
assert error is None
|
|
assert good_outputs == ["1"]
|
|
assert node_errors == {}
|