summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/templates/cowdata.h4
-rw-r--r--core/templates/vector.h1
-rw-r--r--core/variant/variant_call.cpp10
-rw-r--r--tests/core/input/test_input_event_key.h294
-rw-r--r--tests/core/input/test_shortcut.h235
-rw-r--r--tests/core/math/test_quaternion.h389
-rw-r--r--tests/core/templates/test_rid.h101
-rw-r--r--tests/scene/test_audio_stream_wav.h243
-rw-r--r--tests/test_main.cpp19
9 files changed, 1291 insertions, 5 deletions
diff --git a/core/templates/cowdata.h b/core/templates/cowdata.h
index 0653aa76a8..f98b2308c9 100644
--- a/core/templates/cowdata.h
+++ b/core/templates/cowdata.h
@@ -159,6 +159,7 @@ public:
return _ptr[p_index];
}
+ template <bool p_ensure_zero = false>
Error resize(int p_size);
_FORCE_INLINE_ void remove_at(int p_index) {
@@ -258,6 +259,7 @@ uint32_t CowData<T>::_copy_on_write() {
}
template <class T>
+template <bool p_ensure_zero>
Error CowData<T>::resize(int p_size) {
ERR_FAIL_COND_V(p_size < 0, ERR_INVALID_PARAMETER);
@@ -307,6 +309,8 @@ Error CowData<T>::resize(int p_size) {
for (int i = *_get_size(); i < p_size; i++) {
memnew_placement(&_ptr[i], T);
}
+ } else if (p_ensure_zero) {
+ memset((void *)(_ptr + current_size), 0, (p_size - current_size) * sizeof(T));
}
*_get_size() = p_size;
diff --git a/core/templates/vector.h b/core/templates/vector.h
index f3f5ed76a7..51595a75f5 100644
--- a/core/templates/vector.h
+++ b/core/templates/vector.h
@@ -89,6 +89,7 @@ public:
_FORCE_INLINE_ void set(int p_index, const T &p_elem) { _cowdata.set(p_index, p_elem); }
_FORCE_INLINE_ int size() const { return _cowdata.size(); }
Error resize(int p_size) { return _cowdata.resize(p_size); }
+ Error resize_zeroed(int p_size) { return _cowdata.template resize<true>(p_size); }
_FORCE_INLINE_ const T &operator[](int p_index) const { return _cowdata.get(p_index); }
Error insert(int p_pos, T p_val) { return _cowdata.insert(p_pos, p_val); }
int find(const T &p_val, int p_from = 0) const { return _cowdata.find(p_val, p_from); }
diff --git a/core/variant/variant_call.cpp b/core/variant/variant_call.cpp
index a774aac52f..d1f1b83457 100644
--- a/core/variant/variant_call.cpp
+++ b/core/variant/variant_call.cpp
@@ -2060,7 +2060,7 @@ static void _register_variant_builtin_methods() {
bind_method(PackedByteArray, remove_at, sarray("index"), varray());
bind_method(PackedByteArray, insert, sarray("at_index", "value"), varray());
bind_method(PackedByteArray, fill, sarray("value"), varray());
- bind_method(PackedByteArray, resize, sarray("new_size"), varray());
+ bind_methodv(PackedByteArray, resize, &PackedByteArray::resize_zeroed, sarray("new_size"), varray());
bind_method(PackedByteArray, has, sarray("value"), varray());
bind_method(PackedByteArray, reverse, sarray(), varray());
bind_method(PackedByteArray, slice, sarray("begin", "end"), varray(INT_MAX));
@@ -2124,7 +2124,7 @@ static void _register_variant_builtin_methods() {
bind_method(PackedInt32Array, remove_at, sarray("index"), varray());
bind_method(PackedInt32Array, insert, sarray("at_index", "value"), varray());
bind_method(PackedInt32Array, fill, sarray("value"), varray());
- bind_method(PackedInt32Array, resize, sarray("new_size"), varray());
+ bind_methodv(PackedInt32Array, resize, &PackedInt32Array::resize_zeroed, sarray("new_size"), varray());
bind_method(PackedInt32Array, has, sarray("value"), varray());
bind_method(PackedInt32Array, reverse, sarray(), varray());
bind_method(PackedInt32Array, slice, sarray("begin", "end"), varray(INT_MAX));
@@ -2147,7 +2147,7 @@ static void _register_variant_builtin_methods() {
bind_method(PackedInt64Array, remove_at, sarray("index"), varray());
bind_method(PackedInt64Array, insert, sarray("at_index", "value"), varray());
bind_method(PackedInt64Array, fill, sarray("value"), varray());
- bind_method(PackedInt64Array, resize, sarray("new_size"), varray());
+ bind_methodv(PackedInt64Array, resize, &PackedInt64Array::resize_zeroed, sarray("new_size"), varray());
bind_method(PackedInt64Array, has, sarray("value"), varray());
bind_method(PackedInt64Array, reverse, sarray(), varray());
bind_method(PackedInt64Array, slice, sarray("begin", "end"), varray(INT_MAX));
@@ -2170,7 +2170,7 @@ static void _register_variant_builtin_methods() {
bind_method(PackedFloat32Array, remove_at, sarray("index"), varray());
bind_method(PackedFloat32Array, insert, sarray("at_index", "value"), varray());
bind_method(PackedFloat32Array, fill, sarray("value"), varray());
- bind_method(PackedFloat32Array, resize, sarray("new_size"), varray());
+ bind_methodv(PackedFloat32Array, resize, &PackedFloat32Array::resize_zeroed, sarray("new_size"), varray());
bind_method(PackedFloat32Array, has, sarray("value"), varray());
bind_method(PackedFloat32Array, reverse, sarray(), varray());
bind_method(PackedFloat32Array, slice, sarray("begin", "end"), varray(INT_MAX));
@@ -2193,7 +2193,7 @@ static void _register_variant_builtin_methods() {
bind_method(PackedFloat64Array, remove_at, sarray("index"), varray());
bind_method(PackedFloat64Array, insert, sarray("at_index", "value"), varray());
bind_method(PackedFloat64Array, fill, sarray("value"), varray());
- bind_method(PackedFloat64Array, resize, sarray("new_size"), varray());
+ bind_methodv(PackedFloat64Array, resize, &PackedFloat64Array::resize_zeroed, sarray("new_size"), varray());
bind_method(PackedFloat64Array, has, sarray("value"), varray());
bind_method(PackedFloat64Array, reverse, sarray(), varray());
bind_method(PackedFloat64Array, slice, sarray("begin", "end"), varray(INT_MAX));
diff --git a/tests/core/input/test_input_event_key.h b/tests/core/input/test_input_event_key.h
new file mode 100644
index 0000000000..5d4ca55a35
--- /dev/null
+++ b/tests/core/input/test_input_event_key.h
@@ -0,0 +1,294 @@
+/*************************************************************************/
+/* test_input_event_key.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* 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 */
+/* "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_INPUT_EVENT_KEY_H
+#define TEST_INPUT_EVENT_KEY_H
+
+#include "core/input/input_event.h"
+#include "core/os/keyboard.h"
+
+#include "tests/test_macros.h"
+
+namespace TestInputEventKey {
+
+TEST_CASE("[InputEventKey] Key correctly registers being pressed") {
+ InputEventKey key;
+ key.set_pressed(true);
+ CHECK(key.is_pressed() == true);
+
+ key.set_pressed(false);
+ CHECK(key.is_pressed() == false);
+}
+
+TEST_CASE("[InputEventKey] Key correctly stores and retrieves keycode") {
+ InputEventKey key;
+
+ key.set_keycode(Key::ENTER);
+ CHECK(key.get_keycode() == Key::ENTER);
+ CHECK(key.get_keycode() != Key::PAUSE);
+
+ key.set_physical_keycode(Key::BACKSPACE);
+ CHECK(key.get_physical_keycode() == Key::BACKSPACE);
+ CHECK(key.get_physical_keycode() != Key::PAUSE);
+}
+
+TEST_CASE("[InputEventKey] Key correctly stores and retrieves keycode with modifiers") {
+ InputEventKey key;
+
+ key.set_keycode(Key::ENTER);
+ key.set_ctrl_pressed(true);
+
+ CHECK(key.get_keycode_with_modifiers() == (Key::ENTER | KeyModifierMask::CTRL));
+ CHECK(key.get_keycode_with_modifiers() != (Key::ENTER | KeyModifierMask::SHIFT));
+ CHECK(key.get_keycode_with_modifiers() != Key::ENTER);
+
+ key.set_physical_keycode(Key::SPACE);
+ key.set_ctrl_pressed(true);
+
+ CHECK(key.get_physical_keycode_with_modifiers() == (Key::SPACE | KeyModifierMask::CTRL));
+ CHECK(key.get_physical_keycode_with_modifiers() != (Key::SPACE | KeyModifierMask::SHIFT));
+ CHECK(key.get_physical_keycode_with_modifiers() != Key::SPACE);
+}
+
+TEST_CASE("[InputEventKey] Key correctly stores and retrieves unicode") {
+ InputEventKey key;
+
+ key.set_unicode('x');
+ CHECK(key.get_unicode() == 'x');
+ CHECK(key.get_unicode() != 'y');
+}
+
+TEST_CASE("[InputEventKey] Key correctly stores and checks echo") {
+ InputEventKey key;
+
+ key.set_echo(true);
+ CHECK(key.is_echo() == true);
+
+ key.set_echo(false);
+ CHECK(key.is_echo() == false);
+}
+
+TEST_CASE("[InputEventKey] Key correctly converts itself to text") {
+ InputEventKey none_key;
+
+ // These next three tests test the functionality of getting a key that is set to None
+ // as text. These cases are a bit weird, since None has no textual representation
+ // (find_keycode_name(Key::NONE) results in a nullptr). Thus, these tests look weird
+ // with only (Physical) or a lonely modifier with (Physical) but (as far as I
+ // understand the code, that is intended behaviour.
+
+ // Key is None without a physical key.
+ none_key.set_keycode(Key::NONE);
+ CHECK(none_key.as_text() == " (Physical)");
+
+ // Key is none and has modifiers.
+ none_key.set_ctrl_pressed(true);
+ CHECK(none_key.as_text() == "Ctrl+ (Physical)");
+
+ // Key is None WITH a physical key AND modifiers.
+ none_key.set_physical_keycode(Key::ENTER);
+ CHECK(none_key.as_text() == "Ctrl+Enter (Physical)");
+
+ InputEventKey none_key2;
+
+ // Key is None without modifers with a physical key.
+ none_key2.set_keycode(Key::NONE);
+ none_key2.set_physical_keycode(Key::ENTER);
+
+ CHECK(none_key2.as_text() == "Enter (Physical)");
+
+ InputEventKey key;
+
+ // Key has keycode.
+ key.set_keycode(Key::SPACE);
+ CHECK(key.as_text() != "");
+ CHECK(key.as_text() == "Space");
+
+ // Key has keycode and modifiers.
+ key.set_ctrl_pressed(true);
+ CHECK(key.as_text() != "Space");
+ CHECK(key.as_text() == "Ctrl+Space");
+
+ // Since the keycode is set to Key::NONE upon initialization of the
+ // InputEventKey and you can only update it with another Key, the keycode
+ // cannot be empty, so the kc.is_empty() case cannot be tested.
+}
+
+TEST_CASE("[InputEventKey] Key correctly converts its state to a string representation") {
+ InputEventKey none_key;
+
+ // Set physical to true.
+ CHECK(none_key.to_string() == "InputEventKey: keycode=0 (), mods=none, physical=true, pressed=false, echo=false");
+ // Set physical key to Escape.
+ none_key.set_physical_keycode(Key::ESCAPE);
+ CHECK(none_key.to_string() == "InputEventKey: keycode=16777217 (Escape), mods=none, physical=true, pressed=false, echo=false");
+
+ InputEventKey key;
+
+ // Set physical to None, set keycode to Space.
+ key.set_keycode(Key::SPACE);
+ CHECK(key.to_string() == "InputEventKey: keycode=32 (Space), mods=none, physical=false, pressed=false, echo=false");
+
+ // Set pressed to true.
+ key.set_pressed(true);
+ CHECK(key.to_string() == "InputEventKey: keycode=32 (Space), mods=none, physical=false, pressed=true, echo=false");
+
+ // set echo to true.
+ key.set_echo(true);
+ CHECK(key.to_string() == "InputEventKey: keycode=32 (Space), mods=none, physical=false, pressed=true, echo=true");
+
+ // Press Ctrl and Alt.
+ key.set_ctrl_pressed(true);
+ key.set_alt_pressed(true);
+ CHECK(key.to_string() == "InputEventKey: keycode=32 (Space), mods=Ctrl+Alt, physical=false, pressed=true, echo=true");
+}
+
+TEST_CASE("[InputEventKey] Key is correctly converted to reference") {
+ InputEventKey base_key;
+ Ref<InputEventKey> key_ref = base_key.create_reference(Key::ENTER);
+
+ CHECK(key_ref->get_keycode() == Key::ENTER);
+}
+
+TEST_CASE("[InputEventKey] Keys are correctly matched based on action") {
+ bool pressed = false;
+ float strength, raw_strength = 0.0;
+
+ InputEventKey key;
+
+ // Nullptr.
+ CHECK_MESSAGE(key.action_match(nullptr, false, 0.0f, &pressed, &strength, &raw_strength) == false, "nullptr as key reference should result in false");
+
+ // Match on keycode.
+ key.set_keycode(Key::SPACE);
+ Ref<InputEventKey> match = key.create_reference(Key::SPACE);
+ Ref<InputEventKey> no_match = key.create_reference(Key::ENTER);
+
+ CHECK(key.action_match(match, false, 0.0f, &pressed, &strength, &raw_strength) == true);
+ CHECK(key.action_match(no_match, false, 0.0f, &pressed, &strength, &raw_strength) == false);
+
+ // Check that values are correctly transferred to the pointers.
+ CHECK(pressed == false);
+ CHECK(strength < 0.5);
+ CHECK(raw_strength < 0.5);
+
+ match->set_pressed(true);
+ key.action_match(match, false, 0.0f, &pressed, &strength, &raw_strength);
+
+ CHECK(pressed == true);
+ CHECK(strength > 0.5);
+ CHECK(raw_strength > 0.5);
+
+ // Tests when keycode is None: Then you rely on physical keycode.
+ InputEventKey none_key;
+ none_key.set_physical_keycode(Key::SPACE);
+
+ Ref<InputEventKey> match_none = none_key.create_reference(Key::NONE);
+ match_none->set_physical_keycode(Key::SPACE);
+
+ Ref<InputEventKey> no_match_none = none_key.create_reference(Key::NONE);
+ no_match_none->set_physical_keycode(Key::ENTER);
+
+ CHECK(none_key.action_match(match_none, false, 0.0f, &pressed, &strength, &raw_strength) == true);
+ CHECK(none_key.action_match(no_match_none, false, 0.0f, &pressed, &strength, &raw_strength) == false);
+
+ // Test exact match.
+ InputEventKey key2, ref_key;
+ key2.set_keycode(Key::SPACE);
+
+ Ref<InputEventKey> match2 = ref_key.create_reference(Key::SPACE);
+
+ // Now both press Ctrl and Shift.
+ key2.set_ctrl_pressed(true);
+ key2.set_shift_pressed(true);
+
+ match2->set_ctrl_pressed(true);
+ match2->set_shift_pressed(true);
+
+ // Now they should match.
+ bool exact_match = true;
+ CHECK(key2.action_match(match2, exact_match, 0.0f, &pressed, &strength, &raw_strength) == true);
+
+ // Modify matching key such that it does no longer match in terms of modifiers: Shift
+ // is no longer pressed.
+ match2->set_shift_pressed(false);
+ CHECK(match2->is_shift_pressed() == false);
+ CHECK(key2.action_match(match2, exact_match, 0.0f, &pressed, &strength, &raw_strength) == false);
+}
+
+TEST_CASE("[IsMatch] Keys are correctly matched") {
+ // Key with NONE as keycode.
+ InputEventKey key;
+ key.set_keycode(Key::NONE);
+ key.set_physical_keycode(Key::SPACE);
+
+ // Nullptr.
+ CHECK(key.is_match(nullptr, false) == false);
+
+ Ref<InputEventKey> none_ref = key.create_reference(Key::NONE);
+
+ none_ref->set_physical_keycode(Key::SPACE);
+ CHECK(key.is_match(none_ref, false) == true);
+
+ none_ref->set_physical_keycode(Key::ENTER);
+ CHECK(key.is_match(none_ref, false) == false);
+
+ none_ref->set_physical_keycode(Key::SPACE);
+
+ key.set_ctrl_pressed(true);
+ none_ref->set_ctrl_pressed(false);
+ CHECK(key.is_match(none_ref, true) == false);
+
+ none_ref->set_ctrl_pressed(true);
+ CHECK(key.is_match(none_ref, true) == true);
+
+ // Ref with actual keycode.
+ InputEventKey key2;
+ key2.set_keycode(Key::SPACE);
+
+ Ref<InputEventKey> match = key2.create_reference(Key::SPACE);
+ Ref<InputEventKey> no_match = key2.create_reference(Key::ENTER);
+
+ CHECK(key2.is_match(match, false) == true);
+ CHECK(key2.is_match(no_match, false) == false);
+
+ // Now the keycode is the same, but the modifiers differ.
+ no_match->set_keycode(Key::SPACE);
+
+ key2.set_ctrl_pressed(true);
+ match->set_ctrl_pressed(true);
+ no_match->set_shift_pressed(true);
+
+ CHECK(key2.is_match(match, true) == true);
+ CHECK(key2.is_match(no_match, true) == false);
+}
+} // namespace TestInputEventKey
+
+#endif // TEST_INPUT_EVENT_KEY_H
diff --git a/tests/core/input/test_shortcut.h b/tests/core/input/test_shortcut.h
new file mode 100644
index 0000000000..93edc685ad
--- /dev/null
+++ b/tests/core/input/test_shortcut.h
@@ -0,0 +1,235 @@
+/*************************************************************************/
+/* test_shortcut.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* 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 */
+/* "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_SHORTCUT_H
+#define TEST_SHORTCUT_H
+
+#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"
+
+#include "tests/test_macros.h"
+
+namespace TestShortcut {
+
+TEST_CASE("[Shortcut] Empty shortcut should have no valid events and text equal to None") {
+ Shortcut s;
+
+ CHECK(s.get_as_text() == "None");
+ CHECK(s.has_valid_event() == false);
+}
+
+TEST_CASE("[Shortcut] Setting and getting an event should result in the same event as the input") {
+ Ref<InputEventKey> k1;
+ Ref<InputEventKey> k2;
+ k1.instantiate();
+ k2.instantiate();
+
+ k1->set_keycode(Key::ENTER);
+ k2->set_keycode(Key::BACKSPACE);
+
+ // Cast to InputEvent so the internal code recognizes the objects.
+ Ref<InputEvent> e1 = k1;
+ Ref<InputEvent> e2 = k2;
+
+ Array input_array;
+ input_array.append(e1);
+ input_array.append(e2);
+
+ Shortcut s;
+ s.set_events(input_array);
+
+ // Get result, read it out, check whether it equals the input.
+ Array result_array = s.get_events();
+ Ref<InputEventKey> result1 = result_array.front();
+ Ref<InputEventKey> result2 = result_array.back();
+
+ CHECK(result1->get_keycode() == k1->get_keycode());
+ CHECK(result2->get_keycode() == k2->get_keycode());
+}
+
+TEST_CASE("[Shortcut] 'set_events_list' should result in the same events as the input") {
+ Ref<InputEventKey> k1;
+ Ref<InputEventKey> k2;
+ k1.instantiate();
+ k2.instantiate();
+
+ k1->set_keycode(Key::ENTER);
+ k2->set_keycode(Key::BACKSPACE);
+
+ // Cast to InputEvent so the set_events_list() method recognizes the objects.
+ Ref<InputEvent> e1 = k1;
+ Ref<InputEvent> e2 = k2;
+
+ List<Ref<InputEvent>> list;
+ list.push_back(e1);
+ list.push_back(e2);
+
+ Shortcut s;
+ s.set_events_list(&list);
+
+ // Get result, read it out, check whether it equals the input.
+ Array result_array = s.get_events();
+ Ref<InputEventKey> result1 = result_array.front();
+ Ref<InputEventKey> result2 = result_array.back();
+
+ CHECK(result1->get_keycode() == k1->get_keycode());
+ CHECK(result2->get_keycode() == k2->get_keycode());
+}
+
+TEST_CASE("[Shortcut] 'matches_event' should correctly match the same event") {
+ Ref<InputEventKey> original; // The one we compare with.
+ Ref<InputEventKey> similar_but_not_equal; // Same keycode, different event.
+ Ref<InputEventKey> different; // Different event, different keycode.
+ Ref<InputEventKey> copy; // Copy of original event.
+
+ original.instantiate();
+ similar_but_not_equal.instantiate();
+ different.instantiate();
+ copy.instantiate();
+
+ original->set_keycode(Key::ENTER);
+ similar_but_not_equal->set_keycode(Key::ENTER);
+ similar_but_not_equal->set_keycode(Key::ESCAPE);
+ copy = original;
+
+ // Only the copy is really the same, so only that one should match.
+ // The rest should not match.
+
+ Ref<InputEvent> e_original = original;
+
+ Ref<InputEvent> e_similar_but_not_equal = similar_but_not_equal;
+ Ref<InputEvent> e_different = different;
+ Ref<InputEvent> e_copy = copy;
+
+ Array a;
+ a.append(e_original);
+ Shortcut s;
+ s.set_events(a);
+
+ CHECK(s.matches_event(e_similar_but_not_equal) == false);
+ CHECK(s.matches_event(e_different) == false);
+
+ CHECK(s.matches_event(e_copy) == true);
+}
+
+TEST_CASE("[Shortcut] 'get_as_text' text representation should be correct") {
+ Ref<InputEventKey> same;
+ // k2 will not go into the shortcut but only be used to compare.
+ Ref<InputEventKey> different;
+
+ same.instantiate();
+ different.instantiate();
+
+ same->set_keycode(Key::ENTER);
+ different->set_keycode(Key::ESCAPE);
+
+ Ref<InputEvent> key_event1 = same;
+
+ Array a;
+ a.append(key_event1);
+ Shortcut s;
+ s.set_events(a);
+
+ CHECK(s.get_as_text() == same->as_text());
+ CHECK(s.get_as_text() != different->as_text());
+}
+
+TEST_CASE("[Shortcut] Event validity should be correctly checked.") {
+ Ref<InputEventKey> valid;
+ // k2 will not go into the shortcut but only be used to compare.
+ Ref<InputEventKey> invalid = nullptr;
+
+ valid.instantiate();
+ valid->set_keycode(Key::ENTER);
+
+ Ref<InputEvent> valid_event = valid;
+ Ref<InputEvent> invalid_event = invalid;
+
+ Array a;
+ a.append(invalid_event);
+ a.append(valid_event);
+
+ Shortcut s;
+ s.set_events(a);
+
+ CHECK(s.has_valid_event() == true);
+
+ Array b;
+ b.append(invalid_event);
+
+ Shortcut shortcut_with_invalid_event;
+ shortcut_with_invalid_event.set_events(b);
+
+ CHECK(shortcut_with_invalid_event.has_valid_event() == false);
+}
+
+TEST_CASE("[Shortcut] Equal arrays should be recognized as such.") {
+ Ref<InputEventKey> k1;
+ // k2 will not go into the shortcut but only be used to compare.
+ Ref<InputEventKey> k2;
+
+ k1.instantiate();
+ k2.instantiate();
+
+ k1->set_keycode(Key::ENTER);
+ k2->set_keycode(Key::ESCAPE);
+
+ Ref<InputEvent> key_event1 = k1;
+ Ref<InputEvent> key_event2 = k2;
+
+ Array same;
+ same.append(key_event1);
+
+ Array same_as_same;
+ same_as_same.append(key_event1);
+
+ Array different1;
+ different1.append(key_event2);
+
+ Array different2;
+ different2.append(key_event1);
+ different2.append(key_event2);
+
+ Array different3;
+
+ Shortcut s;
+
+ CHECK(s.is_event_array_equal(same, same_as_same) == true);
+ CHECK(s.is_event_array_equal(same, different1) == false);
+ CHECK(s.is_event_array_equal(same, different2) == false);
+ CHECK(s.is_event_array_equal(same, different3) == false);
+}
+} // namespace TestShortcut
+
+#endif // TEST_SHORTCUT_H
diff --git a/tests/core/math/test_quaternion.h b/tests/core/math/test_quaternion.h
new file mode 100644
index 0000000000..94eef6c463
--- /dev/null
+++ b/tests/core/math/test_quaternion.h
@@ -0,0 +1,389 @@
+/*************************************************************************/
+/* test_quaternion.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* 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 */
+/* "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_QUATERNION_H
+#define TEST_QUATERNION_H
+
+#include "core/math/math_defs.h"
+#include "core/math/math_funcs.h"
+#include "core/math/quaternion.h"
+#include "core/math/vector3.h"
+
+#include "tests/test_macros.h"
+
+namespace TestQuaternion {
+
+Quaternion quat_euler_yxz_deg(Vector3 angle) {
+ double yaw = Math::deg2rad(angle[1]);
+ double pitch = Math::deg2rad(angle[0]);
+ double roll = Math::deg2rad(angle[2]);
+
+ // Generate YXZ (Z-then-X-then-Y) Quaternion using single-axis Euler
+ // constructor and quaternion product, both tested separately.
+ Quaternion q_y(Vector3(0.0, yaw, 0.0));
+ Quaternion q_p(Vector3(pitch, 0.0, 0.0));
+ Quaternion q_r(Vector3(0.0, 0.0, roll));
+ // Roll-Z is followed by Pitch-X, then Yaw-Y.
+ Quaternion q_yxz = q_y * q_p * q_r;
+
+ return q_yxz;
+}
+
+TEST_CASE("[Quaternion] Default Construct") {
+ Quaternion q;
+
+ CHECK(q[0] == 0.0);
+ CHECK(q[1] == 0.0);
+ CHECK(q[2] == 0.0);
+ CHECK(q[3] == 1.0);
+}
+
+TEST_CASE("[Quaternion] Construct x,y,z,w") {
+ // Values are taken from actual use in another project & are valid (except roundoff error).
+ Quaternion q(0.2391, 0.099, 0.3696, 0.8924);
+
+ CHECK(q[0] == doctest::Approx(0.2391));
+ CHECK(q[1] == doctest::Approx(0.099));
+ CHECK(q[2] == doctest::Approx(0.3696));
+ CHECK(q[3] == doctest::Approx(0.8924));
+}
+
+TEST_CASE("[Quaternion] Construct AxisAngle 1") {
+ // Easy to visualize: 120 deg about X-axis.
+ Quaternion q(Vector3(1.0, 0.0, 0.0), Math::deg2rad(120.0));
+
+ // 0.866 isn't close enough; doctest::Approx doesn't cut much slack!
+ CHECK(q[0] == doctest::Approx(0.866025)); // Sine of half the angle.
+ CHECK(q[1] == doctest::Approx(0.0));
+ CHECK(q[2] == doctest::Approx(0.0));
+ CHECK(q[3] == doctest::Approx(0.5)); // Cosine of half the angle.
+}
+
+TEST_CASE("[Quaternion] Construct AxisAngle 2") {
+ // Easy to visualize: 30 deg about Y-axis.
+ Quaternion q(Vector3(0.0, 1.0, 0.0), Math::deg2rad(30.0));
+
+ CHECK(q[0] == doctest::Approx(0.0));
+ CHECK(q[1] == doctest::Approx(0.258819)); // Sine of half the angle.
+ CHECK(q[2] == doctest::Approx(0.0));
+ CHECK(q[3] == doctest::Approx(0.965926)); // Cosine of half the angle.
+}
+
+TEST_CASE("[Quaternion] Construct AxisAngle 3") {
+ // Easy to visualize: 60 deg about Z-axis.
+ Quaternion q(Vector3(0.0, 0.0, 1.0), Math::deg2rad(60.0));
+
+ CHECK(q[0] == doctest::Approx(0.0));
+ CHECK(q[1] == doctest::Approx(0.0));
+ CHECK(q[2] == doctest::Approx(0.5)); // Sine of half the angle.
+ CHECK(q[3] == doctest::Approx(0.866025)); // Cosine of half the angle.
+}
+
+TEST_CASE("[Quaternion] Construct AxisAngle 4") {
+ // More complex & hard to visualize, so test w/ data from online calculator.
+ Vector3 axis(1.0, 2.0, 0.5);
+ Quaternion q(axis.normalized(), Math::deg2rad(35.0));
+
+ CHECK(q[0] == doctest::Approx(0.131239));
+ CHECK(q[1] == doctest::Approx(0.262478));
+ CHECK(q[2] == doctest::Approx(0.0656194));
+ CHECK(q[3] == doctest::Approx(0.953717));
+}
+
+TEST_CASE("[Quaternion] Construct from Quaternion") {
+ Vector3 axis(1.0, 2.0, 0.5);
+ Quaternion q_src(axis.normalized(), Math::deg2rad(35.0));
+ Quaternion q(q_src);
+
+ CHECK(q[0] == doctest::Approx(0.131239));
+ CHECK(q[1] == doctest::Approx(0.262478));
+ CHECK(q[2] == doctest::Approx(0.0656194));
+ CHECK(q[3] == doctest::Approx(0.953717));
+}
+
+TEST_CASE("[Quaternion] Construct Euler SingleAxis") {
+ double yaw = Math::deg2rad(45.0);
+ double pitch = Math::deg2rad(30.0);
+ double roll = Math::deg2rad(10.0);
+
+ Vector3 euler_y(0.0, yaw, 0.0);
+ Quaternion q_y(euler_y);
+ CHECK(q_y[0] == doctest::Approx(0.0));
+ CHECK(q_y[1] == doctest::Approx(0.382684));
+ CHECK(q_y[2] == doctest::Approx(0.0));
+ CHECK(q_y[3] == doctest::Approx(0.923879));
+
+ Vector3 euler_p(pitch, 0.0, 0.0);
+ Quaternion q_p(euler_p);
+ CHECK(q_p[0] == doctest::Approx(0.258819));
+ CHECK(q_p[1] == doctest::Approx(0.0));
+ CHECK(q_p[2] == doctest::Approx(0.0));
+ CHECK(q_p[3] == doctest::Approx(0.965926));
+
+ Vector3 euler_r(0.0, 0.0, roll);
+ Quaternion q_r(euler_r);
+ CHECK(q_r[0] == doctest::Approx(0.0));
+ CHECK(q_r[1] == doctest::Approx(0.0));
+ CHECK(q_r[2] == doctest::Approx(0.0871558));
+ CHECK(q_r[3] == doctest::Approx(0.996195));
+}
+
+TEST_CASE("[Quaternion] Construct Euler YXZ dynamic axes") {
+ double yaw = Math::deg2rad(45.0);
+ double pitch = Math::deg2rad(30.0);
+ double roll = Math::deg2rad(10.0);
+
+ // Generate YXZ comparision data (Z-then-X-then-Y) using single-axis Euler
+ // constructor and quaternion product, both tested separately.
+ Vector3 euler_y(0.0, yaw, 0.0);
+ Quaternion q_y(euler_y);
+ Vector3 euler_p(pitch, 0.0, 0.0);
+ Quaternion q_p(euler_p);
+ Vector3 euler_r(0.0, 0.0, roll);
+ Quaternion q_r(euler_r);
+
+ // Roll-Z is followed by Pitch-X.
+ Quaternion check_xz = q_p * q_r;
+ // Then Yaw-Y follows both.
+ Quaternion check_yxz = q_y * check_xz;
+
+ // Test construction from YXZ Euler angles.
+ Vector3 euler_yxz(pitch, yaw, roll);
+ Quaternion q(euler_yxz);
+ CHECK(q[0] == doctest::Approx(check_yxz[0]));
+ CHECK(q[1] == doctest::Approx(check_yxz[1]));
+ CHECK(q[2] == doctest::Approx(check_yxz[2]));
+ CHECK(q[3] == doctest::Approx(check_yxz[3]));
+
+ // Sneak in a test of is_equal_approx.
+ CHECK(q.is_equal_approx(check_yxz));
+}
+
+TEST_CASE("[Quaternion] Construct Basis Euler") {
+ double yaw = Math::deg2rad(45.0);
+ double pitch = Math::deg2rad(30.0);
+ double roll = Math::deg2rad(10.0);
+ Vector3 euler_yxz(pitch, yaw, roll);
+ Quaternion q_yxz(euler_yxz);
+ Basis basis_axes(euler_yxz);
+ Quaternion q(basis_axes);
+ CHECK(q.is_equal_approx(q_yxz));
+}
+
+TEST_CASE("[Quaternion] Construct Basis Axes") {
+ // Arbitrary Euler angles.
+ Vector3 euler_yxz(Math::deg2rad(31.41), Math::deg2rad(-49.16), Math::deg2rad(12.34));
+ // Basis vectors from online calculation of rotation matrix.
+ Vector3 i_unit(0.5545787, 0.1823950, 0.8118957);
+ Vector3 j_unit(-0.5249245, 0.8337420, 0.1712555);
+ Vector3 k_unit(-0.6456754, -0.5211586, 0.5581192);
+ // Quaternion from online calculation.
+ Quaternion q_calc(0.2016913, -0.4245716, 0.206033, 0.8582598);
+ // Quaternion from local calculation.
+ Quaternion q_local = quat_euler_yxz_deg(Vector3(31.41, -49.16, 12.34));
+ // Quaternion from Euler angles constructor.
+ Quaternion q_euler(euler_yxz);
+ CHECK(q_calc.is_equal_approx(q_local));
+ CHECK(q_local.is_equal_approx(q_euler));
+
+ // Calculate Basis and construct Quaternion.
+ // When this is written, C++ Basis class does not construct from basis vectors.
+ // This is by design, but may be subject to change.
+ // Workaround by constructing Basis from Euler angles.
+ // basis_axes = Basis(i_unit, j_unit, k_unit);
+ Basis basis_axes(euler_yxz);
+ Quaternion q(basis_axes);
+
+ CHECK(basis_axes.get_column(0).is_equal_approx(i_unit));
+ CHECK(basis_axes.get_column(1).is_equal_approx(j_unit));
+ CHECK(basis_axes.get_column(2).is_equal_approx(k_unit));
+
+ CHECK(q.is_equal_approx(q_calc));
+ CHECK_FALSE(q.inverse().is_equal_approx(q_calc));
+ CHECK(q.is_equal_approx(q_local));
+ CHECK(q.is_equal_approx(q_euler));
+ CHECK(q[0] == doctest::Approx(0.2016913));
+ CHECK(q[1] == doctest::Approx(-0.4245716));
+ CHECK(q[2] == doctest::Approx(0.206033));
+ CHECK(q[3] == doctest::Approx(0.8582598));
+}
+
+TEST_CASE("[Quaternion] Product (book)") {
+ // Example from "Quaternions and Rotation Sequences" by Jack Kuipers, p. 108.
+ Quaternion p(1.0, -2.0, 1.0, 3.0);
+ Quaternion q(-1.0, 2.0, 3.0, 2.0);
+
+ Quaternion pq = p * q;
+ CHECK(pq[0] == doctest::Approx(-9.0));
+ CHECK(pq[1] == doctest::Approx(-2.0));
+ CHECK(pq[2] == doctest::Approx(11.0));
+ CHECK(pq[3] == doctest::Approx(8.0));
+}
+
+TEST_CASE("[Quaternion] Product") {
+ double yaw = Math::deg2rad(45.0);
+ double pitch = Math::deg2rad(30.0);
+ double roll = Math::deg2rad(10.0);
+
+ Vector3 euler_y(0.0, yaw, 0.0);
+ Quaternion q_y(euler_y);
+ CHECK(q_y[0] == doctest::Approx(0.0));
+ CHECK(q_y[1] == doctest::Approx(0.382684));
+ CHECK(q_y[2] == doctest::Approx(0.0));
+ CHECK(q_y[3] == doctest::Approx(0.923879));
+
+ Vector3 euler_p(pitch, 0.0, 0.0);
+ Quaternion q_p(euler_p);
+ CHECK(q_p[0] == doctest::Approx(0.258819));
+ CHECK(q_p[1] == doctest::Approx(0.0));
+ CHECK(q_p[2] == doctest::Approx(0.0));
+ CHECK(q_p[3] == doctest::Approx(0.965926));
+
+ Vector3 euler_r(0.0, 0.0, roll);
+ Quaternion q_r(euler_r);
+ CHECK(q_r[0] == doctest::Approx(0.0));
+ CHECK(q_r[1] == doctest::Approx(0.0));
+ CHECK(q_r[2] == doctest::Approx(0.0871558));
+ CHECK(q_r[3] == doctest::Approx(0.996195));
+
+ // Test ZYX dynamic-axes since test data is available online.
+ // Rotate first about X axis, then new Y axis, then new Z axis.
+ // (Godot uses YXZ Yaw-Pitch-Roll order).
+ Quaternion q_yp = q_y * q_p;
+ CHECK(q_yp[0] == doctest::Approx(0.239118));
+ CHECK(q_yp[1] == doctest::Approx(0.369644));
+ CHECK(q_yp[2] == doctest::Approx(-0.099046));
+ CHECK(q_yp[3] == doctest::Approx(0.892399));
+
+ Quaternion q_ryp = q_r * q_yp;
+ CHECK(q_ryp[0] == doctest::Approx(0.205991));
+ CHECK(q_ryp[1] == doctest::Approx(0.389078));
+ CHECK(q_ryp[2] == doctest::Approx(-0.0208912));
+ CHECK(q_ryp[3] == doctest::Approx(0.897636));
+}
+
+TEST_CASE("[Quaternion] xform unit vectors") {
+ // Easy to visualize: 120 deg about X-axis.
+ // Transform the i, j, & k unit vectors.
+ Quaternion q(Vector3(1.0, 0.0, 0.0), Math::deg2rad(120.0));
+ Vector3 i_t = q.xform(Vector3(1.0, 0.0, 0.0));
+ Vector3 j_t = q.xform(Vector3(0.0, 1.0, 0.0));
+ Vector3 k_t = q.xform(Vector3(0.0, 0.0, 1.0));
+ //
+ CHECK(i_t.is_equal_approx(Vector3(1.0, 0.0, 0.0)));
+ CHECK(j_t.is_equal_approx(Vector3(0.0, -0.5, 0.866025)));
+ CHECK(k_t.is_equal_approx(Vector3(0.0, -0.866025, -0.5)));
+ CHECK(i_t.length_squared() == doctest::Approx(1.0));
+ CHECK(j_t.length_squared() == doctest::Approx(1.0));
+ CHECK(k_t.length_squared() == doctest::Approx(1.0));
+
+ // Easy to visualize: 30 deg about Y-axis.
+ q = Quaternion(Vector3(0.0, 1.0, 0.0), Math::deg2rad(30.0));
+ i_t = q.xform(Vector3(1.0, 0.0, 0.0));
+ j_t = q.xform(Vector3(0.0, 1.0, 0.0));
+ k_t = q.xform(Vector3(0.0, 0.0, 1.0));
+ //
+ CHECK(i_t.is_equal_approx(Vector3(0.866025, 0.0, -0.5)));
+ CHECK(j_t.is_equal_approx(Vector3(0.0, 1.0, 0.0)));
+ CHECK(k_t.is_equal_approx(Vector3(0.5, 0.0, 0.866025)));
+ CHECK(i_t.length_squared() == doctest::Approx(1.0));
+ CHECK(j_t.length_squared() == doctest::Approx(1.0));
+ CHECK(k_t.length_squared() == doctest::Approx(1.0));
+
+ // Easy to visualize: 60 deg about Z-axis.
+ q = Quaternion(Vector3(0.0, 0.0, 1.0), Math::deg2rad(60.0));
+ i_t = q.xform(Vector3(1.0, 0.0, 0.0));
+ j_t = q.xform(Vector3(0.0, 1.0, 0.0));
+ k_t = q.xform(Vector3(0.0, 0.0, 1.0));
+ //
+ CHECK(i_t.is_equal_approx(Vector3(0.5, 0.866025, 0.0)));
+ CHECK(j_t.is_equal_approx(Vector3(-0.866025, 0.5, 0.0)));
+ CHECK(k_t.is_equal_approx(Vector3(0.0, 0.0, 1.0)));
+ CHECK(i_t.length_squared() == doctest::Approx(1.0));
+ CHECK(j_t.length_squared() == doctest::Approx(1.0));
+ CHECK(k_t.length_squared() == doctest::Approx(1.0));
+}
+
+TEST_CASE("[Quaternion] xform vector") {
+ // Arbitrary quaternion rotates an arbitrary vector.
+ Vector3 euler_yzx(Math::deg2rad(31.41), Math::deg2rad(-49.16), Math::deg2rad(12.34));
+ Basis basis_axes(euler_yzx);
+ Quaternion q(basis_axes);
+
+ Vector3 v_arb(3.0, 4.0, 5.0);
+ Vector3 v_rot = q.xform(v_arb);
+ Vector3 v_compare = basis_axes.xform(v_arb);
+
+ CHECK(v_rot.length_squared() == doctest::Approx(v_arb.length_squared()));
+ CHECK(v_rot.is_equal_approx(v_compare));
+}
+
+// Test vector xform for a single combination of Quaternion and Vector.
+void test_quat_vec_rotate(Vector3 euler_yzx, Vector3 v_in) {
+ Basis basis_axes(euler_yzx);
+ Quaternion q(basis_axes);
+
+ Vector3 v_rot = q.xform(v_in);
+ Vector3 v_compare = basis_axes.xform(v_in);
+
+ CHECK(v_rot.length_squared() == doctest::Approx(v_in.length_squared()));
+ CHECK(v_rot.is_equal_approx(v_compare));
+}
+
+TEST_CASE("[Stress][Quaternion] Many vector xforms") {
+ // Many arbitrary quaternions rotate many arbitrary vectors.
+ // For each trial, check that rotation by Quaternion yields same result as
+ // rotation by Basis.
+ const int STEPS = 100; // Number of test steps in each dimension
+ const double delta = 2.0 * Math_PI / STEPS; // Angle increment per step
+ const double delta_vec = 20.0 / STEPS; // Vector increment per step
+ Vector3 vec_arb(1.0, 1.0, 1.0);
+ double x_angle = -Math_PI;
+ double y_angle = -Math_PI;
+ double z_angle = -Math_PI;
+ for (double i = 0; i < STEPS; ++i) {
+ vec_arb[0] = -10.0 + i * delta_vec;
+ x_angle = i * delta - Math_PI;
+ for (double j = 0; j < STEPS; ++j) {
+ vec_arb[1] = -10.0 + j * delta_vec;
+ y_angle = j * delta - Math_PI;
+ for (double k = 0; k < STEPS; ++k) {
+ vec_arb[2] = -10.0 + k * delta_vec;
+ z_angle = k * delta - Math_PI;
+ Vector3 euler_yzx(x_angle, y_angle, z_angle);
+ test_quat_vec_rotate(euler_yzx, vec_arb);
+ }
+ }
+ }
+}
+
+} // namespace TestQuaternion
+
+#endif // TEST_QUATERNION_H
diff --git a/tests/core/templates/test_rid.h b/tests/core/templates/test_rid.h
new file mode 100644
index 0000000000..8d4dd0703b
--- /dev/null
+++ b/tests/core/templates/test_rid.h
@@ -0,0 +1,101 @@
+/*************************************************************************/
+/* test_rid.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* 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 */
+/* "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_RID_H
+#define TEST_RID_H
+
+#include "core/templates/rid.h"
+
+#include "tests/test_macros.h"
+
+namespace TestRID {
+TEST_CASE("[RID] Default Constructor") {
+ RID rid;
+
+ CHECK(rid.get_id() == 0);
+}
+
+TEST_CASE("[RID] Factory method") {
+ RID rid = RID::from_uint64(1);
+
+ CHECK(rid.get_id() == 1);
+}
+
+TEST_CASE("[RID] Operators") {
+ RID rid = RID::from_uint64(1);
+
+ RID rid_zero = RID::from_uint64(0);
+ RID rid_one = RID::from_uint64(1);
+ RID rid_two = RID::from_uint64(2);
+
+ CHECK_FALSE(rid == rid_zero);
+ CHECK(rid == rid_one);
+ CHECK_FALSE(rid == rid_two);
+
+ CHECK_FALSE(rid < rid_zero);
+ CHECK_FALSE(rid < rid_one);
+ CHECK(rid < rid_two);
+
+ CHECK_FALSE(rid <= rid_zero);
+ CHECK(rid <= rid_one);
+ CHECK(rid <= rid_two);
+
+ CHECK(rid > rid_zero);
+ CHECK_FALSE(rid > rid_one);
+ CHECK_FALSE(rid > rid_two);
+
+ CHECK(rid >= rid_zero);
+ CHECK(rid >= rid_one);
+ CHECK_FALSE(rid >= rid_two);
+
+ CHECK(rid != rid_zero);
+ CHECK_FALSE(rid != rid_one);
+ CHECK(rid != rid_two);
+}
+
+TEST_CASE("[RID] 'is_valid' & 'is_null'") {
+ RID rid_zero = RID::from_uint64(0);
+ RID rid_one = RID::from_uint64(1);
+
+ CHECK_FALSE(rid_zero.is_valid());
+ CHECK(rid_zero.is_null());
+
+ CHECK(rid_one.is_valid());
+ CHECK_FALSE(rid_one.is_null());
+}
+
+TEST_CASE("[RID] 'get_local_index'") {
+ CHECK(RID::from_uint64(1).get_local_index() == 1);
+ CHECK(RID::from_uint64(4'294'967'295).get_local_index() == 4'294'967'295);
+ CHECK(RID::from_uint64(4'294'967'297).get_local_index() == 1);
+}
+} // namespace TestRID
+
+#endif // TEST_RID_H
diff --git a/tests/scene/test_audio_stream_wav.h b/tests/scene/test_audio_stream_wav.h
new file mode 100644
index 0000000000..92c524525c
--- /dev/null
+++ b/tests/scene/test_audio_stream_wav.h
@@ -0,0 +1,243 @@
+/*************************************************************************/
+/* test_audio_stream_wav.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* 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 */
+/* "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_AUDIO_STREAM_WAV_H
+#define TEST_AUDIO_STREAM_WAV_H
+
+#include "core/math/math_defs.h"
+#include "core/math/math_funcs.h"
+#include "scene/resources/audio_stream_wav.h"
+
+#include "tests/test_macros.h"
+
+#ifdef TOOLS_ENABLED
+#include "core/io/resource_loader.h"
+#include "editor/import/resource_importer_wav.h"
+#endif
+
+namespace TestAudioStreamWAV {
+
+// Default wav rate for test cases.
+constexpr float WAV_RATE = 44100;
+/* Default wav count for test cases. 1 second of audio is used so that the file can be listened
+to manually if needed. */
+constexpr int WAV_COUNT = WAV_RATE;
+
+float gen_wav(float frequency, float wav_rate, int wav_number) {
+ // formula for generating a sin wave with given frequency.
+ return Math::sin((Math_TAU * frequency / wav_rate) * wav_number);
+}
+
+/* Generates a 440Hz sin wave in channel 0 (mono channel or left stereo channel)
+ * and a 261.63Hz wave in channel 1 (right stereo channel).
+ * These waves correspond to the music notes A4 and C4 respectively.
+ */
+Vector<uint8_t> gen_pcm8_test(float wav_rate, int wav_count, bool stereo) {
+ Vector<uint8_t> buffer;
+ buffer.resize(stereo ? wav_count * 2 : wav_count);
+
+ uint8_t *write_ptr = buffer.ptrw();
+ for (int i = 0; i < buffer.size(); i++) {
+ float wav;
+ if (stereo) {
+ if (i % 2 == 0) {
+ wav = gen_wav(440, wav_rate, i / 2);
+ } else {
+ wav = gen_wav(261.63, wav_rate, i / 2);
+ }
+ } else {
+ wav = gen_wav(440, wav_rate, i);
+ }
+
+ // Map sin wave to full range of 8-bit values.
+ uint8_t wav_8bit = Math::fast_ftoi(((wav + 1) / 2) * UINT8_MAX);
+ // Unlike the .wav format, AudioStreamWAV expects signed 8-bit wavs.
+ uint8_t wav_8bit_signed = wav_8bit - (INT8_MAX + 1);
+ write_ptr[i] = wav_8bit_signed;
+ }
+
+ return buffer;
+}
+
+// Same as gen_pcm8_test but with 16-bit wavs.
+Vector<uint8_t> gen_pcm16_test(float wav_rate, int wav_count, bool stereo) {
+ Vector<uint8_t> buffer;
+ buffer.resize(stereo ? wav_count * 4 : wav_count * 2);
+
+ uint8_t *write_ptr = buffer.ptrw();
+ for (int i = 0; i < buffer.size() / 2; i++) {
+ float wav;
+ if (stereo) {
+ if (i % 2 == 0) {
+ wav = gen_wav(440, wav_rate, i / 2);
+ } else {
+ wav = gen_wav(261.63, wav_rate, i / 2);
+ }
+ } else {
+ wav = gen_wav(440, wav_rate, i);
+ }
+
+ // Map sin wave to full range of 16-bit values.
+ uint16_t wav_16bit = Math::fast_ftoi(((wav + 1) / 2) * UINT16_MAX);
+ // The .wav format expects wavs larger than 8 bits to be signed.
+ uint16_t wav_16bit_signed = wav_16bit - (INT16_MAX + 1);
+ encode_uint16(wav_16bit_signed, write_ptr + (i * 2));
+ }
+
+ return buffer;
+}
+
+void run_test(String file_name, AudioStreamWAV::Format data_format, bool stereo, float wav_rate, float wav_count) {
+ String save_path = OS::get_singleton()->get_cache_path().plus_file(file_name);
+
+ Vector<uint8_t> test_data;
+ if (data_format == AudioStreamWAV::FORMAT_8_BITS) {
+ test_data = gen_pcm8_test(wav_rate, wav_count, stereo);
+ } else {
+ test_data = gen_pcm16_test(wav_rate, wav_count, stereo);
+ }
+
+ Ref<AudioStreamWAV> stream = memnew(AudioStreamWAV);
+ stream->set_mix_rate(wav_rate);
+ CHECK(stream->get_mix_rate() == wav_rate);
+
+ stream->set_format(data_format);
+ CHECK(stream->get_format() == data_format);
+
+ stream->set_stereo(stereo);
+ CHECK(stream->is_stereo() == stereo);
+
+ stream->set_data(test_data);
+ CHECK(stream->get_data() == test_data);
+
+ SUBCASE("Stream length is computed properly") {
+ CHECK(Math::is_equal_approx(stream->get_length(), wav_count / wav_rate));
+ }
+
+ SUBCASE("Stream can be saved as .wav") {
+ REQUIRE(stream->save_to_wav(save_path) == OK);
+
+ Error error;
+ Ref<FileAccess> wav_file = FileAccess::open(save_path, FileAccess::READ, &error);
+ REQUIRE(error == OK);
+
+#if TOOLS_ENABLED
+ // The WAV importer can be used if enabled to check that the saved file is valid.
+ Ref<ResourceImporterWAV> wav_importer = memnew(ResourceImporterWAV);
+
+ List<ResourceImporter::ImportOption> options_list;
+ wav_importer->get_import_options("", &options_list);
+
+ HashMap<StringName, Variant> options_map;
+ for (const ResourceImporter::ImportOption &E : options_list) {
+ options_map[E.option.name] = E.default_value;
+ }
+
+ REQUIRE(wav_importer->import(save_path, save_path, options_map, nullptr) == OK);
+
+ String load_path = save_path + "." + wav_importer->get_save_extension();
+ Ref<AudioStreamWAV> loaded_stream = ResourceLoader::load(load_path, "AudioStreamWAV", ResourceFormatImporter::CACHE_MODE_IGNORE, &error);
+ REQUIRE(error == OK);
+
+ CHECK(loaded_stream->get_format() == stream->get_format());
+ CHECK(loaded_stream->get_loop_mode() == stream->get_loop_mode());
+ CHECK(loaded_stream->get_loop_begin() == stream->get_loop_begin());
+ CHECK(loaded_stream->get_loop_end() == stream->get_loop_end());
+ CHECK(loaded_stream->get_mix_rate() == stream->get_mix_rate());
+ CHECK(loaded_stream->is_stereo() == stream->is_stereo());
+ CHECK(loaded_stream->get_length() == stream->get_length());
+ CHECK(loaded_stream->is_monophonic() == stream->is_monophonic());
+ CHECK(loaded_stream->get_data() == stream->get_data());
+#endif
+ }
+}
+
+TEST_CASE("[AudioStreamWAV] Mono PCM8 format") {
+ run_test("test_pcm8_mono.wav", AudioStreamWAV::FORMAT_8_BITS, false, WAV_RATE, WAV_COUNT);
+}
+
+TEST_CASE("[AudioStreamWAV] Mono PCM16 format") {
+ run_test("test_pcm16_mono.wav", AudioStreamWAV::FORMAT_16_BITS, false, WAV_RATE, WAV_COUNT);
+}
+
+TEST_CASE("[AudioStreamWAV] Stereo PCM8 format") {
+ run_test("test_pcm8_stereo.wav", AudioStreamWAV::FORMAT_8_BITS, true, WAV_RATE, WAV_COUNT);
+}
+
+TEST_CASE("[AudioStreamWAV] Stereo PCM16 format") {
+ run_test("test_pcm16_stereo.wav", AudioStreamWAV::FORMAT_16_BITS, true, WAV_RATE, WAV_COUNT);
+}
+
+TEST_CASE("[AudioStreamWAV] Alternate mix rate") {
+ run_test("test_pcm16_stereo_38000Hz.wav", AudioStreamWAV::FORMAT_16_BITS, true, 38000, 38000);
+}
+
+TEST_CASE("[AudioStreamWAV] save_to_wav() adds '.wav' file extension automatically") {
+ String save_path = OS::get_singleton()->get_cache_path().plus_file("test_wav_extension");
+ Vector<uint8_t> test_data = gen_pcm8_test(WAV_RATE, WAV_COUNT, false);
+ Ref<AudioStreamWAV> stream = memnew(AudioStreamWAV);
+ stream->set_data(test_data);
+
+ REQUIRE(stream->save_to_wav(save_path) == OK);
+ Error error;
+ Ref<FileAccess> wav_file = FileAccess::open(save_path + ".wav", FileAccess::READ, &error);
+ CHECK(error == OK);
+}
+
+TEST_CASE("[AudioStreamWAV] Default values") {
+ Ref<AudioStreamWAV> stream = memnew(AudioStreamWAV);
+ CHECK(stream->get_format() == AudioStreamWAV::FORMAT_8_BITS);
+ CHECK(stream->get_loop_mode() == AudioStreamWAV::LOOP_DISABLED);
+ CHECK(stream->get_loop_begin() == 0);
+ CHECK(stream->get_loop_end() == 0);
+ CHECK(stream->get_mix_rate() == 44100);
+ CHECK(stream->is_stereo() == false);
+ CHECK(stream->get_length() == 0);
+ CHECK(stream->is_monophonic() == false);
+ CHECK(stream->get_data() == Vector<uint8_t>{});
+ CHECK(stream->get_stream_name() == "");
+}
+
+TEST_CASE("[AudioStreamWAV] Save empty file") {
+ run_test("test_empty.wav", AudioStreamWAV::FORMAT_8_BITS, false, WAV_RATE, 0);
+}
+
+TEST_CASE("[AudioStreamWAV] Saving IMA ADPCM is not supported") {
+ String save_path = OS::get_singleton()->get_cache_path().plus_file("test_adpcm.wav");
+ Ref<AudioStreamWAV> stream = memnew(AudioStreamWAV);
+ stream->set_format(AudioStreamWAV::FORMAT_IMA_ADPCM);
+ ERR_PRINT_OFF;
+ CHECK(stream->save_to_wav(save_path) == ERR_UNAVAILABLE);
+ ERR_PRINT_ON;
+}
+
+} // namespace TestAudioStreamWAV
+
+#endif // TEST_AUDIO_STREAM_WAV_H
diff --git a/tests/test_main.cpp b/tests/test_main.cpp
index d533ce4f63..e8502f6b46 100644
--- a/tests/test_main.cpp
+++ b/tests/test_main.cpp
@@ -30,6 +30,8 @@
#include "test_main.h"
+#include "tests/core/input/test_input_event_key.h"
+#include "tests/core/input/test_shortcut.h"
#include "tests/core/io/test_config_file.h"
#include "tests/core/io/test_file_access.h"
#include "tests/core/io/test_image.h"
@@ -46,6 +48,7 @@
#include "tests/core/math/test_geometry_2d.h"
#include "tests/core/math/test_geometry_3d.h"
#include "tests/core/math/test_plane.h"
+#include "tests/core/math/test_quaternion.h"
#include "tests/core/math/test_random_number_generator.h"
#include "tests/core/math/test_rect2.h"
#include "tests/core/math/test_rect2i.h"
@@ -69,6 +72,7 @@
#include "tests/core/templates/test_local_vector.h"
#include "tests/core/templates/test_lru.h"
#include "tests/core/templates/test_paged_array.h"
+#include "tests/core/templates/test_rid.h"
#include "tests/core/templates/test_vector.h"
#include "tests/core/test_crypto.h"
#include "tests/core/test_hashing_context.h"
@@ -78,6 +82,7 @@
#include "tests/core/variant/test_dictionary.h"
#include "tests/core/variant/test_variant.h"
#include "tests/scene/test_animation.h"
+#include "tests/scene/test_audio_stream_wav.h"
#include "tests/scene/test_code_edit.h"
#include "tests/scene/test_curve.h"
#include "tests/scene/test_gradient.h"
@@ -215,6 +220,15 @@ struct GodotTestCaseListener : public doctest::IReporter {
SceneTree::get_singleton()->initialize();
return;
}
+
+ if (name.find("Audio") != -1) {
+ // The last driver index should always be the dummy driver.
+ int dummy_idx = AudioDriverManager::get_driver_count() - 1;
+ AudioDriverManager::initialize(dummy_idx);
+ AudioServer *audio_server = memnew(AudioServer);
+ audio_server->init();
+ return;
+ }
}
void test_case_end(const doctest::CurrentTestCaseStats &) override {
@@ -277,6 +291,11 @@ struct GodotTestCaseListener : public doctest::IReporter {
MessageQueue::get_singleton()->flush();
memdelete(MessageQueue::get_singleton());
}
+
+ if (AudioServer::get_singleton()) {
+ AudioServer::get_singleton()->finish();
+ memdelete(AudioServer::get_singleton());
+ }
}
void test_run_start() override {