Files
ReSkate/contrib/plugins/reskate-test/plugin/harness.lua
T
AyeZeeBB 373c141381 Plugins: skater.read, skater.teleport and world.raycast
- skater.read: reskate.skater.get() (position, heading, speed, vertical speed, airborne, air
  time) and position, heading, speed and on_ground on their own; nil without a skater. From the
  trainer's telemetry. The heading is radians, -pi to pi: 0 faces +Z, pi/2 faces +X, and a higher
  heading is turned to the skater's left (checked in game).
- skater.teleport: reskate.skater.teleport(at, heading?) through teleport_local_skater_heading;
  false and why when the host has turned teleporting off (as the console's tp), there is no
  skater, or it is asked more than twice a second. Positions must be finite and within 100 km.
- world.raycast: reskate.world.raycast(from, to) is where the line first meets the world's
  collision (local_surface_hit), or nil; 64 a tick, 1000 m at most.
- The host passes the game to plugins through Environment, so the tests fake a skater and flat
  ground. reskate-test has a skater module (skipped without a skater; the harness counts skips)
  and ptest tp; reskate-hostile has teleport-spam and raycast-flood.

Checked in game: teleporting, the two-a-second cap and the raycast cap. ptest skater's heading
check failed there (0 to 2 pi from the trainer) and is fixed, not yet rerun in game.
2026-10-19 12:34:18 -05:00

118 lines
3.9 KiB
Lua

-- The test runner: each file in checks/ is a module of checks, and `ptest` runs them. A check
-- passes when its function returns without an error, and is skipped when it returns h.skip(why)
-- (it needs something there is not now, such as a skater); a later check passes when it calls
-- done(true) in time. Only failures and the summary are printed, to stay under the 50 lines a second a plugin has.
local harness = {}
local modules, order = {}, {}
local running = nil
function harness.module(name)
if not modules[name] then
modules[name] = {}
table.insert(order, name)
end
local list = modules[name]
local m = {}
function m.check(title, fn)
table.insert(list, {title = title, fn = fn})
end
-- fn(done): call done(ok, why) within `within` ms of the run starting.
function m.later(title, within, fn)
table.insert(list, {title = title, fn = fn, within = within})
end
return m
end
-- What the call raised; an error when it was allowed.
function harness.refused(fn, ...)
local ok, err = pcall(fn, ...)
if ok then error("was allowed", 2) end
return tostring(err)
end
local skip_mark = {}
function harness.skip(why)
return {[skip_mark] = true, why = why}
end
function harness.names()
return table.clone(order)
end
function harness.has(name)
return modules[name] ~= nil
end
local function finish(run)
running = nil
local shown = 0
for _, line in run.failures do
shown += 1
if shown > 20 then
reskate.console.print("FAIL ... and " .. (#run.failures - 20) .. " more")
break
end
reskate.console.print("FAIL " .. line)
end
local parts = {}
for _, name in run.names do
local tally = run.per[name]
table.insert(parts, name .. " " .. tally.passed .. "/" .. tally.total)
end
local skipped = run.skipped > 0 and (", " .. run.skipped .. " skipped (" .. table.concat(run.skips, "; ") .. ")") or ""
reskate.console.print("ptest: " .. run.passed .. " passed, " .. run.failed .. " failed" .. skipped .. " (" .. table.concat(parts, ", ") .. ")")
end
local function record(run, name, title, ok, why)
local tally = run.per[name]
if ok then
run.passed += 1
tally.passed += 1
else
run.failed += 1
table.insert(run.failures, name .. ": " .. title .. ": " .. tostring(why or "failed"))
end
end
function harness.run(names)
if running then
reskate.console.print("ptest: a run is still going on")
return
end
local run = {names = names, passed = 0, failed = 0, skipped = 0, pending = 0, failures = {}, skips = {}, per = {}}
running = run
for _, name in names do
run.per[name] = {passed = 0, total = #modules[name]}
end
for _, name in names do
for _, item in modules[name] do
if item.within then
run.pending += 1
local finished = false
local function done(ok, why)
if finished then return end
finished = true
record(run, name, item.title, ok, why)
run.pending -= 1
if run.pending == 0 then finish(run) end
end
reskate.time.after(item.within, function() done(false, "did not finish within " .. item.within .. " ms") end)
local ok, err = pcall(item.fn, done)
if not ok then done(false, err) end
else
local ok, err = pcall(item.fn)
if ok and type(err) == "table" and err[skip_mark] then
run.skipped += 1
run.per[name].total -= 1
table.insert(run.skips, name .. ": " .. item.title .. ": " .. err.why)
else
record(run, name, item.title, ok, err)
end
end
end
end
if run.pending == 0 then finish(run) end
end
return harness