blob: d8aac3dca98a9afddd9d656e3edd019f1beb8878 [file] [log] [blame] [view]
Gabriel Charette0b20ee62019-09-18 14:06:121# Testing Components Which Post Tasks
2
3[TOC]
4
5## Overview
6
7So 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
9you want to test it :). This document will explain how to write matching
10state-of-the-art tests.
11
12## Task Environments
13
14In order to **unit test** a component which post tasks, you'll need to bring up
15the task environment in the scope of your test (or test fixture). It will need
16to outlive the majority of other members to ensure they have access to the task
17system throughout their lifetime. There are a rare exceptions, like
18`base::test::ScopedFeatureList`, that need to outlive the task environment. For
19browser tests, see the [Browser tests](#browser-tests) section below.
20
21Task environments come in various forms but share the same fundamental
22characteristics:
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
37The `TaskEnvironment` member is typically exposed in the protected section of
38the test fixture to allow tests to drive it directly (there's no need to expose
39public Run\*() methods that merely forward to the private member).
40
41### base::test::SingleThreadTaskEnvironment
42
Sean Maher03efef12022-09-23 22:43:1343Your component uses `base::SingleThreadTaskRunner::GetCurrentDefault()` or
44`base::SequencedTaskRunner::GetCurrentDefault()` to post tasks to the thread it
45was created on? You'll need at least a `base::test::SingleThreadTaskEnvironment`
46in order for these APIs to be functional and `base::RunLoop` to run the posted
Gabriel Charette0b20ee62019-09-18 14:06:1247tasks.
48
49Typically this will look something like this:
50
51foo.h
Raphael Kubo da Costa9d32d0f2019-09-26 17:39:4852```c++
Gabriel Charette0b20ee62019-09-18 14:06:1253class Foo {
54 public:
Sean Maher03efef12022-09-23 22:43:1355 Foo() : owning_sequence_(base::SequencedTaskRunner::GetCurrentDefault()) {}
Gabriel Charette0b20ee62019-09-18 14:06:1256
Trent Beginbe871422020-03-25 23:06:5457 DoSomethingAndReply(base::OnceClosure on_done) {
Gabriel Charette0b20ee62019-09-18 14:06:1258 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
71foo_unittest.cc
Raphael Kubo da Costa9d32d0f2019-09-26 17:39:4872```c++
Gabriel Charette0b20ee62019-09-18 14:06:1273TEST(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
84Note that `RunLoop().RunUntilIdle()` could be used instead of a `QuitClosure()`
85above but [best
86practices](https://developers.google.com/web/updates/2019/04/chromium-chronicle-1)
87favor QuitClosure() over RunUntilIdle() as the latter can lead to flaky tests.
88
89### Full fledged base::test::TaskEnvironment
90
91If your components depends on `base::ThreadPool` (that's a good thing!), you'll
92need a full `base::test::TaskEnvironment`. Don't be afraid to use a full
93`TaskEnvironment` when appropriate: think of "SingleThread" as being a
94readability term like "const", it documents that ThreadPool isn't used when it's
95not but you shouldn't be afraid to lift it.
96
97Task runners are still obtained by the product code through
Gabriel Charette9b6c04072022-04-01 23:22:4698[base/task/thread_pool.h] without necessitating a test-only task runner injection
Gabriel Charette0b20ee62019-09-18 14:06:1299seam :).
100
101Typical use case:
102
103foo_service.h
Raphael Kubo da Costa9d32d0f2019-09-26 17:39:48104```c++
Gabriel Charette0b20ee62019-09-18 14:06:12105class FooService {
106 public:
107 FooService()
108 : backend_task_runner_(
Gabriel Charette1138d602020-01-29 08:51:52109 base::ThreadPool::CreateSequencedTaskRunner(
110 {base::MayBlock(), base::TaskPriority::BEST_EFFORT})),
Gabriel Charette0b20ee62019-09-18 14:06:12111 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 Xu294f4512023-10-21 01:13:10117 backend_task_runner_->PostTaskAndReply(FROM_HERE,
Gabriel Charette0b20ee62019-09-18 14:06:12118 base::BindOnce(&FooBackend::Flush, Unretained(backend_.get()),
Gabriel Charette1138d602020-01-29 08:51:52119 std::move(on_done)));
Gabriel Charette0b20ee62019-09-18 14:06:12120 }
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
131foo_service_unittest.cc
Raphael Kubo da Costa9d32d0f2019-09-26 17:39:48132```c++
Gabriel Charette0b20ee62019-09-18 14:06:12133TEST(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
146This 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
148test is using `BrowserThread::UI` or `BrowserThread::IO`. For determinism, both
149BrowserThreads will share the main thread and can be driven by RunLoop. By
150default the main thread will use `MainThreadType::UI` but you can override this
151via the [MainThreadType trait](#mainthreadtype-trait) to ask for an IO pump.
152
153`BrowserTaskEnvironment::REAL_IO_THREAD` can be also used as a construction
154trait for rare instances that desire distinct physical BrowserThreads.
155
156### web::WebTaskEnvironment
157
158This is the //ios equivalent of `content::BrowserTaskEnvironment` to simulate
159`web::WebThread`.
160
Etienne Pierre-dorayc3c341b2023-12-21 16:57:30161### blink::test::TaskEnvironment
Gabriel Charette3ae250ed2020-03-31 16:04:56162
Etienne Pierre-dorayc3c341b2023-12-21 16:57:30163This is the same thing as base::test::TaskEnvironment with the addition of
164blink::MainThreadScheduler and blink::MainThreadIsolate support. You need this
165if-and-only-if the code under test is using blink::Thread::Current() or needs
166v8::Isolate::GetCurrent() to be a blink Isolate.
Gabriel Charette3ae250ed2020-03-31 16:04:56167
Gabriel Charette0b20ee62019-09-18 14:06:12168## Task Environment Traits and Abilities
169
170### Driving the Task Environment
171
172All 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
187By default tests run under `TimeSource::SYSTEM_TIME` which means delays are
188real-time and `base::Time::Now()` and `base::TimeTicks::Now()` return live
189system times
190([context](https://chromium-review.googlesource.com/c/chromium/src/+/1742616)).
191
192Whenever testing code with delays, you should favor `TimeSource::MOCK_TIME` as a
193trait. This makes it such that delayed tasks and `base::Time::Now()` +
194`base::TimeTicks::Now()` use a mock clock.
195
196Under this mode, the mock clock will start at the current system time but will
197then only advance when explicitly requested by `TaskEnvironment::FastForward*()`
Matt Muelleraec1fa62019-09-20 20:24:56198and `TaskEnvironment::AdvanceClock()` methods *or* when `RunLoop::Run()` is
199running and all managed threads become idle (auto-advances to the soonest
200delayed task, if any, amongst all managed threads).
201
202`TaskEnvironment::FastForwardBy()` repeatedly runs existing immediately
203executable tasks until idle and then advances the mock clock incrementally to
204run the next delayed task within the time delta. It may advance time by more
205than the requested amount if running the tasks causes nested
206time-advancing-method calls.
Gabriel Charette0b20ee62019-09-18 14:06:12207
208This makes it possible to test code with flush intervals, repeating timers,
209timeouts, etc. without any test-specific seams in the product code, e.g.:
210
211foo_storage.h
Raphael Kubo da Costa9d32d0f2019-09-26 17:39:48212```c++
Gabriel Charette0b20ee62019-09-18 14:06:12213class FooStorage {
214 public:
215 static constexpr base::TimeDelta::kFlushInterval =
Peter Kastinge5a38ed2021-10-02 03:06:35216 base::Seconds(30);
Gabriel Charette0b20ee62019-09-18 14:06:12217
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
223foo_unittest.cc
Raphael Kubo da Costa9d32d0f2019-09-26 17:39:48224```c++
Gabriel Charette0b20ee62019-09-18 14:06:12225class 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
238TEST_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 Muelleraec1fa62019-09-20 20:24:56246In contrast, `TaskEnvironment::AdvanceClock()` simply advances the mock time by
247the requested amount, and does not run tasks. This may be useful in
248cases where `TaskEnvironment::FastForwardBy()` would result in a livelock. For
249example, if one task is blocked on a `WaitableEvent` and there is a delayed
250task that would signal the event (e.g., a timeout), then
251`TaskEnvironment::FastForwardBy()` will never complete. In this case, you could
252advance the clock enough that the delayed task becomes runnable, and then
253`TaskEnvironment::RunUntilIdle()` would run the delayed task, signalling the
254event.
255
Raphael Kubo da Costa9d32d0f2019-09-26 17:39:48256```c++
Matt Muelleraec1fa62019-09-20 20:24:56257TEST(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 Charette1138d602020-01-29 08:51:52263 base::ThreadPool::PostTaskAndReply(
264 FROM_HERE, {base::MayBlock()},
Matt Muelleraec1fa62019-09-20 20:24:56265 base::BindOnce(&BlocksOnEvent, base::Unretained(&event)),
266 run_loop.QuitClosure());
Gabriel Charette1138d602020-01-29 08:51:52267 base::ThreadPool::PostDelayedTask(
268 FROM_HERE, {},
Matt Muelleraec1fa62019-09-20 20:24:56269 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 Charette0b20ee62019-09-18 14:06:12283### MainThreadType trait
284
285The average component only cares about running its tasks and
286`MainThreadType::DEFAULT` is sufficient. Components that care to interact
287asynchronously with the system will likely need a `MainThreadType::UI` to be
288able to receive system events (e.g,. UI or clipboard events).
289
290Some components will prefer a main thread that handles asynchronous IO events
291and will use `MainThreadType::IO`. Such components are typically the ones living
292on BrowserThread::IO and being tested with a `BrowserTaskEnvironment`
293initialized with `MainThreadType::IO`.
294
295Note: This is strictly about requesting a specific `MessagePumpType` for the
296main 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
301By default non-delayed tasks posted to `base::ThreadPool` may run at any point.
302Tests 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
307thread.
308
309When `ThreadPoolExecutionMode::QUEUED` is mixed with `TimeSource::MOCK_TIME`,
310time will auto-advance to the soonest task *that is allowed to run* when
311required (i.e. it will ignore delayed tasks in the thread pool while in
312`RunLoop::Run()`). See
313`TaskEnvironmentTest.MultiThreadedMockTimeAndThreadPoolQueuedMode` for an
314example.
315
316This trait is of course irrelevant under `SingleThreadTaskEnvironment`.
317
318### ThreadingMode trait
319
320Prefer 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
323fixture manages the `TaskEnvironment` but takes `TaskEnvironmentTraits` to let
324its subclasses customize it and you really need a `SingleThreadTaskEnvironment`.
325
326## Base Fixture managed TaskEnvironment
327
328In some cases it makes sense to have the base fixture of an entire section of
329the codebase be managing the `TaskEnvironment` (e.g. [ViewsTestBase]). It's
330useful if such base fixture exposes `TaskEnvironmentTraits` to their subclasses
331so that individual tests within that domain can fine-tune their traits as
332desired.
333
334This 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 Costa9d32d0f2019-09-26 17:39:48337```c++
Gabriel Charette0b20ee62019-09-18 14:06:12338// Constructs a FooTestBase with |traits| being forwarded to its
339// TaskEnvironment. MainThreadType always defaults to UI and must not be
340// specified.
341template <typename... TaskEnvironmentTraits>
342NOINLINE explicit FooTestBase(TaskEnvironmentTraits&&... traits)
343 : task_environment_(base::test::TaskEnvironment::MainThreadType::UI,
344 std::forward<TaskEnvironmentTraits>(traits)...) {}
345```
346
347Note, if you're not familiar with traits: TaskEnvironment traits use
348[base/traits_bag.h] and will automatically complain at compile-time if an
349enum-based trait is specified more than once (i.e. subclasses will not compile
350if re-specifying `MainThreadType` in the above example).
351
352## Browser tests
353
354This is all nice and fancy for unit tests, but what about browser\_tests,
355ui\_integration\_tests, etc? Tests that subclass `content::BrowserTestBase` bring
356up the entire environment (tasks & more) by default.
357
358The downside is that you don't have fine-grained control over it like you would
359with all the `TaskEnvironment` methods.
360
361The favored paradigm is `RunLoop::Run()` + `QuitClosure()`. The asynchronous
362nature of Chromium code makes this the most reliable way to wait for an event.
363
364There 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,
367execute scripts, simulate UI interactions, etc.
368
369But 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
376So you fell in love with `TimeSource::MOCK_TIME` but now you're in a browser
377test... yeah, sorry about that...
378
379The eventual goal is to make it possible to set up TaskEnvironmentTraits from
380your test fixture just like you can override command-line, etc. but we're not
381there yet...
382
383In the mean time you can still use the old
384`base::ScopedMockTimeMessageLoopTaskRunner` to mock delayed tasks on the main
385thread (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
388You think that's a mess? Just think that it used to be this way in unit tests
389too and you'll be happy again :).
390
391## Old paradigms
392
393Here are some paradigms you might see throughout the code base and some insight
394on what to do about them (hint: copying them is not one!). Migration help is
395welcome [crbug.com/984323](https://crbug.com/984323)!
396
397### base::TestMockTimeTaskRunner
398
399This is the ancestor of `SingleThreadTaskEnvironment` + `TimeSource::MOCK_TIME`.
400It's sort of equivalent but prefer task environments for consistency.
401
402The only case where `base::TestMockTimeTaskRunner` is still the only option is
403when writing regression tests that simulate a specific task execution order
404across multiple sequences. To do so, use two `base::TestMockTimeTaskRunner` and
405have the racing components post their tasks to separate ones. You can then
406explicitly run tasks posted to each one from the main test thread in a way that
407deterministically exercises the race resolution under test. This only applies to
408task execution order races, data races still require parallel execution and this
409is the main reason `TaskEnvironment` doesn't multiplex the `ThreadPool` tasks
410onto the main thread (i.e. exercise data races, especially in the scope of
411TSAN).
412
413### base::TestSimpleTaskRunner
414
415Prefer using `SingleThreadTaskEnvironment` over `base::TestSimpleTaskRunner`.
416`TestSimpleTaskRunner` isn't as "simple" as it seems specifically because it
417runs tasks in a surprising order (delays aren't respected and nesting doesn't
418behave as usual). Should you prefer to flush all tasks regardless of delays,
419`TimeSource::MOCK_TIME` and `TaskEnvironment::FastForwardUntilNoTasksRemain()`
420have you covered.
421
422### base::NullTaskRunner
423
424Prefer `SingleThreadTaskEnvironment` or `TaskEnvironment` with
425`ThreadPoolExecutionMode::QUEUED` over `base::NullTaskRunner`. A
426`NullTaskRunner` might seem appealing, but not posting tasks is under-testing
427the potential side-effects of the code under tests. All tests should be okay if
428tasks born from their actions are run or deleted at a later point.
429
430### base::ScopedMockTimeMessageLoopTaskRunner
431
432This is the ancestor of `base::TestMockTimeTaskRunner` which is itself mostly
433deprecated. As mentioned above in the [TimeSource trait](#timesource-trait)
434section: This should never be used anymore except to mock time when there's
435already a task system in place, e.g. in browser\_tests.
436
437### SetTaskRunnerForTesting() and SetTickClockForTesting()
438
439Prior to `TaskEnvironment::TimeSource::MOCK_TIME`, many components had
440`SetClockForTesting()` in their product code. And before modern [Threading and
441Tasks], some components had SetTaskRunnerForTesting(). Neither of these
442test-only seams are required anymore now that task environments can mock those
443from under-the-hood. Cleanup in favor of modern TaskEnvironment paradigms is
444always appreciated ([crbug.com/984323](https://crbug.com/984323)).
445
446### Other helper task runners
447
448Different parts of the codebase have their own helper task runners. Please
449migrate 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
451task testing API surface once and for all.
452
453It is still okay for specific areas to have a base fixture that configures a
454default `TaskEnvironment` appropriate for that area and use the
455`TaskEnvironmentTraits` paradigm outlined in the [Base Fixture managed
456TaskEnvironment](#base-fixture-managed-taskenvironment) section above to let
457individual 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 Charette9b6c04072022-04-01 23:22:46463[base/task/thread_pool.h]: https://cs.chromium.org/chromium/src/base/task/thread_pool.h
Gabriel Charette0b20ee62019-09-18 14:06:12464[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