Modified: trunk/Tools/Scripts/webkitpy/port/driver.py (173936 => 173937)
--- trunk/Tools/Scripts/webkitpy/port/driver.py 2014-09-24 23:11:46 UTC (rev 173936)
+++ trunk/Tools/Scripts/webkitpy/port/driver.py 2014-09-24 23:28:13 UTC (rev 173937)
@@ -511,8 +511,8 @@
runtime = self._port.get_option('runtime')
device_type = self._port.get_option('device_type')
relay_args = [
- '-runtime', runtime,
- '-deviceType', device_type,
+ '-runtime', runtime.identifier,
+ '-deviceType', device_type.identifier,
'-suffix', str(self._worker_number),
'-productDir', product_dir,
'-app', dump_tool,
Modified: trunk/Tools/Scripts/webkitpy/port/ios.py (173936 => 173937)
--- trunk/Tools/Scripts/webkitpy/port/ios.py 2014-09-24 23:11:46 UTC (rev 173936)
+++ trunk/Tools/Scripts/webkitpy/port/ios.py 2014-09-24 23:28:13 UTC (rev 173937)
@@ -35,6 +35,7 @@
from webkitpy.port.base import Port
from webkitpy.port.leakdetector import LeakDetector
from webkitpy.port import config as port_config
+from webkitpy.xcode import simulator
_log = logging.getLogger(__name__)
@@ -67,6 +68,8 @@
mac_config = port_config.Config(self._executive, self._filesystem, 'mac')
self._mac_build_directory = mac_config.build_directory(self.get_option('configuration'))
+ self._testing_device = None
+
def driver_name(self):
if self.get_option('driver_name'):
return self.get_option('driver_name')
@@ -157,7 +160,7 @@
time.sleep(2)
self._executive.run_command([
'open', '-a', os.path.join(self.developer_dir, 'Applications', 'iOS Simulator.app'),
- '--args', '-CurrentDeviceUDID', self.simulator_udid()])
+ '--args', '-CurrentDeviceUDID', self.testing_device.udid])
def clean_up_test_run(self):
super(IOSSimulatorPort, self).clean_up_test_run()
@@ -252,22 +255,16 @@
return stderr, None
return stderr, crash_log
- def simulator_udid(self):
- device_name = self.get_option('device_type').split('.')[-1].replace('-', ' ') + ' WebKit Tester'
- stdout = subprocess.check_output(['xcrun', '--sdk', 'iphonesimulator', 'simctl', 'list'])
- lines = stdout.splitlines()
- try:
- devices_index = lines.index('== Devices ==')
- device_regex = re.compile('(?P<device_name>[^(]+) \((?P<udid>[^)]+)\) \((?P<state>[^)]+)\)')
- for device_line in itertools.takewhile(lambda line: not line.startswith('=='), lines[devices_index + 1:]):
- device = device_regex.match(device_line.lstrip().rstrip())
- if not device:
- continue
- if device.group('device_name') == device_name:
- return device.group('udid')
- except ValueError:
- pass
+ @property
+ def testing_device(self):
+ if self._testing_device is not None:
+ return self._testing_device
+ device_type = self.get_option('device_type')
+ runtime = self.get_option('runtime')
+ self._testing_device = simulator.Simulator().testing_device(device_type, runtime)
+ return self.testing_device
+
def simulator_path(self, udid):
if udid:
return os.path.realpath(os.path.expanduser(os.path.join('~/Library/Developer/CoreSimulator/Devices', udid)))
@@ -323,9 +320,7 @@
return self._image_differ.diff_image(expected_contents, actual_contents, tolerance)
def reset_preferences(self):
- simulator_path = self.simulator_path(self.simulator_udid())
- if not simulator_path:
- return
+ simulator_path = self.testing_device.path
data_path = os.path.join(simulator_path, 'data')
if os.path.isdir(data_path):
shutil.rmtree(data_path)
Modified: trunk/Tools/Scripts/webkitpy/xcode/simulator.py (173936 => 173937)
--- trunk/Tools/Scripts/webkitpy/xcode/simulator.py 2014-09-24 23:11:46 UTC (rev 173936)
+++ trunk/Tools/Scripts/webkitpy/xcode/simulator.py 2014-09-24 23:28:13 UTC (rev 173937)
@@ -1,6 +1,12 @@
+import itertools
+import logging
+import os
+import re
import subprocess
-import re
+import time
+_log = logging.getLogger(__name__)
+
"""
Minimally wraps CoreSimulator functionality through simctl.
@@ -9,97 +15,389 @@
"""
-def get_runtimes(_only_available_=True):
+class DeviceType(object):
"""
- Give a dictionary mapping
- :return: A dictionary mapping iOS version string to runtime identifier.
- :rtype: dict
+ Represents a CoreSimulator device type.
"""
- runtimes = {}
- runtime_re = re.compile(b'iOS (?P<version>[0-9]+\.[0-9]) \([0-9]+\.[0-9]+ - (?P<update>[^)]+)\) \((?P<identifier>[^)]+)\)( \((?P<unavailable>[^)]+)\))?')
- stdout = subprocess.check_output(['xcrun', '-sdk', 'iphonesimulator', 'simctl', 'list', 'runtimes'])
- lines = iter(stdout.splitlines())
- header = next(lines)
- if header != '== Runtimes ==':
- return None
+ def __init__(self, name, identifier):
+ """
+ :param name: The device type's human-readable name
+ :type name: str
+ :param identifier: The CoreSimulator identifier.
+ :type identifier: str
+ """
+ self.name = name
+ self.identifier = identifier
- for line in lines:
- runtime_match = runtime_re.match(line)
- if not runtime_match:
- continue
- runtime = runtime_match.groupdict()
- version = tuple([int(component) for component in runtime_match.group('version').split('.')])
- runtime = {
- 'identifier': runtime['identifier'],
- 'available': runtime['unavailable'] is None,
- 'version': version,
- }
- if only_available and not runtime['available']:
- continue
+ @classmethod
+ def from_name(cls, name):
+ """
+ :param name: The name for the desired device type.
+ :type name: str
+ :returns: A `DeviceType` object with the specified identifier or throws a TypeError if it doesn't exist.
+ :rtype: DeviceType
+ """
+ identifier = None
+ for device_type in Simulator().device_types:
+ if device_type.name == name:
+ identifier = device_type.identifier
+ break
- runtimes[version] = runtime
+ if identifier is None:
+ raise TypeError('A device type with name "{name}" does not exist.'.format(name=name))
- return runtimes
+ return DeviceType(name, identifier)
+ @classmethod
+ def from_identifier(cls, identifier):
+ """
+ :param identifier: The CoreSimulator identifier for the desired runtime.
+ :type identifier: str
+ :returns: A `Runtime` object witht the specified identifier or throws a TypeError if it doesn't exist.
+ :rtype: DeviceType
+ """
+ name = None
+ for device_type in Simulator().device_types:
+ if device_type.identifier == identifier:
+ name = device_type.name
+ break
-def get_devices():
+ if name is None:
+ raise TypeError('A device type with identifier "{identifier}" does not exist.'.format(
+ identifier=identifier))
+
+ return DeviceType(name, identifier)
+
+ def __eq__(self, other):
+ return (self.name == other.name) and (self.identifier == other.identifier)
+
+ def __ne__(self, other):
+ return not self.__eq__(other)
+
+ def __repr__(self):
+ return '<DeviceType "{name}": {identifier}>'.format(name=self.name, identifier=self.identifier)
+
+
+class Runtime(object):
"""
- :return: A dictionary mapping iOS version to device hardware model, simulator UDID, and state.
- :rtype: dict
+ Represents a CoreSimulator runtime associated with an iOS SDK.
"""
- devices = {}
- version_re = re.compile('-- iOS (?P<version>[0-9]+\.[0-9]+) --')
- devices_re = re.compile('\s*(?P<name>[^(]+ )\((?P<udid>[^)]+)\) \((?P<state>[^)]+)\)')
- stdout = subprocess.check_output(['xcrun', '-sdk', 'iphonesimulator', 'simctl', 'list', 'devices'])
- lines = iter(stdout.splitlines())
- header = next(lines)
- version = None
- if header != '== Devices ==':
- return None
- for line in lines:
- version_match = version_re.match(line)
- if version_match:
- version = tuple([int(component) for component in version_match.group('version').split('.')])
- continue
- device_match = devices_re.match(line)
- if not device_match:
- raise RuntimeError()
- device = device_match.groupdict()
- device['name'] = device['name'].rstrip()
+ def __init__(self, version, identifier, available, devices=None):
+ """
+ :param version: The iOS SDK version
+ :type version: tuple
+ :param identifier: The CoreSimualtor runtime identifier
+ :type identifier: str
+ :param availability: Whether the runtime is available for use.
+ :type availability: bool
+ :param devices: A list of devices under this runtime
+ :type devices: list or None
+ """
+ self.version = version
+ self.identifier = identifier
+ self.available = available
+ self.devices = devices or []
- devices[version][device['udid']] = device
+ @classmethod
+ def from_identifier(cls, identifier):
+ """
+ :param identifier: The identifier for the desired CoreSimulator runtime.
+ :type identifier: str
+ :returns: A `Runtime` object with the specified identifier or throws a TypeError if it doesn't exist.
+ :rtype: Runtime
+ """
+ runtime = None
+ for runtime in Simulator().runtimes:
+ if runtime.identifier == identifier:
+ break
+ if runtime is None:
+ raise TypeError('A runtime with identifier "{identifier}" does not exist.'.format(identifier=identifier))
+ return runtime
- return devices
+ def __eq__(self, other):
+ return (self.version == other.version) and (self.identifier == other.identifier)
+ def __ne__(self, other):
+ return not self.__eq__(other)
-def get_device_types():
+ def __repr__(self):
+ return '<Runtime {version}: {identifier}. Available: {available}, {num_devices} devices>'.format(
+ version='.'.join(map(str, self.version)),
+ identifier=self.identifier,
+ available=self.available,
+ num_devices=len(self.devices))
+
+
+class Device(object):
"""
- :return: A dictionary mapping of device name -> identifier
- :rtype: dict
+ Represents a CoreSimulator device underneath a runtime
"""
- device_types = {}
+
+ def __init__(self, name, udid, state, available, runtime):
+ """
+ :param name: The device name
+ :type name: str
+ :param udid: The device UDID (a UUID string)
+ :type udid: str
+ :param state: The last known device state
+ :type state: str
+ :param available: Whether the device is available for use.
+ :type available: bool
+ :param runtime: The iOS Simulator runtime that hosts this device
+ :type runtime: Runtime
+ """
+ self.name = name
+ self.udid = udid
+ self.state = state
+ self.available = available
+ self.runtime = runtime
+
+ @property
+ def path(self):
+ """
+ :returns: The filesystem path that contains the simulator device's data.
+ :rtype: str
+ """
+ return os.path.realpath(
+ os.path.expanduser(
+ os.path.join('~/Library/Developer/CoreSimulator/Devices', self.udid)))
+
+ @classmethod
+ def create(cls, name, device_type, runtime):
+ """
+ Create a new CoreSimulator device.
+ :param name: The name of the device.
+ :type name: str
+ :param device_type: The CoreSimulatort device type.
+ :type device_type: DeviceType
+ :param runtime: The CoreSimualtor runtime.
+ :type runtime: Runtime
+ :return: The new device or raises a CalledProcessError if ``simctl create`` failed.
+ :rtype: Device
+ """
+ sim = Simulator()
+ subprocess.check_call(['xcrun', 'simctl', 'create', name, device_type.identifier, runtime.identifier])
+
+ device = None
+ while device is None:
+ sim.refresh()
+ device = sim.device(name, runtime)
+ if device is None or device.state == 'Creating':
+ time.sleep(2)
+ else:
+ break
+ return device
+
+ def __eq__(self, other):
+ return self.udid == other.udid
+
+ def __ne__(self, other):
+ return not self.__eq__(other)
+
+ def __repr__(self):
+ return '<Device "{name}": {udid}. State: {state}. Runtime: {runtime}, Available: {available}>'.format(
+ name=self.name,
+ udid=self.udid,
+ state=self.state,
+ available=self.available,
+ runtime=self.runtime.identifier)
+
+
+class Simulator(object):
+ """
+ Represents the iOS Simulator infrastructure under the currently select Xcode.app bundle.
+ """
device_type_re = re.compile('(?P<name>[^(]+)\((?P<identifier>[^)]+)\)')
- stdout = subprocess.check_output(['xcrun', '-sdk', 'iphonesimulator', 'simctl', 'list', 'devicetypes'])
- lines = iter(stdout.splitlines())
- header = next(lines)
- if header != '== Device Types ==':
+ runtime_re = re.compile(
+ 'iOS (?P<version>[0-9]+\.[0-9]) \([0-9]+\.[0-9]+ - (?P<build_version>[^)]+)\) \((?P<identifier>[^)]+)\)( \((?P<availability>[^)]+)\))?')
+ version_re = re.compile('-- iOS (?P<version>[0-9]+\.[0-9]+) --')
+ devices_re = re.compile(
+ '\s*(?P<name>[^(]+ )\((?P<udid>[^)]+)\) \((?P<state>[^)]+)\)( \((?P<availability>[^)]+)\))?')
+
+ def __init__(self):
+ self.runtimes = []
+ self.device_types = []
+ self.refresh()
+
+ def refresh(self):
+ """
+ Refresh runtime and device type information from ``simctl list``.
+ """
+ command = ['xcrun', 'simctl', 'list']
+ simctl_p = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ stdout, stderr = simctl_p.communicate()
+ if simctl_p.returncode != 0:
+ raise RuntimeError(
+ '{command} failed:\n{stdout}\n{stderr}'.format(command=' '.join(command), stdout=stdout, stderr=stderr))
+
+ lines = (line for line in stdout.splitlines())
+ device_types_header = next(lines)
+ if device_types_header != '== Device Types ==':
+ raise RuntimeError('Expected == Device Types == header but got: "{}"'.format(device_types_header))
+ self._parse_device_types(lines)
+
+ def _parse_device_types(self, lines):
+ """
+ Parse device types from ``simctl list``.
+ :param lines: A generator for the output lines from ``simctl list``.
+ :type lines: genexpr
+ :return: None
+ """
+ for line in lines:
+ device_type_match = self.device_type_re.match(line)
+ if not device_type_match:
+ if line != '== Runtimes ==':
+ raise RuntimeError('Expected == Runtimes == header but got: "{}"'.format(line))
+ break
+ device_type = DeviceType(name=device_type_match.group('name').rstrip(),
+ identifier=device_type_match.group('identifier'))
+ self.device_types.append(device_type)
+
+ self._parse_runtimes(lines)
+
+ def _parse_runtimes(self, lines):
+ """
+ Continue to parse runtimes from ``simctl list``.
+ :param lines: A generator for the output lines from ``simctl list``.
+ :type lines: genexpr
+ :return: None
+ """
+ for line in lines:
+ runtime_match = self.runtime_re.match(line)
+ if not runtime_match:
+ if line != '== Devices ==':
+ raise RuntimeError('Expected == Devices == header but got: "{}"'.format(line))
+ break
+ version = tuple(map(int, runtime_match.group('version').split('.')))
+ runtime = Runtime(version=version,
+ identifier=runtime_match.group('identifier'),
+ available=runtime_match.group('availability') is None)
+ self.runtimes.append(runtime)
+ self._parse_devices(lines)
+
+ def _parse_devices(self, lines):
+ """
+ Continue to parse devices from ``simctl list``.
+ :param lines: A generator for the output lines from ``simctl list``.
+ :type lines: genexpr
+ :return: None
+ """
+ current_runtime = None
+ for line in lines:
+ version_match = self.version_re.match(line)
+ if version_match:
+ version = tuple(map(int, version_match.group('version').split('.')))
+ current_runtime = self.runtime(version=version)
+ assert current_runtime
+ continue
+ device_match = self.devices_re.match(line)
+ if not device_match:
+ raise RuntimeError('Expected an iOS Simulator device line, got "{}"'.format(line))
+ device = Device(name=device_match.group('name').rstrip(),
+ udid=device_match.group('udid'),
+ state=device_match.group('state'),
+ available=device_match.group('availability') is None,
+ runtime=current_runtime)
+ current_runtime.devices.append(device)
+
+ def device_type(self, name=None, identifier=None):
+ """
+ :param name: The short name of the device type.
+ :type name: str
+ :param identifier: The CoreSimulator identifier of the desired device type.
+ :type identifier: str
+ :return: A device type with the specified name and/or identifier, or None if one doesn't exist as such.
+ :rtype: DeviceType
+ """
+ for device_type in self.device_types:
+ if name and device_type.name != name:
+ continue
+ if identifier and device_type.identifier != identifier:
+ continue
+ return device_type
return None
- for line in lines:
- device_type_match = device_type_re.match(line)
- if not device_type_match:
- continue
- device_type = device_type_match.groupdict()
- device_type['name'] = device_type['name'].rstrip()
- device_types[device_type['name']] = device_type['identifier']
+ def runtime(self, version=None, identifier=None):
+ """
+ :param version: The iOS version of the desired runtime.
+ :type version: tuple
+ :param identifier: The CoreSimulator identifier of the desired runtime.
+ :type identifier: str
+ :return: A runtime with the specified version and/or identifier, or None if one doesn't exist as such.
+ :rtype: Runtime or None
+ """
+ if version is None and identifier is None:
+ raise TypeError('Must supply version and/or identifier.')
- return device_types
+ for runtime in self.runtimes:
+ if version and runtime.version != version:
+ continue
+ if identifier and runtime.identifier != identifier:
+ continue
+ return runtime
+ return None
+ def device(self, name=None, runtime=None):
+ """
+ :param name: The name of the desired device.
+ :type name: str
+ :param runtime: The runtime of the desired device.
+ :type runtime: Runtime
+ :return: A device with the specified name and/or runtime, or None if one doesn't exist as such
+ :rtype: Device or None
+ """
+ if name is None and runtime is None:
+ raise TypeError('Must supply name and/or runtime.')
-def get_latest_runtime():
- runtimes = get_runtimes()
- if not runtimes:
+ for device in self.devices:
+ if name and device.name != name:
+ continue
+ if runtime and device.runtime != runtime:
+ continue
+ return device
return None
- latest_version = sorted(runtimes.keys())[0]
- return runtimes[latest_version]
+
+ @property
+ def devices(self):
+ """
+ :return: An iterator of all devices from all runtimes.
+ :rtype: iter
+ """
+ return itertools.chain(*[runtime.devices for runtime in self.runtimes])
+
+ @property
+ def latest_runtime(self):
+ """
+ :return: Returns a Runtime object with the highest version.
+ :rtype: Runtime or None
+ """
+ if not self.runtimes:
+ return None
+ return sorted(self.runtimes, key=lambda runtime: runtime.version)[-1]
+
+ def testing_device(self, device_type, runtime):
+ """
+ Get an iOS Simulator device for testing.
+ :param device_type: The CoreSimulator device type.
+ :type device_type: DeviceType
+ :param runtime: The CoreSimulator runtime.
+ :type runtime: Runtime
+ :return: A dictionary describing the device.
+ :rtype: Device
+ """
+ # Check to see if the testing device already exists
+ name = device_type.name + ' WebKit Tester'
+ return self.device(name=name, runtime=runtime) or Device.create(name, device_type, runtime)
+
+ def __repr__(self):
+ return '<iOS Simulator: {num_runtimes} runtimes, {num_device_types} device types>'.format(
+ num_runtimes=len(self.runtimes),
+ num_device_types=len(self.device_types))
+
+ def __str__(self):
+ description = ['iOS Simulator:']
+ description += map(str, self.runtimes)
+ description += map(str, self.device_types)
+ description += map(str, self.devices)
+ return '\n'.join(description)