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

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: Review 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
17 import browsertester.browserlauncher
15 18
16 # This script extends browser_tester to check for the presence of 19 # This script extends browser_tester to check for the presence of
17 # Breakpad crash dumps. 20 # Breakpad crash dumps.
18 21
19 22
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. 23 # This reads a file of lines containing 'key:value' pairs.
57 # The file contains entries like the following: 24 # The file contains entries like the following:
58 # plat:Win32 25 # plat:Win32
59 # prod:Chromium 26 # prod:Chromium
60 # ptype:nacl-loader 27 # ptype:nacl-loader
61 # rept:crash svc 28 # rept:crash svc
62 def ReadDumpTxtFile(filename): 29 def ReadDumpTxtFile(filename):
63 dump_info = {} 30 dump_info = {}
64 fh = open(filename, 'r') 31 fh = open(filename, 'r')
65 for line in fh: 32 for line in fh:
66 if ':' in line: 33 if ':' in line:
67 key, value = line.rstrip().split(':', 1) 34 key, value = line.rstrip().split(':', 1)
68 dump_info[key] = value 35 dump_info[key] = value
69 fh.close() 36 fh.close()
70 return dump_info 37 return dump_info
71 38
72 39
73 def StartCrashService(browser_path): 40 def StartCrashService(browser_path, dumps_dir, windows_pipe_name):
74 if sys.platform == 'win32': 41 if sys.platform == 'win32':
75 # Find crash_service.exe relative to chrome.exe. This is a bit icky. 42 # Find crash_service.exe relative to chrome.exe. This is a bit icky.
76 browser_dir = os.path.dirname(browser_path) 43 browser_dir = os.path.dirname(browser_path)
77 proc = subprocess.Popen([os.path.join(browser_dir, 'crash_service.exe')]) 44 proc = subprocess.Popen([os.path.join(browser_dir, 'crash_service.exe'),
45 '--dumps-dir=%s' % dumps_dir,
46 '--pipe-name=%s' % windows_pipe_name])
78 def Cleanup(): 47 def Cleanup():
79 try: 48 # Note that if the process has already exited, this will raise
80 proc.terminate() 49 # an 'Access is denied' WindowsError exception, but
81 sys.stdout.write('crash_dump_tester: Stopped crash_service.exe\n') 50 # crash_service.exe is not supposed to do this and such
82 except WindowsError: 51 # behaviour should make the test fail.
83 # If the process has already exited, we will get an 'Access is 52 proc.terminate()
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() 53 status = proc.wait()
91 sys.stdout.write('crash_dump_tester: ' 54 sys.stdout.write('crash_dump_tester: '
92 'crash_service.exe exited with status %s\n' % status) 55 'crash_service.exe exited with status %s\n' % status)
93 # We add a delay because there is probably a race condition: 56 # We add a delay because there is probably a race condition:
94 # crash_service.exe might not have finished doing 57 # crash_service.exe might not have finished doing
95 # CreateNamedPipe() before NaCl does a crash dump and tries to 58 # CreateNamedPipe() before NaCl does a crash dump and tries to
96 # connect to that pipe. 59 # connect to that pipe.
97 # TODO(mseaborn): We could change crash_service.exe to report when 60 # TODO(mseaborn): We could change crash_service.exe to report when
98 # it has successfully created the named pipe. 61 # it has successfully created the named pipe.
99 time.sleep(1) 62 time.sleep(1)
100 else: 63 else:
101 def Cleanup(): 64 def Cleanup():
102 pass 65 pass
103 return Cleanup 66 return Cleanup
104 67
105 68
69 def GetDumpFiles(dumps_dir):
70 all_files = [os.path.join(dumps_dir, dump_file)
71 for dump_file in os.listdir(dumps_dir)]
72 sys.stdout.write('crash_dump_tester: Found %i files\n' % len(all_files))
73 for dump_file in all_files:
74 sys.stdout.write(' %s\n' % dump_file)
75 return [dump_file for dump_file in all_files
76 if dump_file.endswith('.dmp')]
77
78
106 def Main(): 79 def Main():
107 parser = browser_tester.BuildArgParser() 80 parser = browser_tester.BuildArgParser()
108 parser.add_option('--expected_crash_dumps', dest='expected_crash_dumps', 81 parser.add_option('--expected_crash_dumps', dest='expected_crash_dumps',
109 type=int, default=0, 82 type=int, default=0,
110 help='The number of crash dumps that we should expect') 83 help='The number of crash dumps that we should expect')
111 options, args = parser.parse_args() 84 options, args = parser.parse_args()
112 85
86 dumps_dir = tempfile.mkdtemp(prefix='nacl_crash_dump_tester_')
87 # To get a guaranteed unique pipe name, use the base name of the
88 # directory we just created.
89 windows_pipe_name = r'\\.\pipe\%s_crash_service' % os.path.basename(dumps_dir)
90
113 # This environment variable enables Breakpad crash dumping in 91 # This environment variable enables Breakpad crash dumping in
114 # non-official Windows builds of Chromium. 92 # non-official Windows builds of Chromium.
115 os.environ['CHROME_HEADLESS'] = '1' 93 os.environ['CHROME_HEADLESS'] = '1'
94 # Override the default (global) Windows pipe name that Chromium will
95 # use for out-of-process crash reporting.
96 os.environ['CHROME_BREAKPAD_PIPE_NAME'] = windows_pipe_name
116 97
117 dumps_before = GetDumpFiles() 98 cleanup_func = StartCrashService(options.browser_path, dumps_dir,
118 PrintDumps('crash dump files before the test', dumps_before) 99 windows_pipe_name)
119
120 cleanup_func = StartCrashService(options.browser_path)
121 try: 100 try:
122 result = browser_tester.Run(options.url, options) 101 result = browser_tester.Run(options.url, options)
123 finally: 102 finally:
124 cleanup_func() 103 cleanup_func()
125 104
126 dumps_after = GetDumpFiles() 105 dmp_files = GetDumpFiles(dumps_dir)
127 PrintDumps('crash dump files after the test', dumps_after)
128 # Find the new files. This is only necessary because we are not
129 # using a clean temp directory. This is subject to a race condition
130 # if running crash dump tests concurrently.
131 dumps_diff = sorted(set(dumps_after).difference(dumps_before))
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')]
135
136 failed = False 106 failed = False
137 msg = ('crash_dump_tester: ERROR: Got %i crash dumps but expected %i\n' % 107 msg = ('crash_dump_tester: ERROR: Got %i crash dumps but expected %i\n' %
138 (len(new_dumps), options.expected_crash_dumps)) 108 (len(dmp_files), options.expected_crash_dumps))
139 if len(new_dumps) != options.expected_crash_dumps: 109 if len(dmp_files) != options.expected_crash_dumps:
140 sys.stdout.write(msg) 110 sys.stdout.write(msg)
141 failed = True 111 failed = True
142 for dump_file in new_dumps: 112 for dump_file in dmp_files:
143 # The crash dumps should come in pairs of a .dmp and .txt file. 113 # The crash dumps should come in pairs of a .dmp and .txt file.
144 second_file = dump_file[:-4] + '.txt' 114 second_file = dump_file[:-4] + '.txt'
145 msg = ('crash_dump_tester: ERROR: File %r is missing a corresponding ' 115 msg = ('crash_dump_tester: ERROR: File %r is missing a corresponding '
146 '%r file\n' % (dump_file, second_file)) 116 '%r file\n' % (dump_file, second_file))
147 if not os.path.exists(second_file): 117 if not os.path.exists(second_file):
148 sys.stdout.write(msg) 118 sys.stdout.write(msg)
149 failed = True 119 failed = True
150 continue 120 continue
151 # Check that the crash dump comes from the NaCl process. 121 # Check that the crash dump comes from the NaCl process.
152 dump_info = ReadDumpTxtFile(second_file) 122 dump_info = ReadDumpTxtFile(second_file)
153 if 'ptype' in dump_info: 123 if 'ptype' in dump_info:
154 msg = ('crash_dump_tester: ERROR: Unexpected ptype value: %r\n' 124 msg = ('crash_dump_tester: ERROR: Unexpected ptype value: %r\n'
155 % dump_info['ptype']) 125 % dump_info['ptype'])
156 if dump_info['ptype'] != 'nacl-loader': 126 if dump_info['ptype'] != 'nacl-loader':
157 sys.stdout.write(msg) 127 sys.stdout.write(msg)
158 failed = True 128 failed = True
159 else: 129 else:
160 sys.stdout.write('crash_dump_tester: ERROR: Missing ptype field\n') 130 sys.stdout.write('crash_dump_tester: ERROR: Missing ptype field\n')
161 failed = True 131 failed = True
162 # TODO(mseaborn): Ideally we would also check that a backtrace 132 # TODO(mseaborn): Ideally we would also check that a backtrace
163 # containing an expected function name can be extracted from the 133 # containing an expected function name can be extracted from the
164 # crash dump. 134 # crash dump.
165 135
166 if failed: 136 if failed:
167 sys.stdout.write('crash_dump_tester: FAILED\n') 137 sys.stdout.write('crash_dump_tester: FAILED\n')
168 result = 1 138 result = 1
169 else: 139 else:
170 sys.stdout.write('crash_dump_tester: PASSED\n') 140 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 141
142 browsertester.browserlauncher.RemoveDirectory(dumps_dir)
180 return result 143 return result
181 144
182 145
183 if __name__ == '__main__': 146 if __name__ == '__main__':
184 sys.exit(Main()) 147 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