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_