From 9597a4ebb6a2f41c6080df1d00c9e3689bbba324 Mon Sep 17 00:00:00 2001 From: zarazaex69 Date: Mon, 25 May 2026 22:54:00 +0300 Subject: [PATCH] test(e2e): add chaos mode to local soak test --- internal/e2e/local_soak_test.go | 34 +++++++++++++++++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/internal/e2e/local_soak_test.go b/internal/e2e/local_soak_test.go index 1a8e18d..06870e1 100644 --- a/internal/e2e/local_soak_test.go +++ b/internal/e2e/local_soak_test.go @@ -77,6 +77,11 @@ var ( true, "verify echoed bytes match the sent pattern (slower, but catches corruption)", ) + localSoakChaos = flag.Duration( //nolint:gochecknoglobals // package-level state intentional + "olcrtc.local-soak-chaos", + 0, + "if >0, trigger carrier reconnect every this interval to simulate network disruption", + ) ) var ( @@ -125,9 +130,9 @@ func runLocalSoakOnce(t *testing.T, transportName string) { // some transports), so don't fold it into the duration budget. const setupBudget = 30 * time.Second - t.Logf("[soak] transport=%s duration=%s chunk=%d verify=%t progress=%s", + t.Logf("[soak] transport=%s duration=%s chunk=%d verify=%t progress=%s chaos=%s", transportName, *localSoakDuration, *localSoakChunk, - *localSoakVerify, *localSoakProgress) + *localSoakVerify, *localSoakProgress, *localSoakChaos) rt := startLocalSoakTunnel(t, transportName) echoAddr := startEchoServer(t) @@ -141,6 +146,10 @@ func runLocalSoakOnce(t *testing.T, transportName string) { pumpCtx, cancelPump := context.WithTimeout(context.Background(), *localSoakDuration) defer cancelPump() + if *localSoakChaos > 0 && rt.room != nil { + go runLocalSoakChaos(pumpCtx, t, rt.room, *localSoakChaos) + } + stats := runLocalSoakPump(pumpCtx, t, conn, *localSoakChunk, *localSoakVerify, *localSoakProgress) if stats.sent == 0 || stats.recv == 0 { @@ -450,3 +459,24 @@ func humanBytes(n int64) string { return fmt.Sprintf("%d B", n) } } + +// runLocalSoakChaos periodically triggers carrier reconnect to simulate +// network disruption (WiFi flap, NAT rebind, etc). This reproduces the +// scenario from issue #72 where a carrier-driven reconnect leaves the +// server and client in a desynchronized state. +func runLocalSoakChaos(ctx context.Context, t *testing.T, room *memoryRoom, interval time.Duration) { + t.Helper() + ticker := time.NewTicker(interval) + defer ticker.Stop() + var count int + for { + select { + case <-ctx.Done(): + return + case <-ticker.C: + count++ + t.Logf("[chaos] triggering carrier reconnect #%d", count) + room.triggerReconnect() + } + } +}