feat: add a headless entry point that runs a test with no UI

Separation that nothing exercises is separation that quietly stops being true,
so this runs a full test through Iperf3Service and Iperf3Session with
WidgetsFlutterBinding but no runApp: no widget tree, no render surface. If the
run path ever starts needing the UI, this stops working. An architecture test
additionally forbids it importing lib/ui.

It is also the accurate way to measure. A 4-stream run against a local server
reports 30.2% local CPU here, against 79.9% for a comparable run with the
transcript repainting alongside — the UI was costing roughly fifty points of
CPU on a phone already near CPU-bound at gigabit rates. Throughput figures
taken through the UI are therefore pessimistic, and this is the path to use
when the number matters.

Parameters come from --dart-define so a run needs no code edit:
  flutter run -t lib/main_headless.dart --dart-define=IPERF_SERVER=…

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-26 20:24:44 +09:00
parent 9485049b57
commit fc15897541
2 changed files with 125 additions and 2 deletions
+20 -2
View File
@@ -82,6 +82,22 @@ void main() {
);
});
test('the headless entry point does not depend on ui', () {
// main_headless.dart is the standing proof that the core runs without a
// widget tree; an import from lib/ui would quietly end that.
final offenders = _importsMatching(
'lib',
(import) => import.contains('/ui/'),
onlyFiles: {'lib/main_headless.dart'},
);
expect(
offenders,
isEmpty,
reason: 'The headless entry point must run the core with no UI.\n'
'${_render(offenders)}',
);
});
test('every layer directory is actually covered by these rules', () {
// Guards against a new top-level layer appearing and silently escaping
// every rule above.
@@ -102,8 +118,9 @@ void main() {
/// Every `(file, import)` pair under [directory] whose import satisfies [test].
List<({String file, String import})> _importsMatching(
String directory,
bool Function(String import) test,
) {
bool Function(String import) test, {
Set<String>? onlyFiles,
}) {
final results = <({String file, String import})>[];
final dir = Directory(directory);
if (!dir.existsSync()) return results;
@@ -111,6 +128,7 @@ List<({String file, String import})> _importsMatching(
final pattern = RegExp(r'''^\s*import\s+['"]([^'"]+)['"]''', multiLine: true);
for (final entity in dir.listSync(recursive: true)) {
if (entity is! File || !entity.path.endsWith('.dart')) continue;
if (onlyFiles != null && !onlyFiles.contains(entity.path)) continue;
final source = entity.readAsStringSync();
for (final match in pattern.allMatches(source)) {
final import = match.group(1)!;