fix: report a user-requested stop as stopped rather than failed

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>
This commit is contained in:
2026-08-26 19:03:59 +09:00
parent fb6ec3af37
commit 02a8ba9c18
7 changed files with 63 additions and 3 deletions
+9 -1
View File
@@ -54,9 +54,13 @@ class Iperf3Service {
/// Whether the native layer reported an error during this run.
bool _sawError = false;
/// Whether [stop] was called for the current run.
bool _stopRequested = false;
/// Cancel any running test.
Future<void> stop() async {
if (!_isRunning) return;
_stopRequested = true;
try {
await _channel.invokeMethod('stopIperf');
} catch (e) {
@@ -83,6 +87,7 @@ class Iperf3Service {
_isRunning = true;
_sawError = false;
_stopRequested = false;
_outputBuffer.clear();
final args = config.toCliArgs();
@@ -157,7 +162,10 @@ class Iperf3Service {
completer.complete(
Iperf3Result(
rawOutput: _transcript(),
exitCode: _sawError ? exitCodeNativeError : 0,
// A user-requested stop is a normal outcome, not a failure.
exitCode:
(_sawError && !_stopRequested) ? exitCodeNativeError : 0,
stopped: _stopRequested,
),
);
}
+5
View File
@@ -3,8 +3,13 @@ class Iperf3Result {
final String rawOutput;
final int exitCode;
/// Whether the run ended because the user asked it to stop, rather than
/// finishing its configured duration or failing.
final bool stopped;
const Iperf3Result({
required this.rawOutput,
required this.exitCode,
this.stopped = false,
});
}
@@ -166,7 +166,10 @@ class _TestRunContentState extends State<_TestRunContent> {
final (String label, Color color) = switch (vm.state) {
TestRunState.idle => ('Ready', scheme.onSurfaceVariant),
TestRunState.running => ('Running…', scheme.primary),
TestRunState.completed => ('Test complete', scheme.primary),
TestRunState.completed => (
vm.wasStopped ? 'Stopped' : 'Test complete',
vm.wasStopped ? scheme.onSurfaceVariant : scheme.primary,
),
TestRunState.error => (
'Error${vm.result != null ? ' (exit ${vm.result!.exitCode})' : ''}',
scheme.error,
@@ -39,6 +39,9 @@ class TestRunViewModel extends ChangeNotifier {
/// Whether a test is currently in flight.
bool get isRunning => _state == TestRunState.running;
/// Whether the most recent finished run ended because the user stopped it.
bool get wasStopped => _result?.stopped ?? false;
/// Start a test with the given configuration.
Future<void> runTest(TestConfiguration config) async {
if (_state == TestRunState.running) return;