Commit d6fc9fd2 authored by maruel@chromium.org's avatar maruel@chromium.org

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
parent 30be1ce6
...@@ -420,10 +420,17 @@ class ProcessMetrics { ...@@ -420,10 +420,17 @@ class ProcessMetrics {
// Note: Returns true on Windows 2000 without doing anything. // Note: Returns true on Windows 2000 without doing anything.
bool EnableLowFragmentationHeap(); 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. // overflow. Has no effect if the OS doesn't provide the necessary facility.
void EnableTerminationOnHeapCorruption(); 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 // If supported on the platform, and the user has sufficent rights, increase
// the current process's scheduling priority to a high priority. // the current process's scheduling priority to a high priority.
void RaiseProcessToHighPriority(); void RaiseProcessToHighPriority();
......
...@@ -16,7 +16,7 @@ ...@@ -16,7 +16,7 @@
#include <limits> #include <limits>
#include <set> #include <set>
#include "base/basictypes.h" #include "base/debug_util.h"
#include "base/eintr_wrapper.h" #include "base/eintr_wrapper.h"
#include "base/logging.h" #include "base/logging.h"
#include "base/platform_thread.h" #include "base/platform_thread.h"
...@@ -30,6 +30,68 @@ const int kMicrosecondsPerSecond = 1000000; ...@@ -30,6 +30,68 @@ const int kMicrosecondsPerSecond = 1000000;
namespace base { 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() { ProcessId GetCurrentProcId() {
return getpid(); return getpid();
} }
...@@ -323,6 +385,29 @@ void EnableTerminationOnHeapCorruption() { ...@@ -323,6 +385,29 @@ void EnableTerminationOnHeapCorruption() {
// On POSIX, there nothing to do AFAIK. // 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() { void RaiseProcessToHighPriority() {
// On POSIX, we don't actually do anything here. We could try to nice() or // 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. // setpriority() or sched_getscheduler, but these all require extra rights.
...@@ -381,63 +466,6 @@ bool WaitForExitCode(ProcessHandle handle, int* exit_code) { ...@@ -381,63 +466,6 @@ bool WaitForExitCode(ProcessHandle handle, int* exit_code) {
return false; 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 WaitForSingleProcess(ProcessHandle handle, int64 wait_milliseconds) {
bool waitpid_success; bool waitpid_success;
int status; int status;
......
...@@ -4,15 +4,22 @@ ...@@ -4,15 +4,22 @@
#include "base/process_util.h" #include "base/process_util.h"
#include <fcntl.h>
#include <io.h>
#include <windows.h> #include <windows.h>
#include <winternl.h> #include <winternl.h>
#include <psapi.h> #include <psapi.h>
#include <ios>
#include "base/debug_util.h"
#include "base/histogram.h" #include "base/histogram.h"
#include "base/logging.h" #include "base/logging.h"
#include "base/scoped_handle_win.h" #include "base/scoped_handle_win.h"
#include "base/scoped_ptr.h" #include "base/scoped_ptr.h"
namespace base {
namespace { namespace {
// System pagesize. This value remains constant on x86/64 architectures. // System pagesize. This value remains constant on x86/64 architectures.
...@@ -21,9 +28,54 @@ const int PAGESIZE_KB = 4; ...@@ -21,9 +28,54 @@ const int PAGESIZE_KB = 4;
// HeapSetInformation function pointer. // HeapSetInformation function pointer.
typedef BOOL (WINAPI* HeapSetFn)(HANDLE, HEAP_INFORMATION_CLASS, PVOID, SIZE_T); 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() { ProcessId GetCurrentProcId() {
return ::GetCurrentProcessId(); return ::GetCurrentProcessId();
...@@ -763,6 +815,14 @@ void EnableTerminationOnHeapCorruption() { ...@@ -763,6 +815,14 @@ void EnableTerminationOnHeapCorruption() {
HeapSetInformation(NULL, HeapEnableTerminationOnCorruption, NULL, 0); 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() { void RaiseProcessToHighPriority() {
SetPriorityClass(GetCurrentProcess(), HIGH_PRIORITY_CLASS); SetPriorityClass(GetCurrentProcess(), HIGH_PRIORITY_CLASS);
} }
......
...@@ -11,52 +11,19 @@ ...@@ -11,52 +11,19 @@
#include "base/at_exit.h" #include "base/at_exit.h"
#include "base/base_paths.h" #include "base/base_paths.h"
#include "base/command_line.h"
#include "base/debug_on_start.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/i18n/icu_util.h"
#include "base/logging.h"
#include "base/multiprocess_test.h" #include "base/multiprocess_test.h"
#include "base/process_util.h"
#include "base/scoped_nsautorelease_pool.h" #include "base/scoped_nsautorelease_pool.h"
#include "base/time.h" #include "base/time.h"
#include "testing/gtest/include/gtest/gtest.h" #include "testing/gtest/include/gtest/gtest.h"
#include "testing/multiprocess_func_list.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) #if defined(OS_LINUX)
#include <gtk/gtk.h> #include <gtk/gtk.h>
#endif #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. // Match function used by the GetTestCount method.
typedef bool (*TestMatch)(const testing::TestInfo&); typedef bool (*TestMatch)(const testing::TestInfo&);
...@@ -185,23 +152,7 @@ class TestSuite { ...@@ -185,23 +152,7 @@ class TestSuite {
// Note: temporarily enabled timestamps in an effort to catch bug 6361. // Note: temporarily enabled timestamps in an effort to catch bug 6361.
logging::SetLogItems(true, true, true, true); logging::SetLogItems(true, true, true, true);
#if defined(OS_POSIX) CHECK(base::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);
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
#if defined(OS_WIN) #if defined(OS_WIN)
// For unit tests we turn on the high resolution timer and disable // For unit tests we turn on the high resolution timer and disable
// base::Time's use of SystemMonitor. Tests create and destroy the message // base::Time's use of SystemMonitor. Tests create and destroy the message
...@@ -217,9 +168,6 @@ class TestSuite { ...@@ -217,9 +168,6 @@ class TestSuite {
// As a hack workaround, just #ifdef out this code for Purify builds. // As a hack workaround, just #ifdef out this code for Purify builds.
logging::SetLogAssertHandler(UnitTestAssertHandler); logging::SetLogAssertHandler(UnitTestAssertHandler);
#endif // !defined(PURIFY) #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) #endif // defined(OS_WIN)
......
...@@ -31,7 +31,7 @@ ...@@ -31,7 +31,7 @@
#include "webkit/tools/test_shell/test_shell_webkit_init.h" #include "webkit/tools/test_shell/test_shell_webkit_init.h"
#include "testing/gtest/include/gtest/gtest.h" #include "testing/gtest/include/gtest/gtest.h"
const char* TestShellTest::kJavascriptDelayExitScript = const char* const TestShellTest::kJavascriptDelayExitScript =
"<script>" "<script>"
"window.layoutTestController.waitUntilDone();" "window.layoutTestController.waitUntilDone();"
"window.addEventListener('load', function() {" "window.addEventListener('load', function() {"
...@@ -42,6 +42,7 @@ const char* TestShellTest::kJavascriptDelayExitScript = ...@@ -42,6 +42,7 @@ const char* TestShellTest::kJavascriptDelayExitScript =
int main(int argc, char* argv[]) { int main(int argc, char* argv[]) {
base::ScopedNSAutoreleasePool autorelease_pool; base::ScopedNSAutoreleasePool autorelease_pool;
base::EnableInProcessStackDumping();
base::EnableTerminationOnHeapCorruption(); base::EnableTerminationOnHeapCorruption();
// Some unittests may use base::Singleton<>, thus we need to instanciate // Some unittests may use base::Singleton<>, thus we need to instanciate
// the AtExitManager or else we will leak objects. // the AtExitManager or else we will leak objects.
......
...@@ -244,6 +244,11 @@ ...@@ -244,6 +244,11 @@
'test_shell_common', 'test_shell_common',
'../../../tools/imagediff/image_diff.gyp:image_diff', '../../../tools/imagediff/image_diff.gyp:image_diff',
], ],
'defines': [
# Technically not a unit test but require functions available only to
# unit tests.
'UNIT_TEST'
],
'sources': [ 'sources': [
'test_shell_main.cc', 'test_shell_main.cc',
], ],
......
...@@ -45,13 +45,14 @@ using WebKit::WebScriptController; ...@@ -45,13 +45,14 @@ using WebKit::WebScriptController;
namespace { namespace {
// StatsTable initialization parameters. // StatsTable initialization parameters.
static const char* kStatsFilePrefix = "testshell_"; const char* const kStatsFilePrefix = "testshell_";
static int kStatsFileThreads = 20; int kStatsFileThreads = 20;
static int kStatsFileCounters = 200; int kStatsFileCounters = 200;
} // namespace } // namespace
int main(int argc, char* argv[]) { int main(int argc, char* argv[]) {
base::EnableInProcessStackDumping();
base::EnableTerminationOnHeapCorruption(); base::EnableTerminationOnHeapCorruption();
// Some tests may use base::Singleton<>, thus we need to instanciate // Some tests may use base::Singleton<>, thus we need to instanciate
......
...@@ -29,7 +29,7 @@ class TestShellTest : public testing::Test { ...@@ -29,7 +29,7 @@ class TestShellTest : public testing::Test {
// Don't refactor away; some unittests override this! // Don't refactor away; some unittests override this!
virtual void CreateEmptyWindow(); virtual void CreateEmptyWindow();
static const char* kJavascriptDelayExitScript; static const char* const kJavascriptDelayExitScript;
protected: protected:
// Location of SOURCE_ROOT/webkit/data/ // Location of SOURCE_ROOT/webkit/data/
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment