Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(677)

Side by Side Diff: tests/inbrowser_crash_test/crash_dump_tester.py

Issue 7569002: Crash dump test: Isolate the test from any global instance of crash_service.exe (Closed) Base URL: svn://svn.chromium.org/native_client/trunk/src/native_client
Patch Set: Rebased Created 9 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « no previous file | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 #!/usr/bin/python 1 #!/usr/bin/python
2 # Copyright (c) 2011 The Native Client Authors. All rights reserved. 2 # Copyright (c) 2011 The Native Client Authors. All rights reserved.
3 # Use of this source code is governed by a BSD-style license that can be 3 # Use of this source code is governed by a BSD-style license that can be
4 # found in the LICENSE file. 4 # found in the LICENSE file.
5 5
6 import os 6 import os
7 import shutil
7 import subprocess 8 import subprocess
8 import sys 9 import sys
10 import tempfile
9 import time 11 import time
10 12
11 script_dir = os.path.dirname(__file__) 13 script_dir = os.path.dirname(__file__)
12 sys.path.append(os.path.join(script_dir, '../../tools/browser_tester')) 14 sys.path.append(os.path.join(script_dir, '../../tools/browser_tester'))
13 15
14 import browser_tester 16 import browser_tester
15 17
16 # This script extends browser_tester to check for the presence of 18 # This script extends browser_tester to check for the presence of
17 # Breakpad crash dumps. 19 # Breakpad crash dumps.
18 20
19 21
20 # TODO(mseaborn): Change Chromium's crash_service.exe so that we can
21 # tell it to put dumps in a per-test temp directory.
22 def GetDumpDirs():
23 if sys.platform == 'win32':
24 # We duplicate Chromium's logic for deciding where its per-user
25 # directory should be on Windows. We can remove this if we use a
26 # temp directory instead.
27 import win32com.shell.shell
28 import win32com.shell.shellcon
29 # This typically returns a pathname like
30 # 'C:\Documents and Settings\Username\Local Settings\Application Data'.
31 appdata_dir = win32com.shell.shell.SHGetFolderPath(
32 0, win32com.shell.shellcon.CSIDL_LOCAL_APPDATA, 0, 0)
33 return [os.path.join(appdata_dir, app_name, 'User Data', 'Crash Reports')
34 for app_name in ('Chromium', os.path.join('Google', 'Chrome'))]
35 else:
36 # TODO(mseaborn): Handle other platforms when NaCl supports
37 # Breakpad crash reporting there.
38 return []
39
40
41 def GetDumpFiles():
42 file_list = []
43 for dump_dir in GetDumpDirs():
44 if os.path.exists(dump_dir):
45 for dump_file in sorted(os.listdir(dump_dir)):
46 file_list.append(os.path.join(dump_dir, dump_file))
47 return file_list
48
49
50 def PrintDumps(desc, dump_files):
51 sys.stdout.write('crash_dump_tester: Found %i %s\n' % (len(dump_files), desc))
52 for dump_file in dump_files:
53 sys.stdout.write(' %s\n' % dump_file)
54
55
56 # This reads a file of lines containing 'key:value' pairs. 22 # This reads a file of lines containing 'key:value' pairs.
57 # The file contains entries like the following: 23 # The file contains entries like the following:
58 # plat:Win32 24 # plat:Win32
59 # prod:Chromium 25 # prod:Chromium
60 # ptype:nacl-loader 26 # ptype:nacl-loader
61 # rept:crash svc 27 # rept:crash svc
62 def ReadDumpTxtFile(filename): 28 def ReadDumpTxtFile(filename):
63 dump_info = {} 29 dump_info = {}
64 fh = open(filename, 'r') 30 fh = open(filename, 'r')
65 for line in fh: 31 for line in fh:
66 if ':' in line: 32 if ':' in line:
67 key, value = line.rstrip().split(':', 1) 33 key, value = line.rstrip().split(':', 1)
68 dump_info[key] = value 34 dump_info[key] = value
69 fh.close() 35 fh.close()
70 return dump_info 36 return dump_info
71 37
72 38
73 def StartCrashService(browser_path): 39 def StartCrashService(browser_path, dumps_dir, windows_pipe_name):
74 if sys.platform == 'win32': 40 if sys.platform == 'win32':
75 # Find crash_service.exe relative to chrome.exe. This is a bit icky. 41 # Find crash_service.exe relative to chrome.exe. This is a bit icky.
76 browser_dir = os.path.dirname(browser_path) 42 browser_dir = os.path.dirname(browser_path)
77 proc = subprocess.Popen([os.path.join(browser_dir, 'crash_service.exe')]) 43 proc = subprocess.Popen([os.path.join(browser_dir, 'crash_service.exe'),
44 '--dumps-dir=%s' % dumps_dir,
45 '--pipe-name=%s' % windows_pipe_name])
78 def Cleanup(): 46 def Cleanup():
79 try: 47 # Note that if the process has already exited, this will raise
80 proc.terminate() 48 # an 'Access is denied' WindowsError exception.
Nick Bray 2011/08/12 21:46:27 ... which should not happen in normal operation?
81 sys.stdout.write('crash_dump_tester: Stopped crash_service.exe\n') 49 proc.terminate()
82 except WindowsError:
83 # If the process has already exited, we will get an 'Access is
84 # denied' error. This can happen if another instance of
85 # crash_service.exe was already running, because our instance
86 # will fail to claim the named pipe.
87 # TODO(mseaborn): We could change crash_service.exe to create
88 # unique pipe names for testing purposes.
89 pass
90 status = proc.wait() 50 status = proc.wait()
91 sys.stdout.write('crash_dump_tester: ' 51 sys.stdout.write('crash_dump_tester: '
92 'crash_service.exe exited with status %s\n' % status) 52 'crash_service.exe exited with status %s\n' % status)
93 # We add a delay because there is probably a race condition: 53 # We add a delay because there is probably a race condition:
94 # crash_service.exe might not have finished doing 54 # crash_service.exe might not have finished doing
95 # CreateNamedPipe() before NaCl does a crash dump and tries to 55 # CreateNamedPipe() before NaCl does a crash dump and tries to
96 # connect to that pipe. 56 # connect to that pipe.
97 # TODO(mseaborn): We could change crash_service.exe to report when 57 # TODO(mseaborn): We could change crash_service.exe to report when
98 # it has successfully created the named pipe. 58 # it has successfully created the named pipe.
99 time.sleep(1) 59 time.sleep(1)
100 else: 60 else:
101 def Cleanup(): 61 def Cleanup():
102 pass 62 pass
103 return Cleanup 63 return Cleanup
104 64
105 65
106 def Main(): 66 def Main():
107 parser = browser_tester.BuildArgParser() 67 parser = browser_tester.BuildArgParser()
108 parser.add_option('--expected_crash_dumps', dest='expected_crash_dumps', 68 parser.add_option('--expected_crash_dumps', dest='expected_crash_dumps',
109 type=int, default=0, 69 type=int, default=0,
110 help='The number of crash dumps that we should expect') 70 help='The number of crash dumps that we should expect')
111 options, args = parser.parse_args() 71 options, args = parser.parse_args()
112 72
73 dumps_dir = tempfile.mkdtemp(prefix='nacl_crash_dump_tester_')
74 # To get a guaranteed unique pipe name, use the base name of the
75 # directory we just created.
76 windows_pipe_name = r'\\.\pipe\%s_crash_service' % os.path.basename(dumps_dir)
77
113 # This environment variable enables Breakpad crash dumping in 78 # This environment variable enables Breakpad crash dumping in
114 # non-official Windows builds of Chromium. 79 # non-official Windows builds of Chromium.
115 os.environ['CHROME_HEADLESS'] = '1' 80 os.environ['CHROME_HEADLESS'] = '1'
81 # Override the default (global) Windows pipe name that Chromium will
82 # use for out-of-process crash reporting.
83 os.environ['CHROME_BREAKPAD_PIPE_NAME'] = windows_pipe_name
Nick Bray 2011/08/12 21:46:27 Hmmm. Everything else being equal, I'd rather see
116 84
117 dumps_before = GetDumpFiles() 85 cleanup_func = StartCrashService(options.browser_path, dumps_dir,
118 PrintDumps('crash dump files before the test', dumps_before) 86 windows_pipe_name)
119
120 cleanup_func = StartCrashService(options.browser_path)
121 try: 87 try:
122 result = browser_tester.Run(options.url, options) 88 result = browser_tester.Run(options.url, options)
123 finally: 89 finally:
124 cleanup_func() 90 cleanup_func()
125 91
126 dumps_after = GetDumpFiles() 92 all_files = [os.path.join(dumps_dir, dump_file)
Nick Bray 2011/08/12 21:46:27 Finding the dump files should be a function taking
127 PrintDumps('crash dump files after the test', dumps_after) 93 for dump_file in os.listdir(dumps_dir)]
128 # Find the new files. This is only necessary because we are not 94 sys.stdout.write('crash_dump_tester: Found %i files\n' % len(all_files))
129 # using a clean temp directory. This is subject to a race condition 95 for dump_file in all_files:
130 # if running crash dump tests concurrently. 96 sys.stdout.write(' %s\n' % dump_file)
131 dumps_diff = sorted(set(dumps_after).difference(dumps_before)) 97 dmp_files = [dump_file for dump_file in all_files
132 PrintDumps('new crash dump files', dumps_diff)
133 new_dumps = [dump_file for dump_file in dumps_diff
134 if dump_file.endswith('.dmp')] 98 if dump_file.endswith('.dmp')]
135 99
136 failed = False 100 failed = False
137 msg = ('crash_dump_tester: ERROR: Got %i crash dumps but expected %i\n' % 101 msg = ('crash_dump_tester: ERROR: Got %i crash dumps but expected %i\n' %
138 (len(new_dumps), options.expected_crash_dumps)) 102 (len(dmp_files), options.expected_crash_dumps))
139 if len(new_dumps) != options.expected_crash_dumps: 103 if len(dmp_files) != options.expected_crash_dumps:
140 sys.stdout.write(msg) 104 sys.stdout.write(msg)
141 failed = True 105 failed = True
142 for dump_file in new_dumps: 106 for dump_file in dmp_files:
143 # The crash dumps should come in pairs of a .dmp and .txt file. 107 # The crash dumps should come in pairs of a .dmp and .txt file.
144 second_file = dump_file[:-4] + '.txt' 108 second_file = dump_file[:-4] + '.txt'
145 msg = ('crash_dump_tester: ERROR: File %r is missing a corresponding ' 109 msg = ('crash_dump_tester: ERROR: File %r is missing a corresponding '
146 '%r file\n' % (dump_file, second_file)) 110 '%r file\n' % (dump_file, second_file))
147 if not os.path.exists(second_file): 111 if not os.path.exists(second_file):
148 sys.stdout.write(msg) 112 sys.stdout.write(msg)
149 failed = True 113 failed = True
150 continue 114 continue
151 # Check that the crash dump comes from the NaCl process. 115 # Check that the crash dump comes from the NaCl process.
152 dump_info = ReadDumpTxtFile(second_file) 116 dump_info = ReadDumpTxtFile(second_file)
153 if 'ptype' in dump_info: 117 if 'ptype' in dump_info:
154 msg = ('crash_dump_tester: ERROR: Unexpected ptype value: %r\n' 118 msg = ('crash_dump_tester: ERROR: Unexpected ptype value: %r\n'
155 % dump_info['ptype']) 119 % dump_info['ptype'])
156 if dump_info['ptype'] != 'nacl-loader': 120 if dump_info['ptype'] != 'nacl-loader':
157 sys.stdout.write(msg) 121 sys.stdout.write(msg)
158 failed = True 122 failed = True
159 else: 123 else:
160 sys.stdout.write('crash_dump_tester: ERROR: Missing ptype field\n') 124 sys.stdout.write('crash_dump_tester: ERROR: Missing ptype field\n')
161 failed = True 125 failed = True
162 # TODO(mseaborn): Ideally we would also check that a backtrace 126 # TODO(mseaborn): Ideally we would also check that a backtrace
163 # containing an expected function name can be extracted from the 127 # containing an expected function name can be extracted from the
164 # crash dump. 128 # crash dump.
165 129
166 if failed: 130 if failed:
167 sys.stdout.write('crash_dump_tester: FAILED\n') 131 sys.stdout.write('crash_dump_tester: FAILED\n')
168 result = 1 132 result = 1
169 else: 133 else:
170 sys.stdout.write('crash_dump_tester: PASSED\n') 134 sys.stdout.write('crash_dump_tester: PASSED\n')
171 # Clean up the dump files only if we are sure we produced them.
172 for dump_file in dumps_diff:
173 try:
174 os.unlink(dump_file)
175 except Exception:
176 # Handle exception in case the file is locked.
177 sys.stdout.write('crash_dump_tester: Deleting %r failed, '
178 'but continuing anyway\n' % dump_file)
179 135
136 shutil.rmtree(dumps_dir)
Nick Bray 2011/08/12 21:46:27 This may occasionally blow up on Windows. You shou
180 return result 137 return result
181 138
182 139
183 if __name__ == '__main__': 140 if __name__ == '__main__':
184 sys.exit(Main()) 141 sys.exit(Main())
OLDNEW
« no previous file with comments | « no previous file | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698