iossim_util.py 8.0 KB

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