Testing and CI#
This page documents how to test cellucid-python locally and what CI currently enforces.
What tests exist#
Tests live under:
cellucid-python/tests/
The suite covers input-format classification and serving every registry
format (test_input_formats.py, test_serve_input_formats.py), the
Cellucid-ready AnnData and MuData writers, exact scientific value and identity
contracts, HTTP routes and server lifecycle, the viewer channel
(test_viewer_channel_contract.py) and Jupyter messaging, session files,
the history and the apply (test_sessions.py, against the golden history in
tests/fixtures/history_golden/ that the web app and cellucid-r read too;
test_session_layout.py, the chunk layout the web app’s writer makes: parts
shared, cyclic, expanding past what a page holds, unreferenced or renamed,
steps and values in many pieces, chunks stored as they are, parameters naming
lazy arrays;
test_history_words.py, the methods text and pseudocode rules the golden
branches do not reach, string for string with cellucid-r;
test_history_model.py, the generated model against
history_model.schema.json), the local MCP server (test_mcp.py: the
pinned mcp_tools.json, the shared local_agents.json fixture, the agent
messages of the viewer channel, a viewer’s publication and loopback endpoint
and its refusals, and cellucid mcp over stdio against a fake page:
initialize, lists, calls, errors, cancellation, elicitation, list changes, a
viewer stopping), vector fields, cache and path confinement, documentation, cross-platform repository
paths, and release artifacts. The MuData tests need mudata, which the dev
extra brings with it (they are skipped without it). On Python 3.11 pip
installs mudata 0.3 (mudata 0.4 needs Python 3.12); tests/conftest.py sets its
pull_on_update=False, so every MuData the suite builds or reads keeps each
modality’s columns in the modality, as mudata 0.4 does.
test_minimum_python_grammar.py parses every Python file with the grammar of
the oldest Python requires-python admits, including the f-strings only Python
3.12 accepts. Changes to a public contract require a focused regression test at
the owning boundary.
Some tests compare with the web app or run its readers: the golden history and
the history model schema byte for byte (test_sessions.py,
test_history_model.py), the session README’s and codecs’ constants
(test_sessions.py, test_session_layout.py), and the web app’s AnnData and
priority readers on what prepare() writes
(test_prepare_data_and_mudata_contract.py,
test_prepare_priority_order_contract.py). They find the web app at
CELLUCID_WEB_ROOT (a checkout or worktree of the cellucid repository),
else in the cellucid checkout beside this repository (tests/_web_root.py).
A web checkout that is named or present must hold the file a test reads: one it
lacks fails the test. Only when there is no web checkout at all, as in this
repository’s own CI, are they skipped, saying so.
CELLUCID_WEB_ROOT=/path/to/cellucid python -m pytest
Running tests locally#
From the cellucid-python/ folder:
python -m pytest
Useful variants:
python -m pytest -k sessions
python -m pytest -k vector_fields
Coverage (if installed):
python -m pytest --cov=cellucid
Linting, formatting, and types#
Ruff#
Format the Python files you intentionally changed, then lint the surfaces CI
lints (src and tests; the release workflow adds
scripts/validate_release.py and scripts/normalize_sdist.py):
python -m ruff format path/to/changed_file.py
python -m ruff check src tests
To check an intentionally formatted file without changing it:
python -m ruff format --check path/to/changed_file.py
mypy#
python -m mypy src/cellucid
Note: mypy is configured pragmatically (ignore_missing_imports = true).
CI workflows (GitHub Actions)#
Workflows live under:
cellucid-python/.github/workflows/
Current workflows:
docs-check.yml: builds Sphinx docs with warnings as errors (sphinx-build -W --keep-going) on PRs and pushes tomaintest.yml: installs.[dev](which bringsmudata) and runs the complete pytest suite, Ruff, mypy, and distribution checks on Python 3.11–3.14 on Ubuntu, and on 3.11, 3.12 and 3.14 on macOS and on Windowspypi-publish.yml: runs when a GitHub Release is published (and on nothing else); proves the release and tag provenance, validates source and docs, normalizes and verifies the source-distribution digest, installs both artifacts on all three operating systems, and publishes through trusted identity (Release process)readthedocs.yml: triggers the Read the Docslatestbuild via API on a push tomain
Writing new tests (recommended patterns)#
General principles:
Prefer small, synthetic inputs over real datasets (speed + privacy).
Test behavior and error messages for edge cases (shape mismatch, NaN handling, dtype selection).
Avoid tests that require network access (hosted UI proxy) or browsers.
Core test categories:
The Cellucid-ready AnnData writer (
tests/test_prepare_anndata_contract.py, andtests/test_prepare_data_and_mudata_contract.pyfordata=and MuData):every output (
.h5ad, Zarr v2/v3,.zarr.zip,.h5mu) reads back exactly with anndata or mudatachunks, codecs, consolidated metadata, and
uns['cellucid']match the specification; Zarr v3 metadata names only codecs of the v3 specificationevery refusal happens before anything is written
Server routes:
health/info endpoints
the server answers store routes with exact
Range/206/HEAD/ETagbytes
The viewer channel and notebook event plumbing:
the WebSocket upgrade,
Originand token checks,hello/welcome, the outbox and binary frames (tests/_viewer_channel_client.pyis a minimal channel client)_handle_frontend_messagecorrectly updatesviewer.statehook registry triggers expected callbacks
Troubleshooting#
Symptom: “Tests pass locally but docs-check fails”#
Docs-check installs only the docs extra and builds Sphinx.
Common causes:
broken MyST syntax (unclosed fences),
bad
{doc}links,missing files referenced by toctrees.
Fix: run the exact CI command, which treats warnings as errors:
sphinx-build -W --keep-going -b html docs docs/_build/html
Symptom: “Ruff complains but formatting looks fine”#
Run both checks on the files you changed:
python -m ruff format path/to/changed_file.py
python -m ruff check path/to/changed_file.py
Any rule suppression must be narrow and justified in the review description.