| // Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file |
| // for details. All rights reserved. Use of this source code is governed by a |
| // BSD-style license that can be found in the LICENSE.md file. |
| |
| import 'dart:async' show StreamSubscription, Timer; |
| import 'dart:convert' show jsonEncode; |
| import 'dart:io' show File, exitCode; |
| import 'dart:isolate' show Isolate, ReceivePort, SendPort; |
| |
| import 'package:args/args.dart' show ArgParser; |
| |
| import "frontend_server_flutter.dart" show Logger, compileTests; |
| |
| const suiteNamePrefix = "flutter_frontend"; |
| |
| class Options { |
| final String configurationName; |
| final bool verbose; |
| final bool printFailureLog; |
| final Uri outputDirectory; |
| final String testFilter; |
| final String flutterDir; |
| final String flutterPlatformDir; |
| |
| Options( |
| this.configurationName, |
| this.verbose, |
| this.printFailureLog, |
| this.outputDirectory, |
| this.testFilter, |
| this.flutterDir, |
| this.flutterPlatformDir); |
| |
| static Options parse(List<String> args) { |
| var parser = new ArgParser() |
| ..addOption("named-configuration", |
| abbr: "n", |
| defaultsTo: suiteNamePrefix, |
| help: "configuration name to use for emitting json result files") |
| ..addOption("output-directory", |
| help: "directory to which results.json and logs.json are written") |
| ..addFlag("verbose", |
| abbr: "v", help: "print additional information", defaultsTo: false) |
| ..addFlag("print", |
| abbr: "p", help: "print failure logs", defaultsTo: false) |
| ..addOption("flutterDir") |
| ..addOption("flutterPlatformDir"); |
| var parsedArguments = parser.parse(args); |
| String outputPath = parsedArguments["output-directory"] ?? "."; |
| Uri outputDirectory = Uri.base.resolveUri(Uri.directory(outputPath)); |
| String filter; |
| if (parsedArguments.rest.length == 1) { |
| filter = parsedArguments.rest.single; |
| if (filter.startsWith("$suiteNamePrefix/")) { |
| filter = filter.substring(suiteNamePrefix.length + 1); |
| } |
| } |
| return Options( |
| parsedArguments["named-configuration"], |
| parsedArguments["verbose"], |
| parsedArguments["print"], |
| outputDirectory, |
| filter, |
| parsedArguments["flutterDir"], |
| parsedArguments["flutterPlatformDir"], |
| ); |
| } |
| } |
| |
| class ResultLogger extends Logger { |
| final SuiteConfiguration suiteConfiguration; |
| final Map<String, Stopwatch> stopwatches = {}; |
| List<String> _log = <String>[]; |
| |
| ResultLogger(this.suiteConfiguration); |
| |
| handleTestResult(String testName, bool matchedExpectations) { |
| String fullTestName = "$suiteNamePrefix/$testName"; |
| suiteConfiguration.resultsPort.send(jsonEncode({ |
| "name": fullTestName, |
| "configuration": suiteConfiguration.configurationName, |
| "suite": suiteNamePrefix, |
| "test_name": testName, |
| "time_ms": stopwatches[testName].elapsedMilliseconds, |
| "expected": "Pass", |
| "result": matchedExpectations ? "Pass" : "Fail", |
| "matches": matchedExpectations, |
| })); |
| if (!matchedExpectations) { |
| String failureLog = _log.join("\n"); |
| failureLog = "$failureLog\n\nRe-run this test: dart --enable-asserts " |
| "pkg/frontend_server/test/test.dart " |
| "--flutterDir=${suiteConfiguration.flutterDir} " |
| "--flutterPlatformDir=${suiteConfiguration.flutterPlatformDir} " |
| "-p $testName"; |
| suiteConfiguration.logsPort.send(jsonEncode({ |
| "name": fullTestName, |
| "configuration": suiteConfiguration.configurationName, |
| "result": matchedExpectations ? "OK" : "Failure", |
| "log": failureLog, |
| })); |
| if (suiteConfiguration.printFailureLog) { |
| print('FAILED: $fullTestName'); |
| print(failureLog); |
| } |
| } |
| if (suiteConfiguration.verbose) { |
| String result = matchedExpectations ? "PASS" : "FAIL"; |
| print("${fullTestName}: ${result}"); |
| } |
| } |
| |
| void logTestStart(String testName) { |
| stopwatches[testName] = Stopwatch()..start(); |
| _log.clear(); |
| } |
| |
| @override |
| void log(String s) { |
| _log.add(s); |
| } |
| |
| @override |
| void notice(String s) { |
| // ignored. |
| } |
| |
| @override |
| void logExpectedResult(String testName) { |
| handleTestResult(testName, true); |
| } |
| |
| @override |
| void logUnexpectedResult(String testName) { |
| handleTestResult(testName, false); |
| } |
| } |
| |
| const Duration timeoutDuration = Duration(minutes: 45); |
| |
| class SuiteConfiguration { |
| final SendPort resultsPort; |
| final SendPort logsPort; |
| final bool verbose; |
| final bool printFailureLog; |
| final String configurationName; |
| final String testFilter; |
| |
| final int shard; |
| final int shards; |
| |
| final String flutterDir; |
| final String flutterPlatformDir; |
| |
| const SuiteConfiguration( |
| this.resultsPort, |
| this.logsPort, |
| this.verbose, |
| this.printFailureLog, |
| this.configurationName, |
| this.testFilter, |
| this.flutterDir, |
| this.flutterPlatformDir, |
| this.shard, |
| this.shards); |
| } |
| |
| void runSuite(SuiteConfiguration configuration) async { |
| ResultLogger logger = ResultLogger(configuration); |
| try { |
| await compileTests( |
| configuration.flutterDir, |
| configuration.flutterPlatformDir, |
| logger, |
| filter: configuration.testFilter, |
| shard: configuration.shard, |
| shards: configuration.shards, |
| ); |
| } catch (e) { |
| logger.logUnexpectedResult("startup"); |
| } |
| } |
| |
| void writeLinesToFile(Uri uri, List<String> lines) async { |
| await File.fromUri(uri).writeAsString(lines.map((line) => "$line\n").join()); |
| } |
| |
| main([List<String> arguments = const <String>[]]) async { |
| List<String> results = []; |
| List<String> logs = []; |
| Options options = Options.parse(arguments); |
| ReceivePort resultsPort = new ReceivePort() |
| ..listen((resultEntry) => results.add(resultEntry)); |
| ReceivePort logsPort = new ReceivePort() |
| ..listen((logEntry) => logs.add(logEntry)); |
| String filter = options.testFilter; |
| |
| const int shards = 4; |
| List<Future<bool>> futures = []; |
| for (int shard = 0; shard < shards; shard++) { |
| // Start the test suite in a new isolate. |
| ReceivePort exitPort = new ReceivePort(); |
| ReceivePort errorPort = new ReceivePort(); |
| SuiteConfiguration configuration = new SuiteConfiguration( |
| resultsPort.sendPort, |
| logsPort.sendPort, |
| options.verbose, |
| options.printFailureLog, |
| options.configurationName, |
| filter, |
| options.flutterDir, |
| options.flutterPlatformDir, |
| shard, |
| shards, |
| ); |
| Future<bool> future = Future<bool>(() async { |
| Stopwatch stopwatch = Stopwatch()..start(); |
| print("Running suite shard $shard of $shards"); |
| Isolate isolate = await Isolate.spawn<SuiteConfiguration>( |
| runSuite, configuration, |
| onExit: exitPort.sendPort, onError: errorPort.sendPort); |
| bool gotError = false; |
| StreamSubscription errorSubscription = errorPort.listen((message) { |
| print("Got error: $message!"); |
| gotError = true; |
| logs.add("$message"); |
| }); |
| bool timedOut = false; |
| Timer timer = Timer(timeoutDuration, () { |
| timedOut = true; |
| print("Suite timed out after " |
| "${timeoutDuration.inMilliseconds}ms"); |
| isolate.kill(priority: Isolate.immediate); |
| }); |
| await exitPort.first; |
| errorSubscription.cancel(); |
| timer.cancel(); |
| if (!timedOut && !gotError) { |
| int seconds = stopwatch.elapsedMilliseconds ~/ 1000; |
| print("Suite finished (shard #$shard) (took ${seconds} seconds)"); |
| } |
| return timedOut || gotError; |
| }); |
| futures.add(future); |
| } |
| |
| // Wait for isolate to terminate and clean up. |
| Iterable<bool> timeoutsOrCrashes = await Future.wait(futures); |
| bool timeoutOrCrash = timeoutsOrCrashes.any((timeout) => timeout); |
| resultsPort.close(); |
| logsPort.close(); |
| |
| // Write results.json and logs.json. |
| Uri resultJsonUri = options.outputDirectory.resolve("results.json"); |
| Uri logsJsonUri = options.outputDirectory.resolve("logs.json"); |
| await writeLinesToFile(resultJsonUri, results); |
| await writeLinesToFile(logsJsonUri, logs); |
| print("Log files written to ${resultJsonUri.toFilePath()} and" |
| " ${logsJsonUri.toFilePath()}"); |
| |
| exitCode = timeoutOrCrash ? 1 : 0; |
| } |