Commit f33d6c5b authored by dpranke@chromium.org's avatar dpranke@chromium.org
Browse files

Re-land fix for r81603 yet again (!) with latest build fix.

https://bugs.webkit.org/show_bug.cgi?id=81603

Unreviewed, build fix.

The latest problem was a bad sprintf() in
BasePort._get_crash_log(). I've reworked that routine and added
unit tests for coverage.

* Scripts/webkitpy/layout_tests/controllers/single_test_runner.py:
(SingleTestRunner._handle_error):
* Scripts/webkitpy/layout_tests/controllers/test_result_writer.py:
(write_test_result):
(TestResultWriter.write_crash_log):
* Scripts/webkitpy/layout_tests/port/base.py:
(Port.diff_text):
(Port._get_crash_log):
* Scripts/webkitpy/layout_tests/port/chromium.py:
(ChromiumDriver.run_test):
* Scripts/webkitpy/layout_tests/port/chromium_unittest.py:
(ChromiumDriverTest.test_crash_log):
* Scripts/webkitpy/layout_tests/port/driver.py:
(DriverOutput.__init__):
* Scripts/webkitpy/layout_tests/port/mac.py:
(MacPort.is_lion):
(MacPort._get_crash_log):
* Scripts/webkitpy/layout_tests/port/mac_unittest.py:
(test_default_child_processes):
(test_get_crash_log):
* Scripts/webkitpy/layout_tests/port/mock_drt_unittest.py:
(MockDRTPortTest.test_get_crash_log):
* Scripts/webkitpy/layout_tests/port/port_testcase.py:
(PortTestCase.test_get_crash_log):
* Scripts/webkitpy/layout_tests/port/test.py:
(TestDriver.run_test):
* Scripts/webkitpy/layout_tests/port/webkit.py:
(WebKitDriver.__init__):
(WebKitDriver._start):
(WebKitDriver.has_crashed):
(WebKitDriver._check_for_driver_crash):
(WebKitDriver.run_test):

git-svn-id: http://svn.webkit.org/repository/webkit/trunk@111736 268f45cc-cd09-0410-ab3c-d52691b4dbfc
parent 46cd6d0f
2012-03-22 Dirk Pranke <dpranke@chromium.org>
Re-land fix for r81603 yet again (!) with latest build fix.
https://bugs.webkit.org/show_bug.cgi?id=81603
Unreviewed, build fix.
The latest problem was a bad sprintf() in
BasePort._get_crash_log(). I've reworked that routine and added
unit tests for coverage.
* Scripts/webkitpy/layout_tests/controllers/single_test_runner.py:
(SingleTestRunner._handle_error):
* Scripts/webkitpy/layout_tests/controllers/test_result_writer.py:
(write_test_result):
(TestResultWriter.write_crash_log):
* Scripts/webkitpy/layout_tests/port/base.py:
(Port.diff_text):
(Port._get_crash_log):
* Scripts/webkitpy/layout_tests/port/chromium.py:
(ChromiumDriver.run_test):
* Scripts/webkitpy/layout_tests/port/chromium_unittest.py:
(ChromiumDriverTest.test_crash_log):
* Scripts/webkitpy/layout_tests/port/driver.py:
(DriverOutput.__init__):
* Scripts/webkitpy/layout_tests/port/mac.py:
(MacPort.is_lion):
(MacPort._get_crash_log):
* Scripts/webkitpy/layout_tests/port/mac_unittest.py:
(test_default_child_processes):
(test_get_crash_log):
* Scripts/webkitpy/layout_tests/port/mock_drt_unittest.py:
(MockDRTPortTest.test_get_crash_log):
* Scripts/webkitpy/layout_tests/port/port_testcase.py:
(PortTestCase.test_get_crash_log):
* Scripts/webkitpy/layout_tests/port/test.py:
(TestDriver.run_test):
* Scripts/webkitpy/layout_tests/port/webkit.py:
(WebKitDriver.__init__):
(WebKitDriver._start):
(WebKitDriver.has_crashed):
(WebKitDriver._check_for_driver_crash):
(WebKitDriver.run_test):
2012-03-22 Alexander Færøy <alexander.faeroy@nokia.com>
 
[Qt] Enable support for handling database quota's in the MiniBrowser
......@@ -198,7 +198,9 @@ class SingleTestRunner:
testname = self._test_name
if driver_output.crash:
failures.append(test_failures.FailureCrash(bool(reference_filename)))
failures.append(test_failures.FailureCrash(bool(reference_filename),
driver_output.crashed_process_name,
driver_output.crashed_pid))
if driver_output.error:
_log.debug("%s %s crashed, stack trace:" % (self._worker_name, testname))
else:
......
......@@ -29,7 +29,6 @@
import logging
from webkitpy.common.system.crashlogs import CrashLogs
from webkitpy.layout_tests.models import test_failures
......@@ -63,7 +62,7 @@ def write_test_result(filesystem, port, test_name, driver_output,
writer.write_audio_files(driver_output.audio, expected_driver_output.audio)
elif isinstance(failure, test_failures.FailureCrash):
crashed_driver_output = expected_driver_output if failure.is_reftest else driver_output
writer.write_crash_report(crashed_driver_output.crashed_process_name, crashed_driver_output.error)
writer.write_crash_log(crashed_driver_output.crash_log)
elif isinstance(failure, test_failures.FailureReftestMismatch):
writer.write_image_files(driver_output.image, expected_driver_output.image)
# FIXME: This work should be done earlier in the pipeline (e.g., when we compare images for non-ref tests).
......@@ -157,15 +156,12 @@ class TestResultWriter(object):
fs.maybe_make_directory(fs.dirname(filename))
fs.write_binary_file(filename, error)
def write_crash_report(self, crashed_process_name, error):
def write_crash_log(self, crash_log):
fs = self._filesystem
filename = self.output_filename(self.FILENAME_SUFFIX_CRASH_LOG + ".txt")
fs.maybe_make_directory(fs.dirname(filename))
crash_logs = CrashLogs(fs)
log = crash_logs.find_newest_log(crashed_process_name)
# CrashLogs doesn't support every platform, so we fall back to
# including the stderr output, which is admittedly somewhat redundant.
fs.write_text_file(filename, log if log else error)
if crash_log is not None:
fs.write_text_file(filename, crash_log)
def write_text_files(self, actual_text, expected_text):
self.write_output_files(".txt", actual_text, expected_text)
......
......@@ -290,9 +290,6 @@ class Port(object):
actual_filename)
return ''.join(diff)
def is_crash_reporter(self, process_name):
return False
def check_for_leaks(self, process_name, process_pid):
# Subclasses should check for leaks in the running process
# and print any necessary warnings if leaks are found.
......@@ -1054,6 +1051,15 @@ class Port(object):
"""Returns the port's driver implementation."""
raise NotImplementedError('Port._driver_class')
def _get_crash_log(self, name, pid, stdout, stderr):
name_str = name or '<unknown process name>'
pid_str = str(pid or '<unknown>')
stdout_lines = (stdout or '<empty>').decode('utf8').splitlines()
stderr_lines = (stderr or '<empty>').decode('utf8').splitlines()
return 'crash log for %s (pid %s):\n%s\n%s\n' % (name_str, pid_str,
'\n'.join(('STDOUT: ' + l) for l in stdout_lines),
'\n'.join(('STDERR: ' + l) for l in stderr_lines))
def virtual_test_suites(self):
return []
......
......@@ -592,16 +592,23 @@ class ChromiumDriver(Driver):
text = None
error = ''.join(error)
crashed_process_name = None
# Currently the stacktrace is in the text output, not error, so append the two together so
# that we can see stack in the output. See http://webkit.org/b/66806
# FIXME: We really should properly handle the stderr output separately.
crash_log = ''
crashed_process_name = None
crashed_pid = None
if crash:
error = error + str(text)
crashed_process_name = self._port.driver_name()
if self._proc:
crashed_pid = self._proc.pid
crash_log = self._port._get_crash_log(crashed_process_name, crashed_pid, text, error)
if text:
error = error + text
return DriverOutput(text, output_image, actual_checksum, audio=audio_bytes,
crash=crash, crashed_process_name=crashed_process_name, test_time=run_time, timeout=timeout, error=error)
crash=crash, crashed_process_name=crashed_process_name, crashed_pid=crashed_pid, crash_log=crash_log,
test_time=run_time, timeout=timeout, error=error)
def start(self, pixel_tests, per_test_args):
if not self._proc:
......
......@@ -92,17 +92,23 @@ class ChromiumDriverTest(unittest.TestCase):
self.driver._proc.stdout.readline = mock_readline
self._assert_write_command_and_read_line(expected_crash=True)
def test_crashed_process_name(self):
def test_crash_log(self):
self.driver._proc = Mock()
# Simulate a crash by having stdout close unexpectedly.
def mock_readline():
raise IOError
self.driver._proc.stdout.readline = mock_readline
self.driver._proc.pid = 1234
self.driver.test_to_uri = lambda test: 'mocktesturi'
self.driver._port.driver_name = lambda: 'mockdriver'
self.driver._port._get_crash_log = lambda name, pid, out, err: 'mockcrashlog'
driver_output = self.driver.run_test(DriverInput(test_name='some/test.html', timeout=1, image_hash=None, is_reftest=False))
self.assertEqual(self.driver._port.driver_name(), driver_output.crashed_process_name)
self.assertTrue(driver_output.crash)
self.assertEqual(driver_output.crashed_process_name, 'mockdriver')
self.assertEqual(driver_output.crashed_pid, 1234)
self.assertEqual(driver_output.crash_log, 'mockcrashlog')
def test_stop(self):
self.pid = None
......
......@@ -64,7 +64,8 @@ class DriverOutput(object):
strip_patterns.append((re.compile('scrollHeight [0-9]+'), 'scrollHeight'))
def __init__(self, text, image, image_hash, audio, crash=False,
test_time=0, timeout=False, error='', crashed_process_name=None):
test_time=0, timeout=False, error='', crashed_process_name='??',
crashed_pid=None, crash_log=None):
# FIXME: Args could be renamed to better clarify what they do.
self.text = text
self.image = image # May be empty-string if the test crashes.
......@@ -73,6 +74,8 @@ class DriverOutput(object):
self.audio = audio # Binary format is port-dependent.
self.crash = crash
self.crashed_process_name = crashed_process_name
self.crashed_pid = crashed_pid
self.crash_log = crash_log
self.test_time = test_time
self.timeout = timeout
self.error = error # stderr output
......
......@@ -29,7 +29,9 @@
import logging
import re
import subprocess
import time
from webkitpy.common.system.crashlogs import CrashLogs
from webkitpy.layout_tests.port.apple import ApplePort
from webkitpy.layout_tests.port.leakdetector import LeakDetector
......@@ -96,10 +98,6 @@ class MacPort(ApplePort):
def is_lion(self):
return self._version == "lion"
# Belongs on a Platform object.
def is_crash_reporter(self, process_name):
return re.search(r'ReportCrash', process_name)
def default_child_processes(self):
if self.is_snowleopard():
_log.warn("Cannot run tests in parallel on Snow Leopard due to rdar://problem/10621525.")
......@@ -160,6 +158,25 @@ class MacPort(ApplePort):
def release_http_lock(self):
pass
def _get_crash_log(self, name, pid, stdout, stderr):
# 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.
# FIXME: Should most of this be moved into CrashLogs()?
crash_log = ''
crash_logs = CrashLogs(self._filesystem)
now = time.time()
deadline = now + 5 * int(self.get_option('child_processes'))
while not crash_log and now <= deadline:
crash_log = crash_logs.find_newest_log(name, pid, include_errors=True)
if not crash_log or not [line for line in crash_log.splitlines() if not line.startswith('ERROR')]:
time.sleep(0.1)
now = time.time()
if not crash_log:
crash_log = 'no crash log found for %s:%d' % (name, pid)
_log.warning(crash_log)
return crash_log
def _path_to_helper(self):
binary_name = 'LayoutTestHelper'
return self._build_path(binary_name)
......
......@@ -188,3 +188,7 @@ java/
expected_logs = "Cannot run tests in parallel on Snow Leopard due to rdar://problem/10621525.\n"
child_processes = OutputCapture().assert_outputs(self, port.default_child_processes, (), expected_logs=expected_logs)
self.assertEqual(child_processes, 1)
def test_get_crash_log(self):
# Mac crash logs are tested elsewhere
pass
......@@ -90,6 +90,8 @@ class MockDRTPortTest(port_testcase.PortTestCase):
def integration_test_helper(self):
pass
def test_get_crash_log(self):
pass
class MockDRTTest(unittest.TestCase):
def input_line(self, port, test_name, checksum=None):
......
......@@ -312,6 +312,14 @@ class PortTestCase(unittest.TestCase):
i += 1
def test_get_crash_log(self):
port = self.make_port()
self.assertEquals(port._get_crash_log(None, None, None, None),
('crash log for <unknown process name> (pid <unknown>):\n'
'STDOUT: <empty>\n'
'STDERR: <empty>\n'))
# FIXME: This class and main() should be merged into test-webkitpy.
class EnhancedTestLoader(unittest.TestLoader):
integration_tests = False
......
......@@ -35,6 +35,7 @@ from webkitpy.layout_tests.port import Port, Driver, DriverOutput
from webkitpy.layout_tests.port.base import VirtualTestSuite
from webkitpy.layout_tests.models.test_configuration import TestConfiguration
from webkitpy.common.system.filesystem_mock import MockFileSystem
from webkitpy.common.system.crashlogs import CrashLogs
# This sets basic expectations for a test. Each individual expectation
......@@ -519,13 +520,22 @@ class TestDriver(Driver):
if test.actual_audio:
audio = base64.b64decode(test.actual_audio)
crashed_process_name = None
crashed_pid = None
if test.crash:
crashed_process_name = self._port.driver_name()
crashed_pid = 1
elif test.web_process_crash:
crashed_process_name = 'WebProcess'
return DriverOutput(actual_text, test.actual_image,
test.actual_checksum, audio, crash=test.crash or test.web_process_crash,
crashed_process_name=crashed_process_name,
crashed_pid = 2
crash_log = ''
if crashed_process_name:
crash_logs = CrashLogs(self._port._filesystem)
crash_log = crash_logs.find_newest_log(crashed_process_name, None) or ''
return DriverOutput(actual_text, test.actual_image, test.actual_checksum, audio,
crash=test.crash or test.web_process_crash, crashed_process_name=crashed_process_name,
crashed_pid=crashed_pid, crash_log=crash_log,
test_time=time.time() - start_time, timeout=test.timeout, error=test.error)
def start(self, pixel_tests, per_test_args):
......
......@@ -437,7 +437,8 @@ class WebKitDriver(Driver):
# "#CRASHED - PROCESSNAME". Since those can happen at any time
# and ServerProcess won't be aware of them (since the actual tool
# didn't crash, just a subprocess) we record the crashed subprocess name here.
self._crashed_subprocess_name = None
self._crashed_process_name = None
self._crashed_pid = None
# stderr reading is scoped on a per-test (not per-block) basis, so we store the accumulated
# stderr output, as well as if we've seen #EOF on this driver instance.
......@@ -483,28 +484,34 @@ class WebKitDriver(Driver):
# FIXME: We're assuming that WebKitTestRunner checks this DumpRenderTree-named environment variable.
environment['DUMPRENDERTREE_TEMP'] = str(self._driver_tempdir)
environment['LOCAL_RESOURCE_ROOT'] = self._port.layout_tests_dir()
self._crashed_subprocess_name = None
self._crashed_process_name = None
self._crashed_pid = None
self._server_process = server_process.ServerProcess(self._port, server_name, self.cmd_line(pixel_tests, per_test_args), environment)
def has_crashed(self):
if self._server_process is None:
return False
if self._crashed_subprocess_name:
if self._crashed_process_name:
return True
if self._server_process.has_crashed():
self._crashed_subprocess_name = self._port.driver_name()
self._crashed_process_name = self._server_process.name()
self._crashed_pid = self._server_process.pid()
return True
return False
def _check_for_driver_crash(self, error_line):
if error_line == "#CRASHED\n":
# This is used on Windows to report that the process has crashed
# See http://trac.webkit.org/changeset/65537.
self._crashed_subprocess_name = self._port.driver_name()
return True
if error_line == "#CRASHED - WebProcess\n":
self._crashed_process_name = self._server_process.name()
self._crashed_pid = self._server_process.pid()
elif error_line.startswith("#CRASHED - WebProcess"):
# WebKitTestRunner uses this to report that the WebProcess subprocess crashed.
self._crashed_subprocess_name = "WebProcess"
pid = None
m = re.match('pid (\d+)', error_line)
if m:
pid = int(m.group(1))
self._crashed_process_name = 'WebProcess'
self._crashed_pid = pid
return True
return self.has_crashed()
......@@ -555,10 +562,15 @@ class WebKitDriver(Driver):
# FIXME: We may need to also read stderr until the process dies?
self.error_from_test += self._server_process.pop_all_buffered_stderr()
crash_log = ''
if self.has_crashed():
crash_log = self._port._get_crash_log(self._crashed_process_name, self._crashed_pid, text, self.error_from_test)
return DriverOutput(text, image, actual_image_hash, audio,
crash=self.has_crashed(), test_time=time.time() - start_time,
timeout=self._server_process.timed_out, error=self.error_from_test,
crashed_process_name=self._crashed_subprocess_name)
crashed_process_name=self._crashed_process_name,
crashed_pid=self._crashed_pid, crash_log=crash_log)
def _read_header(self, block, line, header_text, header_attr, header_filter=None):
if line.startswith(header_text) and getattr(block, header_attr) is None:
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment