Files
robertvoy-ComfyUI-Distributed/tests/api/test_queue_orchestration.py
T
Robert Wojciechowski 3058b11ff2 Remove list splitter/collector feature and refactor distributed modules
- Remove DistributedListSplitter and DistributedListCollector nodes
- Remove /distributed/init_list_queue and /distributed/request_list_item endpoints
- Clean up all references from prompt_transform, queue_orchestration, constants, and tests
- Refactor and reorganize distributed modules for quality improvements
2026-03-07 05:22:09 +00:00

196 lines
8.9 KiB
Python

import asyncio
import importlib.util
import sys
import types
import unittest
from pathlib import Path
from tests.api.harness import (
bootstrap_test_package,
install_server_stub,
)
def _bootstrap_package(package_name):
bootstrap_test_package(
package_name,
with_api=True,
with_utils=True,
with_orchestration=True,
)
prompt_server_instance = types.SimpleNamespace(address="127.0.0.1", port=8188)
install_server_stub(prompt_server_instance)
async_helpers_module = types.ModuleType(f"{package_name}.utils.async_helpers")
async def _queue_prompt_payload(prompt_obj, workflow_meta, client_id):
_ = (prompt_obj, workflow_meta, client_id)
return "prompt-id"
async_helpers_module.queue_prompt_payload = _queue_prompt_payload
sys.modules[f"{package_name}.utils.async_helpers"] = async_helpers_module
config_module = types.ModuleType(f"{package_name}.utils.config")
config_module.load_config = lambda: {"settings": {}, "workers": []}
sys.modules[f"{package_name}.utils.config"] = config_module
constants_module = types.ModuleType(f"{package_name}.utils.constants")
constants_module.ORCHESTRATION_MEDIA_SYNC_CONCURRENCY = 2
constants_module.ORCHESTRATION_MEDIA_SYNC_TIMEOUT = 120.0
constants_module.ORCHESTRATION_WORKER_PROBE_CONCURRENCY = 8
constants_module.ORCHESTRATION_WORKER_PREP_CONCURRENCY = 4
sys.modules[f"{package_name}.utils.constants"] = constants_module
logging_module = types.ModuleType(f"{package_name}.utils.logging")
logging_module.debug_log = lambda *_args, **_kwargs: None
logging_module.log = lambda *_args, **_kwargs: None
sys.modules[f"{package_name}.utils.logging"] = logging_module
network_module = types.ModuleType(f"{package_name}.utils.network")
network_module.build_master_url = lambda **_kwargs: "http://127.0.0.1:8188"
sys.modules[f"{package_name}.utils.network"] = network_module
trace_logger_module = types.ModuleType(f"{package_name}.utils.trace_logger")
trace_logger_module.trace_debug = lambda *_args, **_kwargs: None
sys.modules[f"{package_name}.utils.trace_logger"] = trace_logger_module
runtime_state_module = types.ModuleType(f"{package_name}.utils.runtime_state")
def _ensure_distributed_runtime_state(server_instance=None):
ps = server_instance or prompt_server_instance
if not hasattr(ps, "distributed_pending_jobs"):
ps.distributed_pending_jobs = {}
if not hasattr(ps, "distributed_jobs_lock"):
ps.distributed_jobs_lock = asyncio.Lock()
if not hasattr(ps, "distributed_job_allowed_workers"):
ps.distributed_job_allowed_workers = {}
if not hasattr(ps, "distributed_pending_tile_jobs"):
ps.distributed_pending_tile_jobs = {}
if not hasattr(ps, "distributed_tile_jobs_lock"):
ps.distributed_tile_jobs_lock = asyncio.Lock()
return types.SimpleNamespace(
distributed_pending_jobs=ps.distributed_pending_jobs,
distributed_jobs_lock=ps.distributed_jobs_lock,
distributed_job_allowed_workers=ps.distributed_job_allowed_workers,
distributed_pending_tile_jobs=ps.distributed_pending_tile_jobs,
distributed_tile_jobs_lock=ps.distributed_tile_jobs_lock,
)
runtime_state_module.ensure_distributed_runtime_state = _ensure_distributed_runtime_state
runtime_state_module.get_prompt_server_instance = lambda: prompt_server_instance
sys.modules[f"{package_name}.utils.runtime_state"] = runtime_state_module
schemas_module = types.ModuleType(f"{package_name}.api.schemas")
schemas_module.coerce_positive_int = (
lambda value, default: int(value) if str(value).isdigit() and int(value) > 0 else default
)
schemas_module.coerce_positive_float = (
lambda value, default: float(value) if isinstance(value, (int, float, str)) and float(value) > 0 else default
)
sys.modules[f"{package_name}.api.schemas"] = schemas_module
dispatch_module = types.ModuleType(f"{package_name}.api.orchestration.dispatch")
dispatch_module.dispatch_worker_prompt = lambda *args, **kwargs: None
dispatch_module.rank_workers_by_load = lambda workers, **kwargs: workers
dispatch_module.select_active_workers = lambda workers, *_args, **_kwargs: (workers, False)
dispatch_module.select_least_busy_worker = lambda workers, **_kwargs: workers[0] if workers else None
sys.modules[f"{package_name}.api.orchestration.dispatch"] = dispatch_module
media_sync_module = types.ModuleType(f"{package_name}.api.orchestration.media_sync")
media_sync_module.convert_paths_for_platform = lambda prompt, _sep: prompt
media_sync_module.fetch_worker_path_separator = lambda *_args, **_kwargs: "/"
media_sync_module.sync_worker_media = lambda *_args, **_kwargs: None
sys.modules[f"{package_name}.api.orchestration.media_sync"] = media_sync_module
prompt_transform_module = types.ModuleType(f"{package_name}.api.orchestration.prompt_transform")
prompt_transform_module.PromptIndex = object
prompt_transform_module.apply_participant_overrides = lambda prompt, *_args, **_kwargs: prompt
prompt_transform_module.find_nodes_by_class = lambda _prompt, _class_name: []
prompt_transform_module.generate_job_id_map = lambda _index, _prefix: {}
prompt_transform_module.prepare_delegate_master_prompt = lambda prompt, _ids: prompt
prompt_transform_module.prune_prompt_for_worker = lambda prompt: prompt
sys.modules[f"{package_name}.api.orchestration.prompt_transform"] = prompt_transform_module
def _load_module(package_name, module_rel_path, module_name):
module_path = Path(__file__).resolve().parents[2] / module_rel_path
spec = importlib.util.spec_from_file_location(
f"{package_name}.{module_name}",
module_path,
)
module = importlib.util.module_from_spec(spec)
assert spec is not None and spec.loader is not None
sys.modules[spec.name] = module
spec.loader.exec_module(module)
return module
def _load_queue_orchestration_module():
package_name = "dist_queue_orch_testpkg"
_bootstrap_package(package_name)
return _load_module(package_name, "api/queue_orchestration.py", "api.queue_orchestration")
queue_orchestration = _load_queue_orchestration_module()
class QueueOrchestrationHelpersTests(unittest.TestCase):
def test_resolve_enabled_workers_filters_disabled_and_requested(self):
config = {
"workers": [
{"id": "1", "enabled": True, "host": "a", "port": 8188},
{"id": "2", "enabled": False, "host": "b", "port": 8189},
{"id": "3", "enabled": True, "host": "c", "port": "bad-port"},
]
}
enabled_only = queue_orchestration._resolve_enabled_workers(config)
requested = queue_orchestration._resolve_enabled_workers(config, requested_ids={"2", "3"})
self.assertEqual([worker["id"] for worker in enabled_only], ["1", "3"])
self.assertEqual([worker["id"] for worker in requested], ["2", "3"])
self.assertEqual(requested[1]["port"], 8188)
def test_resolve_orchestration_limits_uses_defaults_for_invalid_values(self):
config = {
"settings": {
"worker_probe_concurrency": "x",
"worker_prep_concurrency": "3",
"media_sync_concurrency": "-1",
"media_sync_timeout_seconds": "42.5",
}
}
limits = queue_orchestration._resolve_orchestration_limits(config)
self.assertEqual(limits[0], 8) # default
self.assertEqual(limits[1], 3) # parsed
self.assertEqual(limits[2], 2) # default
self.assertEqual(limits[3], 42.5)
def test_is_load_balance_enabled_accepts_common_forms(self):
self.assertTrue(queue_orchestration._is_load_balance_enabled(True))
self.assertTrue(queue_orchestration._is_load_balance_enabled(1))
self.assertTrue(queue_orchestration._is_load_balance_enabled(" yes "))
self.assertFalse(queue_orchestration._is_load_balance_enabled(0))
self.assertFalse(queue_orchestration._is_load_balance_enabled("off"))
def test_prompt_requests_load_balance_detects_collector_flag(self):
prompt_index = types.SimpleNamespace(
nodes_for_class=lambda class_name: ["10"] if class_name == "DistributedCollector" else [],
inputs_by_node={"10": {"load_balance": "true"}},
)
self.assertTrue(queue_orchestration._prompt_requests_load_balance(prompt_index))
def test_ensure_distributed_state_initializes_attrs(self):
prompt_server = types.SimpleNamespace()
queue_orchestration.ensure_distributed_state(prompt_server)
self.assertTrue(hasattr(prompt_server, "distributed_pending_jobs"))
self.assertTrue(hasattr(prompt_server, "distributed_jobs_lock"))
self.assertIsInstance(prompt_server.distributed_jobs_lock, asyncio.Lock)
if __name__ == "__main__":
unittest.main()