summaryrefslogtreecommitdiff
path: root/tests/core
diff options
context:
space:
mode:
Diffstat (limited to 'tests/core')
-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/io/test_file_access.h23
-rw-r--r--tests/core/io/test_image.h1
-rw-r--r--tests/core/io/test_marshalls.h2
-rw-r--r--tests/core/io/test_resource.h10
-rw-r--r--tests/core/math/test_aabb.h20
-rw-r--r--tests/core/math/test_basis.h2
-rw-r--r--tests/core/math/test_quaternion.h389
-rw-r--r--tests/core/math/test_transform_2d.h88
-rw-r--r--tests/core/math/test_transform_3d.h89
-rw-r--r--tests/core/math/test_vector4.h315
-rw-r--r--tests/core/math/test_vector4i.h148
-rw-r--r--tests/core/object/test_class_db.h86
-rw-r--r--tests/core/object/test_method_bind.h1
-rw-r--r--tests/core/object/test_object.h4
-rw-r--r--tests/core/os/test_os.h158
-rw-r--r--tests/core/string/test_string.h18
-rw-r--r--tests/core/templates/test_hash_set.h4
-rw-r--r--tests/core/templates/test_rid.h101
-rw-r--r--tests/core/templates/test_vector.h4
-rw-r--r--tests/core/threads/test_worker_thread_pool.h158
-rw-r--r--tests/core/variant/test_variant.h62
23 files changed, 2141 insertions, 71 deletions
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/io/test_file_access.h b/tests/core/io/test_file_access.h
index f0e1cceacf..aab62955cb 100644
--- a/tests/core/io/test_file_access.h
+++ b/tests/core/io/test_file_access.h
@@ -78,6 +78,29 @@ TEST_CASE("[FileAccess] CSV read") {
CHECK(row5[1] == "tab separated");
CHECK(row5[2] == "lines, good?");
}
+
+TEST_CASE("[FileAccess] Get as UTF-8 String") {
+ Ref<FileAccess> f_lf = FileAccess::open(TestUtils::get_data_path("line_endings_lf.test.txt"), FileAccess::READ);
+ String s_lf = f_lf->get_as_utf8_string();
+ f_lf->seek(0);
+ String s_lf_nocr = f_lf->get_as_utf8_string(true);
+ CHECK(s_lf == "Hello darkness\nMy old friend\nI've come to talk\nWith you again\n");
+ CHECK(s_lf_nocr == "Hello darkness\nMy old friend\nI've come to talk\nWith you again\n");
+
+ Ref<FileAccess> f_crlf = FileAccess::open(TestUtils::get_data_path("line_endings_crlf.test.txt"), FileAccess::READ);
+ String s_crlf = f_crlf->get_as_utf8_string();
+ f_crlf->seek(0);
+ String s_crlf_nocr = f_crlf->get_as_utf8_string(true);
+ CHECK(s_crlf == "Hello darkness\r\nMy old friend\r\nI've come to talk\r\nWith you again\r\n");
+ CHECK(s_crlf_nocr == "Hello darkness\nMy old friend\nI've come to talk\nWith you again\n");
+
+ Ref<FileAccess> f_cr = FileAccess::open(TestUtils::get_data_path("line_endings_cr.test.txt"), FileAccess::READ);
+ String s_cr = f_cr->get_as_utf8_string();
+ f_cr->seek(0);
+ String s_cr_nocr = f_cr->get_as_utf8_string(true);
+ CHECK(s_cr == "Hello darkness\rMy old friend\rI've come to talk\rWith you again\r");
+ CHECK(s_cr_nocr == "Hello darknessMy old friendI've come to talkWith you again");
+}
} // namespace TestFileAccess
#endif // TEST_FILE_ACCESS_H
diff --git a/tests/core/io/test_image.h b/tests/core/io/test_image.h
index e6e23912d3..36e6b83bfd 100644
--- a/tests/core/io/test_image.h
+++ b/tests/core/io/test_image.h
@@ -300,4 +300,5 @@ TEST_CASE("[Image] Modifying pixels of an image") {
"flip_y() should not leave old pixels behind.");
}
} // namespace TestImage
+
#endif // TEST_IMAGE_H
diff --git a/tests/core/io/test_marshalls.h b/tests/core/io/test_marshalls.h
index 546a2e9358..7490df2b2c 100644
--- a/tests/core/io/test_marshalls.h
+++ b/tests/core/io/test_marshalls.h
@@ -254,11 +254,13 @@ TEST_CASE("[Marshalls] Invalid data Variant decoding") {
uint8_t some_buffer[1] = { 0x00 };
uint8_t out_of_range_type_buffer[4] = { 0xff }; // Greater than Variant::VARIANT_MAX
+ ERR_PRINT_OFF;
CHECK(decode_variant(variant, some_buffer, /* less than 4 */ 1, &r_len) == ERR_INVALID_DATA);
CHECK(r_len == 0);
CHECK(decode_variant(variant, out_of_range_type_buffer, 4, &r_len) == ERR_INVALID_DATA);
CHECK(r_len == 0);
+ ERR_PRINT_ON;
}
TEST_CASE("[Marshalls] NIL Variant decoding") {
diff --git a/tests/core/io/test_resource.h b/tests/core/io/test_resource.h
index 84d651b63f..c880ca7d2a 100644
--- a/tests/core/io/test_resource.h
+++ b/tests/core/io/test_resource.h
@@ -28,8 +28,8 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
-#ifndef TEST_RESOURCE
-#define TEST_RESOURCE
+#ifndef TEST_RESOURCE_H
+#define TEST_RESOURCE_H
#include "core/io/resource.h"
#include "core/io/resource_loader.h"
@@ -76,8 +76,8 @@ TEST_CASE("[Resource] Saving and loading") {
resource->set_meta("other_resource", child_resource);
const String save_path_binary = OS::get_singleton()->get_cache_path().plus_file("resource.res");
const String save_path_text = OS::get_singleton()->get_cache_path().plus_file("resource.tres");
- ResourceSaver::save(save_path_binary, resource);
- ResourceSaver::save(save_path_text, resource);
+ ResourceSaver::save(resource, save_path_binary);
+ ResourceSaver::save(resource, save_path_text);
const Ref<Resource> &loaded_resource_binary = ResourceLoader::load(save_path_binary);
CHECK_MESSAGE(
@@ -111,4 +111,4 @@ TEST_CASE("[Resource] Saving and loading") {
}
} // namespace TestResource
-#endif // TEST_RESOURCE
+#endif // TEST_RESOURCE_H
diff --git a/tests/core/math/test_aabb.h b/tests/core/math/test_aabb.h
index 526972a82f..447420fc12 100644
--- a/tests/core/math/test_aabb.h
+++ b/tests/core/math/test_aabb.h
@@ -299,34 +299,28 @@ TEST_CASE("[AABB] Get longest/shortest axis") {
"get_shortest_axis_size() should return the expected value.");
}
-#ifndef _MSC_VER
-#warning Support tests need to be re-done
-#endif
-
-/* Support function was actually broken. As it was fixed, the tests now fail. Tests need to be re-done.
-
TEST_CASE("[AABB] Get support") {
const AABB aabb = AABB(Vector3(-1.5, 2, -2.5), Vector3(4, 5, 6));
CHECK_MESSAGE(
- aabb.get_support(Vector3(1, 0, 0)).is_equal_approx(Vector3(-1.5, 7, 3.5)),
+ aabb.get_support(Vector3(1, 0, 0)).is_equal_approx(Vector3(2.5, 2, -2.5)),
"get_support() should return the expected value.");
CHECK_MESSAGE(
- aabb.get_support(Vector3(0.5, 1, 0)).is_equal_approx(Vector3(-1.5, 2, 3.5)),
+ aabb.get_support(Vector3(0.5, 1, 0)).is_equal_approx(Vector3(2.5, 7, -2.5)),
"get_support() should return the expected value.");
CHECK_MESSAGE(
- aabb.get_support(Vector3(0.5, 1, -400)).is_equal_approx(Vector3(-1.5, 2, 3.5)),
+ aabb.get_support(Vector3(0.5, 1, -400)).is_equal_approx(Vector3(2.5, 7, -2.5)),
"get_support() should return the expected value.");
CHECK_MESSAGE(
- aabb.get_support(Vector3(0, -1, 0)).is_equal_approx(Vector3(2.5, 7, 3.5)),
+ aabb.get_support(Vector3(0, -1, 0)).is_equal_approx(Vector3(-1.5, 2, -2.5)),
"get_support() should return the expected value.");
CHECK_MESSAGE(
- aabb.get_support(Vector3(0, -0.1, 0)).is_equal_approx(Vector3(2.5, 7, 3.5)),
+ aabb.get_support(Vector3(0, -0.1, 0)).is_equal_approx(Vector3(-1.5, 2, -2.5)),
"get_support() should return the expected value.");
CHECK_MESSAGE(
- aabb.get_support(Vector3()).is_equal_approx(Vector3(2.5, 7, 3.5)),
+ aabb.get_support(Vector3()).is_equal_approx(Vector3(-1.5, 2, -2.5)),
"get_support() should return the expected value with a null vector.");
}
-*/
+
TEST_CASE("[AABB] Grow") {
const AABB aabb = AABB(Vector3(-1.5, 2, -2.5), Vector3(4, 5, 6));
CHECK_MESSAGE(
diff --git a/tests/core/math/test_basis.h b/tests/core/math/test_basis.h
index ec58d95eed..ae8ca4acde 100644
--- a/tests/core/math/test_basis.h
+++ b/tests/core/math/test_basis.h
@@ -283,4 +283,4 @@ TEST_CASE("[Stress][Basis] Euler conversions") {
}
} // namespace TestBasis
-#endif
+#endif // TEST_BASIS_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/math/test_transform_2d.h b/tests/core/math/test_transform_2d.h
new file mode 100644
index 0000000000..697bf63fc5
--- /dev/null
+++ b/tests/core/math/test_transform_2d.h
@@ -0,0 +1,88 @@
+/*************************************************************************/
+/* test_transform_2d.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_TRANSFORM_2D_H
+#define TEST_TRANSFORM_2D_H
+
+#include "core/math/transform_2d.h"
+
+#include "tests/test_macros.h"
+
+namespace TestTransform2D {
+
+Transform2D create_dummy_transform() {
+ return Transform2D(Vector2(1, 2), Vector2(3, 4), Vector2(5, 6));
+}
+
+Transform2D identity() {
+ return Transform2D();
+}
+
+TEST_CASE("[Transform2D] translation") {
+ Vector2 offset = Vector2(1, 2);
+
+ // Both versions should give the same result applied to identity.
+ CHECK(identity().translated(offset) == identity().translated_local(offset));
+
+ // Check both versions against left and right multiplications.
+ Transform2D orig = create_dummy_transform();
+ Transform2D T = identity().translated(offset);
+ CHECK(orig.translated(offset) == T * orig);
+ CHECK(orig.translated_local(offset) == orig * T);
+}
+
+TEST_CASE("[Transform2D] scaling") {
+ Vector2 scaling = Vector2(1, 2);
+
+ // Both versions should give the same result applied to identity.
+ CHECK(identity().scaled(scaling) == identity().scaled_local(scaling));
+
+ // Check both versions against left and right multiplications.
+ Transform2D orig = create_dummy_transform();
+ Transform2D S = identity().scaled(scaling);
+ CHECK(orig.scaled(scaling) == S * orig);
+ CHECK(orig.scaled_local(scaling) == orig * S);
+}
+
+TEST_CASE("[Transform2D] rotation") {
+ real_t phi = 1.0;
+
+ // Both versions should give the same result applied to identity.
+ CHECK(identity().rotated(phi) == identity().rotated_local(phi));
+
+ // Check both versions against left and right multiplications.
+ Transform2D orig = create_dummy_transform();
+ Transform2D R = identity().rotated(phi);
+ CHECK(orig.rotated(phi) == R * orig);
+ CHECK(orig.rotated_local(phi) == orig * R);
+}
+} // namespace TestTransform2D
+
+#endif // TEST_TRANSFORM_2D_H
diff --git a/tests/core/math/test_transform_3d.h b/tests/core/math/test_transform_3d.h
new file mode 100644
index 0000000000..da166b43f7
--- /dev/null
+++ b/tests/core/math/test_transform_3d.h
@@ -0,0 +1,89 @@
+/*************************************************************************/
+/* test_transform_3d.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_TRANSFORM_3D_H
+#define TEST_TRANSFORM_3D_H
+
+#include "core/math/transform_3d.h"
+
+#include "tests/test_macros.h"
+
+namespace TestTransform3D {
+
+Transform3D create_dummy_transform() {
+ return Transform3D(Basis(Vector3(1, 2, 3), Vector3(4, 5, 6), Vector3(7, 8, 9)), Vector3(10, 11, 12));
+}
+
+Transform3D identity() {
+ return Transform3D();
+}
+
+TEST_CASE("[Transform3D] translation") {
+ Vector3 offset = Vector3(1, 2, 3);
+
+ // Both versions should give the same result applied to identity.
+ CHECK(identity().translated(offset) == identity().translated_local(offset));
+
+ // Check both versions against left and right multiplications.
+ Transform3D orig = create_dummy_transform();
+ Transform3D T = identity().translated(offset);
+ CHECK(orig.translated(offset) == T * orig);
+ CHECK(orig.translated_local(offset) == orig * T);
+}
+
+TEST_CASE("[Transform3D] scaling") {
+ Vector3 scaling = Vector3(1, 2, 3);
+
+ // Both versions should give the same result applied to identity.
+ CHECK(identity().scaled(scaling) == identity().scaled_local(scaling));
+
+ // Check both versions against left and right multiplications.
+ Transform3D orig = create_dummy_transform();
+ Transform3D S = identity().scaled(scaling);
+ CHECK(orig.scaled(scaling) == S * orig);
+ CHECK(orig.scaled_local(scaling) == orig * S);
+}
+
+TEST_CASE("[Transform3D] rotation") {
+ Vector3 axis = Vector3(1, 2, 3).normalized();
+ real_t phi = 1.0;
+
+ // Both versions should give the same result applied to identity.
+ CHECK(identity().rotated(axis, phi) == identity().rotated_local(axis, phi));
+
+ // Check both versions against left and right multiplications.
+ Transform3D orig = create_dummy_transform();
+ Transform3D R = identity().rotated(axis, phi);
+ CHECK(orig.rotated(axis, phi) == R * orig);
+ CHECK(orig.rotated_local(axis, phi) == orig * R);
+}
+} // namespace TestTransform3D
+
+#endif // TEST_TRANSFORM_3D_H
diff --git a/tests/core/math/test_vector4.h b/tests/core/math/test_vector4.h
new file mode 100644
index 0000000000..ccf991401b
--- /dev/null
+++ b/tests/core/math/test_vector4.h
@@ -0,0 +1,315 @@
+/*************************************************************************/
+/* test_vector4.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_VECTOR4_H
+#define TEST_VECTOR4_H
+
+#include "core/math/vector4.h"
+#include "tests/test_macros.h"
+
+#define Math_SQRT3 1.7320508075688772935274463415059
+
+namespace TestVector4 {
+
+TEST_CASE("[Vector4] Axis methods") {
+ Vector4 vector = Vector4(1.2, 3.4, 5.6, -0.9);
+ CHECK_MESSAGE(
+ vector.max_axis_index() == Vector4::Axis::AXIS_Z,
+ "Vector4 max_axis_index should work as expected.");
+ CHECK_MESSAGE(
+ vector.min_axis_index() == Vector4::Axis::AXIS_W,
+ "Vector4 min_axis_index should work as expected.");
+ CHECK_MESSAGE(
+ vector.get_axis(vector.max_axis_index()) == (real_t)5.6,
+ "Vector4 get_axis should work as expected.");
+ CHECK_MESSAGE(
+ vector[vector.min_axis_index()] == (real_t)-0.9,
+ "Vector4 array operator should work as expected.");
+
+ vector.set_axis(Vector4::Axis::AXIS_Y, 4.7);
+ CHECK_MESSAGE(
+ vector.get_axis(Vector4::Axis::AXIS_Y) == (real_t)4.7,
+ "Vector4 set_axis should work as expected.");
+ vector[Vector4::Axis::AXIS_Y] = 3.7;
+ CHECK_MESSAGE(
+ vector[Vector4::Axis::AXIS_Y] == (real_t)3.7,
+ "Vector4 array operator setter should work as expected.");
+}
+
+TEST_CASE("[Vector4] Interpolation methods") {
+ const Vector4 vector1 = Vector4(1, 2, 3, 4);
+ const Vector4 vector2 = Vector4(4, 5, 6, 7);
+ CHECK_MESSAGE(
+ vector1.lerp(vector2, 0.5) == Vector4(2.5, 3.5, 4.5, 5.5),
+ "Vector4 lerp should work as expected.");
+ CHECK_MESSAGE(
+ vector1.lerp(vector2, 1.0 / 3.0).is_equal_approx(Vector4(2, 3, 4, 5)),
+ "Vector4 lerp should work as expected.");
+ CHECK_MESSAGE(
+ vector1.cubic_interpolate(vector2, Vector4(), Vector4(7, 7, 7, 7), 0.5) == Vector4(2.375, 3.5, 4.625, 5.75),
+ "Vector4 cubic_interpolate should work as expected.");
+ CHECK_MESSAGE(
+ vector1.cubic_interpolate(vector2, Vector4(), Vector4(7, 7, 7, 7), 1.0 / 3.0).is_equal_approx(Vector4(1.851851940155029297, 2.962963104248046875, 4.074074268341064453, 5.185185185185)),
+ "Vector4 cubic_interpolate should work as expected.");
+}
+
+TEST_CASE("[Vector4] Length methods") {
+ const Vector4 vector1 = Vector4(10, 10, 10, 10);
+ const Vector4 vector2 = Vector4(20, 30, 40, 50);
+ CHECK_MESSAGE(
+ vector1.length_squared() == 400,
+ "Vector4 length_squared should work as expected and return exact result.");
+ CHECK_MESSAGE(
+ Math::is_equal_approx(vector1.length(), 20),
+ "Vector4 length should work as expected.");
+ CHECK_MESSAGE(
+ vector2.length_squared() == 5400,
+ "Vector4 length_squared should work as expected and return exact result.");
+ CHECK_MESSAGE(
+ Math::is_equal_approx(vector2.length(), (real_t)73.484692283495),
+ "Vector4 length should work as expected.");
+ CHECK_MESSAGE(
+ Math::is_equal_approx(vector1.distance_to(vector2), (real_t)54.772255750517),
+ "Vector4 distance_to should work as expected.");
+ CHECK_MESSAGE(
+ Math::is_equal_approx(vector1.distance_squared_to(vector2), 3000),
+ "Vector4 distance_squared_to should work as expected.");
+}
+
+TEST_CASE("[Vector4] Limiting methods") {
+ const Vector4 vector = Vector4(10, 10, 10, 10);
+ CHECK_MESSAGE(
+ Vector4(-5, 5, 15, -15).clamp(Vector4(), vector) == Vector4(0, 5, 10, 0),
+ "Vector4 clamp should work as expected.");
+ CHECK_MESSAGE(
+ vector.clamp(Vector4(0, 10, 15, 18), Vector4(5, 10, 20, 25)) == Vector4(5, 10, 15, 18),
+ "Vector4 clamp should work as expected.");
+}
+
+TEST_CASE("[Vector4] Normalization methods") {
+ CHECK_MESSAGE(
+ Vector4(1, 0, 0, 0).is_normalized() == true,
+ "Vector4 is_normalized should return true for a normalized vector.");
+ CHECK_MESSAGE(
+ Vector4(1, 1, 1, 1).is_normalized() == false,
+ "Vector4 is_normalized should return false for a non-normalized vector.");
+ CHECK_MESSAGE(
+ Vector4(1, 0, 0, 0).normalized() == Vector4(1, 0, 0, 0),
+ "Vector4 normalized should return the same vector for a normalized vector.");
+ CHECK_MESSAGE(
+ Vector4(1, 1, 0, 0).normalized().is_equal_approx(Vector4(Math_SQRT12, Math_SQRT12, 0, 0)),
+ "Vector4 normalized should work as expected.");
+ CHECK_MESSAGE(
+ Vector4(1, 1, 1, 1).normalized().is_equal_approx(Vector4(0.5, 0.5, 0.5, 0.5)),
+ "Vector4 normalized should work as expected.");
+}
+
+TEST_CASE("[Vector4] Operators") {
+ const Vector4 decimal1 = Vector4(2.3, 4.9, 7.8, 3.2);
+ const Vector4 decimal2 = Vector4(1.2, 3.4, 5.6, 1.7);
+ const Vector4 power1 = Vector4(0.75, 1.5, 0.625, 0.125);
+ const Vector4 power2 = Vector4(0.5, 0.125, 0.25, 0.75);
+ const Vector4 int1 = Vector4(4, 5, 9, 2);
+ const Vector4 int2 = Vector4(1, 2, 3, 1);
+
+ CHECK_MESSAGE(
+ -decimal1 == Vector4(-2.3, -4.9, -7.8, -3.2),
+ "Vector4 change of sign should work as expected.");
+ CHECK_MESSAGE(
+ (decimal1 + decimal2).is_equal_approx(Vector4(3.5, 8.3, 13.4, 4.9)),
+ "Vector4 addition should behave as expected.");
+ CHECK_MESSAGE(
+ (power1 + power2) == Vector4(1.25, 1.625, 0.875, 0.875),
+ "Vector4 addition with powers of two should give exact results.");
+ CHECK_MESSAGE(
+ (int1 + int2) == Vector4(5, 7, 12, 3),
+ "Vector4 addition with integers should give exact results.");
+
+ CHECK_MESSAGE(
+ (decimal1 - decimal2).is_equal_approx(Vector4(1.1, 1.5, 2.2, 1.5)),
+ "Vector4 subtraction should behave as expected.");
+ CHECK_MESSAGE(
+ (power1 - power2) == Vector4(0.25, 1.375, 0.375, -0.625),
+ "Vector4 subtraction with powers of two should give exact results.");
+ CHECK_MESSAGE(
+ (int1 - int2) == Vector4(3, 3, 6, 1),
+ "Vector4 subtraction with integers should give exact results.");
+
+ CHECK_MESSAGE(
+ (decimal1 * decimal2).is_equal_approx(Vector4(2.76, 16.66, 43.68, 5.44)),
+ "Vector4 multiplication should behave as expected.");
+ CHECK_MESSAGE(
+ (power1 * power2) == Vector4(0.375, 0.1875, 0.15625, 0.09375),
+ "Vector4 multiplication with powers of two should give exact results.");
+ CHECK_MESSAGE(
+ (int1 * int2) == Vector4(4, 10, 27, 2),
+ "Vector4 multiplication with integers should give exact results.");
+
+ CHECK_MESSAGE(
+ (decimal1 / decimal2).is_equal_approx(Vector4(1.91666666666666666, 1.44117647058823529, 1.39285714285714286, 1.88235294118)),
+ "Vector4 division should behave as expected.");
+ CHECK_MESSAGE(
+ (power1 / power2) == Vector4(1.5, 12.0, 2.5, 1.0 / 6.0),
+ "Vector4 division with powers of two should give exact results.");
+ CHECK_MESSAGE(
+ (int1 / int2) == Vector4(4, 2.5, 3, 2),
+ "Vector4 division with integers should give exact results.");
+
+ CHECK_MESSAGE(
+ (decimal1 * 2).is_equal_approx(Vector4(4.6, 9.8, 15.6, 6.4)),
+ "Vector4 multiplication should behave as expected.");
+ CHECK_MESSAGE(
+ (power1 * 2) == Vector4(1.5, 3, 1.25, 0.25),
+ "Vector4 multiplication with powers of two should give exact results.");
+ CHECK_MESSAGE(
+ (int1 * 2) == Vector4(8, 10, 18, 4),
+ "Vector4 multiplication with integers should give exact results.");
+
+ CHECK_MESSAGE(
+ (decimal1 / 2).is_equal_approx(Vector4(1.15, 2.45, 3.9, 1.6)),
+ "Vector4 division should behave as expected.");
+ CHECK_MESSAGE(
+ (power1 / 2) == Vector4(0.375, 0.75, 0.3125, 0.0625),
+ "Vector4 division with powers of two should give exact results.");
+ CHECK_MESSAGE(
+ (int1 / 2) == Vector4(2, 2.5, 4.5, 1),
+ "Vector4 division with integers should give exact results.");
+
+ CHECK_MESSAGE(
+ ((String)decimal1) == "(2.3, 4.9, 7.8, 3.2)",
+ "Vector4 cast to String should work as expected.");
+ CHECK_MESSAGE(
+ ((String)decimal2) == "(1.2, 3.4, 5.6, 1.7)",
+ "Vector4 cast to String should work as expected.");
+ CHECK_MESSAGE(
+ ((String)Vector4(9.7, 9.8, 9.9, -1.8)) == "(9.7, 9.8, 9.9, -1.8)",
+ "Vector4 cast to String should work as expected.");
+#ifdef REAL_T_IS_DOUBLE
+ CHECK_MESSAGE(
+ ((String)Vector4(Math_E, Math_SQRT2, Math_SQRT3, Math_SQRT3)) == "(2.71828182845905, 1.4142135623731, 1.73205080756888, 1.73205080756888)",
+ "Vector4 cast to String should print the correct amount of digits for real_t = double.");
+#else
+ CHECK_MESSAGE(
+ ((String)Vector4(Math_E, Math_SQRT2, Math_SQRT3, Math_SQRT3)) == "(2.718282, 1.414214, 1.732051, 1.732051)",
+ "Vector4 cast to String should print the correct amount of digits for real_t = float.");
+#endif // REAL_T_IS_DOUBLE
+}
+
+TEST_CASE("[Vector4] Other methods") {
+ const Vector4 vector = Vector4(1.2, 3.4, 5.6, 1.6);
+ CHECK_MESSAGE(
+ vector.direction_to(Vector4()).is_equal_approx(-vector.normalized()),
+ "Vector4 direction_to should work as expected.");
+ CHECK_MESSAGE(
+ Vector4(1, 1, 1, 1).direction_to(Vector4(2, 2, 2, 2)).is_equal_approx(Vector4(0.5, 0.5, 0.5, 0.5)),
+ "Vector4 direction_to should work as expected.");
+ CHECK_MESSAGE(
+ vector.inverse().is_equal_approx(Vector4(1 / 1.2, 1 / 3.4, 1 / 5.6, 1 / 1.6)),
+ "Vector4 inverse should work as expected.");
+ CHECK_MESSAGE(
+ vector.posmod(2).is_equal_approx(Vector4(1.2, 1.4, 1.6, 1.6)),
+ "Vector4 posmod should work as expected.");
+ CHECK_MESSAGE(
+ (-vector).posmod(2).is_equal_approx(Vector4(0.8, 0.6, 0.4, 0.4)),
+ "Vector4 posmod should work as expected.");
+ CHECK_MESSAGE(
+ vector.posmodv(Vector4(1, 2, 3, 4)).is_equal_approx(Vector4(0.2, 1.4, 2.6, 1.6)),
+ "Vector4 posmodv should work as expected.");
+ CHECK_MESSAGE(
+ (-vector).posmodv(Vector4(2, 3, 4, 5)).is_equal_approx(Vector4(0.8, 2.6, 2.4, 3.4)),
+ "Vector4 posmodv should work as expected.");
+ CHECK_MESSAGE(
+ vector.snapped(Vector4(1, 1, 1, 1)) == Vector4(1, 3, 6, 2),
+ "Vector4 snapped to integers should be the same as rounding.");
+ CHECK_MESSAGE(
+ vector.snapped(Vector4(0.25, 0.25, 0.25, 0.25)) == Vector4(1.25, 3.5, 5.5, 1.5),
+ "Vector4 snapped to 0.25 should give exact results.");
+}
+
+TEST_CASE("[Vector4] Rounding methods") {
+ const Vector4 vector1 = Vector4(1.2, 3.4, 5.6, 1.6);
+ const Vector4 vector2 = Vector4(1.2, -3.4, -5.6, -1.6);
+ CHECK_MESSAGE(
+ vector1.abs() == vector1,
+ "Vector4 abs should work as expected.");
+ CHECK_MESSAGE(
+ vector2.abs() == vector1,
+ "Vector4 abs should work as expected.");
+ CHECK_MESSAGE(
+ vector1.ceil() == Vector4(2, 4, 6, 2),
+ "Vector4 ceil should work as expected.");
+ CHECK_MESSAGE(
+ vector2.ceil() == Vector4(2, -3, -5, -1),
+ "Vector4 ceil should work as expected.");
+
+ CHECK_MESSAGE(
+ vector1.floor() == Vector4(1, 3, 5, 1),
+ "Vector4 floor should work as expected.");
+ CHECK_MESSAGE(
+ vector2.floor() == Vector4(1, -4, -6, -2),
+ "Vector4 floor should work as expected.");
+
+ CHECK_MESSAGE(
+ vector1.round() == Vector4(1, 3, 6, 2),
+ "Vector4 round should work as expected.");
+ CHECK_MESSAGE(
+ vector2.round() == Vector4(1, -3, -6, -2),
+ "Vector4 round should work as expected.");
+
+ CHECK_MESSAGE(
+ vector1.sign() == Vector4(1, 1, 1, 1),
+ "Vector4 sign should work as expected.");
+ CHECK_MESSAGE(
+ vector2.sign() == Vector4(1, -1, -1, -1),
+ "Vector4 sign should work as expected.");
+}
+
+TEST_CASE("[Vector4] Linear algebra methods") {
+ const Vector4 vector_x = Vector4(1, 0, 0, 0);
+ const Vector4 vector_y = Vector4(0, 1, 0, 0);
+ const Vector4 vector1 = Vector4(1.7, 2.3, 1, 9.1);
+ const Vector4 vector2 = Vector4(-8.2, -16, 3, 2.4);
+
+ CHECK_MESSAGE(
+ vector_x.dot(vector_y) == 0.0,
+ "Vector4 dot product of perpendicular vectors should be zero.");
+ CHECK_MESSAGE(
+ vector_x.dot(vector_x) == 1.0,
+ "Vector4 dot product of identical unit vectors should be one.");
+ CHECK_MESSAGE(
+ (vector_x * 10).dot(vector_x * 10) == 100.0,
+ "Vector4 dot product of same direction vectors should behave as expected.");
+ CHECK_MESSAGE(
+ Math::is_equal_approx((vector1 * 2).dot(vector2 * 4), (real_t)-25.9 * 8),
+ "Vector4 dot product should work as expected.");
+}
+} // namespace TestVector4
+
+#endif // TEST_VECTOR4_H
diff --git a/tests/core/math/test_vector4i.h b/tests/core/math/test_vector4i.h
new file mode 100644
index 0000000000..ac63001b24
--- /dev/null
+++ b/tests/core/math/test_vector4i.h
@@ -0,0 +1,148 @@
+/*************************************************************************/
+/* test_vector4i.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_VECTOR4I_H
+#define TEST_VECTOR4I_H
+
+#include "core/math/vector4i.h"
+#include "tests/test_macros.h"
+
+namespace TestVector4i {
+
+TEST_CASE("[Vector4i] Axis methods") {
+ Vector4i vector = Vector4i(1, 2, 3, 4);
+ CHECK_MESSAGE(
+ vector.max_axis_index() == Vector4i::Axis::AXIS_W,
+ "Vector4i max_axis_index should work as expected.");
+ CHECK_MESSAGE(
+ vector.min_axis_index() == Vector4i::Axis::AXIS_X,
+ "Vector4i min_axis_index should work as expected.");
+ CHECK_MESSAGE(
+ vector.get_axis(vector.max_axis_index()) == 4,
+ "Vector4i get_axis should work as expected.");
+ CHECK_MESSAGE(
+ vector[vector.min_axis_index()] == 1,
+ "Vector4i array operator should work as expected.");
+
+ vector.set_axis(Vector4i::Axis::AXIS_Y, 5);
+ CHECK_MESSAGE(
+ vector.get_axis(Vector4i::Axis::AXIS_Y) == 5,
+ "Vector4i set_axis should work as expected.");
+ vector[Vector4i::Axis::AXIS_Y] = 5;
+ CHECK_MESSAGE(
+ vector[Vector4i::Axis::AXIS_Y] == 5,
+ "Vector4i array operator setter should work as expected.");
+}
+
+TEST_CASE("[Vector4i] Clamp method") {
+ const Vector4i vector = Vector4i(10, 10, 10, 10);
+ CHECK_MESSAGE(
+ Vector4i(-5, 5, 15, INT_MAX).clamp(Vector4i(), vector) == Vector4i(0, 5, 10, 10),
+ "Vector4i clamp should work as expected.");
+ CHECK_MESSAGE(
+ vector.clamp(Vector4i(0, 10, 15, -10), Vector4i(5, 10, 20, -5)) == Vector4i(5, 10, 15, -5),
+ "Vector4i clamp should work as expected.");
+}
+
+TEST_CASE("[Vector4i] Length methods") {
+ const Vector4i vector1 = Vector4i(10, 10, 10, 10);
+ const Vector4i vector2 = Vector4i(20, 30, 40, 50);
+ CHECK_MESSAGE(
+ vector1.length_squared() == 400,
+ "Vector4i length_squared should work as expected and return exact result.");
+ CHECK_MESSAGE(
+ Math::is_equal_approx(vector1.length(), 20),
+ "Vector4i length should work as expected.");
+ CHECK_MESSAGE(
+ vector2.length_squared() == 5400,
+ "Vector4i length_squared should work as expected and return exact result.");
+ CHECK_MESSAGE(
+ Math::is_equal_approx(vector2.length(), 73.4846922835),
+ "Vector4i length should work as expected.");
+}
+
+TEST_CASE("[Vector4i] Operators") {
+ const Vector4i vector1 = Vector4i(4, 5, 9, 2);
+ const Vector4i vector2 = Vector4i(1, 2, 3, 4);
+
+ CHECK_MESSAGE(
+ -vector1 == Vector4i(-4, -5, -9, -2),
+ "Vector4i change of sign should work as expected.");
+ CHECK_MESSAGE(
+ (vector1 + vector2) == Vector4i(5, 7, 12, 6),
+ "Vector4i addition with integers should give exact results.");
+ CHECK_MESSAGE(
+ (vector1 - vector2) == Vector4i(3, 3, 6, -2),
+ "Vector4i subtraction with integers should give exact results.");
+ CHECK_MESSAGE(
+ (vector1 * vector2) == Vector4i(4, 10, 27, 8),
+ "Vector4i multiplication with integers should give exact results.");
+ CHECK_MESSAGE(
+ (vector1 / vector2) == Vector4i(4, 2, 3, 0),
+ "Vector4i division with integers should give exact results.");
+
+ CHECK_MESSAGE(
+ (vector1 * 2) == Vector4i(8, 10, 18, 4),
+ "Vector4i multiplication with integers should give exact results.");
+ CHECK_MESSAGE(
+ (vector1 / 2) == Vector4i(2, 2, 4, 1),
+ "Vector4i division with integers should give exact results.");
+
+ CHECK_MESSAGE(
+ ((Vector4)vector1) == Vector4(4, 5, 9, 2),
+ "Vector4i cast to Vector4 should work as expected.");
+ CHECK_MESSAGE(
+ ((Vector4)vector2) == Vector4(1, 2, 3, 4),
+ "Vector4i cast to Vector4 should work as expected.");
+ CHECK_MESSAGE(
+ Vector4i(Vector4(1.1, 2.9, 3.9, 100.5)) == Vector4i(1, 2, 3, 100),
+ "Vector4i constructed from Vector4 should work as expected.");
+}
+
+TEST_CASE("[Vector4i] Abs and sign methods") {
+ const Vector4i vector1 = Vector4i(1, 3, 5, 7);
+ const Vector4i vector2 = Vector4i(1, -3, -5, 7);
+ CHECK_MESSAGE(
+ vector1.abs() == vector1,
+ "Vector4i abs should work as expected.");
+ CHECK_MESSAGE(
+ vector2.abs() == vector1,
+ "Vector4i abs should work as expected.");
+
+ CHECK_MESSAGE(
+ vector1.sign() == Vector4i(1, 1, 1, 1),
+ "Vector4i sign should work as expected.");
+ CHECK_MESSAGE(
+ vector2.sign() == Vector4i(1, -1, -1, 1),
+ "Vector4i sign should work as expected.");
+}
+} // namespace TestVector4i
+
+#endif // TEST_VECTOR4I_H
diff --git a/tests/core/object/test_class_db.h b/tests/core/object/test_class_db.h
index c7535426df..208923edb9 100644
--- a/tests/core/object/test_class_db.h
+++ b/tests/core/object/test_class_db.h
@@ -71,6 +71,7 @@ struct ArgumentData {
String name;
bool has_defval = false;
Variant defval;
+ int position;
};
struct MethodData {
@@ -371,6 +372,39 @@ void validate_property(const Context &p_context, const ExposedClass &p_class, co
}
}
+void validate_argument(const Context &p_context, const ExposedClass &p_class, const String &p_owner_name, const String &p_owner_type, const ArgumentData &p_arg) {
+ TEST_COND((p_arg.name.is_empty() || p_arg.name.begins_with("_unnamed_arg")),
+ vformat("Unnamed argument in position %d of %s '%s.%s'.", p_arg.position, p_owner_type, p_class.name, p_owner_name));
+
+ const ExposedClass *arg_class = p_context.find_exposed_class(p_arg.type);
+ if (arg_class) {
+ TEST_COND(arg_class->is_singleton,
+ vformat("Argument type is a singleton: '%s' of %s '%s.%s'.", p_arg.name, p_owner_type, p_class.name, p_owner_name));
+
+ if (p_class.api_type == ClassDB::API_CORE) {
+ TEST_COND(arg_class->api_type == ClassDB::API_EDITOR,
+ vformat("Argument '%s' of %s '%s.%s' has type '%s' from the editor API. Core API cannot have dependencies on the editor API.",
+ p_arg.name, p_owner_type, p_class.name, p_owner_name, arg_class->name));
+ }
+ } else {
+ // Look for types that don't inherit Object.
+ TEST_FAIL_COND(!p_context.has_type(p_arg.type),
+ vformat("Argument type '%s' not found: '%s' of %s '%s.%s'.", p_arg.type.name, p_arg.name, p_owner_type, p_class.name, p_owner_name));
+ }
+
+ if (p_arg.has_defval) {
+ String type_error_msg;
+ bool arg_defval_assignable_to_type = arg_default_value_is_assignable_to_type(p_context, p_arg.defval, p_arg.type, &type_error_msg);
+
+ String err_msg = vformat("Invalid default value for parameter '%s' of %s '%s.%s'.", p_arg.name, p_owner_type, p_class.name, p_owner_name);
+ if (!type_error_msg.is_empty()) {
+ err_msg += " " + type_error_msg;
+ }
+
+ TEST_COND(!arg_defval_assignable_to_type, err_msg.utf8().get_data());
+ }
+}
+
void validate_method(const Context &p_context, const ExposedClass &p_class, const MethodData &p_method) {
if (p_method.return_type.name != StringName()) {
const ExposedClass *return_class = p_context.find_exposed_class(p_method.return_type);
@@ -392,54 +426,14 @@ void validate_method(const Context &p_context, const ExposedClass &p_class, cons
for (const ArgumentData &F : p_method.arguments) {
const ArgumentData &arg = F;
-
- const ExposedClass *arg_class = p_context.find_exposed_class(arg.type);
- if (arg_class) {
- TEST_COND(arg_class->is_singleton,
- "Argument type is a singleton: '", arg.name, "' of method '", p_class.name, ".", p_method.name, "'.");
-
- if (p_class.api_type == ClassDB::API_CORE) {
- TEST_COND(arg_class->api_type == ClassDB::API_EDITOR,
- "Argument '", arg.name, "' of method '", p_class.name, ".", p_method.name, "' has type '",
- arg_class->name, "' from the editor API. Core API cannot have dependencies on the editor API.");
- }
- } else {
- // Look for types that don't inherit Object
- TEST_FAIL_COND(!p_context.has_type(arg.type),
- "Argument type '", arg.type.name, "' not found: '", arg.name, "' of method", p_class.name, ".", p_method.name, "'.");
- }
-
- if (arg.has_defval) {
- String type_error_msg;
- bool arg_defval_assignable_to_type = arg_default_value_is_assignable_to_type(p_context, arg.defval, arg.type, &type_error_msg);
- String err_msg = vformat("Invalid default value for parameter '%s' of method '%s.%s'.", arg.name, p_class.name, p_method.name);
- if (!type_error_msg.is_empty()) {
- err_msg += " " + type_error_msg;
- }
- TEST_COND(!arg_defval_assignable_to_type, err_msg.utf8().get_data());
- }
+ validate_argument(p_context, p_class, p_method.name, "method", arg);
}
}
void validate_signal(const Context &p_context, const ExposedClass &p_class, const SignalData &p_signal) {
for (const ArgumentData &F : p_signal.arguments) {
const ArgumentData &arg = F;
-
- const ExposedClass *arg_class = p_context.find_exposed_class(arg.type);
- if (arg_class) {
- TEST_COND(arg_class->is_singleton,
- "Argument class is a singleton: '", arg.name, "' of signal '", p_class.name, ".", p_signal.name, "'.");
-
- if (p_class.api_type == ClassDB::API_CORE) {
- TEST_COND(arg_class->api_type == ClassDB::API_EDITOR,
- "Argument '", arg.name, "' of signal '", p_class.name, ".", p_signal.name, "' has type '",
- arg_class->name, "' from the editor API. Core API cannot have dependencies on the editor API.");
- }
- } else {
- // Look for types that don't inherit Object
- TEST_FAIL_COND(!p_context.has_type(arg.type),
- "Argument type '", arg.type.name, "' not found: '", arg.name, "' of signal", p_class.name, ".", p_signal.name, "'.");
- }
+ validate_argument(p_context, p_class, p_signal.name, "signal", arg);
}
}
@@ -493,13 +487,13 @@ void add_exposed_classes(Context &r_context) {
}
if (!ClassDB::is_class_exposed(class_name)) {
- MESSAGE(vformat("Ignoring class '%s' because it's not exposed.", class_name).utf8().get_data());
+ MESSAGE(vformat("Ignoring class '%s' because it's not exposed.", class_name));
class_list.pop_front();
continue;
}
if (!ClassDB::is_class_enabled(class_name)) {
- MESSAGE(vformat("Ignoring class '%s' because it's not enabled.", class_name).utf8().get_data());
+ MESSAGE(vformat("Ignoring class '%s' because it's not enabled.", class_name));
class_list.pop_front();
continue;
}
@@ -625,6 +619,7 @@ void add_exposed_classes(Context &r_context) {
ArgumentData arg;
arg.name = orig_arg_name;
+ arg.position = i;
if (arg_info.type == Variant::INT && arg_info.usage & (PROPERTY_USAGE_CLASS_IS_ENUM | PROPERTY_USAGE_CLASS_IS_BITFIELD)) {
arg.type.name = arg_info.class_name;
@@ -693,6 +688,7 @@ void add_exposed_classes(Context &r_context) {
ArgumentData arg;
arg.name = orig_arg_name;
+ arg.position = i;
if (arg_info.type == Variant::INT && arg_info.usage & (PROPERTY_USAGE_CLASS_IS_ENUM | PROPERTY_USAGE_CLASS_IS_BITFIELD)) {
arg.type.name = arg_info.class_name;
@@ -841,7 +837,7 @@ TEST_SUITE("[ClassDB]") {
add_builtin_types(context);
add_global_enums(context);
- SUBCASE("[ClassDB] Find exposed class") {
+ SUBCASE("[ClassDB] Validate exposed classes") {
const ExposedClass *object_class = context.find_exposed_class(context.names_cache.object_class);
TEST_FAIL_COND(!object_class, "Object class not found.");
TEST_FAIL_COND(object_class->base != StringName(),
diff --git a/tests/core/object/test_method_bind.h b/tests/core/object/test_method_bind.h
index 350a08b6e2..2fe0c264a1 100644
--- a/tests/core/object/test_method_bind.h
+++ b/tests/core/object/test_method_bind.h
@@ -155,7 +155,6 @@ public:
TEST_CASE("[MethodBind] check all method binds") {
MethodBindTester *mbt = memnew(MethodBindTester);
- print_line("testing method bind");
mbt->run_tests();
CHECK(mbt->test_valid[MethodBindTester::TEST_METHOD]);
diff --git a/tests/core/object/test_object.h b/tests/core/object/test_object.h
index 5b9d9cab53..88a3e4ccad 100644
--- a/tests/core/object/test_object.h
+++ b/tests/core/object/test_object.h
@@ -95,8 +95,8 @@ public:
Ref<Script> get_script() const override {
return Ref<Script>();
}
- const Vector<Multiplayer::RPCConfig> get_rpc_methods() const override {
- return Vector<Multiplayer::RPCConfig>();
+ const Variant get_rpc_config() const override {
+ return Variant();
}
ScriptLanguage *get_language() override {
return nullptr;
diff --git a/tests/core/os/test_os.h b/tests/core/os/test_os.h
new file mode 100644
index 0000000000..c46da5e156
--- /dev/null
+++ b/tests/core/os/test_os.h
@@ -0,0 +1,158 @@
+/*************************************************************************/
+/* test_os.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_OS_H
+#define TEST_OS_H
+
+#include "core/os/os.h"
+
+#include "thirdparty/doctest/doctest.h"
+
+namespace TestOS {
+
+TEST_CASE("[OS] Environment variables") {
+#ifdef WINDOWS_ENABLED
+ CHECK_MESSAGE(
+ OS::get_singleton()->has_environment("USERPROFILE"),
+ "The USERPROFILE environment variable should be present.");
+#else
+ CHECK_MESSAGE(
+ OS::get_singleton()->has_environment("HOME"),
+ "The HOME environment variable should be present.");
+#endif
+
+ OS::get_singleton()->set_environment("HELLO", "world");
+ CHECK_MESSAGE(
+ OS::get_singleton()->get_environment("HELLO") == "world",
+ "The previously-set HELLO environment variable should return the expected value.");
+}
+
+TEST_CASE("[OS] Command line arguments") {
+ List<String> arguments = OS::get_singleton()->get_cmdline_args();
+ bool found = false;
+ for (int i = 0; i < arguments.size(); i++) {
+ if (arguments[i] == "--test") {
+ found = true;
+ break;
+ }
+ }
+ CHECK_MESSAGE(
+ found,
+ "The `--test` option must be present in the list of command line arguments.");
+}
+
+TEST_CASE("[OS] Executable and data paths") {
+ CHECK_MESSAGE(
+ OS::get_singleton()->get_executable_path().is_absolute_path(),
+ "The executable path returned should be an absolute path.");
+ CHECK_MESSAGE(
+ OS::get_singleton()->get_data_path().is_absolute_path(),
+ "The user data path returned should be an absolute path.");
+ CHECK_MESSAGE(
+ OS::get_singleton()->get_config_path().is_absolute_path(),
+ "The user configuration path returned should be an absolute path.");
+ CHECK_MESSAGE(
+ OS::get_singleton()->get_cache_path().is_absolute_path(),
+ "The cache path returned should be an absolute path.");
+}
+
+TEST_CASE("[OS] Ticks") {
+ CHECK_MESSAGE(
+ OS::get_singleton()->get_ticks_usec() > 1000,
+ "The returned ticks (in microseconds) must be greater than 1,000.");
+ CHECK_MESSAGE(
+ OS::get_singleton()->get_ticks_msec() > 1,
+ "The returned ticks (in milliseconds) must be greater than 1.");
+}
+
+TEST_CASE("[OS] Feature tags") {
+ CHECK_MESSAGE(
+ OS::get_singleton()->has_feature("editor"),
+ "The binary has the \"editor\" feature tag.");
+ CHECK_MESSAGE(
+ !OS::get_singleton()->has_feature("standalone"),
+ "The binary does not have the \"standalone\" feature tag.");
+ CHECK_MESSAGE(
+ OS::get_singleton()->has_feature("debug"),
+ "The binary has the \"debug\" feature tag.");
+ CHECK_MESSAGE(
+ !OS::get_singleton()->has_feature("release"),
+ "The binary does not have the \"release\" feature tag.");
+}
+
+TEST_CASE("[OS] Process ID") {
+ CHECK_MESSAGE(
+ OS::get_singleton()->get_process_id() >= 1,
+ "The returned process ID should be greater than zero.");
+}
+
+TEST_CASE("[OS] Processor count and memory information") {
+ CHECK_MESSAGE(
+ OS::get_singleton()->get_processor_count() >= 1,
+ "The returned processor count should be greater than zero.");
+ CHECK_MESSAGE(
+ OS::get_singleton()->get_static_memory_usage() >= 1,
+ "The returned static memory usage should be greater than zero.");
+ CHECK_MESSAGE(
+ OS::get_singleton()->get_static_memory_peak_usage() >= 1,
+ "The returned static memory peak usage should be greater than zero.");
+}
+
+TEST_CASE("[OS] Execute") {
+#ifdef WINDOWS_ENABLED
+ List<String> arguments;
+ arguments.push_back("/C");
+ arguments.push_back("dir > NUL");
+ int exit_code;
+ const Error err = OS::get_singleton()->execute("cmd", arguments, nullptr, &exit_code);
+ CHECK_MESSAGE(
+ err == OK,
+ "(Running the command `cmd /C \"dir > NUL\"` returns the expected Godot error code (OK).");
+ CHECK_MESSAGE(
+ exit_code == 0,
+ "Running the command `cmd /C \"dir > NUL\"` returns a zero (successful) exit code.");
+#else
+ List<String> arguments;
+ arguments.push_back("-c");
+ arguments.push_back("ls > /dev/null");
+ int exit_code;
+ const Error err = OS::get_singleton()->execute("sh", arguments, nullptr, &exit_code);
+ CHECK_MESSAGE(
+ err == OK,
+ "(Running the command `sh -c \"ls > /dev/null\"` returns the expected Godot error code (OK).");
+ CHECK_MESSAGE(
+ exit_code == 0,
+ "Running the command `sh -c \"ls > /dev/null\"` returns a zero (successful) exit code.");
+#endif
+}
+
+} // namespace TestOS
+
+#endif // TEST_OS_H
diff --git a/tests/core/string/test_string.h b/tests/core/string/test_string.h
index 0c5704d6c9..8914dbfd9a 100644
--- a/tests/core/string/test_string.h
+++ b/tests/core/string/test_string.h
@@ -152,6 +152,20 @@ TEST_CASE("[String] UTF16 with BOM") {
CHECK(String::utf16(cs) == s);
}
+TEST_CASE("[String] UTF8 with CR") {
+ const String base = U"Hello darkness\r\nMy old friend\nI've come to talk\rWith you again";
+
+ String keep_cr;
+ Error err = keep_cr.parse_utf8(base.utf8().get_data());
+ CHECK(err == OK);
+ CHECK(keep_cr == base);
+
+ String no_cr;
+ err = no_cr.parse_utf8(base.utf8().get_data(), -1, true); // Skip CR.
+ CHECK(err == OK);
+ CHECK(no_cr == base.replace("\r", ""));
+}
+
TEST_CASE("[String] Invalid UTF8 (non-standard)") {
ERR_PRINT_OFF
static const uint8_t u8str[] = { 0x45, 0xE3, 0x81, 0x8A, 0xE3, 0x82, 0x88, 0xE3, 0x81, 0x86, 0xF0, 0x9F, 0x8E, 0xA4, 0xF0, 0x82, 0x82, 0xAC, 0xED, 0xA0, 0x81, 0 };
@@ -683,7 +697,9 @@ TEST_CASE("[String] sprintf") {
format = "fish %-05d frog";
args.clear();
args.push_back(-5);
+ ERR_PRINT_OFF; // Silence warning about 0 ignored.
output = format.sprintf(args, &error);
+ ERR_PRINT_ON;
REQUIRE(error == false);
CHECK(output == String("fish -5 frog"));
@@ -781,7 +797,9 @@ TEST_CASE("[String] sprintf") {
format = "fish %-011f frog";
args.clear();
args.push_back(-99.99);
+ ERR_PRINT_OFF; // Silence warning about 0 ignored.
output = format.sprintf(args, &error);
+ ERR_PRINT_ON;
REQUIRE(error == false);
CHECK(output == String("fish -99.990000 frog"));
diff --git a/tests/core/templates/test_hash_set.h b/tests/core/templates/test_hash_set.h
index 93fc0b26a3..dad149604b 100644
--- a/tests/core/templates/test_hash_set.h
+++ b/tests/core/templates/test_hash_set.h
@@ -38,7 +38,6 @@
namespace TestHashSet {
TEST_CASE("[HashSet] Insert element") {
- print_line("SMALL BEGIN MEM: ", Memory::get_mem_usage());
HashSet<int> set;
HashSet<int>::Iterator e = set.insert(42);
@@ -47,7 +46,6 @@ TEST_CASE("[HashSet] Insert element") {
CHECK(set.has(42));
CHECK(set.find(42));
set.reset();
- print_line("SMALL END MEM: ", Memory::get_mem_usage());
}
TEST_CASE("[HashSet] Insert existing element") {
@@ -225,4 +223,4 @@ TEST_CASE("[HashSet] Copy") {
} // namespace TestHashSet
-#endif // TEST_HASH_MAP_H
+#endif // TEST_HASH_SET_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/core/templates/test_vector.h b/tests/core/templates/test_vector.h
index f27d6a332e..3fc9264f5a 100644
--- a/tests/core/templates/test_vector.h
+++ b/tests/core/templates/test_vector.h
@@ -291,8 +291,10 @@ TEST_CASE("[Vector] Slice") {
CHECK(slice6[1] == 3);
CHECK(slice6[2] == 4);
+ ERR_PRINT_OFF;
Vector<int> slice7 = vector.slice(5, 1);
- CHECK(slice7.size() == 0);
+ CHECK(slice7.size() == 0); // Expected to fail.
+ ERR_PRINT_ON;
}
TEST_CASE("[Vector] Find, has") {
diff --git a/tests/core/threads/test_worker_thread_pool.h b/tests/core/threads/test_worker_thread_pool.h
new file mode 100644
index 0000000000..641b293c8a
--- /dev/null
+++ b/tests/core/threads/test_worker_thread_pool.h
@@ -0,0 +1,158 @@
+/*************************************************************************/
+/* test_worker_thread_pool.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_WORKER_THREAD_POOL_H
+#define TEST_WORKER_THREAD_POOL_H
+
+#include "core/object/worker_thread_pool.h"
+
+#include "tests/test_macros.h"
+
+namespace TestWorkerThreadPool {
+
+int u32scmp(const char32_t *l, const char32_t *r) {
+ for (; *l == *r && *l && *r; l++, r++) {
+ // Continue.
+ }
+ return *l - *r;
+}
+
+static void static_test(void *p_arg) {
+ SafeNumeric<uint32_t> *counter = (SafeNumeric<uint32_t> *)p_arg;
+ counter->increment();
+}
+
+static SafeNumeric<uint32_t> callable_counter;
+
+static void static_callable_test() {
+ callable_counter.increment();
+}
+
+TEST_CASE("[WorkerThreadPool] Process 256 threads using native task") {
+ const int count = 256;
+ SafeNumeric<uint32_t> counter;
+ WorkerThreadPool::TaskID tasks[count];
+ for (int i = 0; i < count; i++) {
+ tasks[i] = WorkerThreadPool::get_singleton()->add_native_task(static_test, &counter, true);
+ }
+ for (int i = 0; i < count; i++) {
+ WorkerThreadPool::get_singleton()->wait_for_task_completion(tasks[i]);
+ }
+
+ CHECK(counter.get() == count);
+}
+
+TEST_CASE("[WorkerThreadPool] Process 256 threads using native low priority") {
+ const int count = 256;
+ SafeNumeric<uint32_t> counter = SafeNumeric<uint32_t>(0);
+ WorkerThreadPool::TaskID tasks[count];
+ for (int i = 0; i < count; i++) {
+ tasks[i] = WorkerThreadPool::get_singleton()->add_native_task(static_test, &counter, false);
+ }
+ for (int i = 0; i < count; i++) {
+ WorkerThreadPool::get_singleton()->wait_for_task_completion(tasks[i]);
+ }
+
+ CHECK(counter.get() == count);
+}
+
+TEST_CASE("[WorkerThreadPool] Process 256 threads using callable") {
+ const int count = 256;
+ WorkerThreadPool::TaskID tasks[count];
+ callable_counter.set(0);
+ for (int i = 0; i < count; i++) {
+ tasks[i] = WorkerThreadPool::get_singleton()->add_task(callable_mp_static(static_callable_test), true);
+ }
+ for (int i = 0; i < count; i++) {
+ WorkerThreadPool::get_singleton()->wait_for_task_completion(tasks[i]);
+ }
+
+ CHECK(callable_counter.get() == count);
+}
+
+TEST_CASE("[WorkerThreadPool] Process 256 threads using callable low priority") {
+ const int count = 256;
+ WorkerThreadPool::TaskID tasks[count];
+ callable_counter.set(0);
+ for (int i = 0; i < count; i++) {
+ tasks[i] = WorkerThreadPool::get_singleton()->add_task(callable_mp_static(static_callable_test), false);
+ }
+ for (int i = 0; i < count; i++) {
+ WorkerThreadPool::get_singleton()->wait_for_task_completion(tasks[i]);
+ }
+
+ CHECK(callable_counter.get() == count);
+}
+
+static void static_group_test(void *p_arg, uint32_t p_index) {
+ SafeNumeric<uint32_t> *counter = (SafeNumeric<uint32_t> *)p_arg;
+ counter->exchange_if_greater(p_index);
+}
+
+TEST_CASE("[WorkerThreadPool] Process 256 elements on native task group") {
+ const int count = 256;
+ SafeNumeric<uint32_t> counter;
+ WorkerThreadPool::GroupID group = WorkerThreadPool::get_singleton()->add_native_group_task(static_group_test, &counter, count, -1, true);
+ WorkerThreadPool::get_singleton()->wait_for_group_task_completion(group);
+ CHECK(counter.get() == count - 1);
+}
+
+TEST_CASE("[WorkerThreadPool] Process 256 elements on native task group low priority") {
+ const int count = 256;
+ SafeNumeric<uint32_t> counter;
+ WorkerThreadPool::GroupID group = WorkerThreadPool::get_singleton()->add_native_group_task(static_group_test, &counter, count, -1, false);
+ WorkerThreadPool::get_singleton()->wait_for_group_task_completion(group);
+ CHECK(counter.get() == count - 1);
+}
+
+static SafeNumeric<uint32_t> callable_group_counter;
+
+static void static_callable_group_test(uint32_t p_index) {
+ callable_group_counter.exchange_if_greater(p_index);
+}
+
+TEST_CASE("[WorkerThreadPool] Process 256 elements on native task group") {
+ const int count = 256;
+ WorkerThreadPool::GroupID group = WorkerThreadPool::get_singleton()->add_group_task(callable_mp_static(static_callable_group_test), count, -1, true);
+ WorkerThreadPool::get_singleton()->wait_for_group_task_completion(group);
+ CHECK(callable_group_counter.get() == count - 1);
+}
+
+TEST_CASE("[WorkerThreadPool] Process 256 elements on native task group low priority") {
+ const int count = 256;
+ callable_group_counter.set(0);
+ WorkerThreadPool::GroupID group = WorkerThreadPool::get_singleton()->add_group_task(callable_mp_static(static_callable_group_test), count, -1, false);
+ WorkerThreadPool::get_singleton()->wait_for_group_task_completion(group);
+ CHECK(callable_group_counter.get() == count - 1);
+}
+
+} // namespace TestWorkerThreadPool
+
+#endif // TEST_WORKER_THREAD_POOL_H
diff --git a/tests/core/variant/test_variant.h b/tests/core/variant/test_variant.h
index 916686d7c1..d6799928b4 100644
--- a/tests/core/variant/test_variant.h
+++ b/tests/core/variant/test_variant.h
@@ -719,6 +719,7 @@ TEST_CASE("[Variant] Assignment To Color from Bool,Int,Float,String,Vec2,Vec2i,V
vec3i_v = col_v;
CHECK(vec3i_v.get_type() == Variant::COLOR);
}
+
TEST_CASE("[Variant] Writer and parser array") {
Array a = build_array(1, String("hello"), build_array(Variant()));
String a_str;
@@ -911,6 +912,67 @@ TEST_CASE("[Variant] Nested dictionary comparison") {
CHECK_FALSE(v_d1 == v_d_other_val);
}
+struct ArgumentData {
+ Variant::Type type;
+ String name;
+ bool has_defval = false;
+ Variant defval;
+ int position;
+};
+
+struct MethodData {
+ StringName name;
+ Variant::Type return_type;
+ List<ArgumentData> arguments;
+ bool is_virtual = false;
+ bool is_vararg = false;
+};
+
+TEST_CASE("[Variant] Utility functions") {
+ List<MethodData> functions;
+
+ List<StringName> function_names;
+ Variant::get_utility_function_list(&function_names);
+ function_names.sort_custom<StringName::AlphCompare>();
+
+ for (const StringName &E : function_names) {
+ MethodData md;
+ md.name = E;
+
+ // Utility function's return type.
+ if (Variant::has_utility_function_return_value(E)) {
+ md.return_type = Variant::get_utility_function_return_type(E);
+ }
+
+ // Utility function's arguments.
+ if (Variant::is_utility_function_vararg(E)) {
+ md.is_vararg = true;
+ } else {
+ for (int i = 0; i < Variant::get_utility_function_argument_count(E); i++) {
+ ArgumentData arg;
+ arg.type = Variant::get_utility_function_argument_type(E, i);
+ arg.name = Variant::get_utility_function_argument_name(E, i);
+ arg.position = i;
+
+ md.arguments.push_back(arg);
+ }
+ }
+
+ functions.push_back(md);
+ }
+
+ SUBCASE("[Variant] Validate utility functions") {
+ for (const MethodData &E : functions) {
+ for (const ArgumentData &F : E.arguments) {
+ const ArgumentData &arg = F;
+
+ TEST_COND((arg.name.is_empty() || arg.name.begins_with("_unnamed_arg")),
+ vformat("Unnamed argument in position %d of function '%s'.", arg.position, E.name));
+ }
+ }
+ }
+}
+
} // namespace TestVariant
#endif // TEST_VARIANT_H