The design goal is that almost everything is testable on the Simulator, and only the two
device-only surfaces (the packetFlow read/write loop and the live TLS handshake against a
real server) require hardware. Structure code so the untestable part is a thin adapter over
tested logic.
device smoke tests (M7/M8, manual + minimal automated, on hardware)
────────────────────────────────────────────────────────
integration tests (store + IPC + parser wired together, Simulator)
────────────────────────────────────────────────────────
unit tests (parsers, reassembly, pcap, models, store — Simulator)
.pcap fixtures, assert the extracted
FlowKey, protocol, ports, flags. Include malformed/truncated inputs that must be rejected
with a typed error, and a fuzz loop that must never crash.tcpdump -r if available) to confirm byte-exact framing and LINKTYPE_RAW.Equatable/Hashable laws, Codable round-trips, FlowKey canonicalization.Unit tests are not the only thing that has to run without hardware — Dynamic Type, VoiceOver and Instruments all need a person driving a populated app. And a Simulator launch is empty: the extension is the only writer of the history database, the capture directory and the live ring, and it does not run there. So the screens worth looking at (Timeline and its scrub bar, Flow Inspector, the packet screen, Captures, the Dashboard’s history) do not exist until something puts data in the shared container.
Shared/Fixtures does that — a Debug-only, seed-deterministic synthetic capture, scripted to cover
every screen state rather than sampled — and FixtureSeeder writes it into the shared container.
Launch the app with -TVSeedFixture (Xcode scheme arguments, or
xcrun simctl launch --console-pty <device> com.juanmmm21.tunnelvision -TVSeedFixture) and it prints
what it wrote. Seeding replaces rather than accumulates, so it can be repeated, and the fixed seed is
what makes a before/after with Instruments measure the same work. See
../../Shared/Fixtures/README.md.
The live feed stays flat regardless: the ring is an SPSC channel whose single producer is the extension, and faking it from the app would mean writing from the producer end of a contract that says there is only one. That half waits for hardware.
Cannot run on Simulator. Documented, semi-automated:
curl a known host from Safari), confirm the flow
appears with correct 5-tuple and byte counts.notInspectable.Store captured packet fixtures under TunnelVisionTests/Fixtures/ as small .pcap files, one
concern per file (e.g. ipv4-tcp-syn.pcap, ipv6-udp.pcap, tcp-out-of-order.pcap). Generate
them with tcpdump/Wireshark and keep them tiny. Never commit real personal traffic —
synthesize or scrub fixtures.
en/US in the scheme.
The copy tests assert the exact English the app shows, and a number interpolated into
String(localized:) is formatted with the process locale — unpinned, an assertion about a
figure said whatever the machine running it happened to say, which is how port 8,080 survived
three screens with two tests watching it. CopyLocaleTests asserts the pin is still in place,
because a lost pin breaks nothing: it only empties the assertions that depend on it. The
reasoning, including why en_US_POSIX would be the wrong pin, is in
02-coding-standards.md.measure {} benchmark for the parser hot path (target: parse well above line rate for
typical Wi-Fi throughput).xcodebuild test -scheme TunnelVision \
-destination 'platform=iOS Simulator,name=iPhone 16'
Run them through the scheme, which is what carries the pinned locale. -testLanguage/
-testRegion override it, and CopyLocaleTests will say so rather than let the run look clean.
Tests are green before every push — this is enforced by the coding standards and CI.