Files
JasonHoku-ComfyUI-Ultimate-…/conftest.py
T
Jason HokuandClaude Opus 4.7 d07657c85b refactor: extract state_to_configs_json as single transformer
Eliminates the JS<->Python drift bug class by making one Python function
the source of truth for state -> configs_json. Both the runtime
(generate_config) and the new preview endpoint (next commit) call it.
Adds golden test for the lora-strength-arrays bracket case that just
broke (regression guard).

Also loads the real config_builder_node module in conftest.py (instead
of a bare stub) so that tests against UltimateConfigBuilder methods work
correctly. Stubs network_utils before loading to satisfy the relative
import.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 09:44:12 -07:00

205 lines
8.3 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
import importlib.util
# 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)
# NOTE: config_builder_node is intentionally excluded from _subs so that
# the real module (with UltimateConfigBuilder.state_to_configs_json etc.)
# is loaded below via importlib. The stubs here are only for __init__.py's
# other relative imports that don't need to be tested directly.
_subs = {
"sampler_node": {"SamplerGridTester": type("SamplerGridTester", (), {})},
"dashboard_node": {"SamplerConfigDashboardViewer": type("SamplerConfigDashboardViewer", (), {})},
"html_generator": {"get_html_template": lambda *a, **kw: ""},
"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)
# 2b. Stub network_utils (used by config_builder_node via relative import)
for _nu_name in ("network_utils", f"{_PKG_NAME}.network_utils"):
if _nu_name not in sys.modules:
_nu = types.ModuleType(_nu_name)
sys.modules[_nu_name] = _nu
if not hasattr(sys.modules[_nu_name], "civitai_fetch_by_hash"):
sys.modules[_nu_name].civitai_fetch_by_hash = lambda *a, **kw: None
# 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)
# 5. Load the real config_builder_node module (with package context so that
# relative imports like `from .network_utils import ...` resolve correctly).
# This must happen after all stubs are registered (steps 1-4) so that the
# module-level decorator calls (server.PromptServer.instance.routes.post/get)
# and relative imports in config_builder_node.py succeed.
if "config_builder_node" not in sys.modules:
_cb_path = os.path.join(_NODE_ROOT, "config_builder_node.py")
_cb_fq = f"{_PKG_NAME}.config_builder_node"
_cb_spec = importlib.util.spec_from_file_location(
_cb_fq, _cb_path, submodule_search_locations=[]
)
_cb_spec.submodule_search_locations = None
_cb_mod = importlib.util.module_from_spec(_cb_spec)
_cb_mod.__package__ = _PKG_NAME
sys.modules[_cb_fq] = _cb_mod
sys.modules["config_builder_node"] = _cb_mod
_cb_spec.loader.exec_module(_cb_mod)
# Also wire as package attribute so __init__.py's relative import finds it
if _PKG_NAME in sys.modules:
setattr(sys.modules[_PKG_NAME], "config_builder_node", _cb_mod)
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()