Skip to main content

Add your description here

Project description

bridle

A harness for running pytest. It handles the collection and upload of test result information in a way that is resilient to crashes/OOMs in the code under test.

Features

  • Crash resilience — pytest runs in a subprocess; events are flushed to disk after every test. A TestStarted event is written before each test runs, followed by a TestFinished event on completion. If the subprocess segfaults or OOMs mid-test, resolve_events() detects the unmatched TestStarted and synthesizes a failed TestFinished.
  • Pluggable backends — can register one or more backends to upload test results to.
  • Rich console output — summary table with outcome counts and duration, plus detailed failure panels, all printed to stderr.
  • Exit code passthrough — the harness returns the subprocess exit code so CI can gate on it.
  • Per-test and total timeouts--test-timeout-sec N kills the subprocess if any single test exceeds N seconds; --total-timeout-sec N kills it if the entire run exceeds N seconds. Timed-out tests appear as normal failures with a timeout-specific message in longrepr.

JSONL Schema

The JSONL file contains a tagged union of two event types, discriminated by the type field:

TestStarted — emitted before each test runs

{"type":"test_started","nodeid":"tests/test_a.py::test_ok","start":1735689600.0,"location":["tests/test_a.py",0,"test_ok"]}
Field Type Description
type "test_started" Event discriminator
nodeid string Pytest node ID
start float Epoch timestamp when the test starts
location [string, int|null, string] [filepath, lineno, domain], or null

TestFinished — emitted when a test phase completes

{"type":"test_finished","nodeid":"tests/test_a.py::test_ok","outcome":"passed","when":"call","duration":0.005,"start":1735689600.0,"stop":1735689600.005,"location":["tests/test_a.py",0,"test_ok"],"longrepr":null,"sections":null,"wasxfail":null}
Field Type Description
type "test_finished" Event discriminator
nodeid string Pytest node ID
outcome string passed, failed, skipped, error, xfailed, xpassed
when string Phase: setup, call, or teardown
duration float Test duration in seconds
start float Epoch timestamp when the phase started
stop float Epoch timestamp when the phase ended
location [string, int|null, string] [filepath, lineno, domain], or null
longrepr string | null Failure representation, null on pass
sections [[string, string]] | null Captured output sections, e.g. [["Captured stdout call", "..."]]
wasxfail string | null xfail reason if the test was marked xfail

TestEvent = TestStarted | TestFinished is a discriminated union. Use resolve_events() to match pairs — unmatched TestStarted events become synthetic failed TestFinished entries.

Timeouts

Kill tests that hang or entire runs that take too long:

# Kill any single test that runs longer than 30 seconds
uv run bridle tests/ --test-timeout-sec 30

# Kill the entire run if it exceeds 2 minutes
uv run bridle tests/ --total-timeout-sec 120

# Both can be combined
uv run bridle tests/ --test-timeout-sec 30 --total-timeout-sec 120

When a timeout fires, the subprocess is killed and TestFinished events are written for all active tests with a timeout-specific longrepr. Downstream code (display, backends) sees them as normal failed tests.

Buildkite Test Analytics

The buildkite backend uploads test results to Buildkite Test Analytics for flaky test detection, suite analytics, and performance tracking.

Setup

Set the BUILDKITE_ANALYTICS_TOKEN environment variable to your Buildkite Test Analytics suite API token:

export BUILDKITE_ANALYTICS_TOKEN="your-suite-token"
uv run bridle tests/ --backend buildkite

Environment Variables

Variable Required Description
BUILDKITE_ANALYTICS_TOKEN Yes Suite API token for authentication
BUILDKITE_ANALYTICS_API_URL No Custom API endpoint (defaults to https://analytics-api.buildkite.com/v1/uploads)

The backend automatically detects CI environment (Buildkite, GitHub Actions, CircleCI) from standard CI env vars. If no CI provider is detected, it falls back to a generic environment.

Behavior

  • Upload is best-effort: HTTP/network errors are logged as warnings, never propagated.
  • If BUILDKITE_ANALYTICS_TOKEN is not set, a warning is logged and upload is skipped.
  • Test results are uploaded in batches of 100.
  • Crash events (unmatched TestStarted) are reported as failed tests.

Adding a Backend

Subclass Backend and register it in backends/__init__.py:

from bridle.backends._base import Backend
from bridle._schema import TestEvent

class MyBackend(Backend):
    def name(self) -> str:
        return "my-backend"

    def upload(self, events: list[TestEvent]) -> None:
        # upload logic here
        ...

Then add it to the registry in backends/__init__.py:

_REGISTRY: dict[str, type[Backend]] = {
    "stub": StubBackend,
    "my-backend": MyBackend,
}

Development

# Install dev dependencies
uv sync

# Run tests
uv run pytest tests/

# Update syrupy snapshots
uv run pytest tests/ --snapshot-update

Project Structure

src/bridle/
├── __init__.py          # main() entrypoint
├── __main__.py          # python -m support
├── _schema.py           # TestStarted/TestFinished pydantic models + Outcome enum + JSONL ser/de
├── _monitor.py          # Subprocess monitor with per-test and total timeout enforcement
├── _plugin.py           # TestResultPlugin (pytest plugin, flush-per-line)
├── _runner.py           # Subprocess entry point
├── _harness.py          # Orchestrator: argparse, subprocess, read results, dispatch
├── _console.py          # Rich-formatted output helpers
└── backends/
    ├── __init__.py      # Registry + get_backend()
    ├── _base.py         # Abstract Backend base class
    ├── _buildkite.py    # BuildkiteBackend (Buildkite Test Analytics)
    └── _stub.py         # StubBackend (logs to console)

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

bridle-0.1.0.tar.gz (10.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

bridle-0.1.0-py3-none-any.whl (15.2 kB view details)

Uploaded Python 3

File details

Details for the file bridle-0.1.0.tar.gz.

File metadata

  • Download URL: bridle-0.1.0.tar.gz
  • Upload date:
  • Size: 10.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.10.6 {"installer":{"name":"uv","version":"0.10.6","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for bridle-0.1.0.tar.gz
Algorithm Hash digest
SHA256 725c0251158d3b8a1fb0218637313d6645646bec3cabc0f6225fb0d4ca1f98dc
MD5 8354b4204253131521a88e98686c437e
BLAKE2b-256 48290ac4185350015ac7ef39954788f0ddbaa093d300bc28c3310dbc37b408c6

See more details on using hashes here.

File details

Details for the file bridle-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: bridle-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 15.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.10.6 {"installer":{"name":"uv","version":"0.10.6","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for bridle-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 b3ad61f86bba453c88fff8da295c4b0ea8bec5a77dd84ab2af70798a19c96dd4
MD5 4ae98222c9dff4428c33b4e08790c281
BLAKE2b-256 17218ceec7195e698be695b71f2e2b749326cbf0aad0c49003dba2cdde1da267

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page