Add base::ScopedObservation.

This class is for observers that observe only one source.
This is more readable and slightly more efficient than using
base::ScopedObserver, where an observer has zero or one subscriptions.

Change-Id: Iefd987ce006aa2106249cf2708f63d90326814ea
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2518028
Reviewed-by: François Doray <[email protected]>
Reviewed-by: Patrick Monette <[email protected]>
Commit-Queue: François Doray <[email protected]>
Commit-Queue: Sigurður Ásgeirsson <[email protected]>
Cr-Commit-Position: refs/heads/master@{#824014}
diff --git a/base/scoped_observation_unittest.cc b/base/scoped_observation_unittest.cc
new file mode 100644
index 0000000..37ca30e
--- /dev/null
+++ b/base/scoped_observation_unittest.cc
@@ -0,0 +1,147 @@
+// Copyright 2020 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/scoped_observation.h"
+
+#include "base/ranges/algorithm.h"
+#include "base/stl_util.h"
+#include "base/test/gtest_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace base {
+namespace {
+
+class TestSourceObserver {};
+
+class TestSource {
+ public:
+  void AddObserver(TestSourceObserver* observer);
+  void RemoveObserver(TestSourceObserver* observer);
+
+  bool HasObserver(TestSourceObserver* observer) const;
+  size_t num_observers() const { return observers_.size(); }
+
+ private:
+  std::vector<TestSourceObserver*> observers_;
+};
+
+void TestSource::AddObserver(TestSourceObserver* observer) {
+  observers_.push_back(observer);
+}
+
+void TestSource::RemoveObserver(TestSourceObserver* observer) {
+  auto it = base::ranges::find(observers_, observer);
+  EXPECT_TRUE(it != observers_.end());
+  observers_.erase(it);
+}
+
+bool TestSource::HasObserver(TestSourceObserver* observer) const {
+  return base::Contains(observers_, observer);
+}
+
+using TestScopedObservation = ScopedObservation<TestSource, TestSourceObserver>;
+
+}  // namespace
+
+TEST(ScopedObservationTest, RemovesObservationOnDestruction) {
+  TestSource s1;
+
+  {
+    TestSourceObserver o1;
+    TestScopedObservation obs(&o1);
+    EXPECT_EQ(0u, s1.num_observers());
+    EXPECT_FALSE(s1.HasObserver(&o1));
+
+    obs.Observe(&s1);
+    EXPECT_EQ(1u, s1.num_observers());
+    EXPECT_TRUE(s1.HasObserver(&o1));
+  }
+
+  // Test that the observation is removed when it goes out of scope.
+  EXPECT_EQ(0u, s1.num_observers());
+}
+
+TEST(ScopedObservationTest, RemoveObservation) {
+  TestSource s1;
+  TestSourceObserver o1;
+  TestScopedObservation obs(&o1);
+  EXPECT_EQ(0u, s1.num_observers());
+  EXPECT_FALSE(s1.HasObserver(&o1));
+
+  obs.Observe(&s1);
+  EXPECT_EQ(1u, s1.num_observers());
+  EXPECT_TRUE(s1.HasObserver(&o1));
+
+  obs.RemoveObservation();
+  EXPECT_EQ(0u, s1.num_observers());
+}
+
+TEST(ScopedObservationTest, IsObserving) {
+  TestSource s1;
+  TestSourceObserver o1;
+  TestScopedObservation obs(&o1);
+  EXPECT_FALSE(obs.IsObserving());
+
+  obs.Observe(&s1);
+  EXPECT_TRUE(obs.IsObserving());
+
+  obs.RemoveObservation();
+  EXPECT_FALSE(obs.IsObserving());
+}
+
+TEST(ScopedObservationTest, IsObservingSource) {
+  TestSource s1;
+  TestSource s2;
+  TestSourceObserver o1;
+  TestScopedObservation obs(&o1);
+  EXPECT_FALSE(obs.IsObservingSource(&s1));
+  EXPECT_FALSE(obs.IsObservingSource(&s2));
+
+  obs.Observe(&s1);
+  EXPECT_TRUE(obs.IsObservingSource(&s1));
+  EXPECT_FALSE(obs.IsObservingSource(&s2));
+
+  obs.RemoveObservation();
+  EXPECT_FALSE(obs.IsObservingSource(&s1));
+  EXPECT_FALSE(obs.IsObservingSource(&s2));
+}
+
+namespace {
+
+// A test source with oddly named Add/Remove functions.
+class TestSourceWithNonDefaultNames {
+ public:
+  void AddFoo(TestSourceObserver* observer) { impl_.AddObserver(observer); }
+  void RemoveFoo(TestSourceObserver* observer) {
+    impl_.RemoveObserver(observer);
+  }
+
+  const TestSource& impl() const { return impl_; }
+
+ private:
+  TestSource impl_;
+};
+
+using TestScopedObservationWithNonDefaultNames =
+    ScopedObservation<TestSourceWithNonDefaultNames,
+                      TestSourceObserver,
+                      &TestSourceWithNonDefaultNames::AddFoo,
+                      &TestSourceWithNonDefaultNames::RemoveFoo>;
+
+}  // namespace
+
+TEST(ScopedObservationTest, NonDefaultNames) {
+  TestSourceWithNonDefaultNames s1;
+  TestSourceObserver o1;
+
+  EXPECT_EQ(0u, s1.impl().num_observers());
+  {
+    TestScopedObservationWithNonDefaultNames obs(&o1);
+    obs.Observe(&s1);
+    EXPECT_EQ(1u, s1.impl().num_observers());
+    EXPECT_TRUE(s1.impl().HasObserver(&o1));
+  }
+}
+
+}  // namespace base