Jonah Williams | 183fe75 | 2020-10-26 10:11:30 -0700 | [diff] [blame] | 1 | // Copyright 2014 The Flutter Authors. All rights reserved. |
| 2 | // Use of this source code is governed by a BSD-style license that can be |
| 3 | // found in the LICENSE file. |
| 4 | |
Jonah Williams | a3863b6 | 2021-05-17 17:39:03 -0700 | [diff] [blame] | 5 | import 'dart:async'; |
| 6 | |
Jonah Williams | 183fe75 | 2020-10-26 10:11:30 -0700 | [diff] [blame] | 7 | import 'package:dds/dds.dart' as dds; |
| 8 | import 'package:file/file.dart'; |
| 9 | import 'package:meta/meta.dart'; |
Jonah Williams | ed5d871 | 2020-10-28 17:32:05 -0700 | [diff] [blame] | 10 | import 'package:package_config/package_config_types.dart'; |
Jonah Williams | 183fe75 | 2020-10-26 10:11:30 -0700 | [diff] [blame] | 11 | import 'package:vm_service/vm_service.dart' as vm_service; |
| 12 | |
| 13 | import '../application_package.dart'; |
| 14 | import '../base/common.dart'; |
| 15 | import '../base/logger.dart'; |
| 16 | import '../base/process.dart'; |
| 17 | import '../build_info.dart'; |
| 18 | import '../device.dart'; |
Jonah Williams | a3863b6 | 2021-05-17 17:39:03 -0700 | [diff] [blame] | 19 | import '../resident_runner.dart'; |
Jonah Williams | 4ae68a3 | 2021-04-01 13:23:40 -0700 | [diff] [blame] | 20 | import '../sksl_writer.dart'; |
Jonah Williams | 183fe75 | 2020-10-26 10:11:30 -0700 | [diff] [blame] | 21 | import '../vmservice.dart'; |
| 22 | import 'web_driver_service.dart'; |
| 23 | |
| 24 | class FlutterDriverFactory { |
| 25 | FlutterDriverFactory({ |
Jonah Williams | 9203448 | 2022-06-15 13:02:07 -0700 | [diff] [blame] | 26 | required ApplicationPackageFactory applicationPackageFactory, |
| 27 | required Logger logger, |
| 28 | required ProcessUtils processUtils, |
| 29 | required String dartSdkPath, |
| 30 | required DevtoolsLauncher devtoolsLauncher, |
Jonah Williams | 183fe75 | 2020-10-26 10:11:30 -0700 | [diff] [blame] | 31 | }) : _applicationPackageFactory = applicationPackageFactory, |
| 32 | _logger = logger, |
| 33 | _processUtils = processUtils, |
Jonah Williams | a3863b6 | 2021-05-17 17:39:03 -0700 | [diff] [blame] | 34 | _dartSdkPath = dartSdkPath, |
| 35 | _devtoolsLauncher = devtoolsLauncher; |
Jonah Williams | 183fe75 | 2020-10-26 10:11:30 -0700 | [diff] [blame] | 36 | |
| 37 | final ApplicationPackageFactory _applicationPackageFactory; |
| 38 | final Logger _logger; |
| 39 | final ProcessUtils _processUtils; |
| 40 | final String _dartSdkPath; |
Jonah Williams | a3863b6 | 2021-05-17 17:39:03 -0700 | [diff] [blame] | 41 | final DevtoolsLauncher _devtoolsLauncher; |
Jonah Williams | 183fe75 | 2020-10-26 10:11:30 -0700 | [diff] [blame] | 42 | |
| 43 | /// Create a driver service for running `flutter drive`. |
| 44 | DriverService createDriverService(bool web) { |
| 45 | if (web) { |
| 46 | return WebDriverService( |
Yegor | 81c9192 | 2021-07-14 08:46:38 -0700 | [diff] [blame] | 47 | logger: _logger, |
Jonah Williams | 183fe75 | 2020-10-26 10:11:30 -0700 | [diff] [blame] | 48 | processUtils: _processUtils, |
| 49 | dartSdkPath: _dartSdkPath, |
| 50 | ); |
| 51 | } |
| 52 | return FlutterDriverService( |
| 53 | logger: _logger, |
| 54 | processUtils: _processUtils, |
| 55 | dartSdkPath: _dartSdkPath, |
| 56 | applicationPackageFactory: _applicationPackageFactory, |
Jonah Williams | a3863b6 | 2021-05-17 17:39:03 -0700 | [diff] [blame] | 57 | devtoolsLauncher: _devtoolsLauncher, |
Jonah Williams | 183fe75 | 2020-10-26 10:11:30 -0700 | [diff] [blame] | 58 | ); |
| 59 | } |
| 60 | } |
| 61 | |
| 62 | /// An interface for the `flutter driver` integration test operations. |
| 63 | abstract class DriverService { |
| 64 | /// Install and launch the application for the provided [device]. |
| 65 | Future<void> start( |
| 66 | BuildInfo buildInfo, |
| 67 | Device device, |
| 68 | DebuggingOptions debuggingOptions, |
| 69 | bool ipv6, { |
Jonah Williams | 9203448 | 2022-06-15 13:02:07 -0700 | [diff] [blame] | 70 | File? applicationBinary, |
| 71 | String? route, |
| 72 | String? userIdentifier, |
| 73 | String? mainPath, |
Jonah Williams | 183fe75 | 2020-10-26 10:11:30 -0700 | [diff] [blame] | 74 | Map<String, Object> platformArgs = const <String, Object>{}, |
| 75 | }); |
| 76 | |
Jonah Williams | d306c37 | 2020-10-28 13:55:29 -0700 | [diff] [blame] | 77 | /// If --use-existing-app is provided, configured the correct VM Service URI. |
| 78 | Future<void> reuseApplication( |
| 79 | Uri vmServiceUri, |
| 80 | Device device, |
| 81 | DebuggingOptions debuggingOptions, |
| 82 | bool ipv6, |
| 83 | ); |
| 84 | |
Jonah Williams | 183fe75 | 2020-10-26 10:11:30 -0700 | [diff] [blame] | 85 | /// Start the test file with the provided [arguments] and [environment], returning |
| 86 | /// the test process exit code. |
Jonah Williams | a3863b6 | 2021-05-17 17:39:03 -0700 | [diff] [blame] | 87 | /// |
| 88 | /// if [profileMemory] is provided, it will be treated as a file path to write a |
| 89 | /// devtools memory profile. |
Jonah Williams | 183fe75 | 2020-10-26 10:11:30 -0700 | [diff] [blame] | 90 | Future<int> startTest( |
| 91 | String testFile, |
| 92 | List<String> arguments, |
Jonah Williams | ed5d871 | 2020-10-28 17:32:05 -0700 | [diff] [blame] | 93 | Map<String, String> environment, |
| 94 | PackageConfig packageConfig, { |
Jonah Williams | 9203448 | 2022-06-15 13:02:07 -0700 | [diff] [blame] | 95 | bool? headless, |
| 96 | String? chromeBinary, |
| 97 | String? browserName, |
| 98 | bool? androidEmulator, |
| 99 | int? driverPort, |
Gustl22 | 47f54ac | 2022-06-24 19:14:08 +0200 | [diff] [blame] | 100 | List<String> webBrowserFlags, |
Jonah Williams | 9203448 | 2022-06-15 13:02:07 -0700 | [diff] [blame] | 101 | List<String>? browserDimension, |
| 102 | String? profileMemory, |
Jonah Williams | 183fe75 | 2020-10-26 10:11:30 -0700 | [diff] [blame] | 103 | }); |
| 104 | |
| 105 | /// Stop the running application and uninstall it from the device. |
| 106 | /// |
| 107 | /// If [writeSkslOnExit] is non-null, will connect to the VM Service |
| 108 | /// and write SkSL to the file. This is only supported on mobile and |
| 109 | /// desktop devices. |
| 110 | Future<void> stop({ |
Jonah Williams | 9203448 | 2022-06-15 13:02:07 -0700 | [diff] [blame] | 111 | File? writeSkslOnExit, |
| 112 | String? userIdentifier, |
Jonah Williams | 183fe75 | 2020-10-26 10:11:30 -0700 | [diff] [blame] | 113 | }); |
| 114 | } |
| 115 | |
| 116 | /// An implementation of the driver service that connects to mobile and desktop |
| 117 | /// applications. |
| 118 | class FlutterDriverService extends DriverService { |
| 119 | FlutterDriverService({ |
Jonah Williams | 9203448 | 2022-06-15 13:02:07 -0700 | [diff] [blame] | 120 | required ApplicationPackageFactory applicationPackageFactory, |
| 121 | required Logger logger, |
| 122 | required ProcessUtils processUtils, |
| 123 | required String dartSdkPath, |
| 124 | required DevtoolsLauncher devtoolsLauncher, |
Jonah Williams | 183fe75 | 2020-10-26 10:11:30 -0700 | [diff] [blame] | 125 | @visibleForTesting VMServiceConnector vmServiceConnector = connectToVmService, |
| 126 | }) : _applicationPackageFactory = applicationPackageFactory, |
| 127 | _logger = logger, |
| 128 | _processUtils = processUtils, |
| 129 | _dartSdkPath = dartSdkPath, |
Jonah Williams | a3863b6 | 2021-05-17 17:39:03 -0700 | [diff] [blame] | 130 | _vmServiceConnector = vmServiceConnector, |
| 131 | _devtoolsLauncher = devtoolsLauncher; |
Jonah Williams | 183fe75 | 2020-10-26 10:11:30 -0700 | [diff] [blame] | 132 | |
| 133 | static const int _kLaunchAttempts = 3; |
| 134 | |
| 135 | final ApplicationPackageFactory _applicationPackageFactory; |
| 136 | final Logger _logger; |
| 137 | final ProcessUtils _processUtils; |
| 138 | final String _dartSdkPath; |
| 139 | final VMServiceConnector _vmServiceConnector; |
Jonah Williams | a3863b6 | 2021-05-17 17:39:03 -0700 | [diff] [blame] | 140 | final DevtoolsLauncher _devtoolsLauncher; |
Jonah Williams | 183fe75 | 2020-10-26 10:11:30 -0700 | [diff] [blame] | 141 | |
Jonah Williams | 9203448 | 2022-06-15 13:02:07 -0700 | [diff] [blame] | 142 | Device? _device; |
| 143 | ApplicationPackage? _applicationPackage; |
| 144 | late String _vmServiceUri; |
| 145 | late FlutterVmService _vmService; |
Jonah Williams | 183fe75 | 2020-10-26 10:11:30 -0700 | [diff] [blame] | 146 | |
| 147 | @override |
| 148 | Future<void> start( |
| 149 | BuildInfo buildInfo, |
| 150 | Device device, |
| 151 | DebuggingOptions debuggingOptions, |
| 152 | bool ipv6, { |
Jonah Williams | 9203448 | 2022-06-15 13:02:07 -0700 | [diff] [blame] | 153 | File? applicationBinary, |
| 154 | String? route, |
| 155 | String? userIdentifier, |
Jonah Williams | 183fe75 | 2020-10-26 10:11:30 -0700 | [diff] [blame] | 156 | Map<String, Object> platformArgs = const <String, Object>{}, |
Jonah Williams | 9203448 | 2022-06-15 13:02:07 -0700 | [diff] [blame] | 157 | String? mainPath, |
Jonah Williams | 183fe75 | 2020-10-26 10:11:30 -0700 | [diff] [blame] | 158 | }) async { |
| 159 | if (buildInfo.isRelease) { |
| 160 | throwToolExit( |
| 161 | 'Flutter Driver (non-web) does not support running in release mode.\n' |
| 162 | '\n' |
| 163 | 'Use --profile mode for testing application performance.\n' |
| 164 | 'Use --debug (default) mode for testing correctness (with assertions).' |
| 165 | ); |
| 166 | } |
| 167 | _device = device; |
| 168 | final TargetPlatform targetPlatform = await device.targetPlatform; |
| 169 | _applicationPackage = await _applicationPackageFactory.getPackageForPlatform( |
| 170 | targetPlatform, |
| 171 | buildInfo: buildInfo, |
| 172 | applicationBinary: applicationBinary, |
| 173 | ); |
| 174 | int attempt = 0; |
Jonah Williams | 9203448 | 2022-06-15 13:02:07 -0700 | [diff] [blame] | 175 | LaunchResult? result; |
Jonah Williams | 183fe75 | 2020-10-26 10:11:30 -0700 | [diff] [blame] | 176 | bool prebuiltApplication = applicationBinary != null; |
| 177 | while (attempt < _kLaunchAttempts) { |
| 178 | result = await device.startApp( |
| 179 | _applicationPackage, |
| 180 | mainPath: mainPath, |
| 181 | route: route, |
| 182 | debuggingOptions: debuggingOptions, |
| 183 | platformArgs: platformArgs, |
| 184 | userIdentifier: userIdentifier, |
| 185 | prebuiltApplication: prebuiltApplication, |
| 186 | ); |
| 187 | if (result != null && result.started) { |
| 188 | break; |
| 189 | } |
| 190 | // On attempts past 1, assume the application is built correctly and re-use it. |
| 191 | attempt += 1; |
| 192 | prebuiltApplication = true; |
| 193 | _logger.printError('Application failed to start on attempt: $attempt'); |
| 194 | } |
| 195 | if (result == null || !result.started) { |
| 196 | throwToolExit('Application failed to start. Will not run test. Quitting.', exitCode: 1); |
| 197 | } |
Jonah Williams | d306c37 | 2020-10-28 13:55:29 -0700 | [diff] [blame] | 198 | return reuseApplication( |
Jonah Williams | 9203448 | 2022-06-15 13:02:07 -0700 | [diff] [blame] | 199 | result.observatoryUri!, |
Jonah Williams | d306c37 | 2020-10-28 13:55:29 -0700 | [diff] [blame] | 200 | device, |
| 201 | debuggingOptions, |
| 202 | ipv6, |
| 203 | ); |
| 204 | } |
| 205 | |
| 206 | @override |
| 207 | Future<void> reuseApplication( |
| 208 | Uri vmServiceUri, |
| 209 | Device device, |
| 210 | DebuggingOptions debuggingOptions, |
| 211 | bool ipv6, |
| 212 | ) async { |
Jonah Williams | 735f6ee | 2020-10-28 15:09:46 -0700 | [diff] [blame] | 213 | Uri uri; |
| 214 | if (vmServiceUri.scheme == 'ws') { |
Dan Field | 111c6ab | 2021-05-10 16:29:03 -0700 | [diff] [blame] | 215 | final List<String> segments = vmServiceUri.pathSegments.toList(); |
| 216 | segments.remove('ws'); |
| 217 | uri = vmServiceUri.replace(scheme: 'http', path: segments.join('/')); |
Jonah Williams | 735f6ee | 2020-10-28 15:09:46 -0700 | [diff] [blame] | 218 | } else { |
| 219 | uri = vmServiceUri; |
| 220 | } |
| 221 | _vmServiceUri = uri.toString(); |
Jonah Williams | d306c37 | 2020-10-28 13:55:29 -0700 | [diff] [blame] | 222 | _device = device; |
Dan Field | 3ce6c1f | 2021-05-03 15:15:10 -0700 | [diff] [blame] | 223 | if (debuggingOptions.enableDds) { |
| 224 | try { |
| 225 | await device.dds.startDartDevelopmentService( |
| 226 | uri, |
Jenn Magder | 2d55032 | 2021-08-17 21:12:03 -0700 | [diff] [blame] | 227 | hostPort: debuggingOptions.ddsPort, |
| 228 | ipv6: ipv6, |
| 229 | disableServiceAuthCodes: debuggingOptions.disableServiceAuthCodes, |
Dan Field | 3ce6c1f | 2021-05-03 15:15:10 -0700 | [diff] [blame] | 230 | logger: _logger, |
| 231 | ); |
| 232 | _vmServiceUri = device.dds.uri.toString(); |
| 233 | } on dds.DartDevelopmentServiceException { |
| 234 | // If there's another flutter_tools instance still connected to the target |
| 235 | // application, DDS will already be running remotely and this call will fail. |
| 236 | // This can be ignored to continue to use the existing remote DDS instance. |
| 237 | } |
Jonah Williams | 183fe75 | 2020-10-26 10:11:30 -0700 | [diff] [blame] | 238 | } |
Dan Field | f82794c | 2021-05-08 23:29:03 -0700 | [diff] [blame] | 239 | _vmService = await _vmServiceConnector(uri, device: _device, logger: _logger); |
Jonah Williams | 183fe75 | 2020-10-26 10:11:30 -0700 | [diff] [blame] | 240 | final DeviceLogReader logReader = await device.getLogReader(app: _applicationPackage); |
| 241 | logReader.logLines.listen(_logger.printStatus); |
| 242 | |
Jonah Williams | a3b14c5 | 2021-02-24 15:40:33 -0800 | [diff] [blame] | 243 | final vm_service.VM vm = await _vmService.service.getVM(); |
Jonah Williams | 183fe75 | 2020-10-26 10:11:30 -0700 | [diff] [blame] | 244 | logReader.appPid = vm.pid; |
| 245 | } |
| 246 | |
| 247 | @override |
| 248 | Future<int> startTest( |
| 249 | String testFile, |
| 250 | List<String> arguments, |
Jonah Williams | ed5d871 | 2020-10-28 17:32:05 -0700 | [diff] [blame] | 251 | Map<String, String> environment, |
| 252 | PackageConfig packageConfig, { |
Jonah Williams | 9203448 | 2022-06-15 13:02:07 -0700 | [diff] [blame] | 253 | bool? headless, |
| 254 | String? chromeBinary, |
| 255 | String? browserName, |
| 256 | bool? androidEmulator, |
| 257 | int? driverPort, |
Gustl22 | 47f54ac | 2022-06-24 19:14:08 +0200 | [diff] [blame] | 258 | List<String> webBrowserFlags = const <String>[], |
Jonah Williams | 9203448 | 2022-06-15 13:02:07 -0700 | [diff] [blame] | 259 | List<String>? browserDimension, |
| 260 | String? profileMemory, |
Jonah Williams | 183fe75 | 2020-10-26 10:11:30 -0700 | [diff] [blame] | 261 | }) async { |
Jonah Williams | a3863b6 | 2021-05-17 17:39:03 -0700 | [diff] [blame] | 262 | if (profileMemory != null) { |
| 263 | unawaited(_devtoolsLauncher.launch( |
| 264 | Uri.parse(_vmServiceUri), |
Jacob Richman | 1a0d873 | 2021-07-08 14:29:41 -0700 | [diff] [blame] | 265 | additionalArguments: <String>['--record-memory-profile=$profileMemory'], |
Jonah Williams | a3863b6 | 2021-05-17 17:39:03 -0700 | [diff] [blame] | 266 | )); |
| 267 | // When profiling memory the original launch future will never complete. |
| 268 | await _devtoolsLauncher.processStart; |
| 269 | } |
| 270 | try { |
| 271 | final int result = await _processUtils.stream(<String>[ |
| 272 | _dartSdkPath, |
| 273 | ...<String>[...arguments, testFile, '-rexpanded'], |
| 274 | ], environment: <String, String>{ |
| 275 | 'VM_SERVICE_URL': _vmServiceUri, |
| 276 | ...environment, |
| 277 | }); |
| 278 | return result; |
| 279 | } finally { |
| 280 | if (profileMemory != null) { |
| 281 | await _devtoolsLauncher.close(); |
| 282 | } |
| 283 | } |
Jonah Williams | 183fe75 | 2020-10-26 10:11:30 -0700 | [diff] [blame] | 284 | } |
| 285 | |
| 286 | @override |
| 287 | Future<void> stop({ |
Jonah Williams | 9203448 | 2022-06-15 13:02:07 -0700 | [diff] [blame] | 288 | File? writeSkslOnExit, |
| 289 | String? userIdentifier, |
Jonah Williams | 183fe75 | 2020-10-26 10:11:30 -0700 | [diff] [blame] | 290 | }) async { |
| 291 | if (writeSkslOnExit != null) { |
| 292 | final FlutterView flutterView = (await _vmService.getFlutterViews()).first; |
Jonah Williams | 428cafa | 2022-08-26 11:03:04 -0700 | [diff] [blame] | 293 | final Map<String, Object?>? result = await _vmService.getSkSLs( |
Jonah Williams | 183fe75 | 2020-10-26 10:11:30 -0700 | [diff] [blame] | 294 | viewId: flutterView.id |
Jonah Williams | 428cafa | 2022-08-26 11:03:04 -0700 | [diff] [blame] | 295 | ); |
Jonah Williams | 9203448 | 2022-06-15 13:02:07 -0700 | [diff] [blame] | 296 | await sharedSkSlWriter(_device!, result, outputFile: writeSkslOnExit, logger: _logger); |
Jonah Williams | 183fe75 | 2020-10-26 10:11:30 -0700 | [diff] [blame] | 297 | } |
Jonah Williams | d306c37 | 2020-10-28 13:55:29 -0700 | [diff] [blame] | 298 | // If the application package is available, stop and uninstall. |
| 299 | if (_applicationPackage != null) { |
Jonah Williams | 9203448 | 2022-06-15 13:02:07 -0700 | [diff] [blame] | 300 | if (!await _device!.stopApp(_applicationPackage, userIdentifier: userIdentifier)) { |
Jonah Williams | 183fe75 | 2020-10-26 10:11:30 -0700 | [diff] [blame] | 301 | _logger.printError('Failed to stop app'); |
| 302 | } |
Jonah Williams | 9203448 | 2022-06-15 13:02:07 -0700 | [diff] [blame] | 303 | if (!await _device!.uninstallApp(_applicationPackage!, userIdentifier: userIdentifier)) { |
Jonah Williams | d306c37 | 2020-10-28 13:55:29 -0700 | [diff] [blame] | 304 | _logger.printError('Failed to uninstall app'); |
Jonah Williams | 183fe75 | 2020-10-26 10:11:30 -0700 | [diff] [blame] | 305 | } |
Jonah Williams | 9203448 | 2022-06-15 13:02:07 -0700 | [diff] [blame] | 306 | } else if (_device!.supportsFlutterExit) { |
Jonah Williams | d306c37 | 2020-10-28 13:55:29 -0700 | [diff] [blame] | 307 | // Otherwise use the VM Service URI to stop the app as a best effort approach. |
Jonah Williams | a3b14c5 | 2021-02-24 15:40:33 -0800 | [diff] [blame] | 308 | final vm_service.VM vm = await _vmService.service.getVM(); |
Jonah Williams | 9203448 | 2022-06-15 13:02:07 -0700 | [diff] [blame] | 309 | final vm_service.IsolateRef isolateRef = vm.isolates! |
Jonah Williams | d306c37 | 2020-10-28 13:55:29 -0700 | [diff] [blame] | 310 | .firstWhere((vm_service.IsolateRef element) { |
Jonah Williams | 9203448 | 2022-06-15 13:02:07 -0700 | [diff] [blame] | 311 | return !element.isSystemIsolate!; |
| 312 | }); |
| 313 | unawaited(_vmService.flutterExit(isolateId: isolateRef.id!)); |
Jonah Williams | d306c37 | 2020-10-28 13:55:29 -0700 | [diff] [blame] | 314 | } else { |
| 315 | _logger.printTrace('No application package for $_device, leaving app running'); |
Jonah Williams | 183fe75 | 2020-10-26 10:11:30 -0700 | [diff] [blame] | 316 | } |
Jonah Williams | 9203448 | 2022-06-15 13:02:07 -0700 | [diff] [blame] | 317 | await _device!.dispose(); |
Jonah Williams | 183fe75 | 2020-10-26 10:11:30 -0700 | [diff] [blame] | 318 | } |
| 319 | } |