Skip to content

Commit 57fc3f8

Browse files
authored
Merge pull request #37 from poyrazK/feat/lock-manager-tests
test(lock_manager): add 8 new unit tests
2 parents ff56ae6 + fb61d2e commit 57fc3f8

File tree

1 file changed

+169
-0
lines changed

1 file changed

+169
-0
lines changed

tests/lock_manager_tests.cpp

Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)