blob: b8d2f1988e8c179cabb7fbaca0968bb570d44ffc [file] [log] [blame]
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter_tools/src/base/user_messages.dart';
import 'package:flutter_tools/src/base/version.dart';
import 'package:flutter_tools/src/doctor_validator.dart';
import 'package:flutter_tools/src/ios/simulators.dart';
import 'package:flutter_tools/src/ios/xcodeproj.dart';
import 'package:flutter_tools/src/macos/xcode.dart';
import 'package:flutter_tools/src/macos/xcode_validator.dart';
import 'package:test/fake.dart';
import '../../src/common.dart';
import '../../src/fake_process_manager.dart';
void main() {
group('Xcode validation', () {
testWithoutContext('Emits missing status when Xcode is not installed', () async {
final ProcessManager processManager = FakeProcessManager.any();
final Xcode xcode = Xcode.test(
processManager: processManager,
xcodeProjectInterpreter: XcodeProjectInterpreter.test(processManager: processManager, version: null),
);
final XcodeValidator validator = XcodeValidator(
xcode: xcode,
userMessages: UserMessages(),
iosSimulatorUtils: FakeIOSSimulatorUtils(),
);
final ValidationResult result = await validator.validate();
expect(result.type, ValidationType.missing);
expect(result.statusInfo, isNull);
expect(result.messages.last.type, ValidationMessageType.error);
expect(result.messages.last.message, contains('Xcode not installed'));
});
testWithoutContext('Emits missing status when Xcode installation is incomplete', () async {
final ProcessManager processManager = FakeProcessManager.list(<FakeCommand>[
const FakeCommand(
command: <String>['/usr/bin/xcode-select', '--print-path'],
stdout: '/Library/Developer/CommandLineTools',
),
]);
final Xcode xcode = Xcode.test(
processManager: processManager,
xcodeProjectInterpreter: XcodeProjectInterpreter.test(processManager: processManager, version: null),
);
final XcodeValidator validator = XcodeValidator(
xcode: xcode,
userMessages: UserMessages(),
iosSimulatorUtils: FakeIOSSimulatorUtils(),
);
final ValidationResult result = await validator.validate();
expect(result.type, ValidationType.missing);
expect(result.messages.last.type, ValidationMessageType.error);
expect(result.messages.last.message, contains('Xcode installation is incomplete'));
});
testWithoutContext('Emits partial status when Xcode version too low', () async {
final ProcessManager processManager = FakeProcessManager.any();
final Xcode xcode = Xcode.test(
processManager: processManager,
xcodeProjectInterpreter: XcodeProjectInterpreter.test(processManager: processManager, version: Version(7, 0, 1)),
);
final XcodeValidator validator = XcodeValidator(
xcode: xcode,
userMessages: UserMessages(),
iosSimulatorUtils: FakeIOSSimulatorUtils(),
);
final ValidationResult result = await validator.validate();
expect(result.type, ValidationType.partial);
expect(result.messages.last.type, ValidationMessageType.error);
expect(result.messages.last.message, contains('Flutter requires Xcode 14 or higher'));
});
testWithoutContext('Emits partial status when Xcode below recommended version', () async {
final ProcessManager processManager = FakeProcessManager.any();
final Xcode xcode = Xcode.test(
processManager: processManager,
xcodeProjectInterpreter: XcodeProjectInterpreter.test(processManager: processManager, version: Version(14, 4, null)),
);
final XcodeValidator validator = XcodeValidator(
xcode: xcode,
userMessages: UserMessages(),
iosSimulatorUtils: FakeIOSSimulatorUtils(),
);
final ValidationResult result = await validator.validate();
expect(result.type, ValidationType.partial);
expect(result.messages.last.type, ValidationMessageType.hint);
expect(result.messages.last.message, contains('Flutter recommends a minimum Xcode version of 15'));
}, skip: false); // [intended] Skip this test when minimum and required check versions converge.
testWithoutContext('Emits partial status when Xcode EULA not signed', () async {
final ProcessManager processManager = FakeProcessManager.list(<FakeCommand>[
const FakeCommand(
command: <String>['/usr/bin/xcode-select', '--print-path'],
stdout: '/Library/Developer/CommandLineTools',
),
const FakeCommand(
command: <String>[
'which',
'sysctl',
],
),
const FakeCommand(
command: <String>[
'sysctl',
'hw.optional.arm64',
],
exitCode: 1,
),
const FakeCommand(
command: <String>['xcrun', 'clang'],
exitCode: 1,
stderr:
'Xcode EULA has not been accepted.\nLaunch Xcode and accept the license.',
),
const FakeCommand(
command: <String>['xcrun', 'simctl', 'list', 'devices', 'booted'],
),
]);
final Xcode xcode = Xcode.test(
processManager: processManager,
xcodeProjectInterpreter: XcodeProjectInterpreter.test(processManager: processManager),
);
final XcodeValidator validator = XcodeValidator(
xcode: xcode,
userMessages: UserMessages(),
iosSimulatorUtils: FakeIOSSimulatorUtils(),
);
final ValidationResult result = await validator.validate();
expect(result.type, ValidationType.partial);
expect(result.messages.last.type, ValidationMessageType.error);
expect(result.messages.last.message, contains('code end user license agreement not signed'));
expect(processManager, hasNoRemainingExpectations);
});
testWithoutContext('Emits partial status when simctl is not installed', () async {
final ProcessManager processManager = FakeProcessManager.list(<FakeCommand>[
const FakeCommand(
command: <String>['/usr/bin/xcode-select', '--print-path'],
stdout: '/Library/Developer/CommandLineTools',
),
const FakeCommand(
command: <String>[
'which',
'sysctl',
],
),
const FakeCommand(
command: <String>[
'sysctl',
'hw.optional.arm64',
],
exitCode: 1,
),
const FakeCommand(
command: <String>['xcrun', 'clang'],
),
const FakeCommand(
command: <String>['xcrun', 'simctl', 'list', 'devices', 'booted'],
exitCode: 1,
),
]);
final Xcode xcode = Xcode.test(
processManager: processManager,
xcodeProjectInterpreter: XcodeProjectInterpreter.test(processManager: processManager),
);
final XcodeValidator validator = XcodeValidator(
xcode: xcode,
userMessages: UserMessages(),
iosSimulatorUtils: FakeIOSSimulatorUtils(),
);
final ValidationResult result = await validator.validate();
expect(result.type, ValidationType.partial);
expect(result.messages.last.type, ValidationMessageType.error);
expect(result.messages.last.message, contains('Xcode requires additional components'));
expect(processManager, hasNoRemainingExpectations);
});
testWithoutContext('Emits partial status when unable to find simulator SDK', () async {
final ProcessManager processManager = FakeProcessManager.list(<FakeCommand>[
const FakeCommand(
command: <String>['/usr/bin/xcode-select', '--print-path'],
stdout: '/Library/Developer/CommandLineTools',
),
const FakeCommand(
command: <String>[
'which',
'sysctl',
],
),
const FakeCommand(
command: <String>[
'sysctl',
'hw.optional.arm64',
],
exitCode: 1,
),
const FakeCommand(
command: <String>['xcrun', 'clang'],
),
const FakeCommand(
command: <String>['xcrun', 'simctl', 'list', 'devices', 'booted'],
),
const FakeCommand(
command: <String>['xcrun', '--sdk', 'iphonesimulator', '--show-sdk-platform-version'],
),
]);
final Xcode xcode = Xcode.test(
processManager: processManager,
xcodeProjectInterpreter: XcodeProjectInterpreter.test(processManager: processManager),
);
final XcodeValidator validator = XcodeValidator(
xcode: xcode,
userMessages: UserMessages(),
iosSimulatorUtils: FakeIOSSimulatorUtils(),
);
final ValidationResult result = await validator.validate();
expect(result.type, ValidationType.partial);
expect(result.messages.last.type, ValidationMessageType.error);
expect(result.messages.last.message, contains('Unable to find the iPhone Simulator SDK'));
expect(processManager, hasNoRemainingExpectations);
});
testWithoutContext('Emits partial status when unable to get simulator runtimes', () async {
final ProcessManager processManager = FakeProcessManager.list(<FakeCommand>[
const FakeCommand(
command: <String>['/usr/bin/xcode-select', '--print-path'],
stdout: '/Library/Developer/CommandLineTools',
),
const FakeCommand(
command: <String>[
'which',
'sysctl',
],
),
const FakeCommand(
command: <String>[
'sysctl',
'hw.optional.arm64',
],
exitCode: 1,
),
const FakeCommand(
command: <String>['xcrun', 'clang'],
),
const FakeCommand(
command: <String>['xcrun', 'simctl', 'list', 'devices', 'booted'],
),
const FakeCommand(
command: <String>['xcrun', '--sdk', 'iphonesimulator', '--show-sdk-platform-version'],
stdout: '17.0'
),
]);
final Xcode xcode = Xcode.test(
processManager: processManager,
xcodeProjectInterpreter: XcodeProjectInterpreter.test(processManager: processManager),
);
final XcodeValidator validator = XcodeValidator(
xcode: xcode,
userMessages: UserMessages(),
iosSimulatorUtils: FakeIOSSimulatorUtils(),
);
final ValidationResult result = await validator.validate();
expect(result.type, ValidationType.partial);
expect(result.messages.last.type, ValidationMessageType.error);
expect(result.messages.last.message, contains('Unable to get list of installed Simulator runtimes'));
expect(processManager, hasNoRemainingExpectations);
});
testWithoutContext('Emits partial status with hint when simulator runtimes do not match SDK', () async {
final ProcessManager processManager = FakeProcessManager.list(<FakeCommand>[
const FakeCommand(
command: <String>['/usr/bin/xcode-select', '--print-path'],
stdout: '/Library/Developer/CommandLineTools',
),
const FakeCommand(
command: <String>[
'which',
'sysctl',
],
),
const FakeCommand(
command: <String>[
'sysctl',
'hw.optional.arm64',
],
exitCode: 1,
),
const FakeCommand(
command: <String>['xcrun', 'clang'],
),
const FakeCommand(
command: <String>['xcrun', 'simctl', 'list', 'devices', 'booted'],
),
const FakeCommand(
command: <String>['xcrun', '--sdk', 'iphonesimulator', '--show-sdk-platform-version'],
stdout: '17.0'
),
]);
final Xcode xcode = Xcode.test(
processManager: processManager,
xcodeProjectInterpreter: XcodeProjectInterpreter.test(processManager: processManager),
);
final FakeIOSSimulatorUtils simulatorUtils = FakeIOSSimulatorUtils(runtimes: <IOSSimulatorRuntime>[
IOSSimulatorRuntime.fromJson(<String, String>{'version': '16.0'}),
]);
final XcodeValidator validator = XcodeValidator(
xcode: xcode,
userMessages: UserMessages(),
iosSimulatorUtils: simulatorUtils,
);
final ValidationResult result = await validator.validate();
expect(result.type, ValidationType.partial);
expect(result.messages.last.type, ValidationMessageType.hint);
expect(result.messages.last.message, contains('iOS 17.0 Simulator not installed'));
expect(processManager, hasNoRemainingExpectations);
});
testWithoutContext('Succeeds when all checks pass', () async {
final ProcessManager processManager = FakeProcessManager.list(<FakeCommand>[
const FakeCommand(
command: <String>['/usr/bin/xcode-select', '--print-path'],
stdout: '/Library/Developer/CommandLineTools',
),
const FakeCommand(
command: <String>[
'which',
'sysctl',
],
),
const FakeCommand(
command: <String>[
'sysctl',
'hw.optional.arm64',
],
exitCode: 1,
),
const FakeCommand(
command: <String>['xcrun', 'clang'],
),
const FakeCommand(
command: <String>['xcrun', 'simctl', 'list', 'devices', 'booted'],
),
const FakeCommand(
command: <String>['xcrun', '--sdk', 'iphonesimulator', '--show-sdk-platform-version'],
stdout: '17.0'
),
]);
final Xcode xcode = Xcode.test(
processManager: processManager,
xcodeProjectInterpreter: XcodeProjectInterpreter.test(processManager: processManager),
);
final FakeIOSSimulatorUtils simulatorUtils = FakeIOSSimulatorUtils(runtimes: <IOSSimulatorRuntime>[
IOSSimulatorRuntime.fromJson(<String, String>{'version': '17.0'}),
]);
final XcodeValidator validator = XcodeValidator(
xcode: xcode,
userMessages: UserMessages(),
iosSimulatorUtils: simulatorUtils,
);
final ValidationResult result = await validator.validate();
expect(result.type, ValidationType.success);
expect(result.messages.length, 2);
final ValidationMessage firstMessage = result.messages.first;
expect(firstMessage.type, ValidationMessageType.information);
expect(firstMessage.message, 'Xcode at /Library/Developer/CommandLineTools');
expect(result.statusInfo, '1000.0.0');
expect(result.messages[1].message, 'Build 13C100');
expect(processManager, hasNoRemainingExpectations);
});
});
}
class FakeIOSSimulatorUtils extends Fake implements IOSSimulatorUtils {
FakeIOSSimulatorUtils({
this.runtimes,
});
List<IOSSimulatorRuntime>? runtimes;
List<IOSSimulatorRuntime> get _runtimesList {
return runtimes ?? <IOSSimulatorRuntime>[];
}
@override
Future<List<IOSSimulatorRuntime>> getAvailableIOSRuntimes() async {
return _runtimesList;
}
}