Skip to content

Commit

Permalink
Reduce boilerplate integration tests with godot_itest! macro
Browse files Browse the repository at this point in the history
  • Loading branch information
Bromeon committed May 21, 2022
1 parent 9c604f2 commit 2c3bac2
Show file tree
Hide file tree
Showing 13 changed files with 523 additions and 845 deletions.
3 changes: 1 addition & 2 deletions gdnative-core/src/macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,6 @@ macro_rules! godot_test {
}
}


/// Declares a test to be run with the Godot engine (i.e. not a pure Rust unit test).
///
/// Creates a wrapper function that catches panics, prints errors and returns true/false.
Expand All @@ -265,4 +264,4 @@ macro_rules! godot_itest {
$crate::godot_test_impl!($test_name $body);
)*
}
}
}
123 changes: 45 additions & 78 deletions test/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
#![allow(deprecated)]

use gdnative::prelude::*;
use gdnative_core::godot_itest;

mod test_as_arg;
mod test_async;
Expand Down Expand Up @@ -82,22 +83,11 @@ pub extern "C" fn run_tests(
Variant::new(status).leak()
}

fn test_underscore_method_binding() -> bool {
println!(" -- test_underscore_method_binding");

let ok = std::panic::catch_unwind(|| {
let script = gdnative::api::NativeScript::new();
let result = script._new(&[]);
assert_eq!(Variant::nil(), result);
})
.is_ok();

if !ok {
godot_error!(" !! Test test_underscore_method_binding failed");
}

ok
}
godot_itest! { test_underscore_method_binding {
let script = gdnative::api::NativeScript::new();
let result = script._new(&[]);
assert_eq!(Variant::nil(), result);
}}

#[derive(NativeClass)]
#[inherit(Reference)]
Expand Down Expand Up @@ -152,31 +142,19 @@ impl Foo {
}
}

fn test_rust_class_construction() -> bool {
println!(" -- test_rust_class_construction");

let ok = std::panic::catch_unwind(|| {
let foo = Foo::new_instance();
godot_itest! { test_rust_class_construction {
let foo = Foo::new_instance();
assert_eq!(Ok(42), foo.map(|foo, owner| { foo.answer(&*owner) }));

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

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

let foo = Instance::<Foo, _>::try_from_base(base).expect("should be able to downcast");
assert_eq!(Ok(42), foo.map(|foo, owner| { foo.answer(&*owner) }));

let base = foo.into_base();
assert!(Instance::<NotFoo, _>::try_from_base(base).is_err());
})
.is_ok();

if !ok {
godot_error!(" !! Test test_rust_class_construction failed");
}

ok
}
let base = foo.into_base();
assert!(Instance::<NotFoo, _>::try_from_base(base).is_err());
}}

#[derive(NativeClass)]
#[inherit(Reference)]
Expand Down Expand Up @@ -205,60 +183,49 @@ impl OptionalArgs {
}
}

fn test_from_instance_id() -> bool {
println!(" -- test_from_instance_id");
godot_itest! { test_from_instance_id {
assert!(unsafe { Node::try_from_instance_id(22).is_none() });
assert!(unsafe { Node::try_from_instance_id(42).is_none() });
assert!(unsafe { Node::try_from_instance_id(503).is_none() });

let ok = std::panic::catch_unwind(|| {
assert!(unsafe { Node::try_from_instance_id(22).is_none() });
assert!(unsafe { Node::try_from_instance_id(42).is_none() });
assert!(unsafe { Node::try_from_instance_id(503).is_none() });
let instance_id;

let instance_id;
{
let foo = unsafe { Node::new().into_shared().assume_safe() };
foo.set_name("foo");

{
let foo = unsafe { Node::new().into_shared().assume_safe() };
foo.set_name("foo");
instance_id = foo.get_instance_id();

instance_id = foo.get_instance_id();

assert!(unsafe { Reference::try_from_instance_id(instance_id).is_none() });

let reconstructed = unsafe { Node::from_instance_id(instance_id) };
assert_eq!("foo", reconstructed.name().to_string());

unsafe { foo.assume_unique().free() };
}
assert!(unsafe { Reference::try_from_instance_id(instance_id).is_none() });

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

let instance_id;
unsafe { foo.assume_unique().free() };
}

{
let foo = Reference::new().into_shared();
let foo = unsafe { foo.assume_safe() };
foo.set_meta("foo", "bar");
assert!(unsafe { Node::try_from_instance_id(instance_id).is_none() });

instance_id = foo.get_instance_id();
let instance_id;

assert!(unsafe { Node::try_from_instance_id(instance_id).is_none() });
{
let foo = Reference::new().into_shared();
let foo = unsafe { foo.assume_safe() };
foo.set_meta("foo", "bar");

let reconstructed = unsafe { Reference::from_instance_id(instance_id) };
assert_eq!(
"bar",
String::from_variant(&reconstructed.get_meta("foo")).unwrap()
);
}
instance_id = foo.get_instance_id();

assert!(unsafe { Reference::try_from_instance_id(instance_id).is_none() });
})
.is_ok();
assert!(unsafe { Node::try_from_instance_id(instance_id).is_none() });

if !ok {
godot_error!(" !! Test test_from_instance_id failed");
let reconstructed = unsafe { Reference::from_instance_id(instance_id) };
assert_eq!(
"bar",
String::from_variant(&reconstructed.get_meta("foo")).unwrap()
);
}

ok
}
assert!(unsafe { Reference::try_from_instance_id(instance_id).is_none() });
}}

fn init(handle: InitHandle) {
handle.add_class::<Foo>();
Expand Down
24 changes: 7 additions & 17 deletions test/src/test_as_arg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,10 @@ pub(crate) fn register(handle: InitHandle) {
}

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

let ok = std::panic::catch_unwind(|| {
println!(" -- test_ref_as_arg");
test_ref_as_arg();

println!(" -- test_instance_as_arg");
test_instance_as_arg();
})
.is_ok();

if !ok {
godot_error!(" !! Test test_as_arg failed");
}
ok &= test_as_arg_ref();
ok &= test_as_arg_instance();

ok
}
Expand All @@ -40,7 +30,7 @@ impl MyNode {}

// ----------------------------------------------------------------------------------------------------------------------------------------------

fn test_ref_as_arg() {
crate::godot_itest! { test_as_arg_ref {
// Ref<T, Unique>
add_node_with(|n: Ref<Node2D, Unique>| n);

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

// TRef<T, Shared>
add_node_with(|n: Ref<Node2D, Unique>| unsafe { n.into_shared().assume_safe() });
}
}}

fn test_instance_as_arg() {
crate::godot_itest! { test_as_arg_instance {
// Instance<T, Unique>
add_instance_with(|n: Instance<MyNode, Unique>| n);

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

// TInstance<T, Shared>
add_instance_with(|n: Instance<MyNode, Unique>| unsafe { n.into_shared().assume_safe() });
}
}}

fn add_node_with<F, T>(to_arg: F)
where
Expand Down
69 changes: 29 additions & 40 deletions test/src/test_constructor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,43 +27,32 @@ fn test_constructor() -> bool {
true
}

fn test_from_class_name() -> bool {
println!(" -- test_from_class_name");

let ok = std::panic::catch_unwind(|| {
// Since this method is restricted to Godot types, there is no way we can detect
// here whether any invalid objects are leaked. Instead, the CI script is modified
// to look at stdout for any reported leaks.

let node = Ref::<Node, _>::by_class_name("Node2D").unwrap();
assert_eq!("Node2D", node.get_class().to_string());
let node = node.cast::<Node2D>().unwrap();
assert_eq!("Node2D", node.get_class().to_string());
let _ = node.position();
node.free();

let shader = Ref::<Reference, _>::by_class_name("Shader").unwrap();
assert_eq!("Shader", &shader.get_class().to_string());
let shader = shader.cast::<Shader>().unwrap();
assert_eq!("Shader", &shader.get_class().to_string());

let none = Ref::<Object, _>::by_class_name("Shader");
assert!(none.is_none());

let none = Ref::<Node2D, _>::by_class_name("Spatial");
assert!(none.is_none());

let none = Ref::<Shader, _>::by_class_name("AudioEffectReverb");
assert!(none.is_none());

let none = Ref::<Object, _>::by_class_name("ClassThatDoesNotExistProbably");
assert!(none.is_none());
})
.is_ok();

if !ok {
godot_error!(" !! Test test_from_class_name failed");
}

ok
}
crate::godot_itest! { test_from_class_name {
// Since this method is restricted to Godot types, there is no way we can detect
// here whether any invalid objects are leaked. Instead, the CI script is modified
// to look at stdout for any reported leaks.

let node = Ref::<Node, _>::by_class_name("Node2D").unwrap();
assert_eq!("Node2D", node.get_class().to_string());
let node = node.cast::<Node2D>().unwrap();
assert_eq!("Node2D", node.get_class().to_string());
let _ = node.position();
node.free();

let shader = Ref::<Reference, _>::by_class_name("Shader").unwrap();
assert_eq!("Shader", &shader.get_class().to_string());
let shader = shader.cast::<Shader>().unwrap();
assert_eq!("Shader", &shader.get_class().to_string());

let none = Ref::<Object, _>::by_class_name("Shader");
assert!(none.is_none());

let none = Ref::<Node2D, _>::by_class_name("Spatial");
assert!(none.is_none());

let none = Ref::<Shader, _>::by_class_name("AudioEffectReverb");
assert!(none.is_none());

let none = Ref::<Object, _>::by_class_name("ClassThatDoesNotExistProbably");
assert!(none.is_none());
}}
Loading

0 comments on commit 2c3bac2

Please sign in to comment.