This is needed in order to reduce the boilerplate involved in converting Notifications into callbacks. It is intended to be the callback version of ObserverList.
Usage Assumptions:
-Publishers outlive listeners (or at least, we can refactor things such that this is the case).
Usage Requirements:
-Typesafe: no blobs and casts.
-Decentralized: listener registers with publisher directly, instead of through a central service.
-Flexible: A listener may register callbacks with several publishers.
-Ease-of-use: Listener does not need to explicitly hold a reference to a publisher to deregister from it.
-Minimizes the risks of use-after-free: detect dangerous states (e.g dead listener is still registered) and fail in predictable ways instead. Also make it explicitly difficult to end up in those states.
BUG=268984
TEST=base/callback_list_unittest.cc
Review URL: https://chromiumcodereview.appspot.com/22877038
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@222559 0039d316-1c4b-4281-b951-d872f2087c98
diff --git a/base/callback_registry_unittest.cc b/base/callback_registry_unittest.cc
new file mode 100644
index 0000000..3459c07
--- /dev/null
+++ b/base/callback_registry_unittest.cc
@@ -0,0 +1,216 @@
+// 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_registry.h"
+
+#include "base/basictypes.h"
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/memory/scoped_ptr.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace base {
+namespace {
+
+class Listener {
+ public:
+ Listener() : total_(0), scaler_(1) {}
+ explicit Listener(int scaler) : total_(0), scaler_(scaler) {}
+ void IncrementTotal() { total_++; }
+ void IncrementByMultipleOfScaler(const int& x) { total_ += x * scaler_; }
+
+ int total_;
+
+ private:
+ int scaler_;
+ DISALLOW_COPY_AND_ASSIGN(Listener);
+};
+
+class Remover {
+ public:
+ Remover() : total_(0) {}
+ void IncrementTotalAndRemove() {
+ total_++;
+ removal_subscription_.reset();
+ }
+ void SetSubscriptionToRemove(
+ scoped_ptr<CallbackRegistry<void>::Subscription> sub) {
+ removal_subscription_ = sub.Pass();
+ }
+
+ int total_;
+
+ private:
+ scoped_ptr<CallbackRegistry<void>::Subscription> removal_subscription_;
+ DISALLOW_COPY_AND_ASSIGN(Remover);
+};
+
+class Adder {
+ public:
+ explicit Adder(CallbackRegistry<void>* cb_reg)
+ : added_(false),
+ total_(0),
+ cb_reg_(cb_reg) {}
+ void AddCallback() {
+ if (!added_) {
+ added_ = true;
+ subscription_ =
+ cb_reg_->Add(Bind(&Adder::IncrementTotal, Unretained(this)));
+ }
+ }
+ void IncrementTotal() { total_++; }
+
+ bool added_;
+ int total_;
+
+ private:
+ CallbackRegistry<void>* cb_reg_;
+ scoped_ptr<CallbackRegistry<void>::Subscription> subscription_;
+ DISALLOW_COPY_AND_ASSIGN(Adder);
+};
+
+// Sanity check that closures added to the list will be run, and those removed
+// from the list will not be run.
+TEST(CallbackRegistryTest, BasicTest) {
+ CallbackRegistry<void> cb_reg;
+ Listener a, b, c;
+
+ scoped_ptr<CallbackRegistry<void>::Subscription> a_subscription =
+ cb_reg.Add(Bind(&Listener::IncrementTotal, Unretained(&a)));
+ scoped_ptr<CallbackRegistry<void>::Subscription> b_subscription =
+ cb_reg.Add(Bind(&Listener::IncrementTotal, Unretained(&b)));
+
+ EXPECT_TRUE(a_subscription.get());
+ EXPECT_TRUE(b_subscription.get());
+
+ cb_reg.Notify();
+
+ EXPECT_EQ(1, a.total_);
+ EXPECT_EQ(1, b.total_);
+
+ b_subscription.reset();
+
+ scoped_ptr<CallbackRegistry<void>::Subscription> c_subscription =
+ cb_reg.Add(Bind(&Listener::IncrementTotal, Unretained(&c)));
+
+ cb_reg.Notify();
+
+ EXPECT_EQ(2, a.total_);
+ EXPECT_EQ(1, b.total_);
+ EXPECT_EQ(1, c.total_);
+
+ a_subscription.reset();
+ b_subscription.reset();
+ c_subscription.reset();
+}
+
+// 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(CallbackRegistryTest, BasicTestWithParams) {
+ CallbackRegistry<int> cb_reg;
+ Listener a(1), b(-1), c(1);
+
+ scoped_ptr<CallbackRegistry<int>::Subscription> a_subscription =
+ cb_reg.Add(Bind(&Listener::IncrementByMultipleOfScaler, Unretained(&a)));
+ scoped_ptr<CallbackRegistry<int>::Subscription> b_subscription =
+ cb_reg.Add(Bind(&Listener::IncrementByMultipleOfScaler, Unretained(&b)));
+
+ EXPECT_TRUE(a_subscription.get());
+ EXPECT_TRUE(b_subscription.get());
+
+ cb_reg.Notify(10);
+
+ EXPECT_EQ(10, a.total_);
+ EXPECT_EQ(-10, b.total_);
+
+ b_subscription.reset();
+
+ scoped_ptr<CallbackRegistry<int>::Subscription> c_subscription =
+ cb_reg.Add(Bind(&Listener::IncrementByMultipleOfScaler, Unretained(&c)));
+
+ cb_reg.Notify(10);
+
+ EXPECT_EQ(20, a.total_);
+ EXPECT_EQ(-10, b.total_);
+ EXPECT_EQ(10, c.total_);
+
+ a_subscription.reset();
+ b_subscription.reset();
+ c_subscription.reset();
+}
+
+// Test the a callback can remove itself or a different callback from the list
+// during iteration without invalidating the iterator.
+TEST(CallbackRegistryTest, RemoveCallbacksDuringIteration) {
+ CallbackRegistry<void> cb_reg;
+ Listener a, b;
+ Remover remover_1, remover_2;
+
+ scoped_ptr<CallbackRegistry<void>::Subscription> remover_1_subscription =
+ cb_reg.Add(Bind(&Remover::IncrementTotalAndRemove,
+ Unretained(&remover_1)));
+ scoped_ptr<CallbackRegistry<void>::Subscription> remover_2_subscription =
+ cb_reg.Add(Bind(&Remover::IncrementTotalAndRemove,
+ Unretained(&remover_2)));
+ scoped_ptr<CallbackRegistry<void>::Subscription> a_subscription =
+ cb_reg.Add(Bind(&Listener::IncrementTotal, Unretained(&a)));
+ scoped_ptr<CallbackRegistry<void>::Subscription> b_subscription =
+ cb_reg.Add(Bind(&Listener::IncrementTotal, Unretained(&b)));
+
+ // |remover_1| will remove itself.
+ remover_1.SetSubscriptionToRemove(remover_1_subscription.Pass());
+ // |remover_2| will remove a.
+ remover_2.SetSubscriptionToRemove(a_subscription.Pass());
+
+ 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_);
+}
+
+// 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(CallbackRegistryTest, AddCallbacksDuringIteration) {
+ CallbackRegistry<void> cb_reg;
+ Adder a(&cb_reg);
+ Listener b;
+ scoped_ptr<CallbackRegistry<void>::Subscription> a_subscription =
+ cb_reg.Add(Bind(&Adder::AddCallback, Unretained(&a)));
+ scoped_ptr<CallbackRegistry<void>::Subscription> b_subscription =
+ cb_reg.Add(Bind(&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(CallbackRegistryTest, EmptyList) {
+ CallbackRegistry<void> cb_reg;
+
+ cb_reg.Notify();
+}
+
+} // namespace
+} // namespace base