From 07a3334a3b3bd8f96dde32706ab6b98498925337 Mon Sep 17 00:00:00 2001 From: "Ming Lyu (CareF)" Date: Thu, 20 Aug 2020 15:04:36 -0400 Subject: [PATCH 1/5] add support to timeline --- .../test_driver/example_integration_io.dart | 27 ++++--- .../lib/integration_test.dart | 77 +++++++++++++++++++ packages/integration_test/pubspec.yaml | 2 + .../integration_test/test/binding_test.dart | 34 ++++++++ 4 files changed, 128 insertions(+), 12 deletions(-) diff --git a/packages/integration_test/example/test_driver/example_integration_io.dart b/packages/integration_test/example/test_driver/example_integration_io.dart index 35fc7271d841..602d14877c18 100644 --- a/packages/integration_test/example/test_driver/example_integration_io.dart +++ b/packages/integration_test/example/test_driver/example_integration_io.dart @@ -13,22 +13,25 @@ import 'package:integration_test/integration_test.dart'; import 'package:integration_test_example/main.dart' as app; void main() { - IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + final IntegrationTestWidgetsFlutterBinding binding = + IntegrationTestWidgetsFlutterBinding.ensureInitialized() as IntegrationTestWidgetsFlutterBinding; testWidgets('verify text', (WidgetTester tester) async { // Build our app and trigger a frame. app.main(); - // Trigger a frame. - await tester.pumpAndSettle(); + await binding.traceAction(() async { + // Trigger a frame. + await tester.pumpAndSettle(); - // Verify that platform version is retrieved. - expect( - find.byWidgetPredicate( - (Widget widget) => - widget is Text && - widget.data.startsWith('Platform: ${Platform.operatingSystem}'), - ), - findsOneWidget, - ); + // Verify that platform version is retrieved. + expect( + find.byWidgetPredicate( + (Widget widget) => + widget is Text && + widget.data.startsWith('Platform: ${Platform.operatingSystem}'), + ), + findsOneWidget, + ); + }); }); } diff --git a/packages/integration_test/lib/integration_test.dart b/packages/integration_test/lib/integration_test.dart index 430b7ee38510..bb4ca4d935ec 100644 --- a/packages/integration_test/lib/integration_test.dart +++ b/packages/integration_test/lib/integration_test.dart @@ -3,12 +3,15 @@ // found in the LICENSE file. import 'dart:async'; +import 'dart:developer' as developer; import 'package:flutter/rendering.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; +import 'package:vm_service/vm_service.dart' as vm; +import 'package:vm_service/vm_service_io.dart' as vm_io; import 'common.dart'; import '_extension_io.dart' if (dart.library.html) '_extension_web.dart'; @@ -191,4 +194,78 @@ class IntegrationTestWidgetsFlutterBinding ); results[description] ??= _success; } + + vm.VmService _vmService; + + /// Initialize the [vm.VmService] settings for the timeline. + @visibleForTesting + Future enableTimeline({ + List streams = const ['all'], + @visibleForTesting vm.VmService vmService, + }) async { + assert(streams != null); + assert(streams.isNotEmpty); + if (vmService != null) { + _vmService = vmService; + } + if (_vmService == null) { + final developer.ServiceProtocolInfo info = await developer.Service.getInfo(); + assert(info.serverUri != null); + _vmService = await vm_io.vmServiceConnectUri( + 'ws://localhost:${info.serverUri.port}${info.serverUri.path}ws', + ); + } + await _vmService.setVMTimelineFlags(streams); + } + + /// Runs [action] and outputs a [vm.Timeline] trace for it. + /// + /// Waits for the `Future` returned by [action] to complete prior to stopping + /// the trace. + /// + /// `streams` limits the recorded timeline event streams to only the ones + /// listed. By default, all streams are recorded. + /// See `timeline_streams` in + /// https://github.com/dart-lang/sdk/blob/master/runtime/vm/timeline.cc + /// + /// If [retainPriorEvents] is true, retains events recorded prior to calling + /// [action]. Otherwise, prior events are cleared before calling [action]. By + /// default, prior events are cleared. + Future traceTimeline( + Future action(), { + List streams = const ['all'], + bool retainPriorEvents = false, + }) async { + await enableTimeline(streams: streams); + if (retainPriorEvents) { + await action(); + return await _vmService.getVMTimeline(); + } + + await _vmService.clearVMTimeline(); + final vm.Timestamp startTime = await _vmService.getVMTimelineMicros(); + await action(); + final vm.Timestamp endTime = await _vmService.getVMTimelineMicros(); + return await _vmService.getVMTimeline( + timeOriginMicros: startTime.timestamp, + timeExtentMicros: endTime.timestamp, + ); + } + + /// This is a convience wrap of [traceTimeline] and send the result back to + /// the host. + Future traceAction( + Future action(), { + List streams = const ['all'], + bool retainPriorEvents = false, + String reportKey = 'timeline', + }) async { + vm.Timeline timeline = await traceTimeline( + action, + streams: streams, + retainPriorEvents: retainPriorEvents, + ); + reportData ??= {}; + reportData[reportKey] = timeline.toJson(); + } } diff --git a/packages/integration_test/pubspec.yaml b/packages/integration_test/pubspec.yaml index c1514146c869..71cfeb65dd6f 100644 --- a/packages/integration_test/pubspec.yaml +++ b/packages/integration_test/pubspec.yaml @@ -15,9 +15,11 @@ dependencies: flutter_test: sdk: flutter path: ^1.6.4 + vm_service: ^4.2.0 dev_dependencies: pedantic: ^1.8.0 + mockito: ^4.1.1 flutter: plugin: diff --git a/packages/integration_test/test/binding_test.dart b/packages/integration_test/test/binding_test.dart index bad365ac59b6..ef4efc59aac0 100644 --- a/packages/integration_test/test/binding_test.dart +++ b/packages/integration_test/test/binding_test.dart @@ -1,8 +1,18 @@ +import 'dart:convert'; + import 'package:flutter/material.dart'; import 'package:integration_test/integration_test.dart'; import 'package:integration_test/common.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:mockito/mockito.dart'; +import 'package:vm_service/vm_service.dart' as vm; + +vm.Timeline _ktimelines = vm.Timeline( + traceEvents: [], + timeOriginMicros: 100, + timeExtentMicros: 200, +); void main() async { Future> request; @@ -14,10 +24,21 @@ void main() async { final IntegrationTestWidgetsFlutterBinding integrationBinding = binding as IntegrationTestWidgetsFlutterBinding; + MockVM mockVM; + List clockTimes = [100, 200]; + setUp(() { request = integrationBinding.callback({ 'command': 'request_data', }); + mockVM = MockVM(); + when(mockVM.getVMTimeline( + timeOriginMicros: anyNamed('timeOriginMicros'), + timeExtentMicros: anyNamed('timeExtentMicros'), + )).thenAnswer((_) => Future.value(_ktimelines)); + when(mockVM.getVMTimelineMicros()).thenAnswer( + (_) => Future.value(vm.Timestamp(timestamp: clockTimes.removeAt(0))), + ); }); testWidgets('Run Integration app', (WidgetTester tester) async { @@ -53,6 +74,17 @@ void main() async { expect(widgetCenter.dx, windowCenterX); expect(widgetCenter.dy, windowCenterY); }); + + testWidgets('Test traceAction', (WidgetTester tester) async { + await integrationBinding.enableTimeline(vmService: mockVM); + await integrationBinding.traceAction(() async {}); + expect(integrationBinding.reportData, isNotNull); + expect(integrationBinding.reportData.containsKey('timeline'), true); + expect( + json.encode(integrationBinding.reportData['timeline']), + json.encode(_ktimelines), + ); + }); }); tearDownAll(() async { @@ -66,3 +98,5 @@ void main() async { assert(result.data['answer'] == 42); }); } + +class MockVM extends Mock implements vm.VmService {} From b2f2a81472da821fae6880508d88cf81ac33fed9 Mon Sep 17 00:00:00 2001 From: "Ming Lyu (CareF)" Date: Thu, 20 Aug 2020 15:06:49 -0400 Subject: [PATCH 2/5] update version --- packages/integration_test/CHANGELOG.md | 4 ++++ packages/integration_test/pubspec.yaml | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/integration_test/CHANGELOG.md b/packages/integration_test/CHANGELOG.md index 6e5e4bb3d51b..0afee99334c6 100644 --- a/packages/integration_test/CHANGELOG.md +++ b/packages/integration_test/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.8.2 + +* Add support to get timeline. + ## 0.8.1 * Show stack trace of widget test errors on the platform side diff --git a/packages/integration_test/pubspec.yaml b/packages/integration_test/pubspec.yaml index 71cfeb65dd6f..9dd4ade6ce49 100644 --- a/packages/integration_test/pubspec.yaml +++ b/packages/integration_test/pubspec.yaml @@ -1,6 +1,6 @@ name: integration_test description: Runs tests that use the flutter_test API as integration tests. -version: 0.8.1 +version: 0.8.2 homepage: https://github.com/flutter/plugins/tree/master/packages/integration_test environment: From ca3d26b3a4ecc351e3df33bdda628eee07c94d1a Mon Sep 17 00:00:00 2001 From: "Ming Lyu (CareF)" Date: Thu, 20 Aug 2020 16:38:42 -0400 Subject: [PATCH 3/5] formatting --- .../example/test_driver/example_integration_io.dart | 3 ++- packages/integration_test/lib/integration_test.dart | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/integration_test/example/test_driver/example_integration_io.dart b/packages/integration_test/example/test_driver/example_integration_io.dart index 602d14877c18..1a09539d21d8 100644 --- a/packages/integration_test/example/test_driver/example_integration_io.dart +++ b/packages/integration_test/example/test_driver/example_integration_io.dart @@ -14,7 +14,8 @@ import 'package:integration_test_example/main.dart' as app; void main() { final IntegrationTestWidgetsFlutterBinding binding = - IntegrationTestWidgetsFlutterBinding.ensureInitialized() as IntegrationTestWidgetsFlutterBinding; + IntegrationTestWidgetsFlutterBinding.ensureInitialized() + as IntegrationTestWidgetsFlutterBinding; testWidgets('verify text', (WidgetTester tester) async { // Build our app and trigger a frame. app.main(); diff --git a/packages/integration_test/lib/integration_test.dart b/packages/integration_test/lib/integration_test.dart index bb4ca4d935ec..ac35148f1afa 100644 --- a/packages/integration_test/lib/integration_test.dart +++ b/packages/integration_test/lib/integration_test.dart @@ -209,7 +209,8 @@ class IntegrationTestWidgetsFlutterBinding _vmService = vmService; } if (_vmService == null) { - final developer.ServiceProtocolInfo info = await developer.Service.getInfo(); + final developer.ServiceProtocolInfo info = + await developer.Service.getInfo(); assert(info.serverUri != null); _vmService = await vm_io.vmServiceConnectUri( 'ws://localhost:${info.serverUri.port}${info.serverUri.path}ws', From 73313c4ee236110b7934fdec4b08bca0481ed4c1 Mon Sep 17 00:00:00 2001 From: "Ming Lyu (CareF)" Date: Thu, 27 Aug 2020 10:33:12 -0400 Subject: [PATCH 4/5] modify according to dnfield@ --- .../test_driver/example_integration_io.dart | 3 +++ .../lib/integration_test.dart | 20 +++++++++++++------ 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/packages/integration_test/example/test_driver/example_integration_io.dart b/packages/integration_test/example/test_driver/example_integration_io.dart index 1a09539d21d8..7ed28963c32b 100644 --- a/packages/integration_test/example/test_driver/example_integration_io.dart +++ b/packages/integration_test/example/test_driver/example_integration_io.dart @@ -20,6 +20,9 @@ void main() { // Build our app and trigger a frame. app.main(); + // Trace the timeline of the following operation. The timeline result will + // be written to `build/integration_response_data.json` with the key + // `timeline`. await binding.traceAction(() async { // Trigger a frame. await tester.pumpAndSettle(); diff --git a/packages/integration_test/lib/integration_test.dart b/packages/integration_test/lib/integration_test.dart index ac35148f1afa..7701ec8e919a 100644 --- a/packages/integration_test/lib/integration_test.dart +++ b/packages/integration_test/lib/integration_test.dart @@ -219,15 +219,15 @@ class IntegrationTestWidgetsFlutterBinding await _vmService.setVMTimelineFlags(streams); } - /// Runs [action] and outputs a [vm.Timeline] trace for it. + /// Runs [action] and returns a [vm.Timeline] trace for it. /// /// Waits for the `Future` returned by [action] to complete prior to stopping /// the trace. /// - /// `streams` limits the recorded timeline event streams to only the ones - /// listed. By default, all streams are recorded. + /// The `streams` parameter limits the recorded timeline event streams to only + /// the ones listed. By default, all streams are recorded. /// See `timeline_streams` in - /// https://github.com/dart-lang/sdk/blob/master/runtime/vm/timeline.cc + /// [Dart-SDK/runtime/vm/timeline.cc](https://github.com/dart-lang/sdk/blob/master/runtime/vm/timeline.cc) /// /// If [retainPriorEvents] is true, retains events recorded prior to calling /// [action]. Otherwise, prior events are cleared before calling [action]. By @@ -253,8 +253,16 @@ class IntegrationTestWidgetsFlutterBinding ); } - /// This is a convience wrap of [traceTimeline] and send the result back to - /// the host. + /// This is a convenience wrap of [traceTimeline] and send the result back to + /// the host for the [flutter_driver] style tests. + /// + /// This records the timeline during `action` and adds the result to + /// [reportData] with `reportKey`. + /// + /// For tests with multiple calls of this method, `reportKey` needs to be a + /// unique key, otherwise the later result will override earlier one. + /// + /// `streams` and `retainPriorEvents` are passed as-is to [traceTimeline]. Future traceAction( Future action(), { List streams = const ['all'], From 60786813e0db3b7f5b4605dcc59677565b9a8ff6 Mon Sep 17 00:00:00 2001 From: "Ming Lyu (CareF)" Date: Thu, 27 Aug 2020 17:02:09 -0400 Subject: [PATCH 5/5] update according to dnfield@ --- packages/integration_test/lib/integration_test.dart | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/integration_test/lib/integration_test.dart b/packages/integration_test/lib/integration_test.dart index 7701ec8e919a..f6980bc9d6d1 100644 --- a/packages/integration_test/lib/integration_test.dart +++ b/packages/integration_test/lib/integration_test.dart @@ -257,12 +257,17 @@ class IntegrationTestWidgetsFlutterBinding /// the host for the [flutter_driver] style tests. /// /// This records the timeline during `action` and adds the result to - /// [reportData] with `reportKey`. + /// [reportData] with `reportKey`. [reportData] contains the extra information + /// of the test other than test success/fail. It will be passed back to the + /// host and be processed by the [ResponseDataCallback] defined in + /// [integrationDriver]. By default it will be written to + /// `build/integration_response_data.json` with the key `timeline`. /// /// For tests with multiple calls of this method, `reportKey` needs to be a /// unique key, otherwise the later result will override earlier one. /// - /// `streams` and `retainPriorEvents` are passed as-is to [traceTimeline]. + /// The `streams` and `retainPriorEvents` parameters are passed as-is to + /// [traceTimeline]. Future traceAction( Future action(), { List streams = const ['all'],