mirror of
https://github.com/comfyanonymous/ComfyUI.git
synced 2026-04-01 22:44:14 +08:00
181 lines
5.5 KiB
Python
181 lines
5.5 KiB
Python
from __future__ import annotations
|
|
|
|
import json
|
|
import os
|
|
import shutil
|
|
import subprocess
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
import nodes
|
|
from tests.isolation.stage_internal_probe_node import (
|
|
PROBE_NODE_NAME,
|
|
stage_probe_node,
|
|
staged_probe_node,
|
|
)
|
|
|
|
|
|
COMFYUI_ROOT = Path(__file__).resolve().parents[2]
|
|
ISOLATION_ROOT = COMFYUI_ROOT / "tests" / "isolation"
|
|
PROBE_SOURCE_ROOT = ISOLATION_ROOT / "internal_probe_node"
|
|
EXPECTED_NODE_IDS = [
|
|
"InternalIsolationProbeAudio",
|
|
"InternalIsolationProbeImage",
|
|
"InternalIsolationProbeUI3D",
|
|
]
|
|
|
|
CLIENT_SCRIPT = """
|
|
import importlib.util
|
|
import json
|
|
import os
|
|
import sys
|
|
|
|
import pyisolate._internal.client # noqa: F401 # triggers snapshot bootstrap
|
|
|
|
module_path = os.environ["PYISOLATE_MODULE_PATH"]
|
|
spec = importlib.util.spec_from_file_location(
|
|
"internal_probe_node",
|
|
os.path.join(module_path, "__init__.py"),
|
|
submodule_search_locations=[module_path],
|
|
)
|
|
module = importlib.util.module_from_spec(spec)
|
|
assert spec is not None
|
|
assert spec.loader is not None
|
|
sys.modules["internal_probe_node"] = module
|
|
spec.loader.exec_module(module)
|
|
print(
|
|
json.dumps(
|
|
{
|
|
"sys_path": list(sys.path),
|
|
"module_path": module_path,
|
|
"node_ids": sorted(module.NODE_CLASS_MAPPINGS.keys()),
|
|
}
|
|
)
|
|
)
|
|
"""
|
|
|
|
|
|
def _run_client_process(env: dict[str, str]) -> dict:
|
|
pythonpath_parts = [str(COMFYUI_ROOT)]
|
|
existing = env.get("PYTHONPATH", "")
|
|
if existing:
|
|
pythonpath_parts.append(existing)
|
|
env["PYTHONPATH"] = ":".join(pythonpath_parts)
|
|
|
|
result = subprocess.run( # noqa: S603
|
|
[sys.executable, "-c", CLIENT_SCRIPT],
|
|
capture_output=True,
|
|
text=True,
|
|
env=env,
|
|
check=True,
|
|
)
|
|
return json.loads(result.stdout.strip().splitlines()[-1])
|
|
|
|
|
|
@pytest.fixture()
|
|
def staged_probe_module(tmp_path: Path) -> tuple[Path, Path]:
|
|
staged_comfy_root = tmp_path / "ComfyUI"
|
|
module_path = staged_comfy_root / "custom_nodes" / "InternalIsolationProbeNode"
|
|
shutil.copytree(PROBE_SOURCE_ROOT, module_path)
|
|
return staged_comfy_root, module_path
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_staged_probe_node_discovered(staged_probe_module: tuple[Path, Path]) -> None:
|
|
_, module_path = staged_probe_module
|
|
class_mappings_snapshot = dict(nodes.NODE_CLASS_MAPPINGS)
|
|
display_name_snapshot = dict(nodes.NODE_DISPLAY_NAME_MAPPINGS)
|
|
loaded_module_dirs_snapshot = dict(nodes.LOADED_MODULE_DIRS)
|
|
|
|
try:
|
|
ignore = set(nodes.NODE_CLASS_MAPPINGS.keys())
|
|
loaded = await nodes.load_custom_node(
|
|
str(module_path), ignore=ignore, module_parent="custom_nodes"
|
|
)
|
|
|
|
assert loaded is True
|
|
assert nodes.LOADED_MODULE_DIRS["InternalIsolationProbeNode"] == str(
|
|
module_path.resolve()
|
|
)
|
|
|
|
for node_id in EXPECTED_NODE_IDS:
|
|
assert node_id in nodes.NODE_CLASS_MAPPINGS
|
|
node_cls = nodes.NODE_CLASS_MAPPINGS[node_id]
|
|
assert (
|
|
getattr(node_cls, "RELATIVE_PYTHON_MODULE", None)
|
|
== "custom_nodes.InternalIsolationProbeNode"
|
|
)
|
|
finally:
|
|
nodes.NODE_CLASS_MAPPINGS.clear()
|
|
nodes.NODE_CLASS_MAPPINGS.update(class_mappings_snapshot)
|
|
nodes.NODE_DISPLAY_NAME_MAPPINGS.clear()
|
|
nodes.NODE_DISPLAY_NAME_MAPPINGS.update(display_name_snapshot)
|
|
nodes.LOADED_MODULE_DIRS.clear()
|
|
nodes.LOADED_MODULE_DIRS.update(loaded_module_dirs_snapshot)
|
|
|
|
|
|
def test_staged_probe_node_module_path_is_valid_for_child_bootstrap(
|
|
tmp_path: Path, staged_probe_module: tuple[Path, Path]
|
|
) -> None:
|
|
staged_comfy_root, module_path = staged_probe_module
|
|
snapshot = {
|
|
"sys_path": [str(COMFYUI_ROOT), "/host/lib1", "/host/lib2"],
|
|
"sys_executable": sys.executable,
|
|
"sys_prefix": sys.prefix,
|
|
"environment": {},
|
|
}
|
|
snapshot_path = tmp_path / "snapshot.json"
|
|
snapshot_path.write_text(json.dumps(snapshot), encoding="utf-8")
|
|
|
|
env = os.environ.copy()
|
|
env.update(
|
|
{
|
|
"PYISOLATE_CHILD": "1",
|
|
"PYISOLATE_HOST_SNAPSHOT": str(snapshot_path),
|
|
"PYISOLATE_MODULE_PATH": str(module_path),
|
|
}
|
|
)
|
|
|
|
payload = _run_client_process(env)
|
|
|
|
assert payload["module_path"] == str(module_path)
|
|
assert payload["node_ids"] == EXPECTED_NODE_IDS
|
|
assert str(COMFYUI_ROOT) in payload["sys_path"]
|
|
assert str(staged_comfy_root) not in payload["sys_path"]
|
|
|
|
|
|
def test_stage_probe_node_stages_only_under_explicit_root(tmp_path: Path) -> None:
|
|
comfy_root = tmp_path / "sandbox-root"
|
|
|
|
module_path = stage_probe_node(comfy_root)
|
|
|
|
assert module_path == comfy_root / "custom_nodes" / PROBE_NODE_NAME
|
|
assert module_path.is_dir()
|
|
assert (module_path / "__init__.py").is_file()
|
|
assert (module_path / "probe_nodes.py").is_file()
|
|
assert (module_path / "pyproject.toml").is_file()
|
|
|
|
|
|
def test_staged_probe_node_context_cleans_up_temp_root() -> None:
|
|
with staged_probe_node() as module_path:
|
|
staging_root = module_path.parents[1]
|
|
assert module_path.name == PROBE_NODE_NAME
|
|
assert module_path.is_dir()
|
|
assert staging_root.is_dir()
|
|
|
|
assert not staging_root.exists()
|
|
|
|
|
|
def test_stage_script_requires_explicit_target_root() -> None:
|
|
result = subprocess.run( # noqa: S603
|
|
[sys.executable, str(ISOLATION_ROOT / "stage_internal_probe_node.py")],
|
|
capture_output=True,
|
|
text=True,
|
|
check=False,
|
|
)
|
|
|
|
assert result.returncode != 0
|
|
assert "--target-root" in result.stderr
|