2 # Copyright (c) 2012 The Chromium Authors. All rights reserved.
3 # Use of this source code is governed by a BSD-style license that can be
4 # found in the LICENSE file.
12 script_dir
= os
.path
.dirname(__file__
)
13 sys
.path
.append(os
.path
.join(script_dir
,
14 '../../tools/browser_tester'))
17 import browsertester
.browserlauncher
19 # This script extends browser_tester to check for the presence of
20 # Breakpad crash dumps.
23 # This reads a file of lines containing 'key:value' pairs.
24 # The file contains entries like the following:
29 def ReadDumpTxtFile(filename
):
31 fh
= open(filename
, 'r')
34 key
, value
= line
.rstrip().split(':', 1)
35 dump_info
[key
] = value
40 def StartCrashService(browser_path
, dumps_dir
, windows_pipe_name
,
41 cleanup_funcs
, crash_service_exe
,
42 skip_if_missing
=False):
43 # Find crash_service.exe relative to chrome.exe. This is a bit icky.
44 browser_dir
= os
.path
.dirname(browser_path
)
45 crash_service_path
= os
.path
.join(browser_dir
, crash_service_exe
)
46 if skip_if_missing
and not os
.path
.exists(crash_service_path
):
48 proc
= subprocess
.Popen([crash_service_path
,
49 '--v=1', # Verbose output for debugging failures
50 '--dumps-dir=%s' % dumps_dir
,
51 '--pipe-name=%s' % windows_pipe_name
])
54 # Note that if the process has already exited, this will raise
55 # an 'Access is denied' WindowsError exception, but
56 # crash_service.exe is not supposed to do this and such
57 # behaviour should make the test fail.
60 sys
.stdout
.write('crash_dump_tester: %s exited with status %s\n'
61 % (crash_service_exe
, status
))
63 cleanup_funcs
.append(Cleanup
)
66 def ListPathsInDir(dir_path
):
67 if os
.path
.exists(dir_path
):
68 return [os
.path
.join(dir_path
, name
)
69 for name
in os
.listdir(dir_path
)]
74 def GetDumpFiles(dumps_dirs
):
76 for dumps_dir
in dumps_dirs
77 for filename
in ListPathsInDir(dumps_dir
)]
78 sys
.stdout
.write('crash_dump_tester: Found %i files\n' % len(all_files
))
79 for dump_file
in all_files
:
80 sys
.stdout
.write(' %s (size %i)\n'
81 % (dump_file
, os
.stat(dump_file
).st_size
))
82 return [dump_file
for dump_file
in all_files
83 if dump_file
.endswith('.dmp')]
86 def Main(cleanup_funcs
):
87 parser
= browser_tester
.BuildArgParser()
88 parser
.add_option('--expected_crash_dumps', dest
='expected_crash_dumps',
90 help='The number of crash dumps that we should expect')
91 parser
.add_option('--expected_process_type_for_crash',
92 dest
='expected_process_type_for_crash',
93 type=str, default
='nacl-loader',
94 help='The type of Chromium process that we expect the '
95 'crash dump to be for')
96 # Ideally we would just query the OS here to find out whether we are
97 # running x86-32 or x86-64 Windows, but Python's win32api module
98 # does not contain a wrapper for GetNativeSystemInfo(), which is
99 # what NaCl uses to check this, or for IsWow64Process(), which is
100 # what Chromium uses. Instead, we just rely on the build system to
102 parser
.add_option('--win64', dest
='win64', action
='store_true',
103 help='Pass this if we are running tests for x86-64 Windows')
104 options
, args
= parser
.parse_args()
106 temp_dir
= tempfile
.mkdtemp(prefix
='nacl_crash_dump_tester_')
107 def CleanUpTempDir():
108 browsertester
.browserlauncher
.RemoveDirectory(temp_dir
)
109 cleanup_funcs
.append(CleanUpTempDir
)
111 # To get a guaranteed unique pipe name, use the base name of the
112 # directory we just created.
113 windows_pipe_name
= r
'\\.\pipe\%s_crash_service' % os
.path
.basename(temp_dir
)
115 # This environment variable enables Breakpad crash dumping in
116 # non-official builds of Chromium.
117 os
.environ
['CHROME_HEADLESS'] = '1'
118 if sys
.platform
== 'win32':
120 # Override the default (global) Windows pipe name that Chromium will
121 # use for out-of-process crash reporting.
122 os
.environ
['CHROME_BREAKPAD_PIPE_NAME'] = windows_pipe_name
123 # Launch the x86-32 crash service so that we can handle crashes in
124 # the browser process.
125 StartCrashService(options
.browser_path
, dumps_dir
, windows_pipe_name
,
126 cleanup_funcs
, 'crash_service.exe')
128 # Launch the x86-64 crash service so that we can handle crashes
129 # in the NaCl loader process (nacl64.exe).
130 # Skip if missing, since in win64 builds crash_service.exe is 64-bit
131 # and crash_service64.exe does not exist.
132 StartCrashService(options
.browser_path
, dumps_dir
, windows_pipe_name
,
133 cleanup_funcs
, 'crash_service64.exe',
134 skip_if_missing
=True)
135 # We add a delay because there is probably a race condition:
136 # crash_service.exe might not have finished doing
137 # CreateNamedPipe() before NaCl does a crash dump and tries to
138 # connect to that pipe.
139 # TODO(mseaborn): We could change crash_service.exe to report when
140 # it has successfully created the named pipe.
142 elif sys
.platform
== 'darwin':
144 os
.environ
['BREAKPAD_DUMP_LOCATION'] = dumps_dir
145 elif sys
.platform
.startswith('linux'):
146 # The "--user-data-dir" option is not effective for the Breakpad
147 # setup in Linux Chromium, because Breakpad is initialized before
148 # "--user-data-dir" is read. So we set HOME to redirect the crash
149 # dumps to a temporary directory.
151 os
.environ
['HOME'] = home_dir
152 options
.enable_crash_reporter
= True
154 result
= browser_tester
.Run(options
.url
, options
)
156 # Find crash dump results.
157 if sys
.platform
.startswith('linux'):
158 # Look in "~/.config/*/Crash Reports". This will find crash
159 # reports under ~/.config/chromium or ~/.config/google-chrome, or
160 # under other subdirectories in case the branding is changed.
161 dumps_dirs
= [os
.path
.join(path
, 'Crash Reports')
162 for path
in ListPathsInDir(os
.path
.join(home_dir
, '.config'))]
164 dumps_dirs
= [dumps_dir
]
165 dmp_files
= GetDumpFiles(dumps_dirs
)
168 msg
= ('crash_dump_tester: ERROR: Got %i crash dumps but expected %i\n' %
169 (len(dmp_files
), options
.expected_crash_dumps
))
170 if len(dmp_files
) != options
.expected_crash_dumps
:
171 sys
.stdout
.write(msg
)
174 for dump_file
in dmp_files
:
175 # Sanity check: Make sure dumping did not fail after opening the file.
176 msg
= 'crash_dump_tester: ERROR: Dump file is empty\n'
177 if os
.stat(dump_file
).st_size
== 0:
178 sys
.stdout
.write(msg
)
181 # On Windows, the crash dumps should come in pairs of a .dmp and
183 if sys
.platform
== 'win32':
184 second_file
= dump_file
[:-4] + '.txt'
185 msg
= ('crash_dump_tester: ERROR: File %r is missing a corresponding '
186 '%r file\n' % (dump_file
, second_file
))
187 if not os
.path
.exists(second_file
):
188 sys
.stdout
.write(msg
)
191 # Check that the crash dump comes from the NaCl process.
192 dump_info
= ReadDumpTxtFile(second_file
)
193 if 'ptype' in dump_info
:
194 msg
= ('crash_dump_tester: ERROR: Unexpected ptype value: %r != %r\n'
195 % (dump_info
['ptype'], options
.expected_process_type_for_crash
))
196 if dump_info
['ptype'] != options
.expected_process_type_for_crash
:
197 sys
.stdout
.write(msg
)
200 sys
.stdout
.write('crash_dump_tester: ERROR: Missing ptype field\n')
202 # TODO(mseaborn): Ideally we would also check that a backtrace
203 # containing an expected function name can be extracted from the
207 sys
.stdout
.write('crash_dump_tester: FAILED\n')
210 sys
.stdout
.write('crash_dump_tester: PASSED\n')
218 return Main(cleanup_funcs
)
220 for func
in cleanup_funcs
:
224 if __name__
== '__main__':
225 sys
.exit(MainWrapper())