842213c574
SaveStore.write_save verschob die geprüfte Temporärdatei direkt auf world.grw. Godot löscht unter Windows ein vorhandenes Ziel, bevor es die Datei verschiebt. Scheiterte die Verschiebung, zum Beispiel weil ein Virenscanner world.grw.tmp festhielt, war der vorige Spielstand schon weg. Jetzt wird der vorige Stand zuerst nach world.grw.bak verschoben, dann rückt die Temporärdatei nach. Scheitert das, kommt der Stand aus der Sicherung zurück; nach Erfolg wird die Sicherung gelöscht. Bleibt nach einem Absturz zwischen den beiden Verschiebungen nur world.grw.bak übrig, holt SaveStore sie beim Öffnen des Ordners zurück. Neue Tests: ein offenes Handle auf world.grw.tmp, eine blockierte Sicherung, die Wiederherstellung aus der Sicherung und dass eine Sicherung einen vorhandenen Stand nie ersetzt. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
129 lines
6.0 KiB
GDScript
129 lines
6.0 KiB
GDScript
extends TestCase
|
|
|
|
|
|
func test_the_player_key_is_created_once_and_kept() -> void:
|
|
var directory := TestFiles.temporary_dir("key")
|
|
var first := SaveStore.new(directory).player_key()
|
|
var second := SaveStore.new(directory).player_key()
|
|
check_eq(first.size(), SaveStore.KEY_BYTES, "sixteen bytes")
|
|
check_eq(second, first, "the same key on the next start")
|
|
TestFiles.remove_dir(directory)
|
|
|
|
|
|
func test_a_save_is_written_and_read_back() -> void:
|
|
var directory := TestFiles.temporary_dir("save")
|
|
var store := SaveStore.new(directory)
|
|
check(not store.has_save(), "no save at first")
|
|
check(store.write_save(PackedByteArray([1, 2, 3])), "written")
|
|
check(store.write_save(PackedByteArray([4, 5])), "overwritten")
|
|
check(store.has_save(), "a save exists")
|
|
check_eq(store.read_save(), PackedByteArray([4, 5]), "the newest save is read")
|
|
check(not FileAccess.file_exists(directory.path_join(SaveStore.SAVE_FILE + ".tmp")), "no temporary file is left")
|
|
check(not FileAccess.file_exists(directory.path_join(SaveStore.BACKUP_FILE)), "no backup is left")
|
|
TestFiles.remove_dir(directory)
|
|
|
|
|
|
func test_an_empty_save_is_not_written() -> void:
|
|
var directory := TestFiles.temporary_dir("empty")
|
|
var store := SaveStore.new(directory)
|
|
check(not store.write_save(PackedByteArray()), "refused")
|
|
check(not store.has_save(), "no file")
|
|
TestFiles.remove_dir(directory)
|
|
|
|
|
|
func test_a_save_that_cannot_be_written_keeps_the_previous_one() -> void:
|
|
var directory := TestFiles.temporary_dir("blocked")
|
|
var store := SaveStore.new(directory)
|
|
store.write_save(PackedByteArray([1, 2, 3]))
|
|
# A folder in place of the temporary file makes the write fail.
|
|
DirAccess.make_dir_absolute(directory.path_join(SaveStore.SAVE_FILE + ".tmp"))
|
|
check(not store.write_save(PackedByteArray([4, 5])), "the failed save is reported")
|
|
check_eq(store.read_save(), PackedByteArray([1, 2, 3]), "the previous save is untouched")
|
|
check(not store.write_save(PackedByteArray()), "an empty save is refused")
|
|
check_eq(store.read_save(), PackedByteArray([1, 2, 3]), "still untouched")
|
|
TestFiles.remove_dir(directory)
|
|
|
|
|
|
func test_a_save_that_cannot_be_moved_into_place_keeps_the_previous_one() -> void:
|
|
var directory := TestFiles.temporary_dir("held")
|
|
var store := SaveStore.new(directory)
|
|
store.write_save(PackedByteArray([1, 2, 3]))
|
|
# The open handle stands for an outside program, for example a virus
|
|
# scanner. The new save can be written, but Windows refuses to move a file
|
|
# that is held open.
|
|
var holder := FileAccess.open(directory.path_join(SaveStore.SAVE_FILE + ".tmp"), FileAccess.WRITE)
|
|
check(not store.write_save(PackedByteArray([4, 5])), "the failed save is reported")
|
|
check_eq(store.read_save(), PackedByteArray([1, 2, 3]), "the previous save is untouched")
|
|
check(not FileAccess.file_exists(directory.path_join(SaveStore.BACKUP_FILE)), "no backup is left")
|
|
holder.close()
|
|
TestFiles.remove_dir(directory)
|
|
|
|
|
|
func test_a_save_that_cannot_move_the_previous_one_aside_keeps_it() -> void:
|
|
var directory := TestFiles.temporary_dir("aside-blocked")
|
|
var store := SaveStore.new(directory)
|
|
store.write_save(PackedByteArray([1, 2, 3]))
|
|
# A folder under the backup name keeps the previous save from moving.
|
|
DirAccess.make_dir_absolute(directory.path_join(SaveStore.BACKUP_FILE))
|
|
check(not store.write_save(PackedByteArray([4, 5])), "the failed save is reported")
|
|
check_eq(store.read_save(), PackedByteArray([1, 2, 3]), "the previous save is untouched")
|
|
check(not FileAccess.file_exists(directory.path_join(SaveStore.SAVE_FILE + ".tmp")), "no temporary file is left")
|
|
TestFiles.remove_dir(directory)
|
|
|
|
|
|
func test_an_interrupted_save_is_recovered_from_the_backup() -> void:
|
|
var directory := TestFiles.temporary_dir("recover")
|
|
# A crash between the two moves of write_save leaves only the backup.
|
|
var backup := directory.path_join(SaveStore.BACKUP_FILE)
|
|
var file := FileAccess.open(backup, FileAccess.WRITE)
|
|
file.store_buffer(PackedByteArray([7, 8, 9]))
|
|
file.close()
|
|
var store := SaveStore.new(directory)
|
|
check(store.has_save(), "the previous save is back")
|
|
check_eq(store.read_save(), PackedByteArray([7, 8, 9]), "with its content")
|
|
check(not FileAccess.file_exists(backup), "the backup is moved, not copied")
|
|
TestFiles.remove_dir(directory)
|
|
|
|
|
|
func test_a_backup_never_replaces_an_existing_save() -> void:
|
|
var directory := TestFiles.temporary_dir("keep")
|
|
SaveStore.new(directory).write_save(PackedByteArray([1, 2, 3]))
|
|
var file := FileAccess.open(directory.path_join(SaveStore.BACKUP_FILE), FileAccess.WRITE)
|
|
file.store_buffer(PackedByteArray([9, 9]))
|
|
file.close()
|
|
var store := SaveStore.new(directory)
|
|
check_eq(store.read_save(), PackedByteArray([1, 2, 3]), "the save is not replaced by the older backup")
|
|
TestFiles.remove_dir(directory)
|
|
|
|
|
|
func test_setting_aside_without_a_save_reports_nothing_moved() -> void:
|
|
var directory := TestFiles.temporary_dir("nothing")
|
|
check_eq(SaveStore.new(directory).set_aside_save(), "", "no save, nothing moved")
|
|
TestFiles.remove_dir(directory)
|
|
|
|
|
|
func test_an_unusable_save_can_be_set_aside() -> void:
|
|
var directory := TestFiles.temporary_dir("aside")
|
|
var store := SaveStore.new(directory)
|
|
store.write_save(PackedByteArray([9, 9]))
|
|
var aside := store.set_aside_save()
|
|
check(not store.has_save(), "the save is out of the way")
|
|
check_eq(FileAccess.get_file_as_bytes(aside), PackedByteArray([9, 9]), "its content is kept")
|
|
TestFiles.remove_dir(directory)
|
|
|
|
|
|
func test_only_the_newest_recordings_are_kept() -> void:
|
|
var directory := TestFiles.temporary_dir("recordings")
|
|
var store := SaveStore.new(directory)
|
|
var paths: PackedStringArray = []
|
|
for number in SaveStore.KEEP_RECORDINGS + 2:
|
|
paths.append(store.write_recording(PackedByteArray([number])))
|
|
OS.delay_usec(10)
|
|
var names := store.recordings()
|
|
check_eq(names.size(), SaveStore.KEEP_RECORDINGS, "only the newest are kept")
|
|
check(not FileAccess.file_exists(paths[0]), "the oldest is gone")
|
|
check(not FileAccess.file_exists(paths[1]), "the second oldest is gone")
|
|
check(FileAccess.file_exists(paths[paths.size() - 1]), "the newest is there")
|
|
check_eq(store.write_recording(PackedByteArray()), "", "an empty recording is not written")
|
|
TestFiles.remove_dir(directory)
|