OLD | NEW |
1 #!/usr/bin/python | 1 #!/usr/bin/python |
2 # Copyright (c) 2012 The Chromium Authors. All rights reserved. | 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 | 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 subprocess | 7 import subprocess |
8 import sys | 8 import sys |
9 import tempfile | 9 import tempfile |
10 import time | 10 import time |
(...skipping 19 matching lines...) Expand all Loading... |
30 dump_info = {} | 30 dump_info = {} |
31 fh = open(filename, 'r') | 31 fh = open(filename, 'r') |
32 for line in fh: | 32 for line in fh: |
33 if ':' in line: | 33 if ':' in line: |
34 key, value = line.rstrip().split(':', 1) | 34 key, value = line.rstrip().split(':', 1) |
35 dump_info[key] = value | 35 dump_info[key] = value |
36 fh.close() | 36 fh.close() |
37 return dump_info | 37 return dump_info |
38 | 38 |
39 | 39 |
40 def StartCrashService(browser_path, dumps_dir, windows_pipe_name, win64): | 40 def StartCrashService(browser_path, dumps_dir, windows_pipe_name, |
41 if sys.platform == 'win32': | 41 cleanup_funcs, crash_service_exe): |
42 # 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. |
43 browser_dir = os.path.dirname(browser_path) | 43 browser_dir = os.path.dirname(browser_path) |
44 # Ideally we would just query the OS here to find out whether we | 44 proc = subprocess.Popen([os.path.join(browser_dir, crash_service_exe), |
45 # are running x86-32 or x86-64 Windows, but Python's win32api | 45 '--dumps-dir=%s' % dumps_dir, |
46 # module does not contain a wrapper for GetNativeSystemInfo(), | 46 '--pipe-name=%s' % windows_pipe_name]) |
47 # which is what NaCl uses to check this, or for IsWow64Process(), | 47 |
48 # which is what Chromium uses. Instead, we just rely on the build | 48 def Cleanup(): |
49 # system to tell us. Furthermore, on an x86-64 Windows system we | 49 # Note that if the process has already exited, this will raise |
50 # could launch both versions of crash_service, in order to check | 50 # an 'Access is denied' WindowsError exception, but |
51 # that they do not interfere, but for simplicity we do not. | 51 # crash_service.exe is not supposed to do this and such |
52 if win64: | 52 # behaviour should make the test fail. |
53 executable_name = 'crash_service64.exe' | 53 proc.terminate() |
54 else: | 54 status = proc.wait() |
55 executable_name = 'crash_service.exe' | 55 sys.stdout.write('crash_dump_tester: %s exited with status %s\n' |
56 proc = subprocess.Popen([os.path.join(browser_dir, executable_name), | 56 % (crash_service_exe, status)) |
57 '--dumps-dir=%s' % dumps_dir, | 57 |
58 '--pipe-name=%s' % windows_pipe_name]) | 58 cleanup_funcs.append(Cleanup) |
59 def Cleanup(): | |
60 # Note that if the process has already exited, this will raise | |
61 # an 'Access is denied' WindowsError exception, but | |
62 # crash_service.exe is not supposed to do this and such | |
63 # behaviour should make the test fail. | |
64 proc.terminate() | |
65 status = proc.wait() | |
66 sys.stdout.write('crash_dump_tester: ' | |
67 'crash_service.exe exited with status %s\n' % status) | |
68 # We add a delay because there is probably a race condition: | |
69 # crash_service.exe might not have finished doing | |
70 # CreateNamedPipe() before NaCl does a crash dump and tries to | |
71 # connect to that pipe. | |
72 # TODO(mseaborn): We could change crash_service.exe to report when | |
73 # it has successfully created the named pipe. | |
74 time.sleep(1) | |
75 else: | |
76 def Cleanup(): | |
77 pass | |
78 return Cleanup | |
79 | 59 |
80 | 60 |
81 def GetDumpFiles(dumps_dir): | 61 def GetDumpFiles(dumps_dir): |
82 all_files = [os.path.join(dumps_dir, dump_file) | 62 all_files = [os.path.join(dumps_dir, dump_file) |
83 for dump_file in os.listdir(dumps_dir)] | 63 for dump_file in os.listdir(dumps_dir)] |
84 sys.stdout.write('crash_dump_tester: Found %i files\n' % len(all_files)) | 64 sys.stdout.write('crash_dump_tester: Found %i files\n' % len(all_files)) |
85 for dump_file in all_files: | 65 for dump_file in all_files: |
86 sys.stdout.write(' %s\n' % dump_file) | 66 sys.stdout.write(' %s\n' % dump_file) |
87 return [dump_file for dump_file in all_files | 67 return [dump_file for dump_file in all_files |
88 if dump_file.endswith('.dmp')] | 68 if dump_file.endswith('.dmp')] |
89 | 69 |
90 | 70 |
91 def Main(): | 71 def Main(cleanup_funcs): |
92 parser = browser_tester.BuildArgParser() | 72 parser = browser_tester.BuildArgParser() |
93 parser.add_option('--expected_crash_dumps', dest='expected_crash_dumps', | 73 parser.add_option('--expected_crash_dumps', dest='expected_crash_dumps', |
94 type=int, default=0, | 74 type=int, default=0, |
95 help='The number of crash dumps that we should expect') | 75 help='The number of crash dumps that we should expect') |
| 76 parser.add_option('--expected_process_type_for_crash', |
| 77 dest='expected_process_type_for_crash', |
| 78 type=str, default='nacl-loader', |
| 79 help='The type of Chromium process that we expect the ' |
| 80 'crash dump to be for') |
| 81 # Ideally we would just query the OS here to find out whether we are |
| 82 # running x86-32 or x86-64 Windows, but Python's win32api module |
| 83 # does not contain a wrapper for GetNativeSystemInfo(), which is |
| 84 # what NaCl uses to check this, or for IsWow64Process(), which is |
| 85 # what Chromium uses. Instead, we just rely on the build system to |
| 86 # tell us. |
96 parser.add_option('--win64', dest='win64', action='store_true', | 87 parser.add_option('--win64', dest='win64', action='store_true', |
97 help='Pass this if we are running tests for x86-64 Windows') | 88 help='Pass this if we are running tests for x86-64 Windows') |
98 options, args = parser.parse_args() | 89 options, args = parser.parse_args() |
99 | 90 |
100 dumps_dir = tempfile.mkdtemp(prefix='nacl_crash_dump_tester_') | 91 dumps_dir = tempfile.mkdtemp(prefix='nacl_crash_dump_tester_') |
| 92 def CleanUpDumpsDir(): |
| 93 browsertester.browserlauncher.RemoveDirectory(dumps_dir) |
| 94 cleanup_funcs.append(CleanUpDumpsDir) |
| 95 |
101 # To get a guaranteed unique pipe name, use the base name of the | 96 # To get a guaranteed unique pipe name, use the base name of the |
102 # directory we just created. | 97 # directory we just created. |
103 windows_pipe_name = r'\\.\pipe\%s_crash_service' % os.path.basename(dumps_dir) | 98 windows_pipe_name = r'\\.\pipe\%s_crash_service' % os.path.basename(dumps_dir) |
104 | 99 |
105 # This environment variable enables Breakpad crash dumping in | 100 # This environment variable enables Breakpad crash dumping in |
106 # non-official builds of Chromium. | 101 # non-official builds of Chromium. |
107 os.environ['CHROME_HEADLESS'] = '1' | 102 os.environ['CHROME_HEADLESS'] = '1' |
108 if sys.platform == 'win32': | 103 if sys.platform == 'win32': |
109 # Override the default (global) Windows pipe name that Chromium will | 104 # Override the default (global) Windows pipe name that Chromium will |
110 # use for out-of-process crash reporting. | 105 # use for out-of-process crash reporting. |
111 os.environ['CHROME_BREAKPAD_PIPE_NAME'] = windows_pipe_name | 106 os.environ['CHROME_BREAKPAD_PIPE_NAME'] = windows_pipe_name |
| 107 # Launch the x86-32 crash service so that we can handle crashes in |
| 108 # the browser process. |
| 109 StartCrashService(options.browser_path, dumps_dir, windows_pipe_name, |
| 110 cleanup_funcs, 'crash_service.exe') |
| 111 if options.win64: |
| 112 # Launch the x86-64 crash service so that we can handle crashes |
| 113 # in the NaCl loader process (nacl64.exe). |
| 114 StartCrashService(options.browser_path, dumps_dir, windows_pipe_name, |
| 115 cleanup_funcs, 'crash_service64.exe') |
| 116 # We add a delay because there is probably a race condition: |
| 117 # crash_service.exe might not have finished doing |
| 118 # CreateNamedPipe() before NaCl does a crash dump and tries to |
| 119 # connect to that pipe. |
| 120 # TODO(mseaborn): We could change crash_service.exe to report when |
| 121 # it has successfully created the named pipe. |
| 122 time.sleep(1) |
112 elif sys.platform == 'darwin': | 123 elif sys.platform == 'darwin': |
113 os.environ['BREAKPAD_DUMP_LOCATION'] = dumps_dir | 124 os.environ['BREAKPAD_DUMP_LOCATION'] = dumps_dir |
114 | 125 |
115 cleanup_func = StartCrashService(options.browser_path, dumps_dir, | 126 result = browser_tester.Run(options.url, options) |
116 windows_pipe_name, options.win64) | |
117 try: | |
118 result = browser_tester.Run(options.url, options) | |
119 finally: | |
120 cleanup_func() | |
121 | 127 |
122 dmp_files = GetDumpFiles(dumps_dir) | 128 dmp_files = GetDumpFiles(dumps_dir) |
123 failed = False | 129 failed = False |
124 msg = ('crash_dump_tester: ERROR: Got %i crash dumps but expected %i\n' % | 130 msg = ('crash_dump_tester: ERROR: Got %i crash dumps but expected %i\n' % |
125 (len(dmp_files), options.expected_crash_dumps)) | 131 (len(dmp_files), options.expected_crash_dumps)) |
126 if len(dmp_files) != options.expected_crash_dumps: | 132 if len(dmp_files) != options.expected_crash_dumps: |
127 sys.stdout.write(msg) | 133 sys.stdout.write(msg) |
128 failed = True | 134 failed = True |
129 # On Windows, the crash dumps should come in pairs of a .dmp and | 135 # On Windows, the crash dumps should come in pairs of a .dmp and |
130 # .txt file. | 136 # .txt file. |
131 if sys.platform == 'win32': | 137 if sys.platform == 'win32': |
132 for dump_file in dmp_files: | 138 for dump_file in dmp_files: |
133 second_file = dump_file[:-4] + '.txt' | 139 second_file = dump_file[:-4] + '.txt' |
134 msg = ('crash_dump_tester: ERROR: File %r is missing a corresponding ' | 140 msg = ('crash_dump_tester: ERROR: File %r is missing a corresponding ' |
135 '%r file\n' % (dump_file, second_file)) | 141 '%r file\n' % (dump_file, second_file)) |
136 if not os.path.exists(second_file): | 142 if not os.path.exists(second_file): |
137 sys.stdout.write(msg) | 143 sys.stdout.write(msg) |
138 failed = True | 144 failed = True |
139 continue | 145 continue |
140 # Check that the crash dump comes from the NaCl process. | 146 # Check that the crash dump comes from the NaCl process. |
141 dump_info = ReadDumpTxtFile(second_file) | 147 dump_info = ReadDumpTxtFile(second_file) |
142 if 'ptype' in dump_info: | 148 if 'ptype' in dump_info: |
143 msg = ('crash_dump_tester: ERROR: Unexpected ptype value: %r\n' | 149 msg = ('crash_dump_tester: ERROR: Unexpected ptype value: %r != %r\n' |
144 % dump_info['ptype']) | 150 % (dump_info['ptype'], options.expected_process_type_for_crash)) |
145 if dump_info['ptype'] != 'nacl-loader': | 151 if dump_info['ptype'] != options.expected_process_type_for_crash: |
146 sys.stdout.write(msg) | 152 sys.stdout.write(msg) |
147 failed = True | 153 failed = True |
148 else: | 154 else: |
149 sys.stdout.write('crash_dump_tester: ERROR: Missing ptype field\n') | 155 sys.stdout.write('crash_dump_tester: ERROR: Missing ptype field\n') |
150 failed = True | 156 failed = True |
151 # TODO(mseaborn): Ideally we would also check that a backtrace | 157 # TODO(mseaborn): Ideally we would also check that a backtrace |
152 # containing an expected function name can be extracted from the | 158 # containing an expected function name can be extracted from the |
153 # crash dump. | 159 # crash dump. |
154 | 160 |
155 if failed: | 161 if failed: |
156 sys.stdout.write('crash_dump_tester: FAILED\n') | 162 sys.stdout.write('crash_dump_tester: FAILED\n') |
157 result = 1 | 163 result = 1 |
158 else: | 164 else: |
159 sys.stdout.write('crash_dump_tester: PASSED\n') | 165 sys.stdout.write('crash_dump_tester: PASSED\n') |
160 | 166 |
161 browsertester.browserlauncher.RemoveDirectory(dumps_dir) | |
162 return result | 167 return result |
163 | 168 |
164 | 169 |
| 170 def MainWrapper(): |
| 171 cleanup_funcs = [] |
| 172 try: |
| 173 return Main(cleanup_funcs) |
| 174 finally: |
| 175 for func in cleanup_funcs: |
| 176 func() |
| 177 |
| 178 |
165 if __name__ == '__main__': | 179 if __name__ == '__main__': |
166 sys.exit(Main()) | 180 sys.exit(MainWrapper()) |
OLD | NEW |