6 local vecw = function(axis, n, base)
11 local pos2str = minetest.pos_to_string
12 local get_node = minetest.get_node
13 local set_node = minetest.set_node
22 -- Loads nodenames to use for tests
23 local function init_nodes()
24 testnode1 = minetest.registered_aliases["mapgen_stone"]
25 testnode2 = minetest.registered_aliases["mapgen_dirt"]
26 testnode3 = minetest.registered_aliases["mapgen_cobble"] or minetest.registered_aliases["mapgen_dirt_with_grass"]
27 assert(testnode1 and testnode2 and testnode3)
29 -- Writes repeating pattern into given area
30 local function place_pattern(pos1, pos2, pattern)
32 local node = {name=""}
34 for z = pos1.z, pos2.z do
36 for y = pos1.y, pos2.y do
38 for x = pos1.x, pos2.x do
40 node.name = pattern[i]
52 assert(minetest.get_mapgen_setting("mg_name") == "singlenode")
55 local areamin, areamax
57 local c_air = minetest.get_content_id(air)
59 -- Assign a new area for use, will emerge and then call ready()
60 area.assign = function(min, max, ready)
63 minetest.emerge_area(min, max, function(bpos, action, remaining)
64 assert(action ~= minetest.EMERGE_ERRORED)
65 if remaining > 0 then return end
66 minetest.after(0, function()
72 -- Reset area contents and state
73 area.clear = function()
74 local vmanip = minetest.get_voxel_manip(areamin, areamax)
75 local vpos1, vpos2 = vmanip:get_emerged_area()
76 local vcount = (vpos2.x - vpos1.x + 1) * (vpos2.y - vpos1.y + 1) * (vpos2.z - vpos1.z + 1)
77 if #vbuffer ~= vcount then
83 vmanip:set_data(vbuffer)
87 -- Returns an usable area [pos1, pos2] that does not overlap previous ones
88 area.get = function(sizex, sizey, sizez)
90 if sizey == nil or sizez == nil then
91 size = {x=sizex, y=sizex, z=sizex}
93 size = {x=sizex, y=sizey, z=sizez}
95 local pos1 = vector.add(areamin, off)
96 local pos2 = vector.subtract(vector.add(pos1, size), 1)
97 if pos2.x > areamax.x or pos2.y > areamax.y or pos2.z > areamax.z then
98 error("Internal failure: out of space")
100 off = vector.add(off, size)
103 -- Returns an axis and count (= n) relative to the last-requested area that is unoccupied
104 area.dir = function(n)
105 local pos1 = vector.add(areamin, off)
106 if pos1.x + n <= areamax.x then
109 elseif pos1.x + n <= areamax.y then
112 elseif pos1.z + n <= areamax.z then
116 error("Internal failure: out of space")
118 -- Returns [XYZ] margin (list of pos pairs) of n around last-requested area
119 -- (may actually be larger but doesn't matter)
120 area.margin = function(n)
121 local pos1, pos2 = area.get(n)
123 { vec(areamin.x, areamin.y, pos1.z), pos2 }, -- X/Y
124 { vec(areamin.x, pos1.y, areamin.z), pos2 }, -- X/Z
125 { vec(pos1.x, areamin.y, areamin.z), pos2 }, -- Y/Z
129 -- Split an existing area into two non-overlapping [pos1, half1], [half2, pos2] parts; returns half1, half2
130 area.split = function(pos1, pos2)
132 if pos2.x - pos1.x >= 1 then
134 elseif pos2.y - pos1.y >= 1 then
136 elseif pos2.z - pos1.z >= 1 then
139 error("Internal failure: area too small to split")
141 local hspan = math.floor((pos2[axis] - pos1[axis] + 1) / 2)
142 local half1 = vecw(axis, pos1[axis] + hspan - 1, pos2)
143 local half2 = vecw(axis, pos1[axis] + hspan, pos2)
148 ---------------------
150 ---------------------
152 -- Check that all nodes in [pos1, pos2] are the node(s) specified
153 check.filled = function(pos1, pos2, nodes)
154 if type(nodes) == "string" then
157 local _, counts = minetest.find_nodes_in_area(pos1, pos2, nodes)
158 local total = worldedit.volume(pos1, pos2)
160 for _, n in pairs(counts) do
164 error((total - sum) .. " " .. table.concat(nodes, ",") .. " nodes missing in " ..
165 pos2str(pos1) .. " -> " .. pos2str(pos2))
168 -- Check that none of the nodes in [pos1, pos2] are the node(s) specified
169 check.not_filled = function(pos1, pos2, nodes)
170 if type(nodes) == "string" then
173 local _, counts = minetest.find_nodes_in_area(pos1, pos2, nodes)
174 for nodename, n in pairs(counts) do
176 error(counts[nodename] .. " " .. nodename .. " nodes found in " ..
177 pos2str(pos1) .. " -> " .. pos2str(pos2))
181 -- Check that all of the areas are only made of node(s) specified
182 check.filled2 = function(list, nodes)
183 for _, pos in ipairs(list) do
184 check.filled(pos[1], pos[2], nodes)
187 -- Check that none of the areas contain the node(s) specified
188 check.not_filled2 = function(list, nodes)
189 for _, pos in ipairs(list) do
190 check.not_filled(pos[1], pos[2], nodes)
193 -- Checks presence of a repeating pattern in [pos1, po2] (cf. place_pattern)
194 check.pattern = function(pos1, pos2, pattern)
197 for z = pos1.z, pos2.z do
199 for y = pos1.y, pos2.y do
201 for x = pos1.x, pos2.x do
203 local node = get_node(pos)
204 if node.name ~= pattern[i] then
205 error(pattern[i] .. " not found at " .. pos2str(pos) .. " (i=" .. i .. ")")
214 ---------------------
216 ---------------------
218 local function register_test(name, func, opts)
219 assert(type(name) == "string")
220 assert(func == nil or type(func) == "function")
224 opts = table.copy(opts)
228 table.insert(tests, opts)
231 -- register_test registers a test with a name and function
232 -- The function should return if the test passes or otherwise cause a Lua error
233 -- The basic structure is: get areas + do operations + check results
235 -- area.get must be used to retrieve areas that can be operated on (these will be cleared before each test)
236 -- check.filled / check.not_filled can be used to check the result
237 -- area.margin + check.filled2 is useful to make sure nodes weren't placed too far
238 -- place_pattern + check.pattern is useful to test ops that operate on existing data
241 register_test("Internal self-test")
242 register_test("is area loaded?", function()
243 local pos1, _ = area.get(1)
244 assert(get_node(pos1).name == "air")
247 register_test("area.split", function()
249 local pos1, pos2 = area.get(1, 1, i)
250 local half1, half2 = area.split(pos1, pos2)
251 assert(pos1.x == half1.x and pos1.y == half1.y)
252 assert(half1.x == half2.x and half1.y == half2.y)
253 assert(half1.z + 1 == half2.z)
255 assert((half1.z - pos1.z) == (pos2.z - half2.z)) -- divided equally
260 register_test("check.filled", function()
261 local pos1, pos2 = area.get(1, 2, 1)
262 set_node(pos1, {name=testnode1})
263 set_node(pos2, {name=testnode2})
264 check.filled(pos1, pos1, testnode1)
265 check.filled(pos1, pos2, {testnode1, testnode2})
266 check.not_filled(pos1, pos1, air)
267 check.not_filled(pos1, pos2, {air, testnode3})
270 register_test("pattern", function()
271 local pos1, pos2 = area.get(3, 2, 1)
272 local pattern = {testnode1, testnode3}
273 place_pattern(pos1, pos2, pattern)
274 assert(get_node(pos1).name == testnode1)
275 check.pattern(pos1, pos2, pattern)
279 register_test("Generic node manipulations")
280 register_test("worldedit.set", function()
281 local pos1, pos2 = area.get(10)
282 local m = area.margin(1)
284 worldedit.set(pos1, pos2, testnode1)
286 check.filled(pos1, pos2, testnode1)
287 check.filled2(m, air)
290 register_test("worldedit.set mix", function()
291 local pos1, pos2 = area.get(10)
292 local m = area.margin(1)
294 worldedit.set(pos1, pos2, {testnode1, testnode2})
296 check.filled(pos1, pos2, {testnode1, testnode2})
297 check.filled2(m, air)
300 register_test("worldedit.replace", function()
301 local pos1, pos2 = area.get(10)
302 local half1, half2 = area.split(pos1, pos2)
304 worldedit.set(pos1, half1, testnode1)
305 worldedit.set(half2, pos2, testnode2)
306 worldedit.replace(pos1, pos2, testnode1, testnode3)
308 check.not_filled(pos1, pos2, testnode1)
309 check.filled(pos1, half1, testnode3)
310 check.filled(half2, pos2, testnode2)
313 register_test("worldedit.replace inverse", function()
314 local pos1, pos2 = area.get(10)
315 local half1, half2 = area.split(pos1, pos2)
317 worldedit.set(pos1, half1, testnode1)
318 worldedit.set(half2, pos2, testnode2)
319 worldedit.replace(pos1, pos2, testnode1, testnode3, true)
321 check.filled(pos1, half1, testnode1)
322 check.filled(half2, pos2, testnode3)
325 -- FIXME?: this one looks overcomplicated
326 register_test("worldedit.copy", function()
327 local pos1, pos2 = area.get(4)
328 local axis, n = area.dir(2)
329 local m = area.margin(1)
332 -- create one slice with testnode1, one with testnode2
333 worldedit.set(pos1, vecw(axis, b + 1, pos2), testnode1)
334 worldedit.set(vecw(axis, b + 2, pos1), pos2, testnode2)
335 worldedit.copy(pos1, pos2, axis, n)
337 -- should have three slices now
338 check.filled(pos1, vecw(axis, b + 1, pos2), testnode1)
339 check.filled(vecw(axis, b + 2, pos1), pos2, testnode1)
340 check.filled(vecw(axis, b + 4, pos1), vector.add(pos2, vecw(axis, n)), testnode2)
341 check.filled2(m, "air")
344 register_test("worldedit.copy2", function()
345 local pos1, pos2 = area.get(6)
346 local m1 = area.margin(1)
347 local pos1_, pos2_ = area.get(6)
348 local m2 = area.margin(1)
350 local pattern = {testnode1, testnode2, testnode3, testnode1, testnode2}
351 place_pattern(pos1, pos2, pattern)
352 worldedit.copy2(pos1, pos2, vector.subtract(pos1_, pos1))
354 check.pattern(pos1, pos2, pattern)
355 check.pattern(pos1_, pos2_, pattern)
356 check.filled2(m1, "air")
357 check.filled2(m2, "air")
360 register_test("worldedit.move (overlap)", function()
361 local pos1, pos2 = area.get(7)
362 local axis, n = area.dir(2)
363 local m = area.margin(1)
365 local pattern = {testnode2, testnode1, testnode2, testnode3, testnode3}
366 place_pattern(pos1, pos2, pattern)
367 worldedit.move(pos1, pos2, axis, n)
369 check.filled(pos1, vecw(axis, pos1[axis] + n - 1, pos2), "air")
370 check.pattern(vecw(axis, pos1[axis] + n, pos1), vecw(axis, pos2[axis] + n, pos2), pattern)
371 check.filled2(m, "air")
374 register_test("worldedit.move", function()
375 local pos1, pos2 = area.get(10)
376 local axis, n = area.dir(10)
377 local m = area.margin(1)
379 local pattern = {testnode1, testnode3, testnode3, testnode2}
380 place_pattern(pos1, pos2, pattern)
381 worldedit.move(pos1, pos2, axis, n)
383 check.filled(pos1, pos2, "air")
384 check.pattern(vecw(axis, pos1[axis] + n, pos1), vecw(axis, pos2[axis] + n, pos2), pattern)
385 check.filled2(m, "air")
388 -- TODO: the rest (also testing param2 + metadata)
391 ---------------------
393 ---------------------
394 worldedit.run_tests = function()
396 local v = minetest.get_version()
397 print("Running " .. #tests .. " tests for WorldEdit " ..
398 worldedit.version_string .. " on " .. v.project .. " " .. (v.hash or v.string))
403 -- emerge area from (0,0,0) ~ (56,56,56) and keep it loaded
404 -- Note: making this area smaller speeds up tests
405 local wanted = vec(56, 56, 56)
406 for x = 0, math.floor(wanted.x/16) do
407 for y = 0, math.floor(wanted.y/16) do
408 for z = 0, math.floor(wanted.z/16) do
409 assert(minetest.forceload_block({x=x*16, y=y*16, z=z*16}, true))
413 area.assign(vec(0, 0, 0), wanted, function()
416 for _, test in ipairs(tests) do
417 if not test.func then
418 local s = "---- " .. test.name .. " "
419 print(s .. string.rep("-", 60 - #s))
424 local ok, err = pcall(test.func)
425 print(string.format("%-60s %s", test.name, ok and "pass" or "FAIL"))
433 print("Done, " .. failed .. " tests failed.")
435 io.close(io.open(minetest.get_worldpath() .. "/tests_ok", "w"))
437 minetest.request_shutdown()
441 -- for debug purposes
442 minetest.register_on_joinplayer(function(player)
443 minetest.set_player_privs(player:get_player_name(),
444 minetest.string_to_privs("fly,fast,noclip,basic_debug,debug,interact"))
446 minetest.register_on_punchnode(function(pos, node, puncher)
447 minetest.chat_send_player(puncher:get_player_name(), pos2str(pos))