Unverified Commit 8474f41e authored by Jenn Magder's avatar Jenn Magder Committed by GitHub

Migrate xcdevice and ios devices to null safety (#92056)

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