Design¶
pytest-r-snapshot is intentionally small and modular. The public API is the r_snapshot fixture (an RSnapshot instance) and a few helpers; everything else is internal plumbing to keep parsing, subprocess execution, and snapshot I/O isolated and testable.
High-level flow¶
When a test calls r_snapshot.assert_match_text(...):
- The snapshot file path is resolved for
(test_file, name, ext). - The expected value is obtained based on the configured mode:
replay: read the snapshot filerecord: run R and rewrite the snapshot fileauto: record only if the snapshot file is missing
- Expected and actual are newline-normalized (and optionally user-normalized).
- If they differ, pytest-friendly unified diff output is included in the failure message.
Module responsibilities¶
The implementation follows a "thin orchestrator" pattern:
pytest_r_snapshot/chunks.py- Parses labelled R fenced chunks from Python source files.
- Scans line-by-line and tracks
start_line/end_linefor diagnostics. - Supports both commented chunks and raw chunks in docstrings/multiline strings.
pytest_r_snapshot/runner.py- Defines the
RRunnerprotocol and a subprocess implementation. - Runs
Rscript --vanilla <tempfile.R>and returns captured stdout. - Wraps user code in a minimal R template using
capture.output({ ... })for deterministic text snapshots.
- Defines the
pytest_r_snapshot/snapshot.py- Implements the public
RSnapshotclass. - Resolves snapshot paths, reads/writes snapshot files, and performs comparisons.
- Delegates parsing to
chunksand execution to anRRunner.
- Implements the public
pytest_r_snapshot/settings.py- Defines settings (
RSnapshotSettings) andSnapshotMode, plus parsing helpers.
- Defines settings (
pytest_r_snapshot/plugin.py- Integrates with pytest: CLI/ini options, marker registration, and fixtures.
- Builds effective settings using the precedence model: CLI overrides everything; a
conftest.pysettings fixture can override ini defaults.
pytest_r_snapshot/errors.py- Custom exception types for clear, targeted error messages.
pytest_r_snapshot/normalize.py- Small, generic normalization helpers (newline normalization, trimming).
Configuration precedence¶
Settings are merged in this order:
- Built-in defaults
pyproject.toml/ pytest ini values- A user-provided
r_snapshot_settingsfixture (session-scoped) inconftest.py - CLI options (highest precedence)
This allows project-wide defaults to live in version control while still letting developers override behavior locally via CLI.
Testing strategy¶
The plugin's own tests are hermetic and do not require R:
- Chunk parsing and path resolution are unit-tested directly.
- Mode behavior is tested using
pytesterand a fakeRRunnerprovided by overriding ther_snapshot_runnersession fixture. - A small contract test verifies that the subprocess runner raises a helpful error when
Rscriptis missing.
This keeps CI fast and avoids coupling plugin correctness to a specific R installation.