Skip to content

Commit

Permalink
Use correct category for entity instances (#212)
Browse files Browse the repository at this point in the history
  • Loading branch information
bitbrain authored Nov 26, 2024
1 parent 3c5765e commit 1370f63
Show file tree
Hide file tree
Showing 26 changed files with 220 additions and 64 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/pandora-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ jobs:
fail-fast: false
max-parallel: 10
matrix:
godot-version: ["4.2.2", "4.3"]
godot-version: ["4.3"]

name: "🤖 CI on Godot ${{ matrix.godot-version }}"
steps:
Expand Down
12 changes: 6 additions & 6 deletions addons/gdUnit4/bin/GdUnitCmdTool.gd
Original file line number Diff line number Diff line change
Expand Up @@ -397,7 +397,7 @@ class CLIRunner:
var skipped := config.skipped()
if skipped.is_empty():
return
_console.prints_warning("Found excluded test suite's configured at '%s'" % _runner_config_file)

for test_suite in test_suites:
# skipp c# testsuites for now
if test_suite.get_script() == null:
Expand All @@ -407,23 +407,23 @@ class CLIRunner:

# Dictionary[String, PackedStringArray]
func skip_suite(test_suite: Node, skipped: Dictionary) -> void:
var skipped_suites :Array[String] = skipped.keys()
var skipped_suites :Array = skipped.keys()
var suite_name := test_suite.get_name()
var test_suite_path: String = (
test_suite.get_meta("ResourcePath") if test_suite.get_script() == null
else test_suite.get_script().resource_path
)
for suite_to_skip in skipped_suites:
for suite_to_skip: String in skipped_suites:
# if suite skipped by path or name
if (
suite_to_skip == test_suite_path
or (suite_to_skip.is_valid_filename() and suite_to_skip == suite_name)
):
var skipped_tests: Array[String] = skipped.get(suite_to_skip)
var skip_reason := "Excluded by config '%s'" % _runner_config_file
var skipped_tests: PackedStringArray = skipped.get(suite_to_skip)
var skip_reason := "Excluded by configuration"
# if no tests skipped test the complete suite is skipped
if skipped_tests.is_empty():
_console.prints_warning("Mark test suite '%s' as skipped!" % suite_to_skip)
_console.prints_warning("Mark the entire test suite '%s' as skipped!" % test_suite_path)
@warning_ignore("unsafe_property_access")
test_suite.__is_skipped = true
@warning_ignore("unsafe_property_access")
Expand Down
2 changes: 1 addition & 1 deletion addons/gdUnit4/plugin.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@
name="gdUnit4"
description="Unit Testing Framework for Godot Scripts"
author="Mike Schulze"
version="4.4.1"
version="4.4.3"
script="plugin.gd"
4 changes: 2 additions & 2 deletions addons/gdUnit4/src/asserts/GdAssertMessages.gd
Original file line number Diff line number Diff line change
Expand Up @@ -179,10 +179,10 @@ static func test_timeout(timeout :int) -> String:
static func test_suite_skipped(hint :String, skip_count :int) -> String:
return """
%s
Tests skipped: %s
Skipped %s tests
Reason: %s
""".dedent().trim_prefix("\n")\
% [_error("Entire test-suite is skipped!"), _colored_value(skip_count), _colored_value(hint)]
% [_error("The Entire test-suite is skipped!"), _colored_value(skip_count), _colored_value(hint)]


static func test_skipped(hint :String) -> String:
Expand Down
30 changes: 18 additions & 12 deletions addons/gdUnit4/src/asserts/GdUnitFuncAssertImpl.gd
Original file line number Diff line number Diff line change
Expand Up @@ -29,15 +29,19 @@ func _init(instance :Object, func_name :String, args := Array()) -> void:
_current_value_provider = CallBackValueProvider.new(instance, func_name, args)


func _notification(_what :int) -> void:
if is_instance_valid(_current_value_provider):
_current_value_provider.dispose()
_current_value_provider = null
if is_instance_valid(_sleep_timer):
(Engine.get_main_loop() as SceneTree).root.remove_child(_sleep_timer)
_sleep_timer.stop()
_sleep_timer.free()
_sleep_timer = null
func _notification(what :int) -> void:
if what == NOTIFICATION_PREDELETE:
_interrupted = true
var main_node :Node = (Engine.get_main_loop() as SceneTree).root
if is_instance_valid(_current_value_provider):
_current_value_provider.dispose()
_current_value_provider = null
if is_instance_valid(_sleep_timer):
_sleep_timer.set_wait_time(0.0001)
_sleep_timer.stop()
main_node.remove_child(_sleep_timer)
_sleep_timer.free()
_sleep_timer = null


func report_success() -> GdUnitFuncAssert:
Expand Down Expand Up @@ -114,6 +118,10 @@ func cb_is_equal(c :Variant, e :Variant) -> bool: return GdObjects.equals(c,e)
func cb_is_not_equal(c :Variant, e :Variant) -> bool: return not GdObjects.equals(c, e)


func do_interrupt() -> void:
_interrupted = true


func _validate_callback(predicate :Callable, expected :Variant = null) -> void:
if _interrupted:
return
Expand All @@ -125,9 +133,7 @@ func _validate_callback(predicate :Callable, expected :Variant = null) -> void:
scene_tree.root.add_child(timer)
timer.add_to_group("GdUnitTimers")
@warning_ignore("return_value_discarded")
timer.timeout.connect(func do_interrupt() -> void:
_interrupted = true
, CONNECT_DEFERRED)
timer.timeout.connect(do_interrupt, CONNECT_DEFERRED)
timer.set_one_shot(true)
timer.start((_timeout/1000.0)*time_scale)
_sleep_timer = Timer.new()
Expand Down
8 changes: 8 additions & 0 deletions addons/gdUnit4/src/asserts/GdUnitSignalAssertImpl.gd
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,14 @@ func _init(emitter :Object) -> void:
GdAssertReports.reset_last_error_line_number()


func _notification(what :int) -> void:
if what == NOTIFICATION_PREDELETE:
_interrupted = true
if is_instance_valid(_emitter):
_signal_collector.unregister_emitter(_emitter)
_emitter = null


func report_success() -> GdUnitAssert:
GdAssertReports.report_success()
return self
Expand Down
8 changes: 8 additions & 0 deletions addons/gdUnit4/src/core/GdArrayTools.gd
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,14 @@ static func as_string(elements: Variant, encode_value := true) -> String:
return prefix + "[" + formatted + "]"


static func has_same_content(current: Array, other: Array) -> bool:
if current.size() != other.size(): return false
for element: Variant in current:
if not other.has(element): return false
if current.count(element) != other.count(element): return false
return true


static func _typeof_as_string(value :Variant) -> String:
var type := typeof(value)
# for untyped array we retun empty string
Expand Down
11 changes: 6 additions & 5 deletions addons/gdUnit4/src/core/GdUnitFileAccess.gd
Original file line number Diff line number Diff line change
Expand Up @@ -192,11 +192,12 @@ static func resource_as_string(resource_path :String) -> String:


static func make_qualified_path(path :String) -> String:
if not path.begins_with("res://"):
if path.begins_with("//"):
return path.replace("//", "res://")
if path.begins_with("/"):
return "res:/" + path
if path.begins_with("res://"):
return path
if path.begins_with("//"):
return path.replace("//", "res://")
if path.begins_with("/"):
return "res:/" + path
return path


Expand Down
4 changes: 2 additions & 2 deletions addons/gdUnit4/src/core/GdUnitSingleton.gd
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,10 @@ static func unregister(p_singleton :String, use_call_deferred :bool = false) ->

static func dispose(use_call_deferred :bool = false) -> void:
# use a copy because unregister is modify the singletons array
var singletons: PackedStringArray = Engine.get_meta(MEATA_KEY, PackedStringArray())
var singletons :PackedStringArray = Engine.get_meta(MEATA_KEY, PackedStringArray())
GdUnitTools.prints_verbose("----------------------------------------------------------------")
GdUnitTools.prints_verbose("Cleanup singletons %s" % singletons)
for singleton in singletons:
for singleton in PackedStringArray(singletons):
unregister(singleton, use_call_deferred)
Engine.remove_meta(MEATA_KEY)
GdUnitTools.prints_verbose("----------------------------------------------------------------")
55 changes: 53 additions & 2 deletions addons/gdUnit4/src/core/discovery/GdUnitTestDiscoverGuard.gd
Original file line number Diff line number Diff line change
@@ -1,22 +1,49 @@
extends RefCounted


# Caches all test indices for parameterized tests
class TestCaseIndicesCache:
var _cache := {}

func _key(resource_path: String, test_name: String) -> StringName:
return &"%s_%s" % [resource_path, test_name]


func contains_test_case(resource_path: String, test_name: String) -> bool:
return _cache.has(_key(resource_path, test_name))


func validate(resource_path: String, test_name: String, indices: PackedStringArray) -> bool:
var cached_indicies: PackedStringArray = _cache[_key(resource_path, test_name)]
return GdArrayTools.has_same_content(cached_indicies, indices)


func sync(resource_path: String, test_name: String, indices: PackedStringArray) -> void:
if indices.is_empty():
_cache[_key(resource_path, test_name)] = []
else:
_cache[_key(resource_path, test_name)] = indices

# contains all tracked test suites where discovered since editor start
# key : test suite resource_path
# value: the list of discovered test case names
var _discover_cache := {}

var discovered_test_case_indices_cache := TestCaseIndicesCache.new()


func _init() -> void:
# Register for discovery events to sync the cache
@warning_ignore("return_value_discarded")
GdUnitSignals.instance().gdunit_add_test_suite.connect(sync_cache)


func sync_cache(dto :GdUnitTestSuiteDto) -> void:
func sync_cache(dto: GdUnitTestSuiteDto) -> void:
var resource_path := ProjectSettings.localize_path(dto.path())
var discovered_test_cases :Array[String] = []
var discovered_test_cases: Array[String] = []
for test_case in dto.test_cases():
discovered_test_cases.append(test_case.name())
discovered_test_case_indices_cache.sync(resource_path, test_case.name(), test_case.test_case_names())
_discover_cache[resource_path] = discovered_test_cases


Expand Down Expand Up @@ -54,6 +81,19 @@ func discover(script: Script) -> void:
if not discovered_test_cases.has(test_case):
tests_added.append(test_case)

# We need to scan for parameterized test because of possible test data changes
# For more details look at https://github.com/MikeSchulze/gdUnit4/issues/592
for test_case_name in script_test_cases:
if discovered_test_case_indices_cache.contains_test_case(script_path, test_case_name):
var test_case: _TestCase = test_suite.find_child(test_case_name, false, false)
var test_indices := test_case.test_case_names()
if not discovered_test_case_indices_cache.validate(script_path, test_case_name, test_indices):
if !tests_removed.has(test_case_name):
tests_removed.append(test_case_name)
if !tests_added.has(test_case_name):
tests_added.append(test_case_name)
discovered_test_case_indices_cache.sync(script_path, test_case_name, test_indices)

# finally notify changes to the inspector
if not tests_removed.is_empty() or not tests_added.is_empty():
# emit deleted tests
Expand All @@ -68,16 +108,27 @@ func discover(script: Script) -> void:
var dto := GdUnitTestCaseDto.new()
dto = dto.deserialize(dto.serialize(test_case))
GdUnitSignals.instance().gdunit_event.emit(GdUnitEventTestDiscoverTestAdded.new(script_path, suite_name, dto))
# if the parameterized test fresh added we need to sync the cache
if not discovered_test_case_indices_cache.contains_test_case(script_path, test_name):
discovered_test_case_indices_cache.sync(script_path, test_name, dto.test_case_names())

# update the cache
_discover_cache[script_path] = discovered_test_cases
test_suite.queue_free()


func extract_test_functions(test_suite :Node) -> PackedStringArray:
return test_suite.get_children()\
.filter(func(child: Node) -> bool: return is_instance_of(child, _TestCase))\
.map(func (child: Node) -> String: return child.get_name())


func is_paramaterized_test(test_suite :Node, test_case_name: String) -> bool:
return test_suite.get_children()\
.filter(func(child: Node) -> bool: return child.name == test_case_name)\
.any(func (test: _TestCase) -> bool: return test.is_parameterized())


# 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)
Expand Down
2 changes: 2 additions & 0 deletions addons/gdUnit4/src/core/execution/GdUnitExecutionContext.gd
Original file line number Diff line number Diff line change
Expand Up @@ -348,5 +348,7 @@ func register_auto_free(obj: Variant) -> Variant:

## Runs the gdunit garbage collector to free registered object
func gc() -> void:
# unreference last used assert form the test to prevent memory leaks
GdUnitThreadManager.get_current_context().clear_assert()
await _memory_observer.gc()
orphan_monitor_stop()
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,7 @@ func _execute(context: GdUnitExecutionContext) -> void:
if _call_stage:
@warning_ignore("redundant_await")
await test_suite.after_test()
# unreference last used assert form the test to prevent memory leaks
GdUnitThreadManager.get_current_context().set_assert(null)

await context.gc()
await context.error_monitor_stop()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,6 @@ func _execute(context :GdUnitExecutionContext) -> void:

@warning_ignore("redundant_await")
await test_suite.after()
# unreference last used assert form the test to prevent memory leaks
GdUnitThreadManager.get_current_context().set_assert(null)
await context.gc()
var reports := context.build_reports(false)
fire_event(GdUnitEvent.new()\
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,19 @@ func fire_test_suite_skipped(context :GdUnitExecutionContext) -> void:
var skip_count := test_suite.get_child_count()
fire_event(GdUnitEvent.new()\
.suite_before(context.get_test_suite_path(), test_suite.get_name(), skip_count))


for test_case_index in context.test_suite.get_child_count():
# iterate only over test cases
var test_case := context.test_suite.get_child(test_case_index) as _TestCase
if not is_instance_valid(test_case):
continue
var test_case_context := GdUnitExecutionContext.of_test_case(context, test_case)
fire_event(GdUnitEvent.new()\
.test_before(test_case_context.get_test_suite_path(), test_case_context.get_test_suite_name(), test_case_context.get_test_case_name()))
fire_test_skipped(test_case_context)


var statistics := {
GdUnitEvent.ORPHAN_NODES: 0,
GdUnitEvent.ELAPSED_TIME: 0,
Expand All @@ -105,6 +118,35 @@ func fire_test_suite_skipped(context :GdUnitExecutionContext) -> void:
await (Engine.get_main_loop() as SceneTree).process_frame


func fire_test_skipped(context: GdUnitExecutionContext) -> void:
var test_case := context.test_case
var statistics := {
GdUnitEvent.ORPHAN_NODES: 0,
GdUnitEvent.ELAPSED_TIME: 0,
GdUnitEvent.WARNINGS: false,
GdUnitEvent.ERRORS: false,
GdUnitEvent.ERROR_COUNT: 0,
GdUnitEvent.FAILED: false,
GdUnitEvent.FAILED_COUNT: 0,
GdUnitEvent.SKIPPED: true,
GdUnitEvent.SKIPPED_COUNT: 1,
}
var report := GdUnitReport.new() \
.create(GdUnitReport.SKIPPED, test_case.line_number(), GdAssertMessages.test_skipped("Skipped from the entire test suite"))
fire_event(GdUnitEvent.new() \
.test_after(context.get_test_suite_path(),
context.get_test_suite_name(),
context.get_test_case_name(),
statistics,
[report]))
# finally fire test statistics report
fire_event(GdUnitEvent.new()\
.test_statistics(context.get_test_suite_path(),
context.get_test_suite_name(),
context.get_test_case_name(),
statistics))


func set_debug_mode(debug_mode :bool = false) -> void:
super.set_debug_mode(debug_mode)
_stage_before.set_debug_mode(debug_mode)
Expand Down
6 changes: 5 additions & 1 deletion addons/gdUnit4/src/core/parse/GdScriptParser.gd
Original file line number Diff line number Diff line change
Expand Up @@ -367,7 +367,7 @@ func get_function_descriptors(script: GDScript, included_functions: PackedString
var func_name: String = method_descriptor["name"]
if included_functions.is_empty() or func_name in included_functions:
# exclude type set/geters
if func_name in ["@type_setter", "@type_getter"]:
if is_getter_or_setter(func_name):
continue
if not fds.any(func(fd: GdFunctionDescriptor) -> bool: return fd.name() == func_name):
fds.append(GdFunctionDescriptor.extract_from(method_descriptor, false))
Expand All @@ -379,6 +379,10 @@ func get_function_descriptors(script: GDScript, included_functions: PackedString
return fds


func is_getter_or_setter(func_name: String) -> bool:
return func_name.begins_with("@") and (func_name.ends_with("getter") or func_name.ends_with("setter"))


func _parse_function_arguments(input: String) -> Dictionary:
var arguments := {}
var current_index := 0
Expand Down
Loading

0 comments on commit 1370f63

Please sign in to comment.