/** * @file llsdutil_tut.cpp * @author Adroit * @date 2007-02 * @brief LLSD conversion routines test cases. * * $LicenseInfo:firstyear=2007&license=viewerlgpl$ * Second Life Viewer Source Code * Copyright (C) 2010, Linden Research, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License only. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA * $/LicenseInfo$ */ #include "linden_common.h" #include "lltut.h" #include "m4math.h" #include "v2math.h" #include "v2math.h" #include "v3color.h" #include "v3math.h" #include "v3dmath.h" #include "v4coloru.h" #include "v4math.h" #include "llquaternion.h" #include "llsdutil.h" #include "llsdutil_math.h" #include "stringize.h" #include <set> #include <boost/range.hpp> namespace tut { struct llsdutil_data { void test_matches(const std::string& proto_key, const LLSD& possibles, const char** begin, const char** end) { std::set<std::string> succeed(begin, end); LLSD prototype(possibles[proto_key]); for (LLSD::map_const_iterator pi(possibles.beginMap()), pend(possibles.endMap()); pi != pend; ++pi) { std::string match(llsd_matches(prototype, pi->second)); std::set<std::string>::const_iterator found = succeed.find(pi->first); if (found != succeed.end()) { // This test is supposed to succeed. Comparing to the // empty string ensures that if the test fails, it will // display the string received so we can tell what failed. ensure_equals("match", match, ""); } else { // This test is supposed to fail. If we get a false match, // the string 'match' will be empty, which doesn't tell us // much about which case went awry. So construct a more // detailed description string. ensure(proto_key + " shouldn't match " + pi->first, ! match.empty()); } } } }; typedef test_group<llsdutil_data> llsdutil_test;; typedef llsdutil_test::object llsdutil_object; tut::llsdutil_test tutil("llsdutil"); template<> template<> void llsdutil_object::test<1>() { LLSD sd; U64 valueIn , valueOut; valueIn = U64L(0xFEDCBA9876543210); sd = ll_sd_from_U64(valueIn); valueOut = ll_U64_from_sd(sd); ensure_equals("U64 valueIn->sd->valueOut", valueIn, valueOut); } template<> template<> void llsdutil_object::test<2>() { LLSD sd; U32 valueIn, valueOut; valueIn = 0x87654321; sd = ll_sd_from_U32(valueIn); valueOut = ll_U32_from_sd(sd); ensure_equals("U32 valueIn->sd->valueOut", valueIn, valueOut); } template<> template<> void llsdutil_object::test<3>() { U32 valueIn, valueOut; valueIn = 0x87654321; LLSD sd; sd = ll_sd_from_ipaddr(valueIn); valueOut = ll_ipaddr_from_sd(sd); ensure_equals("valueIn->sd->valueOut", valueIn, valueOut); } template<> template<> void llsdutil_object::test<4>() { LLSD sd; LLVector3 vec1(-1.0, 2.0, -3.0); sd = ll_sd_from_vector3(vec1); LLVector3 vec2 = ll_vector3_from_sd(sd); ensure_equals("vector3 -> sd -> vector3: 1", vec1, vec2); LLVector3 vec3(sd); ensure_equals("vector3 -> sd -> vector3: 2", vec1, vec3); sd.clear(); vec1.setVec(0., 0., 0.); sd = ll_sd_from_vector3(vec1); vec2 = ll_vector3_from_sd(sd); ensure_equals("vector3 -> sd -> vector3: 3", vec1, vec2); sd.clear(); } template<> template<> void llsdutil_object::test<5>() { LLSD sd; LLVector3d vec1((F64)(U64L(0xFEDCBA9876543210) << 2), -1., 0); sd = ll_sd_from_vector3d(vec1); LLVector3d vec2 = ll_vector3d_from_sd(sd); ensure_equals("vector3d -> sd -> vector3d: 1", vec1, vec2); LLVector3d vec3(sd); ensure_equals("vector3d -> sd -> vector3d : 2", vec1, vec3); } template<> template<> void llsdutil_object::test<6>() { LLSD sd; LLVector2 vec((F32) -3., (F32) 4.2); sd = ll_sd_from_vector2(vec); LLVector2 vec1 = ll_vector2_from_sd(sd); ensure_equals("vector2 -> sd -> vector2", vec, vec1); LLSD sd2 = ll_sd_from_vector2(vec1); ensure_equals("sd -> vector2 -> sd: 2", sd, sd2); } template<> template<> void llsdutil_object::test<7>() { LLSD sd; LLQuaternion quat((F32) 1., (F32) -0.98, (F32) 2.3, (F32) 0xffff); sd = ll_sd_from_quaternion(quat); LLQuaternion quat1 = ll_quaternion_from_sd(sd); ensure_equals("LLQuaternion -> sd -> LLQuaternion", quat, quat1); LLSD sd2 = ll_sd_from_quaternion(quat1); ensure_equals("sd -> LLQuaternion -> sd ", sd, sd2); } template<> template<> void llsdutil_object::test<8>() { LLSD sd; LLColor4 c(1.0f, 2.2f, 4.0f, 7.f); sd = ll_sd_from_color4(c); LLColor4 c1 =ll_color4_from_sd(sd); ensure_equals("LLColor4 -> sd -> LLColor4", c, c1); LLSD sd1 = ll_sd_from_color4(c1); ensure_equals("sd -> LLColor4 -> sd", sd, sd1); } template<> template<> void llsdutil_object::test<9>() { set_test_name("llsd_matches"); // for this test, construct a map of all possible LLSD types LLSD map; map.insert("empty", LLSD()); map.insert("Boolean", LLSD::Boolean()); map.insert("Integer", LLSD::Integer(0)); map.insert("Real", LLSD::Real(0.0)); map.insert("String", LLSD::String("bah")); map.insert("NumString", LLSD::String("1")); map.insert("UUID", LLSD::UUID()); map.insert("Date", LLSD::Date()); map.insert("URI", LLSD::URI()); map.insert("Binary", LLSD::Binary()); map.insert("Map", LLSD().with("foo", LLSD())); // Only an empty array can be constructed on the fly LLSD array; array.append(LLSD()); map.insert("Array", array); // These iterators are declared outside our various for loops to avoid // fatal MSVC warning: "I used to be broken, but I'm all better now!" LLSD::map_const_iterator mi, mend(map.endMap()); /*-------------------------- llsd_matches --------------------------*/ // empty prototype matches anything for (mi = map.beginMap(); mi != mend; ++mi) { ensure_equals(std::string("empty matches ") + mi->first, llsd_matches(LLSD(), mi->second), ""); } LLSD proto_array, data_array; for (int i = 0; i < 3; ++i) { proto_array.append(LLSD()); data_array.append(LLSD()); } // prototype array matches only array for (mi = map.beginMap(); mi != mend; ++mi) { ensure(std::string("array doesn't match ") + mi->first, ! llsd_matches(proto_array, mi->second).empty()); } // data array must be at least as long as prototype array proto_array.append(LLSD()); ensure_equals("data array too short", llsd_matches(proto_array, data_array), "Array size 4 required instead of Array size 3"); data_array.append(LLSD()); ensure_equals("data array just right", llsd_matches(proto_array, data_array), ""); data_array.append(LLSD()); ensure_equals("data array longer", llsd_matches(proto_array, data_array), ""); // array element matching data_array[0] = LLSD::String(); ensure_equals("undefined prototype array entry", llsd_matches(proto_array, data_array), ""); proto_array[0] = LLSD::Binary(); ensure_equals("scalar prototype array entry", llsd_matches(proto_array, data_array), "[0]: Binary required instead of String"); data_array[0] = LLSD::Binary(); ensure_equals("matching prototype array entry", llsd_matches(proto_array, data_array), ""); // build a coupla maps LLSD proto_map, data_map; data_map["got"] = LLSD(); data_map["found"] = LLSD(); for (LLSD::map_const_iterator dmi(data_map.beginMap()), dmend(data_map.endMap()); dmi != dmend; ++dmi) { proto_map[dmi->first] = dmi->second; } proto_map["foo"] = LLSD(); proto_map["bar"] = LLSD(); // prototype map matches only map for (mi = map.beginMap(); mi != mend; ++mi) { ensure(std::string("map doesn't match ") + mi->first, ! llsd_matches(proto_map, mi->second).empty()); } // data map must contain all keys in prototype map std::string error(llsd_matches(proto_map, data_map)); ensure_contains("missing keys", error, "missing keys"); ensure_contains("missing foo", error, "foo"); ensure_contains("missing bar", error, "bar"); ensure_does_not_contain("found found", error, "found"); ensure_does_not_contain("got got", error, "got"); data_map["bar"] = LLSD(); error = llsd_matches(proto_map, data_map); ensure_contains("missing foo", error, "foo"); ensure_does_not_contain("got bar", error, "bar"); data_map["foo"] = LLSD(); ensure_equals("data map just right", llsd_matches(proto_map, data_map), ""); data_map["extra"] = LLSD(); ensure_equals("data map with extra", llsd_matches(proto_map, data_map), ""); // map element matching data_map["foo"] = LLSD::String(); ensure_equals("undefined prototype map entry", llsd_matches(proto_map, data_map), ""); proto_map["foo"] = LLSD::Binary(); ensure_equals("scalar prototype map entry", llsd_matches(proto_map, data_map), "['foo']: Binary required instead of String"); data_map["foo"] = LLSD::Binary(); ensure_equals("matching prototype map entry", llsd_matches(proto_map, data_map), ""); // String { static const char* matches[] = { "String", "NumString", "Boolean", "Integer", "Real", "UUID", "Date", "URI" }; test_matches("String", map, boost::begin(matches), boost::end(matches)); } // Boolean, Integer, Real static const char* numerics[] = { "Boolean", "Integer", "Real" }; for (const char **ni = boost::begin(numerics), **nend = boost::end(numerics); ni != nend; ++ni) { static const char* matches[] = { "Boolean", "Integer", "Real", "String", "NumString" }; test_matches(*ni, map, boost::begin(matches), boost::end(matches)); } // UUID { static const char* matches[] = { "UUID", "String", "NumString" }; test_matches("UUID", map, boost::begin(matches), boost::end(matches)); } // Date { static const char* matches[] = { "Date", "String", "NumString" }; test_matches("Date", map, boost::begin(matches), boost::end(matches)); } // URI { static const char* matches[] = { "URI", "String", "NumString" }; test_matches("URI", map, boost::begin(matches), boost::end(matches)); } // Binary { static const char* matches[] = { "Binary" }; test_matches("Binary", map, boost::begin(matches), boost::end(matches)); } /*-------------------------- llsd_equals ---------------------------*/ // Cross-product of each LLSD type with every other for (LLSD::map_const_iterator lmi(map.beginMap()), lmend(map.endMap()); lmi != lmend; ++lmi) { for (LLSD::map_const_iterator rmi(map.beginMap()), rmend(map.endMap()); rmi != rmend; ++rmi) { // Name this test based on the map keys naming the types of // interest, e.g "String::Integer". // We expect the values (xmi->second) to be equal if and only // if the type names (xmi->first) are equal. ensure(STRINGIZE(lmi->first << "::" << rmi->first), bool(lmi->first == rmi->first) == bool(llsd_equals(lmi->second, rmi->second))); } } // Array cases LLSD rarray; rarray.append(1.0); rarray.append(2); rarray.append("3"); LLSD larray(rarray); ensure("llsd_equals(equal arrays)", llsd_equals(larray, rarray)); rarray[2] = "4"; ensure("llsd_equals(different [2])", ! llsd_equals(larray, rarray)); rarray = larray; rarray.append(LLSD::Date()); ensure("llsd_equals(longer right array)", ! llsd_equals(larray, rarray)); rarray = larray; rarray.erase(2); ensure("llsd_equals(shorter right array)", ! llsd_equals(larray, rarray)); // Map cases LLSD rmap; rmap["San Francisco"] = 65; rmap["Phoenix"] = 92; rmap["Boston"] = 77; LLSD lmap(rmap); ensure("llsd_equals(equal maps)", llsd_equals(lmap, rmap)); rmap["Boston"] = 80; ensure("llsd_equals(different [\"Boston\"])", ! llsd_equals(lmap, rmap)); rmap = lmap; rmap["Atlanta"] = 95; ensure("llsd_equals(superset right map)", ! llsd_equals(lmap, rmap)); rmap = lmap; lmap["Seattle"] = 72; ensure("llsd_equals(superset left map)", ! llsd_equals(lmap, rmap)); } template<> template<> void llsdutil_object::test<10>() { set_test_name("llsd_hashing"); { LLSD data_s1 = LLSD::String("The quick brown aardvark jumped over the lazy lemming."); LLSD data_s2 = LLSD::String("The quick brown aardvark jumped over the lazy lemming."); ensure("hash: Identical string hashes match.", boost::hash<LLSD>{}(data_s1) == boost::hash<LLSD>{}(data_s2)); } { LLSD data_r1 = LLSD::Real(3.0f); LLSD data_i1 = LLSD::Integer(3); ensure("hash: equivalent values but different types do not match.", boost::hash<LLSD>{}(data_r1) != boost::hash<LLSD>{}(data_i1)); } { LLSD data_a1 = llsd::array("A", "B", "C"); LLSD data_a2 = llsd::array("A", "B", "C"); ensure("hash: identical arrays produce identical results", boost::hash<LLSD>{}(data_a1) == boost::hash<LLSD>{}(data_a2)); data_a2.append(llsd::array(1, 2)); ensure("hash: changing the array changes the hash.", boost::hash<LLSD>{}(data_a1) != boost::hash<LLSD>{}(data_a2)); data_a1.append(llsd::array(1, 2)); ensure("hash: identical arrays produce identical results with nested arrays", boost::hash<LLSD>{}(data_a1) == boost::hash<LLSD>{}(data_a2)); } { LLSD data_m1 = LLSDMap("key1", LLSD::Real(3.0))("key2", "value2")("key3", llsd::array(1, 2, 3)); LLSD data_m2 = LLSDMap("key1", LLSD::Real(3.0))("key2", "value2")("key3", llsd::array(1, 2, 3)); ensure("hash: identical maps produce identical results", boost::hash<LLSD>{}(data_m1) == boost::hash<LLSD>{}(data_m2)); LLSD data_m3 = LLSDMap("key1", LLSD::Real(5.0))("key2", "value2")("key3", llsd::array(1, 2, 3)); ensure("hash: Different values in the map produce different hashes.", boost::hash<LLSD>{}(data_m1) != boost::hash<LLSD>{}(data_m3)); LLSD data_m4 = LLSDMap("keyA", LLSD::Real(3.0))("key2", "value2")("key3", llsd::array(1, 2, 3)); ensure("hash: Different keys in the map produce different hashes.", boost::hash<LLSD>{}(data_m1) != boost::hash<LLSD>{}(data_m4)); } } }