#include "../src/page_table.h"
#include <cassert>
#include <cstdio>
static int tests_passed = 0;
#define TEST(name) \
do { \
printf(" %-40s", #name "..."); \
} while (0)
#define PASS() \
do { \
printf("PASS\n"); \
tests_passed++; \
} while (0)
void test_init() {
TEST(init);
PageTable pt;
pt.init(8, 128);
assert(pt.mip_levels() == 8);
assert(pt.base_tiles() == 128);
assert(pt.tiles_at_mip(0) == 128);
assert(pt.tiles_at_mip(1) == 64);
assert(pt.tiles_at_mip(7) == 1);
PASS();
}
void test_map_lookup() {
TEST(map_lookup);
PageTable pt;
pt.init(8, 128);
PhysicalLocation loc = { 10, 20 };
pt.map(0, 5, 3, loc);
PageLookup result = pt.lookup(0, 5, 3);
assert(result.valid);
assert(result.location.x == 10);
assert(result.location.y == 20);
// Unmapped → invalid
PageLookup miss = pt.lookup(0, 6, 3);
assert(!miss.valid);
PASS();
}
void test_evict() {
TEST(evict);
PageTable pt;
pt.init(4, 16);
PhysicalLocation loc = { 5, 5 };
pt.map(0, 3, 3, loc);
assert(pt.lookup(0, 3, 3).valid);
pt.evict(0, 3, 3);
assert(!pt.lookup(0, 3, 3).valid);
PASS();
}
void test_map_with_fallback() {
TEST(map_with_fallback);
PageTable pt;
pt.init(4, 16);
// Map at mip 2 (4×4 tiles)
PhysicalLocation parent_loc = { 30, 40 };
pt.map_with_fallback(2, 1, 1, parent_loc);
// Parent should be mapped
PageLookup parent = pt.lookup(2, 1, 1);
assert(parent.valid);
assert(parent.location.x == 30);
// Children at mip 1 (8×8): (2,2), (2,3), (3,2), (3,3)
for (int dy = 0; dy < 2; dy++) {
for (int dx = 0; dx < 2; dx++) {
PageLookup child = pt.lookup(1, 2 + dx, 2 + dy);
assert(child.valid);
assert(child.location.x == 30);
assert(child.location.y == 40);
}
}
// Grandchildren at mip 0 (16×16): 4..7 in each dimension
for (int dy = 0; dy < 4; dy++) {
for (int dx = 0; dx < 4; dx++) {
PageLookup gc = pt.lookup(0, 4 + dx, 4 + dy);
assert(gc.valid);
assert(gc.location.x == 30);
}
}
PASS();
}
void test_fallback_doesnt_overwrite() {
TEST(fallback_doesnt_overwrite);
PageTable pt;
pt.init(4, 16);
// Map a child first
PhysicalLocation child_loc = { 50, 60 };
pt.map(1, 2, 2, child_loc);
// Now map parent with fallback — should NOT overwrite existing child
PhysicalLocation parent_loc = { 30, 40 };
pt.map_with_fallback(2, 1, 1, parent_loc);
// Child at (1,2,2) should still have its own mapping
PageLookup child = pt.lookup(1, 2, 2);
assert(child.valid);
assert(child.location.x == 50);
assert(child.location.y == 60);
// Other children should have parent's fallback
PageLookup other = pt.lookup(1, 3, 3);
assert(other.valid);
assert(other.location.x == 30);
PASS();
}
void test_invalidate_region() {
TEST(invalidate_region);
PageTable pt;
pt.init(4, 16);
// Map some tiles at various mips
pt.map(2, 1, 1, { 10, 10 });
pt.map(1, 2, 2, { 20, 20 });
pt.map(1, 3, 3, { 21, 21 });
pt.map(0, 4, 4, { 30, 30 });
pt.map(0, 5, 5, { 31, 31 });
// Invalidate at mip 2, tile (1,1), 1×1 — should cascade to finer mips
pt.invalidate_region(2, 1, 1, 1, 1);
// Mip 2 (1,1) should be evicted
assert(!pt.lookup(2, 1, 1).valid);
// Mip 1: (2,2)..(3,3) should be evicted
assert(!pt.lookup(1, 2, 2).valid);
assert(!pt.lookup(1, 3, 3).valid);
// Mip 0: (4,4)..(7,7) should be evicted
assert(!pt.lookup(0, 4, 4).valid);
assert(!pt.lookup(0, 5, 5).valid);
PASS();
}
void test_clear() {
TEST(clear);
PageTable pt;
pt.init(4, 16);
for (int i = 0; i < 16; i++) {
pt.map(0, i, 0, { (uint8_t)i, 0 });
}
assert(pt.lookup(0, 5, 0).valid);
pt.clear();
for (int i = 0; i < 16; i++) {
assert(!pt.lookup(0, i, 0).valid);
}
PASS();
}
void test_gpu_data() {
TEST(gpu_data);
PageTable pt;
pt.init(4, 16);
pt.map(0, 0, 0, { 10, 20 });
pt.map(0, 1, 0, { 11, 21 });
uint8_t buf[16 * 16 * 4];
int written = pt.get_gpu_data(0, buf, sizeof(buf));
assert(written == 16 * 16 * 4);
// Entry (0,0): x=10, y=20, valid=1
assert(buf[0] == 10);
assert(buf[1] == 20);
assert(buf[2] == 1);
// Entry (1,0): x=11, y=21, valid=1
assert(buf[4] == 11);
assert(buf[5] == 21);
assert(buf[6] == 1);
// Entry (2,0): unmapped, valid=0
assert(buf[8] == 0);
assert(buf[9] == 0);
assert(buf[10] == 0);
PASS();
}
void test_out_of_bounds() {
TEST(out_of_bounds);
PageTable pt;
pt.init(4, 16);
// Out-of-bounds operations should not crash
pt.map(5, 0, 0, { 1, 1 }); // mip 5 doesn't exist
pt.map(0, 100, 100, { 1, 1 }); // coords too large
PageLookup r = pt.lookup(5, 0, 0);
assert(!r.valid);
PageLookup r2 = pt.lookup(0, 100, 100);
assert(!r2.valid);
pt.evict(5, 0, 0); // should not crash
PASS();
}
int main() {
printf("=== Page Table Tests ===\n");
test_init();
test_map_lookup();
test_evict();
test_map_with_fallback();
test_fallback_doesnt_overwrite();
test_invalidate_region();
test_clear();
test_gpu_data();
test_out_of_bounds();
printf("\nAll %d tests passed.\n", tests_passed);
return 0;
}