#include #include #include #include #include #include #include namespace smo { // Mock implementation of LockerAndInvokerBase for testing class MockLockerAndInvoker : public LockerAndInvokerBase { public: explicit MockLockerAndInvoker(const void* addr) : LockerAndInvokerBase(addr), awakened(false) {} bool awakened; Qutex* registeredQutex = nullptr; List::iterator queueIterator; List::iterator getLockvokerIteratorForQutex(Qutex& qutex) override { registeredQutex = &qutex; queueIterator = qutex.registerInQueue(std::shared_ptr(this)); return queueIterator; } void awaken(bool forceAwaken = false) override { awakened = true; } }; 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 } Qutex 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 auto it1 = 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 auto it1 = qutex.registerInQueue(mock1); auto it2 = qutex.registerInQueue(mock2); auto it3 = 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 auto it1 = qutex.registerInQueue(mock1); auto it2 = 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 auto it1 = qutex.registerInQueue(mock1); auto it2 = qutex.registerInQueue(mock2); auto it3 = qutex.registerInQueue(mock3); auto it4 = 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 auto it1 = qutex.registerInQueue(mock1); auto it2 = qutex.registerInQueue(mock2); auto it3 = qutex.registerInQueue(mock3); auto it4 = qutex.registerInQueue(mock4); auto it5 = qutex.registerInQueue(mock5); // Create one more mock int addr6 = 6; auto mock6 = std::make_shared(&addr6); auto it6 = 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 auto it1 = 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 auto it1 = qutex.registerInQueue(mock1); // Set as owned first qutex.isOwned = true; // Backoff should not rotate and should awaken the front item mock1->awakened = false; qutex.backoff(*mock1, 1); EXPECT_FALSE(qutex.isOwned); EXPECT_EQ(qutex.queue.size(), 1); // 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 auto it1 = qutex.registerInQueue(mock1); auto it2 = qutex.registerInQueue(mock2); auto it3 = 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 auto it1 = qutex.registerInQueue(mock1); auto it2 = 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 auto it1 = qutex.registerInQueue(mock1); auto it2 = qutex.registerInQueue(mock2); // Set as owned qutex.isOwned = true; // 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 with empty queue TEST_F(QutexTest, ReleaseEmptyQueue) { // Set as owned qutex.isOwned = true; // Release with empty queue should just set isOwned to false qutex.release(); EXPECT_FALSE(qutex.isOwned); 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 auto it1 = 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