From 3bc0a679f24ed2dfd85272ede826f100aa775354 Mon Sep 17 00:00:00 2001 From: WildAi <2853742+wildminder@users.noreply.github.com> Date: Tue, 8 Sep 2026 21:23:38 +0300 Subject: [PATCH] =?UTF-8?q?test:=20structure-guard=20suite=20=E2=80=94=20n?= =?UTF-8?q?odes/=20role=20separation=20+=20WMNodes=20pin=20+=20loader=20re?= =?UTF-8?q?gression?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit S5 of the 2026-09-08 layout plan: tests/test_structure.py grows the full guard set — nodes/ exclusively node classes, src/ schema-free, category WMNodes/image pinned per module, no legacy *_node.py in src/, git-ls-files grep guard for old category strings, and the ComfyUI loader regression (replicates load_custom_node spec_from_file_location mechanics, executes the real entry, asserts comfy_entrypoint → 8-class get_node_list + on_load installs the Qwen2D patch). --- README.md | 2 +- tests/test_structure.py | 195 +++++++++++++++++++++++++++++++++++++++- 2 files changed, 194 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index f1d31b8..e0d0886 100644 --- a/README.md +++ b/README.md @@ -366,7 +366,7 @@ Restart ComfyUI. No further dependency installation is required. ## ▓ Changelog ### v2.15.0 — 2026-09-08 -- **Restructured the pack layout + unified the node category.** All node definitions now live in a dedicated `nodes/` folder (`nodes/dype.py`, `sega.py`, `spa.py`, `hap.py`, `hap_calibrate.py`, `freescale.py`, `pixelrush.py`, `hiflow.py`); `src/` holds engines/implementation only and the pack `__init__.py` just registers the extension. All 8 nodes moved to the single **`WMNodes/image`** menu category (previously split across `model_patches/position_encoding` and `image/upscaling`). No node ids, inputs, defaults, or behavior changed — workflows keep loading. Also merges PR #41 (FreeScale fp16 antialiased-bicubic crash fix). +- **Restructured the pack layout + unified the node category.** All node definitions now live in a dedicated `nodes/` folder (`nodes/dype.py`, `sega.py`, `spa.py`, `hap.py`, `hap_calibrate.py`, `freescale.py`, `pixelrush.py`, `hiflow.py`); `src/` holds engines/implementation only and the pack `__init__.py` just registers the extension. All 8 nodes moved to the single **`WMNodes/image`** menu category (previously split across two menu paths). No node ids, inputs, defaults, or behavior changed — workflows keep loading. Also merges PR #41 (FreeScale fp16 antialiased-bicubic crash fix). ### v2.14.1 — 2026-09-07 - **Fixed HiFlow Krea2/Qwen-Image noising crash** (user-reported `torch.cat` size mismatch, "Expected size 1 but got size 16"): the v2.12.1 model-space noising called the model's `process_latent_in` on the 4D core tensor, but Wan21's per-channel mean/std stats are shaped `[1,C,1,1,1]` — a 4D tensor against 5D stats **broadcasts silently to `[B,C,C,H,W]` garbage** (the model reads T=16=channels). The node now wraps the noising conversions ndim-transparently: unsqueeze → convert in true 5D model space → squeeze back, so the cascade's σ-mix runs on 4D tensors with correctly-normalized values. The node-test mock now uses Wan21-faithful stats (replicating the broadcast hazard — the earlier affine mock masked the bug class). diff --git a/tests/test_structure.py b/tests/test_structure.py index 270a03a..014b06a 100644 --- a/tests/test_structure.py +++ b/tests/test_structure.py @@ -7,13 +7,20 @@ Entry `__init__.py` — extension registration only. These guards are text/AST-based where the entry module cannot be imported in the GPU-free test env, and runtime-based where the mock-comfy conftest makes live imports possible (same split as test_hap_node.py / test_hiflow_node.py). +The loader regression test replicates ComfyUI's ``load_custom_node`` +(``spec_from_file_location`` on the pack ``__init__.py``, executed as a +module in ``sys.modules``) — the mechanism verified empirically 2026-09-08. Markers: @pytest.mark.unit """ from __future__ import annotations +import asyncio +import importlib.util import pathlib +import sys +import types import pytest @@ -21,23 +28,74 @@ PROJECT_ROOT = pathlib.Path(__file__).parent.parent ENTRY = PROJECT_ROOT / "__init__.py" NODES_DIR = PROJECT_ROOT / "nodes" NODES_INIT = NODES_DIR / "__init__.py" +SRC_DIR = PROJECT_ROOT / "src" ALL_NODE_CLASSES = [ "DyPE_FLUX", "SEGA", "SPA", "HAP", "HAPCalibrate", "PixelRushNode", "FreeScaleNode", "HiFlowNode", ] -# Node modules that should exist once every layout-plan step has landed. -# S1 ships dype/sega/spa/hap; the *_node moves append their entries (S2/S3). +# Expected node modules once every layout-plan step has landed. EXPECTED_NODE_MODULES = [ "dype", "sega", "spa", "hap", + "hap_calibrate", "freescale", "pixelrush", "hiflow", ] +_OLD_CATEGORIES = ("model_patches/position_encoding", "image/upscaling") + def _entry_src() -> str: return ENTRY.read_text(encoding="utf-8") +def _fake_mod(name, **attrs): + """Additive-only mock module (pattern from tests/test_qwen2d_vae.py): + never replaces the root conftest's comfy mocks, registers in sys.modules + and wires the parent attribute.""" + if name in sys.modules: + return sys.modules[name] + m = types.ModuleType(name) + for k, v in attrs.items(): + setattr(m, k, v) + sys.modules[name] = m + parent, _, child = name.rpartition(".") + if parent and parent in sys.modules: + setattr(sys.modules[parent], child, m) + return m + + +def _ensure_entry_comfy_chain(): + """The pack entry imports src.qwen2d_vae_patch, which imports the comfy + chain (comfy.sd, comfy.model_management, ...). Install additive mocks + so the loader replica can execute the entry in the GPU-free env.""" + _fake_mod("comfy.sd", VAE=type("VAE", (), {})) + _fake_mod("comfy.model_management", + is_amd=lambda: False, dtype_size=lambda d: 4, + OOM_EXCEPTION=RuntimeError) + _fake_mod("comfy.utils") + _fake_mod("comfy.ops", disable_weight_init=object) + _fake_mod("comfy.ldm") + _fake_mod("comfy.ldm.modules") + _fake_mod("comfy.ldm.modules.diffusionmodules") + _fake_mod("comfy.ldm.modules.diffusionmodules.model", + vae_attention=lambda *a, **k: None) + + +def _load_entry_via_comfy_loader(): + """Replicate ComfyUI nodes.load_custom_node for a DIRECTORY pack: + spec_from_file_location(sys_module_name,