extends "res://tests/test_case.gd" # Task 5.10. The log's whole value is that a recorded match can be replayed # faithfully enough to reproduce a reported snap, so what matters is that the # bytes come back BYTE-IDENTICAL and correctly framed — not merely that # something was written. const ReplayLogScript = preload("res://scripts/replay_log.gd") func _temp_path(suffix: String) -> String: return "user://test_replay_%s_%d.ccrp" % [suffix, Time.get_ticks_usec()] func test_records_round_trip_byte_for_byte() -> void: var path := _temp_path("roundtrip") var log_writer = ReplayLogScript.new() assert_eq(log_writer.open_for_write(path), OK, "opens for write") var input_payload := PackedByteArray([0x01, 0xFF, 0x00, 0x7F, 0x80]) var snapshot_payload := PackedByteArray([0xDE, 0xAD, 0xBE, 0xEF]) log_writer.record_input(120, 4242, input_payload) log_writer.record_snapshot(121, snapshot_payload) log_writer.close() var read := ReplayLogScript.read_all(path) assert_eq(read.get("version", -1), ReplayLogScript.FORMAT_VERSION, "version round-trips") assert_eq(read.get("tick_hz", -1), SimConstants.TICK_HZ, "tick rate is recorded so a reader need not guess") var records: Array = read.get("records", []) assert_eq(records.size(), 2, "both records read back") assert_eq(records[0]["kind"], ReplayLogScript.RecordKind.INPUT, "first is an input") assert_eq(records[0]["tick"], 120, "input tick") assert_eq(records[0]["peer_id"], 4242, "input peer") assert_eq(records[0]["payload"], input_payload, "input payload is byte-identical") assert_eq(records[1]["kind"], ReplayLogScript.RecordKind.SNAPSHOT, "second is a snapshot") assert_eq(records[1]["tick"], 121, "snapshot tick") assert_eq(records[1]["payload"], snapshot_payload, "snapshot payload is byte-identical") DirAccess.remove_absolute(ProjectSettings.globalize_path(path)) func test_a_real_packet_survives_the_round_trip() -> void: # The payloads above are hand-made. Use a genuine NetCodec input packet so # a framing bug that only shows up at real packet sizes cannot hide. var path := _temp_path("realpacket") var action := ShipAction.new() action.thrust = Vector3(0.5, -0.25, 1.0) action.turbo = true var packet := NetCodec.pack_input(77, 55, 1234, [action, action, action]) var log_writer = ReplayLogScript.new() log_writer.open_for_write(path) log_writer.record_input(500, 7, packet) log_writer.close() var records: Array = ReplayLogScript.read_all(path).get("records", []) assert_eq(records.size(), 1, "one record") assert_eq(records[0]["payload"], packet, "a real input packet round-trips unchanged") # And it must still decode as the packet it was. var decoded := NetCodec.unpack_input(records[0]["payload"]) assert_eq(decoded["seq"], 77, "the replayed packet still decodes to its own sequence") DirAccess.remove_absolute(ProjectSettings.globalize_path(path)) func test_empty_log_reads_back_as_no_records() -> void: var path := _temp_path("empty") var log_writer = ReplayLogScript.new() log_writer.open_for_write(path) log_writer.close() var read := ReplayLogScript.read_all(path) assert_eq(read.get("records", [-1]).size(), 0, "a header-only log has no records") assert_eq(read.get("tick_hz", -1), SimConstants.TICK_HZ, "but still reports its header") DirAccess.remove_absolute(ProjectSettings.globalize_path(path)) func test_a_non_replay_file_is_rejected_rather_than_misread() -> void: # FileAccess zero-fills past EOF exactly as StreamPeerBuffer does, so # without a magic check an arbitrary file decodes as an endless run of # zero-length records instead of failing. var path := _temp_path("garbage") var f := FileAccess.open(path, FileAccess.WRITE) f.store_string("this is definitely not a replay log") f.close() assert_true(ReplayLogScript.read_all(path).is_empty(), "a foreign file is refused") DirAccess.remove_absolute(ProjectSettings.globalize_path(path)) func test_a_truncated_log_yields_its_intact_records() -> void: # A server killed mid-write is the NORMAL way one of these ends, so a # partial final record must not discard the whole session. var path := _temp_path("truncated") var log_writer = ReplayLogScript.new() log_writer.open_for_write(path) log_writer.record_input(1, 1, PackedByteArray([1, 2, 3, 4])) log_writer.record_input(2, 1, PackedByteArray([5, 6, 7, 8])) log_writer.close() var whole := FileAccess.get_file_as_bytes(path) var cut := whole.slice(0, whole.size() - 3) # lop off part of the last payload var f := FileAccess.open(path, FileAccess.WRITE) f.store_buffer(cut) f.close() var records: Array = ReplayLogScript.read_all(path).get("records", []) assert_eq(records.size(), 1, "the intact record survives a truncated tail") assert_eq(records[0]["payload"], PackedByteArray([1, 2, 3, 4]), "and is still correct") DirAccess.remove_absolute(ProjectSettings.globalize_path(path)) func test_writing_to_an_unopened_log_is_a_no_op() -> void: # --replay-log is optional, so every record_* call happens behind a null # check in production — but the class must not corrupt or crash if that # check is ever missed. var log_writer = ReplayLogScript.new() assert_true(not log_writer.is_open(), "starts closed") log_writer.record_input(1, 1, PackedByteArray([1])) log_writer.record_snapshot(1, PackedByteArray([1])) assert_eq(log_writer.records_written, 0, "nothing was recorded") log_writer.close()