From 4bcc30671bdf2217f10cfeb9063918c0519197cc Mon Sep 17 00:00:00 2001 From: Hayodea Hekol Date: Sat, 13 Jun 2026 16:19:30 -0400 Subject: [PATCH] Tests: Move qutex and nursery tests into libspinscale --- libspinscale | 2 +- tests/CMakeLists.txt | 31 - .../nonViralTaskNursery_tests.cpp | 657 ------------------ tests/smocore/qutex_tests.cpp | 373 ---------- 4 files changed, 1 insertion(+), 1062 deletions(-) delete mode 100644 tests/libspinscale/nonViralTaskNursery_tests.cpp delete mode 100644 tests/smocore/qutex_tests.cpp diff --git a/libspinscale b/libspinscale index 016b2d2..1763685 160000 --- a/libspinscale +++ b/libspinscale @@ -1 +1 @@ -Subproject commit 016b2d26deb54e73fa5e717f220a4211ec86d1c9 +Subproject commit 1763685c0e688700e4685a59218d0729c6cf392b diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 83e5378..80caa86 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,20 +1,3 @@ -# Create a test executable for qutex -add_executable(qutex_tests smocore/qutex_tests.cpp) - -# Link against Google Test and the smocore library -target_link_libraries(qutex_tests - gtest_main - smocore - ${Boost_LIBRARIES} - ${DL_LIBRARY} -) - -# Ensure Google Test is built before our test executable -add_dependencies(qutex_tests gtest_main) - -# Add the test to CTest -add_test(NAME qutex_tests COMMAND qutex_tests) - # Create a test executable for StagingBuffer add_executable(stagingBuffer_tests commonLibs/attachmentSupport/stagingBuffer_tests.cpp) @@ -30,17 +13,3 @@ add_dependencies(stagingBuffer_tests gtest_main) # Add the test to CTest add_test(NAME stagingBuffer_tests COMMAND stagingBuffer_tests) - -# Create a test executable for NonViralTaskNursery -add_executable(nonViralTaskNursery_tests - libspinscale/nonViralTaskNursery_tests.cpp) - -target_link_libraries(nonViralTaskNursery_tests - gtest_main - spinscale - ${Boost_LIBRARIES} -) - -add_dependencies(nonViralTaskNursery_tests gtest_main) - -add_test(NAME nonViralTaskNursery_tests COMMAND nonViralTaskNursery_tests) diff --git a/tests/libspinscale/nonViralTaskNursery_tests.cpp b/tests/libspinscale/nonViralTaskNursery_tests.cpp deleted file mode 100644 index 4fdf7bb..0000000 --- a/tests/libspinscale/nonViralTaskNursery_tests.cpp +++ /dev/null @@ -1,657 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -#include -#include -#include - -namespace { - -struct ResumeGate -{ - std::coroutine_handle<> waitingHandle; - - bool await_ready() const noexcept - { return false; } - - bool await_suspend(std::coroutine_handle<> callerHandle) noexcept - { - waitingHandle = callerHandle; - return true; - } - - void await_resume() const noexcept - {} -}; - -sscl::co::NonViralNonPostingInvoker immediateCompleteCReq( - std::exception_ptr &exceptionPtr, - std::function completion) -{ - (void)exceptionPtr; - (void)completion; - co_return; -} - -sscl::co::NonViralNonPostingInvoker throwingCompleteCReq( - std::exception_ptr &exceptionPtr, - std::function completion) -{ - (void)exceptionPtr; - (void)completion; - throw std::runtime_error("nursery test failure"); - co_return; -} - -sscl::co::NonViralNonPostingInvoker suspendUntilResumeCReq( - std::exception_ptr &exceptionPtr, - std::function completion, - ResumeGate &gate) -{ - (void)exceptionPtr; - (void)completion; - co_await gate; - co_return; -} - -sscl::co::NonViralNonPostingInvoker cancelAwareSuspendCReq( - std::exception_ptr &exceptionPtr, - std::function completion, - sscl::SyncCancelerForAsyncWork &canceler, - ResumeGate &gate) -{ - (void)exceptionPtr; - (void)completion; - - while (!canceler.isCancellationRequested()) - { - co_await gate; - } - - co_return; -} - -} // namespace - -class NonViralTaskNurseryTest : public ::testing::Test -{ -protected: - void SetUp() override - { - nursery.openAdmission(); - } - - sscl::co::NonViralTaskNursery nursery; - ResumeGate gate; - ResumeGate gate2; -}; - -TEST_F(NonViralTaskNurseryTest, GetNewSlotLeaseFillCommitRetires) -{ - auto lease = nursery.getNewSlotLease(); - lease.fillSlot( - [&lease]() - { - return immediateCompleteCReq( - lease.getExceptionStorage(), - lease.getCallerLambda()); - }); - lease.commit(); - - EXPECT_TRUE(nursery.allSettled()); - EXPECT_EQ(nursery.unsettledCount(), 0U); -} - -TEST_F(NonViralTaskNurseryTest, UncommittedLeaseReleasesReservation) -{ - EXPECT_EQ(nursery.unsettledCount(), 0U); - { - auto lease = nursery.getNewSlotLease(); - (void)lease; - } - - EXPECT_TRUE(nursery.allSettled()); -} - -TEST_F(NonViralTaskNurseryTest, CloseAdmissionRejectsNewLeases) -{ - nursery.closeAdmission(); - - EXPECT_THROW(nursery.getNewSlotLease(), std::runtime_error); -} - -TEST_F(NonViralTaskNurseryTest, SetOnSettledHookRejectsAfterFillSlot) -{ - auto lease = nursery.getNewSlotLease(); - lease.fillSlot( - [&lease]() - { - return immediateCompleteCReq( - lease.getExceptionStorage(), - lease.getCallerLambda()); - }); - - EXPECT_THROW( - lease.setOnSettledHook([](std::exception_ptr &) {}), - std::runtime_error); - lease.commit(); -} - -TEST_F(NonViralTaskNurseryTest, AsyncAwaitFiresOnDrain) -{ - std::atomic drained{false}; - - auto lease = nursery.getNewSlotLease(); - lease.fillSlot( - [&lease]() - { - return immediateCompleteCReq( - lease.getExceptionStorage(), - lease.getCallerLambda()); - }); - lease.commit(); - - nursery.closeAdmission(); - nursery.asyncAwaitAllSettlements( - [&drained]() - { - drained.store(true, std::memory_order_release); - }); - - EXPECT_TRUE(drained.load(std::memory_order_acquire)); -} - -TEST_F(NonViralTaskNurseryTest, AsyncAwaitRejectsWhenAdmissionOpen) -{ - EXPECT_THROW(nursery.asyncAwaitAllSettlements([]() {}), std::runtime_error); -} - -TEST_F(NonViralTaskNurseryTest, SecondDrainWaiterThrows) -{ - auto lease = nursery.getNewSlotLease(); - lease.fillSlot( - [&lease, this]() - { - return suspendUntilResumeCReq( - lease.getExceptionStorage(), - lease.getCallerLambda(), - gate); - }); - lease.commit(); - - nursery.closeAdmission(); - - bool firstWaiterRegistered = false; - nursery.asyncAwaitAllSettlements( - [&firstWaiterRegistered]() - { - firstWaiterRegistered = true; - }); - - EXPECT_FALSE(firstWaiterRegistered); - EXPECT_THROW( - nursery.asyncAwaitAllSettlements([]() {}), - std::runtime_error); - - if (gate.waitingHandle) - { - gate.waitingHandle.resume(); - } -} - -TEST_F(NonViralTaskNurseryTest, SyncAwaitNestedRun) -{ - boost::asio::io_context ioContext; - - auto lease = nursery.getNewSlotLease(); - lease.fillSlot( - [&lease, this]() - { - return suspendUntilResumeCReq( - lease.getExceptionStorage(), - lease.getCallerLambda(), - gate); - }); - lease.commit(); - - std::thread awaitThread( - [this, &ioContext]() - { - nursery.closeAdmission(); - nursery.syncAwaitAllSettlements(ioContext); - }); - - std::this_thread::sleep_for(std::chrono::milliseconds(10)); - - ASSERT_TRUE(static_cast(gate.waitingHandle)); - gate.waitingHandle.resume(); - - awaitThread.join(); - EXPECT_TRUE(nursery.allSettled()); -} - -TEST_F(NonViralTaskNurseryTest, RequestCancelOnAllDoesNotDestroyInvokers) -{ - auto lease = nursery.getNewSlotLease(); - lease.getSyncCanceler().startAcceptingWork(); - lease.fillSlot( - [&lease, this]() - { - return suspendUntilResumeCReq( - lease.getExceptionStorage(), - lease.getCallerLambda(), - gate); - }); - lease.commit(); - - EXPECT_EQ(nursery.unsettledCount(), 1U); - nursery.requestCancelOnAll(); - EXPECT_EQ(nursery.unsettledCount(), 1U); - - ASSERT_TRUE(static_cast(gate.waitingHandle)); - gate.waitingHandle.resume(); - - std::this_thread::sleep_for(std::chrono::milliseconds(10)); - EXPECT_TRUE(nursery.allSettled()); -} - -TEST_F(NonViralTaskNurseryTest, RequestCancelOnAllStopsCanceler) -{ - auto lease = nursery.getNewSlotLease(); - lease.getSyncCanceler().startAcceptingWork(); - lease.fillSlot( - [&lease, this]() - { - return cancelAwareSuspendCReq( - lease.getExceptionStorage(), - lease.getCallerLambda(), - lease.getSyncCanceler(), - gate); - }); - lease.commit(); - - nursery.requestCancelOnAll(); - EXPECT_TRUE(lease.getSyncCanceler().isCancellationRequested()); - - ASSERT_TRUE(static_cast(gate.waitingHandle)); - gate.waitingHandle.resume(); - - std::this_thread::sleep_for(std::chrono::milliseconds(10)); - EXPECT_TRUE(nursery.allSettled()); -} - -TEST_F(NonViralTaskNurseryTest, ExceptionPtrRecorded) -{ - std::exception_ptr captured; - - auto lease = nursery.getNewSlotLease(); - lease.fillSlot( - [&captured, &lease]() - { - std::exception_ptr &exceptionStorage = - lease.getExceptionStorage(); - auto invoker = throwingCompleteCReq( - exceptionStorage, - lease.getCallerLambda()); - captured = exceptionStorage; - return invoker; - }); - lease.commit(); - - EXPECT_TRUE(captured != nullptr); - EXPECT_TRUE(nursery.allSettled()); -} - -TEST_F(NonViralTaskNurseryTest, LaunchSugar) -{ - auto handle = nursery.launch( - [](sscl::co::NonViralTaskNursery::Slot::Lease &lease) - { - return immediateCompleteCReq( - lease.getExceptionStorage(), - lease.getCallerLambda()); - }); - - EXPECT_TRUE(handle == handle); - EXPECT_TRUE(nursery.allSettled()); -} - -TEST_F(NonViralTaskNurseryTest, LaunchWithOnSettledHook) -{ - std::atomic hookRan{false}; - - nursery.launch( - [](sscl::co::NonViralTaskNursery::Slot::Lease &lease) - { - return immediateCompleteCReq( - lease.getExceptionStorage(), - lease.getCallerLambda()); - }, - [&hookRan](std::exception_ptr &) - { - hookRan.store(true, std::memory_order_release); - }); - - EXPECT_TRUE(hookRan.load(std::memory_order_acquire)); - EXPECT_TRUE(nursery.allSettled()); -} - -TEST_F(NonViralTaskNurseryTest, HandleStability) -{ - auto handle = nursery.launch( - [](sscl::co::NonViralTaskNursery::Slot::Lease &lease) - { - return immediateCompleteCReq( - lease.getExceptionStorage(), - lease.getCallerLambda()); - }); - - sscl::co::NonViralTaskNursery::Slot::Handle copy = handle; - EXPECT_TRUE(handle == copy); - EXPECT_TRUE(nursery.allSettled()); -} - -TEST_F(NonViralTaskNurseryTest, CommitWithoutFillSlotThrows) -{ - auto lease = nursery.getNewSlotLease(); - - EXPECT_THROW(lease.commit(), std::runtime_error); -} - -TEST_F(NonViralTaskNurseryTest, DoubleCommitThrows) -{ - auto lease = nursery.getNewSlotLease(); - lease.fillSlot( - [&lease]() - { - return immediateCompleteCReq( - lease.getExceptionStorage(), - lease.getCallerLambda()); - }); - lease.commit(); - - EXPECT_THROW(lease.commit(), std::runtime_error); -} - -TEST_F(NonViralTaskNurseryTest, FillSlotTwiceThrows) -{ - auto lease = nursery.getNewSlotLease(); - lease.fillSlot( - [&lease, this]() - { - return suspendUntilResumeCReq( - lease.getExceptionStorage(), - lease.getCallerLambda(), - gate); - }); - - EXPECT_THROW( - lease.fillSlot( - [&lease]() - { - return immediateCompleteCReq( - lease.getExceptionStorage(), - lease.getCallerLambda()); - }), - std::runtime_error); - - if (gate.waitingHandle) { - gate.waitingHandle.resume(); - } - - lease.commit(); - EXPECT_TRUE(nursery.allSettled()); -} - -TEST_F(NonViralTaskNurseryTest, SyncAwaitRejectsWhenAdmissionOpen) -{ - boost::asio::io_context ioContext; - - EXPECT_THROW( - nursery.syncAwaitAllSettlements(ioContext), - std::runtime_error); -} - -TEST_F(NonViralTaskNurseryTest, SyncAwaitRejectsStoppedIoContext) -{ - auto lease = nursery.getNewSlotLease(); - lease.fillSlot( - [&lease, this]() - { - return suspendUntilResumeCReq( - lease.getExceptionStorage(), - lease.getCallerLambda(), - gate); - }); - lease.commit(); - - nursery.closeAdmission(); - - boost::asio::io_context ioContext; - ioContext.stop(); - - EXPECT_THROW( - nursery.syncAwaitAllSettlements(ioContext), - std::runtime_error); - - if (gate.waitingHandle) { - gate.waitingHandle.resume(); - } -} - -TEST_F(NonViralTaskNurseryTest, SyncAwaitReturnsImmediatelyWhenDrained) -{ - boost::asio::io_context ioContext; - - nursery.closeAdmission(); - EXPECT_TRUE(nursery.allSettled()); - - nursery.syncAwaitAllSettlements(ioContext); - EXPECT_TRUE(nursery.allSettled()); -} - -TEST_F(NonViralTaskNurseryTest, UnsettledCountTracksInFlightTasks) -{ - auto lease = nursery.getNewSlotLease(); - lease.fillSlot( - [&lease, this]() - { - return suspendUntilResumeCReq( - lease.getExceptionStorage(), - lease.getCallerLambda(), - gate); - }); - lease.commit(); - - EXPECT_EQ(nursery.unsettledCount(), 1U); - EXPECT_FALSE(nursery.allSettled()); - - if (gate.waitingHandle) { - gate.waitingHandle.resume(); - } - - std::this_thread::sleep_for(std::chrono::milliseconds(10)); - EXPECT_EQ(nursery.unsettledCount(), 0U); - EXPECT_TRUE(nursery.allSettled()); -} - -TEST_F(NonViralTaskNurseryTest, MultipleTasksDrainTogether) -{ - std::atomic drained{false}; - - auto lease1 = nursery.getNewSlotLease(); - lease1.fillSlot( - [&lease1, this]() - { - return suspendUntilResumeCReq( - lease1.getExceptionStorage(), - lease1.getCallerLambda(), - gate); - }); - lease1.commit(); - - auto lease2 = nursery.getNewSlotLease(); - lease2.fillSlot( - [&lease2, this]() - { - return suspendUntilResumeCReq( - lease2.getExceptionStorage(), - lease2.getCallerLambda(), - gate2); - }); - lease2.commit(); - - EXPECT_EQ(nursery.unsettledCount(), 2U); - - nursery.closeAdmission(); - nursery.asyncAwaitAllSettlements( - [&drained]() - { - drained.store(true, std::memory_order_release); - }); - - EXPECT_FALSE(drained.load(std::memory_order_acquire)); - - if (gate.waitingHandle) { - gate.waitingHandle.resume(); - } - - std::this_thread::sleep_for(std::chrono::milliseconds(10)); - EXPECT_FALSE(drained.load(std::memory_order_acquire)); - - if (gate2.waitingHandle) { - gate2.waitingHandle.resume(); - } - - std::this_thread::sleep_for(std::chrono::milliseconds(10)); - EXPECT_TRUE(drained.load(std::memory_order_acquire)); - EXPECT_TRUE(nursery.allSettled()); -} - -TEST_F(NonViralTaskNurseryTest, OnSettledHookRunsAtRetirement) -{ - std::atomic hookRan{false}; - - auto lease = nursery.getNewSlotLease(); - lease.setOnSettledHook( - [&hookRan](std::exception_ptr &) - { - hookRan.store(true, std::memory_order_release); - }); - lease.fillSlot( - [&lease]() - { - return immediateCompleteCReq( - lease.getExceptionStorage(), - lease.getCallerLambda()); - }); - lease.commit(); - - EXPECT_TRUE(hookRan.load(std::memory_order_acquire)); -} - -TEST_F(NonViralTaskNurseryTest, OnSettledHookSeesRetiredSlot) -{ - auto lease = nursery.getNewSlotLease(); - lease.setOnSettledHook( - [this](std::exception_ptr &) - { - EXPECT_TRUE(nursery.allSettled()); - EXPECT_EQ(nursery.unsettledCount(), 0U); - }); - lease.fillSlot( - [&lease]() - { - return immediateCompleteCReq( - lease.getExceptionStorage(), - lease.getCallerLambda()); - }); - lease.commit(); -} - -TEST_F(NonViralTaskNurseryTest, DuplicateRetireThrows) -{ - std::function completion; - - auto lease = nursery.getNewSlotLease(); - lease.fillSlot( - [&completion, &lease]() - { - completion = lease.getCallerLambda(); - return immediateCompleteCReq( - lease.getExceptionStorage(), - completion); - }); - lease.commit(); - - ASSERT_TRUE(static_cast(completion)); - EXPECT_THROW(completion(), std::runtime_error); - EXPECT_TRUE(nursery.allSettled()); -} - -TEST_F(NonViralTaskNurseryTest, MovedLeaseTransfersReleaseObligation) -{ - EXPECT_EQ(nursery.unsettledCount(), 0U); - { - auto lease = nursery.getNewSlotLease(); - auto movedLease = std::move(lease); - (void)movedLease; - } - - EXPECT_TRUE(nursery.allSettled()); - EXPECT_EQ(nursery.unsettledCount(), 0U); -} - -TEST_F(NonViralTaskNurseryTest, LaunchAssignsDistinctHandles) -{ - auto handle1 = nursery.launch( - [this](sscl::co::NonViralTaskNursery::Slot::Lease &lease) - { - return suspendUntilResumeCReq( - lease.getExceptionStorage(), - lease.getCallerLambda(), - gate); - }); - - auto handle2 = nursery.launch( - [this](sscl::co::NonViralTaskNursery::Slot::Lease &lease) - { - return suspendUntilResumeCReq( - lease.getExceptionStorage(), - lease.getCallerLambda(), - gate2); - }); - - EXPECT_NE(handle1, handle2); - EXPECT_EQ(nursery.unsettledCount(), 2U); - - if (gate.waitingHandle) { - gate.waitingHandle.resume(); - } - - if (gate2.waitingHandle) { - gate2.waitingHandle.resume(); - } - - std::this_thread::sleep_for(std::chrono::milliseconds(10)); - EXPECT_TRUE(nursery.allSettled()); -} - -TEST_F(NonViralTaskNurseryTest, AdmissionIsOpenReflectsCloseAndOpen) -{ - EXPECT_TRUE(nursery.admissionIsOpen()); - - nursery.closeAdmission(); - EXPECT_FALSE(nursery.admissionIsOpen()); - - nursery.openAdmission(); - EXPECT_TRUE(nursery.admissionIsOpen()); -} diff --git a/tests/smocore/qutex_tests.cpp b/tests/smocore/qutex_tests.cpp deleted file mode 100644 index bf33a01..0000000 --- a/tests/smocore/qutex_tests.cpp +++ /dev/null @@ -1,373 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include - -namespace smo { - -// Mock implementation of LockerAndInvokerBase for testing -class MockLockerAndInvoker : public sscl::cps::LockerAndInvokerBase { -public: - explicit MockLockerAndInvoker(const void* addr) - : sscl::cps::LockerAndInvokerBase(addr), awakened(false) {} - - bool awakened; - mutable sscl::cps::Qutex* registeredQutex = nullptr; - mutable sscl::cps::LockerAndInvokerBase::List::iterator queueIterator; - - sscl::cps::LockerAndInvokerBase::List::iterator - getLockvokerIteratorForQutex(sscl::cps::Qutex& qutex) const override - { - registeredQutex = &qutex; - - for (auto it = qutex.queue.begin(); it != qutex.queue.end(); ++it) - { - if ((**it) == *this) - { - queueIterator = it; - return it; - } - } - - throw std::runtime_error( - "MockLockerAndInvoker: not registered in qutex queue"); - } - - void awaken(bool forceAwaken = false) override - { - (void)forceAwaken; - awakened = true; - } - - size_t getLockSetSize() const override - { - return 1; - } - - sscl::cps::Qutex& getLockAt(size_t index) const override - { - if (index != 0 || registeredQutex == nullptr) - { - throw std::runtime_error( - "MockLockerAndInvoker: invalid lock index or no registered qutex"); - } - - return *registeredQutex; - } -}; - -class QutexTest : public ::testing::Test { -protected: - void SetUp() override { - // Create mock lockvokers with unique addresses - mock1 = std::make_shared(&addr1); - mock2 = std::make_shared(&addr2); - mock3 = std::make_shared(&addr3); - mock4 = std::make_shared(&addr4); - mock5 = std::make_shared(&addr5); - } - - void TearDown() override { - // Clean up - } - - sscl::cps::Qutex qutex{"test-qutex"}; - std::shared_ptr mock1, mock2, mock3, mock4, mock5; - - // Unique addresses for testing - int addr1 = 1; - int addr2 = 2; - int addr3 = 3; - int addr4 = 4; - int addr5 = 5; -}; - -// Test basic queue registration and unregistration -TEST_F(QutexTest, QueueRegistrationAndUnregistration) { - // Register mock1 in queue - auto it1 = qutex.registerInQueue(mock1); - EXPECT_EQ(qutex.queue.size(), 1); - EXPECT_FALSE(qutex.isOwned); - - // Register mock2 in queue - auto it2 = qutex.registerInQueue(mock2); - EXPECT_EQ(qutex.queue.size(), 2); - - // Unregister mock1 - qutex.unregisterFromQueue(it1); - EXPECT_EQ(qutex.queue.size(), 1); - - // Unregister mock2 - qutex.unregisterFromQueue(it2); - EXPECT_EQ(qutex.queue.size(), 0); -} - -// Test single lock acquisition when queue is empty -TEST_F(QutexTest, SingleLockAcquisitionEmptyQueue) { - // Register mock1 - (void)qutex.registerInQueue(mock1); - - // Try to acquire with nRequiredLocks = 1 - bool acquired = qutex.tryAcquire(*mock1, 1); - EXPECT_TRUE(acquired); - EXPECT_TRUE(qutex.isOwned); -} - -// Test single lock acquisition when at front of queue -TEST_F(QutexTest, SingleLockAcquisitionAtFront) { - // Register multiple lockvokers - (void)qutex.registerInQueue(mock1); - (void)qutex.registerInQueue(mock2); - (void)qutex.registerInQueue(mock3); - - // mock1 should be at front, mock3 at back - EXPECT_EQ(qutex.queue.front().get(), mock1.get()); - EXPECT_EQ(qutex.queue.back().get(), mock3.get()); - - // mock1 (at front) should succeed - bool acquired = qutex.tryAcquire(*mock1, 1); - EXPECT_TRUE(acquired); - EXPECT_TRUE(qutex.isOwned); - - // mock2 (not at front) should fail - qutex.isOwned = false; // Reset for testing - bool acquired2 = qutex.tryAcquire(*mock2, 1); - EXPECT_FALSE(acquired2); -} - -// Test single lock acquisition failure when not at front -TEST_F(QutexTest, SingleLockAcquisitionNotAtFront) { - // Register multiple lockvokers - (void)qutex.registerInQueue(mock1); - (void)qutex.registerInQueue(mock2); - - // mock2 (not at front) should fail - bool acquired = qutex.tryAcquire(*mock2, 1); - EXPECT_FALSE(acquired); - EXPECT_FALSE(qutex.isOwned); -} - -// Test multi-lock acquisition (nRequiredLocks > 1) -TEST_F(QutexTest, MultiLockAcquisition) { - // Register 4 lockvokers - (void)qutex.registerInQueue(mock1); - (void)qutex.registerInQueue(mock2); - (void)qutex.registerInQueue(mock3); - (void)qutex.registerInQueue(mock4); - - // For nRequiredLocks = 2, need to be in top 50% (top 2 out of 4) - // mock1 (position 1) should succeed - bool acquired1 = qutex.tryAcquire(*mock1, 2); - EXPECT_TRUE(acquired1); - - // Reset for next test - qutex.isOwned = false; - - // mock2 (position 2) should succeed - bool acquired2 = qutex.tryAcquire(*mock2, 2); - EXPECT_TRUE(acquired2); - - // Reset for next test - qutex.isOwned = false; - - // mock3 (position 3) should fail (in bottom 50%) - bool acquired3 = qutex.tryAcquire(*mock3, 2); - EXPECT_FALSE(acquired3); - - // Reset for next test - qutex.isOwned = false; - - // mock4 (position 4) should fail (in bottom 50%) - bool acquired4 = qutex.tryAcquire(*mock4, 2); - EXPECT_FALSE(acquired4); -} - -// Test multi-lock acquisition with 3 required locks -TEST_F(QutexTest, MultiLockAcquisitionThreeLocks) { - // Register 6 lockvokers - (void)qutex.registerInQueue(mock1); - (void)qutex.registerInQueue(mock2); - (void)qutex.registerInQueue(mock3); - (void)qutex.registerInQueue(mock4); - (void)qutex.registerInQueue(mock5); - - // Create one more mock - int addr6 = 6; - auto mock6 = std::make_shared(&addr6); - (void)qutex.registerInQueue(mock6); - - // For nRequiredLocks = 3, need to be in top 66% (top 4 out of 6) - // Positions 1, 2, 3, 4 should succeed - // Positions 5, 6 should fail - - bool acquired1 = qutex.tryAcquire(*mock1, 3); - EXPECT_TRUE(acquired1); - qutex.isOwned = false; - - bool acquired2 = qutex.tryAcquire(*mock2, 3); - EXPECT_TRUE(acquired2); - qutex.isOwned = false; - - bool acquired3 = qutex.tryAcquire(*mock3, 3); - EXPECT_TRUE(acquired3); - qutex.isOwned = false; - - bool acquired4 = qutex.tryAcquire(*mock4, 3); - EXPECT_TRUE(acquired4); - qutex.isOwned = false; - - bool acquired5 = qutex.tryAcquire(*mock5, 3); - EXPECT_FALSE(acquired5); - - qutex.isOwned = false; - bool acquired6 = qutex.tryAcquire(*mock6, 3); - EXPECT_FALSE(acquired6); -} - -// Test acquisition failure when already owned -TEST_F(QutexTest, AcquisitionFailureWhenOwned) { - // Register mock1 - (void)qutex.registerInQueue(mock1); - - // Manually set as owned - qutex.isOwned = true; - - // Try to acquire should fail - bool acquired = qutex.tryAcquire(*mock1, 1); - EXPECT_FALSE(acquired); - EXPECT_TRUE(qutex.isOwned); -} - -// Test backoff with single item (should not rotate) -TEST_F(QutexTest, BackoffSingleItem) { - // Register only one lockvoker - (void)qutex.registerInQueue(mock1); - - // Set as owned first - qutex.isOwned = true; - - // nRequiredLocks > 1 avoids the "front item with nRequiredLocks==1" guard - mock1->awakened = false; - qutex.backoff(*mock1, 2); - - EXPECT_FALSE(qutex.isOwned); - EXPECT_EQ(qutex.queue.size(), 1u); - // Should not awaken since there's only one item - EXPECT_FALSE(mock1->awakened); -} - -// Test backoff with multiple items and rotation -TEST_F(QutexTest, BackoffWithRotation) { - // Register multiple lockvokers - (void)qutex.registerInQueue(mock1); - (void)qutex.registerInQueue(mock2); - (void)qutex.registerInQueue(mock3); - - // Set as owned first - qutex.isOwned = true; - - // mock1 should be at front initially - EXPECT_EQ(qutex.queue.front().get(), mock1.get()); - - // Backoff from mock1 (at front) with nRequiredLocks = 2 - mock2->awakened = false; - qutex.backoff(*mock1, 2); - - // mock1 should have been rotated to position 2 - // mock2 should now be at front - EXPECT_EQ(qutex.queue.front().get(), mock2.get()); - EXPECT_FALSE(qutex.isOwned); - - // mock2 should have been awakened - EXPECT_TRUE(mock2->awakened); -} - -// Test backoff with rotation to back when queue smaller than nRequiredLocks -TEST_F(QutexTest, BackoffRotationToBack) { - // Register only 2 lockvokers - (void)qutex.registerInQueue(mock1); - (void)qutex.registerInQueue(mock2); - - // Set as owned first - qutex.isOwned = true; - - // mock1 should be at front initially - EXPECT_EQ(qutex.queue.front().get(), mock1.get()); - EXPECT_EQ(qutex.queue.back().get(), mock2.get()); - - // Backoff from mock1 with nRequiredLocks = 5 (larger than queue size) - mock2->awakened = false; - qutex.backoff(*mock1, 5); - - // mock1 should have been moved to the back - EXPECT_EQ(qutex.queue.front().get(), mock2.get()); - EXPECT_EQ(qutex.queue.back().get(), mock1.get()); - EXPECT_FALSE(qutex.isOwned); - - // mock2 should have been awakened - EXPECT_TRUE(mock2->awakened); -} - -// Test release functionality -TEST_F(QutexTest, Release) { - // Register multiple lockvokers - (void)qutex.registerInQueue(mock1); - (void)qutex.registerInQueue(mock2); - - ASSERT_TRUE(qutex.tryAcquire(*mock1, 1)); - - // Release should set isOwned to false and awaken front item - mock1->awakened = false; - qutex.release(); - - EXPECT_FALSE(qutex.isOwned); - EXPECT_TRUE(mock1->awakened); -} - -// Test release without a prior acquire is rejected -TEST_F(QutexTest, ReleaseWithoutAcquireThrows) { - qutex.isOwned = true; - - EXPECT_THROW(qutex.release(), std::runtime_error); - EXPECT_TRUE(qutex.queue.empty()); -} - -// Test exception when trying to acquire from empty queue -TEST_F(QutexTest, ExceptionOnEmptyQueueAcquisition) { - // Don't register any lockvokers - EXPECT_THROW(qutex.tryAcquire(*mock1, 1), std::runtime_error); -} - -// Test exception when backoff called on empty queue -TEST_F(QutexTest, ExceptionOnEmptyQueueBackoff) { - // Don't register any lockvokers - EXPECT_THROW(qutex.backoff(*mock1, 1), std::runtime_error); -} - -// Test edge case: single lockvoker with multiple required locks -TEST_F(QutexTest, SingleLockvokerMultipleRequiredLocks) { - // Register only one lockvoker - (void)qutex.registerInQueue(mock1); - - // Should succeed regardless of nRequiredLocks when only one item - bool acquired = qutex.tryAcquire(*mock1, 5); - EXPECT_TRUE(acquired); - EXPECT_TRUE(qutex.isOwned); -} - -// Test unregistration without locking -TEST_F(QutexTest, UnregistrationWithoutLocking) { - // Register lockvoker - auto it1 = qutex.registerInQueue(mock1); - EXPECT_EQ(qutex.queue.size(), 1); - - // Unregister without locking - qutex.unregisterFromQueue(it1, false); - EXPECT_EQ(qutex.queue.size(), 0); -} - -} // namespace smo