Files
wildminder-ComfyUI-Chatterbox/tests/test_init.py
T
WildAi dc29843483 test: add mock-based suite
Vendored tree mocked at collection, so the suite runs
green without perth, diffusers, s3tokenizer or conformer.
Frozen widget snapshot pins node ids and widget order.
2026-10-06 18:36:08 +03:00

265 lines
10 KiB
Python

"""S6.5 -- the repository-root entry point.
A V3 custom-node package has exactly one public name: ``comfy_entrypoint``.
Everything else in ``__init__.py`` is bootstrap -- register the folders, scan the
models, wire the routes -- and has to survive being imported in a process that
has no ComfyUI server, because that is what a headless run and this test suite
look like.
The pytest guard is the other half. pytest imports every ``__init__.py`` it
meets during collection, and imports it *parentless*: the relative imports
below would fail with "attempted relative import with no known parent package"
before a single test ran. Under pytest the module therefore exports nothing and
returns. That is why the functional half of this file re-executes the source
with pytest temporarily out of ``sys.modules``, against fake ``folder_paths`` and
``server`` modules, rather than trusting the guarded import.
"""
from __future__ import annotations
import ast
import importlib.util
import os
import sys
from types import SimpleNamespace
from unittest.mock import MagicMock
import pytest
import conftest
from conftest import PKG_ALIAS, ROOT_DIR
INIT_PATH = os.path.join(ROOT_DIR, "__init__.py")
def read_init_source() -> str:
with open(INIT_PATH, "r", encoding="utf-8") as handle:
return handle.read()
@pytest.fixture(scope="module")
def source() -> str:
return read_init_source()
@pytest.fixture(scope="module")
def tree(source: str) -> ast.Module:
return ast.parse(source, filename=INIT_PATH)
def _assigned_names(tree: ast.Module) -> dict[str, ast.AST]:
"""Every ``NAME = ...`` assignment anywhere in the module, by name."""
assigned: dict[str, ast.AST] = {}
for node in ast.walk(tree):
if isinstance(node, ast.Assign):
for target in node.targets:
if isinstance(target, ast.Name):
assigned[target.id] = node.value
return assigned
def _referenced_names(tree: ast.Module) -> set[str]:
"""Every identifier the module binds or reads, as code.
Deliberately an AST walk rather than a substring search: this file's own
docstrings *name* the artifacts it refuses to declare, so a text scan
reports the documentation as the defect.
"""
names: set[str] = set()
for node in ast.walk(tree):
if isinstance(node, ast.Name):
names.add(node.id)
elif isinstance(node, ast.Attribute):
names.add(node.attr)
return names
def _first_statement(tree: ast.Module) -> ast.stmt:
"""The first *executable* statement, skipping the docstring and imports.
Importing ``sys`` before the guard is fine -- it is the guard's own
subject. What must not precede it is any bootstrap side effect.
"""
for node in tree.body:
if isinstance(node, (ast.Import, ast.ImportFrom)):
continue
if isinstance(node, ast.Expr) and isinstance(node.value, ast.Constant):
continue # the module docstring
return node
raise AssertionError("the entry point has no executable statements")
class TestExports:
def test_all_is_exactly_the_entrypoint(self, tree: ast.Module) -> None:
"""One export. A V1 package exported two mapping dicts instead."""
value = _assigned_names(tree)["__all__"]
exported = [ast.literal_eval(element) for element in value.elts]
assert exported == ["comfy_entrypoint"]
def test_the_v1_mappings_are_gone(self, tree: ast.Module) -> None:
referenced = _referenced_names(tree)
assert "NODE_CLASS_MAPPINGS" not in referenced
assert "NODE_DISPLAY_NAME_MAPPINGS" not in referenced
def test_no_web_directory(self, tree: ast.Module) -> None:
"""The package ships no frontend assets.
VibeVoice deliberately declares none. Pointing ComfyUI at a directory
that does not exist produces a startup warning on every boot and, worse,
makes "we have a web extension" a claim nothing backs up.
"""
referenced = _referenced_names(tree)
assert "WEB_DIRECTORY" not in referenced
assert "web_directory" not in referenced
class TestPytestGuard:
def test_under_pytest_the_module_exports_nothing(self) -> None:
module = sys.modules[PKG_ALIAS]
assert module.__all__ == []
assert not hasattr(module, "comfy_entrypoint"), (
"the entry point ran its bootstrap during collection; under pytest "
"it must return before touching folder_paths or the server"
)
def test_the_guard_is_the_first_thing_the_module_does(self, tree: ast.Module) -> None:
first = _first_statement(tree)
assert isinstance(first, ast.If)
rendered = ast.unparse(first.test)
assert "pytest" in rendered and "sys.modules" in rendered
def test_the_guard_has_an_empty_exports_branch(self, tree: ast.Module) -> None:
first = _first_statement(tree)
assert isinstance(first, ast.If)
guarded = first.body
assert len(guarded) == 1
statement = guarded[0]
assert isinstance(statement, (ast.Assign, ast.AnnAssign))
assert ast.literal_eval(statement.value) == []
class TestBootstrapRunsForReal:
"""Execute the module the way ComfyUI does, minus the server.
pytest is removed from ``sys.modules`` for the duration so the guard opens,
and ``folder_paths`` / ``server`` are replaced by stand-ins so nothing in
this process's real ComfyUI registry is mutated. ``sys.path`` is snapshotted
and restored because the vendor loader appends to it by design.
"""
@pytest.fixture
def loaded(self, monkeypatch):
fake_folder_paths = SimpleNamespace(
models_dir=os.path.join(ROOT_DIR, ".test-models"),
folder_names_and_paths={},
supported_pt_extensions={".safetensors"},
get_folder_paths=lambda name: [],
add_model_folder_path=lambda *a, **k: None,
get_user_directory=lambda: os.path.join(ROOT_DIR, ".test-user"),
)
fake_server = SimpleNamespace(
PromptServer=SimpleNamespace(instance=SimpleNamespace(routes=MagicMock()))
)
monkeypatch.setitem(sys.modules, "folder_paths", fake_folder_paths)
monkeypatch.setitem(sys.modules, "server", fake_server)
# The guard keys off pytest being importable; hide it for this exec only.
monkeypatch.delitem(sys.modules, "pytest")
spec = importlib.util.spec_from_file_location(
f"{PKG_ALIAS}._entrypoint_probe",
INIT_PATH,
submodule_search_locations=[ROOT_DIR],
)
module = importlib.util.module_from_spec(spec)
monkeypatch.setitem(sys.modules, spec.name, module)
path_before = list(sys.path)
try:
spec.loader.exec_module(module)
yield module, fake_folder_paths, fake_server
finally:
for entry in list(sys.path):
if entry not in path_before:
sys.path.remove(entry)
def test_the_entrypoint_is_exported(self, loaded) -> None:
module, _folder_paths, _server = loaded
assert module.__all__ == ["comfy_entrypoint"]
assert callable(module.comfy_entrypoint)
def test_folders_are_registered(self, loaded) -> None:
_module, folder_paths_module, _server = loaded
registered = folder_paths_module.folder_names_and_paths
assert "tts" in registered
assert "chatterbox" in registered
def test_the_chatterbox_root_is_under_tts(self, loaded) -> None:
_module, folder_paths_module, _server = loaded
registered = folder_paths_module.folder_names_and_paths
assert registered["tts"][0][-1].endswith(os.path.join("tts"))
def test_routes_are_registered_when_a_server_exists(self, loaded) -> None:
_module, _folder_paths, server = loaded
assert server.PromptServer.instance.routes is not None
def test_module_source_does_not_mutate_sys_modules(self, source: str) -> None:
assert "sys.modules[" not in source
assert "sys.path.insert" not in source
class TestAbsentServerDegrades:
"""A process with no ``PromptServer`` still has to import cleanly."""
@pytest.fixture
def loaded_without_server(self, monkeypatch):
monkeypatch.setitem(sys.modules, "folder_paths", SimpleNamespace(
models_dir=os.path.join(ROOT_DIR, ".test-models"),
folder_names_and_paths={},
supported_pt_extensions={".safetensors"},
get_folder_paths=lambda name: [],
add_model_folder_path=lambda *a, **k: None,
get_user_directory=lambda: os.path.join(ROOT_DIR, ".test-user"),
))
monkeypatch.setitem(sys.modules, "server", None)
monkeypatch.delitem(sys.modules, "pytest")
spec = importlib.util.spec_from_file_location(
f"{PKG_ALIAS}._entrypoint_probe_noserver",
INIT_PATH,
submodule_search_locations=[ROOT_DIR],
)
module = importlib.util.module_from_spec(spec)
monkeypatch.setitem(sys.modules, spec.name, module)
path_before = list(sys.path)
try:
spec.loader.exec_module(module)
yield module
finally:
for entry in list(sys.path):
if entry not in path_before:
sys.path.remove(entry)
def test_the_entrypoint_is_still_exported(self, loaded_without_server) -> None:
module = loaded_without_server
assert module.__all__ == ["comfy_entrypoint"]
assert callable(module.comfy_entrypoint)
class TestSourceHygiene:
def test_the_module_owns_no_logging_handler(self, source: str) -> None:
"""ComfyUI installs its own ``ColoredFormatter`` on the root logger."""
for forbidden in (
"addHandler",
"setLevel",
"logging.Formatter",
"logging.StreamHandler",
"logging.Logger(",
"LoggerAdapter",
):
assert forbidden not in source, (
f"{forbidden} is a custom-logging artifact; the package owns "
f"only its message prefix"
)
def test_the_module_is_reachable_as_the_alias(self) -> None:
assert conftest.PKG_ALIAS in sys.modules
assert sys.modules[PKG_ALIAS].__file__ in (INIT_PATH, os.path.abspath(INIT_PATH))