| // Copyright 2014 The Flutter Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| import 'dart:async'; | |
| import 'dart:convert'; | |
| import 'dart:io'; | |
| /// Reads through the print commands from [process] waiting for the magic phase | |
| /// that contains microbenchmarks results as defined in | |
| /// `dev/benchmarks/microbenchmarks/lib/common.dart`. | |
| /// | |
| /// If you are using this outside of microbenchmarks, ensure you print a single | |
| /// line with `╡ ••• Done ••• ╞` to signal the end of collection. | |
| Future<Map<String, double>> readJsonResults(Process process) { | |
| // IMPORTANT: keep these values in sync with dev/benchmarks/microbenchmarks/lib/common.dart | |
| const jsonStart = '================ RESULTS ================'; | |
| const jsonEnd = '================ FORMATTED =============='; | |
| const jsonPrefix = ':::JSON:::'; | |
| const testComplete = '╡ ••• Done ••• ╞'; | |
| var jsonStarted = false; | |
| final jsonBuf = StringBuffer(); | |
| final completer = Completer<Map<String, double>>(); | |
| final StreamSubscription<String> stderrSub = process.stderr | |
| .transform<String>(const Utf8Decoder()) | |
| .transform<String>(const LineSplitter()) | |
| .listen((String line) { | |
| stderr.writeln('[STDERR] $line'); | |
| }); | |
| final collectedJson = <String>[]; | |
| var processWasKilledIntentionally = false; | |
| final StreamSubscription<String> stdoutSub = process.stdout | |
| .transform<String>(const Utf8Decoder()) | |
| .transform<String>(const LineSplitter()) | |
| .listen((String line) async { | |
| print('[STDOUT] $line'); | |
| if (line.contains(jsonStart)) { | |
| jsonStarted = true; | |
| return; | |
| } | |
| if (line.contains(testComplete)) { | |
| processWasKilledIntentionally = true; | |
| // Sending a SIGINT/SIGTERM to the process here isn't reliable because [process] is | |
| // the shell (flutter is a shell script) and doesn't pass the signal on. | |
| // Sending a `q` is an instruction to quit using the console runner. | |
| // See https://github.com/flutter/flutter/issues/19208 | |
| process.stdin.write('q'); | |
| await process.stdin.flush(); | |
| // Give the process a couple of seconds to exit and run shutdown hooks | |
| // before sending kill signal. | |
| // TODO(fujino): https://github.com/flutter/flutter/issues/134566 | |
| await Future<void>.delayed(const Duration(seconds: 2)); | |
| // Also send a kill signal in case the `q` above didn't work. | |
| process.kill(ProcessSignal.sigint); | |
| try { | |
| final results = Map<String, double>.from(<String, dynamic>{ | |
| for (final String data in collectedJson) ...json.decode(data) as Map<String, dynamic>, | |
| }); | |
| completer.complete(results); | |
| } catch (ex) { | |
| completer.completeError( | |
| 'Decoding JSON failed ($ex). JSON strings where: $collectedJson', | |
| ); | |
| } | |
| return; | |
| } | |
| if (jsonStarted && line.contains(jsonEnd)) { | |
| collectedJson.add(jsonBuf.toString().trim()); | |
| jsonBuf.clear(); | |
| jsonStarted = false; | |
| } | |
| if (jsonStarted && line.contains(jsonPrefix)) { | |
| jsonBuf.writeln(line.substring(line.indexOf(jsonPrefix) + jsonPrefix.length)); | |
| } | |
| }); | |
| process.exitCode.then<void>((int code) async { | |
| await Future.wait<void>(<Future<void>>[stdoutSub.cancel(), stderrSub.cancel()]); | |
| if (!processWasKilledIntentionally && code != 0) { | |
| completer.completeError('flutter run failed: exit code=$code'); | |
| } | |
| }); | |
| return completer.future; | |
| } |