Skip to content

Commit 00aaa2b

Browse files
committed
GDScript test suite now has tests that fail until confirmed at end
Necessary because some operations cause Godot errors, which cause immediate return from the `test_*` GDScript function. However, the runner cannot differentiate that from a legitimate return at the end, as the only difference is a printed error message (which isn't accessible programmatically). As a result, a new pattern with mark_test_pending() + mark_test_succeeded() is introduced. If the former method is called without the latter, the test is considered failed.
1 parent cf0a33a commit 00aaa2b

File tree

4 files changed

+57
-5
lines changed

4 files changed

+57
-5
lines changed

itest/godot/InheritTests.gd

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,13 @@ extends ArrayTest
77

88
var test_suite: TestSuite = TestSuite.new()
99

10+
# Override public state management methods of TestSuite, since this is treated as a suite by the runner.
11+
func reset_state():
12+
test_suite.reset_state()
13+
14+
func is_test_failed() -> bool:
15+
return test_suite.is_test_failed()
16+
1017
# In order to reproduce the behavior discovered in https://github.com/godot-rust/gdext/issues/138
1118
# we must inherit a Godot Node. Because of this we can't just inherit TesSuite like the rest of the tests.
1219
func assert_that(what: bool, message: String = "") -> bool:

itest/godot/TestRunner.gd

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -87,11 +87,11 @@ func _ready():
8787

8888

8989
class GDScriptTestCase:
90-
var suite: Object
90+
var suite: RefCounted # not always TestSuite, e.g. InheritTests.
9191
var method_name: String
9292
var suite_name: String
9393

94-
func _init(suite: Object, method_name: String):
94+
func _init(suite: RefCounted, method_name: String):
9595
self.suite = suite
9696
self.method_name = method_name
9797
self.suite_name = _suite_name(suite)
@@ -100,17 +100,17 @@ class GDScriptTestCase:
100100
push_error("run unimplemented")
101101
return false
102102

103-
static func _suite_name(suite: Object) -> String:
103+
static func _suite_name(suite: RefCounted) -> String:
104104
var script: GDScript = suite.get_script()
105105
return str(script.resource_path.get_file().get_basename(), ".gd")
106106

107107
# Standard test case used for whenever something can be tested by just running a GDScript function.
108108
class GDScriptExecutableTestCase extends GDScriptTestCase:
109109
func run():
110110
# This is a no-op if the suite doesn't have this property.
111-
suite.set("_assertion_failed", false)
111+
suite.reset_state()
112112
var result = suite.call(method_name)
113-
var ok: bool = (result == true or result == null) and not suite.get("_assertion_failed")
113+
var ok: bool = (result == true or result == null) and not suite.is_test_failed()
114114
return ok
115115

116116
# Hardcoded test case used for special cases where the standard testing API is not sufficient.

itest/godot/TestSuite.gd

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,20 @@ class_name TestSuite
77
extends RefCounted
88

99
var _assertion_failed: bool = false
10+
var _pending: bool = false
11+
12+
# -----------------------------------------------------------------------------------------------------------------------------------------------
13+
# Public API, called by the test runner.
14+
15+
func reset_state():
16+
_assertion_failed = false
17+
_pending = false
18+
19+
func is_test_failed() -> bool:
20+
return _assertion_failed
21+
22+
# -----------------------------------------------------------------------------------------------------------------------------------------------
23+
# Protected API, called by individual test .gd files.
1024

1125
func print_newline():
1226
printerr()
@@ -45,6 +59,29 @@ func assert_eq(actual, expected, message: String = "") -> bool:
4559
# Note: stacktrace cannot be printed, because not connected to a debugging server (editor).
4660
return false
4761

62+
## Pre-emptively mark this test as "failed unless confirmed success". Use mark_test_succeeded() to rollback if test actually succeeds.
63+
##
64+
## For situations where statements coming after this will abort the test function without reliable ways to detect.
65+
func mark_test_pending():
66+
if _pending:
67+
push_error("Test is already pending.")
68+
_assertion_failed = true
69+
return
70+
71+
_pending = true
72+
_assertion_failed = true
73+
#print("Test will fail unless rolled back: ", message)
74+
75+
## Roll back the failure assumption if the test actually succeeded.
76+
func mark_test_succeeded():
77+
if not _pending:
78+
push_error("Cannot mark test as succeeded, test was not marked as pending.")
79+
_assertion_failed = true
80+
return
81+
82+
_pending = false
83+
_assertion_failed = false
84+
4885
# Disable error message printing from godot.
4986
#
5087
# Error messages are always re-enabled by the rust test runner after a test has been run.

itest/godot/input/GenFfiTests.template.gd

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ extends TestSuite
1212

1313
#(
1414
func test_varcall_IDENT():
15+
mark_test_pending()
1516
var ffi = GenFfi.new()
1617

1718
var from_rust: Variant = ffi.return_IDENT()
@@ -24,6 +25,7 @@ func test_varcall_IDENT():
2425
var mirrored: Variant = ffi.mirror_IDENT(from_gdscript)
2526
assert_eq(mirrored, from_gdscript, "mirrored == from_gdscript")
2627
_check_callconv("mirror_IDENT", "varcall")
28+
mark_test_succeeded()
2729
#)
2830

2931
# Godot currently does not support calling static methods via reflection, which is why we use an instance for the return_static_IDENT() call.
@@ -32,6 +34,7 @@ func test_varcall_IDENT():
3234
# so Godot cannot use ptrcalls anyway.
3335
#(
3436
func test_varcall_static_IDENT():
37+
mark_test_pending()
3538
var instance = GenFfi.new() # workaround
3639
var from_rust: Variant = instance.return_static_IDENT()
3740
_check_callconv("return_static_IDENT", "varcall")
@@ -43,10 +46,12 @@ func test_varcall_static_IDENT():
4346
var mirrored: Variant = GenFfi.mirror_static_IDENT(from_gdscript)
4447
assert_eq(mirrored, from_gdscript, "mirrored_static == from_gdscript")
4548
_check_callconv("mirror_static_IDENT", "varcall")
49+
mark_test_succeeded()
4650
#)
4751

4852
#(
4953
func test_ptrcall_IDENT():
54+
mark_test_pending()
5055
var ffi := GenFfi.new()
5156

5257
var from_rust: TYPE = ffi.return_IDENT()
@@ -59,10 +64,12 @@ func test_ptrcall_IDENT():
5964
var mirrored: TYPE = ffi.mirror_IDENT(from_gdscript)
6065
assert_eq(mirrored, from_gdscript, "mirrored == from_gdscript")
6166
_check_callconv("mirror_IDENT", "ptrcall")
67+
mark_test_succeeded()
6268
#)
6369

6470
#(
6571
func test_ptrcall_static_IDENT():
72+
mark_test_pending()
6673
var from_rust: TYPE = GenFfi.return_static_IDENT()
6774
_check_callconv("return_static_IDENT", "ptrcall")
6875

@@ -73,6 +80,7 @@ func test_ptrcall_static_IDENT():
7380
var mirrored: TYPE = GenFfi.mirror_static_IDENT(from_gdscript)
7481
assert_eq(mirrored, from_gdscript, "mirrored_static == from_gdscript")
7582
_check_callconv("mirror_static_IDENT", "ptrcall")
83+
mark_test_succeeded()
7684
#)
7785

7886
func _check_callconv(function: String, expected: String) -> void:

0 commit comments

Comments
 (0)