summaryrefslogtreecommitdiffstats
path: root/tests/core
diff options
context:
space:
mode:
Diffstat (limited to 'tests/core')
-rw-r--r--tests/core/io/test_ip.h51
-rw-r--r--tests/core/io/test_marshalls.h78
-rw-r--r--tests/core/io/test_resource.h5
-rw-r--r--tests/core/math/test_geometry_2d.h94
-rw-r--r--tests/core/math/test_geometry_3d.h6
-rw-r--r--tests/core/math/test_transform_3d.h29
-rw-r--r--tests/core/math/test_vector2.h4
-rw-r--r--tests/core/object/test_object.h6
-rw-r--r--tests/core/object/test_undo_redo.h202
-rw-r--r--tests/core/os/test_os.h26
-rw-r--r--tests/core/string/test_node_path.h53
-rw-r--r--tests/core/string/test_string.h73
-rw-r--r--tests/core/templates/test_command_queue.h44
-rw-r--r--tests/core/templates/test_oa_hash_map.h225
-rw-r--r--tests/core/threads/test_worker_thread_pool.h67
-rw-r--r--tests/core/variant/test_array.h52
-rw-r--r--tests/core/variant/test_callable.h140
17 files changed, 1071 insertions, 84 deletions
diff --git a/tests/core/io/test_ip.h b/tests/core/io/test_ip.h
new file mode 100644
index 0000000000..7b5583faa0
--- /dev/null
+++ b/tests/core/io/test_ip.h
@@ -0,0 +1,51 @@
+/**************************************************************************/
+/* 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. */
+/**************************************************************************/
+
+#ifndef TEST_IP_H
+#define TEST_IP_H
+
+#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
+
+#endif // TEST_IP_H
diff --git a/tests/core/io/test_marshalls.h b/tests/core/io/test_marshalls.h
index 3c0ba611c6..de8d6e1406 100644
--- a/tests/core/io/test_marshalls.h
+++ b/tests/core/io/test_marshalls.h
@@ -160,7 +160,7 @@ TEST_CASE("[Marshalls] NIL Variant encoding") {
uint8_t buffer[4];
CHECK(encode_variant(variant, buffer, r_len) == OK);
- CHECK_MESSAGE(r_len == 4, "Length == 4 bytes for Variant::Type");
+ 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);
@@ -174,7 +174,7 @@ TEST_CASE("[Marshalls] INT 32 bit Variant encoding") {
uint8_t buffer[8];
CHECK(encode_variant(variant, buffer, r_len) == OK);
- CHECK_MESSAGE(r_len == 8, "Length == 4 bytes for Variant::Type + 4 bytes for int32_t");
+ 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);
@@ -192,10 +192,10 @@ TEST_CASE("[Marshalls] INT 64 bit Variant encoding") {
uint8_t buffer[12];
CHECK(encode_variant(variant, buffer, r_len) == OK);
- CHECK_MESSAGE(r_len == 12, "Length == 4 bytes for Variant::Type + 8 bytes for int64_t");
+ 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, "ENCODE_FLAG_64");
+ CHECK_MESSAGE(buffer[2] == 0x01, "HEADER_DATA_FLAG_64");
CHECK(buffer[3] == 0x00);
// Check value
CHECK(buffer[4] == 0xef);
@@ -214,7 +214,7 @@ TEST_CASE("[Marshalls] FLOAT single precision Variant encoding") {
uint8_t buffer[8];
CHECK(encode_variant(variant, buffer, r_len) == OK);
- CHECK_MESSAGE(r_len == 8, "Length == 4 bytes for Variant::Type + 4 bytes for float");
+ 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);
@@ -232,10 +232,10 @@ TEST_CASE("[Marshalls] FLOAT double precision Variant encoding") {
uint8_t buffer[12];
CHECK(encode_variant(variant, buffer, r_len) == OK);
- CHECK_MESSAGE(r_len == 12, "Length == 4 bytes for Variant::Type + 8 bytes for double");
+ 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, "ENCODE_FLAG_64");
+ CHECK_MESSAGE(buffer[2] == 0x01, "HEADER_DATA_FLAG_64");
CHECK(buffer[3] == 0x00);
// Check value
CHECK(buffer[4] == 0x55);
@@ -292,7 +292,7 @@ TEST_CASE("[Marshalls] INT 64 bit Variant decoding") {
Variant variant;
int r_len;
uint8_t buffer[] = {
- 0x02, 0x00, 0x01, 0x00, // Variant::INT & ENCODE_FLAG_64
+ 0x02, 0x00, 0x01, 0x00, // Variant::INT, HEADER_DATA_FLAG_64
0xef, 0xcd, 0xab, 0x89, 0x67, 0x45, 0x23, 0xf1 // value
};
@@ -318,7 +318,7 @@ TEST_CASE("[Marshalls] FLOAT double precision Variant decoding") {
Variant variant;
int r_len;
uint8_t buffer[] = {
- 0x03, 0x00, 0x01, 0x00, // Variant::FLOAT & ENCODE_FLAG_64
+ 0x03, 0x00, 0x01, 0x00, // Variant::FLOAT, HEADER_DATA_FLAG_64
0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0xd5, 0x3f // value
};
@@ -326,6 +326,66 @@ TEST_CASE("[Marshalls] FLOAT double precision Variant decoding") {
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, "HEADER_DATA_FIELD_TYPED_ARRAY_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, HEADER_DATA_FIELD_TYPED_ARRAY_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)));
+}
+
} // namespace TestMarshalls
#endif // TEST_MARSHALLS_H
diff --git a/tests/core/io/test_resource.h b/tests/core/io/test_resource.h
index 9ddb51220b..a83e7f88ba 100644
--- a/tests/core/io/test_resource.h
+++ b/tests/core/io/test_resource.h
@@ -38,6 +38,8 @@
#include "thirdparty/doctest/doctest.h"
+#include "tests/test_macros.h"
+
namespace TestResource {
TEST_CASE("[Resource] Duplication") {
@@ -124,9 +126,12 @@ TEST_CASE("[Resource] Breaking circular references on save") {
const String save_path_binary = OS::get_singleton()->get_cache_path().path_join("resource.res");
const String save_path_text = OS::get_singleton()->get_cache_path().path_join("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.");
diff --git a/tests/core/math/test_geometry_2d.h b/tests/core/math/test_geometry_2d.h
index c3ff4f3ec9..a4bb6dfca0 100644
--- a/tests/core/math/test_geometry_2d.h
+++ b/tests/core/math/test_geometry_2d.h
@@ -282,41 +282,67 @@ TEST_CASE("[Geometry2D] Closest point to uncapped segment") {
TEST_CASE("[Geometry2D] Closest points between segments") {
Vector2 c1, c2;
- 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)));
+ // 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)));
+ }
- 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] 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)));
+ }
- 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)));
+ 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)));
+ }
- Geometry2D::get_closest_points_between_segments(Vector2(-3, 4), Vector2(-3, 4), Vector2(-4, 3), Vector2(-2, 3), c1, c2);
- CHECK_MESSAGE(
- c1.is_equal_approx(Vector2(-3, 4)),
- "1st line segment is only a point, this point should be the closest point to the 2nd line segment.");
- CHECK_MESSAGE(
- c2.is_equal_approx(Vector2(-3, 3)),
- "1st line segment is only a point, this should not matter when determining the closest point on the 2nd line segment.");
+ 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)));
+ }
- Geometry2D::get_closest_points_between_segments(Vector2(-4, 3), Vector2(-2, 3), Vector2(-3, 4), Vector2(-3, 4), c1, c2);
- CHECK_MESSAGE(
- c1.is_equal_approx(Vector2(-3, 3)),
- "2nd line segment is only a point, this should not matter when determining the closest point on the 1st line segment.");
- CHECK_MESSAGE(
- c2.is_equal_approx(Vector2(-3, 4)),
- "2nd line segment is only a point, this point should be the closest point to the 1st line segment.");
+ 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)));
+ }
- Geometry2D::get_closest_points_between_segments(Vector2(5, -4), Vector2(5, -4), Vector2(-2, 1), Vector2(-2, 1), c1, c2);
- CHECK_MESSAGE(
- c1.is_equal_approx(Vector2(5, -4)),
- "Both line segments are only a point. On the 1st line segment, that point should be the closest point to the 2nd line segment.");
- CHECK_MESSAGE(
- c2.is_equal_approx(Vector2(-2, 1)),
- "Both line segments are only a point. On the 2nd line segment, that point should be the closest point to the 1st line segment.");
+ 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") {
@@ -685,12 +711,12 @@ TEST_CASE("[Geometry2D] Clip polyline with polygon") {
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(160, 320)));
+ 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(121.412682, 225.038757)));
+ 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(53.07737, 116.143021)));
- CHECK(r[1][1].is_equal_approx(Vector2(55, 70)));
+ CHECK(r[1][0].is_equal_approx(Vector2(55, 70)));
+ CHECK(r[1][1].is_equal_approx(Vector2(53.07737, 116.143021)));
}
}
diff --git a/tests/core/math/test_geometry_3d.h b/tests/core/math/test_geometry_3d.h
index 3d83653f11..aaa02cb6a8 100644
--- a/tests/core/math/test_geometry_3d.h
+++ b/tests/core/math/test_geometry_3d.h
@@ -136,9 +136,9 @@ TEST_CASE("[Geometry3D] Get Closest Point To Segment") {
}
TEST_CASE("[Geometry3D] Plane and Box Overlap") {
- CHECK(Geometry3D::planeBoxOverlap(Vector3(3, 4, 2), 5, Vector3(5, 5, 5)) == true);
- CHECK(Geometry3D::planeBoxOverlap(Vector3(0, 1, 0), -10, Vector3(5, 5, 5)) == false);
- CHECK(Geometry3D::planeBoxOverlap(Vector3(1, 0, 0), -6, Vector3(5, 5, 5)) == false);
+ 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") {
diff --git a/tests/core/math/test_transform_3d.h b/tests/core/math/test_transform_3d.h
index 551b20fe74..fba0fcb280 100644
--- a/tests/core/math/test_transform_3d.h
+++ b/tests/core/math/test_transform_3d.h
@@ -107,6 +107,35 @@ TEST_CASE("[Transform3D] Finite number checks") {
"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
#endif // TEST_TRANSFORM_3D_H
diff --git a/tests/core/math/test_vector2.h b/tests/core/math/test_vector2.h
index f23fffe5eb..fc3fd6a87d 100644
--- a/tests/core/math/test_vector2.h
+++ b/tests/core/math/test_vector2.h
@@ -354,6 +354,7 @@ TEST_CASE("[Vector2] Plane methods") {
const Vector2 vector_y = Vector2(0, 1);
const Vector2 vector_normal = Vector2(0.95879811270838721622267, 0.2840883296913739899919);
const Vector2 vector_non_normal = Vector2(5.4, 1.6);
+ const 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.");
@@ -373,6 +374,9 @@ TEST_CASE("[Vector2] Plane methods") {
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(
diff --git a/tests/core/object/test_object.h b/tests/core/object/test_object.h
index e5d91db650..3a3013a102 100644
--- a/tests/core/object/test_object.h
+++ b/tests/core/object/test_object.h
@@ -95,6 +95,12 @@ public:
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();
}
diff --git a/tests/core/object/test_undo_redo.h b/tests/core/object/test_undo_redo.h
new file mode 100644
index 0000000000..ad3554b58c
--- /dev/null
+++ b/tests/core/object/test_undo_redo.h
@@ -0,0 +1,202 @@
+/**************************************************************************/
+/* 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. */
+/**************************************************************************/
+
+#ifndef TEST_UNDO_REDO_H
+#define TEST_UNDO_REDO_H
+
+#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
+
+#endif // TEST_UNDO_REDO_H
diff --git a/tests/core/os/test_os.h b/tests/core/os/test_os.h
index 1a5d360f57..63f8b18238 100644
--- a/tests/core/os/test_os.h
+++ b/tests/core/os/test_os.h
@@ -47,11 +47,33 @@ TEST_CASE("[OS] Environment variables") {
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");
+}
- OS::get_singleton()->set_environment("HELLO", "world");
+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(
- OS::get_singleton()->get_environment("HELLO") == "world",
+ 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") {
diff --git a/tests/core/string/test_node_path.h b/tests/core/string/test_node_path.h
index 031a33c570..bdbc578e85 100644
--- a/tests/core/string/test_node_path.h
+++ b/tests/core/string/test_node_path.h
@@ -167,6 +167,59 @@ TEST_CASE("[NodePath] Empty path") {
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");
+ 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(":prop"),
+ "Slicing on the length of the path should return the last entry.");
+ 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(":prop"),
+ "Slicing on -1 should return the last entry.");
+ CHECK_MESSAGE(
+ node_path_relative.slice(0, -1) == NodePath("Parent/Child"),
+ "Slicing up to -1 should include the second-to-last entry.");
+ CHECK_MESSAGE(
+ node_path_relative.slice(-2, -1) == NodePath("Child"),
+ "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"),
+ "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
#endif // TEST_NODE_PATH_H
diff --git a/tests/core/string/test_string.h b/tests/core/string/test_string.h
index 8d2607b874..64f03e5879 100644
--- a/tests/core/string/test_string.h
+++ b/tests/core/string/test_string.h
@@ -336,6 +336,16 @@ TEST_CASE("[String] Natural compare function test") {
CHECK(a.naturalnocasecmp_to("img10.png") < 0);
}
+TEST_CASE("[String] File compare function test") {
+ String a = "_img2.png";
+ String b = "img2.png";
+
+ CHECK(a.nocasecmp_to("img10.png") > 0);
+ CHECK_MESSAGE(a.filenocasecmp_to("img10.png") < 0, "Should sort before letters.");
+ CHECK_MESSAGE(a.filenocasecmp_to(".img10.png") > 0, "Should sort after period.");
+ CHECK(b.filenocasecmp_to("img3.png") < 0);
+}
+
TEST_CASE("[String] hex_encode_buffer") {
static const uint8_t u8str[] = { 0x45, 0xE3, 0x81, 0x8A, 0x8F, 0xE3 };
String s = String::hex_encode_buffer(u8str, 6);
@@ -642,48 +652,59 @@ struct test_27_data {
TEST_CASE("[String] Begins with") {
test_27_data tc[] = {
+ // Test cases for true:
{ "res://foobar", "res://", true },
+ { "abc", "abc", true },
+ { "abc", "", true },
+ { "", "", true },
+ // Test cases for false:
{ "res", "res://", false },
- { "abc", "abc", true }
+ { "abcdef", "foo", false },
+ { "abc", "ax", false },
+ { "", "abc", false }
};
size_t count = sizeof(tc) / sizeof(tc[0]);
bool state = true;
- for (size_t i = 0; state && i < count; ++i) {
+ for (size_t i = 0; i < count; ++i) {
String s = tc[i].data;
state = s.begins_with(tc[i].part) == tc[i].expected;
- if (state) {
- String sb = tc[i].part;
- state = s.begins_with(sb) == tc[i].expected;
- }
- CHECK(state);
- if (!state) {
- break;
- }
- };
- CHECK(state);
+ CHECK_MESSAGE(state, "first check failed at: ", i);
+
+ String sb = tc[i].part;
+ state = s.begins_with(sb) == tc[i].expected;
+ CHECK_MESSAGE(state, "second check failed at: ", i);
+ }
+
+ // Test "const char *" version also with nullptr.
+ String s("foo");
+ state = s.begins_with(nullptr) == false;
+ CHECK_MESSAGE(state, "nullptr check failed");
+
+ String empty("");
+ state = empty.begins_with(nullptr) == false;
+ CHECK_MESSAGE(state, "nullptr check with empty string failed");
}
TEST_CASE("[String] Ends with") {
test_27_data tc[] = {
+ // test cases for true:
{ "res://foobar", "foobar", true },
+ { "abc", "abc", true },
+ { "abc", "", true },
+ { "", "", true },
+ // test cases for false:
{ "res", "res://", false },
- { "abc", "abc", true }
+ { "", "abc", false },
+ { "abcdef", "foo", false },
+ { "abc", "xc", false }
};
size_t count = sizeof(tc) / sizeof(tc[0]);
- bool state = true;
- for (size_t i = 0; state && i < count; ++i) {
+ for (size_t i = 0; i < count; ++i) {
String s = tc[i].data;
- state = s.ends_with(tc[i].part) == tc[i].expected;
- if (state) {
- String sb = tc[i].part;
- state = s.ends_with(sb) == tc[i].expected;
- }
- CHECK(state);
- if (!state) {
- break;
- }
- };
- CHECK(state);
+ String sb = tc[i].part;
+ bool state = s.ends_with(sb) == tc[i].expected;
+ CHECK_MESSAGE(state, "check failed at: ", i);
+ }
}
TEST_CASE("[String] format") {
diff --git a/tests/core/templates/test_command_queue.h b/tests/core/templates/test_command_queue.h
index e94c108694..d2957b5c40 100644
--- a/tests/core/templates/test_command_queue.h
+++ b/tests/core/templates/test_command_queue.h
@@ -33,6 +33,7 @@
#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"
@@ -100,7 +101,7 @@ public:
ThreadWork reader_threadwork;
ThreadWork writer_threadwork;
- CommandQueueMT command_queue = CommandQueueMT(true);
+ CommandQueueMT command_queue;
enum TestMsgType {
TEST_MSG_FUNC1_TRANSFORM,
@@ -119,6 +120,7 @@ public:
bool exit_threads = false;
Thread reader_thread;
+ WorkerThreadPool::TaskID reader_task_id = WorkerThreadPool::INVALID_TASK_ID;
Thread writer_thread;
int func1_count = 0;
@@ -148,11 +150,16 @@ public:
void reader_thread_loop() {
reader_threadwork.thread_wait_for_work();
while (!exit_threads) {
- if (message_count_to_read < 0) {
+ if (reader_task_id == WorkerThreadPool::INVALID_TASK_ID) {
command_queue.flush_all();
- }
- for (int i = 0; i < message_count_to_read; i++) {
- command_queue.wait_and_flush();
+ } 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;
@@ -216,8 +223,13 @@ public:
sts->writer_thread_loop();
}
- void init_threads() {
- reader_thread.start(&SharedThreadState::static_reader_thread_loop, this);
+ 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() {
@@ -225,16 +237,20 @@ public:
reader_threadwork.main_start_work();
writer_threadwork.main_start_work();
- reader_thread.wait_to_finish();
+ 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();
}
};
-TEST_CASE("[CommandQueue] Test Queue Basics") {
+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();
+ sts.init_threads(p_use_thread_pool_sync);
sts.add_msg_to_write(SharedThreadState::TEST_MSG_FUNC1_TRANSFORM);
sts.writer_threadwork.main_start_work();
@@ -272,6 +288,14 @@ TEST_CASE("[CommandQueue] Test Queue Basics") {
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);
diff --git a/tests/core/templates/test_oa_hash_map.h b/tests/core/templates/test_oa_hash_map.h
new file mode 100644
index 0000000000..9359efa964
--- /dev/null
+++ b/tests/core/templates/test_oa_hash_map.h
@@ -0,0 +1,225 @@
+/**************************************************************************/
+/* test_oa_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. */
+/**************************************************************************/
+
+#ifndef TEST_OA_HASH_MAP_H
+#define TEST_OA_HASH_MAP_H
+
+#include "core/templates/oa_hash_map.h"
+#include "scene/resources/texture.h"
+
+#include "tests/test_macros.h"
+
+namespace TestOAHashMap {
+
+TEST_CASE("[OAHashMap] Insert element") {
+ OAHashMap<int, int> map;
+ map.insert(42, 84);
+ int data = 0;
+ bool lookup_res = map.lookup(42, data);
+ int value = *map.lookup_ptr(42);
+ CHECK(lookup_res);
+ CHECK(value == 84);
+ CHECK(data == 84);
+}
+
+TEST_CASE("[OAHashMap] Set element") {
+ OAHashMap<int, int> map;
+ map.set(42, 84);
+ int data = 0;
+ bool lookup_res = map.lookup(42, data);
+ int value = *map.lookup_ptr(42);
+ CHECK(lookup_res);
+ CHECK(value == 84);
+ CHECK(data == 84);
+}
+
+TEST_CASE("[OAHashMap] Overwrite element") {
+ OAHashMap<int, int> map;
+ map.set(42, 84);
+ map.set(42, 1234);
+ int result = *map.lookup_ptr(42);
+ CHECK(result == 1234);
+}
+
+TEST_CASE("[OAHashMap] Remove element") {
+ OAHashMap<int, int> map;
+ map.insert(42, 84);
+ map.remove(42);
+ CHECK(!map.has(42));
+}
+
+TEST_CASE("[OAHashMap] Get Num_Elements") {
+ OAHashMap<int, int> map;
+ map.set(42, 84);
+ map.set(123, 84);
+ map.set(123, 84);
+ map.set(0, 84);
+ map.set(123485, 84);
+
+ CHECK(map.get_num_elements() == 4);
+}
+
+TEST_CASE("[OAHashMap] Iteration") {
+ OAHashMap<int, int> map;
+ map.insert(42, 84);
+ map.insert(123, 12385);
+ map.insert(0, 12934);
+ map.insert(123485, 1238888);
+ map.set(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));
+
+ for (OAHashMap<int, int>::Iterator it = map.iter(); it.valid; it = map.next_iter(it)) {
+ int64_t result = expected.find(Pair<int, int>(*it.key, *it.value));
+ CHECK(result >= 0);
+ }
+}
+
+TEST_CASE("[OAHashMap] Insert, iterate, remove many strings") {
+ uint64_t pre_mem = Memory::get_mem_usage();
+ {
+ const int elem_max = 40;
+ OAHashMap<String, int> map;
+ for (int i = 0; i < elem_max; i++) {
+ map.insert(itos(i), i);
+ }
+
+ Vector<String> elems_still_valid;
+
+ for (int i = 0; i < elem_max; i++) {
+ if ((i % 5) == 0) {
+ map.remove(itos(i));
+ } else {
+ elems_still_valid.push_back(itos(i));
+ }
+ }
+
+ CHECK(elems_still_valid.size() == map.get_num_elements());
+
+ for (int i = 0; i < elems_still_valid.size(); i++) {
+ CHECK(map.has(elems_still_valid[i]));
+ }
+ }
+
+ CHECK(Memory::get_mem_usage() == pre_mem);
+}
+
+TEST_CASE("[OAHashMap] Clear") {
+ OAHashMap<int, int> map;
+ map.insert(42, 84);
+ map.insert(0, 1234);
+ map.clear();
+ CHECK(!map.has(42));
+ CHECK(!map.has(0));
+ CHECK(map.is_empty());
+}
+
+TEST_CASE("[OAHashMap] Copy constructor") {
+ uint64_t pre_mem = Memory::get_mem_usage();
+ {
+ OAHashMap<int, int> map0;
+ const uint32_t count = 5;
+ for (uint32_t i = 0; i < count; i++) {
+ map0.insert(i, i);
+ }
+ OAHashMap<int, int> map1(map0);
+ CHECK(map0.get_num_elements() == map1.get_num_elements());
+ CHECK(map0.get_capacity() == map1.get_capacity());
+ CHECK(*map0.lookup_ptr(0) == *map1.lookup_ptr(0));
+ }
+ CHECK(Memory::get_mem_usage() == pre_mem);
+}
+
+TEST_CASE("[OAHashMap] Operator =") {
+ uint64_t pre_mem = Memory::get_mem_usage();
+ {
+ OAHashMap<int, int> map0;
+ OAHashMap<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.get_num_elements() == map1.get_num_elements());
+ CHECK(map0.get_capacity() == map1.get_capacity());
+ CHECK(*map0.lookup_ptr(0) == *map1.lookup_ptr(0));
+ }
+ CHECK(Memory::get_mem_usage() == pre_mem);
+}
+
+TEST_CASE("[OAHashMap] Non-trivial types") {
+ uint64_t pre_mem = Memory::get_mem_usage();
+ {
+ OAHashMap<String, Ref<Texture2D>> map1;
+ const uint32_t count = 10;
+ for (uint32_t i = 0; i < count; i++) {
+ String string = "qwerty";
+ string += itos(i);
+ Ref<Texture2D> ref_texture_2d;
+
+ map1.set(string, ref_texture_2d);
+ Ref<Texture2D> map_vec = *map1.lookup_ptr(string);
+ CHECK(map_vec == ref_texture_2d);
+ }
+ OAHashMap<String, Ref<Texture2D>> map1copy(map1);
+ CHECK(map1copy.has(String("qwerty0")));
+ map1copy = map1;
+ CHECK(map1copy.has(String("qwerty2")));
+
+ OAHashMap<int64_t, Vector4 *> map2;
+
+ for (uint32_t i = 0; i < count; i++) {
+ Vector4 *vec = memnew(Vector4);
+ vec->x = 10;
+ vec->y = 12;
+ vec->z = 151;
+ vec->w = -13;
+ map2.set(i, vec);
+ Vector4 *p = nullptr;
+ map2.lookup(i, p);
+ CHECK(*p == *vec);
+ }
+
+ OAHashMap<int64_t, Vector4 *> map3(map2);
+ for (OAHashMap<int64_t, Vector4 *>::Iterator it = map2.iter(); it.valid; it = map2.next_iter(it)) {
+ memdelete(*(it.value));
+ }
+ }
+ CHECK(Memory::get_mem_usage() == pre_mem);
+}
+
+} // namespace TestOAHashMap
+
+#endif // TEST_OA_HASH_MAP_H
diff --git a/tests/core/threads/test_worker_thread_pool.h b/tests/core/threads/test_worker_thread_pool.h
index e9a762b57b..0a0291d11b 100644
--- a/tests/core/threads/test_worker_thread_pool.h
+++ b/tests/core/threads/test_worker_thread_pool.h
@@ -38,6 +38,7 @@
namespace TestWorkerThreadPool {
static LocalVector<SafeNumeric<int>> counter;
+static SafeFlag exit;
static void static_test(void *p_arg) {
counter[(uint64_t)p_arg].increment();
@@ -106,6 +107,72 @@ TEST_CASE("[WorkerThreadPool] Process elements using group tasks") {
}
}
+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
#endif // TEST_WORKER_THREAD_POOL_H
diff --git a/tests/core/variant/test_array.h b/tests/core/variant/test_array.h
index ea61ae2a02..c54854e4d7 100644
--- a/tests/core/variant/test_array.h
+++ b/tests/core/variant/test_array.h
@@ -545,6 +545,58 @@ TEST_CASE("[Array] Recursive self comparison") {
a2.clear();
}
+TEST_CASE("[Array] Iteration") {
+ Array a1 = build_array(1, 2, 3);
+ Array a2 = build_array(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 = build_array(1, 2, 3);
+ Array a2 = build_array(2, 3, 4);
+ Array a3 = build_array(1, 2, 3);
+ Array a4 = build_array(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();
+}
+
} // namespace TestArray
#endif // TEST_ARRAY_H
diff --git a/tests/core/variant/test_callable.h b/tests/core/variant/test_callable.h
new file mode 100644
index 0000000000..3228e0a583
--- /dev/null
+++ b/tests/core/variant/test_callable.h
@@ -0,0 +1,140 @@
+/**************************************************************************/
+/* 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. */
+/**************************************************************************/
+
+#ifndef TEST_CALLABLE_H
+#define TEST_CALLABLE_H
+
+#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);
+}
+} // namespace TestCallable
+
+#endif // TEST_CALLABLE_H