Files
gen1recomp/tests/drivers/online_walkthrough.lua
T
2026-08-28 10:14:44 -04:00

241 lines
8.0 KiB
Lua

-- Drives the ONLINE tab's own screen stack against a real ../pokeserver:
-- connect, host from the Play screen, a second seat joins, both ready, the
-- arena spec is built and handed to playArena, and the panel is back on the
-- Room screen with the match result.
--
-- POKEPORT_RELAY_ADDR=127.0.0.1:17778 POKEPORT_TOUCH=0 \
-- POKEPORT_DRIVER=tests/drivers/online_walkthrough.lua love .
return function()
local U = dofile("tests/drivers/util.lua")
local PORT = tonumber(os.getenv("POKEPORT_LINK_PORT") or "") or 17778
local SERVER = os.getenv("POKESERVER_DIR") or "../pokeserver"
local failures = 0
local function check(cond, msg)
if cond then
U.log("ok ", msg)
else
failures = failures + 1
U.log("FAIL", msg)
end
end
local probe = io.open(SERVER .. "/server.js", "r")
if not probe then
U.log("FAIL", SERVER .. "/server.js is not checked out")
love.event.quit(1)
return
end
probe:close()
local pidFile = "/tmp/pokeserver_walk_" .. PORT .. ".pid"
os.execute(("(cd %q && PORT=%d HTTP_PORT=%d node server.js " ..
">/tmp/pokeserver_walk.log 2>&1 & echo $! > %q)")
:format(SERVER, PORT, PORT + 1, pidFile))
local function stopServer()
local handle = io.open(pidFile, "r")
if handle then
local pid = handle:read("*l")
handle:close()
if pid and pid ~= "" then os.execute("kill " .. pid .. " >/dev/null 2>&1") end
end
os.remove(pidFile)
end
local function finish()
stopServer()
U.log(failures == 0 and "online walkthrough passed"
or (failures .. " online walkthrough check(s) failed"))
love.event.quit(failures == 0 and 0 or 1)
while true do coroutine.yield() end
end
local Host = require("src.online.Client")
Host.reset()
Host.configure({ relayAddress = "127.0.0.1:" .. PORT })
package.loaded["src.online.Client"] = nil
local Guest = require("src.online.Client")
package.loaded["src.online.Client"] = Host
Guest.reset()
Guest.configure({ relayAddress = "127.0.0.1:" .. PORT })
local Net = require("src.link.Net")
local function reachable()
local socketProbe = Net.new()
if not socketProbe:connectTCP("127.0.0.1:" .. PORT) then return false end
for _ = 1, 30 do
socketProbe:update()
if socketProbe.closed then
pcall(function() socketProbe:close() end)
return false
end
if not socketProbe.connecting then
pcall(function() socketProbe:close() end)
return true
end
end
pcall(function() socketProbe:close() end)
return false
end
local up = false
for _ = 1, 300 do
up = reachable()
if up then break end
U.wait(2)
end
if not up then
check(false, "the spawned pokeserver answered on 127.0.0.1:" .. PORT)
return finish()
end
U.log("pokeserver up on", PORT)
local OnlinePanel = require("src.import.OnlinePanel")
local SaveData = require("src.core.SaveData")
local TeamPick = require("src.online.TeamPick")
local GameVersion = require("src.core.GameVersion")
local version
for _, id in ipairs(GameVersion.ORDER) do
if GameVersion.generation(id) == 1 then
local ok, rows = pcall(SaveData.listSlots, id)
if ok and type(rows) == "table" then
for _, row in ipairs(rows) do
if row.exists then version = version or id end
end
end
end
if version then break end
end
if not version then
check(false, "a Gen 1 save is imported for the walkthrough")
return finish()
end
local slots = SaveData.listSlots(version)
local slotId
for _, row in ipairs(slots) do
if row.exists and not slotId then slotId = row.id end
end
local booted = nil
local imp = {
ready = { [version] = true },
activeSlot = {}, slots = { [version] = slots }, carts = {}, pulse = 0,
_pages = {}, _uiActions = {}, _actAt = {},
playArena = function(_, bootVersion, cartId, spec)
booted = { version = bootVersion, cartId = cartId, spec = spec }
return true
end,
}
local st = OnlinePanel.state(imp)
st.version, st.slotId = version, slotId
st.team = { { where = "party", index = 1 } }
local function pump(n)
for _ = 1, n or 1 do
Guest.update(1 / 60)
OnlinePanel.update(imp, 1 / 60)
coroutine.yield()
end
end
local function waitFor(fn, frames, what)
for _ = 1, frames or 900 do
if fn() then return true end
pump(1)
end
check(false, "timed out waiting for " .. tostring(what))
return false
end
waitFor(function() return OnlinePanel.myProfile(imp) ~= nil end, 300,
"the arena profile to compute")
local profile = OnlinePanel.myProfile(imp)
check(profile ~= nil, "the panel computes its own arena profile")
if not profile then return finish() end
OnlinePanel.connect(imp)
Guest.connect({ name = "BLUE#221", profiles = { profile } })
waitFor(function()
return Host.state() == "online" and Guest.state() == "online"
end, 900, "both seats to come online")
check(Host.state() == "online", "the panel's own client is online")
OnlinePanel.home(imp)
OnlinePanel.go(imp, "play")
check(OnlinePanel.screen(imp) == "play", "Home walks to the Play screen")
check(OnlinePanel.startWizard(imp, "hostBattle"),
"Host a battle opens the wizard")
check(OnlinePanel.screen(imp) == "wizard", "on its own screen")
local steps = OnlinePanel.wizardSteps(imp)
check(table.concat(steps, ",") == "game,save,team,rules,visibility,summary",
"one step per choice: " .. table.concat(steps, ","))
OnlinePanel.wizardTo(imp, "rules")
check(OnlinePanel.ruleFor(imp).partySize == 1,
"the rule defaults to the number of POKeMON picked")
OnlinePanel.wizardTo(imp, "summary")
check(OnlinePanel.wizardNext(imp),
"the summary confirm hosts the room: " .. tostring(st.status))
waitFor(function() return Host.room() ~= nil end, 900, "the room to open")
pump(2)
local room = Host.room()
check(room ~= nil and room.code ~= nil, "hosting opens a room")
if not room then return finish() end
check(OnlinePanel.screen(imp) == "room",
"the panel routes itself onto the Room screen: "
.. tostring(OnlinePanel.screen(imp)))
check(OnlinePanel.back(imp) and OnlinePanel.screen(imp) == "play",
"Back walks out of the Room without leaving it")
OnlinePanel.refresh(imp)
local mine = OnlinePanel.cache(imp).mine
check(mine ~= nil and mine.code == room.code,
"Play shows the player's own lobby as a card, not a joinable row")
for _, entry in ipairs(OnlinePanel.cache(imp).rooms) do
check(entry.code ~= room.code, "and never as a row you can join")
end
OnlinePanel.go(imp, "room")
local joined = Guest.joinRoom(room.code, "player", profile)
waitFor(function() return joined.done end, 900, "the guest's join to answer")
check(joined.error == nil, "the second seat joins: " .. tostring(joined.error))
waitFor(function()
local live = Host.room()
return live and #live.players == 2
end, 900, "the room to hold two players")
local pick = TeamPick.readSlot(version, slotId)
local packed = TeamPick.pack(pick, { 1 }, pick.generation)
local sent = OnlinePanel.sendReady(imp)
check(sent, "READY packs the chosen team: " .. tostring(st.status))
Guest.ready(packed, "guest")
waitFor(function() return booted ~= nil end, 900, "the arena to boot")
check(booted ~= nil, "match_start hands an arena spec to playArena")
if booted then
check(booted.spec.role == "host", "the panel boots as the host seat")
check(booted.spec.session ~= nil, "over the room's own session")
check(booted.spec.team ~= nil and #booted.spec.team == 1,
"carrying the team the wizard picked")
check(booted.spec.team[1] == 1, "as a party index ArenaBoot understands")
end
OnlinePanel.recordResult("win")
st.routeKey = nil
OnlinePanel.update(imp, 1 / 60)
check(OnlinePanel.lastResult == "win", "the arena's result survives")
check(OnlinePanel.screen(imp) == "room",
"and the tab is back on the Room screen: "
.. tostring(OnlinePanel.screen(imp)))
Guest.disconnect()
Host.disconnect()
pump(5)
return finish()
end