| Index: base/callback_list_unittest.cc
|
| diff --git a/base/callback_list_unittest.cc b/base/callback_list_unittest.cc
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..3bc856a18509d3043c673c7d36dc533831f6c423
|
| --- /dev/null
|
| +++ b/base/callback_list_unittest.cc
|
| @@ -0,0 +1,365 @@
|
| +// 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 <vector>
|
| +
|
| +#include "base/bind_helpers.h"
|
| +#include "base/compiler_specific.h"
|
| +#include "base/memory/scoped_ptr.h"
|
| +#include "base/memory/weak_ptr.h"
|
| +#include "testing/gtest/include/gtest/gtest.h"
|
| +
|
| +namespace base {
|
| +namespace {
|
| +
|
| +typedef base::Callback<void(const int&)> OneParamCallback;
|
| +typedef CallbackListWithDetails<int> TestCallbackListWithParam;
|
| +
|
| +class Listener {
|
| + public:
|
| + explicit Listener() : total_(0), scaler_(1) {}
|
| + Listener(int scaler) : total_(0), scaler_(scaler) {}
|
| + ~Listener() {}
|
| + void IncrementTotal() { total_++; }
|
| + void MultiplyTotal(const int& x) { total_ += x * scaler_; }
|
| +
|
| + int total_;
|
| + private:
|
| + int scaler_;
|
| + DISALLOW_COPY_AND_ASSIGN(Listener);
|
| +};
|
| +
|
| +class Remover {
|
| + public:
|
| + explicit Remover() : total_(0), removal_cb_(base::Closure()) {}
|
| + ~Remover() {}
|
| + void IncrementTotalAndRemove() {
|
| + total_++;
|
| + removal_cb_.Run();
|
| + }
|
| + void SetCallback(const base::Closure& cb) { removal_cb_ = cb; }
|
| +
|
| + int total_;
|
| + private:
|
| + base::Closure removal_cb_;
|
| + DISALLOW_COPY_AND_ASSIGN(Remover);
|
| +};
|
| +
|
| +class Adder {
|
| + public:
|
| + explicit Adder(CallbackList* cb_list)
|
| + : added_(false),
|
| + total_(0),
|
| + cb_list_(cb_list) {}
|
| + ~Adder() {}
|
| + void AddCallback() {
|
| + if (!added_) {
|
| + added_ = true;
|
| + cb_list_->Add(
|
| + base::Bind(&Adder::IncrementTotal, base::Unretained(this)));
|
| + }
|
| + }
|
| + void IncrementTotal() { total_++; }
|
| +
|
| + bool added_;
|
| + int total_;
|
| + private:
|
| + CallbackList* cb_list_;
|
| + DISALLOW_COPY_AND_ASSIGN(Adder);
|
| +};
|
| +
|
| +class Clearer {
|
| + public:
|
| + explicit Clearer(CallbackList* cb_list)
|
| + : added_(false),
|
| + total_(0),
|
| + cb_list_(cb_list) {}
|
| + ~Clearer() {}
|
| + void ClearListAndAddCallback() {
|
| + cb_list_->Clear();
|
| + cb_list_->Add(
|
| + base::Bind(&Clearer::IncrementTotal, base::Unretained(this)));
|
| + added_ = true;
|
| + }
|
| + void IncrementTotal() { total_++; }
|
| +
|
| + bool added_;
|
| + int total_;
|
| + private:
|
| + CallbackList* cb_list_;
|
| + DISALLOW_COPY_AND_ASSIGN(Clearer);
|
| +};
|
| +
|
| +class ListDestructor {
|
| + public:
|
| + explicit ListDestructor(CallbackList* cb_list)
|
| + : cb_list_(cb_list) {}
|
| + ~ListDestructor() {}
|
| + void DestroyList() {
|
| + delete cb_list_;
|
| + }
|
| +
|
| + private:
|
| + CallbackList* cb_list_;
|
| + DISALLOW_COPY_AND_ASSIGN(ListDestructor);
|
| +};
|
| +
|
| +// Sanity check that closures added to the list will be run, and those removed
|
| +// from the list will not be run.
|
| +TEST(CallbackListTest, BasicTest) {
|
| + CallbackList cb_list(CALLBACKS_NOTIFY_ALL);
|
| + Listener a, b, c;
|
| +
|
| + base::Closure remove_a = cb_list.Add(
|
| + base::Bind(&Listener::IncrementTotal, base::Unretained(&a)));
|
| + base::Closure remove_b = cb_list.Add(
|
| + base::Bind(&Listener::IncrementTotal, base::Unretained(&b)));
|
| +
|
| + EXPECT_FALSE(remove_a.is_null());
|
| + EXPECT_FALSE(remove_b.is_null());
|
| +
|
| + cb_list.Run();
|
| +
|
| + EXPECT_EQ(1, a.total_);
|
| + EXPECT_EQ(1, b.total_);
|
| +
|
| + remove_b.Run();
|
| +
|
| + base::Closure remove_c = cb_list.Add(
|
| + base::Bind(&Listener::IncrementTotal, base::Unretained(&c)));
|
| +
|
| + cb_list.Run();
|
| +
|
| + EXPECT_EQ(2, a.total_);
|
| + EXPECT_EQ(1, b.total_);
|
| + EXPECT_EQ(1, c.total_);
|
| +
|
| + remove_a.Run();
|
| + remove_b.Run();
|
| + remove_c.Run();
|
| +
|
| + EXPECT_FALSE(cb_list.might_have_callbacks());
|
| +}
|
| +
|
| +// 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) {
|
| + TestCallbackListWithParam cb_list(CALLBACKS_NOTIFY_ALL);
|
| + Listener a(1), b(-1), c(1);
|
| +
|
| + base::Closure remove_a = cb_list.Add(
|
| + base::Bind(&Listener::MultiplyTotal, base::Unretained(&a)));
|
| + base::Closure remove_b = cb_list.Add(
|
| + base::Bind(&Listener::MultiplyTotal, base::Unretained(&b)));
|
| +
|
| + EXPECT_FALSE(remove_a.is_null());
|
| + EXPECT_FALSE(remove_b.is_null());
|
| +
|
| + cb_list.Run(10);
|
| +
|
| + EXPECT_EQ(10, a.total_);
|
| + EXPECT_EQ(-10, b.total_);
|
| +
|
| + remove_b.Run();
|
| +
|
| + base::Closure remove_c = cb_list.Add(
|
| + base::Bind(&Listener::MultiplyTotal, base::Unretained(&c)));
|
| +
|
| + cb_list.Run(10);
|
| +
|
| + EXPECT_EQ(20, a.total_);
|
| + EXPECT_EQ(-10, b.total_);
|
| + EXPECT_EQ(10, c.total_);
|
| +
|
| + remove_a.Run();
|
| + remove_b.Run();
|
| + remove_c.Run();
|
| +
|
| + EXPECT_FALSE(cb_list.might_have_callbacks());
|
| +}
|
| +
|
| +// Test the a callback can remove itself or a different callback from the list
|
| +// during iteration without invalidating the iterator.
|
| +TEST(CallbackListTest, RemoveCallbacksDuringIteration) {
|
| + CallbackList cb_list(CALLBACKS_NOTIFY_ALL);
|
| + Listener a, b;
|
| + Remover remover_1, remover_2;
|
| +
|
| + base::Closure remover_1_cb = cb_list.Add(
|
| + base::Bind(&Remover::IncrementTotalAndRemove,
|
| + base::Unretained(&remover_1)));
|
| + base::Closure remover_2_cb = cb_list.Add(
|
| + base::Bind(&Remover::IncrementTotalAndRemove,
|
| + base::Unretained(&remover_2)));
|
| + base::Closure a_cb = cb_list.Add(
|
| + base::Bind(&Listener::IncrementTotal, base::Unretained(&a)));
|
| + base::Closure b_cb = cb_list.Add(
|
| + base::Bind(&Listener::IncrementTotal, base::Unretained(&b)));
|
| +
|
| + // |remover_1| will remove itself.
|
| + remover_1.SetCallback(remover_1_cb);
|
| + // |remover_2| will remove a.
|
| + remover_2.SetCallback(a_cb);
|
| +
|
| + cb_list.Run();
|
| +
|
| + // |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_list.Run();
|
| +
|
| + // 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_);
|
| +
|
| + EXPECT_TRUE(cb_list.might_have_callbacks());
|
| +
|
| + remover_2_cb.Run();
|
| + b_cb.Run();
|
| +
|
| + EXPECT_FALSE(cb_list.might_have_callbacks());
|
| +}
|
| +
|
| +// Test that a callback can add another callback to the list durning iteration
|
| +// without invalidating the iterator.
|
| +// - If the CallbackNotificationType is |CALLBACKS_NOTIFY_ALL|, the newly added
|
| +// callback will be run on the current iteration.
|
| +// - All other callbacks in the list will be run as well.
|
| +TEST(CallbackListTest, AddCallbacksDuringIteration) {
|
| + CallbackList cb_list(CALLBACKS_NOTIFY_ALL);
|
| + Adder a(&cb_list);
|
| + Listener b;
|
| + cb_list.Add(
|
| + base::Bind(&Adder::AddCallback, base::Unretained(&a)));
|
| + cb_list.Add(
|
| + base::Bind(&Listener::IncrementTotal, base::Unretained(&b)));
|
| +
|
| + cb_list.Run();
|
| +
|
| + EXPECT_EQ(1, a.total_);
|
| + EXPECT_EQ(1, b.total_);
|
| + EXPECT_TRUE(a.added_);
|
| +
|
| + cb_list.Run();
|
| +
|
| + EXPECT_EQ(2, a.total_);
|
| + EXPECT_EQ(2, b.total_);
|
| +}
|
| +
|
| +// Test that a callback can add another callback to the list durning iteration
|
| +// without invalidating the iterator.
|
| +// - If the CallbackNotificationType is |CALLBACKS_NOTIFY_EXISTING_ONLY|, the
|
| +// newly added will *not* be run during the current iteration, but will run on
|
| +// subsequent iterations.
|
| +// - All other callbacks in the list will be run as well.
|
| +TEST(CallbackListTest, AddCallbacksDuringIteration_Existing) {
|
| + CallbackList cb_list(CALLBACKS_NOTIFY_EXISTING_ONLY);
|
| + Adder a(&cb_list);
|
| + Listener b;
|
| + cb_list.Add(
|
| + base::Bind(&Adder::AddCallback, base::Unretained(&a)));
|
| + cb_list.Add(
|
| + base::Bind(&Listener::IncrementTotal, base::Unretained(&b)));
|
| +
|
| + cb_list.Run();
|
| +
|
| + // |a| should have been added, but not run yet, as we are in
|
| + // |CALLBACKS_NOTIFY_EXISTING_ONLY| mode.
|
| + EXPECT_EQ(0, a.total_);
|
| + EXPECT_EQ(1, b.total_);
|
| + EXPECT_TRUE(a.added_);
|
| +
|
| + cb_list.Run();
|
| +
|
| + // Now |a| should have run.
|
| + EXPECT_EQ(1, a.total_);
|
| + EXPECT_EQ(2, b.total_);
|
| +}
|
| +
|
| +// Test that the list behaves as expected when callbacks are both added and
|
| +// removed during iteration. In particular:
|
| +// - If the CallbackNotificationType is |CALLBACKS_NOTIFY_EXISTING_ONLY|, the
|
| +// newly added callback should *not* be called during the current iteration.
|
| +// - The callback which was removed should not run again after removal
|
| +TEST(CallbackListTest, AddAndRemoveCallbacksDuringIteration_Existing) {
|
| + CallbackList cb_list(CALLBACKS_NOTIFY_EXISTING_ONLY);
|
| + Adder a(&cb_list);
|
| + Remover b;
|
| + Listener c;
|
| +
|
| + base::Closure c_cb = cb_list.Add(
|
| + base::Bind(&Listener::IncrementTotal, base::Unretained(&c)));
|
| +
|
| + // |b| removes |c|.
|
| + b.SetCallback(c_cb);
|
| + cb_list.Add(
|
| + base::Bind(&Remover::IncrementTotalAndRemove, base::Unretained(&b)));
|
| +
|
| + // |a| adds a new callback.
|
| + cb_list.Add(base::Bind(&Adder::AddCallback, base::Unretained(&a)));
|
| +
|
| + cb_list.Run();
|
| +
|
| + // |c| ran once, new callback (|a|) should have been added but not yet run.
|
| + EXPECT_EQ(1, c.total_);
|
| + EXPECT_EQ(0, a.total_);
|
| + EXPECT_TRUE(a.added_);
|
| +
|
| + cb_list.Run();
|
| +
|
| + // Now |a| should have been run.
|
| + EXPECT_EQ(1, c.total_);
|
| + EXPECT_EQ(1, a.total_);
|
| +}
|
| +
|
| +// Test that if we clear the list during iteration, no callbacks are run after
|
| +// the one that did the clearing.
|
| +TEST(CallbackListTest, ClearCallbacksDuringIteration) {
|
| + CallbackList cb_list(CALLBACKS_NOTIFY_ALL);
|
| + Clearer a(&cb_list);
|
| + Listener b;
|
| + cb_list.Add(
|
| + base::Bind(&Clearer::ClearListAndAddCallback, base::Unretained(&a)));
|
| + cb_list.Add(
|
| + base::Bind(&Listener::IncrementTotal, base::Unretained(&b)));
|
| +
|
| + cb_list.Run();
|
| +
|
| + EXPECT_EQ(1, a.total_);
|
| + // |b| never runs because we cleared the list first.
|
| + EXPECT_EQ(0, b.total_);
|
| + EXPECT_TRUE(a.added_);
|
| +}
|
| +
|
| +// Test that if the iterator outlives the list (that is, for some reason or
|
| +// we delete the list mid-iteration) then:
|
| +// - no callbacks run after deletion.
|
| +// - the list is cleaned up properly (there will be memory errors if this is not
|
| +// the case).
|
| +TEST(CallbackListTest, IteratorOutlivesList) {
|
| + CallbackList* cb_list =
|
| + new CallbackList(CALLBACKS_NOTIFY_ALL);
|
| + ListDestructor destructor(cb_list);
|
| + cb_list->Add(
|
| + base::Bind(&ListDestructor::DestroyList, base::Unretained(&destructor)));
|
| + Listener a;
|
| + cb_list->Add(
|
| + base::Bind(&Listener::IncrementTotal, base::Unretained(&a)));
|
| +
|
| + cb_list->Run();
|
| +
|
| + // |a| never gets called, as |cb_list| got deleted first.
|
| + EXPECT_EQ(0, a.total_);
|
| +}
|
| +
|
| +} // namespace
|
| +} // namespace base
|
|
|