diff --git a/src/prioqTest.c b/src/prioqTest.c new file mode 100644 index 0000000..e427aad --- /dev/null +++ b/src/prioqTest.c @@ -0,0 +1,182 @@ +#include "ktest.h" +#include "prioq.h" +#include "heap.h" + +/* Fisher-Yates over a small LCG so insertion order is scrambled but + reproducible across runs (same generator as rbtTest.c's shuffle). */ +static void shuffle(uint32* arr, int32 n, uint32 seed) +{ + uint32 state = seed; + for (int32 i = n - 1; i > 0; i--) { + state = state * 1664525u + 1013904223u; + int32 j = (int32)(state % (uint32)(i + 1)); + uint32 tmp = arr[i]; + arr[i] = arr[j]; + arr[j] = tmp; + } +} + +KTEST(prioqEmptyQueue) +{ + PrioQueue q; + prioqInit(&q); + + KT_TRUE(t, prioqIsEmpty(&q)); + KT_EQ(t, prioqCount(&q), 0); + KT_NULL(t, prioqPeekMin(&q)); + KT_NULL(t, prioqPopMin(&q)); +} + +KTEST(prioqOrdersByVruntimeAscending) +{ + PrioQueue q; + prioqInit(&q); + + PrioQNode nodes[5]; + uint64 vruntimes[] = { 300, 100, 500, 200, 400 }; + for (uint32 i = 0; i < 5; i++) + prioqInsert(&q, &nodes[i], vruntimes[i], i); + + KT_EQ(t, prioqCount(&q), 5); + + uint64 expected[] = { 100, 200, 300, 400, 500 }; + for (uint32 i = 0; i < 5; i++) { + PrioQNode* min = prioqPopMin(&q); + KT_NOTNULL(t, min); + if (min) + KT_EQU(t, min->vruntime, expected[i]); + } + KT_TRUE(t, prioqIsEmpty(&q)); +} + +KTEST(prioqPeekDoesNotRemove) +{ + PrioQueue q; + prioqInit(&q); + + PrioQNode a, b; + prioqInsert(&q, &a, 50, 1); + prioqInsert(&q, &b, 10, 2); + + PrioQNode* peeked = prioqPeekMin(&q); + KT_NOTNULL(t, peeked); + if (peeked) + KT_EQ(t, peeked->id, 2); + KT_EQ(t, prioqCount(&q), 2); + + PrioQNode* popped = prioqPopMin(&q); + KT_EQP(t, popped, peeked); + KT_EQ(t, prioqCount(&q), 1); +} + +KTEST(prioqTiesBreakByIdAscending) +{ + PrioQueue q; + prioqInit(&q); + + PrioQNode nodes[4]; + uint32 ids[] = { 7, 3, 9, 1 }; + for (uint32 i = 0; i < 4; i++) + prioqInsert(&q, &nodes[i], 1000, ids[i]); /* identical vruntime */ + + uint32 expected[] = { 1, 3, 7, 9 }; + for (uint32 i = 0; i < 4; i++) { + PrioQNode* min = prioqPopMin(&q); + KT_NOTNULL(t, min); + if (min) + KT_EQ(t, min->id, expected[i]); + } +} + +KTEST(prioqRemoveArbitraryNodePreservesOrder) +{ + PrioQueue q; + prioqInit(&q); + + PrioQNode nodes[4]; + uint64 vruntimes[] = { 10, 20, 30, 40 }; + for (uint32 i = 0; i < 4; i++) + prioqInsert(&q, &nodes[i], vruntimes[i], i); + + prioqRemove(&q, &nodes[1]); /* drop vruntime=20 before it is ever popped */ + KT_EQ(t, prioqCount(&q), 3); + + uint64 expected[] = { 10, 30, 40 }; + for (uint32 i = 0; i < 3; i++) { + PrioQNode* min = prioqPopMin(&q); + KT_NOTNULL(t, min); + if (min) + KT_EQU(t, min->vruntime, expected[i]); + } + KT_TRUE(t, prioqIsEmpty(&q)); +} + +KTEST(prioqNodeStorageIsReusableAcrossQueueLifetimes) +{ + PrioQNode node; + + PrioQueue first; + prioqInit(&first); + prioqInsert(&first, &node, 500, 42); + PrioQNode* popped = prioqPopMin(&first); + KT_EQP(t, popped, &node); + + /* Same PrioQNode storage, brand new queue: rbtInsert must not be + tripped up by stale left/right/parent pointers left over from the + first tree. */ + PrioQueue second; + prioqInit(&second); + prioqInsert(&second, &node, 5, 1); + KT_EQ(t, prioqCount(&second), 1); + PrioQNode* min = prioqPeekMin(&second); + KT_NOTNULL(t, min); + if (min) + KT_EQU(t, min->vruntime, 5); +} + +#define prioqStressN 300 + +KTEST(prioqStressDrainsInFullyAscendingOrder) +{ + PrioQNode* pool = (PrioQNode*)kmalloc(sizeof(PrioQNode) * prioqStressN); + uint32* order = (uint32*)kmalloc(sizeof(uint32) * prioqStressN); + KT_NOTNULL(t, pool); + KT_NOTNULL(t, order); + if (!pool || !order) { + kfree(pool); + kfree(order); + return; + } + + for (uint32 i = 0; i < prioqStressN; i++) + order[i] = i; + shuffle(order, prioqStressN, 0xFEEDFACEu); + + PrioQueue q; + prioqInit(&q); + for (uint32 i = 0; i < prioqStressN; i++) { + uint32 id = order[i]; + /* vruntime = id * 3 keeps a strict, checkable total order while + still exercising ids that arrive in scrambled sequence. */ + prioqInsert(&q, &pool[id], (uint64)id * 3, id); + } + KT_EQ(t, prioqCount(&q), prioqStressN); + + uint64 lastVruntime = 0; + uint32 visited = 0; + for (;;) { + PrioQNode* min = prioqPopMin(&q); + if (!min) + break; + if (visited > 0) + KT_TRUE(t, min->vruntime >= lastVruntime); + lastVruntime = min->vruntime; + KT_EQU(t, min->vruntime, (uint64)min->id * 3); + visited++; + } + KT_EQ(t, visited, prioqStressN); + KT_TRUE(t, prioqIsEmpty(&q)); + + kfree(pool); + kfree(order); +}