diff --git a/packages/flutter_driver/lib/src/driver/timeline_summary.dart b/packages/flutter_driver/lib/src/driver/timeline_summary.dart index b364277d33..cd7451c299 100644 --- a/packages/flutter_driver/lib/src/driver/timeline_summary.dart +++ b/packages/flutter_driver/lib/src/driver/timeline_summary.dart @@ -20,6 +20,16 @@ import 'vsync_frame_lag_summarizer.dart'; const JsonEncoder _prettyEncoder = JsonEncoder.withIndent(' '); +const String _kEmptyDurationMessage = r''' +The TimelineSummary had no events to summarize. + +This can happen if the timeline summarization covered too short of a period +or if the driver script failed to interact with the application to generate +events. For example, if your driver script contained only a "driver.scroll()" +command but the app under test was not scrollable then no events would be +generated by the interaction. +'''; + /// The maximum amount of time considered safe to spend for a frame's build /// phase. Anything past that is in the danger of missing the frame as 60FPS. const Duration kBuildBudget = Duration(milliseconds: 16); @@ -40,21 +50,21 @@ class TimelineSummary { /// Average amount of time spent per frame in the framework building widgets, /// updating layout, painting and compositing. /// - /// Returns null if no frames were recorded. + /// Throws a [StateError] if this summary contains no timeline events. double computeAverageFrameBuildTimeMillis() { return _averageInMillis(_extractFrameDurations()); } /// The [p]-th percentile frame rasterization time in milliseconds. /// - /// Returns null if no frames were recorded. + /// Throws a [StateError] if this summary contains no timeline events. double computePercentileFrameBuildTimeMillis(double p) { return _percentileInMillis(_extractFrameDurations(), p); } /// The longest frame build time in milliseconds. /// - /// Returns null if no frames were recorded. + /// Throws a [StateError] if this summary contains no timeline events. double computeWorstFrameBuildTimeMillis() { return _maxInMillis(_extractFrameDurations()); } @@ -67,21 +77,21 @@ class TimelineSummary { /// Average amount of time spent per frame in the engine rasterizer. /// - /// Returns null if no frames were recorded. + /// Throws a [StateError] if this summary contains no timeline events. double computeAverageFrameRasterizerTimeMillis() { return _averageInMillis(_extractGpuRasterizerDrawDurations()); } /// The longest frame rasterization time in milliseconds. /// - /// Returns null if no frames were recorded. + /// Throws a [StateError] if this summary contains no timeline events. double computeWorstFrameRasterizerTimeMillis() { return _maxInMillis(_extractGpuRasterizerDrawDurations()); } /// The [p]-th percentile frame rasterization time in milliseconds. /// - /// Returns null if no frames were recorded. + /// Throws a [StateError] if this summary contains no timeline events. double computePercentileFrameRasterizerTimeMillis(double p) { return _percentileInMillis(_extractGpuRasterizerDrawDurations(), p); } @@ -409,26 +419,26 @@ class TimelineSummary { return result; } - double _averageInMillis(Iterable durations) { + double _averageInMillis(List durations) { if (durations.isEmpty) { - throw ArgumentError('durations is empty!'); + throw StateError(_kEmptyDurationMessage); } final double total = durations.fold(0.0, (double t, Duration duration) => t + duration.inMicroseconds.toDouble() / 1000.0); return total / durations.length; } - double _percentileInMillis(Iterable durations, double percentile) { + double _percentileInMillis(List durations, double percentile) { if (durations.isEmpty) { - throw ArgumentError('durations is empty!'); + throw StateError(_kEmptyDurationMessage); } assert(percentile >= 0.0 && percentile <= 100.0); final List doubles = durations.map((Duration duration) => duration.inMicroseconds.toDouble() / 1000.0).toList(); return findPercentile(doubles, percentile); } - double _maxInMillis(Iterable durations) { + double _maxInMillis(List durations) { if (durations.isEmpty) { - throw ArgumentError('durations is empty!'); + throw StateError(_kEmptyDurationMessage); } return durations .map((Duration duration) => duration.inMicroseconds.toDouble() / 1000.0) diff --git a/packages/flutter_driver/test/src/real_tests/timeline_summary_test.dart b/packages/flutter_driver/test/src/real_tests/timeline_summary_test.dart index 3fec56866f..b94249191e 100644 --- a/packages/flutter_driver/test/src/real_tests/timeline_summary_test.dart +++ b/packages/flutter_driver/test/src/real_tests/timeline_summary_test.dart @@ -159,7 +159,12 @@ void main() { test('throws when there is no data', () { expect( () => summarize(>[]).computeAverageFrameBuildTimeMillis(), - throwsA(predicate((ArgumentError e) => e.message == 'durations is empty!')), + throwsA( + isA() + .having((StateError e) => e.message, + 'message', + contains('The TimelineSummary had no events to summarize.'), + )), ); }); @@ -223,7 +228,12 @@ void main() { test('throws when there is no data', () { expect( () => summarize(>[]).computeWorstFrameBuildTimeMillis(), - throwsA(predicate((ArgumentError e) => e.message == 'durations is empty!')), + throwsA( + isA() + .having((StateError e) => e.message, + 'message', + contains('The TimelineSummary had no events to summarize.'), + )), ); }); @@ -282,7 +292,12 @@ void main() { test('throws when there is no data', () { expect( () => summarize(>[]).computeAverageFrameRasterizerTimeMillis(), - throwsA(predicate((ArgumentError e) => e.message == 'durations is empty!')), + throwsA( + isA() + .having((StateError e) => e.message, + 'message', + contains('The TimelineSummary had no events to summarize.'), + )), ); }); @@ -321,7 +336,12 @@ void main() { test('throws when there is no data', () { expect( () => summarize(>[]).computeWorstFrameRasterizerTimeMillis(), - throwsA(predicate((ArgumentError e) => e.message == 'durations is empty!')), + throwsA( + isA() + .having((StateError e) => e.message, + 'message', + contains('The TimelineSummary had no events to summarize.'), + )), ); }); @@ -368,7 +388,12 @@ void main() { test('throws when there is no data', () { expect( () => summarize(>[]).computePercentileFrameRasterizerTimeMillis(90.0), - throwsA(predicate((ArgumentError e) => e.message == 'durations is empty!')), + throwsA( + isA() + .having((StateError e) => e.message, + 'message', + contains('The TimelineSummary had no events to summarize.'), + )), ); });