// 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 '../base/dds.dart'; import '../build_info.dart'; import '../device.dart'; import '../globals.dart' as globals; import '../vmservice.dart'; import 'test_device.dart'; const kIntegrationTestExtension = 'Flutter.IntegrationTest'; const kIntegrationTestData = 'data'; const 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; late final _ddsLauncher = DartDevelopmentService(logger: globals.logger); ApplicationPackage? _applicationPackage; final _finished = Completer(); final _gotProcessVmServiceUri = Completer(); /// Starts the device. /// /// [entrypointPath] must be a path to an un-compiled source file. @override Future> 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: {}, debuggingOptions: debuggingOptions, userIdentifier: userIdentifier, ); if (!launchResult.started) { throw TestDeviceException('Unable to start the app on the device.', StackTrace.current); } Uri? vmServiceUri = launchResult.vmServiceUri; if (vmServiceUri == null) { throw TestDeviceException( 'The VM Service 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. if (debuggingOptions.enableDds) { globals.printTrace('test $id: Starting Dart Development Service'); await _ddsLauncher.startDartDevelopmentServiceFromDebuggingOptions( vmServiceUri, debuggingOptions: debuggingOptions, ); globals.printTrace( 'test $id: Dart Development Service started at ${_ddsLauncher.uri}, forwarding to VM service at $vmServiceUri.', ); vmServiceUri = _ddsLauncher.uri; } _gotProcessVmServiceUri.complete(vmServiceUri); globals.printTrace('test $id: Connecting to vm service'); final FlutterVmService vmService = await connectToVmService( vmServiceUri!, 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 remoteMessages = vmService.service.onExtensionEvent .where((vm_service.Event e) => e.extensionKind == kIntegrationTestExtension) .map((vm_service.Event e) => e.extensionData!.data[kIntegrationTestData] as String); final controller = StreamChannelController(); controller.local.stream.listen((String event) { vmService.service.callServiceExtension( kIntegrationTestMethod, isolateId: isolateRef.id, args: {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 get vmServiceUri => _gotProcessVmServiceUri.future; @override Future 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(); _ddsLauncher.shutdown(); _finished.complete(); } @override Future get finished => _finished.future; }