Index: snapshot/win/end_to_end_test.py |
diff --git a/snapshot/win/end_to_end_test.py b/snapshot/win/end_to_end_test.py |
index 0a13befd1b76fddc42bfe96c1dfc1a7512490138..64de3b21078edcf07735d22fd0fccf8f6887c679 100644 |
--- a/snapshot/win/end_to_end_test.py |
+++ b/snapshot/win/end_to_end_test.py |
@@ -15,7 +15,26 @@ |
# limitations under the License. |
import os |
+import random |
+import re |
+import subprocess |
import sys |
+import tempfile |
+ |
+g_temp_dirs = [] |
+ |
+ |
+def MakeTempDir(): |
+ global g_temp_dirs |
+ new_dir = tempfile.mkdtemp() |
+ g_temp_dirs.append(new_dir) |
+ return new_dir |
+ |
+ |
+def CleanUpTempDirs(): |
+ global g_temp_dirs |
+ for d in g_temp_dirs: |
+ subprocess.call(['rmdir', '/s', '/q', d], shell=True) |
def FindInstalledWindowsApplication(app_path): |
@@ -35,6 +54,9 @@ def FindInstalledWindowsApplication(app_path): |
def GetCdbPath(): |
+ """Search in some reasonable places to find cdb.exe. Searches x64 before x86 |
+ and newer versions before older versions. |
+ """ |
possible_paths = ( |
os.path.join('Windows Kits', '10', 'Debuggers', 'x64'), |
os.path.join('Windows Kits', '10', 'Debuggers', 'x86'), |
@@ -44,12 +66,7 @@ def GetCdbPath(): |
os.path.join('Windows Kits', '8.0', 'Debuggers', 'x86'), |
'Debugging Tools For Windows (x64)', |
'Debugging Tools For Windows (x86)', |
- 'Debugging Tools For Windows', |
- os.path.join('win_toolchain', 'vs2013_files', 'win8sdk', 'Debuggers', |
- 'x64'), |
- os.path.join('win_toolchain', 'vs2013_files', 'win8sdk', 'Debuggers', |
- 'x86'), |
- ) |
+ 'Debugging Tools For Windows',) |
for possible_path in possible_paths: |
app_path = os.path.join(possible_path, 'cdb.exe') |
app_path = FindInstalledWindowsApplication(app_path) |
@@ -58,10 +75,137 @@ def GetCdbPath(): |
return None |
+def GetDumpFromCrashyProgram(out_dir, pipe_name): |
+ """Initialize a crash database, run crashpad_handler, run crashy_program |
+ connecting to the crash_handler. Returns the minidump generated by |
+ crash_handler for further testing. |
+ """ |
+ test_database = MakeTempDir() |
+ |
+ try: |
+ if subprocess.call( |
+ [os.path.join(out_dir, 'crashpad_database_util.exe'), '--create', |
+ '--database=' + test_database]) != 0: |
+ print 'could not initialize report database' |
+ return None |
+ |
+ handler = subprocess.Popen([ |
+ os.path.join(out_dir, 'crashpad_handler.exe'), |
+ '--pipe-name=' + pipe_name, |
+ '--database=' + test_database |
+ ]) |
+ |
+ subprocess.call([os.path.join(out_dir, 'crashy_program.exe'), pipe_name]) |
+ |
+ out = subprocess.check_output([ |
+ os.path.join(out_dir, 'crashpad_database_util.exe'), |
+ '--database=' + test_database, |
+ '--show-completed-reports', |
+ '--show-all-report-info', |
+ ]) |
+ for line in out.splitlines(): |
+ if line.strip().startswith('Path:'): |
+ return line.partition(':')[2].strip() |
+ |
+ finally: |
+ if handler: |
+ handler.kill() |
+ |
+ |
+class CdbRun(object): |
+ """Run cdb.exe passing it a cdb command and capturing the output. |
+ `Check()` searches for regex patterns in sequence allowing verification of |
+ expected output. |
+ """ |
+ |
+ def __init__(self, cdb_path, dump_path, command): |
+ # Run a command line that loads the dump, runs the specified cdb command, |
+ # and then quits, and capturing stdout. |
+ self.out = subprocess.check_output([ |
+ cdb_path, |
+ '-z', dump_path, |
+ '-c', command + ';q' |
+ ]) |
+ |
+ def Check(self, pattern, message): |
+ match_obj = re.search(pattern, self.out) |
+ if match_obj: |
+ # Matched. Consume up to end of match. |
+ self.out = self.out[match_obj.end(0):] |
+ print 'ok - %s' % message |
+ else: |
+ print >>sys.stderr, '-' * 80 |
+ print >>sys.stderr, 'FAILED - %s' % message |
+ print >>sys.stderr, '-' * 80 |
+ print >>sys.stderr, 'did not match:\n %s' % pattern |
+ print >>sys.stderr, '-' * 80 |
+ print >>sys.stderr, 'remaining output was:\n %s' % self.out |
+ print >>sys.stderr, '-' * 80 |
+ sys.exit(1) |
+ |
+ |
+def RunTests(cdb_path, dump_path, pipe_name): |
+ """Runs various tests in sequence. Runs a new cdb instance on the dump for |
+ each block of tests to reduce the chances that output from one command is |
+ confused for output from another. |
+ """ |
+ out = CdbRun(cdb_path, dump_path, '.ecxr') |
+ out.Check('This dump file has an exception of interest stored in it', |
+ 'captured exception') |
+ out.Check( |
+ 'crashy_program!crashpad::`anonymous namespace\'::SomeCrashyFunction', |
+ 'exception at correct location') |
+ |
+ out = CdbRun(cdb_path, dump_path, '!peb') |
+ out.Check(r'PEB at', 'found the PEB') |
+ out.Check(r'Ldr\.InMemoryOrderModuleList:.*\d+ \. \d+', 'PEB_LDR_DATA saved') |
+ out.Check(r'Base TimeStamp Module', 'module list present') |
+ pipe_name_escaped = pipe_name.replace('\\', '\\\\') |
+ out.Check(r'CommandLine: *\'.*crashy_program.exe *' + pipe_name_escaped, |
+ 'some PEB data is correct') |
+ out.Check(r'SystemRoot=C:\\Windows', 'some of environment captured') |
+ |
+ out = CdbRun(cdb_path, dump_path, '!teb') |
+ out.Check(r'TEB at', 'found the TEB') |
+ out.Check(r'ExceptionList:\s+[0-9a-fA-F]+', 'some valid teb data') |
+ out.Check(r'LastErrorValue:\s+2', 'correct LastErrorValue') |
+ |
+ out = CdbRun(cdb_path, dump_path, '!gle') |
+ out.Check('LastErrorValue: \(Win32\) 0x2 \(2\) - The system cannot find the ' |
+ 'file specified.', '!gle gets last error') |
+ out.Check('LastStatusValue: \(NTSTATUS\) 0xc000000f - {File Not Found} The ' |
+ 'file %hs does not exist.', '!gle gets last ntstatus') |
+ |
+ # Locks. |
+ out = CdbRun(cdb_path, dump_path, '!locks') |
+ out.Check(r'CritSec crashy_program!crashpad::`anonymous namespace\'::' |
+ r'g_test_critical_section', 'lock was captured') |
+ out.Check(r'\*\*\* Locked', 'lock debug info was captured, and is locked') |
+ |
+ |
def main(args): |
- cdb_path = GetCdbPath() |
- print 'cdb_path:', cdb_path |
- return 0 |
+ try: |
+ if len(args) != 1: |
+ print >>sys.stderr, 'must supply out dir' |
+ return 1 |
+ |
+ cdb_path = GetCdbPath() |
+ if not cdb_path: |
+ print >>sys.stderr, 'could not find cdb' |
+ return 1 |
+ |
+ pipe_name = r'\\.\pipe\end-to-end_%s_%s' % ( |
+ os.getpid(), str(random.getrandbits(64))) |
+ |
+ dump_path = GetDumpFromCrashyProgram(args[0], pipe_name) |
+ if not dump_path: |
+ return 1 |
+ |
+ RunTests(cdb_path, dump_path, pipe_name) |
+ |
+ return 0 |
+ finally: |
+ CleanUpTempDirs() |
if __name__ == '__main__': |