Unverified Commit 5d590afa authored by Andrew Davies's avatar Andrew Davies Committed by GitHub

[frdp] Adds DartVM events/driver support. (#17170)

This change adds Dart VM event support (listening for when a VM starts/stops by using a periodic heartbeat).

This also adds support to connect to a specific `IsolateRef` through the flutter driver, so that when an application spawns, it can immediately be driven (as shown in included example code).
parent 03173082
......@@ -62,6 +62,9 @@ const List<TimelineStream> _defaultStreams = const <TimelineStream>[TimelineStre
/// Default timeout for short-running RPCs.
const Duration _kShortTimeout = const Duration(seconds: 5);
/// Default timeout for awaiting an Isolate to become runnable.
const Duration _kIsolateLoadRunnableTimeout = const Duration(minutes: 1);
/// Default timeout for long-running RPCs.
final Duration _kLongTimeout = _kShortTimeout * 6;
......@@ -146,34 +149,46 @@ class FlutterDriver {
/// [logCommunicationToFile] determines whether the command communication
/// between the test and the app should be logged to `flutter_driver_commands.log`.
///
/// [isolateNumber] (optional) determines the specific isolate to connect to.
/// If this is left as `null`, will connect to the first isolate found
/// running on [dartVmServiceUrl].
///
/// [isolateReadyTimeout] determines how long after we connect to the VM
/// service we will wait for the first isolate to become runnable.
static Future<FlutterDriver> connect({
String dartVmServiceUrl,
bool printCommunication: false,
bool logCommunicationToFile: true,
Duration isolateReadyTimeout: const Duration(minutes: 1),
int isolateNumber,
Duration isolateReadyTimeout: _kIsolateLoadRunnableTimeout,
}) async {
dartVmServiceUrl ??= Platform.environment['VM_SERVICE_URL'];
if (dartVmServiceUrl == null) {
throw new DriverError(
'Could not determine URL to connect to application.\n'
'Either the VM_SERVICE_URL environment variable should be set, or an explicit\n'
'URL should be provided to the FlutterDriver.connect() method.'
);
'Could not determine URL to connect to application.\n'
'Either the VM_SERVICE_URL environment variable should be set, or an explicit\n'
'URL should be provided to the FlutterDriver.connect() method.');
}
// Connect to Dart VM services
_log.info('Connecting to Flutter application at $dartVmServiceUrl');
final VMServiceClientConnection connection = await vmServiceConnectFunction(dartVmServiceUrl);
final VMServiceClientConnection connection =
await vmServiceConnectFunction(dartVmServiceUrl);
final VMServiceClient client = connection.client;
final VM vm = await client.getVM();
_log.trace('Looking for the isolate');
VMIsolate isolate = await vm.isolates.first.loadRunnable()
final VMIsolateRef isolateRef = isolateNumber ==
null ? vm.isolates.first :
vm.isolates.firstWhere(
(VMIsolateRef isolate) => isolate.number == isolateNumber);
_log.trace('Isolate found with number: ${isolateRef.number}');
VMIsolate isolate = await isolateRef
.loadRunnable()
.timeout(isolateReadyTimeout, onTimeout: () {
throw new TimeoutException('Timeout while waiting for the isolate to become runnable');
});
throw new TimeoutException(
'Timeout while waiting for the isolate to become runnable');
});
// TODO(yjbanov): vm_service_client does not support "None" pause event yet.
// It is currently reported as null, but we cannot rely on it because
......@@ -189,7 +204,7 @@ class FlutterDriver {
isolate.pauseEvent is! VMPauseInterruptedEvent &&
isolate.pauseEvent is! VMResumeEvent) {
await new Future<Null>.delayed(_kShortTimeout ~/ 10);
isolate = await vm.isolates.first.loadRunnable();
isolate = await isolateRef.loadRunnable();
}
final FlutterDriver driver = new FlutterDriver.connectedTo(
......@@ -319,7 +334,7 @@ class FlutterDriver {
/// JSON-RPC client useful for sending raw JSON requests.
final rpc.Peer _peer;
/// The main isolate hosting the Flutter application
final VMIsolateRef _appIsolate;
final VMIsolate _appIsolate;
/// Whether to print communication between host and app to `stdout`.
final bool _printCommunication;
/// Whether to log communication between host and app to `flutter_driver_commands.log`.
......
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:async';
import 'dart:core';
import 'package:flutter_driver/flutter_driver.dart';
import 'package:fuchsia_remote_debug_protocol/fuchsia_remote_debug_protocol.dart';
import 'package:fuchsia_remote_debug_protocol/logging.dart';
/// Runs through a simple usage of the fuchsia_remote_debug_protocol library:
/// connects to a remote machine at the address in argument 1 (interface
/// optional for argument 2) to drive an application named 'todo_list' by
/// scrolling up and down on the main scaffold.
///
/// Make sure to set up your application (you can change the name from
/// 'todo_list') follows the setup for testing with the flutter driver:
/// https://flutter.io/testing/#adding-the-flutter_driver-dependency
///
/// Example usage:
///
/// $ dart examples/driver_todo_list_scroll.dart \
/// fe80::8eae:4cff:fef4:9247 eno1
Future<Null> main(List<String> args) async {
// Log only at info level within the library. If issues arise, this can be
// changed to [LoggingLevel.all] or [LoggingLevel.fine] to see more
// information.
Logger.globalLevel = LoggingLevel.info;
if (args.isEmpty) {
print('Expects an IP address and/or network interface');
return;
}
final String address = args[0];
final String interface = args.length > 1 ? args[1] : '';
// Example ssh config path for the Fuchsia device after having made a local
// build.
const String sshConfigPath =
'../../../fuchsia/out/x64rel/ssh-keys/ssh_config';
final FuchsiaRemoteConnection connection =
await FuchsiaRemoteConnection.connect(address, interface, sshConfigPath);
const Pattern isolatePattern = 'todo_list';
print('Finding $isolatePattern');
final List<IsolateRef> refs =
await connection.getMainIsolatesByPattern(isolatePattern);
final IsolateRef ref = refs.first;
print('Driving ${ref.name}');
final FlutterDriver driver = await FlutterDriver.connect(
dartVmServiceUrl: ref.dartVm.uri.toString(),
isolateNumber: ref.number,
printCommunication: true,
logCommunicationToFile: false);
for (int i = 0; i < 5; ++i) {
// Scrolls down 300px.
await driver.scroll(find.byType('Scaffold'), 0.0, -300.0,
const Duration(milliseconds: 300));
await new Future<Null>.delayed(const Duration(milliseconds: 500));
// Scrolls up 300px.
await driver.scroll(find.byType('Scaffold'), 300.0, 300.0,
const Duration(milliseconds: 300));
}
await driver.close();
await connection.stop();
}
......@@ -93,10 +93,13 @@ class RpcFormatError extends Error {
/// Either wraps existing RPC calls to the Dart VM service, or runs raw RPC
/// function calls via [invokeRpc].
class DartVm {
DartVm._(this._peer);
DartVm._(this._peer, this.uri);
final json_rpc.Peer _peer;
/// The URI through which this DartVM instance is connected.
final Uri uri;
/// Attempts to connect to the given [Uri].
///
/// Throws an error if unable to connect.
......@@ -108,7 +111,24 @@ class DartVm {
if (peer == null) {
return null;
}
return new DartVm._(peer);
return new DartVm._(peer, uri);
}
/// Returns a [List] of [IsolateRef] objects whose name matches `pattern`.
///
/// Also checks to make sure it was launched from the `main()` function.
Future<List<IsolateRef>> getMainIsolatesByPattern(Pattern pattern) async {
final Map<String, dynamic> jsonVmRef =
await invokeRpc('getVM', timeout: _kRpcTimeout);
final List<Map<String, dynamic>> jsonIsolates = jsonVmRef['isolates'];
final List<IsolateRef> result = <IsolateRef>[];
for (Map<String, dynamic> jsonIsolate in jsonIsolates) {
final String name = jsonIsolate['name'];
if (name.contains(pattern) && name.contains(new RegExp(r':main\(\)'))) {
result.add(new IsolateRef._fromJson(jsonIsolate, this));
}
}
return result;
}
/// Invokes a raw JSON RPC command with the VM service.
......@@ -119,7 +139,7 @@ class DartVm {
Future<Map<String, dynamic>> invokeRpc(
String function, {
Map<String, dynamic> params,
Duration timeout,
Duration timeout = _kRpcTimeout,
}) async {
final Future<Map<String, dynamic>> future = _peer.sendRequest(
function,
......@@ -208,3 +228,44 @@ class FlutterView {
/// May be null if there is no associated isolate.
String get name => _name;
}
/// This is a wrapper class for the `@Isolate` RPC object.
///
/// See:
/// https://github.com/dart-lang/sdk/blob/master/runtime/vm/service/service.md#isolate
///
/// This class contains information about the Isolate like its name and ID, as
/// well as a reference to the parent DartVM on which it is running.
class IsolateRef {
IsolateRef._(this.name, this.number, this.dartVm);
factory IsolateRef._fromJson(Map<String, dynamic> json, DartVm dartVm) {
final String number = json['number'];
final String name = json['name'];
final String type = json['type'];
if (type == null) {
throw new RpcFormatError('Unable to find type within JSON "$json"');
}
if (type != '@Isolate') {
throw new RpcFormatError('Type "$type" does not match for IsolateRef');
}
if (number == null) {
throw new RpcFormatError(
'Unable to find number for isolate ref within JSON "$json"');
}
if (name == null) {
throw new RpcFormatError(
'Unable to find name for isolate ref within JSON "$json"');
}
return new IsolateRef._(name, int.parse(number), dartVm);
}
/// The full name of this Isolate (not guaranteed to be unique).
final String name;
/// The unique number ID of this isolate.
final int number;
/// The parent [DartVm] on which this Isolate lives.
final DartVm dartVm;
}
......@@ -10,6 +10,8 @@ dependencies:
web_socket_channel: 1.0.7
flutter_test:
sdk: flutter
flutter_driver:
sdk: flutter
args: 1.4.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
async: 2.0.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment