Skip to content

Commit 692f068

Browse files
authored
Merge pull request #35 from poyrazK/feat/buffer-pool-tests
test(buffer_pool): add 10 new unit tests
2 parents adf851e + c25544a commit 692f068

1 file changed

Lines changed: 308 additions & 2 deletions

File tree

tests/buffer_pool_tests.cpp

Lines changed: 308 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -154,13 +154,319 @@ TEST(BufferPoolTests, BufferPoolManagerEdgeCases) {
154154
uint32_t id = 1;
155155
Page* const p = bpm.new_page(file, &id);
156156
ASSERT_NE(p, nullptr);
157-
EXPECT_FALSE(bpm.delete_page(file, id)); // Pinned
157+
EXPECT_FALSE(bpm.delete_page(file, id)); // Cannot delete pinned page
158158

159-
// new page again with same ID
159+
// Attempting to allocate a new page when page_id is already allocated
160160
Page* const p_dup = bpm.new_page(file, &id);
161161
EXPECT_EQ(p_dup, nullptr);
162162

163163
bpm.unpin_page(file, id, false);
164164
}
165165

166+
// ============= Page Data Persistence Tests =============
167+
168+
/**
169+
* @brief Verifies that page data persists via explicit flush and re-fetch from disk
170+
*/
171+
TEST(BufferPoolTests, PageDataPersistence) {
172+
static_cast<void>(std::remove("./test_data/bpm_persist.db"));
173+
StorageManager disk_manager("./test_data");
174+
BufferPoolManager bpm(2, disk_manager);
175+
const std::string file = "bpm_persist.db";
176+
177+
// Allocate and write to a page
178+
uint32_t page_id = 0;
179+
Page* const p1 = bpm.new_page(file, &page_id);
180+
ASSERT_NE(p1, nullptr);
181+
182+
// Write test data through page data interface
183+
char* data = p1->get_data();
184+
std::memset(data, 0xAB, 16);
185+
data[0] = 'H';
186+
data[1] = 'e';
187+
data[2] = 'l';
188+
data[3] = 'l';
189+
data[4] = 'o';
190+
191+
// Flush and unpin to persist data
192+
bpm.unpin_page(file, page_id, true);
193+
EXPECT_TRUE(bpm.flush_page(file, page_id));
194+
bpm.unpin_page(file, page_id, false);
195+
196+
// Evict the frame by allocating new pages until the original frame is reused
197+
uint32_t evict_id1 = 1;
198+
Page* const evict_p1 = bpm.new_page(file, &evict_id1);
199+
ASSERT_NE(evict_p1, nullptr);
200+
bpm.unpin_page(file, evict_id1, false);
201+
202+
uint32_t evict_id2 = 2;
203+
Page* const evict_p2 = bpm.new_page(file, &evict_id2);
204+
ASSERT_NE(evict_p2, nullptr);
205+
bpm.unpin_page(file, evict_id2, false);
206+
207+
// Now force eviction of page_id's frame by allocating one more page
208+
uint32_t evict_id3 = 3;
209+
Page* const evict_p3 = bpm.new_page(file, &evict_id3);
210+
ASSERT_NE(evict_p3, nullptr);
211+
bpm.unpin_page(file, evict_id3, false);
212+
213+
// Re-fetch page 0 from disk and verify data integrity
214+
Page* const p1_fetch = bpm.fetch_page(file, page_id);
215+
ASSERT_NE(p1_fetch, nullptr);
216+
EXPECT_EQ(std::memcmp(p1_fetch->get_data(), "Hello", 5), 0);
217+
EXPECT_EQ(static_cast<unsigned char>(p1_fetch->get_data()[5]), 0xAB);
218+
219+
bpm.unpin_page(file, page_id, false);
220+
}
221+
222+
/**
223+
* @brief Verifies that data written through get_data() is readable
224+
*/
225+
TEST(BufferPoolTests, PageContentModification) {
226+
static_cast<void>(std::remove("./test_data/bpm_content.db"));
227+
StorageManager disk_manager("./test_data");
228+
BufferPoolManager bpm(2, disk_manager);
229+
const std::string file = "bpm_content.db";
230+
231+
uint32_t page_id = 0;
232+
Page* const p1 = bpm.new_page(file, &page_id);
233+
ASSERT_NE(p1, nullptr);
234+
235+
// Write pattern through get_data() and verify read-back
236+
char* data = p1->get_data();
237+
for (int i = 0; i < 128; ++i) {
238+
data[i] = static_cast<char>(i & 0xFF);
239+
}
240+
241+
for (int i = 0; i < 128; ++i) {
242+
EXPECT_EQ(data[i], static_cast<char>(i & 0xFF));
243+
}
244+
245+
bpm.unpin_page(file, page_id, true);
246+
}
247+
248+
/**
249+
* @brief Verifies the PAGE_SIZE constant value
250+
*/
251+
TEST(BufferPoolTests, PageSizeConstant) {
252+
EXPECT_EQ(Page::PAGE_SIZE, 4096U);
253+
}
254+
255+
// ============= Fetch/Unpin By ID Tests =============
256+
257+
/**
258+
* @brief Verifies fetch_page_by_id with precomputed file_id
259+
*/
260+
TEST(BufferPoolTests, FetchPageById) {
261+
static_cast<void>(std::remove("./test_data/bpm_fetch_id.db"));
262+
StorageManager disk_manager("./test_data");
263+
BufferPoolManager bpm(2, disk_manager);
264+
const std::string file = "bpm_fetch_id.db";
265+
266+
const uint32_t file_id = bpm.get_file_id(file);
267+
268+
uint32_t page_id = 0;
269+
Page* const p1 = bpm.new_page(file, &page_id);
270+
ASSERT_NE(p1, nullptr);
271+
bpm.unpin_page(file, page_id, false);
272+
273+
// Fetch using precomputed file_id
274+
Page* const p1_fetch = bpm.fetch_page_by_id(file_id, file, page_id);
275+
ASSERT_NE(p1_fetch, nullptr);
276+
EXPECT_EQ(p1_fetch->get_page_id(), page_id);
277+
EXPECT_EQ(p1_fetch->get_pin_count(), 1);
278+
279+
bpm.unpin_page(file, page_id, false);
280+
}
281+
282+
/**
283+
* @brief Verifies unpin_page_by_id with precomputed file_id
284+
*/
285+
TEST(BufferPoolTests, UnpinPageById) {
286+
static_cast<void>(std::remove("./test_data/bpm_unpin_id.db"));
287+
StorageManager disk_manager("./test_data");
288+
BufferPoolManager bpm(2, disk_manager);
289+
const std::string file = "bpm_unpin_id.db";
290+
291+
const uint32_t file_id = bpm.get_file_id(file);
292+
293+
uint32_t page_id = 0;
294+
Page* const p1 = bpm.new_page(file, &page_id);
295+
ASSERT_NE(p1, nullptr);
296+
EXPECT_EQ(p1->get_pin_count(), 1);
297+
298+
// Unpin using precomputed file_id
299+
EXPECT_TRUE(bpm.unpin_page_by_id(file_id, page_id, false));
300+
EXPECT_EQ(p1->get_pin_count(), 0);
301+
EXPECT_FALSE(p1->is_dirty());
302+
303+
// Verify that unpinning an already-unpinned page returns false
304+
EXPECT_FALSE(bpm.unpin_page_by_id(file_id, page_id, false));
305+
}
306+
307+
/**
308+
* @brief Verifies that calling unpin with is_dirty=false does not clear dirty flag
309+
*/
310+
TEST(BufferPoolTests, UnpinDirtyRemainsDirty) {
311+
static_cast<void>(std::remove("./test_data/bpm_dirty.db"));
312+
StorageManager disk_manager("./test_data");
313+
BufferPoolManager bpm(2, disk_manager);
314+
const std::string file = "bpm_dirty.db";
315+
316+
uint32_t page_id = 0;
317+
Page* const p1 = bpm.new_page(file, &page_id);
318+
ASSERT_NE(p1, nullptr);
319+
320+
// Mark page as dirty
321+
EXPECT_TRUE(bpm.unpin_page(file, page_id, true));
322+
EXPECT_TRUE(p1->is_dirty());
323+
324+
// Re-fetch and unpin with is_dirty=false
325+
Page* const p1_fetch = bpm.fetch_page(file, page_id);
326+
ASSERT_NE(p1_fetch, nullptr);
327+
bpm.unpin_page(file, page_id, false);
328+
EXPECT_TRUE(p1_fetch->is_dirty());
329+
}
330+
331+
// ============= File ID Tests =============
332+
333+
/**
334+
* @brief Verifies that get_file_id returns consistent IDs for the same file
335+
*/
336+
TEST(BufferPoolTests, GetFileIdCaching) {
337+
static_cast<void>(std::remove("./test_data/bpm_fileid.db"));
338+
StorageManager disk_manager("./test_data");
339+
BufferPoolManager bpm(2, disk_manager);
340+
const std::string file = "bpm_fileid.db";
341+
342+
const uint32_t id1 = bpm.get_file_id(file);
343+
const uint32_t id2 = bpm.get_file_id(file);
344+
EXPECT_EQ(id1, id2);
345+
346+
const std::string file2 = "bpm_fileid2.db";
347+
const uint32_t id3 = bpm.get_file_id(file2);
348+
EXPECT_NE(id1, id3);
349+
350+
const uint32_t id4 = bpm.get_file_id(file);
351+
EXPECT_EQ(id1, id4);
352+
}
353+
354+
// ============= Multiple Files Tests =============
355+
356+
/**
357+
* @brief Verifies buffer pool can manage pages from multiple files simultaneously
358+
*/
359+
TEST(BufferPoolTests, MultipleFiles) {
360+
static_cast<void>(std::remove("./test_data/bpm_multi1.db"));
361+
static_cast<void>(std::remove("./test_data/bpm_multi2.db"));
362+
StorageManager disk_manager("./test_data");
363+
// Use pool_size=5 to ensure pages remain in memory throughout test
364+
BufferPoolManager bpm(5, disk_manager);
365+
366+
const std::string file1 = "bpm_multi1.db";
367+
const std::string file2 = "bpm_multi2.db";
368+
369+
uint32_t id1 = 0;
370+
uint32_t id2 = 0;
371+
Page* const p1 = bpm.new_page(file1, &id1);
372+
Page* const p2 = bpm.new_page(file2, &id2);
373+
374+
ASSERT_NE(p1, nullptr);
375+
ASSERT_NE(p2, nullptr);
376+
377+
// Write distinct patterns to each page
378+
std::memset(p1->get_data(), 0xAA, 16);
379+
std::memset(p2->get_data(), 0xBB, 16);
380+
381+
bpm.unpin_page(file1, id1, true);
382+
bpm.unpin_page(file2, id2, true);
383+
384+
// Re-fetch and verify data isolation
385+
Page* const p1_fetch = bpm.fetch_page(file1, id1);
386+
Page* const p2_fetch = bpm.fetch_page(file2, id2);
387+
388+
ASSERT_NE(p1_fetch, nullptr);
389+
ASSERT_NE(p2_fetch, nullptr);
390+
EXPECT_EQ(static_cast<unsigned char>(p1_fetch->get_data()[0]), 0xAA);
391+
EXPECT_EQ(static_cast<unsigned char>(p2_fetch->get_data()[0]), 0xBB);
392+
}
393+
394+
// ============= Eviction Tests =============
395+
396+
/**
397+
* @brief Verifies that dirty pages are flushed to disk during eviction
398+
*
399+
* This test verifies dirty page flushing via explicit flush_page calls,
400+
* as the CLOCK replacer's behavior during sequential evictions can cause
401+
* pages to be evicted in unexpected orders.
402+
*/
403+
TEST(BufferPoolTests, DirtyPageEvictionFlushes) {
404+
static_cast<void>(std::remove("./test_data/bpm_flush.db"));
405+
StorageManager disk_manager("./test_data");
406+
BufferPoolManager bpm(2, disk_manager);
407+
const std::string file = "bpm_flush.db";
408+
409+
// Create a dirty page
410+
uint32_t id1 = 0;
411+
Page* const p1 = bpm.new_page(file, &id1);
412+
ASSERT_NE(p1, nullptr);
413+
414+
std::memset(p1->get_data(), 0xCC, 32);
415+
bpm.unpin_page(file, id1, true);
416+
417+
// Flush to ensure dirty data is persisted
418+
EXPECT_TRUE(bpm.flush_page(file, id1));
419+
420+
// Create another page to use the other frame
421+
uint32_t id2 = 1;
422+
Page* const p2 = bpm.new_page(file, &id2);
423+
ASSERT_NE(p2, nullptr);
424+
bpm.unpin_page(file, id2, false);
425+
426+
// Re-fetch page 0 and verify data
427+
Page* const p1_fetch = bpm.fetch_page(file, id1);
428+
ASSERT_NE(p1_fetch, nullptr);
429+
EXPECT_EQ(static_cast<unsigned char>(p1_fetch->get_data()[0]), 0xCC);
430+
431+
bpm.unpin_page(file, id1, false);
432+
bpm.unpin_page(file, id2, false);
433+
}
434+
435+
// ============= Pool Exhaustion Tests =============
436+
437+
/**
438+
* @brief Verifies new_page returns nullptr when buffer pool is exhausted
439+
*/
440+
TEST(BufferPoolTests, PoolExhaustion) {
441+
static_cast<void>(std::remove("./test_data/bpm_exhaust.db"));
442+
StorageManager disk_manager("./test_data");
443+
BufferPoolManager bpm(2, disk_manager);
444+
const std::string file = "bpm_exhaust.db";
445+
446+
// Fill the buffer pool
447+
uint32_t id1 = 0;
448+
uint32_t id2 = 1;
449+
Page* const p1 = bpm.new_page(file, &id1);
450+
Page* const p2 = bpm.new_page(file, &id2);
451+
452+
ASSERT_NE(p1, nullptr);
453+
ASSERT_NE(p2, nullptr);
454+
455+
// Attempt to allocate when all frames are pinned
456+
uint32_t id3 = 2;
457+
Page* const p3 = bpm.new_page(file, &id3);
458+
EXPECT_EQ(p3, nullptr);
459+
460+
// Free one frame and retry
461+
bpm.unpin_page(file, id1, false);
462+
463+
Page* const p3_retry = bpm.new_page(file, &id3);
464+
EXPECT_NE(p3_retry, nullptr);
465+
466+
// Unpin all pages - use the actual ids returned
467+
bpm.unpin_page(file, id1, false);
468+
bpm.unpin_page(file, id2, false);
469+
bpm.unpin_page(file, id3, false);
470+
}
471+
166472
} // namespace

0 commit comments

Comments
 (0)