| // Copyright (c) 2012 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/observer_list.h" |
| #include "base/observer_list_threadsafe.h" |
| |
| #include <memory> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/bind.h" |
| #include "base/compiler_specific.h" |
| #include "base/location.h" |
| #include "base/memory/weak_ptr.h" |
| #include "base/message_loop/message_loop.h" |
| #include "base/run_loop.h" |
| #include "base/sequenced_task_runner.h" |
| #include "base/single_thread_task_runner.h" |
| #include "base/strings/string_piece.h" |
| #include "base/synchronization/waitable_event.h" |
| // TaskScheduler not supported in libchrome |
| // #include "base/task_scheduler/post_task.h" |
| // #include "base/task_scheduler/task_scheduler.h" |
| #include "base/test/gtest_util.h" |
| #include "base/test/scoped_task_environment.h" |
| #include "base/threading/platform_thread.h" |
| #include "base/threading/thread_restrictions.h" |
| #include "build/build_config.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| namespace base { |
| namespace { |
| |
| class Foo { |
| public: |
| virtual void Observe(int x) = 0; |
| virtual ~Foo() = default; |
| virtual int GetValue() const { return 0; } |
| }; |
| |
| class Adder : public Foo { |
| public: |
| explicit Adder(int scaler) : total(0), scaler_(scaler) {} |
| ~Adder() override = default; |
| |
| void Observe(int x) override { total += x * scaler_; } |
| int GetValue() const override { return total; } |
| |
| int total; |
| |
| private: |
| int scaler_; |
| }; |
| |
| class Disrupter : public Foo { |
| public: |
| Disrupter(ObserverList<Foo>* list, Foo* doomed, bool remove_self) |
| : list_(list), doomed_(doomed), remove_self_(remove_self) {} |
| Disrupter(ObserverList<Foo>* list, Foo* doomed) |
| : Disrupter(list, doomed, false) {} |
| Disrupter(ObserverList<Foo>* list, bool remove_self) |
| : Disrupter(list, nullptr, remove_self) {} |
| |
| ~Disrupter() override = default; |
| |
| void Observe(int x) override { |
| if (remove_self_) |
| list_->RemoveObserver(this); |
| if (doomed_) |
| list_->RemoveObserver(doomed_); |
| } |
| |
| void SetDoomed(Foo* doomed) { doomed_ = doomed; } |
| |
| private: |
| ObserverList<Foo>* list_; |
| Foo* doomed_; |
| bool remove_self_; |
| }; |
| |
| template <typename ObserverListType> |
| class AddInObserve : public Foo { |
| public: |
| explicit AddInObserve(ObserverListType* observer_list) |
| : observer_list(observer_list), to_add_() {} |
| |
| void SetToAdd(Foo* to_add) { to_add_ = to_add; } |
| |
| void Observe(int x) override { |
| if (to_add_) { |
| observer_list->AddObserver(to_add_); |
| to_add_ = nullptr; |
| } |
| } |
| |
| ObserverListType* observer_list; |
| Foo* to_add_; |
| }; |
| |
| |
| static const int kThreadRunTime = 2000; // ms to run the multi-threaded test. |
| |
| // A thread for use in the ThreadSafeObserver test |
| // which will add and remove itself from the notification |
| // list repeatedly. |
| class AddRemoveThread : public PlatformThread::Delegate, |
| public Foo { |
| public: |
| AddRemoveThread(ObserverListThreadSafe<Foo>* list, |
| bool notify, |
| WaitableEvent* ready) |
| : list_(list), |
| loop_(nullptr), |
| in_list_(false), |
| start_(Time::Now()), |
| count_observes_(0), |
| count_addtask_(0), |
| do_notifies_(notify), |
| ready_(ready), |
| weak_factory_(this) {} |
| |
| ~AddRemoveThread() override = default; |
| |
| void ThreadMain() override { |
| loop_ = new MessageLoop(); // Fire up a message loop. |
| loop_->task_runner()->PostTask( |
| FROM_HERE, |
| base::BindOnce(&AddRemoveThread::AddTask, weak_factory_.GetWeakPtr())); |
| ready_->Signal(); |
| // After ready_ is signaled, loop_ is only accessed by the main test thread |
| // (i.e. not this thread) in particular by Quit() which causes Run() to |
| // return, and we "control" loop_ again. |
| RunLoop run_loop; |
| quit_loop_ = run_loop.QuitClosure(); |
| run_loop.Run(); |
| delete loop_; |
| loop_ = reinterpret_cast<MessageLoop*>(0xdeadbeef); |
| delete this; |
| } |
| |
| // This task just keeps posting to itself in an attempt |
| // to race with the notifier. |
| void AddTask() { |
| count_addtask_++; |
| |
| if ((Time::Now() - start_).InMilliseconds() > kThreadRunTime) { |
| VLOG(1) << "DONE!"; |
| return; |
| } |
| |
| if (!in_list_) { |
| list_->AddObserver(this); |
| in_list_ = true; |
| } |
| |
| if (do_notifies_) { |
| list_->Notify(FROM_HERE, &Foo::Observe, 10); |
| } |
| |
| loop_->task_runner()->PostTask( |
| FROM_HERE, |
| base::BindOnce(&AddRemoveThread::AddTask, weak_factory_.GetWeakPtr())); |
| } |
| |
| // This function is only callable from the main thread. |
| void Quit() { std::move(quit_loop_).Run(); } |
| |
| void Observe(int x) override { |
| count_observes_++; |
| |
| // If we're getting called after we removed ourselves from |
| // the list, that is very bad! |
| DCHECK(in_list_); |
| |
| // This callback should fire on the appropriate thread |
| EXPECT_EQ(loop_, MessageLoop::current()); |
| |
| list_->RemoveObserver(this); |
| in_list_ = false; |
| } |
| |
| private: |
| ObserverListThreadSafe<Foo>* list_; |
| MessageLoop* loop_; |
| bool in_list_; // Are we currently registered for notifications. |
| // in_list_ is only used on |this| thread. |
| Time start_; // The time we started the test. |
| |
| int count_observes_; // Number of times we observed. |
| int count_addtask_; // Number of times thread AddTask was called |
| bool do_notifies_; // Whether these threads should do notifications. |
| WaitableEvent* ready_; |
| |
| base::OnceClosure quit_loop_; |
| |
| base::WeakPtrFactory<AddRemoveThread> weak_factory_; |
| }; |
| |
| } // namespace |
| |
| TEST(ObserverListTest, BasicTest) { |
| ObserverList<Foo> observer_list; |
| const ObserverList<Foo>& const_observer_list = observer_list; |
| |
| { |
| const ObserverList<Foo>::const_iterator it1 = const_observer_list.begin(); |
| EXPECT_EQ(it1, const_observer_list.end()); |
| // Iterator copy. |
| const ObserverList<Foo>::const_iterator it2 = it1; |
| EXPECT_EQ(it2, it1); |
| // Iterator assignment. |
| ObserverList<Foo>::const_iterator it3; |
| it3 = it2; |
| EXPECT_EQ(it3, it1); |
| EXPECT_EQ(it3, it2); |
| // Self assignment. |
| it3 = *&it3; // The *& defeats Clang's -Wself-assign warning. |
| EXPECT_EQ(it3, it1); |
| EXPECT_EQ(it3, it2); |
| } |
| |
| { |
| const ObserverList<Foo>::iterator it1 = observer_list.begin(); |
| EXPECT_EQ(it1, observer_list.end()); |
| // Iterator copy. |
| const ObserverList<Foo>::iterator it2 = it1; |
| EXPECT_EQ(it2, it1); |
| // Iterator assignment. |
| ObserverList<Foo>::iterator it3; |
| it3 = it2; |
| EXPECT_EQ(it3, it1); |
| EXPECT_EQ(it3, it2); |
| // Self assignment. |
| it3 = *&it3; // The *& defeats Clang's -Wself-assign warning. |
| EXPECT_EQ(it3, it1); |
| EXPECT_EQ(it3, it2); |
| } |
| |
| Adder a(1), b(-1), c(1), d(-1), e(-1); |
| Disrupter evil(&observer_list, &c); |
| |
| observer_list.AddObserver(&a); |
| observer_list.AddObserver(&b); |
| |
| EXPECT_TRUE(const_observer_list.HasObserver(&a)); |
| EXPECT_FALSE(const_observer_list.HasObserver(&c)); |
| |
| { |
| const ObserverList<Foo>::const_iterator it1 = const_observer_list.begin(); |
| EXPECT_NE(it1, const_observer_list.end()); |
| // Iterator copy. |
| const ObserverList<Foo>::const_iterator it2 = it1; |
| EXPECT_EQ(it2, it1); |
| EXPECT_NE(it2, const_observer_list.end()); |
| // Iterator assignment. |
| ObserverList<Foo>::const_iterator it3; |
| it3 = it2; |
| EXPECT_EQ(it3, it1); |
| EXPECT_EQ(it3, it2); |
| // Self assignment. |
| it3 = *&it3; // The *& defeats Clang's -Wself-assign warning. |
| EXPECT_EQ(it3, it1); |
| EXPECT_EQ(it3, it2); |
| // Iterator post increment. |
| ObserverList<Foo>::const_iterator it4 = it3++; |
| EXPECT_EQ(it4, it1); |
| EXPECT_EQ(it4, it2); |
| EXPECT_NE(it4, it3); |
| } |
| |
| { |
| const ObserverList<Foo>::iterator it1 = observer_list.begin(); |
| EXPECT_NE(it1, observer_list.end()); |
| // Iterator copy. |
| const ObserverList<Foo>::iterator it2 = it1; |
| EXPECT_EQ(it2, it1); |
| EXPECT_NE(it2, observer_list.end()); |
| // Iterator assignment. |
| ObserverList<Foo>::iterator it3; |
| it3 = it2; |
| EXPECT_EQ(it3, it1); |
| EXPECT_EQ(it3, it2); |
| // Self assignment. |
| it3 = *&it3; // The *& defeats Clang's -Wself-assign warning. |
| EXPECT_EQ(it3, it1); |
| EXPECT_EQ(it3, it2); |
| // Iterator post increment. |
| ObserverList<Foo>::iterator it4 = it3++; |
| EXPECT_EQ(it4, it1); |
| EXPECT_EQ(it4, it2); |
| EXPECT_NE(it4, it3); |
| } |
| |
| for (auto& observer : observer_list) |
| observer.Observe(10); |
| |
| observer_list.AddObserver(&evil); |
| observer_list.AddObserver(&c); |
| observer_list.AddObserver(&d); |
| |
| // Removing an observer not in the list should do nothing. |
| observer_list.RemoveObserver(&e); |
| |
| for (auto& observer : observer_list) |
| observer.Observe(10); |
| |
| EXPECT_EQ(20, a.total); |
| EXPECT_EQ(-20, b.total); |
| EXPECT_EQ(0, c.total); |
| EXPECT_EQ(-10, d.total); |
| EXPECT_EQ(0, e.total); |
| } |
| |
| TEST(ObserverListTest, CompactsWhenNoActiveIterator) { |
| ObserverList<const Foo> ol; |
| const ObserverList<const Foo>& col = ol; |
| |
| const Adder a(1); |
| const Adder b(2); |
| const Adder c(3); |
| |
| ol.AddObserver(&a); |
| ol.AddObserver(&b); |
| |
| EXPECT_TRUE(col.HasObserver(&a)); |
| EXPECT_FALSE(col.HasObserver(&c)); |
| |
| EXPECT_TRUE(col.might_have_observers()); |
| |
| using It = ObserverList<const Foo>::const_iterator; |
| |
| { |
| It it = col.begin(); |
| EXPECT_NE(it, col.end()); |
| It ita = it; |
| EXPECT_EQ(ita, it); |
| EXPECT_NE(++it, col.end()); |
| EXPECT_NE(ita, it); |
| It itb = it; |
| EXPECT_EQ(itb, it); |
| EXPECT_EQ(++it, col.end()); |
| |
| EXPECT_TRUE(col.might_have_observers()); |
| EXPECT_EQ(&*ita, &a); |
| EXPECT_EQ(&*itb, &b); |
| |
| ol.RemoveObserver(&a); |
| EXPECT_TRUE(col.might_have_observers()); |
| EXPECT_FALSE(col.HasObserver(&a)); |
| EXPECT_EQ(&*itb, &b); |
| |
| ol.RemoveObserver(&b); |
| EXPECT_TRUE(col.might_have_observers()); |
| EXPECT_FALSE(col.HasObserver(&a)); |
| EXPECT_FALSE(col.HasObserver(&b)); |
| |
| it = It(); |
| ita = It(); |
| EXPECT_TRUE(col.might_have_observers()); |
| ita = itb; |
| itb = It(); |
| EXPECT_TRUE(col.might_have_observers()); |
| ita = It(); |
| EXPECT_FALSE(col.might_have_observers()); |
| } |
| |
| ol.AddObserver(&a); |
| ol.AddObserver(&b); |
| EXPECT_TRUE(col.might_have_observers()); |
| ol.Clear(); |
| EXPECT_FALSE(col.might_have_observers()); |
| |
| ol.AddObserver(&a); |
| ol.AddObserver(&b); |
| EXPECT_TRUE(col.might_have_observers()); |
| { |
| const It it = col.begin(); |
| ol.Clear(); |
| EXPECT_TRUE(col.might_have_observers()); |
| } |
| EXPECT_FALSE(col.might_have_observers()); |
| } |
| |
| TEST(ObserverListTest, DisruptSelf) { |
| ObserverList<Foo> observer_list; |
| Adder a(1), b(-1), c(1), d(-1); |
| Disrupter evil(&observer_list, true); |
| |
| observer_list.AddObserver(&a); |
| observer_list.AddObserver(&b); |
| |
| for (auto& observer : observer_list) |
| observer.Observe(10); |
| |
| observer_list.AddObserver(&evil); |
| observer_list.AddObserver(&c); |
| observer_list.AddObserver(&d); |
| |
| for (auto& observer : observer_list) |
| observer.Observe(10); |
| |
| EXPECT_EQ(20, a.total); |
| EXPECT_EQ(-20, b.total); |
| EXPECT_EQ(10, c.total); |
| EXPECT_EQ(-10, d.total); |
| } |
| |
| TEST(ObserverListTest, DisruptBefore) { |
| ObserverList<Foo> observer_list; |
| Adder a(1), b(-1), c(1), d(-1); |
| Disrupter evil(&observer_list, &b); |
| |
| observer_list.AddObserver(&a); |
| observer_list.AddObserver(&b); |
| observer_list.AddObserver(&evil); |
| observer_list.AddObserver(&c); |
| observer_list.AddObserver(&d); |
| |
| for (auto& observer : observer_list) |
| observer.Observe(10); |
| for (auto& observer : observer_list) |
| observer.Observe(10); |
| |
| EXPECT_EQ(20, a.total); |
| EXPECT_EQ(-10, b.total); |
| EXPECT_EQ(20, c.total); |
| EXPECT_EQ(-20, d.total); |
| } |
| |
| TEST(ObserverListThreadSafeTest, BasicTest) { |
| MessageLoop loop; |
| |
| scoped_refptr<ObserverListThreadSafe<Foo> > observer_list( |
| new ObserverListThreadSafe<Foo>); |
| Adder a(1); |
| Adder b(-1); |
| Adder c(1); |
| Adder d(-1); |
| |
| observer_list->AddObserver(&a); |
| observer_list->AddObserver(&b); |
| |
| observer_list->Notify(FROM_HERE, &Foo::Observe, 10); |
| RunLoop().RunUntilIdle(); |
| |
| observer_list->AddObserver(&c); |
| observer_list->AddObserver(&d); |
| |
| observer_list->Notify(FROM_HERE, &Foo::Observe, 10); |
| observer_list->RemoveObserver(&c); |
| RunLoop().RunUntilIdle(); |
| |
| EXPECT_EQ(20, a.total); |
| EXPECT_EQ(-20, b.total); |
| EXPECT_EQ(0, c.total); |
| EXPECT_EQ(-10, d.total); |
| } |
| |
| TEST(ObserverListThreadSafeTest, RemoveObserver) { |
| MessageLoop loop; |
| |
| scoped_refptr<ObserverListThreadSafe<Foo> > observer_list( |
| new ObserverListThreadSafe<Foo>); |
| Adder a(1), b(1); |
| |
| // A workaround for the compiler bug. See http://crbug.com/121960. |
| EXPECT_NE(&a, &b); |
| |
| // Should do nothing. |
| observer_list->RemoveObserver(&a); |
| observer_list->RemoveObserver(&b); |
| |
| observer_list->Notify(FROM_HERE, &Foo::Observe, 10); |
| RunLoop().RunUntilIdle(); |
| |
| EXPECT_EQ(0, a.total); |
| EXPECT_EQ(0, b.total); |
| |
| observer_list->AddObserver(&a); |
| |
| // Should also do nothing. |
| observer_list->RemoveObserver(&b); |
| |
| observer_list->Notify(FROM_HERE, &Foo::Observe, 10); |
| RunLoop().RunUntilIdle(); |
| |
| EXPECT_EQ(10, a.total); |
| EXPECT_EQ(0, b.total); |
| } |
| |
| TEST(ObserverListThreadSafeTest, WithoutSequence) { |
| scoped_refptr<ObserverListThreadSafe<Foo> > observer_list( |
| new ObserverListThreadSafe<Foo>); |
| |
| Adder a(1), b(1), c(1); |
| |
| // No sequence, so these should not be added. |
| observer_list->AddObserver(&a); |
| observer_list->AddObserver(&b); |
| |
| { |
| // Add c when there's a sequence. |
| MessageLoop loop; |
| observer_list->AddObserver(&c); |
| |
| observer_list->Notify(FROM_HERE, &Foo::Observe, 10); |
| RunLoop().RunUntilIdle(); |
| |
| EXPECT_EQ(0, a.total); |
| EXPECT_EQ(0, b.total); |
| EXPECT_EQ(10, c.total); |
| |
| // Now add a when there's a sequence. |
| observer_list->AddObserver(&a); |
| |
| // Remove c when there's a sequence. |
| observer_list->RemoveObserver(&c); |
| |
| // Notify again. |
| observer_list->Notify(FROM_HERE, &Foo::Observe, 20); |
| RunLoop().RunUntilIdle(); |
| |
| EXPECT_EQ(20, a.total); |
| EXPECT_EQ(0, b.total); |
| EXPECT_EQ(10, c.total); |
| } |
| |
| // Removing should always succeed with or without a sequence. |
| observer_list->RemoveObserver(&a); |
| |
| // Notifying should not fail but should also be a no-op. |
| MessageLoop loop; |
| observer_list->AddObserver(&b); |
| observer_list->Notify(FROM_HERE, &Foo::Observe, 30); |
| RunLoop().RunUntilIdle(); |
| |
| EXPECT_EQ(20, a.total); |
| EXPECT_EQ(30, b.total); |
| EXPECT_EQ(10, c.total); |
| } |
| |
| class FooRemover : public Foo { |
| public: |
| explicit FooRemover(ObserverListThreadSafe<Foo>* list) : list_(list) {} |
| ~FooRemover() override = default; |
| |
| void AddFooToRemove(Foo* foo) { |
| foos_.push_back(foo); |
| } |
| |
| void Observe(int x) override { |
| std::vector<Foo*> tmp; |
| tmp.swap(foos_); |
| for (std::vector<Foo*>::iterator it = tmp.begin(); |
| it != tmp.end(); ++it) { |
| list_->RemoveObserver(*it); |
| } |
| } |
| |
| private: |
| const scoped_refptr<ObserverListThreadSafe<Foo> > list_; |
| std::vector<Foo*> foos_; |
| }; |
| |
| TEST(ObserverListThreadSafeTest, RemoveMultipleObservers) { |
| MessageLoop loop; |
| scoped_refptr<ObserverListThreadSafe<Foo> > observer_list( |
| new ObserverListThreadSafe<Foo>); |
| |
| FooRemover a(observer_list.get()); |
| Adder b(1); |
| |
| observer_list->AddObserver(&a); |
| observer_list->AddObserver(&b); |
| |
| a.AddFooToRemove(&a); |
| a.AddFooToRemove(&b); |
| |
| observer_list->Notify(FROM_HERE, &Foo::Observe, 1); |
| RunLoop().RunUntilIdle(); |
| } |
| |
| // A test driver for a multi-threaded notification loop. Runs a number |
| // of observer threads, each of which constantly adds/removes itself |
| // from the observer list. Optionally, if cross_thread_notifies is set |
| // to true, the observer threads will also trigger notifications to |
| // all observers. |
| static void ThreadSafeObserverHarness(int num_threads, |
| bool cross_thread_notifies) { |
| MessageLoop loop; |
| |
| scoped_refptr<ObserverListThreadSafe<Foo> > observer_list( |
| new ObserverListThreadSafe<Foo>); |
| Adder a(1); |
| Adder b(-1); |
| |
| observer_list->AddObserver(&a); |
| observer_list->AddObserver(&b); |
| |
| std::vector<AddRemoveThread*> threaded_observer; |
| std::vector<base::PlatformThreadHandle> threads(num_threads); |
| std::vector<std::unique_ptr<base::WaitableEvent>> ready; |
| threaded_observer.reserve(num_threads); |
| ready.reserve(num_threads); |
| for (int index = 0; index < num_threads; index++) { |
| ready.push_back(std::make_unique<WaitableEvent>( |
| WaitableEvent::ResetPolicy::MANUAL, |
| WaitableEvent::InitialState::NOT_SIGNALED)); |
| threaded_observer.push_back(new AddRemoveThread( |
| observer_list.get(), cross_thread_notifies, ready.back().get())); |
| EXPECT_TRUE( |
| PlatformThread::Create(0, threaded_observer.back(), &threads[index])); |
| } |
| ASSERT_EQ(static_cast<size_t>(num_threads), threaded_observer.size()); |
| ASSERT_EQ(static_cast<size_t>(num_threads), ready.size()); |
| |
| // This makes sure that threaded_observer has gotten to set loop_, so that we |
| // can call Quit() below safe-ish-ly. |
| for (int i = 0; i < num_threads; ++i) |
| ready[i]->Wait(); |
| |
| Time start = Time::Now(); |
| while (true) { |
| if ((Time::Now() - start).InMilliseconds() > kThreadRunTime) |
| break; |
| |
| observer_list->Notify(FROM_HERE, &Foo::Observe, 10); |
| |
| RunLoop().RunUntilIdle(); |
| } |
| |
| for (int index = 0; index < num_threads; index++) { |
| threaded_observer[index]->Quit(); |
| PlatformThread::Join(threads[index]); |
| } |
| } |
| |
| #if defined(OS_FUCHSIA) |
| // TODO(crbug.com/738275): This is flaky on Fuchsia. |
| #define MAYBE_CrossThreadObserver DISABLED_CrossThreadObserver |
| #else |
| #define MAYBE_CrossThreadObserver CrossThreadObserver |
| #endif |
| TEST(ObserverListThreadSafeTest, MAYBE_CrossThreadObserver) { |
| // Use 7 observer threads. Notifications only come from |
| // the main thread. |
| ThreadSafeObserverHarness(7, false); |
| } |
| |
| TEST(ObserverListThreadSafeTest, CrossThreadNotifications) { |
| // Use 3 observer threads. Notifications will fire from |
| // the main thread and all 3 observer threads. |
| ThreadSafeObserverHarness(3, true); |
| } |
| |
| TEST(ObserverListThreadSafeTest, OutlivesMessageLoop) { |
| MessageLoop* loop = new MessageLoop; |
| scoped_refptr<ObserverListThreadSafe<Foo> > observer_list( |
| new ObserverListThreadSafe<Foo>); |
| |
| Adder a(1); |
| observer_list->AddObserver(&a); |
| delete loop; |
| // Test passes if we don't crash here. |
| observer_list->Notify(FROM_HERE, &Foo::Observe, 1); |
| } |
| |
| namespace { |
| |
| class SequenceVerificationObserver : public Foo { |
| public: |
| explicit SequenceVerificationObserver( |
| scoped_refptr<SequencedTaskRunner> task_runner) |
| : task_runner_(std::move(task_runner)) {} |
| ~SequenceVerificationObserver() override = default; |
| |
| void Observe(int x) override { |
| called_on_valid_sequence_ = task_runner_->RunsTasksInCurrentSequence(); |
| } |
| |
| bool called_on_valid_sequence() const { return called_on_valid_sequence_; } |
| |
| private: |
| const scoped_refptr<SequencedTaskRunner> task_runner_; |
| bool called_on_valid_sequence_ = false; |
| |
| DISALLOW_COPY_AND_ASSIGN(SequenceVerificationObserver); |
| }; |
| |
| } // namespace |
| |
| // Verify that observers are notified on the correct sequence. |
| // TaskScheduler not supported in libchrome |
| #if 0 |
| TEST(ObserverListThreadSafeTest, NotificationOnValidSequence) { |
| test::ScopedTaskEnvironment scoped_task_environment; |
| |
| auto task_runner_1 = CreateSequencedTaskRunnerWithTraits(TaskTraits()); |
| auto task_runner_2 = CreateSequencedTaskRunnerWithTraits(TaskTraits()); |
| |
| auto observer_list = MakeRefCounted<ObserverListThreadSafe<Foo>>(); |
| |
| SequenceVerificationObserver observer_1(task_runner_1); |
| SequenceVerificationObserver observer_2(task_runner_2); |
| |
| task_runner_1->PostTask(FROM_HERE, |
| BindOnce(&ObserverListThreadSafe<Foo>::AddObserver, |
| observer_list, Unretained(&observer_1))); |
| task_runner_2->PostTask(FROM_HERE, |
| BindOnce(&ObserverListThreadSafe<Foo>::AddObserver, |
| observer_list, Unretained(&observer_2))); |
| |
| TaskScheduler::GetInstance()->FlushForTesting(); |
| |
| observer_list->Notify(FROM_HERE, &Foo::Observe, 1); |
| |
| TaskScheduler::GetInstance()->FlushForTesting(); |
| |
| EXPECT_TRUE(observer_1.called_on_valid_sequence()); |
| EXPECT_TRUE(observer_2.called_on_valid_sequence()); |
| } |
| #endif |
| |
| // Verify that when an observer is added to a NOTIFY_ALL ObserverListThreadSafe |
| // from a notification, it is itself notified. |
| // TaskScheduler not supported in libchrome |
| #if 0 |
| TEST(ObserverListThreadSafeTest, AddObserverFromNotificationNotifyAll) { |
| test::ScopedTaskEnvironment scoped_task_environment; |
| auto observer_list = MakeRefCounted<ObserverListThreadSafe<Foo>>(); |
| |
| Adder observer_added_from_notification(1); |
| |
| AddInObserve<ObserverListThreadSafe<Foo>> initial_observer( |
| observer_list.get()); |
| initial_observer.SetToAdd(&observer_added_from_notification); |
| observer_list->AddObserver(&initial_observer); |
| |
| observer_list->Notify(FROM_HERE, &Foo::Observe, 1); |
| |
| base::RunLoop().RunUntilIdle(); |
| |
| EXPECT_EQ(1, observer_added_from_notification.GetValue()); |
| } |
| #endif |
| |
| namespace { |
| |
| class RemoveWhileNotificationIsRunningObserver : public Foo { |
| public: |
| RemoveWhileNotificationIsRunningObserver() |
| : notification_running_(WaitableEvent::ResetPolicy::AUTOMATIC, |
| WaitableEvent::InitialState::NOT_SIGNALED), |
| barrier_(WaitableEvent::ResetPolicy::AUTOMATIC, |
| WaitableEvent::InitialState::NOT_SIGNALED) {} |
| ~RemoveWhileNotificationIsRunningObserver() override = default; |
| |
| void Observe(int x) override { |
| notification_running_.Signal(); |
| ScopedAllowBaseSyncPrimitivesForTesting allow_base_sync_primitives; |
| barrier_.Wait(); |
| } |
| |
| void WaitForNotificationRunning() { notification_running_.Wait(); } |
| void Unblock() { barrier_.Signal(); } |
| |
| private: |
| WaitableEvent notification_running_; |
| WaitableEvent barrier_; |
| |
| DISALLOW_COPY_AND_ASSIGN(RemoveWhileNotificationIsRunningObserver); |
| }; |
| |
| } // namespace |
| |
| // Verify that there is no crash when an observer is removed while it is being |
| // notified. |
| // TaskScheduler not supported in libchrome |
| #if 0 |
| TEST(ObserverListThreadSafeTest, RemoveWhileNotificationIsRunning) { |
| auto observer_list = MakeRefCounted<ObserverListThreadSafe<Foo>>(); |
| RemoveWhileNotificationIsRunningObserver observer; |
| |
| WaitableEvent task_running(WaitableEvent::ResetPolicy::AUTOMATIC, |
| WaitableEvent::InitialState::NOT_SIGNALED); |
| WaitableEvent barrier(WaitableEvent::ResetPolicy::AUTOMATIC, |
| WaitableEvent::InitialState::NOT_SIGNALED); |
| |
| // This must be after the declaration of |barrier| so that tasks posted to |
| // TaskScheduler can safely use |barrier|. |
| test::ScopedTaskEnvironment scoped_task_environment; |
| |
| CreateSequencedTaskRunnerWithTraits({})->PostTask( |
| FROM_HERE, base::BindOnce(&ObserverListThreadSafe<Foo>::AddObserver, |
| observer_list, Unretained(&observer))); |
| TaskScheduler::GetInstance()->FlushForTesting(); |
| |
| observer_list->Notify(FROM_HERE, &Foo::Observe, 1); |
| observer.WaitForNotificationRunning(); |
| observer_list->RemoveObserver(&observer); |
| |
| observer.Unblock(); |
| } |
| #endif |
| |
| TEST(ObserverListTest, Existing) { |
| ObserverList<Foo> observer_list(ObserverListPolicy::EXISTING_ONLY); |
| Adder a(1); |
| AddInObserve<ObserverList<Foo> > b(&observer_list); |
| Adder c(1); |
| b.SetToAdd(&c); |
| |
| observer_list.AddObserver(&a); |
| observer_list.AddObserver(&b); |
| |
| for (auto& observer : observer_list) |
| observer.Observe(1); |
| |
| EXPECT_FALSE(b.to_add_); |
| // B's adder should not have been notified because it was added during |
| // notification. |
| EXPECT_EQ(0, c.total); |
| |
| // Notify again to make sure b's adder is notified. |
| for (auto& observer : observer_list) |
| observer.Observe(1); |
| EXPECT_EQ(1, c.total); |
| } |
| |
| // Same as above, but for ObserverListThreadSafe |
| TEST(ObserverListThreadSafeTest, Existing) { |
| MessageLoop loop; |
| scoped_refptr<ObserverListThreadSafe<Foo>> observer_list( |
| new ObserverListThreadSafe<Foo>(ObserverListPolicy::EXISTING_ONLY)); |
| Adder a(1); |
| AddInObserve<ObserverListThreadSafe<Foo> > b(observer_list.get()); |
| Adder c(1); |
| b.SetToAdd(&c); |
| |
| observer_list->AddObserver(&a); |
| observer_list->AddObserver(&b); |
| |
| observer_list->Notify(FROM_HERE, &Foo::Observe, 1); |
| RunLoop().RunUntilIdle(); |
| |
| EXPECT_FALSE(b.to_add_); |
| // B's adder should not have been notified because it was added during |
| // notification. |
| EXPECT_EQ(0, c.total); |
| |
| // Notify again to make sure b's adder is notified. |
| observer_list->Notify(FROM_HERE, &Foo::Observe, 1); |
| RunLoop().RunUntilIdle(); |
| EXPECT_EQ(1, c.total); |
| } |
| |
| class AddInClearObserve : public Foo { |
| public: |
| explicit AddInClearObserve(ObserverList<Foo>* list) |
| : list_(list), added_(false), adder_(1) {} |
| |
| void Observe(int /* x */) override { |
| list_->Clear(); |
| list_->AddObserver(&adder_); |
| added_ = true; |
| } |
| |
| bool added() const { return added_; } |
| const Adder& adder() const { return adder_; } |
| |
| private: |
| ObserverList<Foo>* const list_; |
| |
| bool added_; |
| Adder adder_; |
| }; |
| |
| TEST(ObserverListTest, ClearNotifyAll) { |
| ObserverList<Foo> observer_list; |
| AddInClearObserve a(&observer_list); |
| |
| observer_list.AddObserver(&a); |
| |
| for (auto& observer : observer_list) |
| observer.Observe(1); |
| EXPECT_TRUE(a.added()); |
| EXPECT_EQ(1, a.adder().total) |
| << "Adder should observe once and have sum of 1."; |
| } |
| |
| TEST(ObserverListTest, ClearNotifyExistingOnly) { |
| ObserverList<Foo> observer_list(ObserverListPolicy::EXISTING_ONLY); |
| AddInClearObserve a(&observer_list); |
| |
| observer_list.AddObserver(&a); |
| |
| for (auto& observer : observer_list) |
| observer.Observe(1); |
| EXPECT_TRUE(a.added()); |
| EXPECT_EQ(0, a.adder().total) |
| << "Adder should not observe, so sum should still be 0."; |
| } |
| |
| class ListDestructor : public Foo { |
| public: |
| explicit ListDestructor(ObserverList<Foo>* list) : list_(list) {} |
| ~ListDestructor() override = default; |
| |
| void Observe(int x) override { delete list_; } |
| |
| private: |
| ObserverList<Foo>* list_; |
| }; |
| |
| |
| TEST(ObserverListTest, IteratorOutlivesList) { |
| ObserverList<Foo>* observer_list = new ObserverList<Foo>; |
| ListDestructor a(observer_list); |
| observer_list->AddObserver(&a); |
| |
| for (auto& observer : *observer_list) |
| observer.Observe(0); |
| |
| // There are no EXPECT* statements for this test, if we catch |
| // use-after-free errors for observer_list (eg with ASan) then |
| // this test has failed. See http://crbug.com/85296. |
| } |
| |
| TEST(ObserverListTest, BasicStdIterator) { |
| using FooList = ObserverList<Foo>; |
| FooList observer_list; |
| |
| // An optimization: begin() and end() do not involve weak pointers on |
| // empty list. |
| EXPECT_FALSE(observer_list.begin().list_); |
| EXPECT_FALSE(observer_list.end().list_); |
| |
| // Iterate over empty list: no effect, no crash. |
| for (auto& i : observer_list) |
| i.Observe(10); |
| |
| Adder a(1), b(-1), c(1), d(-1); |
| |
| observer_list.AddObserver(&a); |
| observer_list.AddObserver(&b); |
| observer_list.AddObserver(&c); |
| observer_list.AddObserver(&d); |
| |
| for (FooList::iterator i = observer_list.begin(), e = observer_list.end(); |
| i != e; ++i) |
| i->Observe(1); |
| |
| EXPECT_EQ(1, a.total); |
| EXPECT_EQ(-1, b.total); |
| EXPECT_EQ(1, c.total); |
| EXPECT_EQ(-1, d.total); |
| |
| // Check an iteration over a 'const view' for a given container. |
| const FooList& const_list = observer_list; |
| for (FooList::const_iterator i = const_list.begin(), e = const_list.end(); |
| i != e; ++i) { |
| EXPECT_EQ(1, std::abs(i->GetValue())); |
| } |
| |
| for (const auto& o : const_list) |
| EXPECT_EQ(1, std::abs(o.GetValue())); |
| } |
| |
| TEST(ObserverListTest, StdIteratorRemoveItself) { |
| ObserverList<Foo> observer_list; |
| Adder a(1), b(-1), c(1), d(-1); |
| Disrupter disrupter(&observer_list, true); |
| |
| observer_list.AddObserver(&a); |
| observer_list.AddObserver(&b); |
| observer_list.AddObserver(&disrupter); |
| observer_list.AddObserver(&c); |
| observer_list.AddObserver(&d); |
| |
| for (auto& o : observer_list) |
| o.Observe(1); |
| |
| for (auto& o : observer_list) |
| o.Observe(10); |
| |
| EXPECT_EQ(11, a.total); |
| EXPECT_EQ(-11, b.total); |
| EXPECT_EQ(11, c.total); |
| EXPECT_EQ(-11, d.total); |
| } |
| |
| TEST(ObserverListTest, StdIteratorRemoveBefore) { |
| ObserverList<Foo> observer_list; |
| Adder a(1), b(-1), c(1), d(-1); |
| Disrupter disrupter(&observer_list, &b); |
| |
| observer_list.AddObserver(&a); |
| observer_list.AddObserver(&b); |
| observer_list.AddObserver(&disrupter); |
| observer_list.AddObserver(&c); |
| observer_list.AddObserver(&d); |
| |
| for (auto& o : observer_list) |
| o.Observe(1); |
| |
| for (auto& o : observer_list) |
| o.Observe(10); |
| |
| EXPECT_EQ(11, a.total); |
| EXPECT_EQ(-1, b.total); |
| EXPECT_EQ(11, c.total); |
| EXPECT_EQ(-11, d.total); |
| } |
| |
| TEST(ObserverListTest, StdIteratorRemoveAfter) { |
| ObserverList<Foo> observer_list; |
| Adder a(1), b(-1), c(1), d(-1); |
| Disrupter disrupter(&observer_list, &c); |
| |
| observer_list.AddObserver(&a); |
| observer_list.AddObserver(&b); |
| observer_list.AddObserver(&disrupter); |
| observer_list.AddObserver(&c); |
| observer_list.AddObserver(&d); |
| |
| for (auto& o : observer_list) |
| o.Observe(1); |
| |
| for (auto& o : observer_list) |
| o.Observe(10); |
| |
| EXPECT_EQ(11, a.total); |
| EXPECT_EQ(-11, b.total); |
| EXPECT_EQ(0, c.total); |
| EXPECT_EQ(-11, d.total); |
| } |
| |
| TEST(ObserverListTest, StdIteratorRemoveAfterFront) { |
| ObserverList<Foo> observer_list; |
| Adder a(1), b(-1), c(1), d(-1); |
| Disrupter disrupter(&observer_list, &a); |
| |
| observer_list.AddObserver(&a); |
| observer_list.AddObserver(&disrupter); |
| observer_list.AddObserver(&b); |
| observer_list.AddObserver(&c); |
| observer_list.AddObserver(&d); |
| |
| for (auto& o : observer_list) |
| o.Observe(1); |
| |
| for (auto& o : observer_list) |
| o.Observe(10); |
| |
| EXPECT_EQ(1, a.total); |
| EXPECT_EQ(-11, b.total); |
| EXPECT_EQ(11, c.total); |
| EXPECT_EQ(-11, d.total); |
| } |
| |
| TEST(ObserverListTest, StdIteratorRemoveBeforeBack) { |
| ObserverList<Foo> observer_list; |
| Adder a(1), b(-1), c(1), d(-1); |
| Disrupter disrupter(&observer_list, &d); |
| |
| observer_list.AddObserver(&a); |
| observer_list.AddObserver(&b); |
| observer_list.AddObserver(&c); |
| observer_list.AddObserver(&disrupter); |
| observer_list.AddObserver(&d); |
| |
| for (auto& o : observer_list) |
| o.Observe(1); |
| |
| for (auto& o : observer_list) |
| o.Observe(10); |
| |
| EXPECT_EQ(11, a.total); |
| EXPECT_EQ(-11, b.total); |
| EXPECT_EQ(11, c.total); |
| EXPECT_EQ(0, d.total); |
| } |
| |
| TEST(ObserverListTest, StdIteratorRemoveFront) { |
| using FooList = ObserverList<Foo>; |
| FooList observer_list; |
| Adder a(1), b(-1), c(1), d(-1); |
| Disrupter disrupter(&observer_list, true); |
| |
| observer_list.AddObserver(&disrupter); |
| observer_list.AddObserver(&a); |
| observer_list.AddObserver(&b); |
| observer_list.AddObserver(&c); |
| observer_list.AddObserver(&d); |
| |
| bool test_disruptor = true; |
| for (FooList::iterator i = observer_list.begin(), e = observer_list.end(); |
| i != e; ++i) { |
| i->Observe(1); |
| // Check that second call to i->Observe() would crash here. |
| if (test_disruptor) { |
| EXPECT_FALSE(i.GetCurrent()); |
| test_disruptor = false; |
| } |
| } |
| |
| for (auto& o : observer_list) |
| o.Observe(10); |
| |
| EXPECT_EQ(11, a.total); |
| EXPECT_EQ(-11, b.total); |
| EXPECT_EQ(11, c.total); |
| EXPECT_EQ(-11, d.total); |
| } |
| |
| TEST(ObserverListTest, StdIteratorRemoveBack) { |
| ObserverList<Foo> observer_list; |
| Adder a(1), b(-1), c(1), d(-1); |
| Disrupter disrupter(&observer_list, true); |
| |
| observer_list.AddObserver(&a); |
| observer_list.AddObserver(&b); |
| observer_list.AddObserver(&c); |
| observer_list.AddObserver(&d); |
| observer_list.AddObserver(&disrupter); |
| |
| for (auto& o : observer_list) |
| o.Observe(1); |
| |
| for (auto& o : observer_list) |
| o.Observe(10); |
| |
| EXPECT_EQ(11, a.total); |
| EXPECT_EQ(-11, b.total); |
| EXPECT_EQ(11, c.total); |
| EXPECT_EQ(-11, d.total); |
| } |
| |
| TEST(ObserverListTest, NestedLoop) { |
| ObserverList<Foo> observer_list; |
| Adder a(1), b(-1), c(1), d(-1); |
| Disrupter disrupter(&observer_list, true); |
| |
| observer_list.AddObserver(&disrupter); |
| observer_list.AddObserver(&a); |
| observer_list.AddObserver(&b); |
| observer_list.AddObserver(&c); |
| observer_list.AddObserver(&d); |
| |
| for (auto& o : observer_list) { |
| o.Observe(10); |
| |
| for (auto& o : observer_list) |
| o.Observe(1); |
| } |
| |
| EXPECT_EQ(15, a.total); |
| EXPECT_EQ(-15, b.total); |
| EXPECT_EQ(15, c.total); |
| EXPECT_EQ(-15, d.total); |
| } |
| |
| TEST(ObserverListTest, NonCompactList) { |
| ObserverList<Foo> observer_list; |
| Adder a(1), b(-1); |
| |
| Disrupter disrupter1(&observer_list, true); |
| Disrupter disrupter2(&observer_list, true); |
| |
| // Disrupt itself and another one. |
| disrupter1.SetDoomed(&disrupter2); |
| |
| observer_list.AddObserver(&disrupter1); |
| observer_list.AddObserver(&disrupter2); |
| observer_list.AddObserver(&a); |
| observer_list.AddObserver(&b); |
| |
| for (auto& o : observer_list) { |
| // Get the { nullptr, nullptr, &a, &b } non-compact list |
| // on the first inner pass. |
| o.Observe(10); |
| |
| for (auto& o : observer_list) |
| o.Observe(1); |
| } |
| |
| EXPECT_EQ(13, a.total); |
| EXPECT_EQ(-13, b.total); |
| } |
| |
| TEST(ObserverListTest, BecomesEmptyThanNonEmpty) { |
| ObserverList<Foo> observer_list; |
| Adder a(1), b(-1); |
| |
| Disrupter disrupter1(&observer_list, true); |
| Disrupter disrupter2(&observer_list, true); |
| |
| // Disrupt itself and another one. |
| disrupter1.SetDoomed(&disrupter2); |
| |
| observer_list.AddObserver(&disrupter1); |
| observer_list.AddObserver(&disrupter2); |
| |
| bool add_observers = true; |
| for (auto& o : observer_list) { |
| // Get the { nullptr, nullptr } empty list on the first inner pass. |
| o.Observe(10); |
| |
| for (auto& o : observer_list) |
| o.Observe(1); |
| |
| if (add_observers) { |
| observer_list.AddObserver(&a); |
| observer_list.AddObserver(&b); |
| add_observers = false; |
| } |
| } |
| |
| EXPECT_EQ(12, a.total); |
| EXPECT_EQ(-12, b.total); |
| } |
| |
| TEST(ObserverListTest, AddObserverInTheLastObserve) { |
| using FooList = ObserverList<Foo>; |
| FooList observer_list; |
| |
| AddInObserve<FooList> a(&observer_list); |
| Adder b(-1); |
| |
| a.SetToAdd(&b); |
| observer_list.AddObserver(&a); |
| |
| auto it = observer_list.begin(); |
| while (it != observer_list.end()) { |
| auto& observer = *it; |
| // Intentionally increment the iterator before calling Observe(). The |
| // ObserverList starts with only one observer, and it == observer_list.end() |
| // should be true after the next line. |
| ++it; |
| // However, the first Observe() call will add a second observer: at this |
| // point, it != observer_list.end() should be true, and Observe() should be |
| // called on the newly added observer on the next iteration of the loop. |
| observer.Observe(10); |
| } |
| |
| EXPECT_EQ(-10, b.total); |
| } |
| |
| class MockLogAssertHandler { |
| public: |
| MOCK_METHOD4( |
| HandleLogAssert, |
| void(const char*, int, const base::StringPiece, const base::StringPiece)); |
| }; |
| |
| #if DCHECK_IS_ON() |
| TEST(ObserverListTest, NonReentrantObserverList) { |
| using ::testing::_; |
| |
| ObserverList<Foo, /*check_empty=*/false, /*allow_reentrancy=*/false> |
| non_reentrant_observer_list; |
| Adder a(1); |
| non_reentrant_observer_list.AddObserver(&a); |
| |
| EXPECT_DCHECK_DEATH({ |
| for (const Foo& a : non_reentrant_observer_list) { |
| for (const Foo& b : non_reentrant_observer_list) { |
| std::ignore = a; |
| std::ignore = b; |
| } |
| } |
| }); |
| } |
| |
| TEST(ObserverListTest, ReentrantObserverList) { |
| using ::testing::_; |
| |
| ReentrantObserverList<Foo> reentrant_observer_list; |
| Adder a(1); |
| reentrant_observer_list.AddObserver(&a); |
| bool passed = false; |
| for (const Foo& a : reentrant_observer_list) { |
| for (const Foo& b : reentrant_observer_list) { |
| std::ignore = a; |
| std::ignore = b; |
| passed = true; |
| } |
| } |
| EXPECT_TRUE(passed); |
| } |
| #endif |
| |
| } // namespace base |