@@ -126,4 +126,173 @@ TEST(LockManagerTests, Deadlock) {
126126 static_cast <void >(lm.unlock (&txn2, ridA));
127127}
128128
129+ // ============= New Tests =============
130+
131+ /* *
132+ * @brief Verifies unlocking a non-existent lock returns false
133+ */
134+ TEST (LockManagerTests, UnlockNonExistent) {
135+ LockManager lm;
136+ Transaction txn (1 );
137+ HeapTable::TupleId rid (1 , 1 );
138+
139+ // Unlock on non-existent lock should return false
140+ EXPECT_FALSE (lm.unlock (&txn, rid));
141+ }
142+
143+ /* *
144+ * @brief Verifies exclusive lock blocks shared lock acquisition
145+ */
146+ TEST (LockManagerTests, ExclusiveBlocksShared) {
147+ LockManager lm;
148+ Transaction txn1 (1 );
149+ Transaction txn2 (2 );
150+ HeapTable::TupleId rid (1 , 1 );
151+
152+ // Acquire exclusive
153+ EXPECT_TRUE (lm.acquire_exclusive (&txn1, rid));
154+
155+ // Shared should block (using try with immediate timeout behavior)
156+ std::atomic<bool > shared_acquired{false };
157+ std::thread t ([&]() {
158+ if (lm.acquire_shared (&txn2, rid)) {
159+ shared_acquired = true ;
160+ }
161+ });
162+
163+ // Give thread time to try acquiring
164+ std::this_thread::sleep_for (TEST_SLEEP_MS);
165+ EXPECT_FALSE (shared_acquired.load ());
166+
167+ // Release exclusive
168+ static_cast <void >(lm.unlock (&txn1, rid));
169+
170+ t.join ();
171+ EXPECT_TRUE (shared_acquired.load ());
172+ static_cast <void >(lm.unlock (&txn2, rid));
173+ }
174+
175+ /* *
176+ * @brief Verifies shared lock blocks exclusive lock acquisition
177+ */
178+ TEST (LockManagerTests, SharedBlocksExclusive) {
179+ LockManager lm;
180+ Transaction txn1 (1 );
181+ Transaction txn2 (2 );
182+ HeapTable::TupleId rid (1 , 1 );
183+
184+ // Acquire shared
185+ EXPECT_TRUE (lm.acquire_shared (&txn1, rid));
186+
187+ // Exclusive should block
188+ std::atomic<bool > exclusive_acquired{false };
189+ std::thread t ([&]() {
190+ if (lm.acquire_exclusive (&txn2, rid)) {
191+ exclusive_acquired = true ;
192+ }
193+ });
194+
195+ // Give thread time to try acquiring
196+ std::this_thread::sleep_for (TEST_SLEEP_MS);
197+ EXPECT_FALSE (exclusive_acquired.load ());
198+
199+ // Release shared
200+ static_cast <void >(lm.unlock (&txn1, rid));
201+
202+ t.join ();
203+ EXPECT_TRUE (exclusive_acquired.load ());
204+ static_cast <void >(lm.unlock (&txn2, rid));
205+ }
206+
207+ /* *
208+ * @brief Verifies unlock only releases the specific transaction's lock
209+ */
210+ TEST (LockManagerTests, UnlockReleasesOnlyTxn) {
211+ LockManager lm;
212+ Transaction txn1 (1 );
213+ Transaction txn2 (2 );
214+ HeapTable::TupleId rid (1 , 1 );
215+
216+ // Two transactions acquire shared
217+ EXPECT_TRUE (lm.acquire_shared (&txn1, rid));
218+ EXPECT_TRUE (lm.acquire_shared (&txn2, rid));
219+
220+ // Unlock txn1 - should not affect txn2
221+ static_cast <void >(lm.unlock (&txn1, rid));
222+
223+ // txn2 should still hold the lock - can still read
224+ EXPECT_TRUE (lm.acquire_shared (&txn2, rid));
225+
226+ static_cast <void >(lm.unlock (&txn2, rid));
227+ }
228+
229+ /* *
230+ * @brief Verifies lock re-acquisition by same transaction is idempotent
231+ */
232+ TEST (LockManagerTests, LockSameRIDTwice) {
233+ LockManager lm;
234+ Transaction txn (1 );
235+ HeapTable::TupleId rid (1 , 1 );
236+
237+ // Lock manager allows re-acquisition by same transaction
238+ EXPECT_TRUE (lm.acquire_shared (&txn, rid));
239+ EXPECT_TRUE (lm.acquire_shared (&txn, rid));
240+
241+ // Should only need one unlock
242+ static_cast <void >(lm.unlock (&txn, rid));
243+ }
244+
245+ /* *
246+ * @brief Verifies three transactions can hold shared locks simultaneously
247+ */
248+ TEST (LockManagerTests, MultipleSharedLocks) {
249+ LockManager lm;
250+ Transaction txn1 (1 );
251+ Transaction txn2 (2 );
252+ Transaction txn3 (3 );
253+ HeapTable::TupleId rid (1 , 1 );
254+
255+ EXPECT_TRUE (lm.acquire_shared (&txn1, rid));
256+ EXPECT_TRUE (lm.acquire_shared (&txn2, rid));
257+ EXPECT_TRUE (lm.acquire_shared (&txn3, rid));
258+
259+ static_cast <void >(lm.unlock (&txn1, rid));
260+ static_cast <void >(lm.unlock (&txn2, rid));
261+ static_cast <void >(lm.unlock (&txn3, rid));
262+ }
263+
264+ /* *
265+ * @brief Verifies transactions can hold locks on different RIDs independently
266+ */
267+ TEST (LockManagerTests, LockDifferentRIDs) {
268+ LockManager lm;
269+ Transaction txn1 (1 );
270+ Transaction txn2 (2 );
271+ HeapTable::TupleId ridA (1 , 1 );
272+ HeapTable::TupleId ridB (1 , 2 );
273+
274+ // txn1 gets exclusive on ridA, txn2 gets exclusive on ridB - both should succeed
275+ EXPECT_TRUE (lm.acquire_exclusive (&txn1, ridA));
276+ EXPECT_TRUE (lm.acquire_exclusive (&txn2, ridB));
277+
278+ static_cast <void >(lm.unlock (&txn1, ridA));
279+ static_cast <void >(lm.unlock (&txn2, ridB));
280+ }
281+
282+ /* *
283+ * @brief Verifies exclusive lock followed by shared on different RID succeeds
284+ */
285+ TEST (LockManagerTests, ExclusiveThenShared) {
286+ LockManager lm;
287+ Transaction txn (1 );
288+ HeapTable::TupleId ridA (1 , 1 );
289+ HeapTable::TupleId ridB (1 , 2 );
290+
291+ EXPECT_TRUE (lm.acquire_exclusive (&txn, ridA));
292+ EXPECT_TRUE (lm.acquire_shared (&txn, ridB));
293+
294+ static_cast <void >(lm.unlock (&txn, ridA));
295+ static_cast <void >(lm.unlock (&txn, ridB));
296+ }
297+
129298} // namespace
0 commit comments