Commit c498cb37 authored by mseaborn@chromium.org's avatar mseaborn@chromium.org

Breakpad: Add a test for crashes in the browser process

Use "--crash-test" to generate a crash in the browser process and
check that it produces a Breakpad crash dump.

This is in preparation for compiling Breakpad into Linux Chromium by
default, so that I will be able to check that crash_dump_tester.py
finds crash dumps OK on Linux.

We extend crash_dump_tester.py to launch both 32-bit and 64-bit
versions of crash_service.exe on Windows, so that we can get crash
dumps from the 32-bit browser process even on 64-bit systems.  We
introduce the cleanups_funcs list to make this cleaner.

This is not the best place to put Breakpad tests, but Chrome doesn't
have any other adequate framework for testing Breakpad at the moment.

BUG=105778
TEST=nacl_integration

Review URL: https://codereview.chromium.org/11818007

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@175701 0039d316-1c4b-4281-b951-d872f2087c98
parent 2e393293
<!--
Copyright (c) 2013 The Chromium Authors. All rights reserved.
Use of this source code is governed by a BSD-style license that can be
found in the LICENSE file.
-->
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head>
<meta http-equiv="Pragma" content="no-cache" />
<meta http-equiv="Expires" content="-1" />
<script type="text/javascript" src="nacltest.js"></script>
<title>Browser Process Crash Test</title>
</head>
<body>
<h1>Browser Process Crash Test</h1>
<script type="text/javascript">
var tester = new Tester();
// We don't normally expect any of this code to run, because we are
// testing a browser process crash. If we reach here, the
// "--crash-test" option does not work.
tester.addTest('browser_process_crash', function(status) {
status.fail('Browser was supposed to crash, not reach here');
});
tester.run();
</script>
</body>
</html>
......@@ -37,45 +37,25 @@ def ReadDumpTxtFile(filename):
return dump_info
def StartCrashService(browser_path, dumps_dir, windows_pipe_name, win64):
if sys.platform == 'win32':
# Find crash_service.exe relative to chrome.exe. This is a bit icky.
browser_dir = os.path.dirname(browser_path)
# Ideally we would just query the OS here to find out whether we
# are running x86-32 or x86-64 Windows, but Python's win32api
# module does not contain a wrapper for GetNativeSystemInfo(),
# which is what NaCl uses to check this, or for IsWow64Process(),
# which is what Chromium uses. Instead, we just rely on the build
# system to tell us. Furthermore, on an x86-64 Windows system we
# could launch both versions of crash_service, in order to check
# that they do not interfere, but for simplicity we do not.
if win64:
executable_name = 'crash_service64.exe'
else:
executable_name = 'crash_service.exe'
proc = subprocess.Popen([os.path.join(browser_dir, executable_name),
'--dumps-dir=%s' % dumps_dir,
'--pipe-name=%s' % windows_pipe_name])
def Cleanup():
# Note that if the process has already exited, this will raise
# an 'Access is denied' WindowsError exception, but
# crash_service.exe is not supposed to do this and such
# behaviour should make the test fail.
proc.terminate()
status = proc.wait()
sys.stdout.write('crash_dump_tester: '
'crash_service.exe exited with status %s\n' % status)
# We add a delay because there is probably a race condition:
# crash_service.exe might not have finished doing
# CreateNamedPipe() before NaCl does a crash dump and tries to
# connect to that pipe.
# TODO(mseaborn): We could change crash_service.exe to report when
# it has successfully created the named pipe.
time.sleep(1)
else:
def Cleanup():
pass
return Cleanup
def StartCrashService(browser_path, dumps_dir, windows_pipe_name,
cleanup_funcs, crash_service_exe):
# Find crash_service.exe relative to chrome.exe. This is a bit icky.
browser_dir = os.path.dirname(browser_path)
proc = subprocess.Popen([os.path.join(browser_dir, crash_service_exe),
'--dumps-dir=%s' % dumps_dir,
'--pipe-name=%s' % windows_pipe_name])
def Cleanup():
# Note that if the process has already exited, this will raise
# an 'Access is denied' WindowsError exception, but
# crash_service.exe is not supposed to do this and such
# behaviour should make the test fail.
proc.terminate()
status = proc.wait()
sys.stdout.write('crash_dump_tester: %s exited with status %s\n'
% (crash_service_exe, status))
cleanup_funcs.append(Cleanup)
def GetDumpFiles(dumps_dir):
......@@ -88,16 +68,31 @@ def GetDumpFiles(dumps_dir):
if dump_file.endswith('.dmp')]
def Main():
def Main(cleanup_funcs):
parser = browser_tester.BuildArgParser()
parser.add_option('--expected_crash_dumps', dest='expected_crash_dumps',
type=int, default=0,
help='The number of crash dumps that we should expect')
parser.add_option('--expected_process_type_for_crash',
dest='expected_process_type_for_crash',
type=str, default='nacl-loader',
help='The type of Chromium process that we expect the '
'crash dump to be for')
# Ideally we would just query the OS here to find out whether we are
# running x86-32 or x86-64 Windows, but Python's win32api module
# does not contain a wrapper for GetNativeSystemInfo(), which is
# what NaCl uses to check this, or for IsWow64Process(), which is
# what Chromium uses. Instead, we just rely on the build system to
# tell us.
parser.add_option('--win64', dest='win64', action='store_true',
help='Pass this if we are running tests for x86-64 Windows')
options, args = parser.parse_args()
dumps_dir = tempfile.mkdtemp(prefix='nacl_crash_dump_tester_')
def CleanUpDumpsDir():
browsertester.browserlauncher.RemoveDirectory(dumps_dir)
cleanup_funcs.append(CleanUpDumpsDir)
# To get a guaranteed unique pipe name, use the base name of the
# directory we just created.
windows_pipe_name = r'\\.\pipe\%s_crash_service' % os.path.basename(dumps_dir)
......@@ -109,15 +104,26 @@ def Main():
# Override the default (global) Windows pipe name that Chromium will
# use for out-of-process crash reporting.
os.environ['CHROME_BREAKPAD_PIPE_NAME'] = windows_pipe_name
# Launch the x86-32 crash service so that we can handle crashes in
# the browser process.
StartCrashService(options.browser_path, dumps_dir, windows_pipe_name,
cleanup_funcs, 'crash_service.exe')
if options.win64:
# Launch the x86-64 crash service so that we can handle crashes
# in the NaCl loader process (nacl64.exe).
StartCrashService(options.browser_path, dumps_dir, windows_pipe_name,
cleanup_funcs, 'crash_service64.exe')
# We add a delay because there is probably a race condition:
# crash_service.exe might not have finished doing
# CreateNamedPipe() before NaCl does a crash dump and tries to
# connect to that pipe.
# TODO(mseaborn): We could change crash_service.exe to report when
# it has successfully created the named pipe.
time.sleep(1)
elif sys.platform == 'darwin':
os.environ['BREAKPAD_DUMP_LOCATION'] = dumps_dir
cleanup_func = StartCrashService(options.browser_path, dumps_dir,
windows_pipe_name, options.win64)
try:
result = browser_tester.Run(options.url, options)
finally:
cleanup_func()
result = browser_tester.Run(options.url, options)
dmp_files = GetDumpFiles(dumps_dir)
failed = False
......@@ -140,9 +146,9 @@ def Main():
# Check that the crash dump comes from the NaCl process.
dump_info = ReadDumpTxtFile(second_file)
if 'ptype' in dump_info:
msg = ('crash_dump_tester: ERROR: Unexpected ptype value: %r\n'
% dump_info['ptype'])
if dump_info['ptype'] != 'nacl-loader':
msg = ('crash_dump_tester: ERROR: Unexpected ptype value: %r != %r\n'
% (dump_info['ptype'], options.expected_process_type_for_crash))
if dump_info['ptype'] != options.expected_process_type_for_crash:
sys.stdout.write(msg)
failed = True
else:
......@@ -158,9 +164,17 @@ def Main():
else:
sys.stdout.write('crash_dump_tester: PASSED\n')
browsertester.browserlauncher.RemoveDirectory(dumps_dir)
return result
def MainWrapper():
cleanup_funcs = []
try:
return Main(cleanup_funcs)
finally:
for func in cleanup_funcs:
func()
if __name__ == '__main__':
sys.exit(Main())
sys.exit(MainWrapper())
......@@ -21,6 +21,28 @@ def GetNexeByName(name):
return env.File('${STAGING_DIR}/%s${PROGSUFFIX}' %
env.ProgramNameForNmf(name))
# This tests that crashes in Chromium's browser process successfully
# produce crash dumps via Breakpad.
# TODO(mseaborn): Enable this for Linux, too, when Breakpad is
# compiled into Chromium by default.
# See http://code.google.com/p/chromium/issues/detail?id=105778
if env.Bit('host_windows') or env.Bit('host_mac'):
node = env.PPAPIBrowserTester(
'breakpad_browser_process_crash_test.out',
python_tester_script=env.File('crash_dump_tester.py'),
browser_flags=['--crash-test'], # Tell the browser process to crash.
url='browser_process_crash.html',
nmf_names=[],
files=[env.File('browser_process_crash.html')],
args=platform_args + ['--expect_browser_process_crash',
'--expected_crash_dumps=1',
'--expected_process_type=browser'])
env.AddNodeToTestSuite(
node, ['chrome_browser_tests'], 'run_breakpad_browser_process_crash_test',
is_broken=env.PPAPIBrowserTesterIsBroken() or
env.Bit('running_on_valgrind'))
# This crash in trusted code should produce a crash dump.
crash_test_url = 'trusted_crash_in_startup.html'
if env.Bit('pnacl_generate_pexe'):
......
......@@ -131,6 +131,11 @@ def BuildArgParser():
parser.add_option('--nacl_exe_stderr', dest='nacl_exe_stderr',
type='string', default=None,
help='Redirect standard error of NaCl executable.')
parser.add_option('--expect_browser_process_crash',
dest='expect_browser_process_crash',
action='store_true',
help='Do not signal a failure if the browser process '
'crashes')
return parser
......@@ -251,6 +256,8 @@ def RunTestsOnce(url, options):
try:
while server.test_in_progress or options.interactive:
if not browser.IsRunning():
if options.expect_browser_process_crash:
break
listener.ServerError('Browser process ended during test '
'(return code %r)' % browser.GetReturnCode())
# If Chrome exits prematurely without making a single request to the
......
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