Testing

Run focused regressions and the full verification gates.

The project uses focused regression tests in tests/async/, plus schema/migration checks at tests/ root. The root Makefile provides a single gate while retaining fast commands for focused work.

Layout

text
tests/
├── async/                       # focused regression + source-contract tests
│   ├── test_*.py                # plain python3 regressions; no framework
│   ├── run_*.sh                 # special-purpose and legacy thin wrappers
│   └── run_*_mysql.sh           # MySQL-backed schema-contract tests (need a live DB)
├── run_regression_tests.py      # discovery, bounded parallelism, deadlines, failure summary
├── compare_bootstrap_mud_schema.sh   # diff live schema vs bootstrap baseline
├── test_migration_replay_safety.sh   # migration re-run safety
└── test_run_migration_persistence_schema.sh

Test styles

Behavioral tests compile the production source into a native harness, or boot the server in a journey, and check what the code does. A new test is behavioral whenever a harness or a journey can reach the code: prefer more assertions in an existing harness for the same source, which adds no compile.

Source-contract tests read the sources as text. One fails on a harmless rename or a reshaped block, and passes when the behavior is wrong but the text still matches, so a text check stays only for what text alone can show: a call site that must not come back, or an order that must hold. Before keeping one, break the code it guards; when another test then fails, the text check is redundant. Example (tests/async/test_sql_pool_shutdown.py): slices out sql_pool_acquire/sql_pool_shutdown from src/sql/sql_pool.c and asserts the closing-pool checks are present.

Schema tests run migrations and repositories against a disposable MariaDB or MySQL. Every one is a line of tests/run_db_tests.sh, so make test-db runs it: a test no gate runs stops passing unnoticed. A leg that needs the full schema runs under tests/async/with_disposable_mariadb.sh and sources tests/async/disposable_schema.sh; none reads the checkout's .env.

Counts and hashes. Check the property a count stands for (every entry has an owner and a retention, names are unique, one array per class), not the count, unless the count itself is the contract, such as the sealed migrations' ids or the shops' durable ids. A count that grows with content breaks the test when nothing is wrong. Hashes stay where they guard a generated file against hand edits.

Harness stubs. A native harness compiles tests/async/harness_stubs.cpp (_paths.HARNESS_STUBS from Python) for the server functions it does not link. Its definitions are weak, so the real source or a harness's own definition replaces them at link time; write a local stub only where it must behave differently. A harness built on its own types, without the server's headers, keeps its stubs.

Running

bash
# Complete gate: builds all maintained binaries and tools,
# generates world data, then runs Python and native regression tests.
make test-all

# Regression tests without rebuilding the server or area editor:
make test

# Limit concurrency, filter by filename, or inspect discovery:
make test TEST_JOBS=1
make test-python TEST_MATCH=wear
make test-list

# Single test (preferred while iterating):
python3 tests/async/test_wear_all_regression.py
# or via its wrapper:
tests/async/run_sql_pool_shutdown.sh

# Isolated Docker database legs and MariaDB journeys (Docker is an optional prerequisite):
make test-db

# Full historical legacy upgrade, replay, bootstrap equivalence, and compatibility:
tests/async/run_legacy_migration_mysql.sh

# Runtime schema compatibility on the supported variants (MySQL 8.0, MariaDB 10.11 and
# 11.8), and the migration runner on all three:
tests/async/run_runtime_compatibility_mysql.sh
RUNTIME_DB_IMAGE=mariadb:10.11 tests/async/run_runtime_compatibility_mysql.sh
RUNTIME_DB_IMAGE=mariadb:11.8 tests/async/run_runtime_compatibility_mysql.sh
python3 tests/async/run_migration_runner_engines.py

TEST_JOBS=0 is the default and selects up to eight workers based on available CPUs. Test output is buffered per process so parallel failures remain readable, and a failing test's output is printed as soon as it fails. A test still running 900 s after it started, or a journey 1800 s after, is ended with everything it started and reported as TIMEOUT; a test ended by a signal is reported by the signal's name. Both are failures, the end of the run lists every failure with its kind, the summary counts timeouts and signals apart, and every 60 s a line names the tests still running. However a test ends, whatever it started and left running ends with it. Stopping the runner, by Ctrl-C or a SIGTERM, ends the tests it is running with everything they started, and starts no others. The runner executes every discovered test_*.py in a separate process and returns nonzero if any test fails. The journeys that boot a real server, and the large sanitizer harness, start together once the parallel phase is done. They wait on game time, not on the CPU, and they share one server build.

make test-all deliberately excludes Docker and externally provisioned database checks. make test-db builds the server and generates the world first, then runs the isolated database legs and the MariaDB journeys listed in tests/run_db_tests.sh, 12 at a time (TEST_DB_JOBS=N changes that). Every test creates and destroys its own MySQL or MariaDB container, prints PASS or FAIL with its seconds, and writes its log under bin/tests/db/. A new leg or journey is one more line in that list; tests/async/with_disposable_mariadb.sh CMD... gives a journey its own MariaDB. The three scripts at the root of tests/ require explicitly named disposable or read-only databases and are manual migration-verification tools. Never point them at production.

The MariaDB playtime journey runs in make test-db. To run it alone after a MariaDB server build, use tests/async/with_disposable_mariadb.sh python3 tests/async/test_mysql_playtime_journey.py --server bin/server/dms_new. The journey creates and removes its own schema and invokes test_playtime_mysql_repository.py with that schema's environment. Both scripts are excluded from argument-free discovery; the database-independent playtime capture, checkpoint, legacy SQL, and flat-file tests remain in make test-all.

The game loop budget journey (test_mysql_game_loop_budget_journey.py, in make test-db) boots the full world on MariaDB, has scripted players camp and rent while the first hourly event runs, and fails if a pulse runs past its 250 ms. It prints what the loop measured (tick, event and command times, deferred events, the costliest callbacks), so it is also the load to measure a build with: tests/async/with_disposable_mariadb.sh python3 tests/async/test_mysql_game_loop_budget_journey.py --server <dms_new> --players 30 --hours 2.

The world capture journey (test_mysql_world_capture_journey.py, in make test-db) boots the full world on MariaDB and Redis with world recovery on and 8,000 more objects on the ground, and has scripted players play while the first capture runs. It fails if the generation is not above the old 64 MiB ceiling, if its capture takes a third of its 300 s, or if a pulse runs past its 250 ms; it then kills the server, and fails unless the next boot restores the generation. It prints the generation's size, the capture's time and what the restore brought back; --players N scales the load. Its first boot is on an empty database, so it is also the boot test for SQL queued before the writer starts: it fails unless every artifact the boot created has its artifacts and artifact_domain_state rows, each outpost's stored hit points are set and the frag list cache is filled. Both boots must cache the six artifact lists (the restored one reads them after the restore), and neither may log a sql job line (not queued, or failed at boot). run_world_recovery_journey.py, also in make test-db, covers the switch on the mini world: off, the alert of an outage, the writer coming back, a crash's restore and the defaults. run_world_restart_journey.py <server> <scenario> covers what a restart leaves, one scenario a run and each its own line in make test-db: crash (the lease is renewed, the boot after a crash consumes the generation it restored and publishes its own, and a second crash restores that one), copyover (the new image holds the lease at once), restart (a clean stop takes a last capture and the boot restores it), taken (an object a character took and saved after the capture is not restored to the floor, and one it saved and dropped before the capture is; the boot reaps the dropped object's ownership record with the dissolved starter kit's, so the login after it counts no missing_payload_rows and the saves wrote no unowned_object line, both without DURIS_PERSISTENCE_TRACE), handover (a character takes the banana after the capture and is checkpointed, gives it to a second one, who saves, drops it and saves, and the server crashes before the giver's next checkpoint: the boot keeps the record, which is what makes the giver's older copy load as stale, so the floor copy comes back and only one character holds the banana), midcapture (an object that leaves a floor container while a capture runs, and whose drop is journaled, is restored once) and slowread (a boot whose reads of Redis miss their deadline asks again and restores).

MySQL fixtures that reuse a table within a statement must account for MySQL's Can't reopen table restriction on connection-local temporary tables; MariaDB may accept the same fixture. Use isolated ordinary tables with explicit cleanup when needed to execute the actual production query shape. Do not weaken queries or narrow the backend matrix merely to make a temporary-table fixture pass.

Shared journey server artifacts

The Python runner defaults DURIS_REGRESSION_BUILD_CACHE to bin/regression-artifacts. Account recovery, flat-file boot preflight, Chaos kit, combat and full-world boot acquire one compatible flat-file server from tests/async/server_build_artifacts.py. Each journey retains its own temporary authority, journals, logs, listeners and process cleanup. Executables are shared read-only; runtime state is never stored in the artifact directory. The item-prompt ASan/UBSan harness remains a separate build with its existing flags and the server build's 600-second ceiling. That one server build uses every core, and the journeys start together when it is done.

The artifact key covers all files under src/ (including untracked files), test headers, the helper contract, the flat-file backend, and the inherited build environment, including profile, feature flags and Make overrides. It also hashes the effective compiler, Make, assembler, linker, compiler internals, installed headers and libraries. Standard GNU compiler search directories and explicit CPATH, CPLUS_INCLUDE_PATH, C_INCLUDE_PATH and LIBRARY_PATH directories, plus explicit include/library paths in Make's effective flags, are fingerprinted by content. Keep external build inputs in these declared paths; use DURIS_REGRESSION_BUILD_CACHE=off for toolchains with hidden inputs or wrappers that do not support GNU compiler discovery. Runtime-only full-world diagnostics and shell working-directory variables do not invalidate a build.

On every acquisition, the helper verifies executable and build-log SHA-256 hashes and rechecks the client-free compilation assertions. Missing or malformed metadata, altered binaries/logs and changed inputs trigger a fresh build. Inputs are checked again after compilation; a build spanning an input change is rejected. A per-key lock prevents duplicate concurrent builds, and atomic manifests publish only successful artifacts. Replacement builds use new directories so an existing process keeps its executable. Old generations remain until the cache is removed when no journeys are using it; make clean-all also removes bin/.

Direct test invocations build privately unless the cache variable is set. To measure the complete Python gate on the same host and worker count:

bash
DURIS_REGRESSION_BUILD_CACHE=off python3 tests/run_regression_tests.py --jobs 4
DURIS_REGRESSION_BUILD_CACHE="$PWD/bin/benchmark-fresh" python3 tests/run_regression_tests.py --jobs 4
DURIS_REGRESSION_BUILD_CACHE="$PWD/bin/benchmark-fresh" python3 tests/run_regression_tests.py --jobs 4

Choose an unused directory for the clean-cache run. Per-journey output reports server build time, artifact validation time, and remaining journey/fixture time. The last category includes inspector compilation and process startup; it is not solely gameplay time. Cache keys hash environment values without storing their plaintext in manifests. Never check cache artifacts into Git.

Before a merge

Run the gate, one command after the other, on a quiet machine. The performance gate (test_telemetry_capacity_272.py) and the latency journeys are CPU-sensitive, and a busy machine fails their time budgets.

bash
./scripts/format.sh --all --check
make test-all -j16 TEST_JOBS=16
make test-db

Runtime depends on the host, selected tests, and build cache. Run the following additional checks when the change touches what they check:

Check When
The backup-recovery container job (root-only test_persistence_backup_integration.py) backup, restore, scripts/backup_*, or the flat-file launcher
CodeQL and Trivy (security.yml) dependencies, packaging/, Dockerfile, network or authentication code, and before a production deploy
npm ci --prefix site && npm test --prefix site docs/ or site/

Do not replay the flatfile-build, quality or build.yml workflow jobs step by step. Their tests are the ones make test-all just ran on the same host.

When a change fixes a defect and adds or changes its regression test, run scripts/check_tests_catch.sh BASE HEAD (BASE is the commit before the fix). For each tests/async/test_*.py the range adds or changes, it runs the test in two fresh worktrees under bin/analysis/, each after make world: HEAD's, where it must pass, and BASE's sources with HEAD's whole tests/ tree. It reports "catches" when the test passes on HEAD and fails on BASE, "does not catch" when it passes on both, and "cannot judge" when it fails on HEAD's tree too, for something a fresh worktree lacks. A test that passes on the code before its fix proves nothing; one reshaped by a refactor may pass and still be right. It reports and does not block: the reviewer reads it, and the landing commit message records it. It does nothing when the range changes no src/. A journey builds a server in each worktree first, about three minutes. The last line of each failure shows how the test failed; a test that extracts a function the old code does not have fails without saying anything about behaviour.

Each run of tests/run_regression_tests.py (so each make test and make test-all) writes bin/test-history/<UTC time>-<short sha>-<pid>.json: the commit, whether the tree was dirty (any changed or untracked file), when it finished, its --match filter and worker count, and each test's path, status and seconds. python3 scripts/test_history.py reads them and lists the tests that both passed and failed on one clean commit, those whose last time rose more than half over their median of the ten runs before that were made like the last (same --match and workers; among tests taking a second or more), and the twenty slowest of the last run. bin/ is ignored, so the history is the checkout's own; a gate run in another worktree keeps its files there.

Coverage

make coverage (or scripts/coverage.sh [--db] [COMMIT]) measures which lines of src/ the suite runs. Nothing else runs it, and its percentage is never a target: it shows what no test reaches. It works in a detached worktree of the commit (HEAD by default) under bin/analysis/coverage-<sha>/, so the checkout and any running gate are untouched, with scripts/coverage/g++ first on PATH. That wrapper gives every compile, the server's, the journeys' flat-file builds and the harnesses', --coverage and its own directory of notes and counts under bin/coverage/<sha>/data/, so a harness built in a temporary directory leaves its counts behind. It runs make -k test-all, and make test-db too with --db (COVERAGE_ARGS=--db make coverage), then writes bin/coverage/<sha>/index.html, summary.txt (per file), directories.txt (per directory) and failed.txt (the tests that failed under instrumentation: their counts are missing, the report is not).

It cannot see a process ended with SIGKILL (the crash probes write no counts; SIGTERM shuts the server down in order, so most journeys count), a test that patches a copy of a server source and compiles the copy (only src/ is reported), or a test that calls c++ or clang++ instead of g++. The wrapper drops -Werror (at -O2 the instrumentation provokes false warnings inside libstdc++), so a test that needs a compile to fail on a warning fails under coverage and is listed in failed.txt.

Mutation testing

python3 scripts/mutate.py [--jobs N] [--journeys] SRC_FILE... measures whether the tests that name a source notice when it is wrong. It writes each mutant (a relational operator flipped, ==/!= or &&/|| swapped, a ! dropped, a returned true, false, 0 or 1 replaced; never in a comment, string or preprocessor line) into a worktree of HEAD under bin/analysis/mutate/, and runs the tests there that name the file, the quickest first (by bin/test-history/), until one fails. It refuses a checkout with uncommitted changes under src/ or tests/, so commit a new test before scoring with it. Each file's tests run once unmutated first; one that fails there is left out of the score and listed. The report, a score per file and every surviving mutant with its line, is printed and kept in bin/analysis/mutate-report.txt. A survivor is a missing test, dead code, or a mutant no caller can tell apart (an equivalent mutant: say why in the commit that leaves it).

scripts/mutate/g++, first on the tests' PATH, splits each harness's one-command build into one ccache compile per source and a link, so a mutant rebuilds only its own file; without ccache it compiles uncached, and mutate.py says so at the start. The journeys run only with --journeys; they name these files to build a helper and take minutes. It never runs in a gate, and its score counts only the tests that name the file.

Fuzzing

Each tests/fuzz/<target>.cpp feeds LLVMFuzzerTestOneInput to code that reads outside input: websocket (the upgrade request and frames), gmcp (a client's Core.Hello and Client.Info) and player_snapshot (save decoding, with an encode-and-decode round trip). Its // fuzz-sources: and // fuzz-libs: lines say what it links, and // fuzz-max-len: its largest input, a little past the sizes its code checks; the shared harness stubs fill in the rest.

make fuzz FUZZ_TARGET=websocket FUZZ_SECONDS=3600 (or scripts/fuzz.sh TARGET [SECONDS]) builds the target with clang's libFuzzer and the address and undefined-behaviour sanitizers under bin/fuzz/<target>/ and fuzzes it, starting from tests/fuzz/corpus/<target>/. It writes what it finds to bin/fuzz/<target>/corpus/ and a crash to bin/fuzz/<target>/crash-*. Copy a crash, and inputs worth keeping, into the committed corpus (bin/fuzz/<target>/fuzzer -merge=1 tests/fuzz/corpus/<target> DIR... keeps the ones that add coverage). Long runs are by hand; nothing schedules them.

tests/async/test_fuzz_corpus.py, in every make test, builds each target with g++, the same sanitizers and a main that feeds it every committed corpus file once, so a fixed crash stays fixed.

Full-world save diagnostics

test_flatfile_full_world_boot.py supports opt-in synthetic failure/recovery experiments without using the configured database:

Variable Effect
DURIS_FULL_WORLD_ARTIFACT_DIR Retain failed synthetic authority, journals, transcripts, and server diagnostics in a private untracked directory; TLS keys are excluded and the fixture password is redacted.
DURIS_FULL_WORLD_REPEATS Run 1-100 fresh fixture journeys with one build.
DURIS_FULL_WORLD_BINARY_CACHE Legacy opt-in cache prefix below bin/; verified generations now live in <prefix>.artifacts/. Existing legacy binary/JSON pairs are not reused. DURIS_REGRESSION_BUILD_CACHE takes precedence when set.
DURIS_FULL_WORLD_DELAY_CAMP=1 Hold the synthetic player lock through camp timeout; verify retention, automatic retry, and a later fresh-intent camp.
DURIS_FULL_WORLD_CRASH_PHASE before_ack or after_ack replaces the first clean shutdown with an intentional crash around the save durability boundary.

Normal runs keep their original deadlines and successful-fixture cleanup. Classify failures by boot, manual save, camp, recovery, or shutdown; distinguish an observation timeout from a rejected save. Retain the revision/worker timeline before diagnosing storage or scheduler latency. A passing rerun does not explain a failure whose timeline was discarded.

The separate combat journey reconnects after creation to hydrate account-bank revision and disables optional boons. It proves that stated fixture scope; it does not prove first-session currency or boon-enabled death/reward behavior.

Validation matrix

No single command proves release readiness. Use the narrowest applicable row while iterating, then run every row required by the session or release gate.

Evidence boundary Command What it proves What it does not prove
Documentation python3 tests/async/test_documentation_contract.py Maintained links, paths, commands, configuration names, safety language, and diagram contracts Runtime or database behavior
Focused source/runtime python3 tests/async/test_<feature>.py One named invariant or compiled harness Unrelated domains or integrated load
Server build make -C src C++20 server compiles under the warning profile Schema compatibility or runtime readiness
Repository gate make test-all Maintained builds, generated world inputs, all discovered Python tests, and native signal tests Docker database suites, representative data, or a 200-player hold
Disposable database make test-db Listed schema contracts, legacy-to-current convergence, and the MariaDB journeys on a real server, each on its own Docker container Configured database state
Engine boot contract tests/async/run_runtime_compatibility_mysql.sh with RUNTIME_DB_IMAGE unset, mariadb:10.11 and mariadb:11.8, and tests/async/run_migration_runner_engines.py Fresh bootstrap, immutable head through the runner with one history checksum, drift rejection, and boot compatibility on MySQL 8.0, MariaDB 10.11 and MariaDB 11.8 A configured or production upgrade
Lifecycle/privacy commands below Pending-policy fail-closed behavior, synthetic archive/export/erasure contracts, and disposable schemas Controller approval, legal compliance, or enabled canonical mutation
Capacity/fault precursors commands below Bounded 25/50/100/200 logical-client codecs and named crash/fault invariants Representative eight-profile 30-minute 200-player readiness
200-player gate PHASE03_READINESS.md Only the complete qualified workload, fault, reconciliation, privacy, migration, and restore evidence can support readiness Nothing until every criterion is executed and recorded

Load, recovery, and fault contracts

These focused tests use source inspection, compiled local harnesses, or synthetic state. They do not connect to the configured database:

bash
python3 tests/async/test_player_load_pipeline.py
python3 tests/async/test_player_load_items.py
python3 tests/async/test_player_load_pets.py
python3 tests/async/test_player_save_pipeline.py
python3 tests/async/test_critical_command_coordinator.py
python3 tests/async/test_critical_transaction_contract.py
python3 tests/async/test_world_recovery_pipeline.py
python3 tests/async/test_maintenance_scheduler.py
python3 tests/async/test_worker_fault_injection.py
python3 tests/async/test_phase01_recovery_gate.py
python3 tests/async/test_phase02_capacity_and_crash_gate.py

The Phase 01 and Phase 02 gates include 25/50/100/200 logical-client waves and bounded codec/crash coverage. They are precursor evidence only. They do not perform the eight representative workload profiles, 30-minute 200-player holds, production-clone query measurements, or complete fault/reconciliation matrix the 200-player gate requires.

Account recovery contracts

Password reset by email has a core harness (injected mail sender and clock, no network), a wire-level SMTP harness against a fake relay on 127.0.0.1, a source-contract pin set, and a telnet journey that boots the client-free server with a fake relay. None of them needs a real relay or a database; the compiled harnesses need g++ plus the libcurl and OpenSSL development headers from the dependency metapackage:

bash
python3 tests/async/test_account_recovery.py
python3 tests/async/test_account_recovery_smtp_live.py
python3 tests/async/test_account_recovery_contract.py
python3 tests/async/test_account_recovery_journey.py

The harnesses never print a reset code or an address; a captured code reaches the driver only through a temporary file. TLS/STARTTLS and SMTP AUTH are not exercised (the fake relay is plaintext on loopback, which the configuration rules permit), so those fixtures do not qualify a real relay.

Lifecycle and privacy contracts

Run the source/synthetic controls first:

bash
python3 scripts/validate_data_lifecycle.py --json
python3 tests/async/test_data_lifecycle_manifest.py
python3 tests/async/test_season_reset_manifest.py
python3 tests/async/test_lifecycle_archive_execution.py
python3 tests/async/test_personal_data_export.py
python3 tests/async/test_account_erasure.py
python3 tests/async/test_immutable_migration_runner.py
python3 tests/async/test_runtime_boot_compatibility.py

Then run schema behavior only through the self-contained disposable wrappers:

bash
tests/async/run_lifecycle_archive_schema_mysql.sh
tests/async/run_personal_data_export_schema_mysql.sh
tests/async/run_account_erasure_schema_mysql.sh
tests/async/run_legacy_migration_mysql.sh
tests/async/run_immutable_migration_ledger_mysql.sh
tests/async/run_runtime_compatibility_mysql.sh
RUNTIME_DB_IMAGE=mariadb:10.11 tests/async/run_runtime_compatibility_mysql.sh
RUNTIME_DB_IMAGE=mariadb:11.8 tests/async/run_runtime_compatibility_mysql.sh
python3 tests/async/run_migration_runner_engines.py

Each wrapper must create and destroy its own isolated container. Never replace its target with .env values or use a configured, shared, restored, or production database. The checked-in lifecycle policy keeps canonical archive, export, and erasure mutation disabled; passing tests prove the guard and synthetic contract, not policy approval.

200-player readiness

Neither make test-all, make test-db, the Phase 01/02 logical-client gates, nor a successful server boot is 200-player readiness evidence. Only the complete gate in PHASE03_READINESS.md is: eight profiles at 25/50/100/200 clients with 30-minute 200-client holds, the fault matrix, reconciliation after every run, and privacy/restore validation on isolated representative data. That document holds the procedure and the gate's current state. UNQUALIFIED is a safe refusal, not a failed workload, and QUALIFIED is not readiness evidence either.

Conventions for new tests

  • One concern per file; name it after the feature/regression (test_<feature>.py). The root runner discovers it automatically. Add a run_<feature>.sh wrapper only when the test needs special environment setup or is useful as a standalone workflow.
  • Keep them fast and deterministic, and behavioral (see "Test styles"); a text check only for what text alone can show.
  • When you change behavior, add or update the focused regression test next to it — this is a stated repo convention (see AGENTS.md).
  • Schema-related changes should extend or add a _schema_mysql variant, listed in tests/run_db_tests.sh, so the contract is checked against a real database.

What exists today (samples)

Area Tests
Persistence run_persistence_contract_mysql.sh, dirty-flush retry, SQL pool shutdown, test_player_corpse_persistence_contract.py, run_corpse_persistence_schema_mysql.sh
Crash regressions wear-all, relic pickup, stuck command gate
Saves copyover save guards, ship save guards/dedup, epic save guards
Phase 01 recovery gate test_phase01_recovery_gate.py drives 25/50/100/200 logical-client waves with ambiguous-commit retries and enforces fork/ownership/route contracts
Critical commands test_critical_command_coordinator.py exercises identity, codec, multi-key ordering, duplicate attachment, exact completion, retries, fences, bounds, and lifecycle
Critical transactions test_critical_transaction_contract.py plus guarded run_critical_command_schema_mysql.sh cover schema, duplicate/mismatch, atomic rollback, concurrent locking, ambiguity lookup, outbox retry/dedupe/dead-letter/restart, and reconciliation
Help files class/race helpfile completeness contracts
Event loop hotspot budget regression; test_cast_lateness_runtime.py runs the production cast segments with late callbacks, and run_cast_timing_probe.py measures a real server's casts
Build contract test_compiler_warning_profile.py, test_message_buffer_bounds.py
Telemetry test_telemetry_repository.py --sql-fixture round-trips every record kind and refuses drifted schemas on a disposable MariaDB; run_telemetry_schema_boot_journey.py boots a real server against them and reads the outage ledger (both are make test-db legs)
Untrusted input test_unicode_runtime.py, test_ansi_runtime.py, test_json_utils_runtime.py, test_ttype_runtime.py

The four untrusted-input suites are behavioral rather than contract-style: they exercise the live decoders that handle network and player-visible text — UTF-8 widths and malformed/overlong/surrogate encodings, ANSI colour parsing and gradients, JSON and GMCP escaping, and RFC 1091 terminal-type negotiation including MTTS capability parsing. That is the shape to copy when the code under test is a parser: every case is a real call, and every defect found while writing them (unbounded continuation runs, invalid UTF-8 re-encoded as U+FFFD and accepted as a map glyph, MTTS 4JUNK silently enabling capabilities, gradient application dereferencing end() on an empty string) stayed as a regression.

test_compiler_warning_profile.py is what keeps the -Werror guarantee in BUILDING.md real: it fails if any of the six formerly-excepted categories is suppressed again, by flag, by a reintroduced exception variable, or by pragma.