Consolidating the UI's seed values onto TestConfiguration's own defaults also
silently changed the default target to iperf.es.net, which was not asked for.
Sets it back in TestConfiguration, which is now the single definition the
screen and the headless entry point both read.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The view model was doing two jobs. Alongside presentation state it held what
happened during a run — the interval series, the rule that iperf3's omitted
warm-up intervals do not count, the retained end summary — and the screen
computed mean and peak inline in a widget builder. All of that is the part
that has to be correct, and all of it would have been rewritten by anyone
replacing the UI.
Iperf3Session accumulates a run in the domain layer: samples, totals, errors,
whether it was stopped, and the statistics over them. The view model now feeds
it events and keeps only a formatted transcript and the repaint schedule; the
screen reads every displayed number off the session and derives none.
Two details the session now owns rather than leaving to a caller. Omitted
intervals are excluded, so a warm-up cannot drag the mean down. And
totalRetransmits stays null when no interval reported a count — UDP does not
measure retransmits, and "not measured" must not render as zero.
The aggregation tests move with the logic, into a pure-Dart test with no
widgets or bindings involved.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The native layer now emits one JSON object per report, so the service parses
each into a sealed Iperf3Event — start, interval, end, error — and publishes
those instead of opaque lines. Interval samples carry bytes, bits per second,
retransmits and per-stream socket/cwnd/rtt detail; the end event carries sent
and received totals and CPU utilisation. None of that was available while the
output was scraped text.
Anything unrecognised — non-JSON, malformed JSON, an event name a future
iperf3 adds — becomes an Iperf3LogLine rather than being dropped, so
unexpected output still reaches the user.
Failures arrive as an `error` event rather than a non-zero return from
iperf_run_client, so that event is what now marks a run as failed. The
result's rawOutput keeps the JSON lines verbatim, since that is what the
native layer actually produced.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Stopping a test shuts down the control socket, so iperf_run_client() returns
-1 with "unable to send control message ... Broken pipe". That is the
expected consequence of the stop, not a fault, but the bridge read the
negative return as a failure — so pressing STOP left the UI showing
"Error (exit 1)" above a broken-pipe message.
The bridge now consults its own g_stop flag before classifying the outcome,
and the distinction travels up as Iperf3Result.stopped so the status line can
say "Stopped". A stop is reported with exit code 0: the run ended the way the
user asked it to.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>