Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 // Copyright 2013 The Chromium Authors. All rights reserved. | |
| 2 // Use of this source code is governed by a BSD-style license that can be | |
| 3 // found in the LICENSE file. | |
| 4 | |
| 5 #include "base/callback_list.h" | |
| 6 | |
| 7 #include <vector> | |
| 8 | |
| 9 #include "base/bind_helpers.h" | |
| 10 #include "base/compiler_specific.h" | |
| 11 #include "base/memory/scoped_ptr.h" | |
| 12 #include "base/memory/weak_ptr.h" | |
| 13 #include "testing/gtest/include/gtest/gtest.h" | |
| 14 | |
| 15 namespace base { | |
| 16 namespace { | |
| 17 | |
| 18 class Listener { | |
| 19 public: | |
| 20 explicit Listener() : total_(0), scaler_(1) {} | |
| 21 Listener(int scaler) : total_(0), scaler_(scaler) {} | |
|
awong
2013/09/04 18:48:21
explicit
Cait (Slow)
2013/09/04 22:09:25
Done.
| |
| 22 ~Listener() {} | |
|
awong
2013/09/04 18:48:21
No need to declare trivial destructors. Please rem
Cait (Slow)
2013/09/04 22:09:25
Done.
| |
| 23 void IncrementTotal() { total_++; } | |
| 24 void MultiplyTotal(const int& x) { total_ += x * scaler_; } | |
| 25 | |
| 26 int total_; | |
| 27 private: | |
| 28 int scaler_; | |
| 29 DISALLOW_COPY_AND_ASSIGN(Listener); | |
| 30 }; | |
| 31 | |
| 32 class Remover { | |
| 33 public: | |
| 34 explicit Remover() : total_(0), removal_cb_(base::Closure()) {} | |
|
awong
2013/09/04 18:48:21
-explicit
Cait (Slow)
2013/09/04 22:09:25
Done.
| |
| 35 ~Remover() {} | |
| 36 void IncrementTotalAndRemove() { | |
| 37 total_++; | |
| 38 removal_cb_.Run(); | |
| 39 } | |
| 40 void SetCallback(const base::Closure& cb) { removal_cb_ = cb; } | |
| 41 | |
| 42 int total_; | |
| 43 private: | |
| 44 base::Closure removal_cb_; | |
| 45 DISALLOW_COPY_AND_ASSIGN(Remover); | |
| 46 }; | |
| 47 | |
| 48 class Adder { | |
| 49 public: | |
| 50 explicit Adder(CallbackList<void>* cb_list) | |
| 51 : added_(false), | |
| 52 total_(0), | |
| 53 cb_list_(cb_list) {} | |
| 54 ~Adder() {} | |
| 55 void AddCallback() { | |
| 56 if (!added_) { | |
| 57 added_ = true; | |
| 58 cb_list_->Add( | |
| 59 base::Bind(&Adder::IncrementTotal, base::Unretained(this))); | |
| 60 } | |
| 61 } | |
| 62 void IncrementTotal() { total_++; } | |
| 63 | |
| 64 bool added_; | |
| 65 int total_; | |
| 66 private: | |
| 67 CallbackList<void>* cb_list_; | |
| 68 DISALLOW_COPY_AND_ASSIGN(Adder); | |
| 69 }; | |
| 70 | |
| 71 class Clearer { | |
| 72 public: | |
| 73 explicit Clearer(CallbackList<void>* cb_list) | |
| 74 : added_(false), | |
| 75 total_(0), | |
| 76 cb_list_(cb_list) {} | |
| 77 ~Clearer() {} | |
| 78 void ClearListAndAddCallback() { | |
| 79 cb_list_->Clear(); | |
| 80 cb_list_->Add( | |
| 81 base::Bind(&Clearer::IncrementTotal, base::Unretained(this))); | |
| 82 added_ = true; | |
| 83 } | |
| 84 void IncrementTotal() { total_++; } | |
| 85 | |
| 86 bool added_; | |
| 87 int total_; | |
| 88 private: | |
| 89 CallbackList<void>* cb_list_; | |
| 90 DISALLOW_COPY_AND_ASSIGN(Clearer); | |
| 91 }; | |
| 92 | |
| 93 class ListDestructor { | |
| 94 public: | |
| 95 explicit ListDestructor(CallbackList<void>* cb_list) | |
| 96 : cb_list_(cb_list) {} | |
| 97 ~ListDestructor() {} | |
| 98 void DestroyList() { | |
| 99 delete cb_list_; | |
| 100 } | |
| 101 | |
| 102 private: | |
| 103 CallbackList<void>* cb_list_; | |
| 104 DISALLOW_COPY_AND_ASSIGN(ListDestructor); | |
| 105 }; | |
| 106 | |
| 107 // Sanity check that closures added to the list will be run, and those removed | |
| 108 // from the list will not be run. | |
| 109 TEST(CallbackListTest, BasicTest) { | |
| 110 CallbackList<void> cb_list(CALLBACKS_NOTIFY_ALL); | |
| 111 Listener a, b, c; | |
| 112 | |
| 113 base::Closure remove_a = cb_list.Add( | |
| 114 base::Bind(&Listener::IncrementTotal, base::Unretained(&a))); | |
| 115 base::Closure remove_b = cb_list.Add( | |
| 116 base::Bind(&Listener::IncrementTotal, base::Unretained(&b))); | |
| 117 | |
| 118 EXPECT_FALSE(remove_a.is_null()); | |
| 119 EXPECT_FALSE(remove_b.is_null()); | |
| 120 | |
| 121 cb_list.Run(); | |
| 122 | |
| 123 EXPECT_EQ(1, a.total_); | |
| 124 EXPECT_EQ(1, b.total_); | |
| 125 | |
| 126 remove_b.Run(); | |
| 127 | |
| 128 base::Closure remove_c = cb_list.Add( | |
| 129 base::Bind(&Listener::IncrementTotal, base::Unretained(&c))); | |
| 130 | |
| 131 cb_list.Run(); | |
| 132 | |
| 133 EXPECT_EQ(2, a.total_); | |
| 134 EXPECT_EQ(1, b.total_); | |
| 135 EXPECT_EQ(1, c.total_); | |
| 136 | |
| 137 remove_a.Run(); | |
| 138 remove_b.Run(); | |
| 139 remove_c.Run(); | |
| 140 | |
| 141 EXPECT_FALSE(cb_list.might_have_callbacks()); | |
| 142 } | |
| 143 | |
| 144 // Sanity check that callbacks with details added to the list will be run, with | |
| 145 // the correct details, and those removed from the list will not be run. | |
|
awong
2013/09/04 18:48:21
Thank you for adding these comments! I like them!
| |
| 146 TEST(CallbackListTest, BasicTestWithParams) { | |
| 147 CallbackList<int> cb_list(CALLBACKS_NOTIFY_ALL); | |
| 148 Listener a(1), b(-1), c(1); | |
| 149 | |
| 150 base::Closure remove_a = cb_list.Add( | |
| 151 base::Bind(&Listener::MultiplyTotal, base::Unretained(&a))); | |
| 152 base::Closure remove_b = cb_list.Add( | |
| 153 base::Bind(&Listener::MultiplyTotal, base::Unretained(&b))); | |
| 154 | |
| 155 EXPECT_FALSE(remove_a.is_null()); | |
| 156 EXPECT_FALSE(remove_b.is_null()); | |
| 157 | |
| 158 cb_list.Run(10); | |
| 159 | |
| 160 EXPECT_EQ(10, a.total_); | |
| 161 EXPECT_EQ(-10, b.total_); | |
| 162 | |
| 163 remove_b.Run(); | |
| 164 | |
| 165 base::Closure remove_c = cb_list.Add( | |
| 166 base::Bind(&Listener::MultiplyTotal, base::Unretained(&c))); | |
| 167 | |
| 168 cb_list.Run(10); | |
| 169 | |
| 170 EXPECT_EQ(20, a.total_); | |
| 171 EXPECT_EQ(-10, b.total_); | |
| 172 EXPECT_EQ(10, c.total_); | |
| 173 | |
| 174 remove_a.Run(); | |
| 175 remove_b.Run(); | |
| 176 remove_c.Run(); | |
| 177 | |
| 178 EXPECT_FALSE(cb_list.might_have_callbacks()); | |
| 179 } | |
| 180 | |
| 181 // Test the a callback can remove itself or a different callback from the list | |
| 182 // during iteration without invalidating the iterator. | |
| 183 TEST(CallbackListTest, RemoveCallbacksDuringIteration) { | |
| 184 CallbackList<void> cb_list(CALLBACKS_NOTIFY_ALL); | |
| 185 Listener a, b; | |
| 186 Remover remover_1, remover_2; | |
| 187 | |
| 188 base::Closure remover_1_cb = cb_list.Add( | |
| 189 base::Bind(&Remover::IncrementTotalAndRemove, | |
| 190 base::Unretained(&remover_1))); | |
| 191 base::Closure remover_2_cb = cb_list.Add( | |
| 192 base::Bind(&Remover::IncrementTotalAndRemove, | |
| 193 base::Unretained(&remover_2))); | |
| 194 base::Closure a_cb = cb_list.Add( | |
| 195 base::Bind(&Listener::IncrementTotal, base::Unretained(&a))); | |
| 196 base::Closure b_cb = cb_list.Add( | |
| 197 base::Bind(&Listener::IncrementTotal, base::Unretained(&b))); | |
| 198 | |
| 199 // |remover_1| will remove itself. | |
| 200 remover_1.SetCallback(remover_1_cb); | |
| 201 // |remover_2| will remove a. | |
| 202 remover_2.SetCallback(a_cb); | |
| 203 | |
| 204 cb_list.Run(); | |
| 205 | |
| 206 // |remover_1| runs once (and removes itself), |remover_2| runs once (and | |
| 207 // removes a), |a| never runs, and |b| runs once. | |
| 208 EXPECT_EQ(1, remover_1.total_); | |
| 209 EXPECT_EQ(1, remover_2.total_); | |
| 210 EXPECT_EQ(0, a.total_); | |
| 211 EXPECT_EQ(1, b.total_); | |
| 212 | |
| 213 cb_list.Run(); | |
| 214 | |
| 215 // Only |remover_2| and |b| run this time. | |
| 216 EXPECT_EQ(1, remover_1.total_); | |
| 217 EXPECT_EQ(2, remover_2.total_); | |
| 218 EXPECT_EQ(0, a.total_); | |
| 219 EXPECT_EQ(2, b.total_); | |
| 220 | |
| 221 EXPECT_TRUE(cb_list.might_have_callbacks()); | |
| 222 | |
| 223 remover_2_cb.Run(); | |
| 224 b_cb.Run(); | |
| 225 | |
| 226 EXPECT_FALSE(cb_list.might_have_callbacks()); | |
| 227 } | |
| 228 | |
| 229 // Test that a callback can add another callback to the list durning iteration | |
| 230 // without invalidating the iterator. | |
| 231 // - If the CallbackNotificationType is |CALLBACKS_NOTIFY_ALL|, the newly added | |
| 232 // callback will be run on the current iteration. | |
| 233 // - All other callbacks in the list will be run as well. | |
| 234 TEST(CallbackListTest, AddCallbacksDuringIteration) { | |
| 235 CallbackList<void> cb_list(CALLBACKS_NOTIFY_ALL); | |
| 236 Adder a(&cb_list); | |
| 237 Listener b; | |
| 238 cb_list.Add( | |
| 239 base::Bind(&Adder::AddCallback, base::Unretained(&a))); | |
| 240 cb_list.Add( | |
| 241 base::Bind(&Listener::IncrementTotal, base::Unretained(&b))); | |
| 242 | |
| 243 cb_list.Run(); | |
| 244 | |
| 245 EXPECT_EQ(1, a.total_); | |
| 246 EXPECT_EQ(1, b.total_); | |
| 247 EXPECT_TRUE(a.added_); | |
| 248 | |
| 249 cb_list.Run(); | |
| 250 | |
| 251 EXPECT_EQ(2, a.total_); | |
| 252 EXPECT_EQ(2, b.total_); | |
| 253 } | |
| 254 | |
| 255 // Test that a callback can add another callback to the list durning iteration | |
| 256 // without invalidating the iterator. | |
| 257 // - If the CallbackNotificationType is |CALLBACKS_NOTIFY_EXISTING_ONLY|, the | |
| 258 // newly added will *not* be run during the current iteration, but will run on | |
| 259 // subsequent iterations. | |
| 260 // - All other callbacks in the list will be run as well. | |
| 261 TEST(CallbackListTest, AddCallbacksDuringIteration_Existing) { | |
| 262 CallbackList<void> cb_list(CALLBACKS_NOTIFY_EXISTING_ONLY); | |
| 263 Adder a(&cb_list); | |
| 264 Listener b; | |
| 265 cb_list.Add( | |
| 266 base::Bind(&Adder::AddCallback, base::Unretained(&a))); | |
| 267 cb_list.Add( | |
| 268 base::Bind(&Listener::IncrementTotal, base::Unretained(&b))); | |
| 269 | |
| 270 cb_list.Run(); | |
| 271 | |
| 272 // |a| should have been added, but not run yet, as we are in | |
| 273 // |CALLBACKS_NOTIFY_EXISTING_ONLY| mode. | |
| 274 EXPECT_EQ(0, a.total_); | |
| 275 EXPECT_EQ(1, b.total_); | |
| 276 EXPECT_TRUE(a.added_); | |
| 277 | |
| 278 cb_list.Run(); | |
| 279 | |
| 280 // Now |a| should have run. | |
| 281 EXPECT_EQ(1, a.total_); | |
| 282 EXPECT_EQ(2, b.total_); | |
| 283 } | |
| 284 | |
| 285 // Test that the list behaves as expected when callbacks are both added and | |
| 286 // removed during iteration. In particular: | |
| 287 // - If the CallbackNotificationType is |CALLBACKS_NOTIFY_EXISTING_ONLY|, the | |
| 288 // newly added callback should *not* be called during the current iteration. | |
| 289 // - The callback which was removed should not run again after removal | |
| 290 TEST(CallbackListTest, AddAndRemoveCallbacksDuringIteration_Existing) { | |
| 291 CallbackList<void> cb_list(CALLBACKS_NOTIFY_EXISTING_ONLY); | |
| 292 Adder a(&cb_list); | |
| 293 Remover b; | |
| 294 Listener c; | |
| 295 | |
| 296 base::Closure c_cb = cb_list.Add( | |
| 297 base::Bind(&Listener::IncrementTotal, base::Unretained(&c))); | |
| 298 | |
| 299 // |b| removes |c|. | |
| 300 b.SetCallback(c_cb); | |
| 301 cb_list.Add( | |
| 302 base::Bind(&Remover::IncrementTotalAndRemove, base::Unretained(&b))); | |
| 303 | |
| 304 // |a| adds a new callback. | |
| 305 cb_list.Add(base::Bind(&Adder::AddCallback, base::Unretained(&a))); | |
| 306 | |
| 307 cb_list.Run(); | |
| 308 | |
| 309 // |c| ran once, new callback (|a|) should have been added but not yet run. | |
| 310 EXPECT_EQ(1, c.total_); | |
| 311 EXPECT_EQ(0, a.total_); | |
| 312 EXPECT_TRUE(a.added_); | |
| 313 | |
| 314 cb_list.Run(); | |
| 315 | |
| 316 // Now |a| should have been run. | |
| 317 EXPECT_EQ(1, c.total_); | |
| 318 EXPECT_EQ(1, a.total_); | |
| 319 } | |
| 320 | |
| 321 // Test that if we clear the list during iteration, no callbacks are run after | |
| 322 // the one that did the clearing. | |
| 323 TEST(CallbackListTest, ClearCallbacksDuringIteration) { | |
| 324 CallbackList<void> cb_list(CALLBACKS_NOTIFY_ALL); | |
| 325 Clearer a(&cb_list); | |
| 326 Listener b; | |
| 327 cb_list.Add( | |
| 328 base::Bind(&Clearer::ClearListAndAddCallback, base::Unretained(&a))); | |
| 329 cb_list.Add( | |
| 330 base::Bind(&Listener::IncrementTotal, base::Unretained(&b))); | |
| 331 | |
| 332 cb_list.Run(); | |
| 333 | |
| 334 EXPECT_EQ(1, a.total_); | |
| 335 // |b| never runs because we cleared the list first. | |
| 336 EXPECT_EQ(0, b.total_); | |
| 337 EXPECT_TRUE(a.added_); | |
| 338 } | |
| 339 | |
| 340 // Test that if the iterator outlives the list (that is, for some reason or | |
| 341 // we delete the list mid-iteration) then: | |
| 342 // - no callbacks run after deletion. | |
| 343 // - the list is cleaned up properly (there will be memory errors if this is not | |
| 344 // the case). | |
| 345 TEST(CallbackListTest, IteratorOutlivesList) { | |
| 346 CallbackList<void>* cb_list = | |
| 347 new CallbackList<void>(CALLBACKS_NOTIFY_ALL); | |
| 348 ListDestructor destructor(cb_list); | |
| 349 cb_list->Add( | |
| 350 base::Bind(&ListDestructor::DestroyList, base::Unretained(&destructor))); | |
|
awong
2013/09/04 18:48:21
Can this be used?
https://code.google.com/p/chrom
Cait (Slow)
2013/09/04 22:09:25
Done.
| |
| 351 Listener a; | |
| 352 cb_list->Add( | |
| 353 base::Bind(&Listener::IncrementTotal, base::Unretained(&a))); | |
| 354 | |
| 355 cb_list->Run(); | |
| 356 | |
| 357 // |a| never gets called, as |cb_list| got deleted first. | |
|
awong
2013/09/04 18:48:21
Hmm...this is allowing a list to delete itself fro
Cait (Slow)
2013/09/04 22:09:25
I think, upon reentrancy, the iterator's weak ptr
| |
| 358 EXPECT_EQ(0, a.total_); | |
| 359 } | |
| 360 | |
| 361 } // namespace | |
| 362 } // namespace base | |
| OLD | NEW |