|  | // Copyright 2017 The Fuchsia Authors. All rights reserved. | 
|  | // Use of this source code is governed by a BSD-style license that can be | 
|  | // found in the LICENSE file. | 
|  |  | 
|  | // Derived from chromium/src/base/observer_list_unittest.cc | 
|  |  | 
|  | #include "src/lib/fxl/observer_list.h" | 
|  |  | 
|  | #include <utility> | 
|  | #include <vector> | 
|  |  | 
|  | #include <gtest/gtest.h> | 
|  |  | 
|  | namespace fxl { | 
|  |  | 
|  | namespace { | 
|  |  | 
|  | class Foo { | 
|  | public: | 
|  | virtual void Observe(int x) = 0; | 
|  | virtual ~Foo() {} | 
|  | virtual int GetValue() const { return 0; } | 
|  | }; | 
|  |  | 
|  | class Adder : public Foo { | 
|  | public: | 
|  | explicit Adder(int scaler) : total(0), scaler_(scaler) {} | 
|  | ~Adder() override {} | 
|  |  | 
|  | 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 {} | 
|  |  | 
|  | 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_; | 
|  | }; | 
|  |  | 
|  | 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_; | 
|  | }; | 
|  |  | 
|  | class ListDestructor : public Foo { | 
|  | public: | 
|  | explicit ListDestructor(ObserverList<Foo>* list) : list_(list) {} | 
|  | ~ListDestructor() override {} | 
|  |  | 
|  | void Observe(int x) override { delete list_; } | 
|  |  | 
|  | private: | 
|  | ObserverList<Foo>* list_; | 
|  | }; | 
|  |  | 
|  | TEST(ObserverListTest, BasicTest) { | 
|  | ObserverList<Foo> observer_list; | 
|  | 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(observer_list.HasObserver(&a)); | 
|  | EXPECT_FALSE(observer_list.HasObserver(&c)); | 
|  |  | 
|  | 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, 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(ObserverListTest, Existing) { | 
|  | ObserverList<Foo> observer_list(ObserverList<Foo>::NotifyWhat::kExistingOnly); | 
|  | 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); | 
|  | } | 
|  |  | 
|  | 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(ObserverList<Foo>::NotifyWhat::kExistingOnly); | 
|  | 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."; | 
|  | } | 
|  |  | 
|  | 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); | 
|  | // If this test fails, there'll be Valgrind errors when this function goes out | 
|  | // of scope. | 
|  | } | 
|  |  | 
|  | 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().GetContainer()); | 
|  | EXPECT_FALSE(observer_list.end().GetContainer()); | 
|  |  | 
|  | // 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); | 
|  | } | 
|  |  | 
|  | }  // namespace | 
|  | }  // namespace fxl |