Gabriel Charette | 0b20ee6 | 2019-09-18 14:06:12 | [diff] [blame] | 1 | # Testing Components Which Post Tasks |
| 2 | |
| 3 | [TOC] |
| 4 | |
| 5 | ## Overview |
| 6 | |
| 7 | So you've read the [Threading and Tasks] documentation, surveyed the associated |
| 8 | [Threading and Tasks FAQ] and have implemented a state-of-the-art component. Now |
| 9 | you want to test it :). This document will explain how to write matching |
| 10 | state-of-the-art tests. |
| 11 | |
| 12 | ## Task Environments |
| 13 | |
| 14 | In order to **unit test** a component which post tasks, you'll need to bring up |
| 15 | the task environment in the scope of your test (or test fixture). It will need |
| 16 | to outlive the majority of other members to ensure they have access to the task |
| 17 | system throughout their lifetime. There are a rare exceptions, like |
| 18 | `base::test::ScopedFeatureList`, that need to outlive the task environment. For |
| 19 | browser tests, see the [Browser tests](#browser-tests) section below. |
| 20 | |
| 21 | Task environments come in various forms but share the same fundamental |
| 22 | characteristics: |
| 23 | * There can be only one per test (if your base fixture already provides one: |
| 24 | see [Base Fixture managed |
| 25 | TaskEnvironment](#base-fixture-managed-taskenvironment) for the correct |
| 26 | paradigm to supplement it). |
| 27 | * Tasks cannot be posted outside the lifetime of a task environment. |
| 28 | * Posted tasks will be run or be destroyed before the end of |
| 29 | ~TaskEnvironment(). |
| 30 | * They all derive from `base::test::TaskEnvironment` and support its |
| 31 | [`ValidTraits`] and sometimes more. |
| 32 | * See usage example in [task_environment.h]. |
| 33 | * For example, a key characteristic is that its [TimeSource |
| 34 | trait](#timesource-trait) can be used to mock time to ease testing of timers, |
| 35 | timeouts, etc. |
| 36 | |
| 37 | The `TaskEnvironment` member is typically exposed in the protected section of |
| 38 | the test fixture to allow tests to drive it directly (there's no need to expose |
| 39 | public Run\*() methods that merely forward to the private member). |
| 40 | |
| 41 | ### base::test::SingleThreadTaskEnvironment |
| 42 | |
Sean Maher | 03efef1 | 2022-09-23 22:43:13 | [diff] [blame] | 43 | Your component uses `base::SingleThreadTaskRunner::GetCurrentDefault()` or |
| 44 | `base::SequencedTaskRunner::GetCurrentDefault()` to post tasks to the thread it |
| 45 | was created on? You'll need at least a `base::test::SingleThreadTaskEnvironment` |
| 46 | in order for these APIs to be functional and `base::RunLoop` to run the posted |
Gabriel Charette | 0b20ee6 | 2019-09-18 14:06:12 | [diff] [blame] | 47 | tasks. |
| 48 | |
| 49 | Typically this will look something like this: |
| 50 | |
| 51 | foo.h |
Raphael Kubo da Costa | 9d32d0f | 2019-09-26 17:39:48 | [diff] [blame] | 52 | ```c++ |
Gabriel Charette | 0b20ee6 | 2019-09-18 14:06:12 | [diff] [blame] | 53 | class Foo { |
| 54 | public: |
Sean Maher | 03efef1 | 2022-09-23 22:43:13 | [diff] [blame] | 55 | Foo() : owning_sequence_(base::SequencedTaskRunner::GetCurrentDefault()) {} |
Gabriel Charette | 0b20ee6 | 2019-09-18 14:06:12 | [diff] [blame] | 56 | |
Trent Begin | be87142 | 2020-03-25 23:06:54 | [diff] [blame] | 57 | DoSomethingAndReply(base::OnceClosure on_done) { |
Gabriel Charette | 0b20ee6 | 2019-09-18 14:06:12 | [diff] [blame] | 58 | DCHECK(owning_sequence_->RunsTasksInCurrentSequence()); |
| 59 | something_was_done_ = true; |
| 60 | owning_sequence_->PostTask(on_done); |
| 61 | } |
| 62 | |
| 63 | bool something_was_done() const { return something_was_done_; } |
| 64 | |
| 65 | private: |
| 66 | bool something_was_done_ = false; |
| 67 | scoped_refptr<base::SequencedTaskRunner> owning_sequence_; |
| 68 | }; |
| 69 | ``` |
| 70 | |
| 71 | foo_unittest.cc |
Raphael Kubo da Costa | 9d32d0f | 2019-09-26 17:39:48 | [diff] [blame] | 72 | ```c++ |
Gabriel Charette | 0b20ee6 | 2019-09-18 14:06:12 | [diff] [blame] | 73 | TEST(FooTest, DoSomething) { |
| 74 | base::test::SingleThreadTaskEnvironment task_environment; |
| 75 | |
| 76 | Foo foo; |
| 77 | RunLoop run_loop; |
| 78 | foo.DoSomethingAndReply(run_loop.QuitClosure()); |
| 79 | run_loop.Run(); |
| 80 | EXPECT_TRUE(foo.something_was_done()); |
| 81 | } |
| 82 | ``` |
| 83 | |
| 84 | Note that `RunLoop().RunUntilIdle()` could be used instead of a `QuitClosure()` |
| 85 | above but [best |
| 86 | practices](https://developers.google.com/web/updates/2019/04/chromium-chronicle-1) |
| 87 | favor QuitClosure() over RunUntilIdle() as the latter can lead to flaky tests. |
| 88 | |
| 89 | ### Full fledged base::test::TaskEnvironment |
| 90 | |
| 91 | If your components depends on `base::ThreadPool` (that's a good thing!), you'll |
| 92 | need a full `base::test::TaskEnvironment`. Don't be afraid to use a full |
| 93 | `TaskEnvironment` when appropriate: think of "SingleThread" as being a |
| 94 | readability term like "const", it documents that ThreadPool isn't used when it's |
| 95 | not but you shouldn't be afraid to lift it. |
| 96 | |
| 97 | Task runners are still obtained by the product code through |
Gabriel Charette | 9b6c0407 | 2022-04-01 23:22:46 | [diff] [blame] | 98 | [base/task/thread_pool.h] without necessitating a test-only task runner injection |
Gabriel Charette | 0b20ee6 | 2019-09-18 14:06:12 | [diff] [blame] | 99 | seam :). |
| 100 | |
| 101 | Typical use case: |
| 102 | |
| 103 | foo_service.h |
Raphael Kubo da Costa | 9d32d0f | 2019-09-26 17:39:48 | [diff] [blame] | 104 | ```c++ |
Gabriel Charette | 0b20ee6 | 2019-09-18 14:06:12 | [diff] [blame] | 105 | class FooService { |
| 106 | public: |
| 107 | FooService() |
| 108 | : backend_task_runner_( |
Gabriel Charette | 1138d60 | 2020-01-29 08:51:52 | [diff] [blame] | 109 | base::ThreadPool::CreateSequencedTaskRunner( |
| 110 | {base::MayBlock(), base::TaskPriority::BEST_EFFORT})), |
Gabriel Charette | 0b20ee6 | 2019-09-18 14:06:12 | [diff] [blame] | 111 | backend_(new FooBackend, |
| 112 | base::OnTaskRunnerDeleter(backend_task_runner_)) {} |
| 113 | |
| 114 | // Flushes state to disk async and replies. |
| 115 | FlushAndReply(base::OnceClosure on_done) { |
| 116 | DCHECK(owning_sequence_->RunsTasksInCurrentSequence()); |
Hong Xu | 294f451 | 2023-10-21 01:13:10 | [diff] [blame] | 117 | backend_task_runner_->PostTaskAndReply(FROM_HERE, |
Gabriel Charette | 0b20ee6 | 2019-09-18 14:06:12 | [diff] [blame] | 118 | base::BindOnce(&FooBackend::Flush, Unretained(backend_.get()), |
Gabriel Charette | 1138d60 | 2020-01-29 08:51:52 | [diff] [blame] | 119 | std::move(on_done))); |
Gabriel Charette | 0b20ee6 | 2019-09-18 14:06:12 | [diff] [blame] | 120 | } |
| 121 | |
| 122 | private: |
| 123 | scoped_refptr<base::SequencedTaskRunner> backend_task_runner_; |
| 124 | |
| 125 | // See https://youtu.be/m6Kz6pMaIxc?t=882 for memory management best |
| 126 | // practices. |
| 127 | std::unique_ptr<FooBackend, base::OnTaskRunnerDeleter> backend_; |
| 128 | }; |
| 129 | ``` |
| 130 | |
| 131 | foo_service_unittest.cc |
Raphael Kubo da Costa | 9d32d0f | 2019-09-26 17:39:48 | [diff] [blame] | 132 | ```c++ |
Gabriel Charette | 0b20ee6 | 2019-09-18 14:06:12 | [diff] [blame] | 133 | TEST(FooServiceTest, FlushAndReply) { |
| 134 | base::test::TaskEnvironment task_environment; |
| 135 | |
| 136 | FooService foo_service; |
| 137 | RunLoop run_loop; |
| 138 | foo_service.FlushAndReply(run_loop.QuitClosure()); |
| 139 | run_loop.Run(); |
| 140 | EXPECT_TRUE(VerifyFooStateOnDisk()); |
| 141 | } |
| 142 | ``` |
| 143 | |
| 144 | ### content::BrowserTaskEnvironment |
| 145 | |
| 146 | This is the same thing as `base::test::TaskEnvironment` with the addition of |
| 147 | `content::BrowserThread` support. You need this if-and-only-if the code under |
| 148 | test is using `BrowserThread::UI` or `BrowserThread::IO`. For determinism, both |
| 149 | BrowserThreads will share the main thread and can be driven by RunLoop. By |
| 150 | default the main thread will use `MainThreadType::UI` but you can override this |
| 151 | via the [MainThreadType trait](#mainthreadtype-trait) to ask for an IO pump. |
| 152 | |
| 153 | `BrowserTaskEnvironment::REAL_IO_THREAD` can be also used as a construction |
| 154 | trait for rare instances that desire distinct physical BrowserThreads. |
| 155 | |
| 156 | ### web::WebTaskEnvironment |
| 157 | |
| 158 | This is the //ios equivalent of `content::BrowserTaskEnvironment` to simulate |
| 159 | `web::WebThread`. |
| 160 | |
Etienne Pierre-doray | c3c341b | 2023-12-21 16:57:30 | [diff] [blame] | 161 | ### blink::test::TaskEnvironment |
Gabriel Charette | 3ae250ed | 2020-03-31 16:04:56 | [diff] [blame] | 162 | |
Etienne Pierre-doray | c3c341b | 2023-12-21 16:57:30 | [diff] [blame] | 163 | This is the same thing as base::test::TaskEnvironment with the addition of |
| 164 | blink::MainThreadScheduler and blink::MainThreadIsolate support. You need this |
| 165 | if-and-only-if the code under test is using blink::Thread::Current() or needs |
| 166 | v8::Isolate::GetCurrent() to be a blink Isolate. |
Gabriel Charette | 3ae250ed | 2020-03-31 16:04:56 | [diff] [blame] | 167 | |
Gabriel Charette | 0b20ee6 | 2019-09-18 14:06:12 | [diff] [blame] | 168 | ## Task Environment Traits and Abilities |
| 169 | |
| 170 | ### Driving the Task Environment |
| 171 | |
| 172 | All task environments support the following methods to run tasks: |
| 173 | * `base::RunLoop:Run()`: run the main thread until the `QuitClosure()` is |
| 174 | invoked (note: other threads also run in parallel by default). |
| 175 | * `base::RunLoop::RunUntilIdle()`: run the main thread until it is idle. This |
| 176 | is typically not what you want in multi-threaded environments as it may |
| 177 | resume before `ThreadPool` is idle. |
| 178 | * `TaskEnvironment::RunUntilIdle()`: Runs everything the TaskEnvironment is |
| 179 | aware of. This excludes system events and any threads outside of the main |
| 180 | thread and ThreadPool. It should be used with care when such external factors |
| 181 | can be involved. |
| 182 | * `TaskEnvironment::FastForward*()`: More on this in the TimeSource section |
| 183 | below. |
| 184 | |
| 185 | ### TimeSource trait |
| 186 | |
| 187 | By default tests run under `TimeSource::SYSTEM_TIME` which means delays are |
| 188 | real-time and `base::Time::Now()` and `base::TimeTicks::Now()` return live |
| 189 | system times |
| 190 | ([context](https://chromium-review.googlesource.com/c/chromium/src/+/1742616)). |
| 191 | |
| 192 | Whenever testing code with delays, you should favor `TimeSource::MOCK_TIME` as a |
| 193 | trait. This makes it such that delayed tasks and `base::Time::Now()` + |
| 194 | `base::TimeTicks::Now()` use a mock clock. |
| 195 | |
| 196 | Under this mode, the mock clock will start at the current system time but will |
| 197 | then only advance when explicitly requested by `TaskEnvironment::FastForward*()` |
Matt Mueller | aec1fa6 | 2019-09-20 20:24:56 | [diff] [blame] | 198 | and `TaskEnvironment::AdvanceClock()` methods *or* when `RunLoop::Run()` is |
| 199 | running and all managed threads become idle (auto-advances to the soonest |
| 200 | delayed task, if any, amongst all managed threads). |
| 201 | |
| 202 | `TaskEnvironment::FastForwardBy()` repeatedly runs existing immediately |
| 203 | executable tasks until idle and then advances the mock clock incrementally to |
| 204 | run the next delayed task within the time delta. It may advance time by more |
| 205 | than the requested amount if running the tasks causes nested |
| 206 | time-advancing-method calls. |
Gabriel Charette | 0b20ee6 | 2019-09-18 14:06:12 | [diff] [blame] | 207 | |
| 208 | This makes it possible to test code with flush intervals, repeating timers, |
| 209 | timeouts, etc. without any test-specific seams in the product code, e.g.: |
| 210 | |
| 211 | foo_storage.h |
Raphael Kubo da Costa | 9d32d0f | 2019-09-26 17:39:48 | [diff] [blame] | 212 | ```c++ |
Gabriel Charette | 0b20ee6 | 2019-09-18 14:06:12 | [diff] [blame] | 213 | class FooStorage { |
| 214 | public: |
| 215 | static constexpr base::TimeDelta::kFlushInterval = |
Peter Kasting | e5a38ed | 2021-10-02 03:06:35 | [diff] [blame] | 216 | base::Seconds(30); |
Gabriel Charette | 0b20ee6 | 2019-09-18 14:06:12 | [diff] [blame] | 217 | |
| 218 | // Sets |key| to |value|. Flushed to disk on the next flush interval. |
| 219 | void Set(base::StringPiece key, base::StringPiece value); |
| 220 | }; |
| 221 | ``` |
| 222 | |
| 223 | foo_unittest.cc |
Raphael Kubo da Costa | 9d32d0f | 2019-09-26 17:39:48 | [diff] [blame] | 224 | ```c++ |
Gabriel Charette | 0b20ee6 | 2019-09-18 14:06:12 | [diff] [blame] | 225 | class FooStorageTest { |
| 226 | public: |
| 227 | FooStorageTest() = default; |
| 228 | |
| 229 | // Test helper that returns true if |key| is found in the on disk storage. |
| 230 | bool FindKeyInOnDiskStorage(base::StringPiece key); |
| 231 | |
| 232 | protected: |
| 233 | base::test::TaskEnvironment task_environment{ |
| 234 | base::test::TaskEnvironment::TimeSource::MOCK_TIME}; |
| 235 | FooStorage foo_storage_; |
| 236 | }; |
| 237 | |
| 238 | TEST_F(FooStorageTest, Set) { |
| 239 | foo_storage_.Set("mykey", "myvalue"); |
| 240 | EXPECT_FALSE(FindKeyInOnDiskStorage("mykey")); |
| 241 | task_environment.FastForwardBy(FooStorage::kFlushInterval); |
| 242 | EXPECT_TRUE(FindKeyInOnDiskStorage("mykey")); |
| 243 | } |
| 244 | ``` |
| 245 | |
Matt Mueller | aec1fa6 | 2019-09-20 20:24:56 | [diff] [blame] | 246 | In contrast, `TaskEnvironment::AdvanceClock()` simply advances the mock time by |
| 247 | the requested amount, and does not run tasks. This may be useful in |
| 248 | cases where `TaskEnvironment::FastForwardBy()` would result in a livelock. For |
| 249 | example, if one task is blocked on a `WaitableEvent` and there is a delayed |
| 250 | task that would signal the event (e.g., a timeout), then |
| 251 | `TaskEnvironment::FastForwardBy()` will never complete. In this case, you could |
| 252 | advance the clock enough that the delayed task becomes runnable, and then |
| 253 | `TaskEnvironment::RunUntilIdle()` would run the delayed task, signalling the |
| 254 | event. |
| 255 | |
Raphael Kubo da Costa | 9d32d0f | 2019-09-26 17:39:48 | [diff] [blame] | 256 | ```c++ |
Matt Mueller | aec1fa6 | 2019-09-20 20:24:56 | [diff] [blame] | 257 | TEST(FooTest, TimeoutExceeded) |
| 258 | { |
| 259 | base::test::TaskEnvironment task_environment{ |
| 260 | base::test::TaskEnvironment::TimeSource::MOCK_TIME}; |
| 261 | base::WaitableEvent event; |
| 262 | base::RunLoop run_loop; |
Gabriel Charette | 1138d60 | 2020-01-29 08:51:52 | [diff] [blame] | 263 | base::ThreadPool::PostTaskAndReply( |
| 264 | FROM_HERE, {base::MayBlock()}, |
Matt Mueller | aec1fa6 | 2019-09-20 20:24:56 | [diff] [blame] | 265 | base::BindOnce(&BlocksOnEvent, base::Unretained(&event)), |
| 266 | run_loop.QuitClosure()); |
Gabriel Charette | 1138d60 | 2020-01-29 08:51:52 | [diff] [blame] | 267 | base::ThreadPool::PostDelayedTask( |
| 268 | FROM_HERE, {}, |
Matt Mueller | aec1fa6 | 2019-09-20 20:24:56 | [diff] [blame] | 269 | base::BindOnce(&WaitableEvent::Signal, base::Unretained(&event)), |
| 270 | kTimeout); |
| 271 | // Can't use task_environment.FastForwardBy() since BlocksOnEvent blocks |
| 272 | // and the task pool will not become idle. |
| 273 | // Instead, advance time until the timeout task becomes runnable. |
| 274 | task_environment.AdvanceClock(kTimeout); |
| 275 | // Now the timeout task is runable. |
| 276 | task_environment.RunUntilIdle(); |
| 277 | // The reply task should already have been executed, but run the run_loop to |
| 278 | // verify. |
| 279 | run_loop.Run(); |
| 280 | } |
| 281 | ``` |
| 282 | |
Gabriel Charette | 0b20ee6 | 2019-09-18 14:06:12 | [diff] [blame] | 283 | ### MainThreadType trait |
| 284 | |
| 285 | The average component only cares about running its tasks and |
| 286 | `MainThreadType::DEFAULT` is sufficient. Components that care to interact |
| 287 | asynchronously with the system will likely need a `MainThreadType::UI` to be |
| 288 | able to receive system events (e.g,. UI or clipboard events). |
| 289 | |
| 290 | Some components will prefer a main thread that handles asynchronous IO events |
| 291 | and will use `MainThreadType::IO`. Such components are typically the ones living |
| 292 | on BrowserThread::IO and being tested with a `BrowserTaskEnvironment` |
| 293 | initialized with `MainThreadType::IO`. |
| 294 | |
| 295 | Note: This is strictly about requesting a specific `MessagePumpType` for the |
| 296 | main thread. It has nothing to do with `BrowserThread::UI` or |
| 297 | `BrowserThread::IO` which are named threads in the //content/browser code. |
| 298 | |
| 299 | ### ThreadPoolExecutionMode trait |
| 300 | |
| 301 | By default non-delayed tasks posted to `base::ThreadPool` may run at any point. |
| 302 | Tests that require more determinism can request |
| 303 | `ThreadPoolExecutionMode::QUEUED` to enforce that tasks posted to |
| 304 | `base::ThreadPool` only run when `TaskEnvironment::RunUntilIdle()` or |
| 305 | `TaskEnvironment::FastForward*()` are invoked. Note that `RunLoop::Run()` does |
| 306 | **not** unblock the ThreadPool in this mode and thus strictly runs only the main |
| 307 | thread. |
| 308 | |
| 309 | When `ThreadPoolExecutionMode::QUEUED` is mixed with `TimeSource::MOCK_TIME`, |
| 310 | time will auto-advance to the soonest task *that is allowed to run* when |
| 311 | required (i.e. it will ignore delayed tasks in the thread pool while in |
| 312 | `RunLoop::Run()`). See |
| 313 | `TaskEnvironmentTest.MultiThreadedMockTimeAndThreadPoolQueuedMode` for an |
| 314 | example. |
| 315 | |
| 316 | This trait is of course irrelevant under `SingleThreadTaskEnvironment`. |
| 317 | |
| 318 | ### ThreadingMode trait |
| 319 | |
| 320 | Prefer an explicit `SingleThreadTaskEnvironment` over using |
| 321 | `ThreadingMode::MAIN_THREAD_ONLY`. The only reason to use |
| 322 | `ThreadingMode::MAIN_THREAD_ONLY` explicitly is if the parent class of your test |
| 323 | fixture manages the `TaskEnvironment` but takes `TaskEnvironmentTraits` to let |
| 324 | its subclasses customize it and you really need a `SingleThreadTaskEnvironment`. |
| 325 | |
| 326 | ## Base Fixture managed TaskEnvironment |
| 327 | |
| 328 | In some cases it makes sense to have the base fixture of an entire section of |
| 329 | the codebase be managing the `TaskEnvironment` (e.g. [ViewsTestBase]). It's |
| 330 | useful if such base fixture exposes `TaskEnvironmentTraits` to their subclasses |
| 331 | so that individual tests within that domain can fine-tune their traits as |
| 332 | desired. |
| 333 | |
| 334 | This typically looks like this (in this case `FooTestBase` opts to enforce |
| 335 | `MainThreadType::UI` and leaves other traits to be specified as desired): |
| 336 | |
Raphael Kubo da Costa | 9d32d0f | 2019-09-26 17:39:48 | [diff] [blame] | 337 | ```c++ |
Gabriel Charette | 0b20ee6 | 2019-09-18 14:06:12 | [diff] [blame] | 338 | // Constructs a FooTestBase with |traits| being forwarded to its |
| 339 | // TaskEnvironment. MainThreadType always defaults to UI and must not be |
| 340 | // specified. |
| 341 | template <typename... TaskEnvironmentTraits> |
| 342 | NOINLINE explicit FooTestBase(TaskEnvironmentTraits&&... traits) |
| 343 | : task_environment_(base::test::TaskEnvironment::MainThreadType::UI, |
| 344 | std::forward<TaskEnvironmentTraits>(traits)...) {} |
| 345 | ``` |
| 346 | |
| 347 | Note, if you're not familiar with traits: TaskEnvironment traits use |
| 348 | [base/traits_bag.h] and will automatically complain at compile-time if an |
| 349 | enum-based trait is specified more than once (i.e. subclasses will not compile |
| 350 | if re-specifying `MainThreadType` in the above example). |
| 351 | |
| 352 | ## Browser tests |
| 353 | |
| 354 | This is all nice and fancy for unit tests, but what about browser\_tests, |
| 355 | ui\_integration\_tests, etc? Tests that subclass `content::BrowserTestBase` bring |
| 356 | up the entire environment (tasks & more) by default. |
| 357 | |
| 358 | The downside is that you don't have fine-grained control over it like you would |
| 359 | with all the `TaskEnvironment` methods. |
| 360 | |
| 361 | The favored paradigm is `RunLoop::Run()` + `QuitClosure()`. The asynchronous |
| 362 | nature of Chromium code makes this the most reliable way to wait for an event. |
| 363 | |
| 364 | There are fancy versions of this to perform common actions, e.g. |
| 365 | [content/public/test/browser_test_utils.h] |
| 366 | [content/public/test/content_browser_test_utils.h] which will let you navigate, |
| 367 | execute scripts, simulate UI interactions, etc. |
| 368 | |
| 369 | But the idea is always the same : |
| 370 | 1) Instantiate `RunLoop run_loop;` |
| 371 | 2) Kick off some work and hand-off `run_loop.QuitClosure()` |
| 372 | 3) `run_loop.Run()` until the `QuitClosure()` is called. |
| 373 | |
| 374 | ### MOCK_TIME in browser tests |
| 375 | |
| 376 | So you fell in love with `TimeSource::MOCK_TIME` but now you're in a browser |
| 377 | test... yeah, sorry about that... |
| 378 | |
| 379 | The eventual goal is to make it possible to set up TaskEnvironmentTraits from |
| 380 | your test fixture just like you can override command-line, etc. but we're not |
| 381 | there yet... |
| 382 | |
| 383 | In the mean time you can still use the old |
| 384 | `base::ScopedMockTimeMessageLoopTaskRunner` to mock delayed tasks on the main |
| 385 | thread (you're out of luck on other threads for now). And you can use |
| 386 | `base::subtle::ScopedTimeClockOverrides` if you want to override `Now()`. |
| 387 | |
| 388 | You think that's a mess? Just think that it used to be this way in unit tests |
| 389 | too and you'll be happy again :). |
| 390 | |
| 391 | ## Old paradigms |
| 392 | |
| 393 | Here are some paradigms you might see throughout the code base and some insight |
| 394 | on what to do about them (hint: copying them is not one!). Migration help is |
| 395 | welcome [crbug.com/984323](https://crbug.com/984323)! |
| 396 | |
| 397 | ### base::TestMockTimeTaskRunner |
| 398 | |
| 399 | This is the ancestor of `SingleThreadTaskEnvironment` + `TimeSource::MOCK_TIME`. |
| 400 | It's sort of equivalent but prefer task environments for consistency. |
| 401 | |
| 402 | The only case where `base::TestMockTimeTaskRunner` is still the only option is |
| 403 | when writing regression tests that simulate a specific task execution order |
| 404 | across multiple sequences. To do so, use two `base::TestMockTimeTaskRunner` and |
| 405 | have the racing components post their tasks to separate ones. You can then |
| 406 | explicitly run tasks posted to each one from the main test thread in a way that |
| 407 | deterministically exercises the race resolution under test. This only applies to |
| 408 | task execution order races, data races still require parallel execution and this |
| 409 | is the main reason `TaskEnvironment` doesn't multiplex the `ThreadPool` tasks |
| 410 | onto the main thread (i.e. exercise data races, especially in the scope of |
| 411 | TSAN). |
| 412 | |
| 413 | ### base::TestSimpleTaskRunner |
| 414 | |
| 415 | Prefer using `SingleThreadTaskEnvironment` over `base::TestSimpleTaskRunner`. |
| 416 | `TestSimpleTaskRunner` isn't as "simple" as it seems specifically because it |
| 417 | runs tasks in a surprising order (delays aren't respected and nesting doesn't |
| 418 | behave as usual). Should you prefer to flush all tasks regardless of delays, |
| 419 | `TimeSource::MOCK_TIME` and `TaskEnvironment::FastForwardUntilNoTasksRemain()` |
| 420 | have you covered. |
| 421 | |
| 422 | ### base::NullTaskRunner |
| 423 | |
| 424 | Prefer `SingleThreadTaskEnvironment` or `TaskEnvironment` with |
| 425 | `ThreadPoolExecutionMode::QUEUED` over `base::NullTaskRunner`. A |
| 426 | `NullTaskRunner` might seem appealing, but not posting tasks is under-testing |
| 427 | the potential side-effects of the code under tests. All tests should be okay if |
| 428 | tasks born from their actions are run or deleted at a later point. |
| 429 | |
| 430 | ### base::ScopedMockTimeMessageLoopTaskRunner |
| 431 | |
| 432 | This is the ancestor of `base::TestMockTimeTaskRunner` which is itself mostly |
| 433 | deprecated. As mentioned above in the [TimeSource trait](#timesource-trait) |
| 434 | section: This should never be used anymore except to mock time when there's |
| 435 | already a task system in place, e.g. in browser\_tests. |
| 436 | |
| 437 | ### SetTaskRunnerForTesting() and SetTickClockForTesting() |
| 438 | |
| 439 | Prior to `TaskEnvironment::TimeSource::MOCK_TIME`, many components had |
| 440 | `SetClockForTesting()` in their product code. And before modern [Threading and |
| 441 | Tasks], some components had SetTaskRunnerForTesting(). Neither of these |
| 442 | test-only seams are required anymore now that task environments can mock those |
| 443 | from under-the-hood. Cleanup in favor of modern TaskEnvironment paradigms is |
| 444 | always appreciated ([crbug.com/984323](https://crbug.com/984323)). |
| 445 | |
| 446 | ### Other helper task runners |
| 447 | |
| 448 | Different parts of the codebase have their own helper task runners. Please |
| 449 | migrate away from them or document them above. Ultimately the goal is for |
| 450 | `TaskEnvironment` and its subclasses to rule them all and to have a consistent |
| 451 | task testing API surface once and for all. |
| 452 | |
| 453 | It is still okay for specific areas to have a base fixture that configures a |
| 454 | default `TaskEnvironment` appropriate for that area and use the |
| 455 | `TaskEnvironmentTraits` paradigm outlined in the [Base Fixture managed |
| 456 | TaskEnvironment](#base-fixture-managed-taskenvironment) section above to let |
| 457 | individual tests provide additional traits. |
| 458 | |
| 459 | [Threading and Tasks]: threading_and_tasks.md |
| 460 | [Threading and Tasks FAQ]: threading_and_tasks_faq.md |
| 461 | [`ValidTraits`]: https://cs.chromium.org/chromium/src/base/test/task_environment.h?type=cs&q=ValidTraits&sq=package:chromium&g=0 |
| 462 | [task_environment.h]: https://cs.chromium.org/chromium/src/base/test/task_environment.h |
Gabriel Charette | 9b6c0407 | 2022-04-01 23:22:46 | [diff] [blame] | 463 | [base/task/thread_pool.h]: https://cs.chromium.org/chromium/src/base/task/thread_pool.h |
Gabriel Charette | 0b20ee6 | 2019-09-18 14:06:12 | [diff] [blame] | 464 | [ViewsTestBase]: https://cs.chromium.org/chromium/src/ui/views/test/views_test_base.h |
| 465 | [base/traits_bag.h]: https://cs.chromium.org/chromium/src/base/traits_bag.h |
| 466 | [content/public/test/browser_test_utils.h]: https://cs.chromium.org/chromium/src/content/public/test/browser_test_utils.h |
| 467 | [content/public/test/content_browser_test_utils.h]: https://cs.chromium.org/chromium/src/content/public/test/content_browser_test_utils.h |
| 468 | [content/public/test/test_utils.h]: https://cs.chromium.org/chromium/src/content/public/test/test_utils.h |