Moonshark/tests/tests.lua

193 lines
4.3 KiB
Lua

local passed = 0
local total = 0
function assert(condition, message, level)
if condition then
return true
end
level = level or 2
local info = debug.getinfo(level, "Sl")
local file = info.source
-- Extract filename from source or use generic name
if file:sub(1,1) == "@" then
file = file:sub(2) -- Remove @ prefix for files
else
file = "<script>" -- Generic name for inline scripts
end
local line = info.currentline or "unknown"
local error_msg = message or "assertion failed"
local full_msg = string.format("%s:%s: %s", file, line, error_msg)
error(full_msg, 0)
end
function assert_equal(expected, actual, message)
if expected == actual then
return true
end
local msg = message or string.format("Expected %s, got %s", tostring(expected), tostring(actual))
assert(false, msg, 3)
end
function assert_table_equal(expected, actual, message, path)
path = path or "root"
if type(expected) ~= type(actual) then
local msg = message or string.format("Type mismatch at %s: expected %s, got %s", path, type(expected), type(actual))
assert(false, msg, 3)
end
if type(expected) ~= "table" then
if expected ~= actual then
local msg = message or string.format("Value mismatch at %s: expected %s, got %s", path, tostring(expected), tostring(actual))
assert(false, msg, 3)
end
return true
end
-- Check all keys in a exist in b with same values
for k, v in pairs(expected) do
local new_path = path .. "." .. tostring(k)
if actual[k] == nil then
local msg = message or string.format("Missing key at %s", new_path)
assert(false, msg, 3)
end
assert_table_equal(v, actual[k], message, new_path)
end
-- Check all keys in b exist in a
for k, v in pairs(actual) do
if expected[k] == nil then
local new_path = path .. "." .. tostring(k)
local msg = message or string.format("Extra key at %s", new_path)
assert(false, msg, 3)
end
end
return true
end
function assert_close(expected, actual, tolerance, message)
tolerance = tolerance or 1e-10
local diff = math.abs(expected - actual)
if diff <= tolerance then
return true
end
local msg = message or string.format("Expected %g, got %g (diff: %g > %g)", expected, actual, diff, tolerance)
assert(false, msg, 3)
end
function test(name, fn)
print("Testing " .. name .. "...")
total = total + 1
local start_time = os.clock()
local ok, err = pcall(fn)
local end_time = os.clock()
local duration = end_time - start_time
if ok then
passed = passed + 1
print(string.format(" ✓ PASS (%.3fs)", duration))
return true
else
print(" ✗ FAIL: " .. err)
if duration > 0.001 then
print(string.format(" (%.3fs)", duration))
end
return false
end
end
function run_tests(tests)
print("Running test suite...")
print("=" .. string.rep("=", 50))
for name, test_fn in pairs(tests) do
test(name, test_fn)
end
return summary()
end
function reset_tests()
passed = 0
total = 0
end
function test_stats()
return {
passed = passed,
total = total,
failed = total - passed,
success_rate = total > 0 and (passed / total) or 0
}
end
function summary()
print("=" .. string.rep("=", 50))
print(string.format("Test Results: %d/%d passed", passed, total))
local success = passed == total
if success then
print("🎉 All tests passed!")
else
local failed = total - passed
local rate = total > 0 and (passed / total * 100) or 0
print(string.format("❌ %d test(s) failed! (%.1f%% success rate)", failed, rate))
end
return success
end
function test_exit()
local success = passed == total
os.exit(success and 0 or 1)
end
function run_and_exit(tests)
local success = run_tests(tests)
os.exit(success and 0 or 1)
end
function benchmark(name, fn, iterations)
iterations = iterations or 1000
print("Benchmarking " .. name .. " (" .. iterations .. " iterations)...")
-- Warmup
for i = 1, math.min(10, iterations) do
fn()
end
-- Actual benchmark
local start = os.clock()
for i = 1, iterations do
fn()
end
local total_time = os.clock() - start
local avg_time = total_time / iterations
print(string.format(" Total: %.3fs, Average: %.6fs, Rate: %.0f ops/sec",
total_time, avg_time, 1/avg_time))
return {
total_time = total_time,
avg_time = avg_time,
ops_per_sec = 1/avg_time,
iterations = iterations
}
end
function file_exists(filename)
local file = io.open(filename, "r")
if file then
file:close()
return true
end
return false
end