Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
| 2 # Copyright 2015 The Chromium Authors. All rights reserved. | 2 # Copyright 2015 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 """A Windows-only end-to-end integration test for Kasko, Chrome and Crashpad. | 6 """A Windows-only end-to-end integration test for the Kasko hang watcher. |
| 7 | 7 |
| 8 This test ensures that the interface between Kasko and Chrome and Crashpad works | 8 This test ensures that Kasko is able to detect when Chrome is hanged and to |
| 9 as expected. The test causes Kasko to set certain crash keys and invoke a crash | 9 generate a report. The report is then delivered to a locally hosted test crash |
| 10 report, which is in turn delivered to a locally hosted test crash server. If the | 10 server. If a crash report is received then all is well. |
| 11 crash report is received intact with the expected crash keys then all is well. | |
| 12 | 11 |
| 13 Note that this test only works against non-component Release and Official builds | 12 Note that this test only works against non-component Release and Official builds |
| 14 of Chrome with Chrome branding, and attempting to use it with anything else will | 13 of Chrome with Chrome branding, and attempting to use it with anything else will |
| 15 most likely lead to constant failures. | 14 most likely lead to constant failures. |
| 16 | 15 |
| 17 Typical usage (assuming in root 'src' directory): | 16 Typical usage (assuming in root 'src' directory): |
| 18 | 17 |
| 19 - generate project files with the following GYP variables: | 18 - generate project files with the following GYP variables: |
| 20 branding=Chrome syzyasan=1 win_z7=0 chromium_win_pch=0 | 19 branding=Chrome kasko=1 kasko_hang_reports=1 |
| 21 - build the release Chrome binaries: | 20 - build the release Chrome binaries and driver: |
| 22 ninja -C out\Release chrome.exe | 21 ninja -C out\Release chrome.exe |
| 22 ninja -C out\Release chromedriver.exe | |
| 23 - run the test: | 23 - run the test: |
| 24 python chrome/test/kasko/kasko_integration_test.py --chrome-dir=out/Release | 24 python chrome/test/kasko/kasko_integration_test.py |
| 25 | 25 |
| 26 Many of the components in this test could be reused in other end-to-end crash | 26 Many of the components in this test could be reused in other end-to-end crash |
| 27 testing. Feel free to open them up for reuse, but please CC chrisha@chromium.org | 27 testing. Feel free to open them up for reuse, but please CC chrisha@chromium.org |
| 28 on any associated reviews or bugs! | 28 on any associated reviews or bugs! |
| 29 """ | 29 """ |
| 30 | 30 |
| 31 import BaseHTTPServer | 31 import BaseHTTPServer |
| 32 import cgi | 32 import cgi |
| 33 import logging | 33 import logging |
| 34 import os | 34 import os |
| (...skipping 308 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 343 | 343 |
| 344 return t | 344 return t |
| 345 | 345 |
| 346 | 346 |
| 347 def _GetProcessCreationDate(pid): | 347 def _GetProcessCreationDate(pid): |
| 348 """Returns the process creation date as local unix epoch time.""" | 348 """Returns the process creation date as local unix epoch time.""" |
| 349 wmi = win32com.client.GetObject('winmgmts:') | 349 wmi = win32com.client.GetObject('winmgmts:') |
| 350 procs = wmi.ExecQuery( | 350 procs = wmi.ExecQuery( |
| 351 'select CreationDate from Win32_Process where ProcessId = %s' % pid) | 351 'select CreationDate from Win32_Process where ProcessId = %s' % pid) |
| 352 for proc in procs: | 352 for proc in procs: |
| 353 return _WmiTimeToLocalEpoch(proc.Properties_['CreationDate'].Value) | 353 print _WmiTimeToLocalEpoch(proc.Properties_('CreationDate').Value) |
| 354 return _WmiTimeToLocalEpoch(proc.Properties_('CreationDate').Value) | |
| 354 raise Exception('Unable to find process with PID %d.' % pid) | 355 raise Exception('Unable to find process with PID %d.' % pid) |
| 355 | 356 |
| 356 | 357 |
| 357 def _ShutdownChildren(parent_pid, child_exe, started_after, started_before, | 358 def _ShutdownChildren(parent_pid, child_exe, started_after, started_before, |
| 358 timeout=_DEFAULT_TIMEOUT, force=False): | 359 timeout=_DEFAULT_TIMEOUT, force=False): |
| 359 """Shuts down any lingering child processes of a given parent. | 360 """Shuts down any lingering child processes of a given parent. |
| 360 | 361 |
| 361 This is an inherently racy thing to do as process IDs are aggressively reused | 362 This is an inherently racy thing to do as process IDs are aggressively reused |
| 362 on Windows. Filtering by a valid known |started_after| and |started_before| | 363 on Windows. Filtering by a valid known |started_after| and |started_before| |
| 363 timestamp, as well as by the executable of the child process resolves this | 364 timestamp, as well as by the executable of the child process resolves this |
| 364 issue. Ugh. | 365 issue. Ugh. |
| 365 """ | 366 """ |
| 366 started = time.time() | 367 started = time.time() |
| 367 wmi = win32com.client.GetObject('winmgmts:') | 368 wmi = win32com.client.GetObject('winmgmts:') |
| 368 _LOGGER.debug('Shutting down lingering children processes.') | 369 _LOGGER.debug('Shutting down lingering children processes.') |
| 369 for proc in wmi.InstancesOf('Win32_Process'): | 370 for proc in wmi.InstancesOf('Win32_Process'): |
| 370 if proc.Properties_['ParentProcessId'].Value != parent_pid: | 371 if proc.Properties_('ParentProcessId').Value != parent_pid: |
|
Patrick Monette
2015/12/23 17:24:46
chrisha: Does this work on your PC?
| |
| 371 continue | 372 continue |
| 372 if proc.Properties_['ExecutablePath'].Value != child_exe: | 373 if proc.Properties_('ExecutablePath').Value != child_exe: |
| 373 continue | 374 continue |
| 374 t = _WmiTimeToLocalEpoch(proc.Properties_['CreationDate'].Value) | 375 t = _WmiTimeToLocalEpoch(proc.Properties_('CreationDate').Value) |
| 375 if t <= started_after or t >= started_before: | 376 if t <= started_after or t >= started_before: |
| 376 continue | 377 continue |
| 377 pid = proc.Properties_['ProcessId'].Value | 378 pid = proc.Properties_('ProcessId').Value |
| 378 remaining = max(0, started + timeout - time.time()) | 379 remaining = max(0, started + timeout - time.time()) |
| 379 _ShutdownProcess(pid, remaining, force=force) | 380 _ShutdownProcess(pid, remaining, force=force) |
| 380 | 381 |
| 381 | 382 |
| 382 class _ChromeInstance(object): | 383 class _ChromeInstance(object): |
| 383 """A class encapsulating a running instance of Chrome for testing.""" | 384 """A class encapsulating a running instance of Chrome for testing.""" |
| 384 | 385 |
| 385 def __init__(self, chromedriver, chrome, user_data_dir): | 386 def __init__(self, chromedriver, chrome, user_data_dir): |
| 386 self.chromedriver_ = os.path.abspath(chromedriver) | 387 self.chromedriver_ = os.path.abspath(chromedriver) |
| 387 self.chrome_ = os.path.abspath(chrome) | 388 self.chrome_ = os.path.abspath(chrome) |
| (...skipping 136 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 524 | 525 |
| 525 # Launch the test server. | 526 # Launch the test server. |
| 526 server = _CrashServer() | 527 server = _CrashServer() |
| 527 with _ScopedStartStop(server): | 528 with _ScopedStartStop(server): |
| 528 _LOGGER.info('Started server on port %d', server.port) | 529 _LOGGER.info('Started server on port %d', server.port) |
| 529 | 530 |
| 530 # Configure the environment so Chrome can find the test crash server. | 531 # Configure the environment so Chrome can find the test crash server. |
| 531 os.environ['KASKO_CRASH_SERVER_URL'] = ( | 532 os.environ['KASKO_CRASH_SERVER_URL'] = ( |
| 532 'http://127.0.0.1:%d/crash' % server.port) | 533 'http://127.0.0.1:%d/crash' % server.port) |
| 533 | 534 |
| 534 # Launch Chrome and navigate it to the test URL. | 535 # Launch Chrome and navigate to the hang URL. |
| 535 chrome = _ChromeInstance(options.chromedriver, options.chrome, | 536 chrome = _ChromeInstance(options.chromedriver, options.chrome, |
| 536 user_data_dir) | 537 user_data_dir) |
| 537 with _ScopedStartStop(chrome): | 538 with _ScopedStartStop(chrome): |
| 538 _LOGGER.info('Navigating to Kasko debug URL') | 539 _LOGGER.info('Navigating to the hang debug url') |
| 539 chrome.navigate_to('chrome://kasko/send-report') | 540 chrome.navigate_to('chrome://uithreadhang') |
| 540 | 541 |
| 541 _LOGGER.info('Waiting for Kasko report') | 542 _LOGGER.info('Waiting for Kasko report') |
| 542 if not server.wait_for_report(10): | |
| 543 raise Exception('No Kasko report received.') | |
| 544 | 543 |
| 545 report = server.crash(0) | 544 # The report can take as long as 80 seconds to be generated because |
| 546 for (key, value) in report.iteritems(): | 545 # the polling is done every 60 seconds and the watcher gives 20 |
| 547 val = value[0] | 546 # seconds to Chrome to ping back before being considered hanged. |
| 548 if (len(val) < 64): | 547 if not server.wait_for_report(120): |
| 549 _LOGGER.debug('Got crashkey "%s": "%s"', key, val) | 548 raise Exception('No Kasko report received') |
| 550 else: | 549 |
| 551 _LOGGER.debug('Got crashkey "%s": ...%d bytes...', key, len(val)) | 550 _LOGGER.info('Received Kasko report successfully') |
| 552 kasko_key = 'kasko-set-crash-key-value-impl' | 551 # TODO(pmonette): Verify that the report contains the crash keys that |
| 553 if kasko_key not in report: | 552 # are expected when they will work. |
| 554 _LOGGER.error('Missing expected "%s" crash key.', kasko_key) | |
| 555 raise Exception('SendCrashKeyValueImpl integration appears broken.') | |
| 556 | 553 |
| 557 return 0 | 554 return 0 |
| 558 | 555 |
| 559 | 556 |
| 560 if __name__ == '__main__': | 557 if __name__ == '__main__': |
| 561 sys.exit(Main()) | 558 sys.exit(Main()) |
| OLD | NEW |