summaryrefslogtreecommitdiff
path: root/tests/core/io
diff options
context:
space:
mode:
Diffstat (limited to 'tests/core/io')
-rw-r--r--tests/core/io/test_config_file.h164
-rw-r--r--tests/core/io/test_file_access.h85
-rw-r--r--tests/core/io/test_image.h303
-rw-r--r--tests/core/io/test_json.h151
-rw-r--r--tests/core/io/test_marshalls.h329
-rw-r--r--tests/core/io/test_pck_packer.h122
-rw-r--r--tests/core/io/test_resource.h114
-rw-r--r--tests/core/io/test_xml_parser.h71
8 files changed, 1339 insertions, 0 deletions
diff --git a/tests/core/io/test_config_file.h b/tests/core/io/test_config_file.h
new file mode 100644
index 0000000000..f6fbaf9a88
--- /dev/null
+++ b/tests/core/io/test_config_file.h
@@ -0,0 +1,164 @@
+/*************************************************************************/
+/* test_config_file.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#ifndef TEST_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").plus_file("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
+)");
+
+ FileAccessRef 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
diff --git a/tests/core/io/test_file_access.h b/tests/core/io/test_file_access.h
new file mode 100644
index 0000000000..4ffc57afe4
--- /dev/null
+++ b/tests/core/io/test_file_access.h
@@ -0,0 +1,85 @@
+/*************************************************************************/
+/* test_file_access.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#ifndef TEST_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") {
+ FileAccessRef f = FileAccess::open(TestUtils::get_data_path("translations.csv"), FileAccess::READ);
+
+ Vector<String> header = f->get_csv_line(); // Default delimiter: ",".
+ REQUIRE(header.size() == 3);
+
+ Vector<String> row1 = f->get_csv_line(","); // Explicit delimiter, should be the same.
+ REQUIRE(row1.size() == 3);
+ CHECK(row1[0] == "GOOD_MORNING");
+ CHECK(row1[1] == "Good Morning");
+ CHECK(row1[2] == "Guten Morgen");
+
+ Vector<String> row2 = f->get_csv_line();
+ REQUIRE(row2.size() == 3);
+ CHECK(row2[0] == "GOOD_EVENING");
+ CHECK(row2[1] == "Good Evening");
+ CHECK(row2[2] == ""); // 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.");
+
+ 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?");
+
+ f->close();
+}
+} // namespace TestFileAccess
+
+#endif // TEST_FILE_ACCESS_H
diff --git a/tests/core/io/test_image.h b/tests/core/io/test_image.h
new file mode 100644
index 0000000000..643d2f31ec
--- /dev/null
+++ b/tests/core/io/test_image.h
@@ -0,0 +1,303 @@
+/*************************************************************************/
+/* test_image.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#ifndef TEST_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"
+
+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 = OS::get_singleton()->get_cache_path().plus_file("image.png");
+ const String save_path_exr = OS::get_singleton()->get_cache_path().plus_file("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.");
+
+ // Save EXR
+ err = image->save_exr(save_path_exr, false);
+ CHECK_MESSAGE(
+ err == OK,
+ "The image should be saved successfully as an .exr file.");
+
+ // 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.");
+
+ // Load BMP
+ Ref<Image> image_bmp = memnew(Image());
+ FileAccessRef f_bmp = FileAccess::open(TestUtils::get_data_path("images/icon.bmp"), FileAccess::READ, &err);
+ 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.");
+
+ // Load JPG
+ Ref<Image> image_jpg = memnew(Image());
+ FileAccessRef f_jpg = FileAccess::open(TestUtils::get_data_path("images/icon.jpg"), FileAccess::READ, &err);
+ 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.");
+
+ // Load WEBP
+ Ref<Image> image_webp = memnew(Image());
+ FileAccessRef f_webp = FileAccess::open(TestUtils::get_data_path("images/icon.webp"), FileAccess::READ, &err);
+ 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.");
+
+ // Load PNG
+ Ref<Image> image_png = memnew(Image());
+ FileAccessRef f_png = FileAccess::open(TestUtils::get_data_path("images/icon.png"), FileAccess::READ, &err);
+ 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.");
+
+ // Load TGA
+ Ref<Image> image_tga = memnew(Image());
+ FileAccessRef f_tga = FileAccess::open(TestUtils::get_data_path("images/icon.tga"), FileAccess::READ, &err);
+ 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.");
+}
+
+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() == Rect2(0, 0, 0, 0));
+ Ref<Image> image_get_rect = image->get_rect(Rect2(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() == Rect2(0, 0, 1, 1),
+ "Image's get_used_rect should return the expected value, larger than Rect2(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<Rect2> rects;
+ rects.push_back(Rect2());
+ rects.push_back(Rect2(-5, -5, 3, 3));
+ rects.push_back(Rect2(img_width, 0, 12, 12));
+ rects.push_back(Rect2(0, img_height, 12, 12));
+ rects.push_back(Rect2(img_width + 1, img_height + 2, 12, 12));
+ rects.push_back(Rect2(1, 1, 1, 1));
+ rects.push_back(Rect2(0, 1, 2, 3));
+ rects.push_back(Rect2(-5, 0, img_width + 10, 2));
+ rects.push_back(Rect2(0, -5, 2, img_height + 10));
+ rects.push_back(Rect2(-1, -1, img_width + 1, img_height + 1));
+
+ for (const Rect2 &rect : rects) {
+ Ref<Image> img = memnew(Image(img_width, img_height, false, Image::FORMAT_RGBA8));
+ CHECK_NOTHROW_MESSAGE(
+ img->fill_rect(rect, Color(1, 1, 1, 1)),
+ "fill_rect() shouldn't throw for any rect.");
+ 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, Rect2(Vector2(0, 0), image2->get_size()), Vector2(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, Rect2(Vector2(0, 0), image3->get_size()), Vector2(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.");
+}
+} // namespace TestImage
+#endif // TEST_IMAGE_H
diff --git a/tests/core/io/test_json.h b/tests/core/io/test_json.h
new file mode 100644
index 0000000000..3af58dfa1c
--- /dev/null
+++ b/tests/core/io/test_json.h
@@ -0,0 +1,151 @@
+/*************************************************************************/
+/* test_json.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#ifndef TEST_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(
+ Math::is_equal_approx(double(json.get_data()), 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.");
+}
+} // namespace TestJSON
+
+#endif // TEST_JSON_H
diff --git a/tests/core/io/test_marshalls.h b/tests/core/io/test_marshalls.h
new file mode 100644
index 0000000000..6bd916164e
--- /dev/null
+++ b/tests/core/io/test_marshalls.h
@@ -0,0 +1,329 @@
+/*************************************************************************/
+/* test_marshalls.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#ifndef TEST_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 Variant::Type");
+ 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 Variant::Type + 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 Variant::Type + 8 bytes for int64_t");
+ CHECK_MESSAGE(buffer[0] == 0x02, "Variant::INT");
+ CHECK(buffer[1] == 0x00);
+ CHECK_MESSAGE(buffer[2] == 0x01, "ENCODE_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 Variant::Type + 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 Variant::Type + 8 bytes for double");
+ CHECK_MESSAGE(buffer[0] == 0x03, "Variant::FLOAT");
+ CHECK(buffer[1] == 0x00);
+ CHECK_MESSAGE(buffer[2] == 0x01, "ENCODE_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
+
+ 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);
+}
+
+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 & ENCODE_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 & ENCODE_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));
+}
+} // namespace TestMarshalls
+
+#endif // TEST_MARSHALLS_H
diff --git a/tests/core/io/test_pck_packer.h b/tests/core/io/test_pck_packer.h
new file mode 100644
index 0000000000..75a4abffbe
--- /dev/null
+++ b/tests/core/io/test_pck_packer.h
@@ -0,0 +1,122 @@
+/*************************************************************************/
+/* test_pck_packer.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#ifndef TEST_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 = OS::get_singleton()->get_cache_path().plus_file("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;
+ FileAccessRef 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 = OS::get_singleton()->get_cache_path().plus_file("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 = OS::get_singleton()->get_cache_path().plus_file("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 = OS::get_singleton()->get_cache_path().plus_file("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.plus_file("../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.plus_file("../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.plus_file("../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.plus_file("../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;
+ FileAccessRef 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() >= 25000,
+ "The generated non-empty PCK file should be large enough to actually hold the contents specified above.");
+ CHECK_MESSAGE(
+ f->get_length() <= 35000,
+ "The generated non-empty PCK file shouldn't be too large.");
+}
+} // namespace TestPCKPacker
+
+#endif // TEST_PCK_PACKER_H
diff --git a/tests/core/io/test_resource.h b/tests/core/io/test_resource.h
new file mode 100644
index 0000000000..cee3281995
--- /dev/null
+++ b/tests/core/io/test_resource.h
@@ -0,0 +1,114 @@
+/*************************************************************************/
+/* test_resource.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#ifndef TEST_RESOURCE
+#define TEST_RESOURCE
+
+#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"
+
+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 = OS::get_singleton()->get_cache_path().plus_file("resource.res");
+ const String save_path_text = OS::get_singleton()->get_cache_path().plus_file("resource.tres");
+ ResourceSaver::save(save_path_binary, resource);
+ ResourceSaver::save(save_path_text, resource);
+
+ 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.");
+}
+} // namespace TestResource
+
+#endif // TEST_RESOURCE
diff --git a/tests/core/io/test_xml_parser.h b/tests/core/io/test_xml_parser.h
new file mode 100644
index 0000000000..2d00f29ddf
--- /dev/null
+++ b/tests/core/io/test_xml_parser.h
@@ -0,0 +1,71 @@
+/*************************************************************************/
+/* test_xml_parser.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#ifndef TEST_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_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();
+}
+} // namespace TestXMLParser
+
+#endif // TEST_XML_PARSER_H