"""Tests split from the Modal executor integration suite.""" from __future__ import annotations from modal_executor_test_support import * # noqa: F401,F403 def test_stable_modal_cloud_entry_imports_without_modal_sdk( modal_cloud_module: Any, ) -> None: """The stable Modal cloud module should stay importable when modal is unavailable.""" assert modal_cloud_module.__name__ == "comfyui_modal_sync_cloud" assert hasattr(modal_cloud_module, "RemoteEngine") def test_modal_cloud_exposes_affinity_parameter_and_local_gap_keepalive() -> None: """The deployed class must make affinity part of identity and expose a heartbeat.""" source = (Path(__file__).resolve().parents[1] / "comfyui_modal_sync_cloud.py").read_text() assert "worker_affinity_key: str = modal.parameter" in source assert "def keepalive_for_local_gap" in source def test_modal_cloud_image_environment_preserves_unique_app_name( modal_cloud_module: Any, ) -> None: """Remote workers should receive the same per-ComfyUI app name resolved locally.""" settings = types.SimpleNamespace( app_name="comfy-modal-sync-AAECAwQFBgc", modal_gpu="B300", modal_secret_name="workflow-credentials", stream_event_queue_maxsize=256, bridge_inline_max_bytes=1024, invocation_result_inline_max_bytes=2048, execution_timeout_seconds=3600, startup_timeout_seconds=900, llm_vllm_execution_mode="throughput", ) image_environment = modal_cloud_module._modal_image_environment(settings, "fingerprint-1") assert image_environment["COMFY_MODAL_APP_NAME"] == "comfy-modal-sync-AAECAwQFBgc" assert image_environment["COMFY_MODAL_GPU"] == "B300" assert image_environment["COMFY_MODAL_REMOTE_STORAGE_ROOT"] == "/storage" assert image_environment["COMFY_MODAL_REMOTE_WORKER"] == "1" assert image_environment["COMFY_MODAL_EXECUTION_MODE"] == "local" assert image_environment["COMFY_MODAL_LLM_VLLM_EXECUTION_MODE"] == "throughput" assert image_environment["VLLM_CACHE_ROOT"].startswith( "/root/.cache/comfy-modal-llm/" ) assert image_environment["TORCHINDUCTOR_CACHE_DIR"].startswith( "/root/.cache/comfy-modal-llm/" ) assert image_environment["VLLM_USE_FLASHINFER_SAMPLER"] == "0" assert image_environment["COMFY_MODAL_LLM_MAX_RESIDENT_MODELS"] == "2" assert image_environment["COMFY_MODAL_LLM_MEMORY_RECOVERY_TIMEOUT_SECONDS"] == "15.0" assert image_environment["COMFY_MODAL_LLM_RESERVE_FREE_GB"] == "24.0" assert image_environment["COMFY_MODAL_SECRET_NAME"] == "workflow-credentials" assert image_environment["COMFY_MODAL_RUNTIME_FINGERPRINT"] == "fingerprint-1" def test_modal_cloud_observes_distinct_workflows_for_llm_auto_mode( modal_cloud_module: Any, monkeypatch: pytest.MonkeyPatch, ) -> None: """The container boundary should count prompts while excluding canary traffic.""" observed_prompt_ids: list[str | None] = [] forced_prompt_ids: list[str | None] = [] runtime_module = types.ModuleType("modal_llm_runtime") runtime_module.observe_modal_workflow_execution = observed_prompt_ids.append runtime_module.force_modal_vllm_throughput_after_memory_recovery = ( forced_prompt_ids.append ) monkeypatch.setitem(sys.modules, "modal_llm_runtime", runtime_module) modal_cloud_module._observe_remote_workflow_for_llm_mode( {"prompt_id": "prompt-1", "payload_kind": "subgraph"} ) modal_cloud_module._observe_remote_workflow_for_llm_mode( {"prompt_id": "prompt-1", "payload_kind": "mapped_subgraph"} ) modal_cloud_module._observe_remote_workflow_for_llm_mode( {"prompt_id": "health-check", "payload_kind": "canary"} ) modal_cloud_module._observe_remote_workflow_for_llm_mode( {"prompt_id": "prompt-2", "payload_kind": "subgraph"} ) modal_cloud_module._observe_remote_workflow_for_llm_mode( { "prompt_id": "prompt-3", "payload_kind": "subgraph", "force_vllm_throughput_after_memory_recovery": True, } ) assert observed_prompt_ids == ["prompt-1", "prompt-1", "prompt-2"] assert forced_prompt_ids == ["prompt-3"] def test_modal_cloud_resolves_configured_named_secret( modal_cloud_module: Any, ) -> None: """Cloud app construction should reference the configured collection without reading .env.""" observed_names: list[str] = [] expected_secret = object() fake_modal = types.SimpleNamespace( Secret=types.SimpleNamespace( from_name=lambda name: observed_names.append(name) or expected_secret, ) ) resolved_secret = modal_cloud_module._modal_secret_from_settings( types.SimpleNamespace(modal_secret_name="workflow-credentials"), fake_modal, ) assert resolved_secret is expected_secret assert observed_names == ["workflow-credentials"] def test_modal_cloud_installs_timestamped_logger_handler( modal_cloud_module: Any, ) -> None: """The cloud runtime should install its own timestamped logger handler.""" matching_handlers = [ handler for handler in modal_cloud_module.logger.handlers if getattr(handler, "name", "") == modal_cloud_module._CLOUD_HANDLER_NAME ] assert len(matching_handlers) == 1 assert modal_cloud_module.logger.propagate is False assert modal_cloud_module.logger.level == logging.INFO assert matching_handlers[0].stream is sys.stdout formatter = matching_handlers[0].formatter assert isinstance(formatter, logging.Formatter) assert "%(asctime)s" in formatter._fmt assert "%(relativeCreated)" in formatter._fmt def test_modal_cloud_traces_remote_node_execution_spans( modal_cloud_module: Any, monkeypatch: Any, capsys: Any, ) -> None: """The tracing prompt server should emit per-node timing lines.""" prompt = { "7": {"class_type": "UNETLoader", "inputs": {}}, "2": {"class_type": "KSampler", "inputs": {}}, } monkeypatch.setenv("MODAL_IS_REMOTE", "1") server = modal_cloud_module._TracingPromptServer("component-1", prompt) server.send_sync("executing", {"node": "7"}, None) server.send_sync("executed", {"node": "7"}, None) server.send_sync("executing", {"node": "2"}, None) server.send_sync("execution_success", {"prompt_id": "component-1"}, None) captured = capsys.readouterr() assert "Remote node 7 class_type=UNETLoader role=model_load started" in captured.out assert "Remote node 7 class_type=UNETLoader role=model_load finished in " in captured.out assert "Remote node 2 class_type=KSampler role=sampling started" in captured.out assert "Remote node 2 class_type=KSampler role=sampling finished in " in captured.out def test_modal_cloud_installs_headless_prompt_server_instance( modal_cloud_module: Any, monkeypatch: Any, ) -> None: """Remote custom-node init should get a minimal PromptServer.instance shim.""" class FakeNodeReplaceManager: """Record replacement registrations made through the headless server.""" def __init__(self) -> None: """Initialize an empty replacement registry.""" self.registrations: list[Any] = [] def register(self, replacement: Any) -> None: """Record one replacement registered by a ComfyUI extension.""" self.registrations.append(replacement) fake_prompt_server_class = type("PromptServer", (), {}) fake_server_module = types.SimpleNamespace( NodeReplaceManager=FakeNodeReplaceManager, PromptServer=fake_prompt_server_class, ) monkeypatch.setitem(sys.modules, "server", fake_server_module) modal_cloud_module._ensure_headless_prompt_server_instance() instance = fake_prompt_server_class.instance assert instance is not None assert hasattr(instance, "routes") assert hasattr(instance, "app") assert isinstance(instance.node_replace_manager, FakeNodeReplaceManager) assert instance.supports == ["custom_nodes_from_web"] assert instance.client_id is None assert instance.last_node_id is None assert instance.number == 0 assert instance.prompt_queue.currently_running == {} assert instance.prompt_queue.get_current_queue() == ([], []) instance.send_progress_text("width: 1024, height: 768", "104") instance.add_on_prompt_handler("handler") assert instance.on_prompt_handlers == ["handler"] instance.node_replace_manager.register("replacement") assert instance.node_replace_manager.registrations == ["replacement"] instance.prompt_queue.put((1, "prompt-id", {}, {}, [])) assert instance.prompt_queue.get_current_queue() == ( [], [(1, "prompt-id", {}, {}, [])], ) assert instance.prompt_queue.get_tasks_remaining() == 1 instance.prompt_queue.set_flag("free_memory", True) assert instance.prompt_queue.get_flags() == {"free_memory": True} assert instance.prompt_queue.get_flags() == {} def test_modal_cloud_dynamic_prompt_preserves_thread_local_metadata_graph( modal_cloud_module: Any, ) -> None: """Hydrated execution graphs should not replace the JSON-safe hidden PROMPT graph.""" class FakeDynamicPrompt: """Minimal ComfyUI DynamicPrompt stand-in.""" def __init__(self, original_prompt: dict[str, Any]) -> None: """Retain the graph used for runtime dependency resolution.""" self.original_prompt = original_prompt def get_original_prompt(self) -> dict[str, Any]: """Return the runtime graph when no metadata override is active.""" return self.original_prompt fake_execution = types.SimpleNamespace(DynamicPrompt=FakeDynamicPrompt) hydrated_prompt = {"1": {"inputs": {"image": object()}}} metadata_prompt = {"1": {"inputs": {"image": ["upstream", 0]}}} modal_cloud_module._install_metadata_safe_dynamic_prompt_wrapper(fake_execution) with modal_cloud_module._temporary_prompt_metadata(metadata_prompt): dynamic_prompt = fake_execution.DynamicPrompt(hydrated_prompt) ordinary_prompt = fake_execution.DynamicPrompt(hydrated_prompt) assert dynamic_prompt.original_prompt is hydrated_prompt assert dynamic_prompt.get_original_prompt() is metadata_prompt assert ordinary_prompt.get_original_prompt() is hydrated_prompt def test_modal_cloud_hidden_prompt_stays_json_safe_after_tensor_boundary_hydration( modal_cloud_module: Any, monkeypatch: Any, ) -> None: """Metadata-writing output nodes should receive links, not hydrated tensors, in PROMPT.""" import torch monkeypatch.setitem(sys.modules, "torchsde", types.ModuleType("torchsde")) import comfy_execution.caching as comfy_caching modal_cloud_module._ensure_comfy_runtime_initialized(None) nodes_module = modal_cloud_module._load_nodes_module() monkeypatch.setitem( nodes_module.NODE_CLASS_MAPPINGS, "PromptMetadataSerializationNode", _PromptMetadataSerializationNode, ) monkeypatch.setitem( comfy_caching.nodes.NODE_CLASS_MAPPINGS, "PromptMetadataSerializationNode", _PromptMetadataSerializationNode, ) monkeypatch.setitem( nodes_module.NODE_DISPLAY_NAME_MAPPINGS, "PromptMetadataSerializationNode", "PromptMetadataSerializationNode", ) monkeypatch.setattr( _cloud_prompt_execution_owner(), "_node_output_cache_store", lambda: None ) payload = { "payload_kind": "subgraph", "component_id": "metadata-output", "prompt_id": "prompt-metadata-output", "component_node_ids": ["output"], "subgraph_prompt": { "output": { "class_type": "PromptMetadataSerializationNode", "inputs": {"image": ["upstream", 0]}, } }, "boundary_inputs": [ { "proxy_input_name": "remote_image", "io_type": "IMAGE", "source_signature": "image-signature", "targets": [{"node_id": "output", "input_name": "image"}], } ], "boundary_outputs": [ { "proxy_output_name": "metadata", "node_id": "output", "output_index": 0, "io_type": "STRING", "is_list": False, } ], "execute_node_ids": ["output"], "extra_data": {}, } outputs = modal_cloud_module._execute_subgraph_prompt( payload, {"remote_image": torch.zeros((1, 2, 2, 3), dtype=torch.float32)}, None, ) assert len(outputs) == 1 assert json.loads(outputs[0]) == payload["subgraph_prompt"] def test_modal_cloud_only_reloads_volume_for_requests_with_new_uploads( modal_cloud_module: Any, ) -> None: """Steady-state requests should skip Modal volume reload when queue-time sync uploaded nothing.""" assert modal_cloud_module._should_reload_modal_volume({"requires_volume_reload": True}) is True assert modal_cloud_module._should_reload_modal_volume({"requires_volume_reload": False}) is False assert modal_cloud_module._should_reload_modal_volume({}) is True def test_modal_cloud_skips_reload_when_uploaded_paths_are_already_visible( modal_cloud_module: Any, monkeypatch: Any, tmp_path: Path, ) -> None: """Visible immutable uploaded paths should not force an extra Modal volume reload.""" storage_root = tmp_path / "storage" storage_root.mkdir() uploaded_path = storage_root / "assets" / "hash_model.safetensors" uploaded_path.parent.mkdir(parents=True, exist_ok=True) uploaded_path.write_bytes(b"weights") recorded_markers: list[str] = [] _patch_cloud_storage_root(monkeypatch, modal_cloud_module, storage_root) monkeypatch.setattr( _cloud_volume_reload_owner(), "_record_modal_volume_reload_marker", lambda marker: recorded_markers.append(marker), ) payload = { "requires_volume_reload": True, "volume_reload_marker": "marker-1", "uploaded_volume_paths": ["/assets/hash_model.safetensors"], } assert modal_cloud_module._should_reload_modal_volume(payload) is False assert recorded_markers == ["marker-1"] def test_modal_cloud_reads_missing_committed_asset_without_reloading_volume( modal_cloud_module: Any, monkeypatch: Any, tmp_path: Path, ) -> None: """A stale warm mount should read a committed asset into ephemeral storage.""" class FakeVolume: """Modal Volume double that exposes committed file reads.""" def __init__(self) -> None: """Record direct read paths.""" self.read_paths: list[str] = [] def read_file(self, path: str) -> Iterator[bytes]: """Yield one committed model file in chunks.""" self.read_paths.append(path) yield b"model-" yield b"weights" storage_root = tmp_path / "storage" storage_root.mkdir() readthrough_root = tmp_path / "readthrough" _patch_cloud_storage_root(monkeypatch, modal_cloud_module, storage_root) bootstrap_owner = _cloud_comfy_bootstrap_owner() monkeypatch.setattr( bootstrap_owner, "_REMOTE_VOLUME_READTHROUGH_ROOT", readthrough_root, ) payload = { "component_id": "component-1", "requires_volume_reload": False, "subgraph_prompt": { "14": { "class_type": "LoraLoaderModelOnly", "inputs": {"lora_name": "/assets/hash_style.safetensors"}, } }, } volume = FakeVolume() hydrated_paths = modal_cloud_module._hydrate_missing_payload_volume_paths(volume, payload) cached_path = readthrough_root / "assets" / "hash_style.safetensors" assert hydrated_paths == [cached_path] assert cached_path.read_bytes() == b"model-weights" assert volume.read_paths == ["assets/hash_style.safetensors"] assert modal_cloud_module._should_reload_modal_volume(payload) is False assert ( modal_cloud_module._resolve_runtime_asset_path("/assets/hash_style.safetensors") == str(cached_path) ) def test_modal_cloud_reloads_when_reused_referenced_asset_is_still_missing( modal_cloud_module: Any, monkeypatch: Any, tmp_path: Path, ) -> None: """A sync-index hit must not suppress recovery for a missing runtime asset.""" storage_root = tmp_path / "storage" storage_root.mkdir() _patch_cloud_storage_root(monkeypatch, modal_cloud_module, storage_root) monkeypatch.setattr( _cloud_comfy_bootstrap_owner(), "_REMOTE_VOLUME_READTHROUGH_ROOT", tmp_path / "readthrough", ) payload = { "requires_volume_reload": False, "subgraph_prompt": { "14": { "class_type": "LoraLoaderModelOnly", "inputs": {"lora_name": "/assets/missing_style.safetensors"}, } }, } assert modal_cloud_module._should_reload_modal_volume(payload) is True def test_modal_cloud_preserves_worker_for_deterministic_remote_failures( modal_cloud_module: Any, monkeypatch: Any, ) -> None: """Ordinary node failures should not discard a healthy warm Modal worker.""" scheduled_exits: list[tuple[float, int]] = [] original_flag = modal_cloud_module._CONTAINER_TERMINATION_SCHEDULED monkeypatch.setattr(modal_cloud_module, "_CONTAINER_TERMINATION_SCHEDULED", False) monkeypatch.setattr(modal_cloud_module, "_is_modal_container_runtime", lambda: True) monkeypatch.setattr( modal_cloud_module, "_schedule_process_exit", lambda delay_seconds, exit_code: scheduled_exits.append((delay_seconds, exit_code)), ) try: scheduled = modal_cloud_module._maybe_schedule_container_termination_on_error( {"component_id": "component-1", "terminate_container_on_error": True}, RuntimeError("boom"), ) finally: monkeypatch.setattr(modal_cloud_module, "_CONTAINER_TERMINATION_SCHEDULED", original_flag) assert scheduled is False assert scheduled_exits == [] def test_modal_cloud_retires_worker_for_poisoned_cuda_runtime( modal_cloud_module: Any, monkeypatch: Any, ) -> None: """CUDA failures that can poison process state should retire the worker.""" scheduled_exits: list[tuple[float, int]] = [] original_flag = modal_cloud_module._CONTAINER_TERMINATION_SCHEDULED monkeypatch.setattr(modal_cloud_module, "_CONTAINER_TERMINATION_SCHEDULED", False) monkeypatch.setattr(modal_cloud_module, "_is_modal_container_runtime", lambda: True) monkeypatch.setattr( modal_cloud_module, "_schedule_process_exit", lambda delay_seconds, exit_code: scheduled_exits.append((delay_seconds, exit_code)), ) try: scheduled = modal_cloud_module._maybe_schedule_container_termination_on_error( {"component_id": "component-1", "terminate_container_on_error": True}, RuntimeError("CUDA error: an illegal memory access was encountered"), ) finally: monkeypatch.setattr(modal_cloud_module, "_CONTAINER_TERMINATION_SCHEDULED", original_flag) assert scheduled is True assert scheduled_exits == [(modal_cloud_module._REMOTE_ERROR_CONTAINER_EXIT_DELAY_SECONDS, 1)] def test_modal_cloud_retires_worker_after_llm_memory_recovery_timeout( modal_cloud_module: Any, monkeypatch: Any, ) -> None: """An unrecovered eviction should retire dirty process state before retry.""" scheduled_exits: list[tuple[float, int]] = [] original_flag = modal_cloud_module._CONTAINER_TERMINATION_SCHEDULED monkeypatch.setattr(modal_cloud_module, "_CONTAINER_TERMINATION_SCHEDULED", False) monkeypatch.setattr(modal_cloud_module, "_is_modal_container_runtime", lambda: True) monkeypatch.setattr( modal_cloud_module, "_schedule_process_exit", lambda delay_seconds, exit_code: scheduled_exits.append((delay_seconds, exit_code)), ) try: scheduled = modal_cloud_module._maybe_schedule_container_termination_on_error( {"component_id": "llm-1", "terminate_container_on_error": True}, RuntimeError("[comfy-modal-llm-memory-recovery-exhausted] still low"), ) finally: monkeypatch.setattr(modal_cloud_module, "_CONTAINER_TERMINATION_SCHEDULED", original_flag) assert scheduled is True assert scheduled_exits == [(modal_cloud_module._REMOTE_ERROR_CONTAINER_EXIT_DELAY_SECONDS, 1)] def test_modal_cloud_raises_after_exhausting_open_file_reload_retries( modal_cloud_module: Any, monkeypatch: Any, ) -> None: """Modal volume reload should surface persistent open-file errors after bounded retries.""" class FakeVolume: """Simple Modal volume double that always fails with open files.""" def __init__(self) -> None: """Initialize the reload attempt counter.""" self.reload_calls = 0 def reload(self) -> None: """Always fail with the same open-file reload error.""" self.reload_calls += 1 raise RuntimeError("there are open files preventing the operation") prepare_calls: list[str] = [] sleep_calls: list[float] = [] monkeypatch.setattr( _cloud_volume_reload_owner(), "_prepare_for_modal_volume_reload", lambda: prepare_calls.append("prepared"), ) monkeypatch.setattr( _cloud_volume_reload_owner(), "_sleep_before_modal_volume_reload_retry", lambda delay_seconds: sleep_calls.append(delay_seconds), ) volume = FakeVolume() with pytest.raises(RuntimeError, match="open files"): modal_cloud_module._reload_modal_volume_for_request(volume, "component-1") assert volume.reload_calls == len( modal_cloud_module._MODAL_VOLUME_RELOAD_OPEN_FILE_RETRY_DELAYS_SECONDS ) assert prepare_calls == ["prepared"] * ( len(modal_cloud_module._MODAL_VOLUME_RELOAD_OPEN_FILE_RETRY_DELAYS_SECONDS) - 1 ) assert sleep_calls == list( modal_cloud_module._MODAL_VOLUME_RELOAD_OPEN_FILE_RETRY_DELAYS_SECONDS[1:] ) def test_modal_cloud_proceeds_when_referenced_volume_paths_are_already_visible( modal_cloud_module: Any, monkeypatch: Any, tmp_path: Path, ) -> None: """Persistent open-file reload errors may be ignored when the payload's mounted files are already visible.""" class FakeVolume: """Simple Modal volume double that always fails with open files.""" def __init__(self) -> None: """Initialize the reload attempt counter.""" self.reload_calls = 0 def reload(self) -> None: """Always fail with the same open-file reload error.""" self.reload_calls += 1 raise RuntimeError("there are open files preventing the operation") storage_root = tmp_path / "storage" storage_root.mkdir() asset_path = storage_root / "assets" / "hash_model.safetensors" asset_path.parent.mkdir(parents=True, exist_ok=True) asset_path.write_bytes(b"weights") bundle_path = storage_root / "custom_nodes" / "hash_bundle.zip" bundle_path.parent.mkdir(parents=True, exist_ok=True) bundle_path.write_bytes(b"bundle") recorded_markers: list[str] = [] prepare_calls: list[str] = [] sleep_calls: list[float] = [] _patch_cloud_storage_root(monkeypatch, modal_cloud_module, storage_root) monkeypatch.setattr( _cloud_volume_reload_owner(), "_prepare_for_modal_volume_reload", lambda: prepare_calls.append("prepared"), ) monkeypatch.setattr( _cloud_volume_reload_owner(), "_sleep_before_modal_volume_reload_retry", lambda delay_seconds: sleep_calls.append(delay_seconds), ) monkeypatch.setattr( _cloud_volume_reload_owner(), "_record_modal_volume_reload_marker", lambda marker: recorded_markers.append(marker), ) payload = { "custom_nodes_bundle": "/custom_nodes/hash_bundle.zip", "subgraph_prompt": { "1": { "class_type": "CheckpointLoaderSimple", "inputs": {"ckpt_name": "/assets/hash_model.safetensors"}, } }, } volume = FakeVolume() modal_cloud_module._reload_modal_volume_for_request( volume, "component-1", reload_marker="marker-1", payload=payload, ) assert volume.reload_calls == len( modal_cloud_module._MODAL_VOLUME_RELOAD_OPEN_FILE_RETRY_DELAYS_SECONDS ) assert recorded_markers == ["marker-1"] assert prepare_calls == ["prepared"] * ( len(modal_cloud_module._MODAL_VOLUME_RELOAD_OPEN_FILE_RETRY_DELAYS_SECONDS) - 1 ) assert sleep_calls == list( modal_cloud_module._MODAL_VOLUME_RELOAD_OPEN_FILE_RETRY_DELAYS_SECONDS[1:] ) def test_modal_cloud_logs_volume_reload_diagnostics_for_open_file_retries( modal_cloud_module: Any, monkeypatch: Any, tmp_path: Path, ) -> None: """Open-file retries should log which uploaded and referenced volume paths matter.""" class FakeVolume: """Simple Modal volume double that always fails with open files.""" def reload(self) -> None: """Always fail with the same open-file reload error.""" raise RuntimeError("there are open files preventing the operation") storage_root = tmp_path / "storage" storage_root.mkdir() uploaded_path = storage_root / "assets" / "missing_model.safetensors" logged_messages: list[tuple[str, tuple[Any, ...]]] = [] _patch_cloud_storage_root(monkeypatch, modal_cloud_module, storage_root) monkeypatch.setattr( _cloud_volume_reload_owner(), "_prepare_for_modal_volume_reload", lambda: None, ) monkeypatch.setattr( _cloud_volume_reload_owner(), "_sleep_before_modal_volume_reload_retry", lambda delay_seconds: None, ) monkeypatch.setattr( _cloud_volume_reload_owner().logger, "info", lambda message, *args: logged_messages.append((message, args)), ) payload = { "uploaded_volume_paths": ["/assets/missing_model.safetensors"], "subgraph_prompt": { "1": { "class_type": "CheckpointLoaderSimple", "inputs": {"ckpt_name": "/assets/missing_model.safetensors"}, } }, } with pytest.raises(RuntimeError, match="open files"): modal_cloud_module._reload_modal_volume_for_request( FakeVolume(), "component-1", payload=payload, ) assert any( "Modal volume reload diagnostics for component=%s context=%s uploaded_paths=%s referenced_paths=%s visible_uploaded=%s visible_referenced=%s." in message and args[0] == "component-1" and args[1] == "open_files_retry" and args[2] == [str(uploaded_path)] and args[3] == [str(uploaded_path)] and args[4] is False and args[5] is False for message, args in logged_messages ) def test_modal_cloud_ignores_heavy_comfyui_paths( modal_cloud_module: Any, ) -> None: """The Modal cloud module should skip heavyweight ComfyUI runtime artifacts.""" from pathlib import Path assert modal_cloud_module._should_ignore_comfyui_path(Path("models/checkpoint.safetensors")) assert modal_cloud_module._should_ignore_comfyui_path(Path("custom_nodes/example/__init__.py")) assert modal_cloud_module._should_ignore_comfyui_path(Path("output/run/output.png")) assert modal_cloud_module._should_ignore_comfyui_path( Path(".cache/strings/acfca22bde9a1a1fee53fe6e1299f4fe54a78a6f1d306dbb6cac2e71cf35d2c2.txt") ) assert modal_cloud_module._should_ignore_comfyui_path(Path("__pycache__/execution.pyc")) assert modal_cloud_module._should_ignore_comfyui_path(Path("False/checkpoints/model.pth")) assert modal_cloud_module._should_ignore_comfyui_path(Path("tests/test_execution.py")) assert modal_cloud_module._should_ignore_comfyui_path(Path("unexpected/code.py")) assert not modal_cloud_module._should_ignore_comfyui_path(Path("execution.py")) assert not modal_cloud_module._should_ignore_comfyui_path(Path("requirements.txt")) assert not modal_cloud_module._should_ignore_comfyui_path(Path("comfy/model_management.py")) assert not modal_cloud_module._should_ignore_comfyui_path(Path("comfy/ldm/models/diffusion/ddpm.py")) def test_modal_cloud_installs_comfyui_runtime_packages( modal_cloud_module: Any, ) -> None: """The Modal cloud image should include the core packages ComfyUI imports at runtime.""" assert modal_cloud_module._comfyui_apt_packages() == ( "build-essential", "libgl1", "libglib2.0-0", ) packages = modal_cloud_module._comfyui_runtime_packages() package_names = {package.split("==", maxsplit=1)[0] for package in packages} assert all("==" in package for package in packages) assert { "aiohttp", "alembic", "blake3", "comfy-angle", "comfy-aimdo", "comfy-kitchen", "hf-xet", "huggingface-hub", "kornia", "opencv-python-headless", "pydantic-settings", "psutil", "pyopengl", "sentencepiece", "simpleeval", "spandrel", "torchsde", "transformers", } <= package_names def test_modal_cloud_selects_gpu_compatible_pytorch_stack( modal_cloud_module: Any, ) -> None: """Every Modal GPU image should expose the shared pinned PyTorch build.""" default_build = modal_cloud_module._select_remote_torch_build("A100") b300_build = modal_cloud_module._select_remote_torch_build("B300") assert default_build.install_layers[0].packages == ( "torch==2.13.0", "torchvision==0.28.0", ) assert default_build.install_layers[0].index_url == "https://download.pytorch.org/whl/cu130" assert default_build == b300_build assert b300_build.install_layers[0].packages == ( "torch==2.13.0", "torchvision==0.28.0", ) assert b300_build.install_layers[0].index_url == "https://download.pytorch.org/whl/cu130" assert b300_build.install_layers[1].packages == ("torchaudio==2.11.0+cpu",) assert b300_build.install_layers[1].index_url == "https://download.pytorch.org/whl/cpu" assert b300_build.install_layers[1].extra_options == "--no-deps" def test_modal_cloud_installs_and_validates_torch_layers_in_order( modal_cloud_module: Any, ) -> None: """The CPU TorchAudio layer must not replace the CUDA-enabled Torch package.""" class RecordingImage: """Record Modal image package and command layers.""" def __init__(self) -> None: """Initialize an empty call record.""" self.calls: list[tuple[str, tuple[Any, ...], dict[str, Any]]] = [] def pip_install(self, *packages: str, **options: Any) -> "RecordingImage": """Record one package installation layer.""" self.calls.append(("pip_install", packages, options)) return self def run_commands(self, *commands: str) -> "RecordingImage": """Record one image validation command.""" self.calls.append(("run_commands", commands, {})) return self build = modal_cloud_module._select_remote_torch_build("B300") image = RecordingImage() result = modal_cloud_module._install_remote_torch_build(image, build) assert result is image assert image.calls[:2] == [ ( "pip_install", ("torch==2.13.0", "torchvision==0.28.0"), { "index_url": "https://download.pytorch.org/whl/cu130", "extra_options": "", }, ), ( "pip_install", ("torchaudio==2.11.0+cpu",), { "index_url": "https://download.pytorch.org/whl/cpu", "extra_options": "--no-deps", }, ), ] assert image.calls[2][0] == "run_commands" assert "import torch, torchaudio, torchvision" in image.calls[2][1][0] def test_modal_cloud_installs_and_validates_vllm_for_non_b300_gpu( modal_cloud_module: Any, ) -> None: """A lower-cost GPU image should install and import the pinned vLLM wheel.""" class RecordingImage: """Record Modal image package and command layers.""" def __init__(self) -> None: """Initialize an empty call record.""" self.calls: list[tuple[str, tuple[Any, ...], dict[str, Any]]] = [] def pip_install(self, *packages: str, **options: Any) -> "RecordingImage": """Record one package installation layer.""" self.calls.append(("pip_install", packages, options)) return self def run_commands(self, *commands: str) -> "RecordingImage": """Record one image validation command.""" self.calls.append(("run_commands", commands, {})) return self image = RecordingImage() result = modal_cloud_module._install_remote_accelerator_packages( image, "RTX-PRO-6000", ) assert result is image assert image.calls[0] == ( "pip_install", (modal_cloud_module._remote_accelerator_packages("RTX-PRO-6000")[0],), {}, ) assert image.calls[1][0] == "run_commands" assert "import cv2, numpy, torch, vllm" in image.calls[1][1][0] def test_modal_cloud_missing_prompt_node_class_raises_clear_error( modal_cloud_module: Any, monkeypatch: Any, tmp_path: Path, ) -> None: """Missing custom-node classes should fail before ComfyUI cache setup raises KeyError.""" async def fake_init_external_custom_nodes() -> None: """Leave the missing custom node unregistered.""" fake_nodes_module = types.SimpleNamespace( NODE_CLASS_MAPPINGS={"KnownNode": object}, init_external_custom_nodes=fake_init_external_custom_nodes, ) monkeypatch.setitem(sys.modules, "nodes", fake_nodes_module) custom_nodes_root = tmp_path / "custom_nodes" package_dir = custom_nodes_root / "Skoogeer-Noise" / "src" package_dir.mkdir(parents=True) (package_dir / "qwen_noise_nodes.py").write_text( "class KSamplerLoraSigmaInverse:\n pass\n", encoding="utf-8", ) with pytest.raises(modal_cloud_module.RemoteSubgraphExecutionError) as exc_info: modal_cloud_module._ensure_prompt_node_classes_registered( component_id="component-1", prompt={ "1": {"class_type": "KnownNode", "inputs": {}}, "2": {"class_type": "KSamplerLoraSigmaInverse", "inputs": {}}, }, custom_nodes_root=custom_nodes_root, ) message = str(exc_info.value) assert "KSamplerLoraSigmaInverse" in message assert "custom-node sync is enabled" in message assert "Skoogeer-Noise/src/qwen_noise_nodes.py" in message assert "package=Skoogeer-Noise" in message def test_modal_cloud_existing_app_guard_uses_non_creating_sdk_lookup( modal_cloud_module: Any, ) -> None: """Deploy-time app construction should fail if the configured Modal app already exists.""" class FakeNotFoundError(Exception): """Stand-in for Modal app lookup misses.""" lookup_calls: list[tuple[str, bool]] = [] class FakeApp: """Minimal Modal App namespace with non-creating lookup support.""" @staticmethod def lookup(app_name: str, create_if_missing: bool = True) -> object: """Record lookup arguments and return an existing app.""" lookup_calls.append((app_name, create_if_missing)) return object() fake_modal = types.SimpleNamespace( App=FakeApp, exception=types.SimpleNamespace(NotFoundError=FakeNotFoundError), is_local=lambda: True, ) settings = types.SimpleNamespace(app_name="comfy-modal-sync") with pytest.raises(modal_cloud_module.ExistingModalAppError) as exc_info: modal_cloud_module._guard_against_existing_modal_app(settings, fake_modal) assert lookup_calls == [(DEFAULT_TEST_DEPLOYMENT_APP_NAME, False)] assert "Delete the existing app" in str(exc_info.value) assert "COMFY_MODAL_GPU" in str(exc_info.value) def test_modal_cloud_existing_app_guard_allows_missing_sdk_lookup( modal_cloud_module: Any, ) -> None: """A missing Modal app should not block first-run app construction.""" class FakeNotFoundError(Exception): """Stand-in for Modal app lookup misses.""" class FakeApp: """Minimal Modal App namespace that reports a missing app.""" @staticmethod def lookup(app_name: str, create_if_missing: bool = True) -> object: """Raise the SDK's not-found error without creating the app.""" del app_name, create_if_missing raise FakeNotFoundError("app not found") fake_modal = types.SimpleNamespace( App=FakeApp, exception=types.SimpleNamespace(NotFoundError=FakeNotFoundError), is_local=lambda: True, ) modal_cloud_module._guard_against_existing_modal_app( types.SimpleNamespace(app_name="comfy-modal-sync"), fake_modal, ) def test_modal_cloud_existing_app_guard_falls_back_to_cli_json( modal_cloud_module: Any, cloud_app_guard_module: Any, monkeypatch: Any, ) -> None: """SDKs without a non-creating lookup should fall back to `modal app list --json`.""" class FakeApp: """Modal App namespace whose lookup signature cannot safely check existence.""" @staticmethod def lookup(app_name: str) -> object: """This must not be called because it may create the app.""" raise AssertionError(f"unsafe lookup called for {app_name}") completed = subprocess.CompletedProcess( args=["modal", "app", "list", "--json"], returncode=0, stdout=json.dumps([{"name": "other"}, {"name": DEFAULT_TEST_DEPLOYMENT_APP_NAME}]), stderr="", ) observed_commands: list[list[str]] = [] def fake_run(command: list[str], **kwargs: Any) -> subprocess.CompletedProcess[str]: """Return a deterministic Modal CLI app list response.""" del kwargs observed_commands.append(command) return completed monkeypatch.setattr( cloud_app_guard_module.shutil, "which", lambda name: f"/usr/bin/{name}" ) monkeypatch.setattr(cloud_app_guard_module.subprocess, "run", fake_run) fake_modal = types.SimpleNamespace(App=FakeApp, exception=types.SimpleNamespace(), is_local=lambda: True) with pytest.raises(modal_cloud_module.ExistingModalAppError): modal_cloud_module._guard_against_existing_modal_app( types.SimpleNamespace(app_name="comfy-modal-sync"), fake_modal, ) assert observed_commands == [["/usr/bin/modal", "app", "list", "--json"]] def test_modal_cloud_llm_compile_profiles_are_limited_to_executable_subgraph( modal_cloud_module: Any, ) -> None: """Nested metadata and disconnected LLM nodes must not trigger cache commits.""" payload = { "payload_kind": "subgraph", "execute_node_ids": ["3"], "subgraph_prompt": { "1": { "class_type": "ModalLLM", "inputs": {"model_profile": "executed-profile"}, }, "2": { "class_type": "ModalLLM", "inputs": {"model_profile": "disconnected-profile"}, }, "3": { "class_type": "PreviewText", "inputs": {"text": ["1", 0]}, }, }, "boundary_inputs": [ { "source_signature": { "class_type": "ModalLLM", "inputs": {"model_profile": "metadata-profile"}, } } ], } assert modal_cloud_module._llm_profiles_in_payload(payload) == ( "executed-profile", ) assert ( modal_cloud_module._llm_profiles_in_payload( {**payload, "payload_kind": "canary"} ) == () ) def test_load_modal_cloud_module_reloads_stale_partial_module( remote_modal_app_module: Any, modal_deployment_module: Any, monkeypatch: Any, ) -> None: """Stale partially imported cloud modules should be discarded and reloaded.""" original_module = sys.modules.get(modal_deployment_module._MODAL_CLOUD_MODULE_NAME) stale_module = types.SimpleNamespace(app=None) sys.modules[modal_deployment_module._MODAL_CLOUD_MODULE_NAME] = stale_module loaded_module = types.SimpleNamespace(app="fresh-app") class FakeLoader: """Populate the fresh replacement module during exec.""" def create_module(self, spec: Any) -> None: """Use the default module creation path.""" del spec return None def exec_module(self, module: Any) -> None: """Install the expected deployable app onto the reloaded module.""" module.app = loaded_module.app monkeypatch.setattr( modal_deployment_module.importlib.util, "spec_from_file_location", lambda *args, **kwargs: importlib.util.spec_from_loader( modal_deployment_module._MODAL_CLOUD_MODULE_NAME, FakeLoader(), ), ) try: reloaded_module = modal_deployment_module._load_modal_cloud_module() finally: sys.modules.pop(modal_deployment_module._MODAL_CLOUD_MODULE_NAME, None) if original_module is not None: sys.modules[modal_deployment_module._MODAL_CLOUD_MODULE_NAME] = original_module assert reloaded_module is not stale_module assert getattr(reloaded_module, "app", None) == "fresh-app" def test_load_modal_cloud_module_reloads_for_workflow_gpu_change( remote_modal_app_module: Any, modal_deployment_module: Any, monkeypatch: Any, ) -> None: """Cloud app construction should receive the workflow-selected GPU settings.""" module_name = modal_deployment_module._MODAL_CLOUD_MODULE_NAME original_module = sys.modules.get(module_name) sys.modules[module_name] = types.SimpleNamespace( app="a100-app", __comfy_modal_gpu__="A100", ) observed_gpu_values: list[str] = [] class FakeLoader: """Populate a replacement cloud module from its injected settings.""" def create_module(self, spec: Any) -> None: """Use the default module creation path.""" del spec return None def exec_module(self, module: Any) -> None: """Capture the settings override and expose a deployable app.""" settings_override = module.__comfy_modal_settings_override__ observed_gpu_values.append(settings_override.modal_gpu) module.app = "b300-app" monkeypatch.setattr( modal_deployment_module.importlib.util, "spec_from_file_location", lambda *args, **kwargs: importlib.util.spec_from_loader(module_name, FakeLoader()), ) settings = remote_modal_app_module.settings_for_modal_gpu( remote_modal_app_module.get_settings(), "B300", ) try: with modal_deployment_module._modal_cloud_settings_override(settings): reloaded_module = modal_deployment_module._load_modal_cloud_module() finally: sys.modules.pop(module_name, None) if original_module is not None: sys.modules[module_name] = original_module assert observed_gpu_values == ["B300"] assert reloaded_module.app == "b300-app" assert reloaded_module.__comfy_modal_gpu__ == "B300" assert reloaded_module.__comfy_modal_app_name__ == "comfy-modal-sync-gpu-b300" def test_load_modal_cloud_module_reloads_for_secret_name_change( remote_modal_app_module: Any, modal_deployment_module: Any, monkeypatch: Any, ) -> None: """Changing only the secret name should rebuild the cloud module with new settings.""" module_name = modal_deployment_module._MODAL_CLOUD_MODULE_NAME original_module = sys.modules.get(module_name) base_settings = remote_modal_app_module.get_settings() sys.modules[module_name] = types.SimpleNamespace( app="old-secret-app", __comfy_modal_gpu__=base_settings.modal_gpu, __comfy_modal_app_name__=remote_modal_app_module.modal_deployment_app_name( base_settings ), __comfy_modal_secret_name__="comfy", ) observed_secret_names: list[str] = [] class FakeLoader: """Populate a replacement cloud module from its injected settings.""" def create_module(self, spec: Any) -> None: """Use the default module creation path.""" del spec return None def exec_module(self, module: Any) -> None: """Capture the secret setting and expose a deployable app.""" settings_override = module.__comfy_modal_settings_override__ observed_secret_names.append(settings_override.modal_secret_name) module.app = "new-secret-app" monkeypatch.setattr( modal_deployment_module.importlib.util, "spec_from_file_location", lambda *args, **kwargs: importlib.util.spec_from_loader(module_name, FakeLoader()), ) new_settings = replace(base_settings, modal_secret_name="workflow-credentials") try: with modal_deployment_module._modal_cloud_settings_override(new_settings): reloaded_module = modal_deployment_module._load_modal_cloud_module() finally: sys.modules.pop(module_name, None) if original_module is not None: sys.modules[module_name] = original_module assert observed_secret_names == ["workflow-credentials"] assert reloaded_module.app == "new-secret-app" assert reloaded_module.__comfy_modal_secret_name__ == "workflow-credentials" def test_remote_modal_installs_cloud_exception_compatibility_module( remote_modal_app_module: Any, modal_deployment_module: Any, ) -> None: """Deployed remote exceptions should have importable local definitions.""" module_name = modal_deployment_module._MODAL_CLOUD_MODULE_NAME original_module = sys.modules.pop(module_name, None) serialization_module = types.ModuleType(module_name) serialization_module.RemoteSubgraphExecutionError = type( "RemoteSubgraphExecutionError", (RuntimeError,), {"__module__": module_name}, ) sys.modules[module_name] = serialization_module serialized_error = pickle.dumps( serialization_module.RemoteSubgraphExecutionError("remote failure") ) sys.modules.pop(module_name) try: modal_deployment_module._install_modal_cloud_exception_compatibility_module() compatibility_module = sys.modules[module_name] assert ( compatibility_module.RemoteSubgraphExecutionError is remote_modal_app_module.RemoteSubgraphExecutionError ) assert issubclass(compatibility_module.RemoteInvocationAbandonedError, RuntimeError) restored_error = pickle.loads(serialized_error) assert isinstance(restored_error, remote_modal_app_module.RemoteSubgraphExecutionError) assert str(restored_error) == "remote failure" finally: sys.modules.pop(module_name, None) if original_module is not None: sys.modules[module_name] = original_module def test_load_modal_cloud_module_clears_failed_import_from_sys_modules( remote_modal_app_module: Any, modal_deployment_module: Any, monkeypatch: Any, ) -> None: """Failed cloud-module imports should not leave a poisoned cache entry behind.""" original_module = sys.modules.pop( modal_deployment_module._MODAL_CLOUD_MODULE_NAME, None, ) class FakeLoader: """Raise during module execution to simulate a partial import failure.""" def create_module(self, spec: Any) -> None: """Use the default module creation path.""" del spec return None def exec_module(self, module: Any) -> None: """Fail while the module is being initialized.""" module.app = None raise RuntimeError("boom") monkeypatch.setattr( modal_deployment_module.importlib.util, "spec_from_file_location", lambda *args, **kwargs: importlib.util.spec_from_loader( modal_deployment_module._MODAL_CLOUD_MODULE_NAME, FakeLoader(), ), ) try: with pytest.raises(RuntimeError, match="boom"): modal_deployment_module._load_modal_cloud_module() assert modal_deployment_module._MODAL_CLOUD_MODULE_NAME not in sys.modules finally: sys.modules.pop(modal_deployment_module._MODAL_CLOUD_MODULE_NAME, None) if original_module is not None: sys.modules[modal_deployment_module._MODAL_CLOUD_MODULE_NAME] = original_module def test_modal_cloud_tracing_prompt_server_emits_executed_outputs( modal_cloud_module: Any, ) -> None: """The cloud tracing prompt server should stream node UI outputs as executed events.""" observed_updates: list[dict[str, Any]] = [] server = modal_cloud_module._TracingPromptServer( "component-1", {"7": {"class_type": "PreviewImage", "inputs": {}}}, status_callback=observed_updates.append, ) server.send_sync( "executed", { "node": "7", "display_node": "7", "output": {"images": [{"filename": "preview.png"}]}, }, None, ) assert observed_updates == [ { "event_type": "executed", "node_id": "7", "display_node_id": "7", "output": {"images": [{"filename": "preview.png"}]}, } ] def test_modal_cloud_tracing_prompt_server_emits_boundary_image_outputs( modal_cloud_module: Any, ) -> None: """The cloud tracing prompt server should stream configured boundary IMAGE outputs once cached.""" torch = pytest.importorskip("torch") image_tensor = torch.zeros((1, 4, 4, 3), dtype=torch.float32) observed_updates: list[dict[str, Any]] = [] server = modal_cloud_module._TracingPromptServer( "component-1", { "7": {"class_type": "VAEDecode", "inputs": {}}, "8": {"class_type": "OtherNode", "inputs": {}}, }, status_callback=observed_updates.append, ) cache_entries = { "7": types.SimpleNamespace(outputs=[image_tensor]), } server.configure_boundary_output_stream( boundary_outputs=[ { "node_id": "7", "output_index": 0, "io_type": "IMAGE", "is_list": False, "preview_target_node_ids": ["9"], } ], lookup_cache_entry=lambda node_id: cache_entries.get(node_id), ) server.send_sync("executing", {"node": "7"}, None) server.send_sync("executing", {"node": "8"}, None) assert observed_updates[0]["phase"] == "executing" assert observed_updates[1] == { "event_type": "boundary_output", "node_id": "7", "output_index": 0, "io_type": "IMAGE", "is_list": False, "preview_target_node_ids": ["9"], "value": image_tensor, } assert observed_updates[2]["phase"] == "executing" @pytest.mark.parametrize( ("module_fixture_name",), [ ("local_execution_module",), ("modal_cloud_module",), ], ) def test_trim_subgraph_payload_to_required_nodes_drops_stale_execute_targets( request: Any, module_fixture_name: str, ) -> None: """Trimmed subgraph payloads should ignore execute targets that are absent from the current prompt.""" target_module = request.getfixturevalue(module_fixture_name) payload = { "component_id": "1::static", "component_node_ids": ["1", "3"], "subgraph_prompt": { "1": {"class_type": "LoadDiffusionModel", "inputs": {}}, "3": {"class_type": "KSampler", "inputs": {"model": ["1", 0], "steps": 20}}, }, "boundary_inputs": [], "boundary_outputs": [ {"node_id": "3", "output_index": 0, "io_type": "LATENT", "is_list": False}, ], "execute_node_ids": ["3", "5"], "mapped_execute_node_ids": [], "static_execute_node_ids": ["3", "5"], } trimmed_payload = target_module._trim_subgraph_payload_to_required_nodes(payload) assert trimmed_payload["component_node_ids"] == ["1", "3"] assert list(trimmed_payload["subgraph_prompt"].keys()) == ["1", "3"] assert trimmed_payload["execute_node_ids"] == ["3"] assert trimmed_payload["mapped_execute_node_ids"] == [] assert trimmed_payload["static_execute_node_ids"] == ["3"] def test_modal_cloud_uses_current_comfy_explicit_ram_pressure_thresholds( modal_cloud_module: Any, monkeypatch: Any, ) -> None: """Two current cache-ram values should flow into active and inactive thresholds.""" fake_args = types.SimpleNamespace( cache_classic=False, cache_lru=0, cache_ram=[3.0, 20.0], cache_none=False, ) fake_cli_args_module = types.SimpleNamespace(args=fake_args) fake_model_management_module = types.SimpleNamespace(total_ram=64 * 1024) fake_execution_module = types.SimpleNamespace( CacheType=types.SimpleNamespace( CLASSIC="classic", LRU="lru", RAM_PRESSURE="ram-pressure", NONE="none", ) ) monkeypatch.setitem(sys.modules, "comfy.cli_args", fake_cli_args_module) monkeypatch.setitem( sys.modules, "comfy.model_management", fake_model_management_module, ) cache_type, cache_args = modal_cloud_module._prompt_executor_cache_config(fake_execution_module) assert cache_type == "ram-pressure" assert cache_args == {"lru": 0, "ram": 3.0, "ram_inactive": 20.0} def test_modal_cloud_awaits_async_prompt_executor_api( modal_cloud_module: Any, ) -> None: """An asynchronous PromptExecutor compatibility API should finish before cache reads.""" observed_calls: list[tuple[dict[str, Any], str, dict[str, Any], list[str]]] = [] class FakeExecutor: """Minimal executor exposing a coroutine-based execute method.""" async def execute( self, *, prompt: dict[str, Any], prompt_id: str, extra_data: dict[str, Any], execute_outputs: list[str], ) -> None: """Record execution after yielding once.""" await asyncio.sleep(0) observed_calls.append((prompt, prompt_id, extra_data, execute_outputs)) modal_cloud_module._execute_prompt_executor_compat( FakeExecutor(), prompt={"1": {"class_type": "Example", "inputs": {}}}, prompt_id="prompt-1", extra_data={"client_id": "client-1"}, execute_outputs=["1"], ) assert observed_calls == [ ( {"1": {"class_type": "Example", "inputs": {}}}, "prompt-1", {"client_id": "client-1"}, ["1"], ) ] def test_modal_cloud_class_options_do_not_use_deprecated_concurrency_flag( modal_cloud_module: Any, ) -> None: """The deployed Modal class options should avoid deprecated concurrency flags.""" fake_settings = types.SimpleNamespace( modal_gpu="A100", remote_storage_root="/vol/data", scaledown_window_seconds=60, min_containers=0, max_containers=4, buffer_containers=1, execution_timeout_seconds=1800, startup_timeout_seconds=600, enable_memory_snapshot=True, enable_gpu_memory_snapshot=False, ) options = modal_cloud_module._remote_engine_cls_options( fake_settings, vol=object(), image=object(), ) assert "allow_concurrent_inputs" not in options assert options["max_containers"] == 4 assert options["buffer_containers"] == 1 assert options["timeout"] == 1800 assert options["startup_timeout"] == 600 module_source = Path(modal_cloud_module.__file__).read_text(encoding="utf-8") assert "@modal.concurrent(max_inputs=1)" in module_source def test_modal_cloud_registered_execution_preserves_pre_start_interrupt_flags( modal_cloud_module: Any, monkeypatch: Any, ) -> None: """Remote execution registration should not delete a valid cancel flag on entry.""" class FakeInterruptFlags: """Simple Modal Dict double that records cleared keys.""" def __init__(self) -> None: """Initialize captured pop calls.""" self.pop_calls: list[tuple[str, Any]] = [] def pop(self, key: str, default: Any = None) -> Any: """Record one cleared interrupt flag.""" self.pop_calls.append((key, default)) return None interrupt_flags = FakeInterruptFlags() monkeypatch.setattr(_cloud_execution_control_owner(), "modal", object()) monkeypatch.setattr( _cloud_execution_control_owner(), "interrupt_flag_store", lambda: interrupt_flags, ) with modal_cloud_module._registered_remote_execution( {"prompt_id": "prompt-1", "component_id": "component-2"} ) as execution_control: assert execution_control.interrupt_flag_key == "prompt-1:component-2" assert interrupt_flags.pop_calls == [] assert interrupt_flags.pop_calls == [ ("prompt-1:component-2", None), ] def test_modal_cloud_interrupt_monitor_consumes_shared_cancel_flag( modal_cloud_module: Any, monkeypatch: Any, ) -> None: """The remote interrupt monitor should trip when the shared Modal Dict flag appears.""" class FakeInterruptFlags: """Simple Modal Dict double that exposes one shared cancel flag.""" def __init__(self) -> None: """Initialize the backing key set.""" self.keys = {"prompt-1:component-2"} self.contains_calls = 0 self.pop_calls: list[tuple[str, Any]] = [] def contains(self, key: str) -> bool: """Report whether the shared interrupt flag exists.""" self.contains_calls += 1 return key in self.keys def pop(self, key: str, default: Any = None) -> Any: """Remove the shared interrupt flag once consumed.""" self.pop_calls.append((key, default)) self.keys.discard(key) return None interrupt_calls: list[str] = [] cancellation_event = threading.Event() monkeypatch.setitem( sys.modules, "nodes", types.SimpleNamespace(interrupt_processing=lambda: interrupt_calls.append("interrupt")), ) with modal_cloud_module._temporary_remote_interrupt_monitor( "component-2", cancellation_event, interrupt_store=FakeInterruptFlags(), interrupt_flag_key="prompt-1:component-2", ): deadline = time.time() + 1.0 while not interrupt_calls and time.time() < deadline: time.sleep(0.01) assert interrupt_calls == ["interrupt"] assert cancellation_event.is_set() def test_modal_cloud_interrupt_monitor_ignores_modal_client_shutdown( modal_cloud_module: Any, monkeypatch: Any, ) -> None: """The remote interrupt monitor should exit quietly if Modal tears down the client first.""" class FakeClientClosedError(RuntimeError): """Stand-in for `modal.exception.ClientClosed`.""" class FakeInterruptFlags: """Simple Modal Dict double that fails once the client is already shutting down.""" def __init__(self) -> None: """Initialize the poll counter.""" self.contains_calls = 0 self.pop_calls: list[tuple[str, Any]] = [] def contains(self, key: str) -> bool: """Raise the same client-shutdown error Modal emits during teardown.""" del key self.contains_calls += 1 raise FakeClientClosedError("client closed") def pop(self, key: str, default: Any = None) -> Any: """Record unexpected cleanup attempts.""" self.pop_calls.append((key, default)) return None interrupt_calls: list[str] = [] thread_exceptions: list[BaseException] = [] cancellation_event = threading.Event() fake_modal_module = types.ModuleType("modal") fake_modal_exception_module = types.ModuleType("modal.exception") fake_modal_exception_module.ClientClosed = FakeClientClosedError fake_modal_module.exception = fake_modal_exception_module monkeypatch.setitem(sys.modules, "modal", fake_modal_module) monkeypatch.setitem(sys.modules, "modal.exception", fake_modal_exception_module) monkeypatch.setitem( sys.modules, "nodes", types.SimpleNamespace(interrupt_processing=lambda: interrupt_calls.append("interrupt")), ) monkeypatch.setattr( threading, "excepthook", lambda args: thread_exceptions.append(args.exc_value), ) interrupt_store = FakeInterruptFlags() with modal_cloud_module._temporary_remote_interrupt_monitor( "component-2", cancellation_event, interrupt_store=interrupt_store, interrupt_flag_key="prompt-1:component-2", ): deadline = time.time() + 1.0 while interrupt_store.contains_calls == 0 and time.time() < deadline: time.sleep(0.01) assert interrupt_store.contains_calls >= 1 assert interrupt_store.pop_calls == [] assert interrupt_calls == [] assert not cancellation_event.is_set() assert thread_exceptions == [] def test_modal_cloud_serializes_only_small_transport_safe_node_outputs( modal_cloud_module: Any, ) -> None: """Persisted node-cache records should keep small tensor outputs and skip oversized ones.""" import torch execution = modal_cloud_module._load_execution_module() small_entry = execution.CacheEntry(ui=None, outputs=[[torch.zeros((8,), dtype=torch.float32)]]) large_entry = execution.CacheEntry(ui=None, outputs=[[torch.zeros((512,), dtype=torch.float32)]]) small_record = modal_cloud_module._serialize_node_output_cache_entry( small_entry, max_bytes=1024, ) large_record = modal_cloud_module._serialize_node_output_cache_entry( large_entry, max_bytes=1024, ) assert small_record is not None assert large_record is None def test_modal_cloud_materializes_synced_asset_paths( modal_cloud_module: Any, monkeypatch: Any, ) -> None: """Remote asset references should resolve to absolute files under the storage root.""" monkeypatch.setenv("COMFY_MODAL_REMOTE_STORAGE_ROOT", "/storage") modal_cloud_module.get_settings.cache_clear() try: assert modal_cloud_module._materialize_remote_asset_path("/assets/model.safetensors") == ( "/storage/assets/model.safetensors" ) assert modal_cloud_module._rewrite_modal_asset_references( {"clip_name": "/assets/model.safetensors", "nested": ["/assets/other.pt", 3]} ) == { "clip_name": "/storage/assets/model.safetensors", "nested": ["/storage/assets/other.pt", 3], } finally: modal_cloud_module.get_settings.cache_clear() def test_modal_cloud_summarizes_suspicious_wrapped_prompt_inputs( modal_cloud_module: Any, ) -> None: """Remote failure diagnostics should flag remaining singleton-list prompt wrappers.""" prompt = { "12": { "class_type": "ExampleNode", "inputs": { "steps": [20], "latent": ["7", [0]], "ok_link": ["8", 0], }, } } findings = modal_cloud_module._summarize_suspicious_prompt_inputs(prompt) assert findings == [ "12.steps=[20]", "12.latent=['7', [0]]", ] def test_modal_cloud_accepts_absolute_asset_paths_in_folder_lookup( modal_cloud_module: Any, monkeypatch: Any, tmp_path: Path, ) -> None: """Patched folder lookups should return already-materialized absolute asset paths.""" remote_storage_root = tmp_path / "storage" asset_path = remote_storage_root / "assets" / "clip.safetensors" asset_path.parent.mkdir(parents=True) asset_path.write_bytes(b"clip") fake_folder_paths_module = types.SimpleNamespace( get_full_path=lambda folder_name, filename: None, get_full_path_or_raise=lambda folder_name, filename: (_ for _ in ()).throw( FileNotFoundError(filename) ), ) monkeypatch.setitem(sys.modules, "folder_paths", fake_folder_paths_module) monkeypatch.setenv("COMFY_MODAL_REMOTE_STORAGE_ROOT", str(remote_storage_root)) modal_cloud_module.get_settings.cache_clear() try: with modal_cloud_module._patched_folder_paths_absolute_lookup(): resolved = fake_folder_paths_module.get_full_path( "text_encoders", "/assets/clip.safetensors", ) assert resolved == str(asset_path) assert ( fake_folder_paths_module.get_full_path_or_raise( "text_encoders", "/assets/clip.safetensors", ) == str(asset_path) ) finally: modal_cloud_module.get_settings.cache_clear() def test_modal_cloud_preserves_absolute_lookup_until_overlapping_contexts_exit( modal_cloud_module: Any, monkeypatch: Any, tmp_path: Path, ) -> None: """One loader finishing must not remove the lookup patch from another loader.""" asset_path = tmp_path / "unet.safetensors" asset_path.write_bytes(b"unet") def original_get_full_path(folder_name: str, filename: str) -> None: """Represent ComfyUI's original lookup missing the absolute asset.""" del folder_name, filename return None def original_get_full_path_or_raise(folder_name: str, filename: str) -> str: """Represent ComfyUI's original raising lookup.""" del folder_name raise FileNotFoundError(filename) fake_folder_paths_module = types.SimpleNamespace( get_full_path=original_get_full_path, get_full_path_or_raise=original_get_full_path_or_raise, ) monkeypatch.setitem(sys.modules, "folder_paths", fake_folder_paths_module) first_entered = threading.Event() second_entered = threading.Event() release_first = threading.Event() first_exited = threading.Event() release_second = threading.Event() results: list[str | None] = [] def first_loader() -> None: """Enter first and exit while the second lookup context remains active.""" with modal_cloud_module._patched_folder_paths_absolute_lookup(): first_entered.set() assert release_first.wait(timeout=2.0) first_exited.set() def second_loader() -> None: """Resolve an absolute model after the first overlapping loader exits.""" assert first_entered.wait(timeout=2.0) with modal_cloud_module._patched_folder_paths_absolute_lookup(): second_entered.set() assert first_exited.wait(timeout=2.0) results.append( fake_folder_paths_module.get_full_path("diffusion_models", str(asset_path)) ) assert release_second.wait(timeout=2.0) with ThreadPoolExecutor(max_workers=2) as executor: first_future = executor.submit(first_loader) second_future = executor.submit(second_loader) assert second_entered.wait(timeout=2.0) release_first.set() assert first_exited.wait(timeout=2.0) first_future.result(timeout=2.0) release_second.set() second_future.result(timeout=2.0) assert results == [str(asset_path)] assert fake_folder_paths_module.get_full_path is original_get_full_path assert fake_folder_paths_module.get_full_path_or_raise is original_get_full_path_or_raise def test_modal_cloud_aliases_flux_rms_norm_weight_keys_for_model_detection( modal_cloud_module: Any, ) -> None: """Saved Flux models using RMSNorm `.weight` keys should gain `.scale` aliases.""" state_dict = { "model.diffusion_model.double_blocks.0.img_attn.norm.key_norm.weight": object(), "model.diffusion_model.double_blocks.0.img_attn.norm.query_norm.weight": object(), "model.diffusion_model.double_blocks.0.txt_attn.norm.key_norm.weight": object(), "model.diffusion_model.double_blocks.0.txt_attn.norm.query_norm.weight": object(), "model.diffusion_model.img_in.weight": object(), } alias_count = modal_cloud_module._alias_flux_rms_norm_weight_keys(state_dict) assert alias_count == 4 assert ( state_dict["model.diffusion_model.double_blocks.0.img_attn.norm.key_norm.scale"] is state_dict["model.diffusion_model.double_blocks.0.img_attn.norm.key_norm.weight"] ) assert ( state_dict["model.diffusion_model.double_blocks.0.img_attn.norm.query_norm.scale"] is state_dict["model.diffusion_model.double_blocks.0.img_attn.norm.query_norm.weight"] ) assert ( state_dict["model.diffusion_model.double_blocks.0.txt_attn.norm.key_norm.scale"] is state_dict["model.diffusion_model.double_blocks.0.txt_attn.norm.key_norm.weight"] ) assert ( state_dict["model.diffusion_model.double_blocks.0.txt_attn.norm.query_norm.scale"] is state_dict["model.diffusion_model.double_blocks.0.txt_attn.norm.query_norm.weight"] ) assert modal_cloud_module._alias_flux_rms_norm_weight_keys(state_dict) == 0 def test_modal_cloud_model_state_dict_wrapper_forwards_new_comfyui_arguments( modal_cloud_module: Any, monkeypatch: Any, ) -> None: """The compatibility wrapper should preserve evolving ComfyUI loader arguments.""" calls: list[tuple[dict[str, Any], tuple[Any, ...], dict[str, Any]]] = [] def original_loader( state_dict: dict[str, Any], *args: Any, **kwargs: Any, ) -> str: """Record the arguments forwarded by the compatibility wrapper.""" calls.append((state_dict, args, kwargs)) return "loaded" fake_comfy_module = types.ModuleType("comfy") fake_comfy_module.__path__ = [] fake_sd_module = types.ModuleType("comfy.sd") fake_sd_module.load_diffusion_model_state_dict = original_loader fake_comfy_module.sd = fake_sd_module monkeypatch.setitem(sys.modules, "comfy", fake_comfy_module) monkeypatch.setitem(sys.modules, "comfy.sd", fake_sd_module) monkeypatch.setattr( _cloud_comfy_bootstrap_owner(), "_MODEL_STATE_DICT_COMPAT_WRAPPED", False, ) modal_cloud_module._install_model_state_dict_compatibility_wrappers() state_dict = { "model.diffusion_model.double_blocks.0.img_attn.norm.key_norm.weight": object() } result = fake_sd_module.load_diffusion_model_state_dict( state_dict, {"dtype": "default"}, metadata={"source": "test"}, disable_dynamic=True, future_option="preserved", ) assert result == "loaded" assert calls == [ ( state_dict, ({"dtype": "default"},), { "metadata": {"source": "test"}, "disable_dynamic": True, "future_option": "preserved", }, ) ] assert ( state_dict["model.diffusion_model.double_blocks.0.img_attn.norm.key_norm.scale"] is state_dict["model.diffusion_model.double_blocks.0.img_attn.norm.key_norm.weight"] ) def test_modal_cloud_force_imports_comfyui_utils_package( modal_cloud_module: Any, monkeypatch: Any, tmp_path: Path, ) -> None: """ComfyUI's utils package should override a shadowing non-package module.""" package_root = tmp_path / "comfyui" utils_dir = package_root / "utils" utils_dir.mkdir(parents=True) (utils_dir / "__init__.py").write_text("SENTINEL = 'comfy-utils'\n", encoding="utf-8") shadow_module = types.ModuleType("utils") shadow_module.__file__ = str(tmp_path / "utils.py") monkeypatch.setitem(sys.modules, "utils", shadow_module) modal_cloud_module._force_import_package_from_root("utils", package_root) imported_module = sys.modules["utils"] assert getattr(imported_module, "SENTINEL", None) == "comfy-utils" assert list(getattr(imported_module, "__path__", [])) == [str(utils_dir)] def test_local_remote_app_executes_subgraph_payload( remote_modal_app_module: Any, serialization_module: Any, ) -> None: """The local fallback remote app should execute rewritten subgraph payloads.""" payload = remote_modal_app_module.execute_subgraph_locally( payload={ "payload_kind": "subgraph", "component_id": "component-1", "subgraph_prompt": { "remote_1": { "class_type": "BoundarySource", "inputs": {"value": 0}, "_meta": {}, }, "remote_2": { "class_type": "BoundarySink", "inputs": {"value": ["remote_1", 0]}, "_meta": {}, }, }, "boundary_inputs": [ { "proxy_input_name": "remote_input_0", "targets": [{"node_id": "remote_1", "input_name": "value"}], } ], "boundary_outputs": [ { "proxy_output_name": "remote_2_value", "node_id": "remote_2", "output_index": 0, "io_type": "INT", "is_list": False, } ], "execute_node_ids": ["remote_2"], "extra_data": {}, "custom_nodes_bundle": None, }, kwargs_payload='{"remote_input_0": 4}', node_mapping={ "BoundarySource": _BoundarySourceNode, "BoundarySink": _BoundarySinkNode, }, ) outputs = serialization_module.deserialize_node_outputs(payload) assert outputs == (10,) def test_local_remote_app_normalizes_wrapped_subgraph_link_indexes( remote_modal_app_module: Any, serialization_module: Any, ) -> None: """The local fallback runner should canonicalize singleton-list prompt link indexes.""" payload = remote_modal_app_module.execute_subgraph_locally( payload={ "payload_kind": "subgraph", "component_id": "component-1", "subgraph_prompt": { "remote_1": { "class_type": "BoundarySource", "inputs": {"value": 0}, "_meta": {}, }, "remote_2": { "class_type": "BoundarySink", "inputs": {"value": [[["remote_1", [0]]]]}, "_meta": {}, }, }, "boundary_inputs": [ { "proxy_input_name": "remote_input_0", "targets": [{"node_id": "remote_1", "input_name": "value"}], } ], "boundary_outputs": [ { "proxy_output_name": "remote_2_value", "node_id": "remote_2", "output_index": [[0]], "io_type": "INT", "is_list": False, } ], "execute_node_ids": ["remote_2"], "extra_data": {}, "custom_nodes_bundle": None, }, kwargs_payload='{"remote_input_0": 4}', node_mapping={ "BoundarySource": _BoundarySourceNode, "BoundarySink": _BoundarySinkNode, }, ) outputs = serialization_module.deserialize_node_outputs(payload) assert outputs == (10,) @pytest.mark.parametrize( ("module_fixture_name",), [ ("local_execution_module",), ("modal_cloud_module",), ], ) def test_format_prompt_executor_error_payload_includes_node_context( request: Any, module_fixture_name: str, ) -> None: """PromptExecutor failure formatting should surface the failing node and current inputs.""" target_module = request.getfixturevalue(module_fixture_name) message = target_module._format_prompt_executor_error_payload( { "exception_message": "int() argument must be a string, a bytes-like object or a real number, not 'list'", "node_id": "12", "node_type": "KSampler", "current_inputs": [{"input_name": "steps", "value": [4, 5]}], } ) assert "node_id='12'" in message assert "node_type='KSampler'" in message assert "current_inputs=" in message