test(page): cover BufferPoolTest and newManager
This commit is contained in:
1 parent
5543856fad
commit
f92883fe98
1 file changed
+154
@@ -0,0 +1,154 @@
|
|||||||
|
package kestrel.store.page
|
||||||
|
|
||||||
|
import java.nio.file.Files
|
||||||
|
import kotlin.test.Test
|
||||||
|
import kotlin.test.assertEquals
|
||||||
|
import kotlin.test.assertFailsWith
|
||||||
|
import kotlin.test.assertTrue
|
||||||
|
|
||||||
|
class BufferPoolTest {
|
||||||
|
private fun newManager() = DiskManager(Files.createTempFile("kestrel-pool", ".db").toFile().also { it.deleteOnExit() })
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun fetchingTheSamePageTwiceReturnsTheSameCachedInstance() {
|
||||||
|
val pool = BufferPool(newManager(), capacity = 4)
|
||||||
|
val page = pool.newPage()
|
||||||
|
page.data[0] = 1
|
||||||
|
pool.unpinPage(page.id, true)
|
||||||
|
val first = pool.fetchPage(page.id)
|
||||||
|
val second = pool.fetchPage(page.id)
|
||||||
|
assertTrue(first === second)
|
||||||
|
pool.unpinPage(page.id, false)
|
||||||
|
pool.unpinPage(page.id, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun dirtyPageIsPersistedOnEviction() {
|
||||||
|
val manager = newManager()
|
||||||
|
val pool = BufferPool(manager, capacity = 1)
|
||||||
|
val page = pool.newPage()
|
||||||
|
page.data[0] = 99
|
||||||
|
pool.unpinPage(page.id, true)
|
||||||
|
|
||||||
|
// Force eviction of the only frame by fetching a second page.
|
||||||
|
val other = pool.newPage()
|
||||||
|
pool.unpinPage(other.id, false)
|
||||||
|
|
||||||
|
val reloaded = manager.readPage(page.id)
|
||||||
|
assertEquals(99, reloaded.data[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun cleanPageIsNotRewrittenOnEviction() {
|
||||||
|
val manager = newManager()
|
||||||
|
val pool = BufferPool(manager, capacity = 1)
|
||||||
|
val page = pool.newPage()
|
||||||
|
pool.unpinPage(page.id, true)
|
||||||
|
pool.flushAll()
|
||||||
|
|
||||||
|
val fetched = pool.fetchPage(page.id)
|
||||||
|
fetched.data[5] = 55 // mutate without marking dirty
|
||||||
|
pool.unpinPage(page.id, markDirty = false)
|
||||||
|
|
||||||
|
pool.newPage() // evicts the clean page without writeback
|
||||||
|
val reloaded = manager.readPage(page.id)
|
||||||
|
assertEquals(0, reloaded.data[5])
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun pinnedPagesAreNeverEvicted() {
|
||||||
|
val pool = BufferPool(newManager(), capacity = 1)
|
||||||
|
val pinned = pool.newPage() // pinCount == 1, never unpinned
|
||||||
|
assertFailsWith<BufferPoolExhaustedException> { pool.newPage() }
|
||||||
|
pool.unpinPage(pinned.id, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun clockGivesRecentlyTouchedPagesASecondChanceOverStalePages() {
|
||||||
|
val pool = BufferPool(newManager(), capacity = 3)
|
||||||
|
val a = pool.newPage(); pool.unpinPage(a.id, true)
|
||||||
|
val b = pool.newPage(); pool.unpinPage(b.id, true)
|
||||||
|
val c = pool.newPage(); pool.unpinPage(c.id, true)
|
||||||
|
|
||||||
|
// One full clock sweep: evicts `a` (first frame revisited) and clears b/c's reference bits.
|
||||||
|
val d = pool.newPage(); pool.unpinPage(d.id, true)
|
||||||
|
|
||||||
|
// Refresh `c`'s reference bit by touching it again; `b` is left stale.
|
||||||
|
val cTouch = pool.fetchPage(c.id)
|
||||||
|
pool.unpinPage(c.id, false)
|
||||||
|
|
||||||
|
// Next eviction must take the stale page (`b`), sparing the freshly touched `c`.
|
||||||
|
val e = pool.newPage(); pool.unpinPage(e.id, true)
|
||||||
|
|
||||||
|
assertTrue(pool.fetchPage(c.id) === cTouch, "c should still be cached, not re-read from disk")
|
||||||
|
pool.unpinPage(c.id, false)
|
||||||
|
assertTrue(pool.fetchPage(b.id) !== b, "b should have been evicted and re-read as a fresh instance")
|
||||||
|
pool.unpinPage(b.id, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun deletingAPageFreesItInTheDiskManager() {
|
||||||
|
val manager = newManager()
|
||||||
|
val pool = BufferPool(manager, capacity = 4)
|
||||||
|
val page = pool.newPage()
|
||||||
|
val id = page.id
|
||||||
|
pool.unpinPage(id, true)
|
||||||
|
pool.deletePage(id)
|
||||||
|
val recycled = pool.newPage()
|
||||||
|
assertEquals(id, recycled.id)
|
||||||
|
pool.unpinPage(recycled.id, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun deletingAPinnedPageIsRejected() {
|
||||||
|
val pool = BufferPool(newManager(), capacity = 4)
|
||||||
|
val page = pool.newPage()
|
||||||
|
assertFailsWith<IllegalStateException> { pool.deletePage(page.id) }
|
||||||
|
pool.unpinPage(page.id, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun flushPageWritesBackImmediatelyWithoutEviction() {
|
||||||
|
val manager = newManager()
|
||||||
|
val pool = BufferPool(manager, capacity = 4)
|
||||||
|
val page = pool.newPage()
|
||||||
|
page.data[0] = 77
|
||||||
|
pool.unpinPage(page.id, true)
|
||||||
|
pool.flushPage(page.id)
|
||||||
|
assertEquals(77, manager.readPage(page.id).data[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun invalidateDropsADirtyPageWithoutWritingItBack() {
|
||||||
|
val manager = newManager()
|
||||||
|
val pool = BufferPool(manager, capacity = 4)
|
||||||
|
val page = pool.newPage()
|
||||||
|
pool.unpinPage(page.id, true)
|
||||||
|
pool.flushAll() // establishes a known, all-zero baseline on disk
|
||||||
|
|
||||||
|
val fetched = pool.fetchPage(page.id)
|
||||||
|
fetched.data[0] = 42
|
||||||
|
pool.unpinPage(page.id, true)
|
||||||
|
|
||||||
|
pool.invalidate(page.id)
|
||||||
|
|
||||||
|
assertEquals(0, manager.readPage(page.id).data[0], "invalidate must not flush the dirty page it drops")
|
||||||
|
val refetched = pool.fetchPage(page.id)
|
||||||
|
assertTrue(refetched !== fetched, "a page dropped by invalidate must be re-read fresh from disk")
|
||||||
|
pool.unpinPage(page.id, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun invalidatingAPinnedPageIsRejected() {
|
||||||
|
val pool = BufferPool(newManager(), capacity = 4)
|
||||||
|
val page = pool.newPage()
|
||||||
|
assertFailsWith<IllegalStateException> { pool.invalidate(page.id) }
|
||||||
|
pool.unpinPage(page.id, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun invalidatingAnUncachedPageIsANoOp() {
|
||||||
|
val pool = BufferPool(newManager(), capacity = 4)
|
||||||
|
pool.invalidate(999L)
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in new issue
Block a user