From fb61d2e72a40654cd6793c25e9fc5189111df464 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Poyraz=20K=C3=BC=C3=A7=C3=BCkarslan?= <83272398+PoyrazK@users.noreply.github.com> Date: Wed, 15 Apr 2026 19:52:31 +0300 Subject: [PATCH] test(lock_manager): add 8 new unit tests - UnlockNonExistent: verify unlocking non-existent lock returns false - ExclusiveBlocksShared: exclusive blocks shared lock acquisition - SharedBlocksExclusive: shared blocks exclusive lock acquisition - UnlockReleasesOnlyTxn: unlock only releases specific transaction's lock - LockSameRIDTwice: verify re-acquisition is idempotent - MultipleSharedLocks: three transactions can hold shared locks - LockDifferentRIDs: transactions hold locks on different RIDs independently - ExclusiveThenShared: exclusive followed by shared on different RID succeeds --- tests/lock_manager_tests.cpp | 169 +++++++++++++++++++++++++++++++++++ 1 file changed, 169 insertions(+) diff --git a/tests/lock_manager_tests.cpp b/tests/lock_manager_tests.cpp index e59ed496..eba498a2 100644 --- a/tests/lock_manager_tests.cpp +++ b/tests/lock_manager_tests.cpp @@ -126,4 +126,173 @@ TEST(LockManagerTests, Deadlock) { static_cast(lm.unlock(&txn2, ridA)); } +// ============= New Tests ============= + +/** + * @brief Verifies unlocking a non-existent lock returns false + */ +TEST(LockManagerTests, UnlockNonExistent) { + LockManager lm; + Transaction txn(1); + HeapTable::TupleId rid(1, 1); + + // Unlock on non-existent lock should return false + EXPECT_FALSE(lm.unlock(&txn, rid)); +} + +/** + * @brief Verifies exclusive lock blocks shared lock acquisition + */ +TEST(LockManagerTests, ExclusiveBlocksShared) { + LockManager lm; + Transaction txn1(1); + Transaction txn2(2); + HeapTable::TupleId rid(1, 1); + + // Acquire exclusive + EXPECT_TRUE(lm.acquire_exclusive(&txn1, rid)); + + // Shared should block (using try with immediate timeout behavior) + std::atomic shared_acquired{false}; + std::thread t([&]() { + if (lm.acquire_shared(&txn2, rid)) { + shared_acquired = true; + } + }); + + // Give thread time to try acquiring + std::this_thread::sleep_for(TEST_SLEEP_MS); + EXPECT_FALSE(shared_acquired.load()); + + // Release exclusive + static_cast(lm.unlock(&txn1, rid)); + + t.join(); + EXPECT_TRUE(shared_acquired.load()); + static_cast(lm.unlock(&txn2, rid)); +} + +/** + * @brief Verifies shared lock blocks exclusive lock acquisition + */ +TEST(LockManagerTests, SharedBlocksExclusive) { + LockManager lm; + Transaction txn1(1); + Transaction txn2(2); + HeapTable::TupleId rid(1, 1); + + // Acquire shared + EXPECT_TRUE(lm.acquire_shared(&txn1, rid)); + + // Exclusive should block + std::atomic exclusive_acquired{false}; + std::thread t([&]() { + if (lm.acquire_exclusive(&txn2, rid)) { + exclusive_acquired = true; + } + }); + + // Give thread time to try acquiring + std::this_thread::sleep_for(TEST_SLEEP_MS); + EXPECT_FALSE(exclusive_acquired.load()); + + // Release shared + static_cast(lm.unlock(&txn1, rid)); + + t.join(); + EXPECT_TRUE(exclusive_acquired.load()); + static_cast(lm.unlock(&txn2, rid)); +} + +/** + * @brief Verifies unlock only releases the specific transaction's lock + */ +TEST(LockManagerTests, UnlockReleasesOnlyTxn) { + LockManager lm; + Transaction txn1(1); + Transaction txn2(2); + HeapTable::TupleId rid(1, 1); + + // Two transactions acquire shared + EXPECT_TRUE(lm.acquire_shared(&txn1, rid)); + EXPECT_TRUE(lm.acquire_shared(&txn2, rid)); + + // Unlock txn1 - should not affect txn2 + static_cast(lm.unlock(&txn1, rid)); + + // txn2 should still hold the lock - can still read + EXPECT_TRUE(lm.acquire_shared(&txn2, rid)); + + static_cast(lm.unlock(&txn2, rid)); +} + +/** + * @brief Verifies lock re-acquisition by same transaction is idempotent + */ +TEST(LockManagerTests, LockSameRIDTwice) { + LockManager lm; + Transaction txn(1); + HeapTable::TupleId rid(1, 1); + + // Lock manager allows re-acquisition by same transaction + EXPECT_TRUE(lm.acquire_shared(&txn, rid)); + EXPECT_TRUE(lm.acquire_shared(&txn, rid)); + + // Should only need one unlock + static_cast(lm.unlock(&txn, rid)); +} + +/** + * @brief Verifies three transactions can hold shared locks simultaneously + */ +TEST(LockManagerTests, MultipleSharedLocks) { + LockManager lm; + Transaction txn1(1); + Transaction txn2(2); + Transaction txn3(3); + HeapTable::TupleId rid(1, 1); + + EXPECT_TRUE(lm.acquire_shared(&txn1, rid)); + EXPECT_TRUE(lm.acquire_shared(&txn2, rid)); + EXPECT_TRUE(lm.acquire_shared(&txn3, rid)); + + static_cast(lm.unlock(&txn1, rid)); + static_cast(lm.unlock(&txn2, rid)); + static_cast(lm.unlock(&txn3, rid)); +} + +/** + * @brief Verifies transactions can hold locks on different RIDs independently + */ +TEST(LockManagerTests, LockDifferentRIDs) { + LockManager lm; + Transaction txn1(1); + Transaction txn2(2); + HeapTable::TupleId ridA(1, 1); + HeapTable::TupleId ridB(1, 2); + + // txn1 gets exclusive on ridA, txn2 gets exclusive on ridB - both should succeed + EXPECT_TRUE(lm.acquire_exclusive(&txn1, ridA)); + EXPECT_TRUE(lm.acquire_exclusive(&txn2, ridB)); + + static_cast(lm.unlock(&txn1, ridA)); + static_cast(lm.unlock(&txn2, ridB)); +} + +/** + * @brief Verifies exclusive lock followed by shared on different RID succeeds + */ +TEST(LockManagerTests, ExclusiveThenShared) { + LockManager lm; + Transaction txn(1); + HeapTable::TupleId ridA(1, 1); + HeapTable::TupleId ridB(1, 2); + + EXPECT_TRUE(lm.acquire_exclusive(&txn, ridA)); + EXPECT_TRUE(lm.acquire_shared(&txn, ridB)); + + static_cast(lm.unlock(&txn, ridA)); + static_cast(lm.unlock(&txn, ridB)); +} + } // namespace