1
0
mirror of https://github.com/SquidDev-CC/CC-Tweaked synced 2024-09-28 15:08:47 +00:00

Add a overly-complex test harness for rednet

Allows us to run multiple "computers" in parallel and send messages
betwene them. I don't think this counts as another test framework, but
it's sure silly.
This commit is contained in:
Jonathan Coates 2021-09-26 16:45:23 +01:00
parent cf2bc667c1
commit eb61c5c5d7
No known key found for this signature in database
GPG Key ID: B9E431FF07C98D06
3 changed files with 233 additions and 1 deletions

View File

@ -97,7 +97,7 @@ public class ComputerTestDelegate
if( REPORT_PATH.delete() ) ComputerCraft.log.info( "Deleted previous coverage report." );
Terminal term = new Terminal( 78, 20 );
Terminal term = new Terminal( 80, 30 );
IWritableMount mount = new FileMount( new File( "test-files/mount" ), 10_000_000 );
// Remove any existing files

View File

@ -83,4 +83,90 @@ describe("The rednet library", function()
expect(rednet.lookup("a_protocol", "a_hostname")):eq(os.getComputerID())
end)
end)
describe("on a fake computers", function()
local fake_computer = require "support.fake_computer"
local function computer_with_rednet(id, fn, options)
local computer = fake_computer.make_computer(id, function(_ENV)
local fns = { _ENV.rednet.run }
if options and options.rep then
fns[#fns + 1] = function() _ENV.dofile("rom/programs/rednet/repeat.lua") end
end
if fn then
fns[#fns + 1] = function()
if options and options.open then
_ENV.rednet.open("back")
_ENV.os.queueEvent("x") _ENV.os.pullEvent("x")
end
return fn(_ENV.rednet, _ENV)
end
end
return parallel.waitForAny(table.unpack(fns))
end)
local modem = fake_computer.add_modem(computer, "back")
fake_computer.add_api(computer, "rom/apis/rednet.lua")
return computer, modem
end
it("opens and closes channels", function()
local id = math.random(256)
local computer = computer_with_rednet(id, function(rednet)
expect(rednet.isOpen()):eq(false)
rednet.open("back")
rednet.open("front")
expect(rednet.isOpen()):eq(true)
expect(rednet.isOpen("back")):eq(true)
expect(rednet.isOpen("front")):eq(true)
rednet.close("back")
expect(rednet.isOpen("back")):eq(false)
expect(rednet.isOpen("front")):eq(true)
expect(rednet.isOpen()):eq(true)
rednet.close()
expect(rednet.isOpen("back")):eq(false)
expect(rednet.isOpen("front")):eq(false)
expect(rednet.isOpen()):eq(false)
end)
fake_computer.add_modem(computer, "front")
fake_computer.run_all { computer }
end)
it("sends and receives rednet messages", function()
local computer_1, modem_1 = computer_with_rednet(1, function(rednet, _ENV)
rednet.send(2, "Hello")
end, { open = true })
local computer_2, modem_2 = computer_with_rednet(2, function(rednet)
local id, message = rednet.receive()
expect(id):eq(1)
expect(message):eq("Hello")
end, { open = true })
fake_computer.add_modem_edge(modem_1, modem_2)
fake_computer.run_all { computer_1, computer_2 }
end)
it("repeats messages between computers", function()
local computer_1, modem_1 = computer_with_rednet(1, function(rednet, _ENV)
rednet.send(3, "Hello")
end, { open = true })
local computer_2, modem_2 = computer_with_rednet(2, nil, { open = true, rep = true })
local computer_3, modem_3 = computer_with_rednet(3, function(rednet)
local id, message = rednet.receive()
expect(id):eq(1)
expect(message):eq("Hello")
end, { open = true })
fake_computer.add_modem_edge(modem_1, modem_2)
fake_computer.add_modem_edge(modem_2, modem_3)
fake_computer.run_all({ computer_1, computer_2, computer_3 }, { computer_1, computer_3 })
end)
end)
end)

View File

@ -0,0 +1,146 @@
local function keys(tbl)
local keys = {}
for k in pairs(tbl) do keys[#keys + 1] = k end
return keys
end
local safe_globals = {
"assert", "bit32", "coroutine", "debug", "error", "fs", "getmetatable", "io", "ipairs", "math", "next", "pairs",
"pcall", "print", "printError", "rawequal", "rawget", "rawlen", "rawset", "select", "setmetatable", "string",
"table", "term", "tonumber", "tostring", "type", "utf8", "xpcall",
}
--- Create a fake computer.
local function make_computer(id, fn)
local env = setmetatable({}, _G)
local peripherals = {}
local events = { { n = 1, env } }
local function queue_event(...) events[#events + 1] = table.pack(...) end
for _, k in pairs(safe_globals) do env[k] = _G[k] end
env.peripheral = {
getNames = function() return keys(peripherals) end,
isPresent = function(name) return peripherals[name] ~= nil end,
getType = function(name) return peripherals[name] and getmetatable(peripherals[name]).type end,
getMethods = function(name) return peripherals[name] and keys(peripherals[name]) end,
call = function(name, method, ...)
local p = peripherals[name]
if p then return p[method](...) end
return nil
end,
wrap = function(name) return peripherals[name] end,
}
env.os = {
getComputerID = function() return id end,
queueEvent = queue_event,
pullEventRaw = coroutine.yield,
pullEvent = function(filter)
local event_data = table.pack(coroutine.yield(filter))
if event_data[1] == "terminate" then error("Terminated", 0) end
return table.unpack(event_data, 1, event_data.n)
end,
startTimer = function() return 0 end,
clock = function() return 0 end,
}
env.dofile = function(path)
local fn, err = loadfile(path, nil, env)
if fn then return fn() else error(err, 2) end
end
local co = coroutine.create(fn)
local filter = nil
local function step()
while true do
if #events == 0 or coroutine.status(co) == "dead" then return false end
local ev = table.remove(events, 1)
if filter == nil or ev[1] == filter or ev[1] == "terminated" then
local ok, result = coroutine.resume(co, table.unpack(ev, 1, ev.n))
if not ok then
if type(result) == "table" and result.trace == nil then result.trace = debug.traceback(co) end
error(result, 0)
end
filter = result
return true
end
end
end
return { env = env, peripherals = peripherals, queue_event = queue_event, step = step, co = co }
end
--- Add a modem to a computer on a particular side
local function add_modem(owner, side)
local open, adjacent = {}, {}
local peripheral = setmetatable({
open = function(channel) open[channel] = true end,
close = function(channel) open[channel] = false end,
closeAll = function(channel) open = {} end,
isOpen = function(channel) return open[channel] == true end,
transmit = function(channel, reply_channel, payload)
for _, adjacent in pairs(adjacent) do
if adjacent.open[channel] then
adjacent.owner.queue_event("modem_message", adjacent.side, channel, reply_channel, payload, 123)
end
end
end,
}, { type = "modem" })
owner.peripherals[side] = peripheral
return { adjacent = adjacent, side = side, owner = owner, open = open }
end
local function add_modem_edge(modem1, modem2)
table.insert(modem1.adjacent, modem2)
table.insert(modem2.adjacent, modem1)
end
--- Load an API into the computer's environment.
local function add_api(computer, path)
local name = fs.getName(path)
if name:sub(-4) == ".lua" then name = name:sub(1, -5) end
local child_env = {}
setmetatable(child_env, { __index = computer.env })
assert(loadfile(path, nil, child_env))()
local api = {}
for k, v in pairs(child_env) do api[k] = v end
computer.env[name] = api
end
--- Step all computers forward by one event.
local function step_all(computers)
local any = false
for _, computer in pairs(computers) do
if computer.step() then any = true end
end
return any
end
--- Run all computers until their event queue is empty.
local function run_all(computers, require_done)
while step_all(computers) do end
if require_done ~= false then
if type(require_done) == "table" then
for _, v in ipairs(require_done) do require_done[v] = true end
end
for _, computer in pairs(computers) do
if coroutine.status(computer.co) ~= "dead" and (type(require_done) ~= "table" or require_done[computer]) then
error(debug.traceback(computer.co, "Computer did not shutdown"), 0)
end
end
end
end
return {
make_computer = make_computer,
add_modem = add_modem,
add_modem_edge = add_modem_edge,
add_api = add_api,
step_all = step_all,
run_all = run_all,
}