pytest-shm
A pytest plugin that puts your test suite's temporary files on the /dev/shm tmpfs, so tests that fsync stop waiting on the disk.
Table of Contents
Why
SQLite commits, atomic file replacement, and anything else that promises durability call fsync, and on a real disk each call waits for the device.
A suite that exercises durable storage can spend most of its time there.
tmpfs lives in memory, so fsync returns immediately.
In MindRoom's suite of about 26,000 tests, summed test time on a 32-worker NVMe machine fell from 5236 s to 1426 s, and the GitHub Actions test step fell from about 16 to 12-15 minutes.
No durability test can observe the difference. Such tests simulate a crashed process, and a crashed process never needed its writes to leave the page cache. The caveats list the differences other tests can see.
Installation
uv add --dev pytest-shm
# or
pip install pytest-shm
It needs Python 3.10+ and pytest 8.4+, and pytest loads it automatically. The report header tells you whether it is active:
shm: temp root /dev/shm
or why it is not:
shm: off, TMPDIR is exported
How it works
- Temp root. Before any
conftest.pyis imported, the plugin setsTMPDIR=/dev/shm, sotmp_path,tmp_path_factory, and everytempfilecall land in memory. - Containment. Tests and the code they drive often call
tempfile.mkdtemp()without removing the result. On disk that clutters/tmp; on tmpfs it would hold memory until reboot. When the session starts, before collection, the plugin pointsTMPDIRat<basetemp>/shm-tmp, so that output lives and dies with pytest's own base directory. - Cleanup. pytest keeps the last three sessions' base directories. When a session passes, collects no tests, or stops at a usage error, the plugin deletes its base directory right away instead of holding it in memory. A failing session keeps everything for inspection.
- pytest-xdist. Each worker owns
<basetemp>/popen-gwNand cleans up after itself, so a failing run keeps only the directories of workers that saw a failure. A--basetempyou pass yourself is never deleted, with or without xdist.
Deleting per test is deliberately not offered: pytest would then reuse the freed directory names, and caches keyed by path would hand the next test the previous one's state.
When it stays off
The plugin leaves the temp root alone, and says why in the report header, when any of these holds:
| Condition | Header |
|---|---|
| Not running on Linux | shm: off, not Linux |
TMPDIR, TEMP, or TMP is exported |
shm: off, TMPDIR is exported |
--basetemp or PYTEST_DEBUG_TEMPROOT puts pytest's base directory outside /dev/shm |
shm: off, --basetemp is outside /dev/shm |
pytest's tmpdir plugin is disabled (-p no:tmpdir) |
shm: off, pytest's tmpdir plugin is disabled |
/dev/shm is missing, or not writable and searchable |
shm: off, /dev/shm is missing or not writable |
/dev/shm is mounted noexec (Docker's default), which would break tests that run scripts they write |
shm: off, /dev/shm is mounted noexec |
/dev/shm has less free space than shm_min_free_gib (Docker's default is 64 MiB) |
shm: off, /dev/shm has 0.1 GiB free, below shm_min_free_gib = 1 |
To turn it off explicitly, export TMPDIR to the directory you want, or pass -o shm_min_free_gib=inf.
-p no:shm works too, but pytest then warns about the unknown shm_min_free_gib option if you configured it, and --strict-config makes that an error.
If you export TMPDIR=/dev/shm yourself, the plugin still contains and cleans up temporary files, as long as pytest's base directory is on /dev/shm too.
Configuration
One ini option sets how much free space /dev/shm needs before the plugin uses it:
[tool.pytest.ini_options]
shm_min_free_gib = 4
The default is 1.
Set it above your suite's peak usage, which you can watch with df -h /dev/shm during a run.
Override it for one run with -o shm_min_free_gib=8.
Caveats
-
Only output during the session is contained. Temporary files created before the session starts (while the initial
conftest.pyfiles are imported, inpytest_configure, or in other plugins'pytest_sessionstarthooks) or after it ends (pytest_terminal_summary,pytest_unconfigure) land directly in/dev/shmand stay there until reboot. Create them in fixtures, or remove them yourself. -
Caches under the temp root become per-session. Libraries that cache downloads under
tempfile.gettempdir()see the contained directory, which the plugin frees after the session. Pin such caches in your rootconftest.py, wheretempfile.gettempdir()is still/dev/shm. For tiktoken:if "TIKTOKEN_CACHE_DIR" not in os.environ and "DATA_GYM_CACHE_DIR" not in os.environ: os.environ["TIKTOKEN_CACHE_DIR"] = str(Path(tempfile.gettempdir()) / "data-gym-cache")
-
Temp files live on another filesystem.
os.renameoros.replacefrom a temporary file into your project fails withEXDEV, as it already does wherever/tmpis tmpfs. -
Paths get longer.
/dev/shm/pytest-of-<user>/pytest-N/popen-gwN/shm-tmp/tmpXXXXXXXXis much longer than/tmp/tmpXXXXXXXX, which matters for the 107-byte limit onAF_UNIXsocket paths. -
Files use RAM. Everything a session writes counts against memory until the session ends. Base directories of failing sessions stay until the machine reboots or later failing sessions push them out of pytest's retention of three numbered directories; passing sessions reuse the freed number instead of advancing it.
-
Plugin autoloading. With
PYTEST_DISABLE_PLUGIN_AUTOLOADset, pass-p shmto load the plugin.
Development
just install # uv sync --dev
just test # uv run pytest -n auto
just lint # ruff, mypy, ty
The tests run real pytest sessions in subprocesses with pytester and inspect what they leave behind on /dev/shm.
License
MIT
Release files for pytest-shm 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| pytest_shm-0.1.0.tar.gz | 11.0 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| pytest_shm-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 20.1 kB
Release files / pytest_shm-0.1.0.tar.gz
| Download URL | pytest_shm-0.1.0.tar.gz |
|---|---|
| Size | 11.0 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
eaa8dac8aa971f3f65763282d1401881fe16364b2ff92bb9adb101638e2d32b0
|
|
BLAKE2b-256 checksum How to use checksums |
01362ac5bb078e3e25571b71025559c55e9b2490dc447ae169f32ba2262d3b75
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 25, 2026.
Transparency logRelease files / pytest_shm-0.1.0-py3-none-any.whl
| Download URL | pytest_shm-0.1.0-py3-none-any.whl |
|---|---|
| Size | 9.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
bfdc62f3b3073b239097f7e66e0cd07c3823f3fe313a4a71ef3773bd9d4cefb4
|
|
BLAKE2b-256 checksum How to use checksums |
cad672b2d1987b48eb4e149a15d0f264c45e5bd6d662e63f490a46c30ade985c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 25, 2026.
Transparency log