Index: base/test/launcher/test_launcher.cc |
diff --git a/base/test/launcher/test_launcher.cc b/base/test/launcher/test_launcher.cc |
index 9ad002b672fd2f22d7e7643379e71770f311d7ca..00766347c8e8818cbb8cbd6b1c6edf193cd59247 100644 |
--- a/base/test/launcher/test_launcher.cc |
+++ b/base/test/launcher/test_launcher.cc |
@@ -25,6 +25,7 @@ |
#include "base/strings/stringprintf.h" |
#include "base/strings/utf_string_conversions.h" |
#include "base/test/launcher/test_results_tracker.h" |
+#include "base/test/sequenced_worker_pool_owner.h" |
#include "base/test/test_switches.h" |
#include "base/test/test_timeouts.h" |
#include "base/threading/thread_checker.h" |
@@ -47,11 +48,21 @@ const char kTestShardIndex[] = "GTEST_SHARD_INDEX"; |
namespace { |
+// Maximum time of no output after which we print list of processes still |
+// running. This deliberately doesn't use TestTimeouts (which is otherwise |
+// a recommended solution), because they can be increased. This would defeat |
+// the purpose of this timeout, which is 1) to avoid buildbot "no output for |
+// X seconds" timeout killing the process 2) help communicate status of |
+// the test launcher to people looking at the output (no output for a long |
+// time is mysterious and gives no info about what is happening) 3) help |
+// debugging in case the process hangs anyway. |
+const int kOutputTimeoutSeconds = 15; |
+ |
// Set of live launch test processes with corresponding lock (it is allowed |
// for callers to launch processes on different threads). |
-LazyInstance<std::set<ProcessHandle> > g_live_process_handles |
+LazyInstance<std::map<ProcessHandle, CommandLine> > g_live_processes |
= LAZY_INSTANCE_INITIALIZER; |
-LazyInstance<Lock> g_live_process_handles_lock = LAZY_INSTANCE_INITIALIZER; |
+LazyInstance<Lock> g_live_processes_lock = LAZY_INSTANCE_INITIALIZER; |
#if defined(OS_POSIX) |
// Self-pipe that makes it possible to do complex shutdown handling |
@@ -65,18 +76,19 @@ void ShutdownPipeSignalHandler(int signal) { |
void KillSpawnedTestProcesses() { |
// Keep the lock until exiting the process to prevent further processes |
// from being spawned. |
- AutoLock lock(g_live_process_handles_lock.Get()); |
+ AutoLock lock(g_live_processes_lock.Get()); |
fprintf(stdout, |
"Sending SIGTERM to %" PRIuS " child processes... ", |
- g_live_process_handles.Get().size()); |
+ g_live_processes.Get().size()); |
fflush(stdout); |
- for (std::set<ProcessHandle>::iterator i = |
- g_live_process_handles.Get().begin(); |
- i != g_live_process_handles.Get().end(); |
+ for (std::map<ProcessHandle, CommandLine>::iterator i = |
+ g_live_processes.Get().begin(); |
+ i != g_live_processes.Get().end(); |
++i) { |
- kill((-1) * (*i), SIGTERM); // Send the signal to entire process group. |
+ // Send the signal to entire process group. |
+ kill((-1) * (i->first), SIGTERM); |
} |
fprintf(stdout, |
@@ -90,14 +102,15 @@ void KillSpawnedTestProcesses() { |
fprintf(stdout, |
"Sending SIGKILL to %" PRIuS " child processes... ", |
- g_live_process_handles.Get().size()); |
+ g_live_processes.Get().size()); |
fflush(stdout); |
- for (std::set<ProcessHandle>::iterator i = |
- g_live_process_handles.Get().begin(); |
- i != g_live_process_handles.Get().end(); |
+ for (std::map<ProcessHandle, CommandLine>::iterator i = |
+ g_live_processes.Get().begin(); |
+ i != g_live_processes.Get().end(); |
++i) { |
- kill((-1) * (*i), SIGKILL); // Send the signal to entire process group. |
+ // Send the signal to entire process group. |
+ kill((-1) * (i->first), SIGKILL); |
} |
fprintf(stdout, "done.\n"); |
@@ -194,6 +207,91 @@ bool MatchesFilter(const std::string& name, const std::string& filter) { |
} |
} |
+void RunCallback( |
+ const TestLauncher::LaunchChildGTestProcessCallback& callback, |
+ int exit_code, |
+ const TimeDelta& elapsed_time, |
+ bool was_timeout, |
+ const std::string& output) { |
+ callback.Run(exit_code, elapsed_time, was_timeout, output); |
+} |
+ |
+void DoLaunchChildTestProcess( |
+ const CommandLine& command_line, |
+ base::TimeDelta timeout, |
+ scoped_refptr<MessageLoopProxy> message_loop_proxy, |
+ const TestLauncher::LaunchChildGTestProcessCallback& callback) { |
+ TimeTicks start_time = TimeTicks::Now(); |
+ |
+ // Redirect child process output to a file. |
+ base::FilePath output_file; |
+ CHECK(file_util::CreateTemporaryFile(&output_file)); |
+ |
+ LaunchOptions options; |
+#if defined(OS_WIN) |
+ // Make the file handle inheritable by the child. |
+ SECURITY_ATTRIBUTES sa_attr; |
+ sa_attr.nLength = sizeof(SECURITY_ATTRIBUTES); |
+ sa_attr.lpSecurityDescriptor = NULL; |
+ sa_attr.bInheritHandle = TRUE; |
+ |
+ win::ScopedHandle handle(CreateFile(output_file.value().c_str(), |
+ GENERIC_WRITE, |
+ FILE_SHARE_READ | FILE_SHARE_DELETE, |
+ &sa_attr, |
+ OPEN_EXISTING, |
+ FILE_ATTRIBUTE_TEMPORARY, |
+ NULL)); |
+ CHECK(handle.IsValid()); |
+ options.inherit_handles = true; |
+ options.stdin_handle = INVALID_HANDLE_VALUE; |
+ options.stdout_handle = handle.Get(); |
+ options.stderr_handle = handle.Get(); |
+#elif defined(OS_POSIX) |
+ options.new_process_group = true; |
+ |
+ int output_file_fd = open(output_file.value().c_str(), O_RDWR); |
+ CHECK_GE(output_file_fd, 0); |
+ |
+ file_util::ScopedFD output_file_fd_closer(&output_file_fd); |
+ |
+ base::FileHandleMappingVector fds_mapping; |
+ fds_mapping.push_back(std::make_pair(output_file_fd, STDOUT_FILENO)); |
+ fds_mapping.push_back(std::make_pair(output_file_fd, STDERR_FILENO)); |
+ options.fds_to_remap = &fds_mapping; |
+#endif |
+ |
+ bool was_timeout = false; |
+ int exit_code = LaunchChildTestProcessWithOptions( |
+ command_line, options, timeout, &was_timeout); |
+ |
+#if defined(OS_WIN) |
+ FlushFileBuffers(handle.Get()); |
+ handle.Close(); |
+#elif defined(OS_POSIX) |
+ output_file_fd_closer.reset(); |
+#endif |
+ |
+ std::string output_file_contents; |
+ CHECK(base::ReadFileToString(output_file, &output_file_contents)); |
+ |
+ if (!base::DeleteFile(output_file, false)) { |
+ // This needs to be non-fatal at least for Windows. |
+ LOG(WARNING) << "Failed to delete " << output_file.AsUTF8Unsafe(); |
+ } |
+ |
+ // Run target callback on the thread it was originating from, not on |
+ // a worker pool thread. |
+ message_loop_proxy->PostTask( |
+ FROM_HERE, |
+ Bind(&RunCallback, |
+ callback, |
+ exit_code, |
+ TimeTicks::Now() - start_time, |
+ was_timeout, |
+ output_file_contents)); |
+} |
+ |
} // namespace |
const char kGTestFilterFlag[] = "gtest_filter"; |
@@ -206,7 +304,8 @@ const char kGTestOutputFlag[] = "gtest_output"; |
TestLauncherDelegate::~TestLauncherDelegate() { |
} |
-TestLauncher::TestLauncher(TestLauncherDelegate* launcher_delegate) |
+TestLauncher::TestLauncher(TestLauncherDelegate* launcher_delegate, |
+ size_t parallel_jobs) |
: launcher_delegate_(launcher_delegate), |
total_shards_(1), |
shard_index_(0), |
@@ -217,10 +316,17 @@ TestLauncher::TestLauncher(TestLauncherDelegate* launcher_delegate) |
test_broken_count_(0), |
retry_count_(0), |
retry_limit_(3), // TODO(phajdan.jr): Make a flag control this. |
- run_result_(true) { |
+ run_result_(true), |
+ watchdog_timer_(FROM_HERE, |
+ TimeDelta::FromSeconds(kOutputTimeoutSeconds), |
+ this, |
+ &TestLauncher::OnOutputTimeout), |
+ worker_pool_owner_( |
+ new SequencedWorkerPoolOwner(parallel_jobs, "test_launcher")) { |
} |
TestLauncher::~TestLauncher() { |
+ worker_pool_owner_->pool()->Shutdown(); |
} |
bool TestLauncher::Run(int argc, char** argv) { |
@@ -250,6 +356,9 @@ bool TestLauncher::Run(int argc, char** argv) { |
&watcher)); |
#endif // defined(OS_POSIX) |
+ // Start the watchdog timer. |
+ watchdog_timer_.Reset(); |
+ |
MessageLoop::current()->PostTask( |
FROM_HERE, |
Bind(&TestLauncher::RunTestIteration, Unretained(this))); |
@@ -264,6 +373,28 @@ bool TestLauncher::Run(int argc, char** argv) { |
return run_result_; |
} |
+void TestLauncher::LaunchChildGTestProcess( |
+ const CommandLine& command_line, |
+ const std::string& wrapper, |
+ base::TimeDelta timeout, |
+ const LaunchChildGTestProcessCallback& callback) { |
+ DCHECK(thread_checker_.CalledOnValidThread()); |
+ |
+ // Record the exact command line used to launch the child. |
+ CommandLine new_command_line( |
+ PrepareCommandLineForGTest(command_line, wrapper)); |
+ |
+ worker_pool_owner_->pool()->PostWorkerTask( |
+ FROM_HERE, |
+ Bind(&DoLaunchChildTestProcess, |
+ new_command_line, |
+ timeout, |
+ MessageLoopProxy::current(), |
+ Bind(&TestLauncher::OnLaunchTestProcessFinished, |
+ Unretained(this), |
+ callback))); |
+} |
+ |
void TestLauncher::OnTestFinished(const TestResult& result) { |
++test_finished_count_; |
@@ -321,6 +452,9 @@ void TestLauncher::OnTestFinished(const TestResult& result) { |
fprintf(stdout, "%s\n", status_line.c_str()); |
fflush(stdout); |
+ // We just printed a status line, reset the watchdog timer. |
+ watchdog_timer_.Reset(); |
+ |
if (test_finished_count_ == test_started_count_) { |
if (!tests_to_retry_.empty() && retry_count_ < retry_limit_) { |
retry_count_++; |
@@ -537,9 +671,6 @@ void TestLauncher::RunTestIteration() { |
FROM_HERE, Bind(&TestLauncher::RunTests, Unretained(this))); |
} |
-void TestLauncher::OnAllTestsStarted() { |
-} |
- |
void TestLauncher::MaybeSaveSummaryAsJSON() { |
const CommandLine* command_line = CommandLine::ForCurrentProcess(); |
if (command_line->HasSwitch(switches::kTestLauncherSummaryOutput)) { |
@@ -551,6 +682,41 @@ void TestLauncher::MaybeSaveSummaryAsJSON() { |
} |
} |
+void TestLauncher::OnLaunchTestProcessFinished( |
+ const LaunchChildGTestProcessCallback& callback, |
+ int exit_code, |
+ const TimeDelta& elapsed_time, |
+ bool was_timeout, |
+ const std::string& output) { |
+ DCHECK(thread_checker_.CalledOnValidThread()); |
+ |
+ callback.Run(exit_code, elapsed_time, was_timeout, output); |
+} |
+ |
+void TestLauncher::OnOutputTimeout() { |
+ DCHECK(thread_checker_.CalledOnValidThread()); |
+ |
+ AutoLock lock(g_live_processes_lock.Get()); |
+ |
+ fprintf(stdout, "Still waiting for the following processes to finish:\n"); |
+ |
+ for (std::map<ProcessHandle, CommandLine>::iterator i = |
+ g_live_processes.Get().begin(); |
+ i != g_live_processes.Get().end(); |
+ ++i) { |
+#if defined(OS_WIN) |
+ fwprintf(stdout, L"\t%s\n", i->second.GetCommandLineString().c_str()); |
+#else |
+ fprintf(stdout, "\t%s\n", i->second.GetCommandLineString().c_str()); |
+#endif |
+ } |
+ |
+ fflush(stdout); |
+ |
+ // Arm the timer again - otherwise it would fire only once. |
+ watchdog_timer_.Reset(); |
+} |
+ |
std::string GetTestOutputSnippet(const TestResult& result, |
const std::string& full_output) { |
size_t run_pos = full_output.find(std::string("[ RUN ] ") + |
@@ -665,12 +831,12 @@ int LaunchChildTestProcessWithOptions(const CommandLine& command_line, |
// Note how we grab the lock before the process possibly gets created. |
// This ensures that when the lock is held, ALL the processes are registered |
// in the set. |
- AutoLock lock(g_live_process_handles_lock.Get()); |
+ AutoLock lock(g_live_processes_lock.Get()); |
if (!base::LaunchProcess(command_line, new_options, &process_handle)) |
return -1; |
- g_live_process_handles.Get().insert(process_handle); |
+ g_live_processes.Get().insert(std::make_pair(process_handle, command_line)); |
} |
int exit_code = 0; |
@@ -688,7 +854,7 @@ int LaunchChildTestProcessWithOptions(const CommandLine& command_line, |
// Note how we grab the log before issuing a possibly broad process kill. |
// Other code parts that grab the log kill processes, so avoid trying |
// to do that twice and trigger all kinds of log messages. |
- AutoLock lock(g_live_process_handles_lock.Get()); |
+ AutoLock lock(g_live_processes_lock.Get()); |
#if defined(OS_POSIX) |
if (exit_code != 0) { |
@@ -700,7 +866,7 @@ int LaunchChildTestProcessWithOptions(const CommandLine& command_line, |
} |
#endif |
- g_live_process_handles.Get().erase(process_handle); |
+ g_live_processes.Get().erase(process_handle); |
} |
base::CloseProcessHandle(process_handle); |