mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00

Xcode 15 will be required for iOS App Store submission > Please note that as of April 2024 all iOS and iPadOS apps submitted to the App Store must be built with a minimum of Xcode 15 and the iOS 17 SDK. https://developer.apple.com/ios/submit/ And will also be required for Swift Package Manager support https://github.com/flutter/flutter/pull/146256. We could swap to "required" but macOS developers don't technically need to upgrade. We can let the Store itself enforce its policies. And we can swap to Xcode 15 "required" when SPM adoption is further along. Part of https://github.com/flutter/flutter/issues/144582
1726 lines
62 KiB
Dart
1726 lines
62 KiB
Dart
// Copyright 2014 The Flutter Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// found in the LICENSE file.
|
|
|
|
import 'dart:async';
|
|
|
|
import 'package:file/memory.dart';
|
|
import 'package:flutter_tools/src/artifacts.dart';
|
|
import 'package:flutter_tools/src/base/io.dart' show ProcessException;
|
|
import 'package:flutter_tools/src/base/logger.dart';
|
|
import 'package:flutter_tools/src/base/platform.dart';
|
|
import 'package:flutter_tools/src/base/user_messages.dart';
|
|
import 'package:flutter_tools/src/base/version.dart';
|
|
import 'package:flutter_tools/src/build_info.dart';
|
|
import 'package:flutter_tools/src/cache.dart';
|
|
import 'package:flutter_tools/src/device.dart';
|
|
import 'package:flutter_tools/src/ios/core_devices.dart';
|
|
import 'package:flutter_tools/src/ios/devices.dart';
|
|
import 'package:flutter_tools/src/ios/iproxy.dart';
|
|
import 'package:flutter_tools/src/ios/xcode_debug.dart';
|
|
import 'package:flutter_tools/src/ios/xcodeproj.dart';
|
|
import 'package:flutter_tools/src/macos/xcdevice.dart';
|
|
import 'package:flutter_tools/src/macos/xcode.dart';
|
|
import 'package:test/fake.dart';
|
|
import 'package:unified_analytics/unified_analytics.dart';
|
|
|
|
import '../../src/common.dart';
|
|
import '../../src/context.dart';
|
|
import '../../src/fake_process_manager.dart';
|
|
import '../../src/fakes.dart';
|
|
|
|
void main() {
|
|
late BufferLogger logger;
|
|
|
|
setUp(() {
|
|
logger = BufferLogger.test();
|
|
});
|
|
|
|
group('FakeProcessManager', () {
|
|
late FakeProcessManager fakeProcessManager;
|
|
|
|
setUp(() {
|
|
fakeProcessManager = FakeProcessManager.empty();
|
|
});
|
|
|
|
group('Xcode', () {
|
|
late FakeXcodeProjectInterpreter xcodeProjectInterpreter;
|
|
|
|
setUp(() {
|
|
xcodeProjectInterpreter = FakeXcodeProjectInterpreter();
|
|
});
|
|
|
|
testWithoutContext('isInstalledAndMeetsVersionCheck is false when not macOS', () {
|
|
final Xcode xcode = Xcode.test(
|
|
platform: FakePlatform(operatingSystem: 'windows'),
|
|
processManager: fakeProcessManager,
|
|
xcodeProjectInterpreter: xcodeProjectInterpreter,
|
|
);
|
|
|
|
expect(xcode.isInstalledAndMeetsVersionCheck, isFalse);
|
|
});
|
|
|
|
testWithoutContext('isSimctlInstalled is true when simctl list succeeds', () {
|
|
fakeProcessManager.addCommand(
|
|
const FakeCommand(
|
|
command: <String>[
|
|
'xcrun',
|
|
'simctl',
|
|
'list',
|
|
'devices',
|
|
'booted',
|
|
],
|
|
),
|
|
);
|
|
final Xcode xcode = Xcode.test(
|
|
processManager: fakeProcessManager,
|
|
xcodeProjectInterpreter: xcodeProjectInterpreter,
|
|
);
|
|
|
|
expect(xcode.isSimctlInstalled, isTrue);
|
|
expect(fakeProcessManager, hasNoRemainingExpectations);
|
|
});
|
|
|
|
testWithoutContext('isSimctlInstalled is false when simctl list fails', () {
|
|
fakeProcessManager.addCommand(
|
|
const FakeCommand(
|
|
command: <String>[
|
|
'xcrun',
|
|
'simctl',
|
|
'list',
|
|
'devices',
|
|
'booted',
|
|
],
|
|
exitCode: 1,
|
|
),
|
|
);
|
|
final Xcode xcode = Xcode.test(
|
|
processManager: fakeProcessManager,
|
|
xcodeProjectInterpreter: xcodeProjectInterpreter,
|
|
);
|
|
|
|
expect(xcode.isSimctlInstalled, isFalse);
|
|
expect(fakeProcessManager, hasNoRemainingExpectations);
|
|
});
|
|
|
|
group('isDevicectlInstalled', () {
|
|
testWithoutContext('is true when Xcode is 15+ and devicectl succeeds', () {
|
|
fakeProcessManager.addCommand(
|
|
const FakeCommand(
|
|
command: <String>[
|
|
'xcrun',
|
|
'devicectl',
|
|
'--version',
|
|
],
|
|
),
|
|
);
|
|
xcodeProjectInterpreter.version = Version(15, 0, 0);
|
|
final Xcode xcode = Xcode.test(
|
|
processManager: fakeProcessManager,
|
|
xcodeProjectInterpreter: xcodeProjectInterpreter,
|
|
);
|
|
|
|
expect(xcode.isDevicectlInstalled, isTrue);
|
|
expect(fakeProcessManager, hasNoRemainingExpectations);
|
|
});
|
|
|
|
testWithoutContext('is false when devicectl fails', () {
|
|
fakeProcessManager.addCommand(
|
|
const FakeCommand(
|
|
command: <String>[
|
|
'xcrun',
|
|
'devicectl',
|
|
'--version',
|
|
],
|
|
exitCode: 1,
|
|
),
|
|
);
|
|
xcodeProjectInterpreter.version = Version(15, 0, 0);
|
|
final Xcode xcode = Xcode.test(
|
|
processManager: fakeProcessManager,
|
|
xcodeProjectInterpreter: xcodeProjectInterpreter,
|
|
);
|
|
|
|
expect(xcode.isDevicectlInstalled, isFalse);
|
|
expect(fakeProcessManager, hasNoRemainingExpectations);
|
|
});
|
|
|
|
testWithoutContext('is false when Xcode is less than 15', () {
|
|
xcodeProjectInterpreter.version = Version(14, 0, 0);
|
|
final Xcode xcode = Xcode.test(
|
|
processManager: fakeProcessManager,
|
|
xcodeProjectInterpreter: xcodeProjectInterpreter,
|
|
);
|
|
|
|
expect(xcode.isDevicectlInstalled, isFalse);
|
|
expect(fakeProcessManager, hasNoRemainingExpectations);
|
|
});
|
|
});
|
|
|
|
group('pathToXcodeApp', () {
|
|
late UserMessages userMessages;
|
|
|
|
setUp(() {
|
|
userMessages = UserMessages();
|
|
});
|
|
|
|
testWithoutContext('parses correctly', () {
|
|
final Xcode xcode = Xcode.test(
|
|
processManager: fakeProcessManager,
|
|
xcodeProjectInterpreter: xcodeProjectInterpreter,
|
|
);
|
|
|
|
fakeProcessManager.addCommand(const FakeCommand(
|
|
command: <String>['/usr/bin/xcode-select', '--print-path'],
|
|
stdout: '/Applications/Xcode.app/Contents/Developer',
|
|
));
|
|
|
|
expect(xcode.xcodeAppPath, '/Applications/Xcode.app');
|
|
expect(fakeProcessManager, hasNoRemainingExpectations);
|
|
});
|
|
|
|
testWithoutContext('throws error if not found', () {
|
|
final Xcode xcode = Xcode.test(
|
|
processManager: FakeProcessManager.any(),
|
|
xcodeProjectInterpreter: xcodeProjectInterpreter,
|
|
);
|
|
|
|
expect(
|
|
() => xcode.xcodeAppPath,
|
|
throwsToolExit(message: userMessages.xcodeMissing),
|
|
);
|
|
});
|
|
|
|
testWithoutContext('throws error with unexpected outcome', () {
|
|
final Xcode xcode = Xcode.test(
|
|
processManager: fakeProcessManager,
|
|
xcodeProjectInterpreter: xcodeProjectInterpreter,
|
|
);
|
|
|
|
fakeProcessManager.addCommand(const FakeCommand(
|
|
command: <String>[
|
|
'/usr/bin/xcode-select',
|
|
'--print-path',
|
|
],
|
|
stdout: '/Library/Developer/CommandLineTools',
|
|
));
|
|
|
|
expect(
|
|
() => xcode.xcodeAppPath,
|
|
throwsToolExit(message: userMessages.xcodeMissing),
|
|
);
|
|
expect(fakeProcessManager, hasNoRemainingExpectations);
|
|
});
|
|
});
|
|
|
|
group('pathToXcodeAutomationScript', () {
|
|
const String flutterRoot = '/path/to/flutter';
|
|
|
|
late MemoryFileSystem fileSystem;
|
|
|
|
setUp(() {
|
|
fileSystem = MemoryFileSystem.test();
|
|
});
|
|
|
|
testWithoutContext('returns path when file is found', () {
|
|
final Xcode xcode = Xcode.test(
|
|
processManager: fakeProcessManager,
|
|
xcodeProjectInterpreter: xcodeProjectInterpreter,
|
|
fileSystem: fileSystem,
|
|
flutterRoot: flutterRoot,
|
|
);
|
|
|
|
fileSystem.file('$flutterRoot/packages/flutter_tools/bin/xcode_debug.js').createSync(recursive: true);
|
|
|
|
expect(
|
|
xcode.xcodeAutomationScriptPath,
|
|
'$flutterRoot/packages/flutter_tools/bin/xcode_debug.js',
|
|
);
|
|
});
|
|
|
|
testWithoutContext('throws error when not found', () {
|
|
final Xcode xcode = Xcode.test(
|
|
processManager: fakeProcessManager,
|
|
xcodeProjectInterpreter: xcodeProjectInterpreter,
|
|
fileSystem: fileSystem,
|
|
flutterRoot: flutterRoot,
|
|
);
|
|
|
|
expect(() =>
|
|
xcode.xcodeAutomationScriptPath,
|
|
throwsToolExit()
|
|
);
|
|
});
|
|
});
|
|
|
|
group('macOS', () {
|
|
late Xcode xcode;
|
|
late BufferLogger logger;
|
|
|
|
setUp(() {
|
|
xcodeProjectInterpreter = FakeXcodeProjectInterpreter();
|
|
logger = BufferLogger.test();
|
|
xcode = Xcode.test(
|
|
processManager: fakeProcessManager,
|
|
xcodeProjectInterpreter: xcodeProjectInterpreter,
|
|
logger: logger,
|
|
);
|
|
});
|
|
|
|
testWithoutContext('xcodeSelectPath returns path when xcode-select is installed', () {
|
|
const String xcodePath = '/Applications/Xcode8.0.app/Contents/Developer';
|
|
fakeProcessManager.addCommand(const FakeCommand(
|
|
command: <String>['/usr/bin/xcode-select', '--print-path'],
|
|
stdout: xcodePath,
|
|
),
|
|
);
|
|
|
|
expect(xcode.xcodeSelectPath, xcodePath);
|
|
expect(fakeProcessManager, hasNoRemainingExpectations);
|
|
});
|
|
|
|
testWithoutContext('xcodeSelectPath returns null when xcode-select is not installed', () {
|
|
fakeProcessManager.addCommand(const FakeCommand(
|
|
command: <String>['/usr/bin/xcode-select', '--print-path'],
|
|
exception: ProcessException('/usr/bin/xcode-select', <String>['--print-path']),
|
|
));
|
|
|
|
expect(xcode.xcodeSelectPath, isNull);
|
|
expect(fakeProcessManager, hasNoRemainingExpectations);
|
|
|
|
fakeProcessManager.addCommand(FakeCommand(
|
|
command: const <String>['/usr/bin/xcode-select', '--print-path'],
|
|
exception: ArgumentError('Invalid argument(s): Cannot find executable for /usr/bin/xcode-select'),
|
|
));
|
|
|
|
expect(xcode.xcodeSelectPath, isNull);
|
|
expect(fakeProcessManager, hasNoRemainingExpectations);
|
|
});
|
|
|
|
testWithoutContext('version checks fail when version is less than minimum', () {
|
|
xcodeProjectInterpreter.isInstalled = true;
|
|
xcodeProjectInterpreter.version = Version(9, null, null);
|
|
|
|
expect(xcode.isRequiredVersionSatisfactory, isFalse);
|
|
expect(xcode.isRecommendedVersionSatisfactory, isFalse);
|
|
});
|
|
|
|
testWithoutContext('version checks fail when xcodebuild tools are not installed', () {
|
|
xcodeProjectInterpreter.isInstalled = false;
|
|
|
|
expect(xcode.isRequiredVersionSatisfactory, isFalse);
|
|
expect(xcode.isRecommendedVersionSatisfactory, isFalse);
|
|
});
|
|
|
|
testWithoutContext('version checks pass when version meets minimum but not recommended', () {
|
|
xcodeProjectInterpreter.isInstalled = true;
|
|
xcodeProjectInterpreter.version = Version(14, null, null);
|
|
|
|
expect(xcode.isRequiredVersionSatisfactory, isTrue);
|
|
expect(xcode.isRecommendedVersionSatisfactory, isFalse);
|
|
});
|
|
|
|
testWithoutContext('version checks pass when major version exceeds minimum', () {
|
|
xcodeProjectInterpreter.isInstalled = true;
|
|
xcodeProjectInterpreter.version = Version(15, 0, 0);
|
|
|
|
expect(xcode.isRequiredVersionSatisfactory, isTrue);
|
|
});
|
|
|
|
testWithoutContext('version checks pass when minor version exceeds minimum', () {
|
|
xcodeProjectInterpreter.isInstalled = true;
|
|
xcodeProjectInterpreter.version = Version(14, 3, 0);
|
|
|
|
expect(xcode.isRequiredVersionSatisfactory, isTrue);
|
|
});
|
|
|
|
testWithoutContext('version checks pass when patch version exceeds minimum', () {
|
|
xcodeProjectInterpreter.isInstalled = true;
|
|
xcodeProjectInterpreter.version = Version(14, 0, 2);
|
|
|
|
expect(xcode.isRequiredVersionSatisfactory, isTrue);
|
|
});
|
|
|
|
testWithoutContext('version checks pass when major version exceeds recommendation', () {
|
|
xcodeProjectInterpreter.isInstalled = true;
|
|
xcodeProjectInterpreter.version = Version(16, 0, 0);
|
|
|
|
expect(xcode.isRequiredVersionSatisfactory, isTrue);
|
|
expect(xcode.isRecommendedVersionSatisfactory, isTrue);
|
|
});
|
|
|
|
testWithoutContext('version checks pass when minor version exceeds recommendation', () {
|
|
xcodeProjectInterpreter.isInstalled = true;
|
|
xcodeProjectInterpreter.version = Version(15, 3, 0);
|
|
|
|
expect(xcode.isRequiredVersionSatisfactory, isTrue);
|
|
expect(xcode.isRecommendedVersionSatisfactory, isTrue);
|
|
});
|
|
|
|
testWithoutContext('version checks pass when patch version exceeds recommendation', () {
|
|
xcodeProjectInterpreter.isInstalled = true;
|
|
xcodeProjectInterpreter.version = Version(15, 0, 2);
|
|
|
|
expect(xcode.isRequiredVersionSatisfactory, isTrue);
|
|
expect(xcode.isRecommendedVersionSatisfactory, isTrue);
|
|
});
|
|
|
|
testWithoutContext('isInstalledAndMeetsVersionCheck is false when not installed', () {
|
|
xcodeProjectInterpreter.isInstalled = false;
|
|
|
|
expect(xcode.isInstalledAndMeetsVersionCheck, isFalse);
|
|
expect(fakeProcessManager, hasNoRemainingExpectations);
|
|
});
|
|
|
|
testWithoutContext('isInstalledAndMeetsVersionCheck is false when version not satisfied', () {
|
|
xcodeProjectInterpreter.isInstalled = true;
|
|
xcodeProjectInterpreter.version = Version(10, 2, 0);
|
|
|
|
expect(xcode.isInstalledAndMeetsVersionCheck, isFalse);
|
|
expect(fakeProcessManager, hasNoRemainingExpectations);
|
|
});
|
|
|
|
testWithoutContext('isInstalledAndMeetsVersionCheck is true when macOS and installed and version is satisfied', () {
|
|
xcodeProjectInterpreter.isInstalled = true;
|
|
xcodeProjectInterpreter.version = Version(14, null, null);
|
|
|
|
expect(xcode.isInstalledAndMeetsVersionCheck, isTrue);
|
|
expect(fakeProcessManager, hasNoRemainingExpectations);
|
|
});
|
|
|
|
testWithoutContext('eulaSigned is false when clang output indicates EULA not yet accepted', () {
|
|
fakeProcessManager.addCommands(const <FakeCommand>[
|
|
FakeCommand(
|
|
command: <String>['xcrun', 'clang'],
|
|
exitCode: 1,
|
|
stderr:
|
|
'Xcode EULA has not been accepted.\nLaunch Xcode and accept the license.',
|
|
),
|
|
]);
|
|
|
|
expect(xcode.eulaSigned, isFalse);
|
|
expect(fakeProcessManager, hasNoRemainingExpectations);
|
|
});
|
|
|
|
testWithoutContext('eulaSigned is false when clang is not installed', () {
|
|
fakeProcessManager.addCommand(
|
|
const FakeCommand(
|
|
command: <String>['xcrun', 'clang'],
|
|
exception: ProcessException('xcrun', <String>['clang']),
|
|
),
|
|
);
|
|
|
|
expect(xcode.eulaSigned, isFalse);
|
|
});
|
|
|
|
testWithoutContext('eulaSigned is true when clang output indicates EULA has been accepted', () {
|
|
fakeProcessManager.addCommands(
|
|
const <FakeCommand>[
|
|
FakeCommand(
|
|
command: <String>['xcrun', 'clang'],
|
|
exitCode: 1,
|
|
stderr: 'clang: error: no input files',
|
|
),
|
|
],
|
|
);
|
|
expect(xcode.eulaSigned, isTrue);
|
|
expect(fakeProcessManager, hasNoRemainingExpectations);
|
|
});
|
|
|
|
testWithoutContext('SDK name', () {
|
|
expect(getSDKNameForIOSEnvironmentType(EnvironmentType.physical), 'iphoneos');
|
|
expect(getSDKNameForIOSEnvironmentType(EnvironmentType.simulator), 'iphonesimulator');
|
|
});
|
|
|
|
group('SDK location', () {
|
|
const String sdkroot = 'Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS13.2.sdk';
|
|
|
|
testWithoutContext('--show-sdk-path iphoneos', () async {
|
|
fakeProcessManager.addCommand(const FakeCommand(
|
|
command: <String>['xcrun', '--sdk', 'iphoneos', '--show-sdk-path'],
|
|
stdout: sdkroot,
|
|
));
|
|
|
|
expect(await xcode.sdkLocation(EnvironmentType.physical), sdkroot);
|
|
expect(fakeProcessManager, hasNoRemainingExpectations);
|
|
});
|
|
|
|
testWithoutContext('--show-sdk-path fails', () async {
|
|
fakeProcessManager.addCommand(const FakeCommand(
|
|
command: <String>['xcrun', '--sdk', 'iphoneos', '--show-sdk-path'],
|
|
exitCode: 1,
|
|
stderr: 'xcrun: error:',
|
|
));
|
|
|
|
expect(() async => xcode.sdkLocation(EnvironmentType.physical),
|
|
throwsToolExit(message: 'Could not find SDK location'));
|
|
expect(fakeProcessManager, hasNoRemainingExpectations);
|
|
});
|
|
});
|
|
|
|
group('SDK Platform Version', () {
|
|
testWithoutContext('--show-sdk-platform-version iphonesimulator', () async {
|
|
fakeProcessManager.addCommand(const FakeCommand(
|
|
command: <String>['xcrun', '--sdk', 'iphonesimulator', '--show-sdk-platform-version'],
|
|
stdout: '16.4',
|
|
));
|
|
|
|
expect(await xcode.sdkPlatformVersion(EnvironmentType.simulator), Version(16, 4, null));
|
|
expect(fakeProcessManager, hasNoRemainingExpectations);
|
|
});
|
|
|
|
testWithoutContext('--show-sdk-platform-version iphonesimulator with leading and trailing new line', () async {
|
|
fakeProcessManager.addCommand(const FakeCommand(
|
|
command: <String>['xcrun', '--sdk', 'iphonesimulator', '--show-sdk-platform-version'],
|
|
stdout: '\n16.4\n',
|
|
));
|
|
|
|
expect(await xcode.sdkPlatformVersion(EnvironmentType.simulator), Version(16, 4, null));
|
|
expect(fakeProcessManager, hasNoRemainingExpectations);
|
|
});
|
|
|
|
testWithoutContext('--show-sdk-platform-version returns version followed by text', () async {
|
|
fakeProcessManager.addCommand(const FakeCommand(
|
|
command: <String>['xcrun', '--sdk', 'iphonesimulator', '--show-sdk-platform-version'],
|
|
stdout: '13.2 (a) 12344',
|
|
));
|
|
|
|
expect(await xcode.sdkPlatformVersion(EnvironmentType.simulator), Version(13, 2, null, text: '13.2 (a) 12344'));
|
|
expect(fakeProcessManager, hasNoRemainingExpectations);
|
|
});
|
|
|
|
testWithoutContext('--show-sdk-platform-version returns something unexpected', () async {
|
|
fakeProcessManager.addCommand(const FakeCommand(
|
|
command: <String>['xcrun', '--sdk', 'iphonesimulator', '--show-sdk-platform-version'],
|
|
stdout: 'bogus',
|
|
));
|
|
|
|
expect(await xcode.sdkPlatformVersion(EnvironmentType.simulator), null);
|
|
expect(fakeProcessManager, hasNoRemainingExpectations);
|
|
});
|
|
|
|
testWithoutContext('--show-sdk-platform-version fails', () async {
|
|
fakeProcessManager.addCommand(const FakeCommand(
|
|
command: <String>['xcrun', '--sdk', 'iphonesimulator', '--show-sdk-platform-version'],
|
|
exitCode: 1,
|
|
stderr: 'xcrun: error:',
|
|
));
|
|
expect(await xcode.sdkPlatformVersion(EnvironmentType.simulator), null);
|
|
expect(fakeProcessManager, hasNoRemainingExpectations);
|
|
expect(logger.errorText, contains('Could not find SDK Platform Version'));
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
group('xcdevice not installed', () {
|
|
late XCDevice xcdevice;
|
|
late Xcode xcode;
|
|
late MemoryFileSystem fileSystem;
|
|
|
|
setUp(() {
|
|
xcode = Xcode.test(
|
|
processManager: FakeProcessManager.any(),
|
|
xcodeProjectInterpreter: XcodeProjectInterpreter.test(
|
|
processManager: FakeProcessManager.any(),
|
|
version: null, // Not installed.
|
|
),
|
|
);
|
|
fileSystem = MemoryFileSystem.test();
|
|
xcdevice = XCDevice(
|
|
processManager: fakeProcessManager,
|
|
logger: logger,
|
|
xcode: xcode,
|
|
platform: FakePlatform(operatingSystem: 'macos'),
|
|
artifacts: Artifacts.test(),
|
|
cache: Cache.test(processManager: FakeProcessManager.any()),
|
|
iproxy: IProxy.test(logger: logger, processManager: fakeProcessManager),
|
|
fileSystem: fileSystem,
|
|
coreDeviceControl: FakeIOSCoreDeviceControl(),
|
|
xcodeDebug: FakeXcodeDebug(),
|
|
analytics: const NoOpAnalytics(),
|
|
);
|
|
});
|
|
|
|
testWithoutContext('Xcode not installed', () async {
|
|
expect(xcode.isInstalled, false);
|
|
|
|
expect(xcdevice.isInstalled, false);
|
|
expect(xcdevice.observedDeviceEvents(), isNull);
|
|
expect(logger.traceText, contains("Xcode not found. Run 'flutter doctor' for more information."));
|
|
expect(await xcdevice.getAvailableIOSDevices(), isEmpty);
|
|
expect(await xcdevice.getDiagnostics(), isEmpty);
|
|
});
|
|
});
|
|
|
|
group('xcdevice', () {
|
|
late XCDevice xcdevice;
|
|
late Xcode xcode;
|
|
late MemoryFileSystem fileSystem;
|
|
late FakeAnalytics fakeAnalytics;
|
|
late FakeIOSCoreDeviceControl coreDeviceControl;
|
|
|
|
setUp(() {
|
|
xcode = Xcode.test(processManager: FakeProcessManager.any());
|
|
fileSystem = MemoryFileSystem.test();
|
|
coreDeviceControl = FakeIOSCoreDeviceControl();
|
|
fakeAnalytics = getInitializedFakeAnalyticsInstance(
|
|
fs: fileSystem,
|
|
fakeFlutterVersion: FakeFlutterVersion(),
|
|
);
|
|
xcdevice = XCDevice(
|
|
processManager: fakeProcessManager,
|
|
logger: logger,
|
|
xcode: xcode,
|
|
platform: FakePlatform(operatingSystem: 'macos'),
|
|
artifacts: Artifacts.test(),
|
|
cache: Cache.test(processManager: FakeProcessManager.any()),
|
|
iproxy: IProxy.test(logger: logger, processManager: fakeProcessManager),
|
|
fileSystem: fileSystem,
|
|
coreDeviceControl: coreDeviceControl,
|
|
xcodeDebug: FakeXcodeDebug(),
|
|
analytics: fakeAnalytics,
|
|
);
|
|
});
|
|
|
|
group('observe device events', () {
|
|
testUsingContext('relays events', () async {
|
|
fakeProcessManager.addCommand(const FakeCommand(
|
|
command: <String>[
|
|
'script',
|
|
'-t',
|
|
'0',
|
|
'/dev/null',
|
|
'xcrun',
|
|
'xcdevice',
|
|
'observe',
|
|
'--usb',
|
|
], stdout: 'Listening for all devices, on USB.\n'
|
|
'Attach: d83d5bc53967baa0ee18626ba87b6254b2ab5418\n'
|
|
'Attach: 00008027-00192736010F802E\n'
|
|
'Detach: d83d5bc53967baa0ee18626ba87b6254b2ab5418',
|
|
stderr: 'Some usb error',
|
|
));
|
|
|
|
fakeProcessManager.addCommand(const FakeCommand(
|
|
command: <String>[
|
|
'script',
|
|
'-t',
|
|
'0',
|
|
'/dev/null',
|
|
'xcrun',
|
|
'xcdevice',
|
|
'observe',
|
|
'--wifi',
|
|
], stdout: 'Listening for all devices, on WiFi.\n'
|
|
'Attach: 00000001-0000000000000000\n'
|
|
'Detach: 00000001-0000000000000000',
|
|
stderr: 'Some wifi error',
|
|
));
|
|
|
|
final Completer<void> attach1 = Completer<void>();
|
|
final Completer<void> attach2 = Completer<void>();
|
|
final Completer<void> detach1 = Completer<void>();
|
|
final Completer<void> attach3 = Completer<void>();
|
|
final Completer<void> detach2 = Completer<void>();
|
|
|
|
// Attach: d83d5bc53967baa0ee18626ba87b6254b2ab5418
|
|
// Attach: 00008027-00192736010F802E
|
|
// Detach: d83d5bc53967baa0ee18626ba87b6254b2ab5418
|
|
xcdevice.observedDeviceEvents()!.listen((XCDeviceEventNotification event) {
|
|
if (event.eventType == XCDeviceEvent.attach) {
|
|
if (event.deviceIdentifier == 'd83d5bc53967baa0ee18626ba87b6254b2ab5418') {
|
|
attach1.complete();
|
|
} else
|
|
if (event.deviceIdentifier == '00008027-00192736010F802E') {
|
|
attach2.complete();
|
|
}
|
|
if (event.deviceIdentifier == '00000001-0000000000000000') {
|
|
attach3.complete();
|
|
}
|
|
} else if (event.eventType == XCDeviceEvent.detach) {
|
|
if (event.deviceIdentifier == 'd83d5bc53967baa0ee18626ba87b6254b2ab5418') {
|
|
detach1.complete();
|
|
}
|
|
if (event.deviceIdentifier == '00000001-0000000000000000') {
|
|
detach2.complete();
|
|
}
|
|
} else {
|
|
fail('Unexpected event');
|
|
}
|
|
});
|
|
await attach1.future;
|
|
await attach2.future;
|
|
await detach1.future;
|
|
await attach3.future;
|
|
await detach2.future;
|
|
expect(logger.errorText, contains('xcdevice observe --usb: Some usb error'));
|
|
expect(logger.errorText, contains('xcdevice observe --wifi: Some wifi error'));
|
|
});
|
|
|
|
testUsingContext('handles exit code', () async {
|
|
fakeProcessManager.addCommand(const FakeCommand(
|
|
command: <String>[
|
|
'script',
|
|
'-t',
|
|
'0',
|
|
'/dev/null',
|
|
'xcrun',
|
|
'xcdevice',
|
|
'observe',
|
|
'--usb',
|
|
],
|
|
));
|
|
fakeProcessManager.addCommand(const FakeCommand(
|
|
command: <String>[
|
|
'script',
|
|
'-t',
|
|
'0',
|
|
'/dev/null',
|
|
'xcrun',
|
|
'xcdevice',
|
|
'observe',
|
|
'--wifi',
|
|
],
|
|
exitCode: 1,
|
|
));
|
|
|
|
final Completer<void> doneCompleter = Completer<void>();
|
|
xcdevice.observedDeviceEvents()!.listen(null, onDone: () {
|
|
doneCompleter.complete();
|
|
});
|
|
await doneCompleter.future;
|
|
expect(logger.traceText, contains('xcdevice observe --usb exited with code 0'));
|
|
expect(logger.traceText, contains('xcdevice observe --wifi exited with code 0'));
|
|
});
|
|
|
|
});
|
|
|
|
group('wait device events', () {
|
|
testUsingContext('relays events', () async {
|
|
const String deviceId = '00000001-0000000000000000';
|
|
|
|
fakeProcessManager.addCommand(const FakeCommand(
|
|
command: <String>[
|
|
'script',
|
|
'-t',
|
|
'0',
|
|
'/dev/null',
|
|
'xcrun',
|
|
'xcdevice',
|
|
'wait',
|
|
'--usb',
|
|
deviceId,
|
|
],
|
|
stdout: 'Waiting for $deviceId to appear, on USB.\n',
|
|
));
|
|
fakeProcessManager.addCommand(const FakeCommand(
|
|
command: <String>[
|
|
'script',
|
|
'-t',
|
|
'0',
|
|
'/dev/null',
|
|
'xcrun',
|
|
'xcdevice',
|
|
'wait',
|
|
'--wifi',
|
|
deviceId,
|
|
],
|
|
stdout:
|
|
'Waiting for $deviceId to appear, on WiFi.\n'
|
|
'Attach: 00000001-0000000000000000\n',
|
|
));
|
|
|
|
// Attach: 00000001-0000000000000000
|
|
|
|
final XCDeviceEventNotification? event = await xcdevice.waitForDeviceToConnect(deviceId);
|
|
|
|
expect(event?.deviceIdentifier, deviceId);
|
|
expect(event?.eventInterface, XCDeviceEventInterface.wifi);
|
|
expect(event?.eventType, XCDeviceEvent.attach);
|
|
});
|
|
|
|
testUsingContext('handles exit code', () async {
|
|
const String deviceId = '00000001-0000000000000000';
|
|
|
|
fakeProcessManager.addCommand(const FakeCommand(
|
|
command: <String>[
|
|
'script',
|
|
'-t',
|
|
'0',
|
|
'/dev/null',
|
|
'xcrun',
|
|
'xcdevice',
|
|
'wait',
|
|
'--usb',
|
|
deviceId,
|
|
],
|
|
exitCode: 1,
|
|
stderr: 'Some error',
|
|
));
|
|
fakeProcessManager.addCommand(const FakeCommand(
|
|
command: <String>[
|
|
'script',
|
|
'-t',
|
|
'0',
|
|
'/dev/null',
|
|
'xcrun',
|
|
'xcdevice',
|
|
'wait',
|
|
'--wifi',
|
|
deviceId,
|
|
],
|
|
));
|
|
|
|
final XCDeviceEventNotification? event = await xcdevice.waitForDeviceToConnect(deviceId);
|
|
|
|
expect(event, isNull);
|
|
expect(logger.errorText, contains('xcdevice wait --usb: Some error'));
|
|
expect(logger.traceText, contains('xcdevice wait --usb exited with code 0'));
|
|
expect(logger.traceText, contains('xcdevice wait --wifi exited with code 0'));
|
|
expect(xcdevice.waitStreamController?.isClosed, isTrue);
|
|
});
|
|
|
|
testUsingContext('handles cancel', () async {
|
|
const String deviceId = '00000001-0000000000000000';
|
|
|
|
fakeProcessManager.addCommand(const FakeCommand(
|
|
command: <String>[
|
|
'script',
|
|
'-t',
|
|
'0',
|
|
'/dev/null',
|
|
'xcrun',
|
|
'xcdevice',
|
|
'wait',
|
|
'--usb',
|
|
deviceId,
|
|
],
|
|
));
|
|
fakeProcessManager.addCommand(const FakeCommand(
|
|
command: <String>[
|
|
'script',
|
|
'-t',
|
|
'0',
|
|
'/dev/null',
|
|
'xcrun',
|
|
'xcdevice',
|
|
'wait',
|
|
'--wifi',
|
|
deviceId,
|
|
],
|
|
));
|
|
|
|
final Future<XCDeviceEventNotification?> futureEvent = xcdevice.waitForDeviceToConnect(deviceId);
|
|
xcdevice.cancelWaitForDeviceToConnect();
|
|
final XCDeviceEventNotification? event = await futureEvent;
|
|
|
|
expect(event, isNull);
|
|
expect(logger.traceText, contains('xcdevice wait --usb exited with code 0'));
|
|
expect(logger.traceText, contains('xcdevice wait --wifi exited with code 0'));
|
|
expect(xcdevice.waitStreamController?.isClosed, isTrue);
|
|
});
|
|
});
|
|
|
|
group('available devices', () {
|
|
final FakePlatform macPlatform = FakePlatform(operatingSystem: 'macos');
|
|
testUsingContext('returns devices', () async {
|
|
const String devicesOutput = '''
|
|
[
|
|
{
|
|
"simulator" : true,
|
|
"operatingSystemVersion" : "13.3 (17K446)",
|
|
"available" : true,
|
|
"platform" : "com.apple.platform.appletvsimulator",
|
|
"modelCode" : "AppleTV5,3",
|
|
"identifier" : "CBB5E1ED-2172-446E-B4E7-F2B5823DBBA6",
|
|
"architecture" : "x86_64",
|
|
"modelName" : "Apple TV",
|
|
"name" : "Apple TV"
|
|
},
|
|
{
|
|
"simulator" : false,
|
|
"operatingSystemVersion" : "13.3 (17C54)",
|
|
"interface" : "usb",
|
|
"available" : true,
|
|
"platform" : "com.apple.platform.iphoneos",
|
|
"modelCode" : "iPhone8,1",
|
|
"identifier" : "00008027-00192736010F802E",
|
|
"architecture" : "arm64",
|
|
"modelName" : "iPhone 6s",
|
|
"name" : "An iPhone (Space Gray)"
|
|
},
|
|
{
|
|
"simulator" : false,
|
|
"operatingSystemVersion" : "10.1 (14C54)",
|
|
"interface" : "usb",
|
|
"available" : true,
|
|
"platform" : "com.apple.platform.iphoneos",
|
|
"modelCode" : "iPad11,4",
|
|
"identifier" : "98206e7a4afd4aedaff06e687594e089dede3c44",
|
|
"architecture" : "armv7",
|
|
"modelName" : "iPad Air 3rd Gen",
|
|
"name" : "iPad 1"
|
|
},
|
|
{
|
|
"simulator" : false,
|
|
"operatingSystemVersion" : "10.1 (14C54)",
|
|
"interface" : "network",
|
|
"available" : true,
|
|
"platform" : "com.apple.platform.iphoneos",
|
|
"modelCode" : "iPad11,4",
|
|
"identifier" : "234234234234234234345445687594e089dede3c44",
|
|
"architecture" : "arm64",
|
|
"modelName" : "iPad Air 3rd Gen",
|
|
"name" : "A networked iPad"
|
|
},
|
|
{
|
|
"simulator" : false,
|
|
"operatingSystemVersion" : "10.1 (14C54)",
|
|
"interface" : "usb",
|
|
"available" : true,
|
|
"platform" : "com.apple.platform.iphoneos",
|
|
"modelCode" : "iPad11,4",
|
|
"identifier" : "f577a7903cc54959be2e34bc4f7f80b7009efcf4",
|
|
"architecture" : "BOGUS",
|
|
"modelName" : "iPad Air 3rd Gen",
|
|
"name" : "iPad 2"
|
|
},
|
|
{
|
|
"simulator" : true,
|
|
"operatingSystemVersion" : "6.1.1 (17S445)",
|
|
"available" : true,
|
|
"platform" : "com.apple.platform.watchsimulator",
|
|
"modelCode" : "Watch5,4",
|
|
"identifier" : "2D74FB11-88A0-44D0-B81E-C0C142B1C94A",
|
|
"architecture" : "i386",
|
|
"modelName" : "Apple Watch Series 5 - 44mm",
|
|
"name" : "Apple Watch Series 5 - 44mm"
|
|
},
|
|
{
|
|
"simulator" : false,
|
|
"operatingSystemVersion" : "13.3 (17C54)",
|
|
"interface" : "usb",
|
|
"available" : false,
|
|
"platform" : "com.apple.platform.iphoneos",
|
|
"modelCode" : "iPhone8,1",
|
|
"identifier" : "c4ca6f7a53027d1b7e4972e28478e7a28e2faee2",
|
|
"architecture" : "arm64",
|
|
"modelName" : "iPhone 6s",
|
|
"name" : "iPhone",
|
|
"error" : {
|
|
"code" : -9,
|
|
"failureReason" : "",
|
|
"description" : "iPhone is not paired with your computer.",
|
|
"domain" : "com.apple.platform.iphoneos"
|
|
}
|
|
}
|
|
]
|
|
''';
|
|
|
|
fakeProcessManager.addCommand(const FakeCommand(
|
|
command: <String>['xcrun', 'xcdevice', 'list', '--timeout', '2'],
|
|
stdout: devicesOutput,
|
|
));
|
|
final List<IOSDevice> devices = await xcdevice.getAvailableIOSDevices();
|
|
expect(devices, hasLength(5));
|
|
expect(devices[0].id, '00008027-00192736010F802E');
|
|
expect(devices[0].name, 'An iPhone (Space Gray)');
|
|
expect(await devices[0].sdkNameAndVersion, 'iOS 13.3 17C54');
|
|
expect(devices[0].cpuArchitecture, DarwinArch.arm64);
|
|
expect(devices[0].connectionInterface, DeviceConnectionInterface.attached);
|
|
expect(devices[0].isConnected, true);
|
|
|
|
expect(devices[1].id, '98206e7a4afd4aedaff06e687594e089dede3c44');
|
|
expect(devices[1].name, 'iPad 1');
|
|
expect(await devices[1].sdkNameAndVersion, 'iOS 10.1 14C54');
|
|
expect(devices[1].cpuArchitecture, DarwinArch.armv7);
|
|
expect(devices[1].connectionInterface, DeviceConnectionInterface.attached);
|
|
expect(devices[1].isConnected, true);
|
|
|
|
expect(devices[2].id, '234234234234234234345445687594e089dede3c44');
|
|
expect(devices[2].name, 'A networked iPad');
|
|
expect(await devices[2].sdkNameAndVersion, 'iOS 10.1 14C54');
|
|
expect(devices[2].cpuArchitecture, DarwinArch.arm64); // Defaults to arm64 for unknown architecture.
|
|
expect(devices[2].connectionInterface, DeviceConnectionInterface.wireless);
|
|
expect(devices[2].isConnected, true);
|
|
|
|
expect(devices[3].id, 'f577a7903cc54959be2e34bc4f7f80b7009efcf4');
|
|
expect(devices[3].name, 'iPad 2');
|
|
expect(await devices[3].sdkNameAndVersion, 'iOS 10.1 14C54');
|
|
expect(devices[3].cpuArchitecture, DarwinArch.arm64); // Defaults to arm64 for unknown architecture.
|
|
expect(devices[3].connectionInterface, DeviceConnectionInterface.attached);
|
|
expect(devices[3].isConnected, true);
|
|
|
|
expect(devices[4].id, 'c4ca6f7a53027d1b7e4972e28478e7a28e2faee2');
|
|
expect(devices[4].name, 'iPhone');
|
|
expect(await devices[4].sdkNameAndVersion, 'iOS 13.3 17C54');
|
|
expect(devices[4].cpuArchitecture, DarwinArch.arm64);
|
|
expect(devices[4].connectionInterface, DeviceConnectionInterface.attached);
|
|
expect(devices[4].isConnected, false);
|
|
|
|
expect(fakeProcessManager, hasNoRemainingExpectations);
|
|
}, overrides: <Type, Generator>{
|
|
Platform: () => macPlatform,
|
|
Artifacts: () => Artifacts.test(),
|
|
});
|
|
|
|
testWithoutContext('available devices xcdevice fails', () async {
|
|
fakeProcessManager.addCommand(const FakeCommand(
|
|
command: <String>['xcrun', 'xcdevice', 'list', '--timeout', '2'],
|
|
exception: ProcessException('xcrun', <String>['xcdevice', 'list', '--timeout', '2']),
|
|
));
|
|
|
|
expect(await xcdevice.getAvailableIOSDevices(), isEmpty);
|
|
});
|
|
|
|
testWithoutContext('uses timeout', () async {
|
|
fakeProcessManager.addCommand(const FakeCommand(
|
|
command: <String>['xcrun', 'xcdevice', 'list', '--timeout', '20'],
|
|
stdout: '[]',
|
|
));
|
|
await xcdevice.getAvailableIOSDevices(timeout: const Duration(seconds: 20));
|
|
expect(fakeProcessManager, hasNoRemainingExpectations);
|
|
});
|
|
|
|
testUsingContext('ignores "Preparing debugger support for iPhone" error', () async {
|
|
const String devicesOutput = '''
|
|
[
|
|
{
|
|
"simulator" : false,
|
|
"operatingSystemVersion" : "13.3 (17C54)",
|
|
"interface" : "usb",
|
|
"available" : false,
|
|
"platform" : "com.apple.platform.iphoneos",
|
|
"modelCode" : "iPhone8,1",
|
|
"identifier" : "43ad2fda7991b34fe1acbda82f9e2fd3d6ddc9f7",
|
|
"architecture" : "arm64",
|
|
"modelName" : "iPhone 6s",
|
|
"name" : "iPhone",
|
|
"error" : {
|
|
"code" : -10,
|
|
"failureReason" : "",
|
|
"description" : "iPhone is busy: Preparing debugger support for iPhone",
|
|
"recoverySuggestion" : "Xcode will continue when iPhone is finished.",
|
|
"domain" : "com.apple.platform.iphoneos"
|
|
}
|
|
}
|
|
]
|
|
''';
|
|
|
|
fakeProcessManager.addCommand(const FakeCommand(
|
|
command: <String>['xcrun', 'xcdevice', 'list', '--timeout', '2'],
|
|
stdout: devicesOutput,
|
|
));
|
|
final List<IOSDevice> devices = await xcdevice.getAvailableIOSDevices();
|
|
expect(devices, hasLength(1));
|
|
expect(devices[0].id, '43ad2fda7991b34fe1acbda82f9e2fd3d6ddc9f7');
|
|
expect(fakeProcessManager, hasNoRemainingExpectations);
|
|
}, overrides: <Type, Generator>{
|
|
Platform: () => macPlatform,
|
|
Artifacts: () => Artifacts.test(),
|
|
});
|
|
|
|
testUsingContext('handles unknown architectures', () async {
|
|
const String devicesOutput = '''
|
|
[
|
|
{
|
|
"simulator" : false,
|
|
"operatingSystemVersion" : "13.3 (17C54)",
|
|
"interface" : "usb",
|
|
"available" : true,
|
|
"platform" : "com.apple.platform.iphoneos",
|
|
"modelCode" : "iPhone8,1",
|
|
"identifier" : "d83d5bc53967baa0ee18626ba87b6254b2ab5418",
|
|
"architecture" : "armv7x",
|
|
"modelName" : "iPad 3 BOGUS",
|
|
"name" : "iPad"
|
|
},
|
|
{
|
|
"simulator" : false,
|
|
"operatingSystemVersion" : "13.3 (17C54)",
|
|
"interface" : "usb",
|
|
"available" : true,
|
|
"platform" : "com.apple.platform.iphoneos",
|
|
"modelCode" : "iPhone8,1",
|
|
"identifier" : "43ad2fda7991b34fe1acbda82f9e2fd3d6ddc9f7",
|
|
"architecture" : "BOGUS",
|
|
"modelName" : "Future iPad",
|
|
"name" : "iPad"
|
|
}
|
|
]
|
|
''';
|
|
|
|
fakeProcessManager.addCommand(const FakeCommand(
|
|
command: <String>['xcrun', 'xcdevice', 'list', '--timeout', '2'],
|
|
stdout: devicesOutput,
|
|
));
|
|
final List<IOSDevice> devices = await xcdevice.getAvailableIOSDevices();
|
|
expect(devices[0].cpuArchitecture, DarwinArch.armv7);
|
|
expect(devices[1].cpuArchitecture, DarwinArch.arm64);
|
|
expect(fakeProcessManager, hasNoRemainingExpectations);
|
|
}, overrides: <Type, Generator>{
|
|
Platform: () => macPlatform,
|
|
Artifacts: () => Artifacts.test(),
|
|
});
|
|
|
|
testUsingContext('Sdk Version is parsed correctly',() async {
|
|
const String devicesOutput = '''
|
|
[
|
|
{
|
|
"simulator" : false,
|
|
"operatingSystemVersion" : "13.3 (17C54)",
|
|
"interface" : "usb",
|
|
"available" : true,
|
|
"platform" : "com.apple.platform.iphoneos",
|
|
"modelCode" : "iPhone8,1",
|
|
"identifier" : "00008027-00192736010F802E",
|
|
"architecture" : "arm64",
|
|
"modelName" : "iPhone 6s",
|
|
"name" : "An iPhone (Space Gray)"
|
|
},
|
|
{
|
|
"simulator" : false,
|
|
"operatingSystemVersion" : "10.1",
|
|
"interface" : "usb",
|
|
"available" : true,
|
|
"platform" : "com.apple.platform.iphoneos",
|
|
"modelCode" : "iPad11,4",
|
|
"identifier" : "234234234234234234345445687594e089dede3c44",
|
|
"architecture" : "arm64",
|
|
"modelName" : "iPad Air 3rd Gen",
|
|
"name" : "A networked iPad"
|
|
},
|
|
{
|
|
"simulator" : false,
|
|
"interface" : "usb",
|
|
"available" : true,
|
|
"platform" : "com.apple.platform.iphoneos",
|
|
"modelCode" : "iPad11,4",
|
|
"identifier" : "f577a7903cc54959be2e34bc4f7f80b7009efcf4",
|
|
"architecture" : "BOGUS",
|
|
"modelName" : "iPad Air 3rd Gen",
|
|
"name" : "iPad 2"
|
|
}
|
|
]
|
|
''';
|
|
fakeProcessManager.addCommand(const FakeCommand(
|
|
command: <String>['xcrun', 'xcdevice', 'list', '--timeout', '2'],
|
|
stdout: devicesOutput,
|
|
));
|
|
|
|
final List<IOSDevice> devices = await xcdevice.getAvailableIOSDevices();
|
|
expect(await devices[0].sdkNameAndVersion,'iOS 13.3 17C54');
|
|
expect(await devices[1].sdkNameAndVersion,'iOS 10.1');
|
|
expect(await devices[2].sdkNameAndVersion,'iOS unknown version');
|
|
}, overrides: <Type, Generator>{
|
|
Platform: () => macPlatform,
|
|
});
|
|
|
|
testUsingContext('use connected entry when filtering out duplicates', () async {
|
|
const String devicesOutput = '''
|
|
[
|
|
{
|
|
"simulator" : false,
|
|
"operatingSystemVersion" : "13.3 (17C54)",
|
|
"interface" : "usb",
|
|
"available" : false,
|
|
"platform" : "com.apple.platform.iphoneos",
|
|
"modelCode" : "iPhone8,1",
|
|
"identifier" : "c4ca6f7a53027d1b7e4972e28478e7a28e2faee2",
|
|
"architecture" : "arm64",
|
|
"modelName" : "iPhone 6s",
|
|
"name" : "iPhone"
|
|
},
|
|
{
|
|
"simulator" : false,
|
|
"operatingSystemVersion" : "13.3 (17C54)",
|
|
"interface" : "usb",
|
|
"available" : false,
|
|
"platform" : "com.apple.platform.iphoneos",
|
|
"modelCode" : "iPhone8,1",
|
|
"identifier" : "c4ca6f7a53027d1b7e4972e28478e7a28e2faee2",
|
|
"architecture" : "arm64",
|
|
"modelName" : "iPhone 6s",
|
|
"name" : "iPhone",
|
|
"error" : {
|
|
"code" : -13,
|
|
"failureReason" : "",
|
|
"description" : "iPhone iPad is not connected",
|
|
"recoverySuggestion" : "Xcode will continue when iPhone is connected and unlocked.",
|
|
"domain" : "com.apple.platform.iphoneos"
|
|
}
|
|
}
|
|
]
|
|
''';
|
|
|
|
fakeProcessManager.addCommand(const FakeCommand(
|
|
command: <String>['xcrun', 'xcdevice', 'list', '--timeout', '2'],
|
|
stdout: devicesOutput,
|
|
));
|
|
final List<IOSDevice> devices = await xcdevice.getAvailableIOSDevices();
|
|
expect(devices, hasLength(1));
|
|
|
|
expect(devices[0].id, 'c4ca6f7a53027d1b7e4972e28478e7a28e2faee2');
|
|
expect(devices[0].name, 'iPhone');
|
|
expect(await devices[0].sdkNameAndVersion, 'iOS 13.3 17C54');
|
|
expect(devices[0].cpuArchitecture, DarwinArch.arm64);
|
|
expect(devices[0].connectionInterface, DeviceConnectionInterface.attached);
|
|
expect(devices[0].isConnected, true);
|
|
|
|
expect(fakeProcessManager, hasNoRemainingExpectations);
|
|
}, overrides: <Type, Generator>{
|
|
Platform: () => macPlatform,
|
|
Artifacts: () => Artifacts.test(),
|
|
});
|
|
|
|
testUsingContext('use entry with sdk when filtering out duplicates', () async {
|
|
const String devicesOutput = '''
|
|
[
|
|
{
|
|
"simulator" : false,
|
|
"interface" : "usb",
|
|
"available" : false,
|
|
"platform" : "com.apple.platform.iphoneos",
|
|
"modelCode" : "iPhone8,1",
|
|
"identifier" : "c4ca6f7a53027d1b7e4972e28478e7a28e2faee2",
|
|
"architecture" : "arm64",
|
|
"modelName" : "iPhone 6s",
|
|
"name" : "iPhone_1",
|
|
"error" : {
|
|
"code" : -13,
|
|
"failureReason" : "",
|
|
"description" : "iPhone iPad is not connected",
|
|
"recoverySuggestion" : "Xcode will continue when iPhone is connected and unlocked.",
|
|
"domain" : "com.apple.platform.iphoneos"
|
|
}
|
|
},
|
|
{
|
|
"simulator" : false,
|
|
"operatingSystemVersion" : "13.3 (17C54)",
|
|
"interface" : "usb",
|
|
"available" : false,
|
|
"platform" : "com.apple.platform.iphoneos",
|
|
"modelCode" : "iPhone8,1",
|
|
"identifier" : "c4ca6f7a53027d1b7e4972e28478e7a28e2faee2",
|
|
"architecture" : "arm64",
|
|
"modelName" : "iPhone 6s",
|
|
"name" : "iPhone_2",
|
|
"error" : {
|
|
"code" : -13,
|
|
"failureReason" : "",
|
|
"description" : "iPhone iPad is not connected",
|
|
"recoverySuggestion" : "Xcode will continue when iPhone is connected and unlocked.",
|
|
"domain" : "com.apple.platform.iphoneos"
|
|
}
|
|
}
|
|
]
|
|
''';
|
|
|
|
fakeProcessManager.addCommand(const FakeCommand(
|
|
command: <String>['xcrun', 'xcdevice', 'list', '--timeout', '2'],
|
|
stdout: devicesOutput,
|
|
));
|
|
final List<IOSDevice> devices = await xcdevice.getAvailableIOSDevices();
|
|
expect(devices, hasLength(1));
|
|
|
|
expect(devices[0].id, 'c4ca6f7a53027d1b7e4972e28478e7a28e2faee2');
|
|
expect(devices[0].name, 'iPhone_2');
|
|
expect(await devices[0].sdkNameAndVersion, 'iOS 13.3 17C54');
|
|
expect(devices[0].cpuArchitecture, DarwinArch.arm64);
|
|
expect(devices[0].connectionInterface, DeviceConnectionInterface.attached);
|
|
expect(devices[0].isConnected, false);
|
|
|
|
expect(fakeProcessManager, hasNoRemainingExpectations);
|
|
}, overrides: <Type, Generator>{
|
|
Platform: () => macPlatform,
|
|
Artifacts: () => Artifacts.test(),
|
|
});
|
|
|
|
testUsingContext('use entry with higher sdk when filtering out duplicates', () async {
|
|
const String devicesOutput = '''
|
|
[
|
|
{
|
|
"simulator" : false,
|
|
"operatingSystemVersion" : "14.3 (17C54)",
|
|
"interface" : "usb",
|
|
"available" : false,
|
|
"platform" : "com.apple.platform.iphoneos",
|
|
"modelCode" : "iPhone8,1",
|
|
"identifier" : "c4ca6f7a53027d1b7e4972e28478e7a28e2faee2",
|
|
"architecture" : "arm64",
|
|
"modelName" : "iPhone 6s",
|
|
"name" : "iPhone_1",
|
|
"error" : {
|
|
"code" : -13,
|
|
"failureReason" : "",
|
|
"description" : "iPhone iPad is not connected",
|
|
"recoverySuggestion" : "Xcode will continue when iPhone is connected and unlocked.",
|
|
"domain" : "com.apple.platform.iphoneos"
|
|
}
|
|
},
|
|
{
|
|
"simulator" : false,
|
|
"operatingSystemVersion" : "13.3 (17C54)",
|
|
"interface" : "usb",
|
|
"available" : false,
|
|
"platform" : "com.apple.platform.iphoneos",
|
|
"modelCode" : "iPhone8,1",
|
|
"identifier" : "c4ca6f7a53027d1b7e4972e28478e7a28e2faee2",
|
|
"architecture" : "arm64",
|
|
"modelName" : "iPhone 6s",
|
|
"name" : "iPhone_2",
|
|
"error" : {
|
|
"code" : -13,
|
|
"failureReason" : "",
|
|
"description" : "iPhone iPad is not connected",
|
|
"recoverySuggestion" : "Xcode will continue when iPhone is connected and unlocked.",
|
|
"domain" : "com.apple.platform.iphoneos"
|
|
}
|
|
}
|
|
]
|
|
''';
|
|
|
|
fakeProcessManager.addCommand(const FakeCommand(
|
|
command: <String>['xcrun', 'xcdevice', 'list', '--timeout', '2'],
|
|
stdout: devicesOutput,
|
|
));
|
|
final List<IOSDevice> devices = await xcdevice.getAvailableIOSDevices();
|
|
expect(devices, hasLength(1));
|
|
|
|
expect(devices[0].id, 'c4ca6f7a53027d1b7e4972e28478e7a28e2faee2');
|
|
expect(devices[0].name, 'iPhone_1');
|
|
expect(await devices[0].sdkNameAndVersion, 'iOS 14.3 17C54');
|
|
expect(devices[0].cpuArchitecture, DarwinArch.arm64);
|
|
expect(devices[0].connectionInterface, DeviceConnectionInterface.attached);
|
|
expect(devices[0].isConnected, false);
|
|
|
|
expect(fakeProcessManager, hasNoRemainingExpectations);
|
|
}, overrides: <Type, Generator>{
|
|
Platform: () => macPlatform,
|
|
Artifacts: () => Artifacts.test(),
|
|
});
|
|
|
|
testUsingContext('handles bad output',() async {
|
|
fakeProcessManager.addCommand(const FakeCommand(
|
|
command: <String>['xcrun', 'xcdevice', 'list', '--timeout', '2'],
|
|
stdout: 'Something bad happened, not JSON',
|
|
));
|
|
|
|
final List<IOSDevice> devices = await xcdevice.getAvailableIOSDevices();
|
|
expect(devices, isEmpty);
|
|
expect(logger.errorText, contains('xcdevice returned non-JSON response'));
|
|
}, overrides: <Type, Generator>{
|
|
Platform: () => macPlatform,
|
|
});
|
|
|
|
group('with CoreDevices', () {
|
|
testUsingContext('returns devices with corresponding CoreDevices', () async {
|
|
const String devicesOutput = '''
|
|
[
|
|
{
|
|
"simulator" : true,
|
|
"operatingSystemVersion" : "13.3 (17K446)",
|
|
"available" : true,
|
|
"platform" : "com.apple.platform.appletvsimulator",
|
|
"modelCode" : "AppleTV5,3",
|
|
"identifier" : "CBB5E1ED-2172-446E-B4E7-F2B5823DBBA6",
|
|
"architecture" : "x86_64",
|
|
"modelName" : "Apple TV",
|
|
"name" : "Apple TV"
|
|
},
|
|
{
|
|
"simulator" : false,
|
|
"operatingSystemVersion" : "13.3 (17C54)",
|
|
"interface" : "usb",
|
|
"available" : true,
|
|
"platform" : "com.apple.platform.iphoneos",
|
|
"modelCode" : "iPhone8,1",
|
|
"identifier" : "00008027-00192736010F802E",
|
|
"architecture" : "arm64",
|
|
"modelName" : "iPhone 6s",
|
|
"name" : "An iPhone (Space Gray)"
|
|
},
|
|
{
|
|
"simulator" : false,
|
|
"operatingSystemVersion" : "10.1 (14C54)",
|
|
"interface" : "usb",
|
|
"available" : true,
|
|
"platform" : "com.apple.platform.iphoneos",
|
|
"modelCode" : "iPad11,4",
|
|
"identifier" : "98206e7a4afd4aedaff06e687594e089dede3c44",
|
|
"architecture" : "armv7",
|
|
"modelName" : "iPad Air 3rd Gen",
|
|
"name" : "iPad 1"
|
|
},
|
|
{
|
|
"simulator" : false,
|
|
"operatingSystemVersion" : "10.1 (14C54)",
|
|
"interface" : "network",
|
|
"available" : true,
|
|
"platform" : "com.apple.platform.iphoneos",
|
|
"modelCode" : "iPad11,4",
|
|
"identifier" : "234234234234234234345445687594e089dede3c44",
|
|
"architecture" : "arm64",
|
|
"modelName" : "iPad Air 3rd Gen",
|
|
"name" : "A networked iPad"
|
|
},
|
|
{
|
|
"simulator" : false,
|
|
"operatingSystemVersion" : "10.1 (14C54)",
|
|
"interface" : "usb",
|
|
"available" : true,
|
|
"platform" : "com.apple.platform.iphoneos",
|
|
"modelCode" : "iPad11,4",
|
|
"identifier" : "f577a7903cc54959be2e34bc4f7f80b7009efcf4",
|
|
"architecture" : "BOGUS",
|
|
"modelName" : "iPad Air 3rd Gen",
|
|
"name" : "iPad 2"
|
|
},
|
|
{
|
|
"simulator" : true,
|
|
"operatingSystemVersion" : "6.1.1 (17S445)",
|
|
"available" : true,
|
|
"platform" : "com.apple.platform.watchsimulator",
|
|
"modelCode" : "Watch5,4",
|
|
"identifier" : "2D74FB11-88A0-44D0-B81E-C0C142B1C94A",
|
|
"architecture" : "i386",
|
|
"modelName" : "Apple Watch Series 5 - 44mm",
|
|
"name" : "Apple Watch Series 5 - 44mm"
|
|
},
|
|
{
|
|
"simulator" : false,
|
|
"operatingSystemVersion" : "13.3 (17C54)",
|
|
"interface" : "usb",
|
|
"available" : false,
|
|
"platform" : "com.apple.platform.iphoneos",
|
|
"modelCode" : "iPhone8,1",
|
|
"identifier" : "c4ca6f7a53027d1b7e4972e28478e7a28e2faee2",
|
|
"architecture" : "arm64",
|
|
"modelName" : "iPhone 6s",
|
|
"name" : "iPhone",
|
|
"error" : {
|
|
"code" : -9,
|
|
"failureReason" : "",
|
|
"description" : "iPhone is not paired with your computer.",
|
|
"domain" : "com.apple.platform.iphoneos"
|
|
}
|
|
}
|
|
]
|
|
''';
|
|
coreDeviceControl.devices.addAll(<FakeIOSCoreDevice>[
|
|
FakeIOSCoreDevice(
|
|
udid: '00008027-00192736010F802E',
|
|
connectionInterface: DeviceConnectionInterface.wireless,
|
|
developerModeStatus: 'enabled',
|
|
),
|
|
FakeIOSCoreDevice(
|
|
connectionInterface: DeviceConnectionInterface.wireless,
|
|
developerModeStatus: 'enabled',
|
|
),
|
|
FakeIOSCoreDevice(
|
|
udid: '234234234234234234345445687594e089dede3c44',
|
|
connectionInterface: DeviceConnectionInterface.attached,
|
|
),
|
|
FakeIOSCoreDevice(
|
|
udid: 'f577a7903cc54959be2e34bc4f7f80b7009efcf4',
|
|
connectionInterface: DeviceConnectionInterface.attached,
|
|
developerModeStatus: 'disabled',
|
|
),
|
|
]);
|
|
|
|
fakeProcessManager.addCommand(const FakeCommand(
|
|
command: <String>['xcrun', 'xcdevice', 'list', '--timeout', '2'],
|
|
stdout: devicesOutput,
|
|
));
|
|
|
|
final List<IOSDevice> devices = await xcdevice.getAvailableIOSDevices();
|
|
expect(devices, hasLength(5));
|
|
expect(devices[0].id, '00008027-00192736010F802E');
|
|
expect(devices[0].name, 'An iPhone (Space Gray)');
|
|
expect(await devices[0].sdkNameAndVersion, 'iOS 13.3 17C54');
|
|
expect(devices[0].cpuArchitecture, DarwinArch.arm64);
|
|
expect(devices[0].connectionInterface, DeviceConnectionInterface.wireless);
|
|
expect(devices[0].isConnected, true);
|
|
expect(devices[0].devModeEnabled, true);
|
|
|
|
expect(devices[1].id, '98206e7a4afd4aedaff06e687594e089dede3c44');
|
|
expect(devices[1].name, 'iPad 1');
|
|
expect(await devices[1].sdkNameAndVersion, 'iOS 10.1 14C54');
|
|
expect(devices[1].cpuArchitecture, DarwinArch.armv7);
|
|
expect(devices[1].connectionInterface, DeviceConnectionInterface.attached);
|
|
expect(devices[1].isConnected, true);
|
|
expect(devices[1].devModeEnabled, true);
|
|
|
|
expect(devices[2].id, '234234234234234234345445687594e089dede3c44');
|
|
expect(devices[2].name, 'A networked iPad');
|
|
expect(await devices[2].sdkNameAndVersion, 'iOS 10.1 14C54');
|
|
expect(devices[2].cpuArchitecture, DarwinArch.arm64); // Defaults to arm64 for unknown architecture.
|
|
expect(devices[2].connectionInterface, DeviceConnectionInterface.attached);
|
|
expect(devices[2].isConnected, true);
|
|
expect(devices[2].devModeEnabled, false);
|
|
|
|
expect(devices[3].id, 'f577a7903cc54959be2e34bc4f7f80b7009efcf4');
|
|
expect(devices[3].name, 'iPad 2');
|
|
expect(await devices[3].sdkNameAndVersion, 'iOS 10.1 14C54');
|
|
expect(devices[3].cpuArchitecture, DarwinArch.arm64); // Defaults to arm64 for unknown architecture.
|
|
expect(devices[3].connectionInterface, DeviceConnectionInterface.attached);
|
|
expect(devices[3].isConnected, true);
|
|
expect(devices[3].devModeEnabled, false);
|
|
|
|
expect(devices[4].id, 'c4ca6f7a53027d1b7e4972e28478e7a28e2faee2');
|
|
expect(devices[4].name, 'iPhone');
|
|
expect(await devices[4].sdkNameAndVersion, 'iOS 13.3 17C54');
|
|
expect(devices[4].cpuArchitecture, DarwinArch.arm64);
|
|
expect(devices[4].connectionInterface, DeviceConnectionInterface.attached);
|
|
expect(devices[4].isConnected, false);
|
|
expect(devices[4].devModeEnabled, true);
|
|
|
|
expect(fakeProcessManager, hasNoRemainingExpectations);
|
|
|
|
expect(fakeAnalytics.sentEvents, contains(
|
|
Event.appleUsageEvent(
|
|
workflow: 'device',
|
|
parameter: 'ios-trust-failure',
|
|
)
|
|
));
|
|
}, overrides: <Type, Generator>{
|
|
Platform: () => macPlatform,
|
|
Artifacts: () => Artifacts.test(),
|
|
});
|
|
|
|
});
|
|
});
|
|
|
|
group('diagnostics', () {
|
|
final FakePlatform macPlatform = FakePlatform(operatingSystem: 'macos');
|
|
testUsingContext('uses cache', () async {
|
|
const String devicesOutput = '''
|
|
[
|
|
{
|
|
"simulator" : false,
|
|
"operatingSystemVersion" : "13.3 (17C54)",
|
|
"interface" : "network",
|
|
"available" : false,
|
|
"platform" : "com.apple.platform.iphoneos",
|
|
"modelCode" : "iPhone8,1",
|
|
"identifier" : "d83d5bc53967baa0ee18626ba87b6254b2ab5418",
|
|
"architecture" : "arm64",
|
|
"modelName" : "iPhone 6s",
|
|
"error" : {
|
|
"code" : -13,
|
|
"failureReason" : "",
|
|
"domain" : "com.apple.platform.iphoneos"
|
|
}
|
|
}
|
|
]
|
|
''';
|
|
|
|
fakeProcessManager.addCommand(const FakeCommand(
|
|
command: <String>['xcrun', 'xcdevice', 'list', '--timeout', '2'],
|
|
stdout: devicesOutput,
|
|
));
|
|
|
|
await xcdevice.getAvailableIOSDevices();
|
|
final List<String> errors = await xcdevice.getDiagnostics();
|
|
expect(errors, hasLength(1));
|
|
expect(fakeProcessManager, hasNoRemainingExpectations);
|
|
}, overrides: <Type, Generator>{
|
|
Platform: () => macPlatform,
|
|
});
|
|
|
|
testWithoutContext('diagnostics xcdevice fails', () async {
|
|
fakeProcessManager.addCommand(const FakeCommand(
|
|
command: <String>['xcrun', 'xcdevice', 'list', '--timeout', '2'],
|
|
exception: ProcessException('xcrun', <String>['xcdevice', 'list', '--timeout', '2']),
|
|
));
|
|
|
|
expect(await xcdevice.getDiagnostics(), isEmpty);
|
|
});
|
|
|
|
testUsingContext('returns error message', () async {
|
|
const String devicesOutput = '''
|
|
[
|
|
{
|
|
"simulator" : false,
|
|
"operatingSystemVersion" : "13.3 (17C54)",
|
|
"interface" : "usb",
|
|
"available" : false,
|
|
"platform" : "com.apple.platform.iphoneos",
|
|
"modelCode" : "iPhone8,1",
|
|
"identifier" : "98206e7a4afd4aedaff06e687594e089dede3c44",
|
|
"architecture" : "arm64",
|
|
"modelName" : "iPhone 6s",
|
|
"name" : "An iPhone (Space Gray)",
|
|
"error" : {
|
|
"code" : -9,
|
|
"failureReason" : "",
|
|
"underlyingErrors" : [
|
|
{
|
|
"code" : 5,
|
|
"failureReason" : "allowsSecureServices: 1. isConnected: 0. Platform: <DVTPlatform:0x7f804ce32880:'com.apple.platform.iphoneos':<DVTFilePath:0x7f804ce32800:'/Users/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform'>>. DTDKDeviceIdentifierIsIDID: 0",
|
|
"description" : "📱<DVTiOSDevice (0x7f801f190450), iPhone, iPhone, 13.3 (17C54), d83d5bc53967baa0ee18626ba87b6254b2ab5418> -- Failed _shouldMakeReadyForDevelopment check even though device is not locked by passcode.",
|
|
"recoverySuggestion" : "",
|
|
"domain" : "com.apple.platform.iphoneos"
|
|
}
|
|
],
|
|
"description" : "iPhone is not paired with your computer.",
|
|
"recoverySuggestion" : "To use iPhone with Xcode, unlock it and choose to trust this computer when prompted.",
|
|
"domain" : "com.apple.platform.iphoneos"
|
|
}
|
|
},
|
|
{
|
|
"simulator" : false,
|
|
"operatingSystemVersion" : "13.3 (17C54)",
|
|
"interface" : "usb",
|
|
"available" : false,
|
|
"platform" : "com.apple.platform.iphoneos",
|
|
"modelCode" : "iPhone8,1",
|
|
"identifier" : "d83d5bc53967baa0ee18626ba87b6254b2ab5418",
|
|
"architecture" : "arm64",
|
|
"modelName" : "iPhone 6s",
|
|
"name" : "iPhone",
|
|
"error" : {
|
|
"failureReason" : "",
|
|
"description" : "iPhone is not paired with your computer",
|
|
"domain" : "com.apple.platform.iphoneos"
|
|
}
|
|
},
|
|
{
|
|
"simulator" : false,
|
|
"operatingSystemVersion" : "13.3 (17C54)",
|
|
"interface" : "network",
|
|
"available" : false,
|
|
"platform" : "com.apple.platform.iphoneos",
|
|
"modelCode" : "iPhone8,1",
|
|
"identifier" : "d83d5bc53967baa0ee18626ba87b6254b2ab5418",
|
|
"architecture" : "arm64",
|
|
"modelName" : "iPhone 6s",
|
|
"error" : {
|
|
"code" : -13,
|
|
"failureReason" : "",
|
|
"domain" : "com.apple.platform.iphoneos"
|
|
}
|
|
},
|
|
{
|
|
"simulator" : false,
|
|
"operatingSystemVersion" : "13.3 (17C54)",
|
|
"interface" : "usb",
|
|
"available" : false,
|
|
"platform" : "com.apple.platform.iphoneos",
|
|
"modelCode" : "iPhone8,1",
|
|
"identifier" : "43ad2fda7991b34fe1acbda82f9e2fd3d6ddc9f7",
|
|
"architecture" : "arm64",
|
|
"modelName" : "iPhone 6s",
|
|
"name" : "iPhone",
|
|
"error" : {
|
|
"code" : -10,
|
|
"failureReason" : "",
|
|
"description" : "iPhone is busy: Preparing debugger support for iPhone",
|
|
"recoverySuggestion" : "Xcode will continue when iPhone is finished.",
|
|
"domain" : "com.apple.platform.iphoneos"
|
|
}
|
|
},
|
|
{
|
|
"modelCode" : "iPad8,5",
|
|
"simulator" : false,
|
|
"modelName" : "iPad Pro (12.9-inch) (3rd generation)",
|
|
"error" : {
|
|
"code" : -13,
|
|
"failureReason" : "",
|
|
"underlyingErrors" : [
|
|
{
|
|
"code" : 4,
|
|
"failureReason" : "",
|
|
"description" : "iPad is locked.",
|
|
"recoverySuggestion" : "To use iPad with Xcode, unlock it.",
|
|
"domain" : "DVTDeviceIneligibilityErrorDomain"
|
|
}
|
|
],
|
|
"description" : "iPad is not connected",
|
|
"recoverySuggestion" : "Xcode will continue when iPad is connected.",
|
|
"domain" : "com.apple.platform.iphoneos"
|
|
},
|
|
"operatingSystemVersion" : "15.6 (19G5027e)",
|
|
"identifier" : "00008027-0019253601068123",
|
|
"platform" : "com.apple.platform.iphoneos",
|
|
"architecture" : "arm64e",
|
|
"interface" : "usb",
|
|
"available" : false,
|
|
"name" : "iPad",
|
|
"modelUTI" : "com.apple.ipad-pro-12point9-1"
|
|
}
|
|
]
|
|
''';
|
|
|
|
fakeProcessManager.addCommand(const FakeCommand(
|
|
command: <String>['xcrun', 'xcdevice', 'list', '--timeout', '2'],
|
|
stdout: devicesOutput,
|
|
));
|
|
|
|
final List<String> errors = await xcdevice.getDiagnostics();
|
|
expect(errors, hasLength(4));
|
|
|
|
expect(errors[0], 'Error: iPhone is not paired with your computer. To use iPhone with Xcode, unlock it and choose to trust this computer when prompted. (code -9)');
|
|
expect(errors[1], 'Error: iPhone is not paired with your computer.');
|
|
expect(errors[2], 'Error: Xcode pairing error. (code -13)');
|
|
expect(errors[3], 'Error: iPhone is busy: Preparing debugger support for iPhone. Xcode will continue when iPhone is finished. (code -10)');
|
|
expect(errors, isNot(contains('Xcode will continue')));
|
|
expect(fakeProcessManager, hasNoRemainingExpectations);
|
|
}, overrides: <Type, Generator>{
|
|
Platform: () => macPlatform,
|
|
});
|
|
});
|
|
});
|
|
});
|
|
}
|
|
|
|
class FakeXcodeProjectInterpreter extends Fake implements XcodeProjectInterpreter {
|
|
@override
|
|
Version version = Version(0, 0, 0);
|
|
|
|
@override
|
|
bool isInstalled = false;
|
|
|
|
@override
|
|
List<String> xcrunCommand() => <String>['xcrun'];
|
|
}
|
|
|
|
class FakeXcodeDebug extends Fake implements XcodeDebug {}
|
|
|
|
class FakeIOSCoreDeviceControl extends Fake implements IOSCoreDeviceControl {
|
|
|
|
List<FakeIOSCoreDevice> devices = <FakeIOSCoreDevice>[];
|
|
|
|
@override
|
|
Future<List<IOSCoreDevice>> getCoreDevices({Duration timeout = Duration.zero}) async {
|
|
return devices;
|
|
}
|
|
}
|
|
|
|
class FakeIOSCoreDevice extends Fake implements IOSCoreDevice {
|
|
FakeIOSCoreDevice({
|
|
this.udid,
|
|
this.connectionInterface,
|
|
this.developerModeStatus,
|
|
});
|
|
|
|
final String? developerModeStatus;
|
|
|
|
@override
|
|
final String? udid;
|
|
|
|
@override
|
|
final DeviceConnectionInterface? connectionInterface;
|
|
|
|
@override
|
|
IOSCoreDeviceProperties? get deviceProperties => FakeIOSCoreDeviceProperties(developerModeStatus: developerModeStatus);
|
|
}
|
|
|
|
class FakeIOSCoreDeviceProperties extends Fake implements IOSCoreDeviceProperties {
|
|
FakeIOSCoreDeviceProperties({required this.developerModeStatus});
|
|
|
|
@override
|
|
final String? developerModeStatus;
|
|
}
|