Path: blob/master/tests/core/variant/test_dictionary.cpp
23450 views
/**************************************************************************/1/* test_dictionary.cpp */2/**************************************************************************/3/* This file is part of: */4/* GODOT ENGINE */5/* https://godotengine.org */6/**************************************************************************/7/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */8/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */9/* */10/* Permission is hereby granted, free of charge, to any person obtaining */11/* a copy of this software and associated documentation files (the */12/* "Software"), to deal in the Software without restriction, including */13/* without limitation the rights to use, copy, modify, merge, publish, */14/* distribute, sublicense, and/or sell copies of the Software, and to */15/* permit persons to whom the Software is furnished to do so, subject to */16/* the following conditions: */17/* */18/* The above copyright notice and this permission notice shall be */19/* included in all copies or substantial portions of the Software. */20/* */21/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */22/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */23/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */24/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */25/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */26/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */27/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */28/**************************************************************************/2930#include "tests/test_macros.h"3132TEST_FORCE_LINK(test_dictionary)3334#include "core/object/ref_counted.h"35#include "core/variant/typed_dictionary.h"3637namespace TestDictionary {3839TEST_CASE("[Dictionary] Assignment using bracket notation ([])") {40Dictionary map;41map["Hello"] = 0;42CHECK(int(map["Hello"]) == 0);43map["Hello"] = 3;44CHECK(int(map["Hello"]) == 3);45map["World!"] = 4;46CHECK(int(map["World!"]) == 4);4748map[StringName("HelloName")] = 6;49CHECK(int(map[StringName("HelloName")]) == 6);50CHECK(int(map.find_key(6).get_type()) == Variant::STRING_NAME);51map[StringName("HelloName")] = 7;52CHECK(int(map[StringName("HelloName")]) == 7);5354// Test String and StringName are equivalent.55map[StringName("Hello")] = 8;56CHECK(int(map["Hello"]) == 8);57map["Hello"] = 9;58CHECK(int(map[StringName("Hello")]) == 9);5960// Test non-string keys, since keys can be of any Variant type.61map[12345] = -5;62CHECK(int(map[12345]) == -5);63map[false] = 128;64CHECK(int(map[false]) == 128);65map[Vector2(10, 20)] = 30;66CHECK(int(map[Vector2(10, 20)]) == 30);67map[0] = 400;68CHECK(int(map[0]) == 400);69// Check that assigning 0 doesn't overwrite the value for `false`.70CHECK(int(map[false]) == 128);7172// Ensure read-only maps aren't modified by non-existing keys.73const int length = map.size();74map.make_read_only();75CHECK(int(map["This key does not exist"].get_type()) == Variant::NIL);76CHECK(map.size() == length);77}7879TEST_CASE("[Dictionary] List init") {80Dictionary dict{81{ 0, "int" },82{ "packed_string_array", PackedStringArray({ "array", "of", "values" }) },83{ "key", Dictionary({ { "nested", 200 } }) },84{ Vector2(), "v2" },85};86CHECK(dict.size() == 4);87CHECK(dict[0] == "int");88CHECK(PackedStringArray(dict["packed_string_array"])[2] == "values");89CHECK(Dictionary(dict["key"])["nested"] == Variant(200));90CHECK(dict[Vector2()] == "v2");9192TypedDictionary<double, double> tdict{93{ 0.0, 1.0 },94{ 5.0, 2.0 },95};96CHECK_EQ(tdict[0.0], Variant(1.0));97CHECK_EQ(tdict[5.0], Variant(2.0));98}99100TEST_CASE("[Dictionary] get_key_list()") {101Dictionary map;102LocalVector<Variant> keys;103keys = map.get_key_list();104CHECK(keys.is_empty());105map[1] = 3;106keys = map.get_key_list();107CHECK(keys.size() == 1);108CHECK(int(keys[0]) == 1);109map[2] = 4;110keys = map.get_key_list();111CHECK(keys.size() == 2);112}113114TEST_CASE("[Dictionary] get_key_at_index()") {115Dictionary map;116map[4] = 3;117Variant val = map.get_key_at_index(0);118CHECK(int(val) == 4);119map[3] = 1;120val = map.get_key_at_index(0);121CHECK(int(val) == 4);122val = map.get_key_at_index(1);123CHECK(int(val) == 3);124}125126TEST_CASE("[Dictionary] getptr()") {127Dictionary map;128map[1] = 3;129Variant *key = map.getptr(1);130CHECK(int(*key) == 3);131key = map.getptr(2);132CHECK(key == nullptr);133}134135TEST_CASE("[Dictionary] get_valid()") {136Dictionary map;137map[1] = 3;138Variant val = map.get_valid(1);139CHECK(int(val) == 3);140}141142TEST_CASE("[Dictionary] set(), get(), and get_or_add()") {143Dictionary map;144145map.set(1, 3);146Variant val = map.get(1, -1);147CHECK(int(val) == 3);148149map.set(1, 5);150val = map.get(1, -1);151CHECK(int(val) == 5);152153CHECK(int(map.get_or_add(1, 7)) == 5);154CHECK(int(map.get_or_add(2, 7)) == 7);155}156157TEST_CASE("[Dictionary] make_read_only() and is_read_only()") {158Dictionary map;159CHECK_FALSE(map.is_read_only());160CHECK(map.set(1, 1));161162map.make_read_only();163CHECK(map.is_read_only());164165ERR_PRINT_OFF;166CHECK_FALSE(map.set(1, 2));167ERR_PRINT_ON;168}169170TEST_CASE("[Dictionary] size(), is_empty() and clear()") {171Dictionary map;172CHECK(map.size() == 0);173CHECK(map.is_empty());174map[1] = 3;175CHECK(map.size() == 1);176CHECK(!map.is_empty());177map.clear();178CHECK(map.size() == 0);179CHECK(map.is_empty());180}181182TEST_CASE("[Dictionary] has() and has_all()") {183Dictionary map;184CHECK(map.has(1) == false);185map[1] = 3;186CHECK(map.has(1));187Array keys;188keys.push_back(1);189CHECK(map.has_all(keys));190keys.push_back(2);191CHECK(map.has_all(keys) == false);192}193194TEST_CASE("[Dictionary] keys() and values()") {195Dictionary map;196Array keys = map.keys();197Array values = map.values();198CHECK(keys.is_empty());199CHECK(values.is_empty());200map[1] = 3;201keys = map.keys();202values = map.values();203CHECK(int(keys[0]) == 1);204CHECK(int(values[0]) == 3);205}206207TEST_CASE("[Dictionary] merge() and merged()") {208Dictionary d1 = {209{ "key1", 1 },210{ "key2", 2 },211};212Dictionary d2 = {213{ "key2", 200 },214{ "key3", 300 },215};216Dictionary expected_no_overwrite = {217{ "key1", 1 },218{ "key2", 2 },219{ "key3", 300 },220};221Dictionary expected_overwrite = {222{ "key1", 1 },223{ "key2", 200 },224{ "key3", 300 },225};226227Dictionary d_test = d1.duplicate();228d_test.merge(d2, false);229CHECK_EQ(d_test, expected_no_overwrite);230231d_test = d1.duplicate();232d_test.merge(d2, true);233CHECK_EQ(d_test, expected_overwrite);234235CHECK_EQ(d1.merged(d2, false), expected_no_overwrite);236CHECK_EQ(d1.merged(d2, true), expected_overwrite);237}238239TEST_CASE("[Dictionary] Duplicate dictionary") {240// d = {1: {1: 1}, {2: 2}: [2], [3]: 3}241Dictionary k2 = { { 2, 2 } };242Array k3 = { 3 };243Dictionary d = {244{ 1, Dictionary({ { 1, 1 } }) },245{ k2, Array({ 2 }) },246{ k3, 3 }247};248249// Deep copy250Dictionary deep_d = d.duplicate(true);251CHECK_MESSAGE(deep_d.id() != d.id(), "Should create a new dictionary");252CHECK_MESSAGE(Dictionary(deep_d[1]).id() != Dictionary(d[1]).id(), "Should clone nested dictionary");253CHECK_MESSAGE(Array(deep_d[k2]).id() != Array(d[k2]).id(), "Should clone nested array");254CHECK_EQ(deep_d, d);255256// Check that duplicate_deep matches duplicate(true)257Dictionary deep_d2 = d.duplicate_deep();258CHECK_EQ(deep_d, deep_d2);259260deep_d[0] = 0;261CHECK_NE(deep_d, d);262deep_d.erase(0);263Dictionary(deep_d[1]).operator[](0) = 0;264CHECK_NE(deep_d, d);265Dictionary(deep_d[1]).erase(0);266CHECK_EQ(deep_d, d);267// Keys should also be copied268k2[0] = 0;269CHECK_NE(deep_d, d);270k2.erase(0);271CHECK_EQ(deep_d, d);272k3.push_back(0);273CHECK_NE(deep_d, d);274k3.pop_back();275CHECK_EQ(deep_d, d);276277// Shallow copy278Dictionary shallow_d = d.duplicate(false);279CHECK_MESSAGE(shallow_d.id() != d.id(), "Should create a new array");280CHECK_MESSAGE(Dictionary(shallow_d[1]).id() == Dictionary(d[1]).id(), "Should keep nested dictionary");281CHECK_MESSAGE(Array(shallow_d[k2]).id() == Array(d[k2]).id(), "Should keep nested array");282CHECK_EQ(shallow_d, d);283shallow_d[0] = 0;284CHECK_NE(shallow_d, d);285shallow_d.erase(0);286#if 0 // TODO: recursion in dict key currently is buggy287// Keys should also be shallowed288k2[0] = 0;289CHECK_EQ(shallow_d, d);290k2.erase(0);291k3.push_back(0);292CHECK_EQ(shallow_d, d);293#endif294}295296TEST_CASE("[Dictionary] Duplicate recursive dictionary") {297// Self recursive298Dictionary d;299d[1] = d;300301Dictionary d_shallow = d.duplicate(false);302CHECK_EQ(d, d_shallow);303304// Deep copy of recursive dictionary endup with recursion limit and return305// an invalid result (multiple nested dictionaries), the point is we should306// not end up with a segfault and an error log should be printed307ERR_PRINT_OFF;308d.duplicate(true);309ERR_PRINT_ON;310311// Nested recursive312Dictionary d1;313Dictionary d2;314d1[2] = d2;315d2[1] = d1;316317Dictionary d1_shallow = d1.duplicate(false);318CHECK_EQ(d1, d1_shallow);319320// Same deep copy issue as above321ERR_PRINT_OFF;322d1.duplicate(true);323ERR_PRINT_ON;324325// Break the recursivity otherwise Dictionary teardown will leak memory326d.clear();327d1.clear();328d2.clear();329}330331#if 0 // TODO: duplicate recursion in dict key is currently buggy332TEST_CASE("[Dictionary] Duplicate recursive dictionary on keys") {333// Self recursive334Dictionary d;335d[d] = d;336337Dictionary d_shallow = d.duplicate(false);338CHECK_EQ(d, d_shallow);339340// Deep copy of recursive dictionary endup with recursion limit and return341// an invalid result (multiple nested dictionaries), the point is we should342// not end up with a segfault and an error log should be printed343ERR_PRINT_OFF;344d.duplicate(true);345ERR_PRINT_ON;346347// Nested recursive348Dictionary d1;349Dictionary d2;350d1[d2] = d2;351d2[d1] = d1;352353Dictionary d1_shallow = d1.duplicate(false);354CHECK_EQ(d1, d1_shallow);355356// Same deep copy issue as above357ERR_PRINT_OFF;358d1.duplicate(true);359ERR_PRINT_ON;360361// Break the recursivity otherwise Dictionary teardown will leak memory362d.clear();363d1.clear();364d2.clear();365}366#endif367368TEST_CASE("[Dictionary] Hash dictionary") {369// d = {1: {1: 1}, {2: 2}: [2], [3]: 3}370Dictionary k2 = { { 2, 2 } };371Array k3 = { 3 };372Dictionary d = {373{ 1, Dictionary({ { 1, 1 } }) },374{ k2, Array({ 2 }) },375{ k3, 3 }376};377uint32_t original_hash = d.hash();378379// Modify dict change the hash380d[0] = 0;381CHECK_NE(d.hash(), original_hash);382d.erase(0);383CHECK_EQ(d.hash(), original_hash);384385// Modify nested item change the hash386Dictionary(d[1]).operator[](0) = 0;387CHECK_NE(d.hash(), original_hash);388Dictionary(d[1]).erase(0);389Array(d[k2]).push_back(0);390CHECK_NE(d.hash(), original_hash);391Array(d[k2]).pop_back();392393// Modify a key change the hash394k2[0] = 0;395CHECK_NE(d.hash(), original_hash);396k2.erase(0);397CHECK_EQ(d.hash(), original_hash);398k3.push_back(0);399CHECK_NE(d.hash(), original_hash);400k3.pop_back();401CHECK_EQ(d.hash(), original_hash);402403// Duplication doesn't change the hash404Dictionary d2 = d.duplicate(true);405CHECK_EQ(d2.hash(), original_hash);406}407408TEST_CASE("[Dictionary] Hash recursive dictionary") {409Dictionary d;410d[1] = d;411412// Hash should reach recursion limit, we just make sure this doesn't blow up413ERR_PRINT_OFF;414d.hash();415ERR_PRINT_ON;416417// Break the recursivity otherwise Dictionary teardown will leak memory418d.clear();419}420421#if 0 // TODO: recursion in dict key is currently buggy422TEST_CASE("[Dictionary] Hash recursive dictionary on keys") {423Dictionary d;424d[d] = 1;425426// Hash should reach recursion limit, we just make sure this doesn't blow up427ERR_PRINT_OFF;428d.hash();429ERR_PRINT_ON;430431// Break the recursivity otherwise Dictionary teardown will leak memory432d.clear();433}434#endif435436TEST_CASE("[Dictionary] Empty comparison") {437Dictionary d1;438Dictionary d2;439440// test both operator== and operator!=441CHECK_EQ(d1, d2);442CHECK_FALSE(d1 != d2);443}444445TEST_CASE("[Dictionary] Flat comparison") {446Dictionary d1 = { { 1, 1 } };447Dictionary d2 = { { 1, 1 } };448Dictionary other_d = { { 2, 1 } };449450// test both operator== and operator!=451CHECK_EQ(d1, d1); // compare self452CHECK_FALSE(d1 != d1);453CHECK_EQ(d1, d2); // different equivalent arrays454CHECK_FALSE(d1 != d2);455CHECK_NE(d1, other_d); // different arrays with different content456CHECK_FALSE(d1 == other_d);457}458459TEST_CASE("[Dictionary] Nested dictionary comparison") {460// d1 = {1: {2: {3: 4}}}461Dictionary d1 = { { 1, Dictionary({ { 2, Dictionary({ { 3, 4 } }) } }) } };462463Dictionary d2 = d1.duplicate(true);464465// other_d = {1: {2: {3: 0}}}466Dictionary other_d = { { 1, Dictionary({ { 2, Dictionary({ { 3, 0 } }) } }) } };467468// test both operator== and operator!=469CHECK_EQ(d1, d1); // compare self470CHECK_FALSE(d1 != d1);471CHECK_EQ(d1, d2); // different equivalent arrays472CHECK_FALSE(d1 != d2);473CHECK_NE(d1, other_d); // different arrays with different content474CHECK_FALSE(d1 == other_d);475}476477TEST_CASE("[Dictionary] Nested array comparison") {478// d1 = {1: [2, 3]}479Dictionary d1 = { { 1, { 2, 3 } } };480481Dictionary d2 = d1.duplicate(true);482483// other_d = {1: [2, 0]}484Dictionary other_d = { { 1, { 2, 0 } } };485486// test both operator== and operator!=487CHECK_EQ(d1, d1); // compare self488CHECK_FALSE(d1 != d1);489CHECK_EQ(d1, d2); // different equivalent arrays490CHECK_FALSE(d1 != d2);491CHECK_NE(d1, other_d); // different arrays with different content492CHECK_FALSE(d1 == other_d);493}494495TEST_CASE("[Dictionary] Recursive comparison") {496Dictionary d1;497d1[1] = d1;498499Dictionary d2;500d2[1] = d2;501502// Comparison should reach recursion limit503ERR_PRINT_OFF;504CHECK_EQ(d1, d2);505CHECK_FALSE(d1 != d2);506ERR_PRINT_ON;507508d1[2] = 2;509d2[2] = 2;510511// Comparison should reach recursion limit512ERR_PRINT_OFF;513CHECK_EQ(d1, d2);514CHECK_FALSE(d1 != d2);515ERR_PRINT_ON;516517d1[3] = 3;518d2[3] = 0;519520// Comparison should reach recursion limit521ERR_PRINT_OFF;522CHECK_NE(d1, d2);523CHECK_FALSE(d1 == d2);524ERR_PRINT_ON;525526// Break the recursivity otherwise Dictionary teardown will leak memory527d1.clear();528d2.clear();529}530531#if 0 // TODO: recursion in dict key is currently buggy532TEST_CASE("[Dictionary] Recursive comparison on keys") {533Dictionary d1;534// Hash computation should reach recursion limit535ERR_PRINT_OFF;536d1[d1] = 1;537ERR_PRINT_ON;538539Dictionary d2;540// Hash computation should reach recursion limit541ERR_PRINT_OFF;542d2[d2] = 1;543ERR_PRINT_ON;544545// Comparison should reach recursion limit546ERR_PRINT_OFF;547CHECK_EQ(d1, d2);548CHECK_FALSE(d1 != d2);549ERR_PRINT_ON;550551d1[2] = 2;552d2[2] = 2;553554// Comparison should reach recursion limit555ERR_PRINT_OFF;556CHECK_EQ(d1, d2);557CHECK_FALSE(d1 != d2);558ERR_PRINT_ON;559560d1[3] = 3;561d2[3] = 0;562563// Comparison should reach recursion limit564ERR_PRINT_OFF;565CHECK_NE(d1, d2);566CHECK_FALSE(d1 == d2);567ERR_PRINT_ON;568569// Break the recursivity otherwise Dictionary teardown will leak memory570d1.clear();571d2.clear();572}573#endif574575TEST_CASE("[Dictionary] Recursive self comparison") {576Dictionary d1;577Dictionary d2;578d1[1] = d2;579d2[1] = d1;580581CHECK_EQ(d1, d1);582CHECK_FALSE(d1 != d1);583584// Break the recursivity otherwise Dictionary teardown will leak memory585d1.clear();586d2.clear();587}588589TEST_CASE("[Dictionary] Order and find") {590Dictionary d;591d[4] = "four";592d[8] = "eight";593d[12] = "twelve";594d["4"] = "four";595596Array keys = { 4, 8, 12, "4" };597598CHECK_EQ(d.keys(), keys);599CHECK_EQ(d.find_key("four"), Variant(4));600CHECK_EQ(d.find_key("does not exist"), Variant());601}602603TEST_CASE("[Dictionary] sort()") {604Dictionary d;605d[3] = 3;606d[2] = 2;607d[4] = 4;608d[1] = 1;609610Array expected_unsorted = { 3, 2, 4, 1 };611CHECK_EQ(d.keys(), expected_unsorted);612613d.sort();614Array expected_sorted = { 1, 2, 3, 4 };615CHECK_EQ(d.keys(), expected_sorted);616617Dictionary d_str;618d_str["b"] = 2;619d_str["c"] = 3;620d_str["a"] = 1;621622d_str.sort();623Array expected_str_sorted = { "a", "b", "c" };624CHECK_EQ(d_str.keys(), expected_str_sorted);625}626627TEST_CASE("[Dictionary] assign()") {628Dictionary untyped;629untyped["key1"] = "value";630CHECK(untyped.size() == 1);631632Dictionary typed;633typed.set_typed(Variant::STRING, StringName(), Variant(), Variant::STRING, StringName(), Variant());634typed.assign(untyped);635CHECK(typed.size() == 1);636typed["key2"] = "value";637638untyped.assign(typed);639CHECK(untyped.size() == 2);640untyped["key3"] = 5;641CHECK(untyped.size() == 3);642643ERR_PRINT_OFF;644typed.assign(untyped);645ERR_PRINT_ON;646CHECK(typed.size() == 2);647}648649TEST_CASE("[Dictionary] Typed copying") {650TypedDictionary<int, int> d1;651d1[0] = 1;652653TypedDictionary<double, double> d2;654d2[0] = 1.0;655656Dictionary d3 = d1;657TypedDictionary<int, int> d4 = d3;658659Dictionary d5 = d2;660TypedDictionary<int, int> d6 = d5;661662d3[0] = 2;663d4[0] = 3;664665// Same typed TypedDictionary should be shared.666CHECK_EQ(d1[0], Variant(3));667CHECK_EQ(d3[0], Variant(3));668CHECK_EQ(d4[0], Variant(3));669670d5[0] = 2.0;671d6[0] = 3.0;672673// Different typed TypedDictionary should not be shared.674CHECK_EQ(d2[0], Variant(2.0));675CHECK_EQ(d5[0], Variant(2.0));676CHECK_EQ(d6[0], Variant(3.0));677678d1.clear();679d2.clear();680d3.clear();681d4.clear();682d5.clear();683d6.clear();684}685686TEST_CASE("[Dictionary] Type checks/comparisons") {687Dictionary d1;688CHECK_FALSE(d1.is_typed());689CHECK_FALSE(d1.is_typed_key());690CHECK_FALSE(d1.is_typed_value());691692d1.set_typed(Variant::STRING, StringName(), Variant(), Variant::OBJECT, "Node", Variant());693CHECK(d1.is_typed());694CHECK(d1.is_typed_key());695CHECK(d1.is_typed_value());696CHECK_EQ(d1.get_typed_key_builtin(), Variant::STRING);697CHECK_EQ(d1.get_typed_value_builtin(), Variant::OBJECT);698CHECK_EQ(d1.get_typed_value_class_name(), "Node");699700Dictionary d2;701CHECK_FALSE(d1.is_same_typed(d2));702CHECK_FALSE(d1.is_same_typed_key(d2));703CHECK_FALSE(d1.is_same_typed_value(d2));704705d2.set_typed(Variant::STRING, StringName(), Variant(), Variant::STRING, StringName(), Variant());706CHECK_FALSE(d1.is_same_typed(d2));707CHECK(d1.is_same_typed_key(d2));708CHECK_FALSE(d1.is_same_typed_value(d2));709}710711TEST_CASE("[Dictionary] Iteration") {712Dictionary a1 = { { 1, 2 }, { 3, 4 }, { 5, 6 } };713Dictionary a2 = { { 1, 2 }, { 3, 4 }, { 5, 6 } };714715int idx = 0;716717for (const KeyValue<Variant, Variant> &kv : (const Dictionary &)a1) {718CHECK_EQ(int(a2[kv.key]), int(kv.value));719idx++;720}721722CHECK_EQ(idx, a1.size());723724a1.clear();725a2.clear();726}727728TEST_CASE("[Dictionary] Object value init") {729Object *a = memnew(Object);730Object *b = memnew(Object);731TypedDictionary<double, Object *> tdict = {732{ 0.0, a },733{ 5.0, b },734};735CHECK_EQ(tdict[0.0], Variant(a));736CHECK_EQ(tdict[5.0], Variant(b));737memdelete(a);738memdelete(b);739}740741TEST_CASE("[Dictionary] RefCounted value init") {742Ref<RefCounted> a = memnew(RefCounted);743Ref<RefCounted> b = memnew(RefCounted);744TypedDictionary<double, Ref<RefCounted>> tdict = {745{ 0.0, a },746{ 5.0, b },747};748CHECK_EQ(tdict[0.0], Variant(a));749CHECK_EQ(tdict[5.0], Variant(b));750}751752} // namespace TestDictionary753754755