Files
TransPyC/Test/BuddyTest/App/mbuddytest.py
2026-07-18 19:25:40 +08:00

536 lines
19 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import t, c
from t import CInt, CPtr, CChar, CUInt8T, CUInt32T, CUInt64T, CSizeT
import viperlib
import viperio
import stdlib
import memhub
import string
import testcheck
import w32.win32process
import w32.win32sync
import w32.win32base
# 线程函数: 在共享 MBuddy 上执行 alloc/free (测试 spinlock 线程安全)
def _thread_alloc_free(param: t.CVoid | t.CPtr) -> t.CUInt32T:
bd: memhub.MemBuddy | t.CPtr = (memhub.MemBuddy | t.CPtr)(param)
i: CInt
for i in range(100):
p: bytes = bd.alloc(32)
if p != None:
p_c: CUInt8T | CPtr = CPtr(p)
p_c[0] = CUInt8T(i & 0xFF)
bd.free(p)
return 0
def mbuddy_main() -> CInt:
buf: bytes = stdlib.malloc(256)
testcheck.begin("BuddyTest: 伙伴系统内存分配器测试")
# === 基础 alloc/free 测试 ===
testcheck.section("基础 alloc/free")
arena: bytes = stdlib.malloc(65536)
bd: memhub.MemBuddy | CPtr = memhub.MemBuddy(arena, 65536)
viperlib.snprintf(buf, 256, "stats=%lu, free_count=%lu", bd.stats(), bd.free_count())
testcheck.info(buf)
p1: bytes = bd.alloc(16)
p2: bytes = bd.alloc(64)
p3: bytes = bd.alloc(256)
testcheck.check(p1 != None and p2 != None and p3 != None,
"3 allocs OK", "alloc FAILED")
# 写入数据验证指针可用
p_c: CUInt8T | CPtr = CPtr(p1)
p_c[0] = CUInt8T(0xAB)
p_c[1] = CUInt8T(0xCD)
viperlib.snprintf(buf, 256, "p1[0]=0x%x p1[1]=0x%x", p_c[0], p_c[1])
testcheck.info(buf)
testcheck.check(p_c[0] == CUInt8T(0xAB) and p_c[1] == CUInt8T(0xCD),
"write/read OK", "write/read FAILED")
bd.free(p1)
bd.free(p2)
bd.free(p3)
testcheck.ok("3 frees OK")
# 释放后验证合并: free_count 应回到 1 (单个大块)
testcheck.check(bd.free_count() == 1,
"coalesce: free_count=1 OK", "coalesce: free_count != 1")
# 合并后应能分配大块
big: bytes = bd.alloc(16000)
testcheck.check(big != None, "big alloc OK", "big alloc FAILED")
bd.free(big)
stdlib.free(arena)
# === calloc 测试 (验证清零) ===
testcheck.section("calloc 清零")
arena2: bytes = stdlib.malloc(4096)
bd2: memhub.MemBuddy | CPtr = memhub.MemBuddy(arena2, 4096)
dirty: bytes = bd2.alloc(64)
d_c: CUInt8T | CPtr = CPtr(dirty)
i: CInt
for i in range(64):
d_c[i] = CUInt8T(0xFF)
bd2.free(dirty)
cl: bytes = bd2.calloc(8, 8)
if cl != None:
cl_c: CUInt8T | CPtr = CPtr(cl)
all_zero: CInt = 1
j: CInt
for j in range(64):
if cl_c[j] != 0:
all_zero = 0
break
testcheck.check(all_zero == 1, "calloc zeroed OK", "calloc NOT zeroed")
bd2.free(cl)
else:
testcheck.fail("calloc FAILED")
stdlib.free(arena2)
# === realloc 扩展测试 ===
testcheck.section("realloc 扩展")
arena3: bytes = stdlib.malloc(4096)
bd3: memhub.MemBuddy | CPtr = memhub.MemBuddy(arena3, 4096)
rp: bytes = bd3.alloc(32)
rp_c: CUInt8T | CPtr = CPtr(rp)
k: CInt
for k in range(32):
rp_c[k] = CUInt8T(k + 1)
rp2: bytes = bd3.realloc(rp, 128)
if rp2 != None:
rp2_c: CUInt8T | CPtr = CPtr(rp2)
ok: CInt = 1
m: CInt
for m in range(32):
if rp2_c[m] != CUInt8T(m + 1):
ok = 0
break
testcheck.check(ok == 1, "realloc grow data preserved OK", "realloc grow data LOST")
bd3.free(rp2)
else:
testcheck.fail("realloc grow FAILED")
stdlib.free(arena3)
# === 原地收缩测试 (验证无泄漏) ===
testcheck.section("原地收缩 (realloc shrink)")
arena_shrink: bytes = stdlib.malloc(4096)
bds: memhub.MemBuddy | CPtr = memhub.MemBuddy(arena_shrink, 4096)
sp: bytes = bds.alloc(256)
testcheck.check(sp != None, "alloc 256 OK", "alloc 256 FAILED")
fc_before: CSizeT = bds.free_count()
# realloc 到更小尺寸: 256+8=264 -> order 4 (512); 32+8=40 -> order 1 (64)
# new_order(1) <= old_order(4), 应原地返回同一指针
sp2: bytes = bds.realloc(sp, 32)
fc_after: CSizeT = bds.free_count()
testcheck.check(sp2 == sp, "shrink same ptr OK", "shrink ptr changed")
testcheck.check(fc_after == fc_before, "shrink no leak OK", "shrink LEAKED blocks")
# 验证数据仍可读写
sp_c: CUInt8T | CPtr = CPtr(sp2)
sp_c[0] = CUInt8T(0x42)
testcheck.check(sp_c[0] == CUInt8T(0x42), "shrink data OK", "shrink data FAILED")
bds.free(sp2)
testcheck.check(bds.free_count() == 1, "shrink+free coalesce OK", "shrink+free coalesce FAILED")
stdlib.free(arena_shrink)
# === 边界情况测试 ===
testcheck.section("边界情况")
arena4: bytes = stdlib.malloc(4096)
bd4: memhub.MemBuddy | CPtr = memhub.MemBuddy(arena4, 4096)
# alloc(0) 应返回 None
z: bytes = bd4.alloc(0)
testcheck.check(z == None, "alloc(0)=None OK", "alloc(0) should be None")
# free(None) 应安全
bd4.free(None)
testcheck.ok("free(None) safe")
# realloc(None, size) 等价于 alloc
rn: bytes = bd4.realloc(None, 32)
testcheck.check(rn != None, "realloc(None)=alloc OK", "realloc(None) FAILED")
bd4.free(rn)
# realloc(ptr, 0) 等价于 free
rz: bytes = bd4.alloc(32)
rz2: bytes = bd4.realloc(rz, 0)
testcheck.check(rz2 == None, "realloc(ptr,0)=free OK", "realloc(ptr,0) should be None")
stdlib.free(arena4)
# === 非法指针防护测试 ===
testcheck.section("非法指针防护")
arena5: bytes = stdlib.malloc(4096)
bd5: memhub.MemBuddy | CPtr = memhub.MemBuddy(arena5, 4096)
valid: bytes = bd5.alloc(32)
testcheck.check(valid != None, "alloc for illegal test OK", "alloc FAILED")
# 1. free 随机地址 (地址 1)
bd5.free(t.CVoid(t.CUInt64T(1), t.CPtr))
testcheck.ok("free(addr=1) ignored")
# 2. free NULL
bd5.free(None)
testcheck.ok("free(NULL) ignored")
# 3. free 越界指针 (arena 之前)
bd5.free(t.CVoid(t.CUInt64T(arena5), t.CPtr))
testcheck.ok("free(before arena) ignored")
# 4. free 越界指针 (arena 之后)
bd5.free(t.CVoid(t.CUInt64T(arena5) + 99999, t.CPtr))
testcheck.ok("free(after arena) ignored")
# 5. free 未对齐指针 (arena + 1, 在 mem 区域内但不对齐)
bd5.free(t.CVoid(t.CUInt64T(bd5.mem) + 1, t.CPtr))
testcheck.ok("free(misaligned) ignored")
# 6. 验证合法 free 仍正常工作 (释放后应能再次分配)
bd5.free(valid)
retry5: bytes = bd5.alloc(32)
testcheck.check(retry5 != None, "valid free still works OK", "valid free BROKEN")
if retry5 != None:
bd5.free(retry5)
# 7. realloc 非法指针应返回 None
r_illegal: bytes = bd5.realloc(t.CVoid(t.CUInt64T(1), t.CPtr), 64)
testcheck.check(r_illegal == None, "realloc(illegal)=None OK", "realloc(illegal) should be None")
stdlib.free(arena5)
# === OOM 内存耗尽测试 ===
testcheck.section("OOM 内存耗尽")
arena6: bytes = stdlib.malloc(4096)
bd6: memhub.MemBuddy | CPtr = memhub.MemBuddy(arena6, 4096)
# 持续分配直到失败
alloc_count: CInt = 0
ptrs_arr: bytes = stdlib.malloc(8 * 256) # 存储指针的数组
ptrs: CUInt64T | CPtr = CPtr(ptrs_arr)
while alloc_count < 256:
p: bytes = bd6.alloc(32)
if p == None:
break
ptrs[alloc_count] = t.CUInt64T(p)
alloc_count += 1
viperlib.snprintf(buf, 256, "allocated %d blocks before OOM", alloc_count)
testcheck.info(buf)
testcheck.check(alloc_count > 0, "OOM: got some allocs OK", "OOM: no allocs at all")
# 尝试再分配应失败
oom_p: bytes = bd6.alloc(32)
testcheck.check(oom_p == None, "OOM: alloc fails OK", "OOM: alloc should fail")
# 释放一个块后应能再次分配
if alloc_count > 0:
first_ptr: bytes = t.CVoid(ptrs[0], t.CPtr)
bd6.free(first_ptr)
retry: bytes = bd6.alloc(32)
testcheck.check(retry != None, "OOM: free+alloc OK", "OOM: free+alloc FAILED")
if retry != None:
bd6.free(retry)
# 释放所有块
idx: CInt = 1
while idx < alloc_count:
cur_p: bytes = t.CVoid(ptrs[idx], t.CPtr)
bd6.free(cur_p)
idx += 1
# 全部释放后应能合并回单个大块
testcheck.check(bd6.free_count() == 1,
"OOM: full coalesce OK", "OOM: free_count != 1 after full free")
stdlib.free(ptrs_arr)
stdlib.free(arena6)
# === 空闲链表状态测试 ===
testcheck.section("空闲链表状态")
arena7: bytes = stdlib.malloc(4096)
bd7: memhub.MemBuddy | CPtr = memhub.MemBuddy(arena7, 4096)
# 初始: 1 个大块
testcheck.check(bd7.free_count() == 1, "init: 1 free block OK", "init: free_count != 1")
# 分配后: 空闲块数应增加 (分裂产生多个小块)
a1: bytes = bd7.alloc(32)
fc1: CSizeT = bd7.free_count()
viperlib.snprintf(buf, 256, "after alloc(32): free_count=%lu", fc1)
testcheck.info(buf)
testcheck.check(fc1 > 1, "after alloc: split OK", "after alloc: no split")
# 再分配
a2: bytes = bd7.alloc(32)
fc2: CSizeT = bd7.free_count()
testcheck.check(fc2 >= 1, "after 2nd alloc: free >= 1 OK", "after 2nd alloc: free < 1")
# 释放 a1: 空闲块数应增加
bd7.free(a1)
fc3: CSizeT = bd7.free_count()
testcheck.check(fc3 > fc2, "after free a1: free increased OK", "after free a1: free not increased")
# 释放 a2: 应合并, 空闲块数回到 1
bd7.free(a2)
testcheck.check(bd7.free_count() == 1,
"after free all: coalesced to 1 OK", "after free all: free_count != 1")
stdlib.free(arena7)
# === with 上下文管理器测试 ===
testcheck.section("with 上下文管理器")
arena8: bytes = stdlib.malloc(4096)
with memhub.MemBuddy(arena8, 4096) as wbd:
wp1: bytes = wbd.alloc(32)
wp2: bytes = wbd.alloc(64)
testcheck.check(wp1 != None and wp2 != None,
"with: allocs OK", "with: allocs FAILED")
# 退出 with 块时 __exit__ 自动 reset
# 退出后重新进入,验证 reset 生效
with memhub.MemBuddy(arena8, 4096) as wbd2:
wp3: bytes = wbd2.alloc(1024)
testcheck.check(wp3 != None, "with: reset+realloc OK", "with: reset+realloc FAILED")
stdlib.free(arena8)
# === 双重 free 检测测试 ===
testcheck.section("双重 free 检测")
arena_df: bytes = stdlib.malloc(4096)
bd_df: memhub.MemBuddy | CPtr = memhub.MemBuddy(arena_df, 4096)
df: bytes = bd_df.alloc(64)
testcheck.check(df != None, "df: alloc OK", "df: alloc FAILED")
# 第一次 free
bd_df.free(df)
fc_df1: CSizeT = bd_df.free_count()
testcheck.ok("first free OK")
# 第二次 free (双重 free) — 应被静默拒绝
bd_df.free(df)
fc_df2: CSizeT = bd_df.free_count()
testcheck.check(fc_df2 == fc_df1, "double free rejected OK", "double free CORRUPTED state")
# 验证分配器仍正常工作
df2: bytes = bd_df.alloc(64)
testcheck.check(df2 != None, "df: alloc after double-free OK", "df: alloc after double-free FAILED")
if df2 != None:
bd_df.free(df2)
stdlib.free(arena_df)
# === 自检功能测试 ===
testcheck.section("自检功能")
arena_sc: bytes = stdlib.malloc(4096)
bd_sc: memhub.MemBuddy | CPtr = memhub.MemBuddy(arena_sc, 4096)
# 初始状态自检
testcheck.check(bd_sc.self_check() == 0, "self_check init OK", "self_check init FAILED")
# 分配后自检
sc_p1: bytes = bd_sc.alloc(32)
sc_p2: bytes = bd_sc.alloc(128)
testcheck.check(bd_sc.self_check() == 0, "self_check after alloc OK", "self_check after alloc FAILED")
# 部分释放后自检
bd_sc.free(sc_p1)
testcheck.check(bd_sc.self_check() == 0, "self_check after partial free OK", "self_check after partial free FAILED")
# 全部释放后自检 (应合并回单个大块)
bd_sc.free(sc_p2)
testcheck.check(bd_sc.self_check() == 0, "self_check after full free OK", "self_check after full free FAILED")
# 双重 free 后自检 (验证状态未损坏)
sc_p3: bytes = bd_sc.alloc(64)
bd_sc.free(sc_p3)
bd_sc.free(sc_p3) # 双重 free应被拒绝
testcheck.check(bd_sc.self_check() == 0, "self_check after double-free OK", "self_check after double-free CORRUPTED")
stdlib.free(arena_sc)
# === 并发多线程测试 (spinlock) ===
testcheck.section("并发多线程 (spinlock)")
arena_mt: bytes = stdlib.malloc(65536)
bd_mt: memhub.MemBuddy | CPtr = memhub.MemBuddy(arena_mt, 65536)
NUM_THREADS: CInt = 4
handles_arr: bytes = stdlib.malloc(8 * 4)
handles: CUInt64T | CPtr = CPtr(handles_arr)
tid: CUInt32T = 0
# 创建 4 个线程并发 alloc/free
t_idx: CInt
for t_idx in range(NUM_THREADS):
h: bytes = w32.win32process.CreateThread(None, 0, _thread_alloc_free, t.CVoid(t.CUInt64T(bd_mt), t.CPtr), 0, c.Addr(tid))
handles[t_idx] = CUInt64T(h)
# 等待所有线程完成
for t_idx in range(NUM_THREADS):
h: bytes = t.CVoid(handles[t_idx], t.CPtr)
w32.win32sync.WaitForSingleObject(h, w32.win32base.INFINITE)
w32.win32base.CloseHandle(h)
# 验证分配器状态: 自检通过,所有块已合并
testcheck.check(bd_mt.self_check() == 0, "mt: self_check OK", "mt: self_check FAILED")
testcheck.check(bd_mt.free_count() == 1, "mt: free_count=1 OK", "mt: free_count != 1 (leaked)")
viperlib.snprintf(buf, 256, "mt: free_count=%lu", bd_mt.free_count())
testcheck.info(buf)
stdlib.free(handles_arr)
stdlib.free(arena_mt)
# === 极端边界测试 ===
testcheck.section("极端边界")
# 1. 分配尺寸恰巧是块边界
arena_ex: bytes = stdlib.malloc(4096)
bd_ex: memhub.MemBuddy | CPtr = memhub.MemBuddy(arena_ex, 4096)
# 24 + 8 = 32 = MIN_BLOCK, 恰好填满 order 0 块
exact_fit: bytes = bd_ex.alloc(24)
testcheck.check(exact_fit != None, "exact fit order 0 OK", "exact fit order 0 FAILED")
# 25 + 8 = 33 > 32, 需要 order 1 (64 字节)
over_fit: bytes = bd_ex.alloc(25)
testcheck.check(over_fit != None, "over by 1 byte OK", "over by 1 byte FAILED")
bd_ex.free(exact_fit)
bd_ex.free(over_fit)
testcheck.check(bd_ex.free_count() == 1, "boundary: coalesce OK", "boundary: coalesce FAILED")
# 2. 单次分配接近 arena 上限
max_usable: CSizeT = bd_ex.mem_size - 8
viperlib.snprintf(buf, 256, "mem_size=%lu, max_usable=%lu", bd_ex.mem_size, max_usable)
testcheck.info(buf)
# 恰好用完整个 arena
exact_max: bytes = bd_ex.alloc(max_usable)
testcheck.check(exact_max != None, "exact max alloc OK", "exact max alloc FAILED")
testcheck.check(bd_ex.free_count() == 0, "max alloc: arena full OK", "max alloc: arena not full")
bd_ex.free(exact_max)
testcheck.check(bd_ex.free_count() == 1, "max free: coalesce OK", "max free: coalesce FAILED")
# 再次分配同样大小 — 验证合并后可再次使用
exact_max2: bytes = bd_ex.alloc(max_usable)
testcheck.check(exact_max2 != None, "exact max re-alloc OK", "exact max re-alloc FAILED")
bd_ex.free(exact_max2)
# 3. 分配超过 arena 上限 — 应失败
over_max: bytes = bd_ex.alloc(bd_ex.mem_size)
testcheck.check(over_max == None, "over max: fails OK", "over max: should fail")
over_max2: bytes = bd_ex.alloc(max_usable + 1)
testcheck.check(over_max2 == None, "max+1: fails OK", "max+1: should fail")
stdlib.free(arena_ex)
# 4. Arena 太小 (仅够 free_lists 开销, 264 字节)
arena_tiny: bytes = stdlib.malloc(264)
bd_tiny: memhub.MemBuddy | CPtr = memhub.MemBuddy(arena_tiny, 264)
testcheck.check(bd_tiny.stats() == 0, "tiny arena: stats=0 OK", "tiny arena: stats != 0")
tiny_p: bytes = bd_tiny.alloc(32)
testcheck.check(tiny_p == None, "tiny arena: alloc fails OK", "tiny arena: should fail")
stdlib.free(arena_tiny)
# 5. Arena 刚好够一个最小块 (264 + 32 = 296)
arena_one: bytes = stdlib.malloc(296)
bd_one: memhub.MemBuddy | CPtr = memhub.MemBuddy(arena_one, 296)
testcheck.check(bd_one.stats() == 32, "one block: stats=32 OK", "one block: stats != 32")
one_p: bytes = bd_one.alloc(24)
testcheck.check(one_p != None, "one block: alloc OK", "one block: alloc FAILED")
one_p2: bytes = bd_one.alloc(24)
testcheck.check(one_p2 == None, "one block: OOM OK", "one block: should be OOM")
bd_one.free(one_p)
testcheck.check(bd_one.free_count() == 1, "one block: free OK", "one block: free FAILED")
stdlib.free(arena_one)
# 6. 2 的幂 arena vs 非 2 的幂 arena
arena_pow2: bytes = stdlib.malloc(512)
bd_pow2: memhub.MemBuddy | CPtr = memhub.MemBuddy(arena_pow2, 512)
# 512 - 264 = 248, largest_pow2(248) = 128
testcheck.check(bd_pow2.stats() == 128, "pow2 arena: stats=128 OK", "pow2 arena: stats != 128")
stdlib.free(arena_pow2)
arena_non2: bytes = stdlib.malloc(500)
bd_non2: memhub.MemBuddy | CPtr = memhub.MemBuddy(arena_non2, 500)
# 500 - 264 = 236, largest_pow2(236) = 128
testcheck.check(bd_non2.stats() == 128, "non-pow2 arena: stats=128 OK", "non-pow2 arena: stats != 128")
stdlib.free(arena_non2)
# 7. 压力测试: 反复 alloc/free 同一尺寸 1000 次
arena_stress: bytes = stdlib.malloc(4096)
bd_stress: memhub.MemBuddy | CPtr = memhub.MemBuddy(arena_stress, 4096)
s: CInt
for s in range(1000):
sp: bytes = bd_stress.alloc(64)
if sp != None:
sp_c: CUInt8T | CPtr = CPtr(sp)
sp_c[0] = CUInt8T(s & 0xFF)
bd_stress.free(sp)
testcheck.check(bd_stress.self_check() == 0, "stress: self_check OK", "stress: self_check FAILED")
testcheck.check(bd_stress.free_count() == 1, "stress: free_count=1 OK", "stress: leaked")
stdlib.free(arena_stress)
# 8. 分配全部最小块然后全部释放
arena_fill: bytes = stdlib.malloc(4096)
bd_fill: memhub.MemBuddy | CPtr = memhub.MemBuddy(arena_fill, 4096)
fill_count: CInt = 0
fill_ptrs_arr: bytes = stdlib.malloc(8 * 256)
fill_ptrs: CUInt64T | CPtr = CPtr(fill_ptrs_arr)
fc: CInt
for fc in range(256):
fp: bytes = bd_fill.alloc(24)
if fp == None:
break
fill_ptrs[fill_count] = t.CUInt64T(fp)
fill_count += 1
viperlib.snprintf(buf, 256, "fill: %d min blocks allocated", fill_count)
testcheck.info(buf)
testcheck.check(fill_count > 0, "fill: got blocks OK", "fill: no blocks")
testcheck.check(bd_fill.self_check() == 0, "fill: self_check OK", "fill: self_check FAILED")
# 全部释放
fi: CInt
for fi in range(fill_count):
fp2: bytes = t.CVoid(fill_ptrs[fi], t.CPtr)
bd_fill.free(fp2)
testcheck.check(bd_fill.free_count() == 1, "fill: all freed OK", "fill: leaked after free all")
testcheck.check(bd_fill.self_check() == 0, "fill: self_check after free OK", "fill: self_check FAILED")
stdlib.free(fill_ptrs_arr)
stdlib.free(arena_fill)
stdlib.free(buf)
return testcheck.end()