From f1e9ce2ab0702ae3b1a94ea9639900ced811b1ff Mon Sep 17 00:00:00 2001 From: Nullpointer Date: Wed, 26 Jun 2024 20:01:51 +0200 Subject: [PATCH] GD-517: Fic test discovery guard fails on CSharpScript tests when editing # Why see https://github.com/MikeSchulze/gdUnit4/issues/517 # What - added rebuild cs scripts before run discovery - fixed invalid script path resolving by using `localize_path` to convert cs script paths - fixes test suite scanner to run on Script class to accept GDScript and CSharpScript --- addons/gdUnit4/plugin.gd | 2 +- .../src/core/GdUnitTestSuiteScanner.gd | 7 +- .../core/discovery/GdUnitTestDiscoverGuard.gd | 63 ++++++++++----- .../src/ui/parts/InspectorTreeMainPanel.gd | 6 +- .../gdUnit4/test/GdUnitTestResourceLoader.gd | 32 ++++---- .../discovery/GdUnitTestDiscoverGuardTest.gd | 79 +++++++++++++++++++ .../resources/DiscoverExampleTestSuite.cs | 21 +++++ .../resources/DiscoverExampleTestSuite.gd | 9 +++ 8 files changed, 178 insertions(+), 41 deletions(-) create mode 100644 addons/gdUnit4/test/core/discovery/GdUnitTestDiscoverGuardTest.gd create mode 100644 addons/gdUnit4/test/core/discovery/resources/DiscoverExampleTestSuite.cs create mode 100644 addons/gdUnit4/test/core/discovery/resources/DiscoverExampleTestSuite.gd diff --git a/addons/gdUnit4/plugin.gd b/addons/gdUnit4/plugin.gd index 37a2a696..dd6d87da 100644 --- a/addons/gdUnit4/plugin.gd +++ b/addons/gdUnit4/plugin.gd @@ -56,4 +56,4 @@ func check_running_in_test_env() -> bool: func _on_resource_saved(resource: Resource) -> void: if resource is Script: - _guard.discover(resource) + await _guard.discover(resource) diff --git a/addons/gdUnit4/src/core/GdUnitTestSuiteScanner.gd b/addons/gdUnit4/src/core/GdUnitTestSuiteScanner.gd index 0f882090..4ae3ea66 100644 --- a/addons/gdUnit4/src/core/GdUnitTestSuiteScanner.gd +++ b/addons/gdUnit4/src/core/GdUnitTestSuiteScanner.gd @@ -113,10 +113,15 @@ static func _is_script_format_supported(resource_path :String) -> bool: return GdUnit4CSharpApiLoader.is_csharp_file(resource_path) -func _parse_test_suite(script :GDScript) -> GdUnitTestSuite: +func _parse_test_suite(script :Script) -> GdUnitTestSuite: if not GdObjects.is_test_suite(script): return null + # If test suite a C# script + if GdUnit4CSharpApiLoader.is_test_suite(script.resource_path): + return GdUnit4CSharpApiLoader.parse_test_suite(script.resource_path) + + # Do pares as GDScript var test_suite :GdUnitTestSuite = script.new() test_suite.set_name(GdUnitTestSuiteScanner.parse_test_suite_name(script)) # add test cases to test suite and parse test case line nummber diff --git a/addons/gdUnit4/src/core/discovery/GdUnitTestDiscoverGuard.gd b/addons/gdUnit4/src/core/discovery/GdUnitTestDiscoverGuard.gd index fa9a65c0..b45f7983 100644 --- a/addons/gdUnit4/src/core/discovery/GdUnitTestDiscoverGuard.gd +++ b/addons/gdUnit4/src/core/discovery/GdUnitTestDiscoverGuard.gd @@ -12,7 +12,7 @@ func _init() -> void: func sync_cache(dto :GdUnitTestSuiteDto) -> void: - var resource_path := dto.path() + var resource_path := ProjectSettings.localize_path(dto.path()) var discovered_test_cases :Array[String] = [] for test_case in dto.test_cases(): discovered_test_cases.append(test_case.name()) @@ -20,41 +20,44 @@ func sync_cache(dto :GdUnitTestSuiteDto) -> void: func discover(script: Script) -> void: + # for cs scripts we need to recomplie before discover new tests + if GdObjects.is_cs_script(script): + await rebuild_project(script) + if GdObjects.is_test_suite(script): # a new test suite is discovered - if not _discover_cache.has(script.resource_path): - var scanner := GdUnitTestSuiteScanner.new() - var test_suite := scanner._parse_test_suite(script) + var script_path := ProjectSettings.localize_path(script.resource_path) + var scanner := GdUnitTestSuiteScanner.new() + var test_suite := scanner._parse_test_suite(script) + var suite_name := test_suite.get_name() + + if not _discover_cache.has(script_path): var dto :GdUnitTestSuiteDto = GdUnitTestSuiteDto.of(test_suite) - GdUnitSignals.instance().gdunit_event.emit(GdUnitEventTestDiscoverTestSuiteAdded.new(script.resource_path, test_suite.get_name(), dto)) + GdUnitSignals.instance().gdunit_event.emit(GdUnitEventTestDiscoverTestSuiteAdded.new(script_path, suite_name, dto)) sync_cache(dto) test_suite.queue_free() return - var tests_added :Array[String] = [] - var tests_removed := PackedStringArray() - var script_test_cases := extract_test_functions(script) - var discovered_test_cases :Array[String] = _discover_cache.get(script.resource_path, [] as Array[String]) + var discovered_test_cases :Array[String] = _discover_cache.get(script_path, [] as Array[String]) + var script_test_cases := extract_test_functions(test_suite) # first detect removed/renamed tests + var tests_removed := PackedStringArray() for test_case in discovered_test_cases: if not script_test_cases.has(test_case): tests_removed.append(test_case) # second detect new added tests + var tests_added :Array[String] = [] for test_case in script_test_cases: if not discovered_test_cases.has(test_case): tests_added.append(test_case) # finally notify changes to the inspector if not tests_removed.is_empty() or not tests_added.is_empty(): - var scanner := GdUnitTestSuiteScanner.new() - var test_suite := scanner._parse_test_suite(script) - var suite_name := test_suite.get_name() - # emit deleted tests for test_name in tests_removed: discovered_test_cases.erase(test_name) - GdUnitSignals.instance().gdunit_event.emit(GdUnitEventTestDiscoverTestRemoved.new(script.resource_path, suite_name, test_name)) + GdUnitSignals.instance().gdunit_event.emit(GdUnitEventTestDiscoverTestRemoved.new(script_path, suite_name, test_name)) # emit new discovered tests for test_name in tests_added: @@ -62,16 +65,14 @@ func discover(script: Script) -> void: var test_case := test_suite.find_child(test_name, false, false) var dto := GdUnitTestCaseDto.new() dto = dto.deserialize(dto.serialize(test_case)) - GdUnitSignals.instance().gdunit_event.emit(GdUnitEventTestDiscoverTestAdded.new(script.resource_path, suite_name, dto)) + GdUnitSignals.instance().gdunit_event.emit(GdUnitEventTestDiscoverTestAdded.new(script_path, suite_name, dto)) # update the cache - _discover_cache[script.resource_path] = discovered_test_cases + _discover_cache[script_path] = discovered_test_cases test_suite.queue_free() -func extract_test_functions(script :Script) -> PackedStringArray: - return script.get_script_method_list()\ - .map(map_func_names)\ - .filter(filter_test_cases) +func extract_test_functions(test_suite :Node) -> PackedStringArray: + return test_suite.get_children().map(func (child: Node) -> String: return child.get_name()) func map_func_names(method_info :Dictionary) -> String: @@ -84,3 +85,25 @@ func filter_test_cases(value :String) -> bool: func filter_by_test_cases(method_info :Dictionary, value :String) -> bool: return method_info["name"] == value + + +# do rebuild the entire project, there is actual no way to enforce the Godot engine itself to do this +func rebuild_project(script: Script) -> void: + var class_path := ProjectSettings.globalize_path(script.resource_path) + print_rich("[color=CORNFLOWER_BLUE]GdUnitTestDiscoverGuard: CSharpScript change detected on: '%s' [/color]" % class_path) + await Engine.get_main_loop().process_frame + + var output := [] + var exit_code := OS.execute("dotnet", ["--version"], output) + if exit_code == -1: + print_rich("[color=CORNFLOWER_BLUE]GdUnitTestDiscoverGuard:[/color] [color=RED]Rebuild the project failed.[/color]") + print_rich("[color=CORNFLOWER_BLUE]GdUnitTestDiscoverGuard:[/color] [color=RED]Can't find installed `dotnet`! Please check your environment is setup correctly.[/color]") + return + print_rich("[color=CORNFLOWER_BLUE]GdUnitTestDiscoverGuard:[/color] [color=DEEP_SKY_BLUE]Found dotnet v%s[/color]" % output[0].strip_edges()) + output.clear() + + exit_code = OS.execute("dotnet", ["build"], output) + print_rich("[color=CORNFLOWER_BLUE]GdUnitTestDiscoverGuard:[/color] [color=DEEP_SKY_BLUE]Rebuild the project ... [/color]") + for out:Variant in output: + print_rich("[color=DEEP_SKY_BLUE] %s" % out.strip_edges()) + await Engine.get_main_loop().process_frame diff --git a/addons/gdUnit4/src/ui/parts/InspectorTreeMainPanel.gd b/addons/gdUnit4/src/ui/parts/InspectorTreeMainPanel.gd index b3949d76..5919fac1 100644 --- a/addons/gdUnit4/src/ui/parts/InspectorTreeMainPanel.gd +++ b/addons/gdUnit4/src/ui/parts/InspectorTreeMainPanel.gd @@ -777,7 +777,7 @@ func discover_test_removed(event: GdUnitEventTestDiscoverTestRemoved) -> void: func do_add_test_suite(test_suite: GdUnitTestSuiteDto) -> void: var item := create_tree_item(test_suite) var suite_name := test_suite.name() - + var resource_path := ProjectSettings.localize_path(test_suite.path()) item.set_text(0, suite_name) item.set_meta(META_GDUNIT_ORIGINAL_INDEX, item.get_index()) item.set_meta(META_GDUNIT_STATE, STATE.INITIAL) @@ -786,12 +786,12 @@ func do_add_test_suite(test_suite: GdUnitTestSuiteDto) -> void: item.set_meta(META_GDUNIT_TOTAL_TESTS, test_suite.test_case_count()) item.set_meta(META_GDUNIT_SUCCESS_TESTS, 0) item.set_meta(META_GDUNIT_EXECUTION_TIME, 0) - item.set_meta(META_RESOURCE_PATH, test_suite.path()) + item.set_meta(META_RESOURCE_PATH, resource_path) item.set_meta(META_LINE_NUMBER, 1) item.collapsed = true set_item_icon_by_state(item) init_item_counter(item) - add_tree_item_to_cache(test_suite.path(), suite_name, item) + add_tree_item_to_cache(resource_path, suite_name, item) for test_case in test_suite.test_cases(): add_test(item, test_case) diff --git a/addons/gdUnit4/test/GdUnitTestResourceLoader.gd b/addons/gdUnit4/test/GdUnitTestResourceLoader.gd index d7519c49..1a69fb55 100644 --- a/addons/gdUnit4/test/GdUnitTestResourceLoader.gd +++ b/addons/gdUnit4/test/GdUnitTestResourceLoader.gd @@ -46,16 +46,16 @@ static func load_cs_script(resource_path :String, debug_write := false) -> Scrip return null var script :Script = ClassDB.instantiate("CSharpScript") script.source_code = GdUnitFileAccess.resource_as_string(resource_path) - script.resource_path = GdUnitFileAccess.create_temp_dir("test") + "/%s" % resource_path.get_file().replace(".resource", ".cs") + var script_resource_path := resource_path.replace(resource_path.get_extension(), "cs") if debug_write: - print_debug("save resource:", script.resource_path) - DirAccess.remove_absolute(script.resource_path) - var err := ResourceSaver.save(script, script.resource_path) + script_resource_path = GdUnitFileAccess.create_temp_dir("test") + "/%s" % script_resource_path.get_file() + print_debug("save resource:", script_resource_path) + DirAccess.remove_absolute(script_resource_path) + var err := ResourceSaver.save(script, script_resource_path) if err != OK: - print_debug("Can't save debug resource", script.resource_path, "Error:", error_string(err)) - script.take_over_path(script.resource_path) - else: - script.take_over_path(resource_path) + print_debug("Can't save debug resource",script_resource_path, "Error:", error_string(err)) + + script.take_over_path(script_resource_path) script.reload() return script @@ -63,15 +63,15 @@ static func load_cs_script(resource_path :String, debug_write := false) -> Scrip static func load_gd_script(resource_path :String, debug_write := false) -> GDScript: var script := GDScript.new() script.source_code = GdUnitFileAccess.resource_as_string(resource_path) - script.resource_path = GdUnitFileAccess.create_temp_dir("test") + "/%s" % resource_path.get_file().replace(".resource", ".gd") + var script_resource_path := resource_path.replace(resource_path.get_extension(), "gd") if debug_write: - print_debug("save resource:", script.resource_path) - DirAccess.remove_absolute(script.resource_path) - var err := ResourceSaver.save(script, script.resource_path) + script_resource_path = GdUnitFileAccess.create_temp_dir("test") + "/%s" % script_resource_path.get_file() + print_debug("save resource:", script_resource_path) + DirAccess.remove_absolute(script_resource_path) + var err := ResourceSaver.save(script, script_resource_path) if err != OK: - print_debug("Can't save debug resource", script.resource_path, "Error:", error_string(err)) - script.take_over_path(script.resource_path) - else: - script.take_over_path(resource_path) + print_debug("Can't save debug resource", script_resource_path, "Error:", error_string(err)) + + script.take_over_path(script_resource_path) script.reload() return script diff --git a/addons/gdUnit4/test/core/discovery/GdUnitTestDiscoverGuardTest.gd b/addons/gdUnit4/test/core/discovery/GdUnitTestDiscoverGuardTest.gd new file mode 100644 index 00000000..4aeb32eb --- /dev/null +++ b/addons/gdUnit4/test/core/discovery/GdUnitTestDiscoverGuardTest.gd @@ -0,0 +1,79 @@ +# GdUnit generated TestSuite +class_name GdUnitTestDiscoverGuardTest +extends GdUnitTestSuite +@warning_ignore('unused_parameter') +@warning_ignore('return_value_discarded') + +# TestSuite generated from +const GdUnitTestDiscoverGuard = preload("res://addons/gdUnit4/src/core/discovery/GdUnitTestDiscoverGuard.gd") + + + + + +func test_inital() -> void: + var discoverer := GdUnitTestDiscoverGuard.new() + + assert_dict(discoverer._discover_cache).is_empty() + + +func test_sync_cache() -> void: + var discoverer := GdUnitTestDiscoverGuard.new() + + var dto := create_test_dto("res://test/my_test_suite.gd", ["test_a", "test_b"]) + discoverer.sync_cache(dto) + + assert_dict(discoverer._discover_cache).contains_key_value("res://test/my_test_suite.gd", ["test_a", "test_b"]) + + +func test_discover_on_GDScript() -> void: + var discoverer :GdUnitTestDiscoverGuard = spy(GdUnitTestDiscoverGuard.new()) + + # connect to catch the events emitted by the test discoverer + var emitted_events :Array[GdUnitEvent] = [] + GdUnitSignals.instance().gdunit_event.connect(func on_gdunit_event(event :GdUnitEvent) -> void: + emitted_events.append(event) + ) + + var script := load("res://addons/gdUnit4/test/core/discovery/resources/DiscoverExampleTestSuite.gd") + assert_that(script).is_not_null() + if script == null: + return + + await discoverer.discover(script) + # verify the rebuild is NOT called for gd scripts + verify(discoverer, 0).rebuild_project(script) + + assert_array(emitted_events).has_size(1) + assert_object(emitted_events[0]).is_instanceof(GdUnitEventTestDiscoverTestSuiteAdded) + assert_dict(discoverer._discover_cache).contains_key_value("res://addons/gdUnit4/test/core/discovery/resources/DiscoverExampleTestSuite.gd", ["test_case1", "test_case2"]) + + +func test_discover_on_CSharpScript(do_skip := !GdUnit4CSharpApiLoader.is_mono_supported()) -> void: + var discoverer :GdUnitTestDiscoverGuard = spy(GdUnitTestDiscoverGuard.new()) + + # connect to catch the events emitted by the test discoverer + var emitted_events :Array[GdUnitEvent] = [] + GdUnitSignals.instance().gdunit_event.connect(func on_gdunit_event(event :GdUnitEvent) -> void: + emitted_events.append(event) + ) + + var script :Script = load("res://addons/gdUnit4/test/core/discovery/resources/DiscoverExampleTestSuite.cs") + + await discoverer.discover(script) + # verify the rebuild is called for cs scripts + verify(discoverer, 1).rebuild_project(script) + assert_array(emitted_events).has_size(1) + assert_object(emitted_events[0]).is_instanceof(GdUnitEventTestDiscoverTestSuiteAdded) + assert_dict(discoverer._discover_cache).contains_key_value("res://addons/gdUnit4/test/core/discovery/resources/DiscoverExampleTestSuite.cs", ["TestCase1", "TestCase2"]) + + +func create_test_dto(path: String, test_cases: PackedStringArray) -> GdUnitTestSuiteDto: + var dto := GdUnitTestSuiteDto.new() + dto._path = path + for test_case in test_cases: + var test_dto := GdUnitTestCaseDto.new() + test_dto._name = test_case + test_dto._line_number = 42 + dto.add_test_case(test_dto) + return dto diff --git a/addons/gdUnit4/test/core/discovery/resources/DiscoverExampleTestSuite.cs b/addons/gdUnit4/test/core/discovery/resources/DiscoverExampleTestSuite.cs new file mode 100644 index 00000000..822d2295 --- /dev/null +++ b/addons/gdUnit4/test/core/discovery/resources/DiscoverExampleTestSuite.cs @@ -0,0 +1,21 @@ +namespace GdUnit4.Tests.Resource +{ + using static Assertions; + + [TestSuite] + public partial class ExampleTestSuite + { + + [TestCase] + public void TestCase1() + { + AssertBool(true).IsEqual(true); + } + + [TestCase] + public void TestCase2() + { + AssertBool(false).IsEqual(false); + } + } +} diff --git a/addons/gdUnit4/test/core/discovery/resources/DiscoverExampleTestSuite.gd b/addons/gdUnit4/test/core/discovery/resources/DiscoverExampleTestSuite.gd new file mode 100644 index 00000000..eeb4ce6c --- /dev/null +++ b/addons/gdUnit4/test/core/discovery/resources/DiscoverExampleTestSuite.gd @@ -0,0 +1,9 @@ +extends GdUnitTestSuite + + +func test_case1() -> void: + assert_bool(true).is_equal(true); + + +func test_case2() -> void: + assert_bool(false).is_equal(false);