From 1c914ce6030c7245d4e9349d88cf1bc7e3c0136a Mon Sep 17 00:00:00 2001 From: Luke Hubmayer-Werner Date: Thu, 3 Aug 2023 17:27:03 +0930 Subject: [PATCH] Add unit test script for save files Remember to disable sample generation after first attempt --- scripts/loaders/common.gd | 107 ++++++++++++++++++++++++++++--- scripts/loaders/sprite_loader.gd | 2 +- test/unit_tests.gd | 68 ++++++++++++++++++++ test/unit_tests.tscn | 8 +++ 4 files changed, 174 insertions(+), 11 deletions(-) create mode 100644 test/unit_tests.gd create mode 100644 test/unit_tests.tscn diff --git a/scripts/loaders/common.gd b/scripts/loaders/common.gd index f3010b1..90b8ec3 100644 --- a/scripts/loaders/common.gd +++ b/scripts/loaders/common.gd @@ -3,17 +3,104 @@ extends Node var base_resolution: Vector2 = Vector2(ProjectSettings.get_setting('display/window/size/width'), ProjectSettings.get_setting('display/window/size/height')) # Vector2(640, 360) var shrink_timer := Timer.new() -static func load_json(filename: String) -> Dictionary: +static func load_json(filename: String): # Valid JSON will return Array or Dictionary. int error code for anything else. var file := File.new() - var error := file.open(filename, File.READ) - if error == OK: - var result = JSON.parse(file.get_as_text()) - if result.error == OK: - return result.result - else: - print_debug(result.error_string) - print_debug(result.error_line) - return {} + match file.open(filename, File.READ): + OK: + var result = JSON.parse(file.get_as_text()) + match result.error: + OK: + return result.result + var error: + print_debug(result.error_string) + print_debug(result.error_line) + return error + var error: + return error + +static func _to_json(data, current_indent: int = 0) -> String: + match typeof(data): + TYPE_RAW_ARRAY: # XX: add more as required + return str(data) + TYPE_ARRAY: + var contents := PoolStringArray() + for value in data: + contents.append(_to_json(value, current_indent + 1)) + return '[' + ', '.join(contents) + ']\n' + TYPE_DICTIONARY: + var contents := PoolStringArray() + for k in data: + contents.append('"%s": %s' % [k, _to_json(data[k], current_indent + 1)]) + return '{' + ', '.join(contents) + '}\n' + _: + return JSON.print(data) + +static func save_json(filename: String, data) -> int: # Returns error code but Error is not usable as return signature + # Oh cool, 3.6.beta2's json serialization is borked on PoolByteArray and PoolVector2Array + # print(JSON.print([PoolByteArray([0,1]), PoolIntArray([0,1]), PoolRealArray([0,1]), PoolVector2Array([Vector2.ONE, Vector2.LEFT]), PoolStringArray(['hello', 'world'])])) + # ["[0, 1]",[0,1],[0,1],"[(1, 1), (-1, 0)]",["hello","world"]] + var file := File.new() + var error := file.open(filename, File.WRITE) + if error != OK: + return error + # file.store_line(to_json(data)) + # file.store_line(JSON.print(data)) + file.store_line(_to_json(data, 0)) + return OK + +static func genericize_type(type: int) -> int: + match type: + TYPE_RAW_ARRAY, TYPE_INT_ARRAY, TYPE_REAL_ARRAY, TYPE_STRING_ARRAY: + return TYPE_ARRAY + TYPE_INT: + return TYPE_REAL + _: + return type + +static func are_values_equal(v1, v2, fuzzy_types: bool = true) -> bool: + var t1 := typeof(v1) + var t2 := typeof(v2) + if fuzzy_types: + t1 = genericize_type(t1) + t2 = genericize_type(t2) + if t1 != t2: + print_debug('Type mismatch: %s vs %s' % [t1, t2]) + return false + match t1: + TYPE_ARRAY: + return are_arrays_equal(v1, v2, fuzzy_types) + TYPE_DICTIONARY: + return are_dictionaries_equal(v1, v2, fuzzy_types) + _: + return v1 == v2 + +static func are_dictionaries_equal(d1: Dictionary, d2: Dictionary, fuzzy_types: bool = true) -> bool: + if len(d1) != len(d2): + print_debug('Array lengths not equal: %s vs %s' % [len(d1), len(d2)]) + return false + for k in d1: + if not (k in d2): + print_debug('d1 has key "%s" that d2 does not' % k) + return false + if not are_values_equal(d1[k], d2[k], fuzzy_types): + print_debug('d1["%s"] != d2["%s"]' % [k, k]) + print_debug(d1[k]) + print_debug(d2[k]) + return false + return true + +static func are_arrays_equal(a1: Array, a2: Array, fuzzy_types: bool = true) -> bool: + var l1 := len(a1) + if l1 != len(a2): + print_debug('Array lengths not equal: %s vs %s' % [l1, len(a2)]) + return false + for i in l1: + if not are_values_equal(a1[i], a2[i], fuzzy_types): + print_debug('a1[%d] != a2[%d]' % [i, i]) + print_debug(a1[i]) + print_debug(a2[i]) + return false + return true static func load_tsv(filename: String, delimiter: String = '\t') -> Dictionary: var file := File.new() diff --git a/scripts/loaders/sprite_loader.gd b/scripts/loaders/sprite_loader.gd index 1da1456..c09be97 100644 --- a/scripts/loaders/sprite_loader.gd +++ b/scripts/loaders/sprite_loader.gd @@ -224,7 +224,7 @@ func snes_load_battle_sprites(rom: File): weapon_textures['Fist'] = texture_from_image(snes_graphics.get_tile(rom, Common.SNES_PSX_addresses['tiles_fist']['SNES'], 24)) -var json_sprite_blocks := Common.load_json('res://data/sprite_blocks.json') +var json_sprite_blocks: Dictionary = Common.load_json('res://data/sprite_blocks.json') # This needs error handling later var sprite_blocks := {} func snes_load_map_sprites(rom: File): # Main palettes diff --git a/test/unit_tests.gd b/test/unit_tests.gd new file mode 100644 index 0000000..8c7bbde --- /dev/null +++ b/test/unit_tests.gd @@ -0,0 +1,68 @@ +extends Control + +var dir_user := Directory.new() +const P_TESTDATA := 'user://test_data/' + +func load_snes_savefile(filename: String = 'res://test.srm'): + var save_file := File.new() + match save_file.open(filename, File.READ): + OK: + pass + var error: + print_debug('Failed to open test.srm for reading: %d' % error) + return + var save_slots = [] + var save_slot_dicts = [] + for i in 4: + save_slots.append(SaveLoader.get_save_slot(save_file, i)) + save_slot_dicts.append(SaveLoader.get_struct(save_slots[i], 'Save_slot')) + print('Loaded test save file') + return save_slot_dicts + +func generate_known_good_results(): + var save_slot_dicts = load_snes_savefile() + if not save_slot_dicts: + return + match dir_user.make_dir_recursive(P_TESTDATA): + OK: + pass + var error: + print_debug('Failed to create "%s" with error code %d' % [P_TESTDATA, error]) + return + var filename := P_TESTDATA + 'test.srm.json' + match Common.save_json(filename, save_slot_dicts): + OK: + pass + var error: + print_debug('Failed to save "%s" with error code %d' % [filename, error]) + return + +func test_save_loading() -> bool: + var save_slot_dicts = load_snes_savefile() + if not save_slot_dicts: + print_debug('Failed to load test savefile') + return false + var filename := P_TESTDATA + 'test.srm.json' + var known_good = Common.load_json(filename) + match typeof(known_good): + TYPE_ARRAY: + print_debug('Comparing known savefile results') + return Common.are_arrays_equal(save_slot_dicts, known_good) + TYPE_DICTIONARY: + print_debug('Known savefile results "%s" is a dict instead of an array. Did we change formats?' % filename) + return false + _: + print_debug('Failed to load known savefile results "%s"' % filename) + return false + + +# Called when the node enters the scene tree for the first time. +func _ready() -> void: + match dir_user.open('user://'): + OK: + pass + var error: + print_debug('Failed to open user directory') + generate_known_good_results() + print(test_save_loading()) + get_tree().quit() diff --git a/test/unit_tests.tscn b/test/unit_tests.tscn new file mode 100644 index 0000000..b6b0322 --- /dev/null +++ b/test/unit_tests.tscn @@ -0,0 +1,8 @@ +[gd_scene load_steps=2 format=2] + +[ext_resource path="res://test/unit_tests.gd" type="Script" id=1] + +[node name="unit_tests" type="Control"] +anchor_right = 1.0 +anchor_bottom = 1.0 +script = ExtResource( 1 )