Skip to main content
Merit provides a pytest-inspired CLI for running tests, filtering by tags or keywords, controlling concurrency, and reporting results. This page covers how to run merits and how the reporting system works, referencing where the behavior lives in the codebase.

Basic Usage

Run all discovered merits in the current directory:
Run merits from specific paths:

Filtering Tests

By Keyword Expression

Use -k to filter tests by name with boolean expressions:
Keyword matching is substring-based: -k agent matches merit_agent_response, merit_weather_agent, etc.

By Tags

Use -t/--tag to include tests with specific tags:
Use --skip-tag to exclude tests:
Combine filters:

Controlling Execution

Stop on Failure

--maxfail N - Stop after N failures:
--fail-fast - Stop at the first failed assertion within a test:
Without --fail-fast, Merit collects all assertion failures in a test. With it, the test stops at the first failure.

Concurrency

Control parallel test execution with --concurrency:
When to use concurrency:
  • Sequential (1): Default. Predictable output, easier debugging.
  • Concurrent (>1): Faster runs for independent tests. Use with stateless SUTs.
  • Unlimited (0): Maximum parallelism for large test suites.
For synchronous merits (def merit_*), Merit runs test bodies in worker threads by default to keep the event loop responsive. Use @merit.run_inline on a sync merit when it must run on the main event-loop thread.

Timeout

Set a global timeout for the entire test run:
The timeout applies to the entire test session, not individual tests. Timeout is cooperative: when the timeout is reached, Merit marks the run as stopped early and stops starting new tests, but in-flight work may not stop immediately (especially synchronous merits already running in worker threads).

Verbosity

Control output detail with -v (verbose) or -q (quiet):
Verbosity levels:
  • -qq or lower: Only failed/errored tests shown
  • -q: Less output
  • Default (0): Standard output
  • -v, -vv: More detail

Output Capture

By default, Merit captures stdout and stderr during test execution. Use -s to show output live:
This is useful for debugging tests with print statements or logging output.

Tracing

Enable OpenTelemetry tracing to capture spans from your SUT and tests:
Use traces for:
  • Asserting tool calls in agent tests
  • Debugging LLM request/response flows
  • Performance analysis
See SUT for trace assertions.

Custom Run UUID

By default, Merit generates a run UUID automatically. You can provide one explicitly when you need a stable external correlation ID (for example, linking CI jobs to Merit runs).

CLI

Provide a UUID with --run-id:
If that UUID already exists in the configured SQLite database, the command exits with code 2 and no tests are executed.

Python API

You can set a default run UUID on the runner, and override it per run() call:
Run IDs are currently configured only via CLI --run-id or Python API parameters. They are not read from pyproject.toml, merit.toml, or environment variables. If save_to_db=True and the selected run UUID already exists, Runner.run() raises ValueError.

Configuration Files

Define default options in pyproject.toml or merit.toml: pyproject.toml:
merit.toml:
Precedence: CLI args override config files. Config files are discovered by walking up the directory tree from the current working directory.

Understanding Test Output

Merit reports test status as tests complete with a compact line per file by default:
Use -v to show per-test lines with durations and detailed sub-results.
When using ConsoleReporter live mode (internally implemented with Rich Live), very long verbose output can exceed the terminal’s vertical render limit. In that case, live in-flight updates may appear to stop until the run finishes, which is more likely with many iterated/case-grouped subtests at -v/-vv. No result data is lost: Merit still records everything and prints the full output at run completion.
Status symbols:
  • (green): PASSED - Test succeeded
  • (red): FAILED - Assertion failed
  • ! (yellow): ERROR - Unexpected exception
  • - (yellow): SKIPPED - Test was skipped
  • x (blue): XFAILED - Expected failure occurred
  • ! (magenta): XPASSED - Expected failure passed (usually bad)
Exit codes:
  • 0: All tests passed (or only skipped/xfailed)
  • 1: At least one test failed or errored
  • 2: Invalid CLI usage or configuration error (including duplicate --run-id)

Repeated Tests

For tests with @merit.repeat(), verbose output shows aggregated results:
The individual run results are attached to execution.sub_executions.

Reporter System

Merit uses an async reporter architecture for flexible output handling.

The Reporter Interface

All reporters implement the Reporter ABC from src/merit/reports/base.py:

Built-in Reporters

ConsoleReporter (default): Outputs to terminal with Rich formatting.

Creating Custom Reporters

Create custom reporters by subclassing Reporter and implementing all abstract methods. Override on_test_start if you need live in-flight state:

Using Multiple Reporters

Use multiple reporters simultaneously with the programmatic API:
Note: Currently, only ConsoleReporter is built-in. Create custom reporters for JSON, HTML, database, or external service integration.

Examples

Run smoke tests concurrently:
Debug a specific test with tracing:
Run fast tests, stop on first failure:
CI configuration (pyproject.toml):

Database Persistence

Merit automatically persists test run data to a SQLite database for historical tracking and analysis.

Database Location

By default, Merit stores the database at the project root:
The database location is determined by walking up from the current directory to find the first directory containing pyproject.toml.

Disabling Database Persistence

To disable database writes (e.g., for CI environments or quick local runs):

Custom Database Path

Specify a custom database location:
Or configure in pyproject.toml:

Database Management Commands

Merit provides CLI commands to manage the database:

Check Database Status

View current schema version and pending migrations:
Output:

Run Migrations

Apply pending schema migrations:
Dry run (preview without applying):

Backup Database

Create a timestamped backup:
Creates: .merit/merit.db.backup.YYYYMMDD_HHMMSS

Reset Database

Delete and recreate the database (destructive):
Without --yes, Merit prints a warning and exits without resetting the database.

What’s Stored

The database stores:
  • Test run metadata (timestamp, environment, git info)
  • Individual test executions and results
  • Assertion results and predicate outcomes
  • Metric results and statistical data
  • Trace references (if tracing enabled)
  • Error tracebacks for failed tests

Database Schema

The schema is versioned and managed through migrations. Current tables include:
  • runs - Test run sessions
  • test_executions - Individual test results
  • metrics - Aggregated metrics
  • assertions - Assertion outcomes
  • predicates - Predicate results linked to assertions
  • trace_spans - Trace spans linked to executions
Schema version is stored in SQLite PRAGMA user_version (shown by merit db status as “Current version”).

Writing Merits

Learn how to write merit functions and use decorators

Merit Concept

Deep dive into discovery, parametrization, and execution