feat: godot-engine-source-4.3-stable

This commit is contained in:
Jan van der Weide 2025-01-17 16:36:38 +01:00
parent c59a7dcade
commit 7125d019b5
11149 changed files with 5070401 additions and 0 deletions

View file

@ -0,0 +1,164 @@
/**************************************************************************/
/* test_config_file.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_CONFIG_FILE_H
#define TEST_CONFIG_FILE_H
#include "core/io/config_file.h"
#include "core/os/os.h"
#include "tests/test_macros.h"
namespace TestConfigFile {
TEST_CASE("[ConfigFile] Parsing well-formatted files") {
ConfigFile config_file;
// Formatting is intentionally hand-edited to see how human-friendly the parser is.
const Error error = config_file.parse(R"(
[player]
name = "Unnamed Player"
tagline="Waiting
for
Godot"
color =Color( 0, 0.5,1, 1) ; Inline comment
position= Vector2(
3,
4
)
[graphics]
antialiasing = true
; Testing comments and case-sensitivity...
antiAliasing = false
)");
CHECK_MESSAGE(error == OK, "The configuration file should parse successfully.");
CHECK_MESSAGE(
String(config_file.get_value("player", "name")) == "Unnamed Player",
"Reading `player/name` should return the expected value.");
CHECK_MESSAGE(
String(config_file.get_value("player", "tagline")) == "Waiting\nfor\nGodot",
"Reading `player/tagline` should return the expected value.");
CHECK_MESSAGE(
Color(config_file.get_value("player", "color")).is_equal_approx(Color(0, 0.5, 1)),
"Reading `player/color` should return the expected value.");
CHECK_MESSAGE(
Vector2(config_file.get_value("player", "position")).is_equal_approx(Vector2(3, 4)),
"Reading `player/position` should return the expected value.");
CHECK_MESSAGE(
bool(config_file.get_value("graphics", "antialiasing")),
"Reading `graphics/antialiasing` should return `true`.");
CHECK_MESSAGE(
bool(config_file.get_value("graphics", "antiAliasing")) == false,
"Reading `graphics/antiAliasing` should return `false`.");
// An empty ConfigFile is valid.
const Error error_empty = config_file.parse("");
CHECK_MESSAGE(error_empty == OK,
"An empty configuration file should parse successfully.");
}
TEST_CASE("[ConfigFile] Parsing malformatted file") {
ConfigFile config_file;
ERR_PRINT_OFF;
const Error error = config_file.parse(R"(
[player]
name = "Unnamed Player"" ; Extraneous closing quote.
tagline = "Waiting\nfor\nGodot"
color = Color(0, 0.5, 1) ; Missing 4th parameter.
position = Vector2(
3,,
4
) ; Extraneous comma.
[graphics]
antialiasing = true
antialiasing = false ; Duplicate key.
)");
ERR_PRINT_ON;
CHECK_MESSAGE(error == ERR_PARSE_ERROR,
"The configuration file shouldn't parse successfully.");
}
TEST_CASE("[ConfigFile] Saving file") {
ConfigFile config_file;
config_file.set_value("player", "name", "Unnamed Player");
config_file.set_value("player", "tagline", "Waiting\nfor\nGodot");
config_file.set_value("player", "color", Color(0, 0.5, 1));
config_file.set_value("player", "position", Vector2(3, 4));
config_file.set_value("graphics", "antialiasing", true);
config_file.set_value("graphics", "antiAliasing", false);
config_file.set_value("quoted", String::utf8("静音"), 42);
config_file.set_value("quoted", "a=b", 7);
#ifdef WINDOWS_ENABLED
const String config_path = OS::get_singleton()->get_environment("TEMP").path_join("config.ini");
#else
const String config_path = "/tmp/config.ini";
#endif
config_file.save(config_path);
// Expected contents of the saved ConfigFile.
const String contents = String::utf8(R"([player]
name="Unnamed Player"
tagline="Waiting
for
Godot"
color=Color(0, 0.5, 1, 1)
position=Vector2(3, 4)
[graphics]
antialiasing=true
antiAliasing=false
[quoted]
"静音"=42
"a=b"=7
)");
Ref<FileAccess> file = FileAccess::open(config_path, FileAccess::READ);
CHECK_MESSAGE(file->get_as_utf8_string() == contents,
"The saved configuration file should match the expected format.");
}
} // namespace TestConfigFile
#endif // TEST_CONFIG_FILE_H

View file

@ -0,0 +1,112 @@
/**************************************************************************/
/* test_file_access.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_FILE_ACCESS_H
#define TEST_FILE_ACCESS_H
#include "core/io/file_access.h"
#include "tests/test_macros.h"
#include "tests/test_utils.h"
namespace TestFileAccess {
TEST_CASE("[FileAccess] CSV read") {
Ref<FileAccess> f = FileAccess::open(TestUtils::get_data_path("testdata.csv"), FileAccess::READ);
REQUIRE(!f.is_null());
Vector<String> header = f->get_csv_line(); // Default delimiter: ",".
REQUIRE(header.size() == 4);
Vector<String> row1 = f->get_csv_line(","); // Explicit delimiter, should be the same.
REQUIRE(row1.size() == 4);
CHECK(row1[0] == "GOOD_MORNING");
CHECK(row1[1] == "Good Morning");
CHECK(row1[2] == "Guten Morgen");
CHECK(row1[3] == "Bonjour");
Vector<String> row2 = f->get_csv_line();
REQUIRE(row2.size() == 4);
CHECK(row2[0] == "GOOD_EVENING");
CHECK(row2[1] == "Good Evening");
CHECK(row2[2].is_empty()); // Use case: not yet translated!
// https://github.com/godotengine/godot/issues/44269
CHECK_MESSAGE(row2[2] != "\"", "Should not parse empty string as a single double quote.");
CHECK(row2[3] == "\"\""); // Intentionally testing only escaped double quotes.
Vector<String> row3 = f->get_csv_line();
REQUIRE(row3.size() == 6);
CHECK(row3[0] == "Without quotes");
CHECK(row3[1] == "With, comma");
CHECK(row3[2] == "With \"inner\" quotes");
CHECK(row3[3] == "With \"inner\", quotes\",\" and comma");
CHECK(row3[4] == "With \"inner\nsplit\" quotes and\nline breaks");
CHECK(row3[5] == "With \\nnewline chars"); // Escaped, not an actual newline.
Vector<String> row4 = f->get_csv_line("~"); // Custom delimiter, makes inline commas easier.
REQUIRE(row4.size() == 3);
CHECK(row4[0] == "Some other");
CHECK(row4[1] == "delimiter");
CHECK(row4[2] == "should still work, shouldn't it?");
Vector<String> row5 = f->get_csv_line("\t"); // Tab separated variables.
REQUIRE(row5.size() == 3);
CHECK(row5[0] == "What about");
CHECK(row5[1] == "tab separated");
CHECK(row5[2] == "lines, good?");
}
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());
String s_lf = f_lf->get_as_utf8_string();
f_lf->seek(0);
String s_lf_nocr = f_lf->get_as_utf8_string(true);
CHECK(s_lf == "Hello darkness\nMy old friend\nI've come to talk\nWith you again\n");
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());
String s_crlf = f_crlf->get_as_utf8_string();
f_crlf->seek(0);
String s_crlf_nocr = f_crlf->get_as_utf8_string(true);
CHECK(s_crlf == "Hello darkness\r\nMy old friend\r\nI've come to talk\r\nWith you again\r\n");
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());
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");
}
} // namespace TestFileAccess
#endif // TEST_FILE_ACCESS_H

View file

@ -0,0 +1,110 @@
/**************************************************************************/
/* test_http_client.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_HTTP_CLIENT_H
#define TEST_HTTP_CLIENT_H
#include "core/io/http_client.h"
#include "tests/test_macros.h"
#include "modules/modules_enabled.gen.h"
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");
}
TEST_CASE("[HTTPClient] query_string_from_dict") {
Ref<HTTPClient> client = HTTPClient::create();
Dictionary empty_dict;
String empty_query = client->query_string_from_dict(empty_dict);
CHECK_MESSAGE(empty_query.is_empty(), "A empty dictionary should return a empty string");
Dictionary dict1;
dict1["key"] = "value";
String single_key = client->query_string_from_dict(dict1);
CHECK_MESSAGE(single_key == "key=value", "The query should return key=value for every string in the dictionary");
// Check Dictionary with multiple values of different types.
Dictionary dict2;
dict2["key1"] = "value";
dict2["key2"] = 123;
Array values;
values.push_back(1);
values.push_back(2);
values.push_back(3);
dict2["key3"] = values;
dict2["key4"] = Variant();
String multiple_keys = client->query_string_from_dict(dict2);
CHECK_MESSAGE(multiple_keys == "key1=value&key2=123&key3=1&key3=2&key3=3&key4",
"The query should return key=value for every string in the dictionary. Pairs should be separated by &, arrays should be have a query for every element, and variants should have empty values");
}
TEST_CASE("[HTTPClient] verify_headers") {
Ref<HTTPClient> client = HTTPClient::create();
Vector<String> headers = { "Accept: text/html", "Content-Type: application/json", "Authorization: Bearer abc123" };
Error err = client->verify_headers(headers);
CHECK_MESSAGE(err == OK, "Expected OK for valid headers");
ERR_PRINT_OFF;
Vector<String> empty_header = { "" };
err = client->verify_headers(empty_header);
CHECK_MESSAGE(err == ERR_INVALID_PARAMETER, "Expected ERR_INVALID_PARAMETER for empty header");
Vector<String> invalid_header = { "InvalidHeader", "Header: " };
err = client->verify_headers(invalid_header);
CHECK_MESSAGE(err == ERR_INVALID_PARAMETER, "Expected ERR_INVALID_PARAMETER for header with no colon");
Vector<String> invalid_header_b = { ":", "Header: " };
err = client->verify_headers(invalid_header_b);
CHECK_MESSAGE(err == ERR_INVALID_PARAMETER, "Expected ERR_INVALID_PARAMETER for header with colon in first position");
ERR_PRINT_ON;
}
#if defined(MODULE_MBEDTLS_ENABLED) || defined(WEB_ENABLED)
TEST_CASE("[HTTPClient] connect_to_host") {
Ref<HTTPClient> client = HTTPClient::create();
String host = "https://www.example.com";
int port = 443;
Ref<TLSOptions> tls_options;
// Connect to host.
Error err = client->connect_to_host(host, port, tls_options);
CHECK_MESSAGE(err == OK, "Expected OK for successful connection");
}
#endif // MODULE_MBEDTLS_ENABLED || WEB_ENABLED
} // namespace TestHTTPClient
#endif // TEST_HTTP_CLIENT_H

View file

@ -0,0 +1,447 @@
/**************************************************************************/
/* test_image.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#ifndef TEST_IMAGE_H
#define TEST_IMAGE_H
#include "core/io/image.h"
#include "core/os/os.h"
#include "tests/test_utils.h"
#include "thirdparty/doctest/doctest.h"
#include "modules/modules_enabled.gen.h"
namespace TestImage {
TEST_CASE("[Image] Instantiation") {
Ref<Image> image = memnew(Image(8, 4, false, Image::FORMAT_RGBA8));
CHECK_MESSAGE(
!image->is_empty(),
"An image created with specified size and format should not be empty at first.");
CHECK_MESSAGE(
image->is_invisible(),
"A newly created image should be invisible.");
CHECK_MESSAGE(
!image->is_compressed(),
"A newly created image should not be compressed.");
CHECK(!image->has_mipmaps());
PackedByteArray image_data = image->get_data();
for (int i = 0; i < image_data.size(); i++) {
CHECK_MESSAGE(
image_data[i] == 0,
"An image created without data specified should have its data zeroed out.");
}
Ref<Image> image_copy = memnew(Image());
CHECK_MESSAGE(
image_copy->is_empty(),
"An image created without any specified size and format be empty at first.");
image_copy->copy_internals_from(image);
CHECK_MESSAGE(
image->get_data() == image_copy->get_data(),
"Duplicated images should have the same data.");
image_data = image->get_data();
Ref<Image> image_from_data = memnew(Image(8, 4, false, Image::FORMAT_RGBA8, image_data));
CHECK_MESSAGE(
image->get_data() == image_from_data->get_data(),
"An image created from data of another image should have the same data of the original image.");
}
TEST_CASE("[Image] Saving and loading") {
Ref<Image> image = memnew(Image(4, 4, false, Image::FORMAT_RGBA8));
const String save_path_png = TestUtils::get_temp_path("image.png");
const String save_path_exr = TestUtils::get_temp_path("image.exr");
// Save PNG
Error err;
err = image->save_png(save_path_png);
CHECK_MESSAGE(
err == OK,
"The image should be saved successfully as a .png file.");
// Only available on editor builds.
#ifdef TOOLS_ENABLED
// Save EXR
err = image->save_exr(save_path_exr, false);
CHECK_MESSAGE(
err == OK,
"The image should be saved successfully as an .exr file.");
#endif // TOOLS_ENABLED
// Load using load()
Ref<Image> image_load = memnew(Image());
err = image_load->load(save_path_png);
CHECK_MESSAGE(
err == OK,
"The image should load successfully using load().");
CHECK_MESSAGE(
image->get_data() == image_load->get_data(),
"The loaded image should have the same data as the one that got saved.");
#ifdef MODULE_BMP_ENABLED
// 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());
PackedByteArray data_bmp;
data_bmp.resize(f_bmp->get_length() + 1);
f_bmp->get_buffer(data_bmp.ptrw(), f_bmp->get_length());
CHECK_MESSAGE(
image_bmp->load_bmp_from_buffer(data_bmp) == OK,
"The BMP image should load successfully.");
#endif // MODULE_BMP_ENABLED
#ifdef MODULE_JPG_ENABLED
// 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());
PackedByteArray data_jpg;
data_jpg.resize(f_jpg->get_length() + 1);
f_jpg->get_buffer(data_jpg.ptrw(), f_jpg->get_length());
CHECK_MESSAGE(
image_jpg->load_jpg_from_buffer(data_jpg) == OK,
"The JPG image should load successfully.");
#endif // MODULE_JPG_ENABLED
#ifdef MODULE_WEBP_ENABLED
// 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());
PackedByteArray data_webp;
data_webp.resize(f_webp->get_length() + 1);
f_webp->get_buffer(data_webp.ptrw(), f_webp->get_length());
CHECK_MESSAGE(
image_webp->load_webp_from_buffer(data_webp) == OK,
"The WebP image should load successfully.");
#endif // MODULE_WEBP_ENABLED
// 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());
PackedByteArray data_png;
data_png.resize(f_png->get_length() + 1);
f_png->get_buffer(data_png.ptrw(), f_png->get_length());
CHECK_MESSAGE(
image_png->load_png_from_buffer(data_png) == OK,
"The PNG image should load successfully.");
#ifdef MODULE_TGA_ENABLED
// 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());
PackedByteArray data_tga;
data_tga.resize(f_tga->get_length() + 1);
f_tga->get_buffer(data_tga.ptrw(), f_tga->get_length());
CHECK_MESSAGE(
image_tga->load_tga_from_buffer(data_tga) == OK,
"The TGA image should load successfully.");
#endif // MODULE_TGA_ENABLED
}
TEST_CASE("[Image] Basic getters") {
Ref<Image> image = memnew(Image(8, 4, false, Image::FORMAT_LA8));
CHECK(image->get_width() == 8);
CHECK(image->get_height() == 4);
CHECK(image->get_size() == Vector2(8, 4));
CHECK(image->get_format() == Image::FORMAT_LA8);
CHECK(image->get_used_rect() == Rect2i(0, 0, 0, 0));
Ref<Image> image_get_rect = image->get_region(Rect2i(0, 0, 2, 1));
CHECK(image_get_rect->get_size() == Vector2(2, 1));
}
TEST_CASE("[Image] Resizing") {
Ref<Image> image = memnew(Image(8, 8, false, Image::FORMAT_RGBA8));
// Crop
image->crop(4, 4);
CHECK_MESSAGE(
image->get_size() == Vector2(4, 4),
"get_size() should return the correct size after cropping.");
image->set_pixel(0, 0, Color(1, 1, 1, 1));
// Resize
for (int i = 0; i < 5; i++) {
Ref<Image> image_resized = memnew(Image());
image_resized->copy_internals_from(image);
Image::Interpolation interpolation = static_cast<Image::Interpolation>(i);
image_resized->resize(8, 8, interpolation);
CHECK_MESSAGE(
image_resized->get_size() == Vector2(8, 8),
"get_size() should return the correct size after resizing.");
CHECK_MESSAGE(
image_resized->get_pixel(1, 1).a > 0,
"Resizing an image should also affect its content.");
}
// shrink_x2()
image->shrink_x2();
CHECK_MESSAGE(
image->get_size() == Vector2(2, 2),
"get_size() should return the correct size after shrink_x2().");
// resize_to_po2()
Ref<Image> image_po_2 = memnew(Image(14, 28, false, Image::FORMAT_RGBA8));
image_po_2->resize_to_po2();
CHECK_MESSAGE(
image_po_2->get_size() == Vector2(16, 32),
"get_size() should return the correct size after resize_to_po2().");
}
TEST_CASE("[Image] Modifying pixels of an image") {
Ref<Image> image = memnew(Image(3, 3, false, Image::FORMAT_RGBA8));
image->set_pixel(0, 0, Color(1, 1, 1, 1));
CHECK_MESSAGE(
!image->is_invisible(),
"Image should not be invisible after drawing on it.");
CHECK_MESSAGE(
image->get_pixelv(Vector2(0, 0)).is_equal_approx(Color(1, 1, 1, 1)),
"Image's get_pixel() should return the same color value as the one being set with set_pixel() in the same position.");
CHECK_MESSAGE(
image->get_used_rect() == Rect2i(0, 0, 1, 1),
"Image's get_used_rect should return the expected value, larger than Rect2i(0, 0, 0, 0) if it's visible.");
image->set_pixelv(Vector2(0, 0), Color(0.5, 0.5, 0.5, 0.5));
Ref<Image> image2 = memnew(Image(3, 3, false, Image::FORMAT_RGBA8));
// Fill image with color
image2->fill(Color(0.5, 0.5, 0.5, 0.5));
for (int y = 0; y < image2->get_height(); y++) {
for (int x = 0; x < image2->get_width(); x++) {
CHECK_MESSAGE(
image2->get_pixel(x, y).r > 0.49,
"fill() should colorize all pixels of the image.");
}
}
// Fill rect with color
{
const int img_width = 3;
const int img_height = 3;
Vector<Rect2i> rects;
rects.push_back(Rect2i());
rects.push_back(Rect2i(-5, -5, 3, 3));
rects.push_back(Rect2i(img_width, 0, 12, 12));
rects.push_back(Rect2i(0, img_height, 12, 12));
rects.push_back(Rect2i(img_width + 1, img_height + 2, 12, 12));
rects.push_back(Rect2i(1, 1, 1, 1));
rects.push_back(Rect2i(0, 1, 2, 3));
rects.push_back(Rect2i(-5, 0, img_width + 10, 2));
rects.push_back(Rect2i(0, -5, 2, img_height + 10));
rects.push_back(Rect2i(-1, -1, img_width + 1, img_height + 1));
for (const Rect2i &rect : rects) {
Ref<Image> img = memnew(Image(img_width, img_height, false, Image::FORMAT_RGBA8));
img->fill_rect(rect, Color(1, 1, 1, 1));
for (int y = 0; y < img->get_height(); y++) {
for (int x = 0; x < img->get_width(); x++) {
if (rect.abs().has_point(Point2(x, y))) {
CHECK_MESSAGE(
img->get_pixel(x, y).is_equal_approx(Color(1, 1, 1, 1)),
"fill_rect() should colorize all image pixels within rect bounds.");
} else {
CHECK_MESSAGE(
!img->get_pixel(x, y).is_equal_approx(Color(1, 1, 1, 1)),
"fill_rect() shouldn't colorize any image pixel out of rect bounds.");
}
}
}
}
}
// Blend two images together
image->blend_rect(image2, Rect2i(Vector2i(0, 0), image2->get_size()), Vector2i(0, 0));
CHECK_MESSAGE(
image->get_pixel(0, 0).a > 0.7,
"blend_rect() should blend the alpha values of the two images.");
CHECK_MESSAGE(
image->get_used_rect().size == image->get_size(),
"get_used_rect() should return the expected value, its Rect size should be the same as get_size() if there are no transparent pixels.");
Ref<Image> image3 = memnew(Image(2, 2, false, Image::FORMAT_RGBA8));
image3->set_pixel(0, 0, Color(0, 1, 0, 1));
//blit_rect() two images together
image->blit_rect(image3, Rect2i(Vector2i(0, 0), image3->get_size()), Vector2i(0, 0));
CHECK_MESSAGE(
image->get_pixel(0, 0).is_equal_approx(Color(0, 1, 0, 1)),
"blit_rect() should replace old colors and not blend them.");
CHECK_MESSAGE(
!image->get_pixel(2, 2).is_equal_approx(Color(0, 1, 0, 1)),
"blit_rect() should not affect the area of the image that is outside src_rect.");
// Flip image
image3->flip_x();
CHECK(image3->get_pixel(1, 0).is_equal_approx(Color(0, 1, 0, 1)));
CHECK_MESSAGE(
image3->get_pixel(0, 0).is_equal_approx(Color(0, 0, 0, 0)),
"flip_x() should not leave old pixels behind.");
image3->flip_y();
CHECK(image3->get_pixel(1, 1).is_equal_approx(Color(0, 1, 0, 1)));
CHECK_MESSAGE(
image3->get_pixel(1, 0).is_equal_approx(Color(0, 0, 0, 0)),
"flip_y() should not leave old pixels behind.");
// Pre-multiply Alpha then Convert from RGBA to L8, checking alpha
{
Ref<Image> gray_image = memnew(Image(3, 3, false, Image::FORMAT_RGBA8));
gray_image->fill_rect(Rect2i(0, 0, 3, 3), Color(1, 1, 1, 0));
gray_image->set_pixel(1, 1, Color(1, 1, 1, 1));
gray_image->set_pixel(1, 2, Color(0.5, 0.5, 0.5, 0.5));
gray_image->set_pixel(2, 1, Color(0.25, 0.05, 0.5, 1.0));
gray_image->set_pixel(2, 2, Color(0.5, 0.25, 0.95, 0.75));
gray_image->premultiply_alpha();
gray_image->convert(Image::FORMAT_L8);
CHECK_MESSAGE(gray_image->get_pixel(0, 0).is_equal_approx(Color(0, 0, 0, 1)), "convert() RGBA to L8 should be black.");
CHECK_MESSAGE(gray_image->get_pixel(0, 1).is_equal_approx(Color(0, 0, 0, 1)), "convert() RGBA to L8 should be black.");
CHECK_MESSAGE(gray_image->get_pixel(0, 2).is_equal_approx(Color(0, 0, 0, 1)), "convert() RGBA to L8 should be black.");
CHECK_MESSAGE(gray_image->get_pixel(1, 0).is_equal_approx(Color(0, 0, 0, 1)), "convert() RGBA to L8 should be black.");
CHECK_MESSAGE(gray_image->get_pixel(1, 1).is_equal_approx(Color(1, 1, 1, 1)), "convert() RGBA to L8 should be white.");
CHECK_MESSAGE(gray_image->get_pixel(1, 2).is_equal_approx(Color(0.250980407, 0.250980407, 0.250980407, 1)), "convert() RGBA to L8 should be around 0.250980407 (64).");
CHECK_MESSAGE(gray_image->get_pixel(2, 0).is_equal_approx(Color(0, 0, 0, 1)), "convert() RGBA to L8 should be black.");
CHECK_MESSAGE(gray_image->get_pixel(2, 1).is_equal_approx(Color(0.121568628, 0.121568628, 0.121568628, 1)), "convert() RGBA to L8 should be around 0.121568628 (31).");
CHECK_MESSAGE(gray_image->get_pixel(2, 2).is_equal_approx(Color(0.266666681, 0.266666681, 0.266666681, 1)), "convert() RGBA to L8 should be around 0.266666681 (68).");
}
}
TEST_CASE("[Image] Custom mipmaps") {
Ref<Image> image = memnew(Image(100, 100, false, Image::FORMAT_RGBA8));
REQUIRE(!image->has_mipmaps());
image->generate_mipmaps();
REQUIRE(image->has_mipmaps());
const int mipmaps = image->get_mipmap_count() + 1;
REQUIRE(mipmaps == 7);
// Initialize reference mipmap data.
// Each byte is given value "mipmap_index * 5".
{
PackedByteArray data = image->get_data();
uint8_t *data_ptr = data.ptrw();
for (int mip = 0; mip < mipmaps; mip++) {
int64_t mip_offset = 0;
int64_t mip_size = 0;
image->get_mipmap_offset_and_size(mip, mip_offset, mip_size);
for (int i = 0; i < mip_size; i++) {
data_ptr[mip_offset + i] = mip * 5;
}
}
image->set_data(image->get_width(), image->get_height(), image->has_mipmaps(), image->get_format(), data);
}
// Byte format conversion.
for (int format = Image::FORMAT_L8; format <= Image::FORMAT_RGBA8; format++) {
Ref<Image> image_bytes = memnew(Image());
image_bytes->copy_internals_from(image);
image_bytes->convert((Image::Format)format);
REQUIRE(image_bytes->has_mipmaps());
PackedByteArray data = image_bytes->get_data();
const uint8_t *data_ptr = data.ptr();
for (int mip = 0; mip < mipmaps; mip++) {
int64_t mip_offset = 0;
int64_t mip_size = 0;
image_bytes->get_mipmap_offset_and_size(mip, mip_offset, mip_size);
for (int i = 0; i < mip_size; i++) {
if (data_ptr[mip_offset + i] != mip * 5) {
REQUIRE_MESSAGE(false, "Byte format conversion error.");
}
}
}
}
// Floating point format conversion.
for (int format = Image::FORMAT_RF; format <= Image::FORMAT_RGBAF; format++) {
Ref<Image> image_rgbaf = memnew(Image());
image_rgbaf->copy_internals_from(image);
image_rgbaf->convert((Image::Format)format);
REQUIRE(image_rgbaf->has_mipmaps());
PackedByteArray data = image_rgbaf->get_data();
const uint8_t *data_ptr = data.ptr();
for (int mip = 0; mip < mipmaps; mip++) {
int64_t mip_offset = 0;
int64_t mip_size = 0;
image_rgbaf->get_mipmap_offset_and_size(mip, mip_offset, mip_size);
for (int i = 0; i < mip_size; i += 4) {
float value = *(float *)(data_ptr + mip_offset + i);
if (!Math::is_equal_approx(value * 255.0f, mip * 5)) {
REQUIRE_MESSAGE(false, "Floating point conversion error.");
}
}
}
}
}
TEST_CASE("[Image] Convert image") {
for (int format = Image::FORMAT_RF; format < Image::FORMAT_RGBE9995; format++) {
for (int new_format = Image::FORMAT_RF; new_format < Image::FORMAT_RGBE9995; new_format++) {
Ref<Image> image = memnew(Image(4, 4, false, (Image::Format)format));
image->convert((Image::Format)new_format);
String format_string = Image::format_names[(Image::Format)format];
String new_format_string = Image::format_names[(Image::Format)new_format];
format_string = "Error converting from " + format_string + " to " + new_format_string + ".";
CHECK_MESSAGE(image->get_format() == new_format, format_string);
}
}
Ref<Image> image = memnew(Image(4, 4, false, Image::FORMAT_RGBA8));
PackedByteArray image_data = image->get_data();
ERR_PRINT_OFF;
image->convert((Image::Format)-1);
ERR_PRINT_ON;
CHECK_MESSAGE(image->get_data() == image_data, "Image conversion to invalid type (-1) should not alter image.");
Ref<Image> image2 = memnew(Image(4, 4, false, Image::FORMAT_RGBA8));
image_data = image2->get_data();
ERR_PRINT_OFF;
image2->convert((Image::Format)(Image::FORMAT_MAX + 1));
ERR_PRINT_ON;
CHECK_MESSAGE(image2->get_data() == image_data, "Image conversion to invalid type (Image::FORMAT_MAX + 1) should not alter image.");
}
} // namespace TestImage
#endif // TEST_IMAGE_H

View file

@ -0,0 +1,51 @@
/**************************************************************************/
/* test_ip.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_IP_H
#define TEST_IP_H
#include "core/io/ip.h"
#include "tests/test_macros.h"
namespace TestIP {
TEST_CASE("[IP] resolve_hostname") {
for (int x = 0; x < 1000; x++) {
IPAddress IPV4 = IP::get_singleton()->resolve_hostname("localhost", IP::TYPE_IPV4);
CHECK("127.0.0.1" == String(IPV4));
IPAddress IPV6 = IP::get_singleton()->resolve_hostname("localhost", IP::TYPE_IPV6);
CHECK("0:0:0:0:0:0:0:1" == String(IPV6));
}
}
} // namespace TestIP
#endif // TEST_IP_H

View file

@ -0,0 +1,237 @@
/**************************************************************************/
/* test_json.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_H
#define TEST_JSON_H
#include "core/io/json.h"
#include "thirdparty/doctest/doctest.h"
namespace TestJSON {
// NOTE: The current JSON parser accepts many non-conformant strings such as
// single-quoted strings, duplicate commas and trailing commas.
// This is intentionally not tested as users shouldn't rely on this behavior.
TEST_CASE("[JSON] Parsing single data types") {
// Parsing a single data type as JSON is valid per the JSON specification.
JSON json;
json.parse("null");
CHECK_MESSAGE(
json.get_error_line() == 0,
"Parsing `null` as JSON should parse successfully.");
CHECK_MESSAGE(
json.get_data() == Variant(),
"Parsing a double quoted string as JSON should return the expected value.");
json.parse("true");
CHECK_MESSAGE(
json.get_error_line() == 0,
"Parsing boolean `true` as JSON should parse successfully.");
CHECK_MESSAGE(
json.get_data(),
"Parsing boolean `true` as JSON should return the expected value.");
json.parse("false");
CHECK_MESSAGE(
json.get_error_line() == 0,
"Parsing boolean `false` as JSON should parse successfully.");
CHECK_MESSAGE(
!json.get_data(),
"Parsing boolean `false` as JSON should return the expected value.");
json.parse("123456");
CHECK_MESSAGE(
json.get_error_line() == 0,
"Parsing an integer number as JSON should parse successfully.");
CHECK_MESSAGE(
(int)(json.get_data()) == 123456,
"Parsing an integer number as JSON should return the expected value.");
json.parse("0.123456");
CHECK_MESSAGE(
json.get_error_line() == 0,
"Parsing a floating-point number as JSON should parse successfully.");
CHECK_MESSAGE(
double(json.get_data()) == doctest::Approx(0.123456),
"Parsing a floating-point number as JSON should return the expected value.");
json.parse("\"hello\"");
CHECK_MESSAGE(
json.get_error_line() == 0,
"Parsing a double quoted string as JSON should parse successfully.");
CHECK_MESSAGE(
json.get_data() == "hello",
"Parsing a double quoted string as JSON should return the expected value.");
}
TEST_CASE("[JSON] Parsing arrays") {
JSON json;
// JSON parsing fails if it's split over several lines (even if leading indentation is removed).
json.parse(R"(["Hello", "world.", "This is",["a","json","array.",[]], "Empty arrays ahoy:", [[["Gotcha!"]]]])");
const Array array = json.get_data();
CHECK_MESSAGE(
json.get_error_line() == 0,
"Parsing a JSON array should parse successfully.");
CHECK_MESSAGE(
array[0] == "Hello",
"The parsed JSON should contain the expected values.");
const Array sub_array = array[3];
CHECK_MESSAGE(
sub_array.size() == 4,
"The parsed JSON should contain the expected values.");
CHECK_MESSAGE(
sub_array[1] == "json",
"The parsed JSON should contain the expected values.");
CHECK_MESSAGE(
sub_array[3].hash() == Array().hash(),
"The parsed JSON should contain the expected values.");
const Array deep_array = Array(Array(array[5])[0])[0];
CHECK_MESSAGE(
deep_array[0] == "Gotcha!",
"The parsed JSON should contain the expected values.");
}
TEST_CASE("[JSON] Parsing objects (dictionaries)") {
JSON json;
json.parse(R"({"name": "Godot Engine", "is_free": true, "bugs": null, "apples": {"red": 500, "green": 0, "blue": -20}, "empty_object": {}})");
const Dictionary dictionary = json.get_data();
CHECK_MESSAGE(
dictionary["name"] == "Godot Engine",
"The parsed JSON should contain the expected values.");
CHECK_MESSAGE(
dictionary["is_free"],
"The parsed JSON should contain the expected values.");
CHECK_MESSAGE(
dictionary["bugs"] == Variant(),
"The parsed JSON should contain the expected values.");
CHECK_MESSAGE(
(int)Dictionary(dictionary["apples"])["blue"] == -20,
"The parsed JSON should contain the expected values.");
CHECK_MESSAGE(
dictionary["empty_object"].hash() == Dictionary().hash(),
"The parsed JSON should contain the expected values.");
}
TEST_CASE("[JSON] Parsing escape sequences") {
// Only certain escape sequences are valid according to the JSON specification.
// Others must result in a parsing error instead.
JSON json;
TypedArray<String> valid_escapes;
valid_escapes.push_back("\";\"");
valid_escapes.push_back("\\;\\");
valid_escapes.push_back("/;/");
valid_escapes.push_back("b;\b");
valid_escapes.push_back("f;\f");
valid_escapes.push_back("n;\n");
valid_escapes.push_back("r;\r");
valid_escapes.push_back("t;\t");
SUBCASE("Basic valid escape sequences") {
for (int i = 0; i < valid_escapes.size(); i++) {
String valid_escape = valid_escapes[i];
String valid_escape_string = valid_escape.get_slice(";", 0);
String valid_escape_value = valid_escape.get_slice(";", 1);
String json_string = "\"\\";
json_string += valid_escape_string;
json_string += "\"";
json.parse(json_string);
CHECK_MESSAGE(
json.get_error_line() == 0,
vformat("Parsing valid escape sequence `%s` as JSON should parse successfully.", valid_escape_string));
String json_value = json.get_data();
CHECK_MESSAGE(
json_value == valid_escape_value,
vformat("Parsing valid escape sequence `%s` as JSON should return the expected value.", valid_escape_string));
}
}
SUBCASE("Valid unicode escape sequences") {
String json_string = "\"\\u0020\"";
json.parse(json_string);
CHECK_MESSAGE(
json.get_error_line() == 0,
vformat("Parsing valid unicode escape sequence with value `0020` as JSON should parse successfully."));
String json_value = json.get_data();
CHECK_MESSAGE(
json_value == " ",
vformat("Parsing valid unicode escape sequence with value `0020` as JSON should return the expected value."));
}
SUBCASE("Invalid escape sequences") {
ERR_PRINT_OFF
for (char32_t i = 0; i < 128; i++) {
bool skip = false;
for (int j = 0; j < valid_escapes.size(); j++) {
String valid_escape = valid_escapes[j];
String valid_escape_string = valid_escape.get_slice(";", 0);
if (valid_escape_string[0] == i) {
skip = true;
break;
}
}
if (skip) {
continue;
}
String json_string = "\"\\";
json_string += i;
json_string += "\"";
Error err = json.parse(json_string);
// TODO: Line number is currently kept on 0, despite an error occurring. This should be fixed in the JSON parser.
// CHECK_MESSAGE(
// json.get_error_line() != 0,
// vformat("Parsing invalid escape sequence with ASCII value `%d` as JSON should fail to parse.", i));
CHECK_MESSAGE(
err == ERR_PARSE_ERROR,
vformat("Parsing invalid escape sequence with ASCII value `%d` as JSON should fail to parse with ERR_PARSE_ERROR.", i));
}
ERR_PRINT_ON
}
}
} // namespace TestJSON
#endif // TEST_JSON_H

View file

@ -0,0 +1,391 @@
/**************************************************************************/
/* test_marshalls.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_MARSHALLS_H
#define TEST_MARSHALLS_H
#include "core/io/marshalls.h"
#include "tests/test_macros.h"
namespace TestMarshalls {
TEST_CASE("[Marshalls] Unsigned 16 bit integer encoding") {
uint8_t arr[2];
unsigned int actual_size = encode_uint16(0x1234, arr);
CHECK(actual_size == sizeof(uint16_t));
CHECK_MESSAGE(arr[0] == 0x34, "First encoded byte value should be equal to low order byte value.");
CHECK_MESSAGE(arr[1] == 0x12, "Last encoded byte value should be equal to high order byte value.");
}
TEST_CASE("[Marshalls] Unsigned 32 bit integer encoding") {
uint8_t arr[4];
unsigned int actual_size = encode_uint32(0x12345678, arr);
CHECK(actual_size == sizeof(uint32_t));
CHECK_MESSAGE(arr[0] == 0x78, "First encoded byte value should be equal to low order byte value.");
CHECK(arr[1] == 0x56);
CHECK(arr[2] == 0x34);
CHECK_MESSAGE(arr[3] == 0x12, "Last encoded byte value should be equal to high order byte value.");
}
TEST_CASE("[Marshalls] Unsigned 64 bit integer encoding") {
uint8_t arr[8];
unsigned int actual_size = encode_uint64(0x0f123456789abcdef, arr);
CHECK(actual_size == sizeof(uint64_t));
CHECK_MESSAGE(arr[0] == 0xef, "First encoded byte value should be equal to low order byte value.");
CHECK(arr[1] == 0xcd);
CHECK(arr[2] == 0xab);
CHECK(arr[3] == 0x89);
CHECK(arr[4] == 0x67);
CHECK(arr[5] == 0x45);
CHECK(arr[6] == 0x23);
CHECK_MESSAGE(arr[7] == 0xf1, "Last encoded byte value should be equal to high order byte value.");
}
TEST_CASE("[Marshalls] Unsigned 16 bit integer decoding") {
uint8_t arr[] = { 0x34, 0x12 };
CHECK(decode_uint16(arr) == 0x1234);
}
TEST_CASE("[Marshalls] Unsigned 32 bit integer decoding") {
uint8_t arr[] = { 0x78, 0x56, 0x34, 0x12 };
CHECK(decode_uint32(arr) == 0x12345678);
}
TEST_CASE("[Marshalls] Unsigned 64 bit integer decoding") {
uint8_t arr[] = { 0xef, 0xcd, 0xab, 0x89, 0x67, 0x45, 0x23, 0xf1 };
CHECK(decode_uint64(arr) == 0x0f123456789abcdef);
}
TEST_CASE("[Marshalls] Floating point single precision encoding") {
uint8_t arr[4];
// Decimal: 0.15625
// IEEE 754 single-precision binary floating-point format:
// sign exponent (8 bits) fraction (23 bits)
// 0 01111100 01000000000000000000000
// Hexadecimal: 0x3E200000
unsigned int actual_size = encode_float(0.15625f, arr);
CHECK(actual_size == sizeof(uint32_t));
CHECK(arr[0] == 0x00);
CHECK(arr[1] == 0x00);
CHECK(arr[2] == 0x20);
CHECK(arr[3] == 0x3e);
}
TEST_CASE("[Marshalls] Floating point double precision encoding") {
uint8_t arr[8];
// Decimal: 0.333333333333333314829616256247390992939472198486328125
// IEEE 754 double-precision binary floating-point format:
// sign exponent (11 bits) fraction (52 bits)
// 0 01111111101 0101010101010101010101010101010101010101010101010101
// Hexadecimal: 0x3FD5555555555555
unsigned int actual_size = encode_double(0.33333333333333333, arr);
CHECK(actual_size == sizeof(uint64_t));
CHECK(arr[0] == 0x55);
CHECK(arr[1] == 0x55);
CHECK(arr[2] == 0x55);
CHECK(arr[3] == 0x55);
CHECK(arr[4] == 0x55);
CHECK(arr[5] == 0x55);
CHECK(arr[6] == 0xd5);
CHECK(arr[7] == 0x3f);
}
TEST_CASE("[Marshalls] Floating point single precision decoding") {
uint8_t arr[] = { 0x00, 0x00, 0x20, 0x3e };
// See floating point encoding test case for details behind expected values
CHECK(decode_float(arr) == 0.15625f);
}
TEST_CASE("[Marshalls] Floating point double precision decoding") {
uint8_t arr[] = { 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0xd5, 0x3f };
// See floating point encoding test case for details behind expected values
CHECK(decode_double(arr) == 0.33333333333333333);
}
TEST_CASE("[Marshalls] C string encoding") {
char cstring[] = "Godot"; // 5 characters
uint8_t data[6];
int actual_size = encode_cstring(cstring, data);
CHECK(actual_size == 6);
CHECK(data[0] == 'G');
CHECK(data[1] == 'o');
CHECK(data[2] == 'd');
CHECK(data[3] == 'o');
CHECK(data[4] == 't');
CHECK(data[5] == '\0');
}
TEST_CASE("[Marshalls] NIL Variant encoding") {
int r_len;
Variant variant;
uint8_t buffer[4];
CHECK(encode_variant(variant, buffer, r_len) == OK);
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);
CHECK(buffer[3] == 0x00);
// No value
}
TEST_CASE("[Marshalls] INT 32 bit Variant encoding") {
int r_len;
Variant variant(0x12345678);
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(buffer[0] == 0x02, "Variant::INT");
CHECK(buffer[1] == 0x00);
CHECK(buffer[2] == 0x00);
CHECK(buffer[3] == 0x00);
// Check value
CHECK(buffer[4] == 0x78);
CHECK(buffer[5] == 0x56);
CHECK(buffer[6] == 0x34);
CHECK(buffer[7] == 0x12);
}
TEST_CASE("[Marshalls] INT 64 bit Variant encoding") {
int r_len;
Variant variant(uint64_t(0x0f123456789abcdef));
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(buffer[0] == 0x02, "Variant::INT");
CHECK(buffer[1] == 0x00);
CHECK_MESSAGE(buffer[2] == 0x01, "HEADER_DATA_FLAG_64");
CHECK(buffer[3] == 0x00);
// Check value
CHECK(buffer[4] == 0xef);
CHECK(buffer[5] == 0xcd);
CHECK(buffer[6] == 0xab);
CHECK(buffer[7] == 0x89);
CHECK(buffer[8] == 0x67);
CHECK(buffer[9] == 0x45);
CHECK(buffer[10] == 0x23);
CHECK(buffer[11] == 0xf1);
}
TEST_CASE("[Marshalls] FLOAT single precision Variant encoding") {
int r_len;
Variant variant(0.15625f);
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(buffer[0] == 0x03, "Variant::FLOAT");
CHECK(buffer[1] == 0x00);
CHECK(buffer[2] == 0x00);
CHECK(buffer[3] == 0x00);
// Check value
CHECK(buffer[4] == 0x00);
CHECK(buffer[5] == 0x00);
CHECK(buffer[6] == 0x20);
CHECK(buffer[7] == 0x3e);
}
TEST_CASE("[Marshalls] FLOAT double precision Variant encoding") {
int r_len;
Variant variant(0.33333333333333333);
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(buffer[0] == 0x03, "Variant::FLOAT");
CHECK(buffer[1] == 0x00);
CHECK_MESSAGE(buffer[2] == 0x01, "HEADER_DATA_FLAG_64");
CHECK(buffer[3] == 0x00);
// Check value
CHECK(buffer[4] == 0x55);
CHECK(buffer[5] == 0x55);
CHECK(buffer[6] == 0x55);
CHECK(buffer[7] == 0x55);
CHECK(buffer[8] == 0x55);
CHECK(buffer[9] == 0x55);
CHECK(buffer[10] == 0xd5);
CHECK(buffer[11] == 0x3f);
}
TEST_CASE("[Marshalls] Invalid data Variant decoding") {
Variant variant;
int r_len = 0;
uint8_t some_buffer[1] = { 0x00 };
uint8_t out_of_range_type_buffer[4] = { 0xff }; // Greater than Variant::VARIANT_MAX
ERR_PRINT_OFF;
CHECK(decode_variant(variant, some_buffer, /* less than 4 */ 1, &r_len) == ERR_INVALID_DATA);
CHECK(r_len == 0);
CHECK(decode_variant(variant, out_of_range_type_buffer, 4, &r_len) == ERR_INVALID_DATA);
CHECK(r_len == 0);
ERR_PRINT_ON;
}
TEST_CASE("[Marshalls] NIL Variant decoding") {
Variant variant;
int r_len;
uint8_t buffer[] = {
0x00, 0x00, 0x00, 0x00 // Variant::NIL
};
CHECK(decode_variant(variant, buffer, 4, &r_len) == OK);
CHECK(r_len == 4);
CHECK(variant == Variant());
}
TEST_CASE("[Marshalls] INT 32 bit Variant decoding") {
Variant variant;
int r_len;
uint8_t buffer[] = {
0x02, 0x00, 0x00, 0x00, // Variant::INT
0x78, 0x56, 0x34, 0x12 // value
};
CHECK(decode_variant(variant, buffer, 8, &r_len) == OK);
CHECK(r_len == 8);
CHECK(variant == Variant(0x12345678));
}
TEST_CASE("[Marshalls] INT 64 bit Variant decoding") {
Variant variant;
int r_len;
uint8_t buffer[] = {
0x02, 0x00, 0x01, 0x00, // Variant::INT, HEADER_DATA_FLAG_64
0xef, 0xcd, 0xab, 0x89, 0x67, 0x45, 0x23, 0xf1 // value
};
CHECK(decode_variant(variant, buffer, 12, &r_len) == OK);
CHECK(r_len == 12);
CHECK(variant == Variant(uint64_t(0x0f123456789abcdef)));
}
TEST_CASE("[Marshalls] FLOAT single precision Variant decoding") {
Variant variant;
int r_len;
uint8_t buffer[] = {
0x03, 0x00, 0x00, 0x00, // Variant::FLOAT
0x00, 0x00, 0x20, 0x3e // value
};
CHECK(decode_variant(variant, buffer, 8, &r_len) == OK);
CHECK(r_len == 8);
CHECK(variant == Variant(0.15625f));
}
TEST_CASE("[Marshalls] FLOAT double precision Variant decoding") {
Variant variant;
int r_len;
uint8_t buffer[] = {
0x03, 0x00, 0x01, 0x00, // Variant::FLOAT, HEADER_DATA_FLAG_64
0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0xd5, 0x3f // value
};
CHECK(decode_variant(variant, buffer, 12, &r_len) == OK);
CHECK(r_len == 12);
CHECK(variant == Variant(0.33333333333333333));
}
TEST_CASE("[Marshalls] Typed array encoding") {
int r_len;
Array array;
array.set_typed(Variant::INT, StringName(), Ref<Script>());
array.push_back(Variant(uint64_t(0x0f123456789abcdef)));
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(buffer[0] == 0x1c, "Variant::ARRAY");
CHECK(buffer[1] == 0x00);
CHECK_MESSAGE(buffer[2] == 0x01, "HEADER_DATA_FIELD_TYPED_ARRAY_BUILTIN");
CHECK(buffer[3] == 0x00);
// Check array type.
CHECK_MESSAGE(buffer[4] == 0x02, "Variant::INT");
CHECK(buffer[5] == 0x00);
CHECK(buffer[6] == 0x00);
CHECK(buffer[7] == 0x00);
// Check array size.
CHECK(buffer[8] == 0x01);
CHECK(buffer[9] == 0x00);
CHECK(buffer[10] == 0x00);
CHECK(buffer[11] == 0x00);
// Check element type.
CHECK_MESSAGE(buffer[12] == 0x02, "Variant::INT");
CHECK(buffer[13] == 0x00);
CHECK_MESSAGE(buffer[14] == 0x01, "HEADER_DATA_FLAG_64");
CHECK(buffer[15] == 0x00);
// Check element value.
CHECK(buffer[16] == 0xef);
CHECK(buffer[17] == 0xcd);
CHECK(buffer[18] == 0xab);
CHECK(buffer[19] == 0x89);
CHECK(buffer[20] == 0x67);
CHECK(buffer[21] == 0x45);
CHECK(buffer[22] == 0x23);
CHECK(buffer[23] == 0xf1);
}
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
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).
0xef, 0xcd, 0xab, 0x89, 0x67, 0x45, 0x23, 0xf1, // Element value.
};
CHECK(decode_variant(variant, buffer, 24, &r_len) == OK);
CHECK(r_len == 24);
CHECK(variant.get_type() == Variant::ARRAY);
Array array = variant;
CHECK(array.get_typed_builtin() == Variant::INT);
CHECK(array.size() == 1);
CHECK(array[0] == Variant(uint64_t(0x0f123456789abcdef)));
}
} // namespace TestMarshalls
#endif // TEST_MARSHALLS_H

View file

@ -0,0 +1,122 @@
/**************************************************************************/
/* test_pck_packer.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_PCK_PACKER_H
#define TEST_PCK_PACKER_H
#include "core/io/file_access_pack.h"
#include "core/io/pck_packer.h"
#include "core/os/os.h"
#include "tests/test_utils.h"
#include "thirdparty/doctest/doctest.h"
namespace TestPCKPacker {
TEST_CASE("[PCKPacker] Pack an empty PCK file") {
PCKPacker pck_packer;
const String output_pck_path = TestUtils::get_temp_path("output_empty.pck");
CHECK_MESSAGE(
pck_packer.pck_start(output_pck_path) == OK,
"Starting a PCK file should return an OK error code.");
CHECK_MESSAGE(
pck_packer.flush() == OK,
"Flushing the PCK should return an OK error code.");
Error err;
Ref<FileAccess> f = FileAccess::open(output_pck_path, FileAccess::READ, &err);
CHECK_MESSAGE(
err == OK,
"The generated empty PCK file should be opened successfully.");
CHECK_MESSAGE(
f->get_length() >= 100,
"The generated empty PCK file shouldn't be too small (it should have the PCK header).");
CHECK_MESSAGE(
f->get_length() <= 500,
"The generated empty PCK file shouldn't be too large.");
}
TEST_CASE("[PCKPacker] Pack empty with zero alignment invalid") {
PCKPacker pck_packer;
const String output_pck_path = TestUtils::get_temp_path("output_empty.pck");
ERR_PRINT_OFF;
CHECK_MESSAGE(pck_packer.pck_start(output_pck_path, 0) != OK, "PCK with zero alignment should fail.");
ERR_PRINT_ON;
}
TEST_CASE("[PCKPacker] Pack empty with invalid key") {
PCKPacker pck_packer;
const String output_pck_path = TestUtils::get_temp_path("output_empty.pck");
ERR_PRINT_OFF;
CHECK_MESSAGE(pck_packer.pck_start(output_pck_path, 32, "") != OK, "PCK with invalid key should fail.");
ERR_PRINT_ON;
}
TEST_CASE("[PCKPacker] Pack a PCK file with some files and directories") {
PCKPacker pck_packer;
const String output_pck_path = TestUtils::get_temp_path("output_with_files.pck");
CHECK_MESSAGE(
pck_packer.pck_start(output_pck_path) == OK,
"Starting a PCK file should return an OK error code.");
const String base_dir = OS::get_singleton()->get_executable_path().get_base_dir();
CHECK_MESSAGE(
pck_packer.add_file("version.py", base_dir.path_join("../version.py"), "version.py") == OK,
"Adding a file to the PCK should return an OK error code.");
CHECK_MESSAGE(
pck_packer.add_file("some/directories with spaces/to/create/icon.png", base_dir.path_join("../icon.png")) == OK,
"Adding a file to a new subdirectory in the PCK should return an OK error code.");
CHECK_MESSAGE(
pck_packer.add_file("some/directories with spaces/to/create/icon.svg", base_dir.path_join("../icon.svg")) == OK,
"Adding a file to an existing subdirectory in the PCK should return an OK error code.");
CHECK_MESSAGE(
pck_packer.add_file("some/directories with spaces/to/create/icon.png", base_dir.path_join("../logo.png")) == OK,
"Overriding a non-flushed file to an existing subdirectory in the PCK should return an OK error code.");
CHECK_MESSAGE(
pck_packer.flush() == OK,
"Flushing the PCK should return an OK error code.");
Error err;
Ref<FileAccess> f = FileAccess::open(output_pck_path, FileAccess::READ, &err);
CHECK_MESSAGE(
err == OK,
"The generated non-empty PCK file should be opened successfully.");
CHECK_MESSAGE(
f->get_length() >= 18000,
"The generated non-empty PCK file should be large enough to actually hold the contents specified above.");
CHECK_MESSAGE(
f->get_length() <= 27000,
"The generated non-empty PCK file shouldn't be too large.");
}
} // namespace TestPCKPacker
#endif // TEST_PCK_PACKER_H

View file

@ -0,0 +1,171 @@
/**************************************************************************/
/* test_resource.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_H
#define TEST_RESOURCE_H
#include "core/io/resource.h"
#include "core/io/resource_loader.h"
#include "core/io/resource_saver.h"
#include "core/os/os.h"
#include "thirdparty/doctest/doctest.h"
#include "tests/test_macros.h"
namespace TestResource {
TEST_CASE("[Resource] Duplication") {
Ref<Resource> resource = memnew(Resource);
resource->set_name("Hello world");
Ref<Resource> child_resource = memnew(Resource);
child_resource->set_name("I'm a child resource");
resource->set_meta("other_resource", child_resource);
Ref<Resource> resource_dupe = resource->duplicate();
const Ref<Resource> &resource_dupe_reference = resource_dupe;
resource_dupe->set_name("Changed name");
child_resource->set_name("My name was changed too");
CHECK_MESSAGE(
resource_dupe->get_name() == "Changed name",
"Duplicated resource should have the new name.");
CHECK_MESSAGE(
resource_dupe_reference->get_name() == "Changed name",
"Reference to the duplicated resource should have the new name.");
CHECK_MESSAGE(
resource->get_name() == "Hello world",
"Original resource name should not be affected after editing the duplicate's name.");
CHECK_MESSAGE(
Ref<Resource>(resource_dupe->get_meta("other_resource"))->get_name() == "My name was changed too",
"Duplicated resource should share its child resource with the original.");
}
TEST_CASE("[Resource] Saving and loading") {
Ref<Resource> resource = memnew(Resource);
resource->set_name("Hello world");
resource->set_meta("ExampleMetadata", Vector2i(40, 80));
resource->set_meta("string", "The\nstring\nwith\nunnecessary\nline\n\t\\\nbreaks");
Ref<Resource> child_resource = memnew(Resource);
child_resource->set_name("I'm a child resource");
resource->set_meta("other_resource", child_resource);
const String save_path_binary = TestUtils::get_temp_path("resource.res");
const String save_path_text = TestUtils::get_temp_path("resource.tres");
ResourceSaver::save(resource, save_path_binary);
ResourceSaver::save(resource, save_path_text);
const Ref<Resource> &loaded_resource_binary = ResourceLoader::load(save_path_binary);
CHECK_MESSAGE(
loaded_resource_binary->get_name() == "Hello world",
"The loaded resource name should be equal to the expected value.");
CHECK_MESSAGE(
loaded_resource_binary->get_meta("ExampleMetadata") == Vector2i(40, 80),
"The loaded resource metadata should be equal to the expected value.");
CHECK_MESSAGE(
loaded_resource_binary->get_meta("string") == "The\nstring\nwith\nunnecessary\nline\n\t\\\nbreaks",
"The loaded resource metadata should be equal to the expected value.");
const Ref<Resource> &loaded_child_resource_binary = loaded_resource_binary->get_meta("other_resource");
CHECK_MESSAGE(
loaded_child_resource_binary->get_name() == "I'm a child resource",
"The loaded child resource name should be equal to the expected value.");
const Ref<Resource> &loaded_resource_text = ResourceLoader::load(save_path_text);
CHECK_MESSAGE(
loaded_resource_text->get_name() == "Hello world",
"The loaded resource name should be equal to the expected value.");
CHECK_MESSAGE(
loaded_resource_text->get_meta("ExampleMetadata") == Vector2i(40, 80),
"The loaded resource metadata should be equal to the expected value.");
CHECK_MESSAGE(
loaded_resource_text->get_meta("string") == "The\nstring\nwith\nunnecessary\nline\n\t\\\nbreaks",
"The loaded resource metadata should be equal to the expected value.");
const Ref<Resource> &loaded_child_resource_text = loaded_resource_text->get_meta("other_resource");
CHECK_MESSAGE(
loaded_child_resource_text->get_name() == "I'm a child resource",
"The loaded child resource name should be equal to the expected value.");
}
TEST_CASE("[Resource] Breaking circular references on save") {
Ref<Resource> resource_a = memnew(Resource);
resource_a->set_name("A");
Ref<Resource> resource_b = memnew(Resource);
resource_b->set_name("B");
Ref<Resource> resource_c = memnew(Resource);
resource_c->set_name("C");
resource_a->set_meta("next", resource_b);
resource_b->set_meta("next", resource_c);
resource_c->set_meta("next", resource_b);
const String save_path_binary = TestUtils::get_temp_path("resource.res");
const String save_path_text = TestUtils::get_temp_path("resource.tres");
ResourceSaver::save(resource_a, save_path_binary);
// Suppress expected errors caused by the resources above being uncached.
ERR_PRINT_OFF;
ResourceSaver::save(resource_a, save_path_text);
const Ref<Resource> &loaded_resource_a_binary = ResourceLoader::load(save_path_binary);
ERR_PRINT_ON;
CHECK_MESSAGE(
loaded_resource_a_binary->get_name() == "A",
"The loaded resource name should be equal to the expected value.");
const Ref<Resource> &loaded_resource_b_binary = loaded_resource_a_binary->get_meta("next");
CHECK_MESSAGE(
loaded_resource_b_binary->get_name() == "B",
"The loaded child resource name should be equal to the expected value.");
const Ref<Resource> &loaded_resource_c_binary = loaded_resource_b_binary->get_meta("next");
CHECK_MESSAGE(
loaded_resource_c_binary->get_name() == "C",
"The loaded child resource name should be equal to the expected value.");
CHECK_MESSAGE(
!loaded_resource_c_binary->has_meta("next"),
"The loaded child resource circular reference should be NULL.");
const Ref<Resource> &loaded_resource_a_text = ResourceLoader::load(save_path_text);
CHECK_MESSAGE(
loaded_resource_a_text->get_name() == "A",
"The loaded resource name should be equal to the expected value.");
const Ref<Resource> &loaded_resource_b_text = loaded_resource_a_text->get_meta("next");
CHECK_MESSAGE(
loaded_resource_b_text->get_name() == "B",
"The loaded child resource name should be equal to the expected value.");
const Ref<Resource> &loaded_resource_c_text = loaded_resource_b_text->get_meta("next");
CHECK_MESSAGE(
loaded_resource_c_text->get_name() == "C",
"The loaded child resource name should be equal to the expected value.");
CHECK_MESSAGE(
!loaded_resource_c_text->has_meta("next"),
"The loaded child resource circular reference should be NULL.");
// Break circular reference to avoid memory leak
resource_c->remove_meta("next");
}
} // namespace TestResource
#endif // TEST_RESOURCE_H

View file

@ -0,0 +1,235 @@
/**************************************************************************/
/* test_xml_parser.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_XML_PARSER_H
#define TEST_XML_PARSER_H
#include "core/io/xml_parser.h"
#include "tests/test_macros.h"
namespace TestXMLParser {
TEST_CASE("[XMLParser] End-to-end") {
String source = "<?xml version = \"1.0\" encoding=\"UTF-8\" ?>\
<top attr=\"attr value\">\
Text&lt;&#65;&#x42;&gt;\
</top>";
Vector<uint8_t> buff = source.to_utf8_buffer();
XMLParser parser;
parser.open_buffer(buff);
// <?xml ...?> gets parsed as NODE_UNKNOWN
CHECK(parser.read() == OK);
CHECK(parser.get_node_type() == XMLParser::NodeType::NODE_UNKNOWN);
CHECK(parser.read() == OK);
CHECK(parser.get_node_type() == XMLParser::NodeType::NODE_ELEMENT);
CHECK(parser.get_node_name() == "top");
CHECK(parser.has_attribute("attr"));
CHECK(parser.get_named_attribute_value("attr") == "attr value");
CHECK(parser.read() == OK);
CHECK(parser.get_node_type() == XMLParser::NodeType::NODE_TEXT);
CHECK(parser.get_node_data().lstrip(" \t") == "Text<AB>");
CHECK(parser.read() == OK);
CHECK(parser.get_node_type() == XMLParser::NodeType::NODE_ELEMENT_END);
CHECK(parser.get_node_name() == "top");
parser.close();
}
TEST_CASE("[XMLParser] Comments") {
XMLParser parser;
SUBCASE("Missing end of comment") {
const String input = "<first></first><!-- foo";
REQUIRE_EQ(parser.open_buffer(input.to_utf8_buffer()), OK);
REQUIRE_EQ(parser.read(), OK);
REQUIRE_EQ(parser.get_node_type(), XMLParser::NodeType::NODE_ELEMENT);
REQUIRE_EQ(parser.read(), OK);
REQUIRE_EQ(parser.get_node_type(), XMLParser::NodeType::NODE_ELEMENT_END);
REQUIRE_EQ(parser.read(), OK);
CHECK_EQ(parser.get_node_type(), XMLParser::NodeType::NODE_COMMENT);
CHECK_EQ(parser.get_node_name(), " foo");
}
SUBCASE("Bad start of comment") {
const String input = "<first></first><!-";
REQUIRE_EQ(parser.open_buffer(input.to_utf8_buffer()), OK);
REQUIRE_EQ(parser.read(), OK);
REQUIRE_EQ(parser.get_node_type(), XMLParser::NodeType::NODE_ELEMENT);
REQUIRE_EQ(parser.read(), OK);
REQUIRE_EQ(parser.get_node_type(), XMLParser::NodeType::NODE_ELEMENT_END);
REQUIRE_EQ(parser.read(), OK);
CHECK_EQ(parser.get_node_type(), XMLParser::NodeType::NODE_COMMENT);
CHECK_EQ(parser.get_node_name(), "-");
}
SUBCASE("Unblanced angle brackets in comment") {
const String input = "<!-- example << --><next-tag></next-tag>";
REQUIRE_EQ(parser.open_buffer(input.to_utf8_buffer()), OK);
REQUIRE_EQ(parser.read(), OK);
CHECK_EQ(parser.get_node_type(), XMLParser::NodeType::NODE_COMMENT);
CHECK_EQ(parser.get_node_name(), " example << ");
}
SUBCASE("Doctype") {
const String input = "<!DOCTYPE greeting [<!ELEMENT greeting (#PCDATA)>]>";
REQUIRE_EQ(parser.open_buffer(input.to_utf8_buffer()), OK);
REQUIRE_EQ(parser.read(), OK);
CHECK_EQ(parser.get_node_type(), XMLParser::NodeType::NODE_COMMENT);
CHECK_EQ(parser.get_node_name(), "DOCTYPE greeting [<!ELEMENT greeting (#PCDATA)>]");
}
}
TEST_CASE("[XMLParser] Premature endings") {
SUBCASE("Simple cases") {
String input;
String expected_name;
XMLParser::NodeType expected_type;
SUBCASE("Incomplete Unknown") {
input = "<first></first><?xml";
expected_type = XMLParser::NodeType::NODE_UNKNOWN;
expected_name = "?xml";
}
SUBCASE("Incomplete CDStart") {
input = "<first></first><![CD";
expected_type = XMLParser::NodeType::NODE_CDATA;
expected_name = "";
}
SUBCASE("Incomplete CData") {
input = "<first></first><![CDATA[example";
expected_type = XMLParser::NodeType::NODE_CDATA;
expected_name = "example";
}
SUBCASE("Incomplete CDEnd") {
input = "<first></first><![CDATA[example]]";
expected_type = XMLParser::NodeType::NODE_CDATA;
expected_name = "example]]";
}
SUBCASE("Incomplete start-tag name") {
input = "<first></first><second";
expected_type = XMLParser::NodeType::NODE_ELEMENT;
expected_name = "second";
}
XMLParser parser;
REQUIRE_EQ(parser.open_buffer(input.to_utf8_buffer()), OK);
REQUIRE_EQ(parser.read(), OK);
REQUIRE_EQ(parser.get_node_type(), XMLParser::NodeType::NODE_ELEMENT);
REQUIRE_EQ(parser.read(), OK);
REQUIRE_EQ(parser.get_node_type(), XMLParser::NodeType::NODE_ELEMENT_END);
REQUIRE_EQ(parser.read(), OK);
CHECK_EQ(parser.get_node_type(), expected_type);
CHECK_EQ(parser.get_node_name(), expected_name);
}
SUBCASE("With attributes and texts") {
XMLParser parser;
SUBCASE("Incomplete start-tag attribute name") {
const String input = "<first></first><second attr1=\"foo\" attr2";
REQUIRE_EQ(parser.open_buffer(input.to_utf8_buffer()), OK);
REQUIRE_EQ(parser.read(), OK);
REQUIRE_EQ(parser.read(), OK);
REQUIRE_EQ(parser.read(), OK);
CHECK_EQ(parser.get_node_type(), XMLParser::NodeType::NODE_ELEMENT);
CHECK_EQ(parser.get_node_name(), "second");
CHECK_EQ(parser.get_attribute_count(), 1);
CHECK_EQ(parser.get_attribute_name(0), "attr1");
CHECK_EQ(parser.get_attribute_value(0), "foo");
}
SUBCASE("Incomplete start-tag attribute unquoted value") {
const String input = "<first></first><second attr1=\"foo\" attr2=bar";
REQUIRE_EQ(parser.open_buffer(input.to_utf8_buffer()), OK);
REQUIRE_EQ(parser.read(), OK);
REQUIRE_EQ(parser.read(), OK);
REQUIRE_EQ(parser.read(), OK);
CHECK_EQ(parser.get_node_type(), XMLParser::NodeType::NODE_ELEMENT);
CHECK_EQ(parser.get_node_name(), "second");
CHECK_EQ(parser.get_attribute_count(), 1);
CHECK_EQ(parser.get_attribute_name(0), "attr1");
CHECK_EQ(parser.get_attribute_value(0), "foo");
}
SUBCASE("Incomplete start-tag attribute quoted value") {
const String input = "<first></first><second attr1=\"foo\" attr2=\"bar";
REQUIRE_EQ(parser.open_buffer(input.to_utf8_buffer()), OK);
REQUIRE_EQ(parser.read(), OK);
REQUIRE_EQ(parser.read(), OK);
REQUIRE_EQ(parser.read(), OK);
CHECK_EQ(parser.get_node_type(), XMLParser::NodeType::NODE_ELEMENT);
CHECK_EQ(parser.get_node_name(), "second");
CHECK_EQ(parser.get_attribute_count(), 2);
CHECK_EQ(parser.get_attribute_name(0), "attr1");
CHECK_EQ(parser.get_attribute_value(0), "foo");
CHECK_EQ(parser.get_attribute_name(1), "attr2");
CHECK_EQ(parser.get_attribute_value(1), "bar");
}
SUBCASE("Incomplete end-tag name") {
const String input = "<first></fir";
REQUIRE_EQ(parser.open_buffer(input.to_utf8_buffer()), OK);
REQUIRE_EQ(parser.read(), OK);
REQUIRE_EQ(parser.read(), OK);
CHECK_EQ(parser.get_node_type(), XMLParser::NodeType::NODE_ELEMENT_END);
CHECK_EQ(parser.get_node_name(), "fir");
}
SUBCASE("Trailing text") {
const String input = "<first></first>example";
REQUIRE_EQ(parser.open_buffer(input.to_utf8_buffer()), OK);
REQUIRE_EQ(parser.read(), OK);
REQUIRE_EQ(parser.read(), OK);
REQUIRE_EQ(parser.read(), OK);
CHECK_EQ(parser.get_node_type(), XMLParser::NodeType::NODE_TEXT);
CHECK_EQ(parser.get_node_data(), "example");
}
}
}
TEST_CASE("[XMLParser] CDATA") {
const String input = "<a><![CDATA[my cdata content goes here]]></a>";
XMLParser parser;
REQUIRE_EQ(parser.open_buffer(input.to_utf8_buffer()), OK);
REQUIRE_EQ(parser.read(), OK);
CHECK_EQ(parser.get_node_type(), XMLParser::NodeType::NODE_ELEMENT);
CHECK_EQ(parser.get_node_name(), "a");
REQUIRE_EQ(parser.read(), OK);
CHECK_EQ(parser.get_node_type(), XMLParser::NodeType::NODE_CDATA);
CHECK_EQ(parser.get_node_name(), "my cdata content goes here");
REQUIRE_EQ(parser.read(), OK);
CHECK_EQ(parser.get_node_type(), XMLParser::NodeType::NODE_ELEMENT_END);
CHECK_EQ(parser.get_node_name(), "a");
}
} // namespace TestXMLParser
#endif // TEST_XML_PARSER_H