Files
gen1recomp/tests/save_editor_mod_tests.lua
johnjohto ff90062741 Make the T3 content tier run on Windows (#269)
* Make ROM-free test tiers actually run on Windows

Suite discovery, the extension-point catalog scan, mod test-dir pickup,
and the meta-coverage corpus all shelled out to ls/find/test -d, which do
not exist in cmd.exe. Every listing came back empty on Windows, so tiers
ran 0 suites and still reported ALL TESTS PASSED.

Add portable probes to tests/fs_io.lua (same Unix commands on
Linux/macOS; dir /b and a shell-free rename-self existence check on
Windows) and rewire the four call sites to them. 15/15 engine suites and
2/2 modkit suites now genuinely run and pass on Windows.

Fixes #266

* Make the T3 content tier run on Windows

The content tier had the same Unix-shell assumptions as the tier
discovery fixed in #267, one level down:

- run_tests.lua redirected to /dev/null when chaining tier runners
- mod_runtime_tests.lua called ffi setenv/unsetenv, which msvcrt lacks
  (_putenv with an empty value unsets)
- modkit_tests.lua captured exit codes with POSIX '; echo 0' (cmd
  needs /v:on and !errorlevel!), called python3 (python on Windows),
  and used mkdir -p / rm -rf; cmd mkdir makes parents on its own and
  read -p as a directory name
- the save-editor suites globbed save backups with ls
- tools/save-editor/Catalog.lua scraped mod flags with ls; this one
  affects the real editor on Windows, not just tests

With a ROM imported, run_tests.lua goes from 12 failures to the 3 that
track the missing audio.lua generation (#268), which fails on any OS
and needs a maintainer decision.

---------

Co-authored-by: johnjohto <johnjohto@users.noreply.github.com>
2026-07-27 10:04:35 -04:00

164 lines
6.0 KiB
Lua

-- Editor mod-awareness (15 testing): with a mod enabled, App.load merges
-- the mod set into Data before the catalogs build, so the species list
-- carries the mod's mon, the flag list carries its MOD_ flags scraped
-- from the mod root, and MonOps stops asserting on the modded species.
-- Runs in its own process (run_tests spawns it): App.load's loader must
-- be the first to merge vanilla records over the singleton Data, or the
-- engine's registrations collide with an earlier loader's merge.
-- Self-contained: own bootstrap, assert-based checks, error() on failure.
package.path = "./?.lua;./?/init.lua;./tools/save-editor/?.lua;"
.. "./tools/save-editor/panels/?.lua;" .. package.path
love = love or require("tests.love_stub")
local Runtime = require("src.mods.Runtime")
local Assets = require("src.render.Assets")
local SaveData = require("src.core.SaveData")
local Data = require("src.core.Data")
local function check(value, message)
assert(value, message)
end
-- the fs surface the editor's loader needs, backed by a flat table
local function memfs(files)
return {
read = function(path) return files[path] end,
getInfo = function(path)
if files[path] then return { type = "file" } end
local prefix = path .. "/"
for key in pairs(files) do
if key:sub(1, #prefix) == prefix then return { type = "directory" } end
end
return nil
end,
load = function(path)
if not files[path] then return nil, "no file: " .. path end
return (loadstring or load)(files[path], path)
end,
getDirectoryItems = function(path)
local seen, items = {}, {}
local prefix = path .. "/"
for key in pairs(files) do
if key:sub(1, #prefix) == prefix then
local child = key:sub(#prefix + 1):match("^[^/]+")
if child and not seen[child] then
seen[child] = true
items[#items + 1] = child
end
end
end
table.sort(items)
return items
end,
}
end
local MOD_ROOT = "mods/zz_editor_fixture"
local MOD_MAIN = [[
return function(mod)
local giftFlag = "MOD_EDITMON_GIFT"
mod.exports.giftFlag = giftFlag
mod.content.pokemon:register("EDITMON", {
id = "EDITMON", name = "EDITMON", dex = 152,
types = { "NORMAL" },
baseStats = { hp = 50, attack = 50, defense = 50, speed = 50, special = 50 },
catchRate = 45, baseExp = 100,
level1Moves = { "GROWL" },
growthRate = "MEDIUM_FAST",
learnset = { { level = 10, move = "TACKLE" } },
evolutions = {},
spriteFront = "editmon_front.png", spriteBack = "editmon_back.png",
frontSize = 5,
})
end
]]
-- the loader discovers the fixture through love.filesystem, while the
-- flag scrape lists the mod root with io; the disk copy carries no
-- manifest, so a crash that strands it leaves a directory the game ignores
local savedFS = love.filesystem
local savedEvents, savedHooks, savedErrors =
Runtime.events, Runtime.hooks, Runtime.errors
local savedBridge = Assets.loader
love.filesystem = memfs({
[MOD_ROOT .. "/manifest.json"] =
'{"id":"zz_editor_fixture","name":"zz_editor_fixture","version":"1.0.0",'
.. '"entry":"main.lua","dependencies":[],"api":2}',
[MOD_ROOT .. "/main.lua"] = MOD_MAIN,
})
-- plain mkdir: cmd reads "-p" as a directory name, and mods/ always exists
os.execute('mkdir "' .. MOD_ROOT .. '"')
local diskMain = assert(io.open(MOD_ROOT .. "/main.lua", "w"))
diskMain:write(MOD_MAIN)
diskMain:close()
local tmpPath = os.tmpname() .. "-editor-modaware.lua"
os.remove(tmpPath)
local ok, err = pcall(function()
local App = require("App")
local MonOps = require("MonOps")
local Growth = require("src.pokemon.Growth")
App.load(tmpPath)
local S = App.getState()
local loaded = S.mods:status().loaded
check(#loaded == 1 and loaded[1].id == "zz_editor_fixture",
"fixture mod loads through the editor's loader")
check(loaded[1].path == MOD_ROOT, "loaded mod reports its root path")
local hasSpecies = false
for _, id in ipairs(S.cat.species) do
if id == "EDITMON" then hasSpecies = true end
end
check(hasSpecies, "editor species catalog carries the mod's mon")
local hasModFlag, hasVanillaFlag = false, false
for _, name in ipairs(S.events) do
if name == "MOD_EDITMON_GIFT" then hasModFlag = true end
if name:match("^EVENT_") then hasVanillaFlag = true end
end
check(hasModFlag, "editor flag list carries the mod's MOD_ flag")
check(hasVanillaFlag, "vanilla EVENT_ flags still scraped beside the mod's")
-- MonOps reads the same merged Data the catalog was built from
check(Data.pokemon.EDITMON ~= nil, "merge landed in the Data table MonOps reads")
local mon = MonOps.create(Data, "PIDGEY", 10)
MonOps.setSpecies(Data, mon, "EDITMON")
check(mon.species == "EDITMON", "MonOps.setSpecies accepts the modded species")
check(mon.exp == Growth.expForLevel("MEDIUM_FAST", 10),
"setSpecies resyncs exp against the mod's growth curve")
check(mon.stats.hp > 0, "recalc computes stats from the mod's base stats")
MonOps.recalc(Data, mon)
-- and the game-side scrub agrees: a save holding the modded mon passes
-- clean instead of quarantining it
local probe = { player = { map = "PALLET_TOWN" },
party = { { species = "EDITMON", level = 10,
moves = { { id = "TACKLE", pp = 10 } } } } }
local report = SaveData.validate(probe, Data)
check(#report.lostMons == 0 and probe.party[1].species == "EDITMON",
"validate keeps the modded mon while the mod is enabled")
end)
os.remove(MOD_ROOT .. "/main.lua")
if package.config:sub(1, 1) == "\\" then
os.execute('rmdir "' .. MOD_ROOT .. '"')
else
os.execute('rmdir "' .. MOD_ROOT .. '" 2>/dev/null')
end
os.remove(tmpPath)
love.filesystem = savedFS
-- leave shared singletons the way we found them (the fixture merged one
-- record into Data.pokemon)
Data.pokemon.EDITMON = nil
Assets.loader = savedBridge
Assets.invalidate()
Runtime.install(savedEvents, savedHooks, savedErrors)
if not ok then error(err, 0) end
print("ok save editor mod awareness")