[integration_test] add support to get timeline (#2947)
diff --git a/packages/integration_test/CHANGELOG.md b/packages/integration_test/CHANGELOG.md
index ee72930..d57819e 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 35fc727..7ed2896 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_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 430b7ee..f6980bc 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 @@
);
results[description] ??= _success;
}
+
+ vm.VmService _vmService;
+
+ /// Initialize the [vm.VmService] settings for the timeline.
+ @visibleForTesting
+ Future<void> enableTimeline({
+ List<String> streams = const <String>['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<vm.Timeline> traceTimeline(
+ Future<dynamic> action(), {
+ List<String> streams = const <String>['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<void> traceAction(
+ Future<dynamic> action(), {
+ List<String> streams = const <String>['all'],
+ bool retainPriorEvents = false,
+ String reportKey = 'timeline',
+ }) async {
+ vm.Timeline timeline = await traceTimeline(
+ action,
+ streams: streams,
+ retainPriorEvents: retainPriorEvents,
+ );
+ reportData ??= <String, dynamic>{};
+ reportData[reportKey] = timeline.toJson();
+ }
}
diff --git a/packages/integration_test/pubspec.yaml b/packages/integration_test/pubspec.yaml
index c151414..9dd4ade 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 @@
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 bad365a..ef4efc5 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: <vm.TimelineEvent>[],
+ timeOriginMicros: 100,
+ timeExtentMicros: 200,
+);
void main() async {
Future<Map<String, dynamic>> request;
@@ -14,10 +24,21 @@
final IntegrationTestWidgetsFlutterBinding integrationBinding =
binding as IntegrationTestWidgetsFlutterBinding;
+ MockVM mockVM;
+ List<int> clockTimes = [100, 200];
+
setUp(() {
request = integrationBinding.callback(<String, String>{
'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 @@
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 @@
assert(result.data['answer'] == 42);
});
}
+
+class MockVM extends Mock implements vm.VmService {}