extends GutTest const WORLD_ID := &"regional_world" const ORIGIN_LOCATION_ID := &"location_origin" const DESTINATION_LOCATION_ID := &"location_destination" const ORIGIN_SETTLEMENT_ID := &"settlement_origin" const DESTINATION_SETTLEMENT_ID := &"settlement_destination" const GROUP_ID := &"group_caravan" const ROUTE_ID := &"route_trade" const DEPARTURE_TICK := 10 var _test_directory := "" func before_each() -> void: var test_parent := ( ProjectSettings.globalize_path("user://regional_chunked_file_store_tests").simplify_path() ) _remove_tree(test_parent) _test_directory = ( "user://regional_chunked_file_store_tests/run_%d_%d" % [Time.get_ticks_usec(), get_instance_id()] ) func after_each() -> void: var absolute_path := ProjectSettings.globalize_path(_test_directory).simplify_path() var expected_parent := ( ProjectSettings.globalize_path("user://regional_chunked_file_store_tests").simplify_path() ) if absolute_path.begins_with(expected_parent + "/"): _remove_tree(absolute_path) func test_atomic_generations_round_trip_exact_archive_and_report_telemetry() -> void: var explicit_absolute_base := ProjectSettings.globalize_path(_test_directory) var store := RegionalChunkedFileStore.new(explicit_absolute_base) var first := _fixture_archive(false) var second := _fixture_archive(true) assert_eq(store.get_base_directory(), explicit_absolute_base) assert_true(store.save(first)) var first_generation := store.get_generation_ids()[0] assert_true(store.save(second)) var generation_ids := store.get_generation_ids() assert_eq(generation_ids.size(), 2) assert_eq(generation_ids[0], first_generation) assert_true( ( DirAccess.open( ProjectSettings.globalize_path(store.get_generation_directory(first_generation)) ) != null ) ) var save_telemetry := store.get_last_telemetry() assert_true(save_telemetry["succeeded"]) assert_gt(save_telemetry["serialized_bytes"], 0) assert_gt(save_telemetry["bytes_written"], 0) assert_gte(save_telemetry["write_usec"], 0) assert_gte(save_telemetry["validation_usec"], 0) var latest_generation := generation_ids[-1] var generation_path := store.get_generation_directory(latest_generation) var generation_files := DirAccess.get_files_at(generation_path) assert_eq(generation_files.size(), second.get_chunk_ids().size() + 1) assert_true(RegionalChunkedFileStore.MANIFEST_FILE_NAME in generation_files) for file_name: String in generation_files: assert_false("/" in file_name) assert_false("\\" in file_name) assert_lte(file_name.length(), 64) var decoded: Variant = _read_variant(generation_path.path_join(file_name)) assert_true(_contains_only_primitive_values(decoded)) var restored := store.load_archive() assert_not_null(restored) assert_eq(restored.checksum(), second.checksum()) assert_eq(restored.restore_service().checksum(), second.restore_service().checksum()) var load_telemetry := store.get_last_telemetry() assert_true(load_telemetry["succeeded"]) assert_eq(load_telemetry["generation_id"], latest_generation) assert_false(load_telemetry["recovered_generation"]) assert_gt(load_telemetry["bytes_read"], 0) assert_gte(load_telemetry["read_usec"], 0) assert_gte(load_telemetry["validation_usec"], 0) func test_interrupted_pointer_and_corrupt_latest_generation_recover_last_valid() -> void: var store := RegionalChunkedFileStore.new(_test_directory) var first := _fixture_archive(false) var second := _fixture_archive(true) assert_true(store.save(first)) var first_generation := store.get_generation_ids()[0] var current_pointer := ProjectSettings.globalize_path(store.get_current_pointer_path()) var previous_pointer := ProjectSettings.globalize_path( _test_directory.path_join(RegionalChunkedFileStore.PREVIOUS_POINTER_FILE_NAME) ) assert_eq(DirAccess.rename_absolute(current_pointer, previous_pointer), OK) var recovered_missing_pointer := store.load_archive() assert_not_null(recovered_missing_pointer) assert_eq(recovered_missing_pointer.checksum(), first.checksum()) assert_true(store.get_last_telemetry()["recovered_generation"]) assert_true(store.save(second)) var latest_generation := store.get_generation_ids()[-1] var latest_path := store.get_generation_directory(latest_generation) var manifest: Dictionary = _read_variant( latest_path.path_join(RegionalChunkedFileStore.MANIFEST_FILE_NAME) ) var first_chunk_file := String(manifest["chunk_files"][0]["file_name"]) _write_variant(latest_path.path_join(first_chunk_file), {"corrupt": true}) var interrupted_path := ProjectSettings.globalize_path( store.get_generations_directory().path_join( "temporary_generation_00000000000000000003_interrupted" ) ) assert_eq(DirAccess.make_dir_recursive_absolute(interrupted_path), OK) _write_variant(interrupted_path.path_join("partial.bin"), {"partial": true}) var recovered := store.load_archive() assert_not_null(recovered) assert_eq(recovered.checksum(), first.checksum()) assert_eq(store.get_last_telemetry()["generation_id"], first_generation) assert_true(store.get_last_telemetry()["recovered_generation"]) assert_gte(store.get_last_telemetry()["candidates_validated"], 2) assert_true(FileAccess.file_exists(latest_path.path_join(first_chunk_file))) func test_partial_location_load_reads_only_selected_chunks_and_keeps_unloaded_authority() -> void: var store := RegionalChunkedFileStore.new(_test_directory) var archive := _fixture_archive(false) assert_true(store.save(archive)) var saved_telemetry := store.get_last_telemetry() var generation_id := store.get_generation_ids()[0] var generation_path := store.get_generation_directory(generation_id) var manifest: Dictionary = _read_variant( generation_path.path_join(RegionalChunkedFileStore.MANIFEST_FILE_NAME) ) var scheduler_entry := _find_chunk_file_entry( manifest, RegionalChunkedPersistence.SCHEDULER_CHUNK_ID ) var scheduler_path := generation_path.path_join(String(scheduler_entry["file_name"])) _write_variant(scheduler_path, {"corrupt_unloaded_scheduler": true}) var partial := store.load_active_location_metadata(ORIGIN_LOCATION_ID) assert_false(partial.is_empty()) assert_eq(partial["generation_id"], generation_id) assert_eq( partial["loaded_chunk_ids"], [ RegionalChunkedPersistence.GLOBAL_CHUNK_ID, "location:%s" % ORIGIN_LOCATION_ID, ] ) assert_true(RegionalChunkedPersistence.SCHEDULER_CHUNK_ID in partial["unloaded_chunk_ids"]) assert_eq(partial["mobile_group_refs"].size(), 1) assert_false(partial["full_generation_validated"]) assert_true(FileAccess.file_exists(scheduler_path)) var partial_telemetry := store.get_last_telemetry() assert_true(partial_telemetry["partial_load"]) assert_eq(partial_telemetry["files_read"], 4) assert_lt(partial_telemetry["bytes_read"], saved_telemetry["bytes_written"]) assert_null(store.load_archive()) func test_unsafe_base_and_traversal_pointer_cannot_escape_generation_root() -> void: var archive := _fixture_archive(false) var unsafe_relative := RegionalChunkedFileStore.new("../regional_escape") assert_false(unsafe_relative.save(archive)) assert_true("unsafe" in unsafe_relative.get_last_error()) assert_false(RegionalChunkedFileStore.new("user://").save(archive)) assert_false(RegionalChunkedFileStore.new("res://").save(archive)) var store := RegionalChunkedFileStore.new(_test_directory) assert_true(store.save(archive)) assert_eq(store.get_generation_directory("../outside"), "") var malicious_pointer := { "schema_version": RegionalChunkedFileStore.SCHEMA_VERSION, "format_id": RegionalChunkedFileStore.FORMAT_ID, "generation_id": "../outside", "manifest_checksum": "0".repeat(64), "archive_checksum": "0".repeat(64), } _write_variant(store.get_current_pointer_path(), malicious_pointer) var recovered := store.load_archive() assert_not_null(recovered) assert_eq(recovered.checksum(), archive.checksum()) assert_true(store.get_last_telemetry()["recovered_generation"]) func test_explicit_conservative_prune_keeps_current_and_two_newest_valid_generations() -> void: var store := RegionalChunkedFileStore.new(_test_directory) var first := _fixture_archive(false) var latest := _fixture_archive(true) assert_true(store.save(first)) assert_true(store.save(latest)) assert_true(store.save(latest)) var before := store.get_generation_ids() assert_eq(before.size(), 3) assert_eq(store.prune_old_generations(2), 1) assert_eq(store.get_generation_ids(), [before[1], before[2]]) assert_eq(store.get_last_telemetry()["operation"], "prune_old_generations") assert_eq(store.get_last_telemetry()["generations_pruned"], 1) assert_true(store.get_last_telemetry()["succeeded"]) assert_gt(store.get_last_telemetry()["files_deleted"], 0) assert_eq(store.load_archive().checksum(), latest.checksum()) var retained := store.get_generation_ids() assert_eq(store.prune_old_generations(1), 0) assert_eq(store.get_generation_ids(), retained) assert_true("at least two" in store.get_last_error()) func test_partial_load_does_not_resurrect_a_location_removed_by_newer_authority() -> void: var store := RegionalChunkedFileStore.new(_test_directory) var older_with_destination := _fixture_archive(false) var latest_without_destination := _single_location_archive() assert_true(store.save(older_with_destination)) assert_true(store.save(latest_without_destination)) assert_true(store.load_active_location_metadata(DESTINATION_LOCATION_ID).is_empty()) assert_true("does not contain" in store.get_last_error()) assert_eq(store.load_archive().checksum(), latest_without_destination.checksum()) func _fixture_archive(departed: bool) -> RegionalChunkedPersistence: var origin_location := LocationStateRecord.create( ORIGIN_LOCATION_ID, WORLD_ID, &"settlement", "Origin", _address(ORIGIN_LOCATION_ID) ) var destination_location := LocationStateRecord.create( DESTINATION_LOCATION_ID, WORLD_ID, &"settlement", "Destination", _address(DESTINATION_LOCATION_ID) ) var origin := SettlementStateRecord.create( ORIGIN_SETTLEMENT_ID, ORIGIN_LOCATION_ID, "Origin", &"polity_origin", 1, &"founders_origin", 0, {&"food": 20.0} ) var destination := SettlementStateRecord.create( DESTINATION_SETTLEMENT_ID, DESTINATION_LOCATION_ID, "Destination", &"polity_destination", 1, &"founders_destination", 0, {&"food": 4.0} ) var route := RouteStateRecord.create(ROUTE_ID, ORIGIN_LOCATION_ID, DESTINATION_LOCATION_ID, 5) var group := MobileGroupStateRecord.create_at_location( GROUP_ID, &"caravan", "Caravan", &"polity_origin", _address(ORIGIN_LOCATION_ID), [], {}, {}, 10.0 ) var origin_polity := PolityStateRecord.create( &"polity_origin", "Origin polity", ORIGIN_LOCATION_ID, [ORIGIN_SETTLEMENT_ID] ) var destination_polity := PolityStateRecord.create( &"polity_destination", "Destination polity", DESTINATION_LOCATION_ID, [DESTINATION_SETTLEMENT_ID] ) var locations: Array[LocationStateRecord] = [destination_location, origin_location] var settlements: Array[SettlementStateRecord] = [destination, origin] var routes: Array[RouteStateRecord] = [route] var groups: Array[MobileGroupStateRecord] = [group] var persons: Array[PersonStateRecord] = [] var cohorts: Array[PopulationCohortRecord] = [] var polities: Array[PolityStateRecord] = [destination_polity, origin_polity] var relations: Array[DiplomaticRelationRecord] = [] var world_state := RegionalWorldState.create( WORLD_ID, locations, settlements, routes, groups, persons, cohorts, polities, relations ) var service := RegionalCaravanService.create( world_state, RegionalJobScheduler.new(), WorldEventStore.new() ) if departed: assert_true( service.depart( GROUP_ID, ORIGIN_SETTLEMENT_ID, DESTINATION_SETTLEMENT_ID, ROUTE_ID, {&"food": 6.0}, DEPARTURE_TICK ) ) return RegionalChunkedPersistence.capture(service, 2) func _single_location_archive() -> RegionalChunkedPersistence: var location := LocationStateRecord.create( ORIGIN_LOCATION_ID, WORLD_ID, &"settlement", "Origin", _address(ORIGIN_LOCATION_ID) ) var settlement := SettlementStateRecord.create( ORIGIN_SETTLEMENT_ID, ORIGIN_LOCATION_ID, "Origin", &"polity_origin", 1, &"founders_origin", 0, {&"food": 20.0} ) var polity := PolityStateRecord.create( &"polity_origin", "Origin polity", ORIGIN_LOCATION_ID, [ORIGIN_SETTLEMENT_ID] ) var locations: Array[LocationStateRecord] = [location] var settlements: Array[SettlementStateRecord] = [settlement] var routes: Array[RouteStateRecord] = [] var groups: Array[MobileGroupStateRecord] = [] var persons: Array[PersonStateRecord] = [] var cohorts: Array[PopulationCohortRecord] = [] var polities: Array[PolityStateRecord] = [polity] var relations: Array[DiplomaticRelationRecord] = [] var world_state := RegionalWorldState.create( WORLD_ID, locations, settlements, routes, groups, persons, cohorts, polities, relations ) var service := RegionalCaravanService.create( world_state, RegionalJobScheduler.new(), WorldEventStore.new() ) return RegionalChunkedPersistence.capture(service, 2) func _address(location_id: StringName) -> SpatialAddress: return SpatialAddress.create(WORLD_ID, location_id, Vector3.ZERO) func _find_chunk_file_entry(manifest: Dictionary, chunk_id: String) -> Dictionary: for raw_entry: Variant in manifest["chunk_files"]: if raw_entry is Dictionary and String(raw_entry.get("chunk_id", "")) == chunk_id: return raw_entry return {} func _read_variant(path: String) -> Variant: var file := FileAccess.open(path, FileAccess.READ) if file == null: return null var bytes := file.get_buffer(file.get_length()) file.close() return bytes_to_var(bytes) if not bytes.is_empty() else null func _write_variant(path: String, value: Variant) -> void: var file := FileAccess.open(path, FileAccess.WRITE) assert_not_null(file) if file == null: return file.store_buffer(var_to_bytes(value)) file.flush() file.close() func _contains_only_primitive_values(value: Variant) -> bool: match typeof(value): TYPE_NIL, TYPE_BOOL, TYPE_INT, TYPE_STRING: return true TYPE_FLOAT: return is_finite(float(value)) TYPE_ARRAY: for item: Variant in value: if not _contains_only_primitive_values(item): return false return true TYPE_DICTIONARY: for key: Variant in value: if not key is String or not _contains_only_primitive_values(value[key]): return false return true return false func _remove_tree(absolute_path: String) -> void: var directory := DirAccess.open(absolute_path) if directory == null: return directory.include_hidden = true directory.list_dir_begin() var entry_name := directory.get_next() while not entry_name.is_empty(): var entry_path := absolute_path.path_join(entry_name) if directory.current_is_dir(): _remove_tree(entry_path) else: DirAccess.remove_absolute(entry_path) entry_name = directory.get_next() directory.list_dir_end() DirAccess.remove_absolute(absolute_path)