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