183 lines
4.6 KiB
C
183 lines
4.6 KiB
C
#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);
|
|
}
|