1# Copyright 2019 The Chromium Authors. All rights reserved. 2# Use of this source code is governed by a BSD-style license that can be 3# found in the LICENSE file. 4 5import json 6import logging 7import subprocess 8 9import test_runner 10 11LOGGER = logging.getLogger(__name__) 12 13 14def _compose_simulator_name(platform, version): 15 """Composes the name of simulator of platform and version strings.""" 16 return '%s %s test simulator' % (platform, version) 17 18 19def get_simulator_list(): 20 """Gets list of available simulator as a dictionary.""" 21 return json.loads(subprocess.check_output(['xcrun', 'simctl', 'list', '-j'])) 22 23 24def get_simulator(platform, version): 25 """Gets a simulator or creates a new one if not exist by platform and version. 26 27 Args: 28 platform: (str) A platform name, e.g. "iPhone 11 Pro" 29 version: (str) A version name, e.g. "13.4" 30 31 Returns: 32 A udid of a simulator device. 33 """ 34 udids = get_simulator_udids_by_platform_and_version(platform, version) 35 if udids: 36 return udids[0] 37 return create_device_by_platform_and_version(platform, version) 38 39 40def get_simulator_device_type_by_platform(simulators, platform): 41 """Gets device type identifier for platform. 42 43 Args: 44 simulators: (dict) A list of available simulators. 45 platform: (str) A platform name, e.g. "iPhone 11 Pro" 46 47 Returns: 48 Simulator device type identifier string of the platform. 49 e.g. 'com.apple.CoreSimulator.SimDeviceType.iPhone-11-Pro' 50 51 Raises: 52 test_runner.SimulatorNotFoundError when the platform can't be found. 53 """ 54 for devicetype in simulators['devicetypes']: 55 if devicetype['name'] == platform: 56 return devicetype['identifier'] 57 raise test_runner.SimulatorNotFoundError( 58 'Not found device "%s" in devicetypes %s' % 59 (platform, simulators['devicetypes'])) 60 61 62def get_simulator_runtime_by_version(simulators, version): 63 """Gets runtime based on iOS version. 64 65 Args: 66 simulators: (dict) A list of available simulators. 67 version: (str) A version name, e.g. "13.4" 68 69 Returns: 70 Simulator runtime identifier string of the version. 71 e.g. 'com.apple.CoreSimulator.SimRuntime.iOS-13-4' 72 73 Raises: 74 test_runner.SimulatorNotFoundError when the version can't be found. 75 """ 76 for runtime in simulators['runtimes']: 77 if runtime['version'] == version and 'iOS' in runtime['name']: 78 return runtime['identifier'] 79 raise test_runner.SimulatorNotFoundError('Not found "%s" SDK in runtimes %s' % 80 (version, simulators['runtimes'])) 81 82 83def get_simulator_runtime_by_device_udid(simulator_udid): 84 """Gets simulator runtime based on simulator UDID. 85 86 Args: 87 simulator_udid: (str) UDID of a simulator. 88 """ 89 simulator_list = get_simulator_list()['devices'] 90 for runtime, simulators in simulator_list.items(): 91 for device in simulators: 92 if simulator_udid == device['udid']: 93 return runtime 94 raise test_runner.SimulatorNotFoundError( 95 'Not found simulator with "%s" UDID in devices %s' % (simulator_udid, 96 simulator_list)) 97 98 99def get_simulator_udids_by_platform_and_version(platform, version): 100 """Gets list of simulators UDID based on platform name and iOS version. 101 102 Args: 103 platform: (str) A platform name, e.g. "iPhone 11" 104 version: (str) A version name, e.g. "13.2.2" 105 """ 106 simulators = get_simulator_list() 107 devices = simulators['devices'] 108 sdk_id = get_simulator_runtime_by_version(simulators, version) 109 results = [] 110 for device in devices.get(sdk_id, []): 111 if device['name'] == _compose_simulator_name(platform, version): 112 results.append(device['udid']) 113 return results 114 115 116def create_device_by_platform_and_version(platform, version): 117 """Creates a simulator and returns UDID of it. 118 119 Args: 120 platform: (str) A platform name, e.g. "iPhone 11" 121 version: (str) A version name, e.g. "13.2.2" 122 """ 123 name = _compose_simulator_name(platform, version) 124 LOGGER.info('Creating simulator %s', name) 125 simulators = get_simulator_list() 126 device_type = get_simulator_device_type_by_platform(simulators, platform) 127 runtime = get_simulator_runtime_by_version(simulators, version) 128 try: 129 udid = subprocess.check_output( 130 ['xcrun', 'simctl', 'create', name, device_type, runtime], 131 stderr=subprocess.STDOUT).rstrip() 132 LOGGER.info('Created simulator in first attempt with UDID: %s', udid) 133 # Sometimes above command fails to create a simulator. Verify it and retry 134 # once if first attempt failed. 135 if not is_device_with_udid_simulator(udid): 136 # Try to delete once to avoid duplicate in case of race condition. 137 delete_simulator_by_udid(udid) 138 udid = subprocess.check_output( 139 ['xcrun', 'simctl', 'create', name, device_type, runtime], 140 stderr=subprocess.STDOUT).rstrip() 141 LOGGER.info('Created simulator in second attempt with UDID: %s', udid) 142 return udid 143 except subprocess.CalledProcessError as e: 144 LOGGER.error('Error when creating simulator "%s": %s' % (name, e.output)) 145 raise e 146 147 148def delete_simulator_by_udid(udid): 149 """Deletes simulator by its udid. 150 151 Args: 152 udid: (str) UDID of simulator. 153 """ 154 LOGGER.info('Deleting simulator %s', udid) 155 try: 156 subprocess.check_output(['xcrun', 'simctl', 'delete', udid], 157 stderr=subprocess.STDOUT) 158 except subprocess.CalledProcessError as e: 159 # Logging error instead of throwing so we don't cause failures in case 160 # this was indeed failing to clean up. 161 message = 'Failed to delete simulator %s with error %s' % (udid, e.output) 162 LOGGER.error(message) 163 164 165def wipe_simulator_by_udid(udid): 166 """Wipes simulators by its udid. 167 168 Args: 169 udid: (str) UDID of simulator. 170 """ 171 for _, devices in get_simulator_list()['devices'].items(): 172 for device in devices: 173 if device['udid'] != udid: 174 continue 175 try: 176 LOGGER.info('Shutdown simulator %s ', device) 177 if device['state'] != 'Shutdown': 178 subprocess.check_call(['xcrun', 'simctl', 'shutdown', device['udid']]) 179 except subprocess.CalledProcessError as ex: 180 LOGGER.error('Shutdown failed %s ', ex) 181 subprocess.check_call(['xcrun', 'simctl', 'erase', device['udid']]) 182 183 184def get_home_directory(platform, version): 185 """Gets directory where simulators are stored. 186 187 Args: 188 platform: (str) A platform name, e.g. "iPhone 11" 189 version: (str) A version name, e.g. "13.2.2" 190 """ 191 return subprocess.check_output( 192 ['xcrun', 'simctl', 'getenv', 193 get_simulator(platform, version), 'HOME']).rstrip() 194 195 196def is_device_with_udid_simulator(device_udid): 197 """Checks whether a device with udid is simulator or not. 198 199 Args: 200 device_udid: (str) UDID of a device. 201 """ 202 simulator_list = get_simulator_list()['devices'] 203 for _, simulators in simulator_list.items(): 204 for device in simulators: 205 if device_udid == device['udid']: 206 return True 207 return False 208