diff options
Diffstat (limited to 'tests')
28 files changed, 1390 insertions, 279 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_image.h b/tests/core/io/test_image.h index 7a0cbb13f9..1b51286a9f 100644 --- a/tests/core/io/test_image.h +++ b/tests/core/io/test_image.h @@ -78,8 +78,8 @@ TEST_CASE("[Image] Instantiation") { TEST_CASE("[Image] Saving and loading") { Ref<Image> image = memnew(Image(4, 4, false, Image::FORMAT_RGBA8)); - const String save_path_png = OS::get_singleton()->get_cache_path().path_join("image.png"); - const String save_path_exr = OS::get_singleton()->get_cache_path().path_join("image.exr"); + const String save_path_png = TestUtils::get_temp_path("image.png"); + const String save_path_exr = TestUtils::get_temp_path("image.exr"); // Save PNG Error err; diff --git a/tests/core/io/test_pck_packer.h b/tests/core/io/test_pck_packer.h index fc4534a949..7ef9451963 100644 --- a/tests/core/io/test_pck_packer.h +++ b/tests/core/io/test_pck_packer.h @@ -42,7 +42,7 @@ namespace TestPCKPacker { TEST_CASE("[PCKPacker] Pack an empty PCK file") { PCKPacker pck_packer; - const String output_pck_path = OS::get_singleton()->get_cache_path().path_join("output_empty.pck"); + const String output_pck_path = TestUtils::get_temp_path("output_empty.pck"); CHECK_MESSAGE( pck_packer.pck_start(output_pck_path) == OK, "Starting a PCK file should return an OK error code."); @@ -66,7 +66,7 @@ TEST_CASE("[PCKPacker] Pack an empty PCK file") { TEST_CASE("[PCKPacker] Pack empty with zero alignment invalid") { PCKPacker pck_packer; - const String output_pck_path = OS::get_singleton()->get_cache_path().path_join("output_empty.pck"); + const String output_pck_path = TestUtils::get_temp_path("output_empty.pck"); ERR_PRINT_OFF; CHECK_MESSAGE(pck_packer.pck_start(output_pck_path, 0) != OK, "PCK with zero alignment should fail."); ERR_PRINT_ON; @@ -74,7 +74,7 @@ TEST_CASE("[PCKPacker] Pack empty with zero alignment invalid") { TEST_CASE("[PCKPacker] Pack empty with invalid key") { PCKPacker pck_packer; - const String output_pck_path = OS::get_singleton()->get_cache_path().path_join("output_empty.pck"); + const String output_pck_path = TestUtils::get_temp_path("output_empty.pck"); ERR_PRINT_OFF; CHECK_MESSAGE(pck_packer.pck_start(output_pck_path, 32, "") != OK, "PCK with invalid key should fail."); ERR_PRINT_ON; @@ -82,7 +82,7 @@ TEST_CASE("[PCKPacker] Pack empty with invalid key") { TEST_CASE("[PCKPacker] Pack a PCK file with some files and directories") { PCKPacker pck_packer; - const String output_pck_path = OS::get_singleton()->get_cache_path().path_join("output_with_files.pck"); + const String output_pck_path = TestUtils::get_temp_path("output_with_files.pck"); CHECK_MESSAGE( pck_packer.pck_start(output_pck_path) == OK, "Starting a PCK file should return an OK error code."); diff --git a/tests/core/io/test_resource.h b/tests/core/io/test_resource.h index a83e7f88ba..cb1fa290b3 100644 --- a/tests/core/io/test_resource.h +++ b/tests/core/io/test_resource.h @@ -76,8 +76,8 @@ TEST_CASE("[Resource] Saving and loading") { Ref<Resource> child_resource = memnew(Resource); child_resource->set_name("I'm a child resource"); resource->set_meta("other_resource", child_resource); - const String save_path_binary = 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"); + const String save_path_binary = TestUtils::get_temp_path("resource.res"); + const String save_path_text = TestUtils::get_temp_path("resource.tres"); ResourceSaver::save(resource, save_path_binary); ResourceSaver::save(resource, save_path_text); @@ -123,8 +123,8 @@ TEST_CASE("[Resource] Breaking circular references on save") { resource_b->set_meta("next", resource_c); resource_c->set_meta("next", resource_b); - 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"); + const String save_path_binary = TestUtils::get_temp_path("resource.res"); + const String save_path_text = TestUtils::get_temp_path("resource.tres"); ResourceSaver::save(resource_a, save_path_binary); // Suppress expected errors caused by the resources above being uncached. ERR_PRINT_OFF; 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_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_2d.h b/tests/core/math/test_transform_2d.h index 36d27ce7a9..6d3c80e5ca 100644 --- a/tests/core/math/test_transform_2d.h +++ b/tests/core/math/test_transform_2d.h @@ -45,48 +45,132 @@ Transform2D identity() { return Transform2D(); } +TEST_CASE("[Transform2D] Default constructor") { + Transform2D default_constructor = Transform2D(); + CHECK(default_constructor == Transform2D(Vector2(1, 0), Vector2(0, 1), Vector2(0, 0))); +} + +TEST_CASE("[Transform2D] Copy constructor") { + Transform2D T = create_dummy_transform(); + Transform2D copy_constructor = Transform2D(T); + CHECK(T == copy_constructor); +} + +TEST_CASE("[Transform2D] Constructor from angle and position") { + constexpr float ROTATION = Math_PI / 4; + const Vector2 TRANSLATION = Vector2(20, -20); + + const Transform2D test = Transform2D(ROTATION, TRANSLATION); + const Transform2D expected = Transform2D().rotated(ROTATION).translated(TRANSLATION); + CHECK(test == expected); +} + +TEST_CASE("[Transform2D] Constructor from angle, scale, skew and position") { + constexpr float ROTATION = Math_PI / 2; + const Vector2 SCALE = Vector2(2, 0.5); + constexpr float SKEW = Math_PI / 4; + const Vector2 TRANSLATION = Vector2(30, 0); + + const Transform2D test = Transform2D(ROTATION, SCALE, SKEW, TRANSLATION); + Transform2D expected = Transform2D().scaled(SCALE).rotated(ROTATION).translated(TRANSLATION); + expected.set_skew(SKEW); + + CHECK(test.is_equal_approx(expected)); +} + +TEST_CASE("[Transform2D] Constructor from raw values") { + const Transform2D test = Transform2D(1, 2, 3, 4, 5, 6); + const Transform2D expected = Transform2D(Vector2(1, 2), Vector2(3, 4), Vector2(5, 6)); + CHECK(test == expected); +} + +TEST_CASE("[Transform2D] xform") { + const Vector2 v = Vector2(2, 3); + const Transform2D T = Transform2D(Vector2(1, 2), Vector2(3, 4), Vector2(5, 6)); + const Vector2 expected = Vector2(1 * 2 + 3 * 3 + 5 * 1, 2 * 2 + 4 * 3 + 6 * 1); + CHECK(T.xform(v) == expected); +} + +TEST_CASE("[Transform2D] Basis xform") { + const Vector2 v = Vector2(2, 2); + const Transform2D T1 = Transform2D(Vector2(1, 2), Vector2(3, 4), Vector2(0, 0)); + + // Both versions should be the same when the origin is (0,0). + CHECK(T1.basis_xform(v) == T1.xform(v)); + + const Transform2D T2 = Transform2D(Vector2(1, 2), Vector2(3, 4), Vector2(5, 6)); + + // Each version should be different when the origin is not (0,0). + CHECK_FALSE(T2.basis_xform(v) == T2.xform(v)); +} + +TEST_CASE("[Transform2D] Affine inverse") { + const Transform2D orig = create_dummy_transform(); + const Transform2D affine_inverted = orig.affine_inverse(); + const Transform2D affine_inverted_again = affine_inverted.affine_inverse(); + CHECK(affine_inverted_again == orig); +} + +TEST_CASE("[Transform2D] Orthonormalized") { + const Transform2D T = create_dummy_transform(); + const Transform2D orthonormalized_T = T.orthonormalized(); + + // Check each basis has length 1. + CHECK(Math::is_equal_approx(orthonormalized_T[0].length_squared(), 1)); + CHECK(Math::is_equal_approx(orthonormalized_T[1].length_squared(), 1)); + + const Vector2 vx = Vector2(orthonormalized_T[0].x, orthonormalized_T[1].x); + const Vector2 vy = Vector2(orthonormalized_T[0].y, orthonormalized_T[1].y); + + // Check the basis are orthogonal. + CHECK(Math::is_equal_approx(orthonormalized_T.tdotx(vx), 1)); + CHECK(Math::is_equal_approx(orthonormalized_T.tdotx(vy), 0)); + CHECK(Math::is_equal_approx(orthonormalized_T.tdoty(vx), 0)); + CHECK(Math::is_equal_approx(orthonormalized_T.tdoty(vy), 1)); +} + TEST_CASE("[Transform2D] translation") { - Vector2 offset = Vector2(1, 2); + const Vector2 offset = Vector2(1, 2); // Both versions should give the same result applied to identity. CHECK(identity().translated(offset) == identity().translated_local(offset)); // Check both versions against left and right multiplications. - Transform2D orig = create_dummy_transform(); - Transform2D T = identity().translated(offset); + const Transform2D orig = create_dummy_transform(); + const Transform2D T = identity().translated(offset); CHECK(orig.translated(offset) == T * orig); CHECK(orig.translated_local(offset) == orig * T); } TEST_CASE("[Transform2D] scaling") { - Vector2 scaling = Vector2(1, 2); + const Vector2 scaling = Vector2(1, 2); // Both versions should give the same result applied to identity. CHECK(identity().scaled(scaling) == identity().scaled_local(scaling)); // Check both versions against left and right multiplications. - Transform2D orig = create_dummy_transform(); - Transform2D S = identity().scaled(scaling); + const Transform2D orig = create_dummy_transform(); + const Transform2D S = identity().scaled(scaling); CHECK(orig.scaled(scaling) == S * orig); CHECK(orig.scaled_local(scaling) == orig * S); } TEST_CASE("[Transform2D] rotation") { - real_t phi = 1.0; + constexpr real_t phi = 1.0; // Both versions should give the same result applied to identity. CHECK(identity().rotated(phi) == identity().rotated_local(phi)); // Check both versions against left and right multiplications. - Transform2D orig = create_dummy_transform(); - Transform2D R = identity().rotated(phi); + const Transform2D orig = create_dummy_transform(); + const Transform2D R = identity().rotated(phi); CHECK(orig.rotated(phi) == R * orig); CHECK(orig.rotated_local(phi) == orig * R); } TEST_CASE("[Transform2D] Interpolation") { - Transform2D rotate_scale_skew_pos = Transform2D(Math::deg_to_rad(170.0), Vector2(3.6, 8.0), Math::deg_to_rad(20.0), Vector2(2.4, 6.8)); - Transform2D rotate_scale_skew_pos_halfway = Transform2D(Math::deg_to_rad(85.0), Vector2(2.3, 4.5), Math::deg_to_rad(10.0), Vector2(1.2, 3.4)); + const Transform2D rotate_scale_skew_pos = Transform2D(Math::deg_to_rad(170.0), Vector2(3.6, 8.0), Math::deg_to_rad(20.0), Vector2(2.4, 6.8)); + const Transform2D rotate_scale_skew_pos_halfway = Transform2D(Math::deg_to_rad(85.0), Vector2(2.3, 4.5), Math::deg_to_rad(10.0), Vector2(1.2, 3.4)); Transform2D interpolated = Transform2D().interpolate_with(rotate_scale_skew_pos, 0.5); CHECK(interpolated.get_origin().is_equal_approx(rotate_scale_skew_pos_halfway.get_origin())); CHECK(interpolated.get_rotation() == doctest::Approx(rotate_scale_skew_pos_halfway.get_rotation())); @@ -98,8 +182,8 @@ TEST_CASE("[Transform2D] Interpolation") { } TEST_CASE("[Transform2D] Finite number checks") { - const Vector2 x(0, 1); - const Vector2 infinite(NAN, NAN); + const Vector2 x = Vector2(0, 1); + const Vector2 infinite = Vector2(NAN, NAN); CHECK_MESSAGE( Transform2D(x, x, x).is_finite(), diff --git a/tests/core/object/test_class_db.h b/tests/core/object/test_class_db.h index fb62d0f056..381d759e5b 100644 --- a/tests/core/object/test_class_db.h +++ b/tests/core/object/test_class_db.h @@ -195,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; } @@ -355,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), "'."); } } @@ -367,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), "'."); } } @@ -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 d714d71416..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" @@ -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/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/create_test.py b/tests/create_test.py index deb53aca20..deb53aca20 100644..100755 --- a/tests/create_test.py +++ b/tests/create_test.py diff --git a/tests/display_server_mock.h b/tests/display_server_mock.h index fd79a46c5c..e4946995a7 100644 --- a/tests/display_server_mock.h +++ b/tests/display_server_mock.h @@ -56,7 +56,7 @@ private: return drivers; } - static DisplayServer *create_func(const String &p_rendering_driver, DisplayServer::WindowMode p_mode, DisplayServer::VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i *p_position, const Vector2i &p_resolution, int p_screen, Error &r_error) { + static DisplayServer *create_func(const String &p_rendering_driver, DisplayServer::WindowMode p_mode, DisplayServer::VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i *p_position, const Vector2i &p_resolution, int p_screen, Context p_context, Error &r_error) { r_error = OK; RasterizerDummy::make_current(); return memnew(DisplayServerMock()); diff --git a/tests/python_build/fixtures/gles3/vertex_fragment_expected_full.glsl b/tests/python_build/fixtures/gles3/vertex_fragment_expected_full.glsl index 58ddd08cdd..8ad5a23eb5 100644 --- a/tests/python_build/fixtures/gles3/vertex_fragment_expected_full.glsl +++ b/tests/python_build/fixtures/gles3/vertex_fragment_expected_full.glsl @@ -48,4 +48,3 @@ protected: }; #endif - diff --git a/tests/python_build/test_gles3_builder.py b/tests/python_build/test_gles3_builder.py index 6f16139eb9..b34d33bde7 100644 --- a/tests/python_build/test_gles3_builder.py +++ b/tests/python_build/test_gles3_builder.py @@ -2,7 +2,7 @@ import json import pytest -from gles3_builders import build_gles3_header, GLES3HeaderStruct +from gles3_builders import GLES3HeaderStruct, build_gles3_header @pytest.mark.parametrize( diff --git a/tests/python_build/test_glsl_builder.py b/tests/python_build/test_glsl_builder.py index 348ef8441c..9f548855ff 100644 --- a/tests/python_build/test_glsl_builder.py +++ b/tests/python_build/test_glsl_builder.py @@ -2,7 +2,7 @@ import json import pytest -from glsl_builders import build_raw_header, RAWHeaderStruct, build_rd_header, RDHeaderStruct +from glsl_builders import RAWHeaderStruct, RDHeaderStruct, build_raw_header, build_rd_header @pytest.mark.parametrize( diff --git a/tests/scene/test_audio_stream_wav.h b/tests/scene/test_audio_stream_wav.h index ed1697929e..e8f3c9e8f5 100644 --- a/tests/scene/test_audio_stream_wav.h +++ b/tests/scene/test_audio_stream_wav.h @@ -115,7 +115,7 @@ Vector<uint8_t> gen_pcm16_test(float wav_rate, int wav_count, bool stereo) { } void run_test(String file_name, AudioStreamWAV::Format data_format, bool stereo, float wav_rate, float wav_count) { - String save_path = OS::get_singleton()->get_cache_path().path_join(file_name); + String save_path = TestUtils::get_temp_path(file_name); Vector<uint8_t> test_data; if (data_format == AudioStreamWAV::FORMAT_8_BITS) { @@ -200,7 +200,7 @@ TEST_CASE("[AudioStreamWAV] Alternate mix rate") { } TEST_CASE("[AudioStreamWAV] save_to_wav() adds '.wav' file extension automatically") { - String save_path = OS::get_singleton()->get_cache_path().path_join("test_wav_extension"); + String save_path = TestUtils::get_temp_path("test_wav_extension"); Vector<uint8_t> test_data = gen_pcm8_test(WAV_RATE, WAV_COUNT, false); Ref<AudioStreamWAV> stream = memnew(AudioStreamWAV); stream->set_data(test_data); @@ -230,7 +230,7 @@ TEST_CASE("[AudioStreamWAV] Save empty file") { } TEST_CASE("[AudioStreamWAV] Saving IMA ADPCM is not supported") { - String save_path = OS::get_singleton()->get_cache_path().path_join("test_adpcm.wav"); + String save_path = TestUtils::get_temp_path("test_adpcm.wav"); Ref<AudioStreamWAV> stream = memnew(AudioStreamWAV); stream->set_format(AudioStreamWAV::FORMAT_IMA_ADPCM); ERR_PRINT_OFF; diff --git a/tests/scene/test_code_edit.h b/tests/scene/test_code_edit.h index c02830b6df..317dbe9ab9 100644 --- a/tests/scene/test_code_edit.h +++ b/tests/scene/test_code_edit.h @@ -3331,6 +3331,45 @@ TEST_CASE("[SceneTree][CodeEdit] folding") { CHECK_FALSE(code_edit->is_line_folded(1)); } + SUBCASE("[CodeEdit] actions unfold") { + // add_selection_for_next_occurrence unfolds. + code_edit->set_text("test\n\tline1 test\n\t\tline 2\ntest2"); + code_edit->select(0, 0, 0, 4); + code_edit->fold_line(0); + CHECK(code_edit->is_line_folded(0)); + code_edit->add_selection_for_next_occurrence(); + + CHECK(code_edit->get_caret_count() == 2); + CHECK(code_edit->has_selection(0)); + CHECK(code_edit->get_caret_line() == 0); + CHECK(code_edit->get_selection_origin_line() == 0); + CHECK(code_edit->get_caret_column() == 4); + CHECK(code_edit->get_selection_origin_column() == 0); + CHECK(code_edit->has_selection(1)); + CHECK(code_edit->get_caret_line(1) == 1); + CHECK(code_edit->get_selection_origin_line(1) == 1); + CHECK(code_edit->get_caret_column(1) == 11); + CHECK(code_edit->get_selection_origin_column(1) == 7); + CHECK_FALSE(code_edit->is_line_folded(0)); + code_edit->remove_secondary_carets(); + + // skip_selection_for_next_occurrence unfolds. + code_edit->select(0, 0, 0, 4); + code_edit->fold_line(0); + CHECK(code_edit->is_line_folded(0)); + code_edit->skip_selection_for_next_occurrence(); + + CHECK(code_edit->get_caret_count() == 1); + CHECK(code_edit->has_selection(0)); + CHECK(code_edit->get_caret_line() == 1); + CHECK(code_edit->get_selection_origin_line() == 1); + CHECK(code_edit->get_caret_column() == 11); + CHECK(code_edit->get_selection_origin_column() == 7); + CHECK_FALSE(code_edit->is_line_folded(0)); + code_edit->remove_secondary_carets(); + code_edit->deselect(); + } + SUBCASE("[CodeEdit] toggle folding carets") { code_edit->set_text("test\n\tline1\ntest2\n\tline2"); diff --git a/tests/scene/test_curve_2d.h b/tests/scene/test_curve_2d.h index 099f6fefa9..1248632630 100644 --- a/tests/scene/test_curve_2d.h +++ b/tests/scene/test_curve_2d.h @@ -147,13 +147,19 @@ TEST_CASE("[Curve2D] Sampling") { CHECK(curve->samplef(1) == Vector2(0, 50)); } - SUBCASE("sample_baked") { + SUBCASE("sample_baked, cubic = false") { CHECK(curve->sample_baked(curve->get_closest_offset(Vector2(0, 0))) == Vector2(0, 0)); CHECK(curve->sample_baked(curve->get_closest_offset(Vector2(0, 25))) == Vector2(0, 25)); CHECK(curve->sample_baked(curve->get_closest_offset(Vector2(0, 50))) == Vector2(0, 50)); } - SUBCASE("sample_baked_with_rotation") { + SUBCASE("sample_baked, cubic = true") { + CHECK(curve->sample_baked(curve->get_closest_offset(Vector2(0, 0)), true) == Vector2(0, 0)); + CHECK(curve->sample_baked(curve->get_closest_offset(Vector2(0, 25)), true) == Vector2(0, 25)); + CHECK(curve->sample_baked(curve->get_closest_offset(Vector2(0, 50)), true) == Vector2(0, 50)); + } + + SUBCASE("sample_baked_with_rotation, cubic = false") { const real_t pi = 3.14159; const real_t half_pi = pi * 0.5; Ref<Curve2D> rot_curve = memnew(Curve2D); @@ -188,6 +194,41 @@ TEST_CASE("[Curve2D] Sampling") { CHECK(Math::is_equal_approx(t.get_rotation(), -half_pi)); } + SUBCASE("sample_baked_with_rotation, cubic = true") { + const real_t pi = 3.14159; + const real_t half_pi = pi * 0.5; + Ref<Curve2D> rot_curve = memnew(Curve2D); + Transform2D t; + + rot_curve->clear_points(); + rot_curve->add_point(Vector2()); + rot_curve->add_point(Vector2(50, 0)); + t = rot_curve->sample_baked_with_rotation(25, true); + CHECK(t.get_origin() == Vector2(25, 0)); + CHECK(Math::is_equal_approx(t.get_rotation(), 0)); + + rot_curve->clear_points(); + rot_curve->add_point(Vector2()); + rot_curve->add_point(Vector2(0, 50)); + t = rot_curve->sample_baked_with_rotation(25, true); + CHECK(t.get_origin() == Vector2(0, 25)); + CHECK(Math::is_equal_approx(t.get_rotation(), half_pi)); + + rot_curve->clear_points(); + rot_curve->add_point(Vector2()); + rot_curve->add_point(Vector2(-50, 0)); + t = rot_curve->sample_baked_with_rotation(25, true); + CHECK(t.get_origin() == Vector2(-25, 0)); + CHECK(Math::is_equal_approx(t.get_rotation(), pi)); + + rot_curve->clear_points(); + rot_curve->add_point(Vector2()); + rot_curve->add_point(Vector2(0, -50)); + t = rot_curve->sample_baked_with_rotation(25, true); + CHECK(t.get_origin() == Vector2(0, -25)); + CHECK(Math::is_equal_approx(t.get_rotation(), -half_pi)); + } + SUBCASE("get_closest_point") { CHECK(curve->get_closest_point(Vector2(0, 0)) == Vector2(0, 0)); CHECK(curve->get_closest_point(Vector2(0, 25)) == Vector2(0, 25)); diff --git a/tests/scene/test_curve_3d.h b/tests/scene/test_curve_3d.h index d73bb1ad35..2e60a9c6e6 100644 --- a/tests/scene/test_curve_3d.h +++ b/tests/scene/test_curve_3d.h @@ -177,12 +177,30 @@ TEST_CASE("[Curve3D] Sampling") { CHECK(curve->sample_baked(curve->get_closest_offset(Vector3(0, 50, 0)), true) == Vector3(0, 50, 0)); } - SUBCASE("sample_baked_with_rotation") { + SUBCASE("sample_baked_with_rotation, cubic = false, p_apply_tilt = false") { CHECK(curve->sample_baked_with_rotation(curve->get_closest_offset(Vector3(0, 0, 0))) == Transform3D(Basis(Vector3(0, 0, -1), Vector3(1, 0, 0), Vector3(0, -1, 0)), Vector3(0, 0, 0))); CHECK(curve->sample_baked_with_rotation(curve->get_closest_offset(Vector3(0, 25, 0))) == Transform3D(Basis(Vector3(0, 0, -1), Vector3(1, 0, 0), Vector3(0, -1, 0)), Vector3(0, 25, 0))); CHECK(curve->sample_baked_with_rotation(curve->get_closest_offset(Vector3(0, 50, 0))) == Transform3D(Basis(Vector3(0, 0, -1), Vector3(1, 0, 0), Vector3(0, -1, 0)), Vector3(0, 50, 0))); } + SUBCASE("sample_baked_with_rotation, cubic = false, p_apply_tilt = true") { + CHECK(curve->sample_baked_with_rotation(curve->get_closest_offset(Vector3(0, 0, 0)), false, true) == Transform3D(Basis(Vector3(0, 0, -1), Vector3(1, 0, 0), Vector3(0, -1, 0)), Vector3(0, 0, 0))); + CHECK(curve->sample_baked_with_rotation(curve->get_closest_offset(Vector3(0, 25, 0)), false, true) == Transform3D(Basis(Vector3(0, 0, -1), Vector3(1, 0, 0), Vector3(0, -1, 0)), Vector3(0, 25, 0))); + CHECK(curve->sample_baked_with_rotation(curve->get_closest_offset(Vector3(0, 50, 0)), false, true) == Transform3D(Basis(Vector3(0, 0, -1), Vector3(1, 0, 0), Vector3(0, -1, 0)), Vector3(0, 50, 0))); + } + + SUBCASE("sample_baked_with_rotation, cubic = true, p_apply_tilt = false") { + CHECK(curve->sample_baked_with_rotation(curve->get_closest_offset(Vector3(0, 0, 0)), true) == Transform3D(Basis(Vector3(0, 0, -1), Vector3(1, 0, 0), Vector3(0, -1, 0)), Vector3(0, 0, 0))); + CHECK(curve->sample_baked_with_rotation(curve->get_closest_offset(Vector3(0, 25, 0)), true) == Transform3D(Basis(Vector3(0, 0, -1), Vector3(1, 0, 0), Vector3(0, -1, 0)), Vector3(0, 25, 0))); + CHECK(curve->sample_baked_with_rotation(curve->get_closest_offset(Vector3(0, 50, 0)), true) == Transform3D(Basis(Vector3(0, 0, -1), Vector3(1, 0, 0), Vector3(0, -1, 0)), Vector3(0, 50, 0))); + } + + SUBCASE("sample_baked_with_rotation, cubic = true, p_apply_tilt = true") { + CHECK(curve->sample_baked_with_rotation(curve->get_closest_offset(Vector3(0, 0, 0)), true, true) == Transform3D(Basis(Vector3(0, 0, -1), Vector3(1, 0, 0), Vector3(0, -1, 0)), Vector3(0, 0, 0))); + CHECK(curve->sample_baked_with_rotation(curve->get_closest_offset(Vector3(0, 25, 0)), true, true) == Transform3D(Basis(Vector3(0, 0, -1), Vector3(1, 0, 0), Vector3(0, -1, 0)), Vector3(0, 25, 0))); + CHECK(curve->sample_baked_with_rotation(curve->get_closest_offset(Vector3(0, 50, 0)), true, true) == Transform3D(Basis(Vector3(0, 0, -1), Vector3(1, 0, 0), Vector3(0, -1, 0)), Vector3(0, 50, 0))); + } + SUBCASE("sample_baked_tilt") { CHECK(curve->sample_baked_tilt(curve->get_closest_offset(Vector3(0, 0, 0))) == 0); CHECK(curve->sample_baked_tilt(curve->get_closest_offset(Vector3(0, 25, 0))) == 0); diff --git a/tests/scene/test_image_texture_3d.h b/tests/scene/test_image_texture_3d.h new file mode 100644 index 0000000000..f2a7abcf69 --- /dev/null +++ b/tests/scene/test_image_texture_3d.h @@ -0,0 +1,101 @@ +/**************************************************************************/ +/* test_image_texture_3d.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef TEST_IMAGE_TEXTURE_3D_H +#define TEST_IMAGE_TEXTURE_3D_H + +#include "core/io/image.h" +#include "scene/resources/image_texture.h" + +#include "tests/test_macros.h" +#include "tests/test_utils.h" + +namespace TestImageTexture3D { + +// [SceneTree] in a test case name enables initializing a mock render server, +// which ImageTexture3D is dependent on. +TEST_CASE("[SceneTree][ImageTexture3D] Constructor") { + Ref<ImageTexture3D> image_texture_3d = memnew(ImageTexture3D); + CHECK(image_texture_3d->get_format() == Image::FORMAT_L8); + CHECK(image_texture_3d->get_width() == 1); + CHECK(image_texture_3d->get_height() == 1); + CHECK(image_texture_3d->get_depth() == 1); + CHECK(image_texture_3d->has_mipmaps() == false); +} + +TEST_CASE("[SceneTree][ImageTexture3D] get_format") { + Ref<ImageTexture3D> image_texture_3d = memnew(ImageTexture3D); + CHECK(image_texture_3d->get_format() == Image::FORMAT_L8); +} + +TEST_CASE("[SceneTree][ImageTexture3D] get_width") { + Ref<ImageTexture3D> image_texture_3d = memnew(ImageTexture3D); + CHECK(image_texture_3d->get_width() == 1); +} + +TEST_CASE("[SceneTree][ImageTexture3D] get_height") { + Ref<ImageTexture3D> image_texture_3d = memnew(ImageTexture3D); + CHECK(image_texture_3d->get_height() == 1); +} + +TEST_CASE("[SceneTree][ImageTexture3D] get_depth") { + Ref<ImageTexture3D> image_texture_3d = memnew(ImageTexture3D); + CHECK(image_texture_3d->get_depth() == 1); +} + +TEST_CASE("[SceneTree][ImageTexture3D] has_mipmaps") { + const Vector<Ref<Image>> images = { memnew(Image(8, 8, false, Image::FORMAT_RGBA8)), memnew(Image(8, 8, false, Image::FORMAT_RGBA8)) }; + Ref<ImageTexture3D> image_texture_3d = memnew(ImageTexture3D); + CHECK(image_texture_3d->has_mipmaps() == false); // No mipmaps. + image_texture_3d->create(Image::FORMAT_RGBA8, 2, 2, 2, true, images); + CHECK(image_texture_3d->has_mipmaps() == true); // Mipmaps. +} + +TEST_CASE("[SceneTree][ImageTexture3D] create") { + const Vector<Ref<Image>> images = { memnew(Image(8, 8, false, Image::FORMAT_RGBA8)), memnew(Image(8, 8, false, Image::FORMAT_RGBA8)) }; + Ref<ImageTexture3D> image_texture_3d = memnew(ImageTexture3D); + CHECK(image_texture_3d->create(Image::FORMAT_RGBA8, 2, 2, 2, true, images) == OK); // Run create and check return value simultaneously. + CHECK(image_texture_3d->get_format() == Image::FORMAT_RGBA8); + CHECK(image_texture_3d->get_width() == 2); + CHECK(image_texture_3d->get_height() == 2); + CHECK(image_texture_3d->get_depth() == 2); + CHECK(image_texture_3d->has_mipmaps() == true); +} + +TEST_CASE("[SceneTree][ImageTexture3D] set_path") { + Ref<ImageTexture3D> image_texture_3d = memnew(ImageTexture3D); + String path = TestUtils::get_data_path("images/icon.png"); + image_texture_3d->set_path(path, true); + CHECK(image_texture_3d->get_path() == path); +} + +} //namespace TestImageTexture3D + +#endif // TEST_IMAGE_TEXTURE_3D_H diff --git a/tests/scene/test_instance_placeholder.h b/tests/scene/test_instance_placeholder.h new file mode 100644 index 0000000000..d915c5d961 --- /dev/null +++ b/tests/scene/test_instance_placeholder.h @@ -0,0 +1,532 @@ +/**************************************************************************/ +/* test_instance_placeholder.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_INSTANCE_PLACEHOLDER_H +#define TEST_INSTANCE_PLACEHOLDER_H + +#include "scene/main/instance_placeholder.h" +#include "scene/resources/packed_scene.h" + +#include "tests/test_macros.h" + +class _TestInstancePlaceholderNode : public Node { + GDCLASS(_TestInstancePlaceholderNode, Node); + +protected: + static void _bind_methods() { + ClassDB::bind_method(D_METHOD("set_int_property", "int_property"), &_TestInstancePlaceholderNode::set_int_property); + ClassDB::bind_method(D_METHOD("get_int_property"), &_TestInstancePlaceholderNode::get_int_property); + + ADD_PROPERTY(PropertyInfo(Variant::INT, "int_property"), "set_int_property", "get_int_property"); + + ClassDB::bind_method(D_METHOD("set_reference_property", "reference_property"), &_TestInstancePlaceholderNode::set_reference_property); + ClassDB::bind_method(D_METHOD("get_reference_property"), &_TestInstancePlaceholderNode::get_reference_property); + + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "reference_property", PROPERTY_HINT_NODE_TYPE), "set_reference_property", "get_reference_property"); + + ClassDB::bind_method(D_METHOD("set_reference_array_property", "reference_array_property"), &_TestInstancePlaceholderNode::set_reference_array_property); + ClassDB::bind_method(D_METHOD("get_reference_array_property"), &_TestInstancePlaceholderNode::get_reference_array_property); + + // The hint string value "24/34:Node" is determined from existing PackedScenes with typed Array properties. + ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "reference_array_property", PROPERTY_HINT_TYPE_STRING, "24/34:Node"), "set_reference_array_property", "get_reference_array_property"); + } + +public: + int int_property = 0; + + void set_int_property(int p_int) { + int_property = p_int; + } + + int get_int_property() const { + return int_property; + } + + Variant reference_property; + + void set_reference_property(const Variant &p_node) { + reference_property = p_node; + } + + Variant get_reference_property() const { + return reference_property; + } + + Array reference_array_property; + + void set_reference_array_property(const Array &p_array) { + reference_array_property = p_array; + } + + Array get_reference_array_property() const { + return reference_array_property; + } + + _TestInstancePlaceholderNode() { + reference_array_property.set_typed(Variant::OBJECT, "Node", Variant()); + } +}; + +namespace TestInstancePlaceholder { + +TEST_CASE("[SceneTree][InstancePlaceholder] Instantiate from placeholder with no overrides") { + GDREGISTER_CLASS(_TestInstancePlaceholderNode); + + SUBCASE("with non-node values") { + InstancePlaceholder *ip = memnew(InstancePlaceholder); + ip->set_name("TestScene"); + Node *root = memnew(Node); + SceneTree::get_singleton()->get_root()->add_child(root); + + root->add_child(ip); + // Create a scene to instance. + _TestInstancePlaceholderNode *scene = memnew(_TestInstancePlaceholderNode); + scene->set_int_property(12); + + // Pack the scene. + PackedScene *packed_scene = memnew(PackedScene); + const Error err = packed_scene->pack(scene); + REQUIRE(err == OK); + + // Instantiate the scene. + _TestInstancePlaceholderNode *created = Object::cast_to<_TestInstancePlaceholderNode>(ip->create_instance(true, packed_scene)); + REQUIRE(created != nullptr); + CHECK(created->get_name() == "TestScene"); + CHECK(created->get_int_property() == 12); + + root->queue_free(); + memdelete(scene); + } + + SUBCASE("with node value") { + InstancePlaceholder *ip = memnew(InstancePlaceholder); + ip->set_name("TestScene"); + Node *root = memnew(Node); + SceneTree::get_singleton()->get_root()->add_child(root); + + root->add_child(ip); + // Create a scene to instance. + _TestInstancePlaceholderNode *scene = memnew(_TestInstancePlaceholderNode); + Node *referenced = memnew(Node); + scene->add_child(referenced); + referenced->set_owner(scene); + scene->set_reference_property(referenced); + // Pack the scene. + PackedScene *packed_scene = memnew(PackedScene); + const Error err = packed_scene->pack(scene); + REQUIRE(err == OK); + + // Instantiate the scene. + _TestInstancePlaceholderNode *created = Object::cast_to<_TestInstancePlaceholderNode>(ip->create_instance(true, packed_scene)); + REQUIRE(created != nullptr); + CHECK(created->get_name() == "TestScene"); + CHECK(created->get_child_count() == 1); + CHECK(created->get_reference_property().identity_compare(created->get_child(0, false))); + CHECK_FALSE(created->get_reference_property().identity_compare(referenced)); + + root->queue_free(); + memdelete(scene); + } + + SUBCASE("with node-array value") { + InstancePlaceholder *ip = memnew(InstancePlaceholder); + ip->set_name("TestScene"); + Node *root = memnew(Node); + SceneTree::get_singleton()->get_root()->add_child(root); + + root->add_child(ip); + // Create a scene to instance. + _TestInstancePlaceholderNode *scene = memnew(_TestInstancePlaceholderNode); + Node *referenced1 = memnew(Node); + Node *referenced2 = memnew(Node); + scene->add_child(referenced1); + scene->add_child(referenced2); + referenced1->set_owner(scene); + referenced2->set_owner(scene); + Array node_array; + node_array.set_typed(Variant::OBJECT, "Node", Variant()); + node_array.push_back(referenced1); + node_array.push_back(referenced2); + scene->set_reference_array_property(node_array); + // Pack the scene. + PackedScene *packed_scene = memnew(PackedScene); + const Error err = packed_scene->pack(scene); + REQUIRE(err == OK); + + // Instantiate the scene. + _TestInstancePlaceholderNode *created = Object::cast_to<_TestInstancePlaceholderNode>(ip->create_instance(true, packed_scene)); + REQUIRE(created != nullptr); + CHECK(created->get_name() == "TestScene"); + CHECK(created->get_child_count() == 2); + Array created_array = created->get_reference_array_property(); + REQUIRE(created_array.size() == node_array.size()); + REQUIRE(created_array.size() == created->get_child_count()); + + // Iterate over all nodes, since the ordering is not guaranteed. + for (int i = 0; i < node_array.size(); i++) { + bool node_found = false; + for (int j = 0; j < created->get_child_count(); j++) { + if (created_array[i].identity_compare(created->get_child(j, true))) { + node_found = true; + } + } + CHECK(node_found); + } + root->queue_free(); + memdelete(scene); + } +} + +TEST_CASE("[SceneTree][InstancePlaceholder] Instantiate from placeholder with overrides") { + GDREGISTER_CLASS(_TestInstancePlaceholderNode); + + SUBCASE("with non-node values") { + InstancePlaceholder *ip = memnew(InstancePlaceholder); + Node *root = memnew(Node); + SceneTree::get_singleton()->get_root()->add_child(root); + + root->add_child(ip); + ip->set_name("TestScene"); + ip->set("int_property", 45); + // Create a scene to pack. + _TestInstancePlaceholderNode *scene = memnew(_TestInstancePlaceholderNode); + scene->set_int_property(12); + + // Pack the scene. + PackedScene *packed_scene = memnew(PackedScene); + packed_scene->pack(scene); + + // Instantiate the scene. + _TestInstancePlaceholderNode *created = Object::cast_to<_TestInstancePlaceholderNode>(ip->create_instance(true, packed_scene)); + REQUIRE(created != nullptr); + CHECK(created->get_int_property() == 45); + + root->queue_free(); + memdelete(scene); + } + + SUBCASE("with node values") { + InstancePlaceholder *ip = memnew(InstancePlaceholder); + ip->set_name("TestScene"); + Node *root = memnew(Node); + Node *overriding = memnew(Node); + SceneTree::get_singleton()->get_root()->add_child(root); + + root->add_child(ip); + root->add_child(overriding); + ip->set("reference_property", overriding); + // Create a scene to instance. + _TestInstancePlaceholderNode *scene = memnew(_TestInstancePlaceholderNode); + Node *referenced = memnew(Node); + scene->add_child(referenced); + referenced->set_owner(scene); + scene->set_reference_property(referenced); + // Pack the scene. + PackedScene *packed_scene = memnew(PackedScene); + const Error err = packed_scene->pack(scene); + REQUIRE(err == OK); + + // Instantiate the scene. + _TestInstancePlaceholderNode *created = Object::cast_to<_TestInstancePlaceholderNode>(ip->create_instance(true, packed_scene)); + REQUIRE(created != nullptr); + CHECK(created->get_name() == "TestScene"); + CHECK(created->get_child_count() == 1); + CHECK(created->get_reference_property().identity_compare(overriding)); + CHECK_FALSE(created->get_reference_property().identity_compare(referenced)); + + root->queue_free(); + memdelete(scene); + } + + SUBCASE("with node-array value") { + InstancePlaceholder *ip = memnew(InstancePlaceholder); + ip->set_name("TestScene"); + Node *root = memnew(Node); + SceneTree::get_singleton()->get_root()->add_child(root); + + Node *override1 = memnew(Node); + Node *override2 = memnew(Node); + Node *override3 = memnew(Node); + root->add_child(ip); + root->add_child(override1); + root->add_child(override2); + root->add_child(override3); + + Array override_node_array; + override_node_array.set_typed(Variant::OBJECT, "Node", Variant()); + override_node_array.push_back(override1); + override_node_array.push_back(override2); + override_node_array.push_back(override3); + + ip->set("reference_array_property", override_node_array); + + // Create a scene to instance. + _TestInstancePlaceholderNode *scene = memnew(_TestInstancePlaceholderNode); + Node *referenced1 = memnew(Node); + Node *referenced2 = memnew(Node); + + scene->add_child(referenced1); + scene->add_child(referenced2); + + referenced1->set_owner(scene); + referenced2->set_owner(scene); + Array referenced_array; + referenced_array.set_typed(Variant::OBJECT, "Node", Variant()); + referenced_array.push_back(referenced1); + referenced_array.push_back(referenced2); + + scene->set_reference_array_property(referenced_array); + // Pack the scene. + PackedScene *packed_scene = memnew(PackedScene); + const Error err = packed_scene->pack(scene); + REQUIRE(err == OK); + + // Instantiate the scene. + _TestInstancePlaceholderNode *created = Object::cast_to<_TestInstancePlaceholderNode>(ip->create_instance(true, packed_scene)); + REQUIRE(created != nullptr); + CHECK(created->get_name() == "TestScene"); + CHECK(created->get_child_count() == 2); + Array created_array = created->get_reference_array_property(); + REQUIRE_FALSE(created_array.size() == referenced_array.size()); + REQUIRE(created_array.size() == override_node_array.size()); + REQUIRE_FALSE(created_array.size() == created->get_child_count()); + + // Iterate over all nodes, since the ordering is not guaranteed. + for (int i = 0; i < override_node_array.size(); i++) { + bool node_found = false; + for (int j = 0; j < created_array.size(); j++) { + if (override_node_array[i].identity_compare(created_array[j])) { + node_found = true; + } + } + CHECK(node_found); + } + root->queue_free(); + memdelete(scene); + } +} + +TEST_CASE("[SceneTree][InstancePlaceholder] Instance a PackedScene containing an InstancePlaceholder with no overrides") { + GDREGISTER_CLASS(_TestInstancePlaceholderNode); + + // Create the internal scene. + _TestInstancePlaceholderNode *internal = memnew(_TestInstancePlaceholderNode); + internal->set_name("InternalNode"); + Node *referenced = memnew(Node); + referenced->set_name("OriginalReference"); + internal->add_child(referenced); + referenced->set_owner(internal); + internal->set_reference_property(referenced); + + // Pack the internal scene. + PackedScene *internal_scene = memnew(PackedScene); + Error err = internal_scene->pack(internal); + REQUIRE(err == OK); + + const String internal_path = TestUtils::get_temp_path("instance_placeholder_test_internal.tscn"); + err = ResourceSaver::save(internal_scene, internal_path); + REQUIRE(err == OK); + + Ref<PackedScene> internal_scene_loaded = ResourceLoader::load(internal_path, "PackedScene", ResourceFormatLoader::CacheMode::CACHE_MODE_IGNORE, &err); + REQUIRE(err == OK); + + // Create the main scene. + Node *root = memnew(Node); + root->set_name("MainNode"); + Node *overriding = memnew(Node); + overriding->set_name("OverridingReference"); + + _TestInstancePlaceholderNode *internal_created = Object::cast_to<_TestInstancePlaceholderNode>(internal_scene_loaded->instantiate(PackedScene::GEN_EDIT_STATE_MAIN_INHERITED)); + internal_created->set_scene_instance_load_placeholder(true); + root->add_child(internal_created); + internal_created->set_owner(root); + + root->add_child(overriding); + overriding->set_owner(root); + // Here we introduce an error, we override the property with an internal node to the instance placeholder. + // The InstancePlaceholder is now forced to properly resolve the Node. + internal_created->set("reference_property", NodePath("OriginalReference")); + + // Pack the main scene. + PackedScene *main_scene = memnew(PackedScene); + err = main_scene->pack(root); + REQUIRE(err == OK); + + const String main_path = TestUtils::get_temp_path("instance_placeholder_test_main.tscn"); + err = ResourceSaver::save(main_scene, main_path); + REQUIRE(err == OK); + + // // Instantiate the scene. + Ref<PackedScene> main_scene_loaded = ResourceLoader::load(main_path, "PackedScene", ResourceFormatLoader::CacheMode::CACHE_MODE_IGNORE, &err); + REQUIRE(err == OK); + + Node *instanced_main_node = main_scene_loaded->instantiate(); + REQUIRE(instanced_main_node != nullptr); + SceneTree::get_singleton()->get_root()->add_child(instanced_main_node); + CHECK(instanced_main_node->get_name() == "MainNode"); + REQUIRE(instanced_main_node->get_child_count() == 2); + InstancePlaceholder *instanced_placeholder = Object::cast_to<InstancePlaceholder>(instanced_main_node->get_child(0, true)); + REQUIRE(instanced_placeholder != nullptr); + + _TestInstancePlaceholderNode *final_node = Object::cast_to<_TestInstancePlaceholderNode>(instanced_placeholder->create_instance(true)); + REQUIRE(final_node != nullptr); + REQUIRE(final_node->get_child_count() == 1); + REQUIRE(final_node->get_reference_property().identity_compare(final_node->get_child(0, true))); + + instanced_main_node->queue_free(); + memdelete(overriding); + memdelete(root); + memdelete(internal); + DirAccess::remove_file_or_error(internal_path); + DirAccess::remove_file_or_error(main_path); +} + +TEST_CASE("[SceneTree][InstancePlaceholder] Instance a PackedScene containing an InstancePlaceholder with overrides") { + GDREGISTER_CLASS(_TestInstancePlaceholderNode); + + // Create the internal scene. + _TestInstancePlaceholderNode *internal = memnew(_TestInstancePlaceholderNode); + internal->set_name("InternalNode"); + Node *referenced = memnew(Node); + referenced->set_name("OriginalReference"); + internal->add_child(referenced); + referenced->set_owner(internal); + internal->set_reference_property(referenced); + + Node *array_ref1 = memnew(Node); + array_ref1->set_name("ArrayRef1"); + internal->add_child(array_ref1); + array_ref1->set_owner(internal); + Node *array_ref2 = memnew(Node); + array_ref2->set_name("ArrayRef2"); + internal->add_child(array_ref2); + array_ref2->set_owner(internal); + Array referenced_array; + referenced_array.set_typed(Variant::OBJECT, "Node", Variant()); + referenced_array.push_back(array_ref1); + referenced_array.push_back(array_ref2); + internal->set_reference_array_property(referenced_array); + + // Pack the internal scene. + PackedScene *internal_scene = memnew(PackedScene); + Error err = internal_scene->pack(internal); + REQUIRE(err == OK); + + const String internal_path = TestUtils::get_temp_path("instance_placeholder_test_internal_override.tscn"); + err = ResourceSaver::save(internal_scene, internal_path); + REQUIRE(err == OK); + + Ref<PackedScene> internal_scene_loaded = ResourceLoader::load(internal_path, "PackedScene", ResourceFormatLoader::CacheMode::CACHE_MODE_IGNORE, &err); + REQUIRE(err == OK); + + // Create the main scene. + Node *root = memnew(Node); + root->set_name("MainNode"); + Node *overriding = memnew(Node); + overriding->set_name("OverridingReference"); + Node *array_ext = memnew(Node); + array_ext->set_name("ExternalArrayMember"); + + _TestInstancePlaceholderNode *internal_created = Object::cast_to<_TestInstancePlaceholderNode>(internal_scene_loaded->instantiate(PackedScene::GEN_EDIT_STATE_MAIN_INHERITED)); + internal_created->set_scene_instance_load_placeholder(true); + root->add_child(internal_created); + internal_created->set_owner(root); + + root->add_child(overriding); + overriding->set_owner(root); + root->add_child(array_ext); + array_ext->set_owner(root); + // Here we introduce an error, we override the property with an internal node to the instance placeholder. + // The InstancePlaceholder is now forced to properly resolve the Node. + internal_created->set_reference_property(overriding); + Array internal_array = internal_created->get_reference_array_property(); + Array override_array; + override_array.set_typed(Variant::OBJECT, "Node", Variant()); + for (int i = 0; i < internal_array.size(); i++) { + override_array.push_back(internal_array[i]); + } + override_array.push_back(array_ext); + internal_created->set_reference_array_property(override_array); + + // Pack the main scene. + PackedScene *main_scene = memnew(PackedScene); + err = main_scene->pack(root); + REQUIRE(err == OK); + + const String main_path = TestUtils::get_temp_path("instance_placeholder_test_main_override.tscn"); + err = ResourceSaver::save(main_scene, main_path); + REQUIRE(err == OK); + + // // Instantiate the scene. + Ref<PackedScene> main_scene_loaded = ResourceLoader::load(main_path, "PackedScene", ResourceFormatLoader::CacheMode::CACHE_MODE_IGNORE, &err); + REQUIRE(err == OK); + + Node *instanced_main_node = main_scene_loaded->instantiate(); + REQUIRE(instanced_main_node != nullptr); + SceneTree::get_singleton()->get_root()->add_child(instanced_main_node); + CHECK(instanced_main_node->get_name() == "MainNode"); + REQUIRE(instanced_main_node->get_child_count() == 3); + InstancePlaceholder *instanced_placeholder = Object::cast_to<InstancePlaceholder>(instanced_main_node->get_child(0, true)); + REQUIRE(instanced_placeholder != nullptr); + + _TestInstancePlaceholderNode *final_node = Object::cast_to<_TestInstancePlaceholderNode>(instanced_placeholder->create_instance(true)); + REQUIRE(final_node != nullptr); + REQUIRE(final_node->get_child_count() == 3); + REQUIRE(final_node->get_reference_property().identity_compare(instanced_main_node->get_child(1, true))); + Array final_array = final_node->get_reference_array_property(); + REQUIRE(final_array.size() == 3); + Array wanted_node_array; + wanted_node_array.push_back(instanced_main_node->get_child(2, true)); // ExternalArrayMember + wanted_node_array.push_back(final_node->get_child(1, true)); // ArrayRef1 + wanted_node_array.push_back(final_node->get_child(2, true)); // ArrayRef2 + + // Iterate over all nodes, since the ordering is not guaranteed. + for (int i = 0; i < wanted_node_array.size(); i++) { + bool node_found = false; + for (int j = 0; j < final_array.size(); j++) { + if (wanted_node_array[i].identity_compare(final_array[j])) { + node_found = true; + } + } + CHECK(node_found); + } + + instanced_main_node->queue_free(); + memdelete(array_ext); + memdelete(overriding); + memdelete(root); + memdelete(internal); + DirAccess::remove_file_or_error(internal_path); + DirAccess::remove_file_or_error(main_path); +} + +} //namespace TestInstancePlaceholder + +#endif // TEST_INSTANCE_PLACEHOLDER_H diff --git a/tests/scene/test_node.h b/tests/scene/test_node.h index b3362b02a8..05764d8f29 100644 --- a/tests/scene/test_node.h +++ b/tests/scene/test_node.h @@ -31,7 +31,9 @@ #ifndef TEST_NODE_H #define TEST_NODE_H +#include "core/object/class_db.h" #include "scene/main/node.h" +#include "scene/resources/packed_scene.h" #include "tests/test_macros.h" @@ -62,6 +64,16 @@ protected: } } + static void _bind_methods() { + ClassDB::bind_method(D_METHOD("set_exported_node", "node"), &TestNode::set_exported_node); + ClassDB::bind_method(D_METHOD("get_exported_node"), &TestNode::get_exported_node); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "exported_node", PROPERTY_HINT_NODE_TYPE, "Node"), "set_exported_node", "get_exported_node"); + + ClassDB::bind_method(D_METHOD("set_exported_nodes", "node"), &TestNode::set_exported_nodes); + ClassDB::bind_method(D_METHOD("get_exported_nodes"), &TestNode::get_exported_nodes); + ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "exported_nodes", PROPERTY_HINT_TYPE_STRING, "24/34:Node"), "set_exported_nodes", "get_exported_nodes"); + } + private: void push_self() { if (callback_list) { @@ -75,7 +87,16 @@ public: int process_counter = 0; int physics_process_counter = 0; + Node *exported_node = nullptr; + Array exported_nodes; + List<Node *> *callback_list = nullptr; + + void set_exported_node(Node *p_node) { exported_node = p_node; } + Node *get_exported_node() const { return exported_node; } + + void set_exported_nodes(const Array &p_nodes) { exported_nodes = p_nodes; } + Array get_exported_nodes() const { return exported_nodes; } }; TEST_CASE("[SceneTree][Node] Testing node operations with a very simple scene tree") { @@ -478,6 +499,113 @@ TEST_CASE("[SceneTree][Node] Testing node operations with a more complex simple memdelete(node2); } +TEST_CASE("[SceneTree][Node]Exported node checks") { + TestNode *node = memnew(TestNode); + SceneTree::get_singleton()->get_root()->add_child(node); + + Node *child = memnew(Node); + child->set_name("Child"); + node->add_child(child); + child->set_owner(node); + + Node *child2 = memnew(Node); + child2->set_name("Child2"); + node->add_child(child2); + child2->set_owner(node); + + Array children; + children.append(child); + + node->set("exported_node", child); + node->set("exported_nodes", children); + + SUBCASE("Property of duplicated node should point to duplicated child") { + GDREGISTER_CLASS(TestNode); + + TestNode *dup = Object::cast_to<TestNode>(node->duplicate()); + Node *new_exported = Object::cast_to<Node>(dup->get("exported_node")); + CHECK(new_exported == dup->get_child(0)); + + memdelete(dup); + } + + SUBCASE("Saving instance with exported nodes should not store the unchanged property") { + Ref<PackedScene> ps; + ps.instantiate(); + ps->pack(node); + + String scene_path = TestUtils::get_temp_path("test_scene.tscn"); + ps->set_path(scene_path); + + Node *root = memnew(Node); + + Node *sub_child = ps->instantiate(PackedScene::GEN_EDIT_STATE_MAIN); + root->add_child(sub_child); + sub_child->set_owner(root); + + Ref<PackedScene> ps2; + ps2.instantiate(); + ps2->pack(root); + + scene_path = TestUtils::get_temp_path("new_test_scene.tscn"); + ResourceSaver::save(ps2, scene_path); + memdelete(root); + + bool is_wrong = false; + Ref<FileAccess> fa = FileAccess::open(scene_path, FileAccess::READ); + while (!fa->eof_reached()) { + const String line = fa->get_line(); + if (line.begins_with("exported_node")) { + // The property was saved, while it shouldn't. + is_wrong = true; + break; + } + } + CHECK_FALSE(is_wrong); + } + + SUBCASE("Saving instance with exported nodes should store property if changed") { + Ref<PackedScene> ps; + ps.instantiate(); + ps->pack(node); + + String scene_path = TestUtils::get_temp_path("test_scene.tscn"); + ps->set_path(scene_path); + + Node *root = memnew(Node); + + Node *sub_child = ps->instantiate(PackedScene::GEN_EDIT_STATE_MAIN); + root->add_child(sub_child); + sub_child->set_owner(root); + + sub_child->set("exported_node", sub_child->get_child(1)); + + children = Array(); + children.append(sub_child->get_child(1)); + sub_child->set("exported_nodes", children); + + Ref<PackedScene> ps2; + ps2.instantiate(); + ps2->pack(root); + + scene_path = TestUtils::get_temp_path("new_test_scene2.tscn"); + ResourceSaver::save(ps2, scene_path); + memdelete(root); + + int stored_properties = 0; + Ref<FileAccess> fa = FileAccess::open(scene_path, FileAccess::READ); + while (!fa->eof_reached()) { + const String line = fa->get_line(); + if (line.begins_with("exported_node")) { + stored_properties++; + } + } + CHECK_EQ(stored_properties, 2); + } + + memdelete(node); +} + TEST_CASE("[Node] Processing checks") { Node *node = memnew(Node); diff --git a/tests/scene/test_viewport.h b/tests/scene/test_viewport.h index 66b0f438cc..1341cc0332 100644 --- a/tests/scene/test_viewport.h +++ b/tests/scene/test_viewport.h @@ -548,8 +548,8 @@ TEST_CASE("[SceneTree][Viewport] Controls and InputEvent handling") { } SUBCASE("[Viewport][GuiInputEvent] Mouse Enter/Exit notification propagation when moving into child.") { - SIGNAL_WATCH(node_i, SNAME("mouse_entered")); - SIGNAL_WATCH(node_i, SNAME("mouse_exited")); + SIGNAL_WATCH(node_i, SceneStringName(mouse_entered)); + SIGNAL_WATCH(node_i, SceneStringName(mouse_exited)); Array signal_args; signal_args.push_back(Array()); @@ -568,8 +568,8 @@ TEST_CASE("[SceneTree][Viewport] Controls and InputEvent handling") { CHECK(node_i->mouse_over_self); CHECK_FALSE(node_j->mouse_over); CHECK_FALSE(node_j->mouse_over_self); - SIGNAL_CHECK(SNAME("mouse_entered"), signal_args); - SIGNAL_CHECK_FALSE(SNAME("mouse_exited")); + SIGNAL_CHECK(SceneStringName(mouse_entered), signal_args); + SIGNAL_CHECK_FALSE(SceneStringName(mouse_exited)); // Move to child Control node_j. node_i should not receive any new Mouse Enter signals. SEND_GUI_MOUSE_MOTION_EVENT(on_j, MouseButtonMask::NONE, Key::NONE); @@ -577,8 +577,8 @@ TEST_CASE("[SceneTree][Viewport] Controls and InputEvent handling") { CHECK_FALSE(node_i->mouse_over_self); CHECK(node_j->mouse_over); CHECK(node_j->mouse_over_self); - SIGNAL_CHECK_FALSE(SNAME("mouse_entered")); - SIGNAL_CHECK_FALSE(SNAME("mouse_exited")); + SIGNAL_CHECK_FALSE(SceneStringName(mouse_entered)); + SIGNAL_CHECK_FALSE(SceneStringName(mouse_exited)); // Move to parent Control node_i. node_i should not receive any new Mouse Enter signals. SEND_GUI_MOUSE_MOTION_EVENT(on_i, MouseButtonMask::NONE, Key::NONE); @@ -586,8 +586,8 @@ TEST_CASE("[SceneTree][Viewport] Controls and InputEvent handling") { CHECK(node_i->mouse_over_self); CHECK_FALSE(node_j->mouse_over); CHECK_FALSE(node_j->mouse_over_self); - SIGNAL_CHECK_FALSE(SNAME("mouse_entered")); - SIGNAL_CHECK_FALSE(SNAME("mouse_exited")); + SIGNAL_CHECK_FALSE(SceneStringName(mouse_entered)); + SIGNAL_CHECK_FALSE(SceneStringName(mouse_exited)); // Move to background. SEND_GUI_MOUSE_MOTION_EVENT(on_background, MouseButtonMask::NONE, Key::NONE); @@ -595,16 +595,16 @@ TEST_CASE("[SceneTree][Viewport] Controls and InputEvent handling") { CHECK_FALSE(node_i->mouse_over_self); CHECK_FALSE(node_j->mouse_over); CHECK_FALSE(node_j->mouse_over_self); - SIGNAL_CHECK_FALSE(SNAME("mouse_entered")); - SIGNAL_CHECK(SNAME("mouse_exited"), signal_args); + SIGNAL_CHECK_FALSE(SceneStringName(mouse_entered)); + SIGNAL_CHECK(SceneStringName(mouse_exited), signal_args); CHECK_FALSE(node_i->invalid_order); CHECK_FALSE(node_j->invalid_order); node_j->set_mouse_filter(Control::MOUSE_FILTER_STOP); - SIGNAL_UNWATCH(node_i, SNAME("mouse_entered")); - SIGNAL_UNWATCH(node_i, SNAME("mouse_exited")); + SIGNAL_UNWATCH(node_i, SceneStringName(mouse_entered)); + SIGNAL_UNWATCH(node_i, SceneStringName(mouse_exited)); } SUBCASE("[Viewport][GuiInputEvent] Mouse Enter/Exit notification propagation with top level.") { @@ -756,8 +756,8 @@ TEST_CASE("[SceneTree][Viewport] Controls and InputEvent handling") { } SUBCASE("[Viewport][GuiInputEvent] Mouse Enter/Exit notification when changing top level.") { - SIGNAL_WATCH(node_i, SNAME("mouse_entered")); - SIGNAL_WATCH(node_i, SNAME("mouse_exited")); + SIGNAL_WATCH(node_i, SceneStringName(mouse_entered)); + SIGNAL_WATCH(node_i, SceneStringName(mouse_exited)); Array signal_args; signal_args.push_back(Array()); @@ -794,8 +794,8 @@ TEST_CASE("[SceneTree][Viewport] Controls and InputEvent handling") { CHECK_FALSE(node_i->mouse_over_self); CHECK(node_j->mouse_over); CHECK(node_j->mouse_over_self); - SIGNAL_CHECK(SNAME("mouse_entered"), signal_args); - SIGNAL_CHECK_FALSE(SNAME("mouse_exited")); + SIGNAL_CHECK(SceneStringName(mouse_entered), signal_args); + SIGNAL_CHECK_FALSE(SceneStringName(mouse_exited)); // Change node_i to top level. node_h should receive Mouse Exit. node_i should not receive any new signals. node_i->set_as_top_level(true); @@ -805,8 +805,8 @@ TEST_CASE("[SceneTree][Viewport] Controls and InputEvent handling") { CHECK_FALSE(node_i->mouse_over_self); CHECK(node_j->mouse_over); CHECK(node_j->mouse_over_self); - SIGNAL_CHECK_FALSE(SNAME("mouse_entered")); - SIGNAL_CHECK_FALSE(SNAME("mouse_exited")); + SIGNAL_CHECK_FALSE(SceneStringName(mouse_entered)); + SIGNAL_CHECK_FALSE(SceneStringName(mouse_exited)); // Change node_i to not top level. node_h should receive Mouse Enter. node_i should not receive any new signals. node_i->set_as_top_level(false); @@ -816,8 +816,8 @@ TEST_CASE("[SceneTree][Viewport] Controls and InputEvent handling") { CHECK_FALSE(node_i->mouse_over_self); CHECK(node_j->mouse_over); CHECK(node_j->mouse_over_self); - SIGNAL_CHECK_FALSE(SNAME("mouse_entered")); - SIGNAL_CHECK_FALSE(SNAME("mouse_exited")); + SIGNAL_CHECK_FALSE(SceneStringName(mouse_entered)); + SIGNAL_CHECK_FALSE(SceneStringName(mouse_exited)); CHECK_FALSE(node_b->invalid_order); CHECK_FALSE(node_d->invalid_order); @@ -830,13 +830,13 @@ TEST_CASE("[SceneTree][Viewport] Controls and InputEvent handling") { node_i->set_mouse_filter(Control::MOUSE_FILTER_STOP); node_j->set_mouse_filter(Control::MOUSE_FILTER_STOP); - SIGNAL_UNWATCH(node_i, SNAME("mouse_entered")); - SIGNAL_UNWATCH(node_i, SNAME("mouse_exited")); + SIGNAL_UNWATCH(node_i, SceneStringName(mouse_entered)); + SIGNAL_UNWATCH(node_i, SceneStringName(mouse_exited)); } SUBCASE("[Viewport][GuiInputEvent] Mouse Enter/Exit notification when changing the mouse filter to stop.") { - SIGNAL_WATCH(node_i, SNAME("mouse_entered")); - SIGNAL_WATCH(node_i, SNAME("mouse_exited")); + SIGNAL_WATCH(node_i, SceneStringName(mouse_entered)); + SIGNAL_WATCH(node_i, SceneStringName(mouse_exited)); Array signal_args; signal_args.push_back(Array()); @@ -851,8 +851,8 @@ TEST_CASE("[SceneTree][Viewport] Controls and InputEvent handling") { CHECK_FALSE(node_i->mouse_over_self); CHECK(node_j->mouse_over); CHECK(node_j->mouse_over_self); - SIGNAL_CHECK(SNAME("mouse_entered"), signal_args); - SIGNAL_CHECK_FALSE(SNAME("mouse_exited")); + SIGNAL_CHECK(SceneStringName(mouse_entered), signal_args); + SIGNAL_CHECK_FALSE(SceneStringName(mouse_exited)); // Change node_i to MOUSE_FILTER_STOP. node_h should receive Mouse Exit. node_i should not receive any new signals. node_i->set_mouse_filter(Control::MOUSE_FILTER_STOP); @@ -862,8 +862,8 @@ TEST_CASE("[SceneTree][Viewport] Controls and InputEvent handling") { CHECK_FALSE(node_i->mouse_over_self); CHECK(node_j->mouse_over); CHECK(node_j->mouse_over_self); - SIGNAL_CHECK_FALSE(SNAME("mouse_entered")); - SIGNAL_CHECK_FALSE(SNAME("mouse_exited")); + SIGNAL_CHECK_FALSE(SceneStringName(mouse_entered)); + SIGNAL_CHECK_FALSE(SceneStringName(mouse_exited)); // Change node_i to MOUSE_FILTER_PASS. node_h should receive Mouse Enter. node_i should not receive any new signals. node_i->set_mouse_filter(Control::MOUSE_FILTER_PASS); @@ -873,8 +873,8 @@ TEST_CASE("[SceneTree][Viewport] Controls and InputEvent handling") { CHECK_FALSE(node_i->mouse_over_self); CHECK(node_j->mouse_over); CHECK(node_j->mouse_over_self); - SIGNAL_CHECK_FALSE(SNAME("mouse_entered")); - SIGNAL_CHECK_FALSE(SNAME("mouse_exited")); + SIGNAL_CHECK_FALSE(SceneStringName(mouse_entered)); + SIGNAL_CHECK_FALSE(SceneStringName(mouse_exited)); CHECK_FALSE(node_h->invalid_order); CHECK_FALSE(node_i->invalid_order); @@ -883,13 +883,13 @@ TEST_CASE("[SceneTree][Viewport] Controls and InputEvent handling") { node_i->set_mouse_filter(Control::MOUSE_FILTER_STOP); node_j->set_mouse_filter(Control::MOUSE_FILTER_STOP); - SIGNAL_UNWATCH(node_i, SNAME("mouse_entered")); - SIGNAL_UNWATCH(node_i, SNAME("mouse_exited")); + SIGNAL_UNWATCH(node_i, SceneStringName(mouse_entered)); + SIGNAL_UNWATCH(node_i, SceneStringName(mouse_exited)); } SUBCASE("[Viewport][GuiInputEvent] Mouse Enter/Exit notification when changing the mouse filter to ignore.") { - SIGNAL_WATCH(node_i, SNAME("mouse_entered")); - SIGNAL_WATCH(node_i, SNAME("mouse_exited")); + SIGNAL_WATCH(node_i, SceneStringName(mouse_entered)); + SIGNAL_WATCH(node_i, SceneStringName(mouse_exited)); Array signal_args; signal_args.push_back(Array()); @@ -904,8 +904,8 @@ TEST_CASE("[SceneTree][Viewport] Controls and InputEvent handling") { CHECK_FALSE(node_i->mouse_over_self); CHECK(node_j->mouse_over); CHECK(node_j->mouse_over_self); - SIGNAL_CHECK(SNAME("mouse_entered"), signal_args); - SIGNAL_CHECK_FALSE(SNAME("mouse_exited")); + SIGNAL_CHECK(SceneStringName(mouse_entered), signal_args); + SIGNAL_CHECK_FALSE(SceneStringName(mouse_exited)); // Change node_i to MOUSE_FILTER_IGNORE. node_i should receive Mouse Exit. node_i->set_mouse_filter(Control::MOUSE_FILTER_IGNORE); @@ -915,8 +915,8 @@ TEST_CASE("[SceneTree][Viewport] Controls and InputEvent handling") { CHECK_FALSE(node_i->mouse_over_self); CHECK(node_j->mouse_over); CHECK(node_j->mouse_over_self); - SIGNAL_CHECK_FALSE(SNAME("mouse_entered")); - SIGNAL_CHECK(SNAME("mouse_exited"), signal_args); + SIGNAL_CHECK_FALSE(SceneStringName(mouse_entered)); + SIGNAL_CHECK(SceneStringName(mouse_exited), signal_args); // Change node_i to MOUSE_FILTER_PASS. node_i should receive Mouse Enter. node_i->set_mouse_filter(Control::MOUSE_FILTER_PASS); @@ -926,8 +926,8 @@ TEST_CASE("[SceneTree][Viewport] Controls and InputEvent handling") { CHECK_FALSE(node_i->mouse_over_self); CHECK(node_j->mouse_over); CHECK(node_j->mouse_over_self); - SIGNAL_CHECK(SNAME("mouse_entered"), signal_args); - SIGNAL_CHECK_FALSE(SNAME("mouse_exited")); + SIGNAL_CHECK(SceneStringName(mouse_entered), signal_args); + SIGNAL_CHECK_FALSE(SceneStringName(mouse_exited)); // Change node_j to MOUSE_FILTER_IGNORE. After updating the mouse motion, node_i should now have mouse_over_self. node_j->set_mouse_filter(Control::MOUSE_FILTER_IGNORE); @@ -938,8 +938,8 @@ TEST_CASE("[SceneTree][Viewport] Controls and InputEvent handling") { CHECK(node_i->mouse_over_self); CHECK_FALSE(node_j->mouse_over); CHECK_FALSE(node_j->mouse_over_self); - SIGNAL_CHECK_FALSE(SNAME("mouse_entered")); - SIGNAL_CHECK_FALSE(SNAME("mouse_exited")); + SIGNAL_CHECK_FALSE(SceneStringName(mouse_entered)); + SIGNAL_CHECK_FALSE(SceneStringName(mouse_exited)); // Change node_j to MOUSE_FILTER_PASS. After updating the mouse motion, node_j should now have mouse_over_self. node_j->set_mouse_filter(Control::MOUSE_FILTER_PASS); @@ -950,8 +950,8 @@ TEST_CASE("[SceneTree][Viewport] Controls and InputEvent handling") { CHECK_FALSE(node_i->mouse_over_self); CHECK(node_j->mouse_over); CHECK(node_j->mouse_over_self); - SIGNAL_CHECK_FALSE(SNAME("mouse_entered")); - SIGNAL_CHECK_FALSE(SNAME("mouse_exited")); + SIGNAL_CHECK_FALSE(SceneStringName(mouse_entered)); + SIGNAL_CHECK_FALSE(SceneStringName(mouse_exited)); CHECK_FALSE(node_h->invalid_order); CHECK_FALSE(node_i->invalid_order); @@ -960,13 +960,13 @@ TEST_CASE("[SceneTree][Viewport] Controls and InputEvent handling") { node_i->set_mouse_filter(Control::MOUSE_FILTER_STOP); node_j->set_mouse_filter(Control::MOUSE_FILTER_STOP); - SIGNAL_UNWATCH(node_i, SNAME("mouse_entered")); - SIGNAL_UNWATCH(node_i, SNAME("mouse_exited")); + SIGNAL_UNWATCH(node_i, SceneStringName(mouse_entered)); + SIGNAL_UNWATCH(node_i, SceneStringName(mouse_exited)); } SUBCASE("[Viewport][GuiInputEvent] Mouse Enter/Exit notification when removing the hovered Control.") { - SIGNAL_WATCH(node_h, SNAME("mouse_entered")); - SIGNAL_WATCH(node_h, SNAME("mouse_exited")); + SIGNAL_WATCH(node_h, SceneStringName(mouse_entered)); + SIGNAL_WATCH(node_h, SceneStringName(mouse_exited)); Array signal_args; signal_args.push_back(Array()); @@ -981,8 +981,8 @@ TEST_CASE("[SceneTree][Viewport] Controls and InputEvent handling") { CHECK_FALSE(node_i->mouse_over_self); CHECK(node_j->mouse_over); CHECK(node_j->mouse_over_self); - SIGNAL_CHECK(SNAME("mouse_entered"), signal_args); - SIGNAL_CHECK_FALSE(SNAME("mouse_exited")); + SIGNAL_CHECK(SceneStringName(mouse_entered), signal_args); + SIGNAL_CHECK_FALSE(SceneStringName(mouse_exited)); // Remove node_i from the tree. node_i and node_j should receive Mouse Exit. node_h should not receive any new signals. node_h->remove_child(node_i); @@ -992,8 +992,8 @@ TEST_CASE("[SceneTree][Viewport] Controls and InputEvent handling") { CHECK_FALSE(node_i->mouse_over_self); CHECK_FALSE(node_j->mouse_over); CHECK_FALSE(node_j->mouse_over_self); - SIGNAL_CHECK_FALSE(SNAME("mouse_entered")); - SIGNAL_CHECK_FALSE(SNAME("mouse_exited")); + SIGNAL_CHECK_FALSE(SceneStringName(mouse_entered)); + SIGNAL_CHECK_FALSE(SceneStringName(mouse_exited)); // Add node_i to the tree and update the mouse. node_i and node_j should receive Mouse Enter. node_h should not receive any new signals. node_h->add_child(node_i); @@ -1004,8 +1004,8 @@ TEST_CASE("[SceneTree][Viewport] Controls and InputEvent handling") { CHECK_FALSE(node_i->mouse_over_self); CHECK(node_j->mouse_over); CHECK(node_j->mouse_over_self); - SIGNAL_CHECK_FALSE(SNAME("mouse_entered")); - SIGNAL_CHECK_FALSE(SNAME("mouse_exited")); + SIGNAL_CHECK_FALSE(SceneStringName(mouse_entered)); + SIGNAL_CHECK_FALSE(SceneStringName(mouse_exited)); CHECK_FALSE(node_h->invalid_order); CHECK_FALSE(node_i->invalid_order); @@ -1014,13 +1014,13 @@ TEST_CASE("[SceneTree][Viewport] Controls and InputEvent handling") { node_i->set_mouse_filter(Control::MOUSE_FILTER_STOP); node_j->set_mouse_filter(Control::MOUSE_FILTER_STOP); - SIGNAL_UNWATCH(node_h, SNAME("mouse_entered")); - SIGNAL_UNWATCH(node_h, SNAME("mouse_exited")); + SIGNAL_UNWATCH(node_h, SceneStringName(mouse_entered)); + SIGNAL_UNWATCH(node_h, SceneStringName(mouse_exited)); } SUBCASE("[Viewport][GuiInputEvent] Mouse Enter/Exit notification when hiding the hovered Control.") { - SIGNAL_WATCH(node_h, SNAME("mouse_entered")); - SIGNAL_WATCH(node_h, SNAME("mouse_exited")); + SIGNAL_WATCH(node_h, SceneStringName(mouse_entered)); + SIGNAL_WATCH(node_h, SceneStringName(mouse_exited)); Array signal_args; signal_args.push_back(Array()); @@ -1035,8 +1035,8 @@ TEST_CASE("[SceneTree][Viewport] Controls and InputEvent handling") { CHECK_FALSE(node_i->mouse_over_self); CHECK(node_j->mouse_over); CHECK(node_j->mouse_over_self); - SIGNAL_CHECK(SNAME("mouse_entered"), signal_args); - SIGNAL_CHECK_FALSE(SNAME("mouse_exited")); + SIGNAL_CHECK(SceneStringName(mouse_entered), signal_args); + SIGNAL_CHECK_FALSE(SceneStringName(mouse_exited)); // Hide node_i. node_i and node_j should receive Mouse Exit. node_h should not receive any new signals. node_i->hide(); @@ -1046,8 +1046,8 @@ TEST_CASE("[SceneTree][Viewport] Controls and InputEvent handling") { CHECK_FALSE(node_i->mouse_over_self); CHECK_FALSE(node_j->mouse_over); CHECK_FALSE(node_j->mouse_over_self); - SIGNAL_CHECK_FALSE(SNAME("mouse_entered")); - SIGNAL_CHECK_FALSE(SNAME("mouse_exited")); + SIGNAL_CHECK_FALSE(SceneStringName(mouse_entered)); + SIGNAL_CHECK_FALSE(SceneStringName(mouse_exited)); // Show node_i and update the mouse. node_i and node_j should receive Mouse Enter. node_h should not receive any new signals. node_i->show(); @@ -1058,8 +1058,8 @@ TEST_CASE("[SceneTree][Viewport] Controls and InputEvent handling") { CHECK_FALSE(node_i->mouse_over_self); CHECK(node_j->mouse_over); CHECK(node_j->mouse_over_self); - SIGNAL_CHECK_FALSE(SNAME("mouse_entered")); - SIGNAL_CHECK_FALSE(SNAME("mouse_exited")); + SIGNAL_CHECK_FALSE(SceneStringName(mouse_entered)); + SIGNAL_CHECK_FALSE(SceneStringName(mouse_exited)); CHECK_FALSE(node_h->invalid_order); CHECK_FALSE(node_i->invalid_order); @@ -1068,26 +1068,26 @@ TEST_CASE("[SceneTree][Viewport] Controls and InputEvent handling") { node_i->set_mouse_filter(Control::MOUSE_FILTER_STOP); node_j->set_mouse_filter(Control::MOUSE_FILTER_STOP); - SIGNAL_UNWATCH(node_h, SNAME("mouse_entered")); - SIGNAL_UNWATCH(node_h, SNAME("mouse_exited")); + SIGNAL_UNWATCH(node_h, SceneStringName(mouse_entered)); + SIGNAL_UNWATCH(node_h, SceneStringName(mouse_exited)); } SUBCASE("[Viewport][GuiInputEvent] Window Mouse Enter/Exit signals.") { - SIGNAL_WATCH(root, SNAME("mouse_entered")); - SIGNAL_WATCH(root, SNAME("mouse_exited")); + SIGNAL_WATCH(root, SceneStringName(mouse_entered)); + SIGNAL_WATCH(root, SceneStringName(mouse_exited)); Array signal_args; signal_args.push_back(Array()); SEND_GUI_MOUSE_MOTION_EVENT(on_outside, MouseButtonMask::NONE, Key::NONE); - SIGNAL_CHECK_FALSE(SNAME("mouse_entered")); - SIGNAL_CHECK(SNAME("mouse_exited"), signal_args); + SIGNAL_CHECK_FALSE(SceneStringName(mouse_entered)); + SIGNAL_CHECK(SceneStringName(mouse_exited), signal_args); SEND_GUI_MOUSE_MOTION_EVENT(on_a, MouseButtonMask::NONE, Key::NONE); - SIGNAL_CHECK(SNAME("mouse_entered"), signal_args); - SIGNAL_CHECK_FALSE(SNAME("mouse_exited")); + SIGNAL_CHECK(SceneStringName(mouse_entered), signal_args); + SIGNAL_CHECK_FALSE(SceneStringName(mouse_exited)); - SIGNAL_UNWATCH(root, SNAME("mouse_entered")); - SIGNAL_UNWATCH(root, SNAME("mouse_exited")); + SIGNAL_UNWATCH(root, SceneStringName(mouse_entered)); + SIGNAL_UNWATCH(root, SceneStringName(mouse_exited)); } SUBCASE("[Viewport][GuiInputEvent] Process-Mode affects, if GUI Mouse Motion Events are processed.") { @@ -1415,9 +1415,9 @@ public: Ref<InputEvent> last_input_event; void init_signals() { - connect(SNAME("mouse_entered"), callable_mp(this, &TestArea2D::_on_mouse_entered)); - connect(SNAME("mouse_exited"), callable_mp(this, &TestArea2D::_on_mouse_exited)); - connect(SNAME("input_event"), callable_mp(this, &TestArea2D::_on_input_event)); + connect(SceneStringName(mouse_entered), callable_mp(this, &TestArea2D::_on_mouse_entered)); + connect(SceneStringName(mouse_exited), callable_mp(this, &TestArea2D::_on_mouse_exited)); + connect(SceneStringName(input_event), callable_mp(this, &TestArea2D::_on_input_event)); } void test_reset() { @@ -1459,8 +1459,8 @@ TEST_CASE("[SceneTree][Viewport] Physics Picking 2D") { pc.a->set_name("A" + itos(i)); pc.c->set_name("C" + itos(i)); v.push_back(pc); - SIGNAL_WATCH(pc.a, SNAME("mouse_entered")); - SIGNAL_WATCH(pc.a, SNAME("mouse_exited")); + SIGNAL_WATCH(pc.a, SceneStringName(mouse_entered)); + SIGNAL_WATCH(pc.a, SceneStringName(mouse_exited)); } Node2D *node_a = memnew(Node2D); @@ -1499,8 +1499,8 @@ TEST_CASE("[SceneTree][Viewport] Physics Picking 2D") { SUBCASE("[Viewport][Picking2D] Mouse Motion") { SEND_GUI_MOUSE_MOTION_EVENT(on_all, MouseButtonMask::NONE, Key::NONE); tree->physics_process(1); - SIGNAL_CHECK(SNAME("mouse_entered"), empty_signal_args_4); - SIGNAL_CHECK_FALSE(SNAME("mouse_exited")); + SIGNAL_CHECK(SceneStringName(mouse_entered), empty_signal_args_4); + SIGNAL_CHECK_FALSE(SceneStringName(mouse_exited)); for (PickingCollider E : v) { CHECK(E.a->enter_id); CHECK_FALSE(E.a->exit_id); @@ -1509,8 +1509,8 @@ TEST_CASE("[SceneTree][Viewport] Physics Picking 2D") { SEND_GUI_MOUSE_MOTION_EVENT(on_01, MouseButtonMask::NONE, Key::NONE); tree->physics_process(1); - SIGNAL_CHECK_FALSE(SNAME("mouse_entered")); - SIGNAL_CHECK(SNAME("mouse_exited"), empty_signal_args_2); + SIGNAL_CHECK_FALSE(SceneStringName(mouse_entered)); + SIGNAL_CHECK(SceneStringName(mouse_exited), empty_signal_args_2); for (int i = 0; i < v.size(); i++) { CHECK_FALSE(v[i].a->enter_id); @@ -1524,8 +1524,8 @@ TEST_CASE("[SceneTree][Viewport] Physics Picking 2D") { SEND_GUI_MOUSE_MOTION_EVENT(on_outside, MouseButtonMask::NONE, Key::NONE); tree->physics_process(1); - SIGNAL_CHECK_FALSE(SNAME("mouse_entered")); - SIGNAL_CHECK(SNAME("mouse_exited"), empty_signal_args_2); + SIGNAL_CHECK_FALSE(SceneStringName(mouse_entered)); + SIGNAL_CHECK(SceneStringName(mouse_exited), empty_signal_args_2); for (int i = 0; i < v.size(); i++) { CHECK_FALSE(v[i].a->enter_id); if (i < 2) { @@ -1788,8 +1788,8 @@ TEST_CASE("[SceneTree][Viewport] Physics Picking 2D") { } for (PickingCollider E : v) { - SIGNAL_UNWATCH(E.a, SNAME("mouse_entered")); - SIGNAL_UNWATCH(E.a, SNAME("mouse_exited")); + SIGNAL_UNWATCH(E.a, SceneStringName(mouse_entered)); + SIGNAL_UNWATCH(E.a, SceneStringName(mouse_exited)); memdelete(E.c); memdelete(E.a); } diff --git a/tests/servers/test_navigation_server_3d.h b/tests/servers/test_navigation_server_3d.h index 8778ea86a6..cf6b89c330 100644 --- a/tests/servers/test_navigation_server_3d.h +++ b/tests/servers/test_navigation_server_3d.h @@ -697,12 +697,16 @@ TEST_SUITE("[Navigation]") { CHECK_NE(navigation_server->map_get_closest_point(map, Vector3(0, 0, 0)), Vector3(0, 0, 0)); CHECK_NE(navigation_server->map_get_closest_point_normal(map, Vector3(0, 0, 0)), Vector3()); CHECK(navigation_server->map_get_closest_point_owner(map, Vector3(0, 0, 0)).is_valid()); - // TODO: Test map_get_closest_point_to_segment() with p_use_collision=true as well. CHECK_NE(navigation_server->map_get_closest_point_to_segment(map, Vector3(0, 0, 0), Vector3(1, 1, 1), false), Vector3()); + CHECK_NE(navigation_server->map_get_closest_point_to_segment(map, Vector3(0, 0, 0), Vector3(1, 1, 1), true), Vector3()); CHECK_NE(navigation_server->map_get_path(map, Vector3(0, 0, 0), Vector3(10, 0, 10), true).size(), 0); CHECK_NE(navigation_server->map_get_path(map, Vector3(0, 0, 0), Vector3(10, 0, 10), false).size(), 0); } + SUBCASE("'map_get_closest_point_to_segment' with 'use_collision' should return default if segment doesn't intersect map") { + CHECK_EQ(navigation_server->map_get_closest_point_to_segment(map, Vector3(1, 2, 1), Vector3(1, 1, 1), true), Vector3()); + } + SUBCASE("Elaborate query with 'CORRIDORFUNNEL' post-processing should yield non-empty result") { Ref<NavigationPathQueryParameters3D> query_parameters = memnew(NavigationPathQueryParameters3D); query_parameters->set_map(map); diff --git a/tests/test_macros.h b/tests/test_macros.h index 25e48c1e05..10f4c59a90 100644 --- a/tests/test_macros.h +++ b/tests/test_macros.h @@ -406,4 +406,71 @@ public: #define SIGNAL_CHECK_FALSE(m_signal) CHECK(SignalWatcher::get_singleton()->check_false(m_signal)); #define SIGNAL_DISCARD(m_signal) SignalWatcher::get_singleton()->discard_signal(m_signal); +#define MULTICHECK_STRING_EQ(m_obj, m_func, m_param1, m_eq) \ + CHECK(m_obj.m_func(m_param1) == m_eq); \ + CHECK(m_obj.m_func(U##m_param1) == m_eq); \ + CHECK(m_obj.m_func(L##m_param1) == m_eq); \ + CHECK(m_obj.m_func(String(m_param1)) == m_eq); + +#define MULTICHECK_STRING_INT_EQ(m_obj, m_func, m_param1, m_param2, m_eq) \ + CHECK(m_obj.m_func(m_param1, m_param2) == m_eq); \ + CHECK(m_obj.m_func(U##m_param1, m_param2) == m_eq); \ + CHECK(m_obj.m_func(L##m_param1, m_param2) == m_eq); \ + CHECK(m_obj.m_func(String(m_param1), m_param2) == m_eq); + +#define MULTICHECK_STRING_INT_INT_EQ(m_obj, m_func, m_param1, m_param2, m_param3, m_eq) \ + CHECK(m_obj.m_func(m_param1, m_param2, m_param3) == m_eq); \ + CHECK(m_obj.m_func(U##m_param1, m_param2, m_param3) == m_eq); \ + CHECK(m_obj.m_func(L##m_param1, m_param2, m_param3) == m_eq); \ + CHECK(m_obj.m_func(String(m_param1), m_param2, m_param3) == m_eq); + +#define MULTICHECK_STRING_STRING_EQ(m_obj, m_func, m_param1, m_param2, m_eq) \ + CHECK(m_obj.m_func(m_param1, m_param2) == m_eq); \ + CHECK(m_obj.m_func(U##m_param1, U##m_param2) == m_eq); \ + CHECK(m_obj.m_func(L##m_param1, L##m_param2) == m_eq); \ + CHECK(m_obj.m_func(String(m_param1), String(m_param2)) == m_eq); + +#define MULTICHECK_GET_SLICE(m_obj, m_param1, m_slices) \ + for (int i = 0; i < m_obj.get_slice_count(m_param1); ++i) { \ + CHECK(m_obj.get_slice(m_param1, i) == m_slices[i]); \ + } \ + for (int i = 0; i < m_obj.get_slice_count(U##m_param1); ++i) { \ + CHECK(m_obj.get_slice(U##m_param1, i) == m_slices[i]); \ + } \ + for (int i = 0; i < m_obj.get_slice_count(L##m_param1); ++i) { \ + CHECK(m_obj.get_slice(L##m_param1, i) == m_slices[i]); \ + } \ + for (int i = 0; i < m_obj.get_slice_count(String(m_param1)); ++i) { \ + CHECK(m_obj.get_slice(String(m_param1), i) == m_slices[i]); \ + } + +#define MULTICHECK_SPLIT(m_obj, m_func, m_param1, m_param2, m_param3, m_slices, m_expected_size) \ + do { \ + Vector<String> string_list; \ + \ + string_list = m_obj.m_func(m_param1, m_param2, m_param3); \ + CHECK(m_expected_size == string_list.size()); \ + for (int i = 0; i < string_list.size(); ++i) { \ + CHECK(string_list[i] == m_slices[i]); \ + } \ + \ + string_list = m_obj.m_func(U##m_param1, m_param2, m_param3); \ + CHECK(m_expected_size == string_list.size()); \ + for (int i = 0; i < string_list.size(); ++i) { \ + CHECK(string_list[i] == m_slices[i]); \ + } \ + \ + string_list = m_obj.m_func(L##m_param1, m_param2, m_param3); \ + CHECK(m_expected_size == string_list.size()); \ + for (int i = 0; i < string_list.size(); ++i) { \ + CHECK(string_list[i] == m_slices[i]); \ + } \ + \ + string_list = m_obj.m_func(String(m_param1), m_param2, m_param3); \ + CHECK(m_expected_size == string_list.size()); \ + for (int i = 0; i < string_list.size(); ++i) { \ + CHECK(string_list[i] == m_slices[i]); \ + } \ + } while (0) + #endif // TEST_MACROS_H diff --git a/tests/test_main.cpp b/tests/test_main.cpp index 69d8113e64..3c875797a4 100644 --- a/tests/test_main.cpp +++ b/tests/test_main.cpp @@ -111,6 +111,8 @@ #include "tests/scene/test_curve_3d.h" #include "tests/scene/test_gradient.h" #include "tests/scene/test_image_texture.h" +#include "tests/scene/test_image_texture_3d.h" +#include "tests/scene/test_instance_placeholder.h" #include "tests/scene/test_node.h" #include "tests/scene/test_node_2d.h" #include "tests/scene/test_packed_scene.h" @@ -242,7 +244,7 @@ struct GodotTestCaseListener : public doctest::IReporter { String name = String(p_in.m_name); String suite_name = String(p_in.m_test_suite); - if (name.find("[SceneTree]") != -1 || name.find("[Editor]") != -1) { + if (name.contains("[SceneTree]") || name.contains("[Editor]")) { memnew(MessageQueue); memnew(Input); @@ -252,7 +254,7 @@ struct GodotTestCaseListener : public doctest::IReporter { OS::get_singleton()->set_has_server_feature_callback(nullptr); for (int i = 0; i < DisplayServer::get_create_function_count(); i++) { if (String("mock") == DisplayServer::get_create_function_name(i)) { - DisplayServer::create(i, "", DisplayServer::WindowMode::WINDOW_MODE_MINIMIZED, DisplayServer::VSyncMode::VSYNC_ENABLED, 0, nullptr, Vector2i(0, 0), DisplayServer::SCREEN_PRIMARY, err); + DisplayServer::create(i, "", DisplayServer::WindowMode::WINDOW_MODE_MINIMIZED, DisplayServer::VSyncMode::VSYNC_ENABLED, 0, nullptr, Vector2i(0, 0), DisplayServer::SCREEN_PRIMARY, DisplayServer::CONTEXT_EDITOR, err); break; } } @@ -291,7 +293,7 @@ struct GodotTestCaseListener : public doctest::IReporter { } #ifdef TOOLS_ENABLED - if (name.find("[Editor]") != -1) { + if (name.contains("[Editor]")) { Engine::get_singleton()->set_editor_hint(true); EditorPaths::create(); EditorSettings::create(); @@ -301,7 +303,7 @@ struct GodotTestCaseListener : public doctest::IReporter { return; } - if (name.find("Audio") != -1) { + if (name.contains("Audio")) { // The last driver index should always be the dummy driver. int dummy_idx = AudioDriverManager::get_driver_count() - 1; AudioDriverManager::initialize(dummy_idx); @@ -311,7 +313,7 @@ struct GodotTestCaseListener : public doctest::IReporter { } #ifndef _3D_DISABLED - if (suite_name.find("[Navigation]") != -1 && navigation_server_2d == nullptr && navigation_server_3d == nullptr) { + if (suite_name.contains("[Navigation]") && navigation_server_2d == nullptr && navigation_server_3d == nullptr) { ERR_PRINT_OFF; navigation_server_3d = NavigationServer3DManager::new_default_server(); navigation_server_2d = NavigationServer2DManager::new_default_server(); diff --git a/tests/test_utils.cpp b/tests/test_utils.cpp index cbd6d1ffbb..9d41e74020 100644 --- a/tests/test_utils.cpp +++ b/tests/test_utils.cpp @@ -30,6 +30,7 @@ #include "tests/test_utils.h" +#include "core/io/dir_access.h" #include "core/os/os.h" String TestUtils::get_data_path(const String &p_file) { @@ -40,3 +41,9 @@ String TestUtils::get_data_path(const String &p_file) { String TestUtils::get_executable_dir() { return OS::get_singleton()->get_executable_path().get_base_dir(); } + +String TestUtils::get_temp_path(const String &p_suffix) { + const String temp_base = OS::get_singleton()->get_cache_path().path_join("godot_test"); + DirAccess::make_dir_absolute(temp_base); // Ensure the directory exists. + return temp_base.path_join(p_suffix); +} diff --git a/tests/test_utils.h b/tests/test_utils.h index 48abe75c06..876a59ee7b 100644 --- a/tests/test_utils.h +++ b/tests/test_utils.h @@ -37,6 +37,7 @@ namespace TestUtils { String get_data_path(const String &p_file); String get_executable_dir(); +String get_temp_path(const String &p_suffix); } // namespace TestUtils #endif // TEST_UTILS_H |