unittest/types_t.cc (1,129 lines of code) (raw):

/* * Copyright (c) 2014, 2024, Oracle and/or its affiliates. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 2.0, * as published by the Free Software Foundation. * * This program is designed to work with certain software (including * but not limited to OpenSSL) that is licensed under separate terms, * as designated in a particular file or component or in included license * documentation. The authors of MySQL hereby grant you an additional * permission to link the program and your derivative works with the * separately licensed software that they have either included with * the program or referenced in the documentation. * * This program 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 General Public License, version 2.0, for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include <algorithm> #include <cstdio> #include <cstdlib> #include <fstream> #include <random> #include <string> #include "scripting/types.h" #include "scripting/types_cpp.h" #include "unittest/test_utils.h" using namespace ::testing; namespace shcore { namespace tests { TEST(ValueTests, SimpleInt) { shcore::Value v(20); std::string mydescr = v.descr(true); std::string myrepr = v.repr(); std::string myjson = v.json(); EXPECT_EQ(shcore::Integer, v.type); EXPECT_STREQ("20", mydescr.c_str()); EXPECT_STREQ("20", myrepr.c_str()); EXPECT_STREQ("20", myjson.c_str()); shcore::Value v2 = Value::parse(myrepr); mydescr = v2.descr(true); myrepr = v2.repr(); myjson = v2.json(); EXPECT_EQ(shcore::Integer, v2.type); EXPECT_STREQ("20", mydescr.c_str()); EXPECT_STREQ("20", myrepr.c_str()); EXPECT_STREQ("20", myjson.c_str()); } TEST(ValueTests, SimpleBool) { shcore::Value v; v = shcore::Value(true); EXPECT_EQ(shcore::Bool, v.type); EXPECT_STREQ("true", v.descr().c_str()); EXPECT_STREQ("true", v.repr().c_str()); EXPECT_STREQ("true", v.json().c_str()); v = shcore::Value(false); EXPECT_EQ(shcore::Bool, v.type); EXPECT_STREQ("false", v.descr().c_str()); EXPECT_STREQ("false", v.repr().c_str()); EXPECT_STREQ("false", v.json().c_str()); EXPECT_EQ(Value::True().as_bool(), true); EXPECT_TRUE(Value::True() == Value::True()); EXPECT_TRUE(Value::True() == Value(1)); EXPECT_FALSE(Value::True() == Value(1.1)); EXPECT_FALSE(Value::True() == Value("1")); EXPECT_FALSE(Value::True() == Value::Null()); EXPECT_TRUE(Value::False() == Value(0)); EXPECT_FALSE(Value::False() == Value(0.1)); EXPECT_FALSE(Value::False() == Value("0")); EXPECT_FALSE(Value::False() == Value::Null()); } TEST(ValueTests, SimpleDouble) { EXPECT_EQ(Value(1.1234).as_double(), 1.1234); } TEST(ValueTests, Conversion) { // OK conversions EXPECT_THROW(Value::Null().as_bool(), shcore::Exception); EXPECT_EQ(true, Value::True().as_bool()); EXPECT_FALSE(Value::False().as_bool()); EXPECT_EQ(true, Value(42).as_bool()); EXPECT_FALSE(Value(0).as_bool()); EXPECT_EQ(true, Value(42U).as_bool()); EXPECT_FALSE(Value(0U).as_bool()); EXPECT_EQ(true, Value(42.5).as_bool()); EXPECT_FALSE(Value(0.0).as_bool()); EXPECT_FALSE(Value("0").as_bool()); EXPECT_FALSE(Value("false").as_bool()); EXPECT_TRUE(Value("1").as_bool()); EXPECT_TRUE(Value("true").as_bool()); EXPECT_THROW(Value("foo").as_bool(), shcore::Exception); EXPECT_THROW(Value::new_array().as_bool(), shcore::Exception); EXPECT_THROW(Value::new_map().as_bool(), shcore::Exception); EXPECT_EQ(1, Value::True().as_int()); EXPECT_EQ(0, Value::False().as_int()); EXPECT_EQ(-42, Value(-42).as_int()); EXPECT_EQ(42, Value(42).as_int()); EXPECT_EQ(0, Value(0).as_int()); EXPECT_EQ(42, Value(42U).as_int()); EXPECT_EQ(0, Value(0U).as_int()); EXPECT_EQ(-42, Value(-42.0).as_int()); EXPECT_EQ(42, Value(42.0).as_int()); EXPECT_THROW(Value(-42.5).as_uint(), shcore::Exception); EXPECT_THROW(Value(42.5).as_uint(), shcore::Exception); EXPECT_EQ(0, Value(0.0).as_int()); EXPECT_EQ(-42, Value("-42").as_int()); EXPECT_EQ(42, Value("42").as_int()); EXPECT_THROW(Value("foo").as_int(), shcore::Exception); EXPECT_THROW(Value::new_array().as_int(), shcore::Exception); EXPECT_THROW(Value::new_map().as_int(), shcore::Exception); EXPECT_EQ(1, Value::True().as_uint()); EXPECT_EQ(0, Value::False().as_uint()); EXPECT_THROW(Value(-42).as_uint(), shcore::Exception); EXPECT_THROW(Value("-42").as_uint(), shcore::Exception); EXPECT_EQ(42, Value(42).as_uint()); EXPECT_EQ(0, Value(0).as_uint()); EXPECT_EQ(42, Value(42U).as_uint()); EXPECT_EQ(0, Value(0U).as_uint()); EXPECT_EQ(42, Value("42").as_uint()); EXPECT_EQ(42, Value(42.0).as_uint()); EXPECT_THROW(Value(-42.5).as_uint(), shcore::Exception); EXPECT_THROW(Value(42.5).as_uint(), shcore::Exception); EXPECT_THROW(Value("42.0").as_uint(), shcore::Exception); EXPECT_EQ(0, Value(0.0).as_uint()); EXPECT_THROW(Value("foo").as_uint(), shcore::Exception); EXPECT_THROW(Value::new_array().as_uint(), shcore::Exception); EXPECT_THROW(Value::new_map().as_uint(), shcore::Exception); EXPECT_EQ(1.0, Value::True().as_double()); EXPECT_EQ(0.0, Value::False().as_double()); EXPECT_EQ(-42.0, Value(-42).as_double()); EXPECT_EQ(42.0, Value(42).as_double()); EXPECT_EQ(0.0, Value(0).as_double()); EXPECT_EQ(42.0, Value(42U).as_double()); EXPECT_EQ(0.0, Value(0U).as_double()); EXPECT_EQ(-42.5, Value(-42.5).as_double()); EXPECT_EQ(42.5, Value(42.5).as_double()); EXPECT_EQ(0.0, Value(0.0).as_double()); EXPECT_EQ(-42.5, Value("-42.5").as_double()); EXPECT_EQ(42.5, Value("42.5").as_double()); EXPECT_THROW(Value("foo").as_double(), shcore::Exception); EXPECT_THROW(Value::new_array().as_double(), shcore::Exception); EXPECT_THROW(Value::new_map().as_double(), shcore::Exception); EXPECT_EQ("true", Value::True().as_string()); EXPECT_EQ("false", Value::False().as_string()); EXPECT_EQ("-42", Value(-42).as_string()); EXPECT_EQ("42", Value(static_cast<uint64_t>(42)).as_string()); EXPECT_EQ("42.5", Value(42.5).as_string()); } TEST(ValueTests, ConversionRanges) { static constexpr uint64_t kBiggestUIntThatFitsInDouble = (1ULL << DBL_MANT_DIG); static constexpr int64_t kSmallestIntThatFitsInDouble = -(1LL << DBL_MANT_DIG); static constexpr int64_t kBiggestIntThatFitsInDouble = (1LL << DBL_MANT_DIG); // int64_t -> uint64_t // min -> error // max -> OK EXPECT_THROW(Value(std::numeric_limits<int64_t>::min()).as_uint(), shcore::Exception); EXPECT_TRUE(static_cast<uint64_t>(std::numeric_limits<int64_t>::max()) == Value(std::numeric_limits<int64_t>::max()).as_uint()); // int64_t -> double // min -> error // max -> error EXPECT_THROW(Value(std::numeric_limits<int64_t>::min()).as_double(), shcore::Exception); EXPECT_THROW(Value(std::numeric_limits<int64_t>::max()).as_double(), shcore::Exception); EXPECT_TRUE(Value(kBiggestIntThatFitsInDouble).as_double() == kBiggestIntThatFitsInDouble); EXPECT_THROW(Value(kBiggestIntThatFitsInDouble + 1).as_double(), shcore::Exception); EXPECT_TRUE(Value(kSmallestIntThatFitsInDouble).as_double() == kSmallestIntThatFitsInDouble); EXPECT_THROW(Value(kSmallestIntThatFitsInDouble - 1).as_double(), shcore::Exception); // uint64_t -> int64_t // min -> OK // max -> error EXPECT_TRUE(std::numeric_limits<uint64_t>::min() == static_cast<uint64_t>( Value(std::numeric_limits<uint64_t>::min()).as_int())); EXPECT_THROW(Value(std::numeric_limits<uint64_t>::max()).as_int(), shcore::Exception); // uint64_t -> double // min -> OK // max -> error EXPECT_TRUE(std::numeric_limits<uint64_t>::min() == Value(std::numeric_limits<uint64_t>::min()).as_double()); EXPECT_THROW(Value(std::numeric_limits<uint64_t>::max()).as_double(), shcore::Exception); EXPECT_TRUE(Value(kBiggestUIntThatFitsInDouble).as_double() == kBiggestUIntThatFitsInDouble); EXPECT_THROW(Value(kBiggestUIntThatFitsInDouble + 1).as_double(), shcore::Exception); // double -> int64_t // min -> error (min for a double means the smallest number in (0.0, 1.0) // interval) max -> error EXPECT_THROW(Value(std::numeric_limits<double>::lowest()).as_int(), shcore::Exception); EXPECT_THROW(Value(std::numeric_limits<double>::max()).as_int(), shcore::Exception); EXPECT_TRUE( Value(static_cast<double>(kBiggestIntThatFitsInDouble)).as_int() == kBiggestIntThatFitsInDouble); EXPECT_THROW( Value(static_cast<double>(kBiggestIntThatFitsInDouble) + 2.0).as_int(), shcore::Exception); EXPECT_TRUE( Value(static_cast<double>(kSmallestIntThatFitsInDouble)).as_int() == kSmallestIntThatFitsInDouble); EXPECT_THROW( Value(static_cast<double>(kSmallestIntThatFitsInDouble) - 2.0).as_int(), shcore::Exception); // double -> uint64_t // min -> error // max -> error EXPECT_THROW(Value(std::numeric_limits<double>::lowest()).as_uint(), shcore::Exception); EXPECT_THROW(Value(std::numeric_limits<double>::max()).as_uint(), shcore::Exception); EXPECT_TRUE( Value(static_cast<double>(kBiggestUIntThatFitsInDouble)).as_uint() == kBiggestUIntThatFitsInDouble); EXPECT_THROW( Value(static_cast<double>(kBiggestUIntThatFitsInDouble) + 2.0).as_uint(), shcore::Exception); } TEST(ValueTests, SimpleString) { const std::string input("Hello world"); shcore::Value v(input); std::string mydescr = v.descr(true); std::string myrepr = v.repr(); std::string myjson = v.json(); EXPECT_EQ(shcore::String, v.type); EXPECT_STREQ("Hello world", mydescr.c_str()); EXPECT_STREQ("\"Hello world\"", myrepr.c_str()); EXPECT_STREQ("\"Hello world\"", myjson.c_str()); shcore::Value v2("Hello / world"); mydescr = v2.descr(true); myrepr = v2.repr(); myjson = v2.json(); EXPECT_EQ(shcore::String, v.type); EXPECT_STREQ("Hello / world", mydescr.c_str()); EXPECT_STREQ("\"Hello / world\"", myrepr.c_str()); EXPECT_STREQ("\"Hello / world\"", myjson.c_str()); // Test unicode literal EXPECT_STREQ(u8"\u0061", shcore::Value::parse("\"\\u0061\"").get_string().c_str()); EXPECT_STREQ(u8"\u0161", shcore::Value::parse("\"\\u0161\"").get_string().c_str()); EXPECT_STREQ(u8"\u0ab0", shcore::Value::parse("\"\\u0ab0\"").get_string().c_str()); EXPECT_STREQ(u8"\u100b0", shcore::Value::parse("\"\\u100b0\"").get_string().c_str()); } TEST(ValueTests, ArrayCompare) { Value arr1(Value::new_array()); Value arr2(Value::new_array()); arr1.as_array()->push_back(Value(12345)); arr2.as_array()->push_back(Value(12345)); EXPECT_TRUE(arr1 == arr2); } static Value do_test(const Argument_list &args) { args.ensure_count(1, 2, "do_test"); int code = args.int_at(0); switch (code) { case 0: // test string_at return Value(args.string_at(1)); } return Value::Null(); } TEST(ValueTests, to_string_container) { Value arr(Value::new_array()); arr.as_array()->push_back(Value("one")); arr.as_array()->push_back(Value("2")); arr.as_array()->push_back(Value("3.4")); std::vector<std::string> v = arr.to_string_container<std::vector<std::string>>(); EXPECT_EQ(3, v.size()); EXPECT_EQ("one", v[0]); EXPECT_EQ("2", v[1]); EXPECT_EQ("3.4", v[2]); std::list<std::string> l = arr.to_string_container<std::list<std::string>>(); auto li = l.begin(); EXPECT_EQ(3, l.size()); EXPECT_EQ("one", *li++); EXPECT_EQ("2", *li++); EXPECT_EQ("3.4", *li++); std::set<std::string> s = arr.to_string_container<std::set<std::string>>(); auto si = s.begin(); EXPECT_EQ(3, s.size()); EXPECT_EQ("2", *si++); EXPECT_EQ("3.4", *si++); EXPECT_EQ("one", *si++); arr.as_array()->push_back(Value(123)); EXPECT_THROW(arr.to_string_container<std::vector<std::string>>(), std::exception); } TEST(Functions, function_wrappers) { std::shared_ptr<Function_base> f(Cpp_function::create( "test", do_test, {{"test_index", Integer}, {"test_arg", String}})); { Argument_list args; args.push_back(Value(1234)); args.push_back(Value("hello")); args.push_back(Value(333)); EXPECT_THROW(f->invoke(args), Exception); } { Argument_list args; args.push_back(Value(0)); args.push_back(Value("hello")); EXPECT_EQ(f->invoke(args), Value("hello")); } } TEST(Parsing, Integer) { { const std::string data = "1984"; shcore::Value v = shcore::Value::parse(data); std::string mydescr = v.descr(true); std::string myrepr = v.repr(); EXPECT_EQ(shcore::Integer, v.type); EXPECT_STREQ("1984", mydescr.c_str()); EXPECT_STREQ("1984", myrepr.c_str()); } { const std::string data = "1984 \n"; shcore::Value v = shcore::Value::parse(data); std::string mydescr = v.descr(true); std::string myrepr = v.repr(); EXPECT_EQ(shcore::Integer, v.type); EXPECT_STREQ("1984", mydescr.c_str()); EXPECT_STREQ("1984", myrepr.c_str()); } } TEST(Parsing, Float) { const std::string data = "3.1"; shcore::Value v = shcore::Value::parse(data); std::string mydescr = v.descr(true); std::string myrepr = v.repr(); EXPECT_EQ(shcore::Float, v.type); double d = std::stod(myrepr); EXPECT_EQ(3.1, d); } TEST(Parsing, Bool) { shcore::Value v; const std::string data = "false"; const std::string data2 = "true"; v = shcore::Value::parse(data); std::string mydescr = v.descr(true); std::string myrepr = v.repr(); EXPECT_EQ(shcore::Bool, v.type); EXPECT_STREQ("false", mydescr.c_str()); EXPECT_STREQ("false", myrepr.c_str()); v = shcore::Value::parse(data2); mydescr = v.descr(true); myrepr = v.repr(); EXPECT_EQ(shcore::Bool, v.type); EXPECT_STREQ("true", mydescr.c_str()); EXPECT_STREQ("true", myrepr.c_str()); const std::string data3 = "FALSE "; const std::string data4 = "TRUE\t"; v = shcore::Value::parse(data3); mydescr = v.descr(true); myrepr = v.repr(); EXPECT_EQ(shcore::Bool, v.type); EXPECT_STREQ("false", mydescr.c_str()); EXPECT_STREQ("false", myrepr.c_str()); v = shcore::Value::parse(data4); mydescr = v.descr(true); myrepr = v.repr(); EXPECT_EQ(shcore::Bool, v.type); EXPECT_STREQ("true", mydescr.c_str()); EXPECT_STREQ("true", myrepr.c_str()); } TEST(Parsing, Null) { const std::string data = "undefined"; shcore::Value v = shcore::Value::parse(data); std::string mydescr = v.descr(true); std::string myrepr = v.repr(); EXPECT_EQ(shcore::Undefined, v.type); EXPECT_STREQ("undefined", mydescr.c_str()); EXPECT_STREQ("undefined", myrepr.c_str()); } TEST(Parsing, String) { const std::string data = "\"Hello World\""; shcore::Value v = shcore::Value::parse(data); std::string mydescr = v.descr(true); std::string myrepr = v.repr(); EXPECT_EQ(shcore::String, v.type); EXPECT_STREQ("Hello World", mydescr.c_str()); EXPECT_STREQ("\"Hello World\"", myrepr.c_str()); } TEST(Parsing, Bad) { EXPECT_THROW(shcore::Value::parse("bla"), shcore::Exception); EXPECT_THROW(shcore::Value::parse("123foo"), shcore::Exception); EXPECT_THROW(shcore::Value::parse("truefoo"), shcore::Exception); EXPECT_THROW(shcore::Value::parse("true123"), shcore::Exception); EXPECT_THROW(shcore::Value::parse("123true"), shcore::Exception); EXPECT_THROW(shcore::Value::parse("falsefoo"), shcore::Exception); EXPECT_THROW(shcore::Value::parse("blafoo"), shcore::Exception); EXPECT_THROW(shcore::Value::parse("nullfoo"), shcore::Exception); EXPECT_THROW(shcore::Value::parse("[true123]"), shcore::Exception); EXPECT_THROW(shcore::Value::parse("{'a':truefoo}"), shcore::Exception); EXPECT_THROW_LIKE(shcore::Value::parse("-"), shcore::Exception, "Error parsing number from: '-'"); EXPECT_THROW_LIKE(shcore::Value::parse("+"), shcore::Exception, "Error parsing number from: '+'"); EXPECT_THROW_LIKE(shcore::Value::parse("45."), shcore::Exception, "Error parsing number from: '45.'"); EXPECT_THROW_LIKE(shcore::Value::parse("45.3e"), shcore::Exception, "Error parsing number from: '45.3e'"); EXPECT_THROW_LIKE(shcore::Value::parse("45.3e+"), shcore::Exception, "Error parsing number from: '45.3e+'"); EXPECT_THROW_LIKE(shcore::Value::parse("45.3e-"), shcore::Exception, "Error parsing number from: '45.3e-'"); EXPECT_THROW_LIKE(shcore::Value::parse("45e"), shcore::Exception, "Error parsing number from: '45e'"); EXPECT_THROW_LIKE(shcore::Value::parse("45e-"), shcore::Exception, "Error parsing number from: '45e-'"); EXPECT_THROW_LIKE(shcore::Value::parse("45e+"), shcore::Exception, "Error parsing number from: '45e+'"); // Not bad EXPECT_NO_THROW(shcore::Value::parse("{'a':true \n}")); EXPECT_NO_THROW(shcore::Value::parse("{'a' : 1 } \t\n")); } TEST(Parsing, StringSingleQuoted) { const std::string data = "\'Hello World\'"; shcore::Value v = shcore::Value::parse(data); std::string mydescr = v.descr(true); std::string myrepr = v.repr(); EXPECT_EQ(shcore::String, v.type); EXPECT_STREQ("Hello World", mydescr.c_str()); EXPECT_STREQ("\"Hello World\"", myrepr.c_str()); } TEST(Parsing, Map) { const std::string data = "{\"null\" : null, \"false\" : false, \"true\" : true, \"string\" : " "\"string value\", \"integer\":560, \"nested\": {\"inner\": \"value\"}}"; shcore::Value v = shcore::Value::parse(data); EXPECT_EQ(shcore::Map, v.type); Value::Map_type_ref map = v.as_map(); EXPECT_TRUE(map->has_key("null")); EXPECT_EQ(shcore::Null, (*map)["null"].type); EXPECT_FALSE((*map)["null"]); EXPECT_TRUE(map->has_key("false")); EXPECT_EQ(shcore::Bool, (*map)["false"].type); EXPECT_FALSE((*map)["false"].as_bool()); EXPECT_TRUE(map->has_key("true")); EXPECT_EQ(shcore::Bool, (*map)["true"].type); EXPECT_TRUE((*map)["true"].as_bool()); EXPECT_TRUE(map->has_key("string")); EXPECT_EQ(shcore::String, (*map)["string"].type); EXPECT_EQ("string value", (*map)["string"].get_string()); EXPECT_TRUE(map->has_key("integer")); EXPECT_EQ(shcore::Integer, (*map)["integer"].type); EXPECT_EQ(560, (*map)["integer"].as_int()); EXPECT_TRUE(map->has_key("nested")); EXPECT_EQ(shcore::Map, (*map)["nested"].type); Value::Map_type_ref nested = (*map)["nested"].as_map(); EXPECT_TRUE(nested->has_key("inner")); EXPECT_EQ(shcore::String, (*nested)["inner"].type); EXPECT_EQ("value", (*nested)["inner"].get_string()); shcore::Value v2 = shcore::Value::parse("{}"); EXPECT_EQ(shcore::Map, v2.type); Value::Map_type_ref map2 = v2.as_map(); EXPECT_EQ(map2->size(), 0); // regression test shcore::Value v3 = shcore::Value::parse( "{'hello': {'item':1}, 'world': {}, 'foo': 'bar', 'bar':32}"); EXPECT_EQ(shcore::Map, v3.type); EXPECT_EQ(4, v3.as_map()->size()); v3 = shcore::Value::parse( "{'hello': {'item':1}, 'world': [], 'foo': 'bar', 'bar':32}"); EXPECT_EQ(shcore::Map, v3.type); EXPECT_EQ(4, v3.as_map()->size()); } TEST(Parsing, Array) { const std::string data = "[450, 450.3, +3.5e-10, \"a string\", [1,2,3], " "{\"nested\":\"document\"}, true, false, null, undefined]"; shcore::Value v = shcore::Value::parse(data); EXPECT_EQ(shcore::Array, v.type); Value::Array_type_ref array = v.as_array(); EXPECT_EQ(shcore::Integer, (*array)[0].type); EXPECT_EQ(450, (*array)[0].as_int()); EXPECT_EQ(shcore::Float, (*array)[1].type); EXPECT_EQ(450.3, (*array)[1].as_double()); EXPECT_EQ(shcore::Float, (*array)[2].type); EXPECT_EQ(3.5e-10, (*array)[2].as_double()); EXPECT_EQ(shcore::String, (*array)[3].type); EXPECT_EQ("a string", (*array)[3].get_string()); EXPECT_EQ(shcore::Array, (*array)[4].type); Value::Array_type_ref inner = (*array)[4].as_array(); EXPECT_EQ(shcore::Integer, (*inner)[0].type); EXPECT_EQ(1, (*inner)[0].as_int()); EXPECT_EQ(shcore::Integer, (*inner)[1].type); EXPECT_EQ(2, (*inner)[1].as_int()); EXPECT_EQ(shcore::Integer, (*inner)[2].type); EXPECT_EQ(3, (*inner)[2].as_int()); EXPECT_EQ(shcore::Map, (*array)[5].type); Value::Map_type_ref nested = (*array)[5].as_map(); EXPECT_TRUE(nested->has_key("nested")); EXPECT_EQ(shcore::String, (*nested)["nested"].type); EXPECT_EQ("document", (*nested)["nested"].get_string()); EXPECT_EQ(shcore::Bool, (*array)[6].type); EXPECT_TRUE((*array)[6].as_bool()); EXPECT_EQ(shcore::Bool, (*array)[7].type); EXPECT_FALSE((*array)[7].as_bool()); EXPECT_EQ(shcore::Null, (*array)[8].type); EXPECT_FALSE((*array)[8]); EXPECT_EQ(shcore::Undefined, (*array)[9].type); EXPECT_FALSE((*array)[9]); shcore::Value v2 = shcore::Value::parse("[]"); EXPECT_EQ(shcore::Array, v2.type); Value::Array_type_ref array2 = v2.as_array(); EXPECT_EQ(array2->size(), 0); } TEST(Parsing, newline_characters) { for (const auto file : {"{\r\n \"k\": [\r\n 1\r\n ]\r\n}", "{\n \"k\": [\n 1\n ]\n}", "{\r \"k\": [\r 1\r ]\r}"}) { SCOPED_TRACE("TESTING: " + std::string(file)); const auto v = shcore::Value::parse(file); EXPECT_EQ(shcore::Value_type::Map, v.type); const auto m = v.as_map(); EXPECT_EQ(1, m->size()); const auto k = m->find("k"); ASSERT_NE(m->end(), k); EXPECT_EQ(shcore::Value_type::Array, k->second.type); const auto a = k->second.as_array(); ASSERT_EQ(1, a->size()); EXPECT_EQ(shcore::Value_type::Integer, (*a)[0].type); EXPECT_EQ(1, (*a)[0].as_int()); } } TEST(Parsing, whitespace_characters) { std::string json = " { \"k\": [ 7 ] } "; for (const auto whitespace : {' ', '\t', '\r', '\n', '\v', '\f'}) { SCOPED_TRACE("TESTING: " + std::to_string(static_cast<int>(whitespace))); json[0] = json[2] = json[7] = json[9] = json[11] = json[13] = json[15] = whitespace; const auto v = shcore::Value::parse(json); EXPECT_EQ(shcore::Value_type::Map, v.type); const auto m = v.as_map(); EXPECT_EQ(1, m->size()); const auto k = m->find("k"); ASSERT_NE(m->end(), k); EXPECT_EQ(shcore::Value_type::Array, k->second.type); const auto a = k->second.as_array(); ASSERT_EQ(1, a->size()); EXPECT_EQ(shcore::Value_type::Integer, (*a)[0].type); EXPECT_EQ(7, (*a)[0].as_int()); } } TEST(Argument_map, all) { { Argument_map args; args["int"] = Value(-1234); args["bool"] = Value(true); args["uint"] = Value(4321); args["str"] = Value("string"); args["flt"] = Value(1.234); args["vec"] = Value::new_array(); args["map"] = Value::new_map(); ASSERT_NO_THROW(args.ensure_keys( {"int", "bool", "uint", "str", "flt", "vec"}, {"map"}, "test1")); ASSERT_THROW(args.ensure_keys({"int", "bool", "uint", "str", "flt"}, {"map"}, "test2"), Exception); ASSERT_THROW(args.ensure_keys({"int", "bool", "uint", "str", "flt", "bla"}, {"map"}, "test3"), Exception); ASSERT_NO_THROW(args.ensure_keys( {"int", "bool", "uint", "str", "flt", "vec"}, {"map", "bla"}, "test2")); EXPECT_EQ(args.bool_at("bool"), true); EXPECT_EQ(args.bool_at("int"), true); EXPECT_EQ(args.bool_at("uint"), true); EXPECT_THROW(args.bool_at("str"), Exception); EXPECT_EQ(args.bool_at("flt"), true); EXPECT_THROW(args.bool_at("vec"), Exception); EXPECT_THROW(args.bool_at("map"), Exception); EXPECT_EQ(args.int_at("bool"), 1); EXPECT_EQ(args.int_at("int"), -1234); EXPECT_EQ(args.int_at("uint"), 4321); EXPECT_THROW(args.int_at("str"), Exception); EXPECT_THROW(args.int_at("flt"), Exception); EXPECT_THROW(args.int_at("vec"), Exception); EXPECT_THROW(args.int_at("map"), Exception); EXPECT_EQ(args.uint_at("bool"), 1); EXPECT_THROW(args.uint_at("int"), Exception); EXPECT_EQ(args.uint_at("uint"), 4321); EXPECT_THROW(args.uint_at("str"), Exception); EXPECT_THROW(args.uint_at("flt"), Exception); EXPECT_THROW(args.uint_at("vec"), Exception); EXPECT_THROW(args.uint_at("map"), Exception); EXPECT_EQ(args.double_at("bool"), 1.0); EXPECT_EQ(args.double_at("int"), -1234.0); EXPECT_EQ(args.double_at("uint"), 4321.0); EXPECT_THROW(args.double_at("str"), Exception); EXPECT_EQ(args.double_at("flt"), 1.234); EXPECT_THROW(args.double_at("vec"), Exception); EXPECT_THROW(args.double_at("map"), Exception); EXPECT_THROW(args.string_at("bool"), Exception); EXPECT_THROW(args.string_at("int"), Exception); EXPECT_THROW(args.string_at("uint"), Exception); EXPECT_EQ(args.string_at("str"), "string"); EXPECT_THROW(args.string_at("flt"), Exception); EXPECT_THROW(args.uint_at("vec"), Exception); EXPECT_THROW(args.uint_at("map"), Exception); } // option alias, success alias 1 { shcore::Value::Map_type_ref options(new shcore::Value::Map_type()); (*options)["user"] = shcore::Value("sample"); (*options)["password"] = shcore::Value("sample"); Argument_map args(*options); ASSERT_NO_THROW( args.ensure_keys({"password|dbPassword"}, {"user"}, "test1")); } // option alias, success alias 2 { shcore::Value::Map_type_ref options(new shcore::Value::Map_type()); (*options)["user"] = shcore::Value("sample"); (*options)["dbPassword"] = shcore::Value("sample"); Argument_map args(*options); ASSERT_NO_THROW( args.ensure_keys({"password|dbPassword"}, {"user"}, "test1")); } // option alias, failure two aliases { shcore::Value::Map_type_ref options(new shcore::Value::Map_type()); (*options)["user"] = shcore::Value("sample"); (*options)["password"] = shcore::Value("sample"); (*options)["dbPassword"] = shcore::Value("sample"); Argument_map args(*options); ASSERT_THROW( args.ensure_keys({"password|dbPassword"}, {"user"}, "multiple aliases"), Exception); } // option alias, failure no aliases { shcore::Value::Map_type_ref options(new shcore::Value::Map_type()); (*options)["user"] = shcore::Value("sample"); Argument_map args(*options); ASSERT_THROW( args.ensure_keys({"password|dbPassword"}, {"user"}, "no aliases"), Exception); } } TEST(Types_descr, double_single_quote) { const char text[] = "There are two common quote characters: \" and ' and we \xe2\x9d\xa4" " Unicode. Backslash \\! \xc3\x98\x00 \x00 \x00." "\x00" "Text after null char with \" and '."; const Value s(text, sizeof(text) - 1); { std::string stdout_string; s.append_descr(stdout_string, -1, '\''); const char expect_text[] = "'There are two common quote characters: \" and \\' and we \xe2\x9d\xa4" " Unicode. Backslash \\\\! \xc3\x98\x00 \x00 \x00." "\x00" "Text after null char with \" and \\'.'"; EXPECT_EQ(stdout_string, std::string(expect_text, sizeof(expect_text) - 1)); } { std::string stdout_string; s.append_descr(stdout_string, -1, '"'); const char expect_text[] = "\"There are two common quote characters: \\\" and ' and we " "\xe2\x9d\xa4" " Unicode. Backslash \\\\! \xc3\x98\x00 \x00 \x00." "\x00" "Text after null char with \\\" and '.\""; EXPECT_EQ(stdout_string, std::string(expect_text, sizeof(expect_text) - 1)); } } TEST(Types_repr, encode_decode_simple) { const char text[] = "There are two common quote characters: \" and ' and we \xe2\x9d\xa4 " "Unicode. Backslash \\! \xc3\x98\x00 \x00 \x00." "\x00" "Text after null char with \" and '."; const Value s(text, sizeof(text) - 1); { const std::string serialized = s.repr(); const char expect_serialized[] = "\"There are two common quote characters: \\\" and \\' and we " "\\xe2\\x9d\\xa4 Unicode. Backslash \\\\! \\xc3\\x98\\x00 \\x00 \\x00." "\\x00" "Text after null char with \\\" and \\'.\""; EXPECT_EQ(serialized, expect_serialized); Value to_original = Value::parse(serialized); EXPECT_EQ(s, to_original); EXPECT_STREQ(text, to_original.get_string().c_str()); EXPECT_EQ(std::string(text, sizeof(text) - 1), to_original.get_string()); } } TEST(Types_repr, encode_decode_nontrivial) { { const char text[] = ""; const Value s(text, sizeof(text) - 1); const std::string serialized = s.repr(); const char expect_serialized[] = "\"\""; EXPECT_EQ(serialized, expect_serialized); Value to_original = Value::parse(serialized); EXPECT_EQ(s, to_original); EXPECT_STREQ(text, to_original.get_string().c_str()); EXPECT_EQ(std::string(text, sizeof(text) - 1), to_original.get_string()); } { const char text[] = "\""; const Value s(text, sizeof(text) - 1); const std::string serialized = s.repr(); const char expect_serialized[] = R"_("\"")_"; EXPECT_EQ(serialized, expect_serialized); Value to_original = Value::parse(serialized); EXPECT_EQ(s, to_original); EXPECT_STREQ(text, to_original.get_string().c_str()); EXPECT_EQ(std::string(text, sizeof(text) - 1), to_original.get_string()); } { const char text[] = "\\"; const Value s(text, sizeof(text) - 1); const std::string serialized = s.repr(); const char expect_serialized[] = R"_("\\")_"; EXPECT_EQ(serialized, expect_serialized); Value to_original = Value::parse(serialized); EXPECT_EQ(s, to_original); EXPECT_STREQ(text, to_original.get_string().c_str()); EXPECT_EQ(std::string(text, sizeof(text) - 1), to_original.get_string()); } } TEST(Types_repr, encode_decode_one_char) { for (int tc = 0; tc <= 0xff; tc++) { const char text[1] = {static_cast<char>(tc)}; const Value s(text, sizeof(text)); { const std::string serialized = s.repr(); Value to_original = Value::parse(serialized); EXPECT_EQ(s, to_original); EXPECT_EQ(std::string(text, sizeof(text)), to_original.get_string()); } } } TEST(Types_repr, encode_decode_random) { std::mt19937 gen(2017); std::uniform_int_distribution<> dist(0, 0xff); for (int tc = 0; tc < 1000; tc++) { char text[1 << 6]; std::generate_n(text, sizeof(text), [&dist, &gen]() { return dist(gen); }); const Value s(text, sizeof(text)); { const std::string serialized = s.repr(); Value to_original = Value::parse(serialized); EXPECT_EQ(s, to_original); EXPECT_EQ(std::string(text, sizeof(text)), to_original.get_string()); } } } /** * Check for proper backtracking for backslashes when compute length in parse * function. * * `text` const char array must be a string returned by `repr()` function. */ TEST(Types_repr, backslash_backtracking) { { const char text[] = {'"', 'p', 's', '\\', '"', 'W', '}', 'q', 't', '"', 0}; EXPECT_THROW_NOTHING(Value::parse(Value(text).get_string()).repr()); EXPECT_THROW_NOTHING(Value::parse(Value(text).repr())); } { const char text[] = {'"', 'p', 's', '\\', '\\', '\\', '"', 'W', '}', 'q', 't', '"', 0}; EXPECT_THROW_NOTHING(Value::parse(Value(text).get_string()).repr()); EXPECT_THROW_NOTHING(Value::parse(Value(text).repr())); } { const char text[] = {'"', 'p', 's', '\\', '\\', '\\', '\\', '\\', '"', 'W', '}', 'q', 't', '"', 0}; EXPECT_THROW_NOTHING(Value::parse(Value(text).get_string()).repr()); EXPECT_THROW_NOTHING(Value::parse(Value(text).repr())); } } TEST(Types_repr, wrong_repr) { { // good. char text[] = {'"', 'p', 's', '\\', '\\', '\\', '\\', '\\', '"', 'W', '}', 'q', 't', '\\', '"', '\\', '"', '\\', '"', '"', 0}; EXPECT_THROW_NOTHING(Value::parse(Value(text).get_string()).repr()); } { // non-escaped `"`. char text[] = {'"', 'p', 's', '\\', '\\', '\\', '\\', '\\', '"', 'W', '}', 'q', 't', '\\', '"', '\\', '"', '"', '"', 0}; EXPECT_THROW(Value::parse(Value(text).get_string()).repr(), shcore::Exception); } { // non-escaped `""`. char text[] = {'"', 'p', 's', '\\', '\\', '\\', '\\', '\\', '"', 'W', '}', 'q', 't', '\\', '"', '"', '"', '"', 0}; EXPECT_THROW(Value::parse(Value(text).get_string()).repr(), shcore::Exception); } { // non-escaped `"""`. char text[] = {'"', 'p', 's', '\\', '\\', '\\', '\\', '\\', '"', 'W', '}', 'q', 't', '"', '"', '"', '"', 0}; EXPECT_THROW(Value::parse(Value(text).get_string()).repr(), shcore::Exception); } } TEST(Types_yaml, array) { const auto array = make_array(); array->emplace_back(Value()); array->emplace_back(Value::Null()); array->emplace_back(Value::False()); array->emplace_back(Value::True()); array->emplace_back("simple string"); array->emplace_back(INT64_C(-1234)); array->emplace_back(UINT64_C(5678)); array->emplace_back(9.01112); const auto sub_array = make_array(); sub_array->emplace_back(1); sub_array->emplace_back(2); sub_array->emplace_back(3); array->emplace_back(std::move(sub_array)); const auto sub_map = make_dict(); sub_map->emplace("4", 7); sub_map->emplace("5", 8); sub_map->emplace("6", 9); array->emplace_back(std::move(sub_map)); static constexpr auto expected = R"(--- - - - false - true - simple string - -1234 - 5678 - 9.01112 - - 1 - 2 - 3 - 4: 7 5: 8 6: 9 )"; EXPECT_EQ(expected, Value{array}.yaml()); } TEST(Types_yaml, map) { const auto map = make_dict(); map->emplace("a", Value()); map->emplace("b", Value::Null()); map->emplace("c", Value::False()); map->emplace("d", Value::True()); map->emplace("e", "simple string"); map->emplace("f", INT64_C(-1234)); map->emplace("g", UINT64_C(5678)); map->emplace("h", 9.01112); const auto sub_array = make_array(); sub_array->emplace_back(1); sub_array->emplace_back(2); sub_array->emplace_back(3); map->emplace("i", std::move(sub_array)); const auto sub_map = make_dict(); sub_map->emplace("4", 7); sub_map->emplace("5", 8); sub_map->emplace("6", 9); map->emplace("j", std::move(sub_map)); static constexpr auto expected = R"(--- a: b: c: false d: true e: simple string f: -1234 g: 5678 h: 9.01112 i: - 1 - 2 - 3 j: 4: 7 5: 8 6: 9 )"; EXPECT_EQ(expected, Value{map}.yaml()); } TEST(Types_yaml, string) { const auto array = make_array(); array->emplace_back(""); array->emplace_back("simple string"); array->emplace_back(" leading space"); array->emplace_back("\tleading tab"); array->emplace_back("trailing space "); array->emplace_back("trailing tab\t"); array->emplace_back("- leading indicator"); array->emplace_back("? leading indicator"); array->emplace_back(": leading indicator"); array->emplace_back(", leading indicator"); array->emplace_back("[ leading indicator"); array->emplace_back("] leading indicator"); array->emplace_back("{ leading indicator"); array->emplace_back("} leading indicator"); array->emplace_back("# leading indicator"); array->emplace_back("& leading indicator"); array->emplace_back("* leading indicator"); array->emplace_back("! leading indicator"); array->emplace_back("| leading indicator"); array->emplace_back("> leading indicator"); array->emplace_back("' leading indicator"); array->emplace_back("\" leading indicator"); array->emplace_back("% leading indicator"); array->emplace_back("@ leading indicator"); array->emplace_back("` leading indicator"); array->emplace_back("the - indicator"); array->emplace_back("the ? indicator"); array->emplace_back("the : indicator"); array->emplace_back("the , indicator"); array->emplace_back("the [ indicator"); array->emplace_back("the ] indicator"); array->emplace_back("the { indicator"); array->emplace_back("the } indicator"); array->emplace_back("the # indicator"); array->emplace_back("the & indicator"); array->emplace_back("the * indicator"); array->emplace_back("the ! indicator"); array->emplace_back("the | indicator"); array->emplace_back("the > indicator"); array->emplace_back("the ' indicator"); array->emplace_back("the \" indicator"); array->emplace_back("the % indicator"); array->emplace_back("the @ indicator"); array->emplace_back("the ` indicator"); array->emplace_back("-_leading indicator without space"); array->emplace_back("?_leading indicator without space"); array->emplace_back(":_leading indicator without space"); array->emplace_back("the \": \" sequence must be quoted"); array->emplace_back("the \" #\" sequence must be quoted"); array->emplace_back("the ': ' -> single quotes must be escaped if quoted"); static constexpr auto expected = R"(--- - '' - simple string - ' leading space' - ' leading tab' - 'trailing space ' - 'trailing tab ' - '- leading indicator' - '? leading indicator' - ': leading indicator' - ', leading indicator' - '[ leading indicator' - '] leading indicator' - '{ leading indicator' - '} leading indicator' - '# leading indicator' - '& leading indicator' - '* leading indicator' - '! leading indicator' - '| leading indicator' - '> leading indicator' - ''' leading indicator' - '" leading indicator' - '% leading indicator' - '@ leading indicator' - '` leading indicator' - the - indicator - the ? indicator - 'the : indicator' - the , indicator - the [ indicator - the ] indicator - the { indicator - the } indicator - 'the # indicator' - the & indicator - the * indicator - the ! indicator - the | indicator - the > indicator - the ' indicator - the " indicator - the % indicator - the @ indicator - the ` indicator - -_leading indicator without space - ?_leading indicator without space - :_leading indicator without space - 'the ": " sequence must be quoted' - 'the " #" sequence must be quoted' - 'the '': '' -> single quotes must be escaped if quoted' )"; EXPECT_EQ(expected, Value{array}.yaml()); } TEST(Types_yaml, multiline_string) { const auto array = make_array(); array->emplace_back("\n"); array->emplace_back("\n\n\n"); array->emplace_back("simple string\nsecond line"); array->emplace_back("second line\nends with a newline\n"); array->emplace_back(" leading space\nsecond line"); array->emplace_back("\tleading tab\nsecond line"); array->emplace_back("trailing space \nsecond line"); array->emplace_back("trailing tab\t\nsecond line"); array->emplace_back("- leading indicator\nsecond line"); array->emplace_back("? leading indicator\nsecond line"); array->emplace_back(": leading indicator\nsecond line"); array->emplace_back(", leading indicator\nsecond line"); array->emplace_back("[ leading indicator\nsecond line"); array->emplace_back("] leading indicator\nsecond line"); array->emplace_back("{ leading indicator\nsecond line"); array->emplace_back("} leading indicator\nsecond line"); array->emplace_back("# leading indicator\nsecond line"); array->emplace_back("& leading indicator\nsecond line"); array->emplace_back("* leading indicator\nsecond line"); array->emplace_back("! leading indicator\nsecond line"); array->emplace_back("| leading indicator\nsecond line"); array->emplace_back("> leading indicator\nsecond line"); array->emplace_back("' leading indicator\nsecond line"); array->emplace_back("\" leading indicator\nsecond line"); array->emplace_back("% leading indicator\nsecond line"); array->emplace_back("@ leading indicator\nsecond line"); array->emplace_back("` leading indicator\nsecond line"); array->emplace_back("the - indicator\nsecond line"); array->emplace_back("the ? indicator\nsecond line"); array->emplace_back("the : indicator\nsecond line"); array->emplace_back("the , indicator\nsecond line"); array->emplace_back("the [ indicator\nsecond line"); array->emplace_back("the ] indicator\nsecond line"); array->emplace_back("the { indicator\nsecond line"); array->emplace_back("the } indicator\nsecond line"); array->emplace_back("the # indicator\nsecond line"); array->emplace_back("the & indicator\nsecond line"); array->emplace_back("the * indicator\nsecond line"); array->emplace_back("the ! indicator\nsecond line"); array->emplace_back("the | indicator\nsecond line"); array->emplace_back("the > indicator\nsecond line"); array->emplace_back("the ' indicator\nsecond line"); array->emplace_back("the \" indicator\nsecond line"); array->emplace_back("the % indicator\nsecond line"); array->emplace_back("the @ indicator\nsecond line"); array->emplace_back("the ` indicator\nsecond line"); array->emplace_back("-_leading indicator without space\nsecond line"); array->emplace_back("?_leading indicator without space\nsecond line"); array->emplace_back(":_leading indicator without space\nsecond line"); array->emplace_back("the \": \" sequence\nsecond line"); array->emplace_back("the \" #\" sequence\nsecond line"); static constexpr auto expected = R"(--- - |+ - |+ - |- simple string second line - | second line ends with a newline - |4- leading space second line - |4- leading tab second line - |- trailing space second line - |- trailing tab second line - |- - leading indicator second line - |- ? leading indicator second line - |- : leading indicator second line - |- , leading indicator second line - |- [ leading indicator second line - |- ] leading indicator second line - |- { leading indicator second line - |- } leading indicator second line - |- # leading indicator second line - |- & leading indicator second line - |- * leading indicator second line - |- ! leading indicator second line - |- | leading indicator second line - |- > leading indicator second line - |- ' leading indicator second line - |- " leading indicator second line - |- % leading indicator second line - |- @ leading indicator second line - |- ` leading indicator second line - |- the - indicator second line - |- the ? indicator second line - |- the : indicator second line - |- the , indicator second line - |- the [ indicator second line - |- the ] indicator second line - |- the { indicator second line - |- the } indicator second line - |- the # indicator second line - |- the & indicator second line - |- the * indicator second line - |- the ! indicator second line - |- the | indicator second line - |- the > indicator second line - |- the ' indicator second line - |- the " indicator second line - |- the % indicator second line - |- the @ indicator second line - |- the ` indicator second line - |- -_leading indicator without space second line - |- ?_leading indicator without space second line - |- :_leading indicator without space second line - |- the ": " sequence second line - |- the " #" sequence second line )"; EXPECT_EQ(expected, Value{array}.yaml()); } } // namespace tests } // namespace shcore