Files
larsupbandClaude Opus 5 7973203f38 test: run the whole suite without a ComfyUI installation
`pytest tests/` previously crashed during collection and 7 of 17 test files
were dead: 61 tests were reachable, all via ad-hoc standalone scripts. Now a
bare `pytest` collects everything and passes 238 tests with ComfyUI absent
(verified by running the suite from outside the ComfyUI tree, where
`import comfy` raises ModuleNotFoundError).

Import structure:
- Drop tests/__init__.py. With it, pytest walks up to the project root's
  __init__.py -- the ComfyUI node entry point -- and imports ComfyUI before
  any test runs.
- Import project code as `src.<module>` instead of putting src/ on sys.path
  and importing bare `merge.algorithms` / `validation` / `types`. Modules in
  src/ use package-relative imports (`from ..types import ...`) that cannot
  resolve when loaded top-level, and `types` collided with the stdlib module.
  Same change for the mock.patch targets in test_algorithms.
- Consolidate conftest.py in tests/, mocking comfy, folder_paths,
  comfy_extras and nodes. It stays in tests/ rather than the project root
  because pytest imports a root-level conftest as part of the root package,
  executing the ComfyUI entry point.
- Guard the script-style runners behind `if __name__ == "__main__":` so they
  no longer sys.exit() during collection. Those files still run standalone.
- Drop run_pytest.py: a mocking wrapper made redundant by conftest, unused
  and pointing at an unresolvable default path.

Bugs the dead tests were hiding:
- validators: the INCOMPATIBLE_DIMENSIONS check sat after the `continue` that
  skips the reference tensor, so a lone LoRA with mismatched up/down ranks
  passed validation unchecked. It is a per-LoRA check and now runs for every
  entry.
- decomposition: __init__ exported a QRDecomposer that exists nowhere, so
  `import src.decomposition` raised ImportError. Export and tests removed.

Stale expectations corrected:
- return_statistics is a constructor argument, not a decompose() kwarg.
- The zero-matrix rank guard only applies under dynamic rank selection; the
  test now exercises that path, plus a new case pinning fixed-rank behavior.
- `reconstruction_error < 0.5` for a rank-10 truncation of a random 100x50
  Gaussian is unreachable -- the optimum is 0.7557 and the decomposer hits
  0.7568. Assert near-optimality instead, and add a genuinely low-rank case
  that reconstructs to 0.003.
- sym/asym distributions differ only by float32 rounding (~5e-7), below the
  default atol of 1e-8.

RUN_TESTS.md is rewritten against the real setup: correct interpreter path,
the two test-file styles, the import rules for adding tests, and a per-file
coverage table. It no longer documents test_gradient_analyzer_integration.py,
which is not in the repo.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-02 11:41:31 +02:00

5.6 KiB

Running Tests

The test suite runs without a ComfyUI installation. ComfyUI's modules (comfy, folder_paths, comfy_extras, nodes) are mocked in tests/conftest.py, so the only requirements are torch, mergekit and pytest.

Quick Start

cd /home/lars/SD/Apps/ComfyUI/custom_nodes/LoRA-Merger-ComfyUI
/home/lars/SD/Apps/ComfyUI/.venv/bin/python -m pytest tests/

Expected: 238 passed.

Any Python with the dependencies below works; /home/lars/SD/Apps/ComfyUI/.venv is simply the environment that already has them on this machine.

Run a single file or test

PY=/home/lars/SD/Apps/ComfyUI/.venv/bin/python

$PY -m pytest tests/test_validation.py            # one file
$PY -m pytest tests/test_gta_sparsify.py -v       # verbose
$PY -m pytest tests/test_types.py::TestTypeGuards # one class
$PY -m pytest tests/ -k "sparsify or interp"      # by name

Coverage

/home/lars/SD/Apps/ComfyUI/.venv/bin/python -m pytest --cov=src tests/

Requires pytest-cov (see requirements-dev.txt).

Two Kinds of Test File

Both kinds are collected by pytest tests/; the difference only matters when you want to run one directly.

Script-style — plain asserts driven by a run([...]) list, guarded behind if __name__ == "__main__":. These also run as standalone scripts with no pytest at all, which is useful for a quick check or when bisecting:

/home/lars/SD/Apps/ComfyUI/.venv/bin/python tests/test_gta_sparsify.py
# -> PASS magnitude ... All 12 passed

Files: test_gta_behavior, test_gta_merge, test_gta_parity, test_gta_sparsify, test_interp_delta_merge, test_interp_fidelity, test_interp_integration, test_lora_save, test_merge_node_names, test_merger_vram_offload.

Pytest-style — Test* classes, fixtures, pytest.raises. Run these through pytest only; executing them directly does not set up the import paths.

Files: test_algorithms, test_blocks, test_decomposition, test_spectral_norm, test_types, test_utility, test_validation.

How Imports Are Set Up

Two rules keep the suite importable outside ComfyUI. Both are handled by tests/conftest.py; they matter when you add a test.

  1. Import project code as src.<module>, never as a bare top-level module:

    from src.validation import LoRAStackValidator   # correct
    from validation import LoRAStackValidator       # breaks
    from types import is_lora_tensors               # breaks: stdlib `types`
    

    Modules under src/ use package-relative imports (from ..types import ...), which only resolve when src itself is the package. conftest puts the project root on sys.path for this. The same applies to unittest.mock.patch targets: @patch('src.merge.algorithms.LinearMergeTask').

  2. Do not add tests/__init__.py. With it, pytest walks up to the project root's __init__.py — the ComfyUI node entry point — and imports ComfyUI before any test runs.

conftest.py lives in tests/, not the project root, for the same reason: a root-level conftest is imported as part of that package.

Test Files

File Covers
test_algorithms.py Merge algorithm registry and dispatch
test_blocks.py Block selection, key normalization, block weights
test_decomposition.py SVD / randomized SVD / energy-based decomposers
test_gta_behavior.py GTA delta-space merge behavior
test_gta_merge.py Sign election and disjoint merge
test_gta_parity.py Parity of local sparsify vs. mergekit
test_gta_sparsify.py magnitude / outliers / bernoulli / della sparsify
test_interp_delta_merge.py slerp / nuslerp / karcher / nearswap in delta space
test_interp_fidelity.py Delta-space blend vs. reference average
test_interp_integration.py End-to-end merge through the node
test_lora_save.py Tensor sanitization before safetensors save
test_merge_node_names.py Node widget names and titles
test_merger_vram_offload.py offload_models widget behavior
test_spectral_norm.py Spectral norm scaling
test_types.py Type guards and validators
test_utility.py SVD/QR pipeline helpers
test_validation.py LoRA stack, shape and parameter validation

Requirements

pip install -r requirements-dev.txt

Needed to run the suite: torch, mergekit, pytest. Optional: pytest-cov (coverage), pytest-xdist (-n auto parallel runs).

A ComfyUI installation is not required.

Syntax and Lint

find src -name "*.py" -exec python3 -m py_compile {} \;
/home/lars/SD/Apps/ComfyUI/.venv/bin/python -m flake8 src/ --max-line-length=120

Debugging Failures

PY=/home/lars/SD/Apps/ComfyUI/.venv/bin/python

$PY -m pytest tests/ -vv --tb=long   # full tracebacks
$PY -m pytest tests/ -x              # stop at first failure
$PY -m pytest tests/ --lf            # rerun last failures

If a new test fails at import with attempted relative import beyond top-level package, No module named 'src', or picks up the stdlib types module, it is importing project code as a top-level module — see "How Imports Are Set Up".

Continuous Integration

name: Tests

on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-python@v5
        with:
          python-version: '3.11'

      - name: Install dependencies
        run: pip install -r requirements-dev.txt

      - name: Run tests
        run: pytest tests/ -v

No ComfyUI checkout is needed in CI.