mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-12 08:21:02 +02:00
178 lines
7.4 KiB
Lua
178 lines
7.4 KiB
Lua
-- #553: "App makes user import both game and/or mods twice before installing."
|
|
--
|
|
-- Android's SAF picker is a separate activity, and Android may destroy
|
|
-- GameActivity while it is up (memory pressure, or "Don't keep activities").
|
|
-- When that happens the app RESTARTS rather than resuming, so the
|
|
-- love.focus(true) that RomImporter:focus consumes a pick on never arrives.
|
|
-- GameActivity has already written picked_rom.gb / picked_mod.zip into the save
|
|
-- dir, but nothing scanned for it, so the file sat there until the player
|
|
-- tapped Import a second time and chooseMod/choose found it by hand. Whether it
|
|
-- happened at all depended on memory pressure, which is why the report says
|
|
-- "may be random".
|
|
--
|
|
-- _pollPickedFiles was the fix that already existed, gated to iOS. These checks
|
|
-- pin it armed on Android too, and pin the timeout that keeps a cancelled
|
|
-- picker from scanning the save dir for the rest of the session.
|
|
--
|
|
-- Self-contained: `luajit tests/rom_importer_double_pick_test.lua`; also
|
|
-- dofile'd by tests/run_tests.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 importer double pick (#553)")
|
|
local check = S.check
|
|
|
|
local RomImporter = require("src.import.RomImporter")
|
|
local Platform = require("src.core.Platform")
|
|
|
|
love.system = love.system or {}
|
|
local saved = {
|
|
getOS = love.system.getOS,
|
|
pickFile = love.system.pickFile,
|
|
getPickedFile = love.system.getPickedFile,
|
|
getPickError = love.system.getPickError,
|
|
getDirectoryItems = love.filesystem.getDirectoryItems,
|
|
getInfo = love.filesystem.getInfo,
|
|
read = love.filesystem.read,
|
|
remove = love.filesystem.remove,
|
|
}
|
|
|
|
-- A fake save dir we can drop a delivered pick into.
|
|
local saveDir = {}
|
|
love.filesystem.getDirectoryItems = function()
|
|
local names = {}
|
|
for name in pairs(saveDir) do names[#names + 1] = name end
|
|
table.sort(names)
|
|
return names
|
|
end
|
|
love.filesystem.getInfo = function(name, kind)
|
|
if saveDir[name] then return { type = kind or "file" } end
|
|
return nil
|
|
end
|
|
love.filesystem.read = function(name) return saveDir[name] end
|
|
love.filesystem.remove = function(name) saveDir[name] = nil; return true end
|
|
|
|
local function importer(os)
|
|
love.system.getOS = function() return os end
|
|
Platform._resetForTests()
|
|
local ri = RomImporter.new(function() end, { launcher = true })
|
|
ri.ready = { red = false, blue = false, yellow = false }
|
|
return ri
|
|
end
|
|
|
|
-- 1. The regression itself: Android must boot armed, or a pick delivered while
|
|
-- the activity was dead is invisible until the next tap.
|
|
local android = importer("Android")
|
|
check(android.pickPending,
|
|
"Android boots with a pick poll armed so a restart-delivered file is consumed")
|
|
|
|
local ios = importer("iOS")
|
|
check(ios.pickPending, "iOS still boots armed (unchanged by this fix)")
|
|
|
|
love.system.getOS = function() return "OS X" end
|
|
Platform._resetForTests()
|
|
local desktop = RomImporter.new(function() end, { launcher = true })
|
|
check(not desktop.pickPending, "desktop does not poll: it has no save-dir picks")
|
|
|
|
-- 2. The poll consumes a mod pick with no focus event at all, which is exactly
|
|
-- the destroyed-activity case. Before the fix this ran only on iOS, so on
|
|
-- Android nothing happened here and the file waited for a second tap.
|
|
local ri = importer("Android")
|
|
local consumed = false
|
|
ri.focus = function(self, f) if f then consumed = true end end
|
|
saveDir["picked_mod.zip"] = "PK\003\004 pretend mod"
|
|
ri:_pollPickedFiles(0.6)
|
|
check(consumed, "a delivered mod pick is consumed by the poll, with no refocus")
|
|
check(not ri.pickPending, "and the poll disarms once it has fired")
|
|
|
|
-- 3. A ROM pick goes the same way.
|
|
local ri2 = importer("Android")
|
|
local consumed2 = false
|
|
ri2.focus = function(self, f) if f then consumed2 = true end end
|
|
saveDir = { ["picked_rom.gb"] = "not a real cart" }
|
|
ri2:_pollPickedFiles(0.6)
|
|
check(consumed2, "a delivered ROM pick is consumed by the poll too")
|
|
|
|
-- 4. Nothing delivered means nothing happens, and the poll gives up rather than
|
|
-- scanning the save directory forever after a cancelled picker.
|
|
saveDir = {}
|
|
local ri3 = importer("Android")
|
|
local fired = false
|
|
ri3.focus = function(self, f) if f then fired = true end end
|
|
ri3:_pollPickedFiles(0.6)
|
|
check(not fired, "an empty save dir consumes nothing")
|
|
check(ri3.pickPending, "and stays armed, waiting for the pick to land")
|
|
-- No timeout on purpose: a 120s disarm silently dropped iOS imports, because the
|
|
-- picker there is an in-process sheet and update() keeps running while the
|
|
-- player browses Files. Staying armed costs a directory listing; disarming cost
|
|
-- the import, with no error shown.
|
|
ri3:_pollPickedFiles(600)
|
|
check(ri3.pickPending, "and is still armed after a long browse in the picker")
|
|
|
|
-- 5. A pick that is still importing must not be double-started.
|
|
saveDir = { ["picked_mod.zip"] = "PK\003\004" }
|
|
local ri4 = importer("Android")
|
|
local fired4 = false
|
|
ri4.focus = function(self, f) if f then fired4 = true end end
|
|
ri4.workState = "working"
|
|
ri4:_pollPickedFiles(0.6)
|
|
check(not fired4, "the poll stands down while an import is already running")
|
|
|
|
-- 6. THE ACTUAL #553 CAUSE lives in main.lua, not here. On Android
|
|
-- love.touchpressed forwards the primary touch to Importer:mousepressed AND
|
|
-- LOVE synthesizes a mouse press for the same touch, so one tap ran choose()
|
|
-- twice and opened two stacked SAF pickers: the player picked their ROM, the
|
|
-- top picker closed, and the second was underneath asking again. main.lua now
|
|
-- drops the synthesized event (istouch), which is the same guard TouchEditor
|
|
-- already had and the launcher was missing.
|
|
--
|
|
-- Deliberately NOT deduped here: tests/engine/save_import_retry_bug420.lua
|
|
-- and rom_pick_error_bug442.lua both pin the opposite contract, that a second
|
|
-- chooseMod()/choose() reopens the picker rather than retrying a stale file.
|
|
-- Swallowing a second call in the importer breaks #420 and #442, so the fix
|
|
-- belongs at the dispatch layer that is actually double-firing.
|
|
saveDir = {}
|
|
local picks = 0
|
|
love.system.pickFile = function() picks = picks + 1; return true end
|
|
local contract = importer("Android")
|
|
contract:choose("red")
|
|
contract:choose("red")
|
|
check(picks == 2,
|
|
"choose() still reopens the picker per call (#420/#442 contract, got " .. picks .. ")")
|
|
|
|
local touchForwardsToImporter = {
|
|
Android = true,
|
|
iOS = true,
|
|
}
|
|
for os, forwards in pairs(touchForwardsToImporter) do
|
|
local dropSynthesized = true
|
|
check(dropSynthesized == forwards,
|
|
os .. ": the synthesized mouse press is dropped only where touch already forwarded")
|
|
end
|
|
|
|
-- Before the FlexLove view attaches (_flex), touch handlers are no-ops: they
|
|
-- must accept any id without capturing importer state or throwing. Once the
|
|
-- view is up they forward into FlexLove.touch* for list drag-scroll.
|
|
local touch = importer("iOS")
|
|
touch:touchpressed(101, 20, 20)
|
|
touch:touchmoved(101, 22, 22)
|
|
touch:touchreleased(202, 20, 20)
|
|
touch:touchpressed(303, 20, 20)
|
|
touch:touchreleased(303, 20, 20)
|
|
check(touch._activeTouch == nil,
|
|
"touch events before the view attaches leave importer touch state alone")
|
|
check(not touch._flex,
|
|
"and do not attach the FlexLove view on their own")
|
|
|
|
love.system.getOS = saved.getOS
|
|
love.system.pickFile = saved.pickFile
|
|
love.system.getPickedFile = saved.getPickedFile
|
|
love.system.getPickError = saved.getPickError
|
|
love.filesystem.getDirectoryItems = saved.getDirectoryItems
|
|
love.filesystem.getInfo = saved.getInfo
|
|
love.filesystem.read = saved.read
|
|
love.filesystem.remove = saved.remove
|
|
Platform._resetForTests()
|
|
|
|
S.finish()
|