summaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
authorAndrii Doroshenko (Xrayez) <xrayez@gmail.com>2020-07-26 00:06:07 +0300
committerAndrii Doroshenko (Xrayez) <xrayez@gmail.com>2020-07-26 00:06:07 +0300
commit9f649efe5d2595977d429625cca30ff7704c0a59 (patch)
tree258c242316e2721d5dc74a5c0388a4dbd2da5c16 /tests
parent6f292f906e749f30ceb7f922df5639add61a5f88 (diff)
Move `tests` to the top-level directory
Diffstat (limited to 'tests')
-rw-r--r--tests/SCsub9
-rw-r--r--tests/test_astar.cpp409
-rw-r--r--tests/test_astar.h41
-rw-r--r--tests/test_basis.cpp325
-rw-r--r--tests/test_basis.h40
-rw-r--r--tests/test_class_db.cpp882
-rw-r--r--tests/test_class_db.h42
-rw-r--r--tests/test_gdscript.cpp187
-rw-r--r--tests/test_gdscript.h48
-rw-r--r--tests/test_gui.cpp271
-rw-r--r--tests/test_gui.h41
-rw-r--r--tests/test_main.cpp127
-rw-r--r--tests/test_main.h41
-rw-r--r--tests/test_math.cpp703
-rw-r--r--tests/test_math.h41
-rw-r--r--tests/test_oa_hash_map.cpp299
-rw-r--r--tests/test_oa_hash_map.h41
-rw-r--r--tests/test_ordered_hash_map.cpp175
-rw-r--r--tests/test_ordered_hash_map.h41
-rw-r--r--tests/test_physics_2d.cpp407
-rw-r--r--tests/test_physics_2d.h41
-rw-r--r--tests/test_physics_3d.cpp413
-rw-r--r--tests/test_physics_3d.h41
-rw-r--r--tests/test_render.cpp240
-rw-r--r--tests/test_render.h41
-rw-r--r--tests/test_shader_lang.cpp361
-rw-r--r--tests/test_shader_lang.h41
-rw-r--r--tests/test_string.h798
-rw-r--r--tests/test_validate_testing.h42
29 files changed, 6188 insertions, 0 deletions
diff --git a/tests/SCsub b/tests/SCsub
new file mode 100644
index 0000000000..cb1d35b12f
--- /dev/null
+++ b/tests/SCsub
@@ -0,0 +1,9 @@
+#!/usr/bin/python
+
+Import("env")
+
+env.tests_sources = []
+env.add_source_files(env.tests_sources, "*.cpp")
+
+lib = env.add_library("tests", env.tests_sources)
+env.Prepend(LIBS=[lib])
diff --git a/tests/test_astar.cpp b/tests/test_astar.cpp
new file mode 100644
index 0000000000..cb5fcfe37b
--- /dev/null
+++ b/tests/test_astar.cpp
@@ -0,0 +1,409 @@
+/*************************************************************************/
+/* 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
new file mode 100644
index 0000000000..0992812c18
--- /dev/null
+++ b/tests/test_astar.h
@@ -0,0 +1,41 @@
+/*************************************************************************/
+/* test_astar.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_ASTAR_H
+#define TEST_ASTAR_H
+
+#include "core/os/main_loop.h"
+
+namespace TestAStar {
+
+MainLoop *test();
+}
+
+#endif
diff --git a/tests/test_basis.cpp b/tests/test_basis.cpp
new file mode 100644
index 0000000000..5904fc386a
--- /dev/null
+++ b/tests/test_basis.cpp
@@ -0,0 +1,325 @@
+/*************************************************************************/
+/* test_basis.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_basis.h"
+
+#include "core/math/random_number_generator.h"
+#include "core/os/os.h"
+#include "core/ustring.h"
+
+namespace TestBasis {
+
+enum RotOrder {
+ EulerXYZ,
+ EulerXZY,
+ EulerYZX,
+ EulerYXZ,
+ EulerZXY,
+ EulerZYX
+};
+
+Vector3 deg2rad(const Vector3 &p_rotation) {
+ return p_rotation / 180.0 * Math_PI;
+}
+
+Vector3 rad2deg(const Vector3 &p_rotation) {
+ return p_rotation / Math_PI * 180.0;
+}
+
+Basis EulerToBasis(RotOrder mode, const Vector3 &p_rotation) {
+ Basis ret;
+ switch (mode) {
+ case EulerXYZ:
+ ret.set_euler_xyz(p_rotation);
+ break;
+
+ case EulerXZY:
+ ret.set_euler_xzy(p_rotation);
+ break;
+
+ case EulerYZX:
+ ret.set_euler_yzx(p_rotation);
+ break;
+
+ case EulerYXZ:
+ ret.set_euler_yxz(p_rotation);
+ break;
+
+ case EulerZXY:
+ ret.set_euler_zxy(p_rotation);
+ break;
+
+ case EulerZYX:
+ ret.set_euler_zyx(p_rotation);
+ break;
+
+ default:
+ // If you land here, Please integrate all rotation orders.
+ CRASH_NOW_MSG("This is not unreachable.");
+ }
+
+ return ret;
+}
+
+Vector3 BasisToEuler(RotOrder mode, const Basis &p_rotation) {
+ switch (mode) {
+ case EulerXYZ:
+ return p_rotation.get_euler_xyz();
+
+ case EulerXZY:
+ return p_rotation.get_euler_xzy();
+
+ case EulerYZX:
+ return p_rotation.get_euler_yzx();
+
+ case EulerYXZ:
+ return p_rotation.get_euler_yxz();
+
+ case EulerZXY:
+ return p_rotation.get_euler_zxy();
+
+ case EulerZYX:
+ return p_rotation.get_euler_zyx();
+
+ default:
+ // If you land here, Please integrate all rotation orders.
+ CRASH_NOW_MSG("This is not unreachable.");
+ return Vector3();
+ }
+}
+
+String get_rot_order_name(RotOrder ro) {
+ switch (ro) {
+ case EulerXYZ:
+ return "XYZ";
+ case EulerXZY:
+ return "XZY";
+ case EulerYZX:
+ return "YZX";
+ case EulerYXZ:
+ return "YXZ";
+ case EulerZXY:
+ return "ZXY";
+ case EulerZYX:
+ return "ZYX";
+ default:
+ return "[Not supported]";
+ }
+}
+
+bool test_rotation(Vector3 deg_original_euler, RotOrder rot_order) {
+ // This test:
+ // 1. Converts the rotation vector from deg to rad.
+ // 2. Converts euler to basis.
+ // 3. Converts the above basis back into euler.
+ // 4. Converts the above euler into basis again.
+ // 5. Compares the basis obtained in step 2 with the basis of step 4
+ //
+ // The conversion "basis to euler", done in the step 3, may be different from
+ // the original euler, even if the final rotation are the same.
+ // This happens because there are more ways to represents the same rotation,
+ // both valid, using eulers.
+ // For this reason is necessary to convert that euler back to basis and finally
+ // compares it.
+ //
+ // In this way we can assert that both functions: basis to euler / euler to basis
+ // are correct.
+
+ bool pass = true;
+
+ // Euler to rotation
+ const Vector3 original_euler = deg2rad(deg_original_euler);
+ const Basis to_rotation = EulerToBasis(rot_order, original_euler);
+
+ // Euler from rotation
+ const Vector3 euler_from_rotation = BasisToEuler(rot_order, to_rotation);
+ const Basis rotation_from_computed_euler = EulerToBasis(rot_order, euler_from_rotation);
+
+ Basis res = to_rotation.inverse() * rotation_from_computed_euler;
+
+ if ((res.get_axis(0) - Vector3(1.0, 0.0, 0.0)).length() > 0.1) {
+ OS::get_singleton()->print("Fail due to X %ls\n", String(res.get_axis(0)).c_str());
+ pass = false;
+ }
+ if ((res.get_axis(1) - Vector3(0.0, 1.0, 0.0)).length() > 0.1) {
+ OS::get_singleton()->print("Fail due to Y %ls\n", String(res.get_axis(1)).c_str());
+ pass = false;
+ }
+ if ((res.get_axis(2) - Vector3(0.0, 0.0, 1.0)).length() > 0.1) {
+ OS::get_singleton()->print("Fail due to Z %ls\n", String(res.get_axis(2)).c_str());
+ pass = false;
+ }
+
+ if (pass) {
+ // Double check `to_rotation` decomposing with XYZ rotation order.
+ const Vector3 euler_xyz_from_rotation = to_rotation.get_euler_xyz();
+ Basis rotation_from_xyz_computed_euler;
+ rotation_from_xyz_computed_euler.set_euler_xyz(euler_xyz_from_rotation);
+
+ res = to_rotation.inverse() * rotation_from_xyz_computed_euler;
+
+ if ((res.get_axis(0) - Vector3(1.0, 0.0, 0.0)).length() > 0.1) {
+ OS::get_singleton()->print("Double check with XYZ rot order failed, due to X %ls\n", String(res.get_axis(0)).c_str());
+ pass = false;
+ }
+ if ((res.get_axis(1) - Vector3(0.0, 1.0, 0.0)).length() > 0.1) {
+ OS::get_singleton()->print("Double check with XYZ rot order failed, due to Y %ls\n", String(res.get_axis(1)).c_str());
+ pass = false;
+ }
+ if ((res.get_axis(2) - Vector3(0.0, 0.0, 1.0)).length() > 0.1) {
+ OS::get_singleton()->print("Double check with XYZ rot order failed, due to Z %ls\n", String(res.get_axis(2)).c_str());
+ pass = false;
+ }
+ }
+
+ if (pass == false) {
+ // Print phase only if not pass.
+ OS *os = OS::get_singleton();
+ os->print("Rotation order: %ls\n.", get_rot_order_name(rot_order).c_str());
+ os->print("Original Rotation: %ls\n", String(deg_original_euler).c_str());
+ os->print("Quaternion to rotation order: %ls\n", String(rad2deg(euler_from_rotation)).c_str());
+ }
+
+ return pass;
+}
+
+void test_euler_conversion() {
+ Vector<RotOrder> rotorder_to_test;
+ rotorder_to_test.push_back(EulerXYZ);
+ rotorder_to_test.push_back(EulerXZY);
+ rotorder_to_test.push_back(EulerYZX);
+ rotorder_to_test.push_back(EulerYXZ);
+ rotorder_to_test.push_back(EulerZXY);
+ rotorder_to_test.push_back(EulerZYX);
+
+ Vector<Vector3> vectors_to_test;
+
+ // Test the special cases.
+ vectors_to_test.push_back(Vector3(0.0, 0.0, 0.0));
+ vectors_to_test.push_back(Vector3(0.5, 0.5, 0.5));
+ vectors_to_test.push_back(Vector3(-0.5, -0.5, -0.5));
+ vectors_to_test.push_back(Vector3(40.0, 40.0, 40.0));
+ vectors_to_test.push_back(Vector3(-40.0, -40.0, -40.0));
+ vectors_to_test.push_back(Vector3(0.0, 0.0, -90.0));
+ vectors_to_test.push_back(Vector3(0.0, -90.0, 0.0));
+ vectors_to_test.push_back(Vector3(-90.0, 0.0, 0.0));
+ vectors_to_test.push_back(Vector3(0.0, 0.0, 90.0));
+ vectors_to_test.push_back(Vector3(0.0, 90.0, 0.0));
+ vectors_to_test.push_back(Vector3(90.0, 0.0, 0.0));
+ vectors_to_test.push_back(Vector3(0.0, 0.0, -30.0));
+ vectors_to_test.push_back(Vector3(0.0, -30.0, 0.0));
+ vectors_to_test.push_back(Vector3(-30.0, 0.0, 0.0));
+ vectors_to_test.push_back(Vector3(0.0, 0.0, 30.0));
+ vectors_to_test.push_back(Vector3(0.0, 30.0, 0.0));
+ vectors_to_test.push_back(Vector3(30.0, 0.0, 0.0));
+ vectors_to_test.push_back(Vector3(0.5, 50.0, 20.0));
+ vectors_to_test.push_back(Vector3(-0.5, -50.0, -20.0));
+ vectors_to_test.push_back(Vector3(0.5, 0.0, 90.0));
+ vectors_to_test.push_back(Vector3(0.5, 0.0, -90.0));
+ vectors_to_test.push_back(Vector3(360.0, 360.0, 360.0));
+ vectors_to_test.push_back(Vector3(-360.0, -360.0, -360.0));
+ vectors_to_test.push_back(Vector3(-90.0, 60.0, -90.0));
+ vectors_to_test.push_back(Vector3(90.0, 60.0, -90.0));
+ vectors_to_test.push_back(Vector3(90.0, -60.0, -90.0));
+ vectors_to_test.push_back(Vector3(-90.0, -60.0, -90.0));
+ vectors_to_test.push_back(Vector3(-90.0, 60.0, 90.0));
+ vectors_to_test.push_back(Vector3(90.0, 60.0, 90.0));
+ vectors_to_test.push_back(Vector3(90.0, -60.0, 90.0));
+ vectors_to_test.push_back(Vector3(-90.0, -60.0, 90.0));
+ vectors_to_test.push_back(Vector3(60.0, 90.0, -40.0));
+ vectors_to_test.push_back(Vector3(60.0, -90.0, -40.0));
+ vectors_to_test.push_back(Vector3(-60.0, -90.0, -40.0));
+ vectors_to_test.push_back(Vector3(-60.0, 90.0, 40.0));
+ vectors_to_test.push_back(Vector3(60.0, 90.0, 40.0));
+ vectors_to_test.push_back(Vector3(60.0, -90.0, 40.0));
+ vectors_to_test.push_back(Vector3(-60.0, -90.0, 40.0));
+ vectors_to_test.push_back(Vector3(-90.0, 90.0, -90.0));
+ vectors_to_test.push_back(Vector3(90.0, 90.0, -90.0));
+ vectors_to_test.push_back(Vector3(90.0, -90.0, -90.0));
+ vectors_to_test.push_back(Vector3(-90.0, -90.0, -90.0));
+ vectors_to_test.push_back(Vector3(-90.0, 90.0, 90.0));
+ vectors_to_test.push_back(Vector3(90.0, 90.0, 90.0));
+ vectors_to_test.push_back(Vector3(90.0, -90.0, 90.0));
+ vectors_to_test.push_back(Vector3(20.0, 150.0, 30.0));
+ vectors_to_test.push_back(Vector3(20.0, -150.0, 30.0));
+ vectors_to_test.push_back(Vector3(-120.0, -150.0, 30.0));
+ vectors_to_test.push_back(Vector3(-120.0, -150.0, -130.0));
+ vectors_to_test.push_back(Vector3(120.0, -150.0, -130.0));
+ vectors_to_test.push_back(Vector3(120.0, 150.0, -130.0));
+ vectors_to_test.push_back(Vector3(120.0, 150.0, 130.0));
+
+ // Add 1000 random vectors with weirds numbers.
+ RandomNumberGenerator rng;
+ for (int _ = 0; _ < 1000; _ += 1) {
+ vectors_to_test.push_back(Vector3(
+ rng.randf_range(-1800, 1800),
+ rng.randf_range(-1800, 1800),
+ rng.randf_range(-1800, 1800)));
+ }
+
+ bool success = true;
+ for (int h = 0; h < rotorder_to_test.size(); h += 1) {
+ int passed = 0;
+ int failed = 0;
+ for (int i = 0; i < vectors_to_test.size(); i += 1) {
+ if (test_rotation(vectors_to_test[i], rotorder_to_test[h])) {
+ //OS::get_singleton()->print("Success. \n\n");
+ passed += 1;
+ } else {
+ OS::get_singleton()->print("FAILED FAILED FAILED. \n\n");
+ OS::get_singleton()->print("------------>\n");
+ OS::get_singleton()->print("------------>\n");
+ failed += 1;
+ success = false;
+ }
+ }
+
+ if (failed == 0) {
+ OS::get_singleton()->print("%i passed tests for rotation order: %ls.\n", passed, get_rot_order_name(rotorder_to_test[h]).c_str());
+ } else {
+ OS::get_singleton()->print("%i FAILED tests for rotation order: %ls.\n", failed, get_rot_order_name(rotorder_to_test[h]).c_str());
+ }
+ }
+
+ if (success) {
+ OS::get_singleton()->print("Euler conversion checks passed.\n");
+ } else {
+ OS::get_singleton()->print("Euler conversion checks FAILED.\n");
+ }
+}
+
+MainLoop *test() {
+ OS::get_singleton()->print("Start euler conversion checks.\n");
+ test_euler_conversion();
+
+ return NULL;
+}
+
+} // namespace TestBasis
diff --git a/tests/test_basis.h b/tests/test_basis.h
new file mode 100644
index 0000000000..63297bd3b8
--- /dev/null
+++ b/tests/test_basis.h
@@ -0,0 +1,40 @@
+/*************************************************************************/
+/* test_basis.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_BASIS_H
+#define TEST_BASIS_H
+
+#include "core/os/main_loop.h"
+
+namespace TestBasis {
+MainLoop *test();
+}
+
+#endif
diff --git a/tests/test_class_db.cpp b/tests/test_class_db.cpp
new file mode 100644
index 0000000000..3171091402
--- /dev/null
+++ b/tests/test_class_db.cpp
@@ -0,0 +1,882 @@
+/*************************************************************************/
+/* test_class_db.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_class_db.h"
+
+#include "core/global_constants.h"
+#include "core/ordered_hash_map.h"
+#include "core/os/os.h"
+#include "core/string_name.h"
+#include "core/ustring.h"
+#include "core/variant.h"
+
+namespace TestClassDB {
+
+enum class [[nodiscard]] TestResult{
+ FAILED,
+ PASS
+};
+
+#define TEST_FAIL_COND(m_cond, m_msg) \
+ if (unlikely(m_cond)) { \
+ ERR_PRINT(m_msg); \
+ return TestResult::FAILED; \
+ } else \
+ ((void)0)
+
+#define TEST_COND(m_cond, m_msg) \
+ if (unlikely(m_cond)) { \
+ ERR_PRINT(m_msg); \
+ __test_result__ = TestResult::FAILED; \
+ } else \
+ ((void)0)
+
+#define TEST_FAIL_CHECK(m_test_expr) \
+ if (unlikely((m_test_expr) == TestResult::FAILED)) { \
+ return TestResult::FAILED; \
+ } else \
+ ((void)0)
+
+#define TEST_CHECK(m_test_expr) \
+ if (unlikely((m_test_expr) == TestResult::FAILED)) { \
+ __test_result__ = TestResult::FAILED; \
+ } else \
+ ((void)0)
+
+#define TEST_START() \
+ TestResult __test_result__ = TestResult::PASS; \
+ ((void)0)
+
+#define TEST_END() return __test_result__;
+
+struct TypeReference {
+ StringName name;
+ bool is_enum = false;
+};
+
+struct ConstantData {
+ String name;
+ int value = 0;
+};
+
+struct EnumData {
+ StringName name;
+ List<ConstantData> constants;
+
+ _FORCE_INLINE_ bool operator==(const EnumData &p_enum) const {
+ return p_enum.name == name;
+ }
+};
+
+struct PropertyData {
+ StringName name;
+ int index = 0;
+
+ StringName getter;
+ StringName setter;
+};
+
+struct ArgumentData {
+ TypeReference type;
+ String name;
+ bool has_defval = false;
+ Variant defval;
+};
+
+struct MethodData {
+ StringName name;
+ TypeReference return_type;
+ List<ArgumentData> arguments;
+ bool is_virtual = false;
+ bool is_vararg = false;
+};
+
+struct SignalData {
+ StringName name;
+ List<ArgumentData> arguments;
+};
+
+struct ExposedClass {
+ StringName name;
+ StringName base;
+
+ bool is_singleton = false;
+ bool is_instantiable = false;
+ bool is_reference = false;
+
+ ClassDB::APIType api_type;
+
+ List<ConstantData> constants;
+ List<EnumData> enums;
+ List<PropertyData> properties;
+ List<MethodData> methods;
+ List<SignalData> signals_;
+
+ const PropertyData *find_property_by_name(const StringName &p_name) const {
+ for (const List<PropertyData>::Element *E = properties.front(); E; E = E->next()) {
+ if (E->get().name == p_name) {
+ return &E->get();
+ }
+ }
+
+ return nullptr;
+ }
+
+ const MethodData *find_method_by_name(const StringName &p_name) const {
+ for (const List<MethodData>::Element *E = methods.front(); E; E = E->next()) {
+ if (E->get().name == p_name) {
+ return &E->get();
+ }
+ }
+
+ return nullptr;
+ }
+};
+
+struct NamesCache {
+ StringName variant_type = StaticCString::create("Variant");
+ StringName object_class = StaticCString::create("Object");
+ StringName reference_class = StaticCString::create("Reference");
+ StringName string_type = StaticCString::create("String");
+ StringName string_name_type = StaticCString::create("StringName");
+ StringName node_path_type = StaticCString::create("NodePath");
+ StringName bool_type = StaticCString::create("bool");
+ StringName int_type = StaticCString::create("int");
+ StringName float_type = StaticCString::create("float");
+ StringName void_type = StaticCString::create("void");
+ StringName vararg_stub_type = StaticCString::create("@VarArg@");
+ StringName vector2_type = StaticCString::create("Vector2");
+ StringName rect2_type = StaticCString::create("Rect2");
+ StringName vector3_type = StaticCString::create("Vector3");
+
+ // Object not included as it must be checked for all derived classes
+ static constexpr int nullable_types_count = 17;
+ StringName nullable_types[nullable_types_count] = {
+ string_type,
+ string_name_type,
+ node_path_type,
+
+ StaticCString::create(_STR(Array)),
+ StaticCString::create(_STR(Dictionary)),
+ StaticCString::create(_STR(Callable)),
+ StaticCString::create(_STR(Signal)),
+
+ StaticCString::create(_STR(PackedByteArray)),
+ StaticCString::create(_STR(PackedInt32Array)),
+ StaticCString::create(_STR(PackedInt64rray)),
+ StaticCString::create(_STR(PackedFloat32Array)),
+ StaticCString::create(_STR(PackedFloat64Array)),
+ StaticCString::create(_STR(PackedStringArray)),
+ StaticCString::create(_STR(PackedVector2Array)),
+ StaticCString::create(_STR(PackedVector3Array)),
+ StaticCString::create(_STR(PackedColorArray)),
+ };
+
+ bool is_nullable_type(const StringName &p_type) const {
+ for (int i = 0; i < nullable_types_count; i++) {
+ if (p_type == nullable_types[i]) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+};
+
+typedef OrderedHashMap<StringName, ExposedClass> ExposedClasses;
+
+struct Context {
+ Vector<StringName> enum_types;
+ Vector<StringName> builtin_types;
+ ExposedClasses exposed_classes;
+ List<EnumData> global_enums;
+ NamesCache names_cache;
+
+ const ExposedClass *find_exposed_class(const StringName &p_name) const {
+ ExposedClasses::ConstElement elem = exposed_classes.find(p_name);
+ return elem ? &elem.value() : nullptr;
+ }
+
+ const ExposedClass *find_exposed_class(const TypeReference &p_type_ref) const {
+ ExposedClasses::ConstElement elem = exposed_classes.find(p_type_ref.name);
+ return elem ? &elem.value() : nullptr;
+ }
+
+ bool has_type(const TypeReference &p_type_ref) const {
+ if (builtin_types.find(p_type_ref.name) >= 0) {
+ return true;
+ }
+
+ if (p_type_ref.is_enum) {
+ if (enum_types.find(p_type_ref.name) >= 0) {
+ return true;
+ }
+
+ // Enum not found. Most likely because none of its constants were bound, so it's empty. That's fine. Use int instead.
+ return builtin_types.find(names_cache.int_type);
+ }
+
+ return false;
+ }
+};
+
+bool arg_default_value_is_assignable_to_type(const Context &p_context, const Variant &p_val, const TypeReference &p_arg_type) {
+ if (p_arg_type.name == p_context.names_cache.variant_type) {
+ // Variant can take anything
+ return true;
+ }
+
+ switch (p_val.get_type()) {
+ case Variant::NIL:
+ return p_context.find_exposed_class(p_arg_type) ||
+ p_context.names_cache.is_nullable_type(p_arg_type.name);
+ case Variant::BOOL:
+ return p_arg_type.name == p_context.names_cache.bool_type;
+ case Variant::INT:
+ return p_arg_type.name == p_context.names_cache.int_type ||
+ p_arg_type.name == p_context.names_cache.float_type ||
+ p_arg_type.is_enum;
+ case Variant::FLOAT:
+ return p_arg_type.name == p_context.names_cache.float_type;
+ case Variant::STRING:
+ case Variant::STRING_NAME:
+ return p_arg_type.name == p_context.names_cache.string_type ||
+ p_arg_type.name == p_context.names_cache.string_name_type ||
+ p_arg_type.name == p_context.names_cache.node_path_type;
+ case Variant::NODE_PATH:
+ return p_arg_type.name == p_context.names_cache.node_path_type;
+ case Variant::TRANSFORM:
+ case Variant::TRANSFORM2D:
+ case Variant::BASIS:
+ case Variant::QUAT:
+ case Variant::PLANE:
+ case Variant::AABB:
+ case Variant::COLOR:
+ case Variant::VECTOR2:
+ case Variant::RECT2:
+ case Variant::VECTOR3:
+ case Variant::_RID:
+ case Variant::ARRAY:
+ case Variant::DICTIONARY:
+ case Variant::PACKED_BYTE_ARRAY:
+ case Variant::PACKED_INT32_ARRAY:
+ case Variant::PACKED_INT64_ARRAY:
+ case Variant::PACKED_FLOAT32_ARRAY:
+ case Variant::PACKED_FLOAT64_ARRAY:
+ case Variant::PACKED_STRING_ARRAY:
+ case Variant::PACKED_VECTOR2_ARRAY:
+ case Variant::PACKED_VECTOR3_ARRAY:
+ case Variant::PACKED_COLOR_ARRAY:
+ case Variant::CALLABLE:
+ case Variant::SIGNAL:
+ return p_arg_type.name == Variant::get_type_name(p_val.get_type());
+ case Variant::OBJECT:
+ return p_context.find_exposed_class(p_arg_type);
+ case Variant::VECTOR2I:
+ return p_arg_type.name == p_context.names_cache.vector2_type ||
+ p_arg_type.name == Variant::get_type_name(p_val.get_type());
+ case Variant::RECT2I:
+ return p_arg_type.name == p_context.names_cache.rect2_type ||
+ p_arg_type.name == Variant::get_type_name(p_val.get_type());
+ case Variant::VECTOR3I:
+ return p_arg_type.name == p_context.names_cache.vector3_type ||
+ p_arg_type.name == Variant::get_type_name(p_val.get_type());
+ default:
+ ERR_PRINT("Unexpected Variant type: " + itos(p_val.get_type()));
+ break;
+ }
+
+ return false;
+}
+
+TestResult validate_property(const Context &p_context, const ExposedClass &p_class, const PropertyData &p_prop) {
+ TEST_START();
+
+ const MethodData *setter = p_class.find_method_by_name(p_prop.setter);
+
+ // Search it in base classes too
+ const ExposedClass *top = &p_class;
+ while (!setter && top->base != StringName()) {
+ top = p_context.find_exposed_class(top->base);
+ TEST_FAIL_COND(!top, "Class not found '" + top->base + "'. Inherited by '" + top->name + "'.");
+ setter = top->find_method_by_name(p_prop.setter);
+ }
+
+ const MethodData *getter = p_class.find_method_by_name(p_prop.getter);
+
+ // Search it in base classes too
+ top = &p_class;
+ while (!getter && top->base != StringName()) {
+ top = p_context.find_exposed_class(top->base);
+ TEST_FAIL_COND(!top, "Class not found '" + top->base + "'. Inherited by '" + top->name + "'.");
+ getter = top->find_method_by_name(p_prop.getter);
+ }
+
+ TEST_FAIL_COND(!setter && !getter,
+ "Couldn't find neither the setter nor the getter for property: '" + p_class.name + "." + String(p_prop.name) + "'.");
+
+ if (setter) {
+ int setter_argc = p_prop.index != -1 ? 2 : 1;
+ TEST_FAIL_COND(setter->arguments.size() != setter_argc,
+ "Invalid property setter argument count: '" + p_class.name + "." + String(p_prop.name) + "'.");
+ }
+
+ if (getter) {
+ int getter_argc = p_prop.index != -1 ? 1 : 0;
+ TEST_FAIL_COND(getter->arguments.size() != getter_argc,
+ "Invalid property setter argument count: '" + p_class.name + "." + String(p_prop.name) + "'.");
+ }
+
+ if (getter && setter) {
+ const ArgumentData &setter_first_arg = setter->arguments.back()->get();
+ if (getter->return_type.name != setter_first_arg.type.name) {
+ // Special case for Node::set_name
+ bool whitelisted = getter->return_type.name == p_context.names_cache.string_name_type &&
+ setter_first_arg.type.name == p_context.names_cache.string_type;
+
+ TEST_FAIL_COND(!whitelisted,
+ "Return type from getter doesn't match first argument of setter, for property: '" + p_class.name + "." + String(p_prop.name) + "'.");
+ }
+ }
+
+ const TypeReference &prop_type_ref = getter ? getter->return_type : setter->arguments.back()->get().type;
+
+ const ExposedClass *prop_class = p_context.find_exposed_class(prop_type_ref);
+ if (prop_class) {
+ TEST_COND(prop_class->is_singleton,
+ "Property type is a singleton: '" + p_class.name + "." + String(p_prop.name) + "'.");
+ } else {
+ TEST_FAIL_COND(!p_context.has_type(prop_type_ref),
+ "Property type '" + prop_type_ref.name + "' not found: '" + p_class.name + "." + String(p_prop.name) + "'.");
+ }
+
+ if (getter) {
+ if (p_prop.index != -1) {
+ 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,
+ "Invalid type '" + idx_arg.type.name + "' for index argument of property getter: '" + p_class.name + "." + String(p_prop.name) + "'.");
+ }
+ }
+ }
+
+ if (setter) {
+ if (p_prop.index != -1) {
+ const ArgumentData &idx_arg = setter->arguments.front()->get();
+ 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,
+ "Invalid type '" + idx_arg.type.name + "' for index argument of property setter: '" + p_class.name + "." + String(p_prop.name) + "'.");
+ }
+ }
+ }
+
+ TEST_END();
+}
+
+TestResult validate_method(const Context &p_context, const ExposedClass &p_class, const MethodData &p_method) {
+ TEST_START();
+
+ const ExposedClass *return_class = p_context.find_exposed_class(p_method.return_type);
+ if (return_class) {
+ TEST_COND(return_class->is_singleton,
+ "Method return type is a singleton: '" + p_class.name + "." + p_method.name + "'.");
+ }
+
+ for (const List<ArgumentData>::Element *F = p_method.arguments.front(); F; F = F->next()) {
+ const ArgumentData &arg = F->get();
+
+ const ExposedClass *arg_class = p_context.find_exposed_class(arg.type);
+ if (arg_class) {
+ TEST_COND(arg_class->is_singleton,
+ "Argument type is a singleton: '" + arg.name + "' of method '" + p_class.name + "." + p_method.name + "'.");
+ } else {
+ TEST_FAIL_COND(!p_context.has_type(arg.type),
+ "Argument type '" + arg.type.name + "' not found: '" + arg.name + "' of method" + p_class.name + "." + p_method.name + "'.");
+ }
+
+ if (arg.has_defval) {
+ TEST_COND(!arg_default_value_is_assignable_to_type(p_context, arg.defval, arg.type),
+ "Invalid default value for parameter '" + arg.name + "' of method '" + p_class.name + "." + p_method.name + "'.");
+ }
+ }
+
+ TEST_END();
+}
+
+TestResult validate_signal(const Context &p_context, const ExposedClass &p_class, const SignalData &p_signal) {
+ TEST_START();
+
+ for (const List<ArgumentData>::Element *F = p_signal.arguments.front(); F; F = F->next()) {
+ const ArgumentData &arg = F->get();
+
+ const ExposedClass *arg_class = p_context.find_exposed_class(arg.type);
+ if (arg_class) {
+ TEST_COND(arg_class->is_singleton,
+ "Argument class is a singleton: '" + arg.name + "' of signal" + p_class.name + "." + p_signal.name + "'.");
+ } else {
+ TEST_FAIL_COND(!p_context.has_type(arg.type),
+ "Argument type '" + arg.type.name + "' not found: '" + arg.name + "' of signal" + p_class.name + "." + p_signal.name + "'.");
+ }
+ }
+
+ TEST_END();
+}
+
+TestResult validate_class(const Context &p_context, const ExposedClass &p_exposed_class) {
+ TEST_START();
+
+ bool is_derived_type = p_exposed_class.base != StringName();
+
+ if (!is_derived_type) {
+ // Asserts about the base Object class
+ TEST_FAIL_COND(p_exposed_class.name != p_context.names_cache.object_class,
+ "Class '" + p_exposed_class.name + "' has no base class.");
+ TEST_FAIL_COND(!p_exposed_class.is_instantiable,
+ "Object class is not instantiable.");
+ TEST_FAIL_COND(p_exposed_class.api_type != ClassDB::API_CORE,
+ "Object class is API is not API_CORE.");
+ TEST_FAIL_COND(p_exposed_class.is_singleton,
+ "Object class is registered as a singleton.");
+ }
+
+ TEST_FAIL_COND(p_exposed_class.is_singleton && p_exposed_class.base != p_context.names_cache.object_class,
+ "Singleton base class '" + String(p_exposed_class.base) + "' is not Object, for class '" + p_exposed_class.name + "'.");
+
+ TEST_FAIL_COND(is_derived_type && !p_context.exposed_classes.has(p_exposed_class.base),
+ "Base type '" + p_exposed_class.base.operator String() + "' does not exist, for class '" + p_exposed_class.name + "'.");
+
+ for (const List<PropertyData>::Element *F = p_exposed_class.properties.front(); F; F = F->next()) {
+ TEST_CHECK(validate_property(p_context, p_exposed_class, F->get()));
+ }
+
+ for (const List<MethodData>::Element *F = p_exposed_class.methods.front(); F; F = F->next()) {
+ TEST_CHECK(validate_method(p_context, p_exposed_class, F->get()));
+ }
+
+ for (const List<SignalData>::Element *F = p_exposed_class.signals_.front(); F; F = F->next()) {
+ TEST_CHECK(validate_signal(p_context, p_exposed_class, F->get()));
+ }
+
+ TEST_END();
+}
+
+TestResult add_exposed_classes(Context &r_context) {
+ TEST_START();
+
+ List<StringName> class_list;
+ ClassDB::get_class_list(&class_list);
+ class_list.sort_custom<StringName::AlphCompare>();
+
+ while (class_list.size()) {
+ StringName class_name = class_list.front()->get();
+
+ ClassDB::APIType api_type = ClassDB::get_api_type(class_name);
+
+ if (api_type == ClassDB::API_NONE) {
+ class_list.pop_front();
+ continue;
+ }
+
+ if (!ClassDB::is_class_exposed(class_name)) {
+ OS::get_singleton()->print("Ignoring class '%s' because it's not exposed\n", String(class_name).utf8().get_data());
+ class_list.pop_front();
+ continue;
+ }
+
+ if (!ClassDB::is_class_enabled(class_name)) {
+ OS::get_singleton()->print("Ignoring class '%s' because it's not enabled\n", String(class_name).utf8().get_data());
+ class_list.pop_front();
+ continue;
+ }
+
+ ClassDB::ClassInfo *class_info = ClassDB::classes.getptr(class_name);
+
+ ExposedClass exposed_class;
+ exposed_class.name = class_name;
+ exposed_class.api_type = api_type;
+ exposed_class.is_singleton = Engine::get_singleton()->has_singleton(class_name);
+ exposed_class.is_instantiable = class_info->creation_func && !exposed_class.is_singleton;
+ exposed_class.is_reference = ClassDB::is_parent_class(class_name, "Reference");
+ exposed_class.base = ClassDB::get_parent_class(class_name);
+
+ // Add properties
+
+ List<PropertyInfo> property_list;
+ ClassDB::get_property_list(class_name, &property_list, true);
+
+ Map<StringName, StringName> accessor_methods;
+
+ for (const List<PropertyInfo>::Element *E = property_list.front(); E; E = E->next()) {
+ const PropertyInfo &property = E->get();
+
+ if (property.usage & PROPERTY_USAGE_GROUP || property.usage & PROPERTY_USAGE_SUBGROUP || property.usage & PROPERTY_USAGE_CATEGORY) {
+ continue;
+ }
+
+ PropertyData prop;
+ prop.name = property.name;
+ prop.setter = ClassDB::get_property_setter(class_name, prop.name);
+ prop.getter = ClassDB::get_property_getter(class_name, prop.name);
+
+ if (prop.setter != StringName()) {
+ accessor_methods[prop.setter] = prop.name;
+ }
+ if (prop.getter != StringName()) {
+ accessor_methods[prop.getter] = prop.name;
+ }
+
+ bool valid = false;
+ prop.index = ClassDB::get_property_index(class_name, prop.name, &valid);
+ TEST_FAIL_COND(!valid, "Invalid property: '" + exposed_class.name + "." + String(prop.name) + "'.");
+
+ exposed_class.properties.push_back(prop);
+ }
+
+ // Add methods
+
+ List<MethodInfo> virtual_method_list;
+ ClassDB::get_virtual_methods(class_name, &virtual_method_list, true);
+
+ List<MethodInfo> method_list;
+ ClassDB::get_method_list(class_name, &method_list, true);
+ method_list.sort();
+
+ for (List<MethodInfo>::Element *E = method_list.front(); E; E = E->next()) {
+ const MethodInfo &method_info = E->get();
+
+ int argc = method_info.arguments.size();
+
+ if (method_info.name.empty()) {
+ continue;
+ }
+
+ MethodData method;
+ method.name = method_info.name;
+
+ if (method_info.flags & METHOD_FLAG_VIRTUAL) {
+ method.is_virtual = true;
+ }
+
+ PropertyInfo return_info = method_info.return_val;
+
+ MethodBind *m = method.is_virtual ? nullptr : ClassDB::get_method(class_name, method_info.name);
+
+ method.is_vararg = m && m->is_vararg();
+
+ if (!m && !method.is_virtual) {
+ TEST_FAIL_COND(!virtual_method_list.find(method_info),
+ "Missing MethodBind for non-virtual method: '" + exposed_class.name + "." + method.name + "'.");
+
+ // A virtual method without the virtual flag. This is a special case.
+
+ // The method Object.free is registered as a virtual method, but without the virtual flag.
+ // This is because this method is not supposed to be overridden, but called.
+ // We assume the return type is void.
+ method.return_type.name = r_context.names_cache.void_type;
+
+ // Actually, more methods like this may be added in the future, which could return
+ // something different. Let's put this check to notify us if that ever happens.
+ if (exposed_class.name != r_context.names_cache.object_class || String(method.name) != "free") {
+ WARN_PRINT("Notification: New unexpected virtual non-overridable method found."
+ " We only expected Object.free, but found '" +
+ exposed_class.name + "." + method.name + "'.");
+ }
+ } else if (return_info.type == Variant::INT && return_info.usage & PROPERTY_USAGE_CLASS_IS_ENUM) {
+ method.return_type.name = return_info.class_name;
+ method.return_type.is_enum = true;
+ } else if (return_info.class_name != StringName()) {
+ method.return_type.name = return_info.class_name;
+
+ bool bad_reference_hint = !method.is_virtual && return_info.hint != PROPERTY_HINT_RESOURCE_TYPE &&
+ ClassDB::is_parent_class(return_info.class_name, r_context.names_cache.reference_class);
+ TEST_COND(bad_reference_hint, String() + "Return type is reference but hint is not '" _STR(PROPERTY_HINT_RESOURCE_TYPE) "'." +
+ " Are you returning a reference type by pointer? Method: '" +
+ exposed_class.name + "." + method.name + "'.");
+ } else if (return_info.hint == PROPERTY_HINT_RESOURCE_TYPE) {
+ method.return_type.name = return_info.hint_string;
+ } else if (return_info.type == Variant::NIL && return_info.usage & PROPERTY_USAGE_NIL_IS_VARIANT) {
+ method.return_type.name = r_context.names_cache.variant_type;
+ } else if (return_info.type == Variant::NIL) {
+ method.return_type.name = r_context.names_cache.void_type;
+ } else {
+ // NOTE: We don't care about the size and sign of int and float in these tests
+ method.return_type.name = Variant::get_type_name(return_info.type);
+ }
+
+ for (int i = 0; i < argc; i++) {
+ PropertyInfo arg_info = method_info.arguments[i];
+
+ String orig_arg_name = arg_info.name;
+
+ ArgumentData arg;
+ arg.name = orig_arg_name;
+
+ if (arg_info.type == Variant::INT && arg_info.usage & PROPERTY_USAGE_CLASS_IS_ENUM) {
+ arg.type.name = arg_info.class_name;
+ arg.type.is_enum = true;
+ } else if (arg_info.class_name != StringName()) {
+ arg.type.name = arg_info.class_name;
+ } else if (arg_info.hint == PROPERTY_HINT_RESOURCE_TYPE) {
+ arg.type.name = arg_info.hint_string;
+ } else if (arg_info.type == Variant::NIL) {
+ arg.type.name = r_context.names_cache.variant_type;
+ } else {
+ // NOTE: We don't care about the size and sign of int and float in these tests
+ arg.type.name = Variant::get_type_name(arg_info.type);
+ }
+
+ if (m && m->has_default_argument(i)) {
+ arg.has_defval = true;
+ arg.defval = m->get_default_argument(i);
+ }
+
+ method.arguments.push_back(arg);
+ }
+
+ if (method.is_vararg) {
+ ArgumentData vararg;
+ vararg.type.name = r_context.names_cache.vararg_stub_type;
+ vararg.name = "@varargs@";
+ method.arguments.push_back(vararg);
+ }
+
+ TEST_COND(exposed_class.find_property_by_name(method.name),
+ "Method name conflicts with property: '" + String(class_name) + "." + String(method.name) + "'.");
+
+ // Classes starting with an underscore are ignored unless they're used as a property setter or getter
+ if (!method.is_virtual && String(method.name)[0] == '_') {
+ for (const List<PropertyData>::Element *F = exposed_class.properties.front(); F; F = F->next()) {
+ const PropertyData &prop = F->get();
+
+ if (prop.setter == method.name || prop.getter == method.name) {
+ exposed_class.methods.push_back(method);
+ break;
+ }
+ }
+ } else {
+ exposed_class.methods.push_back(method);
+ }
+ }
+
+ // Add signals
+
+ const HashMap<StringName, MethodInfo> &signal_map = class_info->signal_map;
+ const StringName *k = nullptr;
+
+ while ((k = signal_map.next(k))) {
+ SignalData signal;
+
+ const MethodInfo &method_info = signal_map.get(*k);
+
+ signal.name = method_info.name;
+
+ int argc = method_info.arguments.size();
+
+ for (int i = 0; i < argc; i++) {
+ PropertyInfo arg_info = method_info.arguments[i];
+
+ String orig_arg_name = arg_info.name;
+
+ ArgumentData arg;
+ arg.name = orig_arg_name;
+
+ if (arg_info.type == Variant::INT && arg_info.usage & PROPERTY_USAGE_CLASS_IS_ENUM) {
+ arg.type.name = arg_info.class_name;
+ arg.type.is_enum = true;
+ } else if (arg_info.class_name != StringName()) {
+ arg.type.name = arg_info.class_name;
+ } else if (arg_info.hint == PROPERTY_HINT_RESOURCE_TYPE) {
+ arg.type.name = arg_info.hint_string;
+ } else if (arg_info.type == Variant::NIL) {
+ arg.type.name = r_context.names_cache.variant_type;
+ } else {
+ // NOTE: We don't care about the size and sign of int and float in these tests
+ arg.type.name = Variant::get_type_name(arg_info.type);
+ }
+
+ signal.arguments.push_back(arg);
+ }
+
+ bool method_conflict = exposed_class.find_property_by_name(signal.name);
+
+ if (method_conflict || exposed_class.find_method_by_name(signal.name)) {
+ // TODO:
+ // ClassDB allows signal names that conflict with method or property names.
+ // However registering a signal with a conflicting name is still considered wrong.
+ // Unfortunately there are some existing cases that are yet to be fixed.
+ // Until those are fixed we will print a warning instead of failing the test.
+ WARN_PRINT("Signal name conflicts with " + String(method_conflict ? "method" : "property") +
+ ": '" + String(class_name) + "." + String(signal.name) + "'.");
+ }
+
+ exposed_class.signals_.push_back(signal);
+ }
+
+ // Add enums and constants
+
+ List<String> constants;
+ ClassDB::get_integer_constant_list(class_name, &constants, true);
+
+ const HashMap<StringName, List<StringName>> &enum_map = class_info->enum_map;
+ k = nullptr;
+
+ while ((k = enum_map.next(k))) {
+ EnumData enum_;
+ enum_.name = *k;
+
+ const List<StringName> &enum_constants = enum_map.get(*k);
+ for (const List<StringName>::Element *E = enum_constants.front(); E; E = E->next()) {
+ const StringName &constant_name = E->get();
+ int *value = class_info->constant_map.getptr(constant_name);
+ TEST_FAIL_COND(!value, "Missing enum constant value: '" +
+ String(class_name) + "." + String(enum_.name) + "." + String(constant_name) + "'.");
+ constants.erase(constant_name);
+
+ ConstantData constant;
+ constant.name = constant_name;
+ constant.value = *value;
+
+ enum_.constants.push_back(constant);
+ }
+
+ exposed_class.enums.push_back(enum_);
+
+ r_context.enum_types.push_back(String(class_name) + "." + String(*k));
+ }
+
+ for (const List<String>::Element *E = constants.front(); E; E = E->next()) {
+ const String &constant_name = E->get();
+ int *value = class_info->constant_map.getptr(StringName(E->get()));
+ TEST_FAIL_COND(!value, "Missing enum constant value: '" + String(class_name) + "." + String(constant_name) + "'.");
+
+ ConstantData constant;
+ constant.name = constant_name;
+ constant.value = *value;
+
+ exposed_class.constants.push_back(constant);
+ }
+
+ r_context.exposed_classes.insert(class_name, exposed_class);
+ class_list.pop_front();
+ }
+
+ TEST_END();
+}
+
+void add_builtin_types(Context &r_context) {
+ // NOTE: We don't care about the size and sign of int and float in these tests
+ for (int i = 0; i < Variant::VARIANT_MAX; i++) {
+ r_context.builtin_types.push_back(Variant::get_type_name(Variant::Type(i)));
+ }
+
+ r_context.builtin_types.push_back(_STR(Variant));
+ r_context.builtin_types.push_back(r_context.names_cache.vararg_stub_type);
+ r_context.builtin_types.push_back("void");
+}
+
+void add_global_enums(Context &r_context) {
+ int global_constants_count = GlobalConstants::get_global_constant_count();
+
+ if (global_constants_count > 0) {
+ for (int i = 0; i < global_constants_count; i++) {
+ StringName enum_name = GlobalConstants::get_global_constant_enum(i);
+
+ if (enum_name != StringName()) {
+ ConstantData constant;
+ constant.name = GlobalConstants::get_global_constant_name(i);
+ constant.value = GlobalConstants::get_global_constant_value(i);
+
+ EnumData enum_;
+ enum_.name = enum_name;
+ List<EnumData>::Element *enum_match = r_context.global_enums.find(enum_);
+ if (enum_match) {
+ enum_match->get().constants.push_back(constant);
+ } else {
+ enum_.constants.push_back(constant);
+ r_context.global_enums.push_back(enum_);
+ }
+ }
+ }
+
+ for (List<EnumData>::Element *E = r_context.global_enums.front(); E; E = E->next()) {
+ r_context.enum_types.push_back(E->get().name);
+ }
+ }
+
+ // HARDCODED
+ List<StringName> hardcoded_enums;
+ hardcoded_enums.push_back("Vector2.Axis");
+ hardcoded_enums.push_back("Vector2i.Axis");
+ hardcoded_enums.push_back("Vector3.Axis");
+ hardcoded_enums.push_back("Vector3i.Axis");
+ for (List<StringName>::Element *E = hardcoded_enums.front(); E; E = E->next()) {
+ // These enums are not generated and must be written manually (e.g.: Vector3.Axis)
+ // Here, we assume core types do not begin with underscore
+ r_context.enum_types.push_back(E->get());
+ }
+}
+
+TestResult run_class_db_tests() {
+ TEST_START();
+
+ Context context;
+
+ TEST_FAIL_CHECK(add_exposed_classes(context));
+ add_builtin_types(context);
+ add_global_enums(context);
+
+ const ExposedClass *object_class = context.find_exposed_class(context.names_cache.object_class);
+ TEST_FAIL_COND(!object_class, "Object class not found.");
+ TEST_FAIL_COND(object_class->base != StringName(),
+ "Object class derives from another class: '" + object_class->base + "'.");
+
+ for (ExposedClasses::Element E = context.exposed_classes.front(); E; E = E.next()) {
+ TEST_CHECK(validate_class(context, E.value()));
+ }
+
+ TEST_END();
+}
+
+MainLoop *test() {
+ TestResult pass = run_class_db_tests();
+
+ OS::get_singleton()->print("ClassDB tests: %s\n", pass == TestResult::PASS ? "PASS" : "FAILED");
+
+ if (pass == TestResult::FAILED) {
+ OS::get_singleton()->set_exit_code(pass == TestResult::PASS ? 0 : 1);
+ }
+
+ return nullptr;
+}
+
+} // namespace TestClassDB
diff --git a/tests/test_class_db.h b/tests/test_class_db.h
new file mode 100644
index 0000000000..1a31cfb01b
--- /dev/null
+++ b/tests/test_class_db.h
@@ -0,0 +1,42 @@
+/*************************************************************************/
+/* test_class_db.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 GODOT_TEST_CLASS_DB_H
+#define GODOT_TEST_CLASS_DB_H
+
+#include "core/os/main_loop.h"
+
+namespace TestClassDB {
+
+MainLoop *test();
+
+}
+
+#endif //GODOT_TEST_CLASS_DB_H
diff --git a/tests/test_gdscript.cpp b/tests/test_gdscript.cpp
new file mode 100644
index 0000000000..a50311972f
--- /dev/null
+++ b/tests/test_gdscript.cpp
@@ -0,0 +1,187 @@
+/*************************************************************************/
+/* test_gdscript.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_gdscript.h"
+
+#include "core/os/file_access.h"
+#include "core/os/main_loop.h"
+#include "core/os/os.h"
+#include "core/string_builder.h"
+
+#include "modules/modules_enabled.gen.h"
+#ifdef MODULE_GDSCRIPT_ENABLED
+
+#include "modules/gdscript/gdscript_parser.h"
+#include "modules/gdscript/gdscript_tokenizer.h"
+
+#ifdef TOOLS_ENABLED
+#include "editor/editor_settings.h"
+#endif
+
+namespace TestGDScript {
+
+static void test_tokenizer(const String &p_code, const Vector<String> &p_lines) {
+ GDScriptTokenizer tokenizer;
+ tokenizer.set_source_code(p_code);
+
+ int tab_size = 4;
+#ifdef TOOLS_ENABLED
+ if (EditorSettings::get_singleton()) {
+ tab_size = EditorSettings::get_singleton()->get_setting("text_editor/indent/size");
+ }
+#endif // TOOLS_ENABLED
+ String tab = String(" ").repeat(tab_size);
+
+ GDScriptTokenizer::Token current = tokenizer.scan();
+ while (current.type != GDScriptTokenizer::Token::TK_EOF) {
+ StringBuilder token;
+ token += " --> "; // Padding for line number.
+
+ for (int l = current.start_line; l <= current.end_line; l++) {
+ print_line(vformat("%04d %s", l, p_lines[l - 1]).replace("\t", tab));
+ }
+
+ {
+ // Print carets to point at the token.
+ StringBuilder pointer;
+ pointer += " "; // Padding for line number.
+ int rightmost_column = current.rightmost_column;
+ if (current.end_line > current.start_line) {
+ rightmost_column--; // Don't point to the newline as a column.
+ }
+ for (int col = 1; col < rightmost_column; col++) {
+ if (col < current.leftmost_column) {
+ pointer += " ";
+ } else {
+ pointer += "^";
+ }
+ }
+ print_line(pointer.as_string());
+ }
+
+ token += current.get_name();
+
+ if (current.type == GDScriptTokenizer::Token::ERROR || current.type == GDScriptTokenizer::Token::LITERAL || current.type == GDScriptTokenizer::Token::IDENTIFIER || current.type == GDScriptTokenizer::Token::ANNOTATION) {
+ token += "(";
+ token += Variant::get_type_name(current.literal.get_type());
+ token += ") ";
+ token += current.literal;
+ }
+
+ print_line(token.as_string());
+
+ print_line("-------------------------------------------------------");
+
+ current = tokenizer.scan();
+ }
+
+ print_line(current.get_name()); // Should be EOF
+}
+
+static void test_parser(const String &p_code, const String &p_script_path, const Vector<String> &p_lines) {
+ GDScriptParser parser;
+ Error err = parser.parse(p_code, p_script_path, false);
+
+ if (err != OK) {
+ const List<GDScriptParser::ParserError> &errors = parser.get_errors();
+ for (const List<GDScriptParser::ParserError>::Element *E = errors.front(); E != nullptr; E = E->next()) {
+ const GDScriptParser::ParserError &error = E->get();
+ print_line(vformat("%02d:%02d: %s", error.line, error.column, error.message));
+ }
+ }
+
+ GDScriptParser::TreePrinter printer;
+
+ printer.print_tree(parser);
+}
+
+MainLoop *test(TestType p_type) {
+ List<String> cmdlargs = OS::get_singleton()->get_cmdline_args();
+
+ if (cmdlargs.empty()) {
+ return nullptr;
+ }
+
+ String test = cmdlargs.back()->get();
+ if (!test.ends_with(".gd")) {
+ print_line("This test expects a path to a GDScript file as its last parameter. Got: " + test);
+ return nullptr;
+ }
+
+ FileAccessRef fa = FileAccess::open(test, FileAccess::READ);
+ ERR_FAIL_COND_V_MSG(!fa, nullptr, "Could not open file: " + test);
+
+ Vector<uint8_t> buf;
+ int flen = fa->get_len();
+ buf.resize(fa->get_len() + 1);
+ fa->get_buffer(buf.ptrw(), flen);
+ buf.write[flen] = 0;
+
+ String code;
+ code.parse_utf8((const char *)&buf[0]);
+
+ Vector<String> lines;
+ int last = 0;
+ for (int i = 0; i <= code.length(); i++) {
+ if (code[i] == '\n' || code[i] == 0) {
+ lines.push_back(code.substr(last, i - last));
+ last = i + 1;
+ }
+ }
+
+ switch (p_type) {
+ case TEST_TOKENIZER:
+ test_tokenizer(code, lines);
+ break;
+ case TEST_PARSER:
+ test_parser(code, test, lines);
+ break;
+ case TEST_COMPILER:
+ case TEST_BYTECODE:
+ print_line("Not implemented.");
+ }
+
+ return nullptr;
+}
+
+} // namespace TestGDScript
+
+#else
+
+namespace TestGDScript {
+
+MainLoop *test(TestType p_type) {
+ ERR_PRINT("The GDScript module is disabled, therefore GDScript tests cannot be used.");
+ return nullptr;
+}
+
+} // namespace TestGDScript
+
+#endif
diff --git a/tests/test_gdscript.h b/tests/test_gdscript.h
new file mode 100644
index 0000000000..6595da1430
--- /dev/null
+++ b/tests/test_gdscript.h
@@ -0,0 +1,48 @@
+/*************************************************************************/
+/* test_gdscript.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_GDSCRIPT_H
+#define TEST_GDSCRIPT_H
+
+#include "core/os/main_loop.h"
+
+namespace TestGDScript {
+
+enum TestType {
+ TEST_TOKENIZER,
+ TEST_PARSER,
+ TEST_COMPILER,
+ TEST_BYTECODE,
+};
+
+MainLoop *test(TestType p_type);
+} // namespace TestGDScript
+
+#endif // TEST_GDSCRIPT_H
diff --git a/tests/test_gui.cpp b/tests/test_gui.cpp
new file mode 100644
index 0000000000..d46a13d2c0
--- /dev/null
+++ b/tests/test_gui.cpp
@@ -0,0 +1,271 @@
+/*************************************************************************/
+/* test_gui.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. */
+/*************************************************************************/
+
+#ifndef _3D_DISABLED
+
+#include "test_gui.h"
+
+#include "core/io/image_loader.h"
+#include "core/os/os.h"
+#include "core/print_string.h"
+#include "scene/2d/sprite_2d.h"
+#include "scene/gui/button.h"
+#include "scene/gui/control.h"
+#include "scene/gui/label.h"
+#include "scene/gui/line_edit.h"
+#include "scene/gui/menu_button.h"
+#include "scene/gui/option_button.h"
+#include "scene/gui/panel.h"
+#include "scene/gui/popup_menu.h"
+#include "scene/gui/progress_bar.h"
+#include "scene/gui/rich_text_label.h"
+#include "scene/gui/scroll_bar.h"
+#include "scene/gui/spin_box.h"
+#include "scene/gui/tab_container.h"
+#include "scene/gui/texture_rect.h"
+#include "scene/gui/tree.h"
+#include "scene/main/scene_tree.h"
+
+#include "scene/3d/camera_3d.h"
+#include "scene/main/window.h"
+
+namespace TestGUI {
+
+class TestMainLoop : public SceneTree {
+public:
+ virtual void request_quit() {
+ quit();
+ }
+ virtual void init() {
+ SceneTree::init();
+
+ Panel *frame = memnew(Panel);
+ frame->set_anchor(MARGIN_RIGHT, Control::ANCHOR_END);
+ frame->set_anchor(MARGIN_BOTTOM, Control::ANCHOR_END);
+ frame->set_end(Point2(0, 0));
+
+ Ref<Theme> t = memnew(Theme);
+ frame->set_theme(t);
+
+ get_root()->add_child(frame);
+
+ Label *label = memnew(Label);
+
+ label->set_position(Point2(80, 90));
+ label->set_size(Point2(170, 80));
+ label->set_align(Label::ALIGN_FILL);
+ label->set_text("There was once upon a time a beautiful unicorn that loved to play with little girls...");
+
+ frame->add_child(label);
+
+ Button *button = memnew(Button);
+
+ button->set_position(Point2(20, 20));
+ button->set_size(Point2(1, 1));
+ button->set_text("This is a biggie button");
+
+ frame->add_child(button);
+
+ Tree *tree = memnew(Tree);
+ tree->set_columns(2);
+
+ tree->set_position(Point2(230, 210));
+ tree->set_size(Point2(150, 250));
+
+ TreeItem *item = tree->create_item();
+ item->set_editable(0, true);
+ item->set_text(0, "root");
+ item = tree->create_item(tree->get_root());
+ item->set_cell_mode(0, TreeItem::CELL_MODE_CHECK);
+ item->set_editable(0, true);
+ item->set_text(0, "check");
+ item->set_cell_mode(1, TreeItem::CELL_MODE_CHECK);
+ item->set_editable(1, true);
+ item->set_text(1, "check2");
+ item = tree->create_item(tree->get_root());
+ item->set_cell_mode(0, TreeItem::CELL_MODE_RANGE);
+ item->set_editable(0, true);
+ item->set_range_config(0, 0, 20, 0.1);
+ item->set_range(0, 2);
+ item->add_button(0, Theme::get_default()->get_icon("folder", "FileDialog"));
+ item->set_cell_mode(1, TreeItem::CELL_MODE_RANGE);
+ item->set_editable(1, true);
+ item->set_range_config(1, 0, 20, 0.1);
+ item->set_range(1, 3);
+
+ item = tree->create_item(tree->get_root());
+ item->set_cell_mode(0, TreeItem::CELL_MODE_RANGE);
+ item->set_editable(0, true);
+ item->set_text(0, "Have,Many,Several,Options!");
+ item->set_range(0, 2);
+
+ item = tree->create_item(item);
+ item->set_editable(0, true);
+ item->set_text(0, "Gershwin!");
+
+ frame->add_child(tree);
+
+ LineEdit *line_edit = memnew(LineEdit);
+
+ line_edit->set_position(Point2(30, 190));
+ line_edit->set_size(Point2(180, 1));
+
+ frame->add_child(line_edit);
+
+ HScrollBar *hscroll = memnew(HScrollBar);
+
+ hscroll->set_position(Point2(30, 290));
+ hscroll->set_size(Point2(180, 1));
+ hscroll->set_max(10);
+ hscroll->set_page(4);
+
+ frame->add_child(hscroll);
+
+ SpinBox *spin = memnew(SpinBox);
+
+ spin->set_position(Point2(30, 260));
+ spin->set_size(Point2(120, 1));
+
+ frame->add_child(spin);
+ hscroll->share(spin);
+
+ ProgressBar *progress = memnew(ProgressBar);
+
+ progress->set_position(Point2(30, 330));
+ progress->set_size(Point2(120, 1));
+
+ frame->add_child(progress);
+ hscroll->share(progress);
+
+ MenuButton *menu_button = memnew(MenuButton);
+
+ menu_button->set_text("I'm a menu!");
+ menu_button->set_position(Point2(30, 380));
+ menu_button->set_size(Point2(1, 1));
+
+ frame->add_child(menu_button);
+
+ PopupMenu *popup = menu_button->get_popup();
+
+ popup->add_item("Hello, testing");
+ popup->add_item("My Dearest");
+ popup->add_separator();
+ popup->add_item("Popup");
+ popup->add_check_item("Check Popup");
+ popup->set_item_checked(4, true);
+ popup->add_separator();
+ popup->add_radio_check_item("Option A");
+ popup->set_item_checked(6, true);
+ popup->add_radio_check_item("Option B");
+
+ OptionButton *options = memnew(OptionButton);
+
+ options->add_item("Hello, testing");
+ options->add_item("My Dearest");
+
+ options->set_position(Point2(230, 180));
+ options->set_size(Point2(1, 1));
+
+ frame->add_child(options);
+
+ RichTextLabel *richtext = memnew(RichTextLabel);
+
+ richtext->set_position(Point2(600, 210));
+ richtext->set_size(Point2(180, 250));
+ richtext->set_anchor_and_margin(MARGIN_RIGHT, Control::ANCHOR_END, -20);
+
+ frame->add_child(richtext);
+
+ richtext->add_text("Hello, My Friends!\n\nWelcome to the amazing world of ");
+
+ richtext->add_newline();
+ richtext->add_newline();
+
+ richtext->push_color(Color(1, 0.5, 0.5));
+ richtext->add_text("leprechauns");
+ richtext->pop();
+
+ richtext->add_text(" and ");
+ richtext->push_color(Color(0, 1.0, 0.5));
+ richtext->add_text("faeries.\n");
+ richtext->pop();
+ richtext->add_text("In this new episode, we will attempt to ");
+ richtext->push_font(richtext->get_theme_font("mono_font", "Fonts"));
+ richtext->push_color(Color(0.7, 0.5, 1.0));
+ richtext->add_text("deliver something nice");
+ richtext->pop();
+ richtext->pop();
+ richtext->add_text(" to all the viewers! Unfortunately, I need to ");
+ richtext->push_underline();
+ richtext->add_text("keep writing a lot of text");
+ richtext->pop();
+ richtext->add_text(" so the label control overflows and the scrollbar appears.\n");
+ richtext->push_meta("http://www.scrollingcapabilities.xz");
+ richtext->add_text("This allows to test for the scrolling capabilities ");
+ richtext->pop();
+ richtext->add_text("of the rich text label for huge text (not like this text will really be huge but, you know).\nAs long as it is so long that it will work nicely for a test/demo, then it's welcomed in my book...\nChanging subject, the day is cloudy today and I'm wondering if I'll get che chance to travel somewhere nice. Sometimes, watching the clouds from satellite images may give a nice insight about how pressure zones in our planet work, although it also makes it pretty obvious to see why most weather forecasts get it wrong so often.\nClouds are so difficult to predict!\nBut it's pretty cool how our civilization has adapted to having water falling from the sky each time it rains...");
+
+ TabContainer *tabc = memnew(TabContainer);
+
+ Control *ctl = memnew(Control);
+ ctl->set_name("tab 1");
+ tabc->add_child(ctl);
+
+ ctl = memnew(Control);
+ ctl->set_name("tab 2");
+ tabc->add_child(ctl);
+ label = memnew(Label);
+ label->set_text("Some Label");
+ label->set_position(Point2(20, 20));
+ ctl->add_child(label);
+
+ ctl = memnew(Control);
+ ctl->set_name("tab 3");
+ button = memnew(Button);
+ button->set_text("Some Button");
+ button->set_position(Point2(30, 50));
+ ctl->add_child(button);
+
+ tabc->add_child(ctl);
+
+ frame->add_child(tabc);
+
+ tabc->set_position(Point2(400, 210));
+ tabc->set_size(Point2(180, 250));
+ }
+};
+
+MainLoop *test() {
+ return memnew(TestMainLoop);
+}
+
+} // namespace TestGUI
+
+#endif
diff --git a/tests/test_gui.h b/tests/test_gui.h
new file mode 100644
index 0000000000..5a23179eee
--- /dev/null
+++ b/tests/test_gui.h
@@ -0,0 +1,41 @@
+/*************************************************************************/
+/* test_gui.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_GUI_H
+#define TEST_GUI_H
+
+#include "core/os/main_loop.h"
+
+namespace TestGUI {
+
+MainLoop *test();
+}
+
+#endif
diff --git a/tests/test_main.cpp b/tests/test_main.cpp
new file mode 100644
index 0000000000..91eff28f86
--- /dev/null
+++ b/tests/test_main.cpp
@@ -0,0 +1,127 @@
+/*************************************************************************/
+/* test_main.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_main.h"
+
+#include "core/list.h"
+
+#ifdef DEBUG_ENABLED
+
+#include "test_astar.h"
+#include "test_basis.h"
+#include "test_class_db.h"
+#include "test_gdscript.h"
+#include "test_gui.h"
+#include "test_math.h"
+#include "test_oa_hash_map.h"
+#include "test_ordered_hash_map.h"
+#include "test_physics_2d.h"
+#include "test_physics_3d.h"
+#include "test_render.h"
+#include "test_shader_lang.h"
+#include "test_string.h"
+#include "test_validate_testing.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;
+}
+
+int test_main(int argc, char *argv[]) {
+ // doctest runner for when legacy unit tests are no found
+ doctest::Context test_context;
+ List<String> valid_arguments;
+
+ // 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) {
+ valid_arguments.push_back(String(argv[x]));
+ argument_count++;
+ }
+ }
+
+ // 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
+ args[x] = new char[strlen(str) + 1];
+ // copy this into memory
+ std::memcpy(args[x], str, strlen(str) + 1);
+ }
+
+ test_context.applyCommandLine(valid_arguments.size(), args);
+
+ 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;
+}
+
+int test_main(int argc, char *argv[]) {
+ return 0;
+}
+
+#endif
diff --git a/tests/test_main.h b/tests/test_main.h
new file mode 100644
index 0000000000..8273b74eac
--- /dev/null
+++ b/tests/test_main.h
@@ -0,0 +1,41 @@
+/*************************************************************************/
+/* test_main.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_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_math.cpp b/tests/test_math.cpp
new file mode 100644
index 0000000000..5f84bad4e9
--- /dev/null
+++ b/tests/test_math.cpp
@@ -0,0 +1,703 @@
+/*************************************************************************/
+/* test_math.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_math.h"
+
+#include "core/math/basis.h"
+#include "core/math/camera_matrix.h"
+#include "core/math/delaunay_3d.h"
+#include "core/math/geometry_2d.h"
+#include "core/math/math_funcs.h"
+#include "core/math/transform.h"
+#include "core/method_ptrcall.h"
+#include "core/os/file_access.h"
+#include "core/os/keyboard.h"
+#include "core/os/os.h"
+#include "core/print_string.h"
+#include "core/ustring.h"
+#include "core/variant.h"
+#include "core/vmap.h"
+#include "scene/main/node.h"
+#include "scene/resources/texture.h"
+#include "servers/rendering/shader_language.h"
+
+namespace TestMath {
+
+class GetClassAndNamespace {
+ String code;
+ int idx;
+ int line;
+ String error_str;
+ bool error;
+ Variant value;
+
+ String class_name;
+
+ enum Token {
+ TK_BRACKET_OPEN,
+ TK_BRACKET_CLOSE,
+ TK_CURLY_BRACKET_OPEN,
+ TK_CURLY_BRACKET_CLOSE,
+ TK_PERIOD,
+ TK_COLON,
+ TK_COMMA,
+ TK_SYMBOL,
+ TK_IDENTIFIER,
+ TK_STRING,
+ TK_NUMBER,
+ TK_EOF,
+ TK_ERROR
+ };
+
+ Token get_token() {
+ while (true) {
+ switch (code[idx]) {
+ case '\n': {
+ line++;
+ idx++;
+ break;
+ };
+ case 0: {
+ return TK_EOF;
+
+ } break;
+ case '{': {
+ idx++;
+ return TK_CURLY_BRACKET_OPEN;
+ };
+ case '}': {
+ idx++;
+ return TK_CURLY_BRACKET_CLOSE;
+ };
+ case '[': {
+ idx++;
+ return TK_BRACKET_OPEN;
+ };
+ case ']': {
+ idx++;
+ return TK_BRACKET_CLOSE;
+ };
+ case ':': {
+ idx++;
+ return TK_COLON;
+ };
+ case ',': {
+ idx++;
+ return TK_COMMA;
+ };
+ case '.': {
+ idx++;
+ return TK_PERIOD;
+ };
+ case '#': {
+ //compiler directive
+ while (code[idx] != '\n' && code[idx] != 0) {
+ idx++;
+ }
+ continue;
+ } break;
+ case '/': {
+ switch (code[idx + 1]) {
+ case '*': { // block comment
+
+ idx += 2;
+ while (true) {
+ if (code[idx] == 0) {
+ error_str = "Unterminated comment";
+ error = true;
+ return TK_ERROR;
+ } else if (code[idx] == '*' && code[idx + 1] == '/') {
+ idx += 2;
+ break;
+ } else if (code[idx] == '\n') {
+ line++;
+ }
+
+ idx++;
+ }
+
+ } break;
+ case '/': { // line comment skip
+
+ while (code[idx] != '\n' && code[idx] != 0) {
+ idx++;
+ }
+
+ } break;
+ default: {
+ value = "/";
+ idx++;
+ return TK_SYMBOL;
+ }
+ }
+
+ continue; // a comment
+ } break;
+ case '\'':
+ case '"': {
+ CharType begin_str = code[idx];
+ idx++;
+ String tk_string = String();
+ while (true) {
+ if (code[idx] == 0) {
+ error_str = "Unterminated String";
+ error = true;
+ return TK_ERROR;
+ } else if (code[idx] == begin_str) {
+ idx++;
+ break;
+ } else if (code[idx] == '\\') {
+ //escaped characters...
+ idx++;
+ CharType next = code[idx];
+ if (next == 0) {
+ error_str = "Unterminated String";
+ error = true;
+ return TK_ERROR;
+ }
+ CharType res = 0;
+
+ switch (next) {
+ case 'b':
+ res = 8;
+ break;
+ case 't':
+ res = 9;
+ break;
+ case 'n':
+ res = 10;
+ break;
+ case 'f':
+ res = 12;
+ break;
+ case 'r':
+ res = 13;
+ break;
+ case '\"':
+ res = '\"';
+ break;
+ case '\\':
+ res = '\\';
+ break;
+ default: {
+ res = next;
+ } break;
+ }
+
+ tk_string += res;
+
+ } else {
+ if (code[idx] == '\n') {
+ line++;
+ }
+ tk_string += code[idx];
+ }
+ idx++;
+ }
+
+ value = tk_string;
+
+ return TK_STRING;
+
+ } break;
+ default: {
+ if (code[idx] <= 32) {
+ idx++;
+ break;
+ }
+
+ if ((code[idx] >= 33 && code[idx] <= 47) || (code[idx] >= 58 && code[idx] <= 64) || (code[idx] >= 91 && code[idx] <= 96) || (code[idx] >= 123 && code[idx] <= 127)) {
+ value = String::chr(code[idx]);
+ idx++;
+ return TK_SYMBOL;
+ }
+
+ if (code[idx] == '-' || (code[idx] >= '0' && code[idx] <= '9')) {
+ //a number
+ const CharType *rptr;
+ double number = String::to_double(&code[idx], &rptr);
+ idx += (rptr - &code[idx]);
+ value = number;
+ return TK_NUMBER;
+
+ } else if ((code[idx] >= 'A' && code[idx] <= 'Z') || (code[idx] >= 'a' && code[idx] <= 'z') || code[idx] > 127) {
+ String id;
+
+ while ((code[idx] >= 'A' && code[idx] <= 'Z') || (code[idx] >= 'a' && code[idx] <= 'z') || code[idx] > 127) {
+ id += code[idx];
+ idx++;
+ }
+
+ value = id;
+ return TK_IDENTIFIER;
+ } else {
+ error_str = "Unexpected character.";
+ error = true;
+ return TK_ERROR;
+ }
+ }
+ }
+ }
+ }
+
+public:
+ Error parse(const String &p_code, const String &p_known_class_name = String()) {
+ code = p_code;
+ idx = 0;
+ line = 0;
+ error_str = String();
+ error = false;
+ value = Variant();
+ class_name = String();
+
+ bool use_next_class = false;
+ Token tk = get_token();
+
+ Map<int, String> namespace_stack;
+ int curly_stack = 0;
+
+ while (!error || tk != TK_EOF) {
+ if (tk == TK_BRACKET_OPEN) {
+ tk = get_token();
+ if (tk == TK_IDENTIFIER && String(value) == "ScriptClass") {
+ if (get_token() == TK_BRACKET_CLOSE) {
+ use_next_class = true;
+ }
+ }
+ } else if (tk == TK_IDENTIFIER && String(value) == "class") {
+ tk = get_token();
+ if (tk == TK_IDENTIFIER) {
+ String name = value;
+ if (use_next_class || p_known_class_name == name) {
+ for (Map<int, String>::Element *E = namespace_stack.front(); E; E = E->next()) {
+ class_name += E->get() + ".";
+ }
+ class_name += String(value);
+ break;
+ }
+ }
+
+ } else if (tk == TK_IDENTIFIER && String(value) == "namespace") {
+ String name;
+ int at_level = curly_stack;
+ while (true) {
+ tk = get_token();
+ if (tk == TK_IDENTIFIER) {
+ name += String(value);
+ }
+
+ tk = get_token();
+ if (tk == TK_PERIOD) {
+ name += ".";
+ } else if (tk == TK_CURLY_BRACKET_OPEN) {
+ curly_stack++;
+ break;
+ } else {
+ break; //whathever else
+ }
+ }
+
+ if (name != String()) {
+ namespace_stack[at_level] = name;
+ }
+
+ } else if (tk == TK_CURLY_BRACKET_OPEN) {
+ curly_stack++;
+ } else if (tk == TK_CURLY_BRACKET_CLOSE) {
+ curly_stack--;
+ if (namespace_stack.has(curly_stack)) {
+ namespace_stack.erase(curly_stack);
+ }
+ }
+
+ tk = get_token();
+ }
+
+ if (error) {
+ return ERR_PARSE_ERROR;
+ }
+
+ return OK;
+ }
+
+ String get_error() {
+ return error_str;
+ }
+
+ String get_class() {
+ return class_name;
+ }
+};
+
+void test_vec(Plane p_vec) {
+ CameraMatrix cm;
+ cm.set_perspective(45, 1, 0, 100);
+ Plane v0 = cm.xform4(p_vec);
+
+ print_line("out: " + v0);
+ v0.normal.z = (v0.d / 100.0 * 2.0 - 1.0) * v0.d;
+ print_line("out_F: " + v0);
+}
+
+uint32_t ihash(uint32_t a) {
+ a = (a + 0x7ed55d16) + (a << 12);
+ a = (a ^ 0xc761c23c) ^ (a >> 19);
+ a = (a + 0x165667b1) + (a << 5);
+ a = (a + 0xd3a2646c) ^ (a << 9);
+ a = (a + 0xfd7046c5) + (a << 3);
+ a = (a ^ 0xb55a4f09) ^ (a >> 16);
+ return a;
+}
+
+uint32_t ihash2(uint32_t a) {
+ a = (a ^ 61) ^ (a >> 16);
+ a = a + (a << 3);
+ a = a ^ (a >> 4);
+ a = a * 0x27d4eb2d;
+ a = a ^ (a >> 15);
+ return a;
+}
+
+uint32_t ihash3(uint32_t a) {
+ a = (a + 0x479ab41d) + (a << 8);
+ a = (a ^ 0xe4aa10ce) ^ (a >> 5);
+ a = (a + 0x9942f0a6) - (a << 14);
+ a = (a ^ 0x5aedd67d) ^ (a >> 3);
+ a = (a + 0x17bea992) + (a << 7);
+ return a;
+}
+
+MainLoop *test() {
+ {
+ Vector<Vector3> points;
+ points.push_back(Vector3(0, 0, 0));
+ points.push_back(Vector3(0, 0, 1));
+ points.push_back(Vector3(0, 1, 0));
+ points.push_back(Vector3(0, 1, 1));
+ points.push_back(Vector3(1, 1, 0));
+ points.push_back(Vector3(1, 0, 0));
+ points.push_back(Vector3(1, 0, 1));
+ points.push_back(Vector3(1, 1, 1));
+
+ for (int i = 0; i < 800; i++) {
+ points.push_back(Vector3(Math::randf() * 2.0 - 1.0, Math::randf() * 2.0 - 1.0, Math::randf() * 2.0 - 1.0) * Vector3(25, 30, 33));
+ }
+
+ Vector<Delaunay3D::OutputSimplex> os = Delaunay3D::tetrahedralize(points);
+ print_line("simplices in the end: " + itos(os.size()));
+ for (int i = 0; i < os.size(); i++) {
+ print_line("Simplex " + itos(i) + ": ");
+ print_line(points[os[i].points[0]]);
+ print_line(points[os[i].points[1]]);
+ print_line(points[os[i].points[2]]);
+ print_line(points[os[i].points[3]]);
+ }
+
+ {
+ FileAccessRef f = FileAccess::open("res://bsp.obj", FileAccess::WRITE);
+ for (int i = 0; i < os.size(); i++) {
+ f->store_line("o Simplex" + itos(i));
+ for (int j = 0; j < 4; j++) {
+ f->store_line(vformat("v %f %f %f", points[os[i].points[j]].x, points[os[i].points[j]].y, points[os[i].points[j]].z));
+ }
+ static const int face_order[4][3] = {
+ { 1, 2, 3 },
+ { 1, 3, 4 },
+ { 1, 2, 4 },
+ { 2, 3, 4 }
+ };
+
+ for (int j = 0; j < 4; j++) {
+ f->store_line(vformat("f %d %d %d", 4 * i + face_order[j][0], 4 * i + face_order[j][1], 4 * i + face_order[j][2]));
+ }
+ }
+ f->close();
+ }
+
+ return nullptr;
+ }
+
+ {
+ float r = 1;
+ float g = 0.5;
+ float b = 0.1;
+
+ const float pow2to9 = 512.0f;
+ const float B = 15.0f;
+ const float N = 9.0f;
+
+ float sharedexp = 65408.000f;
+
+ float cRed = MAX(0.0f, MIN(sharedexp, r));
+ float cGreen = MAX(0.0f, MIN(sharedexp, g));
+ float cBlue = MAX(0.0f, MIN(sharedexp, b));
+
+ float cMax = MAX(cRed, MAX(cGreen, cBlue));
+
+ float expp = MAX(-B - 1.0f, floor(Math::log(cMax) / Math_LN2)) + 1.0f + B;
+
+ float sMax = (float)floor((cMax / Math::pow(2.0f, expp - B - N)) + 0.5f);
+
+ float exps = expp + 1.0f;
+
+ if (0.0 <= sMax && sMax < pow2to9) {
+ exps = expp;
+ }
+
+ float sRed = Math::floor((cRed / pow(2.0f, exps - B - N)) + 0.5f);
+ float sGreen = Math::floor((cGreen / pow(2.0f, exps - B - N)) + 0.5f);
+ float sBlue = Math::floor((cBlue / pow(2.0f, exps - B - N)) + 0.5f);
+
+ print_line("R: " + rtos(sRed) + " G: " + rtos(sGreen) + " B: " + rtos(sBlue) + " EXP: " + rtos(exps));
+
+ uint32_t rgbe = (Math::fast_ftoi(sRed) & 0x1FF) | ((Math::fast_ftoi(sGreen) & 0x1FF) << 9) | ((Math::fast_ftoi(sBlue) & 0x1FF) << 18) | ((Math::fast_ftoi(exps) & 0x1F) << 27);
+
+ float rb = rgbe & 0x1ff;
+ float gb = (rgbe >> 9) & 0x1ff;
+ float bb = (rgbe >> 18) & 0x1ff;
+ float eb = (rgbe >> 27);
+ float mb = Math::pow(2.0, eb - 15.0 - 9.0);
+ float rd = rb * mb;
+ float gd = gb * mb;
+ float bd = bb * mb;
+
+ print_line("RGBE: " + Color(rd, gd, bd));
+ }
+
+ Vector<int> ints;
+ ints.resize(20);
+
+ {
+ int *w;
+ w = ints.ptrw();
+ for (int i = 0; i < ints.size(); i++) {
+ w[i] = i;
+ }
+ }
+
+ Vector<int> posho = ints;
+
+ {
+ const int *r = posho.ptr();
+ for (int i = 0; i < posho.size(); i++) {
+ print_line(itos(i) + " : " + itos(r[i]));
+ }
+ }
+
+ List<String> cmdlargs = OS::get_singleton()->get_cmdline_args();
+
+ if (cmdlargs.empty()) {
+ //try editor!
+ return nullptr;
+ }
+
+ String test = cmdlargs.back()->get();
+ if (test == "math") {
+ // Not a file name but the test name, abort.
+ // FIXME: This test is ugly as heck, needs fixing :)
+ return nullptr;
+ }
+
+ FileAccess *fa = FileAccess::open(test, FileAccess::READ);
+ ERR_FAIL_COND_V_MSG(!fa, nullptr, "Could not open file: " + test);
+
+ Vector<uint8_t> buf;
+ int flen = fa->get_len();
+ buf.resize(fa->get_len() + 1);
+ fa->get_buffer(buf.ptrw(), flen);
+ buf.write[flen] = 0;
+
+ String code;
+ code.parse_utf8((const char *)&buf[0]);
+
+ GetClassAndNamespace getclass;
+ if (getclass.parse(code)) {
+ print_line("Parse error: " + getclass.get_error());
+ } else {
+ print_line("Found class: " + getclass.get_class());
+ }
+
+ {
+ Vector<int> hashes;
+ List<StringName> tl;
+ ClassDB::get_class_list(&tl);
+
+ for (List<StringName>::Element *E = tl.front(); E; E = E->next()) {
+ Vector<uint8_t> m5b = E->get().operator String().md5_buffer();
+ hashes.push_back(hashes.size());
+ }
+
+ for (int i = nearest_shift(hashes.size()); i < 20; i++) {
+ bool success = true;
+ for (int s = 0; s < 10000; s++) {
+ Set<uint32_t> existing;
+ success = true;
+
+ for (int j = 0; j < hashes.size(); j++) {
+ uint32_t eh = ihash2(ihash3(hashes[j] + ihash(s) + s)) & ((1 << i) - 1);
+ if (existing.has(eh)) {
+ success = false;
+ break;
+ }
+ existing.insert(eh);
+ }
+
+ if (success) {
+ print_line("success at " + itos(i) + "/" + itos(nearest_shift(hashes.size())) + " shift " + itos(s));
+ break;
+ }
+ }
+ if (success) {
+ break;
+ }
+ }
+
+ print_line("DONE");
+ }
+
+ {
+ print_line("NUM: " + itos(-128));
+ }
+
+ {
+ Vector3 v(1, 2, 3);
+ v.normalize();
+ float a = 0.3;
+
+ Basis m(v, a);
+
+ Vector3 v2(7, 3, 1);
+ v2.normalize();
+ float a2 = 0.8;
+
+ Basis m2(v2, a2);
+
+ Quat q = m;
+ Quat q2 = m2;
+
+ Basis m3 = m.inverse() * m2;
+ Quat q3 = (q.inverse() * q2); //.normalized();
+
+ print_line(Quat(m3));
+ print_line(q3);
+
+ print_line("before v: " + v + " a: " + rtos(a));
+ q.get_axis_angle(v, a);
+ print_line("after v: " + v + " a: " + rtos(a));
+ }
+
+ String ret;
+
+ List<String> args;
+ args.push_back("-l");
+ Error err = OS::get_singleton()->execute("/bin/ls", args, true, nullptr, &ret);
+ print_line("error: " + itos(err));
+ print_line(ret);
+
+ Basis m3;
+ m3.rotate(Vector3(1, 0, 0), 0.2);
+ m3.rotate(Vector3(0, 1, 0), 1.77);
+ m3.rotate(Vector3(0, 0, 1), 212);
+ Basis m32;
+ m32.set_euler(m3.get_euler());
+ print_line("ELEULEEEEEEEEEEEEEEEEEER: " + m3.get_euler() + " vs " + m32.get_euler());
+
+ {
+ Dictionary d;
+ d["momo"] = 1;
+ Dictionary b = d;
+ b["44"] = 4;
+ }
+
+ print_line("inters: " + rtos(Geometry2D::segment_intersects_circle(Vector2(-5, 0), Vector2(-2, 0), Vector2(), 1.0)));
+
+ print_line("cross: " + Vector3(1, 2, 3).cross(Vector3(4, 5, 7)));
+ print_line("dot: " + rtos(Vector3(1, 2, 3).dot(Vector3(4, 5, 7))));
+ print_line("abs: " + Vector3(-1, 2, -3).abs());
+ print_line("distance_to: " + rtos(Vector3(1, 2, 3).distance_to(Vector3(4, 5, 7))));
+ print_line("distance_squared_to: " + rtos(Vector3(1, 2, 3).distance_squared_to(Vector3(4, 5, 7))));
+ print_line("plus: " + (Vector3(1, 2, 3) + Vector3(Vector3(4, 5, 7))));
+ print_line("minus: " + (Vector3(1, 2, 3) - Vector3(Vector3(4, 5, 7))));
+ print_line("mul: " + (Vector3(1, 2, 3) * Vector3(Vector3(4, 5, 7))));
+ print_line("div: " + (Vector3(1, 2, 3) / Vector3(Vector3(4, 5, 7))));
+ print_line("mul scalar: " + (Vector3(1, 2, 3) * 2.0));
+ print_line("premul scalar: " + (2.0 * Vector3(1, 2, 3)));
+ print_line("div scalar: " + (Vector3(1, 2, 3) / 3.0));
+ print_line("length: " + rtos(Vector3(1, 2, 3).length()));
+ print_line("length squared: " + rtos(Vector3(1, 2, 3).length_squared()));
+ print_line("normalized: " + Vector3(1, 2, 3).normalized());
+ print_line("inverse: " + Vector3(1, 2, 3).inverse());
+
+ {
+ Vector3 v(4, 5, 7);
+ v.normalize();
+ print_line("normalize: " + v);
+ }
+
+ {
+ Vector3 v(4, 5, 7);
+ v += Vector3(1, 2, 3);
+ print_line("+=: " + v);
+ }
+
+ {
+ Vector3 v(4, 5, 7);
+ v -= Vector3(1, 2, 3);
+ print_line("-=: " + v);
+ }
+
+ {
+ Vector3 v(4, 5, 7);
+ v *= Vector3(1, 2, 3);
+ print_line("*=: " + v);
+ }
+
+ {
+ Vector3 v(4, 5, 7);
+ v /= Vector3(1, 2, 3);
+ print_line("/=: " + v);
+ }
+
+ {
+ Vector3 v(4, 5, 7);
+ v *= 2.0;
+ print_line("scalar *=: " + v);
+ }
+
+ {
+ Vector3 v(4, 5, 7);
+ v /= 2.0;
+ print_line("scalar /=: " + v);
+ }
+
+ return nullptr;
+}
+
+} // namespace TestMath
diff --git a/tests/test_math.h b/tests/test_math.h
new file mode 100644
index 0000000000..77bce8dd66
--- /dev/null
+++ b/tests/test_math.h
@@ -0,0 +1,41 @@
+/*************************************************************************/
+/* test_math.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_MATH_H
+#define TEST_MATH_H
+
+#include "core/os/main_loop.h"
+
+namespace TestMath {
+
+MainLoop *test();
+}
+
+#endif
diff --git a/tests/test_oa_hash_map.cpp b/tests/test_oa_hash_map.cpp
new file mode 100644
index 0000000000..9182f66b61
--- /dev/null
+++ b/tests/test_oa_hash_map.cpp
@@ -0,0 +1,299 @@
+/*************************************************************************/
+/* test_oa_hash_map.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_oa_hash_map.h"
+
+#include "core/oa_hash_map.h"
+#include "core/os/os.h"
+
+namespace TestOAHashMap {
+
+struct CountedItem {
+ static int count;
+
+ int id = -1;
+ bool destroyed = false;
+
+ CountedItem() {
+ count++;
+ }
+
+ CountedItem(int p_id) :
+ id(p_id) {
+ count++;
+ }
+
+ CountedItem(const CountedItem &p_other) :
+ id(p_other.id) {
+ count++;
+ }
+
+ CountedItem &operator=(const CountedItem &p_other) = default;
+
+ ~CountedItem() {
+ CRASH_COND(destroyed);
+ count--;
+ destroyed = true;
+ }
+};
+
+int CountedItem::count;
+
+MainLoop *test() {
+ OS::get_singleton()->print("\n\n\nHello from test\n");
+
+ // test element tracking.
+ {
+ OAHashMap<int, int> map;
+
+ map.set(42, 1337);
+ map.set(1337, 21);
+ map.set(42, 11880);
+
+ int value = 0;
+ map.lookup(42, value);
+
+ OS::get_singleton()->print("capacity %d\n", map.get_capacity());
+ OS::get_singleton()->print("elements %d\n", map.get_num_elements());
+
+ OS::get_singleton()->print("map[42] = %d\n", value);
+ }
+
+ // rehashing and deletion
+ {
+ OAHashMap<int, int> map;
+
+ for (int i = 0; i < 500; i++) {
+ map.set(i, i * 2);
+ }
+
+ for (int i = 0; i < 500; i += 2) {
+ map.remove(i);
+ }
+
+ uint32_t num_elems = 0;
+ for (int i = 0; i < 500; i++) {
+ int tmp;
+ if (map.lookup(i, tmp) && tmp == i * 2) {
+ num_elems++;
+ }
+ }
+
+ OS::get_singleton()->print("elements %d == %d.\n", map.get_num_elements(), num_elems);
+ }
+
+ // iteration
+ {
+ OAHashMap<String, int> map;
+
+ map.set("Hello", 1);
+ map.set("World", 2);
+ map.set("Godot rocks", 42);
+
+ for (OAHashMap<String, int>::Iterator it = map.iter(); it.valid; it = map.next_iter(it)) {
+ OS::get_singleton()->print("map[\"%s\"] = %d\n", it.key->utf8().get_data(), *it.value);
+ }
+ }
+
+ // stress test / test for issue #22928
+ {
+ OAHashMap<int, int> map;
+ int dummy = 0;
+ const int N = 1000;
+ uint32_t *keys = new uint32_t[N];
+
+ Math::seed(0);
+
+ // insert a couple of random keys (with a dummy value, which is ignored)
+ for (int i = 0; i < N; i++) {
+ keys[i] = Math::rand();
+ map.set(keys[i], dummy);
+
+ if (!map.lookup(keys[i], dummy)) {
+ OS::get_singleton()->print("could not find 0x%X despite it was just inserted!\n", unsigned(keys[i]));
+ }
+ }
+
+ // check whether the keys are still present
+ for (int i = 0; i < N; i++) {
+ if (!map.lookup(keys[i], dummy)) {
+ OS::get_singleton()->print("could not find 0x%X despite it has been inserted previously! (not checking the other keys, breaking...)\n", unsigned(keys[i]));
+ break;
+ }
+ }
+
+ delete[] keys;
+ }
+
+ // regression test / test for issue related to #31402
+ {
+ OS::get_singleton()->print("test for issue #31402 started...\n");
+
+ const int num_test_values = 12;
+ int test_values[num_test_values] = { 0, 24, 48, 72, 96, 120, 144, 168, 192, 216, 240, 264 };
+
+ int dummy = 0;
+ OAHashMap<int, int> map;
+ map.clear();
+
+ for (int i = 0; i < num_test_values; ++i) {
+ map.set(test_values[i], dummy);
+ }
+
+ OS::get_singleton()->print("test for issue #31402 passed.\n");
+ }
+
+ // test collision resolution, should not crash or run indefinitely
+ {
+ OAHashMap<int, int> map(4);
+ map.set(1, 1);
+ map.set(5, 1);
+ map.set(9, 1);
+ map.set(13, 1);
+ map.remove(5);
+ map.remove(9);
+ map.remove(13);
+ map.set(5, 1);
+ }
+
+ // test memory management of items, should not crash or leak items
+ {
+ // Exercise different patterns of removal
+ for (int i = 0; i < 4; ++i) {
+ {
+ OAHashMap<String, CountedItem> map;
+ int id = 0;
+ for (int j = 0; j < 100; ++j) {
+ map.insert(itos(j), CountedItem(id));
+ }
+ if (i <= 1) {
+ for (int j = 0; j < 100; ++j) {
+ map.remove(itos(j));
+ }
+ }
+ if (i % 2 == 0) {
+ map.clear();
+ }
+ }
+
+ if (CountedItem::count != 0) {
+ OS::get_singleton()->print("%d != 0 (not performing the other test sub-cases, breaking...)\n", CountedItem::count);
+ break;
+ }
+ }
+ }
+
+ // Test map with 0 capacity.
+ {
+ OAHashMap<int, String> original_map(0);
+ original_map.set(1, "1");
+ OS::get_singleton()->print("OAHashMap 0 capacity initialization passed.\n");
+ }
+
+ // Test copy constructor.
+ {
+ OAHashMap<int, String> original_map;
+ original_map.set(1, "1");
+ original_map.set(2, "2");
+ original_map.set(3, "3");
+ original_map.set(4, "4");
+ original_map.set(5, "5");
+
+ OAHashMap<int, String> map_copy(original_map);
+
+ bool pass = true;
+ for (
+ OAHashMap<int, String>::Iterator it = original_map.iter();
+ it.valid;
+ it = original_map.next_iter(it)) {
+ if (map_copy.lookup_ptr(*it.key) == nullptr) {
+ pass = false;
+ }
+ if (*it.value != *map_copy.lookup_ptr(*it.key)) {
+ pass = false;
+ }
+ }
+ if (pass) {
+ OS::get_singleton()->print("OAHashMap copy constructor test passed.\n");
+ } else {
+ OS::get_singleton()->print("OAHashMap copy constructor test FAILED.\n");
+ }
+
+ map_copy.set(1, "Random String");
+ if (*map_copy.lookup_ptr(1) == *original_map.lookup_ptr(1)) {
+ OS::get_singleton()->print("OAHashMap copy constructor, atomic copy test FAILED.\n");
+ } else {
+ OS::get_singleton()->print("OAHashMap copy constructor, atomic copy test passed.\n");
+ }
+ }
+
+ // Test assign operator.
+ {
+ OAHashMap<int, String> original_map;
+ original_map.set(1, "1");
+ original_map.set(2, "2");
+ original_map.set(3, "3");
+ original_map.set(4, "4");
+ original_map.set(5, "5");
+
+ OAHashMap<int, String> map_copy(100000);
+ map_copy.set(1, "Just a string.");
+ map_copy = original_map;
+
+ bool pass = true;
+ for (
+ OAHashMap<int, String>::Iterator it = map_copy.iter();
+ it.valid;
+ it = map_copy.next_iter(it)) {
+ if (original_map.lookup_ptr(*it.key) == nullptr) {
+ pass = false;
+ }
+ if (*it.value != *original_map.lookup_ptr(*it.key)) {
+ pass = false;
+ }
+ }
+ if (pass) {
+ OS::get_singleton()->print("OAHashMap assign operation test passed.\n");
+ } else {
+ OS::get_singleton()->print("OAHashMap assign operation test FAILED.\n");
+ }
+
+ map_copy.set(1, "Random String");
+ if (*map_copy.lookup_ptr(1) == *original_map.lookup_ptr(1)) {
+ OS::get_singleton()->print("OAHashMap assign operation atomic copy test FAILED.\n");
+ } else {
+ OS::get_singleton()->print("OAHashMap assign operation atomic copy test passed.\n");
+ }
+ }
+
+ return nullptr;
+}
+
+} // namespace TestOAHashMap
diff --git a/tests/test_oa_hash_map.h b/tests/test_oa_hash_map.h
new file mode 100644
index 0000000000..eb2b3d1e99
--- /dev/null
+++ b/tests/test_oa_hash_map.h
@@ -0,0 +1,41 @@
+/*************************************************************************/
+/* test_oa_hash_map.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_OA_HASH_MAP_H
+#define TEST_OA_HASH_MAP_H
+
+#include "core/os/main_loop.h"
+
+namespace TestOAHashMap {
+
+MainLoop *test();
+}
+
+#endif // TEST_OA_HASH_MAP_H
diff --git a/tests/test_ordered_hash_map.cpp b/tests/test_ordered_hash_map.cpp
new file mode 100644
index 0000000000..d18a3784be
--- /dev/null
+++ b/tests/test_ordered_hash_map.cpp
@@ -0,0 +1,175 @@
+/*************************************************************************/
+/* test_ordered_hash_map.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_ordered_hash_map.h"
+
+#include "core/ordered_hash_map.h"
+#include "core/os/os.h"
+#include "core/pair.h"
+#include "core/vector.h"
+
+namespace TestOrderedHashMap {
+
+bool test_insert() {
+ OrderedHashMap<int, int> map;
+ OrderedHashMap<int, int>::Element e = map.insert(42, 84);
+
+ return e && e.key() == 42 && e.get() == 84 && e.value() == 84 && map[42] == 84 && map.has(42) && map.find(42);
+}
+
+bool test_insert_overwrite() {
+ OrderedHashMap<int, int> map;
+ map.insert(42, 84);
+ map.insert(42, 1234);
+
+ return map[42] == 1234;
+}
+
+bool test_erase_via_element() {
+ OrderedHashMap<int, int> map;
+ OrderedHashMap<int, int>::Element e = map.insert(42, 84);
+
+ map.erase(e);
+ return !e && !map.has(42) && !map.find(42);
+}
+
+bool test_erase_via_key() {
+ OrderedHashMap<int, int> map;
+ map.insert(42, 84);
+ map.erase(42);
+ return !map.has(42) && !map.find(42);
+}
+
+bool test_size() {
+ OrderedHashMap<int, int> map;
+ map.insert(42, 84);
+ map.insert(123, 84);
+ map.insert(123, 84);
+ map.insert(0, 84);
+ map.insert(123485, 84);
+
+ return map.size() == 4;
+}
+
+bool test_iteration() {
+ OrderedHashMap<int, int> map;
+ map.insert(42, 84);
+ map.insert(123, 12385);
+ map.insert(0, 12934);
+ map.insert(123485, 1238888);
+ map.insert(123, 111111);
+
+ Vector<Pair<int, int>> expected;
+ expected.push_back(Pair<int, int>(42, 84));
+ expected.push_back(Pair<int, int>(123, 111111));
+ expected.push_back(Pair<int, int>(0, 12934));
+ expected.push_back(Pair<int, int>(123485, 1238888));
+
+ int idx = 0;
+ for (OrderedHashMap<int, int>::Element E = map.front(); E; E = E.next()) {
+ if (expected[idx] != Pair<int, int>(E.key(), E.value())) {
+ return false;
+ }
+ ++idx;
+ }
+ return true;
+}
+
+bool test_const_iteration(const OrderedHashMap<int, int> &map) {
+ Vector<Pair<int, int>> expected;
+ expected.push_back(Pair<int, int>(42, 84));
+ expected.push_back(Pair<int, int>(123, 111111));
+ expected.push_back(Pair<int, int>(0, 12934));
+ expected.push_back(Pair<int, int>(123485, 1238888));
+
+ int idx = 0;
+ for (OrderedHashMap<int, int>::ConstElement E = map.front(); E; E = E.next()) {
+ if (expected[idx] != Pair<int, int>(E.key(), E.value())) {
+ return false;
+ }
+ ++idx;
+ }
+ return true;
+}
+
+bool test_const_iteration() {
+ OrderedHashMap<int, int> map;
+ map.insert(42, 84);
+ map.insert(123, 12385);
+ map.insert(0, 12934);
+ map.insert(123485, 1238888);
+ map.insert(123, 111111);
+
+ return test_const_iteration(map);
+}
+
+typedef bool (*TestFunc)();
+
+TestFunc test_funcs[] = {
+
+ test_insert,
+ test_insert_overwrite,
+ test_erase_via_element,
+ test_erase_via_key,
+ test_size,
+ test_iteration,
+ test_const_iteration,
+ 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\n\n");
+ OS::get_singleton()->print("*************\n");
+ OS::get_singleton()->print("***TOTALS!***\n");
+ OS::get_singleton()->print("*************\n");
+
+ OS::get_singleton()->print("Passed %i of %i tests\n", passed, count);
+
+ return nullptr;
+}
+
+} // namespace TestOrderedHashMap
diff --git a/tests/test_ordered_hash_map.h b/tests/test_ordered_hash_map.h
new file mode 100644
index 0000000000..f251da0ba2
--- /dev/null
+++ b/tests/test_ordered_hash_map.h
@@ -0,0 +1,41 @@
+/*************************************************************************/
+/* test_ordered_hash_map.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_ORDERED_HASH_MAP_H
+#define TEST_ORDERED_HASH_MAP_H
+
+#include "core/os/main_loop.h"
+
+namespace TestOrderedHashMap {
+
+MainLoop *test();
+}
+
+#endif // TEST_ORDERED_HASH_MAP_H
diff --git a/tests/test_physics_2d.cpp b/tests/test_physics_2d.cpp
new file mode 100644
index 0000000000..c82ae920bc
--- /dev/null
+++ b/tests/test_physics_2d.cpp
@@ -0,0 +1,407 @@
+/*************************************************************************/
+/* test_physics_2d.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_physics_2d.h"
+
+#include "core/map.h"
+#include "core/os/main_loop.h"
+#include "core/os/os.h"
+#include "core/print_string.h"
+#include "scene/resources/texture.h"
+#include "servers/display_server.h"
+#include "servers/physics_server_2d.h"
+#include "servers/rendering_server.h"
+
+static const unsigned char convex_png[] = {
+ 0x89, 0x50, 0x4e, 0x47, 0xd, 0xa, 0x1a, 0xa, 0x0, 0x0, 0x0, 0xd, 0x49, 0x48, 0x44, 0x52, 0x0, 0x0, 0x0, 0x40, 0x0, 0x0, 0x0, 0x40, 0x8, 0x6, 0x0, 0x0, 0x0, 0xaa, 0x69, 0x71, 0xde, 0x0, 0x0, 0x0, 0x1, 0x73, 0x52, 0x47, 0x42, 0x0, 0xae, 0xce, 0x1c, 0xe9, 0x0, 0x0, 0x0, 0x6, 0x62, 0x4b, 0x47, 0x44, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xf9, 0x43, 0xbb, 0x7f, 0x0, 0x0, 0x0, 0x9, 0x70, 0x48, 0x59, 0x73, 0x0, 0x0, 0xb, 0x13, 0x0, 0x0, 0xb, 0x13, 0x1, 0x0, 0x9a, 0x9c, 0x18, 0x0, 0x0, 0x0, 0x7, 0x74, 0x49, 0x4d, 0x45, 0x7, 0xdb, 0x6, 0xa, 0x3, 0x13, 0x31, 0x66, 0xa7, 0xac, 0x79, 0x0, 0x0, 0x4, 0xef, 0x49, 0x44, 0x41, 0x54, 0x78, 0xda, 0xed, 0x9b, 0xdd, 0x4e, 0x2a, 0x57, 0x14, 0xc7, 0xf7, 0x1e, 0xc0, 0x19, 0x38, 0x32, 0x80, 0xa, 0x6a, 0xda, 0x18, 0xa3, 0xc6, 0x47, 0x50, 0x7b, 0xa1, 0xd9, 0x36, 0x27, 0x7e, 0x44, 0xed, 0x45, 0x4d, 0x93, 0x3e, 0x40, 0x1f, 0x64, 0x90, 0xf4, 0x1, 0xbc, 0xf0, 0xc2, 0x9c, 0x57, 0x30, 0x4d, 0xbc, 0xa8, 0x6d, 0xc, 0x69, 0x26, 0xb5, 0x68, 0x8b, 0x35, 0x7e, 0x20, 0xb4, 0xf5, 0x14, 0xbf, 0x51, 0x3c, 0x52, 0xe, 0xc, 0xe, 0xc8, 0xf0, 0xb1, 0x7a, 0x51, 0x3d, 0xb1, 0x9e, 0x19, 0x1c, 0x54, 0x70, 0x1c, 0xdc, 0x9, 0x17, 0x64, 0x8, 0xc9, 0xff, 0xb7, 0xd6, 0x7f, 0xcd, 0x3f, 0x2b, 0xd9, 0x8, 0xbd, 0x9c, 0xda, 0x3e, 0xf8, 0x31, 0xff, 0xc, 0x0, 0x8, 0x42, 0x88, 0x9c, 0x9f, 0x9f, 0xbf, 0xa, 0x87, 0xc3, 0xad, 0x7d, 0x7d, 0x7d, 0x7f, 0x23, 0x84, 0x78, 0x8c, 0x31, 0xaf, 0x55, 0x0, 0xc6, 0xc7, 0x14, 0x1e, 0x8f, 0xc7, 0xbf, 0x38, 0x3c, 0x3c, 0x6c, 0x9b, 0x9f, 0x9f, 0x6f, 0xb8, 0x82, 0x9b, 0xee, 0xe8, 0xe8, 0xf8, 0x12, 0x0, 0xbe, 0xd3, 0x2a, 0x8, 0xfc, 0x50, 0xd1, 0xf9, 0x7c, 0x9e, 0x8a, 0x46, 0xa3, 0x5f, 0x9d, 0x9e, 0x9e, 0x7e, 0xb2, 0xb0, 0xb0, 0x60, 0xe5, 0x79, 0x1e, 0xf1, 0xfc, 0x7f, 0x3a, 0x9, 0x21, 0x88, 0x10, 0x82, 0x26, 0x26, 0x26, 0xde, 0x77, 0x75, 0x75, 0x85, 0x59, 0x96, 0xfd, 0x5e, 0x6b, 0x20, 0xf0, 0x7d, 0x85, 0x4b, 0x92, 0xf4, 0xfa, 0xe0, 0xe0, 0xe0, 0xd3, 0xb9, 0xb9, 0xb9, 0x46, 0x49, 0x92, 0xea, 0x6f, 0xa, 0xbf, 0x7d, 0x8, 0x21, 0x68, 0x70, 0x70, 0xb0, 0x38, 0x39, 0x39, 0x79, 0xd6, 0xd9, 0xd9, 0xb9, 0xcf, 0x30, 0xcc, 0xa2, 0xd6, 0xad, 0x21, 0x2b, 0x1c, 0x0, 0x38, 0x41, 0x10, 0xfc, 0xdb, 0xdb, 0xdb, 0x27, 0x1e, 0x8f, 0x27, 0x4b, 0x8, 0x1, 0x84, 0x90, 0xea, 0xf, 0x21, 0x4, 0x3c, 0x1e, 0x4f, 0x76, 0x67, 0x67, 0x67, 0x3f, 0x9f, 0xcf, 0xff, 0x7c, 0x5, 0xf3, 0xd9, 0x0, 0xe0, 0x2, 0x81, 0xc0, 0xa9, 0xdb, 0xed, 0x2e, 0x94, 0x2b, 0x5c, 0xe, 0xc4, 0xca, 0xca, 0x8a, 0x18, 0x8d, 0x46, 0x3, 0x0, 0xc0, 0x69, 0x1e, 0x4, 0x0, 0x90, 0x48, 0x24, 0x12, 0xe4, 0x38, 0xee, 0x41, 0xc2, 0x6f, 0x43, 0xe0, 0x38, 0xe, 0xfc, 0x7e, 0xbf, 0x10, 0x8b, 0xc5, 0xd6, 0x35, 0xd, 0x22, 0x9b, 0xcd, 0x7a, 0x96, 0x97, 0x97, 0x33, 0xf, 0xad, 0x7c, 0x29, 0x10, 0x9b, 0x9b, 0x9b, 0xef, 0x2e, 0x2e, 0x2e, 0x7e, 0xd5, 0x1c, 0x8, 0x0, 0x20, 0xe1, 0x70, 0x38, 0xfc, 0x98, 0xd5, 0x57, 0x2, 0xe1, 0x76, 0xbb, 0xf3, 0xa1, 0x50, 0xe8, 0x38, 0x9b, 0xcd, 0xfe, 0xa2, 0x9, 0x8, 0x0, 0x40, 0x2e, 0x2f, 0x2f, 0x7d, 0x4b, 0x4b, 0x4b, 0xb9, 0x4a, 0x54, 0x5f, 0x9, 0xc4, 0xd2, 0xd2, 0x92, 0xb4, 0xb7, 0xb7, 0xf7, 0x36, 0x97, 0xcb, 0x4d, 0x3d, 0x29, 0x8, 0x0, 0xe0, 0x42, 0xa1, 0xd0, 0x71, 0xb5, 0xc4, 0xdf, 0xb6, 0xc5, 0x93, 0xe, 0x4a, 0x0, 0x20, 0xa9, 0x54, 0xea, 0x37, 0xb7, 0xdb, 0x5d, 0xa8, 0xa6, 0x78, 0x39, 0x10, 0x6b, 0x6b, 0x6b, 0xf1, 0x64, 0x32, 0xb9, 0x5a, 0x55, 0x10, 0x0, 0xc0, 0x6d, 0x6c, 0x6c, 0x9c, 0x57, 0xbb, 0xfa, 0x25, 0x40, 0x14, 0x3, 0x81, 0x40, 0x34, 0x93, 0xc9, 0x2c, 0x57, 0x1c, 0x4, 0x0, 0x90, 0x58, 0x2c, 0xb6, 0x5e, 0xe9, 0xc1, 0x77, 0x1f, 0x10, 0x53, 0x53, 0x53, 0x52, 0xc5, 0x83, 0x14, 0x0, 0x70, 0x7e, 0xbf, 0x5f, 0xd0, 0x42, 0xf5, 0x95, 0x40, 0xf8, 0x7c, 0xbe, 0xcb, 0xa3, 0xa3, 0xa3, 0x3f, 0x1e, 0xbd, 0x1b, 0x0, 0x80, 0x1c, 0x1f, 0x1f, 0x87, 0xb4, 0x56, 0xfd, 0xaa, 0x5, 0x29, 0x51, 0x14, 0xbf, 0xf5, 0xf9, 0x7c, 0x97, 0x5a, 0xad, 0xbe, 0x12, 0x88, 0xf5, 0xf5, 0xf5, 0xd8, 0x83, 0x83, 0x54, 0xb5, 0x42, 0x8f, 0x66, 0x83, 0x94, 0xd6, 0xbd, 0x5f, 0xce, 0x7c, 0x38, 0x3c, 0x3c, 0xfc, 0xb3, 0x50, 0x28, 0xb8, 0xcb, 0x2, 0x1, 0x0, 0xdc, 0xf4, 0xf4, 0xf4, 0xfe, 0x73, 0x15, 0x2f, 0x17, 0xa4, 0x22, 0x91, 0x48, 0x50, 0xb5, 0x2d, 0x0, 0x80, 0x9b, 0x99, 0x99, 0x79, 0xfb, 0xdc, 0x1, 0xc8, 0x5, 0xa9, 0x44, 0x22, 0xf1, 0xfb, 0x9d, 0x10, 0x0, 0x80, 0x9b, 0x9d, 0x9d, 0xd, 0xea, 0x5, 0xc0, 0xad, 0xfd, 0x43, 0x1a, 0x0, 0xb8, 0xdb, 0x9a, 0xa9, 0x8f, 0xb6, 0xa4, 0x46, 0xa3, 0xa4, 0xb7, 0xd5, 0x37, 0xcf, 0xf3, 0x68, 0x75, 0x75, 0xf5, 0x4c, 0xee, 0x99, 0x1c, 0x80, 0x9c, 0x1e, 0xf7, 0xff, 0x16, 0x8b, 0x45, 0x50, 0x5, 0xa0, 0xb7, 0xb7, 0xb7, 0x85, 0x10, 0xa2, 0x2b, 0xf1, 0x84, 0x10, 0xd4, 0xdf, 0xdf, 0x6f, 0x57, 0x3, 0x80, 0x37, 0x18, 0xc, 0x5, 0x3d, 0x2, 0xa0, 0x69, 0x3a, 0x8b, 0x10, 0xe2, 0x4b, 0x2, 0xc0, 0x18, 0xf3, 0xc1, 0x60, 0x70, 0x47, 0x8f, 0x16, 0x38, 0x3a, 0x3a, 0x5a, 0x93, 0x5b, 0xc3, 0x7f, 0x64, 0x81, 0xba, 0xba, 0x3a, 0x49, 0x8f, 0x0, 0x1a, 0x1a, 0x1a, 0xd4, 0xcd, 0x0, 0x93, 0xc9, 0xa4, 0xcb, 0x21, 0xe8, 0x74, 0x3a, 0xd5, 0x1, 0xa0, 0x69, 0x5a, 0x77, 0x1d, 0x80, 0x31, 0x2e, 0x38, 0x9d, 0x4e, 0xb1, 0x66, 0x1, 0x30, 0xc, 0x23, 0x28, 0x3d, 0x93, 0x9b, 0x1, 0xb9, 0x9a, 0x6, 0x60, 0x36, 0x9b, 0x75, 0xd7, 0x1, 0x4a, 0x21, 0xa8, 0x26, 0x0, 0x94, 0xa, 0x41, 0xb2, 0x0, 0x18, 0x86, 0xc9, 0xe9, 0xd, 0x80, 0x52, 0x8, 0x92, 0x5, 0x60, 0xb1, 0x58, 0x74, 0x67, 0x1, 0xa5, 0x10, 0xa4, 0x4, 0x40, 0x77, 0x43, 0xd0, 0xe1, 0x70, 0xa8, 0x9f, 0x1, 0x14, 0x45, 0x1, 0x45, 0x51, 0x79, 0x3d, 0x1, 0x68, 0x6e, 0x6e, 0x4e, 0xaa, 0x6, 0x80, 0x10, 0x42, 0x6, 0x83, 0x41, 0x37, 0x36, 0x28, 0x15, 0x82, 0x6a, 0x2, 0x0, 0x4d, 0xd3, 0xa9, 0x52, 0xcf, 0x95, 0x0, 0xe8, 0x66, 0xe, 0x98, 0xcd, 0x66, 0xa1, 0x6c, 0x0, 0x7a, 0x5a, 0x8b, 0x59, 0x2c, 0x96, 0x64, 0xcd, 0x2, 0xb8, 0x2b, 0x4, 0xe9, 0xde, 0x2, 0x77, 0x85, 0xa0, 0x9a, 0xb0, 0x40, 0xa9, 0x10, 0xa4, 0x8, 0xc0, 0x64, 0x32, 0xe9, 0x6, 0x40, 0xa9, 0x10, 0x54, 0xaa, 0x3, 0x74, 0xf3, 0x16, 0x70, 0xb9, 0x5c, 0xe5, 0x3, 0xe8, 0xe9, 0xe9, 0x69, 0xd5, 0xc3, 0x66, 0x18, 0x63, 0x5c, 0x68, 0x6a, 0x6a, 0x12, 0xcb, 0x5, 0xa0, 0x9b, 0xd5, 0x38, 0x4d, 0xd3, 0x29, 0x8a, 0xa2, 0xa0, 0x2c, 0x0, 0x18, 0x63, 0x3e, 0x14, 0xa, 0xfd, 0x55, 0xb, 0x21, 0x48, 0xd1, 0x2, 0x7a, 0x59, 0x8d, 0xdf, 0x1b, 0x80, 0x1e, 0x56, 0xe3, 0x84, 0x10, 0x34, 0x30, 0x30, 0x60, 0xbb, 0xeb, 0x77, 0x46, 0x5, 0xef, 0x48, 0xcf, 0x4d, 0xec, 0x8d, 0x99, 0x5, 0xf5, 0xf5, 0xf5, 0xef, 0x46, 0x47, 0x47, 0xb, 0x2e, 0x97, 0xeb, 0xbc, 0x54, 0x8, 0x52, 0x4, 0xc0, 0x30, 0x8c, 0xf4, 0x5c, 0x4, 0x9b, 0x4c, 0xa6, 0xf4, 0xf8, 0xf8, 0xb8, 0xc8, 0xb2, 0x6c, 0x32, 0x9d, 0x4e, 0xff, 0xd4, 0xdd, 0xdd, 0x7d, 0x66, 0x34, 0x1a, 0x8b, 0xd7, 0x3, 0xfd, 0xae, 0x5b, 0x29, 0xb2, 0x57, 0x66, 0xb6, 0xb6, 0xb6, 0xde, 0xc4, 0xe3, 0xf1, 0x6f, 0xae, 0xaf, 0xc1, 0x28, 0x5d, 0x85, 0x79, 0x2, 0xc1, 0x60, 0xb5, 0x5a, 0xa3, 0xa3, 0xa3, 0xa3, 0x45, 0xab, 0xd5, 0x9a, 0x2a, 0x16, 0x8b, 0x8b, 0x6d, 0x6d, 0x6d, 0xef, 0xd5, 0x8a, 0x55, 0xd, 0x20, 0x91, 0x48, 0xbc, 0x3e, 0x38, 0x38, 0xf8, 0xda, 0x6e, 0xb7, 0xf7, 0x5f, 0x5c, 0x5c, 0xd4, 0x7b, 0xbd, 0xde, 0xbc, 0x20, 0x8, 0xcd, 0x85, 0x42, 0x81, 0xfe, 0xf0, 0xae, 0xac, 0x10, 0x98, 0x9b, 0xd5, 0xc5, 0x18, 0x17, 0x59, 0x96, 0x3d, 0x1d, 0x19, 0x19, 0x1, 0x96, 0x65, 0x5, 0x8a, 0xa2, 0x7e, 0x6c, 0x69, 0x69, 0x49, 0x3d, 0x44, 0xb0, 0x2a, 0x0, 0x1f, 0xcc, 0x74, 0x75, 0x41, 0xea, 0xfa, 0x7b, 0x32, 0x99, 0x64, 0x76, 0x77, 0x77, 0x5d, 0xe, 0x87, 0xa3, 0x5f, 0x14, 0xc5, 0x57, 0x57, 0x60, 0x5a, 0x8b, 0xc5, 0xa2, 0xf1, 0xbe, 0x50, 0x6e, 0xa, 0x66, 0x18, 0x26, 0x31, 0x36, 0x36, 0x96, 0x65, 0x59, 0x36, 0x29, 0x49, 0x92, 0xb7, 0xbd, 0xbd, 0xfd, 0x9f, 0x72, 0xda, 0xf9, 0xd1, 0x1, 0xa8, 0x1, 0x93, 0xcf, 0xe7, 0xa9, 0x93, 0x93, 0x13, 0x1b, 0x4d, 0xd3, 0x9f, 0xb, 0x82, 0x60, 0xf5, 0x7a, 0xbd, 0xd9, 0x54, 0x2a, 0xe5, 0xcc, 0x64, 0x32, 0xe, 0xb9, 0x6e, 0xb9, 0x16, 0x8c, 0x31, 0x2e, 0xda, 0x6c, 0xb6, 0xc8, 0xd0, 0xd0, 0x10, 0x65, 0xb3, 0xd9, 0x92, 0x95, 0xa8, 0x6e, 0xc5, 0x0, 0xa8, 0xe9, 0x96, 0x68, 0x34, 0x6a, 0xdd, 0xdf, 0xdf, 0x6f, 0x76, 0xb9, 0x5c, 0x9f, 0x89, 0xa2, 0x58, 0xbf, 0xb8, 0xb8, 0x8, 0x26, 0x93, 0x29, 0x3b, 0x3c, 0x3c, 0x8c, 0xed, 0x76, 0x7b, 0xd2, 0x68, 0x34, 0xfe, 0xd0, 0xd8, 0xd8, 0x98, 0xae, 0xb6, 0xe0, 0x8a, 0x1, 0x50, 0xb, 0xe6, 0xa9, 0x5, 0xbf, 0x9c, 0x97, 0xf3, 0xff, 0xf3, 0x2f, 0x6a, 0x82, 0x7f, 0xf6, 0x4e, 0xca, 0x1b, 0xf5, 0x0, 0x0, 0x0, 0x0, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82
+};
+
+class TestPhysics2DMainLoop : public MainLoop {
+ GDCLASS(TestPhysics2DMainLoop, MainLoop);
+
+ RID circle_img;
+ RID circle_shape;
+ RID space;
+ RID canvas;
+ RID ray;
+ RID ray_query;
+ Transform2D view_xform;
+
+ Vector2 ray_from, ray_to;
+
+ struct BodyShapeData {
+ RID image;
+ RID shape;
+ };
+
+ BodyShapeData body_shape_data[8];
+
+ void _create_body_shape_data() {
+ RenderingServer *vs = RenderingServer::get_singleton();
+ PhysicsServer2D *ps = PhysicsServer2D::get_singleton();
+
+ // SEGMENT
+
+ {
+ Vector<uint8_t> pixels;
+ pixels.resize(32 * 2 * 2);
+ for (int i = 0; i < 2; i++) {
+ for (int j = 0; j < 32; j++) {
+ pixels.set(i * 32 * 2 + j * 2 + 0, (j == 0) ? 255 : 0);
+ pixels.set(i * 32 * 2 + j * 2 + 1, 255);
+ }
+ }
+
+ Ref<Image> image = memnew(Image(32, 2, 0, Image::FORMAT_LA8, pixels));
+
+ body_shape_data[PhysicsServer2D::SHAPE_SEGMENT].image = vs->texture_2d_create(image);
+
+ RID segment_shape = ps->segment_shape_create();
+ Rect2 sg(Point2(-16, 0), Point2(16, 0));
+ ps->shape_set_data(segment_shape, sg);
+
+ body_shape_data[PhysicsServer2D::SHAPE_SEGMENT].shape = segment_shape;
+ }
+ // CIRCLE
+
+ {
+ Vector<uint8_t> pixels;
+ pixels.resize(32 * 32 * 2);
+ for (int i = 0; i < 32; i++) {
+ for (int j = 0; j < 32; j++) {
+ bool black = Vector2(i - 16, j - 16).length_squared() < 16 * 16;
+
+ pixels.set(i * 32 * 2 + j * 2 + 0, (i == 16 || j == 16) ? 255 : 0);
+ pixels.set(i * 32 * 2 + j * 2 + 1, black ? 255 : 0);
+ }
+ }
+
+ Ref<Image> image = memnew(Image(32, 32, 0, Image::FORMAT_LA8, pixels));
+
+ body_shape_data[PhysicsServer2D::SHAPE_CIRCLE].image = vs->texture_2d_create(image);
+
+ RID circle_shape = ps->circle_shape_create();
+ ps->shape_set_data(circle_shape, 16);
+
+ body_shape_data[PhysicsServer2D::SHAPE_CIRCLE].shape = circle_shape;
+ }
+
+ // BOX
+
+ {
+ Vector<uint8_t> pixels;
+ pixels.resize(32 * 32 * 2);
+ for (int i = 0; i < 32; i++) {
+ for (int j = 0; j < 32; j++) {
+ bool black = i > 0 && i < 31 && j > 0 && j < 31;
+
+ pixels.set(i * 32 * 2 + j * 2 + 0, black ? 0 : 255);
+ pixels.set(i * 32 * 2 + j * 2 + 1, 255);
+ }
+ }
+
+ Ref<Image> image = memnew(Image(32, 32, 0, Image::FORMAT_LA8, pixels));
+
+ body_shape_data[PhysicsServer2D::SHAPE_RECTANGLE].image = vs->texture_2d_create(image);
+
+ RID rectangle_shape = ps->rectangle_shape_create();
+ ps->shape_set_data(rectangle_shape, Vector2(16, 16));
+
+ body_shape_data[PhysicsServer2D::SHAPE_RECTANGLE].shape = rectangle_shape;
+ }
+
+ // CAPSULE
+
+ {
+ Vector<uint8_t> pixels;
+ pixels.resize(32 * 64 * 2);
+ for (int i = 0; i < 64; i++) {
+ for (int j = 0; j < 32; j++) {
+ int si = i > 48 ? i - 32 : (i < 16 ? i : 16);
+ bool black = Vector2(si - 16, j - 16).length_squared() < 16 * 16;
+
+ pixels.set(i * 32 * 2 + j * 2 + 0, (i == 16 || j == 16 || i == 48) ? 255 : 0);
+ pixels.set(i * 32 * 2 + j * 2 + 1, black ? 255 : 0);
+ }
+ }
+
+ Ref<Image> image = memnew(Image(32, 64, 0, Image::FORMAT_LA8, pixels));
+
+ body_shape_data[PhysicsServer2D::SHAPE_CAPSULE].image = vs->texture_2d_create(image);
+
+ RID capsule_shape = ps->capsule_shape_create();
+ ps->shape_set_data(capsule_shape, Vector2(16, 32));
+
+ body_shape_data[PhysicsServer2D::SHAPE_CAPSULE].shape = capsule_shape;
+ }
+
+ // CONVEX
+
+ {
+ Ref<Image> image = memnew(Image(convex_png));
+
+ body_shape_data[PhysicsServer2D::SHAPE_CONVEX_POLYGON].image = vs->texture_2d_create(image);
+
+ RID convex_polygon_shape = ps->convex_polygon_shape_create();
+
+ Vector<Vector2> arr;
+ Point2 sb(32, 32);
+ arr.push_back(Point2(20, 3) - sb);
+ arr.push_back(Point2(58, 23) - sb);
+ arr.push_back(Point2(55, 54) - sb);
+ arr.push_back(Point2(27, 60) - sb);
+ arr.push_back(Point2(5, 56) - sb);
+ arr.push_back(Point2(4, 20) - sb);
+ arr.push_back(Point2(11, 7) - sb);
+ ps->shape_set_data(convex_polygon_shape, arr);
+
+ body_shape_data[PhysicsServer2D::SHAPE_CONVEX_POLYGON].shape = convex_polygon_shape;
+ }
+ }
+
+ void _do_ray_query() {
+ /*
+ PhysicsServer2D *ps = PhysicsServer2D::get_singleton();
+ ps->query_intersection_segment(ray_query,ray_from,ray_to);
+ */
+ }
+
+protected:
+ void input_event(const Ref<InputEvent> &p_event) {
+ Ref<InputEventMouseButton> mb = p_event;
+
+ if (mb.is_valid()) {
+ if (mb->is_pressed()) {
+ Point2 p(mb->get_position().x, mb->get_position().y);
+
+ if (mb->get_button_index() == 1) {
+ ray_to = p;
+ _do_ray_query();
+ } else if (mb->get_button_index() == 2) {
+ ray_from = p;
+ _do_ray_query();
+ }
+ }
+ }
+
+ Ref<InputEventMouseMotion> mm = p_event;
+
+ if (mm.is_valid()) {
+ Point2 p = mm->get_position();
+
+ if (mm->get_button_mask() & BUTTON_MASK_LEFT) {
+ ray_to = p;
+ _do_ray_query();
+ } else if (mm->get_button_mask() & BUTTON_MASK_RIGHT) {
+ ray_from = p;
+ _do_ray_query();
+ }
+ }
+ }
+
+ RID _add_body(PhysicsServer2D::ShapeType p_shape, const Transform2D &p_xform) {
+ RenderingServer *vs = RenderingServer::get_singleton();
+ PhysicsServer2D *ps = PhysicsServer2D::get_singleton();
+
+ RID body = ps->body_create();
+ ps->body_add_shape(body, body_shape_data[p_shape].shape);
+ ps->body_set_space(body, space);
+ ps->body_set_continuous_collision_detection_mode(body, PhysicsServer2D::CCD_MODE_CAST_SHAPE);
+ ps->body_set_state(body, PhysicsServer2D::BODY_STATE_TRANSFORM, p_xform);
+
+ //print_line("add body with xform: "+p_xform);
+ RID sprite = vs->canvas_item_create();
+ vs->canvas_item_set_parent(sprite, canvas);
+ vs->canvas_item_set_transform(sprite, p_xform);
+ Size2 imgsize(5, 5); //vs->texture_get_width(body_shape_data[p_shape].image), vs->texture_get_height(body_shape_data[p_shape].image));
+ vs->canvas_item_add_texture_rect(sprite, Rect2(-imgsize / 2.0, imgsize), body_shape_data[p_shape].image);
+
+ ps->body_set_force_integration_callback(body, this, "_body_moved", sprite);
+ //RID q = ps->query_create(this,"_body_moved",sprite);
+ //ps->query_body_state(q,body);
+
+ return body;
+ }
+
+ void _add_plane(const Vector2 &p_normal, real_t p_d) {
+ PhysicsServer2D *ps = PhysicsServer2D::get_singleton();
+
+ Array arr;
+ arr.push_back(p_normal);
+ arr.push_back(p_d);
+
+ RID plane = ps->line_shape_create();
+ ps->shape_set_data(plane, arr);
+
+ RID plane_body = ps->body_create();
+ ps->body_set_mode(plane_body, PhysicsServer2D::BODY_MODE_STATIC);
+ ps->body_set_space(plane_body, space);
+ ps->body_add_shape(plane_body, plane);
+ }
+
+ void _add_concave(const Vector<Vector2> &p_points, const Transform2D &p_xform = Transform2D()) {
+ PhysicsServer2D *ps = PhysicsServer2D::get_singleton();
+ RenderingServer *vs = RenderingServer::get_singleton();
+
+ RID concave = ps->concave_polygon_shape_create();
+ ps->shape_set_data(concave, p_points);
+ RID body = ps->body_create();
+ ps->body_set_mode(body, PhysicsServer2D::BODY_MODE_STATIC);
+ ps->body_set_space(body, space);
+ ps->body_add_shape(body, concave);
+ ps->body_set_state(body, PhysicsServer2D::BODY_STATE_TRANSFORM, p_xform);
+
+ RID sprite = vs->canvas_item_create();
+ vs->canvas_item_set_parent(sprite, canvas);
+ vs->canvas_item_set_transform(sprite, p_xform);
+ for (int i = 0; i < p_points.size(); i += 2) {
+ vs->canvas_item_add_line(sprite, p_points[i], p_points[i + 1], Color(0, 0, 0), 2);
+ }
+ }
+
+ void _body_moved(Object *p_state, RID p_sprite) {
+ PhysicsDirectBodyState2D *state = (PhysicsDirectBodyState2D *)p_state;
+ RenderingServer::get_singleton()->canvas_item_set_transform(p_sprite, state->get_transform());
+ }
+
+ void _ray_query_callback(const RID &p_rid, ObjectID p_id, int p_shape, const Vector2 &p_point, const Vector2 &p_normal) {
+ Vector2 ray_end;
+
+ if (p_rid.is_valid()) {
+ ray_end = p_point;
+ } else {
+ ray_end = ray_to;
+ }
+
+ RenderingServer *vs = RenderingServer::get_singleton();
+
+ vs->canvas_item_clear(ray);
+ vs->canvas_item_add_line(ray, ray_from, ray_end, p_rid.is_valid() ? Color(0, 1, 0.4) : Color(1, 0.4, 0), 2);
+ if (p_rid.is_valid()) {
+ vs->canvas_item_add_line(ray, ray_end, ray_end + p_normal * 20, p_rid.is_valid() ? Color(0, 1, 0.4) : Color(1, 0.4, 0), 2);
+ }
+ }
+
+ static void _bind_methods() {
+ ClassDB::bind_method(D_METHOD("_body_moved"), &TestPhysics2DMainLoop::_body_moved);
+ ClassDB::bind_method(D_METHOD("_ray_query_callback"), &TestPhysics2DMainLoop::_ray_query_callback);
+ }
+
+public:
+ virtual void init() override {
+ RenderingServer *vs = RenderingServer::get_singleton();
+ PhysicsServer2D *ps = PhysicsServer2D::get_singleton();
+
+ space = ps->space_create();
+ ps->space_set_active(space, true);
+ ps->set_active(true);
+ ps->area_set_param(space, PhysicsServer2D::AREA_PARAM_GRAVITY_VECTOR, Vector2(0, 1));
+ ps->area_set_param(space, PhysicsServer2D::AREA_PARAM_GRAVITY, 98);
+
+ {
+ RID vp = vs->viewport_create();
+ canvas = vs->canvas_create();
+
+ Size2i screen_size = DisplayServer::get_singleton()->window_get_size();
+ vs->viewport_attach_canvas(vp, canvas);
+ vs->viewport_set_size(vp, screen_size.x, screen_size.y);
+ vs->viewport_attach_to_screen(vp, Rect2(Vector2(), screen_size));
+ vs->viewport_set_active(vp, true);
+
+ Transform2D smaller;
+ //smaller.scale(Vector2(0.6,0.6));
+ //smaller.elements[2]=Vector2(100,0);
+
+ //view_xform = smaller;
+ vs->viewport_set_canvas_transform(vp, canvas, view_xform);
+ }
+
+ ray = vs->canvas_item_create();
+ vs->canvas_item_set_parent(ray, canvas);
+ //ray_query = ps->query_create(this,"_ray_query_callback",Variant());
+ //ps->query_intersection(ray_query,space);
+
+ _create_body_shape_data();
+
+ for (int i = 0; i < 32; i++) {
+ PhysicsServer2D::ShapeType types[4] = {
+ PhysicsServer2D::SHAPE_CIRCLE,
+ PhysicsServer2D::SHAPE_CAPSULE,
+ PhysicsServer2D::SHAPE_RECTANGLE,
+ PhysicsServer2D::SHAPE_CONVEX_POLYGON,
+
+ };
+
+ PhysicsServer2D::ShapeType type = types[i % 4];
+ //type=PhysicsServer2D::SHAPE_SEGMENT;
+ _add_body(type, Transform2D(i * 0.8, Point2(152 + i * 40, 100 - 40 * i)));
+ /*
+ if (i==0)
+ ps->body_set_mode(b,PhysicsServer2D::BODY_MODE_STATIC);
+ */
+ }
+
+ //RID b= _add_body(PhysicsServer2D::SHAPE_CIRCLE,Transform2D(0,Point2(101,140)));
+ //ps->body_set_mode(b,PhysicsServer2D::BODY_MODE_STATIC);
+
+ Point2 prev;
+
+ Vector<Point2> parr;
+ for (int i = 0; i < 30; i++) {
+ Point2 p(i * 60, Math::randf() * 70 + 340);
+ if (i > 0) {
+ parr.push_back(prev);
+ parr.push_back(p);
+ }
+ prev = p;
+ }
+
+ _add_concave(parr);
+ //_add_plane(Vector2(0.0,-1).normalized(),-300);
+ //_add_plane(Vector2(1,0).normalized(),50);
+ //_add_plane(Vector2(-1,0).normalized(),-600);
+ }
+
+ virtual bool idle(float p_time) override {
+ return false;
+ }
+ virtual void finish() override {
+ }
+
+ TestPhysics2DMainLoop() {}
+};
+
+namespace TestPhysics2D {
+
+MainLoop *test() {
+ return memnew(TestPhysics2DMainLoop);
+}
+
+} // namespace TestPhysics2D
diff --git a/tests/test_physics_2d.h b/tests/test_physics_2d.h
new file mode 100644
index 0000000000..517d324f3b
--- /dev/null
+++ b/tests/test_physics_2d.h
@@ -0,0 +1,41 @@
+/*************************************************************************/
+/* test_physics_2d.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_PHYSICS_2D_H
+#define TEST_PHYSICS_2D_H
+
+#include "core/os/main_loop.h"
+
+namespace TestPhysics2D {
+
+MainLoop *test();
+}
+
+#endif // TEST_PHYSICS_2D_H
diff --git a/tests/test_physics_3d.cpp b/tests/test_physics_3d.cpp
new file mode 100644
index 0000000000..72de2041e4
--- /dev/null
+++ b/tests/test_physics_3d.cpp
@@ -0,0 +1,413 @@
+/*************************************************************************/
+/* test_physics_3d.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_physics_3d.h"
+
+#include "core/map.h"
+#include "core/math/math_funcs.h"
+#include "core/math/quick_hull.h"
+#include "core/os/main_loop.h"
+#include "core/os/os.h"
+#include "core/print_string.h"
+#include "servers/display_server.h"
+#include "servers/physics_server_3d.h"
+#include "servers/rendering_server.h"
+
+class TestPhysics3DMainLoop : public MainLoop {
+ GDCLASS(TestPhysics3DMainLoop, MainLoop);
+
+ enum {
+ LINK_COUNT = 20,
+ };
+
+ RID test_cube;
+
+ RID plane;
+ RID sphere;
+ RID light;
+ RID camera;
+ RID mover;
+ RID scenario;
+ RID space;
+
+ RID character;
+
+ float ofs_x, ofs_y;
+
+ Point2 joy_direction;
+
+ List<RID> bodies;
+ Map<PhysicsServer3D::ShapeType, RID> type_shape_map;
+ Map<PhysicsServer3D::ShapeType, RID> type_mesh_map;
+
+ void body_changed_transform(Object *p_state, RID p_visual_instance) {
+ PhysicsDirectBodyState3D *state = (PhysicsDirectBodyState3D *)p_state;
+ RenderingServer *vs = RenderingServer::get_singleton();
+ Transform t = state->get_transform();
+ vs->instance_set_transform(p_visual_instance, t);
+ }
+
+ bool quit;
+
+protected:
+ static void _bind_methods() {
+ ClassDB::bind_method("body_changed_transform", &TestPhysics3DMainLoop::body_changed_transform);
+ }
+
+ RID create_body(PhysicsServer3D::ShapeType p_shape, PhysicsServer3D::BodyMode p_body, const Transform p_location, bool p_active_default = true, const Transform &p_shape_xform = Transform()) {
+ RenderingServer *vs = RenderingServer::get_singleton();
+ PhysicsServer3D *ps = PhysicsServer3D::get_singleton();
+
+ RID mesh_instance = vs->instance_create2(type_mesh_map[p_shape], scenario);
+ RID body = ps->body_create(p_body, !p_active_default);
+ ps->body_set_space(body, space);
+ ps->body_set_param(body, PhysicsServer3D::BODY_PARAM_BOUNCE, 0.0);
+ //todo set space
+ ps->body_add_shape(body, type_shape_map[p_shape]);
+ ps->body_set_force_integration_callback(body, this, "body_changed_transform", mesh_instance);
+
+ ps->body_set_state(body, PhysicsServer3D::BODY_STATE_TRANSFORM, p_location);
+ bodies.push_back(body);
+
+ if (p_body == PhysicsServer3D::BODY_MODE_STATIC) {
+ vs->instance_set_transform(mesh_instance, p_location);
+ }
+ return body;
+ }
+
+ RID create_static_plane(const Plane &p_plane) {
+ PhysicsServer3D *ps = PhysicsServer3D::get_singleton();
+
+ RID world_margin_shape = ps->shape_create(PhysicsServer3D::SHAPE_PLANE);
+ ps->shape_set_data(world_margin_shape, p_plane);
+
+ RID b = ps->body_create(PhysicsServer3D::BODY_MODE_STATIC);
+ ps->body_set_space(b, space);
+ //todo set space
+ ps->body_add_shape(b, world_margin_shape);
+ return b;
+ }
+
+ void configure_body(RID p_body, float p_mass, float p_friction, float p_bounce) {
+ PhysicsServer3D *ps = PhysicsServer3D::get_singleton();
+ ps->body_set_param(p_body, PhysicsServer3D::BODY_PARAM_MASS, p_mass);
+ ps->body_set_param(p_body, PhysicsServer3D::BODY_PARAM_FRICTION, p_friction);
+ ps->body_set_param(p_body, PhysicsServer3D::BODY_PARAM_BOUNCE, p_bounce);
+ }
+
+ void init_shapes() {
+ RenderingServer *vs = RenderingServer::get_singleton();
+ PhysicsServer3D *ps = PhysicsServer3D::get_singleton();
+
+ /* SPHERE SHAPE */
+ RID sphere_mesh = vs->make_sphere_mesh(10, 20, 0.5);
+ type_mesh_map[PhysicsServer3D::SHAPE_SPHERE] = sphere_mesh;
+
+ RID sphere_shape = ps->shape_create(PhysicsServer3D::SHAPE_SPHERE);
+ ps->shape_set_data(sphere_shape, 0.5);
+ type_shape_map[PhysicsServer3D::SHAPE_SPHERE] = sphere_shape;
+
+ /* BOX SHAPE */
+
+ Vector<Plane> box_planes = Geometry3D::build_box_planes(Vector3(0.5, 0.5, 0.5));
+ RID box_mesh = vs->mesh_create();
+ Geometry3D::MeshData box_data = Geometry3D::build_convex_mesh(box_planes);
+ vs->mesh_add_surface_from_mesh_data(box_mesh, box_data);
+ type_mesh_map[PhysicsServer3D::SHAPE_BOX] = box_mesh;
+
+ RID box_shape = ps->shape_create(PhysicsServer3D::SHAPE_BOX);
+ ps->shape_set_data(box_shape, Vector3(0.5, 0.5, 0.5));
+ type_shape_map[PhysicsServer3D::SHAPE_BOX] = box_shape;
+
+ /* CAPSULE SHAPE */
+
+ Vector<Plane> capsule_planes = Geometry3D::build_capsule_planes(0.5, 0.7, 12, Vector3::AXIS_Z);
+
+ RID capsule_mesh = vs->mesh_create();
+ Geometry3D::MeshData capsule_data = Geometry3D::build_convex_mesh(capsule_planes);
+ vs->mesh_add_surface_from_mesh_data(capsule_mesh, capsule_data);
+
+ type_mesh_map[PhysicsServer3D::SHAPE_CAPSULE] = capsule_mesh;
+
+ RID capsule_shape = ps->shape_create(PhysicsServer3D::SHAPE_CAPSULE);
+ Dictionary capsule_params;
+ capsule_params["radius"] = 0.5;
+ capsule_params["height"] = 1.4;
+ ps->shape_set_data(capsule_shape, capsule_params);
+ type_shape_map[PhysicsServer3D::SHAPE_CAPSULE] = capsule_shape;
+
+ /* CONVEX SHAPE */
+
+ Vector<Plane> convex_planes = Geometry3D::build_cylinder_planes(0.5, 0.7, 5, Vector3::AXIS_Z);
+
+ RID convex_mesh = vs->mesh_create();
+ Geometry3D::MeshData convex_data = Geometry3D::build_convex_mesh(convex_planes);
+ QuickHull::build(convex_data.vertices, convex_data);
+ vs->mesh_add_surface_from_mesh_data(convex_mesh, convex_data);
+
+ type_mesh_map[PhysicsServer3D::SHAPE_CONVEX_POLYGON] = convex_mesh;
+
+ RID convex_shape = ps->shape_create(PhysicsServer3D::SHAPE_CONVEX_POLYGON);
+ ps->shape_set_data(convex_shape, convex_data.vertices);
+ type_shape_map[PhysicsServer3D::SHAPE_CONVEX_POLYGON] = convex_shape;
+ }
+
+ void make_trimesh(Vector<Vector3> p_faces, const Transform &p_xform = Transform()) {
+ RenderingServer *vs = RenderingServer::get_singleton();
+ PhysicsServer3D *ps = PhysicsServer3D::get_singleton();
+ RID trimesh_shape = ps->shape_create(PhysicsServer3D::SHAPE_CONCAVE_POLYGON);
+ ps->shape_set_data(trimesh_shape, p_faces);
+ p_faces = ps->shape_get_data(trimesh_shape); // optimized one
+ Vector<Vector3> normals; // for drawing
+ for (int i = 0; i < p_faces.size() / 3; i++) {
+ Plane p(p_faces[i * 3 + 0], p_faces[i * 3 + 1], p_faces[i * 3 + 2]);
+ normals.push_back(p.normal);
+ normals.push_back(p.normal);
+ normals.push_back(p.normal);
+ }
+
+ RID trimesh_mesh = vs->mesh_create();
+ Array d;
+ d.resize(RS::ARRAY_MAX);
+ d[RS::ARRAY_VERTEX] = p_faces;
+ d[RS::ARRAY_NORMAL] = normals;
+ vs->mesh_add_surface_from_arrays(trimesh_mesh, RS::PRIMITIVE_TRIANGLES, d);
+
+ RID triins = vs->instance_create2(trimesh_mesh, scenario);
+
+ RID tribody = ps->body_create(PhysicsServer3D::BODY_MODE_STATIC);
+ ps->body_set_space(tribody, space);
+ //todo set space
+ ps->body_add_shape(tribody, trimesh_shape);
+ Transform tritrans = p_xform;
+ ps->body_set_state(tribody, PhysicsServer3D::BODY_STATE_TRANSFORM, tritrans);
+ vs->instance_set_transform(triins, tritrans);
+ }
+
+ void make_grid(int p_width, int p_height, float p_cellsize, float p_cellheight, const Transform &p_xform = Transform()) {
+ Vector<Vector<float>> grid;
+
+ grid.resize(p_width);
+
+ for (int i = 0; i < p_width; i++) {
+ grid.write[i].resize(p_height);
+
+ for (int j = 0; j < p_height; j++) {
+ grid.write[i].write[j] = 1.0 + Math::random(-p_cellheight, p_cellheight);
+ }
+ }
+
+ Vector<Vector3> faces;
+
+ for (int i = 1; i < p_width; i++) {
+ for (int j = 1; j < p_height; j++) {
+#define MAKE_VERTEX(m_x, m_z) \
+ faces.push_back(Vector3((m_x - p_width / 2) * p_cellsize, grid[m_x][m_z], (m_z - p_height / 2) * p_cellsize))
+
+ MAKE_VERTEX(i, j - 1);
+ MAKE_VERTEX(i, j);
+ MAKE_VERTEX(i - 1, j);
+
+ MAKE_VERTEX(i - 1, j - 1);
+ MAKE_VERTEX(i, j - 1);
+ MAKE_VERTEX(i - 1, j);
+ }
+ }
+
+ make_trimesh(faces, p_xform);
+ }
+
+public:
+ virtual void input_event(const Ref<InputEvent> &p_event) {
+ Ref<InputEventMouseMotion> mm = p_event;
+ if (mm.is_valid() && mm->get_button_mask() & 4) {
+ ofs_y -= mm->get_relative().y / 200.0;
+ ofs_x += mm->get_relative().x / 200.0;
+ }
+
+ if (mm.is_valid() && mm->get_button_mask() & 1) {
+ float y = -mm->get_relative().y / 20.0;
+ float x = mm->get_relative().x / 20.0;
+
+ if (mover.is_valid()) {
+ PhysicsServer3D *ps = PhysicsServer3D::get_singleton();
+ Transform t = ps->body_get_state(mover, PhysicsServer3D::BODY_STATE_TRANSFORM);
+ t.origin += Vector3(x, y, 0);
+
+ ps->body_set_state(mover, PhysicsServer3D::BODY_STATE_TRANSFORM, t);
+ }
+ }
+ }
+
+ virtual void request_quit() {
+ quit = true;
+ }
+ virtual void init() override {
+ ofs_x = ofs_y = 0;
+ init_shapes();
+
+ PhysicsServer3D *ps = PhysicsServer3D::get_singleton();
+ space = ps->space_create();
+ ps->space_set_active(space, true);
+
+ RenderingServer *vs = RenderingServer::get_singleton();
+
+ /* LIGHT */
+ RID lightaux = vs->directional_light_create();
+ scenario = vs->scenario_create();
+ vs->light_set_shadow(lightaux, true);
+ light = vs->instance_create2(lightaux, scenario);
+ Transform t;
+ t.rotate(Vector3(1.0, 0, 0), 0.6);
+ vs->instance_set_transform(light, t);
+
+ /* CAMERA */
+
+ camera = vs->camera_create();
+
+ RID viewport = vs->viewport_create();
+ Size2i screen_size = DisplayServer::get_singleton()->window_get_size();
+ vs->viewport_set_size(viewport, screen_size.x, screen_size.y);
+ vs->viewport_attach_to_screen(viewport, Rect2(Vector2(), screen_size));
+ vs->viewport_set_active(viewport, true);
+ vs->viewport_attach_camera(viewport, camera);
+ vs->viewport_set_scenario(viewport, scenario);
+
+ vs->camera_set_perspective(camera, 60, 0.1, 40.0);
+ vs->camera_set_transform(camera, Transform(Basis(), Vector3(0, 9, 12)));
+
+ Transform gxf;
+ gxf.basis.scale(Vector3(1.4, 0.4, 1.4));
+ gxf.origin = Vector3(-2, 1, -2);
+ make_grid(5, 5, 2.5, 1, gxf);
+ test_fall();
+ quit = false;
+ }
+ virtual bool iteration(float p_time) override {
+ if (mover.is_valid()) {
+ static float joy_speed = 10;
+ PhysicsServer3D *ps = PhysicsServer3D::get_singleton();
+ Transform t = ps->body_get_state(mover, PhysicsServer3D::BODY_STATE_TRANSFORM);
+ t.origin += Vector3(joy_speed * joy_direction.x * p_time, -joy_speed * joy_direction.y * p_time, 0);
+ ps->body_set_state(mover, PhysicsServer3D::BODY_STATE_TRANSFORM, t);
+ };
+
+ Transform cameratr;
+ cameratr.rotate(Vector3(0, 1, 0), ofs_x);
+ cameratr.rotate(Vector3(1, 0, 0), -ofs_y);
+ cameratr.translate(Vector3(0, 2, 8));
+ RenderingServer *vs = RenderingServer::get_singleton();
+ vs->camera_set_transform(camera, cameratr);
+
+ return quit;
+ }
+ virtual void finish() override {
+ }
+
+ void test_joint() {
+ }
+
+ void test_hinge() {
+ }
+
+ void test_character() {
+ RenderingServer *vs = RenderingServer::get_singleton();
+ PhysicsServer3D *ps = PhysicsServer3D::get_singleton();
+
+ Vector<Plane> capsule_planes = Geometry3D::build_capsule_planes(0.5, 1, 12, 5, Vector3::AXIS_Y);
+
+ RID capsule_mesh = vs->mesh_create();
+ Geometry3D::MeshData capsule_data = Geometry3D::build_convex_mesh(capsule_planes);
+ vs->mesh_add_surface_from_mesh_data(capsule_mesh, capsule_data);
+ type_mesh_map[PhysicsServer3D::SHAPE_CAPSULE] = capsule_mesh;
+
+ RID capsule_shape = ps->shape_create(PhysicsServer3D::SHAPE_CAPSULE);
+ Dictionary capsule_params;
+ capsule_params["radius"] = 0.5;
+ capsule_params["height"] = 1;
+ Transform shape_xform;
+ shape_xform.rotate(Vector3(1, 0, 0), Math_PI / 2.0);
+ //shape_xform.origin=Vector3(1,1,1);
+ ps->shape_set_data(capsule_shape, capsule_params);
+
+ RID mesh_instance = vs->instance_create2(capsule_mesh, scenario);
+ character = ps->body_create(PhysicsServer3D::BODY_MODE_CHARACTER);
+ ps->body_set_space(character, space);
+ //todo add space
+ ps->body_add_shape(character, capsule_shape);
+
+ ps->body_set_force_integration_callback(character, this, "body_changed_transform", mesh_instance);
+
+ ps->body_set_state(character, PhysicsServer3D::BODY_STATE_TRANSFORM, Transform(Basis(), Vector3(-2, 5, -2)));
+ bodies.push_back(character);
+ }
+
+ void test_fall() {
+ for (int i = 0; i < 35; i++) {
+ static const PhysicsServer3D::ShapeType shape_idx[] = {
+ PhysicsServer3D::SHAPE_CAPSULE,
+ PhysicsServer3D::SHAPE_BOX,
+ PhysicsServer3D::SHAPE_SPHERE,
+ PhysicsServer3D::SHAPE_CONVEX_POLYGON
+ };
+
+ PhysicsServer3D::ShapeType type = shape_idx[i % 4];
+
+ Transform t;
+
+ t.origin = Vector3(0.0 * i, 3.5 + 1.1 * i, 0.7 + 0.0 * i);
+ t.basis.rotate(Vector3(0.2, -1, 0), Math_PI / 2 * 0.6);
+
+ create_body(type, PhysicsServer3D::BODY_MODE_RIGID, t);
+ }
+
+ create_static_plane(Plane(Vector3(0, 1, 0), -1));
+ }
+
+ void test_activate() {
+ create_body(PhysicsServer3D::SHAPE_BOX, PhysicsServer3D::BODY_MODE_RIGID, Transform(Basis(), Vector3(0, 2, 0)), true);
+ create_static_plane(Plane(Vector3(0, 1, 0), -1));
+ }
+
+ virtual bool idle(float p_time) override {
+ return false;
+ }
+
+ TestPhysics3DMainLoop() {
+ }
+};
+
+namespace TestPhysics3D {
+
+MainLoop *test() {
+ return memnew(TestPhysics3DMainLoop);
+}
+
+} // namespace TestPhysics3D
diff --git a/tests/test_physics_3d.h b/tests/test_physics_3d.h
new file mode 100644
index 0000000000..d03f2c6573
--- /dev/null
+++ b/tests/test_physics_3d.h
@@ -0,0 +1,41 @@
+/*************************************************************************/
+/* test_physics_3d.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_PHYSICS_H
+#define TEST_PHYSICS_H
+
+#include "core/os/main_loop.h"
+
+namespace TestPhysics3D {
+
+MainLoop *test();
+}
+
+#endif
diff --git a/tests/test_render.cpp b/tests/test_render.cpp
new file mode 100644
index 0000000000..d936dd72e7
--- /dev/null
+++ b/tests/test_render.cpp
@@ -0,0 +1,240 @@
+/*************************************************************************/
+/* test_render.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_render.h"
+
+#include "core/math/math_funcs.h"
+#include "core/math/quick_hull.h"
+#include "core/os/keyboard.h"
+#include "core/os/main_loop.h"
+#include "core/os/os.h"
+#include "core/print_string.h"
+#include "servers/display_server.h"
+#include "servers/rendering_server.h"
+
+#define OBJECT_COUNT 50
+
+namespace TestRender {
+
+class TestMainLoop : public MainLoop {
+ RID test_cube;
+ RID instance;
+ RID camera;
+ RID viewport;
+ RID light;
+ RID scenario;
+
+ struct InstanceInfo {
+ RID instance;
+ Transform base;
+ Vector3 rot_axis;
+ };
+
+ List<InstanceInfo> instances;
+
+ float ofs;
+ bool quit;
+
+protected:
+public:
+ virtual void input_event(const Ref<InputEvent> &p_event) {
+ if (p_event->is_pressed()) {
+ quit = true;
+ }
+ }
+
+ virtual void init() {
+ print_line("INITIALIZING TEST RENDER");
+ RenderingServer *vs = RenderingServer::get_singleton();
+ test_cube = vs->get_test_cube();
+ scenario = vs->scenario_create();
+
+ Vector<Vector3> vts;
+
+ /*
+ Vector<Plane> sp = Geometry3D::build_sphere_planes(2,5,5);
+ Geometry3D::MeshData md2 = Geometry3D::build_convex_mesh(sp);
+ vts=md2.vertices;
+*/
+ /*
+
+ static const int s = 20;
+ for(int i=0;i<s;i++) {
+ Basis rot(Vector3(0,1,0),i*Math_PI/s);
+
+ for(int j=0;j<s;j++) {
+ Vector3 v;
+ v.x=Math::sin(j*Math_PI*2/s);
+ v.y=Math::cos(j*Math_PI*2/s);
+
+ vts.push_back( rot.xform(v*2 ) );
+ }
+ }*/
+ /*for(int i=0;i<100;i++) {
+
+ vts.push_back( Vector3(Math::randf()*2-1.0,Math::randf()*2-1.0,Math::randf()*2-1.0).normalized()*2);
+ }*/
+ /*
+ vts.push_back(Vector3(0,0,1));
+ vts.push_back(Vector3(0,0,-1));
+ vts.push_back(Vector3(0,1,0));
+ vts.push_back(Vector3(0,-1,0));
+ vts.push_back(Vector3(1,0,0));
+ vts.push_back(Vector3(-1,0,0));*/
+
+ vts.push_back(Vector3(1, 1, 1));
+ vts.push_back(Vector3(1, -1, 1));
+ vts.push_back(Vector3(-1, 1, 1));
+ vts.push_back(Vector3(-1, -1, 1));
+ vts.push_back(Vector3(1, 1, -1));
+ vts.push_back(Vector3(1, -1, -1));
+ vts.push_back(Vector3(-1, 1, -1));
+ vts.push_back(Vector3(-1, -1, -1));
+
+ Geometry3D::MeshData md;
+ Error err = QuickHull::build(vts, md);
+ print_line("ERR: " + itos(err));
+ test_cube = vs->mesh_create();
+ vs->mesh_add_surface_from_mesh_data(test_cube, md);
+ //vs->scenario_set_debug(scenario,RS::SCENARIO_DEBUG_WIREFRAME);
+
+ /*
+ RID sm = vs->shader_create();
+ //vs->shader_set_fragment_code(sm,"OUT_ALPHA=mod(TIME,1);");
+ //vs->shader_set_vertex_code(sm,"OUT_VERTEX=IN_VERTEX*mod(TIME,1);");
+ vs->shader_set_fragment_code(sm,"OUT_DIFFUSE=vec3(1,0,1);OUT_GLOW=abs(sin(TIME));");
+ RID tcmat = vs->mesh_surface_get_material(test_cube,0);
+ vs->material_set_shader(tcmat,sm);
+ */
+
+ List<String> cmdline = OS::get_singleton()->get_cmdline_args();
+ int object_count = OBJECT_COUNT;
+ if (cmdline.size() > 0 && cmdline[cmdline.size() - 1].to_int()) {
+ object_count = cmdline[cmdline.size() - 1].to_int();
+ };
+
+ for (int i = 0; i < object_count; i++) {
+ InstanceInfo ii;
+
+ ii.instance = vs->instance_create2(test_cube, scenario);
+
+ ii.base.translate(Math::random(-20, 20), Math::random(-20, 20), Math::random(-20, 18));
+ ii.base.rotate(Vector3(0, 1, 0), Math::randf() * Math_PI);
+ ii.base.rotate(Vector3(1, 0, 0), Math::randf() * Math_PI);
+ vs->instance_set_transform(ii.instance, ii.base);
+
+ ii.rot_axis = Vector3(Math::random(-1, 1), Math::random(-1, 1), Math::random(-1, 1)).normalized();
+
+ instances.push_back(ii);
+ }
+
+ camera = vs->camera_create();
+
+ // vs->camera_set_perspective( camera, 60.0,0.1, 100.0 );
+
+ viewport = vs->viewport_create();
+ Size2i screen_size = DisplayServer::get_singleton()->window_get_size();
+ vs->viewport_set_size(viewport, screen_size.x, screen_size.y);
+ vs->viewport_attach_to_screen(viewport, Rect2(Vector2(), screen_size));
+ vs->viewport_set_active(viewport, true);
+ vs->viewport_attach_camera(viewport, camera);
+ vs->viewport_set_scenario(viewport, scenario);
+ vs->camera_set_transform(camera, Transform(Basis(), Vector3(0, 3, 30)));
+ vs->camera_set_perspective(camera, 60, 0.1, 1000);
+
+ /*
+ RID lightaux = vs->light_create( RenderingServer::LIGHT_OMNI );
+ vs->light_set_var( lightaux, RenderingServer::LIGHT_VAR_RADIUS, 80 );
+ vs->light_set_var( lightaux, RenderingServer::LIGHT_VAR_ATTENUATION, 1 );
+ vs->light_set_var( lightaux, RenderingServer::LIGHT_VAR_ENERGY, 1.5 );
+ light = vs->instance_create( lightaux );
+ */
+ RID lightaux;
+
+ lightaux = vs->directional_light_create();
+ //vs->light_set_color( lightaux, RenderingServer::LIGHT_COLOR_AMBIENT, Color(0.0,0.0,0.0) );
+ vs->light_set_color(lightaux, Color(1.0, 1.0, 1.0));
+ //vs->light_set_shadow( lightaux, true );
+ light = vs->instance_create2(lightaux, scenario);
+ Transform lla;
+ //lla.set_look_at(Vector3(),Vector3(1,-1,1),Vector3(0,1,0));
+ lla.set_look_at(Vector3(), Vector3(-0.000000, -0.836026, -0.548690), Vector3(0, 1, 0));
+
+ vs->instance_set_transform(light, lla);
+
+ lightaux = vs->omni_light_create();
+ //vs->light_set_color( lightaux, RenderingServer::LIGHT_COLOR_AMBIENT, Color(0.0,0.0,1.0) );
+ vs->light_set_color(lightaux, Color(1.0, 1.0, 0.0));
+ vs->light_set_param(lightaux, RenderingServer::LIGHT_PARAM_RANGE, 4);
+ vs->light_set_param(lightaux, RenderingServer::LIGHT_PARAM_ENERGY, 8);
+ //vs->light_set_shadow( lightaux, true );
+ //light = vs->instance_create( lightaux );
+
+ ofs = 0;
+ quit = false;
+ }
+ virtual bool iteration(float p_time) {
+ RenderingServer *vs = RenderingServer::get_singleton();
+ //Transform t;
+ //t.rotate(Vector3(0, 1, 0), ofs);
+ //t.translate(Vector3(0,0,20 ));
+ //vs->camera_set_transform(camera, t);
+
+ ofs += p_time * 0.05;
+
+ //return quit;
+
+ for (List<InstanceInfo>::Element *E = instances.front(); E; E = E->next()) {
+ Transform pre(Basis(E->get().rot_axis, ofs), Vector3());
+ vs->instance_set_transform(E->get().instance, pre * E->get().base);
+ /*
+ if( !E->next() ) {
+
+ vs->free( E->get().instance );
+ instances.erase(E );
+ }*/
+ }
+
+ return quit;
+ }
+
+ virtual bool idle(float p_time) {
+ return quit;
+ }
+
+ virtual void finish() {
+ }
+};
+
+MainLoop *test() {
+ return memnew(TestMainLoop);
+}
+
+} // namespace TestRender
diff --git a/tests/test_render.h b/tests/test_render.h
new file mode 100644
index 0000000000..4a6340c443
--- /dev/null
+++ b/tests/test_render.h
@@ -0,0 +1,41 @@
+/*************************************************************************/
+/* test_render.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_RENDER_H
+#define TEST_RENDER_H
+
+#include "core/os/main_loop.h"
+
+namespace TestRender {
+
+MainLoop *test();
+}
+
+#endif
diff --git a/tests/test_shader_lang.cpp b/tests/test_shader_lang.cpp
new file mode 100644
index 0000000000..34ee3e3210
--- /dev/null
+++ b/tests/test_shader_lang.cpp
@@ -0,0 +1,361 @@
+/*************************************************************************/
+/* test_shader_lang.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_shader_lang.h"
+
+#include "core/os/file_access.h"
+#include "core/os/main_loop.h"
+#include "core/os/os.h"
+
+#include "core/print_string.h"
+#include "scene/gui/control.h"
+#include "scene/gui/text_edit.h"
+#include "servers/rendering/shader_language.h"
+
+typedef ShaderLanguage SL;
+
+namespace TestShaderLang {
+
+static String _mktab(int p_level) {
+ String tb;
+ for (int i = 0; i < p_level; i++) {
+ tb += "\t";
+ }
+
+ return tb;
+}
+
+static String _typestr(SL::DataType p_type) {
+ return ShaderLanguage::get_datatype_name(p_type);
+}
+
+static String _prestr(SL::DataPrecision p_pres) {
+ switch (p_pres) {
+ case SL::PRECISION_LOWP:
+ return "lowp ";
+ case SL::PRECISION_MEDIUMP:
+ return "mediump ";
+ case SL::PRECISION_HIGHP:
+ return "highp ";
+ case SL::PRECISION_DEFAULT:
+ return "";
+ }
+ return "";
+}
+
+static String _opstr(SL::Operator p_op) {
+ return ShaderLanguage::get_operator_text(p_op);
+}
+
+static String get_constant_text(SL::DataType p_type, const Vector<SL::ConstantNode::Value> &p_values) {
+ switch (p_type) {
+ case SL::TYPE_BOOL:
+ return p_values[0].boolean ? "true" : "false";
+ case SL::TYPE_BVEC2:
+ return String() + "bvec2(" + (p_values[0].boolean ? "true" : "false") + (p_values[1].boolean ? "true" : "false") + ")";
+ case SL::TYPE_BVEC3:
+ return String() + "bvec3(" + (p_values[0].boolean ? "true" : "false") + "," + (p_values[1].boolean ? "true" : "false") + "," + (p_values[2].boolean ? "true" : "false") + ")";
+ case SL::TYPE_BVEC4:
+ return String() + "bvec4(" + (p_values[0].boolean ? "true" : "false") + "," + (p_values[1].boolean ? "true" : "false") + "," + (p_values[2].boolean ? "true" : "false") + "," + (p_values[3].boolean ? "true" : "false") + ")";
+ case SL::TYPE_INT:
+ return rtos(p_values[0].sint);
+ case SL::TYPE_IVEC2:
+ return String() + "ivec2(" + rtos(p_values[0].sint) + "," + rtos(p_values[1].sint) + ")";
+ case SL::TYPE_IVEC3:
+ return String() + "ivec3(" + rtos(p_values[0].sint) + "," + rtos(p_values[1].sint) + "," + rtos(p_values[2].sint) + ")";
+ case SL::TYPE_IVEC4:
+ return String() + "ivec4(" + rtos(p_values[0].sint) + "," + rtos(p_values[1].sint) + "," + rtos(p_values[2].sint) + "," + rtos(p_values[3].sint) + ")";
+ case SL::TYPE_UINT:
+ return rtos(p_values[0].real);
+ case SL::TYPE_UVEC2:
+ return String() + "uvec2(" + rtos(p_values[0].real) + "," + rtos(p_values[1].real) + ")";
+ case SL::TYPE_UVEC3:
+ return String() + "uvec3(" + rtos(p_values[0].real) + "," + rtos(p_values[1].real) + "," + rtos(p_values[2].real) + ")";
+ case SL::TYPE_UVEC4:
+ return String() + "uvec4(" + rtos(p_values[0].real) + "," + rtos(p_values[1].real) + "," + rtos(p_values[2].real) + "," + rtos(p_values[3].real) + ")";
+ case SL::TYPE_FLOAT:
+ return rtos(p_values[0].real);
+ case SL::TYPE_VEC2:
+ return String() + "vec2(" + rtos(p_values[0].real) + "," + rtos(p_values[1].real) + ")";
+ case SL::TYPE_VEC3:
+ return String() + "vec3(" + rtos(p_values[0].real) + "," + rtos(p_values[1].real) + "," + rtos(p_values[2].real) + ")";
+ case SL::TYPE_VEC4:
+ return String() + "vec4(" + rtos(p_values[0].real) + "," + rtos(p_values[1].real) + "," + rtos(p_values[2].real) + "," + rtos(p_values[3].real) + ")";
+ default:
+ ERR_FAIL_V(String());
+ }
+}
+
+static String dump_node_code(SL::Node *p_node, int p_level) {
+ String code;
+
+ switch (p_node->type) {
+ case SL::Node::TYPE_SHADER: {
+ SL::ShaderNode *pnode = (SL::ShaderNode *)p_node;
+
+ for (Map<StringName, SL::ShaderNode::Uniform>::Element *E = pnode->uniforms.front(); E; E = E->next()) {
+ String ucode = "uniform ";
+ ucode += _prestr(E->get().precision);
+ ucode += _typestr(E->get().type);
+ ucode += " " + String(E->key());
+
+ if (E->get().default_value.size()) {
+ ucode += " = " + get_constant_text(E->get().type, E->get().default_value);
+ }
+
+ static const char *hint_name[SL::ShaderNode::Uniform::HINT_MAX] = {
+ "",
+ "color",
+ "range",
+ "albedo",
+ "normal",
+ "black",
+ "white"
+ };
+
+ if (E->get().hint) {
+ ucode += " : " + String(hint_name[E->get().hint]);
+ }
+
+ code += ucode + "\n";
+ }
+
+ for (Map<StringName, SL::ShaderNode::Varying>::Element *E = pnode->varyings.front(); E; E = E->next()) {
+ String vcode = "varying ";
+ vcode += _prestr(E->get().precision);
+ vcode += _typestr(E->get().type);
+ vcode += " " + String(E->key());
+
+ code += vcode + "\n";
+ }
+ for (int i = 0; i < pnode->functions.size(); i++) {
+ SL::FunctionNode *fnode = pnode->functions[i].function;
+
+ String header;
+ header = _typestr(fnode->return_type) + " " + fnode->name + "(";
+ for (int j = 0; j < fnode->arguments.size(); j++) {
+ if (j > 0) {
+ header += ", ";
+ }
+ header += _prestr(fnode->arguments[j].precision) + _typestr(fnode->arguments[j].type) + " " + fnode->arguments[j].name;
+ }
+
+ header += ")\n";
+ code += header;
+ code += dump_node_code(fnode->body, p_level + 1);
+ }
+
+ //code+=dump_node_code(pnode->body,p_level);
+ } break;
+ case SL::Node::TYPE_STRUCT: {
+ } break;
+ case SL::Node::TYPE_FUNCTION: {
+ } break;
+ case SL::Node::TYPE_BLOCK: {
+ SL::BlockNode *bnode = (SL::BlockNode *)p_node;
+
+ //variables
+ code += _mktab(p_level - 1) + "{\n";
+ for (Map<StringName, SL::BlockNode::Variable>::Element *E = bnode->variables.front(); E; E = E->next()) {
+ code += _mktab(p_level) + _prestr(E->get().precision) + _typestr(E->get().type) + " " + E->key() + ";\n";
+ }
+
+ for (int i = 0; i < bnode->statements.size(); i++) {
+ String scode = dump_node_code(bnode->statements[i], p_level);
+
+ if (bnode->statements[i]->type == SL::Node::TYPE_CONTROL_FLOW) {
+ code += scode; //use directly
+ } else {
+ code += _mktab(p_level) + scode + ";\n";
+ }
+ }
+ code += _mktab(p_level - 1) + "}\n";
+
+ } break;
+ case SL::Node::TYPE_VARIABLE: {
+ SL::VariableNode *vnode = (SL::VariableNode *)p_node;
+ code = vnode->name;
+
+ } break;
+ case SL::Node::TYPE_VARIABLE_DECLARATION: {
+ // FIXME: Implement
+ } break;
+ case SL::Node::TYPE_ARRAY: {
+ SL::ArrayNode *vnode = (SL::ArrayNode *)p_node;
+ code = vnode->name;
+ } break;
+ case SL::Node::TYPE_ARRAY_DECLARATION: {
+ // FIXME: Implement
+ } break;
+ case SL::Node::TYPE_ARRAY_CONSTRUCT: {
+ // FIXME: Implement
+ } break;
+ case SL::Node::TYPE_CONSTANT: {
+ SL::ConstantNode *cnode = (SL::ConstantNode *)p_node;
+ return get_constant_text(cnode->datatype, cnode->values);
+
+ } break;
+ case SL::Node::TYPE_OPERATOR: {
+ SL::OperatorNode *onode = (SL::OperatorNode *)p_node;
+
+ switch (onode->op) {
+ case SL::OP_ASSIGN:
+ case SL::OP_ASSIGN_ADD:
+ case SL::OP_ASSIGN_SUB:
+ case SL::OP_ASSIGN_MUL:
+ case SL::OP_ASSIGN_DIV:
+ case SL::OP_ASSIGN_SHIFT_LEFT:
+ case SL::OP_ASSIGN_SHIFT_RIGHT:
+ case SL::OP_ASSIGN_MOD:
+ case SL::OP_ASSIGN_BIT_AND:
+ case SL::OP_ASSIGN_BIT_OR:
+ case SL::OP_ASSIGN_BIT_XOR:
+ code = dump_node_code(onode->arguments[0], p_level) + _opstr(onode->op) + dump_node_code(onode->arguments[1], p_level);
+ break;
+ case SL::OP_BIT_INVERT:
+ case SL::OP_NEGATE:
+ case SL::OP_NOT:
+ case SL::OP_DECREMENT:
+ case SL::OP_INCREMENT:
+ code = _opstr(onode->op) + dump_node_code(onode->arguments[0], p_level);
+ break;
+ case SL::OP_POST_DECREMENT:
+ case SL::OP_POST_INCREMENT:
+ code = dump_node_code(onode->arguments[0], p_level) + _opstr(onode->op);
+ break;
+ case SL::OP_CALL:
+ case SL::OP_CONSTRUCT:
+ code = dump_node_code(onode->arguments[0], p_level) + "(";
+ for (int i = 1; i < onode->arguments.size(); i++) {
+ if (i > 1) {
+ code += ", ";
+ }
+ code += dump_node_code(onode->arguments[i], p_level);
+ }
+ code += ")";
+ break;
+ default: {
+ code = "(" + dump_node_code(onode->arguments[0], p_level) + _opstr(onode->op) + dump_node_code(onode->arguments[1], p_level) + ")";
+ break;
+ }
+ }
+
+ } break;
+ case SL::Node::TYPE_CONTROL_FLOW: {
+ SL::ControlFlowNode *cfnode = (SL::ControlFlowNode *)p_node;
+ if (cfnode->flow_op == SL::FLOW_OP_IF) {
+ code += _mktab(p_level) + "if (" + dump_node_code(cfnode->expressions[0], p_level) + ")\n";
+ code += dump_node_code(cfnode->blocks[0], p_level + 1);
+ if (cfnode->blocks.size() == 2) {
+ code += _mktab(p_level) + "else\n";
+ code += dump_node_code(cfnode->blocks[1], p_level + 1);
+ }
+
+ } else if (cfnode->flow_op == SL::FLOW_OP_RETURN) {
+ if (cfnode->blocks.size()) {
+ code = "return " + dump_node_code(cfnode->blocks[0], p_level);
+ } else {
+ code = "return";
+ }
+ }
+
+ } break;
+ case SL::Node::TYPE_MEMBER: {
+ SL::MemberNode *mnode = (SL::MemberNode *)p_node;
+ code = dump_node_code(mnode->owner, p_level) + "." + mnode->name;
+
+ } break;
+ }
+
+ return code;
+}
+
+static Error recreate_code(void *p_str, SL::ShaderNode *p_program) {
+ String *str = (String *)p_str;
+
+ *str = dump_node_code(p_program, 0);
+
+ return OK;
+}
+
+MainLoop *test() {
+ List<String> cmdlargs = OS::get_singleton()->get_cmdline_args();
+
+ if (cmdlargs.empty()) {
+ //try editor!
+ print_line("usage: godot -test shader_lang <shader>");
+ return nullptr;
+ }
+
+ String test = cmdlargs.back()->get();
+
+ FileAccess *fa = FileAccess::open(test, FileAccess::READ);
+
+ if (!fa) {
+ ERR_FAIL_V(nullptr);
+ }
+
+ String code;
+
+ while (true) {
+ CharType c = fa->get_8();
+ if (fa->eof_reached()) {
+ break;
+ }
+ code += c;
+ }
+
+ SL sl;
+ print_line("tokens:\n\n" + sl.token_debug(code));
+
+ Map<StringName, SL::FunctionInfo> dt;
+ dt["fragment"].built_ins["ALBEDO"] = SL::TYPE_VEC3;
+ dt["fragment"].can_discard = true;
+
+ Vector<StringName> rm;
+ rm.push_back("popo");
+ Set<String> types;
+ types.insert("spatial");
+
+ Error err = sl.compile(code, dt, rm, types, nullptr);
+
+ if (err) {
+ print_line("Error at line: " + rtos(sl.get_error_line()) + ": " + sl.get_error_text());
+ return nullptr;
+ } else {
+ String code2;
+ recreate_code(&code2, sl.get_shader());
+ print_line("code:\n\n" + code2);
+ }
+
+ return nullptr;
+}
+
+} // namespace TestShaderLang
diff --git a/tests/test_shader_lang.h b/tests/test_shader_lang.h
new file mode 100644
index 0000000000..2811c5f46e
--- /dev/null
+++ b/tests/test_shader_lang.h
@@ -0,0 +1,41 @@
+/*************************************************************************/
+/* test_shader_lang.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_SHADER_LANG_H
+#define TEST_SHADER_LANG_H
+
+#include "core/os/main_loop.h"
+
+namespace TestShaderLang {
+
+MainLoop *test();
+}
+
+#endif // TEST_SHADER_LANG_H
diff --git a/tests/test_string.h b/tests/test_string.h
new file mode 100644
index 0000000000..25fd513a1a
--- /dev/null
+++ b/tests/test_string.h
@@ -0,0 +1,798 @@
+/*************************************************************************/
+/* test_string.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_STRING_H
+#define TEST_STRING_H
+
+#include <inttypes.h>
+#include <stdio.h>
+#include <wchar.h>
+
+#include "core/io/ip_address.h"
+#include "core/os/main_loop.h"
+#include "core/os/os.h"
+#include "core/ustring.h"
+
+#ifdef MODULE_REGEX_ENABLED
+#include "modules/regex/regex.h"
+#endif
+
+#include "thirdparty/doctest/doctest.h"
+
+namespace TestString {
+
+TEST_CASE("[String] Assign from cstr") {
+ String s = "Hello";
+ CHECK(wcscmp(s.c_str(), L"Hello") == 0);
+}
+
+TEST_CASE("[String] Assign from string (operator=)") {
+ String s = "Dolly";
+ const String &t = s;
+ CHECK(wcscmp(t.c_str(), L"Dolly") == 0);
+}
+
+TEST_CASE("[String] Assign from c-string (copycon)") {
+ String s("Sheep");
+ const String &t(s);
+ CHECK(wcscmp(t.c_str(), L"Sheep") == 0);
+}
+
+TEST_CASE("[String] Assign from c-widechar (operator=)") {
+ String s(L"Give me");
+ CHECK(wcscmp(s.c_str(), L"Give me") == 0);
+}
+
+TEST_CASE("[String] Assign from c-widechar (copycon)") {
+ String s(L"Wool");
+ CHECK(wcscmp(s.c_str(), L"Wool") == 0);
+}
+
+TEST_CASE("[String] Comparisons (equal)") {
+ String s = "Test Compare";
+ CHECK(s == "Test Compare");
+ CHECK(s == L"Test Compare");
+ CHECK(s == String("Test Compare"));
+}
+
+TEST_CASE("[String] Comparisons (not equal)") {
+ String s = "Test Compare";
+ CHECK(s != "Peanut");
+ CHECK(s != L"Coconut");
+ CHECK(s != String("Butter"));
+}
+
+TEST_CASE("[String] Comparisons (operator <)") {
+ String s = "Bees";
+ CHECK(s < "Elephant");
+ CHECK(!(s < L"Amber"));
+ CHECK(!(s < String("Beatrix")));
+}
+
+TEST_CASE("[String] Concatenation") {
+ String s;
+
+ s += "Have";
+ s += ' ';
+ s += 'a';
+ s += String(" ");
+ s = s + L"Nice";
+ s = s + " ";
+ s = s + String("Day");
+
+ CHECK(s == "Have a Nice Day");
+}
+
+TEST_CASE("[String] Testing size and length of string") {
+ // todo: expand this test to do more tests on size() as it is complicated under the hood.
+ CHECK(String("Mellon").size() == 7);
+ CHECK(String("Mellon1").size() == 8);
+
+ // length works fine and is easier to test
+ CHECK(String("Mellon").length() == 6);
+ CHECK(String("Mellon1").length() == 7);
+ CHECK(String("Mellon2").length() == 7);
+ CHECK(String("Mellon3").length() == 7);
+}
+
+TEST_CASE("[String] Testing for empty string") {
+ CHECK(!String("Mellon").empty());
+ // do this more than once, to check for string corruption
+ CHECK(String("").empty());
+ CHECK(String("").empty());
+ CHECK(String("").empty());
+}
+
+TEST_CASE("[String] Operator []") {
+ String a = "Kugar Sane";
+ a[0] = 'S';
+ a[6] = 'C';
+ CHECK(a == "Sugar Cane");
+ CHECK(a[1] == 'u');
+}
+
+TEST_CASE("[String] Case function test") {
+ String a = "MoMoNgA";
+
+ CHECK(a.to_upper() == "MOMONGA");
+ CHECK(a.nocasecmp_to("momonga") == 0);
+}
+
+TEST_CASE("[String] UTF8") {
+ /* how can i embed UTF in here? */
+ static const CharType ustr[] = { 0x304A, 0x360F, 0x3088, 0x3046, 0 };
+ //static const wchar_t ustr[] = { 'P', 0xCE, 'p',0xD3, 0 };
+ String s = ustr;
+ s.parse_utf8(s.utf8().get_data());
+ CHECK(s == ustr);
+}
+
+TEST_CASE("[String] ASCII") {
+ String s = L"Primero Leche";
+ String t = s.ascii().get_data();
+ CHECK(s == t);
+}
+
+TEST_CASE("[String] Substr") {
+ String s = "Killer Baby";
+ CHECK(s.substr(3, 4) == "ler ");
+}
+
+TEST_CASE("[string] Find") {
+ String s = "Pretty Woman";
+ s.find("Revenge of the Monster Truck");
+
+ CHECK(s.find("tty") == 3);
+ CHECK(s.find("Revenge of the Monster Truck") == -1);
+}
+
+TEST_CASE("[String] find no case") {
+ String s = "Pretty Whale";
+ CHECK(s.findn("WHA") == 7);
+ CHECK(s.findn("Revenge of the Monster SawFish") == -1);
+}
+
+TEST_CASE("[String] Find and replace") {
+ String s = "Happy Birthday, Anna!";
+ s = s.replace("Birthday", "Halloween");
+ CHECK(s == "Happy Halloween, Anna!");
+}
+
+TEST_CASE("[String] Insertion") {
+ String s = "Who is Frederic?";
+ s = s.insert(s.find("?"), " Chopin");
+ CHECK(s == "Who is Frederic Chopin?");
+}
+
+TEST_CASE("[String] Number to string") {
+ CHECK(String::num(3.141593) == "3.141593");
+}
+
+TEST_CASE("[String] String to integer") {
+ static const char *nums[4] = { "1237461283", "- 22", "0", " - 1123412" };
+ static const int num[4] = { 1237461283, -22, 0, -1123412 };
+
+ for (int i = 0; i < 4; i++) {
+ CHECK(String(nums[i]).to_int() == num[i]);
+ }
+}
+
+TEST_CASE("[String] String to float") {
+ static const char *nums[4] = { "-12348298412.2", "0.05", "2.0002", " -0.0001" };
+ static const double num[4] = { -12348298412.2, 0.05, 2.0002, -0.0001 };
+
+ for (int i = 0; i < 4; i++) {
+ CHECK(!(ABS(String(nums[i]).to_double() - num[i]) > 0.00001));
+ }
+}
+
+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]);
+ }
+}
+
+TEST_CASE("[String] Erasing") {
+ String s = "Josephine is such a cute girl!";
+ s.erase(s.find("cute "), String("cute ").length());
+ CHECK(s == "Josephine is such a girl!");
+}
+
+#ifdef MODULE_REGEX_ENABLED
+TEST_CASE("[String] Regex substitution") {
+ String s = "Double all the vowels.";
+ RegEx re("(?<vowel>[aeiou])");
+ s = re.sub(s, "$0$vowel", true);
+ CHECK(s == "Doouublee aall thee vooweels.");
+}
+#endif
+
+struct test_27_data {
+ char const *data;
+ char const *begin;
+ bool expected;
+};
+
+TEST_CASE("[String] Begins with") {
+ test_27_data tc[] = {
+ { "res://foobar", "res://", true },
+ { "res", "res://", false },
+ { "abc", "abc", true }
+ };
+ size_t count = sizeof(tc) / sizeof(tc[0]);
+ bool state = true;
+ for (size_t i = 0; state && i < count; ++i) {
+ String s = tc[i].data;
+ state = s.begins_with(tc[i].begin) == tc[i].expected;
+ if (state) {
+ String sb = tc[i].begin;
+ state = s.begins_with(sb) == tc[i].expected;
+ }
+ CHECK(state);
+ if (!state) {
+ break;
+ }
+ };
+ CHECK(state);
+}
+
+TEST_CASE("[String] sprintf") {
+ String format, output;
+ Array args;
+ bool error;
+
+ // %%
+ format = "fish %% frog";
+ args.clear();
+ output = format.sprintf(args, &error);
+ REQUIRE(error == false);
+ CHECK(output == String("fish % frog"));
+ //////// INTS
+
+ // Int
+ format = "fish %d frog";
+ args.clear();
+ args.push_back(5);
+ output = format.sprintf(args, &error);
+ REQUIRE(error == false);
+ CHECK(output == String("fish 5 frog"));
+
+ // Int left padded with zeroes.
+ format = "fish %05d frog";
+ args.clear();
+ args.push_back(5);
+ output = format.sprintf(args, &error);
+ REQUIRE(error == false);
+ CHECK(output == String("fish 00005 frog"));
+
+ // Int left padded with spaces.
+ format = "fish %5d frog";
+ args.clear();
+ args.push_back(5);
+ output = format.sprintf(args, &error);
+ REQUIRE(error == false);
+ CHECK(output == String("fish 5 frog"));
+
+ // Int right padded with spaces.
+ format = "fish %-5d frog";
+ args.clear();
+ args.push_back(5);
+ output = format.sprintf(args, &error);
+ REQUIRE(error == false);
+ CHECK(output == String("fish 5 frog"));
+
+ // Int with sign (positive).
+ format = "fish %+d frog";
+ args.clear();
+ args.push_back(5);
+ output = format.sprintf(args, &error);
+ REQUIRE(error == false);
+ CHECK(output == String("fish +5 frog"));
+
+ // Negative int.
+ format = "fish %d frog";
+ args.clear();
+ args.push_back(-5);
+ output = format.sprintf(args, &error);
+ REQUIRE(error == false);
+ CHECK(output == String("fish -5 frog"));
+
+ // Hex (lower)
+ format = "fish %x frog";
+ args.clear();
+ args.push_back(45);
+ output = format.sprintf(args, &error);
+ REQUIRE(error == false);
+ CHECK(output == String("fish 2d frog"));
+
+ // Hex (upper)
+ format = "fish %X frog";
+ args.clear();
+ args.push_back(45);
+ output = format.sprintf(args, &error);
+ REQUIRE(error == false);
+ CHECK(output == String("fish 2D frog"));
+
+ // Octal
+ format = "fish %o frog";
+ args.clear();
+ args.push_back(99);
+ output = format.sprintf(args, &error);
+ REQUIRE(error == false);
+ CHECK(output == String("fish 143 frog"));
+
+ ////// REALS
+
+ // Real
+ format = "fish %f frog";
+ args.clear();
+ args.push_back(99.99);
+ output = format.sprintf(args, &error);
+ REQUIRE(error == false);
+ CHECK(output == String("fish 99.990000 frog"));
+
+ // Real left-padded
+ format = "fish %11f frog";
+ args.clear();
+ args.push_back(99.99);
+ output = format.sprintf(args, &error);
+ REQUIRE(error == false);
+ CHECK(output == String("fish 99.990000 frog"));
+
+ // Real right-padded
+ format = "fish %-11f frog";
+ args.clear();
+ args.push_back(99.99);
+ output = format.sprintf(args, &error);
+ REQUIRE(error == false);
+ CHECK(output == String("fish 99.990000 frog"));
+
+ // Real given int.
+ format = "fish %f frog";
+ args.clear();
+ args.push_back(99);
+ output = format.sprintf(args, &error);
+ REQUIRE(error == false);
+ CHECK(output == String("fish 99.000000 frog"));
+
+ // Real with sign (positive).
+ format = "fish %+f frog";
+ args.clear();
+ args.push_back(99.99);
+ output = format.sprintf(args, &error);
+ REQUIRE(error == false);
+ CHECK(output == String("fish +99.990000 frog"));
+
+ // Real with 1 decimals.
+ format = "fish %.1f frog";
+ args.clear();
+ args.push_back(99.99);
+ output = format.sprintf(args, &error);
+ REQUIRE(error == false);
+ CHECK(output == String("fish 100.0 frog"));
+
+ // Real with 12 decimals.
+ format = "fish %.12f frog";
+ args.clear();
+ args.push_back(99.99);
+ output = format.sprintf(args, &error);
+ REQUIRE(error == false);
+ CHECK(output == String("fish 99.990000000000 frog"));
+
+ // Real with no decimals.
+ format = "fish %.f frog";
+ args.clear();
+ args.push_back(99.99);
+ output = format.sprintf(args, &error);
+ REQUIRE(error == false);
+ CHECK(output == String("fish 100 frog"));
+
+ /////// Strings.
+
+ // String
+ format = "fish %s frog";
+ args.clear();
+ args.push_back("cheese");
+ output = format.sprintf(args, &error);
+ REQUIRE(error == false);
+ CHECK(output == String("fish cheese frog"));
+
+ // String left-padded
+ format = "fish %10s frog";
+ args.clear();
+ args.push_back("cheese");
+ output = format.sprintf(args, &error);
+ REQUIRE(error == false);
+ CHECK(output == String("fish cheese frog"));
+
+ // String right-padded
+ format = "fish %-10s frog";
+ args.clear();
+ args.push_back("cheese");
+ output = format.sprintf(args, &error);
+ REQUIRE(error == false);
+ CHECK(output == String("fish cheese frog"));
+
+ ///// Characters
+
+ // Character as string.
+ format = "fish %c frog";
+ args.clear();
+ args.push_back("A");
+ output = format.sprintf(args, &error);
+ REQUIRE(error == false);
+ CHECK(output == String("fish A frog"));
+
+ // Character as int.
+ format = "fish %c frog";
+ args.clear();
+ args.push_back(65);
+ output = format.sprintf(args, &error);
+ REQUIRE(error == false);
+ CHECK(output == String("fish A frog"));
+
+ ///// Dynamic width
+
+ // String dynamic width
+ format = "fish %*s frog";
+ args.clear();
+ args.push_back(10);
+ args.push_back("cheese");
+ output = format.sprintf(args, &error);
+ REQUIRE(error == false);
+ REQUIRE(output == String("fish cheese frog"));
+
+ // Int dynamic width
+ format = "fish %*d frog";
+ args.clear();
+ args.push_back(10);
+ args.push_back(99);
+ output = format.sprintf(args, &error);
+ REQUIRE(error == false);
+ REQUIRE(output == String("fish 99 frog"));
+
+ // Float dynamic width
+ format = "fish %*.*f frog";
+ args.clear();
+ args.push_back(10);
+ args.push_back(3);
+ args.push_back(99.99);
+ output = format.sprintf(args, &error);
+ REQUIRE(error == false);
+ CHECK(output == String("fish 99.990 frog"));
+
+ ///// Errors
+
+ // More formats than arguments.
+ format = "fish %s %s frog";
+ args.clear();
+ args.push_back("cheese");
+ output = format.sprintf(args, &error);
+ REQUIRE(error);
+ CHECK(output == "not enough arguments for format string");
+
+ // More arguments than formats.
+ format = "fish %s frog";
+ args.clear();
+ args.push_back("hello");
+ args.push_back("cheese");
+ output = format.sprintf(args, &error);
+ REQUIRE(error);
+ CHECK(output == "not all arguments converted during string formatting");
+
+ // Incomplete format.
+ format = "fish %10";
+ args.clear();
+ args.push_back("cheese");
+ output = format.sprintf(args, &error);
+ REQUIRE(error);
+ CHECK(output == "incomplete format");
+
+ // Bad character in format string
+ format = "fish %&f frog";
+ args.clear();
+ args.push_back("cheese");
+ output = format.sprintf(args, &error);
+ REQUIRE(error);
+ CHECK(output == "unsupported format character");
+
+ // Too many decimals.
+ format = "fish %2.2.2f frog";
+ args.clear();
+ args.push_back(99.99);
+ output = format.sprintf(args, &error);
+ REQUIRE(error);
+ CHECK(output == "too many decimal points in format");
+
+ // * not a number
+ format = "fish %*f frog";
+ args.clear();
+ args.push_back("cheese");
+ args.push_back(99.99);
+ output = format.sprintf(args, &error);
+ REQUIRE(error);
+ CHECK(output == "* wants number");
+
+ // Character too long.
+ format = "fish %c frog";
+ args.clear();
+ args.push_back("sc");
+ output = format.sprintf(args, &error);
+ REQUIRE(error);
+ CHECK(output == "%c requires number or single-character string");
+
+ // Character bad type.
+ format = "fish %c frog";
+ args.clear();
+ args.push_back(Array());
+ output = format.sprintf(args, &error);
+ REQUIRE(error);
+ CHECK(output == "%c requires number or single-character string");
+}
+
+TEST_CASE("[String] IPVX address to string") {
+ IP_Address ip0("2001:0db8:85a3:0000:0000:8a2e:0370:7334");
+ IP_Address ip(0x0123, 0x4567, 0x89ab, 0xcdef, true);
+ IP_Address ip2("fe80::52e5:49ff:fe93:1baf");
+ IP_Address ip3("::ffff:192.168.0.1");
+ String ip4 = "192.168.0.1";
+ CHECK(ip4.is_valid_ip_address());
+
+ ip4 = "192.368.0.1";
+ CHECK(!ip4.is_valid_ip_address());
+
+ String ip6 = "2001:0db8:85a3:0000:0000:8a2e:0370:7334";
+ CHECK(ip6.is_valid_ip_address());
+
+ ip6 = "2001:0db8:85j3:0000:0000:8a2e:0370:7334";
+ CHECK(!ip6.is_valid_ip_address());
+
+ ip6 = "2001:0db8:85f345:0000:0000:8a2e:0370:7334";
+ CHECK(!ip6.is_valid_ip_address());
+
+ ip6 = "2001:0db8::0:8a2e:370:7334";
+ CHECK(ip6.is_valid_ip_address());
+
+ ip6 = "::ffff:192.168.0.1";
+ CHECK(ip6.is_valid_ip_address());
+}
+
+TEST_CASE("[String] Capitalize against many strings") {
+ String input = "bytes2var";
+ String output = "Bytes 2 Var";
+ CHECK(input.capitalize() == output);
+
+ input = "linear2db";
+ output = "Linear 2 Db";
+ CHECK(input.capitalize() == output);
+
+ input = "vector3";
+ output = "Vector 3";
+ CHECK(input.capitalize() == output);
+
+ input = "sha256";
+ output = "Sha 256";
+ CHECK(input.capitalize() == output);
+
+ input = "2db";
+ output = "2 Db";
+ CHECK(input.capitalize() == output);
+
+ input = "PascalCase";
+ output = "Pascal Case";
+ CHECK(input.capitalize() == output);
+
+ input = "PascalPascalCase";
+ output = "Pascal Pascal Case";
+ CHECK(input.capitalize() == output);
+
+ input = "snake_case";
+ output = "Snake Case";
+ CHECK(input.capitalize() == output);
+
+ input = "snake_snake_case";
+ output = "Snake Snake Case";
+ CHECK(input.capitalize() == output);
+
+ input = "sha256sum";
+ output = "Sha 256 Sum";
+ CHECK(input.capitalize() == output);
+
+ input = "cat2dog";
+ output = "Cat 2 Dog";
+ CHECK(input.capitalize() == output);
+
+ input = "function(name)";
+ output = "Function(name)";
+ CHECK(input.capitalize() == output);
+
+ input = "snake_case_function(snake_case_arg)";
+ output = "Snake Case Function(snake Case Arg)";
+ CHECK(input.capitalize() == output);
+
+ input = "snake_case_function( snake_case_arg )";
+ output = "Snake Case Function( Snake Case Arg )";
+ CHECK(input.capitalize() == output);
+}
+
+TEST_CASE("[String] Checking string is empty when it should be") {
+ bool state = true;
+ bool success;
+
+ String a = "";
+ success = a[0] == 0;
+ if (!success) {
+ state = false;
+ }
+ String b = "Godot";
+ success = b[b.size()] == 0;
+ if (!success) {
+ state = false;
+ }
+ const String c = "";
+ success = c[0] == 0;
+ if (!success) {
+ state = false;
+ }
+
+ const String d = "Godot";
+ success = d[d.size()] == 0;
+ if (!success) {
+ state = false;
+ }
+
+ CHECK(state);
+}
+
+TEST_CASE("[String] lstrip and rstrip") {
+#define STRIP_TEST(x) \
+ { \
+ bool success = x; \
+ state = state && success; \
+ }
+
+ bool state = true;
+
+ // strip none
+ STRIP_TEST(String("abc").lstrip("") == "abc");
+ STRIP_TEST(String("abc").rstrip("") == "abc");
+ // strip one
+ STRIP_TEST(String("abc").lstrip("a") == "bc");
+ STRIP_TEST(String("abc").rstrip("c") == "ab");
+ // strip lots
+ STRIP_TEST(String("bababbababccc").lstrip("ab") == "ccc");
+ STRIP_TEST(String("aaabcbcbcbbcbbc").rstrip("cb") == "aaa");
+ // strip empty string
+ STRIP_TEST(String("").lstrip("") == "");
+ STRIP_TEST(String("").rstrip("") == "");
+ // strip to empty string
+ STRIP_TEST(String("abcabcabc").lstrip("bca") == "");
+ STRIP_TEST(String("abcabcabc").rstrip("bca") == "");
+ // don't strip wrong end
+ STRIP_TEST(String("abc").lstrip("c") == "abc");
+ STRIP_TEST(String("abca").lstrip("a") == "bca");
+ STRIP_TEST(String("abc").rstrip("a") == "abc");
+ STRIP_TEST(String("abca").rstrip("a") == "abc");
+ // in utf-8 "¿" (\u00bf) has the same first byte as "µ" (\u00b5)
+ // and the same second as "ÿ" (\u00ff)
+ STRIP_TEST(String::utf8("¿").lstrip(String::utf8("µÿ")) == String::utf8("¿"));
+ STRIP_TEST(String::utf8("¿").rstrip(String::utf8("µÿ")) == String::utf8("¿"));
+ STRIP_TEST(String::utf8("µ¿ÿ").lstrip(String::utf8("µÿ")) == String::utf8("¿ÿ"));
+ STRIP_TEST(String::utf8("µ¿ÿ").rstrip(String::utf8("µÿ")) == String::utf8("µ¿"));
+
+ // the above tests repeated with additional superfluous strip chars
+
+ // strip none
+ STRIP_TEST(String("abc").lstrip("qwjkl") == "abc");
+ STRIP_TEST(String("abc").rstrip("qwjkl") == "abc");
+ // strip one
+ STRIP_TEST(String("abc").lstrip("qwajkl") == "bc");
+ STRIP_TEST(String("abc").rstrip("qwcjkl") == "ab");
+ // strip lots
+ STRIP_TEST(String("bababbababccc").lstrip("qwabjkl") == "ccc");
+ STRIP_TEST(String("aaabcbcbcbbcbbc").rstrip("qwcbjkl") == "aaa");
+ // strip empty string
+ STRIP_TEST(String("").lstrip("qwjkl") == "");
+ STRIP_TEST(String("").rstrip("qwjkl") == "");
+ // strip to empty string
+ STRIP_TEST(String("abcabcabc").lstrip("qwbcajkl") == "");
+ STRIP_TEST(String("abcabcabc").rstrip("qwbcajkl") == "");
+ // don't strip wrong end
+ STRIP_TEST(String("abc").lstrip("qwcjkl") == "abc");
+ STRIP_TEST(String("abca").lstrip("qwajkl") == "bca");
+ STRIP_TEST(String("abc").rstrip("qwajkl") == "abc");
+ STRIP_TEST(String("abca").rstrip("qwajkl") == "abc");
+ // in utf-8 "¿" (\u00bf) has the same first byte as "µ" (\u00b5)
+ // and the same second as "ÿ" (\u00ff)
+ STRIP_TEST(String::utf8("¿").lstrip(String::utf8("qwaµÿjkl")) == String::utf8("¿"));
+ STRIP_TEST(String::utf8("¿").rstrip(String::utf8("qwaµÿjkl")) == String::utf8("¿"));
+ STRIP_TEST(String::utf8("µ¿ÿ").lstrip(String::utf8("qwaµÿjkl")) == String::utf8("¿ÿ"));
+ STRIP_TEST(String::utf8("µ¿ÿ").rstrip(String::utf8("qwaµÿjkl")) == String::utf8("µ¿"));
+
+ CHECK(state);
+
+#undef STRIP_TEST
+}
+
+TEST_CASE("[String] ensuring empty string into parse_utf8 passes empty string") {
+ String empty;
+ CHECK(empty.parse_utf8(NULL, -1));
+}
+
+TEST_CASE("[String] Cyrillic to_lower()") {
+ String upper = String::utf8("АБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ");
+ String lower = String::utf8("абвгдеёжзийклмнопрстуфхцчшщъыьэюя");
+
+ String test = upper.to_lower();
+
+ bool state = test == lower;
+
+ CHECK(state);
+}
+
+TEST_CASE("[String] Count and countn functionality") {
+#define COUNT_TEST(x) \
+ { \
+ bool success = x; \
+ state = state && success; \
+ }
+
+ bool state = true;
+
+ 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);
+
+ CHECK(state);
+}
+} // namespace TestString
+
+#endif // TEST_STRING_H
diff --git a/tests/test_validate_testing.h b/tests/test_validate_testing.h
new file mode 100644
index 0000000000..5be7d45185
--- /dev/null
+++ b/tests/test_validate_testing.h
@@ -0,0 +1,42 @@
+/*************************************************************************/
+/* test_validate_testing.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_VALIDATE_TESTING_H
+#define TEST_VALIDATE_TESTING_H
+
+#include "core/os/os.h"
+
+#include "thirdparty/doctest/doctest.h"
+
+TEST_CASE("Validate Test will always pass") {
+ CHECK(true);
+}
+
+#endif // TEST_VALIDATE_TESTING_H