diff --git a/packages/flutter_tools/lib/executable.dart b/packages/flutter_tools/lib/executable.dart index e55a7af7523..aeed10fd6a5 100644 --- a/packages/flutter_tools/lib/executable.dart +++ b/packages/flutter_tools/lib/executable.dart @@ -95,7 +95,10 @@ Future main(List args) async { DevicesCommand(), DoctorCommand(verbose: verbose), DowngradeCommand(), - DriveCommand(verboseHelp: verboseHelp), + DriveCommand(verboseHelp: verboseHelp, + fileSystem: globals.fs, + logger: globals.logger, + ), EmulatorsCommand(), FormatCommand(), GenerateCommand(), diff --git a/packages/flutter_tools/lib/src/android/android_device.dart b/packages/flutter_tools/lib/src/android/android_device.dart index 0f7ee7036e1..a47ef50dc9f 100644 --- a/packages/flutter_tools/lib/src/android/android_device.dart +++ b/packages/flutter_tools/lib/src/android/android_device.dart @@ -590,13 +590,17 @@ class AndroidDevice extends Device { } final bool traceStartup = platformArgs['trace-startup'] as bool ?? false; - _logger.printTrace('$this startApp'); - ProtocolDiscovery observatoryDiscovery; if (debuggingOptions.debuggingEnabled) { observatoryDiscovery = ProtocolDiscovery.observatory( - await getLogReader(), + // Avoid using getLogReader, which returns a singleton instance, because the + // observatory discovery will dipose at the end. creating a new logger here allows + // logs to be surfaced normally during `flutter drive`. + await AdbLogReader.createLogReader( + this, + _processManager, + ), portForwarder: portForwarder, hostPort: debuggingOptions.hostVmServicePort, devicePort: debuggingOptions.deviceVmServicePort, @@ -669,8 +673,6 @@ class AndroidDevice extends Device { // Wait for the service protocol port here. This will complete once the // device has printed "Observatory is listening on...". _logger.printTrace('Waiting for observatory port to be available...'); - - // TODO(danrubel): Waiting for observatory services can be made common across all devices. try { Uri observatoryUri; if (debuggingOptions.buildInfo.isDebug || debuggingOptions.buildInfo.isProfile) { diff --git a/packages/flutter_tools/lib/src/build_system/targets/web.dart b/packages/flutter_tools/lib/src/build_system/targets/web.dart index 17db0721c83..1f306b7aaea 100644 --- a/packages/flutter_tools/lib/src/build_system/targets/web.dart +++ b/packages/flutter_tools/lib/src/build_system/targets/web.dart @@ -22,9 +22,6 @@ import 'assets.dart'; import 'common.dart'; import 'localizations.dart'; -/// Whether web builds should call the platform initialization logic. -const String kInitializePlatform = 'InitializePlatform'; - /// Whether the application has web plugins. const String kHasWebPlugins = 'HasWebPlugins'; @@ -89,7 +86,6 @@ class WebEntrypointTarget extends Target { @override Future build(Environment environment) async { final String targetFile = environment.defines[kTargetFile]; - final bool shouldInitializePlatform = environment.defines[kInitializePlatform] == 'true'; final bool hasPlugins = environment.defines[kHasWebPlugins] == 'true'; final Uri importUri = environment.fileSystem.file(targetFile).absolute.uri; // TODO(jonahwilliams): support configuration of this file. @@ -137,9 +133,7 @@ import '$mainImport' as entrypoint; Future main() async { registerPlugins(webPluginRegistry); - if ($shouldInitializePlatform) { - await ui.webOnlyInitializePlatform(); - } + await ui.webOnlyInitializePlatform(); entrypoint.main(); } '''; @@ -152,9 +146,7 @@ import 'dart:ui' as ui; import '$mainImport' as entrypoint; Future main() async { - if ($shouldInitializePlatform) { - await ui.webOnlyInitializePlatform(); - } + await ui.webOnlyInitializePlatform(); entrypoint.main(); } '''; diff --git a/packages/flutter_tools/lib/src/commands/attach.dart b/packages/flutter_tools/lib/src/commands/attach.dart index 853f42131c6..db5f5927382 100644 --- a/packages/flutter_tools/lib/src/commands/attach.dart +++ b/packages/flutter_tools/lib/src/commands/attach.dart @@ -384,7 +384,6 @@ known, it can be explicitly provided to attach via the command-line, e.g. final FlutterDevice flutterDevice = await FlutterDevice.create( device, - flutterProject: flutterProject, fileSystemRoots: stringsArg('filesystem-root'), fileSystemScheme: stringArg('filesystem-scheme'), target: stringArg('target'), diff --git a/packages/flutter_tools/lib/src/commands/build_web.dart b/packages/flutter_tools/lib/src/commands/build_web.dart index 0e4e59c000f..d5b1f74cab9 100644 --- a/packages/flutter_tools/lib/src/commands/build_web.dart +++ b/packages/flutter_tools/lib/src/commands/build_web.dart @@ -25,12 +25,6 @@ class BuildWebCommand extends BuildSubCommand { usesDartDefineOption(); addEnableExperimentation(hide: !verboseHelp); addNullSafetyModeOptions(hide: !verboseHelp); - argParser.addFlag('web-initialize-platform', - defaultsTo: true, - negatable: true, - hide: true, - help: 'Whether to automatically invoke webOnlyInitializePlatform.', - ); argParser.addFlag('csp', defaultsTo: false, negatable: false, @@ -92,7 +86,6 @@ class BuildWebCommand extends BuildSubCommand { flutterProject, target, buildInfo, - boolArg('web-initialize-platform'), boolArg('csp'), stringArg('pwa-strategy'), boolArg('source-maps') diff --git a/packages/flutter_tools/lib/src/commands/daemon.dart b/packages/flutter_tools/lib/src/commands/daemon.dart index bf069016ad3..a7bdda01a97 100644 --- a/packages/flutter_tools/lib/src/commands/daemon.dart +++ b/packages/flutter_tools/lib/src/commands/daemon.dart @@ -466,7 +466,6 @@ class AppDomain extends Domain { final FlutterDevice flutterDevice = await FlutterDevice.create( device, - flutterProject: flutterProject, target: target, buildInfo: options.buildInfo, platform: globals.platform, diff --git a/packages/flutter_tools/lib/src/commands/drive.dart b/packages/flutter_tools/lib/src/commands/drive.dart index d966bacd354..f436fffd82c 100644 --- a/packages/flutter_tools/lib/src/commands/drive.dart +++ b/packages/flutter_tools/lib/src/commands/drive.dart @@ -3,29 +3,21 @@ // found in the LICENSE file. import 'dart:async'; -import 'dart:math' as math; -import 'package:dds/dds.dart' as dds; -import 'package:vm_service/vm_service_io.dart' as vm_service; -import 'package:vm_service/vm_service.dart' as vm_service; import 'package:meta/meta.dart'; -import 'package:webdriver/async_io.dart' as async_io; import '../android/android_device.dart'; import '../application_package.dart'; import '../artifacts.dart'; import '../base/common.dart'; import '../base/file_system.dart'; +import '../base/logger.dart'; import '../build_info.dart'; -import '../convert.dart'; -import '../dart/package_map.dart'; import '../device.dart'; +import '../drive/drive_service.dart'; import '../globals.dart' as globals; -import '../project.dart'; -import '../resident_runner.dart'; import '../runner/flutter_command.dart' show FlutterCommandResult, FlutterOptions; -import '../vmservice.dart'; -import '../web/web_runner.dart'; +import '../web/web_device.dart'; import 'run.dart'; /// Runs integration (a.k.a. end-to-end) tests. @@ -51,7 +43,12 @@ import 'run.dart'; class DriveCommand extends RunCommandBase { DriveCommand({ bool verboseHelp = false, - }) { + @visibleForTesting FlutterDriverFactory flutterDriverFactory, + @required FileSystem fileSystem, + @required Logger logger, + }) : _flutterDriverFactory = flutterDriverFactory, + _fileSystem = fileSystem, + _logger = logger { requiresPubspecYaml(); addEnableExperimentation(hide: !verboseHelp); @@ -109,7 +106,7 @@ class DriveCommand extends RunCommandBase { 'firefox', 'ios-safari', 'safari', - ] + ], ) ..addOption('browser-dimension', defaultsTo: '1600,1024', @@ -127,10 +124,15 @@ class DriveCommand extends RunCommandBase { ..addOption('write-sksl-on-exit', help: 'Attempts to write an SkSL file when the drive process is finished ' - 'to the provided file, overwriting it if necessary.', - ); + 'to the provided file, overwriting it if necessary.') + ..addMultiOption('test-arguments', help: 'Additional arguments to pass to the ' + 'Dart VM running The test script.'); } + FlutterDriverFactory _flutterDriverFactory; + final FileSystem _fileSystem; + final Logger _logger; + @override final String name = 'drive'; @@ -140,20 +142,15 @@ class DriveCommand extends RunCommandBase { @override final List aliases = ['driver']; - Device _device; - Device get device => _device; - - bool get verboseSystemLogs => boolArg('verbose-system-logs'); String get userIdentifier => stringArg(FlutterOptions.kDeviceUser); - /// Subscription to log messages printed on the device or simulator. - // ignore: cancel_subscriptions - StreamSubscription _deviceLogSubscription; + @override + bool get startPausedDefault => true; @override Future validateCommand() async { if (userIdentifier != null) { - final Device device = await findTargetDevice(timeout: deviceDiscoveryTimeout); + final Device device = await findTargetDevice(); if (device is! AndroidDevice) { throwToolExit('--${FlutterOptions.kDeviceUser} is only supported for Android'); } @@ -167,229 +164,70 @@ class DriveCommand extends RunCommandBase { if (testFile == null) { throwToolExit(null); } - - _device = await findTargetDevice(timeout: deviceDiscoveryTimeout); + if (await _fileSystem.type(testFile) != FileSystemEntityType.file) { + throwToolExit('Test file not found: $testFile'); + } + final Device device = await findTargetDevice(includeUnsupportedDevices: stringArg('use-application-binary') == null); if (device == null) { throwToolExit(null); } - if (await globals.fs.type(testFile) != FileSystemEntityType.file) { - throwToolExit('Test file not found: $testFile'); - } - - String observatoryUri; - ResidentRunner residentRunner; + final bool web = device is WebServerDevice || device is ChromiumDevice; + _flutterDriverFactory ??= FlutterDriverFactory( + applicationPackageFactory: ApplicationPackageFactory.instance, + logger: _logger, + processUtils: globals.processUtils, + dartSdkPath: globals.artifacts.getArtifactPath(Artifact.engineDartBinary), + ); + final DriverService driverService = _flutterDriverFactory.createDriverService(web); final BuildInfo buildInfo = getBuildInfo(); - final bool isWebPlatform = await device.targetPlatform == TargetPlatform.web_javascript; + final DebuggingOptions debuggingOptions = createDebuggingOptions(); final File applicationBinary = stringArg('use-application-binary') == null ? null - : globals.fs.file(stringArg('use-application-binary')); - final ApplicationPackage package = await applicationPackages.getPackageForPlatform( - await device.targetPlatform, - buildInfo: buildInfo, + : _fileSystem.file(stringArg('use-application-binary')); + + await driverService.start( + buildInfo, + device, + debuggingOptions, + ipv6, applicationBinary: applicationBinary, + route: route, + userIdentifier: userIdentifier, + mainPath: targetFile, + platformArgs: { + if (traceStartup) + 'trace-startup': traceStartup, + if (web) + '--no-launch-chrome': true, + } ); - if (argResults['use-existing-app'] == null) { - globals.printStatus('Starting application: $targetFile'); - if (buildInfo.isRelease && !isWebPlatform) { - // This is because we need VM service to be able to drive the app. - // For Flutter Web, testing in release mode is allowed. - throwToolExit( - 'Flutter Driver (non-web) does not support running in release mode.\n' - '\n' - 'Use --profile mode for testing application performance.\n' - 'Use --debug (default) mode for testing correctness (with assertions).' - ); - } + final int testResult = await driverService.startTest( + testFile, + stringsArg('test-arguments'), + {}, + chromeBinary: stringArg('chrome-binary'), + headless: boolArg('headless'), + browserDimension: stringArg('browser-dimension').split(','), + browserName: stringArg('browser-name'), + driverPort: stringArg('driver-port') != null + ? int.tryParse(stringArg('driver-port')) + : null, + androidEmulator: boolArg('android-emulator'), + ); - Uri webUri; - - if (isWebPlatform) { - // Start Flutter web application for current test - final FlutterProject flutterProject = FlutterProject.current(); - final FlutterDevice flutterDevice = await FlutterDevice.create( - device, - flutterProject: flutterProject, - target: targetFile, - buildInfo: buildInfo, - platform: globals.platform, - ); - residentRunner = webRunnerFactory.createWebRunner( - flutterDevice, - target: targetFile, - flutterProject: flutterProject, - ipv6: ipv6, - debuggingOptions: getBuildInfo().isRelease ? - DebuggingOptions.disabled( - getBuildInfo(), - port: stringArg('web-port') - ) - : DebuggingOptions.enabled( - getBuildInfo(), - port: stringArg('web-port'), - disablePortPublication: disablePortPublication, - ), - stayResident: false, - urlTunneller: null, - ); - final Completer appStartedCompleter = Completer.sync(); - final int result = await residentRunner.run( - appStartedCompleter: appStartedCompleter, - route: route, - ); - if (result != 0) { - throwToolExit(null, exitCode: result); - } - // Wait until the app is started. - await appStartedCompleter.future; - webUri = residentRunner.uri; - } - - // Attempt to launch the application up to 3 times, to validate whether it - // is possible to reduce flakiness by hardnening the launch code. - int attempt = 0; - LaunchResult result; - while (attempt < 3) { - // On attempts past 1, assume the application is built correctly and re-use it. - result = await appStarter(this, webUri, package, applicationBinary != null || attempt > 0); - if (result != null) { - break; - } - attempt += 1; - globals.printError('Application failed to start on attempt: $attempt'); - } - if (result == null) { - throwToolExit('Application failed to start. Will not run test. Quitting.', exitCode: 1); - } - observatoryUri = result.observatoryUri.toString(); - // TODO(bkonyi): add web support (https://github.com/flutter/flutter/issues/61259) - if (!isWebPlatform && !disableDds) { - try { - // If there's another flutter_tools instance still connected to the target - // application, DDS will already be running remotely and this call will fail. - // We can ignore this and continue to use the remote DDS instance. - await device.dds.startDartDevelopmentService( - Uri.parse(observatoryUri), - ddsPort, - ipv6, - disableServiceAuthCodes, - ); - observatoryUri = device.dds.uri.toString(); - } on dds.DartDevelopmentServiceException catch(_) { - globals.printTrace('Note: DDS is already connected to $observatoryUri.'); - } - } + if (boolArg('keep-app-running') ?? (argResults['use-existing-app'] != null)) { + _logger.printStatus('Leaving the application running.'); } else { - globals.printStatus('Will connect to already running application instance.'); - observatoryUri = stringArg('use-existing-app'); + final File skslFile = stringArg('write-sksl-on-exit') != null + ? _fileSystem.file(stringArg('write-sksl-on-exit')) + : null; + await driverService.stop(userIdentifier: userIdentifier, writeSkslOnExit: skslFile); } - - final Map environment = { - 'VM_SERVICE_URL': observatoryUri, - }; - - async_io.WebDriver driver; - // For web device, WebDriver session will be launched beforehand - // so that FlutterDriver can reuse it. - if (isWebPlatform) { - final Browser browser = _browserNameToEnum( - argResults['browser-name'].toString()); - final String driverPort = argResults['driver-port'].toString(); - // start WebDriver - try { - driver = await _createDriver( - driverPort, - browser, - argResults['headless'].toString() == 'true', - stringArg('chrome-binary'), - ); - } on Exception catch (ex) { - throwToolExit( - 'Unable to start WebDriver Session for Flutter for Web testing. \n' - 'Make sure you have the correct WebDriver Server running at $driverPort. \n' - 'Make sure the WebDriver Server matches option --browser-name. \n' - '$ex' - ); - } - - final bool isAndroidChrome = browser == Browser.androidChrome; - final bool useEmulator = argResults['android-emulator'] as bool; - // set window size - // for android chrome, skip such action - if (!isAndroidChrome) { - final List dimensions = argResults['browser-dimension'].split( - ',') as List; - assert(dimensions.length == 2); - int x, y; - try { - x = int.parse(dimensions[0]); - y = int.parse(dimensions[1]); - } on FormatException catch (ex) { - throwToolExit(''' -Dimension provided to --browser-dimension is invalid: -$ex - '''); - } - final async_io.Window window = await driver.window; - await window.setLocation(const math.Point(0, 0)); - await window.setSize(math.Rectangle(0, 0, x, y)); - } - - // add driver info to environment variables - environment.addAll( { - 'DRIVER_SESSION_ID': driver.id, - 'DRIVER_SESSION_URI': driver.uri.toString(), - 'DRIVER_SESSION_SPEC': driver.spec.toString(), - 'DRIVER_SESSION_CAPABILITIES': json.encode(driver.capabilities), - 'SUPPORT_TIMELINE_ACTION': (browser == Browser.chrome).toString(), - 'FLUTTER_WEB_TEST': 'true', - 'ANDROID_CHROME_ON_EMULATOR': (isAndroidChrome && useEmulator).toString(), - }); + if (testResult != 0) { + return FlutterCommandResult.fail(); } - - try { - await testRunner( - [ - if (buildInfo.dartExperiments.isNotEmpty) - '--enable-experiment=${buildInfo.dartExperiments.join(',')}', - if (buildInfo.nullSafetyMode == NullSafetyMode.sound) - '--sound-null-safety', - if (buildInfo.nullSafetyMode == NullSafetyMode.unsound) - '--no-sound-null-safety', - testFile, - ], - environment, - ); - } on Exception catch (error, stackTrace) { - if (error is ToolExit) { - rethrow; - } - throw Exception('Unable to run test: $error\n$stackTrace'); - } finally { - await residentRunner?.exit(); - await driver?.quit(); - if (stringArg('write-sksl-on-exit') != null) { - final File outputFile = globals.fs.file(stringArg('write-sksl-on-exit')); - final vm_service.VmService vmService = await connectToVmService( - Uri.parse(observatoryUri), - ); - final FlutterView flutterView = (await vmService.getFlutterViews()).first; - final Map result = await vmService.getSkSLs( - viewId: flutterView.id - ); - await sharedSkSlWriter(_device, result, outputFile: outputFile); - } - if (boolArg('keep-app-running') ?? (argResults['use-existing-app'] != null)) { - globals.printStatus('Leaving the application running.'); - } else { - globals.printStatus('Stopping application instance.'); - await appStopper(this, package); - } - - await device?.dispose(); - } - return FlutterCommandResult.success(); } @@ -400,28 +238,28 @@ $ex // If the --driver argument wasn't provided, then derive the value from // the target file. - String appFile = globals.fs.path.normalize(targetFile); + String appFile = _fileSystem.path.normalize(targetFile); // This command extends `flutter run` and therefore CWD == package dir - final String packageDir = globals.fs.currentDirectory.path; + final String packageDir = _fileSystem.currentDirectory.path; // Make appFile path relative to package directory because we are looking // for the corresponding test file relative to it. - if (!globals.fs.path.isRelative(appFile)) { - if (!globals.fs.path.isWithin(packageDir, appFile)) { - globals.printError( + if (!_fileSystem.path.isRelative(appFile)) { + if (!_fileSystem.path.isWithin(packageDir, appFile)) { + _logger.printError( 'Application file $appFile is outside the package directory $packageDir' ); return null; } - appFile = globals.fs.path.relative(appFile, from: packageDir); + appFile = _fileSystem.path.relative(appFile, from: packageDir); } - final List parts = globals.fs.path.split(appFile); + final List parts = _fileSystem.path.split(appFile); if (parts.length < 2) { - globals.printError( + _logger.printError( 'Application file $appFile must reside in one of the sub-directories ' 'of the package structure, not in the root directory.' ); @@ -431,277 +269,8 @@ $ex // Look for the test file inside `test_driver/` matching the sub-path, e.g. // if the application is `lib/foo/bar.dart`, the test file is expected to // be `test_driver/foo/bar_test.dart`. - final String pathWithNoExtension = globals.fs.path.withoutExtension(globals.fs.path.joinAll( + final String pathWithNoExtension = _fileSystem.path.withoutExtension(_fileSystem.path.joinAll( [packageDir, 'test_driver', ...parts.skip(1)])); - return '${pathWithNoExtension}_test${globals.fs.path.extension(appFile)}'; - } -} - -Future findTargetDevice({ @required Duration timeout }) async { - final DeviceManager deviceManager = globals.deviceManager; - final List devices = await deviceManager.findTargetDevices(null, timeout: timeout); - - if (deviceManager.hasSpecifiedDeviceId) { - if (devices.isEmpty) { - globals.printStatus("No devices found with name or id matching '${deviceManager.specifiedDeviceId}'"); - return null; - } - if (devices.length > 1) { - globals.printStatus("Found ${devices.length} devices with name or id matching '${deviceManager.specifiedDeviceId}':"); - await Device.printDevices(devices, globals.logger); - return null; - } - return devices.first; - } - - if (devices.isEmpty) { - globals.printError('No devices found.'); - return null; - } else if (devices.length > 1) { - globals.printStatus('Found multiple connected devices:'); - await Device.printDevices(devices, globals.logger); - } - globals.printStatus('Using device ${devices.first.name}.'); - return devices.first; -} - -/// Starts the application on the device given command configuration. -typedef AppStarter = Future Function(DriveCommand command, Uri webUri, ApplicationPackage applicationPackage, bool prebuiltApplication); - -AppStarter appStarter = _startApp; // (mutable for testing) -void restoreAppStarter() { - appStarter = _startApp; -} - -Future _startApp( - DriveCommand command, - Uri webUri, - ApplicationPackage applicationPackage, - bool prebuiltApplication, -) async { - final String mainPath = findMainDartFile(command.targetFile); - if (await globals.fs.type(mainPath) != FileSystemEntityType.file) { - globals.printError('Tried to run $mainPath, but that file does not exist.'); - return null; - } - - globals.printTrace('Stopping previously running application, if any.'); - await appStopper(command, applicationPackage); - - - final Map platformArgs = {}; - if (command.traceStartup) { - platformArgs['trace-startup'] = command.traceStartup; - } - - if (webUri != null) { - platformArgs['uri'] = webUri.toString(); - if (!command.getBuildInfo().isDebug) { - // For web device, startApp will be triggered twice - // and it will error out for chrome the second time. - platformArgs['no-launch-chrome'] = true; - } - } - - globals.printTrace('Starting application.'); - - // Forward device log messages to the terminal window running the "drive" command. - final DeviceLogReader logReader = await command.device.getLogReader(app: applicationPackage); - command._deviceLogSubscription = logReader - .logLines - .listen(globals.printStatus); - - final LaunchResult result = await command.device.startApp( - applicationPackage, - mainPath: mainPath, - route: command.route, - debuggingOptions: DebuggingOptions.enabled( - command.getBuildInfo(), - startPaused: true, - hostVmServicePort: webUri != null ? command.hostVmservicePort : 0, - disablePortPublication: command.disablePortPublication, - ddsPort: command.ddsPort, - verboseSystemLogs: command.verboseSystemLogs, - cacheSkSL: command.cacheSkSL, - dumpSkpOnShaderCompilation: command.dumpSkpOnShaderCompilation, - purgePersistentCache: command.purgePersistentCache, - ), - platformArgs: platformArgs, - userIdentifier: command.userIdentifier, - prebuiltApplication: prebuiltApplication, - ); - - if (!result.started) { - await command._deviceLogSubscription.cancel(); - return null; - } - - return result; -} - -/// Runs driver tests. -typedef TestRunner = Future Function(List testArgs, Map environment); -TestRunner testRunner = _runTests; -void restoreTestRunner() { - testRunner = _runTests; -} - -Future _runTests(List testArgs, Map environment) async { - globals.printTrace('Running driver tests.'); - - globalPackagesPath = globals.fs.path.normalize(globals.fs.path.absolute(globalPackagesPath)); - final int result = await globals.processUtils.stream( - [ - globals.artifacts.getArtifactPath(Artifact.engineDartBinary), - ...testArgs, - '--packages=$globalPackagesPath', - '-rexpanded', - ], - environment: environment, - ); - if (result != 0) { - throwToolExit('Driver tests failed: $result', exitCode: result); - } -} - - -/// Stops the application. -typedef AppStopper = Future Function(DriveCommand command, ApplicationPackage applicationPackage); -AppStopper appStopper = _stopApp; -void restoreAppStopper() { - appStopper = _stopApp; -} - -Future _stopApp(DriveCommand command, ApplicationPackage package) async { - globals.printTrace('Stopping application.'); - final bool stopped = await command.device.stopApp(package, userIdentifier: command.userIdentifier); - await command.device.uninstallApp(package); - await command._deviceLogSubscription?.cancel(); - return stopped; -} - -/// A list of supported browsers. -@visibleForTesting -enum Browser { - /// Chrome on Android: https://developer.chrome.com/multidevice/android/overview - androidChrome, - /// Chrome: https://www.google.com/chrome/ - chrome, - /// Edge: https://www.microsoft.com/en-us/windows/microsoft-edge - edge, - /// Firefox: https://www.mozilla.org/en-US/firefox/ - firefox, - /// Safari in iOS: https://www.apple.com/safari/ - iosSafari, - /// Safari in macOS: https://www.apple.com/safari/ - safari, -} - -/// Converts [browserName] string to [Browser] -Browser _browserNameToEnum(String browserName){ - switch (browserName) { - case 'android-chrome': return Browser.androidChrome; - case 'chrome': return Browser.chrome; - case 'edge': return Browser.edge; - case 'firefox': return Browser.firefox; - case 'ios-safari': return Browser.iosSafari; - case 'safari': return Browser.safari; - } - throw UnsupportedError('Browser $browserName not supported'); -} - -Future _createDriver(String driverPort, Browser browser, bool headless, String chromeBinary) async { - return async_io.createDriver( - uri: Uri.parse('http://localhost:$driverPort/'), - desired: getDesiredCapabilities(browser, headless, chromeBinary), - spec: async_io.WebDriverSpec.Auto - ); -} - -/// Returns desired capabilities for given [browser], [headless] and -/// [chromeBinary]. -@visibleForTesting -Map getDesiredCapabilities(Browser browser, bool headless, [String chromeBinary]) { - switch (browser) { - case Browser.chrome: - return { - 'acceptInsecureCerts': true, - 'browserName': 'chrome', - 'goog:loggingPrefs': { async_io.LogType.performance: 'ALL'}, - 'chromeOptions': { - if (chromeBinary != null) - 'binary': chromeBinary, - 'w3c': false, - 'args': [ - '--bwsi', - '--disable-background-timer-throttling', - '--disable-default-apps', - '--disable-extensions', - '--disable-popup-blocking', - '--disable-translate', - '--no-default-browser-check', - '--no-sandbox', - '--no-first-run', - if (headless) '--headless' - ], - 'perfLoggingPrefs': { - 'traceCategories': - 'devtools.timeline,' - 'v8,blink.console,benchmark,blink,' - 'blink.user_timing' - } - }, - }; - break; - case Browser.firefox: - return { - 'acceptInsecureCerts': true, - 'browserName': 'firefox', - 'moz:firefoxOptions' : { - 'args': [ - if (headless) '-headless' - ], - 'prefs': { - 'dom.file.createInChild': true, - 'dom.timeout.background_throttling_max_budget': -1, - 'media.autoplay.default': 0, - 'media.gmp-manager.url': '', - 'media.gmp-provider.enabled': false, - 'network.captive-portal-service.enabled': false, - 'security.insecure_field_warning.contextual.enabled': false, - 'test.currentTimeOffsetSeconds': 11491200 - }, - 'log': {'level': 'trace'} - } - }; - break; - case Browser.edge: - return { - 'acceptInsecureCerts': true, - 'browserName': 'edge', - }; - break; - case Browser.safari: - return { - 'browserName': 'safari', - }; - break; - case Browser.iosSafari: - return { - 'platformName': 'ios', - 'browserName': 'safari', - 'safari:useSimulator': true - }; - case Browser.androidChrome: - return { - 'browserName': 'chrome', - 'platformName': 'android', - 'goog:chromeOptions': { - 'androidPackage': 'com.android.chrome', - 'args': ['--disable-fullscreen'] - }, - }; - default: - throw UnsupportedError('Browser $browser not supported.'); + return '${pathWithNoExtension}_test${_fileSystem.path.extension(appFile)}'; } } diff --git a/packages/flutter_tools/lib/src/commands/run.dart b/packages/flutter_tools/lib/src/commands/run.dart index fb0a27c5875..4e93282f0ed 100644 --- a/packages/flutter_tools/lib/src/commands/run.dart +++ b/packages/flutter_tools/lib/src/commands/run.dart @@ -4,8 +4,6 @@ import 'dart:async'; -import 'package:args/command_runner.dart'; - import '../android/android_device.dart'; import '../base/common.dart'; import '../base/file_system.dart'; @@ -25,8 +23,8 @@ import '../tracing.dart'; import '../web/web_runner.dart'; import 'daemon.dart'; +/// Shared logic between `flutter run` and `flutter drive` commands. abstract class RunCommandBase extends FlutterCommand with DeviceBasedDevelopmentArtifacts { - // Used by run and drive commands. RunCommandBase({ bool verboseHelp = false }) { addBuildModeFlags(defaultToRelease: false, verboseHelp: verboseHelp); usesDartDefineOption(); @@ -77,6 +75,45 @@ abstract class RunCommandBase extends FlutterCommand with DeviceBasedDevelopment 'this must be the path to an APK. For iOS applications, the path to an IPA. Other device types ' 'do not yet support prebuilt application binaries', valueHelp: 'path/to/app.apk', + ) + ..addFlag('start-paused', + defaultsTo: startPausedDefault, + help: 'Start in a paused mode and wait for a debugger to connect.', + ) + ..addOption('dart-flags', + hide: !verboseHelp, + help: 'Pass a list of comma separated flags to the Dart instance at ' + 'application startup. Flags passed through this option must be ' + 'present on the allowlist defined within the Flutter engine. If ' + 'a disallowed flag is encountered, the process will be ' + 'terminated immediately.\n\n' + 'This flag is not available on the stable channel and is only ' + 'applied in debug and profile modes. This option should only ' + 'be used for experiments and should not be used by typical users.' + ) + ..addFlag('endless-trace-buffer', + negatable: false, + help: 'Enable tracing to the endless tracer. This is useful when ' + 'recording huge amounts of traces. If we need to use endless buffer to ' + 'record startup traces, we can combine the ("--trace-startup"). ' + 'For example, flutter run --trace-startup --endless-trace-buffer. ', + ) + ..addFlag('trace-systrace', + negatable: false, + help: 'Enable tracing to the system tracer. This is only useful on ' + 'platforms where such a tracer is available (Android and Fuchsia).', + ) + ..addFlag('trace-skia', + negatable: false, + help: 'Enable tracing of Skia code. This is useful when debugging ' + 'the raster thread (formerly known as the GPU thread). ' + 'By default, Flutter will not log skia code.', + ) + ..addOption('trace-allowlist', + hide: true, + help: 'Filters out all trace events except those that are specified in ' + 'this comma separated list of allowed prefixes.', + valueHelp: 'foo,bar', ); usesWebOptions(hide: !verboseHelp); usesTargetOption(); @@ -96,7 +133,71 @@ abstract class RunCommandBase extends FlutterCommand with DeviceBasedDevelopment bool get dumpSkpOnShaderCompilation => boolArg('dump-skp-on-shader-compilation'); bool get purgePersistentCache => boolArg('purge-persistent-cache'); bool get disableServiceAuthCodes => boolArg('disable-service-auth-codes'); + bool get runningWithPrebuiltApplication => argResults['use-application-binary'] != null; + bool get trackWidgetCreation => boolArg('track-widget-creation'); + + /// Whether to start the application paused by default. + bool get startPausedDefault; + String get route => stringArg('route'); + + String get traceAllowlist => stringArg('trace-allowlist'); + + /// Create a debugging options instance for the current `run` or `drive` invocation. + DebuggingOptions createDebuggingOptions() { + final BuildInfo buildInfo = getBuildInfo(); + final int browserDebugPort = featureFlags.isWebEnabled && argResults.wasParsed('web-browser-debug-port') + ? int.parse(stringArg('web-browser-debug-port')) + : null; + if (buildInfo.mode.isRelease) { + return DebuggingOptions.disabled( + buildInfo, + hostname: featureFlags.isWebEnabled ? stringArg('web-hostname') : '', + port: featureFlags.isWebEnabled ? stringArg('web-port') : '', + webUseSseForDebugProxy: featureFlags.isWebEnabled && stringArg('web-server-debug-protocol') == 'sse', + webUseSseForDebugBackend: featureFlags.isWebEnabled && stringArg('web-server-debug-backend-protocol') == 'sse', + webEnableExposeUrl: featureFlags.isWebEnabled && boolArg('web-allow-expose-url'), + webRunHeadless: featureFlags.isWebEnabled && boolArg('web-run-headless'), + webBrowserDebugPort: browserDebugPort, + ); + } else { + return DebuggingOptions.enabled( + buildInfo, + startPaused: boolArg('start-paused'), + disableServiceAuthCodes: boolArg('disable-service-auth-codes'), + disableDds: boolArg('disable-dds'), + dartFlags: stringArg('dart-flags') ?? '', + useTestFonts: argParser.options.containsKey('use-test-fonts') && boolArg('use-test-fonts'), + enableSoftwareRendering: argParser.options.containsKey('enable-software-rendering') && boolArg('enable-software-rendering'), + skiaDeterministicRendering: argParser.options.containsKey('skia-deterministic-rendering') && boolArg('skia-deterministic-rendering'), + traceSkia: boolArg('trace-skia'), + traceAllowlist: traceAllowlist, + traceSystrace: boolArg('trace-systrace'), + endlessTraceBuffer: boolArg('endless-trace-buffer'), + dumpSkpOnShaderCompilation: dumpSkpOnShaderCompilation, + cacheSkSL: cacheSkSL, + purgePersistentCache: purgePersistentCache, + deviceVmServicePort: deviceVmservicePort, + hostVmServicePort: hostVmservicePort, + disablePortPublication: disablePortPublication, + ddsPort: ddsPort, + verboseSystemLogs: boolArg('verbose-system-logs'), + hostname: featureFlags.isWebEnabled ? stringArg('web-hostname') : '', + port: featureFlags.isWebEnabled ? stringArg('web-port') : '', + webUseSseForDebugProxy: featureFlags.isWebEnabled && stringArg('web-server-debug-protocol') == 'sse', + webUseSseForDebugBackend: featureFlags.isWebEnabled && stringArg('web-server-debug-backend-protocol') == 'sse', + webEnableExposeUrl: featureFlags.isWebEnabled && boolArg('web-allow-expose-url'), + webRunHeadless: featureFlags.isWebEnabled && boolArg('web-run-headless'), + webBrowserDebugPort: browserDebugPort, + webEnableExpressionEvaluation: featureFlags.isWebEnabled && boolArg('web-enable-expression-evaluation'), + vmserviceOutFile: stringArg('vmservice-out-file'), + fastStart: argParser.options.containsKey('fast-start') + && boolArg('fast-start') + && !runningWithPrebuiltApplication, + nullAssertions: boolArg('null-assertions'), + ); + } + } } class RunCommand extends RunCommandBase { @@ -111,10 +212,6 @@ class RunCommand extends RunCommandBase { // without needing to know the port. addPublishPort(enabledByDefault: true, verboseHelp: verboseHelp); argParser - ..addFlag('start-paused', - negatable: false, - help: 'Start in a paused mode and wait for a debugger to connect.', - ) ..addFlag('enable-software-rendering', negatable: false, help: 'Enable rendering using the Skia software backend. ' @@ -127,35 +224,6 @@ class RunCommand extends RunCommandBase { help: 'When combined with --enable-software-rendering, provides 100% ' 'deterministic Skia rendering.', ) - ..addFlag('trace-skia', - negatable: false, - help: 'Enable tracing of Skia code. This is useful when debugging ' - 'the raster thread (formerly known as the GPU thread). ' - 'By default, Flutter will not log skia code.', - ) - ..addOption('trace-whitelist', - hide: true, - help: '(deprecated) Use --trace-allowlist instead', - valueHelp: 'foo,bar', - ) - ..addOption('trace-allowlist', - hide: true, - help: 'Filters out all trace events except those that are specified in ' - 'this comma separated list of allowed prefixes.', - valueHelp: 'foo,bar', - ) - ..addFlag('endless-trace-buffer', - negatable: false, - help: 'Enable tracing to the endless tracer. This is useful when ' - 'recording huge amounts of traces. If we need to use endless buffer to ' - 'record startup traces, we can combine the ("--trace-startup"). ' - 'For example, flutter run --trace-startup --endless-trace-buffer. ', - ) - ..addFlag('trace-systrace', - negatable: false, - help: 'Enable tracing to the system tracer. This is only useful on ' - 'platforms where such a tracer is available (Android and Fuchsia).', - ) ..addFlag('await-first-frame-when-tracing', defaultsTo: true, help: 'Whether to wait for the first frame when tracing startup ("--trace-startup"), ' @@ -176,16 +244,6 @@ class RunCommand extends RunCommandBase { defaultsTo: true, help: 'If necessary, build the app before running.', ) - ..addOption('dart-flags', - hide: !verboseHelp, - help: 'Pass a list of comma separated flags to the Dart instance at ' - 'application startup. Flags passed through this option must be ' - 'present on the allowlist defined within the Flutter engine. If ' - 'a disallowed flag is encountered, the process will be ' - 'terminated immediately.\n\n' - 'This flag is not available on the stable channel and is only ' - 'applied in debug and profile modes. This option should only ' - 'be used for experiments and should not be used by typical users.') ..addOption('project-root', hide: !verboseHelp, help: 'Specify the project root directory.', @@ -248,6 +306,9 @@ class RunCommand extends RunCommandBase { String get userIdentifier => stringArg(FlutterOptions.kDeviceUser); + @override + bool get startPausedDefault => false; + @override Future get usagePath async { final String command = await super.usagePath; @@ -342,9 +403,6 @@ class RunCommand extends RunCommandBase { return getBuildInfo().isDebug && shouldUseHotMode; } - bool get runningWithPrebuiltApplication => - argResults['use-application-binary'] != null; - bool get stayResident => boolArg('resident'); bool get awaitFirstFrameWhenTracing => boolArg('await-first-frame-when-tracing'); @@ -372,73 +430,6 @@ class RunCommand extends RunCommandBase { } } - String get _traceAllowlist { - final String deprecatedValue = stringArg('trace-whitelist'); - if (deprecatedValue != null) { - globals.printError('--trace-whitelist has been deprecated, use --trace-allowlist instead'); - } - return stringArg('trace-allowlist') ?? deprecatedValue; - } - - DebuggingOptions _createDebuggingOptions() { - final BuildInfo buildInfo = getBuildInfo(); - final int browserDebugPort = featureFlags.isWebEnabled && argResults.wasParsed('web-browser-debug-port') - ? int.parse(stringArg('web-browser-debug-port')) - : null; - if (buildInfo.mode.isRelease) { - return DebuggingOptions.disabled( - buildInfo, - initializePlatform: boolArg('web-initialize-platform'), - hostname: featureFlags.isWebEnabled ? stringArg('web-hostname') : '', - port: featureFlags.isWebEnabled ? stringArg('web-port') : '', - webUseSseForDebugProxy: featureFlags.isWebEnabled && stringArg('web-server-debug-protocol') == 'sse', - webUseSseForDebugBackend: featureFlags.isWebEnabled && stringArg('web-server-debug-backend-protocol') == 'sse', - webEnableExposeUrl: featureFlags.isWebEnabled && boolArg('web-allow-expose-url'), - webRunHeadless: featureFlags.isWebEnabled && boolArg('web-run-headless'), - webBrowserDebugPort: browserDebugPort, - ); - } else { - return DebuggingOptions.enabled( - buildInfo, - startPaused: boolArg('start-paused'), - disableServiceAuthCodes: boolArg('disable-service-auth-codes'), - disableDds: boolArg('disable-dds'), - dartFlags: stringArg('dart-flags') ?? '', - useTestFonts: boolArg('use-test-fonts'), - enableSoftwareRendering: boolArg('enable-software-rendering'), - skiaDeterministicRendering: boolArg('skia-deterministic-rendering'), - traceSkia: boolArg('trace-skia'), - traceAllowlist: _traceAllowlist, - traceSystrace: boolArg('trace-systrace'), - endlessTraceBuffer: boolArg('endless-trace-buffer'), - dumpSkpOnShaderCompilation: dumpSkpOnShaderCompilation, - cacheSkSL: cacheSkSL, - purgePersistentCache: purgePersistentCache, - deviceVmServicePort: deviceVmservicePort, - hostVmServicePort: hostVmservicePort, - disablePortPublication: disablePortPublication, - ddsPort: ddsPort, - verboseSystemLogs: boolArg('verbose-system-logs'), - initializePlatform: boolArg('web-initialize-platform'), - hostname: featureFlags.isWebEnabled ? stringArg('web-hostname') : '', - port: featureFlags.isWebEnabled ? stringArg('web-port') : '', - webUseSseForDebugProxy: featureFlags.isWebEnabled && stringArg('web-server-debug-protocol') == 'sse', - webUseSseForDebugBackend: featureFlags.isWebEnabled && stringArg('web-server-debug-backend-protocol') == 'sse', - webEnableExposeUrl: featureFlags.isWebEnabled && boolArg('web-allow-expose-url'), - webRunHeadless: featureFlags.isWebEnabled && boolArg('web-run-headless'), - webBrowserDebugPort: browserDebugPort, - webEnableExpressionEvaluation: featureFlags.isWebEnabled && boolArg('web-enable-expression-evaluation'), - vmserviceOutFile: stringArg('vmservice-out-file'), - // Allow forcing fast-start to off to prevent doing more work on devices that - // don't support it. - fastStart: boolArg('fast-start') - && !runningWithPrebuiltApplication - && devices.every((Device device) => device.supportsFastStart), - nullAssertions: boolArg('null-assertions'), - ); - } - } - @override Future runCommand() async { // Enable hot mode by default if `--no-hot` was not passed and we are in @@ -464,11 +455,11 @@ class RunCommand extends RunCommandBase { final String applicationBinaryPath = stringArg('use-application-binary'); app = await daemon.appDomain.startApp( devices.first, globals.fs.currentDirectory.path, targetFile, route, - _createDebuggingOptions(), hotMode, + createDebuggingOptions(), hotMode, applicationBinary: applicationBinaryPath == null ? null : globals.fs.file(applicationBinaryPath), - trackWidgetCreation: boolArg('track-widget-creation'), + trackWidgetCreation: trackWidgetCreation, projectRootPath: stringArg('project-root'), packagesFilePath: globalResults['packages'] as String, dillOutputPath: stringArg('output-dill'), @@ -491,11 +482,6 @@ class RunCommand extends RunCommandBase { } globals.terminal.usesTerminalUi = true; - if (argResults['dart-flags'] != null && !globals.flutterVersion.isMaster) { - throw UsageException('--dart-flags is not available on the stable ' - 'channel.', null); - } - final BuildMode buildMode = getBuildMode(); for (final Device device in devices) { if (!await device.supportsRuntimeMode(buildMode)) { @@ -534,7 +520,6 @@ class RunCommand extends RunCommandBase { for (final Device device in devices) await FlutterDevice.create( device, - flutterProject: flutterProject, fileSystemRoots: stringsArg('filesystem-root'), fileSystemScheme: stringArg('filesystem-scheme'), experimentalFlags: expFlags, @@ -556,7 +541,7 @@ class RunCommand extends RunCommandBase { runner = HotRunner( flutterDevices, target: targetFile, - debuggingOptions: _createDebuggingOptions(), + debuggingOptions: createDebuggingOptions(), benchmarkMode: boolArg('benchmark'), applicationBinary: applicationBinaryPath == null ? null @@ -572,7 +557,7 @@ class RunCommand extends RunCommandBase { target: targetFile, flutterProject: flutterProject, ipv6: ipv6, - debuggingOptions: _createDebuggingOptions(), + debuggingOptions: createDebuggingOptions(), stayResident: stayResident, urlTunneller: null, ); @@ -580,7 +565,7 @@ class RunCommand extends RunCommandBase { runner = ColdRunner( flutterDevices, target: targetFile, - debuggingOptions: _createDebuggingOptions(), + debuggingOptions: createDebuggingOptions(), traceStartup: traceStartup, awaitFirstFrameWhenTracing: awaitFirstFrameWhenTracing, applicationBinary: applicationBinaryPath == null diff --git a/packages/flutter_tools/lib/src/device.dart b/packages/flutter_tools/lib/src/device.dart index c76b9a1e353..5fffc870747 100644 --- a/packages/flutter_tools/lib/src/device.dart +++ b/packages/flutter_tools/lib/src/device.dart @@ -847,7 +847,6 @@ class DebuggingOptions { this.disablePortPublication = false, this.deviceVmServicePort, this.ddsPort, - this.initializePlatform = true, this.hostname, this.port, this.webEnableExposeUrl, @@ -862,7 +861,6 @@ class DebuggingOptions { }) : debuggingEnabled = true; DebuggingOptions.disabled(this.buildInfo, { - this.initializePlatform = true, this.port, this.hostname, this.webEnableExposeUrl, @@ -913,8 +911,6 @@ class DebuggingOptions { final bool purgePersistentCache; final bool useTestFonts; final bool verboseSystemLogs; - /// Whether to invoke webOnlyInitializePlatform in Flutter for web. - final bool initializePlatform; final int hostVmServicePort; final int deviceVmServicePort; final bool disablePortPublication; diff --git a/packages/flutter_tools/lib/src/drive/drive_service.dart b/packages/flutter_tools/lib/src/drive/drive_service.dart new file mode 100644 index 00000000000..f985df32d46 --- /dev/null +++ b/packages/flutter_tools/lib/src/drive/drive_service.dart @@ -0,0 +1,245 @@ +// 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 'package:dds/dds.dart' as dds; +import 'package:file/file.dart'; +import 'package:meta/meta.dart'; +import 'package:vm_service/vm_service.dart' as vm_service; + +import '../application_package.dart'; +import '../base/common.dart'; +import '../base/logger.dart'; +import '../base/process.dart'; +import '../build_info.dart'; +import '../device.dart'; +import '../vmservice.dart'; +import 'web_driver_service.dart'; + +class FlutterDriverFactory { + FlutterDriverFactory({ + @required ApplicationPackageFactory applicationPackageFactory, + @required Logger logger, + @required ProcessUtils processUtils, + @required String dartSdkPath, + }) : _applicationPackageFactory = applicationPackageFactory, + _logger = logger, + _processUtils = processUtils, + _dartSdkPath = dartSdkPath; + + final ApplicationPackageFactory _applicationPackageFactory; + final Logger _logger; + final ProcessUtils _processUtils; + final String _dartSdkPath; + + /// Create a driver service for running `flutter drive`. + DriverService createDriverService(bool web) { + if (web) { + return WebDriverService( + processUtils: _processUtils, + dartSdkPath: _dartSdkPath, + ); + } + return FlutterDriverService( + logger: _logger, + processUtils: _processUtils, + dartSdkPath: _dartSdkPath, + applicationPackageFactory: _applicationPackageFactory, + ); + } +} + +/// An interface for the `flutter driver` integration test operations. +abstract class DriverService { + /// Install and launch the application for the provided [device]. + Future start( + BuildInfo buildInfo, + Device device, + DebuggingOptions debuggingOptions, + bool ipv6, { + File applicationBinary, + String route, + String userIdentifier, + String mainPath, + Map platformArgs = const {}, + }); + + /// Start the test file with the provided [arguments] and [environment], returning + /// the test process exit code. + Future startTest( + String testFile, + List arguments, + Map environment, { + bool headless, + String chromeBinary, + String browserName, + bool androidEmulator, + int driverPort, + List browserDimension, + }); + + /// Stop the running application and uninstall it from the device. + /// + /// If [writeSkslOnExit] is non-null, will connect to the VM Service + /// and write SkSL to the file. This is only supported on mobile and + /// desktop devices. + Future stop({ + File writeSkslOnExit, + String userIdentifier, + }); +} + +/// An implementation of the driver service that connects to mobile and desktop +/// applications. +class FlutterDriverService extends DriverService { + FlutterDriverService({ + @required ApplicationPackageFactory applicationPackageFactory, + @required Logger logger, + @required ProcessUtils processUtils, + @required String dartSdkPath, + @visibleForTesting VMServiceConnector vmServiceConnector = connectToVmService, + }) : _applicationPackageFactory = applicationPackageFactory, + _logger = logger, + _processUtils = processUtils, + _dartSdkPath = dartSdkPath, + _vmServiceConnector = vmServiceConnector; + + static const int _kLaunchAttempts = 3; + + final ApplicationPackageFactory _applicationPackageFactory; + final Logger _logger; + final ProcessUtils _processUtils; + final String _dartSdkPath; + final VMServiceConnector _vmServiceConnector; + + Device _device; + ApplicationPackage _applicationPackage; + String _vmServiceUri; + vm_service.VmService _vmService; + + @override + Future start( + BuildInfo buildInfo, + Device device, + DebuggingOptions debuggingOptions, + bool ipv6, { + File applicationBinary, + String route, + String userIdentifier, + Map platformArgs = const {}, + String mainPath, + }) async { + if (buildInfo.isRelease) { + throwToolExit( + 'Flutter Driver (non-web) does not support running in release mode.\n' + '\n' + 'Use --profile mode for testing application performance.\n' + 'Use --debug (default) mode for testing correctness (with assertions).' + ); + } + _device = device; + final TargetPlatform targetPlatform = await device.targetPlatform; + _applicationPackage = await _applicationPackageFactory.getPackageForPlatform( + targetPlatform, + buildInfo: buildInfo, + applicationBinary: applicationBinary, + ); + int attempt = 0; + LaunchResult result; + bool prebuiltApplication = applicationBinary != null; + while (attempt < _kLaunchAttempts) { + result = await device.startApp( + _applicationPackage, + mainPath: mainPath, + route: route, + debuggingOptions: debuggingOptions, + platformArgs: platformArgs, + userIdentifier: userIdentifier, + prebuiltApplication: prebuiltApplication, + ); + if (result != null && result.started) { + break; + } + // On attempts past 1, assume the application is built correctly and re-use it. + attempt += 1; + prebuiltApplication = true; + _logger.printError('Application failed to start on attempt: $attempt'); + } + if (result == null || !result.started) { + throwToolExit('Application failed to start. Will not run test. Quitting.', exitCode: 1); + } + _vmServiceUri = result.observatoryUri.toString(); + try { + await device.dds.startDartDevelopmentService( + result.observatoryUri, + debuggingOptions.ddsPort, + ipv6, + debuggingOptions.disableServiceAuthCodes, + ); + _vmServiceUri = device.dds.uri.toString(); + } on dds.DartDevelopmentServiceException { + // If there's another flutter_tools instance still connected to the target + // application, DDS will already be running remotely and this call will fail. + // This can be ignored to continue to use the existing remote DDS instance. + } + _vmService = await _vmServiceConnector(Uri.parse(_vmServiceUri), device: _device); + final DeviceLogReader logReader = await device.getLogReader(app: _applicationPackage); + logReader.logLines.listen(_logger.printStatus); + + final vm_service.VM vm = await _vmService.getVM(); + logReader.appPid = vm.pid; + } + + @override + Future startTest( + String testFile, + List arguments, + Map environment, { + bool headless, + String chromeBinary, + String browserName, + bool androidEmulator, + int driverPort, + List browserDimension, + }) async { + return _processUtils.stream([ + _dartSdkPath, + ...arguments, + testFile, + '-rexpanded', + ], environment: { + 'VM_SERVICE_URL': _vmServiceUri, + ...environment, + }); + } + + @override + Future stop({ + File writeSkslOnExit, + String userIdentifier, + }) async { + if (writeSkslOnExit != null) { + final FlutterView flutterView = (await _vmService.getFlutterViews()).first; + final Map result = await _vmService.getSkSLs( + viewId: flutterView.id + ); + await sharedSkSlWriter(_device, result, outputFile: writeSkslOnExit, logger: _logger); + } + try { + if (!await _device.stopApp(_applicationPackage, userIdentifier: userIdentifier)) { + _logger.printError('Failed to stop app'); + } + } on Exception catch (err) { + _logger.printError('Failed to stop app due to unhandled error: $err'); + } + + try { + if (!await _device.uninstallApp(_applicationPackage, userIdentifier: userIdentifier)) { + _logger.printError('Failed to uninstall app'); + } + } on Exception catch (err) { + _logger.printError('Failed to uninstall app due to unhandled error: $err'); + } + await _device.dispose(); + } +} diff --git a/packages/flutter_tools/lib/src/drive/web_driver_service.dart b/packages/flutter_tools/lib/src/drive/web_driver_service.dart new file mode 100644 index 00000000000..5e1e0bde370 --- /dev/null +++ b/packages/flutter_tools/lib/src/drive/web_driver_service.dart @@ -0,0 +1,264 @@ +// 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 'dart:math' as math; + +import 'package:file/file.dart'; +import 'package:meta/meta.dart'; +import 'package:webdriver/async_io.dart' as async_io; + +import '../base/common.dart'; +import '../base/process.dart'; +import '../build_info.dart'; +import '../convert.dart'; +import '../device.dart'; +import '../globals.dart' as globals; +import '../project.dart'; +import '../resident_runner.dart'; +import '../web/web_runner.dart'; +import 'drive_service.dart'; + +/// An implementation of the driver service for web debug and release applications. +class WebDriverService extends DriverService { + WebDriverService({ + @required ProcessUtils processUtils, + @required String dartSdkPath, + }) : _processUtils = processUtils, + _dartSdkPath = dartSdkPath; + + final ProcessUtils _processUtils; + final String _dartSdkPath; + + ResidentRunner _residentRunner; + Uri _webUri; + + @override + Future start( + BuildInfo buildInfo, + Device device, + DebuggingOptions debuggingOptions, + bool ipv6, { + File applicationBinary, + String route, + String userIdentifier, + String mainPath, + Map platformArgs = const {}, + }) async { + final FlutterDevice flutterDevice = await FlutterDevice.create( + device, + target: mainPath, + buildInfo: buildInfo, + platform: globals.platform, + ); + _residentRunner = webRunnerFactory.createWebRunner( + flutterDevice, + target: mainPath, + ipv6: ipv6, + debuggingOptions: debuggingOptions, + stayResident: false, + urlTunneller: null, + flutterProject: FlutterProject.current(), + ); + final Completer appStartedCompleter = Completer.sync(); + final int result = await _residentRunner.run( + appStartedCompleter: appStartedCompleter, + route: route, + ); + _webUri = _residentRunner.uri; + if (result != 0) { + throwToolExit(null); + } + } + + @override + Future startTest(String testFile, List arguments, Map environment, { + bool headless, + String chromeBinary, + String browserName, + bool androidEmulator, + int driverPort, + List browserDimension, + }) async { + async_io.WebDriver webDriver; + final Browser browser = _browserNameToEnum(browserName); + try { + webDriver = await async_io.createDriver( + uri: Uri.parse('http://localhost:$driverPort/'), + desired: getDesiredCapabilities(browser, headless, chromeBinary), + spec: async_io.WebDriverSpec.Auto + ); + } on Exception catch (ex) { + throwToolExit( + 'Unable to start WebDriver Session for Flutter for Web testing. \n' + 'Make sure you have the correct WebDriver Server running at $driverPort. \n' + 'Make sure the WebDriver Server matches option --browser-name. \n' + '$ex' + ); + } + + final bool isAndroidChrome = browser == Browser.androidChrome; + // Do not set the window size for android chrome browser. + if (!isAndroidChrome) { + assert(browserDimension.length == 2); + int x; + int y; + try { + x = int.parse(browserDimension[0]); + y = int.parse(browserDimension[1]); + } on FormatException catch (ex) { + throwToolExit('Dimension provided to --browser-dimension is invalid: $ex'); + } + final async_io.Window window = await webDriver.window; + await window.setLocation(const math.Point(0, 0)); + await window.setSize(math.Rectangle(0, 0, x, y)); + } + final int result = await _processUtils.stream([ + _dartSdkPath, + ...arguments, + testFile, + '-rexpanded', + ], environment: { + 'VM_SERVICE_URL': _webUri.toString(), + ..._additionalDriverEnvironment(webDriver, browserName, androidEmulator), + ...environment, + }); + await webDriver.quit(); + return result; + } + + @override + Future stop({File writeSkslOnExit, String userIdentifier}) async { + await _residentRunner.cleanupAtFinish(); + } + + Map _additionalDriverEnvironment(async_io.WebDriver webDriver, String browserName, bool androidEmulator) { + return { + 'DRIVER_SESSION_ID': webDriver.id, + 'DRIVER_SESSION_URI': webDriver.uri.toString(), + 'DRIVER_SESSION_SPEC': webDriver.spec.toString(), + 'DRIVER_SESSION_CAPABILITIES': json.encode(webDriver.capabilities), + 'SUPPORT_TIMELINE_ACTION': (_browserNameToEnum(browserName) == Browser.chrome).toString(), + 'FLUTTER_WEB_TEST': 'true', + 'ANDROID_CHROME_ON_EMULATOR': (_browserNameToEnum(browserName) == Browser.androidChrome && androidEmulator).toString(), + }; + } +} + +/// A list of supported browsers. +enum Browser { + /// Chrome on Android: https://developer.chrome.com/multidevice/android/overview + androidChrome, + /// Chrome: https://www.google.com/chrome/ + chrome, + /// Edge: https://www.microsoft.com/en-us/windows/microsoft-edge + edge, + /// Firefox: https://www.mozilla.org/en-US/firefox/ + firefox, + /// Safari in iOS: https://www.apple.com/safari/ + iosSafari, + /// Safari in macOS: https://www.apple.com/safari/ + safari, +} + +/// Returns desired capabilities for given [browser], [headless] and +/// [chromeBinary]. +@visibleForTesting +Map getDesiredCapabilities(Browser browser, bool headless, [String chromeBinary]) { + switch (browser) { + case Browser.chrome: + return { + 'acceptInsecureCerts': true, + 'browserName': 'chrome', + 'goog:loggingPrefs': { async_io.LogType.performance: 'ALL'}, + 'chromeOptions': { + if (chromeBinary != null) + 'binary': chromeBinary, + 'w3c': false, + 'args': [ + '--bwsi', + '--disable-background-timer-throttling', + '--disable-default-apps', + '--disable-extensions', + '--disable-popup-blocking', + '--disable-translate', + '--no-default-browser-check', + '--no-sandbox', + '--no-first-run', + if (headless) '--headless' + ], + 'perfLoggingPrefs': { + 'traceCategories': + 'devtools.timeline,' + 'v8,blink.console,benchmark,blink,' + 'blink.user_timing' + } + }, + }; + break; + case Browser.firefox: + return { + 'acceptInsecureCerts': true, + 'browserName': 'firefox', + 'moz:firefoxOptions' : { + 'args': [ + if (headless) '-headless' + ], + 'prefs': { + 'dom.file.createInChild': true, + 'dom.timeout.background_throttling_max_budget': -1, + 'media.autoplay.default': 0, + 'media.gmp-manager.url': '', + 'media.gmp-provider.enabled': false, + 'network.captive-portal-service.enabled': false, + 'security.insecure_field_warning.contextual.enabled': false, + 'test.currentTimeOffsetSeconds': 11491200 + }, + 'log': {'level': 'trace'} + } + }; + break; + case Browser.edge: + return { + 'acceptInsecureCerts': true, + 'browserName': 'edge', + }; + break; + case Browser.safari: + return { + 'browserName': 'safari', + }; + break; + case Browser.iosSafari: + return { + 'platformName': 'ios', + 'browserName': 'safari', + 'safari:useSimulator': true + }; + case Browser.androidChrome: + return { + 'browserName': 'chrome', + 'platformName': 'android', + 'goog:chromeOptions': { + 'androidPackage': 'com.android.chrome', + 'args': ['--disable-fullscreen'] + }, + }; + default: + throw UnsupportedError('Browser $browser not supported.'); + } +} + +/// Converts [browserName] string to [Browser] +Browser _browserNameToEnum(String browserName){ + switch (browserName) { + case 'android-chrome': return Browser.androidChrome; + case 'chrome': return Browser.chrome; + case 'edge': return Browser.edge; + case 'firefox': return Browser.firefox; + case 'ios-safari': return Browser.iosSafari; + case 'safari': return Browser.safari; + } + throw UnsupportedError('Browser $browserName not supported'); +} diff --git a/packages/flutter_tools/lib/src/isolated/resident_web_runner.dart b/packages/flutter_tools/lib/src/isolated/resident_web_runner.dart index 2205adc24f7..a908af66528 100644 --- a/packages/flutter_tools/lib/src/isolated/resident_web_runner.dart +++ b/packages/flutter_tools/lib/src/isolated/resident_web_runner.dart @@ -528,7 +528,6 @@ class _ResidentWebRunner extends ResidentWebRunner { flutterProject, target, debuggingOptions.buildInfo, - debuggingOptions.initializePlatform, false, kNoneWorker, true, @@ -596,7 +595,6 @@ class _ResidentWebRunner extends ResidentWebRunner { flutterProject, target, debuggingOptions.buildInfo, - debuggingOptions.initializePlatform, false, kNoneWorker, true, diff --git a/packages/flutter_tools/lib/src/resident_runner.dart b/packages/flutter_tools/lib/src/resident_runner.dart index ac8c6ac639f..f072ed84ad3 100644 --- a/packages/flutter_tools/lib/src/resident_runner.dart +++ b/packages/flutter_tools/lib/src/resident_runner.dart @@ -70,7 +70,6 @@ class FlutterDevice { /// Create a [FlutterDevice] with optional code generation enabled. static Future create( Device device, { - @required FlutterProject flutterProject, @required String target, @required BuildInfo buildInfo, @required Platform platform, diff --git a/packages/flutter_tools/lib/src/vmservice.dart b/packages/flutter_tools/lib/src/vmservice.dart index 79e4e95c50d..1e8acae81ef 100644 --- a/packages/flutter_tools/lib/src/vmservice.dart +++ b/packages/flutter_tools/lib/src/vmservice.dart @@ -8,6 +8,7 @@ import 'package:vm_service/vm_service.dart' as vm_service; import 'base/context.dart'; import 'base/io.dart' as io; +import 'base/logger.dart'; import 'build_info.dart'; import 'convert.dart'; import 'device.dart'; @@ -806,9 +807,11 @@ bool isPauseEvent(String kind) { // or delete it. Future sharedSkSlWriter(Device device, Map data, { File outputFile, + Logger logger, }) async { + logger ??= globals.logger; if (data.isEmpty) { - globals.logger.printStatus( + logger.printStatus( 'No data was received. To ensure SkSL data can be generated use a ' 'physical device then:\n' ' 1. Pass "--cache-sksl" as an argument to flutter run.\n' @@ -844,7 +847,7 @@ Future sharedSkSlWriter(Device device, Map data, { 'data': data, }; outputFile.writeAsStringSync(json.encode(manifest)); - globals.logger.printStatus('Wrote SkSL data to ${outputFile.path}.'); + logger.printStatus('Wrote SkSL data to ${outputFile.path}.'); return outputFile.path; } diff --git a/packages/flutter_tools/lib/src/web/compile.dart b/packages/flutter_tools/lib/src/web/compile.dart index d494c576755..85006cc1630 100644 --- a/packages/flutter_tools/lib/src/web/compile.dart +++ b/packages/flutter_tools/lib/src/web/compile.dart @@ -26,7 +26,6 @@ Future buildWeb( FlutterProject flutterProject, String target, BuildInfo buildInfo, - bool initializePlatform, bool csp, String serviceWorkerStrategy, bool sourceMaps, @@ -54,7 +53,6 @@ Future buildWeb( defines: { kBuildMode: getNameForBuildMode(buildInfo.mode), kTargetFile: target, - kInitializePlatform: initializePlatform.toString(), kHasWebPlugins: hasWebPlugins.toString(), kDartDefines: encodeDartDefines(buildInfo.dartDefines), kCspMode: csp.toString(), diff --git a/packages/flutter_tools/test/commands.shard/hermetic/build_web_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/build_web_test.dart index 41555ceb9ec..4a76cf5bab4 100644 --- a/packages/flutter_tools/test/commands.shard/hermetic/build_web_test.dart +++ b/packages/flutter_tools/test/commands.shard/hermetic/build_web_test.dart @@ -54,7 +54,6 @@ void main() { fileSystem.path.join('lib', 'main.dart'), BuildInfo.debug, false, - false, null, true, ), throwsToolExit()); diff --git a/packages/flutter_tools/test/commands.shard/hermetic/drive_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/drive_test.dart deleted file mode 100644 index 9538adab9ad..00000000000 --- a/packages/flutter_tools/test/commands.shard/hermetic/drive_test.dart +++ /dev/null @@ -1,882 +0,0 @@ -// 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 'package:file/memory.dart'; -import 'package:file_testing/file_testing.dart'; -import 'package:flutter_tools/src/android/android_device.dart'; -import 'package:flutter_tools/src/application_package.dart'; -import 'package:flutter_tools/src/base/common.dart'; -import 'package:flutter_tools/src/base/dds.dart'; -import 'package:flutter_tools/src/base/file_system.dart'; -import 'package:flutter_tools/src/base/io.dart'; -import 'package:flutter_tools/src/base/platform.dart'; -import 'package:flutter_tools/src/build_info.dart'; -import 'package:flutter_tools/src/cache.dart'; -import 'package:flutter_tools/src/commands/drive.dart'; -import 'package:flutter_tools/src/device.dart'; -import 'package:flutter_tools/src/globals.dart' as globals; -import 'package:mockito/mockito.dart'; -import 'package:webdriver/sync_io.dart' as sync_io; -import 'package:flutter_tools/src/vmservice.dart'; - -import '../../src/common.dart'; -import '../../src/context.dart'; -import '../../src/fakes.dart'; - -void main() { - group('drive', () { - DriveCommand command; - Device mockUnsupportedDevice; - MemoryFileSystem fs; - Directory tempDir; - - setUpAll(() { - Cache.disableLocking(); - }); - - setUp(() { - command = DriveCommand(); - fs = MemoryFileSystem.test(); - tempDir = fs.systemTempDirectory.createTempSync('flutter_drive_test.'); - fs.currentDirectory = tempDir; - fs.directory('test').createSync(); - fs.directory('test_driver').createSync(); - fs.file('pubspec.yaml').createSync(); - fs.file('.packages').createSync(); - setExitFunctionForTests(); - appStarter = (DriveCommand command, Uri webUri, ApplicationPackage package, bool prebuiltApplication) { - throw 'Unexpected call to appStarter'; - }; - testRunner = (List testArgs, Map environment) { - throw 'Unexpected call to testRunner'; - }; - appStopper = (DriveCommand command, ApplicationPackage package) { - throw 'Unexpected call to appStopper'; - }; - command.applicationPackages = FakeApplicationPackageFactory(); - }); - - tearDown(() { - command = null; - restoreExitFunction(); - restoreAppStarter(); - restoreAppStopper(); - restoreTestRunner(); - tryToDelete(tempDir); - }); - - void applyDdsMocks(Device device) { - final MockDartDevelopmentService mockDds = MockDartDevelopmentService(); - when(device.dds).thenReturn(mockDds); - when(mockDds.startDartDevelopmentService(any, any, any, any)).thenReturn(null); - when(mockDds.uri).thenReturn(Uri.parse('http://localhost:8181')); - } - - testUsingContext('returns 1 when test file is not found', () async { - testDeviceManager.addDevice(MockDevice()); - - final String testApp = globals.fs.path.join(tempDir.path, 'test', 'e2e.dart'); - final String testFile = globals.fs.path.join(tempDir.path, 'test_driver', 'e2e_test.dart'); - globals.fs.file(testApp).createSync(recursive: true); - - final List args = [ - 'drive', - '--target=$testApp', - '--no-pub', - ]; - try { - await createTestCommandRunner(command).run(args); - fail('Expect exception'); - } on ToolExit catch (e) { - expect(e.exitCode ?? 1, 1); - expect(e.message, contains('Test file not found: $testFile')); - } - }, overrides: { - FileSystem: () => fs, - ProcessManager: () => FakeProcessManager.any(), - }); - - testUsingContext('returns 1 when app fails to run', () async { - testDeviceManager.addDevice(MockDevice()); - appStarter = expectAsync4((DriveCommand command, Uri webUri, ApplicationPackage package, bool prebuiltApplication) async => null, count: 3); - - final String testApp = globals.fs.path.join(tempDir.path, 'test_driver', 'e2e.dart'); - final String testFile = globals.fs.path.join(tempDir.path, 'test_driver', 'e2e_test.dart'); - - final MemoryFileSystem memFs = fs; - await memFs.file(testApp).writeAsString('main() { }'); - await memFs.file(testFile).writeAsString('main() { }'); - - final List args = [ - 'drive', - '--target=$testApp', - '--no-pub', - ]; - try { - await createTestCommandRunner(command).run(args); - fail('Expect exception'); - } on ToolExit catch (e) { - expect(e.exitCode, 1); - expect(e.message, contains('Application failed to start. Will not run test. Quitting.')); - } - }, overrides: { - FileSystem: () => fs, - ProcessManager: () => FakeProcessManager.any(), - }); - - testUsingContext('returns 1 when app file is outside package', () async { - final String appFile = globals.fs.path.join(tempDir.dirname, 'other_app', 'app.dart'); - globals.fs.file(appFile).createSync(recursive: true); - final List args = [ - '--no-wrap', - 'drive', - '--target=$appFile', - '--no-pub', - ]; - try { - await createTestCommandRunner(command).run(args); - fail('Expect exception'); - } on ToolExit catch (e) { - expect(e.exitCode ?? 1, 1); - expect(testLogger.errorText, contains( - 'Application file $appFile is outside the package directory ${tempDir.path}', - )); - } - }, overrides: { - FileSystem: () => fs, - ProcessManager: () => FakeProcessManager.any(), - }); - - testUsingContext('returns 1 when app file is in the root dir', () async { - final String appFile = globals.fs.path.join(tempDir.path, 'main.dart'); - globals.fs.file(appFile).createSync(recursive: true); - final List args = [ - '--no-wrap', - 'drive', - '--target=$appFile', - '--no-pub', - ]; - try { - await createTestCommandRunner(command).run(args); - fail('Expect exception'); - } on ToolExit catch (e) { - expect(e.exitCode ?? 1, 1); - expect(testLogger.errorText, contains( - 'Application file main.dart must reside in one of the ' - 'sub-directories of the package structure, not in the root directory.', - )); - } - }, overrides: { - FileSystem: () => fs, - ProcessManager: () => FakeProcessManager.any(), - }); - - testUsingContext('returns 1 when targeted device is not Android with --device-user', () async { - testDeviceManager.addDevice(MockDevice()); - - final String testApp = globals.fs.path.join(tempDir.path, 'test', 'e2e.dart'); - globals.fs.file(testApp).createSync(recursive: true); - - final List args = [ - 'drive', - '--target=$testApp', - '--no-pub', - '--device-user', - '10', - ]; - - expect(() async => await createTestCommandRunner(command).run(args), - throwsToolExit(message: '--device-user is only supported for Android')); - }, overrides: { - FileSystem: () => fs, - ProcessManager: () => FakeProcessManager.any(), - }); - - testUsingContext('returns 0 when test ends successfully', () async { - final MockAndroidDevice mockDevice = MockAndroidDevice(); - applyDdsMocks(mockDevice); - testDeviceManager.addDevice(mockDevice); - - final String testApp = globals.fs.path.join(tempDir.path, 'test', 'e2e.dart'); - final String testFile = globals.fs.path.join(tempDir.path, 'test_driver', 'e2e_test.dart'); - - appStarter = expectAsync4((DriveCommand command, Uri webUri, ApplicationPackage package, bool prebuiltApplication) async { - return LaunchResult.succeeded(); - }); - testRunner = expectAsync2((List testArgs, Map environment) async { - expect(testArgs, ['--no-sound-null-safety', testFile]); - // VM_SERVICE_URL is not set by drive command arguments - expect(environment, { - 'VM_SERVICE_URL': 'null', - }); - }); - appStopper = expectAsync2((DriveCommand command, ApplicationPackage package) async { - return true; - }); - - final MemoryFileSystem memFs = fs; - await memFs.file(testApp).writeAsString('main() {}'); - await memFs.file(testFile).writeAsString('main() {}'); - - final List args = [ - 'drive', - '--target=$testApp', - '--no-pub', - '--disable-dds', - '--device-user', - '10', - ]; - await createTestCommandRunner(command).run(args); - verify(mockDevice.dispose()); - expect(testLogger.errorText, isEmpty); - }, overrides: { - FileSystem: () => fs, - ProcessManager: () => FakeProcessManager.any(), - }); - - testUsingContext('returns exitCode set by test runner', () async { - final MockDevice mockDevice = MockDevice(); - applyDdsMocks(mockDevice); - testDeviceManager.addDevice(mockDevice); - - final String testApp = globals.fs.path.join(tempDir.path, 'test', 'e2e.dart'); - final String testFile = globals.fs.path.join(tempDir.path, 'test_driver', 'e2e_test.dart'); - - appStarter = expectAsync4((DriveCommand command, Uri webUri, ApplicationPackage package, bool prebuiltApplication) async { - return LaunchResult.succeeded(); - }); - testRunner = (List testArgs, Map environment) async { - throwToolExit(null, exitCode: 123); - }; - appStopper = expectAsync2((DriveCommand command, ApplicationPackage package) async { - return true; - }); - - final MemoryFileSystem memFs = fs; - await memFs.file(testApp).writeAsString('main() {}'); - await memFs.file(testFile).writeAsString('main() {}'); - - final List args = [ - 'drive', - '--target=$testApp', - '--no-pub', - ]; - try { - await createTestCommandRunner(command).run(args); - fail('Expect exception'); - } on ToolExit catch (e) { - expect(e.exitCode ?? 1, 123); - expect(e.message, isNull); - } - }, overrides: { - FileSystem: () => fs, - ProcessManager: () => FakeProcessManager.any(), - }); - - testUsingContext('enable experiment', () async { - final MockAndroidDevice mockDevice = MockAndroidDevice(); - applyDdsMocks(mockDevice); - testDeviceManager.addDevice(mockDevice); - - final String testApp = globals.fs.path.join(tempDir.path, 'test', 'e2e.dart'); - final String testFile = globals.fs.path.join(tempDir.path, 'test_driver', 'e2e_test.dart'); - - appStarter = expectAsync4((DriveCommand command, Uri webUri, ApplicationPackage package, bool prebuiltApplication) async { - return LaunchResult.succeeded(); - }); - testRunner = expectAsync2((List testArgs, Map environment) async { - expect( - testArgs, - [ - '--enable-experiment=experiment1,experiment2', - '--no-sound-null-safety', - testFile, - ] - ); - }); - appStopper = expectAsync2((DriveCommand command, ApplicationPackage package) async { - return true; - }); - - final MemoryFileSystem memFs = fs; - await memFs.file(testApp).writeAsString('main() {}'); - await memFs.file(testFile).writeAsString('main() {}'); - - final List args = [ - 'drive', - '--target=$testApp', - '--no-pub', - '--enable-experiment=experiment1', - '--enable-experiment=experiment2', - ]; - await createTestCommandRunner(command).run(args); - expect(testLogger.errorText, isEmpty); - }, overrides: { - FileSystem: () => fs, - ProcessManager: () => FakeProcessManager.any(), - }); - - testUsingContext('sound null safety', () async { - final MockAndroidDevice mockDevice = MockAndroidDevice(); - applyDdsMocks(mockDevice); - testDeviceManager.addDevice(mockDevice); - - final String testApp = globals.fs.path.join(tempDir.path, 'test', 'e2e.dart'); - final String testFile = globals.fs.path.join(tempDir.path, 'test_driver', 'e2e_test.dart'); - - appStarter = expectAsync4((DriveCommand command, Uri webUri, ApplicationPackage package, bool prebuiltApplication) async { - return LaunchResult.succeeded(); - }); - testRunner = expectAsync2((List testArgs, Map environment) async { - expect( - testArgs, - [ - '--sound-null-safety', - testFile, - ] - ); - }); - appStopper = expectAsync2((DriveCommand command, ApplicationPackage package) async { - return true; - }); - - final MemoryFileSystem memFs = fs; - await memFs.file(testApp).writeAsString('main() {}'); - await memFs.file(testFile).writeAsString('main() {}'); - - final List args = [ - 'drive', - '--target=$testApp', - '--no-pub', - '--sound-null-safety', - ]; - await createTestCommandRunner(command).run(args); - expect(testLogger.errorText, isEmpty); - }, overrides: { - FileSystem: () => fs, - ProcessManager: () => FakeProcessManager.any(), - }); - - group('findTargetDevice', () { - testUsingContext('uses specified device', () async { - testDeviceManager.specifiedDeviceId = '123'; - final Device mockDevice = MockDevice(); - testDeviceManager.addDevice(mockDevice); - when(mockDevice.name).thenReturn('specified-device'); - when(mockDevice.id).thenReturn('123'); - - final Device device = await findTargetDevice(timeout: null); - expect(device.name, 'specified-device'); - }, overrides: { - FileSystem: () => fs, - ProcessManager: () => FakeProcessManager.any(), - }); - }); - - void findTargetDeviceOnOperatingSystem(String operatingSystem) { - Platform platform() => FakePlatform(operatingSystem: operatingSystem); - - testUsingContext('returns null if no devices found', () async { - expect(await findTargetDevice(timeout: null), isNull); - }, overrides: { - FileSystem: () => fs, - ProcessManager: () => FakeProcessManager.any(), - Platform: platform, - }); - - testUsingContext('uses existing Android device', () async { - final Device mockDevice = MockAndroidDevice(); - when(mockDevice.name).thenReturn('mock-android-device'); - testDeviceManager.addDevice(mockDevice); - - final Device device = await findTargetDevice(timeout: null); - expect(device.name, 'mock-android-device'); - }, overrides: { - FileSystem: () => fs, - ProcessManager: () => FakeProcessManager.any(), - Platform: platform, - }); - - testUsingContext('skips unsupported device', () async { - final Device mockDevice = MockAndroidDevice(); - mockUnsupportedDevice = MockDevice(); - when(mockUnsupportedDevice.isSupportedForProject(any)) - .thenReturn(false); - when(mockUnsupportedDevice.isSupported()) - .thenReturn(false); - when(mockUnsupportedDevice.name).thenReturn('mock-web'); - when(mockUnsupportedDevice.id).thenReturn('web-1'); - when(mockUnsupportedDevice.targetPlatform).thenAnswer((_) => Future(() => TargetPlatform.web_javascript)); - when(mockUnsupportedDevice.isLocalEmulator).thenAnswer((_) => Future(() => false)); - when(mockUnsupportedDevice.sdkNameAndVersion).thenAnswer((_) => Future(() => 'html5')); - when(mockDevice.name).thenReturn('mock-android-device'); - when(mockDevice.id).thenReturn('mad-28'); - when(mockDevice.isSupported()) - .thenReturn(true); - when(mockDevice.isSupportedForProject(any)) - .thenReturn(true); - when(mockDevice.targetPlatform).thenAnswer((_) => Future(() => TargetPlatform.android_x64)); - when(mockDevice.isLocalEmulator).thenAnswer((_) => Future(() => false)); - when(mockDevice.sdkNameAndVersion).thenAnswer((_) => Future(() => 'sdk-28')); - testDeviceManager.addDevice(mockDevice); - testDeviceManager.addDevice(mockUnsupportedDevice); - - final Device device = await findTargetDevice(timeout: null); - expect(device.name, 'mock-android-device'); - }, overrides: { - FileSystem: () => fs, - ProcessManager: () => FakeProcessManager.any(), - Platform: platform, - }); - } - - group('findTargetDevice on Linux', () { - findTargetDeviceOnOperatingSystem('linux'); - }); - - group('findTargetDevice on Windows', () { - findTargetDeviceOnOperatingSystem('windows'); - }); - - group('findTargetDevice on macOS', () { - findTargetDeviceOnOperatingSystem('macos'); - - Platform macOsPlatform() => FakePlatform(operatingSystem: 'macos'); - - testUsingContext('uses existing simulator', () async { - final Device mockDevice = MockDevice(); - testDeviceManager.addDevice(mockDevice); - when(mockDevice.name).thenReturn('mock-simulator'); - when(mockDevice.isLocalEmulator) - .thenAnswer((Invocation invocation) => Future.value(true)); - - final Device device = await findTargetDevice(timeout: null); - expect(device.name, 'mock-simulator'); - }, overrides: { - FileSystem: () => fs, - ProcessManager: () => FakeProcessManager.any(), - Platform: macOsPlatform, - }); - }); - - group('build arguments', () { - String testApp, testFile; - - setUp(() { - restoreAppStarter(); - }); - - Future appStarterSetup() async { - final Device mockDevice = MockDevice(); - applyDdsMocks(mockDevice); - testDeviceManager.addDevice(mockDevice); - - final FakeDeviceLogReader mockDeviceLogReader = FakeDeviceLogReader(); - when(mockDevice.getLogReader()).thenReturn(mockDeviceLogReader); - final MockLaunchResult mockLaunchResult = MockLaunchResult(); - when(mockLaunchResult.started).thenReturn(true); - when(mockDevice.startApp( - null, - mainPath: anyNamed('mainPath'), - route: anyNamed('route'), - debuggingOptions: anyNamed('debuggingOptions'), - platformArgs: anyNamed('platformArgs'), - prebuiltApplication: anyNamed('prebuiltApplication'), - userIdentifier: anyNamed('userIdentifier'), - )).thenAnswer((_) => Future.value(mockLaunchResult)); - when(mockDevice.isAppInstalled(any, userIdentifier: anyNamed('userIdentifier'))) - .thenAnswer((_) => Future.value(false)); - - testApp = globals.fs.path.join(tempDir.path, 'test', 'e2e.dart'); - testFile = globals.fs.path.join(tempDir.path, 'test_driver', 'e2e_test.dart'); - - testRunner = (List testArgs, Map environment) async { - throwToolExit(null, exitCode: 123); - }; - appStopper = expectAsync2( - (DriveCommand command, ApplicationPackage package) async { - return true; - }, - count: 2, - ); - - final MemoryFileSystem memFs = fs; - await memFs.file(testApp).writeAsString('main() {}'); - await memFs.file(testFile).writeAsString('main() {}'); - return mockDevice; - } - - testUsingContext('does not use pre-built app if no build arg provided', () async { - final Device mockDevice = await appStarterSetup(); - - final List args = [ - 'drive', - '--target=$testApp', - '--no-pub', - ]; - try { - await createTestCommandRunner(command).run(args); - } on ToolExit catch (e) { - expect(e.exitCode, 123); - expect(e.message, null); - } - verify(mockDevice.startApp( - null, - mainPath: anyNamed('mainPath'), - route: anyNamed('route'), - debuggingOptions: anyNamed('debuggingOptions'), - platformArgs: anyNamed('platformArgs'), - prebuiltApplication: false, - userIdentifier: anyNamed('userIdentifier'), - )); - verify(mockDevice.dispose()); - }, overrides: { - FileSystem: () => fs, - ProcessManager: () => FakeProcessManager.any(), - }); - - testUsingContext('does not use pre-built app if --build arg provided', () async { - final Device mockDevice = await appStarterSetup(); - - final List args = [ - 'drive', - '--build', - '--target=$testApp', - '--no-pub', - ]; - try { - await createTestCommandRunner(command).run(args); - } on ToolExit catch (e) { - expect(e.exitCode, 123); - expect(e.message, null); - } - verify(mockDevice.startApp( - null, - mainPath: anyNamed('mainPath'), - route: anyNamed('route'), - debuggingOptions: anyNamed('debuggingOptions'), - platformArgs: anyNamed('platformArgs'), - prebuiltApplication: false, - userIdentifier: anyNamed('userIdentifier'), - )); - verify(mockDevice.dispose()); - }, overrides: { - FileSystem: () => fs, - ProcessManager: () => FakeProcessManager.any(), - }); - }); - - group('debugging options', () { - DebuggingOptions debuggingOptions; - - String testApp, testFile; - - setUp(() { - restoreAppStarter(); - }); - - Future appStarterSetup() async { - final Device mockDevice = MockDevice(); - applyDdsMocks(mockDevice); - testDeviceManager.addDevice(mockDevice); - - final FakeDeviceLogReader mockDeviceLogReader = FakeDeviceLogReader(); - when(mockDevice.getLogReader()).thenReturn(mockDeviceLogReader); - final MockLaunchResult mockLaunchResult = MockLaunchResult(); - when(mockLaunchResult.started).thenReturn(true); - when(mockDevice.startApp( - null, - mainPath: anyNamed('mainPath'), - route: anyNamed('route'), - debuggingOptions: anyNamed('debuggingOptions'), - platformArgs: anyNamed('platformArgs'), - prebuiltApplication: anyNamed('prebuiltApplication'), - userIdentifier: anyNamed('userIdentifier'), - )).thenAnswer((Invocation invocation) async { - debuggingOptions = invocation.namedArguments[#debuggingOptions] as DebuggingOptions; - return mockLaunchResult; - }); - when(mockDevice.isAppInstalled(any, userIdentifier: anyNamed('userIdentifier'))) - .thenAnswer((_) => Future.value(false)); - - testApp = globals.fs.path.join(tempDir.path, 'test', 'e2e.dart'); - testFile = globals.fs.path.join(tempDir.path, 'test_driver', 'e2e_test.dart'); - - testRunner = (List testArgs, Map environment) async { - throwToolExit(null, exitCode: 123); - }; - appStopper = expectAsync2( - (DriveCommand command, ApplicationPackage package) async { - return true; - }, - count: 2, - ); - - final MemoryFileSystem memFs = fs; - await memFs.file(testApp).writeAsString('main() {}'); - await memFs.file(testFile).writeAsString('main() {}'); - return mockDevice; - } - - void _testOptionThatDefaultsToFalse( - String optionName, - bool setToTrue, - bool optionValue(), - ) { - testUsingContext('$optionName ${setToTrue ? 'works' : 'defaults to false'}', () async { - final Device mockDevice = await appStarterSetup(); - - final List args = [ - 'drive', - '--target=$testApp', - if (setToTrue) optionName, - '--no-pub', - ]; - try { - await createTestCommandRunner(command).run(args); - } on ToolExit catch (e) { - expect(e.exitCode, 123); - expect(e.message, null); - } - verify(mockDevice.startApp( - null, - mainPath: anyNamed('mainPath'), - route: anyNamed('route'), - debuggingOptions: anyNamed('debuggingOptions'), - platformArgs: anyNamed('platformArgs'), - prebuiltApplication: false, - userIdentifier: anyNamed('userIdentifier'), - )); - expect(optionValue(), setToTrue ? isTrue : isFalse); - }, overrides: { - FileSystem: () => fs, - ProcessManager: () => FakeProcessManager.any(), - }); - } - - void testOptionThatDefaultsToFalse( - String optionName, - bool optionValue(), - ) { - _testOptionThatDefaultsToFalse(optionName, true, optionValue); - _testOptionThatDefaultsToFalse(optionName, false, optionValue); - } - - testOptionThatDefaultsToFalse( - '--dump-skp-on-shader-compilation', - () => debuggingOptions.dumpSkpOnShaderCompilation, - ); - - testOptionThatDefaultsToFalse( - '--verbose-system-logs', - () => debuggingOptions.verboseSystemLogs, - ); - - testOptionThatDefaultsToFalse( - '--cache-sksl', - () => debuggingOptions.cacheSkSL, - ); - - testOptionThatDefaultsToFalse( - '--purge-persistent-cache', - () => debuggingOptions.purgePersistentCache, - ); - - testOptionThatDefaultsToFalse( - '--publish-port', - () => !debuggingOptions.disablePortPublication, - ); - }); - }); - - group('getDesiredCapabilities', () { - test('Chrome with headless on', () { - final Map expected = { - 'acceptInsecureCerts': true, - 'browserName': 'chrome', - 'goog:loggingPrefs': { sync_io.LogType.performance: 'ALL'}, - 'chromeOptions': { - 'w3c': false, - 'args': [ - '--bwsi', - '--disable-background-timer-throttling', - '--disable-default-apps', - '--disable-extensions', - '--disable-popup-blocking', - '--disable-translate', - '--no-default-browser-check', - '--no-sandbox', - '--no-first-run', - '--headless' - ], - 'perfLoggingPrefs': { - 'traceCategories': - 'devtools.timeline,' - 'v8,blink.console,benchmark,blink,' - 'blink.user_timing' - } - } - }; - - expect(getDesiredCapabilities(Browser.chrome, true), expected); - }); - - test('Chrome with headless off', () { - const String chromeBinary = 'random-binary'; - final Map expected = { - 'acceptInsecureCerts': true, - 'browserName': 'chrome', - 'goog:loggingPrefs': { sync_io.LogType.performance: 'ALL'}, - 'chromeOptions': { - 'binary': chromeBinary, - 'w3c': false, - 'args': [ - '--bwsi', - '--disable-background-timer-throttling', - '--disable-default-apps', - '--disable-extensions', - '--disable-popup-blocking', - '--disable-translate', - '--no-default-browser-check', - '--no-sandbox', - '--no-first-run', - ], - 'perfLoggingPrefs': { - 'traceCategories': - 'devtools.timeline,' - 'v8,blink.console,benchmark,blink,' - 'blink.user_timing' - } - } - }; - - expect(getDesiredCapabilities(Browser.chrome, false, chromeBinary), expected); - - }); - - test('Firefox with headless on', () { - final Map expected = { - 'acceptInsecureCerts': true, - 'browserName': 'firefox', - 'moz:firefoxOptions' : { - 'args': ['-headless'], - 'prefs': { - 'dom.file.createInChild': true, - 'dom.timeout.background_throttling_max_budget': -1, - 'media.autoplay.default': 0, - 'media.gmp-manager.url': '', - 'media.gmp-provider.enabled': false, - 'network.captive-portal-service.enabled': false, - 'security.insecure_field_warning.contextual.enabled': false, - 'test.currentTimeOffsetSeconds': 11491200 - }, - 'log': {'level': 'trace'} - } - }; - - expect(getDesiredCapabilities(Browser.firefox, true), expected); - }); - - test('Firefox with headless off', () { - final Map expected = { - 'acceptInsecureCerts': true, - 'browserName': 'firefox', - 'moz:firefoxOptions' : { - 'args': [], - 'prefs': { - 'dom.file.createInChild': true, - 'dom.timeout.background_throttling_max_budget': -1, - 'media.autoplay.default': 0, - 'media.gmp-manager.url': '', - 'media.gmp-provider.enabled': false, - 'network.captive-portal-service.enabled': false, - 'security.insecure_field_warning.contextual.enabled': false, - 'test.currentTimeOffsetSeconds': 11491200 - }, - 'log': {'level': 'trace'} - } - }; - - expect(getDesiredCapabilities(Browser.firefox, false), expected); - }); - - test('Edge', () { - final Map expected = { - 'acceptInsecureCerts': true, - 'browserName': 'edge', - }; - - expect(getDesiredCapabilities(Browser.edge, false), expected); - }); - - test('macOS Safari', () { - final Map expected = { - 'browserName': 'safari', - }; - - expect(getDesiredCapabilities(Browser.safari, false), expected); - }); - - test('iOS Safari', () { - final Map expected = { - 'platformName': 'ios', - 'browserName': 'safari', - 'safari:useSimulator': true - }; - - expect(getDesiredCapabilities(Browser.iosSafari, false), expected); - }); - - test('android chrome', () { - final Map expected = { - 'browserName': 'chrome', - 'platformName': 'android', - 'goog:chromeOptions': { - 'androidPackage': 'com.android.chrome', - 'args': ['--disable-fullscreen'] - }, - }; - - expect(getDesiredCapabilities(Browser.androidChrome, false), expected); - }); - }); - - testUsingContext('Can write SkSL file with provided output file', () async { - final MockDevice device = MockDevice(); - when(device.name).thenReturn('foo'); - when(device.targetPlatform).thenAnswer((Invocation invocation) async { - return TargetPlatform.android_arm; - }); - final File outputFile = globals.fs.file('out/foo'); - - final String result = await sharedSkSlWriter( - device, - {'foo': 'bar'}, - outputFile: outputFile, - ); - - expect(result, 'out/foo'); - expect(outputFile, exists); - expect(outputFile.readAsStringSync(), '{"platform":"android","name":"foo","engineRevision":null,"data":{"foo":"bar"}}'); - }, overrides: { - FileSystem: () => MemoryFileSystem.test(), - ProcessManager: () => FakeProcessManager.any(), - }); -} - -class MockDevice extends Mock implements Device { - MockDevice() { - when(isSupported()).thenReturn(true); - } -} - -class MockAndroidDevice extends Mock implements AndroidDevice { } -class MockDartDevelopmentService extends Mock implements DartDevelopmentService { } -class MockLaunchResult extends Mock implements LaunchResult { } -class FakeApplicationPackageFactory extends Fake implements ApplicationPackageFactory { - @override - Future getPackageForPlatform(TargetPlatform platform, {BuildInfo buildInfo, File applicationBinary}) { - return null; - } -} diff --git a/packages/flutter_tools/test/general.shard/build_system/targets/web_test.dart b/packages/flutter_tools/test/general.shard/build_system/targets/web_test.dart index dcc2d8fd1f7..615d0e00ed4 100644 --- a/packages/flutter_tools/test/general.shard/build_system/targets/web_test.dart +++ b/packages/flutter_tools/test/general.shard/build_system/targets/web_test.dart @@ -77,7 +77,6 @@ void main() { ..writeAsStringSync('void main() {}'); environment.defines[kTargetFile] = mainFile.path; environment.defines[kHasWebPlugins] = 'true'; - environment.defines[kInitializePlatform] = 'true'; await const WebEntrypointTarget().build(environment); final String generated = environment.buildDir.childFile('main.dart').readAsStringSync(); @@ -86,9 +85,6 @@ void main() { expect(generated, contains("import 'package:foo/generated_plugin_registrant.dart';")); expect(generated, contains('registerPlugins(webPluginRegistry);')); - // Platform - expect(generated, contains('if (true) {')); - // Main expect(generated, contains('entrypoint.main();')); @@ -183,7 +179,6 @@ void main() { environment.defines[kTargetFile] = mainFile.path; environment.defines[kHasWebPlugins] = 'true'; - environment.defines[kInitializePlatform] = 'true'; await const WebEntrypointTarget().build(environment); final String generated = environment.buildDir.childFile('main.dart').readAsStringSync(); @@ -192,9 +187,6 @@ void main() { expect(generated, contains("import 'package:foo/generated_plugin_registrant.dart';")); expect(generated, contains('registerPlugins(webPluginRegistry);')); - // Platform - expect(generated, contains('if (true) {')); - // Main expect(generated, contains('entrypoint.main();')); @@ -210,7 +202,6 @@ void main() { ..writeAsStringSync('void main() {}'); environment.defines[kTargetFile] = mainFile.path; environment.defines[kHasWebPlugins] = 'false'; - environment.defines[kInitializePlatform] = 'true'; await const WebEntrypointTarget().build(environment); final String generated = environment.buildDir.childFile('main.dart').readAsStringSync(); @@ -218,32 +209,6 @@ void main() { // Plugins expect(generated, isNot(contains("import 'package:foo/generated_plugin_registrant.dart';"))); expect(generated, isNot(contains('registerPlugins(webPluginRegistry);'))); - - // Platform - expect(generated, contains('if (true) {')); - - // Main - expect(generated, contains('entrypoint.main();')); - })); - - test('WebEntrypointTarget generates an entrypoint with plugins and without init platform', () => testbed.run(() async { - final File mainFile = globals.fs.file(globals.fs.path.join('foo', 'lib', 'main.dart')) - ..createSync(recursive: true) - ..writeAsStringSync('void main() {}'); - environment.defines[kTargetFile] = mainFile.path; - environment.defines[kHasWebPlugins] = 'true'; - environment.defines[kInitializePlatform] = 'false'; - await const WebEntrypointTarget().build(environment); - - final String generated = environment.buildDir.childFile('main.dart').readAsStringSync(); - - // Plugins - expect(generated, contains("import 'package:foo/generated_plugin_registrant.dart';")); - expect(generated, contains('registerPlugins(webPluginRegistry);')); - - // Platform - expect(generated, contains('if (false) {')); - // Main expect(generated, contains('entrypoint.main();')); })); @@ -282,7 +247,6 @@ void main() { ..writeAsStringSync('void main() {}'); environment.defines[kTargetFile] = mainFile.path; environment.defines[kHasWebPlugins] = 'false'; - environment.defines[kInitializePlatform] = 'false'; await const WebEntrypointTarget().build(environment); final String generated = environment.buildDir.childFile('main.dart').readAsStringSync(); @@ -291,9 +255,6 @@ void main() { expect(generated, isNot(contains("import 'package:foo/generated_plugin_registrant.dart';"))); expect(generated, isNot(contains('registerPlugins(webPluginRegistry);'))); - // Platform - expect(generated, contains('if (false) {')); - // Main expect(generated, contains('entrypoint.main();')); })); diff --git a/packages/flutter_tools/test/general.shard/desktop_device_test.dart b/packages/flutter_tools/test/general.shard/desktop_device_test.dart index 76a5604d757..11f4d28a4ed 100644 --- a/packages/flutter_tools/test/general.shard/desktop_device_test.dart +++ b/packages/flutter_tools/test/general.shard/desktop_device_test.dart @@ -55,7 +55,7 @@ void main() { testWithoutContext('Install and uninstall are no-ops that report success', () async { final FakeDesktopDevice device = setUpDesktopDevice(); - final FakeAppplicationPackage package = FakeAppplicationPackage(); + final FakeApplicationPackage package = FakeApplicationPackage(); expect(await device.uninstallApp(package), true); expect(await device.isAppInstalled(package), true); @@ -71,7 +71,7 @@ void main() { group('Starting and stopping application', () { testWithoutContext('Stop without start is a successful no-op', () async { final FakeDesktopDevice device = setUpDesktopDevice(); - final FakeAppplicationPackage package = FakeAppplicationPackage(); + final FakeApplicationPackage package = FakeApplicationPackage(); expect(await device.stopApp(package), true); }); @@ -89,7 +89,7 @@ void main() { final FakeDesktopDevice device = setUpDesktopDevice(processManager: processManager, fileSystem: fileSystem); final String executableName = device.executablePathForDevice(null, BuildMode.debug); fileSystem.file(executableName).writeAsStringSync('\n'); - final FakeAppplicationPackage package = FakeAppplicationPackage(); + final FakeApplicationPackage package = FakeApplicationPackage(); final LaunchResult result = await device.startApp( package, prebuiltApplication: true, @@ -103,7 +103,7 @@ void main() { testWithoutContext('Null executable path fails gracefully', () async { final BufferLogger logger = BufferLogger.test(); final DesktopDevice device = setUpDesktopDevice(nullExecutablePathForDevice: true, logger: logger); - final FakeAppplicationPackage package = FakeAppplicationPackage(); + final FakeApplicationPackage package = FakeApplicationPackage(); final LaunchResult result = await device.startApp( package, prebuiltApplication: true, @@ -124,7 +124,7 @@ void main() { ), ]); final FakeDesktopDevice device = setUpDesktopDevice(processManager: processManager); - final FakeAppplicationPackage package = FakeAppplicationPackage(); + final FakeApplicationPackage package = FakeApplicationPackage(); final LaunchResult result = await device.startApp( package, prebuiltApplication: true, @@ -168,7 +168,7 @@ void main() { ), ]); final FakeDesktopDevice device = setUpDesktopDevice(processManager: processManager); - final FakeAppplicationPackage package = FakeAppplicationPackage(); + final FakeApplicationPackage package = FakeApplicationPackage(); final LaunchResult result = await device.startApp( package, prebuiltApplication: true, @@ -191,7 +191,6 @@ void main() { purgePersistentCache: true, useTestFonts: true, verboseSystemLogs: true, - initializePlatform: true, nullAssertions: true, ), ); @@ -217,7 +216,7 @@ void main() { ), ]); final FakeDesktopDevice device = setUpDesktopDevice(processManager: processManager); - final FakeAppplicationPackage package = FakeAppplicationPackage(); + final FakeApplicationPackage package = FakeApplicationPackage(); final LaunchResult result = await device.startApp( package, prebuiltApplication: true, @@ -228,7 +227,6 @@ void main() { BuildInfo.debug, traceAllowlist: 'foo,bar', cacheSkSL: true, - initializePlatform: true, ), ); @@ -325,7 +323,7 @@ class FakeDesktopDevice extends DesktopDevice { } } -class FakeAppplicationPackage extends Fake implements ApplicationPackage {} +class FakeApplicationPackage extends Fake implements ApplicationPackage {} class FakeOperatingSystemUtils extends Fake implements OperatingSystemUtils { @override String get name => 'Example'; diff --git a/packages/flutter_tools/test/general.shard/drive/drive_service_test.dart b/packages/flutter_tools/test/general.shard/drive/drive_service_test.dart new file mode 100644 index 00000000000..9aa02a5f065 --- /dev/null +++ b/packages/flutter_tools/test/general.shard/drive/drive_service_test.dart @@ -0,0 +1,380 @@ +// 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 'package:file/file.dart'; +import 'package:file/memory.dart'; +import 'package:flutter_tools/src/application_package.dart'; +import 'package:flutter_tools/src/base/dds.dart'; +import 'package:flutter_tools/src/base/logger.dart'; +import 'package:flutter_tools/src/base/process.dart'; +import 'package:flutter_tools/src/build_info.dart'; +import 'package:flutter_tools/src/convert.dart'; +import 'package:flutter_tools/src/device.dart'; +import 'package:flutter_tools/src/drive/drive_service.dart'; +import 'package:flutter_tools/src/vmservice.dart'; +import 'package:mockito/mockito.dart'; +import 'package:process/process.dart'; +import 'package:vm_service/vm_service.dart' as vm_service; + +import '../../src/common.dart'; +import '../../src/context.dart'; + + +final vm_service.Isolate fakeUnpausedIsolate = vm_service.Isolate( + id: '1', + pauseEvent: vm_service.Event( + kind: vm_service.EventKind.kResume, + timestamp: 0 + ), + breakpoints: [], + exceptionPauseMode: null, + libraries: [ + vm_service.LibraryRef( + id: '1', + uri: 'file:///hello_world/main.dart', + name: '', + ), + ], + livePorts: 0, + name: 'test', + number: '1', + pauseOnExit: false, + runnable: true, + startTime: 0, +); + +final vm_service.Isolate fakePausedIsolate = vm_service.Isolate( + id: '1', + pauseEvent: vm_service.Event( + kind: vm_service.EventKind.kPauseException, + timestamp: 0 + ), + breakpoints: [ + vm_service.Breakpoint( + breakpointNumber: 123, + id: 'test-breakpoint', + location: vm_service.SourceLocation( + tokenPos: 0, + script: vm_service.ScriptRef(id: 'test-script', uri: 'foo.dart'), + ), + resolved: true, + ), + ], + exceptionPauseMode: null, + libraries: [], + livePorts: 0, + name: 'test', + number: '1', + pauseOnExit: false, + runnable: true, + startTime: 0, +); + +final vm_service.VM fakeVM = vm_service.VM( + isolates: [fakeUnpausedIsolate], + pid: 1, + hostCPU: '', + isolateGroups: [], + targetCPU: '', + startTime: 0, + name: 'dart', + architectureBits: 64, + operatingSystem: '', + version: '', +); + +final FlutterView fakeFlutterView = FlutterView( + id: 'a', + uiIsolate: fakeUnpausedIsolate, +); + +final FakeVmServiceRequest listViews = FakeVmServiceRequest( + method: kListViewsMethod, + jsonResponse: { + 'views': [ + fakeFlutterView.toJson(), + ], + }, +); + +final FakeVmServiceRequest getVM = FakeVmServiceRequest( + method: 'getVM', + args: {}, + jsonResponse: fakeVM.toJson(), +); + +void main() { + testWithoutContext('Exits if device fails to start', () { + final DriverService driverService = setUpDriverService(); + final Device device = FakeDevice(LaunchResult.failed()); + + expect( + () => driverService.start(BuildInfo.profile, device, DebuggingOptions.enabled(BuildInfo.profile), true), + throwsToolExit(message: 'Application failed to start. Will not run test. Quitting.'), + ); + }); + + testWithoutContext('Retries application launch if it fails the first time', () async { + final FakeVmServiceHost fakeVmServiceHost = FakeVmServiceHost(requests: [ + getVM, + ]); + final FakeProcessManager processManager = FakeProcessManager.list([ + const FakeCommand( + command: ['dart', '--enable-experiment=non-nullable', 'foo.test', '-rexpanded'], + exitCode: 23, + environment: { + 'FOO': 'BAR', + 'VM_SERVICE_URL': 'http://127.0.0.1:1234/' // dds forwarded URI + }, + ), + ]); + final DriverService driverService = setUpDriverService(processManager: processManager, vmService: fakeVmServiceHost.vmService); + final Device device = FakeDevice(LaunchResult.succeeded( + observatoryUri: Uri.parse('http://127.0.0.1:63426/1UasC_ihpXY=/'), + ))..failOnce = true; + + await expectLater( + () async => await driverService.start(BuildInfo.profile, device, DebuggingOptions.enabled(BuildInfo.profile), true), + returnsNormally, + ); + }); + + testWithoutContext('Connects to device VM Service and runs test application', () async { + final FakeVmServiceHost fakeVmServiceHost = FakeVmServiceHost(requests: [ + getVM, + ]); + final FakeProcessManager processManager = FakeProcessManager.list([ + const FakeCommand( + command: ['dart', '--enable-experiment=non-nullable', 'foo.test', '-rexpanded'], + exitCode: 23, + environment: { + 'FOO': 'BAR', + 'VM_SERVICE_URL': 'http://127.0.0.1:1234/' // dds forwarded URI + }, + ), + ]); + final DriverService driverService = setUpDriverService(processManager: processManager, vmService: fakeVmServiceHost.vmService); + final Device device = FakeDevice(LaunchResult.succeeded( + observatoryUri: Uri.parse('http://127.0.0.1:63426/1UasC_ihpXY=/'), + )); + + await driverService.start(BuildInfo.profile, device, DebuggingOptions.enabled(BuildInfo.profile), true); + final int testResult = await driverService.startTest( + 'foo.test', + ['--enable-experiment=non-nullable'], + {'FOO': 'BAR'}, + ); + + expect(testResult, 23); + }); + + testWithoutContext('Connects to device VM Service and runs test application without dds', () async { + final FakeVmServiceHost fakeVmServiceHost = FakeVmServiceHost(requests: [ + getVM, + ]); + final FakeProcessManager processManager = FakeProcessManager.list([ + const FakeCommand( + command: ['dart', 'foo.test', '-rexpanded'], + exitCode: 11, + environment: { + 'VM_SERVICE_URL': 'http://127.0.0.1:1234/' + }, + ), + ]); + final DriverService driverService = setUpDriverService(processManager: processManager, vmService: fakeVmServiceHost.vmService); + final Device device = FakeDevice(LaunchResult.succeeded( + observatoryUri: Uri.parse('http://127.0.0.1:63426/1UasC_ihpXY=/'), + )); + + await driverService.start(BuildInfo.profile, device, DebuggingOptions.enabled(BuildInfo.profile, disableDds: true), true); + final int testResult = await driverService.startTest( + 'foo.test', + [], + {}, + ); + + expect(testResult, 11); + }); + + testWithoutContext('Safely stops and uninstalls application', () async { + final FakeVmServiceHost fakeVmServiceHost = FakeVmServiceHost(requests: [ + getVM, + ]); + final FakeProcessManager processManager = FakeProcessManager.list([]); + final DriverService driverService = setUpDriverService(processManager: processManager, vmService: fakeVmServiceHost.vmService); + final FakeDevice device = FakeDevice(LaunchResult.succeeded( + observatoryUri: Uri.parse('http://127.0.0.1:63426/1UasC_ihpXY=/'), + )); + + await driverService.start(BuildInfo.profile, device, DebuggingOptions.enabled(BuildInfo.profile), true); + await driverService.stop(); + + expect(device.didStopApp, true); + expect(device.didUninstallApp, true); + expect(device.didDispose, true); + }); + + // FlutterVersion requires context. + testUsingContext('Writes SkSL to file when provided with out file', () async { + final MemoryFileSystem fileSystem = MemoryFileSystem.test(); + final FakeVmServiceHost fakeVmServiceHost = FakeVmServiceHost(requests: [ + getVM, + listViews, + const FakeVmServiceRequest( + method: '_flutter.getSkSLs', + args: { + 'viewId': 'a' + }, + jsonResponse: { + 'SkSLs': { + 'A': 'B', + } + } + ), + ]); + final FakeProcessManager processManager = FakeProcessManager.list([]); + final DriverService driverService = setUpDriverService(processManager: processManager, vmService: fakeVmServiceHost.vmService); + final FakeDevice device = FakeDevice(LaunchResult.succeeded( + observatoryUri: Uri.parse('http://127.0.0.1:63426/1UasC_ihpXY=/'), + )); + + await driverService.start(BuildInfo.profile, device, DebuggingOptions.enabled(BuildInfo.profile), true); + await driverService.stop(writeSkslOnExit: fileSystem.file('out.json')); + + expect(device.didStopApp, true); + expect(device.didUninstallApp, true); + expect(json.decode(fileSystem.file('out.json').readAsStringSync()), { + 'platform': 'android', + 'name': 'test', + 'engineRevision': null, + 'data': {'A': 'B'} + }); + }); +} + +FlutterDriverService setUpDriverService({ + Logger logger, + ProcessManager processManager, + vm_service.VmService vmService, +}) { + logger ??= BufferLogger.test(); + return FlutterDriverService( + applicationPackageFactory: FakeApplicationPackageFactory(FakeApplicationPackage()), + logger: logger, + processUtils: ProcessUtils( + logger: logger, + processManager: processManager ?? FakeProcessManager.any(), + ), + dartSdkPath: 'dart', + vmServiceConnector: (Uri httpUri, { + ReloadSources reloadSources, + Restart restart, + CompileExpression compileExpression, + GetSkSLMethod getSkSLMethod, + PrintStructuredErrorLogMethod printStructuredErrorLogMethod, + Object compression, + Device device, + }) async { + return vmService; + } + ); +} + +class FakeApplicationPackageFactory extends Fake implements ApplicationPackageFactory { + FakeApplicationPackageFactory(this.applicationPackage); + + ApplicationPackage applicationPackage; + + @override + Future getPackageForPlatform( + TargetPlatform platform, { + BuildInfo buildInfo, + File applicationBinary, + }) async => applicationPackage; +} + +class FakeApplicationPackage extends Fake implements ApplicationPackage {} + +class FakeDevice extends Fake implements Device { + FakeDevice(this.result); + + LaunchResult result; + bool didStopApp = false; + bool didUninstallApp = false; + bool didDispose = false; + bool failOnce = false; + + @override + String get name => 'test'; + + @override + final DartDevelopmentService dds = FakeDartDevelopmentService(); + + @override + Future get targetPlatform async => TargetPlatform.android_arm; + + @override + Future getLogReader({ + covariant ApplicationPackage app, + bool includePastLogs = false, + }) async => NoOpDeviceLogReader('test'); + + @override + Future startApp( + covariant ApplicationPackage package, { + String mainPath, + String route, + DebuggingOptions debuggingOptions, + Map platformArgs, + bool prebuiltApplication = false, + bool ipv6 = false, + String userIdentifier, + }) async { + if (failOnce) { + failOnce = false; + return LaunchResult.failed(); + } + return result; + } + + @override + Future stopApp(covariant ApplicationPackage app, {String userIdentifier}) async { + didStopApp = true; + return true; + } + + @override + Future uninstallApp(covariant ApplicationPackage app, {String userIdentifier}) async { + didUninstallApp = true; + return true; + } + + @override + Future dispose() async { + didDispose = true; + } +} + +class FakeDartDevelopmentService extends Fake implements DartDevelopmentService { + bool started = false; + bool disposed = false; + + @override + final Uri uri = Uri.parse('http://127.0.0.1:1234/'); + + @override + Future startDartDevelopmentService( + Uri observatoryUri, + int hostPort, + bool ipv6, + bool disableServiceAuthCodes, + ) async { + started = true; + } + + @override + Future shutdown() async { + disposed = true; + } +} diff --git a/packages/flutter_tools/test/general.shard/drive/web_driver_service_test.dart b/packages/flutter_tools/test/general.shard/drive/web_driver_service_test.dart new file mode 100644 index 00000000000..9b85ab1565f --- /dev/null +++ b/packages/flutter_tools/test/general.shard/drive/web_driver_service_test.dart @@ -0,0 +1,160 @@ +// 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 'package:flutter_tools/src/drive/web_driver_service.dart'; +import 'package:webdriver/sync_io.dart' as sync_io; + +import '../../src/common.dart'; + +void main() { + testWithoutContext('getDesiredCapabilities Chrome with headless on', () { + final Map expected = { + 'acceptInsecureCerts': true, + 'browserName': 'chrome', + 'goog:loggingPrefs': { sync_io.LogType.performance: 'ALL'}, + 'chromeOptions': { + 'w3c': false, + 'args': [ + '--bwsi', + '--disable-background-timer-throttling', + '--disable-default-apps', + '--disable-extensions', + '--disable-popup-blocking', + '--disable-translate', + '--no-default-browser-check', + '--no-sandbox', + '--no-first-run', + '--headless' + ], + 'perfLoggingPrefs': { + 'traceCategories': + 'devtools.timeline,' + 'v8,blink.console,benchmark,blink,' + 'blink.user_timing' + } + } + }; + + expect(getDesiredCapabilities(Browser.chrome, true), expected); + }); + + testWithoutContext('getDesiredCapabilities Chrome with headless off', () { + const String chromeBinary = 'random-binary'; + final Map expected = { + 'acceptInsecureCerts': true, + 'browserName': 'chrome', + 'goog:loggingPrefs': { sync_io.LogType.performance: 'ALL'}, + 'chromeOptions': { + 'binary': chromeBinary, + 'w3c': false, + 'args': [ + '--bwsi', + '--disable-background-timer-throttling', + '--disable-default-apps', + '--disable-extensions', + '--disable-popup-blocking', + '--disable-translate', + '--no-default-browser-check', + '--no-sandbox', + '--no-first-run', + ], + 'perfLoggingPrefs': { + 'traceCategories': + 'devtools.timeline,' + 'v8,blink.console,benchmark,blink,' + 'blink.user_timing' + } + } + }; + + expect(getDesiredCapabilities(Browser.chrome, false, chromeBinary), expected); + + }); + + testWithoutContext('getDesiredCapabilities Firefox with headless on', () { + final Map expected = { + 'acceptInsecureCerts': true, + 'browserName': 'firefox', + 'moz:firefoxOptions' : { + 'args': ['-headless'], + 'prefs': { + 'dom.file.createInChild': true, + 'dom.timeout.background_throttling_max_budget': -1, + 'media.autoplay.default': 0, + 'media.gmp-manager.url': '', + 'media.gmp-provider.enabled': false, + 'network.captive-portal-service.enabled': false, + 'security.insecure_field_warning.contextual.enabled': false, + 'test.currentTimeOffsetSeconds': 11491200 + }, + 'log': {'level': 'trace'} + } + }; + + expect(getDesiredCapabilities(Browser.firefox, true), expected); + }); + + testWithoutContext('getDesiredCapabilities Firefox with headless off', () { + final Map expected = { + 'acceptInsecureCerts': true, + 'browserName': 'firefox', + 'moz:firefoxOptions' : { + 'args': [], + 'prefs': { + 'dom.file.createInChild': true, + 'dom.timeout.background_throttling_max_budget': -1, + 'media.autoplay.default': 0, + 'media.gmp-manager.url': '', + 'media.gmp-provider.enabled': false, + 'network.captive-portal-service.enabled': false, + 'security.insecure_field_warning.contextual.enabled': false, + 'test.currentTimeOffsetSeconds': 11491200 + }, + 'log': {'level': 'trace'} + } + }; + + expect(getDesiredCapabilities(Browser.firefox, false), expected); + }); + + testWithoutContext('getDesiredCapabilities Edge', () { + final Map expected = { + 'acceptInsecureCerts': true, + 'browserName': 'edge', + }; + + expect(getDesiredCapabilities(Browser.edge, false), expected); + }); + + testWithoutContext('getDesiredCapabilities macOS Safari', () { + final Map expected = { + 'browserName': 'safari', + }; + + expect(getDesiredCapabilities(Browser.safari, false), expected); + }); + + testWithoutContext('getDesiredCapabilities iOS Safari', () { + final Map expected = { + 'platformName': 'ios', + 'browserName': 'safari', + 'safari:useSimulator': true + }; + + expect(getDesiredCapabilities(Browser.iosSafari, false), expected); + }); + + testWithoutContext('getDesiredCapabilities android chrome', () { + final Map expected = { + 'browserName': 'chrome', + 'platformName': 'android', + 'goog:chromeOptions': { + 'androidPackage': 'com.android.chrome', + 'args': ['--disable-fullscreen'] + }, + }; + + expect(getDesiredCapabilities(Browser.androidChrome, false), expected); + }); +} diff --git a/packages/flutter_tools/test/general.shard/resident_runner_test.dart b/packages/flutter_tools/test/general.shard/resident_runner_test.dart index 96891cfcb07..3e5e08ad900 100644 --- a/packages/flutter_tools/test/general.shard/resident_runner_test.dart +++ b/packages/flutter_tools/test/general.shard/resident_runner_test.dart @@ -24,7 +24,6 @@ import 'package:flutter_tools/src/convert.dart'; import 'package:flutter_tools/src/devfs.dart'; import 'package:flutter_tools/src/device.dart'; import 'package:flutter_tools/src/globals.dart' as globals; -import 'package:flutter_tools/src/project.dart'; import 'package:flutter_tools/src/reporting/reporting.dart'; import 'package:flutter_tools/src/resident_runner.dart'; import 'package:flutter_tools/src/run_cold.dart'; @@ -2386,7 +2385,6 @@ void main() { treeShakeIcons: false, nullSafetyMode: NullSafetyMode.unsound, ), - flutterProject: FlutterProject.current(), target: null, platform: FakePlatform(operatingSystem: 'linux'), )).generator as DefaultResidentCompiler; @@ -2421,7 +2419,6 @@ void main() { treeShakeIcons: false, extraFrontEndOptions: ['--enable-experiment=non-nullable'], ), - flutterProject: FlutterProject.current(), target: null, platform: FakePlatform(operatingSystem: 'linux'), )).generator as DefaultResidentCompiler; @@ -2456,7 +2453,6 @@ void main() { treeShakeIcons: false, extraFrontEndOptions: [], ), - flutterProject: FlutterProject.current(), target: null, platform: null, )).generator as DefaultResidentCompiler; diff --git a/packages/flutter_tools/test/integration.shard/analyze_size_test.dart b/packages/flutter_tools/test/integration.shard/analyze_size_test.dart index 824cf240c61..702d93316d3 100644 --- a/packages/flutter_tools/test/integration.shard/analyze_size_test.dart +++ b/packages/flutter_tools/test/integration.shard/analyze_size_test.dart @@ -4,7 +4,6 @@ import 'package:file_testing/file_testing.dart'; import 'package:flutter_tools/src/base/io.dart'; -import 'package:flutter_tools/src/base/platform.dart'; import '../src/common.dart'; import 'test_utils.dart'; @@ -61,7 +60,7 @@ void main() { final String outputFilePath = line.split(iosDebugMessage).last.trim(); expect(fileSystem.file(fileSystem.path.join(woringDirectory, outputFilePath)), exists); expect(result.exitCode, 0); - }, skip: !const LocalPlatform().isMacOS); // Only supported on macOS + }, skip: true); // Extremely flaky due to https://github.com/flutter/flutter/issues/68144 testWithoutContext('--analyze-size is only supported in release mode', () async { final String flutterBin = fileSystem.path.join(getFlutterRoot(), 'bin', 'flutter');