diff options
Diffstat (limited to 'tests/test_macros.h')
-rw-r--r-- | tests/test_macros.h | 300 |
1 files changed, 276 insertions, 24 deletions
diff --git a/tests/test_macros.h b/tests/test_macros.h index a13f3abbe7..6029a9cfc7 100644 --- a/tests/test_macros.h +++ b/tests/test_macros.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -31,7 +31,8 @@ #ifndef TEST_MACROS_H #define TEST_MACROS_H -#include "core/templates/map.h" +#include "core/input/input_map.h" +#include "core/object/message_queue.h" #include "core/variant/variant.h" // See documentation for doctest at: @@ -62,22 +63,22 @@ // Stringify all `Variant` compatible types for doctest output by default. // https://github.com/onqtam/doctest/blob/master/doc/markdown/stringification.md -#define DOCTEST_STRINGIFY_VARIANT(m_type) \ - template <> \ - struct doctest::StringMaker<m_type> { \ - static doctest::String convert(const m_type &p_val) { \ - const Variant val = p_val; \ - return val.get_construct_string().utf8().get_data(); \ - } \ +#define DOCTEST_STRINGIFY_VARIANT(m_type) \ + template <> \ + struct doctest::StringMaker<m_type> { \ + static doctest::String convert(const m_type &p_val) { \ + const Variant val = p_val; \ + return val.operator ::String().utf8().get_data(); \ + } \ }; -#define DOCTEST_STRINGIFY_VARIANT_POINTER(m_type) \ - template <> \ - struct doctest::StringMaker<m_type> { \ - static doctest::String convert(const m_type *p_val) { \ - const Variant val = p_val; \ - return val.get_construct_string().utf8().get_data(); \ - } \ +#define DOCTEST_STRINGIFY_VARIANT_POINTER(m_type) \ + template <> \ + struct doctest::StringMaker<m_type> { \ + static doctest::String convert(const m_type *p_val) { \ + const Variant val = p_val; \ + return val.operator ::String().utf8().get_data(); \ + } \ }; DOCTEST_STRINGIFY_VARIANT(Variant); @@ -91,10 +92,10 @@ DOCTEST_STRINGIFY_VARIANT(Vector3); DOCTEST_STRINGIFY_VARIANT(Vector3i); DOCTEST_STRINGIFY_VARIANT(Transform2D); DOCTEST_STRINGIFY_VARIANT(Plane); -DOCTEST_STRINGIFY_VARIANT(Quat); +DOCTEST_STRINGIFY_VARIANT(Quaternion); DOCTEST_STRINGIFY_VARIANT(AABB); DOCTEST_STRINGIFY_VARIANT(Basis); -DOCTEST_STRINGIFY_VARIANT(Transform); +DOCTEST_STRINGIFY_VARIANT(Transform3D); DOCTEST_STRINGIFY_VARIANT(::Color); // Disambiguate from `doctest::Color`. DOCTEST_STRINGIFY_VARIANT(StringName); @@ -121,12 +122,263 @@ DOCTEST_STRINGIFY_VARIANT(PackedColorArray); // Example usage: `godot --test gdscript-parser`. typedef void (*TestFunc)(); -extern Map<String, TestFunc> *test_commands; +extern HashMap<String, TestFunc> *test_commands; int register_test_command(String p_command, TestFunc p_function); -#define REGISTER_TEST_COMMAND(m_command, m_function) \ - DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_ANONYMOUS(_DOCTEST_ANON_VAR_)) = \ - register_test_command(m_command, m_function); \ - DOCTEST_GLOBAL_NO_WARNINGS_END() +#define REGISTER_TEST_COMMAND(m_command, m_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 object and a input map key. e.g SEND_GUI_ACTION(code_edit, "ui_text_newline"). +// SEND_GUI_KEY_EVENT - takes an object and a keycode set. e.g SEND_GUI_KEY_EVENT(code_edit, Key::A | KeyModifierMask::CMD). +// SEND_GUI_MOUSE_BUTTON_EVENT - takes an object, position, mouse button, mouse mask and modifiers e.g SEND_GUI_MOUSE_BUTTON_EVENT(code_edit, Vector2(50, 50), MOUSE_BUTTON_NONE, MOUSE_BUTTON_NONE, Key::None); +// SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT - takes an object, position, mouse button, mouse mask and modifiers e.g SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT(code_edit, Vector2(50, 50), MOUSE_BUTTON_NONE, MOUSE_BUTTON_NONE, Key::None); +// SEND_GUI_MOUSE_MOTION_EVENT - takes an object, position, mouse mask and modifiers e.g SEND_GUI_MOUSE_MOTION_EVENT(code_edit, Vector2(50, 50), MouseButton::MASK_LEFT, KeyModifierMask::CMD); +// SEND_GUI_DOUBLE_CLICK - takes an object, position and modifiers. e.g SEND_GUI_DOUBLE_CLICK(code_edit, Vector2(50, 50), KeyModifierMask::CMD); + +#define SEND_GUI_ACTION(m_object, m_action) \ + { \ + const List<Ref<InputEvent>> *events = InputMap::get_singleton()->action_get_events(m_action); \ + const List<Ref<InputEvent>>::Element *first_event = events->front(); \ + Ref<InputEventKey> event = first_event->get(); \ + event->set_pressed(true); \ + m_object->get_viewport()->push_input(event); \ + MessageQueue::get_singleton()->flush(); \ + } + +#define SEND_GUI_KEY_EVENT(m_object, m_input) \ + { \ + Ref<InputEventKey> event = InputEventKey::create_reference(m_input); \ + event->set_pressed(true); \ + m_object->get_viewport()->push_input(event); \ + MessageQueue::get_singleton()->flush(); \ + } + +#define _UPDATE_EVENT_MODIFERS(m_event, m_modifers) \ + m_event->set_shift_pressed(((m_modifers)&KeyModifierMask::SHIFT) != Key::NONE); \ + m_event->set_alt_pressed(((m_modifers)&KeyModifierMask::ALT) != Key::NONE); \ + m_event->set_ctrl_pressed(((m_modifers)&KeyModifierMask::CTRL) != Key::NONE); \ + m_event->set_command_pressed(((m_modifers)&KeyModifierMask::CMD) != Key::NONE); \ + m_event->set_meta_pressed(((m_modifers)&KeyModifierMask::META) != Key::NONE); + +#define _CREATE_GUI_MOUSE_EVENT(m_object, m_local_pos, m_input, m_mask, m_modifers) \ + Ref<InputEventMouseButton> event; \ + event.instantiate(); \ + event->set_position(m_local_pos); \ + event->set_button_index(m_input); \ + event->set_button_mask(m_mask); \ + event->set_factor(1); \ + _UPDATE_EVENT_MODIFERS(event, m_modifers); \ + event->set_pressed(true); + +#define SEND_GUI_MOUSE_BUTTON_EVENT(m_object, m_local_pos, m_input, m_mask, m_modifers) \ + { \ + _CREATE_GUI_MOUSE_EVENT(m_object, m_local_pos, m_input, m_mask, m_modifers); \ + m_object->get_viewport()->push_input(event); \ + MessageQueue::get_singleton()->flush(); \ + } + +#define SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT(m_object, m_local_pos, m_input, m_mask, m_modifers) \ + { \ + _CREATE_GUI_MOUSE_EVENT(m_object, m_local_pos, m_input, m_mask, m_modifers); \ + event->set_pressed(false); \ + m_object->get_viewport()->push_input(event); \ + MessageQueue::get_singleton()->flush(); \ + } + +#define SEND_GUI_DOUBLE_CLICK(m_object, m_local_pos, m_modifers) \ + { \ + _CREATE_GUI_MOUSE_EVENT(m_object, m_local_pos, MouseButton::LEFT, MouseButton::LEFT, m_modifers); \ + event->set_double_click(true); \ + m_object->get_viewport()->push_input(event); \ + MessageQueue::get_singleton()->flush(); \ + } + +// We toggle _print_error_enabled to prevent display server not supported warnings. +#define SEND_GUI_MOUSE_MOTION_EVENT(m_object, m_local_pos, m_mask, m_modifers) \ + { \ + bool errors_enabled = _print_error_enabled; \ + _print_error_enabled = false; \ + Ref<InputEventMouseMotion> event; \ + event.instantiate(); \ + event->set_position(m_local_pos); \ + event->set_button_mask(m_mask); \ + event->set_relative(Vector2(10, 10)); \ + _UPDATE_EVENT_MODIFERS(event, m_modifers); \ + m_object->get_viewport()->push_input(event); \ + MessageQueue::get_singleton()->flush(); \ + _print_error_enabled = errors_enabled; \ + } + +// 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<Vector<Variant>>), 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<String, Vector<Vector<Variant>>> */ + HashMap<String, Array> _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; + args.push_back(p_arg1); + _add_signal_entry(args, p_name); + } + + void _signal_callback_two(Variant p_arg1, Variant p_arg2, const String &p_name) { + Array args; + args.push_back(p_arg1); + args.push_back(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; + args.push_back(p_arg1); + args.push_back(p_arg2); + args.push_back(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) { + Vector<Variant> args; + args.push_back(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), args); + } break; + case 1: { + p_object->connect(p_signal, callable_mp(this, &SignalWatcher::_signal_callback_one), args); + } break; + case 2: { + p_object->connect(p_signal, callable_mp(this, &SignalWatcher::_signal_callback_two), args); + } break; + case 3: { + p_object->connect(p_signal, callable_mp(this, &SignalWatcher::_signal_callback_three), args); + } 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); + 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); #endif // TEST_MACROS_H |