From 21e97ca329ec049ca1c9bcf803567b775fbfc210 Mon Sep 17 00:00:00 2001 From: Lars Mueller Date: Mon, 27 Jun 2022 21:19:24 +0200 Subject: [PATCH] Remove local tests in favor of a test repo --- Readme.md | 3 + init.lua | 4 - test.lua | 557 ------------------------------------------------------ 3 files changed, 3 insertions(+), 561 deletions(-) delete mode 100644 test.lua diff --git a/Readme.md b/Readme.md index c78586c..d95b342 100644 --- a/Readme.md +++ b/Readme.md @@ -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. diff --git a/init.lua b/init.lua index c386bdf..43a319a 100644 --- a/init.lua +++ b/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 \ No newline at end of file diff --git a/test.lua b/test.lua deleted file mode 100644 index 8ba60b7..0000000 --- a/test.lua +++ /dev/null @@ -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 = " & '\"" - 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 \ No newline at end of file