123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580 |
- // Copyright 2013 The Chromium Authors. All rights reserved.
- // Use of this source code is governed by a BSD-style license that can be
- // found in the LICENSE file.
- #include "base/callback_list.h"
- #include <memory>
- #include <utility>
- #include "base/bind.h"
- #include "base/callback_helpers.h"
- #include "base/memory/raw_ptr.h"
- #include "testing/gtest/include/gtest/gtest.h"
- namespace base {
- namespace {
- class Listener {
- public:
- Listener() = default;
- explicit Listener(int scaler) : scaler_(scaler) {}
- Listener(const Listener&) = delete;
- Listener& operator=(const Listener&) = delete;
- ~Listener() = default;
- void IncrementTotal() { ++total_; }
- void IncrementByMultipleOfScaler(int x) { total_ += x * scaler_; }
- int total() const { return total_; }
- private:
- int total_ = 0;
- int scaler_ = 1;
- };
- class Remover {
- public:
- Remover() = default;
- Remover(const Remover&) = delete;
- Remover& operator=(const Remover&) = delete;
- ~Remover() = default;
- void IncrementTotalAndRemove() {
- ++total_;
- removal_subscription_ = {};
- }
- void SetSubscriptionToRemove(CallbackListSubscription subscription) {
- removal_subscription_ = std::move(subscription);
- }
- int total() const { return total_; }
- private:
- int total_ = 0;
- CallbackListSubscription removal_subscription_;
- };
- class Adder {
- public:
- explicit Adder(RepeatingClosureList* cb_reg) : cb_reg_(cb_reg) {}
- Adder(const Adder&) = delete;
- Adder& operator=(const Adder&) = delete;
- ~Adder() = default;
- void AddCallback() {
- if (!added_) {
- added_ = true;
- subscription_ =
- cb_reg_->Add(BindRepeating(&Adder::IncrementTotal, Unretained(this)));
- }
- }
- void IncrementTotal() { ++total_; }
- bool added() const { return added_; }
- int total() const { return total_; }
- private:
- bool added_ = false;
- int total_ = 0;
- raw_ptr<RepeatingClosureList> cb_reg_;
- CallbackListSubscription subscription_;
- };
- class Summer {
- public:
- Summer() = default;
- Summer(const Summer&) = delete;
- Summer& operator=(const Summer&) = delete;
- ~Summer() = default;
- void AddOneParam(int a) { value_ = a; }
- void AddTwoParam(int a, int b) { value_ = a + b; }
- void AddThreeParam(int a, int b, int c) { value_ = a + b + c; }
- void AddFourParam(int a, int b, int c, int d) { value_ = a + b + c + d; }
- void AddFiveParam(int a, int b, int c, int d, int e) {
- value_ = a + b + c + d + e;
- }
- void AddSixParam(int a, int b, int c, int d, int e , int f) {
- value_ = a + b + c + d + e + f;
- }
- int value() const { return value_; }
- private:
- int value_ = 0;
- };
- class Counter {
- public:
- Counter() = default;
- Counter(const Counter&) = delete;
- Counter& operator=(const Counter&) = delete;
- ~Counter() = default;
- void Increment() { ++value_; }
- int value() const { return value_; }
- private:
- int value_ = 0;
- };
- // Sanity check that we can instantiate a CallbackList for each arity.
- TEST(CallbackListTest, ArityTest) {
- Summer s;
- RepeatingCallbackList<void(int)> c1;
- CallbackListSubscription subscription1 =
- c1.Add(BindRepeating(&Summer::AddOneParam, Unretained(&s)));
- c1.Notify(1);
- EXPECT_EQ(1, s.value());
- RepeatingCallbackList<void(int, int)> c2;
- CallbackListSubscription subscription2 =
- c2.Add(BindRepeating(&Summer::AddTwoParam, Unretained(&s)));
- c2.Notify(1, 2);
- EXPECT_EQ(3, s.value());
- RepeatingCallbackList<void(int, int, int)> c3;
- CallbackListSubscription subscription3 =
- c3.Add(BindRepeating(&Summer::AddThreeParam, Unretained(&s)));
- c3.Notify(1, 2, 3);
- EXPECT_EQ(6, s.value());
- RepeatingCallbackList<void(int, int, int, int)> c4;
- CallbackListSubscription subscription4 =
- c4.Add(BindRepeating(&Summer::AddFourParam, Unretained(&s)));
- c4.Notify(1, 2, 3, 4);
- EXPECT_EQ(10, s.value());
- RepeatingCallbackList<void(int, int, int, int, int)> c5;
- CallbackListSubscription subscription5 =
- c5.Add(BindRepeating(&Summer::AddFiveParam, Unretained(&s)));
- c5.Notify(1, 2, 3, 4, 5);
- EXPECT_EQ(15, s.value());
- RepeatingCallbackList<void(int, int, int, int, int, int)> c6;
- CallbackListSubscription subscription6 =
- c6.Add(BindRepeating(&Summer::AddSixParam, Unretained(&s)));
- c6.Notify(1, 2, 3, 4, 5, 6);
- EXPECT_EQ(21, s.value());
- }
- // Sanity check that closures added to the list will be run, and those removed
- // from the list will not be run.
- TEST(CallbackListTest, BasicTest) {
- Listener a, b, c;
- RepeatingClosureList cb_reg;
- CallbackListSubscription a_subscription =
- cb_reg.Add(BindRepeating(&Listener::IncrementTotal, Unretained(&a)));
- CallbackListSubscription b_subscription =
- cb_reg.Add(BindRepeating(&Listener::IncrementTotal, Unretained(&b)));
- cb_reg.AddUnsafe(BindRepeating(&Listener::IncrementTotal, Unretained(&c)));
- EXPECT_TRUE(a_subscription);
- EXPECT_TRUE(b_subscription);
- cb_reg.Notify();
- EXPECT_EQ(1, a.total());
- EXPECT_EQ(1, b.total());
- EXPECT_EQ(1, c.total());
- b_subscription = {};
- CallbackListSubscription c_subscription =
- cb_reg.Add(BindRepeating(&Listener::IncrementTotal, Unretained(&c)));
- cb_reg.Notify();
- EXPECT_EQ(2, a.total());
- EXPECT_EQ(1, b.total());
- EXPECT_EQ(3, c.total());
- }
- // Similar to BasicTest but with OnceCallbacks instead of Repeating.
- TEST(CallbackListTest, OnceCallbacks) {
- OnceClosureList cb_reg;
- Listener a, b, c;
- CallbackListSubscription a_subscription =
- cb_reg.Add(BindOnce(&Listener::IncrementTotal, Unretained(&a)));
- CallbackListSubscription b_subscription =
- cb_reg.Add(BindOnce(&Listener::IncrementTotal, Unretained(&b)));
- EXPECT_TRUE(a_subscription);
- EXPECT_TRUE(b_subscription);
- cb_reg.Notify();
- EXPECT_EQ(1, a.total());
- EXPECT_EQ(1, b.total());
- // OnceCallbacks should auto-remove themselves after calling Notify().
- EXPECT_TRUE(cb_reg.empty());
- // Destroying a subscription after the callback is canceled should not cause
- // any problems.
- b_subscription = {};
- CallbackListSubscription c_subscription =
- cb_reg.Add(BindOnce(&Listener::IncrementTotal, Unretained(&c)));
- cb_reg.Notify();
- EXPECT_EQ(1, a.total());
- EXPECT_EQ(1, b.total());
- EXPECT_EQ(1, c.total());
- }
- // Sanity check that callbacks with details added to the list will be run, with
- // the correct details, and those removed from the list will not be run.
- TEST(CallbackListTest, BasicTestWithParams) {
- using CallbackListType = RepeatingCallbackList<void(int)>;
- CallbackListType cb_reg;
- Listener a(1), b(-1), c(1);
- CallbackListSubscription a_subscription = cb_reg.Add(
- BindRepeating(&Listener::IncrementByMultipleOfScaler, Unretained(&a)));
- CallbackListSubscription b_subscription = cb_reg.Add(
- BindRepeating(&Listener::IncrementByMultipleOfScaler, Unretained(&b)));
- EXPECT_TRUE(a_subscription);
- EXPECT_TRUE(b_subscription);
- cb_reg.Notify(10);
- EXPECT_EQ(10, a.total());
- EXPECT_EQ(-10, b.total());
- b_subscription = {};
- CallbackListSubscription c_subscription = cb_reg.Add(
- BindRepeating(&Listener::IncrementByMultipleOfScaler, Unretained(&c)));
- cb_reg.Notify(10);
- EXPECT_EQ(20, a.total());
- EXPECT_EQ(-10, b.total());
- EXPECT_EQ(10, c.total());
- }
- // Test the a callback can remove itself or a different callback from the list
- // during iteration without invalidating the iterator.
- TEST(CallbackListTest, RemoveCallbacksDuringIteration) {
- RepeatingClosureList cb_reg;
- Listener a, b;
- Remover remover_1, remover_2;
- CallbackListSubscription remover_1_sub = cb_reg.Add(
- BindRepeating(&Remover::IncrementTotalAndRemove, Unretained(&remover_1)));
- CallbackListSubscription remover_2_sub = cb_reg.Add(
- BindRepeating(&Remover::IncrementTotalAndRemove, Unretained(&remover_2)));
- CallbackListSubscription a_subscription =
- cb_reg.Add(BindRepeating(&Listener::IncrementTotal, Unretained(&a)));
- CallbackListSubscription b_subscription =
- cb_reg.Add(BindRepeating(&Listener::IncrementTotal, Unretained(&b)));
- // |remover_1| will remove itself.
- remover_1.SetSubscriptionToRemove(std::move(remover_1_sub));
- // |remover_2| will remove a.
- remover_2.SetSubscriptionToRemove(std::move(a_subscription));
- cb_reg.Notify();
- // |remover_1| runs once (and removes itself), |remover_2| runs once (and
- // removes a), |a| never runs, and |b| runs once.
- EXPECT_EQ(1, remover_1.total());
- EXPECT_EQ(1, remover_2.total());
- EXPECT_EQ(0, a.total());
- EXPECT_EQ(1, b.total());
- cb_reg.Notify();
- // Only |remover_2| and |b| run this time.
- EXPECT_EQ(1, remover_1.total());
- EXPECT_EQ(2, remover_2.total());
- EXPECT_EQ(0, a.total());
- EXPECT_EQ(2, b.total());
- }
- // Similar to RemoveCallbacksDuringIteration but with OnceCallbacks instead of
- // Repeating.
- TEST(CallbackListTest, RemoveOnceCallbacksDuringIteration) {
- OnceClosureList cb_reg;
- Listener a, b;
- Remover remover_1, remover_2;
- CallbackListSubscription remover_1_sub = cb_reg.Add(
- BindOnce(&Remover::IncrementTotalAndRemove, Unretained(&remover_1)));
- CallbackListSubscription remover_2_sub = cb_reg.Add(
- BindOnce(&Remover::IncrementTotalAndRemove, Unretained(&remover_2)));
- CallbackListSubscription a_subscription =
- cb_reg.Add(BindOnce(&Listener::IncrementTotal, Unretained(&a)));
- CallbackListSubscription b_subscription =
- cb_reg.Add(BindOnce(&Listener::IncrementTotal, Unretained(&b)));
- // |remover_1| will remove itself.
- remover_1.SetSubscriptionToRemove(std::move(remover_1_sub));
- // |remover_2| will remove a.
- remover_2.SetSubscriptionToRemove(std::move(a_subscription));
- cb_reg.Notify();
- // |remover_1| runs once (and removes itself), |remover_2| runs once (and
- // removes a), |a| never runs, and |b| runs once.
- EXPECT_EQ(1, remover_1.total());
- EXPECT_EQ(1, remover_2.total());
- EXPECT_EQ(0, a.total());
- EXPECT_EQ(1, b.total());
- cb_reg.Notify();
- // Nothing runs this time.
- EXPECT_EQ(1, remover_1.total());
- EXPECT_EQ(1, remover_2.total());
- EXPECT_EQ(0, a.total());
- EXPECT_EQ(1, b.total());
- }
- // Test that a callback can add another callback to the list durning iteration
- // without invalidating the iterator. The newly added callback should be run on
- // the current iteration as will all other callbacks in the list.
- TEST(CallbackListTest, AddCallbacksDuringIteration) {
- RepeatingClosureList cb_reg;
- Adder a(&cb_reg);
- Listener b;
- CallbackListSubscription a_subscription =
- cb_reg.Add(BindRepeating(&Adder::AddCallback, Unretained(&a)));
- CallbackListSubscription b_subscription =
- cb_reg.Add(BindRepeating(&Listener::IncrementTotal, Unretained(&b)));
- cb_reg.Notify();
- EXPECT_EQ(1, a.total());
- EXPECT_EQ(1, b.total());
- EXPECT_TRUE(a.added());
- cb_reg.Notify();
- EXPECT_EQ(2, a.total());
- EXPECT_EQ(2, b.total());
- }
- // Sanity check: notifying an empty list is a no-op.
- TEST(CallbackListTest, EmptyList) {
- RepeatingClosureList cb_reg;
- cb_reg.Notify();
- }
- // empty() should be callable during iteration, and return false if not all the
- // remaining callbacks in the list are null.
- TEST(CallbackListTest, NonEmptyListDuringIteration) {
- // Declare items such that |cb_reg| is torn down before the subscriptions.
- // This ensures the removal callback's invariant that the callback list is
- // nonempty will always hold.
- Remover remover;
- Listener listener;
- CallbackListSubscription remover_sub, listener_sub;
- RepeatingClosureList cb_reg;
- cb_reg.set_removal_callback(base::BindRepeating(
- [](const RepeatingClosureList* callbacks) {
- EXPECT_FALSE(callbacks->empty());
- },
- Unretained(&cb_reg)));
- remover_sub = cb_reg.Add(
- BindRepeating(&Remover::IncrementTotalAndRemove, Unretained(&remover)));
- listener_sub = cb_reg.Add(
- BindRepeating(&Listener::IncrementTotal, Unretained(&listener)));
- // |remover| will remove |listener|.
- remover.SetSubscriptionToRemove(std::move(listener_sub));
- cb_reg.Notify();
- EXPECT_EQ(1, remover.total());
- EXPECT_EQ(0, listener.total());
- }
- // empty() should be callable during iteration, and return true if all the
- // remaining callbacks in the list are null.
- TEST(CallbackListTest, EmptyListDuringIteration) {
- OnceClosureList cb_reg;
- cb_reg.set_removal_callback(base::BindRepeating(
- [](const OnceClosureList* callbacks) { EXPECT_TRUE(callbacks->empty()); },
- Unretained(&cb_reg)));
- Remover remover;
- Listener listener;
- CallbackListSubscription remover_sub = cb_reg.Add(
- BindOnce(&Remover::IncrementTotalAndRemove, Unretained(&remover)));
- CallbackListSubscription listener_sub =
- cb_reg.Add(BindOnce(&Listener::IncrementTotal, Unretained(&listener)));
- // |remover| will remove |listener|.
- remover.SetSubscriptionToRemove(std::move(listener_sub));
- cb_reg.Notify();
- EXPECT_EQ(1, remover.total());
- EXPECT_EQ(0, listener.total());
- }
- TEST(CallbackListTest, RemovalCallback) {
- Counter remove_count;
- RepeatingClosureList cb_reg;
- cb_reg.set_removal_callback(
- BindRepeating(&Counter::Increment, Unretained(&remove_count)));
- CallbackListSubscription subscription = cb_reg.Add(DoNothing());
- // Removing a subscription outside of iteration signals the callback.
- EXPECT_EQ(0, remove_count.value());
- subscription = {};
- EXPECT_EQ(1, remove_count.value());
- // Configure two subscriptions to remove themselves.
- Remover remover_1, remover_2;
- CallbackListSubscription remover_1_sub = cb_reg.Add(
- BindRepeating(&Remover::IncrementTotalAndRemove, Unretained(&remover_1)));
- CallbackListSubscription remover_2_sub = cb_reg.Add(
- BindRepeating(&Remover::IncrementTotalAndRemove, Unretained(&remover_2)));
- remover_1.SetSubscriptionToRemove(std::move(remover_1_sub));
- remover_2.SetSubscriptionToRemove(std::move(remover_2_sub));
- // The callback should be signaled exactly once.
- EXPECT_EQ(1, remove_count.value());
- cb_reg.Notify();
- EXPECT_EQ(2, remove_count.value());
- EXPECT_TRUE(cb_reg.empty());
- }
- TEST(CallbackListTest, AbandonSubscriptions) {
- Listener listener;
- CallbackListSubscription subscription;
- {
- RepeatingClosureList cb_reg;
- subscription = cb_reg.Add(
- BindRepeating(&Listener::IncrementTotal, Unretained(&listener)));
- // Make sure the callback is signaled while cb_reg is in scope.
- cb_reg.Notify();
- // Exiting this scope and running the cb_reg destructor shouldn't fail.
- }
- EXPECT_EQ(1, listener.total());
- // Destroying the subscription after the list should not cause any problems.
- subscription = {};
- }
- // Subscriptions should be movable.
- TEST(CallbackListTest, MoveSubscription) {
- RepeatingClosureList cb_reg;
- Listener listener;
- CallbackListSubscription subscription1 = cb_reg.Add(
- BindRepeating(&Listener::IncrementTotal, Unretained(&listener)));
- cb_reg.Notify();
- EXPECT_EQ(1, listener.total());
- auto subscription2 = std::move(subscription1);
- cb_reg.Notify();
- EXPECT_EQ(2, listener.total());
- subscription2 = {};
- cb_reg.Notify();
- EXPECT_EQ(2, listener.total());
- }
- TEST(CallbackListTest, CancelBeforeRunning) {
- OnceClosureList cb_reg;
- Listener a;
- CallbackListSubscription a_subscription =
- cb_reg.Add(BindOnce(&Listener::IncrementTotal, Unretained(&a)));
- EXPECT_TRUE(a_subscription);
- // Canceling a OnceCallback before running it should not cause problems.
- a_subscription = {};
- cb_reg.Notify();
- // |a| should not have received any callbacks.
- EXPECT_EQ(0, a.total());
- }
- // Verifies Notify() can be called reentrantly and what its expected effects
- // are.
- TEST(CallbackListTest, ReentrantNotify) {
- RepeatingClosureList cb_reg;
- Listener a, b, c, d;
- CallbackListSubscription a_subscription, c_subscription;
- // A callback to run for |a|.
- const auto a_callback = [](RepeatingClosureList* callbacks, Listener* a,
- CallbackListSubscription* a_subscription,
- const Listener* b, Listener* c,
- CallbackListSubscription* c_subscription,
- Listener* d) {
- // This should be the first callback.
- EXPECT_EQ(0, a->total());
- EXPECT_EQ(0, b->total());
- EXPECT_EQ(0, c->total());
- EXPECT_EQ(0, d->total());
- // Increment |a| once.
- a->IncrementTotal();
- // Prevent |a| from being incremented again during the reentrant Notify().
- // Since this is the first callback, this also verifies the inner Notify()
- // doesn't assume the first callback (or all callbacks) are valid.
- *a_subscription = {};
- // Add |c| and |d| to be incremented by the reentrant Notify().
- *c_subscription =
- callbacks->Add(BindRepeating(&Listener::IncrementTotal, Unretained(c)));
- CallbackListSubscription d_subscription =
- callbacks->Add(BindRepeating(&Listener::IncrementTotal, Unretained(d)));
- // Notify reentrantly. This should not increment |a|, but all the others
- // should be incremented.
- callbacks->Notify();
- EXPECT_EQ(1, b->total());
- EXPECT_EQ(1, c->total());
- EXPECT_EQ(1, d->total());
- // Since |d_subscription| is locally scoped, it should be canceled before
- // the outer Notify() increments |d|. |c_subscription| already exists and
- // thus |c| should get incremented again by the outer Notify() even though
- // it wasn't scoped when that was called.
- };
- // Add |a| and |b| to the list to be notified, and notify.
- a_subscription = cb_reg.Add(
- BindRepeating(a_callback, Unretained(&cb_reg), Unretained(&a),
- Unretained(&a_subscription), Unretained(&b), Unretained(&c),
- Unretained(&c_subscription), Unretained(&d)));
- CallbackListSubscription b_subscription =
- cb_reg.Add(BindRepeating(&Listener::IncrementTotal, Unretained(&b)));
- // Execute both notifications and check the cumulative effect.
- cb_reg.Notify();
- EXPECT_EQ(1, a.total());
- EXPECT_EQ(2, b.total());
- EXPECT_EQ(2, c.total());
- EXPECT_EQ(1, d.total());
- }
- } // namespace
- } // namespace base
|