Merge e999d57c19abd0953762249532778926c3959ba1 into 9b84b361225facd8e623f25d76f889d3dc54a879

This commit is contained in:
Eric Gang 2026-08-11 09:05:57 +02:00 committed by GitHub
commit ef0183abfe
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 39 additions and 5 deletions

View File

@ -29,6 +29,29 @@ final class SimulatedMesh {
private var pendingDeliveries: [(from: Int, packet: BitchatPacket)] = []
/// Total (packet, receiving-node) deliveries pumped — the storm bound.
private(set) var deliveredFrameCount = 0
/// The same deliveries split by packet type. A whole-mesh frame budget
/// silently includes announce traffic, and announce *admission* is the one
/// decision the manual scheduler does not own: `sendAnnounceNow` asks
/// `announceThrottle.shouldSend(force:now: Date())` (BLEService), so
/// whether a scheduled re-announce reaches the wire depends on real
/// elapsed seconds, not on `advanceTime`. Burn 0.2s of wall clock in the
/// middle of a test — the forced-announce window is 0.15s — and announce
/// deliveries rise (measured: 22 → 36) while every other type is
/// unchanged. A test that drains the mesh to quiet before it measures
/// absorbs that into its baseline; one that cannot has a budget moving
/// with runner load. Counting per type lets a test bound the traffic it
/// is actually about either way.
private var deliveredFrameCountsByType: [UInt8: Int] = [:]
/// Deliveries of one packet type — the storm bound for a specific kind of
/// traffic, immune to unrelated announce frames.
/// Unlocked deliberately, matching `deliveredFrameCount` above: both
/// counters are written only by `pump` on the test thread (the outbound
/// tap touches `pendingDeliveries`/`emitted`, never these). Taking the
/// lock here would advertise a protection the writer does not hold.
func deliveredFrameCount(ofType type: MessageType) -> Int {
deliveredFrameCountsByType[type.rawValue] ?? 0
}
private(set) var nodes: [Node] = []
private var neighbors: [Set<Int>] = []
@ -168,6 +191,7 @@ final class SimulatedMesh {
for receiver in neighbors[from] {
for link in links(from: from, at: receiver) {
deliveredFrameCount += 1
deliveredFrameCountsByType[packet.type, default: 0] += 1
nodes[receiver].service._test_ingestFrame(packet, link: link)
}
}

View File

@ -63,7 +63,8 @@ struct SimulatedMeshTests {
// baseline snapshot depends on the draw — the budget assertion below
// flaked on CI at 14 and 18 frames for exactly that reason. Advance
// until the mesh goes a full window with no new frames, so the
// baseline only ever measures the message under test.
// baseline only ever measures the message under test. Quiescence is
// judged on every frame; the baseline itself is taken per type.
var settled = mesh.deliveredFrameCount
for _ in 0..<20 {
mesh.advanceTime(by: 2)
@ -71,7 +72,7 @@ struct SimulatedMeshTests {
if now == settled { break }
settled = now
}
let baseline = mesh.deliveredFrameCount
let baseline = mesh.deliveredFrameCount(ofType: .message)
let capture = TransportEventCapture()
c.service.eventDelegate = capture
@ -84,9 +85,18 @@ struct SimulatedMeshTests {
let arrived = await capture.drainedPublicMessageCount(content: "hello line") == 1
#expect(arrived)
// Storm bound: a single public message across one relay hop must
// not multiply into more than a handful of frames.
#expect(mesh.deliveredFrameCount - baseline <= 12)
// Storm bound: a single public message across one relay hop must not
// multiply into more than a handful of frames. Counted per type so
// the budget measures relay fan-out and nothing else. The whole-mesh
// total also carries announces, and announce admission is the one
// decision the manual scheduler does not own — `sendAnnounceNow` asks
// `announceThrottle.shouldSend(force:now: Date())` — so announce
// volume tracks real elapsed seconds. The settle loop above keeps
// that traffic in the baseline rather than in the delta: burning 0.2s
// of wall clock before it moves the baseline 66 → 80 frames and
// leaves this delta at 4. Per-type counting is what makes the
// assertion say which traffic it bounds.
#expect(mesh.deliveredFrameCount(ofType: .message) - baseline <= 8)
}
@Test