Commit Graph

7 Commits

Author SHA1 Message Date
mikkeli 314a3ea059 refactor: depend on an Iperf3Runner interface instead of the service
The presentation layer imported Iperf3Service directly, so the UI was welded
to the MethodChannel transport: the view model named it, and the screen
constructed one. Dependency direction was already inward-only, but the UI
depended on an implementation rather than a contract.

Iperf3Runner states that contract in the domain layer in five members, in pure
Dart with no Flutter import, and Iperf3Service implements it. The view model
and screen now take an Iperf3Runner; main.dart is the only file that names a
concrete one, which makes it the composition root.

Two consequences worth having. The UI can be driven against an in-memory
runner with no MethodChannel, native library or device involved. And a test
double implements five stable members rather than tracking every public member
the transport happens to expose — the previous double, forced to implement the
concrete class, broke twice while this branch's work was going on.

Also drops flutter/foundation from the data layer: debugPrint gives way to
dart:developer, leaving flutter/services (where MethodChannel lives) as that
layer's only Flutter dependency.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-26 19:56:03 +09:00
mikkeli cbb2aba8b0 feat(ui): render the event stream and show live throughput figures
Consuming JSON means the familiar iperf3 text layout has to be rebuilt, so a
pure formatter turns each event back into a line — binary units for transfer
totals, decimal units for bitrates, matching how iperf3 quotes each. The view
model keeps the interval series and the end summary as data alongside the
transcript, and a stat row shows current/total, mean, peak and sample count
off that series. A chart can be driven from the same list without re-parsing
text.

Repaints are coalesced on a 250ms timer rather than one per event. Reports can
now arrive several times a second across many streams, and each rebuild
re-renders the whole transcript, so notifying per event made the cost of
drawing scale with the report rate — which competes with the test itself for
CPU and skews the measurement.

Warm-up intervals (-O) are marked omitted by iperf3 and are kept out of the
series so they cannot drag the mean down.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-26 19:35:10 +09:00
mikkeli 02a8ba9c18 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>
2026-08-26 19:03:59 +09:00
mikkeli fb6ec3af37 feat(ui): show live output, add a stop button and duration/stream inputs
The screen could only start a 10s single-stream test and read the result
afterwards. It now renders the transcript as it arrives in a scrolling
monospace view that follows the newest line, exposes the duration and
parallel-stream settings TestConfiguration already carried, can stop a run in
progress, and copies the transcript to the clipboard.

The output field previously built a TextEditingController inside build(), so
every rebuild — one per output line now that output streams — discarded and
replaced it. Replaced with SelectableText, which needs no controller.

Seeds the inputs from TestConfiguration's own defaults rather than a second
hardcoded server address, so there is one default to change.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-26 19:03:22 +09:00
irene 0dd202291c fix: resolve SIGBUS crash in JNI bridge — use CallVoidMethod instead of function pointer casts
The root cause of the SIGBUS (alignment fault) crash was casting jmethodID
values to function pointers and invoking them directly. jmethodID is an
opaque JNI type that must only be used via CallVoidMethod/CallObjectMethod
etc. Calling it as a function pointer dereferences garbage memory, causing
SIGBUS at address 0xb1.

Changes:
- Replace function pointer typedefs (output_callback_t etc.) with a struct
  storing JNIEnv + callback global ref + jmethodIDs
- All callbacks now go through CallVoidMethod(env, ref, mid, ...)
- Remove getTestProgress() (never used, unused start_time workaround)
- Fix Kotlin arg cast: List<String> -> toTypedArray() (ClassCastException)
- Update UI default server to speedtest.uztelecom.uz
2026-08-25 18:59:42 +09:00
irene d551f41b41 feat: switch iperf3 to JNI with 3.19 source
- Switch from bundled iperf3 binary (Process.start) to JNI compilation
- iperf3 3.21 source API was incompatible (start_time, iperf_strerror, etc.)
- iperf3 3.19 source is proven to work (used by CellularLab)
- CMakeLists.txt updated: config.h generated into cpp/iperf/ for correct include paths
- iperf_jni.c fixed for 3.19 API (no start_time on iperf_test)
- getTestProgress() returns g_test->duration as fallback (no real-time elapsed tracking yet)
- NDK CMake pinned to 3.22.1
- UI layer updated: removed Iperf3BinaryPathProvider references
- Tests updated: TestRunViewModelTest matches new service interface
- pubspec.yaml: removed iperf3 assets bundle, added http dependency
- AndroidManifest: INTERNET permission (needed for hostname resolution)
2026-08-25 13:46:33 +09:00
irene 815fc48a15 refactor: fix lint issues and wire up main screen
- Use const Iperf3Result constructor in tests
- Remove unused import from widget_test.dart
- Fix unnecessary underscore warnings in Consumer builders
- Use initializing formal for iperf3Service field
- Wire up main.dart to call Iperf3BinaryPathProvider.initialize()
2026-08-24 06:51:06 +09:00