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