<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>[173937] trunk/Tools</title>
</head>
<body>
<style type="text/css"><!--
#msg dl.meta { border: 1px #006 solid; background: #369; padding: 6px; color: #fff; }
#msg dl.meta dt { float: left; width: 6em; font-weight: bold; }
#msg dt:after { content:':';}
#msg dl, #msg dt, #msg ul, #msg li, #header, #footer, #logmsg { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; }
#msg dl a { font-weight: bold}
#msg dl a:link { color:#fc3; }
#msg dl a:active { color:#ff0; }
#msg dl a:visited { color:#cc6; }
h3 { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; font-weight: bold; }
#msg pre { overflow: auto; background: #ffc; border: 1px #fa0 solid; padding: 6px; }
#logmsg { background: #ffc; border: 1px #fa0 solid; padding: 1em 1em 0 1em; }
#logmsg p, #logmsg pre, #logmsg blockquote { margin: 0 0 1em 0; }
#logmsg p, #logmsg li, #logmsg dt, #logmsg dd { line-height: 14pt; }
#logmsg h1, #logmsg h2, #logmsg h3, #logmsg h4, #logmsg h5, #logmsg h6 { margin: .5em 0; }
#logmsg h1:first-child, #logmsg h2:first-child, #logmsg h3:first-child, #logmsg h4:first-child, #logmsg h5:first-child, #logmsg h6:first-child { margin-top: 0; }
#logmsg ul, #logmsg ol { padding: 0; list-style-position: inside; margin: 0 0 0 1em; }
#logmsg ul { text-indent: -1em; padding-left: 1em; }#logmsg ol { text-indent: -1.5em; padding-left: 1.5em; }
#logmsg > ul, #logmsg > ol { margin: 0 0 1em 0; }
#logmsg pre { background: #eee; padding: 1em; }
#logmsg blockquote { border: 1px solid #fa0; border-left-width: 10px; padding: 1em 1em 0 1em; background: white;}
#logmsg dl { margin: 0; }
#logmsg dt { font-weight: bold; }
#logmsg dd { margin: 0; padding: 0 0 0.5em 0; }
#logmsg dd:before { content:'\00bb';}
#logmsg table { border-spacing: 0px; border-collapse: collapse; border-top: 4px solid #fa0; border-bottom: 1px solid #fa0; background: #fff; }
#logmsg table th { text-align: left; font-weight: normal; padding: 0.2em 0.5em; border-top: 1px dotted #fa0; }
#logmsg table td { text-align: right; border-top: 1px dotted #fa0; padding: 0.2em 0.5em; }
#logmsg table thead th { text-align: center; border-bottom: 1px solid #fa0; }
#logmsg table th.Corner { text-align: left; }
#logmsg hr { border: none 0; border-top: 2px dashed #fa0; height: 1px; }
#header, #footer { color: #fff; background: #636; border: 1px #300 solid; padding: 6px; }
#patch { width: 100%; }
#patch h4 {font-family: verdana,arial,helvetica,sans-serif;font-size:10pt;padding:8px;background:#369;color:#fff;margin:0;}
#patch .propset h4, #patch .binary h4 {margin:0;}
#patch pre {padding:0;line-height:1.2em;margin:0;}
#patch .diff {width:100%;background:#eee;padding: 0 0 10px 0;overflow:auto;}
#patch .propset .diff, #patch .binary .diff {padding:10px 0;}
#patch span {display:block;padding:0 10px;}
#patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, #patch .copfile {border:1px solid #ccc;margin:10px 0;}
#patch ins {background:#dfd;text-decoration:none;display:block;padding:0 10px;}
#patch del {background:#fdd;text-decoration:none;display:block;padding:0 10px;}
#patch .lines, .info {color:#888;background:#fff;}
--></style>
<div id="msg">
<dl class="meta">
<dt>Revision</dt> <dd><a href="http://trac.webkit.org/projects/webkit/changeset/173937">173937</a></dd>
<dt>Author</dt> <dd>dfarler@apple.com</dd>
<dt>Date</dt> <dd>2014-09-24 16:28:13 -0700 (Wed, 24 Sep 2014)</dd>
</dl>
<h3>Log Message</h3>
<pre>[iOS] run-webkit-tests should support minor versions under devices and create a testing device under the right runtime
https://bugs.webkit.org/show_bug.cgi?id=136895
Reviewed by David Kilzer.
Create Device, DeviceType, and Runtime data classes.
Create Simulator class represent simctl output.
Wherever possible, use structured data classes anywhere a raw
identifier or UDID string was used for a cleaner implementation
and to encapsulate the inherent fragility of scraping simctl output.
Create a suitably named testing device if one doesn't exist.
Finally, accurately handle having multiple simulator runtimes (SDKs)
installed in the active Xcode.app bundle.
* Scripts/webkitpy/layout_tests/run_webkit_tests.py:
(_set_up_derived_options):
* Scripts/webkitpy/port/driver.py:
(IOSSimulatorDriver.cmd_line):
Construct DeviceType and Runtime objects from identifiers passed at the
command line, still providing sensible defaults for 32- and 64-bit testing.
* Scripts/webkitpy/port/ios.py:
(IOSSimulatorPort.__init__):
(IOSSimulatorPort.setup_test_run):
(IOSSimulatorPort):
(IOSSimulatorPort.testing_device):
Cache the testing device once it is created or found.
(IOSSimulatorPort.reset_preferences):
Get the device path from the Device object instead of consructing it
in the port class.
(IOSSimulatorPort.simulator_udid): Deleted.
Get the UDID from the testing_device :: Device object itself.
* Scripts/webkitpy/xcode/simulator.py:
Created Device, DeviceType, Runtime, and Simulator classes.
(get_runtimes): Deleted.
(get_devices): Deleted.
(get_device_types): Deleted.
(get_latest_runtime): Deleted.</pre>
<h3>Modified Paths</h3>
<ul>
<li><a href="#trunkToolsChangeLog">trunk/Tools/ChangeLog</a></li>
<li><a href="#trunkToolsScriptswebkitpylayout_testsrun_webkit_testspy">trunk/Tools/Scripts/webkitpy/layout_tests/run_webkit_tests.py</a></li>
<li><a href="#trunkToolsScriptswebkitpyportdriverpy">trunk/Tools/Scripts/webkitpy/port/driver.py</a></li>
<li><a href="#trunkToolsScriptswebkitpyportiospy">trunk/Tools/Scripts/webkitpy/port/ios.py</a></li>
<li><a href="#trunkToolsScriptswebkitpyxcodesimulatorpy">trunk/Tools/Scripts/webkitpy/xcode/simulator.py</a></li>
</ul>
</div>
<div id="patch">
<h3>Diff</h3>
<a id="trunkToolsChangeLog"></a>
<div class="modfile"><h4>Modified: trunk/Tools/ChangeLog (173936 => 173937)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Tools/ChangeLog        2014-09-24 23:11:46 UTC (rev 173936)
+++ trunk/Tools/ChangeLog        2014-09-24 23:28:13 UTC (rev 173937)
</span><span class="lines">@@ -1,3 +1,46 @@
</span><ins>+2014-09-23 David Farler <dfarler@apple.com>
+
+ [iOS] run-webkit-tests should support minor versions under devices and create a testing device under the right runtime
+ https://bugs.webkit.org/show_bug.cgi?id=136895
+
+ Reviewed by David Kilzer.
+
+ Create Device, DeviceType, and Runtime data classes.
+ Create Simulator class represent simctl output.
+
+ Wherever possible, use structured data classes anywhere a raw
+ identifier or UDID string was used for a cleaner implementation
+ and to encapsulate the inherent fragility of scraping simctl output.
+
+ Create a suitably named testing device if one doesn't exist.
+
+ Finally, accurately handle having multiple simulator runtimes (SDKs)
+ installed in the active Xcode.app bundle.
+
+ * Scripts/webkitpy/layout_tests/run_webkit_tests.py:
+ (_set_up_derived_options):
+ * Scripts/webkitpy/port/driver.py:
+ (IOSSimulatorDriver.cmd_line):
+ Construct DeviceType and Runtime objects from identifiers passed at the
+ command line, still providing sensible defaults for 32- and 64-bit testing.
+ * Scripts/webkitpy/port/ios.py:
+ (IOSSimulatorPort.__init__):
+ (IOSSimulatorPort.setup_test_run):
+ (IOSSimulatorPort):
+ (IOSSimulatorPort.testing_device):
+ Cache the testing device once it is created or found.
+ (IOSSimulatorPort.reset_preferences):
+ Get the device path from the Device object instead of consructing it
+ in the port class.
+ (IOSSimulatorPort.simulator_udid): Deleted.
+ Get the UDID from the testing_device :: Device object itself.
+ * Scripts/webkitpy/xcode/simulator.py:
+ Created Device, DeviceType, Runtime, and Simulator classes.
+ (get_runtimes): Deleted.
+ (get_devices): Deleted.
+ (get_device_types): Deleted.
+ (get_latest_runtime): Deleted.
+
</ins><span class="cx"> 2014-09-24 Roger Fong <roger_fong@apple.com>
</span><span class="cx">
</span><span class="cx"> [Windows] Tentative fix for Windows test bots.
</span></span></pre></div>
<a id="trunkToolsScriptswebkitpylayout_testsrun_webkit_testspy"></a>
<div class="modfile"><h4>Modified: trunk/Tools/Scripts/webkitpy/layout_tests/run_webkit_tests.py (173936 => 173937)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Tools/Scripts/webkitpy/layout_tests/run_webkit_tests.py        2014-09-24 23:11:46 UTC (rev 173936)
+++ trunk/Tools/Scripts/webkitpy/layout_tests/run_webkit_tests.py        2014-09-24 23:28:13 UTC (rev 173937)
</span><span class="lines">@@ -394,10 +394,15 @@
</span><span class="cx"> if options.platform == 'ios-simulator':
</span><span class="cx"> from webkitpy import xcode
</span><span class="cx"> if options.runtime is None:
</span><del>- options.runtime = xcode.simulator.get_latest_runtime()['identifier']
</del><ins>+ options.runtime = xcode.simulator.Simulator().latest_runtime
+ else:
+ options.runtime = xcode.simulator.Runtime.from_identifier(options.runtime)
</ins><span class="cx"> if options.device_type is None:
</span><del>- device_types = xcode.simulator.get_device_types()
- options.device_type = device_types['iPhone 5'] if options.architecture == 'x86' else device_types['iPhone 5s']
</del><ins>+ iphone5 = xcode.simulator.DeviceType.from_name('iPhone 5')
+ iphone5s = xcode.simulator.DeviceType.from_name('iPhone 5s')
+ options.device_type = iphone5 if options.architecture == 'x86' else iphone5s
+ else:
+ options.device_type = xcode.simulator.DeviceType.from_identifier(options.device_type)
</ins><span class="cx">
</span><span class="cx">
</span><span class="cx"> def run(port, options, args, logging_stream):
</span></span></pre></div>
<a id="trunkToolsScriptswebkitpyportdriverpy"></a>
<div class="modfile"><h4>Modified: trunk/Tools/Scripts/webkitpy/port/driver.py (173936 => 173937)</h4>
<pre class="diff"><span>
<span class="info">--- 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)
</span><span class="lines">@@ -511,8 +511,8 @@
</span><span class="cx"> runtime = self._port.get_option('runtime')
</span><span class="cx"> device_type = self._port.get_option('device_type')
</span><span class="cx"> relay_args = [
</span><del>- '-runtime', runtime,
- '-deviceType', device_type,
</del><ins>+ '-runtime', runtime.identifier,
+ '-deviceType', device_type.identifier,
</ins><span class="cx"> '-suffix', str(self._worker_number),
</span><span class="cx"> '-productDir', product_dir,
</span><span class="cx"> '-app', dump_tool,
</span></span></pre></div>
<a id="trunkToolsScriptswebkitpyportiospy"></a>
<div class="modfile"><h4>Modified: trunk/Tools/Scripts/webkitpy/port/ios.py (173936 => 173937)</h4>
<pre class="diff"><span>
<span class="info">--- 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)
</span><span class="lines">@@ -35,6 +35,7 @@
</span><span class="cx"> from webkitpy.port.base import Port
</span><span class="cx"> from webkitpy.port.leakdetector import LeakDetector
</span><span class="cx"> from webkitpy.port import config as port_config
</span><ins>+from webkitpy.xcode import simulator
</ins><span class="cx">
</span><span class="cx">
</span><span class="cx"> _log = logging.getLogger(__name__)
</span><span class="lines">@@ -67,6 +68,8 @@
</span><span class="cx"> mac_config = port_config.Config(self._executive, self._filesystem, 'mac')
</span><span class="cx"> self._mac_build_directory = mac_config.build_directory(self.get_option('configuration'))
</span><span class="cx">
</span><ins>+ self._testing_device = None
+
</ins><span class="cx"> def driver_name(self):
</span><span class="cx"> if self.get_option('driver_name'):
</span><span class="cx"> return self.get_option('driver_name')
</span><span class="lines">@@ -157,7 +160,7 @@
</span><span class="cx"> time.sleep(2)
</span><span class="cx"> self._executive.run_command([
</span><span class="cx"> 'open', '-a', os.path.join(self.developer_dir, 'Applications', 'iOS Simulator.app'),
</span><del>- '--args', '-CurrentDeviceUDID', self.simulator_udid()])
</del><ins>+ '--args', '-CurrentDeviceUDID', self.testing_device.udid])
</ins><span class="cx">
</span><span class="cx"> def clean_up_test_run(self):
</span><span class="cx"> super(IOSSimulatorPort, self).clean_up_test_run()
</span><span class="lines">@@ -252,22 +255,16 @@
</span><span class="cx"> return stderr, None
</span><span class="cx"> return stderr, crash_log
</span><span class="cx">
</span><del>- 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
</del><ins>+ @property
+ def testing_device(self):
+ if self._testing_device is not None:
+ return self._testing_device
</ins><span class="cx">
</span><ins>+ 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
+
</ins><span class="cx"> def simulator_path(self, udid):
</span><span class="cx"> if udid:
</span><span class="cx"> return os.path.realpath(os.path.expanduser(os.path.join('~/Library/Developer/CoreSimulator/Devices', udid)))
</span><span class="lines">@@ -323,9 +320,7 @@
</span><span class="cx"> return self._image_differ.diff_image(expected_contents, actual_contents, tolerance)
</span><span class="cx">
</span><span class="cx"> def reset_preferences(self):
</span><del>- simulator_path = self.simulator_path(self.simulator_udid())
- if not simulator_path:
- return
</del><ins>+ simulator_path = self.testing_device.path
</ins><span class="cx"> data_path = os.path.join(simulator_path, 'data')
</span><span class="cx"> if os.path.isdir(data_path):
</span><span class="cx"> shutil.rmtree(data_path)
</span></span></pre></div>
<a id="trunkToolsScriptswebkitpyxcodesimulatorpy"></a>
<div class="modfile"><h4>Modified: trunk/Tools/Scripts/webkitpy/xcode/simulator.py (173936 => 173937)</h4>
<pre class="diff"><span>
<span class="info">--- 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)
</span><span class="lines">@@ -1,6 +1,12 @@
</span><ins>+import itertools
+import logging
+import os
+import re
</ins><span class="cx"> import subprocess
</span><del>-import re
</del><ins>+import time
</ins><span class="cx">
</span><ins>+_log = logging.getLogger(__name__)
+
</ins><span class="cx"> """
</span><span class="cx"> Minimally wraps CoreSimulator functionality through simctl.
</span><span class="cx">
</span><span class="lines">@@ -9,97 +15,389 @@
</span><span class="cx"> """
</span><span class="cx">
</span><span class="cx">
</span><del>-def get_runtimes(only_available=True):
</del><ins>+class DeviceType(object):
</ins><span class="cx"> """
</span><del>- Give a dictionary mapping
- :return: A dictionary mapping iOS version string to runtime identifier.
- :rtype: dict
</del><ins>+ Represents a CoreSimulator device type.
</ins><span class="cx"> """
</span><del>- 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
</del><ins>+ 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
</ins><span class="cx">
</span><del>- 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
</del><ins>+ @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
</ins><span class="cx">
</span><del>- runtimes[version] = runtime
</del><ins>+ if identifier is None:
+ raise TypeError('A device type with name "{name}" does not exist.'.format(name=name))
</ins><span class="cx">
</span><del>- return runtimes
</del><ins>+ return DeviceType(name, identifier)
</ins><span class="cx">
</span><ins>+ @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
</ins><span class="cx">
</span><del>-def get_devices():
</del><ins>+ 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):
</ins><span class="cx"> """
</span><del>- :return: A dictionary mapping iOS version to device hardware model, simulator UDID, and state.
- :rtype: dict
</del><ins>+ Represents a CoreSimulator runtime associated with an iOS SDK.
</ins><span class="cx"> """
</span><del>- 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
</del><span class="cx">
</span><del>- 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()
</del><ins>+ 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 []
</ins><span class="cx">
</span><del>- devices[version][device['udid']] = device
</del><ins>+ @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
</ins><span class="cx">
</span><del>- return devices
</del><ins>+ def __eq__(self, other):
+ return (self.version == other.version) and (self.identifier == other.identifier)
</ins><span class="cx">
</span><ins>+ def __ne__(self, other):
+ return not self.__eq__(other)
</ins><span class="cx">
</span><del>-def get_device_types():
</del><ins>+ 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):
</ins><span class="cx"> """
</span><del>- :return: A dictionary mapping of device name -> identifier
- :rtype: dict
</del><ins>+ Represents a CoreSimulator device underneath a runtime
</ins><span class="cx"> """
</span><del>- device_types = {}
</del><ins>+
+ 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.
+ """
</ins><span class="cx"> device_type_re = re.compile('(?P<name>[^(]+)\((?P<identifier>[^)]+)\)')
</span><del>- stdout = subprocess.check_output(['xcrun', '-sdk', 'iphonesimulator', 'simctl', 'list', 'devicetypes'])
- lines = iter(stdout.splitlines())
- header = next(lines)
- if header != '== Device Types ==':
</del><ins>+ 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
</ins><span class="cx"> return None
</span><span class="cx">
</span><del>- 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']
</del><ins>+ 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.')
</ins><span class="cx">
</span><del>- return device_types
</del><ins>+ for runtime in self.runtimes:
+ if version and runtime.version != version:
+ continue
+ if identifier and runtime.identifier != identifier:
+ continue
+ return runtime
+ return None
</ins><span class="cx">
</span><ins>+ 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.')
</ins><span class="cx">
</span><del>-def get_latest_runtime():
- runtimes = get_runtimes()
- if not runtimes:
</del><ins>+ for device in self.devices:
+ if name and device.name != name:
+ continue
+ if runtime and device.runtime != runtime:
+ continue
+ return device
</ins><span class="cx"> return None
</span><del>- latest_version = sorted(runtimes.keys())[0]
- return runtimes[latest_version]
</del><ins>+
+ @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)
</ins></span></pre>
</div>
</div>
</body>
</html>