`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>
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.
-
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 whensrcitself is the package. conftest puts the project root onsys.pathfor this. The same applies tounittest.mock.patchtargets:@patch('src.merge.algorithms.LinearMergeTask'). -
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.