Remove usage of SequencedWorkerPool in GN.
SequencedWorkerPool is deprecated. A process-wide TaskScheduler
must be used instead.
Bug: 667892
Change-Id: I67f5bafe5e5eaba3aeb40664dea7cdd7d4250b60
Reviewed-on: https://chromium-review.googlesource.com/879354
Commit-Queue: François Doray <fdoray@chromium.org>
Reviewed-by: Sylvain Defresne <sdefresne@chromium.org>
Cr-Original-Commit-Position: refs/heads/master@{#531270}
Cr-Mirrored-From: https://chromium.googlesource.com/chromium/src
Cr-Mirrored-Commit: cb4f92cc31ff0e1e598b8e023a40a0409a766a60
diff --git a/tools/gn/BUILD.gn b/tools/gn/BUILD.gn
index a2616a2..31e2493 100644
--- a/tools/gn/BUILD.gn
+++ b/tools/gn/BUILD.gn
@@ -343,6 +343,8 @@
"substitution_writer_unittest.cc",
"target_unittest.cc",
"template_unittest.cc",
+ "test_with_scheduler.cc",
+ "test_with_scheduler.h",
"test_with_scope.cc",
"test_with_scope.h",
"tokenizer_unittest.cc",
diff --git a/tools/gn/action_target_generator_unittest.cc b/tools/gn/action_target_generator_unittest.cc
index 5a45932..254bc98 100644
--- a/tools/gn/action_target_generator_unittest.cc
+++ b/tools/gn/action_target_generator_unittest.cc
@@ -4,11 +4,13 @@
#include "testing/gtest/include/gtest/gtest.h"
#include "tools/gn/scheduler.h"
+#include "tools/gn/test_with_scheduler.h"
#include "tools/gn/test_with_scope.h"
+using ActionTargetGenerator = TestWithScheduler;
+
// Tests that actions can't have output substitutions.
-TEST(ActionTargetGenerator, ActionOutputSubstitutions) {
- Scheduler scheduler;
+TEST_F(ActionTargetGenerator, ActionOutputSubstitutions) {
TestWithScope setup;
Scope::ItemVector items_;
setup.scope()->set_item_collector(&items_);
@@ -43,8 +45,7 @@
// Tests that arg and response file substitutions are validated for
// action_foreach targets.
-TEST(ActionTargetGenerator, ActionForeachSubstitutions) {
- Scheduler scheduler;
+TEST_F(ActionTargetGenerator, ActionForeachSubstitutions) {
TestWithScope setup;
Scope::ItemVector items_;
setup.scope()->set_item_collector(&items_);
diff --git a/tools/gn/command_format_unittest.cc b/tools/gn/command_format_unittest.cc
index bf84061..c351df0 100644
--- a/tools/gn/command_format_unittest.cc
+++ b/tools/gn/command_format_unittest.cc
@@ -2,16 +2,20 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+#include "tools/gn/command_format.h"
+
#include "base/files/file_util.h"
#include "base/path_service.h"
#include "base/strings/string_util.h"
#include "testing/gtest/include/gtest/gtest.h"
-#include "tools/gn/command_format.h"
#include "tools/gn/commands.h"
#include "tools/gn/setup.h"
+#include "tools/gn/test_with_scheduler.h"
+
+using FormatTest = TestWithScheduler;
#define FORMAT_TEST(n) \
- TEST(Format, n) { \
+ TEST_F(FormatTest, n) { \
::Setup setup; \
std::string out; \
std::string expected; \
diff --git a/tools/gn/function_forward_variables_from_unittest.cc b/tools/gn/function_forward_variables_from_unittest.cc
index 84d8b6e..3d69788 100644
--- a/tools/gn/function_forward_variables_from_unittest.cc
+++ b/tools/gn/function_forward_variables_from_unittest.cc
@@ -4,10 +4,12 @@
#include "testing/gtest/include/gtest/gtest.h"
#include "tools/gn/scheduler.h"
+#include "tools/gn/test_with_scheduler.h"
#include "tools/gn/test_with_scope.h"
-TEST(FunctionForwardVariablesFrom, List) {
- Scheduler scheduler;
+using FunctionForwardVariablesFromTest = TestWithScheduler;
+
+TEST_F(FunctionForwardVariablesFromTest, List) {
Err err;
std::string program =
"template(\"a\") {\n"
@@ -50,8 +52,7 @@
}
}
-TEST(FunctionForwardVariablesFrom, LiteralList) {
- Scheduler scheduler;
+TEST_F(FunctionForwardVariablesFromTest, LiteralList) {
TestWithScope setup;
// Forwards all variables from a literal scope into another scope definition.
@@ -72,8 +73,7 @@
setup.print_output().clear();
}
-TEST(FunctionForwardVariablesFrom, ListWithExclusion) {
- Scheduler scheduler;
+TEST_F(FunctionForwardVariablesFromTest, ListWithExclusion) {
TestWithScope setup;
// Defines a template and copy the two x and y, and z values out.
@@ -100,8 +100,7 @@
setup.print_output().clear();
}
-TEST(FunctionForwardVariablesFrom, ErrorCases) {
- Scheduler scheduler;
+TEST_F(FunctionForwardVariablesFromTest, ErrorCases) {
TestWithScope setup;
// Type check the source scope.
@@ -190,8 +189,7 @@
EXPECT_EQ("Wrong number of arguments.", err.message());
}
-TEST(FunctionForwardVariablesFrom, Star) {
- Scheduler scheduler;
+TEST_F(FunctionForwardVariablesFromTest, Star) {
TestWithScope setup;
// Defines a template and copy the two x and y values out. The "*" behavior
@@ -217,9 +215,7 @@
setup.print_output().clear();
}
-
-TEST(FunctionForwardVariablesFrom, StarWithExclusion) {
- Scheduler scheduler;
+TEST_F(FunctionForwardVariablesFromTest, StarWithExclusion) {
TestWithScope setup;
// Defines a template and copy all values except z value. The "*" behavior
diff --git a/tools/gn/function_toolchain_unittest.cc b/tools/gn/function_toolchain_unittest.cc
index 2d8a548..eec8779 100644
--- a/tools/gn/function_toolchain_unittest.cc
+++ b/tools/gn/function_toolchain_unittest.cc
@@ -5,10 +5,12 @@
#include "testing/gtest/include/gtest/gtest.h"
#include "tools/gn/functions.h"
#include "tools/gn/scheduler.h"
+#include "tools/gn/test_with_scheduler.h"
#include "tools/gn/test_with_scope.h"
-TEST(FunctionToolchain, RuntimeOutputs) {
- Scheduler scheduler;
+using FunctionToolchain = TestWithScheduler;
+
+TEST_F(FunctionToolchain, RuntimeOutputs) {
TestWithScope setup;
// These runtime outputs are a subset of the outputs so are OK.
diff --git a/tools/gn/function_write_file_unittest.cc b/tools/gn/function_write_file_unittest.cc
index 8f2b42a..db2ea1e 100644
--- a/tools/gn/function_write_file_unittest.cc
+++ b/tools/gn/function_write_file_unittest.cc
@@ -10,6 +10,7 @@
#include "testing/gtest/include/gtest/gtest.h"
#include "tools/gn/functions.h"
#include "tools/gn/scheduler.h"
+#include "tools/gn/test_with_scheduler.h"
#include "tools/gn/test_with_scope.h"
namespace {
@@ -33,8 +34,9 @@
} // namespace
-TEST(WriteFile, WithData) {
- Scheduler scheduler;
+using WriteFileTest = TestWithScheduler;
+
+TEST_F(WriteFileTest, WithData) {
TestWithScope setup;
// Make a real directory for writing the files.
diff --git a/tools/gn/functions_target_unittest.cc b/tools/gn/functions_target_unittest.cc
index cef7517..de701ef 100644
--- a/tools/gn/functions_target_unittest.cc
+++ b/tools/gn/functions_target_unittest.cc
@@ -5,12 +5,13 @@
#include "testing/gtest/include/gtest/gtest.h"
#include "tools/gn/scheduler.h"
#include "tools/gn/scope.h"
+#include "tools/gn/test_with_scheduler.h"
#include "tools/gn/test_with_scope.h"
+using FunctionsTarget = TestWithScheduler;
// Checks that we find unused identifiers in targets.
-TEST(FunctionsTarget, CheckUnused) {
- Scheduler scheduler;
+TEST_F(FunctionsTarget, CheckUnused) {
TestWithScope setup;
// The target generator needs a place to put the targets or it will fail.
@@ -38,8 +39,7 @@
}
// Checks that we find uses of identifiers marked as not needed.
-TEST(FunctionsTarget, CheckNotNeeded) {
- Scheduler scheduler;
+TEST_F(FunctionsTarget, CheckNotNeeded) {
TestWithScope setup;
// The target generator needs a place to put the targets or it will fail.
@@ -93,8 +93,7 @@
// Checks that the defaults applied to a template invoked by target() use
// the name of the template, rather than the string "target" (which is the
// name of the actual function being called).
-TEST(FunctionsTarget, TemplateDefaults) {
- Scheduler scheduler;
+TEST_F(FunctionsTarget, TemplateDefaults) {
TestWithScope setup;
// The target generator needs a place to put the targets or it will fail.
diff --git a/tools/gn/gn_main.cc b/tools/gn/gn_main.cc
index 245270e..4d51158 100644
--- a/tools/gn/gn_main.cc
+++ b/tools/gn/gn_main.cc
@@ -2,9 +2,16 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+#include <algorithm>
+#include <string>
+
#include "base/at_exit.h"
#include "base/command_line.h"
+#include "base/message_loop/message_loop.h"
+#include "base/strings/string_number_conversions.h"
#include "base/strings/utf_string_conversions.h"
+#include "base/sys_info.h"
+#include "base/task_scheduler/task_scheduler.h"
#include "build/build_config.h"
#include "tools/gn/commands.h"
#include "tools/gn/err.h"
@@ -34,6 +41,54 @@
#endif
}
+int GetThreadCount() {
+ std::string thread_count =
+ base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
+ switches::kThreads);
+
+ // See if an override was specified on the command line.
+ int result;
+ if (!thread_count.empty() && base::StringToInt(thread_count, &result) &&
+ result >= 1) {
+ return result;
+ }
+
+ // Base the default number of worker threads on number of cores in the
+ // system. When building large projects, the speed can be limited by how fast
+ // the main thread can dispatch work and connect the dependency graph. If
+ // there are too many worker threads, the main thread can be starved and it
+ // will run slower overall.
+ //
+ // One less worker thread than the number of physical CPUs seems to be a
+ // good value, both theoretically and experimentally. But always use at
+ // least some workers to prevent us from being too sensitive to I/O latency
+ // on low-end systems.
+ //
+ // The minimum thread count is based on measuring the optimal threads for the
+ // Chrome build on a several-year-old 4-core MacBook.
+ // Almost all CPUs now are hyperthreaded.
+ int num_cores = base::SysInfo::NumberOfProcessors() / 2;
+ return std::max(num_cores - 1, 8);
+}
+
+void StartTaskScheduler() {
+ constexpr base::TimeDelta kSuggestedReclaimTime =
+ base::TimeDelta::FromSeconds(30);
+
+ constexpr int kBackgroundMaxThreads = 1;
+ constexpr int kBackgroundBlockingMaxThreads = 2;
+ const int kForegroundMaxThreads =
+ std::max(1, base::SysInfo::NumberOfProcessors());
+ const int foreground_blocking_max_threads = GetThreadCount();
+
+ base::TaskScheduler::Create("gn");
+ base::TaskScheduler::GetInstance()->Start(
+ {{kBackgroundMaxThreads, kSuggestedReclaimTime},
+ {kBackgroundBlockingMaxThreads, kSuggestedReclaimTime},
+ {kForegroundMaxThreads, kSuggestedReclaimTime},
+ {foreground_blocking_max_threads, kSuggestedReclaimTime}});
+}
+
} // namespace
int main(int argc, char** argv) {
@@ -71,7 +126,10 @@
int retval;
if (found_command != command_map.end()) {
+ base::MessageLoop message_loop;
+ StartTaskScheduler();
retval = found_command->second.runner(args);
+ base::TaskScheduler::GetInstance()->Shutdown();
} else {
Err(Location(), "Command \"" + command + "\" unknown.").PrintToStdout();
OutputString(
diff --git a/tools/gn/header_checker.cc b/tools/gn/header_checker.cc
index 30ab4c1..39d53ee 100644
--- a/tools/gn/header_checker.cc
+++ b/tools/gn/header_checker.cc
@@ -9,9 +9,8 @@
#include "base/bind.h"
#include "base/containers/queue.h"
#include "base/files/file_util.h"
-#include "base/message_loop/message_loop.h"
#include "base/strings/string_util.h"
-#include "base/threading/sequenced_worker_pool.h"
+#include "base/task_scheduler/post_task.h"
#include "tools/gn/build_settings.h"
#include "tools/gn/builder.h"
#include "tools/gn/c_include_iterator.h"
@@ -128,8 +127,7 @@
HeaderChecker::HeaderChecker(const BuildSettings* build_settings,
const std::vector<const Target*>& targets)
- : main_loop_(base::MessageLoop::current()),
- build_settings_(build_settings) {
+ : build_settings_(build_settings), task_count_cv_(&lock_) {
for (auto* target : targets)
AddTargetToFileMap(target, &file_map_);
}
@@ -151,11 +149,6 @@
}
void HeaderChecker::RunCheckOverFiles(const FileMap& files, bool force_check) {
- if (files.empty())
- return;
-
- scoped_refptr<base::SequencedWorkerPool> pool(new base::SequencedWorkerPool(
- 16, "HeaderChecker", base::TaskPriority::USER_VISIBLE));
for (const auto& file : files) {
// Only check C-like source files (RC files also have includes).
SourceFileType type = GetSourceFileType(file.first);
@@ -174,16 +167,17 @@
for (const auto& vect_i : file.second) {
if (vect_i.target->check_includes()) {
- pool->PostWorkerTaskWithShutdownBehavior(
- FROM_HERE,
- base::Bind(&HeaderChecker::DoWork, this, vect_i.target, file.first),
- base::SequencedWorkerPool::BLOCK_SHUTDOWN);
+ base::PostTaskWithTraits(FROM_HERE, {base::MayBlock()},
+ base::BindOnce(&HeaderChecker::DoWork, this,
+ vect_i.target, file.first));
}
}
}
- // After this call we're single-threaded again.
- pool->Shutdown();
+ // Wait for all tasks posted by this method to complete.
+ base::AutoLock auto_lock(lock_);
+ while (!task_count_.IsZero())
+ task_count_cv_.Wait();
}
void HeaderChecker::DoWork(const Target* target, const SourceFile& file) {
@@ -192,6 +186,12 @@
base::AutoLock lock(lock_);
errors_.push_back(err);
}
+
+ if (!task_count_.Decrement()) {
+ // Signal |task_count_cv_| when |task_count_| becomes zero.
+ base::AutoLock auto_lock(lock_);
+ task_count_cv_.Signal();
+ }
}
// static
diff --git a/tools/gn/header_checker.h b/tools/gn/header_checker.h
index 0c0eb4a..c6411eb 100644
--- a/tools/gn/header_checker.h
+++ b/tools/gn/header_checker.h
@@ -6,14 +6,14 @@
#define TOOLS_GN_HEADER_CHECKER_H_
#include <map>
-#include <set>
#include <vector>
+#include "base/atomic_ref_count.h"
#include "base/gtest_prod_util.h"
#include "base/macros.h"
#include "base/memory/ref_counted.h"
-#include "base/run_loop.h"
#include "base/strings/string_piece.h"
+#include "base/synchronization/condition_variable.h"
#include "base/synchronization/lock.h"
#include "tools/gn/err.h"
@@ -23,10 +23,6 @@
class SourceFile;
class Target;
-namespace base {
-class MessageLoop;
-}
-
class HeaderChecker : public base::RefCountedThreadSafe<HeaderChecker> {
public:
// Represents a dependency chain.
@@ -160,14 +156,15 @@
// These are initialized during construction (which happens on one thread)
// and are not modified after, so any thread can read these without locking.
- base::MessageLoop* main_loop_;
- base::RunLoop main_thread_runner_;
-
const BuildSettings* build_settings_;
// Maps source files to targets it appears in (usually just one target).
FileMap file_map_;
+ // Number of tasks posted by RunCheckOverFiles() that haven't completed their
+ // execution.
+ base::AtomicRefCount task_count_;
+
// Locked variables ----------------------------------------------------------
//
// These are mutable during runtime and require locking.
@@ -176,6 +173,9 @@
std::vector<Err> errors_;
+ // Signaled when |task_count_| becomes zero.
+ base::ConditionVariable task_count_cv_;
+
DISALLOW_COPY_AND_ASSIGN(HeaderChecker);
};
diff --git a/tools/gn/header_checker_unittest.cc b/tools/gn/header_checker_unittest.cc
index 84550b4..2aa0860 100644
--- a/tools/gn/header_checker_unittest.cc
+++ b/tools/gn/header_checker_unittest.cc
@@ -9,11 +9,12 @@
#include "tools/gn/header_checker.h"
#include "tools/gn/scheduler.h"
#include "tools/gn/target.h"
+#include "tools/gn/test_with_scheduler.h"
#include "tools/gn/test_with_scope.h"
namespace {
-class HeaderCheckerTest : public testing::Test {
+class HeaderCheckerTest : public TestWithScheduler {
public:
HeaderCheckerTest()
: a_(setup_.settings(), Label(SourceDir("//a/"), "a")),
@@ -52,8 +53,6 @@
}
protected:
- Scheduler scheduler_;
-
TestWithScope setup_;
// Some headers that are automatically set up with a public dependency chain.
diff --git a/tools/gn/input_conversion_unittest.cc b/tools/gn/input_conversion_unittest.cc
index a2cfe4d..9568daf 100644
--- a/tools/gn/input_conversion_unittest.cc
+++ b/tools/gn/input_conversion_unittest.cc
@@ -2,19 +2,21 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+#include "tools/gn/input_conversion.h"
+
#include "testing/gtest/include/gtest/gtest.h"
#include "tools/gn/err.h"
-#include "tools/gn/input_conversion.h"
#include "tools/gn/input_file.h"
#include "tools/gn/parse_tree.h"
#include "tools/gn/scheduler.h"
+#include "tools/gn/test_with_scheduler.h"
#include "tools/gn/test_with_scope.h"
#include "tools/gn/value.h"
namespace {
// InputConversion needs a global scheduler object.
-class InputConversionTest : public testing::Test {
+class InputConversionTest : public TestWithScheduler {
public:
InputConversionTest() = default;
@@ -22,8 +24,6 @@
private:
TestWithScope setup_;
-
- Scheduler scheduler_;
};
} // namespace
diff --git a/tools/gn/loader_unittest.cc b/tools/gn/loader_unittest.cc
index 4b240c3..e42ee5b 100644
--- a/tools/gn/loader_unittest.cc
+++ b/tools/gn/loader_unittest.cc
@@ -16,6 +16,7 @@
#include "tools/gn/parse_tree.h"
#include "tools/gn/parser.h"
#include "tools/gn/scheduler.h"
+#include "tools/gn/test_with_scheduler.h"
#include "tools/gn/tokenizer.h"
namespace {
@@ -139,14 +140,13 @@
// LoaderTest ------------------------------------------------------------------
-class LoaderTest : public testing::Test {
+class LoaderTest : public TestWithScheduler {
public:
LoaderTest() {
build_settings_.SetBuildDir(SourceDir("//out/Debug/"));
}
protected:
- Scheduler scheduler_;
BuildSettings build_settings_;
MockBuilder mock_builder_;
MockInputFileManager mock_ifm_;
@@ -212,7 +212,7 @@
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(mock_ifm_.HasTwoPending(second_file, third_file));
- EXPECT_FALSE(scheduler_.is_failed());
+ EXPECT_FALSE(scheduler().is_failed());
}
TEST_F(LoaderTest, BuildDependencyFilesAreCollected) {
@@ -260,5 +260,5 @@
EXPECT_TRUE(items[3]->AsPool());
EXPECT_TRUE(ItemContainsBuildDependencyFile(items[3], root_build));
- EXPECT_FALSE(scheduler_.is_failed());
+ EXPECT_FALSE(scheduler().is_failed());
}
diff --git a/tools/gn/ninja_binary_target_writer_unittest.cc b/tools/gn/ninja_binary_target_writer_unittest.cc
index 8700869..e801723 100644
--- a/tools/gn/ninja_binary_target_writer_unittest.cc
+++ b/tools/gn/ninja_binary_target_writer_unittest.cc
@@ -12,9 +12,12 @@
#include "tools/gn/config.h"
#include "tools/gn/scheduler.h"
#include "tools/gn/target.h"
+#include "tools/gn/test_with_scheduler.h"
#include "tools/gn/test_with_scope.h"
-TEST(NinjaBinaryTargetWriter, SourceSet) {
+using NinjaBinaryTargetWriterTest = TestWithScheduler;
+
+TEST_F(NinjaBinaryTargetWriterTest, SourceSet) {
Err err;
TestWithScope setup;
@@ -147,7 +150,7 @@
}
}
-TEST(NinjaBinaryTargetWriter, StaticLibrary) {
+TEST_F(NinjaBinaryTargetWriterTest, StaticLibrary) {
TestWithScope setup;
Err err;
@@ -179,7 +182,7 @@
EXPECT_EQ(expected, out_str);
}
-TEST(NinjaBinaryTargetWriter, CompleteStaticLibrary) {
+TEST_F(NinjaBinaryTargetWriterTest, CompleteStaticLibrary) {
TestWithScope setup;
Err err;
@@ -256,7 +259,7 @@
// This tests that output extension and output dir overrides apply, and input
// dependencies are applied.
-TEST(NinjaBinaryTargetWriter, OutputExtensionAndInputDeps) {
+TEST_F(NinjaBinaryTargetWriterTest, OutputExtensionAndInputDeps) {
Err err;
TestWithScope setup;
@@ -311,7 +314,7 @@
}
// Tests libs are applied.
-TEST(NinjaBinaryTargetWriter, LibsAndLibDirs) {
+TEST_F(NinjaBinaryTargetWriterTest, LibsAndLibDirs) {
Err err;
TestWithScope setup;
@@ -346,7 +349,7 @@
EXPECT_EQ(expected, out_str);
}
-TEST(NinjaBinaryTargetWriter, EmptyOutputExtension) {
+TEST_F(NinjaBinaryTargetWriterTest, EmptyOutputExtension) {
Err err;
TestWithScope setup;
@@ -390,7 +393,7 @@
EXPECT_EQ(expected, out_str);
}
-TEST(NinjaBinaryTargetWriter, SourceSetDataDeps) {
+TEST_F(NinjaBinaryTargetWriterTest, SourceSetDataDeps) {
Err err;
TestWithScope setup;
@@ -470,7 +473,7 @@
EXPECT_EQ(final_expected, final_out.str());
}
-TEST(NinjaBinaryTargetWriter, SharedLibraryModuleDefinitionFile) {
+TEST_F(NinjaBinaryTargetWriterTest, SharedLibraryModuleDefinitionFile) {
Err err;
TestWithScope setup;
@@ -504,7 +507,7 @@
EXPECT_EQ(expected, out.str());
}
-TEST(NinjaBinaryTargetWriter, LoadableModule) {
+TEST_F(NinjaBinaryTargetWriterTest, LoadableModule) {
Err err;
TestWithScope setup;
@@ -570,7 +573,7 @@
EXPECT_EQ(final_expected, final_out.str());
}
-TEST(NinjaBinaryTargetWriter, WinPrecompiledHeaders) {
+TEST_F(NinjaBinaryTargetWriterTest, WinPrecompiledHeaders) {
Err err;
// This setup's toolchain does not have precompiled headers defined.
@@ -697,7 +700,7 @@
}
}
-TEST(NinjaBinaryTargetWriter, GCCPrecompiledHeaders) {
+TEST_F(NinjaBinaryTargetWriterTest, GCCPrecompiledHeaders) {
Err err;
// This setup's toolchain does not have precompiled headers defined.
@@ -822,27 +825,25 @@
// Should throw an error with the scheduler if a duplicate object file exists.
// This is dependent on the toolchain's object file mapping.
-TEST(NinjaBinaryTargetWriter, DupeObjFileError) {
- Scheduler scheduler;
-
+TEST_F(NinjaBinaryTargetWriterTest, DupeObjFileError) {
TestWithScope setup;
TestTarget target(setup, "//foo:bar", Target::EXECUTABLE);
target.sources().push_back(SourceFile("//a.cc"));
target.sources().push_back(SourceFile("//a.cc"));
- EXPECT_FALSE(scheduler.is_failed());
+ EXPECT_FALSE(scheduler().is_failed());
std::ostringstream out;
NinjaBinaryTargetWriter writer(&target, out);
writer.Run();
// Should have issued an error.
- EXPECT_TRUE(scheduler.is_failed());
+ EXPECT_TRUE(scheduler().is_failed());
}
// This tests that output extension and output dir overrides apply, and input
// dependencies are applied.
-TEST(NinjaBinaryTargetWriter, InputFiles) {
+TEST_F(NinjaBinaryTargetWriterTest, InputFiles) {
Err err;
TestWithScope setup;
diff --git a/tools/gn/ninja_build_writer_unittest.cc b/tools/gn/ninja_build_writer_unittest.cc
index 6dac481..1937135 100644
--- a/tools/gn/ninja_build_writer_unittest.cc
+++ b/tools/gn/ninja_build_writer_unittest.cc
@@ -9,10 +9,12 @@
#include "tools/gn/pool.h"
#include "tools/gn/scheduler.h"
#include "tools/gn/target.h"
+#include "tools/gn/test_with_scheduler.h"
#include "tools/gn/test_with_scope.h"
-TEST(NinjaBuildWriter, TwoTargets) {
- Scheduler scheduler;
+using NinjaBuildWriterTest = TestWithScheduler;
+
+TEST_F(NinjaBuildWriterTest, TwoTargets) {
TestWithScope setup;
Err err;
@@ -95,8 +97,7 @@
#undef EXPECT_SNIPPET
}
-TEST(NinjaBuildWriter, DuplicateOutputs) {
- Scheduler scheduler;
+TEST_F(NinjaBuildWriterTest, DuplicateOutputs) {
TestWithScope setup;
Err err;
diff --git a/tools/gn/runtime_deps_unittest.cc b/tools/gn/runtime_deps_unittest.cc
index 5dc89eb..bb5d46f 100644
--- a/tools/gn/runtime_deps_unittest.cc
+++ b/tools/gn/runtime_deps_unittest.cc
@@ -9,6 +9,7 @@
#include "tools/gn/runtime_deps.h"
#include "tools/gn/scheduler.h"
#include "tools/gn/target.h"
+#include "tools/gn/test_with_scheduler.h"
#include "tools/gn/test_with_scope.h"
namespace {
@@ -40,8 +41,10 @@
} // namespace
+using RuntimeDeps = TestWithScheduler;
+
// Tests an exe depending on different types of libraries.
-TEST(RuntimeDeps, Libs) {
+TEST_F(RuntimeDeps, Libs) {
TestWithScope setup;
Err err;
@@ -113,7 +116,7 @@
// Tests that executables that aren't listed as data deps aren't included in
// the output, but executables that are data deps are included.
-TEST(RuntimeDeps, ExeDataDep) {
+TEST_F(RuntimeDeps, ExeDataDep) {
TestWithScope setup;
Err err;
@@ -165,7 +168,7 @@
<< GetVectorDescription(result);
}
-TEST(RuntimeDeps, ActionSharedLib) {
+TEST_F(RuntimeDeps, ActionSharedLib) {
TestWithScope setup;
Err err;
@@ -208,7 +211,7 @@
// Tests that action and copy outputs are considered if they're data deps, but
// not if they're regular deps. Action and copy "data" files are always
// included.
-TEST(RuntimeDeps, ActionOutputs) {
+TEST_F(RuntimeDeps, ActionOutputs) {
TestWithScope setup;
Err err;
@@ -294,7 +297,7 @@
// Tests that the search for dependencies terminates at a bundle target,
// ignoring any shared libraries or loadable modules that get copied into the
// bundle.
-TEST(RuntimeDeps, CreateBundle) {
+TEST_F(RuntimeDeps, CreateBundle) {
TestWithScope setup;
Err err;
@@ -393,7 +396,7 @@
// Tests that a dependency duplicated in regular and data deps is processed
// as a data dep.
-TEST(RuntimeDeps, Dupe) {
+TEST_F(RuntimeDeps, Dupe) {
TestWithScope setup;
Err err;
@@ -418,8 +421,7 @@
}
// Tests that actions can't have output substitutions.
-TEST(RuntimeDeps, WriteRuntimeDepsVariable) {
- Scheduler scheduler;
+TEST_F(RuntimeDeps, WriteRuntimeDepsVariable) {
TestWithScope setup;
Err err;
@@ -442,5 +444,5 @@
" group(\"bar\") { write_runtime_deps = \"//out/Debug/bar.txt\" }\n"
"}", &err));
EXPECT_EQ(1U, setup.items().size());
- EXPECT_EQ(1U, scheduler.GetWriteRuntimeDepsTargets().size());
+ EXPECT_EQ(1U, scheduler().GetWriteRuntimeDepsTargets().size());
}
diff --git a/tools/gn/scheduler.cc b/tools/gn/scheduler.cc
index 5327792..cf66dc7 100644
--- a/tools/gn/scheduler.cc
+++ b/tools/gn/scheduler.cc
@@ -7,80 +7,26 @@
#include <algorithm>
#include "base/bind.h"
-#include "base/command_line.h"
#include "base/single_thread_task_runner.h"
-#include "base/strings/string_number_conversions.h"
-#include "build/build_config.h"
+#include "base/task_scheduler/post_task.h"
+#include "base/threading/thread_task_runner_handle.h"
#include "tools/gn/standard_out.h"
-#include "tools/gn/switches.h"
#include "tools/gn/target.h"
-#if defined(OS_WIN)
-#include <windows.h>
-#else
-#include <unistd.h>
-#endif
-
Scheduler* g_scheduler = nullptr;
-namespace {
-
-#if defined(OS_WIN)
-int GetCPUCount() {
- SYSTEM_INFO sysinfo;
- ::GetSystemInfo(&sysinfo);
- return sysinfo.dwNumberOfProcessors;
-}
-#else
-int GetCPUCount() {
- return static_cast<int>(sysconf(_SC_NPROCESSORS_ONLN));
-}
-#endif
-
-int GetThreadCount() {
- std::string thread_count =
- base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
- switches::kThreads);
-
- // See if an override was specified on the command line.
- int result;
- if (!thread_count.empty() && base::StringToInt(thread_count, &result))
- return result;
-
- // Base the default number of worker threads on number of cores in the
- // system. When building large projects, the speed can be limited by how fast
- // the main thread can dispatch work and connect the dependency graph. If
- // there are too many worker threads, the main thread can be starved and it
- // will run slower overall.
- //
- // One less worker thread than the number of physical CPUs seems to be a
- // good value, both theoretically and experimentally. But always use at
- // least some workers to prevent us from being too sensitive to I/O latency
- // on low-end systems.
- //
- // The minimum thread count is based on measuring the optimal threads for the
- // Chrome build on a several-year-old 4-core MacBook.
- int num_cores = GetCPUCount() / 2; // Almost all CPUs now are hyperthreaded.
- return std::max(num_cores - 1, 8);
-}
-
-} // namespace
-
Scheduler::Scheduler()
- : pool_(new base::SequencedWorkerPool(GetThreadCount(),
- "worker_",
- base::TaskPriority::USER_VISIBLE)),
+ : main_thread_task_runner_(base::ThreadTaskRunnerHandle::Get()),
input_file_manager_(new InputFileManager),
verbose_logging_(false),
- work_count_(0),
+ pool_work_count_cv_(&pool_work_count_lock_),
is_failed_(false),
has_been_shutdown_(false) {
g_scheduler = this;
}
Scheduler::~Scheduler() {
- if (!has_been_shutdown_)
- pool_->Shutdown();
+ WaitForPoolTasks();
g_scheduler = nullptr;
}
@@ -92,9 +38,9 @@
local_is_failed = is_failed();
has_been_shutdown_ = true;
}
- // Don't do this inside the lock since it will block on the workers, which
- // may be in turn waiting on the lock.
- pool_->Shutdown();
+ // Don't do this while holding |lock_|, since it will block on the workers,
+ // which may be in turn waiting on the lock.
+ WaitForPoolTasks();
return !local_is_failed;
}
@@ -131,12 +77,13 @@
}
}
-void Scheduler::ScheduleWork(const base::Closure& work) {
+void Scheduler::ScheduleWork(base::OnceClosure work) {
IncrementWorkCount();
- pool_->PostWorkerTaskWithShutdownBehavior(
- FROM_HERE, base::Bind(&Scheduler::DoWork,
- base::Unretained(this), work),
- base::SequencedWorkerPool::BLOCK_SHUTDOWN);
+ pool_work_count_.Increment();
+ base::PostTaskWithTraits(
+ FROM_HERE, {base::MayBlock()},
+ base::BindOnce(&Scheduler::DoWork, base::Unretained(this),
+ std::move(work)));
}
void Scheduler::AddGenDependency(const base::FilePath& file) {
@@ -230,9 +177,13 @@
runner_.Quit();
}
-void Scheduler::DoWork(const base::Closure& closure) {
- closure.Run();
+void Scheduler::DoWork(base::OnceClosure closure) {
+ std::move(closure).Run();
DecrementWorkCount();
+ if (!pool_work_count_.Decrement()) {
+ base::AutoLock auto_lock(pool_work_count_lock_);
+ pool_work_count_cv_.Signal();
+ }
}
void Scheduler::OnComplete() {
@@ -240,3 +191,9 @@
DCHECK(task_runner()->BelongsToCurrentThread());
runner_.Quit();
}
+
+void Scheduler::WaitForPoolTasks() {
+ base::AutoLock lock(pool_work_count_lock_);
+ while (!pool_work_count_.IsZero())
+ pool_work_count_cv_.Wait();
+}
diff --git a/tools/gn/scheduler.h b/tools/gn/scheduler.h
index e20bced..b0732ae 100644
--- a/tools/gn/scheduler.h
+++ b/tools/gn/scheduler.h
@@ -13,8 +13,8 @@
#include "base/message_loop/message_loop.h"
#include "base/run_loop.h"
#include "base/single_thread_task_runner.h"
+#include "base/synchronization/condition_variable.h"
#include "base/synchronization/lock.h"
-#include "base/threading/sequenced_worker_pool.h"
#include "tools/gn/input_file_manager.h"
#include "tools/gn/label.h"
#include "tools/gn/source_file.h"
@@ -31,7 +31,7 @@
bool Run();
scoped_refptr<base::SingleThreadTaskRunner> task_runner() {
- return main_loop_.task_runner();
+ return main_thread_task_runner_;
}
InputFileManager* input_file_manager() { return input_file_manager_.get(); }
@@ -45,7 +45,7 @@
void Log(const std::string& verb, const std::string& msg);
void FailWithError(const Err& err);
- void ScheduleWork(const base::Closure& work);
+ void ScheduleWork(base::OnceClosure work);
void Shutdown();
@@ -97,12 +97,15 @@
void DoTargetFileWrite(const Target* target);
- void DoWork(const base::Closure& closure);
+ void DoWork(base::OnceClosure closure);
void OnComplete();
- base::MessageLoop main_loop_;
- scoped_refptr<base::SequencedWorkerPool> pool_;
+ // Waits for tasks scheduled via ScheduleWork() to complete their execution.
+ void WaitForPoolTasks();
+
+ // TaskRunner for the thread on which the Scheduler is initialized.
+ const scoped_refptr<base::SingleThreadTaskRunner> main_thread_task_runner_;
scoped_refptr<InputFileManager> input_file_manager_;
@@ -112,6 +115,16 @@
base::AtomicRefCount work_count_;
+ // Number of tasks scheduled by ScheduleWork() that haven't completed their
+ // execution.
+ base::AtomicRefCount pool_work_count_;
+
+ // Lock for |pool_work_count_cv_|.
+ base::Lock pool_work_count_lock_;
+
+ // Condition variable signaled when |pool_work_count_| reaches zero.
+ base::ConditionVariable pool_work_count_cv_;
+
mutable base::Lock lock_;
bool is_failed_;
diff --git a/tools/gn/target.h b/tools/gn/target.h
index de5c13d..b286518 100644
--- a/tools/gn/target.h
+++ b/tools/gn/target.h
@@ -309,7 +309,7 @@
std::vector<OutputFile>* outputs) const;
private:
- FRIEND_TEST_ALL_PREFIXES(Target, ResolvePrecompiledHeaders);
+ FRIEND_TEST_ALL_PREFIXES(TargetTest, ResolvePrecompiledHeaders);
// Pulls necessary information from dependencies to this one when all
// dependencies have been resolved.
diff --git a/tools/gn/target_unittest.cc b/tools/gn/target_unittest.cc
index bee46f1..31d3382 100644
--- a/tools/gn/target_unittest.cc
+++ b/tools/gn/target_unittest.cc
@@ -12,6 +12,7 @@
#include "tools/gn/config.h"
#include "tools/gn/scheduler.h"
#include "tools/gn/settings.h"
+#include "tools/gn/test_with_scheduler.h"
#include "tools/gn/test_with_scope.h"
#include "tools/gn/toolchain.h"
@@ -33,9 +34,11 @@
} // namespace
+using TargetTest = TestWithScheduler;
+
// Tests that lib[_dir]s are inherited across deps boundaries for static
// libraries but not executables.
-TEST(Target, LibInheritance) {
+TEST_F(TargetTest, LibInheritance) {
TestWithScope setup;
Err err;
@@ -80,7 +83,7 @@
}
// Test all_dependent_configs and public_config inheritance.
-TEST(Target, DependentConfigs) {
+TEST_F(TargetTest, DependentConfigs) {
TestWithScope setup;
Err err;
@@ -139,7 +142,7 @@
}
// Tests that dependent configs don't propagate between toolchains.
-TEST(Target, NoDependentConfigsBetweenToolchains) {
+TEST_F(TargetTest, NoDependentConfigsBetweenToolchains) {
TestWithScope setup;
Err err;
@@ -193,7 +196,7 @@
ASSERT_EQ(0u, a.all_dependent_configs().size());
}
-TEST(Target, InheritLibs) {
+TEST_F(TargetTest, InheritLibs) {
TestWithScope setup;
Err err;
@@ -230,7 +233,7 @@
EXPECT_EQ(&b, a_inherited[0]);
}
-TEST(Target, InheritCompleteStaticLib) {
+TEST_F(TargetTest, InheritCompleteStaticLib) {
TestWithScope setup;
Err err;
@@ -271,7 +274,7 @@
EXPECT_EQ(lib_dir, a.all_lib_dirs()[0]);
}
-TEST(Target, InheritCompleteStaticLibStaticLibDeps) {
+TEST_F(TargetTest, InheritCompleteStaticLibStaticLibDeps) {
TestWithScope setup;
Err err;
@@ -300,7 +303,7 @@
EXPECT_EQ(&b, a_inherited[0]);
}
-TEST(Target, InheritCompleteStaticLibInheritedCompleteStaticLibDeps) {
+TEST_F(TargetTest, InheritCompleteStaticLibInheritedCompleteStaticLibDeps) {
TestWithScope setup;
Err err;
@@ -331,7 +334,7 @@
EXPECT_EQ(&c, a_inherited[1]);
}
-TEST(Target, NoActionDepPropgation) {
+TEST_F(TargetTest, NoActionDepPropgation) {
TestWithScope setup;
Err err;
@@ -356,7 +359,7 @@
}
}
-TEST(Target, GetComputedOutputName) {
+TEST_F(TargetTest, GetComputedOutputName) {
TestWithScope setup;
Err err;
@@ -393,7 +396,7 @@
}
// Test visibility failure case.
-TEST(Target, VisibilityFails) {
+TEST_F(TargetTest, VisibilityFails) {
TestWithScope setup;
Err err;
@@ -411,7 +414,7 @@
}
// Test visibility with a single data_dep.
-TEST(Target, VisibilityDatadeps) {
+TEST_F(TargetTest, VisibilityDatadeps) {
TestWithScope setup;
Err err;
@@ -429,7 +432,7 @@
// Tests that A -> Group -> B where the group is visible from A but B isn't,
// passes visibility even though the group's deps get expanded into A.
-TEST(Target, VisibilityGroup) {
+TEST_F(TargetTest, VisibilityGroup) {
TestWithScope setup;
Err err;
@@ -457,7 +460,7 @@
// Verifies that only testonly targets can depend on other testonly targets.
// Many of the above dependency checking cases covered the non-testonly
// case.
-TEST(Target, Testonly) {
+TEST_F(TargetTest, Testonly) {
TestWithScope setup;
Err err;
@@ -479,7 +482,7 @@
ASSERT_FALSE(product.OnResolved(&err));
}
-TEST(Target, PublicConfigs) {
+TEST_F(TargetTest, PublicConfigs) {
TestWithScope setup;
Err err;
@@ -519,7 +522,7 @@
}
// Tests that configs are ordered properly between local and pulled ones.
-TEST(Target, ConfigOrdering) {
+TEST_F(TargetTest, ConfigOrdering) {
TestWithScope setup;
Err err;
@@ -584,7 +587,7 @@
}
// Tests that different link/depend outputs work for solink tools.
-TEST(Target, LinkAndDepOutputs) {
+TEST_F(TargetTest, LinkAndDepOutputs) {
TestWithScope setup;
Err err;
@@ -625,7 +628,7 @@
// Tests that runtime_outputs works without an explicit link_output for
// solink tools.
-TEST(Target, RuntimeOuputs) {
+TEST_F(TargetTest, RuntimeOuputs) {
TestWithScope setup;
Err err;
@@ -670,7 +673,7 @@
// Shared libraries should be inherited across public shared liobrary
// boundaries.
-TEST(Target, SharedInheritance) {
+TEST_F(TargetTest, SharedInheritance) {
TestWithScope setup;
Err err;
@@ -710,8 +713,7 @@
EXPECT_EQ(&pub, exe_inherited[1]);
}
-TEST(Target, GeneratedInputs) {
- Scheduler scheduler;
+TEST_F(TargetTest, GeneratedInputs) {
TestWithScope setup;
Err err;
@@ -724,7 +726,7 @@
EXPECT_TRUE(non_existent_generator.OnResolved(&err)) << err.message();
AssertSchedulerHasOneUnknownFileMatching(&non_existent_generator,
generated_file);
- scheduler.ClearUnknownGeneratedInputsAndWrittenFiles();
+ scheduler().ClearUnknownGeneratedInputsAndWrittenFiles();
// Make a target that generates the file.
TestTarget generator(setup, "//foo:generator", Target::ACTION);
@@ -740,7 +742,7 @@
existent_generator.sources().push_back(generated_file);
existent_generator.private_deps().push_back(LabelTargetPair(&generator));
EXPECT_TRUE(existent_generator.OnResolved(&err)) << err.message();
- EXPECT_TRUE(scheduler.GetUnknownGeneratedInputs().empty());
+ EXPECT_TRUE(scheduler().GetUnknownGeneratedInputs().empty());
// A target that depends on the previous one should *not* be allowed to
// use the generated file, because existent_generator used private deps.
@@ -753,7 +755,7 @@
LabelTargetPair(&existent_generator));
EXPECT_TRUE(indirect_private.OnResolved(&err));
AssertSchedulerHasOneUnknownFileMatching(&indirect_private, generated_file);
- scheduler.ClearUnknownGeneratedInputsAndWrittenFiles();
+ scheduler().ClearUnknownGeneratedInputsAndWrittenFiles();
// Now make a chain like the above but with all public deps, it should be OK.
TestTarget existent_public(setup, "//foo:existent_public",
@@ -765,12 +767,11 @@
indirect_public.sources().push_back(generated_file);
indirect_public.public_deps().push_back(LabelTargetPair(&existent_public));
EXPECT_TRUE(indirect_public.OnResolved(&err)) << err.message();
- EXPECT_TRUE(scheduler.GetUnknownGeneratedInputs().empty());
+ EXPECT_TRUE(scheduler().GetUnknownGeneratedInputs().empty());
}
// This is sort of a Scheduler test, but is related to the above test more.
-TEST(Target, WriteFileGeneratedInputs) {
- Scheduler scheduler;
+TEST_F(TargetTest, WriteFileGeneratedInputs) {
TestWithScope setup;
Err err;
@@ -783,21 +784,20 @@
EXPECT_TRUE(non_existent_generator.OnResolved(&err));
AssertSchedulerHasOneUnknownFileMatching(&non_existent_generator,
generated_file);
- scheduler.ClearUnknownGeneratedInputsAndWrittenFiles();
+ scheduler().ClearUnknownGeneratedInputsAndWrittenFiles();
// This target has a generated file and we've decared we write it.
TestTarget existent_generator(setup, "//foo:existent_generator",
Target::EXECUTABLE);
existent_generator.sources().push_back(generated_file);
EXPECT_TRUE(existent_generator.OnResolved(&err));
- scheduler.AddWrittenFile(generated_file);
+ scheduler().AddWrittenFile(generated_file);
// Should be OK.
- EXPECT_TRUE(scheduler.GetUnknownGeneratedInputs().empty());
+ EXPECT_TRUE(scheduler().GetUnknownGeneratedInputs().empty());
}
-TEST(Target, WriteRuntimeDepsGeneratedInputs) {
- Scheduler scheduler;
+TEST_F(TargetTest, WriteRuntimeDepsGeneratedInputs) {
TestWithScope setup;
Err err;
@@ -816,14 +816,14 @@
dep_missing.sources().push_back(source_file);
EXPECT_TRUE(dep_missing.OnResolved(&err));
AssertSchedulerHasOneUnknownFileMatching(&dep_missing, source_file);
- scheduler.ClearUnknownGeneratedInputsAndWrittenFiles();
+ scheduler().ClearUnknownGeneratedInputsAndWrittenFiles();
// This target has a generated file and we've directly dependended on it.
TestTarget dep_present(setup, "//foo:with_dep", Target::EXECUTABLE);
dep_present.sources().push_back(source_file);
dep_present.private_deps().push_back(LabelTargetPair(&generator));
EXPECT_TRUE(dep_present.OnResolved(&err));
- EXPECT_TRUE(scheduler.GetUnknownGeneratedInputs().empty());
+ EXPECT_TRUE(scheduler().GetUnknownGeneratedInputs().empty());
// This target has a generated file and we've indirectly dependended on it
// via data_deps.
@@ -832,7 +832,7 @@
dep_indirect.data_deps().push_back(LabelTargetPair(&middle_data_dep));
EXPECT_TRUE(dep_indirect.OnResolved(&err));
AssertSchedulerHasOneUnknownFileMatching(&dep_indirect, source_file);
- scheduler.ClearUnknownGeneratedInputsAndWrittenFiles();
+ scheduler().ClearUnknownGeneratedInputsAndWrittenFiles();
// This target has a generated file and we've directly dependended on it
// via data_deps.
@@ -840,15 +840,14 @@
data_dep_present.sources().push_back(source_file);
data_dep_present.data_deps().push_back(LabelTargetPair(&generator));
EXPECT_TRUE(data_dep_present.OnResolved(&err));
- EXPECT_TRUE(scheduler.GetUnknownGeneratedInputs().empty());
+ EXPECT_TRUE(scheduler().GetUnknownGeneratedInputs().empty());
}
// Tests that intermediate object files generated by binary targets are also
// considered generated for the purposes of input checking. Above, we tested
// the failure cases for generated inputs, so here only test .o files that are
// present.
-TEST(Target, ObjectGeneratedInputs) {
- Scheduler scheduler;
+TEST_F(TargetTest, ObjectGeneratedInputs) {
TestWithScope setup;
Err err;
@@ -868,7 +867,7 @@
AssertSchedulerHasOneUnknownFileMatching(&final_target, object_file);
}
-TEST(Target, ResolvePrecompiledHeaders) {
+TEST_F(TargetTest, ResolvePrecompiledHeaders) {
TestWithScope setup;
Err err;
@@ -886,7 +885,7 @@
ASSERT_TRUE(config_1.OnResolved(&err));
target.configs().push_back(LabelConfigPair(&config_1));
- // No PCH info specified on target, but the config specifies one, the
+ // No PCH info specified on TargetTest, but the config specifies one, the
// values should get copied to the target.
EXPECT_TRUE(target.ResolvePrecompiledHeaders(&err));
EXPECT_EQ(pch_1, target.config_values().precompiled_header());
@@ -925,7 +924,7 @@
err.help_text());
}
-TEST(Target, AssertNoDeps) {
+TEST_F(TargetTest, AssertNoDeps) {
TestWithScope setup;
Err err;
@@ -980,7 +979,7 @@
ASSERT_TRUE(a2.OnResolved(&err));
}
-TEST(Target, PullRecursiveBundleData) {
+TEST_F(TargetTest, PullRecursiveBundleData) {
TestWithScope setup;
Err err;
diff --git a/tools/gn/test_with_scheduler.cc b/tools/gn/test_with_scheduler.cc
new file mode 100644
index 0000000..ce7a90c
--- /dev/null
+++ b/tools/gn/test_with_scheduler.cc
@@ -0,0 +1,8 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "tools/gn/test_with_scheduler.h"
+
+TestWithScheduler::TestWithScheduler() = default;
+TestWithScheduler::~TestWithScheduler() = default;
diff --git a/tools/gn/test_with_scheduler.h b/tools/gn/test_with_scheduler.h
new file mode 100644
index 0000000..230f69e
--- /dev/null
+++ b/tools/gn/test_with_scheduler.h
@@ -0,0 +1,27 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TOOLS_GN_TEST_WITH_SCHEDULER_H_
+#define TOOLS_GN_TEST_WITH_SCHEDULER_H_
+
+#include "base/macros.h"
+#include "base/test/scoped_task_environment.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "tools/gn/scheduler.h"
+
+class TestWithScheduler : public testing::Test {
+ protected:
+ TestWithScheduler();
+ ~TestWithScheduler() override;
+
+ Scheduler& scheduler() { return scheduler_; }
+
+ private:
+ base::test::ScopedTaskEnvironment scoped_task_environment_;
+ Scheduler scheduler_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestWithScheduler);
+};
+
+#endif // TOOLS_GN_TEST_WITH_SCHEDULER_H_