feat: updated engine version to 4.4-rc1

This commit is contained in:
Sara 2025-02-23 14:38:14 +01:00
parent ee00efde1f
commit 21ba8e33af
5459 changed files with 1128836 additions and 198305 deletions

View 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

View file

@ -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"),

View file

@ -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!";

View file

@ -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);

View file

@ -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