diff --git a/dev/devicelab/lib/framework/framework.dart b/dev/devicelab/lib/framework/framework.dart index f1935a83c3..71cf696408 100644 --- a/dev/devicelab/lib/framework/framework.dart +++ b/dev/devicelab/lib/framework/framework.dart @@ -9,6 +9,7 @@ import 'dart:io'; import 'dart:isolate'; import 'package:logging/logging.dart'; +import 'package:stack_trace/stack_trace.dart'; import 'utils.dart'; @@ -115,16 +116,24 @@ class _TaskRunner { _keepAlivePort?.close(); } - Future _performTask() async { - try { - return await task(); - } catch (taskError, taskErrorStack) { + Future _performTask() { + Completer completer = new Completer(); + Chain.capture(() async { + completer.complete(await task()); + }, onError: (dynamic taskError, Chain taskErrorStack) { String message = 'Task failed: $taskError'; - if (taskErrorStack != null) { - message += '\n\n$taskErrorStack'; - } - return new TaskResult.failure(message); - } + stderr + ..writeln(message) + ..writeln('\nStack trace:') + ..writeln(taskErrorStack.terse); + // IMPORTANT: We're completing the future _successfully_ but with a value + // that indicates a task failure. This is intentional. At this point we + // are catching errors coming from arbitrary (and untrustworthy) task + // code. Our goal is to convert the failure into a readable message. + // Propagating it further is not useful. + completer.complete(new TaskResult.failure(message)); + }); + return completer.future; } } diff --git a/examples/flutter_gallery/test_driver/transitions_perf_test.dart b/examples/flutter_gallery/test_driver/transitions_perf_test.dart index 5adba0a05e..8c17bc34c7 100644 --- a/examples/flutter_gallery/test_driver/transitions_perf_test.dart +++ b/examples/flutter_gallery/test_driver/transitions_perf_test.dart @@ -59,6 +59,8 @@ final List demoTitles = [ 'Typography' ]; +final FileSystem _fs = new LocalFileSystem(); + Future saveDurationsHistogram(List> events) async { final Map> durations = new Map>(); Map startEvent; @@ -87,9 +89,8 @@ Future saveDurationsHistogram(List> events) async { // Save the durations Map to a file. final String destinationDirectory = 'build'; - final FileSystem fs = new LocalFileSystem(); - await fs.directory(destinationDirectory).create(recursive: true); - final File file = fs.file(path.join(destinationDirectory, 'transition_durations.timeline.json')); + await _fs.directory(destinationDirectory).create(recursive: true); + final File file = _fs.file(path.join(destinationDirectory, 'transition_durations.timeline.json')); await file.writeAsString(new JsonEncoder.withIndent(' ').convert(durations)); } @@ -136,10 +137,16 @@ void main() { // Save the duration (in microseconds) of the first timeline Frame event // that follows a 'Start Transition' event. The Gallery app adds a // 'Start Transition' event when a demo is launched (see GalleryItem). - saveDurationsHistogram(timeline.json['traceEvents']); - TimelineSummary summary = new TimelineSummary.summarize(timeline); - summary.writeSummaryToFile('transitions'); + summary.writeSummaryToFile('transitions', pretty: true); + try { + saveDurationsHistogram(timeline.json['traceEvents']); + } catch(_) { + summary.writeTimelineToFile('transitions', pretty: true); + print('ERROR: failed to extract transition events. Here is the full timeline:\n'); + print(await _fs.file('build/transitions.timeline.json').readAsString()); + rethrow; + } }, timeout: new Timeout(new Duration(minutes: 5))); });