matrix-axon

Smoke Testing Plan

Status: S1 has shipped, and the stub lane is now the required PR job this plan originally called for — smoke.yml runs scripts/smoke-gate.sh tui on every pull request (no Docker, ~90s). The Docker-backed lanes remain off the PR path and run on push to main and nightly; scripts/smoke-gate.sh all also runs locally via the pre-push hook’s RUN_SMOKE= opt-in (the smoke-gate hook in .pre-commit-config.yaml, which fires for git and jj alike — ADR 0092). The lanes were split once measured: keeping all of them manual meant nothing ran automatically, and scroll_pin_on_relation_refresh sat broken on main undetected (#190). Most of S2’s journey coverage (login, the full mutation set, live updates/resilience, the live-stack journey) is also built out under the same harness. S3 (dedicated Windows/macOS PTY runners) and S4 (external-homeserver profile) have not started. This plan predates 7b bearer-token auth landing — see the note under “Assumptions” below, which is now stale in one respect.

The nightly and post-merge smoke workflow also runs ADR 0097’s four Matrix OAuth QR API and interoperability lanes in a separate job. That job uploads no failure artifacts because its throwaway state handles OAuth and encryption secrets.

Summary

Stand up black-box smoke coverage for the two shipped binaries:

Both harnesses are Rust workspace members that depend on zero axon-* crates — they interact with the binaries the way a user or operator does: process spawn, HTTP, WebSocket, keystrokes. The work is sequenced in four milestones (S1–S4) so a minimal PR-blocking smoke job lands quickly and the heavier infrastructure follows only after the basic suite has proven its value.

What exists today, and the gap smoke fills

We are not starting from zero. Current coverage:

What none of that covers is the smoke target:

Smoke scenarios target only this delta. Logic permutations stay in unit tests; contract details stay in the axon-api tests; deep E2EE stays in integration-test.sh, which is kept as-is.

Language and tooling: Rust

The first draft proposed Python (pytest + pexpect + pyte + pywinpty) on the rationale that a non-Rust harness guarantees black-box discipline and survives a future repo split. That rationale does not hold up: black-boxness is a dependency property, not a language property. A Rust crate that depends on no axon-* crate and only spawns released binaries is exactly as black-box as a Python package, and each harness moves intact in a split (smoke/server with the server, smoke/tui with the client).

Given that, Rust wins the remaining tradeoffs:

Harness shape. Each smoke package is a binary, not a #[test] suite:

cargo run -p axon-smoke-server -- --profile true-local [--filter NAME]
cargo run -p axon-smoke-tui    -- --profile stub  [--filter NAME]

A sequential scenario runner owns the expensive shared environment (compose stack, server process), controls ordering and teardown, captures artifacts on failure, and exits nonzero with a per-scenario summary. This avoids fighting test-framework process models (cargo test’s parallelism, nextest’s process-per-test) for a suite whose scenarios deliberately share one stateful environment, and it keeps reporting needs down to an exit code plus uploaded artifacts — no JUnit machinery.

Shared harness rules

Server smoke (axon-smoke-server)

Profiles

Scenarios

TUI smoke (axon-smoke-tui)

Terminal driver

Spawn axon-tui under portable-pty with fixed dimensions and a controlled environment (temporary config dir, pinned TERM and locale, isolated working directory). All output feeds a vt100 screen; assertions read rendered rows, not raw ANSI. Driver surface: spawn, send_keys, resize, wait_for(predicate), screen_text, terminate.

Profiles

Scenarios

Milestones

CI

Assumptions

References