Files
wildminder-ComfyUI-KEEP/tests/test_keep_paths.py
T
WildAi 01a4924489 plan(P5): address review findings
The five review findings of 2026-10-23 for phase P5:

1. The P5.5 fallback branch (a checkpoint without flownet.* keys) died with
   a bare RuntimeError at the strict load: the arch always owns the flownet
   (keep_arch.py:882), so the state dict of the net always carries the
   flownet.* keys - and the GMFlow weights the arch had just loaded were
   discarded.  The flownet tensors of the built net (the trained ones - the
   arch was built with flownet_path) are now merged into the checkpoint
   state dict before load_state_dict(..., strict=True).  The load stays
   strict (an unexpected key still raises) and the trained flow weights
   survive.  The stub arches of the tests behave like nn.Module now - a
   no-op recorder had hidden the defect.

2. P5.4's acceptance is no longer unmet: the GPU acceptance run was
   executed (real weights, real ComfyUI modules, RTX 4070 Ti SUPER) and is
   green (5 passed, exit code 0; the run log is in the audit doc section 5).
   KEEP_MODEL_DIR now really decides where the weights come from - it is the
   models root of the run (folder_paths.models_dir), the harness fixture
   no longer isolates the gpu_acceptance tier, and the run needs
   KEEP_NO_COMFY_MOCKS=1 so the real ComfyUI modules are in charge.

3. The "the flow weights are not random" assertion of the acceptance test
   could not distinguish random weights from trained ones.  It now compares
   the flownet tensors with the tensors of their source (the KEEP checkpoint
   or the GMFlow file) bit for bit.

4. The superseded download helper load_file_from_url_comfy is gone
   (src/keep_paths.py only resolves paths; the download lives in
   src/keep_download.py since P5.2).  The stale docstrings, the stale
   comment in the vendored facelib and the two tests that called the dead
   helper are fixed.

5. The risk list mentioned a pending deletion of assets/keep-preview.png
   that does not exist - the asset is committed and present (93279 bytes).
   The rule in the implementation workflow now states the truth.

Checks: the gate (pytest -q -m "unit or mock_integration") is green (234
passed, 1 skipped, 1 xfailed); the opt-in acceptance run is green (5 passed);
the acceptance tier still skips cleanly without its environment variables.
2026-10-24 00:01:48 +03:00

344 lines
12 KiB
Python

"""P3.5 - the model folders and the path resolution.
The pack registers three model folders with ComfyUI's ``folder_paths`` and
resolves every weight path through them:
* ``keep_models`` (with the ``KEEP`` **sub-directory**),
* ``facedetection``,
* ``gmflow``.
The on-disk layout must not change: the KEEP checkpoints stay in
``models/keep_models/KEEP`` and the facelib weights in ``models/facedetection``.
"""
from __future__ import annotations
import ast
import importlib
import json
import os
import subprocess
import sys
from pathlib import Path
import pytest
REPO_ROOT = Path(__file__).resolve().parent.parent
SRC_DIR = REPO_ROOT / "src"
COMFYUI_ROOT_ENV = "COMFYUI_ROOT"
#: The probe: the pack is imported against the **real** ComfyUI source tree
#: and the registered folders are reported.
_REAL_FOLDER_PATHS_PROBE = """
import json, os, sys
comfyui_root, repo_root = sys.argv[1], sys.argv[2]
sys.path.insert(0, comfyui_root)
sys.path.insert(0, repo_root)
sys.path.insert(0, os.path.join(repo_root, "tests"))
import _headless
module = _headless.register_package(
repo_root, alias="ComfyUI_KEEP_paths", execute=True
)
import folder_paths
print(
"KEEP_FOLDER_PATHS:"
+ json.dumps(
{
name: folder_paths.get_folder_paths(name)
for name in ("keep_models", "facedetection", "gmflow")
}
)
)
"""
@pytest.fixture(scope="module")
def keep_paths():
return importlib.import_module("ComfyUI_KEEP.src.keep_paths")
@pytest.mark.unit
def test_the_three_model_folders_are_registered(keep_paths, mock_folder_paths):
"""The registration adds the three folders with the expected paths."""
assert keep_paths.register_model_folders() is True
registered = mock_folder_paths.folder_names_and_paths
for name in ("keep_models", "facedetection", "gmflow"):
assert name in registered, sorted(registered)
expected = os.path.join(mock_folder_paths.models_dir, name)
assert registered[name][0][0] == expected, (name, registered[name])
@pytest.mark.unit
def test_the_registration_is_idempotent(keep_paths, mock_folder_paths):
"""A second registration does not add the folder twice."""
keep_paths.register_model_folders()
keep_paths.register_model_folders()
paths = mock_folder_paths.folder_names_and_paths["keep_models"]
assert len(paths) == 1, paths
@pytest.mark.unit
def test_the_keep_subdir_is_preserved(keep_paths, mock_folder_paths):
"""The KEEP checkpoints resolve to ``models/keep_models/KEEP/<file>``."""
keep_paths.register_model_folders()
target = keep_paths.resolve_model_target(
"keep_models", "KEEP-b76feb75.pth", subdir="KEEP"
)
expected = os.path.join(
mock_folder_paths.models_dir, "keep_models", "KEEP", "KEEP-b76feb75.pth"
)
assert target == expected, target
assert os.path.isdir(os.path.dirname(target))
@pytest.mark.unit
def test_an_existing_weight_is_resolved(keep_paths, mock_folder_paths):
"""An existing file is resolved through the registered folders."""
keep_paths.register_model_folders()
folder = os.path.join(mock_folder_paths.models_dir, "keep_models", "KEEP")
os.makedirs(folder, exist_ok=True)
weight = os.path.join(folder, "KEEP-b76feb75.pth")
with open(weight, "wb") as handle:
handle.write(b"not a real checkpoint")
resolved = keep_paths.resolve_model_file(
"keep_models", "KEEP-b76feb75.pth", subdir="KEEP"
)
assert resolved == weight, resolved
@pytest.mark.unit
def test_a_missing_weight_names_the_file(keep_paths, mock_folder_paths):
"""A missing weight raises an error that names the file."""
keep_paths.register_model_folders()
with pytest.raises(FileNotFoundError) as excinfo:
keep_paths.resolve_model_file(
"keep_models", "does-not-exist.pth", subdir="KEEP"
)
assert "does-not-exist.pth" in str(excinfo.value)
def _extra_model_root(mock_folder_paths) -> str:
"""A second model root next to the (temporary) primary one."""
root = str(Path(mock_folder_paths.models_dir).parent / "extra-models")
os.makedirs(root, exist_ok=True)
return root
def _write_weight(path: str, content: bytes = b"not a real checkpoint") -> str:
"""A weight file at ``path`` (the parent directories are created)."""
os.makedirs(os.path.dirname(path), exist_ok=True)
with open(path, "wb") as handle:
handle.write(content)
return path
@pytest.mark.unit
def test_the_loader_uses_a_weight_from_an_extra_model_root(keep_paths, mock_folder_paths):
"""The **production** path finds a weight in an extra model root.
The plan (P3.5) wants the multi-root handling in the load path, not
only in a helper the product never calls: a weight that lives in an
extra model root is used where it is instead of being downloaded into
the primary root again. The lookup that the pack really uses is
:func:`src.keep_paths.find_model_file` (the downloader of P5.2 calls
it), so the test calls that.
"""
keep_paths.register_model_folders()
extra = _extra_model_root(mock_folder_paths)
mock_folder_paths.add_model_folder_path("keep_models", extra)
weight = _write_weight(os.path.join(extra, "KEEP", "KEEP-b76feb75.pth"))
resolved = keep_paths.find_model_file(
"keep_models", "KEEP-b76feb75.pth", subdir="KEEP"
)
assert resolved == weight, resolved
# nothing was downloaded into the primary root
primary = os.path.join(
mock_folder_paths.models_dir, "keep_models", "KEEP", "KEEP-b76feb75.pth"
)
assert not os.path.exists(primary), primary
@pytest.mark.unit
def test_the_primary_root_wins_over_an_extra_model_root(keep_paths, mock_folder_paths):
"""The primary root is searched first."""
keep_paths.register_model_folders()
extra = _extra_model_root(mock_folder_paths)
mock_folder_paths.add_model_folder_path("keep_models", extra)
primary_weight = _write_weight(
os.path.join(
mock_folder_paths.models_dir, "keep_models", "KEEP", "KEEP-b76feb75.pth"
)
)
_write_weight(os.path.join(extra, "KEEP", "KEEP-b76feb75.pth"))
resolved = keep_paths.find_model_file(
"keep_models", "KEEP-b76feb75.pth", subdir="KEEP"
)
assert resolved == primary_weight, resolved
@pytest.mark.unit
def test_the_model_root_follows_the_weights(keep_paths, mock_folder_paths):
"""The root of a folder is the one the weights really live in.
The facelib helper builds the weight paths from a **single** root, so
the root has to be the one that holds the weights - an extra model root
included.
"""
keep_paths.register_model_folders()
names = ["parsing_parsenet.pth", "detection_Resnet50_Final.pth"]
extra = _extra_model_root(mock_folder_paths)
mock_folder_paths.add_model_folder_path("facedetection", extra)
for name in names:
_write_weight(os.path.join(extra, name))
assert keep_paths.resolve_model_root("facedetection", names) == extra
# a weight that is nowhere is downloaded to the primary root
assert keep_paths.resolve_model_root("facedetection", ["missing.pth"]) == os.path.join(
mock_folder_paths.models_dir, "facedetection"
)
@pytest.mark.unit
def test_the_facedetection_folder_has_no_subdir(keep_paths, mock_folder_paths):
"""The facelib weights stay directly in ``models/facedetection``."""
keep_paths.register_model_folders()
target = keep_paths.resolve_model_target(
"facedetection", "parsing_parsenet.pth"
)
expected = os.path.join(
mock_folder_paths.models_dir, "facedetection", "parsing_parsenet.pth"
)
assert target == expected, target
@pytest.mark.unit
def test_no_engine_concatenates_the_models_dir():
"""The grep guard: only the registration knows ``models_dir``.
The registration needs the primary root, but no engine may build a
weight path from ``folder_paths.models_dir`` itself. Even inside
``src/keep_paths.py`` the attribute may only be used by the
registration - never to build the path of a weight file.
"""
offenders: list[str] = []
for path in sorted(SRC_DIR.glob("*.py")):
tree = ast.parse(path.read_text(encoding="utf-8"), filename=str(path))
registration = _function_line_range(tree, "register_model_folders")
for node in ast.walk(tree):
if not (isinstance(node, ast.Attribute) and node.attr == "models_dir"):
continue
if path.name != "keep_paths.py":
offenders.append(f"{path.name}:{node.lineno}")
continue
if registration is None or not (
registration[0] <= node.lineno <= registration[1]
):
offenders.append(
f"{path.name}:{node.lineno} (outside the registration)"
)
assert offenders == [], (
f"only the registration in src/keep_paths.py may use "
f"folder_paths.models_dir: {offenders}"
)
def _function_line_range(tree: ast.Module, name: str) -> tuple[int, int] | None:
"""The line range of a function of the module (``None`` when absent)."""
for node in ast.walk(tree):
if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)) and node.name == name:
return node.lineno, node.end_lineno or node.lineno
return None
@pytest.mark.unit
def test_the_loader_resolves_the_facelib_root_through_keep_paths():
"""The loader does not build the facelib path itself any more."""
path = SRC_DIR / "keep_model_loader.py"
source = path.read_text(encoding="utf-8")
tree = ast.parse(source, filename=str(path))
offenders = [
node.lineno
for node in ast.walk(tree)
if isinstance(node, ast.Attribute) and node.attr == "models_dir"
]
assert offenders == [], (
f"the loader must not use folder_paths.models_dir (lines {offenders})"
)
# the root comes from keep_paths - and it is the root the weights are in
assert "keep_paths.resolve_model_root" in source
@pytest.mark.headless
def test_the_real_folder_paths_know_the_keep_folders():
"""Against the real ComfyUI: the folders are registered and resolvable."""
value = (os.environ.get(COMFYUI_ROOT_ENV) or "").strip()
if not value:
pytest.skip(
f"{COMFYUI_ROOT_ENV} is not set - the headless folder_paths test "
"needs the ComfyUI source tree"
)
root = Path(value).expanduser()
if not (root / "folder_paths.py").is_file():
pytest.skip(
f"{COMFYUI_ROOT_ENV}={value!r} is not a ComfyUI source tree "
"(folder_paths.py is missing)"
)
result = subprocess.run(
[
sys.executable,
"-c",
_REAL_FOLDER_PATHS_PROBE,
str(root.resolve()),
str(REPO_ROOT),
],
cwd=str(REPO_ROOT),
capture_output=True,
text=True,
timeout=600,
)
assert result.returncode == 0, (
f"the package did not import\nstdout:\n{result.stdout}\n"
f"stderr:\n{result.stderr}"
)
sentinel = [
line for line in result.stdout.splitlines() if line.startswith("KEEP_FOLDER_PATHS:")
]
assert sentinel, (
f"the probe produced no report\nstdout:\n{result.stdout}\n"
f"stderr:\n{result.stderr}"
)
report = json.loads(sentinel[-1].split(":", 1)[1])
for name in ("keep_models", "facedetection", "gmflow"):
assert report[name], f"{name} is not registered: {report}"
assert report[name][0].endswith(name), report
@pytest.mark.unit
def test_the_primary_root_is_registered_first(keep_paths, mock_folder_paths):
"""The primary-root-first rule (P3.5 / P5.2).
An extra model root that was registered **before** the pack must not
shadow the primary root: the lookup (``get_full_path_or_raise``) and the
download target both follow the primary root first.
"""
extra = _extra_model_root(mock_folder_paths)
mock_folder_paths.add_model_folder_path("keep_models", extra)
keep_paths.register_model_folders()
roots = keep_paths.get_model_folder_paths("keep_models")
primary = os.path.join(mock_folder_paths.models_dir, "keep_models")
assert roots[0] == primary, roots
assert keep_paths.get_primary_folder("keep_models") == primary
assert keep_paths.resolve_model_target("keep_models", "x.pth").startswith(primary)