190 lines
6.1 KiB
Python
190 lines
6.1 KiB
Python
import importlib.util
|
|
import logging
|
|
import pathlib
|
|
import sys
|
|
import types
|
|
import unittest
|
|
from types import SimpleNamespace
|
|
|
|
|
|
REPO_ROOT = pathlib.Path(__file__).resolve().parents[1]
|
|
|
|
|
|
def _load_workflow_node_module():
|
|
package_name = "swf_isolated_test"
|
|
package = types.ModuleType(package_name)
|
|
package.__path__ = [str(REPO_ROOT)]
|
|
sys.modules[package_name] = package
|
|
|
|
latest = types.ModuleType("comfy_api.latest")
|
|
latest.io = SimpleNamespace(
|
|
ComfyNode=object,
|
|
NodeOutput=lambda *args, **kwargs: (args, kwargs),
|
|
Hidden=SimpleNamespace(unique_id="UNIQUE_ID", dynprompt="DYNPROMPT"),
|
|
)
|
|
comfy_api = types.ModuleType("comfy_api")
|
|
comfy_api.latest = latest
|
|
sys.modules["comfy_api"] = comfy_api
|
|
sys.modules["comfy_api.latest"] = latest
|
|
|
|
debug_utils = types.ModuleType(f"{package_name}.debug_utils")
|
|
debug_utils.configure_logger = lambda name: logging.getLogger(name)
|
|
sys.modules[debug_utils.__name__] = debug_utils
|
|
|
|
workflow_utils = types.ModuleType(f"{package_name}.workflow_utils")
|
|
for name in (
|
|
"list_workflow_files",
|
|
"load_workflow_file",
|
|
"load_workflow_url",
|
|
"build_expansion",
|
|
"build_modifier_source_expansion",
|
|
"apply_control_after_generate",
|
|
"validate_workflow_nodes_installed",
|
|
"get_workflow_interface",
|
|
):
|
|
setattr(workflow_utils, name, lambda *args, **kwargs: None)
|
|
sys.modules[workflow_utils.__name__] = workflow_utils
|
|
|
|
module_name = f"{package_name}.workflow_node"
|
|
spec = importlib.util.spec_from_file_location(
|
|
module_name,
|
|
REPO_ROOT / "workflow_node.py",
|
|
)
|
|
module = importlib.util.module_from_spec(spec)
|
|
sys.modules[module_name] = module
|
|
spec.loader.exec_module(module)
|
|
return module
|
|
|
|
|
|
workflow_node = _load_workflow_node_module()
|
|
|
|
|
|
class FakePrompt:
|
|
def __init__(self, nodes=None):
|
|
self.nodes = nodes or {}
|
|
|
|
def has_node(self, node_id):
|
|
return node_id in self.nodes
|
|
|
|
def get_node(self, node_id):
|
|
return self.nodes[node_id]
|
|
|
|
|
|
class ModifierSourceTargetTests(unittest.TestCase):
|
|
def test_resolves_file_and_url_subworkflow_targets(self):
|
|
prompt = FakePrompt({
|
|
"12": {
|
|
"class_type": "SWF_Subworkflow",
|
|
"inputs": {
|
|
"workflow": "inner.json",
|
|
"reload_each_execution": False,
|
|
},
|
|
},
|
|
"13": {
|
|
"class_type": "SWF_SubworkflowFromURL",
|
|
"inputs": {
|
|
"url": "https://example.test/workflow.json",
|
|
"verify_ssl": False,
|
|
"reload_each_execution": True,
|
|
},
|
|
},
|
|
})
|
|
workflow_node.SubworkflowModifierSource.hidden = SimpleNamespace(
|
|
dynprompt=prompt
|
|
)
|
|
|
|
file_target = workflow_node.SubworkflowModifierSource._resolve_target(
|
|
"12 | Local | inner.json"
|
|
)
|
|
url_target = workflow_node.SubworkflowModifierSource._resolve_target(
|
|
"13 | Remote | https://example.test/workflow.json"
|
|
)
|
|
|
|
self.assertIs(file_target[1], workflow_node.Subworkflow)
|
|
self.assertEqual(file_target[2], {"workflow": "inner.json"})
|
|
self.assertIs(file_target[3], False)
|
|
self.assertIs(url_target[1], workflow_node.SubworkflowFromURL)
|
|
self.assertEqual(url_target[2]["verify_ssl"], False)
|
|
self.assertIs(url_target[3], True)
|
|
|
|
def test_rejects_non_subworkflow_target(self):
|
|
prompt = FakePrompt({
|
|
"20": {"class_type": "KSampler", "inputs": {}},
|
|
})
|
|
workflow_node.SubworkflowModifierSource.hidden = SimpleNamespace(
|
|
dynprompt=prompt
|
|
)
|
|
with self.assertRaisesRegex(ValueError, "not a Subworkflow"):
|
|
workflow_node.SubworkflowModifierSource._resolve_target("20 | Sampler")
|
|
|
|
|
|
class WorkflowDataCacheTests(unittest.TestCase):
|
|
def setUp(self):
|
|
workflow_node.BaseSubworkflow._loaded_workflows.clear()
|
|
workflow_node.BaseSubworkflow._loaded_workflow_instances.clear()
|
|
workflow_node.BaseSubworkflow._prompt_workflows.clear()
|
|
self.loads = []
|
|
self.original_loader = workflow_node.Subworkflow.__dict__["_load_source"]
|
|
loads = self.loads
|
|
workflow_node.Subworkflow._load_source = classmethod(
|
|
lambda cls, workflow, **kwargs: (
|
|
loads.append(workflow) or {"load": len(loads)}
|
|
)
|
|
)
|
|
|
|
def tearDown(self):
|
|
workflow_node.Subworkflow._load_source = self.original_loader
|
|
|
|
def test_reload_loads_once_per_prompt(self):
|
|
prompt = FakePrompt()
|
|
first = workflow_node.Subworkflow._get_workflow_data(
|
|
True,
|
|
instance_id="12",
|
|
dynprompt=prompt,
|
|
workflow="inner.json",
|
|
)
|
|
second = workflow_node.Subworkflow._get_workflow_data(
|
|
True,
|
|
instance_id="12",
|
|
dynprompt=prompt,
|
|
workflow="inner.json",
|
|
)
|
|
self.assertIs(first, second)
|
|
self.assertEqual(len(self.loads), 1)
|
|
|
|
workflow_node.Subworkflow._get_workflow_data(
|
|
True,
|
|
instance_id="12",
|
|
dynprompt=FakePrompt(),
|
|
workflow="inner.json",
|
|
)
|
|
self.assertEqual(len(self.loads), 2)
|
|
|
|
def test_keep_loaded_is_isolated_by_target_node(self):
|
|
kept = workflow_node.Subworkflow._get_workflow_data(
|
|
False,
|
|
instance_id="12",
|
|
dynprompt=FakePrompt(),
|
|
workflow="inner.json",
|
|
)
|
|
kept_again = workflow_node.Subworkflow._get_workflow_data(
|
|
False,
|
|
instance_id="12",
|
|
dynprompt=FakePrompt(),
|
|
workflow="inner.json",
|
|
)
|
|
self.assertIs(kept, kept_again)
|
|
self.assertEqual(len(self.loads), 1)
|
|
|
|
workflow_node.Subworkflow._get_workflow_data(
|
|
False,
|
|
instance_id="99",
|
|
dynprompt=FakePrompt(),
|
|
workflow="inner.json",
|
|
)
|
|
self.assertEqual(len(self.loads), 2)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|