130 lines
7.0 KiB
GDScript
130 lines
7.0 KiB
GDScript
extends SceneTree
|
|
|
|
## P07-01 F7, reader phase (a separate engine process after the writer). Each
|
|
## handed-off save package is loaded into a fresh Runtime, must equal the writer's
|
|
## state at that minute, and must continue to the writer's uninterrupted final
|
|
## state, with every remaining 06:00 draw happening exactly once. The fog scene
|
|
## (WX7) packages must also equal the writer's state one minute after each save,
|
|
## so the fog mana yield and the adult intimacy apply exactly once across the
|
|
## process boundary.
|
|
|
|
const Fixture := preload("res://tests/helpers/p07_weather_restart_fixture.gd")
|
|
|
|
var _checks := 0
|
|
var _failures := 0
|
|
|
|
|
|
func _initialize() -> void:
|
|
call_deferred("_run")
|
|
|
|
|
|
func _run() -> void:
|
|
print("P07-01: restart reader")
|
|
var handoff := _read_handoff()
|
|
if not handoff.is_empty():
|
|
_check_restarts(handoff)
|
|
_check_fog(handoff.get("fog", {}))
|
|
await process_frame
|
|
if _failures == 0:
|
|
print("PASS: %d checks" % _checks)
|
|
else:
|
|
push_error("FAIL: %d of %d checks failed" % [_failures, _checks])
|
|
quit(_failures)
|
|
|
|
|
|
func _read_handoff() -> Dictionary:
|
|
_expect(FileAccess.file_exists(Fixture.HANDOFF_PATH), "the writer phase left its handoff (run test_p07_weather_restart_1_write.gd first)")
|
|
if not FileAccess.file_exists(Fixture.HANDOFF_PATH):
|
|
return {}
|
|
var parsed: Variant = JSON.parse_string(FileAccess.get_file_as_string(Fixture.HANDOFF_PATH))
|
|
_expect(parsed is Dictionary and str((parsed as Dictionary).get("schema", "")) == Fixture.HANDOFF_SCHEMA, "handoff schema")
|
|
if not parsed is Dictionary:
|
|
return {}
|
|
var handoff: Dictionary = parsed
|
|
var writer_pid := int(handoff.get("writer_pid", 0))
|
|
_expect(writer_pid > 0 and writer_pid != OS.get_process_id(), "the writer ran in another engine process")
|
|
_expect(str(handoff.get("writer_source_sha256", "")) == FileAccess.get_sha256(Fixture.WRITER_SOURCE), "the handoff comes from the current writer suite")
|
|
_expect(int(handoff.get("seed", -1)) == Fixture.SEED and int(handoff.get("last_minute", -1)) == Fixture.LAST_MINUTE, "same fixture and minute script")
|
|
print("P07_RESTART_READER pid=%d writer_pid=%d handoff_sha256=%s" % [OS.get_process_id(), writer_pid, FileAccess.get_sha256(Fixture.HANDOFF_PATH)])
|
|
return handoff
|
|
|
|
|
|
func _check_restarts(handoff: Dictionary) -> void:
|
|
var fixtures := Fixture.new(_expect)
|
|
var final_state := Marshalls.base64_to_raw(str(handoff.get("straight_final_state_base64", "")))
|
|
_expect(Fixture.sha256(final_state) == str(handoff.get("straight_final_state_sha256", "")), "the writer's final state arrived intact")
|
|
var saves: Array = handoff.get("saves", [])
|
|
_expect(saves.size() == Fixture.SAVE_MINUTES.size(), "every save package was handed off")
|
|
for save: Variant in saves:
|
|
var minute := int((save as Dictionary).get("minute", -1))
|
|
var path := Fixture.READER_SAVE % minute
|
|
_expect(_write_package(save, path), "save package at minute %d arrived byte-exact" % minute)
|
|
var expected_state := Marshalls.base64_to_raw(str((save as Dictionary).get("state_base64", "")))
|
|
var fixture := fixtures.new_fixture(false)
|
|
var runtime: Variant = fixture[&"runtime"]
|
|
_expect(bool(runtime.load_from_path(path).get(&"ok", false)), "the reader loads the minute %d save" % minute)
|
|
_expect(fixtures.state_bytes(runtime) == expected_state and fixture[&"clock"].absolute_minute == minute, "the minute %d save resumes the writer's weather, holds, stream and plans" % minute)
|
|
# Loading replaces the event stream, so the draw counter subscribes again.
|
|
fixture[&"draws"][&"count"] = 0
|
|
fixtures.watch_draws(fixture)
|
|
fixtures.play(fixture, minute, Fixture.LAST_MINUTE, Callable())
|
|
var expected_draws := 0
|
|
for draw_minute: int in Fixture.DRAW_MINUTES:
|
|
if draw_minute > minute:
|
|
expected_draws += 1
|
|
_expect(fixture[&"draws"][&"count"] == expected_draws, "after the minute %d save the remaining 06:00 draws happen once (%d)" % [minute, expected_draws])
|
|
_expect(fixtures.state_bytes(runtime) == final_state, "continuing from minute %d reaches the uninterrupted final state" % minute)
|
|
fixtures.free_fixture(fixture)
|
|
SaveService.delete_save(path)
|
|
|
|
|
|
func _check_fog(fog: Dictionary) -> void:
|
|
var fixtures := Fixture.new(_expect)
|
|
_expect(int(fog.get("seed", -1)) == Fixture.FOG_SEED and int(fog.get("last_minute", -1)) == Fixture.FOG_LAST_MINUTE, "same fog scene and minute script")
|
|
var final_state := Marshalls.base64_to_raw(str(fog.get("straight_final_state_base64", "")))
|
|
_expect(not final_state.is_empty() and Fixture.sha256(final_state) == str(fog.get("straight_final_state_sha256", "")), "the writer's fog final state arrived intact")
|
|
var saves: Array = fog.get("saves", [])
|
|
_expect(saves.size() == Fixture.FOG_SAVE_MINUTES.size(), "every fog save package was handed off")
|
|
var after: Dictionary = fog.get("after_states", {})
|
|
for save: Variant in saves:
|
|
var minute := int((save as Dictionary).get("minute", -1))
|
|
var path := Fixture.FOG_READER_SAVE % minute
|
|
_expect(_write_package(save, path), "fog save package at minute %d arrived byte-exact" % minute)
|
|
var expected_state := Marshalls.base64_to_raw(str((save as Dictionary).get("state_base64", "")))
|
|
var next_row: Dictionary = after.get(str(minute + 1), {})
|
|
var next_state := Marshalls.base64_to_raw(str(next_row.get("base64", "")))
|
|
_expect(not next_state.is_empty() and Fixture.sha256(next_state) == str(next_row.get("sha256", "")), "the writer's state one minute after %d arrived intact" % minute)
|
|
var fixture := fixtures.new_fixture(false, Fixture.FOG_SEED)
|
|
var runtime: Variant = fixture[&"runtime"]
|
|
_expect(bool(runtime.load_from_path(path).get(&"ok", false)) and fixtures.state_bytes(runtime) == expected_state and fixture[&"clock"].absolute_minute == minute,
|
|
"the reader resumes the minute %d fog save exactly" % minute)
|
|
fixtures.play(fixture, minute, minute + 1, Callable())
|
|
_expect(fixtures.state_bytes(runtime) == next_state, "one minute after the minute %d fog save the reader equals the uninterrupted run (no double fog yield or intimacy)" % minute)
|
|
fixtures.play(fixture, minute + 1, Fixture.FOG_LAST_MINUTE, Callable())
|
|
_expect(fixtures.state_bytes(runtime) == final_state, "continuing the minute %d fog save reaches the uninterrupted final state" % minute)
|
|
fixtures.free_fixture(fixture)
|
|
SaveService.delete_save(path)
|
|
|
|
|
|
## Writes one handed-off save package byte for byte; true when every file matches its pin.
|
|
func _write_package(save: Dictionary, path: String) -> bool:
|
|
var directory := ProjectSettings.globalize_path(path)
|
|
DirAccess.make_dir_recursive_absolute(directory)
|
|
var files: Dictionary = save.get("files", {})
|
|
var intact := not files.is_empty()
|
|
for name: String in files:
|
|
var bytes := Marshalls.base64_to_raw(str(files[name].get("base64", "")))
|
|
intact = intact and Fixture.sha256(bytes) == str(files[name].get("sha256", "")) and bytes.size() == int(files[name].get("bytes", -1))
|
|
var file := FileAccess.open(directory.path_join(name), FileAccess.WRITE)
|
|
if file != null:
|
|
file.store_buffer(bytes)
|
|
file.close()
|
|
return intact
|
|
|
|
|
|
func _expect(condition: bool, message: String) -> void:
|
|
_checks += 1
|
|
if not condition:
|
|
_failures += 1
|
|
push_error(message)
|