diff options
author | maruel@chromium.org <maruel@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-10-27 18:03:47 +0000 |
---|---|---|
committer | maruel@chromium.org <maruel@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-10-27 18:03:47 +0000 |
commit | d6fc9fd286eb7a3a6cad937da3b99cf8f4acd5c2 (patch) | |
tree | ff8de9ff438626c6d26a9df0acb94fadfd640431 | |
parent | 30be1ce62471386dbdebf8d8f4f87e31a772661c (diff) | |
download | chromium_src-d6fc9fd286eb7a3a6cad937da3b99cf8f4acd5c2.zip chromium_src-d6fc9fd286eb7a3a6cad937da3b99cf8f4acd5c2.tar.gz chromium_src-d6fc9fd286eb7a3a6cad937da3b99cf8f4acd5c2.tar.bz2 |
Move console stack dumping code to a function so it can be reused in test_shell_tests.
TEST=none
BUG=13770
Review URL: http://codereview.chromium.org/339024
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@30220 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r-- | base/process_util.h | 9 | ||||
-rw-r--r-- | base/process_util_posix.cc | 144 | ||||
-rw-r--r-- | base/process_util_win.cc | 64 | ||||
-rw-r--r-- | base/test/test_suite.h | 56 | ||||
-rw-r--r-- | webkit/tools/test_shell/run_all_tests.cc | 3 | ||||
-rw-r--r-- | webkit/tools/test_shell/test_shell.gyp | 5 | ||||
-rw-r--r-- | webkit/tools/test_shell/test_shell_main.cc | 7 | ||||
-rw-r--r-- | webkit/tools/test_shell/test_shell_test.h | 2 |
8 files changed, 170 insertions, 120 deletions
diff --git a/base/process_util.h b/base/process_util.h index caf39e8..cc91c67 100644 --- a/base/process_util.h +++ b/base/process_util.h @@ -420,10 +420,17 @@ class ProcessMetrics { // Note: Returns true on Windows 2000 without doing anything. bool EnableLowFragmentationHeap(); -// Enable 'terminate on heap corruption' flag. Helps protect against heap +// Enables 'terminate on heap corruption' flag. Helps protect against heap // overflow. Has no effect if the OS doesn't provide the necessary facility. void EnableTerminationOnHeapCorruption(); +#if defined(UNIT_TEST) +// Enables stack dump to console output on exception and signals. +// When enabled, the process will quit immediately. This is meant to be used in +// unit_tests only! +bool EnableInProcessStackDumping(); +#endif // defined(UNIT_TEST) + // If supported on the platform, and the user has sufficent rights, increase // the current process's scheduling priority to a high priority. void RaiseProcessToHighPriority(); diff --git a/base/process_util_posix.cc b/base/process_util_posix.cc index d365d01..d5eb4ee 100644 --- a/base/process_util_posix.cc +++ b/base/process_util_posix.cc @@ -16,7 +16,7 @@ #include <limits> #include <set> -#include "base/basictypes.h" +#include "base/debug_util.h" #include "base/eintr_wrapper.h" #include "base/logging.h" #include "base/platform_thread.h" @@ -30,6 +30,68 @@ const int kMicrosecondsPerSecond = 1000000; namespace base { +namespace { + +int WaitpidWithTimeout(ProcessHandle handle, int64 wait_milliseconds, + bool* success) { + // This POSIX version of this function only guarantees that we wait no less + // than |wait_milliseconds| for the proces to exit. The child process may + // exit sometime before the timeout has ended but we may still block for + // up to 0.25 seconds after the fact. + // + // waitpid() has no direct support on POSIX for specifying a timeout, you can + // either ask it to block indefinitely or return immediately (WNOHANG). + // When a child process terminates a SIGCHLD signal is sent to the parent. + // Catching this signal would involve installing a signal handler which may + // affect other parts of the application and would be difficult to debug. + // + // Our strategy is to call waitpid() once up front to check if the process + // has already exited, otherwise to loop for wait_milliseconds, sleeping for + // at most 0.25 secs each time using usleep() and then calling waitpid(). + // + // usleep() is speced to exit if a signal is received for which a handler + // has been installed. This means that when a SIGCHLD is sent, it will exit + // depending on behavior external to this function. + // + // This function is used primarily for unit tests, if we want to use it in + // the application itself it would probably be best to examine other routes. + int status = -1; + pid_t ret_pid = HANDLE_EINTR(waitpid(handle, &status, WNOHANG)); + static const int64 kQuarterSecondInMicroseconds = kMicrosecondsPerSecond / 4; + + // If the process hasn't exited yet, then sleep and try again. + Time wakeup_time = Time::Now() + TimeDelta::FromMilliseconds( + wait_milliseconds); + while (ret_pid == 0) { + Time now = Time::Now(); + if (now > wakeup_time) + break; + // Guaranteed to be non-negative! + int64 sleep_time_usecs = (wakeup_time - now).InMicroseconds(); + // Don't sleep for more than 0.25 secs at a time. + if (sleep_time_usecs > kQuarterSecondInMicroseconds) { + sleep_time_usecs = kQuarterSecondInMicroseconds; + } + + // usleep() will return 0 and set errno to EINTR on receipt of a signal + // such as SIGCHLD. + usleep(sleep_time_usecs); + ret_pid = HANDLE_EINTR(waitpid(handle, &status, WNOHANG)); + } + + if (success) + *success = (ret_pid != -1); + + return status; +} + +void StackDumpSignalHandler(int signal) { + StackTrace().PrintBacktrace(); + _exit(1); +} + +} // namespace + ProcessId GetCurrentProcId() { return getpid(); } @@ -323,6 +385,29 @@ void EnableTerminationOnHeapCorruption() { // On POSIX, there nothing to do AFAIK. } +bool EnableInProcessStackDumping() { + // When running in an application, our code typically expects SIGPIPE + // to be ignored. Therefore, when testing that same code, it should run + // with SIGPIPE ignored as well. + struct sigaction action; + action.sa_handler = SIG_IGN; + action.sa_flags = 0; + sigemptyset(&action.sa_mask); + bool success = (sigaction(SIGPIPE, &action, NULL) == 0); + + // TODO(phajdan.jr): Catch other crashy signals, like SIGABRT. + success &= (signal(SIGSEGV, &StackDumpSignalHandler) != SIG_ERR); + success &= (signal(SIGILL, &StackDumpSignalHandler) != SIG_ERR); + success &= (signal(SIGBUS, &StackDumpSignalHandler) != SIG_ERR); + success &= (signal(SIGFPE, &StackDumpSignalHandler) != SIG_ERR); + return success; +} + +void AttachToConsole() { + // On POSIX, there nothing to do AFAIK. Maybe create a new console if none + // exist? +} + void RaiseProcessToHighPriority() { // On POSIX, we don't actually do anything here. We could try to nice() or // setpriority() or sched_getscheduler, but these all require extra rights. @@ -381,63 +466,6 @@ bool WaitForExitCode(ProcessHandle handle, int* exit_code) { return false; } -namespace { - -int WaitpidWithTimeout(ProcessHandle handle, int64 wait_milliseconds, - bool* success) { - // This POSIX version of this function only guarantees that we wait no less - // than |wait_milliseconds| for the proces to exit. The child process may - // exit sometime before the timeout has ended but we may still block for - // up to 0.25 seconds after the fact. - // - // waitpid() has no direct support on POSIX for specifying a timeout, you can - // either ask it to block indefinitely or return immediately (WNOHANG). - // When a child process terminates a SIGCHLD signal is sent to the parent. - // Catching this signal would involve installing a signal handler which may - // affect other parts of the application and would be difficult to debug. - // - // Our strategy is to call waitpid() once up front to check if the process - // has already exited, otherwise to loop for wait_milliseconds, sleeping for - // at most 0.25 secs each time using usleep() and then calling waitpid(). - // - // usleep() is speced to exit if a signal is received for which a handler - // has been installed. This means that when a SIGCHLD is sent, it will exit - // depending on behavior external to this function. - // - // This function is used primarily for unit tests, if we want to use it in - // the application itself it would probably be best to examine other routes. - int status = -1; - pid_t ret_pid = HANDLE_EINTR(waitpid(handle, &status, WNOHANG)); - static const int64 kQuarterSecondInMicroseconds = kMicrosecondsPerSecond/4; - - // If the process hasn't exited yet, then sleep and try again. - Time wakeup_time = Time::Now() + TimeDelta::FromMilliseconds( - wait_milliseconds); - while (ret_pid == 0) { - Time now = Time::Now(); - if (now > wakeup_time) - break; - // Guaranteed to be non-negative! - int64 sleep_time_usecs = (wakeup_time - now).InMicroseconds(); - // Don't sleep for more than 0.25 secs at a time. - if (sleep_time_usecs > kQuarterSecondInMicroseconds) { - sleep_time_usecs = kQuarterSecondInMicroseconds; - } - - // usleep() will return 0 and set errno to EINTR on receipt of a signal - // such as SIGCHLD. - usleep(sleep_time_usecs); - ret_pid = HANDLE_EINTR(waitpid(handle, &status, WNOHANG)); - } - - if (success) - *success = (ret_pid != -1); - - return status; -} - -} // namespace - bool WaitForSingleProcess(ProcessHandle handle, int64 wait_milliseconds) { bool waitpid_success; int status; diff --git a/base/process_util_win.cc b/base/process_util_win.cc index eaa6c88..ea1df60 100644 --- a/base/process_util_win.cc +++ b/base/process_util_win.cc @@ -4,15 +4,22 @@ #include "base/process_util.h" +#include <fcntl.h> +#include <io.h> #include <windows.h> #include <winternl.h> #include <psapi.h> +#include <ios> + +#include "base/debug_util.h" #include "base/histogram.h" #include "base/logging.h" #include "base/scoped_handle_win.h" #include "base/scoped_ptr.h" +namespace base { + namespace { // System pagesize. This value remains constant on x86/64 architectures. @@ -21,9 +28,54 @@ const int PAGESIZE_KB = 4; // HeapSetInformation function pointer. typedef BOOL (WINAPI* HeapSetFn)(HANDLE, HEAP_INFORMATION_CLASS, PVOID, SIZE_T); -} // namespace +// Previous unhandled filter. Will be called if not NULL when we intercept an +// exception. Only used in unit tests. +LPTOP_LEVEL_EXCEPTION_FILTER g_previous_filter = NULL; + +// Prints the exception call stack. +// This is the unit tests exception filter. +long WINAPI StackDumpExceptionFilter(EXCEPTION_POINTERS* info) { + StackTrace(info).PrintBacktrace(); + if (g_previous_filter) + return g_previous_filter(info); + return EXCEPTION_CONTINUE_SEARCH; +} + +// Connects back to a console if available. +// Only necessary on Windows, no-op on other platforms. +void AttachToConsole() { + if (!AttachConsole(ATTACH_PARENT_PROCESS)) { + unsigned int result = GetLastError(); + // Was probably already attached. + if (result == ERROR_ACCESS_DENIED) + return; -namespace base { + if (result == ERROR_INVALID_HANDLE || result == ERROR_INVALID_HANDLE) { + // TODO(maruel): Walk up the process chain if deemed necessary. + } + // Continue even if the function call fails. + AllocConsole(); + } + // http://support.microsoft.com/kb/105305 + int raw_out = _open_osfhandle( + reinterpret_cast<intptr_t>(GetStdHandle(STD_OUTPUT_HANDLE)), _O_TEXT); + *stdout = *_fdopen(raw_out, "w"); + setvbuf(stdout, NULL, _IONBF, 0); + + int raw_err = _open_osfhandle( + reinterpret_cast<intptr_t>(GetStdHandle(STD_ERROR_HANDLE)), _O_TEXT); + *stderr = *_fdopen(raw_err, "w"); + setvbuf(stderr, NULL, _IONBF, 0); + + int raw_in = _open_osfhandle( + reinterpret_cast<intptr_t>(GetStdHandle(STD_INPUT_HANDLE)), _O_TEXT); + *stdin = *_fdopen(raw_in, "r"); + setvbuf(stdin, NULL, _IONBF, 0); + // Fix all cout, wcout, cin, wcin, cerr, wcerr, clog and wclog. + std::ios::sync_with_stdio(); +} + +} // namespace ProcessId GetCurrentProcId() { return ::GetCurrentProcessId(); @@ -763,6 +815,14 @@ void EnableTerminationOnHeapCorruption() { HeapSetInformation(NULL, HeapEnableTerminationOnCorruption, NULL, 0); } +bool EnableInProcessStackDumping() { + // Add stack dumping support on exception on windows. Similar to OS_POSIX + // signal() handling in process_util_posix.cc. + g_previous_filter = SetUnhandledExceptionFilter(&StackDumpExceptionFilter); + AttachToConsole(); + return true; +} + void RaiseProcessToHighPriority() { SetPriorityClass(GetCurrentProcess(), HIGH_PRIORITY_CLASS); } diff --git a/base/test/test_suite.h b/base/test/test_suite.h index 5fd1acc..42ad027 100644 --- a/base/test/test_suite.h +++ b/base/test/test_suite.h @@ -11,52 +11,19 @@ #include "base/at_exit.h" #include "base/base_paths.h" -#include "base/command_line.h" #include "base/debug_on_start.h" -#include "base/debug_util.h" -#include "base/file_path.h" #include "base/i18n/icu_util.h" -#include "base/logging.h" #include "base/multiprocess_test.h" +#include "base/process_util.h" #include "base/scoped_nsautorelease_pool.h" #include "base/time.h" #include "testing/gtest/include/gtest/gtest.h" #include "testing/multiprocess_func_list.h" -#if defined(OS_WIN) -#include <windows.h> -#endif - -#if defined(OS_POSIX) -#include <signal.h> -#endif - #if defined(OS_LINUX) #include <gtk/gtk.h> #endif -#if defined(OS_POSIX) -static void TestSuiteCrashHandler(int signal) { - StackTrace().PrintBacktrace(); - _exit(1); -} -#endif // OS_POSIX - -#if defined(OS_WIN) -// Previous unhandled filter. Will be called if not NULL when we intercept an -// exception. -__declspec(selectany) LPTOP_LEVEL_EXCEPTION_FILTER g_previous_filter = NULL; - -// Prints the exception call stack. -// This is the unit tests exception filter. -inline long WINAPI UnitTestExceptionFilter(EXCEPTION_POINTERS* info) { - StackTrace(info).PrintBacktrace(); - if (g_previous_filter) - return g_previous_filter(info); - return EXCEPTION_EXECUTE_HANDLER; -} -#endif // OS_WIN - // Match function used by the GetTestCount method. typedef bool (*TestMatch)(const testing::TestInfo&); @@ -185,23 +152,7 @@ class TestSuite { // Note: temporarily enabled timestamps in an effort to catch bug 6361. logging::SetLogItems(true, true, true, true); -#if defined(OS_POSIX) - // When running in an application, our code typically expects SIGPIPE - // to be ignored. Therefore, when testing that same code, it should run - // with SIGPIPE ignored as well. - struct sigaction action; - action.sa_handler = SIG_IGN; - action.sa_flags = 0; - sigemptyset(&action.sa_mask); - CHECK(sigaction(SIGPIPE, &action, NULL) == 0); - - // TODO(phajdan.jr): Catch other crashy signals, like SIGABRT. - CHECK(signal(SIGSEGV, &TestSuiteCrashHandler) != SIG_ERR); - CHECK(signal(SIGILL, &TestSuiteCrashHandler) != SIG_ERR); - CHECK(signal(SIGBUS, &TestSuiteCrashHandler) != SIG_ERR); - CHECK(signal(SIGFPE, &TestSuiteCrashHandler) != SIG_ERR); -#endif // OS_POSIX - + CHECK(base::EnableInProcessStackDumping()); #if defined(OS_WIN) // For unit tests we turn on the high resolution timer and disable // base::Time's use of SystemMonitor. Tests create and destroy the message @@ -217,9 +168,6 @@ class TestSuite { // As a hack workaround, just #ifdef out this code for Purify builds. logging::SetLogAssertHandler(UnitTestAssertHandler); #endif // !defined(PURIFY) - // Add stack dumping support on exception on windows. Similar to OS_POSIX - // signal() handling above. - g_previous_filter = SetUnhandledExceptionFilter(&UnitTestExceptionFilter); } #endif // defined(OS_WIN) diff --git a/webkit/tools/test_shell/run_all_tests.cc b/webkit/tools/test_shell/run_all_tests.cc index 3c6ec3f..560604b 100644 --- a/webkit/tools/test_shell/run_all_tests.cc +++ b/webkit/tools/test_shell/run_all_tests.cc @@ -31,7 +31,7 @@ #include "webkit/tools/test_shell/test_shell_webkit_init.h" #include "testing/gtest/include/gtest/gtest.h" -const char* TestShellTest::kJavascriptDelayExitScript = +const char* const TestShellTest::kJavascriptDelayExitScript = "<script>" "window.layoutTestController.waitUntilDone();" "window.addEventListener('load', function() {" @@ -42,6 +42,7 @@ const char* TestShellTest::kJavascriptDelayExitScript = int main(int argc, char* argv[]) { base::ScopedNSAutoreleasePool autorelease_pool; + base::EnableInProcessStackDumping(); base::EnableTerminationOnHeapCorruption(); // Some unittests may use base::Singleton<>, thus we need to instanciate // the AtExitManager or else we will leak objects. diff --git a/webkit/tools/test_shell/test_shell.gyp b/webkit/tools/test_shell/test_shell.gyp index 6870e10..f0fe364 100644 --- a/webkit/tools/test_shell/test_shell.gyp +++ b/webkit/tools/test_shell/test_shell.gyp @@ -244,6 +244,11 @@ 'test_shell_common', '../../../tools/imagediff/image_diff.gyp:image_diff', ], + 'defines': [ + # Technically not a unit test but require functions available only to + # unit tests. + 'UNIT_TEST' + ], 'sources': [ 'test_shell_main.cc', ], diff --git a/webkit/tools/test_shell/test_shell_main.cc b/webkit/tools/test_shell/test_shell_main.cc index 9f2ae67..b069b2f 100644 --- a/webkit/tools/test_shell/test_shell_main.cc +++ b/webkit/tools/test_shell/test_shell_main.cc @@ -45,13 +45,14 @@ using WebKit::WebScriptController; namespace { // StatsTable initialization parameters. -static const char* kStatsFilePrefix = "testshell_"; -static int kStatsFileThreads = 20; -static int kStatsFileCounters = 200; +const char* const kStatsFilePrefix = "testshell_"; +int kStatsFileThreads = 20; +int kStatsFileCounters = 200; } // namespace int main(int argc, char* argv[]) { + base::EnableInProcessStackDumping(); base::EnableTerminationOnHeapCorruption(); // Some tests may use base::Singleton<>, thus we need to instanciate diff --git a/webkit/tools/test_shell/test_shell_test.h b/webkit/tools/test_shell/test_shell_test.h index a5e0705..48dc8f93 100644 --- a/webkit/tools/test_shell/test_shell_test.h +++ b/webkit/tools/test_shell/test_shell_test.h @@ -29,7 +29,7 @@ class TestShellTest : public testing::Test { // Don't refactor away; some unittests override this! virtual void CreateEmptyWindow(); - static const char* kJavascriptDelayExitScript; + static const char* const kJavascriptDelayExitScript; protected: // Location of SOURCE_ROOT/webkit/data/ |