// 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 #include #include #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 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 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 callback, size_t i) { callback.Run(i); }, callback, i))); } } AtomicFlagSetForTest atomic_flag_set_{AssociatedThreadId::CreateBound()}; std::vector atomic_flags_; }; TEST_F(AtomicFlagSetTest, VisitEmptyAtomicFlagSet) { atomic_flag_set_.RunActiveCallbacks(); // Shouldn't crash. } TEST_F(AtomicFlagSetTest, SetActiveOneFlag) { std::vector 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 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 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 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