summaryrefslogtreecommitdiffstats
path: root/tests
diff options
context:
space:
mode:
Diffstat (limited to 'tests')
-rw-r--r--tests/core/object/test_object.h78
-rw-r--r--tests/core/variant/test_array.h18
-rw-r--r--tests/scene/test_skeleton_3d.h78
-rw-r--r--tests/scene/test_viewport.h144
-rw-r--r--tests/test_main.cpp1
5 files changed, 306 insertions, 13 deletions
diff --git a/tests/core/object/test_object.h b/tests/core/object/test_object.h
index f1bb62cb70..e703698ec6 100644
--- a/tests/core/object/test_object.h
+++ b/tests/core/object/test_object.h
@@ -37,6 +37,16 @@
#include "tests/test_macros.h"
+#ifdef SANITIZERS_ENABLED
+#ifdef __has_feature
+#if __has_feature(address_sanitizer) || __has_feature(thread_sanitizer)
+#define ASAN_OR_TSAN_ENABLED
+#endif
+#elif defined(__SANITIZE_ADDRESS__) || defined(__SANITIZE_THREAD__)
+#define ASAN_OR_TSAN_ENABLED
+#endif
+#endif
+
// Declared in global namespace because of GDCLASS macro warning (Windows):
// "Unqualified friend declaration referring to type outside of the nearest enclosing namespace
// is a Microsoft extension; add a nested name specifier".
@@ -524,6 +534,74 @@ TEST_CASE("[Object] Notification order") { // GH-52325
memdelete(test_notification_object);
}
+TEST_CASE("[Object] Destruction at the end of the call chain is safe") {
+ Object *object = memnew(Object);
+ ObjectID obj_id = object->get_instance_id();
+
+ class _SelfDestroyingScriptInstance : public _MockScriptInstance {
+ Object *self = nullptr;
+
+ // This has to be static because ~Object() also destroys the script instance.
+ static void free_self(Object *p_self) {
+#if defined(ASAN_OR_TSAN_ENABLED)
+ // Regular deletion is enough becausa asan/tsan will catch a potential heap-after-use.
+ memdelete(p_self);
+#else
+ // Without asan/tsan, try at least to force a crash by replacing the otherwise seemingly good data with garbage.
+ // Operations such as dereferencing pointers or decreasing a refcount would fail.
+ // Unfortunately, we may not poison the memory after the deletion, because the memory would no longer belong to us
+ // and on doing so we may cause a more generalized crash on some platforms (allocator implementations).
+ p_self->~Object();
+ memset((void *)p_self, 0, sizeof(Object));
+ Memory::free_static(p_self, false);
+#endif
+ }
+
+ public:
+ Variant callp(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) override {
+ free_self(self);
+ return Variant();
+ }
+ Variant call_const(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) override {
+ free_self(self);
+ return Variant();
+ }
+ bool has_method(const StringName &p_method) const override {
+ return p_method == "some_method";
+ }
+
+ public:
+ _SelfDestroyingScriptInstance(Object *p_self) :
+ self(p_self) {}
+ };
+
+ _SelfDestroyingScriptInstance *script_instance = memnew(_SelfDestroyingScriptInstance(object));
+ object->set_script_instance(script_instance);
+
+ SUBCASE("Within callp()") {
+ SUBCASE("Through call()") {
+ object->call("some_method");
+ }
+ SUBCASE("Through callv()") {
+ object->callv("some_method", Array());
+ }
+ }
+ SUBCASE("Within call_const()") {
+ Callable::CallError call_error;
+ object->call_const("some_method", nullptr, 0, call_error);
+ }
+ SUBCASE("Within signal handling (from emit_signalp(), through emit_signal())") {
+ Object emitter;
+ emitter.add_user_signal(MethodInfo("some_signal"));
+ emitter.connect("some_signal", Callable(object, "some_method"));
+ emitter.emit_signal("some_signal");
+ }
+
+ CHECK_MESSAGE(
+ ObjectDB::get_instance(obj_id) == nullptr,
+ "Object was tail-deleted without crashes.");
+}
+
} // namespace TestObject
#endif // TEST_OBJECT_H
diff --git a/tests/core/variant/test_array.h b/tests/core/variant/test_array.h
index 787b8f39d9..15e2cebe09 100644
--- a/tests/core/variant/test_array.h
+++ b/tests/core/variant/test_array.h
@@ -634,6 +634,24 @@ TEST_CASE("[Array] Typed copying") {
a6.clear();
}
+static bool _find_custom_callable(const Variant &p_val) {
+ return (int)p_val % 2 == 0;
+}
+
+TEST_CASE("[Array] Test find_custom") {
+ Array a1 = build_array(1, 3, 4, 5, 8, 9);
+ // Find first even number.
+ int index = a1.find_custom(callable_mp_static(_find_custom_callable));
+ CHECK_EQ(index, 2);
+}
+
+TEST_CASE("[Array] Test rfind_custom") {
+ Array a1 = build_array(1, 3, 4, 5, 8, 9);
+ // Find last even number.
+ int index = a1.rfind_custom(callable_mp_static(_find_custom_callable));
+ CHECK_EQ(index, 4);
+}
+
} // namespace TestArray
#endif // TEST_ARRAY_H
diff --git a/tests/scene/test_skeleton_3d.h b/tests/scene/test_skeleton_3d.h
new file mode 100644
index 0000000000..b5cf49c4eb
--- /dev/null
+++ b/tests/scene/test_skeleton_3d.h
@@ -0,0 +1,78 @@
+/**************************************************************************/
+/* test_skeleton_3d.h */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#ifndef TEST_SKELETON_3D_H
+#define TEST_SKELETON_3D_H
+
+#include "tests/test_macros.h"
+
+#include "scene/3d/skeleton_3d.h"
+
+namespace TestSkeleton3D {
+
+TEST_CASE("[Skeleton3D] Test per-bone meta") {
+ Skeleton3D *skeleton = memnew(Skeleton3D);
+ skeleton->add_bone("root");
+ skeleton->set_bone_rest(0, Transform3D());
+
+ // Adding meta to bone.
+ skeleton->set_bone_meta(0, "key1", "value1");
+ skeleton->set_bone_meta(0, "key2", 12345);
+ CHECK_MESSAGE(skeleton->get_bone_meta(0, "key1") == "value1", "Bone meta missing.");
+ CHECK_MESSAGE(skeleton->get_bone_meta(0, "key2") == Variant(12345), "Bone meta missing.");
+
+ // Rename bone and check if meta persists.
+ skeleton->set_bone_name(0, "renamed_root");
+ CHECK_MESSAGE(skeleton->get_bone_meta(0, "key1") == "value1", "Bone meta missing.");
+ CHECK_MESSAGE(skeleton->get_bone_meta(0, "key2") == Variant(12345), "Bone meta missing.");
+
+ // Retrieve list of keys.
+ List<StringName> keys;
+ skeleton->get_bone_meta_list(0, &keys);
+ CHECK_MESSAGE(keys.size() == 2, "Wrong number of bone meta keys.");
+ CHECK_MESSAGE(keys.find("key1"), "key1 not found in bone meta list");
+ CHECK_MESSAGE(keys.find("key2"), "key2 not found in bone meta list");
+
+ // Removing meta.
+ skeleton->set_bone_meta(0, "key1", Variant());
+ skeleton->set_bone_meta(0, "key2", Variant());
+ CHECK_MESSAGE(!skeleton->has_bone_meta(0, "key1"), "Bone meta key1 should be deleted.");
+ CHECK_MESSAGE(!skeleton->has_bone_meta(0, "key2"), "Bone meta key2 should be deleted.");
+ List<StringName> should_be_empty_keys;
+ skeleton->get_bone_meta_list(0, &should_be_empty_keys);
+ CHECK_MESSAGE(should_be_empty_keys.size() == 0, "Wrong number of bone meta keys.");
+
+ // Deleting non-existing key should succeed.
+ skeleton->set_bone_meta(0, "non-existing-key", Variant());
+ memdelete(skeleton);
+}
+} // namespace TestSkeleton3D
+
+#endif // TEST_SKELETON_3D_H
diff --git a/tests/scene/test_viewport.h b/tests/scene/test_viewport.h
index 1341cc0332..9d02c41719 100644
--- a/tests/scene/test_viewport.h
+++ b/tests/scene/test_viewport.h
@@ -119,8 +119,23 @@ public:
class DragTarget : public NotificationControlViewport {
GDCLASS(DragTarget, NotificationControlViewport);
+protected:
+ void _notification(int p_what) {
+ switch (p_what) {
+ case NOTIFICATION_DRAG_BEGIN: {
+ during_drag = true;
+ } break;
+
+ case NOTIFICATION_DRAG_END: {
+ during_drag = false;
+ } break;
+ }
+ }
+
public:
Variant drag_data;
+ bool valid_drop = false;
+ bool during_drag = false;
virtual bool can_drop_data(const Point2 &p_point, const Variant &p_data) const override {
StringName string_data = p_data;
// Verify drag data is compatible.
@@ -136,6 +151,7 @@ public:
virtual void drop_data(const Point2 &p_point, const Variant &p_data) override {
drag_data = p_data;
+ valid_drop = true;
}
};
@@ -1107,12 +1123,10 @@ TEST_CASE("[SceneTree][Viewport] Controls and InputEvent handling") {
SUBCASE("[Viewport][GuiInputEvent] Drag and Drop") {
// FIXME: Drag-Preview will likely change. Tests for this part would have to be rewritten anyway.
// See https://github.com/godotengine/godot/pull/67531#issuecomment-1385353430 for details.
- // FIXME: Testing Drag and Drop with non-embedded windows would require DisplayServerMock additions
- // FIXME: Drag and Drop currently doesn't work with embedded Windows and SubViewports - not testing.
- // See https://github.com/godotengine/godot/issues/28522 for example.
+ // Note: Testing Drag and Drop with non-embedded windows would require DisplayServerMock additions.
int min_grab_movement = 11;
- SUBCASE("[Viewport][GuiInputEvent] Drag from one Control to another in the same viewport.") {
- SUBCASE("[Viewport][GuiInputEvent] Perform successful Drag and Drop on a different Control.") {
+ SUBCASE("[Viewport][GuiInputEvent][DnD] Drag from one Control to another in the same viewport.") {
+ SUBCASE("[Viewport][GuiInputEvent][DnD] Perform successful Drag and Drop on a different Control.") {
SEND_GUI_MOUSE_BUTTON_EVENT(on_a, MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE);
CHECK_FALSE(root->gui_is_dragging());
@@ -1131,7 +1145,7 @@ TEST_CASE("[SceneTree][Viewport] Controls and InputEvent handling") {
CHECK((StringName)node_d->drag_data == SNAME("Drag Data"));
}
- SUBCASE("[Viewport][GuiInputEvent] Perform unsuccessful drop on Control.") {
+ SUBCASE("[Viewport][GuiInputEvent][DnD] Perform unsuccessful drop on Control.") {
SEND_GUI_MOUSE_BUTTON_EVENT(on_a, MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE);
CHECK_FALSE(root->gui_is_dragging());
@@ -1157,7 +1171,7 @@ TEST_CASE("[SceneTree][Viewport] Controls and InputEvent handling") {
CHECK_FALSE(root->gui_is_drag_successful());
}
- SUBCASE("[Viewport][GuiInputEvent] Perform unsuccessful drop on No-Control.") {
+ SUBCASE("[Viewport][GuiInputEvent][DnD] Perform unsuccessful drop on No-Control.") {
SEND_GUI_MOUSE_BUTTON_EVENT(on_a, MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE);
CHECK_FALSE(root->gui_is_dragging());
@@ -1171,7 +1185,7 @@ TEST_CASE("[SceneTree][Viewport] Controls and InputEvent handling") {
// Move away from Controls.
SEND_GUI_MOUSE_MOTION_EVENT(on_background, MouseButtonMask::LEFT, Key::NONE);
- CHECK(DS->get_cursor_shape() == DisplayServer::CURSOR_ARROW); // This could also be CURSOR_FORBIDDEN.
+ CHECK(DS->get_cursor_shape() == DisplayServer::CURSOR_ARROW);
CHECK(root->gui_is_dragging());
SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT(on_background, MouseButton::LEFT, MouseButtonMask::NONE, Key::NONE);
@@ -1179,7 +1193,7 @@ TEST_CASE("[SceneTree][Viewport] Controls and InputEvent handling") {
CHECK_FALSE(root->gui_is_drag_successful());
}
- SUBCASE("[Viewport][GuiInputEvent] Perform unsuccessful drop outside of window.") {
+ SUBCASE("[Viewport][GuiInputEvent][DnD] Perform unsuccessful drop outside of window.") {
SEND_GUI_MOUSE_BUTTON_EVENT(on_a, MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE);
CHECK_FALSE(root->gui_is_dragging());
@@ -1192,7 +1206,6 @@ TEST_CASE("[SceneTree][Viewport] Controls and InputEvent handling") {
// Move outside of window.
SEND_GUI_MOUSE_MOTION_EVENT(on_outside, MouseButtonMask::LEFT, Key::NONE);
- CHECK(DS->get_cursor_shape() == DisplayServer::CURSOR_ARROW);
CHECK(root->gui_is_dragging());
SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT(on_outside, MouseButton::LEFT, MouseButtonMask::NONE, Key::NONE);
@@ -1200,7 +1213,7 @@ TEST_CASE("[SceneTree][Viewport] Controls and InputEvent handling") {
CHECK_FALSE(root->gui_is_drag_successful());
}
- SUBCASE("[Viewport][GuiInputEvent] Drag and Drop doesn't work with other Mouse Buttons than LMB.") {
+ SUBCASE("[Viewport][GuiInputEvent][DnD] Drag and Drop doesn't work with other Mouse Buttons than LMB.") {
SEND_GUI_MOUSE_BUTTON_EVENT(on_a, MouseButton::MIDDLE, MouseButtonMask::MIDDLE, Key::NONE);
CHECK_FALSE(root->gui_is_dragging());
@@ -1209,7 +1222,7 @@ TEST_CASE("[SceneTree][Viewport] Controls and InputEvent handling") {
SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT(on_a, MouseButton::MIDDLE, MouseButtonMask::NONE, Key::NONE);
}
- SUBCASE("[Viewport][GuiInputEvent] Drag and Drop parent propagation.") {
+ SUBCASE("[Viewport][GuiInputEvent][DnD] Drag and Drop parent propagation.") {
Node2D *node_aa = memnew(Node2D);
Control *node_aaa = memnew(Control);
Node2D *node_dd = memnew(Node2D);
@@ -1318,7 +1331,7 @@ TEST_CASE("[SceneTree][Viewport] Controls and InputEvent handling") {
memdelete(node_aa);
}
- SUBCASE("[Viewport][GuiInputEvent] Force Drag and Drop.") {
+ SUBCASE("[Viewport][GuiInputEvent][DnD] Force Drag and Drop.") {
SEND_GUI_MOUSE_MOTION_EVENT(on_background, MouseButtonMask::NONE, Key::NONE);
CHECK_FALSE(root->gui_is_dragging());
node_a->force_drag(SNAME("Drag Data"), nullptr);
@@ -1339,6 +1352,111 @@ TEST_CASE("[SceneTree][Viewport] Controls and InputEvent handling") {
SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT(on_d, MouseButton::LEFT, MouseButtonMask::NONE, Key::NONE);
}
}
+
+ SUBCASE("[Viewport][GuiInputEvent][DnD] Drag to a different Viewport.") {
+ SubViewportContainer *svc = memnew(SubViewportContainer);
+ svc->set_size(Size2(100, 100));
+ svc->set_position(Point2(200, 50));
+ root->add_child(svc);
+
+ SubViewport *sv = memnew(SubViewport);
+ sv->set_embedding_subwindows(true);
+ sv->set_size(Size2i(100, 100));
+ svc->add_child(sv);
+
+ DragStart *sv_a = memnew(DragStart);
+ sv_a->set_position(Point2(10, 10));
+ sv_a->set_size(Size2(10, 10));
+ sv->add_child(sv_a);
+ Point2i on_sva = Point2i(215, 65);
+
+ DragTarget *sv_b = memnew(DragTarget);
+ sv_b->set_position(Point2(30, 30));
+ sv_b->set_size(Size2(20, 20));
+ sv->add_child(sv_b);
+ Point2i on_svb = Point2i(235, 85);
+
+ Window *ew = memnew(Window);
+ ew->set_position(Point2(50, 200));
+ ew->set_size(Size2(100, 100));
+ root->add_child(ew);
+
+ DragStart *ew_a = memnew(DragStart);
+ ew_a->set_position(Point2(10, 10));
+ ew_a->set_size(Size2(10, 10));
+ ew->add_child(ew_a);
+ Point2i on_ewa = Point2i(65, 215);
+
+ DragTarget *ew_b = memnew(DragTarget);
+ ew_b->set_position(Point2(30, 30));
+ ew_b->set_size(Size2(20, 20));
+ ew->add_child(ew_b);
+ Point2i on_ewb = Point2i(85, 235);
+
+ SUBCASE("[Viewport][GuiInputEvent][DnD] Drag to SubViewport") {
+ sv_b->valid_drop = false;
+ SEND_GUI_MOUSE_BUTTON_EVENT(on_a, MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE);
+ SEND_GUI_MOUSE_MOTION_EVENT(on_a + Point2i(min_grab_movement, 0), MouseButtonMask::LEFT, Key::NONE);
+ CHECK(root->gui_is_dragging());
+ CHECK(sv_b->during_drag);
+ SEND_GUI_MOUSE_MOTION_EVENT(on_svb, MouseButtonMask::LEFT, Key::NONE);
+ CHECK(DS->get_cursor_shape() == DisplayServer::CURSOR_CAN_DROP);
+
+ SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT(on_svb, MouseButton::LEFT, MouseButtonMask::NONE, Key::NONE);
+ CHECK(sv_b->valid_drop);
+ CHECK(!sv_b->during_drag);
+ }
+
+ SUBCASE("[Viewport][GuiInputEvent][DnD] Drag from SubViewport") {
+ node_d->valid_drop = false;
+ SEND_GUI_MOUSE_BUTTON_EVENT(on_sva, MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE);
+ SEND_GUI_MOUSE_MOTION_EVENT(on_sva + Point2i(min_grab_movement, 0), MouseButtonMask::LEFT, Key::NONE);
+ CHECK(sv->gui_is_dragging());
+ CHECK(node_d->during_drag);
+ SEND_GUI_MOUSE_MOTION_EVENT(on_d, MouseButtonMask::LEFT, Key::NONE);
+ CHECK(DS->get_cursor_shape() == DisplayServer::CURSOR_CAN_DROP);
+
+ SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT(on_d, MouseButton::LEFT, MouseButtonMask::NONE, Key::NONE);
+ CHECK(node_d->valid_drop);
+ CHECK(!node_d->during_drag);
+ }
+
+ SUBCASE("[Viewport][GuiInputEvent][DnD] Drag to embedded Window") {
+ ew_b->valid_drop = false;
+ SEND_GUI_MOUSE_BUTTON_EVENT(on_a, MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE);
+ SEND_GUI_MOUSE_MOTION_EVENT(on_a + Point2i(min_grab_movement, 0), MouseButtonMask::LEFT, Key::NONE);
+ CHECK(root->gui_is_dragging());
+ CHECK(ew_b->during_drag);
+ SEND_GUI_MOUSE_MOTION_EVENT(on_ewb, MouseButtonMask::LEFT, Key::NONE);
+ CHECK(DS->get_cursor_shape() == DisplayServer::CURSOR_CAN_DROP);
+
+ SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT(on_ewb, MouseButton::LEFT, MouseButtonMask::NONE, Key::NONE);
+ CHECK(ew_b->valid_drop);
+ CHECK(!ew_b->during_drag);
+ }
+
+ SUBCASE("[Viewport][GuiInputEvent][DnD] Drag from embedded Window") {
+ node_d->valid_drop = false;
+ SEND_GUI_MOUSE_BUTTON_EVENT(on_ewa, MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE);
+ SEND_GUI_MOUSE_MOTION_EVENT(on_ewa + Point2i(min_grab_movement, 0), MouseButtonMask::LEFT, Key::NONE);
+ CHECK(ew->gui_is_dragging());
+ CHECK(node_d->during_drag);
+ SEND_GUI_MOUSE_MOTION_EVENT(on_d, MouseButtonMask::LEFT, Key::NONE);
+ CHECK(DS->get_cursor_shape() == DisplayServer::CURSOR_CAN_DROP);
+
+ SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT(on_d, MouseButton::LEFT, MouseButtonMask::NONE, Key::NONE);
+ CHECK(node_d->valid_drop);
+ CHECK(!node_d->during_drag);
+ }
+
+ memdelete(ew_a);
+ memdelete(ew_b);
+ memdelete(ew);
+ memdelete(sv_a);
+ memdelete(sv_b);
+ memdelete(sv);
+ memdelete(svc);
+ }
}
memdelete(node_j);
diff --git a/tests/test_main.cpp b/tests/test_main.cpp
index 2b6461e9ca..949e4f0b33 100644
--- a/tests/test_main.cpp
+++ b/tests/test_main.cpp
@@ -160,6 +160,7 @@
#include "tests/scene/test_path_3d.h"
#include "tests/scene/test_path_follow_3d.h"
#include "tests/scene/test_primitives.h"
+#include "tests/scene/test_skeleton_3d.h"
#endif // _3D_DISABLED
#include "modules/modules_tests.gen.h"