Files
wildminder-ComfyUI-KEEP/tests/test_structure.py
T
WildAi aba4cfa739 Round 13: implement keep-fix5 (Phases 1-4; 4.2 skipped as optional)
Implements .dev/docs/keep-fix5-implement1.txt against the 9e6c50f tree:

Phase 1 - the paste pipeline: the face upscaler joins the batched core
(the _batched_paste/_batched_paste_multiface aborts are deleted), mode
is forced chunked with a bg model, face_upscale_model threads through
the paste chunk bodies (parse masks stay on the 512 faces, the ROI
grid_sample warps the upscaled faces), upscale_faces_batch_gpu (B=1,
FP16 autocast on CUDA), and _tiled_scale_and_resize_batch replaces
_tiled_scale_batch + the round-12 bg grouping (B=1 FP16 loop, one
bicubic-antialias resize straight to the paste canvas; the no-model
branch keeps _resize_bg_frames). bg_chunk_size and its pins are removed
as dead machinery. parse_soft_masks and _run_upscaler run under
autocast(enabled=(device.type == 'cuda')).

Phase 2 - facelib: RetinaFace caches priors by (h, w, device) and
detect_faces filters by confidence and runs torchvision.ops.nms on the
device before the single CPU transfer (output format preserved);
read_image gains check_gray=True, only the three sequence call sites
pass False.

Phase 3 - archs: SDPA in CrossAttention._attention, AttnBlock and both
gmflow attention forms; get_flow pinned to float32 with autocast
disabled; pos_emb cast to query_emb.dtype; keep_net under autocast at
both sequence call sites.

Phase 4 - the vendored basicsr pruning: data/, losses/, models/,
train.py, test.py, 23 unused archs, 7 unused utils deleted (78 files);
DCNv2Pack removed from arch_util; the three __init__.py rewrites;
enumeration manifests regenerated. metrics/ and ops/ stay on disk
byte-identical, off the import path. 4.2 skipped (explicitly optional).

Autocast is CUDA-only everywhere, so the CPU gate keeps exercising the
FP32 paths; no model is loaded. Gate: 556 passed, 0 failed (embedded
python; 557 -> 556 = the dead bg_chunk_size pins out, new pins in).
GPU throughput and the FP16/SDPA numerics stay owner-measured.
2026-11-03 20:23:44 +03:00

792 lines
32 KiB
Python

"""P1.3 - structure guards.
The guards are **source-text based**: they never import the package, they read
the files. That keeps them honest while the package is being moved around
(the very thing they guard) and lets them run on a throw-away copy of the
repository.
Five guards, each with the step that flips it from ``xfail`` to strict:
===== ================================================ ==================
guard what it checks flip owner
===== ================================================ ==================
(a) every class under ``nodes/*.py`` is exported **P3.1** (flipped)
from ``nodes/__init__.py``
(b) ``src/**`` contains no ``io.Schema(`` / **P3.2** (flipped)
``define_schema``
(c) the root ``__init__.py`` exposes exactly **P4.4**
``comfy_entrypoint`` (no V1 mapping names)
(d) no ``sys.path.insert`` / ``sys.path.append`` **P2.5**
anywhere in the package (the vendored tree
included - it was exempt only until P2.5)
(e) no ``pdb`` / ``torch.profiler`` / ``import pip`` **P2.1**
/ ``pip.main`` in the vendored runtime closure
(f) no module-level ``comfy`` / ``comfy_api`` import **P3** (flipped)
in ``src/**`` (the plan's §3 rule: the pack
imports without a ComfyUI server)
(g) no ``torch.cuda.empty_cache()`` in the **P4.6**
runtime closure (the pack's code - entry point,
node layer and ``src/`` - **and** the
vendored tree - the known upstream occurrences
are allowlisted by file and count)
(h) the dead video path (``process_video`` / **P4.7**
``VideoReader`` / ``VideoWriter``) is gone
===== ================================================ ==================
Every guard is ``xfail(strict=False)`` until its owner step lands; the reason
of each xfail names that step, so an xfail can never be orphaned. The
self-tests at the bottom prove that each guard really fails on a deliberately
violating copy - a guard that cannot fail is worthless.
"""
from __future__ import annotations
import ast
import re
import shutil
import sys
from pathlib import Path
import pytest
REPO_ROOT = Path(__file__).resolve().parent.parent
#: The production sources the ``sys.path`` guard (d) looks at. Test code
#: (``tests/``, ``tools/``, ``conftest.py``) is not part of the package and is
#: therefore out of scope - the guard is about the shipped package.
PACKAGE_SOURCE_ROOTS = ("__init__.py", "nodes.py", "nodes", "src", "modules")
#: The production sources of the pack **itself** - the vendored tree
#: (``modules/``) is not part of it. These are the files ComfyUI executes:
#: the root entry point, the node layer (``nodes/``) and ``src/``.
PACKAGE_OWN_SOURCE_ROOTS = ("__init__.py", "nodes.py", "nodes", "src")
#: The patterns of guard (e). They are the ``pdb``/``torch_profiler``/
#: ``import_pip``/``pip_main`` patterns of the P0.3 scanner, so the guard and
#: the scanner never drift apart.
BREAKAGE_PATTERNS = ("pdb", "torch_profiler", "import_pip", "pip_main")
_SYS_PATH_PATTERN = re.compile(r"sys\.path\s*\.\s*(insert|append)\s*\(")
# ---------------------------------------------------------------------------
# helpers
# ---------------------------------------------------------------------------
def _read(path: Path) -> str:
return path.read_text(encoding="utf-8", errors="replace")
def _relative(path: Path, root: Path) -> str:
try:
return path.resolve().relative_to(root.resolve()).as_posix()
except ValueError:
return path.as_posix()
def _package_files(root: Path, roots: tuple[str, ...] = PACKAGE_SOURCE_ROOTS) -> list[Path]:
"""Every production python file of the package (the vendored tree included).
The vendored tree used to be exempt from guard (d): it was a plain folder
on ``sys.path`` until P2.5 package-ized it. Since P2.5 it is a real part
of the package, so the plan's §3 rule ("no ``sys.path`` mutation anywhere
in the package") applies to it as well - the last vendored mutation was
removed in the P2 review fix.
``roots`` selects the trees to walk: the whole package by default, the
pack's own code without the vendored tree for the guards that scan the
vendored part through the import closure instead.
"""
files: list[Path] = []
for entry in roots:
target = root / entry
if target.is_file():
files.append(target)
elif target.is_dir():
files.extend(sorted(target.rglob("*.py")))
return files
def _closure_files(root: Path) -> list[Path]:
"""The static runtime import closure of the package (P0.3 machinery).
``tools/scan_compat.py`` resolves the vendored imports against its own
``DEPS_DIR``; the guard runs against arbitrary roots (the self-test uses a
copy), so the directory is pointed at the root in question.
"""
from tools import scan_compat # noqa: PLC0415 - local import keeps the module light
original_deps_dir = scan_compat.DEPS_DIR
scan_compat.DEPS_DIR = root / "modules" / "deps"
try:
entry_points = [root / rel for rel in scan_compat.ENTRY_POINTS]
return scan_compat.find_import_closure(entry_points)
finally:
scan_compat.DEPS_DIR = original_deps_dir
# ---------------------------------------------------------------------------
# guard (a) - the node classes are exported
# ---------------------------------------------------------------------------
def nodes_export_violations(root: Path) -> list[str]:
"""Every class defined in ``nodes/*.py`` must be exported by ``nodes/__init__.py``."""
nodes_dir = root / "nodes"
if not nodes_dir.is_dir():
return ["nodes/ does not exist yet (P3.1 creates it)"]
init_file = nodes_dir / "__init__.py"
if not init_file.is_file():
return ["nodes/__init__.py does not exist"]
exported: set[str] = set()
tree = ast.parse(_read(init_file), filename=str(init_file))
for node in ast.walk(tree):
if isinstance(node, ast.ImportFrom):
for alias in node.names:
exported.add(alias.asname or alias.name)
elif isinstance(node, ast.Import):
for alias in node.names:
exported.add((alias.asname or alias.name).split(".")[0])
elif isinstance(node, ast.Assign):
for target in node.targets:
if isinstance(target, ast.Name) and target.id == "__all__":
for element in ast.walk(node.value):
if isinstance(element, ast.Constant) and isinstance(element.value, str):
exported.add(element.value)
violations: list[str] = []
for path in sorted(nodes_dir.glob("*.py")):
if path.name == "__init__.py":
continue
module_tree = ast.parse(_read(path), filename=str(path))
for node in module_tree.body:
if isinstance(node, ast.ClassDef) and node.name not in exported:
violations.append(
f"{_relative(path, root)}: class {node.name} is not exported "
f"by nodes/__init__.py"
)
return violations
# ---------------------------------------------------------------------------
# guard (b) - src/ holds no schema
# ---------------------------------------------------------------------------
def src_schema_violations(root: Path) -> list[str]:
"""``src/**`` must not define a node schema (schemas belong to ``nodes/``)."""
src_dir = root / "src"
if not src_dir.is_dir():
return ["src/ does not exist yet (P3.2 creates it)"]
violations: list[str] = []
for path in sorted(src_dir.rglob("*.py")):
text = _read(path)
for number, line in enumerate(text.splitlines(), 1):
if "io.Schema(" in line or "define_schema" in line:
violations.append(
f"{_relative(path, root)}:{number}: schema definition in src/ "
f"({line.strip()})"
)
return violations
# ---------------------------------------------------------------------------
# guard (c) - exactly one entry point
# ---------------------------------------------------------------------------
def entrypoint_violations(root: Path) -> list[str]:
"""The root ``__init__.py`` must expose exactly ``comfy_entrypoint``.
The check is **AST based**: a mention of ``comfy_entrypoint`` in a
docstring is not a definition, and the V1 names are violations whether
they are assigned or imported.
"""
init_file = root / "__init__.py"
if not init_file.is_file():
return ["__init__.py does not exist"]
tree = ast.parse(_read(init_file), filename=str(init_file))
violations: list[str] = []
has_entrypoint = any(
isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef))
and node.name == "comfy_entrypoint"
for node in tree.body
)
if not has_entrypoint:
violations.append("__init__.py does not define comfy_entrypoint")
v1_names = ("NODE_CLASS_MAPPINGS", "NODE_DISPLAY_NAME_MAPPINGS")
for node in tree.body:
if isinstance(node, ast.Assign):
for target in node.targets:
if isinstance(target, ast.Name) and target.id in v1_names:
violations.append(f"__init__.py still exports the V1 name {target.id}")
elif isinstance(node, ast.AnnAssign):
if isinstance(node.target, ast.Name) and node.target.id in v1_names:
violations.append(f"__init__.py still exports the V1 name {node.target.id}")
elif isinstance(node, ast.ImportFrom):
for alias in node.names:
if alias.name in v1_names:
violations.append(f"__init__.py still exports the V1 name {alias.name}")
return violations
# ---------------------------------------------------------------------------
# guard (d) - no sys.path mutation
# ---------------------------------------------------------------------------
def sys_path_violations(root: Path) -> list[str]:
"""No ``sys.path.insert``/``append`` anywhere in the package."""
violations: list[str] = []
for path in _package_files(root):
for number, line in enumerate(_read(path).splitlines(), 1):
if _SYS_PATH_PATTERN.search(line):
violations.append(f"{_relative(path, root)}:{number}: {line.strip()}")
return violations
# ---------------------------------------------------------------------------
# guard (e) - no debug/pip leftovers in the vendored closure
# ---------------------------------------------------------------------------
def vendored_breakage_violations(root: Path) -> list[str]:
"""No ``pdb``/``torch.profiler``/``import pip``/``pip.main`` in the closure."""
from tools import scan_compat # noqa: PLC0415 - local import keeps the module light
violations: list[str] = []
for path in _closure_files(root):
if not path.is_file():
continue
text = _read(path)
for finding in scan_compat.scan_source(text, _relative(path, root)):
if finding["pattern"] in BREAKAGE_PATTERNS:
violations.append(
f"{finding['path']}:{finding['line']}: {finding['pattern']}"
)
return violations
# ---------------------------------------------------------------------------
# guard (f) - no module-level comfy import in src/
# ---------------------------------------------------------------------------
#: The module roots ComfyUI provides. Inside ``src/`` they must only be
#: imported **lazily** (function level), so the pack imports without a
#: ComfyUI server (the plan's §3 import rule).
COMFY_MODULE_ROOTS = ("comfy", "comfy_api")
def _module_level_imports(tree: ast.Module):
"""Every import that runs while the module is imported.
Function and class bodies are **not** module level: an import there is
lazy by definition. The body of a module level ``try`` / ``if`` is.
"""
def walk(node):
for child in ast.iter_child_nodes(node):
if isinstance(child, (ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef)):
continue # a function / class body runs later
if isinstance(child, (ast.Import, ast.ImportFrom)):
yield child
yield from walk(child)
yield from walk(tree)
def _imported_roots(node) -> set[str]:
"""The top level module names an import statement pulls in."""
if isinstance(node, ast.Import):
return {alias.name.split(".")[0] for alias in node.names}
if isinstance(node, ast.ImportFrom):
if node.level: # a relative import - never a comfy import
return set()
module = node.module or ""
return {module.split(".")[0]} if module else set()
return set()
def comfy_import_violations(root: Path) -> list[str]:
"""``src/**`` must not import ``comfy.*`` at module level."""
src_dir = root / "src"
if not src_dir.is_dir():
return ["src/ does not exist yet (P3.2 creates it)"]
violations: list[str] = []
for path in sorted(src_dir.rglob("*.py")):
tree = ast.parse(_read(path), filename=str(path))
for node in _module_level_imports(tree):
roots = _imported_roots(node) & set(COMFY_MODULE_ROOTS)
if roots:
violations.append(
f"{_relative(path, root)}:{node.lineno}: module-level "
f"{sorted(roots)} import"
)
return violations
# ---------------------------------------------------------------------------
# guard (g) - no unconditional torch.cuda.empty_cache in the runtime closure
# ---------------------------------------------------------------------------
#: The pattern of guard (g). ``torch.cuda.empty_cache()`` is unconditional: it
#: releases the cached blocks of **every** device, it fights with the VRAM
#: arbitration of ComfyUI and it crashes without a GPU (the B7 breakage of
#: the plan). The pack releases the VRAM through
#: ``model_management.soft_empty_cache()``.
EMPTY_CACHE_PATTERN = re.compile(r"torch\.cuda\.empty_cache\s*\(")
#: The known occurrences in the **vendored** BasicSR tree. 2026-11-03,
#: keep-fix5-implement1 Phase 4.1: the training-only tree (``models/``, the
#: BasicVSR++ arch, ...) is deleted, so every upstream occurrence is gone
#: with it and the allowlist is **empty** - any occurrence is a violation.
#: The mapping is still **exact** (file *and* count): an entry that gains or
#: loses an occurrence is reported, so the guard cannot go stale and a new
#: occurrence is never silently tolerated.
VENDORED_EMPTY_CACHE_ALLOWLIST: dict[str, int] = {}
def _code_text(path: Path) -> str:
"""The source of a file **without** the comments and the string literals.
The guard greps the **code**: the pack documents the breakage it fixed
(the pattern is named in the docstrings), and a docstring must not be
reported as a call.
"""
import io
import tokenize
source = _read(path)
pieces: list[str] = []
try:
for token in tokenize.generate_tokens(io.StringIO(source).readline):
if token.type in (tokenize.COMMENT, tokenize.STRING):
continue
pieces.append(token.string)
except (tokenize.TokenError, IndentationError, SyntaxError):
return source
return "".join(pieces)
def empty_cache_violations(root: Path) -> list[str]:
"""No ``torch.cuda.empty_cache()`` in the surviving runtime closure.
The scan covers the pack's **own** code - the root entry point, the node
layer (``nodes/**``) and ``src/**`` - **and** the vendored ``modules/deps``
closure (not just ``src/``). The node layer is the code ComfyUI
actually executes, so it is guarded like ``src/`` (P4 review fix). The
vendored occurrences are allowlisted by file and count (see
``VENDORED_EMPTY_CACHE_ALLOWLIST``); every other occurrence is a
violation.
"""
files: dict[str, Path] = {}
for path in _closure_files(root):
if path.is_file():
files[_relative(path, root)] = path
for path in _package_files(root, PACKAGE_OWN_SOURCE_ROOTS):
files[_relative(path, root)] = path
counts: dict[str, int] = {}
for relative, path in files.items():
found = len(EMPTY_CACHE_PATTERN.findall(_code_text(path)))
if found:
counts[relative] = found
violations: list[str] = []
for relative, found in sorted(counts.items()):
allowed = VENDORED_EMPTY_CACHE_ALLOWLIST.get(relative, 0)
if found != allowed:
violations.append(
f"{relative}: {found} occurrence(s) of torch.cuda.empty_cache() "
f"(allowlisted: {allowed})"
)
for relative in sorted(set(VENDORED_EMPTY_CACHE_ALLOWLIST) - set(counts)):
violations.append(
f"{relative}: the allowlist expects "
f"{VENDORED_EMPTY_CACHE_ALLOWLIST[relative]} occurrence(s) of "
f"torch.cuda.empty_cache() but the file has none"
)
return violations
# ---------------------------------------------------------------------------
# guard (h) - the dead video path is gone
# ---------------------------------------------------------------------------
#: The symbols of the deleted video path (P4.7). The vendored ffmpeg shim
#: (``basicsr.utils.video_util``) was deleted in P2.1, so the video path of
#: the processor was dead code - it is gone with P4.7.
DEAD_VIDEO_SYMBOLS = ("process_video", "VideoReader", "VideoWriter")
def dead_video_violations(root: Path) -> list[str]:
"""The dead video path must not appear in the package any more."""
violations: list[str] = []
for path in _package_files(root):
for number, line in enumerate(_read(path).splitlines(), 1):
for symbol in DEAD_VIDEO_SYMBOLS:
if symbol in line:
violations.append(
f"{_relative(path, root)}:{number}: {symbol} ({line.strip()})"
)
return violations
# ---------------------------------------------------------------------------
# the guards (xfail until their owner step lands)
# ---------------------------------------------------------------------------
@pytest.mark.unit
def test_guard_nodes_classes_are_exported():
"""Guard (a) - flipped to strict in P3.1 (its flip owner)."""
assert nodes_export_violations(REPO_ROOT) == []
@pytest.mark.unit
def test_guard_src_has_no_schema():
"""Guard (b) - flipped to strict in P3.2 (its flip owner)."""
assert src_schema_violations(REPO_ROOT) == []
@pytest.mark.unit
def test_guard_entrypoint_is_v3_only():
"""Guard (c) - flipped to strict in P4.4 (its flip owner)."""
assert entrypoint_violations(REPO_ROOT) == []
@pytest.mark.unit
def test_guard_no_sys_path_mutation():
"""Guard (d) - flipped to strict in P2.5 (its flip owner)."""
assert sys_path_violations(REPO_ROOT) == []
@pytest.mark.unit
def test_guard_vendored_tree_has_no_debug_or_pip_leftovers():
"""Guard (e) - flipped to strict in P2.1 (its flip owner)."""
assert vendored_breakage_violations(REPO_ROOT) == []
@pytest.mark.unit
def test_guard_src_has_no_module_level_comfy_import():
"""Guard (f) - flipped to strict in P3 (the plan's §3 import rule)."""
assert comfy_import_violations(REPO_ROOT) == []
@pytest.mark.unit
def test_guard_no_unconditional_empty_cache_in_the_closure():
"""Guard (g) - flipped to strict in P4.6 (its flip owner)."""
assert empty_cache_violations(REPO_ROOT) == []
@pytest.mark.unit
def test_guard_the_dead_video_path_is_gone():
"""Guard (h) - flipped to strict in P4.7 (its flip owner)."""
assert dead_video_violations(REPO_ROOT) == []
# ---------------------------------------------------------------------------
# self-tests: every guard must fail on a deliberately violating copy
# ---------------------------------------------------------------------------
@pytest.fixture
def repo_copy(tmp_path):
"""A throw-away copy of the repository the guards can be run against."""
destination = tmp_path / "repo"
shutil.copytree(
REPO_ROOT,
destination,
ignore=shutil.ignore_patterns(
".git",
".dev",
".codegraph",
".pytest_cache",
".zcode",
"__pycache__",
"*.pyc",
),
)
return destination
@pytest.mark.unit
def test_self_test_guard_a_detects_an_unexported_class(repo_copy):
nodes_dir = repo_copy / "nodes"
nodes_dir.mkdir(exist_ok=True)
# The self-test owns the whole ``nodes/`` folder of the copy: the other
# node modules of the repository would be reported as unexported (they
# are not part of this fixture) and the guard would fail for the wrong
# reason.
for stale in nodes_dir.glob("*.py"):
stale.unlink()
(nodes_dir / "__init__.py").write_text(
"from .keep_model_loader import KEEP_ModelLoaderNode\n"
"__all__ = ['KEEP_ModelLoaderNode']\n",
encoding="utf-8",
)
(nodes_dir / "keep_model_loader.py").write_text(
"class KEEP_ModelLoaderNode:\n pass\n", encoding="utf-8"
)
assert nodes_export_violations(repo_copy) == [], (
"the exported class must not be reported"
)
(nodes_dir / "orphan.py").write_text("class OrphanNode:\n pass\n", encoding="utf-8")
violations = nodes_export_violations(repo_copy)
assert any("OrphanNode" in violation for violation in violations), (
f"the guard did not catch the unexported class: {violations}"
)
@pytest.mark.unit
def test_self_test_guard_b_detects_a_schema_in_src(repo_copy):
src_dir = repo_copy / "src"
src_dir.mkdir(exist_ok=True)
(src_dir / "keep_image_utils.py").write_text("VALUE = 1\n", encoding="utf-8")
assert src_schema_violations(repo_copy) == [], "a clean src/ must not be reported"
(src_dir / "keep_nodes.py").write_text(
"from comfy_api.latest import io\n\n"
"SCHEMA = io.Schema(node_id='KEEP_Bad')\n",
encoding="utf-8",
)
violations = src_schema_violations(repo_copy)
assert any("keep_nodes.py" in violation for violation in violations), (
f"the guard did not catch the schema in src/: {violations}"
)
@pytest.mark.unit
def test_self_test_guard_c_detects_a_v1_entrypoint(repo_copy):
init_file = repo_copy / "__init__.py"
init_file.write_text(
"async def comfy_entrypoint():\n return None\n", encoding="utf-8"
)
assert entrypoint_violations(repo_copy) == [], (
"a pure V3 entry point must not be reported"
)
init_file.write_text(
"NODE_CLASS_MAPPINGS = {}\n"
"async def comfy_entrypoint():\n return None\n",
encoding="utf-8",
)
violations = entrypoint_violations(repo_copy)
assert any("NODE_CLASS_MAPPINGS" in violation for violation in violations), (
f"the guard did not catch the V1 mapping: {violations}"
)
@pytest.mark.unit
def test_self_test_guard_d_detects_a_sys_path_mutation(repo_copy):
utils_file = repo_copy / "src" / "keep_model_configs.py"
text = utils_file.read_text(encoding="utf-8")
cleaned = "\n".join(
line for line in text.splitlines() if "sys.path.insert" not in line
)
utils_file.write_text(cleaned + "\n", encoding="utf-8")
assert sys_path_violations(repo_copy) == [], (
"a package without sys.path mutation must not be reported"
)
src_dir = repo_copy / "src"
src_dir.mkdir(exist_ok=True)
(src_dir / "evil.py").write_text(
"import sys\nsys.path.append('/tmp/evil')\n", encoding="utf-8"
)
violations = sys_path_violations(repo_copy)
assert any("evil.py" in violation for violation in violations), (
f"the guard did not catch the sys.path mutation: {violations}"
)
@pytest.mark.unit
def test_self_test_guard_e_detects_a_debug_import_in_the_closure(repo_copy):
"""The guard reports a ``pdb`` import in a file that joins the closure.
The self-test does **not** depend on the current tree still containing the
known breakage: after P2.1 the baseline is empty and the self-test must
keep working (it proves the guard, not the state of the tree).
"""
before = vendored_breakage_violations(repo_copy)
# A new ``*_arch.py`` file joins the closure through the explicit import
# list of ``basicsr/archs/__init__.py`` (P2.4 replaced the eager scan).
arch_file = repo_copy / "modules" / "deps" / "basicsr" / "archs" / "zz_guard_arch.py"
arch_file.write_text("import pdb\n\n\ndef f():\n pdb.set_trace()\n", encoding="utf-8")
archs_init = repo_copy / "modules" / "deps" / "basicsr" / "archs" / "__init__.py"
init_text = archs_init.read_text(encoding="utf-8")
assert "from . import (" in init_text, init_text[:200]
archs_init.write_text(
init_text.replace("from . import (\n", "from . import (\n zz_guard_arch,\n", 1),
encoding="utf-8",
)
after = vendored_breakage_violations(repo_copy)
assert any("zz_guard_arch.py" in violation for violation in after), (
f"the guard did not catch the new closure member: {after}"
)
# The pre-existing findings are unchanged: the guard reports exactly the
# baseline plus the injected file.
assert [violation for violation in after if "zz_guard_arch.py" not in violation] == before
@pytest.mark.unit
def test_self_test_guard_f_detects_a_module_level_comfy_import(repo_copy):
"""Guard (f) reports a ``comfy`` import that runs at import time."""
src_dir = repo_copy / "src"
src_dir.mkdir(exist_ok=True)
target = src_dir / "keep_lazy_probe.py"
target.write_text(
"from comfy.utils import ProgressBar\n\n"
"def build():\n"
" from comfy import model_management\n"
" return ProgressBar, model_management\n",
encoding="utf-8",
)
violations = comfy_import_violations(repo_copy)
assert any("keep_lazy_probe.py" in violation for violation in violations), (
f"the guard did not catch the module-level comfy import: {violations}"
)
# the function-level import of the same module is **not** a violation
target.write_text(
"def build():\n"
" from comfy.utils import ProgressBar\n"
" return ProgressBar\n",
encoding="utf-8",
)
assert comfy_import_violations(repo_copy) == [], (
"a lazy comfy import must not be reported"
)
@pytest.mark.unit
def test_self_test_guard_g_detects_an_empty_cache_call(repo_copy):
"""The guard reports a ``torch.cuda.empty_cache()`` in the pack's code."""
target = repo_copy / "src" / "keep_processor.py"
assert empty_cache_violations(repo_copy) == [], (
"a clean closure must not be reported"
)
target.write_text(
target.read_text(encoding="utf-8")
+ "\n\ndef _probe():\n torch.cuda.empty_cache()\n",
encoding="utf-8",
)
violations = empty_cache_violations(repo_copy)
assert any("keep_processor.py" in violation for violation in violations), (
f"the guard did not catch the empty_cache call: {violations}"
)
@pytest.mark.unit
def test_self_test_guard_g_detects_an_empty_cache_call_in_the_node_layer(repo_copy):
"""The guard covers the node layer - the code ComfyUI executes."""
target = repo_copy / "nodes" / "keep_process_image_sequence.py"
assert empty_cache_violations(repo_copy) == [], (
"a clean package must not be reported"
)
target.write_text(
target.read_text(encoding="utf-8")
+ "\n\ndef _probe():\n torch.cuda.empty_cache()\n",
encoding="utf-8",
)
violations = empty_cache_violations(repo_copy)
assert any("keep_process_image_sequence.py" in violation for violation in violations), (
f"the guard did not catch the empty_cache call in the node layer: {violations}"
)
@pytest.mark.unit
def test_self_test_guard_g_detects_an_empty_cache_call_in_the_entry_point(repo_copy):
"""The guard covers the root ``__init__.py`` - the entry point."""
target = repo_copy / "__init__.py"
assert empty_cache_violations(repo_copy) == [], (
"a clean package must not be reported"
)
target.write_text(
target.read_text(encoding="utf-8")
+ "\n\ndef _probe():\n torch.cuda.empty_cache()\n",
encoding="utf-8",
)
violations = empty_cache_violations(repo_copy)
assert any("__init__.py" in violation for violation in violations), (
f"the guard did not catch the empty_cache call in the entry point: {violations}"
)
@pytest.mark.unit
def test_self_test_guard_g_detects_a_new_vendored_empty_cache_call(repo_copy):
"""The guard covers the vendored closure too - not only ``src/``.
2026-11-03, keep-fix5-implement1 Phase 4.1: the probe file moved from
the deleted ``models/sr_model.py`` to the surviving ``utils/misc.py``
(a runtime-closure member); the allowlist is empty since Phase 4.1, so
the injected occurrence must be reported as a violation.
"""
vendored = repo_copy / "modules" / "deps" / "basicsr" / "utils" / "misc.py"
vendored.write_text(
vendored.read_text(encoding="utf-8")
+ "\n\ndef _probe():\n torch.cuda.empty_cache()\n",
encoding="utf-8",
)
violations = empty_cache_violations(repo_copy)
assert any("misc.py" in violation for violation in violations), (
f"the guard did not catch the new vendored occurrence: {violations}"
)
@pytest.mark.unit
def test_self_test_guard_g_detects_a_stale_allowlist_entry(repo_copy, monkeypatch):
"""The allowlist is exact: a **removed** occurrence is reported, too.
2026-11-03, keep-fix5-implement1 Phase 4.1: the allowlist is empty (the
allowlisted files are deleted), so the stale-entry branch is exercised
with a temporary entry for a surviving closure file that has no
occurrence.
"""
monkeypatch.setattr(
sys.modules[__name__],
"VENDORED_EMPTY_CACHE_ALLOWLIST",
{"modules/deps/basicsr/utils/misc.py": 1},
)
violations = empty_cache_violations(repo_copy)
assert any(
"misc.py" in violation and "allowlist expects" in violation
for violation in violations
), (
f"the guard did not catch the stale allowlist entry: {violations}"
)
@pytest.mark.unit
def test_self_test_guard_h_detects_the_dead_video_path(repo_copy):
"""The guard reports a reference to the deleted video path."""
target = repo_copy / "src" / "keep_processor.py"
target.write_text(
target.read_text(encoding="utf-8")
+ "\n\ndef _probe():\n return VideoReader\n",
encoding="utf-8",
)
violations = dead_video_violations(repo_copy)
assert any("VideoReader" in violation for violation in violations), (
f"the guard did not catch the dead video symbol: {violations}"
)
@pytest.mark.unit
def test_guard_e_flip_owner_covers_every_current_finding():
"""P2.1 must be able to clear every finding the guard reports.
Guard (e) is ``xfail(strict=False)``: nothing forces it to flip. If the
flip owner's work list misses a file, the guard stays red forever and
nobody notices. This test compares the P2.1 section of the plan with the
live scanner output - the plan's work list and the tree cannot drift.
"""
plan = REPO_ROOT / ".dev" / "docs" / "plans" / "2026-10-22-comfyui-keep-v3-modernization.md"
if not plan.is_file():
pytest.skip("the modernization plan is not part of this checkout")
text = plan.read_text(encoding="utf-8")
section = re.search(r"\*\*P2\.1\b.*?(?=\n\*\*P2\.2)", text, re.DOTALL)
assert section, "the plan must have a P2.1 section"
for violation in vendored_breakage_violations(REPO_ROOT):
path = violation.split(":", 1)[0]
assert Path(path).name in section.group(0), (
f"{path} is not on the P2.1 work list: guard (e) cannot flip green "
f"while the file is not cleaned up"
)