blob: 259d8ea36cd2ec0d5eeabecd6067be284cc13f84 [file] [log] [blame]
// Copyright 2013 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:convert';
import 'dart:io' as io;
import 'package:file/file.dart';
import 'package:file/local.dart';
import 'package:flutter_plugin_tools/src/common/xcode.dart';
import 'package:test/test.dart';
import '../mocks.dart';
import '../util.dart';
void main() {
late RecordingProcessRunner processRunner;
late Xcode xcode;
setUp(() {
processRunner = RecordingProcessRunner();
xcode = Xcode(processRunner: processRunner);
});
group('findBestAvailableIphoneSimulator', () {
test('finds the newest device', () async {
const String expectedDeviceId = '1E76A0FD-38AC-4537-A989-EA639D7D012A';
// Note: This uses `dynamic` deliberately, and should not be updated to
// Object, in order to ensure that the code correctly handles this return
// type from JSON decoding.
final Map<String, dynamic> devices = <String, dynamic>{
'runtimes': <Map<String, dynamic>>[
<String, dynamic>{
'bundlePath':
'/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS 13.0.simruntime',
'buildversion': '17A577',
'runtimeRoot':
'/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS 13.0.simruntime/Contents/Resources/RuntimeRoot',
'identifier': 'com.apple.CoreSimulator.SimRuntime.iOS-13-0',
'version': '13.0',
'isAvailable': true,
'name': 'iOS 13.0'
},
<String, dynamic>{
'bundlePath':
'/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS 13.4.simruntime',
'buildversion': '17L255',
'runtimeRoot':
'/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS 13.4.simruntime/Contents/Resources/RuntimeRoot',
'identifier': 'com.apple.CoreSimulator.SimRuntime.iOS-13-4',
'version': '13.4',
'isAvailable': true,
'name': 'iOS 13.4'
},
<String, dynamic>{
'bundlePath':
'/Applications/Xcode_11_7.app/Contents/Developer/Platforms/WatchOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/watchOS.simruntime',
'buildversion': '17T531',
'runtimeRoot':
'/Applications/Xcode_11_7.app/Contents/Developer/Platforms/WatchOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/watchOS.simruntime/Contents/Resources/RuntimeRoot',
'identifier': 'com.apple.CoreSimulator.SimRuntime.watchOS-6-2',
'version': '6.2.1',
'isAvailable': true,
'name': 'watchOS 6.2'
}
],
'devices': <String, dynamic>{
'com.apple.CoreSimulator.SimRuntime.iOS-13-4': <Map<String, dynamic>>[
<String, dynamic>{
'dataPath':
'/Users/xxx/Library/Developer/CoreSimulator/Devices/2706BBEB-1E01-403E-A8E9-70E8E5A24774/data',
'logPath':
'/Users/xxx/Library/Logs/CoreSimulator/2706BBEB-1E01-403E-A8E9-70E8E5A24774',
'udid': '2706BBEB-1E01-403E-A8E9-70E8E5A24774',
'isAvailable': true,
'deviceTypeIdentifier':
'com.apple.CoreSimulator.SimDeviceType.iPhone-8',
'state': 'Shutdown',
'name': 'iPhone 8'
},
<String, dynamic>{
'dataPath':
'/Users/xxx/Library/Developer/CoreSimulator/Devices/1E76A0FD-38AC-4537-A989-EA639D7D012A/data',
'logPath':
'/Users/xxx/Library/Logs/CoreSimulator/1E76A0FD-38AC-4537-A989-EA639D7D012A',
'udid': expectedDeviceId,
'isAvailable': true,
'deviceTypeIdentifier':
'com.apple.CoreSimulator.SimDeviceType.iPhone-8-Plus',
'state': 'Shutdown',
'name': 'iPhone 8 Plus'
}
]
}
};
processRunner.mockProcessesForExecutable['xcrun'] = <io.Process>[
MockProcess(stdout: jsonEncode(devices)),
];
expect(await xcode.findBestAvailableIphoneSimulator(), expectedDeviceId);
});
test('ignores non-iOS runtimes', () async {
// Note: This uses `dynamic` deliberately, and should not be updated to
// Object, in order to ensure that the code correctly handles this return
// type from JSON decoding.
final Map<String, dynamic> devices = <String, dynamic>{
'runtimes': <Map<String, dynamic>>[
<String, dynamic>{
'bundlePath':
'/Applications/Xcode_11_7.app/Contents/Developer/Platforms/WatchOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/watchOS.simruntime',
'buildversion': '17T531',
'runtimeRoot':
'/Applications/Xcode_11_7.app/Contents/Developer/Platforms/WatchOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/watchOS.simruntime/Contents/Resources/RuntimeRoot',
'identifier': 'com.apple.CoreSimulator.SimRuntime.watchOS-6-2',
'version': '6.2.1',
'isAvailable': true,
'name': 'watchOS 6.2'
}
],
'devices': <String, dynamic>{
'com.apple.CoreSimulator.SimRuntime.watchOS-6-2':
<Map<String, dynamic>>[
<String, dynamic>{
'dataPath':
'/Users/xxx/Library/Developer/CoreSimulator/Devices/1E76A0FD-38AC-4537-A989-EA639D7D012A/data',
'logPath':
'/Users/xxx/Library/Logs/CoreSimulator/1E76A0FD-38AC-4537-A989-EA639D7D012A',
'udid': '1E76A0FD-38AC-4537-A989-EA639D7D012A',
'isAvailable': true,
'deviceTypeIdentifier':
'com.apple.CoreSimulator.SimDeviceType.Apple-Watch-38mm',
'state': 'Shutdown',
'name': 'Apple Watch'
}
]
}
};
processRunner.mockProcessesForExecutable['xcrun'] = <io.Process>[
MockProcess(stdout: jsonEncode(devices)),
];
expect(await xcode.findBestAvailableIphoneSimulator(), null);
});
test('returns null if simctl fails', () async {
processRunner.mockProcessesForExecutable['xcrun'] = <io.Process>[
MockProcess(exitCode: 1),
];
expect(await xcode.findBestAvailableIphoneSimulator(), null);
});
});
group('runXcodeBuild', () {
test('handles minimal arguments', () async {
final Directory directory = const LocalFileSystem().currentDirectory;
final int exitCode = await xcode.runXcodeBuild(
directory,
workspace: 'A.xcworkspace',
scheme: 'AScheme',
);
expect(exitCode, 0);
expect(
processRunner.recordedCalls,
orderedEquals(<ProcessCall>[
ProcessCall(
'xcrun',
const <String>[
'xcodebuild',
'build',
'-workspace',
'A.xcworkspace',
'-scheme',
'AScheme',
],
directory.path),
]));
});
test('handles all arguments', () async {
final Directory directory = const LocalFileSystem().currentDirectory;
final int exitCode = await xcode.runXcodeBuild(directory,
actions: <String>['action1', 'action2'],
workspace: 'A.xcworkspace',
scheme: 'AScheme',
configuration: 'Debug',
extraFlags: <String>['-a', '-b', 'c=d']);
expect(exitCode, 0);
expect(
processRunner.recordedCalls,
orderedEquals(<ProcessCall>[
ProcessCall(
'xcrun',
const <String>[
'xcodebuild',
'action1',
'action2',
'-workspace',
'A.xcworkspace',
'-scheme',
'AScheme',
'-configuration',
'Debug',
'-a',
'-b',
'c=d',
],
directory.path),
]));
});
test('returns error codes', () async {
processRunner.mockProcessesForExecutable['xcrun'] = <io.Process>[
MockProcess(exitCode: 1),
];
final Directory directory = const LocalFileSystem().currentDirectory;
final int exitCode = await xcode.runXcodeBuild(
directory,
workspace: 'A.xcworkspace',
scheme: 'AScheme',
);
expect(exitCode, 1);
expect(
processRunner.recordedCalls,
orderedEquals(<ProcessCall>[
ProcessCall(
'xcrun',
const <String>[
'xcodebuild',
'build',
'-workspace',
'A.xcworkspace',
'-scheme',
'AScheme',
],
directory.path),
]));
});
});
group('projectHasTarget', () {
test('returns true when present', () async {
const String stdout = '''
{
"project" : {
"configurations" : [
"Debug",
"Release"
],
"name" : "Runner",
"schemes" : [
"Runner"
],
"targets" : [
"Runner",
"RunnerTests",
"RunnerUITests"
]
}
}''';
processRunner.mockProcessesForExecutable['xcrun'] = <io.Process>[
MockProcess(stdout: stdout),
];
final Directory project =
const LocalFileSystem().directory('/foo.xcodeproj');
expect(await xcode.projectHasTarget(project, 'RunnerTests'), true);
expect(
processRunner.recordedCalls,
orderedEquals(<ProcessCall>[
ProcessCall(
'xcrun',
<String>[
'xcodebuild',
'-list',
'-json',
'-project',
project.path,
],
null),
]));
});
test('returns false when not present', () async {
const String stdout = '''
{
"project" : {
"configurations" : [
"Debug",
"Release"
],
"name" : "Runner",
"schemes" : [
"Runner"
],
"targets" : [
"Runner",
"RunnerUITests"
]
}
}''';
processRunner.mockProcessesForExecutable['xcrun'] = <io.Process>[
MockProcess(stdout: stdout),
];
final Directory project =
const LocalFileSystem().directory('/foo.xcodeproj');
expect(await xcode.projectHasTarget(project, 'RunnerTests'), false);
expect(
processRunner.recordedCalls,
orderedEquals(<ProcessCall>[
ProcessCall(
'xcrun',
<String>[
'xcodebuild',
'-list',
'-json',
'-project',
project.path,
],
null),
]));
});
test('returns null for unexpected output', () async {
processRunner.mockProcessesForExecutable['xcrun'] = <io.Process>[
MockProcess(stdout: '{}'),
];
final Directory project =
const LocalFileSystem().directory('/foo.xcodeproj');
expect(await xcode.projectHasTarget(project, 'RunnerTests'), null);
expect(
processRunner.recordedCalls,
orderedEquals(<ProcessCall>[
ProcessCall(
'xcrun',
<String>[
'xcodebuild',
'-list',
'-json',
'-project',
project.path,
],
null),
]));
});
test('returns null for invalid output', () async {
processRunner.mockProcessesForExecutable['xcrun'] = <io.Process>[
MockProcess(stdout: ':)'),
];
final Directory project =
const LocalFileSystem().directory('/foo.xcodeproj');
expect(await xcode.projectHasTarget(project, 'RunnerTests'), null);
expect(
processRunner.recordedCalls,
orderedEquals(<ProcessCall>[
ProcessCall(
'xcrun',
<String>[
'xcodebuild',
'-list',
'-json',
'-project',
project.path,
],
null),
]));
});
test('returns null for failure', () async {
processRunner.mockProcessesForExecutable['xcrun'] = <io.Process>[
MockProcess(exitCode: 1), // xcodebuild -list
];
final Directory project =
const LocalFileSystem().directory('/foo.xcodeproj');
expect(await xcode.projectHasTarget(project, 'RunnerTests'), null);
expect(
processRunner.recordedCalls,
orderedEquals(<ProcessCall>[
ProcessCall(
'xcrun',
<String>[
'xcodebuild',
'-list',
'-json',
'-project',
project.path,
],
null),
]));
});
});
}