Movatterモバイル変換


[0]ホーム

URL:


Google Git
Sign in
chromium /chromium /src /refs/heads/main /. /base /callback_list_unittest.cc
blob: 7474278525e5efecc0de903809a54d366896d524 [file] [log] [blame] [edit]
// Copyright 2013 The Chromium Authors
// 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/functional/bind.h"
#include"base/functional/callback_helpers.h"
#include"base/memory/raw_ptr.h"
#include"base/test/test_future.h"
#include"testing/gtest/include/gtest/gtest.h"
namespace base{
namespace{
classListener{
public:
Listener()=default;
explicitListener(int scaler): scaler_(scaler){}
Listener(constListener&)=delete;
Listener&operator=(constListener&)=delete;
~Listener()=default;
voidIncrementTotal(){++total_;}
voidIncrementByMultipleOfScaler(int x){ total_+= x* scaler_;}
int total()const{return total_;}
private:
int total_=0;
int scaler_=1;
};
classRemover{
public:
Remover()=default;
Remover(constRemover&)=delete;
Remover&operator=(constRemover&)=delete;
~Remover()=default;
voidIncrementTotalAndRemove(){
++total_;
removal_subscription_={};
}
voidSetSubscriptionToRemove(CallbackListSubscription subscription){
removal_subscription_= std::move(subscription);
}
int total()const{return total_;}
private:
int total_=0;
CallbackListSubscription removal_subscription_;
};
classAdder{
public:
explicitAdder(RepeatingClosureList* cb_reg): cb_reg_(cb_reg){}
Adder(constAdder&)=delete;
Adder&operator=(constAdder&)=delete;
~Adder()=default;
voidAddCallback(){
if(!added_){
added_=true;
subscription_=
cb_reg_->Add(BindRepeating(&Adder::IncrementTotal,Unretained(this)));
}
}
voidIncrementTotal(){++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_;
};
classSummer{
public:
Summer()=default;
Summer(constSummer&)=delete;
Summer&operator=(constSummer&)=delete;
~Summer()=default;
voidAddOneParam(int a){ value_= a;}
voidAddTwoParam(int a,int b){ value_= a+ b;}
voidAddThreeParam(int a,int b,int c){ value_= a+ b+ c;}
voidAddFourParam(int a,int b,int c,int d){ value_= a+ b+ c+ d;}
voidAddFiveParam(int a,int b,int c,int d,int e){
value_= a+ b+ c+ d+ e;
}
voidAddSixParam(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;
};
classCounter{
public:
Counter()=default;
Counter(constCounter&)=delete;
Counter&operator=(constCounter&)=delete;
~Counter()=default;
voidIncrement(){++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){
usingCallbackListType=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(
[](constRepeatingClosureList* 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(
[](constOnceClosureList* 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|.
constauto a_callback=[](RepeatingClosureList* callbacks,Listener* a,
CallbackListSubscription* a_subscription,
constListener* 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());
}
TEST(CallbackListTest,ClearPreventsInvocation){
Listener listener;
RepeatingClosureList cb_reg;
CallbackListSubscription subscription= cb_reg.Add(
BindRepeating(&Listener::IncrementTotal,Unretained(&listener)));
cb_reg.Clear();
cb_reg.Notify();
EXPECT_EQ(0, listener.total());
}
TEST(CallbackListTest,ClearInvokesRemovalCallback){
RepeatingClosureList cb_reg;
base::test::TestFuture<void> removal_callback;
cb_reg.set_removal_callback(removal_callback.GetRepeatingCallback());
cb_reg.AddUnsafe(base::DoNothing());
// Removing the callback calls the removal callback.
cb_reg.Clear();
ASSERT_TRUE(removal_callback.IsReady());
removal_callback.Clear();
// No callback registered, removal callback isn't called.
cb_reg.Clear();
ASSERT_FALSE(removal_callback.IsReady());
}
TEST(CallbackListTest,ClearInvalidatesSubscription){
RepeatingClosureList cb_reg;
CallbackListSubscription subscription= cb_reg.Add(base::DoNothing());
// `Clear()` invalidates `subscription`. `subscription` does nothing when it's
// destroyed.
cb_reg.Clear();
subscription={};
}
}// namespace
}// namespace base

[8]ページ先頭

©2009-2025 Movatter.jp