Skip to content

Commit a6063f5

Browse files
bors[bot]Bromeon
andauthored
Merge #896
896: Refactor integration tests by reusing godot_test macro r=Bromeon a=Bromeon Reduces boilerplate of the form ```rs fn test_with_certain_name() -> bool { println!(" -- test_with_certain_name"); let ok = std::panic::catch_unwind(|| { // actual test code here }) .is_ok(); if !ok { godot_error!(" !! Test test_with_certain_name failed"); } ok } ``` to ```rs godot_itest! { test_with_certain_name { // actual test code here }} ``` which reduces repetition and focuses on important things. This is done by slightly adjusting the existing `godot_test` macro to `godot_itest`, which is needed so it can also be used in the `tests` crate. This change leads to a net removal of 300 lines of code. --- I was also considering a proc-macro attribute ```rs #[godot_test] fn test_with_certain_name() { // actual test code here } ``` which might be a tiny bit nicer syntax-wise, and even started implementing it. But I think the declarative macro does the job, is simpler and likely faster to compile (as we don't need `syn` to tokenize the entire code). --- Tests of this form now all have the `#[must_use]` attribute, yielding a warning if a boolean test result is ignored. Co-authored-by: Jan Haller <[email protected]>
2 parents c803753 + 2c3bac2 commit a6063f5

15 files changed

+570
-867
lines changed

gdnative-core/src/macros.rs

+37-3
Original file line numberDiff line numberDiff line change
@@ -208,12 +208,15 @@ macro_rules! impl_basic_traits_as_sys {
208208
)
209209
}
210210

211-
macro_rules! godot_test {
212-
($($test_name:ident $body:block)*) => {
211+
#[doc(hidden)]
212+
#[macro_export]
213+
macro_rules! godot_test_impl {
214+
( $( $test_name:ident $body:block $($attrs:tt)* )* ) => {
213215
$(
214-
#[cfg(feature = "gd-test")]
216+
$($attrs)*
215217
#[doc(hidden)]
216218
#[inline]
219+
#[must_use]
217220
pub fn $test_name() -> bool {
218221
let str_name = stringify!($test_name);
219222
println!(" -- {}", str_name);
@@ -231,3 +234,34 @@ macro_rules! godot_test {
231234
)*
232235
}
233236
}
237+
238+
/// Declares a test to be run with the Godot engine (i.e. not a pure Rust unit test).
239+
///
240+
/// Creates a wrapper function that catches panics, prints errors and returns true/false.
241+
/// To be manually invoked in higher-level test routine.
242+
///
243+
/// This macro is designed to be used within the current crate only, hence the #[cfg] attribute.
244+
#[doc(hidden)]
245+
macro_rules! godot_test {
246+
($($test_name:ident $body:block)*) => {
247+
$(
248+
godot_test_impl!($test_name $body #[cfg(feature = "gd-test")]);
249+
)*
250+
}
251+
}
252+
253+
/// Declares a test to be run with the Godot engine (i.e. not a pure Rust unit test).
254+
///
255+
/// Creates a wrapper function that catches panics, prints errors and returns true/false.
256+
/// To be manually invoked in higher-level test routine.
257+
///
258+
/// This macro is designed to be used within the `test` crate, hence the method is always declared (not only in certain features).
259+
#[doc(hidden)]
260+
#[macro_export]
261+
macro_rules! godot_itest {
262+
($($test_name:ident $body:block)*) => {
263+
$(
264+
$crate::godot_test_impl!($test_name $body);
265+
)*
266+
}
267+
}

impl/proc-macros/src/lib.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,14 @@ mod pool_array_element;
1212

1313
#[proc_macro]
1414
pub fn impl_typed_array_element(input: TokenStream) -> TokenStream {
15-
self::pool_array_element::impl_element(input)
15+
pool_array_element::impl_element(input)
1616
.unwrap_or_else(to_compile_errors)
1717
.into()
1818
}
1919

2020
#[proc_macro]
2121
pub fn decl_typed_array_element(input: TokenStream) -> TokenStream {
22-
self::pool_array_element::decl_element(input)
22+
pool_array_element::decl_element(input)
2323
.unwrap_or_else(to_compile_errors)
2424
.into()
2525
}

test/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ custom-godot = ["gdnative/custom-godot"]
1717

1818
[dependencies]
1919
gdnative = { path = "../gdnative", features = ["gd-test", "serde", "async"] }
20+
gdnative-core = { path = "../gdnative-core" }
2021
approx = "0.5"
2122
ron = "0.7"
2223
serde = "1"

test/src/lib.rs

+46-79
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
#![allow(deprecated)]
33

44
use gdnative::prelude::*;
5+
use gdnative_core::godot_itest;
56

67
mod test_as_arg;
78
mod test_async;
@@ -79,25 +80,14 @@ pub extern "C" fn run_tests(
7980
status &= test_variant_call_args::run_tests();
8081
status &= test_variant_ops::run_tests();
8182

82-
gdnative::core_types::Variant::new(status).leak()
83+
Variant::new(status).leak()
8384
}
8485

85-
fn test_underscore_method_binding() -> bool {
86-
println!(" -- test_underscore_method_binding");
87-
88-
let ok = std::panic::catch_unwind(|| {
89-
let script = gdnative::api::NativeScript::new();
90-
let result = script._new(&[]);
91-
assert_eq!(Variant::nil(), result);
92-
})
93-
.is_ok();
94-
95-
if !ok {
96-
godot_error!(" !! Test test_underscore_method_binding failed");
97-
}
98-
99-
ok
100-
}
86+
godot_itest! { test_underscore_method_binding {
87+
let script = gdnative::api::NativeScript::new();
88+
let result = script._new(&[]);
89+
assert_eq!(Variant::nil(), result);
90+
}}
10191

10292
#[derive(NativeClass)]
10393
#[inherit(Reference)]
@@ -152,31 +142,19 @@ impl Foo {
152142
}
153143
}
154144

155-
fn test_rust_class_construction() -> bool {
156-
println!(" -- test_rust_class_construction");
157-
158-
let ok = std::panic::catch_unwind(|| {
159-
let foo = Foo::new_instance();
145+
godot_itest! { test_rust_class_construction {
146+
let foo = Foo::new_instance();
147+
assert_eq!(Ok(42), foo.map(|foo, owner| { foo.answer(&*owner) }));
160148

161-
assert_eq!(Ok(42), foo.map(|foo, owner| { foo.answer(&*owner) }));
149+
let base = foo.into_base();
150+
assert_eq!(Some(42), unsafe { base.call("answer", &[]).to() });
162151

163-
let base = foo.into_base();
164-
assert_eq!(Some(42), unsafe { base.call("answer", &[]).to() });
152+
let foo = Instance::<Foo, _>::try_from_base(base).expect("should be able to downcast");
153+
assert_eq!(Ok(42), foo.map(|foo, owner| { foo.answer(&*owner) }));
165154

166-
let foo = Instance::<Foo, _>::try_from_base(base).expect("should be able to downcast");
167-
assert_eq!(Ok(42), foo.map(|foo, owner| { foo.answer(&*owner) }));
168-
169-
let base = foo.into_base();
170-
assert!(Instance::<NotFoo, _>::try_from_base(base).is_err());
171-
})
172-
.is_ok();
173-
174-
if !ok {
175-
godot_error!(" !! Test test_rust_class_construction failed");
176-
}
177-
178-
ok
179-
}
155+
let base = foo.into_base();
156+
assert!(Instance::<NotFoo, _>::try_from_base(base).is_err());
157+
}}
180158

181159
#[derive(NativeClass)]
182160
#[inherit(Reference)]
@@ -205,60 +183,49 @@ impl OptionalArgs {
205183
}
206184
}
207185

208-
fn test_from_instance_id() -> bool {
209-
println!(" -- test_from_instance_id");
186+
godot_itest! { test_from_instance_id {
187+
assert!(unsafe { Node::try_from_instance_id(22).is_none() });
188+
assert!(unsafe { Node::try_from_instance_id(42).is_none() });
189+
assert!(unsafe { Node::try_from_instance_id(503).is_none() });
210190

211-
let ok = std::panic::catch_unwind(|| {
212-
assert!(unsafe { Node::try_from_instance_id(22).is_none() });
213-
assert!(unsafe { Node::try_from_instance_id(42).is_none() });
214-
assert!(unsafe { Node::try_from_instance_id(503).is_none() });
191+
let instance_id;
215192

216-
let instance_id;
193+
{
194+
let foo = unsafe { Node::new().into_shared().assume_safe() };
195+
foo.set_name("foo");
217196

218-
{
219-
let foo = unsafe { Node::new().into_shared().assume_safe() };
220-
foo.set_name("foo");
197+
instance_id = foo.get_instance_id();
221198

222-
instance_id = foo.get_instance_id();
223-
224-
assert!(unsafe { Reference::try_from_instance_id(instance_id).is_none() });
225-
226-
let reconstructed = unsafe { Node::from_instance_id(instance_id) };
227-
assert_eq!("foo", reconstructed.name().to_string());
228-
229-
unsafe { foo.assume_unique().free() };
230-
}
199+
assert!(unsafe { Reference::try_from_instance_id(instance_id).is_none() });
231200

232-
assert!(unsafe { Node::try_from_instance_id(instance_id).is_none() });
201+
let reconstructed = unsafe { Node::from_instance_id(instance_id) };
202+
assert_eq!("foo", reconstructed.name().to_string());
233203

234-
let instance_id;
204+
unsafe { foo.assume_unique().free() };
205+
}
235206

236-
{
237-
let foo = Reference::new().into_shared();
238-
let foo = unsafe { foo.assume_safe() };
239-
foo.set_meta("foo", "bar");
207+
assert!(unsafe { Node::try_from_instance_id(instance_id).is_none() });
240208

241-
instance_id = foo.get_instance_id();
209+
let instance_id;
242210

243-
assert!(unsafe { Node::try_from_instance_id(instance_id).is_none() });
211+
{
212+
let foo = Reference::new().into_shared();
213+
let foo = unsafe { foo.assume_safe() };
214+
foo.set_meta("foo", "bar");
244215

245-
let reconstructed = unsafe { Reference::from_instance_id(instance_id) };
246-
assert_eq!(
247-
"bar",
248-
String::from_variant(&reconstructed.get_meta("foo")).unwrap()
249-
);
250-
}
216+
instance_id = foo.get_instance_id();
251217

252-
assert!(unsafe { Reference::try_from_instance_id(instance_id).is_none() });
253-
})
254-
.is_ok();
218+
assert!(unsafe { Node::try_from_instance_id(instance_id).is_none() });
255219

256-
if !ok {
257-
godot_error!(" !! Test test_from_instance_id failed");
220+
let reconstructed = unsafe { Reference::from_instance_id(instance_id) };
221+
assert_eq!(
222+
"bar",
223+
String::from_variant(&reconstructed.get_meta("foo")).unwrap()
224+
);
258225
}
259226

260-
ok
261-
}
227+
assert!(unsafe { Reference::try_from_instance_id(instance_id).is_none() });
228+
}}
262229

263230
fn init(handle: InitHandle) {
264231
handle.add_class::<Foo>();

test/src/test_as_arg.rs

+7-17
Original file line numberDiff line numberDiff line change
@@ -8,20 +8,10 @@ pub(crate) fn register(handle: InitHandle) {
88
}
99

1010
pub(crate) fn run_tests() -> bool {
11-
println!(" -- test_as_arg");
11+
let mut ok = true;
1212

13-
let ok = std::panic::catch_unwind(|| {
14-
println!(" -- test_ref_as_arg");
15-
test_ref_as_arg();
16-
17-
println!(" -- test_instance_as_arg");
18-
test_instance_as_arg();
19-
})
20-
.is_ok();
21-
22-
if !ok {
23-
godot_error!(" !! Test test_as_arg failed");
24-
}
13+
ok &= test_as_arg_ref();
14+
ok &= test_as_arg_instance();
2515

2616
ok
2717
}
@@ -40,7 +30,7 @@ impl MyNode {}
4030

4131
// ----------------------------------------------------------------------------------------------------------------------------------------------
4232

43-
fn test_ref_as_arg() {
33+
crate::godot_itest! { test_as_arg_ref {
4434
// Ref<T, Unique>
4535
add_node_with(|n: Ref<Node2D, Unique>| n);
4636

@@ -56,9 +46,9 @@ fn test_ref_as_arg() {
5646

5747
// TRef<T, Shared>
5848
add_node_with(|n: Ref<Node2D, Unique>| unsafe { n.into_shared().assume_safe() });
59-
}
49+
}}
6050

61-
fn test_instance_as_arg() {
51+
crate::godot_itest! { test_as_arg_instance {
6252
// Instance<T, Unique>
6353
add_instance_with(|n: Instance<MyNode, Unique>| n);
6454

@@ -74,7 +64,7 @@ fn test_instance_as_arg() {
7464

7565
// TInstance<T, Shared>
7666
add_instance_with(|n: Instance<MyNode, Unique>| unsafe { n.into_shared().assume_safe() });
77-
}
67+
}}
7868

7969
fn add_node_with<F, T>(to_arg: F)
8070
where

test/src/test_constructor.rs

+29-40
Original file line numberDiff line numberDiff line change
@@ -27,43 +27,32 @@ fn test_constructor() -> bool {
2727
true
2828
}
2929

30-
fn test_from_class_name() -> bool {
31-
println!(" -- test_from_class_name");
32-
33-
let ok = std::panic::catch_unwind(|| {
34-
// Since this method is restricted to Godot types, there is no way we can detect
35-
// here whether any invalid objects are leaked. Instead, the CI script is modified
36-
// to look at stdout for any reported leaks.
37-
38-
let node = Ref::<Node, _>::by_class_name("Node2D").unwrap();
39-
assert_eq!("Node2D", node.get_class().to_string());
40-
let node = node.cast::<Node2D>().unwrap();
41-
assert_eq!("Node2D", node.get_class().to_string());
42-
let _ = node.position();
43-
node.free();
44-
45-
let shader = Ref::<Reference, _>::by_class_name("Shader").unwrap();
46-
assert_eq!("Shader", &shader.get_class().to_string());
47-
let shader = shader.cast::<Shader>().unwrap();
48-
assert_eq!("Shader", &shader.get_class().to_string());
49-
50-
let none = Ref::<Object, _>::by_class_name("Shader");
51-
assert!(none.is_none());
52-
53-
let none = Ref::<Node2D, _>::by_class_name("Spatial");
54-
assert!(none.is_none());
55-
56-
let none = Ref::<Shader, _>::by_class_name("AudioEffectReverb");
57-
assert!(none.is_none());
58-
59-
let none = Ref::<Object, _>::by_class_name("ClassThatDoesNotExistProbably");
60-
assert!(none.is_none());
61-
})
62-
.is_ok();
63-
64-
if !ok {
65-
godot_error!(" !! Test test_from_class_name failed");
66-
}
67-
68-
ok
69-
}
30+
crate::godot_itest! { test_from_class_name {
31+
// Since this method is restricted to Godot types, there is no way we can detect
32+
// here whether any invalid objects are leaked. Instead, the CI script is modified
33+
// to look at stdout for any reported leaks.
34+
35+
let node = Ref::<Node, _>::by_class_name("Node2D").unwrap();
36+
assert_eq!("Node2D", node.get_class().to_string());
37+
let node = node.cast::<Node2D>().unwrap();
38+
assert_eq!("Node2D", node.get_class().to_string());
39+
let _ = node.position();
40+
node.free();
41+
42+
let shader = Ref::<Reference, _>::by_class_name("Shader").unwrap();
43+
assert_eq!("Shader", &shader.get_class().to_string());
44+
let shader = shader.cast::<Shader>().unwrap();
45+
assert_eq!("Shader", &shader.get_class().to_string());
46+
47+
let none = Ref::<Object, _>::by_class_name("Shader");
48+
assert!(none.is_none());
49+
50+
let none = Ref::<Node2D, _>::by_class_name("Spatial");
51+
assert!(none.is_none());
52+
53+
let none = Ref::<Shader, _>::by_class_name("AudioEffectReverb");
54+
assert!(none.is_none());
55+
56+
let none = Ref::<Object, _>::by_class_name("ClassThatDoesNotExistProbably");
57+
assert!(none.is_none());
58+
}}

0 commit comments

Comments
 (0)