|  | // Copyright 2019 The Chromium Authors | 
|  | // Use of this source code is governed by a BSD-style license that can be | 
|  | // found in the LICENSE file. | 
|  |  | 
|  | #include "base/task/sequence_manager/atomic_flag_set.h" | 
|  |  | 
|  | #include <set> | 
|  | #include <utility> | 
|  | #include <vector> | 
|  |  | 
|  | #include "base/test/bind.h" | 
|  | #include "testing/gmock/include/gmock/gmock.h" | 
|  |  | 
|  | using testing::ElementsAre; | 
|  | using testing::IsNull; | 
|  | using testing::NotNull; | 
|  |  | 
|  | namespace base { | 
|  | namespace sequence_manager { | 
|  | namespace internal { | 
|  |  | 
|  | class AtomicFlagSetForTest : public AtomicFlagSet { | 
|  | public: | 
|  | explicit AtomicFlagSetForTest( | 
|  | scoped_refptr<AssociatedThreadId> associated_thread) | 
|  | : AtomicFlagSet(std::move(associated_thread)) {} | 
|  |  | 
|  | using AtomicFlagSet::GetAllocListForTesting; | 
|  | using AtomicFlagSet::GetPartiallyFreeListForTesting; | 
|  | using AtomicFlagSet::Group; | 
|  | }; | 
|  |  | 
|  | class AtomicFlagSetTest : public testing::Test { | 
|  | public: | 
|  | void CreateFlags(size_t number_of_flags, | 
|  | RepeatingCallback<void(size_t index)> callback) { | 
|  | atomic_flags_.reserve(number_of_flags); | 
|  | for (size_t i = 0; i < number_of_flags; i++) { | 
|  | atomic_flags_.push_back(atomic_flag_set_.AddFlag( | 
|  | base::BindRepeating([](RepeatingCallback<void(size_t index)> callback, | 
|  | size_t i) { callback.Run(i); }, | 
|  | callback, i))); | 
|  | } | 
|  | } | 
|  |  | 
|  | AtomicFlagSetForTest atomic_flag_set_{AssociatedThreadId::CreateBound()}; | 
|  | std::vector<AtomicFlagSet::AtomicFlag> atomic_flags_; | 
|  | }; | 
|  |  | 
|  | TEST_F(AtomicFlagSetTest, VisitEmptyAtomicFlagSet) { | 
|  | atomic_flag_set_.RunActiveCallbacks();  // Shouldn't crash. | 
|  | } | 
|  |  | 
|  | TEST_F(AtomicFlagSetTest, SetActiveOneFlag) { | 
|  | std::vector<size_t> flags_visited; | 
|  |  | 
|  | CreateFlags(3, BindLambdaForTesting( | 
|  | [&](size_t index) { flags_visited.push_back(index); })); | 
|  |  | 
|  | atomic_flags_[1].SetActive(true); | 
|  | EXPECT_TRUE(flags_visited.empty()); | 
|  |  | 
|  | atomic_flag_set_.RunActiveCallbacks(); | 
|  | EXPECT_THAT(flags_visited, ElementsAre(1)); | 
|  |  | 
|  | // A subsequent call to RunActiveCallbacks should not visit anything. | 
|  | flags_visited.clear(); | 
|  | atomic_flag_set_.RunActiveCallbacks(); | 
|  | EXPECT_TRUE(flags_visited.empty()); | 
|  | } | 
|  |  | 
|  | TEST_F(AtomicFlagSetTest, SetActiveManyFlags) { | 
|  | constexpr size_t num_flags = 1000; | 
|  | std::set<size_t> flags_visited; | 
|  |  | 
|  | CreateFlags(num_flags, BindLambdaForTesting([&](size_t index) { | 
|  | flags_visited.insert(index); | 
|  | })); | 
|  |  | 
|  | // Set active all even numbered flags. | 
|  | for (size_t i = 0; i < num_flags; i += 2) { | 
|  | atomic_flags_[i].SetActive(true); | 
|  | } | 
|  |  | 
|  | atomic_flag_set_.RunActiveCallbacks(); | 
|  |  | 
|  | ASSERT_EQ(flags_visited.size(), num_flags / 2); | 
|  | for (size_t i = 0; i < flags_visited.size(); i++) { | 
|  | ASSERT_EQ(flags_visited.count(i * 2), 1u); | 
|  | } | 
|  | } | 
|  |  | 
|  | TEST_F(AtomicFlagSetTest, SetActiveFalse) { | 
|  | std::vector<size_t> flags_visited; | 
|  |  | 
|  | CreateFlags(3, BindLambdaForTesting( | 
|  | [&](size_t index) { flags_visited.push_back(index); })); | 
|  |  | 
|  | atomic_flags_[1].SetActive(true); | 
|  | atomic_flags_[1].SetActive(false); | 
|  |  | 
|  | atomic_flag_set_.RunActiveCallbacks(); | 
|  | EXPECT_TRUE(flags_visited.empty()); | 
|  | } | 
|  |  | 
|  | TEST_F(AtomicFlagSetTest, ReleaseAtomicFlag) { | 
|  | constexpr size_t num_flags = 1000; | 
|  | constexpr size_t half_num_flags = num_flags / 2; | 
|  | std::set<size_t> flags_visited; | 
|  |  | 
|  | CreateFlags(num_flags, BindLambdaForTesting([&](size_t index) { | 
|  | flags_visited.insert(index); | 
|  | })); | 
|  |  | 
|  | // Set active all flags. | 
|  | for (size_t i = 0; i < num_flags; i++) { | 
|  | atomic_flags_[i].SetActive(true); | 
|  | } | 
|  |  | 
|  | // Release half the AtomicFlags. | 
|  | for (size_t i = 0; i < half_num_flags; i++) { | 
|  | atomic_flags_[i].ReleaseAtomicFlag(); | 
|  | } | 
|  |  | 
|  | atomic_flag_set_.RunActiveCallbacks(); | 
|  |  | 
|  | // We should only have visited the unreleased half. | 
|  | ASSERT_EQ(flags_visited.size(), half_num_flags); | 
|  | for (size_t i = 0; i < flags_visited.size(); i++) { | 
|  | ASSERT_EQ(flags_visited.count(i + half_num_flags), 1u); | 
|  | } | 
|  | } | 
|  |  | 
|  | TEST_F(AtomicFlagSetTest, GroupBecomesFull) { | 
|  | CreateFlags(AtomicFlagSetForTest::Group::kNumFlags - 1, | 
|  | BindLambdaForTesting([](size_t index) {})); | 
|  | AtomicFlagSetForTest::Group* group1 = | 
|  | atomic_flag_set_.GetAllocListForTesting(); | 
|  | EXPECT_THAT(group1->next.get(), IsNull()); | 
|  | EXPECT_EQ(group1, atomic_flag_set_.GetPartiallyFreeListForTesting()); | 
|  | EXPECT_FALSE(group1->IsFull()); | 
|  | EXPECT_FALSE(group1->IsEmpty()); | 
|  |  | 
|  | // Add an extra flag to fill up the group. | 
|  | atomic_flags_.push_back( | 
|  | atomic_flag_set_.AddFlag(base::BindRepeating([]() {}))); | 
|  |  | 
|  | EXPECT_TRUE(group1->IsFull()); | 
|  | EXPECT_THAT(atomic_flag_set_.GetPartiallyFreeListForTesting(), IsNull()); | 
|  | } | 
|  |  | 
|  | TEST_F(AtomicFlagSetTest, GroupBecomesEmptyOnlyEntryInPartiallyFreeList) { | 
|  | CreateFlags(AtomicFlagSetForTest::Group::kNumFlags + 1, | 
|  | BindLambdaForTesting([](size_t index) {})); | 
|  |  | 
|  | AtomicFlagSetForTest::Group* group1 = | 
|  | atomic_flag_set_.GetAllocListForTesting(); | 
|  | ASSERT_THAT(group1, NotNull()); | 
|  | EXPECT_FALSE(group1->IsFull()); | 
|  | EXPECT_FALSE(group1->IsEmpty()); | 
|  | EXPECT_EQ(group1, atomic_flag_set_.GetPartiallyFreeListForTesting()); | 
|  | AtomicFlagSetForTest::Group* group2 = group1->next.get(); | 
|  | ASSERT_THAT(group2, NotNull()); | 
|  | EXPECT_THAT(group2->next.get(), IsNull()); | 
|  | EXPECT_TRUE(group2->IsFull()); | 
|  |  | 
|  | // This will release |group1|. | 
|  | atomic_flags_[AtomicFlagSetForTest::Group::kNumFlags].ReleaseAtomicFlag(); | 
|  |  | 
|  | EXPECT_THAT(group2->next.get(), IsNull()); | 
|  | EXPECT_THAT(atomic_flag_set_.GetPartiallyFreeListForTesting(), IsNull()); | 
|  | } | 
|  |  | 
|  | TEST_F(AtomicFlagSetTest, GroupBecomesEmptyHeadOfPartiallyFreeList) { | 
|  | CreateFlags(AtomicFlagSetForTest::Group::kNumFlags * 2 + 1, | 
|  | BindLambdaForTesting([](size_t index) {})); | 
|  |  | 
|  | AtomicFlagSetForTest::Group* group1 = | 
|  | atomic_flag_set_.GetAllocListForTesting(); | 
|  | ASSERT_THAT(group1, NotNull()); | 
|  | EXPECT_FALSE(group1->IsFull()); | 
|  | EXPECT_FALSE(group1->IsEmpty()); | 
|  | AtomicFlagSetForTest::Group* group2 = group1->next.get(); | 
|  | ASSERT_THAT(group2, NotNull()); | 
|  | EXPECT_TRUE(group2->IsFull()); | 
|  | AtomicFlagSetForTest::Group* group3 = group2->next.get(); | 
|  | ASSERT_THAT(group3, NotNull()); | 
|  | EXPECT_THAT(group3->next.get(), IsNull()); | 
|  | EXPECT_TRUE(group3->IsFull()); | 
|  |  | 
|  | // |group1| is on the head of the partially free list, now add groups 2 and 3. | 
|  | atomic_flags_[AtomicFlagSetForTest::Group::kNumFlags].ReleaseAtomicFlag(); | 
|  | EXPECT_FALSE(group2->IsFull()); | 
|  | atomic_flags_[0].ReleaseAtomicFlag(); | 
|  | EXPECT_FALSE(group3->IsFull()); | 
|  |  | 
|  | EXPECT_EQ(group3, atomic_flag_set_.GetPartiallyFreeListForTesting()); | 
|  | EXPECT_EQ(group3->partially_free_list_prev, nullptr); | 
|  | EXPECT_EQ(group3->partially_free_list_next, group2); | 
|  | EXPECT_EQ(group2->partially_free_list_prev, group3); | 
|  | EXPECT_EQ(group2->partially_free_list_next, group1); | 
|  | EXPECT_EQ(group1->partially_free_list_prev, group2); | 
|  | EXPECT_EQ(group1->partially_free_list_next, nullptr); | 
|  | EXPECT_EQ(group1->prev, nullptr); | 
|  | EXPECT_EQ(group2->prev, group1); | 
|  | EXPECT_EQ(group3->prev, group2); | 
|  |  | 
|  | // This will release |group3|. | 
|  | for (size_t i = 0; i < AtomicFlagSetForTest::Group::kNumFlags; i++) { | 
|  | atomic_flags_[i].ReleaseAtomicFlag(); | 
|  | } | 
|  | EXPECT_EQ(group2, atomic_flag_set_.GetPartiallyFreeListForTesting()); | 
|  | EXPECT_EQ(group2->partially_free_list_prev, nullptr); | 
|  | EXPECT_EQ(group2->partially_free_list_next, group1); | 
|  | EXPECT_EQ(group1->partially_free_list_prev, group2); | 
|  | EXPECT_EQ(group1->partially_free_list_next, nullptr); | 
|  | EXPECT_EQ(group1, atomic_flag_set_.GetAllocListForTesting()); | 
|  | EXPECT_EQ(group1->next.get(), group2); | 
|  | EXPECT_EQ(group1->prev, nullptr); | 
|  | EXPECT_EQ(group2->prev, group1); | 
|  | EXPECT_EQ(group2->next.get(), nullptr); | 
|  | } | 
|  |  | 
|  | TEST_F(AtomicFlagSetTest, GroupBecomesEmptyMiddleOfPartiallyFreeList) { | 
|  | CreateFlags(AtomicFlagSetForTest::Group::kNumFlags * 2 + 1, | 
|  | BindLambdaForTesting([](size_t index) {})); | 
|  |  | 
|  | AtomicFlagSetForTest::Group* group1 = | 
|  | atomic_flag_set_.GetAllocListForTesting(); | 
|  | ASSERT_THAT(group1, NotNull()); | 
|  | EXPECT_FALSE(group1->IsFull()); | 
|  | EXPECT_FALSE(group1->IsEmpty()); | 
|  | AtomicFlagSetForTest::Group* group2 = group1->next.get(); | 
|  | ASSERT_THAT(group2, NotNull()); | 
|  | EXPECT_TRUE(group2->IsFull()); | 
|  | AtomicFlagSetForTest::Group* group3 = group2->next.get(); | 
|  | ASSERT_THAT(group3, NotNull()); | 
|  | EXPECT_THAT(group3->next.get(), IsNull()); | 
|  | EXPECT_TRUE(group3->IsFull()); | 
|  |  | 
|  | // |group1| is on the head of the partially free list, now add groups 2 and 3. | 
|  | atomic_flags_[AtomicFlagSetForTest::Group::kNumFlags].ReleaseAtomicFlag(); | 
|  | EXPECT_FALSE(group2->IsFull()); | 
|  | atomic_flags_[0].ReleaseAtomicFlag(); | 
|  | EXPECT_FALSE(group3->IsFull()); | 
|  |  | 
|  | EXPECT_EQ(group3, atomic_flag_set_.GetPartiallyFreeListForTesting()); | 
|  | EXPECT_EQ(group3->partially_free_list_prev, nullptr); | 
|  | EXPECT_EQ(group3->partially_free_list_next, group2); | 
|  | EXPECT_EQ(group2->partially_free_list_prev, group3); | 
|  | EXPECT_EQ(group2->partially_free_list_next, group1); | 
|  | EXPECT_EQ(group1->partially_free_list_prev, group2); | 
|  | EXPECT_EQ(group1->partially_free_list_next, nullptr); | 
|  | EXPECT_EQ(group1->prev, nullptr); | 
|  | EXPECT_EQ(group2->prev, group1); | 
|  | EXPECT_EQ(group3->prev, group2); | 
|  |  | 
|  | // This will release |group2|. | 
|  | for (size_t i = AtomicFlagSetForTest::Group::kNumFlags; | 
|  | i < AtomicFlagSetForTest::Group::kNumFlags * 2; i++) { | 
|  | atomic_flags_[i].ReleaseAtomicFlag(); | 
|  | } | 
|  | EXPECT_EQ(group3, atomic_flag_set_.GetPartiallyFreeListForTesting()); | 
|  | EXPECT_EQ(group3->partially_free_list_prev, nullptr); | 
|  | EXPECT_EQ(group3->partially_free_list_next, group1); | 
|  | EXPECT_EQ(group1->partially_free_list_prev, group3); | 
|  | EXPECT_EQ(group1->partially_free_list_next, nullptr); | 
|  | EXPECT_EQ(group1, atomic_flag_set_.GetAllocListForTesting()); | 
|  | EXPECT_EQ(group1->prev, nullptr); | 
|  | EXPECT_EQ(group1->next.get(), group3); | 
|  | EXPECT_EQ(group3->prev, group1); | 
|  | EXPECT_EQ(group3->next.get(), nullptr); | 
|  | } | 
|  |  | 
|  | TEST_F(AtomicFlagSetTest, GroupBecomesEmptyTailOfPartiallyFreeList) { | 
|  | CreateFlags(AtomicFlagSetForTest::Group::kNumFlags * 2 + 1, | 
|  | BindLambdaForTesting([](size_t index) {})); | 
|  |  | 
|  | AtomicFlagSetForTest::Group* group1 = | 
|  | atomic_flag_set_.GetAllocListForTesting(); | 
|  | ASSERT_THAT(group1, NotNull()); | 
|  | EXPECT_FALSE(group1->IsFull()); | 
|  | EXPECT_FALSE(group1->IsEmpty()); | 
|  | AtomicFlagSetForTest::Group* group2 = group1->next.get(); | 
|  | ASSERT_THAT(group2, NotNull()); | 
|  | EXPECT_TRUE(group2->IsFull()); | 
|  | AtomicFlagSetForTest::Group* group3 = group2->next.get(); | 
|  | ASSERT_THAT(group3, NotNull()); | 
|  | EXPECT_THAT(group3->next.get(), IsNull()); | 
|  | EXPECT_TRUE(group3->IsFull()); | 
|  |  | 
|  | // |group1| is on the head of the partially free list, now add groups 2 and 3. | 
|  | atomic_flags_[AtomicFlagSetForTest::Group::kNumFlags].ReleaseAtomicFlag(); | 
|  | EXPECT_FALSE(group2->IsFull()); | 
|  | atomic_flags_[0].ReleaseAtomicFlag(); | 
|  | EXPECT_FALSE(group3->IsFull()); | 
|  |  | 
|  | EXPECT_EQ(group3, atomic_flag_set_.GetPartiallyFreeListForTesting()); | 
|  | EXPECT_EQ(group3->partially_free_list_prev, nullptr); | 
|  | EXPECT_EQ(group3->partially_free_list_next, group2); | 
|  | EXPECT_EQ(group2->partially_free_list_prev, group3); | 
|  | EXPECT_EQ(group2->partially_free_list_next, group1); | 
|  | EXPECT_EQ(group1->partially_free_list_prev, group2); | 
|  | EXPECT_EQ(group1->partially_free_list_next, nullptr); | 
|  | EXPECT_EQ(group1->prev, nullptr); | 
|  | EXPECT_EQ(group2->prev, group1); | 
|  | EXPECT_EQ(group3->prev, group2); | 
|  |  | 
|  | // This will release |group1|. | 
|  | atomic_flags_[AtomicFlagSetForTest::Group::kNumFlags * 2].ReleaseAtomicFlag(); | 
|  | EXPECT_EQ(group3, atomic_flag_set_.GetPartiallyFreeListForTesting()); | 
|  | EXPECT_EQ(group3->partially_free_list_prev, nullptr); | 
|  | EXPECT_EQ(group3->partially_free_list_next, group2); | 
|  | EXPECT_EQ(group2->partially_free_list_prev, group3); | 
|  | EXPECT_EQ(group2->partially_free_list_next, nullptr); | 
|  | EXPECT_EQ(group2, atomic_flag_set_.GetAllocListForTesting()); | 
|  | EXPECT_EQ(group2->prev, nullptr); | 
|  | EXPECT_EQ(group2->next.get(), group3); | 
|  | EXPECT_EQ(group3->prev, group2); | 
|  | EXPECT_EQ(group3->next.get(), nullptr); | 
|  | } | 
|  |  | 
|  | }  // namespace internal | 
|  | }  // namespace sequence_manager | 
|  | }  // namespace base |