File: crash_dump_tester.py

package info (click to toggle)
chromium 139.0.7258.127-2
  • links: PTS, VCS
  • area: main
  • in suites: forky
  • size: 6,122,156 kB
  • sloc: cpp: 35,100,771; ansic: 7,163,530; javascript: 4,103,002; python: 1,436,920; asm: 946,517; xml: 746,709; pascal: 187,653; perl: 88,691; sh: 88,436; objc: 79,953; sql: 51,488; cs: 44,583; fortran: 24,137; makefile: 22,147; tcl: 15,277; php: 13,980; yacc: 8,984; ruby: 7,485; awk: 3,720; lisp: 3,096; lex: 1,327; ada: 727; jsp: 228; sed: 36
file content (225 lines) | stat: -rwxr-xr-x 8,523 bytes parent folder | download | duplicates (7)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
#!/usr/bin/env python
# Copyright 2012 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

import os
import subprocess
import sys
import tempfile
import time

script_dir = os.path.dirname(__file__)
sys.path.append(os.path.join(script_dir,
                             '../../tools/browser_tester'))

import browser_tester
import browsertester.browserlauncher

# This script extends browser_tester to check for the presence of
# Breakpad crash dumps.


# This reads a file of lines containing 'key:value' pairs.
# The file contains entries like the following:
#   plat:Win32
#   prod:Chromium
#   ptype:nacl-loader
#   rept:crash svc
def ReadDumpTxtFile(filename):
  dump_info = {}
  fh = open(filename, 'r')
  for line in fh:
    if ':' in line:
      key, value = line.rstrip().split(':', 1)
      dump_info[key] = value
  fh.close()
  return dump_info


def StartCrashService(browser_path, dumps_dir, windows_pipe_name,
                      cleanup_funcs, crash_service_exe,
                      skip_if_missing=False):
  # Find crash_service.exe relative to chrome.exe.  This is a bit icky.
  browser_dir = os.path.dirname(browser_path)
  crash_service_path = os.path.join(browser_dir, crash_service_exe)
  if skip_if_missing and not os.path.exists(crash_service_path):
    return
  proc = subprocess.Popen([crash_service_path,
                           '--v=1',  # Verbose output for debugging failures
                           '--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 ListPathsInDir(dir_path):
  if os.path.exists(dir_path):
    return [os.path.join(dir_path, name)
            for name in os.listdir(dir_path)]
  else:
    return []


def GetDumpFiles(dumps_dirs):
  all_files = [filename
               for dumps_dir in dumps_dirs
               for filename in ListPathsInDir(dumps_dir)]
  sys.stdout.write('crash_dump_tester: Found %i files\n' % len(all_files))
  for dump_file in all_files:
    sys.stdout.write('  %s (size %i)\n'
                     % (dump_file, os.stat(dump_file).st_size))
  return [dump_file for dump_file in all_files
          if dump_file.endswith('.dmp')]


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()

  temp_dir = tempfile.mkdtemp(prefix='nacl_crash_dump_tester_')
  def CleanUpTempDir():
    browsertester.browserlauncher.RemoveDirectory(temp_dir)
  cleanup_funcs.append(CleanUpTempDir)

  # 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(temp_dir)

  # This environment variable enables Breakpad crash dumping in
  # non-official builds of Chromium.
  os.environ['CHROME_HEADLESS'] = '1'
  if sys.platform == 'win32':
    dumps_dir = temp_dir
    # 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).
      # Skip if missing, since in win64 builds crash_service.exe is 64-bit
      # and crash_service64.exe does not exist.
      StartCrashService(options.browser_path, dumps_dir, windows_pipe_name,
                        cleanup_funcs, 'crash_service64.exe',
                        skip_if_missing=True)
    # 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':
    dumps_dir = temp_dir
    os.environ['BREAKPAD_DUMP_LOCATION'] = dumps_dir
  elif sys.platform.startswith('linux'):
    # The "--user-data-dir" option is not effective for the Breakpad
    # setup in Linux Chromium, because Breakpad is initialized before
    # "--user-data-dir" is read.  So we set HOME to redirect the crash
    # dumps to a temporary directory.
    home_dir = temp_dir
    os.environ['HOME'] = home_dir
    options.enable_crash_reporter = True

  result = browser_tester.Run(options.url, options)

  # Find crash dump results.
  if sys.platform.startswith('linux'):
    # Look in "~/.config/*/Crash Reports".  This will find crash
    # reports under ~/.config/chromium or ~/.config/google-chrome, or
    # under other subdirectories in case the branding is changed.
    dumps_dirs = [os.path.join(path, 'Crash Reports')
                  for path in ListPathsInDir(os.path.join(home_dir, '.config'))]
  else:
    dumps_dirs = [dumps_dir]
  dmp_files = GetDumpFiles(dumps_dirs)

  failed = False
  msg = ('crash_dump_tester: ERROR: Got %i crash dumps but expected %i\n' %
         (len(dmp_files), options.expected_crash_dumps))
  if len(dmp_files) != options.expected_crash_dumps:
    sys.stdout.write(msg)
    failed = True

  for dump_file in dmp_files:
    # Sanity check: Make sure dumping did not fail after opening the file.
    msg = 'crash_dump_tester: ERROR: Dump file is empty\n'
    if os.stat(dump_file).st_size == 0:
      sys.stdout.write(msg)
      failed = True

    # On Windows, the crash dumps should come in pairs of a .dmp and
    # .txt file.
    if sys.platform == 'win32':
      second_file = dump_file[:-4] + '.txt'
      msg = ('crash_dump_tester: ERROR: File %r is missing a corresponding '
             '%r file\n' % (dump_file, second_file))
      if not os.path.exists(second_file):
        sys.stdout.write(msg)
        failed = True
        continue
      # 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 != %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:
        sys.stdout.write('crash_dump_tester: ERROR: Missing ptype field\n')
        failed = True
    # TODO(mseaborn): Ideally we would also check that a backtrace
    # containing an expected function name can be extracted from the
    # crash dump.

  if failed:
    sys.stdout.write('crash_dump_tester: FAILED\n')
    result = 1
  else:
    sys.stdout.write('crash_dump_tester: PASSED\n')

  return result


def MainWrapper():
  cleanup_funcs = []
  try:
    return Main(cleanup_funcs)
  finally:
    for func in cleanup_funcs:
      func()


if __name__ == '__main__':
  sys.exit(MainWrapper())