diff options
Diffstat (limited to 'tests/core')
-rw-r--r-- | tests/core/input/test_input_event.h | 4 | ||||
-rw-r--r-- | tests/core/io/test_resource.h | 5 | ||||
-rw-r--r-- | tests/core/math/test_aabb.h | 61 | ||||
-rw-r--r-- | tests/core/math/test_geometry_2d.h | 94 | ||||
-rw-r--r-- | tests/core/math/test_math_funcs.h | 3 | ||||
-rw-r--r-- | tests/core/math/test_transform_3d.h | 29 | ||||
-rw-r--r-- | tests/core/object/test_class_db.h | 30 | ||||
-rw-r--r-- | tests/core/object/test_object.h | 7 | ||||
-rw-r--r-- | tests/core/os/test_os.h | 4 | ||||
-rw-r--r-- | tests/core/string/test_string.h | 309 | ||||
-rw-r--r-- | tests/core/templates/test_command_queue.h | 44 | ||||
-rw-r--r-- | tests/core/templates/test_local_vector.h | 11 | ||||
-rw-r--r-- | tests/core/threads/test_worker_thread_pool.h | 67 | ||||
-rw-r--r-- | tests/core/variant/test_array.h | 4 | ||||
-rw-r--r-- | tests/core/variant/test_dictionary.h | 2 | ||||
-rw-r--r-- | tests/core/variant/test_variant.h | 4 |
16 files changed, 467 insertions, 211 deletions
diff --git a/tests/core/input/test_input_event.h b/tests/core/input/test_input_event.h index 6b4b80486c..c6a287e47b 100644 --- a/tests/core/input/test_input_event.h +++ b/tests/core/input/test_input_event.h @@ -43,7 +43,7 @@ TEST_CASE("[InputEvent] Signal is emitted when device is changed") { Ref<InputEventKey> input_event; input_event.instantiate(); - SIGNAL_WATCH(*input_event, SNAME("changed")); + SIGNAL_WATCH(*input_event, CoreStringName(changed)); Array args1; Array empty_args; empty_args.push_back(args1); @@ -53,7 +53,7 @@ TEST_CASE("[InputEvent] Signal is emitted when device is changed") { SIGNAL_CHECK("changed", empty_args); CHECK(input_event->get_device() == 1); - SIGNAL_UNWATCH(*input_event, SNAME("changed")); + SIGNAL_UNWATCH(*input_event, CoreStringName(changed)); } TEST_CASE("[InputEvent] Test accumulate") { 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_aabb.h b/tests/core/math/test_aabb.h index b9f84cca24..dbc62bc248 100644 --- a/tests/core/math/test_aabb.h +++ b/tests/core/math/test_aabb.h @@ -204,6 +204,67 @@ TEST_CASE("[AABB] Intersection") { CHECK_MESSAGE( !aabb_big.intersects_segment(Vector3(0, 300, 0), Vector3(0, 300, 0)), "intersects_segment() should return the expected result with segment of length 0."); + CHECK_MESSAGE( // Simple ray intersection test. + aabb_big.intersects_ray(Vector3(-100, 3, 0), Vector3(1, 0, 0)), + "intersects_ray() should return true when ray points directly to AABB from outside."); + CHECK_MESSAGE( // Ray parallel to an edge. + !aabb_big.intersects_ray(Vector3(10, 10, 0), Vector3(0, 1, 0)), + "intersects_ray() should return false for ray parallel and outside of AABB."); + CHECK_MESSAGE( // Ray origin inside aabb. + aabb_big.intersects_ray(Vector3(1, 1, 1), Vector3(0, 1, 0)), + "intersects_ray() should return true for rays originating inside the AABB."); + CHECK_MESSAGE( // Ray pointing away from aabb. + !aabb_big.intersects_ray(Vector3(-10, 0, 0), Vector3(-1, 0, 0)), + "intersects_ray() should return false when ray points away from AABB."); + CHECK_MESSAGE( // Ray along a diagonal of aabb. + aabb_big.intersects_ray(Vector3(0, 0, 0), Vector3(1, 1, 1)), + "intersects_ray() should return true for rays along the AABB diagonal."); + CHECK_MESSAGE( // Ray originating at aabb edge. + aabb_big.intersects_ray(aabb_big.position, Vector3(-1, 0, 0)), + "intersects_ray() should return true for rays starting on AABB's edge."); + CHECK_MESSAGE( // Ray with zero direction inside. + aabb_big.intersects_ray(Vector3(-1, 3, -2), Vector3(0, 0, 0)), + "intersects_ray() should return true because its inside."); + CHECK_MESSAGE( // Ray with zero direction outside. + !aabb_big.intersects_ray(Vector3(-1000, 3, -2), Vector3(0, 0, 0)), + "intersects_ray() should return false for being outside."); + + // Finding ray intersections. + const AABB aabb_simple = AABB(Vector3(), Vector3(1, 1, 1)); + bool inside = false; + Vector3 intersection_point; + Vector3 intersection_normal; + + // Borders. + aabb_simple.find_intersects_ray(Vector3(0.5, 0, 0.5), Vector3(0, 1, 0), inside, &intersection_point, &intersection_normal); + CHECK_MESSAGE(inside == false, "find_intersects_ray() should return outside on borders."); + CHECK_MESSAGE(intersection_point.is_equal_approx(Vector3(0.5, 0, 0.5)), "find_intersects_ray() border intersection point incorrect."); + CHECK_MESSAGE(intersection_normal.is_equal_approx(Vector3(0, -1, 0)), "find_intersects_ray() border intersection normal incorrect."); + aabb_simple.find_intersects_ray(Vector3(0.5, 1, 0.5), Vector3(0, -1, 0), inside, &intersection_point, &intersection_normal); + CHECK_MESSAGE(inside == false, "find_intersects_ray() should return outside on borders."); + CHECK_MESSAGE(intersection_point.is_equal_approx(Vector3(0.5, 1, 0.5)), "find_intersects_ray() border intersection point incorrect."); + CHECK_MESSAGE(intersection_normal.is_equal_approx(Vector3(0, 1, 0)), "find_intersects_ray() border intersection normal incorrect."); + + // Inside. + aabb_simple.find_intersects_ray(Vector3(0.5, 0.1, 0.5), Vector3(0, 1, 0), inside, &intersection_point, &intersection_normal); + CHECK_MESSAGE(inside == true, "find_intersects_ray() should return inside when inside."); + CHECK_MESSAGE(intersection_point.is_equal_approx(Vector3(0.5, 0, 0.5)), "find_intersects_ray() inside backtracking intersection point incorrect."); + CHECK_MESSAGE(intersection_normal.is_equal_approx(Vector3(0, -1, 0)), "find_intersects_ray() inside intersection normal incorrect."); + + // Zero sized AABB. + const AABB aabb_zero = AABB(Vector3(), Vector3(1, 0, 1)); + aabb_zero.find_intersects_ray(Vector3(0.5, 0, 0.5), Vector3(0, 1, 0), inside, &intersection_point, &intersection_normal); + CHECK_MESSAGE(inside == false, "find_intersects_ray() should return outside on borders of zero sized AABB."); + CHECK_MESSAGE(intersection_point.is_equal_approx(Vector3(0.5, 0, 0.5)), "find_intersects_ray() border intersection point incorrect for zero sized AABB."); + CHECK_MESSAGE(intersection_normal.is_equal_approx(Vector3(0, -1, 0)), "find_intersects_ray() border intersection normal incorrect for zero sized AABB."); + aabb_zero.find_intersects_ray(Vector3(0.5, 0, 0.5), Vector3(0, -1, 0), inside, &intersection_point, &intersection_normal); + CHECK_MESSAGE(inside == false, "find_intersects_ray() should return outside on borders of zero sized AABB."); + CHECK_MESSAGE(intersection_point.is_equal_approx(Vector3(0.5, 0, 0.5)), "find_intersects_ray() border intersection point incorrect for zero sized AABB."); + CHECK_MESSAGE(intersection_normal.is_equal_approx(Vector3(0, 1, 0)), "find_intersects_ray() border intersection normal incorrect for zero sized AABB."); + aabb_zero.find_intersects_ray(Vector3(0.5, -1, 0.5), Vector3(0, 1, 0), inside, &intersection_point, &intersection_normal); + CHECK_MESSAGE(inside == false, "find_intersects_ray() should return outside on borders of zero sized AABB."); + CHECK_MESSAGE(intersection_point.is_equal_approx(Vector3(0.5, 0, 0.5)), "find_intersects_ray() border intersection point incorrect for zero sized AABB."); + CHECK_MESSAGE(intersection_normal.is_equal_approx(Vector3(0, -1, 0)), "find_intersects_ray() border intersection normal incorrect for zero sized AABB."); } TEST_CASE("[AABB] Merging") { 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_math_funcs.h b/tests/core/math/test_math_funcs.h index 0a9d9c97d9..68540e4d61 100644 --- a/tests/core/math/test_math_funcs.h +++ b/tests/core/math/test_math_funcs.h @@ -381,6 +381,9 @@ TEST_CASE_TEMPLATE("[Math] remap", T, float, double) { CHECK(Math::remap((T)-100.0, (T)-100.0, (T)-200.0, (T)0.0, (T)-1000.0) == doctest::Approx((T)0.0)); CHECK(Math::remap((T)-200.0, (T)-100.0, (T)-200.0, (T)0.0, (T)-1000.0) == doctest::Approx((T)-1000.0)); CHECK(Math::remap((T)-250.0, (T)-100.0, (T)-200.0, (T)0.0, (T)-1000.0) == doctest::Approx((T)-1500.0)); + + // Note: undefined behavior can happen when `p_istart == p_istop`. We don't bother testing this as it will + // vary between hardware and compilers properly implementing IEEE 754. } TEST_CASE_TEMPLATE("[Math] angle_difference", T, float, double) { 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/object/test_class_db.h b/tests/core/object/test_class_db.h index 5f7de11c71..381d759e5b 100644 --- a/tests/core/object/test_class_db.h +++ b/tests/core/object/test_class_db.h @@ -141,7 +141,7 @@ struct NamesCache { StringName vector3_type = StaticCString::create("Vector3"); // Object not included as it must be checked for all derived classes - static constexpr int nullable_types_count = 17; + static constexpr int nullable_types_count = 18; StringName nullable_types[nullable_types_count] = { string_type, string_name_type, @@ -161,6 +161,7 @@ struct NamesCache { StaticCString::create(_STR(PackedVector2Array)), StaticCString::create(_STR(PackedVector3Array)), StaticCString::create(_STR(PackedColorArray)), + StaticCString::create(_STR(PackedVector4Array)), }; bool is_nullable_type(const StringName &p_type) const { @@ -194,12 +195,12 @@ struct Context { } bool has_type(const TypeReference &p_type_ref) const { - if (builtin_types.find(p_type_ref.name) >= 0) { + if (builtin_types.has(p_type_ref.name)) { return true; } if (p_type_ref.is_enum) { - if (enum_types.find(p_type_ref.name) >= 0) { + if (enum_types.has(p_type_ref.name)) { return true; } @@ -258,6 +259,7 @@ bool arg_default_value_is_assignable_to_type(const Context &p_context, const Var case Variant::PACKED_VECTOR2_ARRAY: case Variant::PACKED_VECTOR3_ARRAY: case Variant::PACKED_COLOR_ARRAY: + case Variant::PACKED_VECTOR4_ARRAY: case Variant::CALLABLE: case Variant::SIGNAL: return p_arg_type.name == Variant::get_type_name(p_val.get_type()); @@ -353,7 +355,7 @@ void validate_property(const Context &p_context, const ExposedClass &p_class, co const ArgumentData &idx_arg = getter->arguments.front()->get(); if (idx_arg.type.name != p_context.names_cache.int_type) { // If not an int, it can be an enum - TEST_COND(p_context.enum_types.find(idx_arg.type.name) < 0, + TEST_COND(!p_context.enum_types.has(idx_arg.type.name), "Invalid type '", idx_arg.type.name, "' for index argument of property getter: '", p_class.name, ".", String(p_prop.name), "'."); } } @@ -365,7 +367,7 @@ void validate_property(const Context &p_context, const ExposedClass &p_class, co if (idx_arg.type.name != p_context.names_cache.int_type) { // Assume the index parameter is an enum // If not an int, it can be an enum - TEST_COND(p_context.enum_types.find(idx_arg.type.name) < 0, + TEST_COND(!p_context.enum_types.has(idx_arg.type.name), "Invalid type '", idx_arg.type.name, "' for index argument of property setter: '", p_class.name, ".", String(p_prop.name), "'."); } } @@ -548,8 +550,6 @@ void add_exposed_classes(Context &r_context) { for (const MethodInfo &E : method_list) { const MethodInfo &method_info = E; - int argc = method_info.arguments.size(); - if (method_info.name.is_empty()) { continue; } @@ -611,8 +611,9 @@ void add_exposed_classes(Context &r_context) { method.return_type.name = Variant::get_type_name(return_info.type); } - for (int i = 0; i < argc; i++) { - PropertyInfo arg_info = method_info.arguments[i]; + int i = 0; + for (List<PropertyInfo>::ConstIterator itr = method_info.arguments.begin(); itr != method_info.arguments.end(); ++itr, ++i) { + const PropertyInfo &arg_info = *itr; String orig_arg_name = arg_info.name; @@ -684,10 +685,9 @@ void add_exposed_classes(Context &r_context) { TEST_FAIL_COND(!String(signal.name).is_valid_identifier(), "Signal name is not a valid identifier: '", exposed_class.name, ".", signal.name, "'."); - int argc = method_info.arguments.size(); - - for (int i = 0; i < argc; i++) { - PropertyInfo arg_info = method_info.arguments[i]; + int i = 0; + for (List<PropertyInfo>::ConstIterator itr = method_info.arguments.begin(); itr != method_info.arguments.end(); ++itr, ++i) { + const PropertyInfo &arg_info = *itr; String orig_arg_name = arg_info.name; @@ -736,7 +736,7 @@ void add_exposed_classes(Context &r_context) { for (const StringName &E : K.value.constants) { const StringName &constant_name = E; - TEST_FAIL_COND(String(constant_name).find("::") != -1, + TEST_FAIL_COND(String(constant_name).contains("::"), "Enum constant contains '::', check bindings to remove the scope: '", String(class_name), ".", String(enum_.name), ".", String(constant_name), "'."); int64_t *value = class_info->constant_map.getptr(constant_name); @@ -758,7 +758,7 @@ void add_exposed_classes(Context &r_context) { for (const String &E : constants) { const String &constant_name = E; - TEST_FAIL_COND(constant_name.find("::") != -1, + TEST_FAIL_COND(constant_name.contains("::"), "Constant contains '::', check bindings to remove the scope: '", String(class_name), ".", constant_name, "'."); int64_t *value = class_info->constant_map.getptr(StringName(E)); diff --git a/tests/core/object/test_object.h b/tests/core/object/test_object.h index 3a3013a102..57bc65328a 100644 --- a/tests/core/object/test_object.h +++ b/tests/core/object/test_object.h @@ -31,7 +31,6 @@ #ifndef TEST_OBJECT_H #define TEST_OBJECT_H -#include "core/core_string_names.h" #include "core/object/class_db.h" #include "core/object/object.h" #include "core/object/script_language.h" @@ -142,7 +141,7 @@ TEST_CASE("[Object] Core getters") { inheritance_list.size() == 1, "The inheritance list should consist of Object only"); CHECK_MESSAGE( - inheritance_list[0] == "Object", + inheritance_list.front()->get() == "Object", "The inheritance list should consist of Object only"); } @@ -251,7 +250,7 @@ TEST_CASE("[Object] Script property setter") { Variant script; bool valid = false; - object.set(CoreStringNames::get_singleton()->_script, script, &valid); + object.set(CoreStringName(script), script, &valid); CHECK(valid); CHECK_MESSAGE( object.get_script() == script, @@ -264,7 +263,7 @@ TEST_CASE("[Object] Script property getter") { object.set_script(script); bool valid = false; - const Variant &actual_value = object.get(CoreStringNames::get_singleton()->_script, &valid); + const Variant &actual_value = object.get(CoreStringName(script), &valid); CHECK(valid); CHECK_MESSAGE( actual_value == script, diff --git a/tests/core/os/test_os.h b/tests/core/os/test_os.h index 63f8b18238..6ee0ff82e7 100644 --- a/tests/core/os/test_os.h +++ b/tests/core/os/test_os.h @@ -79,8 +79,8 @@ TEST_CASE("[OS] Non-UTF-8 environment variables") { TEST_CASE("[OS] Command line arguments") { List<String> arguments = OS::get_singleton()->get_cmdline_args(); bool found = false; - for (int i = 0; i < arguments.size(); i++) { - if (arguments[i] == "--test") { + for (const String &arg : arguments) { + if (arg == "--test") { found = true; break; } diff --git a/tests/core/string/test_string.h b/tests/core/string/test_string.h index 64f03e5879..cf57183a02 100644 --- a/tests/core/string/test_string.h +++ b/tests/core/string/test_string.h @@ -297,6 +297,19 @@ TEST_CASE("[String] Contains") { CHECK(!s.contains(String("\\char_test.tscn"))); } +TEST_CASE("[String] Contains case insensitive") { + String s = "C:\\Godot\\project\\string_test.tscn"; + CHECK(s.containsn("Godot")); + CHECK(s.containsn("godot")); + CHECK(s.containsn(String("Project\\string_test"))); + CHECK(s.containsn(String("\\string_Test.tscn"))); + + CHECK(!s.containsn("Godoh")); + CHECK(!s.containsn("godoh")); + CHECK(!s.containsn(String("project\\string test"))); + CHECK(!s.containsn(String("\\char_test.tscn"))); +} + TEST_CASE("[String] Test chr") { CHECK(String::chr('H') == "H"); CHECK(String::chr(0x3012)[0] == 0x3012); @@ -360,18 +373,37 @@ TEST_CASE("[String] Substr") { TEST_CASE("[String] Find") { String s = "Pretty Woman Woman"; - CHECK(s.find("tty") == 3); - CHECK(s.find("Wo", 9) == 13); - CHECK(s.find("Revenge of the Monster Truck") == -1); - CHECK(s.rfind("man") == 15); -} - -TEST_CASE("[String] Find no case") { + MULTICHECK_STRING_EQ(s, find, "tty", 3); + MULTICHECK_STRING_EQ(s, find, "Revenge of the Monster Truck", -1); + MULTICHECK_STRING_INT_EQ(s, find, "Wo", 9, 13); + MULTICHECK_STRING_EQ(s, find, "", -1); + MULTICHECK_STRING_EQ(s, find, "Pretty Woman Woman", 0); + MULTICHECK_STRING_EQ(s, find, "WOMAN", -1); + MULTICHECK_STRING_INT_EQ(s, find, "", 9, -1); + + MULTICHECK_STRING_EQ(s, rfind, "", -1); + MULTICHECK_STRING_EQ(s, rfind, "foo", -1); + MULTICHECK_STRING_EQ(s, rfind, "Pretty Woman Woman", 0); + MULTICHECK_STRING_EQ(s, rfind, "man", 15); + MULTICHECK_STRING_EQ(s, rfind, "WOMAN", -1); + MULTICHECK_STRING_INT_EQ(s, rfind, "", 15, -1); +} + +TEST_CASE("[String] Find case insensitive") { String s = "Pretty Whale Whale"; - CHECK(s.findn("WHA") == 7); - CHECK(s.findn("WHA", 9) == 13); - CHECK(s.findn("Revenge of the Monster SawFish") == -1); - CHECK(s.rfindn("WHA") == 13); + MULTICHECK_STRING_EQ(s, findn, "WHA", 7); + MULTICHECK_STRING_INT_EQ(s, findn, "WHA", 9, 13); + MULTICHECK_STRING_EQ(s, findn, "Revenge of the Monster SawFish", -1); + MULTICHECK_STRING_EQ(s, findn, "", -1); + MULTICHECK_STRING_EQ(s, findn, "wha", 7); + MULTICHECK_STRING_EQ(s, findn, "Wha", 7); + MULTICHECK_STRING_INT_EQ(s, findn, "", 3, -1); + + MULTICHECK_STRING_EQ(s, rfindn, "WHA", 13); + MULTICHECK_STRING_EQ(s, rfindn, "", -1); + MULTICHECK_STRING_EQ(s, rfindn, "wha", 13); + MULTICHECK_STRING_EQ(s, rfindn, "Wha", 13); + MULTICHECK_STRING_INT_EQ(s, rfindn, "", 13, -1); } TEST_CASE("[String] Find MK") { @@ -392,11 +424,9 @@ TEST_CASE("[String] Find MK") { TEST_CASE("[String] Find and replace") { String s = "Happy Birthday, Anna!"; - s = s.replace("Birthday", "Halloween"); - CHECK(s == "Happy Halloween, Anna!"); - - s = s.replace_first("H", "W"); - CHECK(s == "Wappy Halloween, Anna!"); + MULTICHECK_STRING_STRING_EQ(s, replace, "Birthday", "Halloween", "Happy Halloween, Anna!"); + MULTICHECK_STRING_STRING_EQ(s, replace_first, "y", "Y", "HappY Birthday, Anna!"); + MULTICHECK_STRING_STRING_EQ(s, replacen, "Y", "Y", "HappY BirthdaY, Anna!"); } TEST_CASE("[String] Insertion") { @@ -557,51 +587,76 @@ TEST_CASE("[String] String to float") { TEST_CASE("[String] Slicing") { String s = "Mars,Jupiter,Saturn,Uranus"; - const char *slices[4] = { "Mars", "Jupiter", "Saturn", "Uranus" }; - for (int i = 0; i < s.get_slice_count(","); i++) { - CHECK(s.get_slice(",", i) == slices[i]); - } + MULTICHECK_GET_SLICE(s, ",", slices); +} + +TEST_CASE("[String] Begins with") { + // Test cases for true: + MULTICHECK_STRING_EQ(String("res://foobar"), begins_with, "res://", true); + MULTICHECK_STRING_EQ(String("abc"), begins_with, "abc", true); + MULTICHECK_STRING_EQ(String("abc"), begins_with, "", true); + MULTICHECK_STRING_EQ(String(""), begins_with, "", true); + + // Test cases for false: + MULTICHECK_STRING_EQ(String("res"), begins_with, "res://", false); + MULTICHECK_STRING_EQ(String("abcdef"), begins_with, "foo", false); + MULTICHECK_STRING_EQ(String("abc"), begins_with, "ax", false); + MULTICHECK_STRING_EQ(String(""), begins_with, "abc", false); + + // Test "const char *" version also with nullptr. + String s("foo"); + bool 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 cases for true: + MULTICHECK_STRING_EQ(String("res://foobar"), ends_with, "foobar", true); + MULTICHECK_STRING_EQ(String("abc"), ends_with, "abc", true); + MULTICHECK_STRING_EQ(String("abc"), ends_with, "", true); + MULTICHECK_STRING_EQ(String(""), ends_with, "", true); + + // Test cases for false: + MULTICHECK_STRING_EQ(String("res"), ends_with, "res://", false); + MULTICHECK_STRING_EQ(String("abcdef"), ends_with, "foo", false); + MULTICHECK_STRING_EQ(String("abc"), ends_with, "ax", false); + MULTICHECK_STRING_EQ(String(""), ends_with, "abc", false); + + // Test "const char *" version also with nullptr. + String s("foo"); + bool state = s.ends_with(nullptr) == false; + CHECK_MESSAGE(state, "nullptr check failed"); + + String empty(""); + state = empty.ends_with(nullptr) == false; + CHECK_MESSAGE(state, "nullptr check with empty string failed"); } TEST_CASE("[String] Splitting") { String s = "Mars,Jupiter,Saturn,Uranus"; - Vector<String> l; - const char *slices_l[3] = { "Mars", "Jupiter", "Saturn,Uranus" }; - const char *slices_r[3] = { "Mars,Jupiter", "Saturn", "Uranus" }; - const char *slices_3[4] = { "t", "e", "s", "t" }; + MULTICHECK_SPLIT(s, split, ",", true, 2, slices_l, 3); - l = s.split(",", true, 2); - CHECK(l.size() == 3); - for (int i = 0; i < l.size(); i++) { - CHECK(l[i] == slices_l[i]); - } - - l = s.rsplit(",", true, 2); - CHECK(l.size() == 3); - for (int i = 0; i < l.size(); i++) { - CHECK(l[i] == slices_r[i]); - } + const char *slices_r[3] = { "Mars,Jupiter", "Saturn", "Uranus" }; + MULTICHECK_SPLIT(s, rsplit, ",", true, 2, slices_r, 3); s = "test"; - l = s.split(); - CHECK(l.size() == 4); - for (int i = 0; i < l.size(); i++) { - CHECK(l[i] == slices_3[i]); - } + const char *slices_3[4] = { "t", "e", "s", "t" }; + MULTICHECK_SPLIT(s, split, "", true, 0, slices_3, 4); s = ""; - l = s.split(); - CHECK(l.size() == 1); - CHECK(l[0] == ""); - - l = s.split("", false); - CHECK(l.size() == 0); + const char *slices_4[1] = { "" }; + MULTICHECK_SPLIT(s, split, "", true, 0, slices_4, 1); + MULTICHECK_SPLIT(s, split, "", false, 0, slices_4, 0); s = "Mars Jupiter Saturn Uranus"; const char *slices_s[4] = { "Mars", "Jupiter", "Saturn", "Uranus" }; - l = s.split_spaces(); + Vector<String> l = s.split_spaces(); for (int i = 0; i < l.size(); i++) { CHECK(l[i] == slices_s[i]); } @@ -644,69 +699,6 @@ TEST_CASE("[String] Splitting") { } } -struct test_27_data { - char const *data; - char const *part; - bool expected; -}; - -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 }, - { "abcdef", "foo", false }, - { "abc", "ax", false }, - { "", "abc", false } - }; - size_t count = sizeof(tc) / sizeof(tc[0]); - bool state = true; - for (size_t i = 0; i < count; ++i) { - String s = tc[i].data; - state = s.begins_with(tc[i].part) == tc[i].expected; - 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", false }, - { "abcdef", "foo", false }, - { "abc", "xc", false } - }; - size_t count = sizeof(tc) / sizeof(tc[0]); - for (size_t i = 0; i < count; ++i) { - String s = tc[i].data; - 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") { const String value_format = "red=\"$red\" green=\"$green\" blue=\"$blue\" alpha=\"$alpha\""; @@ -1498,39 +1490,62 @@ TEST_CASE("[String] Cyrillic to_lower()") { } TEST_CASE("[String] Count and countn functionality") { -#define COUNT_TEST(x) \ - { \ - bool success = x; \ - state = state && success; \ - } + String s = String(""); + MULTICHECK_STRING_EQ(s, count, "Test", 0); - bool state = true; + s = "Test"; + MULTICHECK_STRING_EQ(s, count, "", 0); - COUNT_TEST(String("").count("Test") == 0); - COUNT_TEST(String("Test").count("") == 0); - COUNT_TEST(String("Test").count("test") == 0); - COUNT_TEST(String("Test").count("TEST") == 0); - COUNT_TEST(String("TEST").count("TEST") == 1); - COUNT_TEST(String("Test").count("Test") == 1); - COUNT_TEST(String("aTest").count("Test") == 1); - COUNT_TEST(String("Testa").count("Test") == 1); - COUNT_TEST(String("TestTestTest").count("Test") == 3); - COUNT_TEST(String("TestTestTest").count("TestTest") == 1); - COUNT_TEST(String("TestGodotTestGodotTestGodot").count("Test") == 3); - - COUNT_TEST(String("TestTestTestTest").count("Test", 4, 8) == 1); - COUNT_TEST(String("TestTestTestTest").count("Test", 4, 12) == 2); - COUNT_TEST(String("TestTestTestTest").count("Test", 4, 16) == 3); - COUNT_TEST(String("TestTestTestTest").count("Test", 4) == 3); - - COUNT_TEST(String("Test").countn("test") == 1); - COUNT_TEST(String("Test").countn("TEST") == 1); - COUNT_TEST(String("testTest-Testatest").countn("tEst") == 4); - COUNT_TEST(String("testTest-TeStatest").countn("tEsT", 4, 16) == 2); + s = "Test"; + MULTICHECK_STRING_EQ(s, count, "test", 0); - CHECK(state); + s = "Test"; + MULTICHECK_STRING_EQ(s, count, "TEST", 0); + + s = "TEST"; + MULTICHECK_STRING_EQ(s, count, "TEST", 1); + + s = "Test"; + MULTICHECK_STRING_EQ(s, count, "Test", 1); + + s = "aTest"; + MULTICHECK_STRING_EQ(s, count, "Test", 1); + + s = "Testa"; + MULTICHECK_STRING_EQ(s, count, "Test", 1); + + s = "TestTestTest"; + MULTICHECK_STRING_EQ(s, count, "Test", 3); + + s = "TestTestTest"; + MULTICHECK_STRING_EQ(s, count, "TestTest", 1); + + s = "TestGodotTestGodotTestGodot"; + MULTICHECK_STRING_EQ(s, count, "Test", 3); + + s = "TestTestTestTest"; + MULTICHECK_STRING_INT_INT_EQ(s, count, "Test", 4, 8, 1); + + s = "TestTestTestTest"; + MULTICHECK_STRING_INT_INT_EQ(s, count, "Test", 4, 12, 2); + + s = "TestTestTestTest"; + MULTICHECK_STRING_INT_INT_EQ(s, count, "Test", 4, 16, 3); + + s = "TestTestTestTest"; + MULTICHECK_STRING_INT_EQ(s, count, "Test", 4, 3); + + s = "Test"; + MULTICHECK_STRING_EQ(s, countn, "test", 1); + + s = "Test"; + MULTICHECK_STRING_EQ(s, countn, "TEST", 1); + + s = "testTest-Testatest"; + MULTICHECK_STRING_EQ(s, countn, "tEst", 4); -#undef COUNT_TEST + s = "testTest-TeStatest"; + MULTICHECK_STRING_INT_INT_EQ(s, countn, "tEsT", 4, 16, 2); } TEST_CASE("[String] Bigrams") { @@ -1703,9 +1718,19 @@ TEST_CASE("[String] Strip edges") { TEST_CASE("[String] Trim") { String s = "aaaTestbbb"; - CHECK(s.trim_prefix("aaa") == "Testbbb"); - CHECK(s.trim_suffix("bbb") == "aaaTest"); - CHECK(s.trim_suffix("Test") == s); + MULTICHECK_STRING_EQ(s, trim_prefix, "aaa", "Testbbb"); + MULTICHECK_STRING_EQ(s, trim_prefix, "Test", s); + MULTICHECK_STRING_EQ(s, trim_prefix, "", s); + MULTICHECK_STRING_EQ(s, trim_prefix, "aaaTestbbb", ""); + MULTICHECK_STRING_EQ(s, trim_prefix, "bbb", s); + MULTICHECK_STRING_EQ(s, trim_prefix, "AAA", s); + + MULTICHECK_STRING_EQ(s, trim_suffix, "bbb", "aaaTest"); + MULTICHECK_STRING_EQ(s, trim_suffix, "Test", s); + MULTICHECK_STRING_EQ(s, trim_suffix, "", s); + MULTICHECK_STRING_EQ(s, trim_suffix, "aaaTestbbb", ""); + MULTICHECK_STRING_EQ(s, trim_suffix, "aaa", s); + MULTICHECK_STRING_EQ(s, trim_suffix, "BBB", s); } TEST_CASE("[String] Right/Left") { 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_local_vector.h b/tests/core/templates/test_local_vector.h index 2873a9a028..c9544c625b 100644 --- a/tests/core/templates/test_local_vector.h +++ b/tests/core/templates/test_local_vector.h @@ -63,7 +63,7 @@ TEST_CASE("[LocalVector] Push Back.") { CHECK(vector[4] == 4); } -TEST_CASE("[LocalVector] Find.") { +TEST_CASE("[LocalVector] Find, has.") { LocalVector<int> vector; vector.push_back(3); vector.push_back(1); @@ -85,6 +85,15 @@ TEST_CASE("[LocalVector] Find.") { CHECK(vector.find(-1) == -1); CHECK(vector.find(5) == -1); + + CHECK(vector.has(0)); + CHECK(vector.has(1)); + CHECK(vector.has(2)); + CHECK(vector.has(3)); + CHECK(vector.has(4)); + + CHECK(!vector.has(-1)); + CHECK(!vector.has(5)); } TEST_CASE("[LocalVector] Remove.") { 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 287345e831..c54854e4d7 100644 --- a/tests/core/variant/test_array.h +++ b/tests/core/variant/test_array.h @@ -555,6 +555,8 @@ TEST_CASE("[Array] Iteration") { idx++; } + CHECK_EQ(idx, a1.size()); + idx = 0; for (const Variant &E : (const Array &)a1) { @@ -562,6 +564,8 @@ TEST_CASE("[Array] Iteration") { idx++; } + CHECK_EQ(idx, a1.size()); + a1.clear(); } diff --git a/tests/core/variant/test_dictionary.h b/tests/core/variant/test_dictionary.h index 5bc56075da..aba20972d9 100644 --- a/tests/core/variant/test_dictionary.h +++ b/tests/core/variant/test_dictionary.h @@ -105,7 +105,7 @@ TEST_CASE("[Dictionary] get_key_lists()") { map[1] = 3; map.get_key_list(ptr); CHECK(keys.size() == 1); - CHECK(int(keys[0]) == 1); + CHECK(int(keys.front()->get()) == 1); map[2] = 4; map.get_key_list(ptr); CHECK(keys.size() == 3); diff --git a/tests/core/variant/test_variant.h b/tests/core/variant/test_variant.h index 54ca06c6c4..be615975f8 100644 --- a/tests/core/variant/test_variant.h +++ b/tests/core/variant/test_variant.h @@ -2034,6 +2034,10 @@ TEST_CASE("[Variant] Identity comparison") { CHECK(packed_color_array.identity_compare(packed_color_array)); CHECK_FALSE(packed_color_array.identity_compare(PackedColorArray())); + Variant packed_vector4_array = PackedVector4Array(); + CHECK(packed_vector4_array.identity_compare(packed_vector4_array)); + CHECK_FALSE(packed_vector4_array.identity_compare(PackedVector4Array())); + Variant packed_float32_array = PackedFloat32Array(); CHECK(packed_float32_array.identity_compare(packed_float32_array)); CHECK_FALSE(packed_float32_array.identity_compare(PackedFloat32Array())); |