diff options
Diffstat (limited to 'tests/core/io')
-rw-r--r-- | tests/core/io/test_config_file.h | 164 | ||||
-rw-r--r-- | tests/core/io/test_file_access.h | 85 | ||||
-rw-r--r-- | tests/core/io/test_image.h | 303 | ||||
-rw-r--r-- | tests/core/io/test_json.h | 151 | ||||
-rw-r--r-- | tests/core/io/test_marshalls.h | 329 | ||||
-rw-r--r-- | tests/core/io/test_pck_packer.h | 122 | ||||
-rw-r--r-- | tests/core/io/test_resource.h | 114 | ||||
-rw-r--r-- | tests/core/io/test_xml_parser.h | 71 |
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<AB>\ +</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 |