1 // Copyright 2013 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "base/callback_list.h"
6
7 #include <memory>
8 #include <utility>
9
10 #include "base/functional/bind.h"
11 #include "base/functional/callback_helpers.h"
12 #include "base/memory/raw_ptr.h"
13 #include "testing/gtest/include/gtest/gtest.h"
14
15 namespace base {
16 namespace {
17
18 class Listener {
19 public:
20 Listener() = default;
Listener(int scaler)21 explicit Listener(int scaler) : scaler_(scaler) {}
22 Listener(const Listener&) = delete;
23 Listener& operator=(const Listener&) = delete;
24 ~Listener() = default;
25
IncrementTotal()26 void IncrementTotal() { ++total_; }
27
IncrementByMultipleOfScaler(int x)28 void IncrementByMultipleOfScaler(int x) { total_ += x * scaler_; }
29
total() const30 int total() const { return total_; }
31
32 private:
33 int total_ = 0;
34 int scaler_ = 1;
35 };
36
37 class Remover {
38 public:
39 Remover() = default;
40 Remover(const Remover&) = delete;
41 Remover& operator=(const Remover&) = delete;
42 ~Remover() = default;
43
IncrementTotalAndRemove()44 void IncrementTotalAndRemove() {
45 ++total_;
46 removal_subscription_ = {};
47 }
48
SetSubscriptionToRemove(CallbackListSubscription subscription)49 void SetSubscriptionToRemove(CallbackListSubscription subscription) {
50 removal_subscription_ = std::move(subscription);
51 }
52
total() const53 int total() const { return total_; }
54
55 private:
56 int total_ = 0;
57 CallbackListSubscription removal_subscription_;
58 };
59
60 class Adder {
61 public:
Adder(RepeatingClosureList * cb_reg)62 explicit Adder(RepeatingClosureList* cb_reg) : cb_reg_(cb_reg) {}
63 Adder(const Adder&) = delete;
64 Adder& operator=(const Adder&) = delete;
65 ~Adder() = default;
66
AddCallback()67 void AddCallback() {
68 if (!added_) {
69 added_ = true;
70 subscription_ =
71 cb_reg_->Add(BindRepeating(&Adder::IncrementTotal, Unretained(this)));
72 }
73 }
74
IncrementTotal()75 void IncrementTotal() { ++total_; }
76
added() const77 bool added() const { return added_; }
total() const78 int total() const { return total_; }
79
80 private:
81 bool added_ = false;
82 int total_ = 0;
83 raw_ptr<RepeatingClosureList> cb_reg_;
84 CallbackListSubscription subscription_;
85 };
86
87 class Summer {
88 public:
89 Summer() = default;
90 Summer(const Summer&) = delete;
91 Summer& operator=(const Summer&) = delete;
92 ~Summer() = default;
93
AddOneParam(int a)94 void AddOneParam(int a) { value_ = a; }
AddTwoParam(int a,int b)95 void AddTwoParam(int a, int b) { value_ = a + b; }
AddThreeParam(int a,int b,int c)96 void AddThreeParam(int a, int b, int c) { value_ = a + b + c; }
AddFourParam(int a,int b,int c,int d)97 void AddFourParam(int a, int b, int c, int d) { value_ = a + b + c + d; }
AddFiveParam(int a,int b,int c,int d,int e)98 void AddFiveParam(int a, int b, int c, int d, int e) {
99 value_ = a + b + c + d + e;
100 }
AddSixParam(int a,int b,int c,int d,int e,int f)101 void AddSixParam(int a, int b, int c, int d, int e , int f) {
102 value_ = a + b + c + d + e + f;
103 }
104
value() const105 int value() const { return value_; }
106
107 private:
108 int value_ = 0;
109 };
110
111 class Counter {
112 public:
113 Counter() = default;
114 Counter(const Counter&) = delete;
115 Counter& operator=(const Counter&) = delete;
116 ~Counter() = default;
117
Increment()118 void Increment() { ++value_; }
119
value() const120 int value() const { return value_; }
121
122 private:
123 int value_ = 0;
124 };
125
126 // Sanity check that we can instantiate a CallbackList for each arity.
TEST(CallbackListTest,ArityTest)127 TEST(CallbackListTest, ArityTest) {
128 Summer s;
129
130 RepeatingCallbackList<void(int)> c1;
131 CallbackListSubscription subscription1 =
132 c1.Add(BindRepeating(&Summer::AddOneParam, Unretained(&s)));
133
134 c1.Notify(1);
135 EXPECT_EQ(1, s.value());
136
137 RepeatingCallbackList<void(int, int)> c2;
138 CallbackListSubscription subscription2 =
139 c2.Add(BindRepeating(&Summer::AddTwoParam, Unretained(&s)));
140
141 c2.Notify(1, 2);
142 EXPECT_EQ(3, s.value());
143
144 RepeatingCallbackList<void(int, int, int)> c3;
145 CallbackListSubscription subscription3 =
146 c3.Add(BindRepeating(&Summer::AddThreeParam, Unretained(&s)));
147
148 c3.Notify(1, 2, 3);
149 EXPECT_EQ(6, s.value());
150
151 RepeatingCallbackList<void(int, int, int, int)> c4;
152 CallbackListSubscription subscription4 =
153 c4.Add(BindRepeating(&Summer::AddFourParam, Unretained(&s)));
154
155 c4.Notify(1, 2, 3, 4);
156 EXPECT_EQ(10, s.value());
157
158 RepeatingCallbackList<void(int, int, int, int, int)> c5;
159 CallbackListSubscription subscription5 =
160 c5.Add(BindRepeating(&Summer::AddFiveParam, Unretained(&s)));
161
162 c5.Notify(1, 2, 3, 4, 5);
163 EXPECT_EQ(15, s.value());
164
165 RepeatingCallbackList<void(int, int, int, int, int, int)> c6;
166 CallbackListSubscription subscription6 =
167 c6.Add(BindRepeating(&Summer::AddSixParam, Unretained(&s)));
168
169 c6.Notify(1, 2, 3, 4, 5, 6);
170 EXPECT_EQ(21, s.value());
171 }
172
173 // Sanity check that closures added to the list will be run, and those removed
174 // from the list will not be run.
TEST(CallbackListTest,BasicTest)175 TEST(CallbackListTest, BasicTest) {
176 Listener a, b, c;
177 RepeatingClosureList cb_reg;
178
179 CallbackListSubscription a_subscription =
180 cb_reg.Add(BindRepeating(&Listener::IncrementTotal, Unretained(&a)));
181 CallbackListSubscription b_subscription =
182 cb_reg.Add(BindRepeating(&Listener::IncrementTotal, Unretained(&b)));
183 cb_reg.AddUnsafe(BindRepeating(&Listener::IncrementTotal, Unretained(&c)));
184
185 EXPECT_TRUE(a_subscription);
186 EXPECT_TRUE(b_subscription);
187
188 cb_reg.Notify();
189
190 EXPECT_EQ(1, a.total());
191 EXPECT_EQ(1, b.total());
192 EXPECT_EQ(1, c.total());
193
194 b_subscription = {};
195
196 CallbackListSubscription c_subscription =
197 cb_reg.Add(BindRepeating(&Listener::IncrementTotal, Unretained(&c)));
198
199 cb_reg.Notify();
200
201 EXPECT_EQ(2, a.total());
202 EXPECT_EQ(1, b.total());
203 EXPECT_EQ(3, c.total());
204 }
205
206 // Similar to BasicTest but with OnceCallbacks instead of Repeating.
TEST(CallbackListTest,OnceCallbacks)207 TEST(CallbackListTest, OnceCallbacks) {
208 OnceClosureList cb_reg;
209 Listener a, b, c;
210
211 CallbackListSubscription a_subscription =
212 cb_reg.Add(BindOnce(&Listener::IncrementTotal, Unretained(&a)));
213 CallbackListSubscription b_subscription =
214 cb_reg.Add(BindOnce(&Listener::IncrementTotal, Unretained(&b)));
215
216 EXPECT_TRUE(a_subscription);
217 EXPECT_TRUE(b_subscription);
218
219 cb_reg.Notify();
220
221 EXPECT_EQ(1, a.total());
222 EXPECT_EQ(1, b.total());
223
224 // OnceCallbacks should auto-remove themselves after calling Notify().
225 EXPECT_TRUE(cb_reg.empty());
226
227 // Destroying a subscription after the callback is canceled should not cause
228 // any problems.
229 b_subscription = {};
230
231 CallbackListSubscription c_subscription =
232 cb_reg.Add(BindOnce(&Listener::IncrementTotal, Unretained(&c)));
233
234 cb_reg.Notify();
235
236 EXPECT_EQ(1, a.total());
237 EXPECT_EQ(1, b.total());
238 EXPECT_EQ(1, c.total());
239 }
240
241 // Sanity check that callbacks with details added to the list will be run, with
242 // the correct details, and those removed from the list will not be run.
TEST(CallbackListTest,BasicTestWithParams)243 TEST(CallbackListTest, BasicTestWithParams) {
244 using CallbackListType = RepeatingCallbackList<void(int)>;
245 CallbackListType cb_reg;
246 Listener a(1), b(-1), c(1);
247
248 CallbackListSubscription a_subscription = cb_reg.Add(
249 BindRepeating(&Listener::IncrementByMultipleOfScaler, Unretained(&a)));
250 CallbackListSubscription b_subscription = cb_reg.Add(
251 BindRepeating(&Listener::IncrementByMultipleOfScaler, Unretained(&b)));
252
253 EXPECT_TRUE(a_subscription);
254 EXPECT_TRUE(b_subscription);
255
256 cb_reg.Notify(10);
257
258 EXPECT_EQ(10, a.total());
259 EXPECT_EQ(-10, b.total());
260
261 b_subscription = {};
262
263 CallbackListSubscription c_subscription = cb_reg.Add(
264 BindRepeating(&Listener::IncrementByMultipleOfScaler, Unretained(&c)));
265
266 cb_reg.Notify(10);
267
268 EXPECT_EQ(20, a.total());
269 EXPECT_EQ(-10, b.total());
270 EXPECT_EQ(10, c.total());
271 }
272
273 // Test the a callback can remove itself or a different callback from the list
274 // during iteration without invalidating the iterator.
TEST(CallbackListTest,RemoveCallbacksDuringIteration)275 TEST(CallbackListTest, RemoveCallbacksDuringIteration) {
276 RepeatingClosureList cb_reg;
277 Listener a, b;
278 Remover remover_1, remover_2;
279
280 CallbackListSubscription remover_1_sub = cb_reg.Add(
281 BindRepeating(&Remover::IncrementTotalAndRemove, Unretained(&remover_1)));
282 CallbackListSubscription remover_2_sub = cb_reg.Add(
283 BindRepeating(&Remover::IncrementTotalAndRemove, Unretained(&remover_2)));
284 CallbackListSubscription a_subscription =
285 cb_reg.Add(BindRepeating(&Listener::IncrementTotal, Unretained(&a)));
286 CallbackListSubscription b_subscription =
287 cb_reg.Add(BindRepeating(&Listener::IncrementTotal, Unretained(&b)));
288
289 // |remover_1| will remove itself.
290 remover_1.SetSubscriptionToRemove(std::move(remover_1_sub));
291 // |remover_2| will remove a.
292 remover_2.SetSubscriptionToRemove(std::move(a_subscription));
293
294 cb_reg.Notify();
295
296 // |remover_1| runs once (and removes itself), |remover_2| runs once (and
297 // removes a), |a| never runs, and |b| runs once.
298 EXPECT_EQ(1, remover_1.total());
299 EXPECT_EQ(1, remover_2.total());
300 EXPECT_EQ(0, a.total());
301 EXPECT_EQ(1, b.total());
302
303 cb_reg.Notify();
304
305 // Only |remover_2| and |b| run this time.
306 EXPECT_EQ(1, remover_1.total());
307 EXPECT_EQ(2, remover_2.total());
308 EXPECT_EQ(0, a.total());
309 EXPECT_EQ(2, b.total());
310 }
311
312 // Similar to RemoveCallbacksDuringIteration but with OnceCallbacks instead of
313 // Repeating.
TEST(CallbackListTest,RemoveOnceCallbacksDuringIteration)314 TEST(CallbackListTest, RemoveOnceCallbacksDuringIteration) {
315 OnceClosureList cb_reg;
316 Listener a, b;
317 Remover remover_1, remover_2;
318
319 CallbackListSubscription remover_1_sub = cb_reg.Add(
320 BindOnce(&Remover::IncrementTotalAndRemove, Unretained(&remover_1)));
321 CallbackListSubscription remover_2_sub = cb_reg.Add(
322 BindOnce(&Remover::IncrementTotalAndRemove, Unretained(&remover_2)));
323 CallbackListSubscription a_subscription =
324 cb_reg.Add(BindOnce(&Listener::IncrementTotal, Unretained(&a)));
325 CallbackListSubscription b_subscription =
326 cb_reg.Add(BindOnce(&Listener::IncrementTotal, Unretained(&b)));
327
328 // |remover_1| will remove itself.
329 remover_1.SetSubscriptionToRemove(std::move(remover_1_sub));
330 // |remover_2| will remove a.
331 remover_2.SetSubscriptionToRemove(std::move(a_subscription));
332
333 cb_reg.Notify();
334
335 // |remover_1| runs once (and removes itself), |remover_2| runs once (and
336 // removes a), |a| never runs, and |b| runs once.
337 EXPECT_EQ(1, remover_1.total());
338 EXPECT_EQ(1, remover_2.total());
339 EXPECT_EQ(0, a.total());
340 EXPECT_EQ(1, b.total());
341
342 cb_reg.Notify();
343
344 // Nothing runs this time.
345 EXPECT_EQ(1, remover_1.total());
346 EXPECT_EQ(1, remover_2.total());
347 EXPECT_EQ(0, a.total());
348 EXPECT_EQ(1, b.total());
349 }
350
351 // Test that a callback can add another callback to the list durning iteration
352 // without invalidating the iterator. The newly added callback should be run on
353 // the current iteration as will all other callbacks in the list.
TEST(CallbackListTest,AddCallbacksDuringIteration)354 TEST(CallbackListTest, AddCallbacksDuringIteration) {
355 RepeatingClosureList cb_reg;
356 Adder a(&cb_reg);
357 Listener b;
358 CallbackListSubscription a_subscription =
359 cb_reg.Add(BindRepeating(&Adder::AddCallback, Unretained(&a)));
360 CallbackListSubscription b_subscription =
361 cb_reg.Add(BindRepeating(&Listener::IncrementTotal, Unretained(&b)));
362
363 cb_reg.Notify();
364
365 EXPECT_EQ(1, a.total());
366 EXPECT_EQ(1, b.total());
367 EXPECT_TRUE(a.added());
368
369 cb_reg.Notify();
370
371 EXPECT_EQ(2, a.total());
372 EXPECT_EQ(2, b.total());
373 }
374
375 // Sanity check: notifying an empty list is a no-op.
TEST(CallbackListTest,EmptyList)376 TEST(CallbackListTest, EmptyList) {
377 RepeatingClosureList cb_reg;
378
379 cb_reg.Notify();
380 }
381
382 // empty() should be callable during iteration, and return false if not all the
383 // remaining callbacks in the list are null.
TEST(CallbackListTest,NonEmptyListDuringIteration)384 TEST(CallbackListTest, NonEmptyListDuringIteration) {
385 // Declare items such that |cb_reg| is torn down before the subscriptions.
386 // This ensures the removal callback's invariant that the callback list is
387 // nonempty will always hold.
388 Remover remover;
389 Listener listener;
390 CallbackListSubscription remover_sub, listener_sub;
391 RepeatingClosureList cb_reg;
392 cb_reg.set_removal_callback(base::BindRepeating(
393 [](const RepeatingClosureList* callbacks) {
394 EXPECT_FALSE(callbacks->empty());
395 },
396 Unretained(&cb_reg)));
397
398 remover_sub = cb_reg.Add(
399 BindRepeating(&Remover::IncrementTotalAndRemove, Unretained(&remover)));
400 listener_sub = cb_reg.Add(
401 BindRepeating(&Listener::IncrementTotal, Unretained(&listener)));
402
403 // |remover| will remove |listener|.
404 remover.SetSubscriptionToRemove(std::move(listener_sub));
405
406 cb_reg.Notify();
407
408 EXPECT_EQ(1, remover.total());
409 EXPECT_EQ(0, listener.total());
410 }
411
412 // empty() should be callable during iteration, and return true if all the
413 // remaining callbacks in the list are null.
TEST(CallbackListTest,EmptyListDuringIteration)414 TEST(CallbackListTest, EmptyListDuringIteration) {
415 OnceClosureList cb_reg;
416 cb_reg.set_removal_callback(base::BindRepeating(
417 [](const OnceClosureList* callbacks) { EXPECT_TRUE(callbacks->empty()); },
418 Unretained(&cb_reg)));
419
420 Remover remover;
421 Listener listener;
422 CallbackListSubscription remover_sub = cb_reg.Add(
423 BindOnce(&Remover::IncrementTotalAndRemove, Unretained(&remover)));
424 CallbackListSubscription listener_sub =
425 cb_reg.Add(BindOnce(&Listener::IncrementTotal, Unretained(&listener)));
426
427 // |remover| will remove |listener|.
428 remover.SetSubscriptionToRemove(std::move(listener_sub));
429
430 cb_reg.Notify();
431
432 EXPECT_EQ(1, remover.total());
433 EXPECT_EQ(0, listener.total());
434 }
435
TEST(CallbackListTest,RemovalCallback)436 TEST(CallbackListTest, RemovalCallback) {
437 Counter remove_count;
438 RepeatingClosureList cb_reg;
439 cb_reg.set_removal_callback(
440 BindRepeating(&Counter::Increment, Unretained(&remove_count)));
441
442 CallbackListSubscription subscription = cb_reg.Add(DoNothing());
443
444 // Removing a subscription outside of iteration signals the callback.
445 EXPECT_EQ(0, remove_count.value());
446 subscription = {};
447 EXPECT_EQ(1, remove_count.value());
448
449 // Configure two subscriptions to remove themselves.
450 Remover remover_1, remover_2;
451 CallbackListSubscription remover_1_sub = cb_reg.Add(
452 BindRepeating(&Remover::IncrementTotalAndRemove, Unretained(&remover_1)));
453 CallbackListSubscription remover_2_sub = cb_reg.Add(
454 BindRepeating(&Remover::IncrementTotalAndRemove, Unretained(&remover_2)));
455 remover_1.SetSubscriptionToRemove(std::move(remover_1_sub));
456 remover_2.SetSubscriptionToRemove(std::move(remover_2_sub));
457
458 // The callback should be signaled exactly once.
459 EXPECT_EQ(1, remove_count.value());
460 cb_reg.Notify();
461 EXPECT_EQ(2, remove_count.value());
462 EXPECT_TRUE(cb_reg.empty());
463 }
464
TEST(CallbackListTest,AbandonSubscriptions)465 TEST(CallbackListTest, AbandonSubscriptions) {
466 Listener listener;
467 CallbackListSubscription subscription;
468 {
469 RepeatingClosureList cb_reg;
470 subscription = cb_reg.Add(
471 BindRepeating(&Listener::IncrementTotal, Unretained(&listener)));
472 // Make sure the callback is signaled while cb_reg is in scope.
473 cb_reg.Notify();
474 // Exiting this scope and running the cb_reg destructor shouldn't fail.
475 }
476 EXPECT_EQ(1, listener.total());
477
478 // Destroying the subscription after the list should not cause any problems.
479 subscription = {};
480 }
481
482 // Subscriptions should be movable.
TEST(CallbackListTest,MoveSubscription)483 TEST(CallbackListTest, MoveSubscription) {
484 RepeatingClosureList cb_reg;
485 Listener listener;
486 CallbackListSubscription subscription1 = cb_reg.Add(
487 BindRepeating(&Listener::IncrementTotal, Unretained(&listener)));
488 cb_reg.Notify();
489 EXPECT_EQ(1, listener.total());
490
491 auto subscription2 = std::move(subscription1);
492 cb_reg.Notify();
493 EXPECT_EQ(2, listener.total());
494
495 subscription2 = {};
496 cb_reg.Notify();
497 EXPECT_EQ(2, listener.total());
498 }
499
TEST(CallbackListTest,CancelBeforeRunning)500 TEST(CallbackListTest, CancelBeforeRunning) {
501 OnceClosureList cb_reg;
502 Listener a;
503
504 CallbackListSubscription a_subscription =
505 cb_reg.Add(BindOnce(&Listener::IncrementTotal, Unretained(&a)));
506
507 EXPECT_TRUE(a_subscription);
508
509 // Canceling a OnceCallback before running it should not cause problems.
510 a_subscription = {};
511 cb_reg.Notify();
512
513 // |a| should not have received any callbacks.
514 EXPECT_EQ(0, a.total());
515 }
516
517 // Verifies Notify() can be called reentrantly and what its expected effects
518 // are.
TEST(CallbackListTest,ReentrantNotify)519 TEST(CallbackListTest, ReentrantNotify) {
520 RepeatingClosureList cb_reg;
521 Listener a, b, c, d;
522 CallbackListSubscription a_subscription, c_subscription;
523
524 // A callback to run for |a|.
525 const auto a_callback = [](RepeatingClosureList* callbacks, Listener* a,
526 CallbackListSubscription* a_subscription,
527 const Listener* b, Listener* c,
528 CallbackListSubscription* c_subscription,
529 Listener* d) {
530 // This should be the first callback.
531 EXPECT_EQ(0, a->total());
532 EXPECT_EQ(0, b->total());
533 EXPECT_EQ(0, c->total());
534 EXPECT_EQ(0, d->total());
535
536 // Increment |a| once.
537 a->IncrementTotal();
538
539 // Prevent |a| from being incremented again during the reentrant Notify().
540 // Since this is the first callback, this also verifies the inner Notify()
541 // doesn't assume the first callback (or all callbacks) are valid.
542 *a_subscription = {};
543
544 // Add |c| and |d| to be incremented by the reentrant Notify().
545 *c_subscription =
546 callbacks->Add(BindRepeating(&Listener::IncrementTotal, Unretained(c)));
547 CallbackListSubscription d_subscription =
548 callbacks->Add(BindRepeating(&Listener::IncrementTotal, Unretained(d)));
549
550 // Notify reentrantly. This should not increment |a|, but all the others
551 // should be incremented.
552 callbacks->Notify();
553 EXPECT_EQ(1, b->total());
554 EXPECT_EQ(1, c->total());
555 EXPECT_EQ(1, d->total());
556
557 // Since |d_subscription| is locally scoped, it should be canceled before
558 // the outer Notify() increments |d|. |c_subscription| already exists and
559 // thus |c| should get incremented again by the outer Notify() even though
560 // it wasn't scoped when that was called.
561 };
562
563 // Add |a| and |b| to the list to be notified, and notify.
564 a_subscription = cb_reg.Add(
565 BindRepeating(a_callback, Unretained(&cb_reg), Unretained(&a),
566 Unretained(&a_subscription), Unretained(&b), Unretained(&c),
567 Unretained(&c_subscription), Unretained(&d)));
568 CallbackListSubscription b_subscription =
569 cb_reg.Add(BindRepeating(&Listener::IncrementTotal, Unretained(&b)));
570
571 // Execute both notifications and check the cumulative effect.
572 cb_reg.Notify();
573 EXPECT_EQ(1, a.total());
574 EXPECT_EQ(2, b.total());
575 EXPECT_EQ(2, c.total());
576 EXPECT_EQ(1, d.total());
577 }
578
579 } // namespace
580 } // namespace base
581