Files
JasonHoku-ComfyUI-Ultimate-…/conftest.py
T

172 lines
6.5 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
Root-level pytest configuration for standalone testing outside ComfyUI.
Installs ComfyUI stubs and a custom import hook so that the package's
__init__.py (which pytest's Package.setup() always imports) loads cleanly
without a live ComfyUI runtime.
"""
import sys
import types
import os
import importlib
import importlib.abc
import importlib.machinery
# Run immediately at conftest import time (before pytest_configure)
_NODE_ROOT = os.path.dirname(os.path.abspath(__file__))
_PKG_NAME = os.path.basename(_NODE_ROOT) # "ComfyUI-Ultimate-Auto-Sampler-Config-Grid-Testing-Suite"
def _install_stubs():
"""Install all stubs needed to import the package __init__.py cleanly."""
# 1. External deps
for name in [
"torch", "torchvision", "torchaudio",
"folder_paths",
"comfy", "comfy.utils", "comfy.sd", "comfy.model_management",
"nodes", "aiohttp",
]:
if name not in sys.modules:
sys.modules[name] = types.ModuleType(name)
# aiohttp.web
if "aiohttp.web" not in sys.modules:
web = types.ModuleType("aiohttp.web")
web.Response = type("Response", (), {"__init__": lambda s, *a, **kw: None})
web.json_response = lambda *a, **kw: None
web.RouteTableDef = type("RouteTableDef", (), {
"get": lambda s, *a, **kw: (lambda f: f),
"post": lambda s, *a, **kw: (lambda f: f),
"delete": lambda s, *a, **kw: (lambda f: f),
"put": lambda s, *a, **kw: (lambda f: f),
})
sys.modules["aiohttp.web"] = web
sys.modules["aiohttp"].web = web # type: ignore[attr-defined]
# folder_paths.get_output_directory
fp = sys.modules["folder_paths"]
if not hasattr(fp, "get_output_directory"):
fp.get_output_directory = lambda: os.path.join(_NODE_ROOT, "_test_output") # type: ignore
# server.PromptServer
if "server" not in sys.modules:
sys.modules["server"] = types.ModuleType("server")
srv = sys.modules["server"]
if not hasattr(srv, "PromptServer"):
_noop_deco = staticmethod(lambda *a, **kw: (lambda f: f))
_routes = type("_Routes", (), {
"get": _noop_deco, "post": _noop_deco,
"delete": _noop_deco, "put": _noop_deco,
})()
_ps_instance = type("_PS", (), {"routes": _routes})()
srv.PromptServer = type("PromptServer", (), {"instance": _ps_instance}) # type: ignore
# 2. Sub-module stubs (for relative imports in __init__.py)
_subs = {
"sampler_node": {"SamplerGridTester": type("SamplerGridTester", (), {})},
"dashboard_node": {"SamplerConfigDashboardViewer": type("SamplerConfigDashboardViewer", (), {})},
"html_generator": {"get_html_template": lambda *a, **kw: ""},
"config_builder_node": {"UltimateConfigBuilder": type("UltimateConfigBuilder", (), {})},
"json_text_node": {"SmartJSONTextNode": type("SmartJSONTextNode", (), {})},
"metadata_packer": {"pack_metadata_into_image": lambda *a, **kw: None},
"directory_scanner": {
"scan_directory_for_images": lambda *a, **kw: ([], {}),
"MEDIA_EXTENSIONS": set(),
},
"distribution_routes": {},
"upscale_runner": {
"start_upscale_job": lambda *a, **kw: (None, None),
"get_upscale_status": lambda *a, **kw: {},
"cancel_upscale_job": lambda *a, **kw: False,
},
}
for bare, attrs in _subs.items():
fq = f"{_PKG_NAME}.{bare}"
if bare not in sys.modules:
stub = types.ModuleType(bare)
sys.modules[bare] = stub
if fq not in sys.modules:
sys.modules[fq] = sys.modules[bare]
for k, v in attrs.items():
setattr(sys.modules[bare], k, v)
setattr(sys.modules[fq], k, v)
# 3. Pre-register the package itself
if _PKG_NAME not in sys.modules:
pkg_stub = types.ModuleType(_PKG_NAME)
pkg_stub.__path__ = [_NODE_ROOT]
pkg_stub.__package__ = _PKG_NAME
sys.modules[_PKG_NAME] = pkg_stub
# 4. Ensure node root is on sys.path
if _NODE_ROOT not in sys.path:
sys.path.insert(0, _NODE_ROOT)
class _PackageInitLoader(importlib.abc.Loader):
"""
Custom loader for the package __init__.py.
Sets __package__ correctly before executing the module so that relative
imports (from .sampler_node import X) resolve via sys.modules.
"""
def __init__(self, path):
self._path = path
def create_module(self, spec):
return None # use default semantics
def exec_module(self, module):
module.__file__ = self._path
module.__package__ = _PKG_NAME
module.__name__ = _PKG_NAME
module.__path__ = [_NODE_ROOT] # makes relative imports resolve parent pkg
# Point sys.modules to this module object so relative imports find it
sys.modules[_PKG_NAME] = module
with open(self._path, "rb") as fh:
src = fh.read()
code = compile(src, self._path, "exec")
exec(code, module.__dict__) # noqa: S102 – intentional dynamic exec for stub loading
class _PackageInitFinder(importlib.abc.MetaPathFinder):
"""
Intercepts any attempt to import the package __init__.py and routes it
through _PackageInitLoader so relative imports get the correct __package__.
"""
_INIT_PATH = os.path.join(_NODE_ROOT, "__init__.py")
def find_spec(self, fullname, path, target=None):
# In importlib mode, pytest imports __init__.py as module name "__init__"
# (module_name_from_path returns "__init__" for package root __init__.py).
# We intercept that import and also the top-level package name import.
if fullname in ("__init__", _PKG_NAME):
if target is not None:
# target is the path pytest wants to load; check it's our __init__.py
try:
if os.path.normcase(str(target.__spec__.origin)) != os.path.normcase(self._INIT_PATH):
return None
except Exception:
pass
spec = importlib.machinery.ModuleSpec(
fullname,
_PackageInitLoader(self._INIT_PATH),
origin=self._INIT_PATH,
)
spec.submodule_search_locations = [_NODE_ROOT]
return spec
return None
# Install stubs + hook immediately so they're ready before pytest does anything
_install_stubs()
sys.meta_path.insert(0, _PackageInitFinder())
def pytest_configure(config):
"""Re-ensure stubs are in place after config initialises."""
_install_stubs()