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/example/test_driver/example_integration_io.dart b/packages/integration_test/example/test_driver/example_integration_io.dart index 35fc7271d841..7ed28963c32b 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,29 @@ 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(); + // 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(); - // 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..f6980bc9d6d1 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,92 @@ 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 returns a [vm.Timeline] trace for it. + /// + /// Waits for the `Future` returned by [action] to complete prior to stopping + /// the trace. + /// + /// The `streams` parameter limits the recorded timeline event streams to only + /// the ones listed. By default, all streams are recorded. + /// See `timeline_streams` in + /// [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 + /// 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 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`. [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. + /// + /// The `streams` and `retainPriorEvents` parameters are passed as-is to + /// [traceTimeline]. + 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..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: @@ -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 {}