mirror of
https://github.com/appgurueu/modlib.git
synced 2024-12-22 21:32:27 +01:00
Remove local tests in favor of a test repo
This commit is contained in:
parent
f3d856c2db
commit
21e97ca329
@ -6,6 +6,9 @@ Multipurpose Minetest Modding Library
|
||||
|
||||
No dependencies. Licensed under the MIT License. Written by Lars Mueller aka LMD or appguru(eu). Notable contributions by [luk3yx](https://github.com/luk3yx) in the form of suggestions, bug reports and fixes.
|
||||
|
||||
## Tests
|
||||
|
||||
The tests are located in a different repo, [`modlib_test`](https://github.com/appgurueu/modlib_test), as they are quite heavy due to testing the PNG reader using PngSuite. Reading the tests for examples of API usage is recommended.
|
||||
## API
|
||||
|
||||
Mostly self-documenting code. Mod namespace is `modlib`, containing all variables & functions.
|
||||
|
4
init.lua
4
init.lua
@ -161,9 +161,5 @@ end
|
||||
-- Run build scripts
|
||||
-- dofile(modlib.mod.get_resource("modlib", "build", "html_entities.lua"))
|
||||
|
||||
--[[
|
||||
--modlib.mod.include"test.lua"
|
||||
]]
|
||||
|
||||
-- TODO verify localizations suffice
|
||||
return modlib
|
557
test.lua
557
test.lua
@ -1,557 +0,0 @@
|
||||
-- ensure modlib API isn't leaking into global environment
|
||||
assert(modlib.bluon.assert ~= assert)
|
||||
|
||||
local random, huge = math.random, math.huge
|
||||
local parent_env = getfenv(1)
|
||||
setfenv(1, setmetatable({}, {
|
||||
__index = function(_, key)
|
||||
local value = modlib[key]
|
||||
if value ~= nil then
|
||||
return value
|
||||
end
|
||||
return parent_env[key]
|
||||
end,
|
||||
__newindex = function(_, key, value)
|
||||
error(dump{key = key, value = value})
|
||||
end
|
||||
}))
|
||||
|
||||
-- math
|
||||
do
|
||||
local function assert_tonumber(num, base)
|
||||
local str = math.tostring(num, base)
|
||||
assert(tonumber(str, base) == num, str)
|
||||
end
|
||||
assert_tonumber(134217503, 36)
|
||||
assert_tonumber(3.14, 10)
|
||||
for i = -100, 100 do
|
||||
local log = math.log[2](2^i)
|
||||
assert(_G.math.abs(log - i) < 2^-40) -- Small tolerance for floating-point precision errors
|
||||
assert(math.log(2^i) == _G.math.log(2^i))
|
||||
assert(math.log(2^i, 2) == log)
|
||||
end
|
||||
end
|
||||
|
||||
-- func
|
||||
do
|
||||
local tab = {a = 1, b = 2}
|
||||
local function check_entry(key, value)
|
||||
assert(tab[key] == value)
|
||||
tab[key] = nil
|
||||
end
|
||||
func.iterate(check_entry, pairs(tab))
|
||||
assert(next(tab) == nil)
|
||||
|
||||
tab = {a = 1, b = 2}
|
||||
local function pairs_callback(callback, tab)
|
||||
for k, v in pairs(tab) do
|
||||
callback(k, v)
|
||||
end
|
||||
end
|
||||
for k, v in func.for_generator(pairs_callback, tab) do
|
||||
check_entry(k, v)
|
||||
end
|
||||
assert(next(tab) == nil)
|
||||
assert(func.aggregate(func.add, 1, 2, 3) == 6)
|
||||
local called = false
|
||||
local function fun(arg)
|
||||
assert(arg == "test")
|
||||
local retval = called
|
||||
called = true
|
||||
return retval
|
||||
end
|
||||
local memo = func.memoize(fun)
|
||||
assert(memo"test" == false)
|
||||
assert(memo.test == false)
|
||||
end
|
||||
|
||||
-- string
|
||||
assert(string.escape_magic_chars"%" == "%%")
|
||||
|
||||
-- table
|
||||
do
|
||||
local tab = {}
|
||||
tab[tab] = tab
|
||||
local table_copy = table.deepcopy(tab)
|
||||
assert(table_copy[table_copy] == table_copy)
|
||||
assert(table.is_circular(tab))
|
||||
assert(not table.is_circular{a = 1})
|
||||
assert(table.equals_noncircular({[{}]={}}, {[{}]={}}))
|
||||
assert(table.equals_content(tab, table_copy))
|
||||
local equals_references = table.equals_references
|
||||
assert(equals_references(tab, table_copy))
|
||||
assert(equals_references({}, {}))
|
||||
assert(not equals_references({a = 1, b = 2}, {a = 1, b = 3}))
|
||||
tab = {}
|
||||
tab.a, tab.b = tab, tab
|
||||
table_copy = table.deepcopy(tab)
|
||||
assert(equals_references(tab, table_copy))
|
||||
local x, y = {}, {}
|
||||
assert(not equals_references({[x] = x, [y] = y}, {[x] = y, [y] = x}))
|
||||
assert(equals_references({[x] = x, [y] = y}, {[x] = x, [y] = y}))
|
||||
local nilget = table.nilget
|
||||
assert(nilget({a = {b = {c = 42}}}, "a", "b", "c") == 42)
|
||||
assert(nilget({a = {}}, "a", "b", "c") == nil)
|
||||
assert(nilget(nil, "a", "b", "c") == nil)
|
||||
assert(nilget(nil, "a", nil, "c") == nil)
|
||||
local rope = table.rope{}
|
||||
rope:write"hello"
|
||||
rope:write" "
|
||||
rope:write"world"
|
||||
assert(rope:to_text() == "hello world", rope:to_text())
|
||||
tab = {a = 1, b = {2}}
|
||||
tab[3] = tab
|
||||
local contents = {
|
||||
a = 1,
|
||||
[1] = 1,
|
||||
b = 1,
|
||||
[tab.b] = 1,
|
||||
[2] = 1,
|
||||
[tab] = 1,
|
||||
[3] = 1
|
||||
}
|
||||
table.deep_foreach_any(tab, function(content)
|
||||
assert(contents[content], content)
|
||||
contents[content] = 2
|
||||
end)
|
||||
for _, value in pairs(contents) do
|
||||
assert(value == 2)
|
||||
end
|
||||
|
||||
-- Test table.binary_search against a linear search
|
||||
local function linear_search(list, value)
|
||||
for i, val in ipairs(list) do
|
||||
if val == value then
|
||||
return i
|
||||
end
|
||||
if val > value then
|
||||
return -i
|
||||
end
|
||||
end
|
||||
return -#list-1
|
||||
end
|
||||
|
||||
for k = 0, 100 do
|
||||
local sorted = {}
|
||||
for i = 1, k do
|
||||
sorted[i] = _G.math.random(1, 1000)
|
||||
end
|
||||
_G.table.sort(sorted)
|
||||
for i = 1, 10 do
|
||||
local pick = _G.math.random(-100, 1100)
|
||||
local linear, binary = linear_search(sorted, pick), table.binary_search(sorted, pick)
|
||||
-- If numbers appear twice (or more often), the indices may differ, as long as the number is the same.
|
||||
assert(linear == binary or (linear > 0 and sorted[linear] == sorted[binary]))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- heaps
|
||||
do
|
||||
local n = 100
|
||||
for _, heap in pairs{heap, hashheap} do
|
||||
local list = {}
|
||||
for index = 1, n do
|
||||
list[index] = index
|
||||
end
|
||||
table.shuffle(list)
|
||||
local heap = heap.new()
|
||||
for index = 1, #list do
|
||||
heap:push(list[index])
|
||||
end
|
||||
for index = 1, #list do
|
||||
local popped = heap:pop()
|
||||
assert(popped == index)
|
||||
end
|
||||
end
|
||||
do -- just hashheap
|
||||
local heap = hashheap.new()
|
||||
for i = 1, n do
|
||||
heap:push(i)
|
||||
end
|
||||
heap:replace(42, 0)
|
||||
assert(heap:pop() == 0)
|
||||
heap:replace(69, 101)
|
||||
assert(not heap:find_index(69))
|
||||
assert(heap:find_index(101))
|
||||
heap:remove(101)
|
||||
assert(not heap:find_index(101))
|
||||
heap:push(101)
|
||||
local last = 0
|
||||
for _ = 1, 98 do
|
||||
local new = heap:pop()
|
||||
assert(new > last)
|
||||
last = new
|
||||
end
|
||||
assert(heap:pop() == 101)
|
||||
end
|
||||
end
|
||||
|
||||
-- hashlist
|
||||
do
|
||||
local n = 100
|
||||
local list = hashlist.new{}
|
||||
for i = 1, n do
|
||||
list:push_tail(i)
|
||||
end
|
||||
for i = 1, n do
|
||||
local head = list:get_head()
|
||||
assert(head == list:pop_head(i) and head == i)
|
||||
end
|
||||
end
|
||||
|
||||
-- ranked set
|
||||
do
|
||||
local n = 100
|
||||
local ranked_set = ranked_set.new()
|
||||
local list = {}
|
||||
for i = 1, n do
|
||||
ranked_set:insert(i)
|
||||
list[i] = i
|
||||
end
|
||||
|
||||
assert(table.equals(ranked_set:to_table(), list))
|
||||
|
||||
local i = 0
|
||||
for rank, key in ranked_set:ipairs() do
|
||||
i = i + 1
|
||||
assert(i == key and i == rank)
|
||||
assert(ranked_set:get_by_rank(rank) == key)
|
||||
local rank, key = ranked_set:get(i)
|
||||
assert(key == i and i == rank)
|
||||
end
|
||||
assert(i == n)
|
||||
|
||||
for i = 1, n do
|
||||
local _, v = ranked_set:delete(i)
|
||||
assert(v == i, i)
|
||||
end
|
||||
assert(not next(ranked_set:to_table()))
|
||||
|
||||
local ranked_set = ranked_set.new()
|
||||
for i = 1, n do
|
||||
ranked_set:insert(i)
|
||||
end
|
||||
|
||||
for rank, key in ranked_set:ipairs(10, 20) do
|
||||
assert(rank == key and key >= 10 and key <= 20)
|
||||
end
|
||||
|
||||
for i = n, 1, -1 do
|
||||
local j = ranked_set:delete_by_rank(i)
|
||||
assert(j == i)
|
||||
end
|
||||
end
|
||||
|
||||
-- k-d-tree
|
||||
local vectors = {}
|
||||
for _ = 1, 1000 do
|
||||
_G.table.insert(vectors, {random(), random(), random()})
|
||||
end
|
||||
local kdtree = kdtree.new(vectors)
|
||||
for _, v in ipairs(vectors) do
|
||||
local neighbor, distance = kdtree:get_nearest_neighbor(v)
|
||||
assert(vector.equals(v, neighbor), distance == 0)
|
||||
end
|
||||
|
||||
for _ = 1, 1000 do
|
||||
local v = {random(), random(), random()}
|
||||
local _, distance = kdtree:get_nearest_neighbor(v)
|
||||
local min_distance = huge
|
||||
for _, w in ipairs(vectors) do
|
||||
local other_distance = vector.distance(v, w)
|
||||
if other_distance < min_distance then
|
||||
min_distance = other_distance
|
||||
end
|
||||
end
|
||||
assert(distance == min_distance)
|
||||
end
|
||||
|
||||
local function serializer_test(is_json, preserve)
|
||||
local function assert_preserves(obj)
|
||||
local preserved = preserve(obj)
|
||||
if obj ~= obj then
|
||||
assert(preserved ~= preserved)
|
||||
else
|
||||
assert(table.equals_references(preserved, obj))
|
||||
end
|
||||
end
|
||||
-- TODO proper deep table comparison with nan support
|
||||
for _, constant in pairs(is_json and {true, false} or {true, false, huge, -huge, 0/0}) do
|
||||
assert_preserves(constant)
|
||||
end
|
||||
-- Strings
|
||||
for i = 1, 1000 do
|
||||
assert_preserves(_G.table.concat(table.repetition(_G.string.char(i % 256), i)))
|
||||
end
|
||||
-- Numbers
|
||||
for _ = 1, 1000 do
|
||||
local int = random(-2^50, 2^50)
|
||||
assert(int % 1 == 0)
|
||||
assert_preserves(int)
|
||||
assert_preserves((random() - 0.5) * 2^random(-20, 20))
|
||||
end
|
||||
assert_preserves(2.9145637014948988508e-06)
|
||||
assert_preserves(1.1496387980481e-07)
|
||||
-- Simple tables
|
||||
assert_preserves{hello = "world", welt = "hallo"}
|
||||
assert_preserves{a = 1, b = "hallo", c = "true"}
|
||||
assert_preserves{"hello", "hello", "hello"}
|
||||
assert_preserves{1, 2, 3, true, false}
|
||||
if is_json then return end
|
||||
local circular = {}
|
||||
circular[circular] = circular
|
||||
circular[1] = circular
|
||||
assert_preserves(circular)
|
||||
local mixed = {1, 2, 3}
|
||||
mixed[mixed] = mixed
|
||||
mixed.vec = {x = 1, y = 2, z = 3}
|
||||
mixed.vec2 = table.copy(mixed.vec)
|
||||
mixed.blah = "blah"
|
||||
assert_preserves(mixed)
|
||||
local a, b, c = {}, {}, {}
|
||||
a[a] = a; a[b] = b; a[c] = c;
|
||||
b[a] = a; b[b] = b; b[c] = c;
|
||||
c[a] = a; c[b] = b; c[c] = c;
|
||||
a.a = {"a", a = a}
|
||||
assert_preserves(a)
|
||||
assert_preserves{["for"] = "keyword", ["in"] = "keyword"}
|
||||
end
|
||||
|
||||
-- JSON
|
||||
do
|
||||
serializer_test(true, function(object)
|
||||
return json:read_string(json:write_string(object))
|
||||
end)
|
||||
-- Verify spacing is accepted
|
||||
assert(table.equals_noncircular(json:read_string'\t\t\n{ "a" : 1, \t"b":2, "c" : [ 1, 2 ,3 ] } \n\r\t', {a = 1, b = 2, c = {1, 2, 3}}))
|
||||
-- Simple surrogate pair tests
|
||||
for _, prefix in pairs{"x", ""} do
|
||||
for _, suffix in pairs{"x", ""} do
|
||||
local function test(str, expected_str)
|
||||
if type(expected_str) == "number" then
|
||||
expected_str = text.utf8(expected_str)
|
||||
end
|
||||
return assert(json:read_string('"' .. prefix .. str .. suffix .. '"') == prefix .. expected_str .. suffix)
|
||||
end
|
||||
test([[\uD834\uDD1E]], 0x1D11E)
|
||||
test([[\uDD1E\uD834]], text.utf8(0xDD1E) .. text.utf8(0xD834))
|
||||
test([[\uD834]], 0xD834)
|
||||
test([[\uDD1E]], 0xDD1E)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- luon
|
||||
do
|
||||
serializer_test(false, function(object)
|
||||
return luon:read_string(luon:write_string(object))
|
||||
end)
|
||||
end
|
||||
|
||||
-- bluon
|
||||
do
|
||||
serializer_test(false, function(object)
|
||||
local rope = table.rope{}
|
||||
local written, read, input
|
||||
bluon:write(object, rope)
|
||||
written = rope:to_text()
|
||||
input = text.inputstream(written)
|
||||
read = bluon:read(input)
|
||||
local remaining = input:read(1)
|
||||
assert(not remaining)
|
||||
return read
|
||||
end)
|
||||
end
|
||||
|
||||
do
|
||||
local text = "<tag> & '\""
|
||||
local escaped = web.html.escape(text)
|
||||
assert(web.html.unescape(escaped) == text)
|
||||
assert(web.html.unescape"*" == _G.string.char(42))
|
||||
assert(web.html.unescape"B" == _G.string.char(0x42))
|
||||
assert(web.uri.encode"https://example.com/foo bar" == "https://example.com/foo%20bar")
|
||||
assert(web.uri.encode_component"foo/bar baz" == "foo%2Fbar%20baz")
|
||||
end
|
||||
|
||||
if not _G.minetest then return end
|
||||
|
||||
assert(minetest.luon:read_string(minetest.luon:write_string(ItemStack"")))
|
||||
|
||||
-- colorspec
|
||||
local colorspec = minetest.colorspec
|
||||
local function test_from_string(string, number)
|
||||
local spec = colorspec.from_string(string)
|
||||
local expected = colorspec.from_number_rgba(number)
|
||||
assertdump(table.equals(spec, expected), {expected = expected, actual = spec})
|
||||
end
|
||||
local spec = colorspec.from_number_rgba(0xDDCCBBAA)
|
||||
assertdump(table.equals(spec, {a = 0xAA, b = 0xBB, g = 0xCC, r = 0xDD}), spec)
|
||||
test_from_string("aliceblue", 0xf0f8ffff)
|
||||
test_from_string("aliceblue#42", 0xf0f8ff42)
|
||||
test_from_string("aliceblue#3", 0xf0f8ff33)
|
||||
test_from_string("#333", 0x333333FF)
|
||||
test_from_string("#694269", 0x694269FF)
|
||||
test_from_string("#11223344", 0x11223344)
|
||||
assert(colorspec.from_string"#694269":to_string() == "#694269")
|
||||
|
||||
-- Persistence
|
||||
local function test_logfile(reference_strings)
|
||||
local path = mod.get_resource"logfile.test.lua"
|
||||
os.remove(path)
|
||||
local logfile = persistence.lua_log_file.new(path, {root_preserved = true}, reference_strings)
|
||||
logfile:init()
|
||||
assert(logfile.root.root_preserved)
|
||||
logfile.root = {a_longer_string = "test"}
|
||||
logfile:rewrite()
|
||||
logfile:set_root({a = 1}, {b = 2, c = 3, d = _G.math.huge, e = -_G.math.huge, ["in"] = "keyword"})
|
||||
local circular = {}
|
||||
circular[circular] = circular
|
||||
logfile:set_root(circular, circular)
|
||||
logfile:close()
|
||||
logfile:init()
|
||||
assert(table.equals_references(logfile.root, {
|
||||
a_longer_string = "test",
|
||||
[{a = 1}] = {b = 2, c = 3, d = _G.math.huge, e = -_G.math.huge, ["in"] = "keyword"},
|
||||
[circular] = circular,
|
||||
}))
|
||||
if not reference_strings then
|
||||
for key in pairs(logfile.references) do
|
||||
assert(type(key) ~= "string")
|
||||
end
|
||||
end
|
||||
end
|
||||
test_logfile(true)
|
||||
test_logfile(false)
|
||||
-- SQLite3
|
||||
do
|
||||
local sqlite3 = persistence.sqlite3()
|
||||
local path = mod.get_resource("modlib", "database.test.sqlite3")
|
||||
local p = sqlite3.new(path, {})
|
||||
p:init()
|
||||
p:rewrite()
|
||||
p:set_root("key", "value")
|
||||
assert(p.root.key == "value")
|
||||
p:set_root("other key", "other value")
|
||||
p:set_root("key", "other value")
|
||||
p:set_root("key", nil)
|
||||
local x = { x = 1, y = 2 }
|
||||
p:set_root("x1", x)
|
||||
p:set_root("x2", x)
|
||||
p:set_root("x2", nil)
|
||||
p:set_root("x1", nil)
|
||||
p:set_root("key", { a = 1, b = 2, c = { a = 1 } })
|
||||
p:set_root("key", nil)
|
||||
p:set_root("key", { a = 1, b = 2, c = 3 })
|
||||
local cyclic = {}
|
||||
cyclic.cycle = cyclic
|
||||
p:set_root("cyclic", cyclic)
|
||||
p:set_root("cyclic", nil)
|
||||
p:collectgarbage()
|
||||
p:defragment_ids()
|
||||
local rows = {}
|
||||
for row in p.database:rows("SELECT * FROM table_entries ORDER BY table_id, key_type, key") do
|
||||
_G.table.insert(rows, row)
|
||||
end
|
||||
assert(table.equals(rows, {
|
||||
{ 1, 3, "key", 4, 2 },
|
||||
{ 1, 3, "other key", 3, "other value" },
|
||||
{ 2, 3, "a", 2, 1 },
|
||||
{ 2, 3, "b", 2, 2 },
|
||||
{ 2, 3, "c", 2, 3 },
|
||||
}))
|
||||
p:close()
|
||||
p = sqlite3.new(path, {})
|
||||
p:init()
|
||||
assert(table.equals(p.root, {
|
||||
key = { a = 1, b = 2, c = 3 },
|
||||
["other key"] = "other value",
|
||||
}))
|
||||
p:close()
|
||||
os.remove(path)
|
||||
end
|
||||
|
||||
-- in-game tests & b3d testing
|
||||
local tests = {
|
||||
-- depends on player_api
|
||||
b3d = false,
|
||||
liquid_dir = false,
|
||||
liquid_raycast = false
|
||||
}
|
||||
if tests.b3d then
|
||||
local stream = assert(io.open(mod.get_resource("player_api", "models", "character.b3d"), "r"))
|
||||
local character = assert(b3d.read(stream))
|
||||
stream:close()
|
||||
--! dirty helper method to truncate tables with 10+ number keys
|
||||
local function _b3d_truncate(table)
|
||||
local count = 1
|
||||
for key, value in pairs(table) do
|
||||
if type(key) == "table" then
|
||||
_b3d_truncate(key)
|
||||
end
|
||||
if type(value) == "table" then
|
||||
_b3d_truncate(value)
|
||||
end
|
||||
count = count + 1
|
||||
if type(key) == "number" and count >= 9 and next(table, key) then
|
||||
if count == 9 then
|
||||
table[key] = "TRUNCATED"
|
||||
else
|
||||
table[key] = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
return table
|
||||
end
|
||||
local str = character:write_string()
|
||||
local read = b3d.read(text.inputstream(str))
|
||||
assert(modlib.table.equals_noncircular(character, read))
|
||||
file.write(mod.get_resource"character.b3d.lua", "return " .. dump(_b3d_truncate(table.copy(character))))
|
||||
end
|
||||
local vector, minetest, ml_mt = _G.vector, _G.minetest, minetest
|
||||
if tests.liquid_dir then
|
||||
minetest.register_abm{
|
||||
label = "get_liquid_corner_levels & get_liquid_direction test",
|
||||
nodenames = {"group:liquid"},
|
||||
interval = 1,
|
||||
chance = 1,
|
||||
action = function(pos, node)
|
||||
assert(type(node) == "table")
|
||||
for _, corner_level in pairs(ml_mt.get_liquid_corner_levels(pos, node)) do
|
||||
minetest.add_particle{
|
||||
pos = vector.add(pos, corner_level),
|
||||
size = 2,
|
||||
texture = "logo.png"
|
||||
}
|
||||
end
|
||||
local direction = ml_mt.get_liquid_flow_direction(pos, node)
|
||||
local start_pos = pos
|
||||
start_pos.y = start_pos.y + 1
|
||||
for i = 0, 5 do
|
||||
minetest.add_particle{
|
||||
pos = vector.add(start_pos, vector.multiply(direction, i/5)),
|
||||
size = i/2.5,
|
||||
texture = "logo.png"
|
||||
}
|
||||
end
|
||||
end
|
||||
}
|
||||
end
|
||||
if tests.liquid_raycast then
|
||||
minetest.register_globalstep(function()
|
||||
for _, player in pairs(minetest.get_connected_players()) do
|
||||
local eye_pos = vector.offset(player:get_pos(), 0, player:get_properties().eye_height, 0)
|
||||
local raycast = ml_mt.raycast(eye_pos, vector.add(eye_pos, vector.multiply(player:get_look_dir(), 3)), false, true)
|
||||
for pointed_thing in raycast do
|
||||
if pointed_thing.type == "node" and minetest.registered_nodes[minetest.get_node(pointed_thing.under).name].liquidtype == "flowing" then
|
||||
minetest.add_particle{
|
||||
pos = vector.add(pointed_thing.intersection_point, vector.multiply(pointed_thing.intersection_normal, 0.1)),
|
||||
size = 0.5,
|
||||
texture = "object_marker_red.png",
|
||||
expirationtime = 3
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
end)
|
||||
end
|
Loading…
Reference in New Issue
Block a user