Skip to main content

pytest-bashdoctest

PyPI version Python 3.12+ CI Status License: MIT

A pytest plugin for testing bash command examples in markdown documentation.

Why pytest-bashdoctest?

API documentation gets outdated fast. Manually testing curl examples is tedious. This plugin automatically tests bash code blocks in your markdown files against actual API responses, keeping docs and code in sync.

Installation

pip install pytest-bashdoctest

Or with uv:

uv add pytest-bashdoctest

Quick Start

1. Write testable examples

In your API.md:

## User API

```bash
$ curl -s "https://api.github.com/users/dnouri"
{
  "login": "dnouri",
  ...
  "name": "Daniel Nouri",
  ...
  "bio": "Machine Learning and Programming",
  ...
}
```

The ... pattern matches any content, perfect for skipping dynamic or irrelevant fields.

2. Run tests

pytest --bashdoctest API.md -v

Your documentation is now executable and verified.

Note: The --bashdoctest flag is required to enable bash doctest collection. This prevents conflicts with other pytest plugins that also process markdown files (like pytest-markdown-docs).

ELLIPSIS Patterns

Line-Level: Skip Blocks

Use standalone ... to skip entire sections:

```bash
$ curl -s "https://api.github.com/users/dnouri"
{
  "login": "dnouri",
  ...
  "type": "User",
  ...
  "bio": "Machine Learning and Programming",
  ...
}
```

Show the fields that matter, skip the rest.

String-Level: Partial Matching

Use ... inside strings for dynamic values:

```bash
$ curl -s "https://api.github.com/users/dnouri"
{
  "login": "dnouri",
  ...
  "avatar_url": "https://avatars.githubusercontent.com/u/...?v=4",
  ...
  "created_at": "2009-...-...T...Z",
  ...
}
```

Useful for URLs, UUIDs, timestamps, and other values that change but follow a pattern.

Collection-Level: Objects and Arrays

Use {...} and [...] to match entire structures:

```bash
$ curl -s "https://api.github.com/repos/dnouri/nolearn"
{
  ...
  "name": "nolearn",
  ...
  "owner": {...},
  ...
  "license": {...},
  ...
}
```

Focus on the structure you care about without showing every detail.

Combining Patterns

Mix all three for flexible matching:

```bash
$ curl -s "https://api.github.com/users/dnouri"
{
  "login": "dnouri",
  ...
  "avatar_url": "https://avatars.githubusercontent.com/...",
  ...
  "bio": "Machine Learning and Programming",
  ...
  "public_repos": ...,
  ...
}
```

Configuration

Specifying Files to Test

Explicit paths:

pytest --bashdoctest README.md -v
pytest --bashdoctest docs/api.md -v

Via configuration in pyproject.toml:

[tool.pytest.ini_options]
testpaths = ["tests", "README.md", "docs"]

Then pytest --bashdoctest will test both your test suite and documentation.

Environment Variables

The Problem: Your bash examples often need API keys, custom URLs, or other configuration. Hard-coding these in documentation is insecure and inflexible.

The Solution: The bashdoctest_env fixture. This session-scoped fixture provides environment variables to all bash examples in your markdown files.

When you DON'T need it: If your bash examples only use public APIs without authentication (like the GitHub examples in this README), you don't need to define this fixture. The plugin provides a default empty fixture.

When you DO need it: If your examples need API keys, custom URLs, or other config, create conftest.py:

import os
import pytest

@pytest.fixture(scope="session")
def bashdoctest_env():
    """Environment variables for bash documentation examples."""
    api_key = os.getenv("API_KEY")
    if not api_key:
        pytest.skip("API_KEY not set - skipping bash doctest examples")

    return {
        "API_KEY": api_key,
        "API_URL": os.getenv("API_URL", "https://api.example.com"),
    }

Use in your markdown files:

```
$ curl -s "$API_URL/users" -H "Authorization: Bearer $API_KEY"
{
  "users": [...]
}
```

The environment variables from your bashdoctest_env fixture are merged with os.environ when executing commands, so $API_KEY and $API_URL will be available to all bash examples.

How It Works

  1. Plugin activates when you pass --bashdoctest flag
  2. Collects markdown files you specify (explicit paths or via testpaths)
  3. Parser extracts bash code blocks (only files with actual bash examples)
  4. Each bash block becomes a test item
  5. Commands execute with your bashdoctest_env variables merged into environment
  6. Output matches against expected using ELLIPSIS rules

Architecture:

src/pytest_bashdoctest/
   parser.py      # Extract bash blocks
   matcher.py     # ELLIPSIS matching
   executor.py    # Run commands
   formatter.py   # Format failures
   plugin.py      # Pytest hooks

Core modules are pure Python (no pytest dependency) for portability.

Testing the Plugin

# Run unit tests
pytest tests/ -v

# Test with coverage
pytest tests/ --cov=pytest_bashdoctest --cov-report=term-missing

# Test this README (dogfooding!)
pytest --bashdoctest README.md -v

Limitations

  • Commands timeout after 30 seconds (configurable in executor.py)
  • Interactive commands will hang
  • Uses shell=True (commands come from your trusted markdown files)
  • Output buffering may differ for very large responses

Development

Setup

git clone https://github.com/dnouri/pytest-bashdoctest.git
cd pytest-bashdoctest

# Install dependencies
uv sync --dev

# Install pre-commit tool globally (recommended, one-time setup)
uv tool install pre-commit

# Install git hooks (runs automatically on every commit)
pre-commit install

Running Tests

# Run unit tests
uv run pytest tests/ -v

# Test README examples
uv run pytest --bashdoctest README.md -v

# Run with coverage
uv run pytest tests/ --cov=pytest_bashdoctest --cov-report=term-missing

Code Quality

Pre-commit hooks automatically run on every commit to ensure code quality. To run manually:

# Run all pre-commit hooks (after uv tool install pre-commit)
pre-commit run --all-files

# Or using project dependencies
uv run pre-commit run --all-files

# Run specific checks
uv run ruff check .              # Linting
uv run ruff format .             # Auto-format code
uv run ruff check --fix .        # Auto-fix linting issues

To skip pre-commit hooks (not recommended):

git commit --no-verify -m "message"

Releasing

Update version in pyproject.toml, then:

git tag v2025.10.4 && git push --tags

Publishes to PyPI automatically via GitHub Actions.

License

MIT License - see LICENSE file for details.

Metadata

Release files for pytest-bashdoctest 2025.10.3

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for pytest-bashdoctest 2025.10.3
File Size Uploaded
pytest_bashdoctest-2025.10.3.tar.gz 17.4 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for pytest-bashdoctest 2025.10.3
File Interpreter ABI Platform
pytest_bashdoctest-2025.10.3-py3-none-any.whl Python 3 none any Details

Total release size: 31.3 kB

Release files / pytest_bashdoctest-2025.10.3.tar.gz

Download URL pytest_bashdoctest-2025.10.3.tar.gz
Size 17.4 kB
Tags Source
SHA-256 checksum
How to use checksums
89e103dbf69ab89ec483f3e3a4eed5e7efc4beac446a757e066eda4a3e6df50d
BLAKE2b-256 checksum
How to use checksums
769850dc0b78878dee303ae4f2e1589430dcc8f0d7692ed9765d2ce242af53f0
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.7

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 Oct 3, 2025.

Transparency log

Release files / pytest_bashdoctest-2025.10.3-py3-none-any.whl

Download URL pytest_bashdoctest-2025.10.3-py3-none-any.whl
Size 13.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
1560cc6a48ae5649523ab0a6f338c2ed8b6d3d95645a76099f4191af19676271
BLAKE2b-256 checksum
How to use checksums
d1eee180271d112093e9be288151f65f07870f2ba1fa7d1d98a3b76f91eaa235
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.7

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 Oct 3, 2025.

Transparency log

Release history Release notifications | RSS feed

This release

2025.10.3 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page