test: cover tests.cliPlayReplDrivesRealCommandsThroughRealStdin
This commit is contained in:
1 parent
9627ab970a
commit
281fe36e73
1 file changed
+173
+173
@@ -14315,6 +14315,179 @@ local function runWarrenCli(argv, stdinText)
|
||||
end
|
||||
|
||||
function tests.cliHeadlessAutoRunPrintsTheStatusBarAndExitsZero()
|
||||
local output, code = runWarrenCli({ "--seed", "42", "--auto", "20" })
|
||||
eq(code, 0, "a plain headless auto-run exits zero")
|
||||
ok(contains(output, "HP "), "the status bar's HP field reaches real stdout")
|
||||
ok(contains(output, "depth "), "the status bar's depth field reaches real stdout")
|
||||
ok(not contains(output, "traceback"), "a routine auto-run never prints a Lua traceback")
|
||||
end
|
||||
|
||||
function tests.cliPlayReplDrivesRealCommandsThroughRealStdin()
|
||||
local script = "status\nlook\nlearn cleave\nlearn cleave\nforge fire\nhelp\nquit\n"
|
||||
local output, code = runWarrenCli({ "--seed", "5", "--class", "fighter", "--play" }, script)
|
||||
eq(code, 0, "a scripted --play session exits zero")
|
||||
ok(contains(output, "you learn cleave"), "learn is wired through the real stdin loop")
|
||||
ok(contains(output, "you already know that"), "a repeated learn is rejected through the real stdin loop")
|
||||
ok(contains(output, "you see no threats nearby"), "look reaches the real command dispatcher")
|
||||
ok(contains(output, "cannot forge: not enough essence"),
|
||||
"forge's failure message reaches real stdout with a fresh fighter's zero essence")
|
||||
ok(contains(output, "commands: north/south/east/west"), "help lists the command vocabulary over real stdout")
|
||||
end
|
||||
|
||||
function tests.cliPlayLoopChargesATurnOnlyForCommandsThatActuallySucceed()
|
||||
local script = "wait\nasdf\nstatus\nquit\n"
|
||||
local output, code = runWarrenCli({ "--seed", "1", "--auto", "0", "--play" }, script)
|
||||
eq(code, 0, "a mixed valid/invalid --play script still exits zero")
|
||||
ok(contains(output, "unknown command: asdf"), "an unrecognized verb is reported over real stdout")
|
||||
ok(contains(output, "turn 1"), "one successful wait (and one rejected typo) leaves the turn counter at exactly 1")
|
||||
ok(not contains(output, "turn 2"), "the rejected typo never advances the turn counter a second time")
|
||||
end
|
||||
|
||||
function tests.cliSaveThenLoadRoundTripsThroughTheRealEntryPoint()
|
||||
local savePath = os.tmpname()
|
||||
local saveOut, saveCode = runWarrenCli({ "--seed", "9", "--auto", "15", "--save", savePath })
|
||||
eq(saveCode, 0, "--save exits zero")
|
||||
ok(contains(saveOut, "saved to"), "--save reports where it wrote the file")
|
||||
local saved = io.open(savePath, "r")
|
||||
ok(saved ~= nil, "the save file actually exists on disk after --save")
|
||||
if saved then
|
||||
ok(#saved:read("a") > 0, "the save file is not empty")
|
||||
saved:close()
|
||||
end
|
||||
local loadOut, loadCode = runWarrenCli({ "--load", savePath, "--auto", "0" })
|
||||
os.remove(savePath)
|
||||
eq(loadCode, 0, "--load exits zero")
|
||||
ok(contains(loadOut, "turn 15"), "the loaded run resumes at exactly the turn it was saved on")
|
||||
end
|
||||
|
||||
function tests.cliVerifyConfirmsADeterministicReplayThroughTheRealEntryPoint()
|
||||
local output, code = runWarrenCli({ "--seed", "7", "--auto", "30", "--verify" })
|
||||
eq(code, 0, "--verify exits zero")
|
||||
ok(contains(output, "replay verified: seed 7 reproduces this run"),
|
||||
"a meta-free run reports a verified replay over real stdout")
|
||||
end
|
||||
|
||||
function tests.cliChallengePresetPrintsAChallengeLineAndAVerdict()
|
||||
local output, code = runWarrenCli({ "--challenge", "3", "--auto", "40" })
|
||||
eq(code, 0, "--challenge exits zero")
|
||||
ok(contains(output, "challenge:"), "the challenge preset announces itself over real stdout")
|
||||
ok(contains(output, "challenge honored") or contains(output, "challenge broken"),
|
||||
"the challenge preset reaches a pass/fail verdict by the end of the run")
|
||||
end
|
||||
|
||||
function tests.cliReportShowsFreshProgressionStateForANewMetaFile()
|
||||
local metaPath = os.tmpname()
|
||||
os.remove(metaPath)
|
||||
local output, code = runWarrenCli({ "--meta", metaPath, "--report" })
|
||||
os.remove(metaPath)
|
||||
eq(code, 0, "--report against a brand-new --meta file exits zero")
|
||||
ok(contains(output, "renown 0"), "a fresh meta file reports zero renown")
|
||||
ok(contains(output, "streak:"), "the report includes the streak line")
|
||||
ok(contains(output, "compendium:"), "the report includes the compendium section")
|
||||
ok(contains(output, "leaderboard:"), "the report includes the leaderboard section")
|
||||
ok(contains(output, "perk vigor"), "the report lists purchasable perks")
|
||||
end
|
||||
|
||||
function tests.cliExportsRealLeaderboardAndCompendiumCsvFiles()
|
||||
local metaPath = os.tmpname()
|
||||
os.remove(metaPath)
|
||||
local csvPath = os.tmpname()
|
||||
local bestiaryPath = os.tmpname()
|
||||
local output, code = runWarrenCli({
|
||||
"--meta", metaPath, "--seed", "5", "--auto", "30",
|
||||
"--export-csv", csvPath, "--export-compendium", bestiaryPath,
|
||||
})
|
||||
eq(code, 0, "a run with both export flags exits zero")
|
||||
ok(contains(output, "leaderboard exported to"), "the CLI reports the leaderboard export path")
|
||||
ok(contains(output, "compendium exported to"), "the CLI reports the compendium export path")
|
||||
local csv = io.open(csvPath, "r")
|
||||
ok(csv ~= nil, "the leaderboard CSV file exists on disk")
|
||||
if csv then
|
||||
local header = csv:read("l")
|
||||
eq(header, "rank,score,className,depth,turn,won,seed", "the leaderboard CSV has the expected header row")
|
||||
csv:close()
|
||||
end
|
||||
local bestiary = io.open(bestiaryPath, "r")
|
||||
ok(bestiary ~= nil, "the compendium CSV file exists on disk")
|
||||
if bestiary then
|
||||
ok(#bestiary:read("a") > 0, "the compendium CSV is not empty")
|
||||
bestiary:close()
|
||||
end
|
||||
os.remove(metaPath)
|
||||
os.remove(csvPath)
|
||||
os.remove(bestiaryPath)
|
||||
end
|
||||
|
||||
function tests.cliLoadingAMissingSaveFileFailsLoudlyRatherThanSilently()
|
||||
local output, code = runWarrenCli({ "--load", "/tmp/warren-tests-nonexistent-save-xyz.sav" })
|
||||
eq(code, 1, "loading a save file that does not exist exits nonzero")
|
||||
ok(contains(output, "cannot load save"), "the failure names the save-loading step, not a generic crash")
|
||||
ok(contains(output, "missing"), "the failure explains that the file itself is what is missing")
|
||||
end
|
||||
|
||||
-- Deliberately loose on exact numbers (unlike the in-process Game:autoExplore
|
||||
-- tests above, this drives a real builder-generated level rather than a
|
||||
-- hand-built arena) - it only asserts the one invariant a real player relying
|
||||
-- on this over actual stdin needs to hold: a single typed "explore" advances
|
||||
-- the turn counter by more than the one turn any other command would.
|
||||
function tests.cliPlayReplExploreAdvancesSeveralTurnsInASingleTypedCommand()
|
||||
local script = "status\nexplore\nstatus\nquit\n"
|
||||
local output, code = runWarrenCli({ "--seed", "5", "--class", "fighter", "--play" }, script)
|
||||
eq(code, 0, "a scripted explore through --play exits zero")
|
||||
ok(not contains(output, "traceback"), "explore over real stdin never crashes")
|
||||
-- Every command re-renders the status bar (its own "turn %d" field) on top
|
||||
-- of whatever reply message it printed, so "turn %d" shows up several
|
||||
-- times per command rather than once per typed "status" - the turn number
|
||||
-- only ever increases, so comparing the very first occurrence (the
|
||||
-- opening render, before any command) against the very last (after
|
||||
-- everything, including "explore") is robust to exactly how many of those
|
||||
-- redundant re-renders there are.
|
||||
local turns = {}
|
||||
for turn in output:gmatch("turn (%d+)") do turns[#turns + 1] = tonumber(turn) end
|
||||
ok(#turns >= 2, "the turn counter appears in the transcript")
|
||||
ok(turns[#turns] > turns[1], "explore actually advances the turn counter through the real entry point")
|
||||
end
|
||||
|
||||
-- A freshly created character always starts at full hp (see entity.lua), so
|
||||
-- this exercises rest's refusal path rather than a multi-turn heal - the
|
||||
-- healing/interrupt logic itself is already covered turn-by-turn by the
|
||||
-- gameRestUntilHealed* tests above; what this proves instead is that the
|
||||
-- real --play REPL actually reaches Commands.apply's "rest" handler and
|
||||
-- reports Game:restUntilHealed's own refusal without spending a turn,
|
||||
-- through the real entry point rather than by requiring src/ modules.
|
||||
function tests.cliPlayReplRestRefusesWithoutSpendingATurnAtFullHealth()
|
||||
local script = "status\nrest\nstatus\nquit\n"
|
||||
local output, code = runWarrenCli({ "--seed", "5", "--class", "fighter", "--play" }, script)
|
||||
eq(code, 0, "a scripted rest through --play exits zero")
|
||||
ok(not contains(output, "traceback"), "rest over real stdin never crashes")
|
||||
ok(contains(output, "you are already at full health"), "the real entry point surfaces the refusal verbatim")
|
||||
ok(not contains(output, "turn 1"), "the refused rest never advances the turn counter past the opening render")
|
||||
end
|
||||
|
||||
-- Seed 1 with the fighter class was picked by hand (see how gameAutoExplore's
|
||||
-- own CLI test picked seed 5): "travel down" on this particular generated
|
||||
-- floor covers real ground before a monster comes into sight and interrupts
|
||||
-- it, exactly the multi-turn-then-interrupt shape gameTravelToStopsTheMoment
|
||||
-- AHostileComesIntoSightWithoutEngagingIt already proves turn-by-turn in
|
||||
-- isolation - this only proves the real --play entry point reaches it too.
|
||||
function tests.cliPlayReplTravelDownAdvancesSeveralTurnsInASingleTypedCommand()
|
||||
local script = "status\ntravel down\nstatus\nquit\n"
|
||||
local output, code = runWarrenCli({ "--seed", "1", "--class", "fighter", "--play" }, script)
|
||||
eq(code, 0, "a scripted travel through --play exits zero")
|
||||
ok(not contains(output, "traceback"), "travel over real stdin never crashes")
|
||||
ok(contains(output, "you stop traveling - a monster is in sight"),
|
||||
"the real entry point surfaces Game:travelTo's own interrupt verbatim")
|
||||
local turns = {}
|
||||
for turn in output:gmatch("turn (%d+)") do turns[#turns + 1] = tonumber(turn) end
|
||||
ok(#turns >= 2, "the turn counter appears in the transcript")
|
||||
ok(turns[#turns] > turns[1], "travel actually advances the turn counter through the real entry point")
|
||||
end
|
||||
|
||||
-- Moonbell is the sixth herb (see alchemy.lua), added purely to make
|
||||
-- confuse/blind/levitate brewable - all three were only ever obtainable as
|
||||
-- loot, a trap, or a spell before this, a gap flagged (and deliberately not
|
||||
-- closed, since the existing five-herb grid was already fully assigned)
|
||||
-- across several earlier sessions today. Its own pair concentrates its
|
||||
end
|
||||
|
||||
local order = {
|
||||
|
||||
Reference in new issue
Block a user