158 lines
6.7 KiB
GDScript3
158 lines
6.7 KiB
GDScript3
|
|
extends SceneTree
|
||
|
|
|
||
|
|
# Headless test: run with
|
||
|
|
# godot --headless -s res://tests/test_setup_file_identity.gd
|
||
|
|
# A setup's id and its FILENAME are not the same thing. res://PlanetSetups/
|
||
|
|
# holds authored resources named however their author named them, and shipped
|
||
|
|
# content already contains a mismatch: ResearchLabSetup.tres holds setup_id
|
||
|
|
# "research_lab_1".
|
||
|
|
#
|
||
|
|
# The editor's save and delete both rebuilt the path from the id, which
|
||
|
|
# produced two bugs that looked unrelated:
|
||
|
|
# - deleting such a setup looked for a file that does not exist, refused,
|
||
|
|
# and reported nothing useful. It was always the same entry, so it read
|
||
|
|
# as "the first item in the list cannot be deleted"
|
||
|
|
# - saving such a setup wrote a SECOND file under the derived name and left
|
||
|
|
# the original in place, so the pool then held two setups claiming one id
|
||
|
|
# and the stale one could still be served
|
||
|
|
# A third bug hid behind those: rescan() re-read through Godot's resource
|
||
|
|
# CACHE, so even a correctly written file came back with its pre-save values.
|
||
|
|
# All three present identically to a player - "my change was not saved".
|
||
|
|
#
|
||
|
|
# Covers:
|
||
|
|
# A. shipped content really does contain an id/filename mismatch, so this is
|
||
|
|
# guarding a live case (checked read-only - see the note on isolation)
|
||
|
|
# B. saving a mismatched setup writes back to ITS file and creates no twin
|
||
|
|
# C. the edit is what the registry serves afterwards, cache and all
|
||
|
|
# D. such a setup can be deleted
|
||
|
|
# E. a brand-new setup still lands at PlanetSetups/<id>.tres
|
||
|
|
#
|
||
|
|
# ISOLATION: every mutation happens on a scratch file this test creates, never
|
||
|
|
# on shipped content. The suite runs tests in parallel against one project
|
||
|
|
# directory, and deleting a real setup mid-run - even briefly, even restored
|
||
|
|
# afterwards - would make some other test fail for reasons of its own.
|
||
|
|
|
||
|
|
const SCRATCH_FILE := "res://PlanetSetups/_identity_scratch_file.tres"
|
||
|
|
const SCRATCH_ID := "_identity_scratch_id"
|
||
|
|
const DERIVED_PATH := "res://PlanetSetups/_identity_scratch_id.tres"
|
||
|
|
const NEW_ID := "test_identity_new"
|
||
|
|
const NEW_PATH := "res://PlanetSetups/test_identity_new.tres"
|
||
|
|
|
||
|
|
var editor: Node
|
||
|
|
var elapsed: float = 0.0
|
||
|
|
var stage: int = 0
|
||
|
|
|
||
|
|
|
||
|
|
func _initialize() -> void:
|
||
|
|
_cleanup()
|
||
|
|
editor = load("res://PlanetSetupEditor.tscn").instantiate()
|
||
|
|
root.add_child(editor)
|
||
|
|
|
||
|
|
|
||
|
|
func _process(delta: float) -> bool:
|
||
|
|
elapsed += delta
|
||
|
|
if stage != 0:
|
||
|
|
return false
|
||
|
|
if elapsed < 0.15:
|
||
|
|
return false
|
||
|
|
stage = 1
|
||
|
|
|
||
|
|
# --- A. shipped content has a mismatch (read-only) ---------------------
|
||
|
|
var shipped_mismatch: String = ""
|
||
|
|
for setup in PlanetSetups.all():
|
||
|
|
var path: String = String(setup.resource_path)
|
||
|
|
if path != "" and String(setup.setup_id) != path.get_file().get_basename():
|
||
|
|
shipped_mismatch = "%s holds id '%s'" % [path.get_file(), setup.setup_id]
|
||
|
|
break
|
||
|
|
if shipped_mismatch == "":
|
||
|
|
print("note: no shipped setup currently has an id/filename mismatch; the scratch case below still covers the behaviour")
|
||
|
|
else:
|
||
|
|
print("shipped mismatch present: %s" % shipped_mismatch)
|
||
|
|
|
||
|
|
# A setup whose FILE NAME deliberately differs from its id.
|
||
|
|
var scratch: Resource = load("res://PlanetSetup.gd").new()
|
||
|
|
scratch.setup_id = SCRATCH_ID
|
||
|
|
scratch.display_name = "Identity Scratch"
|
||
|
|
scratch.surface_profile_id = "rocky"
|
||
|
|
scratch.capture_points = PackedVector2Array([Vector2(10.0, 20.0)])
|
||
|
|
scratch.research_point_reward = 1
|
||
|
|
if ResourceSaver.save(scratch, SCRATCH_FILE) != OK:
|
||
|
|
return _fail("could not write the scratch setup")
|
||
|
|
PlanetSetups.rescan()
|
||
|
|
if PlanetSetups.path_for_id(SCRATCH_ID) != SCRATCH_FILE:
|
||
|
|
return _fail("the registry resolved '%s' to %s, not the file it lives in (%s)" \
|
||
|
|
% [SCRATCH_ID, PlanetSetups.path_for_id(SCRATCH_ID), SCRATCH_FILE])
|
||
|
|
print("'%s' lives in %s - id and filename disagree" % [SCRATCH_ID, SCRATCH_FILE.get_file()])
|
||
|
|
|
||
|
|
# --- B/C. edit, save, and see the edit served -------------------------
|
||
|
|
if not editor.load_setup(SCRATCH_ID):
|
||
|
|
return _fail("the editor could not open '%s'" % SCRATCH_ID)
|
||
|
|
var before: int = int(editor.current_setup.research_point_reward)
|
||
|
|
editor._on_reward_pressed()
|
||
|
|
var chosen: int = int(editor.current_setup.research_point_reward)
|
||
|
|
if chosen == before:
|
||
|
|
return _fail("cycling the reward did not change it")
|
||
|
|
var outcome: Dictionary = editor.save_setup()
|
||
|
|
if not outcome.ok:
|
||
|
|
return _fail("saving failed: %s" % outcome.message)
|
||
|
|
if FileAccess.file_exists(ProjectSettings.globalize_path(DERIVED_PATH)):
|
||
|
|
return _fail("saving wrote a twin at %s instead of back to the file it was loaded from" % DERIVED_PATH)
|
||
|
|
|
||
|
|
PlanetSetups.rescan()
|
||
|
|
var served: Resource = PlanetSetups.find_by_id(SCRATCH_ID)
|
||
|
|
if served == null:
|
||
|
|
return _fail("'%s' vanished from the pool after saving" % SCRATCH_ID)
|
||
|
|
if int(served.research_point_reward) != chosen:
|
||
|
|
return _fail("saved reward %d but the pool still serves %d - the rescan read the resource cache rather than the file" \
|
||
|
|
% [chosen, int(served.research_point_reward)])
|
||
|
|
var duplicates: int = 0
|
||
|
|
for setup in PlanetSetups.all():
|
||
|
|
if String(setup.setup_id) == SCRATCH_ID:
|
||
|
|
duplicates += 1
|
||
|
|
if duplicates != 1:
|
||
|
|
return _fail("the pool holds %d setups all claiming id '%s'" % [duplicates, SCRATCH_ID])
|
||
|
|
print("reward %d -> %d saved back into %s, no twin, and the pool serves the edit" \
|
||
|
|
% [before, chosen, SCRATCH_FILE.get_file()])
|
||
|
|
|
||
|
|
# --- E. a NEW setup still lands under its id --------------------------
|
||
|
|
editor.new_setup(NEW_ID, "Identity New", "rocky")
|
||
|
|
editor.add_capture_point(Vector2(20.0, 30.0))
|
||
|
|
if not editor.save_setup().ok:
|
||
|
|
return _fail("saving a brand-new setup failed")
|
||
|
|
if not FileAccess.file_exists(ProjectSettings.globalize_path(NEW_PATH)):
|
||
|
|
return _fail("a new setup did not land at %s" % NEW_PATH)
|
||
|
|
PlanetSetups.rescan()
|
||
|
|
if not editor.delete_setup(NEW_ID).ok:
|
||
|
|
return _fail("could not delete the new setup")
|
||
|
|
print("a new setup still saves to PlanetSetups/<id>.tres, and deletes again")
|
||
|
|
|
||
|
|
# --- D. the mismatched setup can be deleted ---------------------------
|
||
|
|
PlanetSetups.rescan()
|
||
|
|
var deleted: Dictionary = editor.delete_setup(SCRATCH_ID)
|
||
|
|
if not deleted.ok:
|
||
|
|
return _fail("deleting '%s' failed: %s" % [SCRATCH_ID, deleted.message])
|
||
|
|
if FileAccess.file_exists(ProjectSettings.globalize_path(SCRATCH_FILE)):
|
||
|
|
return _fail("delete reported success but %s is still there" % SCRATCH_FILE)
|
||
|
|
print("a setup whose filename differs from its id can be deleted")
|
||
|
|
|
||
|
|
_cleanup()
|
||
|
|
print("TEST_SETUP_FILE_IDENTITY: PASS")
|
||
|
|
quit(0)
|
||
|
|
return true
|
||
|
|
|
||
|
|
|
||
|
|
func _cleanup() -> void:
|
||
|
|
for path in [SCRATCH_FILE, DERIVED_PATH, NEW_PATH]:
|
||
|
|
var absolute: String = ProjectSettings.globalize_path(path)
|
||
|
|
if FileAccess.file_exists(absolute):
|
||
|
|
DirAccess.remove_absolute(absolute)
|
||
|
|
if FileAccess.file_exists(absolute + ".uid"):
|
||
|
|
DirAccess.remove_absolute(absolute + ".uid")
|
||
|
|
PlanetSetups.rescan()
|
||
|
|
|
||
|
|
|
||
|
|
func _fail(message: String) -> bool:
|
||
|
|
_cleanup()
|
||
|
|
print("TEST_SETUP_FILE_IDENTITY: FAIL (%s)" % message)
|
||
|
|
quit(1)
|
||
|
|
return true
|