diff options
Diffstat (limited to 'tests')
-rw-r--r-- | tests/core/object/test_object.h | 78 | ||||
-rw-r--r-- | tests/core/variant/test_array.h | 18 | ||||
-rw-r--r-- | tests/scene/test_skeleton_3d.h | 78 | ||||
-rw-r--r-- | tests/scene/test_viewport.h | 144 | ||||
-rw-r--r-- | tests/test_main.cpp | 1 |
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" |