diff options
Diffstat (limited to 'tests/core')
-rw-r--r-- | tests/core/io/test_ip.h | 51 | ||||
-rw-r--r-- | tests/core/io/test_marshalls.h | 78 | ||||
-rw-r--r-- | tests/core/io/test_resource.h | 5 | ||||
-rw-r--r-- | tests/core/math/test_geometry_2d.h | 94 | ||||
-rw-r--r-- | tests/core/math/test_geometry_3d.h | 6 | ||||
-rw-r--r-- | tests/core/math/test_transform_3d.h | 29 | ||||
-rw-r--r-- | tests/core/math/test_vector2.h | 4 | ||||
-rw-r--r-- | tests/core/object/test_object.h | 6 | ||||
-rw-r--r-- | tests/core/object/test_undo_redo.h | 202 | ||||
-rw-r--r-- | tests/core/os/test_os.h | 26 | ||||
-rw-r--r-- | tests/core/string/test_node_path.h | 53 | ||||
-rw-r--r-- | tests/core/string/test_string.h | 73 | ||||
-rw-r--r-- | tests/core/templates/test_command_queue.h | 44 | ||||
-rw-r--r-- | tests/core/templates/test_oa_hash_map.h | 225 | ||||
-rw-r--r-- | tests/core/threads/test_worker_thread_pool.h | 67 | ||||
-rw-r--r-- | tests/core/variant/test_array.h | 52 | ||||
-rw-r--r-- | tests/core/variant/test_callable.h | 140 |
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 |