blob: 637953c39b31274f1eac548b601bb0be040a25dd [file] [log] [blame]
# Copyright 2019 The Chromium 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 json
import logging
import subprocess
import test_runner
LOGGER = logging.getLogger(__name__)
def _compose_simulator_name(platform, version):
"""Composes the name of simulator of platform and version strings."""
return '%s %s test simulator' % (platform, version)
def get_simulator_list():
"""Gets list of available simulator as a dictionary."""
return json.loads(subprocess.check_output(['xcrun', 'simctl', 'list', '-j']))
def get_simulator(platform, version):
"""Gets a simulator or creates a new one if not exist by platform and version.
Args:
platform: (str) A platform name, e.g. "iPhone 11 Pro"
version: (str) A version name, e.g. "13.4"
Returns:
A udid of a simulator device.
"""
udids = get_simulator_udids_by_platform_and_version(platform, version)
if udids:
return udids[0]
return create_device_by_platform_and_version(platform, version)
def get_simulator_device_type_by_platform(simulators, platform):
"""Gets device type identifier for platform.
Args:
simulators: (dict) A list of available simulators.
platform: (str) A platform name, e.g. "iPhone 11 Pro"
Returns:
Simulator device type identifier string of the platform.
e.g. 'com.apple.CoreSimulator.SimDeviceType.iPhone-11-Pro'
Raises:
test_runner.SimulatorNotFoundError when the platform can't be found.
"""
for devicetype in simulators['devicetypes']:
if devicetype['name'] == platform:
return devicetype['identifier']
raise test_runner.SimulatorNotFoundError(
'Not found device "%s" in devicetypes %s' %
(platform, simulators['devicetypes']))
def get_simulator_runtime_by_version(simulators, version):
"""Gets runtime based on iOS version.
Args:
simulators: (dict) A list of available simulators.
version: (str) A version name, e.g. "13.4"
Returns:
Simulator runtime identifier string of the version.
e.g. 'com.apple.CoreSimulator.SimRuntime.iOS-13-4'
Raises:
test_runner.SimulatorNotFoundError when the version can't be found.
"""
for runtime in simulators['runtimes']:
if runtime['version'] == version and 'iOS' in runtime['name']:
return runtime['identifier']
raise test_runner.SimulatorNotFoundError('Not found "%s" SDK in runtimes %s' %
(version, simulators['runtimes']))
def get_simulator_runtime_by_device_udid(simulator_udid):
"""Gets simulator runtime based on simulator UDID.
Args:
simulator_udid: (str) UDID of a simulator.
"""
simulator_list = get_simulator_list()['devices']
for runtime, simulators in simulator_list.items():
for device in simulators:
if simulator_udid == device['udid']:
return runtime
raise test_runner.SimulatorNotFoundError(
'Not found simulator with "%s" UDID in devices %s' % (simulator_udid,
simulator_list))
def get_simulator_udids_by_platform_and_version(platform, version):
"""Gets list of simulators UDID based on platform name and iOS version.
Args:
platform: (str) A platform name, e.g. "iPhone 11"
version: (str) A version name, e.g. "13.2.2"
"""
simulators = get_simulator_list()
devices = simulators['devices']
sdk_id = get_simulator_runtime_by_version(simulators, version)
results = []
for device in devices.get(sdk_id, []):
if device['name'] == _compose_simulator_name(platform, version):
results.append(device['udid'])
return results
def create_device_by_platform_and_version(platform, version):
"""Creates a simulator and returns UDID of it.
Args:
platform: (str) A platform name, e.g. "iPhone 11"
version: (str) A version name, e.g. "13.2.2"
"""
name = _compose_simulator_name(platform, version)
LOGGER.info('Creating simulator %s', name)
simulators = get_simulator_list()
device_type = get_simulator_device_type_by_platform(simulators, platform)
runtime = get_simulator_runtime_by_version(simulators, version)
try:
udid = subprocess.check_output(
['xcrun', 'simctl', 'create', name, device_type, runtime]).rstrip()
LOGGER.info('Created simulator in first attempt with UDID: %s', udid)
# Sometimes above command fails to create a simulator. Verify it and retry
# once if first attempt failed.
if not is_device_with_udid_simulator(udid):
# Try to delete once to avoid duplicate in case of race condition.
delete_simulator_by_udid(udid)
udid = subprocess.check_output(
['xcrun', 'simctl', 'create', name, device_type, runtime]).rstrip()
LOGGER.info('Created simulator in second attempt with UDID: %s', udid)
return udid
except subprocess.CalledProcessError as e:
LOGGER.error('Error when creating simulator "%s": %s' % (name, e.output))
raise e
def delete_simulator_by_udid(udid):
"""Deletes simulator by its udid.
Args:
udid: (str) UDID of simulator.
"""
LOGGER.info('Deleting simulator %s', udid)
try:
subprocess.check_output(['xcrun', 'simctl', 'delete', udid],
stderr=subprocess.STDOUT)
except subprocess.CalledProcessError as e:
# Logging error instead of throwing so we don't cause failures in case
# this was indeed failing to clean up.
message = 'Failed to delete simulator %s with error %s' % (udid, e.output)
LOGGER.error(message)
def wipe_simulator_by_udid(udid):
"""Wipes simulators by its udid.
Args:
udid: (str) UDID of simulator.
"""
for _, devices in get_simulator_list()['devices'].items():
for device in devices:
if device['udid'] != udid:
continue
try:
LOGGER.info('Shutdown simulator %s ', device)
if device['state'] != 'Shutdown':
subprocess.check_call(['xcrun', 'simctl', 'shutdown', device['udid']])
except subprocess.CalledProcessError as ex:
LOGGER.error('Shutdown failed %s ', ex)
subprocess.check_call(['xcrun', 'simctl', 'erase', device['udid']])
def get_home_directory(platform, version):
"""Gets directory where simulators are stored.
Args:
platform: (str) A platform name, e.g. "iPhone 11"
version: (str) A version name, e.g. "13.2.2"
"""
return subprocess.check_output(
['xcrun', 'simctl', 'getenv',
get_simulator(platform, version), 'HOME']).rstrip()
def is_device_with_udid_simulator(device_udid):
"""Checks whether a device with udid is simulator or not.
Args:
device_udid: (str) UDID of a device.
"""
simulator_list = get_simulator_list()['devices']
for _, simulators in simulator_list.items():
for device in simulators:
if device_udid == device['udid']:
return True
return False