Skip to content

Commit 20cd346

Browse files
authored
Merge pull request #1203 from godot-rust/qol/failed-gdscript-calls
Verify that marshalling errors cause failed *GDScript* function
2 parents 5789b22 + abcaaf9 commit 20cd346

File tree

3 files changed

+86
-24
lines changed

3 files changed

+86
-24
lines changed

itest/godot/ManualFfiTests.gd

Lines changed: 63 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -92,9 +92,8 @@ func test_export_dyn_gd_should_fail_for_wrong_type():
9292
var dyn_gd_exporter = RefcDynGdVarDeclarer.new()
9393
var refc = RefcHealth.new()
9494

95-
disable_error_messages()
96-
dyn_gd_exporter.second = refc # Should fail.
97-
enable_error_messages()
95+
expect_fail()
96+
dyn_gd_exporter.second = refc # Causes current function to fail. Following code unreachable.
9897

9998
assert_fail("`DynGdExporter.second` should only accept NodeHealth and only if it implements `InstanceIdProvider` trait")
10099

@@ -310,19 +309,19 @@ func test_custom_property_wrong_values_1():
310309
return
311310

312311
var has_property: HasCustomProperty = HasCustomProperty.new()
313-
disable_error_messages()
314-
has_property.some_c_style_enum = 10 # Should fail.
315-
enable_error_messages()
312+
expect_fail()
313+
has_property.some_c_style_enum = 10 # Causes current function to fail. Following code unreachable.
314+
316315
assert_fail("HasCustomProperty.some_c_style_enum should only accept integers in the range `(0 ..= 2)`")
317316

318317
func test_custom_property_wrong_values_2():
319318
if runs_release():
320319
return
321320

322321
var has_property: HasCustomProperty = HasCustomProperty.new()
323-
disable_error_messages()
324-
has_property.not_exportable = {"a": "hello", "b": Callable()} # Should fail.
325-
enable_error_messages()
322+
expect_fail()
323+
has_property.not_exportable = {"a": "hello", "b": Callable()} # Causes current function to fail. Following code unreachable.
324+
326325
assert_fail("HasCustomProperty.not_exportable should only accept dictionaries with float values")
327326

328327
func test_option_export():
@@ -482,3 +481,58 @@ func test_renamed_func_get_set():
482481
assert_eq(obj.f1(), 84)
483482

484483
obj.free()
484+
485+
# -----------------------------------------------------------------------------------------------------------------------------------------------
486+
# Tests below verify the following:
487+
# Calling a typed Rust function with a Variant that cannot be converted to the Rust type will cause a failed function call on _GDScript_ side,
488+
# meaning the GDScript function aborts immediately. This happens because a `Variant -> T` conversion occurs dynamically *on GDScript side*,
489+
# before the Rust function is called.In contrast, panics inside the Rust function (e.g. variant.to::<T>()) just cause the *Rust* function to fail.
490+
#
491+
# Store arguments as Variant, as GDScript wouldn't parse script otherwise. Results in varcall being used.
492+
493+
func test_marshalling_fail_variant_type():
494+
if runs_release():
495+
return
496+
497+
# Expects Object, pass GString.
498+
var obj := ObjectTest.new()
499+
var arg: Variant = "not an object"
500+
expect_fail()
501+
obj.pass_object(arg) # Causes current function to fail. Following code unreachable.
502+
503+
assert_fail("GDScript function should fail after marshalling error (bad variant type)")
504+
505+
func test_marshalling_fail_non_null():
506+
if runs_release():
507+
return
508+
509+
# Expects Object, pass null.
510+
var obj := ObjectTest.new()
511+
512+
expect_fail()
513+
obj.pass_object(null) # Causes current function to fail. Following code unreachable.
514+
515+
assert_fail("GDScript function should fail after marshalling error (required non-null)")
516+
517+
func test_marshalling_fail_integer_overflow():
518+
if runs_release():
519+
return
520+
521+
# Expects i32. This overflows.
522+
var obj := ObjectTest.new()
523+
var arg: Variant = 9223372036854775807
524+
525+
expect_fail()
526+
obj.pass_i32(arg) # Causes current function to fail. Following code unreachable.
527+
528+
assert_fail("GDScript function should fail after marshalling error (int overflow)")
529+
530+
func test_marshalling_continues_on_panic():
531+
mark_test_pending()
532+
533+
# Expects i32. This overflows.
534+
var obj := ObjectTest.new()
535+
var result = obj.cause_panic() # Fails in Rust, current function continues.
536+
537+
assert_eq(result, Vector3.ZERO, "Default value returned on failed function call")
538+
mark_test_succeeded()

itest/godot/TestSuite.gd

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -12,20 +12,22 @@ var _pending: bool = false
1212
# -----------------------------------------------------------------------------------------------------------------------------------------------
1313
# Public API, called by the test runner.
1414

15-
func reset_state():
15+
func reset_state() -> void:
1616
_assertion_failed = false
1717
_pending = false
1818

19+
# Note: some tests disable error messages, but they are re-enabled by the Rust test runner before each test.
20+
1921
func is_test_failed() -> bool:
2022
return _assertion_failed
2123

2224
# -----------------------------------------------------------------------------------------------------------------------------------------------
2325
# Protected API, called by individual test .gd files.
2426

25-
func print_newline():
27+
func print_newline() -> void:
2628
printerr()
2729

28-
func print_error(s: String):
30+
func print_error(s: String) -> void:
2931
push_error(s)
3032

3133
## Asserts that `what` is `true`, but does not abort the test. Returns `what` so you can return
@@ -62,7 +64,7 @@ func assert_eq(actual, expected, message: String = "") -> bool:
6264
## Pre-emptively mark this test as "failed unless confirmed success". Use mark_test_succeeded() to rollback if test actually succeeds.
6365
##
6466
## For situations where statements coming after this will abort the test function without reliable ways to detect.
65-
func mark_test_pending():
67+
func mark_test_pending() -> void:
6668
if _pending:
6769
push_error("Test is already pending.")
6870
_assertion_failed = true
@@ -73,7 +75,7 @@ func mark_test_pending():
7375
#print("Test will fail unless rolled back: ", message)
7476

7577
## Roll back the failure assumption if the test actually succeeded.
76-
func mark_test_succeeded():
78+
func mark_test_succeeded() -> void:
7779
if not _pending:
7880
push_error("Cannot mark test as succeeded, test was not marked as pending.")
7981
_assertion_failed = true
@@ -82,17 +84,15 @@ func mark_test_succeeded():
8284
_pending = false
8385
_assertion_failed = false
8486

85-
# Disable error message printing from godot.
86-
#
87-
# Error messages are always re-enabled by the rust test runner after a test has been run.
88-
func disable_error_messages():
89-
Engine.print_error_messages = false
87+
## Expects that one of the next statements will cause the calling GDScript function to abort.
88+
##
89+
## This should always be followed by an assert_fail() call at the end of the function.
90+
func expect_fail() -> void:
91+
if runs_release():
92+
push_warning("Release builds do not have proper error handling, the calling test is likely to not work as expected.")
9093

91-
# Enable error message printing from godot.
92-
#
93-
# Error messages are always re-enabled by the rust test runner after a test has been run.
94-
func enable_error_messages():
95-
Engine.print_error_messages = true
94+
# Note: error messages are re-enabled by the Rust test runner before each test.
95+
Engine.print_error_messages = false
9696

9797
# Asserts that the test failed to reach this point. You should disable error messages before running code
9898
# that is expected to print an error message that would otherwise cause the CI to report failure.

itest/rust/src/object_tests/object_test.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1023,6 +1023,14 @@ pub mod object_test_gd {
10231023
// Forward compat: .upcast() here becomes a breaking change if we generalize AsArg to include derived->base conversions.
10241024
array![&Self::return_self().upcast()]
10251025
}
1026+
1027+
#[func]
1028+
fn pass_i32(&self, _i: i32) {}
1029+
1030+
#[func]
1031+
fn cause_panic(&self) -> Vector3 {
1032+
panic!("Rust panics")
1033+
}
10261034
}
10271035

10281036
// ----------------------------------------------------------------------------------------------------------------------------------------------

0 commit comments

Comments
 (0)