forked from Foxyz/sync_replay_project
Win32Window::Create calls Destroy() before it makes the window, which ran FlutterWindow::OnDestroy and armed the 3 s exit timer at launch: the window showed, then the process ended with code 0, no crash, no close lines. The timer is only armed with an engine to tear down. Found and fixed by Fun Maker's agent on Windows (a debugger caught the exit on the timer's thread). The smoke runs missed it (they closed at ~4 s anyway): the app now stays up 10 s, and the scripts require 'closing' and 'quitting' from its own close. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
79 lines
3.1 KiB
Dart
79 lines
3.1 KiB
Dart
import 'dart:async';
|
|
import 'dart:io';
|
|
|
|
import 'package:flutter/foundation.dart' show debugPrint;
|
|
|
|
import '../app/app_graph.dart';
|
|
import 'app_shutdown.dart';
|
|
import 'ports/player_host.dart';
|
|
|
|
// The close smoke run (CI, `tool/smoke_windows.ps1` / `tool/smoke_linux.sh`):
|
|
// env `STARSYNC_SMOKE=<media file>` makes the app, once up, open that file
|
|
// in a session player with a video output through the media pipeline,
|
|
// play a second and close the normal way ([shutDownApp]). Progress goes to
|
|
// `STARSYNC_SMOKE_OUT` (one line per step: `start`, `opened <ms> render=<mode>`,
|
|
// `closing`, `quitting`, or `skip <why>` / `fail <why>`), so the script can
|
|
// tell a runner that cannot show a window (skip) from a close that hangs or
|
|
// leaves a process behind, or a process that ends on its own (fail). It
|
|
// stays up [smokeUptime] first: a start that dies by itself (the 1.3.0
|
|
// Windows exit timer) ends before `closing`.
|
|
|
|
/// How long the smoke run keeps the app up before closing it.
|
|
const smokeUptime = Duration(seconds: 10);
|
|
|
|
/// The smoke run's media, or null for a normal start.
|
|
String? get smokeMedia {
|
|
final m = Platform.environment['STARSYNC_SMOKE'];
|
|
return m == null || m.isEmpty ? null : m;
|
|
}
|
|
|
|
Future<void> runSmoke(AppGraph graph, String media) async {
|
|
final outPath = Platform.environment['STARSYNC_SMOKE_OUT'];
|
|
void step(String line) {
|
|
debugPrint('smoke: $line'); // into the app log too
|
|
if (outPath == null || outPath.isEmpty) return;
|
|
try {
|
|
File(outPath).writeAsStringSync('$line\n', mode: FileMode.append, flush: true);
|
|
} catch (_) {}
|
|
}
|
|
|
|
step('start');
|
|
final sw = Stopwatch()..start();
|
|
final pipe = graph.coordinator.media;
|
|
try {
|
|
if (!await File(media).exists()) {
|
|
step('fail no media file at $media');
|
|
} else {
|
|
await pipe.start();
|
|
await pipe.playback.setVolume(0);
|
|
// The video output the session view would make: the render path
|
|
// (EGL / ANGLE) is what breaks on some machines.
|
|
final video = pipe.playback.video;
|
|
await video.ready.timeout(const Duration(seconds: 15));
|
|
final ok = await pipe.open(media);
|
|
final end = DateTime.now().add(const Duration(seconds: 20));
|
|
while (ok && pipe.playback.durationSecs <= 0 && DateTime.now().isBefore(end)) {
|
|
await Future.delayed(const Duration(milliseconds: 100));
|
|
}
|
|
if (pipe.playback.durationSecs <= 0) {
|
|
step('fail the media did not open in 20 s');
|
|
} else {
|
|
await pipe.playback.player.play();
|
|
await Future.delayed(const Duration(seconds: 1));
|
|
final mode = graph.ports.players.renderMode.peek();
|
|
step('opened ${sw.elapsedMilliseconds} render=${mode.name}');
|
|
if (mode == RenderMode.unknown) await Future.delayed(const Duration(seconds: 2));
|
|
}
|
|
}
|
|
} on TimeoutException {
|
|
step('skip the video output did not come up (no display for this runner?)');
|
|
} catch (e) {
|
|
step('fail $e');
|
|
}
|
|
final wait = smokeUptime - sw.elapsed;
|
|
if (wait > Duration.zero) await Future.delayed(wait);
|
|
step('closing');
|
|
// Reached only through the app's own close (not a kill on the way).
|
|
await shutDownApp(beforeQuit: () async => step('quitting'));
|
|
}
|