From 8474f41e90c67dfffcbfbc6ba0edc3692b8d1846 Mon Sep 17 00:00:00 2001 From: Jenn Magder Date: Tue, 19 Oct 2021 12:23:03 -0700 Subject: [PATCH] Migrate xcdevice and ios devices to null safety (#92056) --- packages/flutter_tools/lib/src/artifacts.dart | 2 - packages/flutter_tools/lib/src/cache.dart | 1 - packages/flutter_tools/lib/src/globals.dart | 3 - .../lib/src/globals_null_migrated.dart | 2 + .../flutter_tools/lib/src/ios/devices.dart | 215 +++++++++--------- packages/flutter_tools/lib/src/ios/mac.dart | 12 +- .../flutter_tools/lib/src/macos/xcdevice.dart | 168 +++++++------- .../test/general.shard/ios/devices_test.dart | 107 ++++----- .../ios/ios_device_project_test.dart | 23 +- .../test/general.shard/macos/xcode_test.dart | 4 +- 10 files changed, 280 insertions(+), 257 deletions(-) diff --git a/packages/flutter_tools/lib/src/artifacts.dart b/packages/flutter_tools/lib/src/artifacts.dart index af99b1f63f..7e348bba87 100644 --- a/packages/flutter_tools/lib/src/artifacts.dart +++ b/packages/flutter_tools/lib/src/artifacts.dart @@ -3,7 +3,6 @@ // found in the LICENSE file. import 'package:file/memory.dart'; -import 'package:meta/meta.dart'; import 'package:process/process.dart'; import 'base/common.dart'; @@ -282,7 +281,6 @@ abstract class Artifacts { /// If a [fileSystem] is not provided, creates a new [MemoryFileSystem] instance. /// /// Creates a [LocalEngineArtifacts] if `localEngine` is non-null - @visibleForTesting factory Artifacts.test({String? localEngine, FileSystem? fileSystem}) { fileSystem ??= MemoryFileSystem.test(); if (localEngine != null) { diff --git a/packages/flutter_tools/lib/src/cache.dart b/packages/flutter_tools/lib/src/cache.dart index e6a39ca39c..254c68eb53 100644 --- a/packages/flutter_tools/lib/src/cache.dart +++ b/packages/flutter_tools/lib/src/cache.dart @@ -127,7 +127,6 @@ class Cache { /// Defaults to a memory file system, fake platform, /// buffer logger, and no accessible artifacts. /// By default, the root cache directory path is "cache". - @visibleForTesting factory Cache.test({ Directory? rootOverride, List? artifacts, diff --git a/packages/flutter_tools/lib/src/globals.dart b/packages/flutter_tools/lib/src/globals.dart index 27d410db3c..1630c460d5 100644 --- a/packages/flutter_tools/lib/src/globals.dart +++ b/packages/flutter_tools/lib/src/globals.dart @@ -7,11 +7,8 @@ import 'base/context.dart'; import 'doctor.dart'; import 'ios/simulators.dart'; -import 'macos/xcdevice.dart'; export 'globals_null_migrated.dart'; Doctor get doctor => context.get(); IOSSimulatorUtils get iosSimulatorUtils => context.get(); - -XCDevice get xcdevice => context.get(); diff --git a/packages/flutter_tools/lib/src/globals_null_migrated.dart b/packages/flutter_tools/lib/src/globals_null_migrated.dart index 85c83d8800..23d688beed 100644 --- a/packages/flutter_tools/lib/src/globals_null_migrated.dart +++ b/packages/flutter_tools/lib/src/globals_null_migrated.dart @@ -34,6 +34,7 @@ import 'ios/plist_parser.dart'; import 'ios/xcodeproj.dart'; import 'macos/cocoapods.dart'; import 'macos/cocoapods_validator.dart'; +import 'macos/xcdevice.dart'; import 'macos/xcode.dart'; import 'persistent_tool_state.dart'; import 'project.dart'; @@ -62,6 +63,7 @@ FlutterVersion get flutterVersion => context.get()!; FuchsiaArtifacts? get fuchsiaArtifacts => context.get(); Usage get flutterUsage => context.get()!; XcodeProjectInterpreter? get xcodeProjectInterpreter => context.get(); +XCDevice? get xcdevice => context.get(); Xcode? get xcode => context.get(); IOSWorkflow? get iosWorkflow => context.get(); LocalEngineLocator? get localEngineLocator => context.get(); diff --git a/packages/flutter_tools/lib/src/ios/devices.dart b/packages/flutter_tools/lib/src/ios/devices.dart index a86b1cd800..67f613a2cb 100644 --- a/packages/flutter_tools/lib/src/ios/devices.dart +++ b/packages/flutter_tools/lib/src/ios/devices.dart @@ -2,8 +2,6 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// @dart = 2.8 - import 'dart:async'; import 'package:meta/meta.dart'; @@ -34,10 +32,10 @@ import 'mac.dart'; class IOSDevices extends PollingDeviceDiscovery { IOSDevices({ - @required Platform platform, - @required XCDevice xcdevice, - @required IOSWorkflow iosWorkflow, - @required Logger logger, + required Platform platform, + required XCDevice xcdevice, + required IOSWorkflow iosWorkflow, + required Logger logger, }) : _platform = platform, _xcdevice = xcdevice, _iosWorkflow = iosWorkflow, @@ -55,7 +53,7 @@ class IOSDevices extends PollingDeviceDiscovery { @override bool get canListAnything => _iosWorkflow.canListDevices; - StreamSubscription> _observedDeviceEventsSubscription; + StreamSubscription>? _observedDeviceEventsSubscription; @override Future startPolling() async { @@ -71,13 +69,13 @@ class IOSDevices extends PollingDeviceDiscovery { deviceNotifier ??= ItemListNotifier(); // Start by populating all currently attached devices. - deviceNotifier.updateWithNewList(await pollingGetDevices()); + deviceNotifier!.updateWithNewList(await pollingGetDevices()); // cancel any outstanding subscriptions. await _observedDeviceEventsSubscription?.cancel(); _observedDeviceEventsSubscription = _xcdevice.observedDeviceEvents()?.listen( _onDeviceEvent, - onError: (dynamic error, StackTrace stack) { + onError: (Object error, StackTrace stack) { _logger.printTrace('Process exception running xcdevice observe:\n$error\n$stack'); }, onDone: () { // If xcdevice is killed or otherwise dies, polling will be stopped. @@ -92,18 +90,26 @@ class IOSDevices extends PollingDeviceDiscovery { Future _onDeviceEvent(Map event) async { final XCDeviceEvent eventType = event.containsKey(XCDeviceEvent.attach) ? XCDeviceEvent.attach : XCDeviceEvent.detach; - final String deviceIdentifier = event[eventType]; - final Device knownDevice = deviceNotifier.items - .firstWhere((Device device) => device.id == deviceIdentifier, orElse: () => null); + final String? deviceIdentifier = event[eventType]; + final ItemListNotifier? notifier = deviceNotifier; + if (notifier == null) { + return; + } + Device? knownDevice; + for (final Device device in notifier.items) { + if (device.id == deviceIdentifier) { + knownDevice = device; + } + } // Ignore already discovered devices (maybe populated at the beginning). if (eventType == XCDeviceEvent.attach && knownDevice == null) { // There's no way to get details for an individual attached device, // so repopulate them all. final List devices = await pollingGetDevices(); - deviceNotifier.updateWithNewList(devices); + notifier.updateWithNewList(devices); } else if (eventType == XCDeviceEvent.detach && knownDevice != null) { - deviceNotifier.removeItem(knownDevice); + notifier.removeItem(knownDevice); } } @@ -113,7 +119,7 @@ class IOSDevices extends PollingDeviceDiscovery { } @override - Future> pollingGetDevices({ Duration timeout }) async { + Future> pollingGetDevices({ Duration? timeout }) async { if (!_platform.isMacOS) { throw UnsupportedError( 'Control of iOS devices or simulators only supported on macOS.' @@ -140,16 +146,16 @@ class IOSDevices extends PollingDeviceDiscovery { class IOSDevice extends Device { IOSDevice(String id, { - @required FileSystem fileSystem, - @required this.name, - @required this.cpuArchitecture, - @required this.interfaceType, - @required String sdkVersion, - @required Platform platform, - @required IOSDeploy iosDeploy, - @required IMobileDevice iMobileDevice, - @required IProxy iProxy, - @required Logger logger, + required FileSystem fileSystem, + required this.name, + required this.cpuArchitecture, + required this.interfaceType, + String? sdkVersion, + required Platform platform, + required IOSDeploy iosDeploy, + required IMobileDevice iMobileDevice, + required IProxy iProxy, + required Logger logger, }) : _sdkVersion = sdkVersion, _iosDeploy = iosDeploy, @@ -170,7 +176,7 @@ class IOSDevice extends Device { } } - final String _sdkVersion; + final String? _sdkVersion; final IOSDeploy _iosDeploy; final FileSystem _fileSystem; final Logger _logger; @@ -180,7 +186,7 @@ class IOSDevice extends Device { /// May be 0 if version cannot be parsed. int get majorSdkVersion { - final String majorVersionString = _sdkVersion?.split('.')?.first?.trim(); + final String? majorVersionString = _sdkVersion?.split('.').first.trim(); return majorVersionString != null ? int.tryParse(majorVersionString) ?? 0 : 0; } @@ -203,18 +209,18 @@ class IOSDevice extends Device { final IOSDeviceConnectionInterface interfaceType; - Map _logReaders; + final Map _logReaders = {}; - DevicePortForwarder _portForwarder; + DevicePortForwarder? _portForwarder; @visibleForTesting - IOSDeployDebugger iosDeployDebugger; + IOSDeployDebugger? iosDeployDebugger; @override Future get isLocalEmulator async => false; @override - Future get emulatorId async => null; + Future get emulatorId async => null; @override bool get supportsStartPaused => false; @@ -222,7 +228,7 @@ class IOSDevice extends Device { @override Future isAppInstalled( IOSApp app, { - String userIdentifier, + String? userIdentifier, }) async { bool result; try { @@ -243,7 +249,7 @@ class IOSDevice extends Device { @override Future installApp( IOSApp app, { - String userIdentifier, + String? userIdentifier, }) async { final Directory bundle = _fileSystem.directory(app.deviceBundlePath); if (!bundle.existsSync()) { @@ -277,7 +283,7 @@ class IOSDevice extends Device { @override Future uninstallApp( IOSApp app, { - String userIdentifier, + String? userIdentifier, }) async { int uninstallationResult; try { @@ -302,16 +308,16 @@ class IOSDevice extends Device { @override Future startApp( IOSApp package, { - String mainPath, - String route, - DebuggingOptions debuggingOptions, - Map platformArgs, + String? mainPath, + String? route, + required DebuggingOptions debuggingOptions, + Map platformArgs = const {}, bool prebuiltApplication = false, bool ipv6 = false, - String userIdentifier, - @visibleForTesting Duration discoveryTimeout, + String? userIdentifier, + @visibleForTesting Duration? discoveryTimeout, }) async { - String packageId; + String? packageId; if (!prebuiltApplication) { _logger.printTrace('Building ${package.name} for $id'); @@ -321,7 +327,6 @@ class IOSDevice extends Device { app: package as BuildableIOSApp, buildInfo: debuggingOptions.buildInfo, targetOverride: mainPath, - environmentType: EnvironmentType.physical, activeArch: cpuArchitecture, deviceID: id, ); @@ -366,14 +371,14 @@ class IOSDevice extends Device { if (debuggingOptions.verboseSystemLogs) '--verbose-logging', if (debuggingOptions.cacheSkSL) '--cache-sksl', if (debuggingOptions.purgePersistentCache) '--purge-persistent-cache', - if (platformArgs['trace-startup'] as bool ?? false) '--trace-startup', + if (platformArgs['trace-startup'] as bool? ?? false) '--trace-startup', ]; final Status installStatus = _logger.startProgress( 'Installing and launching...', ); try { - ProtocolDiscovery observatoryDiscovery; + ProtocolDiscovery? observatoryDiscovery; int installationResult = 1; if (debuggingOptions.debuggingEnabled) { _logger.printTrace('Debugging is enabled, connecting to observatory'); @@ -411,7 +416,7 @@ class IOSDevice extends Device { interfaceType: interfaceType, ); } else { - installationResult = await iosDeployDebugger.launchAndAttach() ? 0 : 1; + installationResult = await iosDeployDebugger!.launchAndAttach() ? 0 : 1; } if (installationResult != 0) { _logger.printError('Could not run ${bundle.path} on $id.'); @@ -429,7 +434,7 @@ class IOSDevice extends Device { final Timer timer = Timer(discoveryTimeout ?? const Duration(seconds: 30), () { _logger.printError('iOS Observatory not discovered after 30 seconds. This is taking much longer than expected...'); }); - final Uri localUri = await observatoryDiscovery.uri; + final Uri? localUri = await observatoryDiscovery?.uri; timer.cancel(); if (localUri == null) { iosDeployDebugger?.detach(); @@ -448,12 +453,12 @@ class IOSDevice extends Device { @override Future stopApp( IOSApp app, { - String userIdentifier, + String? userIdentifier, }) async { // If the debugger is not attached, killing the ios-deploy process won't stop the app. - if (iosDeployDebugger!= null && iosDeployDebugger.debuggerAttached) { - // Avoid null. - return iosDeployDebugger?.exit() == true; + final IOSDeployDebugger? deployDebugger = iosDeployDebugger; + if (deployDebugger != null && deployDebugger.debuggerAttached) { + return deployDebugger.exit() == true; } return false; } @@ -466,11 +471,10 @@ class IOSDevice extends Device { @override DeviceLogReader getLogReader({ - IOSApp app, + IOSApp? app, bool includePastLogs = false, }) { assert(!includePastLogs, 'Past log reading not supported on iOS devices.'); - _logReaders ??= {}; return _logReaders.putIfAbsent(app, () => IOSDeviceLogReader.create( device: this, app: app, @@ -480,7 +484,6 @@ class IOSDevice extends Device { @visibleForTesting void setLogReader(IOSApp app, DeviceLogReader logReader) { - _logReaders ??= {}; _logReaders[app] = logReader; } @@ -515,9 +518,10 @@ class IOSDevice extends Device { @override Future dispose() async { - _logReaders?.forEach((IOSApp application, DeviceLogReader logReader) { + for (final DeviceLogReader logReader in _logReaders.values) { logReader.dispose(); - }); + } + _logReaders.clear(); await _portForwarder?.dispose(); } } @@ -590,31 +594,19 @@ class IOSDeviceLogReader extends DeviceLogReader { this._deviceId, this.name, String appName, - ) { - _linesController = StreamController.broadcast( - onListen: _listenToSysLog, - onCancel: dispose, - ); - - // Match for lines for the runner in syslog. - // - // iOS 9 format: Runner[297] : - // iOS 10 format: Runner(Flutter)[297] : - _runnerLineRegex = RegExp(appName + r'(\(Flutter\))?\[[\d]+\] <[A-Za-z]+>: '); - // Similar to above, but allows ~arbitrary components instead of "Runner" - // and "Flutter". The regex tries to strike a balance between not producing - // false positives and not producing false negatives. - _anyLineRegex = RegExp(r'\w+(\([^)]*\))?\[\d+\] <[A-Za-z]+>: '); - _loggingSubscriptions = >[]; - } + ) : // Match for lines for the runner in syslog. + // + // iOS 9 format: Runner[297] : + // iOS 10 format: Runner(Flutter)[297] : + _runnerLineRegex = RegExp(appName + r'(\(Flutter\))?\[[\d]+\] <[A-Za-z]+>: '); /// Create a new [IOSDeviceLogReader]. factory IOSDeviceLogReader.create({ - @required IOSDevice device, - @required IOSApp app, - @required IMobileDevice iMobileDevice, + required IOSDevice device, + IOSApp? app, + required IMobileDevice iMobileDevice, }) { - final String appName = app == null ? '' : app.name.replaceAll('.app', ''); + final String appName = app?.name?.replaceAll('.app', '') ?? ''; return IOSDeviceLogReader._( iMobileDevice, device.majorSdkVersion, @@ -626,7 +618,7 @@ class IOSDeviceLogReader extends DeviceLogReader { /// Create an [IOSDeviceLogReader] for testing. factory IOSDeviceLogReader.test({ - @required IMobileDevice iMobileDevice, + required IMobileDevice iMobileDevice, bool useSyslog = true, }) { return IOSDeviceLogReader._( @@ -641,8 +633,11 @@ class IOSDeviceLogReader extends DeviceLogReader { // Matches a syslog line from the runner. RegExp _runnerLineRegex; - // Matches a syslog line from any app. - RegExp _anyLineRegex; + + // Similar to above, but allows ~arbitrary components instead of "Runner" + // and "Flutter". The regex tries to strike a balance between not producing + // false positives and not producing false negatives. + final RegExp _anyLineRegex = RegExp(r'\w+(\([^)]*\))?\[\d+\] <[A-Za-z]+>: '); // Logging from native code/Flutter engine is prefixed by timestamp and process metadata: // 2020-09-15 19:15:10.931434-0700 Runner[541:226276] Did finish launching. @@ -651,19 +646,24 @@ class IOSDeviceLogReader extends DeviceLogReader { // Logging from the dart code has no prefixing metadata. final RegExp _debuggerLoggingRegex = RegExp(r'^\S* \S* \S*\[[0-9:]*] (.*)'); - StreamController _linesController; - List> _loggingSubscriptions; + late final StreamController _linesController = StreamController.broadcast( + onListen: _listenToSysLog, + onCancel: dispose, + ); + final List> _loggingSubscriptions = >[]; @override Stream get logLines => _linesController.stream; @override - FlutterVmService get connectedVMService => _connectedVMService; - FlutterVmService _connectedVMService; + FlutterVmService? get connectedVMService => _connectedVMService; + FlutterVmService? _connectedVMService; @override - set connectedVMService(FlutterVmService connectedVmService) { - _listenToUnifiedLoggingEvents(connectedVmService); + set connectedVMService(FlutterVmService? connectedVmService) { + if (connectedVmService != null) { + _listenToUnifiedLoggingEvents(connectedVmService); + } _connectedVMService = connectedVmService; } @@ -687,7 +687,7 @@ class IOSDeviceLogReader extends DeviceLogReader { } void logMessage(vm_service.Event event) { - if (_iosDeployDebugger != null && _iosDeployDebugger.debuggerAttached) { + if (_iosDeployDebugger != null && _iosDeployDebugger!.debuggerAttached) { // Prefer the more complete logs from the attached debugger. return; } @@ -704,13 +704,16 @@ class IOSDeviceLogReader extends DeviceLogReader { } /// Log reader will listen to [debugger.logLines] and will detach debugger on dispose. - IOSDeployDebugger get debuggerStream => _iosDeployDebugger; - set debuggerStream(IOSDeployDebugger debugger) { + IOSDeployDebugger? get debuggerStream => _iosDeployDebugger; + set debuggerStream(IOSDeployDebugger? debugger) { // Logging is gathered from syslog on iOS 13 and earlier. if (_majorSdkVersion < minimumUniversalLoggingSdkVersion) { return; } _iosDeployDebugger = debugger; + if (debugger == null) { + return; + } // Add the debugger logs to the controller created on initialization. _loggingSubscriptions.add(debugger.logLines.listen( (String line) => _linesController.add(_debuggerLineHandler(line)), @@ -719,10 +722,10 @@ class IOSDeviceLogReader extends DeviceLogReader { cancelOnError: true, )); } - IOSDeployDebugger _iosDeployDebugger; + IOSDeployDebugger? _iosDeployDebugger; // Strip off the logging metadata (leave the category), or just echo the line. - String _debuggerLineHandler(String line) => _debuggerLoggingRegex?.firstMatch(line)?.group(1) ?? line; + String _debuggerLineHandler(String line) => _debuggerLoggingRegex.firstMatch(line)?.group(1) ?? line; void _listenToSysLog() { // syslog is not written on iOS 13+. @@ -743,7 +746,7 @@ class IOSDeviceLogReader extends DeviceLogReader { } @visibleForTesting - Process idevicesyslogProcess; + Process? idevicesyslogProcess; // Returns a stateful line handler to properly capture multiline output. // @@ -764,7 +767,7 @@ class IOSDeviceLogReader extends DeviceLogReader { printing = false; } - final Match match = _runnerLineRegex.firstMatch(line); + final Match? match = _runnerLineRegex.firstMatch(line); if (match != null) { final String logLine = line.substring(match.end); @@ -791,10 +794,10 @@ class IOSDevicePortForwarder extends DevicePortForwarder { /// Create a new [IOSDevicePortForwarder]. IOSDevicePortForwarder({ - @required Logger logger, - @required String id, - @required IProxy iproxy, - @required OperatingSystemUtils operatingSystemUtils, + required Logger logger, + required String id, + required IProxy iproxy, + required OperatingSystemUtils operatingSystemUtils, }) : _logger = logger, _id = id, _iproxy = iproxy, @@ -807,10 +810,10 @@ class IOSDevicePortForwarder extends DevicePortForwarder { /// /// The device id may be provided, but otherwise defaults to '1234'. factory IOSDevicePortForwarder.test({ - @required ProcessManager processManager, - @required Logger logger, - String id, - OperatingSystemUtils operatingSystemUtils, + required ProcessManager processManager, + required Logger logger, + String? id, + required OperatingSystemUtils operatingSystemUtils, }) { return IOSDevicePortForwarder( logger: logger, @@ -839,20 +842,20 @@ class IOSDevicePortForwarder extends DevicePortForwarder { static const Duration _kiProxyPortForwardTimeout = Duration(seconds: 1); @override - Future forward(int devicePort, { int hostPort }) async { + Future forward(int devicePort, { int? hostPort }) async { final bool autoselect = hostPort == null || hostPort == 0; if (autoselect) { - final int freePort = await _operatingSystemUtils?.findFreePort(); + final int freePort = await _operatingSystemUtils.findFreePort(); // Dynamic port range 49152 - 65535. - hostPort = freePort == null || freePort == 0 ? 49152 : freePort; + hostPort = freePort == 0 ? 49152 : freePort; } - Process process; + Process? process; bool connected = false; while (!connected) { _logger.printTrace('Attempting to forward device port $devicePort to host port $hostPort'); - process = await _iproxy.forward(devicePort, hostPort, _id); + process = await _iproxy.forward(devicePort, hostPort!, _id); // TODO(ianh): This is a flaky race condition, https://github.com/libimobiledevice/libimobiledevice/issues/674 connected = !await process.stdout.isEmpty.timeout(_kiProxyPortForwardTimeout, onTimeout: () => false); if (!connected) { @@ -871,7 +874,7 @@ class IOSDevicePortForwarder extends DevicePortForwarder { assert(process != null); final ForwardedPort forwardedPort = ForwardedPort.withContext( - hostPort, devicePort, process, + hostPort!, devicePort, process, ); _logger.printTrace('Forwarded port $forwardedPort'); forwardedPorts.add(forwardedPort); diff --git a/packages/flutter_tools/lib/src/ios/mac.dart b/packages/flutter_tools/lib/src/ios/mac.dart index 152b6c4935..c2f2ccad5a 100644 --- a/packages/flutter_tools/lib/src/ios/mac.dart +++ b/packages/flutter_tools/lib/src/ios/mac.dart @@ -45,6 +45,16 @@ class IMobileDevice { _processUtils = ProcessUtils(logger: logger, processManager: processManager), _processManager = processManager; + /// Create an [IMobileDevice] for testing. + factory IMobileDevice.test({ required ProcessManager processManager }) { + return IMobileDevice( + artifacts: Artifacts.test(), + cache: Cache.test(processManager: processManager), + processManager: processManager, + logger: BufferLogger.test(), + ); + } + final String _idevicesyslogPath; final String _idevicescreenshotPath; final MapEntry _dyLdLibEntry; @@ -93,7 +103,7 @@ class IMobileDevice { Future buildXcodeProject({ required BuildableIOSApp app, required BuildInfo buildInfo, - required String targetOverride, + String? targetOverride, EnvironmentType environmentType = EnvironmentType.physical, DarwinArch? activeArch, bool codesign = true, diff --git a/packages/flutter_tools/lib/src/macos/xcdevice.dart b/packages/flutter_tools/lib/src/macos/xcdevice.dart index 00f8c67b3c..dcd9d588ed 100644 --- a/packages/flutter_tools/lib/src/macos/xcdevice.dart +++ b/packages/flutter_tools/lib/src/macos/xcdevice.dart @@ -2,11 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// @dart = 2.8 - import 'dart:async'; -import 'package:meta/meta.dart'; import 'package:process/process.dart'; import '../artifacts.dart'; @@ -34,13 +31,13 @@ enum XCDeviceEvent { /// A utility class for interacting with Xcode xcdevice command line tools. class XCDevice { XCDevice({ - @required Artifacts artifacts, - @required Cache cache, - @required ProcessManager processManager, - @required Logger logger, - @required Xcode xcode, - @required Platform platform, - @required IProxy iproxy, + required Artifacts artifacts, + required Cache cache, + required ProcessManager processManager, + required Logger logger, + required Xcode xcode, + required Platform platform, + required IProxy iproxy, }) : _processUtils = ProcessUtils(logger: logger, processManager: processManager), _logger = logger, _iMobileDevice = IMobileDevice( @@ -73,9 +70,9 @@ class XCDevice { final Xcode _xcode; final IProxy _iProxy; - List _cachedListResults; - Process _deviceObservationProcess; - StreamController> _deviceIdentifierByEvent; + List? _cachedListResults; + Process? _deviceObservationProcess; + StreamController>? _deviceIdentifierByEvent; void _setupDeviceIdentifierByEventStream() { // _deviceIdentifierByEvent Should always be available for listeners @@ -88,9 +85,9 @@ class XCDevice { bool get isInstalled => _xcode.isInstalledAndMeetsVersionCheck; - Future> _getAllDevices({ + Future?> _getAllDevices({ bool useCache = false, - @required Duration timeout + required Duration timeout }) async { if (!isInstalled) { _logger.printTrace("Xcode not found. Run 'flutter doctor' for more information."); @@ -114,7 +111,7 @@ class XCDevice { if (result.exitCode == 0) { final String listOutput = result.stdout; try { - final List listResults = json.decode(listOutput) as List; + final List listResults = (json.decode(result.stdout) as List).whereType().toList(); _cachedListResults = listResults; return listResults; } on FormatException { @@ -137,12 +134,12 @@ class XCDevice { /// /// Each attach and detach event is a tuple of one event type /// and identifier. - Stream> observedDeviceEvents() { + Stream>? observedDeviceEvents() { if (!isInstalled) { _logger.printTrace("Xcode not found. Run 'flutter doctor' for more information."); return null; } - return _deviceIdentifierByEvent.stream; + return _deviceIdentifierByEvent?.stream; } // Attach: d83d5bc53967baa0ee18626ba87b6254b2ab5418 @@ -171,7 +168,7 @@ class XCDevice { ], ); - final StreamSubscription stdoutSubscription = _deviceObservationProcess.stdout + final StreamSubscription stdoutSubscription = _deviceObservationProcess!.stdout .transform(utf8.decoder) .transform(const LineSplitter()) .listen((String line) { @@ -183,35 +180,35 @@ class XCDevice { // Attach: 00008027-00192736010F802E // Detach: d83d5bc53967baa0ee18626ba87b6254b2ab5418 // Attach: d83d5bc53967baa0ee18626ba87b6254b2ab5418 - final RegExpMatch match = _observationIdentifierPattern.firstMatch(line); + final RegExpMatch? match = _observationIdentifierPattern.firstMatch(line); if (match != null && match.groupCount == 2) { - final String verb = match.group(1).toLowerCase(); - final String identifier = match.group(2); + final String verb = match.group(1)!.toLowerCase(); + final String identifier = match.group(2)!; if (verb.startsWith('attach')) { - _deviceIdentifierByEvent.add({ + _deviceIdentifierByEvent?.add({ XCDeviceEvent.attach: identifier }); } else if (verb.startsWith('detach')) { - _deviceIdentifierByEvent.add({ + _deviceIdentifierByEvent?.add({ XCDeviceEvent.detach: identifier }); } } }); - final StreamSubscription stderrSubscription = _deviceObservationProcess.stderr + final StreamSubscription stderrSubscription = _deviceObservationProcess!.stderr .transform(utf8.decoder) .transform(const LineSplitter()) .listen((String line) { _logger.printTrace('xcdevice observe error: $line'); }); - unawaited(_deviceObservationProcess.exitCode.then((int status) { + unawaited(_deviceObservationProcess?.exitCode.then((int status) { _logger.printTrace('xcdevice exited with code $exitCode'); unawaited(stdoutSubscription.cancel()); unawaited(stderrSubscription.cancel()); }).whenComplete(() async { - if (_deviceIdentifierByEvent.hasListener) { + if (_deviceIdentifierByEvent?.hasListener == true) { // Tell listeners the process died. - await _deviceIdentifierByEvent.close(); + await _deviceIdentifierByEvent?.close(); } _deviceObservationProcess = null; @@ -219,9 +216,9 @@ class XCDevice { _setupDeviceIdentifierByEventStream(); })); } on ProcessException catch (exception, stackTrace) { - _deviceIdentifierByEvent.addError(exception, stackTrace); + _deviceIdentifierByEvent?.addError(exception, stackTrace); } on ArgumentError catch (exception, stackTrace) { - _deviceIdentifierByEvent.addError(exception, stackTrace); + _deviceIdentifierByEvent?.addError(exception, stackTrace); } } @@ -230,8 +227,8 @@ class XCDevice { } /// [timeout] defaults to 2 seconds. - Future> getAvailableIOSDevices({ Duration timeout }) async { - final List allAvailableDevices = await _getAllDevices(timeout: timeout ?? const Duration(seconds: 2)); + Future> getAvailableIOSDevices({ Duration? timeout }) async { + final List? allAvailableDevices = await _getAllDevices(timeout: timeout ?? const Duration(seconds: 2)); if (allAvailableDevices == null) { return const []; @@ -275,22 +272,29 @@ class XCDevice { // ... final List devices = []; - for (final dynamic device in allAvailableDevices) { - if (device is Map) { + for (final Object device in allAvailableDevices) { + if (device is Map) { // Only include iPhone, iPad, iPod, or other iOS devices. if (!_isIPhoneOSDevice(device)) { continue; } + final String? identifier = device['identifier'] as String?; + final String? name = device['name'] as String?; + if (identifier == null || name == null) { + continue; + } - final Map errorProperties = _errorProperties(device); + final Map? errorProperties = _errorProperties(device); if (errorProperties != null) { - final String errorMessage = _parseErrorMessage(errorProperties); - if (errorMessage.contains('not paired')) { - UsageEvent('device', 'ios-trust-failure', flutterUsage: globals.flutterUsage).send(); + final String? errorMessage = _parseErrorMessage(errorProperties); + if (errorMessage != null) { + if (errorMessage.contains('not paired')) { + UsageEvent('device', 'ios-trust-failure', flutterUsage: globals.flutterUsage).send(); + } + _logger.printTrace(errorMessage); } - _logger.printTrace(errorMessage); - final int code = _errorCode(errorProperties); + final int? code = _errorCode(errorProperties); // Temporary error -10: iPhone is busy: Preparing debugger support for iPhone. // Sometimes the app launch will fail on these devices until Xcode is done setting up the device. @@ -308,18 +312,18 @@ class XCDevice { continue; } - String sdkVersion = _sdkVersion(device); + String? sdkVersion = _sdkVersion(device); if (sdkVersion != null) { - final String buildVersion = _buildVersion(device); + final String? buildVersion = _buildVersion(device); if (buildVersion != null) { sdkVersion = '$sdkVersion $buildVersion'; } } devices.add(IOSDevice( - device['identifier'] as String, - name: device['name'] as String, + identifier, + name: name, cpuArchitecture: _cpuArchitecture(device), interfaceType: interface, sdkVersion: sdkVersion, @@ -338,33 +342,30 @@ class XCDevice { /// Despite the name, com.apple.platform.iphoneos includes iPhone, iPads, and all iOS devices. /// Excludes simulators. - static bool _isIPhoneOSDevice(Map deviceProperties) { - if (deviceProperties.containsKey('platform')) { - final String platform = deviceProperties['platform'] as String; + static bool _isIPhoneOSDevice(Map deviceProperties) { + final Object? platform = deviceProperties['platform']; + if (platform is String) { return platform == 'com.apple.platform.iphoneos'; } return false; } - static Map _errorProperties(Map deviceProperties) { - if (deviceProperties.containsKey('error')) { - return deviceProperties['error'] as Map; - } - return null; + static Map? _errorProperties(Map deviceProperties) { + final Object? error = deviceProperties['error']; + return error is Map ? error : null; } - static int _errorCode(Map errorProperties) { - if (errorProperties.containsKey('code') && errorProperties['code'] is int) { - return errorProperties['code'] as int; - } - return null; + static int? _errorCode(Map errorProperties) { + final Object? code = errorProperties['code']; + return code is int ? code : null; } - static IOSDeviceConnectionInterface _interfaceType(Map deviceProperties) { + static IOSDeviceConnectionInterface _interfaceType(Map deviceProperties) { // Interface can be "usb", "network", or "none" for simulators // and unknown future interfaces. - if (deviceProperties.containsKey('interface')) { - if ((deviceProperties['interface'] as String).toLowerCase() == 'network') { + final Object? interface = deviceProperties['interface']; + if (interface is String) { + if (interface.toLowerCase() == 'network') { return IOSDeviceConnectionInterface.network; } else { return IOSDeviceConnectionInterface.usb; @@ -374,13 +375,13 @@ class XCDevice { return IOSDeviceConnectionInterface.none; } - static String _sdkVersion(Map deviceProperties) { - if (deviceProperties.containsKey('operatingSystemVersion')) { + static String? _sdkVersion(Map deviceProperties) { + final Object? operatingSystemVersion = deviceProperties['operatingSystemVersion']; + if (operatingSystemVersion is String) { // Parse out the OS version, ignore the build number in parentheses. // "13.3 (17C54)" final RegExp operatingSystemRegex = RegExp(r'(.*) \(.*\)$'); - final String operatingSystemVersion = deviceProperties['operatingSystemVersion'] as String; - if(operatingSystemRegex.hasMatch(operatingSystemVersion.trim())) { + if (operatingSystemRegex.hasMatch(operatingSystemVersion.trim())) { return operatingSystemRegex.firstMatch(operatingSystemVersion.trim())?.group(1); } return operatingSystemVersion; @@ -388,20 +389,20 @@ class XCDevice { return null; } - static String _buildVersion(Map deviceProperties) { - if (deviceProperties.containsKey('operatingSystemVersion')) { + static String? _buildVersion(Map deviceProperties) { + final Object? operatingSystemVersion = deviceProperties['operatingSystemVersion']; + if (operatingSystemVersion is String) { // Parse out the build version, for example 17C54 from "13.3 (17C54)". final RegExp buildVersionRegex = RegExp(r'\(.*\)$'); - final String operatingSystemVersion = deviceProperties['operatingSystemVersion'] as String; return buildVersionRegex.firstMatch(operatingSystemVersion)?.group(0)?.replaceAll(RegExp('[()]'), ''); } return null; } - DarwinArch _cpuArchitecture(Map deviceProperties) { - DarwinArch cpuArchitecture; - if (deviceProperties.containsKey('architecture')) { - final String architecture = deviceProperties['architecture'] as String; + DarwinArch _cpuArchitecture(Map deviceProperties) { + DarwinArch? cpuArchitecture; + final Object? architecture = deviceProperties['architecture']; + if (architecture is String) { try { cpuArchitecture = getIOSArchForName(architecture); } on Exception { @@ -420,11 +421,11 @@ class XCDevice { ); } } - return cpuArchitecture; + return cpuArchitecture ?? DarwinArch.arm64; } /// Error message parsed from xcdevice. null if no error. - static String _parseErrorMessage(Map errorProperties) { + static String? _parseErrorMessage(Map? errorProperties) { // { // "simulator" : false, // "operatingSystemVersion" : "13.3 (17C54)", @@ -479,8 +480,8 @@ class XCDevice { final StringBuffer errorMessage = StringBuffer('Error: '); - if (errorProperties.containsKey('description')) { - final String description = errorProperties['description'] as String; + final Object? description = errorProperties['description']; + if (description is String) { errorMessage.write(description); if (!description.endsWith('.')) { errorMessage.write('.'); @@ -489,12 +490,12 @@ class XCDevice { errorMessage.write('Xcode pairing error.'); } - if (errorProperties.containsKey('recoverySuggestion')) { - final String recoverySuggestion = errorProperties['recoverySuggestion'] as String; + final Object? recoverySuggestion = errorProperties['recoverySuggestion']; + if (recoverySuggestion is String) { errorMessage.write(' $recoverySuggestion'); } - final int code = _errorCode(errorProperties); + final int? code = _errorCode(errorProperties); if (code != null) { errorMessage.write(' (code $code)'); } @@ -504,7 +505,7 @@ class XCDevice { /// List of all devices reporting errors. Future> getDiagnostics() async { - final List allAvailableDevices = await _getAllDevices( + final List? allAvailableDevices = await _getAllDevices( useCache: true, timeout: const Duration(seconds: 2) ); @@ -514,13 +515,12 @@ class XCDevice { } final List diagnostics = []; - for (final dynamic device in allAvailableDevices) { - if (device is! Map) { + for (final Object deviceProperties in allAvailableDevices) { + if (deviceProperties is! Map) { continue; } - final Map deviceProperties = device as Map; - final Map errorProperties = _errorProperties(deviceProperties); - final String errorMessage = _parseErrorMessage(errorProperties); + final Map? errorProperties = _errorProperties(deviceProperties); + final String? errorMessage = _parseErrorMessage(errorProperties); if (errorMessage != null) { diagnostics.add(errorMessage); } diff --git a/packages/flutter_tools/test/general.shard/ios/devices_test.dart b/packages/flutter_tools/test/general.shard/ios/devices_test.dart index b956d6e074..2f7d8ce8e7 100644 --- a/packages/flutter_tools/test/general.shard/ios/devices_test.dart +++ b/packages/flutter_tools/test/general.shard/ios/devices_test.dart @@ -2,8 +2,6 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// @dart = 2.8 - import 'dart:async'; import 'dart:io' as io; @@ -39,16 +37,17 @@ void main() { group('IOSDevice', () { final List unsupportedPlatforms = [linuxPlatform, windowsPlatform]; - Cache cache; - Logger logger; - IOSDeploy iosDeploy; - IMobileDevice iMobileDevice; - FileSystem nullFileSystem; + late Cache cache; + late Logger logger; + late IOSDeploy iosDeploy; + late IMobileDevice iMobileDevice; + late FileSystem fileSystem; setUp(() { final Artifacts artifacts = Artifacts.test(); cache = Cache.test(processManager: FakeProcessManager.any()); logger = BufferLogger.test(); + fileSystem = MemoryFileSystem.test(); iosDeploy = IOSDeploy( artifacts: artifacts, cache: cache, @@ -68,7 +67,7 @@ void main() { IOSDevice( 'device-123', iProxy: IProxy.test(logger: logger, processManager: FakeProcessManager.any()), - fileSystem: nullFileSystem, + fileSystem: fileSystem, logger: logger, platform: macPlatform, iosDeploy: iosDeploy, @@ -84,7 +83,7 @@ void main() { expect(IOSDevice( 'device-123', iProxy: IProxy.test(logger: logger, processManager: FakeProcessManager.any()), - fileSystem: nullFileSystem, + fileSystem: fileSystem, logger: logger, platform: macPlatform, iosDeploy: iosDeploy, @@ -97,7 +96,7 @@ void main() { expect(IOSDevice( 'device-123', iProxy: IProxy.test(logger: logger, processManager: FakeProcessManager.any()), - fileSystem: nullFileSystem, + fileSystem: fileSystem, logger: logger, platform: macPlatform, iosDeploy: iosDeploy, @@ -110,7 +109,7 @@ void main() { expect(IOSDevice( 'device-123', iProxy: IProxy.test(logger: logger, processManager: FakeProcessManager.any()), - fileSystem: nullFileSystem, + fileSystem: fileSystem, logger: logger, platform: macPlatform, iosDeploy: iosDeploy, @@ -123,7 +122,7 @@ void main() { expect(IOSDevice( 'device-123', iProxy: IProxy.test(logger: logger, processManager: FakeProcessManager.any()), - fileSystem: nullFileSystem, + fileSystem: fileSystem, logger: logger, platform: macPlatform, iosDeploy: iosDeploy, @@ -136,7 +135,7 @@ void main() { expect(IOSDevice( 'device-123', iProxy: IProxy.test(logger: logger, processManager: FakeProcessManager.any()), - fileSystem: nullFileSystem, + fileSystem: fileSystem, logger: logger, platform: macPlatform, iosDeploy: iosDeploy, @@ -152,7 +151,7 @@ void main() { final IOSDevice device = IOSDevice( 'device-123', iProxy: IProxy.test(logger: logger, processManager: FakeProcessManager.any()), - fileSystem: nullFileSystem, + fileSystem: fileSystem, logger: logger, platform: macPlatform, iosDeploy: iosDeploy, @@ -170,7 +169,7 @@ void main() { final IOSDevice device = IOSDevice( 'device-123', iProxy: IProxy.test(logger: logger, processManager: FakeProcessManager.any()), - fileSystem: nullFileSystem, + fileSystem: fileSystem, logger: logger, platform: macPlatform, iosDeploy: iosDeploy, @@ -194,7 +193,7 @@ void main() { IOSDevice( 'device-123', iProxy: IProxy.test(logger: logger, processManager: FakeProcessManager.any()), - fileSystem: nullFileSystem, + fileSystem: fileSystem, logger: logger, platform: platform, iosDeploy: iosDeploy, @@ -211,21 +210,21 @@ void main() { } group('.dispose()', () { - IOSDevice device; - FakeIOSApp appPackage1; - FakeIOSApp appPackage2; - IOSDeviceLogReader logReader1; - IOSDeviceLogReader logReader2; - FakeProcess process1; - FakeProcess process2; - FakeProcess process3; - IOSDevicePortForwarder portForwarder; - ForwardedPort forwardedPort; - Cache cache; - Logger logger; - IOSDeploy iosDeploy; - FileSystem nullFileSystem; - IProxy iproxy; + late IOSDevice device; + late FakeIOSApp appPackage1; + late FakeIOSApp appPackage2; + late IOSDeviceLogReader logReader1; + late IOSDeviceLogReader logReader2; + late FakeProcess process1; + late FakeProcess process2; + late FakeProcess process3; + late IOSDevicePortForwarder portForwarder; + late ForwardedPort forwardedPort; + late Cache cache; + late Logger logger; + late IOSDeploy iosDeploy; + late FileSystem fileSystem; + late IProxy iproxy; IOSDevicePortForwarder createPortForwarder( ForwardedPort forwardedPort, @@ -235,7 +234,7 @@ void main() { id: device.id, logger: logger, operatingSystemUtils: OperatingSystemUtils( - fileSystem: nullFileSystem, + fileSystem: fileSystem, logger: logger, platform: FakePlatform(operatingSystem: 'macos'), processManager: FakeProcessManager.any(), @@ -253,7 +252,7 @@ void main() { final IOSDeviceLogReader logReader = IOSDeviceLogReader.create( device: device, app: appPackage, - iMobileDevice: null, // not used by this test. + iMobileDevice: IMobileDevice.test(processManager: FakeProcessManager.any()), ); logReader.idevicesyslogProcess = process; return logReader; @@ -269,6 +268,8 @@ void main() { cache = Cache.test( processManager: FakeProcessManager.any(), ); + fileSystem = MemoryFileSystem.test(); + logger = BufferLogger.test(); iosDeploy = IOSDeploy( artifacts: Artifacts.test(), cache: cache, @@ -282,7 +283,7 @@ void main() { device = IOSDevice( '123', iProxy: IProxy.test(logger: logger, processManager: FakeProcessManager.any()), - fileSystem: nullFileSystem, + fileSystem: fileSystem, logger: logger, platform: macPlatform, iosDeploy: iosDeploy, @@ -309,15 +310,15 @@ void main() { }); group('polling', () { - FakeXcdevice xcdevice; - Cache cache; - FakeProcessManager fakeProcessManager; - BufferLogger logger; - IOSDeploy iosDeploy; - IMobileDevice iMobileDevice; - IOSWorkflow iosWorkflow; - IOSDevice device1; - IOSDevice device2; + late FakeXcdevice xcdevice; + late Cache cache; + late FakeProcessManager fakeProcessManager; + late BufferLogger logger; + late IOSDeploy iosDeploy; + late IMobileDevice iMobileDevice; + late IOSWorkflow iosWorkflow; + late IOSDevice device1; + late IOSDevice device2; setUp(() { xcdevice = FakeXcdevice(); @@ -414,22 +415,22 @@ void main() { await iosDevices.startPolling(); expect(xcdevice.getAvailableIOSDevicesCount, 1); - expect(iosDevices.deviceNotifier.items, isEmpty); + expect(iosDevices.deviceNotifier!.items, isEmpty); expect(xcdevice.deviceEventController.hasListener, isTrue); xcdevice.deviceEventController.add({ XCDeviceEvent.attach: 'd83d5bc53967baa0ee18626ba87b6254b2ab5418' }); await added.future; - expect(iosDevices.deviceNotifier.items.length, 2); - expect(iosDevices.deviceNotifier.items, contains(device1)); - expect(iosDevices.deviceNotifier.items, contains(device2)); + expect(iosDevices.deviceNotifier!.items.length, 2); + expect(iosDevices.deviceNotifier!.items, contains(device1)); + expect(iosDevices.deviceNotifier!.items, contains(device2)); xcdevice.deviceEventController.add({ XCDeviceEvent.detach: 'd83d5bc53967baa0ee18626ba87b6254b2ab5418' }); await removed.future; - expect(iosDevices.deviceNotifier.items, [device2]); + expect(iosDevices.deviceNotifier!.items, [device2]); // Remove stream will throw over-completion if called more than once // which proves this is ignored. @@ -489,7 +490,7 @@ void main() { xcdevice.devices.add([]); await iosDevices.startPolling(); - expect(iosDevices.deviceNotifier.items, isEmpty); + expect(iosDevices.deviceNotifier!.items, isEmpty); expect(xcdevice.deviceEventController.hasListener, isTrue); iosDevices.dispose(); @@ -531,9 +532,9 @@ void main() { }); group('getDiagnostics', () { - FakeXcdevice xcdevice; - IOSWorkflow iosWorkflow; - Logger logger; + late FakeXcdevice xcdevice; + late IOSWorkflow iosWorkflow; + late Logger logger; setUp(() { xcdevice = FakeXcdevice(); @@ -601,7 +602,7 @@ class FakeXcdevice extends Fake implements XCDevice { } @override - Future> getAvailableIOSDevices({Duration timeout}) async { + Future> getAvailableIOSDevices({Duration? timeout}) async { return devices[getAvailableIOSDevicesCount++]; } } diff --git a/packages/flutter_tools/test/general.shard/ios/ios_device_project_test.dart b/packages/flutter_tools/test/general.shard/ios/ios_device_project_test.dart index b157f95600..8712e701c4 100644 --- a/packages/flutter_tools/test/general.shard/ios/ios_device_project_test.dart +++ b/packages/flutter_tools/test/general.shard/ios/ios_device_project_test.dart @@ -5,12 +5,16 @@ // @dart = 2.8 import 'package:file/memory.dart'; +import 'package:flutter_tools/src/artifacts.dart'; import 'package:flutter_tools/src/base/file_system.dart'; import 'package:flutter_tools/src/base/logger.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/ios/devices.dart'; +import 'package:flutter_tools/src/ios/ios_deploy.dart'; import 'package:flutter_tools/src/ios/iproxy.dart'; +import 'package:flutter_tools/src/ios/mac.dart'; import 'package:flutter_tools/src/project.dart'; import '../../src/common.dart'; @@ -76,17 +80,26 @@ flutter: } IOSDevice setUpIOSDevice(FileSystem fileSystem) { + final Platform platform = FakePlatform(operatingSystem: 'macos'); + final Logger logger = BufferLogger.test(); + final ProcessManager processManager = FakeProcessManager.any(); return IOSDevice( 'test', fileSystem: fileSystem, - logger: BufferLogger.test(), - iosDeploy: null, // not used in this test - iMobileDevice: null, // not used in this test - platform: FakePlatform(operatingSystem: 'macos'), + logger: logger, + iosDeploy: IOSDeploy( + platform: platform, + logger: logger, + processManager: processManager, + artifacts: Artifacts.test(), + cache: Cache.test(processManager: processManager), + ), + iMobileDevice: IMobileDevice.test(processManager: processManager), + platform: platform, name: 'iPhone 1', sdkVersion: '13.3', cpuArchitecture: DarwinArch.arm64, - iProxy: IProxy.test(logger: BufferLogger.test(), processManager: FakeProcessManager.any()), + iProxy: IProxy.test(logger: logger, processManager: processManager), interfaceType: IOSDeviceConnectionInterface.usb, ); } diff --git a/packages/flutter_tools/test/general.shard/macos/xcode_test.dart b/packages/flutter_tools/test/general.shard/macos/xcode_test.dart index 430ac0709a..9546cc08d0 100644 --- a/packages/flutter_tools/test/general.shard/macos/xcode_test.dart +++ b/packages/flutter_tools/test/general.shard/macos/xcode_test.dart @@ -326,7 +326,7 @@ void main() { processManager: fakeProcessManager, logger: logger, xcode: xcode, - platform: null, + platform: FakePlatform(operatingSystem: 'macos'), artifacts: Artifacts.test(), cache: Cache.test(processManager: FakeProcessManager.any()), iproxy: IProxy.test(logger: logger, processManager: fakeProcessManager), @@ -354,7 +354,7 @@ void main() { processManager: fakeProcessManager, logger: logger, xcode: xcode, - platform: null, + platform: FakePlatform(operatingSystem: 'macos'), artifacts: Artifacts.test(), cache: Cache.test(processManager: FakeProcessManager.any()), iproxy: IProxy.test(logger: logger, processManager: fakeProcessManager),