initial commit, 4.5 stable
Some checks failed
🔗 GHA / 📊 Static checks (push) Has been cancelled
🔗 GHA / 🤖 Android (push) Has been cancelled
🔗 GHA / 🍏 iOS (push) Has been cancelled
🔗 GHA / 🐧 Linux (push) Has been cancelled
🔗 GHA / 🍎 macOS (push) Has been cancelled
🔗 GHA / 🏁 Windows (push) Has been cancelled
🔗 GHA / 🌐 Web (push) Has been cancelled
23
tests/SCsub
Normal file
@@ -0,0 +1,23 @@
|
||||
#!/usr/bin/env python
|
||||
from misc.utility.scons_hints import *
|
||||
|
||||
Import("env")
|
||||
|
||||
env.tests_sources = []
|
||||
|
||||
env_tests = env.Clone()
|
||||
|
||||
# We must disable the THREAD_LOCAL entirely in doctest to prevent crashes on debugging
|
||||
# Since we link with /MT thread_local is always expired when the header is used
|
||||
# So the debugger crashes the engine and it causes weird errors
|
||||
# Explained in https://github.com/onqtam/doctest/issues/401
|
||||
if env_tests["platform"] == "windows":
|
||||
env_tests.Append(CPPDEFINES=[("DOCTEST_THREAD_LOCAL", "")])
|
||||
|
||||
if env["disable_exceptions"]:
|
||||
env_tests.Append(CPPDEFINES=["DOCTEST_CONFIG_NO_EXCEPTIONS_BUT_WITH_ALL_ASSERTS"])
|
||||
|
||||
env_tests.add_source_files(env.tests_sources, "*.cpp")
|
||||
|
||||
lib = env_tests.add_library("tests", env.tests_sources)
|
||||
env.Prepend(LIBS=[lib])
|
||||
158
tests/core/config/test_project_settings.h
Normal file
@@ -0,0 +1,158 @@
|
||||
/**************************************************************************/
|
||||
/* test_project_settings.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/config/project_settings.h"
|
||||
#include "core/io/dir_access.h"
|
||||
#include "core/variant/variant.h"
|
||||
#include "tests/test_macros.h"
|
||||
|
||||
class TestProjectSettingsInternalsAccessor {
|
||||
public:
|
||||
static String &resource_path() {
|
||||
return ProjectSettings::get_singleton()->resource_path;
|
||||
}
|
||||
};
|
||||
|
||||
namespace TestProjectSettings {
|
||||
|
||||
TEST_CASE("[ProjectSettings] Get existing setting") {
|
||||
CHECK(ProjectSettings::get_singleton()->has_setting("application/run/main_scene"));
|
||||
|
||||
Variant variant = ProjectSettings::get_singleton()->get_setting("application/run/main_scene");
|
||||
CHECK_EQ(variant.get_type(), Variant::STRING);
|
||||
|
||||
String name = variant;
|
||||
CHECK_EQ(name, String());
|
||||
}
|
||||
|
||||
TEST_CASE("[ProjectSettings] Default value is ignored if setting exists") {
|
||||
CHECK(ProjectSettings::get_singleton()->has_setting("application/run/main_scene"));
|
||||
|
||||
Variant variant = ProjectSettings::get_singleton()->get_setting("application/run/main_scene", "SomeDefaultValue");
|
||||
CHECK_EQ(variant.get_type(), Variant::STRING);
|
||||
|
||||
String name = variant;
|
||||
CHECK_EQ(name, String());
|
||||
}
|
||||
|
||||
TEST_CASE("[ProjectSettings] Non existing setting is null") {
|
||||
CHECK_FALSE(ProjectSettings::get_singleton()->has_setting("not_existing_setting"));
|
||||
|
||||
Variant variant = ProjectSettings::get_singleton()->get_setting("not_existing_setting");
|
||||
CHECK_EQ(variant.get_type(), Variant::NIL);
|
||||
}
|
||||
|
||||
TEST_CASE("[ProjectSettings] Non existing setting should return default value") {
|
||||
CHECK_FALSE(ProjectSettings::get_singleton()->has_setting("not_existing_setting"));
|
||||
|
||||
Variant variant = ProjectSettings::get_singleton()->get_setting("not_existing_setting");
|
||||
CHECK_EQ(variant.get_type(), Variant::NIL);
|
||||
|
||||
variant = ProjectSettings::get_singleton()->get_setting("not_existing_setting", "my_nice_default_value");
|
||||
CHECK_EQ(variant.get_type(), Variant::STRING);
|
||||
|
||||
String name = variant;
|
||||
CHECK_EQ(name, "my_nice_default_value");
|
||||
|
||||
CHECK_FALSE(ProjectSettings::get_singleton()->has_setting("not_existing_setting"));
|
||||
}
|
||||
|
||||
TEST_CASE("[ProjectSettings] Set value should be returned when retrieved") {
|
||||
CHECK_FALSE(ProjectSettings::get_singleton()->has_setting("my_custom_setting"));
|
||||
|
||||
Variant variant = ProjectSettings::get_singleton()->get_setting("my_custom_setting");
|
||||
CHECK_EQ(variant.get_type(), Variant::NIL);
|
||||
|
||||
ProjectSettings::get_singleton()->set_setting("my_custom_setting", true);
|
||||
CHECK(ProjectSettings::get_singleton()->has_setting("my_custom_setting"));
|
||||
|
||||
variant = ProjectSettings::get_singleton()->get_setting("my_custom_setting");
|
||||
CHECK_EQ(variant.get_type(), Variant::BOOL);
|
||||
|
||||
bool value = variant;
|
||||
CHECK_EQ(true, value);
|
||||
|
||||
CHECK(ProjectSettings::get_singleton()->has_setting("my_custom_setting"));
|
||||
}
|
||||
|
||||
TEST_CASE("[ProjectSettings] localize_path") {
|
||||
String old_resource_path = TestProjectSettingsInternalsAccessor::resource_path();
|
||||
TestProjectSettingsInternalsAccessor::resource_path() = DirAccess::create(DirAccess::ACCESS_FILESYSTEM)->get_current_dir();
|
||||
String root_path = ProjectSettings::get_singleton()->get_resource_path();
|
||||
#ifdef WINDOWS_ENABLED
|
||||
String root_path_win = ProjectSettings::get_singleton()->get_resource_path().replace_char('/', '\\');
|
||||
#endif
|
||||
|
||||
CHECK_EQ(ProjectSettings::get_singleton()->localize_path("filename"), "res://filename");
|
||||
CHECK_EQ(ProjectSettings::get_singleton()->localize_path("path/filename"), "res://path/filename");
|
||||
CHECK_EQ(ProjectSettings::get_singleton()->localize_path("path/something/../filename"), "res://path/filename");
|
||||
CHECK_EQ(ProjectSettings::get_singleton()->localize_path("path/./filename"), "res://path/filename");
|
||||
#ifdef WINDOWS_ENABLED
|
||||
CHECK_EQ(ProjectSettings::get_singleton()->localize_path("path\\filename"), "res://path/filename");
|
||||
CHECK_EQ(ProjectSettings::get_singleton()->localize_path("path\\something\\..\\filename"), "res://path/filename");
|
||||
CHECK_EQ(ProjectSettings::get_singleton()->localize_path("path\\.\\filename"), "res://path/filename");
|
||||
#endif
|
||||
|
||||
CHECK_EQ(ProjectSettings::get_singleton()->localize_path("../filename"), "../filename");
|
||||
CHECK_EQ(ProjectSettings::get_singleton()->localize_path("../path/filename"), "../path/filename");
|
||||
CHECK_EQ(ProjectSettings::get_singleton()->localize_path("..\\path\\filename"), "../path/filename");
|
||||
|
||||
CHECK_EQ(ProjectSettings::get_singleton()->localize_path("/testroot/filename"), "/testroot/filename");
|
||||
CHECK_EQ(ProjectSettings::get_singleton()->localize_path("/testroot/path/filename"), "/testroot/path/filename");
|
||||
CHECK_EQ(ProjectSettings::get_singleton()->localize_path("/testroot/path/something/../filename"), "/testroot/path/filename");
|
||||
CHECK_EQ(ProjectSettings::get_singleton()->localize_path("/testroot/path/./filename"), "/testroot/path/filename");
|
||||
#ifdef WINDOWS_ENABLED
|
||||
CHECK_EQ(ProjectSettings::get_singleton()->localize_path("C:/testroot/filename"), "C:/testroot/filename");
|
||||
CHECK_EQ(ProjectSettings::get_singleton()->localize_path("C:/testroot/path/filename"), "C:/testroot/path/filename");
|
||||
CHECK_EQ(ProjectSettings::get_singleton()->localize_path("C:/testroot/path/something/../filename"), "C:/testroot/path/filename");
|
||||
CHECK_EQ(ProjectSettings::get_singleton()->localize_path("C:/testroot/path/./filename"), "C:/testroot/path/filename");
|
||||
CHECK_EQ(ProjectSettings::get_singleton()->localize_path("C:\\testroot\\filename"), "C:/testroot/filename");
|
||||
CHECK_EQ(ProjectSettings::get_singleton()->localize_path("C:\\testroot\\path\\filename"), "C:/testroot/path/filename");
|
||||
CHECK_EQ(ProjectSettings::get_singleton()->localize_path("C:\\testroot\\path\\something\\..\\filename"), "C:/testroot/path/filename");
|
||||
CHECK_EQ(ProjectSettings::get_singleton()->localize_path("C:\\testroot\\path\\.\\filename"), "C:/testroot/path/filename");
|
||||
#endif
|
||||
|
||||
CHECK_EQ(ProjectSettings::get_singleton()->localize_path(root_path + "/filename"), "res://filename");
|
||||
CHECK_EQ(ProjectSettings::get_singleton()->localize_path(root_path + "/path/filename"), "res://path/filename");
|
||||
CHECK_EQ(ProjectSettings::get_singleton()->localize_path(root_path + "/path/something/../filename"), "res://path/filename");
|
||||
CHECK_EQ(ProjectSettings::get_singleton()->localize_path(root_path + "/path/./filename"), "res://path/filename");
|
||||
#ifdef WINDOWS_ENABLED
|
||||
CHECK_EQ(ProjectSettings::get_singleton()->localize_path(root_path_win + "\\filename"), "res://filename");
|
||||
CHECK_EQ(ProjectSettings::get_singleton()->localize_path(root_path_win + "\\path\\filename"), "res://path/filename");
|
||||
CHECK_EQ(ProjectSettings::get_singleton()->localize_path(root_path_win + "\\path\\something\\..\\filename"), "res://path/filename");
|
||||
CHECK_EQ(ProjectSettings::get_singleton()->localize_path(root_path_win + "\\path\\.\\filename"), "res://path/filename");
|
||||
#endif
|
||||
|
||||
TestProjectSettingsInternalsAccessor::resource_path() = old_resource_path;
|
||||
}
|
||||
|
||||
} // namespace TestProjectSettings
|
||||
110
tests/core/input/test_input_event.h
Normal file
@@ -0,0 +1,110 @@
|
||||
/**************************************************************************/
|
||||
/* test_input_event.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/input/input_event.h"
|
||||
#include "core/math/rect2.h"
|
||||
#include "core/os/memory.h"
|
||||
#include "core/variant/array.h"
|
||||
|
||||
#include "tests/test_macros.h"
|
||||
|
||||
namespace TestInputEvent {
|
||||
TEST_CASE("[InputEvent] Signal is emitted when device is changed") {
|
||||
Ref<InputEventKey> input_event;
|
||||
input_event.instantiate();
|
||||
|
||||
SIGNAL_WATCH(*input_event, CoreStringName(changed));
|
||||
|
||||
Array empty_args = { {} };
|
||||
input_event->set_device(1);
|
||||
|
||||
SIGNAL_CHECK("changed", empty_args);
|
||||
CHECK(input_event->get_device() == 1);
|
||||
|
||||
SIGNAL_UNWATCH(*input_event, CoreStringName(changed));
|
||||
}
|
||||
|
||||
TEST_CASE("[InputEvent] Test accumulate") {
|
||||
Ref<InputEventMouseMotion> iemm1, iemm2;
|
||||
Ref<InputEventKey> iek;
|
||||
|
||||
iemm1.instantiate(), iemm2.instantiate();
|
||||
iek.instantiate();
|
||||
|
||||
iemm1->set_button_mask(MouseButtonMask::LEFT);
|
||||
|
||||
CHECK_FALSE(iemm1->accumulate(iemm2));
|
||||
|
||||
iemm2->set_button_mask(MouseButtonMask::LEFT);
|
||||
|
||||
CHECK(iemm1->accumulate(iemm2));
|
||||
|
||||
CHECK_FALSE(iemm1->accumulate(iek));
|
||||
CHECK_FALSE(iemm2->accumulate(iek));
|
||||
}
|
||||
|
||||
TEST_CASE("[InputEvent][SceneTree] Test methods that interact with the InputMap") {
|
||||
const String mock_action = "mock_action";
|
||||
Ref<InputEventJoypadMotion> iejm;
|
||||
iejm.instantiate();
|
||||
|
||||
InputMap::get_singleton()->add_action(mock_action, 0.5);
|
||||
InputMap::get_singleton()->action_add_event(mock_action, iejm);
|
||||
|
||||
CHECK(iejm->is_action_type());
|
||||
CHECK(iejm->is_action(mock_action));
|
||||
|
||||
CHECK(iejm->is_action_released(mock_action));
|
||||
CHECK(Math::is_equal_approx(iejm->get_action_strength(mock_action), 0.0f));
|
||||
|
||||
iejm->set_axis_value(0.8f);
|
||||
// Since deadzone is 0.5, action_strength grows linearly from 0.5 to 1.0.
|
||||
CHECK(Math::is_equal_approx(iejm->get_action_strength(mock_action), 0.6f));
|
||||
CHECK(Math::is_equal_approx(iejm->get_action_raw_strength(mock_action), 0.8f));
|
||||
CHECK(iejm->is_action_pressed(mock_action));
|
||||
|
||||
InputMap::get_singleton()->erase_action(mock_action);
|
||||
}
|
||||
|
||||
TEST_CASE("[InputEvent] Test xformed_by") {
|
||||
Ref<InputEventMouseMotion> iemm1;
|
||||
iemm1.instantiate();
|
||||
|
||||
iemm1->set_position(Vector2(0.0f, 0.0f));
|
||||
Transform2D transform;
|
||||
transform = transform.translated(Vector2(2.0f, 3.0f));
|
||||
|
||||
Ref<InputEventMouseMotion> iemm2 = iemm1->xformed_by(transform);
|
||||
|
||||
CHECK(iemm2->get_position().is_equal_approx(Vector2(2.0f, 3.0f)));
|
||||
}
|
||||
} // namespace TestInputEvent
|
||||
336
tests/core/input/test_input_event_key.h
Normal file
@@ -0,0 +1,336 @@
|
||||
/**************************************************************************/
|
||||
/* test_input_event_key.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/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 retrieves location") {
|
||||
InputEventKey key;
|
||||
|
||||
CHECK(key.get_location() == KeyLocation::UNSPECIFIED);
|
||||
|
||||
key.set_location(KeyLocation::LEFT);
|
||||
CHECK(key.get_location() == KeyLocation::LEFT);
|
||||
CHECK(key.get_location() != KeyLocation::RIGHT);
|
||||
}
|
||||
|
||||
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 behavior.
|
||||
|
||||
// Key is None without a physical key.
|
||||
none_key.set_keycode(Key::NONE);
|
||||
CHECK(none_key.as_text() == "(Unset)");
|
||||
|
||||
// Key is none and has modifiers.
|
||||
none_key.set_ctrl_pressed(true);
|
||||
CHECK(none_key.as_text() == "Ctrl+(Unset)");
|
||||
|
||||
// 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 modifiers 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;
|
||||
|
||||
CHECK(none_key.to_string() == "InputEventKey: keycode=(Unset), mods=none, physical=false, location=unspecified, pressed=false, echo=false");
|
||||
// Set physical key to Escape.
|
||||
none_key.set_physical_keycode(Key::ESCAPE);
|
||||
CHECK(none_key.to_string() == "InputEventKey: keycode=4194305 (Escape), mods=none, physical=true, location=unspecified, 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, location=unspecified, pressed=false, echo=false");
|
||||
|
||||
// Set location
|
||||
key.set_location(KeyLocation::RIGHT);
|
||||
CHECK(key.to_string() == "InputEventKey: keycode=32 (Space), mods=none, physical=false, location=right, pressed=false, echo=false");
|
||||
|
||||
// Set pressed to true.
|
||||
key.set_pressed(true);
|
||||
CHECK(key.to_string() == "InputEventKey: keycode=32 (Space), mods=none, physical=false, location=right, pressed=true, echo=false");
|
||||
|
||||
// set echo to true.
|
||||
key.set_echo(true);
|
||||
CHECK(key.to_string() == "InputEventKey: keycode=32 (Space), mods=none, physical=false, location=right, pressed=true, echo=true");
|
||||
|
||||
// Press Ctrl and Alt.
|
||||
key.set_ctrl_pressed(true);
|
||||
key.set_alt_pressed(true);
|
||||
#ifdef MACOS_ENABLED
|
||||
CHECK(key.to_string() == "InputEventKey: keycode=32 (Space), mods=Ctrl+Option, physical=false, location=right, pressed=true, echo=true");
|
||||
#else
|
||||
CHECK(key.to_string() == "InputEventKey: keycode=32 (Space), mods=Ctrl+Alt, physical=false, location=right, pressed=true, echo=true");
|
||||
#endif
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
// Physical key with location.
|
||||
InputEventKey key3;
|
||||
key3.set_keycode(Key::NONE);
|
||||
key3.set_physical_keycode(Key::SHIFT);
|
||||
|
||||
Ref<InputEventKey> loc_ref = key.create_reference(Key::NONE);
|
||||
|
||||
loc_ref->set_keycode(Key::SHIFT);
|
||||
loc_ref->set_physical_keycode(Key::SHIFT);
|
||||
|
||||
CHECK(key3.is_match(loc_ref, false) == true);
|
||||
key3.set_location(KeyLocation::UNSPECIFIED);
|
||||
CHECK(key3.is_match(loc_ref, false) == true);
|
||||
|
||||
loc_ref->set_location(KeyLocation::LEFT);
|
||||
CHECK(key3.is_match(loc_ref, false) == true);
|
||||
|
||||
key3.set_location(KeyLocation::LEFT);
|
||||
CHECK(key3.is_match(loc_ref, false) == true);
|
||||
|
||||
key3.set_location(KeyLocation::RIGHT);
|
||||
CHECK(key3.is_match(loc_ref, false) == false);
|
||||
|
||||
// Keycode key with location.
|
||||
key3.set_physical_keycode(Key::NONE);
|
||||
key3.set_keycode(Key::SHIFT);
|
||||
CHECK(key3.is_match(loc_ref, false) == true);
|
||||
}
|
||||
} // namespace TestInputEventKey
|
||||
78
tests/core/input/test_input_event_mouse.h
Normal file
@@ -0,0 +1,78 @@
|
||||
/**************************************************************************/
|
||||
/* test_input_event_mouse.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/input/input_event.h"
|
||||
#include "tests/test_macros.h"
|
||||
|
||||
namespace TestInputEventMouse {
|
||||
|
||||
TEST_CASE("[InputEventMouse] Mouse button mask is set correctly") {
|
||||
InputEventMouse mousekey;
|
||||
|
||||
mousekey.set_button_mask(MouseButtonMask::LEFT);
|
||||
CHECK(mousekey.get_button_mask().has_flag(MouseButtonMask::LEFT));
|
||||
|
||||
mousekey.set_button_mask(MouseButtonMask::MB_XBUTTON1);
|
||||
CHECK(mousekey.get_button_mask().has_flag(MouseButtonMask::MB_XBUTTON1));
|
||||
|
||||
mousekey.set_button_mask(MouseButtonMask::MB_XBUTTON2);
|
||||
CHECK(mousekey.get_button_mask().has_flag(MouseButtonMask::MB_XBUTTON2));
|
||||
|
||||
mousekey.set_button_mask(MouseButtonMask::MIDDLE);
|
||||
CHECK(mousekey.get_button_mask().has_flag(MouseButtonMask::MIDDLE));
|
||||
|
||||
mousekey.set_button_mask(MouseButtonMask::RIGHT);
|
||||
CHECK(mousekey.get_button_mask().has_flag(MouseButtonMask::RIGHT));
|
||||
}
|
||||
|
||||
TEST_CASE("[InputEventMouse] Setting the mouse position works correctly") {
|
||||
InputEventMouse mousekey;
|
||||
|
||||
mousekey.set_position(Vector2{ 10, 10 });
|
||||
CHECK(mousekey.get_position() == Vector2{ 10, 10 });
|
||||
|
||||
mousekey.set_position(Vector2{ -1, -1 });
|
||||
CHECK(mousekey.get_position() == Vector2{ -1, -1 });
|
||||
}
|
||||
|
||||
TEST_CASE("[InputEventMouse] Setting the global mouse position works correctly") {
|
||||
InputEventMouse mousekey;
|
||||
|
||||
mousekey.set_global_position(Vector2{ 10, 10 });
|
||||
CHECK(mousekey.get_global_position() == Vector2{ 10, 10 });
|
||||
CHECK(mousekey.get_global_position() != Vector2{ 1, 1 });
|
||||
|
||||
mousekey.set_global_position(Vector2{ -1, -1 });
|
||||
CHECK(mousekey.get_global_position() == Vector2{ -1, -1 });
|
||||
CHECK(mousekey.get_global_position() != Vector2{ 1, 1 });
|
||||
}
|
||||
} // namespace TestInputEventMouse
|
||||
218
tests/core/input/test_shortcut.h
Normal file
@@ -0,0 +1,218 @@
|
||||
/**************************************************************************/
|
||||
/* test_shortcut.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/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 = { e1, 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 = { 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 = { 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 = { invalid_event, valid_event };
|
||||
|
||||
Shortcut s;
|
||||
s.set_events(a);
|
||||
|
||||
CHECK(s.has_valid_event() == true);
|
||||
|
||||
Array b = { 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 = { key_event2 };
|
||||
Array different2 = { key_event1, 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
|
||||
161
tests/core/io/test_config_file.h
Normal file
@@ -0,0 +1,161 @@
|
||||
/**************************************************************************/
|
||||
/* test_config_file.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/io/config_file.h"
|
||||
#include "core/os/os.h"
|
||||
|
||||
#include "tests/test_macros.h"
|
||||
|
||||
namespace TestConfigFile {
|
||||
|
||||
TEST_CASE("[ConfigFile] Parsing well-formatted files") {
|
||||
ConfigFile config_file;
|
||||
// Formatting is intentionally hand-edited to see how human-friendly the parser is.
|
||||
const Error error = config_file.parse(R"(
|
||||
[player]
|
||||
|
||||
name = "Unnamed Player"
|
||||
tagline="Waiting
|
||||
for
|
||||
Godot"
|
||||
|
||||
color =Color( 0, 0.5,1, 1) ; Inline comment
|
||||
position= Vector2(
|
||||
3,
|
||||
4
|
||||
)
|
||||
|
||||
[graphics]
|
||||
|
||||
antialiasing = true
|
||||
|
||||
; Testing comments and case-sensitivity...
|
||||
antiAliasing = false
|
||||
)");
|
||||
|
||||
CHECK_MESSAGE(error == OK, "The configuration file should parse successfully.");
|
||||
CHECK_MESSAGE(
|
||||
String(config_file.get_value("player", "name")) == "Unnamed Player",
|
||||
"Reading `player/name` should return the expected value.");
|
||||
CHECK_MESSAGE(
|
||||
String(config_file.get_value("player", "tagline")) == "Waiting\nfor\nGodot",
|
||||
"Reading `player/tagline` should return the expected value.");
|
||||
CHECK_MESSAGE(
|
||||
Color(config_file.get_value("player", "color")).is_equal_approx(Color(0, 0.5, 1)),
|
||||
"Reading `player/color` should return the expected value.");
|
||||
CHECK_MESSAGE(
|
||||
Vector2(config_file.get_value("player", "position")).is_equal_approx(Vector2(3, 4)),
|
||||
"Reading `player/position` should return the expected value.");
|
||||
CHECK_MESSAGE(
|
||||
bool(config_file.get_value("graphics", "antialiasing")),
|
||||
"Reading `graphics/antialiasing` should return `true`.");
|
||||
CHECK_MESSAGE(
|
||||
bool(config_file.get_value("graphics", "antiAliasing")) == false,
|
||||
"Reading `graphics/antiAliasing` should return `false`.");
|
||||
|
||||
// An empty ConfigFile is valid.
|
||||
const Error error_empty = config_file.parse("");
|
||||
CHECK_MESSAGE(error_empty == OK,
|
||||
"An empty configuration file should parse successfully.");
|
||||
}
|
||||
|
||||
TEST_CASE("[ConfigFile] Parsing malformatted file") {
|
||||
ConfigFile config_file;
|
||||
ERR_PRINT_OFF;
|
||||
const Error error = config_file.parse(R"(
|
||||
[player]
|
||||
|
||||
name = "Unnamed Player"" ; Extraneous closing quote.
|
||||
tagline = "Waiting\nfor\nGodot"
|
||||
|
||||
color = Color(0, 0.5, 1) ; Missing 4th parameter.
|
||||
position = Vector2(
|
||||
3,,
|
||||
4
|
||||
) ; Extraneous comma.
|
||||
|
||||
[graphics]
|
||||
|
||||
antialiasing = true
|
||||
antialiasing = false ; Duplicate key.
|
||||
)");
|
||||
ERR_PRINT_ON;
|
||||
|
||||
CHECK_MESSAGE(error == ERR_PARSE_ERROR,
|
||||
"The configuration file shouldn't parse successfully.");
|
||||
}
|
||||
|
||||
TEST_CASE("[ConfigFile] Saving file") {
|
||||
ConfigFile config_file;
|
||||
config_file.set_value("player", "name", "Unnamed Player");
|
||||
config_file.set_value("player", "tagline", "Waiting\nfor\nGodot");
|
||||
config_file.set_value("player", "color", Color(0, 0.5, 1));
|
||||
config_file.set_value("player", "position", Vector2(3, 4));
|
||||
config_file.set_value("graphics", "antialiasing", true);
|
||||
config_file.set_value("graphics", "antiAliasing", false);
|
||||
config_file.set_value("quoted", String::utf8("静音"), 42);
|
||||
config_file.set_value("quoted", "a=b", 7);
|
||||
|
||||
#ifdef WINDOWS_ENABLED
|
||||
const String config_path = OS::get_singleton()->get_environment("TEMP").path_join("config.ini");
|
||||
#else
|
||||
const String config_path = "/tmp/config.ini";
|
||||
#endif
|
||||
|
||||
config_file.save(config_path);
|
||||
|
||||
// Expected contents of the saved ConfigFile.
|
||||
const String contents = String::utf8(R"([player]
|
||||
|
||||
name="Unnamed Player"
|
||||
tagline="Waiting
|
||||
for
|
||||
Godot"
|
||||
color=Color(0, 0.5, 1, 1)
|
||||
position=Vector2(3, 4)
|
||||
|
||||
[graphics]
|
||||
|
||||
antialiasing=true
|
||||
antiAliasing=false
|
||||
|
||||
[quoted]
|
||||
|
||||
"静音"=42
|
||||
"a=b"=7
|
||||
)");
|
||||
|
||||
Ref<FileAccess> file = FileAccess::open(config_path, FileAccess::READ);
|
||||
CHECK_MESSAGE(file->get_as_utf8_string() == contents,
|
||||
"The saved configuration file should match the expected format.");
|
||||
}
|
||||
} // namespace TestConfigFile
|
||||
223
tests/core/io/test_file_access.h
Normal file
@@ -0,0 +1,223 @@
|
||||
/**************************************************************************/
|
||||
/* test_file_access.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/io/file_access.h"
|
||||
#include "tests/test_macros.h"
|
||||
#include "tests/test_utils.h"
|
||||
|
||||
namespace TestFileAccess {
|
||||
|
||||
TEST_CASE("[FileAccess] CSV read") {
|
||||
Ref<FileAccess> f = FileAccess::open(TestUtils::get_data_path("testdata.csv"), FileAccess::READ);
|
||||
REQUIRE(f.is_valid());
|
||||
|
||||
Vector<String> header = f->get_csv_line(); // Default delimiter: ",".
|
||||
REQUIRE(header.size() == 4);
|
||||
|
||||
Vector<String> row1 = f->get_csv_line(","); // Explicit delimiter, should be the same.
|
||||
REQUIRE(row1.size() == 4);
|
||||
CHECK(row1[0] == "GOOD_MORNING");
|
||||
CHECK(row1[1] == "Good Morning");
|
||||
CHECK(row1[2] == "Guten Morgen");
|
||||
CHECK(row1[3] == "Bonjour");
|
||||
|
||||
Vector<String> row2 = f->get_csv_line();
|
||||
REQUIRE(row2.size() == 4);
|
||||
CHECK(row2[0] == "GOOD_EVENING");
|
||||
CHECK(row2[1] == "Good Evening");
|
||||
CHECK(row2[2].is_empty()); // Use case: not yet translated!
|
||||
// https://github.com/godotengine/godot/issues/44269
|
||||
CHECK_MESSAGE(row2[2] != "\"", "Should not parse empty string as a single double quote.");
|
||||
CHECK(row2[3] == "\"\""); // Intentionally testing only escaped double quotes.
|
||||
|
||||
Vector<String> row3 = f->get_csv_line();
|
||||
REQUIRE(row3.size() == 6);
|
||||
CHECK(row3[0] == "Without quotes");
|
||||
CHECK(row3[1] == "With, comma");
|
||||
CHECK(row3[2] == "With \"inner\" quotes");
|
||||
CHECK(row3[3] == "With \"inner\", quotes\",\" and comma");
|
||||
CHECK(row3[4] == "With \"inner\nsplit\" quotes and\nline breaks");
|
||||
CHECK(row3[5] == "With \\nnewline chars"); // Escaped, not an actual newline.
|
||||
|
||||
Vector<String> row4 = f->get_csv_line("~"); // Custom delimiter, makes inline commas easier.
|
||||
REQUIRE(row4.size() == 3);
|
||||
CHECK(row4[0] == "Some other");
|
||||
CHECK(row4[1] == "delimiter");
|
||||
CHECK(row4[2] == "should still work, shouldn't it?");
|
||||
|
||||
Vector<String> row5 = f->get_csv_line("\t"); // Tab separated variables.
|
||||
REQUIRE(row5.size() == 3);
|
||||
CHECK(row5[0] == "What about");
|
||||
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);
|
||||
REQUIRE(f_lf.is_valid());
|
||||
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);
|
||||
REQUIRE(f_crlf.is_valid());
|
||||
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);
|
||||
REQUIRE(f_cr.is_valid());
|
||||
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");
|
||||
}
|
||||
|
||||
TEST_CASE("[FileAccess] Get/Store floating point values") {
|
||||
// BigEndian Hex: 0x40490E56
|
||||
// LittleEndian Hex: 0x560E4940
|
||||
float value = 3.1415f;
|
||||
|
||||
SUBCASE("Little Endian") {
|
||||
const String file_path = TestUtils::get_data_path("floating_point_little_endian.bin");
|
||||
const String file_path_new = TestUtils::get_data_path("floating_point_little_endian_new.bin");
|
||||
|
||||
Ref<FileAccess> f = FileAccess::open(file_path, FileAccess::READ);
|
||||
REQUIRE(f.is_valid());
|
||||
CHECK_EQ(f->get_float(), value);
|
||||
|
||||
Ref<FileAccess> fw = FileAccess::open(file_path_new, FileAccess::WRITE);
|
||||
REQUIRE(fw.is_valid());
|
||||
fw->store_float(value);
|
||||
fw->close();
|
||||
|
||||
CHECK_EQ(FileAccess::get_sha256(file_path_new), FileAccess::get_sha256(file_path));
|
||||
|
||||
DirAccess::remove_file_or_error(file_path_new);
|
||||
}
|
||||
|
||||
SUBCASE("Big Endian") {
|
||||
const String file_path = TestUtils::get_data_path("floating_point_big_endian.bin");
|
||||
const String file_path_new = TestUtils::get_data_path("floating_point_big_endian_new.bin");
|
||||
|
||||
Ref<FileAccess> f = FileAccess::open(file_path, FileAccess::READ);
|
||||
REQUIRE(f.is_valid());
|
||||
f->set_big_endian(true);
|
||||
CHECK_EQ(f->get_float(), value);
|
||||
|
||||
Ref<FileAccess> fw = FileAccess::open(file_path_new, FileAccess::WRITE);
|
||||
REQUIRE(fw.is_valid());
|
||||
fw->set_big_endian(true);
|
||||
fw->store_float(value);
|
||||
fw->close();
|
||||
|
||||
CHECK_EQ(FileAccess::get_sha256(file_path_new), FileAccess::get_sha256(file_path));
|
||||
|
||||
DirAccess::remove_file_or_error(file_path_new);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("[FileAccess] Get/Store floating point half precision values") {
|
||||
// IEEE 754 half-precision binary floating-point format:
|
||||
// sign exponent (5 bits) fraction (10 bits)
|
||||
// 0 01101 0101010101
|
||||
// BigEndian Hex: 0x3555
|
||||
// LittleEndian Hex: 0x5535
|
||||
float value = 0.33325195f;
|
||||
|
||||
SUBCASE("Little Endian") {
|
||||
const String file_path = TestUtils::get_data_path("half_precision_floating_point_little_endian.bin");
|
||||
const String file_path_new = TestUtils::get_data_path("half_precision_floating_point_little_endian_new.bin");
|
||||
|
||||
Ref<FileAccess> f = FileAccess::open(file_path, FileAccess::READ);
|
||||
REQUIRE(f.is_valid());
|
||||
CHECK_EQ(f->get_half(), value);
|
||||
|
||||
Ref<FileAccess> fw = FileAccess::open(file_path_new, FileAccess::WRITE);
|
||||
REQUIRE(fw.is_valid());
|
||||
fw->store_half(value);
|
||||
fw->close();
|
||||
|
||||
CHECK_EQ(FileAccess::get_sha256(file_path_new), FileAccess::get_sha256(file_path));
|
||||
|
||||
DirAccess::remove_file_or_error(file_path_new);
|
||||
}
|
||||
|
||||
SUBCASE("Big Endian") {
|
||||
const String file_path = TestUtils::get_data_path("half_precision_floating_point_big_endian.bin");
|
||||
const String file_path_new = TestUtils::get_data_path("half_precision_floating_point_big_endian_new.bin");
|
||||
|
||||
Ref<FileAccess> f = FileAccess::open(file_path, FileAccess::READ);
|
||||
REQUIRE(f.is_valid());
|
||||
f->set_big_endian(true);
|
||||
CHECK_EQ(f->get_half(), value);
|
||||
|
||||
Ref<FileAccess> fw = FileAccess::open(file_path_new, FileAccess::WRITE);
|
||||
REQUIRE(fw.is_valid());
|
||||
fw->set_big_endian(true);
|
||||
fw->store_half(value);
|
||||
fw->close();
|
||||
|
||||
CHECK_EQ(FileAccess::get_sha256(file_path_new), FileAccess::get_sha256(file_path));
|
||||
|
||||
DirAccess::remove_file_or_error(file_path_new);
|
||||
}
|
||||
|
||||
SUBCASE("4096 bytes fastlz compressed") {
|
||||
const String file_path = TestUtils::get_data_path("exactly_4096_bytes_fastlz.bin");
|
||||
|
||||
Ref<FileAccess> f = FileAccess::open_compressed(file_path, FileAccess::READ, FileAccess::COMPRESSION_FASTLZ);
|
||||
const Vector<uint8_t> full_data = f->get_buffer(4096 * 2);
|
||||
CHECK(full_data.size() == 4096);
|
||||
CHECK(f->eof_reached());
|
||||
|
||||
// Data should be empty.
|
||||
PackedByteArray reference;
|
||||
reference.resize_initialized(4096);
|
||||
CHECK(reference == full_data);
|
||||
|
||||
f->seek(0);
|
||||
const Vector<uint8_t> partial_data = f->get_buffer(4095);
|
||||
CHECK(partial_data.size() == 4095);
|
||||
CHECK(!f->eof_reached());
|
||||
|
||||
reference.resize_initialized(4095);
|
||||
CHECK(reference == partial_data);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace TestFileAccess
|
||||
104
tests/core/io/test_http_client.h
Normal file
@@ -0,0 +1,104 @@
|
||||
/**************************************************************************/
|
||||
/* test_http_client.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/io/http_client.h"
|
||||
|
||||
#include "tests/test_macros.h"
|
||||
|
||||
#include "modules/modules_enabled.gen.h"
|
||||
|
||||
namespace TestHTTPClient {
|
||||
|
||||
TEST_CASE("[HTTPClient] Instantiation") {
|
||||
Ref<HTTPClient> client = HTTPClient::create();
|
||||
CHECK_MESSAGE(client.is_valid(), "A HTTP Client created should not be a null pointer");
|
||||
}
|
||||
|
||||
TEST_CASE("[HTTPClient] query_string_from_dict") {
|
||||
Ref<HTTPClient> client = HTTPClient::create();
|
||||
Dictionary empty_dict;
|
||||
String empty_query = client->query_string_from_dict(empty_dict);
|
||||
CHECK_MESSAGE(empty_query.is_empty(), "A empty dictionary should return a empty string");
|
||||
|
||||
Dictionary dict1;
|
||||
dict1["key"] = "value";
|
||||
String single_key = client->query_string_from_dict(dict1);
|
||||
CHECK_MESSAGE(single_key == "key=value", "The query should return key=value for every string in the dictionary");
|
||||
|
||||
// Check Dictionary with multiple values of different types.
|
||||
Dictionary dict2;
|
||||
dict2["key1"] = "value";
|
||||
dict2["key2"] = 123;
|
||||
Array values = { 1, 2, 3 };
|
||||
dict2["key3"] = values;
|
||||
dict2["key4"] = Variant();
|
||||
String multiple_keys = client->query_string_from_dict(dict2);
|
||||
CHECK_MESSAGE(multiple_keys == "key1=value&key2=123&key3=1&key3=2&key3=3&key4",
|
||||
"The query should return key=value for every string in the dictionary. Pairs should be separated by &, arrays should be have a query for every element, and variants should have empty values");
|
||||
}
|
||||
|
||||
TEST_CASE("[HTTPClient] verify_headers") {
|
||||
Ref<HTTPClient> client = HTTPClient::create();
|
||||
Vector<String> headers = { "Accept: text/html", "Content-Type: application/json", "Authorization: Bearer abc123" };
|
||||
|
||||
Error err = client->verify_headers(headers);
|
||||
CHECK_MESSAGE(err == OK, "Expected OK for valid headers");
|
||||
|
||||
ERR_PRINT_OFF;
|
||||
Vector<String> empty_header = { "" };
|
||||
err = client->verify_headers(empty_header);
|
||||
CHECK_MESSAGE(err == ERR_INVALID_PARAMETER, "Expected ERR_INVALID_PARAMETER for empty header");
|
||||
|
||||
Vector<String> invalid_header = { "InvalidHeader", "Header: " };
|
||||
err = client->verify_headers(invalid_header);
|
||||
CHECK_MESSAGE(err == ERR_INVALID_PARAMETER, "Expected ERR_INVALID_PARAMETER for header with no colon");
|
||||
|
||||
Vector<String> invalid_header_b = { ":", "Header: " };
|
||||
err = client->verify_headers(invalid_header_b);
|
||||
CHECK_MESSAGE(err == ERR_INVALID_PARAMETER, "Expected ERR_INVALID_PARAMETER for header with colon in first position");
|
||||
ERR_PRINT_ON;
|
||||
}
|
||||
|
||||
#if defined(MODULE_MBEDTLS_ENABLED) || defined(WEB_ENABLED)
|
||||
TEST_CASE("[HTTPClient] connect_to_host") {
|
||||
Ref<HTTPClient> client = HTTPClient::create();
|
||||
String host = "https://www.example.com";
|
||||
int port = 443;
|
||||
Ref<TLSOptions> tls_options;
|
||||
|
||||
// Connect to host.
|
||||
Error err = client->connect_to_host(host, port, tls_options);
|
||||
CHECK_MESSAGE(err == OK, "Expected OK for successful connection");
|
||||
}
|
||||
#endif // MODULE_MBEDTLS_ENABLED || WEB_ENABLED
|
||||
|
||||
} // namespace TestHTTPClient
|
||||
473
tests/core/io/test_image.h
Normal file
@@ -0,0 +1,473 @@
|
||||
/**************************************************************************/
|
||||
/* test_image.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/io/image.h"
|
||||
#include "core/os/os.h"
|
||||
|
||||
#include "tests/test_utils.h"
|
||||
|
||||
#include "modules/modules_enabled.gen.h"
|
||||
|
||||
#include "thirdparty/doctest/doctest.h"
|
||||
|
||||
namespace TestImage {
|
||||
|
||||
TEST_CASE("[Image] Instantiation") {
|
||||
Ref<Image> image = memnew(Image(8, 4, false, Image::FORMAT_RGBA8));
|
||||
CHECK_MESSAGE(
|
||||
!image->is_empty(),
|
||||
"An image created with specified size and format should not be empty at first.");
|
||||
CHECK_MESSAGE(
|
||||
image->is_invisible(),
|
||||
"A newly created image should be invisible.");
|
||||
CHECK_MESSAGE(
|
||||
!image->is_compressed(),
|
||||
"A newly created image should not be compressed.");
|
||||
CHECK(!image->has_mipmaps());
|
||||
|
||||
PackedByteArray image_data = image->get_data();
|
||||
for (int i = 0; i < image_data.size(); i++) {
|
||||
CHECK_MESSAGE(
|
||||
image_data[i] == 0,
|
||||
"An image created without data specified should have its data zeroed out.");
|
||||
}
|
||||
|
||||
Ref<Image> image_copy = memnew(Image());
|
||||
CHECK_MESSAGE(
|
||||
image_copy->is_empty(),
|
||||
"An image created without any specified size and format be empty at first.");
|
||||
image_copy->copy_internals_from(image);
|
||||
|
||||
CHECK_MESSAGE(
|
||||
image->get_data() == image_copy->get_data(),
|
||||
"Duplicated images should have the same data.");
|
||||
|
||||
image_data = image->get_data();
|
||||
Ref<Image> image_from_data = memnew(Image(8, 4, false, Image::FORMAT_RGBA8, image_data));
|
||||
CHECK_MESSAGE(
|
||||
image->get_data() == image_from_data->get_data(),
|
||||
"An image created from data of another image should have the same data of the original image.");
|
||||
}
|
||||
|
||||
TEST_CASE("[Image] Saving and loading") {
|
||||
Ref<Image> image = memnew(Image(4, 4, false, Image::FORMAT_RGBA8));
|
||||
const String save_path_png = TestUtils::get_temp_path("image.png");
|
||||
const String save_path_exr = TestUtils::get_temp_path("image.exr");
|
||||
|
||||
// Save PNG
|
||||
Error err;
|
||||
err = image->save_png(save_path_png);
|
||||
CHECK_MESSAGE(
|
||||
err == OK,
|
||||
"The image should be saved successfully as a .png file.");
|
||||
|
||||
// Only available on editor builds.
|
||||
#ifdef TOOLS_ENABLED
|
||||
// Save EXR
|
||||
err = image->save_exr(save_path_exr, false);
|
||||
CHECK_MESSAGE(
|
||||
err == OK,
|
||||
"The image should be saved successfully as an .exr file.");
|
||||
#endif // TOOLS_ENABLED
|
||||
|
||||
// Load using load()
|
||||
Ref<Image> image_load = memnew(Image());
|
||||
err = image_load->load(save_path_png);
|
||||
CHECK_MESSAGE(
|
||||
err == OK,
|
||||
"The image should load successfully using load().");
|
||||
CHECK_MESSAGE(
|
||||
image->get_data() == image_load->get_data(),
|
||||
"The loaded image should have the same data as the one that got saved.");
|
||||
|
||||
#ifdef MODULE_BMP_ENABLED
|
||||
// Load BMP
|
||||
Ref<Image> image_bmp = memnew(Image());
|
||||
Ref<FileAccess> f_bmp = FileAccess::open(TestUtils::get_data_path("images/icon.bmp"), FileAccess::READ, &err);
|
||||
REQUIRE(f_bmp.is_valid());
|
||||
PackedByteArray data_bmp;
|
||||
data_bmp.resize(f_bmp->get_length() + 1);
|
||||
f_bmp->get_buffer(data_bmp.ptrw(), f_bmp->get_length());
|
||||
CHECK_MESSAGE(
|
||||
image_bmp->load_bmp_from_buffer(data_bmp) == OK,
|
||||
"The BMP image should load successfully.");
|
||||
#endif // MODULE_BMP_ENABLED
|
||||
|
||||
#ifdef MODULE_JPG_ENABLED
|
||||
// Load JPG
|
||||
Ref<Image> image_jpg = memnew(Image());
|
||||
Ref<FileAccess> f_jpg = FileAccess::open(TestUtils::get_data_path("images/icon.jpg"), FileAccess::READ, &err);
|
||||
REQUIRE(f_jpg.is_valid());
|
||||
PackedByteArray data_jpg;
|
||||
data_jpg.resize(f_jpg->get_length() + 1);
|
||||
f_jpg->get_buffer(data_jpg.ptrw(), f_jpg->get_length());
|
||||
CHECK_MESSAGE(
|
||||
image_jpg->load_jpg_from_buffer(data_jpg) == OK,
|
||||
"The JPG image should load successfully.");
|
||||
|
||||
Ref<Image> image_grayscale_jpg = memnew(Image());
|
||||
Ref<FileAccess> f_grayscale_jpg = FileAccess::open(TestUtils::get_data_path("images/grayscale.jpg"), FileAccess::READ, &err);
|
||||
REQUIRE(f_grayscale_jpg.is_valid());
|
||||
PackedByteArray data_grayscale_jpg;
|
||||
data_grayscale_jpg.resize(f_grayscale_jpg->get_length() + 1);
|
||||
f_grayscale_jpg->get_buffer(data_grayscale_jpg.ptrw(), f_grayscale_jpg->get_length());
|
||||
CHECK_MESSAGE(
|
||||
image_jpg->load_jpg_from_buffer(data_grayscale_jpg) == OK,
|
||||
"The grayscale JPG image should load successfully.");
|
||||
|
||||
// Save JPG
|
||||
const String save_path_jpg = TestUtils::get_temp_path("image.jpg");
|
||||
CHECK_MESSAGE(image->save_jpg(save_path_jpg) == OK,
|
||||
"The image should be saved successfully as a .jpg file.");
|
||||
|
||||
#ifdef MODULE_SVG_ENABLED
|
||||
// Load SVG with embedded jpg image
|
||||
Ref<Image> image_svg = memnew(Image());
|
||||
Ref<FileAccess> f_svg = FileAccess::open(TestUtils::get_data_path("images/embedded_jpg.svg"), FileAccess::READ, &err);
|
||||
REQUIRE(f_svg.is_valid());
|
||||
PackedByteArray data_svg;
|
||||
data_svg.resize(f_svg->get_length() + 1);
|
||||
f_svg->get_buffer(data_svg.ptrw(), f_svg->get_length());
|
||||
CHECK_MESSAGE(
|
||||
image_svg->load_svg_from_buffer(data_svg) == OK,
|
||||
"The SVG image should load successfully.");
|
||||
#endif // MODULE_SVG_ENABLED
|
||||
#endif // MODULE_JPG_ENABLED
|
||||
|
||||
#ifdef MODULE_WEBP_ENABLED
|
||||
// Load WebP
|
||||
Ref<Image> image_webp = memnew(Image());
|
||||
Ref<FileAccess> f_webp = FileAccess::open(TestUtils::get_data_path("images/icon.webp"), FileAccess::READ, &err);
|
||||
REQUIRE(f_webp.is_valid());
|
||||
PackedByteArray data_webp;
|
||||
data_webp.resize(f_webp->get_length() + 1);
|
||||
f_webp->get_buffer(data_webp.ptrw(), f_webp->get_length());
|
||||
CHECK_MESSAGE(
|
||||
image_webp->load_webp_from_buffer(data_webp) == OK,
|
||||
"The WebP image should load successfully.");
|
||||
#endif // MODULE_WEBP_ENABLED
|
||||
|
||||
// Load PNG
|
||||
Ref<Image> image_png = memnew(Image());
|
||||
Ref<FileAccess> f_png = FileAccess::open(TestUtils::get_data_path("images/icon.png"), FileAccess::READ, &err);
|
||||
REQUIRE(f_png.is_valid());
|
||||
PackedByteArray data_png;
|
||||
data_png.resize(f_png->get_length() + 1);
|
||||
f_png->get_buffer(data_png.ptrw(), f_png->get_length());
|
||||
CHECK_MESSAGE(
|
||||
image_png->load_png_from_buffer(data_png) == OK,
|
||||
"The PNG image should load successfully.");
|
||||
|
||||
#ifdef MODULE_TGA_ENABLED
|
||||
// Load TGA
|
||||
Ref<Image> image_tga = memnew(Image());
|
||||
Ref<FileAccess> f_tga = FileAccess::open(TestUtils::get_data_path("images/icon.tga"), FileAccess::READ, &err);
|
||||
REQUIRE(f_tga.is_valid());
|
||||
PackedByteArray data_tga;
|
||||
data_tga.resize(f_tga->get_length() + 1);
|
||||
f_tga->get_buffer(data_tga.ptrw(), f_tga->get_length());
|
||||
CHECK_MESSAGE(
|
||||
image_tga->load_tga_from_buffer(data_tga) == OK,
|
||||
"The TGA image should load successfully.");
|
||||
#endif // MODULE_TGA_ENABLED
|
||||
}
|
||||
|
||||
TEST_CASE("[Image] Basic getters") {
|
||||
Ref<Image> image = memnew(Image(8, 4, false, Image::FORMAT_LA8));
|
||||
CHECK(image->get_width() == 8);
|
||||
CHECK(image->get_height() == 4);
|
||||
CHECK(image->get_size() == Vector2(8, 4));
|
||||
CHECK(image->get_format() == Image::FORMAT_LA8);
|
||||
CHECK(image->get_used_rect() == Rect2i(0, 0, 0, 0));
|
||||
Ref<Image> image_get_rect = image->get_region(Rect2i(0, 0, 2, 1));
|
||||
CHECK(image_get_rect->get_size() == Vector2(2, 1));
|
||||
}
|
||||
|
||||
TEST_CASE("[Image] Resizing") {
|
||||
Ref<Image> image = memnew(Image(8, 8, false, Image::FORMAT_RGBA8));
|
||||
// Crop
|
||||
image->crop(4, 4);
|
||||
CHECK_MESSAGE(
|
||||
image->get_size() == Vector2(4, 4),
|
||||
"get_size() should return the correct size after cropping.");
|
||||
image->set_pixel(0, 0, Color(1, 1, 1, 1));
|
||||
|
||||
// Resize
|
||||
for (int i = 0; i < 5; i++) {
|
||||
Ref<Image> image_resized = memnew(Image());
|
||||
image_resized->copy_internals_from(image);
|
||||
Image::Interpolation interpolation = static_cast<Image::Interpolation>(i);
|
||||
image_resized->resize(8, 8, interpolation);
|
||||
CHECK_MESSAGE(
|
||||
image_resized->get_size() == Vector2(8, 8),
|
||||
"get_size() should return the correct size after resizing.");
|
||||
CHECK_MESSAGE(
|
||||
image_resized->get_pixel(1, 1).a > 0,
|
||||
"Resizing an image should also affect its content.");
|
||||
}
|
||||
|
||||
// shrink_x2()
|
||||
image->shrink_x2();
|
||||
CHECK_MESSAGE(
|
||||
image->get_size() == Vector2(2, 2),
|
||||
"get_size() should return the correct size after shrink_x2().");
|
||||
|
||||
// resize_to_po2()
|
||||
Ref<Image> image_po_2 = memnew(Image(14, 28, false, Image::FORMAT_RGBA8));
|
||||
image_po_2->resize_to_po2();
|
||||
CHECK_MESSAGE(
|
||||
image_po_2->get_size() == Vector2(16, 32),
|
||||
"get_size() should return the correct size after resize_to_po2().");
|
||||
}
|
||||
|
||||
TEST_CASE("[Image] Modifying pixels of an image") {
|
||||
Ref<Image> image = memnew(Image(3, 3, false, Image::FORMAT_RGBA8));
|
||||
image->set_pixel(0, 0, Color(1, 1, 1, 1));
|
||||
CHECK_MESSAGE(
|
||||
!image->is_invisible(),
|
||||
"Image should not be invisible after drawing on it.");
|
||||
CHECK_MESSAGE(
|
||||
image->get_pixelv(Vector2(0, 0)).is_equal_approx(Color(1, 1, 1, 1)),
|
||||
"Image's get_pixel() should return the same color value as the one being set with set_pixel() in the same position.");
|
||||
CHECK_MESSAGE(
|
||||
image->get_used_rect() == Rect2i(0, 0, 1, 1),
|
||||
"Image's get_used_rect should return the expected value, larger than Rect2i(0, 0, 0, 0) if it's visible.");
|
||||
|
||||
image->set_pixelv(Vector2(0, 0), Color(0.5, 0.5, 0.5, 0.5));
|
||||
Ref<Image> image2 = memnew(Image(3, 3, false, Image::FORMAT_RGBA8));
|
||||
|
||||
// Fill image with color
|
||||
image2->fill(Color(0.5, 0.5, 0.5, 0.5));
|
||||
for (int y = 0; y < image2->get_height(); y++) {
|
||||
for (int x = 0; x < image2->get_width(); x++) {
|
||||
CHECK_MESSAGE(
|
||||
image2->get_pixel(x, y).r > 0.49,
|
||||
"fill() should colorize all pixels of the image.");
|
||||
}
|
||||
}
|
||||
|
||||
// Fill rect with color
|
||||
{
|
||||
const int img_width = 3;
|
||||
const int img_height = 3;
|
||||
Vector<Rect2i> rects;
|
||||
rects.push_back(Rect2i());
|
||||
rects.push_back(Rect2i(-5, -5, 3, 3));
|
||||
rects.push_back(Rect2i(img_width, 0, 12, 12));
|
||||
rects.push_back(Rect2i(0, img_height, 12, 12));
|
||||
rects.push_back(Rect2i(img_width + 1, img_height + 2, 12, 12));
|
||||
rects.push_back(Rect2i(1, 1, 1, 1));
|
||||
rects.push_back(Rect2i(0, 1, 2, 3));
|
||||
rects.push_back(Rect2i(-5, 0, img_width + 10, 2));
|
||||
rects.push_back(Rect2i(0, -5, 2, img_height + 10));
|
||||
rects.push_back(Rect2i(-1, -1, img_width + 1, img_height + 1));
|
||||
|
||||
for (const Rect2i &rect : rects) {
|
||||
Ref<Image> img = memnew(Image(img_width, img_height, false, Image::FORMAT_RGBA8));
|
||||
img->fill_rect(rect, Color(1, 1, 1, 1));
|
||||
for (int y = 0; y < img->get_height(); y++) {
|
||||
for (int x = 0; x < img->get_width(); x++) {
|
||||
if (rect.abs().has_point(Point2(x, y))) {
|
||||
CHECK_MESSAGE(
|
||||
img->get_pixel(x, y).is_equal_approx(Color(1, 1, 1, 1)),
|
||||
"fill_rect() should colorize all image pixels within rect bounds.");
|
||||
} else {
|
||||
CHECK_MESSAGE(
|
||||
!img->get_pixel(x, y).is_equal_approx(Color(1, 1, 1, 1)),
|
||||
"fill_rect() shouldn't colorize any image pixel out of rect bounds.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Blend two images together
|
||||
image->blend_rect(image2, Rect2i(Vector2i(0, 0), image2->get_size()), Vector2i(0, 0));
|
||||
CHECK_MESSAGE(
|
||||
image->get_pixel(0, 0).a > 0.7,
|
||||
"blend_rect() should blend the alpha values of the two images.");
|
||||
CHECK_MESSAGE(
|
||||
image->get_used_rect().size == image->get_size(),
|
||||
"get_used_rect() should return the expected value, its Rect size should be the same as get_size() if there are no transparent pixels.");
|
||||
|
||||
Ref<Image> image3 = memnew(Image(2, 2, false, Image::FORMAT_RGBA8));
|
||||
image3->set_pixel(0, 0, Color(0, 1, 0, 1));
|
||||
|
||||
//blit_rect() two images together
|
||||
image->blit_rect(image3, Rect2i(Vector2i(0, 0), image3->get_size()), Vector2i(0, 0));
|
||||
CHECK_MESSAGE(
|
||||
image->get_pixel(0, 0).is_equal_approx(Color(0, 1, 0, 1)),
|
||||
"blit_rect() should replace old colors and not blend them.");
|
||||
CHECK_MESSAGE(
|
||||
!image->get_pixel(2, 2).is_equal_approx(Color(0, 1, 0, 1)),
|
||||
"blit_rect() should not affect the area of the image that is outside src_rect.");
|
||||
|
||||
// Flip image
|
||||
image3->flip_x();
|
||||
CHECK(image3->get_pixel(1, 0).is_equal_approx(Color(0, 1, 0, 1)));
|
||||
CHECK_MESSAGE(
|
||||
image3->get_pixel(0, 0).is_equal_approx(Color(0, 0, 0, 0)),
|
||||
"flip_x() should not leave old pixels behind.");
|
||||
image3->flip_y();
|
||||
CHECK(image3->get_pixel(1, 1).is_equal_approx(Color(0, 1, 0, 1)));
|
||||
CHECK_MESSAGE(
|
||||
image3->get_pixel(1, 0).is_equal_approx(Color(0, 0, 0, 0)),
|
||||
"flip_y() should not leave old pixels behind.");
|
||||
|
||||
// Pre-multiply Alpha then Convert from RGBA to L8, checking alpha
|
||||
{
|
||||
Ref<Image> gray_image = memnew(Image(3, 3, false, Image::FORMAT_RGBA8));
|
||||
gray_image->fill_rect(Rect2i(0, 0, 3, 3), Color(1, 1, 1, 0));
|
||||
gray_image->set_pixel(1, 1, Color(1, 1, 1, 1));
|
||||
gray_image->set_pixel(1, 2, Color(0.5, 0.5, 0.5, 0.5));
|
||||
gray_image->set_pixel(2, 1, Color(0.25, 0.05, 0.5, 1.0));
|
||||
gray_image->set_pixel(2, 2, Color(0.5, 0.25, 0.95, 0.75));
|
||||
gray_image->premultiply_alpha();
|
||||
gray_image->convert(Image::FORMAT_L8);
|
||||
CHECK_MESSAGE(gray_image->get_pixel(0, 0).is_equal_approx(Color(0, 0, 0, 1)), "convert() RGBA to L8 should be black.");
|
||||
CHECK_MESSAGE(gray_image->get_pixel(0, 1).is_equal_approx(Color(0, 0, 0, 1)), "convert() RGBA to L8 should be black.");
|
||||
CHECK_MESSAGE(gray_image->get_pixel(0, 2).is_equal_approx(Color(0, 0, 0, 1)), "convert() RGBA to L8 should be black.");
|
||||
CHECK_MESSAGE(gray_image->get_pixel(1, 0).is_equal_approx(Color(0, 0, 0, 1)), "convert() RGBA to L8 should be black.");
|
||||
CHECK_MESSAGE(gray_image->get_pixel(1, 1).is_equal_approx(Color(1, 1, 1, 1)), "convert() RGBA to L8 should be white.");
|
||||
CHECK_MESSAGE(gray_image->get_pixel(1, 2).is_equal_approx(Color(0.250980407, 0.250980407, 0.250980407, 1)), "convert() RGBA to L8 should be around 0.250980407 (64).");
|
||||
CHECK_MESSAGE(gray_image->get_pixel(2, 0).is_equal_approx(Color(0, 0, 0, 1)), "convert() RGBA to L8 should be black.");
|
||||
CHECK_MESSAGE(gray_image->get_pixel(2, 1).is_equal_approx(Color(0.121568628, 0.121568628, 0.121568628, 1)), "convert() RGBA to L8 should be around 0.121568628 (31).");
|
||||
CHECK_MESSAGE(gray_image->get_pixel(2, 2).is_equal_approx(Color(0.266666681, 0.266666681, 0.266666681, 1)), "convert() RGBA to L8 should be around 0.266666681 (68).");
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("[Image] Custom mipmaps") {
|
||||
Ref<Image> image = memnew(Image(100, 100, false, Image::FORMAT_RGBA8));
|
||||
|
||||
REQUIRE(!image->has_mipmaps());
|
||||
image->generate_mipmaps();
|
||||
REQUIRE(image->has_mipmaps());
|
||||
|
||||
const int mipmaps = image->get_mipmap_count() + 1;
|
||||
REQUIRE(mipmaps == 7);
|
||||
|
||||
// Initialize reference mipmap data.
|
||||
// Each byte is given value "mipmap_index * 5".
|
||||
|
||||
{
|
||||
PackedByteArray data = image->get_data();
|
||||
uint8_t *data_ptr = data.ptrw();
|
||||
|
||||
for (int mip = 0; mip < mipmaps; mip++) {
|
||||
int64_t mip_offset = 0;
|
||||
int64_t mip_size = 0;
|
||||
image->get_mipmap_offset_and_size(mip, mip_offset, mip_size);
|
||||
|
||||
for (int i = 0; i < mip_size; i++) {
|
||||
data_ptr[mip_offset + i] = mip * 5;
|
||||
}
|
||||
}
|
||||
image->set_data(image->get_width(), image->get_height(), image->has_mipmaps(), image->get_format(), data);
|
||||
}
|
||||
|
||||
// Byte format conversion.
|
||||
|
||||
for (int format = Image::FORMAT_L8; format <= Image::FORMAT_RGBA8; format++) {
|
||||
Ref<Image> image_bytes = memnew(Image());
|
||||
image_bytes->copy_internals_from(image);
|
||||
image_bytes->convert((Image::Format)format);
|
||||
REQUIRE(image_bytes->has_mipmaps());
|
||||
|
||||
PackedByteArray data = image_bytes->get_data();
|
||||
const uint8_t *data_ptr = data.ptr();
|
||||
|
||||
for (int mip = 0; mip < mipmaps; mip++) {
|
||||
int64_t mip_offset = 0;
|
||||
int64_t mip_size = 0;
|
||||
image_bytes->get_mipmap_offset_and_size(mip, mip_offset, mip_size);
|
||||
|
||||
for (int i = 0; i < mip_size; i++) {
|
||||
if (data_ptr[mip_offset + i] != mip * 5) {
|
||||
REQUIRE_MESSAGE(false, "Byte format conversion error.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Floating point format conversion.
|
||||
|
||||
for (int format = Image::FORMAT_RF; format <= Image::FORMAT_RGBAF; format++) {
|
||||
Ref<Image> image_rgbaf = memnew(Image());
|
||||
image_rgbaf->copy_internals_from(image);
|
||||
image_rgbaf->convert((Image::Format)format);
|
||||
REQUIRE(image_rgbaf->has_mipmaps());
|
||||
|
||||
PackedByteArray data = image_rgbaf->get_data();
|
||||
const uint8_t *data_ptr = data.ptr();
|
||||
|
||||
for (int mip = 0; mip < mipmaps; mip++) {
|
||||
int64_t mip_offset = 0;
|
||||
int64_t mip_size = 0;
|
||||
image_rgbaf->get_mipmap_offset_and_size(mip, mip_offset, mip_size);
|
||||
|
||||
for (int i = 0; i < mip_size; i += 4) {
|
||||
float value = *(float *)(data_ptr + mip_offset + i);
|
||||
if (!Math::is_equal_approx(value * 255.0f, mip * 5)) {
|
||||
REQUIRE_MESSAGE(false, "Floating point conversion error.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("[Image] Convert image") {
|
||||
for (int format = Image::FORMAT_RF; format < Image::FORMAT_RGBE9995; format++) {
|
||||
for (int new_format = Image::FORMAT_RF; new_format < Image::FORMAT_RGBE9995; new_format++) {
|
||||
Ref<Image> image = memnew(Image(4, 4, false, (Image::Format)format));
|
||||
image->convert((Image::Format)new_format);
|
||||
String format_string = Image::format_names[(Image::Format)format];
|
||||
String new_format_string = Image::format_names[(Image::Format)new_format];
|
||||
format_string = "Error converting from " + format_string + " to " + new_format_string + ".";
|
||||
CHECK_MESSAGE(image->get_format() == new_format, format_string);
|
||||
}
|
||||
}
|
||||
|
||||
Ref<Image> image = memnew(Image(4, 4, false, Image::FORMAT_RGBA8));
|
||||
PackedByteArray image_data = image->get_data();
|
||||
ERR_PRINT_OFF;
|
||||
image->convert((Image::Format)-1);
|
||||
ERR_PRINT_ON;
|
||||
CHECK_MESSAGE(image->get_data() == image_data, "Image conversion to invalid type (-1) should not alter image.");
|
||||
Ref<Image> image2 = memnew(Image(4, 4, false, Image::FORMAT_RGBA8));
|
||||
image_data = image2->get_data();
|
||||
ERR_PRINT_OFF;
|
||||
image2->convert((Image::Format)(Image::FORMAT_MAX + 1));
|
||||
ERR_PRINT_ON;
|
||||
CHECK_MESSAGE(image2->get_data() == image_data, "Image conversion to invalid type (Image::FORMAT_MAX + 1) should not alter image.");
|
||||
}
|
||||
|
||||
} // namespace TestImage
|
||||
48
tests/core/io/test_ip.h
Normal file
@@ -0,0 +1,48 @@
|
||||
/**************************************************************************/
|
||||
/* test_ip.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/io/ip.h"
|
||||
|
||||
#include "tests/test_macros.h"
|
||||
|
||||
namespace TestIP {
|
||||
|
||||
TEST_CASE("[IP] resolve_hostname") {
|
||||
for (int x = 0; x < 1000; x++) {
|
||||
IPAddress IPV4 = IP::get_singleton()->resolve_hostname("localhost", IP::TYPE_IPV4);
|
||||
CHECK("127.0.0.1" == String(IPV4));
|
||||
IPAddress IPV6 = IP::get_singleton()->resolve_hostname("localhost", IP::TYPE_IPV6);
|
||||
CHECK("0:0:0:0:0:0:0:1" == String(IPV6));
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace TestIP
|
||||
404
tests/core/io/test_json.h
Normal file
@@ -0,0 +1,404 @@
|
||||
/**************************************************************************/
|
||||
/* test_json.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/io/json.h"
|
||||
|
||||
#include "thirdparty/doctest/doctest.h"
|
||||
|
||||
namespace TestJSON {
|
||||
|
||||
TEST_CASE("[JSON] Stringify single data types") {
|
||||
CHECK(JSON::stringify(Variant()) == "null");
|
||||
CHECK(JSON::stringify(false) == "false");
|
||||
CHECK(JSON::stringify(true) == "true");
|
||||
CHECK(JSON::stringify(0) == "0");
|
||||
CHECK(JSON::stringify(12345) == "12345");
|
||||
CHECK(JSON::stringify(0.75) == "0.75");
|
||||
CHECK(JSON::stringify("test") == "\"test\"");
|
||||
CHECK(JSON::stringify("\\\b\f\n\r\t\v\"") == "\"\\\\\\b\\f\\n\\r\\t\\v\\\"\"");
|
||||
}
|
||||
|
||||
TEST_CASE("[JSON] Stringify arrays") {
|
||||
CHECK(JSON::stringify(Array()) == "[]");
|
||||
|
||||
Array int_array;
|
||||
for (int i = 0; i < 10; i++) {
|
||||
int_array.push_back(i);
|
||||
}
|
||||
CHECK(JSON::stringify(int_array) == "[0,1,2,3,4,5,6,7,8,9]");
|
||||
|
||||
Array str_array;
|
||||
str_array.push_back("Hello");
|
||||
str_array.push_back("World");
|
||||
str_array.push_back("!");
|
||||
CHECK(JSON::stringify(str_array) == "[\"Hello\",\"World\",\"!\"]");
|
||||
|
||||
Array indented_array;
|
||||
Array nested_array;
|
||||
for (int i = 0; i < 5; i++) {
|
||||
indented_array.push_back(i);
|
||||
nested_array.push_back(i);
|
||||
}
|
||||
indented_array.push_back(nested_array);
|
||||
CHECK(JSON::stringify(indented_array, "\t") == "[\n\t0,\n\t1,\n\t2,\n\t3,\n\t4,\n\t[\n\t\t0,\n\t\t1,\n\t\t2,\n\t\t3,\n\t\t4\n\t]\n]");
|
||||
|
||||
Array full_precision_array;
|
||||
full_precision_array.push_back(0.123456789012345677);
|
||||
CHECK(JSON::stringify(full_precision_array, "", true, true) == "[0.123456789012345677]");
|
||||
|
||||
ERR_PRINT_OFF
|
||||
Array self_array;
|
||||
self_array.push_back(self_array);
|
||||
CHECK(JSON::stringify(self_array) == "[\"[...]\"]");
|
||||
self_array.clear();
|
||||
|
||||
Array max_recursion_array;
|
||||
for (int i = 0; i < Variant::MAX_RECURSION_DEPTH + 1; i++) {
|
||||
Array next;
|
||||
next.push_back(max_recursion_array);
|
||||
max_recursion_array = next;
|
||||
}
|
||||
CHECK(JSON::stringify(max_recursion_array).contains("[...]"));
|
||||
ERR_PRINT_ON
|
||||
}
|
||||
|
||||
TEST_CASE("[JSON] Stringify dictionaries") {
|
||||
CHECK(JSON::stringify(Dictionary()) == "{}");
|
||||
|
||||
Dictionary single_entry;
|
||||
single_entry["key"] = "value";
|
||||
CHECK(JSON::stringify(single_entry) == "{\"key\":\"value\"}");
|
||||
|
||||
Dictionary indented;
|
||||
indented["key1"] = "value1";
|
||||
indented["key2"] = 2;
|
||||
CHECK(JSON::stringify(indented, "\t") == "{\n\t\"key1\": \"value1\",\n\t\"key2\": 2\n}");
|
||||
|
||||
Dictionary outer;
|
||||
Dictionary inner;
|
||||
inner["key"] = "value";
|
||||
outer["inner"] = inner;
|
||||
CHECK(JSON::stringify(outer) == "{\"inner\":{\"key\":\"value\"}}");
|
||||
|
||||
Dictionary full_precision_dictionary;
|
||||
full_precision_dictionary["key"] = 0.123456789012345677;
|
||||
CHECK(JSON::stringify(full_precision_dictionary, "", true, true) == "{\"key\":0.123456789012345677}");
|
||||
|
||||
ERR_PRINT_OFF
|
||||
Dictionary self_dictionary;
|
||||
self_dictionary["key"] = self_dictionary;
|
||||
CHECK(JSON::stringify(self_dictionary) == "{\"key\":\"{...}\"}");
|
||||
self_dictionary.clear();
|
||||
|
||||
Dictionary max_recursion_dictionary;
|
||||
for (int i = 0; i < Variant::MAX_RECURSION_DEPTH + 1; i++) {
|
||||
Dictionary next;
|
||||
next["key"] = max_recursion_dictionary;
|
||||
max_recursion_dictionary = next;
|
||||
}
|
||||
CHECK(JSON::stringify(max_recursion_dictionary).contains("{...:...}"));
|
||||
ERR_PRINT_ON
|
||||
}
|
||||
|
||||
// NOTE: The current JSON parser accepts many non-conformant strings such as
|
||||
// single-quoted strings, duplicate commas and trailing commas.
|
||||
// This is intentionally not tested as users shouldn't rely on this behavior.
|
||||
|
||||
TEST_CASE("[JSON] Parsing single data types") {
|
||||
// Parsing a single data type as JSON is valid per the JSON specification.
|
||||
|
||||
JSON json;
|
||||
|
||||
json.parse("null");
|
||||
CHECK_MESSAGE(
|
||||
json.get_error_line() == 0,
|
||||
"Parsing `null` as JSON should parse successfully.");
|
||||
CHECK_MESSAGE(
|
||||
json.get_data() == Variant(),
|
||||
"Parsing a double quoted string as JSON should return the expected value.");
|
||||
|
||||
json.parse("true");
|
||||
CHECK_MESSAGE(
|
||||
json.get_error_line() == 0,
|
||||
"Parsing boolean `true` as JSON should parse successfully.");
|
||||
CHECK_MESSAGE(
|
||||
json.get_data(),
|
||||
"Parsing boolean `true` as JSON should return the expected value.");
|
||||
|
||||
json.parse("false");
|
||||
CHECK_MESSAGE(
|
||||
json.get_error_line() == 0,
|
||||
"Parsing boolean `false` as JSON should parse successfully.");
|
||||
CHECK_MESSAGE(
|
||||
!json.get_data(),
|
||||
"Parsing boolean `false` as JSON should return the expected value.");
|
||||
|
||||
json.parse("123456");
|
||||
CHECK_MESSAGE(
|
||||
json.get_error_line() == 0,
|
||||
"Parsing an integer number as JSON should parse successfully.");
|
||||
CHECK_MESSAGE(
|
||||
(int)(json.get_data()) == 123456,
|
||||
"Parsing an integer number as JSON should return the expected value.");
|
||||
|
||||
json.parse("0.123456");
|
||||
CHECK_MESSAGE(
|
||||
json.get_error_line() == 0,
|
||||
"Parsing a floating-point number as JSON should parse successfully.");
|
||||
CHECK_MESSAGE(
|
||||
double(json.get_data()) == doctest::Approx(0.123456),
|
||||
"Parsing a floating-point number as JSON should return the expected value.");
|
||||
|
||||
json.parse("\"hello\"");
|
||||
CHECK_MESSAGE(
|
||||
json.get_error_line() == 0,
|
||||
"Parsing a double quoted string as JSON should parse successfully.");
|
||||
CHECK_MESSAGE(
|
||||
json.get_data() == "hello",
|
||||
"Parsing a double quoted string as JSON should return the expected value.");
|
||||
}
|
||||
|
||||
TEST_CASE("[JSON] Parsing arrays") {
|
||||
JSON json;
|
||||
|
||||
// JSON parsing fails if it's split over several lines (even if leading indentation is removed).
|
||||
json.parse(R"(["Hello", "world.", "This is",["a","json","array.",[]], "Empty arrays ahoy:", [[["Gotcha!"]]]])");
|
||||
|
||||
const Array array = json.get_data();
|
||||
CHECK_MESSAGE(
|
||||
json.get_error_line() == 0,
|
||||
"Parsing a JSON array should parse successfully.");
|
||||
CHECK_MESSAGE(
|
||||
array[0] == "Hello",
|
||||
"The parsed JSON should contain the expected values.");
|
||||
const Array sub_array = array[3];
|
||||
CHECK_MESSAGE(
|
||||
sub_array.size() == 4,
|
||||
"The parsed JSON should contain the expected values.");
|
||||
CHECK_MESSAGE(
|
||||
sub_array[1] == "json",
|
||||
"The parsed JSON should contain the expected values.");
|
||||
CHECK_MESSAGE(
|
||||
sub_array[3].hash() == Array().hash(),
|
||||
"The parsed JSON should contain the expected values.");
|
||||
const Array deep_array = Array(Array(array[5])[0])[0];
|
||||
CHECK_MESSAGE(
|
||||
deep_array[0] == "Gotcha!",
|
||||
"The parsed JSON should contain the expected values.");
|
||||
}
|
||||
|
||||
TEST_CASE("[JSON] Parsing objects (dictionaries)") {
|
||||
JSON json;
|
||||
|
||||
json.parse(R"({"name": "Godot Engine", "is_free": true, "bugs": null, "apples": {"red": 500, "green": 0, "blue": -20}, "empty_object": {}})");
|
||||
|
||||
const Dictionary dictionary = json.get_data();
|
||||
CHECK_MESSAGE(
|
||||
dictionary["name"] == "Godot Engine",
|
||||
"The parsed JSON should contain the expected values.");
|
||||
CHECK_MESSAGE(
|
||||
dictionary["is_free"],
|
||||
"The parsed JSON should contain the expected values.");
|
||||
CHECK_MESSAGE(
|
||||
dictionary["bugs"] == Variant(),
|
||||
"The parsed JSON should contain the expected values.");
|
||||
CHECK_MESSAGE(
|
||||
(int)Dictionary(dictionary["apples"])["blue"] == -20,
|
||||
"The parsed JSON should contain the expected values.");
|
||||
CHECK_MESSAGE(
|
||||
dictionary["empty_object"].hash() == Dictionary().hash(),
|
||||
"The parsed JSON should contain the expected values.");
|
||||
}
|
||||
|
||||
TEST_CASE("[JSON] Parsing escape sequences") {
|
||||
// Only certain escape sequences are valid according to the JSON specification.
|
||||
// Others must result in a parsing error instead.
|
||||
|
||||
JSON json;
|
||||
|
||||
TypedArray<String> valid_escapes = { "\";\"", "\\;\\", "/;/", "b;\b", "f;\f", "n;\n", "r;\r", "t;\t" };
|
||||
|
||||
SUBCASE("Basic valid escape sequences") {
|
||||
for (int i = 0; i < valid_escapes.size(); i++) {
|
||||
String valid_escape = valid_escapes[i];
|
||||
String valid_escape_string = valid_escape.get_slicec(';', 0);
|
||||
String valid_escape_value = valid_escape.get_slicec(';', 1);
|
||||
|
||||
String json_string = "\"\\";
|
||||
json_string += valid_escape_string;
|
||||
json_string += "\"";
|
||||
json.parse(json_string);
|
||||
|
||||
CHECK_MESSAGE(
|
||||
json.get_error_line() == 0,
|
||||
vformat("Parsing valid escape sequence `%s` as JSON should parse successfully.", valid_escape_string));
|
||||
|
||||
String json_value = json.get_data();
|
||||
CHECK_MESSAGE(
|
||||
json_value == valid_escape_value,
|
||||
vformat("Parsing valid escape sequence `%s` as JSON should return the expected value.", valid_escape_string));
|
||||
}
|
||||
}
|
||||
|
||||
SUBCASE("Valid unicode escape sequences") {
|
||||
String json_string = "\"\\u0020\"";
|
||||
json.parse(json_string);
|
||||
|
||||
CHECK_MESSAGE(
|
||||
json.get_error_line() == 0,
|
||||
vformat("Parsing valid unicode escape sequence with value `0020` as JSON should parse successfully."));
|
||||
|
||||
String json_value = json.get_data();
|
||||
CHECK_MESSAGE(
|
||||
json_value == " ",
|
||||
vformat("Parsing valid unicode escape sequence with value `0020` as JSON should return the expected value."));
|
||||
}
|
||||
|
||||
SUBCASE("Invalid escape sequences") {
|
||||
ERR_PRINT_OFF
|
||||
for (char32_t i = 0; i < 128; i++) {
|
||||
bool skip = false;
|
||||
for (int j = 0; j < valid_escapes.size(); j++) {
|
||||
String valid_escape = valid_escapes[j];
|
||||
String valid_escape_string = valid_escape.get_slicec(';', 0);
|
||||
if (valid_escape_string[0] == i) {
|
||||
skip = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (skip) {
|
||||
continue;
|
||||
}
|
||||
|
||||
String json_string = "\"\\";
|
||||
json_string += i;
|
||||
json_string += "\"";
|
||||
Error err = json.parse(json_string);
|
||||
|
||||
// TODO: Line number is currently kept on 0, despite an error occurring. This should be fixed in the JSON parser.
|
||||
// CHECK_MESSAGE(
|
||||
// json.get_error_line() != 0,
|
||||
// vformat("Parsing invalid escape sequence with ASCII value `%d` as JSON should fail to parse.", i));
|
||||
CHECK_MESSAGE(
|
||||
err == ERR_PARSE_ERROR,
|
||||
vformat("Parsing invalid escape sequence with ASCII value `%d` as JSON should fail to parse with ERR_PARSE_ERROR.", i));
|
||||
}
|
||||
ERR_PRINT_ON
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("[JSON] Serialization") {
|
||||
JSON json;
|
||||
|
||||
struct FpTestCase {
|
||||
double number;
|
||||
String json;
|
||||
};
|
||||
|
||||
struct IntTestCase {
|
||||
int64_t number;
|
||||
String json;
|
||||
};
|
||||
|
||||
struct UIntTestCase {
|
||||
uint64_t number;
|
||||
String json;
|
||||
};
|
||||
|
||||
static FpTestCase fp_tests_default_precision[] = {
|
||||
{ 0.0, "0.0" },
|
||||
{ 1000.1234567890123456789, "1000.12345678901" },
|
||||
{ -1000.1234567890123456789, "-1000.12345678901" },
|
||||
{ DBL_MAX, "179769313486231570814527423731704356798070567525844996598917476803157260780028538760589558632766878171540458953514382464234321326889464182768467546703537516986049910576551282076245490090389328944075868508455133942304583236903222948165808559332123348274797826204144723168738177180919299881250404026184124858368.0" },
|
||||
{ DBL_MAX - 1, "179769313486231570814527423731704356798070567525844996598917476803157260780028538760589558632766878171540458953514382464234321326889464182768467546703537516986049910576551282076245490090389328944075868508455133942304583236903222948165808559332123348274797826204144723168738177180919299881250404026184124858368.0" },
|
||||
{ std::pow(2, 53), "9007199254740992.0" },
|
||||
{ -std::pow(2, 53), "-9007199254740992.0" },
|
||||
{ 0.00000000000000011, "0.00000000000000011" },
|
||||
{ -0.00000000000000011, "-0.00000000000000011" },
|
||||
{ 1.0 / 3.0, "0.333333333333333" },
|
||||
{ 0.9999999999999999, "1.0" },
|
||||
{ 1.0000000000000001, "1.0" },
|
||||
};
|
||||
|
||||
static FpTestCase fp_tests_full_precision[] = {
|
||||
{ 0.0, "0.0" },
|
||||
{ 1000.1234567890123456789, "1000.12345678901238" },
|
||||
{ -1000.1234567890123456789, "-1000.12345678901238" },
|
||||
{ DBL_MAX, "179769313486231570814527423731704356798070567525844996598917476803157260780028538760589558632766878171540458953514382464234321326889464182768467546703537516986049910576551282076245490090389328944075868508455133942304583236903222948165808559332123348274797826204144723168738177180919299881250404026184124858368.0" },
|
||||
{ DBL_MAX - 1, "179769313486231570814527423731704356798070567525844996598917476803157260780028538760589558632766878171540458953514382464234321326889464182768467546703537516986049910576551282076245490090389328944075868508455133942304583236903222948165808559332123348274797826204144723168738177180919299881250404026184124858368.0" },
|
||||
{ std::pow(2, 53), "9007199254740992.0" },
|
||||
{ -std::pow(2, 53), "-9007199254740992.0" },
|
||||
{ 0.00000000000000011, "0.00000000000000011" },
|
||||
{ -0.00000000000000011, "-0.00000000000000011" },
|
||||
{ 1.0 / 3.0, "0.333333333333333315" },
|
||||
{ 0.9999999999999999, "0.999999999999999889" },
|
||||
{ 1.0000000000000001, "1.0" },
|
||||
};
|
||||
|
||||
static IntTestCase int_tests[] = {
|
||||
{ 0, "0" },
|
||||
{ INT64_MAX, "9223372036854775807" },
|
||||
{ INT64_MIN, "-9223372036854775808" },
|
||||
};
|
||||
|
||||
SUBCASE("Floating point default precision") {
|
||||
for (FpTestCase &test : fp_tests_default_precision) {
|
||||
String json_value = json.stringify(test.number, "", true, false);
|
||||
|
||||
CHECK_MESSAGE(
|
||||
json_value == test.json,
|
||||
vformat("Serializing `%.20d` to JSON should return the expected value.", test.number));
|
||||
}
|
||||
}
|
||||
|
||||
SUBCASE("Floating point full precision") {
|
||||
for (FpTestCase &test : fp_tests_full_precision) {
|
||||
String json_value = json.stringify(test.number, "", true, true);
|
||||
|
||||
CHECK_MESSAGE(
|
||||
json_value == test.json,
|
||||
vformat("Serializing `%20f` to JSON should return the expected value.", test.number));
|
||||
}
|
||||
}
|
||||
|
||||
SUBCASE("Signed integer") {
|
||||
for (IntTestCase &test : int_tests) {
|
||||
String json_value = json.stringify(test.number, "", true, true);
|
||||
|
||||
CHECK_MESSAGE(
|
||||
json_value == test.json,
|
||||
vformat("Serializing `%d` to JSON should return the expected value.", test.number));
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace TestJSON
|
||||
210
tests/core/io/test_json_native.h
Normal file
@@ -0,0 +1,210 @@
|
||||
/**************************************************************************/
|
||||
/* test_json_native.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/io/json.h"
|
||||
|
||||
#include "core/variant/typed_array.h"
|
||||
#include "core/variant/typed_dictionary.h"
|
||||
#include "tests/test_macros.h"
|
||||
|
||||
namespace TestJSONNative {
|
||||
|
||||
String encode(const Variant &p_variant, bool p_full_objects = false) {
|
||||
return JSON::stringify(JSON::from_native(p_variant, p_full_objects), "", false);
|
||||
}
|
||||
|
||||
Variant decode(const String &p_string, bool p_allow_objects = false) {
|
||||
return JSON::to_native(JSON::parse_string(p_string), p_allow_objects);
|
||||
}
|
||||
|
||||
void test(const Variant &p_variant, const String &p_string, bool p_with_objects = false) {
|
||||
CHECK(encode(p_variant, p_with_objects) == p_string);
|
||||
CHECK(decode(p_string, p_with_objects).get_construct_string() == p_variant.get_construct_string());
|
||||
}
|
||||
|
||||
TEST_CASE("[JSON][Native] Conversion between native and JSON formats") {
|
||||
// `Nil` and `bool` (represented as JSON keyword literals).
|
||||
test(Variant(), "null");
|
||||
test(false, "false");
|
||||
test(true, "true");
|
||||
|
||||
// Numbers and strings (represented as JSON strings).
|
||||
test(1, R"("i:1")");
|
||||
test(1.0, R"("f:1.0")");
|
||||
test(String("abc"), R"("s:abc")");
|
||||
test(StringName("abc"), R"("sn:abc")");
|
||||
test(NodePath("abc"), R"("np:abc")");
|
||||
|
||||
// Non-serializable types (always empty after deserialization).
|
||||
test(RID(), R"({"type":"RID"})");
|
||||
test(Callable(), R"({"type":"Callable"})");
|
||||
test(Signal(), R"({"type":"Signal"})");
|
||||
|
||||
// Math types.
|
||||
|
||||
test(Vector2(1, 2), R"({"type":"Vector2","args":[1.0,2.0]})");
|
||||
test(Vector2i(1, 2), R"({"type":"Vector2i","args":[1,2]})");
|
||||
test(Rect2(1, 2, 3, 4), R"({"type":"Rect2","args":[1.0,2.0,3.0,4.0]})");
|
||||
test(Rect2i(1, 2, 3, 4), R"({"type":"Rect2i","args":[1,2,3,4]})");
|
||||
test(Vector3(1, 2, 3), R"({"type":"Vector3","args":[1.0,2.0,3.0]})");
|
||||
test(Vector3i(1, 2, 3), R"({"type":"Vector3i","args":[1,2,3]})");
|
||||
test(Transform2D(1, 2, 3, 4, 5, 6), R"({"type":"Transform2D","args":[1.0,2.0,3.0,4.0,5.0,6.0]})");
|
||||
test(Vector4(1, 2, 3, 4), R"({"type":"Vector4","args":[1.0,2.0,3.0,4.0]})");
|
||||
test(Vector4i(1, 2, 3, 4), R"({"type":"Vector4i","args":[1,2,3,4]})");
|
||||
test(Plane(1, 2, 3, 4), R"({"type":"Plane","args":[1.0,2.0,3.0,4.0]})");
|
||||
test(Quaternion(1, 2, 3, 4), R"({"type":"Quaternion","args":[1.0,2.0,3.0,4.0]})");
|
||||
test(AABB(Vector3(1, 2, 3), Vector3(4, 5, 6)), R"({"type":"AABB","args":[1.0,2.0,3.0,4.0,5.0,6.0]})");
|
||||
|
||||
const Basis b(Vector3(1, 2, 3), Vector3(4, 5, 6), Vector3(7, 8, 9));
|
||||
test(b, R"({"type":"Basis","args":[1.0,2.0,3.0,4.0,5.0,6.0,7.0,8.0,9.0]})");
|
||||
|
||||
const Transform3D tr3d(Vector3(1, 2, 3), Vector3(4, 5, 6), Vector3(7, 8, 9), Vector3(10, 11, 12));
|
||||
test(tr3d, R"({"type":"Transform3D","args":[1.0,2.0,3.0,4.0,5.0,6.0,7.0,8.0,9.0,10.0,11.0,12.0]})");
|
||||
|
||||
const Projection p(Vector4(1, 2, 3, 4), Vector4(5, 6, 7, 8), Vector4(9, 10, 11, 12), Vector4(13, 14, 15, 16));
|
||||
test(p, R"({"type":"Projection","args":[1.0,2.0,3.0,4.0,5.0,6.0,7.0,8.0,9.0,10.0,11.0,12.0,13.0,14.0,15.0,16.0]})");
|
||||
|
||||
test(Color(1, 2, 3, 4), R"({"type":"Color","args":[1.0,2.0,3.0,4.0]})");
|
||||
|
||||
// `Object`.
|
||||
|
||||
Ref<Resource> res;
|
||||
res.instantiate();
|
||||
|
||||
// The properties are stored in an array because the order in which they are assigned may be important during initialization.
|
||||
const String res_repr = R"({"type":"Resource","props":["resource_local_to_scene",false,"resource_name","s:","script",null]})";
|
||||
|
||||
test(res, res_repr, true);
|
||||
ERR_PRINT_OFF;
|
||||
CHECK(encode(res) == "null");
|
||||
CHECK(decode(res_repr).get_type() == Variant::NIL);
|
||||
ERR_PRINT_ON;
|
||||
|
||||
// `Dictionary`.
|
||||
|
||||
Dictionary dict;
|
||||
dict[false] = true;
|
||||
dict[0] = 1;
|
||||
dict[0.0] = 1.0;
|
||||
|
||||
// Godot dictionaries preserve insertion order, so an array is used for keys/values.
|
||||
test(dict, R"({"type":"Dictionary","args":[false,true,"i:0","i:1","f:0.0","f:1.0"]})");
|
||||
|
||||
TypedDictionary<int64_t, int64_t> int_int_dict;
|
||||
int_int_dict[1] = 2;
|
||||
int_int_dict[3] = 4;
|
||||
|
||||
test(int_int_dict, R"({"type":"Dictionary","key_type":"int","value_type":"int","args":["i:1","i:2","i:3","i:4"]})");
|
||||
|
||||
TypedDictionary<int64_t, Variant> int_var_dict;
|
||||
int_var_dict[1] = "2";
|
||||
int_var_dict[3] = "4";
|
||||
|
||||
test(int_var_dict, R"({"type":"Dictionary","key_type":"int","args":["i:1","s:2","i:3","s:4"]})");
|
||||
|
||||
TypedDictionary<Variant, int64_t> var_int_dict;
|
||||
var_int_dict["1"] = 2;
|
||||
var_int_dict["3"] = 4;
|
||||
|
||||
test(var_int_dict, R"({"type":"Dictionary","value_type":"int","args":["s:1","i:2","s:3","i:4"]})");
|
||||
|
||||
Dictionary dict2;
|
||||
dict2["x"] = res;
|
||||
|
||||
const String dict2_repr = vformat(R"({"type":"Dictionary","args":["s:x",%s]})", res_repr);
|
||||
|
||||
test(dict2, dict2_repr, true);
|
||||
ERR_PRINT_OFF;
|
||||
CHECK(encode(dict2) == R"({"type":"Dictionary","args":["s:x",null]})");
|
||||
CHECK(decode(dict2_repr).get_construct_string() == "{\n\"x\": null\n}");
|
||||
ERR_PRINT_ON;
|
||||
|
||||
TypedDictionary<String, Resource> res_dict;
|
||||
res_dict["x"] = res;
|
||||
|
||||
const String res_dict_repr = vformat(R"({"type":"Dictionary","key_type":"String","value_type":"Resource","args":["s:x",%s]})", res_repr);
|
||||
|
||||
test(res_dict, res_dict_repr, true);
|
||||
ERR_PRINT_OFF;
|
||||
CHECK(encode(res_dict) == "null");
|
||||
CHECK(decode(res_dict_repr).get_type() == Variant::NIL);
|
||||
ERR_PRINT_ON;
|
||||
|
||||
// `Array`.
|
||||
|
||||
Array arr = { true, 1, "abc" };
|
||||
test(arr, R"([true,"i:1","s:abc"])");
|
||||
|
||||
TypedArray<int64_t> int_arr = { 1, 2, 3 };
|
||||
test(int_arr, R"({"type":"Array","elem_type":"int","args":["i:1","i:2","i:3"]})");
|
||||
|
||||
Array arr2 = { 1, res, 9 };
|
||||
const String arr2_repr = vformat(R"(["i:1",%s,"i:9"])", res_repr);
|
||||
|
||||
test(arr2, arr2_repr, true);
|
||||
ERR_PRINT_OFF;
|
||||
CHECK(encode(arr2) == R"(["i:1",null,"i:9"])");
|
||||
CHECK(decode(arr2_repr).get_construct_string() == "[1, null, 9]");
|
||||
ERR_PRINT_ON;
|
||||
|
||||
TypedArray<Resource> res_arr = { res };
|
||||
const String res_arr_repr = vformat(R"({"type":"Array","elem_type":"Resource","args":[%s]})", res_repr);
|
||||
|
||||
test(res_arr, res_arr_repr, true);
|
||||
ERR_PRINT_OFF;
|
||||
CHECK(encode(res_arr) == "null");
|
||||
CHECK(decode(res_arr_repr).get_type() == Variant::NIL);
|
||||
ERR_PRINT_ON;
|
||||
|
||||
// Packed arrays.
|
||||
|
||||
test(PackedByteArray({ 1, 2, 3 }), R"({"type":"PackedByteArray","args":[1,2,3]})");
|
||||
test(PackedInt32Array({ 1, 2, 3 }), R"({"type":"PackedInt32Array","args":[1,2,3]})");
|
||||
test(PackedInt64Array({ 1, 2, 3 }), R"({"type":"PackedInt64Array","args":[1,2,3]})");
|
||||
test(PackedFloat32Array({ 1, 2, 3 }), R"({"type":"PackedFloat32Array","args":[1.0,2.0,3.0]})");
|
||||
test(PackedFloat64Array({ 1, 2, 3 }), R"({"type":"PackedFloat64Array","args":[1.0,2.0,3.0]})");
|
||||
test(PackedStringArray({ "a", "b", "c" }), R"({"type":"PackedStringArray","args":["a","b","c"]})");
|
||||
|
||||
const PackedVector2Array pv2arr({ Vector2(1, 2), Vector2(3, 4), Vector2(5, 6) });
|
||||
test(pv2arr, R"({"type":"PackedVector2Array","args":[1.0,2.0,3.0,4.0,5.0,6.0]})");
|
||||
|
||||
const PackedVector3Array pv3arr({ Vector3(1, 2, 3), Vector3(4, 5, 6), Vector3(7, 8, 9) });
|
||||
test(pv3arr, R"({"type":"PackedVector3Array","args":[1.0,2.0,3.0,4.0,5.0,6.0,7.0,8.0,9.0]})");
|
||||
|
||||
const PackedColorArray pcolarr({ Color(1, 2, 3, 4), Color(5, 6, 7, 8), Color(9, 10, 11, 12) });
|
||||
test(pcolarr, R"({"type":"PackedColorArray","args":[1.0,2.0,3.0,4.0,5.0,6.0,7.0,8.0,9.0,10.0,11.0,12.0]})");
|
||||
|
||||
const PackedVector4Array pv4arr({ Vector4(1, 2, 3, 4), Vector4(5, 6, 7, 8), Vector4(9, 10, 11, 12) });
|
||||
test(pv4arr, R"({"type":"PackedVector4Array","args":[1.0,2.0,3.0,4.0,5.0,6.0,7.0,8.0,9.0,10.0,11.0,12.0]})");
|
||||
}
|
||||
|
||||
} // namespace TestJSONNative
|
||||
167
tests/core/io/test_logger.h
Normal file
@@ -0,0 +1,167 @@
|
||||
/**************************************************************************/
|
||||
/* test_logger.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/io/dir_access.h"
|
||||
#include "core/io/logger.h"
|
||||
#include "modules/regex/regex.h"
|
||||
#include "tests/test_macros.h"
|
||||
|
||||
namespace TestLogger {
|
||||
|
||||
constexpr int sleep_duration = 1200000;
|
||||
|
||||
void initialize_logs() {
|
||||
ProjectSettings::get_singleton()->set_setting("application/config/name", "godot_tests");
|
||||
DirAccess::make_dir_recursive_absolute(OS::get_singleton()->get_user_data_dir().path_join("logs"));
|
||||
}
|
||||
|
||||
void cleanup_logs() {
|
||||
ProjectSettings::get_singleton()->set_setting("application/config/name", "godot_tests");
|
||||
Ref<DirAccess> dir = DirAccess::open("user://logs");
|
||||
dir->list_dir_begin();
|
||||
String file = dir->get_next();
|
||||
while (file != "") {
|
||||
if (file.match("*.log")) {
|
||||
dir->remove(file);
|
||||
}
|
||||
file = dir->get_next();
|
||||
}
|
||||
DirAccess::remove_absolute(OS::get_singleton()->get_user_data_dir().path_join("logs"));
|
||||
DirAccess::remove_absolute(OS::get_singleton()->get_user_data_dir());
|
||||
}
|
||||
|
||||
TEST_CASE("[Logger][RotatedFileLogger] Creates the first log file and logs on it") {
|
||||
initialize_logs();
|
||||
|
||||
String waiting_for_godot = "Waiting for Godot";
|
||||
RotatedFileLogger logger("user://logs/godot.log");
|
||||
logger.logf("%s", "Waiting for Godot");
|
||||
|
||||
Error err = Error::OK;
|
||||
Ref<FileAccess> log = FileAccess::open("user://logs/godot.log", FileAccess::READ, &err);
|
||||
CHECK_EQ(err, Error::OK);
|
||||
CHECK_EQ(log->get_as_text(), waiting_for_godot);
|
||||
|
||||
cleanup_logs();
|
||||
}
|
||||
|
||||
void get_log_files(Vector<String> &log_files) {
|
||||
Ref<DirAccess> dir = DirAccess::open("user://logs");
|
||||
dir->list_dir_begin();
|
||||
String file = dir->get_next();
|
||||
while (file != "") {
|
||||
// Filtering godot.log because ordered_insert will put it first and should be the last.
|
||||
if (file.match("*.log") && file != "godot.log") {
|
||||
log_files.ordered_insert(file);
|
||||
}
|
||||
file = dir->get_next();
|
||||
}
|
||||
if (FileAccess::exists("user://logs/godot.log")) {
|
||||
log_files.push_back("godot.log");
|
||||
}
|
||||
}
|
||||
|
||||
// All things related to log file rotation are in the same test because testing it require some sleeps.
|
||||
TEST_CASE("[Logger][RotatedFileLogger] Rotates logs files") {
|
||||
initialize_logs();
|
||||
|
||||
Vector<String> all_waiting_for_godot;
|
||||
|
||||
const int number_of_files = 3;
|
||||
for (int i = 0; i < number_of_files; i++) {
|
||||
String waiting_for_godot = "Waiting for Godot " + itos(i);
|
||||
RotatedFileLogger logger("user://logs/godot.log", number_of_files);
|
||||
logger.logf("%s", waiting_for_godot.ascii().get_data());
|
||||
all_waiting_for_godot.push_back(waiting_for_godot);
|
||||
|
||||
// Required to ensure the rotation of the log file.
|
||||
OS::get_singleton()->delay_usec(sleep_duration);
|
||||
}
|
||||
|
||||
Vector<String> log_files;
|
||||
get_log_files(log_files);
|
||||
CHECK_MESSAGE(log_files.size() == number_of_files, "Did not rotate all files");
|
||||
|
||||
for (int i = 0; i < log_files.size(); i++) {
|
||||
Error err = Error::OK;
|
||||
Ref<FileAccess> log_file = FileAccess::open("user://logs/" + log_files[i], FileAccess::READ, &err);
|
||||
REQUIRE_EQ(err, Error::OK);
|
||||
CHECK_EQ(log_file->get_as_text(), all_waiting_for_godot[i]);
|
||||
}
|
||||
|
||||
// Required to ensure the rotation of the log file.
|
||||
OS::get_singleton()->delay_usec(sleep_duration);
|
||||
|
||||
// This time the oldest log must be removed and godot.log updated.
|
||||
String new_waiting_for_godot = "Waiting for Godot " + itos(number_of_files);
|
||||
all_waiting_for_godot = all_waiting_for_godot.slice(1, all_waiting_for_godot.size());
|
||||
all_waiting_for_godot.push_back(new_waiting_for_godot);
|
||||
RotatedFileLogger logger("user://logs/godot.log", number_of_files);
|
||||
logger.logf("%s", new_waiting_for_godot.ascii().get_data());
|
||||
|
||||
log_files.clear();
|
||||
get_log_files(log_files);
|
||||
CHECK_MESSAGE(log_files.size() == number_of_files, "Did not remove old log file");
|
||||
|
||||
for (int i = 0; i < log_files.size(); i++) {
|
||||
Error err = Error::OK;
|
||||
Ref<FileAccess> log_file = FileAccess::open("user://logs/" + log_files[i], FileAccess::READ, &err);
|
||||
REQUIRE_EQ(err, Error::OK);
|
||||
CHECK_EQ(log_file->get_as_text(), all_waiting_for_godot[i]);
|
||||
}
|
||||
|
||||
cleanup_logs();
|
||||
}
|
||||
|
||||
TEST_CASE("[Logger][CompositeLogger] Logs the same into multiple loggers") {
|
||||
initialize_logs();
|
||||
|
||||
Vector<Logger *> all_loggers;
|
||||
all_loggers.push_back(memnew(RotatedFileLogger("user://logs/godot_logger_1.log", 1)));
|
||||
all_loggers.push_back(memnew(RotatedFileLogger("user://logs/godot_logger_2.log", 1)));
|
||||
|
||||
String waiting_for_godot = "Waiting for Godot";
|
||||
CompositeLogger logger(all_loggers);
|
||||
logger.logf("%s", "Waiting for Godot");
|
||||
|
||||
Error err = Error::OK;
|
||||
Ref<FileAccess> log = FileAccess::open("user://logs/godot_logger_1.log", FileAccess::READ, &err);
|
||||
CHECK_EQ(err, Error::OK);
|
||||
CHECK_EQ(log->get_as_text(), waiting_for_godot);
|
||||
log = FileAccess::open("user://logs/godot_logger_2.log", FileAccess::READ, &err);
|
||||
CHECK_EQ(err, Error::OK);
|
||||
CHECK_EQ(log->get_as_text(), waiting_for_godot);
|
||||
|
||||
cleanup_logs();
|
||||
}
|
||||
|
||||
} // namespace TestLogger
|
||||
492
tests/core/io/test_marshalls.h
Normal file
@@ -0,0 +1,492 @@
|
||||
/**************************************************************************/
|
||||
/* test_marshalls.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/io/marshalls.h"
|
||||
|
||||
#include "tests/test_macros.h"
|
||||
|
||||
namespace TestMarshalls {
|
||||
|
||||
TEST_CASE("[Marshalls] Unsigned 16 bit integer encoding") {
|
||||
uint8_t arr[2];
|
||||
|
||||
unsigned int actual_size = encode_uint16(0x1234, arr);
|
||||
CHECK(actual_size == sizeof(uint16_t));
|
||||
CHECK_MESSAGE(arr[0] == 0x34, "First encoded byte value should be equal to low order byte value.");
|
||||
CHECK_MESSAGE(arr[1] == 0x12, "Last encoded byte value should be equal to high order byte value.");
|
||||
}
|
||||
|
||||
TEST_CASE("[Marshalls] Unsigned 32 bit integer encoding") {
|
||||
uint8_t arr[4];
|
||||
|
||||
unsigned int actual_size = encode_uint32(0x12345678, arr);
|
||||
CHECK(actual_size == sizeof(uint32_t));
|
||||
CHECK_MESSAGE(arr[0] == 0x78, "First encoded byte value should be equal to low order byte value.");
|
||||
CHECK(arr[1] == 0x56);
|
||||
CHECK(arr[2] == 0x34);
|
||||
CHECK_MESSAGE(arr[3] == 0x12, "Last encoded byte value should be equal to high order byte value.");
|
||||
}
|
||||
|
||||
TEST_CASE("[Marshalls] Unsigned 64 bit integer encoding") {
|
||||
uint8_t arr[8];
|
||||
|
||||
unsigned int actual_size = encode_uint64(0x0f123456789abcdef, arr);
|
||||
CHECK(actual_size == sizeof(uint64_t));
|
||||
CHECK_MESSAGE(arr[0] == 0xef, "First encoded byte value should be equal to low order byte value.");
|
||||
CHECK(arr[1] == 0xcd);
|
||||
CHECK(arr[2] == 0xab);
|
||||
CHECK(arr[3] == 0x89);
|
||||
CHECK(arr[4] == 0x67);
|
||||
CHECK(arr[5] == 0x45);
|
||||
CHECK(arr[6] == 0x23);
|
||||
CHECK_MESSAGE(arr[7] == 0xf1, "Last encoded byte value should be equal to high order byte value.");
|
||||
}
|
||||
|
||||
TEST_CASE("[Marshalls] Unsigned 16 bit integer decoding") {
|
||||
uint8_t arr[] = { 0x34, 0x12 };
|
||||
|
||||
CHECK(decode_uint16(arr) == 0x1234);
|
||||
}
|
||||
|
||||
TEST_CASE("[Marshalls] Unsigned 32 bit integer decoding") {
|
||||
uint8_t arr[] = { 0x78, 0x56, 0x34, 0x12 };
|
||||
|
||||
CHECK(decode_uint32(arr) == 0x12345678);
|
||||
}
|
||||
|
||||
TEST_CASE("[Marshalls] Unsigned 64 bit integer decoding") {
|
||||
uint8_t arr[] = { 0xef, 0xcd, 0xab, 0x89, 0x67, 0x45, 0x23, 0xf1 };
|
||||
|
||||
CHECK(decode_uint64(arr) == 0x0f123456789abcdef);
|
||||
}
|
||||
|
||||
TEST_CASE("[Marshalls] Floating point half precision encoding") {
|
||||
uint8_t arr[2];
|
||||
|
||||
// Decimal: 0.33325195
|
||||
// IEEE 754 half-precision binary floating-point format:
|
||||
// sign exponent (5 bits) fraction (10 bits)
|
||||
// 0 01101 0101010101
|
||||
// Hexadecimal: 0x3555
|
||||
unsigned int actual_size = encode_half(0.33325195f, arr);
|
||||
CHECK(actual_size == sizeof(uint16_t));
|
||||
CHECK(arr[0] == 0x55);
|
||||
CHECK(arr[1] == 0x35);
|
||||
}
|
||||
|
||||
TEST_CASE("[Marshalls] Floating point single precision encoding") {
|
||||
uint8_t arr[4];
|
||||
|
||||
// Decimal: 0.15625
|
||||
// IEEE 754 single-precision binary floating-point format:
|
||||
// sign exponent (8 bits) fraction (23 bits)
|
||||
// 0 01111100 01000000000000000000000
|
||||
// Hexadecimal: 0x3E200000
|
||||
unsigned int actual_size = encode_float(0.15625f, arr);
|
||||
CHECK(actual_size == sizeof(uint32_t));
|
||||
CHECK(arr[0] == 0x00);
|
||||
CHECK(arr[1] == 0x00);
|
||||
CHECK(arr[2] == 0x20);
|
||||
CHECK(arr[3] == 0x3e);
|
||||
}
|
||||
|
||||
TEST_CASE("[Marshalls] Floating point double precision encoding") {
|
||||
uint8_t arr[8];
|
||||
|
||||
// Decimal: 0.333333333333333314829616256247390992939472198486328125
|
||||
// IEEE 754 double-precision binary floating-point format:
|
||||
// sign exponent (11 bits) fraction (52 bits)
|
||||
// 0 01111111101 0101010101010101010101010101010101010101010101010101
|
||||
// Hexadecimal: 0x3FD5555555555555
|
||||
unsigned int actual_size = encode_double(0.33333333333333333, arr);
|
||||
CHECK(actual_size == sizeof(uint64_t));
|
||||
CHECK(arr[0] == 0x55);
|
||||
CHECK(arr[1] == 0x55);
|
||||
CHECK(arr[2] == 0x55);
|
||||
CHECK(arr[3] == 0x55);
|
||||
CHECK(arr[4] == 0x55);
|
||||
CHECK(arr[5] == 0x55);
|
||||
CHECK(arr[6] == 0xd5);
|
||||
CHECK(arr[7] == 0x3f);
|
||||
}
|
||||
|
||||
TEST_CASE("[Marshalls] Floating point half precision decoding") {
|
||||
uint8_t arr[] = { 0x55, 0x35 };
|
||||
|
||||
// See floating point half precision encoding test case for details behind expected values.
|
||||
CHECK(decode_half(arr) == 0.33325195f);
|
||||
}
|
||||
|
||||
TEST_CASE("[Marshalls] Floating point single precision decoding") {
|
||||
uint8_t arr[] = { 0x00, 0x00, 0x20, 0x3e };
|
||||
|
||||
// See floating point encoding test case for details behind expected values
|
||||
CHECK(decode_float(arr) == 0.15625f);
|
||||
}
|
||||
|
||||
TEST_CASE("[Marshalls] Floating point double precision decoding") {
|
||||
uint8_t arr[] = { 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0xd5, 0x3f };
|
||||
|
||||
// See floating point encoding test case for details behind expected values
|
||||
CHECK(decode_double(arr) == 0.33333333333333333);
|
||||
}
|
||||
|
||||
TEST_CASE("[Marshalls] C string encoding") {
|
||||
char cstring[] = "Godot"; // 5 characters
|
||||
uint8_t data[6];
|
||||
|
||||
int actual_size = encode_cstring(cstring, data);
|
||||
CHECK(actual_size == 6);
|
||||
CHECK(data[0] == 'G');
|
||||
CHECK(data[1] == 'o');
|
||||
CHECK(data[2] == 'd');
|
||||
CHECK(data[3] == 'o');
|
||||
CHECK(data[4] == 't');
|
||||
CHECK(data[5] == '\0');
|
||||
}
|
||||
|
||||
TEST_CASE("[Marshalls] NIL Variant encoding") {
|
||||
int r_len;
|
||||
Variant variant;
|
||||
uint8_t buffer[4];
|
||||
|
||||
CHECK(encode_variant(variant, buffer, r_len) == OK);
|
||||
CHECK_MESSAGE(r_len == 4, "Length == 4 bytes for header.");
|
||||
CHECK_MESSAGE(buffer[0] == 0x00, "Variant::NIL");
|
||||
CHECK(buffer[1] == 0x00);
|
||||
CHECK(buffer[2] == 0x00);
|
||||
CHECK(buffer[3] == 0x00);
|
||||
// No value
|
||||
}
|
||||
|
||||
TEST_CASE("[Marshalls] INT 32 bit Variant encoding") {
|
||||
int r_len;
|
||||
Variant variant(0x12345678);
|
||||
uint8_t buffer[8];
|
||||
|
||||
CHECK(encode_variant(variant, buffer, r_len) == OK);
|
||||
CHECK_MESSAGE(r_len == 8, "Length == 4 bytes for header + 4 bytes for `int32_t`.");
|
||||
CHECK_MESSAGE(buffer[0] == 0x02, "Variant::INT");
|
||||
CHECK(buffer[1] == 0x00);
|
||||
CHECK(buffer[2] == 0x00);
|
||||
CHECK(buffer[3] == 0x00);
|
||||
// Check value
|
||||
CHECK(buffer[4] == 0x78);
|
||||
CHECK(buffer[5] == 0x56);
|
||||
CHECK(buffer[6] == 0x34);
|
||||
CHECK(buffer[7] == 0x12);
|
||||
}
|
||||
|
||||
TEST_CASE("[Marshalls] INT 64 bit Variant encoding") {
|
||||
int r_len;
|
||||
Variant variant(uint64_t(0x0f123456789abcdef));
|
||||
uint8_t buffer[12];
|
||||
|
||||
CHECK(encode_variant(variant, buffer, r_len) == OK);
|
||||
CHECK_MESSAGE(r_len == 12, "Length == 4 bytes for header + 8 bytes for `int64_t`.");
|
||||
CHECK_MESSAGE(buffer[0] == 0x02, "Variant::INT");
|
||||
CHECK(buffer[1] == 0x00);
|
||||
CHECK_MESSAGE(buffer[2] == 0x01, "HEADER_DATA_FLAG_64");
|
||||
CHECK(buffer[3] == 0x00);
|
||||
// Check value
|
||||
CHECK(buffer[4] == 0xef);
|
||||
CHECK(buffer[5] == 0xcd);
|
||||
CHECK(buffer[6] == 0xab);
|
||||
CHECK(buffer[7] == 0x89);
|
||||
CHECK(buffer[8] == 0x67);
|
||||
CHECK(buffer[9] == 0x45);
|
||||
CHECK(buffer[10] == 0x23);
|
||||
CHECK(buffer[11] == 0xf1);
|
||||
}
|
||||
|
||||
TEST_CASE("[Marshalls] FLOAT single precision Variant encoding") {
|
||||
int r_len;
|
||||
Variant variant(0.15625f);
|
||||
uint8_t buffer[8];
|
||||
|
||||
CHECK(encode_variant(variant, buffer, r_len) == OK);
|
||||
CHECK_MESSAGE(r_len == 8, "Length == 4 bytes for header + 4 bytes for `float`.");
|
||||
CHECK_MESSAGE(buffer[0] == 0x03, "Variant::FLOAT");
|
||||
CHECK(buffer[1] == 0x00);
|
||||
CHECK(buffer[2] == 0x00);
|
||||
CHECK(buffer[3] == 0x00);
|
||||
// Check value
|
||||
CHECK(buffer[4] == 0x00);
|
||||
CHECK(buffer[5] == 0x00);
|
||||
CHECK(buffer[6] == 0x20);
|
||||
CHECK(buffer[7] == 0x3e);
|
||||
}
|
||||
|
||||
TEST_CASE("[Marshalls] FLOAT double precision Variant encoding") {
|
||||
int r_len;
|
||||
Variant variant(0.33333333333333333);
|
||||
uint8_t buffer[12];
|
||||
|
||||
CHECK(encode_variant(variant, buffer, r_len) == OK);
|
||||
CHECK_MESSAGE(r_len == 12, "Length == 4 bytes for header + 8 bytes for `double`.");
|
||||
CHECK_MESSAGE(buffer[0] == 0x03, "Variant::FLOAT");
|
||||
CHECK(buffer[1] == 0x00);
|
||||
CHECK_MESSAGE(buffer[2] == 0x01, "HEADER_DATA_FLAG_64");
|
||||
CHECK(buffer[3] == 0x00);
|
||||
// Check value
|
||||
CHECK(buffer[4] == 0x55);
|
||||
CHECK(buffer[5] == 0x55);
|
||||
CHECK(buffer[6] == 0x55);
|
||||
CHECK(buffer[7] == 0x55);
|
||||
CHECK(buffer[8] == 0x55);
|
||||
CHECK(buffer[9] == 0x55);
|
||||
CHECK(buffer[10] == 0xd5);
|
||||
CHECK(buffer[11] == 0x3f);
|
||||
}
|
||||
|
||||
TEST_CASE("[Marshalls] Invalid data Variant decoding") {
|
||||
Variant variant;
|
||||
int r_len = 0;
|
||||
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") {
|
||||
Variant variant;
|
||||
int r_len;
|
||||
uint8_t buffer[] = {
|
||||
0x00, 0x00, 0x00, 0x00 // Variant::NIL
|
||||
};
|
||||
|
||||
CHECK(decode_variant(variant, buffer, 4, &r_len) == OK);
|
||||
CHECK(r_len == 4);
|
||||
CHECK(variant == Variant());
|
||||
}
|
||||
|
||||
TEST_CASE("[Marshalls] INT 32 bit Variant decoding") {
|
||||
Variant variant;
|
||||
int r_len;
|
||||
uint8_t buffer[] = {
|
||||
0x02, 0x00, 0x00, 0x00, // Variant::INT
|
||||
0x78, 0x56, 0x34, 0x12 // value
|
||||
};
|
||||
|
||||
CHECK(decode_variant(variant, buffer, 8, &r_len) == OK);
|
||||
CHECK(r_len == 8);
|
||||
CHECK(variant == Variant(0x12345678));
|
||||
}
|
||||
|
||||
TEST_CASE("[Marshalls] INT 64 bit Variant decoding") {
|
||||
Variant variant;
|
||||
int r_len;
|
||||
uint8_t buffer[] = {
|
||||
0x02, 0x00, 0x01, 0x00, // Variant::INT, HEADER_DATA_FLAG_64
|
||||
0xef, 0xcd, 0xab, 0x89, 0x67, 0x45, 0x23, 0xf1 // value
|
||||
};
|
||||
|
||||
CHECK(decode_variant(variant, buffer, 12, &r_len) == OK);
|
||||
CHECK(r_len == 12);
|
||||
CHECK(variant == Variant(uint64_t(0x0f123456789abcdef)));
|
||||
}
|
||||
|
||||
TEST_CASE("[Marshalls] FLOAT single precision Variant decoding") {
|
||||
Variant variant;
|
||||
int r_len;
|
||||
uint8_t buffer[] = {
|
||||
0x03, 0x00, 0x00, 0x00, // Variant::FLOAT
|
||||
0x00, 0x00, 0x20, 0x3e // value
|
||||
};
|
||||
|
||||
CHECK(decode_variant(variant, buffer, 8, &r_len) == OK);
|
||||
CHECK(r_len == 8);
|
||||
CHECK(variant == Variant(0.15625f));
|
||||
}
|
||||
|
||||
TEST_CASE("[Marshalls] FLOAT double precision Variant decoding") {
|
||||
Variant variant;
|
||||
int r_len;
|
||||
uint8_t buffer[] = {
|
||||
0x03, 0x00, 0x01, 0x00, // Variant::FLOAT, HEADER_DATA_FLAG_64
|
||||
0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0xd5, 0x3f // value
|
||||
};
|
||||
|
||||
CHECK(decode_variant(variant, buffer, 12, &r_len) == OK);
|
||||
CHECK(r_len == 12);
|
||||
CHECK(variant == Variant(0.33333333333333333));
|
||||
}
|
||||
|
||||
TEST_CASE("[Marshalls] Typed array encoding") {
|
||||
int r_len;
|
||||
Array array;
|
||||
array.set_typed(Variant::INT, StringName(), Ref<Script>());
|
||||
array.push_back(Variant(uint64_t(0x0f123456789abcdef)));
|
||||
uint8_t buffer[24];
|
||||
|
||||
CHECK(encode_variant(array, buffer, r_len) == OK);
|
||||
CHECK_MESSAGE(r_len == 24, "Length == 4 bytes for header + 4 bytes for array type + 4 bytes for array size + 12 bytes for element.");
|
||||
CHECK_MESSAGE(buffer[0] == 0x1c, "Variant::ARRAY");
|
||||
CHECK(buffer[1] == 0x00);
|
||||
CHECK_MESSAGE(buffer[2] == 0x01, "CONTAINER_TYPE_KIND_BUILTIN");
|
||||
CHECK(buffer[3] == 0x00);
|
||||
// Check array type.
|
||||
CHECK_MESSAGE(buffer[4] == 0x02, "Variant::INT");
|
||||
CHECK(buffer[5] == 0x00);
|
||||
CHECK(buffer[6] == 0x00);
|
||||
CHECK(buffer[7] == 0x00);
|
||||
// Check array size.
|
||||
CHECK(buffer[8] == 0x01);
|
||||
CHECK(buffer[9] == 0x00);
|
||||
CHECK(buffer[10] == 0x00);
|
||||
CHECK(buffer[11] == 0x00);
|
||||
// Check element type.
|
||||
CHECK_MESSAGE(buffer[12] == 0x02, "Variant::INT");
|
||||
CHECK(buffer[13] == 0x00);
|
||||
CHECK_MESSAGE(buffer[14] == 0x01, "HEADER_DATA_FLAG_64");
|
||||
CHECK(buffer[15] == 0x00);
|
||||
// Check element value.
|
||||
CHECK(buffer[16] == 0xef);
|
||||
CHECK(buffer[17] == 0xcd);
|
||||
CHECK(buffer[18] == 0xab);
|
||||
CHECK(buffer[19] == 0x89);
|
||||
CHECK(buffer[20] == 0x67);
|
||||
CHECK(buffer[21] == 0x45);
|
||||
CHECK(buffer[22] == 0x23);
|
||||
CHECK(buffer[23] == 0xf1);
|
||||
}
|
||||
|
||||
TEST_CASE("[Marshalls] Typed array decoding") {
|
||||
Variant variant;
|
||||
int r_len;
|
||||
uint8_t buffer[] = {
|
||||
0x1c, 0x00, 0x01, 0x00, // Variant::ARRAY, CONTAINER_TYPE_KIND_BUILTIN
|
||||
0x02, 0x00, 0x00, 0x00, // Array type (Variant::INT).
|
||||
0x01, 0x00, 0x00, 0x00, // Array size.
|
||||
0x02, 0x00, 0x01, 0x00, // Element type (Variant::INT, HEADER_DATA_FLAG_64).
|
||||
0xef, 0xcd, 0xab, 0x89, 0x67, 0x45, 0x23, 0xf1, // Element value.
|
||||
};
|
||||
|
||||
CHECK(decode_variant(variant, buffer, 24, &r_len) == OK);
|
||||
CHECK(r_len == 24);
|
||||
CHECK(variant.get_type() == Variant::ARRAY);
|
||||
Array array = variant;
|
||||
CHECK(array.get_typed_builtin() == Variant::INT);
|
||||
CHECK(array.size() == 1);
|
||||
CHECK(array[0] == Variant(uint64_t(0x0f123456789abcdef)));
|
||||
}
|
||||
|
||||
TEST_CASE("[Marshalls] Typed dicttionary encoding") {
|
||||
int r_len;
|
||||
Dictionary dictionary;
|
||||
dictionary.set_typed(Variant::INT, StringName(), Ref<Script>(), Variant::INT, StringName(), Ref<Script>());
|
||||
dictionary[Variant(uint64_t(0x0f123456789abcdef))] = Variant(uint64_t(0x0f123456789abcdef));
|
||||
uint8_t buffer[40];
|
||||
|
||||
CHECK(encode_variant(dictionary, buffer, r_len) == OK);
|
||||
CHECK_MESSAGE(r_len == 40, "Length == 4 bytes for header + 8 bytes for dictionary type + 4 bytes for dictionary size + 24 bytes for key-value pair.");
|
||||
CHECK_MESSAGE(buffer[0] == 0x1b, "Variant::DICTIONARY");
|
||||
CHECK(buffer[1] == 0x00);
|
||||
CHECK_MESSAGE(buffer[2] == 0x05, "key: CONTAINER_TYPE_KIND_BUILTIN | value: CONTAINER_TYPE_KIND_BUILTIN");
|
||||
CHECK(buffer[3] == 0x00);
|
||||
// Check dictionary key type.
|
||||
CHECK_MESSAGE(buffer[4] == 0x02, "Variant::INT");
|
||||
CHECK(buffer[5] == 0x00);
|
||||
CHECK(buffer[6] == 0x00);
|
||||
CHECK(buffer[7] == 0x00);
|
||||
// Check dictionary value type.
|
||||
CHECK_MESSAGE(buffer[8] == 0x02, "Variant::INT");
|
||||
CHECK(buffer[9] == 0x00);
|
||||
CHECK(buffer[10] == 0x00);
|
||||
CHECK(buffer[11] == 0x00);
|
||||
// Check dictionary size.
|
||||
CHECK(buffer[12] == 0x01);
|
||||
CHECK(buffer[13] == 0x00);
|
||||
CHECK(buffer[14] == 0x00);
|
||||
CHECK(buffer[15] == 0x00);
|
||||
// Check key type.
|
||||
CHECK_MESSAGE(buffer[16] == 0x02, "Variant::INT");
|
||||
CHECK(buffer[17] == 0x00);
|
||||
CHECK_MESSAGE(buffer[18] == 0x01, "HEADER_DATA_FLAG_64");
|
||||
CHECK(buffer[19] == 0x00);
|
||||
// Check key value.
|
||||
CHECK(buffer[20] == 0xef);
|
||||
CHECK(buffer[21] == 0xcd);
|
||||
CHECK(buffer[22] == 0xab);
|
||||
CHECK(buffer[23] == 0x89);
|
||||
CHECK(buffer[24] == 0x67);
|
||||
CHECK(buffer[25] == 0x45);
|
||||
CHECK(buffer[26] == 0x23);
|
||||
CHECK(buffer[27] == 0xf1);
|
||||
// Check value type.
|
||||
CHECK_MESSAGE(buffer[28] == 0x02, "Variant::INT");
|
||||
CHECK(buffer[29] == 0x00);
|
||||
CHECK_MESSAGE(buffer[30] == 0x01, "HEADER_DATA_FLAG_64");
|
||||
CHECK(buffer[31] == 0x00);
|
||||
// Check value value.
|
||||
CHECK(buffer[32] == 0xef);
|
||||
CHECK(buffer[33] == 0xcd);
|
||||
CHECK(buffer[34] == 0xab);
|
||||
CHECK(buffer[35] == 0x89);
|
||||
CHECK(buffer[36] == 0x67);
|
||||
CHECK(buffer[37] == 0x45);
|
||||
CHECK(buffer[38] == 0x23);
|
||||
CHECK(buffer[39] == 0xf1);
|
||||
}
|
||||
|
||||
TEST_CASE("[Marshalls] Typed dictionary decoding") {
|
||||
Variant variant;
|
||||
int r_len;
|
||||
uint8_t buffer[] = {
|
||||
0x1b, 0x00, 0x05, 0x00, // Variant::DICTIONARY, key: CONTAINER_TYPE_KIND_BUILTIN | value: CONTAINER_TYPE_KIND_BUILTIN
|
||||
0x02, 0x00, 0x00, 0x00, // Dictionary key type (Variant::INT).
|
||||
0x02, 0x00, 0x00, 0x00, // Dictionary value type (Variant::INT).
|
||||
0x01, 0x00, 0x00, 0x00, // Dictionary size.
|
||||
0x02, 0x00, 0x01, 0x00, // Key type (Variant::INT, HEADER_DATA_FLAG_64).
|
||||
0xef, 0xcd, 0xab, 0x89, 0x67, 0x45, 0x23, 0xf1, // Key value.
|
||||
0x02, 0x00, 0x01, 0x00, // Value type (Variant::INT, HEADER_DATA_FLAG_64).
|
||||
0xef, 0xcd, 0xab, 0x89, 0x67, 0x45, 0x23, 0xf1, // Value value.
|
||||
};
|
||||
|
||||
CHECK(decode_variant(variant, buffer, 40, &r_len) == OK);
|
||||
CHECK(r_len == 40);
|
||||
CHECK(variant.get_type() == Variant::DICTIONARY);
|
||||
Dictionary dictionary = variant;
|
||||
CHECK(dictionary.get_typed_key_builtin() == Variant::INT);
|
||||
CHECK(dictionary.get_typed_value_builtin() == Variant::INT);
|
||||
CHECK(dictionary.size() == 1);
|
||||
CHECK(dictionary.has(Variant(uint64_t(0x0f123456789abcdef))));
|
||||
CHECK(dictionary[Variant(uint64_t(0x0f123456789abcdef))] == Variant(uint64_t(0x0f123456789abcdef)));
|
||||
}
|
||||
|
||||
} // namespace TestMarshalls
|
||||
201
tests/core/io/test_packet_peer.h
Normal file
@@ -0,0 +1,201 @@
|
||||
/**************************************************************************/
|
||||
/* test_packet_peer.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/io/packet_peer.h"
|
||||
#include "tests/test_macros.h"
|
||||
|
||||
namespace TestPacketPeer {
|
||||
|
||||
TEST_CASE("[PacketPeer][PacketPeerStream] Encode buffer max size") {
|
||||
Ref<PacketPeerStream> pps;
|
||||
pps.instantiate();
|
||||
|
||||
SUBCASE("Default value") {
|
||||
CHECK_EQ(pps->get_encode_buffer_max_size(), 8 * 1024 * 1024);
|
||||
}
|
||||
|
||||
SUBCASE("Max encode buffer must be at least 1024 bytes") {
|
||||
ERR_PRINT_OFF;
|
||||
pps->set_encode_buffer_max_size(42);
|
||||
ERR_PRINT_ON;
|
||||
|
||||
CHECK_EQ(pps->get_encode_buffer_max_size(), 8 * 1024 * 1024);
|
||||
}
|
||||
|
||||
SUBCASE("Max encode buffer cannot exceed 256 MiB") {
|
||||
ERR_PRINT_OFF;
|
||||
pps->set_encode_buffer_max_size((256 * 1024 * 1024) + 42);
|
||||
ERR_PRINT_ON;
|
||||
|
||||
CHECK_EQ(pps->get_encode_buffer_max_size(), 8 * 1024 * 1024);
|
||||
}
|
||||
|
||||
SUBCASE("Should be next power of two") {
|
||||
pps->set_encode_buffer_max_size(2000);
|
||||
|
||||
CHECK_EQ(pps->get_encode_buffer_max_size(), 2048);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("[PacketPeer][PacketPeerStream] Read a variant from peer") {
|
||||
String godot_rules = "Godot Rules!!!";
|
||||
|
||||
Ref<StreamPeerBuffer> spb;
|
||||
spb.instantiate();
|
||||
spb->put_var(godot_rules);
|
||||
spb->seek(0);
|
||||
|
||||
Ref<PacketPeerStream> pps;
|
||||
pps.instantiate();
|
||||
pps->set_stream_peer(spb);
|
||||
|
||||
Variant value;
|
||||
CHECK_EQ(pps->get_var(value), Error::OK);
|
||||
CHECK_EQ(String(value), godot_rules);
|
||||
}
|
||||
|
||||
TEST_CASE("[PacketPeer][PacketPeerStream] Read a variant from peer fails") {
|
||||
Ref<PacketPeerStream> pps;
|
||||
pps.instantiate();
|
||||
|
||||
Variant value;
|
||||
ERR_PRINT_OFF;
|
||||
CHECK_EQ(pps->get_var(value), Error::ERR_UNCONFIGURED);
|
||||
ERR_PRINT_ON;
|
||||
}
|
||||
|
||||
TEST_CASE("[PacketPeer][PacketPeerStream] Put a variant to peer") {
|
||||
String godot_rules = "Godot Rules!!!";
|
||||
|
||||
Ref<StreamPeerBuffer> spb;
|
||||
spb.instantiate();
|
||||
|
||||
Ref<PacketPeerStream> pps;
|
||||
pps.instantiate();
|
||||
pps->set_stream_peer(spb);
|
||||
|
||||
CHECK_EQ(pps->put_var(godot_rules), Error::OK);
|
||||
|
||||
spb->seek(0);
|
||||
CHECK_EQ(String(spb->get_var()), godot_rules);
|
||||
}
|
||||
|
||||
TEST_CASE("[PacketPeer][PacketPeerStream] Put a variant to peer out of memory failure") {
|
||||
String more_than_1mb = String("*").repeat(1024 + 1);
|
||||
|
||||
Ref<StreamPeerBuffer> spb;
|
||||
spb.instantiate();
|
||||
|
||||
Ref<PacketPeerStream> pps;
|
||||
pps.instantiate();
|
||||
pps->set_stream_peer(spb);
|
||||
pps->set_encode_buffer_max_size(1024);
|
||||
|
||||
ERR_PRINT_OFF;
|
||||
CHECK_EQ(pps->put_var(more_than_1mb), Error::ERR_OUT_OF_MEMORY);
|
||||
ERR_PRINT_ON;
|
||||
}
|
||||
|
||||
TEST_CASE("[PacketPeer][PacketPeerStream] Get packet buffer") {
|
||||
String godot_rules = "Godot Rules!!!";
|
||||
|
||||
Ref<StreamPeerBuffer> spb;
|
||||
spb.instantiate();
|
||||
// First 4 bytes are the length of the string.
|
||||
CharString cs = godot_rules.ascii();
|
||||
Vector<uint8_t> buffer = { (uint8_t)(cs.length() + 1), 0, 0, 0 };
|
||||
buffer.resize_initialized(4 + cs.length() + 1);
|
||||
memcpy(buffer.ptrw() + 4, cs.get_data(), cs.length());
|
||||
spb->set_data_array(buffer);
|
||||
|
||||
Ref<PacketPeerStream> pps;
|
||||
pps.instantiate();
|
||||
pps->set_stream_peer(spb);
|
||||
|
||||
buffer.clear();
|
||||
CHECK_EQ(pps->get_packet_buffer(buffer), Error::OK);
|
||||
|
||||
CHECK_EQ(String(reinterpret_cast<const char *>(buffer.ptr())), godot_rules);
|
||||
}
|
||||
|
||||
TEST_CASE("[PacketPeer][PacketPeerStream] Get packet buffer from an empty peer") {
|
||||
Ref<StreamPeerBuffer> spb;
|
||||
spb.instantiate();
|
||||
|
||||
Ref<PacketPeerStream> pps;
|
||||
pps.instantiate();
|
||||
pps->set_stream_peer(spb);
|
||||
|
||||
Vector<uint8_t> buffer;
|
||||
ERR_PRINT_OFF;
|
||||
CHECK_EQ(pps->get_packet_buffer(buffer), Error::ERR_UNAVAILABLE);
|
||||
ERR_PRINT_ON;
|
||||
CHECK_EQ(buffer.size(), 0);
|
||||
}
|
||||
|
||||
TEST_CASE("[PacketPeer][PacketPeerStream] Put packet buffer") {
|
||||
String godot_rules = "Godot Rules!!!";
|
||||
|
||||
Ref<StreamPeerBuffer> spb;
|
||||
spb.instantiate();
|
||||
|
||||
Ref<PacketPeerStream> pps;
|
||||
pps.instantiate();
|
||||
pps->set_stream_peer(spb);
|
||||
|
||||
CHECK_EQ(pps->put_packet_buffer(godot_rules.to_ascii_buffer()), Error::OK);
|
||||
|
||||
spb->seek(0);
|
||||
CHECK_EQ(spb->get_string(), godot_rules);
|
||||
// First 4 bytes are the length of the string.
|
||||
CharString cs = godot_rules.ascii();
|
||||
Vector<uint8_t> buffer = { (uint8_t)cs.length(), 0, 0, 0 };
|
||||
buffer.resize(4 + cs.length());
|
||||
memcpy(buffer.ptrw() + 4, cs.get_data(), cs.length());
|
||||
CHECK_EQ(spb->get_data_array(), buffer);
|
||||
}
|
||||
|
||||
TEST_CASE("[PacketPeer][PacketPeerStream] Put packet buffer when is empty") {
|
||||
Ref<StreamPeerBuffer> spb;
|
||||
spb.instantiate();
|
||||
|
||||
Ref<PacketPeerStream> pps;
|
||||
pps.instantiate();
|
||||
pps->set_stream_peer(spb);
|
||||
|
||||
Vector<uint8_t> buffer;
|
||||
CHECK_EQ(pps->put_packet_buffer(buffer), Error::OK);
|
||||
|
||||
CHECK_EQ(spb->get_size(), 0);
|
||||
}
|
||||
|
||||
} // namespace TestPacketPeer
|
||||
119
tests/core/io/test_pck_packer.h
Normal file
@@ -0,0 +1,119 @@
|
||||
/**************************************************************************/
|
||||
/* test_pck_packer.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/io/file_access_pack.h"
|
||||
#include "core/io/pck_packer.h"
|
||||
#include "core/os/os.h"
|
||||
|
||||
#include "tests/test_utils.h"
|
||||
#include "thirdparty/doctest/doctest.h"
|
||||
|
||||
namespace TestPCKPacker {
|
||||
|
||||
TEST_CASE("[PCKPacker] Pack an empty PCK file") {
|
||||
PCKPacker pck_packer;
|
||||
const String output_pck_path = TestUtils::get_temp_path("output_empty.pck");
|
||||
CHECK_MESSAGE(
|
||||
pck_packer.pck_start(output_pck_path) == OK,
|
||||
"Starting a PCK file should return an OK error code.");
|
||||
|
||||
CHECK_MESSAGE(
|
||||
pck_packer.flush() == OK,
|
||||
"Flushing the PCK should return an OK error code.");
|
||||
|
||||
Error err;
|
||||
Ref<FileAccess> f = FileAccess::open(output_pck_path, FileAccess::READ, &err);
|
||||
CHECK_MESSAGE(
|
||||
err == OK,
|
||||
"The generated empty PCK file should be opened successfully.");
|
||||
CHECK_MESSAGE(
|
||||
f->get_length() >= 100,
|
||||
"The generated empty PCK file shouldn't be too small (it should have the PCK header).");
|
||||
CHECK_MESSAGE(
|
||||
f->get_length() <= 500,
|
||||
"The generated empty PCK file shouldn't be too large.");
|
||||
}
|
||||
|
||||
TEST_CASE("[PCKPacker] Pack empty with zero alignment invalid") {
|
||||
PCKPacker pck_packer;
|
||||
const String output_pck_path = TestUtils::get_temp_path("output_empty.pck");
|
||||
ERR_PRINT_OFF;
|
||||
CHECK_MESSAGE(pck_packer.pck_start(output_pck_path, 0) != OK, "PCK with zero alignment should fail.");
|
||||
ERR_PRINT_ON;
|
||||
}
|
||||
|
||||
TEST_CASE("[PCKPacker] Pack empty with invalid key") {
|
||||
PCKPacker pck_packer;
|
||||
const String output_pck_path = TestUtils::get_temp_path("output_empty.pck");
|
||||
ERR_PRINT_OFF;
|
||||
CHECK_MESSAGE(pck_packer.pck_start(output_pck_path, 32, "") != OK, "PCK with invalid key should fail.");
|
||||
ERR_PRINT_ON;
|
||||
}
|
||||
|
||||
TEST_CASE("[PCKPacker] Pack a PCK file with some files and directories") {
|
||||
PCKPacker pck_packer;
|
||||
const String output_pck_path = TestUtils::get_temp_path("output_with_files.pck");
|
||||
CHECK_MESSAGE(
|
||||
pck_packer.pck_start(output_pck_path) == OK,
|
||||
"Starting a PCK file should return an OK error code.");
|
||||
|
||||
const String base_dir = OS::get_singleton()->get_executable_path().get_base_dir();
|
||||
|
||||
CHECK_MESSAGE(
|
||||
pck_packer.add_file("version.py", base_dir.path_join("../version.py"), "version.py") == OK,
|
||||
"Adding a file to the PCK should return an OK error code.");
|
||||
CHECK_MESSAGE(
|
||||
pck_packer.add_file("some/directories with spaces/to/create/icon.png", base_dir.path_join("../icon.png")) == OK,
|
||||
"Adding a file to a new subdirectory in the PCK should return an OK error code.");
|
||||
CHECK_MESSAGE(
|
||||
pck_packer.add_file("some/directories with spaces/to/create/icon.svg", base_dir.path_join("../icon.svg")) == OK,
|
||||
"Adding a file to an existing subdirectory in the PCK should return an OK error code.");
|
||||
CHECK_MESSAGE(
|
||||
pck_packer.add_file("some/directories with spaces/to/create/icon.png", base_dir.path_join("../logo.png")) == OK,
|
||||
"Overriding a non-flushed file to an existing subdirectory in the PCK should return an OK error code.");
|
||||
CHECK_MESSAGE(
|
||||
pck_packer.flush() == OK,
|
||||
"Flushing the PCK should return an OK error code.");
|
||||
|
||||
Error err;
|
||||
Ref<FileAccess> f = FileAccess::open(output_pck_path, FileAccess::READ, &err);
|
||||
CHECK_MESSAGE(
|
||||
err == OK,
|
||||
"The generated non-empty PCK file should be opened successfully.");
|
||||
CHECK_MESSAGE(
|
||||
f->get_length() >= 18000,
|
||||
"The generated non-empty PCK file should be large enough to actually hold the contents specified above.");
|
||||
CHECK_MESSAGE(
|
||||
f->get_length() <= 27000,
|
||||
"The generated non-empty PCK file shouldn't be too large.");
|
||||
}
|
||||
} // namespace TestPCKPacker
|
||||
631
tests/core/io/test_resource.h
Normal file
@@ -0,0 +1,631 @@
|
||||
/**************************************************************************/
|
||||
/* test_resource.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/io/resource.h"
|
||||
#include "core/io/resource_loader.h"
|
||||
#include "core/io/resource_saver.h"
|
||||
#include "core/os/os.h"
|
||||
#include "scene/main/node.h"
|
||||
|
||||
#include "thirdparty/doctest/doctest.h"
|
||||
|
||||
#include "tests/test_macros.h"
|
||||
|
||||
#include <functional>
|
||||
|
||||
namespace TestResource {
|
||||
|
||||
enum TestDuplicateMode {
|
||||
TEST_MODE_RESOURCE_DUPLICATE_SHALLOW,
|
||||
TEST_MODE_RESOURCE_DUPLICATE_DEEP,
|
||||
TEST_MODE_RESOURCE_DUPLICATE_DEEP_WITH_MODE,
|
||||
TEST_MODE_RESOURCE_DUPLICATE_FOR_LOCAL_SCENE,
|
||||
TEST_MODE_VARIANT_DUPLICATE_SHALLOW,
|
||||
TEST_MODE_VARIANT_DUPLICATE_DEEP,
|
||||
TEST_MODE_VARIANT_DUPLICATE_DEEP_WITH_MODE,
|
||||
};
|
||||
|
||||
class DuplicateGuineaPigData : public Object {
|
||||
GDSOFTCLASS(DuplicateGuineaPigData, Object)
|
||||
|
||||
public:
|
||||
const Variant SENTINEL_1 = "A";
|
||||
const Variant SENTINEL_2 = 645;
|
||||
const Variant SENTINEL_3 = StringName("X");
|
||||
const Variant SENTINEL_4 = true;
|
||||
|
||||
Ref<Resource> SUBRES_1 = memnew(Resource);
|
||||
Ref<Resource> SUBRES_2 = memnew(Resource);
|
||||
Ref<Resource> SUBRES_3 = memnew(Resource);
|
||||
Ref<Resource> SUBRES_SL_1 = memnew(Resource);
|
||||
Ref<Resource> SUBRES_SL_2 = memnew(Resource);
|
||||
Ref<Resource> SUBRES_SL_3 = memnew(Resource);
|
||||
|
||||
Variant obj; // Variant helps with lifetime so duplicates pointing to the same don't try to double-free it.
|
||||
Array arr;
|
||||
Dictionary dict;
|
||||
Variant packed; // A PackedByteArray, but using Variant to be able to tell if the array is shared or not.
|
||||
Ref<Resource> subres;
|
||||
Ref<Resource> subres_sl;
|
||||
|
||||
void set_defaults() {
|
||||
SUBRES_1->set_name("juan");
|
||||
SUBRES_2->set_name("you");
|
||||
SUBRES_3->set_name("tree");
|
||||
SUBRES_SL_1->set_name("maybe_scene_local");
|
||||
SUBRES_SL_2->set_name("perhaps_local_to_scene");
|
||||
SUBRES_SL_3->set_name("sometimes_locality_scenial");
|
||||
|
||||
// To try some cases of internal and external.
|
||||
SUBRES_1->set_path_cache("");
|
||||
SUBRES_2->set_path_cache("local://hehe");
|
||||
SUBRES_3->set_path_cache("res://some.tscn::1");
|
||||
DEV_ASSERT(SUBRES_1->is_built_in());
|
||||
DEV_ASSERT(SUBRES_2->is_built_in());
|
||||
DEV_ASSERT(SUBRES_3->is_built_in());
|
||||
SUBRES_SL_1->set_path_cache("res://thing.scn");
|
||||
SUBRES_SL_2->set_path_cache("C:/not/really/possible/but/still/external");
|
||||
SUBRES_SL_3->set_path_cache("/this/neither");
|
||||
DEV_ASSERT(!SUBRES_SL_1->is_built_in());
|
||||
DEV_ASSERT(!SUBRES_SL_2->is_built_in());
|
||||
DEV_ASSERT(!SUBRES_SL_3->is_built_in());
|
||||
|
||||
obj = memnew(Object);
|
||||
|
||||
// Construct enough cases to test deep recursion involving resources;
|
||||
// we mix some primitive values with recurses nested in different ways,
|
||||
// acting as array values and dictionary keys and values, some of those
|
||||
// being marked as scene-local when for subcases where scene-local is relevant.
|
||||
|
||||
arr.push_back(SENTINEL_1);
|
||||
arr.push_back(SUBRES_1);
|
||||
arr.push_back(SUBRES_SL_1);
|
||||
{
|
||||
Dictionary d;
|
||||
d[SENTINEL_2] = SENTINEL_3;
|
||||
d[SENTINEL_4] = SUBRES_2;
|
||||
d[SUBRES_3] = SUBRES_SL_2;
|
||||
d[SUBRES_SL_3] = SUBRES_1;
|
||||
arr.push_back(d);
|
||||
}
|
||||
|
||||
dict[SENTINEL_4] = SENTINEL_1;
|
||||
dict[SENTINEL_2] = SUBRES_2;
|
||||
dict[SUBRES_3] = SUBRES_SL_1;
|
||||
dict[SUBRES_SL_2] = SUBRES_1;
|
||||
{
|
||||
Array a;
|
||||
a.push_back(SENTINEL_3);
|
||||
a.push_back(SUBRES_2);
|
||||
a.push_back(SUBRES_SL_3);
|
||||
dict[SENTINEL_4] = a;
|
||||
}
|
||||
|
||||
packed = PackedByteArray{ 0xaa, 0xbb, 0xcc };
|
||||
|
||||
subres = SUBRES_1;
|
||||
subres_sl = SUBRES_SL_1;
|
||||
}
|
||||
|
||||
void verify_empty() const {
|
||||
CHECK(obj.get_type() == Variant::NIL);
|
||||
CHECK(arr.size() == 0);
|
||||
CHECK(dict.size() == 0);
|
||||
CHECK(packed.get_type() == Variant::NIL);
|
||||
CHECK(subres.is_null());
|
||||
}
|
||||
|
||||
void verify_duplication(const DuplicateGuineaPigData *p_orig, uint32_t p_property_usage, TestDuplicateMode p_test_mode, ResourceDeepDuplicateMode p_deep_mode) const {
|
||||
if (!(p_property_usage & PROPERTY_USAGE_STORAGE)) {
|
||||
verify_empty();
|
||||
return;
|
||||
}
|
||||
|
||||
// To see if each resource involved is copied once at most,
|
||||
// and then the reference to the duplicate reused.
|
||||
HashMap<Resource *, Resource *> duplicates;
|
||||
|
||||
auto _verify_resource = [&](const Ref<Resource> &p_dupe_res, const Ref<Resource> &p_orig_res, bool p_is_property = false) {
|
||||
bool expect_true_copy = (p_test_mode == TEST_MODE_RESOURCE_DUPLICATE_DEEP && p_orig_res->is_built_in()) ||
|
||||
(p_test_mode == TEST_MODE_RESOURCE_DUPLICATE_DEEP_WITH_MODE && p_deep_mode == RESOURCE_DEEP_DUPLICATE_INTERNAL && p_orig_res->is_built_in()) ||
|
||||
(p_test_mode == TEST_MODE_RESOURCE_DUPLICATE_DEEP_WITH_MODE && p_deep_mode == RESOURCE_DEEP_DUPLICATE_ALL) ||
|
||||
(p_test_mode == TEST_MODE_RESOURCE_DUPLICATE_FOR_LOCAL_SCENE && p_orig_res->is_local_to_scene()) ||
|
||||
(p_test_mode == TEST_MODE_VARIANT_DUPLICATE_DEEP_WITH_MODE && p_deep_mode == RESOURCE_DEEP_DUPLICATE_INTERNAL && p_orig_res->is_built_in()) ||
|
||||
(p_test_mode == TEST_MODE_VARIANT_DUPLICATE_DEEP_WITH_MODE && p_deep_mode == RESOURCE_DEEP_DUPLICATE_ALL);
|
||||
|
||||
if (expect_true_copy) {
|
||||
if (p_deep_mode == RESOURCE_DEEP_DUPLICATE_NONE) {
|
||||
expect_true_copy = false;
|
||||
} else if (p_deep_mode == RESOURCE_DEEP_DUPLICATE_INTERNAL) {
|
||||
expect_true_copy = p_orig_res->is_built_in();
|
||||
}
|
||||
}
|
||||
|
||||
if (p_is_property) {
|
||||
if ((p_property_usage & PROPERTY_USAGE_ALWAYS_DUPLICATE)) {
|
||||
expect_true_copy = true;
|
||||
} else if ((p_property_usage & PROPERTY_USAGE_NEVER_DUPLICATE)) {
|
||||
expect_true_copy = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (expect_true_copy) {
|
||||
CHECK(p_dupe_res != p_orig_res);
|
||||
CHECK(p_dupe_res->get_name() == p_orig_res->get_name());
|
||||
if (duplicates.has(p_orig_res.ptr())) {
|
||||
CHECK(duplicates[p_orig_res.ptr()] == p_dupe_res.ptr());
|
||||
} else {
|
||||
duplicates[p_orig_res.ptr()] = p_dupe_res.ptr();
|
||||
}
|
||||
} else {
|
||||
CHECK(p_dupe_res == p_orig_res);
|
||||
}
|
||||
};
|
||||
|
||||
std::function<void(const Variant &p_a, const Variant &p_b)> _verify_deep_copied_variants = [&](const Variant &p_a, const Variant &p_b) {
|
||||
CHECK(p_a.get_type() == p_b.get_type());
|
||||
const Ref<Resource> &res_a = p_a;
|
||||
const Ref<Resource> &res_b = p_b;
|
||||
if (res_a.is_valid()) {
|
||||
_verify_resource(res_a, res_b);
|
||||
} else if (p_a.get_type() == Variant::ARRAY) {
|
||||
const Array &arr_a = p_a;
|
||||
const Array &arr_b = p_b;
|
||||
CHECK(!arr_a.is_same_instance(arr_b));
|
||||
CHECK(arr_a.size() == arr_b.size());
|
||||
for (int i = 0; i < arr_a.size(); i++) {
|
||||
_verify_deep_copied_variants(arr_a[i], arr_b[i]);
|
||||
}
|
||||
} else if (p_a.get_type() == Variant::DICTIONARY) {
|
||||
const Dictionary &dict_a = p_a;
|
||||
const Dictionary &dict_b = p_b;
|
||||
CHECK(!dict_a.is_same_instance(dict_b));
|
||||
CHECK(dict_a.size() == dict_b.size());
|
||||
for (int i = 0; i < dict_a.size(); i++) {
|
||||
_verify_deep_copied_variants(dict_a.get_key_at_index(i), dict_b.get_key_at_index(i));
|
||||
_verify_deep_copied_variants(dict_a.get_value_at_index(i), dict_b.get_value_at_index(i));
|
||||
}
|
||||
} else {
|
||||
CHECK(p_a == p_b);
|
||||
}
|
||||
};
|
||||
|
||||
CHECK(this != p_orig);
|
||||
|
||||
CHECK((Object *)obj == (Object *)p_orig->obj);
|
||||
|
||||
bool expect_true_copy = p_test_mode == TEST_MODE_RESOURCE_DUPLICATE_DEEP ||
|
||||
p_test_mode == TEST_MODE_RESOURCE_DUPLICATE_DEEP_WITH_MODE ||
|
||||
p_test_mode == TEST_MODE_RESOURCE_DUPLICATE_FOR_LOCAL_SCENE ||
|
||||
p_test_mode == TEST_MODE_VARIANT_DUPLICATE_DEEP ||
|
||||
p_test_mode == TEST_MODE_VARIANT_DUPLICATE_DEEP_WITH_MODE;
|
||||
if (expect_true_copy) {
|
||||
_verify_deep_copied_variants(arr, p_orig->arr);
|
||||
_verify_deep_copied_variants(dict, p_orig->dict);
|
||||
CHECK(!packed.identity_compare(p_orig->packed));
|
||||
} else {
|
||||
CHECK(arr.is_same_instance(p_orig->arr));
|
||||
CHECK(dict.is_same_instance(p_orig->dict));
|
||||
CHECK(packed.identity_compare(p_orig->packed));
|
||||
}
|
||||
|
||||
_verify_resource(subres, p_orig->subres, true);
|
||||
_verify_resource(subres_sl, p_orig->subres_sl, true);
|
||||
}
|
||||
|
||||
void enable_scene_local_subresources() {
|
||||
SUBRES_SL_1->set_local_to_scene(true);
|
||||
SUBRES_SL_2->set_local_to_scene(true);
|
||||
SUBRES_SL_3->set_local_to_scene(true);
|
||||
}
|
||||
|
||||
virtual ~DuplicateGuineaPigData() {
|
||||
Object *obj_ptr = obj.get_validated_object();
|
||||
if (obj_ptr) {
|
||||
memdelete(obj_ptr);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
#define DEFINE_DUPLICATE_GUINEA_PIG(m_class_name, m_property_usage) \
|
||||
class m_class_name : public Resource { \
|
||||
GDCLASS(m_class_name, Resource) \
|
||||
\
|
||||
DuplicateGuineaPigData data; \
|
||||
\
|
||||
public: \
|
||||
void set_obj(Object *p_obj) { \
|
||||
data.obj = p_obj; \
|
||||
} \
|
||||
Object *get_obj() const { \
|
||||
return data.obj; \
|
||||
} \
|
||||
\
|
||||
void set_arr(const Array &p_arr) { \
|
||||
data.arr = p_arr; \
|
||||
} \
|
||||
Array get_arr() const { \
|
||||
return data.arr; \
|
||||
} \
|
||||
\
|
||||
void set_dict(const Dictionary &p_dict) { \
|
||||
data.dict = p_dict; \
|
||||
} \
|
||||
Dictionary get_dict() const { \
|
||||
return data.dict; \
|
||||
} \
|
||||
\
|
||||
void set_packed(const Variant &p_packed) { \
|
||||
data.packed = p_packed; \
|
||||
} \
|
||||
Variant get_packed() const { \
|
||||
return data.packed; \
|
||||
} \
|
||||
\
|
||||
void set_subres(const Ref<Resource> &p_subres) { \
|
||||
data.subres = p_subres; \
|
||||
} \
|
||||
Ref<Resource> get_subres() const { \
|
||||
return data.subres; \
|
||||
} \
|
||||
\
|
||||
void set_subres_sl(const Ref<Resource> &p_subres) { \
|
||||
data.subres_sl = p_subres; \
|
||||
} \
|
||||
Ref<Resource> get_subres_sl() const { \
|
||||
return data.subres_sl; \
|
||||
} \
|
||||
\
|
||||
void set_defaults() { \
|
||||
data.set_defaults(); \
|
||||
} \
|
||||
\
|
||||
Object *get_data() { \
|
||||
return &data; \
|
||||
} \
|
||||
\
|
||||
void verify_duplication(const Ref<Resource> &p_orig, int p_test_mode, int p_deep_mode) const { \
|
||||
const DuplicateGuineaPigData *orig_data = Object::cast_to<DuplicateGuineaPigData>(p_orig->call("get_data")); \
|
||||
data.verify_duplication(orig_data, m_property_usage, (TestDuplicateMode)p_test_mode, (ResourceDeepDuplicateMode)p_deep_mode); \
|
||||
} \
|
||||
\
|
||||
protected: \
|
||||
static void _bind_methods() { \
|
||||
ClassDB::bind_method(D_METHOD("set_obj", "obj"), &m_class_name::set_obj); \
|
||||
ClassDB::bind_method(D_METHOD("get_obj"), &m_class_name::get_obj); \
|
||||
\
|
||||
ClassDB::bind_method(D_METHOD("set_arr", "arr"), &m_class_name::set_arr); \
|
||||
ClassDB::bind_method(D_METHOD("get_arr"), &m_class_name::get_arr); \
|
||||
\
|
||||
ClassDB::bind_method(D_METHOD("set_dict", "dict"), &m_class_name::set_dict); \
|
||||
ClassDB::bind_method(D_METHOD("get_dict"), &m_class_name::get_dict); \
|
||||
\
|
||||
ClassDB::bind_method(D_METHOD("set_packed", "packed"), &m_class_name::set_packed); \
|
||||
ClassDB::bind_method(D_METHOD("get_packed"), &m_class_name::get_packed); \
|
||||
\
|
||||
ClassDB::bind_method(D_METHOD("set_subres", "subres"), &m_class_name::set_subres); \
|
||||
ClassDB::bind_method(D_METHOD("get_subres"), &m_class_name::get_subres); \
|
||||
\
|
||||
ClassDB::bind_method(D_METHOD("set_subres_sl", "subres"), &m_class_name::set_subres_sl); \
|
||||
ClassDB::bind_method(D_METHOD("get_subres_sl"), &m_class_name::get_subres_sl); \
|
||||
\
|
||||
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "obj", PROPERTY_HINT_NONE, "", m_property_usage), "set_obj", "get_obj"); \
|
||||
ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "arr", PROPERTY_HINT_NONE, "", m_property_usage), "set_arr", "get_arr"); \
|
||||
ADD_PROPERTY(PropertyInfo(Variant::DICTIONARY, "dict", PROPERTY_HINT_NONE, "", m_property_usage), "set_dict", "get_dict"); \
|
||||
ADD_PROPERTY(PropertyInfo(Variant::PACKED_BYTE_ARRAY, "packed", PROPERTY_HINT_NONE, "", m_property_usage), "set_packed", "get_packed"); \
|
||||
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "subres", PROPERTY_HINT_NONE, "", m_property_usage), "set_subres", "get_subres"); \
|
||||
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "subres_sl", PROPERTY_HINT_NONE, "", m_property_usage), "set_subres_sl", "get_subres_sl"); \
|
||||
\
|
||||
ClassDB::bind_method(D_METHOD("set_defaults"), &m_class_name::set_defaults); \
|
||||
ClassDB::bind_method(D_METHOD("get_data"), &m_class_name::get_data); \
|
||||
ClassDB::bind_method(D_METHOD("verify_duplication", "orig", "test_mode", "deep_mode"), &m_class_name::verify_duplication); \
|
||||
} \
|
||||
\
|
||||
public: \
|
||||
static m_class_name *register_and_instantiate() { \
|
||||
static bool registered = false; \
|
||||
if (!registered) { \
|
||||
GDREGISTER_CLASS(m_class_name); \
|
||||
registered = true; \
|
||||
} \
|
||||
return memnew(m_class_name); \
|
||||
} \
|
||||
};
|
||||
|
||||
DEFINE_DUPLICATE_GUINEA_PIG(DuplicateGuineaPig_None, PROPERTY_USAGE_NONE)
|
||||
DEFINE_DUPLICATE_GUINEA_PIG(DuplicateGuineaPig_Always, PROPERTY_USAGE_ALWAYS_DUPLICATE)
|
||||
DEFINE_DUPLICATE_GUINEA_PIG(DuplicateGuineaPig_Storage, PROPERTY_USAGE_STORAGE)
|
||||
DEFINE_DUPLICATE_GUINEA_PIG(DuplicateGuineaPig_Storage_Always, (PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_ALWAYS_DUPLICATE))
|
||||
DEFINE_DUPLICATE_GUINEA_PIG(DuplicateGuineaPig_Storage_Never, (PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_NEVER_DUPLICATE))
|
||||
|
||||
TEST_CASE("[Resource] Duplication") {
|
||||
auto _run_test = [](
|
||||
TestDuplicateMode p_test_mode,
|
||||
ResourceDeepDuplicateMode p_deep_mode,
|
||||
Ref<Resource> (*p_duplicate_fn)(const Ref<Resource> &)) -> void {
|
||||
LocalVector<Ref<Resource>> resources = {
|
||||
DuplicateGuineaPig_None::register_and_instantiate(),
|
||||
DuplicateGuineaPig_Always::register_and_instantiate(),
|
||||
DuplicateGuineaPig_Storage::register_and_instantiate(),
|
||||
DuplicateGuineaPig_Storage_Always::register_and_instantiate(),
|
||||
DuplicateGuineaPig_Storage_Never::register_and_instantiate(),
|
||||
};
|
||||
|
||||
for (const Ref<Resource> &orig : resources) {
|
||||
INFO(std::string(String(orig->get_class_name()).utf8().get_data()));
|
||||
|
||||
orig->call("set_defaults");
|
||||
const Ref<Resource> &dupe = p_duplicate_fn(orig);
|
||||
dupe->call("verify_duplication", orig, p_test_mode, p_deep_mode);
|
||||
}
|
||||
};
|
||||
|
||||
SUBCASE("Resource::duplicate(), shallow") {
|
||||
_run_test(
|
||||
TEST_MODE_RESOURCE_DUPLICATE_SHALLOW,
|
||||
RESOURCE_DEEP_DUPLICATE_MAX,
|
||||
[](const Ref<Resource> &p_res) -> Ref<Resource> {
|
||||
return p_res->duplicate(false);
|
||||
});
|
||||
}
|
||||
|
||||
SUBCASE("Resource::duplicate(), deep") {
|
||||
_run_test(
|
||||
TEST_MODE_RESOURCE_DUPLICATE_DEEP,
|
||||
RESOURCE_DEEP_DUPLICATE_MAX,
|
||||
[](const Ref<Resource> &p_res) -> Ref<Resource> {
|
||||
return p_res->duplicate(true);
|
||||
});
|
||||
}
|
||||
|
||||
SUBCASE("Resource::duplicate_deep()") {
|
||||
static int deep_mode = 0;
|
||||
for (deep_mode = 0; deep_mode < RESOURCE_DEEP_DUPLICATE_MAX; deep_mode++) {
|
||||
_run_test(
|
||||
TEST_MODE_RESOURCE_DUPLICATE_DEEP_WITH_MODE,
|
||||
(ResourceDeepDuplicateMode)deep_mode,
|
||||
[](const Ref<Resource> &p_res) -> Ref<Resource> {
|
||||
return p_res->duplicate_deep((ResourceDeepDuplicateMode)deep_mode);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
SUBCASE("Resource::duplicate_for_local_scene()") {
|
||||
static int mark_main_as_local = 0;
|
||||
static int mark_some_subs_as_local = 0;
|
||||
for (mark_main_as_local = 0; mark_main_as_local < 2; ++mark_main_as_local) { // Whether main is local-to-scene shouldn't matter.
|
||||
for (mark_some_subs_as_local = 0; mark_some_subs_as_local < 2; ++mark_some_subs_as_local) {
|
||||
_run_test(
|
||||
TEST_MODE_RESOURCE_DUPLICATE_FOR_LOCAL_SCENE,
|
||||
RESOURCE_DEEP_DUPLICATE_MAX,
|
||||
[](const Ref<Resource> &p_res) -> Ref<Resource> {
|
||||
if (mark_main_as_local) {
|
||||
p_res->set_local_to_scene(true);
|
||||
}
|
||||
if (mark_some_subs_as_local) {
|
||||
Object::cast_to<DuplicateGuineaPigData>(p_res->call("get_data"))->enable_scene_local_subresources();
|
||||
}
|
||||
HashMap<Ref<Resource>, Ref<Resource>> remap_cache;
|
||||
Node fake_scene;
|
||||
return p_res->duplicate_for_local_scene(&fake_scene, remap_cache);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SUBCASE("Variant::duplicate(), shallow") {
|
||||
_run_test(
|
||||
TEST_MODE_VARIANT_DUPLICATE_SHALLOW,
|
||||
RESOURCE_DEEP_DUPLICATE_MAX,
|
||||
[](const Ref<Resource> &p_res) -> Ref<Resource> {
|
||||
return Variant(p_res).duplicate(false);
|
||||
});
|
||||
}
|
||||
|
||||
SUBCASE("Variant::duplicate(), deep") {
|
||||
_run_test(
|
||||
TEST_MODE_VARIANT_DUPLICATE_DEEP,
|
||||
RESOURCE_DEEP_DUPLICATE_MAX,
|
||||
[](const Ref<Resource> &p_res) -> Ref<Resource> {
|
||||
return Variant(p_res).duplicate(true);
|
||||
});
|
||||
}
|
||||
|
||||
SUBCASE("Variant::duplicate_deep()") {
|
||||
static int deep_mode = 0;
|
||||
for (deep_mode = 0; deep_mode < RESOURCE_DEEP_DUPLICATE_MAX; deep_mode++) {
|
||||
_run_test(
|
||||
TEST_MODE_VARIANT_DUPLICATE_DEEP_WITH_MODE,
|
||||
(ResourceDeepDuplicateMode)deep_mode,
|
||||
[](const Ref<Resource> &p_res) -> Ref<Resource> {
|
||||
return Variant(p_res).duplicate_deep((ResourceDeepDuplicateMode)deep_mode);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
SUBCASE("Via Variant, resource not being the root") {
|
||||
// Variant controls the deep copy, recursing until resources are found, and then
|
||||
// it's Resource who controls the deep copy from it onwards.
|
||||
// Therefore, we have to test if Variant is able to track unique duplicates across
|
||||
// multiple times Resource takes over.
|
||||
// Since the other test cases already prove Resource's mechanism to have at most
|
||||
// one duplicate per resource involved, the test for Variant is simple.
|
||||
|
||||
Ref<Resource> res;
|
||||
res.instantiate();
|
||||
res->set_name("risi");
|
||||
Array a;
|
||||
a.push_back(res);
|
||||
{
|
||||
Dictionary d;
|
||||
d[res] = res;
|
||||
a.push_back(d);
|
||||
}
|
||||
|
||||
Array dupe_a;
|
||||
Ref<Resource> dupe_res;
|
||||
|
||||
SUBCASE("Variant::duplicate(), shallow") {
|
||||
dupe_a = Variant(a).duplicate(false);
|
||||
// Ensure it's referencing the original.
|
||||
dupe_res = dupe_a[0];
|
||||
CHECK(dupe_res == res);
|
||||
}
|
||||
SUBCASE("Variant::duplicate(), deep") {
|
||||
dupe_a = Variant(a).duplicate(true);
|
||||
// Ensure it's referencing the original.
|
||||
dupe_res = dupe_a[0];
|
||||
CHECK(dupe_res == res);
|
||||
}
|
||||
SUBCASE("Variant::duplicate_deep(), no resources") {
|
||||
dupe_a = Variant(a).duplicate_deep(RESOURCE_DEEP_DUPLICATE_NONE);
|
||||
// Ensure it's referencing the original.
|
||||
dupe_res = dupe_a[0];
|
||||
CHECK(dupe_res == res);
|
||||
}
|
||||
SUBCASE("Variant::duplicate_deep(), with resources") {
|
||||
dupe_a = Variant(a).duplicate_deep(RESOURCE_DEEP_DUPLICATE_ALL);
|
||||
// Ensure it's a copy.
|
||||
dupe_res = dupe_a[0];
|
||||
CHECK(dupe_res != res);
|
||||
CHECK(dupe_res->get_name() == "risi");
|
||||
|
||||
// Ensure the map is already gone so we get new instances.
|
||||
Array dupe_a_2 = Variant(a).duplicate_deep(RESOURCE_DEEP_DUPLICATE_ALL);
|
||||
CHECK(dupe_a_2[0] != dupe_a[0]);
|
||||
}
|
||||
|
||||
// Ensure all the usages are of the same resource.
|
||||
CHECK(((Dictionary)dupe_a[1]).get_key_at_index(0) == dupe_res);
|
||||
CHECK(((Dictionary)dupe_a[1]).get_value_at_index(0) == dupe_res);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("[Resource] Saving and loading") {
|
||||
Ref<Resource> resource = memnew(Resource);
|
||||
resource->set_name("Hello world");
|
||||
resource->set_meta("ExampleMetadata", Vector2i(40, 80));
|
||||
resource->set_meta("string", "The\nstring\nwith\nunnecessary\nline\n\t\\\nbreaks");
|
||||
Ref<Resource> child_resource = memnew(Resource);
|
||||
child_resource->set_name("I'm a child resource");
|
||||
resource->set_meta("other_resource", child_resource);
|
||||
const String save_path_binary = TestUtils::get_temp_path("resource.res");
|
||||
const String save_path_text = TestUtils::get_temp_path("resource.tres");
|
||||
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(
|
||||
loaded_resource_binary->get_name() == "Hello world",
|
||||
"The loaded resource name should be equal to the expected value.");
|
||||
CHECK_MESSAGE(
|
||||
loaded_resource_binary->get_meta("ExampleMetadata") == Vector2i(40, 80),
|
||||
"The loaded resource metadata should be equal to the expected value.");
|
||||
CHECK_MESSAGE(
|
||||
loaded_resource_binary->get_meta("string") == "The\nstring\nwith\nunnecessary\nline\n\t\\\nbreaks",
|
||||
"The loaded resource metadata should be equal to the expected value.");
|
||||
const Ref<Resource> &loaded_child_resource_binary = loaded_resource_binary->get_meta("other_resource");
|
||||
CHECK_MESSAGE(
|
||||
loaded_child_resource_binary->get_name() == "I'm a child resource",
|
||||
"The loaded child resource name should be equal to the expected value.");
|
||||
|
||||
const Ref<Resource> &loaded_resource_text = ResourceLoader::load(save_path_text);
|
||||
CHECK_MESSAGE(
|
||||
loaded_resource_text->get_name() == "Hello world",
|
||||
"The loaded resource name should be equal to the expected value.");
|
||||
CHECK_MESSAGE(
|
||||
loaded_resource_text->get_meta("ExampleMetadata") == Vector2i(40, 80),
|
||||
"The loaded resource metadata should be equal to the expected value.");
|
||||
CHECK_MESSAGE(
|
||||
loaded_resource_text->get_meta("string") == "The\nstring\nwith\nunnecessary\nline\n\t\\\nbreaks",
|
||||
"The loaded resource metadata should be equal to the expected value.");
|
||||
const Ref<Resource> &loaded_child_resource_text = loaded_resource_text->get_meta("other_resource");
|
||||
CHECK_MESSAGE(
|
||||
loaded_child_resource_text->get_name() == "I'm a child resource",
|
||||
"The loaded child resource name should be equal to the expected value.");
|
||||
}
|
||||
|
||||
TEST_CASE("[Resource] Breaking circular references on save") {
|
||||
Ref<Resource> resource_a = memnew(Resource);
|
||||
resource_a->set_name("A");
|
||||
Ref<Resource> resource_b = memnew(Resource);
|
||||
resource_b->set_name("B");
|
||||
Ref<Resource> resource_c = memnew(Resource);
|
||||
resource_c->set_name("C");
|
||||
resource_a->set_meta("next", resource_b);
|
||||
resource_b->set_meta("next", resource_c);
|
||||
resource_c->set_meta("next", resource_b);
|
||||
|
||||
const String save_path_binary = TestUtils::get_temp_path("resource.res");
|
||||
const String save_path_text = TestUtils::get_temp_path("resource.tres");
|
||||
ResourceSaver::save(resource_a, save_path_binary);
|
||||
// Suppress expected errors caused by the resources above being uncached.
|
||||
ERR_PRINT_OFF;
|
||||
ResourceSaver::save(resource_a, save_path_text);
|
||||
|
||||
const Ref<Resource> &loaded_resource_a_binary = ResourceLoader::load(save_path_binary);
|
||||
ERR_PRINT_ON;
|
||||
CHECK_MESSAGE(
|
||||
loaded_resource_a_binary->get_name() == "A",
|
||||
"The loaded resource name should be equal to the expected value.");
|
||||
const Ref<Resource> &loaded_resource_b_binary = loaded_resource_a_binary->get_meta("next");
|
||||
CHECK_MESSAGE(
|
||||
loaded_resource_b_binary->get_name() == "B",
|
||||
"The loaded child resource name should be equal to the expected value.");
|
||||
const Ref<Resource> &loaded_resource_c_binary = loaded_resource_b_binary->get_meta("next");
|
||||
CHECK_MESSAGE(
|
||||
loaded_resource_c_binary->get_name() == "C",
|
||||
"The loaded child resource name should be equal to the expected value.");
|
||||
CHECK_MESSAGE(
|
||||
!loaded_resource_c_binary->has_meta("next"),
|
||||
"The loaded child resource circular reference should be NULL.");
|
||||
|
||||
const Ref<Resource> &loaded_resource_a_text = ResourceLoader::load(save_path_text);
|
||||
CHECK_MESSAGE(
|
||||
loaded_resource_a_text->get_name() == "A",
|
||||
"The loaded resource name should be equal to the expected value.");
|
||||
const Ref<Resource> &loaded_resource_b_text = loaded_resource_a_text->get_meta("next");
|
||||
CHECK_MESSAGE(
|
||||
loaded_resource_b_text->get_name() == "B",
|
||||
"The loaded child resource name should be equal to the expected value.");
|
||||
const Ref<Resource> &loaded_resource_c_text = loaded_resource_b_text->get_meta("next");
|
||||
CHECK_MESSAGE(
|
||||
loaded_resource_c_text->get_name() == "C",
|
||||
"The loaded child resource name should be equal to the expected value.");
|
||||
CHECK_MESSAGE(
|
||||
!loaded_resource_c_text->has_meta("next"),
|
||||
"The loaded child resource circular reference should be NULL.");
|
||||
|
||||
// Break circular reference to avoid memory leak
|
||||
resource_c->remove_meta("next");
|
||||
}
|
||||
} // namespace TestResource
|
||||
68
tests/core/io/test_resource_uid.h
Normal file
@@ -0,0 +1,68 @@
|
||||
/**************************************************************************/
|
||||
/* test_resource_uid.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/io/resource_uid.h"
|
||||
|
||||
#include "thirdparty/doctest/doctest.h"
|
||||
|
||||
#include "tests/test_macros.h"
|
||||
|
||||
namespace TestResourceUID {
|
||||
|
||||
TEST_CASE("[ResourceUID] Must encode/decode maximum/minimum UID correctly") {
|
||||
CHECK_MESSAGE(ResourceUID::get_singleton()->id_to_text(0x7fffffffffffffff) == "uid://d4n4ub6itg400", "Maximum UID must encode correctly.");
|
||||
CHECK_MESSAGE(ResourceUID::get_singleton()->text_to_id("uid://d4n4ub6itg400") == 0x7fffffffffffffff, "Maximum UID must decode correctly.");
|
||||
|
||||
CHECK_MESSAGE(ResourceUID::get_singleton()->id_to_text(0) == "uid://a", "Minimum UID must encode correctly.");
|
||||
CHECK_MESSAGE(ResourceUID::get_singleton()->text_to_id("uid://a") == 0, "Minimum UID must decode correctly.");
|
||||
}
|
||||
|
||||
TEST_CASE("[ResourceUID] Must encode and decode invalid UIDs correctly") {
|
||||
ResourceUID *rid = ResourceUID::get_singleton();
|
||||
CHECK_MESSAGE(rid->id_to_text(-1) == "uid://<invalid>", "Invalid UID -1 must encode correctly.");
|
||||
CHECK_MESSAGE(rid->text_to_id("uid://<invalid>") == -1, "Invalid UID -1 must decode correctly.");
|
||||
|
||||
CHECK_MESSAGE(rid->id_to_text(-2) == rid->id_to_text(-1), "Invalid UID -2 must encode to the same as -1.");
|
||||
|
||||
CHECK_MESSAGE(rid->text_to_id("dm3rdgs30kfci") == -1, "UID without scheme must decode correctly.");
|
||||
}
|
||||
|
||||
TEST_CASE("[ResourceUID] Must encode and decode various UIDs correctly") {
|
||||
ResourceUID *rid = ResourceUID::get_singleton();
|
||||
CHECK_MESSAGE(rid->id_to_text(1) == "uid://b", "UID 1 must encode correctly.");
|
||||
CHECK_MESSAGE(rid->text_to_id("uid://b") == 1, "UID 1 must decode correctly.");
|
||||
|
||||
CHECK_MESSAGE(rid->id_to_text(8060368642360689600) == "uid://dm3rdgs30kfci", "A normal UID must encode correctly.");
|
||||
CHECK_MESSAGE(rid->text_to_id("uid://dm3rdgs30kfci") == 8060368642360689600, "A normal UID must decode correctly.");
|
||||
}
|
||||
|
||||
} // namespace TestResourceUID
|
||||
308
tests/core/io/test_stream_peer.h
Normal file
@@ -0,0 +1,308 @@
|
||||
/**************************************************************************/
|
||||
/* test_stream_peer.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/io/stream_peer.h"
|
||||
#include "tests/test_macros.h"
|
||||
|
||||
namespace TestStreamPeer {
|
||||
|
||||
TEST_CASE("[StreamPeer] Initialization through StreamPeerBuffer") {
|
||||
Ref<StreamPeerBuffer> spb;
|
||||
spb.instantiate();
|
||||
|
||||
CHECK_EQ(spb->is_big_endian_enabled(), false);
|
||||
}
|
||||
|
||||
TEST_CASE("[StreamPeer] Get and sets through StreamPeerBuffer") {
|
||||
Ref<StreamPeerBuffer> spb;
|
||||
spb.instantiate();
|
||||
|
||||
SUBCASE("A int8_t value") {
|
||||
int8_t value = 42;
|
||||
|
||||
spb->clear();
|
||||
spb->put_8(value);
|
||||
spb->seek(0);
|
||||
|
||||
CHECK_EQ(spb->get_8(), value);
|
||||
}
|
||||
|
||||
SUBCASE("A uint8_t value") {
|
||||
uint8_t value = 42;
|
||||
|
||||
spb->clear();
|
||||
spb->put_u8(value);
|
||||
spb->seek(0);
|
||||
|
||||
CHECK_EQ(spb->get_u8(), value);
|
||||
}
|
||||
|
||||
SUBCASE("A int16_t value") {
|
||||
int16_t value = 42;
|
||||
|
||||
spb->clear();
|
||||
spb->put_16(value);
|
||||
spb->seek(0);
|
||||
|
||||
CHECK_EQ(spb->get_16(), value);
|
||||
}
|
||||
|
||||
SUBCASE("A uint16_t value") {
|
||||
uint16_t value = 42;
|
||||
|
||||
spb->clear();
|
||||
spb->put_u16(value);
|
||||
spb->seek(0);
|
||||
|
||||
CHECK_EQ(spb->get_u16(), value);
|
||||
}
|
||||
|
||||
SUBCASE("A int32_t value") {
|
||||
int32_t value = 42;
|
||||
|
||||
spb->clear();
|
||||
spb->put_32(value);
|
||||
spb->seek(0);
|
||||
|
||||
CHECK_EQ(spb->get_32(), value);
|
||||
}
|
||||
|
||||
SUBCASE("A uint32_t value") {
|
||||
uint32_t value = 42;
|
||||
|
||||
spb->clear();
|
||||
spb->put_u32(value);
|
||||
spb->seek(0);
|
||||
|
||||
CHECK_EQ(spb->get_u32(), value);
|
||||
}
|
||||
|
||||
SUBCASE("A int64_t value") {
|
||||
int64_t value = 42;
|
||||
|
||||
spb->clear();
|
||||
spb->put_64(value);
|
||||
spb->seek(0);
|
||||
|
||||
CHECK_EQ(spb->get_64(), value);
|
||||
}
|
||||
|
||||
SUBCASE("A int64_t value") {
|
||||
uint64_t value = 42;
|
||||
|
||||
spb->clear();
|
||||
spb->put_u64(value);
|
||||
spb->seek(0);
|
||||
|
||||
CHECK_EQ(spb->get_u64(), value);
|
||||
}
|
||||
|
||||
SUBCASE("A half-precision float value") {
|
||||
float value = 3.1415927f;
|
||||
float expected = 3.14062f;
|
||||
|
||||
spb->clear();
|
||||
spb->put_half(value);
|
||||
spb->seek(0);
|
||||
|
||||
CHECK(spb->get_half() == doctest::Approx(expected));
|
||||
}
|
||||
|
||||
SUBCASE("A float value") {
|
||||
float value = 42.0f;
|
||||
|
||||
spb->clear();
|
||||
spb->put_float(value);
|
||||
spb->seek(0);
|
||||
|
||||
CHECK_EQ(spb->get_float(), value);
|
||||
}
|
||||
|
||||
SUBCASE("A double value") {
|
||||
double value = 42.0;
|
||||
|
||||
spb->clear();
|
||||
spb->put_double(value);
|
||||
spb->seek(0);
|
||||
|
||||
CHECK_EQ(spb->get_double(), value);
|
||||
}
|
||||
|
||||
SUBCASE("A string value") {
|
||||
String value = "Hello, World!";
|
||||
|
||||
spb->clear();
|
||||
spb->put_string(value);
|
||||
spb->seek(0);
|
||||
|
||||
CHECK_EQ(spb->get_string(), value);
|
||||
}
|
||||
|
||||
SUBCASE("A utf8 string value") {
|
||||
String value = String::utf8("Hello✩, World✩!");
|
||||
|
||||
spb->clear();
|
||||
spb->put_utf8_string(value);
|
||||
spb->seek(0);
|
||||
|
||||
CHECK_EQ(spb->get_utf8_string(), value);
|
||||
}
|
||||
|
||||
SUBCASE("A variant value") {
|
||||
Array value;
|
||||
value.push_front(42);
|
||||
value.push_front("Hello, World!");
|
||||
|
||||
spb->clear();
|
||||
spb->put_var(value);
|
||||
spb->seek(0);
|
||||
|
||||
CHECK_EQ(spb->get_var(), value);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("[StreamPeer] Get and sets big endian through StreamPeerBuffer") {
|
||||
Ref<StreamPeerBuffer> spb;
|
||||
spb.instantiate();
|
||||
spb->set_big_endian(true);
|
||||
|
||||
SUBCASE("A int16_t value") {
|
||||
int16_t value = 42;
|
||||
|
||||
spb->clear();
|
||||
spb->put_16(value);
|
||||
spb->seek(0);
|
||||
|
||||
CHECK_EQ(spb->get_16(), value);
|
||||
}
|
||||
|
||||
SUBCASE("A uint16_t value") {
|
||||
uint16_t value = 42;
|
||||
|
||||
spb->clear();
|
||||
spb->put_u16(value);
|
||||
spb->seek(0);
|
||||
|
||||
CHECK_EQ(spb->get_u16(), value);
|
||||
}
|
||||
|
||||
SUBCASE("A int32_t value") {
|
||||
int32_t value = 42;
|
||||
|
||||
spb->clear();
|
||||
spb->put_32(value);
|
||||
spb->seek(0);
|
||||
|
||||
CHECK_EQ(spb->get_32(), value);
|
||||
}
|
||||
|
||||
SUBCASE("A uint32_t value") {
|
||||
uint32_t value = 42;
|
||||
|
||||
spb->clear();
|
||||
spb->put_u32(value);
|
||||
spb->seek(0);
|
||||
|
||||
CHECK_EQ(spb->get_u32(), value);
|
||||
}
|
||||
|
||||
SUBCASE("A int64_t value") {
|
||||
int64_t value = 42;
|
||||
|
||||
spb->clear();
|
||||
spb->put_64(value);
|
||||
spb->seek(0);
|
||||
|
||||
CHECK_EQ(spb->get_64(), value);
|
||||
}
|
||||
|
||||
SUBCASE("A int64_t value") {
|
||||
uint64_t value = 42;
|
||||
|
||||
spb->clear();
|
||||
spb->put_u64(value);
|
||||
spb->seek(0);
|
||||
|
||||
CHECK_EQ(spb->get_u64(), value);
|
||||
}
|
||||
|
||||
SUBCASE("A float value") {
|
||||
float value = 42.0f;
|
||||
|
||||
spb->clear();
|
||||
spb->put_float(value);
|
||||
spb->seek(0);
|
||||
|
||||
CHECK_EQ(spb->get_float(), value);
|
||||
}
|
||||
|
||||
SUBCASE("A half-precision float value") {
|
||||
float value = 3.1415927f;
|
||||
float expected = 3.14062f;
|
||||
|
||||
spb->clear();
|
||||
spb->put_half(value);
|
||||
spb->seek(0);
|
||||
|
||||
CHECK(spb->get_half() == doctest::Approx(expected));
|
||||
}
|
||||
|
||||
SUBCASE("A double value") {
|
||||
double value = 42.0;
|
||||
|
||||
spb->clear();
|
||||
spb->put_double(value);
|
||||
spb->seek(0);
|
||||
|
||||
CHECK_EQ(spb->get_double(), value);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("[StreamPeer] Get string when there is no string") {
|
||||
Ref<StreamPeerBuffer> spb;
|
||||
spb.instantiate();
|
||||
|
||||
ERR_PRINT_OFF;
|
||||
CHECK_EQ(spb->get_string(), "");
|
||||
ERR_PRINT_ON;
|
||||
}
|
||||
|
||||
TEST_CASE("[StreamPeer] Get UTF8 string when there is no string") {
|
||||
Ref<StreamPeerBuffer> spb;
|
||||
spb.instantiate();
|
||||
|
||||
ERR_PRINT_OFF;
|
||||
CHECK_EQ(spb->get_utf8_string(), "");
|
||||
ERR_PRINT_ON;
|
||||
}
|
||||
|
||||
} // namespace TestStreamPeer
|
||||
182
tests/core/io/test_stream_peer_buffer.h
Normal file
@@ -0,0 +1,182 @@
|
||||
/**************************************************************************/
|
||||
/* test_stream_peer_buffer.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/io/stream_peer.h"
|
||||
#include "tests/test_macros.h"
|
||||
|
||||
namespace TestStreamPeerBuffer {
|
||||
|
||||
TEST_CASE("[StreamPeerBuffer] Initialization") {
|
||||
Ref<StreamPeerBuffer> spb;
|
||||
spb.instantiate();
|
||||
CHECK_EQ(spb->get_size(), 0);
|
||||
CHECK_EQ(spb->get_position(), 0);
|
||||
CHECK_EQ(spb->get_available_bytes(), 0);
|
||||
}
|
||||
|
||||
TEST_CASE("[StreamPeerBuffer] Seek") {
|
||||
Ref<StreamPeerBuffer> spb;
|
||||
spb.instantiate();
|
||||
uint8_t first = 5;
|
||||
uint8_t second = 7;
|
||||
uint8_t third = 11;
|
||||
|
||||
spb->put_u8(first);
|
||||
spb->put_u8(second);
|
||||
spb->put_u8(third);
|
||||
|
||||
spb->seek(0);
|
||||
CHECK_EQ(spb->get_u8(), first);
|
||||
CHECK_EQ(spb->get_u8(), second);
|
||||
CHECK_EQ(spb->get_u8(), third);
|
||||
|
||||
spb->seek(1);
|
||||
CHECK_EQ(spb->get_position(), 1);
|
||||
CHECK_EQ(spb->get_u8(), second);
|
||||
|
||||
spb->seek(1);
|
||||
ERR_PRINT_OFF;
|
||||
spb->seek(-1);
|
||||
ERR_PRINT_ON;
|
||||
CHECK_EQ(spb->get_position(), 1);
|
||||
ERR_PRINT_OFF;
|
||||
spb->seek(5);
|
||||
ERR_PRINT_ON;
|
||||
CHECK_EQ(spb->get_position(), 1);
|
||||
}
|
||||
|
||||
TEST_CASE("[StreamPeerBuffer] Resize") {
|
||||
Ref<StreamPeerBuffer> spb;
|
||||
spb.instantiate();
|
||||
CHECK_EQ(spb->get_size(), 0);
|
||||
CHECK_EQ(spb->get_position(), 0);
|
||||
CHECK_EQ(spb->get_available_bytes(), 0);
|
||||
|
||||
spb->resize(42);
|
||||
CHECK_EQ(spb->get_size(), 42);
|
||||
CHECK_EQ(spb->get_position(), 0);
|
||||
CHECK_EQ(spb->get_available_bytes(), 42);
|
||||
|
||||
spb->seek(21);
|
||||
CHECK_EQ(spb->get_size(), 42);
|
||||
CHECK_EQ(spb->get_position(), 21);
|
||||
CHECK_EQ(spb->get_available_bytes(), 21);
|
||||
}
|
||||
|
||||
TEST_CASE("[StreamPeerBuffer] Get underlying data array") {
|
||||
uint8_t first = 5;
|
||||
uint8_t second = 7;
|
||||
uint8_t third = 11;
|
||||
|
||||
Ref<StreamPeerBuffer> spb;
|
||||
spb.instantiate();
|
||||
spb->put_u8(first);
|
||||
spb->put_u8(second);
|
||||
spb->put_u8(third);
|
||||
|
||||
Vector<uint8_t> data_array = spb->get_data_array();
|
||||
|
||||
CHECK_EQ(data_array[0], first);
|
||||
CHECK_EQ(data_array[1], second);
|
||||
CHECK_EQ(data_array[2], third);
|
||||
}
|
||||
|
||||
TEST_CASE("[StreamPeerBuffer] Set underlying data array") {
|
||||
uint8_t first = 5;
|
||||
uint8_t second = 7;
|
||||
uint8_t third = 11;
|
||||
|
||||
Ref<StreamPeerBuffer> spb;
|
||||
spb.instantiate();
|
||||
spb->put_u8(1);
|
||||
spb->put_u8(2);
|
||||
spb->put_u8(3);
|
||||
|
||||
Vector<uint8_t> new_data_array;
|
||||
new_data_array.push_back(first);
|
||||
new_data_array.push_back(second);
|
||||
new_data_array.push_back(third);
|
||||
|
||||
spb->set_data_array(new_data_array);
|
||||
|
||||
CHECK_EQ(spb->get_u8(), first);
|
||||
CHECK_EQ(spb->get_u8(), second);
|
||||
CHECK_EQ(spb->get_u8(), third);
|
||||
}
|
||||
|
||||
TEST_CASE("[StreamPeerBuffer] Duplicate") {
|
||||
uint8_t first = 5;
|
||||
uint8_t second = 7;
|
||||
uint8_t third = 11;
|
||||
|
||||
Ref<StreamPeerBuffer> spb;
|
||||
spb.instantiate();
|
||||
spb->put_u8(first);
|
||||
spb->put_u8(second);
|
||||
spb->put_u8(third);
|
||||
|
||||
Ref<StreamPeerBuffer> spb2 = spb->duplicate();
|
||||
|
||||
CHECK_EQ(spb2->get_u8(), first);
|
||||
CHECK_EQ(spb2->get_u8(), second);
|
||||
CHECK_EQ(spb2->get_u8(), third);
|
||||
}
|
||||
|
||||
TEST_CASE("[StreamPeerBuffer] Put data with size equal to zero does nothing") {
|
||||
Ref<StreamPeerBuffer> spb;
|
||||
spb.instantiate();
|
||||
uint8_t data = 42;
|
||||
|
||||
Error error = spb->put_data((const uint8_t *)&data, 0);
|
||||
|
||||
CHECK_EQ(error, OK);
|
||||
CHECK_EQ(spb->get_size(), 0);
|
||||
CHECK_EQ(spb->get_position(), 0);
|
||||
CHECK_EQ(spb->get_available_bytes(), 0);
|
||||
}
|
||||
|
||||
TEST_CASE("[StreamPeerBuffer] Get data with invalid size returns an error") {
|
||||
Ref<StreamPeerBuffer> spb;
|
||||
spb.instantiate();
|
||||
uint8_t data = 42;
|
||||
spb->put_u8(data);
|
||||
spb->seek(0);
|
||||
|
||||
uint8_t data_out = 0;
|
||||
Error error = spb->get_data(&data_out, 3);
|
||||
|
||||
CHECK_EQ(error, ERR_INVALID_PARAMETER);
|
||||
CHECK_EQ(spb->get_size(), 1);
|
||||
CHECK_EQ(spb->get_position(), 1);
|
||||
}
|
||||
|
||||
} // namespace TestStreamPeerBuffer
|
||||
195
tests/core/io/test_stream_peer_gzip.h
Normal file
@@ -0,0 +1,195 @@
|
||||
/**************************************************************************/
|
||||
/* test_stream_peer_gzip.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/io/stream_peer_gzip.h"
|
||||
#include "tests/test_macros.h"
|
||||
|
||||
namespace TestStreamPeerGZIP {
|
||||
|
||||
const String hello = "Hello World!!!";
|
||||
|
||||
TEST_CASE("[StreamPeerGZIP] Initialization") {
|
||||
Ref<StreamPeerGZIP> spgz;
|
||||
spgz.instantiate();
|
||||
CHECK_EQ(spgz->get_available_bytes(), 0);
|
||||
}
|
||||
|
||||
TEST_CASE("[StreamPeerGZIP] Compress/Decompress") {
|
||||
Ref<StreamPeerGZIP> spgz;
|
||||
spgz.instantiate();
|
||||
|
||||
bool is_deflate = false;
|
||||
|
||||
SUBCASE("GZIP") {
|
||||
is_deflate = false;
|
||||
}
|
||||
|
||||
SUBCASE("DEFLATE") {
|
||||
is_deflate = true;
|
||||
}
|
||||
|
||||
CHECK_EQ(spgz->start_compression(is_deflate), Error::OK);
|
||||
CHECK_EQ(spgz->put_data(hello.to_ascii_buffer().ptr(), hello.to_ascii_buffer().size()), Error::OK);
|
||||
CHECK_EQ(spgz->finish(), Error::OK);
|
||||
|
||||
Vector<uint8_t> hello_compressed;
|
||||
int hello_compressed_size = spgz->get_available_bytes();
|
||||
hello_compressed.resize(hello_compressed_size);
|
||||
CHECK_EQ(spgz->get_data(hello_compressed.ptrw(), hello_compressed_size), Error::OK);
|
||||
|
||||
spgz->clear();
|
||||
|
||||
CHECK_EQ(spgz->start_decompression(is_deflate), Error::OK);
|
||||
CHECK_EQ(spgz->put_data(hello_compressed.ptr(), hello_compressed.size()), Error::OK);
|
||||
|
||||
Vector<uint8_t> hello_decompressed;
|
||||
int hello_decompressed_size = spgz->get_available_bytes();
|
||||
hello_decompressed.resize(hello_decompressed_size);
|
||||
CHECK_EQ(spgz->get_data(hello_decompressed.ptrw(), hello_decompressed_size), Error::OK);
|
||||
CHECK_EQ(hello_decompressed, hello.to_ascii_buffer());
|
||||
}
|
||||
|
||||
TEST_CASE("[StreamPeerGZIP] Compress/Decompress big chunks of data") { // GH-97201
|
||||
Ref<StreamPeerGZIP> spgz;
|
||||
spgz.instantiate();
|
||||
CHECK_EQ(spgz->start_compression(false), Error::OK);
|
||||
|
||||
Vector<uint8_t> big_data;
|
||||
big_data.resize(2500);
|
||||
// Filling it with random data because the issue is related to the size of the data when it's compress.
|
||||
// Random data results in bigger compressed data size.
|
||||
for (int i = 0; i < big_data.size(); i++) {
|
||||
big_data.write[i] = Math::random(48, 122);
|
||||
}
|
||||
CHECK_EQ(spgz->put_data(big_data.ptr(), big_data.size()), Error::OK);
|
||||
CHECK_EQ(spgz->finish(), Error::OK);
|
||||
|
||||
Vector<uint8_t> big_data_compressed;
|
||||
int big_data_compressed_size = spgz->get_available_bytes();
|
||||
big_data_compressed.resize(big_data_compressed_size);
|
||||
CHECK_EQ(spgz->get_data(big_data_compressed.ptrw(), big_data_compressed_size), Error::OK);
|
||||
|
||||
spgz->clear();
|
||||
|
||||
CHECK_EQ(spgz->start_decompression(false), Error::OK);
|
||||
CHECK_EQ(spgz->put_data(big_data_compressed.ptr(), big_data_compressed.size()), Error::OK);
|
||||
|
||||
Vector<uint8_t> big_data_decompressed;
|
||||
int big_data_decompressed_size = spgz->get_available_bytes();
|
||||
big_data_decompressed.resize(big_data_decompressed_size);
|
||||
CHECK_EQ(spgz->get_data(big_data_decompressed.ptrw(), big_data_decompressed_size), Error::OK);
|
||||
CHECK_EQ(big_data_decompressed, big_data);
|
||||
}
|
||||
|
||||
TEST_CASE("[StreamPeerGZIP] Can't start twice") {
|
||||
Ref<StreamPeerGZIP> spgz;
|
||||
spgz.instantiate();
|
||||
CHECK_EQ(spgz->start_compression(false), Error::OK);
|
||||
|
||||
ERR_PRINT_OFF;
|
||||
CHECK_EQ(spgz->start_compression(false), Error::ERR_ALREADY_IN_USE);
|
||||
CHECK_EQ(spgz->start_decompression(false), Error::ERR_ALREADY_IN_USE);
|
||||
ERR_PRINT_ON;
|
||||
}
|
||||
|
||||
TEST_CASE("[StreamPeerGZIP] Can't start with a buffer size equal or less than zero") {
|
||||
Ref<StreamPeerGZIP> spgz;
|
||||
spgz.instantiate();
|
||||
|
||||
ERR_PRINT_OFF;
|
||||
CHECK_EQ(spgz->start_compression(false, 0), Error::ERR_INVALID_PARAMETER);
|
||||
CHECK_EQ(spgz->start_compression(false, -1), Error::ERR_INVALID_PARAMETER);
|
||||
CHECK_EQ(spgz->start_decompression(false, 0), Error::ERR_INVALID_PARAMETER);
|
||||
CHECK_EQ(spgz->start_decompression(false, -1), Error::ERR_INVALID_PARAMETER);
|
||||
ERR_PRINT_ON;
|
||||
}
|
||||
|
||||
TEST_CASE("[StreamPeerGZIP] Can't put/get data with a buffer size less than zero") {
|
||||
Ref<StreamPeerGZIP> spgz;
|
||||
spgz.instantiate();
|
||||
CHECK_EQ(spgz->start_compression(false), Error::OK);
|
||||
|
||||
ERR_PRINT_OFF;
|
||||
CHECK_EQ(spgz->put_data(hello.to_ascii_buffer().ptr(), -1), Error::ERR_INVALID_PARAMETER);
|
||||
|
||||
Vector<uint8_t> hello_compressed;
|
||||
hello_compressed.resize(5);
|
||||
CHECK_EQ(spgz->get_data(hello_compressed.ptrw(), -1), Error::ERR_INVALID_PARAMETER);
|
||||
ERR_PRINT_ON;
|
||||
}
|
||||
|
||||
TEST_CASE("[StreamPeerGZIP] Needs to be started before use") {
|
||||
Ref<StreamPeerGZIP> spgz;
|
||||
spgz.instantiate();
|
||||
|
||||
ERR_PRINT_OFF;
|
||||
CHECK_EQ(spgz->put_data(hello.to_ascii_buffer().ptr(), hello.to_ascii_buffer().size()), Error::ERR_UNCONFIGURED);
|
||||
ERR_PRINT_ON;
|
||||
}
|
||||
|
||||
TEST_CASE("[StreamPeerGZIP] Can't be finished after clear or if it's decompressing") {
|
||||
Ref<StreamPeerGZIP> spgz;
|
||||
spgz.instantiate();
|
||||
|
||||
CHECK_EQ(spgz->start_compression(false), Error::OK);
|
||||
spgz->clear();
|
||||
ERR_PRINT_OFF;
|
||||
CHECK_EQ(spgz->finish(), Error::ERR_UNAVAILABLE);
|
||||
ERR_PRINT_ON;
|
||||
|
||||
spgz->clear();
|
||||
CHECK_EQ(spgz->start_decompression(false), Error::OK);
|
||||
ERR_PRINT_OFF;
|
||||
CHECK_EQ(spgz->finish(), Error::ERR_UNAVAILABLE);
|
||||
ERR_PRINT_ON;
|
||||
}
|
||||
|
||||
TEST_CASE("[StreamPeerGZIP] Fails to get if nothing was compress/decompress") {
|
||||
Ref<StreamPeerGZIP> spgz;
|
||||
spgz.instantiate();
|
||||
|
||||
SUBCASE("Compression") {
|
||||
CHECK_EQ(spgz->start_compression(false), Error::OK);
|
||||
}
|
||||
|
||||
SUBCASE("Decompression") {
|
||||
CHECK_EQ(spgz->start_decompression(false), Error::OK);
|
||||
}
|
||||
|
||||
ERR_PRINT_OFF;
|
||||
Vector<uint8_t> hello_compressed;
|
||||
hello_compressed.resize(5);
|
||||
CHECK_EQ(spgz->get_data(hello_compressed.ptrw(), hello_compressed.size()), Error::ERR_UNAVAILABLE);
|
||||
ERR_PRINT_ON;
|
||||
}
|
||||
|
||||
} // namespace TestStreamPeerGZIP
|
||||
252
tests/core/io/test_tcp_server.h
Normal file
@@ -0,0 +1,252 @@
|
||||
/**************************************************************************/
|
||||
/* test_tcp_server.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/io/stream_peer_tcp.h"
|
||||
#include "core/io/tcp_server.h"
|
||||
#include "tests/test_macros.h"
|
||||
|
||||
#include <functional>
|
||||
|
||||
namespace TestTCPServer {
|
||||
|
||||
const int PORT = 12345;
|
||||
const IPAddress LOCALHOST("127.0.0.1");
|
||||
const uint32_t SLEEP_DURATION = 1000;
|
||||
const uint64_t MAX_WAIT_USEC = 2000000;
|
||||
|
||||
void wait_for_condition(std::function<bool()> f_test) {
|
||||
const uint64_t time = OS::get_singleton()->get_ticks_usec();
|
||||
while (!f_test() && (OS::get_singleton()->get_ticks_usec() - time) < MAX_WAIT_USEC) {
|
||||
OS::get_singleton()->delay_usec(SLEEP_DURATION);
|
||||
}
|
||||
}
|
||||
|
||||
Ref<TCPServer> create_server(const IPAddress &p_address, int p_port) {
|
||||
Ref<TCPServer> server;
|
||||
server.instantiate();
|
||||
|
||||
REQUIRE_EQ(server->listen(PORT, LOCALHOST), Error::OK);
|
||||
REQUIRE(server->is_listening());
|
||||
CHECK_FALSE(server->is_connection_available());
|
||||
|
||||
return server;
|
||||
}
|
||||
|
||||
Ref<StreamPeerTCP> create_client(const IPAddress &p_address, int p_port) {
|
||||
Ref<StreamPeerTCP> client;
|
||||
client.instantiate();
|
||||
|
||||
REQUIRE_EQ(client->connect_to_host(LOCALHOST, PORT), Error::OK);
|
||||
CHECK_EQ(client->get_connected_host(), LOCALHOST);
|
||||
CHECK_EQ(client->get_connected_port(), PORT);
|
||||
CHECK_EQ(client->get_status(), StreamPeerTCP::STATUS_CONNECTING);
|
||||
|
||||
return client;
|
||||
}
|
||||
|
||||
Ref<StreamPeerTCP> accept_connection(Ref<TCPServer> &p_server) {
|
||||
wait_for_condition([&]() {
|
||||
return p_server->is_connection_available();
|
||||
});
|
||||
|
||||
REQUIRE(p_server->is_connection_available());
|
||||
Ref<StreamPeerTCP> client_from_server = p_server->take_connection();
|
||||
REQUIRE(client_from_server.is_valid());
|
||||
CHECK_EQ(client_from_server->get_connected_host(), LOCALHOST);
|
||||
CHECK_EQ(client_from_server->get_status(), StreamPeerTCP::STATUS_CONNECTED);
|
||||
|
||||
return client_from_server;
|
||||
}
|
||||
|
||||
TEST_CASE("[TCPServer] Instantiation") {
|
||||
Ref<TCPServer> server;
|
||||
server.instantiate();
|
||||
|
||||
REQUIRE(server.is_valid());
|
||||
CHECK_EQ(false, server->is_listening());
|
||||
}
|
||||
|
||||
TEST_CASE("[TCPServer] Accept a connection and receive/send data") {
|
||||
Ref<TCPServer> server = create_server(LOCALHOST, PORT);
|
||||
Ref<StreamPeerTCP> client = create_client(LOCALHOST, PORT);
|
||||
Ref<StreamPeerTCP> client_from_server = accept_connection(server);
|
||||
|
||||
wait_for_condition([&]() {
|
||||
return client->poll() != Error::OK || client->get_status() == StreamPeerTCP::STATUS_CONNECTED;
|
||||
});
|
||||
|
||||
CHECK_EQ(client->get_status(), StreamPeerTCP::STATUS_CONNECTED);
|
||||
|
||||
// Sending data from client to server.
|
||||
const String hello_world = "Hello World!";
|
||||
client->put_string(hello_world);
|
||||
CHECK_EQ(client_from_server->get_string(), hello_world);
|
||||
|
||||
// Sending data from server to client.
|
||||
const float pi = 3.1415;
|
||||
client_from_server->put_float(pi);
|
||||
CHECK_EQ(client->get_float(), pi);
|
||||
|
||||
client->disconnect_from_host();
|
||||
server->stop();
|
||||
CHECK_FALSE(server->is_listening());
|
||||
}
|
||||
|
||||
TEST_CASE("[TCPServer] Handle multiple clients at the same time") {
|
||||
Ref<TCPServer> server = create_server(LOCALHOST, PORT);
|
||||
|
||||
Vector<Ref<StreamPeerTCP>> clients;
|
||||
for (int i = 0; i < 5; i++) {
|
||||
clients.push_back(create_client(LOCALHOST, PORT));
|
||||
}
|
||||
|
||||
Vector<Ref<StreamPeerTCP>> clients_from_server;
|
||||
for (int i = 0; i < clients.size(); i++) {
|
||||
clients_from_server.push_back(accept_connection(server));
|
||||
}
|
||||
|
||||
wait_for_condition([&]() {
|
||||
bool should_exit = true;
|
||||
for (Ref<StreamPeerTCP> &c : clients) {
|
||||
if (c->poll() != Error::OK) {
|
||||
return true;
|
||||
}
|
||||
StreamPeerTCP::Status status = c->get_status();
|
||||
if (status != StreamPeerTCP::STATUS_CONNECTED && status != StreamPeerTCP::STATUS_CONNECTING) {
|
||||
return true;
|
||||
}
|
||||
if (status != StreamPeerTCP::STATUS_CONNECTED) {
|
||||
should_exit = false;
|
||||
}
|
||||
}
|
||||
return should_exit;
|
||||
});
|
||||
|
||||
for (Ref<StreamPeerTCP> &c : clients) {
|
||||
REQUIRE_EQ(c->get_status(), StreamPeerTCP::STATUS_CONNECTED);
|
||||
}
|
||||
|
||||
// Sending data from each client to server.
|
||||
for (int i = 0; i < clients.size(); i++) {
|
||||
String hello_client = "Hello " + itos(i);
|
||||
clients[i]->put_string(hello_client);
|
||||
CHECK_EQ(clients_from_server[i]->get_string(), hello_client);
|
||||
}
|
||||
|
||||
for (Ref<StreamPeerTCP> &c : clients) {
|
||||
c->disconnect_from_host();
|
||||
}
|
||||
server->stop();
|
||||
}
|
||||
|
||||
TEST_CASE("[TCPServer] When stopped shouldn't accept new connections") {
|
||||
Ref<TCPServer> server = create_server(LOCALHOST, PORT);
|
||||
Ref<StreamPeerTCP> client = create_client(LOCALHOST, PORT);
|
||||
Ref<StreamPeerTCP> client_from_server = accept_connection(server);
|
||||
|
||||
wait_for_condition([&]() {
|
||||
return client->poll() != Error::OK || client->get_status() == StreamPeerTCP::STATUS_CONNECTED;
|
||||
});
|
||||
|
||||
CHECK_EQ(client->get_status(), StreamPeerTCP::STATUS_CONNECTED);
|
||||
|
||||
// Sending data from client to server.
|
||||
const String hello_world = "Hello World!";
|
||||
client->put_string(hello_world);
|
||||
CHECK_EQ(client_from_server->get_string(), hello_world);
|
||||
|
||||
client->disconnect_from_host();
|
||||
server->stop();
|
||||
CHECK_FALSE(server->is_listening());
|
||||
|
||||
// Make sure the client times out in less than the wait time.
|
||||
int timeout = ProjectSettings::get_singleton()->get_setting("network/limits/tcp/connect_timeout_seconds");
|
||||
ProjectSettings::get_singleton()->set_setting("network/limits/tcp/connect_timeout_seconds", 1);
|
||||
|
||||
Ref<StreamPeerTCP> new_client = create_client(LOCALHOST, PORT);
|
||||
|
||||
// Reset the timeout setting.
|
||||
ProjectSettings::get_singleton()->set_setting("network/limits/tcp/connect_timeout_seconds", timeout);
|
||||
|
||||
CHECK_FALSE(server->is_connection_available());
|
||||
|
||||
wait_for_condition([&]() {
|
||||
return new_client->poll() != Error::OK || new_client->get_status() == StreamPeerTCP::STATUS_ERROR;
|
||||
});
|
||||
|
||||
CHECK_FALSE(server->is_connection_available());
|
||||
|
||||
CHECK_EQ(new_client->get_status(), StreamPeerTCP::STATUS_ERROR);
|
||||
new_client->disconnect_from_host();
|
||||
CHECK_EQ(new_client->get_status(), StreamPeerTCP::STATUS_NONE);
|
||||
}
|
||||
|
||||
TEST_CASE("[TCPServer] Should disconnect client") {
|
||||
Ref<TCPServer> server = create_server(LOCALHOST, PORT);
|
||||
Ref<StreamPeerTCP> client = create_client(LOCALHOST, PORT);
|
||||
Ref<StreamPeerTCP> client_from_server = accept_connection(server);
|
||||
|
||||
wait_for_condition([&]() {
|
||||
return client->poll() != Error::OK || client->get_status() == StreamPeerTCP::STATUS_CONNECTED;
|
||||
});
|
||||
|
||||
CHECK_EQ(client->get_status(), StreamPeerTCP::STATUS_CONNECTED);
|
||||
|
||||
// Sending data from client to server.
|
||||
const String hello_world = "Hello World!";
|
||||
client->put_string(hello_world);
|
||||
CHECK_EQ(client_from_server->get_string(), hello_world);
|
||||
|
||||
client_from_server->disconnect_from_host();
|
||||
server->stop();
|
||||
CHECK_FALSE(server->is_listening());
|
||||
|
||||
// Wait for disconnection
|
||||
wait_for_condition([&]() {
|
||||
return client->poll() != Error::OK || client->get_status() == StreamPeerTCP::STATUS_NONE;
|
||||
});
|
||||
|
||||
// Wait for disconnection
|
||||
wait_for_condition([&]() {
|
||||
return client_from_server->poll() != Error::OK || client_from_server->get_status() == StreamPeerTCP::STATUS_NONE;
|
||||
});
|
||||
|
||||
CHECK_EQ(client->get_status(), StreamPeerTCP::STATUS_NONE);
|
||||
CHECK_EQ(client_from_server->get_status(), StreamPeerTCP::STATUS_NONE);
|
||||
|
||||
ERR_PRINT_OFF;
|
||||
CHECK_EQ(client->get_string(), String());
|
||||
CHECK_EQ(client_from_server->get_string(), String());
|
||||
ERR_PRINT_ON;
|
||||
}
|
||||
|
||||
} // namespace TestTCPServer
|
||||
216
tests/core/io/test_udp_server.h
Normal file
@@ -0,0 +1,216 @@
|
||||
/**************************************************************************/
|
||||
/* test_udp_server.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/io/packet_peer_udp.h"
|
||||
#include "core/io/udp_server.h"
|
||||
#include "tests/test_macros.h"
|
||||
|
||||
namespace TestUDPServer {
|
||||
|
||||
const int PORT = 12345;
|
||||
const IPAddress LOCALHOST("127.0.0.1");
|
||||
const uint32_t SLEEP_DURATION = 1000;
|
||||
const uint64_t MAX_WAIT_USEC = 100000;
|
||||
|
||||
void wait_for_condition(std::function<bool()> f_test) {
|
||||
const uint64_t time = OS::get_singleton()->get_ticks_usec();
|
||||
while (!f_test() && (OS::get_singleton()->get_ticks_usec() - time) < MAX_WAIT_USEC) {
|
||||
OS::get_singleton()->delay_usec(SLEEP_DURATION);
|
||||
}
|
||||
}
|
||||
|
||||
Ref<UDPServer> create_server(const IPAddress &p_address, int p_port) {
|
||||
Ref<UDPServer> server;
|
||||
server.instantiate();
|
||||
|
||||
Error err = server->listen(PORT, LOCALHOST);
|
||||
REQUIRE_EQ(Error::OK, err);
|
||||
REQUIRE(server->is_listening());
|
||||
CHECK_FALSE(server->is_connection_available());
|
||||
CHECK_EQ(server->get_max_pending_connections(), 16);
|
||||
|
||||
return server;
|
||||
}
|
||||
|
||||
Ref<PacketPeerUDP> create_client(const IPAddress &p_address, int p_port) {
|
||||
Ref<PacketPeerUDP> client;
|
||||
client.instantiate();
|
||||
|
||||
Error err = client->connect_to_host(LOCALHOST, PORT);
|
||||
REQUIRE_EQ(Error::OK, err);
|
||||
CHECK(client->is_bound());
|
||||
CHECK(client->is_socket_connected());
|
||||
|
||||
return client;
|
||||
}
|
||||
|
||||
Ref<PacketPeerUDP> accept_connection(Ref<UDPServer> &p_server) {
|
||||
wait_for_condition([&]() {
|
||||
return p_server->poll() != Error::OK || p_server->is_connection_available();
|
||||
});
|
||||
|
||||
CHECK_EQ(p_server->poll(), Error::OK);
|
||||
REQUIRE(p_server->is_connection_available());
|
||||
Ref<PacketPeerUDP> client_from_server = p_server->take_connection();
|
||||
REQUIRE(client_from_server.is_valid());
|
||||
CHECK(client_from_server->is_bound());
|
||||
CHECK(client_from_server->is_socket_connected());
|
||||
|
||||
return client_from_server;
|
||||
}
|
||||
|
||||
TEST_CASE("[UDPServer] Instantiation") {
|
||||
Ref<UDPServer> server;
|
||||
server.instantiate();
|
||||
|
||||
REQUIRE(server.is_valid());
|
||||
CHECK_EQ(false, server->is_listening());
|
||||
}
|
||||
|
||||
TEST_CASE("[UDPServer] Accept a connection and receive/send data") {
|
||||
Ref<UDPServer> server = create_server(LOCALHOST, PORT);
|
||||
Ref<PacketPeerUDP> client = create_client(LOCALHOST, PORT);
|
||||
|
||||
// Sending data from client to server.
|
||||
const String hello_world = "Hello World!";
|
||||
CHECK_EQ(client->put_var(hello_world), Error::OK);
|
||||
|
||||
Variant hello_world_received;
|
||||
Ref<PacketPeerUDP> client_from_server = accept_connection(server);
|
||||
CHECK_EQ(client_from_server->get_var(hello_world_received), Error::OK);
|
||||
CHECK_EQ(String(hello_world_received), hello_world);
|
||||
|
||||
// Sending data from server to client.
|
||||
const Variant pi = 3.1415;
|
||||
CHECK_EQ(client_from_server->put_var(pi), Error::OK);
|
||||
|
||||
wait_for_condition([&]() {
|
||||
return client->get_available_packet_count() > 0;
|
||||
});
|
||||
|
||||
CHECK_GT(client->get_available_packet_count(), 0);
|
||||
|
||||
Variant pi_received;
|
||||
CHECK_EQ(client->get_var(pi_received), Error::OK);
|
||||
CHECK_EQ(pi_received, pi);
|
||||
|
||||
client->close();
|
||||
server->stop();
|
||||
CHECK_FALSE(server->is_listening());
|
||||
}
|
||||
|
||||
TEST_CASE("[UDPServer] Handle multiple clients at the same time") {
|
||||
Ref<UDPServer> server = create_server(LOCALHOST, PORT);
|
||||
|
||||
Vector<Ref<PacketPeerUDP>> clients;
|
||||
for (int i = 0; i < 5; i++) {
|
||||
Ref<PacketPeerUDP> c = create_client(LOCALHOST, PORT);
|
||||
|
||||
// Sending data from client to server.
|
||||
const String hello_client = itos(i);
|
||||
CHECK_EQ(c->put_var(hello_client), Error::OK);
|
||||
|
||||
clients.push_back(c);
|
||||
}
|
||||
|
||||
Array packets;
|
||||
for (int i = 0; i < clients.size(); i++) {
|
||||
Ref<PacketPeerUDP> cfs = accept_connection(server);
|
||||
|
||||
Variant received_var;
|
||||
CHECK_EQ(cfs->get_var(received_var), Error::OK);
|
||||
CHECK_EQ(received_var.get_type(), Variant::STRING);
|
||||
packets.push_back(received_var);
|
||||
|
||||
// Sending data from server to client.
|
||||
const float sent_float = 3.1415 + received_var.operator String().to_float();
|
||||
CHECK_EQ(cfs->put_var(sent_float), Error::OK);
|
||||
}
|
||||
|
||||
CHECK_EQ(packets.size(), clients.size());
|
||||
|
||||
packets.sort();
|
||||
for (int i = 0; i < clients.size(); i++) {
|
||||
CHECK_EQ(packets[i].operator String(), itos(i));
|
||||
}
|
||||
|
||||
wait_for_condition([&]() {
|
||||
bool should_exit = true;
|
||||
for (Ref<PacketPeerUDP> &c : clients) {
|
||||
int count = c->get_available_packet_count();
|
||||
if (count < 0) {
|
||||
return true;
|
||||
}
|
||||
if (count == 0) {
|
||||
should_exit = false;
|
||||
}
|
||||
}
|
||||
return should_exit;
|
||||
});
|
||||
|
||||
for (int i = 0; i < clients.size(); i++) {
|
||||
CHECK_GT(clients[i]->get_available_packet_count(), 0);
|
||||
|
||||
Variant received_var;
|
||||
const float expected = 3.1415 + i;
|
||||
CHECK_EQ(clients[i]->get_var(received_var), Error::OK);
|
||||
CHECK_EQ(received_var.get_type(), Variant::FLOAT);
|
||||
CHECK_EQ(received_var.operator float(), expected);
|
||||
}
|
||||
|
||||
for (Ref<PacketPeerUDP> &c : clients) {
|
||||
c->close();
|
||||
}
|
||||
server->stop();
|
||||
}
|
||||
|
||||
TEST_CASE("[UDPServer] Should not accept new connections after stop") {
|
||||
Ref<UDPServer> server = create_server(LOCALHOST, PORT);
|
||||
Ref<PacketPeerUDP> client = create_client(LOCALHOST, PORT);
|
||||
|
||||
// Sending data from client to server.
|
||||
const String hello_world = "Hello World!";
|
||||
CHECK_EQ(client->put_var(hello_world), Error::OK);
|
||||
|
||||
wait_for_condition([&]() {
|
||||
return server->poll() != Error::OK || server->is_connection_available();
|
||||
});
|
||||
|
||||
REQUIRE(server->is_connection_available());
|
||||
|
||||
server->stop();
|
||||
|
||||
CHECK_FALSE(server->is_listening());
|
||||
CHECK_FALSE(server->is_connection_available());
|
||||
}
|
||||
|
||||
} // namespace TestUDPServer
|
||||
232
tests/core/io/test_xml_parser.h
Normal file
@@ -0,0 +1,232 @@
|
||||
/**************************************************************************/
|
||||
/* test_xml_parser.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/io/xml_parser.h"
|
||||
|
||||
#include "tests/test_macros.h"
|
||||
|
||||
namespace TestXMLParser {
|
||||
TEST_CASE("[XMLParser] End-to-end") {
|
||||
String source = "<?xml version = \"1.0\" encoding=\"UTF-8\" ?>\
|
||||
<top attr=\"attr value\">\
|
||||
Text<AB>\
|
||||
</top>";
|
||||
Vector<uint8_t> buff = source.to_utf8_buffer();
|
||||
|
||||
XMLParser parser;
|
||||
parser.open_buffer(buff);
|
||||
|
||||
// <?xml ...?> gets parsed as NODE_UNKNOWN
|
||||
CHECK(parser.read() == OK);
|
||||
CHECK(parser.get_node_type() == XMLParser::NodeType::NODE_UNKNOWN);
|
||||
|
||||
CHECK(parser.read() == OK);
|
||||
CHECK(parser.get_node_type() == XMLParser::NodeType::NODE_ELEMENT);
|
||||
CHECK(parser.get_node_name() == "top");
|
||||
CHECK(parser.has_attribute("attr"));
|
||||
CHECK(parser.get_named_attribute_value("attr") == "attr value");
|
||||
|
||||
CHECK(parser.read() == OK);
|
||||
CHECK(parser.get_node_type() == XMLParser::NodeType::NODE_TEXT);
|
||||
CHECK(parser.get_node_data().lstrip(" \t") == "Text<AB>");
|
||||
|
||||
CHECK(parser.read() == OK);
|
||||
CHECK(parser.get_node_type() == XMLParser::NodeType::NODE_ELEMENT_END);
|
||||
CHECK(parser.get_node_name() == "top");
|
||||
|
||||
parser.close();
|
||||
}
|
||||
|
||||
TEST_CASE("[XMLParser] Comments") {
|
||||
XMLParser parser;
|
||||
|
||||
SUBCASE("Missing end of comment") {
|
||||
const String input = "<first></first><!-- foo";
|
||||
REQUIRE_EQ(parser.open_buffer(input.to_utf8_buffer()), OK);
|
||||
REQUIRE_EQ(parser.read(), OK);
|
||||
REQUIRE_EQ(parser.get_node_type(), XMLParser::NodeType::NODE_ELEMENT);
|
||||
REQUIRE_EQ(parser.read(), OK);
|
||||
REQUIRE_EQ(parser.get_node_type(), XMLParser::NodeType::NODE_ELEMENT_END);
|
||||
REQUIRE_EQ(parser.read(), OK);
|
||||
CHECK_EQ(parser.get_node_type(), XMLParser::NodeType::NODE_COMMENT);
|
||||
CHECK_EQ(parser.get_node_name(), " foo");
|
||||
}
|
||||
SUBCASE("Bad start of comment") {
|
||||
const String input = "<first></first><!-";
|
||||
REQUIRE_EQ(parser.open_buffer(input.to_utf8_buffer()), OK);
|
||||
REQUIRE_EQ(parser.read(), OK);
|
||||
REQUIRE_EQ(parser.get_node_type(), XMLParser::NodeType::NODE_ELEMENT);
|
||||
REQUIRE_EQ(parser.read(), OK);
|
||||
REQUIRE_EQ(parser.get_node_type(), XMLParser::NodeType::NODE_ELEMENT_END);
|
||||
REQUIRE_EQ(parser.read(), OK);
|
||||
CHECK_EQ(parser.get_node_type(), XMLParser::NodeType::NODE_COMMENT);
|
||||
CHECK_EQ(parser.get_node_name(), "-");
|
||||
}
|
||||
SUBCASE("Unblanced angle brackets in comment") {
|
||||
const String input = "<!-- example << --><next-tag></next-tag>";
|
||||
REQUIRE_EQ(parser.open_buffer(input.to_utf8_buffer()), OK);
|
||||
REQUIRE_EQ(parser.read(), OK);
|
||||
CHECK_EQ(parser.get_node_type(), XMLParser::NodeType::NODE_COMMENT);
|
||||
CHECK_EQ(parser.get_node_name(), " example << ");
|
||||
}
|
||||
SUBCASE("Doctype") {
|
||||
const String input = "<!DOCTYPE greeting [<!ELEMENT greeting (#PCDATA)>]>";
|
||||
REQUIRE_EQ(parser.open_buffer(input.to_utf8_buffer()), OK);
|
||||
REQUIRE_EQ(parser.read(), OK);
|
||||
CHECK_EQ(parser.get_node_type(), XMLParser::NodeType::NODE_COMMENT);
|
||||
CHECK_EQ(parser.get_node_name(), "DOCTYPE greeting [<!ELEMENT greeting (#PCDATA)>]");
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("[XMLParser] Premature endings") {
|
||||
SUBCASE("Simple cases") {
|
||||
String input;
|
||||
String expected_name;
|
||||
XMLParser::NodeType expected_type;
|
||||
|
||||
SUBCASE("Incomplete Unknown") {
|
||||
input = "<first></first><?xml";
|
||||
expected_type = XMLParser::NodeType::NODE_UNKNOWN;
|
||||
expected_name = "?xml";
|
||||
}
|
||||
SUBCASE("Incomplete CDStart") {
|
||||
input = "<first></first><![CD";
|
||||
expected_type = XMLParser::NodeType::NODE_CDATA;
|
||||
expected_name = "";
|
||||
}
|
||||
SUBCASE("Incomplete CData") {
|
||||
input = "<first></first><![CDATA[example";
|
||||
expected_type = XMLParser::NodeType::NODE_CDATA;
|
||||
expected_name = "example";
|
||||
}
|
||||
SUBCASE("Incomplete CDEnd") {
|
||||
input = "<first></first><![CDATA[example]]";
|
||||
expected_type = XMLParser::NodeType::NODE_CDATA;
|
||||
expected_name = "example]]";
|
||||
}
|
||||
SUBCASE("Incomplete start-tag name") {
|
||||
input = "<first></first><second";
|
||||
expected_type = XMLParser::NodeType::NODE_ELEMENT;
|
||||
expected_name = "second";
|
||||
}
|
||||
|
||||
XMLParser parser;
|
||||
REQUIRE_EQ(parser.open_buffer(input.to_utf8_buffer()), OK);
|
||||
REQUIRE_EQ(parser.read(), OK);
|
||||
REQUIRE_EQ(parser.get_node_type(), XMLParser::NodeType::NODE_ELEMENT);
|
||||
REQUIRE_EQ(parser.read(), OK);
|
||||
REQUIRE_EQ(parser.get_node_type(), XMLParser::NodeType::NODE_ELEMENT_END);
|
||||
REQUIRE_EQ(parser.read(), OK);
|
||||
CHECK_EQ(parser.get_node_type(), expected_type);
|
||||
CHECK_EQ(parser.get_node_name(), expected_name);
|
||||
}
|
||||
|
||||
SUBCASE("With attributes and texts") {
|
||||
XMLParser parser;
|
||||
|
||||
SUBCASE("Incomplete start-tag attribute name") {
|
||||
const String input = "<first></first><second attr1=\"foo\" attr2";
|
||||
REQUIRE_EQ(parser.open_buffer(input.to_utf8_buffer()), OK);
|
||||
REQUIRE_EQ(parser.read(), OK);
|
||||
REQUIRE_EQ(parser.read(), OK);
|
||||
REQUIRE_EQ(parser.read(), OK);
|
||||
CHECK_EQ(parser.get_node_type(), XMLParser::NodeType::NODE_ELEMENT);
|
||||
CHECK_EQ(parser.get_node_name(), "second");
|
||||
CHECK_EQ(parser.get_attribute_count(), 1);
|
||||
CHECK_EQ(parser.get_attribute_name(0), "attr1");
|
||||
CHECK_EQ(parser.get_attribute_value(0), "foo");
|
||||
}
|
||||
|
||||
SUBCASE("Incomplete start-tag attribute unquoted value") {
|
||||
const String input = "<first></first><second attr1=\"foo\" attr2=bar";
|
||||
REQUIRE_EQ(parser.open_buffer(input.to_utf8_buffer()), OK);
|
||||
REQUIRE_EQ(parser.read(), OK);
|
||||
REQUIRE_EQ(parser.read(), OK);
|
||||
REQUIRE_EQ(parser.read(), OK);
|
||||
CHECK_EQ(parser.get_node_type(), XMLParser::NodeType::NODE_ELEMENT);
|
||||
CHECK_EQ(parser.get_node_name(), "second");
|
||||
CHECK_EQ(parser.get_attribute_count(), 1);
|
||||
CHECK_EQ(parser.get_attribute_name(0), "attr1");
|
||||
CHECK_EQ(parser.get_attribute_value(0), "foo");
|
||||
}
|
||||
|
||||
SUBCASE("Incomplete start-tag attribute quoted value") {
|
||||
const String input = "<first></first><second attr1=\"foo\" attr2=\"bar";
|
||||
REQUIRE_EQ(parser.open_buffer(input.to_utf8_buffer()), OK);
|
||||
REQUIRE_EQ(parser.read(), OK);
|
||||
REQUIRE_EQ(parser.read(), OK);
|
||||
REQUIRE_EQ(parser.read(), OK);
|
||||
CHECK_EQ(parser.get_node_type(), XMLParser::NodeType::NODE_ELEMENT);
|
||||
CHECK_EQ(parser.get_node_name(), "second");
|
||||
CHECK_EQ(parser.get_attribute_count(), 2);
|
||||
CHECK_EQ(parser.get_attribute_name(0), "attr1");
|
||||
CHECK_EQ(parser.get_attribute_value(0), "foo");
|
||||
CHECK_EQ(parser.get_attribute_name(1), "attr2");
|
||||
CHECK_EQ(parser.get_attribute_value(1), "bar");
|
||||
}
|
||||
|
||||
SUBCASE("Incomplete end-tag name") {
|
||||
const String input = "<first></fir";
|
||||
REQUIRE_EQ(parser.open_buffer(input.to_utf8_buffer()), OK);
|
||||
REQUIRE_EQ(parser.read(), OK);
|
||||
REQUIRE_EQ(parser.read(), OK);
|
||||
CHECK_EQ(parser.get_node_type(), XMLParser::NodeType::NODE_ELEMENT_END);
|
||||
CHECK_EQ(parser.get_node_name(), "fir");
|
||||
}
|
||||
|
||||
SUBCASE("Trailing text") {
|
||||
const String input = "<first></first>example";
|
||||
REQUIRE_EQ(parser.open_buffer(input.to_utf8_buffer()), OK);
|
||||
REQUIRE_EQ(parser.read(), OK);
|
||||
REQUIRE_EQ(parser.read(), OK);
|
||||
REQUIRE_EQ(parser.read(), OK);
|
||||
CHECK_EQ(parser.get_node_type(), XMLParser::NodeType::NODE_TEXT);
|
||||
CHECK_EQ(parser.get_node_data(), "example");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("[XMLParser] CDATA") {
|
||||
const String input = "<a><![CDATA[my cdata content goes here]]></a>";
|
||||
XMLParser parser;
|
||||
REQUIRE_EQ(parser.open_buffer(input.to_utf8_buffer()), OK);
|
||||
REQUIRE_EQ(parser.read(), OK);
|
||||
CHECK_EQ(parser.get_node_type(), XMLParser::NodeType::NODE_ELEMENT);
|
||||
CHECK_EQ(parser.get_node_name(), "a");
|
||||
REQUIRE_EQ(parser.read(), OK);
|
||||
CHECK_EQ(parser.get_node_type(), XMLParser::NodeType::NODE_CDATA);
|
||||
CHECK_EQ(parser.get_node_name(), "my cdata content goes here");
|
||||
REQUIRE_EQ(parser.read(), OK);
|
||||
CHECK_EQ(parser.get_node_type(), XMLParser::NodeType::NODE_ELEMENT_END);
|
||||
CHECK_EQ(parser.get_node_name(), "a");
|
||||
}
|
||||
} // namespace TestXMLParser
|
||||
482
tests/core/math/test_aabb.h
Normal file
@@ -0,0 +1,482 @@
|
||||
/**************************************************************************/
|
||||
/* test_aabb.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/math/aabb.h"
|
||||
|
||||
#include "tests/test_macros.h"
|
||||
|
||||
namespace TestAABB {
|
||||
|
||||
TEST_CASE("[AABB] Constructor methods") {
|
||||
constexpr AABB aabb = AABB(Vector3(-1.5, 2, -2.5), Vector3(4, 5, 6));
|
||||
constexpr AABB aabb_copy = AABB(aabb);
|
||||
|
||||
CHECK_MESSAGE(
|
||||
aabb == aabb_copy,
|
||||
"AABBs created with the same dimensions but by different methods should be equal.");
|
||||
}
|
||||
|
||||
TEST_CASE("[AABB] String conversion") {
|
||||
CHECK_MESSAGE(
|
||||
String(AABB(Vector3(-1.5, 2, -2.5), Vector3(4, 5, 6))) == "[P: (-1.5, 2.0, -2.5), S: (4.0, 5.0, 6.0)]",
|
||||
"The string representation should match the expected value.");
|
||||
}
|
||||
|
||||
TEST_CASE("[AABB] Basic getters") {
|
||||
constexpr AABB aabb = AABB(Vector3(-1.5, 2, -2.5), Vector3(4, 5, 6));
|
||||
CHECK_MESSAGE(
|
||||
aabb.get_position().is_equal_approx(Vector3(-1.5, 2, -2.5)),
|
||||
"get_position() should return the expected value.");
|
||||
CHECK_MESSAGE(
|
||||
aabb.get_size().is_equal_approx(Vector3(4, 5, 6)),
|
||||
"get_size() should return the expected value.");
|
||||
CHECK_MESSAGE(
|
||||
aabb.get_end().is_equal_approx(Vector3(2.5, 7, 3.5)),
|
||||
"get_end() should return the expected value.");
|
||||
CHECK_MESSAGE(
|
||||
aabb.get_center().is_equal_approx(Vector3(0.5, 4.5, 0.5)),
|
||||
"get_center() should return the expected value.");
|
||||
}
|
||||
|
||||
TEST_CASE("[AABB] Basic setters") {
|
||||
AABB aabb = AABB(Vector3(-1.5, 2, -2.5), Vector3(4, 5, 6));
|
||||
aabb.set_end(Vector3(100, 0, 100));
|
||||
CHECK_MESSAGE(
|
||||
aabb.is_equal_approx(AABB(Vector3(-1.5, 2, -2.5), Vector3(101.5, -2, 102.5))),
|
||||
"set_end() should result in the expected AABB.");
|
||||
|
||||
aabb = AABB(Vector3(-1.5, 2, -2.5), Vector3(4, 5, 6));
|
||||
aabb.set_position(Vector3(-1000, -2000, -3000));
|
||||
CHECK_MESSAGE(
|
||||
aabb.is_equal_approx(AABB(Vector3(-1000, -2000, -3000), Vector3(4, 5, 6))),
|
||||
"set_position() should result in the expected AABB.");
|
||||
|
||||
aabb = AABB(Vector3(-1.5, 2, -2.5), Vector3(4, 5, 6));
|
||||
aabb.set_size(Vector3(0, 0, -50));
|
||||
CHECK_MESSAGE(
|
||||
aabb.is_equal_approx(AABB(Vector3(-1.5, 2, -2.5), Vector3(0, 0, -50))),
|
||||
"set_size() should result in the expected AABB.");
|
||||
}
|
||||
|
||||
TEST_CASE("[AABB] Volume getters") {
|
||||
AABB aabb = AABB(Vector3(-1.5, 2, -2.5), Vector3(4, 5, 6));
|
||||
CHECK_MESSAGE(
|
||||
aabb.get_volume() == doctest::Approx(120),
|
||||
"get_volume() should return the expected value with positive size.");
|
||||
CHECK_MESSAGE(
|
||||
aabb.has_volume(),
|
||||
"Non-empty volumetric AABB should have a volume.");
|
||||
|
||||
aabb = AABB(Vector3(-1.5, 2, -2.5), Vector3(-4, 5, 6));
|
||||
CHECK_MESSAGE(
|
||||
aabb.get_volume() == doctest::Approx(-120),
|
||||
"get_volume() should return the expected value with negative size (1 component).");
|
||||
|
||||
aabb = AABB(Vector3(-1.5, 2, -2.5), Vector3(-4, -5, 6));
|
||||
CHECK_MESSAGE(
|
||||
aabb.get_volume() == doctest::Approx(120),
|
||||
"get_volume() should return the expected value with negative size (2 components).");
|
||||
|
||||
aabb = AABB(Vector3(-1.5, 2, -2.5), Vector3(-4, -5, -6));
|
||||
CHECK_MESSAGE(
|
||||
aabb.get_volume() == doctest::Approx(-120),
|
||||
"get_volume() should return the expected value with negative size (3 components).");
|
||||
|
||||
aabb = AABB(Vector3(-1.5, 2, -2.5), Vector3(4, 0, 6));
|
||||
CHECK_MESSAGE(
|
||||
!aabb.has_volume(),
|
||||
"Non-empty flat AABB should not have a volume.");
|
||||
|
||||
CHECK_MESSAGE(
|
||||
!AABB().has_volume(),
|
||||
"Empty AABB should not have a volume.");
|
||||
}
|
||||
|
||||
TEST_CASE("[AABB] Surface getters") {
|
||||
AABB aabb = AABB(Vector3(-1.5, 2, -2.5), Vector3(4, 5, 6));
|
||||
CHECK_MESSAGE(
|
||||
aabb.has_surface(),
|
||||
"Non-empty volumetric AABB should have an surface.");
|
||||
|
||||
aabb = AABB(Vector3(-1.5, 2, -2.5), Vector3(4, 0, 6));
|
||||
CHECK_MESSAGE(
|
||||
aabb.has_surface(),
|
||||
"Non-empty flat AABB should have a surface.");
|
||||
|
||||
aabb = AABB(Vector3(-1.5, 2, -2.5), Vector3(4, 0, 0));
|
||||
CHECK_MESSAGE(
|
||||
aabb.has_surface(),
|
||||
"Non-empty linear AABB should have a surface.");
|
||||
|
||||
CHECK_MESSAGE(
|
||||
!AABB().has_surface(),
|
||||
"Empty AABB should not have an surface.");
|
||||
}
|
||||
|
||||
TEST_CASE("[AABB] Intersection") {
|
||||
constexpr AABB aabb_big = AABB(Vector3(-1.5, 2, -2.5), Vector3(4, 5, 6));
|
||||
|
||||
AABB aabb_small = AABB(Vector3(-1.5, 2, -2.5), Vector3(1, 1, 1));
|
||||
CHECK_MESSAGE(
|
||||
aabb_big.intersects(aabb_small),
|
||||
"intersects() with fully contained AABB (touching the edge) should return the expected result.");
|
||||
|
||||
aabb_small = AABB(Vector3(0.5, 1.5, -2), Vector3(1, 1, 1));
|
||||
CHECK_MESSAGE(
|
||||
aabb_big.intersects(aabb_small),
|
||||
"intersects() with partially contained AABB (overflowing on Y axis) should return the expected result.");
|
||||
|
||||
aabb_small = AABB(Vector3(10, -10, -10), Vector3(1, 1, 1));
|
||||
CHECK_MESSAGE(
|
||||
!aabb_big.intersects(aabb_small),
|
||||
"intersects() with non-contained AABB should return the expected result.");
|
||||
|
||||
aabb_small = AABB(Vector3(-1.5, 2, -2.5), Vector3(1, 1, 1));
|
||||
CHECK_MESSAGE(
|
||||
aabb_big.intersection(aabb_small).is_equal_approx(aabb_small),
|
||||
"intersection() with fully contained AABB (touching the edge) should return the expected result.");
|
||||
|
||||
aabb_small = AABB(Vector3(0.5, 1.5, -2), Vector3(1, 1, 1));
|
||||
CHECK_MESSAGE(
|
||||
aabb_big.intersection(aabb_small).is_equal_approx(AABB(Vector3(0.5, 2, -2), Vector3(1, 0.5, 1))),
|
||||
"intersection() with partially contained AABB (overflowing on Y axis) should return the expected result.");
|
||||
|
||||
aabb_small = AABB(Vector3(10, -10, -10), Vector3(1, 1, 1));
|
||||
CHECK_MESSAGE(
|
||||
aabb_big.intersection(aabb_small).is_equal_approx(AABB()),
|
||||
"intersection() with non-contained AABB should return the expected result.");
|
||||
|
||||
CHECK_MESSAGE(
|
||||
aabb_big.intersects_plane(Plane(Vector3(0, 1, 0), 4)),
|
||||
"intersects_plane() should return the expected result.");
|
||||
CHECK_MESSAGE(
|
||||
aabb_big.intersects_plane(Plane(Vector3(0, -1, 0), -4)),
|
||||
"intersects_plane() should return the expected result.");
|
||||
CHECK_MESSAGE(
|
||||
!aabb_big.intersects_plane(Plane(Vector3(0, 1, 0), 200)),
|
||||
"intersects_plane() should return the expected result.");
|
||||
|
||||
CHECK_MESSAGE(
|
||||
aabb_big.intersects_segment(Vector3(1, 3, 0), Vector3(0, 3, 0)),
|
||||
"intersects_segment() should return the expected result.");
|
||||
CHECK_MESSAGE(
|
||||
aabb_big.intersects_segment(Vector3(0, 3, 0), Vector3(0, -300, 0)),
|
||||
"intersects_segment() should return the expected result.");
|
||||
CHECK_MESSAGE(
|
||||
aabb_big.intersects_segment(Vector3(-50, 3, -50), Vector3(50, 3, 50)),
|
||||
"intersects_segment() should return the expected result.");
|
||||
CHECK_MESSAGE(
|
||||
!aabb_big.intersects_segment(Vector3(-50, 25, -50), Vector3(50, 25, 50)),
|
||||
"intersects_segment() should return the expected result.");
|
||||
CHECK_MESSAGE(
|
||||
aabb_big.intersects_segment(Vector3(0, 3, 0), Vector3(0, 3, 0)),
|
||||
"intersects_segment() should return the expected result with segment of length 0.");
|
||||
CHECK_MESSAGE(
|
||||
!aabb_big.intersects_segment(Vector3(0, 300, 0), Vector3(0, 300, 0)),
|
||||
"intersects_segment() should return the expected result with segment of length 0.");
|
||||
CHECK_MESSAGE( // Simple ray intersection test.
|
||||
aabb_big.intersects_ray(Vector3(-100, 3, 0), Vector3(1, 0, 0)),
|
||||
"intersects_ray() should return true when ray points directly to AABB from outside.");
|
||||
CHECK_MESSAGE( // Ray parallel to an edge.
|
||||
!aabb_big.intersects_ray(Vector3(10, 10, 0), Vector3(0, 1, 0)),
|
||||
"intersects_ray() should return false for ray parallel and outside of AABB.");
|
||||
CHECK_MESSAGE( // Ray origin inside aabb.
|
||||
aabb_big.intersects_ray(Vector3(1, 1, 1), Vector3(0, 1, 0)),
|
||||
"intersects_ray() should return true for rays originating inside the AABB.");
|
||||
CHECK_MESSAGE( // Ray pointing away from aabb.
|
||||
!aabb_big.intersects_ray(Vector3(-10, 0, 0), Vector3(-1, 0, 0)),
|
||||
"intersects_ray() should return false when ray points away from AABB.");
|
||||
CHECK_MESSAGE( // Ray along a diagonal of aabb.
|
||||
aabb_big.intersects_ray(Vector3(0, 0, 0), Vector3(1, 1, 1)),
|
||||
"intersects_ray() should return true for rays along the AABB diagonal.");
|
||||
CHECK_MESSAGE( // Ray originating at aabb edge.
|
||||
aabb_big.intersects_ray(aabb_big.position, Vector3(-1, 0, 0)),
|
||||
"intersects_ray() should return true for rays starting on AABB's edge.");
|
||||
CHECK_MESSAGE( // Ray with zero direction inside.
|
||||
aabb_big.intersects_ray(Vector3(-1, 3, -2), Vector3(0, 0, 0)),
|
||||
"intersects_ray() should return true because its inside.");
|
||||
CHECK_MESSAGE( // Ray with zero direction outside.
|
||||
!aabb_big.intersects_ray(Vector3(-1000, 3, -2), Vector3(0, 0, 0)),
|
||||
"intersects_ray() should return false for being outside.");
|
||||
|
||||
// Finding ray intersections.
|
||||
constexpr AABB aabb_simple = AABB(Vector3(), Vector3(1, 1, 1));
|
||||
bool inside = false;
|
||||
Vector3 intersection_point;
|
||||
Vector3 intersection_normal;
|
||||
|
||||
// Borders.
|
||||
aabb_simple.find_intersects_ray(Vector3(0.5, 0, 0.5), Vector3(0, 1, 0), inside, &intersection_point, &intersection_normal);
|
||||
CHECK_MESSAGE(inside == false, "find_intersects_ray() should return outside on borders.");
|
||||
CHECK_MESSAGE(intersection_point.is_equal_approx(Vector3(0.5, 0, 0.5)), "find_intersects_ray() border intersection point incorrect.");
|
||||
CHECK_MESSAGE(intersection_normal.is_equal_approx(Vector3(0, -1, 0)), "find_intersects_ray() border intersection normal incorrect.");
|
||||
aabb_simple.find_intersects_ray(Vector3(0.5, 1, 0.5), Vector3(0, -1, 0), inside, &intersection_point, &intersection_normal);
|
||||
CHECK_MESSAGE(inside == false, "find_intersects_ray() should return outside on borders.");
|
||||
CHECK_MESSAGE(intersection_point.is_equal_approx(Vector3(0.5, 1, 0.5)), "find_intersects_ray() border intersection point incorrect.");
|
||||
CHECK_MESSAGE(intersection_normal.is_equal_approx(Vector3(0, 1, 0)), "find_intersects_ray() border intersection normal incorrect.");
|
||||
|
||||
// Inside.
|
||||
aabb_simple.find_intersects_ray(Vector3(0.5, 0.1, 0.5), Vector3(0, 1, 0), inside, &intersection_point, &intersection_normal);
|
||||
CHECK_MESSAGE(inside == true, "find_intersects_ray() should return inside when inside.");
|
||||
CHECK_MESSAGE(intersection_point.is_equal_approx(Vector3(0.5, 0, 0.5)), "find_intersects_ray() inside backtracking intersection point incorrect.");
|
||||
CHECK_MESSAGE(intersection_normal.is_equal_approx(Vector3(0, -1, 0)), "find_intersects_ray() inside intersection normal incorrect.");
|
||||
|
||||
// Zero sized AABB.
|
||||
constexpr AABB aabb_zero = AABB(Vector3(), Vector3(1, 0, 1));
|
||||
aabb_zero.find_intersects_ray(Vector3(0.5, 0, 0.5), Vector3(0, 1, 0), inside, &intersection_point, &intersection_normal);
|
||||
CHECK_MESSAGE(inside == false, "find_intersects_ray() should return outside on borders of zero sized AABB.");
|
||||
CHECK_MESSAGE(intersection_point.is_equal_approx(Vector3(0.5, 0, 0.5)), "find_intersects_ray() border intersection point incorrect for zero sized AABB.");
|
||||
CHECK_MESSAGE(intersection_normal.is_equal_approx(Vector3(0, -1, 0)), "find_intersects_ray() border intersection normal incorrect for zero sized AABB.");
|
||||
aabb_zero.find_intersects_ray(Vector3(0.5, 0, 0.5), Vector3(0, -1, 0), inside, &intersection_point, &intersection_normal);
|
||||
CHECK_MESSAGE(inside == false, "find_intersects_ray() should return outside on borders of zero sized AABB.");
|
||||
CHECK_MESSAGE(intersection_point.is_equal_approx(Vector3(0.5, 0, 0.5)), "find_intersects_ray() border intersection point incorrect for zero sized AABB.");
|
||||
CHECK_MESSAGE(intersection_normal.is_equal_approx(Vector3(0, 1, 0)), "find_intersects_ray() border intersection normal incorrect for zero sized AABB.");
|
||||
aabb_zero.find_intersects_ray(Vector3(0.5, -1, 0.5), Vector3(0, 1, 0), inside, &intersection_point, &intersection_normal);
|
||||
CHECK_MESSAGE(inside == false, "find_intersects_ray() should return outside on borders of zero sized AABB.");
|
||||
CHECK_MESSAGE(intersection_point.is_equal_approx(Vector3(0.5, 0, 0.5)), "find_intersects_ray() border intersection point incorrect for zero sized AABB.");
|
||||
CHECK_MESSAGE(intersection_normal.is_equal_approx(Vector3(0, -1, 0)), "find_intersects_ray() border intersection normal incorrect for zero sized AABB.");
|
||||
}
|
||||
|
||||
TEST_CASE("[AABB] Merging") {
|
||||
constexpr AABB aabb_big = AABB(Vector3(-1.5, 2, -2.5), Vector3(4, 5, 6));
|
||||
|
||||
AABB aabb_small = AABB(Vector3(-1.5, 2, -2.5), Vector3(1, 1, 1));
|
||||
CHECK_MESSAGE(
|
||||
aabb_big.merge(aabb_small).is_equal_approx(aabb_big),
|
||||
"merge() with fully contained AABB (touching the edge) should return the expected result.");
|
||||
|
||||
aabb_small = AABB(Vector3(0.5, 1.5, -2), Vector3(1, 1, 1));
|
||||
CHECK_MESSAGE(
|
||||
aabb_big.merge(aabb_small).is_equal_approx(AABB(Vector3(-1.5, 1.5, -2.5), Vector3(4, 5.5, 6))),
|
||||
"merge() with partially contained AABB (overflowing on Y axis) should return the expected result.");
|
||||
|
||||
aabb_small = AABB(Vector3(10, -10, -10), Vector3(1, 1, 1));
|
||||
CHECK_MESSAGE(
|
||||
aabb_big.merge(aabb_small).is_equal_approx(AABB(Vector3(-1.5, -10, -10), Vector3(12.5, 17, 13.5))),
|
||||
"merge() with non-contained AABB should return the expected result.");
|
||||
}
|
||||
|
||||
TEST_CASE("[AABB] Encloses") {
|
||||
constexpr AABB aabb_big = AABB(Vector3(-1.5, 2, -2.5), Vector3(4, 5, 6));
|
||||
|
||||
CHECK_MESSAGE(
|
||||
aabb_big.encloses(aabb_big),
|
||||
"encloses() with itself should return the expected result.");
|
||||
|
||||
AABB aabb_small = AABB(Vector3(-1.5, 2, -2.5), Vector3(1, 1, 1));
|
||||
CHECK_MESSAGE(
|
||||
aabb_big.encloses(aabb_small),
|
||||
"encloses() with fully contained AABB (touching the edge) should return the expected result.");
|
||||
|
||||
aabb_small = AABB(Vector3(1.5, 6, 2.5), Vector3(1, 1, 1));
|
||||
CHECK_MESSAGE(
|
||||
aabb_big.encloses(aabb_small),
|
||||
"encloses() with fully contained AABB (touching the edge) should return the expected result.");
|
||||
|
||||
aabb_small = AABB(Vector3(0.5, 1.5, -2), Vector3(1, 1, 1));
|
||||
CHECK_MESSAGE(
|
||||
!aabb_big.encloses(aabb_small),
|
||||
"encloses() with partially contained AABB (overflowing on Y axis) should return the expected result.");
|
||||
|
||||
aabb_small = AABB(Vector3(10, -10, -10), Vector3(1, 1, 1));
|
||||
CHECK_MESSAGE(
|
||||
!aabb_big.encloses(aabb_small),
|
||||
"encloses() with non-contained AABB should return the expected result.");
|
||||
}
|
||||
|
||||
TEST_CASE("[AABB] Get endpoints") {
|
||||
constexpr AABB aabb = AABB(Vector3(-1.5, 2, -2.5), Vector3(4, 5, 6));
|
||||
CHECK_MESSAGE(
|
||||
aabb.get_endpoint(0).is_equal_approx(Vector3(-1.5, 2, -2.5)),
|
||||
"The endpoint at index 0 should match the expected value.");
|
||||
CHECK_MESSAGE(
|
||||
aabb.get_endpoint(1).is_equal_approx(Vector3(-1.5, 2, 3.5)),
|
||||
"The endpoint at index 1 should match the expected value.");
|
||||
CHECK_MESSAGE(
|
||||
aabb.get_endpoint(2).is_equal_approx(Vector3(-1.5, 7, -2.5)),
|
||||
"The endpoint at index 2 should match the expected value.");
|
||||
CHECK_MESSAGE(
|
||||
aabb.get_endpoint(3).is_equal_approx(Vector3(-1.5, 7, 3.5)),
|
||||
"The endpoint at index 3 should match the expected value.");
|
||||
CHECK_MESSAGE(
|
||||
aabb.get_endpoint(4).is_equal_approx(Vector3(2.5, 2, -2.5)),
|
||||
"The endpoint at index 4 should match the expected value.");
|
||||
CHECK_MESSAGE(
|
||||
aabb.get_endpoint(5).is_equal_approx(Vector3(2.5, 2, 3.5)),
|
||||
"The endpoint at index 5 should match the expected value.");
|
||||
CHECK_MESSAGE(
|
||||
aabb.get_endpoint(6).is_equal_approx(Vector3(2.5, 7, -2.5)),
|
||||
"The endpoint at index 6 should match the expected value.");
|
||||
CHECK_MESSAGE(
|
||||
aabb.get_endpoint(7).is_equal_approx(Vector3(2.5, 7, 3.5)),
|
||||
"The endpoint at index 7 should match the expected value.");
|
||||
|
||||
ERR_PRINT_OFF;
|
||||
CHECK_MESSAGE(
|
||||
aabb.get_endpoint(8).is_equal_approx(Vector3()),
|
||||
"The endpoint at invalid index 8 should match the expected value.");
|
||||
CHECK_MESSAGE(
|
||||
aabb.get_endpoint(-1).is_equal_approx(Vector3()),
|
||||
"The endpoint at invalid index -1 should match the expected value.");
|
||||
ERR_PRINT_ON;
|
||||
}
|
||||
|
||||
TEST_CASE("[AABB] Get longest/shortest axis") {
|
||||
constexpr AABB aabb = AABB(Vector3(-1.5, 2, -2.5), Vector3(4, 5, 6));
|
||||
CHECK_MESSAGE(
|
||||
aabb.get_longest_axis() == Vector3(0, 0, 1),
|
||||
"get_longest_axis() should return the expected value.");
|
||||
CHECK_MESSAGE(
|
||||
aabb.get_longest_axis_index() == Vector3::AXIS_Z,
|
||||
"get_longest_axis_index() should return the expected value.");
|
||||
CHECK_MESSAGE(
|
||||
aabb.get_longest_axis_size() == 6,
|
||||
"get_longest_axis_size() should return the expected value.");
|
||||
|
||||
CHECK_MESSAGE(
|
||||
aabb.get_shortest_axis() == Vector3(1, 0, 0),
|
||||
"get_shortest_axis() should return the expected value.");
|
||||
CHECK_MESSAGE(
|
||||
aabb.get_shortest_axis_index() == Vector3::AXIS_X,
|
||||
"get_shortest_axis_index() should return the expected value.");
|
||||
CHECK_MESSAGE(
|
||||
aabb.get_shortest_axis_size() == 4,
|
||||
"get_shortest_axis_size() should return the expected value.");
|
||||
}
|
||||
|
||||
TEST_CASE("[AABB] Get support") {
|
||||
constexpr AABB aabb = AABB(Vector3(-1.5, 2, -2.5), Vector3(4, 5, 6));
|
||||
CHECK_MESSAGE(
|
||||
aabb.get_support(Vector3(1, 0, 0)) == Vector3(2.5, 2, -2.5),
|
||||
"get_support() should return the expected value.");
|
||||
CHECK_MESSAGE(
|
||||
aabb.get_support(Vector3(0.5, 1, 1)) == Vector3(2.5, 7, 3.5),
|
||||
"get_support() should return the expected value.");
|
||||
CHECK_MESSAGE(
|
||||
aabb.get_support(Vector3(0.5, 1, -400)) == Vector3(2.5, 7, -2.5),
|
||||
"get_support() should return the expected value.");
|
||||
CHECK_MESSAGE(
|
||||
aabb.get_support(Vector3(0, -1, 0)) == Vector3(-1.5, 2, -2.5),
|
||||
"get_support() should return the expected value.");
|
||||
CHECK_MESSAGE(
|
||||
aabb.get_support(Vector3(0, -0.1, 0)) == Vector3(-1.5, 2, -2.5),
|
||||
"get_support() should return the expected value.");
|
||||
CHECK_MESSAGE(
|
||||
aabb.get_support(Vector3()) == Vector3(-1.5, 2, -2.5),
|
||||
"get_support() should return the AABB position when given a zero vector.");
|
||||
}
|
||||
|
||||
TEST_CASE("[AABB] Grow") {
|
||||
constexpr AABB aabb = AABB(Vector3(-1.5, 2, -2.5), Vector3(4, 5, 6));
|
||||
CHECK_MESSAGE(
|
||||
aabb.grow(0.25).is_equal_approx(AABB(Vector3(-1.75, 1.75, -2.75), Vector3(4.5, 5.5, 6.5))),
|
||||
"grow() with positive value should return the expected AABB.");
|
||||
CHECK_MESSAGE(
|
||||
aabb.grow(-0.25).is_equal_approx(AABB(Vector3(-1.25, 2.25, -2.25), Vector3(3.5, 4.5, 5.5))),
|
||||
"grow() with negative value should return the expected AABB.");
|
||||
CHECK_MESSAGE(
|
||||
aabb.grow(-10).is_equal_approx(AABB(Vector3(8.5, 12, 7.5), Vector3(-16, -15, -14))),
|
||||
"grow() with large negative value should return the expected AABB.");
|
||||
}
|
||||
|
||||
TEST_CASE("[AABB] Has point") {
|
||||
constexpr AABB aabb = AABB(Vector3(-1.5, 2, -2.5), Vector3(4, 5, 6));
|
||||
CHECK_MESSAGE(
|
||||
aabb.has_point(Vector3(-1, 3, 0)),
|
||||
"has_point() with contained point should return the expected value.");
|
||||
CHECK_MESSAGE(
|
||||
aabb.has_point(Vector3(2, 3, 0)),
|
||||
"has_point() with contained point should return the expected value.");
|
||||
CHECK_MESSAGE(
|
||||
!aabb.has_point(Vector3(-20, 0, 0)),
|
||||
"has_point() with non-contained point should return the expected value.");
|
||||
|
||||
CHECK_MESSAGE(
|
||||
aabb.has_point(Vector3(-1.5, 3, 0)),
|
||||
"has_point() with positive size should include point on near face (X axis).");
|
||||
CHECK_MESSAGE(
|
||||
aabb.has_point(Vector3(2.5, 3, 0)),
|
||||
"has_point() with positive size should include point on far face (X axis).");
|
||||
CHECK_MESSAGE(
|
||||
aabb.has_point(Vector3(0, 2, 0)),
|
||||
"has_point() with positive size should include point on near face (Y axis).");
|
||||
CHECK_MESSAGE(
|
||||
aabb.has_point(Vector3(0, 7, 0)),
|
||||
"has_point() with positive size should include point on far face (Y axis).");
|
||||
CHECK_MESSAGE(
|
||||
aabb.has_point(Vector3(0, 3, -2.5)),
|
||||
"has_point() with positive size should include point on near face (Z axis).");
|
||||
CHECK_MESSAGE(
|
||||
aabb.has_point(Vector3(0, 3, 3.5)),
|
||||
"has_point() with positive size should include point on far face (Z axis).");
|
||||
}
|
||||
|
||||
TEST_CASE("[AABB] Expanding") {
|
||||
constexpr AABB aabb = AABB(Vector3(-1.5, 2, -2.5), Vector3(4, 5, 6));
|
||||
CHECK_MESSAGE(
|
||||
aabb.expand(Vector3(-1, 3, 0)).is_equal_approx(aabb),
|
||||
"expand() with contained point should return the expected AABB.");
|
||||
CHECK_MESSAGE(
|
||||
aabb.expand(Vector3(2, 3, 0)).is_equal_approx(aabb),
|
||||
"expand() with contained point should return the expected AABB.");
|
||||
CHECK_MESSAGE(
|
||||
aabb.expand(Vector3(-1.5, 3, 0)).is_equal_approx(aabb),
|
||||
"expand() with contained point on negative edge should return the expected AABB.");
|
||||
CHECK_MESSAGE(
|
||||
aabb.expand(Vector3(2.5, 3, 0)).is_equal_approx(aabb),
|
||||
"expand() with contained point on positive edge should return the expected AABB.");
|
||||
CHECK_MESSAGE(
|
||||
aabb.expand(Vector3(-20, 0, 0)).is_equal_approx(AABB(Vector3(-20, 0, -2.5), Vector3(22.5, 7, 6))),
|
||||
"expand() with non-contained point should return the expected AABB.");
|
||||
}
|
||||
|
||||
TEST_CASE("[AABB] Finite number checks") {
|
||||
constexpr Vector3 x(0, 1, 2);
|
||||
constexpr Vector3 infinite(Math::NaN, Math::NaN, Math::NaN);
|
||||
|
||||
CHECK_MESSAGE(
|
||||
AABB(x, x).is_finite(),
|
||||
"AABB with all components finite should be finite");
|
||||
|
||||
CHECK_FALSE_MESSAGE(
|
||||
AABB(infinite, x).is_finite(),
|
||||
"AABB with one component infinite should not be finite.");
|
||||
CHECK_FALSE_MESSAGE(
|
||||
AABB(x, infinite).is_finite(),
|
||||
"AABB with one component infinite should not be finite.");
|
||||
|
||||
CHECK_FALSE_MESSAGE(
|
||||
AABB(infinite, infinite).is_finite(),
|
||||
"AABB with two components infinite should not be finite.");
|
||||
}
|
||||
|
||||
} // namespace TestAABB
|
||||
359
tests/core/math/test_astar.h
Normal file
@@ -0,0 +1,359 @@
|
||||
/**************************************************************************/
|
||||
/* test_astar.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/math/a_star.h"
|
||||
|
||||
#include "tests/test_macros.h"
|
||||
|
||||
namespace TestAStar {
|
||||
|
||||
class ABCX : public AStar3D {
|
||||
public:
|
||||
enum {
|
||||
A,
|
||||
B,
|
||||
C,
|
||||
X,
|
||||
};
|
||||
|
||||
ABCX() {
|
||||
add_point(A, Vector3(0, 0, 0));
|
||||
add_point(B, Vector3(1, 0, 0));
|
||||
add_point(C, Vector3(0, 1, 0));
|
||||
add_point(X, Vector3(0, 0, 1));
|
||||
connect_points(A, B);
|
||||
connect_points(A, C);
|
||||
connect_points(B, C);
|
||||
connect_points(X, A);
|
||||
}
|
||||
|
||||
// Disable heuristic completely.
|
||||
real_t _compute_cost(int64_t p_from, int64_t p_to) {
|
||||
if (p_from == A && p_to == C) {
|
||||
return 1000;
|
||||
}
|
||||
return 100;
|
||||
}
|
||||
};
|
||||
|
||||
TEST_CASE("[AStar3D] ABC path") {
|
||||
ABCX abcx;
|
||||
Vector<int64_t> path = abcx.get_id_path(ABCX::A, ABCX::C);
|
||||
REQUIRE(path.size() == 3);
|
||||
CHECK(path[0] == ABCX::A);
|
||||
CHECK(path[1] == ABCX::B);
|
||||
CHECK(path[2] == ABCX::C);
|
||||
}
|
||||
|
||||
TEST_CASE("[AStar3D] ABCX path") {
|
||||
ABCX abcx;
|
||||
Vector<int64_t> path = abcx.get_id_path(ABCX::X, ABCX::C);
|
||||
REQUIRE(path.size() == 4);
|
||||
CHECK(path[0] == ABCX::X);
|
||||
CHECK(path[1] == ABCX::A);
|
||||
CHECK(path[2] == ABCX::B);
|
||||
CHECK(path[3] == ABCX::C);
|
||||
}
|
||||
|
||||
TEST_CASE("[AStar3D] Add/Remove") {
|
||||
AStar3D a;
|
||||
|
||||
// Manual tests.
|
||||
a.add_point(1, Vector3(0, 0, 0));
|
||||
a.add_point(2, Vector3(0, 1, 0));
|
||||
a.add_point(3, Vector3(1, 1, 0));
|
||||
a.add_point(4, Vector3(2, 0, 0));
|
||||
a.connect_points(1, 2, true);
|
||||
a.connect_points(1, 3, true);
|
||||
a.connect_points(1, 4, false);
|
||||
|
||||
CHECK(a.are_points_connected(2, 1));
|
||||
CHECK(a.are_points_connected(4, 1));
|
||||
CHECK(a.are_points_connected(2, 1, false));
|
||||
CHECK_FALSE(a.are_points_connected(4, 1, false));
|
||||
|
||||
a.disconnect_points(1, 2, true);
|
||||
CHECK(a.get_point_connections(1).size() == 2); // 3, 4
|
||||
CHECK(a.get_point_connections(2).size() == 0);
|
||||
|
||||
a.disconnect_points(4, 1, false);
|
||||
CHECK(a.get_point_connections(1).size() == 2); // 3, 4
|
||||
CHECK(a.get_point_connections(4).size() == 0);
|
||||
|
||||
a.disconnect_points(4, 1, true);
|
||||
CHECK(a.get_point_connections(1).size() == 1); // 3
|
||||
CHECK(a.get_point_connections(4).size() == 0);
|
||||
|
||||
a.connect_points(2, 3, false);
|
||||
CHECK(a.get_point_connections(2).size() == 1); // 3
|
||||
CHECK(a.get_point_connections(3).size() == 1); // 1
|
||||
|
||||
a.connect_points(2, 3, true);
|
||||
CHECK(a.get_point_connections(2).size() == 1); // 3
|
||||
CHECK(a.get_point_connections(3).size() == 2); // 1, 2
|
||||
|
||||
a.disconnect_points(2, 3, false);
|
||||
CHECK(a.get_point_connections(2).size() == 0);
|
||||
CHECK(a.get_point_connections(3).size() == 2); // 1, 2
|
||||
|
||||
a.connect_points(4, 3, true);
|
||||
CHECK(a.get_point_connections(3).size() == 3); // 1, 2, 4
|
||||
CHECK(a.get_point_connections(4).size() == 1); // 3
|
||||
|
||||
a.disconnect_points(3, 4, false);
|
||||
CHECK(a.get_point_connections(3).size() == 2); // 1, 2
|
||||
CHECK(a.get_point_connections(4).size() == 1); // 3
|
||||
|
||||
a.remove_point(3);
|
||||
CHECK(a.get_point_connections(1).size() == 0);
|
||||
CHECK(a.get_point_connections(2).size() == 0);
|
||||
CHECK(a.get_point_connections(4).size() == 0);
|
||||
|
||||
a.add_point(0, Vector3(0, -1, 0));
|
||||
a.add_point(3, Vector3(2, 1, 0));
|
||||
// 0: (0, -1)
|
||||
// 1: (0, 0)
|
||||
// 2: (0, 1)
|
||||
// 3: (2, 1)
|
||||
// 4: (2, 0)
|
||||
|
||||
// Tests for get_closest_position_in_segment.
|
||||
a.connect_points(2, 3);
|
||||
CHECK(a.get_closest_position_in_segment(Vector3(0.5, 0.5, 0)) == Vector3(0.5, 1, 0));
|
||||
|
||||
a.connect_points(3, 4);
|
||||
a.connect_points(0, 3);
|
||||
a.connect_points(1, 4);
|
||||
a.disconnect_points(1, 4, false);
|
||||
a.disconnect_points(4, 3, false);
|
||||
a.disconnect_points(3, 4, false);
|
||||
// Remaining edges: <2, 3>, <0, 3>, <1, 4> (directed).
|
||||
CHECK(a.get_closest_position_in_segment(Vector3(2, 0.5, 0)) == Vector3(1.75, 0.75, 0));
|
||||
CHECK(a.get_closest_position_in_segment(Vector3(-1, 0.2, 0)) == Vector3(0, 0, 0));
|
||||
CHECK(a.get_closest_position_in_segment(Vector3(3, 2, 0)) == Vector3(2, 1, 0));
|
||||
|
||||
Math::seed(0);
|
||||
|
||||
// Random tests for connectivity checks
|
||||
for (int i = 0; i < 20000; i++) {
|
||||
int u = Math::rand() % 5;
|
||||
int v = Math::rand() % 4;
|
||||
if (u == v) {
|
||||
v = 4;
|
||||
}
|
||||
if (Math::rand() % 2 == 1) {
|
||||
// Add a (possibly existing) directed edge and confirm connectivity.
|
||||
a.connect_points(u, v, false);
|
||||
CHECK(a.are_points_connected(u, v, false));
|
||||
} else {
|
||||
// Remove a (possibly nonexistent) directed edge and confirm disconnectivity.
|
||||
a.disconnect_points(u, v, false);
|
||||
CHECK_FALSE(a.are_points_connected(u, v, false));
|
||||
}
|
||||
}
|
||||
|
||||
// Random tests for point removal.
|
||||
for (int i = 0; i < 20000; i++) {
|
||||
a.clear();
|
||||
for (int j = 0; j < 5; j++) {
|
||||
a.add_point(j, Vector3(0, 0, 0));
|
||||
}
|
||||
|
||||
// Add or remove random edges.
|
||||
for (int j = 0; j < 10; j++) {
|
||||
int u = Math::rand() % 5;
|
||||
int v = Math::rand() % 4;
|
||||
if (u == v) {
|
||||
v = 4;
|
||||
}
|
||||
if (Math::rand() % 2 == 1) {
|
||||
a.connect_points(u, v, false);
|
||||
} else {
|
||||
a.disconnect_points(u, v, false);
|
||||
}
|
||||
}
|
||||
|
||||
// Remove point 0.
|
||||
a.remove_point(0);
|
||||
// White box: this will check all edges remaining in the segments set.
|
||||
for (int j = 1; j < 5; j++) {
|
||||
CHECK_FALSE(a.are_points_connected(0, j, true));
|
||||
}
|
||||
}
|
||||
// It's been great work, cheers. \(^ ^)/
|
||||
}
|
||||
|
||||
TEST_CASE("[Stress][AStar3D] Find paths") {
|
||||
// Random stress tests with Floyd-Warshall.
|
||||
constexpr int N = 30;
|
||||
Math::seed(0);
|
||||
|
||||
for (int test = 0; test < 1000; test++) {
|
||||
AStar3D a;
|
||||
Vector3 p[N];
|
||||
bool adj[N][N] = { { false } };
|
||||
|
||||
// Assign initial coordinates.
|
||||
for (int u = 0; u < N; u++) {
|
||||
p[u].x = Math::rand() % 100;
|
||||
p[u].y = Math::rand() % 100;
|
||||
p[u].z = Math::rand() % 100;
|
||||
a.add_point(u, p[u]);
|
||||
}
|
||||
// Generate a random sequence of operations.
|
||||
for (int i = 0; i < 1000; i++) {
|
||||
// Pick two different vertices.
|
||||
int u, v;
|
||||
u = Math::rand() % N;
|
||||
v = Math::rand() % (N - 1);
|
||||
if (u == v) {
|
||||
v = N - 1;
|
||||
}
|
||||
// Pick a random operation.
|
||||
int op = Math::rand();
|
||||
switch (op % 9) {
|
||||
case 0:
|
||||
case 1:
|
||||
case 2:
|
||||
case 3:
|
||||
case 4:
|
||||
case 5:
|
||||
// Add edge (u, v); possibly bidirectional.
|
||||
a.connect_points(u, v, op % 2);
|
||||
adj[u][v] = true;
|
||||
if (op % 2) {
|
||||
adj[v][u] = true;
|
||||
}
|
||||
break;
|
||||
case 6:
|
||||
case 7:
|
||||
// Remove edge (u, v); possibly bidirectional.
|
||||
a.disconnect_points(u, v, op % 2);
|
||||
adj[u][v] = false;
|
||||
if (op % 2) {
|
||||
adj[v][u] = false;
|
||||
}
|
||||
break;
|
||||
case 8:
|
||||
// Remove point u and add it back; clears adjacent edges and changes coordinates.
|
||||
a.remove_point(u);
|
||||
p[u].x = Math::rand() % 100;
|
||||
p[u].y = Math::rand() % 100;
|
||||
p[u].z = Math::rand() % 100;
|
||||
a.add_point(u, p[u]);
|
||||
for (v = 0; v < N; v++) {
|
||||
adj[u][v] = adj[v][u] = false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Floyd-Warshall.
|
||||
float d[N][N];
|
||||
for (int u = 0; u < N; u++) {
|
||||
for (int v = 0; v < N; v++) {
|
||||
d[u][v] = (u == v || adj[u][v]) ? p[u].distance_to(p[v]) : Math::INF;
|
||||
}
|
||||
}
|
||||
for (int w = 0; w < N; w++) {
|
||||
for (int u = 0; u < N; u++) {
|
||||
for (int v = 0; v < N; v++) {
|
||||
if (d[u][v] > d[u][w] + d[w][v]) {
|
||||
d[u][v] = d[u][w] + d[w][v];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Display statistics.
|
||||
int count = 0;
|
||||
for (int u = 0; u < N; u++) {
|
||||
for (int v = 0; v < N; v++) {
|
||||
if (adj[u][v]) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
print_verbose(vformat("Test #%4d: %3d edges, ", test + 1, count));
|
||||
count = 0;
|
||||
for (int u = 0; u < N; u++) {
|
||||
for (int v = 0; v < N; v++) {
|
||||
if (!Math::is_inf(d[u][v])) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
print_verbose(vformat("%3d/%d pairs of reachable points\n", count - N, N * (N - 1)));
|
||||
|
||||
// Check A*'s output.
|
||||
bool match = true;
|
||||
for (int u = 0; u < N; u++) {
|
||||
for (int v = 0; v < N; v++) {
|
||||
if (u != v) {
|
||||
Vector<int64_t> route = a.get_id_path(u, v);
|
||||
if (!Math::is_inf(d[u][v])) {
|
||||
// Reachable.
|
||||
if (route.size() == 0) {
|
||||
print_verbose(vformat("From %d to %d: A* did not find a path\n", u, v));
|
||||
match = false;
|
||||
goto exit;
|
||||
}
|
||||
float astar_dist = 0;
|
||||
for (int i = 1; i < route.size(); i++) {
|
||||
if (!adj[route[i - 1]][route[i]]) {
|
||||
print_verbose(vformat("From %d to %d: edge (%d, %d) does not exist\n",
|
||||
u, v, route[i - 1], route[i]));
|
||||
match = false;
|
||||
goto exit;
|
||||
}
|
||||
astar_dist += p[route[i - 1]].distance_to(p[route[i]]);
|
||||
}
|
||||
if (!Math::is_equal_approx(astar_dist, d[u][v])) {
|
||||
print_verbose(vformat("From %d to %d: Floyd-Warshall gives %.6f, A* gives %.6f\n",
|
||||
u, v, d[u][v], astar_dist));
|
||||
match = false;
|
||||
goto exit;
|
||||
}
|
||||
} else {
|
||||
// Unreachable.
|
||||
if (route.size() > 0) {
|
||||
print_verbose(vformat("From %d to %d: A* somehow found a nonexistent path\n", u, v));
|
||||
match = false;
|
||||
goto exit;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
exit:
|
||||
CHECK_MESSAGE(match, "Found all paths.");
|
||||
}
|
||||
}
|
||||
} // namespace TestAStar
|
||||
428
tests/core/math/test_basis.h
Normal file
@@ -0,0 +1,428 @@
|
||||
/**************************************************************************/
|
||||
/* test_basis.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/math/basis.h"
|
||||
#include "core/math/random_number_generator.h"
|
||||
|
||||
#include "tests/test_macros.h"
|
||||
|
||||
namespace TestBasis {
|
||||
|
||||
Vector3 deg_to_rad(const Vector3 &p_rotation) {
|
||||
return p_rotation / 180.0 * Math::PI;
|
||||
}
|
||||
|
||||
Vector3 rad2deg(const Vector3 &p_rotation) {
|
||||
return p_rotation / Math::PI * 180.0;
|
||||
}
|
||||
|
||||
String get_rot_order_name(EulerOrder ro) {
|
||||
switch (ro) {
|
||||
case EulerOrder::XYZ:
|
||||
return "XYZ";
|
||||
case EulerOrder::XZY:
|
||||
return "XZY";
|
||||
case EulerOrder::YZX:
|
||||
return "YZX";
|
||||
case EulerOrder::YXZ:
|
||||
return "YXZ";
|
||||
case EulerOrder::ZXY:
|
||||
return "ZXY";
|
||||
case EulerOrder::ZYX:
|
||||
return "ZYX";
|
||||
default:
|
||||
return "[Not supported]";
|
||||
}
|
||||
}
|
||||
|
||||
void test_rotation(Vector3 deg_original_euler, EulerOrder rot_order) {
|
||||
// This test:
|
||||
// 1. Converts the rotation vector from deg to rad.
|
||||
// 2. Converts euler to basis.
|
||||
// 3. Converts the above basis back into euler.
|
||||
// 4. Converts the above euler into basis again.
|
||||
// 5. Compares the basis obtained in step 2 with the basis of step 4
|
||||
//
|
||||
// The conversion "basis to euler", done in the step 3, may be different from
|
||||
// the original euler, even if the final rotation are the same.
|
||||
// This happens because there are more ways to represents the same rotation,
|
||||
// both valid, using eulers.
|
||||
// For this reason is necessary to convert that euler back to basis and finally
|
||||
// compares it.
|
||||
//
|
||||
// In this way we can assert that both functions: basis to euler / euler to basis
|
||||
// are correct.
|
||||
|
||||
// Euler to rotation
|
||||
const Vector3 original_euler = deg_to_rad(deg_original_euler);
|
||||
const Basis to_rotation = Basis::from_euler(original_euler, rot_order);
|
||||
|
||||
// Euler from rotation
|
||||
const Vector3 euler_from_rotation = to_rotation.get_euler(rot_order);
|
||||
const Basis rotation_from_computed_euler = Basis::from_euler(euler_from_rotation, rot_order);
|
||||
|
||||
Basis res = to_rotation.inverse() * rotation_from_computed_euler;
|
||||
|
||||
CHECK_MESSAGE((res.get_column(0) - Vector3(1.0, 0.0, 0.0)).length() <= 0.001, vformat("Fail due to X %s\n", String(res.get_column(0))));
|
||||
CHECK_MESSAGE((res.get_column(1) - Vector3(0.0, 1.0, 0.0)).length() <= 0.001, vformat("Fail due to Y %s\n", String(res.get_column(1))));
|
||||
CHECK_MESSAGE((res.get_column(2) - Vector3(0.0, 0.0, 1.0)).length() <= 0.001, vformat("Fail due to Z %s\n", String(res.get_column(2))));
|
||||
|
||||
// Double check `to_rotation` decomposing with XYZ rotation order.
|
||||
const Vector3 euler_xyz_from_rotation = to_rotation.get_euler(EulerOrder::XYZ);
|
||||
Basis rotation_from_xyz_computed_euler = Basis::from_euler(euler_xyz_from_rotation, EulerOrder::XYZ);
|
||||
|
||||
res = to_rotation.inverse() * rotation_from_xyz_computed_euler;
|
||||
|
||||
CHECK_MESSAGE((res.get_column(0) - Vector3(1.0, 0.0, 0.0)).length() <= 0.001, vformat("Double check with XYZ rot order failed, due to X %s\n", String(res.get_column(0))));
|
||||
CHECK_MESSAGE((res.get_column(1) - Vector3(0.0, 1.0, 0.0)).length() <= 0.001, vformat("Double check with XYZ rot order failed, due to Y %s\n", String(res.get_column(1))));
|
||||
CHECK_MESSAGE((res.get_column(2) - Vector3(0.0, 0.0, 1.0)).length() <= 0.001, vformat("Double check with XYZ rot order failed, due to Z %s\n", String(res.get_column(2))));
|
||||
|
||||
INFO(vformat("Rotation order: %s\n.", get_rot_order_name(rot_order)));
|
||||
INFO(vformat("Original Rotation: %s\n", String(deg_original_euler)));
|
||||
INFO(vformat("Quaternion to rotation order: %s\n", String(rad2deg(euler_from_rotation))));
|
||||
}
|
||||
|
||||
TEST_CASE("[Basis] Euler conversions") {
|
||||
Vector<EulerOrder> euler_order_to_test;
|
||||
euler_order_to_test.push_back(EulerOrder::XYZ);
|
||||
euler_order_to_test.push_back(EulerOrder::XZY);
|
||||
euler_order_to_test.push_back(EulerOrder::YZX);
|
||||
euler_order_to_test.push_back(EulerOrder::YXZ);
|
||||
euler_order_to_test.push_back(EulerOrder::ZXY);
|
||||
euler_order_to_test.push_back(EulerOrder::ZYX);
|
||||
|
||||
Vector<Vector3> vectors_to_test;
|
||||
|
||||
// Test the special cases.
|
||||
vectors_to_test.push_back(Vector3(0.0, 0.0, 0.0));
|
||||
vectors_to_test.push_back(Vector3(0.5, 0.5, 0.5));
|
||||
vectors_to_test.push_back(Vector3(-0.5, -0.5, -0.5));
|
||||
vectors_to_test.push_back(Vector3(40.0, 40.0, 40.0));
|
||||
vectors_to_test.push_back(Vector3(-40.0, -40.0, -40.0));
|
||||
vectors_to_test.push_back(Vector3(0.0, 0.0, -90.0));
|
||||
vectors_to_test.push_back(Vector3(0.0, -90.0, 0.0));
|
||||
vectors_to_test.push_back(Vector3(-90.0, 0.0, 0.0));
|
||||
vectors_to_test.push_back(Vector3(0.0, 0.0, 90.0));
|
||||
vectors_to_test.push_back(Vector3(0.0, 90.0, 0.0));
|
||||
vectors_to_test.push_back(Vector3(90.0, 0.0, 0.0));
|
||||
vectors_to_test.push_back(Vector3(0.0, 0.0, -30.0));
|
||||
vectors_to_test.push_back(Vector3(0.0, -30.0, 0.0));
|
||||
vectors_to_test.push_back(Vector3(-30.0, 0.0, 0.0));
|
||||
vectors_to_test.push_back(Vector3(0.0, 0.0, 30.0));
|
||||
vectors_to_test.push_back(Vector3(0.0, 30.0, 0.0));
|
||||
vectors_to_test.push_back(Vector3(30.0, 0.0, 0.0));
|
||||
vectors_to_test.push_back(Vector3(0.5, 50.0, 20.0));
|
||||
vectors_to_test.push_back(Vector3(-0.5, -50.0, -20.0));
|
||||
vectors_to_test.push_back(Vector3(0.5, 0.0, 90.0));
|
||||
vectors_to_test.push_back(Vector3(0.5, 0.0, -90.0));
|
||||
vectors_to_test.push_back(Vector3(360.0, 360.0, 360.0));
|
||||
vectors_to_test.push_back(Vector3(-360.0, -360.0, -360.0));
|
||||
vectors_to_test.push_back(Vector3(-90.0, 60.0, -90.0));
|
||||
vectors_to_test.push_back(Vector3(90.0, 60.0, -90.0));
|
||||
vectors_to_test.push_back(Vector3(90.0, -60.0, -90.0));
|
||||
vectors_to_test.push_back(Vector3(-90.0, -60.0, -90.0));
|
||||
vectors_to_test.push_back(Vector3(-90.0, 60.0, 90.0));
|
||||
vectors_to_test.push_back(Vector3(90.0, 60.0, 90.0));
|
||||
vectors_to_test.push_back(Vector3(90.0, -60.0, 90.0));
|
||||
vectors_to_test.push_back(Vector3(-90.0, -60.0, 90.0));
|
||||
vectors_to_test.push_back(Vector3(60.0, 90.0, -40.0));
|
||||
vectors_to_test.push_back(Vector3(60.0, -90.0, -40.0));
|
||||
vectors_to_test.push_back(Vector3(-60.0, -90.0, -40.0));
|
||||
vectors_to_test.push_back(Vector3(-60.0, 90.0, 40.0));
|
||||
vectors_to_test.push_back(Vector3(60.0, 90.0, 40.0));
|
||||
vectors_to_test.push_back(Vector3(60.0, -90.0, 40.0));
|
||||
vectors_to_test.push_back(Vector3(-60.0, -90.0, 40.0));
|
||||
vectors_to_test.push_back(Vector3(-90.0, 90.0, -90.0));
|
||||
vectors_to_test.push_back(Vector3(90.0, 90.0, -90.0));
|
||||
vectors_to_test.push_back(Vector3(90.0, -90.0, -90.0));
|
||||
vectors_to_test.push_back(Vector3(-90.0, -90.0, -90.0));
|
||||
vectors_to_test.push_back(Vector3(-90.0, 90.0, 90.0));
|
||||
vectors_to_test.push_back(Vector3(90.0, 90.0, 90.0));
|
||||
vectors_to_test.push_back(Vector3(90.0, -90.0, 90.0));
|
||||
vectors_to_test.push_back(Vector3(20.0, 150.0, 30.0));
|
||||
vectors_to_test.push_back(Vector3(20.0, -150.0, 30.0));
|
||||
vectors_to_test.push_back(Vector3(-120.0, -150.0, 30.0));
|
||||
vectors_to_test.push_back(Vector3(-120.0, -150.0, -130.0));
|
||||
vectors_to_test.push_back(Vector3(120.0, -150.0, -130.0));
|
||||
vectors_to_test.push_back(Vector3(120.0, 150.0, -130.0));
|
||||
vectors_to_test.push_back(Vector3(120.0, 150.0, 130.0));
|
||||
vectors_to_test.push_back(Vector3(89.9, 0.0, 0.0));
|
||||
vectors_to_test.push_back(Vector3(-89.9, 0.0, 0.0));
|
||||
vectors_to_test.push_back(Vector3(0.0, 89.9, 0.0));
|
||||
vectors_to_test.push_back(Vector3(0.0, -89.9, 0.0));
|
||||
vectors_to_test.push_back(Vector3(0.0, 0.0, 89.9));
|
||||
vectors_to_test.push_back(Vector3(0.0, 0.0, -89.9));
|
||||
|
||||
for (int h = 0; h < euler_order_to_test.size(); h += 1) {
|
||||
for (int i = 0; i < vectors_to_test.size(); i += 1) {
|
||||
test_rotation(vectors_to_test[i], euler_order_to_test[h]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("[Stress][Basis] Euler conversions") {
|
||||
Vector<EulerOrder> euler_order_to_test;
|
||||
euler_order_to_test.push_back(EulerOrder::XYZ);
|
||||
euler_order_to_test.push_back(EulerOrder::XZY);
|
||||
euler_order_to_test.push_back(EulerOrder::YZX);
|
||||
euler_order_to_test.push_back(EulerOrder::YXZ);
|
||||
euler_order_to_test.push_back(EulerOrder::ZXY);
|
||||
euler_order_to_test.push_back(EulerOrder::ZYX);
|
||||
|
||||
Vector<Vector3> vectors_to_test;
|
||||
// Add 1000 random vectors with weirds numbers.
|
||||
RandomNumberGenerator rng;
|
||||
for (int _ = 0; _ < 1000; _ += 1) {
|
||||
vectors_to_test.push_back(Vector3(
|
||||
rng.randf_range(-1800, 1800),
|
||||
rng.randf_range(-1800, 1800),
|
||||
rng.randf_range(-1800, 1800)));
|
||||
}
|
||||
|
||||
for (int h = 0; h < euler_order_to_test.size(); h += 1) {
|
||||
for (int i = 0; i < vectors_to_test.size(); i += 1) {
|
||||
test_rotation(vectors_to_test[i], euler_order_to_test[h]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("[Basis] Set axis angle") {
|
||||
Vector3 axis;
|
||||
real_t angle;
|
||||
real_t pi = (real_t)Math::PI;
|
||||
|
||||
// Testing the singularity when the angle is 0°.
|
||||
Basis identity(1, 0, 0, 0, 1, 0, 0, 0, 1);
|
||||
identity.get_axis_angle(axis, angle);
|
||||
CHECK(angle == 0);
|
||||
|
||||
// Testing the singularity when the angle is 180°.
|
||||
Basis singularityPi(-1, 0, 0, 0, 1, 0, 0, 0, -1);
|
||||
singularityPi.get_axis_angle(axis, angle);
|
||||
CHECK(angle == doctest::Approx(pi));
|
||||
|
||||
// Testing reversing the an axis (of an 30° angle).
|
||||
float cos30deg = Math::cos(Math::deg_to_rad((real_t)30.0));
|
||||
Basis z_positive(cos30deg, -0.5, 0, 0.5, cos30deg, 0, 0, 0, 1);
|
||||
Basis z_negative(cos30deg, 0.5, 0, -0.5, cos30deg, 0, 0, 0, 1);
|
||||
|
||||
z_positive.get_axis_angle(axis, angle);
|
||||
CHECK(angle == doctest::Approx(Math::deg_to_rad((real_t)30.0)));
|
||||
CHECK(axis == Vector3(0, 0, 1));
|
||||
|
||||
z_negative.get_axis_angle(axis, angle);
|
||||
CHECK(angle == doctest::Approx(Math::deg_to_rad((real_t)30.0)));
|
||||
CHECK(axis == Vector3(0, 0, -1));
|
||||
|
||||
// Testing a rotation of 90° on x-y-z.
|
||||
Basis x90deg(1, 0, 0, 0, 0, -1, 0, 1, 0);
|
||||
x90deg.get_axis_angle(axis, angle);
|
||||
CHECK(angle == doctest::Approx(pi / (real_t)2));
|
||||
CHECK(axis == Vector3(1, 0, 0));
|
||||
|
||||
Basis y90deg(0, 0, 1, 0, 1, 0, -1, 0, 0);
|
||||
y90deg.get_axis_angle(axis, angle);
|
||||
CHECK(axis == Vector3(0, 1, 0));
|
||||
|
||||
Basis z90deg(0, -1, 0, 1, 0, 0, 0, 0, 1);
|
||||
z90deg.get_axis_angle(axis, angle);
|
||||
CHECK(axis == Vector3(0, 0, 1));
|
||||
|
||||
// Regression test: checks that the method returns a small angle (not 0).
|
||||
Basis tiny(1, 0, 0, 0, 0.9999995, -0.001, 0, 001, 0.9999995); // The min angle possible with float is 0.001rad.
|
||||
tiny.get_axis_angle(axis, angle);
|
||||
CHECK(angle == doctest::Approx(0.001).epsilon(0.0001));
|
||||
|
||||
// Regression test: checks that the method returns an angle which is a number (not NaN)
|
||||
Basis bugNan(1.00000024, 0, 0.000100001693, 0, 1, 0, -0.000100009143, 0, 1.00000024);
|
||||
bugNan.get_axis_angle(axis, angle);
|
||||
CHECK(!Math::is_nan(angle));
|
||||
}
|
||||
|
||||
TEST_CASE("[Basis] Finite number checks") {
|
||||
constexpr Vector3 x(0, 1, 2);
|
||||
constexpr Vector3 infinite(Math::NaN, Math::NaN, Math::NaN);
|
||||
|
||||
CHECK_MESSAGE(
|
||||
Basis(x, x, x).is_finite(),
|
||||
"Basis with all components finite should be finite");
|
||||
|
||||
CHECK_FALSE_MESSAGE(
|
||||
Basis(infinite, x, x).is_finite(),
|
||||
"Basis with one component infinite should not be finite.");
|
||||
CHECK_FALSE_MESSAGE(
|
||||
Basis(x, infinite, x).is_finite(),
|
||||
"Basis with one component infinite should not be finite.");
|
||||
CHECK_FALSE_MESSAGE(
|
||||
Basis(x, x, infinite).is_finite(),
|
||||
"Basis with one component infinite should not be finite.");
|
||||
|
||||
CHECK_FALSE_MESSAGE(
|
||||
Basis(infinite, infinite, x).is_finite(),
|
||||
"Basis with two components infinite should not be finite.");
|
||||
CHECK_FALSE_MESSAGE(
|
||||
Basis(infinite, x, infinite).is_finite(),
|
||||
"Basis with two components infinite should not be finite.");
|
||||
CHECK_FALSE_MESSAGE(
|
||||
Basis(x, infinite, infinite).is_finite(),
|
||||
"Basis with two components infinite should not be finite.");
|
||||
|
||||
CHECK_FALSE_MESSAGE(
|
||||
Basis(infinite, infinite, infinite).is_finite(),
|
||||
"Basis with three components infinite should not be finite.");
|
||||
}
|
||||
|
||||
TEST_CASE("[Basis] Is conformal checks") {
|
||||
CHECK_MESSAGE(
|
||||
Basis().is_conformal(),
|
||||
"Identity Basis should be conformal.");
|
||||
|
||||
CHECK_MESSAGE(
|
||||
Basis::from_euler(Vector3(1.2, 3.4, 5.6)).is_conformal(),
|
||||
"Basis with only rotation should be conformal.");
|
||||
|
||||
CHECK_MESSAGE(
|
||||
Basis::from_scale(Vector3(-1, -1, -1)).is_conformal(),
|
||||
"Basis with only a flip should be conformal.");
|
||||
|
||||
CHECK_MESSAGE(
|
||||
Basis::from_scale(Vector3(1.2, 1.2, 1.2)).is_conformal(),
|
||||
"Basis with only uniform scale should be conformal.");
|
||||
|
||||
CHECK_MESSAGE(
|
||||
Basis(Vector3(3, 4, 0), Vector3(4, -3, 0.0), Vector3(0, 0, 5)).is_conformal(),
|
||||
"Basis with a flip, rotation, and uniform scale should be conformal.");
|
||||
|
||||
CHECK_FALSE_MESSAGE(
|
||||
Basis::from_scale(Vector3(1.2, 3.4, 5.6)).is_conformal(),
|
||||
"Basis with non-uniform scale should not be conformal.");
|
||||
|
||||
CHECK_FALSE_MESSAGE(
|
||||
Basis(Vector3(Math::SQRT12, Math::SQRT12, 0), Vector3(0, 1, 0), Vector3(0, 0, 1)).is_conformal(),
|
||||
"Basis with the X axis skewed 45 degrees should not be conformal.");
|
||||
|
||||
CHECK_MESSAGE(
|
||||
Basis(0, 0, 0, 0, 0, 0, 0, 0, 0).is_conformal(),
|
||||
"Edge case: Basis with all zeroes should return true for is_conformal (because a 0 scale is uniform).");
|
||||
}
|
||||
|
||||
TEST_CASE("[Basis] Is orthogonal checks") {
|
||||
CHECK_MESSAGE(
|
||||
Basis().is_orthogonal(),
|
||||
"Identity Basis should be orthogonal.");
|
||||
|
||||
CHECK_MESSAGE(
|
||||
Basis::from_euler(Vector3(1.2, 3.4, 5.6)).is_orthogonal(),
|
||||
"Basis with only rotation should be orthogonal.");
|
||||
|
||||
CHECK_MESSAGE(
|
||||
Basis::from_scale(Vector3(-1, -1, -1)).is_orthogonal(),
|
||||
"Basis with only a flip should be orthogonal.");
|
||||
|
||||
CHECK_MESSAGE(
|
||||
Basis::from_scale(Vector3(1.2, 3.4, 5.6)).is_orthogonal(),
|
||||
"Basis with only scale should be orthogonal.");
|
||||
|
||||
CHECK_MESSAGE(
|
||||
Basis(Vector3(3, 4, 0), Vector3(4, -3, 0), Vector3(0, 0, 5)).is_orthogonal(),
|
||||
"Basis with a flip, rotation, and uniform scale should be orthogonal.");
|
||||
|
||||
CHECK_FALSE_MESSAGE(
|
||||
Basis(Vector3(Math::SQRT12, Math::SQRT12, 0), Vector3(0, 1, 0), Vector3(0, 0, 1)).is_orthogonal(),
|
||||
"Basis with the X axis skewed 45 degrees should not be orthogonal.");
|
||||
|
||||
CHECK_MESSAGE(
|
||||
Basis(0, 0, 0, 0, 0, 0, 0, 0, 0).is_orthogonal(),
|
||||
"Edge case: Basis with all zeroes should return true for is_orthogonal, since zero vectors are orthogonal to all vectors.");
|
||||
}
|
||||
|
||||
TEST_CASE("[Basis] Is orthonormal checks") {
|
||||
CHECK_MESSAGE(
|
||||
Basis().is_orthonormal(),
|
||||
"Identity Basis should be orthonormal.");
|
||||
|
||||
CHECK_MESSAGE(
|
||||
Basis::from_euler(Vector3(1.2, 3.4, 5.6)).is_orthonormal(),
|
||||
"Basis with only rotation should be orthonormal.");
|
||||
|
||||
CHECK_MESSAGE(
|
||||
Basis::from_scale(Vector3(-1, -1, -1)).is_orthonormal(),
|
||||
"Basis with only a flip should be orthonormal.");
|
||||
|
||||
CHECK_FALSE_MESSAGE(
|
||||
Basis::from_scale(Vector3(1.2, 3.4, 5.6)).is_orthonormal(),
|
||||
"Basis with only scale should not be orthonormal.");
|
||||
|
||||
CHECK_FALSE_MESSAGE(
|
||||
Basis(Vector3(3, 4, 0), Vector3(4, -3, 0), Vector3(0, 0, 5)).is_orthonormal(),
|
||||
"Basis with a flip, rotation, and uniform scale should not be orthonormal.");
|
||||
|
||||
CHECK_FALSE_MESSAGE(
|
||||
Basis(Vector3(Math::SQRT12, Math::SQRT12, 0), Vector3(0, 1, 0), Vector3(0, 0, 1)).is_orthonormal(),
|
||||
"Basis with the X axis skewed 45 degrees should not be orthonormal.");
|
||||
|
||||
CHECK_FALSE_MESSAGE(
|
||||
Basis(0, 0, 0, 0, 0, 0, 0, 0, 0).is_orthonormal(),
|
||||
"Edge case: Basis with all zeroes should return false for is_orthonormal, since the vectors do not have a length of 1.");
|
||||
}
|
||||
|
||||
TEST_CASE("[Basis] Is rotation checks") {
|
||||
CHECK_MESSAGE(
|
||||
Basis().is_rotation(),
|
||||
"Identity Basis should be a rotation (a rotation of zero).");
|
||||
|
||||
CHECK_MESSAGE(
|
||||
Basis::from_euler(Vector3(1.2, 3.4, 5.6)).is_rotation(),
|
||||
"Basis with only rotation should be a rotation.");
|
||||
|
||||
CHECK_FALSE_MESSAGE(
|
||||
Basis::from_scale(Vector3(-1, -1, -1)).is_rotation(),
|
||||
"Basis with only a flip should not be a rotation.");
|
||||
|
||||
CHECK_FALSE_MESSAGE(
|
||||
Basis::from_scale(Vector3(1.2, 3.4, 5.6)).is_rotation(),
|
||||
"Basis with only scale should not be a rotation.");
|
||||
|
||||
CHECK_FALSE_MESSAGE(
|
||||
Basis(Vector3(2, 0, 0), Vector3(0, 0.5, 0), Vector3(0, 0, 1)).is_rotation(),
|
||||
"Basis with a squeeze should not be a rotation.");
|
||||
|
||||
CHECK_FALSE_MESSAGE(
|
||||
Basis(Vector3(Math::SQRT12, Math::SQRT12, 0), Vector3(0, 1, 0), Vector3(0, 0, 1)).is_rotation(),
|
||||
"Basis with the X axis skewed 45 degrees should not be a rotation.");
|
||||
|
||||
CHECK_FALSE_MESSAGE(
|
||||
Basis(0, 0, 0, 0, 0, 0, 0, 0, 0).is_rotation(),
|
||||
"Edge case: Basis with all zeroes should return false for is_rotation, because it is not just a rotation (has a scale of 0).");
|
||||
}
|
||||
|
||||
} // namespace TestBasis
|
||||
228
tests/core/math/test_color.h
Normal file
@@ -0,0 +1,228 @@
|
||||
/**************************************************************************/
|
||||
/* test_color.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/math/color.h"
|
||||
|
||||
#include "tests/test_macros.h"
|
||||
|
||||
namespace TestColor {
|
||||
|
||||
TEST_CASE("[Color] Constructor methods") {
|
||||
constexpr Color blue_rgba = Color(0.25098, 0.376471, 1, 0.501961);
|
||||
const Color blue_html = Color::html("#4060ff80");
|
||||
const Color blue_hex = Color::hex(0x4060ff80);
|
||||
const Color blue_hex64 = Color::hex64(0x4040'6060'ffff'8080);
|
||||
|
||||
CHECK_MESSAGE(
|
||||
blue_rgba.is_equal_approx(blue_html),
|
||||
"Creation with HTML notation should result in components approximately equal to the default constructor.");
|
||||
CHECK_MESSAGE(
|
||||
blue_rgba.is_equal_approx(blue_hex),
|
||||
"Creation with a 32-bit hexadecimal number should result in components approximately equal to the default constructor.");
|
||||
CHECK_MESSAGE(
|
||||
blue_rgba.is_equal_approx(blue_hex64),
|
||||
"Creation with a 64-bit hexadecimal number should result in components approximately equal to the default constructor.");
|
||||
|
||||
ERR_PRINT_OFF;
|
||||
const Color html_invalid = Color::html("invalid");
|
||||
ERR_PRINT_ON;
|
||||
|
||||
CHECK_MESSAGE(
|
||||
html_invalid.is_equal_approx(Color()),
|
||||
"Creation with invalid HTML notation should result in a Color with the default values.");
|
||||
|
||||
constexpr Color green_rgba = Color(0, 1, 0, 0.25);
|
||||
const Color green_hsva = Color(0, 0, 0).from_hsv(120 / 360.0, 1, 1, 0.25);
|
||||
|
||||
CHECK_MESSAGE(
|
||||
green_rgba.is_equal_approx(green_hsva),
|
||||
"Creation with HSV notation should result in components approximately equal to the default constructor.");
|
||||
}
|
||||
|
||||
TEST_CASE("[Color] Operators") {
|
||||
constexpr Color blue = Color(0.2, 0.2, 1);
|
||||
constexpr Color dark_red = Color(0.3, 0.1, 0.1);
|
||||
|
||||
// Color components may be negative. Also, the alpha component may be greater than 1.0.
|
||||
CHECK_MESSAGE(
|
||||
(blue + dark_red).is_equal_approx(Color(0.5, 0.3, 1.1, 2)),
|
||||
"Color addition should behave as expected.");
|
||||
CHECK_MESSAGE(
|
||||
(blue - dark_red).is_equal_approx(Color(-0.1, 0.1, 0.9, 0)),
|
||||
"Color subtraction should behave as expected.");
|
||||
CHECK_MESSAGE(
|
||||
(blue * 2).is_equal_approx(Color(0.4, 0.4, 2, 2)),
|
||||
"Color multiplication with a scalar should behave as expected.");
|
||||
CHECK_MESSAGE(
|
||||
(blue / 2).is_equal_approx(Color(0.1, 0.1, 0.5, 0.5)),
|
||||
"Color division with a scalar should behave as expected.");
|
||||
CHECK_MESSAGE(
|
||||
(blue * dark_red).is_equal_approx(Color(0.06, 0.02, 0.1)),
|
||||
"Color multiplication with another Color should behave as expected.");
|
||||
CHECK_MESSAGE(
|
||||
(blue / dark_red).is_equal_approx(Color(0.666667, 2, 10)),
|
||||
"Color division with another Color should behave as expected.");
|
||||
CHECK_MESSAGE(
|
||||
(-blue).is_equal_approx(Color(0.8, 0.8, 0, 0)),
|
||||
"Color negation should behave as expected (affecting the alpha channel, unlike `invert()`).");
|
||||
}
|
||||
|
||||
TEST_CASE("[Color] Reading methods") {
|
||||
constexpr Color dark_blue = Color(0, 0, 0.5, 0.4);
|
||||
|
||||
CHECK_MESSAGE(
|
||||
dark_blue.get_h() == doctest::Approx(240.0f / 360.0f),
|
||||
"The returned HSV hue should match the expected value.");
|
||||
CHECK_MESSAGE(
|
||||
dark_blue.get_s() == doctest::Approx(1.0f),
|
||||
"The returned HSV saturation should match the expected value.");
|
||||
CHECK_MESSAGE(
|
||||
dark_blue.get_v() == doctest::Approx(0.5f),
|
||||
"The returned HSV value should match the expected value.");
|
||||
}
|
||||
|
||||
TEST_CASE("[Color] Conversion methods") {
|
||||
constexpr Color cyan = Color(0, 1, 1);
|
||||
constexpr Color cyan_transparent = Color(0, 1, 1, 0);
|
||||
|
||||
CHECK_MESSAGE(
|
||||
cyan.to_html() == "00ffffff",
|
||||
"The returned RGB HTML color code should match the expected value.");
|
||||
CHECK_MESSAGE(
|
||||
cyan_transparent.to_html() == "00ffff00",
|
||||
"The returned RGBA HTML color code should match the expected value.");
|
||||
CHECK_MESSAGE(
|
||||
cyan.to_argb32() == 0xff00ffff,
|
||||
"The returned 32-bit RGB number should match the expected value.");
|
||||
CHECK_MESSAGE(
|
||||
cyan.to_abgr32() == 0xffffff00,
|
||||
"The returned 32-bit BGR number should match the expected value.");
|
||||
CHECK_MESSAGE(
|
||||
cyan.to_rgba32() == 0x00ffffff,
|
||||
"The returned 32-bit BGR number should match the expected value.");
|
||||
CHECK_MESSAGE(
|
||||
cyan.to_argb64() == 0xffff'0000'ffff'ffff,
|
||||
"The returned 64-bit RGB number should match the expected value.");
|
||||
CHECK_MESSAGE(
|
||||
cyan.to_abgr64() == 0xffff'ffff'ffff'0000,
|
||||
"The returned 64-bit BGR number should match the expected value.");
|
||||
CHECK_MESSAGE(
|
||||
cyan.to_rgba64() == 0x0000'ffff'ffff'ffff,
|
||||
"The returned 64-bit BGR number should match the expected value.");
|
||||
CHECK_MESSAGE(
|
||||
String(cyan) == "(0.0, 1.0, 1.0, 1.0)",
|
||||
"The string representation should match the expected value.");
|
||||
}
|
||||
|
||||
TEST_CASE("[Color] Linear <-> sRGB conversion") {
|
||||
constexpr Color color = Color(0.35, 0.5, 0.6, 0.7);
|
||||
const Color color_linear = color.srgb_to_linear();
|
||||
const Color color_srgb = color.linear_to_srgb();
|
||||
CHECK_MESSAGE(
|
||||
color_linear.is_equal_approx(Color(0.100481, 0.214041, 0.318547, 0.7)),
|
||||
"The color converted to linear color space should match the expected value.");
|
||||
CHECK_MESSAGE(
|
||||
color_srgb.is_equal_approx(Color(0.62621, 0.735357, 0.797738, 0.7)),
|
||||
"The color converted to sRGB color space should match the expected value.");
|
||||
CHECK_MESSAGE(
|
||||
color_linear.linear_to_srgb().is_equal_approx(Color(0.35, 0.5, 0.6, 0.7)),
|
||||
"The linear color converted back to sRGB color space should match the expected value.");
|
||||
CHECK_MESSAGE(
|
||||
color_srgb.srgb_to_linear().is_equal_approx(Color(0.35, 0.5, 0.6, 0.7)),
|
||||
"The sRGB color converted back to linear color space should match the expected value.");
|
||||
CHECK_MESSAGE(
|
||||
Color(1.0, 1.0, 1.0, 1.0).srgb_to_linear() == (Color(1.0, 1.0, 1.0, 1.0)),
|
||||
"White converted from sRGB to linear should remain white.");
|
||||
CHECK_MESSAGE(
|
||||
Color(1.0, 1.0, 1.0, 1.0).linear_to_srgb() == (Color(1.0, 1.0, 1.0, 1.0)),
|
||||
"White converted from linear to sRGB should remain white.");
|
||||
}
|
||||
|
||||
TEST_CASE("[Color] Named colors") {
|
||||
CHECK_MESSAGE(
|
||||
Color::named("red").is_equal_approx(Color::hex(0xFF0000FF)),
|
||||
"The named color \"red\" should match the expected value.");
|
||||
|
||||
// Named colors have their names automatically normalized.
|
||||
CHECK_MESSAGE(
|
||||
Color::named("white_smoke").is_equal_approx(Color::hex(0xF5F5F5FF)),
|
||||
"The named color \"white_smoke\" should match the expected value.");
|
||||
CHECK_MESSAGE(
|
||||
Color::named("Slate Blue").is_equal_approx(Color::hex(0x6A5ACDFF)),
|
||||
"The named color \"Slate Blue\" should match the expected value.");
|
||||
|
||||
ERR_PRINT_OFF;
|
||||
CHECK_MESSAGE(
|
||||
Color::named("doesn't exist").is_equal_approx(Color()),
|
||||
"The invalid named color \"doesn't exist\" should result in a Color with the default values.");
|
||||
ERR_PRINT_ON;
|
||||
}
|
||||
|
||||
TEST_CASE("[Color] Validation methods") {
|
||||
CHECK_MESSAGE(
|
||||
Color::html_is_valid("#4080ff"),
|
||||
"Valid HTML color (with leading #) should be considered valid.");
|
||||
CHECK_MESSAGE(
|
||||
Color::html_is_valid("4080ff"),
|
||||
"Valid HTML color (without leading #) should be considered valid.");
|
||||
CHECK_MESSAGE(
|
||||
!Color::html_is_valid("12345"),
|
||||
"Invalid HTML color should be considered invalid.");
|
||||
CHECK_MESSAGE(
|
||||
!Color::html_is_valid("#fuf"),
|
||||
"Invalid HTML color should be considered invalid.");
|
||||
}
|
||||
|
||||
TEST_CASE("[Color] Manipulation methods") {
|
||||
constexpr Color blue = Color(0, 0, 1, 0.4);
|
||||
|
||||
CHECK_MESSAGE(
|
||||
blue.inverted().is_equal_approx(Color(1, 1, 0, 0.4)),
|
||||
"Inverted color should have its red, green and blue components inverted.");
|
||||
|
||||
constexpr Color purple = Color(0.5, 0.2, 0.5, 0.25);
|
||||
|
||||
CHECK_MESSAGE(
|
||||
purple.lightened(0.2).is_equal_approx(Color(0.6, 0.36, 0.6, 0.25)),
|
||||
"Color should be lightened by the expected amount.");
|
||||
CHECK_MESSAGE(
|
||||
purple.darkened(0.2).is_equal_approx(Color(0.4, 0.16, 0.4, 0.25)),
|
||||
"Color should be darkened by the expected amount.");
|
||||
|
||||
constexpr Color red = Color(1, 0, 0, 0.2);
|
||||
constexpr Color yellow = Color(1, 1, 0, 0.8);
|
||||
|
||||
CHECK_MESSAGE(
|
||||
red.lerp(yellow, 0.5).is_equal_approx(Color(1, 0.5, 0, 0.5)),
|
||||
"Red interpolated with yellow should be orange (with interpolated alpha).");
|
||||
}
|
||||
} // namespace TestColor
|
||||
490
tests/core/math/test_expression.h
Normal file
@@ -0,0 +1,490 @@
|
||||
/**************************************************************************/
|
||||
/* test_expression.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/math/expression.h"
|
||||
|
||||
#include "tests/test_macros.h"
|
||||
|
||||
namespace TestExpression {
|
||||
|
||||
TEST_CASE("[Expression] Integer arithmetic") {
|
||||
Expression expression;
|
||||
|
||||
CHECK_MESSAGE(
|
||||
expression.parse("-123456") == OK,
|
||||
"Integer identity should parse successfully.");
|
||||
CHECK_MESSAGE(
|
||||
int(expression.execute()) == -123456,
|
||||
"Integer identity should return the expected result.");
|
||||
|
||||
CHECK_MESSAGE(
|
||||
expression.parse("2 + 3") == OK,
|
||||
"Integer addition should parse successfully.");
|
||||
CHECK_MESSAGE(
|
||||
int(expression.execute()) == 5,
|
||||
"Integer addition should return the expected result.");
|
||||
|
||||
CHECK_MESSAGE(
|
||||
expression.parse("999999999999 + 999999999999") == OK,
|
||||
"Large integer addition should parse successfully.");
|
||||
CHECK_MESSAGE(
|
||||
int64_t(expression.execute()) == 1'999'999'999'998,
|
||||
"Large integer addition should return the expected result.");
|
||||
|
||||
CHECK_MESSAGE(
|
||||
expression.parse("25 / 10") == OK,
|
||||
"Integer / integer division should parse successfully.");
|
||||
CHECK_MESSAGE(
|
||||
int(expression.execute()) == 2,
|
||||
"Integer / integer division should return the expected result.");
|
||||
|
||||
CHECK_MESSAGE(
|
||||
expression.parse("2 * (6 + 14) / 2 - 5") == OK,
|
||||
"Integer multiplication-addition-subtraction-division should parse successfully.");
|
||||
CHECK_MESSAGE(
|
||||
int(expression.execute()) == 15,
|
||||
"Integer multiplication-addition-subtraction-division should return the expected result.");
|
||||
}
|
||||
|
||||
TEST_CASE("[Expression] Floating-point arithmetic") {
|
||||
Expression expression;
|
||||
|
||||
CHECK_MESSAGE(
|
||||
expression.parse("-123.456") == OK,
|
||||
"Float identity should parse successfully.");
|
||||
CHECK_MESSAGE(
|
||||
double(expression.execute()) == doctest::Approx(-123.456),
|
||||
"Float identity should return the expected result.");
|
||||
|
||||
CHECK_MESSAGE(
|
||||
expression.parse("2.0 + 3.0") == OK,
|
||||
"Float addition should parse successfully.");
|
||||
CHECK_MESSAGE(
|
||||
double(expression.execute()) == doctest::Approx(5),
|
||||
"Float addition should return the expected result.");
|
||||
|
||||
CHECK_MESSAGE(
|
||||
expression.parse("3.0 / 10") == OK,
|
||||
"Float / integer division should parse successfully.");
|
||||
CHECK_MESSAGE(
|
||||
double(expression.execute()) == doctest::Approx(0.3),
|
||||
"Float / integer division should return the expected result.");
|
||||
|
||||
CHECK_MESSAGE(
|
||||
expression.parse("3 / 10.0") == OK,
|
||||
"Basic integer / float division should parse successfully.");
|
||||
CHECK_MESSAGE(
|
||||
double(expression.execute()) == doctest::Approx(0.3),
|
||||
"Basic integer / float division should return the expected result.");
|
||||
|
||||
CHECK_MESSAGE(
|
||||
expression.parse("3.0 / 10.0") == OK,
|
||||
"Float / float division should parse successfully.");
|
||||
CHECK_MESSAGE(
|
||||
double(expression.execute()) == doctest::Approx(0.3),
|
||||
"Float / float division should return the expected result.");
|
||||
|
||||
CHECK_MESSAGE(
|
||||
expression.parse("2.5 * (6.0 + 14.25) / 2.0 - 5.12345") == OK,
|
||||
"Float multiplication-addition-subtraction-division should parse successfully.");
|
||||
CHECK_MESSAGE(
|
||||
double(expression.execute()) == doctest::Approx(20.18905),
|
||||
"Float multiplication-addition-subtraction-division should return the expected result.");
|
||||
}
|
||||
|
||||
TEST_CASE("[Expression] Floating-point notation") {
|
||||
Expression expression;
|
||||
|
||||
CHECK_MESSAGE(
|
||||
expression.parse("2.") == OK,
|
||||
"The expression should parse successfully.");
|
||||
CHECK_MESSAGE(
|
||||
double(expression.execute()) == doctest::Approx(2.0),
|
||||
"The expression should return the expected result.");
|
||||
|
||||
CHECK_MESSAGE(
|
||||
expression.parse("(2.)") == OK,
|
||||
"The expression should parse successfully.");
|
||||
CHECK_MESSAGE(
|
||||
double(expression.execute()) == doctest::Approx(2.0),
|
||||
"The expression should return the expected result.");
|
||||
|
||||
CHECK_MESSAGE(
|
||||
expression.parse(".3") == OK,
|
||||
"The expression should parse successfully.");
|
||||
CHECK_MESSAGE(
|
||||
double(expression.execute()) == doctest::Approx(0.3),
|
||||
"The expression should return the expected result.");
|
||||
|
||||
CHECK_MESSAGE(
|
||||
expression.parse("2.+5.") == OK,
|
||||
"The expression should parse successfully.");
|
||||
CHECK_MESSAGE(
|
||||
double(expression.execute()) == doctest::Approx(7.0),
|
||||
"The expression should return the expected result.");
|
||||
|
||||
CHECK_MESSAGE(
|
||||
expression.parse(".3-.8") == OK,
|
||||
"The expression should parse successfully.");
|
||||
CHECK_MESSAGE(
|
||||
double(expression.execute()) == doctest::Approx(-0.5),
|
||||
"The expression should return the expected result.");
|
||||
|
||||
CHECK_MESSAGE(
|
||||
expression.parse("2.+.2") == OK,
|
||||
"The expression should parse successfully.");
|
||||
CHECK_MESSAGE(
|
||||
double(expression.execute()) == doctest::Approx(2.2),
|
||||
"The expression should return the expected result.");
|
||||
|
||||
CHECK_MESSAGE(
|
||||
expression.parse(".0*0.") == OK,
|
||||
"The expression should parse successfully.");
|
||||
CHECK_MESSAGE(
|
||||
double(expression.execute()) == doctest::Approx(0.0),
|
||||
"The expression should return the expected result.");
|
||||
}
|
||||
|
||||
TEST_CASE("[Expression] Scientific notation") {
|
||||
Expression expression;
|
||||
|
||||
CHECK_MESSAGE(
|
||||
expression.parse("2.e5") == OK,
|
||||
"The expression should parse successfully.");
|
||||
CHECK_MESSAGE(
|
||||
expression.parse("2.E5") == OK,
|
||||
"The expression should parse successfully.");
|
||||
CHECK_MESSAGE(
|
||||
double(expression.execute()) == doctest::Approx(200'000),
|
||||
"The expression should return the expected result.");
|
||||
|
||||
// The middle "e" is ignored here.
|
||||
CHECK_MESSAGE(
|
||||
expression.parse("2e5") == OK,
|
||||
"The expression should parse successfully.");
|
||||
CHECK_MESSAGE(
|
||||
double(expression.execute()) == doctest::Approx(2e5),
|
||||
"The expression should return the expected result.");
|
||||
|
||||
CHECK_MESSAGE(
|
||||
expression.parse("2e.5") == OK,
|
||||
"The expression should parse successfully.");
|
||||
CHECK_MESSAGE(
|
||||
double(expression.execute()) == doctest::Approx(2),
|
||||
"The expression should return the expected result.");
|
||||
}
|
||||
|
||||
TEST_CASE("[Expression] Underscored numeric literals") {
|
||||
Expression expression;
|
||||
|
||||
CHECK_MESSAGE(
|
||||
expression.parse("1_000_000") == OK,
|
||||
"The expression should parse successfully.");
|
||||
CHECK_MESSAGE(
|
||||
expression.parse("1_000.000") == OK,
|
||||
"The expression should parse successfully.");
|
||||
CHECK_MESSAGE(
|
||||
expression.parse("0xff_99_00") == OK,
|
||||
"The expression should parse successfully.");
|
||||
CHECK_MESSAGE(
|
||||
expression.parse("0Xff_99_00") == OK,
|
||||
"The expression should parse successfully.");
|
||||
CHECK_MESSAGE(
|
||||
expression.parse("0b10_11_00") == OK,
|
||||
"The expression should parse successfully.");
|
||||
CHECK_MESSAGE(
|
||||
expression.parse("0B10_11_00") == OK,
|
||||
"The expression should parse successfully.");
|
||||
}
|
||||
|
||||
TEST_CASE("[Expression] Built-in functions") {
|
||||
Expression expression;
|
||||
|
||||
CHECK_MESSAGE(
|
||||
expression.parse("sqrt(pow(3, 2) + pow(4, 2))") == OK,
|
||||
"The expression should parse successfully.");
|
||||
CHECK_MESSAGE(
|
||||
int(expression.execute()) == 5,
|
||||
"`sqrt(pow(3, 2) + pow(4, 2))` should return the expected result.");
|
||||
|
||||
CHECK_MESSAGE(
|
||||
expression.parse("snapped(sin(0.5), 0.01)") == OK,
|
||||
"The expression should parse successfully.");
|
||||
CHECK_MESSAGE(
|
||||
double(expression.execute()) == doctest::Approx(0.48),
|
||||
"`snapped(sin(0.5), 0.01)` should return the expected result.");
|
||||
|
||||
CHECK_MESSAGE(
|
||||
expression.parse("pow(2.0, -2500)") == OK,
|
||||
"The expression should parse successfully.");
|
||||
CHECK_MESSAGE(
|
||||
Math::is_zero_approx(double(expression.execute())),
|
||||
"`pow(2.0, -2500)` should return the expected result (asymptotically zero).");
|
||||
}
|
||||
|
||||
TEST_CASE("[Expression] Boolean expressions") {
|
||||
Expression expression;
|
||||
|
||||
CHECK_MESSAGE(
|
||||
expression.parse("24 >= 12") == OK,
|
||||
"The boolean expression should parse successfully.");
|
||||
CHECK_MESSAGE(
|
||||
bool(expression.execute()),
|
||||
"The boolean expression should evaluate to `true`.");
|
||||
|
||||
CHECK_MESSAGE(
|
||||
expression.parse("1.0 < 1.25 && 1.25 < 2.0") == OK,
|
||||
"The boolean expression should parse successfully.");
|
||||
CHECK_MESSAGE(
|
||||
bool(expression.execute()),
|
||||
"The boolean expression should evaluate to `true`.");
|
||||
|
||||
CHECK_MESSAGE(
|
||||
expression.parse("!2") == OK,
|
||||
"The boolean expression should parse successfully.");
|
||||
CHECK_MESSAGE(
|
||||
!bool(expression.execute()),
|
||||
"The boolean expression should evaluate to `false`.");
|
||||
|
||||
CHECK_MESSAGE(
|
||||
expression.parse("!!2") == OK,
|
||||
"The boolean expression should parse successfully.");
|
||||
CHECK_MESSAGE(
|
||||
bool(expression.execute()),
|
||||
"The boolean expression should evaluate to `true`.");
|
||||
|
||||
CHECK_MESSAGE(
|
||||
expression.parse("!0") == OK,
|
||||
"The boolean expression should parse successfully.");
|
||||
CHECK_MESSAGE(
|
||||
bool(expression.execute()),
|
||||
"The boolean expression should evaluate to `true`.");
|
||||
|
||||
CHECK_MESSAGE(
|
||||
expression.parse("!!0") == OK,
|
||||
"The boolean expression should parse successfully.");
|
||||
CHECK_MESSAGE(
|
||||
!bool(expression.execute()),
|
||||
"The boolean expression should evaluate to `false`.");
|
||||
|
||||
CHECK_MESSAGE(
|
||||
expression.parse("2 && 5") == OK,
|
||||
"The boolean expression should parse successfully.");
|
||||
CHECK_MESSAGE(
|
||||
bool(expression.execute()),
|
||||
"The boolean expression should evaluate to `true`.");
|
||||
|
||||
CHECK_MESSAGE(
|
||||
expression.parse("0 || 0") == OK,
|
||||
"The boolean expression should parse successfully.");
|
||||
CHECK_MESSAGE(
|
||||
!bool(expression.execute()),
|
||||
"The boolean expression should evaluate to `false`.");
|
||||
|
||||
CHECK_MESSAGE(
|
||||
expression.parse("(2 <= 4) && (2 > 5)") == OK,
|
||||
"The boolean expression should parse successfully.");
|
||||
CHECK_MESSAGE(
|
||||
!bool(expression.execute()),
|
||||
"The boolean expression should evaluate to `false`.");
|
||||
}
|
||||
|
||||
TEST_CASE("[Expression] Expressions with variables") {
|
||||
Expression expression;
|
||||
|
||||
PackedStringArray parameter_names = { "foo", "bar" };
|
||||
CHECK_MESSAGE(
|
||||
expression.parse("foo + bar + 50", parameter_names) == OK,
|
||||
"The expression should parse successfully.");
|
||||
Array values = { 60, 20 };
|
||||
CHECK_MESSAGE(
|
||||
int(expression.execute(values)) == 130,
|
||||
"The expression should return the expected value.");
|
||||
|
||||
PackedStringArray parameter_names_invalid;
|
||||
parameter_names_invalid.push_back("foo");
|
||||
parameter_names_invalid.push_back("baz"); // Invalid parameter name.
|
||||
CHECK_MESSAGE(
|
||||
expression.parse("foo + bar + 50", parameter_names_invalid) == OK,
|
||||
"The expression should parse successfully.");
|
||||
Array values_invalid = { 60, 20 };
|
||||
// Invalid parameters will parse successfully but print an error message when executing.
|
||||
ERR_PRINT_OFF;
|
||||
CHECK_MESSAGE(
|
||||
int(expression.execute(values_invalid)) == 0,
|
||||
"The expression should return the expected value.");
|
||||
ERR_PRINT_ON;
|
||||
|
||||
// Mismatched argument count (more values than parameters).
|
||||
PackedStringArray parameter_names_mismatch = { "foo", "bar" };
|
||||
CHECK_MESSAGE(
|
||||
expression.parse("foo + bar + 50", parameter_names_mismatch) == OK,
|
||||
"The expression should parse successfully.");
|
||||
Array values_mismatch = { 60, 20, 110 };
|
||||
CHECK_MESSAGE(
|
||||
int(expression.execute(values_mismatch)) == 130,
|
||||
"The expression should return the expected value.");
|
||||
|
||||
// Mismatched argument count (more parameters than values).
|
||||
PackedStringArray parameter_names_mismatch2 = { "foo", "bar", "baz" };
|
||||
CHECK_MESSAGE(
|
||||
expression.parse("foo + bar + baz + 50", parameter_names_mismatch2) == OK,
|
||||
"The expression should parse successfully.");
|
||||
Array values_mismatch2 = { 60, 20 };
|
||||
// Having more parameters than values will parse successfully but print an
|
||||
// error message when executing.
|
||||
ERR_PRINT_OFF;
|
||||
CHECK_MESSAGE(
|
||||
int(expression.execute(values_mismatch2)) == 0,
|
||||
"The expression should return the expected value.");
|
||||
ERR_PRINT_ON;
|
||||
}
|
||||
|
||||
TEST_CASE("[Expression] Invalid expressions") {
|
||||
Expression expression;
|
||||
|
||||
CHECK_MESSAGE(
|
||||
expression.parse("\\") == ERR_INVALID_PARAMETER,
|
||||
"The expression shouldn't parse successfully.");
|
||||
|
||||
CHECK_MESSAGE(
|
||||
expression.parse("0++") == ERR_INVALID_PARAMETER,
|
||||
"The expression shouldn't parse successfully.");
|
||||
|
||||
CHECK_MESSAGE(
|
||||
expression.parse("()") == ERR_INVALID_PARAMETER,
|
||||
"The expression shouldn't parse successfully.");
|
||||
|
||||
CHECK_MESSAGE(
|
||||
expression.parse("()()") == ERR_INVALID_PARAMETER,
|
||||
"The expression shouldn't parse successfully.");
|
||||
|
||||
CHECK_MESSAGE(
|
||||
expression.parse("() - ()") == ERR_INVALID_PARAMETER,
|
||||
"The expression shouldn't parse successfully.");
|
||||
|
||||
CHECK_MESSAGE(
|
||||
expression.parse("() * 12345") == ERR_INVALID_PARAMETER,
|
||||
"The expression shouldn't parse successfully.");
|
||||
|
||||
CHECK_MESSAGE(
|
||||
expression.parse("() * 12345") == ERR_INVALID_PARAMETER,
|
||||
"The expression shouldn't parse successfully.");
|
||||
|
||||
CHECK_MESSAGE(
|
||||
expression.parse("123'456") == ERR_INVALID_PARAMETER,
|
||||
"The expression shouldn't parse successfully.");
|
||||
|
||||
CHECK_MESSAGE(
|
||||
expression.parse("123\"456") == ERR_INVALID_PARAMETER,
|
||||
"The expression shouldn't parse successfully.");
|
||||
}
|
||||
|
||||
TEST_CASE("[Expression] Unusual expressions") {
|
||||
Expression expression;
|
||||
|
||||
// Redundant parentheses don't cause a parse error as long as they're matched.
|
||||
CHECK_MESSAGE(
|
||||
expression.parse("(((((((((((((((666)))))))))))))))") == OK,
|
||||
"The expression should parse successfully.");
|
||||
|
||||
// Using invalid identifiers doesn't cause a parse error.
|
||||
ERR_PRINT_OFF;
|
||||
CHECK_MESSAGE(
|
||||
expression.parse("hello + hello") == OK,
|
||||
"The expression should parse successfully.");
|
||||
CHECK_MESSAGE(
|
||||
int(expression.execute()) == 0,
|
||||
"The expression should return the expected result.");
|
||||
ERR_PRINT_ON;
|
||||
|
||||
ERR_PRINT_OFF;
|
||||
CHECK_MESSAGE(
|
||||
expression.parse("$1.00 + ???5") == OK,
|
||||
"The expression should parse successfully.");
|
||||
CHECK_MESSAGE(
|
||||
int(expression.execute()) == 0,
|
||||
"The expression should return the expected result.");
|
||||
ERR_PRINT_ON;
|
||||
|
||||
// Commas can't be used as a decimal parameter.
|
||||
CHECK_MESSAGE(
|
||||
expression.parse("123,456") == OK,
|
||||
"The expression should parse successfully.");
|
||||
CHECK_MESSAGE(
|
||||
int(expression.execute()) == 123,
|
||||
"The expression should return the expected result.");
|
||||
|
||||
// Spaces can't be used as a separator for large numbers.
|
||||
CHECK_MESSAGE(
|
||||
expression.parse("123 456") == OK,
|
||||
"The expression should parse successfully.");
|
||||
CHECK_MESSAGE(
|
||||
int(expression.execute()) == 123,
|
||||
"The expression should return the expected result.");
|
||||
|
||||
// Division by zero is accepted, even though it prints an error message normally.
|
||||
CHECK_MESSAGE(
|
||||
expression.parse("-25.4 / 0") == OK,
|
||||
"The expression should parse successfully.");
|
||||
ERR_PRINT_OFF;
|
||||
CHECK_MESSAGE(
|
||||
Math::is_inf(double(expression.execute())),
|
||||
"`-25.4 / 0` should return inf.");
|
||||
ERR_PRINT_ON;
|
||||
|
||||
CHECK_MESSAGE(
|
||||
expression.parse("0 / 0") == OK,
|
||||
"The expression should parse successfully.");
|
||||
ERR_PRINT_OFF;
|
||||
CHECK_MESSAGE(
|
||||
int(expression.execute()) == 0,
|
||||
"`0 / 0` should return 0.");
|
||||
ERR_PRINT_ON;
|
||||
|
||||
// The tests below currently crash the engine.
|
||||
//
|
||||
//CHECK_MESSAGE(
|
||||
// expression.parse("(-9223372036854775807 - 1) % -1") == OK,
|
||||
// "The expression should parse successfully.");
|
||||
//CHECK_MESSAGE(
|
||||
// int64_t(expression.execute()) == 0,
|
||||
// "`(-9223372036854775807 - 1) % -1` should return the expected result.");
|
||||
//
|
||||
//CHECK_MESSAGE(
|
||||
// expression.parse("(-9223372036854775807 - 1) / -1") == OK,
|
||||
// "The expression should parse successfully.");
|
||||
//CHECK_MESSAGE(
|
||||
// int64_t(expression.execute()) == 0,
|
||||
// "`(-9223372036854775807 - 1) / -1` should return the expected result.");
|
||||
}
|
||||
} // namespace TestExpression
|
||||
892
tests/core/math/test_geometry_2d.h
Normal file
@@ -0,0 +1,892 @@
|
||||
/**************************************************************************/
|
||||
/* test_geometry_2d.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/math/geometry_2d.h"
|
||||
|
||||
#include "thirdparty/doctest/doctest.h"
|
||||
|
||||
namespace TestGeometry2D {
|
||||
|
||||
TEST_CASE("[Geometry2D] Point in circle") {
|
||||
CHECK(Geometry2D::is_point_in_circle(Vector2(0, 0), Vector2(0, 0), 1.0));
|
||||
|
||||
CHECK(Geometry2D::is_point_in_circle(Vector2(0, 0), Vector2(11.99, 0), 12));
|
||||
CHECK(Geometry2D::is_point_in_circle(Vector2(-11.99, 0), Vector2(0, 0), 12));
|
||||
|
||||
CHECK_FALSE(Geometry2D::is_point_in_circle(Vector2(0, 0), Vector2(12.01, 0), 12));
|
||||
CHECK_FALSE(Geometry2D::is_point_in_circle(Vector2(-12.01, 0), Vector2(0, 0), 12));
|
||||
|
||||
CHECK(Geometry2D::is_point_in_circle(Vector2(7, -42), Vector2(4, -40), 3.7));
|
||||
CHECK_FALSE(Geometry2D::is_point_in_circle(Vector2(7, -42), Vector2(4, -40), 3.5));
|
||||
|
||||
// This tests points on the edge of the circle. They are treated as being inside the circle.
|
||||
CHECK(Geometry2D::is_point_in_circle(Vector2(1.0, 0.0), Vector2(0, 0), 1.0));
|
||||
CHECK(Geometry2D::is_point_in_circle(Vector2(0.0, -1.0), Vector2(0, 0), 1.0));
|
||||
}
|
||||
|
||||
TEST_CASE("[Geometry2D] Point in triangle") {
|
||||
CHECK(Geometry2D::is_point_in_triangle(Vector2(0, 0), Vector2(-1, 1), Vector2(0, -1), Vector2(1, 1)));
|
||||
CHECK_FALSE(Geometry2D::is_point_in_triangle(Vector2(-1.01, 1.0), Vector2(-1, 1), Vector2(0, -1), Vector2(1, 1)));
|
||||
|
||||
CHECK(Geometry2D::is_point_in_triangle(Vector2(3, 2.5), Vector2(1, 4), Vector2(3, 2), Vector2(5, 4)));
|
||||
CHECK(Geometry2D::is_point_in_triangle(Vector2(-3, -2.5), Vector2(-1, -4), Vector2(-3, -2), Vector2(-5, -4)));
|
||||
CHECK_FALSE(Geometry2D::is_point_in_triangle(Vector2(0, 0), Vector2(1, 4), Vector2(3, 2), Vector2(5, 4)));
|
||||
|
||||
// This tests points on the edge of the triangle. They are treated as being outside the triangle.
|
||||
// In `is_point_in_circle` and `is_point_in_polygon` they are treated as being inside, so in order the make
|
||||
// the behavior consistent this may change in the future (see issue #44717 and PR #44274).
|
||||
CHECK_FALSE(Geometry2D::is_point_in_triangle(Vector2(1, 1), Vector2(-1, 1), Vector2(0, -1), Vector2(1, 1)));
|
||||
CHECK_FALSE(Geometry2D::is_point_in_triangle(Vector2(0, 1), Vector2(-1, 1), Vector2(0, -1), Vector2(1, 1)));
|
||||
}
|
||||
|
||||
TEST_CASE("[Geometry2D] Point in polygon") {
|
||||
Vector<Vector2> p;
|
||||
CHECK_FALSE(Geometry2D::is_point_in_polygon(Vector2(0, 0), p));
|
||||
|
||||
p.push_back(Vector2(-88, 120));
|
||||
p.push_back(Vector2(-74, -38));
|
||||
p.push_back(Vector2(135, -145));
|
||||
p.push_back(Vector2(425, 70));
|
||||
p.push_back(Vector2(68, 112));
|
||||
p.push_back(Vector2(-120, 370));
|
||||
p.push_back(Vector2(-323, -145));
|
||||
CHECK_FALSE(Geometry2D::is_point_in_polygon(Vector2(-350, 0), p));
|
||||
CHECK_FALSE(Geometry2D::is_point_in_polygon(Vector2(-110, 60), p));
|
||||
CHECK_FALSE(Geometry2D::is_point_in_polygon(Vector2(412, 96), p));
|
||||
CHECK_FALSE(Geometry2D::is_point_in_polygon(Vector2(83, 130), p));
|
||||
CHECK_FALSE(Geometry2D::is_point_in_polygon(Vector2(-320, -153), p));
|
||||
|
||||
CHECK(Geometry2D::is_point_in_polygon(Vector2(0, 0), p));
|
||||
CHECK(Geometry2D::is_point_in_polygon(Vector2(-230, 0), p));
|
||||
CHECK(Geometry2D::is_point_in_polygon(Vector2(130, -110), p));
|
||||
CHECK(Geometry2D::is_point_in_polygon(Vector2(370, 55), p));
|
||||
CHECK(Geometry2D::is_point_in_polygon(Vector2(-160, 190), p));
|
||||
|
||||
// This tests points on the edge of the polygon. They are treated as being inside the polygon.
|
||||
int c = p.size();
|
||||
for (int i = 0; i < c; i++) {
|
||||
const Vector2 &p1 = p[i];
|
||||
CHECK(Geometry2D::is_point_in_polygon(p1, p));
|
||||
|
||||
const Vector2 &p2 = p[(i + 1) % c];
|
||||
Vector2 midpoint((p1 + p2) * 0.5);
|
||||
CHECK(Geometry2D::is_point_in_polygon(midpoint, p));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("[Geometry2D] Polygon clockwise") {
|
||||
Vector<Vector2> p;
|
||||
CHECK_FALSE(Geometry2D::is_polygon_clockwise(p));
|
||||
|
||||
p.push_back(Vector2(5, -5));
|
||||
p.push_back(Vector2(-1, -5));
|
||||
p.push_back(Vector2(-5, -1));
|
||||
p.push_back(Vector2(-1, 3));
|
||||
p.push_back(Vector2(1, 5));
|
||||
CHECK(Geometry2D::is_polygon_clockwise(p));
|
||||
|
||||
p.reverse();
|
||||
CHECK_FALSE(Geometry2D::is_polygon_clockwise(p));
|
||||
}
|
||||
|
||||
TEST_CASE("[Geometry2D] Line intersection") {
|
||||
Vector2 r;
|
||||
CHECK(Geometry2D::line_intersects_line(Vector2(2, 0), Vector2(0, 1), Vector2(0, 2), Vector2(1, 0), r));
|
||||
CHECK(r.is_equal_approx(Vector2(2, 2)));
|
||||
|
||||
CHECK(Geometry2D::line_intersects_line(Vector2(-1, 1), Vector2(1, -1), Vector2(4, 1), Vector2(-1, -1), r));
|
||||
CHECK(r.is_equal_approx(Vector2(1.5, -1.5)));
|
||||
|
||||
CHECK(Geometry2D::line_intersects_line(Vector2(-1, 0), Vector2(-1, -1), Vector2(1, 0), Vector2(1, -1), r));
|
||||
CHECK(r.is_equal_approx(Vector2(0, 1)));
|
||||
|
||||
CHECK_FALSE_MESSAGE(
|
||||
Geometry2D::line_intersects_line(Vector2(-1, 1), Vector2(1, -1), Vector2(0, 1), Vector2(1, -1), r),
|
||||
"Parallel lines should not intersect.");
|
||||
}
|
||||
|
||||
TEST_CASE("[Geometry2D] Segment intersection") {
|
||||
Vector2 r;
|
||||
|
||||
CHECK(Geometry2D::segment_intersects_segment(Vector2(-1, 1), Vector2(1, -1), Vector2(1, 1), Vector2(-1, -1), &r));
|
||||
CHECK(r.is_equal_approx(Vector2(0, 0)));
|
||||
|
||||
CHECK_FALSE(Geometry2D::segment_intersects_segment(Vector2(-1, 1), Vector2(1, -1), Vector2(1, 1), Vector2(0.1, 0.1), &r));
|
||||
CHECK_FALSE(Geometry2D::segment_intersects_segment(Vector2(-1, 1), Vector2(1, -1), Vector2(0.1, 0.1), Vector2(1, 1), &r));
|
||||
|
||||
CHECK_FALSE_MESSAGE(
|
||||
Geometry2D::segment_intersects_segment(Vector2(-1, 1), Vector2(1, -1), Vector2(0, 1), Vector2(2, -1), &r),
|
||||
"Parallel segments should not intersect.");
|
||||
|
||||
CHECK_FALSE_MESSAGE(
|
||||
Geometry2D::segment_intersects_segment(Vector2(1, 2), Vector2(3, 2), Vector2(0, 2), Vector2(-2, 2), &r),
|
||||
"Non-overlapping collinear segments should not intersect.");
|
||||
|
||||
CHECK_MESSAGE(
|
||||
Geometry2D::segment_intersects_segment(Vector2(0, 0), Vector2(0, 1), Vector2(0, 0), Vector2(1, 0), &r),
|
||||
"Touching segments should intersect.");
|
||||
CHECK(r.is_equal_approx(Vector2(0, 0)));
|
||||
|
||||
CHECK_MESSAGE(
|
||||
Geometry2D::segment_intersects_segment(Vector2(0, 1), Vector2(0, 0), Vector2(0, 0), Vector2(1, 0), &r),
|
||||
"Touching segments should intersect.");
|
||||
CHECK(r.is_equal_approx(Vector2(0, 0)));
|
||||
}
|
||||
|
||||
TEST_CASE("[Geometry2D] Segment intersection with circle") {
|
||||
constexpr real_t minus_one = -1.0;
|
||||
constexpr real_t zero = 0.0;
|
||||
constexpr real_t one_quarter = 0.25;
|
||||
constexpr real_t three_quarters = 0.75;
|
||||
constexpr real_t one = 1.0;
|
||||
|
||||
CHECK_MESSAGE(
|
||||
Geometry2D::segment_intersects_circle(Vector2(0, 0), Vector2(4, 0), Vector2(0, 0), 1.0) == doctest::Approx(one_quarter),
|
||||
"Segment from inside to outside of circle should intersect it.");
|
||||
CHECK_MESSAGE(
|
||||
Geometry2D::segment_intersects_circle(Vector2(4, 0), Vector2(0, 0), Vector2(0, 0), 1.0) == doctest::Approx(three_quarters),
|
||||
"Segment from outside to inside of circle should intersect it.");
|
||||
|
||||
CHECK_MESSAGE(
|
||||
Geometry2D::segment_intersects_circle(Vector2(-2, 0), Vector2(2, 0), Vector2(0, 0), 1.0) == doctest::Approx(one_quarter),
|
||||
"Segment running through circle should intersect it.");
|
||||
CHECK_MESSAGE(
|
||||
Geometry2D::segment_intersects_circle(Vector2(2, 0), Vector2(-2, 0), Vector2(0, 0), 1.0) == doctest::Approx(one_quarter),
|
||||
"Segment running through circle should intersect it.");
|
||||
|
||||
CHECK_MESSAGE(
|
||||
Geometry2D::segment_intersects_circle(Vector2(0, 0), Vector2(1, 0), Vector2(0, 0), 1.0) == doctest::Approx(one),
|
||||
"Segment starting inside the circle and ending on the circle should intersect it");
|
||||
CHECK_MESSAGE(
|
||||
Geometry2D::segment_intersects_circle(Vector2(1, 0), Vector2(0, 0), Vector2(0, 0), 1.0) == doctest::Approx(zero),
|
||||
"Segment starting on the circle and going inwards should intersect it");
|
||||
CHECK_MESSAGE(
|
||||
Geometry2D::segment_intersects_circle(Vector2(1, 0), Vector2(2, 0), Vector2(0, 0), 1.0) == doctest::Approx(zero),
|
||||
"Segment starting on the circle and going outwards should intersect it");
|
||||
CHECK_MESSAGE(
|
||||
Geometry2D::segment_intersects_circle(Vector2(2, 0), Vector2(1, 0), Vector2(0, 0), 1.0) == doctest::Approx(one),
|
||||
"Segment starting outside the circle and ending on the circle intersect it");
|
||||
|
||||
CHECK_MESSAGE(
|
||||
Geometry2D::segment_intersects_circle(Vector2(-1, 0), Vector2(1, 0), Vector2(0, 0), 2.0) == doctest::Approx(minus_one),
|
||||
"Segment completely within the circle should not intersect it");
|
||||
CHECK_MESSAGE(
|
||||
Geometry2D::segment_intersects_circle(Vector2(1, 0), Vector2(-1, 0), Vector2(0, 0), 2.0) == doctest::Approx(minus_one),
|
||||
"Segment completely within the circle should not intersect it");
|
||||
CHECK_MESSAGE(
|
||||
Geometry2D::segment_intersects_circle(Vector2(2, 0), Vector2(3, 0), Vector2(0, 0), 1.0) == doctest::Approx(minus_one),
|
||||
"Segment completely outside the circle should not intersect it");
|
||||
CHECK_MESSAGE(
|
||||
Geometry2D::segment_intersects_circle(Vector2(3, 0), Vector2(2, 0), Vector2(0, 0), 1.0) == doctest::Approx(minus_one),
|
||||
"Segment completely outside the circle should not intersect it");
|
||||
}
|
||||
|
||||
TEST_CASE("[Geometry2D] Segment intersection with polygon") {
|
||||
Vector<Point2> a;
|
||||
|
||||
a.push_back(Point2(-2, 2));
|
||||
a.push_back(Point2(3, 4));
|
||||
a.push_back(Point2(1, 1));
|
||||
a.push_back(Point2(2, -2));
|
||||
a.push_back(Point2(-1, -1));
|
||||
|
||||
CHECK_MESSAGE(
|
||||
Geometry2D::is_segment_intersecting_polygon(Vector2(0, 2), Vector2(2, 2), a),
|
||||
"Segment from inside to outside of polygon should intersect it.");
|
||||
CHECK_MESSAGE(
|
||||
Geometry2D::is_segment_intersecting_polygon(Vector2(2, 2), Vector2(0, 2), a),
|
||||
"Segment from outside to inside of polygon should intersect it.");
|
||||
|
||||
CHECK_MESSAGE(
|
||||
Geometry2D::is_segment_intersecting_polygon(Vector2(2, 4), Vector2(3, 3), a),
|
||||
"Segment running through polygon should intersect it.");
|
||||
CHECK_MESSAGE(
|
||||
Geometry2D::is_segment_intersecting_polygon(Vector2(3, 3), Vector2(2, 4), a),
|
||||
"Segment running through polygon should intersect it.");
|
||||
|
||||
CHECK_MESSAGE(
|
||||
Geometry2D::is_segment_intersecting_polygon(Vector2(0, 0), Vector2(1, 1), a),
|
||||
"Segment starting inside the polygon and ending on the polygon should intersect it");
|
||||
CHECK_MESSAGE(
|
||||
Geometry2D::is_segment_intersecting_polygon(Vector2(1, 1), Vector2(0, 0), a),
|
||||
"Segment starting on the polygon and going inwards should intersect it");
|
||||
CHECK_MESSAGE(
|
||||
Geometry2D::is_segment_intersecting_polygon(Vector2(-2, 2), Vector2(-2, -1), a),
|
||||
"Segment starting on the polygon and going outwards should intersect it");
|
||||
CHECK_MESSAGE(
|
||||
Geometry2D::is_segment_intersecting_polygon(Vector2(-2, 1), Vector2(-2, 2), a),
|
||||
"Segment starting outside the polygon and ending on the polygon intersect it");
|
||||
|
||||
CHECK_FALSE_MESSAGE(
|
||||
Geometry2D::is_segment_intersecting_polygon(Vector2(-1, 2), Vector2(1, -1), a),
|
||||
"Segment completely within the polygon should not intersect it");
|
||||
CHECK_FALSE_MESSAGE(
|
||||
Geometry2D::is_segment_intersecting_polygon(Vector2(1, -1), Vector2(-1, 2), a),
|
||||
"Segment completely within the polygon should not intersect it");
|
||||
CHECK_FALSE_MESSAGE(
|
||||
Geometry2D::is_segment_intersecting_polygon(Vector2(2, 2), Vector2(2, -1), a),
|
||||
"Segment completely outside the polygon should not intersect it");
|
||||
CHECK_FALSE_MESSAGE(
|
||||
Geometry2D::is_segment_intersecting_polygon(Vector2(2, -1), Vector2(2, 2), a),
|
||||
"Segment completely outside the polygon should not intersect it");
|
||||
}
|
||||
|
||||
TEST_CASE("[Geometry2D] Closest point to segment") {
|
||||
Vector2 a = Vector2(-4, -4);
|
||||
Vector2 b = Vector2(4, 4);
|
||||
CHECK(Geometry2D::get_closest_point_to_segment(Vector2(4.1, 4.1), a, b).is_equal_approx(Vector2(4, 4)));
|
||||
CHECK(Geometry2D::get_closest_point_to_segment(Vector2(-4.1, -4.1), a, b).is_equal_approx(Vector2(-4, -4)));
|
||||
CHECK(Geometry2D::get_closest_point_to_segment(Vector2(-1, 1), a, b).is_equal_approx(Vector2(0, 0)));
|
||||
|
||||
a = Vector2(1, -2);
|
||||
b = Vector2(1, -2);
|
||||
CHECK_MESSAGE(
|
||||
Geometry2D::get_closest_point_to_segment(Vector2(-3, 4), a, b).is_equal_approx(Vector2(1, -2)),
|
||||
"Line segment is only a single point. This point should be the closest.");
|
||||
}
|
||||
|
||||
TEST_CASE("[Geometry2D] Closest point to uncapped segment") {
|
||||
constexpr Vector2 a = Vector2(-4, -4);
|
||||
constexpr Vector2 b = Vector2(4, 4);
|
||||
CHECK(Geometry2D::get_closest_point_to_segment_uncapped(Vector2(-1, 1), a, b).is_equal_approx(Vector2(0, 0)));
|
||||
CHECK(Geometry2D::get_closest_point_to_segment_uncapped(Vector2(-4, -6), a, b).is_equal_approx(Vector2(-5, -5)));
|
||||
CHECK(Geometry2D::get_closest_point_to_segment_uncapped(Vector2(4, 6), a, b).is_equal_approx(Vector2(5, 5)));
|
||||
}
|
||||
|
||||
TEST_CASE("[Geometry2D] Closest points between segments") {
|
||||
Vector2 c1, c2;
|
||||
// Basis Path Testing suite
|
||||
SUBCASE("[Geometry2D] Both segments degenerate to a point") {
|
||||
Geometry2D::get_closest_points_between_segments(Vector2(0, 0), Vector2(0, 0), Vector2(0, 0), Vector2(0, 0), c1, c2);
|
||||
CHECK(c1.is_equal_approx(Vector2(0, 0)));
|
||||
CHECK(c2.is_equal_approx(Vector2(0, 0)));
|
||||
}
|
||||
|
||||
SUBCASE("[Geometry2D] Closest point on second segment trajectory is above [0,1]") {
|
||||
Geometry2D::get_closest_points_between_segments(Vector2(50, -25), Vector2(50, -10), Vector2(-50, 10), Vector2(-40, 10), c1, c2);
|
||||
CHECK(c1.is_equal_approx(Vector2(50, -10)));
|
||||
CHECK(c2.is_equal_approx(Vector2(-40, 10)));
|
||||
}
|
||||
|
||||
SUBCASE("[Geometry2D] Parallel segments") {
|
||||
Geometry2D::get_closest_points_between_segments(Vector2(2, 1), Vector2(4, 3), Vector2(2, 3), Vector2(4, 5), c1, c2);
|
||||
CHECK(c1.is_equal_approx(Vector2(3, 2)));
|
||||
CHECK(c2.is_equal_approx(Vector2(2, 3)));
|
||||
}
|
||||
|
||||
SUBCASE("[Geometry2D] Closest point on second segment trajectory is within [0,1]") {
|
||||
Geometry2D::get_closest_points_between_segments(Vector2(2, 4), Vector2(2, 3), Vector2(1, 1), Vector2(4, 4), c1, c2);
|
||||
CHECK(c1.is_equal_approx(Vector2(2, 3)));
|
||||
CHECK(c2.is_equal_approx(Vector2(2.5, 2.5)));
|
||||
}
|
||||
|
||||
SUBCASE("[Geometry2D] Closest point on second segment trajectory is below [0,1]") {
|
||||
Geometry2D::get_closest_points_between_segments(Vector2(-20, -20), Vector2(-10, -40), Vector2(10, 25), Vector2(25, 40), c1, c2);
|
||||
CHECK(c1.is_equal_approx(Vector2(-20, -20)));
|
||||
CHECK(c2.is_equal_approx(Vector2(10, 25)));
|
||||
}
|
||||
|
||||
SUBCASE("[Geometry2D] Second segment degenerates to a point") {
|
||||
Geometry2D::get_closest_points_between_segments(Vector2(1, 2), Vector2(2, 1), Vector2(3, 3), Vector2(3, 3), c1, c2);
|
||||
CHECK(c1.is_equal_approx(Vector2(1.5, 1.5)));
|
||||
CHECK(c2.is_equal_approx(Vector2(3, 3)));
|
||||
}
|
||||
|
||||
SUBCASE("[Geometry2D] First segment degenerates to a point") {
|
||||
Geometry2D::get_closest_points_between_segments(Vector2(1, 1), Vector2(1, 1), Vector2(2, 2), Vector2(4, 4), c1, c2);
|
||||
CHECK(c1.is_equal_approx(Vector2(1, 1)));
|
||||
CHECK(c2.is_equal_approx(Vector2(2, 2)));
|
||||
}
|
||||
// End Basis Path Testing suite
|
||||
|
||||
SUBCASE("[Geometry2D] Segments are equal vectors") {
|
||||
Geometry2D::get_closest_points_between_segments(Vector2(2, 2), Vector2(3, 3), Vector2(4, 4), Vector2(4, 5), c1, c2);
|
||||
CHECK(c1.is_equal_approx(Vector2(3, 3)));
|
||||
CHECK(c2.is_equal_approx(Vector2(4, 4)));
|
||||
}
|
||||
|
||||
SUBCASE("[Geometry2D] Standard case") {
|
||||
Geometry2D::get_closest_points_between_segments(Vector2(0, 1), Vector2(-2, -1), Vector2(0, 0), Vector2(2, -2), c1, c2);
|
||||
CHECK(c1.is_equal_approx(Vector2(-0.5, 0.5)));
|
||||
CHECK(c2.is_equal_approx(Vector2(0, 0)));
|
||||
}
|
||||
|
||||
SUBCASE("[Geometry2D] Segments intersect") {
|
||||
Geometry2D::get_closest_points_between_segments(Vector2(-1, 1), Vector2(1, -1), Vector2(1, 1), Vector2(-1, -1), c1, c2);
|
||||
CHECK(c1.is_equal_approx(Vector2(0, 0)));
|
||||
CHECK(c2.is_equal_approx(Vector2(0, 0)));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("[Geometry2D] Make atlas") {
|
||||
Vector<Point2i> result;
|
||||
Size2i size;
|
||||
|
||||
Vector<Size2i> r;
|
||||
r.push_back(Size2i(2, 2));
|
||||
Geometry2D::make_atlas(r, result, size);
|
||||
CHECK(size == Size2i(2, 2));
|
||||
CHECK(result.size() == r.size());
|
||||
|
||||
r.clear();
|
||||
result.clear();
|
||||
r.push_back(Size2i(1, 2));
|
||||
r.push_back(Size2i(3, 4));
|
||||
r.push_back(Size2i(5, 6));
|
||||
r.push_back(Size2i(7, 8));
|
||||
Geometry2D::make_atlas(r, result, size);
|
||||
CHECK(result.size() == r.size());
|
||||
}
|
||||
|
||||
TEST_CASE("[Geometry2D] Polygon intersection") {
|
||||
Vector<Point2> a;
|
||||
Vector<Point2> b;
|
||||
Vector<Vector<Point2>> r;
|
||||
|
||||
a.push_back(Point2(30, 60));
|
||||
a.push_back(Point2(70, 5));
|
||||
a.push_back(Point2(200, 40));
|
||||
a.push_back(Point2(80, 200));
|
||||
|
||||
SUBCASE("[Geometry2D] Both polygons are empty") {
|
||||
r = Geometry2D::intersect_polygons(Vector<Point2>(), Vector<Point2>());
|
||||
CHECK_MESSAGE(r.is_empty(), "Both polygons are empty. The intersection should also be empty.");
|
||||
}
|
||||
|
||||
SUBCASE("[Geometry2D] One polygon is empty") {
|
||||
r = Geometry2D::intersect_polygons(a, b);
|
||||
REQUIRE_MESSAGE(r.is_empty(), "One polygon is empty. The intersection should also be empty.");
|
||||
}
|
||||
|
||||
SUBCASE("[Geometry2D] Basic intersection") {
|
||||
b.push_back(Point2(200, 300));
|
||||
b.push_back(Point2(90, 200));
|
||||
b.push_back(Point2(50, 100));
|
||||
b.push_back(Point2(200, 90));
|
||||
r = Geometry2D::intersect_polygons(a, b);
|
||||
REQUIRE_MESSAGE(r.size() == 1, "The polygons should intersect each other with 1 resulting intersection polygon.");
|
||||
REQUIRE_MESSAGE(r[0].size() == 3, "The resulting intersection polygon should have 3 vertices.");
|
||||
CHECK(r[0][0].is_equal_approx(Point2(86.52174, 191.30436)));
|
||||
CHECK(r[0][1].is_equal_approx(Point2(50, 100)));
|
||||
CHECK(r[0][2].is_equal_approx(Point2(160.52632, 92.63157)));
|
||||
}
|
||||
|
||||
SUBCASE("[Geometry2D] Intersection with one polygon being completely inside the other polygon") {
|
||||
b.push_back(Point2(80, 100));
|
||||
b.push_back(Point2(50, 50));
|
||||
b.push_back(Point2(150, 50));
|
||||
r = Geometry2D::intersect_polygons(a, b);
|
||||
REQUIRE_MESSAGE(r.size() == 1, "The polygons should intersect each other with 1 resulting intersection polygon.");
|
||||
REQUIRE_MESSAGE(r[0].size() == 3, "The resulting intersection polygon should have 3 vertices.");
|
||||
CHECK(r[0][0].is_equal_approx(b[0]));
|
||||
CHECK(r[0][1].is_equal_approx(b[1]));
|
||||
CHECK(r[0][2].is_equal_approx(b[2]));
|
||||
}
|
||||
|
||||
SUBCASE("[Geometry2D] No intersection with 2 non-empty polygons") {
|
||||
b.push_back(Point2(150, 150));
|
||||
b.push_back(Point2(250, 100));
|
||||
b.push_back(Point2(300, 200));
|
||||
r = Geometry2D::intersect_polygons(a, b);
|
||||
REQUIRE_MESSAGE(r.is_empty(), "The polygons should not intersect each other.");
|
||||
}
|
||||
|
||||
SUBCASE("[Geometry2D] Intersection with 2 resulting polygons") {
|
||||
a.clear();
|
||||
a.push_back(Point2(70, 5));
|
||||
a.push_back(Point2(140, 7));
|
||||
a.push_back(Point2(100, 52));
|
||||
a.push_back(Point2(170, 50));
|
||||
a.push_back(Point2(60, 125));
|
||||
b.push_back(Point2(70, 105));
|
||||
b.push_back(Point2(115, 55));
|
||||
b.push_back(Point2(90, 15));
|
||||
b.push_back(Point2(160, 50));
|
||||
r = Geometry2D::intersect_polygons(a, b);
|
||||
REQUIRE_MESSAGE(r.size() == 2, "The polygons should intersect each other with 2 resulting intersection polygons.");
|
||||
REQUIRE_MESSAGE(r[0].size() == 4, "The resulting intersection polygon should have 4 vertices.");
|
||||
CHECK(r[0][0].is_equal_approx(Point2(70, 105)));
|
||||
CHECK(r[0][1].is_equal_approx(Point2(115, 55)));
|
||||
CHECK(r[0][2].is_equal_approx(Point2(112.894737, 51.63158)));
|
||||
CHECK(r[0][3].is_equal_approx(Point2(159.509537, 50.299728)));
|
||||
|
||||
REQUIRE_MESSAGE(r[1].size() == 3, "The intersection polygon should have 3 vertices.");
|
||||
CHECK(r[1][0].is_equal_approx(Point2(119.692307, 29.846149)));
|
||||
CHECK(r[1][1].is_equal_approx(Point2(107.706421, 43.33028)));
|
||||
CHECK(r[1][2].is_equal_approx(Point2(90, 15)));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("[Geometry2D] Merge polygons") {
|
||||
Vector<Point2> a;
|
||||
Vector<Point2> b;
|
||||
Vector<Vector<Point2>> r;
|
||||
|
||||
a.push_back(Point2(225, 180));
|
||||
a.push_back(Point2(160, 230));
|
||||
a.push_back(Point2(20, 212));
|
||||
a.push_back(Point2(50, 115));
|
||||
|
||||
SUBCASE("[Geometry2D] Both polygons are empty") {
|
||||
r = Geometry2D::merge_polygons(Vector<Point2>(), Vector<Point2>());
|
||||
REQUIRE_MESSAGE(r.is_empty(), "Both polygons are empty. The union should also be empty.");
|
||||
}
|
||||
|
||||
SUBCASE("[Geometry2D] One polygon is empty") {
|
||||
r = Geometry2D::merge_polygons(a, b);
|
||||
REQUIRE_MESSAGE(r.size() == 1, "One polygon is non-empty. There should be 1 resulting merged polygon.");
|
||||
REQUIRE_MESSAGE(r[0].size() == 4, "The resulting merged polygon should have 4 vertices.");
|
||||
CHECK(r[0][0].is_equal_approx(a[0]));
|
||||
CHECK(r[0][1].is_equal_approx(a[1]));
|
||||
CHECK(r[0][2].is_equal_approx(a[2]));
|
||||
CHECK(r[0][3].is_equal_approx(a[3]));
|
||||
}
|
||||
|
||||
SUBCASE("[Geometry2D] Basic merge with 2 polygons") {
|
||||
b.push_back(Point2(180, 190));
|
||||
b.push_back(Point2(60, 140));
|
||||
b.push_back(Point2(160, 80));
|
||||
r = Geometry2D::merge_polygons(a, b);
|
||||
REQUIRE_MESSAGE(r.size() == 1, "The merged polygons should result in 1 polygon.");
|
||||
REQUIRE_MESSAGE(r[0].size() == 7, "The resulting merged polygon should have 7 vertices.");
|
||||
CHECK(r[0][0].is_equal_approx(Point2(174.791077, 161.350967)));
|
||||
CHECK(r[0][1].is_equal_approx(Point2(225, 180)));
|
||||
CHECK(r[0][2].is_equal_approx(Point2(160, 230)));
|
||||
CHECK(r[0][3].is_equal_approx(Point2(20, 212)));
|
||||
CHECK(r[0][4].is_equal_approx(Point2(50, 115)));
|
||||
CHECK(r[0][5].is_equal_approx(Point2(81.911758, 126.852943)));
|
||||
CHECK(r[0][6].is_equal_approx(Point2(160, 80)));
|
||||
}
|
||||
|
||||
SUBCASE("[Geometry2D] Merge with 2 resulting merged polygons (outline and hole)") {
|
||||
b.push_back(Point2(180, 190));
|
||||
b.push_back(Point2(140, 125));
|
||||
b.push_back(Point2(60, 140));
|
||||
b.push_back(Point2(160, 80));
|
||||
r = Geometry2D::merge_polygons(a, b);
|
||||
REQUIRE_MESSAGE(r.size() == 2, "The merged polygons should result in 2 polygons.");
|
||||
|
||||
REQUIRE_MESSAGE(!Geometry2D::is_polygon_clockwise(r[0]), "The merged polygon (outline) should be counter-clockwise.");
|
||||
REQUIRE_MESSAGE(r[0].size() == 7, "The resulting merged polygon (outline) should have 7 vertices.");
|
||||
CHECK(r[0][0].is_equal_approx(Point2(174.791077, 161.350967)));
|
||||
CHECK(r[0][1].is_equal_approx(Point2(225, 180)));
|
||||
CHECK(r[0][2].is_equal_approx(Point2(160, 230)));
|
||||
CHECK(r[0][3].is_equal_approx(Point2(20, 212)));
|
||||
CHECK(r[0][4].is_equal_approx(Point2(50, 115)));
|
||||
CHECK(r[0][5].is_equal_approx(Point2(81.911758, 126.852943)));
|
||||
CHECK(r[0][6].is_equal_approx(Point2(160, 80)));
|
||||
|
||||
REQUIRE_MESSAGE(Geometry2D::is_polygon_clockwise(r[1]), "The resulting merged polygon (hole) should be clockwise.");
|
||||
REQUIRE_MESSAGE(r[1].size() == 3, "The resulting merged polygon (hole) should have 3 vertices.");
|
||||
CHECK(r[1][0].is_equal_approx(Point2(98.083069, 132.859421)));
|
||||
CHECK(r[1][1].is_equal_approx(Point2(158.689453, 155.370377)));
|
||||
CHECK(r[1][2].is_equal_approx(Point2(140, 125)));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("[Geometry2D] Clip polygons") {
|
||||
Vector<Point2> a;
|
||||
Vector<Point2> b;
|
||||
Vector<Vector<Point2>> r;
|
||||
|
||||
a.push_back(Point2(225, 180));
|
||||
a.push_back(Point2(160, 230));
|
||||
a.push_back(Point2(20, 212));
|
||||
a.push_back(Point2(50, 115));
|
||||
|
||||
SUBCASE("[Geometry2D] Both polygons are empty") {
|
||||
r = Geometry2D::clip_polygons(Vector<Point2>(), Vector<Point2>());
|
||||
CHECK_MESSAGE(r.is_empty(), "Both polygons are empty. The clip should also be empty.");
|
||||
}
|
||||
|
||||
SUBCASE("[Geometry2D] Basic clip with one result polygon") {
|
||||
b.push_back(Point2(250, 170));
|
||||
b.push_back(Point2(175, 270));
|
||||
b.push_back(Point2(120, 260));
|
||||
b.push_back(Point2(25, 80));
|
||||
r = Geometry2D::clip_polygons(a, b);
|
||||
REQUIRE_MESSAGE(r.size() == 1, "The clipped polygons should result in 1 polygon.");
|
||||
REQUIRE_MESSAGE(r[0].size() == 3, "The resulting clipped polygon should have 3 vertices.");
|
||||
CHECK(r[0][0].is_equal_approx(Point2(100.102173, 222.298843)));
|
||||
CHECK(r[0][1].is_equal_approx(Point2(20, 212)));
|
||||
CHECK(r[0][2].is_equal_approx(Point2(47.588089, 122.798492)));
|
||||
}
|
||||
|
||||
SUBCASE("[Geometry2D] Polygon b completely overlaps polygon a") {
|
||||
b.push_back(Point2(250, 170));
|
||||
b.push_back(Point2(175, 270));
|
||||
b.push_back(Point2(10, 210));
|
||||
b.push_back(Point2(55, 80));
|
||||
r = Geometry2D::clip_polygons(a, b);
|
||||
CHECK_MESSAGE(r.is_empty(), "Polygon 'b' completely overlaps polygon 'a'. This should result in no clipped polygons.");
|
||||
}
|
||||
|
||||
SUBCASE("[Geometry2D] Polygon a completely overlaps polygon b") {
|
||||
b.push_back(Point2(150, 200));
|
||||
b.push_back(Point2(65, 190));
|
||||
b.push_back(Point2(80, 140));
|
||||
r = Geometry2D::clip_polygons(a, b);
|
||||
REQUIRE_MESSAGE(r.size() == 2, "Polygon 'a' completely overlaps polygon 'b'. This should result in 2 clipped polygons.");
|
||||
REQUIRE_MESSAGE(r[0].size() == 4, "The resulting clipped polygon should have 4 vertices.");
|
||||
REQUIRE_MESSAGE(!Geometry2D::is_polygon_clockwise(r[0]), "The resulting clipped polygon (outline) should be counter-clockwise.");
|
||||
CHECK(r[0][0].is_equal_approx(a[0]));
|
||||
CHECK(r[0][1].is_equal_approx(a[1]));
|
||||
CHECK(r[0][2].is_equal_approx(a[2]));
|
||||
CHECK(r[0][3].is_equal_approx(a[3]));
|
||||
REQUIRE_MESSAGE(r[1].size() == 3, "The resulting clipped polygon should have 3 vertices.");
|
||||
REQUIRE_MESSAGE(Geometry2D::is_polygon_clockwise(r[1]), "The resulting clipped polygon (hole) should be clockwise.");
|
||||
CHECK(r[1][0].is_equal_approx(b[1]));
|
||||
CHECK(r[1][1].is_equal_approx(b[0]));
|
||||
CHECK(r[1][2].is_equal_approx(b[2]));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("[Geometry2D] Exclude polygons") {
|
||||
Vector<Point2> a;
|
||||
Vector<Point2> b;
|
||||
Vector<Vector<Point2>> r;
|
||||
|
||||
a.push_back(Point2(225, 180));
|
||||
a.push_back(Point2(160, 230));
|
||||
a.push_back(Point2(20, 212));
|
||||
a.push_back(Point2(50, 115));
|
||||
|
||||
SUBCASE("[Geometry2D] Both polygons are empty") {
|
||||
r = Geometry2D::exclude_polygons(Vector<Point2>(), Vector<Point2>());
|
||||
CHECK_MESSAGE(r.is_empty(), "Both polygons are empty. The excluded polygon should also be empty.");
|
||||
}
|
||||
|
||||
SUBCASE("[Geometry2D] One polygon is empty") {
|
||||
r = Geometry2D::exclude_polygons(a, b);
|
||||
REQUIRE_MESSAGE(r.size() == 1, "One polygon is non-empty. There should be 1 resulting excluded polygon.");
|
||||
REQUIRE_MESSAGE(r[0].size() == 4, "The resulting excluded polygon should have 4 vertices.");
|
||||
CHECK(r[0][0].is_equal_approx(a[0]));
|
||||
CHECK(r[0][1].is_equal_approx(a[1]));
|
||||
CHECK(r[0][2].is_equal_approx(a[2]));
|
||||
CHECK(r[0][3].is_equal_approx(a[3]));
|
||||
}
|
||||
|
||||
SUBCASE("[Geometry2D] Exclude with 2 resulting polygons (outline and hole)") {
|
||||
b.push_back(Point2(140, 160));
|
||||
b.push_back(Point2(150, 220));
|
||||
b.push_back(Point2(40, 200));
|
||||
b.push_back(Point2(60, 140));
|
||||
r = Geometry2D::exclude_polygons(a, b);
|
||||
REQUIRE_MESSAGE(r.size() == 2, "There should be 2 resulting excluded polygons (outline and hole).");
|
||||
REQUIRE_MESSAGE(r[0].size() == 4, "The resulting excluded polygon should have 4 vertices.");
|
||||
REQUIRE_MESSAGE(!Geometry2D::is_polygon_clockwise(r[0]), "The resulting excluded polygon (outline) should be counter-clockwise.");
|
||||
CHECK(r[0][0].is_equal_approx(a[0]));
|
||||
CHECK(r[0][1].is_equal_approx(a[1]));
|
||||
CHECK(r[0][2].is_equal_approx(a[2]));
|
||||
CHECK(r[0][3].is_equal_approx(a[3]));
|
||||
REQUIRE_MESSAGE(r[1].size() == 4, "The resulting excluded polygon should have 4 vertices.");
|
||||
REQUIRE_MESSAGE(Geometry2D::is_polygon_clockwise(r[1]), "The resulting excluded polygon (hole) should be clockwise.");
|
||||
CHECK(r[1][0].is_equal_approx(Point2(40, 200)));
|
||||
CHECK(r[1][1].is_equal_approx(Point2(150, 220)));
|
||||
CHECK(r[1][2].is_equal_approx(Point2(140, 160)));
|
||||
CHECK(r[1][3].is_equal_approx(Point2(60, 140)));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("[Geometry2D] Intersect polyline with polygon") {
|
||||
Vector<Vector2> l;
|
||||
Vector<Vector2> p;
|
||||
Vector<Vector<Point2>> r;
|
||||
|
||||
l.push_back(Vector2(100, 90));
|
||||
l.push_back(Vector2(120, 250));
|
||||
|
||||
p.push_back(Vector2(225, 180));
|
||||
p.push_back(Vector2(160, 230));
|
||||
p.push_back(Vector2(20, 212));
|
||||
p.push_back(Vector2(50, 115));
|
||||
|
||||
SUBCASE("[Geometry2D] Both line and polygon are empty") {
|
||||
r = Geometry2D::intersect_polyline_with_polygon(Vector<Vector2>(), Vector<Vector2>());
|
||||
CHECK_MESSAGE(r.is_empty(), "Both line and polygon are empty. The intersection line should also be empty.");
|
||||
}
|
||||
|
||||
SUBCASE("[Geometry2D] Line is non-empty and polygon is empty") {
|
||||
r = Geometry2D::intersect_polyline_with_polygon(l, Vector<Vector2>());
|
||||
CHECK_MESSAGE(r.is_empty(), "The polygon is empty while the line is non-empty. The intersection line should be empty.");
|
||||
}
|
||||
|
||||
SUBCASE("[Geometry2D] Basic intersection with 1 resulting intersection line") {
|
||||
r = Geometry2D::intersect_polyline_with_polygon(l, p);
|
||||
REQUIRE_MESSAGE(r.size() == 1, "There should be 1 resulting intersection line.");
|
||||
REQUIRE_MESSAGE(r[0].size() == 2, "The resulting intersection line should have 2 vertices.");
|
||||
CHECK(r[0][0].is_equal_approx(Vector2(105.711609, 135.692886)));
|
||||
CHECK(r[0][1].is_equal_approx(Vector2(116.805809, 224.446457)));
|
||||
}
|
||||
|
||||
SUBCASE("[Geometry2D] Complex intersection with 2 resulting intersection lines") {
|
||||
l.clear();
|
||||
l.push_back(Vector2(100, 90));
|
||||
l.push_back(Vector2(190, 255));
|
||||
l.push_back(Vector2(135, 260));
|
||||
l.push_back(Vector2(57, 200));
|
||||
l.push_back(Vector2(50, 170));
|
||||
l.push_back(Vector2(15, 155));
|
||||
r = Geometry2D::intersect_polyline_with_polygon(l, p);
|
||||
REQUIRE_MESSAGE(r.size() == 2, "There should be 2 resulting intersection lines.");
|
||||
REQUIRE_MESSAGE(r[0].size() == 2, "The resulting intersection line should have 2 vertices.");
|
||||
CHECK(r[0][0].is_equal_approx(Vector2(129.804565, 144.641693)));
|
||||
CHECK(r[0][1].is_equal_approx(Vector2(171.527084, 221.132996)));
|
||||
REQUIRE_MESSAGE(r[1].size() == 4, "The resulting intersection line should have 4 vertices.");
|
||||
CHECK(r[1][0].is_equal_approx(Vector2(83.15609, 220.120087)));
|
||||
CHECK(r[1][1].is_equal_approx(Vector2(57, 200)));
|
||||
CHECK(r[1][2].is_equal_approx(Vector2(50, 170)));
|
||||
CHECK(r[1][3].is_equal_approx(Vector2(34.980492, 163.563065)));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("[Geometry2D] Clip polyline with polygon") {
|
||||
Vector<Vector2> l;
|
||||
Vector<Vector2> p;
|
||||
Vector<Vector<Point2>> r;
|
||||
|
||||
l.push_back(Vector2(70, 140));
|
||||
l.push_back(Vector2(160, 320));
|
||||
|
||||
p.push_back(Vector2(225, 180));
|
||||
p.push_back(Vector2(160, 230));
|
||||
p.push_back(Vector2(20, 212));
|
||||
p.push_back(Vector2(50, 115));
|
||||
|
||||
SUBCASE("[Geometry2D] Both line and polygon are empty") {
|
||||
r = Geometry2D::clip_polyline_with_polygon(Vector<Vector2>(), Vector<Vector2>());
|
||||
CHECK_MESSAGE(r.is_empty(), "Both line and polygon are empty. The clipped line should also be empty.");
|
||||
}
|
||||
|
||||
SUBCASE("[Geometry2D] Polygon is empty and line is non-empty") {
|
||||
r = Geometry2D::clip_polyline_with_polygon(l, Vector<Vector2>());
|
||||
REQUIRE_MESSAGE(r.size() == 1, "There should be 1 resulting clipped line.");
|
||||
REQUIRE_MESSAGE(r[0].size() == 2, "The resulting clipped line should have 2 vertices.");
|
||||
CHECK(r[0][0].is_equal_approx(l[0]));
|
||||
CHECK(r[0][1].is_equal_approx(l[1]));
|
||||
}
|
||||
|
||||
SUBCASE("[Geometry2D] Basic clip with 1 resulting clipped line") {
|
||||
r = Geometry2D::clip_polyline_with_polygon(l, p);
|
||||
REQUIRE_MESSAGE(r.size() == 1, "There should be 1 resulting clipped line.");
|
||||
REQUIRE_MESSAGE(r[0].size() == 2, "The resulting clipped line should have 2 vertices.");
|
||||
CHECK(r[0][0].is_equal_approx(Vector2(111.908401, 223.816803)));
|
||||
CHECK(r[0][1].is_equal_approx(Vector2(160, 320)));
|
||||
}
|
||||
|
||||
SUBCASE("[Geometry2D] Complex clip with 2 resulting clipped lines") {
|
||||
l.clear();
|
||||
l.push_back(Vector2(55, 70));
|
||||
l.push_back(Vector2(50, 190));
|
||||
l.push_back(Vector2(120, 165));
|
||||
l.push_back(Vector2(122, 250));
|
||||
l.push_back(Vector2(160, 320));
|
||||
r = Geometry2D::clip_polyline_with_polygon(l, p);
|
||||
REQUIRE_MESSAGE(r.size() == 2, "There should be 2 resulting clipped lines.");
|
||||
REQUIRE_MESSAGE(r[0].size() == 3, "The resulting clipped line should have 3 vertices.");
|
||||
CHECK(r[0][0].is_equal_approx(Vector2(121.412682, 225.038757)));
|
||||
CHECK(r[0][1].is_equal_approx(Vector2(122, 250)));
|
||||
CHECK(r[0][2].is_equal_approx(Vector2(160, 320)));
|
||||
REQUIRE_MESSAGE(r[1].size() == 2, "The resulting clipped line should have 2 vertices.");
|
||||
CHECK(r[1][0].is_equal_approx(Vector2(55, 70)));
|
||||
CHECK(r[1][1].is_equal_approx(Vector2(53.07737, 116.143021)));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("[Geometry2D] Convex hull") {
|
||||
Vector<Point2> a;
|
||||
Vector<Point2> r;
|
||||
|
||||
a.push_back(Point2(-4, -8));
|
||||
a.push_back(Point2(-10, -4));
|
||||
a.push_back(Point2(8, 2));
|
||||
a.push_back(Point2(-6, 10));
|
||||
a.push_back(Point2(-12, 4));
|
||||
a.push_back(Point2(10, -8));
|
||||
a.push_back(Point2(4, 8));
|
||||
|
||||
SUBCASE("[Geometry2D] No points") {
|
||||
r = Geometry2D::convex_hull(Vector<Vector2>());
|
||||
|
||||
CHECK_MESSAGE(r.is_empty(), "The convex hull should be empty if there are no input points.");
|
||||
}
|
||||
|
||||
SUBCASE("[Geometry2D] Single point") {
|
||||
Vector<Point2> b;
|
||||
b.push_back(Point2(4, -3));
|
||||
|
||||
r = Geometry2D::convex_hull(b);
|
||||
REQUIRE_MESSAGE(r.size() == 1, "Convex hull should contain 1 point.");
|
||||
CHECK(r[0].is_equal_approx(b[0]));
|
||||
}
|
||||
|
||||
SUBCASE("[Geometry2D] All points form the convex hull") {
|
||||
r = Geometry2D::convex_hull(a);
|
||||
REQUIRE_MESSAGE(r.size() == 8, "Convex hull should contain 8 points.");
|
||||
CHECK(r[0].is_equal_approx(Point2(-12, 4)));
|
||||
CHECK(r[1].is_equal_approx(Point2(-10, -4)));
|
||||
CHECK(r[2].is_equal_approx(Point2(-4, -8)));
|
||||
CHECK(r[3].is_equal_approx(Point2(10, -8)));
|
||||
CHECK(r[4].is_equal_approx(Point2(8, 2)));
|
||||
CHECK(r[5].is_equal_approx(Point2(4, 8)));
|
||||
CHECK(r[6].is_equal_approx(Point2(-6, 10)));
|
||||
CHECK(r[7].is_equal_approx(Point2(-12, 4)));
|
||||
}
|
||||
|
||||
SUBCASE("[Geometry2D] Add extra points inside original convex hull") {
|
||||
a.push_back(Point2(-4, -8));
|
||||
a.push_back(Point2(0, 0));
|
||||
a.push_back(Point2(0, 8));
|
||||
a.push_back(Point2(-10, -3));
|
||||
a.push_back(Point2(9, -4));
|
||||
a.push_back(Point2(6, 4));
|
||||
|
||||
r = Geometry2D::convex_hull(a);
|
||||
REQUIRE_MESSAGE(r.size() == 8, "Convex hull should contain 8 points.");
|
||||
CHECK(r[0].is_equal_approx(Point2(-12, 4)));
|
||||
CHECK(r[1].is_equal_approx(Point2(-10, -4)));
|
||||
CHECK(r[2].is_equal_approx(Point2(-4, -8)));
|
||||
CHECK(r[3].is_equal_approx(Point2(10, -8)));
|
||||
CHECK(r[4].is_equal_approx(Point2(8, 2)));
|
||||
CHECK(r[5].is_equal_approx(Point2(4, 8)));
|
||||
CHECK(r[6].is_equal_approx(Point2(-6, 10)));
|
||||
CHECK(r[7].is_equal_approx(Point2(-12, 4)));
|
||||
}
|
||||
|
||||
SUBCASE("[Geometry2D] Add extra points on border of original convex hull") {
|
||||
a.push_back(Point2(9, -3));
|
||||
a.push_back(Point2(-2, -8));
|
||||
|
||||
r = Geometry2D::convex_hull(a);
|
||||
REQUIRE_MESSAGE(r.size() == 8, "Convex hull should contain 8 points.");
|
||||
CHECK(r[0].is_equal_approx(Point2(-12, 4)));
|
||||
CHECK(r[1].is_equal_approx(Point2(-10, -4)));
|
||||
CHECK(r[2].is_equal_approx(Point2(-4, -8)));
|
||||
CHECK(r[3].is_equal_approx(Point2(10, -8)));
|
||||
CHECK(r[4].is_equal_approx(Point2(8, 2)));
|
||||
CHECK(r[5].is_equal_approx(Point2(4, 8)));
|
||||
CHECK(r[6].is_equal_approx(Point2(-6, 10)));
|
||||
CHECK(r[7].is_equal_approx(Point2(-12, 4)));
|
||||
}
|
||||
|
||||
SUBCASE("[Geometry2D] Add extra points outside border of original convex hull") {
|
||||
a.push_back(Point2(-11, -1));
|
||||
a.push_back(Point2(7, 6));
|
||||
|
||||
r = Geometry2D::convex_hull(a);
|
||||
REQUIRE_MESSAGE(r.size() == 10, "Convex hull should contain 10 points.");
|
||||
CHECK(r[0].is_equal_approx(Point2(-12, 4)));
|
||||
CHECK(r[1].is_equal_approx(Point2(-11, -1)));
|
||||
CHECK(r[2].is_equal_approx(Point2(-10, -4)));
|
||||
CHECK(r[3].is_equal_approx(Point2(-4, -8)));
|
||||
CHECK(r[4].is_equal_approx(Point2(10, -8)));
|
||||
CHECK(r[5].is_equal_approx(Point2(8, 2)));
|
||||
CHECK(r[6].is_equal_approx(Point2(7, 6)));
|
||||
CHECK(r[7].is_equal_approx(Point2(4, 8)));
|
||||
CHECK(r[8].is_equal_approx(Point2(-6, 10)));
|
||||
CHECK(r[9].is_equal_approx(Point2(-12, 4)));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("[Geometry2D] Bresenham line") {
|
||||
Vector<Vector2i> r;
|
||||
|
||||
SUBCASE("[Geometry2D] Single point") {
|
||||
r = Geometry2D::bresenham_line(Point2i(0, 0), Point2i(0, 0));
|
||||
|
||||
REQUIRE_MESSAGE(r.size() == 1, "The Bresenham line should contain exactly one point.");
|
||||
CHECK(r[0] == Vector2i(0, 0));
|
||||
}
|
||||
|
||||
SUBCASE("[Geometry2D] Line parallel to x-axis") {
|
||||
r = Geometry2D::bresenham_line(Point2i(1, 2), Point2i(5, 2));
|
||||
|
||||
REQUIRE_MESSAGE(r.size() == 5, "The Bresenham line should contain exactly five points.");
|
||||
CHECK(r[0] == Vector2i(1, 2));
|
||||
CHECK(r[1] == Vector2i(2, 2));
|
||||
CHECK(r[2] == Vector2i(3, 2));
|
||||
CHECK(r[3] == Vector2i(4, 2));
|
||||
CHECK(r[4] == Vector2i(5, 2));
|
||||
}
|
||||
|
||||
SUBCASE("[Geometry2D] 45 degree line from the origin") {
|
||||
r = Geometry2D::bresenham_line(Point2i(0, 0), Point2i(4, 4));
|
||||
|
||||
REQUIRE_MESSAGE(r.size() == 5, "The Bresenham line should contain exactly five points.");
|
||||
CHECK(r[0] == Vector2i(0, 0));
|
||||
CHECK(r[1] == Vector2i(1, 1));
|
||||
CHECK(r[2] == Vector2i(2, 2));
|
||||
CHECK(r[3] == Vector2i(3, 3));
|
||||
CHECK(r[4] == Vector2i(4, 4));
|
||||
}
|
||||
|
||||
SUBCASE("[Geometry2D] Sloped line going up one unit") {
|
||||
r = Geometry2D::bresenham_line(Point2i(0, 0), Point2i(4, 1));
|
||||
|
||||
REQUIRE_MESSAGE(r.size() == 5, "The Bresenham line should contain exactly five points.");
|
||||
CHECK(r[0] == Vector2i(0, 0));
|
||||
CHECK(r[1] == Vector2i(1, 0));
|
||||
CHECK(r[2] == Vector2i(2, 0));
|
||||
CHECK(r[3] == Vector2i(3, 1));
|
||||
CHECK(r[4] == Vector2i(4, 1));
|
||||
}
|
||||
|
||||
SUBCASE("[Geometry2D] Sloped line going up two units") {
|
||||
r = Geometry2D::bresenham_line(Point2i(0, 0), Point2i(4, 2));
|
||||
|
||||
REQUIRE_MESSAGE(r.size() == 5, "The Bresenham line should contain exactly five points.");
|
||||
CHECK(r[0] == Vector2i(0, 0));
|
||||
CHECK(r[1] == Vector2i(1, 0));
|
||||
CHECK(r[2] == Vector2i(2, 1));
|
||||
CHECK(r[3] == Vector2i(3, 1));
|
||||
CHECK(r[4] == Vector2i(4, 2));
|
||||
}
|
||||
|
||||
SUBCASE("[Geometry2D] Long sloped line") {
|
||||
r = Geometry2D::bresenham_line(Point2i(0, 0), Point2i(11, 5));
|
||||
|
||||
REQUIRE_MESSAGE(r.size() == 12, "The Bresenham line should contain exactly twelve points.");
|
||||
CHECK(r[0] == Vector2i(0, 0));
|
||||
CHECK(r[1] == Vector2i(1, 0));
|
||||
CHECK(r[2] == Vector2i(2, 1));
|
||||
CHECK(r[3] == Vector2i(3, 1));
|
||||
CHECK(r[4] == Vector2i(4, 2));
|
||||
CHECK(r[5] == Vector2i(5, 2));
|
||||
CHECK(r[6] == Vector2i(6, 3));
|
||||
CHECK(r[7] == Vector2i(7, 3));
|
||||
CHECK(r[8] == Vector2i(8, 4));
|
||||
CHECK(r[9] == Vector2i(9, 4));
|
||||
CHECK(r[10] == Vector2i(10, 5));
|
||||
CHECK(r[11] == Vector2i(11, 5));
|
||||
}
|
||||
}
|
||||
} // namespace TestGeometry2D
|
||||
201
tests/core/math/test_geometry_3d.h
Normal file
@@ -0,0 +1,201 @@
|
||||
/**************************************************************************/
|
||||
/* test_geometry_3d.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/math/geometry_3d.h"
|
||||
#include "tests/test_macros.h"
|
||||
|
||||
namespace TestGeometry3D {
|
||||
TEST_CASE("[Geometry3D] Closest Points Between Segments") {
|
||||
Vector3 ps, qt;
|
||||
Geometry3D::get_closest_points_between_segments(Vector3(1, -1, 1), Vector3(1, 1, -1), Vector3(-1, -2, -1), Vector3(-1, 1, 1), ps, qt);
|
||||
CHECK(ps.is_equal_approx(Vector3(1, -0.2, 0.2)));
|
||||
CHECK(qt.is_equal_approx(Vector3(-1, -0.2, 0.2)));
|
||||
}
|
||||
|
||||
TEST_CASE("[Geometry3D] Closest Distance Between Segments") {
|
||||
CHECK(Geometry3D::get_closest_distance_between_segments(Vector3(1, -2, 0), Vector3(1, 2, 0), Vector3(-1, 2, 0), Vector3(-1, -2, 0)) == 2.0f);
|
||||
}
|
||||
|
||||
TEST_CASE("[Geometry3D] Build Box Planes") {
|
||||
constexpr Vector3 extents = Vector3(5, 5, 20);
|
||||
Vector<Plane> box = Geometry3D::build_box_planes(extents);
|
||||
CHECK(box.size() == 6);
|
||||
CHECK(extents.x == box[0].d);
|
||||
CHECK(box[0].normal == Vector3(1, 0, 0));
|
||||
CHECK(extents.x == box[1].d);
|
||||
CHECK(box[1].normal == Vector3(-1, 0, 0));
|
||||
CHECK(extents.y == box[2].d);
|
||||
CHECK(box[2].normal == Vector3(0, 1, 0));
|
||||
CHECK(extents.y == box[3].d);
|
||||
CHECK(box[3].normal == Vector3(0, -1, 0));
|
||||
CHECK(extents.z == box[4].d);
|
||||
CHECK(box[4].normal == Vector3(0, 0, 1));
|
||||
CHECK(extents.z == box[5].d);
|
||||
CHECK(box[5].normal == Vector3(0, 0, -1));
|
||||
}
|
||||
|
||||
TEST_CASE("[Geometry3D] Build Capsule Planes") {
|
||||
Vector<Plane> capsule = Geometry3D::build_capsule_planes(10, 20, 6, 10);
|
||||
CHECK(capsule.size() == 126);
|
||||
}
|
||||
|
||||
TEST_CASE("[Geometry3D] Build Cylinder Planes") {
|
||||
Vector<Plane> planes = Geometry3D::build_cylinder_planes(3.0f, 10.0f, 10);
|
||||
CHECK(planes.size() == 12);
|
||||
}
|
||||
|
||||
TEST_CASE("[Geometry3D] Build Sphere Planes") {
|
||||
Vector<Plane> planes = Geometry3D::build_sphere_planes(10.0f, 10, 3);
|
||||
CHECK(planes.size() == 63);
|
||||
}
|
||||
|
||||
#if false
|
||||
// This test has been temporarily disabled because it's really fragile and
|
||||
// breaks if calculations change very slightly. For example, it breaks when
|
||||
// using doubles, and it breaks when making Plane calculations more accurate.
|
||||
TEST_CASE("[Geometry3D] Build Convex Mesh") {
|
||||
struct Case {
|
||||
Vector<Plane> object;
|
||||
int want_faces, want_edges, want_vertices;
|
||||
Case(){};
|
||||
Case(Vector<Plane> p_object, int p_want_faces, int p_want_edges, int p_want_vertices) :
|
||||
object(p_object), want_faces(p_want_faces), want_edges(p_want_edges), want_vertices(p_want_vertices){};
|
||||
};
|
||||
Vector<Case> tt;
|
||||
tt.push_back(Case(Geometry3D::build_box_planes(Vector3(5, 10, 5)), 6, 12, 8));
|
||||
tt.push_back(Case(Geometry3D::build_capsule_planes(5, 5, 20, 20, Vector3::Axis()), 820, 7603, 6243));
|
||||
tt.push_back(Case(Geometry3D::build_cylinder_planes(5, 5, 20, Vector3::Axis()), 22, 100, 80));
|
||||
tt.push_back(Case(Geometry3D::build_sphere_planes(5, 5, 20), 220, 1011, 522));
|
||||
for (int i = 0; i < tt.size(); ++i) {
|
||||
Case current_case = tt[i];
|
||||
Geometry3D::MeshData mesh = Geometry3D::build_convex_mesh(current_case.object);
|
||||
CHECK(mesh.faces.size() == current_case.want_faces);
|
||||
CHECK(mesh.edges.size() == current_case.want_edges);
|
||||
CHECK(mesh.vertices.size() == current_case.want_vertices);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
TEST_CASE("[Geometry3D] Clip Polygon") {
|
||||
Vector<Plane> box_planes = Geometry3D::build_box_planes(Vector3(5, 10, 5));
|
||||
Vector<Vector3> box = Geometry3D::compute_convex_mesh_points(&box_planes[0], box_planes.size());
|
||||
Vector<Vector3> output = Geometry3D::clip_polygon(box, Plane());
|
||||
CHECK(output == box);
|
||||
output = Geometry3D::clip_polygon(box, Plane(Vector3(0, 1, 0), Vector3(0, 3, 0)));
|
||||
CHECK(output != box);
|
||||
}
|
||||
|
||||
TEST_CASE("[Geometry3D] Compute Convex Mesh Points") {
|
||||
Vector<Vector3> cube;
|
||||
cube.push_back(Vector3(-5, -5, -5));
|
||||
cube.push_back(Vector3(5, -5, -5));
|
||||
cube.push_back(Vector3(-5, 5, -5));
|
||||
cube.push_back(Vector3(5, 5, -5));
|
||||
cube.push_back(Vector3(-5, -5, 5));
|
||||
cube.push_back(Vector3(5, -5, 5));
|
||||
cube.push_back(Vector3(-5, 5, 5));
|
||||
cube.push_back(Vector3(5, 5, 5));
|
||||
Vector<Plane> box_planes = Geometry3D::build_box_planes(Vector3(5, 5, 5));
|
||||
CHECK(Geometry3D::compute_convex_mesh_points(&box_planes[0], box_planes.size()) == cube);
|
||||
}
|
||||
|
||||
TEST_CASE("[Geometry3D] Get Closest Point To Segment") {
|
||||
constexpr Vector3 a = Vector3(1, 1, 1);
|
||||
constexpr Vector3 b = Vector3(5, 5, 5);
|
||||
Vector3 output = Geometry3D::get_closest_point_to_segment(Vector3(2, 1, 4), a, b);
|
||||
CHECK(output.is_equal_approx(Vector3(2.33333, 2.33333, 2.33333)));
|
||||
}
|
||||
|
||||
TEST_CASE("[Geometry3D] Plane and Box Overlap") {
|
||||
CHECK(Geometry3D::planeBoxOverlap(Vector3(3, 4, 2), 5.0f, Vector3(5, 5, 5)) == true);
|
||||
CHECK(Geometry3D::planeBoxOverlap(Vector3(0, 1, 0), -10.0f, Vector3(5, 5, 5)) == false);
|
||||
CHECK(Geometry3D::planeBoxOverlap(Vector3(1, 0, 0), -6.0f, Vector3(5, 5, 5)) == false);
|
||||
}
|
||||
|
||||
TEST_CASE("[Geometry3D] Is Point in Projected Triangle") {
|
||||
CHECK(Geometry3D::point_in_projected_triangle(Vector3(1, 1, 0), Vector3(3, 0, 0), Vector3(0, 3, 0), Vector3(-3, 0, 0)) == true);
|
||||
CHECK(Geometry3D::point_in_projected_triangle(Vector3(5, 1, 0), Vector3(3, 0, 0), Vector3(0, 3, 0), Vector3(-3, 0, 0)) == false);
|
||||
CHECK(Geometry3D::point_in_projected_triangle(Vector3(3, 0, 0), Vector3(3, 0, 0), Vector3(0, 3, 0), Vector3(-3, 0, 0)) == true);
|
||||
}
|
||||
|
||||
TEST_CASE("[Geometry3D] Does Ray Intersect Triangle") {
|
||||
Vector3 result;
|
||||
CHECK(Geometry3D::ray_intersects_triangle(Vector3(0, 1, 1), Vector3(0, 0, -10), Vector3(0, 3, 0), Vector3(-3, 0, 0), Vector3(3, 0, 0), &result) == true);
|
||||
CHECK(Geometry3D::ray_intersects_triangle(Vector3(5, 10, 1), Vector3(0, 0, -10), Vector3(0, 3, 0), Vector3(-3, 0, 0), Vector3(3, 0, 0), &result) == false);
|
||||
CHECK(Geometry3D::ray_intersects_triangle(Vector3(0, 1, 1), Vector3(0, 0, 10), Vector3(0, 3, 0), Vector3(-3, 0, 0), Vector3(3, 0, 0), &result) == false);
|
||||
}
|
||||
|
||||
TEST_CASE("[Geometry3D] Does Segment Intersect Convex") {
|
||||
Vector<Plane> box_planes = Geometry3D::build_box_planes(Vector3(5, 5, 5));
|
||||
Vector3 result, normal;
|
||||
CHECK(Geometry3D::segment_intersects_convex(Vector3(10, 10, 10), Vector3(0, 0, 0), &box_planes[0], box_planes.size(), &result, &normal) == true);
|
||||
CHECK(Geometry3D::segment_intersects_convex(Vector3(10, 10, 10), Vector3(5, 5, 5), &box_planes[0], box_planes.size(), &result, &normal) == true);
|
||||
CHECK(Geometry3D::segment_intersects_convex(Vector3(10, 10, 10), Vector3(6, 5, 5), &box_planes[0], box_planes.size(), &result, &normal) == false);
|
||||
}
|
||||
|
||||
TEST_CASE("[Geometry3D] Segment Intersects Cylinder") {
|
||||
Vector3 result, normal;
|
||||
CHECK(Geometry3D::segment_intersects_cylinder(Vector3(10, 10, 10), Vector3(0, 0, 0), 5, 5, &result, &normal) == true);
|
||||
CHECK(Geometry3D::segment_intersects_cylinder(Vector3(10, 10, 10), Vector3(6, 6, 6), 5, 5, &result, &normal) == false);
|
||||
}
|
||||
|
||||
TEST_CASE("[Geometry3D] Segment Intersects Cylinder") {
|
||||
Vector3 result, normal;
|
||||
CHECK(Geometry3D::segment_intersects_sphere(Vector3(10, 10, 10), Vector3(0, 0, 0), Vector3(0, 0, 0), 5, &result, &normal) == true);
|
||||
CHECK(Geometry3D::segment_intersects_sphere(Vector3(10, 10, 10), Vector3(0, 0, 2.5), Vector3(0, 0, 0), 5, &result, &normal) == true);
|
||||
CHECK(Geometry3D::segment_intersects_sphere(Vector3(10, 10, 10), Vector3(5, 5, 5), Vector3(0, 0, 0), 5, &result, &normal) == false);
|
||||
}
|
||||
|
||||
TEST_CASE("[Geometry3D] Segment Intersects Triangle") {
|
||||
Vector3 result;
|
||||
CHECK(Geometry3D::segment_intersects_triangle(Vector3(1, 1, 1), Vector3(-1, -1, -1), Vector3(-3, 0, 0), Vector3(0, 3, 0), Vector3(3, 0, 0), &result) == true);
|
||||
CHECK(Geometry3D::segment_intersects_triangle(Vector3(1, 1, 1), Vector3(3, 0, 0), Vector3(-3, 0, 0), Vector3(0, 3, 0), Vector3(3, 0, 0), &result) == true);
|
||||
CHECK(Geometry3D::segment_intersects_triangle(Vector3(1, 1, 1), Vector3(10, -1, -1), Vector3(-3, 0, 0), Vector3(0, 3, 0), Vector3(3, 0, 0), &result) == false);
|
||||
}
|
||||
|
||||
TEST_CASE("[Geometry3D] Triangle and Box Overlap") {
|
||||
constexpr Vector3 good_triangle[3] = { Vector3(3, 2, 3), Vector3(2, 2, 1), Vector3(2, 1, 1) };
|
||||
CHECK(Geometry3D::triangle_box_overlap(Vector3(0, 0, 0), Vector3(5, 5, 5), good_triangle) == true);
|
||||
constexpr Vector3 bad_triangle[3] = { Vector3(100, 100, 100), Vector3(-100, -100, -100), Vector3(10, 10, 10) };
|
||||
CHECK(Geometry3D::triangle_box_overlap(Vector3(1000, 1000, 1000), Vector3(1, 1, 1), bad_triangle) == false);
|
||||
}
|
||||
|
||||
TEST_CASE("[Geometry3D] Triangle and Sphere Intersect") {
|
||||
constexpr Vector3 triangle_a = Vector3(3, 0, 0);
|
||||
constexpr Vector3 triangle_b = Vector3(-3, 0, 0);
|
||||
constexpr Vector3 triangle_c = Vector3(0, 3, 0);
|
||||
Vector3 triangle_contact, sphere_contact;
|
||||
CHECK(Geometry3D::triangle_sphere_intersection_test(triangle_a, triangle_b, triangle_c, Vector3(0, -1, 0), Vector3(0, 0, 0), 5, triangle_contact, sphere_contact) == true);
|
||||
CHECK(Geometry3D::triangle_sphere_intersection_test(triangle_a, triangle_b, triangle_c, Vector3(0, 1, 0), Vector3(0, 0, 0), 5, triangle_contact, sphere_contact) == true);
|
||||
CHECK(Geometry3D::triangle_sphere_intersection_test(triangle_a, triangle_b, triangle_c, Vector3(0, 1, 0), Vector3(20, 0, 0), 5, triangle_contact, sphere_contact) == false);
|
||||
}
|
||||
} // namespace TestGeometry3D
|
||||
641
tests/core/math/test_math_funcs.h
Normal file
@@ -0,0 +1,641 @@
|
||||
/**************************************************************************/
|
||||
/* test_math_funcs.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "tests/test_macros.h"
|
||||
|
||||
namespace TestMath {
|
||||
|
||||
TEST_CASE("[Math] C++ macros") {
|
||||
CHECK(MIN(-2, 2) == -2);
|
||||
CHECK(MIN(600, 2) == 2);
|
||||
|
||||
CHECK(MAX(-2, 2) == 2);
|
||||
CHECK(MAX(600, 2) == 600);
|
||||
|
||||
CHECK(CLAMP(600, -2, 2) == 2);
|
||||
CHECK(CLAMP(620, 600, 650) == 620);
|
||||
// `max` is lower than `min`.
|
||||
CHECK(CLAMP(620, 600, 50) == 50);
|
||||
|
||||
CHECK(Math::abs(-5) == 5);
|
||||
CHECK(Math::abs(0) == 0);
|
||||
CHECK(Math::abs(5) == 5);
|
||||
|
||||
CHECK(SIGN(-5) == -1.0);
|
||||
CHECK(SIGN(0) == 0.0);
|
||||
CHECK(SIGN(5) == 1.0);
|
||||
// Check that SIGN(Math::NaN) returns 0.0.
|
||||
CHECK(SIGN(Math::NaN) == 0.0);
|
||||
}
|
||||
|
||||
TEST_CASE("[Math] Power of two functions") {
|
||||
CHECK(next_power_of_2((uint32_t)0) == 0);
|
||||
CHECK(next_power_of_2((uint32_t)1) == 1);
|
||||
CHECK(next_power_of_2((uint32_t)16) == 16);
|
||||
CHECK(next_power_of_2((uint32_t)17) == 32);
|
||||
CHECK(next_power_of_2((uint32_t)65535) == 65536);
|
||||
|
||||
CHECK(previous_power_of_2((uint32_t)0) == 0);
|
||||
CHECK(previous_power_of_2((uint32_t)1) == 1);
|
||||
CHECK(previous_power_of_2((uint32_t)16) == 16);
|
||||
CHECK(previous_power_of_2((uint32_t)17) == 16);
|
||||
CHECK(previous_power_of_2((uint32_t)65535) == 32768);
|
||||
|
||||
CHECK(closest_power_of_2((uint32_t)0) == 0);
|
||||
CHECK(closest_power_of_2((uint32_t)1) == 1);
|
||||
CHECK(closest_power_of_2((uint32_t)16) == 16);
|
||||
CHECK(closest_power_of_2((uint32_t)17) == 16);
|
||||
CHECK(closest_power_of_2((uint32_t)65535) == 65536);
|
||||
|
||||
CHECK(get_shift_from_power_of_2((uint32_t)0) == -1);
|
||||
CHECK(get_shift_from_power_of_2((uint32_t)1) == 0);
|
||||
CHECK(get_shift_from_power_of_2((uint32_t)16) == 4);
|
||||
CHECK(get_shift_from_power_of_2((uint32_t)17) == -1);
|
||||
CHECK(get_shift_from_power_of_2((uint32_t)65535) == -1);
|
||||
|
||||
CHECK(nearest_shift((uint32_t)0) == 0);
|
||||
CHECK(nearest_shift((uint32_t)1) == 1);
|
||||
CHECK(nearest_shift((uint32_t)16) == 5);
|
||||
CHECK(nearest_shift((uint32_t)17) == 5);
|
||||
CHECK(nearest_shift((uint32_t)65535) == 16);
|
||||
}
|
||||
|
||||
TEST_CASE_TEMPLATE("[Math] abs", T, int, float, double) {
|
||||
CHECK(Math::abs((T)-1) == (T)1);
|
||||
CHECK(Math::abs((T)0) == (T)0);
|
||||
CHECK(Math::abs((T)1) == (T)1);
|
||||
CHECK(Math::abs((T)0.1) == (T)0.1);
|
||||
}
|
||||
|
||||
TEST_CASE_TEMPLATE("[Math] round/floor/ceil", T, float, double) {
|
||||
CHECK(Math::round((T)1.5) == (T)2.0);
|
||||
CHECK(Math::round((T)1.6) == (T)2.0);
|
||||
CHECK(Math::round((T)-1.5) == (T)-2.0);
|
||||
CHECK(Math::round((T)-1.1) == (T)-1.0);
|
||||
|
||||
CHECK(Math::floor((T)1.5) == (T)1.0);
|
||||
CHECK(Math::floor((T)-1.5) == (T)-2.0);
|
||||
|
||||
CHECK(Math::ceil((T)1.5) == (T)2.0);
|
||||
CHECK(Math::ceil((T)-1.9) == (T)-1.0);
|
||||
}
|
||||
|
||||
TEST_CASE_TEMPLATE("[Math] integer division round up unsigned", T, uint32_t, uint64_t) {
|
||||
CHECK(Math::division_round_up((T)0, (T)64) == 0);
|
||||
CHECK(Math::division_round_up((T)1, (T)64) == 1);
|
||||
CHECK(Math::division_round_up((T)63, (T)64) == 1);
|
||||
CHECK(Math::division_round_up((T)64, (T)64) == 1);
|
||||
CHECK(Math::division_round_up((T)65, (T)64) == 2);
|
||||
CHECK(Math::division_round_up((T)65, (T)1) == 65);
|
||||
}
|
||||
|
||||
TEST_CASE_TEMPLATE("[Math] integer division round up signed", T, int32_t, int64_t) {
|
||||
CHECK(Math::division_round_up((T)0, (T)64) == 0);
|
||||
CHECK(Math::division_round_up((T)1, (T)64) == 1);
|
||||
CHECK(Math::division_round_up((T)63, (T)64) == 1);
|
||||
CHECK(Math::division_round_up((T)64, (T)64) == 1);
|
||||
CHECK(Math::division_round_up((T)65, (T)64) == 2);
|
||||
CHECK(Math::division_round_up((T)65, (T)1) == 65);
|
||||
CHECK(Math::division_round_up((T)-1, (T)64) == 0);
|
||||
CHECK(Math::division_round_up((T)-1, (T)-1) == 1);
|
||||
CHECK(Math::division_round_up((T)-1, (T)1) == -1);
|
||||
CHECK(Math::division_round_up((T)-1, (T)-2) == 1);
|
||||
CHECK(Math::division_round_up((T)-4, (T)-2) == 2);
|
||||
}
|
||||
|
||||
TEST_CASE_TEMPLATE("[Math] sin/cos/tan", T, float, double) {
|
||||
CHECK(Math::sin((T)-0.1) == doctest::Approx((T)-0.0998334166));
|
||||
CHECK(Math::sin((T)0.1) == doctest::Approx((T)0.0998334166));
|
||||
CHECK(Math::sin((T)0.5) == doctest::Approx((T)0.4794255386));
|
||||
CHECK(Math::sin((T)1.0) == doctest::Approx((T)0.8414709848));
|
||||
CHECK(Math::sin((T)1.5) == doctest::Approx((T)0.9974949866));
|
||||
CHECK(Math::sin((T)450.0) == doctest::Approx((T)-0.683283725));
|
||||
|
||||
CHECK(Math::cos((T)-0.1) == doctest::Approx((T)0.99500416530));
|
||||
CHECK(Math::cos((T)0.1) == doctest::Approx((T)0.9950041653));
|
||||
CHECK(Math::cos((T)0.5) == doctest::Approx((T)0.8775825619));
|
||||
CHECK(Math::cos((T)1.0) == doctest::Approx((T)0.5403023059));
|
||||
CHECK(Math::cos((T)1.5) == doctest::Approx((T)0.0707372017));
|
||||
CHECK(Math::cos((T)450.0) == doctest::Approx((T)-0.7301529642));
|
||||
|
||||
CHECK(Math::tan((T)-0.1) == doctest::Approx((T)-0.1003346721));
|
||||
CHECK(Math::tan((T)0.1) == doctest::Approx((T)0.1003346721));
|
||||
CHECK(Math::tan((T)0.5) == doctest::Approx((T)0.5463024898));
|
||||
CHECK(Math::tan((T)1.0) == doctest::Approx((T)1.5574077247));
|
||||
CHECK(Math::tan((T)1.5) == doctest::Approx((T)14.1014199472));
|
||||
CHECK(Math::tan((T)450.0) == doctest::Approx((T)0.9358090134));
|
||||
}
|
||||
|
||||
TEST_CASE_TEMPLATE("[Math] sinh/cosh/tanh", T, float, double) {
|
||||
CHECK(Math::sinh((T)-0.1) == doctest::Approx((T)-0.10016675));
|
||||
CHECK(Math::sinh((T)0.1) == doctest::Approx((T)0.10016675));
|
||||
CHECK(Math::sinh((T)0.5) == doctest::Approx((T)0.5210953055));
|
||||
CHECK(Math::sinh((T)1.0) == doctest::Approx((T)1.1752011936));
|
||||
CHECK(Math::sinh((T)1.5) == doctest::Approx((T)2.1292794551));
|
||||
|
||||
CHECK(Math::cosh((T)-0.1) == doctest::Approx((T)1.0050041681));
|
||||
CHECK(Math::cosh((T)0.1) == doctest::Approx((T)1.0050041681));
|
||||
CHECK(Math::cosh((T)0.5) == doctest::Approx((T)1.1276259652));
|
||||
CHECK(Math::cosh((T)1.0) == doctest::Approx((T)1.5430806348));
|
||||
CHECK(Math::cosh((T)1.5) == doctest::Approx((T)2.3524096152));
|
||||
|
||||
CHECK(Math::tanh((T)-0.1) == doctest::Approx((T)-0.0996679946));
|
||||
CHECK(Math::tanh((T)0.1) == doctest::Approx((T)0.0996679946));
|
||||
CHECK(Math::tanh((T)0.5) == doctest::Approx((T)0.4621171573));
|
||||
CHECK(Math::tanh((T)1.0) == doctest::Approx((T)0.761594156));
|
||||
CHECK(Math::tanh((T)1.5) == doctest::Approx((T)0.9051482536));
|
||||
CHECK(Math::tanh((T)450.0) == doctest::Approx((T)1.0));
|
||||
}
|
||||
|
||||
TEST_CASE_TEMPLATE("[Math] asin/acos/atan", T, float, double) {
|
||||
CHECK(Math::asin((T)-0.1) == doctest::Approx((T)-0.1001674212));
|
||||
CHECK(Math::asin((T)0.1) == doctest::Approx((T)0.1001674212));
|
||||
CHECK(Math::asin((T)0.5) == doctest::Approx((T)0.5235987756));
|
||||
CHECK(Math::asin((T)1.0) == doctest::Approx((T)1.5707963268));
|
||||
CHECK(Math::asin((T)2.0) == doctest::Approx((T)1.5707963268));
|
||||
CHECK(Math::asin((T)-2.0) == doctest::Approx((T)-1.5707963268));
|
||||
|
||||
CHECK(Math::acos((T)-0.1) == doctest::Approx((T)1.670963748));
|
||||
CHECK(Math::acos((T)0.1) == doctest::Approx((T)1.4706289056));
|
||||
CHECK(Math::acos((T)0.5) == doctest::Approx((T)1.0471975512));
|
||||
CHECK(Math::acos((T)1.0) == doctest::Approx((T)0.0));
|
||||
CHECK(Math::acos((T)2.0) == doctest::Approx((T)0.0));
|
||||
CHECK(Math::acos((T)-2.0) == doctest::Approx((T)Math::PI));
|
||||
|
||||
CHECK(Math::atan((T)-0.1) == doctest::Approx((T)-0.0996686525));
|
||||
CHECK(Math::atan((T)0.1) == doctest::Approx((T)0.0996686525));
|
||||
CHECK(Math::atan((T)0.5) == doctest::Approx((T)0.463647609));
|
||||
CHECK(Math::atan((T)1.0) == doctest::Approx((T)0.7853981634));
|
||||
CHECK(Math::atan((T)1.5) == doctest::Approx((T)0.9827937232));
|
||||
CHECK(Math::atan((T)450.0) == doctest::Approx((T)1.5685741082));
|
||||
}
|
||||
|
||||
TEST_CASE_TEMPLATE("[Math] asinh/acosh/atanh", T, float, double) {
|
||||
CHECK(Math::asinh((T)-2.0) == doctest::Approx((T)-1.4436354751));
|
||||
CHECK(Math::asinh((T)-0.1) == doctest::Approx((T)-0.0998340788));
|
||||
CHECK(Math::asinh((T)0.1) == doctest::Approx((T)0.0998340788));
|
||||
CHECK(Math::asinh((T)0.5) == doctest::Approx((T)0.4812118250));
|
||||
CHECK(Math::asinh((T)1.0) == doctest::Approx((T)0.8813735870));
|
||||
CHECK(Math::asinh((T)2.0) == doctest::Approx((T)1.4436354751));
|
||||
|
||||
CHECK(Math::acosh((T)-2.0) == doctest::Approx((T)0.0));
|
||||
CHECK(Math::acosh((T)-0.1) == doctest::Approx((T)0.0));
|
||||
CHECK(Math::acosh((T)0.1) == doctest::Approx((T)0.0));
|
||||
CHECK(Math::acosh((T)0.5) == doctest::Approx((T)0.0));
|
||||
CHECK(Math::acosh((T)1.0) == doctest::Approx((T)0.0));
|
||||
CHECK(Math::acosh((T)2.0) == doctest::Approx((T)1.3169578969));
|
||||
CHECK(Math::acosh((T)450.0) == doctest::Approx((T)6.8023935287));
|
||||
|
||||
CHECK(Math::is_inf(Math::atanh((T)-2.0)));
|
||||
CHECK(Math::atanh((T)-2.0) < (T)0.0);
|
||||
CHECK(Math::is_inf(Math::atanh((T)-1.0)));
|
||||
CHECK(Math::atanh((T)-1.0) < (T)0.0);
|
||||
CHECK(Math::atanh((T)-0.1) == doctest::Approx((T)-0.1003353477));
|
||||
CHECK(Math::atanh((T)0.1) == doctest::Approx((T)0.1003353477));
|
||||
CHECK(Math::atanh((T)0.5) == doctest::Approx((T)0.5493061443));
|
||||
CHECK(Math::is_inf(Math::atanh((T)1.0)));
|
||||
CHECK(Math::atanh((T)1.0) > (T)0.0);
|
||||
CHECK(Math::is_inf(Math::atanh((T)1.5)));
|
||||
CHECK(Math::atanh((T)1.5) > (T)0.0);
|
||||
CHECK(Math::is_inf(Math::atanh((T)450.0)));
|
||||
CHECK(Math::atanh((T)450.0) > (T)0.0);
|
||||
}
|
||||
|
||||
TEST_CASE_TEMPLATE("[Math] sinc/sincn/atan2", T, float, double) {
|
||||
CHECK(Math::sinc((T)-0.1) == doctest::Approx((T)0.9983341665));
|
||||
CHECK(Math::sinc((T)0.1) == doctest::Approx((T)0.9983341665));
|
||||
CHECK(Math::sinc((T)0.5) == doctest::Approx((T)0.9588510772));
|
||||
CHECK(Math::sinc((T)1.0) == doctest::Approx((T)0.8414709848));
|
||||
CHECK(Math::sinc((T)1.5) == doctest::Approx((T)0.6649966577));
|
||||
CHECK(Math::sinc((T)450.0) == doctest::Approx((T)-0.0015184083));
|
||||
|
||||
CHECK(Math::sincn((T)-0.1) == doctest::Approx((T)0.9836316431));
|
||||
CHECK(Math::sincn((T)0.1) == doctest::Approx((T)0.9836316431));
|
||||
CHECK(Math::sincn((T)0.5) == doctest::Approx((T)0.6366197724));
|
||||
CHECK(Math::sincn((T)1.0) == doctest::Approx((T)0.0));
|
||||
CHECK(Math::sincn((T)1.5) == doctest::Approx((T)-0.2122065908));
|
||||
CHECK(Math::sincn((T)450.0) == doctest::Approx((T)0.0));
|
||||
|
||||
CHECK(Math::atan2((T)-0.1, (T)0.5) == doctest::Approx((T)-0.1973955598));
|
||||
CHECK(Math::atan2((T)0.1, (T)-0.5) == doctest::Approx((T)2.9441970937));
|
||||
CHECK(Math::atan2((T)0.5, (T)1.5) == doctest::Approx((T)0.3217505544));
|
||||
CHECK(Math::atan2((T)1.0, (T)2.5) == doctest::Approx((T)0.3805063771));
|
||||
CHECK(Math::atan2((T)1.5, (T)1.0) == doctest::Approx((T)0.9827937232));
|
||||
CHECK(Math::atan2((T)450.0, (T)1.0) == doctest::Approx((T)1.5685741082));
|
||||
}
|
||||
|
||||
TEST_CASE_TEMPLATE("[Math] pow/log/log2/exp/sqrt", T, float, double) {
|
||||
CHECK(Math::pow((T)-0.1, (T)2.0) == doctest::Approx((T)0.01));
|
||||
CHECK(Math::pow((T)0.1, (T)2.5) == doctest::Approx((T)0.0031622777));
|
||||
CHECK(Math::pow((T)0.5, (T)0.5) == doctest::Approx((T)0.7071067812));
|
||||
CHECK(Math::pow((T)1.0, (T)1.0) == doctest::Approx((T)1.0));
|
||||
CHECK(Math::pow((T)1.5, (T)-1.0) == doctest::Approx((T)0.6666666667));
|
||||
CHECK(Math::pow((T)450.0, (T)-2.0) == doctest::Approx((T)0.0000049383));
|
||||
CHECK(Math::pow((T)450.0, (T)0.0) == doctest::Approx((T)1.0));
|
||||
|
||||
CHECK(Math::is_nan(Math::log((T)-0.1)));
|
||||
CHECK(Math::log((T)0.1) == doctest::Approx((T)-2.302585093));
|
||||
CHECK(Math::log((T)0.5) == doctest::Approx((T)-0.6931471806));
|
||||
CHECK(Math::log((T)1.0) == doctest::Approx((T)0.0));
|
||||
CHECK(Math::log((T)1.5) == doctest::Approx((T)0.4054651081));
|
||||
CHECK(Math::log((T)450.0) == doctest::Approx((T)6.1092475828));
|
||||
|
||||
CHECK(Math::is_nan(Math::log2((T)-0.1)));
|
||||
CHECK(Math::log2((T)0.1) == doctest::Approx((T)-3.3219280949));
|
||||
CHECK(Math::log2((T)0.5) == doctest::Approx((T)-1.0));
|
||||
CHECK(Math::log2((T)1.0) == doctest::Approx((T)0.0));
|
||||
CHECK(Math::log2((T)1.5) == doctest::Approx((T)0.5849625007));
|
||||
CHECK(Math::log2((T)450.0) == doctest::Approx((T)8.8137811912));
|
||||
|
||||
CHECK(Math::exp((T)-0.1) == doctest::Approx((T)0.904837418));
|
||||
CHECK(Math::exp((T)0.1) == doctest::Approx((T)1.1051709181));
|
||||
CHECK(Math::exp((T)0.5) == doctest::Approx((T)1.6487212707));
|
||||
CHECK(Math::exp((T)1.0) == doctest::Approx((T)2.7182818285));
|
||||
CHECK(Math::exp((T)1.5) == doctest::Approx((T)4.4816890703));
|
||||
|
||||
CHECK(Math::is_nan(Math::sqrt((T)-0.1)));
|
||||
CHECK(Math::sqrt((T)0.1) == doctest::Approx((T)0.316228));
|
||||
CHECK(Math::sqrt((T)0.5) == doctest::Approx((T)0.707107));
|
||||
CHECK(Math::sqrt((T)1.0) == doctest::Approx((T)1.0));
|
||||
CHECK(Math::sqrt((T)1.5) == doctest::Approx((T)1.224745));
|
||||
}
|
||||
|
||||
TEST_CASE_TEMPLATE("[Math] is_nan/is_inf", T, float, double) {
|
||||
CHECK(!Math::is_nan((T)0.0));
|
||||
CHECK(Math::is_nan((T)Math::NaN));
|
||||
|
||||
CHECK(!Math::is_inf((T)0.0));
|
||||
CHECK(Math::is_inf((T)Math::INF));
|
||||
}
|
||||
|
||||
TEST_CASE_TEMPLATE("[Math] linear_to_db", T, float, double) {
|
||||
CHECK(Math::linear_to_db((T)1.0) == doctest::Approx((T)0.0));
|
||||
CHECK(Math::linear_to_db((T)20.0) == doctest::Approx((T)26.0206));
|
||||
CHECK(Math::is_inf(Math::linear_to_db((T)0.0)));
|
||||
CHECK(Math::is_nan(Math::linear_to_db((T)-20.0)));
|
||||
}
|
||||
|
||||
TEST_CASE_TEMPLATE("[Math] db_to_linear", T, float, double) {
|
||||
CHECK(Math::db_to_linear((T)0.0) == doctest::Approx((T)1.0));
|
||||
CHECK(Math::db_to_linear((T)1.0) == doctest::Approx((T)1.122018));
|
||||
CHECK(Math::db_to_linear((T)20.0) == doctest::Approx((T)10.0));
|
||||
CHECK(Math::db_to_linear((T)-20.0) == doctest::Approx((T)0.1));
|
||||
}
|
||||
|
||||
TEST_CASE_TEMPLATE("[Math] step_decimals", T, float, double) {
|
||||
CHECK(Math::step_decimals((T)-0.5) == 1);
|
||||
CHECK(Math::step_decimals((T)0) == 0);
|
||||
CHECK(Math::step_decimals((T)1) == 0);
|
||||
CHECK(Math::step_decimals((T)0.1) == 1);
|
||||
CHECK(Math::step_decimals((T)0.01) == 2);
|
||||
CHECK(Math::step_decimals((T)0.001) == 3);
|
||||
CHECK(Math::step_decimals((T)0.0001) == 4);
|
||||
CHECK(Math::step_decimals((T)0.00001) == 5);
|
||||
CHECK(Math::step_decimals((T)0.000001) == 6);
|
||||
CHECK(Math::step_decimals((T)0.0000001) == 7);
|
||||
CHECK(Math::step_decimals((T)0.00000001) == 8);
|
||||
CHECK(Math::step_decimals((T)0.000000001) == 9);
|
||||
// Too many decimals to handle.
|
||||
CHECK(Math::step_decimals((T)0.0000000001) == 0);
|
||||
}
|
||||
|
||||
TEST_CASE_TEMPLATE("[Math] range_step_decimals", T, float, double) {
|
||||
CHECK(Math::range_step_decimals((T)0.000000001) == 9);
|
||||
// Too many decimals to handle.
|
||||
CHECK(Math::range_step_decimals((T)0.0000000001) == 0);
|
||||
// Should be treated as a step of 0 for use by the editor.
|
||||
CHECK(Math::range_step_decimals((T)0.0) == 16);
|
||||
CHECK(Math::range_step_decimals((T)-0.5) == 16);
|
||||
}
|
||||
|
||||
TEST_CASE_TEMPLATE("[Math] lerp", T, float, double) {
|
||||
CHECK(Math::lerp((T)2.0, (T)5.0, (T)-0.1) == doctest::Approx((T)1.7));
|
||||
CHECK(Math::lerp((T)2.0, (T)5.0, (T)0.0) == doctest::Approx((T)2.0));
|
||||
CHECK(Math::lerp((T)2.0, (T)5.0, (T)0.1) == doctest::Approx((T)2.3));
|
||||
CHECK(Math::lerp((T)2.0, (T)5.0, (T)1.0) == doctest::Approx((T)5.0));
|
||||
CHECK(Math::lerp((T)2.0, (T)5.0, (T)2.0) == doctest::Approx((T)8.0));
|
||||
|
||||
CHECK(Math::lerp((T)-2.0, (T)-5.0, (T)-0.1) == doctest::Approx((T)-1.7));
|
||||
CHECK(Math::lerp((T)-2.0, (T)-5.0, (T)0.0) == doctest::Approx((T)-2.0));
|
||||
CHECK(Math::lerp((T)-2.0, (T)-5.0, (T)0.1) == doctest::Approx((T)-2.3));
|
||||
CHECK(Math::lerp((T)-2.0, (T)-5.0, (T)1.0) == doctest::Approx((T)-5.0));
|
||||
CHECK(Math::lerp((T)-2.0, (T)-5.0, (T)2.0) == doctest::Approx((T)-8.0));
|
||||
}
|
||||
|
||||
TEST_CASE_TEMPLATE("[Math] inverse_lerp", T, float, double) {
|
||||
CHECK(Math::inverse_lerp((T)2.0, (T)5.0, (T)1.7) == doctest::Approx((T)-0.1));
|
||||
CHECK(Math::inverse_lerp((T)2.0, (T)5.0, (T)2.0) == doctest::Approx((T)0.0));
|
||||
CHECK(Math::inverse_lerp((T)2.0, (T)5.0, (T)2.3) == doctest::Approx((T)0.1));
|
||||
CHECK(Math::inverse_lerp((T)2.0, (T)5.0, (T)5.0) == doctest::Approx((T)1.0));
|
||||
CHECK(Math::inverse_lerp((T)2.0, (T)5.0, (T)8.0) == doctest::Approx((T)2.0));
|
||||
|
||||
CHECK(Math::inverse_lerp((T)-2.0, (T)-5.0, (T)-1.7) == doctest::Approx((T)-0.1));
|
||||
CHECK(Math::inverse_lerp((T)-2.0, (T)-5.0, (T)-2.0) == doctest::Approx((T)0.0));
|
||||
CHECK(Math::inverse_lerp((T)-2.0, (T)-5.0, (T)-2.3) == doctest::Approx((T)0.1));
|
||||
CHECK(Math::inverse_lerp((T)-2.0, (T)-5.0, (T)-5.0) == doctest::Approx((T)1.0));
|
||||
CHECK(Math::inverse_lerp((T)-2.0, (T)-5.0, (T)-8.0) == doctest::Approx((T)2.0));
|
||||
}
|
||||
|
||||
TEST_CASE_TEMPLATE("[Math] remap", T, float, double) {
|
||||
CHECK(Math::remap((T)50.0, (T)100.0, (T)200.0, (T)0.0, (T)1000.0) == doctest::Approx((T)-500.0));
|
||||
CHECK(Math::remap((T)100.0, (T)100.0, (T)200.0, (T)0.0, (T)1000.0) == doctest::Approx((T)0.0));
|
||||
CHECK(Math::remap((T)200.0, (T)100.0, (T)200.0, (T)0.0, (T)1000.0) == doctest::Approx((T)1000.0));
|
||||
CHECK(Math::remap((T)250.0, (T)100.0, (T)200.0, (T)0.0, (T)1000.0) == doctest::Approx((T)1500.0));
|
||||
|
||||
CHECK(Math::remap((T)-50.0, (T)-100.0, (T)-200.0, (T)0.0, (T)1000.0) == doctest::Approx((T)-500.0));
|
||||
CHECK(Math::remap((T)-100.0, (T)-100.0, (T)-200.0, (T)0.0, (T)1000.0) == doctest::Approx((T)0.0));
|
||||
CHECK(Math::remap((T)-200.0, (T)-100.0, (T)-200.0, (T)0.0, (T)1000.0) == doctest::Approx((T)1000.0));
|
||||
CHECK(Math::remap((T)-250.0, (T)-100.0, (T)-200.0, (T)0.0, (T)1000.0) == doctest::Approx((T)1500.0));
|
||||
|
||||
CHECK(Math::remap((T)-50.0, (T)-100.0, (T)-200.0, (T)0.0, (T)-1000.0) == doctest::Approx((T)500.0));
|
||||
CHECK(Math::remap((T)-100.0, (T)-100.0, (T)-200.0, (T)0.0, (T)-1000.0) == doctest::Approx((T)0.0));
|
||||
CHECK(Math::remap((T)-200.0, (T)-100.0, (T)-200.0, (T)0.0, (T)-1000.0) == doctest::Approx((T)-1000.0));
|
||||
CHECK(Math::remap((T)-250.0, (T)-100.0, (T)-200.0, (T)0.0, (T)-1000.0) == doctest::Approx((T)-1500.0));
|
||||
|
||||
// Note: undefined behavior can happen when `p_istart == p_istop`. We don't bother testing this as it will
|
||||
// vary between hardware and compilers properly implementing IEEE 754.
|
||||
}
|
||||
|
||||
TEST_CASE_TEMPLATE("[Math] angle_difference", T, float, double) {
|
||||
// Loops around, should return 0.0.
|
||||
CHECK(Math::angle_difference((T)0.0, (T)Math::TAU) == doctest::Approx((T)0.0));
|
||||
CHECK(Math::angle_difference((T)Math::PI, (T)-Math::PI) == doctest::Approx((T)0.0));
|
||||
CHECK(Math::angle_difference((T)0.0, (T)Math::TAU * (T)4.0) == doctest::Approx((T)0.0));
|
||||
|
||||
// Rotation is clockwise, so it should return -PI.
|
||||
CHECK(Math::angle_difference((T)0.0, (T)Math::PI) == doctest::Approx((T)-Math::PI));
|
||||
CHECK(Math::angle_difference((T)0.0, (T)-Math::PI) == doctest::Approx((T)Math::PI));
|
||||
CHECK(Math::angle_difference((T)Math::PI, (T)0.0) == doctest::Approx((T)Math::PI));
|
||||
CHECK(Math::angle_difference((T)-Math::PI, (T)0.0) == doctest::Approx((T)-Math::PI));
|
||||
|
||||
CHECK(Math::angle_difference((T)0.0, (T)3.0) == doctest::Approx((T)3.0));
|
||||
CHECK(Math::angle_difference((T)1.0, (T)-2.0) == doctest::Approx((T)-3.0));
|
||||
CHECK(Math::angle_difference((T)-1.0, (T)2.0) == doctest::Approx((T)3.0));
|
||||
CHECK(Math::angle_difference((T)-2.0, (T)-4.5) == doctest::Approx((T)-2.5));
|
||||
CHECK(Math::angle_difference((T)100.0, (T)102.5) == doctest::Approx((T)2.5));
|
||||
}
|
||||
|
||||
TEST_CASE_TEMPLATE("[Math] lerp_angle", T, float, double) {
|
||||
// Counter-clockwise rotation.
|
||||
CHECK(Math::lerp_angle((T)0.24 * Math::TAU, 0.75 * Math::TAU, 0.5) == doctest::Approx((T)-0.005 * Math::TAU));
|
||||
// Counter-clockwise rotation.
|
||||
CHECK(Math::lerp_angle((T)0.25 * Math::TAU, 0.75 * Math::TAU, 0.5) == doctest::Approx((T)0.0));
|
||||
// Clockwise rotation.
|
||||
CHECK(Math::lerp_angle((T)0.26 * Math::TAU, 0.75 * Math::TAU, 0.5) == doctest::Approx((T)0.505 * Math::TAU));
|
||||
|
||||
CHECK(Math::lerp_angle((T)-0.25 * Math::TAU, 1.25 * Math::TAU, 0.5) == doctest::Approx((T)-0.5 * Math::TAU));
|
||||
CHECK(Math::lerp_angle((T)0.72 * Math::TAU, 1.44 * Math::TAU, 0.96) == doctest::Approx((T)0.4512 * Math::TAU));
|
||||
CHECK(Math::lerp_angle((T)0.72 * Math::TAU, 1.44 * Math::TAU, 1.04) == doctest::Approx((T)0.4288 * Math::TAU));
|
||||
|
||||
// Initial and final angles are effectively identical, so the value returned
|
||||
// should always be the same regardless of the `weight` parameter.
|
||||
CHECK(Math::lerp_angle((T)-4 * Math::TAU, 4 * Math::TAU, -1.0) == doctest::Approx((T)-4.0 * Math::TAU));
|
||||
CHECK(Math::lerp_angle((T)-4 * Math::TAU, 4 * Math::TAU, 0.0) == doctest::Approx((T)-4.0 * Math::TAU));
|
||||
CHECK(Math::lerp_angle((T)-4 * Math::TAU, 4 * Math::TAU, 0.5) == doctest::Approx((T)-4.0 * Math::TAU));
|
||||
CHECK(Math::lerp_angle((T)-4 * Math::TAU, 4 * Math::TAU, 1.0) == doctest::Approx((T)-4.0 * Math::TAU));
|
||||
CHECK(Math::lerp_angle((T)-4 * Math::TAU, 4 * Math::TAU, 500.0) == doctest::Approx((T)-4.0 * Math::TAU));
|
||||
}
|
||||
|
||||
TEST_CASE_TEMPLATE("[Math] move_toward", T, float, double) {
|
||||
CHECK(Math::move_toward(2.0, 5.0, -1.0) == doctest::Approx((T)1.0));
|
||||
CHECK(Math::move_toward(2.0, 5.0, 2.5) == doctest::Approx((T)4.5));
|
||||
CHECK(Math::move_toward(2.0, 5.0, 4.0) == doctest::Approx((T)5.0));
|
||||
CHECK(Math::move_toward(-2.0, -5.0, -1.0) == doctest::Approx((T)-1.0));
|
||||
CHECK(Math::move_toward(-2.0, -5.0, 2.5) == doctest::Approx((T)-4.5));
|
||||
CHECK(Math::move_toward(-2.0, -5.0, 4.0) == doctest::Approx((T)-5.0));
|
||||
}
|
||||
|
||||
TEST_CASE_TEMPLATE("[Math] rotate_toward", T, float, double) {
|
||||
// Rotate toward.
|
||||
CHECK(Math::rotate_toward((T)0.0, (T)Math::PI * (T)0.75, (T)1.5) == doctest::Approx((T)1.5));
|
||||
CHECK(Math::rotate_toward((T)-2.0, (T)1.0, (T)2.5) == doctest::Approx((T)0.5));
|
||||
CHECK(Math::rotate_toward((T)-2.0, (T)Math::PI, (T)Math::PI) == doctest::Approx((T)-Math::PI));
|
||||
CHECK(Math::rotate_toward((T)1.0, (T)Math::PI, (T)20.0) == doctest::Approx((T)Math::PI));
|
||||
|
||||
// Rotate away.
|
||||
CHECK(Math::rotate_toward((T)0.0, (T)0.0, (T)-1.5) == doctest::Approx((T)-1.5));
|
||||
CHECK(Math::rotate_toward((T)0.0, (T)0.0, (T)-Math::PI) == doctest::Approx((T)-Math::PI));
|
||||
CHECK(Math::rotate_toward((T)3.0, (T)Math::PI, (T)-Math::PI) == doctest::Approx((T)0.0));
|
||||
CHECK(Math::rotate_toward((T)2.0, (T)Math::PI, (T)-1.5) == doctest::Approx((T)0.5));
|
||||
CHECK(Math::rotate_toward((T)1.0, (T)2.0, (T)-0.5) == doctest::Approx((T)0.5));
|
||||
CHECK(Math::rotate_toward((T)2.5, (T)2.0, (T)-0.5) == doctest::Approx((T)3.0));
|
||||
CHECK(Math::rotate_toward((T)-1.0, (T)1.0, (T)-1.0) == doctest::Approx((T)-2.0));
|
||||
}
|
||||
|
||||
TEST_CASE_TEMPLATE("[Math] smoothstep", T, float, double) {
|
||||
CHECK(Math::smoothstep((T)0.0, (T)2.0, (T)-5.0) == doctest::Approx((T)0.0));
|
||||
CHECK(Math::smoothstep((T)0.0, (T)2.0, (T)0.5) == doctest::Approx((T)0.15625));
|
||||
CHECK(Math::smoothstep((T)0.0, (T)2.0, (T)1.0) == doctest::Approx((T)0.5));
|
||||
CHECK(Math::smoothstep((T)0.0, (T)2.0, (T)2.0) == doctest::Approx((T)1.0));
|
||||
}
|
||||
|
||||
TEST_CASE("[Math] ease") {
|
||||
CHECK(Math::ease(0.1, 1.0) == doctest::Approx(0.1));
|
||||
CHECK(Math::ease(0.1, 2.0) == doctest::Approx(0.01));
|
||||
CHECK(Math::ease(0.1, 0.5) == doctest::Approx(0.19));
|
||||
CHECK(Math::ease(0.1, 0.0) == doctest::Approx(0));
|
||||
CHECK(Math::ease(0.1, -0.5) == doctest::Approx(0.2236067977));
|
||||
CHECK(Math::ease(0.1, -1.0) == doctest::Approx(0.1));
|
||||
CHECK(Math::ease(0.1, -2.0) == doctest::Approx(0.02));
|
||||
|
||||
CHECK(Math::ease(-1.0, 1.0) == doctest::Approx(0));
|
||||
CHECK(Math::ease(-1.0, 2.0) == doctest::Approx(0));
|
||||
CHECK(Math::ease(-1.0, 0.5) == doctest::Approx(0));
|
||||
CHECK(Math::ease(-1.0, 0.0) == doctest::Approx(0));
|
||||
CHECK(Math::ease(-1.0, -0.5) == doctest::Approx(0));
|
||||
CHECK(Math::ease(-1.0, -1.0) == doctest::Approx(0));
|
||||
CHECK(Math::ease(-1.0, -2.0) == doctest::Approx(0));
|
||||
}
|
||||
|
||||
TEST_CASE("[Math] snapped") {
|
||||
CHECK(Math::snapped(0.5, 0.04) == doctest::Approx(0.52));
|
||||
CHECK(Math::snapped(-0.5, 0.04) == doctest::Approx(-0.48));
|
||||
CHECK(Math::snapped(0.0, 0.04) == doctest::Approx(0));
|
||||
CHECK(Math::snapped(128'000.025, 0.04) == doctest::Approx(128'000.04));
|
||||
|
||||
CHECK(Math::snapped(0.5, 400) == doctest::Approx(0));
|
||||
CHECK(Math::snapped(-0.5, 400) == doctest::Approx(0));
|
||||
CHECK(Math::snapped(0.0, 400) == doctest::Approx(0));
|
||||
CHECK(Math::snapped(128'000.025, 400) == doctest::Approx(128'000.0));
|
||||
|
||||
CHECK(Math::snapped(0.5, 0.0) == doctest::Approx(0.5));
|
||||
CHECK(Math::snapped(-0.5, 0.0) == doctest::Approx(-0.5));
|
||||
CHECK(Math::snapped(0.0, 0.0) == doctest::Approx(0.0));
|
||||
CHECK(Math::snapped(128'000.025, 0.0) == doctest::Approx(128'000.0));
|
||||
|
||||
CHECK(Math::snapped(0.5, -1.0) == doctest::Approx(0));
|
||||
CHECK(Math::snapped(-0.5, -1.0) == doctest::Approx(-1.0));
|
||||
CHECK(Math::snapped(0.0, -1.0) == doctest::Approx(0));
|
||||
CHECK(Math::snapped(128'000.025, -1.0) == doctest::Approx(128'000.0));
|
||||
}
|
||||
|
||||
TEST_CASE("[Math] larger_prime") {
|
||||
CHECK(Math::larger_prime(0) == 5);
|
||||
CHECK(Math::larger_prime(1) == 5);
|
||||
CHECK(Math::larger_prime(2) == 5);
|
||||
CHECK(Math::larger_prime(5) == 13);
|
||||
CHECK(Math::larger_prime(500) == 769);
|
||||
CHECK(Math::larger_prime(1'000'000) == 1'572'869);
|
||||
CHECK(Math::larger_prime(1'000'000'000) == 1'610'612'741);
|
||||
|
||||
// The next prime is larger than `INT32_MAX` and is not present in the built-in prime table.
|
||||
ERR_PRINT_OFF;
|
||||
CHECK(Math::larger_prime(2'000'000'000) == 0);
|
||||
ERR_PRINT_ON;
|
||||
}
|
||||
|
||||
TEST_CASE_TEMPLATE("[Math] fmod", T, float, double) {
|
||||
CHECK(Math::fmod((T)-2.0, (T)0.3) == doctest::Approx((T)-0.2));
|
||||
CHECK(Math::fmod((T)0.0, (T)0.3) == doctest::Approx((T)0.0));
|
||||
CHECK(Math::fmod((T)2.0, (T)0.3) == doctest::Approx((T)0.2));
|
||||
|
||||
CHECK(Math::fmod((T)-2.0, (T)-0.3) == doctest::Approx((T)-0.2));
|
||||
CHECK(Math::fmod((T)0.0, (T)-0.3) == doctest::Approx((T)0.0));
|
||||
CHECK(Math::fmod((T)2.0, (T)-0.3) == doctest::Approx((T)0.2));
|
||||
}
|
||||
|
||||
TEST_CASE_TEMPLATE("[Math] fposmod", T, float, double) {
|
||||
CHECK(Math::fposmod((T)-2.0, (T)0.3) == doctest::Approx((T)0.1));
|
||||
CHECK(Math::fposmod((T)0.0, (T)0.3) == doctest::Approx((T)0.0));
|
||||
CHECK(Math::fposmod((T)2.0, (T)0.3) == doctest::Approx((T)0.2));
|
||||
|
||||
CHECK(Math::fposmod((T)-2.0, (T)-0.3) == doctest::Approx((T)-0.2));
|
||||
CHECK(Math::fposmod((T)0.0, (T)-0.3) == doctest::Approx((T)0.0));
|
||||
CHECK(Math::fposmod((T)2.0, (T)-0.3) == doctest::Approx((T)-0.1));
|
||||
}
|
||||
|
||||
TEST_CASE_TEMPLATE("[Math] fposmodp", T, float, double) {
|
||||
CHECK(Math::fposmodp((T)-2.0, (T)0.3) == doctest::Approx((T)0.1));
|
||||
CHECK(Math::fposmodp((T)0.0, (T)0.3) == doctest::Approx((T)0.0));
|
||||
CHECK(Math::fposmodp((T)2.0, (T)0.3) == doctest::Approx((T)0.2));
|
||||
|
||||
CHECK(Math::fposmodp((T)-2.0, (T)-0.3) == doctest::Approx((T)-0.5));
|
||||
CHECK(Math::fposmodp((T)0.0, (T)-0.3) == doctest::Approx((T)0.0));
|
||||
CHECK(Math::fposmodp((T)2.0, (T)-0.3) == doctest::Approx((T)0.2));
|
||||
}
|
||||
|
||||
TEST_CASE("[Math] posmod") {
|
||||
CHECK(Math::posmod(-20, 3) == 1);
|
||||
CHECK(Math::posmod(0, 3) == 0);
|
||||
CHECK(Math::posmod(20, 3) == 2);
|
||||
CHECK(Math::posmod(-20, -3) == -2);
|
||||
CHECK(Math::posmod(0, -3) == 0);
|
||||
CHECK(Math::posmod(20, -3) == -1);
|
||||
}
|
||||
|
||||
TEST_CASE("[Math] wrapi") {
|
||||
CHECK(Math::wrapi(-30, -20, 160) == 150);
|
||||
CHECK(Math::wrapi(30, -20, 160) == 30);
|
||||
CHECK(Math::wrapi(300, -20, 160) == 120);
|
||||
CHECK(Math::wrapi(300'000'000'000, -20, 160) == 120);
|
||||
}
|
||||
|
||||
TEST_CASE_TEMPLATE("[Math] wrapf", T, float, double) {
|
||||
CHECK(Math::wrapf((T)-30.0, (T)-20.0, (T)160.0) == doctest::Approx((T)150.0));
|
||||
CHECK(Math::wrapf((T)30.0, (T)-2.0, (T)160.0) == doctest::Approx((T)30.0));
|
||||
CHECK(Math::wrapf((T)300.0, (T)-20.0, (T)160.0) == doctest::Approx((T)120.0));
|
||||
|
||||
CHECK(Math::wrapf(300'000'000'000.0, -20.0, 160.0) == doctest::Approx((T)120.0));
|
||||
// float's precision is too low for 300'000'000'000.0, so we reduce it by a factor of 1000.
|
||||
CHECK(Math::wrapf((float)15'000'000.0, (float)-20.0, (float)160.0) == doctest::Approx((T)60.0));
|
||||
}
|
||||
|
||||
TEST_CASE_TEMPLATE("[Math] fract", T, float, double) {
|
||||
CHECK(Math::fract((T)1.0) == doctest::Approx((T)0.0));
|
||||
CHECK(Math::fract((T)77.8) == doctest::Approx((T)0.8));
|
||||
CHECK(Math::fract((T)-10.1) == doctest::Approx((T)0.9));
|
||||
}
|
||||
|
||||
TEST_CASE_TEMPLATE("[Math] pingpong", T, float, double) {
|
||||
CHECK(Math::pingpong((T)0.0, (T)0.0) == doctest::Approx((T)0.0));
|
||||
CHECK(Math::pingpong((T)1.0, (T)1.0) == doctest::Approx((T)1.0));
|
||||
CHECK(Math::pingpong((T)0.5, (T)2.0) == doctest::Approx((T)0.5));
|
||||
CHECK(Math::pingpong((T)3.5, (T)2.0) == doctest::Approx((T)0.5));
|
||||
CHECK(Math::pingpong((T)11.5, (T)2.0) == doctest::Approx((T)0.5));
|
||||
CHECK(Math::pingpong((T)-2.5, (T)2.0) == doctest::Approx((T)1.5));
|
||||
}
|
||||
|
||||
TEST_CASE_TEMPLATE("[Math] deg_to_rad/rad_to_deg", T, float, double) {
|
||||
CHECK(Math::deg_to_rad((T)180.0) == doctest::Approx((T)Math::PI));
|
||||
CHECK(Math::deg_to_rad((T)-27.0) == doctest::Approx((T)-0.471239));
|
||||
|
||||
CHECK(Math::rad_to_deg((T)Math::PI) == doctest::Approx((T)180.0));
|
||||
CHECK(Math::rad_to_deg((T)-1.5) == doctest::Approx((T)-85.94366927));
|
||||
}
|
||||
|
||||
TEST_CASE_TEMPLATE("[Math] cubic_interpolate", T, float, double) {
|
||||
CHECK(Math::cubic_interpolate((T)0.2, (T)0.8, (T)0.0, (T)1.0, (T)0.0) == doctest::Approx((T)0.2));
|
||||
CHECK(Math::cubic_interpolate((T)0.2, (T)0.8, (T)0.0, (T)1.0, (T)0.25) == doctest::Approx((T)0.33125));
|
||||
CHECK(Math::cubic_interpolate((T)0.2, (T)0.8, (T)0.0, (T)1.0, (T)0.5) == doctest::Approx((T)0.5));
|
||||
CHECK(Math::cubic_interpolate((T)0.2, (T)0.8, (T)0.0, (T)1.0, (T)0.75) == doctest::Approx((T)0.66875));
|
||||
CHECK(Math::cubic_interpolate((T)0.2, (T)0.8, (T)0.0, (T)1.0, (T)1.0) == doctest::Approx((T)0.8));
|
||||
|
||||
CHECK(Math::cubic_interpolate((T)20.2, (T)30.1, (T)-100.0, (T)32.0, (T)-50.0) == doctest::Approx((T)-6662732.3));
|
||||
CHECK(Math::cubic_interpolate((T)20.2, (T)30.1, (T)-100.0, (T)32.0, (T)-5.0) == doctest::Approx((T)-9356.3));
|
||||
CHECK(Math::cubic_interpolate((T)20.2, (T)30.1, (T)-100.0, (T)32.0, (T)0.0) == doctest::Approx((T)20.2));
|
||||
CHECK(Math::cubic_interpolate((T)20.2, (T)30.1, (T)-100.0, (T)32.0, (T)1.0) == doctest::Approx((T)30.1));
|
||||
CHECK(Math::cubic_interpolate((T)20.2, (T)30.1, (T)-100.0, (T)32.0, (T)4.0) == doctest::Approx((T)1853.2));
|
||||
}
|
||||
|
||||
TEST_CASE_TEMPLATE("[Math] cubic_interpolate_angle", T, float, double) {
|
||||
CHECK(Math::cubic_interpolate_angle((T)(Math::PI * (1.0 / 6.0)), (T)(Math::PI * (5.0 / 6.0)), (T)0.0, (T)Math::PI, (T)0.0) == doctest::Approx((T)Math::PI * (1.0 / 6.0)));
|
||||
CHECK(Math::cubic_interpolate_angle((T)(Math::PI * (1.0 / 6.0)), (T)(Math::PI * (5.0 / 6.0)), (T)0.0, (T)Math::PI, (T)0.25) == doctest::Approx((T)0.973566));
|
||||
CHECK(Math::cubic_interpolate_angle((T)(Math::PI * (1.0 / 6.0)), (T)(Math::PI * (5.0 / 6.0)), (T)0.0, (T)Math::PI, (T)0.5) == doctest::Approx((T)Math::PI / 2.0));
|
||||
CHECK(Math::cubic_interpolate_angle((T)(Math::PI * (1.0 / 6.0)), (T)(Math::PI * (5.0 / 6.0)), (T)0.0, (T)Math::PI, (T)0.75) == doctest::Approx((T)2.16803));
|
||||
CHECK(Math::cubic_interpolate_angle((T)(Math::PI * (1.0 / 6.0)), (T)(Math::PI * (5.0 / 6.0)), (T)0.0, (T)Math::PI, (T)1.0) == doctest::Approx((T)Math::PI * (5.0 / 6.0)));
|
||||
}
|
||||
|
||||
TEST_CASE_TEMPLATE("[Math] cubic_interpolate_in_time", T, float, double) {
|
||||
CHECK(Math::cubic_interpolate_in_time((T)0.2, (T)0.8, (T)0.0, (T)1.0, (T)0.0, (T)0.5, (T)0.0, (T)1.0) == doctest::Approx((T)0.0));
|
||||
CHECK(Math::cubic_interpolate_in_time((T)0.2, (T)0.8, (T)0.0, (T)1.0, (T)0.25, (T)0.5, (T)0.0, (T)1.0) == doctest::Approx((T)0.1625));
|
||||
CHECK(Math::cubic_interpolate_in_time((T)0.2, (T)0.8, (T)0.0, (T)1.0, (T)0.5, (T)0.5, (T)0.0, (T)1.0) == doctest::Approx((T)0.4));
|
||||
CHECK(Math::cubic_interpolate_in_time((T)0.2, (T)0.8, (T)0.0, (T)1.0, (T)0.75, (T)0.5, (T)0.0, (T)1.0) == doctest::Approx((T)0.6375));
|
||||
CHECK(Math::cubic_interpolate_in_time((T)0.2, (T)0.8, (T)0.0, (T)1.0, (T)1.0, (T)0.5, (T)0.0, (T)1.0) == doctest::Approx((T)0.8));
|
||||
}
|
||||
|
||||
TEST_CASE_TEMPLATE("[Math] cubic_interpolate_angle_in_time", T, float, double) {
|
||||
CHECK(Math::cubic_interpolate_angle_in_time((T)(Math::PI * (1.0 / 6.0)), (T)(Math::PI * (5.0 / 6.0)), (T)0.0, (T)Math::PI, (T)0.0, (T)0.5, (T)0.0, (T)1.0) == doctest::Approx((T)0.0));
|
||||
CHECK(Math::cubic_interpolate_angle_in_time((T)(Math::PI * (1.0 / 6.0)), (T)(Math::PI * (5.0 / 6.0)), (T)0.0, (T)Math::PI, (T)0.25, (T)0.5, (T)0.0, (T)1.0) == doctest::Approx((T)0.494964));
|
||||
CHECK(Math::cubic_interpolate_angle_in_time((T)(Math::PI * (1.0 / 6.0)), (T)(Math::PI * (5.0 / 6.0)), (T)0.0, (T)Math::PI, (T)0.5, (T)0.5, (T)0.0, (T)1.0) == doctest::Approx((T)1.27627));
|
||||
CHECK(Math::cubic_interpolate_angle_in_time((T)(Math::PI * (1.0 / 6.0)), (T)(Math::PI * (5.0 / 6.0)), (T)0.0, (T)Math::PI, (T)0.75, (T)0.5, (T)0.0, (T)1.0) == doctest::Approx((T)2.07394));
|
||||
CHECK(Math::cubic_interpolate_angle_in_time((T)(Math::PI * (1.0 / 6.0)), (T)(Math::PI * (5.0 / 6.0)), (T)0.0, (T)Math::PI, (T)1.0, (T)0.5, (T)0.0, (T)1.0) == doctest::Approx((T)Math::PI * (5.0 / 6.0)));
|
||||
}
|
||||
|
||||
TEST_CASE_TEMPLATE("[Math] bezier_interpolate", T, float, double) {
|
||||
CHECK(Math::bezier_interpolate((T)0.0, (T)0.2, (T)0.8, (T)1.0, (T)0.0) == doctest::Approx((T)0.0));
|
||||
CHECK(Math::bezier_interpolate((T)0.0, (T)0.2, (T)0.8, (T)1.0, (T)0.25) == doctest::Approx((T)0.2125));
|
||||
CHECK(Math::bezier_interpolate((T)0.0, (T)0.2, (T)0.8, (T)1.0, (T)0.5) == doctest::Approx((T)0.5));
|
||||
CHECK(Math::bezier_interpolate((T)0.0, (T)0.2, (T)0.8, (T)1.0, (T)0.75) == doctest::Approx((T)0.7875));
|
||||
CHECK(Math::bezier_interpolate((T)0.0, (T)0.2, (T)0.8, (T)1.0, (T)1.0) == doctest::Approx((T)1.0));
|
||||
}
|
||||
|
||||
} // namespace TestMath
|
||||
192
tests/core/math/test_plane.h
Normal file
@@ -0,0 +1,192 @@
|
||||
/**************************************************************************/
|
||||
/* test_plane.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/math/plane.h"
|
||||
|
||||
#include "thirdparty/doctest/doctest.h"
|
||||
|
||||
namespace TestPlane {
|
||||
|
||||
// Plane
|
||||
|
||||
TEST_CASE("[Plane] Constructor methods") {
|
||||
constexpr Plane plane = Plane(32, 22, 16, 3);
|
||||
constexpr Plane plane_vector = Plane(Vector3(32, 22, 16), 3);
|
||||
constexpr Plane plane_copy_plane = Plane(plane);
|
||||
|
||||
static_assert(
|
||||
plane == plane_vector,
|
||||
"Planes created with same values but different methods should be equal.");
|
||||
|
||||
static_assert(
|
||||
plane == plane_copy_plane,
|
||||
"Planes created with same values but different methods should be equal.");
|
||||
}
|
||||
|
||||
TEST_CASE("[Plane] Basic getters") {
|
||||
constexpr Plane plane = Plane(32, 22, 16, 3);
|
||||
constexpr Plane plane_normalized = Plane(32.0 / 42, 22.0 / 42, 16.0 / 42, 3.0 / 42);
|
||||
|
||||
CHECK_MESSAGE(
|
||||
plane.get_normal().is_equal_approx(Vector3(32, 22, 16)),
|
||||
"get_normal() should return the expected value.");
|
||||
|
||||
CHECK_MESSAGE(
|
||||
plane.normalized().is_equal_approx(plane_normalized),
|
||||
"normalized() should return a copy of the normalized value.");
|
||||
}
|
||||
|
||||
TEST_CASE("[Plane] Basic setters") {
|
||||
Plane plane = Plane(32, 22, 16, 3);
|
||||
plane.set_normal(Vector3(4, 2, 3));
|
||||
|
||||
CHECK_MESSAGE(
|
||||
plane.is_equal_approx(Plane(4, 2, 3, 3)),
|
||||
"set_normal() should result in the expected plane.");
|
||||
|
||||
plane = Plane(32, 22, 16, 3);
|
||||
plane.normalize();
|
||||
|
||||
CHECK_MESSAGE(
|
||||
plane.is_equal_approx(Plane(32.0 / 42, 22.0 / 42, 16.0 / 42, 3.0 / 42)),
|
||||
"normalize() should result in the expected plane.");
|
||||
}
|
||||
|
||||
TEST_CASE("[Plane] Plane-point operations") {
|
||||
constexpr Plane plane = Plane(32, 22, 16, 3);
|
||||
constexpr Plane y_facing_plane = Plane(0, 1, 0, 4);
|
||||
|
||||
CHECK_MESSAGE(
|
||||
plane.get_center().is_equal_approx(Vector3(32 * 3, 22 * 3, 16 * 3)),
|
||||
"get_center() should return a vector pointing to the center of the plane.");
|
||||
|
||||
CHECK_MESSAGE(
|
||||
y_facing_plane.is_point_over(Vector3(0, 5, 0)),
|
||||
"is_point_over() should return the expected result.");
|
||||
|
||||
CHECK_MESSAGE(
|
||||
y_facing_plane.get_any_perpendicular_normal().is_equal_approx(Vector3(1, 0, 0)),
|
||||
"get_any_perpendicular_normal() should return the expected result.");
|
||||
|
||||
// TODO distance_to()
|
||||
}
|
||||
|
||||
TEST_CASE("[Plane] Has point") {
|
||||
constexpr Plane x_facing_plane = Plane(1, 0, 0, 0);
|
||||
constexpr Plane y_facing_plane = Plane(0, 1, 0, 0);
|
||||
constexpr Plane z_facing_plane = Plane(0, 0, 1, 0);
|
||||
|
||||
constexpr Vector3 x_axis_point = Vector3(10, 0, 0);
|
||||
constexpr Vector3 y_axis_point = Vector3(0, 10, 0);
|
||||
constexpr Vector3 z_axis_point = Vector3(0, 0, 10);
|
||||
|
||||
constexpr Plane x_facing_plane_with_d_offset = Plane(1, 0, 0, 1);
|
||||
constexpr Vector3 y_axis_point_with_d_offset = Vector3(1, 10, 0);
|
||||
|
||||
CHECK_MESSAGE(
|
||||
x_facing_plane.has_point(y_axis_point),
|
||||
"has_point() with contained Vector3 should return the expected result.");
|
||||
CHECK_MESSAGE(
|
||||
x_facing_plane.has_point(z_axis_point),
|
||||
"has_point() with contained Vector3 should return the expected result.");
|
||||
|
||||
CHECK_MESSAGE(
|
||||
y_facing_plane.has_point(x_axis_point),
|
||||
"has_point() with contained Vector3 should return the expected result.");
|
||||
CHECK_MESSAGE(
|
||||
y_facing_plane.has_point(z_axis_point),
|
||||
"has_point() with contained Vector3 should return the expected result.");
|
||||
|
||||
CHECK_MESSAGE(
|
||||
z_facing_plane.has_point(y_axis_point),
|
||||
"has_point() with contained Vector3 should return the expected result.");
|
||||
CHECK_MESSAGE(
|
||||
z_facing_plane.has_point(x_axis_point),
|
||||
"has_point() with contained Vector3 should return the expected result.");
|
||||
|
||||
CHECK_MESSAGE(
|
||||
x_facing_plane_with_d_offset.has_point(y_axis_point_with_d_offset),
|
||||
"has_point() with passed Vector3 should return the expected result.");
|
||||
}
|
||||
|
||||
TEST_CASE("[Plane] Intersection") {
|
||||
constexpr Plane x_facing_plane = Plane(1, 0, 0, 1);
|
||||
constexpr Plane y_facing_plane = Plane(0, 1, 0, 2);
|
||||
constexpr Plane z_facing_plane = Plane(0, 0, 1, 3);
|
||||
|
||||
Vector3 vec_out;
|
||||
|
||||
CHECK_MESSAGE(
|
||||
x_facing_plane.intersect_3(y_facing_plane, z_facing_plane, &vec_out),
|
||||
"intersect_3() should return the expected result.");
|
||||
CHECK_MESSAGE(
|
||||
vec_out.is_equal_approx(Vector3(1, 2, 3)),
|
||||
"intersect_3() should modify vec_out to the expected result.");
|
||||
|
||||
CHECK_MESSAGE(
|
||||
x_facing_plane.intersects_ray(Vector3(0, 1, 1), Vector3(2, 0, 0), &vec_out),
|
||||
"intersects_ray() should return the expected result.");
|
||||
CHECK_MESSAGE(
|
||||
vec_out.is_equal_approx(Vector3(1, 1, 1)),
|
||||
"intersects_ray() should modify vec_out to the expected result.");
|
||||
|
||||
CHECK_MESSAGE(
|
||||
x_facing_plane.intersects_segment(Vector3(0, 1, 1), Vector3(2, 1, 1), &vec_out),
|
||||
"intersects_segment() should return the expected result.");
|
||||
CHECK_MESSAGE(
|
||||
vec_out.is_equal_approx(Vector3(1, 1, 1)),
|
||||
"intersects_segment() should modify vec_out to the expected result.");
|
||||
}
|
||||
|
||||
TEST_CASE("[Plane] Finite number checks") {
|
||||
constexpr Vector3 x(0, 1, 2);
|
||||
constexpr Vector3 infinite_vec(Math::NaN, Math::NaN, Math::NaN);
|
||||
constexpr real_t y = 0;
|
||||
constexpr real_t infinite_y = Math::NaN;
|
||||
|
||||
CHECK_MESSAGE(
|
||||
Plane(x, y).is_finite(),
|
||||
"Plane with all components finite should be finite");
|
||||
|
||||
CHECK_FALSE_MESSAGE(
|
||||
Plane(x, infinite_y).is_finite(),
|
||||
"Plane with one component infinite should not be finite.");
|
||||
CHECK_FALSE_MESSAGE(
|
||||
Plane(infinite_vec, y).is_finite(),
|
||||
"Plane with one component infinite should not be finite.");
|
||||
|
||||
CHECK_FALSE_MESSAGE(
|
||||
Plane(infinite_vec, infinite_y).is_finite(),
|
||||
"Plane with two components infinite should not be finite.");
|
||||
}
|
||||
|
||||
} // namespace TestPlane
|
||||
694
tests/core/math/test_projection.h
Normal file
@@ -0,0 +1,694 @@
|
||||
/**************************************************************************/
|
||||
/* test_projection.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/math/aabb.h"
|
||||
#include "core/math/plane.h"
|
||||
#include "core/math/projection.h"
|
||||
#include "core/math/rect2.h"
|
||||
#include "core/math/transform_3d.h"
|
||||
|
||||
#include "thirdparty/doctest/doctest.h"
|
||||
|
||||
namespace TestProjection {
|
||||
|
||||
TEST_CASE("[Projection] Construction") {
|
||||
Projection default_proj;
|
||||
|
||||
CHECK(default_proj[0].is_equal_approx(Vector4(1, 0, 0, 0)));
|
||||
CHECK(default_proj[1].is_equal_approx(Vector4(0, 1, 0, 0)));
|
||||
CHECK(default_proj[2].is_equal_approx(Vector4(0, 0, 1, 0)));
|
||||
CHECK(default_proj[3].is_equal_approx(Vector4(0, 0, 0, 1)));
|
||||
|
||||
Projection from_vec4(
|
||||
Vector4(1, 2, 3, 4),
|
||||
Vector4(5, 6, 7, 8),
|
||||
Vector4(9, 10, 11, 12),
|
||||
Vector4(13, 14, 15, 16));
|
||||
|
||||
CHECK(from_vec4[0].is_equal_approx(Vector4(1, 2, 3, 4)));
|
||||
CHECK(from_vec4[1].is_equal_approx(Vector4(5, 6, 7, 8)));
|
||||
CHECK(from_vec4[2].is_equal_approx(Vector4(9, 10, 11, 12)));
|
||||
CHECK(from_vec4[3].is_equal_approx(Vector4(13, 14, 15, 16)));
|
||||
|
||||
Transform3D transform(
|
||||
Basis(
|
||||
Vector3(1, 0, 0),
|
||||
Vector3(0, 2, 0),
|
||||
Vector3(0, 0, 3)),
|
||||
Vector3(4, 5, 6));
|
||||
|
||||
Projection from_transform(transform);
|
||||
|
||||
CHECK(from_transform[0].is_equal_approx(Vector4(1, 0, 0, 0)));
|
||||
CHECK(from_transform[1].is_equal_approx(Vector4(0, 2, 0, 0)));
|
||||
CHECK(from_transform[2].is_equal_approx(Vector4(0, 0, 3, 0)));
|
||||
CHECK(from_transform[3].is_equal_approx(Vector4(4, 5, 6, 1)));
|
||||
}
|
||||
|
||||
TEST_CASE("[Projection] set_zero()") {
|
||||
Projection proj;
|
||||
proj.set_zero();
|
||||
|
||||
for (int i = 0; i < 4; i++) {
|
||||
for (int j = 0; j < 4; j++) {
|
||||
CHECK(proj.columns[i][j] == 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("[Projection] set_identity()") {
|
||||
Projection proj;
|
||||
proj.set_identity();
|
||||
|
||||
for (int i = 0; i < 4; i++) {
|
||||
for (int j = 0; j < 4; j++) {
|
||||
CHECK(proj.columns[i][j] == (i == j ? 1 : 0));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("[Projection] determinant()") {
|
||||
Projection proj(
|
||||
Vector4(1, 5, 9, 13),
|
||||
Vector4(2, 6, 11, 15),
|
||||
Vector4(4, 7, 11, 15),
|
||||
Vector4(4, 8, 12, 16));
|
||||
|
||||
CHECK(proj.determinant() == -12);
|
||||
}
|
||||
|
||||
TEST_CASE("[Projection] Inverse and invert") {
|
||||
SUBCASE("[Projection] Arbitrary projection matrix inversion") {
|
||||
Projection proj(
|
||||
Vector4(1, 5, 9, 13),
|
||||
Vector4(2, 6, 11, 15),
|
||||
Vector4(4, 7, 11, 15),
|
||||
Vector4(4, 8, 12, 16));
|
||||
|
||||
Projection inverse_truth(
|
||||
Vector4(-4.0 / 12, 0, 1, -8.0 / 12),
|
||||
Vector4(8.0 / 12, -1, -1, 16.0 / 12),
|
||||
Vector4(-20.0 / 12, 2, -1, 5.0 / 12),
|
||||
Vector4(1, -1, 1, -0.75));
|
||||
|
||||
Projection inverse = proj.inverse();
|
||||
CHECK(inverse[0].is_equal_approx(inverse_truth[0]));
|
||||
CHECK(inverse[1].is_equal_approx(inverse_truth[1]));
|
||||
CHECK(inverse[2].is_equal_approx(inverse_truth[2]));
|
||||
CHECK(inverse[3].is_equal_approx(inverse_truth[3]));
|
||||
|
||||
proj.invert();
|
||||
CHECK(proj[0].is_equal_approx(inverse_truth[0]));
|
||||
CHECK(proj[1].is_equal_approx(inverse_truth[1]));
|
||||
CHECK(proj[2].is_equal_approx(inverse_truth[2]));
|
||||
CHECK(proj[3].is_equal_approx(inverse_truth[3]));
|
||||
}
|
||||
|
||||
SUBCASE("[Projection] Orthogonal projection matrix inversion") {
|
||||
Projection p = Projection::create_orthogonal(-125.0f, 125.0f, -125.0f, 125.0f, 0.01f, 25.0f);
|
||||
p = p.inverse() * p;
|
||||
|
||||
CHECK(p[0].is_equal_approx(Vector4(1, 0, 0, 0)));
|
||||
CHECK(p[1].is_equal_approx(Vector4(0, 1, 0, 0)));
|
||||
CHECK(p[2].is_equal_approx(Vector4(0, 0, 1, 0)));
|
||||
CHECK(p[3].is_equal_approx(Vector4(0, 0, 0, 1)));
|
||||
}
|
||||
|
||||
SUBCASE("[Projection] Perspective projection matrix inversion") {
|
||||
Projection p = Projection::create_perspective(90.0f, 1.77777f, 0.05f, 4000.0f);
|
||||
p = p.inverse() * p;
|
||||
|
||||
CHECK(p[0].is_equal_approx(Vector4(1, 0, 0, 0)));
|
||||
CHECK(p[1].is_equal_approx(Vector4(0, 1, 0, 0)));
|
||||
CHECK(p[2].is_equal_approx(Vector4(0, 0, 1, 0)));
|
||||
CHECK(p[3].is_equal_approx(Vector4(0, 0, 0, 1)));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("[Projection] Matrix product") {
|
||||
Projection proj1(
|
||||
Vector4(1, 5, 9, 13),
|
||||
Vector4(2, 6, 11, 15),
|
||||
Vector4(4, 7, 11, 15),
|
||||
Vector4(4, 8, 12, 16));
|
||||
|
||||
Projection proj2(
|
||||
Vector4(0, 1, 2, 3),
|
||||
Vector4(10, 11, 12, 13),
|
||||
Vector4(20, 21, 22, 23),
|
||||
Vector4(30, 31, 32, 33));
|
||||
|
||||
Projection prod = proj1 * proj2;
|
||||
|
||||
CHECK(prod[0].is_equal_approx(Vector4(22, 44, 69, 93)));
|
||||
CHECK(prod[1].is_equal_approx(Vector4(132, 304, 499, 683)));
|
||||
CHECK(prod[2].is_equal_approx(Vector4(242, 564, 929, 1273)));
|
||||
CHECK(prod[3].is_equal_approx(Vector4(352, 824, 1359, 1863)));
|
||||
}
|
||||
|
||||
TEST_CASE("[Projection] Vector transformation") {
|
||||
Projection proj(
|
||||
Vector4(1, 5, 9, 13),
|
||||
Vector4(2, 6, 11, 15),
|
||||
Vector4(4, 7, 11, 15),
|
||||
Vector4(4, 8, 12, 16));
|
||||
|
||||
Vector4 vec4(1, 2, 3, 4);
|
||||
CHECK(proj.xform(vec4).is_equal_approx(Vector4(33, 70, 112, 152)));
|
||||
CHECK(proj.xform_inv(vec4).is_equal_approx(Vector4(90, 107, 111, 120)));
|
||||
|
||||
Vector3 vec3(1, 2, 3);
|
||||
CHECK(proj.xform(vec3).is_equal_approx(Vector3(21, 46, 76) / 104));
|
||||
}
|
||||
|
||||
TEST_CASE("[Projection] Plane transformation") {
|
||||
Projection proj(
|
||||
Vector4(1, 5, 9, 13),
|
||||
Vector4(2, 6, 11, 15),
|
||||
Vector4(4, 7, 11, 15),
|
||||
Vector4(4, 8, 12, 16));
|
||||
|
||||
Plane plane(1, 2, 3, 4);
|
||||
CHECK(proj.xform4(plane).is_equal_approx(Plane(33, 70, 112, 152)));
|
||||
}
|
||||
|
||||
TEST_CASE("[Projection] Values access") {
|
||||
Projection proj(
|
||||
Vector4(00, 01, 02, 03),
|
||||
Vector4(10, 11, 12, 13),
|
||||
Vector4(20, 21, 22, 23),
|
||||
Vector4(30, 31, 32, 33));
|
||||
|
||||
CHECK(proj[0] == Vector4(00, 01, 02, 03));
|
||||
CHECK(proj[1] == Vector4(10, 11, 12, 13));
|
||||
CHECK(proj[2] == Vector4(20, 21, 22, 23));
|
||||
CHECK(proj[3] == Vector4(30, 31, 32, 33));
|
||||
}
|
||||
|
||||
TEST_CASE("[Projection] flip_y() and flipped_y()") {
|
||||
Projection proj(
|
||||
Vector4(00, 01, 02, 03),
|
||||
Vector4(10, 11, 12, 13),
|
||||
Vector4(20, 21, 22, 23),
|
||||
Vector4(30, 31, 32, 33));
|
||||
|
||||
Projection flipped = proj.flipped_y();
|
||||
|
||||
CHECK(flipped[0] == proj[0]);
|
||||
CHECK(flipped[1] == -proj[1]);
|
||||
CHECK(flipped[2] == proj[2]);
|
||||
CHECK(flipped[3] == proj[3]);
|
||||
|
||||
proj.flip_y();
|
||||
|
||||
CHECK(proj[0] == flipped[0]);
|
||||
CHECK(proj[1] == flipped[1]);
|
||||
CHECK(proj[2] == flipped[2]);
|
||||
CHECK(proj[3] == flipped[3]);
|
||||
}
|
||||
|
||||
TEST_CASE("[Projection] Jitter offset") {
|
||||
Projection proj(
|
||||
Vector4(00, 01, 02, 03),
|
||||
Vector4(10, 11, 12, 13),
|
||||
Vector4(20, 21, 22, 23),
|
||||
Vector4(30, 31, 32, 33));
|
||||
|
||||
Projection offsetted = proj.jitter_offseted(Vector2(1, 2));
|
||||
|
||||
CHECK(offsetted[0] == proj[0]);
|
||||
CHECK(offsetted[1] == proj[1]);
|
||||
CHECK(offsetted[2] == proj[2]);
|
||||
CHECK(offsetted[3] == proj[3] + Vector4(1, 2, 0, 0));
|
||||
|
||||
proj.add_jitter_offset(Vector2(1, 2));
|
||||
|
||||
CHECK(proj[0] == offsetted[0]);
|
||||
CHECK(proj[1] == offsetted[1]);
|
||||
CHECK(proj[2] == offsetted[2]);
|
||||
CHECK(proj[3] == offsetted[3]);
|
||||
}
|
||||
|
||||
TEST_CASE("[Projection] Adjust znear") {
|
||||
Projection persp = Projection::create_perspective(90, 0.5, 1, 50, false);
|
||||
Projection adjusted = persp.perspective_znear_adjusted(2);
|
||||
|
||||
CHECK(adjusted[0] == persp[0]);
|
||||
CHECK(adjusted[1] == persp[1]);
|
||||
CHECK(adjusted[2].is_equal_approx(Vector4(persp[2][0], persp[2][1], -1.083333, persp[2][3])));
|
||||
CHECK(adjusted[3].is_equal_approx(Vector4(persp[3][0], persp[3][1], -4.166666, persp[3][3])));
|
||||
|
||||
persp.adjust_perspective_znear(2);
|
||||
|
||||
CHECK(persp[0] == adjusted[0]);
|
||||
CHECK(persp[1] == adjusted[1]);
|
||||
CHECK(persp[2] == adjusted[2]);
|
||||
CHECK(persp[3] == adjusted[3]);
|
||||
}
|
||||
|
||||
TEST_CASE("[Projection] Set light bias") {
|
||||
Projection proj;
|
||||
proj.set_light_bias();
|
||||
|
||||
CHECK(proj[0] == Vector4(0.5, 0, 0, 0));
|
||||
CHECK(proj[1] == Vector4(0, 0.5, 0, 0));
|
||||
CHECK(proj[2] == Vector4(0, 0, 0.5, 0));
|
||||
CHECK(proj[3] == Vector4(0.5, 0.5, 0.5, 1));
|
||||
}
|
||||
|
||||
TEST_CASE("[Projection] Depth correction") {
|
||||
Projection corrected = Projection::create_depth_correction(true);
|
||||
|
||||
CHECK(corrected[0] == Vector4(1, 0, 0, 0));
|
||||
CHECK(corrected[1] == Vector4(0, -1, 0, 0));
|
||||
CHECK(corrected[2] == Vector4(0, 0, -0.5, 0));
|
||||
CHECK(corrected[3] == Vector4(0, 0, 0.5, 1));
|
||||
|
||||
Projection proj;
|
||||
proj.set_depth_correction(true, true, true);
|
||||
|
||||
CHECK(proj[0] == corrected[0]);
|
||||
CHECK(proj[1] == corrected[1]);
|
||||
CHECK(proj[2] == corrected[2]);
|
||||
CHECK(proj[3] == corrected[3]);
|
||||
|
||||
proj.set_depth_correction(false, true, true);
|
||||
|
||||
CHECK(proj[0] == Vector4(1, 0, 0, 0));
|
||||
CHECK(proj[1] == Vector4(0, 1, 0, 0));
|
||||
CHECK(proj[2] == Vector4(0, 0, -0.5, 0));
|
||||
CHECK(proj[3] == Vector4(0, 0, 0.5, 1));
|
||||
|
||||
proj.set_depth_correction(false, false, true);
|
||||
|
||||
CHECK(proj[0] == Vector4(1, 0, 0, 0));
|
||||
CHECK(proj[1] == Vector4(0, 1, 0, 0));
|
||||
CHECK(proj[2] == Vector4(0, 0, 0.5, 0));
|
||||
CHECK(proj[3] == Vector4(0, 0, 0.5, 1));
|
||||
|
||||
proj.set_depth_correction(false, false, false);
|
||||
|
||||
CHECK(proj[0] == Vector4(1, 0, 0, 0));
|
||||
CHECK(proj[1] == Vector4(0, 1, 0, 0));
|
||||
CHECK(proj[2] == Vector4(0, 0, 1, 0));
|
||||
CHECK(proj[3] == Vector4(0, 0, 0, 1));
|
||||
|
||||
proj.set_depth_correction(true, true, false);
|
||||
|
||||
CHECK(proj[0] == Vector4(1, 0, 0, 0));
|
||||
CHECK(proj[1] == Vector4(0, -1, 0, 0));
|
||||
CHECK(proj[2] == Vector4(0, 0, -1, 0));
|
||||
CHECK(proj[3] == Vector4(0, 0, 0, 1));
|
||||
}
|
||||
|
||||
TEST_CASE("[Projection] Light atlas rect") {
|
||||
Projection rect = Projection::create_light_atlas_rect(Rect2(1, 2, 30, 40));
|
||||
|
||||
CHECK(rect[0] == Vector4(30, 0, 0, 0));
|
||||
CHECK(rect[1] == Vector4(0, 40, 0, 0));
|
||||
CHECK(rect[2] == Vector4(0, 0, 1, 0));
|
||||
CHECK(rect[3] == Vector4(1, 2, 0, 1));
|
||||
|
||||
Projection proj;
|
||||
proj.set_light_atlas_rect(Rect2(1, 2, 30, 40));
|
||||
|
||||
CHECK(proj[0] == rect[0]);
|
||||
CHECK(proj[1] == rect[1]);
|
||||
CHECK(proj[2] == rect[2]);
|
||||
CHECK(proj[3] == rect[3]);
|
||||
}
|
||||
|
||||
TEST_CASE("[Projection] Make scale") {
|
||||
Projection proj;
|
||||
proj.make_scale(Vector3(2, 3, 4));
|
||||
|
||||
CHECK(proj[0] == Vector4(2, 0, 0, 0));
|
||||
CHECK(proj[1] == Vector4(0, 3, 0, 0));
|
||||
CHECK(proj[2] == Vector4(0, 0, 4, 0));
|
||||
CHECK(proj[3] == Vector4(0, 0, 0, 1));
|
||||
}
|
||||
|
||||
TEST_CASE("[Projection] Scale translate to fit aabb") {
|
||||
Projection fit = Projection::create_fit_aabb(AABB(Vector3(), Vector3(0.1, 0.2, 0.4)));
|
||||
|
||||
CHECK(fit[0] == Vector4(20, 0, 0, 0));
|
||||
CHECK(fit[1] == Vector4(0, 10, 0, 0));
|
||||
CHECK(fit[2] == Vector4(0, 0, 5, 0));
|
||||
CHECK(fit[3] == Vector4(-1, -1, -1, 1));
|
||||
|
||||
Projection proj;
|
||||
proj.scale_translate_to_fit(AABB(Vector3(), Vector3(0.1, 0.2, 0.4)));
|
||||
|
||||
CHECK(proj[0] == fit[0]);
|
||||
CHECK(proj[1] == fit[1]);
|
||||
CHECK(proj[2] == fit[2]);
|
||||
CHECK(proj[3] == fit[3]);
|
||||
}
|
||||
|
||||
TEST_CASE("[Projection] Perspective") {
|
||||
Projection persp = Projection::create_perspective(90, 0.5, 5, 15, false);
|
||||
|
||||
CHECK(persp[0].is_equal_approx(Vector4(2, 0, 0, 0)));
|
||||
CHECK(persp[1].is_equal_approx(Vector4(0, 1, 0, 0)));
|
||||
CHECK(persp[2].is_equal_approx(Vector4(0, 0, -2, -1)));
|
||||
CHECK(persp[3].is_equal_approx(Vector4(0, 0, -15, 0)));
|
||||
|
||||
Projection proj;
|
||||
proj.set_perspective(90, 0.5, 5, 15, false);
|
||||
|
||||
CHECK(proj[0] == persp[0]);
|
||||
CHECK(proj[1] == persp[1]);
|
||||
CHECK(proj[2] == persp[2]);
|
||||
CHECK(proj[3] == persp[3]);
|
||||
}
|
||||
|
||||
TEST_CASE("[Projection] Frustum") {
|
||||
Projection frustum = Projection::create_frustum(15, 20, 10, 12, 5, 15);
|
||||
|
||||
CHECK(frustum[0].is_equal_approx(Vector4(2, 0, 0, 0)));
|
||||
CHECK(frustum[1].is_equal_approx(Vector4(0, 5, 0, 0)));
|
||||
CHECK(frustum[2].is_equal_approx(Vector4(7, 11, -2, -1)));
|
||||
CHECK(frustum[3].is_equal_approx(Vector4(0, 0, -15, 0)));
|
||||
|
||||
Projection proj;
|
||||
proj.set_frustum(15, 20, 10, 12, 5, 15);
|
||||
|
||||
CHECK(proj[0] == frustum[0]);
|
||||
CHECK(proj[1] == frustum[1]);
|
||||
CHECK(proj[2] == frustum[2]);
|
||||
CHECK(proj[3] == frustum[3]);
|
||||
}
|
||||
|
||||
TEST_CASE("[Projection] Ortho") {
|
||||
Projection ortho = Projection::create_orthogonal(15, 20, 10, 12, 5, 15);
|
||||
|
||||
CHECK(ortho[0].is_equal_approx(Vector4(0.4, 0, 0, 0)));
|
||||
CHECK(ortho[1].is_equal_approx(Vector4(0, 1, 0, 0)));
|
||||
CHECK(ortho[2].is_equal_approx(Vector4(0, 0, -0.2, 0)));
|
||||
CHECK(ortho[3].is_equal_approx(Vector4(-7, -11, -2, 1)));
|
||||
|
||||
Projection proj;
|
||||
proj.set_orthogonal(15, 20, 10, 12, 5, 15);
|
||||
|
||||
CHECK(proj[0] == ortho[0]);
|
||||
CHECK(proj[1] == ortho[1]);
|
||||
CHECK(proj[2] == ortho[2]);
|
||||
CHECK(proj[3] == ortho[3]);
|
||||
}
|
||||
|
||||
TEST_CASE("[Projection] get_fovy()") {
|
||||
double fov = Projection::get_fovy(90, 0.5);
|
||||
CHECK(fov == doctest::Approx(53.1301));
|
||||
}
|
||||
|
||||
TEST_CASE("[Projection] Perspective values extraction") {
|
||||
Projection persp = Projection::create_perspective(90, 0.5, 1, 50, true);
|
||||
|
||||
double znear = persp.get_z_near();
|
||||
double zfar = persp.get_z_far();
|
||||
double aspect = persp.get_aspect();
|
||||
double fov = persp.get_fov();
|
||||
|
||||
CHECK(znear == doctest::Approx(1));
|
||||
CHECK(zfar == doctest::Approx(50));
|
||||
CHECK(aspect == doctest::Approx(0.5));
|
||||
CHECK(fov == doctest::Approx(90));
|
||||
|
||||
persp.set_perspective(38, 1.3, 0.2, 8, false);
|
||||
|
||||
znear = persp.get_z_near();
|
||||
zfar = persp.get_z_far();
|
||||
aspect = persp.get_aspect();
|
||||
fov = persp.get_fov();
|
||||
|
||||
CHECK(znear == doctest::Approx(0.2));
|
||||
CHECK(zfar == doctest::Approx(8));
|
||||
CHECK(aspect == doctest::Approx(1.3));
|
||||
CHECK(fov == doctest::Approx(Projection::get_fovy(38, 1.3)));
|
||||
|
||||
persp.set_perspective(47, 2.5, 0.9, 14, true);
|
||||
|
||||
znear = persp.get_z_near();
|
||||
zfar = persp.get_z_far();
|
||||
aspect = persp.get_aspect();
|
||||
fov = persp.get_fov();
|
||||
|
||||
CHECK(znear == doctest::Approx(0.9));
|
||||
CHECK(zfar == doctest::Approx(14));
|
||||
CHECK(aspect == doctest::Approx(2.5));
|
||||
CHECK(fov == doctest::Approx(47));
|
||||
}
|
||||
|
||||
TEST_CASE("[Projection] Frustum values extraction") {
|
||||
Projection frustum = Projection::create_frustum_aspect(1.0, 4.0 / 3.0, Vector2(0.5, -0.25), 0.5, 50, true);
|
||||
|
||||
double znear = frustum.get_z_near();
|
||||
double zfar = frustum.get_z_far();
|
||||
double aspect = frustum.get_aspect();
|
||||
double fov = frustum.get_fov();
|
||||
|
||||
CHECK(znear == doctest::Approx(0.5));
|
||||
CHECK(zfar == doctest::Approx(50));
|
||||
CHECK(aspect == doctest::Approx(4.0 / 3.0));
|
||||
CHECK(fov == doctest::Approx(Math::rad_to_deg(Math::atan(2.0))));
|
||||
|
||||
frustum.set_frustum(2.0, 1.5, Vector2(-0.5, 2), 2, 12, false);
|
||||
|
||||
znear = frustum.get_z_near();
|
||||
zfar = frustum.get_z_far();
|
||||
aspect = frustum.get_aspect();
|
||||
fov = frustum.get_fov();
|
||||
|
||||
CHECK(znear == doctest::Approx(2));
|
||||
CHECK(zfar == doctest::Approx(12));
|
||||
CHECK(aspect == doctest::Approx(1.5));
|
||||
CHECK(fov == doctest::Approx(Math::rad_to_deg(Math::atan(1.0) + Math::atan(0.5))));
|
||||
}
|
||||
|
||||
TEST_CASE("[Projection] Orthographic values extraction") {
|
||||
Projection ortho = Projection::create_orthogonal(-2, 3, -0.5, 1.5, 1.2, 15);
|
||||
|
||||
double znear = ortho.get_z_near();
|
||||
double zfar = ortho.get_z_far();
|
||||
double aspect = ortho.get_aspect();
|
||||
|
||||
CHECK(znear == doctest::Approx(1.2));
|
||||
CHECK(zfar == doctest::Approx(15));
|
||||
CHECK(aspect == doctest::Approx(2.5));
|
||||
|
||||
ortho.set_orthogonal(-7, 2, 2.5, 5.5, 0.5, 6);
|
||||
|
||||
znear = ortho.get_z_near();
|
||||
zfar = ortho.get_z_far();
|
||||
aspect = ortho.get_aspect();
|
||||
|
||||
CHECK(znear == doctest::Approx(0.5));
|
||||
CHECK(zfar == doctest::Approx(6));
|
||||
CHECK(aspect == doctest::Approx(3));
|
||||
}
|
||||
|
||||
TEST_CASE("[Projection] Orthographic check") {
|
||||
Projection persp = Projection::create_perspective(90, 0.5, 1, 50, false);
|
||||
Projection ortho = Projection::create_orthogonal(15, 20, 10, 12, 5, 15);
|
||||
|
||||
CHECK(!persp.is_orthogonal());
|
||||
CHECK(ortho.is_orthogonal());
|
||||
}
|
||||
|
||||
TEST_CASE("[Projection] Planes extraction") {
|
||||
Projection persp = Projection::create_perspective(90, 1, 1, 40, false);
|
||||
Vector<Plane> planes = persp.get_projection_planes(Transform3D());
|
||||
|
||||
CHECK(planes[Projection::PLANE_NEAR].normalized().is_equal_approx(Plane(0, 0, 1, -1)));
|
||||
CHECK(planes[Projection::PLANE_FAR].normalized().is_equal_approx(Plane(0, 0, -1, 40)));
|
||||
CHECK(planes[Projection::PLANE_LEFT].normalized().is_equal_approx(Plane(-0.707107, 0, 0.707107, 0)));
|
||||
CHECK(planes[Projection::PLANE_TOP].normalized().is_equal_approx(Plane(0, 0.707107, 0.707107, 0)));
|
||||
CHECK(planes[Projection::PLANE_RIGHT].normalized().is_equal_approx(Plane(0.707107, 0, 0.707107, 0)));
|
||||
CHECK(planes[Projection::PLANE_BOTTOM].normalized().is_equal_approx(Plane(0, -0.707107, 0.707107, 0)));
|
||||
|
||||
Plane plane_array[6]{
|
||||
persp.get_projection_plane(Projection::PLANE_NEAR),
|
||||
persp.get_projection_plane(Projection::PLANE_FAR),
|
||||
persp.get_projection_plane(Projection::PLANE_LEFT),
|
||||
persp.get_projection_plane(Projection::PLANE_TOP),
|
||||
persp.get_projection_plane(Projection::PLANE_RIGHT),
|
||||
persp.get_projection_plane(Projection::PLANE_BOTTOM)
|
||||
};
|
||||
|
||||
CHECK(plane_array[Projection::PLANE_NEAR].normalized().is_equal_approx(planes[Projection::PLANE_NEAR].normalized()));
|
||||
CHECK(plane_array[Projection::PLANE_FAR].normalized().is_equal_approx(planes[Projection::PLANE_FAR].normalized()));
|
||||
CHECK(plane_array[Projection::PLANE_LEFT].normalized().is_equal_approx(planes[Projection::PLANE_LEFT].normalized()));
|
||||
CHECK(plane_array[Projection::PLANE_TOP].normalized().is_equal_approx(planes[Projection::PLANE_TOP].normalized()));
|
||||
CHECK(plane_array[Projection::PLANE_RIGHT].normalized().is_equal_approx(planes[Projection::PLANE_RIGHT].normalized()));
|
||||
CHECK(plane_array[Projection::PLANE_BOTTOM].normalized().is_equal_approx(planes[Projection::PLANE_BOTTOM].normalized()));
|
||||
}
|
||||
|
||||
TEST_CASE("[Projection] Perspective Half extents") {
|
||||
constexpr real_t sqrt3 = 1.7320508;
|
||||
Projection persp = Projection::create_perspective(90, 1, 1, 40, false);
|
||||
Vector2 ne = persp.get_viewport_half_extents();
|
||||
Vector2 fe = persp.get_far_plane_half_extents();
|
||||
|
||||
CHECK(ne.is_equal_approx(Vector2(1, 1) * 1));
|
||||
CHECK(fe.is_equal_approx(Vector2(1, 1) * 40));
|
||||
|
||||
persp.set_perspective(120, sqrt3, 0.8, 10, true);
|
||||
ne = persp.get_viewport_half_extents();
|
||||
fe = persp.get_far_plane_half_extents();
|
||||
|
||||
CHECK(ne.is_equal_approx(Vector2(sqrt3, 1.0) * 0.8));
|
||||
CHECK(fe.is_equal_approx(Vector2(sqrt3, 1.0) * 10));
|
||||
|
||||
persp.set_perspective(60, 1.2, 0.5, 15, false);
|
||||
ne = persp.get_viewport_half_extents();
|
||||
fe = persp.get_far_plane_half_extents();
|
||||
|
||||
CHECK(ne.is_equal_approx(Vector2(sqrt3 / 3 * 1.2, sqrt3 / 3) * 0.5));
|
||||
CHECK(fe.is_equal_approx(Vector2(sqrt3 / 3 * 1.2, sqrt3 / 3) * 15));
|
||||
}
|
||||
|
||||
TEST_CASE("[Projection] Orthographic Half extents") {
|
||||
Projection ortho = Projection::create_orthogonal(-3, 3, -1.5, 1.5, 1.2, 15);
|
||||
Vector2 ne = ortho.get_viewport_half_extents();
|
||||
Vector2 fe = ortho.get_far_plane_half_extents();
|
||||
|
||||
CHECK(ne.is_equal_approx(Vector2(3, 1.5)));
|
||||
CHECK(fe.is_equal_approx(Vector2(3, 1.5)));
|
||||
|
||||
ortho.set_orthogonal(-7, 7, -2.5, 2.5, 0.5, 6);
|
||||
ne = ortho.get_viewport_half_extents();
|
||||
fe = ortho.get_far_plane_half_extents();
|
||||
|
||||
CHECK(ne.is_equal_approx(Vector2(7, 2.5)));
|
||||
CHECK(fe.is_equal_approx(Vector2(7, 2.5)));
|
||||
}
|
||||
|
||||
TEST_CASE("[Projection] Endpoints") {
|
||||
constexpr real_t sqrt3 = 1.7320508;
|
||||
Projection persp = Projection::create_perspective(90, 1, 1, 40, false);
|
||||
Vector3 ep[8];
|
||||
persp.get_endpoints(Transform3D(), ep);
|
||||
|
||||
CHECK(ep[0].is_equal_approx(Vector3(-1, 1, -1) * 40));
|
||||
CHECK(ep[1].is_equal_approx(Vector3(-1, -1, -1) * 40));
|
||||
CHECK(ep[2].is_equal_approx(Vector3(1, 1, -1) * 40));
|
||||
CHECK(ep[3].is_equal_approx(Vector3(1, -1, -1) * 40));
|
||||
CHECK(ep[4].is_equal_approx(Vector3(-1, 1, -1) * 1));
|
||||
CHECK(ep[5].is_equal_approx(Vector3(-1, -1, -1) * 1));
|
||||
CHECK(ep[6].is_equal_approx(Vector3(1, 1, -1) * 1));
|
||||
CHECK(ep[7].is_equal_approx(Vector3(1, -1, -1) * 1));
|
||||
|
||||
persp.set_perspective(120, sqrt3, 0.8, 10, true);
|
||||
persp.get_endpoints(Transform3D(), ep);
|
||||
|
||||
CHECK(ep[0].is_equal_approx(Vector3(-sqrt3, 1, -1) * 10));
|
||||
CHECK(ep[1].is_equal_approx(Vector3(-sqrt3, -1, -1) * 10));
|
||||
CHECK(ep[2].is_equal_approx(Vector3(sqrt3, 1, -1) * 10));
|
||||
CHECK(ep[3].is_equal_approx(Vector3(sqrt3, -1, -1) * 10));
|
||||
CHECK(ep[4].is_equal_approx(Vector3(-sqrt3, 1, -1) * 0.8));
|
||||
CHECK(ep[5].is_equal_approx(Vector3(-sqrt3, -1, -1) * 0.8));
|
||||
CHECK(ep[6].is_equal_approx(Vector3(sqrt3, 1, -1) * 0.8));
|
||||
CHECK(ep[7].is_equal_approx(Vector3(sqrt3, -1, -1) * 0.8));
|
||||
|
||||
persp.set_perspective(60, 1.2, 0.5, 15, false);
|
||||
persp.get_endpoints(Transform3D(), ep);
|
||||
|
||||
CHECK(ep[0].is_equal_approx(Vector3(-sqrt3 / 3 * 1.2, sqrt3 / 3, -1) * 15));
|
||||
CHECK(ep[1].is_equal_approx(Vector3(-sqrt3 / 3 * 1.2, -sqrt3 / 3, -1) * 15));
|
||||
CHECK(ep[2].is_equal_approx(Vector3(sqrt3 / 3 * 1.2, sqrt3 / 3, -1) * 15));
|
||||
CHECK(ep[3].is_equal_approx(Vector3(sqrt3 / 3 * 1.2, -sqrt3 / 3, -1) * 15));
|
||||
CHECK(ep[4].is_equal_approx(Vector3(-sqrt3 / 3 * 1.2, sqrt3 / 3, -1) * 0.5));
|
||||
CHECK(ep[5].is_equal_approx(Vector3(-sqrt3 / 3 * 1.2, -sqrt3 / 3, -1) * 0.5));
|
||||
CHECK(ep[6].is_equal_approx(Vector3(sqrt3 / 3 * 1.2, sqrt3 / 3, -1) * 0.5));
|
||||
CHECK(ep[7].is_equal_approx(Vector3(sqrt3 / 3 * 1.2, -sqrt3 / 3, -1) * 0.5));
|
||||
}
|
||||
|
||||
TEST_CASE("[Projection] LOD multiplier") {
|
||||
constexpr real_t sqrt3 = 1.7320508;
|
||||
Projection proj;
|
||||
real_t multiplier;
|
||||
|
||||
proj.set_perspective(60, 1, 1, 40, false);
|
||||
multiplier = proj.get_lod_multiplier();
|
||||
CHECK(multiplier == doctest::Approx(2 * sqrt3 / 3));
|
||||
|
||||
proj.set_perspective(120, 1.5, 0.5, 20, false);
|
||||
multiplier = proj.get_lod_multiplier();
|
||||
CHECK(multiplier == doctest::Approx(3 * sqrt3));
|
||||
|
||||
proj.set_orthogonal(15, 20, 10, 12, 5, 15);
|
||||
multiplier = proj.get_lod_multiplier();
|
||||
CHECK(multiplier == doctest::Approx(5));
|
||||
|
||||
proj.set_orthogonal(-5, 15, -8, 10, 1.5, 10);
|
||||
multiplier = proj.get_lod_multiplier();
|
||||
CHECK(multiplier == doctest::Approx(20));
|
||||
|
||||
proj.set_frustum(1.0, 4.0 / 3.0, Vector2(0.5, -0.25), 0.5, 50, false);
|
||||
multiplier = proj.get_lod_multiplier();
|
||||
CHECK(multiplier == doctest::Approx(8.0 / 3.0));
|
||||
|
||||
proj.set_frustum(2.0, 1.2, Vector2(-0.1, 0.8), 1, 10, true);
|
||||
multiplier = proj.get_lod_multiplier();
|
||||
CHECK(multiplier == doctest::Approx(2));
|
||||
}
|
||||
|
||||
TEST_CASE("[Projection] Pixels per meter") {
|
||||
constexpr real_t sqrt3 = 1.7320508;
|
||||
Projection proj;
|
||||
int ppm;
|
||||
|
||||
proj.set_perspective(60, 1, 1, 40, false);
|
||||
ppm = proj.get_pixels_per_meter(1024);
|
||||
CHECK(ppm == int(1536.0f / sqrt3));
|
||||
|
||||
proj.set_perspective(120, 1.5, 0.5, 20, false);
|
||||
ppm = proj.get_pixels_per_meter(1200);
|
||||
CHECK(ppm == int(800.0f / sqrt3));
|
||||
|
||||
proj.set_orthogonal(15, 20, 10, 12, 5, 15);
|
||||
ppm = proj.get_pixels_per_meter(500);
|
||||
CHECK(ppm == 100);
|
||||
|
||||
proj.set_orthogonal(-5, 15, -8, 10, 1.5, 10);
|
||||
ppm = proj.get_pixels_per_meter(640);
|
||||
CHECK(ppm == 32);
|
||||
|
||||
proj.set_frustum(1.0, 4.0 / 3.0, Vector2(0.5, -0.25), 0.5, 50, false);
|
||||
ppm = proj.get_pixels_per_meter(2048);
|
||||
CHECK(ppm == 1536);
|
||||
|
||||
proj.set_frustum(2.0, 1.2, Vector2(-0.1, 0.8), 1, 10, true);
|
||||
ppm = proj.get_pixels_per_meter(800);
|
||||
CHECK(ppm == 400);
|
||||
}
|
||||
} //namespace TestProjection
|
||||
493
tests/core/math/test_quaternion.h
Normal file
@@ -0,0 +1,493 @@
|
||||
/**************************************************************************/
|
||||
/* test_quaternion.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/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::deg_to_rad(angle[1]);
|
||||
double pitch = Math::deg_to_rad(angle[0]);
|
||||
double roll = Math::deg_to_rad(angle[2]);
|
||||
|
||||
// Generate YXZ (Z-then-X-then-Y) Quaternion using single-axis Euler
|
||||
// constructor and quaternion product, both tested separately.
|
||||
Quaternion q_y = Quaternion::from_euler(Vector3(0.0, yaw, 0.0));
|
||||
Quaternion q_p = Quaternion::from_euler(Vector3(pitch, 0.0, 0.0));
|
||||
Quaternion q_r = Quaternion::from_euler(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") {
|
||||
constexpr 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).
|
||||
constexpr 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::deg_to_rad(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::deg_to_rad(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::deg_to_rad(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.
|
||||
constexpr Vector3 axis(1.0, 2.0, 0.5);
|
||||
Quaternion q(axis.normalized(), Math::deg_to_rad(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") {
|
||||
constexpr Vector3 axis(1.0, 2.0, 0.5);
|
||||
Quaternion q_src(axis.normalized(), Math::deg_to_rad(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::deg_to_rad(45.0);
|
||||
double pitch = Math::deg_to_rad(30.0);
|
||||
double roll = Math::deg_to_rad(10.0);
|
||||
|
||||
Vector3 euler_y(0.0, yaw, 0.0);
|
||||
Quaternion q_y = Quaternion::from_euler(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 = Quaternion::from_euler(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 = Quaternion::from_euler(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::deg_to_rad(45.0);
|
||||
double pitch = Math::deg_to_rad(30.0);
|
||||
double roll = Math::deg_to_rad(10.0);
|
||||
|
||||
// Generate YXZ comparison 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 = Quaternion::from_euler(euler_y);
|
||||
Vector3 euler_p(pitch, 0.0, 0.0);
|
||||
Quaternion q_p = Quaternion::from_euler(euler_p);
|
||||
Vector3 euler_r(0.0, 0.0, roll);
|
||||
Quaternion q_r = Quaternion::from_euler(euler_r);
|
||||
|
||||
// Instrinsically, Yaw-Y then Pitch-X then Roll-Z.
|
||||
// Extrinsically, Roll-Z is followed by Pitch-X, then Yaw-Y.
|
||||
Quaternion check_yxz = q_y * q_p * q_r;
|
||||
|
||||
// Test construction from YXZ Euler angles.
|
||||
Vector3 euler_yxz(pitch, yaw, roll);
|
||||
Quaternion q = Quaternion::from_euler(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]));
|
||||
|
||||
CHECK(q.is_equal_approx(check_yxz));
|
||||
CHECK(q.get_euler().is_equal_approx(euler_yxz));
|
||||
CHECK(check_yxz.get_euler().is_equal_approx(euler_yxz));
|
||||
}
|
||||
|
||||
TEST_CASE("[Quaternion] Construct Basis Euler") {
|
||||
double yaw = Math::deg_to_rad(45.0);
|
||||
double pitch = Math::deg_to_rad(30.0);
|
||||
double roll = Math::deg_to_rad(10.0);
|
||||
Vector3 euler_yxz(pitch, yaw, roll);
|
||||
Quaternion q_yxz = Quaternion::from_euler(euler_yxz);
|
||||
Basis basis_axes = Basis::from_euler(euler_yxz);
|
||||
Quaternion q(basis_axes);
|
||||
CHECK(q.is_equal_approx(q_yxz));
|
||||
}
|
||||
|
||||
TEST_CASE("[Quaternion] Construct Basis Axes") {
|
||||
// Arbitrary Euler angles.
|
||||
const Vector3 euler_yxz(Math::deg_to_rad(31.41), Math::deg_to_rad(-49.16), Math::deg_to_rad(12.34));
|
||||
// Basis vectors from online calculation of rotation matrix.
|
||||
constexpr Vector3 i_unit(0.5545787, 0.1823950, 0.8118957);
|
||||
constexpr Vector3 j_unit(-0.5249245, 0.8337420, 0.1712555);
|
||||
constexpr Vector3 k_unit(-0.6456754, -0.5211586, 0.5581192);
|
||||
// Quaternion from online calculation.
|
||||
constexpr Quaternion q_calc(0.2016913, -0.4245716, 0.206033, 0.8582598);
|
||||
// Quaternion from local calculation.
|
||||
const Quaternion q_local = quat_euler_yxz_deg(Vector3(31.41, -49.16, 12.34));
|
||||
// Quaternion from Euler angles constructor.
|
||||
const Quaternion q_euler = Quaternion::from_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 = Basis::from_euler(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] Construct Shortest Arc For 180 Degree Arc") {
|
||||
Vector3 up(0, 1, 0);
|
||||
Vector3 down(0, -1, 0);
|
||||
Vector3 left(-1, 0, 0);
|
||||
Vector3 right(1, 0, 0);
|
||||
Vector3 forward(0, 0, -1);
|
||||
Vector3 back(0, 0, 1);
|
||||
|
||||
// When we have a 180 degree rotation quaternion which was defined as
|
||||
// A to B, logically when we transform A we expect to get B.
|
||||
Quaternion left_to_right(left, right);
|
||||
Quaternion right_to_left(right, left);
|
||||
CHECK(left_to_right.xform(left).is_equal_approx(right));
|
||||
CHECK(Quaternion(right, left).xform(right).is_equal_approx(left));
|
||||
CHECK(Quaternion(up, down).xform(up).is_equal_approx(down));
|
||||
CHECK(Quaternion(down, up).xform(down).is_equal_approx(up));
|
||||
CHECK(Quaternion(forward, back).xform(forward).is_equal_approx(back));
|
||||
CHECK(Quaternion(back, forward).xform(back).is_equal_approx(forward));
|
||||
|
||||
// With (arbitrary) opposite vectors that are not axis-aligned as parameters.
|
||||
Vector3 diagonal_up = Vector3(1.2, 2.3, 4.5).normalized();
|
||||
Vector3 diagonal_down = -diagonal_up;
|
||||
Quaternion q1(diagonal_up, diagonal_down);
|
||||
CHECK(q1.xform(diagonal_down).is_equal_approx(diagonal_up));
|
||||
CHECK(q1.xform(diagonal_up).is_equal_approx(diagonal_down));
|
||||
|
||||
// For the consistency of the rotation direction, they should be symmetrical to the plane.
|
||||
CHECK(left_to_right.is_equal_approx(right_to_left.inverse()));
|
||||
|
||||
// If vectors are same, no rotation.
|
||||
CHECK(Quaternion(diagonal_up, diagonal_up).is_equal_approx(Quaternion()));
|
||||
}
|
||||
|
||||
TEST_CASE("[Quaternion] Get Euler Orders") {
|
||||
double x = Math::deg_to_rad(30.0);
|
||||
double y = Math::deg_to_rad(45.0);
|
||||
double z = Math::deg_to_rad(10.0);
|
||||
Vector3 euler(x, y, z);
|
||||
for (int i = 0; i < 6; i++) {
|
||||
EulerOrder order = (EulerOrder)i;
|
||||
Basis basis = Basis::from_euler(euler, order);
|
||||
Quaternion q = Quaternion(basis);
|
||||
Vector3 check = q.get_euler(order);
|
||||
CHECK_MESSAGE(check.is_equal_approx(euler),
|
||||
"Quaternion get_euler method should return the original angles.");
|
||||
CHECK_MESSAGE(check.is_equal_approx(basis.get_euler(order)),
|
||||
"Quaternion get_euler method should behave the same as Basis get_euler.");
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("[Quaternion] Product (book)") {
|
||||
// Example from "Quaternions and Rotation Sequences" by Jack Kuipers, p. 108.
|
||||
constexpr Quaternion p(1.0, -2.0, 1.0, 3.0);
|
||||
constexpr Quaternion q(-1.0, 2.0, 3.0, 2.0);
|
||||
|
||||
constexpr 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::deg_to_rad(45.0);
|
||||
double pitch = Math::deg_to_rad(30.0);
|
||||
double roll = Math::deg_to_rad(10.0);
|
||||
|
||||
Vector3 euler_y(0.0, yaw, 0.0);
|
||||
Quaternion q_y = Quaternion::from_euler(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 = Quaternion::from_euler(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 = Quaternion::from_euler(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::deg_to_rad(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::deg_to_rad(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::deg_to_rad(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.
|
||||
const Vector3 euler_yzx(Math::deg_to_rad(31.41), Math::deg_to_rad(-49.16), Math::deg_to_rad(12.34));
|
||||
const Basis basis_axes = Basis::from_euler(euler_yzx);
|
||||
const Quaternion q(basis_axes);
|
||||
|
||||
constexpr Vector3 v_arb(3.0, 4.0, 5.0);
|
||||
const Vector3 v_rot = q.xform(v_arb);
|
||||
const 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) {
|
||||
const Basis basis_axes = Basis::from_euler(euler_yzx);
|
||||
const Quaternion q(basis_axes);
|
||||
|
||||
const Vector3 v_rot = q.xform(v_in);
|
||||
const 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.
|
||||
constexpr int STEPS = 100; // Number of test steps in each dimension
|
||||
constexpr double delta = 2.0 * Math::PI / STEPS; // Angle increment per step
|
||||
constexpr 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("[Quaternion] Finite number checks") {
|
||||
constexpr real_t x = Math::NaN;
|
||||
|
||||
CHECK_MESSAGE(
|
||||
Quaternion(0, 1, 2, 3).is_finite(),
|
||||
"Quaternion with all components finite should be finite");
|
||||
|
||||
CHECK_FALSE_MESSAGE(
|
||||
Quaternion(x, 1, 2, 3).is_finite(),
|
||||
"Quaternion with one component infinite should not be finite.");
|
||||
CHECK_FALSE_MESSAGE(
|
||||
Quaternion(0, x, 2, 3).is_finite(),
|
||||
"Quaternion with one component infinite should not be finite.");
|
||||
CHECK_FALSE_MESSAGE(
|
||||
Quaternion(0, 1, x, 3).is_finite(),
|
||||
"Quaternion with one component infinite should not be finite.");
|
||||
CHECK_FALSE_MESSAGE(
|
||||
Quaternion(0, 1, 2, x).is_finite(),
|
||||
"Quaternion with one component infinite should not be finite.");
|
||||
|
||||
CHECK_FALSE_MESSAGE(
|
||||
Quaternion(x, x, 2, 3).is_finite(),
|
||||
"Quaternion with two components infinite should not be finite.");
|
||||
CHECK_FALSE_MESSAGE(
|
||||
Quaternion(x, 1, x, 3).is_finite(),
|
||||
"Quaternion with two components infinite should not be finite.");
|
||||
CHECK_FALSE_MESSAGE(
|
||||
Quaternion(x, 1, 2, x).is_finite(),
|
||||
"Quaternion with two components infinite should not be finite.");
|
||||
CHECK_FALSE_MESSAGE(
|
||||
Quaternion(0, x, x, 3).is_finite(),
|
||||
"Quaternion with two components infinite should not be finite.");
|
||||
CHECK_FALSE_MESSAGE(
|
||||
Quaternion(0, x, 2, x).is_finite(),
|
||||
"Quaternion with two components infinite should not be finite.");
|
||||
CHECK_FALSE_MESSAGE(
|
||||
Quaternion(0, 1, x, x).is_finite(),
|
||||
"Quaternion with two components infinite should not be finite.");
|
||||
|
||||
CHECK_FALSE_MESSAGE(
|
||||
Quaternion(0, x, x, x).is_finite(),
|
||||
"Quaternion with three components infinite should not be finite.");
|
||||
CHECK_FALSE_MESSAGE(
|
||||
Quaternion(x, 1, x, x).is_finite(),
|
||||
"Quaternion with three components infinite should not be finite.");
|
||||
CHECK_FALSE_MESSAGE(
|
||||
Quaternion(x, x, 2, x).is_finite(),
|
||||
"Quaternion with three components infinite should not be finite.");
|
||||
CHECK_FALSE_MESSAGE(
|
||||
Quaternion(x, x, x, 3).is_finite(),
|
||||
"Quaternion with three components infinite should not be finite.");
|
||||
|
||||
CHECK_FALSE_MESSAGE(
|
||||
Quaternion(x, x, x, x).is_finite(),
|
||||
"Quaternion with four components infinite should not be finite.");
|
||||
}
|
||||
|
||||
} // namespace TestQuaternion
|
||||
272
tests/core/math/test_random_number_generator.h
Normal file
@@ -0,0 +1,272 @@
|
||||
/**************************************************************************/
|
||||
/* test_random_number_generator.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/math/random_number_generator.h"
|
||||
#include "tests/test_macros.h"
|
||||
|
||||
namespace TestRandomNumberGenerator {
|
||||
|
||||
TEST_CASE("[RandomNumberGenerator] Float") {
|
||||
Ref<RandomNumberGenerator> rng = memnew(RandomNumberGenerator);
|
||||
rng->set_seed(0);
|
||||
|
||||
INFO("Should give float between 0.0 and 1.0.");
|
||||
for (int i = 0; i < 1000; i++) {
|
||||
real_t n = rng->randf();
|
||||
CHECK(n >= 0.0);
|
||||
CHECK(n <= 1.0);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("[RandomNumberGenerator] Integer range via modulo") {
|
||||
Ref<RandomNumberGenerator> rng = memnew(RandomNumberGenerator);
|
||||
rng->set_seed(0);
|
||||
|
||||
INFO("Should give integer between 0 and 100.");
|
||||
for (int i = 0; i < 1000; i++) {
|
||||
uint32_t n = rng->randi() % 100;
|
||||
CHECK(n >= 0);
|
||||
CHECK(n <= 100);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_MAY_FAIL("[RandomNumberGenerator] Integer 32 bit") {
|
||||
Ref<RandomNumberGenerator> rng = memnew(RandomNumberGenerator);
|
||||
rng->set_seed(0); // Change the seed if this fails.
|
||||
|
||||
bool higher = false;
|
||||
int i;
|
||||
for (i = 0; i < 1000; i++) {
|
||||
uint32_t n = rng->randi();
|
||||
if (n > 0x0fff'ffff) {
|
||||
higher = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
INFO("Current seed: ", rng->get_seed());
|
||||
INFO("Current iteration: ", i);
|
||||
CHECK_MESSAGE(higher, "Given current seed, this should give an integer higher than 0x0fff'ffff at least once.");
|
||||
}
|
||||
|
||||
TEST_CASE("[RandomNumberGenerator] Float and integer range") {
|
||||
Ref<RandomNumberGenerator> rng = memnew(RandomNumberGenerator);
|
||||
rng->set_seed(0);
|
||||
uint64_t initial_state = rng->get_state();
|
||||
uint32_t initial_seed = rng->get_seed();
|
||||
|
||||
INFO("Should give float between -100.0 and 100.0, base test.");
|
||||
for (int i = 0; i < 1000; i++) {
|
||||
real_t n0 = rng->randf_range(-100.0, 100.0);
|
||||
CHECK(n0 >= -100);
|
||||
CHECK(n0 <= 100);
|
||||
}
|
||||
|
||||
rng->randomize();
|
||||
INFO("Should give float between -75.0 and 75.0.");
|
||||
INFO("Shouldn't be affected by randomize.");
|
||||
for (int i = 0; i < 1000; i++) {
|
||||
real_t n1 = rng->randf_range(-75.0, 75.0);
|
||||
CHECK(n1 >= -75);
|
||||
CHECK(n1 <= 75);
|
||||
}
|
||||
|
||||
rng->set_state(initial_state);
|
||||
INFO("Should give integer between -50 and 50.");
|
||||
INFO("Shouldn't be affected by set_state.");
|
||||
for (int i = 0; i < 1000; i++) {
|
||||
real_t n2 = rng->randi_range(-50, 50);
|
||||
CHECK(n2 >= -50);
|
||||
CHECK(n2 <= 50);
|
||||
}
|
||||
|
||||
rng->set_seed(initial_seed);
|
||||
INFO("Should give integer between -25 and 25.");
|
||||
INFO("Shouldn't be affected by set_seed.");
|
||||
for (int i = 0; i < 1000; i++) {
|
||||
int32_t n3 = rng->randi_range(-25, 25);
|
||||
CHECK(n3 >= -25);
|
||||
CHECK(n3 <= 25);
|
||||
}
|
||||
|
||||
rng->randf();
|
||||
rng->randf();
|
||||
|
||||
INFO("Should give float between -10.0 and 10.0.");
|
||||
INFO("Shouldn't be affected after generating new numbers.");
|
||||
for (int i = 0; i < 1000; i++) {
|
||||
real_t n4 = rng->randf_range(-10.0, 10.0);
|
||||
CHECK(n4 >= -10);
|
||||
CHECK(n4 <= 10);
|
||||
}
|
||||
|
||||
rng->randi();
|
||||
rng->randi();
|
||||
|
||||
INFO("Should give integer between -5 and 5.");
|
||||
INFO("Shouldn't be affected after generating new numbers.");
|
||||
for (int i = 0; i < 1000; i++) {
|
||||
real_t n5 = rng->randf_range(-5, 5);
|
||||
CHECK(n5 >= -5);
|
||||
CHECK(n5 <= 5);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_MAY_FAIL("[RandomNumberGenerator] Normal distribution") {
|
||||
Ref<RandomNumberGenerator> rng = memnew(RandomNumberGenerator);
|
||||
rng->set_seed(1); // Change the seed if this fails.
|
||||
INFO("Should give a number between -5 to 5 (5 std deviations away; above 99.7% chance it will be in this range).");
|
||||
INFO("Standard randfn function call.");
|
||||
for (int i = 0; i < 100; i++) {
|
||||
real_t n = rng->randfn();
|
||||
CHECK(n >= -5);
|
||||
CHECK(n <= 5);
|
||||
}
|
||||
|
||||
INFO("Should give number between -5 to 5 after multiple randi/randf calls.");
|
||||
INFO("5 std deviations away; above 99.7% chance it will be in this range.");
|
||||
rng->randf();
|
||||
rng->randi();
|
||||
for (int i = 0; i < 100; i++) {
|
||||
real_t n = rng->randfn();
|
||||
CHECK(n >= -5);
|
||||
CHECK(n <= 5);
|
||||
}
|
||||
|
||||
INFO("Checks if user defined mean and deviation work properly.");
|
||||
INFO("5 std deviations away; above 99.7% chance it will be in this range.");
|
||||
for (int i = 0; i < 100; i++) {
|
||||
real_t n = rng->randfn(5, 10);
|
||||
CHECK(n >= -45);
|
||||
CHECK(n <= 55);
|
||||
}
|
||||
|
||||
INFO("Checks if randfn works with changed seeds.");
|
||||
INFO("5 std deviations away; above 99.7% chance it will be in this range.");
|
||||
rng->randomize();
|
||||
for (int i = 0; i < 100; i++) {
|
||||
real_t n = rng->randfn(3, 3);
|
||||
CHECK(n >= -12);
|
||||
CHECK(n <= 18);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("[RandomNumberGenerator] Zero for first number immediately after seeding") {
|
||||
Ref<RandomNumberGenerator> rng = memnew(RandomNumberGenerator);
|
||||
rng->set_seed(0);
|
||||
uint32_t n1 = rng->randi();
|
||||
uint32_t n2 = rng->randi();
|
||||
INFO("Initial random values: ", n1, " ", n2);
|
||||
CHECK(n1 != 0);
|
||||
|
||||
rng->set_seed(1);
|
||||
uint32_t n3 = rng->randi();
|
||||
uint32_t n4 = rng->randi();
|
||||
INFO("Values after changing the seed: ", n3, " ", n4);
|
||||
CHECK(n3 != 0);
|
||||
}
|
||||
|
||||
TEST_CASE("[RandomNumberGenerator] Restore state") {
|
||||
Ref<RandomNumberGenerator> rng = memnew(RandomNumberGenerator);
|
||||
rng->randomize();
|
||||
uint64_t last_seed = rng->get_seed();
|
||||
INFO("Current seed: ", last_seed);
|
||||
|
||||
rng->randi();
|
||||
rng->randi();
|
||||
|
||||
CHECK_MESSAGE(rng->get_seed() == last_seed,
|
||||
"The seed should remain the same after generating some numbers");
|
||||
|
||||
uint64_t saved_state = rng->get_state();
|
||||
INFO("Current state: ", saved_state);
|
||||
|
||||
real_t f1_before = rng->randf();
|
||||
real_t f2_before = rng->randf();
|
||||
INFO("This seed produces: ", f1_before, " ", f2_before);
|
||||
|
||||
// Restore now.
|
||||
rng->set_state(saved_state);
|
||||
|
||||
real_t f1_after = rng->randf();
|
||||
real_t f2_after = rng->randf();
|
||||
INFO("Resetting the state produces: ", f1_after, " ", f2_after);
|
||||
|
||||
String msg = "Should restore the sequence of numbers after resetting the state";
|
||||
CHECK_MESSAGE(f1_before == f1_after, msg);
|
||||
CHECK_MESSAGE(f2_before == f2_after, msg);
|
||||
}
|
||||
|
||||
TEST_CASE("[RandomNumberGenerator] Restore from seed") {
|
||||
Ref<RandomNumberGenerator> rng = memnew(RandomNumberGenerator);
|
||||
rng->set_seed(0);
|
||||
INFO("Current seed: ", rng->get_seed());
|
||||
uint32_t s0_1_before = rng->randi();
|
||||
uint32_t s0_2_before = rng->randi();
|
||||
INFO("This seed produces: ", s0_1_before, " ", s0_2_before);
|
||||
|
||||
rng->set_seed(9000);
|
||||
INFO("Current seed: ", rng->get_seed());
|
||||
uint32_t s9000_1 = rng->randi();
|
||||
uint32_t s9000_2 = rng->randi();
|
||||
INFO("This seed produces: ", s9000_1, " ", s9000_2);
|
||||
|
||||
rng->set_seed(0);
|
||||
INFO("Current seed: ", rng->get_seed());
|
||||
uint32_t s0_1_after = rng->randi();
|
||||
uint32_t s0_2_after = rng->randi();
|
||||
INFO("This seed produces: ", s0_1_after, " ", s0_2_after);
|
||||
|
||||
String msg = "Should restore the sequence of numbers after resetting the seed";
|
||||
CHECK_MESSAGE(s0_1_before == s0_1_after, msg);
|
||||
CHECK_MESSAGE(s0_2_before == s0_2_after, msg);
|
||||
}
|
||||
|
||||
TEST_CASE_MAY_FAIL("[RandomNumberGenerator] randi_range bias check") {
|
||||
int zeros = 0;
|
||||
int ones = 0;
|
||||
Ref<RandomNumberGenerator> rng = memnew(RandomNumberGenerator);
|
||||
for (int i = 0; i < 10000; i++) {
|
||||
int val = rng->randi_range(0, 1);
|
||||
val == 0 ? zeros++ : ones++;
|
||||
}
|
||||
CHECK_MESSAGE(std::abs(zeros * 1.0 / ones - 1.0) < 0.1, "The ratio of zeros to ones should be nearly 1");
|
||||
|
||||
int vals[10] = { 0 };
|
||||
for (int i = 0; i < 1000000; i++) {
|
||||
vals[rng->randi_range(0, 9)]++;
|
||||
}
|
||||
|
||||
for (int i = 0; i < 10; i++) {
|
||||
CHECK_MESSAGE(std::abs(vals[i] / 1000000.0 - 0.1) < 0.01, "Each element should appear roughly 10% of the time");
|
||||
}
|
||||
}
|
||||
} // namespace TestRandomNumberGenerator
|
||||
345
tests/core/math/test_rect2.h
Normal file
@@ -0,0 +1,345 @@
|
||||
/**************************************************************************/
|
||||
/* test_rect2.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/math/rect2.h"
|
||||
#include "core/math/rect2i.h"
|
||||
|
||||
#include "thirdparty/doctest/doctest.h"
|
||||
|
||||
namespace TestRect2 {
|
||||
TEST_CASE("[Rect2] Constructor methods") {
|
||||
constexpr Rect2 rect = Rect2(0, 100, 1280, 720);
|
||||
constexpr Rect2 rect_vector = Rect2(Vector2(0, 100), Vector2(1280, 720));
|
||||
constexpr Rect2 rect_copy_rect = Rect2(rect);
|
||||
const Rect2 rect_copy_recti = Rect2(Rect2i(0, 100, 1280, 720));
|
||||
|
||||
static_assert(
|
||||
rect == rect_vector,
|
||||
"Rect2s created with the same dimensions but by different methods should be equal.");
|
||||
static_assert(
|
||||
rect == rect_copy_rect,
|
||||
"Rect2s created with the same dimensions but by different methods should be equal.");
|
||||
CHECK_MESSAGE(
|
||||
rect == rect_copy_recti,
|
||||
"Rect2s created with the same dimensions but by different methods should be equal.");
|
||||
}
|
||||
|
||||
TEST_CASE("[Rect2] String conversion") {
|
||||
// Note: This also depends on the Vector2 string representation.
|
||||
CHECK_MESSAGE(
|
||||
String(Rect2(0, 100, 1280, 720)) == "[P: (0.0, 100.0), S: (1280.0, 720.0)]",
|
||||
"The string representation should match the expected value.");
|
||||
}
|
||||
|
||||
TEST_CASE("[Rect2] Basic getters") {
|
||||
constexpr Rect2 rect = Rect2(0, 100, 1280, 720);
|
||||
CHECK_MESSAGE(
|
||||
rect.get_position().is_equal_approx(Vector2(0, 100)),
|
||||
"get_position() should return the expected value.");
|
||||
CHECK_MESSAGE(
|
||||
rect.get_size().is_equal_approx(Vector2(1280, 720)),
|
||||
"get_size() should return the expected value.");
|
||||
CHECK_MESSAGE(
|
||||
rect.get_end().is_equal_approx(Vector2(1280, 820)),
|
||||
"get_end() should return the expected value.");
|
||||
CHECK_MESSAGE(
|
||||
rect.get_center().is_equal_approx(Vector2(640, 460)),
|
||||
"get_center() should return the expected value.");
|
||||
CHECK_MESSAGE(
|
||||
Rect2(0, 100, 1281, 721).get_center().is_equal_approx(Vector2(640.5, 460.5)),
|
||||
"get_center() should return the expected value.");
|
||||
}
|
||||
|
||||
TEST_CASE("[Rect2] Basic setters") {
|
||||
Rect2 rect = Rect2(0, 100, 1280, 720);
|
||||
rect.set_end(Vector2(4000, 4000));
|
||||
CHECK_MESSAGE(
|
||||
rect.is_equal_approx(Rect2(0, 100, 4000, 3900)),
|
||||
"set_end() should result in the expected Rect2.");
|
||||
|
||||
rect = Rect2(0, 100, 1280, 720);
|
||||
rect.set_position(Vector2(4000, 4000));
|
||||
CHECK_MESSAGE(
|
||||
rect.is_equal_approx(Rect2(4000, 4000, 1280, 720)),
|
||||
"set_position() should result in the expected Rect2.");
|
||||
|
||||
rect = Rect2(0, 100, 1280, 720);
|
||||
rect.set_size(Vector2(4000, 4000));
|
||||
CHECK_MESSAGE(
|
||||
rect.is_equal_approx(Rect2(0, 100, 4000, 4000)),
|
||||
"set_size() should result in the expected Rect2.");
|
||||
}
|
||||
|
||||
TEST_CASE("[Rect2] Area getters") {
|
||||
CHECK_MESSAGE(
|
||||
Rect2(0, 100, 1280, 720).get_area() == doctest::Approx(921'600),
|
||||
"get_area() should return the expected value.");
|
||||
CHECK_MESSAGE(
|
||||
Rect2(0, 100, -1280, -720).get_area() == doctest::Approx(921'600),
|
||||
"get_area() should return the expected value.");
|
||||
CHECK_MESSAGE(
|
||||
Rect2(0, 100, 1280, -720).get_area() == doctest::Approx(-921'600),
|
||||
"get_area() should return the expected value.");
|
||||
CHECK_MESSAGE(
|
||||
Rect2(0, 100, -1280, 720).get_area() == doctest::Approx(-921'600),
|
||||
"get_area() should return the expected value.");
|
||||
CHECK_MESSAGE(
|
||||
Math::is_zero_approx(Rect2(0, 100, 0, 720).get_area()),
|
||||
"get_area() should return the expected value.");
|
||||
|
||||
CHECK_MESSAGE(
|
||||
Rect2(0, 100, 1280, 720).has_area(),
|
||||
"has_area() should return the expected value on Rect2 with an area.");
|
||||
CHECK_MESSAGE(
|
||||
!Rect2(0, 100, 0, 500).has_area(),
|
||||
"has_area() should return the expected value on Rect2 with no area.");
|
||||
CHECK_MESSAGE(
|
||||
!Rect2(0, 100, 500, 0).has_area(),
|
||||
"has_area() should return the expected value on Rect2 with no area.");
|
||||
CHECK_MESSAGE(
|
||||
!Rect2(0, 100, 0, 0).has_area(),
|
||||
"has_area() should return the expected value on Rect2 with no area.");
|
||||
}
|
||||
|
||||
TEST_CASE("[Rect2] Absolute coordinates") {
|
||||
CHECK_MESSAGE(
|
||||
Rect2(0, 100, 1280, 720).abs().is_equal_approx(Rect2(0, 100, 1280, 720)),
|
||||
"abs() should return the expected Rect2.");
|
||||
CHECK_MESSAGE(
|
||||
Rect2(0, -100, 1280, 720).abs().is_equal_approx(Rect2(0, -100, 1280, 720)),
|
||||
"abs() should return the expected Rect2.");
|
||||
CHECK_MESSAGE(
|
||||
Rect2(0, -100, -1280, -720).abs().is_equal_approx(Rect2(-1280, -820, 1280, 720)),
|
||||
"abs() should return the expected Rect2.");
|
||||
CHECK_MESSAGE(
|
||||
Rect2(0, 100, -1280, 720).abs().is_equal_approx(Rect2(-1280, 100, 1280, 720)),
|
||||
"abs() should return the expected Rect2.");
|
||||
}
|
||||
|
||||
TEST_CASE("[Rect2] Intersection") {
|
||||
CHECK_MESSAGE(
|
||||
Rect2(0, 100, 1280, 720).intersection(Rect2(0, 300, 100, 100)).is_equal_approx(Rect2(0, 300, 100, 100)),
|
||||
"intersection() with fully enclosed Rect2 should return the expected result.");
|
||||
// The resulting Rect2 is 100 pixels high because the first Rect2 is vertically offset by 100 pixels.
|
||||
CHECK_MESSAGE(
|
||||
Rect2(0, 100, 1280, 720).intersection(Rect2(1200, 700, 100, 100)).is_equal_approx(Rect2(1200, 700, 80, 100)),
|
||||
"intersection() with partially enclosed Rect2 should return the expected result.");
|
||||
CHECK_MESSAGE(
|
||||
Rect2(0, 100, 1280, 720).intersection(Rect2(-4000, -4000, 100, 100)).is_equal_approx(Rect2()),
|
||||
"intersection() with non-enclosed Rect2 should return the expected result.");
|
||||
}
|
||||
|
||||
TEST_CASE("[Rect2] Enclosing") {
|
||||
CHECK_MESSAGE(
|
||||
Rect2(0, 100, 1280, 720).encloses(Rect2(0, 300, 100, 100)),
|
||||
"encloses() with fully contained Rect2 should return the expected result.");
|
||||
CHECK_MESSAGE(
|
||||
!Rect2(0, 100, 1280, 720).encloses(Rect2(1200, 700, 100, 100)),
|
||||
"encloses() with partially contained Rect2 should return the expected result.");
|
||||
CHECK_MESSAGE(
|
||||
!Rect2(0, 100, 1280, 720).encloses(Rect2(-4000, -4000, 100, 100)),
|
||||
"encloses() with non-contained Rect2 should return the expected result.");
|
||||
}
|
||||
|
||||
TEST_CASE("[Rect2] Expanding") {
|
||||
CHECK_MESSAGE(
|
||||
Rect2(0, 100, 1280, 720).expand(Vector2(500, 600)).is_equal_approx(Rect2(0, 100, 1280, 720)),
|
||||
"expand() with contained Vector2 should return the expected result.");
|
||||
CHECK_MESSAGE(
|
||||
Rect2(0, 100, 1280, 720).expand(Vector2(0, 0)).is_equal_approx(Rect2(0, 0, 1280, 820)),
|
||||
"expand() with non-contained Vector2 should return the expected result.");
|
||||
}
|
||||
|
||||
TEST_CASE("[Rect2] Get support") {
|
||||
constexpr Rect2 rect = Rect2(Vector2(-1.5, 2), Vector2(4, 5));
|
||||
CHECK_MESSAGE(
|
||||
rect.get_support(Vector2(1, 0)) == Vector2(2.5, 2),
|
||||
"get_support() should return the expected value.");
|
||||
CHECK_MESSAGE(
|
||||
rect.get_support(Vector2(0.5, 1)) == Vector2(2.5, 7),
|
||||
"get_support() should return the expected value.");
|
||||
CHECK_MESSAGE(
|
||||
rect.get_support(Vector2(0.5, 1)) == Vector2(2.5, 7),
|
||||
"get_support() should return the expected value.");
|
||||
CHECK_MESSAGE(
|
||||
rect.get_support(Vector2(0, -1)) == Vector2(-1.5, 2),
|
||||
"get_support() should return the expected value.");
|
||||
CHECK_MESSAGE(
|
||||
rect.get_support(Vector2(0, -0.1)) == Vector2(-1.5, 2),
|
||||
"get_support() should return the expected value.");
|
||||
CHECK_MESSAGE(
|
||||
rect.get_support(Vector2()) == Vector2(-1.5, 2),
|
||||
"get_support() should return the Rect2 position when given a zero vector.");
|
||||
}
|
||||
|
||||
TEST_CASE("[Rect2] Growing") {
|
||||
CHECK_MESSAGE(
|
||||
Rect2(0, 100, 1280, 720).grow(100).is_equal_approx(Rect2(-100, 0, 1480, 920)),
|
||||
"grow() with positive value should return the expected Rect2.");
|
||||
CHECK_MESSAGE(
|
||||
Rect2(0, 100, 1280, 720).grow(-100).is_equal_approx(Rect2(100, 200, 1080, 520)),
|
||||
"grow() with negative value should return the expected Rect2.");
|
||||
CHECK_MESSAGE(
|
||||
Rect2(0, 100, 1280, 720).grow(-4000).is_equal_approx(Rect2(4000, 4100, -6720, -7280)),
|
||||
"grow() with large negative value should return the expected Rect2.");
|
||||
|
||||
CHECK_MESSAGE(
|
||||
Rect2(0, 100, 1280, 720).grow_individual(100, 200, 300, 400).is_equal_approx(Rect2(-100, -100, 1680, 1320)),
|
||||
"grow_individual() with positive values should return the expected Rect2.");
|
||||
CHECK_MESSAGE(
|
||||
Rect2(0, 100, 1280, 720).grow_individual(-100, 200, 300, -400).is_equal_approx(Rect2(100, -100, 1480, 520)),
|
||||
"grow_individual() with positive and negative values should return the expected Rect2.");
|
||||
|
||||
CHECK_MESSAGE(
|
||||
Rect2(0, 100, 1280, 720).grow_side(SIDE_TOP, 500).is_equal_approx(Rect2(0, -400, 1280, 1220)),
|
||||
"grow_side() with positive value should return the expected Rect2.");
|
||||
CHECK_MESSAGE(
|
||||
Rect2(0, 100, 1280, 720).grow_side(SIDE_TOP, -500).is_equal_approx(Rect2(0, 600, 1280, 220)),
|
||||
"grow_side() with negative value should return the expected Rect2.");
|
||||
}
|
||||
|
||||
TEST_CASE("[Rect2] Has point") {
|
||||
Rect2 rect = Rect2(0, 100, 1280, 720);
|
||||
CHECK_MESSAGE(
|
||||
rect.has_point(Vector2(500, 600)),
|
||||
"has_point() with contained Vector2 should return the expected result.");
|
||||
CHECK_MESSAGE(
|
||||
!rect.has_point(Vector2(0, 0)),
|
||||
"has_point() with non-contained Vector2 should return the expected result.");
|
||||
|
||||
CHECK_MESSAGE(
|
||||
rect.has_point(rect.position),
|
||||
"has_point() with positive size should include `position`.");
|
||||
CHECK_MESSAGE(
|
||||
rect.has_point(rect.position + Vector2(1, 1)),
|
||||
"has_point() with positive size should include `position + (1, 1)`.");
|
||||
CHECK_MESSAGE(
|
||||
!rect.has_point(rect.position + Vector2(1, -1)),
|
||||
"has_point() with positive size should not include `position + (1, -1)`.");
|
||||
CHECK_MESSAGE(
|
||||
!rect.has_point(rect.position + rect.size),
|
||||
"has_point() with positive size should not include `position + size`.");
|
||||
CHECK_MESSAGE(
|
||||
!rect.has_point(rect.position + rect.size + Vector2(1, 1)),
|
||||
"has_point() with positive size should not include `position + size + (1, 1)`.");
|
||||
CHECK_MESSAGE(
|
||||
rect.has_point(rect.position + rect.size + Vector2(-1, -1)),
|
||||
"has_point() with positive size should include `position + size + (-1, -1)`.");
|
||||
CHECK_MESSAGE(
|
||||
!rect.has_point(rect.position + rect.size + Vector2(-1, 1)),
|
||||
"has_point() with positive size should not include `position + size + (-1, 1)`.");
|
||||
|
||||
CHECK_MESSAGE(
|
||||
rect.has_point(rect.position + Vector2(0, 10)),
|
||||
"has_point() with point located on left edge should return true.");
|
||||
CHECK_MESSAGE(
|
||||
!rect.has_point(rect.position + Vector2(rect.size.x, 10)),
|
||||
"has_point() with point located on right edge should return false.");
|
||||
CHECK_MESSAGE(
|
||||
rect.has_point(rect.position + Vector2(10, 0)),
|
||||
"has_point() with point located on top edge should return true.");
|
||||
CHECK_MESSAGE(
|
||||
!rect.has_point(rect.position + Vector2(10, rect.size.y)),
|
||||
"has_point() with point located on bottom edge should return false.");
|
||||
|
||||
/*
|
||||
// FIXME: Disabled for now until GH-37617 is fixed one way or another.
|
||||
// More tests should then be written like for the positive size case.
|
||||
rect = Rect2(0, 100, -1280, -720);
|
||||
CHECK_MESSAGE(
|
||||
rect.has_point(rect.position),
|
||||
"has_point() with negative size should include `position`.");
|
||||
CHECK_MESSAGE(
|
||||
!rect.has_point(rect.position + rect.size),
|
||||
"has_point() with negative size should not include `position + size`.");
|
||||
*/
|
||||
|
||||
rect = Rect2(-4000, -200, 1280, 720);
|
||||
CHECK_MESSAGE(
|
||||
rect.has_point(rect.position + Vector2(0, 10)),
|
||||
"has_point() with negative position and point located on left edge should return true.");
|
||||
CHECK_MESSAGE(
|
||||
!rect.has_point(rect.position + Vector2(rect.size.x, 10)),
|
||||
"has_point() with negative position and point located on right edge should return false.");
|
||||
CHECK_MESSAGE(
|
||||
rect.has_point(rect.position + Vector2(10, 0)),
|
||||
"has_point() with negative position and point located on top edge should return true.");
|
||||
CHECK_MESSAGE(
|
||||
!rect.has_point(rect.position + Vector2(10, rect.size.y)),
|
||||
"has_point() with negative position and point located on bottom edge should return false.");
|
||||
}
|
||||
|
||||
TEST_CASE("[Rect2] Intersection") {
|
||||
CHECK_MESSAGE(
|
||||
Rect2(0, 100, 1280, 720).intersects(Rect2(0, 300, 100, 100)),
|
||||
"intersects() with fully enclosed Rect2 should return the expected result.");
|
||||
CHECK_MESSAGE(
|
||||
Rect2(0, 100, 1280, 720).intersects(Rect2(1200, 700, 100, 100)),
|
||||
"intersects() with partially enclosed Rect2 should return the expected result.");
|
||||
CHECK_MESSAGE(
|
||||
!Rect2(0, 100, 1280, 720).intersects(Rect2(-4000, -4000, 100, 100)),
|
||||
"intersects() with non-enclosed Rect2 should return the expected result.");
|
||||
}
|
||||
|
||||
TEST_CASE("[Rect2] Merging") {
|
||||
CHECK_MESSAGE(
|
||||
Rect2(0, 100, 1280, 720).merge(Rect2(0, 300, 100, 100)).is_equal_approx(Rect2(0, 100, 1280, 720)),
|
||||
"merge() with fully enclosed Rect2 should return the expected result.");
|
||||
CHECK_MESSAGE(
|
||||
Rect2(0, 100, 1280, 720).merge(Rect2(1200, 700, 100, 100)).is_equal_approx(Rect2(0, 100, 1300, 720)),
|
||||
"merge() with partially enclosed Rect2 should return the expected result.");
|
||||
CHECK_MESSAGE(
|
||||
Rect2(0, 100, 1280, 720).merge(Rect2(-4000, -4000, 100, 100)).is_equal_approx(Rect2(-4000, -4000, 5280, 4820)),
|
||||
"merge() with non-enclosed Rect2 should return the expected result.");
|
||||
}
|
||||
|
||||
TEST_CASE("[Rect2] Finite number checks") {
|
||||
constexpr Vector2 x(0, 1);
|
||||
constexpr Vector2 infinite(Math::NaN, Math::NaN);
|
||||
|
||||
CHECK_MESSAGE(
|
||||
Rect2(x, x).is_finite(),
|
||||
"Rect2 with all components finite should be finite");
|
||||
|
||||
CHECK_FALSE_MESSAGE(
|
||||
Rect2(infinite, x).is_finite(),
|
||||
"Rect2 with one component infinite should not be finite.");
|
||||
CHECK_FALSE_MESSAGE(
|
||||
Rect2(x, infinite).is_finite(),
|
||||
"Rect2 with one component infinite should not be finite.");
|
||||
|
||||
CHECK_FALSE_MESSAGE(
|
||||
Rect2(infinite, infinite).is_finite(),
|
||||
"Rect2 with two components infinite should not be finite.");
|
||||
}
|
||||
|
||||
} // namespace TestRect2
|
||||
308
tests/core/math/test_rect2i.h
Normal file
@@ -0,0 +1,308 @@
|
||||
/**************************************************************************/
|
||||
/* test_rect2i.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/math/rect2.h"
|
||||
#include "core/math/rect2i.h"
|
||||
|
||||
#include "thirdparty/doctest/doctest.h"
|
||||
|
||||
namespace TestRect2i {
|
||||
TEST_CASE("[Rect2i] Constructor methods") {
|
||||
constexpr Rect2i recti = Rect2i(0, 100, 1280, 720);
|
||||
constexpr Rect2i recti_vector = Rect2i(Vector2i(0, 100), Vector2i(1280, 720));
|
||||
constexpr Rect2i recti_copy_recti = Rect2i(recti);
|
||||
const Rect2i recti_copy_rect = Rect2i(Rect2(0, 100, 1280, 720));
|
||||
|
||||
static_assert(
|
||||
recti == recti_vector,
|
||||
"Rect2is created with the same dimensions but by different methods should be equal.");
|
||||
static_assert(
|
||||
recti == recti_copy_recti,
|
||||
"Rect2is created with the same dimensions but by different methods should be equal.");
|
||||
CHECK_MESSAGE(
|
||||
recti == recti_copy_rect,
|
||||
"Rect2is created with the same dimensions but by different methods should be equal.");
|
||||
}
|
||||
|
||||
TEST_CASE("[Rect2i] String conversion") {
|
||||
// Note: This also depends on the Vector2 string representation.
|
||||
CHECK_MESSAGE(
|
||||
String(Rect2i(0, 100, 1280, 720)) == "[P: (0, 100), S: (1280, 720)]",
|
||||
"The string representation should match the expected value.");
|
||||
}
|
||||
|
||||
TEST_CASE("[Rect2i] Basic getters") {
|
||||
constexpr Rect2i rect = Rect2i(0, 100, 1280, 720);
|
||||
CHECK_MESSAGE(
|
||||
rect.get_position() == Vector2i(0, 100),
|
||||
"get_position() should return the expected value.");
|
||||
CHECK_MESSAGE(
|
||||
rect.get_size() == Vector2i(1280, 720),
|
||||
"get_size() should return the expected value.");
|
||||
CHECK_MESSAGE(
|
||||
rect.get_end() == Vector2i(1280, 820),
|
||||
"get_end() should return the expected value.");
|
||||
CHECK_MESSAGE(
|
||||
rect.get_center() == Vector2i(640, 460),
|
||||
"get_center() should return the expected value.");
|
||||
CHECK_MESSAGE(
|
||||
Rect2i(0, 100, 1281, 721).get_center() == Vector2i(640, 460),
|
||||
"get_center() should return the expected value.");
|
||||
}
|
||||
|
||||
TEST_CASE("[Rect2i] Basic setters") {
|
||||
Rect2i rect = Rect2i(0, 100, 1280, 720);
|
||||
rect.set_end(Vector2i(4000, 4000));
|
||||
CHECK_MESSAGE(
|
||||
rect == Rect2i(0, 100, 4000, 3900),
|
||||
"set_end() should result in the expected Rect2i.");
|
||||
|
||||
rect = Rect2i(0, 100, 1280, 720);
|
||||
rect.set_position(Vector2i(4000, 4000));
|
||||
CHECK_MESSAGE(
|
||||
rect == Rect2i(4000, 4000, 1280, 720),
|
||||
"set_position() should result in the expected Rect2i.");
|
||||
|
||||
rect = Rect2i(0, 100, 1280, 720);
|
||||
rect.set_size(Vector2i(4000, 4000));
|
||||
CHECK_MESSAGE(
|
||||
rect == Rect2i(0, 100, 4000, 4000),
|
||||
"set_size() should result in the expected Rect2i.");
|
||||
}
|
||||
|
||||
TEST_CASE("[Rect2i] Area getters") {
|
||||
CHECK_MESSAGE(
|
||||
Rect2i(0, 100, 1280, 720).get_area() == 921'600,
|
||||
"get_area() should return the expected value.");
|
||||
CHECK_MESSAGE(
|
||||
Rect2i(0, 100, -1280, -720).get_area() == 921'600,
|
||||
"get_area() should return the expected value.");
|
||||
CHECK_MESSAGE(
|
||||
Rect2i(0, 100, 1280, -720).get_area() == -921'600,
|
||||
"get_area() should return the expected value.");
|
||||
CHECK_MESSAGE(
|
||||
Rect2i(0, 100, -1280, 720).get_area() == -921'600,
|
||||
"get_area() should return the expected value.");
|
||||
CHECK_MESSAGE(
|
||||
Rect2i(0, 100, 0, 720).get_area() == 0,
|
||||
"get_area() should return the expected value.");
|
||||
|
||||
CHECK_MESSAGE(
|
||||
Rect2i(0, 100, 1280, 720).has_area(),
|
||||
"has_area() should return the expected value on Rect2i with an area.");
|
||||
CHECK_MESSAGE(
|
||||
!Rect2i(0, 100, 0, 500).has_area(),
|
||||
"has_area() should return the expected value on Rect2i with no area.");
|
||||
CHECK_MESSAGE(
|
||||
!Rect2i(0, 100, 500, 0).has_area(),
|
||||
"has_area() should return the expected value on Rect2i with no area.");
|
||||
CHECK_MESSAGE(
|
||||
!Rect2i(0, 100, 0, 0).has_area(),
|
||||
"has_area() should return the expected value on Rect2i with no area.");
|
||||
}
|
||||
|
||||
TEST_CASE("[Rect2i] Absolute coordinates") {
|
||||
CHECK_MESSAGE(
|
||||
Rect2i(0, 100, 1280, 720).abs() == Rect2i(0, 100, 1280, 720),
|
||||
"abs() should return the expected Rect2i.");
|
||||
CHECK_MESSAGE(
|
||||
Rect2i(0, -100, 1280, 720).abs() == Rect2i(0, -100, 1280, 720),
|
||||
"abs() should return the expected Rect2i.");
|
||||
CHECK_MESSAGE(
|
||||
Rect2i(0, -100, -1280, -720).abs() == Rect2i(-1280, -820, 1280, 720),
|
||||
"abs() should return the expected Rect2i.");
|
||||
CHECK_MESSAGE(
|
||||
Rect2i(0, 100, -1280, 720).abs() == Rect2i(-1280, 100, 1280, 720),
|
||||
"abs() should return the expected Rect2i.");
|
||||
}
|
||||
|
||||
TEST_CASE("[Rect2i] Intersection") {
|
||||
CHECK_MESSAGE(
|
||||
Rect2i(0, 100, 1280, 720).intersection(Rect2i(0, 300, 100, 100)) == Rect2i(0, 300, 100, 100),
|
||||
"intersection() with fully enclosed Rect2i should return the expected result.");
|
||||
// The resulting Rect2i is 100 pixels high because the first Rect2i is vertically offset by 100 pixels.
|
||||
CHECK_MESSAGE(
|
||||
Rect2i(0, 100, 1280, 720).intersection(Rect2i(1200, 700, 100, 100)) == Rect2i(1200, 700, 80, 100),
|
||||
"intersection() with partially enclosed Rect2i should return the expected result.");
|
||||
CHECK_MESSAGE(
|
||||
Rect2i(0, 100, 1280, 720).intersection(Rect2i(-4000, -4000, 100, 100)) == Rect2i(),
|
||||
"intersection() with non-enclosed Rect2i should return the expected result.");
|
||||
}
|
||||
|
||||
TEST_CASE("[Rect2i] Enclosing") {
|
||||
CHECK_MESSAGE(
|
||||
Rect2i(0, 100, 1280, 720).encloses(Rect2i(0, 300, 100, 100)),
|
||||
"encloses() with fully contained Rect2i should return the expected result.");
|
||||
CHECK_MESSAGE(
|
||||
!Rect2i(0, 100, 1280, 720).encloses(Rect2i(1200, 700, 100, 100)),
|
||||
"encloses() with partially contained Rect2i should return the expected result.");
|
||||
CHECK_MESSAGE(
|
||||
!Rect2i(0, 100, 1280, 720).encloses(Rect2i(-4000, -4000, 100, 100)),
|
||||
"encloses() with non-contained Rect2i should return the expected result.");
|
||||
CHECK_MESSAGE(
|
||||
Rect2i(0, 100, 1280, 720).encloses(Rect2i(0, 100, 1280, 720)),
|
||||
"encloses() with identical Rect2i should return the expected result.");
|
||||
}
|
||||
|
||||
TEST_CASE("[Rect2i] Expanding") {
|
||||
CHECK_MESSAGE(
|
||||
Rect2i(0, 100, 1280, 720).expand(Vector2i(500, 600)) == Rect2i(0, 100, 1280, 720),
|
||||
"expand() with contained Vector2i should return the expected result.");
|
||||
CHECK_MESSAGE(
|
||||
Rect2i(0, 100, 1280, 720).expand(Vector2i(0, 0)) == Rect2i(0, 0, 1280, 820),
|
||||
"expand() with non-contained Vector2i should return the expected result.");
|
||||
}
|
||||
|
||||
TEST_CASE("[Rect2i] Growing") {
|
||||
CHECK_MESSAGE(
|
||||
Rect2i(0, 100, 1280, 720).grow(100) == Rect2i(-100, 0, 1480, 920),
|
||||
"grow() with positive value should return the expected Rect2i.");
|
||||
CHECK_MESSAGE(
|
||||
Rect2i(0, 100, 1280, 720).grow(-100) == Rect2i(100, 200, 1080, 520),
|
||||
"grow() with negative value should return the expected Rect2i.");
|
||||
CHECK_MESSAGE(
|
||||
Rect2i(0, 100, 1280, 720).grow(-4000) == Rect2i(4000, 4100, -6720, -7280),
|
||||
"grow() with large negative value should return the expected Rect2i.");
|
||||
|
||||
CHECK_MESSAGE(
|
||||
Rect2i(0, 100, 1280, 720).grow_individual(100, 200, 300, 400) == Rect2i(-100, -100, 1680, 1320),
|
||||
"grow_individual() with positive values should return the expected Rect2i.");
|
||||
CHECK_MESSAGE(
|
||||
Rect2i(0, 100, 1280, 720).grow_individual(-100, 200, 300, -400) == Rect2i(100, -100, 1480, 520),
|
||||
"grow_individual() with positive and negative values should return the expected Rect2i.");
|
||||
|
||||
CHECK_MESSAGE(
|
||||
Rect2i(0, 100, 1280, 720).grow_side(SIDE_TOP, 500) == Rect2i(0, -400, 1280, 1220),
|
||||
"grow_side() with positive value should return the expected Rect2i.");
|
||||
CHECK_MESSAGE(
|
||||
Rect2i(0, 100, 1280, 720).grow_side(SIDE_TOP, -500) == Rect2i(0, 600, 1280, 220),
|
||||
"grow_side() with negative value should return the expected Rect2i.");
|
||||
}
|
||||
|
||||
TEST_CASE("[Rect2i] Has point") {
|
||||
Rect2i rect = Rect2i(0, 100, 1280, 720);
|
||||
CHECK_MESSAGE(
|
||||
rect.has_point(Vector2i(500, 600)),
|
||||
"has_point() with contained Vector2i should return the expected result.");
|
||||
CHECK_MESSAGE(
|
||||
!rect.has_point(Vector2i(0, 0)),
|
||||
"has_point() with non-contained Vector2i should return the expected result.");
|
||||
|
||||
CHECK_MESSAGE(
|
||||
rect.has_point(rect.position),
|
||||
"has_point() with positive size should include `position`.");
|
||||
CHECK_MESSAGE(
|
||||
rect.has_point(rect.position + Vector2i(1, 1)),
|
||||
"has_point() with positive size should include `position + (1, 1)`.");
|
||||
CHECK_MESSAGE(
|
||||
!rect.has_point(rect.position + Vector2i(1, -1)),
|
||||
"has_point() with positive size should not include `position + (1, -1)`.");
|
||||
CHECK_MESSAGE(
|
||||
!rect.has_point(rect.position + rect.size),
|
||||
"has_point() with positive size should not include `position + size`.");
|
||||
CHECK_MESSAGE(
|
||||
!rect.has_point(rect.position + rect.size + Vector2i(1, 1)),
|
||||
"has_point() with positive size should not include `position + size + (1, 1)`.");
|
||||
CHECK_MESSAGE(
|
||||
rect.has_point(rect.position + rect.size + Vector2i(-1, -1)),
|
||||
"has_point() with positive size should include `position + size + (-1, -1)`.");
|
||||
CHECK_MESSAGE(
|
||||
!rect.has_point(rect.position + rect.size + Vector2i(-1, 1)),
|
||||
"has_point() with positive size should not include `position + size + (-1, 1)`.");
|
||||
|
||||
CHECK_MESSAGE(
|
||||
rect.has_point(rect.position + Vector2i(0, 10)),
|
||||
"has_point() with point located on left edge should return true.");
|
||||
CHECK_MESSAGE(
|
||||
!rect.has_point(rect.position + Vector2i(rect.size.x, 10)),
|
||||
"has_point() with point located on right edge should return false.");
|
||||
CHECK_MESSAGE(
|
||||
rect.has_point(rect.position + Vector2i(10, 0)),
|
||||
"has_point() with point located on top edge should return true.");
|
||||
CHECK_MESSAGE(
|
||||
!rect.has_point(rect.position + Vector2i(10, rect.size.y)),
|
||||
"has_point() with point located on bottom edge should return false.");
|
||||
|
||||
/*
|
||||
// FIXME: Disabled for now until GH-37617 is fixed one way or another.
|
||||
// More tests should then be written like for the positive size case.
|
||||
rect = Rect2i(0, 100, -1280, -720);
|
||||
CHECK_MESSAGE(
|
||||
rect.has_point(rect.position),
|
||||
"has_point() with negative size should include `position`.");
|
||||
CHECK_MESSAGE(
|
||||
!rect.has_point(rect.position + rect.size),
|
||||
"has_point() with negative size should not include `position + size`.");
|
||||
*/
|
||||
|
||||
rect = Rect2i(-4000, -200, 1280, 720);
|
||||
CHECK_MESSAGE(
|
||||
rect.has_point(rect.position + Vector2i(0, 10)),
|
||||
"has_point() with negative position and point located on left edge should return true.");
|
||||
CHECK_MESSAGE(
|
||||
!rect.has_point(rect.position + Vector2i(rect.size.x, 10)),
|
||||
"has_point() with negative position and point located on right edge should return false.");
|
||||
CHECK_MESSAGE(
|
||||
rect.has_point(rect.position + Vector2i(10, 0)),
|
||||
"has_point() with negative position and point located on top edge should return true.");
|
||||
CHECK_MESSAGE(
|
||||
!rect.has_point(rect.position + Vector2i(10, rect.size.y)),
|
||||
"has_point() with negative position and point located on bottom edge should return false.");
|
||||
}
|
||||
|
||||
TEST_CASE("[Rect2i] Intersection") {
|
||||
CHECK_MESSAGE(
|
||||
Rect2i(0, 100, 1280, 720).intersects(Rect2i(0, 300, 100, 100)),
|
||||
"intersects() with fully enclosed Rect2i should return the expected result.");
|
||||
CHECK_MESSAGE(
|
||||
Rect2i(0, 100, 1280, 720).intersects(Rect2i(1200, 700, 100, 100)),
|
||||
"intersects() with partially enclosed Rect2i should return the expected result.");
|
||||
CHECK_MESSAGE(
|
||||
!Rect2i(0, 100, 1280, 720).intersects(Rect2i(-4000, -4000, 100, 100)),
|
||||
"intersects() with non-enclosed Rect2i should return the expected result.");
|
||||
CHECK_MESSAGE(
|
||||
!Rect2i(0, 0, 2, 2).intersects(Rect2i(2, 2, 2, 2)),
|
||||
"intersects() with adjacent Rect2i should return the expected result.");
|
||||
}
|
||||
|
||||
TEST_CASE("[Rect2i] Merging") {
|
||||
CHECK_MESSAGE(
|
||||
Rect2i(0, 100, 1280, 720).merge(Rect2i(0, 300, 100, 100)) == Rect2i(0, 100, 1280, 720),
|
||||
"merge() with fully enclosed Rect2i should return the expected result.");
|
||||
CHECK_MESSAGE(
|
||||
Rect2i(0, 100, 1280, 720).merge(Rect2i(1200, 700, 100, 100)) == Rect2i(0, 100, 1300, 720),
|
||||
"merge() with partially enclosed Rect2i should return the expected result.");
|
||||
CHECK_MESSAGE(
|
||||
Rect2i(0, 100, 1280, 720).merge(Rect2i(-4000, -4000, 100, 100)) == Rect2i(-4000, -4000, 5280, 4820),
|
||||
"merge() with non-enclosed Rect2i should return the expected result.");
|
||||
}
|
||||
} // namespace TestRect2i
|
||||
246
tests/core/math/test_transform_2d.h
Normal file
@@ -0,0 +1,246 @@
|
||||
/**************************************************************************/
|
||||
/* test_transform_2d.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/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] Default constructor") {
|
||||
Transform2D default_constructor = Transform2D();
|
||||
CHECK(default_constructor == Transform2D(Vector2(1, 0), Vector2(0, 1), Vector2(0, 0)));
|
||||
}
|
||||
|
||||
TEST_CASE("[Transform2D] Copy constructor") {
|
||||
Transform2D T = create_dummy_transform();
|
||||
Transform2D copy_constructor = Transform2D(T);
|
||||
CHECK(T == copy_constructor);
|
||||
}
|
||||
|
||||
TEST_CASE("[Transform2D] Constructor from angle and position") {
|
||||
constexpr float ROTATION = Math::PI / 4;
|
||||
constexpr Vector2 TRANSLATION = Vector2(20, -20);
|
||||
|
||||
const Transform2D test = Transform2D(ROTATION, TRANSLATION);
|
||||
const Transform2D expected = Transform2D().rotated(ROTATION).translated(TRANSLATION);
|
||||
CHECK(test == expected);
|
||||
}
|
||||
|
||||
TEST_CASE("[Transform2D] Constructor from angle, scale, skew and position") {
|
||||
constexpr float ROTATION = Math::PI / 2;
|
||||
constexpr Vector2 SCALE = Vector2(2, 0.5);
|
||||
constexpr float SKEW = Math::PI / 4;
|
||||
constexpr Vector2 TRANSLATION = Vector2(30, 0);
|
||||
|
||||
const Transform2D test = Transform2D(ROTATION, SCALE, SKEW, TRANSLATION);
|
||||
Transform2D expected = Transform2D().scaled(SCALE).rotated(ROTATION).translated(TRANSLATION);
|
||||
expected.set_skew(SKEW);
|
||||
|
||||
CHECK(test.is_equal_approx(expected));
|
||||
}
|
||||
|
||||
TEST_CASE("[Transform2D] Constructor from raw values") {
|
||||
constexpr Transform2D test = Transform2D(1, 2, 3, 4, 5, 6);
|
||||
constexpr Transform2D expected = Transform2D(Vector2(1, 2), Vector2(3, 4), Vector2(5, 6));
|
||||
static_assert(test == expected);
|
||||
}
|
||||
|
||||
TEST_CASE("[Transform2D] xform") {
|
||||
constexpr Vector2 v = Vector2(2, 3);
|
||||
constexpr Transform2D T = Transform2D(Vector2(1, 2), Vector2(3, 4), Vector2(5, 6));
|
||||
constexpr Vector2 expected = Vector2(1 * 2 + 3 * 3 + 5 * 1, 2 * 2 + 4 * 3 + 6 * 1);
|
||||
CHECK(T.xform(v) == expected);
|
||||
}
|
||||
|
||||
TEST_CASE("[Transform2D] Basis xform") {
|
||||
constexpr Vector2 v = Vector2(2, 2);
|
||||
constexpr Transform2D T1 = Transform2D(Vector2(1, 2), Vector2(3, 4), Vector2(0, 0));
|
||||
|
||||
// Both versions should be the same when the origin is (0,0).
|
||||
CHECK(T1.basis_xform(v) == T1.xform(v));
|
||||
|
||||
constexpr Transform2D T2 = Transform2D(Vector2(1, 2), Vector2(3, 4), Vector2(5, 6));
|
||||
|
||||
// Each version should be different when the origin is not (0,0).
|
||||
CHECK_FALSE(T2.basis_xform(v) == T2.xform(v));
|
||||
}
|
||||
|
||||
TEST_CASE("[Transform2D] Affine inverse") {
|
||||
const Transform2D orig = create_dummy_transform();
|
||||
const Transform2D affine_inverted = orig.affine_inverse();
|
||||
const Transform2D affine_inverted_again = affine_inverted.affine_inverse();
|
||||
CHECK(affine_inverted_again == orig);
|
||||
}
|
||||
|
||||
TEST_CASE("[Transform2D] Orthonormalized") {
|
||||
const Transform2D T = create_dummy_transform();
|
||||
const Transform2D orthonormalized_T = T.orthonormalized();
|
||||
|
||||
// Check each basis has length 1.
|
||||
CHECK(Math::is_equal_approx(orthonormalized_T[0].length_squared(), 1));
|
||||
CHECK(Math::is_equal_approx(orthonormalized_T[1].length_squared(), 1));
|
||||
|
||||
const Vector2 vx = Vector2(orthonormalized_T[0].x, orthonormalized_T[1].x);
|
||||
const Vector2 vy = Vector2(orthonormalized_T[0].y, orthonormalized_T[1].y);
|
||||
|
||||
// Check the basis are orthogonal.
|
||||
CHECK(Math::is_equal_approx(orthonormalized_T.tdotx(vx), 1));
|
||||
CHECK(Math::is_equal_approx(orthonormalized_T.tdotx(vy), 0));
|
||||
CHECK(Math::is_equal_approx(orthonormalized_T.tdoty(vx), 0));
|
||||
CHECK(Math::is_equal_approx(orthonormalized_T.tdoty(vy), 1));
|
||||
}
|
||||
|
||||
TEST_CASE("[Transform2D] translation") {
|
||||
constexpr 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.
|
||||
const Transform2D orig = create_dummy_transform();
|
||||
const Transform2D T = identity().translated(offset);
|
||||
CHECK(orig.translated(offset) == T * orig);
|
||||
CHECK(orig.translated_local(offset) == orig * T);
|
||||
}
|
||||
|
||||
TEST_CASE("[Transform2D] scaling") {
|
||||
constexpr 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.
|
||||
const Transform2D orig = create_dummy_transform();
|
||||
const Transform2D S = identity().scaled(scaling);
|
||||
CHECK(orig.scaled(scaling) == S * orig);
|
||||
CHECK(orig.scaled_local(scaling) == orig * S);
|
||||
}
|
||||
|
||||
TEST_CASE("[Transform2D] rotation") {
|
||||
constexpr 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.
|
||||
const Transform2D orig = create_dummy_transform();
|
||||
const Transform2D R = identity().rotated(phi);
|
||||
CHECK(orig.rotated(phi) == R * orig);
|
||||
CHECK(orig.rotated_local(phi) == orig * R);
|
||||
}
|
||||
|
||||
TEST_CASE("[Transform2D] Interpolation") {
|
||||
const Transform2D rotate_scale_skew_pos = Transform2D(Math::deg_to_rad(170.0), Vector2(3.6, 8.0), Math::deg_to_rad(20.0), Vector2(2.4, 6.8));
|
||||
const Transform2D rotate_scale_skew_pos_halfway = Transform2D(Math::deg_to_rad(85.0), Vector2(2.3, 4.5), Math::deg_to_rad(10.0), Vector2(1.2, 3.4));
|
||||
Transform2D interpolated = Transform2D().interpolate_with(rotate_scale_skew_pos, 0.5);
|
||||
CHECK(interpolated.get_origin().is_equal_approx(rotate_scale_skew_pos_halfway.get_origin()));
|
||||
CHECK(interpolated.get_rotation() == doctest::Approx(rotate_scale_skew_pos_halfway.get_rotation()));
|
||||
CHECK(interpolated.get_scale().is_equal_approx(rotate_scale_skew_pos_halfway.get_scale()));
|
||||
CHECK(interpolated.get_skew() == doctest::Approx(rotate_scale_skew_pos_halfway.get_skew()));
|
||||
CHECK(interpolated.is_equal_approx(rotate_scale_skew_pos_halfway));
|
||||
interpolated = rotate_scale_skew_pos.interpolate_with(Transform2D(), 0.5);
|
||||
CHECK(interpolated.is_equal_approx(rotate_scale_skew_pos_halfway));
|
||||
}
|
||||
|
||||
TEST_CASE("[Transform2D] Finite number checks") {
|
||||
constexpr Vector2 x = Vector2(0, 1);
|
||||
constexpr Vector2 infinite = Vector2(Math::NaN, Math::NaN);
|
||||
|
||||
CHECK_MESSAGE(
|
||||
Transform2D(x, x, x).is_finite(),
|
||||
"Transform2D with all components finite should be finite");
|
||||
|
||||
CHECK_FALSE_MESSAGE(
|
||||
Transform2D(infinite, x, x).is_finite(),
|
||||
"Transform2D with one component infinite should not be finite.");
|
||||
CHECK_FALSE_MESSAGE(
|
||||
Transform2D(x, infinite, x).is_finite(),
|
||||
"Transform2D with one component infinite should not be finite.");
|
||||
CHECK_FALSE_MESSAGE(
|
||||
Transform2D(x, x, infinite).is_finite(),
|
||||
"Transform2D with one component infinite should not be finite.");
|
||||
|
||||
CHECK_FALSE_MESSAGE(
|
||||
Transform2D(infinite, infinite, x).is_finite(),
|
||||
"Transform2D with two components infinite should not be finite.");
|
||||
CHECK_FALSE_MESSAGE(
|
||||
Transform2D(infinite, x, infinite).is_finite(),
|
||||
"Transform2D with two components infinite should not be finite.");
|
||||
CHECK_FALSE_MESSAGE(
|
||||
Transform2D(x, infinite, infinite).is_finite(),
|
||||
"Transform2D with two components infinite should not be finite.");
|
||||
|
||||
CHECK_FALSE_MESSAGE(
|
||||
Transform2D(infinite, infinite, infinite).is_finite(),
|
||||
"Transform2D with three components infinite should not be finite.");
|
||||
}
|
||||
|
||||
TEST_CASE("[Transform2D] Is conformal checks") {
|
||||
CHECK_MESSAGE(
|
||||
Transform2D().is_conformal(),
|
||||
"Identity Transform2D should be conformal.");
|
||||
|
||||
CHECK_MESSAGE(
|
||||
Transform2D(1.2, Vector2()).is_conformal(),
|
||||
"Transform2D with only rotation should be conformal.");
|
||||
|
||||
CHECK_MESSAGE(
|
||||
Transform2D(Vector2(1, 0), Vector2(0, -1), Vector2()).is_conformal(),
|
||||
"Transform2D with only a flip should be conformal.");
|
||||
|
||||
CHECK_MESSAGE(
|
||||
Transform2D(Vector2(1.2, 0), Vector2(0, 1.2), Vector2()).is_conformal(),
|
||||
"Transform2D with only uniform scale should be conformal.");
|
||||
|
||||
CHECK_MESSAGE(
|
||||
Transform2D(Vector2(1.2, 3.4), Vector2(3.4, -1.2), Vector2()).is_conformal(),
|
||||
"Transform2D with a flip, rotation, and uniform scale should be conformal.");
|
||||
|
||||
CHECK_FALSE_MESSAGE(
|
||||
Transform2D(Vector2(1.2, 0), Vector2(0, 3.4), Vector2()).is_conformal(),
|
||||
"Transform2D with non-uniform scale should not be conformal.");
|
||||
|
||||
CHECK_FALSE_MESSAGE(
|
||||
Transform2D(Vector2(Math::SQRT12, Math::SQRT12), Vector2(0, 1), Vector2()).is_conformal(),
|
||||
"Transform2D with the X axis skewed 45 degrees should not be conformal.");
|
||||
}
|
||||
|
||||
} // namespace TestTransform2D
|
||||
138
tests/core/math/test_transform_3d.h
Normal file
@@ -0,0 +1,138 @@
|
||||
/**************************************************************************/
|
||||
/* test_transform_3d.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#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") {
|
||||
constexpr 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") {
|
||||
constexpr 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") {
|
||||
const Vector3 axis = Vector3(1, 2, 3).normalized();
|
||||
constexpr 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);
|
||||
}
|
||||
|
||||
TEST_CASE("[Transform3D] Finite number checks") {
|
||||
constexpr Vector3 y(0, 1, 2);
|
||||
constexpr Vector3 infinite_vec(Math::NaN, Math::NaN, Math::NaN);
|
||||
constexpr Basis x(y, y, y);
|
||||
constexpr Basis infinite_basis(infinite_vec, infinite_vec, infinite_vec);
|
||||
|
||||
CHECK_MESSAGE(
|
||||
Transform3D(x, y).is_finite(),
|
||||
"Transform3D with all components finite should be finite");
|
||||
|
||||
CHECK_FALSE_MESSAGE(
|
||||
Transform3D(x, infinite_vec).is_finite(),
|
||||
"Transform3D with one component infinite should not be finite.");
|
||||
CHECK_FALSE_MESSAGE(
|
||||
Transform3D(infinite_basis, y).is_finite(),
|
||||
"Transform3D with one component infinite should not be finite.");
|
||||
|
||||
CHECK_FALSE_MESSAGE(
|
||||
Transform3D(infinite_basis, infinite_vec).is_finite(),
|
||||
"Transform3D with two components infinite should not be finite.");
|
||||
}
|
||||
|
||||
TEST_CASE("[Transform3D] Rotate around global origin") {
|
||||
// Start with the default orientation, but not centered on the origin.
|
||||
// Rotating should rotate both our basis and the origin.
|
||||
Transform3D transform = Transform3D();
|
||||
transform.origin = Vector3(0, 0, 1);
|
||||
|
||||
Transform3D expected = Transform3D();
|
||||
expected.origin = Vector3(0, 0, -1);
|
||||
expected.basis[0] = Vector3(-1, 0, 0);
|
||||
expected.basis[2] = Vector3(0, 0, -1);
|
||||
|
||||
const Transform3D rotated_transform = transform.rotated(Vector3(0, 1, 0), Math::PI);
|
||||
CHECK_MESSAGE(rotated_transform.is_equal_approx(expected), "The rotated transform should have a new orientation and basis.");
|
||||
}
|
||||
|
||||
TEST_CASE("[Transform3D] Rotate in-place (local rotation)") {
|
||||
// Start with the default orientation.
|
||||
// Local rotation should not change the origin, only the basis.
|
||||
Transform3D transform = Transform3D();
|
||||
transform.origin = Vector3(1, 2, 3);
|
||||
|
||||
Transform3D expected = Transform3D();
|
||||
expected.origin = Vector3(1, 2, 3);
|
||||
expected.basis[0] = Vector3(-1, 0, 0);
|
||||
expected.basis[2] = Vector3(0, 0, -1);
|
||||
|
||||
const Transform3D rotated_transform = Transform3D(transform.rotated_local(Vector3(0, 1, 0), Math::PI));
|
||||
CHECK_MESSAGE(rotated_transform.is_equal_approx(expected), "The rotated transform should have a new orientation but still be based on the same origin.");
|
||||
}
|
||||
} // namespace TestTransform3D
|
||||
82
tests/core/math/test_triangle_mesh.h
Normal file
@@ -0,0 +1,82 @@
|
||||
/**************************************************************************/
|
||||
/* test_triangle_mesh.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/math/triangle_mesh.h"
|
||||
#include "scene/resources/3d/primitive_meshes.h"
|
||||
|
||||
#include "tests/test_macros.h"
|
||||
|
||||
namespace TestTriangleMesh {
|
||||
|
||||
TEST_CASE("[SceneTree][TriangleMesh] BVH creation and intersection") {
|
||||
Ref<BoxMesh> box_mesh;
|
||||
box_mesh.instantiate();
|
||||
|
||||
const Vector<Face3> faces = box_mesh->get_faces();
|
||||
|
||||
Ref<TriangleMesh> triangle_mesh;
|
||||
triangle_mesh.instantiate();
|
||||
CHECK(triangle_mesh->create_from_faces(Variant(faces)));
|
||||
|
||||
const Vector3 begin = Vector3(0.0, 2.0, 0.0);
|
||||
const Vector3 end = Vector3(0.0, -2.0, 0.0);
|
||||
|
||||
{
|
||||
Vector3 point;
|
||||
Vector3 normal;
|
||||
int32_t *surf_index = nullptr;
|
||||
int32_t face_index = -1;
|
||||
const bool has_result = triangle_mesh->intersect_segment(begin, end, point, normal, surf_index, &face_index);
|
||||
CHECK(has_result);
|
||||
CHECK(point.is_equal_approx(Vector3(0.0, 0.5, 0.0)));
|
||||
CHECK(normal.is_equal_approx(Vector3(0.0, 1.0, 0.0)));
|
||||
CHECK(surf_index == nullptr);
|
||||
REQUIRE(face_index != -1);
|
||||
CHECK(face_index == 8);
|
||||
}
|
||||
|
||||
{
|
||||
Vector3 dir = begin.direction_to(end);
|
||||
Vector3 point;
|
||||
Vector3 normal;
|
||||
int32_t *surf_index = nullptr;
|
||||
int32_t face_index = -1;
|
||||
const bool has_result = triangle_mesh->intersect_ray(begin, dir, point, normal, surf_index, &face_index);
|
||||
CHECK(has_result);
|
||||
CHECK(point.is_equal_approx(Vector3(0.0, 0.5, 0.0)));
|
||||
CHECK(normal.is_equal_approx(Vector3(0.0, 1.0, 0.0)));
|
||||
CHECK(surf_index == nullptr);
|
||||
REQUIRE(face_index != -1);
|
||||
CHECK(face_index == 8);
|
||||
}
|
||||
}
|
||||
} // namespace TestTriangleMesh
|
||||
499
tests/core/math/test_vector2.h
Normal file
@@ -0,0 +1,499 @@
|
||||
/**************************************************************************/
|
||||
/* test_vector2.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/math/vector2.h"
|
||||
#include "core/math/vector2i.h"
|
||||
#include "tests/test_macros.h"
|
||||
|
||||
namespace TestVector2 {
|
||||
|
||||
TEST_CASE("[Vector2] Constructor methods") {
|
||||
constexpr Vector2 vector_empty = Vector2();
|
||||
constexpr Vector2 vector_zero = Vector2(0.0, 0.0);
|
||||
static_assert(
|
||||
vector_empty == vector_zero,
|
||||
"Vector2 Constructor with no inputs should return a zero Vector2.");
|
||||
}
|
||||
|
||||
TEST_CASE("[Vector2] Angle methods") {
|
||||
constexpr Vector2 vector_x = Vector2(1, 0);
|
||||
constexpr Vector2 vector_y = Vector2(0, 1);
|
||||
CHECK_MESSAGE(
|
||||
vector_x.angle_to(vector_y) == doctest::Approx((real_t)Math::TAU / 4),
|
||||
"Vector2 angle_to should work as expected.");
|
||||
CHECK_MESSAGE(
|
||||
vector_y.angle_to(vector_x) == doctest::Approx((real_t)-Math::TAU / 4),
|
||||
"Vector2 angle_to should work as expected.");
|
||||
CHECK_MESSAGE(
|
||||
vector_x.angle_to_point(vector_y) == doctest::Approx((real_t)Math::TAU * 3 / 8),
|
||||
"Vector2 angle_to_point should work as expected.");
|
||||
CHECK_MESSAGE(
|
||||
vector_y.angle_to_point(vector_x) == doctest::Approx((real_t)-Math::TAU / 8),
|
||||
"Vector2 angle_to_point should work as expected.");
|
||||
}
|
||||
|
||||
TEST_CASE("[Vector2] Axis methods") {
|
||||
Vector2 vector = Vector2(1.2, 3.4);
|
||||
CHECK_MESSAGE(
|
||||
vector.max_axis_index() == Vector2::Axis::AXIS_Y,
|
||||
"Vector2 max_axis_index should work as expected.");
|
||||
CHECK_MESSAGE(
|
||||
vector.min_axis_index() == Vector2::Axis::AXIS_X,
|
||||
"Vector2 min_axis_index should work as expected.");
|
||||
CHECK_MESSAGE(
|
||||
vector[vector.min_axis_index()] == (real_t)1.2,
|
||||
"Vector2 array operator should work as expected.");
|
||||
vector[Vector2::Axis::AXIS_Y] = 3.7;
|
||||
CHECK_MESSAGE(
|
||||
vector[Vector2::Axis::AXIS_Y] == (real_t)3.7,
|
||||
"Vector2 array operator setter should work as expected.");
|
||||
}
|
||||
|
||||
TEST_CASE("[Vector2] Interpolation methods") {
|
||||
constexpr Vector2 vector1 = Vector2(1, 2);
|
||||
constexpr Vector2 vector2 = Vector2(4, 5);
|
||||
CHECK_MESSAGE(
|
||||
vector1.lerp(vector2, 0.5) == Vector2(2.5, 3.5),
|
||||
"Vector2 lerp should work as expected.");
|
||||
CHECK_MESSAGE(
|
||||
vector1.lerp(vector2, 1.0 / 3.0).is_equal_approx(Vector2(2, 3)),
|
||||
"Vector2 lerp should work as expected.");
|
||||
CHECK_MESSAGE(
|
||||
vector1.normalized().slerp(vector2.normalized(), 0.5).is_equal_approx(Vector2(0.538953602313995361, 0.84233558177947998)),
|
||||
"Vector2 slerp should work as expected.");
|
||||
CHECK_MESSAGE(
|
||||
vector1.normalized().slerp(vector2.normalized(), 1.0 / 3.0).is_equal_approx(Vector2(0.508990883827209473, 0.860771894454956055)),
|
||||
"Vector2 slerp should work as expected.");
|
||||
CHECK_MESSAGE(
|
||||
Vector2(5, 0).slerp(Vector2(0, 5), 0.5).is_equal_approx(Vector2(5, 5) * Math::SQRT12),
|
||||
"Vector2 slerp with non-normalized values should work as expected.");
|
||||
CHECK_MESSAGE(
|
||||
Vector2(1, 1).slerp(Vector2(2, 2), 0.5).is_equal_approx(Vector2(1.5, 1.5)),
|
||||
"Vector2 slerp with colinear inputs should behave as expected.");
|
||||
CHECK_MESSAGE(
|
||||
Vector2().slerp(Vector2(), 0.5) == Vector2(),
|
||||
"Vector2 slerp with both inputs as zero vectors should return a zero vector.");
|
||||
CHECK_MESSAGE(
|
||||
Vector2().slerp(Vector2(1, 1), 0.5) == Vector2(0.5, 0.5),
|
||||
"Vector2 slerp with one input as zero should behave like a regular lerp.");
|
||||
CHECK_MESSAGE(
|
||||
Vector2(1, 1).slerp(Vector2(), 0.5) == Vector2(0.5, 0.5),
|
||||
"Vector2 slerp with one input as zero should behave like a regular lerp.");
|
||||
CHECK_MESSAGE(
|
||||
Vector2(4, 6).slerp(Vector2(8, 10), 0.5).is_equal_approx(Vector2(5.9076470794008017626, 8.07918879020090480697)),
|
||||
"Vector2 slerp should work as expected.");
|
||||
CHECK_MESSAGE(
|
||||
vector1.slerp(vector2, 0.5).length() == doctest::Approx((real_t)4.31959610746631919),
|
||||
"Vector2 slerp with different length input should return a vector with an interpolated length.");
|
||||
CHECK_MESSAGE(
|
||||
vector1.angle_to(vector1.slerp(vector2, 0.5)) * 2 == doctest::Approx(vector1.angle_to(vector2)),
|
||||
"Vector2 slerp with different length input should return a vector with an interpolated angle.");
|
||||
CHECK_MESSAGE(
|
||||
vector1.cubic_interpolate(vector2, Vector2(), Vector2(7, 7), 0.5) == Vector2(2.375, 3.5),
|
||||
"Vector2 cubic_interpolate should work as expected.");
|
||||
CHECK_MESSAGE(
|
||||
vector1.cubic_interpolate(vector2, Vector2(), Vector2(7, 7), 1.0 / 3.0).is_equal_approx(Vector2(1.851851940155029297, 2.962963104248046875)),
|
||||
"Vector2 cubic_interpolate should work as expected.");
|
||||
CHECK_MESSAGE(
|
||||
Vector2(1, 0).move_toward(Vector2(10, 0), 3) == Vector2(4, 0),
|
||||
"Vector2 move_toward should work as expected.");
|
||||
}
|
||||
|
||||
TEST_CASE("[Vector2] Length methods") {
|
||||
constexpr Vector2 vector1 = Vector2(10, 10);
|
||||
constexpr Vector2 vector2 = Vector2(20, 30);
|
||||
CHECK_MESSAGE(
|
||||
vector1.length_squared() == 200,
|
||||
"Vector2 length_squared should work as expected and return exact result.");
|
||||
CHECK_MESSAGE(
|
||||
vector1.length() == doctest::Approx(10 * (real_t)Math::SQRT2),
|
||||
"Vector2 length should work as expected.");
|
||||
CHECK_MESSAGE(
|
||||
vector2.length_squared() == 1300,
|
||||
"Vector2 length_squared should work as expected and return exact result.");
|
||||
CHECK_MESSAGE(
|
||||
vector2.length() == doctest::Approx((real_t)36.05551275463989293119),
|
||||
"Vector2 length should work as expected.");
|
||||
CHECK_MESSAGE(
|
||||
vector1.distance_squared_to(vector2) == 500,
|
||||
"Vector2 distance_squared_to should work as expected and return exact result.");
|
||||
CHECK_MESSAGE(
|
||||
vector1.distance_to(vector2) == doctest::Approx((real_t)22.36067977499789696409),
|
||||
"Vector2 distance_to should work as expected.");
|
||||
}
|
||||
|
||||
TEST_CASE("[Vector2] Limiting methods") {
|
||||
constexpr Vector2 vector = Vector2(10, 10);
|
||||
CHECK_MESSAGE(
|
||||
vector.limit_length().is_equal_approx(Vector2(Math::SQRT12, Math::SQRT12)),
|
||||
"Vector2 limit_length should work as expected.");
|
||||
CHECK_MESSAGE(
|
||||
vector.limit_length(5).is_equal_approx(5 * Vector2(Math::SQRT12, Math::SQRT12)),
|
||||
"Vector2 limit_length should work as expected.");
|
||||
|
||||
CHECK_MESSAGE(
|
||||
Vector2(-5, 15).clamp(Vector2(), vector).is_equal_approx(Vector2(0, 10)),
|
||||
"Vector2 clamp should work as expected.");
|
||||
CHECK_MESSAGE(
|
||||
vector.clamp(Vector2(0, 15), Vector2(5, 20)).is_equal_approx(Vector2(5, 15)),
|
||||
"Vector2 clamp should work as expected.");
|
||||
}
|
||||
|
||||
TEST_CASE("[Vector2] Normalization methods") {
|
||||
CHECK_MESSAGE(
|
||||
Vector2(1, 0).is_normalized() == true,
|
||||
"Vector2 is_normalized should return true for a normalized vector.");
|
||||
CHECK_MESSAGE(
|
||||
Vector2(1, 1).is_normalized() == false,
|
||||
"Vector2 is_normalized should return false for a non-normalized vector.");
|
||||
CHECK_MESSAGE(
|
||||
Vector2(1, 0).normalized() == Vector2(1, 0),
|
||||
"Vector2 normalized should return the same vector for a normalized vector.");
|
||||
CHECK_MESSAGE(
|
||||
Vector2(1, 1).normalized().is_equal_approx(Vector2(Math::SQRT12, Math::SQRT12)),
|
||||
"Vector2 normalized should work as expected.");
|
||||
|
||||
Vector2 vector = Vector2(3.2, -5.4);
|
||||
vector.normalize();
|
||||
CHECK_MESSAGE(
|
||||
vector == Vector2(3.2, -5.4).normalized(),
|
||||
"Vector2 normalize should convert same way as Vector2 normalized.");
|
||||
CHECK_MESSAGE(
|
||||
vector.is_equal_approx(Vector2(0.509802390301732898898, -0.860291533634174266891)),
|
||||
"Vector2 normalize should work as expected.");
|
||||
}
|
||||
|
||||
TEST_CASE("[Vector2] Operators") {
|
||||
constexpr Vector2 decimal1 = Vector2(2.3, 4.9);
|
||||
constexpr Vector2 decimal2 = Vector2(1.2, 3.4);
|
||||
constexpr Vector2 power1 = Vector2(0.75, 1.5);
|
||||
constexpr Vector2 power2 = Vector2(0.5, 0.125);
|
||||
constexpr Vector2 int1 = Vector2(4, 5);
|
||||
constexpr Vector2 int2 = Vector2(1, 2);
|
||||
|
||||
CHECK_MESSAGE(
|
||||
(decimal1 + decimal2).is_equal_approx(Vector2(3.5, 8.3)),
|
||||
"Vector2 addition should behave as expected.");
|
||||
static_assert(
|
||||
(power1 + power2) == Vector2(1.25, 1.625),
|
||||
"Vector2 addition with powers of two should give exact results.");
|
||||
static_assert(
|
||||
(int1 + int2) == Vector2(5, 7),
|
||||
"Vector2 addition with integers should give exact results.");
|
||||
|
||||
CHECK_MESSAGE(
|
||||
(decimal1 - decimal2).is_equal_approx(Vector2(1.1, 1.5)),
|
||||
"Vector2 subtraction should behave as expected.");
|
||||
static_assert(
|
||||
(power1 - power2) == Vector2(0.25, 1.375),
|
||||
"Vector2 subtraction with powers of two should give exact results.");
|
||||
static_assert(
|
||||
(int1 - int2) == Vector2(3, 3),
|
||||
"Vector2 subtraction with integers should give exact results.");
|
||||
|
||||
CHECK_MESSAGE(
|
||||
(decimal1 * decimal2).is_equal_approx(Vector2(2.76, 16.66)),
|
||||
"Vector2 multiplication should behave as expected.");
|
||||
static_assert(
|
||||
(power1 * power2) == Vector2(0.375, 0.1875),
|
||||
"Vector2 multiplication with powers of two should give exact results.");
|
||||
static_assert(
|
||||
(int1 * int2) == Vector2(4, 10),
|
||||
"Vector2 multiplication with integers should give exact results.");
|
||||
|
||||
CHECK_MESSAGE(
|
||||
(decimal1 / decimal2).is_equal_approx(Vector2(1.91666666666666666, 1.44117647058823529)),
|
||||
"Vector2 division should behave as expected.");
|
||||
static_assert(
|
||||
(power1 / power2) == Vector2(1.5, 12.0),
|
||||
"Vector2 division with powers of two should give exact results.");
|
||||
static_assert(
|
||||
(int1 / int2) == Vector2(4, 2.5),
|
||||
"Vector2 division with integers should give exact results.");
|
||||
|
||||
CHECK_MESSAGE(
|
||||
(decimal1 * 2).is_equal_approx(Vector2(4.6, 9.8)),
|
||||
"Vector2 multiplication should behave as expected.");
|
||||
static_assert(
|
||||
(power1 * 2) == Vector2(1.5, 3),
|
||||
"Vector2 multiplication with powers of two should give exact results.");
|
||||
static_assert(
|
||||
(int1 * 2) == Vector2(8, 10),
|
||||
"Vector2 multiplication with integers should give exact results.");
|
||||
|
||||
CHECK_MESSAGE(
|
||||
(decimal1 / 2).is_equal_approx(Vector2(1.15, 2.45)),
|
||||
"Vector2 division should behave as expected.");
|
||||
static_assert(
|
||||
(power1 / 2) == Vector2(0.375, 0.75),
|
||||
"Vector2 division with powers of two should give exact results.");
|
||||
static_assert(
|
||||
(int1 / 2) == Vector2(2, 2.5),
|
||||
"Vector2 division with integers should give exact results.");
|
||||
|
||||
CHECK_MESSAGE(
|
||||
((Vector2i)decimal1) == Vector2i(2, 4),
|
||||
"Vector2 cast to Vector2i should work as expected.");
|
||||
CHECK_MESSAGE(
|
||||
((Vector2i)decimal2) == Vector2i(1, 3),
|
||||
"Vector2 cast to Vector2i should work as expected.");
|
||||
CHECK_MESSAGE(
|
||||
Vector2(Vector2i(1, 2)) == Vector2(1, 2),
|
||||
"Vector2 constructed from Vector2i should work as expected.");
|
||||
|
||||
CHECK_MESSAGE(
|
||||
((String)decimal1) == "(2.3, 4.9)",
|
||||
"Vector2 cast to String should work as expected.");
|
||||
CHECK_MESSAGE(
|
||||
((String)decimal2) == "(1.2, 3.4)",
|
||||
"Vector2 cast to String should work as expected.");
|
||||
CHECK_MESSAGE(
|
||||
((String)Vector2(9.8, 9.9)) == "(9.8, 9.9)",
|
||||
"Vector2 cast to String should work as expected.");
|
||||
#ifdef REAL_T_IS_DOUBLE
|
||||
CHECK_MESSAGE(
|
||||
((String)Vector2(Math::PI, Math::TAU)) == "(3.14159265358979, 6.28318530717959)",
|
||||
"Vector2 cast to String should print the correct amount of digits for real_t = double.");
|
||||
#else
|
||||
CHECK_MESSAGE(
|
||||
((String)Vector2(Math::PI, Math::TAU)) == "(3.141593, 6.283185)",
|
||||
"Vector2 cast to String should print the correct amount of digits for real_t = float.");
|
||||
#endif // REAL_T_IS_DOUBLE
|
||||
}
|
||||
|
||||
TEST_CASE("[Vector2] Other methods") {
|
||||
constexpr Vector2 vector = Vector2(1.2, 3.4);
|
||||
CHECK_MESSAGE(
|
||||
vector.aspect() == doctest::Approx((real_t)1.2 / (real_t)3.4),
|
||||
"Vector2 aspect should work as expected.");
|
||||
|
||||
CHECK_MESSAGE(
|
||||
vector.direction_to(Vector2()).is_equal_approx(-vector.normalized()),
|
||||
"Vector2 direction_to should work as expected.");
|
||||
CHECK_MESSAGE(
|
||||
Vector2(1, 1).direction_to(Vector2(2, 2)).is_equal_approx(Vector2(Math::SQRT12, Math::SQRT12)),
|
||||
"Vector2 direction_to should work as expected.");
|
||||
|
||||
CHECK_MESSAGE(
|
||||
vector.posmod(2).is_equal_approx(Vector2(1.2, 1.4)),
|
||||
"Vector2 posmod should work as expected.");
|
||||
CHECK_MESSAGE(
|
||||
(-vector).posmod(2).is_equal_approx(Vector2(0.8, 0.6)),
|
||||
"Vector2 posmod should work as expected.");
|
||||
CHECK_MESSAGE(
|
||||
vector.posmodv(Vector2(1, 2)).is_equal_approx(Vector2(0.2, 1.4)),
|
||||
"Vector2 posmodv should work as expected.");
|
||||
CHECK_MESSAGE(
|
||||
(-vector).posmodv(Vector2(2, 3)).is_equal_approx(Vector2(0.8, 2.6)),
|
||||
"Vector2 posmodv should work as expected.");
|
||||
|
||||
CHECK_MESSAGE(
|
||||
vector.rotated(Math::TAU).is_equal_approx(Vector2(1.2, 3.4)),
|
||||
"Vector2 rotated should work as expected.");
|
||||
CHECK_MESSAGE(
|
||||
vector.rotated(Math::TAU / 4).is_equal_approx(Vector2(-3.4, 1.2)),
|
||||
"Vector2 rotated should work as expected.");
|
||||
CHECK_MESSAGE(
|
||||
vector.rotated(Math::TAU / 3).is_equal_approx(Vector2(-3.544486372867091398996, -0.660769515458673623883)),
|
||||
"Vector2 rotated should work as expected.");
|
||||
CHECK_MESSAGE(
|
||||
vector.rotated(Math::TAU / 2).is_equal_approx(vector.rotated(Math::TAU / -2)),
|
||||
"Vector2 rotated should work as expected.");
|
||||
|
||||
CHECK_MESSAGE(
|
||||
vector.snapped(Vector2(1, 1)) == Vector2(1, 3),
|
||||
"Vector2 snapped to integers should be the same as rounding.");
|
||||
CHECK_MESSAGE(
|
||||
Vector2(3.4, 5.6).snapped(Vector2(1, 1)) == Vector2(3, 6),
|
||||
"Vector2 snapped to integers should be the same as rounding.");
|
||||
CHECK_MESSAGE(
|
||||
vector.snapped(Vector2(0.25, 0.25)) == Vector2(1.25, 3.5),
|
||||
"Vector2 snapped to 0.25 should give exact results.");
|
||||
|
||||
CHECK_MESSAGE(
|
||||
Vector2(1.2, 2.5).is_equal_approx(vector.min(Vector2(3.0, 2.5))),
|
||||
"Vector2 min should return expected value.");
|
||||
|
||||
CHECK_MESSAGE(
|
||||
Vector2(5.3, 3.4).is_equal_approx(vector.max(Vector2(5.3, 2.0))),
|
||||
"Vector2 max should return expected value.");
|
||||
}
|
||||
|
||||
TEST_CASE("[Vector2] Plane methods") {
|
||||
constexpr Vector2 vector = Vector2(1.2, 3.4);
|
||||
constexpr Vector2 vector_y = Vector2(0, 1);
|
||||
constexpr Vector2 vector_normal = Vector2(0.95879811270838721622267, 0.2840883296913739899919);
|
||||
constexpr real_t p_d = 99.1;
|
||||
CHECK_MESSAGE(
|
||||
vector.bounce(vector_y) == Vector2(1.2, -3.4),
|
||||
"Vector2 bounce on a plane with normal of the Y axis should.");
|
||||
CHECK_MESSAGE(
|
||||
vector.bounce(vector_normal).is_equal_approx(Vector2(-2.85851197982345523329, 2.197477931904161412358)),
|
||||
"Vector2 bounce with normal should return expected value.");
|
||||
CHECK_MESSAGE(
|
||||
vector.reflect(vector_y) == Vector2(-1.2, 3.4),
|
||||
"Vector2 reflect on a plane with normal of the Y axis should.");
|
||||
CHECK_MESSAGE(
|
||||
vector.reflect(vector_normal).is_equal_approx(Vector2(2.85851197982345523329, -2.197477931904161412358)),
|
||||
"Vector2 reflect with normal should return expected value.");
|
||||
CHECK_MESSAGE(
|
||||
vector.project(vector_y) == Vector2(0, 3.4),
|
||||
"Vector2 projected on the Y axis should only give the Y component.");
|
||||
CHECK_MESSAGE(
|
||||
vector.project(vector_normal).is_equal_approx(Vector2(2.0292559899117276166, 0.60126103404791929382)),
|
||||
"Vector2 projected on a normal should return expected value.");
|
||||
CHECK_MESSAGE(
|
||||
vector_normal.plane_project(p_d, vector).is_equal_approx(Vector2(94.187635516479631, 30.951892004882851)),
|
||||
"Vector2 plane_project should return expected value.");
|
||||
CHECK_MESSAGE(
|
||||
vector.slide(vector_y) == Vector2(1.2, 0),
|
||||
"Vector2 slide on a plane with normal of the Y axis should set the Y to zero.");
|
||||
CHECK_MESSAGE(
|
||||
vector.slide(vector_normal).is_equal_approx(Vector2(-0.8292559899117276166456, 2.798738965952080706179)),
|
||||
"Vector2 slide with normal should return expected value.");
|
||||
// There's probably a better way to test these ones?
|
||||
#ifdef MATH_CHECKS
|
||||
constexpr Vector2 vector_non_normal = Vector2(5.4, 1.6);
|
||||
ERR_PRINT_OFF;
|
||||
CHECK_MESSAGE(
|
||||
vector.bounce(vector_non_normal).is_equal_approx(Vector2()),
|
||||
"Vector2 bounce should return empty Vector2 with non-normalized input.");
|
||||
CHECK_MESSAGE(
|
||||
vector.reflect(vector_non_normal).is_equal_approx(Vector2()),
|
||||
"Vector2 reflect should return empty Vector2 with non-normalized input.");
|
||||
CHECK_MESSAGE(
|
||||
vector.slide(vector_non_normal).is_equal_approx(Vector2()),
|
||||
"Vector2 slide should return empty Vector2 with non-normalized input.");
|
||||
ERR_PRINT_ON;
|
||||
#endif // MATH_CHECKS
|
||||
}
|
||||
|
||||
TEST_CASE("[Vector2] Rounding methods") {
|
||||
constexpr Vector2 vector1 = Vector2(1.2, 5.6);
|
||||
constexpr Vector2 vector2 = Vector2(1.2, -5.6);
|
||||
CHECK_MESSAGE(
|
||||
vector1.abs() == vector1,
|
||||
"Vector2 abs should work as expected.");
|
||||
CHECK_MESSAGE(
|
||||
vector2.abs() == vector1,
|
||||
"Vector2 abs should work as expected.");
|
||||
|
||||
CHECK_MESSAGE(
|
||||
vector1.ceil() == Vector2(2, 6),
|
||||
"Vector2 ceil should work as expected.");
|
||||
CHECK_MESSAGE(
|
||||
vector2.ceil() == Vector2(2, -5),
|
||||
"Vector2 ceil should work as expected.");
|
||||
|
||||
CHECK_MESSAGE(
|
||||
vector1.floor() == Vector2(1, 5),
|
||||
"Vector2 floor should work as expected.");
|
||||
CHECK_MESSAGE(
|
||||
vector2.floor() == Vector2(1, -6),
|
||||
"Vector2 floor should work as expected.");
|
||||
|
||||
CHECK_MESSAGE(
|
||||
vector1.round() == Vector2(1, 6),
|
||||
"Vector2 round should work as expected.");
|
||||
CHECK_MESSAGE(
|
||||
vector2.round() == Vector2(1, -6),
|
||||
"Vector2 round should work as expected.");
|
||||
|
||||
CHECK_MESSAGE(
|
||||
vector1.sign() == Vector2(1, 1),
|
||||
"Vector2 sign should work as expected.");
|
||||
CHECK_MESSAGE(
|
||||
vector2.sign() == Vector2(1, -1),
|
||||
"Vector2 sign should work as expected.");
|
||||
}
|
||||
|
||||
TEST_CASE("[Vector2] Linear algebra methods") {
|
||||
constexpr Vector2 vector_x = Vector2(1, 0);
|
||||
constexpr Vector2 vector_y = Vector2(0, 1);
|
||||
constexpr Vector2 a = Vector2(3.5, 8.5);
|
||||
constexpr Vector2 b = Vector2(5.2, 4.6);
|
||||
CHECK_MESSAGE(
|
||||
vector_x.cross(vector_y) == 1,
|
||||
"Vector2 cross product of X and Y should give 1.");
|
||||
CHECK_MESSAGE(
|
||||
vector_y.cross(vector_x) == -1,
|
||||
"Vector2 cross product of Y and X should give negative 1.");
|
||||
CHECK_MESSAGE(
|
||||
a.cross(b) == doctest::Approx((real_t)-28.1),
|
||||
"Vector2 cross should return expected value.");
|
||||
CHECK_MESSAGE(
|
||||
Vector2(-a.x, a.y).cross(Vector2(b.x, -b.y)) == doctest::Approx((real_t)-28.1),
|
||||
"Vector2 cross should return expected value.");
|
||||
|
||||
CHECK_MESSAGE(
|
||||
vector_x.dot(vector_y) == 0.0,
|
||||
"Vector2 dot product of perpendicular vectors should be zero.");
|
||||
CHECK_MESSAGE(
|
||||
vector_x.dot(vector_x) == 1.0,
|
||||
"Vector2 dot product of identical unit vectors should be one.");
|
||||
CHECK_MESSAGE(
|
||||
(vector_x * 10).dot(vector_x * 10) == 100.0,
|
||||
"Vector2 dot product of same direction vectors should behave as expected.");
|
||||
CHECK_MESSAGE(
|
||||
a.dot(b) == doctest::Approx((real_t)57.3),
|
||||
"Vector2 dot should return expected value.");
|
||||
CHECK_MESSAGE(
|
||||
Vector2(-a.x, a.y).dot(Vector2(b.x, -b.y)) == doctest::Approx((real_t)-57.3),
|
||||
"Vector2 dot should return expected value.");
|
||||
}
|
||||
|
||||
TEST_CASE("[Vector2] Finite number checks") {
|
||||
constexpr double infinite[] = { Math::NaN, Math::INF, -Math::INF };
|
||||
|
||||
CHECK_MESSAGE(
|
||||
Vector2(0, 1).is_finite(),
|
||||
"Vector2(0, 1) should be finite");
|
||||
|
||||
for (double x : infinite) {
|
||||
CHECK_FALSE_MESSAGE(
|
||||
Vector2(x, 1).is_finite(),
|
||||
"Vector2 with one component infinite should not be finite.");
|
||||
CHECK_FALSE_MESSAGE(
|
||||
Vector2(0, x).is_finite(),
|
||||
"Vector2 with one component infinite should not be finite.");
|
||||
}
|
||||
|
||||
for (double x : infinite) {
|
||||
for (double y : infinite) {
|
||||
CHECK_FALSE_MESSAGE(
|
||||
Vector2(x, y).is_finite(),
|
||||
"Vector2 with two components infinite should not be finite.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace TestVector2
|
||||
168
tests/core/math/test_vector2i.h
Normal file
@@ -0,0 +1,168 @@
|
||||
/**************************************************************************/
|
||||
/* test_vector2i.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/math/vector2.h"
|
||||
#include "core/math/vector2i.h"
|
||||
#include "tests/test_macros.h"
|
||||
|
||||
namespace TestVector2i {
|
||||
|
||||
TEST_CASE("[Vector2i] Constructor methods") {
|
||||
constexpr Vector2i vector_empty = Vector2i();
|
||||
constexpr Vector2i vector_zero = Vector2i(0, 0);
|
||||
static_assert(
|
||||
vector_empty == vector_zero,
|
||||
"Vector2i Constructor with no inputs should return a zero Vector2i.");
|
||||
}
|
||||
|
||||
TEST_CASE("[Vector2i] Axis methods") {
|
||||
Vector2i vector = Vector2i(2, 3);
|
||||
CHECK_MESSAGE(
|
||||
vector.max_axis_index() == Vector2i::Axis::AXIS_Y,
|
||||
"Vector2i max_axis_index should work as expected.");
|
||||
CHECK_MESSAGE(
|
||||
vector.min_axis_index() == Vector2i::Axis::AXIS_X,
|
||||
"Vector2i min_axis_index should work as expected.");
|
||||
CHECK_MESSAGE(
|
||||
vector[vector.min_axis_index()] == 2,
|
||||
"Vector2i array operator should work as expected.");
|
||||
vector[Vector2i::Axis::AXIS_Y] = 5;
|
||||
CHECK_MESSAGE(
|
||||
vector[Vector2i::Axis::AXIS_Y] == 5,
|
||||
"Vector2i array operator setter should work as expected.");
|
||||
}
|
||||
|
||||
TEST_CASE("[Vector2i] Clamp method") {
|
||||
constexpr Vector2i vector = Vector2i(10, 10);
|
||||
CHECK_MESSAGE(
|
||||
Vector2i(-5, 15).clamp(Vector2i(), vector) == Vector2i(0, 10),
|
||||
"Vector2i clamp should work as expected.");
|
||||
CHECK_MESSAGE(
|
||||
vector.clamp(Vector2i(0, 15), Vector2i(5, 20)) == Vector2i(5, 15),
|
||||
"Vector2i clamp should work as expected.");
|
||||
}
|
||||
|
||||
TEST_CASE("[Vector2i] Length methods") {
|
||||
constexpr Vector2i vector1 = Vector2i(10, 10);
|
||||
constexpr Vector2i vector2 = Vector2i(20, 30);
|
||||
CHECK_MESSAGE(
|
||||
vector1.length_squared() == 200,
|
||||
"Vector2i length_squared should work as expected and return exact result.");
|
||||
CHECK_MESSAGE(
|
||||
vector1.length() == doctest::Approx(10 * Math::SQRT2),
|
||||
"Vector2i length should work as expected.");
|
||||
CHECK_MESSAGE(
|
||||
vector2.length_squared() == 1300,
|
||||
"Vector2i length_squared should work as expected and return exact result.");
|
||||
CHECK_MESSAGE(
|
||||
vector2.length() == doctest::Approx(36.05551275463989293119),
|
||||
"Vector2i length should work as expected.");
|
||||
CHECK_MESSAGE(
|
||||
vector1.distance_squared_to(vector2) == 500,
|
||||
"Vector2i distance_squared_to should work as expected and return exact result.");
|
||||
CHECK_MESSAGE(
|
||||
vector1.distance_to(vector2) == doctest::Approx(22.36067977499789696409),
|
||||
"Vector2i distance_to should work as expected.");
|
||||
}
|
||||
|
||||
TEST_CASE("[Vector2i] Operators") {
|
||||
constexpr Vector2i vector1 = Vector2i(5, 9);
|
||||
constexpr Vector2i vector2 = Vector2i(2, 3);
|
||||
|
||||
static_assert(
|
||||
(vector1 + vector2) == Vector2i(7, 12),
|
||||
"Vector2i addition with integers should give exact results.");
|
||||
static_assert(
|
||||
(vector1 - vector2) == Vector2i(3, 6),
|
||||
"Vector2i subtraction with integers should give exact results.");
|
||||
static_assert(
|
||||
(vector1 * vector2) == Vector2i(10, 27),
|
||||
"Vector2i multiplication with integers should give exact results.");
|
||||
static_assert(
|
||||
(vector1 / vector2) == Vector2i(2, 3),
|
||||
"Vector2i division with integers should give exact results.");
|
||||
|
||||
static_assert(
|
||||
(vector1 * 2) == Vector2i(10, 18),
|
||||
"Vector2i multiplication with integers should give exact results.");
|
||||
static_assert(
|
||||
(vector1 / 2) == Vector2i(2, 4),
|
||||
"Vector2i division with integers should give exact results.");
|
||||
|
||||
CHECK_MESSAGE(
|
||||
((Vector2)vector1) == Vector2(5, 9),
|
||||
"Vector2i cast to Vector2 should work as expected.");
|
||||
CHECK_MESSAGE(
|
||||
((Vector2)vector2) == Vector2(2, 3),
|
||||
"Vector2i cast to Vector2 should work as expected.");
|
||||
CHECK_MESSAGE(
|
||||
Vector2i(Vector2(1.1, 2.9)) == Vector2i(1, 2),
|
||||
"Vector2i constructed from Vector2 should work as expected.");
|
||||
}
|
||||
|
||||
TEST_CASE("[Vector2i] Other methods") {
|
||||
constexpr Vector2i vector = Vector2i(1, 3);
|
||||
CHECK_MESSAGE(
|
||||
vector.aspect() == doctest::Approx((real_t)1.0 / (real_t)3.0),
|
||||
"Vector2i aspect should work as expected.");
|
||||
|
||||
CHECK_MESSAGE(
|
||||
vector.min(Vector2i(3, 2)) == Vector2i(1, 2),
|
||||
"Vector2i min should return expected value.");
|
||||
|
||||
CHECK_MESSAGE(
|
||||
vector.max(Vector2i(5, 2)) == Vector2i(5, 3),
|
||||
"Vector2i max should return expected value.");
|
||||
|
||||
CHECK_MESSAGE(
|
||||
vector.snapped(Vector2i(4, 2)) == Vector2i(0, 4),
|
||||
"Vector2i snapped should work as expected.");
|
||||
}
|
||||
|
||||
TEST_CASE("[Vector2i] Abs and sign methods") {
|
||||
constexpr Vector2i vector1 = Vector2i(1, 3);
|
||||
constexpr Vector2i vector2 = Vector2i(1, -3);
|
||||
CHECK_MESSAGE(
|
||||
vector1.abs() == vector1,
|
||||
"Vector2i abs should work as expected.");
|
||||
CHECK_MESSAGE(
|
||||
vector2.abs() == vector1,
|
||||
"Vector2i abs should work as expected.");
|
||||
|
||||
CHECK_MESSAGE(
|
||||
vector1.sign() == Vector2i(1, 1),
|
||||
"Vector2i sign should work as expected.");
|
||||
CHECK_MESSAGE(
|
||||
vector2.sign() == Vector2i(1, -1),
|
||||
"Vector2i sign should work as expected.");
|
||||
}
|
||||
} // namespace TestVector2i
|
||||
533
tests/core/math/test_vector3.h
Normal file
@@ -0,0 +1,533 @@
|
||||
/**************************************************************************/
|
||||
/* test_vector3.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/math/vector3.h"
|
||||
#include "tests/test_macros.h"
|
||||
|
||||
namespace TestVector3 {
|
||||
|
||||
TEST_CASE("[Vector3] Constructor methods") {
|
||||
constexpr Vector3 vector_empty = Vector3();
|
||||
constexpr Vector3 vector_zero = Vector3(0.0, 0.0, 0.0);
|
||||
static_assert(
|
||||
vector_empty == vector_zero,
|
||||
"Vector3 Constructor with no inputs should return a zero Vector3.");
|
||||
}
|
||||
|
||||
TEST_CASE("[Vector3] Angle methods") {
|
||||
constexpr Vector3 vector_x = Vector3(1, 0, 0);
|
||||
constexpr Vector3 vector_y = Vector3(0, 1, 0);
|
||||
constexpr Vector3 vector_yz = Vector3(0, 1, 1);
|
||||
CHECK_MESSAGE(
|
||||
vector_x.angle_to(vector_y) == doctest::Approx((real_t)Math::TAU / 4),
|
||||
"Vector3 angle_to should work as expected.");
|
||||
CHECK_MESSAGE(
|
||||
vector_x.angle_to(vector_yz) == doctest::Approx((real_t)Math::TAU / 4),
|
||||
"Vector3 angle_to should work as expected.");
|
||||
CHECK_MESSAGE(
|
||||
vector_yz.angle_to(vector_x) == doctest::Approx((real_t)Math::TAU / 4),
|
||||
"Vector3 angle_to should work as expected.");
|
||||
CHECK_MESSAGE(
|
||||
vector_y.angle_to(vector_yz) == doctest::Approx((real_t)Math::TAU / 8),
|
||||
"Vector3 angle_to should work as expected.");
|
||||
|
||||
CHECK_MESSAGE(
|
||||
vector_x.signed_angle_to(vector_y, vector_y) == doctest::Approx((real_t)Math::TAU / 4),
|
||||
"Vector3 signed_angle_to edge case should be positive.");
|
||||
CHECK_MESSAGE(
|
||||
vector_x.signed_angle_to(vector_yz, vector_y) == doctest::Approx((real_t)Math::TAU / -4),
|
||||
"Vector3 signed_angle_to should work as expected.");
|
||||
CHECK_MESSAGE(
|
||||
vector_yz.signed_angle_to(vector_x, vector_y) == doctest::Approx((real_t)Math::TAU / 4),
|
||||
"Vector3 signed_angle_to should work as expected.");
|
||||
}
|
||||
|
||||
TEST_CASE("[Vector3] Axis methods") {
|
||||
Vector3 vector = Vector3(1.2, 3.4, 5.6);
|
||||
CHECK_MESSAGE(
|
||||
vector.max_axis_index() == Vector3::Axis::AXIS_Z,
|
||||
"Vector3 max_axis_index should work as expected.");
|
||||
CHECK_MESSAGE(
|
||||
vector.min_axis_index() == Vector3::Axis::AXIS_X,
|
||||
"Vector3 min_axis_index should work as expected.");
|
||||
CHECK_MESSAGE(
|
||||
vector[vector.max_axis_index()] == (real_t)5.6,
|
||||
"Vector3 array operator should work as expected.");
|
||||
CHECK_MESSAGE(
|
||||
vector[vector.min_axis_index()] == (real_t)1.2,
|
||||
"Vector3 array operator should work as expected.");
|
||||
|
||||
vector[Vector3::Axis::AXIS_Y] = 3.7;
|
||||
CHECK_MESSAGE(
|
||||
vector[Vector3::Axis::AXIS_Y] == (real_t)3.7,
|
||||
"Vector3 array operator setter should work as expected.");
|
||||
}
|
||||
|
||||
TEST_CASE("[Vector3] Interpolation methods") {
|
||||
constexpr Vector3 vector1 = Vector3(1, 2, 3);
|
||||
constexpr Vector3 vector2 = Vector3(4, 5, 6);
|
||||
CHECK_MESSAGE(
|
||||
vector1.lerp(vector2, 0.5) == Vector3(2.5, 3.5, 4.5),
|
||||
"Vector3 lerp should work as expected.");
|
||||
CHECK_MESSAGE(
|
||||
vector1.lerp(vector2, 1.0 / 3.0).is_equal_approx(Vector3(2, 3, 4)),
|
||||
"Vector3 lerp should work as expected.");
|
||||
CHECK_MESSAGE(
|
||||
vector1.normalized().slerp(vector2.normalized(), 0.5).is_equal_approx(Vector3(0.363866806030273438, 0.555698215961456299, 0.747529566287994385)),
|
||||
"Vector3 slerp should work as expected.");
|
||||
CHECK_MESSAGE(
|
||||
vector1.normalized().slerp(vector2.normalized(), 1.0 / 3.0).is_equal_approx(Vector3(0.332119762897491455, 0.549413740634918213, 0.766707837581634521)),
|
||||
"Vector3 slerp should work as expected.");
|
||||
CHECK_MESSAGE(
|
||||
Vector3(5, 0, 0).slerp(Vector3(0, 3, 4), 0.5).is_equal_approx(Vector3(3.535533905029296875, 2.121320486068725586, 2.828427314758300781)),
|
||||
"Vector3 slerp with non-normalized values should work as expected.");
|
||||
CHECK_MESSAGE(
|
||||
Vector3(1, 1, 1).slerp(Vector3(2, 2, 2), 0.5).is_equal_approx(Vector3(1.5, 1.5, 1.5)),
|
||||
"Vector3 slerp with colinear inputs should behave as expected.");
|
||||
CHECK_MESSAGE(
|
||||
Vector3().slerp(Vector3(), 0.5) == Vector3(),
|
||||
"Vector3 slerp with both inputs as zero vectors should return a zero vector.");
|
||||
CHECK_MESSAGE(
|
||||
Vector3().slerp(Vector3(1, 1, 1), 0.5) == Vector3(0.5, 0.5, 0.5),
|
||||
"Vector3 slerp with one input as zero should behave like a regular lerp.");
|
||||
CHECK_MESSAGE(
|
||||
Vector3(1, 1, 1).slerp(Vector3(), 0.5) == Vector3(0.5, 0.5, 0.5),
|
||||
"Vector3 slerp with one input as zero should behave like a regular lerp.");
|
||||
CHECK_MESSAGE(
|
||||
Vector3(4, 6, 2).slerp(Vector3(8, 10, 3), 0.5).is_equal_approx(Vector3(5.90194219811429941053, 8.06758688849378394534, 2.558307894718317120038)),
|
||||
"Vector3 slerp should work as expected.");
|
||||
CHECK_MESSAGE(
|
||||
vector1.slerp(vector2, 0.5).length() == doctest::Approx((real_t)6.25831088708303172),
|
||||
"Vector3 slerp with different length input should return a vector with an interpolated length.");
|
||||
CHECK_MESSAGE(
|
||||
vector1.angle_to(vector1.slerp(vector2, 0.5)) * 2 == doctest::Approx(vector1.angle_to(vector2)),
|
||||
"Vector3 slerp with different length input should return a vector with an interpolated angle.");
|
||||
CHECK_MESSAGE(
|
||||
vector1.cubic_interpolate(vector2, Vector3(), Vector3(7, 7, 7), 0.5) == Vector3(2.375, 3.5, 4.625),
|
||||
"Vector3 cubic_interpolate should work as expected.");
|
||||
CHECK_MESSAGE(
|
||||
vector1.cubic_interpolate(vector2, Vector3(), Vector3(7, 7, 7), 1.0 / 3.0).is_equal_approx(Vector3(1.851851940155029297, 2.962963104248046875, 4.074074268341064453)),
|
||||
"Vector3 cubic_interpolate should work as expected.");
|
||||
CHECK_MESSAGE(
|
||||
Vector3(1, 0, 0).move_toward(Vector3(10, 0, 0), 3) == Vector3(4, 0, 0),
|
||||
"Vector3 move_toward should work as expected.");
|
||||
}
|
||||
|
||||
TEST_CASE("[Vector3] Length methods") {
|
||||
constexpr Vector3 vector1 = Vector3(10, 10, 10);
|
||||
constexpr Vector3 vector2 = Vector3(20, 30, 40);
|
||||
CHECK_MESSAGE(
|
||||
vector1.length_squared() == 300,
|
||||
"Vector3 length_squared should work as expected and return exact result.");
|
||||
CHECK_MESSAGE(
|
||||
vector1.length() == doctest::Approx(10 * (real_t)Math::SQRT3),
|
||||
"Vector3 length should work as expected.");
|
||||
CHECK_MESSAGE(
|
||||
vector2.length_squared() == 2900,
|
||||
"Vector3 length_squared should work as expected and return exact result.");
|
||||
CHECK_MESSAGE(
|
||||
vector2.length() == doctest::Approx((real_t)53.8516480713450403125),
|
||||
"Vector3 length should work as expected.");
|
||||
CHECK_MESSAGE(
|
||||
vector1.distance_squared_to(vector2) == 1400,
|
||||
"Vector3 distance_squared_to should work as expected and return exact result.");
|
||||
CHECK_MESSAGE(
|
||||
vector1.distance_to(vector2) == doctest::Approx((real_t)37.41657386773941385584),
|
||||
"Vector3 distance_to should work as expected.");
|
||||
}
|
||||
|
||||
TEST_CASE("[Vector3] Limiting methods") {
|
||||
constexpr Vector3 vector = Vector3(10, 10, 10);
|
||||
CHECK_MESSAGE(
|
||||
vector.limit_length().is_equal_approx(Vector3(Math::SQRT13, Math::SQRT13, Math::SQRT13)),
|
||||
"Vector3 limit_length should work as expected.");
|
||||
CHECK_MESSAGE(
|
||||
vector.limit_length(5).is_equal_approx(5 * Vector3(Math::SQRT13, Math::SQRT13, Math::SQRT13)),
|
||||
"Vector3 limit_length should work as expected.");
|
||||
|
||||
CHECK_MESSAGE(
|
||||
Vector3(-5, 5, 15).clamp(Vector3(), vector) == Vector3(0, 5, 10),
|
||||
"Vector3 clamp should work as expected.");
|
||||
CHECK_MESSAGE(
|
||||
vector.clamp(Vector3(0, 10, 15), Vector3(5, 10, 20)) == Vector3(5, 10, 15),
|
||||
"Vector3 clamp should work as expected.");
|
||||
}
|
||||
|
||||
TEST_CASE("[Vector3] Normalization methods") {
|
||||
CHECK_MESSAGE(
|
||||
Vector3(1, 0, 0).is_normalized() == true,
|
||||
"Vector3 is_normalized should return true for a normalized vector.");
|
||||
CHECK_MESSAGE(
|
||||
Vector3(1, 1, 1).is_normalized() == false,
|
||||
"Vector3 is_normalized should return false for a non-normalized vector.");
|
||||
CHECK_MESSAGE(
|
||||
Vector3(1, 0, 0).normalized() == Vector3(1, 0, 0),
|
||||
"Vector3 normalized should return the same vector for a normalized vector.");
|
||||
CHECK_MESSAGE(
|
||||
Vector3(1, 1, 0).normalized().is_equal_approx(Vector3(Math::SQRT12, Math::SQRT12, 0)),
|
||||
"Vector3 normalized should work as expected.");
|
||||
CHECK_MESSAGE(
|
||||
Vector3(1, 1, 1).normalized().is_equal_approx(Vector3(Math::SQRT13, Math::SQRT13, Math::SQRT13)),
|
||||
"Vector3 normalized should work as expected.");
|
||||
|
||||
Vector3 vector = Vector3(3.2, -5.4, 6);
|
||||
vector.normalize();
|
||||
CHECK_MESSAGE(
|
||||
vector == Vector3(3.2, -5.4, 6).normalized(),
|
||||
"Vector3 normalize should convert same way as Vector3 normalized.");
|
||||
CHECK_MESSAGE(
|
||||
vector.is_equal_approx(Vector3(0.368522751763902980457, -0.621882143601586279522, 0.6909801595573180883585)),
|
||||
"Vector3 normalize should work as expected.");
|
||||
}
|
||||
|
||||
TEST_CASE("[Vector3] Operators") {
|
||||
constexpr Vector3 decimal1 = Vector3(2.3, 4.9, 7.8);
|
||||
constexpr Vector3 decimal2 = Vector3(1.2, 3.4, 5.6);
|
||||
constexpr Vector3 power1 = Vector3(0.75, 1.5, 0.625);
|
||||
constexpr Vector3 power2 = Vector3(0.5, 0.125, 0.25);
|
||||
constexpr Vector3 int1 = Vector3(4, 5, 9);
|
||||
constexpr Vector3 int2 = Vector3(1, 2, 3);
|
||||
|
||||
CHECK_MESSAGE(
|
||||
(decimal1 + decimal2).is_equal_approx(Vector3(3.5, 8.3, 13.4)),
|
||||
"Vector3 addition should behave as expected.");
|
||||
static_assert(
|
||||
(power1 + power2) == Vector3(1.25, 1.625, 0.875),
|
||||
"Vector3 addition with powers of two should give exact results.");
|
||||
static_assert(
|
||||
(int1 + int2) == Vector3(5, 7, 12),
|
||||
"Vector3 addition with integers should give exact results.");
|
||||
|
||||
CHECK_MESSAGE(
|
||||
(decimal1 - decimal2).is_equal_approx(Vector3(1.1, 1.5, 2.2)),
|
||||
"Vector3 subtraction should behave as expected.");
|
||||
static_assert(
|
||||
(power1 - power2) == Vector3(0.25, 1.375, 0.375),
|
||||
"Vector3 subtraction with powers of two should give exact results.");
|
||||
static_assert(
|
||||
(int1 - int2) == Vector3(3, 3, 6),
|
||||
"Vector3 subtraction with integers should give exact results.");
|
||||
|
||||
CHECK_MESSAGE(
|
||||
(decimal1 * decimal2).is_equal_approx(Vector3(2.76, 16.66, 43.68)),
|
||||
"Vector3 multiplication should behave as expected.");
|
||||
static_assert(
|
||||
(power1 * power2) == Vector3(0.375, 0.1875, 0.15625),
|
||||
"Vector3 multiplication with powers of two should give exact results.");
|
||||
static_assert(
|
||||
(int1 * int2) == Vector3(4, 10, 27),
|
||||
"Vector3 multiplication with integers should give exact results.");
|
||||
|
||||
CHECK_MESSAGE(
|
||||
(decimal1 / decimal2).is_equal_approx(Vector3(1.91666666666666666, 1.44117647058823529, 1.39285714285714286)),
|
||||
"Vector3 division should behave as expected.");
|
||||
static_assert(
|
||||
(power1 / power2) == Vector3(1.5, 12.0, 2.5),
|
||||
"Vector3 division with powers of two should give exact results.");
|
||||
static_assert(
|
||||
(int1 / int2) == Vector3(4, 2.5, 3),
|
||||
"Vector3 division with integers should give exact results.");
|
||||
|
||||
CHECK_MESSAGE(
|
||||
(decimal1 * 2).is_equal_approx(Vector3(4.6, 9.8, 15.6)),
|
||||
"Vector3 multiplication should behave as expected.");
|
||||
static_assert(
|
||||
(power1 * 2) == Vector3(1.5, 3, 1.25),
|
||||
"Vector3 multiplication with powers of two should give exact results.");
|
||||
static_assert(
|
||||
(int1 * 2) == Vector3(8, 10, 18),
|
||||
"Vector3 multiplication with integers should give exact results.");
|
||||
|
||||
CHECK_MESSAGE(
|
||||
(decimal1 / 2).is_equal_approx(Vector3(1.15, 2.45, 3.9)),
|
||||
"Vector3 division should behave as expected.");
|
||||
static_assert(
|
||||
(power1 / 2) == Vector3(0.375, 0.75, 0.3125),
|
||||
"Vector3 division with powers of two should give exact results.");
|
||||
static_assert(
|
||||
(int1 / 2) == Vector3(2, 2.5, 4.5),
|
||||
"Vector3 division with integers should give exact results.");
|
||||
|
||||
CHECK_MESSAGE(
|
||||
((Vector3i)decimal1) == Vector3i(2, 4, 7),
|
||||
"Vector3 cast to Vector3i should work as expected.");
|
||||
CHECK_MESSAGE(
|
||||
((Vector3i)decimal2) == Vector3i(1, 3, 5),
|
||||
"Vector3 cast to Vector3i should work as expected.");
|
||||
CHECK_MESSAGE(
|
||||
Vector3(Vector3i(1, 2, 3)) == Vector3(1, 2, 3),
|
||||
"Vector3 constructed from Vector3i should work as expected.");
|
||||
|
||||
CHECK_MESSAGE(
|
||||
((String)decimal1) == "(2.3, 4.9, 7.8)",
|
||||
"Vector3 cast to String should work as expected.");
|
||||
CHECK_MESSAGE(
|
||||
((String)decimal2) == "(1.2, 3.4, 5.6)",
|
||||
"Vector3 cast to String should work as expected.");
|
||||
CHECK_MESSAGE(
|
||||
((String)Vector3(9.7, 9.8, 9.9)) == "(9.7, 9.8, 9.9)",
|
||||
"Vector3 cast to String should work as expected.");
|
||||
#ifdef REAL_T_IS_DOUBLE
|
||||
CHECK_MESSAGE(
|
||||
((String)Vector3(Math::E, Math::SQRT2, Math::SQRT3)) == "(2.71828182845905, 1.4142135623731, 1.73205080756888)",
|
||||
"Vector3 cast to String should print the correct amount of digits for real_t = double.");
|
||||
#else
|
||||
CHECK_MESSAGE(
|
||||
((String)Vector3(Math::E, Math::SQRT2, Math::SQRT3)) == "(2.718282, 1.414214, 1.732051)",
|
||||
"Vector3 cast to String should print the correct amount of digits for real_t = float.");
|
||||
#endif // REAL_T_IS_DOUBLE
|
||||
}
|
||||
|
||||
TEST_CASE("[Vector3] Other methods") {
|
||||
constexpr Vector3 vector = Vector3(1.2, 3.4, 5.6);
|
||||
CHECK_MESSAGE(
|
||||
vector.direction_to(Vector3()).is_equal_approx(-vector.normalized()),
|
||||
"Vector3 direction_to should work as expected.");
|
||||
CHECK_MESSAGE(
|
||||
Vector3(1, 1, 1).direction_to(Vector3(2, 2, 2)).is_equal_approx(Vector3(Math::SQRT13, Math::SQRT13, Math::SQRT13)),
|
||||
"Vector3 direction_to should work as expected.");
|
||||
CHECK_MESSAGE(
|
||||
vector.inverse().is_equal_approx(Vector3(1 / 1.2, 1 / 3.4, 1 / 5.6)),
|
||||
"Vector3 inverse should work as expected.");
|
||||
CHECK_MESSAGE(
|
||||
vector.posmod(2).is_equal_approx(Vector3(1.2, 1.4, 1.6)),
|
||||
"Vector3 posmod should work as expected.");
|
||||
CHECK_MESSAGE(
|
||||
(-vector).posmod(2).is_equal_approx(Vector3(0.8, 0.6, 0.4)),
|
||||
"Vector3 posmod should work as expected.");
|
||||
CHECK_MESSAGE(
|
||||
vector.posmodv(Vector3(1, 2, 3)).is_equal_approx(Vector3(0.2, 1.4, 2.6)),
|
||||
"Vector3 posmodv should work as expected.");
|
||||
CHECK_MESSAGE(
|
||||
(-vector).posmodv(Vector3(2, 3, 4)).is_equal_approx(Vector3(0.8, 2.6, 2.4)),
|
||||
"Vector3 posmodv should work as expected.");
|
||||
|
||||
CHECK_MESSAGE(
|
||||
vector.rotated(Vector3(0, 1, 0), Math::TAU).is_equal_approx(vector),
|
||||
"Vector3 rotated should work as expected.");
|
||||
CHECK_MESSAGE(
|
||||
vector.rotated(Vector3(0, 1, 0), Math::TAU / 4).is_equal_approx(Vector3(5.6, 3.4, -1.2)),
|
||||
"Vector3 rotated should work as expected.");
|
||||
CHECK_MESSAGE(
|
||||
vector.rotated(Vector3(1, 0, 0), Math::TAU / 3).is_equal_approx(Vector3(1.2, -6.54974226119285642, 0.1444863728670914)),
|
||||
"Vector3 rotated should work as expected.");
|
||||
CHECK_MESSAGE(
|
||||
vector.rotated(Vector3(0, 0, 1), Math::TAU / 2).is_equal_approx(vector.rotated(Vector3(0, 0, 1), Math::TAU / -2)),
|
||||
"Vector3 rotated should work as expected.");
|
||||
|
||||
CHECK_MESSAGE(
|
||||
vector.snapped(Vector3(1, 1, 1)) == Vector3(1, 3, 6),
|
||||
"Vector3 snapped to integers should be the same as rounding.");
|
||||
CHECK_MESSAGE(
|
||||
vector.snapped(Vector3(0.25, 0.25, 0.25)) == Vector3(1.25, 3.5, 5.5),
|
||||
"Vector3 snapped to 0.25 should give exact results.");
|
||||
|
||||
CHECK_MESSAGE(
|
||||
Vector3(1.2, 2.5, 2.0).is_equal_approx(vector.min(Vector3(3.0, 2.5, 2.0))),
|
||||
"Vector3 min should return expected value.");
|
||||
|
||||
CHECK_MESSAGE(
|
||||
Vector3(5.3, 3.4, 5.6).is_equal_approx(vector.max(Vector3(5.3, 2.0, 3.0))),
|
||||
"Vector3 max should return expected value.");
|
||||
}
|
||||
|
||||
TEST_CASE("[Vector3] Plane methods") {
|
||||
constexpr Vector3 vector = Vector3(1.2, 3.4, 5.6);
|
||||
constexpr Vector3 vector_y = Vector3(0, 1, 0);
|
||||
constexpr Vector3 vector_normal = Vector3(0.88763458893247992491, 0.26300284116517923701, 0.37806658417494515320);
|
||||
CHECK_MESSAGE(
|
||||
vector.bounce(vector_y) == Vector3(1.2, -3.4, 5.6),
|
||||
"Vector3 bounce on a plane with normal of the Y axis should.");
|
||||
CHECK_MESSAGE(
|
||||
vector.bounce(vector_normal).is_equal_approx(Vector3(-6.0369629829775736287, 1.25571467171034855444, 2.517589840583626047)),
|
||||
"Vector3 bounce with normal should return expected value.");
|
||||
CHECK_MESSAGE(
|
||||
vector.reflect(vector_y) == Vector3(-1.2, 3.4, -5.6),
|
||||
"Vector3 reflect on a plane with normal of the Y axis should.");
|
||||
CHECK_MESSAGE(
|
||||
vector.reflect(vector_normal).is_equal_approx(Vector3(6.0369629829775736287, -1.25571467171034855444, -2.517589840583626047)),
|
||||
"Vector3 reflect with normal should return expected value.");
|
||||
CHECK_MESSAGE(
|
||||
vector.project(vector_y) == Vector3(0, 3.4, 0),
|
||||
"Vector3 projected on the Y axis should only give the Y component.");
|
||||
CHECK_MESSAGE(
|
||||
vector.project(vector_normal).is_equal_approx(Vector3(3.61848149148878681437, 1.0721426641448257227776, 1.54120507970818697649)),
|
||||
"Vector3 projected on a normal should return expected value.");
|
||||
CHECK_MESSAGE(
|
||||
vector.slide(vector_y) == Vector3(1.2, 0, 5.6),
|
||||
"Vector3 slide on a plane with normal of the Y axis should set the Y to zero.");
|
||||
CHECK_MESSAGE(
|
||||
vector.slide(vector_normal).is_equal_approx(Vector3(-2.41848149148878681437, 2.32785733585517427722237, 4.0587949202918130235)),
|
||||
"Vector3 slide with normal should return expected value.");
|
||||
// There's probably a better way to test these ones?
|
||||
#ifdef MATH_CHECKS
|
||||
constexpr Vector3 vector_non_normal = Vector3(5.4, 1.6, 2.3);
|
||||
ERR_PRINT_OFF;
|
||||
CHECK_MESSAGE(
|
||||
vector.bounce(vector_non_normal).is_equal_approx(Vector3()),
|
||||
"Vector3 bounce should return empty Vector3 with non-normalized input.");
|
||||
CHECK_MESSAGE(
|
||||
vector.reflect(vector_non_normal).is_equal_approx(Vector3()),
|
||||
"Vector3 reflect should return empty Vector3 with non-normalized input.");
|
||||
CHECK_MESSAGE(
|
||||
vector.slide(vector_non_normal).is_equal_approx(Vector3()),
|
||||
"Vector3 slide should return empty Vector3 with non-normalized input.");
|
||||
ERR_PRINT_ON;
|
||||
#endif // MATH_CHECKS
|
||||
}
|
||||
|
||||
TEST_CASE("[Vector3] Rounding methods") {
|
||||
constexpr Vector3 vector1 = Vector3(1.2, 3.4, 5.6);
|
||||
constexpr Vector3 vector2 = Vector3(1.2, -3.4, -5.6);
|
||||
CHECK_MESSAGE(
|
||||
vector1.abs() == vector1,
|
||||
"Vector3 abs should work as expected.");
|
||||
CHECK_MESSAGE(
|
||||
vector2.abs() == vector1,
|
||||
"Vector3 abs should work as expected.");
|
||||
|
||||
CHECK_MESSAGE(
|
||||
vector1.ceil() == Vector3(2, 4, 6),
|
||||
"Vector3 ceil should work as expected.");
|
||||
CHECK_MESSAGE(
|
||||
vector2.ceil() == Vector3(2, -3, -5),
|
||||
"Vector3 ceil should work as expected.");
|
||||
|
||||
CHECK_MESSAGE(
|
||||
vector1.floor() == Vector3(1, 3, 5),
|
||||
"Vector3 floor should work as expected.");
|
||||
CHECK_MESSAGE(
|
||||
vector2.floor() == Vector3(1, -4, -6),
|
||||
"Vector3 floor should work as expected.");
|
||||
|
||||
CHECK_MESSAGE(
|
||||
vector1.round() == Vector3(1, 3, 6),
|
||||
"Vector3 round should work as expected.");
|
||||
CHECK_MESSAGE(
|
||||
vector2.round() == Vector3(1, -3, -6),
|
||||
"Vector3 round should work as expected.");
|
||||
|
||||
CHECK_MESSAGE(
|
||||
vector1.sign() == Vector3(1, 1, 1),
|
||||
"Vector3 sign should work as expected.");
|
||||
CHECK_MESSAGE(
|
||||
vector2.sign() == Vector3(1, -1, -1),
|
||||
"Vector3 sign should work as expected.");
|
||||
}
|
||||
|
||||
TEST_CASE("[Vector3] Linear algebra methods") {
|
||||
constexpr Vector3 vector_x = Vector3(1, 0, 0);
|
||||
constexpr Vector3 vector_y = Vector3(0, 1, 0);
|
||||
constexpr Vector3 vector_z = Vector3(0, 0, 1);
|
||||
constexpr Vector3 a = Vector3(3.5, 8.5, 2.3);
|
||||
constexpr Vector3 b = Vector3(5.2, 4.6, 7.8);
|
||||
CHECK_MESSAGE(
|
||||
vector_x.cross(vector_y) == vector_z,
|
||||
"Vector3 cross product of X and Y should give Z.");
|
||||
CHECK_MESSAGE(
|
||||
vector_y.cross(vector_x) == -vector_z,
|
||||
"Vector3 cross product of Y and X should give negative Z.");
|
||||
CHECK_MESSAGE(
|
||||
vector_y.cross(vector_z) == vector_x,
|
||||
"Vector3 cross product of Y and Z should give X.");
|
||||
CHECK_MESSAGE(
|
||||
vector_z.cross(vector_x) == vector_y,
|
||||
"Vector3 cross product of Z and X should give Y.");
|
||||
CHECK_MESSAGE(
|
||||
a.cross(b).is_equal_approx(Vector3(55.72, -15.34, -28.1)),
|
||||
"Vector3 cross should return expected value.");
|
||||
CHECK_MESSAGE(
|
||||
Vector3(-a.x, a.y, -a.z).cross(Vector3(b.x, -b.y, b.z)).is_equal_approx(Vector3(55.72, 15.34, -28.1)),
|
||||
"Vector2 cross should return expected value.");
|
||||
|
||||
CHECK_MESSAGE(
|
||||
vector_x.dot(vector_y) == 0.0,
|
||||
"Vector3 dot product of perpendicular vectors should be zero.");
|
||||
CHECK_MESSAGE(
|
||||
vector_x.dot(vector_x) == 1.0,
|
||||
"Vector3 dot product of identical unit vectors should be one.");
|
||||
CHECK_MESSAGE(
|
||||
(vector_x * 10).dot(vector_x * 10) == 100.0,
|
||||
"Vector3 dot product of same direction vectors should behave as expected.");
|
||||
CHECK_MESSAGE(
|
||||
a.dot(b) == doctest::Approx((real_t)75.24),
|
||||
"Vector3 dot should return expected value.");
|
||||
CHECK_MESSAGE(
|
||||
Vector3(-a.x, a.y, -a.z).dot(Vector3(b.x, -b.y, b.z)) == doctest::Approx((real_t)-75.24),
|
||||
"Vector3 dot should return expected value.");
|
||||
}
|
||||
|
||||
TEST_CASE("[Vector3] Finite number checks") {
|
||||
constexpr double infinite[] = { Math::NaN, Math::INF, -Math::INF };
|
||||
|
||||
CHECK_MESSAGE(
|
||||
Vector3(0, 1, 2).is_finite(),
|
||||
"Vector3(0, 1, 2) should be finite");
|
||||
|
||||
for (double x : infinite) {
|
||||
CHECK_FALSE_MESSAGE(
|
||||
Vector3(x, 1, 2).is_finite(),
|
||||
"Vector3 with one component infinite should not be finite.");
|
||||
CHECK_FALSE_MESSAGE(
|
||||
Vector3(0, x, 2).is_finite(),
|
||||
"Vector3 with one component infinite should not be finite.");
|
||||
CHECK_FALSE_MESSAGE(
|
||||
Vector3(0, 1, x).is_finite(),
|
||||
"Vector3 with one component infinite should not be finite.");
|
||||
}
|
||||
|
||||
for (double x : infinite) {
|
||||
for (double y : infinite) {
|
||||
CHECK_FALSE_MESSAGE(
|
||||
Vector3(x, y, 2).is_finite(),
|
||||
"Vector3 with two components infinite should not be finite.");
|
||||
CHECK_FALSE_MESSAGE(
|
||||
Vector3(x, 1, y).is_finite(),
|
||||
"Vector3 with two components infinite should not be finite.");
|
||||
CHECK_FALSE_MESSAGE(
|
||||
Vector3(0, x, y).is_finite(),
|
||||
"Vector3 with two components infinite should not be finite.");
|
||||
}
|
||||
}
|
||||
|
||||
for (double x : infinite) {
|
||||
for (double y : infinite) {
|
||||
for (double z : infinite) {
|
||||
CHECK_FALSE_MESSAGE(
|
||||
Vector3(x, y, z).is_finite(),
|
||||
"Vector3 with three components infinite should not be finite.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace TestVector3
|
||||
167
tests/core/math/test_vector3i.h
Normal file
@@ -0,0 +1,167 @@
|
||||
/**************************************************************************/
|
||||
/* test_vector3i.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/math/vector3i.h"
|
||||
#include "tests/test_macros.h"
|
||||
|
||||
namespace TestVector3i {
|
||||
|
||||
TEST_CASE("[Vector3i] Constructor methods") {
|
||||
constexpr Vector3i vector_empty = Vector3i();
|
||||
constexpr Vector3i vector_zero = Vector3i(0, 0, 0);
|
||||
static_assert(
|
||||
vector_empty == vector_zero,
|
||||
"Vector3i Constructor with no inputs should return a zero Vector3i.");
|
||||
}
|
||||
|
||||
TEST_CASE("[Vector3i] Axis methods") {
|
||||
Vector3i vector = Vector3i(1, 2, 3);
|
||||
CHECK_MESSAGE(
|
||||
vector.max_axis_index() == Vector3i::Axis::AXIS_Z,
|
||||
"Vector3i max_axis_index should work as expected.");
|
||||
CHECK_MESSAGE(
|
||||
vector.min_axis_index() == Vector3i::Axis::AXIS_X,
|
||||
"Vector3i min_axis_index should work as expected.");
|
||||
CHECK_MESSAGE(
|
||||
vector[vector.max_axis_index()] == 3,
|
||||
"Vector3i array operator should work as expected.");
|
||||
CHECK_MESSAGE(
|
||||
vector[vector.min_axis_index()] == 1,
|
||||
"Vector3i array operator should work as expected.");
|
||||
|
||||
vector[Vector3i::Axis::AXIS_Y] = 5;
|
||||
CHECK_MESSAGE(
|
||||
vector[Vector3i::Axis::AXIS_Y] == 5,
|
||||
"Vector3i array operator setter should work as expected.");
|
||||
}
|
||||
|
||||
TEST_CASE("[Vector3i] Clamp method") {
|
||||
constexpr Vector3i vector = Vector3i(10, 10, 10);
|
||||
CHECK_MESSAGE(
|
||||
Vector3i(-5, 5, 15).clamp(Vector3i(), vector) == Vector3i(0, 5, 10),
|
||||
"Vector3i clamp should work as expected.");
|
||||
CHECK_MESSAGE(
|
||||
vector.clamp(Vector3i(0, 10, 15), Vector3i(5, 10, 20)) == Vector3i(5, 10, 15),
|
||||
"Vector3i clamp should work as expected.");
|
||||
}
|
||||
|
||||
TEST_CASE("[Vector3i] Length methods") {
|
||||
constexpr Vector3i vector1 = Vector3i(10, 10, 10);
|
||||
constexpr Vector3i vector2 = Vector3i(20, 30, 40);
|
||||
CHECK_MESSAGE(
|
||||
vector1.length_squared() == 300,
|
||||
"Vector3i length_squared should work as expected and return exact result.");
|
||||
CHECK_MESSAGE(
|
||||
vector1.length() == doctest::Approx(10 * Math::SQRT3),
|
||||
"Vector3i length should work as expected.");
|
||||
CHECK_MESSAGE(
|
||||
vector2.length_squared() == 2900,
|
||||
"Vector3i length_squared should work as expected and return exact result.");
|
||||
CHECK_MESSAGE(
|
||||
vector2.length() == doctest::Approx(53.8516480713450403125),
|
||||
"Vector3i length should work as expected.");
|
||||
CHECK_MESSAGE(
|
||||
vector1.distance_squared_to(vector2) == 1400,
|
||||
"Vector3i distance_squared_to should work as expected and return exact result.");
|
||||
CHECK_MESSAGE(
|
||||
vector1.distance_to(vector2) == doctest::Approx(37.41657386773941385584),
|
||||
"Vector3i distance_to should work as expected.");
|
||||
}
|
||||
|
||||
TEST_CASE("[Vector3i] Operators") {
|
||||
constexpr Vector3i vector1 = Vector3i(4, 5, 9);
|
||||
constexpr Vector3i vector2 = Vector3i(1, 2, 3);
|
||||
|
||||
static_assert(
|
||||
(vector1 + vector2) == Vector3i(5, 7, 12),
|
||||
"Vector3i addition with integers should give exact results.");
|
||||
static_assert(
|
||||
(vector1 - vector2) == Vector3i(3, 3, 6),
|
||||
"Vector3i subtraction with integers should give exact results.");
|
||||
static_assert(
|
||||
(vector1 * vector2) == Vector3i(4, 10, 27),
|
||||
"Vector3i multiplication with integers should give exact results.");
|
||||
static_assert(
|
||||
(vector1 / vector2) == Vector3i(4, 2, 3),
|
||||
"Vector3i division with integers should give exact results.");
|
||||
|
||||
static_assert(
|
||||
(vector1 * 2) == Vector3i(8, 10, 18),
|
||||
"Vector3i multiplication with integers should give exact results.");
|
||||
static_assert(
|
||||
(vector1 / 2) == Vector3i(2, 2, 4),
|
||||
"Vector3i division with integers should give exact results.");
|
||||
|
||||
CHECK_MESSAGE(
|
||||
((Vector3)vector1) == Vector3(4, 5, 9),
|
||||
"Vector3i cast to Vector3 should work as expected.");
|
||||
CHECK_MESSAGE(
|
||||
((Vector3)vector2) == Vector3(1, 2, 3),
|
||||
"Vector3i cast to Vector3 should work as expected.");
|
||||
CHECK_MESSAGE(
|
||||
Vector3i(Vector3(1.1, 2.9, 3.9)) == Vector3i(1, 2, 3),
|
||||
"Vector3i constructed from Vector3 should work as expected.");
|
||||
}
|
||||
|
||||
TEST_CASE("[Vector3i] Other methods") {
|
||||
constexpr Vector3i vector = Vector3i(1, 3, -7);
|
||||
|
||||
CHECK_MESSAGE(
|
||||
vector.min(Vector3i(3, 2, 5)) == Vector3i(1, 2, -7),
|
||||
"Vector3i min should return expected value.");
|
||||
CHECK_MESSAGE(
|
||||
vector.max(Vector3i(5, 2, 4)) == Vector3i(5, 3, 4),
|
||||
"Vector3i max should return expected value.");
|
||||
|
||||
CHECK_MESSAGE(
|
||||
vector.snapped(Vector3i(4, 2, 5)) == Vector3i(0, 4, -5),
|
||||
"Vector3i snapped should work as expected.");
|
||||
}
|
||||
|
||||
TEST_CASE("[Vector3i] Abs and sign methods") {
|
||||
constexpr Vector3i vector1 = Vector3i(1, 3, 5);
|
||||
constexpr Vector3i vector2 = Vector3i(1, -3, -5);
|
||||
CHECK_MESSAGE(
|
||||
vector1.abs() == vector1,
|
||||
"Vector3i abs should work as expected.");
|
||||
CHECK_MESSAGE(
|
||||
vector2.abs() == vector1,
|
||||
"Vector3i abs should work as expected.");
|
||||
|
||||
CHECK_MESSAGE(
|
||||
vector1.sign() == Vector3i(1, 1, 1),
|
||||
"Vector3i sign should work as expected.");
|
||||
CHECK_MESSAGE(
|
||||
vector2.sign() == Vector3i(1, -1, -1),
|
||||
"Vector3i sign should work as expected.");
|
||||
}
|
||||
} // namespace TestVector3i
|
||||
400
tests/core/math/test_vector4.h
Normal file
@@ -0,0 +1,400 @@
|
||||
/**************************************************************************/
|
||||
/* test_vector4.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/math/vector4.h"
|
||||
#include "tests/test_macros.h"
|
||||
|
||||
namespace TestVector4 {
|
||||
|
||||
TEST_CASE("[Vector4] Constructor methods") {
|
||||
constexpr Vector4 vector_empty = Vector4();
|
||||
constexpr Vector4 vector_zero = Vector4(0.0, 0.0, 0.0, 0.0);
|
||||
static_assert(
|
||||
vector_empty == vector_zero,
|
||||
"Vector4 Constructor with no inputs should return a zero Vector4.");
|
||||
}
|
||||
|
||||
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[vector.max_axis_index()] == (real_t)5.6,
|
||||
"Vector4 array operator should work as expected.");
|
||||
CHECK_MESSAGE(
|
||||
vector[vector.min_axis_index()] == (real_t)-0.9,
|
||||
"Vector4 array operator 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") {
|
||||
constexpr Vector4 vector1 = Vector4(1, 2, 3, 4);
|
||||
constexpr 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") {
|
||||
constexpr Vector4 vector1 = Vector4(10, 10, 10, 10);
|
||||
constexpr 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(
|
||||
vector1.length() == doctest::Approx(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(
|
||||
vector2.length() == doctest::Approx((real_t)73.484692283495),
|
||||
"Vector4 length should work as expected.");
|
||||
CHECK_MESSAGE(
|
||||
vector1.distance_to(vector2) == doctest::Approx((real_t)54.772255750517),
|
||||
"Vector4 distance_to should work as expected.");
|
||||
CHECK_MESSAGE(
|
||||
vector1.distance_squared_to(vector2) == doctest::Approx(3000),
|
||||
"Vector4 distance_squared_to should work as expected.");
|
||||
}
|
||||
|
||||
TEST_CASE("[Vector4] Limiting methods") {
|
||||
constexpr 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") {
|
||||
constexpr Vector4 decimal1 = Vector4(2.3, 4.9, 7.8, 3.2);
|
||||
constexpr Vector4 decimal2 = Vector4(1.2, 3.4, 5.6, 1.7);
|
||||
constexpr Vector4 power1 = Vector4(0.75, 1.5, 0.625, 0.125);
|
||||
constexpr Vector4 power2 = Vector4(0.5, 0.125, 0.25, 0.75);
|
||||
constexpr Vector4 int1 = Vector4(4, 5, 9, 2);
|
||||
constexpr Vector4 int2 = Vector4(1, 2, 3, 1);
|
||||
|
||||
static_assert(
|
||||
-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.");
|
||||
static_assert(
|
||||
(power1 + power2) == Vector4(1.25, 1.625, 0.875, 0.875),
|
||||
"Vector4 addition with powers of two should give exact results.");
|
||||
static_assert(
|
||||
(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.");
|
||||
static_assert(
|
||||
(power1 - power2) == Vector4(0.25, 1.375, 0.375, -0.625),
|
||||
"Vector4 subtraction with powers of two should give exact results.");
|
||||
static_assert(
|
||||
(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.");
|
||||
static_assert(
|
||||
(power1 * power2) == Vector4(0.375, 0.1875, 0.15625, 0.09375),
|
||||
"Vector4 multiplication with powers of two should give exact results.");
|
||||
static_assert(
|
||||
(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.");
|
||||
static_assert(
|
||||
(power1 / power2) == Vector4(1.5, 12.0, 2.5, 1.0 / 6.0),
|
||||
"Vector4 division with powers of two should give exact results.");
|
||||
static_assert(
|
||||
(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.");
|
||||
static_assert(
|
||||
(power1 * 2) == Vector4(1.5, 3, 1.25, 0.25),
|
||||
"Vector4 multiplication with powers of two should give exact results.");
|
||||
static_assert(
|
||||
(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.");
|
||||
static_assert(
|
||||
(power1 / 2) == Vector4(0.375, 0.75, 0.3125, 0.0625),
|
||||
"Vector4 division with powers of two should give exact results.");
|
||||
static_assert(
|
||||
(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") {
|
||||
constexpr 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.");
|
||||
|
||||
CHECK_MESSAGE(
|
||||
Vector4(1.2, 2.5, 2.0, 1.6).is_equal_approx(vector.min(Vector4(3.0, 2.5, 2.0, 3.4))),
|
||||
"Vector4 min should return expected value.");
|
||||
|
||||
CHECK_MESSAGE(
|
||||
Vector4(5.3, 3.4, 5.6, 4.2).is_equal_approx(vector.max(Vector4(5.3, 2.0, 3.0, 4.2))),
|
||||
"Vector4 max should return expected value.");
|
||||
}
|
||||
|
||||
TEST_CASE("[Vector4] Rounding methods") {
|
||||
constexpr Vector4 vector1 = Vector4(1.2, 3.4, 5.6, 1.6);
|
||||
constexpr 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") {
|
||||
constexpr Vector4 vector_x = Vector4(1, 0, 0, 0);
|
||||
constexpr Vector4 vector_y = Vector4(0, 1, 0, 0);
|
||||
constexpr Vector4 vector1 = Vector4(1.7, 2.3, 1, 9.1);
|
||||
constexpr 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(
|
||||
(vector1 * 2).dot(vector2 * 4) == doctest::Approx((real_t)-25.9 * 8),
|
||||
"Vector4 dot product should work as expected.");
|
||||
}
|
||||
|
||||
TEST_CASE("[Vector4] Finite number checks") {
|
||||
constexpr double infinite[] = { Math::NaN, Math::INF, -Math::INF };
|
||||
|
||||
CHECK_MESSAGE(
|
||||
Vector4(0, 1, 2, 3).is_finite(),
|
||||
"Vector4(0, 1, 2, 3) should be finite");
|
||||
|
||||
for (double x : infinite) {
|
||||
CHECK_FALSE_MESSAGE(
|
||||
Vector4(x, 1, 2, 3).is_finite(),
|
||||
"Vector4 with one component infinite should not be finite.");
|
||||
CHECK_FALSE_MESSAGE(
|
||||
Vector4(0, x, 2, 3).is_finite(),
|
||||
"Vector4 with one component infinite should not be finite.");
|
||||
CHECK_FALSE_MESSAGE(
|
||||
Vector4(0, 1, x, 3).is_finite(),
|
||||
"Vector4 with one component infinite should not be finite.");
|
||||
CHECK_FALSE_MESSAGE(
|
||||
Vector4(0, 1, 2, x).is_finite(),
|
||||
"Vector4 with one component infinite should not be finite.");
|
||||
}
|
||||
|
||||
for (double x : infinite) {
|
||||
for (double y : infinite) {
|
||||
CHECK_FALSE_MESSAGE(
|
||||
Vector4(x, y, 2, 3).is_finite(),
|
||||
"Vector4 with two components infinite should not be finite.");
|
||||
CHECK_FALSE_MESSAGE(
|
||||
Vector4(x, 1, y, 3).is_finite(),
|
||||
"Vector4 with two components infinite should not be finite.");
|
||||
CHECK_FALSE_MESSAGE(
|
||||
Vector4(x, 1, 2, y).is_finite(),
|
||||
"Vector4 with two components infinite should not be finite.");
|
||||
CHECK_FALSE_MESSAGE(
|
||||
Vector4(0, x, y, 3).is_finite(),
|
||||
"Vector4 with two components infinite should not be finite.");
|
||||
CHECK_FALSE_MESSAGE(
|
||||
Vector4(0, x, 2, y).is_finite(),
|
||||
"Vector4 with two components infinite should not be finite.");
|
||||
CHECK_FALSE_MESSAGE(
|
||||
Vector4(0, 1, x, y).is_finite(),
|
||||
"Vector4 with two components infinite should not be finite.");
|
||||
}
|
||||
}
|
||||
|
||||
for (double x : infinite) {
|
||||
for (double y : infinite) {
|
||||
for (double z : infinite) {
|
||||
CHECK_FALSE_MESSAGE(
|
||||
Vector4(0, x, y, z).is_finite(),
|
||||
"Vector4 with three components infinite should not be finite.");
|
||||
CHECK_FALSE_MESSAGE(
|
||||
Vector4(x, 1, y, z).is_finite(),
|
||||
"Vector4 with three components infinite should not be finite.");
|
||||
CHECK_FALSE_MESSAGE(
|
||||
Vector4(x, y, 2, z).is_finite(),
|
||||
"Vector4 with three components infinite should not be finite.");
|
||||
CHECK_FALSE_MESSAGE(
|
||||
Vector4(x, y, z, 3).is_finite(),
|
||||
"Vector4 with three components infinite should not be finite.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (double x : infinite) {
|
||||
for (double y : infinite) {
|
||||
for (double z : infinite) {
|
||||
for (double w : infinite) {
|
||||
CHECK_FALSE_MESSAGE(
|
||||
Vector4(x, y, z, w).is_finite(),
|
||||
"Vector4 with four components infinite should not be finite.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace TestVector4
|
||||
171
tests/core/math/test_vector4i.h
Normal file
@@ -0,0 +1,171 @@
|
||||
/**************************************************************************/
|
||||
/* test_vector4i.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/math/vector4i.h"
|
||||
#include "tests/test_macros.h"
|
||||
|
||||
namespace TestVector4i {
|
||||
|
||||
TEST_CASE("[Vector4i] Constructor methods") {
|
||||
constexpr Vector4i vector_empty = Vector4i();
|
||||
constexpr Vector4i vector_zero = Vector4i(0, 0, 0, 0);
|
||||
static_assert(
|
||||
vector_empty == vector_zero,
|
||||
"Vector4i Constructor with no inputs should return a zero Vector4i.");
|
||||
}
|
||||
|
||||
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[vector.max_axis_index()] == 4,
|
||||
"Vector4i array operator should work as expected.");
|
||||
CHECK_MESSAGE(
|
||||
vector[vector.min_axis_index()] == 1,
|
||||
"Vector4i array operator 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") {
|
||||
constexpr 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") {
|
||||
constexpr Vector4i vector1 = Vector4i(10, 10, 10, 10);
|
||||
constexpr 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(
|
||||
vector1.length() == doctest::Approx(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(
|
||||
vector2.length() == doctest::Approx(73.4846922835),
|
||||
"Vector4i length should work as expected.");
|
||||
CHECK_MESSAGE(
|
||||
vector1.distance_squared_to(vector2) == 3000,
|
||||
"Vector4i distance_squared_to should work as expected.");
|
||||
CHECK_MESSAGE(
|
||||
vector1.distance_to(vector2) == doctest::Approx(54.772255750517),
|
||||
"Vector4i distance_to should work as expected.");
|
||||
}
|
||||
|
||||
TEST_CASE("[Vector4i] Operators") {
|
||||
constexpr Vector4i vector1 = Vector4i(4, 5, 9, 2);
|
||||
constexpr Vector4i vector2 = Vector4i(1, 2, 3, 4);
|
||||
|
||||
static_assert(
|
||||
-vector1 == Vector4i(-4, -5, -9, -2),
|
||||
"Vector4i change of sign should work as expected.");
|
||||
static_assert(
|
||||
(vector1 + vector2) == Vector4i(5, 7, 12, 6),
|
||||
"Vector4i addition with integers should give exact results.");
|
||||
static_assert(
|
||||
(vector1 - vector2) == Vector4i(3, 3, 6, -2),
|
||||
"Vector4i subtraction with integers should give exact results.");
|
||||
static_assert(
|
||||
(vector1 * vector2) == Vector4i(4, 10, 27, 8),
|
||||
"Vector4i multiplication with integers should give exact results.");
|
||||
static_assert(
|
||||
(vector1 / vector2) == Vector4i(4, 2, 3, 0),
|
||||
"Vector4i division with integers should give exact results.");
|
||||
|
||||
static_assert(
|
||||
(vector1 * 2) == Vector4i(8, 10, 18, 4),
|
||||
"Vector4i multiplication with integers should give exact results.");
|
||||
static_assert(
|
||||
(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("[Vector3i] Other methods") {
|
||||
constexpr Vector4i vector = Vector4i(1, 3, -7, 13);
|
||||
|
||||
CHECK_MESSAGE(
|
||||
vector.min(Vector4i(3, 2, 5, 8)) == Vector4i(1, 2, -7, 8),
|
||||
"Vector4i min should return expected value.");
|
||||
|
||||
CHECK_MESSAGE(
|
||||
vector.max(Vector4i(5, 2, 4, 8)) == Vector4i(5, 3, 4, 13),
|
||||
"Vector4i max should return expected value.");
|
||||
|
||||
CHECK_MESSAGE(
|
||||
vector.snapped(Vector4i(4, 2, 5, 8)) == Vector4i(0, 4, -5, 16),
|
||||
"Vector4i snapped should work as expected.");
|
||||
}
|
||||
|
||||
TEST_CASE("[Vector4i] Abs and sign methods") {
|
||||
constexpr Vector4i vector1 = Vector4i(1, 3, 5, 7);
|
||||
constexpr 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
|
||||
898
tests/core/object/test_class_db.h
Normal file
@@ -0,0 +1,898 @@
|
||||
/**************************************************************************/
|
||||
/* test_class_db.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/core_bind.h"
|
||||
#include "core/core_constants.h"
|
||||
#include "core/object/class_db.h"
|
||||
|
||||
#include "tests/test_macros.h"
|
||||
|
||||
namespace TestClassDB {
|
||||
|
||||
struct TypeReference {
|
||||
StringName name;
|
||||
bool is_enum = false;
|
||||
};
|
||||
|
||||
struct ConstantData {
|
||||
String name;
|
||||
int64_t value = 0;
|
||||
};
|
||||
|
||||
struct EnumData {
|
||||
StringName name;
|
||||
List<ConstantData> constants;
|
||||
|
||||
_FORCE_INLINE_ bool operator==(const EnumData &p_enum) const {
|
||||
return p_enum.name == name;
|
||||
}
|
||||
};
|
||||
|
||||
struct PropertyData {
|
||||
StringName name;
|
||||
int index = 0;
|
||||
|
||||
StringName getter;
|
||||
StringName setter;
|
||||
};
|
||||
|
||||
struct ArgumentData {
|
||||
TypeReference type;
|
||||
String name;
|
||||
bool has_defval = false;
|
||||
Variant defval;
|
||||
int position;
|
||||
};
|
||||
|
||||
struct MethodData {
|
||||
StringName name;
|
||||
TypeReference return_type;
|
||||
List<ArgumentData> arguments;
|
||||
bool is_virtual = false;
|
||||
bool is_vararg = false;
|
||||
};
|
||||
|
||||
struct SignalData {
|
||||
StringName name;
|
||||
List<ArgumentData> arguments;
|
||||
};
|
||||
|
||||
struct ExposedClass {
|
||||
StringName name;
|
||||
StringName base;
|
||||
|
||||
bool is_singleton = false;
|
||||
bool is_instantiable = false;
|
||||
bool is_ref_counted = false;
|
||||
|
||||
ClassDB::APIType api_type;
|
||||
|
||||
List<ConstantData> constants;
|
||||
List<EnumData> enums;
|
||||
List<PropertyData> properties;
|
||||
List<MethodData> methods;
|
||||
List<SignalData> signals_;
|
||||
|
||||
const PropertyData *find_property_by_name(const StringName &p_name) const {
|
||||
for (const PropertyData &E : properties) {
|
||||
if (E.name == p_name) {
|
||||
return &E;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const MethodData *find_method_by_name(const StringName &p_name) const {
|
||||
for (const MethodData &E : methods) {
|
||||
if (E.name == p_name) {
|
||||
return &E;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
struct NamesCache {
|
||||
StringName variant_type = StringName("Variant");
|
||||
StringName object_class = StringName("Object");
|
||||
StringName ref_counted_class = StringName("RefCounted");
|
||||
StringName string_type = StringName("String");
|
||||
StringName string_name_type = StringName("StringName");
|
||||
StringName node_path_type = StringName("NodePath");
|
||||
StringName bool_type = StringName("bool");
|
||||
StringName int_type = StringName("int");
|
||||
StringName float_type = StringName("float");
|
||||
StringName void_type = StringName("void");
|
||||
StringName vararg_stub_type = StringName("@VarArg@");
|
||||
StringName vector2_type = StringName("Vector2");
|
||||
StringName rect2_type = StringName("Rect2");
|
||||
StringName vector3_type = StringName("Vector3");
|
||||
StringName vector4_type = StringName("Vector4");
|
||||
|
||||
// Object not included as it must be checked for all derived classes
|
||||
static constexpr int nullable_types_count = 18;
|
||||
StringName nullable_types[nullable_types_count] = {
|
||||
string_type,
|
||||
string_name_type,
|
||||
node_path_type,
|
||||
|
||||
StringName(_STR(Array)),
|
||||
StringName(_STR(Dictionary)),
|
||||
StringName(_STR(Callable)),
|
||||
StringName(_STR(Signal)),
|
||||
|
||||
StringName(_STR(PackedByteArray)),
|
||||
StringName(_STR(PackedInt32Array)),
|
||||
StringName(_STR(PackedInt64rray)),
|
||||
StringName(_STR(PackedFloat32Array)),
|
||||
StringName(_STR(PackedFloat64Array)),
|
||||
StringName(_STR(PackedStringArray)),
|
||||
StringName(_STR(PackedVector2Array)),
|
||||
StringName(_STR(PackedVector3Array)),
|
||||
StringName(_STR(PackedColorArray)),
|
||||
StringName(_STR(PackedVector4Array)),
|
||||
};
|
||||
|
||||
bool is_nullable_type(const StringName &p_type) const {
|
||||
for (int i = 0; i < nullable_types_count; i++) {
|
||||
if (p_type == nullable_types[i]) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
typedef HashMap<StringName, ExposedClass> ExposedClasses;
|
||||
|
||||
struct Context {
|
||||
Vector<StringName> enum_types;
|
||||
Vector<StringName> builtin_types;
|
||||
ExposedClasses exposed_classes;
|
||||
List<EnumData> global_enums;
|
||||
NamesCache names_cache;
|
||||
|
||||
const ExposedClass *find_exposed_class(const StringName &p_name) const {
|
||||
ExposedClasses::ConstIterator elem = exposed_classes.find(p_name);
|
||||
return elem ? &elem->value : nullptr;
|
||||
}
|
||||
|
||||
const ExposedClass *find_exposed_class(const TypeReference &p_type_ref) const {
|
||||
ExposedClasses::ConstIterator elem = exposed_classes.find(p_type_ref.name);
|
||||
return elem ? &elem->value : nullptr;
|
||||
}
|
||||
|
||||
bool has_type(const TypeReference &p_type_ref) const {
|
||||
if (builtin_types.has(p_type_ref.name)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (p_type_ref.is_enum) {
|
||||
if (enum_types.has(p_type_ref.name)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Enum not found. Most likely because none of its constants were bound, so it's empty. That's fine. Use int instead.
|
||||
return builtin_types.find(names_cache.int_type);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
bool arg_default_value_is_assignable_to_type(const Context &p_context, const Variant &p_val, const TypeReference &p_arg_type, String *r_err_msg = nullptr) {
|
||||
if (p_arg_type.name == p_context.names_cache.variant_type) {
|
||||
// Variant can take anything
|
||||
return true;
|
||||
}
|
||||
|
||||
switch (p_val.get_type()) {
|
||||
case Variant::NIL:
|
||||
return p_context.find_exposed_class(p_arg_type) ||
|
||||
p_context.names_cache.is_nullable_type(p_arg_type.name);
|
||||
case Variant::BOOL:
|
||||
return p_arg_type.name == p_context.names_cache.bool_type;
|
||||
case Variant::INT:
|
||||
return p_arg_type.name == p_context.names_cache.int_type ||
|
||||
p_arg_type.name == p_context.names_cache.float_type ||
|
||||
p_arg_type.is_enum;
|
||||
case Variant::FLOAT:
|
||||
return p_arg_type.name == p_context.names_cache.float_type;
|
||||
case Variant::STRING:
|
||||
case Variant::STRING_NAME:
|
||||
return p_arg_type.name == p_context.names_cache.string_type ||
|
||||
p_arg_type.name == p_context.names_cache.string_name_type ||
|
||||
p_arg_type.name == p_context.names_cache.node_path_type;
|
||||
case Variant::NODE_PATH:
|
||||
return p_arg_type.name == p_context.names_cache.node_path_type;
|
||||
case Variant::TRANSFORM3D:
|
||||
case Variant::TRANSFORM2D:
|
||||
case Variant::BASIS:
|
||||
case Variant::QUATERNION:
|
||||
case Variant::PLANE:
|
||||
case Variant::AABB:
|
||||
case Variant::COLOR:
|
||||
case Variant::VECTOR2:
|
||||
case Variant::RECT2:
|
||||
case Variant::VECTOR3:
|
||||
case Variant::VECTOR4:
|
||||
case Variant::PROJECTION:
|
||||
case Variant::RID:
|
||||
case Variant::ARRAY:
|
||||
case Variant::DICTIONARY:
|
||||
case Variant::PACKED_BYTE_ARRAY:
|
||||
case Variant::PACKED_INT32_ARRAY:
|
||||
case Variant::PACKED_INT64_ARRAY:
|
||||
case Variant::PACKED_FLOAT32_ARRAY:
|
||||
case Variant::PACKED_FLOAT64_ARRAY:
|
||||
case Variant::PACKED_STRING_ARRAY:
|
||||
case Variant::PACKED_VECTOR2_ARRAY:
|
||||
case Variant::PACKED_VECTOR3_ARRAY:
|
||||
case Variant::PACKED_COLOR_ARRAY:
|
||||
case Variant::PACKED_VECTOR4_ARRAY:
|
||||
case Variant::CALLABLE:
|
||||
case Variant::SIGNAL:
|
||||
return p_arg_type.name == Variant::get_type_name(p_val.get_type());
|
||||
case Variant::OBJECT:
|
||||
return p_context.find_exposed_class(p_arg_type);
|
||||
case Variant::VECTOR2I:
|
||||
return p_arg_type.name == p_context.names_cache.vector2_type ||
|
||||
p_arg_type.name == Variant::get_type_name(p_val.get_type());
|
||||
case Variant::RECT2I:
|
||||
return p_arg_type.name == p_context.names_cache.rect2_type ||
|
||||
p_arg_type.name == Variant::get_type_name(p_val.get_type());
|
||||
case Variant::VECTOR3I:
|
||||
return p_arg_type.name == p_context.names_cache.vector3_type ||
|
||||
p_arg_type.name == Variant::get_type_name(p_val.get_type());
|
||||
case Variant::VECTOR4I:
|
||||
return p_arg_type.name == p_context.names_cache.vector4_type ||
|
||||
p_arg_type.name == Variant::get_type_name(p_val.get_type());
|
||||
case Variant::VARIANT_MAX:
|
||||
break;
|
||||
}
|
||||
if (r_err_msg) {
|
||||
*r_err_msg = "Unexpected Variant type: " + itos(p_val.get_type());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool arg_default_value_is_valid_data(const Variant &p_val, String *r_err_msg = nullptr) {
|
||||
switch (p_val.get_type()) {
|
||||
case Variant::RID:
|
||||
case Variant::ARRAY:
|
||||
case Variant::DICTIONARY:
|
||||
case Variant::PACKED_BYTE_ARRAY:
|
||||
case Variant::PACKED_INT32_ARRAY:
|
||||
case Variant::PACKED_INT64_ARRAY:
|
||||
case Variant::PACKED_FLOAT32_ARRAY:
|
||||
case Variant::PACKED_FLOAT64_ARRAY:
|
||||
case Variant::PACKED_STRING_ARRAY:
|
||||
case Variant::PACKED_VECTOR2_ARRAY:
|
||||
case Variant::PACKED_VECTOR3_ARRAY:
|
||||
case Variant::PACKED_COLOR_ARRAY:
|
||||
case Variant::PACKED_VECTOR4_ARRAY:
|
||||
case Variant::CALLABLE:
|
||||
case Variant::SIGNAL:
|
||||
case Variant::OBJECT:
|
||||
if (p_val.is_zero()) {
|
||||
return true;
|
||||
}
|
||||
if (r_err_msg) {
|
||||
*r_err_msg = "Must be zero.";
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void validate_property(const Context &p_context, const ExposedClass &p_class, const PropertyData &p_prop) {
|
||||
const MethodData *setter = p_class.find_method_by_name(p_prop.setter);
|
||||
|
||||
// Search it in base classes too
|
||||
const ExposedClass *top = &p_class;
|
||||
while (!setter && top->base != StringName()) {
|
||||
top = p_context.find_exposed_class(top->base);
|
||||
TEST_FAIL_COND(!top, "Class not found '", top->base, "'. Inherited by '", top->name, "'.");
|
||||
setter = top->find_method_by_name(p_prop.setter);
|
||||
}
|
||||
|
||||
const MethodData *getter = p_class.find_method_by_name(p_prop.getter);
|
||||
|
||||
// Search it in base classes too
|
||||
top = &p_class;
|
||||
while (!getter && top->base != StringName()) {
|
||||
top = p_context.find_exposed_class(top->base);
|
||||
TEST_FAIL_COND(!top, "Class not found '", top->base, "'. Inherited by '", top->name, "'.");
|
||||
getter = top->find_method_by_name(p_prop.getter);
|
||||
}
|
||||
|
||||
TEST_FAIL_COND((!setter && !getter),
|
||||
"Couldn't find neither the setter nor the getter for property: '", p_class.name, ".", String(p_prop.name), "'.");
|
||||
|
||||
if (setter) {
|
||||
int setter_argc = p_prop.index != -1 ? 2 : 1;
|
||||
TEST_FAIL_COND(setter->arguments.size() != setter_argc,
|
||||
"Invalid property setter argument count: '", p_class.name, ".", String(p_prop.name), "'.");
|
||||
}
|
||||
|
||||
if (getter) {
|
||||
int getter_argc = p_prop.index != -1 ? 1 : 0;
|
||||
TEST_FAIL_COND(getter->arguments.size() != getter_argc,
|
||||
"Invalid property setter argument count: '", p_class.name, ".", String(p_prop.name), "'.");
|
||||
}
|
||||
|
||||
if (getter && setter) {
|
||||
const ArgumentData &setter_first_arg = setter->arguments.back()->get();
|
||||
if (getter->return_type.name != setter_first_arg.type.name) {
|
||||
TEST_FAIL(
|
||||
"Return type from getter doesn't match first argument of setter, for property: '", p_class.name, ".", String(p_prop.name), "'.");
|
||||
}
|
||||
}
|
||||
|
||||
const TypeReference &prop_type_ref = getter ? getter->return_type : setter->arguments.back()->get().type;
|
||||
|
||||
const ExposedClass *prop_class = p_context.find_exposed_class(prop_type_ref);
|
||||
if (prop_class) {
|
||||
TEST_COND(prop_class->is_singleton,
|
||||
"Property type is a singleton: '", p_class.name, ".", String(p_prop.name), "'.");
|
||||
|
||||
if (p_class.api_type == ClassDB::API_CORE) {
|
||||
TEST_COND(prop_class->api_type == ClassDB::API_EDITOR,
|
||||
"Property '", p_class.name, ".", p_prop.name, "' has type '", prop_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(prop_type_ref),
|
||||
"Property type '", prop_type_ref.name, "' not found: '", p_class.name, ".", String(p_prop.name), "'.");
|
||||
}
|
||||
|
||||
if (getter) {
|
||||
if (p_prop.index != -1) {
|
||||
const ArgumentData &idx_arg = getter->arguments.front()->get();
|
||||
if (idx_arg.type.name != p_context.names_cache.int_type) {
|
||||
// If not an int, it can be an enum
|
||||
TEST_COND(!p_context.enum_types.has(idx_arg.type.name),
|
||||
"Invalid type '", idx_arg.type.name, "' for index argument of property getter: '", p_class.name, ".", String(p_prop.name), "'.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (setter) {
|
||||
if (p_prop.index != -1) {
|
||||
const ArgumentData &idx_arg = setter->arguments.front()->get();
|
||||
if (idx_arg.type.name != p_context.names_cache.int_type) {
|
||||
// Assume the index parameter is an enum
|
||||
// If not an int, it can be an enum
|
||||
TEST_COND(!p_context.enum_types.has(idx_arg.type.name),
|
||||
"Invalid type '", idx_arg.type.name, "' for index argument of property setter: '", p_class.name, ".", String(p_prop.name), "'.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
#ifdef DEBUG_ENABLED
|
||||
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));
|
||||
|
||||
TEST_FAIL_COND((p_arg.name != "@varargs@" && !p_arg.name.is_valid_ascii_identifier()),
|
||||
vformat("Invalid argument name '%s' of %s '%s.%s'.", p_arg.name, p_owner_type, p_class.name, p_owner_name));
|
||||
#endif // DEBUG_ENABLED
|
||||
|
||||
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);
|
||||
|
||||
bool arg_defval_valid_data = arg_default_value_is_valid_data(p_arg.defval, &type_error_msg);
|
||||
|
||||
if (!type_error_msg.is_empty()) {
|
||||
err_msg += " " + type_error_msg;
|
||||
}
|
||||
|
||||
TEST_COND(!arg_defval_valid_data, err_msg);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
if (return_class) {
|
||||
if (p_class.api_type == ClassDB::API_CORE) {
|
||||
TEST_COND(return_class->api_type == ClassDB::API_EDITOR,
|
||||
"Method '", p_class.name, ".", p_method.name, "' has return type '", return_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(p_method.return_type),
|
||||
"Method return type '", p_method.return_type.name, "' not found: '", p_class.name, ".", p_method.name, "'.");
|
||||
}
|
||||
}
|
||||
|
||||
for (const ArgumentData &F : p_method.arguments) {
|
||||
const ArgumentData &arg = F;
|
||||
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;
|
||||
validate_argument(p_context, p_class, p_signal.name, "signal", arg);
|
||||
}
|
||||
}
|
||||
|
||||
void validate_class(const Context &p_context, const ExposedClass &p_exposed_class) {
|
||||
bool is_derived_type = p_exposed_class.base != StringName();
|
||||
|
||||
if (!is_derived_type) {
|
||||
// Asserts about the base Object class
|
||||
TEST_FAIL_COND(p_exposed_class.name != p_context.names_cache.object_class,
|
||||
"Class '", p_exposed_class.name, "' has no base class.");
|
||||
TEST_FAIL_COND(!p_exposed_class.is_instantiable,
|
||||
"Object class is not instantiable.");
|
||||
TEST_FAIL_COND(p_exposed_class.api_type != ClassDB::API_CORE,
|
||||
"Object class is API is not API_CORE.");
|
||||
TEST_FAIL_COND(p_exposed_class.is_singleton,
|
||||
"Object class is registered as a singleton.");
|
||||
}
|
||||
|
||||
TEST_FAIL_COND((p_exposed_class.is_singleton && p_exposed_class.base != p_context.names_cache.object_class),
|
||||
"Singleton base class '", String(p_exposed_class.base), "' is not Object, for class '", p_exposed_class.name, "'.");
|
||||
|
||||
TEST_FAIL_COND((is_derived_type && !p_context.exposed_classes.has(p_exposed_class.base)),
|
||||
"Base type '", p_exposed_class.base.operator String(), "' does not exist, for class '", p_exposed_class.name, "'.");
|
||||
|
||||
for (const PropertyData &F : p_exposed_class.properties) {
|
||||
validate_property(p_context, p_exposed_class, F);
|
||||
}
|
||||
|
||||
for (const MethodData &F : p_exposed_class.methods) {
|
||||
validate_method(p_context, p_exposed_class, F);
|
||||
}
|
||||
|
||||
for (const SignalData &F : p_exposed_class.signals_) {
|
||||
validate_signal(p_context, p_exposed_class, F);
|
||||
}
|
||||
}
|
||||
|
||||
void add_exposed_classes(Context &r_context) {
|
||||
List<StringName> class_list;
|
||||
ClassDB::get_class_list(&class_list);
|
||||
class_list.sort_custom<StringName::AlphCompare>();
|
||||
|
||||
while (class_list.size()) {
|
||||
StringName class_name = class_list.front()->get();
|
||||
|
||||
ClassDB::APIType api_type = ClassDB::get_api_type(class_name);
|
||||
|
||||
if (api_type == ClassDB::API_NONE) {
|
||||
class_list.pop_front();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!ClassDB::is_class_exposed(class_name)) {
|
||||
INFO(vformat("Ignoring class '%s' because it's not exposed.", class_name));
|
||||
class_list.pop_front();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!ClassDB::is_class_enabled(class_name)) {
|
||||
INFO(vformat("Ignoring class '%s' because it's not enabled.", class_name));
|
||||
class_list.pop_front();
|
||||
continue;
|
||||
}
|
||||
|
||||
ClassDB::ClassInfo *class_info = ClassDB::classes.getptr(class_name);
|
||||
|
||||
ExposedClass exposed_class;
|
||||
exposed_class.name = class_name;
|
||||
exposed_class.api_type = api_type;
|
||||
exposed_class.is_singleton = Engine::get_singleton()->has_singleton(class_name);
|
||||
exposed_class.is_instantiable = class_info->creation_func && !exposed_class.is_singleton;
|
||||
exposed_class.is_ref_counted = ClassDB::is_parent_class(class_name, "RefCounted");
|
||||
exposed_class.base = ClassDB::get_parent_class(class_name);
|
||||
|
||||
// Add properties
|
||||
|
||||
List<PropertyInfo> property_list;
|
||||
ClassDB::get_property_list(class_name, &property_list, true);
|
||||
|
||||
HashMap<StringName, StringName> accessor_methods;
|
||||
|
||||
for (const PropertyInfo &property : property_list) {
|
||||
if (property.usage & PROPERTY_USAGE_GROUP || property.usage & PROPERTY_USAGE_SUBGROUP || property.usage & PROPERTY_USAGE_CATEGORY || (property.type == Variant::NIL && property.usage & PROPERTY_USAGE_ARRAY)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
PropertyData prop;
|
||||
prop.name = property.name;
|
||||
prop.setter = ClassDB::get_property_setter(class_name, prop.name);
|
||||
prop.getter = ClassDB::get_property_getter(class_name, prop.name);
|
||||
|
||||
if (prop.setter != StringName()) {
|
||||
accessor_methods[prop.setter] = prop.name;
|
||||
}
|
||||
if (prop.getter != StringName()) {
|
||||
accessor_methods[prop.getter] = prop.name;
|
||||
}
|
||||
|
||||
bool valid = false;
|
||||
prop.index = ClassDB::get_property_index(class_name, prop.name, &valid);
|
||||
TEST_FAIL_COND(!valid, "Invalid property: '", exposed_class.name, ".", String(prop.name), "'.");
|
||||
|
||||
exposed_class.properties.push_back(prop);
|
||||
}
|
||||
|
||||
// Add methods
|
||||
|
||||
List<MethodInfo> virtual_method_list;
|
||||
ClassDB::get_virtual_methods(class_name, &virtual_method_list, true);
|
||||
|
||||
List<MethodInfo> method_list;
|
||||
ClassDB::get_method_list(class_name, &method_list, true);
|
||||
method_list.sort();
|
||||
|
||||
for (const MethodInfo &E : method_list) {
|
||||
const MethodInfo &method_info = E;
|
||||
|
||||
if (method_info.name.is_empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
MethodData method;
|
||||
method.name = method_info.name;
|
||||
TEST_FAIL_COND(!String(method.name).is_valid_ascii_identifier(),
|
||||
"Method name is not a valid identifier: '", exposed_class.name, ".", method.name, "'.");
|
||||
|
||||
if (method_info.flags & METHOD_FLAG_VIRTUAL) {
|
||||
method.is_virtual = true;
|
||||
}
|
||||
|
||||
PropertyInfo return_info = method_info.return_val;
|
||||
|
||||
MethodBind *m = method.is_virtual ? nullptr : ClassDB::get_method(class_name, method_info.name);
|
||||
|
||||
method.is_vararg = m && m->is_vararg();
|
||||
|
||||
if (!m && !method.is_virtual) {
|
||||
TEST_FAIL_COND(!virtual_method_list.find(method_info),
|
||||
"Missing MethodBind for non-virtual method: '", exposed_class.name, ".", method.name, "'.");
|
||||
|
||||
// A virtual method without the virtual flag. This is a special case.
|
||||
|
||||
// The method Object.free is registered as a virtual method, but without the virtual flag.
|
||||
// This is because this method is not supposed to be overridden, but called.
|
||||
// We assume the return type is void.
|
||||
method.return_type.name = r_context.names_cache.void_type;
|
||||
|
||||
// Actually, more methods like this may be added in the future, which could return
|
||||
// something different. Let's put this check to notify us if that ever happens.
|
||||
String warn_msg = vformat(
|
||||
"Notification: New unexpected virtual non-overridable method found. "
|
||||
"We only expected Object.free, but found '%s.%s'.",
|
||||
exposed_class.name, method.name);
|
||||
TEST_FAIL_COND_WARN(
|
||||
(exposed_class.name != r_context.names_cache.object_class || String(method.name) != "free"),
|
||||
warn_msg);
|
||||
|
||||
} else if (return_info.type == Variant::INT && return_info.usage & (PROPERTY_USAGE_CLASS_IS_ENUM | PROPERTY_USAGE_CLASS_IS_BITFIELD)) {
|
||||
method.return_type.name = return_info.class_name;
|
||||
method.return_type.is_enum = true;
|
||||
} else if (return_info.class_name != StringName()) {
|
||||
method.return_type.name = return_info.class_name;
|
||||
|
||||
bool bad_reference_hint = !method.is_virtual && return_info.hint != PROPERTY_HINT_RESOURCE_TYPE &&
|
||||
ClassDB::is_parent_class(return_info.class_name, r_context.names_cache.ref_counted_class);
|
||||
TEST_COND(bad_reference_hint, "Return type is reference but hint is not '" _STR(PROPERTY_HINT_RESOURCE_TYPE) "'.", " Are you returning a reference type by pointer? Method: '",
|
||||
exposed_class.name, ".", method.name, "'.");
|
||||
} else if (return_info.hint == PROPERTY_HINT_RESOURCE_TYPE) {
|
||||
method.return_type.name = return_info.hint_string;
|
||||
} else if (return_info.type == Variant::NIL && return_info.usage & PROPERTY_USAGE_NIL_IS_VARIANT) {
|
||||
method.return_type.name = r_context.names_cache.variant_type;
|
||||
} else if (return_info.type == Variant::NIL) {
|
||||
method.return_type.name = r_context.names_cache.void_type;
|
||||
} else {
|
||||
// NOTE: We don't care about the size and sign of int and float in these tests
|
||||
method.return_type.name = Variant::get_type_name(return_info.type);
|
||||
}
|
||||
|
||||
for (int64_t i = 0; i < method_info.arguments.size(); ++i) {
|
||||
const PropertyInfo &arg_info = method_info.arguments[i];
|
||||
|
||||
String orig_arg_name = arg_info.name;
|
||||
|
||||
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;
|
||||
arg.type.is_enum = true;
|
||||
} else if (arg_info.class_name != StringName()) {
|
||||
arg.type.name = arg_info.class_name;
|
||||
} else if (arg_info.hint == PROPERTY_HINT_RESOURCE_TYPE) {
|
||||
arg.type.name = arg_info.hint_string;
|
||||
} else if (arg_info.type == Variant::NIL) {
|
||||
arg.type.name = r_context.names_cache.variant_type;
|
||||
} else {
|
||||
// NOTE: We don't care about the size and sign of int and float in these tests
|
||||
arg.type.name = Variant::get_type_name(arg_info.type);
|
||||
}
|
||||
|
||||
if (m && m->has_default_argument(i)) {
|
||||
arg.has_defval = true;
|
||||
arg.defval = m->get_default_argument(i);
|
||||
}
|
||||
|
||||
method.arguments.push_back(arg);
|
||||
}
|
||||
|
||||
if (method.is_vararg) {
|
||||
ArgumentData vararg;
|
||||
vararg.type.name = r_context.names_cache.vararg_stub_type;
|
||||
vararg.name = "@varargs@";
|
||||
method.arguments.push_back(vararg);
|
||||
}
|
||||
|
||||
TEST_COND(exposed_class.find_property_by_name(method.name),
|
||||
"Method name conflicts with property: '", String(class_name), ".", String(method.name), "'.");
|
||||
|
||||
// Methods starting with an underscore are ignored unless they're virtual or used as a property setter or getter.
|
||||
if (!method.is_virtual && String(method.name)[0] == '_') {
|
||||
for (const PropertyData &F : exposed_class.properties) {
|
||||
const PropertyData &prop = F;
|
||||
|
||||
if (prop.setter == method.name || prop.getter == method.name) {
|
||||
exposed_class.methods.push_back(method);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
exposed_class.methods.push_back(method);
|
||||
}
|
||||
|
||||
if (method.is_virtual) {
|
||||
TEST_COND(String(method.name)[0] != '_', "Virtual method ", String(method.name), " does not start with underscore.");
|
||||
}
|
||||
}
|
||||
|
||||
// Add signals
|
||||
|
||||
const HashMap<StringName, MethodInfo> &signal_map = class_info->signal_map;
|
||||
|
||||
for (const KeyValue<StringName, MethodInfo> &K : signal_map) {
|
||||
SignalData signal;
|
||||
|
||||
const MethodInfo &method_info = signal_map.get(K.key);
|
||||
|
||||
signal.name = method_info.name;
|
||||
TEST_FAIL_COND(!String(signal.name).is_valid_ascii_identifier(),
|
||||
"Signal name is not a valid identifier: '", exposed_class.name, ".", signal.name, "'.");
|
||||
|
||||
for (int64_t i = 0; i < method_info.arguments.size(); ++i) {
|
||||
const PropertyInfo &arg_info = method_info.arguments[i];
|
||||
|
||||
String orig_arg_name = arg_info.name;
|
||||
|
||||
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;
|
||||
arg.type.is_enum = true;
|
||||
} else if (arg_info.class_name != StringName()) {
|
||||
arg.type.name = arg_info.class_name;
|
||||
} else if (arg_info.hint == PROPERTY_HINT_RESOURCE_TYPE) {
|
||||
arg.type.name = arg_info.hint_string;
|
||||
} else if (arg_info.type == Variant::NIL) {
|
||||
arg.type.name = r_context.names_cache.variant_type;
|
||||
} else {
|
||||
// NOTE: We don't care about the size and sign of int and float in these tests
|
||||
arg.type.name = Variant::get_type_name(arg_info.type);
|
||||
}
|
||||
|
||||
signal.arguments.push_back(arg);
|
||||
}
|
||||
|
||||
bool method_conflict = exposed_class.find_property_by_name(signal.name);
|
||||
|
||||
String warn_msg = vformat(
|
||||
"Signal name conflicts with %s: '%s.%s.",
|
||||
method_conflict ? "method" : "property", class_name, signal.name);
|
||||
TEST_FAIL_COND((method_conflict || exposed_class.find_method_by_name(signal.name)),
|
||||
warn_msg);
|
||||
|
||||
exposed_class.signals_.push_back(signal);
|
||||
}
|
||||
|
||||
// Add enums and constants
|
||||
|
||||
List<String> constants;
|
||||
ClassDB::get_integer_constant_list(class_name, &constants, true);
|
||||
|
||||
const HashMap<StringName, ClassDB::ClassInfo::EnumInfo> &enum_map = class_info->enum_map;
|
||||
|
||||
for (const KeyValue<StringName, ClassDB::ClassInfo::EnumInfo> &K : enum_map) {
|
||||
EnumData enum_;
|
||||
enum_.name = K.key;
|
||||
|
||||
for (const StringName &E : K.value.constants) {
|
||||
const StringName &constant_name = E;
|
||||
TEST_FAIL_COND(String(constant_name).contains("::"),
|
||||
"Enum constant contains '::', check bindings to remove the scope: '",
|
||||
String(class_name), ".", String(enum_.name), ".", String(constant_name), "'.");
|
||||
int64_t *value = class_info->constant_map.getptr(constant_name);
|
||||
TEST_FAIL_COND(!value, "Missing enum constant value: '",
|
||||
String(class_name), ".", String(enum_.name), ".", String(constant_name), "'.");
|
||||
constants.erase(constant_name);
|
||||
|
||||
ConstantData constant;
|
||||
constant.name = constant_name;
|
||||
constant.value = *value;
|
||||
|
||||
enum_.constants.push_back(constant);
|
||||
}
|
||||
|
||||
exposed_class.enums.push_back(enum_);
|
||||
|
||||
r_context.enum_types.push_back(String(class_name) + "." + String(K.key));
|
||||
}
|
||||
|
||||
for (const String &E : constants) {
|
||||
const String &constant_name = E;
|
||||
TEST_FAIL_COND(constant_name.contains("::"),
|
||||
"Constant contains '::', check bindings to remove the scope: '",
|
||||
String(class_name), ".", constant_name, "'.");
|
||||
int64_t *value = class_info->constant_map.getptr(StringName(E));
|
||||
TEST_FAIL_COND(!value, "Missing constant value: '", String(class_name), ".", String(constant_name), "'.");
|
||||
|
||||
ConstantData constant;
|
||||
constant.name = constant_name;
|
||||
constant.value = *value;
|
||||
|
||||
exposed_class.constants.push_back(constant);
|
||||
}
|
||||
|
||||
r_context.exposed_classes.insert(class_name, exposed_class);
|
||||
class_list.pop_front();
|
||||
}
|
||||
}
|
||||
|
||||
void add_builtin_types(Context &r_context) {
|
||||
// NOTE: We don't care about the size and sign of int and float in these tests
|
||||
for (int i = 0; i < Variant::VARIANT_MAX; i++) {
|
||||
r_context.builtin_types.push_back(Variant::get_type_name(Variant::Type(i)));
|
||||
}
|
||||
|
||||
r_context.builtin_types.push_back(_STR(Variant));
|
||||
r_context.builtin_types.push_back(r_context.names_cache.vararg_stub_type);
|
||||
r_context.builtin_types.push_back("void");
|
||||
}
|
||||
|
||||
void add_global_enums(Context &r_context) {
|
||||
int global_constants_count = CoreConstants::get_global_constant_count();
|
||||
|
||||
if (global_constants_count > 0) {
|
||||
for (int i = 0; i < global_constants_count; i++) {
|
||||
StringName enum_name = CoreConstants::get_global_constant_enum(i);
|
||||
|
||||
if (enum_name != StringName()) {
|
||||
ConstantData constant;
|
||||
constant.name = CoreConstants::get_global_constant_name(i);
|
||||
constant.value = CoreConstants::get_global_constant_value(i);
|
||||
|
||||
EnumData enum_;
|
||||
enum_.name = enum_name;
|
||||
List<EnumData>::Element *enum_match = r_context.global_enums.find(enum_);
|
||||
if (enum_match) {
|
||||
enum_match->get().constants.push_back(constant);
|
||||
} else {
|
||||
enum_.constants.push_back(constant);
|
||||
r_context.global_enums.push_back(enum_);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const EnumData &E : r_context.global_enums) {
|
||||
r_context.enum_types.push_back(E.name);
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < Variant::VARIANT_MAX; i++) {
|
||||
if (i == Variant::OBJECT) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const Variant::Type type = Variant::Type(i);
|
||||
|
||||
List<StringName> enum_names;
|
||||
Variant::get_enums_for_type(type, &enum_names);
|
||||
|
||||
for (const StringName &enum_name : enum_names) {
|
||||
r_context.enum_types.push_back(Variant::get_type_name(type) + "." + enum_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_SUITE("[ClassDB]") {
|
||||
TEST_CASE("[ClassDB] Add exposed classes, builtin types, and global enums") {
|
||||
Context context;
|
||||
|
||||
add_exposed_classes(context);
|
||||
add_builtin_types(context);
|
||||
add_global_enums(context);
|
||||
|
||||
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(),
|
||||
"Object class derives from another class: '", object_class->base, "'.");
|
||||
|
||||
for (const KeyValue<StringName, ExposedClass> &E : context.exposed_classes) {
|
||||
validate_class(context, E.value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace TestClassDB
|
||||
174
tests/core/object/test_method_bind.h
Normal file
@@ -0,0 +1,174 @@
|
||||
/**************************************************************************/
|
||||
/* test_method_bind.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/object/class_db.h"
|
||||
|
||||
#include "tests/test_macros.h"
|
||||
|
||||
namespace TestMethodBind {
|
||||
|
||||
class MethodBindTester : public Object {
|
||||
GDCLASS(MethodBindTester, Object);
|
||||
|
||||
public:
|
||||
enum Test {
|
||||
TEST_METHOD,
|
||||
TEST_METHOD_ARGS,
|
||||
TEST_METHODC,
|
||||
TEST_METHODC_ARGS,
|
||||
TEST_METHODR,
|
||||
TEST_METHODR_ARGS,
|
||||
TEST_METHODRC,
|
||||
TEST_METHODRC_ARGS,
|
||||
TEST_METHOD_DEFARGS,
|
||||
TEST_METHOD_OBJECT_CAST,
|
||||
TEST_MAX
|
||||
};
|
||||
|
||||
class ObjectSubclass : public Object {
|
||||
GDSOFTCLASS(ObjectSubclass, Object);
|
||||
|
||||
public:
|
||||
int value = 1;
|
||||
};
|
||||
|
||||
int test_num = 0;
|
||||
|
||||
bool test_valid[TEST_MAX];
|
||||
|
||||
void test_method() {
|
||||
test_valid[TEST_METHOD] = true;
|
||||
}
|
||||
|
||||
void test_method_args(int p_arg) {
|
||||
test_valid[TEST_METHOD_ARGS] = p_arg == test_num;
|
||||
}
|
||||
|
||||
void test_methodc() {
|
||||
test_valid[TEST_METHODC] = true;
|
||||
}
|
||||
|
||||
void test_methodc_args(int p_arg) {
|
||||
test_valid[TEST_METHODC_ARGS] = p_arg == test_num;
|
||||
}
|
||||
|
||||
int test_methodr() {
|
||||
test_valid[TEST_METHODR] = true; //temporary
|
||||
return test_num;
|
||||
}
|
||||
|
||||
int test_methodr_args(int p_arg) {
|
||||
test_valid[TEST_METHODR_ARGS] = true; //temporary
|
||||
return p_arg;
|
||||
}
|
||||
|
||||
int test_methodrc() {
|
||||
test_valid[TEST_METHODRC] = true; //temporary
|
||||
return test_num;
|
||||
}
|
||||
|
||||
int test_methodrc_args(int p_arg) {
|
||||
test_valid[TEST_METHODRC_ARGS] = true; //temporary
|
||||
return p_arg;
|
||||
}
|
||||
|
||||
void test_method_default_args(int p_arg1, int p_arg2, int p_arg3, int p_arg4, int p_arg5) {
|
||||
test_valid[TEST_METHOD_DEFARGS] = p_arg1 == 1 && p_arg2 == 2 && p_arg3 == 3 && p_arg4 == 4 && p_arg5 == 5; //temporary
|
||||
}
|
||||
|
||||
void test_method_object_cast(ObjectSubclass *p_object) {
|
||||
test_valid[TEST_METHOD_OBJECT_CAST] = p_object->value == 1;
|
||||
}
|
||||
|
||||
static void _bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("test_method"), &MethodBindTester::test_method);
|
||||
ClassDB::bind_method(D_METHOD("test_method_args"), &MethodBindTester::test_method_args);
|
||||
ClassDB::bind_method(D_METHOD("test_methodc"), &MethodBindTester::test_methodc);
|
||||
ClassDB::bind_method(D_METHOD("test_methodc_args"), &MethodBindTester::test_methodc_args);
|
||||
ClassDB::bind_method(D_METHOD("test_methodr"), &MethodBindTester::test_methodr);
|
||||
ClassDB::bind_method(D_METHOD("test_methodr_args"), &MethodBindTester::test_methodr_args);
|
||||
ClassDB::bind_method(D_METHOD("test_methodrc"), &MethodBindTester::test_methodrc);
|
||||
ClassDB::bind_method(D_METHOD("test_methodrc_args"), &MethodBindTester::test_methodrc_args);
|
||||
ClassDB::bind_method(D_METHOD("test_method_default_args"), &MethodBindTester::test_method_default_args, DEFVAL(9) /* wrong on purpose */, DEFVAL(4), DEFVAL(5));
|
||||
ClassDB::bind_method(D_METHOD("test_method_object_cast", "object"), &MethodBindTester::test_method_object_cast);
|
||||
}
|
||||
|
||||
virtual void run_tests() {
|
||||
for (int i = 0; i < TEST_MAX; i++) {
|
||||
test_valid[i] = false;
|
||||
}
|
||||
//regular
|
||||
test_num = Math::rand();
|
||||
call("test_method");
|
||||
test_num = Math::rand();
|
||||
call("test_method_args", test_num);
|
||||
test_num = Math::rand();
|
||||
call("test_methodc");
|
||||
test_num = Math::rand();
|
||||
call("test_methodc_args", test_num);
|
||||
//return
|
||||
test_num = Math::rand();
|
||||
test_valid[TEST_METHODR] = int(call("test_methodr")) == test_num && test_valid[TEST_METHODR];
|
||||
test_num = Math::rand();
|
||||
test_valid[TEST_METHODR_ARGS] = int(call("test_methodr_args", test_num)) == test_num && test_valid[TEST_METHODR_ARGS];
|
||||
test_num = Math::rand();
|
||||
test_valid[TEST_METHODRC] = int(call("test_methodrc")) == test_num && test_valid[TEST_METHODRC];
|
||||
test_num = Math::rand();
|
||||
test_valid[TEST_METHODRC_ARGS] = int(call("test_methodrc_args", test_num)) == test_num && test_valid[TEST_METHODRC_ARGS];
|
||||
|
||||
call("test_method_default_args", 1, 2, 3, 4);
|
||||
|
||||
ObjectSubclass *obj = memnew(ObjectSubclass);
|
||||
call("test_method_object_cast", obj);
|
||||
memdelete(obj);
|
||||
}
|
||||
};
|
||||
|
||||
TEST_CASE("[MethodBind] check all method binds") {
|
||||
MethodBindTester *mbt = memnew(MethodBindTester);
|
||||
|
||||
mbt->run_tests();
|
||||
|
||||
CHECK(mbt->test_valid[MethodBindTester::TEST_METHOD]);
|
||||
CHECK(mbt->test_valid[MethodBindTester::TEST_METHOD_ARGS]);
|
||||
CHECK(mbt->test_valid[MethodBindTester::TEST_METHODC]);
|
||||
CHECK(mbt->test_valid[MethodBindTester::TEST_METHODC_ARGS]);
|
||||
CHECK(mbt->test_valid[MethodBindTester::TEST_METHODR]);
|
||||
CHECK(mbt->test_valid[MethodBindTester::TEST_METHODR_ARGS]);
|
||||
CHECK(mbt->test_valid[MethodBindTester::TEST_METHODRC]);
|
||||
CHECK(mbt->test_valid[MethodBindTester::TEST_METHODRC_ARGS]);
|
||||
CHECK(mbt->test_valid[MethodBindTester::TEST_METHOD_DEFARGS]);
|
||||
CHECK(mbt->test_valid[MethodBindTester::TEST_METHOD_OBJECT_CAST]);
|
||||
|
||||
memdelete(mbt);
|
||||
}
|
||||
} // namespace TestMethodBind
|
||||
597
tests/core/object/test_object.h
Normal file
@@ -0,0 +1,597 @@
|
||||
/**************************************************************************/
|
||||
/* test_object.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/object/class_db.h"
|
||||
#include "core/object/object.h"
|
||||
#include "core/object/script_language.h"
|
||||
|
||||
#include "tests/test_macros.h"
|
||||
|
||||
#ifdef SANITIZERS_ENABLED
|
||||
#ifdef __has_feature
|
||||
#if __has_feature(address_sanitizer) || __has_feature(thread_sanitizer)
|
||||
#define ASAN_OR_TSAN_ENABLED
|
||||
#endif
|
||||
#elif defined(__SANITIZE_ADDRESS__) || defined(__SANITIZE_THREAD__)
|
||||
#define ASAN_OR_TSAN_ENABLED
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// Declared in global namespace because of GDCLASS macro warning (Windows):
|
||||
// "Unqualified friend declaration referring to type outside of the nearest enclosing namespace
|
||||
// is a Microsoft extension; add a nested name specifier".
|
||||
class _TestDerivedObject : public Object {
|
||||
GDCLASS(_TestDerivedObject, Object);
|
||||
|
||||
int property_value;
|
||||
|
||||
protected:
|
||||
static void _bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("set_property", "property"), &_TestDerivedObject::set_property);
|
||||
ClassDB::bind_method(D_METHOD("get_property"), &_TestDerivedObject::get_property);
|
||||
ADD_PROPERTY(PropertyInfo(Variant::INT, "property"), "set_property", "get_property");
|
||||
}
|
||||
|
||||
public:
|
||||
void set_property(int value) { property_value = value; }
|
||||
int get_property() const { return property_value; }
|
||||
};
|
||||
|
||||
namespace TestObject {
|
||||
|
||||
class _MockScriptInstance : public ScriptInstance {
|
||||
StringName property_name = "NO_NAME";
|
||||
Variant property_value;
|
||||
|
||||
public:
|
||||
bool set(const StringName &p_name, const Variant &p_value) override {
|
||||
property_name = p_name;
|
||||
property_value = p_value;
|
||||
return true;
|
||||
}
|
||||
bool get(const StringName &p_name, Variant &r_ret) const override {
|
||||
if (property_name == p_name) {
|
||||
r_ret = property_value;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
void get_property_list(List<PropertyInfo> *p_properties) const override {
|
||||
}
|
||||
Variant::Type get_property_type(const StringName &p_name, bool *r_is_valid) const override {
|
||||
return Variant::PACKED_FLOAT32_ARRAY;
|
||||
}
|
||||
virtual void validate_property(PropertyInfo &p_property) const override {
|
||||
}
|
||||
bool property_can_revert(const StringName &p_name) const override {
|
||||
return false;
|
||||
}
|
||||
bool property_get_revert(const StringName &p_name, Variant &r_ret) const override {
|
||||
return false;
|
||||
}
|
||||
void get_method_list(List<MethodInfo> *p_list) const override {
|
||||
}
|
||||
bool has_method(const StringName &p_method) const override {
|
||||
return false;
|
||||
}
|
||||
int get_method_argument_count(const StringName &p_method, bool *r_is_valid = nullptr) const override {
|
||||
if (r_is_valid) {
|
||||
*r_is_valid = false;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
Variant callp(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) override {
|
||||
return Variant();
|
||||
}
|
||||
void notification(int p_notification, bool p_reversed = false) override {
|
||||
}
|
||||
Ref<Script> get_script() const override {
|
||||
return Ref<Script>();
|
||||
}
|
||||
const Variant get_rpc_config() const override {
|
||||
return Variant();
|
||||
}
|
||||
ScriptLanguage *get_language() override {
|
||||
return nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
TEST_CASE("[Object] Core getters") {
|
||||
Object object;
|
||||
|
||||
CHECK_MESSAGE(
|
||||
object.is_class("Object"),
|
||||
"is_class() should return the expected value.");
|
||||
CHECK_MESSAGE(
|
||||
object.get_class() == "Object",
|
||||
"The returned class should match the expected value.");
|
||||
CHECK_MESSAGE(
|
||||
object.get_class_name() == "Object",
|
||||
"The returned class name should match the expected value.");
|
||||
CHECK_MESSAGE(
|
||||
object.get_class_static() == "Object",
|
||||
"The returned static class should match the expected value.");
|
||||
CHECK_MESSAGE(
|
||||
object.get_save_class() == "Object",
|
||||
"The returned save class should match the expected value.");
|
||||
}
|
||||
|
||||
TEST_CASE("[Object] Metadata") {
|
||||
const String meta_path = "complex_metadata_path";
|
||||
Object object;
|
||||
|
||||
object.set_meta(meta_path, Color(0, 1, 0));
|
||||
CHECK_MESSAGE(
|
||||
Color(object.get_meta(meta_path)).is_equal_approx(Color(0, 1, 0)),
|
||||
"The returned object metadata after setting should match the expected value.");
|
||||
|
||||
List<StringName> meta_list;
|
||||
object.get_meta_list(&meta_list);
|
||||
CHECK_MESSAGE(
|
||||
meta_list.size() == 1,
|
||||
"The metadata list should only contain 1 item after adding one metadata item.");
|
||||
|
||||
object.remove_meta(meta_path);
|
||||
// Also try removing nonexistent metadata (it should do nothing, without printing an error message).
|
||||
object.remove_meta("I don't exist");
|
||||
ERR_PRINT_OFF;
|
||||
CHECK_MESSAGE(
|
||||
object.get_meta(meta_path) == Variant(),
|
||||
"The returned object metadata after removing should match the expected value.");
|
||||
ERR_PRINT_ON;
|
||||
|
||||
List<StringName> meta_list2;
|
||||
object.get_meta_list(&meta_list2);
|
||||
CHECK_MESSAGE(
|
||||
meta_list2.size() == 0,
|
||||
"The metadata list should contain 0 items after removing all metadata items.");
|
||||
|
||||
Object other;
|
||||
object.set_meta("conflicting_meta", "string");
|
||||
object.set_meta("not_conflicting_meta", 123);
|
||||
other.set_meta("conflicting_meta", Color(0, 1, 0));
|
||||
other.set_meta("other_meta", "other");
|
||||
object.merge_meta_from(&other);
|
||||
|
||||
CHECK_MESSAGE(
|
||||
Color(object.get_meta("conflicting_meta")).is_equal_approx(Color(0, 1, 0)),
|
||||
"String meta should be overwritten with Color after merging.");
|
||||
|
||||
CHECK_MESSAGE(
|
||||
int(object.get_meta("not_conflicting_meta")) == 123,
|
||||
"Not conflicting meta on destination should be kept intact.");
|
||||
|
||||
CHECK_MESSAGE(
|
||||
object.get_meta("other_meta", String()) == "other",
|
||||
"Not conflicting meta name on source should merged.");
|
||||
|
||||
List<StringName> meta_list3;
|
||||
object.get_meta_list(&meta_list3);
|
||||
CHECK_MESSAGE(
|
||||
meta_list3.size() == 3,
|
||||
"The metadata list should contain 3 items after merging meta from two objects.");
|
||||
}
|
||||
|
||||
TEST_CASE("[Object] Construction") {
|
||||
Object object;
|
||||
|
||||
CHECK_MESSAGE(
|
||||
!object.is_ref_counted(),
|
||||
"Object is not a RefCounted.");
|
||||
|
||||
Object *p_db = ObjectDB::get_instance(object.get_instance_id());
|
||||
CHECK_MESSAGE(
|
||||
p_db == &object,
|
||||
"The database pointer returned by the object id should reference same object.");
|
||||
}
|
||||
|
||||
TEST_CASE("[Object] Script instance property setter") {
|
||||
Object object;
|
||||
_MockScriptInstance *script_instance = memnew(_MockScriptInstance);
|
||||
object.set_script_instance(script_instance);
|
||||
|
||||
bool valid = false;
|
||||
object.set("some_name", 100, &valid);
|
||||
CHECK(valid);
|
||||
Variant actual_value;
|
||||
CHECK_MESSAGE(
|
||||
script_instance->get("some_name", actual_value),
|
||||
"The assigned script instance should successfully retrieve value by name.");
|
||||
CHECK_MESSAGE(
|
||||
actual_value == Variant(100),
|
||||
"The returned value should equal the one which was set by the object.");
|
||||
}
|
||||
|
||||
TEST_CASE("[Object] Script instance property getter") {
|
||||
Object object;
|
||||
_MockScriptInstance *script_instance = memnew(_MockScriptInstance);
|
||||
script_instance->set("some_name", 100); // Make sure script instance has the property
|
||||
object.set_script_instance(script_instance);
|
||||
|
||||
bool valid = false;
|
||||
const Variant &actual_value = object.get("some_name", &valid);
|
||||
CHECK(valid);
|
||||
CHECK_MESSAGE(
|
||||
actual_value == Variant(100),
|
||||
"The returned value should equal the one which was set by the script instance.");
|
||||
}
|
||||
|
||||
TEST_CASE("[Object] Built-in property setter") {
|
||||
GDREGISTER_CLASS(_TestDerivedObject);
|
||||
_TestDerivedObject derived_object;
|
||||
|
||||
bool valid = false;
|
||||
derived_object.set("property", 100, &valid);
|
||||
CHECK(valid);
|
||||
CHECK_MESSAGE(
|
||||
derived_object.get_property() == 100,
|
||||
"The property value should equal the one which was set with built-in setter.");
|
||||
}
|
||||
|
||||
TEST_CASE("[Object] Built-in property getter") {
|
||||
GDREGISTER_CLASS(_TestDerivedObject);
|
||||
_TestDerivedObject derived_object;
|
||||
derived_object.set_property(100);
|
||||
|
||||
bool valid = false;
|
||||
const Variant &actual_value = derived_object.get("property", &valid);
|
||||
CHECK(valid);
|
||||
CHECK_MESSAGE(
|
||||
actual_value == Variant(100),
|
||||
"The returned value should equal the one which was set with built-in setter.");
|
||||
}
|
||||
|
||||
TEST_CASE("[Object] Script property setter") {
|
||||
Object object;
|
||||
Variant script;
|
||||
|
||||
bool valid = false;
|
||||
object.set(CoreStringName(script), script, &valid);
|
||||
CHECK(valid);
|
||||
CHECK_MESSAGE(
|
||||
object.get_script() == script,
|
||||
"The object script should be equal to the assigned one.");
|
||||
}
|
||||
|
||||
TEST_CASE("[Object] Script property getter") {
|
||||
Object object;
|
||||
Variant script;
|
||||
object.set_script(script);
|
||||
|
||||
bool valid = false;
|
||||
const Variant &actual_value = object.get(CoreStringName(script), &valid);
|
||||
CHECK(valid);
|
||||
CHECK_MESSAGE(
|
||||
actual_value == script,
|
||||
"The returned value should be equal to the assigned script.");
|
||||
}
|
||||
|
||||
TEST_CASE("[Object] Absent name setter") {
|
||||
Object object;
|
||||
|
||||
bool valid = true;
|
||||
object.set("absent_name", 100, &valid);
|
||||
CHECK(!valid);
|
||||
}
|
||||
|
||||
TEST_CASE("[Object] Absent name getter") {
|
||||
Object object;
|
||||
|
||||
bool valid = true;
|
||||
const Variant &actual_value = object.get("absent_name", &valid);
|
||||
CHECK(!valid);
|
||||
CHECK_MESSAGE(
|
||||
actual_value == Variant(),
|
||||
"The returned value should equal nil variant.");
|
||||
}
|
||||
|
||||
TEST_CASE("[Object] Signals") {
|
||||
Object object;
|
||||
|
||||
CHECK_FALSE(object.has_signal("my_custom_signal"));
|
||||
|
||||
List<MethodInfo> signals_before;
|
||||
object.get_signal_list(&signals_before);
|
||||
|
||||
object.add_user_signal(MethodInfo("my_custom_signal"));
|
||||
|
||||
CHECK(object.has_signal("my_custom_signal"));
|
||||
|
||||
List<MethodInfo> signals_after;
|
||||
object.get_signal_list(&signals_after);
|
||||
|
||||
// Should be one more signal.
|
||||
CHECK_EQ(signals_before.size() + 1, signals_after.size());
|
||||
|
||||
SUBCASE("Adding the same user signal again should not have any effect") {
|
||||
CHECK(object.has_signal("my_custom_signal"));
|
||||
ERR_PRINT_OFF;
|
||||
object.add_user_signal(MethodInfo("my_custom_signal"));
|
||||
ERR_PRINT_ON;
|
||||
CHECK(object.has_signal("my_custom_signal"));
|
||||
|
||||
List<MethodInfo> signals_after_existing_added;
|
||||
object.get_signal_list(&signals_after_existing_added);
|
||||
|
||||
CHECK_EQ(signals_after.size(), signals_after_existing_added.size());
|
||||
}
|
||||
|
||||
SUBCASE("Trying to add a preexisting signal should not have any effect") {
|
||||
CHECK(object.has_signal("script_changed"));
|
||||
ERR_PRINT_OFF;
|
||||
object.add_user_signal(MethodInfo("script_changed"));
|
||||
ERR_PRINT_ON;
|
||||
CHECK(object.has_signal("script_changed"));
|
||||
|
||||
List<MethodInfo> signals_after_existing_added;
|
||||
object.get_signal_list(&signals_after_existing_added);
|
||||
|
||||
CHECK_EQ(signals_after.size(), signals_after_existing_added.size());
|
||||
}
|
||||
|
||||
SUBCASE("Adding an empty signal should not have any effect") {
|
||||
CHECK_FALSE(object.has_signal(""));
|
||||
ERR_PRINT_OFF;
|
||||
object.add_user_signal(MethodInfo(""));
|
||||
ERR_PRINT_ON;
|
||||
CHECK_FALSE(object.has_signal(""));
|
||||
|
||||
List<MethodInfo> signals_after_empty_added;
|
||||
object.get_signal_list(&signals_after_empty_added);
|
||||
|
||||
CHECK_EQ(signals_after.size(), signals_after_empty_added.size());
|
||||
}
|
||||
|
||||
SUBCASE("Deleting an object with connected signals should disconnect them") {
|
||||
List<Object::Connection> signal_connections;
|
||||
|
||||
{
|
||||
Object target;
|
||||
target.add_user_signal(MethodInfo("my_custom_signal"));
|
||||
|
||||
ERR_PRINT_OFF;
|
||||
target.connect("nonexistent_signal1", callable_mp(&object, &Object::notify_property_list_changed));
|
||||
target.connect("my_custom_signal", callable_mp(&object, &Object::notify_property_list_changed));
|
||||
target.connect("nonexistent_signal2", callable_mp(&object, &Object::notify_property_list_changed));
|
||||
ERR_PRINT_ON;
|
||||
|
||||
signal_connections.clear();
|
||||
object.get_all_signal_connections(&signal_connections);
|
||||
CHECK(signal_connections.size() == 0);
|
||||
signal_connections.clear();
|
||||
object.get_signals_connected_to_this(&signal_connections);
|
||||
CHECK(signal_connections.size() == 1);
|
||||
|
||||
ERR_PRINT_OFF;
|
||||
object.connect("nonexistent_signal1", callable_mp(&target, &Object::notify_property_list_changed));
|
||||
object.connect("my_custom_signal", callable_mp(&target, &Object::notify_property_list_changed));
|
||||
object.connect("nonexistent_signal2", callable_mp(&target, &Object::notify_property_list_changed));
|
||||
ERR_PRINT_ON;
|
||||
|
||||
signal_connections.clear();
|
||||
object.get_all_signal_connections(&signal_connections);
|
||||
CHECK(signal_connections.size() == 1);
|
||||
signal_connections.clear();
|
||||
object.get_signals_connected_to_this(&signal_connections);
|
||||
CHECK(signal_connections.size() == 1);
|
||||
}
|
||||
|
||||
signal_connections.clear();
|
||||
object.get_all_signal_connections(&signal_connections);
|
||||
CHECK(signal_connections.size() == 0);
|
||||
signal_connections.clear();
|
||||
object.get_signals_connected_to_this(&signal_connections);
|
||||
CHECK(signal_connections.size() == 0);
|
||||
}
|
||||
|
||||
SUBCASE("Emitting a non existing signal will return an error") {
|
||||
Error err = object.emit_signal("some_signal");
|
||||
CHECK(err == ERR_UNAVAILABLE);
|
||||
}
|
||||
|
||||
SUBCASE("Emitting an existing signal should call the connected method") {
|
||||
Array empty_signal_args = { {} };
|
||||
|
||||
SIGNAL_WATCH(&object, "my_custom_signal");
|
||||
SIGNAL_CHECK_FALSE("my_custom_signal");
|
||||
|
||||
Error err = object.emit_signal("my_custom_signal");
|
||||
CHECK(err == OK);
|
||||
|
||||
SIGNAL_CHECK("my_custom_signal", empty_signal_args);
|
||||
SIGNAL_UNWATCH(&object, "my_custom_signal");
|
||||
}
|
||||
|
||||
SUBCASE("Connecting and then disconnecting many signals should not leave anything behind") {
|
||||
List<Object::Connection> signal_connections;
|
||||
Object targets[100];
|
||||
|
||||
for (int i = 0; i < 10; i++) {
|
||||
ERR_PRINT_OFF;
|
||||
for (Object &target : targets) {
|
||||
object.connect("my_custom_signal", callable_mp(&target, &Object::notify_property_list_changed));
|
||||
}
|
||||
ERR_PRINT_ON;
|
||||
signal_connections.clear();
|
||||
object.get_all_signal_connections(&signal_connections);
|
||||
CHECK(signal_connections.size() == 100);
|
||||
}
|
||||
|
||||
for (Object &target : targets) {
|
||||
object.disconnect("my_custom_signal", callable_mp(&target, &Object::notify_property_list_changed));
|
||||
}
|
||||
signal_connections.clear();
|
||||
object.get_all_signal_connections(&signal_connections);
|
||||
CHECK(signal_connections.size() == 0);
|
||||
}
|
||||
}
|
||||
|
||||
class NotificationObjectSuperclass : public Object {
|
||||
GDCLASS(NotificationObjectSuperclass, Object);
|
||||
|
||||
protected:
|
||||
void _notification(int p_what) {
|
||||
order_superclass = ++order_global;
|
||||
}
|
||||
|
||||
public:
|
||||
static inline int order_global = 0;
|
||||
int order_superclass = -1;
|
||||
};
|
||||
|
||||
class NotificationObjectSubclass : public NotificationObjectSuperclass {
|
||||
GDCLASS(NotificationObjectSubclass, NotificationObjectSuperclass);
|
||||
|
||||
protected:
|
||||
void _notification(int p_what) {
|
||||
order_subclass = ++order_global;
|
||||
}
|
||||
|
||||
public:
|
||||
int order_subclass = -1;
|
||||
};
|
||||
|
||||
class NotificationScriptInstance : public _MockScriptInstance {
|
||||
void notification(int p_notification, bool p_reversed) override {
|
||||
order_script = ++NotificationObjectSuperclass::order_global;
|
||||
}
|
||||
|
||||
public:
|
||||
int order_script = -1;
|
||||
};
|
||||
|
||||
TEST_CASE("[Object] Notification order") { // GH-52325
|
||||
NotificationObjectSubclass *object = memnew(NotificationObjectSubclass);
|
||||
|
||||
NotificationScriptInstance *script = memnew(NotificationScriptInstance);
|
||||
object->set_script_instance(script);
|
||||
|
||||
SUBCASE("regular order") {
|
||||
NotificationObjectSubclass::order_global = 0;
|
||||
object->order_superclass = -1;
|
||||
object->order_subclass = -1;
|
||||
script->order_script = -1;
|
||||
object->notification(12345, false);
|
||||
|
||||
CHECK_EQ(object->order_superclass, 1);
|
||||
CHECK_EQ(object->order_subclass, 2);
|
||||
// TODO If an extension is attached, it should come here.
|
||||
CHECK_EQ(script->order_script, 3);
|
||||
CHECK_EQ(NotificationObjectSubclass::order_global, 3);
|
||||
}
|
||||
|
||||
SUBCASE("reverse order") {
|
||||
NotificationObjectSubclass::order_global = 0;
|
||||
object->order_superclass = -1;
|
||||
object->order_subclass = -1;
|
||||
script->order_script = -1;
|
||||
object->notification(12345, true);
|
||||
|
||||
CHECK_EQ(script->order_script, 1);
|
||||
// TODO If an extension is attached, it should come here.
|
||||
CHECK_EQ(object->order_subclass, 2);
|
||||
CHECK_EQ(object->order_superclass, 3);
|
||||
CHECK_EQ(NotificationObjectSubclass::order_global, 3);
|
||||
}
|
||||
|
||||
memdelete(object);
|
||||
}
|
||||
|
||||
TEST_CASE("[Object] Destruction at the end of the call chain is safe") {
|
||||
Object *object = memnew(Object);
|
||||
ObjectID obj_id = object->get_instance_id();
|
||||
|
||||
class _SelfDestroyingScriptInstance : public _MockScriptInstance {
|
||||
Object *self = nullptr;
|
||||
|
||||
// This has to be static because ~Object() also destroys the script instance.
|
||||
static void free_self(Object *p_self) {
|
||||
#if defined(ASAN_OR_TSAN_ENABLED)
|
||||
// Regular deletion is enough becausa asan/tsan will catch a potential heap-after-use.
|
||||
memdelete(p_self);
|
||||
#else
|
||||
// Without asan/tsan, try at least to force a crash by replacing the otherwise seemingly good data with garbage.
|
||||
// Operations such as dereferencing pointers or decreasing a refcount would fail.
|
||||
// Unfortunately, we may not poison the memory after the deletion, because the memory would no longer belong to us
|
||||
// and on doing so we may cause a more generalized crash on some platforms (allocator implementations).
|
||||
p_self->~Object();
|
||||
memset((void *)p_self, 0, sizeof(Object));
|
||||
Memory::free_static(p_self, false);
|
||||
#endif
|
||||
}
|
||||
|
||||
public:
|
||||
Variant callp(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) override {
|
||||
free_self(self);
|
||||
return Variant();
|
||||
}
|
||||
Variant call_const(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) override {
|
||||
free_self(self);
|
||||
return Variant();
|
||||
}
|
||||
bool has_method(const StringName &p_method) const override {
|
||||
return p_method == "some_method";
|
||||
}
|
||||
|
||||
public:
|
||||
_SelfDestroyingScriptInstance(Object *p_self) :
|
||||
self(p_self) {}
|
||||
};
|
||||
|
||||
_SelfDestroyingScriptInstance *script_instance = memnew(_SelfDestroyingScriptInstance(object));
|
||||
object->set_script_instance(script_instance);
|
||||
|
||||
SUBCASE("Within callp()") {
|
||||
SUBCASE("Through call()") {
|
||||
object->call("some_method");
|
||||
}
|
||||
SUBCASE("Through callv()") {
|
||||
object->callv("some_method", Array());
|
||||
}
|
||||
}
|
||||
SUBCASE("Within call_const()") {
|
||||
Callable::CallError call_error;
|
||||
object->call_const("some_method", nullptr, 0, call_error);
|
||||
}
|
||||
SUBCASE("Within signal handling (from emit_signalp(), through emit_signal())") {
|
||||
Object emitter;
|
||||
emitter.add_user_signal(MethodInfo("some_signal"));
|
||||
emitter.connect("some_signal", Callable(object, "some_method"));
|
||||
emitter.emit_signal("some_signal");
|
||||
}
|
||||
|
||||
CHECK_MESSAGE(
|
||||
ObjectDB::get_instance(obj_id) == nullptr,
|
||||
"Object was tail-deleted without crashes.");
|
||||
}
|
||||
|
||||
} // namespace TestObject
|
||||
199
tests/core/object/test_undo_redo.h
Normal file
@@ -0,0 +1,199 @@
|
||||
/**************************************************************************/
|
||||
/* test_undo_redo.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/object/undo_redo.h"
|
||||
#include "tests/test_macros.h"
|
||||
|
||||
// Declared in global namespace because of GDCLASS macro warning (Windows):
|
||||
// "Unqualified friend declaration referring to type outside of the nearest enclosing namespace
|
||||
// is a Microsoft extension; add a nested name specifier".
|
||||
class _TestUndoRedoObject : public Object {
|
||||
GDCLASS(_TestUndoRedoObject, Object);
|
||||
int property_value = 0;
|
||||
|
||||
protected:
|
||||
static void _bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("set_property", "property"), &_TestUndoRedoObject::set_property);
|
||||
ClassDB::bind_method(D_METHOD("get_property"), &_TestUndoRedoObject::get_property);
|
||||
ADD_PROPERTY(PropertyInfo(Variant::INT, "property"), "set_property", "get_property");
|
||||
}
|
||||
|
||||
public:
|
||||
void set_property(int value) { property_value = value; }
|
||||
int get_property() const { return property_value; }
|
||||
void add_to_property(int value) { property_value += value; }
|
||||
void subtract_from_property(int value) { property_value -= value; }
|
||||
};
|
||||
|
||||
namespace TestUndoRedo {
|
||||
|
||||
void set_property_action(UndoRedo *undo_redo, const String &name, _TestUndoRedoObject *test_object, int value, UndoRedo::MergeMode merge_mode = UndoRedo::MERGE_DISABLE) {
|
||||
undo_redo->create_action(name, merge_mode);
|
||||
undo_redo->add_do_property(test_object, "property", value);
|
||||
undo_redo->add_undo_property(test_object, "property", test_object->get_property());
|
||||
undo_redo->commit_action();
|
||||
}
|
||||
|
||||
void increment_property_action(UndoRedo *undo_redo, const String &name, _TestUndoRedoObject *test_object, int value, UndoRedo::MergeMode merge_mode = UndoRedo::MERGE_DISABLE) {
|
||||
undo_redo->create_action(name, merge_mode);
|
||||
undo_redo->add_do_method(callable_mp(test_object, &_TestUndoRedoObject::add_to_property).bind(value));
|
||||
undo_redo->add_undo_method(callable_mp(test_object, &_TestUndoRedoObject::subtract_from_property).bind(value));
|
||||
undo_redo->commit_action();
|
||||
}
|
||||
|
||||
TEST_CASE("[UndoRedo] Simple Property UndoRedo") {
|
||||
GDREGISTER_CLASS(_TestUndoRedoObject);
|
||||
UndoRedo *undo_redo = memnew(UndoRedo());
|
||||
|
||||
_TestUndoRedoObject *test_object = memnew(_TestUndoRedoObject());
|
||||
|
||||
CHECK(test_object->get_property() == 0);
|
||||
CHECK(undo_redo->get_version() == 1);
|
||||
CHECK(undo_redo->get_history_count() == 0);
|
||||
|
||||
set_property_action(undo_redo, "Set Property", test_object, 10);
|
||||
|
||||
CHECK(test_object->get_property() == 10);
|
||||
CHECK(undo_redo->get_version() == 2);
|
||||
CHECK(undo_redo->get_history_count() == 1);
|
||||
|
||||
undo_redo->undo();
|
||||
|
||||
CHECK(test_object->get_property() == 0);
|
||||
CHECK(undo_redo->get_version() == 1);
|
||||
CHECK(undo_redo->get_history_count() == 1);
|
||||
|
||||
undo_redo->redo();
|
||||
|
||||
CHECK(test_object->get_property() == 10);
|
||||
CHECK(undo_redo->get_version() == 2);
|
||||
CHECK(undo_redo->get_history_count() == 1);
|
||||
|
||||
set_property_action(undo_redo, "Set Property", test_object, 100);
|
||||
|
||||
CHECK(test_object->get_property() == 100);
|
||||
CHECK(undo_redo->get_version() == 3);
|
||||
CHECK(undo_redo->get_history_count() == 2);
|
||||
|
||||
set_property_action(undo_redo, "Set Property", test_object, 1000);
|
||||
|
||||
CHECK(test_object->get_property() == 1000);
|
||||
CHECK(undo_redo->get_version() == 4);
|
||||
CHECK(undo_redo->get_history_count() == 3);
|
||||
|
||||
undo_redo->undo();
|
||||
|
||||
CHECK(test_object->get_property() == 100);
|
||||
CHECK(undo_redo->get_version() == 3);
|
||||
CHECK(undo_redo->get_history_count() == 3);
|
||||
|
||||
memdelete(test_object);
|
||||
memdelete(undo_redo);
|
||||
}
|
||||
|
||||
TEST_CASE("[UndoRedo] Merge Property UndoRedo") {
|
||||
GDREGISTER_CLASS(_TestUndoRedoObject);
|
||||
UndoRedo *undo_redo = memnew(UndoRedo());
|
||||
|
||||
_TestUndoRedoObject *test_object = memnew(_TestUndoRedoObject());
|
||||
|
||||
CHECK(test_object->get_property() == 0);
|
||||
CHECK(undo_redo->get_version() == 1);
|
||||
CHECK(undo_redo->get_history_count() == 0);
|
||||
|
||||
set_property_action(undo_redo, "Merge Action 1", test_object, 10, UndoRedo::MERGE_ALL);
|
||||
|
||||
CHECK(test_object->get_property() == 10);
|
||||
CHECK(undo_redo->get_version() == 2);
|
||||
CHECK(undo_redo->get_history_count() == 1);
|
||||
|
||||
set_property_action(undo_redo, "Merge Action 1", test_object, 100, UndoRedo::MERGE_ALL);
|
||||
|
||||
CHECK(test_object->get_property() == 100);
|
||||
CHECK(undo_redo->get_version() == 2);
|
||||
CHECK(undo_redo->get_history_count() == 1);
|
||||
|
||||
set_property_action(undo_redo, "Merge Action 1", test_object, 1000, UndoRedo::MERGE_ALL);
|
||||
|
||||
CHECK(test_object->get_property() == 1000);
|
||||
CHECK(undo_redo->get_version() == 2);
|
||||
CHECK(undo_redo->get_history_count() == 1);
|
||||
|
||||
memdelete(test_object);
|
||||
memdelete(undo_redo);
|
||||
}
|
||||
|
||||
TEST_CASE("[UndoRedo] Merge Method UndoRedo") {
|
||||
GDREGISTER_CLASS(_TestUndoRedoObject);
|
||||
UndoRedo *undo_redo = memnew(UndoRedo());
|
||||
|
||||
_TestUndoRedoObject *test_object = memnew(_TestUndoRedoObject());
|
||||
|
||||
CHECK(test_object->get_property() == 0);
|
||||
CHECK(undo_redo->get_version() == 1);
|
||||
CHECK(undo_redo->get_history_count() == 0);
|
||||
|
||||
increment_property_action(undo_redo, "Merge Increment 1", test_object, 10, UndoRedo::MERGE_ALL);
|
||||
|
||||
CHECK(test_object->get_property() == 10);
|
||||
CHECK(undo_redo->get_version() == 2);
|
||||
CHECK(undo_redo->get_history_count() == 1);
|
||||
|
||||
increment_property_action(undo_redo, "Merge Increment 1", test_object, 10, UndoRedo::MERGE_ALL);
|
||||
|
||||
CHECK(test_object->get_property() == 20);
|
||||
CHECK(undo_redo->get_version() == 2);
|
||||
CHECK(undo_redo->get_history_count() == 1);
|
||||
|
||||
increment_property_action(undo_redo, "Merge Increment 1", test_object, 10, UndoRedo::MERGE_ALL);
|
||||
|
||||
CHECK(test_object->get_property() == 30);
|
||||
CHECK(undo_redo->get_version() == 2);
|
||||
CHECK(undo_redo->get_history_count() == 1);
|
||||
|
||||
undo_redo->undo();
|
||||
|
||||
CHECK(test_object->get_property() == 0);
|
||||
CHECK(undo_redo->get_version() == 1);
|
||||
CHECK(undo_redo->get_history_count() == 1);
|
||||
|
||||
undo_redo->redo();
|
||||
|
||||
CHECK(test_object->get_property() == 30);
|
||||
CHECK(undo_redo->get_version() == 2);
|
||||
CHECK(undo_redo->get_history_count() == 1);
|
||||
|
||||
memdelete(test_object);
|
||||
memdelete(undo_redo);
|
||||
}
|
||||
|
||||
} //namespace TestUndoRedo
|
||||
203
tests/core/os/test_os.h
Normal file
@@ -0,0 +1,203 @@
|
||||
/**************************************************************************/
|
||||
/* test_os.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/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
|
||||
}
|
||||
|
||||
TEST_CASE("[OS] UTF-8 environment variables") {
|
||||
String value = String::utf8("hell\xc3\xb6"); // "hellö", UTF-8 encoded
|
||||
|
||||
OS::get_singleton()->set_environment("HELLO", value);
|
||||
String val = OS::get_singleton()->get_environment("HELLO");
|
||||
CHECK_MESSAGE(
|
||||
val == value,
|
||||
"The previously-set HELLO environment variable should return the expected value.");
|
||||
CHECK_MESSAGE(
|
||||
val.length() == 5,
|
||||
"The previously-set HELLO environment variable was decoded as UTF-8 and should have a length of 5.");
|
||||
OS::get_singleton()->unset_environment("HELLO");
|
||||
}
|
||||
|
||||
TEST_CASE("[OS] Non-UTF-8 environment variables") {
|
||||
String value = String("\xff t\xf6rkylempij\xe4vongahdus"); // hex FF and a Finnish pangram, latin-1
|
||||
OS::get_singleton()->set_environment("HELLO", value);
|
||||
String val = OS::get_singleton()->get_environment("HELLO");
|
||||
CHECK_MESSAGE(
|
||||
val == value,
|
||||
"The previously-set HELLO environment variable should return the expected value.");
|
||||
CHECK_MESSAGE(
|
||||
val.length() == 23,
|
||||
"The previously-set HELLO environment variable was not decoded from Latin-1.");
|
||||
OS::get_singleton()->unset_environment("HELLO");
|
||||
}
|
||||
|
||||
TEST_CASE("[OS] Command line arguments") {
|
||||
List<String> arguments = OS::get_singleton()->get_cmdline_args();
|
||||
bool found = false;
|
||||
for (const String &arg : arguments) {
|
||||
if (arg == "--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") {
|
||||
#ifdef TOOLS_ENABLED
|
||||
CHECK_MESSAGE(
|
||||
OS::get_singleton()->has_feature("editor"),
|
||||
"The binary has the \"editor\" feature tag.");
|
||||
CHECK_MESSAGE(
|
||||
!OS::get_singleton()->has_feature("template"),
|
||||
"The binary does not have the \"template\" feature tag.");
|
||||
CHECK_MESSAGE(
|
||||
!OS::get_singleton()->has_feature("template_debug"),
|
||||
"The binary does not have the \"template_debug\" feature tag.");
|
||||
CHECK_MESSAGE(
|
||||
!OS::get_singleton()->has_feature("template_release"),
|
||||
"The binary does not have the \"template_release\" feature tag.");
|
||||
#else
|
||||
CHECK_MESSAGE(
|
||||
!OS::get_singleton()->has_feature("editor"),
|
||||
"The binary does not have the \"editor\" feature tag.");
|
||||
CHECK_MESSAGE(
|
||||
OS::get_singleton()->has_feature("template"),
|
||||
"The binary has the \"template\" feature tag.");
|
||||
#ifdef DEBUG_ENABLED
|
||||
CHECK_MESSAGE(
|
||||
OS::get_singleton()->has_feature("template_debug"),
|
||||
"The binary has the \"template_debug\" feature tag.");
|
||||
CHECK_MESSAGE(
|
||||
!OS::get_singleton()->has_feature("template_release"),
|
||||
"The binary does not have the \"template_release\" feature tag.");
|
||||
#else
|
||||
CHECK_MESSAGE(
|
||||
!OS::get_singleton()->has_feature("template_debug"),
|
||||
"The binary does not have the \"template_debug\" feature tag.");
|
||||
CHECK_MESSAGE(
|
||||
OS::get_singleton()->has_feature("template_release"),
|
||||
"The binary has the \"template_release\" feature tag.");
|
||||
#endif // DEBUG_ENABLED
|
||||
#endif // TOOLS_ENABLED
|
||||
}
|
||||
|
||||
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.");
|
||||
#ifdef DEBUG_ENABLED
|
||||
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.");
|
||||
#endif // DEBUG_ENABLED
|
||||
}
|
||||
|
||||
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
|
||||
80
tests/core/string/test_fuzzy_search.h
Normal file
@@ -0,0 +1,80 @@
|
||||
/**************************************************************************/
|
||||
/* test_fuzzy_search.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/string/fuzzy_search.h"
|
||||
#include "tests/test_macros.h"
|
||||
|
||||
namespace TestFuzzySearch {
|
||||
|
||||
struct FuzzySearchTestCase {
|
||||
String query;
|
||||
String expected;
|
||||
};
|
||||
|
||||
// Ideally each of these test queries should represent a different aspect, and potentially bottleneck, of the search process.
|
||||
const FuzzySearchTestCase test_cases[] = {
|
||||
// Short query, many matches, few adjacent characters
|
||||
{ "///gd", "./menu/hud/hud.gd" },
|
||||
// Filename match with typo
|
||||
{ "sm.png", "./entity/blood_sword/sam.png" },
|
||||
// Multipart filename word matches
|
||||
{ "ham ", "./entity/game_trap/ha_missed_me.wav" },
|
||||
// Single word token matches
|
||||
{ "push background", "./entity/background_zone1/background/push.png" },
|
||||
// Long token matches
|
||||
{ "background_freighter background png", "./entity/background_freighter/background/background.png" },
|
||||
// Many matches, many short tokens
|
||||
{ "menu menu characters wav", "./menu/menu/characters/smoker/0.wav" },
|
||||
// Maximize total matches
|
||||
{ "entity gd", "./entity/entity_man.gd" }
|
||||
};
|
||||
|
||||
Vector<String> load_test_data() {
|
||||
Ref<FileAccess> fp = FileAccess::open(TestUtils::get_data_path("fuzzy_search/project_dir_tree.txt"), FileAccess::READ);
|
||||
REQUIRE(fp.is_valid());
|
||||
return fp->get_as_utf8_string().split("\n");
|
||||
}
|
||||
|
||||
TEST_CASE("[FuzzySearch] Test fuzzy search results") {
|
||||
FuzzySearch search;
|
||||
Vector<FuzzySearchResult> results;
|
||||
Vector<String> targets = load_test_data();
|
||||
|
||||
for (FuzzySearchTestCase test_case : test_cases) {
|
||||
search.set_query(test_case.query);
|
||||
search.search_all(targets, results);
|
||||
CHECK_GT(results.size(), 0);
|
||||
CHECK_EQ(results[0].target, test_case.expected);
|
||||
}
|
||||
}
|
||||
|
||||
} //namespace TestFuzzySearch
|
||||
225
tests/core/string/test_node_path.h
Normal file
@@ -0,0 +1,225 @@
|
||||
/**************************************************************************/
|
||||
/* test_node_path.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/string/node_path.h"
|
||||
|
||||
#include "tests/test_macros.h"
|
||||
|
||||
namespace TestNodePath {
|
||||
|
||||
TEST_CASE("[NodePath] Relative path") {
|
||||
const NodePath node_path_relative = NodePath("Path2D/PathFollow2D/Sprite2D:position:x");
|
||||
|
||||
CHECK_MESSAGE(
|
||||
node_path_relative.get_as_property_path() == NodePath(":Path2D/PathFollow2D/Sprite2D:position:x"),
|
||||
"The returned property path should match the expected value.");
|
||||
CHECK_MESSAGE(
|
||||
node_path_relative.get_concatenated_subnames() == "position:x",
|
||||
"The returned concatenated subnames should match the expected value.");
|
||||
|
||||
CHECK_MESSAGE(
|
||||
node_path_relative.get_name(0) == "Path2D",
|
||||
"The returned name at index 0 should match the expected value.");
|
||||
CHECK_MESSAGE(
|
||||
node_path_relative.get_name(1) == "PathFollow2D",
|
||||
"The returned name at index 1 should match the expected value.");
|
||||
CHECK_MESSAGE(
|
||||
node_path_relative.get_name(2) == "Sprite2D",
|
||||
"The returned name at index 2 should match the expected value.");
|
||||
ERR_PRINT_OFF;
|
||||
CHECK_MESSAGE(
|
||||
node_path_relative.get_name(3) == "",
|
||||
"The returned name at invalid index 3 should match the expected value.");
|
||||
CHECK_MESSAGE(
|
||||
node_path_relative.get_name(-1) == "",
|
||||
"The returned name at invalid index -1 should match the expected value.");
|
||||
ERR_PRINT_ON;
|
||||
|
||||
CHECK_MESSAGE(
|
||||
node_path_relative.get_name_count() == 3,
|
||||
"The returned number of names should match the expected value.");
|
||||
|
||||
CHECK_MESSAGE(
|
||||
node_path_relative.get_subname(0) == "position",
|
||||
"The returned subname at index 0 should match the expected value.");
|
||||
CHECK_MESSAGE(
|
||||
node_path_relative.get_subname(1) == "x",
|
||||
"The returned subname at index 1 should match the expected value.");
|
||||
ERR_PRINT_OFF;
|
||||
CHECK_MESSAGE(
|
||||
node_path_relative.get_subname(2) == "",
|
||||
"The returned subname at invalid index 2 should match the expected value.");
|
||||
CHECK_MESSAGE(
|
||||
node_path_relative.get_subname(-1) == "",
|
||||
"The returned subname at invalid index -1 should match the expected value.");
|
||||
ERR_PRINT_ON;
|
||||
|
||||
CHECK_MESSAGE(
|
||||
node_path_relative.get_subname_count() == 2,
|
||||
"The returned number of subnames should match the expected value.");
|
||||
|
||||
CHECK_MESSAGE(
|
||||
!node_path_relative.is_absolute(),
|
||||
"The node path should be considered relative.");
|
||||
|
||||
CHECK_MESSAGE(
|
||||
!node_path_relative.is_empty(),
|
||||
"The node path shouldn't be considered empty.");
|
||||
}
|
||||
|
||||
TEST_CASE("[NodePath] Absolute path") {
|
||||
const NodePath node_path_absolute = NodePath("/root/Sprite2D");
|
||||
|
||||
CHECK_MESSAGE(
|
||||
node_path_absolute.get_as_property_path() == NodePath(":root/Sprite2D"),
|
||||
"The returned property path should match the expected value.");
|
||||
CHECK_MESSAGE(
|
||||
node_path_absolute.get_concatenated_subnames() == "",
|
||||
"The returned concatenated subnames should match the expected value.");
|
||||
|
||||
CHECK_MESSAGE(
|
||||
node_path_absolute.get_name(0) == "root",
|
||||
"The returned name at index 0 should match the expected value.");
|
||||
CHECK_MESSAGE(
|
||||
node_path_absolute.get_name(1) == "Sprite2D",
|
||||
"The returned name at index 1 should match the expected value.");
|
||||
ERR_PRINT_OFF;
|
||||
CHECK_MESSAGE(
|
||||
node_path_absolute.get_name(2) == "",
|
||||
"The returned name at invalid index 2 should match the expected value.");
|
||||
CHECK_MESSAGE(
|
||||
node_path_absolute.get_name(-1) == "",
|
||||
"The returned name at invalid index -1 should match the expected value.");
|
||||
ERR_PRINT_ON;
|
||||
|
||||
CHECK_MESSAGE(
|
||||
node_path_absolute.get_name_count() == 2,
|
||||
"The returned number of names should match the expected value.");
|
||||
|
||||
CHECK_MESSAGE(
|
||||
node_path_absolute.get_subname_count() == 0,
|
||||
"The returned number of subnames should match the expected value.");
|
||||
|
||||
CHECK_MESSAGE(
|
||||
node_path_absolute.is_absolute(),
|
||||
"The node path should be considered absolute.");
|
||||
|
||||
CHECK_MESSAGE(
|
||||
!node_path_absolute.is_empty(),
|
||||
"The node path shouldn't be considered empty.");
|
||||
}
|
||||
|
||||
TEST_CASE("[NodePath] Empty path") {
|
||||
const NodePath node_path_empty = NodePath();
|
||||
|
||||
CHECK_MESSAGE(
|
||||
node_path_empty.get_as_property_path() == NodePath(),
|
||||
"The returned property path should match the expected value.");
|
||||
ERR_PRINT_OFF;
|
||||
CHECK_MESSAGE(
|
||||
node_path_empty.get_concatenated_subnames() == "",
|
||||
"The returned concatenated subnames should match the expected value.");
|
||||
ERR_PRINT_ON;
|
||||
|
||||
CHECK_MESSAGE(
|
||||
node_path_empty.get_name_count() == 0,
|
||||
"The returned number of names should match the expected value.");
|
||||
|
||||
CHECK_MESSAGE(
|
||||
node_path_empty.get_subname_count() == 0,
|
||||
"The returned number of subnames should match the expected value.");
|
||||
|
||||
CHECK_MESSAGE(
|
||||
!node_path_empty.is_absolute(),
|
||||
"The node path shouldn't be considered absolute.");
|
||||
|
||||
CHECK_MESSAGE(
|
||||
node_path_empty.is_empty(),
|
||||
"The node path should be considered empty.");
|
||||
}
|
||||
|
||||
TEST_CASE("[NodePath] Slice") {
|
||||
const NodePath node_path_relative = NodePath("Parent/Child:prop:subprop");
|
||||
const NodePath node_path_absolute = NodePath("/root/Parent/Child:prop");
|
||||
CHECK_MESSAGE(
|
||||
node_path_relative.slice(0, 2) == NodePath("Parent/Child"),
|
||||
"The slice lower bound should be inclusive and the slice upper bound should be exclusive.");
|
||||
CHECK_MESSAGE(
|
||||
node_path_relative.slice(3) == NodePath(":subprop"),
|
||||
"Slicing on the last index (length - 1) should return the last entry.");
|
||||
CHECK_MESSAGE(
|
||||
node_path_relative.slice(1) == NodePath("Child:prop:subprop"),
|
||||
"Slicing without upper bound should return remaining entries after index.");
|
||||
CHECK_MESSAGE(
|
||||
node_path_relative.slice(1, 3) == NodePath("Child:prop"),
|
||||
"Slicing should include names and subnames.");
|
||||
CHECK_MESSAGE(
|
||||
node_path_relative.slice(-1) == NodePath(":subprop"),
|
||||
"Slicing on -1 should return the last entry.");
|
||||
CHECK_MESSAGE(
|
||||
node_path_relative.slice(0, -1) == NodePath("Parent/Child:prop"),
|
||||
"Slicing up to -1 should include the second-to-last entry.");
|
||||
CHECK_MESSAGE(
|
||||
node_path_relative.slice(-2, -1) == NodePath(":prop"),
|
||||
"Slicing from negative to negative should treat lower bound as inclusive and upper bound as exclusive.");
|
||||
CHECK_MESSAGE(
|
||||
node_path_relative.slice(0, 10) == NodePath("Parent/Child:prop:subprop"),
|
||||
"Slicing past the length of the path should work like slicing up to the last entry.");
|
||||
CHECK_MESSAGE(
|
||||
node_path_relative.slice(-10, 2) == NodePath("Parent/Child"),
|
||||
"Slicing negatively past the length of the path should work like slicing from the first entry.");
|
||||
CHECK_MESSAGE(
|
||||
node_path_relative.slice(1, 1) == NodePath(""),
|
||||
"Slicing with a lower bound equal to upper bound should return empty path.");
|
||||
|
||||
CHECK_MESSAGE(
|
||||
node_path_absolute.slice(0, 2) == NodePath("/root/Parent"),
|
||||
"Slice from beginning of an absolute path should be an absolute path.");
|
||||
CHECK_MESSAGE(
|
||||
node_path_absolute.slice(1, 4) == NodePath("Parent/Child:prop"),
|
||||
"Slice of an absolute path that does not start at the beginning should be a relative path.");
|
||||
CHECK_MESSAGE(
|
||||
node_path_absolute.slice(3, 4) == NodePath(":prop"),
|
||||
"Slice of an absolute path that does not start at the beginning should be a relative path.");
|
||||
|
||||
CHECK_MESSAGE(
|
||||
NodePath("").slice(0, 1) == NodePath(""),
|
||||
"Slice of an empty path should be an empty path.");
|
||||
CHECK_MESSAGE(
|
||||
NodePath("").slice(-1, 2) == NodePath(""),
|
||||
"Slice of an empty path should be an empty path.");
|
||||
CHECK_MESSAGE(
|
||||
NodePath("/").slice(-1, 2) == NodePath("/"),
|
||||
"Slice of an empty absolute path should be an empty absolute path.");
|
||||
}
|
||||
|
||||
} // namespace TestNodePath
|
||||
2220
tests/core/string/test_string.h
Normal file
202
tests/core/string/test_translation.h
Normal file
@@ -0,0 +1,202 @@
|
||||
/**************************************************************************/
|
||||
/* test_translation.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/string/optimized_translation.h"
|
||||
#include "core/string/translation.h"
|
||||
#include "core/string/translation_po.h"
|
||||
#include "core/string/translation_server.h"
|
||||
|
||||
#ifdef TOOLS_ENABLED
|
||||
#include "editor/import/resource_importer_csv_translation.h"
|
||||
#endif
|
||||
|
||||
#include "tests/test_macros.h"
|
||||
#include "tests/test_utils.h"
|
||||
|
||||
namespace TestTranslation {
|
||||
|
||||
TEST_CASE("[Translation] Messages") {
|
||||
Ref<Translation> translation = memnew(Translation);
|
||||
translation->set_locale("fr");
|
||||
translation->add_message("Hello", "Bonjour");
|
||||
CHECK(translation->get_message("Hello") == "Bonjour");
|
||||
|
||||
translation->erase_message("Hello");
|
||||
// The message no longer exists, so it returns an empty string instead.
|
||||
CHECK(translation->get_message("Hello") == "");
|
||||
|
||||
List<StringName> messages;
|
||||
translation->get_message_list(&messages);
|
||||
CHECK(translation->get_message_count() == 0);
|
||||
CHECK(messages.size() == 0);
|
||||
|
||||
translation->add_message("Hello2", "Bonjour2");
|
||||
translation->add_message("Hello3", "Bonjour3");
|
||||
messages.clear();
|
||||
translation->get_message_list(&messages);
|
||||
CHECK(translation->get_message_count() == 2);
|
||||
CHECK(messages.size() == 2);
|
||||
// Messages are stored in a Map, don't assume ordering.
|
||||
CHECK(messages.find("Hello2"));
|
||||
CHECK(messages.find("Hello3"));
|
||||
}
|
||||
|
||||
TEST_CASE("[TranslationPO] Messages with context") {
|
||||
Ref<TranslationPO> translation = memnew(TranslationPO);
|
||||
translation->set_locale("fr");
|
||||
translation->add_message("Hello", "Bonjour");
|
||||
translation->add_message("Hello", "Salut", "friendly");
|
||||
CHECK(translation->get_message("Hello") == "Bonjour");
|
||||
CHECK(translation->get_message("Hello", "friendly") == "Salut");
|
||||
CHECK(translation->get_message("Hello", "nonexistent_context") == "");
|
||||
|
||||
// Only remove the message for the default context, not the "friendly" context.
|
||||
translation->erase_message("Hello");
|
||||
// The message no longer exists, so it returns an empty string instead.
|
||||
CHECK(translation->get_message("Hello") == "");
|
||||
CHECK(translation->get_message("Hello", "friendly") == "Salut");
|
||||
CHECK(translation->get_message("Hello", "nonexistent_context") == "");
|
||||
|
||||
List<StringName> messages;
|
||||
translation->get_message_list(&messages);
|
||||
|
||||
// `get_message_count()` takes all contexts into account.
|
||||
CHECK(translation->get_message_count() == 1);
|
||||
// Only the default context is taken into account.
|
||||
// Since "Hello" is now only present in a non-default context, it is not counted in the list of messages.
|
||||
CHECK(messages.size() == 0);
|
||||
|
||||
translation->add_message("Hello2", "Bonjour2");
|
||||
translation->add_message("Hello2", "Salut2", "friendly");
|
||||
translation->add_message("Hello3", "Bonjour3");
|
||||
messages.clear();
|
||||
translation->get_message_list(&messages);
|
||||
|
||||
// `get_message_count()` takes all contexts into account.
|
||||
CHECK(translation->get_message_count() == 4);
|
||||
// Only the default context is taken into account.
|
||||
CHECK(messages.size() == 2);
|
||||
// Messages are stored in a Map, don't assume ordering.
|
||||
CHECK(messages.find("Hello2"));
|
||||
CHECK(messages.find("Hello3"));
|
||||
}
|
||||
|
||||
TEST_CASE("[TranslationPO] Plural messages") {
|
||||
Ref<TranslationPO> translation = memnew(TranslationPO);
|
||||
translation->set_locale("fr");
|
||||
translation->set_plural_rule("Plural-Forms: nplurals=2; plural=(n >= 2);");
|
||||
CHECK(translation->get_plural_forms() == 2);
|
||||
|
||||
PackedStringArray plurals;
|
||||
plurals.push_back("Il y a %d pomme");
|
||||
plurals.push_back("Il y a %d pommes");
|
||||
translation->add_plural_message("There are %d apples", plurals);
|
||||
ERR_PRINT_OFF;
|
||||
// This is invalid, as the number passed to `get_plural_message()` may not be negative.
|
||||
CHECK(vformat(translation->get_plural_message("There are %d apples", "", -1), -1) == "");
|
||||
ERR_PRINT_ON;
|
||||
CHECK(vformat(translation->get_plural_message("There are %d apples", "", 0), 0) == "Il y a 0 pomme");
|
||||
CHECK(vformat(translation->get_plural_message("There are %d apples", "", 1), 1) == "Il y a 1 pomme");
|
||||
CHECK(vformat(translation->get_plural_message("There are %d apples", "", 2), 2) == "Il y a 2 pommes");
|
||||
}
|
||||
|
||||
#ifdef TOOLS_ENABLED
|
||||
TEST_CASE("[OptimizedTranslation] Generate from Translation and read messages") {
|
||||
Ref<Translation> translation = memnew(Translation);
|
||||
translation->set_locale("fr");
|
||||
translation->add_message("Hello", "Bonjour");
|
||||
translation->add_message("Hello2", "Bonjour2");
|
||||
translation->add_message("Hello3", "Bonjour3");
|
||||
|
||||
Ref<OptimizedTranslation> optimized_translation = memnew(OptimizedTranslation);
|
||||
optimized_translation->generate(translation);
|
||||
CHECK(optimized_translation->get_message("Hello") == "Bonjour");
|
||||
CHECK(optimized_translation->get_message("Hello2") == "Bonjour2");
|
||||
CHECK(optimized_translation->get_message("Hello3") == "Bonjour3");
|
||||
CHECK(optimized_translation->get_message("DoesNotExist") == "");
|
||||
|
||||
List<StringName> messages;
|
||||
// `get_message_list()` can't return the list of messages stored in an OptimizedTranslation.
|
||||
optimized_translation->get_message_list(&messages);
|
||||
CHECK(optimized_translation->get_message_count() == 0);
|
||||
CHECK(messages.size() == 0);
|
||||
}
|
||||
|
||||
TEST_CASE("[TranslationCSV] CSV import") {
|
||||
Ref<ResourceImporterCSVTranslation> import_csv_translation = memnew(ResourceImporterCSVTranslation);
|
||||
|
||||
HashMap<StringName, Variant> options;
|
||||
options["compress"] = false;
|
||||
options["delimiter"] = 0;
|
||||
|
||||
List<String> gen_files;
|
||||
|
||||
Error result = import_csv_translation->import(0, TestUtils::get_data_path("translations.csv"),
|
||||
"", options, nullptr, &gen_files);
|
||||
CHECK(result == OK);
|
||||
CHECK(gen_files.size() == 4);
|
||||
|
||||
TranslationServer *ts = TranslationServer::get_singleton();
|
||||
|
||||
for (const String &file : gen_files) {
|
||||
Ref<Translation> translation = ResourceLoader::load(file);
|
||||
CHECK(translation.is_valid());
|
||||
ts->add_translation(translation);
|
||||
}
|
||||
|
||||
ts->set_locale("en");
|
||||
|
||||
// `tr` can be called on any Object, we reuse TranslationServer for convenience.
|
||||
CHECK(ts->tr("GOOD_MORNING") == "Good Morning");
|
||||
CHECK(ts->tr("GOOD_EVENING") == "Good Evening");
|
||||
|
||||
ts->set_locale("de");
|
||||
|
||||
CHECK(ts->tr("GOOD_MORNING") == "Guten Morgen");
|
||||
CHECK(ts->tr("GOOD_EVENING") == "Good Evening"); // Left blank in CSV, should source from 'en'.
|
||||
|
||||
ts->set_locale("ja");
|
||||
|
||||
CHECK(ts->tr("GOOD_MORNING") == String::utf8("おはよう"));
|
||||
CHECK(ts->tr("GOOD_EVENING") == String::utf8("こんばんは"));
|
||||
|
||||
/* FIXME: This passes, but triggers a chain reaction that makes test_viewport
|
||||
* and test_text_edit explode in a billion glittery Unicode particles.
|
||||
ts->set_locale("fa");
|
||||
|
||||
CHECK(ts->tr("GOOD_MORNING") == String::utf8("صبح بخیر"));
|
||||
CHECK(ts->tr("GOOD_EVENING") == String::utf8("عصر بخیر"));
|
||||
*/
|
||||
}
|
||||
#endif // TOOLS_ENABLED
|
||||
|
||||
} // namespace TestTranslation
|
||||
223
tests/core/string/test_translation_server.h
Normal file
@@ -0,0 +1,223 @@
|
||||
/**************************************************************************/
|
||||
/* test_translation_server.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/string/translation_server.h"
|
||||
|
||||
#include "tests/test_macros.h"
|
||||
|
||||
namespace TestTranslationServer {
|
||||
TEST_CASE("[TranslationServer] Translation operations") {
|
||||
Ref<Translation> t1 = memnew(Translation);
|
||||
t1->set_locale("uk");
|
||||
t1->add_message("Good Morning", String(U"Добрий ранок"));
|
||||
|
||||
Ref<Translation> t2 = memnew(Translation);
|
||||
t2->set_locale("uk");
|
||||
t2->add_message("Hello Godot", String(U"你好戈多"));
|
||||
|
||||
TranslationServer *ts = TranslationServer::get_singleton();
|
||||
|
||||
// Adds translation for UK locale for the first time.
|
||||
int l_count_before = ts->get_loaded_locales().size();
|
||||
ts->add_translation(t1);
|
||||
int l_count_after = ts->get_loaded_locales().size();
|
||||
CHECK(l_count_after > l_count_before);
|
||||
|
||||
// Adds translation for UK locale again.
|
||||
ts->add_translation(t2);
|
||||
CHECK_EQ(ts->get_loaded_locales().size(), l_count_after);
|
||||
|
||||
// Removing that translation.
|
||||
ts->remove_translation(t2);
|
||||
CHECK_EQ(ts->get_loaded_locales().size(), l_count_after);
|
||||
|
||||
CHECK(ts->get_translation_object("uk").is_valid());
|
||||
|
||||
ts->set_locale("uk");
|
||||
CHECK(ts->translate("Good Morning") == String::utf8("Добрий ранок"));
|
||||
|
||||
ts->remove_translation(t1);
|
||||
CHECK(ts->get_translation_object("uk").is_null());
|
||||
// If no suitable Translation object has been found - the original message should be returned.
|
||||
CHECK(ts->translate("Good Morning") == "Good Morning");
|
||||
}
|
||||
|
||||
TEST_CASE("[TranslationServer] Locale operations") {
|
||||
TranslationServer *ts = TranslationServer::get_singleton();
|
||||
|
||||
// Language variant test; we supplied the variant of Español and the result should be the same string.
|
||||
String loc = "es_Hani_ES_tradnl";
|
||||
String res = ts->standardize_locale(loc);
|
||||
|
||||
CHECK(res == loc);
|
||||
|
||||
// No such variant in variant_map; should return everything except the variant.
|
||||
loc = "es_Hani_ES_missing";
|
||||
res = ts->standardize_locale(loc);
|
||||
|
||||
CHECK(res == "es_Hani_ES");
|
||||
|
||||
// Non-ISO language name check (Windows issue).
|
||||
loc = "iw_Hani_IL";
|
||||
res = ts->standardize_locale(loc);
|
||||
|
||||
CHECK(res == "he_Hani_IL");
|
||||
|
||||
// Country rename check.
|
||||
loc = "uk_Hani_UK";
|
||||
res = ts->standardize_locale(loc);
|
||||
|
||||
CHECK(res == "uk_Hani_GB");
|
||||
|
||||
// Supplying a script name that is not in the list.
|
||||
loc = "de_Wrong_DE";
|
||||
res = ts->standardize_locale(loc);
|
||||
|
||||
CHECK(res == "de_DE");
|
||||
|
||||
// No added defaults.
|
||||
loc = "es_ES";
|
||||
res = ts->standardize_locale(loc, true);
|
||||
|
||||
CHECK(res == "es_ES");
|
||||
|
||||
// Add default script.
|
||||
loc = "az_AZ";
|
||||
res = ts->standardize_locale(loc, true);
|
||||
|
||||
CHECK(res == "az_Latn_AZ");
|
||||
|
||||
// Add default country.
|
||||
loc = "pa_Arab";
|
||||
res = ts->standardize_locale(loc, true);
|
||||
|
||||
CHECK(res == "pa_Arab_PK");
|
||||
|
||||
// Add default script and country.
|
||||
loc = "zh";
|
||||
res = ts->standardize_locale(loc, true);
|
||||
|
||||
CHECK(res == "zh_Hans_CN");
|
||||
|
||||
// Explicitly don't add defaults.
|
||||
loc = "zh";
|
||||
res = ts->standardize_locale(loc, false);
|
||||
|
||||
CHECK(res == "zh");
|
||||
}
|
||||
|
||||
TEST_CASE("[TranslationServer] Comparing locales") {
|
||||
TranslationServer *ts = TranslationServer::get_singleton();
|
||||
|
||||
String locale_a = "es";
|
||||
String locale_b = "es";
|
||||
|
||||
// Exact match check.
|
||||
int res = ts->compare_locales(locale_a, locale_b);
|
||||
|
||||
CHECK(res == 10);
|
||||
|
||||
locale_a = "sr-Latn-CS";
|
||||
locale_b = "sr-Latn-RS";
|
||||
|
||||
// Script matches (+1) but country doesn't (-1).
|
||||
res = ts->compare_locales(locale_a, locale_b);
|
||||
|
||||
CHECK(res == 5);
|
||||
|
||||
locale_a = "uz-Cyrl-UZ";
|
||||
locale_b = "uz-Latn-UZ";
|
||||
|
||||
// Country matches (+1) but script doesn't (-1).
|
||||
res = ts->compare_locales(locale_a, locale_b);
|
||||
|
||||
CHECK(res == 5);
|
||||
|
||||
locale_a = "aa-Latn-ER";
|
||||
locale_b = "aa-Latn-ER-saaho";
|
||||
|
||||
// Script and country match (+2) with variant on one locale (+0).
|
||||
res = ts->compare_locales(locale_a, locale_b);
|
||||
|
||||
CHECK(res == 7);
|
||||
|
||||
locale_a = "uz-Cyrl-UZ";
|
||||
locale_b = "uz-Latn-KG";
|
||||
|
||||
// Both script and country mismatched (-2).
|
||||
res = ts->compare_locales(locale_a, locale_b);
|
||||
|
||||
CHECK(res == 3);
|
||||
|
||||
locale_a = "es-ES";
|
||||
locale_b = "es-AR";
|
||||
|
||||
// Mismatched country (-1).
|
||||
res = ts->compare_locales(locale_a, locale_b);
|
||||
|
||||
CHECK(res == 4);
|
||||
|
||||
locale_a = "es";
|
||||
locale_b = "es-AR";
|
||||
|
||||
// No country for one locale (+0).
|
||||
res = ts->compare_locales(locale_a, locale_b);
|
||||
|
||||
CHECK(res == 5);
|
||||
|
||||
locale_a = "es-EC";
|
||||
locale_b = "fr-LU";
|
||||
|
||||
// No match.
|
||||
res = ts->compare_locales(locale_a, locale_b);
|
||||
|
||||
CHECK(res == 0);
|
||||
|
||||
locale_a = "zh-HK";
|
||||
locale_b = "zh";
|
||||
|
||||
// In full standardization, zh-HK becomes zh_Hant_HK and zh becomes
|
||||
// zh_Hans_CN. Both script and country mismatch (-2).
|
||||
res = ts->compare_locales(locale_a, locale_b);
|
||||
|
||||
CHECK(res == 3);
|
||||
|
||||
locale_a = "zh-CN";
|
||||
locale_b = "zh";
|
||||
|
||||
// In full standardization, zh and zh-CN both become zh_Hans_CN for an
|
||||
// exact match.
|
||||
res = ts->compare_locales(locale_a, locale_b);
|
||||
|
||||
CHECK(res == 10);
|
||||
}
|
||||
} // namespace TestTranslationServer
|
||||
314
tests/core/templates/test_a_hash_map.h
Normal file
@@ -0,0 +1,314 @@
|
||||
/**************************************************************************/
|
||||
/* test_a_hash_map.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/templates/a_hash_map.h"
|
||||
|
||||
#include "tests/test_macros.h"
|
||||
|
||||
namespace TestAHashMap {
|
||||
|
||||
TEST_CASE("[AHashMap] List initialization") {
|
||||
AHashMap<int, String> map{ { 0, "A" }, { 1, "B" }, { 2, "C" }, { 3, "D" }, { 4, "E" } };
|
||||
|
||||
CHECK(map.size() == 5);
|
||||
CHECK(map[0] == "A");
|
||||
CHECK(map[1] == "B");
|
||||
CHECK(map[2] == "C");
|
||||
CHECK(map[3] == "D");
|
||||
CHECK(map[4] == "E");
|
||||
}
|
||||
|
||||
TEST_CASE("[AHashMap] List initialization with existing elements") {
|
||||
AHashMap<int, String> map{ { 0, "A" }, { 0, "B" }, { 0, "C" }, { 0, "D" }, { 0, "E" } };
|
||||
|
||||
CHECK(map.size() == 1);
|
||||
CHECK(map[0] == "E");
|
||||
}
|
||||
|
||||
TEST_CASE("[AHashMap] Insert element") {
|
||||
AHashMap<int, int> map;
|
||||
AHashMap<int, int>::Iterator e = map.insert(42, 84);
|
||||
|
||||
CHECK(e);
|
||||
CHECK(e->key == 42);
|
||||
CHECK(e->value == 84);
|
||||
CHECK(map[42] == 84);
|
||||
CHECK(map.has(42));
|
||||
CHECK(map.find(42));
|
||||
}
|
||||
|
||||
TEST_CASE("[AHashMap] Overwrite element") {
|
||||
AHashMap<int, int> map;
|
||||
map.insert(42, 84);
|
||||
map.insert(42, 1234);
|
||||
|
||||
CHECK(map[42] == 1234);
|
||||
}
|
||||
|
||||
TEST_CASE("[AHashMap] Erase via element") {
|
||||
AHashMap<int, int> map;
|
||||
AHashMap<int, int>::Iterator e = map.insert(42, 84);
|
||||
map.remove(e);
|
||||
CHECK(!map.has(42));
|
||||
CHECK(!map.find(42));
|
||||
}
|
||||
|
||||
TEST_CASE("[AHashMap] Erase via key") {
|
||||
AHashMap<int, int> map;
|
||||
map.insert(42, 84);
|
||||
map.erase(42);
|
||||
CHECK(!map.has(42));
|
||||
CHECK(!map.find(42));
|
||||
}
|
||||
|
||||
TEST_CASE("[AHashMap] Size") {
|
||||
AHashMap<int, int> map;
|
||||
map.insert(42, 84);
|
||||
map.insert(123, 84);
|
||||
map.insert(123, 84);
|
||||
map.insert(0, 84);
|
||||
map.insert(123485, 84);
|
||||
|
||||
CHECK(map.size() == 4);
|
||||
}
|
||||
|
||||
TEST_CASE("[AHashMap] Iteration") {
|
||||
AHashMap<int, int> map;
|
||||
|
||||
map.insert(42, 84);
|
||||
map.insert(123, 12385);
|
||||
map.insert(0, 12934);
|
||||
map.insert(123485, 1238888);
|
||||
map.insert(123, 111111);
|
||||
|
||||
Vector<Pair<int, int>> expected;
|
||||
expected.push_back(Pair<int, int>(42, 84));
|
||||
expected.push_back(Pair<int, int>(123, 111111));
|
||||
expected.push_back(Pair<int, int>(0, 12934));
|
||||
expected.push_back(Pair<int, int>(123485, 1238888));
|
||||
|
||||
int idx = 0;
|
||||
for (const KeyValue<int, int> &E : map) {
|
||||
CHECK(expected[idx] == Pair<int, int>(E.key, E.value));
|
||||
idx++;
|
||||
}
|
||||
|
||||
idx--;
|
||||
for (AHashMap<int, int>::Iterator it = map.last(); it; --it) {
|
||||
CHECK(expected[idx] == Pair<int, int>(it->key, it->value));
|
||||
idx--;
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("[AHashMap] Const iteration") {
|
||||
AHashMap<int, int> map;
|
||||
map.insert(42, 84);
|
||||
map.insert(123, 12385);
|
||||
map.insert(0, 12934);
|
||||
map.insert(123485, 1238888);
|
||||
map.insert(123, 111111);
|
||||
|
||||
const AHashMap<int, int> const_map = map;
|
||||
|
||||
Vector<Pair<int, int>> expected;
|
||||
expected.push_back(Pair<int, int>(42, 84));
|
||||
expected.push_back(Pair<int, int>(123, 111111));
|
||||
expected.push_back(Pair<int, int>(0, 12934));
|
||||
expected.push_back(Pair<int, int>(123485, 1238888));
|
||||
expected.push_back(Pair<int, int>(123, 111111));
|
||||
|
||||
int idx = 0;
|
||||
for (const KeyValue<int, int> &E : const_map) {
|
||||
CHECK(expected[idx] == Pair<int, int>(E.key, E.value));
|
||||
idx++;
|
||||
}
|
||||
|
||||
idx--;
|
||||
for (AHashMap<int, int>::ConstIterator it = const_map.last(); it; --it) {
|
||||
CHECK(expected[idx] == Pair<int, int>(it->key, it->value));
|
||||
idx--;
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("[AHashMap] Replace key") {
|
||||
AHashMap<int, int> map;
|
||||
map.insert(42, 84);
|
||||
map.insert(0, 12934);
|
||||
CHECK(map.replace_key(0, 1));
|
||||
CHECK(map.has(1));
|
||||
CHECK(map[1] == 12934);
|
||||
}
|
||||
|
||||
TEST_CASE("[AHashMap] Clear") {
|
||||
AHashMap<int, int> map;
|
||||
map.insert(42, 84);
|
||||
map.insert(123, 12385);
|
||||
map.insert(0, 12934);
|
||||
|
||||
map.clear();
|
||||
CHECK(!map.has(42));
|
||||
CHECK(map.size() == 0);
|
||||
CHECK(map.is_empty());
|
||||
}
|
||||
|
||||
TEST_CASE("[AHashMap] Get") {
|
||||
AHashMap<int, int> map;
|
||||
map.insert(42, 84);
|
||||
map.insert(123, 12385);
|
||||
map.insert(0, 12934);
|
||||
|
||||
CHECK(map.get(123) == 12385);
|
||||
map.get(123) = 10;
|
||||
CHECK(map.get(123) == 10);
|
||||
|
||||
CHECK(*map.getptr(0) == 12934);
|
||||
*map.getptr(0) = 1;
|
||||
CHECK(*map.getptr(0) == 1);
|
||||
|
||||
CHECK(map.get(42) == 84);
|
||||
CHECK(map.getptr(-10) == nullptr);
|
||||
}
|
||||
|
||||
TEST_CASE("[AHashMap] Insert, iterate and remove many elements") {
|
||||
const int elem_max = 1234;
|
||||
AHashMap<int, int> map;
|
||||
for (int i = 0; i < elem_max; i++) {
|
||||
map.insert(i, i);
|
||||
}
|
||||
|
||||
//insert order should have been kept
|
||||
int idx = 0;
|
||||
for (const KeyValue<int, int> &K : map) {
|
||||
CHECK(idx == K.key);
|
||||
CHECK(idx == K.value);
|
||||
CHECK(map.has(idx));
|
||||
idx++;
|
||||
}
|
||||
|
||||
Vector<int> elems_still_valid;
|
||||
|
||||
for (int i = 0; i < elem_max; i++) {
|
||||
if ((i % 5) == 0) {
|
||||
map.erase(i);
|
||||
} else {
|
||||
elems_still_valid.push_back(i);
|
||||
}
|
||||
}
|
||||
|
||||
CHECK(elems_still_valid.size() == map.size());
|
||||
|
||||
for (int i = 0; i < elems_still_valid.size(); i++) {
|
||||
CHECK(map.has(elems_still_valid[i]));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("[AHashMap] Insert, iterate and remove many strings") {
|
||||
const int elem_max = 432;
|
||||
AHashMap<String, String> map;
|
||||
|
||||
// To not print WARNING: Excessive collision count (NN), is the right hash function being used?
|
||||
ERR_PRINT_OFF;
|
||||
for (int i = 0; i < elem_max; i++) {
|
||||
map.insert(itos(i), itos(i));
|
||||
}
|
||||
ERR_PRINT_ON;
|
||||
|
||||
//insert order should have been kept
|
||||
int idx = 0;
|
||||
for (auto &K : map) {
|
||||
CHECK(itos(idx) == K.key);
|
||||
CHECK(itos(idx) == K.value);
|
||||
CHECK(map.has(itos(idx)));
|
||||
idx++;
|
||||
}
|
||||
|
||||
Vector<String> elems_still_valid;
|
||||
|
||||
for (int i = 0; i < elem_max; i++) {
|
||||
if ((i % 5) == 0) {
|
||||
map.erase(itos(i));
|
||||
} else {
|
||||
elems_still_valid.push_back(itos(i));
|
||||
}
|
||||
}
|
||||
|
||||
CHECK(elems_still_valid.size() == map.size());
|
||||
|
||||
for (int i = 0; i < elems_still_valid.size(); i++) {
|
||||
CHECK(map.has(elems_still_valid[i]));
|
||||
}
|
||||
|
||||
elems_still_valid.clear();
|
||||
}
|
||||
|
||||
TEST_CASE("[AHashMap] Copy constructor") {
|
||||
AHashMap<int, int> map0;
|
||||
const uint32_t count = 5;
|
||||
for (uint32_t i = 0; i < count; i++) {
|
||||
map0.insert(i, i);
|
||||
}
|
||||
AHashMap<int, int> map1(map0);
|
||||
CHECK(map0.size() == map1.size());
|
||||
CHECK(map0.get_capacity() == map1.get_capacity());
|
||||
CHECK(*map0.getptr(0) == *map1.getptr(0));
|
||||
}
|
||||
|
||||
TEST_CASE("[AHashMap] Operator =") {
|
||||
AHashMap<int, int> map0;
|
||||
AHashMap<int, int> map1;
|
||||
const uint32_t count = 5;
|
||||
map1.insert(1234, 1234);
|
||||
for (uint32_t i = 0; i < count; i++) {
|
||||
map0.insert(i, i);
|
||||
}
|
||||
map1 = map0;
|
||||
CHECK(map0.size() == map1.size());
|
||||
CHECK(map0.get_capacity() == map1.get_capacity());
|
||||
CHECK(*map0.getptr(0) == *map1.getptr(0));
|
||||
}
|
||||
|
||||
TEST_CASE("[AHashMap] Array methods") {
|
||||
AHashMap<int, int> map;
|
||||
for (int i = 0; i < 100; i++) {
|
||||
map.insert(100 - i, i);
|
||||
}
|
||||
for (int i = 0; i < 100; i++) {
|
||||
CHECK(map.get_by_index(i).value == i);
|
||||
}
|
||||
int index = map.get_index(1);
|
||||
CHECK(map.get_by_index(index).value == 99);
|
||||
CHECK(map.erase_by_index(index));
|
||||
CHECK(!map.erase_by_index(index));
|
||||
CHECK(map.get_index(1) == -1);
|
||||
}
|
||||
|
||||
} // namespace TestAHashMap
|
||||
563
tests/core/templates/test_command_queue.h
Normal file
@@ -0,0 +1,563 @@
|
||||
/**************************************************************************/
|
||||
/* test_command_queue.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/config/project_settings.h"
|
||||
#include "core/math/random_number_generator.h"
|
||||
#include "core/object/worker_thread_pool.h"
|
||||
#include "core/os/os.h"
|
||||
#include "core/os/thread.h"
|
||||
#include "core/templates/command_queue_mt.h"
|
||||
#include "tests/test_macros.h"
|
||||
|
||||
namespace TestCommandQueue {
|
||||
|
||||
class ThreadWork {
|
||||
Semaphore thread_sem;
|
||||
Semaphore main_sem;
|
||||
Mutex mut;
|
||||
int threading_errors = 0;
|
||||
enum State {
|
||||
MAIN_START,
|
||||
MAIN_DONE,
|
||||
THREAD_START,
|
||||
THREAD_DONE,
|
||||
} state;
|
||||
|
||||
public:
|
||||
ThreadWork() {
|
||||
mut.lock();
|
||||
state = MAIN_START;
|
||||
}
|
||||
~ThreadWork() {
|
||||
CHECK_MESSAGE(threading_errors == 0, "threads did not lock/unlock correctly");
|
||||
}
|
||||
void thread_wait_for_work() {
|
||||
thread_sem.wait();
|
||||
mut.lock();
|
||||
if (state != MAIN_DONE) {
|
||||
threading_errors++;
|
||||
}
|
||||
state = THREAD_START;
|
||||
}
|
||||
void thread_done_work() {
|
||||
if (state != THREAD_START) {
|
||||
threading_errors++;
|
||||
}
|
||||
state = THREAD_DONE;
|
||||
mut.unlock();
|
||||
main_sem.post();
|
||||
}
|
||||
|
||||
void main_wait_for_done() {
|
||||
main_sem.wait();
|
||||
mut.lock();
|
||||
if (state != THREAD_DONE) {
|
||||
threading_errors++;
|
||||
}
|
||||
state = MAIN_START;
|
||||
}
|
||||
void main_start_work() {
|
||||
if (state != MAIN_START) {
|
||||
threading_errors++;
|
||||
}
|
||||
state = MAIN_DONE;
|
||||
mut.unlock();
|
||||
thread_sem.post();
|
||||
}
|
||||
};
|
||||
|
||||
class SharedThreadState {
|
||||
public:
|
||||
ThreadWork reader_threadwork;
|
||||
ThreadWork writer_threadwork;
|
||||
|
||||
CommandQueueMT command_queue;
|
||||
|
||||
enum TestMsgType {
|
||||
TEST_MSG_FUNC1_TRANSFORM,
|
||||
TEST_MSG_FUNC2_TRANSFORM_FLOAT,
|
||||
TEST_MSG_FUNC3_TRANSFORMx6,
|
||||
TEST_MSGSYNC_FUNC1_TRANSFORM,
|
||||
TEST_MSGSYNC_FUNC2_TRANSFORM_FLOAT,
|
||||
TEST_MSGRET_FUNC1_TRANSFORM,
|
||||
TEST_MSGRET_FUNC2_TRANSFORM_FLOAT,
|
||||
TEST_MSG_MAX
|
||||
};
|
||||
|
||||
Vector<TestMsgType> message_types_to_write;
|
||||
bool during_writing = false;
|
||||
int message_count_to_read = 0;
|
||||
bool exit_threads = false;
|
||||
|
||||
Thread reader_thread;
|
||||
WorkerThreadPool::TaskID reader_task_id = WorkerThreadPool::INVALID_TASK_ID;
|
||||
Thread writer_thread;
|
||||
|
||||
int func1_count = 0;
|
||||
|
||||
void func1(Transform3D t) {
|
||||
func1_count++;
|
||||
}
|
||||
void func2(Transform3D t, float f) {
|
||||
func1_count++;
|
||||
}
|
||||
void func3(Transform3D t1, Transform3D t2, Transform3D t3, Transform3D t4, Transform3D t5, Transform3D t6) {
|
||||
func1_count++;
|
||||
}
|
||||
Transform3D func1r(Transform3D t) {
|
||||
func1_count++;
|
||||
return t;
|
||||
}
|
||||
Transform3D func2r(Transform3D t, float f) {
|
||||
func1_count++;
|
||||
return t;
|
||||
}
|
||||
|
||||
void add_msg_to_write(TestMsgType type) {
|
||||
message_types_to_write.push_back(type);
|
||||
}
|
||||
|
||||
void reader_thread_loop() {
|
||||
reader_threadwork.thread_wait_for_work();
|
||||
while (!exit_threads) {
|
||||
if (reader_task_id == WorkerThreadPool::INVALID_TASK_ID) {
|
||||
command_queue.flush_all();
|
||||
} else {
|
||||
if (message_count_to_read < 0) {
|
||||
command_queue.flush_all();
|
||||
}
|
||||
for (int i = 0; i < message_count_to_read; i++) {
|
||||
WorkerThreadPool::get_singleton()->yield();
|
||||
command_queue.wait_and_flush();
|
||||
}
|
||||
}
|
||||
message_count_to_read = 0;
|
||||
|
||||
reader_threadwork.thread_done_work();
|
||||
reader_threadwork.thread_wait_for_work();
|
||||
}
|
||||
command_queue.flush_all();
|
||||
reader_threadwork.thread_done_work();
|
||||
}
|
||||
static void static_reader_thread_loop(void *stsvoid) {
|
||||
SharedThreadState *sts = static_cast<SharedThreadState *>(stsvoid);
|
||||
sts->reader_thread_loop();
|
||||
}
|
||||
|
||||
void writer_thread_loop() {
|
||||
during_writing = false;
|
||||
writer_threadwork.thread_wait_for_work();
|
||||
while (!exit_threads) {
|
||||
Transform3D tr;
|
||||
Transform3D otr;
|
||||
float f = 1;
|
||||
during_writing = true;
|
||||
for (int i = 0; i < message_types_to_write.size(); i++) {
|
||||
TestMsgType msg_type = message_types_to_write[i];
|
||||
switch (msg_type) {
|
||||
case TEST_MSG_FUNC1_TRANSFORM:
|
||||
command_queue.push(this, &SharedThreadState::func1, tr);
|
||||
break;
|
||||
case TEST_MSG_FUNC2_TRANSFORM_FLOAT:
|
||||
command_queue.push(this, &SharedThreadState::func2, tr, f);
|
||||
break;
|
||||
case TEST_MSG_FUNC3_TRANSFORMx6:
|
||||
command_queue.push(this, &SharedThreadState::func3, tr, tr, tr, tr, tr, tr);
|
||||
break;
|
||||
case TEST_MSGSYNC_FUNC1_TRANSFORM:
|
||||
command_queue.push_and_sync(this, &SharedThreadState::func1, tr);
|
||||
break;
|
||||
case TEST_MSGSYNC_FUNC2_TRANSFORM_FLOAT:
|
||||
command_queue.push_and_sync(this, &SharedThreadState::func2, tr, f);
|
||||
break;
|
||||
case TEST_MSGRET_FUNC1_TRANSFORM:
|
||||
command_queue.push_and_ret(this, &SharedThreadState::func1r, &otr, tr);
|
||||
break;
|
||||
case TEST_MSGRET_FUNC2_TRANSFORM_FLOAT:
|
||||
command_queue.push_and_ret(this, &SharedThreadState::func2r, &otr, tr, f);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
message_types_to_write.clear();
|
||||
during_writing = false;
|
||||
|
||||
writer_threadwork.thread_done_work();
|
||||
writer_threadwork.thread_wait_for_work();
|
||||
}
|
||||
writer_threadwork.thread_done_work();
|
||||
}
|
||||
static void static_writer_thread_loop(void *stsvoid) {
|
||||
SharedThreadState *sts = static_cast<SharedThreadState *>(stsvoid);
|
||||
sts->writer_thread_loop();
|
||||
}
|
||||
|
||||
void init_threads(bool p_use_thread_pool_sync = false) {
|
||||
if (p_use_thread_pool_sync) {
|
||||
reader_task_id = WorkerThreadPool::get_singleton()->add_native_task(&SharedThreadState::static_reader_thread_loop, this, true);
|
||||
command_queue.set_pump_task_id(reader_task_id);
|
||||
} else {
|
||||
reader_thread.start(&SharedThreadState::static_reader_thread_loop, this);
|
||||
}
|
||||
writer_thread.start(&SharedThreadState::static_writer_thread_loop, this);
|
||||
}
|
||||
void destroy_threads() {
|
||||
exit_threads = true;
|
||||
reader_threadwork.main_start_work();
|
||||
writer_threadwork.main_start_work();
|
||||
|
||||
if (reader_task_id != WorkerThreadPool::INVALID_TASK_ID) {
|
||||
WorkerThreadPool::get_singleton()->wait_for_task_completion(reader_task_id);
|
||||
} else {
|
||||
reader_thread.wait_to_finish();
|
||||
}
|
||||
writer_thread.wait_to_finish();
|
||||
}
|
||||
|
||||
struct CopyMoveTestType {
|
||||
inline static int copy_count;
|
||||
inline static int move_count;
|
||||
int value = 0;
|
||||
|
||||
CopyMoveTestType(int p_value = 0) :
|
||||
value(p_value) {}
|
||||
|
||||
CopyMoveTestType(const CopyMoveTestType &p_other) :
|
||||
value(p_other.value) {
|
||||
copy_count++;
|
||||
}
|
||||
|
||||
CopyMoveTestType(CopyMoveTestType &&p_other) :
|
||||
value(p_other.value) {
|
||||
move_count++;
|
||||
}
|
||||
|
||||
CopyMoveTestType &operator=(const CopyMoveTestType &p_other) {
|
||||
value = p_other.value;
|
||||
copy_count++;
|
||||
return *this;
|
||||
}
|
||||
|
||||
CopyMoveTestType &operator=(CopyMoveTestType &&p_other) {
|
||||
value = p_other.value;
|
||||
move_count++;
|
||||
return *this;
|
||||
}
|
||||
};
|
||||
|
||||
void copy_move_test_copy(CopyMoveTestType p_test_type) {
|
||||
}
|
||||
void copy_move_test_ref(const CopyMoveTestType &p_test_type) {
|
||||
}
|
||||
void copy_move_test_move(CopyMoveTestType &&p_test_type) {
|
||||
}
|
||||
};
|
||||
|
||||
static void test_command_queue_basic(bool p_use_thread_pool_sync) {
|
||||
const char *COMMAND_QUEUE_SETTING = "memory/limits/command_queue/multithreading_queue_size_kb";
|
||||
ProjectSettings::get_singleton()->set_setting(COMMAND_QUEUE_SETTING, 1);
|
||||
SharedThreadState sts;
|
||||
sts.init_threads(p_use_thread_pool_sync);
|
||||
|
||||
sts.add_msg_to_write(SharedThreadState::TEST_MSG_FUNC1_TRANSFORM);
|
||||
sts.writer_threadwork.main_start_work();
|
||||
sts.writer_threadwork.main_wait_for_done();
|
||||
CHECK_MESSAGE(sts.func1_count == 0,
|
||||
"Control: no messages read before reader has run.");
|
||||
|
||||
sts.message_count_to_read = 1;
|
||||
sts.reader_threadwork.main_start_work();
|
||||
sts.reader_threadwork.main_wait_for_done();
|
||||
CHECK_MESSAGE(sts.func1_count == 1,
|
||||
"Reader should have read one message");
|
||||
|
||||
sts.message_count_to_read = -1;
|
||||
sts.reader_threadwork.main_start_work();
|
||||
sts.reader_threadwork.main_wait_for_done();
|
||||
CHECK_MESSAGE(sts.func1_count == 1,
|
||||
"Reader should have read no additional messages from flush_all");
|
||||
|
||||
sts.add_msg_to_write(SharedThreadState::TEST_MSG_FUNC1_TRANSFORM);
|
||||
sts.writer_threadwork.main_start_work();
|
||||
sts.writer_threadwork.main_wait_for_done();
|
||||
|
||||
sts.message_count_to_read = -1;
|
||||
sts.reader_threadwork.main_start_work();
|
||||
sts.reader_threadwork.main_wait_for_done();
|
||||
CHECK_MESSAGE(sts.func1_count == 2,
|
||||
"Reader should have read one additional message from flush_all");
|
||||
|
||||
sts.destroy_threads();
|
||||
|
||||
CHECK_MESSAGE(sts.func1_count == 2,
|
||||
"Reader should have read no additional messages after join");
|
||||
ProjectSettings::get_singleton()->set_setting(COMMAND_QUEUE_SETTING,
|
||||
ProjectSettings::get_singleton()->property_get_revert(COMMAND_QUEUE_SETTING));
|
||||
}
|
||||
|
||||
TEST_CASE("[CommandQueue] Test Queue Basics") {
|
||||
test_command_queue_basic(false);
|
||||
}
|
||||
|
||||
TEST_CASE("[CommandQueue] Test Queue Basics with WorkerThreadPool sync.") {
|
||||
test_command_queue_basic(true);
|
||||
}
|
||||
|
||||
TEST_CASE("[CommandQueue] Test Queue Wrapping to same spot.") {
|
||||
const char *COMMAND_QUEUE_SETTING = "memory/limits/command_queue/multithreading_queue_size_kb";
|
||||
ProjectSettings::get_singleton()->set_setting(COMMAND_QUEUE_SETTING, 1);
|
||||
SharedThreadState sts;
|
||||
sts.init_threads();
|
||||
|
||||
sts.add_msg_to_write(SharedThreadState::TEST_MSG_FUNC3_TRANSFORMx6);
|
||||
sts.add_msg_to_write(SharedThreadState::TEST_MSG_FUNC3_TRANSFORMx6);
|
||||
sts.add_msg_to_write(SharedThreadState::TEST_MSG_FUNC1_TRANSFORM);
|
||||
sts.writer_threadwork.main_start_work();
|
||||
sts.writer_threadwork.main_wait_for_done();
|
||||
|
||||
sts.message_count_to_read = -1;
|
||||
sts.reader_threadwork.main_start_work();
|
||||
sts.reader_threadwork.main_wait_for_done();
|
||||
CHECK_MESSAGE(sts.func1_count == 3,
|
||||
"Reader should have read at least three messages");
|
||||
|
||||
sts.add_msg_to_write(SharedThreadState::TEST_MSG_FUNC3_TRANSFORMx6);
|
||||
sts.writer_threadwork.main_start_work();
|
||||
sts.writer_threadwork.main_wait_for_done();
|
||||
sts.add_msg_to_write(SharedThreadState::TEST_MSG_FUNC1_TRANSFORM);
|
||||
sts.add_msg_to_write(SharedThreadState::TEST_MSG_FUNC3_TRANSFORMx6);
|
||||
sts.writer_threadwork.main_start_work();
|
||||
OS::get_singleton()->delay_usec(1000);
|
||||
|
||||
sts.message_count_to_read = -1;
|
||||
sts.reader_threadwork.main_start_work();
|
||||
OS::get_singleton()->delay_usec(1000);
|
||||
|
||||
sts.writer_threadwork.main_wait_for_done();
|
||||
sts.reader_threadwork.main_wait_for_done();
|
||||
CHECK_MESSAGE(sts.func1_count >= 3,
|
||||
"Reader should have read at least three messages");
|
||||
|
||||
sts.message_count_to_read = 6 - sts.func1_count;
|
||||
sts.reader_threadwork.main_start_work();
|
||||
|
||||
// The following will fail immediately.
|
||||
// The reason it hangs indefinitely in engine, is all subsequent calls to
|
||||
// CommandQueue.wait_and_flush_one will also fail.
|
||||
sts.reader_threadwork.main_wait_for_done();
|
||||
|
||||
// Because looping around uses an extra message, easiest to consume all.
|
||||
sts.message_count_to_read = -1;
|
||||
sts.reader_threadwork.main_start_work();
|
||||
sts.reader_threadwork.main_wait_for_done();
|
||||
CHECK_MESSAGE(sts.func1_count == 6,
|
||||
"Reader should have read both message sets");
|
||||
|
||||
sts.destroy_threads();
|
||||
|
||||
CHECK_MESSAGE(sts.func1_count == 6,
|
||||
"Reader should have read no additional messages after join");
|
||||
ProjectSettings::get_singleton()->set_setting(COMMAND_QUEUE_SETTING,
|
||||
ProjectSettings::get_singleton()->property_get_revert(COMMAND_QUEUE_SETTING));
|
||||
}
|
||||
|
||||
TEST_CASE("[CommandQueue] Test Queue Lapping") {
|
||||
const char *COMMAND_QUEUE_SETTING = "memory/limits/command_queue/multithreading_queue_size_kb";
|
||||
ProjectSettings::get_singleton()->set_setting(COMMAND_QUEUE_SETTING, 1);
|
||||
SharedThreadState sts;
|
||||
sts.init_threads();
|
||||
|
||||
sts.add_msg_to_write(SharedThreadState::TEST_MSG_FUNC1_TRANSFORM);
|
||||
sts.add_msg_to_write(SharedThreadState::TEST_MSG_FUNC3_TRANSFORMx6);
|
||||
sts.add_msg_to_write(SharedThreadState::TEST_MSG_FUNC3_TRANSFORMx6);
|
||||
sts.writer_threadwork.main_start_work();
|
||||
sts.writer_threadwork.main_wait_for_done();
|
||||
|
||||
// We need to read an extra message so that it triggers the dealloc logic once.
|
||||
// Otherwise, the queue will be considered full.
|
||||
sts.message_count_to_read = 3;
|
||||
sts.reader_threadwork.main_start_work();
|
||||
sts.reader_threadwork.main_wait_for_done();
|
||||
CHECK_MESSAGE(sts.func1_count == 3,
|
||||
"Reader should have read first set of messages");
|
||||
|
||||
sts.add_msg_to_write(SharedThreadState::TEST_MSG_FUNC3_TRANSFORMx6);
|
||||
sts.add_msg_to_write(SharedThreadState::TEST_MSG_FUNC3_TRANSFORMx6);
|
||||
sts.writer_threadwork.main_start_work();
|
||||
// Don't wait for these, because the queue isn't big enough.
|
||||
sts.writer_threadwork.main_wait_for_done();
|
||||
|
||||
sts.add_msg_to_write(SharedThreadState::TEST_MSG_FUNC2_TRANSFORM_FLOAT);
|
||||
sts.writer_threadwork.main_start_work();
|
||||
OS::get_singleton()->delay_usec(1000);
|
||||
|
||||
sts.message_count_to_read = 3;
|
||||
sts.reader_threadwork.main_start_work();
|
||||
sts.reader_threadwork.main_wait_for_done();
|
||||
|
||||
sts.writer_threadwork.main_wait_for_done();
|
||||
|
||||
sts.message_count_to_read = -1;
|
||||
sts.reader_threadwork.main_start_work();
|
||||
sts.reader_threadwork.main_wait_for_done();
|
||||
|
||||
CHECK_MESSAGE(sts.func1_count == 6,
|
||||
"Reader should have read rest of the messages after lapping writers.");
|
||||
|
||||
sts.destroy_threads();
|
||||
|
||||
CHECK_MESSAGE(sts.func1_count == 6,
|
||||
"Reader should have read no additional messages after join");
|
||||
ProjectSettings::get_singleton()->set_setting(COMMAND_QUEUE_SETTING,
|
||||
ProjectSettings::get_singleton()->property_get_revert(COMMAND_QUEUE_SETTING));
|
||||
}
|
||||
|
||||
TEST_CASE("[Stress][CommandQueue] Stress test command queue") {
|
||||
const char *COMMAND_QUEUE_SETTING = "memory/limits/command_queue/multithreading_queue_size_kb";
|
||||
ProjectSettings::get_singleton()->set_setting(COMMAND_QUEUE_SETTING, 1);
|
||||
SharedThreadState sts;
|
||||
sts.init_threads();
|
||||
|
||||
RandomNumberGenerator rng;
|
||||
|
||||
rng.set_seed(1837267);
|
||||
|
||||
int msgs_to_add = 2048;
|
||||
|
||||
for (int i = 0; i < msgs_to_add; i++) {
|
||||
// randi_range is inclusive, so allow any enum value except MAX.
|
||||
sts.add_msg_to_write((SharedThreadState::TestMsgType)rng.randi_range(0, SharedThreadState::TEST_MSG_MAX - 1));
|
||||
}
|
||||
sts.writer_threadwork.main_start_work();
|
||||
|
||||
int max_loop_iters = msgs_to_add * 2;
|
||||
int loop_iters = 0;
|
||||
while (sts.func1_count < msgs_to_add && loop_iters < max_loop_iters) {
|
||||
int remaining = (msgs_to_add - sts.func1_count);
|
||||
sts.message_count_to_read = rng.randi_range(1, remaining < 128 ? remaining : 128);
|
||||
if (loop_iters % 3 == 0) {
|
||||
sts.message_count_to_read = -1;
|
||||
}
|
||||
sts.reader_threadwork.main_start_work();
|
||||
sts.reader_threadwork.main_wait_for_done();
|
||||
loop_iters++;
|
||||
}
|
||||
CHECK_MESSAGE(loop_iters < max_loop_iters,
|
||||
"Reader needed too many iterations to read messages!");
|
||||
sts.writer_threadwork.main_wait_for_done();
|
||||
|
||||
sts.destroy_threads();
|
||||
|
||||
CHECK_MESSAGE(sts.func1_count == msgs_to_add,
|
||||
"Reader should have read no additional messages after join");
|
||||
ProjectSettings::get_singleton()->set_setting(COMMAND_QUEUE_SETTING,
|
||||
ProjectSettings::get_singleton()->property_get_revert(COMMAND_QUEUE_SETTING));
|
||||
}
|
||||
|
||||
TEST_CASE("[CommandQueue] Test Parameter Passing Semantics") {
|
||||
SharedThreadState sts;
|
||||
sts.init_threads();
|
||||
|
||||
SUBCASE("Testing with lvalue") {
|
||||
SharedThreadState::CopyMoveTestType::copy_count = 0;
|
||||
SharedThreadState::CopyMoveTestType::move_count = 0;
|
||||
|
||||
SharedThreadState::CopyMoveTestType lvalue(42);
|
||||
|
||||
SUBCASE("Pass by copy") {
|
||||
sts.command_queue.push(&sts, &SharedThreadState::copy_move_test_copy, lvalue);
|
||||
|
||||
sts.message_count_to_read = -1;
|
||||
sts.reader_threadwork.main_start_work();
|
||||
sts.reader_threadwork.main_wait_for_done();
|
||||
|
||||
CHECK(SharedThreadState::CopyMoveTestType::copy_count == 1);
|
||||
CHECK(SharedThreadState::CopyMoveTestType::move_count == 1);
|
||||
}
|
||||
|
||||
SUBCASE("Pass by reference") {
|
||||
sts.command_queue.push(&sts, &SharedThreadState::copy_move_test_ref, lvalue);
|
||||
|
||||
sts.message_count_to_read = -1;
|
||||
sts.reader_threadwork.main_start_work();
|
||||
sts.reader_threadwork.main_wait_for_done();
|
||||
|
||||
CHECK(SharedThreadState::CopyMoveTestType::copy_count == 1);
|
||||
CHECK(SharedThreadState::CopyMoveTestType::move_count == 0);
|
||||
}
|
||||
}
|
||||
|
||||
SUBCASE("Testing with rvalue") {
|
||||
SharedThreadState::CopyMoveTestType::copy_count = 0;
|
||||
SharedThreadState::CopyMoveTestType::move_count = 0;
|
||||
|
||||
SUBCASE("Pass by copy") {
|
||||
sts.command_queue.push(&sts, &SharedThreadState::copy_move_test_copy,
|
||||
SharedThreadState::CopyMoveTestType(43));
|
||||
|
||||
sts.message_count_to_read = -1;
|
||||
sts.reader_threadwork.main_start_work();
|
||||
sts.reader_threadwork.main_wait_for_done();
|
||||
|
||||
CHECK(SharedThreadState::CopyMoveTestType::copy_count == 0);
|
||||
CHECK(SharedThreadState::CopyMoveTestType::move_count == 2);
|
||||
}
|
||||
|
||||
SUBCASE("Pass by reference") {
|
||||
sts.command_queue.push(&sts, &SharedThreadState::copy_move_test_ref,
|
||||
SharedThreadState::CopyMoveTestType(43));
|
||||
|
||||
sts.message_count_to_read = -1;
|
||||
sts.reader_threadwork.main_start_work();
|
||||
sts.reader_threadwork.main_wait_for_done();
|
||||
|
||||
CHECK(SharedThreadState::CopyMoveTestType::copy_count == 0);
|
||||
CHECK(SharedThreadState::CopyMoveTestType::move_count == 1);
|
||||
}
|
||||
|
||||
SUBCASE("Pass by rvalue reference") {
|
||||
sts.command_queue.push(&sts, &SharedThreadState::copy_move_test_move,
|
||||
SharedThreadState::CopyMoveTestType(43));
|
||||
|
||||
sts.message_count_to_read = -1;
|
||||
sts.reader_threadwork.main_start_work();
|
||||
sts.reader_threadwork.main_wait_for_done();
|
||||
|
||||
CHECK(SharedThreadState::CopyMoveTestType::copy_count == 0);
|
||||
CHECK(SharedThreadState::CopyMoveTestType::move_count == 1);
|
||||
}
|
||||
}
|
||||
|
||||
sts.destroy_threads();
|
||||
}
|
||||
} // namespace TestCommandQueue
|
||||
107
tests/core/templates/test_fixed_vector.h
Normal file
@@ -0,0 +1,107 @@
|
||||
/**************************************************************************/
|
||||
/* test_fixed_vector.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/templates/fixed_vector.h"
|
||||
|
||||
#include "tests/test_macros.h"
|
||||
|
||||
namespace TestFixedVector {
|
||||
|
||||
TEST_CASE("[FixedVector] Basic Checks") {
|
||||
FixedVector<uint16_t, 1> vector;
|
||||
CHECK_EQ(vector.capacity(), 1);
|
||||
|
||||
CHECK_EQ(vector.size(), 0);
|
||||
CHECK(vector.is_empty());
|
||||
CHECK(!vector.is_full());
|
||||
|
||||
vector.push_back(5);
|
||||
CHECK_EQ(vector.size(), 1);
|
||||
CHECK_EQ(vector[0], 5);
|
||||
CHECK_EQ(vector.ptr()[0], 5);
|
||||
CHECK(!vector.is_empty());
|
||||
CHECK(vector.is_full());
|
||||
|
||||
vector.pop_back();
|
||||
CHECK_EQ(vector.size(), 0);
|
||||
CHECK(vector.is_empty());
|
||||
CHECK(!vector.is_full());
|
||||
|
||||
FixedVector<uint16_t, 2> vector1 = { 1, 2 };
|
||||
CHECK_EQ(vector1.capacity(), 2);
|
||||
CHECK_EQ(vector1.size(), 2);
|
||||
CHECK_EQ(vector1[0], 1);
|
||||
CHECK_EQ(vector1[1], 2);
|
||||
|
||||
FixedVector<uint16_t, 3> vector2(vector1);
|
||||
CHECK_EQ(vector2.capacity(), 3);
|
||||
CHECK_EQ(vector2.size(), 2);
|
||||
CHECK_EQ(vector2[0], 1);
|
||||
CHECK_EQ(vector2[1], 2);
|
||||
|
||||
FixedVector<Variant, 3> vector_variant;
|
||||
CHECK_EQ(vector_variant.size(), 0);
|
||||
CHECK_EQ(vector_variant.capacity(), 3);
|
||||
vector_variant.resize_initialized(3);
|
||||
vector_variant[0] = "Test";
|
||||
vector_variant[1] = 1;
|
||||
CHECK_EQ(vector_variant.capacity(), 3);
|
||||
CHECK_EQ(vector_variant.size(), 3);
|
||||
CHECK_EQ(vector_variant[0], "Test");
|
||||
CHECK_EQ(vector_variant[1], Variant(1));
|
||||
CHECK_EQ(vector_variant[2].get_type(), Variant::NIL);
|
||||
}
|
||||
|
||||
TEST_CASE("[FixedVector] Alignment Checks") {
|
||||
FixedVector<uint16_t, 4> vector_uint16;
|
||||
vector_uint16.resize_uninitialized(4);
|
||||
CHECK((size_t)&vector_uint16[0] % alignof(uint16_t) == 0);
|
||||
CHECK((size_t)&vector_uint16[1] % alignof(uint16_t) == 0);
|
||||
CHECK((size_t)&vector_uint16[2] % alignof(uint16_t) == 0);
|
||||
CHECK((size_t)&vector_uint16[3] % alignof(uint16_t) == 0);
|
||||
|
||||
FixedVector<uint32_t, 4> vector_uint32;
|
||||
vector_uint32.resize_uninitialized(4);
|
||||
CHECK((size_t)&vector_uint32[0] % alignof(uint32_t) == 0);
|
||||
CHECK((size_t)&vector_uint32[1] % alignof(uint32_t) == 0);
|
||||
CHECK((size_t)&vector_uint32[2] % alignof(uint32_t) == 0);
|
||||
CHECK((size_t)&vector_uint32[3] % alignof(uint32_t) == 0);
|
||||
|
||||
FixedVector<uint64_t, 4> vector_uint64;
|
||||
vector_uint64.resize_uninitialized(4);
|
||||
CHECK((size_t)&vector_uint64[0] % alignof(uint64_t) == 0);
|
||||
CHECK((size_t)&vector_uint64[1] % alignof(uint64_t) == 0);
|
||||
CHECK((size_t)&vector_uint64[2] % alignof(uint64_t) == 0);
|
||||
CHECK((size_t)&vector_uint64[3] % alignof(uint64_t) == 0);
|
||||
}
|
||||
|
||||
} //namespace TestFixedVector
|
||||
176
tests/core/templates/test_hash_map.h
Normal file
@@ -0,0 +1,176 @@
|
||||
/**************************************************************************/
|
||||
/* test_hash_map.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/templates/hash_map.h"
|
||||
|
||||
#include "tests/test_macros.h"
|
||||
|
||||
namespace TestHashMap {
|
||||
|
||||
TEST_CASE("[HashMap] List initialization") {
|
||||
HashMap<int, String> map{ { 0, "A" }, { 1, "B" }, { 2, "C" }, { 3, "D" }, { 4, "E" } };
|
||||
|
||||
CHECK(map.size() == 5);
|
||||
CHECK(map[0] == "A");
|
||||
CHECK(map[1] == "B");
|
||||
CHECK(map[2] == "C");
|
||||
CHECK(map[3] == "D");
|
||||
CHECK(map[4] == "E");
|
||||
}
|
||||
|
||||
TEST_CASE("[HashMap] List initialization with existing elements") {
|
||||
HashMap<int, String> map{ { 0, "A" }, { 0, "B" }, { 0, "C" }, { 0, "D" }, { 0, "E" } };
|
||||
|
||||
CHECK(map.size() == 1);
|
||||
CHECK(map[0] == "E");
|
||||
}
|
||||
|
||||
TEST_CASE("[HashMap] Insert element") {
|
||||
HashMap<int, int> map;
|
||||
HashMap<int, int>::Iterator e = map.insert(42, 84);
|
||||
|
||||
CHECK(e);
|
||||
CHECK(e->key == 42);
|
||||
CHECK(e->value == 84);
|
||||
CHECK(map[42] == 84);
|
||||
CHECK(map.has(42));
|
||||
CHECK(map.find(42));
|
||||
}
|
||||
|
||||
TEST_CASE("[HashMap] Overwrite element") {
|
||||
HashMap<int, int> map;
|
||||
map.insert(42, 84);
|
||||
map.insert(42, 1234);
|
||||
|
||||
CHECK(map[42] == 1234);
|
||||
}
|
||||
|
||||
TEST_CASE("[HashMap] Erase via element") {
|
||||
HashMap<int, int> map;
|
||||
HashMap<int, int>::Iterator e = map.insert(42, 84);
|
||||
map.remove(e);
|
||||
CHECK(!map.has(42));
|
||||
CHECK(!map.find(42));
|
||||
}
|
||||
|
||||
TEST_CASE("[HashMap] Erase via key") {
|
||||
HashMap<int, int> map;
|
||||
map.insert(42, 84);
|
||||
map.erase(42);
|
||||
CHECK(!map.has(42));
|
||||
CHECK(!map.find(42));
|
||||
}
|
||||
|
||||
TEST_CASE("[HashMap] Size") {
|
||||
HashMap<int, int> map;
|
||||
map.insert(42, 84);
|
||||
map.insert(123, 84);
|
||||
map.insert(123, 84);
|
||||
map.insert(0, 84);
|
||||
map.insert(123485, 84);
|
||||
|
||||
CHECK(map.size() == 4);
|
||||
}
|
||||
|
||||
TEST_CASE("[HashMap] Iteration") {
|
||||
HashMap<int, int> map;
|
||||
map.insert(42, 84);
|
||||
map.insert(123, 12385);
|
||||
map.insert(0, 12934);
|
||||
map.insert(123485, 1238888);
|
||||
map.insert(123, 111111);
|
||||
|
||||
Vector<Pair<int, int>> expected;
|
||||
expected.push_back(Pair<int, int>(42, 84));
|
||||
expected.push_back(Pair<int, int>(123, 111111));
|
||||
expected.push_back(Pair<int, int>(0, 12934));
|
||||
expected.push_back(Pair<int, int>(123485, 1238888));
|
||||
|
||||
int idx = 0;
|
||||
for (const KeyValue<int, int> &E : map) {
|
||||
CHECK(expected[idx] == Pair<int, int>(E.key, E.value));
|
||||
++idx;
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("[HashMap] Const iteration") {
|
||||
HashMap<int, int> map;
|
||||
map.insert(42, 84);
|
||||
map.insert(123, 12385);
|
||||
map.insert(0, 12934);
|
||||
map.insert(123485, 1238888);
|
||||
map.insert(123, 111111);
|
||||
|
||||
const HashMap<int, int> const_map = map;
|
||||
|
||||
Vector<Pair<int, int>> expected;
|
||||
expected.push_back(Pair<int, int>(42, 84));
|
||||
expected.push_back(Pair<int, int>(123, 111111));
|
||||
expected.push_back(Pair<int, int>(0, 12934));
|
||||
expected.push_back(Pair<int, int>(123485, 1238888));
|
||||
expected.push_back(Pair<int, int>(123, 111111));
|
||||
|
||||
int idx = 0;
|
||||
for (const KeyValue<int, int> &E : const_map) {
|
||||
CHECK(expected[idx] == Pair<int, int>(E.key, E.value));
|
||||
++idx;
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("[HashMap] Sort") {
|
||||
HashMap<int, int> hashmap;
|
||||
int shuffled_ints[]{ 6, 1, 9, 8, 3, 0, 4, 5, 7, 2 };
|
||||
|
||||
for (int i : shuffled_ints) {
|
||||
hashmap[i] = i;
|
||||
}
|
||||
hashmap.sort();
|
||||
|
||||
int i = 0;
|
||||
for (const KeyValue<int, int> &kv : hashmap) {
|
||||
CHECK_EQ(kv.key, i);
|
||||
i++;
|
||||
}
|
||||
|
||||
struct ReverseSort {
|
||||
bool operator()(const KeyValue<int, int> &p_a, const KeyValue<int, int> &p_b) {
|
||||
return p_a.key > p_b.key;
|
||||
}
|
||||
};
|
||||
hashmap.sort_custom<ReverseSort>();
|
||||
|
||||
for (const KeyValue<int, int> &kv : hashmap) {
|
||||
i--;
|
||||
CHECK_EQ(kv.key, i);
|
||||
}
|
||||
}
|
||||
} // namespace TestHashMap
|
||||
257
tests/core/templates/test_hash_set.h
Normal file
@@ -0,0 +1,257 @@
|
||||
/**************************************************************************/
|
||||
/* test_hash_set.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/templates/hash_set.h"
|
||||
|
||||
#include "tests/test_macros.h"
|
||||
|
||||
namespace TestHashSet {
|
||||
|
||||
TEST_CASE("[HashSet] List initialization") {
|
||||
HashSet<int> set{ 0, 1, 2, 3, 4 };
|
||||
|
||||
CHECK(set.size() == 5);
|
||||
CHECK(set.has(0));
|
||||
CHECK(set.has(1));
|
||||
CHECK(set.has(2));
|
||||
CHECK(set.has(3));
|
||||
CHECK(set.has(4));
|
||||
}
|
||||
|
||||
TEST_CASE("[HashSet] List initialization with existing elements") {
|
||||
HashSet<int> set{ 0, 0, 0, 0, 0 };
|
||||
|
||||
CHECK(set.size() == 1);
|
||||
CHECK(set.has(0));
|
||||
}
|
||||
|
||||
TEST_CASE("[HashSet] Insert element") {
|
||||
HashSet<int> set;
|
||||
HashSet<int>::Iterator e = set.insert(42);
|
||||
|
||||
CHECK(e);
|
||||
CHECK(*e == 42);
|
||||
CHECK(set.has(42));
|
||||
CHECK(set.find(42));
|
||||
set.reset();
|
||||
}
|
||||
|
||||
TEST_CASE("[HashSet] Insert existing element") {
|
||||
HashSet<int> set;
|
||||
set.insert(42);
|
||||
set.insert(42);
|
||||
|
||||
CHECK(set.has(42));
|
||||
CHECK(set.size() == 1);
|
||||
}
|
||||
|
||||
TEST_CASE("[HashSet] Insert, iterate and remove many elements") {
|
||||
const int elem_max = 12343;
|
||||
HashSet<int> set;
|
||||
for (int i = 0; i < elem_max; i++) {
|
||||
set.insert(i);
|
||||
}
|
||||
|
||||
//insert order should have been kept
|
||||
int idx = 0;
|
||||
for (const int &K : set) {
|
||||
CHECK(idx == K);
|
||||
CHECK(set.has(idx));
|
||||
idx++;
|
||||
}
|
||||
|
||||
Vector<int> elems_still_valid;
|
||||
|
||||
for (int i = 0; i < elem_max; i++) {
|
||||
if ((i % 5) == 0) {
|
||||
set.erase(i);
|
||||
} else {
|
||||
elems_still_valid.push_back(i);
|
||||
}
|
||||
}
|
||||
|
||||
CHECK(elems_still_valid.size() == set.size());
|
||||
|
||||
for (int i = 0; i < elems_still_valid.size(); i++) {
|
||||
CHECK(set.has(elems_still_valid[i]));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("[HashSet] Insert, iterate and remove many strings") {
|
||||
// This tests a key that uses allocation, to see if any leaks occur
|
||||
|
||||
uint64_t pre_mem = Memory::get_mem_usage();
|
||||
const int elem_max = 4018;
|
||||
HashSet<String> set;
|
||||
for (int i = 0; i < elem_max; i++) {
|
||||
set.insert(itos(i));
|
||||
}
|
||||
|
||||
//insert order should have been kept
|
||||
int idx = 0;
|
||||
for (const String &K : set) {
|
||||
CHECK(itos(idx) == K);
|
||||
CHECK(set.has(itos(idx)));
|
||||
idx++;
|
||||
}
|
||||
|
||||
Vector<String> elems_still_valid;
|
||||
|
||||
for (int i = 0; i < elem_max; i++) {
|
||||
if ((i % 5) == 0) {
|
||||
set.erase(itos(i));
|
||||
} else {
|
||||
elems_still_valid.push_back(itos(i));
|
||||
}
|
||||
}
|
||||
|
||||
CHECK(elems_still_valid.size() == set.size());
|
||||
|
||||
for (int i = 0; i < elems_still_valid.size(); i++) {
|
||||
CHECK(set.has(elems_still_valid[i]));
|
||||
}
|
||||
|
||||
elems_still_valid.clear();
|
||||
set.reset();
|
||||
|
||||
CHECK(Memory::get_mem_usage() == pre_mem);
|
||||
}
|
||||
|
||||
TEST_CASE("[HashSet] Erase via element") {
|
||||
HashSet<int> set;
|
||||
HashSet<int>::Iterator e = set.insert(42);
|
||||
set.remove(e);
|
||||
CHECK(!set.has(42));
|
||||
CHECK(!set.find(42));
|
||||
}
|
||||
|
||||
TEST_CASE("[HashSet] Erase via key") {
|
||||
HashSet<int> set;
|
||||
set.insert(42);
|
||||
set.insert(49);
|
||||
set.erase(42);
|
||||
CHECK(!set.has(42));
|
||||
CHECK(!set.find(42));
|
||||
}
|
||||
|
||||
TEST_CASE("[HashSet] Insert and erase half elements") {
|
||||
HashSet<int> set;
|
||||
set.insert(1);
|
||||
set.insert(2);
|
||||
set.insert(3);
|
||||
set.insert(4);
|
||||
set.erase(1);
|
||||
set.erase(3);
|
||||
|
||||
CHECK(set.size() == 2);
|
||||
CHECK(set.has(2));
|
||||
CHECK(set.has(4));
|
||||
}
|
||||
|
||||
TEST_CASE("[HashSet] Size") {
|
||||
HashSet<int> set;
|
||||
set.insert(42);
|
||||
set.insert(123);
|
||||
set.insert(123);
|
||||
set.insert(0);
|
||||
set.insert(123485);
|
||||
|
||||
CHECK(set.size() == 4);
|
||||
}
|
||||
|
||||
TEST_CASE("[HashSet] Iteration") {
|
||||
HashSet<int> set;
|
||||
set.insert(42);
|
||||
set.insert(123);
|
||||
set.insert(0);
|
||||
set.insert(123485);
|
||||
|
||||
Vector<int> expected;
|
||||
expected.push_back(42);
|
||||
expected.push_back(123);
|
||||
expected.push_back(0);
|
||||
expected.push_back(123485);
|
||||
|
||||
int idx = 0;
|
||||
for (const int &E : set) {
|
||||
CHECK(expected[idx] == E);
|
||||
++idx;
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("[HashSet] Copy") {
|
||||
HashSet<int> set;
|
||||
set.insert(42);
|
||||
set.insert(123);
|
||||
set.insert(0);
|
||||
set.insert(123485);
|
||||
|
||||
Vector<int> expected;
|
||||
expected.push_back(42);
|
||||
expected.push_back(123);
|
||||
expected.push_back(0);
|
||||
expected.push_back(123485);
|
||||
|
||||
HashSet<int> copy_assign = set;
|
||||
|
||||
int idx = 0;
|
||||
for (const int &E : copy_assign) {
|
||||
CHECK(expected[idx] == E);
|
||||
++idx;
|
||||
}
|
||||
|
||||
HashSet<int> copy_construct(set);
|
||||
|
||||
idx = 0;
|
||||
for (const int &E : copy_construct) {
|
||||
CHECK(expected[idx] == E);
|
||||
++idx;
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("[HashSet] Equality") {
|
||||
// Empty sets.
|
||||
CHECK(HashSet<int>{} == HashSet<int>{});
|
||||
CHECK(HashSet<int>{} != HashSet<int>{ 1, 2, 3 });
|
||||
CHECK(HashSet<int>{ 1, 2, 3 } != HashSet<int>{});
|
||||
|
||||
// Different length.
|
||||
CHECK(HashSet<int>{ 1, 2, 3 } != HashSet<int>{ 1, 2, 3, 4 });
|
||||
CHECK(HashSet<int>{ 1, 2, 3, 4 } != HashSet<int>{ 4, 3, 2 });
|
||||
|
||||
// Same length.
|
||||
CHECK(HashSet<int>{ 1, 2, 3 } == HashSet<int>{ 1, 2, 3 });
|
||||
CHECK(HashSet<int>{ 1, 2, 3 } == HashSet<int>{ 3, 2, 1 });
|
||||
CHECK(HashSet<int>{ 1, 2, 3 } != HashSet<int>{ 1, 2, 8 });
|
||||
}
|
||||
|
||||
} // namespace TestHashSet
|
||||
601
tests/core/templates/test_list.h
Normal file
@@ -0,0 +1,601 @@
|
||||
/**************************************************************************/
|
||||
/* test_list.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/templates/list.h"
|
||||
|
||||
#include "tests/test_macros.h"
|
||||
|
||||
namespace TestList {
|
||||
|
||||
static void populate_integers(List<int> &p_list, List<int>::Element *r_elements[], int num_elements) {
|
||||
p_list.clear();
|
||||
for (int i = 0; i < num_elements; ++i) {
|
||||
List<int>::Element *n = p_list.push_back(i);
|
||||
r_elements[i] = n;
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("[List] List initialization") {
|
||||
List<int> list{ 0, 1, 2, 3, 4 };
|
||||
|
||||
CHECK(list.size() == 5);
|
||||
CHECK(list.get(0) == 0);
|
||||
CHECK(list.get(1) == 1);
|
||||
CHECK(list.get(2) == 2);
|
||||
CHECK(list.get(3) == 3);
|
||||
CHECK(list.get(4) == 4);
|
||||
}
|
||||
|
||||
TEST_CASE("[List] Push/pop back") {
|
||||
List<String> list;
|
||||
|
||||
List<String>::Element *n;
|
||||
n = list.push_back("A");
|
||||
CHECK(n->get() == "A");
|
||||
n = list.push_back("B");
|
||||
CHECK(n->get() == "B");
|
||||
n = list.push_back("C");
|
||||
CHECK(n->get() == "C");
|
||||
|
||||
CHECK(list.size() == 3);
|
||||
CHECK(!list.is_empty());
|
||||
|
||||
String v;
|
||||
v = list.back()->get();
|
||||
list.pop_back();
|
||||
CHECK(v == "C");
|
||||
v = list.back()->get();
|
||||
list.pop_back();
|
||||
CHECK(v == "B");
|
||||
v = list.back()->get();
|
||||
list.pop_back();
|
||||
CHECK(v == "A");
|
||||
|
||||
CHECK(list.size() == 0);
|
||||
CHECK(list.is_empty());
|
||||
|
||||
CHECK(list.back() == nullptr);
|
||||
CHECK(list.front() == nullptr);
|
||||
}
|
||||
|
||||
TEST_CASE("[List] Push/pop front") {
|
||||
List<String> list;
|
||||
|
||||
List<String>::Element *n;
|
||||
n = list.push_front("A");
|
||||
CHECK(n->get() == "A");
|
||||
n = list.push_front("B");
|
||||
CHECK(n->get() == "B");
|
||||
n = list.push_front("C");
|
||||
CHECK(n->get() == "C");
|
||||
|
||||
CHECK(list.size() == 3);
|
||||
CHECK(!list.is_empty());
|
||||
|
||||
String v;
|
||||
v = list.front()->get();
|
||||
list.pop_front();
|
||||
CHECK(v == "C");
|
||||
v = list.front()->get();
|
||||
list.pop_front();
|
||||
CHECK(v == "B");
|
||||
v = list.front()->get();
|
||||
list.pop_front();
|
||||
CHECK(v == "A");
|
||||
|
||||
CHECK(list.size() == 0);
|
||||
CHECK(list.is_empty());
|
||||
|
||||
CHECK(list.back() == nullptr);
|
||||
CHECK(list.front() == nullptr);
|
||||
}
|
||||
|
||||
TEST_CASE("[List] Set and get") {
|
||||
List<String> list;
|
||||
list.push_back("A");
|
||||
|
||||
List<String>::Element *n = list.front();
|
||||
CHECK(n->get() == "A");
|
||||
|
||||
n->set("X");
|
||||
CHECK(n->get() == "X");
|
||||
}
|
||||
|
||||
TEST_CASE("[List] Insert before") {
|
||||
List<String> list;
|
||||
List<String>::Element *a = list.push_back("A");
|
||||
List<String>::Element *b = list.push_back("B");
|
||||
List<String>::Element *c = list.push_back("C");
|
||||
|
||||
list.insert_before(b, "I");
|
||||
|
||||
CHECK(a->next()->get() == "I");
|
||||
CHECK(c->prev()->prev()->get() == "I");
|
||||
CHECK(list.front()->next()->get() == "I");
|
||||
CHECK(list.back()->prev()->prev()->get() == "I");
|
||||
}
|
||||
|
||||
TEST_CASE("[List] Insert after") {
|
||||
List<String> list;
|
||||
List<String>::Element *a = list.push_back("A");
|
||||
List<String>::Element *b = list.push_back("B");
|
||||
List<String>::Element *c = list.push_back("C");
|
||||
|
||||
list.insert_after(b, "I");
|
||||
|
||||
CHECK(a->next()->next()->get() == "I");
|
||||
CHECK(c->prev()->get() == "I");
|
||||
CHECK(list.front()->next()->next()->get() == "I");
|
||||
CHECK(list.back()->prev()->get() == "I");
|
||||
}
|
||||
|
||||
TEST_CASE("[List] Insert before null") {
|
||||
List<String> list;
|
||||
List<String>::Element *a = list.push_back("A");
|
||||
List<String>::Element *b = list.push_back("B");
|
||||
List<String>::Element *c = list.push_back("C");
|
||||
|
||||
list.insert_before(nullptr, "I");
|
||||
|
||||
CHECK(a->next()->get() == "B");
|
||||
CHECK(b->get() == "B");
|
||||
CHECK(c->prev()->prev()->get() == "A");
|
||||
CHECK(list.front()->next()->get() == "B");
|
||||
CHECK(list.back()->prev()->prev()->get() == "B");
|
||||
CHECK(list.back()->get() == "I");
|
||||
}
|
||||
|
||||
TEST_CASE("[List] Insert after null") {
|
||||
List<String> list;
|
||||
List<String>::Element *a = list.push_back("A");
|
||||
List<String>::Element *b = list.push_back("B");
|
||||
List<String>::Element *c = list.push_back("C");
|
||||
|
||||
list.insert_after(nullptr, "I");
|
||||
|
||||
CHECK(a->next()->get() == "B");
|
||||
CHECK(b->get() == "B");
|
||||
CHECK(c->prev()->prev()->get() == "A");
|
||||
CHECK(list.front()->next()->get() == "B");
|
||||
CHECK(list.back()->prev()->prev()->get() == "B");
|
||||
CHECK(list.back()->get() == "I");
|
||||
}
|
||||
|
||||
TEST_CASE("[List] Find") {
|
||||
List<int> list;
|
||||
List<int>::Element *n[10];
|
||||
// Indices match values.
|
||||
populate_integers(list, n, 10);
|
||||
|
||||
for (int i = 0; i < 10; ++i) {
|
||||
CHECK(n[i]->get() == list.find(i)->get());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("[List] Erase (by value)") {
|
||||
List<int> list;
|
||||
List<int>::Element *n[4];
|
||||
// Indices match values.
|
||||
populate_integers(list, n, 4);
|
||||
|
||||
CHECK(list.front()->next()->next()->get() == 2);
|
||||
bool erased = list.erase(2); // 0, 1, 3.
|
||||
CHECK(erased);
|
||||
CHECK(list.size() == 3);
|
||||
|
||||
// The pointer n[2] points to the freed memory which is not reset to zero,
|
||||
// so the below assertion may pass, but this relies on undefined behavior.
|
||||
// CHECK(n[2]->get() == 2);
|
||||
|
||||
CHECK(list.front()->get() == 0);
|
||||
CHECK(list.front()->next()->next()->get() == 3);
|
||||
CHECK(list.back()->get() == 3);
|
||||
CHECK(list.back()->prev()->get() == 1);
|
||||
|
||||
CHECK(n[1]->next()->get() == 3);
|
||||
CHECK(n[3]->prev()->get() == 1);
|
||||
|
||||
erased = list.erase(9000); // Doesn't exist.
|
||||
CHECK(!erased);
|
||||
}
|
||||
|
||||
TEST_CASE("[List] Erase (by element)") {
|
||||
List<int> list;
|
||||
List<int>::Element *n[4];
|
||||
// Indices match values.
|
||||
populate_integers(list, n, 4);
|
||||
|
||||
bool erased = list.erase(n[2]);
|
||||
CHECK(erased);
|
||||
CHECK(list.size() == 3);
|
||||
CHECK(n[1]->next()->get() == 3);
|
||||
CHECK(n[3]->prev()->get() == 1);
|
||||
}
|
||||
|
||||
TEST_CASE("[List] Element erase") {
|
||||
List<int> list;
|
||||
List<int>::Element *n[4];
|
||||
// Indices match values.
|
||||
populate_integers(list, n, 4);
|
||||
|
||||
n[2]->erase();
|
||||
|
||||
CHECK(list.size() == 3);
|
||||
CHECK(n[1]->next()->get() == 3);
|
||||
CHECK(n[3]->prev()->get() == 1);
|
||||
}
|
||||
|
||||
TEST_CASE("[List] Clear") {
|
||||
List<int> list;
|
||||
List<int>::Element *n[100];
|
||||
populate_integers(list, n, 100);
|
||||
|
||||
list.clear();
|
||||
|
||||
CHECK(list.size() == 0);
|
||||
CHECK(list.is_empty());
|
||||
}
|
||||
|
||||
TEST_CASE("[List] Invert") {
|
||||
List<int> list;
|
||||
List<int>::Element *n[4];
|
||||
populate_integers(list, n, 4);
|
||||
|
||||
list.reverse();
|
||||
|
||||
CHECK(list.front()->get() == 3);
|
||||
CHECK(list.front()->next()->get() == 2);
|
||||
CHECK(list.back()->prev()->get() == 1);
|
||||
CHECK(list.back()->get() == 0);
|
||||
}
|
||||
|
||||
TEST_CASE("[List] Move to front") {
|
||||
List<int> list;
|
||||
List<int>::Element *n[4];
|
||||
populate_integers(list, n, 4);
|
||||
|
||||
list.move_to_front(n[3]);
|
||||
|
||||
CHECK(list.front()->get() == 3);
|
||||
CHECK(list.back()->get() == 2);
|
||||
}
|
||||
|
||||
TEST_CASE("[List] Move to back") {
|
||||
List<int> list;
|
||||
List<int>::Element *n[4];
|
||||
populate_integers(list, n, 4);
|
||||
|
||||
list.move_to_back(n[0]);
|
||||
|
||||
CHECK(list.back()->get() == 0);
|
||||
CHECK(list.front()->get() == 1);
|
||||
}
|
||||
|
||||
TEST_CASE("[List] Move before") {
|
||||
List<int> list;
|
||||
List<int>::Element *n[4];
|
||||
populate_integers(list, n, 4);
|
||||
|
||||
list.move_before(n[3], n[1]);
|
||||
|
||||
CHECK(list.front()->next()->get() == n[3]->get());
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static void compare_lists(const List<T> &p_result, const List<T> &p_expected) {
|
||||
CHECK_EQ(p_result.size(), p_expected.size());
|
||||
const typename List<T>::Element *result_it = p_result.front();
|
||||
const typename List<T>::Element *expected_it = p_expected.front();
|
||||
for (int i = 0; i < p_result.size(); i++) {
|
||||
CHECK(result_it);
|
||||
CHECK(expected_it);
|
||||
CHECK_EQ(result_it->get(), expected_it->get());
|
||||
result_it = result_it->next();
|
||||
expected_it = expected_it->next();
|
||||
}
|
||||
CHECK(!result_it);
|
||||
CHECK(!expected_it);
|
||||
|
||||
result_it = p_result.back();
|
||||
expected_it = p_expected.back();
|
||||
for (int i = 0; i < p_result.size(); i++) {
|
||||
CHECK(result_it);
|
||||
CHECK(expected_it);
|
||||
CHECK_EQ(result_it->get(), expected_it->get());
|
||||
result_it = result_it->prev();
|
||||
expected_it = expected_it->prev();
|
||||
}
|
||||
CHECK(!result_it);
|
||||
CHECK(!expected_it);
|
||||
}
|
||||
|
||||
TEST_CASE("[List] Sort") {
|
||||
List<String> result{ "D", "B", "A", "C" };
|
||||
result.sort();
|
||||
List<String> expected{ "A", "B", "C", "D" };
|
||||
compare_lists(result, expected);
|
||||
|
||||
List<int> empty_result{};
|
||||
empty_result.sort();
|
||||
List<int> empty_expected{};
|
||||
compare_lists(empty_result, empty_expected);
|
||||
|
||||
List<int> one_result{ 1 };
|
||||
one_result.sort();
|
||||
List<int> one_expected{ 1 };
|
||||
compare_lists(one_result, one_expected);
|
||||
|
||||
List<float> reversed_result{ 2.0, 1.5, 1.0 };
|
||||
reversed_result.sort();
|
||||
List<float> reversed_expected{ 1.0, 1.5, 2.0 };
|
||||
compare_lists(reversed_result, reversed_expected);
|
||||
|
||||
List<int> already_sorted_result{ 1, 2, 3, 4, 5 };
|
||||
already_sorted_result.sort();
|
||||
List<int> already_sorted_expected{ 1, 2, 3, 4, 5 };
|
||||
compare_lists(already_sorted_result, already_sorted_expected);
|
||||
|
||||
List<int> with_duplicates_result{ 1, 2, 3, 1, 2, 3 };
|
||||
with_duplicates_result.sort();
|
||||
List<int> with_duplicates_expected{ 1, 1, 2, 2, 3, 3 };
|
||||
compare_lists(with_duplicates_result, with_duplicates_expected);
|
||||
}
|
||||
|
||||
TEST_CASE("[List] Swap adjacent front and back") {
|
||||
List<int> list;
|
||||
List<int>::Element *n[2];
|
||||
populate_integers(list, n, 2);
|
||||
|
||||
list.swap(list.front(), list.back());
|
||||
|
||||
CHECK(list.front()->prev() == nullptr);
|
||||
CHECK(list.front() != list.front()->next());
|
||||
|
||||
CHECK(list.front() == n[1]);
|
||||
CHECK(list.back() == n[0]);
|
||||
|
||||
CHECK(list.back()->next() == nullptr);
|
||||
CHECK(list.back() != list.back()->prev());
|
||||
}
|
||||
|
||||
TEST_CASE("[List] Swap first adjacent pair") {
|
||||
List<int> list;
|
||||
List<int>::Element *n[4];
|
||||
populate_integers(list, n, 4);
|
||||
|
||||
list.swap(n[0], n[1]);
|
||||
|
||||
CHECK(list.front()->prev() == nullptr);
|
||||
CHECK(list.front() != list.front()->next());
|
||||
|
||||
CHECK(list.front() == n[1]);
|
||||
CHECK(list.front()->next() == n[0]);
|
||||
CHECK(list.back()->prev() == n[2]);
|
||||
CHECK(list.back() == n[3]);
|
||||
|
||||
CHECK(list.back()->next() == nullptr);
|
||||
CHECK(list.back() != list.back()->prev());
|
||||
}
|
||||
|
||||
TEST_CASE("[List] Swap middle adjacent pair") {
|
||||
List<int> list;
|
||||
List<int>::Element *n[4];
|
||||
populate_integers(list, n, 4);
|
||||
|
||||
list.swap(n[1], n[2]);
|
||||
|
||||
CHECK(list.front()->prev() == nullptr);
|
||||
|
||||
CHECK(list.front() == n[0]);
|
||||
CHECK(list.front()->next() == n[2]);
|
||||
CHECK(list.back()->prev() == n[1]);
|
||||
CHECK(list.back() == n[3]);
|
||||
|
||||
CHECK(list.back()->next() == nullptr);
|
||||
}
|
||||
|
||||
TEST_CASE("[List] Swap last adjacent pair") {
|
||||
List<int> list;
|
||||
List<int>::Element *n[4];
|
||||
populate_integers(list, n, 4);
|
||||
|
||||
list.swap(n[2], n[3]);
|
||||
|
||||
CHECK(list.front()->prev() == nullptr);
|
||||
|
||||
CHECK(list.front() == n[0]);
|
||||
CHECK(list.front()->next() == n[1]);
|
||||
CHECK(list.back()->prev() == n[3]);
|
||||
CHECK(list.back() == n[2]);
|
||||
|
||||
CHECK(list.back()->next() == nullptr);
|
||||
}
|
||||
|
||||
TEST_CASE("[List] Swap first cross pair") {
|
||||
List<int> list;
|
||||
List<int>::Element *n[4];
|
||||
populate_integers(list, n, 4);
|
||||
|
||||
list.swap(n[0], n[2]);
|
||||
|
||||
CHECK(list.front()->prev() == nullptr);
|
||||
|
||||
CHECK(list.front() == n[2]);
|
||||
CHECK(list.front()->next() == n[1]);
|
||||
CHECK(list.back()->prev() == n[0]);
|
||||
CHECK(list.back() == n[3]);
|
||||
|
||||
CHECK(list.back()->next() == nullptr);
|
||||
}
|
||||
|
||||
TEST_CASE("[List] Swap last cross pair") {
|
||||
List<int> list;
|
||||
List<int>::Element *n[4];
|
||||
populate_integers(list, n, 4);
|
||||
|
||||
list.swap(n[1], n[3]);
|
||||
|
||||
CHECK(list.front()->prev() == nullptr);
|
||||
|
||||
CHECK(list.front() == n[0]);
|
||||
CHECK(list.front()->next() == n[3]);
|
||||
CHECK(list.back()->prev() == n[2]);
|
||||
CHECK(list.back() == n[1]);
|
||||
|
||||
CHECK(list.back()->next() == nullptr);
|
||||
}
|
||||
|
||||
TEST_CASE("[List] Swap edges") {
|
||||
List<int> list;
|
||||
List<int>::Element *n[4];
|
||||
populate_integers(list, n, 4);
|
||||
|
||||
list.swap(n[1], n[3]);
|
||||
|
||||
CHECK(list.front()->prev() == nullptr);
|
||||
|
||||
CHECK(list.front() == n[0]);
|
||||
CHECK(list.front()->next() == n[3]);
|
||||
CHECK(list.back()->prev() == n[2]);
|
||||
CHECK(list.back() == n[1]);
|
||||
|
||||
CHECK(list.back()->next() == nullptr);
|
||||
}
|
||||
|
||||
TEST_CASE("[List] Swap middle (values check)") {
|
||||
List<String> list;
|
||||
List<String>::Element *n_str1 = list.push_back("Still");
|
||||
List<String>::Element *n_str2 = list.push_back("waiting");
|
||||
List<String>::Element *n_str3 = list.push_back("for");
|
||||
List<String>::Element *n_str4 = list.push_back("Godot.");
|
||||
|
||||
CHECK(n_str1->get() == "Still");
|
||||
CHECK(n_str4->get() == "Godot.");
|
||||
|
||||
CHECK(list.front()->get() == "Still");
|
||||
CHECK(list.front()->next()->get() == "waiting");
|
||||
CHECK(list.back()->prev()->get() == "for");
|
||||
CHECK(list.back()->get() == "Godot.");
|
||||
|
||||
list.swap(n_str2, n_str3);
|
||||
|
||||
CHECK(list.front()->next()->get() == "for");
|
||||
CHECK(list.back()->prev()->get() == "waiting");
|
||||
}
|
||||
|
||||
TEST_CASE("[List] Swap front and back (values check)") {
|
||||
List<Variant> list;
|
||||
Variant str = "Godot";
|
||||
List<Variant>::Element *n_str = list.push_back(str);
|
||||
Variant color = Color(0, 0, 1);
|
||||
List<Variant>::Element *n_color = list.push_back(color);
|
||||
|
||||
CHECK(list.front()->get() == "Godot");
|
||||
CHECK(list.back()->get() == Color(0, 0, 1));
|
||||
|
||||
list.swap(n_str, n_color);
|
||||
|
||||
CHECK(list.front()->get() == Color(0, 0, 1));
|
||||
CHECK(list.back()->get() == "Godot");
|
||||
}
|
||||
|
||||
TEST_CASE("[List] Swap adjacent back and front (reverse order of elements)") {
|
||||
List<int> list;
|
||||
List<int>::Element *n[2];
|
||||
populate_integers(list, n, 2);
|
||||
|
||||
list.swap(n[1], n[0]);
|
||||
|
||||
List<int>::Element *it = list.front();
|
||||
while (it) {
|
||||
List<int>::Element *prev_it = it;
|
||||
it = it->next();
|
||||
if (it == prev_it) {
|
||||
FAIL_CHECK("Infinite loop detected.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void swap_random(List<int> &p_list, List<int>::Element *r_elements[], size_t p_size, size_t p_iterations) {
|
||||
Math::seed(0);
|
||||
|
||||
for (size_t test_i = 0; test_i < p_iterations; ++test_i) {
|
||||
// A and B elements have corresponding indices as values.
|
||||
const int a_idx = static_cast<int>(Math::rand() % p_size);
|
||||
const int b_idx = static_cast<int>(Math::rand() % p_size);
|
||||
List<int>::Element *a = p_list.find(a_idx); // via find.
|
||||
List<int>::Element *b = r_elements[b_idx]; // via pointer.
|
||||
|
||||
int va = a->get();
|
||||
int vb = b->get();
|
||||
|
||||
p_list.swap(a, b);
|
||||
|
||||
CHECK(va == a->get());
|
||||
CHECK(vb == b->get());
|
||||
|
||||
size_t element_count = 0;
|
||||
|
||||
// Fully traversable after swap?
|
||||
List<int>::Element *it = p_list.front();
|
||||
while (it) {
|
||||
element_count += 1;
|
||||
List<int>::Element *prev_it = it;
|
||||
it = it->next();
|
||||
if (it == prev_it) {
|
||||
FAIL_CHECK("Infinite loop detected.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
// We should not lose anything in the process.
|
||||
if (element_count != p_size) {
|
||||
FAIL_CHECK("Element count mismatch.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("[Stress][List] Swap random 100 elements, 500 iterations.") {
|
||||
List<int> list;
|
||||
List<int>::Element *n[100];
|
||||
populate_integers(list, n, 100);
|
||||
swap_random(list, n, 100, 500);
|
||||
}
|
||||
|
||||
TEST_CASE("[Stress][List] Swap random 10 elements, 1000 iterations.") {
|
||||
List<int> list;
|
||||
List<int>::Element *n[10];
|
||||
populate_integers(list, n, 10);
|
||||
swap_random(list, n, 10, 1000);
|
||||
}
|
||||
} // namespace TestList
|
||||
263
tests/core/templates/test_local_vector.h
Normal file
@@ -0,0 +1,263 @@
|
||||
/**************************************************************************/
|
||||
/* test_local_vector.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/templates/local_vector.h"
|
||||
|
||||
#include "tests/test_macros.h"
|
||||
|
||||
namespace TestLocalVector {
|
||||
|
||||
TEST_CASE("[LocalVector] List Initialization.") {
|
||||
LocalVector<int> vector{ 0, 1, 2, 3, 4 };
|
||||
|
||||
CHECK(vector.size() == 5);
|
||||
CHECK(vector[0] == 0);
|
||||
CHECK(vector[1] == 1);
|
||||
CHECK(vector[2] == 2);
|
||||
CHECK(vector[3] == 3);
|
||||
CHECK(vector[4] == 4);
|
||||
}
|
||||
|
||||
TEST_CASE("[LocalVector] Push Back.") {
|
||||
LocalVector<int> vector;
|
||||
vector.push_back(0);
|
||||
vector.push_back(1);
|
||||
vector.push_back(2);
|
||||
vector.push_back(3);
|
||||
vector.push_back(4);
|
||||
|
||||
CHECK(vector[0] == 0);
|
||||
CHECK(vector[1] == 1);
|
||||
CHECK(vector[2] == 2);
|
||||
CHECK(vector[3] == 3);
|
||||
CHECK(vector[4] == 4);
|
||||
}
|
||||
|
||||
TEST_CASE("[LocalVector] Find, has.") {
|
||||
LocalVector<int> vector;
|
||||
vector.push_back(3);
|
||||
vector.push_back(1);
|
||||
vector.push_back(4);
|
||||
vector.push_back(0);
|
||||
vector.push_back(2);
|
||||
|
||||
CHECK(vector[0] == 3);
|
||||
CHECK(vector[1] == 1);
|
||||
CHECK(vector[2] == 4);
|
||||
CHECK(vector[3] == 0);
|
||||
CHECK(vector[4] == 2);
|
||||
|
||||
CHECK(vector.find(0) == 3);
|
||||
CHECK(vector.find(1) == 1);
|
||||
CHECK(vector.find(2) == 4);
|
||||
CHECK(vector.find(3) == 0);
|
||||
CHECK(vector.find(4) == 2);
|
||||
|
||||
CHECK(vector.find(-1) == -1);
|
||||
CHECK(vector.find(5) == -1);
|
||||
|
||||
CHECK(vector.has(0));
|
||||
CHECK(vector.has(1));
|
||||
CHECK(vector.has(2));
|
||||
CHECK(vector.has(3));
|
||||
CHECK(vector.has(4));
|
||||
|
||||
CHECK(!vector.has(-1));
|
||||
CHECK(!vector.has(5));
|
||||
}
|
||||
|
||||
TEST_CASE("[LocalVector] Remove.") {
|
||||
LocalVector<int> vector;
|
||||
vector.push_back(0);
|
||||
vector.push_back(1);
|
||||
vector.push_back(2);
|
||||
vector.push_back(3);
|
||||
vector.push_back(4);
|
||||
|
||||
vector.remove_at(0);
|
||||
|
||||
CHECK(vector[0] == 1);
|
||||
CHECK(vector[1] == 2);
|
||||
CHECK(vector[2] == 3);
|
||||
CHECK(vector[3] == 4);
|
||||
|
||||
vector.remove_at(2);
|
||||
|
||||
CHECK(vector[0] == 1);
|
||||
CHECK(vector[1] == 2);
|
||||
CHECK(vector[2] == 4);
|
||||
|
||||
vector.remove_at(1);
|
||||
|
||||
CHECK(vector[0] == 1);
|
||||
CHECK(vector[1] == 4);
|
||||
|
||||
vector.remove_at(0);
|
||||
|
||||
CHECK(vector[0] == 4);
|
||||
}
|
||||
|
||||
TEST_CASE("[LocalVector] Remove Unordered.") {
|
||||
LocalVector<int> vector;
|
||||
vector.push_back(0);
|
||||
vector.push_back(1);
|
||||
vector.push_back(2);
|
||||
vector.push_back(3);
|
||||
vector.push_back(4);
|
||||
|
||||
CHECK(vector.size() == 5);
|
||||
|
||||
vector.remove_at_unordered(0);
|
||||
|
||||
CHECK(vector.size() == 4);
|
||||
|
||||
CHECK(vector.find(0) == -1);
|
||||
CHECK(vector.find(1) != -1);
|
||||
CHECK(vector.find(2) != -1);
|
||||
CHECK(vector.find(3) != -1);
|
||||
CHECK(vector.find(4) != -1);
|
||||
|
||||
// Now the vector is no more ordered.
|
||||
vector.remove_at_unordered(vector.find(3));
|
||||
|
||||
CHECK(vector.size() == 3);
|
||||
|
||||
CHECK(vector.find(3) == -1);
|
||||
CHECK(vector.find(1) != -1);
|
||||
CHECK(vector.find(2) != -1);
|
||||
CHECK(vector.find(4) != -1);
|
||||
|
||||
vector.remove_at_unordered(vector.find(2));
|
||||
|
||||
CHECK(vector.size() == 2);
|
||||
|
||||
CHECK(vector.find(2) == -1);
|
||||
CHECK(vector.find(1) != -1);
|
||||
CHECK(vector.find(4) != -1);
|
||||
|
||||
vector.remove_at_unordered(vector.find(4));
|
||||
|
||||
CHECK(vector.size() == 1);
|
||||
|
||||
CHECK(vector.find(4) == -1);
|
||||
CHECK(vector.find(1) != -1);
|
||||
|
||||
// Remove the last one.
|
||||
vector.remove_at_unordered(0);
|
||||
|
||||
CHECK(vector.is_empty());
|
||||
CHECK(vector.size() == 0);
|
||||
}
|
||||
|
||||
TEST_CASE("[LocalVector] Erase Unordered.") {
|
||||
LocalVector<int> vector;
|
||||
vector.push_back(1);
|
||||
vector.push_back(3);
|
||||
vector.push_back(0);
|
||||
vector.push_back(2);
|
||||
vector.push_back(4);
|
||||
|
||||
CHECK(vector.find(1) == 0);
|
||||
|
||||
vector.erase_unordered(1);
|
||||
|
||||
CHECK(vector.find(1) == -1);
|
||||
CHECK(vector.size() == 4);
|
||||
CHECK(vector[0] == 4);
|
||||
}
|
||||
|
||||
TEST_CASE("[LocalVector] Erase.") {
|
||||
LocalVector<int> vector;
|
||||
vector.push_back(1);
|
||||
vector.push_back(3);
|
||||
vector.push_back(0);
|
||||
vector.push_back(2);
|
||||
vector.push_back(4);
|
||||
|
||||
CHECK(vector.find(2) == 3);
|
||||
|
||||
vector.erase(2);
|
||||
|
||||
CHECK(vector.find(2) == -1);
|
||||
CHECK(vector.size() == 4);
|
||||
}
|
||||
|
||||
TEST_CASE("[LocalVector] Size / Resize / Reserve.") {
|
||||
LocalVector<int> vector;
|
||||
|
||||
CHECK(vector.is_empty());
|
||||
CHECK(vector.size() == 0);
|
||||
CHECK(vector.get_capacity() == 0);
|
||||
|
||||
vector.resize(10);
|
||||
|
||||
CHECK(vector.size() == 10);
|
||||
CHECK(vector.get_capacity() >= 10);
|
||||
|
||||
vector.resize(5);
|
||||
|
||||
CHECK(vector.size() == 5);
|
||||
// Capacity is supposed to change only when the size increase.
|
||||
CHECK(vector.get_capacity() >= 10);
|
||||
|
||||
vector.remove_at(0);
|
||||
vector.remove_at(0);
|
||||
vector.remove_at(0);
|
||||
|
||||
CHECK(vector.size() == 2);
|
||||
// Capacity is supposed to change only when the size increase.
|
||||
CHECK(vector.get_capacity() >= 10);
|
||||
|
||||
vector.reset();
|
||||
|
||||
CHECK(vector.size() == 0);
|
||||
CHECK(vector.get_capacity() == 0);
|
||||
|
||||
vector.reserve(3);
|
||||
|
||||
CHECK(vector.is_empty());
|
||||
CHECK(vector.size() == 0);
|
||||
CHECK(vector.get_capacity() >= 3);
|
||||
|
||||
vector.push_back(0);
|
||||
vector.push_back(0);
|
||||
vector.push_back(0);
|
||||
|
||||
CHECK(vector.size() == 3);
|
||||
CHECK(vector.get_capacity() >= 3);
|
||||
|
||||
vector.push_back(0);
|
||||
|
||||
CHECK(vector.size() == 4);
|
||||
CHECK(vector.get_capacity() >= 4);
|
||||
}
|
||||
} // namespace TestLocalVector
|
||||
96
tests/core/templates/test_lru.h
Normal file
@@ -0,0 +1,96 @@
|
||||
/**************************************************************************/
|
||||
/* test_lru.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/templates/lru.h"
|
||||
|
||||
#include "tests/test_macros.h"
|
||||
|
||||
namespace TestLRU {
|
||||
|
||||
TEST_CASE("[LRU] Store and read") {
|
||||
LRUCache<int, int> lru;
|
||||
|
||||
lru.set_capacity(3);
|
||||
lru.insert(1, 1);
|
||||
lru.insert(50, 2);
|
||||
lru.insert(100, 5);
|
||||
|
||||
CHECK(lru.has(1));
|
||||
CHECK(lru.has(50));
|
||||
CHECK(lru.has(100));
|
||||
CHECK(!lru.has(200));
|
||||
|
||||
CHECK(lru.get(1) == 1);
|
||||
CHECK(lru.get(50) == 2);
|
||||
CHECK(lru.get(100) == 5);
|
||||
|
||||
CHECK(lru.getptr(1) != nullptr);
|
||||
CHECK(lru.getptr(1000) == nullptr);
|
||||
|
||||
lru.insert(600, 600); // Erase <50>
|
||||
CHECK(lru.has(600));
|
||||
CHECK(!lru.has(50));
|
||||
}
|
||||
|
||||
TEST_CASE("[LRU] Resize and clear") {
|
||||
LRUCache<int, int> lru;
|
||||
|
||||
lru.set_capacity(3);
|
||||
lru.insert(1, 1);
|
||||
lru.insert(2, 2);
|
||||
lru.insert(3, 3);
|
||||
|
||||
CHECK(lru.get_capacity() == 3);
|
||||
|
||||
lru.set_capacity(5);
|
||||
CHECK(lru.get_capacity() == 5);
|
||||
|
||||
CHECK(lru.has(1));
|
||||
CHECK(lru.has(2));
|
||||
CHECK(lru.has(3));
|
||||
CHECK(!lru.has(4));
|
||||
|
||||
lru.set_capacity(2);
|
||||
CHECK(lru.get_capacity() == 2);
|
||||
|
||||
CHECK(!lru.has(1));
|
||||
CHECK(lru.has(2));
|
||||
CHECK(lru.has(3));
|
||||
CHECK(!lru.has(4));
|
||||
|
||||
lru.clear();
|
||||
CHECK(!lru.has(1));
|
||||
CHECK(!lru.has(2));
|
||||
CHECK(!lru.has(3));
|
||||
CHECK(!lru.has(4));
|
||||
}
|
||||
} // namespace TestLRU
|
||||
201
tests/core/templates/test_paged_array.h
Normal file
@@ -0,0 +1,201 @@
|
||||
/**************************************************************************/
|
||||
/* test_paged_array.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/templates/paged_array.h"
|
||||
|
||||
#include "thirdparty/doctest/doctest.h"
|
||||
|
||||
namespace TestPagedArray {
|
||||
|
||||
// PagedArray
|
||||
|
||||
TEST_CASE("[PagedArray] Simple fill and refill") {
|
||||
PagedArrayPool<uint32_t> pool;
|
||||
PagedArray<uint32_t> array;
|
||||
array.set_page_pool(&pool);
|
||||
|
||||
for (uint32_t i = 0; i < 123456; i++) {
|
||||
array.push_back(i);
|
||||
}
|
||||
CHECK_MESSAGE(
|
||||
array.size() == 123456,
|
||||
"PagedArray should have 123456 elements.");
|
||||
|
||||
bool all_match = true;
|
||||
for (uint32_t i = 0; i < 123456; i++) {
|
||||
if (array[i] != i) {
|
||||
all_match = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
CHECK_MESSAGE(
|
||||
all_match,
|
||||
"PagedArray elements should match from 0 to 123455.");
|
||||
|
||||
array.clear();
|
||||
|
||||
CHECK_MESSAGE(
|
||||
array.size() == 0,
|
||||
"PagedArray elements should be 0 after clear.");
|
||||
|
||||
for (uint32_t i = 0; i < 999; i++) {
|
||||
array.push_back(i);
|
||||
}
|
||||
CHECK_MESSAGE(
|
||||
array.size() == 999,
|
||||
"PagedArray should have 999 elements.");
|
||||
|
||||
all_match = true;
|
||||
for (uint32_t i = 0; i < 999; i++) {
|
||||
if (array[i] != i) {
|
||||
all_match = false;
|
||||
}
|
||||
}
|
||||
|
||||
CHECK_MESSAGE(
|
||||
all_match,
|
||||
"PagedArray elements should match from 0 to 998.");
|
||||
|
||||
array.reset(); //reset so pagepool can be reset
|
||||
pool.reset();
|
||||
}
|
||||
|
||||
TEST_CASE("[PagedArray] Shared pool fill, including merging") {
|
||||
PagedArrayPool<uint32_t> pool;
|
||||
PagedArray<uint32_t> array1;
|
||||
PagedArray<uint32_t> array2;
|
||||
array1.set_page_pool(&pool);
|
||||
array2.set_page_pool(&pool);
|
||||
|
||||
for (uint32_t i = 0; i < 123456; i++) {
|
||||
array1.push_back(i);
|
||||
}
|
||||
CHECK_MESSAGE(
|
||||
array1.size() == 123456,
|
||||
"PagedArray #1 should have 123456 elements.");
|
||||
|
||||
bool all_match = true;
|
||||
for (uint32_t i = 0; i < 123456; i++) {
|
||||
if (array1[i] != i) {
|
||||
all_match = false;
|
||||
}
|
||||
}
|
||||
|
||||
CHECK_MESSAGE(
|
||||
all_match,
|
||||
"PagedArray #1 elements should match from 0 to 123455.");
|
||||
|
||||
for (uint32_t i = 0; i < 999; i++) {
|
||||
array2.push_back(i);
|
||||
}
|
||||
CHECK_MESSAGE(
|
||||
array2.size() == 999,
|
||||
"PagedArray #2 should have 999 elements.");
|
||||
|
||||
all_match = true;
|
||||
for (uint32_t i = 0; i < 999; i++) {
|
||||
if (array2[i] != i) {
|
||||
all_match = false;
|
||||
}
|
||||
}
|
||||
|
||||
CHECK_MESSAGE(
|
||||
all_match,
|
||||
"PagedArray #2 elements should match from 0 to 998.");
|
||||
|
||||
array1.merge_unordered(array2);
|
||||
|
||||
CHECK_MESSAGE(
|
||||
array1.size() == 123456 + 999,
|
||||
"PagedArray #1 should now be 123456 + 999 elements.");
|
||||
|
||||
CHECK_MESSAGE(
|
||||
array2.size() == 0,
|
||||
"PagedArray #2 should now be 0 elements.");
|
||||
|
||||
array1.reset(); //reset so pagepool can be reset
|
||||
array2.reset(); //reset so pagepool can be reset
|
||||
pool.reset();
|
||||
}
|
||||
|
||||
TEST_CASE("[PagedArray] Extensive merge_unordered() test") {
|
||||
for (int page_size = 1; page_size <= 128; page_size *= 2) {
|
||||
PagedArrayPool<uint32_t> pool(page_size);
|
||||
PagedArray<uint32_t> array1;
|
||||
PagedArray<uint32_t> array2;
|
||||
array1.set_page_pool(&pool);
|
||||
array2.set_page_pool(&pool);
|
||||
|
||||
const int max_count = 123;
|
||||
// Test merging arrays of lengths 0+123, 1+122, 2+121, ..., 123+0
|
||||
for (uint32_t j = 0; j < max_count; j++) {
|
||||
CHECK(array1.size() == 0);
|
||||
CHECK(array2.size() == 0);
|
||||
|
||||
uint32_t sum = 12345;
|
||||
for (uint32_t i = 0; i < j; i++) {
|
||||
// Hashing the addend makes it extremely unlikely for any values
|
||||
// other than the original inputs to produce a matching sum
|
||||
uint32_t addend = hash_murmur3_one_32(i) + i;
|
||||
array1.push_back(addend);
|
||||
sum += addend;
|
||||
}
|
||||
for (uint32_t i = j; i < max_count; i++) {
|
||||
// See above
|
||||
uint32_t addend = hash_murmur3_one_32(i) + i;
|
||||
array2.push_back(addend);
|
||||
sum += addend;
|
||||
}
|
||||
|
||||
CHECK(array1.size() == j);
|
||||
CHECK(array2.size() == max_count - j);
|
||||
|
||||
array1.merge_unordered(array2);
|
||||
CHECK_MESSAGE(array1.size() == max_count, "merge_unordered() added/dropped elements while merging");
|
||||
|
||||
// If any elements were altered during merging, the sum will not match up.
|
||||
for (uint32_t i = 0; i < array1.size(); i++) {
|
||||
sum -= array1[i];
|
||||
}
|
||||
CHECK_MESSAGE(sum == 12345, "merge_unordered() altered elements while merging");
|
||||
|
||||
array1.clear();
|
||||
}
|
||||
|
||||
array1.reset();
|
||||
array2.reset();
|
||||
pool.reset();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace TestPagedArray
|
||||
256
tests/core/templates/test_rid.h
Normal file
@@ -0,0 +1,256 @@
|
||||
/**************************************************************************/
|
||||
/* test_rid.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/os/thread.h"
|
||||
#include "core/templates/local_vector.h"
|
||||
#include "core/templates/rid.h"
|
||||
#include "core/templates/rid_owner.h"
|
||||
|
||||
#include "tests/test_macros.h"
|
||||
|
||||
#ifdef THREADS_ENABLED
|
||||
#ifdef SANITIZERS_ENABLED
|
||||
#ifdef __has_feature
|
||||
#if __has_feature(thread_sanitizer)
|
||||
#define TSAN_ENABLED
|
||||
#endif
|
||||
#elif defined(__SANITIZE_THREAD__)
|
||||
#define TSAN_ENABLED
|
||||
#endif
|
||||
#endif // SANITIZERS_ENABLED
|
||||
#endif // THREADS_ENABLED
|
||||
|
||||
#ifdef TSAN_ENABLED
|
||||
#include <sanitizer/tsan_interface.h>
|
||||
#endif
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
#ifdef THREADS_ENABLED
|
||||
// This case would let sanitizers realize data races.
|
||||
// Additionally, on purely weakly ordered architectures, it would detect synchronization issues
|
||||
// if RID_Alloc failed to impose proper memory ordering and the test's threads are distributed
|
||||
// among multiple L1 caches.
|
||||
TEST_CASE("[RID_Owner] Thread safety") {
|
||||
struct DataHolder {
|
||||
char data[Thread::CACHE_LINE_BYTES];
|
||||
};
|
||||
|
||||
struct RID_OwnerTester {
|
||||
uint32_t thread_count = 0;
|
||||
RID_Owner<DataHolder, true> rid_owner;
|
||||
TightLocalVector<Thread> threads;
|
||||
SafeNumeric<uint32_t> next_thread_idx;
|
||||
// Using std::atomic directly since SafeNumeric doesn't support relaxed ordering.
|
||||
TightLocalVector<std::atomic<uint64_t>> rids;
|
||||
std::atomic<uint32_t> sync[2] = {};
|
||||
std::atomic<uint32_t> correct = 0;
|
||||
|
||||
// A barrier that doesn't introduce memory ordering constraints, only compiler ones.
|
||||
// The idea is not to cause any sync traffic that would make the code we want to test
|
||||
// seem correct as a side effect.
|
||||
void lockstep(uint32_t p_step) {
|
||||
uint32_t buf_idx = p_step % 2;
|
||||
uint32_t target = (p_step / 2 + 1) * threads.size();
|
||||
sync[buf_idx].fetch_add(1, std::memory_order_relaxed);
|
||||
do {
|
||||
Thread::yield();
|
||||
} while (sync[buf_idx].load(std::memory_order_relaxed) != target);
|
||||
}
|
||||
|
||||
explicit RID_OwnerTester(bool p_chunk_for_all, bool p_chunks_preallocated) :
|
||||
thread_count(OS::get_singleton()->get_processor_count()),
|
||||
rid_owner(sizeof(DataHolder) * (p_chunk_for_all ? thread_count : 1)) {
|
||||
threads.resize(thread_count);
|
||||
rids.resize(threads.size());
|
||||
if (p_chunks_preallocated) {
|
||||
LocalVector<RID> prealloc_rids;
|
||||
for (uint32_t i = 0; i < (p_chunk_for_all ? 1 : threads.size()); i++) {
|
||||
prealloc_rids.push_back(rid_owner.make_rid());
|
||||
}
|
||||
for (uint32_t i = 0; i < prealloc_rids.size(); i++) {
|
||||
rid_owner.free(prealloc_rids[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
~RID_OwnerTester() {
|
||||
for (uint32_t i = 0; i < threads.size(); i++) {
|
||||
rid_owner.free(RID::from_uint64(rids[i].load(std::memory_order_relaxed)));
|
||||
}
|
||||
}
|
||||
|
||||
void test() {
|
||||
for (uint32_t i = 0; i < threads.size(); i++) {
|
||||
threads[i].start(
|
||||
[](void *p_data) {
|
||||
RID_OwnerTester *rot = (RID_OwnerTester *)p_data;
|
||||
|
||||
auto _compute_thread_unique_byte = [](uint32_t p_idx) -> char {
|
||||
return ((p_idx & 0xff) ^ (0b11111110 << (p_idx % 8)));
|
||||
};
|
||||
|
||||
// 1. Each thread gets a zero-based index.
|
||||
uint32_t self_th_idx = rot->next_thread_idx.postincrement();
|
||||
|
||||
rot->lockstep(0);
|
||||
|
||||
// 2. Each thread makes a RID holding unique data.
|
||||
DataHolder initial_data;
|
||||
memset(&initial_data, _compute_thread_unique_byte(self_th_idx), Thread::CACHE_LINE_BYTES);
|
||||
RID my_rid = rot->rid_owner.make_rid(initial_data);
|
||||
rot->rids[self_th_idx].store(my_rid.get_id(), std::memory_order_relaxed);
|
||||
|
||||
rot->lockstep(1);
|
||||
|
||||
// 3. Each thread verifies all the others.
|
||||
uint32_t local_correct = 0;
|
||||
for (uint32_t th_idx = 0; th_idx < rot->threads.size(); th_idx++) {
|
||||
if (th_idx == self_th_idx) {
|
||||
continue;
|
||||
}
|
||||
char expected_unique_byte = _compute_thread_unique_byte(th_idx);
|
||||
RID rid = RID::from_uint64(rot->rids[th_idx].load(std::memory_order_relaxed));
|
||||
DataHolder *data = rot->rid_owner.get_or_null(rid);
|
||||
#ifdef TSAN_ENABLED
|
||||
__tsan_acquire(data); // We know not a race in practice.
|
||||
#endif
|
||||
bool ok = true;
|
||||
for (uint32_t j = 0; j < Thread::CACHE_LINE_BYTES; j++) {
|
||||
if (data->data[j] != expected_unique_byte) {
|
||||
ok = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (ok) {
|
||||
local_correct++;
|
||||
}
|
||||
#ifdef TSAN_ENABLED
|
||||
__tsan_release(data);
|
||||
#endif
|
||||
}
|
||||
|
||||
rot->lockstep(2);
|
||||
|
||||
rot->correct.fetch_add(local_correct, std::memory_order_acq_rel);
|
||||
},
|
||||
this);
|
||||
}
|
||||
|
||||
for (uint32_t i = 0; i < threads.size(); i++) {
|
||||
threads[i].wait_to_finish();
|
||||
}
|
||||
|
||||
CHECK_EQ(correct.load(), threads.size() * (threads.size() - 1));
|
||||
}
|
||||
};
|
||||
|
||||
SUBCASE("All items in one chunk, pre-allocated") {
|
||||
RID_OwnerTester tester(true, true);
|
||||
tester.test();
|
||||
}
|
||||
SUBCASE("All items in one chunk, NOT pre-allocated") {
|
||||
RID_OwnerTester tester(true, false);
|
||||
tester.test();
|
||||
}
|
||||
SUBCASE("One item per chunk, pre-allocated") {
|
||||
RID_OwnerTester tester(false, true);
|
||||
tester.test();
|
||||
}
|
||||
SUBCASE("One item per chunk, NOT pre-allocated") {
|
||||
RID_OwnerTester tester(false, false);
|
||||
tester.test();
|
||||
}
|
||||
}
|
||||
#endif // THREADS_ENABLED
|
||||
|
||||
} // namespace TestRID
|
||||
72
tests/core/templates/test_self_list.h
Normal file
@@ -0,0 +1,72 @@
|
||||
/**************************************************************************/
|
||||
/* test_self_list.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/templates/self_list.h"
|
||||
|
||||
#include "tests/test_macros.h"
|
||||
|
||||
namespace TestSelfList {
|
||||
|
||||
TEST_CASE("[SelfList] Sort") {
|
||||
const int SIZE = 5;
|
||||
int numbers[SIZE]{ 3, 2, 5, 1, 4 };
|
||||
SelfList<int> elements[SIZE]{
|
||||
SelfList<int>(&numbers[0]),
|
||||
SelfList<int>(&numbers[1]),
|
||||
SelfList<int>(&numbers[2]),
|
||||
SelfList<int>(&numbers[3]),
|
||||
SelfList<int>(&numbers[4]),
|
||||
};
|
||||
|
||||
SelfList<int>::List list;
|
||||
for (int i = 0; i < SIZE; i++) {
|
||||
list.add_last(&elements[i]);
|
||||
}
|
||||
|
||||
SelfList<int> *it = list.first();
|
||||
for (int i = 0; i < SIZE; i++) {
|
||||
CHECK_EQ(numbers[i], *it->self());
|
||||
it = it->next();
|
||||
}
|
||||
|
||||
list.sort();
|
||||
it = list.first();
|
||||
for (int i = 1; i <= SIZE; i++) {
|
||||
CHECK_EQ(i, *it->self());
|
||||
it = it->next();
|
||||
}
|
||||
|
||||
for (SelfList<int> &element : elements) {
|
||||
element.remove_from_list();
|
||||
}
|
||||
}
|
||||
} // namespace TestSelfList
|
||||
70
tests/core/templates/test_span.h
Normal file
@@ -0,0 +1,70 @@
|
||||
/**************************************************************************/
|
||||
/* test_span.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/templates/span.h"
|
||||
|
||||
#include "tests/test_macros.h"
|
||||
|
||||
namespace TestSpan {
|
||||
|
||||
TEST_CASE("[Span] Constexpr Validators") {
|
||||
constexpr Span<uint16_t> span_empty;
|
||||
static_assert(span_empty.ptr() == nullptr);
|
||||
static_assert(span_empty.size() == 0);
|
||||
static_assert(span_empty.is_empty());
|
||||
|
||||
constexpr static uint16_t value = 5;
|
||||
Span<uint16_t> span_value(&value, 1);
|
||||
CHECK(span_value.ptr() == &value);
|
||||
CHECK(span_value.size() == 1);
|
||||
CHECK(!span_value.is_empty());
|
||||
|
||||
static constexpr int ints[] = { 0, 1, 2, 3, 4, 5 };
|
||||
constexpr Span<int> span_array = ints;
|
||||
static_assert(span_array.size() == 6);
|
||||
static_assert(!span_array.is_empty());
|
||||
static_assert(span_array[0] == 0);
|
||||
static_assert(span_array[span_array.size() - 1] == 5);
|
||||
|
||||
constexpr Span<char32_t> span_string = U"122345";
|
||||
static_assert(span_string.size() == 6);
|
||||
static_assert(!span_string.is_empty());
|
||||
static_assert(span_string[0] == U'1');
|
||||
static_assert(span_string[span_string.size() - 1] == U'5');
|
||||
|
||||
int idx = 0;
|
||||
for (const char32_t &chr : span_string) {
|
||||
CHECK_EQ(chr, span_string[idx++]);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace TestSpan
|
||||
707
tests/core/templates/test_vector.h
Normal file
@@ -0,0 +1,707 @@
|
||||
/**************************************************************************/
|
||||
/* test_vector.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/templates/vector.h"
|
||||
|
||||
#include "tests/test_macros.h"
|
||||
|
||||
namespace TestVector {
|
||||
|
||||
TEST_CASE("[Vector] List initialization") {
|
||||
Vector<int> vector{ 0, 1, 2, 3, 4 };
|
||||
|
||||
CHECK(vector.size() == 5);
|
||||
CHECK(vector[0] == 0);
|
||||
CHECK(vector[1] == 1);
|
||||
CHECK(vector[2] == 2);
|
||||
CHECK(vector[3] == 3);
|
||||
CHECK(vector[4] == 4);
|
||||
}
|
||||
|
||||
TEST_CASE("[Vector] Push back and append") {
|
||||
Vector<int> vector;
|
||||
vector.push_back(0);
|
||||
vector.push_back(1);
|
||||
vector.push_back(2);
|
||||
vector.push_back(3);
|
||||
// Alias for `push_back`.
|
||||
vector.append(4);
|
||||
|
||||
CHECK(vector[0] == 0);
|
||||
CHECK(vector[1] == 1);
|
||||
CHECK(vector[2] == 2);
|
||||
CHECK(vector[3] == 3);
|
||||
CHECK(vector[4] == 4);
|
||||
}
|
||||
|
||||
TEST_CASE("[Vector] Append array") {
|
||||
Vector<int> vector;
|
||||
vector.push_back(1);
|
||||
vector.push_back(2);
|
||||
|
||||
Vector<int> vector_other;
|
||||
vector_other.push_back(128);
|
||||
vector_other.push_back(129);
|
||||
vector.append_array(vector_other);
|
||||
|
||||
CHECK(vector.size() == 4);
|
||||
CHECK(vector[0] == 1);
|
||||
CHECK(vector[1] == 2);
|
||||
CHECK(vector[2] == 128);
|
||||
CHECK(vector[3] == 129);
|
||||
}
|
||||
|
||||
TEST_CASE("[Vector] Insert") {
|
||||
Vector<int> vector;
|
||||
vector.insert(0, 2);
|
||||
vector.insert(0, 8);
|
||||
vector.insert(2, 5);
|
||||
vector.insert(1, 5);
|
||||
vector.insert(0, -2);
|
||||
|
||||
CHECK(vector.size() == 5);
|
||||
CHECK(vector[0] == -2);
|
||||
CHECK(vector[1] == 8);
|
||||
CHECK(vector[2] == 5);
|
||||
CHECK(vector[3] == 2);
|
||||
CHECK(vector[4] == 5);
|
||||
}
|
||||
|
||||
TEST_CASE("[Vector] Ordered insert") {
|
||||
Vector<int> vector;
|
||||
vector.ordered_insert(2);
|
||||
vector.ordered_insert(8);
|
||||
vector.ordered_insert(5);
|
||||
vector.ordered_insert(5);
|
||||
vector.ordered_insert(-2);
|
||||
|
||||
CHECK(vector.size() == 5);
|
||||
CHECK(vector[0] == -2);
|
||||
CHECK(vector[1] == 2);
|
||||
CHECK(vector[2] == 5);
|
||||
CHECK(vector[3] == 5);
|
||||
CHECK(vector[4] == 8);
|
||||
}
|
||||
|
||||
TEST_CASE("[Vector] Insert + Ordered insert") {
|
||||
Vector<int> vector;
|
||||
vector.ordered_insert(2);
|
||||
vector.ordered_insert(8);
|
||||
vector.insert(0, 5);
|
||||
vector.ordered_insert(5);
|
||||
vector.insert(1, -2);
|
||||
|
||||
CHECK(vector.size() == 5);
|
||||
CHECK(vector[0] == 5);
|
||||
CHECK(vector[1] == -2);
|
||||
CHECK(vector[2] == 2);
|
||||
CHECK(vector[3] == 5);
|
||||
CHECK(vector[4] == 8);
|
||||
}
|
||||
|
||||
TEST_CASE("[Vector] Fill large array and modify it") {
|
||||
Vector<int> vector;
|
||||
vector.resize(1'000'000);
|
||||
vector.fill(0x60d07);
|
||||
|
||||
vector.write[200] = 0;
|
||||
CHECK(vector.size() == 1'000'000);
|
||||
CHECK(vector[0] == 0x60d07);
|
||||
CHECK(vector[200] == 0);
|
||||
CHECK(vector[499'999] == 0x60d07);
|
||||
CHECK(vector[999'999] == 0x60d07);
|
||||
vector.remove_at(200);
|
||||
CHECK(vector[200] == 0x60d07);
|
||||
|
||||
vector.clear();
|
||||
CHECK(vector.size() == 0);
|
||||
}
|
||||
|
||||
TEST_CASE("[Vector] Copy creation") {
|
||||
Vector<int> vector;
|
||||
vector.push_back(0);
|
||||
vector.push_back(1);
|
||||
vector.push_back(2);
|
||||
vector.push_back(3);
|
||||
vector.push_back(4);
|
||||
|
||||
Vector<int> vector_other = Vector<int>(vector);
|
||||
vector_other.remove_at(0);
|
||||
CHECK(vector_other[0] == 1);
|
||||
CHECK(vector_other[1] == 2);
|
||||
CHECK(vector_other[2] == 3);
|
||||
CHECK(vector_other[3] == 4);
|
||||
|
||||
// Make sure the original vector isn't modified.
|
||||
CHECK(vector[0] == 0);
|
||||
CHECK(vector[1] == 1);
|
||||
CHECK(vector[2] == 2);
|
||||
CHECK(vector[3] == 3);
|
||||
CHECK(vector[4] == 4);
|
||||
}
|
||||
|
||||
TEST_CASE("[Vector] Duplicate") {
|
||||
Vector<int> vector;
|
||||
vector.push_back(0);
|
||||
vector.push_back(1);
|
||||
vector.push_back(2);
|
||||
vector.push_back(3);
|
||||
vector.push_back(4);
|
||||
|
||||
Vector<int> vector_other = vector.duplicate();
|
||||
vector_other.remove_at(0);
|
||||
CHECK(vector_other[0] == 1);
|
||||
CHECK(vector_other[1] == 2);
|
||||
CHECK(vector_other[2] == 3);
|
||||
CHECK(vector_other[3] == 4);
|
||||
|
||||
// Make sure the original vector isn't modified.
|
||||
CHECK(vector[0] == 0);
|
||||
CHECK(vector[1] == 1);
|
||||
CHECK(vector[2] == 2);
|
||||
CHECK(vector[3] == 3);
|
||||
CHECK(vector[4] == 4);
|
||||
}
|
||||
|
||||
TEST_CASE("[Vector] Get, set") {
|
||||
Vector<int> vector;
|
||||
vector.push_back(0);
|
||||
vector.push_back(1);
|
||||
vector.push_back(2);
|
||||
vector.push_back(3);
|
||||
vector.push_back(4);
|
||||
|
||||
CHECK(vector.get(0) == 0);
|
||||
CHECK(vector.get(1) == 1);
|
||||
vector.set(2, 256);
|
||||
CHECK(vector.get(2) == 256);
|
||||
CHECK(vector.get(3) == 3);
|
||||
|
||||
ERR_PRINT_OFF;
|
||||
// Invalid (but should not crash): setting out of bounds.
|
||||
vector.set(6, 500);
|
||||
ERR_PRINT_ON;
|
||||
|
||||
CHECK(vector.get(4) == 4);
|
||||
}
|
||||
|
||||
TEST_CASE("[Vector] To byte array (variant call)") {
|
||||
// PackedInt32Array.
|
||||
{
|
||||
PackedInt32Array vector[] = { { 0, -1, 2008 }, {} };
|
||||
PackedByteArray out[] = { { /* 0 */ 0x00, 0x00, 0x00, 0x00, /* -1 */ 0xFF, 0xFF, 0xFF, 0xFF, /* 2008 */ 0xD8, 0x07, 0x00, 0x00 }, {} };
|
||||
|
||||
for (size_t i = 0; i < std::size(vector); i++) {
|
||||
Callable::CallError err;
|
||||
Variant v_ret;
|
||||
Variant v_vector = vector[i];
|
||||
v_vector.callp("to_byte_array", nullptr, 0, v_ret, err);
|
||||
CHECK(v_ret.get_type() == Variant::PACKED_BYTE_ARRAY);
|
||||
CHECK(v_ret.operator PackedByteArray() == out[i]);
|
||||
}
|
||||
}
|
||||
|
||||
// PackedInt64Array.
|
||||
{
|
||||
PackedInt64Array vector[] = { { 0, -1, 2008 }, {} };
|
||||
PackedByteArray out[] = { { /* 0 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* -1 */ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, /* 2008 */ 0xD8, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, {} };
|
||||
|
||||
for (size_t i = 0; i < std::size(vector); i++) {
|
||||
Callable::CallError err;
|
||||
Variant v_ret;
|
||||
Variant v_vector = vector[i];
|
||||
v_vector.callp("to_byte_array", nullptr, 0, v_ret, err);
|
||||
CHECK(v_ret.get_type() == Variant::PACKED_BYTE_ARRAY);
|
||||
CHECK(v_ret.operator PackedByteArray() == out[i]);
|
||||
}
|
||||
}
|
||||
|
||||
// PackedFloat32Array.
|
||||
{
|
||||
PackedFloat32Array vector[] = { { 0.0, -1.0, 200e24 }, {} };
|
||||
PackedByteArray out[] = { { /* 0.0 */ 0x00, 0x00, 0x00, 0x00, /* -1.0 */ 0x00, 0x00, 0x80, 0xBF, /* 200e24 */ 0xA6, 0x6F, 0x25, 0x6B }, {} };
|
||||
|
||||
for (size_t i = 0; i < std::size(vector); i++) {
|
||||
Callable::CallError err;
|
||||
Variant v_ret;
|
||||
Variant v_vector = vector[i];
|
||||
v_vector.callp("to_byte_array", nullptr, 0, v_ret, err);
|
||||
CHECK(v_ret.get_type() == Variant::PACKED_BYTE_ARRAY);
|
||||
CHECK(v_ret.operator PackedByteArray() == out[i]);
|
||||
}
|
||||
}
|
||||
// PackedFloat64Array.
|
||||
{
|
||||
PackedFloat64Array vector[] = { { 0.0, -1.0, 200e24 }, {} };
|
||||
PackedByteArray out[] = { { /* 0.0 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* -1.0 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0xBF, /* 200e24 */ 0x35, 0x03, 0x32, 0xB7, 0xF4, 0xAD, 0x64, 0x45 }, {} };
|
||||
|
||||
for (size_t i = 0; i < std::size(vector); i++) {
|
||||
Callable::CallError err;
|
||||
Variant v_ret;
|
||||
Variant v_vector = vector[i];
|
||||
v_vector.callp("to_byte_array", nullptr, 0, v_ret, err);
|
||||
CHECK(v_ret.get_type() == Variant::PACKED_BYTE_ARRAY);
|
||||
CHECK(v_ret.operator PackedByteArray() == out[i]);
|
||||
}
|
||||
}
|
||||
|
||||
// PackedStringArray.
|
||||
{
|
||||
PackedStringArray vector[] = { { "test", "string" }, {}, { "", "test" } };
|
||||
PackedByteArray out[] = { { /* test */ 0x74, 0x65, 0x73, 0x74, /* null */ 0x00, /* string */ 0x73, 0x74, 0x72, 0x69, 0x6E, 0x67, /* null */ 0x00 }, {}, { /* null */ 0x00, /* test */ 0x74, 0x65, 0x73, 0x74, /* null */ 0x00 } };
|
||||
|
||||
for (size_t i = 0; i < std::size(vector); i++) {
|
||||
Callable::CallError err;
|
||||
Variant v_ret;
|
||||
Variant v_vector = vector[i];
|
||||
v_vector.callp("to_byte_array", nullptr, 0, v_ret, err);
|
||||
CHECK(v_ret.get_type() == Variant::PACKED_BYTE_ARRAY);
|
||||
CHECK(v_ret.operator PackedByteArray() == out[i]);
|
||||
}
|
||||
}
|
||||
|
||||
// PackedVector2Array.
|
||||
{
|
||||
PackedVector2Array vector[] = { { Vector2(), Vector2(1, -1) }, {} };
|
||||
#ifdef REAL_T_IS_DOUBLE
|
||||
PackedByteArray out[] = { { /* X=0.0 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* Y=0.0 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* X=1.0 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x3F, /* Y=-1.0 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0xBF }, {} };
|
||||
#else
|
||||
PackedByteArray out[] = { { /* X=0.0 */ 0x00, 0x00, 0x00, 0x00, /* Y=0.0 */ 0x00, 0x00, 0x00, 0x00, /* X=1.0 */ 0x00, 0x00, 0x80, 0x3F, /* Y=-1.0 */ 0x00, 0x00, 0x80, 0xBF }, {} };
|
||||
#endif
|
||||
|
||||
for (size_t i = 0; i < std::size(vector); i++) {
|
||||
Callable::CallError err;
|
||||
Variant v_ret;
|
||||
Variant v_vector = vector[i];
|
||||
v_vector.callp("to_byte_array", nullptr, 0, v_ret, err);
|
||||
CHECK(v_ret.get_type() == Variant::PACKED_BYTE_ARRAY);
|
||||
CHECK(v_ret.operator PackedByteArray() == out[i]);
|
||||
}
|
||||
}
|
||||
|
||||
// PackedVector3Array.
|
||||
{
|
||||
PackedVector3Array vector[] = { { Vector3(), Vector3(1, 1, -1) }, {} };
|
||||
#ifdef REAL_T_IS_DOUBLE
|
||||
PackedByteArray out[] = { { /* X=0.0 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* Y=0.0 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* Z=0.0 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* X=1.0 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x3F, /* Y=1.0 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x3F, /* Z=-1.0 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0xBF }, {} };
|
||||
#else
|
||||
PackedByteArray out[] = { { /* X=0.0 */ 0x00, 0x00, 0x00, 0x00, /* Y=0.0 */ 0x00, 0x00, 0x00, 0x00, /* Z=0.0 */ 0x00, 0x00, 0x00, 0x00, /* X=1.0 */ 0x00, 0x00, 0x80, 0x3F, /* Y=1.0 */ 0x00, 0x00, 0x80, 0x3F, /* Z=-1.0 */ 0x00, 0x00, 0x80, 0xBF }, {} };
|
||||
#endif
|
||||
|
||||
for (size_t i = 0; i < std::size(vector); i++) {
|
||||
Callable::CallError err;
|
||||
Variant v_ret;
|
||||
Variant v_vector = vector[i];
|
||||
v_vector.callp("to_byte_array", nullptr, 0, v_ret, err);
|
||||
CHECK(v_ret.get_type() == Variant::PACKED_BYTE_ARRAY);
|
||||
CHECK(v_ret.operator PackedByteArray() == out[i]);
|
||||
}
|
||||
}
|
||||
|
||||
// PackedColorArray.
|
||||
{
|
||||
PackedColorArray vector[] = { { Color(), Color(1, 1, 1) }, {} };
|
||||
PackedByteArray out[] = { { /* R=0.0 */ 0x00, 0x00, 0x00, 0x00, /* G=0.0 */ 0x00, 0x00, 0x00, 0x00, /* B=0.0 */ 0x00, 0x00, 0x00, 0x00, /* A=1.0 */ 0x00, 0x00, 0x80, 0x3F, /* R=1.0 */ 0x00, 0x00, 0x80, 0x3F, /* G=1.0 */ 0x00, 0x00, 0x80, 0x3F, /* B=1.0 */ 0x00, 0x00, 0x80, 0x3F, /* A=1.0 */ 0x00, 0x00, 0x80, 0x3F }, {} };
|
||||
|
||||
for (size_t i = 0; i < std::size(vector); i++) {
|
||||
Callable::CallError err;
|
||||
Variant v_ret;
|
||||
Variant v_vector = vector[i];
|
||||
v_vector.callp("to_byte_array", nullptr, 0, v_ret, err);
|
||||
CHECK(v_ret.get_type() == Variant::PACKED_BYTE_ARRAY);
|
||||
CHECK(v_ret.operator PackedByteArray() == out[i]);
|
||||
}
|
||||
}
|
||||
|
||||
// PackedVector4Array.
|
||||
{
|
||||
PackedVector4Array vector[] = { { Vector4(), Vector4(1, -1, 1, -1) }, {} };
|
||||
#ifdef REAL_T_IS_DOUBLE
|
||||
PackedByteArray out[] = { { /* X=0.0 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* Y=0.0 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* Z 0.0 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* W=0.0 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* X=1.0 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x3F, /* Y=-1.0 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0xBF, /* Z=1.0 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x3F, /* W=-1.0 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0xBF }, {} };
|
||||
#else
|
||||
PackedByteArray out[] = { { /* X=0.0 */ 0x00, 0x00, 0x00, 0x00, /* Y=0.0 */ 0x00, 0x00, 0x00, 0x00, /* Z=0.0 */ 0x00, 0x00, 0x00, 0x00, /* W 0.0 */ 0x00, 0x00, 0x00, 0x00, /* X 1.0 */ 0x00, 0x00, 0x80, 0x3F, /* Y=-1.0 */ 0x00, 0x00, 0x80, 0xBF, /* Z=1.0 */ 0x00, 0x00, 0x80, 0x3F, /* W=-1.0 */ 0x00, 0x00, 0x80, 0xBF }, {} };
|
||||
#endif
|
||||
|
||||
for (size_t i = 0; i < std::size(vector); i++) {
|
||||
Callable::CallError err;
|
||||
Variant v_ret;
|
||||
Variant v_vector = vector[i];
|
||||
v_vector.callp("to_byte_array", nullptr, 0, v_ret, err);
|
||||
CHECK(v_ret.get_type() == Variant::PACKED_BYTE_ARRAY);
|
||||
CHECK(v_ret.operator PackedByteArray() == out[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("[Vector] To byte array") {
|
||||
Vector<int> vector;
|
||||
vector.push_back(0);
|
||||
vector.push_back(-1);
|
||||
vector.push_back(2008);
|
||||
vector.push_back(999999999);
|
||||
|
||||
Vector<uint8_t> byte_array = vector.to_byte_array();
|
||||
CHECK(byte_array.size() == 16);
|
||||
// vector[0]
|
||||
CHECK(byte_array[0] == 0);
|
||||
CHECK(byte_array[1] == 0);
|
||||
CHECK(byte_array[2] == 0);
|
||||
CHECK(byte_array[3] == 0);
|
||||
|
||||
// vector[1]
|
||||
CHECK(byte_array[4] == 255);
|
||||
CHECK(byte_array[5] == 255);
|
||||
CHECK(byte_array[6] == 255);
|
||||
CHECK(byte_array[7] == 255);
|
||||
|
||||
// vector[2]
|
||||
CHECK(byte_array[8] == 216);
|
||||
CHECK(byte_array[9] == 7);
|
||||
CHECK(byte_array[10] == 0);
|
||||
CHECK(byte_array[11] == 0);
|
||||
|
||||
// vector[3]
|
||||
CHECK(byte_array[12] == 255);
|
||||
CHECK(byte_array[13] == 201);
|
||||
CHECK(byte_array[14] == 154);
|
||||
CHECK(byte_array[15] == 59);
|
||||
}
|
||||
|
||||
TEST_CASE("[Vector] Slice") {
|
||||
Vector<int> vector;
|
||||
vector.push_back(0);
|
||||
vector.push_back(1);
|
||||
vector.push_back(2);
|
||||
vector.push_back(3);
|
||||
vector.push_back(4);
|
||||
|
||||
Vector<int> slice0 = vector.slice(0, 0);
|
||||
CHECK(slice0.size() == 0);
|
||||
|
||||
Vector<int> slice1 = vector.slice(1, 3);
|
||||
CHECK(slice1.size() == 2);
|
||||
CHECK(slice1[0] == 1);
|
||||
CHECK(slice1[1] == 2);
|
||||
|
||||
Vector<int> slice2 = vector.slice(1, -1);
|
||||
CHECK(slice2.size() == 3);
|
||||
CHECK(slice2[0] == 1);
|
||||
CHECK(slice2[1] == 2);
|
||||
CHECK(slice2[2] == 3);
|
||||
|
||||
Vector<int> slice3 = vector.slice(3);
|
||||
CHECK(slice3.size() == 2);
|
||||
CHECK(slice3[0] == 3);
|
||||
CHECK(slice3[1] == 4);
|
||||
|
||||
Vector<int> slice4 = vector.slice(2, -2);
|
||||
CHECK(slice4.size() == 1);
|
||||
CHECK(slice4[0] == 2);
|
||||
|
||||
Vector<int> slice5 = vector.slice(-2);
|
||||
CHECK(slice5.size() == 2);
|
||||
CHECK(slice5[0] == 3);
|
||||
CHECK(slice5[1] == 4);
|
||||
|
||||
Vector<int> slice6 = vector.slice(2, 42);
|
||||
CHECK(slice6.size() == 3);
|
||||
CHECK(slice6[0] == 2);
|
||||
CHECK(slice6[1] == 3);
|
||||
CHECK(slice6[2] == 4);
|
||||
|
||||
ERR_PRINT_OFF;
|
||||
Vector<int> slice7 = vector.slice(5, 1);
|
||||
CHECK(slice7.size() == 0); // Expected to fail.
|
||||
ERR_PRINT_ON;
|
||||
}
|
||||
|
||||
TEST_CASE("[Vector] Find, has") {
|
||||
Vector<int> vector;
|
||||
vector.push_back(3);
|
||||
vector.push_back(1);
|
||||
vector.push_back(4);
|
||||
vector.push_back(0);
|
||||
vector.push_back(2);
|
||||
|
||||
CHECK(vector[0] == 3);
|
||||
CHECK(vector[1] == 1);
|
||||
CHECK(vector[2] == 4);
|
||||
CHECK(vector[3] == 0);
|
||||
CHECK(vector[4] == 2);
|
||||
|
||||
CHECK(vector.find(0) == 3);
|
||||
CHECK(vector.find(1) == 1);
|
||||
CHECK(vector.find(2) == 4);
|
||||
CHECK(vector.find(3) == 0);
|
||||
CHECK(vector.find(4) == 2);
|
||||
|
||||
CHECK(vector.find(-1) == -1);
|
||||
CHECK(vector.find(5) == -1);
|
||||
|
||||
CHECK(vector.has(0));
|
||||
CHECK(vector.has(1));
|
||||
CHECK(vector.has(2));
|
||||
CHECK(vector.has(3));
|
||||
CHECK(vector.has(4));
|
||||
|
||||
CHECK(!vector.has(-1));
|
||||
CHECK(!vector.has(5));
|
||||
}
|
||||
|
||||
TEST_CASE("[Vector] Remove at") {
|
||||
Vector<int> vector;
|
||||
vector.push_back(0);
|
||||
vector.push_back(1);
|
||||
vector.push_back(2);
|
||||
vector.push_back(3);
|
||||
vector.push_back(4);
|
||||
|
||||
vector.remove_at(0);
|
||||
|
||||
CHECK(vector[0] == 1);
|
||||
CHECK(vector[1] == 2);
|
||||
CHECK(vector[2] == 3);
|
||||
CHECK(vector[3] == 4);
|
||||
|
||||
vector.remove_at(2);
|
||||
|
||||
CHECK(vector[0] == 1);
|
||||
CHECK(vector[1] == 2);
|
||||
CHECK(vector[2] == 4);
|
||||
|
||||
vector.remove_at(1);
|
||||
|
||||
CHECK(vector[0] == 1);
|
||||
CHECK(vector[1] == 4);
|
||||
|
||||
vector.remove_at(0);
|
||||
|
||||
CHECK(vector[0] == 4);
|
||||
}
|
||||
|
||||
TEST_CASE("[Vector] Remove at and find") {
|
||||
Vector<int> vector;
|
||||
vector.push_back(0);
|
||||
vector.push_back(1);
|
||||
vector.push_back(2);
|
||||
vector.push_back(3);
|
||||
vector.push_back(4);
|
||||
|
||||
CHECK(vector.size() == 5);
|
||||
|
||||
vector.remove_at(0);
|
||||
|
||||
CHECK(vector.size() == 4);
|
||||
|
||||
CHECK(vector.find(0) == -1);
|
||||
CHECK(vector.find(1) != -1);
|
||||
CHECK(vector.find(2) != -1);
|
||||
CHECK(vector.find(3) != -1);
|
||||
CHECK(vector.find(4) != -1);
|
||||
|
||||
vector.remove_at(vector.find(3));
|
||||
|
||||
CHECK(vector.size() == 3);
|
||||
|
||||
CHECK(vector.find(3) == -1);
|
||||
CHECK(vector.find(1) != -1);
|
||||
CHECK(vector.find(2) != -1);
|
||||
CHECK(vector.find(4) != -1);
|
||||
|
||||
vector.remove_at(vector.find(2));
|
||||
|
||||
CHECK(vector.size() == 2);
|
||||
|
||||
CHECK(vector.find(2) == -1);
|
||||
CHECK(vector.find(1) != -1);
|
||||
CHECK(vector.find(4) != -1);
|
||||
|
||||
vector.remove_at(vector.find(4));
|
||||
|
||||
CHECK(vector.size() == 1);
|
||||
|
||||
CHECK(vector.find(4) == -1);
|
||||
CHECK(vector.find(1) != -1);
|
||||
|
||||
vector.remove_at(0);
|
||||
|
||||
CHECK(vector.is_empty());
|
||||
CHECK(vector.size() == 0);
|
||||
}
|
||||
|
||||
TEST_CASE("[Vector] Erase") {
|
||||
Vector<int> vector;
|
||||
vector.push_back(1);
|
||||
vector.push_back(3);
|
||||
vector.push_back(0);
|
||||
vector.push_back(2);
|
||||
vector.push_back(4);
|
||||
|
||||
CHECK(vector.find(2) == 3);
|
||||
|
||||
vector.erase(2);
|
||||
|
||||
CHECK(vector.find(2) == -1);
|
||||
CHECK(vector.size() == 4);
|
||||
}
|
||||
|
||||
TEST_CASE("[Vector] Size, resize, reserve") {
|
||||
Vector<int> vector;
|
||||
CHECK(vector.is_empty());
|
||||
CHECK(vector.size() == 0);
|
||||
|
||||
vector.resize(10);
|
||||
|
||||
CHECK(vector.size() == 10);
|
||||
|
||||
vector.resize(5);
|
||||
|
||||
CHECK(vector.size() == 5);
|
||||
|
||||
vector.remove_at(0);
|
||||
vector.remove_at(0);
|
||||
vector.remove_at(0);
|
||||
|
||||
CHECK(vector.size() == 2);
|
||||
|
||||
vector.clear();
|
||||
|
||||
CHECK(vector.size() == 0);
|
||||
CHECK(vector.is_empty());
|
||||
|
||||
vector.push_back(0);
|
||||
vector.push_back(0);
|
||||
vector.push_back(0);
|
||||
|
||||
CHECK(vector.size() == 3);
|
||||
|
||||
vector.push_back(0);
|
||||
|
||||
CHECK(vector.size() == 4);
|
||||
}
|
||||
|
||||
TEST_CASE("[Vector] Sort") {
|
||||
Vector<int> vector;
|
||||
vector.push_back(2);
|
||||
vector.push_back(8);
|
||||
vector.push_back(-4);
|
||||
vector.push_back(5);
|
||||
vector.sort();
|
||||
|
||||
CHECK(vector.size() == 4);
|
||||
CHECK(vector[0] == -4);
|
||||
CHECK(vector[1] == 2);
|
||||
CHECK(vector[2] == 5);
|
||||
CHECK(vector[3] == 8);
|
||||
}
|
||||
|
||||
TEST_CASE("[Vector] Sort custom") {
|
||||
Vector<String> vector;
|
||||
vector.push_back("world");
|
||||
vector.push_back("World");
|
||||
vector.push_back("Hello");
|
||||
vector.push_back("10Hello");
|
||||
vector.push_back("12Hello");
|
||||
vector.push_back("01Hello");
|
||||
vector.push_back("1Hello");
|
||||
vector.push_back(".Hello");
|
||||
vector.sort_custom<NaturalNoCaseComparator>();
|
||||
|
||||
CHECK(vector.size() == 8);
|
||||
CHECK(vector[0] == ".Hello");
|
||||
CHECK(vector[1] == "01Hello");
|
||||
CHECK(vector[2] == "1Hello");
|
||||
CHECK(vector[3] == "10Hello");
|
||||
CHECK(vector[4] == "12Hello");
|
||||
CHECK(vector[5] == "Hello");
|
||||
CHECK(vector[6] == "world");
|
||||
CHECK(vector[7] == "World");
|
||||
}
|
||||
|
||||
TEST_CASE("[Vector] Search") {
|
||||
Vector<int> vector;
|
||||
vector.push_back(1);
|
||||
vector.push_back(2);
|
||||
vector.push_back(3);
|
||||
vector.push_back(5);
|
||||
vector.push_back(8);
|
||||
CHECK(vector.bsearch(2, true) == 1);
|
||||
CHECK(vector.bsearch(2, false) == 2);
|
||||
CHECK(vector.bsearch(5, true) == 3);
|
||||
CHECK(vector.bsearch(5, false) == 4);
|
||||
}
|
||||
|
||||
TEST_CASE("[Vector] Operators") {
|
||||
Vector<int> vector;
|
||||
vector.push_back(2);
|
||||
vector.push_back(8);
|
||||
vector.push_back(-4);
|
||||
vector.push_back(5);
|
||||
|
||||
Vector<int> vector_other;
|
||||
vector_other.push_back(2);
|
||||
vector_other.push_back(8);
|
||||
vector_other.push_back(-4);
|
||||
vector_other.push_back(5);
|
||||
|
||||
CHECK(vector == vector_other);
|
||||
|
||||
vector_other.push_back(10);
|
||||
CHECK(vector != vector_other);
|
||||
}
|
||||
|
||||
struct CyclicVectorHolder {
|
||||
Vector<CyclicVectorHolder> *vector = nullptr;
|
||||
bool is_destructing = false;
|
||||
|
||||
~CyclicVectorHolder() {
|
||||
if (is_destructing) {
|
||||
// The vector must exist and not expose its backing array at this point.
|
||||
CHECK_NE(vector, nullptr);
|
||||
CHECK_EQ(vector->ptr(), nullptr);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
TEST_CASE("[Vector] Cyclic Reference") {
|
||||
// Create a stack-space vector.
|
||||
Vector<CyclicVectorHolder> vector;
|
||||
// Add a new (empty) element.
|
||||
vector.resize(1);
|
||||
// Expose the vector to its element through CyclicVectorHolder.
|
||||
// This is questionable behavior, but should still behave graciously.
|
||||
vector.ptrw()[0] = CyclicVectorHolder{ &vector };
|
||||
vector.ptrw()[0].is_destructing = true;
|
||||
// The vector goes out of scope and destructs, calling CyclicVectorHolder's destructor.
|
||||
}
|
||||
|
||||
} // namespace TestVector
|
||||
99
tests/core/templates/test_vset.h
Normal file
@@ -0,0 +1,99 @@
|
||||
/**************************************************************************/
|
||||
/* test_vset.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/templates/vset.h"
|
||||
|
||||
#include "tests/test_macros.h"
|
||||
|
||||
namespace TestVSet {
|
||||
|
||||
template <typename T>
|
||||
class TestClass : public VSet<T> {
|
||||
public:
|
||||
int _find(const T &p_val, bool &r_exact) const {
|
||||
return VSet<T>::_find(p_val, r_exact);
|
||||
}
|
||||
};
|
||||
|
||||
TEST_CASE("[VSet] _find and _find_exact correctness.") {
|
||||
TestClass<int> set;
|
||||
|
||||
// insert some values
|
||||
set.insert(10);
|
||||
set.insert(20);
|
||||
set.insert(30);
|
||||
set.insert(40);
|
||||
set.insert(50);
|
||||
|
||||
// data should be sorted
|
||||
CHECK(set.size() == 5);
|
||||
CHECK(set[0] == 10);
|
||||
CHECK(set[1] == 20);
|
||||
CHECK(set[2] == 30);
|
||||
CHECK(set[3] == 40);
|
||||
CHECK(set[4] == 50);
|
||||
|
||||
// _find_exact return exact position for existing elements
|
||||
CHECK(set.find(10) == 0);
|
||||
CHECK(set.find(30) == 2);
|
||||
CHECK(set.find(50) == 4);
|
||||
|
||||
// _find_exact return -1 for non-existing elements
|
||||
CHECK(set.find(15) == -1);
|
||||
CHECK(set.find(0) == -1);
|
||||
CHECK(set.find(60) == -1);
|
||||
|
||||
// test _find
|
||||
bool exact;
|
||||
|
||||
// existing elements
|
||||
CHECK(set._find(10, exact) == 0);
|
||||
CHECK(exact == true);
|
||||
|
||||
CHECK(set._find(30, exact) == 2);
|
||||
CHECK(exact == true);
|
||||
|
||||
// non-existing elements
|
||||
CHECK(set._find(25, exact) == 2);
|
||||
CHECK(exact == false);
|
||||
|
||||
CHECK(set._find(35, exact) == 3);
|
||||
CHECK(exact == false);
|
||||
|
||||
CHECK(set._find(5, exact) == 0);
|
||||
CHECK(exact == false);
|
||||
|
||||
CHECK(set._find(60, exact) == 5);
|
||||
CHECK(exact == false);
|
||||
}
|
||||
|
||||
} // namespace TestVSet
|
||||
70
tests/core/test_crypto.h
Normal file
@@ -0,0 +1,70 @@
|
||||
/**************************************************************************/
|
||||
/* test_crypto.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/crypto/crypto.h"
|
||||
#include "tests/test_macros.h"
|
||||
|
||||
namespace TestCrypto {
|
||||
|
||||
class _MockCrypto : public Crypto {
|
||||
virtual PackedByteArray generate_random_bytes(int p_bytes) { return PackedByteArray(); }
|
||||
virtual Ref<CryptoKey> generate_rsa(int p_bytes) { return nullptr; }
|
||||
virtual Ref<X509Certificate> generate_self_signed_certificate(Ref<CryptoKey> p_key, const String &p_issuer_name, const String &p_not_before, const String &p_not_after) { return nullptr; }
|
||||
|
||||
virtual Vector<uint8_t> sign(HashingContext::HashType p_hash_type, const Vector<uint8_t> &p_hash, Ref<CryptoKey> p_key) { return Vector<uint8_t>(); }
|
||||
virtual bool verify(HashingContext::HashType p_hash_type, const Vector<uint8_t> &p_hash, const Vector<uint8_t> &p_signature, Ref<CryptoKey> p_key) { return false; }
|
||||
virtual Vector<uint8_t> encrypt(Ref<CryptoKey> p_key, const Vector<uint8_t> &p_plaintext) { return Vector<uint8_t>(); }
|
||||
virtual Vector<uint8_t> decrypt(Ref<CryptoKey> p_key, const Vector<uint8_t> &p_ciphertext) { return Vector<uint8_t>(); }
|
||||
virtual PackedByteArray hmac_digest(HashingContext::HashType p_hash_type, const PackedByteArray &p_key, const PackedByteArray &p_msg) { return PackedByteArray(); }
|
||||
};
|
||||
|
||||
PackedByteArray raw_to_pba(const uint8_t *arr, size_t len) {
|
||||
PackedByteArray pba;
|
||||
pba.resize(len);
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
pba.set(i, arr[i]);
|
||||
}
|
||||
return pba;
|
||||
}
|
||||
|
||||
TEST_CASE("[Crypto] PackedByteArray constant time compare") {
|
||||
const uint8_t hm1[] = { 144, 140, 176, 38, 88, 113, 101, 45, 71, 105, 10, 91, 248, 16, 117, 244, 189, 30, 238, 29, 219, 134, 82, 130, 212, 114, 161, 166, 188, 169, 200, 106 };
|
||||
const uint8_t hm2[] = { 80, 30, 144, 228, 108, 38, 188, 125, 150, 64, 165, 127, 221, 118, 144, 232, 45, 100, 15, 248, 193, 244, 245, 34, 116, 147, 132, 200, 110, 27, 38, 75 };
|
||||
PackedByteArray p1 = raw_to_pba(hm1, std::size(hm1));
|
||||
PackedByteArray p2 = raw_to_pba(hm2, std::size(hm2));
|
||||
_MockCrypto crypto;
|
||||
bool equal = crypto.constant_time_compare(p1, p1);
|
||||
CHECK(equal);
|
||||
equal = crypto.constant_time_compare(p1, p2);
|
||||
CHECK(!equal);
|
||||
}
|
||||
} // namespace TestCrypto
|
||||
162
tests/core/test_hashing_context.h
Normal file
@@ -0,0 +1,162 @@
|
||||
/**************************************************************************/
|
||||
/* test_hashing_context.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/crypto/hashing_context.h"
|
||||
|
||||
#include "tests/test_macros.h"
|
||||
|
||||
namespace TestHashingContext {
|
||||
|
||||
TEST_CASE("[HashingContext] Default - MD5/SHA1/SHA256") {
|
||||
HashingContext ctx;
|
||||
|
||||
static const uint8_t md5_expected[] = {
|
||||
0xd4, 0x1d, 0x8c, 0xd9, 0x8f, 0x00, 0xb2, 0x04, 0xe9, 0x80, 0x09, 0x98, 0xec, 0xf8, 0x42, 0x7e
|
||||
};
|
||||
static const uint8_t sha1_expected[] = {
|
||||
0xda, 0x39, 0xa3, 0xee, 0x5e, 0x6b, 0x4b, 0x0d, 0x32, 0x55, 0xbf, 0xef, 0x95, 0x60, 0x18, 0x90,
|
||||
0xaf, 0xd8, 0x07, 0x09
|
||||
};
|
||||
static const uint8_t sha256_expected[] = {
|
||||
0xe3, 0xb0, 0xc4, 0x42, 0x98, 0xfc, 0x1c, 0x14, 0x9a, 0xfb, 0xf4, 0xc8, 0x99, 0x6f, 0xb9, 0x24,
|
||||
0x27, 0xae, 0x41, 0xe4, 0x64, 0x9b, 0x93, 0x4c, 0xa4, 0x95, 0x99, 0x1b, 0x78, 0x52, 0xb8, 0x55
|
||||
};
|
||||
|
||||
CHECK(ctx.start(HashingContext::HASH_MD5) == OK);
|
||||
PackedByteArray result = ctx.finish();
|
||||
REQUIRE(result.size() == 16);
|
||||
CHECK(memcmp(result.ptr(), md5_expected, 16) == 0);
|
||||
|
||||
CHECK(ctx.start(HashingContext::HASH_SHA1) == OK);
|
||||
result = ctx.finish();
|
||||
REQUIRE(result.size() == 20);
|
||||
CHECK(memcmp(result.ptr(), sha1_expected, 20) == 0);
|
||||
|
||||
CHECK(ctx.start(HashingContext::HASH_SHA256) == OK);
|
||||
result = ctx.finish();
|
||||
REQUIRE(result.size() == 32);
|
||||
CHECK(memcmp(result.ptr(), sha256_expected, 32) == 0);
|
||||
}
|
||||
|
||||
TEST_CASE("[HashingContext] Multiple updates - MD5/SHA1/SHA256") {
|
||||
HashingContext ctx;
|
||||
const String s = "xyz";
|
||||
|
||||
const PackedByteArray s_byte_parts[] = {
|
||||
String("x").to_ascii_buffer(),
|
||||
String("y").to_ascii_buffer(),
|
||||
String("z").to_ascii_buffer()
|
||||
};
|
||||
|
||||
static const uint8_t md5_expected[] = {
|
||||
0xd1, 0x6f, 0xb3, 0x6f, 0x09, 0x11, 0xf8, 0x78, 0x99, 0x8c, 0x13, 0x61, 0x91, 0xaf, 0x70, 0x5e
|
||||
};
|
||||
static const uint8_t sha1_expected[] = {
|
||||
0x66, 0xb2, 0x74, 0x17, 0xd3, 0x7e, 0x02, 0x4c, 0x46, 0x52, 0x6c, 0x2f, 0x6d, 0x35, 0x8a, 0x75,
|
||||
0x4f, 0xc5, 0x52, 0xf3
|
||||
};
|
||||
static const uint8_t sha256_expected[] = {
|
||||
0x36, 0x08, 0xbc, 0xa1, 0xe4, 0x4e, 0xa6, 0xc4, 0xd2, 0x68, 0xeb, 0x6d, 0xb0, 0x22, 0x60, 0x26,
|
||||
0x98, 0x92, 0xc0, 0xb4, 0x2b, 0x86, 0xbb, 0xf1, 0xe7, 0x7a, 0x6f, 0xa1, 0x6c, 0x3c, 0x92, 0x82
|
||||
};
|
||||
|
||||
CHECK(ctx.start(HashingContext::HASH_MD5) == OK);
|
||||
CHECK(ctx.update(s_byte_parts[0]) == OK);
|
||||
CHECK(ctx.update(s_byte_parts[1]) == OK);
|
||||
CHECK(ctx.update(s_byte_parts[2]) == OK);
|
||||
PackedByteArray result = ctx.finish();
|
||||
REQUIRE(result.size() == 16);
|
||||
CHECK(memcmp(result.ptr(), md5_expected, 16) == 0);
|
||||
|
||||
CHECK(ctx.start(HashingContext::HASH_SHA1) == OK);
|
||||
CHECK(ctx.update(s_byte_parts[0]) == OK);
|
||||
CHECK(ctx.update(s_byte_parts[1]) == OK);
|
||||
CHECK(ctx.update(s_byte_parts[2]) == OK);
|
||||
result = ctx.finish();
|
||||
REQUIRE(result.size() == 20);
|
||||
CHECK(memcmp(result.ptr(), sha1_expected, 20) == 0);
|
||||
|
||||
CHECK(ctx.start(HashingContext::HASH_SHA256) == OK);
|
||||
CHECK(ctx.update(s_byte_parts[0]) == OK);
|
||||
CHECK(ctx.update(s_byte_parts[1]) == OK);
|
||||
CHECK(ctx.update(s_byte_parts[2]) == OK);
|
||||
result = ctx.finish();
|
||||
REQUIRE(result.size() == 32);
|
||||
CHECK(memcmp(result.ptr(), sha256_expected, 32) == 0);
|
||||
}
|
||||
|
||||
TEST_CASE("[HashingContext] Invalid use of start") {
|
||||
HashingContext ctx;
|
||||
|
||||
ERR_PRINT_OFF;
|
||||
CHECK_MESSAGE(
|
||||
ctx.start(static_cast<HashingContext::HashType>(-1)) == ERR_UNAVAILABLE,
|
||||
"Using invalid hash types should fail.");
|
||||
ERR_PRINT_ON;
|
||||
|
||||
REQUIRE(ctx.start(HashingContext::HASH_MD5) == OK);
|
||||
|
||||
ERR_PRINT_OFF;
|
||||
CHECK_MESSAGE(
|
||||
ctx.start(HashingContext::HASH_MD5) == ERR_ALREADY_IN_USE,
|
||||
"Calling 'start' twice before 'finish' should fail.");
|
||||
ERR_PRINT_ON;
|
||||
}
|
||||
|
||||
TEST_CASE("[HashingContext] Invalid use of update") {
|
||||
HashingContext ctx;
|
||||
|
||||
ERR_PRINT_OFF;
|
||||
CHECK_MESSAGE(
|
||||
ctx.update(PackedByteArray()) == ERR_UNCONFIGURED,
|
||||
"Calling 'update' before 'start' should fail.");
|
||||
ERR_PRINT_ON;
|
||||
|
||||
REQUIRE(ctx.start(HashingContext::HASH_MD5) == OK);
|
||||
|
||||
ERR_PRINT_OFF;
|
||||
CHECK_MESSAGE(
|
||||
ctx.update(PackedByteArray()) == FAILED,
|
||||
"Calling 'update' with an empty byte array should fail.");
|
||||
ERR_PRINT_ON;
|
||||
}
|
||||
|
||||
TEST_CASE("[HashingContext] Invalid use of finish") {
|
||||
HashingContext ctx;
|
||||
|
||||
ERR_PRINT_OFF;
|
||||
CHECK_MESSAGE(
|
||||
ctx.finish() == PackedByteArray(),
|
||||
"Calling 'finish' before 'start' should return an empty byte array.");
|
||||
ERR_PRINT_ON;
|
||||
}
|
||||
} // namespace TestHashingContext
|
||||
145
tests/core/test_time.h
Normal file
@@ -0,0 +1,145 @@
|
||||
/**************************************************************************/
|
||||
/* test_time.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/os/time.h"
|
||||
|
||||
#include "thirdparty/doctest/doctest.h"
|
||||
|
||||
#define YEAR_KEY "year"
|
||||
#define MONTH_KEY "month"
|
||||
#define DAY_KEY "day"
|
||||
#define WEEKDAY_KEY "weekday"
|
||||
#define HOUR_KEY "hour"
|
||||
#define MINUTE_KEY "minute"
|
||||
#define SECOND_KEY "second"
|
||||
#define DST_KEY "dst"
|
||||
|
||||
namespace TestTime {
|
||||
|
||||
TEST_CASE("[Time] Unix time conversion to/from datetime string") {
|
||||
const Time *time = Time::get_singleton();
|
||||
|
||||
CHECK_MESSAGE(time->get_unix_time_from_datetime_string("1970-01-01T00:00:00") == 0, "Time get_unix_time_from_datetime_string: The timestamp for Unix epoch is zero.");
|
||||
CHECK_MESSAGE(time->get_unix_time_from_datetime_string("1970-01-01 00:00:00") == 0, "Time get_unix_time_from_datetime_string: The timestamp for Unix epoch with space is zero.");
|
||||
CHECK_MESSAGE(time->get_unix_time_from_datetime_string("1970-01-01") == 0, "Time get_unix_time_from_datetime_string: The timestamp for Unix epoch without time is zero.");
|
||||
CHECK_MESSAGE(time->get_unix_time_from_datetime_string("00:00:00") == 0, "Time get_unix_time_from_datetime_string: The timestamp for zero time without date is zero.");
|
||||
CHECK_MESSAGE(time->get_unix_time_from_datetime_string("1969-12-31T23:59:59") == -1, "Time get_unix_time_from_datetime_string: The timestamp for just before Unix epoch is negative one.");
|
||||
CHECK_MESSAGE(time->get_unix_time_from_datetime_string("1234-05-06T07:08:09") == -23215049511, "Time get_unix_time_from_datetime_string: The timestamp for an arbitrary datetime is as expected.");
|
||||
CHECK_MESSAGE(time->get_unix_time_from_datetime_string("1234-05-06 07:08:09") == -23215049511, "Time get_unix_time_from_datetime_string: The timestamp for an arbitrary datetime with space is as expected.");
|
||||
CHECK_MESSAGE(time->get_unix_time_from_datetime_string("1234-05-06") == -23215075200, "Time get_unix_time_from_datetime_string: The timestamp for an arbitrary date without time is as expected.");
|
||||
CHECK_MESSAGE(time->get_unix_time_from_datetime_string("07:08:09") == 25689, "Time get_unix_time_from_datetime_string: The timestamp for an arbitrary time without date is as expected.");
|
||||
CHECK_MESSAGE(time->get_unix_time_from_datetime_string("2014-02-09T22:10:30") == 1391983830, "Time get_unix_time_from_datetime_string: The timestamp for GODOT IS OPEN SOURCE is as expected.");
|
||||
CHECK_MESSAGE(time->get_unix_time_from_datetime_string("2014-02-09 22:10:30") == 1391983830, "Time get_unix_time_from_datetime_string: The timestamp for GODOT IS OPEN SOURCE with space is as expected.");
|
||||
CHECK_MESSAGE(time->get_unix_time_from_datetime_string("2014-02-09") == 1391904000, "Time get_unix_time_from_datetime_string: The date for GODOT IS OPEN SOURCE without time is as expected.");
|
||||
CHECK_MESSAGE(time->get_unix_time_from_datetime_string("22:10:30") == 79830, "Time get_unix_time_from_datetime_string: The time for GODOT IS OPEN SOURCE without date is as expected.");
|
||||
CHECK_MESSAGE(time->get_unix_time_from_datetime_string("-1000000000-01-01T00:00:00") == -31557014167219200, "Time get_unix_time_from_datetime_string: In the year negative a billion, Japan might not have been here.");
|
||||
CHECK_MESSAGE(time->get_unix_time_from_datetime_string("1000000-01-01T00:00:00") == 31494784780800, "Time get_unix_time_from_datetime_string: The timestamp for the year a million is as expected.");
|
||||
|
||||
CHECK_MESSAGE(time->get_datetime_string_from_unix_time(0) == "1970-01-01T00:00:00", "Time get_datetime_string_from_unix_time: The timestamp string for Unix epoch is zero.");
|
||||
CHECK_MESSAGE(time->get_datetime_string_from_unix_time(0, true) == "1970-01-01 00:00:00", "Time get_datetime_string_from_unix_time: The timestamp string for Unix epoch with space is zero.");
|
||||
CHECK_MESSAGE(time->get_date_string_from_unix_time(0) == "1970-01-01", "Time get_date_string_from_unix_time: The date string for zero is Unix epoch date.");
|
||||
CHECK_MESSAGE(time->get_time_string_from_unix_time(0) == "00:00:00", "Time get_time_string_from_unix_time: The date for zero zero is Unix epoch date.");
|
||||
CHECK_MESSAGE(time->get_datetime_string_from_unix_time(-1) == "1969-12-31T23:59:59", "Time get_time_string_from_unix_time: The timestamp string for just before Unix epoch is as expected.");
|
||||
CHECK_MESSAGE(time->get_datetime_string_from_unix_time(-23215049511) == "1234-05-06T07:08:09", "Time get_datetime_string_from_unix_time: The timestamp for an arbitrary datetime is as expected.");
|
||||
CHECK_MESSAGE(time->get_datetime_string_from_unix_time(-23215049511, true) == "1234-05-06 07:08:09", "Time get_datetime_string_from_unix_time: The timestamp for an arbitrary datetime with space is as expected.");
|
||||
CHECK_MESSAGE(time->get_date_string_from_unix_time(-23215075200) == "1234-05-06", "Time get_date_string_from_unix_time: The timestamp for an arbitrary date without time is as expected.");
|
||||
CHECK_MESSAGE(time->get_time_string_from_unix_time(25689) == "07:08:09", "Time get_time_string_from_unix_time: The timestamp for an arbitrary time without date is as expected.");
|
||||
CHECK_MESSAGE(time->get_datetime_string_from_unix_time(1391983830) == "2014-02-09T22:10:30", "Time get_datetime_string_from_unix_time: The timestamp for GODOT IS OPEN SOURCE is as expected.");
|
||||
CHECK_MESSAGE(time->get_datetime_string_from_unix_time(1391983830, true) == "2014-02-09 22:10:30", "Time get_datetime_string_from_unix_time: The timestamp for GODOT IS OPEN SOURCE with space is as expected.");
|
||||
CHECK_MESSAGE(time->get_date_string_from_unix_time(1391904000) == "2014-02-09", "Time get_date_string_from_unix_time: The date for GODOT IS OPEN SOURCE without time is as expected.");
|
||||
CHECK_MESSAGE(time->get_time_string_from_unix_time(79830) == "22:10:30", "Time get_time_string_from_unix_time: The time for GODOT IS OPEN SOURCE without date is as expected.");
|
||||
CHECK_MESSAGE(time->get_datetime_string_from_unix_time(31494784780800) == "1000000-01-01T00:00:00", "Time get_datetime_string_from_unix_time: The timestamp for the year a million is as expected.");
|
||||
CHECK_MESSAGE(time->get_offset_string_from_offset_minutes(0) == "+00:00", "Time get_offset_string_from_offset_minutes: The offset string is as expected.");
|
||||
CHECK_MESSAGE(time->get_offset_string_from_offset_minutes(-600) == "-10:00", "Time get_offset_string_from_offset_minutes: The offset string is as expected.");
|
||||
CHECK_MESSAGE(time->get_offset_string_from_offset_minutes(345) == "+05:45", "Time get_offset_string_from_offset_minutes: The offset string is as expected.");
|
||||
}
|
||||
|
||||
TEST_CASE("[Time] Datetime dictionary conversion methods") {
|
||||
const Time *time = Time::get_singleton();
|
||||
|
||||
Dictionary datetime;
|
||||
datetime[YEAR_KEY] = 2014;
|
||||
datetime[MONTH_KEY] = 2;
|
||||
datetime[DAY_KEY] = 9;
|
||||
datetime[WEEKDAY_KEY] = (int64_t)Weekday::WEEKDAY_SUNDAY;
|
||||
datetime[HOUR_KEY] = 22;
|
||||
datetime[MINUTE_KEY] = 10;
|
||||
datetime[SECOND_KEY] = 30;
|
||||
|
||||
Dictionary date_only;
|
||||
date_only[YEAR_KEY] = 2014;
|
||||
date_only[MONTH_KEY] = 2;
|
||||
date_only[DAY_KEY] = 9;
|
||||
date_only[WEEKDAY_KEY] = (int64_t)Weekday::WEEKDAY_SUNDAY;
|
||||
|
||||
Dictionary time_only;
|
||||
time_only[HOUR_KEY] = 22;
|
||||
time_only[MINUTE_KEY] = 10;
|
||||
time_only[SECOND_KEY] = 30;
|
||||
|
||||
CHECK_MESSAGE(time->get_unix_time_from_datetime_dict(datetime) == 1391983830, "Time get_unix_time_from_datetime_dict: The datetime dictionary for GODOT IS OPEN SOURCE is converted to a timestamp as expected.");
|
||||
CHECK_MESSAGE(time->get_unix_time_from_datetime_dict(date_only) == 1391904000, "Time get_unix_time_from_datetime_dict: The date dictionary for GODOT IS OPEN SOURCE is converted to a timestamp as expected.");
|
||||
CHECK_MESSAGE(time->get_unix_time_from_datetime_dict(time_only) == 79830, "Time get_unix_time_from_datetime_dict: The time dictionary for GODOT IS OPEN SOURCE is converted to a timestamp as expected.");
|
||||
|
||||
CHECK_MESSAGE(time->get_datetime_dict_from_unix_time(1391983830).hash() == datetime.hash(), "Time get_datetime_dict_from_unix_time: The datetime timestamp for GODOT IS OPEN SOURCE is converted to a dictionary as expected.");
|
||||
CHECK_MESSAGE(time->get_date_dict_from_unix_time(1391904000).hash() == date_only.hash(), "Time get_date_dict_from_unix_time: The date timestamp for GODOT IS OPEN SOURCE is converted to a dictionary as expected.");
|
||||
CHECK_MESSAGE(time->get_time_dict_from_unix_time(79830).hash() == time_only.hash(), "Time get_time_dict_from_unix_time: The time timestamp for GODOT IS OPEN SOURCE is converted to a dictionary as expected.");
|
||||
|
||||
CHECK_MESSAGE((Weekday)(int)time->get_datetime_dict_from_unix_time(0)[WEEKDAY_KEY] == Weekday::WEEKDAY_THURSDAY, "Time get_datetime_dict_from_unix_time: The weekday for the Unix epoch is a Thursday as expected.");
|
||||
CHECK_MESSAGE((Weekday)(int)time->get_datetime_dict_from_unix_time(1391983830)[WEEKDAY_KEY] == Weekday::WEEKDAY_SUNDAY, "Time get_datetime_dict_from_unix_time: The weekday for GODOT IS OPEN SOURCE is a Sunday as expected.");
|
||||
|
||||
CHECK_MESSAGE(time->get_datetime_dict_from_datetime_string("2014-02-09T22:10:30").hash() == datetime.hash(), "Time get_datetime_dict_from_string: The dictionary from string for GODOT IS OPEN SOURCE works as expected.");
|
||||
CHECK_MESSAGE(!time->get_datetime_dict_from_datetime_string("2014-02-09T22:10:30", false).has(WEEKDAY_KEY), "Time get_datetime_dict_from_string: The dictionary from string for GODOT IS OPEN SOURCE without weekday doesn't contain the weekday key as expected.");
|
||||
CHECK_MESSAGE(time->get_datetime_string_from_datetime_dict(datetime) == "2014-02-09T22:10:30", "Time get_datetime_string_from_dict: The string from dictionary for GODOT IS OPEN SOURCE works as expected.");
|
||||
CHECK_MESSAGE(time->get_datetime_string_from_datetime_dict(time->get_datetime_dict_from_datetime_string("2014-02-09T22:10:30")) == "2014-02-09T22:10:30", "Time get_datetime_string_from_dict: The round-trip string to dict to string GODOT IS OPEN SOURCE works as expected.");
|
||||
CHECK_MESSAGE(time->get_datetime_string_from_datetime_dict(time->get_datetime_dict_from_datetime_string("2014-02-09 22:10:30"), true) == "2014-02-09 22:10:30", "Time get_datetime_string_from_dict: The round-trip string to dict to string GODOT IS OPEN SOURCE with spaces works as expected.");
|
||||
}
|
||||
|
||||
TEST_CASE("[Time] System time methods") {
|
||||
const Time *time = Time::get_singleton();
|
||||
|
||||
const uint64_t ticks_msec = time->get_ticks_msec();
|
||||
const uint64_t ticks_usec = time->get_ticks_usec();
|
||||
|
||||
CHECK_MESSAGE(time->get_unix_time_from_system() > 1000000000, "Time get_unix_time_from_system: The timestamp from system time doesn't fail and is very positive.");
|
||||
CHECK_MESSAGE(time->get_unix_time_from_datetime_dict(time->get_datetime_dict_from_system()) > 1000000000, "Time get_datetime_string_from_system: The timestamp from system time doesn't fail and is very positive.");
|
||||
CHECK_MESSAGE(time->get_unix_time_from_datetime_dict(time->get_date_dict_from_system()) > 1000000000, "Time get_datetime_string_from_system: The date from system time doesn't fail and is very positive.");
|
||||
CHECK_MESSAGE(time->get_unix_time_from_datetime_dict(time->get_time_dict_from_system()) < 86400, "Time get_datetime_string_from_system: The time from system time doesn't fail and is within the acceptable range.");
|
||||
CHECK_MESSAGE(time->get_unix_time_from_datetime_string(time->get_datetime_string_from_system()) > 1000000000, "Time get_datetime_string_from_system: The timestamp from system time doesn't fail and is very positive.");
|
||||
CHECK_MESSAGE(time->get_unix_time_from_datetime_string(time->get_date_string_from_system()) > 1000000000, "Time get_datetime_string_from_system: The date from system time doesn't fail and is very positive.");
|
||||
CHECK_MESSAGE(time->get_unix_time_from_datetime_string(time->get_time_string_from_system()) < 86400, "Time get_datetime_string_from_system: The time from system time doesn't fail and is within the acceptable range.");
|
||||
|
||||
CHECK_MESSAGE(time->get_ticks_msec() >= ticks_msec, "Time get_ticks_msec: The value has not decreased.");
|
||||
CHECK_MESSAGE(time->get_ticks_usec() > ticks_usec, "Time get_ticks_usec: The value has increased.");
|
||||
}
|
||||
|
||||
} // namespace TestTime
|
||||
175
tests/core/threads/test_worker_thread_pool.h
Normal file
@@ -0,0 +1,175 @@
|
||||
/**************************************************************************/
|
||||
/* test_worker_thread_pool.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/object/worker_thread_pool.h"
|
||||
|
||||
#include "tests/test_macros.h"
|
||||
|
||||
namespace TestWorkerThreadPool {
|
||||
|
||||
static LocalVector<SafeNumeric<int>> counter;
|
||||
static SafeFlag exit;
|
||||
|
||||
static void static_test(void *p_arg) {
|
||||
counter[(uint64_t)p_arg].increment();
|
||||
counter[0].add(2);
|
||||
}
|
||||
static void static_callable_test() {
|
||||
counter[0].sub(2);
|
||||
}
|
||||
TEST_CASE("[WorkerThreadPool] Process threads using individual tasks") {
|
||||
for (int iterations = 0; iterations < 500; iterations++) {
|
||||
const int count = Math::pow(2.0f, Math::random(0.0f, 5.0f));
|
||||
const bool low_priority = Math::rand() % 2;
|
||||
|
||||
LocalVector<WorkerThreadPool::TaskID> tasks1;
|
||||
LocalVector<WorkerThreadPool::TaskID> tasks2;
|
||||
tasks1.resize(count);
|
||||
tasks2.resize(count);
|
||||
|
||||
counter.clear();
|
||||
counter.resize(count);
|
||||
for (int i = 0; i < count; i++) {
|
||||
tasks1[i] = WorkerThreadPool::get_singleton()->add_native_task(static_test, (void *)(uintptr_t)i, low_priority);
|
||||
tasks2[i] = WorkerThreadPool::get_singleton()->add_task(callable_mp_static(static_callable_test), !low_priority);
|
||||
}
|
||||
for (int i = 0; i < count; i++) {
|
||||
WorkerThreadPool::get_singleton()->wait_for_task_completion(tasks1[i]);
|
||||
WorkerThreadPool::get_singleton()->wait_for_task_completion(tasks2[i]);
|
||||
}
|
||||
|
||||
bool all_run_once = true;
|
||||
for (int i = 0; i < count; i++) {
|
||||
//Reduce number of check messages
|
||||
all_run_once &= counter[i].get() == 1;
|
||||
}
|
||||
CHECK(all_run_once);
|
||||
}
|
||||
}
|
||||
|
||||
static void static_group_test(void *p_arg, uint32_t p_index) {
|
||||
counter[p_index].increment();
|
||||
counter[0].add((uintptr_t)p_arg);
|
||||
}
|
||||
static void static_callable_group_test(uint32_t p_index) {
|
||||
counter[p_index].increment();
|
||||
counter[0].sub(2);
|
||||
}
|
||||
TEST_CASE("[WorkerThreadPool] Process elements using group tasks") {
|
||||
for (int iterations = 0; iterations < 500; iterations++) {
|
||||
const int count = Math::pow(2.0f, Math::random(0.0f, 5.0f));
|
||||
const int tasks = Math::pow(2.0f, Math::random(0.0f, 5.0f));
|
||||
const bool low_priority = Math::rand() % 2;
|
||||
|
||||
counter.clear();
|
||||
counter.resize(count);
|
||||
WorkerThreadPool::GroupID group1 = WorkerThreadPool::get_singleton()->add_native_group_task(static_group_test, (void *)2, count, tasks, !low_priority);
|
||||
WorkerThreadPool::GroupID group2 = WorkerThreadPool::get_singleton()->add_group_task(callable_mp_static(static_callable_group_test), count, tasks, low_priority);
|
||||
WorkerThreadPool::get_singleton()->wait_for_group_task_completion(group1);
|
||||
WorkerThreadPool::get_singleton()->wait_for_group_task_completion(group2);
|
||||
|
||||
bool all_run_once = true;
|
||||
for (int i = 0; i < count; i++) {
|
||||
//Reduce number of check messages
|
||||
all_run_once &= counter[i].get() == 2;
|
||||
}
|
||||
CHECK(all_run_once);
|
||||
}
|
||||
}
|
||||
|
||||
static void static_test_daemon(void *p_arg) {
|
||||
while (!exit.is_set()) {
|
||||
counter[0].add(1);
|
||||
WorkerThreadPool::get_singleton()->yield();
|
||||
}
|
||||
}
|
||||
|
||||
static void static_busy_task(void *p_arg) {
|
||||
while (!exit.is_set()) {
|
||||
OS::get_singleton()->delay_usec(1);
|
||||
}
|
||||
}
|
||||
|
||||
static void static_legit_task(void *p_arg) {
|
||||
*((bool *)p_arg) = counter[0].get() > 0;
|
||||
counter[1].add(1);
|
||||
}
|
||||
|
||||
TEST_CASE("[WorkerThreadPool] Run a yielding daemon as the only hope for other tasks to run") {
|
||||
exit.clear();
|
||||
counter.clear();
|
||||
counter.resize(2);
|
||||
|
||||
WorkerThreadPool::TaskID daemon_task_id = WorkerThreadPool::get_singleton()->add_native_task(static_test_daemon, nullptr, true);
|
||||
|
||||
int num_threads = WorkerThreadPool::get_singleton()->get_thread_count();
|
||||
|
||||
// Keep all the other threads busy.
|
||||
LocalVector<WorkerThreadPool::TaskID> task_ids;
|
||||
for (int i = 0; i < num_threads - 1; i++) {
|
||||
task_ids.push_back(WorkerThreadPool::get_singleton()->add_native_task(static_busy_task, nullptr, true));
|
||||
}
|
||||
|
||||
LocalVector<WorkerThreadPool::TaskID> legit_task_ids;
|
||||
LocalVector<bool> legit_task_needed_yield;
|
||||
int legit_tasks_count = num_threads * 4;
|
||||
legit_task_needed_yield.resize(legit_tasks_count);
|
||||
for (int i = 0; i < legit_tasks_count; i++) {
|
||||
legit_task_needed_yield[i] = false;
|
||||
task_ids.push_back(WorkerThreadPool::get_singleton()->add_native_task(static_legit_task, &legit_task_needed_yield[i], i >= legit_tasks_count / 2));
|
||||
}
|
||||
|
||||
while (counter[1].get() != legit_tasks_count) {
|
||||
OS::get_singleton()->delay_usec(1);
|
||||
}
|
||||
|
||||
exit.set();
|
||||
for (uint32_t i = 0; i < task_ids.size(); i++) {
|
||||
WorkerThreadPool::get_singleton()->wait_for_task_completion(task_ids[i]);
|
||||
}
|
||||
WorkerThreadPool::get_singleton()->notify_yield_over(daemon_task_id);
|
||||
WorkerThreadPool::get_singleton()->wait_for_task_completion(daemon_task_id);
|
||||
|
||||
CHECK_MESSAGE(counter[0].get() > 0, "Daemon task should have looped at least once.");
|
||||
CHECK_MESSAGE(counter[1].get() == legit_tasks_count, "All legit tasks should have been able to run.");
|
||||
|
||||
bool all_needed_yield = true;
|
||||
for (int i = 0; i < legit_tasks_count; i++) {
|
||||
if (!legit_task_needed_yield[i]) {
|
||||
all_needed_yield = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
CHECK_MESSAGE(all_needed_yield, "All legit tasks should have needed the daemon yielding to run.");
|
||||
}
|
||||
|
||||
} // namespace TestWorkerThreadPool
|
||||
647
tests/core/variant/test_array.h
Normal file
@@ -0,0 +1,647 @@
|
||||
/**************************************************************************/
|
||||
/* test_array.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/variant/array.h"
|
||||
#include "tests/test_macros.h"
|
||||
#include "tests/test_tools.h"
|
||||
|
||||
namespace TestArray {
|
||||
TEST_CASE("[Array] initializer list") {
|
||||
Array arr = { 0, 1, "test", true, { 0.0, 1.0 } };
|
||||
CHECK(arr.size() == 5);
|
||||
CHECK(arr[0] == Variant(0));
|
||||
CHECK(arr[1] == Variant(1));
|
||||
CHECK(arr[2] == Variant("test"));
|
||||
CHECK(arr[3] == Variant(true));
|
||||
CHECK(arr[4] == Variant({ 0.0, 1.0 }));
|
||||
|
||||
arr = { "reassign" };
|
||||
CHECK(arr.size() == 1);
|
||||
CHECK(arr[0] == Variant("reassign"));
|
||||
|
||||
TypedArray<int> typed_arr = { 0, 1, 2 };
|
||||
CHECK(typed_arr.size() == 3);
|
||||
CHECK(typed_arr[0] == Variant(0));
|
||||
CHECK(typed_arr[1] == Variant(1));
|
||||
CHECK(typed_arr[2] == Variant(2));
|
||||
}
|
||||
|
||||
TEST_CASE("[Array] size(), clear(), and is_empty()") {
|
||||
Array arr;
|
||||
CHECK(arr.size() == 0);
|
||||
CHECK(arr.is_empty());
|
||||
arr.push_back(1);
|
||||
CHECK(arr.size() == 1);
|
||||
arr.clear();
|
||||
CHECK(arr.is_empty());
|
||||
CHECK(arr.size() == 0);
|
||||
}
|
||||
|
||||
TEST_CASE("[Array] Assignment and comparison operators") {
|
||||
Array arr1;
|
||||
Array arr2;
|
||||
arr1.push_back(1);
|
||||
CHECK(arr1 != arr2);
|
||||
CHECK(arr1 > arr2);
|
||||
CHECK(arr1 >= arr2);
|
||||
arr2.push_back(2);
|
||||
CHECK(arr1 != arr2);
|
||||
CHECK(arr1 < arr2);
|
||||
CHECK(arr1 <= arr2);
|
||||
CHECK(arr2 > arr1);
|
||||
CHECK(arr2 >= arr1);
|
||||
Array arr3 = arr2;
|
||||
CHECK(arr3 == arr2);
|
||||
}
|
||||
|
||||
TEST_CASE("[Array] append_array()") {
|
||||
Array arr1;
|
||||
Array arr2;
|
||||
arr1.push_back(1);
|
||||
arr1.append_array(arr2);
|
||||
CHECK(arr1.size() == 1);
|
||||
arr2.push_back(2);
|
||||
arr1.append_array(arr2);
|
||||
CHECK(arr1.size() == 2);
|
||||
CHECK(int(arr1[0]) == 1);
|
||||
CHECK(int(arr1[1]) == 2);
|
||||
}
|
||||
|
||||
TEST_CASE("[Array] resize(), insert(), and erase()") {
|
||||
Array arr;
|
||||
arr.resize(2);
|
||||
CHECK(arr.size() == 2);
|
||||
arr.insert(0, 1);
|
||||
CHECK(int(arr[0]) == 1);
|
||||
arr.insert(0, 2);
|
||||
CHECK(int(arr[0]) == 2);
|
||||
arr.erase(2);
|
||||
CHECK(int(arr[0]) == 1);
|
||||
arr.resize(0);
|
||||
CHECK(arr.size() == 0);
|
||||
arr.insert(0, 8);
|
||||
CHECK(arr.size() == 1);
|
||||
arr.insert(1, 16);
|
||||
CHECK(int(arr[1]) == 16);
|
||||
arr.insert(-1, 3);
|
||||
CHECK(int(arr[1]) == 3);
|
||||
}
|
||||
|
||||
TEST_CASE("[Array] front() and back()") {
|
||||
Array arr;
|
||||
arr.push_back(1);
|
||||
CHECK(int(arr.front()) == 1);
|
||||
CHECK(int(arr.back()) == 1);
|
||||
arr.push_back(3);
|
||||
CHECK(int(arr.front()) == 1);
|
||||
CHECK(int(arr.back()) == 3);
|
||||
}
|
||||
|
||||
TEST_CASE("[Array] has() and count()") {
|
||||
Array arr = { 1, 1 };
|
||||
CHECK(arr.has(1));
|
||||
CHECK(!arr.has(2));
|
||||
CHECK(arr.count(1) == 2);
|
||||
CHECK(arr.count(2) == 0);
|
||||
}
|
||||
|
||||
TEST_CASE("[Array] remove_at()") {
|
||||
Array arr = { 1, 2 };
|
||||
arr.remove_at(0);
|
||||
CHECK(arr.size() == 1);
|
||||
CHECK(int(arr[0]) == 2);
|
||||
arr.remove_at(0);
|
||||
CHECK(arr.size() == 0);
|
||||
|
||||
// Negative index.
|
||||
arr.push_back(3);
|
||||
arr.push_back(4);
|
||||
arr.remove_at(-1);
|
||||
CHECK(arr.size() == 1);
|
||||
CHECK(int(arr[0]) == 3);
|
||||
arr.remove_at(-1);
|
||||
CHECK(arr.size() == 0);
|
||||
|
||||
// The array is now empty; try to use `remove_at()` again.
|
||||
// Normally, this prints an error message so we silence it.
|
||||
ERR_PRINT_OFF;
|
||||
arr.remove_at(0);
|
||||
ERR_PRINT_ON;
|
||||
|
||||
CHECK(arr.size() == 0);
|
||||
}
|
||||
|
||||
TEST_CASE("[Array] get()") {
|
||||
Array arr = { 1 };
|
||||
CHECK(int(arr.get(0)) == 1);
|
||||
}
|
||||
|
||||
TEST_CASE("[Array] sort()") {
|
||||
Array arr = { 3, 4, 2, 1 };
|
||||
arr.sort();
|
||||
int val = 1;
|
||||
for (int i = 0; i < arr.size(); i++) {
|
||||
CHECK(int(arr[i]) == val);
|
||||
val++;
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("[Array] push_front(), pop_front(), pop_back()") {
|
||||
Array arr;
|
||||
arr.push_front(1);
|
||||
arr.push_front(2);
|
||||
CHECK(int(arr[0]) == 2);
|
||||
arr.pop_front();
|
||||
CHECK(int(arr[0]) == 1);
|
||||
CHECK(arr.size() == 1);
|
||||
arr.push_front(2);
|
||||
arr.push_front(3);
|
||||
arr.pop_back();
|
||||
CHECK(int(arr[1]) == 2);
|
||||
CHECK(arr.size() == 2);
|
||||
}
|
||||
|
||||
TEST_CASE("[Array] pop_at()") {
|
||||
ErrorDetector ed;
|
||||
|
||||
Array arr = { 2, 4, 6, 8, 10 };
|
||||
|
||||
REQUIRE(int(arr.pop_at(2)) == 6);
|
||||
REQUIRE(arr.size() == 4);
|
||||
CHECK(int(arr[0]) == 2);
|
||||
CHECK(int(arr[1]) == 4);
|
||||
CHECK(int(arr[2]) == 8);
|
||||
CHECK(int(arr[3]) == 10);
|
||||
|
||||
REQUIRE(int(arr.pop_at(2)) == 8);
|
||||
REQUIRE(arr.size() == 3);
|
||||
CHECK(int(arr[0]) == 2);
|
||||
CHECK(int(arr[1]) == 4);
|
||||
CHECK(int(arr[2]) == 10);
|
||||
|
||||
// Negative index.
|
||||
REQUIRE(int(arr.pop_at(-1)) == 10);
|
||||
REQUIRE(arr.size() == 2);
|
||||
CHECK(int(arr[0]) == 2);
|
||||
CHECK(int(arr[1]) == 4);
|
||||
|
||||
// Invalid pop.
|
||||
ed.clear();
|
||||
ERR_PRINT_OFF;
|
||||
const Variant ret = arr.pop_at(-15);
|
||||
ERR_PRINT_ON;
|
||||
REQUIRE(ret.is_null());
|
||||
CHECK(ed.has_error);
|
||||
|
||||
REQUIRE(int(arr.pop_at(0)) == 2);
|
||||
REQUIRE(arr.size() == 1);
|
||||
CHECK(int(arr[0]) == 4);
|
||||
|
||||
REQUIRE(int(arr.pop_at(0)) == 4);
|
||||
REQUIRE(arr.is_empty());
|
||||
|
||||
// Pop from empty array.
|
||||
ed.clear();
|
||||
REQUIRE(arr.pop_at(24).is_null());
|
||||
CHECK_FALSE(ed.has_error);
|
||||
}
|
||||
|
||||
TEST_CASE("[Array] max() and min()") {
|
||||
Array arr;
|
||||
arr.push_back(3);
|
||||
arr.push_front(4);
|
||||
arr.push_back(5);
|
||||
arr.push_back(2);
|
||||
int max = int(arr.max());
|
||||
int min = int(arr.min());
|
||||
CHECK(max == 5);
|
||||
CHECK(min == 2);
|
||||
}
|
||||
|
||||
TEST_CASE("[Array] slice()") {
|
||||
Array array = { 0, 1, 2, 3, 4, 5 };
|
||||
|
||||
Array slice0 = array.slice(0, 0);
|
||||
CHECK(slice0.size() == 0);
|
||||
|
||||
Array slice1 = array.slice(1, 3);
|
||||
CHECK(slice1.size() == 2);
|
||||
CHECK(slice1[0] == Variant(1));
|
||||
CHECK(slice1[1] == Variant(2));
|
||||
|
||||
Array slice2 = array.slice(1, -1);
|
||||
CHECK(slice2.size() == 4);
|
||||
CHECK(slice2[0] == Variant(1));
|
||||
CHECK(slice2[1] == Variant(2));
|
||||
CHECK(slice2[2] == Variant(3));
|
||||
CHECK(slice2[3] == Variant(4));
|
||||
|
||||
Array slice3 = array.slice(3);
|
||||
CHECK(slice3.size() == 3);
|
||||
CHECK(slice3[0] == Variant(3));
|
||||
CHECK(slice3[1] == Variant(4));
|
||||
CHECK(slice3[2] == Variant(5));
|
||||
|
||||
Array slice4 = array.slice(2, -2);
|
||||
CHECK(slice4.size() == 2);
|
||||
CHECK(slice4[0] == Variant(2));
|
||||
CHECK(slice4[1] == Variant(3));
|
||||
|
||||
Array slice5 = array.slice(-2);
|
||||
CHECK(slice5.size() == 2);
|
||||
CHECK(slice5[0] == Variant(4));
|
||||
CHECK(slice5[1] == Variant(5));
|
||||
|
||||
Array slice6 = array.slice(2, 42);
|
||||
CHECK(slice6.size() == 4);
|
||||
CHECK(slice6[0] == Variant(2));
|
||||
CHECK(slice6[1] == Variant(3));
|
||||
CHECK(slice6[2] == Variant(4));
|
||||
CHECK(slice6[3] == Variant(5));
|
||||
|
||||
Array slice7 = array.slice(4, 0, -2);
|
||||
CHECK(slice7.size() == 2);
|
||||
CHECK(slice7[0] == Variant(4));
|
||||
CHECK(slice7[1] == Variant(2));
|
||||
|
||||
Array slice8 = array.slice(5, 0, -2);
|
||||
CHECK(slice8.size() == 3);
|
||||
CHECK(slice8[0] == Variant(5));
|
||||
CHECK(slice8[1] == Variant(3));
|
||||
CHECK(slice8[2] == Variant(1));
|
||||
|
||||
Array slice9 = array.slice(10, 0, -2);
|
||||
CHECK(slice9.size() == 3);
|
||||
CHECK(slice9[0] == Variant(5));
|
||||
CHECK(slice9[1] == Variant(3));
|
||||
CHECK(slice9[2] == Variant(1));
|
||||
|
||||
Array slice10 = array.slice(2, -10, -1);
|
||||
CHECK(slice10.size() == 3);
|
||||
CHECK(slice10[0] == Variant(2));
|
||||
CHECK(slice10[1] == Variant(1));
|
||||
CHECK(slice10[2] == Variant(0));
|
||||
|
||||
ERR_PRINT_OFF;
|
||||
Array slice11 = array.slice(4, 1);
|
||||
CHECK(slice11.size() == 0);
|
||||
|
||||
Array slice12 = array.slice(3, -4);
|
||||
CHECK(slice12.size() == 0);
|
||||
ERR_PRINT_ON;
|
||||
|
||||
Array slice13 = Array().slice(1);
|
||||
CHECK(slice13.size() == 0);
|
||||
|
||||
Array slice14 = array.slice(6);
|
||||
CHECK(slice14.size() == 0);
|
||||
}
|
||||
|
||||
TEST_CASE("[Array] Duplicate array") {
|
||||
// a = [1, [2, 2], {3: 3}]
|
||||
Array a = { 1, { 2, 2 }, Dictionary({ { 3, 3 } }) };
|
||||
|
||||
// Deep copy
|
||||
Array deep_a = a.duplicate(true);
|
||||
CHECK_MESSAGE(deep_a.id() != a.id(), "Should create a new array");
|
||||
CHECK_MESSAGE(Array(deep_a[1]).id() != Array(a[1]).id(), "Should clone nested array");
|
||||
CHECK_MESSAGE(Dictionary(deep_a[2]).id() != Dictionary(a[2]).id(), "Should clone nested dictionary");
|
||||
CHECK_EQ(deep_a, a);
|
||||
deep_a.push_back(1);
|
||||
CHECK_NE(deep_a, a);
|
||||
deep_a.pop_back();
|
||||
Array(deep_a[1]).push_back(1);
|
||||
CHECK_NE(deep_a, a);
|
||||
Array(deep_a[1]).pop_back();
|
||||
CHECK_EQ(deep_a, a);
|
||||
|
||||
// Shallow copy
|
||||
Array shallow_a = a.duplicate(false);
|
||||
CHECK_MESSAGE(shallow_a.id() != a.id(), "Should create a new array");
|
||||
CHECK_MESSAGE(Array(shallow_a[1]).id() == Array(a[1]).id(), "Should keep nested array");
|
||||
CHECK_MESSAGE(Dictionary(shallow_a[2]).id() == Dictionary(a[2]).id(), "Should keep nested dictionary");
|
||||
CHECK_EQ(shallow_a, a);
|
||||
Array(shallow_a).push_back(1);
|
||||
CHECK_NE(shallow_a, a);
|
||||
}
|
||||
|
||||
TEST_CASE("[Array] Duplicate recursive array") {
|
||||
// Self recursive
|
||||
Array a;
|
||||
a.push_back(a);
|
||||
|
||||
Array a_shallow = a.duplicate(false);
|
||||
CHECK_EQ(a, a_shallow);
|
||||
|
||||
// Deep copy of recursive array ends up with recursion limit and return
|
||||
// an invalid result (multiple nested arrays), the point is we should
|
||||
// not end up with a segfault and an error log should be printed
|
||||
ERR_PRINT_OFF;
|
||||
a.duplicate(true);
|
||||
ERR_PRINT_ON;
|
||||
|
||||
// Nested recursive
|
||||
Array a1;
|
||||
Array a2;
|
||||
a2.push_back(a1);
|
||||
a1.push_back(a2);
|
||||
|
||||
Array a1_shallow = a1.duplicate(false);
|
||||
CHECK_EQ(a1, a1_shallow);
|
||||
|
||||
// Same deep copy issue as above
|
||||
ERR_PRINT_OFF;
|
||||
a1.duplicate(true);
|
||||
ERR_PRINT_ON;
|
||||
|
||||
// Break the recursivity otherwise Array teardown will leak memory
|
||||
a.clear();
|
||||
a1.clear();
|
||||
a2.clear();
|
||||
}
|
||||
|
||||
TEST_CASE("[Array] Hash array") {
|
||||
// a = [1, [2, 2], {3: 3}]
|
||||
Array a = { 1, { 2, 2 }, Dictionary({ { 3, 3 } }) };
|
||||
uint32_t original_hash = a.hash();
|
||||
|
||||
a.push_back(1);
|
||||
CHECK_NE(a.hash(), original_hash);
|
||||
|
||||
a.pop_back();
|
||||
CHECK_EQ(a.hash(), original_hash);
|
||||
|
||||
Array(a[1]).push_back(1);
|
||||
CHECK_NE(a.hash(), original_hash);
|
||||
Array(a[1]).pop_back();
|
||||
CHECK_EQ(a.hash(), original_hash);
|
||||
|
||||
(Dictionary(a[2]))[1] = 1;
|
||||
CHECK_NE(a.hash(), original_hash);
|
||||
Dictionary(a[2]).erase(1);
|
||||
CHECK_EQ(a.hash(), original_hash);
|
||||
|
||||
Array a2 = a.duplicate(true);
|
||||
CHECK_EQ(a2.hash(), a.hash());
|
||||
}
|
||||
|
||||
TEST_CASE("[Array] Hash recursive array") {
|
||||
Array a1;
|
||||
a1.push_back(a1);
|
||||
|
||||
Array a2;
|
||||
a2.push_back(a2);
|
||||
|
||||
// Hash should reach recursion limit
|
||||
ERR_PRINT_OFF;
|
||||
CHECK_EQ(a1.hash(), a2.hash());
|
||||
ERR_PRINT_ON;
|
||||
|
||||
// Break the recursivity otherwise Array teardown will leak memory
|
||||
a1.clear();
|
||||
a2.clear();
|
||||
}
|
||||
|
||||
TEST_CASE("[Array] Empty comparison") {
|
||||
Array a1;
|
||||
Array a2;
|
||||
|
||||
// test both operator== and operator!=
|
||||
CHECK_EQ(a1, a2);
|
||||
CHECK_FALSE(a1 != a2);
|
||||
}
|
||||
|
||||
TEST_CASE("[Array] Flat comparison") {
|
||||
Array a1 = { 1 };
|
||||
Array a2 = { 1 };
|
||||
Array other_a = { 2 };
|
||||
|
||||
// test both operator== and operator!=
|
||||
CHECK_EQ(a1, a1); // compare self
|
||||
CHECK_FALSE(a1 != a1);
|
||||
CHECK_EQ(a1, a2); // different equivalent arrays
|
||||
CHECK_FALSE(a1 != a2);
|
||||
CHECK_NE(a1, other_a); // different arrays with different content
|
||||
CHECK_FALSE(a1 == other_a);
|
||||
}
|
||||
|
||||
TEST_CASE("[Array] Nested array comparison") {
|
||||
// a1 = [[[1], 2], 3]
|
||||
Array a1 = { { { 1 }, 2 }, 3 };
|
||||
|
||||
Array a2 = a1.duplicate(true);
|
||||
|
||||
// other_a = [[[1, 0], 2], 3]
|
||||
Array other_a = { { { 1, 0 }, 2 }, 3 };
|
||||
|
||||
// test both operator== and operator!=
|
||||
CHECK_EQ(a1, a1); // compare self
|
||||
CHECK_FALSE(a1 != a1);
|
||||
CHECK_EQ(a1, a2); // different equivalent arrays
|
||||
CHECK_FALSE(a1 != a2);
|
||||
CHECK_NE(a1, other_a); // different arrays with different content
|
||||
CHECK_FALSE(a1 == other_a);
|
||||
}
|
||||
|
||||
TEST_CASE("[Array] Nested dictionary comparison") {
|
||||
// a1 = [{1: 2}, 3]
|
||||
Array a1 = { Dictionary({ { 1, 2 } }), 3 };
|
||||
|
||||
Array a2 = a1.duplicate(true);
|
||||
|
||||
// other_a = [{1: 0}, 3]
|
||||
Array other_a = { Dictionary({ { 1, 0 } }), 3 };
|
||||
|
||||
// test both operator== and operator!=
|
||||
CHECK_EQ(a1, a1); // compare self
|
||||
CHECK_FALSE(a1 != a1);
|
||||
CHECK_EQ(a1, a2); // different equivalent arrays
|
||||
CHECK_FALSE(a1 != a2);
|
||||
CHECK_NE(a1, other_a); // different arrays with different content
|
||||
CHECK_FALSE(a1 == other_a);
|
||||
}
|
||||
|
||||
TEST_CASE("[Array] Recursive comparison") {
|
||||
Array a1;
|
||||
a1.push_back(a1);
|
||||
|
||||
Array a2;
|
||||
a2.push_back(a2);
|
||||
|
||||
// Comparison should reach recursion limit
|
||||
ERR_PRINT_OFF;
|
||||
CHECK_EQ(a1, a2);
|
||||
CHECK_FALSE(a1 != a2);
|
||||
ERR_PRINT_ON;
|
||||
|
||||
a1.push_back(1);
|
||||
a2.push_back(1);
|
||||
|
||||
// Comparison should reach recursion limit
|
||||
ERR_PRINT_OFF;
|
||||
CHECK_EQ(a1, a2);
|
||||
CHECK_FALSE(a1 != a2);
|
||||
ERR_PRINT_ON;
|
||||
|
||||
a1.push_back(1);
|
||||
a2.push_back(2);
|
||||
|
||||
// Comparison should reach recursion limit
|
||||
ERR_PRINT_OFF;
|
||||
CHECK_NE(a1, a2);
|
||||
CHECK_FALSE(a1 == a2);
|
||||
ERR_PRINT_ON;
|
||||
|
||||
// Break the recursivity otherwise Array tearndown will leak memory
|
||||
a1.clear();
|
||||
a2.clear();
|
||||
}
|
||||
|
||||
TEST_CASE("[Array] Recursive self comparison") {
|
||||
Array a1;
|
||||
Array a2;
|
||||
a2.push_back(a1);
|
||||
a1.push_back(a2);
|
||||
|
||||
CHECK_EQ(a1, a1);
|
||||
CHECK_FALSE(a1 != a1);
|
||||
|
||||
// Break the recursivity otherwise Array tearndown will leak memory
|
||||
a1.clear();
|
||||
a2.clear();
|
||||
}
|
||||
|
||||
TEST_CASE("[Array] Iteration") {
|
||||
Array a1 = { 1, 2, 3 };
|
||||
Array a2 = { 1, 2, 3 };
|
||||
|
||||
int idx = 0;
|
||||
for (Variant &E : a1) {
|
||||
CHECK_EQ(int(a2[idx]), int(E));
|
||||
idx++;
|
||||
}
|
||||
|
||||
CHECK_EQ(idx, a1.size());
|
||||
|
||||
idx = 0;
|
||||
|
||||
for (const Variant &E : (const Array &)a1) {
|
||||
CHECK_EQ(int(a2[idx]), int(E));
|
||||
idx++;
|
||||
}
|
||||
|
||||
CHECK_EQ(idx, a1.size());
|
||||
|
||||
a1.clear();
|
||||
}
|
||||
|
||||
TEST_CASE("[Array] Iteration and modification") {
|
||||
Array a1 = { 1, 2, 3 };
|
||||
Array a2 = { 2, 3, 4 };
|
||||
Array a3 = { 1, 2, 3 };
|
||||
Array a4 = { 1, 2, 3 };
|
||||
a3.make_read_only();
|
||||
|
||||
int idx = 0;
|
||||
for (Variant &E : a1) {
|
||||
E = a2[idx];
|
||||
idx++;
|
||||
}
|
||||
|
||||
CHECK_EQ(a1, a2);
|
||||
|
||||
// Ensure read-only is respected.
|
||||
idx = 0;
|
||||
for (Variant &E : a3) {
|
||||
E = a2[idx];
|
||||
}
|
||||
|
||||
CHECK_EQ(a3, a4);
|
||||
|
||||
a1.clear();
|
||||
a2.clear();
|
||||
a4.clear();
|
||||
}
|
||||
|
||||
TEST_CASE("[Array] Typed copying") {
|
||||
TypedArray<int> a1 = { 1 };
|
||||
TypedArray<double> a2 = { 1.0 };
|
||||
|
||||
Array a3 = a1;
|
||||
TypedArray<int> a4 = a3;
|
||||
|
||||
Array a5 = a2;
|
||||
TypedArray<int> a6 = a5;
|
||||
|
||||
a3[0] = 2;
|
||||
a4[0] = 3;
|
||||
|
||||
// Same typed TypedArray should be shared.
|
||||
CHECK_EQ(a1[0], Variant(3));
|
||||
CHECK_EQ(a3[0], Variant(3));
|
||||
CHECK_EQ(a4[0], Variant(3));
|
||||
|
||||
a5[0] = 2.0;
|
||||
a6[0] = 3.0;
|
||||
|
||||
// Different typed TypedArray should not be shared.
|
||||
CHECK_EQ(a2[0], Variant(2.0));
|
||||
CHECK_EQ(a5[0], Variant(2.0));
|
||||
CHECK_EQ(a6[0], Variant(3.0));
|
||||
|
||||
a1.clear();
|
||||
a2.clear();
|
||||
a3.clear();
|
||||
a4.clear();
|
||||
a5.clear();
|
||||
a6.clear();
|
||||
}
|
||||
|
||||
static bool _find_custom_callable(const Variant &p_val) {
|
||||
return (int)p_val % 2 == 0;
|
||||
}
|
||||
|
||||
TEST_CASE("[Array] Test find_custom") {
|
||||
Array a1 = { 1, 3, 4, 5, 8, 9 };
|
||||
// Find first even number.
|
||||
int index = a1.find_custom(callable_mp_static(_find_custom_callable));
|
||||
CHECK_EQ(index, 2);
|
||||
}
|
||||
|
||||
TEST_CASE("[Array] Test rfind_custom") {
|
||||
Array a1 = { 1, 3, 4, 5, 8, 9 };
|
||||
// Find last even number.
|
||||
int index = a1.rfind_custom(callable_mp_static(_find_custom_callable));
|
||||
CHECK_EQ(index, 4);
|
||||
}
|
||||
|
||||
} // namespace TestArray
|
||||
197
tests/core/variant/test_callable.h
Normal file
@@ -0,0 +1,197 @@
|
||||
/**************************************************************************/
|
||||
/* test_callable.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/object/class_db.h"
|
||||
#include "core/object/object.h"
|
||||
|
||||
#include "tests/test_macros.h"
|
||||
|
||||
namespace TestCallable {
|
||||
|
||||
class TestClass : public Object {
|
||||
GDCLASS(TestClass, Object);
|
||||
|
||||
protected:
|
||||
static void _bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("test_func_1", "foo", "bar"), &TestClass::test_func_1);
|
||||
ClassDB::bind_method(D_METHOD("test_func_2", "foo", "bar", "baz"), &TestClass::test_func_2);
|
||||
ClassDB::bind_static_method("TestClass", D_METHOD("test_func_5", "foo", "bar"), &TestClass::test_func_5);
|
||||
ClassDB::bind_static_method("TestClass", D_METHOD("test_func_6", "foo", "bar", "baz"), &TestClass::test_func_6);
|
||||
|
||||
{
|
||||
MethodInfo mi;
|
||||
mi.name = "test_func_7";
|
||||
mi.arguments.push_back(PropertyInfo(Variant::INT, "foo"));
|
||||
mi.arguments.push_back(PropertyInfo(Variant::INT, "bar"));
|
||||
|
||||
ClassDB::bind_vararg_method(METHOD_FLAGS_DEFAULT, "test_func_7", &TestClass::test_func_7, mi, varray(), false);
|
||||
}
|
||||
|
||||
{
|
||||
MethodInfo mi;
|
||||
mi.name = "test_func_8";
|
||||
mi.arguments.push_back(PropertyInfo(Variant::INT, "foo"));
|
||||
mi.arguments.push_back(PropertyInfo(Variant::INT, "bar"));
|
||||
mi.arguments.push_back(PropertyInfo(Variant::INT, "baz"));
|
||||
|
||||
ClassDB::bind_vararg_method(METHOD_FLAGS_DEFAULT, "test_func_8", &TestClass::test_func_8, mi, varray(), false);
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
void test_func_1(int p_foo, int p_bar) {}
|
||||
void test_func_2(int p_foo, int p_bar, int p_baz) {}
|
||||
|
||||
int test_func_3(int p_foo, int p_bar) const { return 0; }
|
||||
int test_func_4(int p_foo, int p_bar, int p_baz) const { return 0; }
|
||||
|
||||
static void test_func_5(int p_foo, int p_bar) {}
|
||||
static void test_func_6(int p_foo, int p_bar, int p_baz) {}
|
||||
|
||||
void test_func_7(const Variant **p_args, int p_argcount, Callable::CallError &r_error) {}
|
||||
void test_func_8(const Variant **p_args, int p_argcount, Callable::CallError &r_error) {}
|
||||
};
|
||||
|
||||
TEST_CASE("[Callable] Argument count") {
|
||||
TestClass *my_test = memnew(TestClass);
|
||||
|
||||
// Bound methods tests.
|
||||
|
||||
// Test simple methods.
|
||||
Callable callable_1 = Callable(my_test, "test_func_1");
|
||||
CHECK_EQ(callable_1.get_argument_count(), 2);
|
||||
Callable callable_2 = Callable(my_test, "test_func_2");
|
||||
CHECK_EQ(callable_2.get_argument_count(), 3);
|
||||
Callable callable_3 = Callable(my_test, "test_func_5");
|
||||
CHECK_EQ(callable_3.get_argument_count(), 2);
|
||||
Callable callable_4 = Callable(my_test, "test_func_6");
|
||||
CHECK_EQ(callable_4.get_argument_count(), 3);
|
||||
|
||||
// Test vararg methods.
|
||||
Callable callable_vararg_1 = Callable(my_test, "test_func_7");
|
||||
CHECK_MESSAGE(callable_vararg_1.get_argument_count() == 2, "vararg Callable should return the number of declared arguments");
|
||||
Callable callable_vararg_2 = Callable(my_test, "test_func_8");
|
||||
CHECK_MESSAGE(callable_vararg_2.get_argument_count() == 3, "vararg Callable should return the number of declared arguments");
|
||||
|
||||
// Callable MP tests.
|
||||
|
||||
// Test simple methods.
|
||||
Callable callable_mp_1 = callable_mp(my_test, &TestClass::test_func_1);
|
||||
CHECK_EQ(callable_mp_1.get_argument_count(), 2);
|
||||
Callable callable_mp_2 = callable_mp(my_test, &TestClass::test_func_2);
|
||||
CHECK_EQ(callable_mp_2.get_argument_count(), 3);
|
||||
Callable callable_mp_3 = callable_mp(my_test, &TestClass::test_func_3);
|
||||
CHECK_EQ(callable_mp_3.get_argument_count(), 2);
|
||||
Callable callable_mp_4 = callable_mp(my_test, &TestClass::test_func_4);
|
||||
CHECK_EQ(callable_mp_4.get_argument_count(), 3);
|
||||
|
||||
// Test static methods.
|
||||
Callable callable_mp_static_1 = callable_mp_static(&TestClass::test_func_5);
|
||||
CHECK_EQ(callable_mp_static_1.get_argument_count(), 2);
|
||||
Callable callable_mp_static_2 = callable_mp_static(&TestClass::test_func_6);
|
||||
CHECK_EQ(callable_mp_static_2.get_argument_count(), 3);
|
||||
|
||||
// Test bind.
|
||||
Callable callable_mp_bind_1 = callable_mp_2.bind(1);
|
||||
CHECK_MESSAGE(callable_mp_bind_1.get_argument_count() == 2, "bind should subtract from the argument count");
|
||||
Callable callable_mp_bind_2 = callable_mp_2.bind(1, 2);
|
||||
CHECK_MESSAGE(callable_mp_bind_2.get_argument_count() == 1, "bind should subtract from the argument count");
|
||||
|
||||
// Test unbind.
|
||||
Callable callable_mp_unbind_1 = callable_mp_2.unbind(1);
|
||||
CHECK_MESSAGE(callable_mp_unbind_1.get_argument_count() == 4, "unbind should add to the argument count");
|
||||
Callable callable_mp_unbind_2 = callable_mp_2.unbind(2);
|
||||
CHECK_MESSAGE(callable_mp_unbind_2.get_argument_count() == 5, "unbind should add to the argument count");
|
||||
|
||||
memdelete(my_test);
|
||||
}
|
||||
|
||||
class TestBoundUnboundArgumentCount : public Object {
|
||||
GDCLASS(TestBoundUnboundArgumentCount, Object);
|
||||
|
||||
protected:
|
||||
static void _bind_methods() {
|
||||
ClassDB::bind_vararg_method(METHOD_FLAGS_DEFAULT, "test_func", &TestBoundUnboundArgumentCount::test_func, MethodInfo("test_func"));
|
||||
}
|
||||
|
||||
public:
|
||||
Variant test_func(const Variant **p_args, int p_argcount, Callable::CallError &r_error) {
|
||||
Array result;
|
||||
result.resize(p_argcount);
|
||||
for (int i = 0; i < p_argcount; i++) {
|
||||
result[i] = *p_args[i];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static String get_output(const Callable &p_callable) {
|
||||
Array effective_args = { 7, 8, 9 };
|
||||
effective_args.resize(3 - p_callable.get_unbound_arguments_count());
|
||||
effective_args.append_array(p_callable.get_bound_arguments());
|
||||
|
||||
return vformat(
|
||||
"%d %d %s %s %s",
|
||||
p_callable.get_unbound_arguments_count(),
|
||||
p_callable.get_bound_arguments_count(),
|
||||
p_callable.get_bound_arguments(),
|
||||
p_callable.call(7, 8, 9),
|
||||
effective_args);
|
||||
}
|
||||
};
|
||||
|
||||
TEST_CASE("[Callable] Bound and unbound argument count") {
|
||||
String (*get_output)(const Callable &) = TestBoundUnboundArgumentCount::get_output;
|
||||
|
||||
TestBoundUnboundArgumentCount *test_instance = memnew(TestBoundUnboundArgumentCount);
|
||||
|
||||
Callable test_func = Callable(test_instance, "test_func");
|
||||
|
||||
CHECK(get_output(test_func) == "0 0 [] [7, 8, 9] [7, 8, 9]");
|
||||
CHECK(get_output(test_func.bind(1, 2)) == "0 2 [1, 2] [7, 8, 9, 1, 2] [7, 8, 9, 1, 2]");
|
||||
CHECK(get_output(test_func.bind(1, 2).unbind(1)) == "1 2 [1, 2] [7, 8, 1, 2] [7, 8, 1, 2]");
|
||||
CHECK(get_output(test_func.bind(1, 2).unbind(1).bind(3, 4)) == "0 3 [3, 1, 2] [7, 8, 9, 3, 1, 2] [7, 8, 9, 3, 1, 2]");
|
||||
CHECK(get_output(test_func.bind(1, 2).unbind(1).bind(3, 4).unbind(1)) == "1 3 [3, 1, 2] [7, 8, 3, 1, 2] [7, 8, 3, 1, 2]");
|
||||
|
||||
CHECK(get_output(test_func.bind(1).bind(2).bind(3).unbind(1)) == "1 3 [3, 2, 1] [7, 8, 3, 2, 1] [7, 8, 3, 2, 1]");
|
||||
CHECK(get_output(test_func.bind(1).bind(2).unbind(1).bind(3)) == "0 2 [2, 1] [7, 8, 9, 2, 1] [7, 8, 9, 2, 1]");
|
||||
CHECK(get_output(test_func.bind(1).unbind(1).bind(2).bind(3)) == "0 2 [3, 1] [7, 8, 9, 3, 1] [7, 8, 9, 3, 1]");
|
||||
CHECK(get_output(test_func.unbind(1).bind(1).bind(2).bind(3)) == "0 2 [3, 2] [7, 8, 9, 3, 2] [7, 8, 9, 3, 2]");
|
||||
|
||||
CHECK(get_output(test_func.unbind(1).unbind(1).unbind(1).bind(1, 2, 3)) == "0 0 [] [7, 8, 9] [7, 8, 9]");
|
||||
CHECK(get_output(test_func.unbind(1).unbind(1).bind(1, 2, 3).unbind(1)) == "1 1 [1] [7, 8, 1] [7, 8, 1]");
|
||||
CHECK(get_output(test_func.unbind(1).bind(1, 2, 3).unbind(1).unbind(1)) == "2 2 [1, 2] [7, 1, 2] [7, 1, 2]");
|
||||
CHECK(get_output(test_func.bind(1, 2, 3).unbind(1).unbind(1).unbind(1)) == "3 3 [1, 2, 3] [1, 2, 3] [1, 2, 3]");
|
||||
|
||||
memdelete(test_instance);
|
||||
}
|
||||
|
||||
} // namespace TestCallable
|
||||
620
tests/core/variant/test_dictionary.h
Normal file
@@ -0,0 +1,620 @@
|
||||
/**************************************************************************/
|
||||
/* test_dictionary.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/variant/typed_dictionary.h"
|
||||
#include "tests/test_macros.h"
|
||||
|
||||
namespace TestDictionary {
|
||||
TEST_CASE("[Dictionary] Assignment using bracket notation ([])") {
|
||||
Dictionary map;
|
||||
map["Hello"] = 0;
|
||||
CHECK(int(map["Hello"]) == 0);
|
||||
map["Hello"] = 3;
|
||||
CHECK(int(map["Hello"]) == 3);
|
||||
map["World!"] = 4;
|
||||
CHECK(int(map["World!"]) == 4);
|
||||
|
||||
map[StringName("HelloName")] = 6;
|
||||
CHECK(int(map[StringName("HelloName")]) == 6);
|
||||
CHECK(int(map.find_key(6).get_type()) == Variant::STRING_NAME);
|
||||
map[StringName("HelloName")] = 7;
|
||||
CHECK(int(map[StringName("HelloName")]) == 7);
|
||||
|
||||
// Test String and StringName are equivalent.
|
||||
map[StringName("Hello")] = 8;
|
||||
CHECK(int(map["Hello"]) == 8);
|
||||
map["Hello"] = 9;
|
||||
CHECK(int(map[StringName("Hello")]) == 9);
|
||||
|
||||
// Test non-string keys, since keys can be of any Variant type.
|
||||
map[12345] = -5;
|
||||
CHECK(int(map[12345]) == -5);
|
||||
map[false] = 128;
|
||||
CHECK(int(map[false]) == 128);
|
||||
map[Vector2(10, 20)] = 30;
|
||||
CHECK(int(map[Vector2(10, 20)]) == 30);
|
||||
map[0] = 400;
|
||||
CHECK(int(map[0]) == 400);
|
||||
// Check that assigning 0 doesn't overwrite the value for `false`.
|
||||
CHECK(int(map[false]) == 128);
|
||||
|
||||
// Ensure read-only maps aren't modified by non-existing keys.
|
||||
const int length = map.size();
|
||||
map.make_read_only();
|
||||
CHECK(int(map["This key does not exist"].get_type()) == Variant::NIL);
|
||||
CHECK(map.size() == length);
|
||||
}
|
||||
|
||||
TEST_CASE("[Dictionary] List init") {
|
||||
Dictionary dict{
|
||||
{ 0, "int" },
|
||||
{ "packed_string_array", PackedStringArray({ "array", "of", "values" }) },
|
||||
{ "key", Dictionary({ { "nested", 200 } }) },
|
||||
{ Vector2(), "v2" },
|
||||
};
|
||||
CHECK(dict.size() == 4);
|
||||
CHECK(dict[0] == "int");
|
||||
CHECK(PackedStringArray(dict["packed_string_array"])[2] == "values");
|
||||
CHECK(Dictionary(dict["key"])["nested"] == Variant(200));
|
||||
CHECK(dict[Vector2()] == "v2");
|
||||
|
||||
TypedDictionary<double, double> tdict{
|
||||
{ 0.0, 1.0 },
|
||||
{ 5.0, 2.0 },
|
||||
};
|
||||
CHECK_EQ(tdict[0.0], Variant(1.0));
|
||||
CHECK_EQ(tdict[5.0], Variant(2.0));
|
||||
}
|
||||
|
||||
TEST_CASE("[Dictionary] get_key_list()") {
|
||||
Dictionary map;
|
||||
LocalVector<Variant> keys;
|
||||
keys = map.get_key_list();
|
||||
CHECK(keys.is_empty());
|
||||
map[1] = 3;
|
||||
keys = map.get_key_list();
|
||||
CHECK(keys.size() == 1);
|
||||
CHECK(int(keys[0]) == 1);
|
||||
map[2] = 4;
|
||||
keys = map.get_key_list();
|
||||
CHECK(keys.size() == 2);
|
||||
}
|
||||
|
||||
TEST_CASE("[Dictionary] get_key_at_index()") {
|
||||
Dictionary map;
|
||||
map[4] = 3;
|
||||
Variant val = map.get_key_at_index(0);
|
||||
CHECK(int(val) == 4);
|
||||
map[3] = 1;
|
||||
val = map.get_key_at_index(0);
|
||||
CHECK(int(val) == 4);
|
||||
val = map.get_key_at_index(1);
|
||||
CHECK(int(val) == 3);
|
||||
}
|
||||
|
||||
TEST_CASE("[Dictionary] getptr()") {
|
||||
Dictionary map;
|
||||
map[1] = 3;
|
||||
Variant *key = map.getptr(1);
|
||||
CHECK(int(*key) == 3);
|
||||
key = map.getptr(2);
|
||||
CHECK(key == nullptr);
|
||||
}
|
||||
|
||||
TEST_CASE("[Dictionary] get_valid()") {
|
||||
Dictionary map;
|
||||
map[1] = 3;
|
||||
Variant val = map.get_valid(1);
|
||||
CHECK(int(val) == 3);
|
||||
}
|
||||
TEST_CASE("[Dictionary] get()") {
|
||||
Dictionary map;
|
||||
map[1] = 3;
|
||||
Variant val = map.get(1, -1);
|
||||
CHECK(int(val) == 3);
|
||||
}
|
||||
|
||||
TEST_CASE("[Dictionary] size(), empty() and clear()") {
|
||||
Dictionary map;
|
||||
CHECK(map.size() == 0);
|
||||
CHECK(map.is_empty());
|
||||
map[1] = 3;
|
||||
CHECK(map.size() == 1);
|
||||
CHECK(!map.is_empty());
|
||||
map.clear();
|
||||
CHECK(map.size() == 0);
|
||||
CHECK(map.is_empty());
|
||||
}
|
||||
|
||||
TEST_CASE("[Dictionary] has() and has_all()") {
|
||||
Dictionary map;
|
||||
CHECK(map.has(1) == false);
|
||||
map[1] = 3;
|
||||
CHECK(map.has(1));
|
||||
Array keys;
|
||||
keys.push_back(1);
|
||||
CHECK(map.has_all(keys));
|
||||
keys.push_back(2);
|
||||
CHECK(map.has_all(keys) == false);
|
||||
}
|
||||
|
||||
TEST_CASE("[Dictionary] keys() and values()") {
|
||||
Dictionary map;
|
||||
Array keys = map.keys();
|
||||
Array values = map.values();
|
||||
CHECK(keys.is_empty());
|
||||
CHECK(values.is_empty());
|
||||
map[1] = 3;
|
||||
keys = map.keys();
|
||||
values = map.values();
|
||||
CHECK(int(keys[0]) == 1);
|
||||
CHECK(int(values[0]) == 3);
|
||||
}
|
||||
|
||||
TEST_CASE("[Dictionary] Duplicate dictionary") {
|
||||
// d = {1: {1: 1}, {2: 2}: [2], [3]: 3}
|
||||
Dictionary k2 = { { 2, 2 } };
|
||||
Array k3 = { 3 };
|
||||
Dictionary d = {
|
||||
{ 1, Dictionary({ { 1, 1 } }) },
|
||||
{ k2, Array({ 2 }) },
|
||||
{ k3, 3 }
|
||||
};
|
||||
|
||||
// Deep copy
|
||||
Dictionary deep_d = d.duplicate(true);
|
||||
CHECK_MESSAGE(deep_d.id() != d.id(), "Should create a new dictionary");
|
||||
CHECK_MESSAGE(Dictionary(deep_d[1]).id() != Dictionary(d[1]).id(), "Should clone nested dictionary");
|
||||
CHECK_MESSAGE(Array(deep_d[k2]).id() != Array(d[k2]).id(), "Should clone nested array");
|
||||
CHECK_EQ(deep_d, d);
|
||||
deep_d[0] = 0;
|
||||
CHECK_NE(deep_d, d);
|
||||
deep_d.erase(0);
|
||||
Dictionary(deep_d[1]).operator[](0) = 0;
|
||||
CHECK_NE(deep_d, d);
|
||||
Dictionary(deep_d[1]).erase(0);
|
||||
CHECK_EQ(deep_d, d);
|
||||
// Keys should also be copied
|
||||
k2[0] = 0;
|
||||
CHECK_NE(deep_d, d);
|
||||
k2.erase(0);
|
||||
CHECK_EQ(deep_d, d);
|
||||
k3.push_back(0);
|
||||
CHECK_NE(deep_d, d);
|
||||
k3.pop_back();
|
||||
CHECK_EQ(deep_d, d);
|
||||
|
||||
// Shallow copy
|
||||
Dictionary shallow_d = d.duplicate(false);
|
||||
CHECK_MESSAGE(shallow_d.id() != d.id(), "Should create a new array");
|
||||
CHECK_MESSAGE(Dictionary(shallow_d[1]).id() == Dictionary(d[1]).id(), "Should keep nested dictionary");
|
||||
CHECK_MESSAGE(Array(shallow_d[k2]).id() == Array(d[k2]).id(), "Should keep nested array");
|
||||
CHECK_EQ(shallow_d, d);
|
||||
shallow_d[0] = 0;
|
||||
CHECK_NE(shallow_d, d);
|
||||
shallow_d.erase(0);
|
||||
#if 0 // TODO: recursion in dict key currently is buggy
|
||||
// Keys should also be shallowed
|
||||
k2[0] = 0;
|
||||
CHECK_EQ(shallow_d, d);
|
||||
k2.erase(0);
|
||||
k3.push_back(0);
|
||||
CHECK_EQ(shallow_d, d);
|
||||
#endif
|
||||
}
|
||||
|
||||
TEST_CASE("[Dictionary] Duplicate recursive dictionary") {
|
||||
// Self recursive
|
||||
Dictionary d;
|
||||
d[1] = d;
|
||||
|
||||
Dictionary d_shallow = d.duplicate(false);
|
||||
CHECK_EQ(d, d_shallow);
|
||||
|
||||
// Deep copy of recursive dictionary endup with recursion limit and return
|
||||
// an invalid result (multiple nested dictionaries), the point is we should
|
||||
// not end up with a segfault and an error log should be printed
|
||||
ERR_PRINT_OFF;
|
||||
d.duplicate(true);
|
||||
ERR_PRINT_ON;
|
||||
|
||||
// Nested recursive
|
||||
Dictionary d1;
|
||||
Dictionary d2;
|
||||
d1[2] = d2;
|
||||
d2[1] = d1;
|
||||
|
||||
Dictionary d1_shallow = d1.duplicate(false);
|
||||
CHECK_EQ(d1, d1_shallow);
|
||||
|
||||
// Same deep copy issue as above
|
||||
ERR_PRINT_OFF;
|
||||
d1.duplicate(true);
|
||||
ERR_PRINT_ON;
|
||||
|
||||
// Break the recursivity otherwise Dictionary teardown will leak memory
|
||||
d.clear();
|
||||
d1.clear();
|
||||
d2.clear();
|
||||
}
|
||||
|
||||
#if 0 // TODO: duplicate recursion in dict key is currently buggy
|
||||
TEST_CASE("[Dictionary] Duplicate recursive dictionary on keys") {
|
||||
// Self recursive
|
||||
Dictionary d;
|
||||
d[d] = d;
|
||||
|
||||
Dictionary d_shallow = d.duplicate(false);
|
||||
CHECK_EQ(d, d_shallow);
|
||||
|
||||
// Deep copy of recursive dictionary endup with recursion limit and return
|
||||
// an invalid result (multiple nested dictionaries), the point is we should
|
||||
// not end up with a segfault and an error log should be printed
|
||||
ERR_PRINT_OFF;
|
||||
d.duplicate(true);
|
||||
ERR_PRINT_ON;
|
||||
|
||||
// Nested recursive
|
||||
Dictionary d1;
|
||||
Dictionary d2;
|
||||
d1[d2] = d2;
|
||||
d2[d1] = d1;
|
||||
|
||||
Dictionary d1_shallow = d1.duplicate(false);
|
||||
CHECK_EQ(d1, d1_shallow);
|
||||
|
||||
// Same deep copy issue as above
|
||||
ERR_PRINT_OFF;
|
||||
d1.duplicate(true);
|
||||
ERR_PRINT_ON;
|
||||
|
||||
// Break the recursivity otherwise Dictionary teardown will leak memory
|
||||
d.clear();
|
||||
d1.clear();
|
||||
d2.clear();
|
||||
}
|
||||
#endif
|
||||
|
||||
TEST_CASE("[Dictionary] Hash dictionary") {
|
||||
// d = {1: {1: 1}, {2: 2}: [2], [3]: 3}
|
||||
Dictionary k2 = { { 2, 2 } };
|
||||
Array k3 = { 3 };
|
||||
Dictionary d = {
|
||||
{ 1, Dictionary({ { 1, 1 } }) },
|
||||
{ k2, Array({ 2 }) },
|
||||
{ k3, 3 }
|
||||
};
|
||||
uint32_t original_hash = d.hash();
|
||||
|
||||
// Modify dict change the hash
|
||||
d[0] = 0;
|
||||
CHECK_NE(d.hash(), original_hash);
|
||||
d.erase(0);
|
||||
CHECK_EQ(d.hash(), original_hash);
|
||||
|
||||
// Modify nested item change the hash
|
||||
Dictionary(d[1]).operator[](0) = 0;
|
||||
CHECK_NE(d.hash(), original_hash);
|
||||
Dictionary(d[1]).erase(0);
|
||||
Array(d[k2]).push_back(0);
|
||||
CHECK_NE(d.hash(), original_hash);
|
||||
Array(d[k2]).pop_back();
|
||||
|
||||
// Modify a key change the hash
|
||||
k2[0] = 0;
|
||||
CHECK_NE(d.hash(), original_hash);
|
||||
k2.erase(0);
|
||||
CHECK_EQ(d.hash(), original_hash);
|
||||
k3.push_back(0);
|
||||
CHECK_NE(d.hash(), original_hash);
|
||||
k3.pop_back();
|
||||
CHECK_EQ(d.hash(), original_hash);
|
||||
|
||||
// Duplication doesn't change the hash
|
||||
Dictionary d2 = d.duplicate(true);
|
||||
CHECK_EQ(d2.hash(), original_hash);
|
||||
}
|
||||
|
||||
TEST_CASE("[Dictionary] Hash recursive dictionary") {
|
||||
Dictionary d;
|
||||
d[1] = d;
|
||||
|
||||
// Hash should reach recursion limit, we just make sure this doesn't blow up
|
||||
ERR_PRINT_OFF;
|
||||
d.hash();
|
||||
ERR_PRINT_ON;
|
||||
|
||||
// Break the recursivity otherwise Dictionary teardown will leak memory
|
||||
d.clear();
|
||||
}
|
||||
|
||||
#if 0 // TODO: recursion in dict key is currently buggy
|
||||
TEST_CASE("[Dictionary] Hash recursive dictionary on keys") {
|
||||
Dictionary d;
|
||||
d[d] = 1;
|
||||
|
||||
// Hash should reach recursion limit, we just make sure this doesn't blow up
|
||||
ERR_PRINT_OFF;
|
||||
d.hash();
|
||||
ERR_PRINT_ON;
|
||||
|
||||
// Break the recursivity otherwise Dictionary teardown will leak memory
|
||||
d.clear();
|
||||
}
|
||||
#endif
|
||||
|
||||
TEST_CASE("[Dictionary] Empty comparison") {
|
||||
Dictionary d1;
|
||||
Dictionary d2;
|
||||
|
||||
// test both operator== and operator!=
|
||||
CHECK_EQ(d1, d2);
|
||||
CHECK_FALSE(d1 != d2);
|
||||
}
|
||||
|
||||
TEST_CASE("[Dictionary] Flat comparison") {
|
||||
Dictionary d1 = { { 1, 1 } };
|
||||
Dictionary d2 = { { 1, 1 } };
|
||||
Dictionary other_d = { { 2, 1 } };
|
||||
|
||||
// test both operator== and operator!=
|
||||
CHECK_EQ(d1, d1); // compare self
|
||||
CHECK_FALSE(d1 != d1);
|
||||
CHECK_EQ(d1, d2); // different equivalent arrays
|
||||
CHECK_FALSE(d1 != d2);
|
||||
CHECK_NE(d1, other_d); // different arrays with different content
|
||||
CHECK_FALSE(d1 == other_d);
|
||||
}
|
||||
|
||||
TEST_CASE("[Dictionary] Nested dictionary comparison") {
|
||||
// d1 = {1: {2: {3: 4}}}
|
||||
Dictionary d1 = { { 1, Dictionary({ { 2, Dictionary({ { 3, 4 } }) } }) } };
|
||||
|
||||
Dictionary d2 = d1.duplicate(true);
|
||||
|
||||
// other_d = {1: {2: {3: 0}}}
|
||||
Dictionary other_d = { { 1, Dictionary({ { 2, Dictionary({ { 3, 0 } }) } }) } };
|
||||
|
||||
// test both operator== and operator!=
|
||||
CHECK_EQ(d1, d1); // compare self
|
||||
CHECK_FALSE(d1 != d1);
|
||||
CHECK_EQ(d1, d2); // different equivalent arrays
|
||||
CHECK_FALSE(d1 != d2);
|
||||
CHECK_NE(d1, other_d); // different arrays with different content
|
||||
CHECK_FALSE(d1 == other_d);
|
||||
}
|
||||
|
||||
TEST_CASE("[Dictionary] Nested array comparison") {
|
||||
// d1 = {1: [2, 3]}
|
||||
Dictionary d1 = { { 1, { 2, 3 } } };
|
||||
|
||||
Dictionary d2 = d1.duplicate(true);
|
||||
|
||||
// other_d = {1: [2, 0]}
|
||||
Dictionary other_d = { { 1, { 2, 0 } } };
|
||||
|
||||
// test both operator== and operator!=
|
||||
CHECK_EQ(d1, d1); // compare self
|
||||
CHECK_FALSE(d1 != d1);
|
||||
CHECK_EQ(d1, d2); // different equivalent arrays
|
||||
CHECK_FALSE(d1 != d2);
|
||||
CHECK_NE(d1, other_d); // different arrays with different content
|
||||
CHECK_FALSE(d1 == other_d);
|
||||
}
|
||||
|
||||
TEST_CASE("[Dictionary] Recursive comparison") {
|
||||
Dictionary d1;
|
||||
d1[1] = d1;
|
||||
|
||||
Dictionary d2;
|
||||
d2[1] = d2;
|
||||
|
||||
// Comparison should reach recursion limit
|
||||
ERR_PRINT_OFF;
|
||||
CHECK_EQ(d1, d2);
|
||||
CHECK_FALSE(d1 != d2);
|
||||
ERR_PRINT_ON;
|
||||
|
||||
d1[2] = 2;
|
||||
d2[2] = 2;
|
||||
|
||||
// Comparison should reach recursion limit
|
||||
ERR_PRINT_OFF;
|
||||
CHECK_EQ(d1, d2);
|
||||
CHECK_FALSE(d1 != d2);
|
||||
ERR_PRINT_ON;
|
||||
|
||||
d1[3] = 3;
|
||||
d2[3] = 0;
|
||||
|
||||
// Comparison should reach recursion limit
|
||||
ERR_PRINT_OFF;
|
||||
CHECK_NE(d1, d2);
|
||||
CHECK_FALSE(d1 == d2);
|
||||
ERR_PRINT_ON;
|
||||
|
||||
// Break the recursivity otherwise Dictionary teardown will leak memory
|
||||
d1.clear();
|
||||
d2.clear();
|
||||
}
|
||||
|
||||
#if 0 // TODO: recursion in dict key is currently buggy
|
||||
TEST_CASE("[Dictionary] Recursive comparison on keys") {
|
||||
Dictionary d1;
|
||||
// Hash computation should reach recursion limit
|
||||
ERR_PRINT_OFF;
|
||||
d1[d1] = 1;
|
||||
ERR_PRINT_ON;
|
||||
|
||||
Dictionary d2;
|
||||
// Hash computation should reach recursion limit
|
||||
ERR_PRINT_OFF;
|
||||
d2[d2] = 1;
|
||||
ERR_PRINT_ON;
|
||||
|
||||
// Comparison should reach recursion limit
|
||||
ERR_PRINT_OFF;
|
||||
CHECK_EQ(d1, d2);
|
||||
CHECK_FALSE(d1 != d2);
|
||||
ERR_PRINT_ON;
|
||||
|
||||
d1[2] = 2;
|
||||
d2[2] = 2;
|
||||
|
||||
// Comparison should reach recursion limit
|
||||
ERR_PRINT_OFF;
|
||||
CHECK_EQ(d1, d2);
|
||||
CHECK_FALSE(d1 != d2);
|
||||
ERR_PRINT_ON;
|
||||
|
||||
d1[3] = 3;
|
||||
d2[3] = 0;
|
||||
|
||||
// Comparison should reach recursion limit
|
||||
ERR_PRINT_OFF;
|
||||
CHECK_NE(d1, d2);
|
||||
CHECK_FALSE(d1 == d2);
|
||||
ERR_PRINT_ON;
|
||||
|
||||
// Break the recursivity otherwise Dictionary teardown will leak memory
|
||||
d1.clear();
|
||||
d2.clear();
|
||||
}
|
||||
#endif
|
||||
|
||||
TEST_CASE("[Dictionary] Recursive self comparison") {
|
||||
Dictionary d1;
|
||||
Dictionary d2;
|
||||
d1[1] = d2;
|
||||
d2[1] = d1;
|
||||
|
||||
CHECK_EQ(d1, d1);
|
||||
CHECK_FALSE(d1 != d1);
|
||||
|
||||
// Break the recursivity otherwise Dictionary teardown will leak memory
|
||||
d1.clear();
|
||||
d2.clear();
|
||||
}
|
||||
|
||||
TEST_CASE("[Dictionary] Order and find") {
|
||||
Dictionary d;
|
||||
d[4] = "four";
|
||||
d[8] = "eight";
|
||||
d[12] = "twelve";
|
||||
d["4"] = "four";
|
||||
|
||||
Array keys = { 4, 8, 12, "4" };
|
||||
|
||||
CHECK_EQ(d.keys(), keys);
|
||||
CHECK_EQ(d.find_key("four"), Variant(4));
|
||||
CHECK_EQ(d.find_key("does not exist"), Variant());
|
||||
}
|
||||
|
||||
TEST_CASE("[Dictionary] Typed copying") {
|
||||
TypedDictionary<int, int> d1;
|
||||
d1[0] = 1;
|
||||
|
||||
TypedDictionary<double, double> d2;
|
||||
d2[0] = 1.0;
|
||||
|
||||
Dictionary d3 = d1;
|
||||
TypedDictionary<int, int> d4 = d3;
|
||||
|
||||
Dictionary d5 = d2;
|
||||
TypedDictionary<int, int> d6 = d5;
|
||||
|
||||
d3[0] = 2;
|
||||
d4[0] = 3;
|
||||
|
||||
// Same typed TypedDictionary should be shared.
|
||||
CHECK_EQ(d1[0], Variant(3));
|
||||
CHECK_EQ(d3[0], Variant(3));
|
||||
CHECK_EQ(d4[0], Variant(3));
|
||||
|
||||
d5[0] = 2.0;
|
||||
d6[0] = 3.0;
|
||||
|
||||
// Different typed TypedDictionary should not be shared.
|
||||
CHECK_EQ(d2[0], Variant(2.0));
|
||||
CHECK_EQ(d5[0], Variant(2.0));
|
||||
CHECK_EQ(d6[0], Variant(3.0));
|
||||
|
||||
d1.clear();
|
||||
d2.clear();
|
||||
d3.clear();
|
||||
d4.clear();
|
||||
d5.clear();
|
||||
d6.clear();
|
||||
}
|
||||
|
||||
TEST_CASE("[Dictionary] Iteration") {
|
||||
Dictionary a1 = { { 1, 2 }, { 3, 4 }, { 5, 6 } };
|
||||
Dictionary a2 = { { 1, 2 }, { 3, 4 }, { 5, 6 } };
|
||||
|
||||
int idx = 0;
|
||||
|
||||
for (const KeyValue<Variant, Variant> &kv : (const Dictionary &)a1) {
|
||||
CHECK_EQ(int(a2[kv.key]), int(kv.value));
|
||||
idx++;
|
||||
}
|
||||
|
||||
CHECK_EQ(idx, a1.size());
|
||||
|
||||
a1.clear();
|
||||
a2.clear();
|
||||
}
|
||||
|
||||
TEST_CASE("[Dictionary] Object value init") {
|
||||
Object *a = memnew(Object);
|
||||
Object *b = memnew(Object);
|
||||
TypedDictionary<double, Object *> tdict = {
|
||||
{ 0.0, a },
|
||||
{ 5.0, b },
|
||||
};
|
||||
CHECK_EQ(tdict[0.0], Variant(a));
|
||||
CHECK_EQ(tdict[5.0], Variant(b));
|
||||
memdelete(a);
|
||||
memdelete(b);
|
||||
}
|
||||
|
||||
TEST_CASE("[Dictionary] RefCounted value init") {
|
||||
Ref<RefCounted> a = memnew(RefCounted);
|
||||
Ref<RefCounted> b = memnew(RefCounted);
|
||||
TypedDictionary<double, Ref<RefCounted>> tdict = {
|
||||
{ 0.0, a },
|
||||
{ 5.0, b },
|
||||
};
|
||||
CHECK_EQ(tdict[0.0], Variant(a));
|
||||
CHECK_EQ(tdict[5.0], Variant(b));
|
||||
}
|
||||
|
||||
} // namespace TestDictionary
|
||||
2248
tests/core/variant/test_variant.h
Normal file
131
tests/core/variant/test_variant_utility.h
Normal file
@@ -0,0 +1,131 @@
|
||||
/**************************************************************************/
|
||||
/* test_variant_utility.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/variant/variant_utility.h"
|
||||
|
||||
#include "tests/test_macros.h"
|
||||
|
||||
namespace TestVariantUtility {
|
||||
|
||||
TEST_CASE("[VariantUtility] Type conversion") {
|
||||
Variant converted;
|
||||
converted = VariantUtilityFunctions::type_convert("Hi!", Variant::Type::NIL);
|
||||
CHECK(converted.get_type() == Variant::Type::NIL);
|
||||
CHECK(converted == Variant());
|
||||
|
||||
converted = VariantUtilityFunctions::type_convert("Hi!", Variant::Type::INT);
|
||||
CHECK(converted.get_type() == Variant::Type::INT);
|
||||
CHECK(converted == Variant(0));
|
||||
|
||||
converted = VariantUtilityFunctions::type_convert("123", Variant::Type::INT);
|
||||
CHECK(converted.get_type() == Variant::Type::INT);
|
||||
CHECK(converted == Variant(123));
|
||||
|
||||
converted = VariantUtilityFunctions::type_convert(123, Variant::Type::STRING);
|
||||
CHECK(converted.get_type() == Variant::Type::STRING);
|
||||
CHECK(converted == Variant("123"));
|
||||
|
||||
converted = VariantUtilityFunctions::type_convert(123.4, Variant::Type::INT);
|
||||
CHECK(converted.get_type() == Variant::Type::INT);
|
||||
CHECK(converted == Variant(123));
|
||||
|
||||
converted = VariantUtilityFunctions::type_convert(5, Variant::Type::VECTOR2);
|
||||
CHECK(converted.get_type() == Variant::Type::VECTOR2);
|
||||
CHECK(converted == Variant(Vector2(0, 0)));
|
||||
|
||||
converted = VariantUtilityFunctions::type_convert(Vector3(1, 2, 3), Variant::Type::VECTOR2);
|
||||
CHECK(converted.get_type() == Variant::Type::VECTOR2);
|
||||
CHECK(converted == Variant(Vector2(1, 2)));
|
||||
|
||||
converted = VariantUtilityFunctions::type_convert(Vector2(1, 2), Variant::Type::VECTOR4);
|
||||
CHECK(converted.get_type() == Variant::Type::VECTOR4);
|
||||
CHECK(converted == Variant(Vector4(1, 2, 0, 0)));
|
||||
|
||||
converted = VariantUtilityFunctions::type_convert(Vector4(1.2, 3.4, 5.6, 7.8), Variant::Type::VECTOR3I);
|
||||
CHECK(converted.get_type() == Variant::Type::VECTOR3I);
|
||||
CHECK(converted == Variant(Vector3i(1, 3, 5)));
|
||||
|
||||
{
|
||||
Basis basis = Basis::from_scale(Vector3(1.2, 3.4, 5.6));
|
||||
Transform3D transform = Transform3D(basis, Vector3());
|
||||
|
||||
converted = VariantUtilityFunctions::type_convert(transform, Variant::Type::BASIS);
|
||||
CHECK(converted.get_type() == Variant::Type::BASIS);
|
||||
CHECK(converted == basis);
|
||||
|
||||
converted = VariantUtilityFunctions::type_convert(basis, Variant::Type::TRANSFORM3D);
|
||||
CHECK(converted.get_type() == Variant::Type::TRANSFORM3D);
|
||||
CHECK(converted == transform);
|
||||
|
||||
converted = VariantUtilityFunctions::type_convert(basis, Variant::Type::STRING);
|
||||
CHECK(converted.get_type() == Variant::Type::STRING);
|
||||
CHECK(converted == Variant("[X: (1.2, 0.0, 0.0), Y: (0.0, 3.4, 0.0), Z: (0.0, 0.0, 5.6)]"));
|
||||
}
|
||||
|
||||
{
|
||||
Array arr = { 1.2, 3.4, 5.6 };
|
||||
PackedFloat64Array packed = { 1.2, 3.4, 5.6 };
|
||||
|
||||
converted = VariantUtilityFunctions::type_convert(arr, Variant::Type::PACKED_FLOAT64_ARRAY);
|
||||
CHECK(converted.get_type() == Variant::Type::PACKED_FLOAT64_ARRAY);
|
||||
CHECK(converted == packed);
|
||||
|
||||
converted = VariantUtilityFunctions::type_convert(packed, Variant::Type::ARRAY);
|
||||
CHECK(converted.get_type() == Variant::Type::ARRAY);
|
||||
CHECK(converted == arr);
|
||||
}
|
||||
|
||||
{
|
||||
// Check that using Variant::call_utility_function also works.
|
||||
Vector<const Variant *> args;
|
||||
Variant data_arg = "Hi!";
|
||||
args.push_back(&data_arg);
|
||||
Variant type_arg = Variant::Type::NIL;
|
||||
args.push_back(&type_arg);
|
||||
Callable::CallError call_error;
|
||||
Variant::call_utility_function("type_convert", &converted, (const Variant **)args.ptr(), 2, call_error);
|
||||
CHECK(converted.get_type() == Variant::Type::NIL);
|
||||
CHECK(converted == Variant());
|
||||
|
||||
type_arg = Variant::Type::INT;
|
||||
Variant::call_utility_function("type_convert", &converted, (const Variant **)args.ptr(), 2, call_error);
|
||||
CHECK(converted.get_type() == Variant::Type::INT);
|
||||
CHECK(converted == Variant(0));
|
||||
|
||||
data_arg = "123";
|
||||
Variant::call_utility_function("type_convert", &converted, (const Variant **)args.ptr(), 2, call_error);
|
||||
CHECK(converted.get_type() == Variant::Type::INT);
|
||||
CHECK(converted == Variant(123));
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace TestVariantUtility
|
||||
136
tests/create_test.py
Normal file
@@ -0,0 +1,136 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
from subprocess import call
|
||||
|
||||
|
||||
def main():
|
||||
# Change to the directory where the script is located,
|
||||
# so that the script can be run from any location.
|
||||
os.chdir(os.path.dirname(os.path.realpath(__file__)))
|
||||
|
||||
parser = argparse.ArgumentParser(description="Creates a new unit test file.")
|
||||
parser.add_argument(
|
||||
"name",
|
||||
type=str,
|
||||
help="Specifies the class or component name to be tested, in PascalCase (e.g., MeshInstance3D). The name will be prefixed with 'test_' for the header file and 'Test' for the namespace.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"path",
|
||||
type=str,
|
||||
nargs="?",
|
||||
help="The path to the unit test file relative to the tests folder (e.g. core). This should correspond to the relative path of the class or component being tested. (default: .)",
|
||||
default=".",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-i",
|
||||
"--invasive",
|
||||
action="store_true",
|
||||
help="if set, the script will automatically insert the include directive in test_main.cpp. Use with caution!",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
snake_case_regex = re.compile(r"(?<!^)(?=[A-Z, 0-9])")
|
||||
# Replace 2D, 3D, and 4D with 2d, 3d, and 4d, respectively. This avoids undesired splits like node_3_d.
|
||||
prefiltered_name = re.sub(r"([234])D", lambda match: match.group(1).lower() + "d", args.name)
|
||||
name_snake_case = snake_case_regex.sub("_", prefiltered_name).lower()
|
||||
file_path = os.path.normpath(os.path.join(args.path, f"test_{name_snake_case}.h"))
|
||||
|
||||
# Ensure the directory exists.
|
||||
os.makedirs(os.path.dirname(file_path), exist_ok=True)
|
||||
|
||||
print(file_path)
|
||||
if os.path.isfile(file_path):
|
||||
print(f'ERROR: The file "{file_path}" already exists.')
|
||||
sys.exit(1)
|
||||
with open(file_path, "w", encoding="utf-8", newline="\n") as file:
|
||||
file.write(
|
||||
"""/**************************************************************************/
|
||||
/* test_{name_snake_case}.h {padding} */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "tests/test_macros.h"
|
||||
|
||||
namespace Test{name_pascal_case} {{
|
||||
|
||||
TEST_CASE("[{name_pascal_case}] Example test case") {{
|
||||
// TODO: Remove this comment and write your test code here.
|
||||
}}
|
||||
|
||||
}} // namespace Test{name_pascal_case}
|
||||
""".format(
|
||||
name_snake_case=name_snake_case,
|
||||
# Capitalize the first letter but keep capitalization for the rest of the string.
|
||||
# This is done in case the user passes a camelCase string instead of PascalCase.
|
||||
name_pascal_case=args.name[0].upper() + args.name[1:],
|
||||
# The padding length depends on the test name length.
|
||||
padding=" " * (61 - len(name_snake_case)),
|
||||
)
|
||||
)
|
||||
|
||||
# Print an absolute path so it can be Ctrl + clicked in some IDEs and terminal emulators.
|
||||
print("Test header file created:")
|
||||
print(os.path.abspath(file_path))
|
||||
|
||||
if args.invasive:
|
||||
print("Trying to insert include directive in test_main.cpp...")
|
||||
with open("test_main.cpp", "r", encoding="utf-8") as file:
|
||||
contents = file.read()
|
||||
match = re.search(r'#include "tests.*\n', contents)
|
||||
|
||||
if match:
|
||||
new_string = contents[: match.start()] + f'#include "tests/{file_path}"\n' + contents[match.start() :]
|
||||
|
||||
with open("test_main.cpp", "w", encoding="utf-8", newline="\n") as file:
|
||||
file.write(new_string)
|
||||
print("Done.")
|
||||
# Use clang format to sort include directives afster insertion.
|
||||
clang_format_args = ["clang-format", "test_main.cpp", "-i"]
|
||||
retcode = call(clang_format_args)
|
||||
if retcode != 0:
|
||||
print(
|
||||
"Include directives in test_main.cpp could not be sorted automatically using clang-format. Please sort them manually."
|
||||
)
|
||||
else:
|
||||
print("Could not find a valid position in test_main.cpp to insert the include directive.")
|
||||
|
||||
else:
|
||||
print("\nRemember to #include the new test header in this file (following alphabetical order):")
|
||||
print(os.path.abspath("test_main.cpp"))
|
||||
print("Insert the following line in the appropriate place:")
|
||||
print(f'#include "tests/{file_path}"')
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
51
tests/data/crypto/in.key
Normal file
@@ -0,0 +1,51 @@
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIJKQIBAAKCAgEAp+sWMepqN+dXhmZkS45W0FTLX2f0zRb+kUhXngfiEprgA2Uu
|
||||
ZP5gGv7/EjOBDniYalGZqYPJum08Px++17P6Hyopr05aPhZ6Ocnt+LAU/WRAFspN
|
||||
3xXTZglqnmGMsdNcqBl3loVNmC1H/zX53oBeP/L/i4lva3DjCRIpHtGjfy3ufSn7
|
||||
fTrlBqKIEHYgQZyzos9dxbk1NJcWVxlNmvFRtgPW6DQ/P3g0ahLJeq/hO3ykUFfi
|
||||
KdhHkS+s9dC9qjZwSxCCEF1o6zkEO6ynxk0sxjOpPTJFw7fQ+2KeJWJQfdZeJ6u+
|
||||
DpbwK4g9tMUMNxf3QMk1KhAnwwUrKXfZcviS3zweD8Tl5zEj45H1ghoUQfuW7Y3w
|
||||
gwHMu8lF8iGA87cf/fhqr0V9piCcwWkfVP/athpMoUfyj9Sa3ag0dvDSo9PAet0u
|
||||
rXmdKTyhMg4lQL6f9BmMuuB/KwWzCuG/5VY9ONxno3OVX6juHTpng5UglbkiDsD3
|
||||
tivl1gCbvIryoGdt+xI0JmAC5eXfg79Nio/BayDR9Npm1m460p3GeRaawyYysBo/
|
||||
L5/YZ/S3bYBRoJ7lq6GkTA+22lWAb04IgtS8wxO4Ma8EOtKD+AoR3C+WLivcp9LN
|
||||
TxbQOMKGL+8imQGBEz3XTR4lrE02QDQy0DIBKy7p7dhlyBdwhTmBX3P2mx0CAwEA
|
||||
AQKCAgBv7edUjIITE5UnFHeEWbQKmIsb5GqsjshPxV4KDA0pA62Q9dAQJ/Od6x3R
|
||||
Xx2GrOJD9HKuKRe9ufSvyxRmKiTuwycYIO1Md6UvgierXowPP9TsnBt+Ock5Ocul
|
||||
GTc0jcQ0lQ0++0p2xrA4MR2GsCCjFfI7a/gmMRBVSpK4ZVtLei1/pw1pM2nYm1yB
|
||||
RIxJ0A951ioWk1cg4BlXI5m0T2l9H2AQVktWnmSp1C4TJsvG4FWS7JHn/K/v2ky7
|
||||
alIS9MizcKSSDgHS0aW9tV/8chMHZwZHsYwJYyzddKYgG0G2L7+BSByfEwOysNUY
|
||||
+0QiMUpyF+zlRfGLMJXNxYLf/UvAhu2xbNi6+A1/xm4FerFF0arMMUzY1Lwwa02t
|
||||
yBmflhZ+s2ngT4grj3waShC14H6idL2j5HFcyBs/UOA+HkV1I5SoGihNziMP8gfX
|
||||
IDSb4WBzckPZD6kUAojNFqhx+W7XpWWE5QnWam76b8Mzdg9Xf9pKo6ULt6kwdC8Z
|
||||
ufbOTRXO08jkb1F64Fmb4F7EAvXLyhFtclY4CuPYSA68Sad8A41ipCsQ5bwvUTMd
|
||||
o2l7kYplk4f/Bvz2yOhZZVdWGanmKvnGUMehJ+B4zi8HFOIRd21bXkeBwwKjqNni
|
||||
3kqVairo3O2HWrAJwRvhCZam14HGkr+HQPEGLn48fstquizoQQKCAQEA3slWrItS
|
||||
wdwciouUbEDbftmWEL+eXAQyVxbIkVuvugLyeZ1lHIk7K5lZ8hecWLNJXPeqyi7P
|
||||
i2AI8O+7VEoJmePwGmZ19rJHNBB4thgZq114sbkHcQNXXhdFnen8HkdZRdjusViC
|
||||
BHXCtG8TzxCrpQl/6dLDUkG7D3DiYGaV1IHQ+BNYjJQfh6grkcqRrOq7JH2CnKV0
|
||||
VCxoSobKgB1zEdc7gKyeRp9SSs5rJe5qTmEMXptrQUeXeElsHfmOV8RUO9b1BBrN
|
||||
bZhMR3zbE/8Oq3umro7WBaSg7XZMSwCeI0FR8uUAy/FZTrWRPeb86ywmP+pQqcdp
|
||||
R0OQL0vSWWaLcQKCAQEAwPOzV9p0dQtERy3Z0hh6A3pj+fV5+l5QsA+R4vDoOH/l
|
||||
GCoEJwSh21qOspSy66fulJR3Jml385/x8I3MC87u8h33aKsdmeVYOdmuiB+lj3sc
|
||||
DRY/J9w9WbL3mdF3H1rU0ldxfKr1HdfK4xcSdJKBE1FYfO9tB6NFvgdSnvMbmSa1
|
||||
LVtc8N5hSB1h+d5LWHzh4TC4SG89TivQi0oEacErVJT9OEdGwAtgEU3K8UGKOcTr
|
||||
OKos0vts281DuKpkfLBstH8l+VOdBD4E+I+MB1Y50oJ/D3h4bl+WDcR0DqlWzay3
|
||||
3WCSjzZC5T9lEyQ/0TKv7GPgAiH5/41nQnSM6ar8bQKCAQEAvSf9q2pvzaFxqkBw
|
||||
uKkotD9SJs5LSp1VkJQLnz9VqH2wGooEu4HY91+w+tgJK1auR30RSbENDq1vagJh
|
||||
72MdW8goqIGuTtN3mUES/KjhwpoOS/dp1g6cM4tW1IlCQwMZTTCvGWyol9jUhBZ7
|
||||
nyfsVKgILyOAK2sbxDR4QJlZRaEjKD5kxJdPXgLvW02++i4izwyxxQbGCmHZ+s0P
|
||||
Sk+2z8MLBmmJyTSkzlcMqpwPLpU/x2P2YOrENKFCZwDoVqSfUF9mkSGgohjZSyk7
|
||||
aXL5pafLEhK8rPXmnTf/9v6DRjPDvJOrZX158lY/B2wD+jj2EPaFnmFthdBbr4yV
|
||||
AMsMQQKCAQEAn8nxroKRyNAAxjV5Wly8xp6XpsucLTPn/DWYqei5VvjLPxykfa9/
|
||||
Xsl6vPcZyMA0esUMezoChTXixUSYQvsmtEkOt5ZlmCnuy1GzELWshMr96vSObrMb
|
||||
92mXVMG7tbKh5mNV71kgTouDUFauCO2+iMHn1ubsUtPqkLk9ubY4F7ePeLVdnXd7
|
||||
9p2moqdtnCUnZjbTleDRUyhDtuYgC3hWKuCLZwzX0XhaIVpcAzk0gCzMYwvCvSJL
|
||||
/ybYu1gYiY4NJ9jYGMcelAHMWg9+diD5F5TMJoKssTLlcBdNyUqBQSiUx3cPSBw2
|
||||
f+TlDloJo3QnbkszmnCKuRBgAA/HFkdsbQKCAQBokUYEzTy3iOwcv9xe2DZoeIeG
|
||||
Y0B/ri8FxQ+Wt5t/LtGKIwKL5BEpgjXVLL1l/is4jncLkUdGrdqglbjgkJ/fUB/5
|
||||
/354BjX9a1EBw95XTrUFcdX8uglYkPZkIR9GWY7m87NvLZUdrsmrfl6HA4I4kpAt
|
||||
1N0dcn/8GIm9vm7COPGDjjPzv+xlMuMjdeTuBxe8tddOwwtXjzkTZmcOdZpianxF
|
||||
R3zY1LCHk9vPulkAs2o+qCTBGT0qHJp04AamY3KWPW4Cf5GzPousOB4p7Hu8UFyv
|
||||
FISkNe48bzSYveJ+yZ3myG2fBCGFQmSRJVcauIokPAl6lKF/Vw5DdWp1LYWa
|
||||
-----END RSA PRIVATE KEY-----
|
||||
14
tests/data/crypto/in.pub
Normal file
@@ -0,0 +1,14 @@
|
||||
-----BEGIN PUBLIC KEY-----
|
||||
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAp+sWMepqN+dXhmZkS45W
|
||||
0FTLX2f0zRb+kUhXngfiEprgA2UuZP5gGv7/EjOBDniYalGZqYPJum08Px++17P6
|
||||
Hyopr05aPhZ6Ocnt+LAU/WRAFspN3xXTZglqnmGMsdNcqBl3loVNmC1H/zX53oBe
|
||||
P/L/i4lva3DjCRIpHtGjfy3ufSn7fTrlBqKIEHYgQZyzos9dxbk1NJcWVxlNmvFR
|
||||
tgPW6DQ/P3g0ahLJeq/hO3ykUFfiKdhHkS+s9dC9qjZwSxCCEF1o6zkEO6ynxk0s
|
||||
xjOpPTJFw7fQ+2KeJWJQfdZeJ6u+DpbwK4g9tMUMNxf3QMk1KhAnwwUrKXfZcviS
|
||||
3zweD8Tl5zEj45H1ghoUQfuW7Y3wgwHMu8lF8iGA87cf/fhqr0V9piCcwWkfVP/a
|
||||
thpMoUfyj9Sa3ag0dvDSo9PAet0urXmdKTyhMg4lQL6f9BmMuuB/KwWzCuG/5VY9
|
||||
ONxno3OVX6juHTpng5UglbkiDsD3tivl1gCbvIryoGdt+xI0JmAC5eXfg79Nio/B
|
||||
ayDR9Npm1m460p3GeRaawyYysBo/L5/YZ/S3bYBRoJ7lq6GkTA+22lWAb04IgtS8
|
||||
wxO4Ma8EOtKD+AoR3C+WLivcp9LNTxbQOMKGL+8imQGBEz3XTR4lrE02QDQy0DIB
|
||||
Ky7p7dhlyBdwhTmBX3P2mx0CAwEAAQ==
|
||||
-----END PUBLIC KEY-----
|
||||
BIN
tests/data/exactly_4096_bytes_fastlz.bin
Normal file
1
tests/data/floating_point_big_endian.bin
Normal file
@@ -0,0 +1 @@
|
||||
@IV
|
||||
1
tests/data/floating_point_little_endian.bin
Normal file
@@ -0,0 +1 @@
|
||||
VI@
|
||||
999
tests/data/fuzzy_search/project_dir_tree.txt
Normal file
@@ -0,0 +1,999 @@
|
||||
./menu/home/home_menu.tscn
|
||||
./menu/tooltips/tooltip_server.tscn
|
||||
./menu/tooltips/tooltip_server.gd
|
||||
./menu/tooltips/tooltip.gd
|
||||
./menu/menu/characters/smoker/4.wav
|
||||
./menu/menu/characters/smoker/6.wav
|
||||
./menu/menu/characters/smoker/10.wav
|
||||
./menu/menu/characters/smoker/smoker.tscn
|
||||
./menu/menu/characters/smoker/8.wav
|
||||
./menu/menu/characters/smoker/type.gd
|
||||
./menu/menu/characters/smoker/9.wav
|
||||
./menu/menu/characters/smoker/5.wav
|
||||
./menu/menu/characters/smoker/0.wav
|
||||
./menu/menu/characters/smoker/back_light.png
|
||||
./menu/menu/characters/smoker/glasses.png
|
||||
./menu/menu/characters/smoker/smoker.gd
|
||||
./menu/menu/characters/smoker/cig.gd
|
||||
./menu/menu/characters/smoker/eyes.png
|
||||
./menu/menu/characters/smoker/3.wav
|
||||
./menu/menu/characters/smoker/to_pixelate.gd
|
||||
./menu/menu/characters/smoker/7.wav
|
||||
./menu/menu/characters/smoker/cig.png
|
||||
./menu/menu/characters/smoker/2.wav
|
||||
./menu/menu/characters/smoker/1.wav
|
||||
./menu/menu/characters/smoke.png
|
||||
./menu/menu/characters/space_bandit.tres
|
||||
./menu/menu/characters/dead_guy/blood_texture.png
|
||||
./menu/menu/characters/dead_guy/head_gibbed.png
|
||||
./menu/menu/characters/dead_guy/back_light.png
|
||||
./menu/menu/characters/dead_guy/smoker.gd
|
||||
./menu/menu/characters/dead_guy/eyes.png
|
||||
./menu/menu/characters/dead_guy/to_pixelate.gd
|
||||
./menu/menu/characters/dead_guy/dead_guy.gd
|
||||
./menu/menu/characters/dead_guy/eyes.gd
|
||||
./menu/menu/characters/dead_guy/x.png
|
||||
./menu/menu/characters/dead_guy/dead_guy.tscn
|
||||
./menu/menu/characters/dead_guy/mouth.png
|
||||
./menu/menu/characters/dead_guy/dead_guy.tres
|
||||
./menu/menu/characters/Label.gd
|
||||
./menu/menu/characters/guns2.png
|
||||
./menu/menu/characters/c.gd
|
||||
./menu/menu/characters/smoke.gd
|
||||
./menu/menu/characters/character.gd
|
||||
./menu/menu/characters/space_bandit/eyes.tres
|
||||
./menu/menu/characters/space_bandit/space_bandit_face_happy.png
|
||||
./menu/menu/characters/space_bandit/space_bandit.gd
|
||||
./menu/menu/characters/space_bandit/space_bandit.tscn
|
||||
./menu/menu/characters/boss/smoker.tscn
|
||||
./menu/menu/characters/boss/back_light.png
|
||||
./menu/menu/characters/boss/glasses.png
|
||||
./menu/menu/characters/boss/smoker.gd
|
||||
./menu/menu/characters/boss/cig.gd
|
||||
./menu/menu/characters/boss/eyes.png
|
||||
./menu/menu/characters/boss/to_pixelate.gd
|
||||
./menu/menu/characters/boss/x.png
|
||||
./menu/menu/characters/boss/cig.png
|
||||
./menu/menu/characters/eye.gd
|
||||
./menu/menu/characters/space_bandit_face_happy.png
|
||||
./menu/menu/characters/face.gd
|
||||
./menu/menu/characters/color.tres
|
||||
./menu/menu/characters/space_bandit.tscn
|
||||
./menu/menu/characters/space_bandit_face_bloody.png
|
||||
./menu/menu/characters/guns.png
|
||||
./menu/menu/characters/eyes2.tres
|
||||
./menu/options/controls/use.tres
|
||||
./menu/options/controls/input_map_button.gd
|
||||
./menu/options/controls/swap.tres
|
||||
./menu/options/controls/teleport.tres
|
||||
./menu/options/controls/joy_controls.tscn
|
||||
./menu/options/controls/mouse_and_keyboard_controls.tscn
|
||||
./menu/options/controls/input_map_button.tscn
|
||||
./menu/options/controls/special.tres
|
||||
./menu/options/controls/throw.tres
|
||||
./menu/options/controls/center.tres
|
||||
./menu/options/controls/input_action.gd
|
||||
./menu/options/controls/move.tres
|
||||
./menu/options/controls/melee.tres
|
||||
./menu/options/controls/controls.gd
|
||||
./menu/options/options.gd
|
||||
./menu/options/options.tscn
|
||||
./menu/options/graphics/graphics.tscn
|
||||
./menu/options/graphics/graphics.gd
|
||||
./menu/options/audio/audio.gd
|
||||
./menu/options/audio/audio.tscn
|
||||
./menu/options/game/game.gd
|
||||
./menu/options/game/game.tscn
|
||||
./menu/circle.tres
|
||||
./menu/fonts/keys.png
|
||||
./menu/fonts/rainbow_font.tres
|
||||
./menu/fonts/fallback_font.tres
|
||||
./menu/fonts/taxi_Driver.png
|
||||
./menu/fonts/NotoSansJP-Regular.ttf
|
||||
./menu/fonts/taxi_Driver_noise.png
|
||||
./menu/fonts/rainbow_font_shader.tres
|
||||
./menu/fonts/m5x7.ttf
|
||||
./menu/colors.gd
|
||||
./menu/toast_enter.wav
|
||||
./menu/ui_colors.tres
|
||||
./menu/pause/pause.gd
|
||||
./menu/pause/rainbow.tres
|
||||
./menu/pause/Label.gd
|
||||
./menu/pause/label.tscn
|
||||
./menu/pause/pause.tscn
|
||||
./menu/hoola.wav
|
||||
./menu/in_game_fallback.tres
|
||||
./menu/widgets/next_unlock.gd
|
||||
./menu/widgets/slider.gd
|
||||
./menu/widgets/fade.tscn
|
||||
./menu/widgets/background_hint.gd
|
||||
./menu/widgets/panel_container_smoke.gd
|
||||
./menu/widgets/wishlist_sticker.gd
|
||||
./menu/widgets/smoke.tres
|
||||
./menu/widgets/color_grade.gd
|
||||
./menu/widgets/rich_text_button.gd
|
||||
./menu/widgets/panel_container_smok2.tscn
|
||||
./menu/widgets/slider.tscn
|
||||
./menu/widgets/rich_text_heading.gd
|
||||
./menu/widgets/background_hint.tscn
|
||||
./menu/widgets/tip.tscn
|
||||
./menu/widgets/rich_text_button.tscn
|
||||
./menu/widgets/toggle.tscn
|
||||
./menu/widgets/heading.tscn
|
||||
./menu/widgets/hover.tscn
|
||||
./menu/widgets/toggle.gd
|
||||
./menu/widgets/smoke_panel_material.tres
|
||||
./menu/widgets/confirm.gd
|
||||
./menu/widgets/tip.gd
|
||||
./menu/widgets/panel.gd
|
||||
./menu/widgets/modal.gd
|
||||
./menu/widgets/NinePatchRect.gd
|
||||
./menu/widgets/smoke.shader
|
||||
./menu/widgets/9patch.png
|
||||
./menu/widgets/big_hint.gd
|
||||
./menu/widgets/TDVB1i.png
|
||||
./menu/widgets/color_grade.tscn
|
||||
./menu/widgets/text.gd
|
||||
./menu/widgets/panel_container_smoke.tscn
|
||||
./menu/widgets/1x1.png
|
||||
./menu/widgets/confirm.tscn
|
||||
./menu/widgets/RichTextPanel.tscn
|
||||
./menu/hud/cursor.png
|
||||
./menu/hud/inventory/draggable.gd
|
||||
./menu/hud/inventory/menu/characters/color.tres
|
||||
./menu/hud/inventory/drop_zone.tscn
|
||||
./menu/hud/inventory/RichTextLabel.gd
|
||||
./menu/hud/inventory/hud_icon_mutation.tscn
|
||||
./menu/hud/inventory/use_count.gd
|
||||
./menu/hud/inventory/draggable.tscn
|
||||
./menu/hud/inventory/black_shadow_font.tres
|
||||
./menu/hud/inventory/x.png
|
||||
./menu/hud/inventory/hud_icon_mutation.gd
|
||||
./menu/hud/inventory/flash_parent.gd
|
||||
./menu/hud/inventory/TextureRect4.gd
|
||||
./menu/hud/cursor.tscn
|
||||
./menu/hud/hud.tscn
|
||||
./menu/hud/cursor.gd
|
||||
./menu/hud/hud.gd
|
||||
./menu/metal_text.tres
|
||||
./menu/rich_text_effects/RichTextType.gd
|
||||
./menu/rich_text_effects/RichTextPanel.gd
|
||||
./menu/rich_text_effects/RichTextFlash.gd
|
||||
./menu/rich_text_effects/RichTextTranslate.gd
|
||||
./menu/in_game.tres
|
||||
./menu/lcd_screen_font.tres
|
||||
./menu/toast_exit.wav
|
||||
./menu/stack/ahses_material.tres
|
||||
./menu/stack/home.kra
|
||||
./menu/stack/fade.gd
|
||||
./menu/stack/stack.tscn
|
||||
./menu/stack/stack.gd
|
||||
./menu/stack/version.gd
|
||||
./menu/stack/art.kra
|
||||
./entity/unlock_skin_classic/icon.png
|
||||
./entity/use.gd
|
||||
./entity/chair/entity.tscn
|
||||
./entity/chair/icon.png
|
||||
./entity/chair/data.gd
|
||||
./entity/man_desert/entity.tscn
|
||||
./entity/man_desert/icon.png
|
||||
./entity/man_desert/teleprompts/need_medbay.wav
|
||||
./entity/man_desert/teleprompts/me_too.wav
|
||||
./entity/man_desert/teleprompts/get_up_alt.wav
|
||||
./entity/man_desert/teleprompts/getting_a_medpack.wav
|
||||
./entity/man_desert/teleprompts/firstaid-incoming.wav
|
||||
./entity/man_desert/teleprompts/batch_name.py
|
||||
./entity/man_desert/teleprompts/what.wav
|
||||
./entity/man_desert/teleprompts/oo.wav
|
||||
./entity/man_desert/teleprompts/yell.wav
|
||||
./entity/man_desert/teleprompts/rushing.wav
|
||||
./entity/man_desert/teleprompts/ooo.wav
|
||||
./entity/man_desert/teleprompts/coming_to_heal_ya.wav
|
||||
./entity/man_desert/teleprompts/where_is_the_medpack.wav
|
||||
./entity/man_desert/teleprompts/ah.wav
|
||||
./entity/man_desert/teleprompts/no.wav
|
||||
./entity/man_desert/teleprompts/going_to_camp_medbay.wav
|
||||
./entity/man_desert/teleprompts/aa.wav
|
||||
./entity/man_desert/teleprompts/pirate_alt.wav
|
||||
./entity/man_desert/teleprompts/take_morphine.wav
|
||||
./entity/man_desert/teleprompts/ee.wav
|
||||
./entity/man_desert/teleprompts/get_up.wav
|
||||
./entity/man_desert/teleprompts/aw.wav
|
||||
./entity/man_desert/teleprompts/easy.wav
|
||||
./entity/man_desert/teleprompts/intruder.wav
|
||||
./entity/man_desert/teleprompts/amateur.wav
|
||||
./entity/man_desert/teleprompts/hes_not_moving.wav
|
||||
./entity/man_desert/teleprompts/pirate.wav
|
||||
./entity/man_desert/teleprompts/i_dont_know.wav
|
||||
./entity/man_desert/teleprompts/index.txt
|
||||
./entity/man_desert/teleprompts/move.wav
|
||||
./entity/man_desert/teleprompts/hes_stuck.wav
|
||||
./entity/man_desert/teleprompts/how.wav
|
||||
./entity/man_desert/teleprompts/uu.wav
|
||||
./entity/man_desert/teleprompts/where_is_the_gun.wav
|
||||
./entity/man_desert/teleprompts/getting_a_gun.wav
|
||||
./entity/man_desert/data.gd
|
||||
./entity/man_desert/hand.png
|
||||
./entity/barrel_side_smoke/entity.tscn
|
||||
./entity/barrel_side_smoke/icon.png
|
||||
./entity/barrel_side_smoke/data.gd
|
||||
./entity/barrel_smoke/entity.tscn
|
||||
./entity/barrel_smoke/icon.png
|
||||
./entity/barrel_smoke/data.gd
|
||||
./entity/project_box/entity.tscn
|
||||
./entity/project_box/icon.png
|
||||
./entity/project_box/data.gd
|
||||
./entity/mutation_saw/entity.tscn
|
||||
./entity/mutation_saw/icon.png
|
||||
./entity/mutation_saw/special.gd
|
||||
./entity/mutation_saw/data.gd
|
||||
./entity/lift_entrance/entity.tscn
|
||||
./entity/lift_entrance/icon.png
|
||||
./entity/lift_entrance/special.gd
|
||||
./entity/lift_entrance/data.gd
|
||||
./entity/mutation_accuracy_boost_DELETE/entity.tscn
|
||||
./entity/mutation_accuracy_boost_DELETE/icon.png
|
||||
./entity/mutation_accuracy_boost_DELETE/special.gd
|
||||
./entity/mutation_accuracy_boost_DELETE/data.gd
|
||||
./entity/skin_ruffle/entity.tscn
|
||||
./entity/skin_ruffle/icon.png
|
||||
./entity/skin_ruffle/carried.png
|
||||
./entity/skin_ruffle/data.gd
|
||||
./entity/editor_only_icon.gd
|
||||
./entity/console_dark/entity.tscn
|
||||
./entity/console_dark/icon.png
|
||||
./entity/console_dark/data.gd
|
||||
./entity/console_dark/animation.png
|
||||
./entity/smg2/entity.tscn
|
||||
./entity/smg2/used.wav
|
||||
./entity/smg2/icon.png
|
||||
./entity/smg2/data.gd
|
||||
./entity/smg2/debug.gd
|
||||
./entity/grenade_launcher/entity.tscn
|
||||
./entity/grenade_launcher/used.wav
|
||||
./entity/grenade_launcher/icon.png
|
||||
./entity/grenade_launcher/special.gd
|
||||
./entity/grenade_launcher/data.gd
|
||||
./entity/floor_tile_full_square/entity.tscn
|
||||
./entity/floor_tile_full_square/icon.png
|
||||
./entity/floor_tile_full_square/data.gd
|
||||
./entity/grate_1/entity.tscn
|
||||
./entity/grate_1/icon.png
|
||||
./entity/grate_1/data.gd
|
||||
./entity/bed_bunk_corner/entity.tscn
|
||||
./entity/bed_bunk_corner/icon.png
|
||||
./entity/bed_bunk_corner/data.gd
|
||||
./entity/kill_streak_rail_gun_level_3/entity.tscn
|
||||
./entity/kill_streak_rail_gun_level_3/data.gd
|
||||
./entity/teleporter_random_weak/entity.tscn
|
||||
./entity/teleporter_random_weak/teleporter_model.gd
|
||||
./entity/teleporter_random_weak/used.wav
|
||||
./entity/teleporter_random_weak/icon.png
|
||||
./entity/teleporter_random_weak/special.gd
|
||||
./entity/teleporter_random_weak/ray.gd
|
||||
./entity/teleporter_random_weak/data.gd
|
||||
./entity/teleporter_random_weak/flap.png
|
||||
./entity/entities.kra
|
||||
./entity/jerry_can/entity.tscn
|
||||
./entity/jerry_can/icon.png
|
||||
./entity/jerry_can/data.gd
|
||||
./entity/kill_streak_helmet_full/entity.tscn
|
||||
./entity/kill_streak_helmet_full/data.gd
|
||||
./entity/background_derelict/background2.gd
|
||||
./entity/background_derelict/entity.tscn
|
||||
./entity/background_derelict/icon.png
|
||||
./entity/background_derelict/background/space.png
|
||||
./entity/background_derelict/background/line.png
|
||||
./entity/background_derelict/background/overlay.png
|
||||
./entity/background_derelict/background/background2.png
|
||||
./entity/background_derelict/background/background.png
|
||||
./entity/background_derelict/background/engine_glow.tscn
|
||||
./entity/background_derelict/background/lines3.png
|
||||
./entity/background_derelict/background/background.tscn
|
||||
./entity/background_derelict/background/lines.tres
|
||||
./entity/background_derelict/background/xx.gd
|
||||
./entity/background_derelict/background/background.gd
|
||||
./entity/background_derelict/background/bayer16tile2.png
|
||||
./entity/background_derelict/background/push.png
|
||||
./entity/background_derelict/background/palette_mono.png
|
||||
./entity/background_derelict/background/stars.gd
|
||||
./entity/background_derelict/background/lines2.png
|
||||
./entity/background_derelict/background/lines.shader
|
||||
./entity/background_derelict/background/ambience.gd
|
||||
./entity/background_derelict/background/space_ship_ambience.ogg
|
||||
./entity/background_derelict/background/stars.png
|
||||
./entity/background_derelict/data.gd
|
||||
./entity/smoker/entity.tscn
|
||||
./entity/smoker/right_hand.png
|
||||
./entity/smoker/eyes.png
|
||||
./entity/smoker/data.gd
|
||||
./entity/smoker/animate.gd
|
||||
./entity/smoker/left_hand.png
|
||||
./entity/EntityStatic.gd
|
||||
./entity/level_model.gd
|
||||
./entity/class_teleporter_drop_chance/entity.tscn
|
||||
./entity/class_teleporter_drop_chance/icon.png
|
||||
./entity/class_teleporter_drop_chance/special.gd
|
||||
./entity/class_teleporter_drop_chance/data.gd
|
||||
./entity/smg4/entity.tscn
|
||||
./entity/smg4/used.wav
|
||||
./entity/smg4/icon.png
|
||||
./entity/smg4/data.gd
|
||||
./entity/medpack/entity.tscn
|
||||
./entity/medpack/icon.png
|
||||
./entity/medpack/dead.png
|
||||
./entity/medpack/data.gd
|
||||
./entity/model.gd
|
||||
./entity/doom_transition/entity.tscn
|
||||
./entity/doom_transition/icon.png
|
||||
./entity/doom_transition/special.gd
|
||||
./entity/doom_transition/Screenshot from 2021-12-08 18-25-03.png
|
||||
./entity/doom_transition/data.gd
|
||||
./entity/glass_block_exploding/entity.tscn
|
||||
./entity/glass_block_exploding/icon.png
|
||||
./entity/glass_block_exploding/special.gd
|
||||
./entity/glass_block_exploding/dead.png
|
||||
./entity/glass_block_exploding/data.gd
|
||||
./entity/floor_ting/entity.tscn
|
||||
./entity/floor_ting/icon.png
|
||||
./entity/floor_ting/data.gd
|
||||
./entity/background_crashed_ship/entity.tscn
|
||||
./entity/background_crashed_ship/icon.png
|
||||
./entity/background_crashed_ship/background/background2.kra
|
||||
./entity/background_crashed_ship/background/dust_storm_negative.png
|
||||
./entity/background_crashed_ship/background/background2.png
|
||||
./entity/background_crashed_ship/background/background2 (copy 1).png
|
||||
./entity/background_crashed_ship/background/dust_bowl.ogg
|
||||
./entity/background_crashed_ship/background/background.tscn
|
||||
./entity/background_crashed_ship/background/background.kra
|
||||
./entity/background_crashed_ship/data.gd
|
||||
./entity/game_aim_hack_boss/entity.tscn
|
||||
./entity/game_aim_hack_boss/icon.png
|
||||
./entity/game_aim_hack_boss/special.gd
|
||||
./entity/game_aim_hack_boss/give_my_arm_back.wav
|
||||
./entity/game_aim_hack_boss/my_arm_came_off.wav
|
||||
./entity/game_aim_hack_boss/data.gd
|
||||
./entity/sink/entity.tscn
|
||||
./entity/sink/icon.png
|
||||
./entity/sink/data.gd
|
||||
./entity/grate_2/entity.tscn
|
||||
./entity/grate_2/icon.png
|
||||
./entity/grate_2/data.gd
|
||||
./entity/barrel_side/entity.tscn
|
||||
./entity/barrel_side/icon.png
|
||||
./entity/barrel_side/data.gd
|
||||
./entity/oxygen/entity.tscn
|
||||
./entity/oxygen/icon.png
|
||||
./entity/oxygen/shadow.png
|
||||
./entity/oxygen/data.gd
|
||||
./entity/oxygen/normal.png
|
||||
./entity/unlock_skin_robo/entity.tscn
|
||||
./entity/unlock_skin_robo/icon.png
|
||||
./entity/unlock_skin_robo/special.gd
|
||||
./entity/unlock_skin_robo/data.gd
|
||||
./entity/entity_agency_model.gd
|
||||
./entity/floor_tile_wood/entity.tscn
|
||||
./entity/floor_tile_wood/icon.png
|
||||
./entity/floor_tile_wood/data.gd
|
||||
./entity/qr_code/entity.tscn
|
||||
./entity/qr_code/icon.png
|
||||
./entity/qr_code/data.gd
|
||||
./entity/background_sun/overlay.png
|
||||
./entity/background_sun/entity.tscn
|
||||
./entity/background_sun/c.gd
|
||||
./entity/background_sun/kill.tscn
|
||||
./entity/background_sun/icon.png
|
||||
./entity/background_sun/special.gd
|
||||
./entity/background_sun/wtf.tres
|
||||
./entity/background_sun/background/background2.png
|
||||
./entity/background_sun/background/background.tscn
|
||||
./entity/background_sun/background/color2s.tres
|
||||
./entity/background_sun/background/background_glow.png
|
||||
./entity/background_sun/data.gd
|
||||
./entity/background_sun/kill.gd
|
||||
./entity/background_sun/stars.png
|
||||
./entity/background_zone_intro/overlay.png
|
||||
./entity/background_zone_intro/entity.tscn
|
||||
./entity/background_zone_intro/icon.png
|
||||
./entity/background_zone_intro/special.gd
|
||||
./entity/background_zone_intro/background/space.png
|
||||
./entity/background_zone_intro/background/line.png
|
||||
./entity/background_zone_intro/background/background2.png
|
||||
./entity/background_zone_intro/background/background.png
|
||||
./entity/background_zone_intro/background/engine_glow.tscn
|
||||
./entity/background_zone_intro/background/lines3.png
|
||||
./entity/background_zone_intro/background/background.tscn
|
||||
./entity/background_zone_intro/background/lines.tres
|
||||
./entity/background_zone_intro/background/background.gd
|
||||
./entity/background_zone_intro/background/bayer16tile2.png
|
||||
./entity/background_zone_intro/background/push.png
|
||||
./entity/background_zone_intro/background/palette_mono.png
|
||||
./entity/background_zone_intro/background/stars.gd
|
||||
./entity/background_zone_intro/background/lines2.png
|
||||
./entity/background_zone_intro/background/lines.shader
|
||||
./entity/background_zone_intro/background/ambience.gd
|
||||
./entity/background_zone_intro/background/space_ship_ambience.ogg
|
||||
./entity/background_zone_intro/background/stars.png
|
||||
./entity/background_zone_intro/background_end.png
|
||||
./entity/background_zone_intro/data.gd
|
||||
./entity/background_zone_intro/tinge.png
|
||||
./entity/closet_alt/entity.tscn
|
||||
./entity/closet_alt/icon.png
|
||||
./entity/closet_alt/data.gd
|
||||
./entity/meta_random_sound/entity.tscn
|
||||
./entity/meta_random_sound/giberish.wav
|
||||
./entity/meta_random_sound/icon.png
|
||||
./entity/meta_random_sound/special.gd
|
||||
./entity/meta_random_sound/who.wav
|
||||
./entity/meta_random_sound/data.gd
|
||||
./entity/meta_random_sound/hoola_boola.wav
|
||||
./entity/meta_random_sound/space_bandit.wav
|
||||
./entity/lines/entity.tscn
|
||||
./entity/lines/icon.png
|
||||
./entity/lines/data.gd
|
||||
./entity/teleporter_random_avoid_ray/entity.tscn
|
||||
./entity/teleporter_random_avoid_ray/used.wav
|
||||
./entity/teleporter_random_avoid_ray/icon.png
|
||||
./entity/teleporter_random_avoid_ray/ray.gd
|
||||
./entity/teleporter_random_avoid_ray/data.gd
|
||||
./entity/teleporter_random_avoid_ray/flap.png
|
||||
./entity/teleporter_random_avoid_ray/RayCast2D.gd
|
||||
./entity/teleporter_random_avoid_ray/area.gd
|
||||
./entity/teleporter_random_avoid_ray/flap.gd
|
||||
./entity/saw/blades.gd
|
||||
./entity/saw/entity.tscn
|
||||
./entity/saw/used.wav
|
||||
./entity/saw/icon.png
|
||||
./entity/saw/special.gd
|
||||
./entity/saw/carried.png
|
||||
./entity/saw/data.gd
|
||||
./entity/saw/used (copy 1).wav
|
||||
./entity/saw/saw.wav
|
||||
./entity/saw/carried_blades.png
|
||||
./entity/floor_tile_checkerdboard/damage.png
|
||||
./entity/floor_tile_checkerdboard/entity.tscn
|
||||
./entity/floor_tile_checkerdboard/icon.png
|
||||
./entity/floor_tile_checkerdboard/entity.tres
|
||||
./entity/floor_tile_checkerdboard/data.gd
|
||||
./entity/mutation_smoke_grenade_upgrade/entity.tscn
|
||||
./entity/mutation_smoke_grenade_upgrade/icon.png
|
||||
./entity/mutation_smoke_grenade_upgrade/special.gd
|
||||
./entity/mutation_smoke_grenade_upgrade/data.gd
|
||||
./entity/mutation_smoke_grenade_upgrade/mutation_model.gd
|
||||
./entity/helmet_full/entity.tscn
|
||||
./entity/helmet_full/pick_up.wav
|
||||
./entity/helmet_full/icon.png
|
||||
./entity/helmet_full/data.gd
|
||||
./entity/helmet_full/helmet-ping.wav
|
||||
./entity/barrel_explosive/entity.tscn
|
||||
./entity/barrel_explosive/icon.png
|
||||
./entity/barrel_explosive/data.gd
|
||||
./entity/bank/entity.tscn
|
||||
./entity/bank/icon.png
|
||||
./entity/bank/special.gd
|
||||
./entity/bank/data.gd
|
||||
./entity/kick/entity.tscn
|
||||
./entity/kick/swipe.png
|
||||
./entity/kick/used.wav
|
||||
./entity/kick/icon.png
|
||||
./entity/kick/AnimatedSprite.gd
|
||||
./entity/kick/data.gd
|
||||
./entity/battery/entity.tscn
|
||||
./entity/battery/icon.png
|
||||
./entity/battery/data.gd
|
||||
./entity/lift/entity.tscn
|
||||
./entity/lift/opening.wav
|
||||
./entity/lift/doors_open.png
|
||||
./entity/lift/RichTextLabel.gd
|
||||
./entity/lift/icon.png
|
||||
./entity/lift/open.wav
|
||||
./entity/lift/elevator_end.wav
|
||||
./entity/lift/lift_model.gd
|
||||
./entity/lift/label.tscn
|
||||
./entity/lift/rumble.gd
|
||||
./entity/lift/level_portal_model.gd
|
||||
./entity/lift/data.gd
|
||||
./entity/lift/doors.png
|
||||
./entity/lift/area.gd
|
||||
./entity/snes/entity.tscn
|
||||
./entity/snes/icon.png
|
||||
./entity/snes/data.gd
|
||||
./entity/passive_disarm/entity.tscn
|
||||
./entity/passive_disarm/icon.png
|
||||
./entity/passive_disarm/special.gd
|
||||
./entity/passive_disarm/data.gd
|
||||
./entity/mutation_lots_of_shot/entity.tscn
|
||||
./entity/mutation_lots_of_shot/icon.png
|
||||
./entity/mutation_lots_of_shot/special.gd
|
||||
./entity/mutation_lots_of_shot/data.gd
|
||||
./entity/pallet2/entity.tscn
|
||||
./entity/pallet2/icon.png
|
||||
./entity/pallet2/data.gd
|
||||
./entity/kill_streak_sword/entity.tscn
|
||||
./entity/kill_streak_sword/data.gd
|
||||
./entity/rain/entity.tscn
|
||||
./entity/rain/icon.png
|
||||
./entity/rain/special.gd
|
||||
./entity/rain/rain.png
|
||||
./entity/rain/rain.tscn
|
||||
./entity/rain/data.gd
|
||||
./entity/rain/rain.gd
|
||||
./entity/white_line/entity.tscn
|
||||
./entity/white_line/icon.png
|
||||
./entity/white_line/data.gd
|
||||
./entity/game_break_sword/entity.tscn
|
||||
./entity/game_break_sword/icon.png
|
||||
./entity/game_break_sword/special.gd
|
||||
./entity/game_break_sword/data.gd
|
||||
./entity/background_zone1/overlay.png
|
||||
./entity/background_zone1/entity.tscn
|
||||
./entity/background_zone1/icon.png
|
||||
./entity/background_zone1/special.gd
|
||||
./entity/background_zone1/background/space.png
|
||||
./entity/background_zone1/background/line.png
|
||||
./entity/background_zone1/background/background2.png
|
||||
./entity/background_zone1/background/background.png
|
||||
./entity/background_zone1/background/engine_glow.tscn
|
||||
./entity/background_zone1/background/lines3.png
|
||||
./entity/background_zone1/background/background.tscn
|
||||
./entity/background_zone1/background/lines.tres
|
||||
./entity/background_zone1/background/background.gd
|
||||
./entity/background_zone1/background/bayer16tile2.png
|
||||
./entity/background_zone1/background/push.png
|
||||
./entity/background_zone1/background/palette_mono.png
|
||||
./entity/background_zone1/background/stars.gd
|
||||
./entity/background_zone1/background/lines2.png
|
||||
./entity/background_zone1/background/lines.shader
|
||||
./entity/background_zone1/background/ambience.gd
|
||||
./entity/background_zone1/background/space_ship_ambience.ogg
|
||||
./entity/background_zone1/background/stars.png
|
||||
./entity/background_zone1/data.gd
|
||||
./entity/background_zone1/tinge.png
|
||||
./entity/mutation_throw_trap_DELETE/entity.tscn
|
||||
./entity/mutation_throw_trap_DELETE/icon.png
|
||||
./entity/mutation_throw_trap_DELETE/special.gd
|
||||
./entity/mutation_throw_trap_DELETE/data.gd
|
||||
./entity/agency.gd
|
||||
./entity/skin_cheese/entity.tscn
|
||||
./entity/skin_cheese/icon.png
|
||||
./entity/skin_cheese/carried.png
|
||||
./entity/skin_cheese/data.gd
|
||||
./entity/toilet/entity.tscn
|
||||
./entity/toilet/icon.png
|
||||
./entity/toilet/special.gd
|
||||
./entity/toilet/water.png
|
||||
./entity/toilet/drink.wav
|
||||
./entity/toilet/data.gd
|
||||
./entity/smg3/entity.tscn
|
||||
./entity/smg3/used.wav
|
||||
./entity/smg3/icon.png
|
||||
./entity/smg3/dead.png
|
||||
./entity/smg3/data.gd
|
||||
./entity/smg3/debug.gd
|
||||
./entity/teleporter_super/entity.tscn
|
||||
./entity/teleporter_super/icon.png
|
||||
./entity/teleporter_super/data.gd
|
||||
./entity/background_zone_end/overlay.png
|
||||
./entity/background_zone_end/entity.tscn
|
||||
./entity/background_zone_end/icon.png
|
||||
./entity/background_zone_end/special.gd
|
||||
./entity/background_zone_end/stars2.png
|
||||
./entity/background_zone_end/background_end.png
|
||||
./entity/background_zone_end/data.gd
|
||||
./entity/background_zone_end/tinge.png
|
||||
./entity/kill_streak_barricade/entity.tscn
|
||||
./entity/kill_streak_barricade/data.gd
|
||||
./entity/game_zone_4_boss_1/entity.tscn
|
||||
./entity/game_zone_4_boss_1/icon.png
|
||||
./entity/game_zone_4_boss_1/special.gd
|
||||
./entity/game_zone_4_boss_1/data.gd
|
||||
./entity/game_zone_4_boss_1/kill_me_and_explode_ship.wav
|
||||
./entity/mutation_remove_melee/entity.tscn
|
||||
./entity/mutation_remove_melee/icon.png
|
||||
./entity/mutation_remove_melee/special.gd
|
||||
./entity/mutation_remove_melee/data.gd
|
||||
./entity/he_grenade_level_2/entity.tscn
|
||||
./entity/he_grenade_level_2/icon.png
|
||||
./entity/he_grenade_level_2/data.gd
|
||||
./entity/background_zone_2/entity.tscn
|
||||
./entity/background_zone_2/icon.png
|
||||
./entity/background_zone_2/background/background2.kra
|
||||
./entity/background_zone_2/background/grad.png
|
||||
./entity/background_zone_2/background/background2.png
|
||||
./entity/background_zone_2/background/background.png
|
||||
./entity/background_zone_2/background/background2 (copy 1).png
|
||||
./entity/background_zone_2/background/backgrounds.gd
|
||||
./entity/background_zone_2/background/wall_overlay.png
|
||||
./entity/background_zone_2/background/background.tscn
|
||||
./entity/background_zone_2/background/Screenshot from 2022-07-07 10-58-48.png
|
||||
./entity/background_zone_2/background/background.gd
|
||||
./entity/background_zone_2/background/shadow.png
|
||||
./entity/background_zone_2/background/engine smoke.png
|
||||
./entity/background_zone_2/background/background.kra
|
||||
./entity/background_zone_2/background/sea.ogg
|
||||
./entity/background_zone_2/background/background2blur.png
|
||||
./entity/background_zone_2/background/test.gd
|
||||
./entity/background_zone_2/background/grad3.png
|
||||
./entity/background_zone_2/background/lines2.png
|
||||
./entity/background_zone_2/background/smoke.tscn
|
||||
./entity/background_zone_2/background/left_water.tscn
|
||||
./entity/background_zone_2/background/grad2.png
|
||||
./entity/background_zone_2/background/para.png
|
||||
./entity/background_zone_2/data.gd
|
||||
./entity/pipe_corner/entity.tscn
|
||||
./entity/pipe_corner/icon.png
|
||||
./entity/pipe_corner/data.gd
|
||||
./entity/floor_tile_metal_cow_trap/entity.tscn
|
||||
./entity/floor_tile_metal_cow_trap/icon.png
|
||||
./entity/floor_tile_metal_cow_trap/data.gd
|
||||
./entity/skin_naked/entity.tscn
|
||||
./entity/skin_naked/icon.png
|
||||
./entity/skin_naked/carried.png
|
||||
./entity/skin_naked/data.gd
|
||||
./entity/valve/entity.tscn
|
||||
./entity/valve/icon.png
|
||||
./entity/valve/.icon.png-autosave.kra
|
||||
./entity/valve/data.gd
|
||||
./entity/bed/entity.tscn
|
||||
./entity/bed/icon.png
|
||||
./entity/bed/data.gd
|
||||
./entity/game_invisible_guy/entity.tscn
|
||||
./entity/game_invisible_guy/icon.png
|
||||
./entity/game_invisible_guy/special.gd
|
||||
./entity/game_invisible_guy/data.gd
|
||||
./entity/smg/entity.tscn
|
||||
./entity/smg/used.wav
|
||||
./entity/smg/icon.png
|
||||
./entity/smg/data.gd
|
||||
./entity/skin_robo/entity.tscn
|
||||
./entity/skin_robo/icon.png
|
||||
./entity/skin_robo/carried.png
|
||||
./entity/skin_robo/data.gd
|
||||
./entity/bandana/entity.tscn
|
||||
./entity/bandana/bob.gd
|
||||
./entity/bandana/icon.png
|
||||
./entity/bandana/special.gd
|
||||
./entity/bandana/carried.png
|
||||
./entity/bandana/data.gd
|
||||
./entity/bandana/pixel.png
|
||||
./entity/floor_plug/entity.tscn
|
||||
./entity/floor_plug/icon.png
|
||||
./entity/floor_plug/data.gd
|
||||
./entity/bench/entity.tscn
|
||||
./entity/bench/icon.png
|
||||
./entity/bench/data.gd
|
||||
./entity/meta_strip_items/entity.tscn
|
||||
./entity/meta_strip_items/special.gd
|
||||
./entity/meta_strip_items/meta_strip_items_model.gd
|
||||
./entity/meta_strip_items/data.gd
|
||||
./entity/crate_teleporter/entity.tscn
|
||||
./entity/crate_teleporter/icon.png
|
||||
./entity/crate_teleporter/data.gd
|
||||
./entity/crate_teleporter/satellite.kra
|
||||
./entity/crate_garbage/entity.tscn
|
||||
./entity/crate_garbage/icon.png
|
||||
./entity/crate_garbage/data.gd
|
||||
./entity/crate_garbage/gibbed.png
|
||||
./entity/meta_stats/entity.tscn
|
||||
./entity/meta_stats/letters.tres
|
||||
./entity/meta_stats/icon.png
|
||||
./entity/meta_stats/special.gd
|
||||
./entity/meta_stats/data.gd
|
||||
./entity/meta_stats/meta_stats_model.gd
|
||||
./entity/rail_gun/entity.tscn
|
||||
./entity/rail_gun/used.wav
|
||||
./entity/rail_gun/icon.png
|
||||
./entity/rail_gun/special.gd
|
||||
./entity/rail_gun/carried.png
|
||||
./entity/rail_gun/data.gd
|
||||
./entity/drop_ship_door/entity.tscn
|
||||
./entity/drop_ship_door/icon.png
|
||||
./entity/drop_ship_door/data.gd
|
||||
./entity/floor_lines/entity.tscn
|
||||
./entity/floor_lines/icon.png
|
||||
./entity/floor_lines/data.gd
|
||||
./entity/game_trap/entity.tscn
|
||||
./entity/game_trap/you_blew_up_my_force_field.wav
|
||||
./entity/game_trap/droped_my_grenade_2.wav
|
||||
./entity/game_trap/icon.png
|
||||
./entity/game_trap/special.gd
|
||||
./entity/game_trap/droped_my_grenade_0.wav
|
||||
./entity/game_trap/shock.wav
|
||||
./entity/game_trap/uh_my_helmet.wav
|
||||
./entity/game_trap/ha_missed_me.wav
|
||||
./entity/game_trap/data.gd
|
||||
./entity/game_trap/try_beat_this_force_field.wav
|
||||
./entity/game_trap/droped_my_grenade_1.wav
|
||||
./entity/blood_sword/entity.tscn
|
||||
./entity/blood_sword/pick_up.wav
|
||||
./entity/blood_sword/used.wav
|
||||
./entity/blood_sword/sam2.png
|
||||
./entity/blood_sword/icon.png
|
||||
./entity/blood_sword/special.gd
|
||||
./entity/blood_sword/hit_bar.gd
|
||||
./entity/blood_sword/data.gd
|
||||
./entity/blood_sword/sam.png
|
||||
./entity/blood_sword/dead.wav
|
||||
./entity/blood_sword/animation.png
|
||||
./entity/auto_cables_thick/entity.tscn
|
||||
./entity/auto_cables_thick/data.gd
|
||||
./entity/auto_cables_thick/wires2.png
|
||||
./entity/shield/entity.tscn
|
||||
./entity/shield/pick_up.wav
|
||||
./entity/shield/icon.png
|
||||
./entity/shield/carried.png
|
||||
./entity/shield/data.gd
|
||||
./entity/shield/helmet-ping.wav
|
||||
./entity/game_teleport_in/entity.tscn
|
||||
./entity/game_teleport_in/icon.png
|
||||
./entity/game_teleport_in/special.gd
|
||||
./entity/game_teleport_in/data.gd
|
||||
./entity/shotgun_super/entity.tscn
|
||||
./entity/shotgun_super/icon.png
|
||||
./entity/shotgun_super/data.gd
|
||||
./entity/bottle/entity.tscn
|
||||
./entity/bottle/icon.png
|
||||
./entity/bottle/data.gd
|
||||
./entity/bottle/normal.png
|
||||
./entity/bottle/icon_shadow.png
|
||||
./entity/kill_streak_p90/entity.tscn
|
||||
./entity/kill_streak_p90/data.gd
|
||||
./entity/drain/entity.tscn
|
||||
./entity/drain/icon.png
|
||||
./entity/drain/data.gd
|
||||
./entity/auto_wires_three/entity.tscn
|
||||
./entity/auto_wires_three/data.gd
|
||||
./entity/light/entity.tscn
|
||||
./entity/light/icon.png
|
||||
./entity/light/special.gd
|
||||
./entity/light/light.wav
|
||||
./entity/light/data.gd
|
||||
./entity/debris/entity.tscn
|
||||
./entity/debris/icon.png
|
||||
./entity/debris/data.gd
|
||||
./entity/debris/gibbed.png
|
||||
./entity/mutation_rail_gun_upgrade/entity.tscn
|
||||
./entity/mutation_rail_gun_upgrade/icon.png
|
||||
./entity/mutation_rail_gun_upgrade/special.gd
|
||||
./entity/mutation_rail_gun_upgrade/data.gd
|
||||
./entity/mutation_rail_gun_upgrade/mutation_model.gd
|
||||
./entity/auto_cables/entity.tscn
|
||||
./entity/auto_cables/data.gd
|
||||
./entity/auto_cables/wires2.png
|
||||
./entity/stealth_camo/entity.tscn
|
||||
./entity/stealth_camo/special.gd
|
||||
./entity/stealth_camo/data.gd
|
||||
./entity/colt_45/entity.tscn
|
||||
./entity/colt_45/used.wav
|
||||
./entity/colt_45/icon.png
|
||||
./entity/colt_45/dead.png
|
||||
./entity/colt_45/data.gd
|
||||
./entity/quantum_suicide_drive/entity.tscn
|
||||
./entity/quantum_suicide_drive/heart.ogg
|
||||
./entity/quantum_suicide_drive/icon.png
|
||||
./entity/quantum_suicide_drive/special.gd
|
||||
./entity/quantum_suicide_drive/qsd_model.gd
|
||||
./entity/quantum_suicide_drive/multi.gd
|
||||
./entity/quantum_suicide_drive/multi.tscn
|
||||
./entity/quantum_suicide_drive/CenterContainer.gd
|
||||
./entity/quantum_suicide_drive/carried.png
|
||||
./entity/quantum_suicide_drive/data.gd
|
||||
./entity/helmet/entity.tscn
|
||||
./entity/helmet/pick_up.wav
|
||||
./entity/helmet/icon.png
|
||||
./entity/helmet/special.gd
|
||||
./entity/helmet/die.wav
|
||||
./entity/helmet/carried.png
|
||||
./entity/helmet/data.gd
|
||||
./entity/helmet/helmet-ping.wav
|
||||
./entity/ammo_box/entity.tscn
|
||||
./entity/ammo_box/icon.png
|
||||
./entity/ammo_box/data.gd
|
||||
./entity/rail_gun_level_2/entity.tscn
|
||||
./entity/rail_gun_level_2/icon.png
|
||||
./entity/rail_gun_level_2/data.gd
|
||||
./entity/glass_block_backup/entity.tscn
|
||||
./entity/glass_block_backup/icon.png
|
||||
./entity/glass_block_backup/data.gd
|
||||
./entity/closet/entity.tscn
|
||||
./entity/closet/icon.png
|
||||
./entity/closet/data.gd
|
||||
./entity/little_boxes/entity.tscn
|
||||
./entity/little_boxes/icon.png
|
||||
./entity/little_boxes/data.gd
|
||||
./entity/meta_health_bar/entity.tscn
|
||||
./entity/meta_health_bar/health_bar_model.gd
|
||||
./entity/meta_health_bar/icon.png
|
||||
./entity/meta_health_bar/special.gd
|
||||
./entity/meta_health_bar/invunerable.png
|
||||
./entity/meta_health_bar/data.gd
|
||||
./entity/night_stand/entity.tscn
|
||||
./entity/night_stand/icon_normal.png
|
||||
./entity/night_stand/icon.png
|
||||
./entity/night_stand/shadow.png
|
||||
./entity/night_stand/data.gd
|
||||
./entity/fan/entity.tscn
|
||||
./entity/fan/flap2.png
|
||||
./entity/fan/flaps.gd
|
||||
./entity/fan/icon.png
|
||||
./entity/fan/data.gd
|
||||
./entity/fan/flap.png
|
||||
./entity/fan/icon_shadow.png
|
||||
./entity/fan/animation.png
|
||||
./entity/fan/gibbed.png
|
||||
./entity/game_tutorial_end/entity.tscn
|
||||
./entity/game_tutorial_end/icon.png
|
||||
./entity/game_tutorial_end/special.gd
|
||||
./entity/game_tutorial_end/data.gd
|
||||
./entity/mutation_disarmament/entity.tscn
|
||||
./entity/mutation_disarmament/icon.png
|
||||
./entity/mutation_disarmament/special.gd
|
||||
./entity/mutation_disarmament/data.gd
|
||||
./entity/air_lock/icon_open.png
|
||||
./entity/air_lock/entity.tscn
|
||||
./entity/air_lock/door_close.wav
|
||||
./entity/air_lock/icon.png
|
||||
./entity/air_lock/special.gd
|
||||
./entity/air_lock/air_lock_model.gd
|
||||
./entity/air_lock/data.gd
|
||||
./entity/scorpion/entity.tscn
|
||||
./entity/scorpion/used.wav
|
||||
./entity/scorpion/laser.gd
|
||||
./entity/scorpion/icon.png
|
||||
./entity/scorpion/data.gd
|
||||
./entity/kill_streak_aim_hack/entity.tscn
|
||||
./entity/kill_streak_aim_hack/data.gd
|
||||
./entity/dungeon_proc_debug/entity.tscn
|
||||
./entity/dungeon_proc_debug/icon.png
|
||||
./entity/dungeon_proc_debug/data.gd
|
||||
./entity/dungeon_proc_debug/debug.gd
|
||||
./entity/dungeon_proc_debug/debug.tscn
|
||||
./entity/tarp/entity.tscn
|
||||
./entity/tarp/icon.png
|
||||
./entity/tarp/data.gd
|
||||
./entity/hit_indicator/entity.tscn
|
||||
./entity/hit_indicator/data.gd
|
||||
./entity/console_corner/entity.tscn
|
||||
./entity/console_corner/animation2.tscn
|
||||
./entity/console_corner/icon.png
|
||||
./entity/console_corner/data.gd
|
||||
./entity/console_corner/animation.tscn
|
||||
./entity/icon.png
|
||||
./entity/couch_corner/entity.tscn
|
||||
./entity/couch_corner/icon.png
|
||||
./entity/couch_corner/data.gd
|
||||
./entity/m4/entity.tscn
|
||||
./entity/m4/used.wav
|
||||
./entity/m4/icon.png
|
||||
./entity/m4/data.gd
|
||||
./entity/game_hud/entity.tscn
|
||||
./entity/game_hud/icon.png
|
||||
./entity/game_hud/data.gd
|
||||
./entity/game_hud/inventory_game.tscn
|
||||
./entity/prototypes.gd
|
||||
./entity/agent_chicken/emotes.png
|
||||
./entity/agent_chicken/entity.tscn
|
||||
./entity/agent_chicken/sound_board.gd
|
||||
./entity/agent_chicken/bones.tscn
|
||||
./entity/agent_chicken/bones.gd
|
||||
./entity/agent_chicken/barks.gd
|
||||
./entity/agent_chicken/emote.gd
|
||||
./entity/agent_chicken/icon.png
|
||||
./entity/agent_chicken/special.gd
|
||||
./entity/agent_chicken/bark.gd
|
||||
./entity/agent_chicken/deaad.png
|
||||
./entity/agent_chicken/icon.gd
|
||||
./entity/agent_chicken/data.gd
|
||||
./entity/agent_chicken/animation.tscn
|
||||
./entity/agent_chicken/emote.tscn
|
||||
./entity/agent_chicken/hand.png
|
||||
./entity/velocity/entity.tscn
|
||||
./entity/velocity/icon.png
|
||||
./entity/velocity/special.gd
|
||||
./entity/velocity/data.gd
|
||||
./entity/aircon/entity.tscn
|
||||
./entity/aircon/grate.png
|
||||
./entity/aircon/icon.png
|
||||
./entity/aircon/data.gd
|
||||
./entity/aircon/animation.png
|
||||
./entity/floor_tile_bricks/entity.tscn
|
||||
./entity/floor_tile_bricks/icon.png
|
||||
./entity/floor_tile_bricks/data.gd
|
||||
./entity/pallet/entity.tscn
|
||||
./entity/pallet/icon.png
|
||||
./entity/pallet/data.gd
|
||||
./entity/barricade_deployed/debug.png
|
||||
./entity/barricade_deployed/field.tscn
|
||||
./entity/barricade_deployed/entity.tscn
|
||||
./entity/barricade_deployed/ambience.ogg
|
||||
./entity/barricade_deployed/icon.png
|
||||
./entity/barricade_deployed/field.gd
|
||||
./entity/barricade_deployed/field_material.tres
|
||||
./entity/barricade_deployed/debug2.png
|
||||
./entity/barricade_deployed/data.gd
|
||||
./entity/barricade_deployed/field_material_invert.tres
|
||||
./entity/barricade_deployed/field_material.gd
|
||||
./entity/barricade_deployed/gibbed.png
|
||||
./entity/helmet_nv/entity.tscn
|
||||
./entity/helmet_nv/pick_up.wav
|
||||
./entity/helmet_nv/icon.png
|
||||
./entity/helmet_nv/special.gd
|
||||
./entity/helmet_nv/carried.png
|
||||
./entity/helmet_nv/eyes.png
|
||||
./entity/helmet_nv/data.gd
|
||||
./entity/helmet_nv/helmet-ping.wav
|
||||
./entity/helmet_nv/eyes.gd
|
||||
./entity/mutation_sword/entity.tscn
|
||||
./entity/mutation_sword/icon.png
|
||||
./entity/mutation_sword/special.gd
|
||||
./entity/mutation_sword/data.gd
|
||||
./entity/field_full_super/entity.tscn
|
||||
./entity/field_full_super/icon.png
|
||||
./entity/field_full_super/special.gd
|
||||
./entity/field_full_super/carried.png
|
||||
./entity/field_full_super/data.gd
|
||||
./entity/entity_man.gd
|
||||
./entity/couch/entity.tscn
|
||||
./entity/couch/icon.png
|
||||
./entity/couch/data.gd
|
||||
./entity/teleporter_lil_hunter/entity.tscn
|
||||
./entity/teleporter_lil_hunter/icon.png
|
||||
./entity/teleporter_lil_hunter/tubes.png
|
||||
./entity/teleporter_lil_hunter/osc_shader.tres
|
||||
./entity/teleporter_lil_hunter/eyes.png
|
||||
./entity/teleporter_lil_hunter/data.gd
|
||||
./entity/teleporter_lil_hunter/osc.tres
|
||||
./entity/game_tutorial_melee_zone/entity.tscn
|
||||
./entity/game_tutorial_melee_zone/icon.png
|
||||
./entity/game_tutorial_melee_zone/special.gd
|
||||
./entity/game_tutorial_melee_zone/data.gd
|
||||
./entity/kill_streak_glock/entity.tscn
|
||||
./entity/kill_streak_glock/data.gd
|
||||
./entity/skin_mime/entity.tscn
|
||||
./entity/skin_mime/icon.png
|
||||
./entity/skin_mime/special.gd
|
||||
./entity/skin_mime/carried.png
|
||||
./entity/skin_mime/data.gd
|
||||
./entity/medpack_hard/entity.tscn
|
||||
./entity/medpack_hard/icon.png
|
||||
./entity/medpack_hard/data.gd
|
||||
./entity/teleporter_overload/entity.tscn
|
||||
./entity/teleporter_overload/icon.png
|
||||
./entity/teleporter_overload/special.gd
|
||||
./entity/teleporter_overload/carried.png
|
||||
./entity/teleporter_overload/data.gd
|
||||
./entity/background_freighter/overlay.png
|
||||
./entity/background_freighter/entity.tscn
|
||||
./entity/background_freighter/icon.png
|
||||
./entity/background_freighter/Master.ogg
|
||||
./entity/background_freighter/background/space.png
|
||||
./entity/background_freighter/background/line.png
|
||||
./entity/background_freighter/background/background2.gd
|
||||
./entity/background_freighter/background/good create.png
|
||||
./entity/background_freighter/background/backgip.png
|
||||
./entity/background_freighter/background/background2.png
|
||||
./entity/background_freighter/background/background.png
|
||||
./entity/background_freighter/background/engine_glow.tscn
|
||||
./entity/background_freighter/background/gra2d.png
|
||||
./entity/background_freighter/background/lines3.png
|
||||
./entity/background_freighter/background/background.tscn
|
||||
./entity/background_freighter/background/lines.tres
|
||||
./entity/background_freighter/background/background.gd
|
||||
./entity/background_freighter/background/bayer16tile2.png
|
||||
./entity/background_freighter/background/goodcrate.png
|
||||
./entity/background_freighter/background/push.png
|
||||
./entity/background_freighter/background/background_floor.png
|
||||
./entity/background_freighter/background/palette_mono.png
|
||||
./entity/background_freighter/background/stars.gd
|
||||
./entity/background_freighter/background/lines2.png
|
||||
./entity/background_freighter/background/lines.shader
|
||||
./entity/background_freighter/background/ambience.gd
|
||||
./entity/background_freighter/background/bacsdas.png
|
||||
./entity/background_freighter/background/space_ship_ambience.ogg
|
||||
./entity/background_freighter/background/stars.png
|
||||
./entity/background_freighter/data.gd
|
||||
./entity/auto_wires/entity.tscn
|
||||
./entity/auto_wires/data.gd
|
||||
./entity/kill_streak/entity.tscn
|
||||
./entity/kill_streak/kill_streak_toast.tscn
|
||||
./entity/kill_streak/icon.png
|
||||
1
tests/data/half_precision_floating_point_big_endian.bin
Normal file
@@ -0,0 +1 @@
|
||||
5U
|
||||
@@ -0,0 +1 @@
|
||||
U5
|
||||
1
tests/data/images/embedded_jpg.svg
Normal file
|
After Width: | Height: | Size: 20 KiB |
BIN
tests/data/images/grayscale.jpg
Normal file
|
After Width: | Height: | Size: 216 B |
BIN
tests/data/images/icon.bmp
Normal file
|
After Width: | Height: | Size: 256 KiB |
BIN
tests/data/images/icon.jpg
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
tests/data/images/icon.png
Normal file
|
After Width: | Height: | Size: 29 KiB |
BIN
tests/data/images/icon.tga
Normal file
|
After Width: | Height: | Size: 256 KiB |
BIN
tests/data/images/icon.webp
Normal file
|
After Width: | Height: | Size: 4.4 KiB |
1
tests/data/line_endings_cr.test.txt
Normal file
@@ -0,0 +1 @@
|
||||
Hello darkness
|
||||
4
tests/data/line_endings_crlf.test.txt
Normal file
@@ -0,0 +1,4 @@
|
||||
Hello darkness
|
||||
My old friend
|
||||
I've come to talk
|
||||
With you again
|
||||
4
tests/data/line_endings_lf.test.txt
Normal file
@@ -0,0 +1,4 @@
|
||||
Hello darkness
|
||||
My old friend
|
||||
I've come to talk
|
||||
With you again
|
||||