webkitpy: Process crash-logs for iOS devices
authorjbedard@apple.com <jbedard@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 6 Jun 2017 20:25:15 +0000 (20:25 +0000)
committerjbedard@apple.com <jbedard@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 6 Jun 2017 20:25:15 +0000 (20:25 +0000)
https://bugs.webkit.org/show_bug.cgi?id=171976
<rdar://problem/32134551>

Reviewed by David Kilzer.

When running layout tests on an iOS device, crash logs should be processed.
Implement crash log searching and parsing for iOS devices.

* Scripts/webkitpy/common/system/crashlogs.py:
(CrashLogs): Moved process regular expression for Darwin to class variable.
(CrashLogs.__init__): Accept optional list of crash logs to ignore.
(CrashLogs.find_newest_log): Add iOS as a potential platform.
(CrashLogs.find_all_logs): Ditto.
(CrashLogs._parse_darwin_crash_log): Share code for parsing of Darwin crash logs.
Do not assume that a Darwin crash log starts with the process.
(CrashLogs._find_newest_log_darwin): Remove .app in process name for iOS, use
shared code for parsing Darwin crash logs.
(CrashLogs._find_newest_log_darwin.is_crash_log): Skip crash logs passed into this
object so that crash logs already on the system before testing are not parsed.
(CrashLogs._find_newest_log_win.is_crash_log): Ditto.
(CrashLogs._find_all_logs_darwin.is_crash_log): Ditto.
(CrashLogs._find_all_logs_darwin): Use shared code for parsing Darwin crash logs.
* Scripts/webkitpy/common/system/crashlogs_unittest.py:
(make_mock_crash_report_darwin): Crash logs may not have their process on the first line.
* Scripts/webkitpy/common/system/systemhost.py:
(SystemHost.symbolicate_crash_log_if_needed): The symbolicated crash log for most
systems is just the crashlog, use this behavior by default.
* Scripts/webkitpy/common/system/systemhost_mock.py:
(MockSystemHost.symbolicate_crash_log_if_needed): The symbolicated crash log for most
systems is just the crashlog, use this behavior by default.
* Scripts/webkitpy/port/apple.py:
(ApplePort): Add a dictionary mapping hosts to a list of crash logs to be skipped.
(ApplePort.setup_test_run): Set the list of crash logs to be skipped to the crash logs on
the system before testing begins
* Scripts/webkitpy/port/base.py:
(Port._get_crash_log): Pass optional target host when getting crash logs.
* Scripts/webkitpy/port/darwin.py:
(DarwinPort._look_for_all_crash_logs_in_log_dir): Pass list of crash logs to be skipped to
CrashLogs object.
(DarwinPort._get_crash_log): Pass optional target host when getting crash logs, pass list of crash
logs to be skipped to CrashLogs object.
* Scripts/webkitpy/port/darwin_testcase.py:
(DarwinTest.test_get_crash_log): Removed unused local function.
(DarwinTest.test_get_crash_log.fake_time_cb): Deleted.
* Scripts/webkitpy/port/device.py:
(Device.symbolicate_crash_log_if_needed): If the platform device has a function with this
name, call it. Otherwise, assume the default behavior and read the file at the provided path.
* Scripts/webkitpy/port/driver.py:
(Driver._get_crash_log): Pass optional target host when getting crash logs.
* Scripts/webkitpy/port/gtk.py:
(GtkPort._get_crash_log): Pass optional target host when getting crash logs.
* Scripts/webkitpy/port/ios.py: Ditto.
(IOSPort.setup_test_run): Each device is treated as an independent host. Set the list of crash logs
to be skipped for each host.
* Scripts/webkitpy/port/ios_device.py:
(IOSDevicePort.path_to_crash_logs): Consult apple_additions for the path to crash logs.
(IOSDevicePort._look_for_all_crash_logs_in_log_dir): Search every connected device for
crash logs and pass list of crash logs to ignore to each instance of CrashLogs.
(IOSDevicePort._get_crash_log): Search the specified target host for a crash log if a target
host is specified. Else, search all connected devices for the specified crash-log.
(IOSDevicePort.look_for_new_crash_logs): Deleted.
* Scripts/webkitpy/port/ios_device_unittest.py:
(IOSDeviceTest.test_crashlog_path): Without apple_additions, an exception should be raised.
(IOSDeviceTest.test_get_crash_log): Ditto.
* Scripts/webkitpy/port/simulator_process.py:
(SimulatorProcess.process_name): Check the provided bundle for the process name.
* Scripts/webkitpy/port/win.py:
(WinPort._get_crash_log): Pass optional target host when getting crash logs, pass list of crash
logs to be skipped to CrashLogs object.
* Scripts/webkitpy/port/wpe.py:
(WPEPort._get_crash_log): Pass optional target host when getting crash logs.

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@217853 268f45cc-cd09-0410-ab3c-d52691b4dbfc

17 files changed:
Tools/ChangeLog
Tools/Scripts/webkitpy/common/system/crashlogs.py
Tools/Scripts/webkitpy/common/system/crashlogs_unittest.py
Tools/Scripts/webkitpy/common/system/systemhost.py
Tools/Scripts/webkitpy/common/system/systemhost_mock.py
Tools/Scripts/webkitpy/port/apple.py
Tools/Scripts/webkitpy/port/base.py
Tools/Scripts/webkitpy/port/darwin.py
Tools/Scripts/webkitpy/port/darwin_testcase.py
Tools/Scripts/webkitpy/port/device.py
Tools/Scripts/webkitpy/port/driver.py
Tools/Scripts/webkitpy/port/gtk.py
Tools/Scripts/webkitpy/port/ios.py
Tools/Scripts/webkitpy/port/ios_device.py
Tools/Scripts/webkitpy/port/ios_device_unittest.py
Tools/Scripts/webkitpy/port/win.py
Tools/Scripts/webkitpy/port/wpe.py

index 831d118..20a1e25 100644 (file)
@@ -1,3 +1,78 @@
+2017-06-06  Jonathan Bedard  <jbedard@apple.com>
+
+        webkitpy: Process crash-logs for iOS devices
+        https://bugs.webkit.org/show_bug.cgi?id=171976
+        <rdar://problem/32134551>
+
+        Reviewed by David Kilzer.
+
+        When running layout tests on an iOS device, crash logs should be processed.
+        Implement crash log searching and parsing for iOS devices.
+
+        * Scripts/webkitpy/common/system/crashlogs.py:
+        (CrashLogs): Moved process regular expression for Darwin to class variable.
+        (CrashLogs.__init__): Accept optional list of crash logs to ignore.
+        (CrashLogs.find_newest_log): Add iOS as a potential platform.
+        (CrashLogs.find_all_logs): Ditto.
+        (CrashLogs._parse_darwin_crash_log): Share code for parsing of Darwin crash logs.
+        Do not assume that a Darwin crash log starts with the process.
+        (CrashLogs._find_newest_log_darwin): Remove .app in process name for iOS, use
+        shared code for parsing Darwin crash logs.
+        (CrashLogs._find_newest_log_darwin.is_crash_log): Skip crash logs passed into this
+        object so that crash logs already on the system before testing are not parsed.
+        (CrashLogs._find_newest_log_win.is_crash_log): Ditto.
+        (CrashLogs._find_all_logs_darwin.is_crash_log): Ditto.
+        (CrashLogs._find_all_logs_darwin): Use shared code for parsing Darwin crash logs.
+        * Scripts/webkitpy/common/system/crashlogs_unittest.py:
+        (make_mock_crash_report_darwin): Crash logs may not have their process on the first line.
+        * Scripts/webkitpy/common/system/systemhost.py:
+        (SystemHost.symbolicate_crash_log_if_needed): The symbolicated crash log for most
+        systems is just the crashlog, use this behavior by default.
+        * Scripts/webkitpy/common/system/systemhost_mock.py:
+        (MockSystemHost.symbolicate_crash_log_if_needed): The symbolicated crash log for most
+        systems is just the crashlog, use this behavior by default.
+        * Scripts/webkitpy/port/apple.py:
+        (ApplePort): Add a dictionary mapping hosts to a list of crash logs to be skipped.
+        (ApplePort.setup_test_run): Set the list of crash logs to be skipped to the crash logs on
+        the system before testing begins
+        * Scripts/webkitpy/port/base.py:
+        (Port._get_crash_log): Pass optional target host when getting crash logs.
+        * Scripts/webkitpy/port/darwin.py:
+        (DarwinPort._look_for_all_crash_logs_in_log_dir): Pass list of crash logs to be skipped to
+        CrashLogs object.
+        (DarwinPort._get_crash_log): Pass optional target host when getting crash logs, pass list of crash
+        logs to be skipped to CrashLogs object.
+        * Scripts/webkitpy/port/darwin_testcase.py:
+        (DarwinTest.test_get_crash_log): Removed unused local function.
+        (DarwinTest.test_get_crash_log.fake_time_cb): Deleted.
+        * Scripts/webkitpy/port/device.py:
+        (Device.symbolicate_crash_log_if_needed): If the platform device has a function with this
+        name, call it. Otherwise, assume the default behavior and read the file at the provided path.
+        * Scripts/webkitpy/port/driver.py:
+        (Driver._get_crash_log): Pass optional target host when getting crash logs.
+        * Scripts/webkitpy/port/gtk.py:
+        (GtkPort._get_crash_log): Pass optional target host when getting crash logs.
+        * Scripts/webkitpy/port/ios.py: Ditto.
+        (IOSPort.setup_test_run): Each device is treated as an independent host. Set the list of crash logs
+        to be skipped for each host.
+        * Scripts/webkitpy/port/ios_device.py:
+        (IOSDevicePort.path_to_crash_logs): Consult apple_additions for the path to crash logs.
+        (IOSDevicePort._look_for_all_crash_logs_in_log_dir): Search every connected device for
+        crash logs and pass list of crash logs to ignore to each instance of CrashLogs.
+        (IOSDevicePort._get_crash_log): Search the specified target host for a crash log if a target
+        host is specified. Else, search all connected devices for the specified crash-log.
+        (IOSDevicePort.look_for_new_crash_logs): Deleted.
+        * Scripts/webkitpy/port/ios_device_unittest.py:
+        (IOSDeviceTest.test_crashlog_path): Without apple_additions, an exception should be raised.
+        (IOSDeviceTest.test_get_crash_log): Ditto.
+        * Scripts/webkitpy/port/simulator_process.py:
+        (SimulatorProcess.process_name): Check the provided bundle for the process name.
+        * Scripts/webkitpy/port/win.py:
+        (WinPort._get_crash_log): Pass optional target host when getting crash logs, pass list of crash
+        logs to be skipped to CrashLogs object.
+        * Scripts/webkitpy/port/wpe.py:
+        (WPEPort._get_crash_log): Pass optional target host when getting crash logs.
+
 2017-06-06  David Kilzer  <ddkilzer@apple.com>
 
         Move WTF_ATTRIBUTE_PRINTF() from implementation to declaration
index 6ee9098..cc47bca 100644 (file)
 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
-import codecs
 import datetime
+import logging
 import re
 
 
+_log = logging.getLogger(__name__)
+
+
 class CrashLogs(object):
 
     GLOBAL_PID_REGEX = re.compile(r'\s+Global\s+PID:\s+\[(?P<pid>\d+)\]')
     EXIT_PROCESS_PID_REGEX = re.compile(r'Exit process \d+:(?P<pid>\w+), code')
+    DARWIN_PROCESS_REGEX = re.compile(r'^Process:\s+(?P<process_name>.*) \[(?P<pid>\d+)\]$')
 
-    def __init__(self, host, crash_log_directory):
+    def __init__(self, host, crash_log_directory, crash_logs_to_skip=[]):
         self._host = host
         self._crash_log_directory = crash_log_directory
+        self._crash_logs_to_skip = crash_logs_to_skip
 
     def find_newest_log(self, process_name, pid=None, include_errors=False, newer_than=None):
-        if self._host.platform.is_mac():
+        if self._host.platform.is_mac() or self._host.platform.is_ios():
             return self._find_newest_log_darwin(process_name, pid, include_errors, newer_than)
         elif self._host.platform.is_win():
             return self._find_newest_log_win(process_name, pid, include_errors, newer_than)
         return None
 
     def find_all_logs(self, include_errors=False, newer_than=None):
-        if self._host.platform.is_mac():
+        if self._host.platform.is_mac() or self._host.platform.is_ios():
             return self._find_all_logs_darwin(include_errors, newer_than)
         return None
 
+    def _parse_darwin_crash_log(self, path):
+        contents = self._host.symbolicate_crash_log_if_needed(path)
+        if not contents:
+            return (None, None, None)
+        for line in contents.splitlines():
+            match = CrashLogs.DARWIN_PROCESS_REGEX.match(line)
+            if match:
+                return (match.group('process_name'), int(match.group('pid')), contents)
+        return (None, None, contents)
+
     def _find_newest_log_darwin(self, process_name, pid, include_errors, newer_than):
         def is_crash_log(fs, dirpath, basename):
-            return basename.startswith(process_name + "_") and basename.endswith(".crash")
+            if self._crash_logs_to_skip and fs.join(dirpath, basename) in self._crash_logs_to_skip:
+                return False
+            return (basename.startswith(process_name + '_') and (basename.endswith('.crash')) or
+                    (process_name in basename  and basename.endswith('.ips')))
 
         logs = self._host.filesystem.files_under(self._crash_log_directory, file_filter=is_crash_log)
-        first_line_regex = re.compile(r'^Process:\s+(?P<process_name>.*) \[(?P<pid>\d+)\]$')
         errors = ''
         for path in reversed(sorted(logs)):
             try:
                 if not newer_than or self._host.filesystem.mtime(path) > newer_than:
-                    f = self._host.filesystem.read_text_file(path)
-                    match = first_line_regex.match(f[0:f.find('\n')])
-                    if match and match.group('process_name') == process_name and (pid is None or int(match.group('pid')) == pid):
-                        return errors + f
+                    parsed_name, parsed_pid, log_contents = self._parse_darwin_crash_log(path)
+                    if parsed_name == process_name and (pid is None or parsed_pid == pid):
+                        return errors + log_contents
             except IOError, e:
                 if include_errors:
                     errors += "ERROR: Failed to read '%s': %s\n" % (path, str(e))
@@ -80,6 +96,8 @@ class CrashLogs(object):
 
     def _find_newest_log_win(self, process_name, pid, include_errors, newer_than):
         def is_crash_log(fs, dirpath, basename):
+            if self._crash_logs_to_skip and fs.join(dirpath, basename) in self._crash_logs_to_skip:
+                return False
             return basename.startswith("CrashLog")
 
         logs = self._host.filesystem.files_under(self._crash_log_directory, file_filter=is_crash_log)
@@ -117,18 +135,22 @@ class CrashLogs(object):
 
     def _find_all_logs_darwin(self, include_errors, newer_than):
         def is_crash_log(fs, dirpath, basename):
-            return basename.endswith(".crash")
+            if self._crash_logs_to_skip and fs.join(dirpath, basename) in self._crash_logs_to_skip:
+                return False
+            return basename.endswith('.crash') or basename.endswith('.ips')
 
         logs = self._host.filesystem.files_under(self._crash_log_directory, file_filter=is_crash_log)
-        first_line_regex = re.compile(r'^Process:\s+(?P<process_name>.*) \[(?P<pid>\d+)\]$')
         errors = ''
         crash_logs = {}
         for path in reversed(sorted(logs)):
             try:
                 if not newer_than or self._host.filesystem.mtime(path) > newer_than:
                     result_name = "Unknown"
-                    pid = 0
-                    log_contents = self._host.filesystem.read_text_file(path)
+                    parsed_name, parsed_pid, log_contents = self._parse_darwin_crash_log(path)
+                    if not log_contents:
+                        _log.warn('No data in crash log at {}'.format(path))
+                        continue
+
                     # Verify timestamp from log contents
                     crash_time = self.get_timestamp_from_log(log_contents)
                     if crash_time is not None and newer_than is not None:
@@ -136,11 +158,8 @@ class CrashLogs(object):
                         if crash_time < start_time:
                             continue
 
-                    match = first_line_regex.match(log_contents[0:log_contents.find('\n')])
-                    if match:
-                        process_name = match.group('process_name')
-                        pid = str(match.group('pid'))
-                        result_name = process_name + "-" + pid
+                    if parsed_name:
+                        result_name = parsed_name + "-" + str(parsed_pid)
 
                     while result_name in crash_logs:
                         result_name = result_name + "-1"
index 08161a7..6e61153 100644 (file)
@@ -34,7 +34,8 @@ from webkitpy.port.win import WinPort
 
 
 def make_mock_crash_report_darwin(process_name, pid):
-    return """Process:         {process_name} [{pid}]
+    return """Crash log may not start with Process line
+Process:         {process_name} [{pid}]
 Path:            /Volumes/Data/slave/snowleopard-intel-release-tests/build/WebKitBuild/Release/{process_name}
 Identifier:      {process_name}
 Version:         ??? (???)
index dfec68b..cb84193 100644 (file)
@@ -46,3 +46,6 @@ class SystemHost(object):
 
     def make_file_lock(self, path):
         return file_lock.FileLock(path)
+
+    def symbolicate_crash_log_if_needed(self, path):
+        return self.filesystem.read_text_file(path)
index a529f34..e57ff5d 100644 (file)
@@ -54,3 +54,6 @@ class MockSystemHost(object):
 
     def make_file_lock(self, path):
         return MockFileLock(path)
+
+    def symbolicate_crash_log_if_needed(self, path):
+        return self.filesystem.read_text_file(path)
index ca1b09e..211b9e8 100644 (file)
@@ -54,6 +54,7 @@ class ApplePort(Port):
     # overridden in subclasses
     VERSION_FALLBACK_ORDER = []
     ARCHITECTURES = []
+    _crash_logs_to_skip_for_host = {}
 
     @classmethod
     def determine_full_port_name(cls, host, options, port_name):
@@ -92,6 +93,9 @@ class ApplePort(Port):
         port_name = port_name.replace('-wk2', '')
         self._version = self._strip_port_name_prefix(port_name)
 
+    def setup_test_run(self, device_class=None):
+        self._crash_logs_to_skip_for_host[self.host] = self.host.filesystem.files_under(self.path_to_crash_logs())
+
     def default_timeout_ms(self):
         if self.get_option('guard_malloc'):
             return 350 * 1000
index 39e6757..d7db93d 100644 (file)
@@ -1364,7 +1364,7 @@ class Port(object):
     def path_to_crash_logs(self):
         raise NotImplementedError
 
-    def _get_crash_log(self, name, pid, stdout, stderr, newer_than):
+    def _get_crash_log(self, name, pid, stdout, stderr, newer_than, target_host=None):
         name_str = name or '<unknown process name>'
         pid_str = str(pid or '<unknown>')
         stdout_lines = (stdout or '<empty>').decode('utf8', 'replace').splitlines()
index 1ebdf6d..3f0dcdf 100644 (file)
@@ -107,17 +107,17 @@ class DarwinPort(ApplePort):
         return logs
 
     def _look_for_all_crash_logs_in_log_dir(self, newer_than):
-        crash_log = CrashLogs(self.host, self.path_to_crash_logs())
+        crash_log = CrashLogs(self.host, self.path_to_crash_logs(), crash_logs_to_skip=self._crash_logs_to_skip_for_host.get(self.host, []))
         return crash_log.find_all_logs(include_errors=True, newer_than=newer_than)
 
-    def _get_crash_log(self, name, pid, stdout, stderr, newer_than, time_fn=None, sleep_fn=None, wait_for_log=True):
+    def _get_crash_log(self, name, pid, stdout, stderr, newer_than, time_fn=None, sleep_fn=None, wait_for_log=True, target_host=None):
         # Note that we do slow-spin here and wait, since it appears the time
         # ReportCrash takes to actually write and flush the file varies when there are
         # lots of simultaneous crashes going on.
         time_fn = time_fn or time.time
         sleep_fn = sleep_fn or time.sleep
         crash_log = ''
-        crash_logs = CrashLogs(self.host, self.path_to_crash_logs())
+        crash_logs = CrashLogs(target_host or self.host, self.path_to_crash_logs(), crash_logs_to_skip=self._crash_logs_to_skip_for_host.get(target_host or self.host, []))
         now = time_fn()
         deadline = now + 5 * int(self.get_option('child_processes', 1))
         while not crash_log and now <= deadline:
index f80c23b..60ae94b 100644 (file)
@@ -137,9 +137,5 @@ class DarwinTest(port_testcase.PortTestCase):
         OutputCapture().assert_outputs(self, port.sample_process, args=['test', 42])
 
     def test_get_crash_log(self):
-        # Darwin crash logs are tested elsewhere, so here we just make sure we don't crash.
-        def fake_time_cb():
-            times = [0, 20, 40]
-            return lambda: times.pop(0)
         port = self.make_port(port_name=self.port_name)
         port._get_crash_log('DumpRenderTree', 1234, None, None, time.time(), wait_for_log=False)
index 67820ff..9c2724b 100644 (file)
@@ -63,6 +63,11 @@ class Device(object):
 
         self.listening_socket = None
 
+    def symbolicate_crash_log_if_needed(self, path):
+        if hasattr(self.platform_device, 'symbolicate_crash_log_if_needed'):
+            return self.platform_device.symbolicate_crash_log_if_needed(path)
+        return self.filesystem.read_text_file(path)
+
     @property
     def executive(self):
         return self.platform_device.executive
index 723244b..36c85b3 100644 (file)
@@ -241,7 +241,7 @@ class Driver(object):
             crashed_pid=self._crashed_pid, crash_log=crash_log, pid=pid)
 
     def _get_crash_log(self, stdout, stderr, newer_than):
-        return self._port._get_crash_log(self._crashed_process_name, self._crashed_pid, stdout, stderr, newer_than)
+        return self._port._get_crash_log(self._crashed_process_name, self._crashed_pid, stdout, stderr, newer_than, target_host=self._target_host)
 
     def _command_wrapper(self):
         # Hook for injecting valgrind or other runtime instrumentation, used by e.g. tools/valgrind/valgrind_tests.py.
index 0c59f5c..5d67aaf 100644 (file)
@@ -220,7 +220,7 @@ class GtkPort(Port):
     def check_sys_deps(self, needs_http):
         return super(GtkPort, self).check_sys_deps(needs_http) and self._driver_class().check_driver(self)
 
-    def _get_crash_log(self, name, pid, stdout, stderr, newer_than):
+    def _get_crash_log(self, name, pid, stdout, stderr, newer_than, target_host=None):
         name = "WebKitWebProcess" if name == "WebProcess" else name
         return GDBCrashLogGenerator(name, pid, newer_than, self._filesystem, self._path_to_driver).generate_crash_log(stdout, stderr)
 
index fdd25c2..dda1b50 100644 (file)
@@ -124,11 +124,13 @@ class IOSPort(DarwinPort):
             _log.debug('Skipping installation')
 
         for i in xrange(self.child_processes()):
-            self.target_host(i).prepare_for_testing(
+            host = self.target_host(i)
+            host.prepare_for_testing(
                 self.ports_to_forward(),
                 self.app_identifier_from_bundle(self._path_to_driver()),
                 self.layout_tests_dir(),
             )
+            self._crash_logs_to_skip_for_host[host] = host.filesystem.files_under(self.path_to_crash_logs())
 
     def clean_up_test_run(self):
         super(IOSPort, self).clean_up_test_run()
index 4ac8c90..521baac 100644 (file)
@@ -23,6 +23,7 @@
 import logging
 
 from webkitpy.common.memoized import memoized
+from webkitpy.common.system.crashlogs import CrashLogs
 from webkitpy.port.config import apple_additions
 from webkitpy.port.ios import IOSPort
 
@@ -67,16 +68,33 @@ class IOSDevicePort(IOSPort):
             port_name = port_name + '-' + major_version_number
         return port_name
 
-    # FIXME: These need device implementations <rdar://problem/30497991>.
     def path_to_crash_logs(self):
-        raise NotImplementedError
+        if not apple_additions():
+            raise RuntimeError(self.NO_ON_DEVICE_TESTING)
+        return apple_additions().ios_crash_log_path()
+
+    def _look_for_all_crash_logs_in_log_dir(self, newer_than):
+        log_list = {}
+        for device in self._device_for_worker_number_map():
+            crash_log = CrashLogs(device, self.path_to_crash_logs(), crash_logs_to_skip=self._crash_logs_to_skip_for_host.get(device, []))
+            log_list.update(crash_log.find_all_logs(include_errors=True, newer_than=newer_than))
+        return log_list
+
+    def _get_crash_log(self, name, pid, stdout, stderr, newer_than, time_fn=None, sleep_fn=None, wait_for_log=True, target_host=None):
+        if target_host:
+            return super(IOSDevicePort, self)._get_crash_log(name, pid, stdout, stderr, newer_than, time_fn=time_fn, sleep_fn=sleep_fn, wait_for_log=wait_for_log, target_host=target_host)
+
+        # We need to search every device since one was not specified.
+        for device in self._device_for_worker_number_map():
+            stderr_out, crashlog = super(IOSDevicePort, self)._get_crash_log(name, pid, stdout, stderr, newer_than, time_fn=time_fn, sleep_fn=sleep_fn, wait_for_log=False, target_host=device)
+            if crashlog:
+                return (stderr, crashlog)
+        return (stderr, None)
 
+    # FIXME: These need device implementations <rdar://problem/30497991>.
     def check_for_leaks(self, process_name, process_pid):
         pass
 
-    def look_for_new_crash_logs(self, crashed_processes, start_time):
-        return {}
-
     def look_for_new_samples(self, unresponsive_processes, start_time):
         return {}
 
@@ -90,9 +108,6 @@ class IOSDevicePort(IOSPort):
     def operating_system(self):
         return 'ios-device'
 
-    def _get_crash_log(self, name, pid, stdout, stderr, newer_than, time_fn=None, sleep_fn=None, wait_for_log=True):
-        return (stderr, None)
-
     def _create_devices(self, device_class):
         if not apple_additions():
             raise RuntimeError(self.NO_ON_DEVICE_TESTING)
index 72c653e..d9237fd 100644 (file)
@@ -20,6 +20,8 @@
 # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
+import time
+
 from webkitpy.port.ios_device import IOSDevicePort
 from webkitpy.port import ios_testcase
 
@@ -37,10 +39,17 @@ class IOSDeviceTest(ios_testcase.IOSTest):
     def test_operating_system(self):
         self.assertEqual('ios-device', self.make_port().operating_system())
 
-    # FIXME: Update tests when <rdar://problem/30497991> is completed.
     def test_crashlog_path(self):
-        pass
+        port = self.make_port()
+        with self.assertRaises(RuntimeError):
+            port.path_to_crash_logs()
 
+    def test_get_crash_log(self):
+        port = self.make_port(port_name=self.port_name)
+        with self.assertRaises(RuntimeError):
+            port._get_crash_log('DumpRenderTree', 1234, None, None, time.time(), wait_for_log=False)
+
+    # FIXME: Update tests when <rdar://problem/30497991> is completed.
     def test_spindump(self):
         pass
 
index 8769c26..99d1ad8 100644 (file)
@@ -372,7 +372,7 @@ class WinPort(ApplePort):
     def path_to_crash_logs(self):
         return self.results_directory()
 
-    def _get_crash_log(self, name, pid, stdout, stderr, newer_than, time_fn=None, sleep_fn=None, wait_for_log=True):
+    def _get_crash_log(self, name, pid, stdout, stderr, newer_than, time_fn=None, sleep_fn=None, wait_for_log=True, target_host=None):
         # Note that we do slow-spin here and wait, since it appears the time
         # ReportCrash takes to actually write and flush the file varies when there are
         # lots of simultaneous crashes going on.
@@ -380,7 +380,7 @@ class WinPort(ApplePort):
         time_fn = time_fn or time.time
         sleep_fn = sleep_fn or time.sleep
         crash_log = ''
-        crash_logs = CrashLogs(self.host, self.path_to_crash_logs())
+        crash_logs = CrashLogs(target_host or self.host, self.path_to_crash_logs(), crash_logs_to_skip=self._crash_logs_to_skip_for_host.get(target_host or self.host, []))
         now = time_fn()
         # FIXME: delete this after we're sure this code is working ...
         _log.debug('looking for crash log for %s:%s' % (name, str(pid)))
index b3cd086..31c156c 100644 (file)
@@ -74,6 +74,6 @@ class WPEPort(Port):
     def _path_to_image_diff(self):
         return self._built_executables_path('ImageDiff')
 
-    def _get_crash_log(self, name, pid, stdout, stderr, newer_than):
+    def _get_crash_log(self, name, pid, stdout, stderr, newer_than, target_host=None):
         name = "WPEWebProcess" if name == "WebProcess" else name
         return GDBCrashLogGenerator(name, pid, newer_than, self._filesystem, self._path_to_driver).generate_crash_log(stdout, stderr)