feat: updated engine version to 4.4-rc1
This commit is contained in:
parent
ee00efde1f
commit
21ba8e33af
5459 changed files with 1128836 additions and 198305 deletions
|
|
@ -40,7 +40,7 @@ class TestProjectSettingsInternalsAccessor {
|
|||
public:
|
||||
static String &resource_path() {
|
||||
return ProjectSettings::get_singleton()->resource_path;
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
namespace TestProjectSettings {
|
||||
|
|
@ -126,10 +126,9 @@ TEST_CASE("[ProjectSettings] localize_path") {
|
|||
CHECK_EQ(ProjectSettings::get_singleton()->localize_path("path\\.\\filename"), "res://path/filename");
|
||||
#endif
|
||||
|
||||
// FIXME?: These checks pass, but that doesn't seems correct
|
||||
CHECK_EQ(ProjectSettings::get_singleton()->localize_path("../filename"), "res://filename");
|
||||
CHECK_EQ(ProjectSettings::get_singleton()->localize_path("../path/filename"), "res://path/filename");
|
||||
CHECK_EQ(ProjectSettings::get_singleton()->localize_path("..\\path\\filename"), "res://path/filename");
|
||||
CHECK_EQ(ProjectSettings::get_singleton()->localize_path("../filename"), "../filename");
|
||||
CHECK_EQ(ProjectSettings::get_singleton()->localize_path("../path/filename"), "../path/filename");
|
||||
CHECK_EQ(ProjectSettings::get_singleton()->localize_path("..\\path\\filename"), "../path/filename");
|
||||
|
||||
CHECK_EQ(ProjectSettings::get_singleton()->localize_path("/testroot/filename"), "/testroot/filename");
|
||||
CHECK_EQ(ProjectSettings::get_singleton()->localize_path("/testroot/path/filename"), "/testroot/path/filename");
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ namespace TestFileAccess {
|
|||
|
||||
TEST_CASE("[FileAccess] CSV read") {
|
||||
Ref<FileAccess> f = FileAccess::open(TestUtils::get_data_path("testdata.csv"), FileAccess::READ);
|
||||
REQUIRE(!f.is_null());
|
||||
REQUIRE(f.is_valid());
|
||||
|
||||
Vector<String> header = f->get_csv_line(); // Default delimiter: ",".
|
||||
REQUIRE(header.size() == 4);
|
||||
|
|
@ -84,7 +84,7 @@ TEST_CASE("[FileAccess] CSV read") {
|
|||
|
||||
TEST_CASE("[FileAccess] Get as UTF-8 String") {
|
||||
Ref<FileAccess> f_lf = FileAccess::open(TestUtils::get_data_path("line_endings_lf.test.txt"), FileAccess::READ);
|
||||
REQUIRE(!f_lf.is_null());
|
||||
REQUIRE(f_lf.is_valid());
|
||||
String s_lf = f_lf->get_as_utf8_string();
|
||||
f_lf->seek(0);
|
||||
String s_lf_nocr = f_lf->get_as_utf8_string(true);
|
||||
|
|
@ -92,7 +92,7 @@ TEST_CASE("[FileAccess] Get as UTF-8 String") {
|
|||
CHECK(s_lf_nocr == "Hello darkness\nMy old friend\nI've come to talk\nWith you again\n");
|
||||
|
||||
Ref<FileAccess> f_crlf = FileAccess::open(TestUtils::get_data_path("line_endings_crlf.test.txt"), FileAccess::READ);
|
||||
REQUIRE(!f_crlf.is_null());
|
||||
REQUIRE(f_crlf.is_valid());
|
||||
String s_crlf = f_crlf->get_as_utf8_string();
|
||||
f_crlf->seek(0);
|
||||
String s_crlf_nocr = f_crlf->get_as_utf8_string(true);
|
||||
|
|
@ -100,13 +100,105 @@ TEST_CASE("[FileAccess] Get as UTF-8 String") {
|
|||
CHECK(s_crlf_nocr == "Hello darkness\nMy old friend\nI've come to talk\nWith you again\n");
|
||||
|
||||
Ref<FileAccess> f_cr = FileAccess::open(TestUtils::get_data_path("line_endings_cr.test.txt"), FileAccess::READ);
|
||||
REQUIRE(!f_cr.is_null());
|
||||
REQUIRE(f_cr.is_valid());
|
||||
String s_cr = f_cr->get_as_utf8_string();
|
||||
f_cr->seek(0);
|
||||
String s_cr_nocr = f_cr->get_as_utf8_string(true);
|
||||
CHECK(s_cr == "Hello darkness\rMy old friend\rI've come to talk\rWith you again\r");
|
||||
CHECK(s_cr_nocr == "Hello darknessMy old friendI've come to talkWith you again");
|
||||
}
|
||||
|
||||
TEST_CASE("[FileAccess] Get/Store floating point values") {
|
||||
// BigEndian Hex: 0x40490E56
|
||||
// LittleEndian Hex: 0x560E4940
|
||||
float value = 3.1415f;
|
||||
|
||||
SUBCASE("Little Endian") {
|
||||
const String file_path = TestUtils::get_data_path("floating_point_little_endian.bin");
|
||||
const String file_path_new = TestUtils::get_data_path("floating_point_little_endian_new.bin");
|
||||
|
||||
Ref<FileAccess> f = FileAccess::open(file_path, FileAccess::READ);
|
||||
REQUIRE(f.is_valid());
|
||||
CHECK_EQ(f->get_float(), value);
|
||||
|
||||
Ref<FileAccess> fw = FileAccess::open(file_path_new, FileAccess::WRITE);
|
||||
REQUIRE(fw.is_valid());
|
||||
fw->store_float(value);
|
||||
fw->close();
|
||||
|
||||
CHECK_EQ(FileAccess::get_sha256(file_path_new), FileAccess::get_sha256(file_path));
|
||||
|
||||
DirAccess::remove_file_or_error(file_path_new);
|
||||
}
|
||||
|
||||
SUBCASE("Big Endian") {
|
||||
const String file_path = TestUtils::get_data_path("floating_point_big_endian.bin");
|
||||
const String file_path_new = TestUtils::get_data_path("floating_point_big_endian_new.bin");
|
||||
|
||||
Ref<FileAccess> f = FileAccess::open(file_path, FileAccess::READ);
|
||||
REQUIRE(f.is_valid());
|
||||
f->set_big_endian(true);
|
||||
CHECK_EQ(f->get_float(), value);
|
||||
|
||||
Ref<FileAccess> fw = FileAccess::open(file_path_new, FileAccess::WRITE);
|
||||
REQUIRE(fw.is_valid());
|
||||
fw->set_big_endian(true);
|
||||
fw->store_float(value);
|
||||
fw->close();
|
||||
|
||||
CHECK_EQ(FileAccess::get_sha256(file_path_new), FileAccess::get_sha256(file_path));
|
||||
|
||||
DirAccess::remove_file_or_error(file_path_new);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("[FileAccess] Get/Store floating point half precision values") {
|
||||
// IEEE 754 half-precision binary floating-point format:
|
||||
// sign exponent (5 bits) fraction (10 bits)
|
||||
// 0 01101 0101010101
|
||||
// BigEndian Hex: 0x3555
|
||||
// LittleEndian Hex: 0x5535
|
||||
float value = 0.33325195f;
|
||||
|
||||
SUBCASE("Little Endian") {
|
||||
const String file_path = TestUtils::get_data_path("half_precision_floating_point_little_endian.bin");
|
||||
const String file_path_new = TestUtils::get_data_path("half_precision_floating_point_little_endian_new.bin");
|
||||
|
||||
Ref<FileAccess> f = FileAccess::open(file_path, FileAccess::READ);
|
||||
REQUIRE(f.is_valid());
|
||||
CHECK_EQ(f->get_half(), value);
|
||||
|
||||
Ref<FileAccess> fw = FileAccess::open(file_path_new, FileAccess::WRITE);
|
||||
REQUIRE(fw.is_valid());
|
||||
fw->store_half(value);
|
||||
fw->close();
|
||||
|
||||
CHECK_EQ(FileAccess::get_sha256(file_path_new), FileAccess::get_sha256(file_path));
|
||||
|
||||
DirAccess::remove_file_or_error(file_path_new);
|
||||
}
|
||||
|
||||
SUBCASE("Big Endian") {
|
||||
const String file_path = TestUtils::get_data_path("half_precision_floating_point_big_endian.bin");
|
||||
const String file_path_new = TestUtils::get_data_path("half_precision_floating_point_big_endian_new.bin");
|
||||
|
||||
Ref<FileAccess> f = FileAccess::open(file_path, FileAccess::READ);
|
||||
REQUIRE(f.is_valid());
|
||||
f->set_big_endian(true);
|
||||
CHECK_EQ(f->get_half(), value);
|
||||
|
||||
Ref<FileAccess> fw = FileAccess::open(file_path_new, FileAccess::WRITE);
|
||||
REQUIRE(fw.is_valid());
|
||||
fw->set_big_endian(true);
|
||||
fw->store_half(value);
|
||||
fw->close();
|
||||
|
||||
CHECK_EQ(FileAccess::get_sha256(file_path_new), FileAccess::get_sha256(file_path));
|
||||
|
||||
DirAccess::remove_file_or_error(file_path_new);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace TestFileAccess
|
||||
|
||||
#endif // TEST_FILE_ACCESS_H
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ namespace TestHTTPClient {
|
|||
|
||||
TEST_CASE("[HTTPClient] Instantiation") {
|
||||
Ref<HTTPClient> client = HTTPClient::create();
|
||||
CHECK_MESSAGE(client != nullptr, "A HTTP Client created should not be a null pointer");
|
||||
CHECK_MESSAGE(client.is_valid(), "A HTTP Client created should not be a null pointer");
|
||||
}
|
||||
|
||||
TEST_CASE("[HTTPClient] query_string_from_dict") {
|
||||
|
|
|
|||
|
|
@ -35,10 +35,11 @@
|
|||
#include "core/os/os.h"
|
||||
|
||||
#include "tests/test_utils.h"
|
||||
#include "thirdparty/doctest/doctest.h"
|
||||
|
||||
#include "modules/modules_enabled.gen.h"
|
||||
|
||||
#include "thirdparty/doctest/doctest.h"
|
||||
|
||||
namespace TestImage {
|
||||
|
||||
TEST_CASE("[Image] Instantiation") {
|
||||
|
|
@ -113,7 +114,7 @@ TEST_CASE("[Image] Saving and loading") {
|
|||
// Load BMP
|
||||
Ref<Image> image_bmp = memnew(Image());
|
||||
Ref<FileAccess> f_bmp = FileAccess::open(TestUtils::get_data_path("images/icon.bmp"), FileAccess::READ, &err);
|
||||
REQUIRE(!f_bmp.is_null());
|
||||
REQUIRE(f_bmp.is_valid());
|
||||
PackedByteArray data_bmp;
|
||||
data_bmp.resize(f_bmp->get_length() + 1);
|
||||
f_bmp->get_buffer(data_bmp.ptrw(), f_bmp->get_length());
|
||||
|
|
@ -126,7 +127,7 @@ TEST_CASE("[Image] Saving and loading") {
|
|||
// Load JPG
|
||||
Ref<Image> image_jpg = memnew(Image());
|
||||
Ref<FileAccess> f_jpg = FileAccess::open(TestUtils::get_data_path("images/icon.jpg"), FileAccess::READ, &err);
|
||||
REQUIRE(!f_jpg.is_null());
|
||||
REQUIRE(f_jpg.is_valid());
|
||||
PackedByteArray data_jpg;
|
||||
data_jpg.resize(f_jpg->get_length() + 1);
|
||||
f_jpg->get_buffer(data_jpg.ptrw(), f_jpg->get_length());
|
||||
|
|
@ -139,7 +140,7 @@ TEST_CASE("[Image] Saving and loading") {
|
|||
// Load WebP
|
||||
Ref<Image> image_webp = memnew(Image());
|
||||
Ref<FileAccess> f_webp = FileAccess::open(TestUtils::get_data_path("images/icon.webp"), FileAccess::READ, &err);
|
||||
REQUIRE(!f_webp.is_null());
|
||||
REQUIRE(f_webp.is_valid());
|
||||
PackedByteArray data_webp;
|
||||
data_webp.resize(f_webp->get_length() + 1);
|
||||
f_webp->get_buffer(data_webp.ptrw(), f_webp->get_length());
|
||||
|
|
@ -151,7 +152,7 @@ TEST_CASE("[Image] Saving and loading") {
|
|||
// Load PNG
|
||||
Ref<Image> image_png = memnew(Image());
|
||||
Ref<FileAccess> f_png = FileAccess::open(TestUtils::get_data_path("images/icon.png"), FileAccess::READ, &err);
|
||||
REQUIRE(!f_png.is_null());
|
||||
REQUIRE(f_png.is_valid());
|
||||
PackedByteArray data_png;
|
||||
data_png.resize(f_png->get_length() + 1);
|
||||
f_png->get_buffer(data_png.ptrw(), f_png->get_length());
|
||||
|
|
@ -163,7 +164,7 @@ TEST_CASE("[Image] Saving and loading") {
|
|||
// Load TGA
|
||||
Ref<Image> image_tga = memnew(Image());
|
||||
Ref<FileAccess> f_tga = FileAccess::open(TestUtils::get_data_path("images/icon.tga"), FileAccess::READ, &err);
|
||||
REQUIRE(!f_tga.is_null());
|
||||
REQUIRE(f_tga.is_valid());
|
||||
PackedByteArray data_tga;
|
||||
data_tga.resize(f_tga->get_length() + 1);
|
||||
f_tga->get_buffer(data_tga.ptrw(), f_tga->get_length());
|
||||
|
|
|
|||
|
|
@ -232,6 +232,91 @@ TEST_CASE("[JSON] Parsing escape sequences") {
|
|||
ERR_PRINT_ON
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("[JSON] Serialization") {
|
||||
JSON json;
|
||||
|
||||
struct FpTestCase {
|
||||
double number;
|
||||
String json;
|
||||
};
|
||||
|
||||
struct IntTestCase {
|
||||
int64_t number;
|
||||
String json;
|
||||
};
|
||||
|
||||
struct UIntTestCase {
|
||||
uint64_t number;
|
||||
String json;
|
||||
};
|
||||
|
||||
static FpTestCase fp_tests_default_precision[] = {
|
||||
{ 0.0, "0.0" },
|
||||
{ 1000.1234567890123456789, "1000.12345678901" },
|
||||
{ -1000.1234567890123456789, "-1000.12345678901" },
|
||||
{ DBL_MAX, "179769313486231570814527423731704356798070567525844996598917476803157260780028538760589558632766878171540458953514382464234321326889464182768467546703537516986049910576551282076245490090389328944075868508455133942304583236903222948165808559332123348274797826204144723168738177180919299881250404026184124858368.0" },
|
||||
{ DBL_MAX - 1, "179769313486231570814527423731704356798070567525844996598917476803157260780028538760589558632766878171540458953514382464234321326889464182768467546703537516986049910576551282076245490090389328944075868508455133942304583236903222948165808559332123348274797826204144723168738177180919299881250404026184124858368.0" },
|
||||
{ pow(2, 53), "9007199254740992.0" },
|
||||
{ -pow(2, 53), "-9007199254740992.0" },
|
||||
{ 0.00000000000000011, "0.00000000000000011" },
|
||||
{ -0.00000000000000011, "-0.00000000000000011" },
|
||||
{ 1.0 / 3.0, "0.333333333333333" },
|
||||
{ 0.9999999999999999, "1.0" },
|
||||
{ 1.0000000000000001, "1.0" },
|
||||
};
|
||||
|
||||
static FpTestCase fp_tests_full_precision[] = {
|
||||
{ 0.0, "0.0" },
|
||||
{ 1000.1234567890123456789, "1000.12345678901238" },
|
||||
{ -1000.1234567890123456789, "-1000.12345678901238" },
|
||||
{ DBL_MAX, "179769313486231570814527423731704356798070567525844996598917476803157260780028538760589558632766878171540458953514382464234321326889464182768467546703537516986049910576551282076245490090389328944075868508455133942304583236903222948165808559332123348274797826204144723168738177180919299881250404026184124858368.0" },
|
||||
{ DBL_MAX - 1, "179769313486231570814527423731704356798070567525844996598917476803157260780028538760589558632766878171540458953514382464234321326889464182768467546703537516986049910576551282076245490090389328944075868508455133942304583236903222948165808559332123348274797826204144723168738177180919299881250404026184124858368.0" },
|
||||
{ pow(2, 53), "9007199254740992.0" },
|
||||
{ -pow(2, 53), "-9007199254740992.0" },
|
||||
{ 0.00000000000000011, "0.00000000000000011" },
|
||||
{ -0.00000000000000011, "-0.00000000000000011" },
|
||||
{ 1.0 / 3.0, "0.333333333333333315" },
|
||||
{ 0.9999999999999999, "0.999999999999999889" },
|
||||
{ 1.0000000000000001, "1.0" },
|
||||
};
|
||||
|
||||
static IntTestCase int_tests[] = {
|
||||
{ 0, "0" },
|
||||
{ INT64_MAX, "9223372036854775807" },
|
||||
{ INT64_MIN, "-9223372036854775808" },
|
||||
};
|
||||
|
||||
SUBCASE("Floating point default precision") {
|
||||
for (FpTestCase &test : fp_tests_default_precision) {
|
||||
String json_value = json.stringify(test.number, "", true, false);
|
||||
|
||||
CHECK_MESSAGE(
|
||||
json_value == test.json,
|
||||
vformat("Serializing `%.20d` to JSON should return the expected value.", test.number));
|
||||
}
|
||||
}
|
||||
|
||||
SUBCASE("Floating point full precision") {
|
||||
for (FpTestCase &test : fp_tests_full_precision) {
|
||||
String json_value = json.stringify(test.number, "", true, true);
|
||||
|
||||
CHECK_MESSAGE(
|
||||
json_value == test.json,
|
||||
vformat("Serializing `%20f` to JSON should return the expected value.", test.number));
|
||||
}
|
||||
}
|
||||
|
||||
SUBCASE("Signed integer") {
|
||||
for (IntTestCase &test : int_tests) {
|
||||
String json_value = json.stringify(test.number, "", true, true);
|
||||
|
||||
CHECK_MESSAGE(
|
||||
json_value == test.json,
|
||||
vformat("Serializing `%d` to JSON should return the expected value.", test.number));
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace TestJSON
|
||||
|
||||
#endif // TEST_JSON_H
|
||||
|
|
|
|||
227
engine/tests/core/io/test_json_native.h
Normal file
227
engine/tests/core/io/test_json_native.h
Normal file
|
|
@ -0,0 +1,227 @@
|
|||
/**************************************************************************/
|
||||
/* test_json_native.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#ifndef TEST_JSON_NATIVE_H
|
||||
#define TEST_JSON_NATIVE_H
|
||||
|
||||
#include "core/io/json.h"
|
||||
|
||||
#include "core/variant/typed_array.h"
|
||||
#include "core/variant/typed_dictionary.h"
|
||||
#include "tests/test_macros.h"
|
||||
|
||||
namespace TestJSONNative {
|
||||
|
||||
String encode(const Variant &p_variant, bool p_full_objects = false) {
|
||||
return JSON::stringify(JSON::from_native(p_variant, p_full_objects), "", false);
|
||||
}
|
||||
|
||||
Variant decode(const String &p_string, bool p_allow_objects = false) {
|
||||
return JSON::to_native(JSON::parse_string(p_string), p_allow_objects);
|
||||
}
|
||||
|
||||
void test(const Variant &p_variant, const String &p_string, bool p_with_objects = false) {
|
||||
CHECK(encode(p_variant, p_with_objects) == p_string);
|
||||
CHECK(decode(p_string, p_with_objects).get_construct_string() == p_variant.get_construct_string());
|
||||
}
|
||||
|
||||
TEST_CASE("[JSON][Native] Conversion between native and JSON formats") {
|
||||
// `Nil` and `bool` (represented as JSON keyword literals).
|
||||
test(Variant(), "null");
|
||||
test(false, "false");
|
||||
test(true, "true");
|
||||
|
||||
// Numbers and strings (represented as JSON strings).
|
||||
test(1, R"("i:1")");
|
||||
test(1.0, R"("f:1.0")");
|
||||
test(String("abc"), R"("s:abc")");
|
||||
test(StringName("abc"), R"("sn:abc")");
|
||||
test(NodePath("abc"), R"("np:abc")");
|
||||
|
||||
// Non-serializable types (always empty after deserialization).
|
||||
test(RID(), R"({"type":"RID"})");
|
||||
test(Callable(), R"({"type":"Callable"})");
|
||||
test(Signal(), R"({"type":"Signal"})");
|
||||
|
||||
// Math types.
|
||||
|
||||
test(Vector2(1, 2), R"({"type":"Vector2","args":[1.0,2.0]})");
|
||||
test(Vector2i(1, 2), R"({"type":"Vector2i","args":[1,2]})");
|
||||
test(Rect2(1, 2, 3, 4), R"({"type":"Rect2","args":[1.0,2.0,3.0,4.0]})");
|
||||
test(Rect2i(1, 2, 3, 4), R"({"type":"Rect2i","args":[1,2,3,4]})");
|
||||
test(Vector3(1, 2, 3), R"({"type":"Vector3","args":[1.0,2.0,3.0]})");
|
||||
test(Vector3i(1, 2, 3), R"({"type":"Vector3i","args":[1,2,3]})");
|
||||
test(Transform2D(1, 2, 3, 4, 5, 6), R"({"type":"Transform2D","args":[1.0,2.0,3.0,4.0,5.0,6.0]})");
|
||||
test(Vector4(1, 2, 3, 4), R"({"type":"Vector4","args":[1.0,2.0,3.0,4.0]})");
|
||||
test(Vector4i(1, 2, 3, 4), R"({"type":"Vector4i","args":[1,2,3,4]})");
|
||||
test(Plane(1, 2, 3, 4), R"({"type":"Plane","args":[1.0,2.0,3.0,4.0]})");
|
||||
test(Quaternion(1, 2, 3, 4), R"({"type":"Quaternion","args":[1.0,2.0,3.0,4.0]})");
|
||||
test(AABB(Vector3(1, 2, 3), Vector3(4, 5, 6)), R"({"type":"AABB","args":[1.0,2.0,3.0,4.0,5.0,6.0]})");
|
||||
|
||||
const Basis b(Vector3(1, 2, 3), Vector3(4, 5, 6), Vector3(7, 8, 9));
|
||||
test(b, R"({"type":"Basis","args":[1.0,2.0,3.0,4.0,5.0,6.0,7.0,8.0,9.0]})");
|
||||
|
||||
const Transform3D tr3d(Vector3(1, 2, 3), Vector3(4, 5, 6), Vector3(7, 8, 9), Vector3(10, 11, 12));
|
||||
test(tr3d, R"({"type":"Transform3D","args":[1.0,2.0,3.0,4.0,5.0,6.0,7.0,8.0,9.0,10.0,11.0,12.0]})");
|
||||
|
||||
const Projection p(Vector4(1, 2, 3, 4), Vector4(5, 6, 7, 8), Vector4(9, 10, 11, 12), Vector4(13, 14, 15, 16));
|
||||
test(p, R"({"type":"Projection","args":[1.0,2.0,3.0,4.0,5.0,6.0,7.0,8.0,9.0,10.0,11.0,12.0,13.0,14.0,15.0,16.0]})");
|
||||
|
||||
test(Color(1, 2, 3, 4), R"({"type":"Color","args":[1.0,2.0,3.0,4.0]})");
|
||||
|
||||
// `Object`.
|
||||
|
||||
Ref<Resource> res;
|
||||
res.instantiate();
|
||||
|
||||
// The properties are stored in an array because the order in which they are assigned may be important during initialization.
|
||||
const String res_repr = R"({"type":"Resource","props":["resource_local_to_scene",false,"resource_name","s:","script",null]})";
|
||||
|
||||
test(res, res_repr, true);
|
||||
ERR_PRINT_OFF;
|
||||
CHECK(encode(res) == "null");
|
||||
CHECK(decode(res_repr).get_type() == Variant::NIL);
|
||||
ERR_PRINT_ON;
|
||||
|
||||
// `Dictionary`.
|
||||
|
||||
Dictionary dict;
|
||||
dict[false] = true;
|
||||
dict[0] = 1;
|
||||
dict[0.0] = 1.0;
|
||||
|
||||
// Godot dictionaries preserve insertion order, so an array is used for keys/values.
|
||||
test(dict, R"({"type":"Dictionary","args":[false,true,"i:0","i:1","f:0.0","f:1.0"]})");
|
||||
|
||||
TypedDictionary<int64_t, int64_t> int_int_dict;
|
||||
int_int_dict[1] = 2;
|
||||
int_int_dict[3] = 4;
|
||||
|
||||
test(int_int_dict, R"({"type":"Dictionary","key_type":"int","value_type":"int","args":["i:1","i:2","i:3","i:4"]})");
|
||||
|
||||
TypedDictionary<int64_t, Variant> int_var_dict;
|
||||
int_var_dict[1] = "2";
|
||||
int_var_dict[3] = "4";
|
||||
|
||||
test(int_var_dict, R"({"type":"Dictionary","key_type":"int","args":["i:1","s:2","i:3","s:4"]})");
|
||||
|
||||
TypedDictionary<Variant, int64_t> var_int_dict;
|
||||
var_int_dict["1"] = 2;
|
||||
var_int_dict["3"] = 4;
|
||||
|
||||
test(var_int_dict, R"({"type":"Dictionary","value_type":"int","args":["s:1","i:2","s:3","i:4"]})");
|
||||
|
||||
Dictionary dict2;
|
||||
dict2["x"] = res;
|
||||
|
||||
const String dict2_repr = vformat(R"({"type":"Dictionary","args":["s:x",%s]})", res_repr);
|
||||
|
||||
test(dict2, dict2_repr, true);
|
||||
ERR_PRINT_OFF;
|
||||
CHECK(encode(dict2) == R"({"type":"Dictionary","args":["s:x",null]})");
|
||||
CHECK(decode(dict2_repr).get_construct_string() == "{\n\"x\": null\n}");
|
||||
ERR_PRINT_ON;
|
||||
|
||||
TypedDictionary<String, Resource> res_dict;
|
||||
res_dict["x"] = res;
|
||||
|
||||
const String res_dict_repr = vformat(R"({"type":"Dictionary","key_type":"String","value_type":"Resource","args":["s:x",%s]})", res_repr);
|
||||
|
||||
test(res_dict, res_dict_repr, true);
|
||||
ERR_PRINT_OFF;
|
||||
CHECK(encode(res_dict) == "null");
|
||||
CHECK(decode(res_dict_repr).get_type() == Variant::NIL);
|
||||
ERR_PRINT_ON;
|
||||
|
||||
// `Array`.
|
||||
|
||||
Array arr;
|
||||
arr.push_back(true);
|
||||
arr.push_back(1);
|
||||
arr.push_back("abc");
|
||||
|
||||
test(arr, R"([true,"i:1","s:abc"])");
|
||||
|
||||
TypedArray<int64_t> int_arr;
|
||||
int_arr.push_back(1);
|
||||
int_arr.push_back(2);
|
||||
int_arr.push_back(3);
|
||||
|
||||
test(int_arr, R"({"type":"Array","elem_type":"int","args":["i:1","i:2","i:3"]})");
|
||||
|
||||
Array arr2;
|
||||
arr2.push_back(1);
|
||||
arr2.push_back(res);
|
||||
arr2.push_back(9);
|
||||
|
||||
const String arr2_repr = vformat(R"(["i:1",%s,"i:9"])", res_repr);
|
||||
|
||||
test(arr2, arr2_repr, true);
|
||||
ERR_PRINT_OFF;
|
||||
CHECK(encode(arr2) == R"(["i:1",null,"i:9"])");
|
||||
CHECK(decode(arr2_repr).get_construct_string() == "[1, null, 9]");
|
||||
ERR_PRINT_ON;
|
||||
|
||||
TypedArray<Resource> res_arr;
|
||||
res_arr.push_back(res);
|
||||
|
||||
const String res_arr_repr = vformat(R"({"type":"Array","elem_type":"Resource","args":[%s]})", res_repr);
|
||||
|
||||
test(res_arr, res_arr_repr, true);
|
||||
ERR_PRINT_OFF;
|
||||
CHECK(encode(res_arr) == "null");
|
||||
CHECK(decode(res_arr_repr).get_type() == Variant::NIL);
|
||||
ERR_PRINT_ON;
|
||||
|
||||
// Packed arrays.
|
||||
|
||||
test(PackedByteArray({ 1, 2, 3 }), R"({"type":"PackedByteArray","args":[1,2,3]})");
|
||||
test(PackedInt32Array({ 1, 2, 3 }), R"({"type":"PackedInt32Array","args":[1,2,3]})");
|
||||
test(PackedInt64Array({ 1, 2, 3 }), R"({"type":"PackedInt64Array","args":[1,2,3]})");
|
||||
test(PackedFloat32Array({ 1, 2, 3 }), R"({"type":"PackedFloat32Array","args":[1.0,2.0,3.0]})");
|
||||
test(PackedFloat64Array({ 1, 2, 3 }), R"({"type":"PackedFloat64Array","args":[1.0,2.0,3.0]})");
|
||||
test(PackedStringArray({ "a", "b", "c" }), R"({"type":"PackedStringArray","args":["a","b","c"]})");
|
||||
|
||||
const PackedVector2Array pv2arr({ Vector2(1, 2), Vector2(3, 4), Vector2(5, 6) });
|
||||
test(pv2arr, R"({"type":"PackedVector2Array","args":[1.0,2.0,3.0,4.0,5.0,6.0]})");
|
||||
|
||||
const PackedVector3Array pv3arr({ Vector3(1, 2, 3), Vector3(4, 5, 6), Vector3(7, 8, 9) });
|
||||
test(pv3arr, R"({"type":"PackedVector3Array","args":[1.0,2.0,3.0,4.0,5.0,6.0,7.0,8.0,9.0]})");
|
||||
|
||||
const PackedColorArray pcolarr({ Color(1, 2, 3, 4), Color(5, 6, 7, 8), Color(9, 10, 11, 12) });
|
||||
test(pcolarr, R"({"type":"PackedColorArray","args":[1.0,2.0,3.0,4.0,5.0,6.0,7.0,8.0,9.0,10.0,11.0,12.0]})");
|
||||
|
||||
const PackedVector4Array pv4arr({ Vector4(1, 2, 3, 4), Vector4(5, 6, 7, 8), Vector4(9, 10, 11, 12) });
|
||||
test(pv4arr, R"({"type":"PackedVector4Array","args":[1.0,2.0,3.0,4.0,5.0,6.0,7.0,8.0,9.0,10.0,11.0,12.0]})");
|
||||
}
|
||||
|
||||
} // namespace TestJSONNative
|
||||
|
||||
#endif // TEST_JSON_NATIVE_H
|
||||
170
engine/tests/core/io/test_logger.h
Normal file
170
engine/tests/core/io/test_logger.h
Normal file
|
|
@ -0,0 +1,170 @@
|
|||
/**************************************************************************/
|
||||
/* test_logger.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#ifndef TEST_LOGGER_H
|
||||
#define TEST_LOGGER_H
|
||||
|
||||
#include "core/io/dir_access.h"
|
||||
#include "core/io/logger.h"
|
||||
#include "modules/regex/regex.h"
|
||||
#include "tests/test_macros.h"
|
||||
|
||||
namespace TestLogger {
|
||||
|
||||
constexpr int sleep_duration = 1200000;
|
||||
|
||||
void initialize_logs() {
|
||||
ProjectSettings::get_singleton()->set_setting("application/config/name", "godot_tests");
|
||||
DirAccess::make_dir_recursive_absolute(OS::get_singleton()->get_user_data_dir().path_join("logs"));
|
||||
}
|
||||
|
||||
void cleanup_logs() {
|
||||
ProjectSettings::get_singleton()->set_setting("application/config/name", "godot_tests");
|
||||
Ref<DirAccess> dir = DirAccess::open("user://logs");
|
||||
dir->list_dir_begin();
|
||||
String file = dir->get_next();
|
||||
while (file != "") {
|
||||
if (file.match("*.log")) {
|
||||
dir->remove(file);
|
||||
}
|
||||
file = dir->get_next();
|
||||
}
|
||||
DirAccess::remove_absolute(OS::get_singleton()->get_user_data_dir().path_join("logs"));
|
||||
DirAccess::remove_absolute(OS::get_singleton()->get_user_data_dir());
|
||||
}
|
||||
|
||||
TEST_CASE("[Logger][RotatedFileLogger] Creates the first log file and logs on it") {
|
||||
initialize_logs();
|
||||
|
||||
String waiting_for_godot = "Waiting for Godot";
|
||||
RotatedFileLogger logger("user://logs/godot.log");
|
||||
logger.logf("%s", "Waiting for Godot");
|
||||
|
||||
Error err = Error::OK;
|
||||
Ref<FileAccess> log = FileAccess::open("user://logs/godot.log", FileAccess::READ, &err);
|
||||
CHECK_EQ(err, Error::OK);
|
||||
CHECK_EQ(log->get_as_text(), waiting_for_godot);
|
||||
|
||||
cleanup_logs();
|
||||
}
|
||||
|
||||
void get_log_files(Vector<String> &log_files) {
|
||||
Ref<DirAccess> dir = DirAccess::open("user://logs");
|
||||
dir->list_dir_begin();
|
||||
String file = dir->get_next();
|
||||
while (file != "") {
|
||||
// Filtering godot.log because ordered_insert will put it first and should be the last.
|
||||
if (file.match("*.log") && file != "godot.log") {
|
||||
log_files.ordered_insert(file);
|
||||
}
|
||||
file = dir->get_next();
|
||||
}
|
||||
if (FileAccess::exists("user://logs/godot.log")) {
|
||||
log_files.push_back("godot.log");
|
||||
}
|
||||
}
|
||||
|
||||
// All things related to log file rotation are in the same test because testing it require some sleeps.
|
||||
TEST_CASE("[Logger][RotatedFileLogger] Rotates logs files") {
|
||||
initialize_logs();
|
||||
|
||||
Vector<String> all_waiting_for_godot;
|
||||
|
||||
const int number_of_files = 3;
|
||||
for (int i = 0; i < number_of_files; i++) {
|
||||
String waiting_for_godot = "Waiting for Godot " + itos(i);
|
||||
RotatedFileLogger logger("user://logs/godot.log", number_of_files);
|
||||
logger.logf("%s", waiting_for_godot.ascii().get_data());
|
||||
all_waiting_for_godot.push_back(waiting_for_godot);
|
||||
|
||||
// Required to ensure the rotation of the log file.
|
||||
OS::get_singleton()->delay_usec(sleep_duration);
|
||||
}
|
||||
|
||||
Vector<String> log_files;
|
||||
get_log_files(log_files);
|
||||
CHECK_MESSAGE(log_files.size() == number_of_files, "Did not rotate all files");
|
||||
|
||||
for (int i = 0; i < log_files.size(); i++) {
|
||||
Error err = Error::OK;
|
||||
Ref<FileAccess> log_file = FileAccess::open("user://logs/" + log_files[i], FileAccess::READ, &err);
|
||||
REQUIRE_EQ(err, Error::OK);
|
||||
CHECK_EQ(log_file->get_as_text(), all_waiting_for_godot[i]);
|
||||
}
|
||||
|
||||
// Required to ensure the rotation of the log file.
|
||||
OS::get_singleton()->delay_usec(sleep_duration);
|
||||
|
||||
// This time the oldest log must be removed and godot.log updated.
|
||||
String new_waiting_for_godot = "Waiting for Godot " + itos(number_of_files);
|
||||
all_waiting_for_godot = all_waiting_for_godot.slice(1, all_waiting_for_godot.size());
|
||||
all_waiting_for_godot.push_back(new_waiting_for_godot);
|
||||
RotatedFileLogger logger("user://logs/godot.log", number_of_files);
|
||||
logger.logf("%s", new_waiting_for_godot.ascii().get_data());
|
||||
|
||||
log_files.clear();
|
||||
get_log_files(log_files);
|
||||
CHECK_MESSAGE(log_files.size() == number_of_files, "Did not remove old log file");
|
||||
|
||||
for (int i = 0; i < log_files.size(); i++) {
|
||||
Error err = Error::OK;
|
||||
Ref<FileAccess> log_file = FileAccess::open("user://logs/" + log_files[i], FileAccess::READ, &err);
|
||||
REQUIRE_EQ(err, Error::OK);
|
||||
CHECK_EQ(log_file->get_as_text(), all_waiting_for_godot[i]);
|
||||
}
|
||||
|
||||
cleanup_logs();
|
||||
}
|
||||
|
||||
TEST_CASE("[Logger][CompositeLogger] Logs the same into multiple loggers") {
|
||||
initialize_logs();
|
||||
|
||||
Vector<Logger *> all_loggers;
|
||||
all_loggers.push_back(memnew(RotatedFileLogger("user://logs/godot_logger_1.log", 1)));
|
||||
all_loggers.push_back(memnew(RotatedFileLogger("user://logs/godot_logger_2.log", 1)));
|
||||
|
||||
String waiting_for_godot = "Waiting for Godot";
|
||||
CompositeLogger logger(all_loggers);
|
||||
logger.logf("%s", "Waiting for Godot");
|
||||
|
||||
Error err = Error::OK;
|
||||
Ref<FileAccess> log = FileAccess::open("user://logs/godot_logger_1.log", FileAccess::READ, &err);
|
||||
CHECK_EQ(err, Error::OK);
|
||||
CHECK_EQ(log->get_as_text(), waiting_for_godot);
|
||||
log = FileAccess::open("user://logs/godot_logger_2.log", FileAccess::READ, &err);
|
||||
CHECK_EQ(err, Error::OK);
|
||||
CHECK_EQ(log->get_as_text(), waiting_for_godot);
|
||||
|
||||
cleanup_logs();
|
||||
}
|
||||
|
||||
} // namespace TestLogger
|
||||
|
||||
#endif // TEST_LOGGER_H
|
||||
|
|
@ -90,6 +90,20 @@ TEST_CASE("[Marshalls] Unsigned 64 bit integer decoding") {
|
|||
CHECK(decode_uint64(arr) == 0x0f123456789abcdef);
|
||||
}
|
||||
|
||||
TEST_CASE("[Marshalls] Floating point half precision encoding") {
|
||||
uint8_t arr[2];
|
||||
|
||||
// Decimal: 0.33325195
|
||||
// IEEE 754 half-precision binary floating-point format:
|
||||
// sign exponent (5 bits) fraction (10 bits)
|
||||
// 0 01101 0101010101
|
||||
// Hexadecimal: 0x3555
|
||||
unsigned int actual_size = encode_half(0.33325195f, arr);
|
||||
CHECK(actual_size == sizeof(uint16_t));
|
||||
CHECK(arr[0] == 0x55);
|
||||
CHECK(arr[1] == 0x35);
|
||||
}
|
||||
|
||||
TEST_CASE("[Marshalls] Floating point single precision encoding") {
|
||||
uint8_t arr[4];
|
||||
|
||||
|
|
@ -126,6 +140,13 @@ TEST_CASE("[Marshalls] Floating point double precision encoding") {
|
|||
CHECK(arr[7] == 0x3f);
|
||||
}
|
||||
|
||||
TEST_CASE("[Marshalls] Floating point half precision decoding") {
|
||||
uint8_t arr[] = { 0x55, 0x35 };
|
||||
|
||||
// See floating point half precision encoding test case for details behind expected values.
|
||||
CHECK(decode_half(arr) == 0.33325195f);
|
||||
}
|
||||
|
||||
TEST_CASE("[Marshalls] Floating point single precision decoding") {
|
||||
uint8_t arr[] = { 0x00, 0x00, 0x20, 0x3e };
|
||||
|
||||
|
|
@ -160,7 +181,7 @@ TEST_CASE("[Marshalls] NIL Variant encoding") {
|
|||
uint8_t buffer[4];
|
||||
|
||||
CHECK(encode_variant(variant, buffer, r_len) == OK);
|
||||
CHECK_MESSAGE(r_len == 4, "Length == 4 bytes for header");
|
||||
CHECK_MESSAGE(r_len == 4, "Length == 4 bytes for header.");
|
||||
CHECK_MESSAGE(buffer[0] == 0x00, "Variant::NIL");
|
||||
CHECK(buffer[1] == 0x00);
|
||||
CHECK(buffer[2] == 0x00);
|
||||
|
|
@ -174,7 +195,7 @@ TEST_CASE("[Marshalls] INT 32 bit Variant encoding") {
|
|||
uint8_t buffer[8];
|
||||
|
||||
CHECK(encode_variant(variant, buffer, r_len) == OK);
|
||||
CHECK_MESSAGE(r_len == 8, "Length == 4 bytes for header + 4 bytes for int32_t");
|
||||
CHECK_MESSAGE(r_len == 8, "Length == 4 bytes for header + 4 bytes for `int32_t`.");
|
||||
CHECK_MESSAGE(buffer[0] == 0x02, "Variant::INT");
|
||||
CHECK(buffer[1] == 0x00);
|
||||
CHECK(buffer[2] == 0x00);
|
||||
|
|
@ -192,7 +213,7 @@ TEST_CASE("[Marshalls] INT 64 bit Variant encoding") {
|
|||
uint8_t buffer[12];
|
||||
|
||||
CHECK(encode_variant(variant, buffer, r_len) == OK);
|
||||
CHECK_MESSAGE(r_len == 12, "Length == 4 bytes for header + 8 bytes for int64_t");
|
||||
CHECK_MESSAGE(r_len == 12, "Length == 4 bytes for header + 8 bytes for `int64_t`.");
|
||||
CHECK_MESSAGE(buffer[0] == 0x02, "Variant::INT");
|
||||
CHECK(buffer[1] == 0x00);
|
||||
CHECK_MESSAGE(buffer[2] == 0x01, "HEADER_DATA_FLAG_64");
|
||||
|
|
@ -214,7 +235,7 @@ TEST_CASE("[Marshalls] FLOAT single precision Variant encoding") {
|
|||
uint8_t buffer[8];
|
||||
|
||||
CHECK(encode_variant(variant, buffer, r_len) == OK);
|
||||
CHECK_MESSAGE(r_len == 8, "Length == 4 bytes for header + 4 bytes for float");
|
||||
CHECK_MESSAGE(r_len == 8, "Length == 4 bytes for header + 4 bytes for `float`.");
|
||||
CHECK_MESSAGE(buffer[0] == 0x03, "Variant::FLOAT");
|
||||
CHECK(buffer[1] == 0x00);
|
||||
CHECK(buffer[2] == 0x00);
|
||||
|
|
@ -232,7 +253,7 @@ TEST_CASE("[Marshalls] FLOAT double precision Variant encoding") {
|
|||
uint8_t buffer[12];
|
||||
|
||||
CHECK(encode_variant(variant, buffer, r_len) == OK);
|
||||
CHECK_MESSAGE(r_len == 12, "Length == 4 bytes for header + 8 bytes for double");
|
||||
CHECK_MESSAGE(r_len == 12, "Length == 4 bytes for header + 8 bytes for `double`.");
|
||||
CHECK_MESSAGE(buffer[0] == 0x03, "Variant::FLOAT");
|
||||
CHECK(buffer[1] == 0x00);
|
||||
CHECK_MESSAGE(buffer[2] == 0x01, "HEADER_DATA_FLAG_64");
|
||||
|
|
@ -335,10 +356,10 @@ TEST_CASE("[Marshalls] Typed array encoding") {
|
|||
uint8_t buffer[24];
|
||||
|
||||
CHECK(encode_variant(array, buffer, r_len) == OK);
|
||||
CHECK_MESSAGE(r_len == 24, "Length == 4 bytes for header + 4 bytes for array type + 4 bytes for array size + 12 bytes for element");
|
||||
CHECK_MESSAGE(r_len == 24, "Length == 4 bytes for header + 4 bytes for array type + 4 bytes for array size + 12 bytes for element.");
|
||||
CHECK_MESSAGE(buffer[0] == 0x1c, "Variant::ARRAY");
|
||||
CHECK(buffer[1] == 0x00);
|
||||
CHECK_MESSAGE(buffer[2] == 0x01, "HEADER_DATA_FIELD_TYPED_ARRAY_BUILTIN");
|
||||
CHECK_MESSAGE(buffer[2] == 0x01, "CONTAINER_TYPE_KIND_BUILTIN");
|
||||
CHECK(buffer[3] == 0x00);
|
||||
// Check array type.
|
||||
CHECK_MESSAGE(buffer[4] == 0x02, "Variant::INT");
|
||||
|
|
@ -370,7 +391,7 @@ TEST_CASE("[Marshalls] Typed array decoding") {
|
|||
Variant variant;
|
||||
int r_len;
|
||||
uint8_t buffer[] = {
|
||||
0x1c, 0x00, 0x01, 0x00, // Variant::ARRAY, HEADER_DATA_FIELD_TYPED_ARRAY_BUILTIN
|
||||
0x1c, 0x00, 0x01, 0x00, // Variant::ARRAY, CONTAINER_TYPE_KIND_BUILTIN
|
||||
0x02, 0x00, 0x00, 0x00, // Array type (Variant::INT).
|
||||
0x01, 0x00, 0x00, 0x00, // Array size.
|
||||
0x02, 0x00, 0x01, 0x00, // Element type (Variant::INT, HEADER_DATA_FLAG_64).
|
||||
|
|
@ -386,6 +407,89 @@ TEST_CASE("[Marshalls] Typed array decoding") {
|
|||
CHECK(array[0] == Variant(uint64_t(0x0f123456789abcdef)));
|
||||
}
|
||||
|
||||
TEST_CASE("[Marshalls] Typed dicttionary encoding") {
|
||||
int r_len;
|
||||
Dictionary dictionary;
|
||||
dictionary.set_typed(Variant::INT, StringName(), Ref<Script>(), Variant::INT, StringName(), Ref<Script>());
|
||||
dictionary[Variant(uint64_t(0x0f123456789abcdef))] = Variant(uint64_t(0x0f123456789abcdef));
|
||||
uint8_t buffer[40];
|
||||
|
||||
CHECK(encode_variant(dictionary, buffer, r_len) == OK);
|
||||
CHECK_MESSAGE(r_len == 40, "Length == 4 bytes for header + 8 bytes for dictionary type + 4 bytes for dictionary size + 24 bytes for key-value pair.");
|
||||
CHECK_MESSAGE(buffer[0] == 0x1b, "Variant::DICTIONARY");
|
||||
CHECK(buffer[1] == 0x00);
|
||||
CHECK_MESSAGE(buffer[2] == 0x05, "key: CONTAINER_TYPE_KIND_BUILTIN | value: CONTAINER_TYPE_KIND_BUILTIN");
|
||||
CHECK(buffer[3] == 0x00);
|
||||
// Check dictionary key type.
|
||||
CHECK_MESSAGE(buffer[4] == 0x02, "Variant::INT");
|
||||
CHECK(buffer[5] == 0x00);
|
||||
CHECK(buffer[6] == 0x00);
|
||||
CHECK(buffer[7] == 0x00);
|
||||
// Check dictionary value type.
|
||||
CHECK_MESSAGE(buffer[8] == 0x02, "Variant::INT");
|
||||
CHECK(buffer[9] == 0x00);
|
||||
CHECK(buffer[10] == 0x00);
|
||||
CHECK(buffer[11] == 0x00);
|
||||
// Check dictionary size.
|
||||
CHECK(buffer[12] == 0x01);
|
||||
CHECK(buffer[13] == 0x00);
|
||||
CHECK(buffer[14] == 0x00);
|
||||
CHECK(buffer[15] == 0x00);
|
||||
// Check key type.
|
||||
CHECK_MESSAGE(buffer[16] == 0x02, "Variant::INT");
|
||||
CHECK(buffer[17] == 0x00);
|
||||
CHECK_MESSAGE(buffer[18] == 0x01, "HEADER_DATA_FLAG_64");
|
||||
CHECK(buffer[19] == 0x00);
|
||||
// Check key value.
|
||||
CHECK(buffer[20] == 0xef);
|
||||
CHECK(buffer[21] == 0xcd);
|
||||
CHECK(buffer[22] == 0xab);
|
||||
CHECK(buffer[23] == 0x89);
|
||||
CHECK(buffer[24] == 0x67);
|
||||
CHECK(buffer[25] == 0x45);
|
||||
CHECK(buffer[26] == 0x23);
|
||||
CHECK(buffer[27] == 0xf1);
|
||||
// Check value type.
|
||||
CHECK_MESSAGE(buffer[28] == 0x02, "Variant::INT");
|
||||
CHECK(buffer[29] == 0x00);
|
||||
CHECK_MESSAGE(buffer[30] == 0x01, "HEADER_DATA_FLAG_64");
|
||||
CHECK(buffer[31] == 0x00);
|
||||
// Check value value.
|
||||
CHECK(buffer[32] == 0xef);
|
||||
CHECK(buffer[33] == 0xcd);
|
||||
CHECK(buffer[34] == 0xab);
|
||||
CHECK(buffer[35] == 0x89);
|
||||
CHECK(buffer[36] == 0x67);
|
||||
CHECK(buffer[37] == 0x45);
|
||||
CHECK(buffer[38] == 0x23);
|
||||
CHECK(buffer[39] == 0xf1);
|
||||
}
|
||||
|
||||
TEST_CASE("[Marshalls] Typed dictionary decoding") {
|
||||
Variant variant;
|
||||
int r_len;
|
||||
uint8_t buffer[] = {
|
||||
0x1b, 0x00, 0x05, 0x00, // Variant::DICTIONARY, key: CONTAINER_TYPE_KIND_BUILTIN | value: CONTAINER_TYPE_KIND_BUILTIN
|
||||
0x02, 0x00, 0x00, 0x00, // Dictionary key type (Variant::INT).
|
||||
0x02, 0x00, 0x00, 0x00, // Dictionary value type (Variant::INT).
|
||||
0x01, 0x00, 0x00, 0x00, // Dictionary size.
|
||||
0x02, 0x00, 0x01, 0x00, // Key type (Variant::INT, HEADER_DATA_FLAG_64).
|
||||
0xef, 0xcd, 0xab, 0x89, 0x67, 0x45, 0x23, 0xf1, // Key value.
|
||||
0x02, 0x00, 0x01, 0x00, // Value type (Variant::INT, HEADER_DATA_FLAG_64).
|
||||
0xef, 0xcd, 0xab, 0x89, 0x67, 0x45, 0x23, 0xf1, // Value value.
|
||||
};
|
||||
|
||||
CHECK(decode_variant(variant, buffer, 40, &r_len) == OK);
|
||||
CHECK(r_len == 40);
|
||||
CHECK(variant.get_type() == Variant::DICTIONARY);
|
||||
Dictionary dictionary = variant;
|
||||
CHECK(dictionary.get_typed_key_builtin() == Variant::INT);
|
||||
CHECK(dictionary.get_typed_value_builtin() == Variant::INT);
|
||||
CHECK(dictionary.size() == 1);
|
||||
CHECK(dictionary.has(Variant(uint64_t(0x0f123456789abcdef))));
|
||||
CHECK(dictionary[Variant(uint64_t(0x0f123456789abcdef))] == Variant(uint64_t(0x0f123456789abcdef)));
|
||||
}
|
||||
|
||||
} // namespace TestMarshalls
|
||||
|
||||
#endif // TEST_MARSHALLS_H
|
||||
|
|
|
|||
204
engine/tests/core/io/test_packet_peer.h
Normal file
204
engine/tests/core/io/test_packet_peer.h
Normal file
|
|
@ -0,0 +1,204 @@
|
|||
/**************************************************************************/
|
||||
/* test_packet_peer.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#ifndef TEST_PACKET_PEER_H
|
||||
#define TEST_PACKET_PEER_H
|
||||
|
||||
#include "core/io/packet_peer.h"
|
||||
#include "tests/test_macros.h"
|
||||
|
||||
namespace TestPacketPeer {
|
||||
|
||||
TEST_CASE("[PacketPeer][PacketPeerStream] Encode buffer max size") {
|
||||
Ref<PacketPeerStream> pps;
|
||||
pps.instantiate();
|
||||
|
||||
SUBCASE("Default value") {
|
||||
CHECK_EQ(pps->get_encode_buffer_max_size(), 8 * 1024 * 1024);
|
||||
}
|
||||
|
||||
SUBCASE("Max encode buffer must be at least 1024 bytes") {
|
||||
ERR_PRINT_OFF;
|
||||
pps->set_encode_buffer_max_size(42);
|
||||
ERR_PRINT_ON;
|
||||
|
||||
CHECK_EQ(pps->get_encode_buffer_max_size(), 8 * 1024 * 1024);
|
||||
}
|
||||
|
||||
SUBCASE("Max encode buffer cannot exceed 256 MiB") {
|
||||
ERR_PRINT_OFF;
|
||||
pps->set_encode_buffer_max_size((256 * 1024 * 1024) + 42);
|
||||
ERR_PRINT_ON;
|
||||
|
||||
CHECK_EQ(pps->get_encode_buffer_max_size(), 8 * 1024 * 1024);
|
||||
}
|
||||
|
||||
SUBCASE("Should be next power of two") {
|
||||
pps->set_encode_buffer_max_size(2000);
|
||||
|
||||
CHECK_EQ(pps->get_encode_buffer_max_size(), 2048);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("[PacketPeer][PacketPeerStream] Read a variant from peer") {
|
||||
String godot_rules = "Godot Rules!!!";
|
||||
|
||||
Ref<StreamPeerBuffer> spb;
|
||||
spb.instantiate();
|
||||
spb->put_var(godot_rules);
|
||||
spb->seek(0);
|
||||
|
||||
Ref<PacketPeerStream> pps;
|
||||
pps.instantiate();
|
||||
pps->set_stream_peer(spb);
|
||||
|
||||
Variant value;
|
||||
CHECK_EQ(pps->get_var(value), Error::OK);
|
||||
CHECK_EQ(String(value), godot_rules);
|
||||
}
|
||||
|
||||
TEST_CASE("[PacketPeer][PacketPeerStream] Read a variant from peer fails") {
|
||||
Ref<PacketPeerStream> pps;
|
||||
pps.instantiate();
|
||||
|
||||
Variant value;
|
||||
ERR_PRINT_OFF;
|
||||
CHECK_EQ(pps->get_var(value), Error::ERR_UNCONFIGURED);
|
||||
ERR_PRINT_ON;
|
||||
}
|
||||
|
||||
TEST_CASE("[PacketPeer][PacketPeerStream] Put a variant to peer") {
|
||||
String godot_rules = "Godot Rules!!!";
|
||||
|
||||
Ref<StreamPeerBuffer> spb;
|
||||
spb.instantiate();
|
||||
|
||||
Ref<PacketPeerStream> pps;
|
||||
pps.instantiate();
|
||||
pps->set_stream_peer(spb);
|
||||
|
||||
CHECK_EQ(pps->put_var(godot_rules), Error::OK);
|
||||
|
||||
spb->seek(0);
|
||||
CHECK_EQ(String(spb->get_var()), godot_rules);
|
||||
}
|
||||
|
||||
TEST_CASE("[PacketPeer][PacketPeerStream] Put a variant to peer out of memory failure") {
|
||||
String more_than_1mb = String("*").repeat(1024 + 1);
|
||||
|
||||
Ref<StreamPeerBuffer> spb;
|
||||
spb.instantiate();
|
||||
|
||||
Ref<PacketPeerStream> pps;
|
||||
pps.instantiate();
|
||||
pps->set_stream_peer(spb);
|
||||
pps->set_encode_buffer_max_size(1024);
|
||||
|
||||
ERR_PRINT_OFF;
|
||||
CHECK_EQ(pps->put_var(more_than_1mb), Error::ERR_OUT_OF_MEMORY);
|
||||
ERR_PRINT_ON;
|
||||
}
|
||||
|
||||
TEST_CASE("[PacketPeer][PacketPeerStream] Get packet buffer") {
|
||||
String godot_rules = "Godot Rules!!!";
|
||||
|
||||
Ref<StreamPeerBuffer> spb;
|
||||
spb.instantiate();
|
||||
// First 4 bytes are the length of the string.
|
||||
CharString cs = godot_rules.ascii();
|
||||
Vector<uint8_t> buffer = { (uint8_t)(cs.length() + 1), 0, 0, 0 };
|
||||
buffer.resize_zeroed(4 + cs.length() + 1);
|
||||
memcpy(buffer.ptrw() + 4, cs.get_data(), cs.length());
|
||||
spb->set_data_array(buffer);
|
||||
|
||||
Ref<PacketPeerStream> pps;
|
||||
pps.instantiate();
|
||||
pps->set_stream_peer(spb);
|
||||
|
||||
buffer.clear();
|
||||
CHECK_EQ(pps->get_packet_buffer(buffer), Error::OK);
|
||||
|
||||
CHECK_EQ(String(reinterpret_cast<const char *>(buffer.ptr())), godot_rules);
|
||||
}
|
||||
|
||||
TEST_CASE("[PacketPeer][PacketPeerStream] Get packet buffer from an empty peer") {
|
||||
Ref<StreamPeerBuffer> spb;
|
||||
spb.instantiate();
|
||||
|
||||
Ref<PacketPeerStream> pps;
|
||||
pps.instantiate();
|
||||
pps->set_stream_peer(spb);
|
||||
|
||||
Vector<uint8_t> buffer;
|
||||
ERR_PRINT_OFF;
|
||||
CHECK_EQ(pps->get_packet_buffer(buffer), Error::ERR_UNAVAILABLE);
|
||||
ERR_PRINT_ON;
|
||||
CHECK_EQ(buffer.size(), 0);
|
||||
}
|
||||
|
||||
TEST_CASE("[PacketPeer][PacketPeerStream] Put packet buffer") {
|
||||
String godot_rules = "Godot Rules!!!";
|
||||
|
||||
Ref<StreamPeerBuffer> spb;
|
||||
spb.instantiate();
|
||||
|
||||
Ref<PacketPeerStream> pps;
|
||||
pps.instantiate();
|
||||
pps->set_stream_peer(spb);
|
||||
|
||||
CHECK_EQ(pps->put_packet_buffer(godot_rules.to_ascii_buffer()), Error::OK);
|
||||
|
||||
spb->seek(0);
|
||||
CHECK_EQ(spb->get_string(), godot_rules);
|
||||
// First 4 bytes are the length of the string.
|
||||
CharString cs = godot_rules.ascii();
|
||||
Vector<uint8_t> buffer = { (uint8_t)cs.length(), 0, 0, 0 };
|
||||
buffer.resize(4 + cs.length());
|
||||
memcpy(buffer.ptrw() + 4, cs.get_data(), cs.length());
|
||||
CHECK_EQ(spb->get_data_array(), buffer);
|
||||
}
|
||||
|
||||
TEST_CASE("[PacketPeer][PacketPeerStream] Put packet buffer when is empty") {
|
||||
Ref<StreamPeerBuffer> spb;
|
||||
spb.instantiate();
|
||||
|
||||
Ref<PacketPeerStream> pps;
|
||||
pps.instantiate();
|
||||
pps->set_stream_peer(spb);
|
||||
|
||||
Vector<uint8_t> buffer;
|
||||
CHECK_EQ(pps->put_packet_buffer(buffer), Error::OK);
|
||||
|
||||
CHECK_EQ(spb->get_size(), 0);
|
||||
}
|
||||
|
||||
} // namespace TestPacketPeer
|
||||
|
||||
#endif // TEST_PACKET_PEER_H
|
||||
71
engine/tests/core/io/test_resource_uid.h
Normal file
71
engine/tests/core/io/test_resource_uid.h
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
/**************************************************************************/
|
||||
/* test_resource_uid.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#ifndef TEST_RESOURCE_UID_H
|
||||
#define TEST_RESOURCE_UID_H
|
||||
|
||||
#include "core/io/resource_uid.h"
|
||||
|
||||
#include "thirdparty/doctest/doctest.h"
|
||||
|
||||
#include "tests/test_macros.h"
|
||||
|
||||
namespace TestResourceUID {
|
||||
|
||||
TEST_CASE("[ResourceUID] Must encode/decode maximum/minimum UID correctly") {
|
||||
CHECK_MESSAGE(ResourceUID::get_singleton()->id_to_text(0x7fffffffffffffff) == "uid://d4n4ub6itg400", "Maximum UID must encode correctly.");
|
||||
CHECK_MESSAGE(ResourceUID::get_singleton()->text_to_id("uid://d4n4ub6itg400") == 0x7fffffffffffffff, "Maximum UID must decode correctly.");
|
||||
|
||||
CHECK_MESSAGE(ResourceUID::get_singleton()->id_to_text(0) == "uid://a", "Minimum UID must encode correctly.");
|
||||
CHECK_MESSAGE(ResourceUID::get_singleton()->text_to_id("uid://a") == 0, "Minimum UID must decode correctly.");
|
||||
}
|
||||
|
||||
TEST_CASE("[ResourceUID] Must encode and decode invalid UIDs correctly") {
|
||||
ResourceUID *rid = ResourceUID::get_singleton();
|
||||
CHECK_MESSAGE(rid->id_to_text(-1) == "uid://<invalid>", "Invalid UID -1 must encode correctly.");
|
||||
CHECK_MESSAGE(rid->text_to_id("uid://<invalid>") == -1, "Invalid UID -1 must decode correctly.");
|
||||
|
||||
CHECK_MESSAGE(rid->id_to_text(-2) == rid->id_to_text(-1), "Invalid UID -2 must encode to the same as -1.");
|
||||
|
||||
CHECK_MESSAGE(rid->text_to_id("dm3rdgs30kfci") == -1, "UID without scheme must decode correctly.");
|
||||
}
|
||||
|
||||
TEST_CASE("[ResourceUID] Must encode and decode various UIDs correctly") {
|
||||
ResourceUID *rid = ResourceUID::get_singleton();
|
||||
CHECK_MESSAGE(rid->id_to_text(1) == "uid://b", "UID 1 must encode correctly.");
|
||||
CHECK_MESSAGE(rid->text_to_id("uid://b") == 1, "UID 1 must decode correctly.");
|
||||
|
||||
CHECK_MESSAGE(rid->id_to_text(8060368642360689600) == "uid://dm3rdgs30kfci", "A normal UID must encode correctly.");
|
||||
CHECK_MESSAGE(rid->text_to_id("uid://dm3rdgs30kfci") == 8060368642360689600, "A normal UID must decode correctly.");
|
||||
}
|
||||
|
||||
} // namespace TestResourceUID
|
||||
|
||||
#endif // TEST_RESOURCE_UID_H
|
||||
311
engine/tests/core/io/test_stream_peer.h
Normal file
311
engine/tests/core/io/test_stream_peer.h
Normal file
|
|
@ -0,0 +1,311 @@
|
|||
/**************************************************************************/
|
||||
/* test_stream_peer.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#ifndef TEST_STREAM_PEER_H
|
||||
#define TEST_STREAM_PEER_H
|
||||
|
||||
#include "core/io/stream_peer.h"
|
||||
#include "tests/test_macros.h"
|
||||
|
||||
namespace TestStreamPeer {
|
||||
|
||||
TEST_CASE("[StreamPeer] Initialization through StreamPeerBuffer") {
|
||||
Ref<StreamPeerBuffer> spb;
|
||||
spb.instantiate();
|
||||
|
||||
CHECK_EQ(spb->is_big_endian_enabled(), false);
|
||||
}
|
||||
|
||||
TEST_CASE("[StreamPeer] Get and sets through StreamPeerBuffer") {
|
||||
Ref<StreamPeerBuffer> spb;
|
||||
spb.instantiate();
|
||||
|
||||
SUBCASE("A int8_t value") {
|
||||
int8_t value = 42;
|
||||
|
||||
spb->clear();
|
||||
spb->put_8(value);
|
||||
spb->seek(0);
|
||||
|
||||
CHECK_EQ(spb->get_8(), value);
|
||||
}
|
||||
|
||||
SUBCASE("A uint8_t value") {
|
||||
uint8_t value = 42;
|
||||
|
||||
spb->clear();
|
||||
spb->put_u8(value);
|
||||
spb->seek(0);
|
||||
|
||||
CHECK_EQ(spb->get_u8(), value);
|
||||
}
|
||||
|
||||
SUBCASE("A int16_t value") {
|
||||
int16_t value = 42;
|
||||
|
||||
spb->clear();
|
||||
spb->put_16(value);
|
||||
spb->seek(0);
|
||||
|
||||
CHECK_EQ(spb->get_16(), value);
|
||||
}
|
||||
|
||||
SUBCASE("A uint16_t value") {
|
||||
uint16_t value = 42;
|
||||
|
||||
spb->clear();
|
||||
spb->put_u16(value);
|
||||
spb->seek(0);
|
||||
|
||||
CHECK_EQ(spb->get_u16(), value);
|
||||
}
|
||||
|
||||
SUBCASE("A int32_t value") {
|
||||
int32_t value = 42;
|
||||
|
||||
spb->clear();
|
||||
spb->put_32(value);
|
||||
spb->seek(0);
|
||||
|
||||
CHECK_EQ(spb->get_32(), value);
|
||||
}
|
||||
|
||||
SUBCASE("A uint32_t value") {
|
||||
uint32_t value = 42;
|
||||
|
||||
spb->clear();
|
||||
spb->put_u32(value);
|
||||
spb->seek(0);
|
||||
|
||||
CHECK_EQ(spb->get_u32(), value);
|
||||
}
|
||||
|
||||
SUBCASE("A int64_t value") {
|
||||
int64_t value = 42;
|
||||
|
||||
spb->clear();
|
||||
spb->put_64(value);
|
||||
spb->seek(0);
|
||||
|
||||
CHECK_EQ(spb->get_64(), value);
|
||||
}
|
||||
|
||||
SUBCASE("A int64_t value") {
|
||||
uint64_t value = 42;
|
||||
|
||||
spb->clear();
|
||||
spb->put_u64(value);
|
||||
spb->seek(0);
|
||||
|
||||
CHECK_EQ(spb->get_u64(), value);
|
||||
}
|
||||
|
||||
SUBCASE("A half-precision float value") {
|
||||
float value = 3.1415927f;
|
||||
float expected = 3.14062f;
|
||||
|
||||
spb->clear();
|
||||
spb->put_half(value);
|
||||
spb->seek(0);
|
||||
|
||||
CHECK(spb->get_half() == doctest::Approx(expected));
|
||||
}
|
||||
|
||||
SUBCASE("A float value") {
|
||||
float value = 42.0f;
|
||||
|
||||
spb->clear();
|
||||
spb->put_float(value);
|
||||
spb->seek(0);
|
||||
|
||||
CHECK_EQ(spb->get_float(), value);
|
||||
}
|
||||
|
||||
SUBCASE("A double value") {
|
||||
double value = 42.0;
|
||||
|
||||
spb->clear();
|
||||
spb->put_double(value);
|
||||
spb->seek(0);
|
||||
|
||||
CHECK_EQ(spb->get_double(), value);
|
||||
}
|
||||
|
||||
SUBCASE("A string value") {
|
||||
String value = "Hello, World!";
|
||||
|
||||
spb->clear();
|
||||
spb->put_string(value);
|
||||
spb->seek(0);
|
||||
|
||||
CHECK_EQ(spb->get_string(), value);
|
||||
}
|
||||
|
||||
SUBCASE("A utf8 string value") {
|
||||
String value = String::utf8("Hello✩, World✩!");
|
||||
|
||||
spb->clear();
|
||||
spb->put_utf8_string(value);
|
||||
spb->seek(0);
|
||||
|
||||
CHECK_EQ(spb->get_utf8_string(), value);
|
||||
}
|
||||
|
||||
SUBCASE("A variant value") {
|
||||
Array value;
|
||||
value.push_front(42);
|
||||
value.push_front("Hello, World!");
|
||||
|
||||
spb->clear();
|
||||
spb->put_var(value);
|
||||
spb->seek(0);
|
||||
|
||||
CHECK_EQ(spb->get_var(), value);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("[StreamPeer] Get and sets big endian through StreamPeerBuffer") {
|
||||
Ref<StreamPeerBuffer> spb;
|
||||
spb.instantiate();
|
||||
spb->set_big_endian(true);
|
||||
|
||||
SUBCASE("A int16_t value") {
|
||||
int16_t value = 42;
|
||||
|
||||
spb->clear();
|
||||
spb->put_16(value);
|
||||
spb->seek(0);
|
||||
|
||||
CHECK_EQ(spb->get_16(), value);
|
||||
}
|
||||
|
||||
SUBCASE("A uint16_t value") {
|
||||
uint16_t value = 42;
|
||||
|
||||
spb->clear();
|
||||
spb->put_u16(value);
|
||||
spb->seek(0);
|
||||
|
||||
CHECK_EQ(spb->get_u16(), value);
|
||||
}
|
||||
|
||||
SUBCASE("A int32_t value") {
|
||||
int32_t value = 42;
|
||||
|
||||
spb->clear();
|
||||
spb->put_32(value);
|
||||
spb->seek(0);
|
||||
|
||||
CHECK_EQ(spb->get_32(), value);
|
||||
}
|
||||
|
||||
SUBCASE("A uint32_t value") {
|
||||
uint32_t value = 42;
|
||||
|
||||
spb->clear();
|
||||
spb->put_u32(value);
|
||||
spb->seek(0);
|
||||
|
||||
CHECK_EQ(spb->get_u32(), value);
|
||||
}
|
||||
|
||||
SUBCASE("A int64_t value") {
|
||||
int64_t value = 42;
|
||||
|
||||
spb->clear();
|
||||
spb->put_64(value);
|
||||
spb->seek(0);
|
||||
|
||||
CHECK_EQ(spb->get_64(), value);
|
||||
}
|
||||
|
||||
SUBCASE("A int64_t value") {
|
||||
uint64_t value = 42;
|
||||
|
||||
spb->clear();
|
||||
spb->put_u64(value);
|
||||
spb->seek(0);
|
||||
|
||||
CHECK_EQ(spb->get_u64(), value);
|
||||
}
|
||||
|
||||
SUBCASE("A float value") {
|
||||
float value = 42.0f;
|
||||
|
||||
spb->clear();
|
||||
spb->put_float(value);
|
||||
spb->seek(0);
|
||||
|
||||
CHECK_EQ(spb->get_float(), value);
|
||||
}
|
||||
|
||||
SUBCASE("A half-precision float value") {
|
||||
float value = 3.1415927f;
|
||||
float expected = 3.14062f;
|
||||
|
||||
spb->clear();
|
||||
spb->put_half(value);
|
||||
spb->seek(0);
|
||||
|
||||
CHECK(spb->get_half() == doctest::Approx(expected));
|
||||
}
|
||||
|
||||
SUBCASE("A double value") {
|
||||
double value = 42.0;
|
||||
|
||||
spb->clear();
|
||||
spb->put_double(value);
|
||||
spb->seek(0);
|
||||
|
||||
CHECK_EQ(spb->get_double(), value);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("[StreamPeer] Get string when there is no string") {
|
||||
Ref<StreamPeerBuffer> spb;
|
||||
spb.instantiate();
|
||||
|
||||
ERR_PRINT_OFF;
|
||||
CHECK_EQ(spb->get_string(), "");
|
||||
ERR_PRINT_ON;
|
||||
}
|
||||
|
||||
TEST_CASE("[StreamPeer] Get UTF8 string when there is no string") {
|
||||
Ref<StreamPeerBuffer> spb;
|
||||
spb.instantiate();
|
||||
|
||||
ERR_PRINT_OFF;
|
||||
CHECK_EQ(spb->get_utf8_string(), "");
|
||||
ERR_PRINT_ON;
|
||||
}
|
||||
|
||||
} // namespace TestStreamPeer
|
||||
|
||||
#endif // TEST_STREAM_PEER_H
|
||||
185
engine/tests/core/io/test_stream_peer_buffer.h
Normal file
185
engine/tests/core/io/test_stream_peer_buffer.h
Normal file
|
|
@ -0,0 +1,185 @@
|
|||
/**************************************************************************/
|
||||
/* test_stream_peer_buffer.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#ifndef TEST_STREAM_PEER_BUFFER_H
|
||||
#define TEST_STREAM_PEER_BUFFER_H
|
||||
|
||||
#include "core/io/stream_peer.h"
|
||||
#include "tests/test_macros.h"
|
||||
|
||||
namespace TestStreamPeerBuffer {
|
||||
|
||||
TEST_CASE("[StreamPeerBuffer] Initialization") {
|
||||
Ref<StreamPeerBuffer> spb;
|
||||
spb.instantiate();
|
||||
CHECK_EQ(spb->get_size(), 0);
|
||||
CHECK_EQ(spb->get_position(), 0);
|
||||
CHECK_EQ(spb->get_available_bytes(), 0);
|
||||
}
|
||||
|
||||
TEST_CASE("[StreamPeerBuffer] Seek") {
|
||||
Ref<StreamPeerBuffer> spb;
|
||||
spb.instantiate();
|
||||
uint8_t first = 5;
|
||||
uint8_t second = 7;
|
||||
uint8_t third = 11;
|
||||
|
||||
spb->put_u8(first);
|
||||
spb->put_u8(second);
|
||||
spb->put_u8(third);
|
||||
|
||||
spb->seek(0);
|
||||
CHECK_EQ(spb->get_u8(), first);
|
||||
CHECK_EQ(spb->get_u8(), second);
|
||||
CHECK_EQ(spb->get_u8(), third);
|
||||
|
||||
spb->seek(1);
|
||||
CHECK_EQ(spb->get_position(), 1);
|
||||
CHECK_EQ(spb->get_u8(), second);
|
||||
|
||||
spb->seek(1);
|
||||
ERR_PRINT_OFF;
|
||||
spb->seek(-1);
|
||||
ERR_PRINT_ON;
|
||||
CHECK_EQ(spb->get_position(), 1);
|
||||
ERR_PRINT_OFF;
|
||||
spb->seek(5);
|
||||
ERR_PRINT_ON;
|
||||
CHECK_EQ(spb->get_position(), 1);
|
||||
}
|
||||
|
||||
TEST_CASE("[StreamPeerBuffer] Resize") {
|
||||
Ref<StreamPeerBuffer> spb;
|
||||
spb.instantiate();
|
||||
CHECK_EQ(spb->get_size(), 0);
|
||||
CHECK_EQ(spb->get_position(), 0);
|
||||
CHECK_EQ(spb->get_available_bytes(), 0);
|
||||
|
||||
spb->resize(42);
|
||||
CHECK_EQ(spb->get_size(), 42);
|
||||
CHECK_EQ(spb->get_position(), 0);
|
||||
CHECK_EQ(spb->get_available_bytes(), 42);
|
||||
|
||||
spb->seek(21);
|
||||
CHECK_EQ(spb->get_size(), 42);
|
||||
CHECK_EQ(spb->get_position(), 21);
|
||||
CHECK_EQ(spb->get_available_bytes(), 21);
|
||||
}
|
||||
|
||||
TEST_CASE("[StreamPeerBuffer] Get underlying data array") {
|
||||
uint8_t first = 5;
|
||||
uint8_t second = 7;
|
||||
uint8_t third = 11;
|
||||
|
||||
Ref<StreamPeerBuffer> spb;
|
||||
spb.instantiate();
|
||||
spb->put_u8(first);
|
||||
spb->put_u8(second);
|
||||
spb->put_u8(third);
|
||||
|
||||
Vector<uint8_t> data_array = spb->get_data_array();
|
||||
|
||||
CHECK_EQ(data_array[0], first);
|
||||
CHECK_EQ(data_array[1], second);
|
||||
CHECK_EQ(data_array[2], third);
|
||||
}
|
||||
|
||||
TEST_CASE("[StreamPeerBuffer] Set underlying data array") {
|
||||
uint8_t first = 5;
|
||||
uint8_t second = 7;
|
||||
uint8_t third = 11;
|
||||
|
||||
Ref<StreamPeerBuffer> spb;
|
||||
spb.instantiate();
|
||||
spb->put_u8(1);
|
||||
spb->put_u8(2);
|
||||
spb->put_u8(3);
|
||||
|
||||
Vector<uint8_t> new_data_array;
|
||||
new_data_array.push_back(first);
|
||||
new_data_array.push_back(second);
|
||||
new_data_array.push_back(third);
|
||||
|
||||
spb->set_data_array(new_data_array);
|
||||
|
||||
CHECK_EQ(spb->get_u8(), first);
|
||||
CHECK_EQ(spb->get_u8(), second);
|
||||
CHECK_EQ(spb->get_u8(), third);
|
||||
}
|
||||
|
||||
TEST_CASE("[StreamPeerBuffer] Duplicate") {
|
||||
uint8_t first = 5;
|
||||
uint8_t second = 7;
|
||||
uint8_t third = 11;
|
||||
|
||||
Ref<StreamPeerBuffer> spb;
|
||||
spb.instantiate();
|
||||
spb->put_u8(first);
|
||||
spb->put_u8(second);
|
||||
spb->put_u8(third);
|
||||
|
||||
Ref<StreamPeerBuffer> spb2 = spb->duplicate();
|
||||
|
||||
CHECK_EQ(spb2->get_u8(), first);
|
||||
CHECK_EQ(spb2->get_u8(), second);
|
||||
CHECK_EQ(spb2->get_u8(), third);
|
||||
}
|
||||
|
||||
TEST_CASE("[StreamPeerBuffer] Put data with size equal to zero does nothing") {
|
||||
Ref<StreamPeerBuffer> spb;
|
||||
spb.instantiate();
|
||||
uint8_t data = 42;
|
||||
|
||||
Error error = spb->put_data((const uint8_t *)&data, 0);
|
||||
|
||||
CHECK_EQ(error, OK);
|
||||
CHECK_EQ(spb->get_size(), 0);
|
||||
CHECK_EQ(spb->get_position(), 0);
|
||||
CHECK_EQ(spb->get_available_bytes(), 0);
|
||||
}
|
||||
|
||||
TEST_CASE("[StreamPeerBuffer] Get data with invalid size returns an error") {
|
||||
Ref<StreamPeerBuffer> spb;
|
||||
spb.instantiate();
|
||||
uint8_t data = 42;
|
||||
spb->put_u8(data);
|
||||
spb->seek(0);
|
||||
|
||||
uint8_t data_out = 0;
|
||||
Error error = spb->get_data(&data_out, 3);
|
||||
|
||||
CHECK_EQ(error, ERR_INVALID_PARAMETER);
|
||||
CHECK_EQ(spb->get_size(), 1);
|
||||
CHECK_EQ(spb->get_position(), 1);
|
||||
}
|
||||
|
||||
} // namespace TestStreamPeerBuffer
|
||||
|
||||
#endif // TEST_STREAM_PEER_BUFFER_H
|
||||
255
engine/tests/core/io/test_tcp_server.h
Normal file
255
engine/tests/core/io/test_tcp_server.h
Normal file
|
|
@ -0,0 +1,255 @@
|
|||
/**************************************************************************/
|
||||
/* test_tcp_server.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#ifndef TEST_TCP_SERVER_H
|
||||
#define TEST_TCP_SERVER_H
|
||||
|
||||
#include "core/io/stream_peer_tcp.h"
|
||||
#include "core/io/tcp_server.h"
|
||||
#include "tests/test_macros.h"
|
||||
|
||||
#include <functional>
|
||||
|
||||
namespace TestTCPServer {
|
||||
|
||||
const int PORT = 12345;
|
||||
const IPAddress LOCALHOST("127.0.0.1");
|
||||
const uint32_t SLEEP_DURATION = 1000;
|
||||
const uint64_t MAX_WAIT_USEC = 2000000;
|
||||
|
||||
void wait_for_condition(std::function<bool()> f_test) {
|
||||
const uint64_t time = OS::get_singleton()->get_ticks_usec();
|
||||
while (!f_test() && (OS::get_singleton()->get_ticks_usec() - time) < MAX_WAIT_USEC) {
|
||||
OS::get_singleton()->delay_usec(SLEEP_DURATION);
|
||||
}
|
||||
}
|
||||
|
||||
Ref<TCPServer> create_server(const IPAddress &p_address, int p_port) {
|
||||
Ref<TCPServer> server;
|
||||
server.instantiate();
|
||||
|
||||
REQUIRE_EQ(server->listen(PORT, LOCALHOST), Error::OK);
|
||||
REQUIRE(server->is_listening());
|
||||
CHECK_FALSE(server->is_connection_available());
|
||||
|
||||
return server;
|
||||
}
|
||||
|
||||
Ref<StreamPeerTCP> create_client(const IPAddress &p_address, int p_port) {
|
||||
Ref<StreamPeerTCP> client;
|
||||
client.instantiate();
|
||||
|
||||
REQUIRE_EQ(client->connect_to_host(LOCALHOST, PORT), Error::OK);
|
||||
CHECK_EQ(client->get_connected_host(), LOCALHOST);
|
||||
CHECK_EQ(client->get_connected_port(), PORT);
|
||||
CHECK_EQ(client->get_status(), StreamPeerTCP::STATUS_CONNECTING);
|
||||
|
||||
return client;
|
||||
}
|
||||
|
||||
Ref<StreamPeerTCP> accept_connection(Ref<TCPServer> &p_server) {
|
||||
wait_for_condition([&]() {
|
||||
return p_server->is_connection_available();
|
||||
});
|
||||
|
||||
REQUIRE(p_server->is_connection_available());
|
||||
Ref<StreamPeerTCP> client_from_server = p_server->take_connection();
|
||||
REQUIRE(client_from_server.is_valid());
|
||||
CHECK_EQ(client_from_server->get_connected_host(), LOCALHOST);
|
||||
CHECK_EQ(client_from_server->get_status(), StreamPeerTCP::STATUS_CONNECTED);
|
||||
|
||||
return client_from_server;
|
||||
}
|
||||
|
||||
TEST_CASE("[TCPServer] Instantiation") {
|
||||
Ref<TCPServer> server;
|
||||
server.instantiate();
|
||||
|
||||
REQUIRE(server.is_valid());
|
||||
CHECK_EQ(false, server->is_listening());
|
||||
}
|
||||
|
||||
TEST_CASE("[TCPServer] Accept a connection and receive/send data") {
|
||||
Ref<TCPServer> server = create_server(LOCALHOST, PORT);
|
||||
Ref<StreamPeerTCP> client = create_client(LOCALHOST, PORT);
|
||||
Ref<StreamPeerTCP> client_from_server = accept_connection(server);
|
||||
|
||||
wait_for_condition([&]() {
|
||||
return client->poll() != Error::OK || client->get_status() == StreamPeerTCP::STATUS_CONNECTED;
|
||||
});
|
||||
|
||||
CHECK_EQ(client->get_status(), StreamPeerTCP::STATUS_CONNECTED);
|
||||
|
||||
// Sending data from client to server.
|
||||
const String hello_world = "Hello World!";
|
||||
client->put_string(hello_world);
|
||||
CHECK_EQ(client_from_server->get_string(), hello_world);
|
||||
|
||||
// Sending data from server to client.
|
||||
const float pi = 3.1415;
|
||||
client_from_server->put_float(pi);
|
||||
CHECK_EQ(client->get_float(), pi);
|
||||
|
||||
client->disconnect_from_host();
|
||||
server->stop();
|
||||
CHECK_FALSE(server->is_listening());
|
||||
}
|
||||
|
||||
TEST_CASE("[TCPServer] Handle multiple clients at the same time") {
|
||||
Ref<TCPServer> server = create_server(LOCALHOST, PORT);
|
||||
|
||||
Vector<Ref<StreamPeerTCP>> clients;
|
||||
for (int i = 0; i < 5; i++) {
|
||||
clients.push_back(create_client(LOCALHOST, PORT));
|
||||
}
|
||||
|
||||
Vector<Ref<StreamPeerTCP>> clients_from_server;
|
||||
for (int i = 0; i < clients.size(); i++) {
|
||||
clients_from_server.push_back(accept_connection(server));
|
||||
}
|
||||
|
||||
wait_for_condition([&]() {
|
||||
bool should_exit = true;
|
||||
for (Ref<StreamPeerTCP> &c : clients) {
|
||||
if (c->poll() != Error::OK) {
|
||||
return true;
|
||||
}
|
||||
StreamPeerTCP::Status status = c->get_status();
|
||||
if (status != StreamPeerTCP::STATUS_CONNECTED && status != StreamPeerTCP::STATUS_CONNECTING) {
|
||||
return true;
|
||||
}
|
||||
if (status != StreamPeerTCP::STATUS_CONNECTED) {
|
||||
should_exit = false;
|
||||
}
|
||||
}
|
||||
return should_exit;
|
||||
});
|
||||
|
||||
for (Ref<StreamPeerTCP> &c : clients) {
|
||||
REQUIRE_EQ(c->get_status(), StreamPeerTCP::STATUS_CONNECTED);
|
||||
}
|
||||
|
||||
// Sending data from each client to server.
|
||||
for (int i = 0; i < clients.size(); i++) {
|
||||
String hello_client = "Hello " + itos(i);
|
||||
clients[i]->put_string(hello_client);
|
||||
CHECK_EQ(clients_from_server[i]->get_string(), hello_client);
|
||||
}
|
||||
|
||||
for (Ref<StreamPeerTCP> &c : clients) {
|
||||
c->disconnect_from_host();
|
||||
}
|
||||
server->stop();
|
||||
}
|
||||
|
||||
TEST_CASE("[TCPServer] When stopped shouldn't accept new connections") {
|
||||
Ref<TCPServer> server = create_server(LOCALHOST, PORT);
|
||||
Ref<StreamPeerTCP> client = create_client(LOCALHOST, PORT);
|
||||
Ref<StreamPeerTCP> client_from_server = accept_connection(server);
|
||||
|
||||
wait_for_condition([&]() {
|
||||
return client->poll() != Error::OK || client->get_status() == StreamPeerTCP::STATUS_CONNECTED;
|
||||
});
|
||||
|
||||
CHECK_EQ(client->get_status(), StreamPeerTCP::STATUS_CONNECTED);
|
||||
|
||||
// Sending data from client to server.
|
||||
const String hello_world = "Hello World!";
|
||||
client->put_string(hello_world);
|
||||
CHECK_EQ(client_from_server->get_string(), hello_world);
|
||||
|
||||
client->disconnect_from_host();
|
||||
server->stop();
|
||||
CHECK_FALSE(server->is_listening());
|
||||
|
||||
// Make sure the client times out in less than the wait time.
|
||||
int timeout = ProjectSettings::get_singleton()->get_setting("network/limits/tcp/connect_timeout_seconds");
|
||||
ProjectSettings::get_singleton()->set_setting("network/limits/tcp/connect_timeout_seconds", 1);
|
||||
|
||||
Ref<StreamPeerTCP> new_client = create_client(LOCALHOST, PORT);
|
||||
|
||||
// Reset the timeout setting.
|
||||
ProjectSettings::get_singleton()->set_setting("network/limits/tcp/connect_timeout_seconds", timeout);
|
||||
|
||||
CHECK_FALSE(server->is_connection_available());
|
||||
|
||||
wait_for_condition([&]() {
|
||||
return new_client->poll() != Error::OK || new_client->get_status() == StreamPeerTCP::STATUS_ERROR;
|
||||
});
|
||||
|
||||
CHECK_FALSE(server->is_connection_available());
|
||||
|
||||
CHECK_EQ(new_client->get_status(), StreamPeerTCP::STATUS_ERROR);
|
||||
new_client->disconnect_from_host();
|
||||
CHECK_EQ(new_client->get_status(), StreamPeerTCP::STATUS_NONE);
|
||||
}
|
||||
|
||||
TEST_CASE("[TCPServer] Should disconnect client") {
|
||||
Ref<TCPServer> server = create_server(LOCALHOST, PORT);
|
||||
Ref<StreamPeerTCP> client = create_client(LOCALHOST, PORT);
|
||||
Ref<StreamPeerTCP> client_from_server = accept_connection(server);
|
||||
|
||||
wait_for_condition([&]() {
|
||||
return client->poll() != Error::OK || client->get_status() == StreamPeerTCP::STATUS_CONNECTED;
|
||||
});
|
||||
|
||||
CHECK_EQ(client->get_status(), StreamPeerTCP::STATUS_CONNECTED);
|
||||
|
||||
// Sending data from client to server.
|
||||
const String hello_world = "Hello World!";
|
||||
client->put_string(hello_world);
|
||||
CHECK_EQ(client_from_server->get_string(), hello_world);
|
||||
|
||||
client_from_server->disconnect_from_host();
|
||||
server->stop();
|
||||
CHECK_FALSE(server->is_listening());
|
||||
|
||||
// Wait for disconnection
|
||||
wait_for_condition([&]() {
|
||||
return client->poll() != Error::OK || client->get_status() == StreamPeerTCP::STATUS_NONE;
|
||||
});
|
||||
|
||||
// Wait for disconnection
|
||||
wait_for_condition([&]() {
|
||||
return client_from_server->poll() != Error::OK || client_from_server->get_status() == StreamPeerTCP::STATUS_NONE;
|
||||
});
|
||||
|
||||
CHECK_EQ(client->get_status(), StreamPeerTCP::STATUS_NONE);
|
||||
CHECK_EQ(client_from_server->get_status(), StreamPeerTCP::STATUS_NONE);
|
||||
|
||||
ERR_PRINT_OFF;
|
||||
CHECK_EQ(client->get_string(), String());
|
||||
CHECK_EQ(client_from_server->get_string(), String());
|
||||
ERR_PRINT_ON;
|
||||
}
|
||||
|
||||
} // namespace TestTCPServer
|
||||
|
||||
#endif // TEST_TCP_SERVER_H
|
||||
219
engine/tests/core/io/test_udp_server.h
Normal file
219
engine/tests/core/io/test_udp_server.h
Normal file
|
|
@ -0,0 +1,219 @@
|
|||
/**************************************************************************/
|
||||
/* test_udp_server.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#ifndef TEST_UDP_SERVER_H
|
||||
#define TEST_UDP_SERVER_H
|
||||
|
||||
#include "core/io/packet_peer_udp.h"
|
||||
#include "core/io/udp_server.h"
|
||||
#include "tests/test_macros.h"
|
||||
|
||||
namespace TestUDPServer {
|
||||
|
||||
const int PORT = 12345;
|
||||
const IPAddress LOCALHOST("127.0.0.1");
|
||||
const uint32_t SLEEP_DURATION = 1000;
|
||||
const uint64_t MAX_WAIT_USEC = 100000;
|
||||
|
||||
void wait_for_condition(std::function<bool()> f_test) {
|
||||
const uint64_t time = OS::get_singleton()->get_ticks_usec();
|
||||
while (!f_test() && (OS::get_singleton()->get_ticks_usec() - time) < MAX_WAIT_USEC) {
|
||||
OS::get_singleton()->delay_usec(SLEEP_DURATION);
|
||||
}
|
||||
}
|
||||
|
||||
Ref<UDPServer> create_server(const IPAddress &p_address, int p_port) {
|
||||
Ref<UDPServer> server;
|
||||
server.instantiate();
|
||||
|
||||
Error err = server->listen(PORT, LOCALHOST);
|
||||
REQUIRE_EQ(Error::OK, err);
|
||||
REQUIRE(server->is_listening());
|
||||
CHECK_FALSE(server->is_connection_available());
|
||||
CHECK_EQ(server->get_max_pending_connections(), 16);
|
||||
|
||||
return server;
|
||||
}
|
||||
|
||||
Ref<PacketPeerUDP> create_client(const IPAddress &p_address, int p_port) {
|
||||
Ref<PacketPeerUDP> client;
|
||||
client.instantiate();
|
||||
|
||||
Error err = client->connect_to_host(LOCALHOST, PORT);
|
||||
REQUIRE_EQ(Error::OK, err);
|
||||
CHECK(client->is_bound());
|
||||
CHECK(client->is_socket_connected());
|
||||
|
||||
return client;
|
||||
}
|
||||
|
||||
Ref<PacketPeerUDP> accept_connection(Ref<UDPServer> &p_server) {
|
||||
wait_for_condition([&]() {
|
||||
return p_server->poll() != Error::OK || p_server->is_connection_available();
|
||||
});
|
||||
|
||||
CHECK_EQ(p_server->poll(), Error::OK);
|
||||
REQUIRE(p_server->is_connection_available());
|
||||
Ref<PacketPeerUDP> client_from_server = p_server->take_connection();
|
||||
REQUIRE(client_from_server.is_valid());
|
||||
CHECK(client_from_server->is_bound());
|
||||
CHECK(client_from_server->is_socket_connected());
|
||||
|
||||
return client_from_server;
|
||||
}
|
||||
|
||||
TEST_CASE("[UDPServer] Instantiation") {
|
||||
Ref<UDPServer> server;
|
||||
server.instantiate();
|
||||
|
||||
REQUIRE(server.is_valid());
|
||||
CHECK_EQ(false, server->is_listening());
|
||||
}
|
||||
|
||||
TEST_CASE("[UDPServer] Accept a connection and receive/send data") {
|
||||
Ref<UDPServer> server = create_server(LOCALHOST, PORT);
|
||||
Ref<PacketPeerUDP> client = create_client(LOCALHOST, PORT);
|
||||
|
||||
// Sending data from client to server.
|
||||
const String hello_world = "Hello World!";
|
||||
CHECK_EQ(client->put_var(hello_world), Error::OK);
|
||||
|
||||
Variant hello_world_received;
|
||||
Ref<PacketPeerUDP> client_from_server = accept_connection(server);
|
||||
CHECK_EQ(client_from_server->get_var(hello_world_received), Error::OK);
|
||||
CHECK_EQ(String(hello_world_received), hello_world);
|
||||
|
||||
// Sending data from server to client.
|
||||
const Variant pi = 3.1415;
|
||||
CHECK_EQ(client_from_server->put_var(pi), Error::OK);
|
||||
|
||||
wait_for_condition([&]() {
|
||||
return client->get_available_packet_count() > 0;
|
||||
});
|
||||
|
||||
CHECK_GT(client->get_available_packet_count(), 0);
|
||||
|
||||
Variant pi_received;
|
||||
CHECK_EQ(client->get_var(pi_received), Error::OK);
|
||||
CHECK_EQ(pi_received, pi);
|
||||
|
||||
client->close();
|
||||
server->stop();
|
||||
CHECK_FALSE(server->is_listening());
|
||||
}
|
||||
|
||||
TEST_CASE("[UDPServer] Handle multiple clients at the same time") {
|
||||
Ref<UDPServer> server = create_server(LOCALHOST, PORT);
|
||||
|
||||
Vector<Ref<PacketPeerUDP>> clients;
|
||||
for (int i = 0; i < 5; i++) {
|
||||
Ref<PacketPeerUDP> c = create_client(LOCALHOST, PORT);
|
||||
|
||||
// Sending data from client to server.
|
||||
const String hello_client = itos(i);
|
||||
CHECK_EQ(c->put_var(hello_client), Error::OK);
|
||||
|
||||
clients.push_back(c);
|
||||
}
|
||||
|
||||
Array packets;
|
||||
for (int i = 0; i < clients.size(); i++) {
|
||||
Ref<PacketPeerUDP> cfs = accept_connection(server);
|
||||
|
||||
Variant received_var;
|
||||
CHECK_EQ(cfs->get_var(received_var), Error::OK);
|
||||
CHECK_EQ(received_var.get_type(), Variant::STRING);
|
||||
packets.push_back(received_var);
|
||||
|
||||
// Sending data from server to client.
|
||||
const float sent_float = 3.1415 + received_var.operator String().to_float();
|
||||
CHECK_EQ(cfs->put_var(sent_float), Error::OK);
|
||||
}
|
||||
|
||||
CHECK_EQ(packets.size(), clients.size());
|
||||
|
||||
packets.sort();
|
||||
for (int i = 0; i < clients.size(); i++) {
|
||||
CHECK_EQ(packets[i].operator String(), itos(i));
|
||||
}
|
||||
|
||||
wait_for_condition([&]() {
|
||||
bool should_exit = true;
|
||||
for (Ref<PacketPeerUDP> &c : clients) {
|
||||
int count = c->get_available_packet_count();
|
||||
if (count < 0) {
|
||||
return true;
|
||||
}
|
||||
if (count == 0) {
|
||||
should_exit = false;
|
||||
}
|
||||
}
|
||||
return should_exit;
|
||||
});
|
||||
|
||||
for (int i = 0; i < clients.size(); i++) {
|
||||
CHECK_GT(clients[i]->get_available_packet_count(), 0);
|
||||
|
||||
Variant received_var;
|
||||
const float expected = 3.1415 + i;
|
||||
CHECK_EQ(clients[i]->get_var(received_var), Error::OK);
|
||||
CHECK_EQ(received_var.get_type(), Variant::FLOAT);
|
||||
CHECK_EQ(received_var.operator float(), expected);
|
||||
}
|
||||
|
||||
for (Ref<PacketPeerUDP> &c : clients) {
|
||||
c->close();
|
||||
}
|
||||
server->stop();
|
||||
}
|
||||
|
||||
TEST_CASE("[UDPServer] Should not accept new connections after stop") {
|
||||
Ref<UDPServer> server = create_server(LOCALHOST, PORT);
|
||||
Ref<PacketPeerUDP> client = create_client(LOCALHOST, PORT);
|
||||
|
||||
// Sending data from client to server.
|
||||
const String hello_world = "Hello World!";
|
||||
CHECK_EQ(client->put_var(hello_world), Error::OK);
|
||||
|
||||
wait_for_condition([&]() {
|
||||
return server->poll() != Error::OK || server->is_connection_available();
|
||||
});
|
||||
|
||||
REQUIRE(server->is_connection_available());
|
||||
|
||||
server->stop();
|
||||
|
||||
CHECK_FALSE(server->is_listening());
|
||||
CHECK_FALSE(server->is_connection_available());
|
||||
}
|
||||
|
||||
} // namespace TestUDPServer
|
||||
|
||||
#endif // TEST_UDP_SERVER_H
|
||||
|
|
@ -48,7 +48,7 @@ TEST_CASE("[AABB] Constructor methods") {
|
|||
|
||||
TEST_CASE("[AABB] String conversion") {
|
||||
CHECK_MESSAGE(
|
||||
String(AABB(Vector3(-1.5, 2, -2.5), Vector3(4, 5, 6))) == "[P: (-1.5, 2, -2.5), S: (4, 5, 6)]",
|
||||
String(AABB(Vector3(-1.5, 2, -2.5), Vector3(4, 5, 6))) == "[P: (-1.5, 2.0, -2.5), S: (4.0, 5.0, 6.0)]",
|
||||
"The string representation should match the expected value.");
|
||||
}
|
||||
|
||||
|
|
@ -377,23 +377,23 @@ TEST_CASE("[AABB] Get longest/shortest axis") {
|
|||
TEST_CASE("[AABB] Get support") {
|
||||
const AABB aabb = AABB(Vector3(-1.5, 2, -2.5), Vector3(4, 5, 6));
|
||||
CHECK_MESSAGE(
|
||||
aabb.get_support(Vector3(1, 0, 0)).is_equal_approx(Vector3(2.5, 2, -2.5)),
|
||||
aabb.get_support(Vector3(1, 0, 0)) == Vector3(2.5, 2, -2.5),
|
||||
"get_support() should return the expected value.");
|
||||
CHECK_MESSAGE(
|
||||
aabb.get_support(Vector3(0.5, 1, 0)).is_equal_approx(Vector3(2.5, 7, -2.5)),
|
||||
aabb.get_support(Vector3(0.5, 1, 1)) == Vector3(2.5, 7, 3.5),
|
||||
"get_support() should return the expected value.");
|
||||
CHECK_MESSAGE(
|
||||
aabb.get_support(Vector3(0.5, 1, -400)).is_equal_approx(Vector3(2.5, 7, -2.5)),
|
||||
aabb.get_support(Vector3(0.5, 1, -400)) == Vector3(2.5, 7, -2.5),
|
||||
"get_support() should return the expected value.");
|
||||
CHECK_MESSAGE(
|
||||
aabb.get_support(Vector3(0, -1, 0)).is_equal_approx(Vector3(-1.5, 2, -2.5)),
|
||||
aabb.get_support(Vector3(0, -1, 0)) == Vector3(-1.5, 2, -2.5),
|
||||
"get_support() should return the expected value.");
|
||||
CHECK_MESSAGE(
|
||||
aabb.get_support(Vector3(0, -0.1, 0)).is_equal_approx(Vector3(-1.5, 2, -2.5)),
|
||||
aabb.get_support(Vector3(0, -0.1, 0)) == Vector3(-1.5, 2, -2.5),
|
||||
"get_support() should return the expected value.");
|
||||
CHECK_MESSAGE(
|
||||
aabb.get_support(Vector3()).is_equal_approx(Vector3(-1.5, 2, -2.5)),
|
||||
"get_support() should return the expected value with a null vector.");
|
||||
aabb.get_support(Vector3()) == Vector3(-1.5, 2, -2.5),
|
||||
"get_support() should return the AABB position when given a zero vector.");
|
||||
}
|
||||
|
||||
TEST_CASE("[AABB] Grow") {
|
||||
|
|
|
|||
|
|
@ -93,9 +93,9 @@ void test_rotation(Vector3 deg_original_euler, EulerOrder rot_order) {
|
|||
|
||||
Basis res = to_rotation.inverse() * rotation_from_computed_euler;
|
||||
|
||||
CHECK_MESSAGE((res.get_column(0) - Vector3(1.0, 0.0, 0.0)).length() <= 0.1, vformat("Fail due to X %s\n", String(res.get_column(0))));
|
||||
CHECK_MESSAGE((res.get_column(1) - Vector3(0.0, 1.0, 0.0)).length() <= 0.1, vformat("Fail due to Y %s\n", String(res.get_column(1))));
|
||||
CHECK_MESSAGE((res.get_column(2) - Vector3(0.0, 0.0, 1.0)).length() <= 0.1, vformat("Fail due to Z %s\n", String(res.get_column(2))));
|
||||
CHECK_MESSAGE((res.get_column(0) - Vector3(1.0, 0.0, 0.0)).length() <= 0.001, vformat("Fail due to X %s\n", String(res.get_column(0))));
|
||||
CHECK_MESSAGE((res.get_column(1) - Vector3(0.0, 1.0, 0.0)).length() <= 0.001, vformat("Fail due to Y %s\n", String(res.get_column(1))));
|
||||
CHECK_MESSAGE((res.get_column(2) - Vector3(0.0, 0.0, 1.0)).length() <= 0.001, vformat("Fail due to Z %s\n", String(res.get_column(2))));
|
||||
|
||||
// Double check `to_rotation` decomposing with XYZ rotation order.
|
||||
const Vector3 euler_xyz_from_rotation = to_rotation.get_euler(EulerOrder::XYZ);
|
||||
|
|
@ -103,9 +103,9 @@ void test_rotation(Vector3 deg_original_euler, EulerOrder rot_order) {
|
|||
|
||||
res = to_rotation.inverse() * rotation_from_xyz_computed_euler;
|
||||
|
||||
CHECK_MESSAGE((res.get_column(0) - Vector3(1.0, 0.0, 0.0)).length() <= 0.1, vformat("Double check with XYZ rot order failed, due to X %s\n", String(res.get_column(0))));
|
||||
CHECK_MESSAGE((res.get_column(1) - Vector3(0.0, 1.0, 0.0)).length() <= 0.1, vformat("Double check with XYZ rot order failed, due to Y %s\n", String(res.get_column(1))));
|
||||
CHECK_MESSAGE((res.get_column(2) - Vector3(0.0, 0.0, 1.0)).length() <= 0.1, vformat("Double check with XYZ rot order failed, due to Z %s\n", String(res.get_column(2))));
|
||||
CHECK_MESSAGE((res.get_column(0) - Vector3(1.0, 0.0, 0.0)).length() <= 0.001, vformat("Double check with XYZ rot order failed, due to X %s\n", String(res.get_column(0))));
|
||||
CHECK_MESSAGE((res.get_column(1) - Vector3(0.0, 1.0, 0.0)).length() <= 0.001, vformat("Double check with XYZ rot order failed, due to Y %s\n", String(res.get_column(1))));
|
||||
CHECK_MESSAGE((res.get_column(2) - Vector3(0.0, 0.0, 1.0)).length() <= 0.001, vformat("Double check with XYZ rot order failed, due to Z %s\n", String(res.get_column(2))));
|
||||
|
||||
INFO(vformat("Rotation order: %s\n.", get_rot_order_name(rot_order)));
|
||||
INFO(vformat("Original Rotation: %s\n", String(deg_original_euler)));
|
||||
|
|
@ -176,6 +176,12 @@ TEST_CASE("[Basis] Euler conversions") {
|
|||
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(89.9, 0.0, 0.0));
|
||||
vectors_to_test.push_back(Vector3(-89.9, 0.0, 0.0));
|
||||
vectors_to_test.push_back(Vector3(0.0, 89.9, 0.0));
|
||||
vectors_to_test.push_back(Vector3(0.0, -89.9, 0.0));
|
||||
vectors_to_test.push_back(Vector3(0.0, 0.0, 89.9));
|
||||
vectors_to_test.push_back(Vector3(0.0, 0.0, -89.9));
|
||||
|
||||
for (int h = 0; h < euler_order_to_test.size(); h += 1) {
|
||||
for (int i = 0; i < vectors_to_test.size(); i += 1) {
|
||||
|
|
|
|||
|
|
@ -140,7 +140,7 @@ TEST_CASE("[Color] Conversion methods") {
|
|||
cyan.to_rgba64() == 0x0000'ffff'ffff'ffff,
|
||||
"The returned 64-bit BGR number should match the expected value.");
|
||||
CHECK_MESSAGE(
|
||||
String(cyan) == "(0, 1, 1, 1)",
|
||||
String(cyan) == "(0.0, 1.0, 1.0, 1.0)",
|
||||
"The string representation should match the expected value.");
|
||||
}
|
||||
|
||||
|
|
@ -160,6 +160,12 @@ TEST_CASE("[Color] Linear <-> sRGB conversion") {
|
|||
CHECK_MESSAGE(
|
||||
color_srgb.srgb_to_linear().is_equal_approx(Color(0.35, 0.5, 0.6, 0.7)),
|
||||
"The sRGB color converted back to linear color space should match the expected value.");
|
||||
CHECK_MESSAGE(
|
||||
Color(1.0, 1.0, 1.0, 1.0).srgb_to_linear() == (Color(1.0, 1.0, 1.0, 1.0)),
|
||||
"White converted from sRGB to linear should remain white.");
|
||||
CHECK_MESSAGE(
|
||||
Color(1.0, 1.0, 1.0, 1.0).linear_to_srgb() == (Color(1.0, 1.0, 1.0, 1.0)),
|
||||
"White converted from linear to sRGB should remain white.");
|
||||
}
|
||||
|
||||
TEST_CASE("[Color] Named colors") {
|
||||
|
|
|
|||
|
|
@ -122,12 +122,68 @@ TEST_CASE("[Expression] Floating-point arithmetic") {
|
|||
"Float multiplication-addition-subtraction-division should return the expected result.");
|
||||
}
|
||||
|
||||
TEST_CASE("[Expression] Floating-point notation") {
|
||||
Expression expression;
|
||||
|
||||
CHECK_MESSAGE(
|
||||
expression.parse("2.") == OK,
|
||||
"The expression should parse successfully.");
|
||||
CHECK_MESSAGE(
|
||||
double(expression.execute()) == doctest::Approx(2.0),
|
||||
"The expression should return the expected result.");
|
||||
|
||||
CHECK_MESSAGE(
|
||||
expression.parse("(2.)") == OK,
|
||||
"The expression should parse successfully.");
|
||||
CHECK_MESSAGE(
|
||||
double(expression.execute()) == doctest::Approx(2.0),
|
||||
"The expression should return the expected result.");
|
||||
|
||||
CHECK_MESSAGE(
|
||||
expression.parse(".3") == OK,
|
||||
"The expression should parse successfully.");
|
||||
CHECK_MESSAGE(
|
||||
double(expression.execute()) == doctest::Approx(0.3),
|
||||
"The expression should return the expected result.");
|
||||
|
||||
CHECK_MESSAGE(
|
||||
expression.parse("2.+5.") == OK,
|
||||
"The expression should parse successfully.");
|
||||
CHECK_MESSAGE(
|
||||
double(expression.execute()) == doctest::Approx(7.0),
|
||||
"The expression should return the expected result.");
|
||||
|
||||
CHECK_MESSAGE(
|
||||
expression.parse(".3-.8") == OK,
|
||||
"The expression should parse successfully.");
|
||||
CHECK_MESSAGE(
|
||||
double(expression.execute()) == doctest::Approx(-0.5),
|
||||
"The expression should return the expected result.");
|
||||
|
||||
CHECK_MESSAGE(
|
||||
expression.parse("2.+.2") == OK,
|
||||
"The expression should parse successfully.");
|
||||
CHECK_MESSAGE(
|
||||
double(expression.execute()) == doctest::Approx(2.2),
|
||||
"The expression should return the expected result.");
|
||||
|
||||
CHECK_MESSAGE(
|
||||
expression.parse(".0*0.") == OK,
|
||||
"The expression should parse successfully.");
|
||||
CHECK_MESSAGE(
|
||||
double(expression.execute()) == doctest::Approx(0.0),
|
||||
"The expression should return the expected result.");
|
||||
}
|
||||
|
||||
TEST_CASE("[Expression] Scientific notation") {
|
||||
Expression expression;
|
||||
|
||||
CHECK_MESSAGE(
|
||||
expression.parse("2.e5") == OK,
|
||||
"The expression should parse successfully.");
|
||||
CHECK_MESSAGE(
|
||||
expression.parse("2.E5") == OK,
|
||||
"The expression should parse successfully.");
|
||||
CHECK_MESSAGE(
|
||||
double(expression.execute()) == doctest::Approx(200'000),
|
||||
"The expression should return the expected result.");
|
||||
|
|
@ -160,6 +216,15 @@ TEST_CASE("[Expression] Underscored numeric literals") {
|
|||
CHECK_MESSAGE(
|
||||
expression.parse("0xff_99_00") == OK,
|
||||
"The expression should parse successfully.");
|
||||
CHECK_MESSAGE(
|
||||
expression.parse("0Xff_99_00") == OK,
|
||||
"The expression should parse successfully.");
|
||||
CHECK_MESSAGE(
|
||||
expression.parse("0b10_11_00") == OK,
|
||||
"The expression should parse successfully.");
|
||||
CHECK_MESSAGE(
|
||||
expression.parse("0B10_11_00") == OK,
|
||||
"The expression should parse successfully.");
|
||||
}
|
||||
|
||||
TEST_CASE("[Expression] Built-in functions") {
|
||||
|
|
|
|||
|
|
@ -96,7 +96,7 @@ TEST_CASE("[Plane] Plane-point operations") {
|
|||
|
||||
CHECK_MESSAGE(
|
||||
y_facing_plane.get_any_perpendicular_normal().is_equal_approx(Vector3(1, 0, 0)),
|
||||
"get_any_perpindicular_normal() should return the expected result.");
|
||||
"get_any_perpendicular_normal() should return the expected result.");
|
||||
|
||||
// TODO distance_to()
|
||||
}
|
||||
|
|
|
|||
516
engine/tests/core/math/test_projection.h
Normal file
516
engine/tests/core/math/test_projection.h
Normal file
|
|
@ -0,0 +1,516 @@
|
|||
/**************************************************************************/
|
||||
/* test_projection.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#ifndef TEST_PROJECTION_H
|
||||
#define TEST_PROJECTION_H
|
||||
|
||||
#include "core/math/aabb.h"
|
||||
#include "core/math/plane.h"
|
||||
#include "core/math/projection.h"
|
||||
#include "core/math/rect2.h"
|
||||
#include "core/math/transform_3d.h"
|
||||
|
||||
#include "thirdparty/doctest/doctest.h"
|
||||
|
||||
namespace TestProjection {
|
||||
|
||||
TEST_CASE("[Projection] Construction") {
|
||||
Projection default_proj;
|
||||
|
||||
CHECK(default_proj[0].is_equal_approx(Vector4(1, 0, 0, 0)));
|
||||
CHECK(default_proj[1].is_equal_approx(Vector4(0, 1, 0, 0)));
|
||||
CHECK(default_proj[2].is_equal_approx(Vector4(0, 0, 1, 0)));
|
||||
CHECK(default_proj[3].is_equal_approx(Vector4(0, 0, 0, 1)));
|
||||
|
||||
Projection from_vec4(
|
||||
Vector4(1, 2, 3, 4),
|
||||
Vector4(5, 6, 7, 8),
|
||||
Vector4(9, 10, 11, 12),
|
||||
Vector4(13, 14, 15, 16));
|
||||
|
||||
CHECK(from_vec4[0].is_equal_approx(Vector4(1, 2, 3, 4)));
|
||||
CHECK(from_vec4[1].is_equal_approx(Vector4(5, 6, 7, 8)));
|
||||
CHECK(from_vec4[2].is_equal_approx(Vector4(9, 10, 11, 12)));
|
||||
CHECK(from_vec4[3].is_equal_approx(Vector4(13, 14, 15, 16)));
|
||||
|
||||
Transform3D transform(
|
||||
Basis(
|
||||
Vector3(1, 0, 0),
|
||||
Vector3(0, 2, 0),
|
||||
Vector3(0, 0, 3)),
|
||||
Vector3(4, 5, 6));
|
||||
|
||||
Projection from_transform(transform);
|
||||
|
||||
CHECK(from_transform[0].is_equal_approx(Vector4(1, 0, 0, 0)));
|
||||
CHECK(from_transform[1].is_equal_approx(Vector4(0, 2, 0, 0)));
|
||||
CHECK(from_transform[2].is_equal_approx(Vector4(0, 0, 3, 0)));
|
||||
CHECK(from_transform[3].is_equal_approx(Vector4(4, 5, 6, 1)));
|
||||
}
|
||||
|
||||
TEST_CASE("[Projection] set_zero()") {
|
||||
Projection proj;
|
||||
proj.set_zero();
|
||||
|
||||
for (int i = 0; i < 4; i++) {
|
||||
for (int j = 0; j < 4; j++) {
|
||||
CHECK(proj.columns[i][j] == 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("[Projection] set_identity()") {
|
||||
Projection proj;
|
||||
proj.set_identity();
|
||||
|
||||
for (int i = 0; i < 4; i++) {
|
||||
for (int j = 0; j < 4; j++) {
|
||||
CHECK(proj.columns[i][j] == (i == j ? 1 : 0));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("[Projection] determinant()") {
|
||||
Projection proj(
|
||||
Vector4(1, 5, 9, 13),
|
||||
Vector4(2, 6, 11, 15),
|
||||
Vector4(4, 7, 11, 15),
|
||||
Vector4(4, 8, 12, 16));
|
||||
|
||||
CHECK(proj.determinant() == -12);
|
||||
}
|
||||
|
||||
TEST_CASE("[Projection] Inverse and invert") {
|
||||
SUBCASE("[Projection] Arbitrary projection matrix inversion") {
|
||||
Projection proj(
|
||||
Vector4(1, 5, 9, 13),
|
||||
Vector4(2, 6, 11, 15),
|
||||
Vector4(4, 7, 11, 15),
|
||||
Vector4(4, 8, 12, 16));
|
||||
|
||||
Projection inverse_truth(
|
||||
Vector4(-4.0 / 12, 0, 1, -8.0 / 12),
|
||||
Vector4(8.0 / 12, -1, -1, 16.0 / 12),
|
||||
Vector4(-20.0 / 12, 2, -1, 5.0 / 12),
|
||||
Vector4(1, -1, 1, -0.75));
|
||||
|
||||
Projection inverse = proj.inverse();
|
||||
CHECK(inverse[0].is_equal_approx(inverse_truth[0]));
|
||||
CHECK(inverse[1].is_equal_approx(inverse_truth[1]));
|
||||
CHECK(inverse[2].is_equal_approx(inverse_truth[2]));
|
||||
CHECK(inverse[3].is_equal_approx(inverse_truth[3]));
|
||||
|
||||
proj.invert();
|
||||
CHECK(proj[0].is_equal_approx(inverse_truth[0]));
|
||||
CHECK(proj[1].is_equal_approx(inverse_truth[1]));
|
||||
CHECK(proj[2].is_equal_approx(inverse_truth[2]));
|
||||
CHECK(proj[3].is_equal_approx(inverse_truth[3]));
|
||||
}
|
||||
|
||||
SUBCASE("[Projection] Orthogonal projection matrix inversion") {
|
||||
Projection p = Projection::create_orthogonal(-125.0f, 125.0f, -125.0f, 125.0f, 0.01f, 25.0f);
|
||||
p = p.inverse() * p;
|
||||
|
||||
CHECK(p[0].is_equal_approx(Vector4(1, 0, 0, 0)));
|
||||
CHECK(p[1].is_equal_approx(Vector4(0, 1, 0, 0)));
|
||||
CHECK(p[2].is_equal_approx(Vector4(0, 0, 1, 0)));
|
||||
CHECK(p[3].is_equal_approx(Vector4(0, 0, 0, 1)));
|
||||
}
|
||||
|
||||
SUBCASE("[Projection] Perspective projection matrix inversion") {
|
||||
Projection p = Projection::create_perspective(90.0f, 1.77777f, 0.05f, 4000.0f);
|
||||
p = p.inverse() * p;
|
||||
|
||||
CHECK(p[0].is_equal_approx(Vector4(1, 0, 0, 0)));
|
||||
CHECK(p[1].is_equal_approx(Vector4(0, 1, 0, 0)));
|
||||
CHECK(p[2].is_equal_approx(Vector4(0, 0, 1, 0)));
|
||||
CHECK(p[3].is_equal_approx(Vector4(0, 0, 0, 1)));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("[Projection] Matrix product") {
|
||||
Projection proj1(
|
||||
Vector4(1, 5, 9, 13),
|
||||
Vector4(2, 6, 11, 15),
|
||||
Vector4(4, 7, 11, 15),
|
||||
Vector4(4, 8, 12, 16));
|
||||
|
||||
Projection proj2(
|
||||
Vector4(0, 1, 2, 3),
|
||||
Vector4(10, 11, 12, 13),
|
||||
Vector4(20, 21, 22, 23),
|
||||
Vector4(30, 31, 32, 33));
|
||||
|
||||
Projection prod = proj1 * proj2;
|
||||
|
||||
CHECK(prod[0].is_equal_approx(Vector4(22, 44, 69, 93)));
|
||||
CHECK(prod[1].is_equal_approx(Vector4(132, 304, 499, 683)));
|
||||
CHECK(prod[2].is_equal_approx(Vector4(242, 564, 929, 1273)));
|
||||
CHECK(prod[3].is_equal_approx(Vector4(352, 824, 1359, 1863)));
|
||||
}
|
||||
|
||||
TEST_CASE("[Projection] Vector transformation") {
|
||||
Projection proj(
|
||||
Vector4(1, 5, 9, 13),
|
||||
Vector4(2, 6, 11, 15),
|
||||
Vector4(4, 7, 11, 15),
|
||||
Vector4(4, 8, 12, 16));
|
||||
|
||||
Projection inverse(
|
||||
Vector4(-4.0 / 12, 0, 1, -8.0 / 12),
|
||||
Vector4(8.0 / 12, -1, -1, 16.0 / 12),
|
||||
Vector4(-20.0 / 12, 2, -1, 5.0 / 12),
|
||||
Vector4(1, -1, 1, -0.75));
|
||||
|
||||
Vector4 vec4(1, 2, 3, 4);
|
||||
CHECK(proj.xform(vec4).is_equal_approx(Vector4(33, 70, 112, 152)));
|
||||
CHECK(proj.xform_inv(vec4).is_equal_approx(Vector4(90, 107, 111, 120)));
|
||||
|
||||
Vector3 vec3(1, 2, 3);
|
||||
CHECK(proj.xform(vec3).is_equal_approx(Vector3(21, 46, 76) / 104));
|
||||
}
|
||||
|
||||
TEST_CASE("[Projection] Plane transformation") {
|
||||
Projection proj(
|
||||
Vector4(1, 5, 9, 13),
|
||||
Vector4(2, 6, 11, 15),
|
||||
Vector4(4, 7, 11, 15),
|
||||
Vector4(4, 8, 12, 16));
|
||||
|
||||
Plane plane(1, 2, 3, 4);
|
||||
CHECK(proj.xform4(plane).is_equal_approx(Plane(33, 70, 112, 152)));
|
||||
}
|
||||
|
||||
TEST_CASE("[Projection] Values access") {
|
||||
Projection proj(
|
||||
Vector4(00, 01, 02, 03),
|
||||
Vector4(10, 11, 12, 13),
|
||||
Vector4(20, 21, 22, 23),
|
||||
Vector4(30, 31, 32, 33));
|
||||
|
||||
CHECK(proj[0] == Vector4(00, 01, 02, 03));
|
||||
CHECK(proj[1] == Vector4(10, 11, 12, 13));
|
||||
CHECK(proj[2] == Vector4(20, 21, 22, 23));
|
||||
CHECK(proj[3] == Vector4(30, 31, 32, 33));
|
||||
}
|
||||
|
||||
TEST_CASE("[Projection] flip_y() and flipped_y()") {
|
||||
Projection proj(
|
||||
Vector4(00, 01, 02, 03),
|
||||
Vector4(10, 11, 12, 13),
|
||||
Vector4(20, 21, 22, 23),
|
||||
Vector4(30, 31, 32, 33));
|
||||
|
||||
Projection flipped = proj.flipped_y();
|
||||
|
||||
CHECK(flipped[0] == proj[0]);
|
||||
CHECK(flipped[1] == -proj[1]);
|
||||
CHECK(flipped[2] == proj[2]);
|
||||
CHECK(flipped[3] == proj[3]);
|
||||
|
||||
proj.flip_y();
|
||||
|
||||
CHECK(proj[0] == flipped[0]);
|
||||
CHECK(proj[1] == flipped[1]);
|
||||
CHECK(proj[2] == flipped[2]);
|
||||
CHECK(proj[3] == flipped[3]);
|
||||
}
|
||||
|
||||
TEST_CASE("[Projection] Jitter offset") {
|
||||
Projection proj(
|
||||
Vector4(00, 01, 02, 03),
|
||||
Vector4(10, 11, 12, 13),
|
||||
Vector4(20, 21, 22, 23),
|
||||
Vector4(30, 31, 32, 33));
|
||||
|
||||
Projection offsetted = proj.jitter_offseted(Vector2(1, 2));
|
||||
|
||||
CHECK(offsetted[0] == proj[0]);
|
||||
CHECK(offsetted[1] == proj[1]);
|
||||
CHECK(offsetted[2] == proj[2]);
|
||||
CHECK(offsetted[3] == proj[3] + Vector4(1, 2, 0, 0));
|
||||
|
||||
proj.add_jitter_offset(Vector2(1, 2));
|
||||
|
||||
CHECK(proj[0] == offsetted[0]);
|
||||
CHECK(proj[1] == offsetted[1]);
|
||||
CHECK(proj[2] == offsetted[2]);
|
||||
CHECK(proj[3] == offsetted[3]);
|
||||
}
|
||||
|
||||
TEST_CASE("[Projection] Adjust znear") {
|
||||
Projection persp = Projection::create_perspective(90, 0.5, 1, 50, false);
|
||||
Projection adjusted = persp.perspective_znear_adjusted(2);
|
||||
|
||||
CHECK(adjusted[0] == persp[0]);
|
||||
CHECK(adjusted[1] == persp[1]);
|
||||
CHECK(adjusted[2].is_equal_approx(Vector4(persp[2][0], persp[2][1], -1.083333, persp[2][3])));
|
||||
CHECK(adjusted[3].is_equal_approx(Vector4(persp[3][0], persp[3][1], -4.166666, persp[3][3])));
|
||||
|
||||
persp.adjust_perspective_znear(2);
|
||||
|
||||
CHECK(persp[0] == adjusted[0]);
|
||||
CHECK(persp[1] == adjusted[1]);
|
||||
CHECK(persp[2] == adjusted[2]);
|
||||
CHECK(persp[3] == adjusted[3]);
|
||||
}
|
||||
|
||||
TEST_CASE("[Projection] Set light bias") {
|
||||
Projection proj;
|
||||
proj.set_light_bias();
|
||||
|
||||
CHECK(proj[0] == Vector4(0.5, 0, 0, 0));
|
||||
CHECK(proj[1] == Vector4(0, 0.5, 0, 0));
|
||||
CHECK(proj[2] == Vector4(0, 0, 0.5, 0));
|
||||
CHECK(proj[3] == Vector4(0.5, 0.5, 0.5, 1));
|
||||
}
|
||||
|
||||
TEST_CASE("[Projection] Depth correction") {
|
||||
Projection corrected = Projection::create_depth_correction(true);
|
||||
|
||||
CHECK(corrected[0] == Vector4(1, 0, 0, 0));
|
||||
CHECK(corrected[1] == Vector4(0, -1, 0, 0));
|
||||
CHECK(corrected[2] == Vector4(0, 0, -0.5, 0));
|
||||
CHECK(corrected[3] == Vector4(0, 0, 0.5, 1));
|
||||
|
||||
Projection proj;
|
||||
proj.set_depth_correction(true, true, true);
|
||||
|
||||
CHECK(proj[0] == corrected[0]);
|
||||
CHECK(proj[1] == corrected[1]);
|
||||
CHECK(proj[2] == corrected[2]);
|
||||
CHECK(proj[3] == corrected[3]);
|
||||
|
||||
proj.set_depth_correction(false, true, true);
|
||||
|
||||
CHECK(proj[0] == Vector4(1, 0, 0, 0));
|
||||
CHECK(proj[1] == Vector4(0, 1, 0, 0));
|
||||
CHECK(proj[2] == Vector4(0, 0, -0.5, 0));
|
||||
CHECK(proj[3] == Vector4(0, 0, 0.5, 1));
|
||||
|
||||
proj.set_depth_correction(false, false, true);
|
||||
|
||||
CHECK(proj[0] == Vector4(1, 0, 0, 0));
|
||||
CHECK(proj[1] == Vector4(0, 1, 0, 0));
|
||||
CHECK(proj[2] == Vector4(0, 0, 0.5, 0));
|
||||
CHECK(proj[3] == Vector4(0, 0, 0.5, 1));
|
||||
|
||||
proj.set_depth_correction(false, false, false);
|
||||
|
||||
CHECK(proj[0] == Vector4(1, 0, 0, 0));
|
||||
CHECK(proj[1] == Vector4(0, 1, 0, 0));
|
||||
CHECK(proj[2] == Vector4(0, 0, 1, 0));
|
||||
CHECK(proj[3] == Vector4(0, 0, 0, 1));
|
||||
|
||||
proj.set_depth_correction(true, true, false);
|
||||
|
||||
CHECK(proj[0] == Vector4(1, 0, 0, 0));
|
||||
CHECK(proj[1] == Vector4(0, -1, 0, 0));
|
||||
CHECK(proj[2] == Vector4(0, 0, -1, 0));
|
||||
CHECK(proj[3] == Vector4(0, 0, 0, 1));
|
||||
}
|
||||
|
||||
TEST_CASE("[Projection] Light atlas rect") {
|
||||
Projection rect = Projection::create_light_atlas_rect(Rect2(1, 2, 30, 40));
|
||||
|
||||
CHECK(rect[0] == Vector4(30, 0, 0, 0));
|
||||
CHECK(rect[1] == Vector4(0, 40, 0, 0));
|
||||
CHECK(rect[2] == Vector4(0, 0, 1, 0));
|
||||
CHECK(rect[3] == Vector4(1, 2, 0, 1));
|
||||
|
||||
Projection proj;
|
||||
proj.set_light_atlas_rect(Rect2(1, 2, 30, 40));
|
||||
|
||||
CHECK(proj[0] == rect[0]);
|
||||
CHECK(proj[1] == rect[1]);
|
||||
CHECK(proj[2] == rect[2]);
|
||||
CHECK(proj[3] == rect[3]);
|
||||
}
|
||||
|
||||
TEST_CASE("[Projection] Make scale") {
|
||||
Projection proj;
|
||||
proj.make_scale(Vector3(2, 3, 4));
|
||||
|
||||
CHECK(proj[0] == Vector4(2, 0, 0, 0));
|
||||
CHECK(proj[1] == Vector4(0, 3, 0, 0));
|
||||
CHECK(proj[2] == Vector4(0, 0, 4, 0));
|
||||
CHECK(proj[3] == Vector4(0, 0, 0, 1));
|
||||
}
|
||||
|
||||
TEST_CASE("[Projection] Scale translate to fit aabb") {
|
||||
Projection fit = Projection::create_fit_aabb(AABB(Vector3(), Vector3(0.1, 0.2, 0.4)));
|
||||
|
||||
CHECK(fit[0] == Vector4(20, 0, 0, 0));
|
||||
CHECK(fit[1] == Vector4(0, 10, 0, 0));
|
||||
CHECK(fit[2] == Vector4(0, 0, 5, 0));
|
||||
CHECK(fit[3] == Vector4(-1, -1, -1, 1));
|
||||
|
||||
Projection proj;
|
||||
proj.scale_translate_to_fit(AABB(Vector3(), Vector3(0.1, 0.2, 0.4)));
|
||||
|
||||
CHECK(proj[0] == fit[0]);
|
||||
CHECK(proj[1] == fit[1]);
|
||||
CHECK(proj[2] == fit[2]);
|
||||
CHECK(proj[3] == fit[3]);
|
||||
}
|
||||
|
||||
TEST_CASE("[Projection] Perspective") {
|
||||
Projection persp = Projection::create_perspective(90, 0.5, 5, 15, false);
|
||||
|
||||
CHECK(persp[0].is_equal_approx(Vector4(2, 0, 0, 0)));
|
||||
CHECK(persp[1].is_equal_approx(Vector4(0, 1, 0, 0)));
|
||||
CHECK(persp[2].is_equal_approx(Vector4(0, 0, -2, -1)));
|
||||
CHECK(persp[3].is_equal_approx(Vector4(0, 0, -15, 0)));
|
||||
|
||||
Projection proj;
|
||||
proj.set_perspective(90, 0.5, 5, 15, false);
|
||||
|
||||
CHECK(proj[0] == persp[0]);
|
||||
CHECK(proj[1] == persp[1]);
|
||||
CHECK(proj[2] == persp[2]);
|
||||
CHECK(proj[3] == persp[3]);
|
||||
}
|
||||
|
||||
TEST_CASE("[Projection] Frustum") {
|
||||
Projection frustum = Projection::create_frustum(15, 20, 10, 12, 5, 15);
|
||||
|
||||
CHECK(frustum[0].is_equal_approx(Vector4(2, 0, 0, 0)));
|
||||
CHECK(frustum[1].is_equal_approx(Vector4(0, 5, 0, 0)));
|
||||
CHECK(frustum[2].is_equal_approx(Vector4(7, 11, -2, -1)));
|
||||
CHECK(frustum[3].is_equal_approx(Vector4(0, 0, -15, 0)));
|
||||
|
||||
Projection proj;
|
||||
proj.set_frustum(15, 20, 10, 12, 5, 15);
|
||||
|
||||
CHECK(proj[0] == frustum[0]);
|
||||
CHECK(proj[1] == frustum[1]);
|
||||
CHECK(proj[2] == frustum[2]);
|
||||
CHECK(proj[3] == frustum[3]);
|
||||
}
|
||||
|
||||
TEST_CASE("[Projection] Ortho") {
|
||||
Projection ortho = Projection::create_orthogonal(15, 20, 10, 12, 5, 15);
|
||||
|
||||
CHECK(ortho[0].is_equal_approx(Vector4(0.4, 0, 0, 0)));
|
||||
CHECK(ortho[1].is_equal_approx(Vector4(0, 1, 0, 0)));
|
||||
CHECK(ortho[2].is_equal_approx(Vector4(0, 0, -0.2, 0)));
|
||||
CHECK(ortho[3].is_equal_approx(Vector4(-7, -11, -2, 1)));
|
||||
|
||||
Projection proj;
|
||||
proj.set_orthogonal(15, 20, 10, 12, 5, 15);
|
||||
|
||||
CHECK(proj[0] == ortho[0]);
|
||||
CHECK(proj[1] == ortho[1]);
|
||||
CHECK(proj[2] == ortho[2]);
|
||||
CHECK(proj[3] == ortho[3]);
|
||||
}
|
||||
|
||||
TEST_CASE("[Projection] get_fovy()") {
|
||||
double fov = Projection::get_fovy(90, 0.5);
|
||||
CHECK(fov == doctest::Approx(53.1301));
|
||||
}
|
||||
|
||||
TEST_CASE("[Projection] Perspective values extraction") {
|
||||
Projection persp = Projection::create_perspective(90, 0.5, 1, 50, true);
|
||||
|
||||
double znear = persp.get_z_near();
|
||||
double zfar = persp.get_z_far();
|
||||
double aspect = persp.get_aspect();
|
||||
double fov = persp.get_fov();
|
||||
|
||||
CHECK(znear == doctest::Approx(1));
|
||||
CHECK(zfar == doctest::Approx(50));
|
||||
CHECK(aspect == doctest::Approx(0.5));
|
||||
CHECK(fov == doctest::Approx(90));
|
||||
}
|
||||
|
||||
TEST_CASE("[Projection] Orthographic check") {
|
||||
Projection persp = Projection::create_perspective(90, 0.5, 1, 50, false);
|
||||
Projection ortho = Projection::create_orthogonal(15, 20, 10, 12, 5, 15);
|
||||
|
||||
CHECK(!persp.is_orthogonal());
|
||||
CHECK(ortho.is_orthogonal());
|
||||
}
|
||||
|
||||
TEST_CASE("[Projection] Planes extraction") {
|
||||
Projection persp = Projection::create_perspective(90, 1, 1, 40, false);
|
||||
Vector<Plane> planes = persp.get_projection_planes(Transform3D());
|
||||
|
||||
CHECK(planes[Projection::PLANE_NEAR].normalized().is_equal_approx(Plane(0, 0, 1, -1)));
|
||||
CHECK(planes[Projection::PLANE_FAR].normalized().is_equal_approx(Plane(0, 0, -1, 40)));
|
||||
CHECK(planes[Projection::PLANE_LEFT].normalized().is_equal_approx(Plane(-0.707107, 0, 0.707107, 0)));
|
||||
CHECK(planes[Projection::PLANE_TOP].normalized().is_equal_approx(Plane(0, 0.707107, 0.707107, 0)));
|
||||
CHECK(planes[Projection::PLANE_RIGHT].normalized().is_equal_approx(Plane(0.707107, 0, 0.707107, 0)));
|
||||
CHECK(planes[Projection::PLANE_BOTTOM].normalized().is_equal_approx(Plane(0, -0.707107, 0.707107, 0)));
|
||||
|
||||
Plane plane_array[6]{
|
||||
persp.get_projection_plane(Projection::PLANE_NEAR),
|
||||
persp.get_projection_plane(Projection::PLANE_FAR),
|
||||
persp.get_projection_plane(Projection::PLANE_LEFT),
|
||||
persp.get_projection_plane(Projection::PLANE_TOP),
|
||||
persp.get_projection_plane(Projection::PLANE_RIGHT),
|
||||
persp.get_projection_plane(Projection::PLANE_BOTTOM)
|
||||
};
|
||||
|
||||
CHECK(plane_array[Projection::PLANE_NEAR].normalized().is_equal_approx(planes[Projection::PLANE_NEAR].normalized()));
|
||||
CHECK(plane_array[Projection::PLANE_FAR].normalized().is_equal_approx(planes[Projection::PLANE_FAR].normalized()));
|
||||
CHECK(plane_array[Projection::PLANE_LEFT].normalized().is_equal_approx(planes[Projection::PLANE_LEFT].normalized()));
|
||||
CHECK(plane_array[Projection::PLANE_TOP].normalized().is_equal_approx(planes[Projection::PLANE_TOP].normalized()));
|
||||
CHECK(plane_array[Projection::PLANE_RIGHT].normalized().is_equal_approx(planes[Projection::PLANE_RIGHT].normalized()));
|
||||
CHECK(plane_array[Projection::PLANE_BOTTOM].normalized().is_equal_approx(planes[Projection::PLANE_BOTTOM].normalized()));
|
||||
}
|
||||
|
||||
TEST_CASE("[Projection] Half extents") {
|
||||
Projection persp = Projection::create_perspective(90, 1, 1, 40, false);
|
||||
Vector2 ne = persp.get_viewport_half_extents();
|
||||
Vector2 fe = persp.get_far_plane_half_extents();
|
||||
|
||||
CHECK(ne.is_equal_approx(Vector2(1, 1) * 1));
|
||||
CHECK(fe.is_equal_approx(Vector2(1, 1) * 40));
|
||||
}
|
||||
|
||||
TEST_CASE("[Projection] Endpoints") {
|
||||
Projection persp = Projection::create_perspective(90, 1, 1, 40, false);
|
||||
Vector3 ep[8];
|
||||
persp.get_endpoints(Transform3D(), ep);
|
||||
|
||||
CHECK(ep[0].is_equal_approx(Vector3(-1, 1, -1) * 40));
|
||||
CHECK(ep[1].is_equal_approx(Vector3(-1, -1, -1) * 40));
|
||||
CHECK(ep[2].is_equal_approx(Vector3(1, 1, -1) * 40));
|
||||
CHECK(ep[3].is_equal_approx(Vector3(1, -1, -1) * 40));
|
||||
CHECK(ep[4].is_equal_approx(Vector3(-1, 1, -1) * 1));
|
||||
CHECK(ep[5].is_equal_approx(Vector3(-1, -1, -1) * 1));
|
||||
CHECK(ep[6].is_equal_approx(Vector3(1, 1, -1) * 1));
|
||||
CHECK(ep[7].is_equal_approx(Vector3(1, -1, -1) * 1));
|
||||
}
|
||||
|
||||
} //namespace TestProjection
|
||||
|
||||
#endif // TEST_PROJECTION_H
|
||||
|
|
@ -235,6 +235,39 @@ TEST_CASE("[Quaternion] Construct Basis Axes") {
|
|||
CHECK(q[3] == doctest::Approx(0.8582598));
|
||||
}
|
||||
|
||||
TEST_CASE("[Quaternion] Construct Shortest Arc For 180 Degree Arc") {
|
||||
Vector3 up(0, 1, 0);
|
||||
Vector3 down(0, -1, 0);
|
||||
Vector3 left(-1, 0, 0);
|
||||
Vector3 right(1, 0, 0);
|
||||
Vector3 forward(0, 0, -1);
|
||||
Vector3 back(0, 0, 1);
|
||||
|
||||
// When we have a 180 degree rotation quaternion which was defined as
|
||||
// A to B, logically when we transform A we expect to get B.
|
||||
Quaternion left_to_right(left, right);
|
||||
Quaternion right_to_left(right, left);
|
||||
CHECK(left_to_right.xform(left).is_equal_approx(right));
|
||||
CHECK(Quaternion(right, left).xform(right).is_equal_approx(left));
|
||||
CHECK(Quaternion(up, down).xform(up).is_equal_approx(down));
|
||||
CHECK(Quaternion(down, up).xform(down).is_equal_approx(up));
|
||||
CHECK(Quaternion(forward, back).xform(forward).is_equal_approx(back));
|
||||
CHECK(Quaternion(back, forward).xform(back).is_equal_approx(forward));
|
||||
|
||||
// With (arbitrary) opposite vectors that are not axis-aligned as parameters.
|
||||
Vector3 diagonal_up = Vector3(1.2, 2.3, 4.5).normalized();
|
||||
Vector3 diagonal_down = -diagonal_up;
|
||||
Quaternion q1(diagonal_up, diagonal_down);
|
||||
CHECK(q1.xform(diagonal_down).is_equal_approx(diagonal_up));
|
||||
CHECK(q1.xform(diagonal_up).is_equal_approx(diagonal_down));
|
||||
|
||||
// For the consistency of the rotation direction, they should be symmetrical to the plane.
|
||||
CHECK(left_to_right.is_equal_approx(right_to_left.inverse()));
|
||||
|
||||
// If vectors are same, no rotation.
|
||||
CHECK(Quaternion(diagonal_up, diagonal_up).is_equal_approx(Quaternion()));
|
||||
}
|
||||
|
||||
TEST_CASE("[Quaternion] Get Euler Orders") {
|
||||
double x = Math::deg_to_rad(30.0);
|
||||
double y = Math::deg_to_rad(45.0);
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ TEST_CASE("[Rect2] Constructor methods") {
|
|||
TEST_CASE("[Rect2] String conversion") {
|
||||
// Note: This also depends on the Vector2 string representation.
|
||||
CHECK_MESSAGE(
|
||||
String(Rect2(0, 100, 1280, 720)) == "[P: (0, 100), S: (1280, 720)]",
|
||||
String(Rect2(0, 100, 1280, 720)) == "[P: (0.0, 100.0), S: (1280.0, 720.0)]",
|
||||
"The string representation should match the expected value.");
|
||||
}
|
||||
|
||||
|
|
@ -180,6 +180,28 @@ TEST_CASE("[Rect2] Expanding") {
|
|||
"expand() with non-contained Vector2 should return the expected result.");
|
||||
}
|
||||
|
||||
TEST_CASE("[Rect2] Get support") {
|
||||
const Rect2 rect = Rect2(Vector2(-1.5, 2), Vector2(4, 5));
|
||||
CHECK_MESSAGE(
|
||||
rect.get_support(Vector2(1, 0)) == Vector2(2.5, 2),
|
||||
"get_support() should return the expected value.");
|
||||
CHECK_MESSAGE(
|
||||
rect.get_support(Vector2(0.5, 1)) == Vector2(2.5, 7),
|
||||
"get_support() should return the expected value.");
|
||||
CHECK_MESSAGE(
|
||||
rect.get_support(Vector2(0.5, 1)) == Vector2(2.5, 7),
|
||||
"get_support() should return the expected value.");
|
||||
CHECK_MESSAGE(
|
||||
rect.get_support(Vector2(0, -1)) == Vector2(-1.5, 2),
|
||||
"get_support() should return the expected value.");
|
||||
CHECK_MESSAGE(
|
||||
rect.get_support(Vector2(0, -0.1)) == Vector2(-1.5, 2),
|
||||
"get_support() should return the expected value.");
|
||||
CHECK_MESSAGE(
|
||||
rect.get_support(Vector2()) == Vector2(-1.5, 2),
|
||||
"get_support() should return the Rect2 position when given a zero vector.");
|
||||
}
|
||||
|
||||
TEST_CASE("[Rect2] Growing") {
|
||||
CHECK_MESSAGE(
|
||||
Rect2(0, 100, 1280, 720).grow(100).is_equal_approx(Rect2(-100, 0, 1480, 920)),
|
||||
|
|
|
|||
|
|
@ -289,6 +289,38 @@ bool arg_default_value_is_assignable_to_type(const Context &p_context, const Var
|
|||
return false;
|
||||
}
|
||||
|
||||
bool arg_default_value_is_valid_data(const Variant &p_val, String *r_err_msg = nullptr) {
|
||||
switch (p_val.get_type()) {
|
||||
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::PACKED_VECTOR4_ARRAY:
|
||||
case Variant::CALLABLE:
|
||||
case Variant::SIGNAL:
|
||||
case Variant::OBJECT:
|
||||
if (p_val.is_zero()) {
|
||||
return true;
|
||||
}
|
||||
if (r_err_msg) {
|
||||
*r_err_msg = "Must be zero.";
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void validate_property(const Context &p_context, const ExposedClass &p_class, const PropertyData &p_prop) {
|
||||
const MethodData *setter = p_class.find_method_by_name(p_prop.setter);
|
||||
|
||||
|
|
@ -411,6 +443,14 @@ void validate_argument(const Context &p_context, const ExposedClass &p_class, co
|
|||
}
|
||||
|
||||
TEST_COND(!arg_defval_assignable_to_type, err_msg);
|
||||
|
||||
bool arg_defval_valid_data = arg_default_value_is_valid_data(p_arg.defval, &type_error_msg);
|
||||
|
||||
if (!type_error_msg.is_empty()) {
|
||||
err_msg += " " + type_error_msg;
|
||||
}
|
||||
|
||||
TEST_COND(!arg_defval_valid_data, err_msg);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -563,7 +603,7 @@ void add_exposed_classes(Context &r_context) {
|
|||
|
||||
MethodData method;
|
||||
method.name = method_info.name;
|
||||
TEST_FAIL_COND(!String(method.name).is_valid_identifier(),
|
||||
TEST_FAIL_COND(!String(method.name).is_valid_ascii_identifier(),
|
||||
"Method name is not a valid identifier: '", exposed_class.name, ".", method.name, "'.");
|
||||
|
||||
if (method_info.flags & METHOD_FLAG_VIRTUAL) {
|
||||
|
|
@ -689,7 +729,7 @@ void add_exposed_classes(Context &r_context) {
|
|||
const MethodInfo &method_info = signal_map.get(K.key);
|
||||
|
||||
signal.name = method_info.name;
|
||||
TEST_FAIL_COND(!String(signal.name).is_valid_identifier(),
|
||||
TEST_FAIL_COND(!String(signal.name).is_valid_ascii_identifier(),
|
||||
"Signal name is not a valid identifier: '", exposed_class.name, ".", signal.name, "'.");
|
||||
|
||||
int i = 0;
|
||||
|
|
@ -823,16 +863,19 @@ void add_global_enums(Context &r_context) {
|
|||
}
|
||||
}
|
||||
|
||||
// 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 (const StringName &E : hardcoded_enums) {
|
||||
// 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);
|
||||
for (int i = 0; i < Variant::VARIANT_MAX; i++) {
|
||||
if (i == Variant::OBJECT) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const Variant::Type type = Variant::Type(i);
|
||||
|
||||
List<StringName> enum_names;
|
||||
Variant::get_enums_for_type(type, &enum_names);
|
||||
|
||||
for (const StringName &enum_name : enum_names) {
|
||||
r_context.enum_types.push_back(Variant::get_type_name(type) + "." + enum_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -37,6 +37,16 @@
|
|||
|
||||
#include "tests/test_macros.h"
|
||||
|
||||
#ifdef SANITIZERS_ENABLED
|
||||
#ifdef __has_feature
|
||||
#if __has_feature(address_sanitizer) || __has_feature(thread_sanitizer)
|
||||
#define ASAN_OR_TSAN_ENABLED
|
||||
#endif
|
||||
#elif defined(__SANITIZE_ADDRESS__) || defined(__SANITIZE_THREAD__)
|
||||
#define ASAN_OR_TSAN_ENABLED
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// Declared in global namespace because of GDCLASS macro warning (Windows):
|
||||
// "Unqualified friend declaration referring to type outside of the nearest enclosing namespace
|
||||
// is a Microsoft extension; add a nested name specifier".
|
||||
|
|
@ -85,10 +95,10 @@ public:
|
|||
}
|
||||
bool property_can_revert(const StringName &p_name) const override {
|
||||
return false;
|
||||
};
|
||||
}
|
||||
bool property_get_revert(const StringName &p_name, Variant &r_ret) const override {
|
||||
return false;
|
||||
};
|
||||
}
|
||||
void get_method_list(List<MethodInfo> *p_list) const override {
|
||||
}
|
||||
bool has_method(const StringName &p_method) const override {
|
||||
|
|
@ -174,6 +184,31 @@ TEST_CASE("[Object] Metadata") {
|
|||
CHECK_MESSAGE(
|
||||
meta_list2.size() == 0,
|
||||
"The metadata list should contain 0 items after removing all metadata items.");
|
||||
|
||||
Object other;
|
||||
object.set_meta("conflicting_meta", "string");
|
||||
object.set_meta("not_conflicting_meta", 123);
|
||||
other.set_meta("conflicting_meta", Color(0, 1, 0));
|
||||
other.set_meta("other_meta", "other");
|
||||
object.merge_meta_from(&other);
|
||||
|
||||
CHECK_MESSAGE(
|
||||
Color(object.get_meta("conflicting_meta")).is_equal_approx(Color(0, 1, 0)),
|
||||
"String meta should be overwritten with Color after merging.");
|
||||
|
||||
CHECK_MESSAGE(
|
||||
int(object.get_meta("not_conflicting_meta")) == 123,
|
||||
"Not conflicting meta on destination should be kept intact.");
|
||||
|
||||
CHECK_MESSAGE(
|
||||
object.get_meta("other_meta", String()) == "other",
|
||||
"Not conflicting meta name on source should merged.");
|
||||
|
||||
List<StringName> meta_list3;
|
||||
object.get_meta_list(&meta_list3);
|
||||
CHECK_MESSAGE(
|
||||
meta_list3.size() == 3,
|
||||
"The metadata list should contain 3 items after merging meta from two objects.");
|
||||
}
|
||||
|
||||
TEST_CASE("[Object] Construction") {
|
||||
|
|
@ -499,6 +534,74 @@ TEST_CASE("[Object] Notification order") { // GH-52325
|
|||
memdelete(test_notification_object);
|
||||
}
|
||||
|
||||
TEST_CASE("[Object] Destruction at the end of the call chain is safe") {
|
||||
Object *object = memnew(Object);
|
||||
ObjectID obj_id = object->get_instance_id();
|
||||
|
||||
class _SelfDestroyingScriptInstance : public _MockScriptInstance {
|
||||
Object *self = nullptr;
|
||||
|
||||
// This has to be static because ~Object() also destroys the script instance.
|
||||
static void free_self(Object *p_self) {
|
||||
#if defined(ASAN_OR_TSAN_ENABLED)
|
||||
// Regular deletion is enough becausa asan/tsan will catch a potential heap-after-use.
|
||||
memdelete(p_self);
|
||||
#else
|
||||
// Without asan/tsan, try at least to force a crash by replacing the otherwise seemingly good data with garbage.
|
||||
// Operations such as dereferencing pointers or decreasing a refcount would fail.
|
||||
// Unfortunately, we may not poison the memory after the deletion, because the memory would no longer belong to us
|
||||
// and on doing so we may cause a more generalized crash on some platforms (allocator implementations).
|
||||
p_self->~Object();
|
||||
memset((void *)p_self, 0, sizeof(Object));
|
||||
Memory::free_static(p_self, false);
|
||||
#endif
|
||||
}
|
||||
|
||||
public:
|
||||
Variant callp(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) override {
|
||||
free_self(self);
|
||||
return Variant();
|
||||
}
|
||||
Variant call_const(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) override {
|
||||
free_self(self);
|
||||
return Variant();
|
||||
}
|
||||
bool has_method(const StringName &p_method) const override {
|
||||
return p_method == "some_method";
|
||||
}
|
||||
|
||||
public:
|
||||
_SelfDestroyingScriptInstance(Object *p_self) :
|
||||
self(p_self) {}
|
||||
};
|
||||
|
||||
_SelfDestroyingScriptInstance *script_instance = memnew(_SelfDestroyingScriptInstance(object));
|
||||
object->set_script_instance(script_instance);
|
||||
|
||||
SUBCASE("Within callp()") {
|
||||
SUBCASE("Through call()") {
|
||||
object->call("some_method");
|
||||
}
|
||||
SUBCASE("Through callv()") {
|
||||
object->callv("some_method", Array());
|
||||
}
|
||||
}
|
||||
SUBCASE("Within call_const()") {
|
||||
Callable::CallError call_error;
|
||||
object->call_const("some_method", nullptr, 0, call_error);
|
||||
}
|
||||
SUBCASE("Within signal handling (from emit_signalp(), through emit_signal())") {
|
||||
Object emitter;
|
||||
emitter.add_user_signal(MethodInfo("some_signal"));
|
||||
emitter.connect("some_signal", Callable(object, "some_method"));
|
||||
emitter.emit_signal("some_signal");
|
||||
}
|
||||
|
||||
CHECK_MESSAGE(
|
||||
ObjectDB::get_instance(obj_id) == nullptr,
|
||||
"Object was tail-deleted without crashes.");
|
||||
}
|
||||
|
||||
} // namespace TestObject
|
||||
|
||||
#endif // TEST_OBJECT_H
|
||||
|
|
|
|||
83
engine/tests/core/string/test_fuzzy_search.h
Normal file
83
engine/tests/core/string/test_fuzzy_search.h
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
/**************************************************************************/
|
||||
/* test_fuzzy_search.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#ifndef TEST_FUZZY_SEARCH_H
|
||||
#define TEST_FUZZY_SEARCH_H
|
||||
|
||||
#include "core/string/fuzzy_search.h"
|
||||
#include "tests/test_macros.h"
|
||||
|
||||
namespace TestFuzzySearch {
|
||||
|
||||
struct FuzzySearchTestCase {
|
||||
String query;
|
||||
String expected;
|
||||
};
|
||||
|
||||
// Ideally each of these test queries should represent a different aspect, and potentially bottleneck, of the search process.
|
||||
const FuzzySearchTestCase test_cases[] = {
|
||||
// Short query, many matches, few adjacent characters
|
||||
{ "///gd", "./menu/hud/hud.gd" },
|
||||
// Filename match with typo
|
||||
{ "sm.png", "./entity/blood_sword/sam.png" },
|
||||
// Multipart filename word matches
|
||||
{ "ham ", "./entity/game_trap/ha_missed_me.wav" },
|
||||
// Single word token matches
|
||||
{ "push background", "./entity/background_zone1/background/push.png" },
|
||||
// Long token matches
|
||||
{ "background_freighter background png", "./entity/background_freighter/background/background.png" },
|
||||
// Many matches, many short tokens
|
||||
{ "menu menu characters wav", "./menu/menu/characters/smoker/0.wav" },
|
||||
// Maximize total matches
|
||||
{ "entity gd", "./entity/entity_man.gd" }
|
||||
};
|
||||
|
||||
Vector<String> load_test_data() {
|
||||
Ref<FileAccess> fp = FileAccess::open(TestUtils::get_data_path("fuzzy_search/project_dir_tree.txt"), FileAccess::READ);
|
||||
REQUIRE(fp.is_valid());
|
||||
return fp->get_as_utf8_string().split("\n");
|
||||
}
|
||||
|
||||
TEST_CASE("[FuzzySearch] Test fuzzy search results") {
|
||||
FuzzySearch search;
|
||||
Vector<FuzzySearchResult> results;
|
||||
Vector<String> targets = load_test_data();
|
||||
|
||||
for (FuzzySearchTestCase test_case : test_cases) {
|
||||
search.set_query(test_case.query);
|
||||
search.search_all(targets, results);
|
||||
CHECK_GT(results.size(), 0);
|
||||
CHECK_EQ(results[0].target, test_case.expected);
|
||||
}
|
||||
}
|
||||
|
||||
} //namespace TestFuzzySearch
|
||||
|
||||
#endif // TEST_FUZZY_SEARCH_H
|
||||
|
|
@ -169,28 +169,31 @@ TEST_CASE("[NodePath] Empty path") {
|
|||
}
|
||||
|
||||
TEST_CASE("[NodePath] Slice") {
|
||||
const NodePath node_path_relative = NodePath("Parent/Child:prop");
|
||||
const NodePath node_path_relative = NodePath("Parent/Child:prop:subprop");
|
||||
const NodePath node_path_absolute = NodePath("/root/Parent/Child:prop");
|
||||
CHECK_MESSAGE(
|
||||
node_path_relative.slice(0, 2) == NodePath("Parent/Child"),
|
||||
"The slice lower bound should be inclusive and the slice upper bound should be exclusive.");
|
||||
CHECK_MESSAGE(
|
||||
node_path_relative.slice(3) == NodePath(":prop"),
|
||||
"Slicing on the length of the path should return the last entry.");
|
||||
node_path_relative.slice(3) == NodePath(":subprop"),
|
||||
"Slicing on the last index (length - 1) should return the last entry.");
|
||||
CHECK_MESSAGE(
|
||||
node_path_relative.slice(1) == NodePath("Child:prop:subprop"),
|
||||
"Slicing without upper bound should return remaining entries after index.");
|
||||
CHECK_MESSAGE(
|
||||
node_path_relative.slice(1, 3) == NodePath("Child:prop"),
|
||||
"Slicing should include names and subnames.");
|
||||
CHECK_MESSAGE(
|
||||
node_path_relative.slice(-1) == NodePath(":prop"),
|
||||
node_path_relative.slice(-1) == NodePath(":subprop"),
|
||||
"Slicing on -1 should return the last entry.");
|
||||
CHECK_MESSAGE(
|
||||
node_path_relative.slice(0, -1) == NodePath("Parent/Child"),
|
||||
node_path_relative.slice(0, -1) == NodePath("Parent/Child:prop"),
|
||||
"Slicing up to -1 should include the second-to-last entry.");
|
||||
CHECK_MESSAGE(
|
||||
node_path_relative.slice(-2, -1) == NodePath("Child"),
|
||||
node_path_relative.slice(-2, -1) == NodePath(":prop"),
|
||||
"Slicing from negative to negative should treat lower bound as inclusive and upper bound as exclusive.");
|
||||
CHECK_MESSAGE(
|
||||
node_path_relative.slice(0, 10) == NodePath("Parent/Child:prop"),
|
||||
node_path_relative.slice(0, 10) == NodePath("Parent/Child:prop:subprop"),
|
||||
"Slicing past the length of the path should work like slicing up to the last entry.");
|
||||
CHECK_MESSAGE(
|
||||
node_path_relative.slice(-10, 2) == NodePath("Parent/Child"),
|
||||
|
|
|
|||
|
|
@ -389,6 +389,19 @@ TEST_CASE("[String] Find") {
|
|||
MULTICHECK_STRING_INT_EQ(s, rfind, "", 15, -1);
|
||||
}
|
||||
|
||||
TEST_CASE("[String] Find character") {
|
||||
String s = "racecar";
|
||||
CHECK_EQ(s.find_char('r'), 0);
|
||||
CHECK_EQ(s.find_char('r', 1), 6);
|
||||
CHECK_EQ(s.find_char('e'), 3);
|
||||
CHECK_EQ(s.find_char('e', 4), -1);
|
||||
|
||||
CHECK_EQ(s.rfind_char('r'), 6);
|
||||
CHECK_EQ(s.rfind_char('r', 5), 0);
|
||||
CHECK_EQ(s.rfind_char('e'), 3);
|
||||
CHECK_EQ(s.rfind_char('e', 2), -1);
|
||||
}
|
||||
|
||||
TEST_CASE("[String] Find case insensitive") {
|
||||
String s = "Pretty Whale Whale";
|
||||
MULTICHECK_STRING_EQ(s, findn, "WHA", 7);
|
||||
|
|
@ -433,6 +446,19 @@ TEST_CASE("[String] Insertion") {
|
|||
String s = "Who is Frederic?";
|
||||
s = s.insert(s.find("?"), " Chopin");
|
||||
CHECK(s == "Who is Frederic Chopin?");
|
||||
|
||||
s = "foobar";
|
||||
CHECK(s.insert(0, "X") == "Xfoobar");
|
||||
CHECK(s.insert(-100, "X") == "foobar");
|
||||
CHECK(s.insert(6, "X") == "foobarX");
|
||||
CHECK(s.insert(100, "X") == "foobarX");
|
||||
CHECK(s.insert(2, "") == "foobar");
|
||||
|
||||
s = "";
|
||||
CHECK(s.insert(0, "abc") == "abc");
|
||||
CHECK(s.insert(100, "abc") == "abc");
|
||||
CHECK(s.insert(-100, "abc") == "");
|
||||
CHECK(s.insert(0, "") == "");
|
||||
}
|
||||
|
||||
TEST_CASE("[String] Erasing") {
|
||||
|
|
@ -442,16 +468,32 @@ TEST_CASE("[String] Erasing") {
|
|||
}
|
||||
|
||||
TEST_CASE("[String] Number to string") {
|
||||
CHECK(String::num(0) == "0");
|
||||
CHECK(String::num(0.0) == "0"); // No trailing zeros.
|
||||
CHECK(String::num(-0.0) == "-0"); // Includes sign even for zero.
|
||||
CHECK(String::num(0) == "0.0"); // The method takes double, so always add zeros.
|
||||
CHECK(String::num(0.0) == "0.0");
|
||||
CHECK(String::num(-0.0) == "-0.0"); // Includes sign even for zero.
|
||||
CHECK(String::num(3.141593) == "3.141593");
|
||||
CHECK(String::num(3.141593, 3) == "3.142");
|
||||
CHECK(String::num(42.100023, 4) == "42.1"); // No trailing zeros.
|
||||
CHECK(String::num_scientific(30000000) == "3e+07");
|
||||
|
||||
// String::num_int64 tests.
|
||||
CHECK(String::num_int64(3141593) == "3141593");
|
||||
CHECK(String::num_int64(-3141593) == "-3141593");
|
||||
CHECK(String::num_int64(0xA141593, 16) == "a141593");
|
||||
CHECK(String::num_int64(0xA141593, 16, true) == "A141593");
|
||||
CHECK(String::num(42.100023, 4) == "42.1"); // No trailing zeros.
|
||||
ERR_PRINT_OFF;
|
||||
CHECK(String::num_int64(3141593, 1) == ""); // Invalid base < 2.
|
||||
CHECK(String::num_int64(3141593, 37) == ""); // Invalid base > 36.
|
||||
ERR_PRINT_ON;
|
||||
|
||||
// String::num_uint64 tests.
|
||||
CHECK(String::num_uint64(4294967295) == "4294967295");
|
||||
CHECK(String::num_uint64(0xF141593, 16) == "f141593");
|
||||
CHECK(String::num_uint64(0xF141593, 16, true) == "F141593");
|
||||
ERR_PRINT_OFF;
|
||||
CHECK(String::num_uint64(4294967295, 1) == ""); // Invalid base < 2.
|
||||
CHECK(String::num_uint64(4294967295, 37) == ""); // Invalid base > 36.
|
||||
ERR_PRINT_ON;
|
||||
|
||||
// String::num_real tests.
|
||||
CHECK(String::num_real(1.0) == "1.0");
|
||||
|
|
@ -463,15 +505,15 @@ TEST_CASE("[String] Number to string") {
|
|||
CHECK(String::num_real(3.141593) == "3.141593");
|
||||
CHECK(String::num_real(3.141) == "3.141"); // No trailing zeros.
|
||||
#ifdef REAL_T_IS_DOUBLE
|
||||
CHECK_MESSAGE(String::num_real(123.456789) == "123.456789", "Prints the appropriate amount of digits for real_t = double.");
|
||||
CHECK_MESSAGE(String::num_real(-123.456789) == "-123.456789", "Prints the appropriate amount of digits for real_t = double.");
|
||||
CHECK_MESSAGE(String::num_real(Math_PI) == "3.14159265358979", "Prints the appropriate amount of digits for real_t = double.");
|
||||
CHECK_MESSAGE(String::num_real(3.1415f) == "3.1414999961853", "Prints more digits of 32-bit float when real_t = double (ones that would be reliable for double) and no trailing zero.");
|
||||
CHECK_MESSAGE(String::num_real(real_t(123.456789)) == "123.456789", "Prints the appropriate amount of digits for real_t = double.");
|
||||
CHECK_MESSAGE(String::num_real(real_t(-123.456789)) == "-123.456789", "Prints the appropriate amount of digits for real_t = double.");
|
||||
CHECK_MESSAGE(String::num_real(real_t(Math_PI)) == "3.14159265358979", "Prints the appropriate amount of digits for real_t = double.");
|
||||
CHECK_MESSAGE(String::num_real(real_t(3.1415f)) == "3.1414999961853", "Prints more digits of 32-bit float when real_t = double (ones that would be reliable for double) and no trailing zero.");
|
||||
#else
|
||||
CHECK_MESSAGE(String::num_real(123.456789) == "123.4568", "Prints the appropriate amount of digits for real_t = float.");
|
||||
CHECK_MESSAGE(String::num_real(-123.456789) == "-123.4568", "Prints the appropriate amount of digits for real_t = float.");
|
||||
CHECK_MESSAGE(String::num_real(Math_PI) == "3.141593", "Prints the appropriate amount of digits for real_t = float.");
|
||||
CHECK_MESSAGE(String::num_real(3.1415f) == "3.1415", "Prints only reliable digits of 32-bit float when real_t = float.");
|
||||
CHECK_MESSAGE(String::num_real(real_t(123.456789)) == "123.4568", "Prints the appropriate amount of digits for real_t = float.");
|
||||
CHECK_MESSAGE(String::num_real(real_t(-123.456789)) == "-123.4568", "Prints the appropriate amount of digits for real_t = float.");
|
||||
CHECK_MESSAGE(String::num_real(real_t(Math_PI)) == "3.141593", "Prints the appropriate amount of digits for real_t = float.");
|
||||
CHECK_MESSAGE(String::num_real(real_t(3.1415f)) == "3.1415", "Prints only reliable digits of 32-bit float when real_t = float.");
|
||||
#endif // REAL_T_IS_DOUBLE
|
||||
|
||||
// Checks doubles with many decimal places.
|
||||
|
|
@ -480,7 +522,7 @@ TEST_CASE("[String] Number to string") {
|
|||
CHECK(String::num(-0.0000012345432123454321) == "-0.00000123454321");
|
||||
CHECK(String::num(-10000.0000012345432123454321) == "-10000.0000012345");
|
||||
CHECK(String::num(0.0000000000012345432123454321) == "0.00000000000123");
|
||||
CHECK(String::num(0.0000000000012345432123454321, 3) == "0");
|
||||
CHECK(String::num(0.0000000000012345432123454321, 3) == "0.0");
|
||||
|
||||
// Note: When relevant (remainder > 0.5), the last digit gets rounded up,
|
||||
// which can also lead to not include a trailing zero, e.g. "...89" -> "...9".
|
||||
|
|
@ -503,7 +545,10 @@ TEST_CASE("[String] String to integer") {
|
|||
CHECK(String(nums[i]).to_int() == num[i]);
|
||||
}
|
||||
CHECK(String("0b1011").to_int() == 1011); // Looks like a binary number, but to_int() handles this as a base-10 number, "b" is just ignored.
|
||||
CHECK(String("0B1011").to_int() == 1011);
|
||||
|
||||
CHECK(String("0x1012").to_int() == 1012); // Looks like a hexadecimal number, but to_int() handles this as a base-10 number, "x" is just ignored.
|
||||
CHECK(String("0X1012").to_int() == 1012);
|
||||
|
||||
ERR_PRINT_OFF
|
||||
CHECK(String("999999999999999999999999999999999999999999999999999999999").to_int() == INT64_MAX); // Too large, largest possible is returned.
|
||||
|
|
@ -512,10 +557,10 @@ TEST_CASE("[String] String to integer") {
|
|||
}
|
||||
|
||||
TEST_CASE("[String] Hex to integer") {
|
||||
static const char *nums[12] = { "0xFFAE", "22", "0", "AADDAD", "0x7FFFFFFFFFFFFFFF", "-0xf", "", "000", "000f", "0xaA", "-ff", "-" };
|
||||
static const int64_t num[12] = { 0xFFAE, 0x22, 0, 0xAADDAD, 0x7FFFFFFFFFFFFFFF, -0xf, 0, 0, 0xf, 0xaa, -0xff, 0x0 };
|
||||
static const char *nums[13] = { "0xFFAE", "22", "0", "AADDAD", "0x7FFFFFFFFFFFFFFF", "-0xf", "", "000", "000f", "0xaA", "-ff", "-", "0XFFAE" };
|
||||
static const int64_t num[13] = { 0xFFAE, 0x22, 0, 0xAADDAD, 0x7FFFFFFFFFFFFFFF, -0xf, 0, 0, 0xf, 0xaa, -0xff, 0x0, 0xFFAE };
|
||||
|
||||
for (int i = 0; i < 12; i++) {
|
||||
for (int i = 0; i < 13; i++) {
|
||||
CHECK(String(nums[i]).hex_to_int() == num[i]);
|
||||
}
|
||||
|
||||
|
|
@ -533,10 +578,10 @@ TEST_CASE("[String] Hex to integer") {
|
|||
}
|
||||
|
||||
TEST_CASE("[String] Bin to integer") {
|
||||
static const char *nums[10] = { "", "0", "0b0", "0b1", "0b", "1", "0b1010", "-0b11", "-1010", "0b0111111111111111111111111111111111111111111111111111111111111111" };
|
||||
static const int64_t num[10] = { 0, 0, 0, 1, 0, 1, 10, -3, -10, 0x7FFFFFFFFFFFFFFF };
|
||||
static const char *nums[11] = { "", "0", "0b0", "0b1", "0b", "1", "0b1010", "-0b11", "-1010", "0b0111111111111111111111111111111111111111111111111111111111111111", "0B1010" };
|
||||
static const int64_t num[11] = { 0, 0, 0, 1, 0, 1, 10, -3, -10, 0x7FFFFFFFFFFFFFFF, 10 };
|
||||
|
||||
for (int i = 0; i < 10; i++) {
|
||||
for (int i = 0; i < 11; i++) {
|
||||
CHECK(String(nums[i]).bin_to_int() == num[i]);
|
||||
}
|
||||
|
||||
|
|
@ -638,64 +683,90 @@ TEST_CASE("[String] Ends with") {
|
|||
}
|
||||
|
||||
TEST_CASE("[String] Splitting") {
|
||||
String s = "Mars,Jupiter,Saturn,Uranus";
|
||||
const char *slices_l[3] = { "Mars", "Jupiter", "Saturn,Uranus" };
|
||||
MULTICHECK_SPLIT(s, split, ",", true, 2, slices_l, 3);
|
||||
{
|
||||
const String s = "Mars,Jupiter,Saturn,Uranus";
|
||||
|
||||
const char *slices_r[3] = { "Mars,Jupiter", "Saturn", "Uranus" };
|
||||
MULTICHECK_SPLIT(s, rsplit, ",", true, 2, slices_r, 3);
|
||||
const char *slices_l[3] = { "Mars", "Jupiter", "Saturn,Uranus" };
|
||||
MULTICHECK_SPLIT(s, split, ",", true, 2, slices_l, 3);
|
||||
|
||||
s = "test";
|
||||
const char *slices_3[4] = { "t", "e", "s", "t" };
|
||||
MULTICHECK_SPLIT(s, split, "", true, 0, slices_3, 4);
|
||||
|
||||
s = "";
|
||||
const char *slices_4[1] = { "" };
|
||||
MULTICHECK_SPLIT(s, split, "", true, 0, slices_4, 1);
|
||||
MULTICHECK_SPLIT(s, split, "", false, 0, slices_4, 0);
|
||||
|
||||
s = "Mars Jupiter Saturn Uranus";
|
||||
const char *slices_s[4] = { "Mars", "Jupiter", "Saturn", "Uranus" };
|
||||
Vector<String> l = s.split_spaces();
|
||||
for (int i = 0; i < l.size(); i++) {
|
||||
CHECK(l[i] == slices_s[i]);
|
||||
const char *slices_r[3] = { "Mars,Jupiter", "Saturn", "Uranus" };
|
||||
MULTICHECK_SPLIT(s, rsplit, ",", true, 2, slices_r, 3);
|
||||
}
|
||||
|
||||
s = "1.2;2.3 4.5";
|
||||
const double slices_d[3] = { 1.2, 2.3, 4.5 };
|
||||
|
||||
Vector<double> d_arr;
|
||||
d_arr = s.split_floats(";");
|
||||
CHECK(d_arr.size() == 2);
|
||||
for (int i = 0; i < d_arr.size(); i++) {
|
||||
CHECK(ABS(d_arr[i] - slices_d[i]) <= 0.00001);
|
||||
{
|
||||
const String s = "test";
|
||||
const char *slices[4] = { "t", "e", "s", "t" };
|
||||
MULTICHECK_SPLIT(s, split, "", true, 0, slices, 4);
|
||||
}
|
||||
|
||||
Vector<String> keys;
|
||||
keys.push_back(";");
|
||||
keys.push_back(" ");
|
||||
|
||||
Vector<float> f_arr;
|
||||
f_arr = s.split_floats_mk(keys);
|
||||
CHECK(f_arr.size() == 3);
|
||||
for (int i = 0; i < f_arr.size(); i++) {
|
||||
CHECK(ABS(f_arr[i] - slices_d[i]) <= 0.00001);
|
||||
{
|
||||
const String s = "";
|
||||
const char *slices[1] = { "" };
|
||||
MULTICHECK_SPLIT(s, split, "", true, 0, slices, 1);
|
||||
MULTICHECK_SPLIT(s, split, "", false, 0, slices, 0);
|
||||
}
|
||||
|
||||
s = "1;2 4";
|
||||
const int slices_i[3] = { 1, 2, 4 };
|
||||
|
||||
Vector<int> ii;
|
||||
ii = s.split_ints(";");
|
||||
CHECK(ii.size() == 2);
|
||||
for (int i = 0; i < ii.size(); i++) {
|
||||
CHECK(ii[i] == slices_i[i]);
|
||||
{
|
||||
const String s = "Mars Jupiter Saturn Uranus";
|
||||
const char *slices[4] = { "Mars", "Jupiter", "Saturn", "Uranus" };
|
||||
Vector<String> l = s.split_spaces();
|
||||
for (int i = 0; i < l.size(); i++) {
|
||||
CHECK(l[i] == slices[i]);
|
||||
}
|
||||
}
|
||||
|
||||
ii = s.split_ints_mk(keys);
|
||||
CHECK(ii.size() == 3);
|
||||
for (int i = 0; i < ii.size(); i++) {
|
||||
CHECK(ii[i] == slices_i[i]);
|
||||
{
|
||||
const String s = "1.2;2.3 4.5";
|
||||
const double slices[3] = { 1.2, 2.3, 4.5 };
|
||||
|
||||
const Vector<double> d_arr = s.split_floats(";");
|
||||
CHECK(d_arr.size() == 2);
|
||||
for (int i = 0; i < d_arr.size(); i++) {
|
||||
CHECK(ABS(d_arr[i] - slices[i]) <= 0.00001);
|
||||
}
|
||||
|
||||
const Vector<String> keys = { ";", " " };
|
||||
const Vector<float> f_arr = s.split_floats_mk(keys);
|
||||
CHECK(f_arr.size() == 3);
|
||||
for (int i = 0; i < f_arr.size(); i++) {
|
||||
CHECK(ABS(f_arr[i] - slices[i]) <= 0.00001);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
const String s = " -2.0 5";
|
||||
const double slices[10] = { 0, -2, 0, 0, 0, 0, 0, 0, 0, 5 };
|
||||
|
||||
const Vector<double> arr = s.split_floats(" ");
|
||||
CHECK(arr.size() == 10);
|
||||
for (int i = 0; i < arr.size(); i++) {
|
||||
CHECK(ABS(arr[i] - slices[i]) <= 0.00001);
|
||||
}
|
||||
|
||||
const Vector<String> keys = { ";", " " };
|
||||
const Vector<float> mk = s.split_floats_mk(keys);
|
||||
CHECK(mk.size() == 10);
|
||||
for (int i = 0; i < mk.size(); i++) {
|
||||
CHECK(mk[i] == slices[i]);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
const String s = "1;2 4";
|
||||
const int slices[3] = { 1, 2, 4 };
|
||||
|
||||
const Vector<int> arr = s.split_ints(";");
|
||||
CHECK(arr.size() == 2);
|
||||
for (int i = 0; i < arr.size(); i++) {
|
||||
CHECK(arr[i] == slices[i]);
|
||||
}
|
||||
|
||||
const Vector<String> keys = { ";", " " };
|
||||
const Vector<int> mk = s.split_ints_mk(keys);
|
||||
CHECK(mk.size() == 3);
|
||||
for (int i = 0; i < mk.size(); i++) {
|
||||
CHECK(mk[i] == slices[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1199,6 +1270,12 @@ TEST_CASE("[String] is_subsequence_of") {
|
|||
CHECK(String("Sub").is_subsequence_ofn(a));
|
||||
}
|
||||
|
||||
TEST_CASE("[String] is_lowercase") {
|
||||
CHECK(String("abcd1234 !@#$%^&*()_-=+,.<>/\\|[]{};':\"`~").is_lowercase());
|
||||
CHECK(String("").is_lowercase());
|
||||
CHECK(!String("abc_ABC").is_lowercase());
|
||||
}
|
||||
|
||||
TEST_CASE("[String] match") {
|
||||
CHECK(String("img1.png").match("*.png"));
|
||||
CHECK(!String("img1.jpeg").match("*.png"));
|
||||
|
|
@ -1594,7 +1671,7 @@ TEST_CASE("[String] Path functions") {
|
|||
static const char *base_name[8] = { "C:\\Godot\\project\\test", "/Godot/project/test", "../Godot/project/test", "Godot\\test", "C:\\test", "res://test", "user://test", "/" };
|
||||
static const char *ext[8] = { "tscn", "xscn", "scn", "doc", "", "", "", "test" };
|
||||
static const char *file[8] = { "test.tscn", "test.xscn", "test.scn", "test.doc", "test.", "test", "test", ".test" };
|
||||
static const char *simplified[8] = { "C:/Godot/project/test.tscn", "/Godot/project/test.xscn", "Godot/project/test.scn", "Godot/test.doc", "C:/test.", "res://test", "user://test", "/.test" };
|
||||
static const char *simplified[8] = { "C:/Godot/project/test.tscn", "/Godot/project/test.xscn", "../Godot/project/test.scn", "Godot/test.doc", "C:/test.", "res://test", "user://test", "/.test" };
|
||||
static const bool abs[8] = { true, true, false, false, true, true, true, true };
|
||||
|
||||
for (int i = 0; i < 8; i++) {
|
||||
|
|
@ -1613,6 +1690,10 @@ TEST_CASE("[String] Path functions") {
|
|||
for (int i = 0; i < 3; i++) {
|
||||
CHECK(String(file_name[i]).is_valid_filename() == valid[i]);
|
||||
}
|
||||
|
||||
CHECK(String("res://texture.png") == String("res://folder/../folder/../texture.png").simplify_path());
|
||||
CHECK(String("res://texture.png") == String("res://folder/sub/../../texture.png").simplify_path());
|
||||
CHECK(String("res://../../texture.png") == String("res://../../texture.png").simplify_path());
|
||||
}
|
||||
|
||||
TEST_CASE("[String] hash") {
|
||||
|
|
@ -1785,31 +1866,45 @@ TEST_CASE("[String] SHA1/SHA256/MD5") {
|
|||
}
|
||||
|
||||
TEST_CASE("[String] Join") {
|
||||
String s = ", ";
|
||||
String comma = ", ";
|
||||
String empty = "";
|
||||
Vector<String> parts;
|
||||
|
||||
CHECK(comma.join(parts) == "");
|
||||
CHECK(empty.join(parts) == "");
|
||||
|
||||
parts.push_back("One");
|
||||
CHECK(comma.join(parts) == "One");
|
||||
CHECK(empty.join(parts) == "One");
|
||||
|
||||
parts.push_back("B");
|
||||
parts.push_back("C");
|
||||
String t = s.join(parts);
|
||||
CHECK(t == "One, B, C");
|
||||
CHECK(comma.join(parts) == "One, B, C");
|
||||
CHECK(empty.join(parts) == "OneBC");
|
||||
|
||||
parts.push_back("");
|
||||
CHECK(comma.join(parts) == "One, B, C, ");
|
||||
CHECK(empty.join(parts) == "OneBC");
|
||||
}
|
||||
|
||||
TEST_CASE("[String] Is_*") {
|
||||
static const char *data[12] = { "-30", "100", "10.1", "10,1", "1e2", "1e-2", "1e2e3", "0xAB", "AB", "Test1", "1Test", "Test*1" };
|
||||
static bool isnum[12] = { true, true, true, false, false, false, false, false, false, false, false, false };
|
||||
static bool isint[12] = { true, true, false, false, false, false, false, false, false, false, false, false };
|
||||
static bool ishex[12] = { true, true, false, false, true, false, true, false, true, false, false, false };
|
||||
static bool ishex_p[12] = { false, false, false, false, false, false, false, true, false, false, false, false };
|
||||
static bool isflt[12] = { true, true, true, false, true, true, false, false, false, false, false, false };
|
||||
static bool isid[12] = { false, false, false, false, false, false, false, false, true, true, false, false };
|
||||
for (int i = 0; i < 12; i++) {
|
||||
String s = String(data[i]);
|
||||
static const char *data[] = { "-30", "100", "10.1", "10,1", "1e2", "1e-2", "1e2e3", "0xAB", "AB", "Test1", "1Test", "Test*1", "文字", "1E2", "1E-2" };
|
||||
static bool isnum[] = { true, true, true, false, false, false, false, false, false, false, false, false, false, false, false };
|
||||
static bool isint[] = { true, true, false, false, false, false, false, false, false, false, false, false, false, false, false };
|
||||
static bool ishex[] = { true, true, false, false, true, false, true, false, true, false, false, false, false, true, false };
|
||||
static bool ishex_p[] = { false, false, false, false, false, false, false, true, false, false, false, false, false, false, false };
|
||||
static bool isflt[] = { true, true, true, false, true, true, false, false, false, false, false, false, false, true, true };
|
||||
static bool isaid[] = { false, false, false, false, false, false, false, false, true, true, false, false, false, false, false };
|
||||
static bool isuid[] = { false, false, false, false, false, false, false, false, true, true, false, false, true, false, false };
|
||||
for (unsigned int i = 0; i < sizeof(data) / sizeof(data[0]); i++) {
|
||||
String s = String::utf8(data[i]);
|
||||
CHECK(s.is_numeric() == isnum[i]);
|
||||
CHECK(s.is_valid_int() == isint[i]);
|
||||
CHECK(s.is_valid_hex_number(false) == ishex[i]);
|
||||
CHECK(s.is_valid_hex_number(true) == ishex_p[i]);
|
||||
CHECK(s.is_valid_float() == isflt[i]);
|
||||
CHECK(s.is_valid_identifier() == isid[i]);
|
||||
CHECK(s.is_valid_ascii_identifier() == isaid[i]);
|
||||
CHECK(s.is_valid_unicode_identifier() == isuid[i]);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1835,18 +1930,32 @@ TEST_CASE("[String] validate_node_name") {
|
|||
CHECK(name_with_invalid_chars.validate_node_name() == "Name with invalid characters ____removed!");
|
||||
}
|
||||
|
||||
TEST_CASE("[String] validate_identifier") {
|
||||
TEST_CASE("[String] validate_ascii_identifier") {
|
||||
String empty_string;
|
||||
CHECK(empty_string.validate_identifier() == "_");
|
||||
CHECK(empty_string.validate_ascii_identifier() == "_");
|
||||
|
||||
String numeric_only = "12345";
|
||||
CHECK(numeric_only.validate_identifier() == "_12345");
|
||||
CHECK(numeric_only.validate_ascii_identifier() == "_12345");
|
||||
|
||||
String name_with_spaces = "Name with spaces";
|
||||
CHECK(name_with_spaces.validate_identifier() == "Name_with_spaces");
|
||||
CHECK(name_with_spaces.validate_ascii_identifier() == "Name_with_spaces");
|
||||
|
||||
String name_with_invalid_chars = U"Invalid characters:@*#&世界";
|
||||
CHECK(name_with_invalid_chars.validate_identifier() == "Invalid_characters_______");
|
||||
CHECK(name_with_invalid_chars.validate_ascii_identifier() == "Invalid_characters_______");
|
||||
}
|
||||
|
||||
TEST_CASE("[String] validate_unicode_identifier") {
|
||||
String empty_string;
|
||||
CHECK(empty_string.validate_unicode_identifier() == "_");
|
||||
|
||||
String numeric_only = "12345";
|
||||
CHECK(numeric_only.validate_unicode_identifier() == "_12345");
|
||||
|
||||
String name_with_spaces = "Name with spaces";
|
||||
CHECK(name_with_spaces.validate_unicode_identifier() == "Name_with_spaces");
|
||||
|
||||
String name_with_invalid_chars = U"Invalid characters:@*#&世界";
|
||||
CHECK(name_with_invalid_chars.validate_unicode_identifier() == U"Invalid_characters_____世界");
|
||||
}
|
||||
|
||||
TEST_CASE("[String] Variant indexed get") {
|
||||
|
|
@ -1921,6 +2030,61 @@ TEST_CASE("[String] Variant ptr indexed set") {
|
|||
CHECK_EQ(s, String("azcd"));
|
||||
}
|
||||
|
||||
TEST_CASE("[String][URL] Parse URL") {
|
||||
#define CHECK_URL(m_url_to_parse, m_expected_schema, m_expected_host, m_expected_port, m_expected_path, m_expected_fragment, m_expected_error) \
|
||||
if (true) { \
|
||||
int port; \
|
||||
String url(m_url_to_parse), schema, host, path, fragment; \
|
||||
\
|
||||
CHECK_EQ(url.parse_url(schema, host, port, path, fragment), m_expected_error); \
|
||||
CHECK_EQ(schema, m_expected_schema); \
|
||||
CHECK_EQ(host, m_expected_host); \
|
||||
CHECK_EQ(path, m_expected_path); \
|
||||
CHECK_EQ(fragment, m_expected_fragment); \
|
||||
CHECK_EQ(port, m_expected_port); \
|
||||
} else \
|
||||
((void)0)
|
||||
|
||||
// All elements.
|
||||
CHECK_URL("https://www.example.com:8080/path/to/file.html#fragment", "https://", "www.example.com", 8080, "/path/to/file.html", "fragment", Error::OK);
|
||||
|
||||
// Valid URLs.
|
||||
CHECK_URL("https://godotengine.org", "https://", "godotengine.org", 0, "", "", Error::OK);
|
||||
CHECK_URL("https://godotengine.org/", "https://", "godotengine.org", 0, "/", "", Error::OK);
|
||||
CHECK_URL("godotengine.org/", "", "godotengine.org", 0, "/", "", Error::OK);
|
||||
CHECK_URL("HTTPS://godotengine.org/", "https://", "godotengine.org", 0, "/", "", Error::OK);
|
||||
CHECK_URL("https://GODOTENGINE.ORG/", "https://", "godotengine.org", 0, "/", "", Error::OK);
|
||||
CHECK_URL("http://godotengine.org", "http://", "godotengine.org", 0, "", "", Error::OK);
|
||||
CHECK_URL("https://godotengine.org:8080", "https://", "godotengine.org", 8080, "", "", Error::OK);
|
||||
CHECK_URL("https://godotengine.org/blog", "https://", "godotengine.org", 0, "/blog", "", Error::OK);
|
||||
CHECK_URL("https://godotengine.org/blog/", "https://", "godotengine.org", 0, "/blog/", "", Error::OK);
|
||||
CHECK_URL("https://docs.godotengine.org/en/stable", "https://", "docs.godotengine.org", 0, "/en/stable", "", Error::OK);
|
||||
CHECK_URL("https://docs.godotengine.org/en/stable/", "https://", "docs.godotengine.org", 0, "/en/stable/", "", Error::OK);
|
||||
CHECK_URL("https://me:secret@godotengine.org", "https://", "godotengine.org", 0, "", "", Error::OK);
|
||||
CHECK_URL("https://[FEDC:BA98:7654:3210:FEDC:BA98:7654:3210]/ipv6", "https://", "fedc:ba98:7654:3210:fedc:ba98:7654:3210", 0, "/ipv6", "", Error::OK);
|
||||
|
||||
// Scheme vs Fragment.
|
||||
CHECK_URL("google.com/#goto=http://redirect_url/", "", "google.com", 0, "/", "goto=http://redirect_url/", Error::OK);
|
||||
|
||||
// Invalid URLs.
|
||||
|
||||
// Invalid Scheme.
|
||||
CHECK_URL("https_://godotengine.org", "", "https_", 0, "//godotengine.org", "", Error::ERR_INVALID_PARAMETER);
|
||||
|
||||
// Multiple ports.
|
||||
CHECK_URL("https://godotengine.org:8080:433", "https://", "", 0, "", "", Error::ERR_INVALID_PARAMETER);
|
||||
// Missing ] on literal IPv6.
|
||||
CHECK_URL("https://[FEDC:BA98:7654:3210:FEDC:BA98:7654:3210/ipv6", "https://", "", 0, "/ipv6", "", Error::ERR_INVALID_PARAMETER);
|
||||
// Missing host.
|
||||
CHECK_URL("https:///blog", "https://", "", 0, "/blog", "", Error::ERR_INVALID_PARAMETER);
|
||||
// Invalid ports.
|
||||
CHECK_URL("https://godotengine.org:notaport", "https://", "godotengine.org", 0, "", "", Error::ERR_INVALID_PARAMETER);
|
||||
CHECK_URL("https://godotengine.org:-8080", "https://", "godotengine.org", -8080, "", "", Error::ERR_INVALID_PARAMETER);
|
||||
CHECK_URL("https://godotengine.org:88888", "https://", "godotengine.org", 88888, "", "", Error::ERR_INVALID_PARAMETER);
|
||||
|
||||
#undef CHECK_URL
|
||||
}
|
||||
|
||||
TEST_CASE("[Stress][String] Empty via ' == String()'") {
|
||||
for (int i = 0; i < 100000; ++i) {
|
||||
String str = "Hello World!";
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@
|
|||
#include "core/string/optimized_translation.h"
|
||||
#include "core/string/translation.h"
|
||||
#include "core/string/translation_po.h"
|
||||
#include "core/string/translation_server.h"
|
||||
|
||||
#ifdef TOOLS_ENABLED
|
||||
#include "editor/import/resource_importer_csv_translation.h"
|
||||
|
|
@ -160,7 +161,7 @@ TEST_CASE("[TranslationCSV] CSV import") {
|
|||
|
||||
List<String> gen_files;
|
||||
|
||||
Error result = import_csv_translation->import(TestUtils::get_data_path("translations.csv"),
|
||||
Error result = import_csv_translation->import(0, TestUtils::get_data_path("translations.csv"),
|
||||
"", options, nullptr, &gen_files);
|
||||
CHECK(result == OK);
|
||||
CHECK(gen_files.size() == 4);
|
||||
|
|
|
|||
|
|
@ -31,33 +31,43 @@
|
|||
#ifndef TEST_TRANSLATION_SERVER_H
|
||||
#define TEST_TRANSLATION_SERVER_H
|
||||
|
||||
#include "core/string/translation.h"
|
||||
#include "core/string/translation_server.h"
|
||||
|
||||
#include "tests/test_macros.h"
|
||||
|
||||
namespace TestTranslationServer {
|
||||
TEST_CASE("[TranslationServer] Translation operations") {
|
||||
Ref<Translation> t = memnew(Translation);
|
||||
t->set_locale("uk");
|
||||
t->add_message("Good Morning", String::utf8("Добрий ранок"));
|
||||
Ref<Translation> t1 = memnew(Translation);
|
||||
t1->set_locale("uk");
|
||||
t1->add_message("Good Morning", String(U"Добрий ранок"));
|
||||
|
||||
Ref<Translation> t2 = memnew(Translation);
|
||||
t2->set_locale("uk");
|
||||
t2->add_message("Hello Godot", String(U"你好戈多"));
|
||||
|
||||
TranslationServer *ts = TranslationServer::get_singleton();
|
||||
|
||||
// Adds translation for UK locale for the first time.
|
||||
int l_count_before = ts->get_loaded_locales().size();
|
||||
ts->add_translation(t);
|
||||
ts->add_translation(t1);
|
||||
int l_count_after = ts->get_loaded_locales().size();
|
||||
// Newly created Translation object should be added to the list, so the counter should increase, too.
|
||||
CHECK(l_count_after > l_count_before);
|
||||
|
||||
Ref<Translation> trans = ts->get_translation_object("uk");
|
||||
CHECK(trans.is_valid());
|
||||
// Adds translation for UK locale again.
|
||||
ts->add_translation(t2);
|
||||
CHECK_EQ(ts->get_loaded_locales().size(), l_count_after);
|
||||
|
||||
// Removing that translation.
|
||||
ts->remove_translation(t2);
|
||||
CHECK_EQ(ts->get_loaded_locales().size(), l_count_after);
|
||||
|
||||
CHECK(ts->get_translation_object("uk").is_valid());
|
||||
|
||||
ts->set_locale("uk");
|
||||
CHECK(ts->translate("Good Morning") == String::utf8("Добрий ранок"));
|
||||
|
||||
ts->remove_translation(t);
|
||||
trans = ts->get_translation_object("uk");
|
||||
CHECK(trans.is_null());
|
||||
ts->remove_translation(t1);
|
||||
CHECK(ts->get_translation_object("uk").is_null());
|
||||
// If no suitable Translation object has been found - the original message should be returned.
|
||||
CHECK(ts->translate("Good Morning") == "Good Morning");
|
||||
}
|
||||
|
|
@ -94,6 +104,36 @@ TEST_CASE("[TranslationServer] Locale operations") {
|
|||
res = ts->standardize_locale(loc);
|
||||
|
||||
CHECK(res == "de_DE");
|
||||
|
||||
// No added defaults.
|
||||
loc = "es_ES";
|
||||
res = ts->standardize_locale(loc, true);
|
||||
|
||||
CHECK(res == "es_ES");
|
||||
|
||||
// Add default script.
|
||||
loc = "az_AZ";
|
||||
res = ts->standardize_locale(loc, true);
|
||||
|
||||
CHECK(res == "az_Latn_AZ");
|
||||
|
||||
// Add default country.
|
||||
loc = "pa_Arab";
|
||||
res = ts->standardize_locale(loc, true);
|
||||
|
||||
CHECK(res == "pa_Arab_PK");
|
||||
|
||||
// Add default script and country.
|
||||
loc = "zh";
|
||||
res = ts->standardize_locale(loc, true);
|
||||
|
||||
CHECK(res == "zh_Hans_CN");
|
||||
|
||||
// Explicitly don't add defaults.
|
||||
loc = "zh";
|
||||
res = ts->standardize_locale(loc, false);
|
||||
|
||||
CHECK(res == "zh");
|
||||
}
|
||||
|
||||
TEST_CASE("[TranslationServer] Comparing locales") {
|
||||
|
|
@ -110,18 +150,50 @@ TEST_CASE("[TranslationServer] Comparing locales") {
|
|||
locale_a = "sr-Latn-CS";
|
||||
locale_b = "sr-Latn-RS";
|
||||
|
||||
// Two elements from locales match.
|
||||
// Script matches (+1) but country doesn't (-1).
|
||||
res = ts->compare_locales(locale_a, locale_b);
|
||||
|
||||
CHECK(res == 2);
|
||||
CHECK(res == 5);
|
||||
|
||||
locale_a = "uz-Cyrl-UZ";
|
||||
locale_b = "uz-Latn-UZ";
|
||||
|
||||
// Two elements match, but they are not sequentual.
|
||||
// Country matches (+1) but script doesn't (-1).
|
||||
res = ts->compare_locales(locale_a, locale_b);
|
||||
|
||||
CHECK(res == 2);
|
||||
CHECK(res == 5);
|
||||
|
||||
locale_a = "aa-Latn-ER";
|
||||
locale_b = "aa-Latn-ER-saaho";
|
||||
|
||||
// Script and country match (+2) with variant on one locale (+0).
|
||||
res = ts->compare_locales(locale_a, locale_b);
|
||||
|
||||
CHECK(res == 7);
|
||||
|
||||
locale_a = "uz-Cyrl-UZ";
|
||||
locale_b = "uz-Latn-KG";
|
||||
|
||||
// Both script and country mismatched (-2).
|
||||
res = ts->compare_locales(locale_a, locale_b);
|
||||
|
||||
CHECK(res == 3);
|
||||
|
||||
locale_a = "es-ES";
|
||||
locale_b = "es-AR";
|
||||
|
||||
// Mismatched country (-1).
|
||||
res = ts->compare_locales(locale_a, locale_b);
|
||||
|
||||
CHECK(res == 4);
|
||||
|
||||
locale_a = "es";
|
||||
locale_b = "es-AR";
|
||||
|
||||
// No country for one locale (+0).
|
||||
res = ts->compare_locales(locale_a, locale_b);
|
||||
|
||||
CHECK(res == 5);
|
||||
|
||||
locale_a = "es-EC";
|
||||
locale_b = "fr-LU";
|
||||
|
|
@ -130,6 +202,24 @@ TEST_CASE("[TranslationServer] Comparing locales") {
|
|||
res = ts->compare_locales(locale_a, locale_b);
|
||||
|
||||
CHECK(res == 0);
|
||||
|
||||
locale_a = "zh-HK";
|
||||
locale_b = "zh";
|
||||
|
||||
// In full standardization, zh-HK becomes zh_Hant_HK and zh becomes
|
||||
// zh_Hans_CN. Both script and country mismatch (-2).
|
||||
res = ts->compare_locales(locale_a, locale_b);
|
||||
|
||||
CHECK(res == 3);
|
||||
|
||||
locale_a = "zh-CN";
|
||||
locale_b = "zh";
|
||||
|
||||
// In full standardization, zh and zh-CN both become zh_Hans_CN for an
|
||||
// exact match.
|
||||
res = ts->compare_locales(locale_a, locale_b);
|
||||
|
||||
CHECK(res == 10);
|
||||
}
|
||||
} // namespace TestTranslationServer
|
||||
|
||||
|
|
|
|||
313
engine/tests/core/templates/test_a_hash_map.h
Normal file
313
engine/tests/core/templates/test_a_hash_map.h
Normal file
|
|
@ -0,0 +1,313 @@
|
|||
/**************************************************************************/
|
||||
/* test_a_hash_map.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#ifndef TEST_A_HASH_MAP_H
|
||||
#define TEST_A_HASH_MAP_H
|
||||
|
||||
#include "core/templates/a_hash_map.h"
|
||||
|
||||
#include "tests/test_macros.h"
|
||||
|
||||
namespace TestAHashMap {
|
||||
|
||||
TEST_CASE("[AHashMap] List initialization") {
|
||||
AHashMap<int, String> map{ { 0, "A" }, { 1, "B" }, { 2, "C" }, { 3, "D" }, { 4, "E" } };
|
||||
|
||||
CHECK(map.size() == 5);
|
||||
CHECK(map[0] == "A");
|
||||
CHECK(map[1] == "B");
|
||||
CHECK(map[2] == "C");
|
||||
CHECK(map[3] == "D");
|
||||
CHECK(map[4] == "E");
|
||||
}
|
||||
|
||||
TEST_CASE("[AHashMap] List initialization with existing elements") {
|
||||
AHashMap<int, String> map{ { 0, "A" }, { 0, "B" }, { 0, "C" }, { 0, "D" }, { 0, "E" } };
|
||||
|
||||
CHECK(map.size() == 1);
|
||||
CHECK(map[0] == "E");
|
||||
}
|
||||
|
||||
TEST_CASE("[AHashMap] Insert element") {
|
||||
AHashMap<int, int> map;
|
||||
AHashMap<int, int>::Iterator e = map.insert(42, 84);
|
||||
|
||||
CHECK(e);
|
||||
CHECK(e->key == 42);
|
||||
CHECK(e->value == 84);
|
||||
CHECK(map[42] == 84);
|
||||
CHECK(map.has(42));
|
||||
CHECK(map.find(42));
|
||||
}
|
||||
|
||||
TEST_CASE("[AHashMap] Overwrite element") {
|
||||
AHashMap<int, int> map;
|
||||
map.insert(42, 84);
|
||||
map.insert(42, 1234);
|
||||
|
||||
CHECK(map[42] == 1234);
|
||||
}
|
||||
|
||||
TEST_CASE("[AHashMap] Erase via element") {
|
||||
AHashMap<int, int> map;
|
||||
AHashMap<int, int>::Iterator e = map.insert(42, 84);
|
||||
map.remove(e);
|
||||
CHECK(!map.has(42));
|
||||
CHECK(!map.find(42));
|
||||
}
|
||||
|
||||
TEST_CASE("[AHashMap] Erase via key") {
|
||||
AHashMap<int, int> map;
|
||||
map.insert(42, 84);
|
||||
map.erase(42);
|
||||
CHECK(!map.has(42));
|
||||
CHECK(!map.find(42));
|
||||
}
|
||||
|
||||
TEST_CASE("[AHashMap] Size") {
|
||||
AHashMap<int, int> map;
|
||||
map.insert(42, 84);
|
||||
map.insert(123, 84);
|
||||
map.insert(123, 84);
|
||||
map.insert(0, 84);
|
||||
map.insert(123485, 84);
|
||||
|
||||
CHECK(map.size() == 4);
|
||||
}
|
||||
|
||||
TEST_CASE("[AHashMap] Iteration") {
|
||||
AHashMap<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 (const KeyValue<int, int> &E : map) {
|
||||
CHECK(expected[idx] == Pair<int, int>(E.key, E.value));
|
||||
idx++;
|
||||
}
|
||||
|
||||
idx--;
|
||||
for (AHashMap<int, int>::Iterator it = map.last(); it; --it) {
|
||||
CHECK(expected[idx] == Pair<int, int>(it->key, it->value));
|
||||
idx--;
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("[AHashMap] Const iteration") {
|
||||
AHashMap<int, int> map;
|
||||
map.insert(42, 84);
|
||||
map.insert(123, 12385);
|
||||
map.insert(0, 12934);
|
||||
map.insert(123485, 1238888);
|
||||
map.insert(123, 111111);
|
||||
|
||||
const AHashMap<int, int> const_map = 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));
|
||||
expected.push_back(Pair<int, int>(123, 111111));
|
||||
|
||||
int idx = 0;
|
||||
for (const KeyValue<int, int> &E : const_map) {
|
||||
CHECK(expected[idx] == Pair<int, int>(E.key, E.value));
|
||||
idx++;
|
||||
}
|
||||
|
||||
idx--;
|
||||
for (AHashMap<int, int>::ConstIterator it = const_map.last(); it; --it) {
|
||||
CHECK(expected[idx] == Pair<int, int>(it->key, it->value));
|
||||
idx--;
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("[AHashMap] Replace key") {
|
||||
AHashMap<int, int> map;
|
||||
map.insert(42, 84);
|
||||
map.insert(0, 12934);
|
||||
CHECK(map.replace_key(0, 1));
|
||||
CHECK(map.has(1));
|
||||
CHECK(map[1] == 12934);
|
||||
}
|
||||
|
||||
TEST_CASE("[AHashMap] Clear") {
|
||||
AHashMap<int, int> map;
|
||||
map.insert(42, 84);
|
||||
map.insert(123, 12385);
|
||||
map.insert(0, 12934);
|
||||
|
||||
map.clear();
|
||||
CHECK(!map.has(42));
|
||||
CHECK(map.size() == 0);
|
||||
CHECK(map.is_empty());
|
||||
}
|
||||
|
||||
TEST_CASE("[AHashMap] Get") {
|
||||
AHashMap<int, int> map;
|
||||
map.insert(42, 84);
|
||||
map.insert(123, 12385);
|
||||
map.insert(0, 12934);
|
||||
|
||||
CHECK(map.get(123) == 12385);
|
||||
map.get(123) = 10;
|
||||
CHECK(map.get(123) == 10);
|
||||
|
||||
CHECK(*map.getptr(0) == 12934);
|
||||
*map.getptr(0) = 1;
|
||||
CHECK(*map.getptr(0) == 1);
|
||||
|
||||
CHECK(map.get(42) == 84);
|
||||
CHECK(map.getptr(-10) == nullptr);
|
||||
}
|
||||
|
||||
TEST_CASE("[AHashMap] Insert, iterate and remove many elements") {
|
||||
const int elem_max = 1234;
|
||||
AHashMap<int, int> map;
|
||||
for (int i = 0; i < elem_max; i++) {
|
||||
map.insert(i, i);
|
||||
}
|
||||
|
||||
//insert order should have been kept
|
||||
int idx = 0;
|
||||
for (auto &K : map) {
|
||||
CHECK(idx == K.key);
|
||||
CHECK(idx == K.value);
|
||||
CHECK(map.has(idx));
|
||||
idx++;
|
||||
}
|
||||
|
||||
Vector<int> elems_still_valid;
|
||||
|
||||
for (int i = 0; i < elem_max; i++) {
|
||||
if ((i % 5) == 0) {
|
||||
map.erase(i);
|
||||
} else {
|
||||
elems_still_valid.push_back(i);
|
||||
}
|
||||
}
|
||||
|
||||
CHECK(elems_still_valid.size() == map.size());
|
||||
|
||||
for (int i = 0; i < elems_still_valid.size(); i++) {
|
||||
CHECK(map.has(elems_still_valid[i]));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("[AHashMap] Insert, iterate and remove many strings") {
|
||||
const int elem_max = 432;
|
||||
AHashMap<String, String> map;
|
||||
for (int i = 0; i < elem_max; i++) {
|
||||
map.insert(itos(i), itos(i));
|
||||
}
|
||||
|
||||
//insert order should have been kept
|
||||
int idx = 0;
|
||||
for (auto &K : map) {
|
||||
CHECK(itos(idx) == K.key);
|
||||
CHECK(itos(idx) == K.value);
|
||||
CHECK(map.has(itos(idx)));
|
||||
idx++;
|
||||
}
|
||||
|
||||
Vector<String> elems_still_valid;
|
||||
|
||||
for (int i = 0; i < elem_max; i++) {
|
||||
if ((i % 5) == 0) {
|
||||
map.erase(itos(i));
|
||||
} else {
|
||||
elems_still_valid.push_back(itos(i));
|
||||
}
|
||||
}
|
||||
|
||||
CHECK(elems_still_valid.size() == map.size());
|
||||
|
||||
for (int i = 0; i < elems_still_valid.size(); i++) {
|
||||
CHECK(map.has(elems_still_valid[i]));
|
||||
}
|
||||
|
||||
elems_still_valid.clear();
|
||||
}
|
||||
|
||||
TEST_CASE("[AHashMap] Copy constructor") {
|
||||
AHashMap<int, int> map0;
|
||||
const uint32_t count = 5;
|
||||
for (uint32_t i = 0; i < count; i++) {
|
||||
map0.insert(i, i);
|
||||
}
|
||||
AHashMap<int, int> map1(map0);
|
||||
CHECK(map0.size() == map1.size());
|
||||
CHECK(map0.get_capacity() == map1.get_capacity());
|
||||
CHECK(*map0.getptr(0) == *map1.getptr(0));
|
||||
}
|
||||
|
||||
TEST_CASE("[AHashMap] Operator =") {
|
||||
AHashMap<int, int> map0;
|
||||
AHashMap<int, int> map1;
|
||||
const uint32_t count = 5;
|
||||
map1.insert(1234, 1234);
|
||||
for (uint32_t i = 0; i < count; i++) {
|
||||
map0.insert(i, i);
|
||||
}
|
||||
map1 = map0;
|
||||
CHECK(map0.size() == map1.size());
|
||||
CHECK(map0.get_capacity() == map1.get_capacity());
|
||||
CHECK(*map0.getptr(0) == *map1.getptr(0));
|
||||
}
|
||||
|
||||
TEST_CASE("[AHashMap] Array methods") {
|
||||
AHashMap<int, int> map;
|
||||
for (int i = 0; i < 100; i++) {
|
||||
map.insert(100 - i, i);
|
||||
}
|
||||
for (int i = 0; i < 100; i++) {
|
||||
CHECK(map.get_by_index(i).value == i);
|
||||
}
|
||||
int index = map.get_index(1);
|
||||
CHECK(map.get_by_index(index).value == 99);
|
||||
CHECK(map.erase_by_index(index));
|
||||
CHECK(!map.erase_by_index(index));
|
||||
CHECK(map.get_index(1) == -1);
|
||||
}
|
||||
|
||||
} // namespace TestAHashMap
|
||||
|
||||
#endif // TEST_A_HASH_MAP_H
|
||||
|
|
@ -201,10 +201,10 @@ public:
|
|||
command_queue.push_and_sync(this, &SharedThreadState::func2, tr, f);
|
||||
break;
|
||||
case TEST_MSGRET_FUNC1_TRANSFORM:
|
||||
command_queue.push_and_ret(this, &SharedThreadState::func1r, tr, &otr);
|
||||
command_queue.push_and_ret(this, &SharedThreadState::func1r, &otr, tr);
|
||||
break;
|
||||
case TEST_MSGRET_FUNC2_TRANSFORM_FLOAT:
|
||||
command_queue.push_and_ret(this, &SharedThreadState::func2r, tr, f, &otr);
|
||||
command_queue.push_and_ret(this, &SharedThreadState::func2r, &otr, tr, f);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
|
|
@ -244,6 +244,44 @@ public:
|
|||
}
|
||||
writer_thread.wait_to_finish();
|
||||
}
|
||||
|
||||
struct CopyMoveTestType {
|
||||
inline static int copy_count;
|
||||
inline static int move_count;
|
||||
int value = 0;
|
||||
|
||||
CopyMoveTestType(int p_value = 0) :
|
||||
value(p_value) {}
|
||||
|
||||
CopyMoveTestType(const CopyMoveTestType &p_other) :
|
||||
value(p_other.value) {
|
||||
copy_count++;
|
||||
}
|
||||
|
||||
CopyMoveTestType(CopyMoveTestType &&p_other) :
|
||||
value(p_other.value) {
|
||||
move_count++;
|
||||
}
|
||||
|
||||
CopyMoveTestType &operator=(const CopyMoveTestType &p_other) {
|
||||
value = p_other.value;
|
||||
copy_count++;
|
||||
return *this;
|
||||
}
|
||||
|
||||
CopyMoveTestType &operator=(CopyMoveTestType &&p_other) {
|
||||
value = p_other.value;
|
||||
move_count++;
|
||||
return *this;
|
||||
}
|
||||
};
|
||||
|
||||
void copy_move_test_copy(CopyMoveTestType p_test_type) {
|
||||
}
|
||||
void copy_move_test_ref(const CopyMoveTestType &p_test_type) {
|
||||
}
|
||||
void copy_move_test_move(CopyMoveTestType &&p_test_type) {
|
||||
}
|
||||
};
|
||||
|
||||
static void test_command_queue_basic(bool p_use_thread_pool_sync) {
|
||||
|
|
@ -446,6 +484,83 @@ TEST_CASE("[Stress][CommandQueue] Stress test command queue") {
|
|||
ProjectSettings::get_singleton()->set_setting(COMMAND_QUEUE_SETTING,
|
||||
ProjectSettings::get_singleton()->property_get_revert(COMMAND_QUEUE_SETTING));
|
||||
}
|
||||
|
||||
TEST_CASE("[CommandQueue] Test Parameter Passing Semantics") {
|
||||
SharedThreadState sts;
|
||||
sts.init_threads();
|
||||
|
||||
SUBCASE("Testing with lvalue") {
|
||||
SharedThreadState::CopyMoveTestType::copy_count = 0;
|
||||
SharedThreadState::CopyMoveTestType::move_count = 0;
|
||||
|
||||
SharedThreadState::CopyMoveTestType lvalue(42);
|
||||
|
||||
SUBCASE("Pass by copy") {
|
||||
sts.command_queue.push(&sts, &SharedThreadState::copy_move_test_copy, lvalue);
|
||||
|
||||
sts.message_count_to_read = -1;
|
||||
sts.reader_threadwork.main_start_work();
|
||||
sts.reader_threadwork.main_wait_for_done();
|
||||
|
||||
CHECK(SharedThreadState::CopyMoveTestType::copy_count == 1);
|
||||
CHECK(SharedThreadState::CopyMoveTestType::move_count == 1);
|
||||
}
|
||||
|
||||
SUBCASE("Pass by reference") {
|
||||
sts.command_queue.push(&sts, &SharedThreadState::copy_move_test_ref, lvalue);
|
||||
|
||||
sts.message_count_to_read = -1;
|
||||
sts.reader_threadwork.main_start_work();
|
||||
sts.reader_threadwork.main_wait_for_done();
|
||||
|
||||
CHECK(SharedThreadState::CopyMoveTestType::copy_count == 1);
|
||||
CHECK(SharedThreadState::CopyMoveTestType::move_count == 0);
|
||||
}
|
||||
}
|
||||
|
||||
SUBCASE("Testing with rvalue") {
|
||||
SharedThreadState::CopyMoveTestType::copy_count = 0;
|
||||
SharedThreadState::CopyMoveTestType::move_count = 0;
|
||||
|
||||
SUBCASE("Pass by copy") {
|
||||
sts.command_queue.push(&sts, &SharedThreadState::copy_move_test_copy,
|
||||
SharedThreadState::CopyMoveTestType(43));
|
||||
|
||||
sts.message_count_to_read = -1;
|
||||
sts.reader_threadwork.main_start_work();
|
||||
sts.reader_threadwork.main_wait_for_done();
|
||||
|
||||
CHECK(SharedThreadState::CopyMoveTestType::copy_count == 0);
|
||||
CHECK(SharedThreadState::CopyMoveTestType::move_count == 2);
|
||||
}
|
||||
|
||||
SUBCASE("Pass by reference") {
|
||||
sts.command_queue.push(&sts, &SharedThreadState::copy_move_test_ref,
|
||||
SharedThreadState::CopyMoveTestType(43));
|
||||
|
||||
sts.message_count_to_read = -1;
|
||||
sts.reader_threadwork.main_start_work();
|
||||
sts.reader_threadwork.main_wait_for_done();
|
||||
|
||||
CHECK(SharedThreadState::CopyMoveTestType::copy_count == 0);
|
||||
CHECK(SharedThreadState::CopyMoveTestType::move_count == 1);
|
||||
}
|
||||
|
||||
SUBCASE("Pass by rvalue reference") {
|
||||
sts.command_queue.push(&sts, &SharedThreadState::copy_move_test_move,
|
||||
SharedThreadState::CopyMoveTestType(43));
|
||||
|
||||
sts.message_count_to_read = -1;
|
||||
sts.reader_threadwork.main_start_work();
|
||||
sts.reader_threadwork.main_wait_for_done();
|
||||
|
||||
CHECK(SharedThreadState::CopyMoveTestType::copy_count == 0);
|
||||
CHECK(SharedThreadState::CopyMoveTestType::move_count == 1);
|
||||
}
|
||||
}
|
||||
|
||||
sts.destroy_threads();
|
||||
}
|
||||
} // namespace TestCommandQueue
|
||||
|
||||
#endif // TEST_COMMAND_QUEUE_H
|
||||
|
|
|
|||
|
|
@ -37,6 +37,24 @@
|
|||
|
||||
namespace TestHashMap {
|
||||
|
||||
TEST_CASE("[HashMap] List initialization") {
|
||||
HashMap<int, String> map{ { 0, "A" }, { 1, "B" }, { 2, "C" }, { 3, "D" }, { 4, "E" } };
|
||||
|
||||
CHECK(map.size() == 5);
|
||||
CHECK(map[0] == "A");
|
||||
CHECK(map[1] == "B");
|
||||
CHECK(map[2] == "C");
|
||||
CHECK(map[3] == "D");
|
||||
CHECK(map[4] == "E");
|
||||
}
|
||||
|
||||
TEST_CASE("[HashMap] List initialization with existing elements") {
|
||||
HashMap<int, String> map{ { 0, "A" }, { 0, "B" }, { 0, "C" }, { 0, "D" }, { 0, "E" } };
|
||||
|
||||
CHECK(map.size() == 1);
|
||||
CHECK(map[0] == "E");
|
||||
}
|
||||
|
||||
TEST_CASE("[HashMap] Insert element") {
|
||||
HashMap<int, int> map;
|
||||
HashMap<int, int>::Iterator e = map.insert(42, 84);
|
||||
|
|
|
|||
|
|
@ -37,6 +37,24 @@
|
|||
|
||||
namespace TestHashSet {
|
||||
|
||||
TEST_CASE("[HashSet] List initialization") {
|
||||
HashSet<int> set{ 0, 1, 2, 3, 4 };
|
||||
|
||||
CHECK(set.size() == 5);
|
||||
CHECK(set.has(0));
|
||||
CHECK(set.has(1));
|
||||
CHECK(set.has(2));
|
||||
CHECK(set.has(3));
|
||||
CHECK(set.has(4));
|
||||
}
|
||||
|
||||
TEST_CASE("[HashSet] List initialization with existing elements") {
|
||||
HashSet<int> set{ 0, 0, 0, 0, 0 };
|
||||
|
||||
CHECK(set.size() == 1);
|
||||
CHECK(set.has(0));
|
||||
}
|
||||
|
||||
TEST_CASE("[HashSet] Insert element") {
|
||||
HashSet<int> set;
|
||||
HashSet<int>::Iterator e = set.insert(42);
|
||||
|
|
|
|||
|
|
@ -45,6 +45,17 @@ static void populate_integers(List<int> &p_list, List<int>::Element *r_elements[
|
|||
}
|
||||
}
|
||||
|
||||
TEST_CASE("[List] List initialization") {
|
||||
List<int> list{ 0, 1, 2, 3, 4 };
|
||||
|
||||
CHECK(list.size() == 5);
|
||||
CHECK(list.get(0) == 0);
|
||||
CHECK(list.get(1) == 1);
|
||||
CHECK(list.get(2) == 2);
|
||||
CHECK(list.get(3) == 3);
|
||||
CHECK(list.get(4) == 4);
|
||||
}
|
||||
|
||||
TEST_CASE("[List] Push/pop back") {
|
||||
List<String> list;
|
||||
|
||||
|
|
|
|||
|
|
@ -38,6 +38,32 @@
|
|||
|
||||
namespace TestOAHashMap {
|
||||
|
||||
TEST_CASE("[OAHashMap] List initialization") {
|
||||
OAHashMap<int, String> map{ { 0, "A" }, { 1, "B" }, { 2, "C" }, { 3, "D" }, { 4, "E" } };
|
||||
|
||||
CHECK(map.get_num_elements() == 5);
|
||||
String value;
|
||||
CHECK(map.lookup(0, value));
|
||||
CHECK(value == "A");
|
||||
CHECK(map.lookup(1, value));
|
||||
CHECK(value == "B");
|
||||
CHECK(map.lookup(2, value));
|
||||
CHECK(value == "C");
|
||||
CHECK(map.lookup(3, value));
|
||||
CHECK(value == "D");
|
||||
CHECK(map.lookup(4, value));
|
||||
CHECK(value == "E");
|
||||
}
|
||||
|
||||
TEST_CASE("[OAHashMap] List initialization with existing elements") {
|
||||
OAHashMap<int, String> map{ { 0, "A" }, { 0, "B" }, { 0, "C" }, { 0, "D" }, { 0, "E" } };
|
||||
|
||||
CHECK(map.get_num_elements() == 1);
|
||||
String value;
|
||||
CHECK(map.lookup(0, value));
|
||||
CHECK(value == "E");
|
||||
}
|
||||
|
||||
TEST_CASE("[OAHashMap] Insert element") {
|
||||
OAHashMap<int, int> map;
|
||||
map.insert(42, 84);
|
||||
|
|
|
|||
|
|
@ -31,10 +31,27 @@
|
|||
#ifndef TEST_RID_H
|
||||
#define TEST_RID_H
|
||||
|
||||
#include "core/os/thread.h"
|
||||
#include "core/templates/local_vector.h"
|
||||
#include "core/templates/rid.h"
|
||||
#include "core/templates/rid_owner.h"
|
||||
|
||||
#include "tests/test_macros.h"
|
||||
|
||||
#ifdef SANITIZERS_ENABLED
|
||||
#ifdef __has_feature
|
||||
#if __has_feature(thread_sanitizer)
|
||||
#define TSAN_ENABLED
|
||||
#endif
|
||||
#elif defined(__SANITIZE_THREAD__)
|
||||
#define TSAN_ENABLED
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef TSAN_ENABLED
|
||||
#include <sanitizer/tsan_interface.h>
|
||||
#endif
|
||||
|
||||
namespace TestRID {
|
||||
TEST_CASE("[RID] Default Constructor") {
|
||||
RID rid;
|
||||
|
|
@ -96,6 +113,142 @@ TEST_CASE("[RID] 'get_local_index'") {
|
|||
CHECK(RID::from_uint64(4'294'967'295).get_local_index() == 4'294'967'295);
|
||||
CHECK(RID::from_uint64(4'294'967'297).get_local_index() == 1);
|
||||
}
|
||||
|
||||
// This case would let sanitizers realize data races.
|
||||
// Additionally, on purely weakly ordered architectures, it would detect synchronization issues
|
||||
// if RID_Alloc failed to impose proper memory ordering and the test's threads are distributed
|
||||
// among multiple L1 caches.
|
||||
TEST_CASE("[RID_Owner] Thread safety") {
|
||||
struct DataHolder {
|
||||
char data[Thread::CACHE_LINE_BYTES];
|
||||
};
|
||||
|
||||
struct RID_OwnerTester {
|
||||
uint32_t thread_count = 0;
|
||||
RID_Owner<DataHolder, true> rid_owner;
|
||||
TightLocalVector<Thread> threads;
|
||||
SafeNumeric<uint32_t> next_thread_idx;
|
||||
// Using std::atomic directly since SafeNumeric doesn't support relaxed ordering.
|
||||
TightLocalVector<std::atomic<uint64_t>> rids;
|
||||
std::atomic<uint32_t> sync[2] = {};
|
||||
std::atomic<uint32_t> correct = 0;
|
||||
|
||||
// A barrier that doesn't introduce memory ordering constraints, only compiler ones.
|
||||
// The idea is not to cause any sync traffic that would make the code we want to test
|
||||
// seem correct as a side effect.
|
||||
void lockstep(uint32_t p_step) {
|
||||
uint32_t buf_idx = p_step % 2;
|
||||
uint32_t target = (p_step / 2 + 1) * threads.size();
|
||||
sync[buf_idx].fetch_add(1, std::memory_order_relaxed);
|
||||
do {
|
||||
std::this_thread::yield();
|
||||
} while (sync[buf_idx].load(std::memory_order_relaxed) != target);
|
||||
}
|
||||
|
||||
explicit RID_OwnerTester(bool p_chunk_for_all, bool p_chunks_preallocated) :
|
||||
thread_count(OS::get_singleton()->get_processor_count()),
|
||||
rid_owner(sizeof(DataHolder) * (p_chunk_for_all ? thread_count : 1)) {
|
||||
threads.resize(thread_count);
|
||||
rids.resize(threads.size());
|
||||
if (p_chunks_preallocated) {
|
||||
LocalVector<RID> prealloc_rids;
|
||||
for (uint32_t i = 0; i < (p_chunk_for_all ? 1 : threads.size()); i++) {
|
||||
prealloc_rids.push_back(rid_owner.make_rid());
|
||||
}
|
||||
for (uint32_t i = 0; i < prealloc_rids.size(); i++) {
|
||||
rid_owner.free(prealloc_rids[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
~RID_OwnerTester() {
|
||||
for (uint32_t i = 0; i < threads.size(); i++) {
|
||||
rid_owner.free(RID::from_uint64(rids[i].load(std::memory_order_relaxed)));
|
||||
}
|
||||
}
|
||||
|
||||
void test() {
|
||||
for (uint32_t i = 0; i < threads.size(); i++) {
|
||||
threads[i].start(
|
||||
[](void *p_data) {
|
||||
RID_OwnerTester *rot = (RID_OwnerTester *)p_data;
|
||||
|
||||
auto _compute_thread_unique_byte = [](uint32_t p_idx) -> char {
|
||||
return ((p_idx & 0xff) ^ (0b11111110 << (p_idx % 8)));
|
||||
};
|
||||
|
||||
// 1. Each thread gets a zero-based index.
|
||||
uint32_t self_th_idx = rot->next_thread_idx.postincrement();
|
||||
|
||||
rot->lockstep(0);
|
||||
|
||||
// 2. Each thread makes a RID holding unique data.
|
||||
DataHolder initial_data;
|
||||
memset(&initial_data, _compute_thread_unique_byte(self_th_idx), Thread::CACHE_LINE_BYTES);
|
||||
RID my_rid = rot->rid_owner.make_rid(initial_data);
|
||||
rot->rids[self_th_idx].store(my_rid.get_id(), std::memory_order_relaxed);
|
||||
|
||||
rot->lockstep(1);
|
||||
|
||||
// 3. Each thread verifies all the others.
|
||||
uint32_t local_correct = 0;
|
||||
for (uint32_t th_idx = 0; th_idx < rot->threads.size(); th_idx++) {
|
||||
if (th_idx == self_th_idx) {
|
||||
continue;
|
||||
}
|
||||
char expected_unique_byte = _compute_thread_unique_byte(th_idx);
|
||||
RID rid = RID::from_uint64(rot->rids[th_idx].load(std::memory_order_relaxed));
|
||||
DataHolder *data = rot->rid_owner.get_or_null(rid);
|
||||
#ifdef TSAN_ENABLED
|
||||
__tsan_acquire(data); // We know not a race in practice.
|
||||
#endif
|
||||
bool ok = true;
|
||||
for (uint32_t j = 0; j < Thread::CACHE_LINE_BYTES; j++) {
|
||||
if (data->data[j] != expected_unique_byte) {
|
||||
ok = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (ok) {
|
||||
local_correct++;
|
||||
}
|
||||
#ifdef TSAN_ENABLED
|
||||
__tsan_release(data);
|
||||
#endif
|
||||
}
|
||||
|
||||
rot->lockstep(2);
|
||||
|
||||
rot->correct.fetch_add(local_correct, std::memory_order_acq_rel);
|
||||
},
|
||||
this);
|
||||
}
|
||||
|
||||
for (uint32_t i = 0; i < threads.size(); i++) {
|
||||
threads[i].wait_to_finish();
|
||||
}
|
||||
|
||||
CHECK_EQ(correct.load(), threads.size() * (threads.size() - 1));
|
||||
}
|
||||
};
|
||||
|
||||
SUBCASE("All items in one chunk, pre-allocated") {
|
||||
RID_OwnerTester tester(true, true);
|
||||
tester.test();
|
||||
}
|
||||
SUBCASE("All items in one chunk, NOT pre-allocated") {
|
||||
RID_OwnerTester tester(true, false);
|
||||
tester.test();
|
||||
}
|
||||
SUBCASE("One item per chunk, pre-allocated") {
|
||||
RID_OwnerTester tester(false, true);
|
||||
tester.test();
|
||||
}
|
||||
SUBCASE("One item per chunk, NOT pre-allocated") {
|
||||
RID_OwnerTester tester(false, false);
|
||||
tester.test();
|
||||
}
|
||||
}
|
||||
} // namespace TestRID
|
||||
|
||||
#endif // TEST_RID_H
|
||||
|
|
|
|||
|
|
@ -215,6 +215,154 @@ TEST_CASE("[Vector] Get, set") {
|
|||
CHECK(vector.get(4) == 4);
|
||||
}
|
||||
|
||||
TEST_CASE("[Vector] To byte array (variant call)") {
|
||||
// PackedInt32Array.
|
||||
{
|
||||
PackedInt32Array vector[] = { { 0, -1, 2008 }, {} };
|
||||
PackedByteArray out[] = { { /* 0 */ 0x00, 0x00, 0x00, 0x00, /* -1 */ 0xFF, 0xFF, 0xFF, 0xFF, /* 2008 */ 0xD8, 0x07, 0x00, 0x00 }, {} };
|
||||
|
||||
for (size_t i = 0; i < std::size(vector); i++) {
|
||||
Callable::CallError err;
|
||||
Variant v_ret;
|
||||
Variant v_vector = vector[i];
|
||||
v_vector.callp("to_byte_array", nullptr, 0, v_ret, err);
|
||||
CHECK(v_ret.get_type() == Variant::PACKED_BYTE_ARRAY);
|
||||
CHECK(v_ret.operator PackedByteArray() == out[i]);
|
||||
}
|
||||
}
|
||||
|
||||
// PackedInt64Array.
|
||||
{
|
||||
PackedInt64Array vector[] = { { 0, -1, 2008 }, {} };
|
||||
PackedByteArray out[] = { { /* 0 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* -1 */ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, /* 2008 */ 0xD8, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, {} };
|
||||
|
||||
for (size_t i = 0; i < std::size(vector); i++) {
|
||||
Callable::CallError err;
|
||||
Variant v_ret;
|
||||
Variant v_vector = vector[i];
|
||||
v_vector.callp("to_byte_array", nullptr, 0, v_ret, err);
|
||||
CHECK(v_ret.get_type() == Variant::PACKED_BYTE_ARRAY);
|
||||
CHECK(v_ret.operator PackedByteArray() == out[i]);
|
||||
}
|
||||
}
|
||||
|
||||
// PackedFloat32Array.
|
||||
{
|
||||
PackedFloat32Array vector[] = { { 0.0, -1.0, 200e24 }, {} };
|
||||
PackedByteArray out[] = { { /* 0.0 */ 0x00, 0x00, 0x00, 0x00, /* -1.0 */ 0x00, 0x00, 0x80, 0xBF, /* 200e24 */ 0xA6, 0x6F, 0x25, 0x6B }, {} };
|
||||
|
||||
for (size_t i = 0; i < std::size(vector); i++) {
|
||||
Callable::CallError err;
|
||||
Variant v_ret;
|
||||
Variant v_vector = vector[i];
|
||||
v_vector.callp("to_byte_array", nullptr, 0, v_ret, err);
|
||||
CHECK(v_ret.get_type() == Variant::PACKED_BYTE_ARRAY);
|
||||
CHECK(v_ret.operator PackedByteArray() == out[i]);
|
||||
}
|
||||
}
|
||||
// PackedFloat64Array.
|
||||
{
|
||||
PackedFloat64Array vector[] = { { 0.0, -1.0, 200e24 }, {} };
|
||||
PackedByteArray out[] = { { /* 0.0 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* -1.0 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0xBF, /* 200e24 */ 0x35, 0x03, 0x32, 0xB7, 0xF4, 0xAD, 0x64, 0x45 }, {} };
|
||||
|
||||
for (size_t i = 0; i < std::size(vector); i++) {
|
||||
Callable::CallError err;
|
||||
Variant v_ret;
|
||||
Variant v_vector = vector[i];
|
||||
v_vector.callp("to_byte_array", nullptr, 0, v_ret, err);
|
||||
CHECK(v_ret.get_type() == Variant::PACKED_BYTE_ARRAY);
|
||||
CHECK(v_ret.operator PackedByteArray() == out[i]);
|
||||
}
|
||||
}
|
||||
|
||||
// PackedStringArray.
|
||||
{
|
||||
PackedStringArray vector[] = { { "test", "string" }, {}, { "", "test" } };
|
||||
PackedByteArray out[] = { { /* test */ 0x74, 0x65, 0x73, 0x74, /* null */ 0x00, /* string */ 0x73, 0x74, 0x72, 0x69, 0x6E, 0x67, /* null */ 0x00 }, {}, { /* null */ 0x00, /* test */ 0x74, 0x65, 0x73, 0x74, /* null */ 0x00 } };
|
||||
|
||||
for (size_t i = 0; i < std::size(vector); i++) {
|
||||
Callable::CallError err;
|
||||
Variant v_ret;
|
||||
Variant v_vector = vector[i];
|
||||
v_vector.callp("to_byte_array", nullptr, 0, v_ret, err);
|
||||
CHECK(v_ret.get_type() == Variant::PACKED_BYTE_ARRAY);
|
||||
CHECK(v_ret.operator PackedByteArray() == out[i]);
|
||||
}
|
||||
}
|
||||
|
||||
// PackedVector2Array.
|
||||
{
|
||||
PackedVector2Array vector[] = { { Vector2(), Vector2(1, -1) }, {} };
|
||||
#ifdef REAL_T_IS_DOUBLE
|
||||
PackedByteArray out[] = { { /* X=0.0 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* Y=0.0 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* X=1.0 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x3F, /* Y=-1.0 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0xBF }, {} };
|
||||
#else
|
||||
PackedByteArray out[] = { { /* X=0.0 */ 0x00, 0x00, 0x00, 0x00, /* Y=0.0 */ 0x00, 0x00, 0x00, 0x00, /* X=1.0 */ 0x00, 0x00, 0x80, 0x3F, /* Y=-1.0 */ 0x00, 0x00, 0x80, 0xBF }, {} };
|
||||
#endif
|
||||
|
||||
for (size_t i = 0; i < std::size(vector); i++) {
|
||||
Callable::CallError err;
|
||||
Variant v_ret;
|
||||
Variant v_vector = vector[i];
|
||||
v_vector.callp("to_byte_array", nullptr, 0, v_ret, err);
|
||||
CHECK(v_ret.get_type() == Variant::PACKED_BYTE_ARRAY);
|
||||
CHECK(v_ret.operator PackedByteArray() == out[i]);
|
||||
}
|
||||
}
|
||||
|
||||
// PackedVector3Array.
|
||||
{
|
||||
PackedVector3Array vector[] = { { Vector3(), Vector3(1, 1, -1) }, {} };
|
||||
#ifdef REAL_T_IS_DOUBLE
|
||||
PackedByteArray out[] = { { /* X=0.0 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* Y=0.0 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* Z=0.0 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* X=1.0 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x3F, /* Y=1.0 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x3F, /* Z=-1.0 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0xBF }, {} };
|
||||
#else
|
||||
PackedByteArray out[] = { { /* X=0.0 */ 0x00, 0x00, 0x00, 0x00, /* Y=0.0 */ 0x00, 0x00, 0x00, 0x00, /* Z=0.0 */ 0x00, 0x00, 0x00, 0x00, /* X=1.0 */ 0x00, 0x00, 0x80, 0x3F, /* Y=1.0 */ 0x00, 0x00, 0x80, 0x3F, /* Z=-1.0 */ 0x00, 0x00, 0x80, 0xBF }, {} };
|
||||
#endif
|
||||
|
||||
for (size_t i = 0; i < std::size(vector); i++) {
|
||||
Callable::CallError err;
|
||||
Variant v_ret;
|
||||
Variant v_vector = vector[i];
|
||||
v_vector.callp("to_byte_array", nullptr, 0, v_ret, err);
|
||||
CHECK(v_ret.get_type() == Variant::PACKED_BYTE_ARRAY);
|
||||
CHECK(v_ret.operator PackedByteArray() == out[i]);
|
||||
}
|
||||
}
|
||||
|
||||
// PackedColorArray.
|
||||
{
|
||||
PackedColorArray vector[] = { { Color(), Color(1, 1, 1) }, {} };
|
||||
PackedByteArray out[] = { { /* R=0.0 */ 0x00, 0x00, 0x00, 0x00, /* G=0.0 */ 0x00, 0x00, 0x00, 0x00, /* B=0.0 */ 0x00, 0x00, 0x00, 0x00, /* A=1.0 */ 0x00, 0x00, 0x80, 0x3F, /* R=1.0 */ 0x00, 0x00, 0x80, 0x3F, /* G=1.0 */ 0x00, 0x00, 0x80, 0x3F, /* B=1.0 */ 0x00, 0x00, 0x80, 0x3F, /* A=1.0 */ 0x00, 0x00, 0x80, 0x3F }, {} };
|
||||
|
||||
for (size_t i = 0; i < std::size(vector); i++) {
|
||||
Callable::CallError err;
|
||||
Variant v_ret;
|
||||
Variant v_vector = vector[i];
|
||||
v_vector.callp("to_byte_array", nullptr, 0, v_ret, err);
|
||||
CHECK(v_ret.get_type() == Variant::PACKED_BYTE_ARRAY);
|
||||
CHECK(v_ret.operator PackedByteArray() == out[i]);
|
||||
}
|
||||
}
|
||||
|
||||
// PackedVector4Array.
|
||||
{
|
||||
PackedVector4Array vector[] = { { Vector4(), Vector4(1, -1, 1, -1) }, {} };
|
||||
#ifdef REAL_T_IS_DOUBLE
|
||||
PackedByteArray out[] = { { /* X=0.0 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* Y=0.0 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* Z 0.0 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* W=0.0 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* X=1.0 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x3F, /* Y=-1.0 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0xBF, /* Z=1.0 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x3F, /* W=-1.0 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0xBF }, {} };
|
||||
#else
|
||||
PackedByteArray out[] = { { /* X=0.0 */ 0x00, 0x00, 0x00, 0x00, /* Y=0.0 */ 0x00, 0x00, 0x00, 0x00, /* Z=0.0 */ 0x00, 0x00, 0x00, 0x00, /* W 0.0 */ 0x00, 0x00, 0x00, 0x00, /* X 1.0 */ 0x00, 0x00, 0x80, 0x3F, /* Y=-1.0 */ 0x00, 0x00, 0x80, 0xBF, /* Z=1.0 */ 0x00, 0x00, 0x80, 0x3F, /* W=-1.0 */ 0x00, 0x00, 0x80, 0xBF }, {} };
|
||||
#endif
|
||||
|
||||
for (size_t i = 0; i < std::size(vector); i++) {
|
||||
Callable::CallError err;
|
||||
Variant v_ret;
|
||||
Variant v_vector = vector[i];
|
||||
v_vector.callp("to_byte_array", nullptr, 0, v_ret, err);
|
||||
CHECK(v_ret.get_type() == Variant::PACKED_BYTE_ARRAY);
|
||||
CHECK(v_ret.operator PackedByteArray() == out[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("[Vector] To byte array") {
|
||||
Vector<int> vector;
|
||||
vector.push_back(0);
|
||||
|
|
@ -532,6 +680,31 @@ TEST_CASE("[Vector] Operators") {
|
|||
CHECK(vector != vector_other);
|
||||
}
|
||||
|
||||
struct CyclicVectorHolder {
|
||||
Vector<CyclicVectorHolder> *vector = nullptr;
|
||||
bool is_destructing = false;
|
||||
|
||||
~CyclicVectorHolder() {
|
||||
if (is_destructing) {
|
||||
// The vector must exist and not expose its backing array at this point.
|
||||
CHECK_NE(vector, nullptr);
|
||||
CHECK_EQ(vector->ptr(), nullptr);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
TEST_CASE("[Vector] Cyclic Reference") {
|
||||
// Create a stack-space vector.
|
||||
Vector<CyclicVectorHolder> vector;
|
||||
// Add a new (empty) element.
|
||||
vector.resize(1);
|
||||
// Expose the vector to its element through CyclicVectorHolder.
|
||||
// This is questionable behavior, but should still behave graciously.
|
||||
vector.ptrw()[0] = CyclicVectorHolder{ &vector };
|
||||
vector.ptrw()[0].is_destructing = true;
|
||||
// The vector goes out of scope and destructs, calling CyclicVectorHolder's destructor.
|
||||
}
|
||||
|
||||
} // namespace TestVector
|
||||
|
||||
#endif // TEST_VECTOR_H
|
||||
|
|
|
|||
|
|
@ -634,6 +634,24 @@ TEST_CASE("[Array] Typed copying") {
|
|||
a6.clear();
|
||||
}
|
||||
|
||||
static bool _find_custom_callable(const Variant &p_val) {
|
||||
return (int)p_val % 2 == 0;
|
||||
}
|
||||
|
||||
TEST_CASE("[Array] Test find_custom") {
|
||||
Array a1 = build_array(1, 3, 4, 5, 8, 9);
|
||||
// Find first even number.
|
||||
int index = a1.find_custom(callable_mp_static(_find_custom_callable));
|
||||
CHECK_EQ(index, 2);
|
||||
}
|
||||
|
||||
TEST_CASE("[Array] Test rfind_custom") {
|
||||
Array a1 = build_array(1, 3, 4, 5, 8, 9);
|
||||
// Find last even number.
|
||||
int index = a1.rfind_custom(callable_mp_static(_find_custom_callable));
|
||||
CHECK_EQ(index, 4);
|
||||
}
|
||||
|
||||
} // namespace TestArray
|
||||
|
||||
#endif // TEST_ARRAY_H
|
||||
|
|
|
|||
|
|
@ -135,6 +135,70 @@ TEST_CASE("[Callable] Argument count") {
|
|||
|
||||
memdelete(my_test);
|
||||
}
|
||||
|
||||
class TestBoundUnboundArgumentCount : public Object {
|
||||
GDCLASS(TestBoundUnboundArgumentCount, Object);
|
||||
|
||||
protected:
|
||||
static void _bind_methods() {
|
||||
ClassDB::bind_vararg_method(METHOD_FLAGS_DEFAULT, "test_func", &TestBoundUnboundArgumentCount::test_func, MethodInfo("test_func"));
|
||||
}
|
||||
|
||||
public:
|
||||
Variant test_func(const Variant **p_args, int p_argcount, Callable::CallError &r_error) {
|
||||
Array result;
|
||||
result.resize(p_argcount);
|
||||
for (int i = 0; i < p_argcount; i++) {
|
||||
result[i] = *p_args[i];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static String get_output(const Callable &p_callable) {
|
||||
Array effective_args;
|
||||
effective_args.push_back(7);
|
||||
effective_args.push_back(8);
|
||||
effective_args.push_back(9);
|
||||
|
||||
effective_args.resize(3 - p_callable.get_unbound_arguments_count());
|
||||
effective_args.append_array(p_callable.get_bound_arguments());
|
||||
|
||||
return vformat(
|
||||
"%d %d %s %s %s",
|
||||
p_callable.get_unbound_arguments_count(),
|
||||
p_callable.get_bound_arguments_count(),
|
||||
p_callable.get_bound_arguments(),
|
||||
p_callable.call(7, 8, 9),
|
||||
effective_args);
|
||||
}
|
||||
};
|
||||
|
||||
TEST_CASE("[Callable] Bound and unbound argument count") {
|
||||
String (*get_output)(const Callable &) = TestBoundUnboundArgumentCount::get_output;
|
||||
|
||||
TestBoundUnboundArgumentCount *test_instance = memnew(TestBoundUnboundArgumentCount);
|
||||
|
||||
Callable test_func = Callable(test_instance, "test_func");
|
||||
|
||||
CHECK(get_output(test_func) == "0 0 [] [7, 8, 9] [7, 8, 9]");
|
||||
CHECK(get_output(test_func.bind(1, 2)) == "0 2 [1, 2] [7, 8, 9, 1, 2] [7, 8, 9, 1, 2]");
|
||||
CHECK(get_output(test_func.bind(1, 2).unbind(1)) == "1 2 [1, 2] [7, 8, 1, 2] [7, 8, 1, 2]");
|
||||
CHECK(get_output(test_func.bind(1, 2).unbind(1).bind(3, 4)) == "0 3 [3, 1, 2] [7, 8, 9, 3, 1, 2] [7, 8, 9, 3, 1, 2]");
|
||||
CHECK(get_output(test_func.bind(1, 2).unbind(1).bind(3, 4).unbind(1)) == "1 3 [3, 1, 2] [7, 8, 3, 1, 2] [7, 8, 3, 1, 2]");
|
||||
|
||||
CHECK(get_output(test_func.bind(1).bind(2).bind(3).unbind(1)) == "1 3 [3, 2, 1] [7, 8, 3, 2, 1] [7, 8, 3, 2, 1]");
|
||||
CHECK(get_output(test_func.bind(1).bind(2).unbind(1).bind(3)) == "0 2 [2, 1] [7, 8, 9, 2, 1] [7, 8, 9, 2, 1]");
|
||||
CHECK(get_output(test_func.bind(1).unbind(1).bind(2).bind(3)) == "0 2 [3, 1] [7, 8, 9, 3, 1] [7, 8, 9, 3, 1]");
|
||||
CHECK(get_output(test_func.unbind(1).bind(1).bind(2).bind(3)) == "0 2 [3, 2] [7, 8, 9, 3, 2] [7, 8, 9, 3, 2]");
|
||||
|
||||
CHECK(get_output(test_func.unbind(1).unbind(1).unbind(1).bind(1, 2, 3)) == "0 0 [] [7, 8, 9] [7, 8, 9]");
|
||||
CHECK(get_output(test_func.unbind(1).unbind(1).bind(1, 2, 3).unbind(1)) == "1 1 [1] [7, 8, 1] [7, 8, 1]");
|
||||
CHECK(get_output(test_func.unbind(1).bind(1, 2, 3).unbind(1).unbind(1)) == "2 2 [1, 2] [7, 1, 2] [7, 1, 2]");
|
||||
CHECK(get_output(test_func.bind(1, 2, 3).unbind(1).unbind(1).unbind(1)) == "3 3 [1, 2, 3] [1, 2, 3] [1, 2, 3]");
|
||||
|
||||
memdelete(test_instance);
|
||||
}
|
||||
|
||||
} // namespace TestCallable
|
||||
|
||||
#endif // TEST_CALLABLE_H
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@
|
|||
#ifndef TEST_DICTIONARY_H
|
||||
#define TEST_DICTIONARY_H
|
||||
|
||||
#include "core/variant/dictionary.h"
|
||||
#include "core/variant/typed_dictionary.h"
|
||||
#include "tests/test_macros.h"
|
||||
|
||||
namespace TestDictionary {
|
||||
|
|
@ -66,8 +66,7 @@ TEST_CASE("[Dictionary] Assignment using bracket notation ([])") {
|
|||
|
||||
map[StringName("HelloName")] = 6;
|
||||
CHECK(int(map[StringName("HelloName")]) == 6);
|
||||
// Check that StringName key is converted to String.
|
||||
CHECK(int(map.find_key(6).get_type()) == Variant::STRING);
|
||||
CHECK(int(map.find_key(6).get_type()) == Variant::STRING_NAME);
|
||||
map[StringName("HelloName")] = 7;
|
||||
CHECK(int(map[StringName("HelloName")]) == 7);
|
||||
|
||||
|
|
@ -96,6 +95,27 @@ TEST_CASE("[Dictionary] Assignment using bracket notation ([])") {
|
|||
CHECK(map.size() == length);
|
||||
}
|
||||
|
||||
TEST_CASE("[Dictionary] List init") {
|
||||
Dictionary dict{
|
||||
{ 0, "int" },
|
||||
{ "packed_string_array", PackedStringArray({ "array", "of", "values" }) },
|
||||
{ "key", Dictionary({ { "nested", 200 } }) },
|
||||
{ Vector2(), "v2" },
|
||||
};
|
||||
CHECK(dict.size() == 4);
|
||||
CHECK(dict[0] == "int");
|
||||
CHECK(PackedStringArray(dict["packed_string_array"])[2] == "values");
|
||||
CHECK(Dictionary(dict["key"])["nested"] == Variant(200));
|
||||
CHECK(dict[Vector2()] == "v2");
|
||||
|
||||
TypedDictionary<double, double> tdict{
|
||||
{ 0.0, 1.0 },
|
||||
{ 5.0, 2.0 },
|
||||
};
|
||||
CHECK_EQ(tdict[0.0], Variant(1.0));
|
||||
CHECK_EQ(tdict[5.0], Variant(2.0));
|
||||
}
|
||||
|
||||
TEST_CASE("[Dictionary] get_key_lists()") {
|
||||
Dictionary map;
|
||||
List<Variant> keys;
|
||||
|
|
@ -537,6 +557,43 @@ TEST_CASE("[Dictionary] Order and find") {
|
|||
CHECK_EQ(d.find_key("does not exist"), Variant());
|
||||
}
|
||||
|
||||
TEST_CASE("[Dictionary] Typed copying") {
|
||||
TypedDictionary<int, int> d1;
|
||||
d1[0] = 1;
|
||||
|
||||
TypedDictionary<double, double> d2;
|
||||
d2[0] = 1.0;
|
||||
|
||||
Dictionary d3 = d1;
|
||||
TypedDictionary<int, int> d4 = d3;
|
||||
|
||||
Dictionary d5 = d2;
|
||||
TypedDictionary<int, int> d6 = d5;
|
||||
|
||||
d3[0] = 2;
|
||||
d4[0] = 3;
|
||||
|
||||
// Same typed TypedDictionary should be shared.
|
||||
CHECK_EQ(d1[0], Variant(3));
|
||||
CHECK_EQ(d3[0], Variant(3));
|
||||
CHECK_EQ(d4[0], Variant(3));
|
||||
|
||||
d5[0] = 2.0;
|
||||
d6[0] = 3.0;
|
||||
|
||||
// Different typed TypedDictionary should not be shared.
|
||||
CHECK_EQ(d2[0], Variant(2.0));
|
||||
CHECK_EQ(d5[0], Variant(2.0));
|
||||
CHECK_EQ(d6[0], Variant(3.0));
|
||||
|
||||
d1.clear();
|
||||
d2.clear();
|
||||
d3.clear();
|
||||
d4.clear();
|
||||
d5.clear();
|
||||
d6.clear();
|
||||
}
|
||||
|
||||
} // namespace TestDictionary
|
||||
|
||||
#endif // TEST_DICTIONARY_H
|
||||
|
|
|
|||
|
|
@ -1806,6 +1806,14 @@ TEST_CASE("[Variant] Writer and parser dictionary") {
|
|||
CHECK_MESSAGE(d_parsed == Variant(d), "Should parse back.");
|
||||
}
|
||||
|
||||
TEST_CASE("[Variant] Writer key sorting") {
|
||||
Dictionary d = build_dictionary(StringName("C"), 3, "A", 1, StringName("B"), 2, "D", 4);
|
||||
String d_str;
|
||||
VariantWriter::write_to_string(d, d_str);
|
||||
|
||||
CHECK_EQ(d_str, "{\n\"A\": 1,\n&\"B\": 2,\n&\"C\": 3,\n\"D\": 4\n}");
|
||||
}
|
||||
|
||||
TEST_CASE("[Variant] Writer recursive dictionary") {
|
||||
// There is no way to accurately represent a recursive dictionary,
|
||||
// the only thing we can do is make sure the writer doesn't blow up
|
||||
|
|
|
|||
|
|
@ -89,7 +89,7 @@ TEST_CASE("[VariantUtility] Type conversion") {
|
|||
|
||||
converted = VariantUtilityFunctions::type_convert(basis, Variant::Type::STRING);
|
||||
CHECK(converted.get_type() == Variant::Type::STRING);
|
||||
CHECK(converted == Variant("[X: (1.2, 0, 0), Y: (0, 3.4, 0), Z: (0, 0, 5.6)]"));
|
||||
CHECK(converted == Variant("[X: (1.2, 0.0, 0.0), Y: (0.0, 3.4, 0.0), Z: (0.0, 0.0, 5.6)]"));
|
||||
}
|
||||
|
||||
{
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue