Files
wildminder-ComfyUI-DyPE/tests/test_hap_calib_node.py
T
WildAi cb677c1794 refactor: hap_calibrate node module moves src→nodes, category unified
S2 of the 2026-09-08 layout plan: src/hap_calib_node.py → nodes/hap_calibrate.py
(git mv). Engine imports (.hap/.hap_calib/.spa/.spa_context) become dual-form
try/except (loader-relative ..src / flat src). Calibration CLI re-imports
DEFAULT_CALIBRATION_PROMPTS + orchestrator from nodes.hap_calibrate. Category →
WMNodes/image. 9 hap_calib test files repointed.
2026-09-08 21:01:58 +03:00

202 lines
7.8 KiB
Python

"""Tests for the HAPCalibrate node (plan P5: T5.1-T5.5).
The node *wiring* (schema, inputs, registration) is covered by text-checks
that read ``__init__.py`` / ``nodes/hap_calibrate.py`` directly — the same
pattern as ``tests/test_hap_node.py``. Functional behaviour is exercised via
``HAPCalibrate.execute`` with an injected forward (monkeypatched
``run_hap_calibration``).
Markers: @pytest.mark.unit
Accept (user-run):
pytest tests/test_hap_calib_node.py -q
"""
import pathlib
import pytest
import nodes.hap_calibrate as hcn
from nodes.hap_calibrate import HAPCalibrate
_ROOT = pathlib.Path(__file__).parent.parent
_INIT = _ROOT / "__init__.py"
_NODE_SRC = _ROOT / "nodes" / "hap_calibrate.py"
# ---------------------------------------------------------------------------
# T5.1 — schema text-checks (every §3.1 input present)
# ---------------------------------------------------------------------------
@pytest.mark.unit
class TestSchemaInputs:
@pytest.mark.parametrize(
"name",
[
"model", "positive", "negative", "width", "height", "prompts",
"prompts_file", "num_prompts", "num_scopes", "budget_ratio",
"bins", "chunk", "text_len", "anchor_stride", "calib_sigma",
"seed", "loss_type", "reference_latent", "output_name", "run",
],
)
def test_input_present(self, name):
src = _NODE_SRC.read_text(encoding="utf-8")
assert f'"{name}"' in src, f"input {name!r} missing from schema"
def test_node_identity(self):
src = _NODE_SRC.read_text(encoding="utf-8")
assert 'node_id="HAPCalibrate"' in src
assert 'display_name="HAP Calibrate (HRDiT)"' in src
assert 'category="WMNodes/image"' in src
def test_outputs_present(self):
src = _NODE_SRC.read_text(encoding="utf-8")
assert 'io.Custom("SCOPE_PLAN").Output(' in src
assert 'display_name="plan_path"' in src
assert 'display_name="summary"' in src
def test_schema_constructs(self):
"""The schema builds against the (mocked) comfy_api without error."""
schema = HAPCalibrate.define_schema()
assert schema.node_id == "HAPCalibrate"
# ---------------------------------------------------------------------------
# T5.2 — execute with injected calibration
# ---------------------------------------------------------------------------
@pytest.mark.unit
class TestExecute:
def test_execute_returns_three_outputs(self, monkeypatch, tmp_path):
plan_dict = {
"alphas": [[0.0, 0.0]],
"betas": [[0.5, 1.0]],
}
summary = {
"num_layers": 1, "num_heads": 2, "seq_len": 100,
"text_len": 0, "num_prompts": 1, "num_scopes": 4,
"budget_ratio": 0.5, "mean_beta_min": 0.5,
"mean_beta_max": 0.75, "flops_ratio": 0.5,
"elapsed_seconds": 0.1,
}
monkeypatch.setattr(
hcn, "run_hap_calibration",
lambda **kw: (plan_dict, summary),
)
monkeypatch.setattr(hcn, "resolve_output_dir", lambda: str(tmp_path))
out = HAPCalibrate.execute(
model=object(), positive=None, negative=None,
width=512, height=512, prompts="a prompt",
num_prompts=1, num_scopes=4, budget_ratio=0.5,
)
plan, path, summary_text = out
assert plan == plan_dict
assert path.endswith(".json")
assert pathlib.Path(path).exists()
assert "HAP Calibration Summary" in summary_text
def test_execute_writes_to_dype_hap_subdir(self, monkeypatch, tmp_path):
plan_dict = {"alphas": [[0.0]], "betas": [[1.0]]}
summary = {
"num_layers": 1, "num_heads": 1, "seq_len": 10,
"text_len": 0, "num_prompts": 1, "num_scopes": 2,
"budget_ratio": 0.5, "mean_beta_min": 1.0,
"mean_beta_max": 1.0, "flops_ratio": 1.0,
"elapsed_seconds": 0.0,
}
monkeypatch.setattr(
hcn, "run_hap_calibration", lambda **kw: (plan_dict, summary)
)
monkeypatch.setattr(hcn, "resolve_output_dir", lambda: str(tmp_path))
_, path, _ = HAPCalibrate.execute(
model=object(), positive=None, negative=None,
prompts="p", num_prompts=1,
)
assert "dype_hap" in path
# ---------------------------------------------------------------------------
# T5.3 — run=False short-circuit
# ---------------------------------------------------------------------------
@pytest.mark.unit
class TestRunFalse:
def test_run_false_skips(self, monkeypatch):
called = []
monkeypatch.setattr(
hcn, "run_hap_calibration",
lambda **kw: called.append(1) or ({}, {}),
)
out = HAPCalibrate.execute(
model=object(), positive=None, negative=None, run=False,
)
plan, path, summary_text = out
assert plan == {}
assert path == ""
assert "skipped" in summary_text
assert called == [] # forward never ran
# ---------------------------------------------------------------------------
# T5.4 — invalid inputs surface with prefix
# ---------------------------------------------------------------------------
@pytest.mark.unit
class TestExecuteErrors:
def test_invalid_budget_prefixed(self, tmp_path, monkeypatch):
monkeypatch.setattr(hcn, "resolve_output_dir", lambda: str(tmp_path))
with pytest.raises(ValueError, match="HAP Calibrate:"):
HAPCalibrate.execute(
model=object(), positive=None, negative=None,
prompts="p", num_prompts=1, budget_ratio=0.0,
)
def test_reference_mse_without_reference_prefixed(self, tmp_path, monkeypatch):
monkeypatch.setattr(hcn, "resolve_output_dir", lambda: str(tmp_path))
with pytest.raises(ValueError, match="HAP Calibrate:"):
HAPCalibrate.execute(
model=object(), positive=None, negative=None,
prompts="p", num_prompts=1, loss_type="reference_mse",
)
def test_zero_prompts_after_resolution_prefixed(self, tmp_path, monkeypatch):
"""A prompts_file that exists but is empty of content still falls
back to defaults, so use an invalid width to trigger the prefix."""
monkeypatch.setattr(hcn, "resolve_output_dir", lambda: str(tmp_path))
with pytest.raises(ValueError, match="HAP Calibrate:"):
HAPCalibrate.execute(
model=object(), positive=None, negative=None,
prompts="p", num_prompts=1, width=100,
)
# ---------------------------------------------------------------------------
# T5.5 — registration text-checks
# ---------------------------------------------------------------------------
@pytest.mark.unit
class TestRegistration:
def test_imported_in_init(self):
"""Entry imports node classes from the .nodes package (layout plan
2026-09-08); HAPCalibrate itself still lives in the (pre-move)
node module re-exported by nodes/__init__."""
src = _INIT.read_text(encoding="utf-8")
assert "from .nodes import" in src
nodes_init = (_ROOT / "nodes" / "__init__.py").read_text(encoding="utf-8")
assert "HAPCalibrate" in nodes_init
def test_listed_in_node_list(self):
src = _INIT.read_text(encoding="utf-8")
assert "HAPCalibrate" in src.split("get_node_list")[1]
def test_hap_node_accepts_scope_plan_input(self):
"""The HAP node gained the optional SCOPE_PLAN input (plan P4.3).
HAP class body lives in nodes/hap.py (layout plan 2026-09-08)."""
src = (_ROOT / "nodes" / "hap.py").read_text(encoding="utf-8")
assert 'io.Custom("SCOPE_PLAN").Input(' in src
assert '"scope_plan"' in src
# execute prefers the linked plan.
assert "if scope_plan is not None:" in src
assert "ScopePlan.from_dict(scope_plan)" in src