Gabriel Charette | 0b20ee6c | 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 environmentin the scope of your test(or test fixture).It will need |
| 16 | to outlive the majority of other members toensure 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 comein various forms but share the same fundamental |
| 22 | characteristics: |
| 23 | *There can be only one per test(if yourbase fixture already provides one: |
| 24 | see[BaseFixture 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 runor be destroyed before theend of |
| 29 | ~TaskEnvironment(). |
| 30 | *They all derivefrom`base::test::TaskEnvironment`and support its |
| 31 | [`ValidTraits`]and sometimes more. |
| 32 | *See usage examplein[task_environment.h]. |
| 33 | *For example, a key characteristicis 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` memberis typically exposedin theprotected 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 orderfor theseAPIs to be functionaland`base::RunLoop` to run the posted |
Gabriel Charette | 0b20ee6c | 2019-09-18 14:06:12 | [diff] [blame] | 47 | tasks. |
| 48 | |
| 49 | Typicallythis will look something likethis: |
| 50 | |
| 51 | foo.h |
Raphael Kubo da Costa | 9d32d0f | 2019-09-26 17:39:48 | [diff] [blame] | 52 | ```c++ |
Gabriel Charette | 0b20ee6c | 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 | 0b20ee6c | 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 | 0b20ee6c | 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 | 0b20ee6c | 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 | favorQuitClosure() overRunUntilIdle()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 usedwhen it's |
| 95 | not but you shouldn't be afraid to lift it. |
| 96 | |
| 97 | Task runners are still obtainedby 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 | 0b20ee6c | 2019-09-18 14:06:12 | [diff] [blame] | 99 | seam:). |
| 100 | |
| 101 | Typicalusecase: |
| 102 | |
| 103 | foo_service.h |
Raphael Kubo da Costa | 9d32d0f | 2019-09-26 17:39:48 | [diff] [blame] | 104 | ```c++ |
Gabriel Charette | 0b20ee6c | 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 | 0b20ee6c | 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 | 0b20ee6c | 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 | 0b20ee6c | 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 | 0b20ee6c | 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 | Thisis the same thingas`base::test::TaskEnvironment`with the addition of |
| 147 | `content::BrowserThread` support.You needthisif-and-only-if the code under |
| 148 | testisusing`BrowserThread::UI`or`BrowserThread::IO`.For determinism, both |
| 149 | BrowserThreads will share the main threadand can be drivenbyRunLoop.By |
| 150 | default the main thread willuse`MainThreadType::UI` but you canoverridethis |
| 151 | via the[MainThreadType trait](#mainthreadtype-trait) to askfor an IO pump. |
| 152 | |
| 153 | `BrowserTaskEnvironment::REAL_IO_THREAD` can be also usedas a construction |
| 154 | traitfor rare instances that desire distinct physicalBrowserThreads. |
| 155 | |
| 156 | ### web::WebTaskEnvironment |
| 157 | |
| 158 | Thisis 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 | Thisis the same thingasbase::test::TaskEnvironmentwith the addition of |
| 164 | blink::MainThreadSchedulerand blink::MainThreadIsolate support.You needthis |
| 165 | if-and-only-if the code under testisusing blink::Thread::Current()or needs |
| 166 | v8::Isolate::GetCurrent() to be a blinkIsolate. |
Gabriel Charette | 3ae250ed | 2020-03-31 16:04:56 | [diff] [blame] | 167 | |
Gabriel Charette | 0b20ee6c | 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 threaduntil the`QuitClosure()`is |
| 174 | invoked(note: other threads also runin parallelbydefault). |
| 175 | *`base::RunLoop::RunUntilIdle()`: run the main threaduntil itis idle.This |
| 176 | is typicallynot what you wantin multi-threaded environmentsas it may |
| 177 | resume before`ThreadPool`is idle. |
| 178 | *`TaskEnvironment::RunUntilIdle()`:Runs everything theTaskEnvironmentis |
| 179 | aware of.This excludes system eventsand any threads outside of the main |
| 180 | threadandThreadPool.It should be usedwith carewhen such external factors |
| 181 | can be involved. |
| 182 | *`TaskEnvironment::FastForward*()`:More onthisin theTimeSource section |
| 183 | below. |
| 184 | |
| 185 | ### TimeSource trait |
| 186 | |
| 187 | Bydefault tests run under`TimeSource::SYSTEM_TIME` which means delays are |
| 188 | real-timeand`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 codewith delays, you should favor`TimeSource::MOCK_TIME`as a |
| 193 | trait.This makes it such that delayed tasksand`base::Time::Now()`+ |
| 194 | `base::TimeTicks::Now()`use a mock clock. |
| 195 | |
| 196 | Underthis mode, the mock clock will start at the current system time but will |
| 197 | then only advancewhen explicitly requestedby`TaskEnvironment::FastForward*()` |
Matt Mueller | aec1fa6 | 2019-09-20 20:24:56 | [diff] [blame] | 198 | and`TaskEnvironment::AdvanceClock()` methods*or*when`RunLoop::Run()`is |
| 199 | runningand 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 tasksuntil idleandthen advances the mock clock incrementally to |
| 204 | run thenext delayed task within the time delta.It may advance timeby more |
| 205 | than the requested amountif running the tasks causes nested |
| 206 | time-advancing-method calls. |
Gabriel Charette | 0b20ee6c | 2019-09-18 14:06:12 | [diff] [blame] | 207 | |
| 208 | This makes it possible to test codewith flush intervals, repeating timers, |
| 209 | timeouts, etc. without any test-specific seamsin 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 | 0b20ee6c | 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 | 0b20ee6c | 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 | 0b20ee6c | 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 timeby |
| 247 | the requested amount,and doesnot run tasks.This may be usefulin |
| 248 | caseswhere`TaskEnvironment::FastForwardBy()` would resultin a livelock.For |
| 249 | example,if one taskis blocked on a`WaitableEvent`and thereis a delayed |
| 250 | task that would signal theevent(e.g., a timeout),then |
| 251 | `TaskEnvironment::FastForwardBy()` will never complete.Inthiscase, you could |
| 252 | advance the clock enough that the delayed task becomes runnable,andthen |
| 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 | 0b20ee6c | 2019-09-18 14:06:12 | [diff] [blame] | 283 | ### MainThreadType trait |
| 284 | |
| 285 | The average component only cares about running its tasksand |
| 286 | `MainThreadType::DEFAULT`is sufficient.Components that care to interact |
| 287 | asynchronouslywith the system will likely need a`MainThreadType::UI` to be |
| 288 | able to receive system events(e.g,. UIor clipboard events). |
| 289 | |
| 290 | Some components will prefer a main thread that handles asynchronous IO events |
| 291 | and willuse`MainThreadType::IO`.Such components are typically the ones living |
| 292 | onBrowserThread::IOand being testedwith a`BrowserTaskEnvironment` |
| 293 | initializedwith`MainThreadType::IO`. |
| 294 | |
| 295 | Note:Thisis strictly about requesting a specific`MessagePumpType`for the |
| 296 | main thread.It has nothing todowith`BrowserThread::UI`or |
| 297 | `BrowserThread::IO` which are named threadsin the//content/browser code. |
| 298 | |
| 299 | ### ThreadPoolExecutionMode trait |
| 300 | |
| 301 | Bydefault non-delayed tasks posted to`base::ThreadPool` may run at any point. |
| 302 | Tests thatrequire more determinism can request |
| 303 | `ThreadPoolExecutionMode::QUEUED` to enforce that tasks posted to |
| 304 | `base::ThreadPool` only runwhen`TaskEnvironment::RunUntilIdle()`or |
| 305 | `TaskEnvironment::FastForward*()` are invoked.Note that`RunLoop::Run()` does |
| 306 | **not** unblock theThreadPoolinthis modeand thus strictly runs only the main |
| 307 | thread. |
| 308 | |
| 309 | When`ThreadPoolExecutionMode::QUEUED`is mixedwith`TimeSource::MOCK_TIME`, |
| 310 | time willauto-advance to the soonest task*thatis allowed to run*when |
| 311 | required(i.e. it will ignore delayed tasksin the thread poolwhilein |
| 312 | `RunLoop::Run()`).See |
| 313 | `TaskEnvironmentTest.MultiThreadedMockTimeAndThreadPoolQueuedMode`for an |
| 314 | example. |
| 315 | |
| 316 | This traitis of course irrelevant under`SingleThreadTaskEnvironment`. |
| 317 | |
| 318 | ### ThreadingMode trait |
| 319 | |
| 320 | Prefer anexplicit`SingleThreadTaskEnvironment` overusing |
| 321 | `ThreadingMode::MAIN_THREAD_ONLY`.The only reason touse |
| 322 | `ThreadingMode::MAIN_THREAD_ONLY` explicitlyisif the parentclass of your test |
| 323 | fixture manages the`TaskEnvironment` but takes`TaskEnvironmentTraits` tolet |
| 324 | its subclasses customize itand you really need a`SingleThreadTaskEnvironment`. |
| 325 | |
| 326 | ## Base Fixture managed TaskEnvironment |
| 327 | |
| 328 | In some cases it makes sense to have thebase 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 | 0b20ee6c | 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'renot familiarwith traits:TaskEnvironment traitsuse |
| 348 | [base/traits_bag.h]and will automatically complain at compile-timeif an |
| 349 | enum-based traitis specified more than once(i.e. subclasses willnot compile |
| 350 | if re-specifying`MainThreadType`in the above example). |
| 351 | |
| 352 | ## Browser tests |
| 353 | |
| 354 | Thisis all niceand fancyfor 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)bydefault. |
| 357 | |
| 358 | The downsideis 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'rein a browser |
| 377 | test... yeah, sorry about that... |
| 378 | |
| 379 | The eventual goalis to make it possible toset upTaskEnvironmentTraitsfrom |
| 380 | your test fixture just like you canoverride 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'reout of luck on other threadsfor now).And you canuse |
| 386 | `base::subtle::ScopedTimeClockOverrides`if you want tooverride`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 codebaseand some insight |
| 394 | on what todo about them(hint: copying themisnot one!).Migration helpis |
| 395 | welcome[crbug.com/984323](https://crbug.com/984323)! |
| 396 | |
| 397 | ### base::TestMockTimeTaskRunner |
| 398 | |
| 399 | Thisis 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, especiallyin the scope of |
| 411 | TSAN). |
| 412 | |
| 413 | ### base::TestSimpleTaskRunner |
| 414 | |
| 415 | Preferusing`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 respectedand 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 systemin 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[Threadingand |
| 441 | Tasks], some components hadSetTaskRunnerForTesting().Neither of these |
| 442 | test-only seams are required anymore now that task environments can mock those |
| 443 | from under-the-hood.Cleanupin favor of modernTaskEnvironment paradigmsis |
| 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 awayfrom themor document them above.Ultimately the goalisfor |
| 450 | `TaskEnvironment`and its subclasses to rule them alland to have a consistent |
| 451 | task testing API surface onceandfor all. |
| 452 | |
| 453 | Itis still okayfor specific areas to have abase fixture that configures a |
| 454 | default`TaskEnvironment` appropriatefor that areaanduse the |
| 455 | `TaskEnvironmentTraits` paradigm outlinedin the[BaseFixture managed |
| 456 | TaskEnvironment](#base-fixture-managed-taskenvironment) section above tolet |
| 457 | individual tests provide additional traits. |
| 458 | |
| 459 | [ThreadingandTasks]: threading_and_tasks.md |
| 460 | [ThreadingandTasks 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 | 0b20ee6c | 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 |