Path: blob/master/tests/core/variant/test_dictionary.h
21240 views
/**************************************************************************/1/* test_dictionary.h */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#pragma once3132#include "core/variant/typed_dictionary.h"33#include "tests/test_macros.h"3435namespace TestDictionary {36TEST_CASE("[Dictionary] Assignment using bracket notation ([])") {37Dictionary map;38map["Hello"] = 0;39CHECK(int(map["Hello"]) == 0);40map["Hello"] = 3;41CHECK(int(map["Hello"]) == 3);42map["World!"] = 4;43CHECK(int(map["World!"]) == 4);4445map[StringName("HelloName")] = 6;46CHECK(int(map[StringName("HelloName")]) == 6);47CHECK(int(map.find_key(6).get_type()) == Variant::STRING_NAME);48map[StringName("HelloName")] = 7;49CHECK(int(map[StringName("HelloName")]) == 7);5051// Test String and StringName are equivalent.52map[StringName("Hello")] = 8;53CHECK(int(map["Hello"]) == 8);54map["Hello"] = 9;55CHECK(int(map[StringName("Hello")]) == 9);5657// Test non-string keys, since keys can be of any Variant type.58map[12345] = -5;59CHECK(int(map[12345]) == -5);60map[false] = 128;61CHECK(int(map[false]) == 128);62map[Vector2(10, 20)] = 30;63CHECK(int(map[Vector2(10, 20)]) == 30);64map[0] = 400;65CHECK(int(map[0]) == 400);66// Check that assigning 0 doesn't overwrite the value for `false`.67CHECK(int(map[false]) == 128);6869// Ensure read-only maps aren't modified by non-existing keys.70const int length = map.size();71map.make_read_only();72CHECK(int(map["This key does not exist"].get_type()) == Variant::NIL);73CHECK(map.size() == length);74}7576TEST_CASE("[Dictionary] List init") {77Dictionary dict{78{ 0, "int" },79{ "packed_string_array", PackedStringArray({ "array", "of", "values" }) },80{ "key", Dictionary({ { "nested", 200 } }) },81{ Vector2(), "v2" },82};83CHECK(dict.size() == 4);84CHECK(dict[0] == "int");85CHECK(PackedStringArray(dict["packed_string_array"])[2] == "values");86CHECK(Dictionary(dict["key"])["nested"] == Variant(200));87CHECK(dict[Vector2()] == "v2");8889TypedDictionary<double, double> tdict{90{ 0.0, 1.0 },91{ 5.0, 2.0 },92};93CHECK_EQ(tdict[0.0], Variant(1.0));94CHECK_EQ(tdict[5.0], Variant(2.0));95}9697TEST_CASE("[Dictionary] get_key_list()") {98Dictionary map;99LocalVector<Variant> keys;100keys = map.get_key_list();101CHECK(keys.is_empty());102map[1] = 3;103keys = map.get_key_list();104CHECK(keys.size() == 1);105CHECK(int(keys[0]) == 1);106map[2] = 4;107keys = map.get_key_list();108CHECK(keys.size() == 2);109}110111TEST_CASE("[Dictionary] get_key_at_index()") {112Dictionary map;113map[4] = 3;114Variant val = map.get_key_at_index(0);115CHECK(int(val) == 4);116map[3] = 1;117val = map.get_key_at_index(0);118CHECK(int(val) == 4);119val = map.get_key_at_index(1);120CHECK(int(val) == 3);121}122123TEST_CASE("[Dictionary] getptr()") {124Dictionary map;125map[1] = 3;126Variant *key = map.getptr(1);127CHECK(int(*key) == 3);128key = map.getptr(2);129CHECK(key == nullptr);130}131132TEST_CASE("[Dictionary] get_valid()") {133Dictionary map;134map[1] = 3;135Variant val = map.get_valid(1);136CHECK(int(val) == 3);137}138139TEST_CASE("[Dictionary] set(), get(), and get_or_add()") {140Dictionary map;141142map.set(1, 3);143Variant val = map.get(1, -1);144CHECK(int(val) == 3);145146map.set(1, 5);147val = map.get(1, -1);148CHECK(int(val) == 5);149150CHECK(int(map.get_or_add(1, 7)) == 5);151CHECK(int(map.get_or_add(2, 7)) == 7);152}153154TEST_CASE("[Dictionary] make_read_only() and is_read_only()") {155Dictionary map;156CHECK_FALSE(map.is_read_only());157CHECK(map.set(1, 1));158159map.make_read_only();160CHECK(map.is_read_only());161162ERR_PRINT_OFF;163CHECK_FALSE(map.set(1, 2));164ERR_PRINT_ON;165}166167TEST_CASE("[Dictionary] size(), is_empty() and clear()") {168Dictionary map;169CHECK(map.size() == 0);170CHECK(map.is_empty());171map[1] = 3;172CHECK(map.size() == 1);173CHECK(!map.is_empty());174map.clear();175CHECK(map.size() == 0);176CHECK(map.is_empty());177}178179TEST_CASE("[Dictionary] has() and has_all()") {180Dictionary map;181CHECK(map.has(1) == false);182map[1] = 3;183CHECK(map.has(1));184Array keys;185keys.push_back(1);186CHECK(map.has_all(keys));187keys.push_back(2);188CHECK(map.has_all(keys) == false);189}190191TEST_CASE("[Dictionary] keys() and values()") {192Dictionary map;193Array keys = map.keys();194Array values = map.values();195CHECK(keys.is_empty());196CHECK(values.is_empty());197map[1] = 3;198keys = map.keys();199values = map.values();200CHECK(int(keys[0]) == 1);201CHECK(int(values[0]) == 3);202}203204TEST_CASE("[Dictionary] merge() and merged()") {205Dictionary d1 = {206{ "key1", 1 },207{ "key2", 2 },208};209Dictionary d2 = {210{ "key2", 200 },211{ "key3", 300 },212};213Dictionary expected_no_overwrite = {214{ "key1", 1 },215{ "key2", 2 },216{ "key3", 300 },217};218Dictionary expected_overwrite = {219{ "key1", 1 },220{ "key2", 200 },221{ "key3", 300 },222};223224Dictionary d_test = d1.duplicate();225d_test.merge(d2, false);226CHECK_EQ(d_test, expected_no_overwrite);227228d_test = d1.duplicate();229d_test.merge(d2, true);230CHECK_EQ(d_test, expected_overwrite);231232CHECK_EQ(d1.merged(d2, false), expected_no_overwrite);233CHECK_EQ(d1.merged(d2, true), expected_overwrite);234}235236TEST_CASE("[Dictionary] Duplicate dictionary") {237// d = {1: {1: 1}, {2: 2}: [2], [3]: 3}238Dictionary k2 = { { 2, 2 } };239Array k3 = { 3 };240Dictionary d = {241{ 1, Dictionary({ { 1, 1 } }) },242{ k2, Array({ 2 }) },243{ k3, 3 }244};245246// Deep copy247Dictionary deep_d = d.duplicate(true);248CHECK_MESSAGE(deep_d.id() != d.id(), "Should create a new dictionary");249CHECK_MESSAGE(Dictionary(deep_d[1]).id() != Dictionary(d[1]).id(), "Should clone nested dictionary");250CHECK_MESSAGE(Array(deep_d[k2]).id() != Array(d[k2]).id(), "Should clone nested array");251CHECK_EQ(deep_d, d);252253// Check that duplicate_deep matches duplicate(true)254Dictionary deep_d2 = d.duplicate_deep();255CHECK_EQ(deep_d, deep_d2);256257deep_d[0] = 0;258CHECK_NE(deep_d, d);259deep_d.erase(0);260Dictionary(deep_d[1]).operator[](0) = 0;261CHECK_NE(deep_d, d);262Dictionary(deep_d[1]).erase(0);263CHECK_EQ(deep_d, d);264// Keys should also be copied265k2[0] = 0;266CHECK_NE(deep_d, d);267k2.erase(0);268CHECK_EQ(deep_d, d);269k3.push_back(0);270CHECK_NE(deep_d, d);271k3.pop_back();272CHECK_EQ(deep_d, d);273274// Shallow copy275Dictionary shallow_d = d.duplicate(false);276CHECK_MESSAGE(shallow_d.id() != d.id(), "Should create a new array");277CHECK_MESSAGE(Dictionary(shallow_d[1]).id() == Dictionary(d[1]).id(), "Should keep nested dictionary");278CHECK_MESSAGE(Array(shallow_d[k2]).id() == Array(d[k2]).id(), "Should keep nested array");279CHECK_EQ(shallow_d, d);280shallow_d[0] = 0;281CHECK_NE(shallow_d, d);282shallow_d.erase(0);283#if 0 // TODO: recursion in dict key currently is buggy284// Keys should also be shallowed285k2[0] = 0;286CHECK_EQ(shallow_d, d);287k2.erase(0);288k3.push_back(0);289CHECK_EQ(shallow_d, d);290#endif291}292293TEST_CASE("[Dictionary] Duplicate recursive dictionary") {294// Self recursive295Dictionary d;296d[1] = d;297298Dictionary d_shallow = d.duplicate(false);299CHECK_EQ(d, d_shallow);300301// Deep copy of recursive dictionary endup with recursion limit and return302// an invalid result (multiple nested dictionaries), the point is we should303// not end up with a segfault and an error log should be printed304ERR_PRINT_OFF;305d.duplicate(true);306ERR_PRINT_ON;307308// Nested recursive309Dictionary d1;310Dictionary d2;311d1[2] = d2;312d2[1] = d1;313314Dictionary d1_shallow = d1.duplicate(false);315CHECK_EQ(d1, d1_shallow);316317// Same deep copy issue as above318ERR_PRINT_OFF;319d1.duplicate(true);320ERR_PRINT_ON;321322// Break the recursivity otherwise Dictionary teardown will leak memory323d.clear();324d1.clear();325d2.clear();326}327328#if 0 // TODO: duplicate recursion in dict key is currently buggy329TEST_CASE("[Dictionary] Duplicate recursive dictionary on keys") {330// Self recursive331Dictionary d;332d[d] = d;333334Dictionary d_shallow = d.duplicate(false);335CHECK_EQ(d, d_shallow);336337// Deep copy of recursive dictionary endup with recursion limit and return338// an invalid result (multiple nested dictionaries), the point is we should339// not end up with a segfault and an error log should be printed340ERR_PRINT_OFF;341d.duplicate(true);342ERR_PRINT_ON;343344// Nested recursive345Dictionary d1;346Dictionary d2;347d1[d2] = d2;348d2[d1] = d1;349350Dictionary d1_shallow = d1.duplicate(false);351CHECK_EQ(d1, d1_shallow);352353// Same deep copy issue as above354ERR_PRINT_OFF;355d1.duplicate(true);356ERR_PRINT_ON;357358// Break the recursivity otherwise Dictionary teardown will leak memory359d.clear();360d1.clear();361d2.clear();362}363#endif364365TEST_CASE("[Dictionary] Hash dictionary") {366// d = {1: {1: 1}, {2: 2}: [2], [3]: 3}367Dictionary k2 = { { 2, 2 } };368Array k3 = { 3 };369Dictionary d = {370{ 1, Dictionary({ { 1, 1 } }) },371{ k2, Array({ 2 }) },372{ k3, 3 }373};374uint32_t original_hash = d.hash();375376// Modify dict change the hash377d[0] = 0;378CHECK_NE(d.hash(), original_hash);379d.erase(0);380CHECK_EQ(d.hash(), original_hash);381382// Modify nested item change the hash383Dictionary(d[1]).operator[](0) = 0;384CHECK_NE(d.hash(), original_hash);385Dictionary(d[1]).erase(0);386Array(d[k2]).push_back(0);387CHECK_NE(d.hash(), original_hash);388Array(d[k2]).pop_back();389390// Modify a key change the hash391k2[0] = 0;392CHECK_NE(d.hash(), original_hash);393k2.erase(0);394CHECK_EQ(d.hash(), original_hash);395k3.push_back(0);396CHECK_NE(d.hash(), original_hash);397k3.pop_back();398CHECK_EQ(d.hash(), original_hash);399400// Duplication doesn't change the hash401Dictionary d2 = d.duplicate(true);402CHECK_EQ(d2.hash(), original_hash);403}404405TEST_CASE("[Dictionary] Hash recursive dictionary") {406Dictionary d;407d[1] = d;408409// Hash should reach recursion limit, we just make sure this doesn't blow up410ERR_PRINT_OFF;411d.hash();412ERR_PRINT_ON;413414// Break the recursivity otherwise Dictionary teardown will leak memory415d.clear();416}417418#if 0 // TODO: recursion in dict key is currently buggy419TEST_CASE("[Dictionary] Hash recursive dictionary on keys") {420Dictionary d;421d[d] = 1;422423// Hash should reach recursion limit, we just make sure this doesn't blow up424ERR_PRINT_OFF;425d.hash();426ERR_PRINT_ON;427428// Break the recursivity otherwise Dictionary teardown will leak memory429d.clear();430}431#endif432433TEST_CASE("[Dictionary] Empty comparison") {434Dictionary d1;435Dictionary d2;436437// test both operator== and operator!=438CHECK_EQ(d1, d2);439CHECK_FALSE(d1 != d2);440}441442TEST_CASE("[Dictionary] Flat comparison") {443Dictionary d1 = { { 1, 1 } };444Dictionary d2 = { { 1, 1 } };445Dictionary other_d = { { 2, 1 } };446447// test both operator== and operator!=448CHECK_EQ(d1, d1); // compare self449CHECK_FALSE(d1 != d1);450CHECK_EQ(d1, d2); // different equivalent arrays451CHECK_FALSE(d1 != d2);452CHECK_NE(d1, other_d); // different arrays with different content453CHECK_FALSE(d1 == other_d);454}455456TEST_CASE("[Dictionary] Nested dictionary comparison") {457// d1 = {1: {2: {3: 4}}}458Dictionary d1 = { { 1, Dictionary({ { 2, Dictionary({ { 3, 4 } }) } }) } };459460Dictionary d2 = d1.duplicate(true);461462// other_d = {1: {2: {3: 0}}}463Dictionary other_d = { { 1, Dictionary({ { 2, Dictionary({ { 3, 0 } }) } }) } };464465// test both operator== and operator!=466CHECK_EQ(d1, d1); // compare self467CHECK_FALSE(d1 != d1);468CHECK_EQ(d1, d2); // different equivalent arrays469CHECK_FALSE(d1 != d2);470CHECK_NE(d1, other_d); // different arrays with different content471CHECK_FALSE(d1 == other_d);472}473474TEST_CASE("[Dictionary] Nested array comparison") {475// d1 = {1: [2, 3]}476Dictionary d1 = { { 1, { 2, 3 } } };477478Dictionary d2 = d1.duplicate(true);479480// other_d = {1: [2, 0]}481Dictionary other_d = { { 1, { 2, 0 } } };482483// test both operator== and operator!=484CHECK_EQ(d1, d1); // compare self485CHECK_FALSE(d1 != d1);486CHECK_EQ(d1, d2); // different equivalent arrays487CHECK_FALSE(d1 != d2);488CHECK_NE(d1, other_d); // different arrays with different content489CHECK_FALSE(d1 == other_d);490}491492TEST_CASE("[Dictionary] Recursive comparison") {493Dictionary d1;494d1[1] = d1;495496Dictionary d2;497d2[1] = d2;498499// Comparison should reach recursion limit500ERR_PRINT_OFF;501CHECK_EQ(d1, d2);502CHECK_FALSE(d1 != d2);503ERR_PRINT_ON;504505d1[2] = 2;506d2[2] = 2;507508// Comparison should reach recursion limit509ERR_PRINT_OFF;510CHECK_EQ(d1, d2);511CHECK_FALSE(d1 != d2);512ERR_PRINT_ON;513514d1[3] = 3;515d2[3] = 0;516517// Comparison should reach recursion limit518ERR_PRINT_OFF;519CHECK_NE(d1, d2);520CHECK_FALSE(d1 == d2);521ERR_PRINT_ON;522523// Break the recursivity otherwise Dictionary teardown will leak memory524d1.clear();525d2.clear();526}527528#if 0 // TODO: recursion in dict key is currently buggy529TEST_CASE("[Dictionary] Recursive comparison on keys") {530Dictionary d1;531// Hash computation should reach recursion limit532ERR_PRINT_OFF;533d1[d1] = 1;534ERR_PRINT_ON;535536Dictionary d2;537// Hash computation should reach recursion limit538ERR_PRINT_OFF;539d2[d2] = 1;540ERR_PRINT_ON;541542// Comparison should reach recursion limit543ERR_PRINT_OFF;544CHECK_EQ(d1, d2);545CHECK_FALSE(d1 != d2);546ERR_PRINT_ON;547548d1[2] = 2;549d2[2] = 2;550551// Comparison should reach recursion limit552ERR_PRINT_OFF;553CHECK_EQ(d1, d2);554CHECK_FALSE(d1 != d2);555ERR_PRINT_ON;556557d1[3] = 3;558d2[3] = 0;559560// Comparison should reach recursion limit561ERR_PRINT_OFF;562CHECK_NE(d1, d2);563CHECK_FALSE(d1 == d2);564ERR_PRINT_ON;565566// Break the recursivity otherwise Dictionary teardown will leak memory567d1.clear();568d2.clear();569}570#endif571572TEST_CASE("[Dictionary] Recursive self comparison") {573Dictionary d1;574Dictionary d2;575d1[1] = d2;576d2[1] = d1;577578CHECK_EQ(d1, d1);579CHECK_FALSE(d1 != d1);580581// Break the recursivity otherwise Dictionary teardown will leak memory582d1.clear();583d2.clear();584}585586TEST_CASE("[Dictionary] Order and find") {587Dictionary d;588d[4] = "four";589d[8] = "eight";590d[12] = "twelve";591d["4"] = "four";592593Array keys = { 4, 8, 12, "4" };594595CHECK_EQ(d.keys(), keys);596CHECK_EQ(d.find_key("four"), Variant(4));597CHECK_EQ(d.find_key("does not exist"), Variant());598}599600TEST_CASE("[Dictionary] sort()") {601Dictionary d;602d[3] = 3;603d[2] = 2;604d[4] = 4;605d[1] = 1;606607Array expected_unsorted = { 3, 2, 4, 1 };608CHECK_EQ(d.keys(), expected_unsorted);609610d.sort();611Array expected_sorted = { 1, 2, 3, 4 };612CHECK_EQ(d.keys(), expected_sorted);613614Dictionary d_str;615d_str["b"] = 2;616d_str["c"] = 3;617d_str["a"] = 1;618619d_str.sort();620Array expected_str_sorted = { "a", "b", "c" };621CHECK_EQ(d_str.keys(), expected_str_sorted);622}623624TEST_CASE("[Dictionary] assign()") {625Dictionary untyped;626untyped["key1"] = "value";627CHECK(untyped.size() == 1);628629Dictionary typed;630typed.set_typed(Variant::STRING, StringName(), Variant(), Variant::STRING, StringName(), Variant());631typed.assign(untyped);632CHECK(typed.size() == 1);633typed["key2"] = "value";634635untyped.assign(typed);636CHECK(untyped.size() == 2);637untyped["key3"] = 5;638CHECK(untyped.size() == 3);639640ERR_PRINT_OFF;641typed.assign(untyped);642ERR_PRINT_ON;643CHECK(typed.size() == 2);644}645646TEST_CASE("[Dictionary] Typed copying") {647TypedDictionary<int, int> d1;648d1[0] = 1;649650TypedDictionary<double, double> d2;651d2[0] = 1.0;652653Dictionary d3 = d1;654TypedDictionary<int, int> d4 = d3;655656Dictionary d5 = d2;657TypedDictionary<int, int> d6 = d5;658659d3[0] = 2;660d4[0] = 3;661662// Same typed TypedDictionary should be shared.663CHECK_EQ(d1[0], Variant(3));664CHECK_EQ(d3[0], Variant(3));665CHECK_EQ(d4[0], Variant(3));666667d5[0] = 2.0;668d6[0] = 3.0;669670// Different typed TypedDictionary should not be shared.671CHECK_EQ(d2[0], Variant(2.0));672CHECK_EQ(d5[0], Variant(2.0));673CHECK_EQ(d6[0], Variant(3.0));674675d1.clear();676d2.clear();677d3.clear();678d4.clear();679d5.clear();680d6.clear();681}682683TEST_CASE("[Dictionary] Type checks/comparisons") {684Dictionary d1;685CHECK_FALSE(d1.is_typed());686CHECK_FALSE(d1.is_typed_key());687CHECK_FALSE(d1.is_typed_value());688689d1.set_typed(Variant::STRING, StringName(), Variant(), Variant::OBJECT, "Node", Variant());690CHECK(d1.is_typed());691CHECK(d1.is_typed_key());692CHECK(d1.is_typed_value());693CHECK_EQ(d1.get_typed_key_builtin(), Variant::STRING);694CHECK_EQ(d1.get_typed_value_builtin(), Variant::OBJECT);695CHECK_EQ(d1.get_typed_value_class_name(), "Node");696697Dictionary d2;698CHECK_FALSE(d1.is_same_typed(d2));699CHECK_FALSE(d1.is_same_typed_key(d2));700CHECK_FALSE(d1.is_same_typed_value(d2));701702d2.set_typed(Variant::STRING, StringName(), Variant(), Variant::STRING, StringName(), Variant());703CHECK_FALSE(d1.is_same_typed(d2));704CHECK(d1.is_same_typed_key(d2));705CHECK_FALSE(d1.is_same_typed_value(d2));706}707708TEST_CASE("[Dictionary] Iteration") {709Dictionary a1 = { { 1, 2 }, { 3, 4 }, { 5, 6 } };710Dictionary a2 = { { 1, 2 }, { 3, 4 }, { 5, 6 } };711712int idx = 0;713714for (const KeyValue<Variant, Variant> &kv : (const Dictionary &)a1) {715CHECK_EQ(int(a2[kv.key]), int(kv.value));716idx++;717}718719CHECK_EQ(idx, a1.size());720721a1.clear();722a2.clear();723}724725TEST_CASE("[Dictionary] Object value init") {726Object *a = memnew(Object);727Object *b = memnew(Object);728TypedDictionary<double, Object *> tdict = {729{ 0.0, a },730{ 5.0, b },731};732CHECK_EQ(tdict[0.0], Variant(a));733CHECK_EQ(tdict[5.0], Variant(b));734memdelete(a);735memdelete(b);736}737738TEST_CASE("[Dictionary] RefCounted value init") {739Ref<RefCounted> a = memnew(RefCounted);740Ref<RefCounted> b = memnew(RefCounted);741TypedDictionary<double, Ref<RefCounted>> tdict = {742{ 0.0, a },743{ 5.0, b },744};745CHECK_EQ(tdict[0.0], Variant(a));746CHECK_EQ(tdict[5.0], Variant(b));747}748749} // namespace TestDictionary750751752