| // Copyright 2014 The Flutter 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 'package:stream_channel/stream_channel.dart'; |
| import 'package:vm_service/vm_service.dart' as vm_service; |
| |
| import '../application_package.dart'; |
| import '../build_info.dart'; |
| import '../device.dart'; |
| import '../globals.dart' as globals; |
| import '../vmservice.dart'; |
| import 'test_device.dart'; |
| |
| const String kIntegrationTestExtension = 'Flutter.IntegrationTest'; |
| const String kIntegrationTestData = 'data'; |
| const String kIntegrationTestMethod = 'ext.flutter.integrationTest'; |
| |
| class IntegrationTestTestDevice implements TestDevice { |
| IntegrationTestTestDevice({ |
| required this.id, |
| required this.device, |
| required this.debuggingOptions, |
| required this.userIdentifier, |
| required this.compileExpression, |
| }); |
| |
| final int id; |
| final Device device; |
| final DebuggingOptions debuggingOptions; |
| final String? userIdentifier; |
| final CompileExpression? compileExpression; |
| |
| ApplicationPackage? _applicationPackage; |
| final Completer<void> _finished = Completer<void>(); |
| final Completer<Uri> _gotProcessObservatoryUri = Completer<Uri>(); |
| |
| /// Starts the device. |
| /// |
| /// [entrypointPath] must be a path to an un-compiled source file. |
| @override |
| Future<StreamChannel<String>> start(String entrypointPath) async { |
| final TargetPlatform targetPlatform = await device.targetPlatform; |
| _applicationPackage = await ApplicationPackageFactory.instance?.getPackageForPlatform( |
| targetPlatform, |
| buildInfo: debuggingOptions.buildInfo, |
| ); |
| final ApplicationPackage? package = _applicationPackage; |
| if (package == null) { |
| throw TestDeviceException('No application found for $targetPlatform.', StackTrace.current); |
| } |
| |
| final LaunchResult launchResult = await device.startApp( |
| package, |
| mainPath: entrypointPath, |
| platformArgs: <String, dynamic>{}, |
| debuggingOptions: debuggingOptions, |
| userIdentifier: userIdentifier, |
| ); |
| if (!launchResult.started) { |
| throw TestDeviceException('Unable to start the app on the device.', StackTrace.current); |
| } |
| final Uri? observatoryUri = launchResult.observatoryUri; |
| if (observatoryUri == null) { |
| throw TestDeviceException('Observatory is not available on the test device.', StackTrace.current); |
| } |
| |
| // No need to set up the log reader because the logs are captured and |
| // streamed to the package:test_core runner. |
| |
| _gotProcessObservatoryUri.complete(observatoryUri); |
| |
| globals.printTrace('test $id: Connecting to vm service'); |
| final FlutterVmService vmService = await connectToVmService( |
| observatoryUri, |
| logger: globals.logger, |
| compileExpression: compileExpression, |
| ).timeout( |
| const Duration(seconds: 5), |
| onTimeout: () => throw TimeoutException('Connecting to the VM Service timed out.'), |
| ); |
| |
| globals.printTrace('test $id: Finding the correct isolate with the integration test service extension'); |
| final vm_service.IsolateRef isolateRef = await vmService.findExtensionIsolate( |
| kIntegrationTestMethod, |
| ); |
| |
| await vmService.service.streamListen(vm_service.EventStreams.kExtension); |
| final Stream<String> remoteMessages = vmService.service.onExtensionEvent |
| .where((vm_service.Event e) => e.extensionKind == kIntegrationTestExtension) |
| .map((vm_service.Event e) => e.extensionData!.data[kIntegrationTestData] as String); |
| |
| final StreamChannelController<String> controller = StreamChannelController<String>(); |
| |
| controller.local.stream.listen((String event) { |
| vmService.service.callServiceExtension( |
| kIntegrationTestMethod, |
| isolateId: isolateRef.id, |
| args: <String, String>{ |
| kIntegrationTestData: event, |
| }, |
| ); |
| }); |
| |
| remoteMessages.listen( |
| (String s) => controller.local.sink.add(s), |
| onError: (Object error, StackTrace stack) => controller.local.sink.addError(error, stack), |
| ); |
| unawaited(vmService.service.onDone.whenComplete( |
| () => controller.local.sink.close(), |
| )); |
| |
| return controller.foreign; |
| } |
| |
| @override |
| Future<Uri> get observatoryUri => _gotProcessObservatoryUri.future; |
| |
| @override |
| Future<void> kill() async { |
| final ApplicationPackage? applicationPackage = _applicationPackage; |
| if (applicationPackage != null) { |
| if (!await device.stopApp(applicationPackage, userIdentifier: userIdentifier)) { |
| globals.printTrace('Could not stop the Integration Test app.'); |
| } |
| if (!await device.uninstallApp(applicationPackage, userIdentifier: userIdentifier)) { |
| globals.printTrace('Could not uninstall the Integration Test app.'); |
| } |
| } |
| |
| await device.dispose(); |
| _finished.complete(); |
| } |
| |
| @override |
| Future<void> get finished => _finished.future; |
| } |