Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(2085)

Unified Diff: base/test/launcher/test_launcher.cc

Issue 55473002: GTTF: Unify parallel test launching. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: reupload Created 7 years, 2 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « base/test/launcher/test_launcher.h ('k') | base/test/launcher/unit_test_launcher.cc » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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);
« no previous file with comments | « base/test/launcher/test_launcher.h ('k') | base/test/launcher/unit_test_launcher.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698