diff options
Diffstat (limited to 'tests')
-rw-r--r-- | tests/test_astar.cpp | 409 | ||||
-rw-r--r-- | tests/test_astar.h | 333 | ||||
-rw-r--r-- | tests/test_color.h | 213 | ||||
-rw-r--r-- | tests/test_macros.h | 50 | ||||
-rw-r--r-- | tests/test_main.cpp | 67 | ||||
-rw-r--r-- | tests/test_main.h | 5 | ||||
-rw-r--r-- | tests/test_string.h | 2 | ||||
-rw-r--r-- | tests/test_validate_testing.h | 22 | ||||
-rw-r--r-- | tests/test_variant.h | 2 |
9 files changed, 629 insertions, 474 deletions
diff --git a/tests/test_astar.cpp b/tests/test_astar.cpp deleted file mode 100644 index cb5fcfe37b..0000000000 --- a/tests/test_astar.cpp +++ /dev/null @@ -1,409 +0,0 @@ -/*************************************************************************/ -/* test_astar.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ - -#include "test_astar.h" - -#include "core/math/a_star.h" -#include "core/math/math_funcs.h" -#include "core/os/os.h" - -#include <math.h> -#include <stdio.h> - -namespace TestAStar { - -class ABCX : public AStar { -public: - enum { A, - B, - C, - X }; - - ABCX() { - add_point(A, Vector3(0, 0, 0)); - add_point(B, Vector3(1, 0, 0)); - add_point(C, Vector3(0, 1, 0)); - add_point(X, Vector3(0, 0, 1)); - connect_points(A, B); - connect_points(A, C); - connect_points(B, C); - connect_points(X, A); - } - - // Disable heuristic completely - float _compute_cost(int p_from, int p_to) { - if (p_from == A && p_to == C) { - return 1000; - } - return 100; - } -}; - -bool test_abc() { - ABCX abcx; - Vector<int> path = abcx.get_id_path(ABCX::A, ABCX::C); - bool ok = path.size() == 3; - int i = 0; - ok = ok && path[i++] == ABCX::A; - ok = ok && path[i++] == ABCX::B; - ok = ok && path[i++] == ABCX::C; - return ok; -} - -bool test_abcx() { - ABCX abcx; - Vector<int> path = abcx.get_id_path(ABCX::X, ABCX::C); - bool ok = path.size() == 4; - int i = 0; - ok = ok && path[i++] == ABCX::X; - ok = ok && path[i++] == ABCX::A; - ok = ok && path[i++] == ABCX::B; - ok = ok && path[i++] == ABCX::C; - return ok; -} - -bool test_add_remove() { - AStar a; - bool ok = true; - - // Manual tests - a.add_point(1, Vector3(0, 0, 0)); - a.add_point(2, Vector3(0, 1, 0)); - a.add_point(3, Vector3(1, 1, 0)); - a.add_point(4, Vector3(2, 0, 0)); - a.connect_points(1, 2, true); - a.connect_points(1, 3, true); - a.connect_points(1, 4, false); - - ok = ok && (a.are_points_connected(2, 1)); - ok = ok && (a.are_points_connected(4, 1)); - ok = ok && (a.are_points_connected(2, 1, false)); - ok = ok && (a.are_points_connected(4, 1, false) == false); - - a.disconnect_points(1, 2, true); - ok = ok && (a.get_point_connections(1).size() == 2); // 3, 4 - ok = ok && (a.get_point_connections(2).size() == 0); - - a.disconnect_points(4, 1, false); - ok = ok && (a.get_point_connections(1).size() == 2); // 3, 4 - ok = ok && (a.get_point_connections(4).size() == 0); - - a.disconnect_points(4, 1, true); - ok = ok && (a.get_point_connections(1).size() == 1); // 3 - ok = ok && (a.get_point_connections(4).size() == 0); - - a.connect_points(2, 3, false); - ok = ok && (a.get_point_connections(2).size() == 1); // 3 - ok = ok && (a.get_point_connections(3).size() == 1); // 1 - - a.connect_points(2, 3, true); - ok = ok && (a.get_point_connections(2).size() == 1); // 3 - ok = ok && (a.get_point_connections(3).size() == 2); // 1, 2 - - a.disconnect_points(2, 3, false); - ok = ok && (a.get_point_connections(2).size() == 0); - ok = ok && (a.get_point_connections(3).size() == 2); // 1, 2 - - a.connect_points(4, 3, true); - ok = ok && (a.get_point_connections(3).size() == 3); // 1, 2, 4 - ok = ok && (a.get_point_connections(4).size() == 1); // 3 - - a.disconnect_points(3, 4, false); - ok = ok && (a.get_point_connections(3).size() == 2); // 1, 2 - ok = ok && (a.get_point_connections(4).size() == 1); // 3 - - a.remove_point(3); - ok = ok && (a.get_point_connections(1).size() == 0); - ok = ok && (a.get_point_connections(2).size() == 0); - ok = ok && (a.get_point_connections(4).size() == 0); - - a.add_point(0, Vector3(0, -1, 0)); - a.add_point(3, Vector3(2, 1, 0)); - // 0: (0, -1) - // 1: (0, 0) - // 2: (0, 1) - // 3: (2, 1) - // 4: (2, 0) - - // Tests for get_closest_position_in_segment - a.connect_points(2, 3); - ok = ok && (a.get_closest_position_in_segment(Vector3(0.5, 0.5, 0)) == Vector3(0.5, 1, 0)); - - a.connect_points(3, 4); - a.connect_points(0, 3); - a.connect_points(1, 4); - a.disconnect_points(1, 4, false); - a.disconnect_points(4, 3, false); - a.disconnect_points(3, 4, false); - // Remaining edges: <2, 3>, <0, 3>, <1, 4> (directed) - ok = ok && (a.get_closest_position_in_segment(Vector3(2, 0.5, 0)) == Vector3(1.75, 0.75, 0)); - ok = ok && (a.get_closest_position_in_segment(Vector3(-1, 0.2, 0)) == Vector3(0, 0, 0)); - ok = ok && (a.get_closest_position_in_segment(Vector3(3, 2, 0)) == Vector3(2, 1, 0)); - - Math::seed(0); - - // Random tests for connectivity checks - for (int i = 0; i < 20000; i++) { - int u = Math::rand() % 5; - int v = Math::rand() % 4; - if (u == v) { - v = 4; - } - if (Math::rand() % 2 == 1) { - // Add a (possibly existing) directed edge and confirm connectivity - a.connect_points(u, v, false); - ok = ok && (a.are_points_connected(u, v, false)); - } else { - // Remove a (possibly nonexistent) directed edge and confirm disconnectivity - a.disconnect_points(u, v, false); - ok = ok && (a.are_points_connected(u, v, false) == false); - } - } - - // Random tests for point removal - for (int i = 0; i < 20000; i++) { - a.clear(); - for (int j = 0; j < 5; j++) { - a.add_point(j, Vector3(0, 0, 0)); - } - - // Add or remove random edges - for (int j = 0; j < 10; j++) { - int u = Math::rand() % 5; - int v = Math::rand() % 4; - if (u == v) { - v = 4; - } - if (Math::rand() % 2 == 1) { - a.connect_points(u, v, false); - } else { - a.disconnect_points(u, v, false); - } - } - - // Remove point 0 - a.remove_point(0); - // White box: this will check all edges remaining in the segments set - for (int j = 1; j < 5; j++) { - ok = ok && (a.are_points_connected(0, j, true) == false); - } - } - - // It's been great work, cheers \(^ ^)/ - return ok; -} - -bool test_solutions() { - // Random stress tests with Floyd-Warshall - - const int N = 30; - Math::seed(0); - - for (int test = 0; test < 1000; test++) { - AStar a; - Vector3 p[N]; - bool adj[N][N] = { { false } }; - - // Assign initial coordinates - for (int u = 0; u < N; u++) { - p[u].x = Math::rand() % 100; - p[u].y = Math::rand() % 100; - p[u].z = Math::rand() % 100; - a.add_point(u, p[u]); - } - - // Generate a random sequence of operations - for (int i = 0; i < 1000; i++) { - // Pick two different vertices - int u, v; - u = Math::rand() % N; - v = Math::rand() % (N - 1); - if (u == v) { - v = N - 1; - } - - // Pick a random operation - int op = Math::rand(); - switch (op % 9) { - case 0: - case 1: - case 2: - case 3: - case 4: - case 5: - // Add edge (u, v); possibly bidirectional - a.connect_points(u, v, op % 2); - adj[u][v] = true; - if (op % 2) { - adj[v][u] = true; - } - break; - case 6: - case 7: - // Remove edge (u, v); possibly bidirectional - a.disconnect_points(u, v, op % 2); - adj[u][v] = false; - if (op % 2) { - adj[v][u] = false; - } - break; - case 8: - // Remove point u and add it back; clears adjacent edges and changes coordinates - a.remove_point(u); - p[u].x = Math::rand() % 100; - p[u].y = Math::rand() % 100; - p[u].z = Math::rand() % 100; - a.add_point(u, p[u]); - for (v = 0; v < N; v++) { - adj[u][v] = adj[v][u] = false; - } - break; - } - } - - // Floyd-Warshall - float d[N][N]; - for (int u = 0; u < N; u++) { - for (int v = 0; v < N; v++) { - d[u][v] = (u == v || adj[u][v]) ? p[u].distance_to(p[v]) : INFINITY; - } - } - - for (int w = 0; w < N; w++) { - for (int u = 0; u < N; u++) { - for (int v = 0; v < N; v++) { - if (d[u][v] > d[u][w] + d[w][v]) { - d[u][v] = d[u][w] + d[w][v]; - } - } - } - } - - // Display statistics - int count = 0; - for (int u = 0; u < N; u++) { - for (int v = 0; v < N; v++) { - if (adj[u][v]) { - count++; - } - } - } - printf("Test #%4d: %3d edges, ", test + 1, count); - count = 0; - for (int u = 0; u < N; u++) { - for (int v = 0; v < N; v++) { - if (!Math::is_inf(d[u][v])) { - count++; - } - } - } - printf("%3d/%d pairs of reachable points\n", count - N, N * (N - 1)); - - // Check A*'s output - bool match = true; - for (int u = 0; u < N; u++) { - for (int v = 0; v < N; v++) { - if (u != v) { - Vector<int> route = a.get_id_path(u, v); - if (!Math::is_inf(d[u][v])) { - // Reachable - if (route.size() == 0) { - printf("From %d to %d: A* did not find a path\n", u, v); - match = false; - goto exit; - } - float astar_dist = 0; - for (int i = 1; i < route.size(); i++) { - if (!adj[route[i - 1]][route[i]]) { - printf("From %d to %d: edge (%d, %d) does not exist\n", - u, v, route[i - 1], route[i]); - match = false; - goto exit; - } - astar_dist += p[route[i - 1]].distance_to(p[route[i]]); - } - if (!Math::is_equal_approx(astar_dist, d[u][v])) { - printf("From %d to %d: Floyd-Warshall gives %.6f, A* gives %.6f\n", - u, v, d[u][v], astar_dist); - match = false; - goto exit; - } - } else { - // Unreachable - if (route.size() > 0) { - printf("From %d to %d: A* somehow found a nonexistent path\n", u, v); - match = false; - goto exit; - } - } - } - } - } - - exit: - if (!match) { - return false; - } - } - return true; -} - -typedef bool (*TestFunc)(); - -TestFunc test_funcs[] = { - test_abc, - test_abcx, - test_add_remove, - test_solutions, - nullptr -}; - -MainLoop *test() { - int count = 0; - int passed = 0; - - while (true) { - if (!test_funcs[count]) { - break; - } - bool pass = test_funcs[count](); - if (pass) { - passed++; - } - OS::get_singleton()->print("\t%s\n", pass ? "PASS" : "FAILED"); - - count++; - } - OS::get_singleton()->print("\n"); - OS::get_singleton()->print("Passed %i of %i tests\n", passed, count); - return nullptr; -} - -} // namespace TestAStar diff --git a/tests/test_astar.h b/tests/test_astar.h index 0992812c18..bef6127471 100644 --- a/tests/test_astar.h +++ b/tests/test_astar.h @@ -31,11 +31,338 @@ #ifndef TEST_ASTAR_H #define TEST_ASTAR_H -#include "core/os/main_loop.h" +#include "core/math/a_star.h" +#include "core/math/math_funcs.h" +#include "core/os/os.h" + +#include <math.h> +#include <stdio.h> + +#include "tests/test_macros.h" namespace TestAStar { -MainLoop *test(); +class ABCX : public AStar { +public: + enum { + A, + B, + C, + X, + }; + + ABCX() { + add_point(A, Vector3(0, 0, 0)); + add_point(B, Vector3(1, 0, 0)); + add_point(C, Vector3(0, 1, 0)); + add_point(X, Vector3(0, 0, 1)); + connect_points(A, B); + connect_points(A, C); + connect_points(B, C); + connect_points(X, A); + } + + // Disable heuristic completely. + float _compute_cost(int p_from, int p_to) { + if (p_from == A && p_to == C) { + return 1000; + } + return 100; + } +}; + +TEST_CASE("[AStar] ABC path") { + ABCX abcx; + Vector<int> path = abcx.get_id_path(ABCX::A, ABCX::C); + REQUIRE(path.size() == 3); + CHECK(path[0] == ABCX::A); + CHECK(path[1] == ABCX::B); + CHECK(path[2] == ABCX::C); +} + +TEST_CASE("[AStar] ABCX path") { + ABCX abcx; + Vector<int> path = abcx.get_id_path(ABCX::X, ABCX::C); + REQUIRE(path.size() == 4); + CHECK(path[0] == ABCX::X); + CHECK(path[1] == ABCX::A); + CHECK(path[2] == ABCX::B); + CHECK(path[3] == ABCX::C); +} + +TEST_CASE("[AStar] Add/Remove") { + AStar a; + + // Manual tests. + a.add_point(1, Vector3(0, 0, 0)); + a.add_point(2, Vector3(0, 1, 0)); + a.add_point(3, Vector3(1, 1, 0)); + a.add_point(4, Vector3(2, 0, 0)); + a.connect_points(1, 2, true); + a.connect_points(1, 3, true); + a.connect_points(1, 4, false); + + CHECK(a.are_points_connected(2, 1)); + CHECK(a.are_points_connected(4, 1)); + CHECK(a.are_points_connected(2, 1, false)); + CHECK_FALSE(a.are_points_connected(4, 1, false)); + + a.disconnect_points(1, 2, true); + CHECK(a.get_point_connections(1).size() == 2); // 3, 4 + CHECK(a.get_point_connections(2).size() == 0); + + a.disconnect_points(4, 1, false); + CHECK(a.get_point_connections(1).size() == 2); // 3, 4 + CHECK(a.get_point_connections(4).size() == 0); + + a.disconnect_points(4, 1, true); + CHECK(a.get_point_connections(1).size() == 1); // 3 + CHECK(a.get_point_connections(4).size() == 0); + + a.connect_points(2, 3, false); + CHECK(a.get_point_connections(2).size() == 1); // 3 + CHECK(a.get_point_connections(3).size() == 1); // 1 + + a.connect_points(2, 3, true); + CHECK(a.get_point_connections(2).size() == 1); // 3 + CHECK(a.get_point_connections(3).size() == 2); // 1, 2 + + a.disconnect_points(2, 3, false); + CHECK(a.get_point_connections(2).size() == 0); + CHECK(a.get_point_connections(3).size() == 2); // 1, 2 + + a.connect_points(4, 3, true); + CHECK(a.get_point_connections(3).size() == 3); // 1, 2, 4 + CHECK(a.get_point_connections(4).size() == 1); // 3 + + a.disconnect_points(3, 4, false); + CHECK(a.get_point_connections(3).size() == 2); // 1, 2 + CHECK(a.get_point_connections(4).size() == 1); // 3 + + a.remove_point(3); + CHECK(a.get_point_connections(1).size() == 0); + CHECK(a.get_point_connections(2).size() == 0); + CHECK(a.get_point_connections(4).size() == 0); + + a.add_point(0, Vector3(0, -1, 0)); + a.add_point(3, Vector3(2, 1, 0)); + // 0: (0, -1) + // 1: (0, 0) + // 2: (0, 1) + // 3: (2, 1) + // 4: (2, 0) + + // Tests for get_closest_position_in_segment. + a.connect_points(2, 3); + CHECK(a.get_closest_position_in_segment(Vector3(0.5, 0.5, 0)) == Vector3(0.5, 1, 0)); + + a.connect_points(3, 4); + a.connect_points(0, 3); + a.connect_points(1, 4); + a.disconnect_points(1, 4, false); + a.disconnect_points(4, 3, false); + a.disconnect_points(3, 4, false); + // Remaining edges: <2, 3>, <0, 3>, <1, 4> (directed). + CHECK(a.get_closest_position_in_segment(Vector3(2, 0.5, 0)) == Vector3(1.75, 0.75, 0)); + CHECK(a.get_closest_position_in_segment(Vector3(-1, 0.2, 0)) == Vector3(0, 0, 0)); + CHECK(a.get_closest_position_in_segment(Vector3(3, 2, 0)) == Vector3(2, 1, 0)); + + Math::seed(0); + + // Random tests for connectivity checks + for (int i = 0; i < 20000; i++) { + int u = Math::rand() % 5; + int v = Math::rand() % 4; + if (u == v) { + v = 4; + } + if (Math::rand() % 2 == 1) { + // Add a (possibly existing) directed edge and confirm connectivity. + a.connect_points(u, v, false); + CHECK(a.are_points_connected(u, v, false)); + } else { + // Remove a (possibly nonexistent) directed edge and confirm disconnectivity. + a.disconnect_points(u, v, false); + CHECK_FALSE(a.are_points_connected(u, v, false)); + } + } + + // Random tests for point removal. + for (int i = 0; i < 20000; i++) { + a.clear(); + for (int j = 0; j < 5; j++) { + a.add_point(j, Vector3(0, 0, 0)); + } + + // Add or remove random edges. + for (int j = 0; j < 10; j++) { + int u = Math::rand() % 5; + int v = Math::rand() % 4; + if (u == v) { + v = 4; + } + if (Math::rand() % 2 == 1) { + a.connect_points(u, v, false); + } else { + a.disconnect_points(u, v, false); + } + } + + // Remove point 0. + a.remove_point(0); + // White box: this will check all edges remaining in the segments set. + for (int j = 1; j < 5; j++) { + CHECK_FALSE(a.are_points_connected(0, j, true)); + } + } + // It's been great work, cheers. \(^ ^)/ +} + +TEST_CASE("[Stress][AStar] Find paths") { + // Random stress tests with Floyd-Warshall. + const int N = 30; + Math::seed(0); + + for (int test = 0; test < 1000; test++) { + AStar a; + Vector3 p[N]; + bool adj[N][N] = { { false } }; + + // Assign initial coordinates. + for (int u = 0; u < N; u++) { + p[u].x = Math::rand() % 100; + p[u].y = Math::rand() % 100; + p[u].z = Math::rand() % 100; + a.add_point(u, p[u]); + } + // Generate a random sequence of operations. + for (int i = 0; i < 1000; i++) { + // Pick two different vertices. + int u, v; + u = Math::rand() % N; + v = Math::rand() % (N - 1); + if (u == v) { + v = N - 1; + } + // Pick a random operation. + int op = Math::rand(); + switch (op % 9) { + case 0: + case 1: + case 2: + case 3: + case 4: + case 5: + // Add edge (u, v); possibly bidirectional. + a.connect_points(u, v, op % 2); + adj[u][v] = true; + if (op % 2) { + adj[v][u] = true; + } + break; + case 6: + case 7: + // Remove edge (u, v); possibly bidirectional. + a.disconnect_points(u, v, op % 2); + adj[u][v] = false; + if (op % 2) { + adj[v][u] = false; + } + break; + case 8: + // Remove point u and add it back; clears adjacent edges and changes coordinates. + a.remove_point(u); + p[u].x = Math::rand() % 100; + p[u].y = Math::rand() % 100; + p[u].z = Math::rand() % 100; + a.add_point(u, p[u]); + for (v = 0; v < N; v++) { + adj[u][v] = adj[v][u] = false; + } + break; + } + } + // Floyd-Warshall. + float d[N][N]; + for (int u = 0; u < N; u++) { + for (int v = 0; v < N; v++) { + d[u][v] = (u == v || adj[u][v]) ? p[u].distance_to(p[v]) : INFINITY; + } + } + for (int w = 0; w < N; w++) { + for (int u = 0; u < N; u++) { + for (int v = 0; v < N; v++) { + if (d[u][v] > d[u][w] + d[w][v]) { + d[u][v] = d[u][w] + d[w][v]; + } + } + } + } + // Display statistics. + int count = 0; + for (int u = 0; u < N; u++) { + for (int v = 0; v < N; v++) { + if (adj[u][v]) { + count++; + } + } + } + print_verbose(vformat("Test #%4d: %3d edges, ", test + 1, count)); + count = 0; + for (int u = 0; u < N; u++) { + for (int v = 0; v < N; v++) { + if (!Math::is_inf(d[u][v])) { + count++; + } + } + } + print_verbose(vformat("%3d/%d pairs of reachable points\n", count - N, N * (N - 1))); + + // Check A*'s output. + bool match = true; + for (int u = 0; u < N; u++) { + for (int v = 0; v < N; v++) { + if (u != v) { + Vector<int> route = a.get_id_path(u, v); + if (!Math::is_inf(d[u][v])) { + // Reachable. + if (route.size() == 0) { + print_verbose(vformat("From %d to %d: A* did not find a path\n", u, v)); + match = false; + goto exit; + } + float astar_dist = 0; + for (int i = 1; i < route.size(); i++) { + if (!adj[route[i - 1]][route[i]]) { + print_verbose(vformat("From %d to %d: edge (%d, %d) does not exist\n", + u, v, route[i - 1], route[i])); + match = false; + goto exit; + } + astar_dist += p[route[i - 1]].distance_to(p[route[i]]); + } + if (!Math::is_equal_approx(astar_dist, d[u][v])) { + print_verbose(vformat("From %d to %d: Floyd-Warshall gives %.6f, A* gives %.6f\n", + u, v, d[u][v], astar_dist)); + match = false; + goto exit; + } + } else { + // Unreachable. + if (route.size() > 0) { + print_verbose(vformat("From %d to %d: A* somehow found a nonexistent path\n", u, v)); + match = false; + goto exit; + } + } + } + } + } + exit: + CHECK_MESSAGE(match, "Found all paths."); + } } -#endif +} // namespace TestAStar + +#endif // TEST_ASTAR_H diff --git a/tests/test_color.h b/tests/test_color.h new file mode 100644 index 0000000000..3633f2746d --- /dev/null +++ b/tests/test_color.h @@ -0,0 +1,213 @@ +/*************************************************************************/ +/* test_color.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* 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_COLOR_H +#define TEST_COLOR_H + +#include "core/color.h" + +#include "thirdparty/doctest/doctest.h" + +namespace TestColor { + +TEST_CASE("[Color] Constructor methods") { + const Color blue_rgba = Color(0.25098, 0.376471, 1, 0.501961); + // HTML currently uses ARGB notation, which is contrary to the CSS standard. + // This may be changed to RGBA in 4.0. + const Color blue_html = Color::html("#804060ff"); + const Color blue_hex = Color::hex(0x4060ff80); + const Color blue_hex64 = Color::hex64(0x4040'6060'ffff'8080); + + CHECK_MESSAGE( + blue_rgba.is_equal_approx(blue_html), + "Creation with HTML notation should result in components approximately equal to the default constructor."); + CHECK_MESSAGE( + blue_rgba.is_equal_approx(blue_hex), + "Creation with a 32-bit hexadecimal number should result in components approximately equal to the default constructor."); + CHECK_MESSAGE( + blue_rgba.is_equal_approx(blue_hex64), + "Creation with a 64-bit hexadecimal number should result in components approximately equal to the default constructor."); + + ERR_PRINT_OFF; + const Color html_invalid = Color::html("invalid"); + ERR_PRINT_ON; + + CHECK_MESSAGE( + html_invalid.is_equal_approx(Color()), + "Creation with invalid HTML notation should result in a Color with the default values."); + + const Color green_rgba = Color(0, 1, 0, 0.25); + const Color green_hsva = Color(0, 0, 0).from_hsv(120 / 360.0, 1, 1, 0.25); + + CHECK_MESSAGE( + green_rgba.is_equal_approx(green_hsva), + "Creation with HSV notation should result in components approximately equal to the default constructor."); +} + +TEST_CASE("[Color] Operators") { + const Color blue = Color(0.2, 0.2, 1); + const Color dark_red = Color(0.3, 0.1, 0.1); + + // Color components may be negative. Also, the alpha component may be greater than 1.0. + CHECK_MESSAGE( + (blue + dark_red).is_equal_approx(Color(0.5, 0.3, 1.1, 2)), + "Color addition should behave as expected."); + CHECK_MESSAGE( + (blue - dark_red).is_equal_approx(Color(-0.1, 0.1, 0.9, 0)), + "Color subtraction should behave as expected."); + CHECK_MESSAGE( + (blue * 2).is_equal_approx(Color(0.4, 0.4, 2, 2)), + "Color multiplication with a scalar should behave as expected."); + CHECK_MESSAGE( + (blue / 2).is_equal_approx(Color(0.1, 0.1, 0.5, 0.5)), + "Color division with a scalar should behave as expected."); + CHECK_MESSAGE( + (blue * dark_red).is_equal_approx(Color(0.06, 0.02, 0.1)), + "Color multiplication with another Color should behave as expected."); + CHECK_MESSAGE( + (blue / dark_red).is_equal_approx(Color(0.666667, 2, 10)), + "Color division with another Color should behave as expected."); + CHECK_MESSAGE( + (-blue).is_equal_approx(Color(0.8, 0.8, 0, 0)), + "Color negation should behave as expected (affecting the alpha channel, unlike `invert()`)."); +} + +TEST_CASE("[Color] Reading methods") { + const Color dark_blue = Color(0, 0, 0.5, 0.4); + + CHECK_MESSAGE( + Math::is_equal_approx(dark_blue.get_h(), 240 / 360.0), + "The returned HSV hue should match the expected value."); + CHECK_MESSAGE( + Math::is_equal_approx(dark_blue.get_s(), 1), + "The returned HSV saturation should match the expected value."); + CHECK_MESSAGE( + Math::is_equal_approx(dark_blue.get_v(), 0.5), + "The returned HSV value should match the expected value."); +} + +TEST_CASE("[Color] Conversion methods") { + const Color cyan = Color(0, 1, 1); + const Color cyan_transparent = Color(0, 1, 1, 0); + + CHECK_MESSAGE( + cyan.to_html() == "ff00ffff", + "The returned RGB HTML color code should match the expected value."); + CHECK_MESSAGE( + cyan_transparent.to_html() == "0000ffff", + "The returned RGBA HTML color code should match the expected value."); + CHECK_MESSAGE( + cyan.to_argb32() == 0xff00ffff, + "The returned 32-bit RGB number should match the expected value."); + CHECK_MESSAGE( + cyan.to_abgr32() == 0xffffff00, + "The returned 32-bit BGR number should match the expected value."); + CHECK_MESSAGE( + cyan.to_rgba32() == 0x00ffffff, + "The returned 32-bit BGR number should match the expected value."); + CHECK_MESSAGE( + cyan.to_argb64() == 0xffff'0000'ffff'ffff, + "The returned 64-bit RGB number should match the expected value."); + CHECK_MESSAGE( + cyan.to_abgr64() == 0xffff'ffff'ffff'0000, + "The returned 64-bit BGR number should match the expected value."); + CHECK_MESSAGE( + cyan.to_rgba64() == 0x0000'ffff'ffff'ffff, + "The returned 64-bit BGR number should match the expected value."); + CHECK_MESSAGE( + String(cyan) == "0, 1, 1, 1", + "The string representation should match the expected value."); +} + +TEST_CASE("[Color] Named colors") { + CHECK_MESSAGE( + Color::named("red").is_equal_approx(Color(1, 0, 0)), + "The named color \"red\" should match the expected value."); + + // Named colors have their names automatically normalized. + CHECK_MESSAGE( + Color::named("white_smoke").is_equal_approx(Color(0.96, 0.96, 0.96)), + "The named color \"white_smoke\" should match the expected value."); + CHECK_MESSAGE( + Color::named("Slate Blue").is_equal_approx(Color(0.42, 0.35, 0.80)), + "The named color \"Slate Blue\" should match the expected value."); + + ERR_PRINT_OFF; + CHECK_MESSAGE( + Color::named("doesn't exist").is_equal_approx(Color()), + "The invalid named color \"doesn't exist\" should result in a Color with the default values."); + ERR_PRINT_ON; +} + +TEST_CASE("[Color] Validation methods") { + CHECK_MESSAGE( + Color::html_is_valid("#4080ff"), + "Valid HTML color (with leading #) should be considered valid."); + CHECK_MESSAGE( + Color::html_is_valid("4080ff"), + "Valid HTML color (without leading #) should be considered valid."); + CHECK_MESSAGE( + !Color::html_is_valid("12345"), + "Invalid HTML color should be considered invalid."); + CHECK_MESSAGE( + !Color::html_is_valid("#fuf"), + "Invalid HTML color should be considered invalid."); +} + +TEST_CASE("[Color] Manipulation methods") { + const Color blue = Color(0, 0, 1, 0.4); + + CHECK_MESSAGE( + blue.inverted().is_equal_approx(Color(1, 1, 0, 0.4)), + "Inverted color should have its red, green and blue components inverted."); + CHECK_MESSAGE( + blue.contrasted().is_equal_approx(Color(0.5, 0.5, 0.5, 0.4)), + "Contrasted pure blue should be fully gray."); + + const Color purple = Color(0.5, 0.2, 0.5, 0.25); + + CHECK_MESSAGE( + purple.lightened(0.2).is_equal_approx(Color(0.6, 0.36, 0.6, 0.25)), + "Color should be lightened by the expected amount."); + CHECK_MESSAGE( + purple.darkened(0.2).is_equal_approx(Color(0.4, 0.16, 0.4, 0.25)), + "Color should be darkened by the expected amount."); + + const Color red = Color(1, 0, 0, 0.2); + const Color yellow = Color(1, 1, 0, 0.8); + + CHECK_MESSAGE( + red.lerp(yellow, 0.5).is_equal_approx(Color(1, 0.5, 0, 0.5)), + "Red interpolated with yellow should be orange (with interpolated alpha)."); +} + +} // namespace TestColor + +#endif // TEST_COLOR_H diff --git a/tests/test_macros.h b/tests/test_macros.h new file mode 100644 index 0000000000..e4494ce11a --- /dev/null +++ b/tests/test_macros.h @@ -0,0 +1,50 @@ +/*************************************************************************/ +/* test_macros.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* 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_MACROS_H +#define TEST_MACROS_H + +// See documentation for doctest at: +// https://github.com/onqtam/doctest/blob/master/doc/markdown/readme.md#reference +#include "thirdparty/doctest/doctest.h" + +// The test is skipped with this, run pending tests with `--test --no-skip`. +#define TEST_CASE_PENDING(name) TEST_CASE(name *doctest::skip()) + +// Temporarily disable error prints to test failure paths. +// This allows to avoid polluting the test summary with error messages. +// The `_print_error_enabled` boolean is defined in `core/print_string.cpp` and +// works at global scope. It's used by various loggers in `should_log()` method, +// which are used by error macros which call into `OS::print_error`, effectively +// disabling any error messages to be printed from the engine side (not tests). +#define ERR_PRINT_OFF _print_error_enabled = false; +#define ERR_PRINT_ON _print_error_enabled = true; + +#endif // TEST_MACROS_H diff --git a/tests/test_main.cpp b/tests/test_main.cpp index 84d2fe1f6c..137882841c 100644 --- a/tests/test_main.cpp +++ b/tests/test_main.cpp @@ -32,11 +32,10 @@ #include "core/list.h" -#ifdef DEBUG_ENABLED - #include "test_astar.h" #include "test_basis.h" #include "test_class_db.h" +#include "test_color.h" #include "test_gdscript.h" #include "test_gui.h" #include "test_math.h" @@ -52,39 +51,14 @@ #include "modules/modules_tests.gen.h" -#include "thirdparty/doctest/doctest.h" - -const char **tests_get_names() { - static const char *test_names[] = { - "*", - "all", - "math", - "basis", - "physics_2d", - "physics_3d", - "render", - "oa_hash_map", - "class_db", - "gui", - "shaderlang", - "gd_tokenizer", - "gd_parser", - "gd_compiler", - "gd_bytecode", - "ordered_hash_map", - "astar", - nullptr - }; - - return test_names; -} +#include "tests/test_macros.h" int test_main(int argc, char *argv[]) { - // doctest runner for when legacy unit tests are no found + // Doctest runner. doctest::Context test_context; List<String> valid_arguments; - // clean arguments of --test from the args + // Clean arguments of "--test" from the args. int argument_count = 0; for (int x = 0; x < argc; x++) { if (strncmp(argv[x], "--test", 6) != 0) { @@ -92,15 +66,15 @@ int test_main(int argc, char *argv[]) { argument_count++; } } - - // convert godot command line arguments back to standard arguments. + // Convert Godot command line arguments back to standard arguments. char **args = new char *[valid_arguments.size()]; for (int x = 0; x < valid_arguments.size(); x++) { - // operation to convert godot string to non wchar string - const char *str = valid_arguments[x].utf8().ptr(); - // allocate the string copy + // Operation to convert Godot string to non wchar string. + CharString cs = valid_arguments[x].utf8(); + const char *str = cs.get_data(); + // Allocate the string copy. args[x] = new char[strlen(str) + 1]; - // copy this into memory + // Copy this into memory. std::memcpy(args[x], str, strlen(str) + 1); } @@ -109,22 +83,11 @@ int test_main(int argc, char *argv[]) { test_context.setOption("order-by", "name"); test_context.setOption("abort-after", 5); test_context.setOption("no-breaks", true); - delete[] args; - return test_context.run(); -} - -#else -const char **tests_get_names() { - static const char *test_names[] = { - nullptr - }; - - return test_names; -} + for (int x = 0; x < valid_arguments.size(); x++) { + delete[] args[x]; + } + delete[] args; -int test_main(int argc, char *argv[]) { - return 0; + return test_context.run(); } - -#endif diff --git a/tests/test_main.h b/tests/test_main.h index 8273b74eac..983bfde402 100644 --- a/tests/test_main.h +++ b/tests/test_main.h @@ -31,11 +31,6 @@ #ifndef TEST_MAIN_H #define TEST_MAIN_H -#include "core/list.h" -#include "core/os/main_loop.h" -#include "core/ustring.h" - -const char **tests_get_names(); int test_main(int argc, char *argv[]); #endif // TEST_MAIN_H diff --git a/tests/test_string.h b/tests/test_string.h index 310be31f4b..22019a64c6 100644 --- a/tests/test_string.h +++ b/tests/test_string.h @@ -44,7 +44,7 @@ #include "modules/regex/regex.h" #endif -#include "thirdparty/doctest/doctest.h" +#include "tests/test_macros.h" namespace TestString { diff --git a/tests/test_validate_testing.h b/tests/test_validate_testing.h index 5be7d45185..24acdec96c 100644 --- a/tests/test_validate_testing.h +++ b/tests/test_validate_testing.h @@ -33,10 +33,26 @@ #include "core/os/os.h" -#include "thirdparty/doctest/doctest.h" +#include "tests/test_macros.h" -TEST_CASE("Validate Test will always pass") { - CHECK(true); +TEST_SUITE("Validate tests") { + TEST_CASE("Always pass") { + CHECK(true); + } + TEST_CASE_PENDING("Pending tests are skipped") { + if (!doctest::getContextOptions()->no_skip) { // Normal run. + FAIL("This should be skipped if `--no-skip` is NOT set (missing `doctest::skip()` decorator?)"); + } else { + CHECK_MESSAGE(true, "Pending test is run with `--no-skip`"); + } + } + TEST_CASE("Muting Godot error messages") { + ERR_PRINT_OFF; + CHECK_MESSAGE(!_print_error_enabled, "Error printing should be disabled."); + ERR_PRINT("Still waiting for Godot!"); // This should never get printed! + ERR_PRINT_ON; + CHECK_MESSAGE(_print_error_enabled, "Error printing should be re-enabled."); + } } #endif // TEST_VALIDATE_TESTING_H diff --git a/tests/test_variant.h b/tests/test_variant.h index 06dcfde664..a384a3e91f 100644 --- a/tests/test_variant.h +++ b/tests/test_variant.h @@ -34,7 +34,7 @@ #include "core/variant.h" #include "core/variant_parser.h" -#include "thirdparty/doctest/doctest.h" +#include "tests/test_macros.h" namespace TestVariant { |