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. 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) 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 var directory := ProjectSettings.globalize_path(path) DirAccess.make_dir_recursive_absolute(directory) var files: Dictionary = (save as Dictionary).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() _expect(intact, "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 _expect(condition: bool, message: String) -> void: _checks += 1 if not condition: _failures += 1 push_error(message)