// 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 devices = { 'runtimes': >[ { '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' }, { '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' }, { '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': { 'com.apple.CoreSimulator.SimRuntime.iOS-13-4': >[ { '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' }, { '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'] = [ 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 devices = { 'runtimes': >[ { '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': { 'com.apple.CoreSimulator.SimRuntime.watchOS-6-2': >[ { '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'] = [ MockProcess(stdout: jsonEncode(devices)), ]; expect(await xcode.findBestAvailableIphoneSimulator(), null); }); test('returns null if simctl fails', () async { processRunner.mockProcessesForExecutable['xcrun'] = [ 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( 'xcrun', const [ '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: ['action1', 'action2'], workspace: 'A.xcworkspace', scheme: 'AScheme', configuration: 'Debug', extraFlags: ['-a', '-b', 'c=d']); expect(exitCode, 0); expect( processRunner.recordedCalls, orderedEquals([ ProcessCall( 'xcrun', const [ 'xcodebuild', 'action1', 'action2', '-workspace', 'A.xcworkspace', '-scheme', 'AScheme', '-configuration', 'Debug', '-a', '-b', 'c=d', ], directory.path), ])); }); test('returns error codes', () async { processRunner.mockProcessesForExecutable['xcrun'] = [ 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( 'xcrun', const [ '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'] = [ MockProcess(stdout: stdout), ]; final Directory project = const LocalFileSystem().directory('/foo.xcodeproj'); expect(await xcode.projectHasTarget(project, 'RunnerTests'), true); expect( processRunner.recordedCalls, orderedEquals([ ProcessCall( 'xcrun', [ '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'] = [ MockProcess(stdout: stdout), ]; final Directory project = const LocalFileSystem().directory('/foo.xcodeproj'); expect(await xcode.projectHasTarget(project, 'RunnerTests'), false); expect( processRunner.recordedCalls, orderedEquals([ ProcessCall( 'xcrun', [ 'xcodebuild', '-list', '-json', '-project', project.path, ], null), ])); }); test('returns null for unexpected output', () async { processRunner.mockProcessesForExecutable['xcrun'] = [ MockProcess(stdout: '{}'), ]; final Directory project = const LocalFileSystem().directory('/foo.xcodeproj'); expect(await xcode.projectHasTarget(project, 'RunnerTests'), null); expect( processRunner.recordedCalls, orderedEquals([ ProcessCall( 'xcrun', [ 'xcodebuild', '-list', '-json', '-project', project.path, ], null), ])); }); test('returns null for invalid output', () async { processRunner.mockProcessesForExecutable['xcrun'] = [ MockProcess(stdout: ':)'), ]; final Directory project = const LocalFileSystem().directory('/foo.xcodeproj'); expect(await xcode.projectHasTarget(project, 'RunnerTests'), null); expect( processRunner.recordedCalls, orderedEquals([ ProcessCall( 'xcrun', [ 'xcodebuild', '-list', '-json', '-project', project.path, ], null), ])); }); test('returns null for failure', () async { processRunner.mockProcessesForExecutable['xcrun'] = [ 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( 'xcrun', [ 'xcodebuild', '-list', '-json', '-project', project.path, ], null), ])); }); }); }