test(heap): cover HeapFileTest and tempFile
This commit is contained in:
1 parent
5570b006ab
commit
577fcdcc9a
1 file changed
+125
@@ -0,0 +1,125 @@
|
||||
package kestrel.store.heap
|
||||
|
||||
import kestrel.store.page.BufferPool
|
||||
import kestrel.store.page.DiskManager
|
||||
import java.io.File
|
||||
import java.nio.file.Files
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertFailsWith
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
class HeapFileTest {
|
||||
private fun tempFile(): File = Files.createTempFile("kestrel-heap", ".db").toFile().also { it.deleteOnExit() }
|
||||
|
||||
private fun newPool(file: File = tempFile(), capacity: Int = 64): BufferPool = BufferPool(DiskManager(file), capacity)
|
||||
|
||||
@Test
|
||||
fun insertAndGetRoundTrips() {
|
||||
val heap = HeapFile.create(newPool())
|
||||
val rid = heap.insert("hello world".toByteArray())
|
||||
assertEquals("hello world", String(heap.get(rid)))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun insertManySmallRecordsAllReadableIndividually() {
|
||||
val heap = HeapFile.create(newPool())
|
||||
val ids = (0 until 50).map { heap.insert("row-$it".toByteArray()) }
|
||||
for ((index, rid) in ids.withIndex()) {
|
||||
assertEquals("row-$index", String(heap.get(rid)))
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun insertSpillsToANewPageWhenCurrentPageIsFull() {
|
||||
val heap = HeapFile.create(newPool())
|
||||
val ids = (0 until 40).map { heap.insert(ByteArray(200) { it.toByte() }) }
|
||||
val distinctPages = ids.map { it.pageId }.toSet()
|
||||
assertTrue(distinctPages.size > 1, "expected records to spread across multiple pages")
|
||||
for (rid in ids) assertEquals(200, heap.get(rid).size)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun scanReturnsEveryLiveRecordAcrossAllPages() {
|
||||
val heap = HeapFile.create(newPool())
|
||||
val inserted = (0 until 30).map { heap.insert(ByteArray(300) { i -> i.toByte() }) }.toSet()
|
||||
val scanned = heap.scan().map { it.first }.toSet()
|
||||
assertEquals(inserted, scanned)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun deleteRemovesRecordFromScanAndGetFails() {
|
||||
val heap = HeapFile.create(newPool())
|
||||
val keep = heap.insert("keep".toByteArray())
|
||||
val drop = heap.insert("drop".toByteArray())
|
||||
heap.delete(drop)
|
||||
assertEquals(setOf(keep), heap.scan().map { it.first }.toSet())
|
||||
assertFailsWith<HeapPageException> { heap.get(drop) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun updateThatFitsInPlaceKeepsSameRecordId() {
|
||||
val heap = HeapFile.create(newPool())
|
||||
val rid = heap.insert("original text".toByteArray())
|
||||
val updated = heap.update(rid, "short".toByteArray())
|
||||
assertEquals(rid, updated)
|
||||
assertEquals("short", String(heap.get(rid)))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun updateThatOutgrowsSlotReturnsNewRecordIdAndDropsOld() {
|
||||
val heap = HeapFile.create(newPool())
|
||||
val rid = heap.insert("tiny".toByteArray())
|
||||
val updated = heap.update(rid, "a much longer replacement value".toByteArray())
|
||||
assertTrue(updated != rid)
|
||||
assertEquals("a much longer replacement value", String(heap.get(updated)))
|
||||
assertFailsWith<HeapPageException> { heap.get(rid) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun deletedSpaceIsReusedByLaterInsertsBeforeGrowingTheFile() {
|
||||
val heap = HeapFile.create(newPool())
|
||||
val filler = (0 until 15).map { heap.insert(ByteArray(250)) }
|
||||
val pagesAfterFill = filler.map { it.pageId }.toSet()
|
||||
for (rid in filler.take(10)) heap.delete(rid)
|
||||
val reused = heap.insert(ByteArray(250))
|
||||
assertTrue(reused.pageId in pagesAfterFill, "expected reclaimed space to be reused instead of allocating a new page")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun insertRejectsRecordLargerThanAnEmptyPage() {
|
||||
val heap = HeapFile.create(newPool())
|
||||
assertFailsWith<HeapFileException> { heap.insert(ByteArray(HeapPage.MAX_RECORD_SIZE + 1)) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun allPageIdsOnAFreshHeapIsJustTheHeader() {
|
||||
val heap = HeapFile.create(newPool())
|
||||
assertEquals(listOf(heap.headerPageId), heap.allPageIds())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun allPageIdsCoversTheHeaderAndEveryDataPage() {
|
||||
val heap = HeapFile.create(newPool())
|
||||
val ids = (0 until 40).map { heap.insert(ByteArray(200) { it.toByte() }) }
|
||||
val dataPages = ids.map { it.pageId }.toSet()
|
||||
|
||||
val allPageIds = heap.allPageIds()
|
||||
assertEquals(allPageIds.size, allPageIds.toSet().size, "expected no page id to be visited twice")
|
||||
assertTrue(allPageIds.contains(heap.headerPageId))
|
||||
assertEquals(dataPages + heap.headerPageId, allPageIds.toSet())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun reopeningWithSameHeaderPageSeesPersistedData() {
|
||||
val file = tempFile()
|
||||
val diskManager = DiskManager(file)
|
||||
val pool = BufferPool(diskManager, 64)
|
||||
val heap = HeapFile.create(pool)
|
||||
val rid = heap.insert("durable".toByteArray())
|
||||
pool.flushAll()
|
||||
|
||||
val reopened = HeapFile.open(pool, heap.headerPageId)
|
||||
assertEquals("durable", String(reopened.get(rid)))
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user