Skip to content

Commit 6198574

Browse files
committed
itests for Unique type
Some tests to check if it works as intended. More real world use-cases would help.
1 parent 5fe9464 commit 6198574

3 files changed

Lines changed: 132 additions & 1 deletion

File tree

check.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -195,7 +195,7 @@ function cmd_test() {
195195

196196
function cmd_itest() {
197197
findGodot && \
198-
run cargo build -p itest "${extraCargoArgs[@]}" || return 1
198+
run cargo build -p itest --features itest/experimental-threads --features itest/codegen-full "${extraCargoArgs[@]}" || return 1
199199

200200
# Keep in sync with: .github/composite/godot-itest/action.yml (steps "Run Godot integration tests" and "Check for memory leaks").
201201

itest/rust/src/object_tests/mod.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,5 +33,8 @@ mod validate_property_test;
3333
mod virtual_methods_niche_test;
3434
mod virtual_methods_test;
3535

36+
#[cfg(feature = "experimental-threads")]
37+
mod thread_safety;
38+
3639
// Need to test this in the init level method.
3740
pub use init_stage_test::*;
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
/*
2+
* Copyright (c) godot-rust; Bromeon and contributors.
3+
* This Source Code Form is subject to the terms of the Mozilla Public
4+
* License, v. 2.0. If a copy of the MPL was not distributed with this
5+
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
6+
*/
7+
8+
use godot::builtin::VarDictionary;
9+
use godot::classes::class_macros::private::virtuals::Os::Vector3;
10+
use godot::classes::mesh::PrimitiveType;
11+
use godot::classes::{ArrayMesh, Node, RefCounted, Resource};
12+
use godot::meta::{ToGodot, owned_into_arg};
13+
use godot::obj::{Gd, NewAlloc, NewGd, Unique};
14+
15+
use crate::framework::{expect_panic_or_nothing, itest};
16+
17+
#[itest]
18+
fn valid_use_case() {
19+
std::thread::spawn(|| {
20+
let shadow_mesh: Unique<Gd<ArrayMesh>> = Unique::new_gd();
21+
let mut mesh: Unique<Gd<ArrayMesh>> = Unique::new_gd();
22+
23+
mesh.apply_gd(|mesh| {
24+
mesh.set_shadow_mesh(shadow_mesh);
25+
});
26+
})
27+
.join()
28+
.unwrap();
29+
}
30+
31+
#[itest]
32+
fn trying_to_cheat() {
33+
std::thread::spawn(|| {
34+
thread_local! {
35+
static CHILD_NODE: Gd<Node> = Node::new_alloc();
36+
}
37+
38+
let mut node = Unique::<Gd<Node>>::new_alloc();
39+
let mut dict = Unique::<VarDictionary>::new();
40+
41+
expect_panic_or_nothing("thread should panic when passing by ref", || {
42+
node.apply_gd(|node: &mut Node| {
43+
let owned_node = CHILD_NODE.with(|value: &Gd<Node>| value.clone());
44+
node.add_child(&owned_node);
45+
});
46+
});
47+
48+
expect_panic_or_nothing("thread should panic when passing by option ref", || {
49+
node.apply_gd(|node: &mut Node| {
50+
let owned_node = CHILD_NODE.with(|value: &Gd<Node>| value.clone());
51+
node.set_owner(&owned_node);
52+
});
53+
});
54+
55+
expect_panic_or_nothing("thread should panic when passing object by variant", || {
56+
dict.apply(|dict| {
57+
let obj = RefCounted::new_gd();
58+
dict.contains_key(&obj.to_variant());
59+
});
60+
});
61+
62+
expect_panic_or_nothing("thread should panic when passing by own reference", || {
63+
dict.apply(|dict| {
64+
let _ = dict.insert("key", &dict.clone());
65+
});
66+
});
67+
68+
expect_panic_or_nothing("thread should panic when passing by Gd<T> by value", || {
69+
node.apply_gd(|node: &mut Node| {
70+
let owned_node = CHILD_NODE.with(|value: &Gd<Node>| value.clone());
71+
node.add_child(owned_into_arg(owned_node));
72+
});
73+
});
74+
75+
// Test clean-up to avoid memory leaks.
76+
CHILD_NODE.with(|value| value.clone().free());
77+
node.share().free();
78+
})
79+
.join()
80+
.unwrap();
81+
}
82+
83+
#[itest]
84+
fn recursive_unique_check() {
85+
let resource = Resource::new_gd();
86+
87+
let unique_res = Unique::try_from_ref_counted(resource);
88+
89+
assert!(
90+
unique_res.is_some(),
91+
"Uniqueness verification with a single ref should succeed"
92+
);
93+
94+
let resource = unique_res.unwrap().share();
95+
let second_ref = resource.clone();
96+
97+
assert!(
98+
Unique::try_from_ref_counted(resource).is_none(),
99+
"Uniqueness verification with two refs should fail"
100+
);
101+
102+
drop(second_ref);
103+
}
104+
105+
#[itest]
106+
#[cfg(feature = "codegen-full")]
107+
fn sub_thread_surface_tool() {
108+
use godot::classes::SurfaceTool;
109+
110+
let result = std::thread::spawn(|| {
111+
let mut builder: Gd<SurfaceTool> = SurfaceTool::new_gd();
112+
113+
builder.begin(PrimitiveType::TRIANGLES);
114+
builder.add_vertex(Vector3::new(1.0, 1.0, 1.0));
115+
builder.add_vertex(Vector3::new(1.0, 2.0, 1.0));
116+
builder.add_vertex(Vector3::new(1.0, 1.0, 2.0));
117+
118+
let existing_mesh: Unique<Gd<ArrayMesh>> = Unique::new_gd();
119+
120+
let result = builder.commit_ex().existing(existing_mesh).done().unwrap();
121+
122+
Unique::try_from_ref_counted(result).unwrap()
123+
})
124+
.join()
125+
.expect("sub-thread should not have paniced");
126+
127+
assert_eq!(result.share().get_reference_count(), 1);
128+
}

0 commit comments

Comments
 (0)