/* * * Copyright (c) 2024 Project CHIP Authors * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include #include #include using namespace chip::Callback; // Expose Cancelable anchor for testing template struct TestGroupedCallbackList : public GroupedCallbackList { Cancelable * Anchor() { return this; } }; static void CallbackFn(void *) {} static void CallbackWithIntFn(void *, int) {} typedef void (*CallWithIntFn)(void *, int); static void * StringContext(char const * string) { return const_cast(static_cast(string)); } static void ValidateList(Cancelable const * anchor) { #if 0 // for manual debugging { ChipLogDetail(NotSpecified, "ANCHOR: %p", anchor); Cancelable * ca = anchor->mNext; while (ca != anchor) { auto * cb = Callback<>::FromCancelable(ca); ChipLogDetail(NotSpecified, "%s%p (prev=%p, cancel=%p) %s", (ca->mPrev->mNext == ca) ? "> " : " ", ca, ca->mPrev, ca->mCancel, static_cast(cb->mContext)); ca = ca->mNext; } } #endif { EXPECT_TRUE(anchor->mPrev->mNext == anchor); EXPECT_TRUE(anchor->mNext->mPrev == anchor); std::unordered_map index; index[anchor] = 0; size_t lastPrevIndex = 0; Cancelable * ca = anchor->mNext; for (size_t i = 1; ca != anchor; i++, ca = ca->mNext) { EXPECT_TRUE(index.find(ca) == index.end()); // cycle? index[ca] = i; Cancelable * prev = ca->mPrev; auto search = index.find(prev); EXPECT_TRUE(search != index.end()); // prev should point backwards EXPECT_GE(search->second, lastPrevIndex); // should be monotonic lastPrevIndex = search->second; } } } TEST(GroupedCallbackListTest, Trivial) { TestGroupedCallbackList list; Callback<> * out = nullptr; EXPECT_TRUE(list.IsEmpty()); EXPECT_FALSE(list.Peek(out)); Callback cbOne(CallbackFn, StringContext("cbOne")); list.Enqueue(&cbOne); EXPECT_FALSE(list.IsEmpty()); EXPECT_TRUE(list.Peek(out)); EXPECT_TRUE(out == &cbOne); cbOne.Cancel(); EXPECT_TRUE(list.IsEmpty()); } TEST(GroupedCallbackListTest, EnqueueAllAndPeek) { TestGroupedCallbackList list; Callback cbOne(CallbackFn, StringContext("cbOne")); Callback cbTwo(CallbackWithIntFn, StringContext("cbTwo")); list.Enqueue(&cbOne, &cbTwo); ValidateList(list.Anchor()); Callback * outOne = nullptr; Callback * outTwo = nullptr; EXPECT_TRUE(list.Peek(outOne, outTwo)); EXPECT_TRUE(outOne == &cbOne); EXPECT_TRUE(outTwo == &cbTwo); } TEST(GroupedCallbackListTest, EnqueueSparseAndPeek) { TestGroupedCallbackList list; Callback cbTwo(CallbackFn, StringContext("cbTwo")); list.Enqueue(nullptr, &cbTwo); ValidateList(list.Anchor()); Callback<> * outOne = &cbTwo; // poison Callback<> * outTwo = nullptr; EXPECT_TRUE(list.Peek(outOne, outTwo)); EXPECT_TRUE(outOne == nullptr); EXPECT_TRUE(outTwo == &cbTwo); } TEST(GroupedCallbackListTest, EnqueueAndClear) { TestGroupedCallbackList list; Callback cbOne(CallbackFn, StringContext("cbOne")); Callback cbTwo(CallbackWithIntFn, StringContext("cbTwo")); list.Enqueue(&cbOne, &cbTwo); Callback cbThree(CallbackFn, StringContext("cbThree")); list.Enqueue(&cbThree, nullptr); ValidateList(list.Anchor()); EXPECT_FALSE(list.IsEmpty()); EXPECT_TRUE(cbOne.IsRegistered()); EXPECT_TRUE(cbTwo.IsRegistered()); EXPECT_TRUE(cbThree.IsRegistered()); list.Clear(); ValidateList(list.Anchor()); EXPECT_TRUE(list.IsEmpty()); EXPECT_FALSE(cbOne.IsRegistered()); EXPECT_FALSE(cbTwo.IsRegistered()); EXPECT_FALSE(cbThree.IsRegistered()); } TEST(GroupedCallbackListTest, Complex) { TestGroupedCallbackList list; ValidateList(list.Anchor()); EXPECT_TRUE(list.IsEmpty()); Callback cbZero(CallbackFn, StringContext("cbZero")); list.Enqueue(&cbZero, nullptr); ValidateList(list.Anchor()); EXPECT_FALSE(list.IsEmpty()); EXPECT_TRUE(cbZero.IsRegistered()); Callback cbOne(CallbackFn, StringContext("cbOne")); Callback cbTwo(CallbackFn, StringContext("cbTwo")); list.Enqueue(&cbOne, &cbTwo); ValidateList(list.Anchor()); EXPECT_TRUE(cbOne.IsRegistered()); EXPECT_TRUE(cbTwo.IsRegistered()); cbZero.Cancel(); ValidateList(list.Anchor()); EXPECT_FALSE(cbZero.IsRegistered()); Callback cbThree(CallbackFn, StringContext("cbThree")); list.Enqueue(&cbThree, nullptr); ValidateList(list.Anchor()); Callback cbFour(CallbackFn, StringContext("cbFour")); list.Enqueue(nullptr, &cbFour); ValidateList(list.Anchor()); cbOne.Cancel(); // also cancels cbTwo ValidateList(list.Anchor()); EXPECT_FALSE(cbOne.IsRegistered()); EXPECT_FALSE(cbTwo.IsRegistered()); Callback<> * outA = &cbZero; Callback<> * outB = &cbZero; EXPECT_TRUE(list.Take(outA, outB)); ValidateList(list.Anchor()); EXPECT_TRUE(outA == &cbThree); EXPECT_TRUE(outB == nullptr); EXPECT_TRUE(list.Take(outA, outB)); ValidateList(list.Anchor()); EXPECT_TRUE(outA == nullptr); EXPECT_TRUE(outB == &cbFour); EXPECT_TRUE(list.IsEmpty()); } TEST(GroupedCallbackListTest, EnqueueTakeAll) { TestGroupedCallbackList listA; Callback cbOne(CallbackFn, StringContext("cbOne")); Callback cbTwo(CallbackFn, StringContext("cbTwo")); listA.Enqueue(&cbOne, &cbTwo); ValidateList(listA.Anchor()); EXPECT_FALSE(listA.IsEmpty()); EXPECT_TRUE(cbOne.IsRegistered()); EXPECT_TRUE(cbTwo.IsRegistered()); TestGroupedCallbackList listB; Callback cbThree(CallbackFn, StringContext("cbThree")); listB.Enqueue(&cbThree, nullptr); ValidateList(listB.Anchor()); EXPECT_FALSE(listB.IsEmpty()); EXPECT_TRUE(cbThree.IsRegistered()); listB.EnqueueTakeAll(listA); ValidateList(listA.Anchor()); ValidateList(listB.Anchor()); EXPECT_TRUE(cbThree.IsRegistered()); EXPECT_TRUE(cbOne.IsRegistered()); EXPECT_TRUE(cbTwo.IsRegistered()); EXPECT_FALSE(listB.IsEmpty()); EXPECT_TRUE(listA.IsEmpty()); }