diff --git a/main/main.cpp b/main/main.cpp index 820183ae43..2d3cffb571 100644 --- a/main/main.cpp +++ b/main/main.cpp @@ -722,7 +722,12 @@ void Main::print_help(const char *p_binary) { OS::get_singleton()->print("\n"); } -#ifdef TESTS_ENABLED +#ifndef TESTS_ENABLED +Error Main::test_setup() { + ERR_FAIL_V(ERR_UNAVAILABLE); +} +void Main::test_cleanup() {} +#else // The order is the same as in `Main::setup()`, only core and some editor types // are initialized here. This also combines `Main::setup2()` initialization. Error Main::test_setup() { @@ -931,7 +936,7 @@ void Main::test_cleanup() { OS::get_singleton()->finalize_core(); } -#endif +#endif // TESTS_ENABLED int Main::test_entrypoint(int argc, char *argv[], bool &tests_need_run) { for (int x = 0; x < argc; x++) { diff --git a/main/main.h b/main/main.h index 0d212be171..1a875a5ed5 100644 --- a/main/main.h +++ b/main/main.h @@ -73,10 +73,8 @@ public: static Error setup2(bool p_show_boot_logo = true); // The thread calling setup2() will effectively become the main thread. static String get_locale_override(); static void setup_boot_logo(); -#ifdef TESTS_ENABLED static Error test_setup(); static void test_cleanup(); -#endif static int start(); static bool iteration(); diff --git a/modules/SCsub b/modules/SCsub index e987dde0e5..69dfadfa31 100644 --- a/modules/SCsub +++ b/modules/SCsub @@ -52,7 +52,6 @@ for name, path in env.module_list.items(): # Generate header to be included in `tests/test_main.cpp` to run module-specific tests. if env["tests"]: - env.Append(CPPDEFINES=["TESTS_ENABLED"]) env.CommandNoCache("modules_tests.gen.h", test_headers, env.Run(modules_builders.modules_tests_builder)) # libmodules.a with only register_module_types. diff --git a/modules/gdscript/SCsub b/modules/gdscript/SCsub index 8f50bf9588..f39b1a0fe9 100644 --- a/modules/gdscript/SCsub +++ b/modules/gdscript/SCsub @@ -25,5 +25,6 @@ if env.editor_build: if env["tests"]: + # TODO: Handle test creation magic without needing to pass macro. env_gdscript.Append(CPPDEFINES=["TESTS_ENABLED"]) env_gdscript.add_source_files(env.modules_sources, "./tests/*.cpp") diff --git a/modules/gdscript/gdscript_cache.h b/modules/gdscript/gdscript_cache.h index bcd2d20206..7a96442341 100644 --- a/modules/gdscript/gdscript_cache.h +++ b/modules/gdscript/gdscript_cache.h @@ -78,11 +78,9 @@ public: ~GDScriptParserRef(); }; -#ifdef TESTS_ENABLED namespace GDScriptTests { class TestGDScriptCacheAccessor; } -#endif // TESTS_ENABLED class GDScriptCache { // String key is full path. @@ -97,9 +95,7 @@ class GDScriptCache { friend class GDScript; friend class GDScriptParserRef; friend class GDScriptInstance; -#ifdef TESTS_ENABLED friend class GDScriptTests::TestGDScriptCacheAccessor; -#endif // TESTS_ENABLED static GDScriptCache *singleton; diff --git a/modules/gdscript/language_server/gdscript_language_protocol.h b/modules/gdscript/language_server/gdscript_language_protocol.h index 4205570c5d..897fe11efb 100644 --- a/modules/gdscript/language_server/gdscript_language_protocol.h +++ b/modules/gdscript/language_server/gdscript_language_protocol.h @@ -47,9 +47,7 @@ class GDScriptLanguageProtocol : public JSONRPC { GDCLASS(GDScriptLanguageProtocol, JSONRPC) -#ifdef TESTS_ENABLED friend class TestGDScriptLanguageProtocolInitializer; -#endif private: struct LSPeer : RefCounted { diff --git a/modules/jsonrpc/SCsub b/modules/jsonrpc/SCsub index 923567b138..7892e03f50 100644 --- a/modules/jsonrpc/SCsub +++ b/modules/jsonrpc/SCsub @@ -8,8 +8,4 @@ env_jsonrpc = env_modules.Clone() env_jsonrpc.add_source_files(env.modules_sources, "*.cpp") if env["tests"]: - env_jsonrpc.Append(CPPDEFINES=["TESTS_ENABLED"]) env_jsonrpc.add_source_files(env.modules_sources, "./tests/*.cpp") - - if env["disable_exceptions"]: - env_jsonrpc.Append(CPPDEFINES=["DOCTEST_CONFIG_NO_EXCEPTIONS_BUT_WITH_ALL_ASSERTS"]) diff --git a/modules/mbedtls/SCsub b/modules/mbedtls/SCsub index 6183fa5944..fbfa4eb3a0 100644 --- a/modules/mbedtls/SCsub +++ b/modules/mbedtls/SCsub @@ -139,12 +139,8 @@ module_obj = [] env_mbed_tls.add_source_files(module_obj, "*.cpp") if env["tests"]: - env_mbed_tls.Append(CPPDEFINES=["TESTS_ENABLED"]) env_mbed_tls.add_source_files(module_obj, "./tests/*.cpp") - if env["disable_exceptions"]: - env_mbed_tls.Append(CPPDEFINES=["DOCTEST_CONFIG_NO_EXCEPTIONS_BUT_WITH_ALL_ASSERTS"]) - env.modules_sources += module_obj # Needed to force rebuilding the module files when the thirdparty library is updated. diff --git a/modules/mbedtls/register_types.cpp b/modules/mbedtls/register_types.cpp index aff1fad3ce..b726a70327 100644 --- a/modules/mbedtls/register_types.cpp +++ b/modules/mbedtls/register_types.cpp @@ -41,10 +41,6 @@ #include #endif -#ifdef TESTS_ENABLED -#include "tests/test_crypto_mbedtls.h" -#endif - #ifdef GODOT_MBEDTLS_THREADING_ALT extern "C" { void godot_mbedtls_mutex_init(mbedtls_threading_mutex_t *p_mutex) { diff --git a/modules/multiplayer/SCsub b/modules/multiplayer/SCsub index f9f4e579e8..fa467ff746 100644 --- a/modules/multiplayer/SCsub +++ b/modules/multiplayer/SCsub @@ -15,8 +15,4 @@ if env.editor_build: env.modules_sources += module_obj if env["tests"]: - env_mp.Append(CPPDEFINES=["TESTS_ENABLED"]) env_mp.add_source_files(env.modules_sources, "./tests/*.cpp") - - if env["disable_exceptions"]: - env_mp.Append(CPPDEFINES=["DOCTEST_CONFIG_NO_EXCEPTIONS_BUT_WITH_ALL_ASSERTS"]) diff --git a/modules/zip/SCsub b/modules/zip/SCsub index 0ae5ae9109..d390dc4d5b 100644 --- a/modules/zip/SCsub +++ b/modules/zip/SCsub @@ -10,5 +10,4 @@ env_zip = env_modules.Clone() env_zip.add_source_files(env.modules_sources, "*.cpp") if env["tests"]: - env_zip.Append(CPPDEFINES=["TESTS_ENABLED"]) env_zip.add_source_files(env.modules_sources, "./tests/*.cpp") diff --git a/tests/SCsub b/tests/SCsub index 068481d475..acf6984943 100644 --- a/tests/SCsub +++ b/tests/SCsub @@ -7,31 +7,19 @@ import test_builders Import("env") -env_tests = env.Clone() - -# We must disable the THREAD_LOCAL entirely in doctest to prevent crashes on debugging -# Since we link with /MT thread_local is always expired when the header is used -# So the debugger crashes the engine and it causes weird errors -# Explained in https://github.com/onqtam/doctest/issues/401 -if env_tests["platform"] == "windows": - env_tests.Append(CPPDEFINES=[("DOCTEST_THREAD_LOCAL", "")]) - -if env["disable_exceptions"]: - env_tests.Append(CPPDEFINES=["DOCTEST_CONFIG_NO_EXCEPTIONS_BUT_WITH_ALL_ASSERTS"]) - # Sources with tests must be explicitly linked first. force_link_sources = glob.glob("*/**/*.cpp", recursive=True) force_link_header = env.CommandNoCache( - "force_link.gen.h", env_tests.Value(force_link_sources), env.Run(test_builders.force_link_builder) + "force_link.gen.h", env.Value(force_link_sources), env.Run(test_builders.force_link_builder) ) env.Depends(force_link_header, "test_builders.py") tests_obj = [] if env["scu_build"]: # HACK: SCU setup doesn't support recursive/dynamic setup, so we must manually pass the files. - env_tests.add_source_files(tests_obj, glob.glob(".scu/*.cpp")) + env.add_source_files(tests_obj, glob.glob(".scu/*.cpp")) else: - env_tests.add_source_files(tests_obj, glob.glob("*.cpp") + force_link_sources) + env.add_source_files(tests_obj, glob.glob("*.cpp") + force_link_sources) -lib = env_tests.add_library("tests", tests_obj) +lib = env.add_library("tests", tests_obj) env.Prepend(LIBS=[lib]) diff --git a/tests/core/config/test_project_settings.cpp b/tests/core/config/test_project_settings.cpp index 7efb8247ce..1d8146a2a4 100644 --- a/tests/core/config/test_project_settings.cpp +++ b/tests/core/config/test_project_settings.cpp @@ -34,7 +34,9 @@ TEST_FORCE_LINK(test_project_settings) #include "core/config/project_settings.h" #include "core/io/dir_access.h" +#include "core/object/message_queue.h" #include "core/variant/variant.h" +#include "tests/signal_watcher.h" #include "tests/test_utils.h" namespace TestProjectSettings { diff --git a/tests/core/input/test_input_event.cpp b/tests/core/input/test_input_event.cpp index 594660d4b2..724f17fcaa 100644 --- a/tests/core/input/test_input_event.cpp +++ b/tests/core/input/test_input_event.cpp @@ -33,9 +33,10 @@ TEST_FORCE_LINK(test_input_event) #include "core/input/input_event.h" +#include "core/input/input_map.h" #include "core/math/rect2.h" -#include "core/os/memory.h" #include "core/variant/array.h" +#include "tests/signal_watcher.h" namespace TestInputEvent { diff --git a/tests/core/input/test_shortcut.cpp b/tests/core/input/test_shortcut.cpp index db0b9919d9..4fe2301809 100644 --- a/tests/core/input/test_shortcut.cpp +++ b/tests/core/input/test_shortcut.cpp @@ -34,10 +34,8 @@ TEST_FORCE_LINK(test_shortcut) #include "core/input/input_event.h" #include "core/input/shortcut.h" -#include "core/io/config_file.h" #include "core/object/ref_counted.h" #include "core/os/keyboard.h" -#include "core/os/os.h" namespace TestShortcut { diff --git a/tests/core/io/test_http_client.cpp b/tests/core/io/test_http_client.cpp index cef8a1a131..c69f1cffae 100644 --- a/tests/core/io/test_http_client.cpp +++ b/tests/core/io/test_http_client.cpp @@ -88,7 +88,7 @@ TEST_CASE("[HTTPClient] verify_headers") { ERR_PRINT_ON; } -#if defined(MODULE_MBEDTLS_ENABLED) || defined(WEB_EMABLED) +#if defined(MODULE_MBEDTLS_ENABLED) || defined(WEB_ENABLED) TEST_CASE("[HTTPClient] connect_to_host") { Ref client = HTTPClient::create(); String host = "https://www.example.com"; @@ -99,6 +99,6 @@ TEST_CASE("[HTTPClient] connect_to_host") { Error err = client->connect_to_host(host, port, tls_options); CHECK_MESSAGE(err == OK, "Expected OK for successful connection"); } -#endif // defined(MODULE_MBEDTLS_ENABLED) || defined(WEB_EMABLED) +#endif // defined(MODULE_MBEDTLS_ENABLED) || defined(WEB_ENABLED) } // namespace TestHTTPClient diff --git a/tests/core/io/test_json.cpp b/tests/core/io/test_json.cpp index 93a3387d7f..6f753c1a21 100644 --- a/tests/core/io/test_json.cpp +++ b/tests/core/io/test_json.cpp @@ -33,6 +33,7 @@ TEST_FORCE_LINK(test_json) #include "core/io/json.h" +#include "core/variant/typed_array.h" namespace TestJSON { diff --git a/tests/core/io/test_logger.cpp b/tests/core/io/test_logger.cpp index 1b9d9affab..f4edcdb6b4 100644 --- a/tests/core/io/test_logger.cpp +++ b/tests/core/io/test_logger.cpp @@ -36,6 +36,7 @@ TEST_FORCE_LINK(test_logger) #include "core/io/dir_access.h" #include "core/io/file_access.h" #include "core/io/logger.h" +#include "core/os/os.h" namespace TestLogger { diff --git a/tests/core/io/test_marshalls.cpp b/tests/core/io/test_marshalls.cpp index 3dff271e90..995b1978e3 100644 --- a/tests/core/io/test_marshalls.cpp +++ b/tests/core/io/test_marshalls.cpp @@ -33,6 +33,7 @@ TEST_FORCE_LINK(test_marshalls) #include "core/io/marshalls.h" +#include "core/object/script_language.h" namespace TestMarshalls { diff --git a/tests/core/io/test_pck_packer.cpp b/tests/core/io/test_pck_packer.cpp index f17224f0e2..4fbe65844d 100644 --- a/tests/core/io/test_pck_packer.cpp +++ b/tests/core/io/test_pck_packer.cpp @@ -32,7 +32,7 @@ TEST_FORCE_LINK(test_pck_packer) -#include "core/io/file_access_pack.h" +#include "core/io/file_access.h" #include "core/io/pck_packer.h" #include "core/os/os.h" #include "tests/test_utils.h" diff --git a/tests/core/math/test_basis.cpp b/tests/core/math/test_basis.cpp index 06657f6c61..f8b2f127dc 100644 --- a/tests/core/math/test_basis.cpp +++ b/tests/core/math/test_basis.cpp @@ -33,7 +33,6 @@ TEST_FORCE_LINK(test_basis) #include "core/math/basis.h" -#include "core/math/random_number_generator.h" namespace TestBasis { diff --git a/tests/core/object/test_class_db.cpp b/tests/core/object/test_class_db.cpp index c4db740aef..44c49c77bd 100644 --- a/tests/core/object/test_class_db.cpp +++ b/tests/core/object/test_class_db.cpp @@ -32,7 +32,7 @@ TEST_FORCE_LINK(test_class_db) -#include "core/core_bind.h" +#include "core/config/engine.h" #include "core/core_constants.h" #include "core/object/class_db.h" diff --git a/tests/core/object/test_object.cpp b/tests/core/object/test_object.cpp index cbc28f2ea0..55b149efaa 100644 --- a/tests/core/object/test_object.cpp +++ b/tests/core/object/test_object.cpp @@ -35,6 +35,7 @@ TEST_FORCE_LINK(test_object) #include "core/object/class_db.h" #include "core/object/object.h" #include "core/object/script_language.h" +#include "tests/signal_watcher.h" namespace TestObject { diff --git a/tests/core/templates/test_command_queue.cpp b/tests/core/templates/test_command_queue.cpp index b5b8c985ee..f1206ff5d5 100644 --- a/tests/core/templates/test_command_queue.cpp +++ b/tests/core/templates/test_command_queue.cpp @@ -33,7 +33,6 @@ TEST_FORCE_LINK(test_command_queue) #include "core/config/project_settings.h" -#include "core/math/random_number_generator.h" #include "core/object/worker_thread_pool.h" #include "core/os/os.h" #include "core/os/thread.h" diff --git a/tests/core/templates/test_rid.cpp b/tests/core/templates/test_rid.cpp index b731b05823..e812fbc5e3 100644 --- a/tests/core/templates/test_rid.cpp +++ b/tests/core/templates/test_rid.cpp @@ -32,6 +32,7 @@ TEST_FORCE_LINK(test_rid) +#include "core/os/os.h" #include "core/os/thread.h" #include "core/templates/local_vector.h" #include "core/templates/rid.h" diff --git a/tests/core/test_validate_testing.cpp b/tests/core/test_validate_testing.cpp index 9cd5e12233..f8b36e5807 100644 --- a/tests/core/test_validate_testing.cpp +++ b/tests/core/test_validate_testing.cpp @@ -33,7 +33,7 @@ TEST_FORCE_LINK(test_validate_testing) #include "core/core_globals.h" -#include "core/os/os.h" +#include "core/object/object.h" #include "tests/test_tools.h" namespace TestValidateTesting { diff --git a/tests/core/variant/test_array.cpp b/tests/core/variant/test_array.cpp index 1187cae6a2..801a89277f 100644 --- a/tests/core/variant/test_array.cpp +++ b/tests/core/variant/test_array.cpp @@ -32,7 +32,9 @@ TEST_FORCE_LINK(test_array) +#include "core/object/callable_method_pointer.h" #include "core/variant/array.h" +#include "core/variant/typed_array.h" #include "tests/test_tools.h" namespace TestArray { diff --git a/tests/core/variant/test_dictionary.cpp b/tests/core/variant/test_dictionary.cpp index 4c620fb0e0..9b3b06f28a 100644 --- a/tests/core/variant/test_dictionary.cpp +++ b/tests/core/variant/test_dictionary.cpp @@ -32,6 +32,7 @@ TEST_FORCE_LINK(test_dictionary) +#include "core/object/ref_counted.h" #include "core/variant/typed_dictionary.h" namespace TestDictionary { diff --git a/tests/display_server_mock.cpp b/tests/display_server_mock.cpp index b6b4055ab8..904cd09d4d 100644 --- a/tests/display_server_mock.cpp +++ b/tests/display_server_mock.cpp @@ -34,6 +34,46 @@ #include "core/input/input_event.h" #include "servers/rendering/dummy/rasterizer_dummy.h" +Vector DisplayServerMock::get_rendering_drivers_func() { + Vector drivers; + drivers.push_back("dummy"); + return drivers; +} + +void DisplayServerMock::_set_mouse_position(const Point2i &p_position) { + if (mouse_position == p_position) { + return; + } + mouse_position = p_position; + _set_window_over(Rect2i(Point2i(0, 0), window_get_size()).has_point(p_position)); +} +void DisplayServerMock::_set_window_over(bool p_over) { + if (p_over == window_over) { + return; + } + window_over = p_over; + _send_window_event(p_over ? WINDOW_EVENT_MOUSE_ENTER : WINDOW_EVENT_MOUSE_EXIT); +} +void DisplayServerMock::_send_window_event(WindowEvent p_event) { + if (event_callback.is_valid()) { + Variant event = int(p_event); + event_callback.call(event); + } +} + +bool DisplayServerMock::has_feature(Feature p_feature) const { + switch (p_feature) { + case FEATURE_MOUSE: + case FEATURE_CURSOR_SHAPE: + case FEATURE_CLIPBOARD: + case FEATURE_CLIPBOARD_PRIMARY: + return true; + default: { + } + } + return false; +} + DisplayServer *DisplayServerMock::create_func(const String &p_rendering_driver, DisplayServer::WindowMode p_mode, DisplayServer::VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i *p_position, const Vector2i &p_resolution, int p_screen, Context p_context, int64_t p_parent_window, Error &r_error) { r_error = OK; RasterizerDummy::make_current(); diff --git a/tests/display_server_mock.h b/tests/display_server_mock.h index 186b0f7048..3c7946ce7c 100644 --- a/tests/display_server_mock.h +++ b/tests/display_server_mock.h @@ -48,50 +48,15 @@ private: String clipboard_text; String primary_clipboard_text; - static Vector get_rendering_drivers_func() { - Vector drivers; - drivers.push_back("dummy"); - return drivers; - } - + static Vector get_rendering_drivers_func(); static DisplayServer *create_func(const String &p_rendering_driver, DisplayServer::WindowMode p_mode, DisplayServer::VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i *p_position, const Vector2i &p_resolution, int p_screen, Context p_context, int64_t p_parent_window, Error &r_error); - void _set_mouse_position(const Point2i &p_position) { - if (mouse_position == p_position) { - return; - } - mouse_position = p_position; - _set_window_over(Rect2i(Point2i(0, 0), window_get_size()).has_point(p_position)); - } - - void _set_window_over(bool p_over) { - if (p_over == window_over) { - return; - } - window_over = p_over; - _send_window_event(p_over ? WINDOW_EVENT_MOUSE_ENTER : WINDOW_EVENT_MOUSE_EXIT); - } - - void _send_window_event(WindowEvent p_event) { - if (event_callback.is_valid()) { - Variant event = int(p_event); - event_callback.call(event); - } - } + void _set_mouse_position(const Point2i &p_position); + void _set_window_over(bool p_over); + void _send_window_event(WindowEvent p_event); public: - bool has_feature(Feature p_feature) const override { - switch (p_feature) { - case FEATURE_MOUSE: - case FEATURE_CURSOR_SHAPE: - case FEATURE_CLIPBOARD: - case FEATURE_CLIPBOARD_PRIMARY: - return true; - default: { - } - } - return false; - } + bool has_feature(Feature p_feature) const override; String get_name() const override { return "mock"; } @@ -129,3 +94,109 @@ public: register_create_function("mock", create_func, get_rendering_drivers_func); } }; + +// Utility macros to send an event actions to a given object +// Requires Message Queue and InputMap to be setup. +// SEND_GUI_ACTION - takes an input map key. e.g SEND_GUI_ACTION("ui_text_newline"). +// SEND_GUI_KEY_EVENT - takes a keycode set. e.g SEND_GUI_KEY_EVENT(Key::A | KeyModifierMask::META). +// SEND_GUI_KEY_UP_EVENT - takes a keycode set. e.g SEND_GUI_KEY_UP_EVENT(Key::A | KeyModifierMask::META). +// SEND_GUI_MOUSE_BUTTON_EVENT - takes a position, mouse button, mouse mask and modifiers e.g SEND_GUI_MOUSE_BUTTON_EVENT(Vector2(50, 50), MOUSE_BUTTON_NONE, MOUSE_BUTTON_NONE, Key::None); +// SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT - takes a position, mouse button, mouse mask and modifiers e.g SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT(Vector2(50, 50), MOUSE_BUTTON_NONE, MOUSE_BUTTON_NONE, Key::None); +// SEND_GUI_MOUSE_MOTION_EVENT - takes a position, mouse mask and modifiers e.g SEND_GUI_MOUSE_MOTION_EVENT(Vector2(50, 50), MouseButtonMask::LEFT, KeyModifierMask::META); +// SEND_GUI_DOUBLE_CLICK - takes a position and modifiers. e.g SEND_GUI_DOUBLE_CLICK(Vector2(50, 50), KeyModifierMask::META); + +#define _SEND_DISPLAYSERVER_EVENT(m_event) ((DisplayServerMock *)(DisplayServer::get_singleton()))->simulate_event(m_event); + +#define SEND_GUI_ACTION(m_action) \ + { \ + const List> *events = InputMap::get_singleton()->action_get_events(m_action); \ + const List>::Element *first_event = events->front(); \ + Ref event = first_event->get()->duplicate(); \ + event->set_pressed(true); \ + _SEND_DISPLAYSERVER_EVENT(event); \ + MessageQueue::get_singleton()->flush(); \ + } + +#define SEND_GUI_KEY_EVENT(m_input) \ + { \ + Ref event = InputEventKey::create_reference(m_input); \ + event->set_pressed(true); \ + _SEND_DISPLAYSERVER_EVENT(event); \ + MessageQueue::get_singleton()->flush(); \ + } + +#define SEND_GUI_KEY_UP_EVENT(m_input) \ + { \ + Ref event = InputEventKey::create_reference(m_input); \ + event->set_pressed(false); \ + _SEND_DISPLAYSERVER_EVENT(event); \ + MessageQueue::get_singleton()->flush(); \ + } + +#define _UPDATE_EVENT_MODIFIERS(m_event, m_modifiers) \ + m_event->set_shift_pressed(((m_modifiers) & KeyModifierMask::SHIFT) != Key::NONE); \ + m_event->set_alt_pressed(((m_modifiers) & KeyModifierMask::ALT) != Key::NONE); \ + m_event->set_ctrl_pressed(((m_modifiers) & KeyModifierMask::CTRL) != Key::NONE); \ + m_event->set_meta_pressed(((m_modifiers) & KeyModifierMask::META) != Key::NONE); + +#define _CREATE_GUI_MOUSE_EVENT(m_screen_pos, m_input, m_mask, m_modifiers) \ + Ref event; \ + event.instantiate(); \ + event->set_position(m_screen_pos); \ + event->set_button_index(m_input); \ + event->set_button_mask(m_mask); \ + event->set_factor(1); \ + _UPDATE_EVENT_MODIFIERS(event, m_modifiers); \ + event->set_pressed(true); + +#define _CREATE_GUI_TOUCH_EVENT(m_screen_pos, m_pressed, m_double) \ + Ref event; \ + event.instantiate(); \ + event->set_position(m_screen_pos); \ + event->set_pressed(m_pressed); \ + event->set_double_tap(m_double); + +#define SEND_GUI_MOUSE_BUTTON_EVENT(m_screen_pos, m_input, m_mask, m_modifiers) \ + { \ + _CREATE_GUI_MOUSE_EVENT(m_screen_pos, m_input, m_mask, m_modifiers); \ + _SEND_DISPLAYSERVER_EVENT(event); \ + MessageQueue::get_singleton()->flush(); \ + } + +#define SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT(m_screen_pos, m_input, m_mask, m_modifiers) \ + { \ + _CREATE_GUI_MOUSE_EVENT(m_screen_pos, m_input, m_mask, m_modifiers); \ + event->set_pressed(false); \ + _SEND_DISPLAYSERVER_EVENT(event); \ + MessageQueue::get_singleton()->flush(); \ + } + +#define SEND_GUI_DOUBLE_CLICK(m_screen_pos, m_modifiers) \ + { \ + _CREATE_GUI_MOUSE_EVENT(m_screen_pos, MouseButton::LEFT, MouseButtonMask::NONE, m_modifiers); \ + event->set_double_click(true); \ + _SEND_DISPLAYSERVER_EVENT(event); \ + MessageQueue::get_singleton()->flush(); \ + } + +// We toggle _print_error_enabled to prevent display server not supported warnings. +#define SEND_GUI_MOUSE_MOTION_EVENT(m_screen_pos, m_mask, m_modifiers) \ + { \ + bool errors_enabled = CoreGlobals::print_error_enabled; \ + CoreGlobals::print_error_enabled = false; \ + Ref event; \ + event.instantiate(); \ + event->set_position(m_screen_pos); \ + event->set_button_mask(m_mask); \ + _UPDATE_EVENT_MODIFIERS(event, m_modifiers); \ + _SEND_DISPLAYSERVER_EVENT(event); \ + MessageQueue::get_singleton()->flush(); \ + CoreGlobals::print_error_enabled = errors_enabled; \ + } + +#define SEND_GUI_TOUCH_EVENT(m_screen_pos, m_pressed, m_double) \ + { \ + _CREATE_GUI_TOUCH_EVENT(m_screen_pos, m_pressed, m_double) \ + _SEND_DISPLAYSERVER_EVENT(event); \ + MessageQueue::get_singleton()->flush(); \ + } diff --git a/tests/scene/test_animation_blend_tree.cpp b/tests/scene/test_animation_blend_tree.cpp index f168751d5b..d579a8b227 100644 --- a/tests/scene/test_animation_blend_tree.cpp +++ b/tests/scene/test_animation_blend_tree.cpp @@ -33,7 +33,6 @@ TEST_FORCE_LINK(test_animation_blend_tree) #include "scene/animation/animation_blend_tree.h" -#include "tests/test_utils.h" namespace TestAnimationBlendTree { diff --git a/tests/scene/test_bit_map.cpp b/tests/scene/test_bit_map.cpp index 5c57fc2ceb..89ebf6d3fe 100644 --- a/tests/scene/test_bit_map.cpp +++ b/tests/scene/test_bit_map.cpp @@ -32,7 +32,6 @@ TEST_FORCE_LINK(test_bit_map) -#include "core/os/memory.h" #include "scene/resources/bit_map.h" namespace TestBitMap { diff --git a/tests/scene/test_button.cpp b/tests/scene/test_button.cpp index f1b33675cd..5217af02d5 100644 --- a/tests/scene/test_button.cpp +++ b/tests/scene/test_button.cpp @@ -34,6 +34,7 @@ TEST_FORCE_LINK(test_button) #include "scene/gui/button.h" #include "scene/main/window.h" +#include "tests/display_server_mock.h" namespace TestButton { diff --git a/tests/scene/test_code_edit.cpp b/tests/scene/test_code_edit.cpp index fe0a4c033f..e7d69c0602 100644 --- a/tests/scene/test_code_edit.cpp +++ b/tests/scene/test_code_edit.cpp @@ -34,7 +34,10 @@ TEST_FORCE_LINK(test_code_edit) #ifndef ADVANCED_GUI_DISABLED +#include "core/input/input_map.h" #include "scene/gui/code_edit.h" +#include "tests/display_server_mock.h" +#include "tests/signal_watcher.h" namespace TestCodeEdit { diff --git a/tests/scene/test_color_picker.cpp b/tests/scene/test_color_picker.cpp index 46f73ea11d..9015893721 100644 --- a/tests/scene/test_color_picker.cpp +++ b/tests/scene/test_color_picker.cpp @@ -35,6 +35,7 @@ TEST_FORCE_LINK(test_color_picker) #ifndef ADVANCED_GUI_DISABLED #include "scene/gui/color_picker.h" +#include "tests/display_server_mock.h" namespace TestColorPicker { diff --git a/tests/scene/test_control.cpp b/tests/scene/test_control.cpp index 00ec1a3424..6db0b0a13f 100644 --- a/tests/scene/test_control.cpp +++ b/tests/scene/test_control.cpp @@ -32,9 +32,12 @@ TEST_FORCE_LINK(test_control) +#include "core/input/input_map.h" // IWYU pragma: keep // Used by `SEND_GUI_ACTION` macro. #include "scene/2d/node_2d.h" #include "scene/gui/control.h" #include "scene/main/window.h" +#include "tests/display_server_mock.h" +#include "tests/signal_watcher.h" namespace TestControl { diff --git a/tests/scene/test_graph_node.cpp b/tests/scene/test_graph_node.cpp index eef3094033..ff75af4d81 100644 --- a/tests/scene/test_graph_node.cpp +++ b/tests/scene/test_graph_node.cpp @@ -35,7 +35,6 @@ TEST_FORCE_LINK(test_graph_node) #ifndef ADVANCED_GUI_DISABLED #include "scene/gui/graph_node.h" -#include "scene/main/window.h" namespace TestGraphNode { diff --git a/tests/scene/test_height_map_shape_3d.cpp b/tests/scene/test_height_map_shape_3d.cpp index 016aa7b5d0..0e7d0c617a 100644 --- a/tests/scene/test_height_map_shape_3d.cpp +++ b/tests/scene/test_height_map_shape_3d.cpp @@ -34,9 +34,8 @@ TEST_FORCE_LINK(test_height_map_shape_3d) #ifndef PHYSICS_3D_DISABLED +#include "core/io/image.h" #include "scene/resources/3d/height_map_shape_3d.h" -#include "scene/resources/image_texture.h" -#include "tests/test_utils.h" namespace TestHeightMapShape3D { diff --git a/tests/scene/test_navigation_region_2d.cpp b/tests/scene/test_navigation_region_2d.cpp index 5a90089974..a33498970a 100644 --- a/tests/scene/test_navigation_region_2d.cpp +++ b/tests/scene/test_navigation_region_2d.cpp @@ -37,7 +37,6 @@ TEST_FORCE_LINK(test_navigation_region_2d) #ifdef MODULE_NAVIGATION_2D_ENABLED #include "scene/2d/navigation/navigation_region_2d.h" -#include "scene/main/window.h" namespace TestNavigationRegion2D { diff --git a/tests/scene/test_split_container.cpp b/tests/scene/test_split_container.cpp index cd4b3195d8..b2eaa48911 100644 --- a/tests/scene/test_split_container.cpp +++ b/tests/scene/test_split_container.cpp @@ -36,6 +36,8 @@ TEST_FORCE_LINK(test_split_container) #include "scene/gui/split_container.h" #include "scene/main/window.h" +#include "tests/display_server_mock.h" +#include "tests/signal_watcher.h" namespace TestSplitContainer { diff --git a/tests/scene/test_sprite_2d.cpp b/tests/scene/test_sprite_2d.cpp index 89c097d27b..668710cfb9 100644 --- a/tests/scene/test_sprite_2d.cpp +++ b/tests/scene/test_sprite_2d.cpp @@ -33,7 +33,6 @@ TEST_FORCE_LINK(test_sprite_2d) #include "scene/2d/sprite_2d.h" -#include "tests/test_utils.h" namespace TestSprite2D { diff --git a/tests/scene/test_tab_bar.cpp b/tests/scene/test_tab_bar.cpp index 85bf82834e..05c84a2db3 100644 --- a/tests/scene/test_tab_bar.cpp +++ b/tests/scene/test_tab_bar.cpp @@ -36,6 +36,8 @@ TEST_FORCE_LINK(test_tab_bar) #include "scene/gui/tab_bar.h" #include "scene/main/window.h" +#include "tests/display_server_mock.h" +#include "tests/signal_watcher.h" namespace TestTabBar { diff --git a/tests/scene/test_tab_container.cpp b/tests/scene/test_tab_container.cpp index 464d92db1a..0813e81084 100644 --- a/tests/scene/test_tab_container.cpp +++ b/tests/scene/test_tab_container.cpp @@ -37,6 +37,8 @@ TEST_FORCE_LINK(test_tab_container) #include "scene/gui/box_container.h" #include "scene/gui/tab_container.h" #include "scene/main/window.h" +#include "tests/display_server_mock.h" +#include "tests/signal_watcher.h" namespace TestTabContainer { diff --git a/tests/scene/test_text_edit.cpp b/tests/scene/test_text_edit.cpp index 87ead54569..be4c98bc91 100644 --- a/tests/scene/test_text_edit.cpp +++ b/tests/scene/test_text_edit.cpp @@ -34,7 +34,10 @@ TEST_FORCE_LINK(test_text_edit) #ifndef ADVANCED_GUI_DISABLED +#include "core/input/input_map.h" #include "scene/gui/text_edit.h" +#include "tests/display_server_mock.h" +#include "tests/signal_watcher.h" namespace TestTextEdit { diff --git a/tests/scene/test_timer.cpp b/tests/scene/test_timer.cpp index b7a13ee1a9..abc6a259d9 100644 --- a/tests/scene/test_timer.cpp +++ b/tests/scene/test_timer.cpp @@ -34,6 +34,7 @@ TEST_FORCE_LINK(test_timer) #include "scene/main/timer.h" #include "scene/main/window.h" +#include "tests/signal_watcher.h" namespace TestTimer { diff --git a/tests/scene/test_viewport.cpp b/tests/scene/test_viewport.cpp index 5c06914df3..8c0b8998d3 100644 --- a/tests/scene/test_viewport.cpp +++ b/tests/scene/test_viewport.cpp @@ -37,6 +37,8 @@ TEST_FORCE_LINK(test_viewport) #include "scene/gui/subviewport_container.h" #include "scene/main/canvas_layer.h" #include "scene/main/window.h" +#include "tests/display_server_mock.h" +#include "tests/signal_watcher.h" #ifndef PHYSICS_2D_DISABLED #include "scene/2d/physics/area_2d.h" diff --git a/tests/scene/test_window.cpp b/tests/scene/test_window.cpp index c20622d876..f129548d74 100644 --- a/tests/scene/test_window.cpp +++ b/tests/scene/test_window.cpp @@ -32,8 +32,10 @@ TEST_FORCE_LINK(test_window) +#include "core/input/input_map.h" // IWYU pragma: keep // Used by `SEND_GUI_MOUSE_MOTION_EVENT` macro. #include "scene/gui/control.h" #include "scene/main/window.h" +#include "tests/display_server_mock.h" namespace TestWindow { diff --git a/tests/servers/test_navigation_server_2d.cpp b/tests/servers/test_navigation_server_2d.cpp index ef952e09a0..bb03890160 100644 --- a/tests/servers/test_navigation_server_2d.cpp +++ b/tests/servers/test_navigation_server_2d.cpp @@ -36,10 +36,10 @@ TEST_FORCE_LINK(test_navigation_server_2d) #ifdef MODULE_NAVIGATION_2D_ENABLED -#include "modules/navigation_2d/nav_utils_2d.h" #include "scene/2d/polygon_2d.h" #include "scene/main/window.h" #include "servers/navigation_2d/navigation_server_2d.h" +#include "tests/signal_watcher.h" namespace TestNavigationServer2D { diff --git a/tests/servers/test_navigation_server_3d.cpp b/tests/servers/test_navigation_server_3d.cpp index d5c20508d8..677e177587 100644 --- a/tests/servers/test_navigation_server_3d.cpp +++ b/tests/servers/test_navigation_server_3d.cpp @@ -40,6 +40,7 @@ TEST_FORCE_LINK(test_navigation_server_3d) #include "scene/main/window.h" #include "scene/resources/3d/primitive_meshes.h" #include "servers/navigation_3d/navigation_server_3d.h" +#include "tests/signal_watcher.h" namespace TestNavigationServer3D { diff --git a/tests/servers/test_text_server.cpp b/tests/servers/test_text_server.cpp index a3f8a6ebb0..2b79e409b5 100644 --- a/tests/servers/test_text_server.cpp +++ b/tests/servers/test_text_server.cpp @@ -34,6 +34,7 @@ TEST_FORCE_LINK(test_text_server) #ifdef TOOLS_ENABLED +#include "core/variant/typed_array.h" #include "editor/themes/builtin_fonts.gen.h" #include "servers/text/text_server.h" diff --git a/tests/signal_watcher.cpp b/tests/signal_watcher.cpp new file mode 100644 index 0000000000..fe04266b9d --- /dev/null +++ b/tests/signal_watcher.cpp @@ -0,0 +1,167 @@ +/**************************************************************************/ +/* signal_watcher.cpp */ +/**************************************************************************/ +/* 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. */ +/**************************************************************************/ + +#include "signal_watcher.h" + +#include "core/object/class_db.h" + +void SignalWatcher::_add_signal_entry(const Array &p_args, const String &p_name) { + if (!_signals.has(p_name)) { + _signals[p_name] = Array(); + } + _signals[p_name].push_back(p_args); +} + +void SignalWatcher::_signal_callback_zero(const String &p_name) { + Array args; + _add_signal_entry(args, p_name); +} + +void SignalWatcher::_signal_callback_one(Variant p_arg1, const String &p_name) { + Array args = { p_arg1 }; + _add_signal_entry(args, p_name); +} + +void SignalWatcher::_signal_callback_two(Variant p_arg1, Variant p_arg2, const String &p_name) { + Array args = { p_arg1, p_arg2 }; + _add_signal_entry(args, p_name); +} + +void SignalWatcher::_signal_callback_three(Variant p_arg1, Variant p_arg2, Variant p_arg3, const String &p_name) { + Array args = { p_arg1, p_arg2, p_arg3 }; + _add_signal_entry(args, p_name); +} + +void SignalWatcher::watch_signal(Object *p_object, const String &p_signal) { + MethodInfo method_info; + ClassDB::get_signal(p_object->get_class(), p_signal, &method_info); + switch (method_info.arguments.size()) { + case 0: { + p_object->connect(p_signal, callable_mp(this, &SignalWatcher::_signal_callback_zero).bind(p_signal)); + } break; + case 1: { + p_object->connect(p_signal, callable_mp(this, &SignalWatcher::_signal_callback_one).bind(p_signal)); + } break; + case 2: { + p_object->connect(p_signal, callable_mp(this, &SignalWatcher::_signal_callback_two).bind(p_signal)); + } break; + case 3: { + p_object->connect(p_signal, callable_mp(this, &SignalWatcher::_signal_callback_three).bind(p_signal)); + } break; + default: { + MESSAGE("Signal ", p_signal, " arg count not supported."); + } break; + } +} + +void SignalWatcher::unwatch_signal(Object *p_object, const String &p_signal) { + MethodInfo method_info; + ClassDB::get_signal(p_object->get_class(), p_signal, &method_info); + switch (method_info.arguments.size()) { + case 0: { + p_object->disconnect(p_signal, callable_mp(this, &SignalWatcher::_signal_callback_zero)); + } break; + case 1: { + p_object->disconnect(p_signal, callable_mp(this, &SignalWatcher::_signal_callback_one)); + } break; + case 2: { + p_object->disconnect(p_signal, callable_mp(this, &SignalWatcher::_signal_callback_two)); + } break; + case 3: { + p_object->disconnect(p_signal, callable_mp(this, &SignalWatcher::_signal_callback_three)); + } break; + default: { + MESSAGE("Signal ", p_signal, " arg count not supported."); + } break; + } +} + +bool SignalWatcher::check(const String &p_name, const Array &p_args) { + if (!_signals.has(p_name)) { + MESSAGE("Signal ", p_name, " not emitted"); + return false; + } + + if (p_args.size() != _signals[p_name].size()) { + MESSAGE("Signal has " << _signals[p_name] << " expected " << p_args); + discard_signal(p_name); + return false; + } + + bool match = true; + for (int i = 0; i < p_args.size(); i++) { + if (((Array)p_args[i]).size() != ((Array)_signals[p_name][i]).size()) { + MESSAGE("Signal has " << _signals[p_name][i] << " expected " << p_args[i]); + match = false; + continue; + } + + for (int j = 0; j < ((Array)p_args[i]).size(); j++) { + if (((Array)p_args[i])[j] != ((Array)_signals[p_name][i])[j]) { + MESSAGE("Signal has " << _signals[p_name][i] << " expected " << p_args[i]); + match = false; + break; + } + } + } + + discard_signal(p_name); + return match; +} + +bool SignalWatcher::check_false(const String &p_name) { + bool has = _signals.has(p_name); + if (has) { + MESSAGE("Signal has " << _signals[p_name] << " expected none."); + } + discard_signal(p_name); + return !has; +} + +void SignalWatcher::discard_signal(const String &p_name) { + if (_signals.has(p_name)) { + _signals.erase(p_name); + } +} + +void SignalWatcher::_clear_signals() { + _signals.clear(); +} + +SignalWatcher::SignalWatcher() { + ERR_FAIL_COND_MSG(singleton, "Singleton in SignalWatcher already exists."); + singleton = this; +} + +SignalWatcher::~SignalWatcher() { + if (this == singleton) { + singleton = nullptr; + } +} diff --git a/tests/signal_watcher.h b/tests/signal_watcher.h new file mode 100644 index 0000000000..0b20b67732 --- /dev/null +++ b/tests/signal_watcher.h @@ -0,0 +1,83 @@ +/**************************************************************************/ +/* signal_watcher.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. */ +/**************************************************************************/ + +#pragma once + +#include "core/object/object.h" +#include "tests/test_macros.h" + +// Utility class / macros for testing signals +// +// Use SIGNAL_WATCH(*object, "signal_name") to start watching +// Makes sure to call SIGNAL_UNWATCH(*object, "signal_name") to stop watching in cleanup, this is not done automatically. +// +// The SignalWatcher will capture all signals and their args sent between checks. +// +// Use SIGNAL_CHECK("signal_name"), Vector>), to check the arguments of all fired signals. +// The outer vector is each fired signal, the inner vector the list of arguments for that signal. Order does matter. +// +// Use SIGNAL_CHECK_FALSE("signal_name") to check if a signal was not fired. +// +// Use SIGNAL_DISCARD("signal_name") to discard records all of the given signal, use only in placed you don't need to check. +// +// All signals are automatically discarded between test/sub test cases. + +class SignalWatcher : public Object { + inline static SignalWatcher *singleton = nullptr; + + HashMap _signals; + + void _add_signal_entry(const Array &p_args, const String &p_name); + void _signal_callback_zero(const String &p_name); + void _signal_callback_one(Variant p_arg1, const String &p_name); + void _signal_callback_two(Variant p_arg1, Variant p_arg2, const String &p_name); + void _signal_callback_three(Variant p_arg1, Variant p_arg2, Variant p_arg3, const String &p_name); + +public: + static SignalWatcher *get_singleton() { return singleton; } + + void watch_signal(Object *p_object, const String &p_signal); + void unwatch_signal(Object *p_object, const String &p_signal); + bool check(const String &p_name, const Array &p_args); + bool check_false(const String &p_name); + void discard_signal(const String &p_name); + + void _clear_signals(); + + SignalWatcher(); + ~SignalWatcher(); +}; + +#define SIGNAL_WATCH(m_object, m_signal) SignalWatcher::get_singleton()->watch_signal(m_object, m_signal); +#define SIGNAL_UNWATCH(m_object, m_signal) SignalWatcher::get_singleton()->unwatch_signal(m_object, m_signal); + +#define SIGNAL_CHECK(m_signal, m_args) CHECK(SignalWatcher::get_singleton()->check(m_signal, m_args)); +#define SIGNAL_CHECK_FALSE(m_signal) CHECK(SignalWatcher::get_singleton()->check_false(m_signal)); +#define SIGNAL_DISCARD(m_signal) SignalWatcher::get_singleton()->discard_signal(m_signal); diff --git a/tests/test_macros.h b/tests/test_macros.h index c7ea02cbf0..f9b8f7b2a2 100644 --- a/tests/test_macros.h +++ b/tests/test_macros.h @@ -30,13 +30,20 @@ #pragma once -#include "display_server_mock.h" - -#include "core/core_globals.h" -#include "core/input/input_map.h" -#include "core/object/message_queue.h" #include "core/variant/variant.h" +#if defined(_MSC_VER) && !defined(DOCTEST_THREAD_LOCAL) +// NOTE: We must disable the THREAD_LOCAL entirely in doctest to prevent crashes on debugging. +// Since we link with /MT, thread_local is always expired when the header is used, so the +// debugger crashes the engine and it causes weird errors. +// See: https://github.com/onqtam/doctest/issues/401 +#define DOCTEST_THREAD_LOCAL +#endif + +#if !__cpp_exceptions && !__EXCEPTIONS && !defined(DOCTEST_CONFIG_NO_EXCEPTIONS_BUT_WITH_ALL_ASSERTS) +#define DOCTEST_CONFIG_NO_EXCEPTIONS_BUT_WITH_ALL_ASSERTS +#endif + // See documentation for doctest at: // https://github.com/onqtam/doctest/blob/master/doc/markdown/readme.md#reference #include "thirdparty/doctest/doctest.h" @@ -141,276 +148,6 @@ int register_test_command(String p_command, TestFunc p_function); DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_ANONYMOUS(DOCTEST_ANON_VAR_), \ register_test_command(m_command, m_function)) -// Utility macros to send an event actions to a given object -// Requires Message Queue and InputMap to be setup. -// SEND_GUI_ACTION - takes an input map key. e.g SEND_GUI_ACTION("ui_text_newline"). -// SEND_GUI_KEY_EVENT - takes a keycode set. e.g SEND_GUI_KEY_EVENT(Key::A | KeyModifierMask::META). -// SEND_GUI_KEY_UP_EVENT - takes a keycode set. e.g SEND_GUI_KEY_UP_EVENT(Key::A | KeyModifierMask::META). -// SEND_GUI_MOUSE_BUTTON_EVENT - takes a position, mouse button, mouse mask and modifiers e.g SEND_GUI_MOUSE_BUTTON_EVENT(Vector2(50, 50), MOUSE_BUTTON_NONE, MOUSE_BUTTON_NONE, Key::None); -// SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT - takes a position, mouse button, mouse mask and modifiers e.g SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT(Vector2(50, 50), MOUSE_BUTTON_NONE, MOUSE_BUTTON_NONE, Key::None); -// SEND_GUI_MOUSE_MOTION_EVENT - takes a position, mouse mask and modifiers e.g SEND_GUI_MOUSE_MOTION_EVENT(Vector2(50, 50), MouseButtonMask::LEFT, KeyModifierMask::META); -// SEND_GUI_DOUBLE_CLICK - takes a position and modifiers. e.g SEND_GUI_DOUBLE_CLICK(Vector2(50, 50), KeyModifierMask::META); - -#define _SEND_DISPLAYSERVER_EVENT(m_event) ((DisplayServerMock *)(DisplayServer::get_singleton()))->simulate_event(m_event); - -#define SEND_GUI_ACTION(m_action) \ - { \ - const List> *events = InputMap::get_singleton()->action_get_events(m_action); \ - const List>::Element *first_event = events->front(); \ - Ref event = first_event->get()->duplicate(); \ - event->set_pressed(true); \ - _SEND_DISPLAYSERVER_EVENT(event); \ - MessageQueue::get_singleton()->flush(); \ - } - -#define SEND_GUI_KEY_EVENT(m_input) \ - { \ - Ref event = InputEventKey::create_reference(m_input); \ - event->set_pressed(true); \ - _SEND_DISPLAYSERVER_EVENT(event); \ - MessageQueue::get_singleton()->flush(); \ - } - -#define SEND_GUI_KEY_UP_EVENT(m_input) \ - { \ - Ref event = InputEventKey::create_reference(m_input); \ - event->set_pressed(false); \ - _SEND_DISPLAYSERVER_EVENT(event); \ - MessageQueue::get_singleton()->flush(); \ - } - -#define _UPDATE_EVENT_MODIFIERS(m_event, m_modifiers) \ - m_event->set_shift_pressed(((m_modifiers) & KeyModifierMask::SHIFT) != Key::NONE); \ - m_event->set_alt_pressed(((m_modifiers) & KeyModifierMask::ALT) != Key::NONE); \ - m_event->set_ctrl_pressed(((m_modifiers) & KeyModifierMask::CTRL) != Key::NONE); \ - m_event->set_meta_pressed(((m_modifiers) & KeyModifierMask::META) != Key::NONE); - -#define _CREATE_GUI_MOUSE_EVENT(m_screen_pos, m_input, m_mask, m_modifiers) \ - Ref event; \ - event.instantiate(); \ - event->set_position(m_screen_pos); \ - event->set_button_index(m_input); \ - event->set_button_mask(m_mask); \ - event->set_factor(1); \ - _UPDATE_EVENT_MODIFIERS(event, m_modifiers); \ - event->set_pressed(true); - -#define _CREATE_GUI_TOUCH_EVENT(m_screen_pos, m_pressed, m_double) \ - Ref event; \ - event.instantiate(); \ - event->set_position(m_screen_pos); \ - event->set_pressed(m_pressed); \ - event->set_double_tap(m_double); - -#define SEND_GUI_MOUSE_BUTTON_EVENT(m_screen_pos, m_input, m_mask, m_modifiers) \ - { \ - _CREATE_GUI_MOUSE_EVENT(m_screen_pos, m_input, m_mask, m_modifiers); \ - _SEND_DISPLAYSERVER_EVENT(event); \ - MessageQueue::get_singleton()->flush(); \ - } - -#define SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT(m_screen_pos, m_input, m_mask, m_modifiers) \ - { \ - _CREATE_GUI_MOUSE_EVENT(m_screen_pos, m_input, m_mask, m_modifiers); \ - event->set_pressed(false); \ - _SEND_DISPLAYSERVER_EVENT(event); \ - MessageQueue::get_singleton()->flush(); \ - } - -#define SEND_GUI_DOUBLE_CLICK(m_screen_pos, m_modifiers) \ - { \ - _CREATE_GUI_MOUSE_EVENT(m_screen_pos, MouseButton::LEFT, MouseButtonMask::NONE, m_modifiers); \ - event->set_double_click(true); \ - _SEND_DISPLAYSERVER_EVENT(event); \ - MessageQueue::get_singleton()->flush(); \ - } - -// We toggle _print_error_enabled to prevent display server not supported warnings. -#define SEND_GUI_MOUSE_MOTION_EVENT(m_screen_pos, m_mask, m_modifiers) \ - { \ - bool errors_enabled = CoreGlobals::print_error_enabled; \ - CoreGlobals::print_error_enabled = false; \ - Ref event; \ - event.instantiate(); \ - event->set_position(m_screen_pos); \ - event->set_button_mask(m_mask); \ - _UPDATE_EVENT_MODIFIERS(event, m_modifiers); \ - _SEND_DISPLAYSERVER_EVENT(event); \ - MessageQueue::get_singleton()->flush(); \ - CoreGlobals::print_error_enabled = errors_enabled; \ - } - -#define SEND_GUI_TOUCH_EVENT(m_screen_pos, m_pressed, m_double) \ - { \ - _CREATE_GUI_TOUCH_EVENT(m_screen_pos, m_pressed, m_double) \ - _SEND_DISPLAYSERVER_EVENT(event); \ - MessageQueue::get_singleton()->flush(); \ - } - -// Utility class / macros for testing signals -// -// Use SIGNAL_WATCH(*object, "signal_name") to start watching -// Makes sure to call SIGNAL_UNWATCH(*object, "signal_name") to stop watching in cleanup, this is not done automatically. -// -// The SignalWatcher will capture all signals and their args sent between checks. -// -// Use SIGNAL_CHECK("signal_name"), Vector>), to check the arguments of all fired signals. -// The outer vector is each fired signal, the inner vector the list of arguments for that signal. Order does matter. -// -// Use SIGNAL_CHECK_FALSE("signal_name") to check if a signal was not fired. -// -// Use SIGNAL_DISCARD("signal_name") to discard records all of the given signal, use only in placed you don't need to check. -// -// All signals are automatically discarded between test/sub test cases. - -class SignalWatcher : public Object { -private: - inline static SignalWatcher *singleton; - - /* Equal to: RBMap>> */ - HashMap _signals; - void _add_signal_entry(const Array &p_args, const String &p_name) { - if (!_signals.has(p_name)) { - _signals[p_name] = Array(); - } - _signals[p_name].push_back(p_args); - } - - void _signal_callback_zero(const String &p_name) { - Array args; - _add_signal_entry(args, p_name); - } - - void _signal_callback_one(Variant p_arg1, const String &p_name) { - Array args = { p_arg1 }; - _add_signal_entry(args, p_name); - } - - void _signal_callback_two(Variant p_arg1, Variant p_arg2, const String &p_name) { - Array args = { p_arg1, p_arg2 }; - _add_signal_entry(args, p_name); - } - - void _signal_callback_three(Variant p_arg1, Variant p_arg2, Variant p_arg3, const String &p_name) { - Array args = { p_arg1, p_arg2, p_arg3 }; - _add_signal_entry(args, p_name); - } - -public: - static SignalWatcher *get_singleton() { return singleton; } - - void watch_signal(Object *p_object, const String &p_signal) { - MethodInfo method_info; - ClassDB::get_signal(p_object->get_class(), p_signal, &method_info); - switch (method_info.arguments.size()) { - case 0: { - p_object->connect(p_signal, callable_mp(this, &SignalWatcher::_signal_callback_zero).bind(p_signal)); - } break; - case 1: { - p_object->connect(p_signal, callable_mp(this, &SignalWatcher::_signal_callback_one).bind(p_signal)); - } break; - case 2: { - p_object->connect(p_signal, callable_mp(this, &SignalWatcher::_signal_callback_two).bind(p_signal)); - } break; - case 3: { - p_object->connect(p_signal, callable_mp(this, &SignalWatcher::_signal_callback_three).bind(p_signal)); - } break; - default: { - MESSAGE("Signal ", p_signal, " arg count not supported."); - } break; - } - } - - void unwatch_signal(Object *p_object, const String &p_signal) { - MethodInfo method_info; - ClassDB::get_signal(p_object->get_class(), p_signal, &method_info); - switch (method_info.arguments.size()) { - case 0: { - p_object->disconnect(p_signal, callable_mp(this, &SignalWatcher::_signal_callback_zero)); - } break; - case 1: { - p_object->disconnect(p_signal, callable_mp(this, &SignalWatcher::_signal_callback_one)); - } break; - case 2: { - p_object->disconnect(p_signal, callable_mp(this, &SignalWatcher::_signal_callback_two)); - } break; - case 3: { - p_object->disconnect(p_signal, callable_mp(this, &SignalWatcher::_signal_callback_three)); - } break; - default: { - MESSAGE("Signal ", p_signal, " arg count not supported."); - } break; - } - } - - bool check(const String &p_name, const Array &p_args) { - if (!_signals.has(p_name)) { - MESSAGE("Signal ", p_name, " not emitted"); - return false; - } - - if (p_args.size() != _signals[p_name].size()) { - MESSAGE("Signal has " << _signals[p_name] << " expected " << p_args); - discard_signal(p_name); - return false; - } - - bool match = true; - for (int i = 0; i < p_args.size(); i++) { - if (((Array)p_args[i]).size() != ((Array)_signals[p_name][i]).size()) { - MESSAGE("Signal has " << _signals[p_name][i] << " expected " << p_args[i]); - match = false; - continue; - } - - for (int j = 0; j < ((Array)p_args[i]).size(); j++) { - if (((Array)p_args[i])[j] != ((Array)_signals[p_name][i])[j]) { - MESSAGE("Signal has " << _signals[p_name][i] << " expected " << p_args[i]); - match = false; - break; - } - } - } - - discard_signal(p_name); - return match; - } - - bool check_false(const String &p_name) { - bool has = _signals.has(p_name); - if (has) { - MESSAGE("Signal has " << _signals[p_name] << " expected none."); - } - discard_signal(p_name); - return !has; - } - - void discard_signal(const String &p_name) { - if (_signals.has(p_name)) { - _signals.erase(p_name); - } - } - - void _clear_signals() { - _signals.clear(); - } - - SignalWatcher() { - singleton = this; - } - - ~SignalWatcher() { - singleton = nullptr; - } -}; - -#define SIGNAL_WATCH(m_object, m_signal) SignalWatcher::get_singleton()->watch_signal(m_object, m_signal); -#define SIGNAL_UNWATCH(m_object, m_signal) SignalWatcher::get_singleton()->unwatch_signal(m_object, m_signal); - -#define SIGNAL_CHECK(m_signal, m_args) CHECK(SignalWatcher::get_singleton()->check(m_signal, m_args)); -#define SIGNAL_CHECK_FALSE(m_signal) CHECK(SignalWatcher::get_singleton()->check_false(m_signal)); -#define SIGNAL_DISCARD(m_signal) SignalWatcher::get_singleton()->discard_signal(m_signal); - #define MULTICHECK_STRING_EQ(m_obj, m_func, m_param1, m_eq) \ CHECK(m_obj.m_func(m_param1) == m_eq); \ CHECK(m_obj.m_func(U##m_param1) == m_eq); \ diff --git a/tests/test_main.cpp b/tests/test_main.cpp index ae57c3a2a7..e78ef63725 100644 --- a/tests/test_main.cpp +++ b/tests/test_main.cpp @@ -31,6 +31,7 @@ #include "test_main.h" #include "core/input/input.h" +#include "core/input/input_map.h" #include "core/io/dir_access.h" #include "core/string/translation_server.h" #include "scene/main/window.h" @@ -39,6 +40,7 @@ #include "servers/rendering/rendering_server_default.h" #include "tests/display_server_mock.h" #include "tests/force_link.gen.h" +#include "tests/signal_watcher.h" #include "tests/test_macros.h" #include "tests/test_utils.h" @@ -63,7 +65,7 @@ #include "servers/physics_3d/physics_server_3d_dummy.h" #endif // PHYSICS_3D_DISABLED -#include "modules/modules_tests.gen.h" // TODO: Migrate module tests to compilation files. +#include "modules/modules_tests.gen.h" // IWYU pragma: keep // TODO: Migrate module tests to compilation files. int test_main(int argc, char *argv[]) { ForceLink::force_link_tests();