test(nip46): kill the e2e flake — local relay for strict kind-0, poison-tolerant vault lock
The suite failed intermittently (1 in ~4 runs) with 3-5 simultaneous failures. Two stacked causes: 1. REAL flake: nip46_bunker_connect_params_match_spec_against_strict_amber published its kind-0 through the DEFAULT relay set — the real internet (wss://relay.damus.io + the known-hanging relay.nostr.band) — so 'at least one relay must accept the signed kind-0' was a network lottery against the 6s send timeout. The QR test already pins settings to the in-process relay; the strict test shipped without it. Now pinned too: zero network dependency, deterministic. 2. CASCADE: a panic while holding VAULT_ENV_LOCK poisoned the mutex, so every later test died on PoisonError and one flake reported as many. The lock only serializes process-global XDG_DATA_HOME, so all four sites now unwrap_or_else into_inner — a real failure reports as ONE. 10/10 consecutive green e2e runs (was ~1 in 4 failing); suite time now uniform ~21.5s (was bimodal — the long tail was network waiting). cargo test 216 unit + 5 e2e green; clippy 0 warnings; fmt clean.
This commit is contained in:
parent
aa3c514e96
commit
a6a4e6f485
1 changed files with 38 additions and 4 deletions
|
|
@ -345,7 +345,13 @@ async fn nip46_client_handshake_and_sign_against_fake_amber() {
|
||||||
// XDG_DATA_HOME is process-global and every test in this binary sets it,
|
// XDG_DATA_HOME is process-global and every test in this binary sets it,
|
||||||
// so the lock is held for the WHOLE test: data_dir() re-reads the env on
|
// so the lock is held for the WHOLE test: data_dir() re-reads the env on
|
||||||
// every save, and a setup-only guard lets parallel tests cross-write.
|
// every save, and a setup-only guard lets parallel tests cross-write.
|
||||||
let _vault_guard = VAULT_ENV_LOCK.lock().unwrap();
|
let _vault_guard = VAULT_ENV_LOCK
|
||||||
|
.lock()
|
||||||
|
// Poison-tolerant on purpose: this lock only serializes the
|
||||||
|
// process-global XDG_DATA_HOME. A sibling test that panics while
|
||||||
|
// holding it must not cascade into PoisonError failures of every
|
||||||
|
// later test — one real failure should report as ONE failure.
|
||||||
|
.unwrap_or_else(|e| e.into_inner());
|
||||||
let app = {
|
let app = {
|
||||||
let tmp = std::env::temp_dir().join(format!("keynectr-e2e-{}", std::process::id()));
|
let tmp = std::env::temp_dir().join(format!("keynectr-e2e-{}", std::process::id()));
|
||||||
// data_dir() is $XDG_DATA_HOME/keynectr — the isolation vault must
|
// data_dir() is $XDG_DATA_HOME/keynectr — the isolation vault must
|
||||||
|
|
@ -615,7 +621,13 @@ async fn run_strict_amber(
|
||||||
#[allow(clippy::await_holding_lock)]
|
#[allow(clippy::await_holding_lock)]
|
||||||
async fn nip46_bunker_connect_params_match_spec_against_strict_amber() {
|
async fn nip46_bunker_connect_params_match_spec_against_strict_amber() {
|
||||||
// Whole-body env lock: data_dir() re-reads XDG_DATA_HOME on every save.
|
// Whole-body env lock: data_dir() re-reads XDG_DATA_HOME on every save.
|
||||||
let _vault_guard = VAULT_ENV_LOCK.lock().unwrap();
|
let _vault_guard = VAULT_ENV_LOCK
|
||||||
|
.lock()
|
||||||
|
// Poison-tolerant on purpose: this lock only serializes the
|
||||||
|
// process-global XDG_DATA_HOME. A sibling test that panics while
|
||||||
|
// holding it must not cascade into PoisonError failures of every
|
||||||
|
// later test — one real failure should report as ONE failure.
|
||||||
|
.unwrap_or_else(|e| e.into_inner());
|
||||||
let app = {
|
let app = {
|
||||||
let tmp = std::env::temp_dir().join(format!("keynectr-e2e-strict-{}", std::process::id()));
|
let tmp = std::env::temp_dir().join(format!("keynectr-e2e-strict-{}", std::process::id()));
|
||||||
let app_dir = tmp.join("keynectr");
|
let app_dir = tmp.join("keynectr");
|
||||||
|
|
@ -714,6 +726,16 @@ async fn nip46_bunker_connect_params_match_spec_against_strict_amber() {
|
||||||
// network-visible profile (what other clients display as the name).
|
// network-visible profile (what other clients display as the name).
|
||||||
// Exercises the real GUI "Publish name" path — Signing::External with
|
// Exercises the real GUI "Publish name" path — Signing::External with
|
||||||
// identity validation — against the strict signer.
|
// identity validation — against the strict signer.
|
||||||
|
// Point settings at the in-process relay FIRST: the default relay set is
|
||||||
|
// the real internet (damus + nostr.band, the latter a known-hanger), so
|
||||||
|
// leaving it made this assertion a network lottery — it failed whenever
|
||||||
|
// damus dawdled past the 6s send timeout. The QR test already does this;
|
||||||
|
// the strict test shipped without it.
|
||||||
|
{
|
||||||
|
let mut a = app.lock().await;
|
||||||
|
a.settings.relays = vec![keynectr::settings::RelayConfig::new(relay_url.clone())];
|
||||||
|
a.save_settings().expect("save settings");
|
||||||
|
}
|
||||||
let (settings, signing) = {
|
let (settings, signing) = {
|
||||||
let guard = app.lock().await;
|
let guard = app.lock().await;
|
||||||
(
|
(
|
||||||
|
|
@ -917,7 +939,13 @@ async fn run_qr_pairing(connect_shape: &'static str) {
|
||||||
|
|
||||||
// Isolated vault + pairing relay; the env lock is held for the whole
|
// Isolated vault + pairing relay; the env lock is held for the whole
|
||||||
// helper body (see above) so parallel tests cannot cross-write vaults.
|
// helper body (see above) so parallel tests cannot cross-write vaults.
|
||||||
let _vault_guard = VAULT_ENV_LOCK.lock().unwrap();
|
let _vault_guard = VAULT_ENV_LOCK
|
||||||
|
.lock()
|
||||||
|
// Poison-tolerant on purpose: this lock only serializes the
|
||||||
|
// process-global XDG_DATA_HOME. A sibling test that panics while
|
||||||
|
// holding it must not cascade into PoisonError failures of every
|
||||||
|
// later test — one real failure should report as ONE failure.
|
||||||
|
.unwrap_or_else(|e| e.into_inner());
|
||||||
let app = {
|
let app = {
|
||||||
let tmp = std::env::temp_dir().join(format!(
|
let tmp = std::env::temp_dir().join(format!(
|
||||||
"keynectr-e2e-pair-{}-{}",
|
"keynectr-e2e-pair-{}-{}",
|
||||||
|
|
@ -1073,7 +1101,13 @@ async fn run_qr_pairing(connect_shape: &'static str) {
|
||||||
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
|
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
|
||||||
#[allow(clippy::await_holding_lock)]
|
#[allow(clippy::await_holding_lock)]
|
||||||
async fn nip46_session_restore_redials_and_refuses_wrong_identity() {
|
async fn nip46_session_restore_redials_and_refuses_wrong_identity() {
|
||||||
let _vault_guard = VAULT_ENV_LOCK.lock().unwrap();
|
let _vault_guard = VAULT_ENV_LOCK
|
||||||
|
.lock()
|
||||||
|
// Poison-tolerant on purpose: this lock only serializes the
|
||||||
|
// process-global XDG_DATA_HOME. A sibling test that panics while
|
||||||
|
// holding it must not cascade into PoisonError failures of every
|
||||||
|
// later test — one real failure should report as ONE failure.
|
||||||
|
.unwrap_or_else(|e| e.into_inner());
|
||||||
let app = {
|
let app = {
|
||||||
let tmp = std::env::temp_dir().join(format!(
|
let tmp = std::env::temp_dir().join(format!(
|
||||||
"keynectr-e2e-restore-{}-{}",
|
"keynectr-e2e-restore-{}-{}",
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue