Commit 1a164fd7 authored by Sigurdur Asgeirsson's avatar Sigurdur Asgeirsson Committed by Commit Bot

Add base::ScopedMultiSourceObservation.

This is the first step in replacing ScopedObserver. The new class is
functionally identical, but has slightly more descriptive member and
class names. Now also with a test.

Bug: 1145565
Change-Id: I5416d7873396238c829fcf31a55cfe0ef5bac1f6
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2519763
Commit-Queue: Sigurður Ásgeirsson <siggi@chromium.org>
Reviewed-by: default avatarPatrick Monette <pmonette@chromium.org>
Reviewed-by: default avatarFrançois Doray <fdoray@chromium.org>
Cr-Commit-Position: refs/heads/master@{#824539}
parent be8b48db
...@@ -495,6 +495,7 @@ component("base") { ...@@ -495,6 +495,7 @@ component("base") {
"sampling_heap_profiler/sampling_heap_profiler.h", "sampling_heap_profiler/sampling_heap_profiler.h",
"scoped_clear_last_error.h", "scoped_clear_last_error.h",
"scoped_generic.h", "scoped_generic.h",
"scoped_multi_source_observation.h",
"scoped_native_library.cc", "scoped_native_library.cc",
"scoped_native_library.h", "scoped_native_library.h",
"scoped_observation.h", "scoped_observation.h",
...@@ -2862,6 +2863,7 @@ test("base_unittests") { ...@@ -2862,6 +2863,7 @@ test("base_unittests") {
"sampling_heap_profiler/lock_free_address_hash_set_unittest.cc", "sampling_heap_profiler/lock_free_address_hash_set_unittest.cc",
"scoped_clear_last_error_unittest.cc", "scoped_clear_last_error_unittest.cc",
"scoped_generic_unittest.cc", "scoped_generic_unittest.cc",
"scoped_multi_source_observation_unittest.cc",
"scoped_native_library_unittest.cc", "scoped_native_library_unittest.cc",
"scoped_observation_unittest.cc", "scoped_observation_unittest.cc",
"security_unittest.cc", "security_unittest.cc",
......
// 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.
#ifndef BASE_SCOPED_MULTI_SOURCE_OBSERVATION_H_
#define BASE_SCOPED_MULTI_SOURCE_OBSERVATION_H_
#include <stddef.h>
#include <vector>
#include "base/check.h"
#include "base/ranges/algorithm.h"
#include "base/stl_util.h"
namespace base {
// ScopedMultiSourceObservation is used to keep track of the set of sources an
// object has attached itself to as an observer.
//
// For objects that observe only a single source, use base::ScopedObservation
// rather than this class. This class and base::ScopedObservation replace
// ScopedObserver.
//
// When ScopedMultiSourceObservation is destroyed it removes the object as an
// observer from all sources it has been added to.
// Basic example (as a member variable):
//
// class MyFooObserver : public FooObserver {
// ...
// private:
// ScopedMultiSourceObservation<Foo, FooObserver> observed_foo_{this};
// };
//
// For cases with methods not named AddObserver/RemoveObserver:
//
// class MyFooStateObserver : public FooStateObserver {
// ...
// private:
// ScopedMultiSourceObservation<Foo,
// FooStateObserver,
// &Foo::AddStateObserver,
// &Foo::RemoveStateObserver>
// observed_foo_{this};
// };
template <class Source,
class Observer,
void (Source::*AddObsFn)(Observer*) = &Source::AddObserver,
void (Source::*RemoveObsFn)(Observer*) = &Source::RemoveObserver>
class ScopedMultiSourceObservation {
public:
explicit ScopedMultiSourceObservation(Observer* observer)
: observer_(observer) {}
ScopedMultiSourceObservation(const ScopedMultiSourceObservation&) = delete;
ScopedMultiSourceObservation& operator=(const ScopedMultiSourceObservation&) =
delete;
~ScopedMultiSourceObservation() { RemoveAllObservations(); }
// Adds the object passed to the constructor as an observer on |source|.
void AddObservation(Source* source) {
sources_.push_back(source);
(source->*AddObsFn)(observer_);
}
// Remove the object passed to the constructor as an observer from |source|.
void RemoveObservation(Source* source) {
auto it = base::ranges::find(sources_, source);
DCHECK(it != sources_.end());
sources_.erase(it);
(source->*RemoveObsFn)(observer_);
}
void RemoveAllObservations() {
for (Source* source : sources_)
(source->*RemoveObsFn)(observer_);
sources_.clear();
}
bool IsObservingSource(Source* source) const {
return base::Contains(sources_, source);
}
bool IsObservingAnySource() const { return !sources_.empty(); }
size_t GetSourcesCount() const { return sources_.size(); }
private:
Observer* observer_;
std::vector<Source*> sources_;
};
} // namespace base
#endif // BASE_SCOPED_MULTI_SOURCE_OBSERVATION_H_
// 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_multi_source_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);
ASSERT_TRUE(it != observers_.end());
observers_.erase(it);
}
bool TestSource::HasObserver(TestSourceObserver* observer) const {
return base::Contains(observers_, observer);
}
using TestScopedMultiSourceObservation =
ScopedMultiSourceObservation<TestSource, TestSourceObserver>;
class ScopedMultiSourceObservationTest : public testing::Test {
public:
TestSource* s1() { return &s1_; }
TestSource* s2() { return &s2_; }
TestSourceObserver* o1() { return &o1_; }
private:
TestSource s1_;
TestSource s2_;
TestSourceObserver o1_;
};
} // namespace
TEST_F(ScopedMultiSourceObservationTest, RemovesSourcesOnDestruction) {
{
TestScopedMultiSourceObservation obs(o1());
EXPECT_EQ(0u, s1()->num_observers());
EXPECT_FALSE(s1()->HasObserver(o1()));
obs.AddObservation(s1());
EXPECT_EQ(1u, s1()->num_observers());
EXPECT_TRUE(s1()->HasObserver(o1()));
obs.AddObservation(s2());
EXPECT_EQ(1u, s2()->num_observers());
EXPECT_TRUE(s2()->HasObserver(o1()));
}
// Test that all observations are removed when it goes out of scope.
EXPECT_EQ(0u, s1()->num_observers());
EXPECT_EQ(0u, s2()->num_observers());
}
TEST_F(ScopedMultiSourceObservationTest, RemoveObservation) {
TestScopedMultiSourceObservation obs(o1());
EXPECT_EQ(0u, s1()->num_observers());
EXPECT_FALSE(s1()->HasObserver(o1()));
EXPECT_EQ(0u, s2()->num_observers());
EXPECT_FALSE(s2()->HasObserver(o1()));
obs.AddObservation(s1());
EXPECT_EQ(1u, s1()->num_observers());
EXPECT_TRUE(s1()->HasObserver(o1()));
obs.AddObservation(s2());
EXPECT_EQ(1u, s2()->num_observers());
EXPECT_TRUE(s2()->HasObserver(o1()));
obs.RemoveObservation(s1());
EXPECT_EQ(0u, s1()->num_observers());
EXPECT_FALSE(s1()->HasObserver(o1()));
EXPECT_EQ(1u, s2()->num_observers());
EXPECT_TRUE(s2()->HasObserver(o1()));
obs.RemoveObservation(s2());
EXPECT_EQ(0u, s1()->num_observers());
EXPECT_FALSE(s1()->HasObserver(o1()));
EXPECT_EQ(0u, s2()->num_observers());
EXPECT_FALSE(s2()->HasObserver(o1()));
}
TEST_F(ScopedMultiSourceObservationTest, RemoveAllObservations) {
TestScopedMultiSourceObservation obs(o1());
EXPECT_EQ(0u, s1()->num_observers());
EXPECT_FALSE(s1()->HasObserver(o1()));
EXPECT_EQ(0u, s2()->num_observers());
EXPECT_FALSE(s2()->HasObserver(o1()));
obs.AddObservation(s1());
obs.AddObservation(s2());
EXPECT_EQ(1u, s1()->num_observers());
EXPECT_TRUE(s1()->HasObserver(o1()));
EXPECT_EQ(1u, s2()->num_observers());
EXPECT_TRUE(s2()->HasObserver(o1()));
obs.RemoveAllObservations();
EXPECT_EQ(0u, s1()->num_observers());
EXPECT_FALSE(s1()->HasObserver(o1()));
EXPECT_EQ(0u, s2()->num_observers());
EXPECT_FALSE(s2()->HasObserver(o1()));
}
TEST_F(ScopedMultiSourceObservationTest, IsObservingSource) {
TestScopedMultiSourceObservation obs(o1());
EXPECT_FALSE(obs.IsObservingSource(s1()));
EXPECT_FALSE(obs.IsObservingSource(s2()));
obs.AddObservation(s1());
EXPECT_TRUE(obs.IsObservingSource(s1()));
EXPECT_FALSE(obs.IsObservingSource(s2()));
obs.AddObservation(s2());
EXPECT_TRUE(obs.IsObservingSource(s1()));
EXPECT_TRUE(obs.IsObservingSource(s2()));
obs.RemoveObservation(s1());
EXPECT_FALSE(obs.IsObservingSource(s1()));
EXPECT_TRUE(obs.IsObservingSource(s2()));
}
TEST_F(ScopedMultiSourceObservationTest, IsObservingAnySource) {
TestScopedMultiSourceObservation obs(o1());
EXPECT_FALSE(obs.IsObservingAnySource());
obs.AddObservation(s1());
EXPECT_TRUE(obs.IsObservingAnySource());
obs.AddObservation(s2());
EXPECT_TRUE(obs.IsObservingAnySource());
obs.RemoveAllObservations();
EXPECT_FALSE(obs.IsObservingAnySource());
}
TEST_F(ScopedMultiSourceObservationTest, GetSourcesCount) {
TestScopedMultiSourceObservation obs(o1());
EXPECT_EQ(0u, obs.GetSourcesCount());
obs.AddObservation(s1());
EXPECT_EQ(1u, obs.GetSourcesCount());
obs.AddObservation(s2());
EXPECT_EQ(2u, obs.GetSourcesCount());
obs.RemoveAllObservations();
EXPECT_EQ(0u, obs.GetSourcesCount());
}
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 TestScopedMultiSourceObservationWithNonDefaultNames =
ScopedMultiSourceObservation<TestSourceWithNonDefaultNames,
TestSourceObserver,
&TestSourceWithNonDefaultNames::AddFoo,
&TestSourceWithNonDefaultNames::RemoveFoo>;
} // namespace
TEST_F(ScopedMultiSourceObservationTest, NonDefaultNames) {
TestSourceWithNonDefaultNames nds1;
EXPECT_EQ(0u, nds1.impl().num_observers());
{
TestScopedMultiSourceObservationWithNonDefaultNames obs(o1());
obs.AddObservation(&nds1);
EXPECT_EQ(1u, nds1.impl().num_observers());
EXPECT_TRUE(nds1.impl().HasObserver(o1()));
}
EXPECT_EQ(0u, nds1.impl().num_observers());
}
} // namespace base
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment