mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-12 00:10:56 +02:00
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>
This commit is contained in:
@@ -64,6 +64,20 @@ function FsIo.luaFilesUnder(dir)
|
||||
return files
|
||||
end
|
||||
|
||||
-- full paths matching the shell's "prefix"* glob (e.g. save.lua.bak-*)
|
||||
function FsIo.globPrefix(prefix)
|
||||
local dir, base = tostring(prefix):match("^(.*[/\\])([^/\\]*)$")
|
||||
if not dir then dir, base = "", tostring(prefix) end
|
||||
if dir == "" then dir = "." end
|
||||
local matches = {}
|
||||
for _, name in ipairs(FsIo.listDir(dir)) do
|
||||
if name:sub(1, #base) == base then
|
||||
matches[#matches + 1] = dir .. name
|
||||
end
|
||||
end
|
||||
return matches
|
||||
end
|
||||
|
||||
-- existence probe that never shells out on Windows: directories do not
|
||||
-- open() there at all, and rename-self succeeds for anything that exists
|
||||
function FsIo.isDir(path)
|
||||
|
||||
@@ -190,15 +190,25 @@ Runtime.install(nullEvents, nullHooks)
|
||||
-- POKEPORT_DATA_DIR points Data:load at another dataset root; the fixture
|
||||
-- set is ROM-free, so this is what lets a runner boot with no data/generated
|
||||
local ffi = require("ffi")
|
||||
ffi.cdef([[
|
||||
int setenv(const char *name, const char *value, int overwrite);
|
||||
int unsetenv(const char *name);
|
||||
]])
|
||||
ffi.C.setenv("POKEPORT_DATA_DIR", "tests/fixture_data", 1)
|
||||
local setVar, unsetVar
|
||||
if ffi.os == "Windows" then
|
||||
-- msvcrt has no setenv/unsetenv; _putenv with an empty value unsets
|
||||
ffi.cdef([[ int _putenv(const char *envstring); ]])
|
||||
setVar = function(name, value) ffi.C._putenv(name .. "=" .. value) end
|
||||
unsetVar = function(name) ffi.C._putenv(name .. "=") end
|
||||
else
|
||||
ffi.cdef([[
|
||||
int setenv(const char *name, const char *value, int overwrite);
|
||||
int unsetenv(const char *name);
|
||||
]])
|
||||
setVar = function(name, value) ffi.C.setenv(name, value, 1) end
|
||||
unsetVar = function(name) ffi.C.unsetenv(name) end
|
||||
end
|
||||
setVar("POKEPORT_DATA_DIR", "tests/fixture_data")
|
||||
local Data = require("src.core.Data")
|
||||
local fixture = setmetatable({}, { __index = Data })
|
||||
local okLoad, loadErr = pcall(Data.load, fixture)
|
||||
ffi.C.unsetenv("POKEPORT_DATA_DIR")
|
||||
unsetVar("POKEPORT_DATA_DIR")
|
||||
check(okLoad, "Data:load honours POKEPORT_DATA_DIR (" .. tostring(loadErr) .. ")")
|
||||
check(okLoad and fixture.pokemon ~= nil and fixture.pokemon.FIXMON_A ~= nil,
|
||||
"the override serves the fixture dataset")
|
||||
|
||||
+29
-11
@@ -368,24 +368,38 @@ check(#empty.lines == 0, "empty report renders no rows")
|
||||
|
||||
-- ------- modkit CLI: scaffold -> validate green, lint gate red
|
||||
|
||||
local isWindows = package.config:sub(1, 1) == "\\"
|
||||
|
||||
-- luajit's pclose drops the exit status, so the shell reports it in-band
|
||||
local function run(command)
|
||||
local pipe = io.popen(command .. ' 2>&1; echo "EXIT:$?"')
|
||||
if isWindows then
|
||||
-- cmd expands %errorlevel% when the line is parsed, before the command
|
||||
-- runs; /v:on defers !errorlevel! to execution time
|
||||
command = 'cmd /v:on /c "' .. command .. ' 2>&1 & echo EXIT:!errorlevel!"'
|
||||
else
|
||||
command = command .. ' 2>&1; echo "EXIT:$?"'
|
||||
end
|
||||
local pipe = io.popen(command)
|
||||
local output = pipe:read("*a")
|
||||
pipe:close()
|
||||
local code = tonumber(output:match("EXIT:(%d+)%s*$")) or -1
|
||||
return output, code
|
||||
end
|
||||
|
||||
local python = "python3"
|
||||
local python = isWindows and "python" or "python3"
|
||||
local haveTools = run(python .. " --version")
|
||||
check(haveTools:find("Python 3", 1, true) ~= nil, "python3 available for modkit")
|
||||
|
||||
local tmp = os.tmpname()
|
||||
os.remove(tmp)
|
||||
local root = tmp .. "_modkit"
|
||||
check(os.execute(("mkdir -p %q"):format(root)) == 0
|
||||
or os.execute(("mkdir -p %q"):format(root)) == true, "scratch dir")
|
||||
-- Windows tmpname is already a full path under TEMP; forward slashes keep
|
||||
-- %q from doubling backslashes for cmd
|
||||
local root = (isWindows and tmp:gsub("\\", "/") or tmp) .. "_modkit"
|
||||
-- cmd mkdir creates parents on its own and treats -p as a directory name
|
||||
local mkdir = isWindows and "mkdir" or "mkdir -p"
|
||||
local rmdir = isWindows and "rmdir /s /q" or "rm -rf"
|
||||
check(os.execute((mkdir .. " %q"):format(root)) == 0
|
||||
or os.execute((mkdir .. " %q"):format(root)) == true, "scratch dir")
|
||||
|
||||
local out, code = run(("%s tools/modkit.py scaffold scaffy --dest %q")
|
||||
:format(python, root))
|
||||
@@ -433,7 +447,7 @@ if packed then packed:close() end
|
||||
|
||||
-- a bad mod trips the schema rule and the no-ROM-content gate
|
||||
local bad = root .. "/badmod"
|
||||
os.execute(("mkdir -p %q"):format(bad))
|
||||
os.execute((mkdir .. " %q"):format(bad))
|
||||
local function write(path, body)
|
||||
local handle = assert(io.open(path, "wb"))
|
||||
handle:write(body)
|
||||
@@ -470,7 +484,7 @@ check(io.open(root .. "/bad.modpkg", "rb") == nil, "no package written on refusa
|
||||
-- whether the machine has an imported dataset
|
||||
local function ruleMod(id, manifestExtra, body)
|
||||
local dir = root .. "/" .. id
|
||||
os.execute(("mkdir -p %q"):format(dir))
|
||||
os.execute((mkdir .. " %q"):format(dir))
|
||||
write(dir .. "/manifest.json",
|
||||
('{"id":"%s","name":"%s","version":"1.0.0","api":2,"entry":"main.lua"%s}')
|
||||
:format(id, id, manifestExtra or ""))
|
||||
@@ -625,7 +639,7 @@ if packed then packed:close() end
|
||||
-- MK305 diffs shipped tables against the imported dataset; fake one under
|
||||
-- a scratch repo root so the check exercises the same on ROM-less machines
|
||||
local fake = root .. "/fakerepo"
|
||||
os.execute(("mkdir -p %q %q"):format(
|
||||
os.execute((mkdir .. " %q %q"):format(
|
||||
fake .. "/data/generated", fake .. "/mods/dumper"))
|
||||
local rows = {}
|
||||
for index = 1, 12 do
|
||||
@@ -645,8 +659,12 @@ check(code ~= 0, "bulk data-table dump fails lint: " .. out)
|
||||
check(out:find("MK305", 1, true) ~= nil, "dump reported as MK305")
|
||||
|
||||
-- no interpreter means no verdict; the gate fails closed, never open
|
||||
out, code = run(("MODKIT_LUAJIT=%q %s tools/modkit.py --repo %q lint %q")
|
||||
:format(fake .. "/no-such-luajit", python, fake, fake .. "/mods/dumper"))
|
||||
local missingLuajit = isWindows
|
||||
and ('set "MODKIT_LUAJIT=%s" && %s tools/modkit.py --repo %q lint %q')
|
||||
:format(fake .. "/no-such-luajit", python, fake, fake .. "/mods/dumper")
|
||||
or ("MODKIT_LUAJIT=%q %s tools/modkit.py --repo %q lint %q")
|
||||
:format(fake .. "/no-such-luajit", python, fake, fake .. "/mods/dumper")
|
||||
out, code = run(missingLuajit)
|
||||
check(code ~= 0, "lint fails when luajit is missing")
|
||||
check(out:find("MK100", 1, true) ~= nil, "missing luajit reported as MK100")
|
||||
|
||||
@@ -656,7 +674,7 @@ out, code = run(("%s tools/modkit.py --repo %q lint %q")
|
||||
check(code == 0, "dump check without a dataset stays a warning")
|
||||
check(out:find("MK305 WARN", 1, true) ~= nil, "skipped dump check is reported")
|
||||
|
||||
os.execute(("rm -rf %q"):format(root))
|
||||
os.execute((rmdir .. " %q"):format(root))
|
||||
|
||||
-- ------- restore shared runtime state for the suites that follow
|
||||
|
||||
|
||||
@@ -56,15 +56,20 @@ do
|
||||
end
|
||||
|
||||
local SaveIO = require("SaveIO")
|
||||
local FsIo = require("tests.fs_io")
|
||||
|
||||
do
|
||||
local path = SaveIO.defaultPath()
|
||||
check(type(path) == "string" and #path > 0, "defaultPath nonempty")
|
||||
check(path:match("save%.lua$"), "defaultPath ends with save.lua")
|
||||
check(path:match("pokemon%-love2d"), "defaultPath uses game identity folder")
|
||||
local uname = io.popen and io.popen("uname -s 2>/dev/null")
|
||||
local sys = uname and uname:read("*l") or ""
|
||||
if uname then uname:close() end
|
||||
local sys = ""
|
||||
if package.config:sub(1, 1) ~= "\\" then
|
||||
-- no uname on Windows; the macOS-only check below just skips there
|
||||
local uname = io.popen("uname -s 2>/dev/null")
|
||||
sys = uname and uname:read("*l") or ""
|
||||
if uname then uname:close() end
|
||||
end
|
||||
if sys == "Darwin" then
|
||||
check(path:match("/LOVE/"), "defaultPath on macOS includes LOVE folder")
|
||||
end
|
||||
@@ -92,14 +97,7 @@ do
|
||||
loaded = assert(SaveIO.load(path))
|
||||
eq(loaded.money, 99, "second save money")
|
||||
|
||||
local bakFiles = {}
|
||||
local bakGlob = io.popen('ls -1 "' .. path .. '.bak-"* 2>/dev/null')
|
||||
if bakGlob then
|
||||
for line in bakGlob:lines() do
|
||||
bakFiles[#bakFiles + 1] = line
|
||||
end
|
||||
bakGlob:close()
|
||||
end
|
||||
local bakFiles = FsIo.globPrefix(path .. ".bak-")
|
||||
check(#bakFiles >= 1, "second save creates .bak-* sibling")
|
||||
if #bakFiles >= 1 then
|
||||
local bakData, berr = SaveIO.load(bakFiles[1])
|
||||
@@ -376,11 +374,7 @@ do
|
||||
|
||||
os.remove(a); os.remove(b)
|
||||
for _, path in ipairs({ a, b }) do
|
||||
local bak = io.popen('ls -1 "' .. path .. '.bak-"* 2>/dev/null')
|
||||
if bak then
|
||||
for line in bak:lines() do os.remove(line) end
|
||||
bak:close()
|
||||
end
|
||||
for _, bak in ipairs(FsIo.globPrefix(path .. ".bak-")) do os.remove(bak) end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
+2
-1
@@ -3178,12 +3178,13 @@ runSuites(orderedGlob("tests/parity_*.lua", {
|
||||
-- always been; scripts/test.sh also runs them directly.
|
||||
do
|
||||
local lua = (arg and arg[-1]) or "luajit"
|
||||
local devnull = package.config:sub(1, 1) == "\\" and "nul" or "/dev/null"
|
||||
for _, tier in ipairs({ "tests/run_content_red.lua",
|
||||
"tests/run_engine.lua", "tests/run_modkit.lua" }) do
|
||||
local handle = io.open(tier, "r")
|
||||
if handle then
|
||||
handle:close()
|
||||
local status = os.execute(("%q %s > /dev/null 2>&1"):format(lua, tier))
|
||||
local status = os.execute(("%q %s > " .. devnull .. " 2>&1"):format(lua, tier))
|
||||
check(status == 0 or status == true, tier:match("([^/]+)%.lua$") .. " tier")
|
||||
end
|
||||
end
|
||||
|
||||
@@ -88,7 +88,8 @@ love.filesystem = memfs({
|
||||
.. '"entry":"main.lua","dependencies":[],"api":2}',
|
||||
[MOD_ROOT .. "/main.lua"] = MOD_MAIN,
|
||||
})
|
||||
os.execute('mkdir -p "' .. MOD_ROOT .. '"')
|
||||
-- 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()
|
||||
@@ -144,7 +145,11 @@ local ok, err = pcall(function()
|
||||
end)
|
||||
|
||||
os.remove(MOD_ROOT .. "/main.lua")
|
||||
os.execute('rmdir "' .. MOD_ROOT .. '" 2>/dev/null')
|
||||
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
|
||||
|
||||
@@ -22,6 +22,17 @@ end
|
||||
function Catalog.scrapeEvents(scriptDir, headerPath, listFiles, extraDirs)
|
||||
listFiles = listFiles or function(dir)
|
||||
local out = {}
|
||||
if package.config:sub(1, 1) == "\\" then
|
||||
-- cmd has no ls; dir /b prints bare names, so re-attach the directory
|
||||
local p = io.popen(string.format('dir /b "%s\\*.lua" 2>nul', dir))
|
||||
if p then
|
||||
for line in p:lines() do
|
||||
if line ~= "" then table.insert(out, dir .. "/" .. line) end
|
||||
end
|
||||
p:close()
|
||||
end
|
||||
return out
|
||||
end
|
||||
local p = io.popen(string.format('ls "%s"/*.lua 2>/dev/null', dir))
|
||||
if p then
|
||||
for line in p:lines() do
|
||||
|
||||
Reference in New Issue
Block a user