Metadata-Version: 2.4
Name: pytest-live-pause
Version: 0.1.1
Summary: Pytest plugin and protocol for pausing live test execution and resuming in-process
License-File: LICENSE
Requires-Python: >=3.10
Requires-Dist: pytest>=9.0.3
Description-Content-Type: text/markdown

## pytest-live-pause

`pytest-live-pause` is intended to be a standalone pytest plugin and runtime protocol for pausing a live test process, inspecting state before teardown, and resuming the same process through a structured control channel.

The repository is currently in bootstrap stage. The design target is:

- a transport-neutral pause runtime
- a thin pytest plugin adapter
- structured stdout and JSONL events
- packaged CLI commands for monitoring and resuming paused runs
- a compatibility layer for existing QAMule-style workflows

The current design scope lives in `CURRENT_SCOPE.md`.

## Current Status

The first working slice is `--pause-on-failure` using a file-backed control channel.

Checkpoint pauses are also supported through `pytest_live_pause.checkpoint(...)`.

The plugin also auto-registers `live_pause` and `pause_run_id` fixtures for direct injection into test function arguments.

When a test fails in the `call` phase and `--pause-on-failure` is enabled, the plugin:

- writes a pause request JSON file
- waits for an external resume command file or timeout
- resumes the same pytest process before teardown starts
- appends any externally supplied failure reason to the pytest report

Tests can also pause explicitly:

```python
from pytest_live_pause import checkpoint


def test_example():
	decision = checkpoint(task="Approve the external step", timeout=300)
	if decision.result is False:
		raise AssertionError(decision.reason or "checkpoint failed")
```

	Or through the auto-registered fixture:

	```python
	def test_example(live_pause, pause_run_id):
		assert live_pause.run_id == pause_run_id
		decision = live_pause.checkpoint(task="Approve the external step", timeout=300)
		assert decision.result is True
	```

Checkpoint resumes use `--result` and `--reason`:

```bash
pytest-live-pause resume <pause_id> --result true --reason "approved"
```

Checkpoint behavior can also be mocked during pytest startup:

```bash
pytest --pause-checkpoint-mock false
```

## Usage

Run pytest with pause-on-failure enabled:

```bash
pytest --pause-on-failure --pause-timeout 300
```

By default, pause state is written under `/tmp/pytest-live-pause`.

You can still override it explicitly:

```bash
pytest --pause-on-failure --pause-dir /some/other/path --pause-timeout 300
```

When a test fails, a pause file appears under the run directory:

```text

/tmp/pytest-live-pause/
	runs/<run_id>/
		events.jsonl
		run.json
		pauses/<pause_id>.json
		commands/
```

Resume the paused failure by pointing the CLI at the pause file:

```bash
pytest-live-pause resume \
	/tmp/pytest-live-pause/runs/<run_id>/pauses/<pause_id>.json \
	--failure-reason "manually confirmed"
```

You can also resume directly by `pause_id`, which is more convenient when multiple pytest processes share the same pause directory:

```bash
pytest-live-pause resume <pause_id> --failure-reason "manually confirmed"
```

If you are using a non-default pause directory, pass it explicitly:

```bash
pytest-live-pause resume <pause_id> --pause-dir /some/other/path --failure-reason "manually confirmed"
```

For failure pauses, `--failure-reason` appends an extra explanation to the pytest failure report. Non-failure pauses must not receive `--failure-reason`; the CLI will reject that combination.

For checkpoint pauses, use `--result true|false|none` and optional `--reason`.

For mocked checkpoint pauses, `reason` is fixed to `Mock`.

The CLI writes the matching command file into `commands/`. The pytest process polls for that command and then continues into teardown.

When multiple pytest processes share the same pause directory, each process gets its own `runs/<run_id>/` directory. `run.json` and each pause file also record `pid`, `hostname`, `cwd`, and optional `worker_id` metadata.

You can inspect currently pending pauses across all runs with:

```bash
pytest-live-pause ls
```

You can also watch for newly paused tests in real time:

```bash
pytest-live-pause watch --run-id <run_id>
```

`watch` requires a specific pytest `run_id`:

```bash
pytest-live-pause watch --run-id <run_id>
```

For bounded monitoring, pass a timeout:

```bash
pytest-live-pause watch --run-id <run_id> --timeout 30
```

`watch` waits until the latest matching pending pause appears, prints the pause details, and then exits.
If the monitored pytest process exits while `watch` is still polling, `watch` now stops immediately and reports that the run is no longer active.

By default, `ls` only shows currently resumable `pending` pauses. To include `stale`, `resumed`, and `timed_out` entries, use:

```bash
pytest-live-pause ls -a
```

If a pytest process is killed while paused, its pause file remains on disk. The CLI treats that as `stale` when the recorded `pid` is no longer alive on the current host. `ls -a` will show the stale state, and `resume` will refuse to write a command for it.

If no command arrives before `--pause-timeout`, the test resumes automatically without adding an external failure reason.

When pytest starts, it prints the current `run_id`, so you can monitor that specific pytest process with `pytest-live-pause watch --run-id <run_id>`.

## File Protocol

`pauses/<pause_id>.json` currently contains:

```json
{
	"pause_id": "...",
	"run_id": "...",
	"nodeid": "tests/test_sample.py::test_failure",
	"timeout": 300.0,
	"original_failure": "AssertionError: boom",
	"status": "pending",
	"created_at": 1710000000.0,
	"pid": 12345,
	"hostname": "host.local",
	"cwd": "/repo"
}
```

`commands/<pause_id>.resume.json` currently contains:

```json
{
	"failure_reason": "manually confirmed"
}
```

Or for a checkpoint pause:

```json
{
	"result": true,
	"reason": "approved"
}
```

This file transport is only the first implementation. The runtime can be split from the transport later if you want to add sockets or HTTP.
