Skip to content

Commit

Permalink
fresh start: yeet custom service extension, use postEvent() instead
Browse files Browse the repository at this point in the history
  • Loading branch information
bartekpacia committed Nov 15, 2022
1 parent 9edb144 commit d5005c1
Show file tree
Hide file tree
Showing 2 changed files with 63 additions and 167 deletions.
194 changes: 58 additions & 136 deletions packages/patrol/lib/patrol_driver.dart
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
// ignore_for_file: avoid_print

import 'dart:async';
import 'dart:convert';
import 'dart:developer' as developer;
import 'dart:io' as io;
import 'dart:isolate';

import 'package:flutter_driver/flutter_driver.dart';
import 'package:integration_test/common.dart';
Expand All @@ -23,26 +20,7 @@ Future<void> patrolIntegrationDriver({
}) async {
final driver = await FlutterDriver.connect();

final deviceId = io.Platform.environment['DRIVER_DEVICE_ID'];
if (deviceId == null || deviceId.isEmpty) {
print('Error: DRIVER_DEVICE_ID is not set');
io.exit(1);
}
print('DRIVER_DEVICE_ID: $deviceId');

final deviceOs = io.Platform.environment['DRIVER_DEVICE_OS'];
if (deviceOs == null || deviceOs.isEmpty) {
print('Error: DRIVER_DEVICE_OS is not set');
io.exit(1);
}
print('DRIVER_DEVICE_OS: $deviceOs');

await _initCommunication(
vmService: driver.serviceClient,
appIsolateId: driver.appIsolate.id!,
deviceId: deviceId,
deviceOs: deviceOs,
);
await _initCommunication(driver.serviceClient);

final jsonResult = await driver.requestData(null, timeout: timeout);
final response = Response.fromJson(jsonResult);
Expand All @@ -61,124 +39,68 @@ Future<void> patrolIntegrationDriver({
}
}

/// Performs Patrol-specific setup to enable bidirectional communication between
/// this test driver file (running on host) and the test target file (running on
/// device).
Future<void> _initCommunication({
required vm.VmService vmService,
required String appIsolateId,
required String deviceId,
required String deviceOs,
}) async {
{
// Call extension
final info = await developer.Service.controlWebServer(enable: true);
final serverWsUri = info.serverWebSocketUri!;

// TODO(bartekpacia): What's the chance of host port being taken?
// TODO(bartekpacia): Improve error handling
if (deviceOs == 'android') {
io.Process.runSync(
'adb',
[
...['-s', deviceId],
...['reverse', 'tcp:${serverWsUri.port}', 'tcp:${serverWsUri.port}'],
],
runInShell: true,
);
} else if (deviceOs == 'ios') {
// FIXME: no way to do reverse port forwarding on real iOS devices
// final process = await io.Process.start('iproxy',
// [
// ...['--udid', deviceId],
// ...[serverWsUri.port.toString(), serverWsUri.port.toString()],
// ],
// runInShell: true,
// );
// process.stdout.listen( (event) => print('iproxy:
// ${io.systemEncoding.decode(event)}'),
// );
// process.stderr.listen( (event) => print('iproxy:
// ${io.systemEncoding.decode(event)}'),
// );
} else {
throw StateError('unknown device OS: $deviceOs');
Future<void> _initCommunication(vm.VmService vmService) async {
await vmService.streamListen('Extension');
vmService.onEvent('Extension').listen((event) {
if (event.extensionKind != 'patrol') {
return;
}

try {
await vmService.callServiceExtension(
'ext.flutter.patrol',
isolateId: appIsolateId,
args: <String, String>{
'DRIVER_ISOLATE_ID': developer.Service.getIsolateID(Isolate.current)!,
'DRIVER_VM_SERVICE_WS_URI': serverWsUri.toString(),
},
);
} on vm.RPCError catch (err) {
print(
'Calling service extension ext.flutter.patrol failed with code ${err.code} and message ${err.message}',
);

print('Exception: ${jsonDecode(err.details!)['exception']}');
io.exit(1);
final method = event.extensionData!.data['method'] as String;
switch (method) {
case 'take_screenshot':
_takeScreenshot(
event.extensionData!.data['args'] as Map<String, dynamic>,
);
break;
default:
throw StateError('unknown method $method');
}
});
}

developer.registerExtension(
'ext.leancode.patrol.status',
(method, args) async {
print('Here I am, the avenger of flakiness, called with $args');
return developer.ServiceExtensionResponse.result(
jsonEncode({'method': method, 'status': 'ok'}),
);
},
);

developer.registerExtension(
'ext.leancode.patrol.screenshot',
(method, parameters) async {
final screenshotName = parameters['name'];
if (screenshotName == null) {
return developer.ServiceExtensionResponse.error(
developer.ServiceExtensionResponse.invalidParams,
'screenshot name is null',
);
}

final screenshotPath = parameters['path'];
if (screenshotPath == null) {
return developer.ServiceExtensionResponse.error(
developer.ServiceExtensionResponse.invalidParams,
'screenshot path is null',
);
}

final screenshotDir = io.Directory(screenshotPath);
if (!screenshotDir.existsSync()) {
screenshotDir.createSync(recursive: true);
}

final proccessResult = await io.Process.run(
'flutter',
[
...['--device-id', deviceId],
'screenshot',
...['--out', join(screenshotPath, '$screenshotName.png')],
],
runInShell: true,
);
void _takeScreenshot(Map<String, dynamic> args) {
final deviceId = io.Platform.environment['DRIVER_DEVICE_ID'];
if (deviceId == null || deviceId.isEmpty) {
print('Error: DRIVER_DEVICE_ID is not set');
io.exit(1);
}
print('DRIVER_DEVICE_ID: $deviceId');

final deviceOs = io.Platform.environment['DRIVER_DEVICE_OS'];
if (deviceOs == null || deviceOs.isEmpty) {
print('Error: DRIVER_DEVICE_OS is not set');
io.exit(1);
}
print('DRIVER_DEVICE_OS: $deviceOs');

final exitCode = proccessResult.exitCode;
if (exitCode != 0) {
return developer.ServiceExtensionResponse.error(
developer.ServiceExtensionResponse.extensionError,
'flutter screenshot exited with exit code $exitCode',
);
}
final screenshotName = args['name'] as String?;
if (screenshotName == null) {
throw StateError('screenshot name is null');
}

return developer.ServiceExtensionResponse.result(
jsonEncode({'method': method}),
);
},
);
final screenshotPath = args['path'] as String?;
if (screenshotPath == null) {
throw StateError('screenshot path is null');
}

final screenshotDir = io.Directory(screenshotPath);
if (!screenshotDir.existsSync()) {
screenshotDir.createSync(recursive: true);
}

final proccessResult = io.Process.runSync(
'flutter',
[
...['--device-id', deviceId],
'screenshot',
...['--out', join(screenshotPath, '$screenshotName.png')],
],
runInShell: true,
);

final exitCode = proccessResult.exitCode;
if (exitCode != 0) {
throw StateError('flutter screenshot exited with code $exitCode');
}
}
36 changes: 5 additions & 31 deletions packages/patrol/lib/src/native/binding.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import 'package:flutter/foundation.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import 'package:vm_service/vm_service.dart' as vm;
import 'package:vm_service/vm_service_io.dart' as vmio;

// ignore: avoid_print
void _defaultPrintLogger(String message) => print('PatrolBinding: $message');
Expand Down Expand Up @@ -44,32 +43,8 @@ class PatrolBinding extends IntegrationTestWidgetsFlutterBinding {
_instance = this;
}

@override
void initServiceExtensions() {
super.initServiceExtensions();

if (!kReleaseMode) {
registerServiceExtension(
name: 'patrol',
callback: (args) async {
_logger('ext.flutter.patrol called');
driverIsolateId = args['DRIVER_ISOLATE_ID']!;
final driverVMServiceWsUri = args['DRIVER_VM_SERVICE_WS_URI']!;
_logger('driver isolate ID: $driverIsolateId');
_logger('driver VM service URI: $driverVMServiceWsUri');

vmService = await vmio.vmServiceConnectUri(driverVMServiceWsUri);

_logger('PatrolBinding: ext.flutter.patrol succeeded');
return <String, String>{'status': 'ok'};
},
);
_logger('registered service extension ext.flutter.patrol');
}
}

/// Sends [message] to the driver. Useful for debugging, otherwise useless.
Future<void> pingDriver(String message) async {
Future<void> ping(String message) async {
await vmService.callServiceExtension(
'ext.leancode.patrol.hello',
isolateId: driverIsolateId,
Expand All @@ -84,11 +59,10 @@ class PatrolBinding extends IntegrationTestWidgetsFlutterBinding {
required String name,
required String path,
}) async {
await vmService.callServiceExtension(
'ext.leancode.patrol.screenshot',
isolateId: driverIsolateId,
args: <String, String>{'name': name, 'path': path},
);
postEvent('patrol', <String, dynamic>{
'method': 'take_screenshot',
'args': {'name': name, 'path': path}
});
}

/// The singleton instance of this object.
Expand Down

0 comments on commit d5005c1

Please sign in to comment.