Files
buzz/scripts/run-tests.sh
Luke Tornquist d44031f418 perf(relay): cut writer-pool round trips past the 48k relay ceiling (#8846)
## Summary

Five changes that take load off the writer pool and the relay's
connection bookkeeping. On a single-relay load rig, the clean ceiling
moves from 40k to about 48k WebSocket clients. In the A/B, connect p99
at 48k fell from 4.1 s (abort) to 1.65 s.

1. **Ban/mute restriction cache with a 1 s max age.** HTTP API requests,
publishes and ephemeral writes read `(community, pubkey)` restriction
state through one cache. An entry is served for 1 s from when its read
began, matching the open-channel cache's bound. Socket admission,
moderation-command actor checks and the `/buzz/v1` ban recheck still
read fresh. An admitted socket outlives the 1 s window, so a banned
client that reconnects inside it would otherwise keep a socket open
indefinitely. A ban drops the target's entry, and each owned agent's
entry, on every pod as part of the live-disconnect fan-out, best-effort.
Timeouts, untimeouts and unbans drop it on the pod that handles them.
2. **Fewer round trips per event read.** Replica reads skip the per-read
`statement_timeout` tighten, because the reader pool installs the
deadline at connect. They keep the savepoint, so a failed or cancelled
read leaves the session usable and releases its locks. That takes them
from 4 round trips to 3. Writer reads send `BEGIN` and the tighten as
one message, which also takes them from 4 to 3.
3. **Concurrent open-channel cache misses share one read.** The first
miss in a community takes a per-community gate and reads; the others
wait for it and use its result. A waiter whose leader failed reads once
on its own and does not queue again. A waiter also stops waiting after
the cache's 1 s max age and reads on its own, because the leader's set
would be stale by then anyway. So a leader whose read hangs stalls only
its own request.
4. **`has_admitted_connection` is one map lookup.** A `(community,
pubkey) → admitted count` index is maintained under each socket's pubkey
lock in `mark_admitted`, `set_authenticated_identity` and `deregister`.
It replaces a scan of every connection on each disconnect.
5. **Host → community cache with a 1 s max age.** Only successful
lookups are cached; unmapped hosts and errors go to the database every
time.

## What got simpler

- The ban handler's separate pod-local cache drop is gone; the
disconnect every ban already runs covers it on every pod.
- One write-restriction gate instead of two.
`enforce_cached_write_restriction`, `enforce_relay_membership_ephemeral`
and the `cached_ban` parameter chain through `enforce_membership` /
`admit_member` / `deny_banned` are deleted.
- The ephemeral path's separate 30 s ban cache is gone, so the relay has
one staleness rule for restriction state instead of two.

## Notes

- Bans reach the restriction cache on every pod. Every ban path (signed
command, report action, relay admin) closes the target's and its agents'
sockets clusterwide, and each pod drops those entries in the same step.
A dropped Redis publish, or a restriction read already in flight when
the drop lands (it can refill the old row), falls back to the 1 s bound.
Timeouts on other pods also rely on the 1 s bound, and so do unbans and
untimeouts, which fail closed.
- On the load rig the restriction-cache hit rate was 28%, because HTTP
agents poll every 5 s. A longer TTL with Redis invalidation would raise
it, but that is a separate change.

## Load test

Single relay pinned to 8 cores, native Postgres 16, a seeded fixture
with 2,000 open channels, and a 6-shard generator. Steady ramp with 120
s holds; a step aborts at p99 > 3 s. Each step adds 10% HTTP agents.

| clients | base connect p99 | this PR connect p99 | base relay cores |
this PR relay cores |
|---|---|---|---|---|
| 32k | 34 ms | 31 ms | 1.71 | 1.66 |
| 40k | 45 ms | 26 ms | 2.33 | 2.15 |
| 48k | 4,107 ms (abort) | 1,652 ms | 4.7 | 3.0 |
| 56k | n/a | 4,535 ms (abort) | n/a | 4.0 |

Open-channel re-reads over the run fell from 72k to 1.6k. The tenant
cache hit rate was 94%.

48k is this branch's knee. A later run of the same build aborted at 48k:
connect p99 2.2 s, but 29% subscribe errors from the concurrent-handler
cap. With a 10 s restriction max age, it passed 48k, at connect p99 1.56
s, 60% hit rate and 10% fewer relay cores, and both arms stopped at 56k.

## Testing

- `cargo fmt --check`, `cargo clippy -p buzz-relay -p buzz-db
--all-targets -D warnings`
- `cargo test -p buzz-db --lib` and `-p buzz-relay --lib`, run in
parallel. The only failures were 8 DB-seeding tests that need a migrated
default database; the Postgres lane covers them. Commits after that run
changed the replica savepoint, the `/buzz/v1` recheck revert and some
test edits.
- Postgres lane at head: `scripts/postgres-test-run.sh -p buzz-relay -p
buzz-db --lib --bins --tests`. All 1,183 tests passed.
- Mutants. Each one must turn its new test red:
  - waiters skipping the gate
  - a no-op index removal
  - the tenant cache ignoring age
  - the restriction cache ignoring age
  - a waiter waiting on the gate with no limit
  - a disconnect that keeps the cached restriction row
  - replica reads dropping the savepoint

buzz-review-completed

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Signed-off-by: Luke Tornquist <tornquist@squareup.com>
Co-authored-by: Elrond <28d6302a099e5225b02c4155ac4236e4912603df2ab08dbfc2f4fef08ce598c8@buzz.block.builderlab.xyz>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 11:15:49 -05:00

529 lines
25 KiB
Bash
Executable File

#!/usr/bin/env bash
# =============================================================================
# run-tests.sh — Run Buzz test suite
# =============================================================================
# Usage:
# ./scripts/run-tests.sh # run all tests (default)
# ./scripts/run-tests.sh unit # unit tests only (no infra needed)
# ./scripts/run-tests.sh integration # integration tests only
# ./scripts/run-tests.sh all # explicit all
# =============================================================================
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
MODE="${1:-all}"
# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
CYAN='\033[0;36m'
NC='\033[0m'
log() { echo -e "${BLUE}[run-tests]${NC} $*"; }
success(){ echo -e "${GREEN}[run-tests]${NC} $*"; }
warn() { echo -e "${YELLOW}[run-tests]${NC} $*"; }
error() { echo -e "${RED}[run-tests]${NC} $*" >&2; }
section(){ echo -e "\n${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"; echo -e "${CYAN} $*${NC}"; echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"; }
cd "${REPO_ROOT}"
# ---- Load .env if present ---------------------------------------------------
if [[ -f ".env" ]]; then
log "Loading .env..."
set -o allexport
# shellcheck disable=SC1091
source .env
set +o allexport
else
# Use defaults matching docker-compose.yml
export DATABASE_URL="postgres://buzz:buzz_dev@localhost:5432/buzz" # sadscan:disable np.postgres.1
export PGHOST=localhost
export PGPORT=5432
export PGUSER=buzz
export PGPASSWORD=buzz_dev
export PGDATABASE=buzz
export REDIS_URL="redis://localhost:6379"
fi
# ---- Track results ----------------------------------------------------------
declare -a PASSED=()
declare -a FAILED=()
run_test_step() {
local name="$1"
shift
log "Running: ${name}"
if "$@"; then
success "${name} passed"
PASSED+=("${name}")
else
error "${name} FAILED"
FAILED+=("${name}")
fi
}
# ---- Check / start infra (for integration tests) ----------------------------
ensure_infra() {
"${REPO_ROOT}/bin/just" _ensure-migrations
}
# ---- Unit tests (no infra needed) -------------------------------------------
run_unit_tests() {
section "Unit Tests (no infra required)"
run_test_step "buzz-core tests" \
cargo test -p buzz-core --lib -- --nocapture
run_test_step "buzz-audit tests" \
cargo test -p buzz-audit --lib -- --nocapture
run_test_step "buzz-auth unit tests" \
cargo test -p buzz-auth --lib -- --nocapture
# Mesh JWKS proxy-bypass regression: infra-free loopback I/O with
# child-process environment isolation. --lib does not select its binary.
# Keep this fallback in step with `just test-unit`.
run_test_step "buzz-auth mesh JWKS proxy regression" \
cargo test -p buzz-auth --test mesh_jwks_proxy -- --nocapture
# Cross-pod disconnect payload tests (NIP-FI and community archive fence;
# infra-free). Mirrors `just test-unit`.
run_test_step "buzz-pubsub conn_control tests" \
cargo test -p buzz-pubsub --lib conn_control::tests:: -- --nocapture
run_test_step "buzz-voice tests" \
cargo test -p buzz-voice --lib -- --nocapture
# Inter-relay mesh crate (infra-free). Mirrors `just test-unit`.
run_test_step "buzz-relay-mesh tests" \
cargo test -p buzz-relay-mesh -- --nocapture
run_test_step "buzz-cli tests" \
cargo test -p buzz-cli -- --nocapture
# buzz-sdk builder/validation unit tests: pure event-builder and input
# validation, no infra. Mirrors the nextest path in `just test-unit` — the
# two lists must stay in step. `--lib` matches the nextest invocation and
# avoids the full-package rustdoc dependency-resolution flake.
run_test_step "buzz-sdk unit tests" \
cargo test -p buzz-sdk --lib -- --nocapture
# buzz-workflow unit tests (infra-free). Mirrors `just test-unit`.
run_test_step "buzz-workflow unit tests" \
cargo test -p buzz-workflow --lib -- --nocapture
# Keep the relay-to-agent trust-boundary regressions in the fallback path
# when cargo-nextest is unavailable.
run_test_step "buzz-acp tests" \
cargo test -p buzz-acp -- --nocapture
# Keep MCP lifecycle coverage in step with the nextest path.
run_test_step "buzz-dev-mcp tests" \
cargo test -p buzz-dev-mcp -- --nocapture
# buzz-db migrator/lint unit tests (no infra): guard the embedded-migrator
# invariant (exactly the consolidated 0001; cutover/backfill stays an operator
# script, not startup state) and the tenant-scoping lints. The Postgres-backed
# buzz-db tests are #[ignore]d; nothing here (or in integration mode below,
# which runs `cargo test -p buzz-db` without --ignored) runs them — they need a
# separate isolated-DB gate, so --lib keeps this step infra-free.
run_test_step "buzz-db unit tests" \
cargo test -p buzz-db --lib -- --nocapture
run_test_step "buzz-db source-policy tests" \
cargo test -p buzz-db --test observability_source -- --nocapture
# buzz-search unit tests (no infra); its Postgres suite runs in the
# PostgreSQL lane.
run_test_step "buzz-search unit tests" \
cargo test -p buzz-search --lib -- --nocapture
run_test_step "buzz-media storage snapshot serialization test" \
cargo test -p buzz-media --lib bucket_index::tests::bucket_snapshot_json_round_trip_preserves_community_keys -- --exact --nocapture
run_test_step "buzz-admin storage snapshot tests" \
cargo test -p buzz-admin storage_snapshot -- --nocapture
run_test_step "buzz-admin community archive command tests" \
cargo test -p buzz-admin communities -- --nocapture
run_test_step "buzz-admin partition audit outcome contract test" \
cargo test -p buzz-admin tests::partition_audit_outcomes_keep_their_json_names_and_exit_codes -- --exact --nocapture
run_test_step "buzz-admin NIP-FI binding command tests" \
cargo test -p buzz-admin nip_fi::tests -- --nocapture
run_test_step "buzz-admin user-deletion intake command tests" \
cargo test -p buzz-admin user_deletions::tests -- --nocapture
# Multi-tenant conformance gate: independent replay checker + golden
# fixtures (buzz-conformance). Pure in-process trace replay, no infra.
run_test_step "buzz-conformance tests" \
cargo test -p buzz-conformance -- --nocapture
run_test_step "buzz-push-gateway tests" \
cargo test -p buzz-push-gateway -- --nocapture
run_test_step "buzz-push-gateway personal development tests" \
cargo test -p buzz-push-gateway --features personal-dev-app-attest -- --nocapture
# Kubernetes backend provider: pure decision layers driven by a fake
# substrate, no cluster. Mirrors the nextest path in `just test-unit` —
# the two lists must stay in step or the fallback silently covers less.
run_test_step "buzz-backend-kubernetes tests" \
cargo test -p buzz-backend-kubernetes -- --nocapture
# Keep fallback parity with `just test-unit`: one LaunchDarkly-feature run
# exercises both default and feature-gated buzz-feature-flags tests.
run_test_step "buzz-feature-flags tests (launchdarkly)" \
cargo test -p buzz-feature-flags --features launchdarkly -- --nocapture
# buzz-agent model-capabilities corpus: the Rust half of the cross-language
# drift guard. model_capabilities.rs embeds scripts/model-capabilities.json +
# scripts/normative-corpus.json via include_str! and replays the full locked
# corpus as pure in-process tests (no infra). Mirrors the nextest path in
# `just test-unit` — the two lists must stay in step.
run_test_step "buzz-agent unit tests" \
cargo test -p buzz-agent --lib -- --nocapture
# ACP author-gate and queue tests are pure unit tests. Keep this fallback in
# step with `just test-unit`; ignored lifecycle tests run elsewhere.
run_test_step "buzz-acp unit tests" \
cargo test -p buzz-acp --lib -- --nocapture
# Mirror the relay filters from `just test-unit`: the three handler modules,
# storage-snapshot helpers, readiness and router unit suites, and the single
# scoped admission regression in state::tests, plus the REQ lifecycle tests.
run_test_step "buzz-relay push runtime tests" \
cargo test -p buzz-relay --lib push_runtime:: -- --nocapture
run_test_step "buzz-relay operator listener tests" \
cargo test -p buzz-relay --lib operator_listener::tests:: -- --nocapture
run_test_step "buzz-relay channel authorization tests" \
cargo test -p buzz-relay --lib handlers::channel_authz:: -- --nocapture
run_test_step "buzz-relay moderation authorization tests" \
cargo test -p buzz-relay --lib handlers::moderation_authz:: -- --nocapture
run_test_step "buzz-relay side-effects helper tests" \
cargo test -p buzz-relay --lib handlers::side_effects::tests:: -- --nocapture
run_test_step "buzz-relay coalesced refresh tests" \
cargo test -p buzz-relay --lib handlers::coalesced_refresh::tests:: -- --nocapture
run_test_step "buzz-relay system channel scope tests" \
cargo test -p buzz-relay --lib handlers::system_channels::tests:: -- --nocapture
run_test_step "buzz-relay NIP-11 document tests" \
cargo test -p buzz-relay --lib nip11::tests:: -- --nocapture
run_test_step "buzz-relay storage snapshot tests" \
cargo test -p buzz-relay --lib storage_sweep::tests:: -- --nocapture
# Mirror the four audio/FI suites from `just test-unit`'s nextest expression.
# These are infra-free (no DB, no Redis); the `#[ignore]`-gated DB witnesses
# are excluded by cargo test's default filter. Keep in step with the Justfile
# `test-unit` relay expression.
run_test_step "buzz-relay audio join tests" \
cargo test -p buzz-relay --lib audio::join::tests:: -- --nocapture
run_test_step "buzz-relay audio handler tests" \
cargo test -p buzz-relay --lib audio::handler::tests:: -- --nocapture
run_test_step "buzz-relay NIP-FI gate tests" \
cargo test -p buzz-relay --lib nip_fi_gate::tests:: -- --nocapture
run_test_step "buzz-relay NIP-FI session tests" \
cargo test -p buzz-relay --lib nip_fi_session::tests:: -- --nocapture
run_test_step "buzz-relay NIP-FI shadow recorder tests" \
cargo test -p buzz-relay --lib nip_fi_shadow::tests:: -- --nocapture
run_test_step "buzz-relay NIP-FI shadow session tests" \
cargo test -p buzz-relay --lib nip_fi_shadow_session::tests:: -- --nocapture
run_test_step "buzz-relay startup step tests" \
cargo test -p buzz-relay --lib startup_steps::tests:: -- --nocapture
run_test_step "buzz-relay mesh boot tests" \
cargo test -p buzz-relay --lib mesh_boot::tests:: -- --nocapture
run_test_step "buzz-relay subscription registry tests" \
cargo test -p buzz-relay --lib subscription::tests:: -- --nocapture
# telemetry and config tests mutate process env. libtest runs one process
# with many threads (nextest, used by `just test-unit`, isolates each test),
# so run them single-threaded here. `--skip` keeps the substring filter
# `config::tests::` from also selecting nip_fi_config, which runs above.
run_test_step "buzz-relay telemetry tests" \
cargo test -p buzz-relay --lib telemetry::tests:: -- --test-threads=1 --nocapture
run_test_step "buzz-relay config tests" \
cargo test -p buzz-relay --lib config::tests:: -- --skip nip_fi_config:: --test-threads=1 --nocapture
# Mirror the NIP-FI (S3) stanza from `just test-unit`: module filters, then
# each exact name. Keep this list in step with that stanza's `test(=...)`s.
run_test_step "buzz-relay NIP-FI config tests" \
cargo test -p buzz-relay --lib nip_fi_config:: -- --nocapture
run_test_step "buzz-relay NIP-FI resolver tests" \
cargo test -p buzz-relay --lib nip_fi_resolver:: -- --nocapture
# Infra-free REQ subscription-lifecycle tests, by exact name: the rest of
# handlers::req needs a database.
run_test_step "buzz-relay REQ subscription lifecycle tests" \
cargo test -p buzz-relay --lib -- --exact --nocapture \
handlers::req::tests::timed_out_historical_read_deregisters_before_closed \
handlers::req::tests::superseded_timeout_leaves_replacement_intact \
handlers::req::tests::search_claim_retires_live_and_yields_to_replacement \
handlers::req::tests::failed_search_closes_without_eose_or_trying_later_filters \
handlers::req::tests::concurrent_claims_and_stale_teardowns_keep_the_last_owner \
handlers::req::tests::timeout_closed_is_emitted_before_a_replacement_can_claim \
handlers::req::tests::revoke_then_replacement_keeps_replacement_whole \
handlers::req::tests::claims_after_connection_cleanup_are_refused \
handlers::req::tests::dropped_terminal_frame_cancels_connection \
handlers::req::tests::revoke_dropped_terminal_frame_cancels_connection \
handlers::req::tests::count_fallback_covers_filters_that_can_match_private_deletions \
handlers::req::tests::group_state_t_and_creator_tags_reach_sql_only_for_group_state_kinds
# Infra-free bridge search extension parser tests, by exact name: the rest of
# api::bridge::postgres_tests needs a database.
run_test_step "buzz-relay bridge search extension tests" \
cargo test -p buzz-relay --lib -- --exact --nocapture \
api::bridge::postgres_tests::bridge_search_mode_extension_accepts_prefix_snake_or_camel_case \
api::bridge::postgres_tests::bridge_search_sort_extension_selects_newest_only_when_named
# Infra-free guard for the destructive invite fixture's database ownership
# check, by exact name: the rest of api::invites::postgres_tests needs a database.
run_test_step "buzz-relay invite fixture guard test" \
cargo test -p buzz-relay --lib -- --exact --nocapture \
api::invites::postgres_tests::restriction_tests::postgres_tests::destructive_fixture_rejects_shared_database_urls
run_test_step "buzz-relay readiness tests" \
cargo test -p buzz-relay --lib readiness::tests:: -- --nocapture
run_test_step "buzz-relay accessible-channel cache tests" \
cargo test -p buzz-relay --lib -- --exact --nocapture \
state::tests::accessible_channel_invalidation_is_scoped_to_community \
state::tests::accessible_channels_join_member_and_open_halves \
state::tests::channel_deleted_invalidation_is_scoped_to_community
run_test_step "buzz-relay admission regression test" \
cargo test -p buzz-relay --lib state::tests::neither_a_confirmed_inactive_community_nor_a_failed_lookup_admits_the_socket -- --nocapture
run_test_step "buzz-relay archive revalidation fence test" \
cargo test -p buzz-relay --lib state::tests::periodic_revalidation_disconnects_inside_the_fenced_callback -- --exact --nocapture
run_test_step "buzz-relay bare disconnect fail-closed test" \
cargo test -p buzz-relay --lib state::tests::bare_community_disconnect_fails_closed_when_the_fence_is_unavailable -- --exact --nocapture
run_test_step "buzz-relay archived close-reason test" \
cargo test -p buzz-relay --lib connection::tests::send_loop_sends_policy_close_when_community_is_archived -- --exact --nocapture
run_test_step "buzz-relay community close first-writer tests" \
cargo test -p buzz-relay --lib state::tests::community_disconnect_then_nip_fi_keeps_community_deleted_reason -- --exact --nocapture
run_test_step "buzz-relay community close suppresses losing denial frame test" \
cargo test -p buzz-relay --lib state::tests::disconnect_community_wins_reason_losing_nip_fi_does_not_enqueue_frame -- --exact --nocapture
run_test_step "buzz-relay read cache tests" \
cargo test -p buzz-relay --lib -- --exact --nocapture \
state::tests::admitted_index_follows_every_key_change \
state::tests::concurrent_open_channel_misses_share_the_in_flight_read \
tenant::tests::app_state_resolver_serves_fresh_cached_hosts_only \
state::tests::disconnect_drops_the_cached_restriction_row
run_test_step "buzz-relay router tests" \
cargo test -p buzz-relay --lib router::tests:: -- --nocapture
run_test_step "buzz-relay NIP-FI shared core tests" \
cargo test -p buzz-relay --lib nip_fi_core::tests:: -- --nocapture
run_test_step "buzz-relay NIP-FI HTTP ingress tests" \
cargo test -p buzz-relay --lib nip_fi_http::tests:: -- --nocapture
run_test_step "buzz-relay API query parsing tests" \
cargo test -p buzz-relay --lib api::parse_query_tests:: -- --nocapture
run_test_step "buzz-relay profile search request tests" \
cargo test -p buzz-relay --lib api::profiles:: -- --nocapture
run_test_step "buzz-relay per-user search cap tests" \
cargo test -p buzz-relay --lib api::bridge::search_slots::tests:: -- --nocapture
run_test_step "buzz-relay Git transport Off-mode precedence tests" \
cargo test -p buzz-relay --lib api::git::transport::off_mode_precedence_tests:: -- --nocapture
run_test_step "buzz-relay NIP-FI upgrade tests" \
cargo test -p buzz-relay --lib nip_fi_upgrade:: -- --nocapture
run_test_step "buzz-relay NIP-FI admin API tests" \
cargo test -p buzz-relay --lib api::nip_fi:: -- --nocapture
run_test_step "buzz-relay auth metrics contract tests" \
cargo test -p buzz-relay --lib metrics::contract_tests:: -- --nocapture
run_test_step "buzz-relay partition alert series tests" \
cargo test -p buzz-relay --lib metrics::partition_alert_tests:: -- --nocapture
local nip_fi_exact_tests=(
audio::room::tests::roster_revisions_are_ordered_and_snapshot_is_authoritative
connection::tests::auth_lifecycle_reconciles_every_terminal_and_never_leaks_gauge
audio::room::tests::b1_pending_peer_removed_before_commit_emits_no_delta
audio::room::tests::b2_commit_peer_emits_exactly_one_joined_delta_and_marks_visible
audio::room::tests::b3_commit_peer_revision_is_monotone_between_concurrent_events
audio::room::tests::f7a_pending_peer_excluded_from_snapshot_until_committed
connection::tests::b2_cancelled_connection_event_frame_not_dispatched
connection::tests::b3_expiry_denial_precedes_close_through_send_loop
connection::tests::b3_root_pairing_denial_precedes_close_through_send_loop
connection::tests::cancellation_during_select_with_fi_denial_routes_through_bounded_path
connection::tests::cancelled_never_ready_sink_with_queued_fi_denial_exits_within_timeout
connection::tests::deadline_exp_is_earliest_selects_exp
connection::tests::deadline_max_connection_lifetime_is_earliest_selects_partition
connection::tests::deadline_no_lifetime_returns_upstream_only
connection::tests::expiry_notice_queued_on_ctrl_before_cancel
connection::tests::f3_root_outer_wrapper_delivers_denial_on_bootstrap_cancellation
connection::tests::f3_root_pre_built_expired_gate_terminates_connection
connection::terminal_close_delivery_tests::key_pairing_mismatch_closes_with_1008_on_real_socket
connection::terminal_close_delivery_tests::session_expiry_denial_closes_with_1008_on_real_socket
connection::terminal_close_delivery_tests::server_holds_socket_for_client_close_reply
connection::terminal_close_delivery_tests::server_finishes_promptly_when_client_answers_close
connection::terminal_close_delivery_tests::stalled_cleanup_does_not_hold_socket_open
connection::terminal_close_delivery_tests::bootstrap_drain_holds_socket_for_client_close_reply
connection::terminal_close_delivery_tests::stalled_expiry_barrier_does_not_hold_socket_open
connection::terminal_close_delivery_tests::blocked_frame_handler_does_not_hold_socket_open
connection::terminal_close_delivery_tests::refused_bootstrap_holds_connection_slot_until_socket_ends
connection::terminal_close_delivery_tests::stalled_bootstrap_holds_connection_slot
connection::terminal_close_delivery_tests::peer_wait_replies_to_client_initiated_close
handlers::auth::tests::b2_pre_cancelled_connection_never_becomes_authenticated
handlers::auth::tests::fi_ban_check_error_emits_terminal_authorization_unavailable
handlers::auth::tests::fi_root_authorization_denied_rows_emit_identical_frames
handlers::auth::tests::fi_invalid_nip42_proof_emits_terminal_evidence_rejected
handlers::auth::tests::handle_auth_pairing_mismatch_runs_full_root_denial_path
handlers::auth::tests::shadow_root_auth_records_pairing_denial
handlers::auth::tests::shadow_root_auth_records_unavailable_owner_read
handlers::ingest::postgres_tests::relay_admin_ban_maps_to_blocked_auth_failure
handlers::ingest::postgres_tests::relay_admin_internal_maps_to_error_not_client_fault
handlers::auth::tests::shadow_root_invalid_nip42_retires_without_record
handlers::auth::tests::nip42_denial_class_separates_internal_failure_from_bad_evidence
handlers::event::tests::fanout_access::owner_only_kinds_keep_only_the_owner
handlers::event::tests::pubsub_fanout::pubsub_owner_only_kinds_reach_only_the_owner
handlers::event::tests::pubsub_fanout::dispatch_owner_only_kinds_reach_only_the_owner
handlers::req::tests::p1a_huddle_liveness_req_barrier_expiry_blocks_query_and_emission
state::tests::f3_cancellation_during_check_terminates_socket_without_waiting_for_check
state::tests::on_not_run_runs_once_on_each_deny_arm_and_never_on_admit
state::tests::conn_manager_disconnect_nip_fi_ignores_unproven_connection
state::tests::conn_manager_disconnect_nip_fi_is_issuer_scoped
state::tests::conn_manager_disconnect_nip_fi_sets_authorization_denied_reason
state::tests::nip_fi_disconnect_audio_is_issuer_scoped
state::tests::nip_fi_owner_disconnect_closes_owned_agent_sockets_on_root_and_audio
state::tests::nip_fi_disconnect_closes_proven_audio_socket_and_sends_policy_close_reason
state::tests::nip_fi_disconnect_closes_target_audio_only_and_preserves_collocated_peer
state::tests::nip_fi_disconnect_does_not_close_different_pubkey_audio_socket
state::tests::nip_fi_disconnect_does_not_close_unproven_audio_socket
state::tests::community_disconnect_then_nip_fi_keeps_community_deleted_reason
state::tests::disconnect_community_wins_reason_losing_nip_fi_does_not_enqueue_frame
state::tests::manager_disconnect_sets_reason_enqueues_frame_then_cancels
)
local name
for name in "${nip_fi_exact_tests[@]}"; do
run_test_step "buzz-relay ${name}" \
cargo test -p buzz-relay --lib "$name" -- --exact --nocapture
done
run_test_step "buzz-relay binary tests" \
cargo test -p buzz-relay --bin buzz-relay -- --nocapture
run_test_step "buzz-relay invitation token and cutoff contracts" \
cargo test -p buzz-relay --lib invite_token:: -- --nocapture
run_test_step "buzz-relay invitation cutoff configuration" \
cargo test -p buzz-relay --lib config::invite_cutoff_tests:: -- --nocapture
run_test_step "buzz-relay mesh receive lifetime regression" \
cargo test -p buzz-relay --lib mesh_boot::tests::demo_echo_retains_pending_receive_across_housekeeping_ticks -- --exact --nocapture
run_test_step "buzz-relay boot lifecycle tests" \
cargo test -p buzz-relay --test boot_lifecycle -- --nocapture
# Load generator unit tests are infra-free. Keep in step with `just test-unit`.
run_test_step "buzz-test-client loadgen unit tests" \
cargo test -p buzz-test-client --lib loadgen:: -- --nocapture
}
# ---- DB / integration tests (infra required) --------------------------------
run_integration_tests() {
section "Integration Tests (requires running services)"
ensure_infra
run_test_step "buzz-db tests" \
cargo test -p buzz-db -- --nocapture
if find crates/buzz-auth/tests -maxdepth 1 -name '*.rs' -print -quit 2>/dev/null | grep -q .; then
run_test_step "buzz-auth integration tests" \
cargo test -p buzz-auth --test '*' -- --nocapture
else
run_test_step "buzz-auth (no integration tests found)" true
fi
run_test_step "workspace integration tests" \
cargo test --test '*' -- --nocapture 2>/dev/null || \
run_test_step "workspace integration tests (none found)" true
}
# ---- Main -------------------------------------------------------------------
START_TIME=$(date +%s)
case "${MODE}" in
unit)
run_unit_tests
;;
integration)
run_integration_tests
;;
all|*)
run_unit_tests
run_integration_tests
;;
esac
END_TIME=$(date +%s)
ELAPSED=$((END_TIME - START_TIME))
# ---- Summary ----------------------------------------------------------------
section "Test Summary"
echo ""
echo -e " Duration: ${ELAPSED}s"
echo ""
if [[ ${#PASSED[@]} -gt 0 ]]; then
echo -e " ${GREEN}Passed (${#PASSED[@]}):${NC}"
for t in "${PASSED[@]}"; do
echo -e " ${GREEN}pass${NC} ${t}"
done
fi
if [[ ${#FAILED[@]} -gt 0 ]]; then
echo ""
echo -e " ${RED}Failed (${#FAILED[@]}):${NC}"
for t in "${FAILED[@]}"; do
echo -e " ${RED}fail${NC} ${t}"
done
echo ""
exit 1
fi
echo ""
success "All tests passed!"
exit 0