mirror of
https://github.com/permissionlesstech/bitchat.git
synced 2026-07-24 22:45:19 +00:00
* Exclude perf baselines from parallel CI via --skip; sample hung tests Every app-suite CI run since the perf baselines landed (#1335) has timed out at the 15-minute job limit — main has been red for five consecutive runs. The job logs show the PerformanceBaselineTests fixtures dispatched into the parallel phase despite the BITCHAT_SKIP_PERF_BASELINES env guard from #1336, followed by ~11 minutes of silence until the timeout kills swiftpm-testing. The suite passes locally in seconds with identical flags, so the hang is specific to the CI toolchain/runners — consistent with the known XCTest-measure-under-parallel-workers hang the serial step was created to avoid. Two changes: - Exclude the baselines from the parallel phase with --skip at the SPM level, which removes them from the worker processes entirely instead of relying on the env guard reaching setUpWithError. They still run (and gate) in the dedicated serial step. - Wrap the parallel run in a 10-minute watchdog that samples any still-running test processes before killing them, so if anything else ever hangs, the run fails fast with thread stacks in the log instead of a silent 15-minute timeout. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Arm the test-hang watchdog only for the test execution phase Codex review: swift test builds before running, so the watchdog timer included dependency resolution and compilation — a cold-cache coverage build on a slow runner could be killed before tests ever started. Build the tests in their own step (bounded by the 15-minute job timeout like any build) and run the watchdog around swift test --skip-build, tightened to 5 minutes now that it times only test execution. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Hammer transport thread-safety tests from the dispatch pool, not the cooperative pool The watchdog added in this PR captured the actual CI hang twice, with identical stacks both times: NostrTransportTests' 100-task groups park every Swift Concurrency cooperative thread in a blocking queue.sync (the pool has one thread per core — 3 on CI runners, 10+ on dev machines, which is why this never reproduced locally). Blocking the entire cooperative pool violates the forward-progress contract, and the runners' dispatch wedges under the resulting asyncAndWait flood — taking concurrently running tests down with it (the panic-reset test deadlocked in a serviceQueue barrier that never got scheduled). Run the same 100 concurrent hammer iterations via DispatchQueue.concurrentPerform from a single global-queue hop instead: identical thread-safety coverage, executed on dispatch worker threads where blocking is legal, zero cooperative threads parked. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: jack <jackjackbits@users.noreply.github.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Test Harness Guide
This test suite uses an in-memory networking harness to make end-to-end and integration tests deterministic, fast, and race-free without touching production code.
In-Memory Bus
- File:
bitchatTests/Mocks/MockBLEService.swift - Registry/Adjacency: Global
registrymapspeerIDto aMockBLEServiceinstance;adjacencyrecords simulated links between peers. - Setup: Call
MockBLEService.resetTestBus()insetUp()to clear state between tests. - Topology: Use
simulateConnectedPeer(_:)andsimulateDisconnectedPeer(_:)to add/remove links.connectFullMesh()helpers in tests build larger topologies. - Handlers: Tests can observe data via
messageDeliveryHandler(decodedBitchatMessage) andpacketDeliveryHandler(rawBitchatPacket). - De‑duplication: A thread-safe
seenMessageIDsprevents duplicate deliveries during flooding/relays.
Broadcast Flooding
- Flag:
MockBLEService.autoFloodEnabled - Intent: When
true, public broadcasts propagate across the entire connected component (ignores TTL for reach) while still de‑duping to prevent loops. - Usage: Enabled in Integration tests (
setUp) to simulate large-network broadcast; disabled in E2E tests to keep routing explicit and verify TTL behavior (seePublicChatE2ETests.testZeroTTLNotRelayed).
Rehandshake Flow (Noise)
- Why: The legacy NACK recovery path was removed; recovery now relies on Noise session rehandshake after decrypt failure or desync.
- Manager:
NoiseSessionManagermanages per-peer sessions. - Pattern: On decrypt failure, proactively clear the local session and re-initiate a handshake. The peer accepts and replaces their session.
- Test:
IntegrationTests.testRehandshakeAfterDecryptionFailure- Corrupts ciphertext to induce a decrypt error.
- Calls
removeSession(for:)on the initiator’s manager beforeinitiateHandshake(with:)to avoidalreadyEstablished. - Verifies encrypt/decrypt succeeds post-rehandshake.
Tips
- Determinism: Add small async delays only where handler installation/topology changes could race the first send.
- Scoping: Keep
autoFloodEnabledtoggled only within Integration tests; always reset intearDown()to avoid cross-test contamination. - Direct vs Relay: Private messages target a specific peer when adjacent; otherwise they are surfaced to neighbors for relay and, if known, also delivered to the target.
Quick Start
- Create nodes and connect them:
let svc = MockBLEService(); svc.myPeerID = "PEER1"svc.simulateConnectedPeer("PEER2")
- Observe messages:
svc.messageDeliveryHandler = { msg in /* asserts */ }
- Enable broadcast flooding for Integration suites only:
MockBLEService.autoFloodEnabled = true