Files
gen1recomp/tests/rom_archive_import_test.lua
T

280 lines
9.2 KiB
Lua

package.path = "./?.lua;./?/init.lua;" .. package.path
if not _G.love then _G.love = require("tests.love_stub") end
local S = require("tests.harness").suite("rom import from archives")
local check, eq = S.check, S.eq
local RomArchive = require("src.import.RomArchive")
local RomImporter = require("src.import.RomImporter")
local KIND_7Z = "7z\188\175\39\28"
local zipProbe = RomArchive._zipProbeBytes()
local userArchive = "PK\3\4" .. "user-archive-body"
local sevenZFile = KIND_7Z .. "user-7z-body"
local goodBody = string.rep("R", 1048576)
local SUPPORT_Z7 = false
local userEntries = { ["Red.gb"] = goodBody }
local function makeVfs()
local vfs = {}
local arch = {}
local mounted = {}
local staged = {}
local function keyOf(archive)
if type(archive) == "table" then return archive end
return staged[archive] and archive or nil
end
function vfs.newFileData(data, name)
return { _fileData = true, data = data, name = name }
end
function vfs.write(path, data)
staged[path] = data
return true
end
function vfs.remove(path)
staged[path] = nil
return true
end
function vfs.mount(archive, point)
local data
if type(archive) == "table" then
data = archive.data
else
data = staged[archive]
end
if not data then return false end
local entries
if data == userArchive then
entries = userEntries
elseif data == sevenZFile then
entries = userEntries
elseif data == zipProbe then
entries = { probe = "ok" }
elseif RomArchive.kind(data) == "7z" then
if not SUPPORT_Z7 then return false end
entries = { ["p.romprobe"] = "gen1recomp-probe" }
else
return false
end
arch[point] = { dir = true }
for rel, body in pairs(entries) do
arch[point .. "/" .. rel] = { body = body }
end
mounted[keyOf(archive) or data] = point
return true
end
function vfs.unmount(archive)
local point = mounted[archive] or mounted[archive.data and archive or nil]
if not point then
for k, v in pairs(mounted) do
if k == archive or (type(k) == "table" and k.data == archive) then
point = v
end
end
end
if not point then return false end
for path in pairs(arch) do
if path == point or path:sub(1, #point + 1) == point .. "/" then
arch[path] = nil
end
end
mounted[archive] = nil
for k, v in pairs(mounted) do
if v == point then mounted[k] = nil end
end
return true
end
function vfs.getDirectoryItems(dir)
if not (arch[dir] and arch[dir].dir) then return {} end
local out = {}
for path in pairs(arch) do
if path ~= dir and path:sub(1, #dir + 1) == dir .. "/" then
local rest = path:sub(#dir + 2)
if not rest:find("/", 1, true) and not arch[path].dir then
out[#out + 1] = rest
end
end
end
table.sort(out)
return out
end
function vfs.getInfo(path)
local node = arch[path]
if node then
if node.dir then return { type = "directory" } end
return { type = "file", size = #node.body }
end
for other in pairs(arch) do
if other:sub(1, #path + 1) == path .. "/" then
return { type = "directory" }
end
end
return nil
end
function vfs.read(path)
local node = arch[path]
if node and node.body then return node.body end
return nil, "not found"
end
return vfs
end
local savedFs = love.filesystem
local vfs = makeVfs()
love.filesystem = vfs
RomArchive._resetForTests()
eq(RomArchive.kind("GBROM" .. string.rep("x", 20)), nil, "raw ROM is not an archive")
eq(RomArchive.kind("PK\3\4rest"), "zip", "PK\\3\\4 is a zip")
eq(RomArchive.kind("PK\5\6rest"), "zip", "empty zip is a zip")
eq(RomArchive.kind(KIND_7Z .. "rest"), "7z", "7z magic recognised")
eq(RomArchive.kind("PK"), nil, "truncated input is not an archive")
eq(RomArchive.kind(nil), nil, "non-string input is not an archive")
local caps = RomArchive.capabilities(vfs)
check(caps.zip == true, "zip probe mounts here")
check(caps.z7 == false, "7z probe refuses while SUPPORT_Z7 is off")
SUPPORT_Z7 = true
RomArchive._resetForTests()
caps = RomArchive.capabilities(vfs)
check(caps.z7 == true, "7z probe mounts once the platform supports it")
SUPPORT_Z7 = false
RomArchive._resetForTests()
check(RomArchive.capabilities(vfs).z7 == false, "caps are re-probed after reset")
userEntries = { ["._Red.gb"] = "APPLEDOUBLE", ["Red.gb"] = goodBody }
local bytes, entry = RomArchive.unwrap(userArchive, "pack.zip", { fs = vfs })
eq(bytes, goodBody, "cart bytes come out")
eq(entry, "Red.gb", "AppleDouble ._Red.gb is skipped, Red.gb wins")
check(RomArchive.unwrap("hello", "x.zip", { fs = vfs }) == nil, "not-an-archive refused")
userEntries = { ["A.gb"] = "AAA", ["Y.gb"] = "YYY" }
bytes, entry = RomArchive.unwrap(userArchive, "pack.zip", {
fs = vfs,
acceptedSize = function(n) return n >= 3 end,
prefer = function(b) return b:sub(1, 1) == "Y" end,
})
eq(entry, "Y.gb", "prefer() picks the known-version cart over sort order")
userEntries = { ["notes.txt"] = "text" }
local noRom, noRomErr = RomArchive.unwrap(userArchive, "pack.zip", { fs = vfs })
eq(noRom, nil, "an archive with no cart refuses")
check(tostring(noRomErr):find("no .gb/.gbc/.gba", 1, true) ~= nil,
"the no-cart message says so: " .. tostring(noRomErr))
userEntries = { ["Red.gb"] = string.rep("R", 2000) }
local wrongSize, wrongErr = RomArchive.unwrap(userArchive, "pack.zip", { fs = vfs })
eq(wrongSize, nil, "a wrong-size cart is never read (zip bomb guard)")
check(tostring(wrongErr):find("2000 bytes", 1, true) ~= nil,
"the wrong-size message lists sizes: " .. tostring(wrongErr))
userEntries = { ["Red.gb"] = goodBody }
local z7bytes, z7err = RomArchive.unwrap(sevenZFile, "cart.7z", { fs = vfs })
eq(z7bytes, nil, "7z refused while the platform lacks the archiver")
check(tostring(z7err):find("cannot open .7z", 1, true) ~= nil,
"the caps refusal names the format: " .. tostring(z7err))
SUPPORT_Z7 = true
RomArchive._resetForTests()
local z7ok, z7entry = RomArchive.unwrap(sevenZFile, "cart.7z", { fs = vfs })
eq(z7ok, goodBody, "7z opens once the platform supports it")
eq(z7entry, "Red.gb", "and yields its cart")
SUPPORT_Z7 = false
RomArchive._resetForTests()
love.data = love.data or {}
local savedData = { hash = love.data.hash, encode = love.data.encode }
love.data.hash = function(_, data)
return { tag = data:sub(1, 1) }
end
love.data.encode = function(_, _, digest)
if type(digest) == "table" and digest.tag == "R" then
return require("src.core.GameVersion").info("red").sha1
end
return "0000000000000000000000000000000000000000"
end
local function freshImporter(extra)
local ri = setmetatable({ tab = "red", workState = nil }, RomImporter)
for k, v in pairs(extra or {}) do ri[k] = v end
return ri
end
userEntries = { ["Red.gb"] = string.rep("R", 2000) }
local ri = freshImporter()
ri:startData(userArchive, "pack.zip")
eq(ri.workState, "error", "wrong-size cart in a zip lands in the error state")
check(tostring(ri.detail):find("2000 bytes", 1, true) ~= nil,
"the size filter runs before any read: " .. tostring(ri.detail))
userEntries = { ["Red.gb"] = string.rep("X", 1048576) }
ri = freshImporter()
ri:startData(userArchive, "pack.zip")
eq(ri.workState, "error", "unknown-sha cart in a zip errors too")
check(tostring(ri.detail):find("Unsupported ROM (SHA-1", 1, true) ~= nil,
"the SHA-1 gate runs on the unwrapped bytes: " .. tostring(ri.detail))
local realMax = RomArchive.MAX_ARCHIVE_BYTES
RomArchive.MAX_ARCHIVE_BYTES = 10
ri = freshImporter()
ri:startData(userArchive, "pack.zip")
RomArchive.MAX_ARCHIVE_BYTES = realMax
eq(ri.workState, "error", "an oversized archive is refused before mounting")
check(tostring(ri.detail):find(".zip", 1, true) ~= nil,
"the oversize message names the format: " .. tostring(ri.detail))
ri = freshImporter()
ri:startData(sevenZFile, "cart.7z")
eq(ri.workState, "error", "a 7z drop takes the same startData path")
check(tostring(ri.detail):find("cannot open .7z", 1, true) ~= nil,
"and is refused by caps: " .. tostring(ri.detail))
userEntries = { ["Red.gb"] = goodBody }
local function fakeFile(path, content)
return {
open = function() return true end,
read = function() return content end,
close = function() end,
getSize = function() return #content end,
getFilename = function() return path end,
}
end
ri = freshImporter({ tab = "red" })
local started
ri.startData = function(self, data, name, src)
started = { data = data, name = name, src = src }
end
ri:filedropped(fakeFile("/tmp/packs/cart-pack.zip", userArchive))
check(started ~= nil, "a game-tab zip holding a cart routes to startData")
if started then
eq(started.data, userArchive, "startData receives the archive payload (it unwraps)")
eq(started.name, "/tmp/packs/cart-pack.zip", "and the dropped path for display")
end
ri = freshImporter({ tab = "red" })
ri:filedropped(fakeFile("/tmp/cart.7z", sevenZFile))
eq(ri.workState, "error", "a dropped .7z reaches startData via the generic path")
check(tostring(ri.detail):find("cannot open .7z", 1, true) ~= nil,
"and is refused when the platform lacks 7z: " .. tostring(ri.detail))
love.filesystem = savedFs
love.data.hash = savedData.hash
love.data.encode = savedData.encode
RomArchive._resetForTests()
S.finish()