diff options
Diffstat (limited to 'src/rapidjson/test')
35 files changed, 13328 insertions, 0 deletions
diff --git a/src/rapidjson/test/CMakeLists.txt b/src/rapidjson/test/CMakeLists.txt new file mode 100644 index 000000000..11c1b04c7 --- /dev/null +++ b/src/rapidjson/test/CMakeLists.txt @@ -0,0 +1,20 @@ +find_package(GTestSrc) + +IF(GTESTSRC_FOUND) + enable_testing() + + if (WIN32 AND (NOT CYGWIN) AND (NOT MINGW)) + set(gtest_disable_pthreads ON) + set(gtest_force_shared_crt ON) + endif() + + add_subdirectory(${GTEST_SOURCE_DIR} ${CMAKE_BINARY_DIR}/googletest) + include_directories(SYSTEM ${GTEST_INCLUDE_DIR}) + + set(TEST_LIBRARIES gtest gtest_main) + + add_custom_target(tests ALL) + add_subdirectory(perftest) + add_subdirectory(unittest) + +ENDIF(GTESTSRC_FOUND) diff --git a/src/rapidjson/test/perftest/CMakeLists.txt b/src/rapidjson/test/perftest/CMakeLists.txt new file mode 100644 index 000000000..c33aae469 --- /dev/null +++ b/src/rapidjson/test/perftest/CMakeLists.txt @@ -0,0 +1,26 @@ +set(PERFTEST_SOURCES + misctest.cpp + perftest.cpp + platformtest.cpp + rapidjsontest.cpp + schematest.cpp) + +add_executable(perftest ${PERFTEST_SOURCES}) +target_link_libraries(perftest ${TEST_LIBRARIES}) + +add_dependencies(tests perftest) + +find_program(CCACHE_FOUND ccache) +if(CCACHE_FOUND) + set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE ccache) + set_property(GLOBAL PROPERTY RULE_LAUNCH_LINK ccache) + if (CMAKE_CXX_COMPILER_ID MATCHES "Clang") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Qunused-arguments -fcolor-diagnostics") + endif() +endif(CCACHE_FOUND) + +IF(NOT (CMAKE_BUILD_TYPE STREQUAL "Debug")) +add_test(NAME perftest + COMMAND ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/perftest + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/bin) +ENDIF() diff --git a/src/rapidjson/test/perftest/misctest.cpp b/src/rapidjson/test/perftest/misctest.cpp new file mode 100644 index 000000000..aac847784 --- /dev/null +++ b/src/rapidjson/test/perftest/misctest.cpp @@ -0,0 +1,974 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#include "perftest.h" + +#if TEST_MISC + +#define __STDC_FORMAT_MACROS +#include "rapidjson/stringbuffer.h" + +#define protected public +#include "rapidjson/writer.h" +#undef private + +class Misc : public PerfTest { +}; + +// Copyright (c) 2008-2010 Bjoern Hoehrmann <bjoern@hoehrmann.de> +// See http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ for details. + +#define UTF8_ACCEPT 0 +#define UTF8_REJECT 12 + +static const unsigned char utf8d[] = { + // The first part of the table maps bytes to character classes that + // to reduce the size of the transition table and create bitmasks. + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 8,8,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, + 10,3,3,3,3,3,3,3,3,3,3,3,3,4,3,3, 11,6,6,6,5,8,8,8,8,8,8,8,8,8,8,8, + + // The second part is a transition table that maps a combination + // of a state of the automaton and a character class to a state. + 0,12,24,36,60,96,84,12,12,12,48,72, 12,12,12,12,12,12,12,12,12,12,12,12, + 12, 0,12,12,12,12,12, 0,12, 0,12,12, 12,24,12,12,12,12,12,24,12,24,12,12, + 12,12,12,12,12,12,12,24,12,12,12,12, 12,24,12,12,12,12,12,12,12,24,12,12, + 12,12,12,12,12,12,12,36,12,36,12,12, 12,36,12,12,12,12,12,36,12,36,12,12, + 12,36,12,12,12,12,12,12,12,12,12,12, +}; + +static unsigned inline decode(unsigned* state, unsigned* codep, unsigned byte) { + unsigned type = utf8d[byte]; + + *codep = (*state != UTF8_ACCEPT) ? + (byte & 0x3fu) | (*codep << 6) : + (0xff >> type) & (byte); + + *state = utf8d[256 + *state + type]; + return *state; +} + +static bool IsUTF8(unsigned char* s) { + unsigned codepoint, state = 0; + + while (*s) + decode(&state, &codepoint, *s++); + + return state == UTF8_ACCEPT; +} + +TEST_F(Misc, Hoehrmann_IsUTF8) { + for (size_t i = 0; i < kTrialCount; i++) { + EXPECT_TRUE(IsUTF8((unsigned char*)json_)); + } +} + +//////////////////////////////////////////////////////////////////////////////// +// CountDecimalDigit: Count number of decimal places + +inline unsigned CountDecimalDigit_naive(unsigned n) { + unsigned count = 1; + while (n >= 10) { + n /= 10; + count++; + } + return count; +} + +inline unsigned CountDecimalDigit_enroll4(unsigned n) { + unsigned count = 1; + while (n >= 10000) { + n /= 10000u; + count += 4; + } + if (n < 10) return count; + if (n < 100) return count + 1; + if (n < 1000) return count + 2; + return count + 3; +} + +inline unsigned CountDecimalDigit64_enroll4(uint64_t n) { + unsigned count = 1; + while (n >= 10000) { + n /= 10000u; + count += 4; + } + if (n < 10) return count; + if (n < 100) return count + 1; + if (n < 1000) return count + 2; + return count + 3; +} + +inline unsigned CountDecimalDigit_fast(unsigned n) { + static const uint32_t powers_of_10[] = { + 0, + 10, + 100, + 1000, + 10000, + 100000, + 1000000, + 10000000, + 100000000, + 1000000000 + }; + +#if defined(_M_IX86) || defined(_M_X64) + unsigned long i = 0; + _BitScanReverse(&i, n | 1); + uint32_t t = (i + 1) * 1233 >> 12; +#elif defined(__GNUC__) + uint32_t t = (32 - __builtin_clz(n | 1)) * 1233 >> 12; +#else +#error +#endif + return t - (n < powers_of_10[t]) + 1; +} + +inline unsigned CountDecimalDigit64_fast(uint64_t n) { + static const uint64_t powers_of_10[] = { + 0, + 10, + 100, + 1000, + 10000, + 100000, + 1000000, + 10000000, + 100000000, + 1000000000, + 10000000000, + 100000000000, + 1000000000000, + 10000000000000, + 100000000000000, + 1000000000000000, + 10000000000000000, + 100000000000000000, + 1000000000000000000, + 10000000000000000000U + }; + +#if defined(_M_IX86) + uint64_t m = n | 1; + unsigned long i = 0; + if (_BitScanReverse(&i, m >> 32)) + i += 32; + else + _BitScanReverse(&i, m & 0xFFFFFFFF); + uint32_t t = (i + 1) * 1233 >> 12; +#elif defined(_M_X64) + unsigned long i = 0; + _BitScanReverse64(&i, n | 1); + uint32_t t = (i + 1) * 1233 >> 12; +#elif defined(__GNUC__) + uint32_t t = (64 - __builtin_clzll(n | 1)) * 1233 >> 12; +#else +#error +#endif + + return t - (n < powers_of_10[t]) + 1; +} + +#if 0 +// Exhaustive, very slow +TEST_F(Misc, CountDecimalDigit_Verify) { + unsigned i = 0; + do { + if (i % (65536 * 256) == 0) + printf("%u\n", i); + ASSERT_EQ(CountDecimalDigit_enroll4(i), CountDecimalDigit_fast(i)); + i++; + } while (i != 0); +} + +static const unsigned kDigits10Trial = 1000000000u; +TEST_F(Misc, CountDecimalDigit_naive) { + unsigned sum = 0; + for (unsigned i = 0; i < kDigits10Trial; i++) + sum += CountDecimalDigit_naive(i); + printf("%u\n", sum); +} + +TEST_F(Misc, CountDecimalDigit_enroll4) { + unsigned sum = 0; + for (unsigned i = 0; i < kDigits10Trial; i++) + sum += CountDecimalDigit_enroll4(i); + printf("%u\n", sum); +} + +TEST_F(Misc, CountDecimalDigit_fast) { + unsigned sum = 0; + for (unsigned i = 0; i < kDigits10Trial; i++) + sum += CountDecimalDigit_fast(i); + printf("%u\n", sum); +} +#endif + +TEST_F(Misc, CountDecimalDigit64_VerifyFast) { + uint64_t i = 1, j; + do { + //printf("%" PRIu64 "\n", i); + ASSERT_EQ(CountDecimalDigit64_enroll4(i), CountDecimalDigit64_fast(i)); + j = i; + i *= 3; + } while (j < i); +} + +//////////////////////////////////////////////////////////////////////////////// +// integer-to-string conversion + +// https://gist.github.com/anonymous/7179097 +static const int randval[] ={ + 936116, 369532, 453755, -72860, 209713, 268347, 435278, -360266, -416287, -182064, + -644712, 944969, 640463, -366588, 471577, -69401, -744294, -505829, 923883, 831785, + -601136, -636767, -437054, 591718, 100758, 231907, -719038, 973540, -605220, 506659, + -871653, 462533, 764843, -919138, 404305, -630931, -288711, -751454, -173726, -718208, + 432689, -281157, 360737, 659827, 19174, -376450, 769984, -858198, 439127, 734703, + -683426, 7, 386135, 186997, -643900, -744422, -604708, -629545, 42313, -933592, + -635566, 182308, 439024, -367219, -73924, -516649, 421935, -470515, 413507, -78952, + -427917, -561158, 737176, 94538, 572322, 405217, 709266, -357278, -908099, -425447, + 601119, 750712, -862285, -177869, 900102, 384877, 157859, -641680, 503738, -702558, + 278225, 463290, 268378, -212840, 580090, 347346, -473985, -950968, -114547, -839893, + -738032, -789424, 409540, 493495, 432099, 119755, 905004, -174834, 338266, 234298, + 74641, -965136, -754593, 685273, 466924, 920560, 385062, 796402, -67229, 994864, + 376974, 299869, -647540, -128724, 469890, -163167, -547803, -743363, 486463, -621028, + 612288, 27459, -514224, 126342, -66612, 803409, -777155, -336453, -284002, 472451, + 342390, -163630, 908356, -456147, -825607, 268092, -974715, 287227, 227890, -524101, + 616370, -782456, 922098, -624001, -813690, 171605, -192962, 796151, 707183, -95696, + -23163, -721260, 508892, 430715, 791331, 482048, -996102, 863274, 275406, -8279, + -556239, -902076, 268647, -818565, 260069, -798232, -172924, -566311, -806503, -885992, + 813969, -78468, 956632, 304288, 494867, -508784, 381751, 151264, 762953, 76352, + 594902, 375424, 271700, -743062, 390176, 924237, 772574, 676610, 435752, -153847, + 3959, -971937, -294181, -538049, -344620, -170136, 19120, -703157, 868152, -657961, + -818631, 219015, -872729, -940001, -956570, 880727, -345910, 942913, -942271, -788115, + 225294, 701108, -517736, -416071, 281940, 488730, 942698, 711494, 838382, -892302, + -533028, 103052, 528823, 901515, 949577, 159364, 718227, -241814, -733661, -462928, + -495829, 165170, 513580, -629188, -509571, -459083, 198437, 77198, -644612, 811276, + -422298, -860842, -52584, 920369, 686424, -530667, -243476, 49763, 345866, -411960, + -114863, 470810, -302860, 683007, -509080, 2, -174981, -772163, -48697, 447770, + -268246, 213268, 269215, 78810, -236340, -639140, -864323, 505113, -986569, -325215, + 541859, 163070, -819998, -645161, -583336, 573414, 696417, -132375, 3, -294501, + 320435, 682591, 840008, 351740, 426951, 609354, 898154, -943254, 227321, -859793, + -727993, 44137, -497965, -782239, 14955, -746080, -243366, 9837, -233083, 606507, + -995864, -615287, -994307, 602715, 770771, -315040, 610860, 446102, -307120, 710728, + -590392, -230474, -762625, -637525, 134963, -202700, -766902, -985541, 218163, 682009, + 926051, 525156, -61195, 403211, -810098, 245539, -431733, 179998, -806533, 745943, + 447597, 131973, -187130, 826019, 286107, -937230, -577419, 20254, 681802, -340500, + 323080, 266283, -667617, 309656, 416386, 611863, 759991, -534257, 523112, -634892, + -169913, -204905, -909867, -882185, -944908, 741811, -717675, 967007, -317396, 407230, + -412805, 792905, 994873, 744793, -456797, 713493, 355232, 116900, -945199, 880539, + 342505, -580824, -262273, 982968, -349497, -735488, 311767, -455191, 570918, 389734, + -958386, 10262, -99267, 155481, 304210, 204724, 704367, -144893, -233664, -671441, + 896849, 408613, 762236, 322697, 981321, 688476, 13663, -970704, -379507, 896412, + 977084, 348869, 875948, 341348, 318710, 512081, 6163, 669044, 833295, 811883, + 708756, -802534, -536057, 608413, -389625, -694603, 541106, -110037, 720322, -540581, + 645420, 32980, 62442, 510157, -981870, -87093, -325960, -500494, -718291, -67889, + 991501, 374804, 769026, -978869, 294747, 714623, 413327, -199164, 671368, 804789, + -362507, 798196, -170790, -568895, -869379, 62020, -316693, -837793, 644994, -39341, + -417504, -243068, -957756, 99072, 622234, -739992, 225668, 8863, -505910, 82483, + -559244, 241572, 1315, -36175, -54990, 376813, -11, 162647, -688204, -486163, + -54934, -197470, 744223, -762707, 732540, 996618, 351561, -445933, -898491, 486531, + 456151, 15276, 290186, -817110, -52995, 313046, -452533, -96267, 94470, -500176, + -818026, -398071, -810548, -143325, -819741, 1338, -897676, -101577, -855445, 37309, + 285742, 953804, -777927, -926962, -811217, -936744, -952245, -802300, -490188, -964953, + -552279, 329142, -570048, -505756, 682898, -381089, -14352, 175138, 152390, -582268, + -485137, 717035, 805329, 239572, -730409, 209643, -184403, -385864, 675086, 819648, + 629058, -527109, -488666, -171981, 532788, 552441, 174666, 984921, 766514, 758787, + 716309, 338801, -978004, -412163, 876079, -734212, 789557, -160491, -522719, 56644, + -991, -286038, -53983, 663740, 809812, 919889, -717502, -137704, 220511, 184396, + -825740, -588447, 430870, 124309, 135956, 558662, -307087, -788055, -451328, 812260, + 931601, 324347, -482989, -117858, -278861, 189068, -172774, 929057, 293787, 198161, + -342386, -47173, 906555, -759955, -12779, 777604, -97869, 899320, 927486, -25284, + -848550, 259450, -485856, -17820, 88, 171400, 235492, -326783, -340793, 886886, + 112428, -246280, 5979, 648444, -114982, 991013, -56489, -9497, 419706, 632820, + -341664, 393926, -848977, -22538, 257307, 773731, -905319, 491153, 734883, -868212, + -951053, 644458, -580758, 764735, 584316, 297077, 28852, -397710, -953669, 201772, + 879050, -198237, -588468, 448102, -116837, 770007, -231812, 642906, -582166, -885828, + 9, 305082, -996577, 303559, 75008, -772956, -447960, 599825, -295552, 870739, + -386278, -950300, 485359, -457081, 629461, -850276, 550496, -451755, -620841, -11766, + -950137, 832337, 28711, -273398, -507197, 91921, -271360, -705991, -753220, -388968, + 967945, 340434, -320883, -662793, -554617, -574568, 477946, -6148, -129519, 689217, + 920020, -656315, -974523, -212525, 80921, -612532, 645096, 545655, 655713, -591631, + -307385, -816688, -618823, -113713, 526430, 673063, 735916, -809095, -850417, 639004, + 432281, -388185, 270708, 860146, -39902, -786157, -258180, -246169, -966720, -264957, + 548072, -306010, -57367, -635665, 933824, 70553, -989936, -488741, 72411, -452509, + 529831, 956277, 449019, -577850, -360986, -803418, 48833, 296073, 203430, 609591, + 715483, 470964, 658106, -718254, -96424, 790163, 334739, 181070, -373578, 5, + -435088, 329841, 330939, -256602, 394355, 912412, 231910, 927278, -661933, 788539, + -769664, -893274, -96856, 298205, 901043, -608122, -527430, 183618, -553963, -35246, + -393924, 948832, -483198, 594501, 35460, -407007, 93494, -336881, -634072, 984205, + -812161, 944664, -31062, 753872, 823933, -69566, 50445, 290147, 85134, 34706, + 551902, 405202, -991246, -84642, 154341, 316432, -695101, -651588, -5030, 137564, + -294665, 332541, 528307, -90572, -344923, 523766, -758498, -968047, 339028, 494578, + 593129, -725773, 31834, -718406, -208638, 159665, -2043, 673344, -442767, 75816, + 755442, 769257, -158730, -410272, 691688, 589550, -878398, -184121, 460679, 346312, + 294163, -544602, 653308, 254167, -276979, 52073, -892684, 887653, -41222, 983065, + -68258, -408799, -99069, -674069, -863635, -32890, 622757, -743862, 40872, -4837, + -967228, 522370, -903951, -818669, 524459, 514702, 925801, 20007, -299229, 579348, + 626021, 430089, 348139, -562692, -607728, -130606, -928451, -424793, -458647, -448892, + -312230, 143337, 109746, 880042, -339658, -785614, 938995, 540916, 118429, 661351, + -402967, 404729, -40918, -976535, 743230, 713110, 440182, -381314, -499252, 74613, + 193652, 912717, 491323, 583633, 324691, 459397, 281253, 195540, -2764, -888651, + 892449, 132663, -478373, -430002, -314551, 527826, 247165, 557966, 554778, 481531, + -946634, 431685, -769059, -348371, 174046, 184597, -354867, 584422, 227390, -850397, + -542924, -849093, -737769, 325359, 736314, 269101, 767940, 674809, 81413, -447458, + 445076, 189072, 906218, 502688, -718476, -863827, -731381, 100660, 623249, 710008, + 572060, 922203, 685740, 55096, 263394, -243695, -353910, -516788, 388471, 455165, + 844103, -643772, 363976, 268875, -899450, 104470, 104029, -238874, -274659, 732969, + -676443, 953291, -916289, -861849, -242344, 958083, -479593, -970395, 799831, 277841, + -243236, -283462, -201510, 166263, -259105, -575706, 878926, 891064, 895297, 655262, + -34807, -809833, -89281, 342585, 554920, 1, 902141, -333425, 139703, 852318, + -618438, 329498, -932596, -692836, -513372, 733656, -523411, 85779, 500478, -682697, + -502836, 138776, 156341, -420037, -557964, -556378, 710993, -50383, -877159, 916334, + 132996, 583516, -603392, -111615, -12288, -780214, 476780, 123327, 137607, 519956, + 745837, 17358, -158581, -53490 +}; +static const size_t randvalCount = sizeof(randval) / sizeof(randval[0]); +static const size_t kItoaTrialCount = 10000; + +static const char digits[201] = +"0001020304050607080910111213141516171819" +"2021222324252627282930313233343536373839" +"4041424344454647484950515253545556575859" +"6061626364656667686970717273747576777879" +"8081828384858687888990919293949596979899"; + +// Prevent code being optimized out +//#define OUTPUT_LENGTH(length) printf("", length) +#define OUTPUT_LENGTH(length) printf("%u\n", (unsigned)length) + +template<typename OutputStream> +class Writer1 { +public: + Writer1() : os_() {} + Writer1(OutputStream& os) : os_(&os) {} + + void Reset(OutputStream& os) { + os_ = &os; + } + + bool WriteInt(int i) { + if (i < 0) { + os_->Put('-'); + i = -i; + } + return WriteUint((unsigned)i); + } + + bool WriteUint(unsigned u) { + char buffer[10]; + char *p = buffer; + do { + *p++ = char(u % 10) + '0'; + u /= 10; + } while (u > 0); + + do { + --p; + os_->Put(*p); + } while (p != buffer); + return true; + } + + bool WriteInt64(int64_t i64) { + if (i64 < 0) { + os_->Put('-'); + i64 = -i64; + } + WriteUint64((uint64_t)i64); + return true; + } + + bool WriteUint64(uint64_t u64) { + char buffer[20]; + char *p = buffer; + do { + *p++ = char(u64 % 10) + '0'; + u64 /= 10; + } while (u64 > 0); + + do { + --p; + os_->Put(*p); + } while (p != buffer); + return true; + } + +private: + OutputStream* os_; +}; + +template<> +bool Writer1<rapidjson::StringBuffer>::WriteUint(unsigned u) { + char buffer[10]; + char* p = buffer; + do { + *p++ = char(u % 10) + '0'; + u /= 10; + } while (u > 0); + + char* d = os_->Push(p - buffer); + do { + --p; + *d++ = *p; + } while (p != buffer); + return true; +} + +// Using digits LUT to reduce divsion/modulo +template<typename OutputStream> +class Writer2 { +public: + Writer2() : os_() {} + Writer2(OutputStream& os) : os_(&os) {} + + void Reset(OutputStream& os) { + os_ = &os; + } + + bool WriteInt(int i) { + if (i < 0) { + os_->Put('-'); + i = -i; + } + return WriteUint((unsigned)i); + } + + bool WriteUint(unsigned u) { + char buffer[10]; + char* p = buffer; + while (u >= 100) { + const unsigned i = (u % 100) << 1; + u /= 100; + *p++ = digits[i + 1]; + *p++ = digits[i]; + } + if (u < 10) + *p++ = char(u) + '0'; + else { + const unsigned i = u << 1; + *p++ = digits[i + 1]; + *p++ = digits[i]; + } + + do { + --p; + os_->Put(*p); + } while (p != buffer); + return true; + } + + bool WriteInt64(int64_t i64) { + if (i64 < 0) { + os_->Put('-'); + i64 = -i64; + } + WriteUint64((uint64_t)i64); + return true; + } + + bool WriteUint64(uint64_t u64) { + char buffer[20]; + char* p = buffer; + while (u64 >= 100) { + const unsigned i = static_cast<unsigned>(u64 % 100) << 1; + u64 /= 100; + *p++ = digits[i + 1]; + *p++ = digits[i]; + } + if (u64 < 10) + *p++ = char(u64) + '0'; + else { + const unsigned i = static_cast<unsigned>(u64) << 1; + *p++ = digits[i + 1]; + *p++ = digits[i]; + } + + do { + --p; + os_->Put(*p); + } while (p != buffer); + return true; + } + +private: + OutputStream* os_; +}; + +// First pass to count digits +template<typename OutputStream> +class Writer3 { +public: + Writer3() : os_() {} + Writer3(OutputStream& os) : os_(&os) {} + + void Reset(OutputStream& os) { + os_ = &os; + } + + bool WriteInt(int i) { + if (i < 0) { + os_->Put('-'); + i = -i; + } + return WriteUint((unsigned)i); + } + + bool WriteUint(unsigned u) { + char buffer[10]; + char *p = buffer; + do { + *p++ = char(u % 10) + '0'; + u /= 10; + } while (u > 0); + + do { + --p; + os_->Put(*p); + } while (p != buffer); + return true; + } + + bool WriteInt64(int64_t i64) { + if (i64 < 0) { + os_->Put('-'); + i64 = -i64; + } + WriteUint64((uint64_t)i64); + return true; + } + + bool WriteUint64(uint64_t u64) { + char buffer[20]; + char *p = buffer; + do { + *p++ = char(u64 % 10) + '0'; + u64 /= 10; + } while (u64 > 0); + + do { + --p; + os_->Put(*p); + } while (p != buffer); + return true; + } + +private: + void WriteUintReverse(char* d, unsigned u) { + do { + *--d = char(u % 10) + '0'; + u /= 10; + } while (u > 0); + } + + void WriteUint64Reverse(char* d, uint64_t u) { + do { + *--d = char(u % 10) + '0'; + u /= 10; + } while (u > 0); + } + + OutputStream* os_; +}; + +template<> +inline bool Writer3<rapidjson::StringBuffer>::WriteUint(unsigned u) { + unsigned digit = CountDecimalDigit_fast(u); + WriteUintReverse(os_->Push(digit) + digit, u); + return true; +} + +template<> +inline bool Writer3<rapidjson::InsituStringStream>::WriteUint(unsigned u) { + unsigned digit = CountDecimalDigit_fast(u); + WriteUintReverse(os_->Push(digit) + digit, u); + return true; +} + +template<> +inline bool Writer3<rapidjson::StringBuffer>::WriteUint64(uint64_t u) { + unsigned digit = CountDecimalDigit64_fast(u); + WriteUint64Reverse(os_->Push(digit) + digit, u); + return true; +} + +template<> +inline bool Writer3<rapidjson::InsituStringStream>::WriteUint64(uint64_t u) { + unsigned digit = CountDecimalDigit64_fast(u); + WriteUint64Reverse(os_->Push(digit) + digit, u); + return true; +} + +// Using digits LUT to reduce divsion/modulo, two passes +template<typename OutputStream> +class Writer4 { +public: + Writer4() : os_() {} + Writer4(OutputStream& os) : os_(&os) {} + + void Reset(OutputStream& os) { + os_ = &os; + } + + bool WriteInt(int i) { + if (i < 0) { + os_->Put('-'); + i = -i; + } + return WriteUint((unsigned)i); + } + + bool WriteUint(unsigned u) { + char buffer[10]; + char* p = buffer; + while (u >= 100) { + const unsigned i = (u % 100) << 1; + u /= 100; + *p++ = digits[i + 1]; + *p++ = digits[i]; + } + if (u < 10) + *p++ = char(u) + '0'; + else { + const unsigned i = u << 1; + *p++ = digits[i + 1]; + *p++ = digits[i]; + } + + do { + --p; + os_->Put(*p); + } while (p != buffer); + return true; + } + + bool WriteInt64(int64_t i64) { + if (i64 < 0) { + os_->Put('-'); + i64 = -i64; + } + WriteUint64((uint64_t)i64); + return true; + } + + bool WriteUint64(uint64_t u64) { + char buffer[20]; + char* p = buffer; + while (u64 >= 100) { + const unsigned i = static_cast<unsigned>(u64 % 100) << 1; + u64 /= 100; + *p++ = digits[i + 1]; + *p++ = digits[i]; + } + if (u64 < 10) + *p++ = char(u64) + '0'; + else { + const unsigned i = static_cast<unsigned>(u64) << 1; + *p++ = digits[i + 1]; + *p++ = digits[i]; + } + + do { + --p; + os_->Put(*p); + } while (p != buffer); + return true; + } + +private: + void WriteUintReverse(char* d, unsigned u) { + while (u >= 100) { + const unsigned i = (u % 100) << 1; + u /= 100; + *--d = digits[i + 1]; + *--d = digits[i]; + } + if (u < 10) { + *--d = char(u) + '0'; + } + else { + const unsigned i = u << 1; + *--d = digits[i + 1]; + *--d = digits[i]; + } + } + + void WriteUint64Reverse(char* d, uint64_t u) { + while (u >= 100) { + const unsigned i = (u % 100) << 1; + u /= 100; + *--d = digits[i + 1]; + *--d = digits[i]; + } + if (u < 10) { + *--d = char(u) + '0'; + } + else { + const unsigned i = u << 1; + *--d = digits[i + 1]; + *--d = digits[i]; + } + } + + OutputStream* os_; +}; + +template<> +inline bool Writer4<rapidjson::StringBuffer>::WriteUint(unsigned u) { + unsigned digit = CountDecimalDigit_fast(u); + WriteUintReverse(os_->Push(digit) + digit, u); + return true; +} + +template<> +inline bool Writer4<rapidjson::InsituStringStream>::WriteUint(unsigned u) { + unsigned digit = CountDecimalDigit_fast(u); + WriteUintReverse(os_->Push(digit) + digit, u); + return true; +} + +template<> +inline bool Writer4<rapidjson::StringBuffer>::WriteUint64(uint64_t u) { + unsigned digit = CountDecimalDigit64_fast(u); + WriteUint64Reverse(os_->Push(digit) + digit, u); + return true; +} + +template<> +inline bool Writer4<rapidjson::InsituStringStream>::WriteUint64(uint64_t u) { + unsigned digit = CountDecimalDigit64_fast(u); + WriteUint64Reverse(os_->Push(digit) + digit, u); + return true; +} + +template <typename Writer> +void itoa_Writer_StringBufferVerify() { + rapidjson::StringBuffer sb; + Writer writer(sb); + for (size_t j = 0; j < randvalCount; j++) { + char buffer[32]; + sprintf(buffer, "%d", randval[j]); + writer.WriteInt(randval[j]); + ASSERT_STREQ(buffer, sb.GetString()); + sb.Clear(); + } +} + +template <typename Writer> +void itoa_Writer_InsituStringStreamVerify() { + Writer writer; + for (size_t j = 0; j < randvalCount; j++) { + char buffer[32]; + sprintf(buffer, "%d", randval[j]); + char buffer2[32]; + rapidjson::InsituStringStream ss(buffer2); + writer.Reset(ss); + char* begin = ss.PutBegin(); + writer.WriteInt(randval[j]); + ss.Put('\0'); + ss.PutEnd(begin); + ASSERT_STREQ(buffer, buffer2); + } +} + +template <typename Writer> +void itoa_Writer_StringBuffer() { + size_t length = 0; + + rapidjson::StringBuffer sb; + Writer writer(sb); + + for (size_t i = 0; i < kItoaTrialCount; i++) { + for (size_t j = 0; j < randvalCount; j++) { + writer.WriteInt(randval[j]); + length += sb.GetSize(); + sb.Clear(); + } + } + OUTPUT_LENGTH(length); +} + +template <typename Writer> +void itoa_Writer_InsituStringStream() { + size_t length = 0; + + char buffer[32]; + Writer writer; + for (size_t i = 0; i < kItoaTrialCount; i++) { + for (size_t j = 0; j < randvalCount; j++) { + rapidjson::InsituStringStream ss(buffer); + writer.Reset(ss); + char* begin = ss.PutBegin(); + writer.WriteInt(randval[j]); + length += ss.PutEnd(begin); + } + } + OUTPUT_LENGTH(length); +}; + +template <typename Writer> +void itoa64_Writer_StringBufferVerify() { + rapidjson::StringBuffer sb; + Writer writer(sb); + for (size_t j = 0; j < randvalCount; j++) { + char buffer[32]; + int64_t x = randval[j] * randval[j]; + sprintf(buffer, "%" PRIi64, x); + writer.WriteInt64(x); + ASSERT_STREQ(buffer, sb.GetString()); + sb.Clear(); + } +} + +template <typename Writer> +void itoa64_Writer_InsituStringStreamVerify() { + Writer writer; + for (size_t j = 0; j < randvalCount; j++) { + char buffer[32]; + int64_t x = randval[j] * randval[j]; + sprintf(buffer, "%" PRIi64, x); + char buffer2[32]; + rapidjson::InsituStringStream ss(buffer2); + writer.Reset(ss); + char* begin = ss.PutBegin(); + writer.WriteInt64(x); + ss.Put('\0'); + ss.PutEnd(begin); + ASSERT_STREQ(buffer, buffer2); + } +} + +template <typename Writer> +void itoa64_Writer_StringBuffer() { + size_t length = 0; + + rapidjson::StringBuffer sb; + Writer writer(sb); + + for (size_t i = 0; i < kItoaTrialCount; i++) { + for (size_t j = 0; j < randvalCount; j++) { + writer.WriteInt64(randval[j] * randval[j]); + length += sb.GetSize(); + sb.Clear(); + } + } + OUTPUT_LENGTH(length); +} + +template <typename Writer> +void itoa64_Writer_InsituStringStream() { + size_t length = 0; + + char buffer[32]; + Writer writer; + for (size_t i = 0; i < kItoaTrialCount; i++) { + for (size_t j = 0; j < randvalCount; j++) { + rapidjson::InsituStringStream ss(buffer); + writer.Reset(ss); + char* begin = ss.PutBegin(); + writer.WriteInt64(randval[j] * randval[j]); + length += ss.PutEnd(begin); + } + } + OUTPUT_LENGTH(length); +}; + +// Full specialization for InsituStringStream to prevent memory copying +// (normally we will not use InsituStringStream for writing, just for testing) + +namespace rapidjson { + +template<> +bool rapidjson::Writer<InsituStringStream>::WriteInt(int i) { + char *buffer = os_->Push(11); + const char* end = internal::i32toa(i, buffer); + os_->Pop(11 - (end - buffer)); + return true; +} + +template<> +bool Writer<InsituStringStream>::WriteUint(unsigned u) { + char *buffer = os_->Push(10); + const char* end = internal::u32toa(u, buffer); + os_->Pop(10 - (end - buffer)); + return true; +} + +template<> +bool Writer<InsituStringStream>::WriteInt64(int64_t i64) { + char *buffer = os_->Push(21); + const char* end = internal::i64toa(i64, buffer); + os_->Pop(21 - (end - buffer)); + return true; +} + +template<> +bool Writer<InsituStringStream>::WriteUint64(uint64_t u) { + char *buffer = os_->Push(20); + const char* end = internal::u64toa(u, buffer); + os_->Pop(20 - (end - buffer)); + return true; +} + +} // namespace rapidjson + +TEST_F(Misc, itoa_Writer_StringBufferVerify) { itoa_Writer_StringBufferVerify<rapidjson::Writer<rapidjson::StringBuffer> >(); } +TEST_F(Misc, itoa_Writer1_StringBufferVerify) { itoa_Writer_StringBufferVerify<Writer1<rapidjson::StringBuffer> >(); } +TEST_F(Misc, itoa_Writer2_StringBufferVerify) { itoa_Writer_StringBufferVerify<Writer2<rapidjson::StringBuffer> >(); } +TEST_F(Misc, itoa_Writer3_StringBufferVerify) { itoa_Writer_StringBufferVerify<Writer3<rapidjson::StringBuffer> >(); } +TEST_F(Misc, itoa_Writer4_StringBufferVerify) { itoa_Writer_StringBufferVerify<Writer4<rapidjson::StringBuffer> >(); } +TEST_F(Misc, itoa_Writer_InsituStringStreamVerify) { itoa_Writer_InsituStringStreamVerify<rapidjson::Writer<rapidjson::InsituStringStream> >(); } +TEST_F(Misc, itoa_Writer1_InsituStringStreamVerify) { itoa_Writer_InsituStringStreamVerify<Writer1<rapidjson::InsituStringStream> >(); } +TEST_F(Misc, itoa_Writer2_InsituStringStreamVerify) { itoa_Writer_InsituStringStreamVerify<Writer2<rapidjson::InsituStringStream> >(); } +TEST_F(Misc, itoa_Writer3_InsituStringStreamVerify) { itoa_Writer_InsituStringStreamVerify<Writer3<rapidjson::InsituStringStream> >(); } +TEST_F(Misc, itoa_Writer4_InsituStringStreamVerify) { itoa_Writer_InsituStringStreamVerify<Writer4<rapidjson::InsituStringStream> >(); } +TEST_F(Misc, itoa_Writer_StringBuffer) { itoa_Writer_StringBuffer<rapidjson::Writer<rapidjson::StringBuffer> >(); } +TEST_F(Misc, itoa_Writer1_StringBuffer) { itoa_Writer_StringBuffer<Writer1<rapidjson::StringBuffer> >(); } +TEST_F(Misc, itoa_Writer2_StringBuffer) { itoa_Writer_StringBuffer<Writer2<rapidjson::StringBuffer> >(); } +TEST_F(Misc, itoa_Writer3_StringBuffer) { itoa_Writer_StringBuffer<Writer3<rapidjson::StringBuffer> >(); } +TEST_F(Misc, itoa_Writer4_StringBuffer) { itoa_Writer_StringBuffer<Writer4<rapidjson::StringBuffer> >(); } +TEST_F(Misc, itoa_Writer_InsituStringStream) { itoa_Writer_InsituStringStream<rapidjson::Writer<rapidjson::InsituStringStream> >(); } +TEST_F(Misc, itoa_Writer1_InsituStringStream) { itoa_Writer_InsituStringStream<Writer1<rapidjson::InsituStringStream> >(); } +TEST_F(Misc, itoa_Writer2_InsituStringStream) { itoa_Writer_InsituStringStream<Writer2<rapidjson::InsituStringStream> >(); } +TEST_F(Misc, itoa_Writer3_InsituStringStream) { itoa_Writer_InsituStringStream<Writer3<rapidjson::InsituStringStream> >(); } +TEST_F(Misc, itoa_Writer4_InsituStringStream) { itoa_Writer_InsituStringStream<Writer4<rapidjson::InsituStringStream> >(); } + +TEST_F(Misc, itoa64_Writer_StringBufferVerify) { itoa64_Writer_StringBufferVerify<rapidjson::Writer<rapidjson::StringBuffer> >(); } +TEST_F(Misc, itoa64_Writer1_StringBufferVerify) { itoa64_Writer_StringBufferVerify<Writer1<rapidjson::StringBuffer> >(); } +TEST_F(Misc, itoa64_Writer2_StringBufferVerify) { itoa64_Writer_StringBufferVerify<Writer2<rapidjson::StringBuffer> >(); } +TEST_F(Misc, itoa64_Writer3_StringBufferVerify) { itoa64_Writer_StringBufferVerify<Writer3<rapidjson::StringBuffer> >(); } +TEST_F(Misc, itoa64_Writer4_StringBufferVerify) { itoa64_Writer_StringBufferVerify<Writer4<rapidjson::StringBuffer> >(); } +TEST_F(Misc, itoa64_Writer_InsituStringStreamVerify) { itoa64_Writer_InsituStringStreamVerify<rapidjson::Writer<rapidjson::InsituStringStream> >(); } +TEST_F(Misc, itoa64_Writer1_InsituStringStreamVerify) { itoa64_Writer_InsituStringStreamVerify<Writer1<rapidjson::InsituStringStream> >(); } +TEST_F(Misc, itoa64_Writer2_InsituStringStreamVerify) { itoa64_Writer_InsituStringStreamVerify<Writer2<rapidjson::InsituStringStream> >(); } +TEST_F(Misc, itoa64_Writer3_InsituStringStreamVerify) { itoa64_Writer_InsituStringStreamVerify<Writer3<rapidjson::InsituStringStream> >(); } +TEST_F(Misc, itoa64_Writer4_InsituStringStreamVerify) { itoa64_Writer_InsituStringStreamVerify<Writer4<rapidjson::InsituStringStream> >(); } +TEST_F(Misc, itoa64_Writer_StringBuffer) { itoa64_Writer_StringBuffer<rapidjson::Writer<rapidjson::StringBuffer> >(); } +TEST_F(Misc, itoa64_Writer1_StringBuffer) { itoa64_Writer_StringBuffer<Writer1<rapidjson::StringBuffer> >(); } +TEST_F(Misc, itoa64_Writer2_StringBuffer) { itoa64_Writer_StringBuffer<Writer2<rapidjson::StringBuffer> >(); } +TEST_F(Misc, itoa64_Writer3_StringBuffer) { itoa64_Writer_StringBuffer<Writer3<rapidjson::StringBuffer> >(); } +TEST_F(Misc, itoa64_Writer4_StringBuffer) { itoa64_Writer_StringBuffer<Writer4<rapidjson::StringBuffer> >(); } +TEST_F(Misc, itoa64_Writer_InsituStringStream) { itoa64_Writer_InsituStringStream<rapidjson::Writer<rapidjson::InsituStringStream> >(); } +TEST_F(Misc, itoa64_Writer1_InsituStringStream) { itoa64_Writer_InsituStringStream<Writer1<rapidjson::InsituStringStream> >(); } +TEST_F(Misc, itoa64_Writer2_InsituStringStream) { itoa64_Writer_InsituStringStream<Writer2<rapidjson::InsituStringStream> >(); } +TEST_F(Misc, itoa64_Writer3_InsituStringStream) { itoa64_Writer_InsituStringStream<Writer3<rapidjson::InsituStringStream> >(); } +TEST_F(Misc, itoa64_Writer4_InsituStringStream) { itoa64_Writer_InsituStringStream<Writer4<rapidjson::InsituStringStream> >(); } + +#endif // TEST_MISC diff --git a/src/rapidjson/test/perftest/perftest.cpp b/src/rapidjson/test/perftest/perftest.cpp new file mode 100644 index 000000000..4e79f1f51 --- /dev/null +++ b/src/rapidjson/test/perftest/perftest.cpp @@ -0,0 +1,24 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#include "perftest.h" + +int main(int argc, char **argv) { +#if _MSC_VER + _CrtSetDbgFlag ( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF ); + //void *testWhetherMemoryLeakDetectionWorks = malloc(1); +#endif + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/src/rapidjson/test/perftest/perftest.h b/src/rapidjson/test/perftest/perftest.h new file mode 100644 index 000000000..b098e4147 --- /dev/null +++ b/src/rapidjson/test/perftest/perftest.h @@ -0,0 +1,182 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef PERFTEST_H_ +#define PERFTEST_H_ + +#define TEST_RAPIDJSON 1 +#define TEST_PLATFORM 0 +#define TEST_MISC 0 + +#define TEST_VERSION_CODE(x,y,z) \ + (((x)*100000) + ((y)*100) + (z)) + +// __SSE2__ and __SSE4_2__ are recognized by gcc, clang, and the Intel compiler. +// We use -march=native with gmake to enable -msse2 and -msse4.2, if supported. +#if defined(__SSE4_2__) +# define RAPIDJSON_SSE42 +#elif defined(__SSE2__) +# define RAPIDJSON_SSE2 +#endif + +#define RAPIDJSON_HAS_STDSTRING 1 + +//////////////////////////////////////////////////////////////////////////////// +// Google Test + +#ifdef __cplusplus + +// gtest indirectly included inttypes.h, without __STDC_CONSTANT_MACROS. +#ifndef __STDC_CONSTANT_MACROS +# define __STDC_CONSTANT_MACROS 1 // required by C++ standard +#endif + +#if defined(__clang__) || defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 2)) +#if defined(__clang__) || (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)) +#pragma GCC diagnostic push +#endif +#pragma GCC diagnostic ignored "-Weffc++" +#endif + +#include "gtest/gtest.h" + +#if defined(__clang__) || defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)) +#pragma GCC diagnostic pop +#endif + +#ifdef _MSC_VER +#define _CRTDBG_MAP_ALLOC +#include <crtdbg.h> +#pragma warning(disable : 4996) // 'function': was declared deprecated +#endif + +//! Base class for all performance tests +class PerfTest : public ::testing::Test { +public: + PerfTest() : filename_(), json_(), length_(), whitespace_(), whitespace_length_() {} + + virtual void SetUp() { + { + const char *paths[] = { + "data/sample.json", + "bin/data/sample.json", + "../bin/data/sample.json", + "../../bin/data/sample.json", + "../../../bin/data/sample.json" + }; + + FILE *fp = 0; + for (size_t i = 0; i < sizeof(paths) / sizeof(paths[0]); i++) { + fp = fopen(filename_ = paths[i], "rb"); + if (fp) + break; + } + ASSERT_TRUE(fp != 0); + + fseek(fp, 0, SEEK_END); + length_ = (size_t)ftell(fp); + fseek(fp, 0, SEEK_SET); + json_ = (char*)malloc(length_ + 1); + ASSERT_EQ(length_, fread(json_, 1, length_, fp)); + json_[length_] = '\0'; + fclose(fp); + } + + // whitespace test + { + whitespace_length_ = 1024 * 1024; + whitespace_ = (char *)malloc(whitespace_length_ + 4); + char *p = whitespace_; + for (size_t i = 0; i < whitespace_length_; i += 4) { + *p++ = ' '; + *p++ = '\n'; + *p++ = '\r'; + *p++ = '\t'; + } + *p++ = '['; + *p++ = '0'; + *p++ = ']'; + *p++ = '\0'; + } + + // types test + { + const char *typespaths[] = { + "data/types", + "bin/types", + "../bin/types", + "../../bin/types/", + "../../../bin/types" + }; + + const char* typesfilenames[] = { + "booleans.json", + "floats.json", + "guids.json", + "integers.json", + "mixed.json", + "nulls.json", + "paragraphs.json" + }; + + for (size_t j = 0; j < sizeof(typesfilenames) / sizeof(typesfilenames[0]); j++) { + types_[j] = 0; + for (size_t i = 0; i < sizeof(typespaths) / sizeof(typespaths[0]); i++) { + char filename[256]; + sprintf(filename, "%s/%s", typespaths[i], typesfilenames[j]); + if (FILE* fp = fopen(filename, "rb")) { + fseek(fp, 0, SEEK_END); + typesLength_[j] = (size_t)ftell(fp); + fseek(fp, 0, SEEK_SET); + types_[j] = (char*)malloc(typesLength_[j] + 1); + ASSERT_EQ(typesLength_[j], fread(types_[j], 1, typesLength_[j], fp)); + types_[j][typesLength_[j]] = '\0'; + fclose(fp); + break; + } + } + } + } + } + + virtual void TearDown() { + free(json_); + free(whitespace_); + json_ = 0; + whitespace_ = 0; + for (size_t i = 0; i < 7; i++) { + free(types_[i]); + types_[i] = 0; + } + } + +private: + PerfTest(const PerfTest&); + PerfTest& operator=(const PerfTest&); + +protected: + const char* filename_; + char *json_; + size_t length_; + char *whitespace_; + size_t whitespace_length_; + char *types_[7]; + size_t typesLength_[7]; + + static const size_t kTrialCount = 1000; +}; + +#endif // __cplusplus + +#endif // PERFTEST_H_ diff --git a/src/rapidjson/test/perftest/platformtest.cpp b/src/rapidjson/test/perftest/platformtest.cpp new file mode 100644 index 000000000..bb905ca73 --- /dev/null +++ b/src/rapidjson/test/perftest/platformtest.cpp @@ -0,0 +1,166 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#include "perftest.h" + +// This file is for giving the performance characteristics of the platform (compiler/OS/CPU). + +#if TEST_PLATFORM + +#include <cmath> +#include <fcntl.h> + +// Windows +#ifdef _WIN32 +#include <windows.h> +#endif + +// UNIX +#if defined(unix) || defined(__unix__) || defined(__unix) +#include <unistd.h> +#ifdef _POSIX_MAPPED_FILES +#include <sys/mman.h> +#endif +#endif + +class Platform : public PerfTest { +public: + virtual void SetUp() { + PerfTest::SetUp(); + + // temp buffer for testing + temp_ = (char *)malloc(length_ + 1); + memcpy(temp_, json_, length_); + checkSum_ = CheckSum(); + } + + char CheckSum() { + char c = 0; + for (size_t i = 0; i < length_; ++i) + c += temp_[i]; + return c; + } + + virtual void TearDown() { + PerfTest::TearDown(); + free(temp_); + } + +protected: + char *temp_; + char checkSum_; +}; + +TEST_F(Platform, CheckSum) { + for (int i = 0; i < kTrialCount; i++) + EXPECT_EQ(checkSum_, CheckSum()); +} + +TEST_F(Platform, strlen) { + for (int i = 0; i < kTrialCount; i++) { + size_t l = strlen(json_); + EXPECT_EQ(length_, l); + } +} + +TEST_F(Platform, memcmp) { + for (int i = 0; i < kTrialCount; i++) { + EXPECT_EQ(0, memcmp(temp_, json_, length_)); + } +} + +TEST_F(Platform, pow) { + double sum = 0; + for (int i = 0; i < kTrialCount * kTrialCount; i++) + sum += pow(10.0, i & 255); + EXPECT_GT(sum, 0.0); +} + +TEST_F(Platform, Whitespace_strlen) { + for (int i = 0; i < kTrialCount; i++) { + size_t l = strlen(whitespace_); + EXPECT_GT(l, whitespace_length_); + } +} + +TEST_F(Platform, Whitespace_strspn) { + for (int i = 0; i < kTrialCount; i++) { + size_t l = strspn(whitespace_, " \n\r\t"); + EXPECT_EQ(whitespace_length_, l); + } +} + +TEST_F(Platform, fread) { + for (int i = 0; i < kTrialCount; i++) { + FILE *fp = fopen(filename_, "rb"); + ASSERT_EQ(length_, fread(temp_, 1, length_, fp)); + EXPECT_EQ(checkSum_, CheckSum()); + fclose(fp); + } +} + +#ifdef _MSC_VER +TEST_F(Platform, read) { + for (int i = 0; i < kTrialCount; i++) { + int fd = _open(filename_, _O_BINARY | _O_RDONLY); + ASSERT_NE(-1, fd); + ASSERT_EQ(length_, _read(fd, temp_, length_)); + EXPECT_EQ(checkSum_, CheckSum()); + _close(fd); + } +} +#else +TEST_F(Platform, read) { + for (int i = 0; i < kTrialCount; i++) { + int fd = open(filename_, O_RDONLY); + ASSERT_NE(-1, fd); + ASSERT_EQ(length_, read(fd, temp_, length_)); + EXPECT_EQ(checkSum_, CheckSum()); + close(fd); + } +} +#endif + +#ifdef _WIN32 +TEST_F(Platform, MapViewOfFile) { + for (int i = 0; i < kTrialCount; i++) { + HANDLE file = CreateFile(filename_, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + ASSERT_NE(INVALID_HANDLE_VALUE, file); + HANDLE mapObject = CreateFileMapping(file, NULL, PAGE_READONLY, 0, length_, NULL); + ASSERT_NE(INVALID_HANDLE_VALUE, mapObject); + void *p = MapViewOfFile(mapObject, FILE_MAP_READ, 0, 0, length_); + ASSERT_TRUE(p != NULL); + EXPECT_EQ(checkSum_, CheckSum()); + ASSERT_TRUE(UnmapViewOfFile(p) == TRUE); + ASSERT_TRUE(CloseHandle(mapObject) == TRUE); + ASSERT_TRUE(CloseHandle(file) == TRUE); + } +} +#endif + +#ifdef _POSIX_MAPPED_FILES +TEST_F(Platform, mmap) { + for (int i = 0; i < kTrialCount; i++) { + int fd = open(filename_, O_RDONLY); + ASSERT_NE(-1, fd); + void *p = mmap(NULL, length_, PROT_READ, MAP_PRIVATE, fd, 0); + ASSERT_TRUE(p != NULL); + EXPECT_EQ(checkSum_, CheckSum()); + munmap(p, length_); + close(fd); + } +} +#endif + +#endif // TEST_PLATFORM diff --git a/src/rapidjson/test/perftest/rapidjsontest.cpp b/src/rapidjson/test/perftest/rapidjsontest.cpp new file mode 100644 index 000000000..675db3182 --- /dev/null +++ b/src/rapidjson/test/perftest/rapidjsontest.cpp @@ -0,0 +1,441 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#include "perftest.h" + +#if TEST_RAPIDJSON + +#include "rapidjson/rapidjson.h" +#include "rapidjson/document.h" +#include "rapidjson/prettywriter.h" +#include "rapidjson/stringbuffer.h" +#include "rapidjson/filereadstream.h" +#include "rapidjson/encodedstream.h" +#include "rapidjson/memorystream.h" + +#ifdef RAPIDJSON_SSE2 +#define SIMD_SUFFIX(name) name##_SSE2 +#elif defined(RAPIDJSON_SSE42) +#define SIMD_SUFFIX(name) name##_SSE42 +#else +#define SIMD_SUFFIX(name) name +#endif + +using namespace rapidjson; + +class RapidJson : public PerfTest { +public: + RapidJson() : temp_(), doc_() {} + + virtual void SetUp() { + PerfTest::SetUp(); + + // temp buffer for insitu parsing. + temp_ = (char *)malloc(length_ + 1); + + // Parse as a document + EXPECT_FALSE(doc_.Parse(json_).HasParseError()); + + for (size_t i = 0; i < 7; i++) + EXPECT_FALSE(typesDoc_[i].Parse(types_[i]).HasParseError()); + } + + virtual void TearDown() { + PerfTest::TearDown(); + free(temp_); + } + +private: + RapidJson(const RapidJson&); + RapidJson& operator=(const RapidJson&); + +protected: + char *temp_; + Document doc_; + Document typesDoc_[7]; +}; + +TEST_F(RapidJson, SIMD_SUFFIX(ReaderParseInsitu_DummyHandler)) { + for (size_t i = 0; i < kTrialCount; i++) { + memcpy(temp_, json_, length_ + 1); + InsituStringStream s(temp_); + BaseReaderHandler<> h; + Reader reader; + EXPECT_TRUE(reader.Parse<kParseInsituFlag>(s, h)); + } +} + +TEST_F(RapidJson, SIMD_SUFFIX(ReaderParseInsitu_DummyHandler_ValidateEncoding)) { + for (size_t i = 0; i < kTrialCount; i++) { + memcpy(temp_, json_, length_ + 1); + InsituStringStream s(temp_); + BaseReaderHandler<> h; + Reader reader; + EXPECT_TRUE(reader.Parse<kParseInsituFlag | kParseValidateEncodingFlag>(s, h)); + } +} + +TEST_F(RapidJson, SIMD_SUFFIX(ReaderParse_DummyHandler)) { + for (size_t i = 0; i < kTrialCount; i++) { + StringStream s(json_); + BaseReaderHandler<> h; + Reader reader; + EXPECT_TRUE(reader.Parse(s, h)); + } +} + +#define TEST_TYPED(index, Name)\ +TEST_F(RapidJson, SIMD_SUFFIX(ReaderParse_DummyHandler_##Name)) {\ + for (size_t i = 0; i < kTrialCount * 10; i++) {\ + StringStream s(types_[index]);\ + BaseReaderHandler<> h;\ + Reader reader;\ + EXPECT_TRUE(reader.Parse(s, h));\ + }\ +}\ +TEST_F(RapidJson, SIMD_SUFFIX(ReaderParseInsitu_DummyHandler_##Name)) {\ + for (size_t i = 0; i < kTrialCount * 10; i++) {\ + memcpy(temp_, types_[index], typesLength_[index] + 1);\ + InsituStringStream s(temp_);\ + BaseReaderHandler<> h;\ + Reader reader;\ + EXPECT_TRUE(reader.Parse<kParseInsituFlag>(s, h));\ + }\ +} + +TEST_TYPED(0, Booleans) +TEST_TYPED(1, Floats) +TEST_TYPED(2, Guids) +TEST_TYPED(3, Integers) +TEST_TYPED(4, Mixed) +TEST_TYPED(5, Nulls) +TEST_TYPED(6, Paragraphs) + +#undef TEST_TYPED + +TEST_F(RapidJson, SIMD_SUFFIX(ReaderParse_DummyHandler_FullPrecision)) { + for (size_t i = 0; i < kTrialCount; i++) { + StringStream s(json_); + BaseReaderHandler<> h; + Reader reader; + EXPECT_TRUE(reader.Parse<kParseFullPrecisionFlag>(s, h)); + } +} + +TEST_F(RapidJson, SIMD_SUFFIX(ReaderParseIterative_DummyHandler)) { + for (size_t i = 0; i < kTrialCount; i++) { + StringStream s(json_); + BaseReaderHandler<> h; + Reader reader; + EXPECT_TRUE(reader.Parse<kParseIterativeFlag>(s, h)); + } +} + +TEST_F(RapidJson, SIMD_SUFFIX(ReaderParseIterativeInsitu_DummyHandler)) { + for (size_t i = 0; i < kTrialCount; i++) { + memcpy(temp_, json_, length_ + 1); + InsituStringStream s(temp_); + BaseReaderHandler<> h; + Reader reader; + EXPECT_TRUE(reader.Parse<kParseIterativeFlag|kParseInsituFlag>(s, h)); + } +} + +TEST_F(RapidJson, SIMD_SUFFIX(ReaderParse_DummyHandler_ValidateEncoding)) { + for (size_t i = 0; i < kTrialCount; i++) { + StringStream s(json_); + BaseReaderHandler<> h; + Reader reader; + EXPECT_TRUE(reader.Parse<kParseValidateEncodingFlag>(s, h)); + } +} + +TEST_F(RapidJson, SIMD_SUFFIX(DocumentParseInsitu_MemoryPoolAllocator)) { + for (size_t i = 0; i < kTrialCount; i++) { + memcpy(temp_, json_, length_ + 1); + Document doc; + doc.ParseInsitu(temp_); + ASSERT_TRUE(doc.IsObject()); + } +} + +TEST_F(RapidJson, SIMD_SUFFIX(DocumentParseIterativeInsitu_MemoryPoolAllocator)) { + for (size_t i = 0; i < kTrialCount; i++) { + memcpy(temp_, json_, length_ + 1); + Document doc; + doc.ParseInsitu<kParseIterativeFlag>(temp_); + ASSERT_TRUE(doc.IsObject()); + } +} + +TEST_F(RapidJson, SIMD_SUFFIX(DocumentParse_MemoryPoolAllocator)) { + for (size_t i = 0; i < kTrialCount; i++) { + Document doc; + doc.Parse(json_); + ASSERT_TRUE(doc.IsObject()); + } +} + +TEST_F(RapidJson, SIMD_SUFFIX(DocumentParseLength_MemoryPoolAllocator)) { + for (size_t i = 0; i < kTrialCount; i++) { + Document doc; + doc.Parse(json_, length_); + ASSERT_TRUE(doc.IsObject()); + } +} + +#if RAPIDJSON_HAS_STDSTRING +TEST_F(RapidJson, SIMD_SUFFIX(DocumentParseStdString_MemoryPoolAllocator)) { + const std::string s(json_, length_); + for (size_t i = 0; i < kTrialCount; i++) { + Document doc; + doc.Parse(s); + ASSERT_TRUE(doc.IsObject()); + } +} +#endif + +TEST_F(RapidJson, SIMD_SUFFIX(DocumentParseIterative_MemoryPoolAllocator)) { + for (size_t i = 0; i < kTrialCount; i++) { + Document doc; + doc.Parse<kParseIterativeFlag>(json_); + ASSERT_TRUE(doc.IsObject()); + } +} + +TEST_F(RapidJson, SIMD_SUFFIX(DocumentParse_CrtAllocator)) { + for (size_t i = 0; i < kTrialCount; i++) { + memcpy(temp_, json_, length_ + 1); + GenericDocument<UTF8<>, CrtAllocator> doc; + doc.Parse(temp_); + ASSERT_TRUE(doc.IsObject()); + } +} + +TEST_F(RapidJson, SIMD_SUFFIX(DocumentParseEncodedInputStream_MemoryStream)) { + for (size_t i = 0; i < kTrialCount; i++) { + MemoryStream ms(json_, length_); + EncodedInputStream<UTF8<>, MemoryStream> is(ms); + Document doc; + doc.ParseStream<0, UTF8<> >(is); + ASSERT_TRUE(doc.IsObject()); + } +} + +TEST_F(RapidJson, SIMD_SUFFIX(DocumentParseAutoUTFInputStream_MemoryStream)) { + for (size_t i = 0; i < kTrialCount; i++) { + MemoryStream ms(json_, length_); + AutoUTFInputStream<unsigned, MemoryStream> is(ms); + Document doc; + doc.ParseStream<0, AutoUTF<unsigned> >(is); + ASSERT_TRUE(doc.IsObject()); + } +} + +template<typename T> +size_t Traverse(const T& value) { + size_t count = 1; + switch(value.GetType()) { + case kObjectType: + for (typename T::ConstMemberIterator itr = value.MemberBegin(); itr != value.MemberEnd(); ++itr) { + count++; // name + count += Traverse(itr->value); + } + break; + + case kArrayType: + for (typename T::ConstValueIterator itr = value.Begin(); itr != value.End(); ++itr) + count += Traverse(*itr); + break; + + default: + // Do nothing. + break; + } + return count; +} + +TEST_F(RapidJson, DocumentTraverse) { + for (size_t i = 0; i < kTrialCount; i++) { + size_t count = Traverse(doc_); + EXPECT_EQ(4339u, count); + //if (i == 0) + // std::cout << count << std::endl; + } +} + +#ifdef __GNUC__ +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(effc++) +#endif + +struct ValueCounter : public BaseReaderHandler<> { + ValueCounter() : count_(1) {} // root + + bool EndObject(SizeType memberCount) { count_ += memberCount * 2; return true; } + bool EndArray(SizeType elementCount) { count_ += elementCount; return true; } + + SizeType count_; +}; + +#ifdef __GNUC__ +RAPIDJSON_DIAG_POP +#endif + +TEST_F(RapidJson, DocumentAccept) { + for (size_t i = 0; i < kTrialCount; i++) { + ValueCounter counter; + doc_.Accept(counter); + EXPECT_EQ(4339u, counter.count_); + } +} + +struct NullStream { + typedef char Ch; + + NullStream() /*: length_(0)*/ {} + void Put(Ch) { /*++length_;*/ } + void Flush() {} + //size_t length_; +}; + +TEST_F(RapidJson, Writer_NullStream) { + for (size_t i = 0; i < kTrialCount; i++) { + NullStream s; + Writer<NullStream> writer(s); + doc_.Accept(writer); + //if (i == 0) + // std::cout << s.length_ << std::endl; + } +} + +TEST_F(RapidJson, SIMD_SUFFIX(Writer_StringBuffer)) { + for (size_t i = 0; i < kTrialCount; i++) { + StringBuffer s(0, 1024 * 1024); + Writer<StringBuffer> writer(s); + doc_.Accept(writer); + const char* str = s.GetString(); + (void)str; + //if (i == 0) + // std::cout << strlen(str) << std::endl; + } +} + +#define TEST_TYPED(index, Name)\ +TEST_F(RapidJson, SIMD_SUFFIX(Writer_StringBuffer_##Name)) {\ + for (size_t i = 0; i < kTrialCount * 10; i++) {\ + StringBuffer s(0, 1024 * 1024);\ + Writer<StringBuffer> writer(s);\ + typesDoc_[index].Accept(writer);\ + const char* str = s.GetString();\ + (void)str;\ + }\ +} + +TEST_TYPED(0, Booleans) +TEST_TYPED(1, Floats) +TEST_TYPED(2, Guids) +TEST_TYPED(3, Integers) +TEST_TYPED(4, Mixed) +TEST_TYPED(5, Nulls) +TEST_TYPED(6, Paragraphs) + +#undef TEST_TYPED + +TEST_F(RapidJson, SIMD_SUFFIX(PrettyWriter_StringBuffer)) { + for (size_t i = 0; i < kTrialCount; i++) { + StringBuffer s(0, 2048 * 1024); + PrettyWriter<StringBuffer> writer(s); + writer.SetIndent(' ', 1); + doc_.Accept(writer); + const char* str = s.GetString(); + (void)str; + //if (i == 0) + // std::cout << strlen(str) << std::endl; + } +} + +TEST_F(RapidJson, internal_Pow10) { + double sum = 0; + for (size_t i = 0; i < kTrialCount * kTrialCount; i++) + sum += internal::Pow10(int(i & 255)); + EXPECT_GT(sum, 0.0); +} + +TEST_F(RapidJson, SkipWhitespace_Basic) { + for (size_t i = 0; i < kTrialCount; i++) { + rapidjson::StringStream s(whitespace_); + while (s.Peek() == ' ' || s.Peek() == '\n' || s.Peek() == '\r' || s.Peek() == '\t') + s.Take(); + ASSERT_EQ('[', s.Peek()); + } +} + +TEST_F(RapidJson, SIMD_SUFFIX(SkipWhitespace)) { + for (size_t i = 0; i < kTrialCount; i++) { + rapidjson::StringStream s(whitespace_); + rapidjson::SkipWhitespace(s); + ASSERT_EQ('[', s.Peek()); + } +} + +TEST_F(RapidJson, SkipWhitespace_strspn) { + for (size_t i = 0; i < kTrialCount; i++) { + const char* s = whitespace_ + std::strspn(whitespace_, " \t\r\n"); + ASSERT_EQ('[', *s); + } +} + +TEST_F(RapidJson, UTF8_Validate) { + NullStream os; + + for (size_t i = 0; i < kTrialCount; i++) { + StringStream is(json_); + bool result = true; + while (is.Peek() != '\0') + result &= UTF8<>::Validate(is, os); + EXPECT_TRUE(result); + } +} + +TEST_F(RapidJson, FileReadStream) { + for (size_t i = 0; i < kTrialCount; i++) { + FILE *fp = fopen(filename_, "rb"); + char buffer[65536]; + FileReadStream s(fp, buffer, sizeof(buffer)); + while (s.Take() != '\0') + ; + fclose(fp); + } +} + +TEST_F(RapidJson, SIMD_SUFFIX(ReaderParse_DummyHandler_FileReadStream)) { + for (size_t i = 0; i < kTrialCount; i++) { + FILE *fp = fopen(filename_, "rb"); + char buffer[65536]; + FileReadStream s(fp, buffer, sizeof(buffer)); + BaseReaderHandler<> h; + Reader reader; + reader.Parse(s, h); + fclose(fp); + } +} + +TEST_F(RapidJson, StringBuffer) { + StringBuffer sb; + for (int i = 0; i < 32 * 1024 * 1024; i++) + sb.Put(i & 0x7f); +} + +#endif // TEST_RAPIDJSON diff --git a/src/rapidjson/test/perftest/schematest.cpp b/src/rapidjson/test/perftest/schematest.cpp new file mode 100644 index 000000000..468f5fe6f --- /dev/null +++ b/src/rapidjson/test/perftest/schematest.cpp @@ -0,0 +1,216 @@ +#include "perftest.h" + +#if TEST_RAPIDJSON + +#include "rapidjson/schema.h" +#include <ctime> +#include <string> +#include <vector> + +#define ARRAY_SIZE(a) sizeof(a) / sizeof(a[0]) + +using namespace rapidjson; + +template <typename Allocator> +static char* ReadFile(const char* filename, Allocator& allocator) { + const char *paths[] = { + "", + "bin/", + "../bin/", + "../../bin/", + "../../../bin/" + }; + char buffer[1024]; + FILE *fp = 0; + for (size_t i = 0; i < sizeof(paths) / sizeof(paths[0]); i++) { + sprintf(buffer, "%s%s", paths[i], filename); + fp = fopen(buffer, "rb"); + if (fp) + break; + } + + if (!fp) + return 0; + + fseek(fp, 0, SEEK_END); + size_t length = static_cast<size_t>(ftell(fp)); + fseek(fp, 0, SEEK_SET); + char* json = reinterpret_cast<char*>(allocator.Malloc(length + 1)); + size_t readLength = fread(json, 1, length, fp); + json[readLength] = '\0'; + fclose(fp); + return json; +} + +class Schema : public PerfTest { +public: + Schema() {} + + virtual void SetUp() { + PerfTest::SetUp(); + + const char* filenames[] = { + "additionalItems.json", + "additionalProperties.json", + "allOf.json", + "anyOf.json", + "default.json", + "definitions.json", + "dependencies.json", + "enum.json", + "items.json", + "maximum.json", + "maxItems.json", + "maxLength.json", + "maxProperties.json", + "minimum.json", + "minItems.json", + "minLength.json", + "minProperties.json", + "multipleOf.json", + "not.json", + "oneOf.json", + "pattern.json", + "patternProperties.json", + "properties.json", + "ref.json", + "refRemote.json", + "required.json", + "type.json", + "uniqueItems.json" + }; + + char jsonBuffer[65536]; + MemoryPoolAllocator<> jsonAllocator(jsonBuffer, sizeof(jsonBuffer)); + + for (size_t i = 0; i < ARRAY_SIZE(filenames); i++) { + char filename[FILENAME_MAX]; + sprintf(filename, "jsonschema/tests/draft4/%s", filenames[i]); + char* json = ReadFile(filename, jsonAllocator); + if (!json) { + printf("json test suite file %s not found", filename); + return; + } + + Document d; + d.Parse(json); + if (d.HasParseError()) { + printf("json test suite file %s has parse error", filename); + return; + } + + for (Value::ConstValueIterator schemaItr = d.Begin(); schemaItr != d.End(); ++schemaItr) { + std::string schemaDescription = (*schemaItr)["description"].GetString(); + if (IsExcludeTestSuite(schemaDescription)) + continue; + + TestSuite* ts = new TestSuite; + ts->schema = new SchemaDocument((*schemaItr)["schema"]); + + const Value& tests = (*schemaItr)["tests"]; + for (Value::ConstValueIterator testItr = tests.Begin(); testItr != tests.End(); ++testItr) { + if (IsExcludeTest(schemaDescription + ", " + (*testItr)["description"].GetString())) + continue; + + Document* d2 = new Document; + d2->CopyFrom((*testItr)["data"], d2->GetAllocator()); + ts->tests.push_back(d2); + } + testSuites.push_back(ts); + } + } + } + + virtual void TearDown() { + PerfTest::TearDown(); + for (TestSuiteList::const_iterator itr = testSuites.begin(); itr != testSuites.end(); ++itr) + delete *itr; + testSuites.clear(); + } + +private: + // Using the same exclusion in https://github.com/json-schema/JSON-Schema-Test-Suite + static bool IsExcludeTestSuite(const std::string& description) { + const char* excludeTestSuites[] = { + //lost failing these tests + "remote ref", + "remote ref, containing refs itself", + "fragment within remote ref", + "ref within remote ref", + "change resolution scope", + // these below were added to get jsck in the benchmarks) + "uniqueItems validation", + "valid definition", + "invalid definition" + }; + + for (size_t i = 0; i < ARRAY_SIZE(excludeTestSuites); i++) + if (excludeTestSuites[i] == description) + return true; + return false; + } + + // Using the same exclusion in https://github.com/json-schema/JSON-Schema-Test-Suite + static bool IsExcludeTest(const std::string& description) { + const char* excludeTests[] = { + //lots of validators fail these + "invalid definition, invalid definition schema", + "maxLength validation, two supplementary Unicode code points is long enough", + "minLength validation, one supplementary Unicode code point is not long enough", + //this is to get tv4 in the benchmarks + "heterogeneous enum validation, something else is invalid" + }; + + for (size_t i = 0; i < ARRAY_SIZE(excludeTests); i++) + if (excludeTests[i] == description) + return true; + return false; + } + + Schema(const Schema&); + Schema& operator=(const Schema&); + +protected: + typedef std::vector<Document*> DocumentList; + + struct TestSuite { + TestSuite() : schema() {} + ~TestSuite() { + delete schema; + for (DocumentList::iterator itr = tests.begin(); itr != tests.end(); ++itr) + delete *itr; + } + SchemaDocument* schema; + DocumentList tests; + }; + + typedef std::vector<TestSuite* > TestSuiteList; + TestSuiteList testSuites; +}; + +TEST_F(Schema, TestSuite) { + char validatorBuffer[65536]; + MemoryPoolAllocator<> validatorAllocator(validatorBuffer, sizeof(validatorBuffer)); + + const int trialCount = 100000; + int testCount = 0; + clock_t start = clock(); + for (int i = 0; i < trialCount; i++) { + for (TestSuiteList::const_iterator itr = testSuites.begin(); itr != testSuites.end(); ++itr) { + const TestSuite& ts = **itr; + GenericSchemaValidator<SchemaDocument, BaseReaderHandler<UTF8<> >, MemoryPoolAllocator<> > validator(*ts.schema, &validatorAllocator); + for (DocumentList::const_iterator testItr = ts.tests.begin(); testItr != ts.tests.end(); ++testItr) { + validator.Reset(); + (*testItr)->Accept(validator); + testCount++; + } + validatorAllocator.Clear(); + } + } + clock_t end = clock(); + double duration = double(end - start) / CLOCKS_PER_SEC; + printf("%d trials in %f s -> %f trials per sec\n", trialCount, duration, trialCount / duration); + printf("%d tests per trial\n", testCount / trialCount); +} + +#endif diff --git a/src/rapidjson/test/unittest/CMakeLists.txt b/src/rapidjson/test/unittest/CMakeLists.txt new file mode 100644 index 000000000..b3204d6c8 --- /dev/null +++ b/src/rapidjson/test/unittest/CMakeLists.txt @@ -0,0 +1,92 @@ +include(CheckCXXCompilerFlag) + +set(UNITTEST_SOURCES + allocatorstest.cpp + bigintegertest.cpp + documenttest.cpp + dtoatest.cpp + encodedstreamtest.cpp + encodingstest.cpp + fwdtest.cpp + filestreamtest.cpp + itoatest.cpp + istreamwrappertest.cpp + jsoncheckertest.cpp + namespacetest.cpp + pointertest.cpp + prettywritertest.cpp + ostreamwrappertest.cpp + readertest.cpp + regextest.cpp + schematest.cpp + simdtest.cpp + strfunctest.cpp + stringbuffertest.cpp + strtodtest.cpp + unittest.cpp + valuetest.cpp + writertest.cpp) + +find_program(CCACHE_FOUND ccache) +if(CCACHE_FOUND) + set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE ccache) + set_property(GLOBAL PROPERTY RULE_LAUNCH_LINK ccache) + if (CMAKE_CXX_COMPILER_ID MATCHES "Clang") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Qunused-arguments -fcolor-diagnostics") + endif() +endif(CCACHE_FOUND) + +if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Werror -Wall -Wextra -Weffc++ -Wswitch-default -Wfloat-equal") +elseif (CMAKE_CXX_COMPILER_ID MATCHES "Clang") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Werror -Wall -Wextra -Weffc++ -Wswitch-default -Wfloat-equal -Wimplicit-fallthrough -Weverything") + # If the user is running a newer version of Clang that includes the + # -Wdouble-promotion, we will ignore that warning. + if (CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 3.7) + CHECK_CXX_COMPILER_FLAG("-Wno-double-promotion" HAS_NO_DOUBLE_PROMOTION) + if (HAS_NO_DOUBLE_PROMOTION) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-double-promotion") + endif() + endif() +elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC") + # Force to always compile with /W4 + if(CMAKE_CXX_FLAGS MATCHES "/W[0-4]") + string(REGEX REPLACE "/W[0-4]" "/W4" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}") + else() + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /W4") + endif() + + # Force to always compile with /WX + if(CMAKE_CXX_FLAGS MATCHES "/WX-") + string(REGEX REPLACE "/WX-" "/WX" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}") + else() + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /WX") + endif() +endif() + +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DRAPIDJSON_HAS_STDSTRING=1") + +add_library(namespacetest STATIC namespacetest.cpp) + +add_executable(unittest ${UNITTEST_SOURCES}) +target_link_libraries(unittest ${TEST_LIBRARIES} namespacetest) + +add_dependencies(tests unittest) + +add_test(NAME unittest + COMMAND ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/unittest + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/bin) + +if(NOT MSVC) + # Not running SIMD.* unit test cases for Valgrind + add_test(NAME valgrind_unittest + COMMAND valgrind --leak-check=full --error-exitcode=1 ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/unittest --gtest_filter=-SIMD.* + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/bin) + + if(CMAKE_BUILD_TYPE STREQUAL "Debug") + add_test(NAME symbol_check + COMMAND sh -c "objdump -t -C libnamespacetest.a | grep rapidjson ; test $? -ne 0" + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) + endif(CMAKE_BUILD_TYPE STREQUAL "Debug") + +endif(NOT MSVC) diff --git a/src/rapidjson/test/unittest/allocatorstest.cpp b/src/rapidjson/test/unittest/allocatorstest.cpp new file mode 100644 index 000000000..a5958de19 --- /dev/null +++ b/src/rapidjson/test/unittest/allocatorstest.cpp @@ -0,0 +1,102 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#include "unittest.h" + +#include "rapidjson/allocators.h" + +using namespace rapidjson; + +template <typename Allocator> +void TestAllocator(Allocator& a) { + EXPECT_TRUE(a.Malloc(0) == 0); + + uint8_t* p = static_cast<uint8_t*>(a.Malloc(100)); + EXPECT_TRUE(p != 0); + for (size_t i = 0; i < 100; i++) + p[i] = static_cast<uint8_t>(i); + + // Expand + uint8_t* q = static_cast<uint8_t*>(a.Realloc(p, 100, 200)); + EXPECT_TRUE(q != 0); + for (size_t i = 0; i < 100; i++) + EXPECT_EQ(i, q[i]); + for (size_t i = 100; i < 200; i++) + q[i] = static_cast<uint8_t>(i); + + // Shrink + uint8_t *r = static_cast<uint8_t*>(a.Realloc(q, 200, 150)); + EXPECT_TRUE(r != 0); + for (size_t i = 0; i < 150; i++) + EXPECT_EQ(i, r[i]); + + Allocator::Free(r); + + // Realloc to zero size + EXPECT_TRUE(a.Realloc(a.Malloc(1), 1, 0) == 0); +} + +TEST(Allocator, CrtAllocator) { + CrtAllocator a; + TestAllocator(a); +} + +TEST(Allocator, MemoryPoolAllocator) { + MemoryPoolAllocator<> a; + TestAllocator(a); + + for (size_t i = 1; i < 1000; i++) { + EXPECT_TRUE(a.Malloc(i) != 0); + EXPECT_LE(a.Size(), a.Capacity()); + } +} + +TEST(Allocator, Alignment) { +#if RAPIDJSON_64BIT == 1 + EXPECT_EQ(RAPIDJSON_UINT64_C2(0x00000000, 0x00000000), RAPIDJSON_ALIGN(0)); + for (uint64_t i = 1; i < 8; i++) { + EXPECT_EQ(RAPIDJSON_UINT64_C2(0x00000000, 0x00000008), RAPIDJSON_ALIGN(i)); + EXPECT_EQ(RAPIDJSON_UINT64_C2(0x00000000, 0x00000010), RAPIDJSON_ALIGN(RAPIDJSON_UINT64_C2(0x00000000, 0x00000008) + i)); + EXPECT_EQ(RAPIDJSON_UINT64_C2(0x00000001, 0x00000000), RAPIDJSON_ALIGN(RAPIDJSON_UINT64_C2(0x00000000, 0xFFFFFFF8) + i)); + EXPECT_EQ(RAPIDJSON_UINT64_C2(0xFFFFFFFF, 0xFFFFFFF8), RAPIDJSON_ALIGN(RAPIDJSON_UINT64_C2(0xFFFFFFFF, 0xFFFFFFF0) + i)); + } +#else + EXPECT_EQ(0u, RAPIDJSON_ALIGN(0u)); + for (uint32_t i = 1; i < 4; i++) { + EXPECT_EQ(4u, RAPIDJSON_ALIGN(i)); + EXPECT_EQ(8u, RAPIDJSON_ALIGN(4u + i)); + EXPECT_EQ(0xFFFFFFF8u, RAPIDJSON_ALIGN(0xFFFFFFF4u + i)); + EXPECT_EQ(0xFFFFFFFCu, RAPIDJSON_ALIGN(0xFFFFFFF8u + i)); + } +#endif +} + +TEST(Allocator, Issue399) { + MemoryPoolAllocator<> a; + void* p = a.Malloc(100); + void* q = a.Realloc(p, 100, 200); + EXPECT_EQ(p, q); + + // exhuasive testing + for (size_t j = 1; j < 32; j++) { + a.Clear(); + a.Malloc(j); // some unaligned size + p = a.Malloc(1); + for (size_t i = 1; i < 1024; i++) { + q = a.Realloc(p, i, i + 1); + EXPECT_EQ(p, q); + p = q; + } + } +} diff --git a/src/rapidjson/test/unittest/bigintegertest.cpp b/src/rapidjson/test/unittest/bigintegertest.cpp new file mode 100644 index 000000000..a68e14446 --- /dev/null +++ b/src/rapidjson/test/unittest/bigintegertest.cpp @@ -0,0 +1,133 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#include "unittest.h" + +#include "rapidjson/internal/biginteger.h" + +using namespace rapidjson::internal; + +#define BIGINTEGER_LITERAL(s) BigInteger(s, sizeof(s) - 1) + +static const BigInteger kZero(0); +static const BigInteger kOne(1); +static const BigInteger kUint64Max = BIGINTEGER_LITERAL("18446744073709551615"); +static const BigInteger kTwo64 = BIGINTEGER_LITERAL("18446744073709551616"); + +TEST(BigInteger, Constructor) { + EXPECT_TRUE(kZero.IsZero()); + EXPECT_TRUE(kZero == kZero); + EXPECT_TRUE(kZero == BIGINTEGER_LITERAL("0")); + EXPECT_TRUE(kZero == BIGINTEGER_LITERAL("00")); + + const BigInteger a(123); + EXPECT_TRUE(a == a); + EXPECT_TRUE(a == BIGINTEGER_LITERAL("123")); + EXPECT_TRUE(a == BIGINTEGER_LITERAL("0123")); + + EXPECT_EQ(2u, kTwo64.GetCount()); + EXPECT_EQ(0u, kTwo64.GetDigit(0)); + EXPECT_EQ(1u, kTwo64.GetDigit(1)); +} + +TEST(BigInteger, AddUint64) { + BigInteger a = kZero; + a += 0u; + EXPECT_TRUE(kZero == a); + + a += 1u; + EXPECT_TRUE(kOne == a); + + a += 1u; + EXPECT_TRUE(BigInteger(2) == a); + + EXPECT_TRUE(BigInteger(RAPIDJSON_UINT64_C2(0xFFFFFFFF, 0xFFFFFFFF)) == kUint64Max); + BigInteger b = kUint64Max; + b += 1u; + EXPECT_TRUE(kTwo64 == b); + b += RAPIDJSON_UINT64_C2(0xFFFFFFFF, 0xFFFFFFFF); + EXPECT_TRUE(BIGINTEGER_LITERAL("36893488147419103231") == b); +} + +TEST(BigInteger, MultiplyUint64) { + BigInteger a = kZero; + a *= static_cast <uint64_t>(0); + EXPECT_TRUE(kZero == a); + a *= static_cast <uint64_t>(123); + EXPECT_TRUE(kZero == a); + + BigInteger b = kOne; + b *= static_cast<uint64_t>(1); + EXPECT_TRUE(kOne == b); + b *= static_cast<uint64_t>(0); + EXPECT_TRUE(kZero == b); + + BigInteger c(123); + c *= static_cast<uint64_t>(456u); + EXPECT_TRUE(BigInteger(123u * 456u) == c); + c *= RAPIDJSON_UINT64_C2(0xFFFFFFFF, 0xFFFFFFFF); + EXPECT_TRUE(BIGINTEGER_LITERAL("1034640981606221330982120") == c); + c *= RAPIDJSON_UINT64_C2(0xFFFFFFFF, 0xFFFFFFFF); + EXPECT_TRUE(BIGINTEGER_LITERAL("19085757395861596536664473018420572782123800") == c); +} + +TEST(BigInteger, MultiplyUint32) { + BigInteger a = kZero; + a *= static_cast <uint32_t>(0); + EXPECT_TRUE(kZero == a); + a *= static_cast <uint32_t>(123); + EXPECT_TRUE(kZero == a); + + BigInteger b = kOne; + b *= static_cast<uint32_t>(1); + EXPECT_TRUE(kOne == b); + b *= static_cast<uint32_t>(0); + EXPECT_TRUE(kZero == b); + + BigInteger c(123); + c *= static_cast<uint32_t>(456u); + EXPECT_TRUE(BigInteger(123u * 456u) == c); + c *= 0xFFFFFFFFu; + EXPECT_TRUE(BIGINTEGER_LITERAL("240896125641960") == c); + c *= 0xFFFFFFFFu; + EXPECT_TRUE(BIGINTEGER_LITERAL("1034640981124429079698200") == c); +} + +TEST(BigInteger, LeftShift) { + BigInteger a = kZero; + a <<= 1; + EXPECT_TRUE(kZero == a); + a <<= 64; + EXPECT_TRUE(kZero == a); + + a = BigInteger(123); + a <<= 0; + EXPECT_TRUE(BigInteger(123) == a); + a <<= 1; + EXPECT_TRUE(BigInteger(246) == a); + a <<= 64; + EXPECT_TRUE(BIGINTEGER_LITERAL("4537899042132549697536") == a); + a <<= 99; + EXPECT_TRUE(BIGINTEGER_LITERAL("2876235222267216943024851750785644982682875244576768") == a); +} + +TEST(BigInteger, Compare) { + EXPECT_EQ(0, kZero.Compare(kZero)); + EXPECT_EQ(1, kOne.Compare(kZero)); + EXPECT_EQ(-1, kZero.Compare(kOne)); + EXPECT_EQ(0, kUint64Max.Compare(kUint64Max)); + EXPECT_EQ(0, kTwo64.Compare(kTwo64)); + EXPECT_EQ(-1, kUint64Max.Compare(kTwo64)); + EXPECT_EQ(1, kTwo64.Compare(kUint64Max)); +} diff --git a/src/rapidjson/test/unittest/documenttest.cpp b/src/rapidjson/test/unittest/documenttest.cpp new file mode 100644 index 000000000..ecd4b79bc --- /dev/null +++ b/src/rapidjson/test/unittest/documenttest.cpp @@ -0,0 +1,652 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#include "unittest.h" +#include "rapidjson/document.h" +#include "rapidjson/writer.h" +#include "rapidjson/filereadstream.h" +#include "rapidjson/encodedstream.h" +#include "rapidjson/stringbuffer.h" +#include <sstream> +#include <algorithm> + +#ifdef __clang__ +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(c++98-compat) +RAPIDJSON_DIAG_OFF(missing-variable-declarations) +#endif + +using namespace rapidjson; + +template <typename DocumentType> +void ParseCheck(DocumentType& doc) { + typedef typename DocumentType::ValueType ValueType; + + EXPECT_FALSE(doc.HasParseError()); + if (doc.HasParseError()) + printf("Error: %d at %zu\n", static_cast<int>(doc.GetParseError()), doc.GetErrorOffset()); + EXPECT_TRUE(static_cast<ParseResult>(doc)); + + EXPECT_TRUE(doc.IsObject()); + + EXPECT_TRUE(doc.HasMember("hello")); + const ValueType& hello = doc["hello"]; + EXPECT_TRUE(hello.IsString()); + EXPECT_STREQ("world", hello.GetString()); + + EXPECT_TRUE(doc.HasMember("t")); + const ValueType& t = doc["t"]; + EXPECT_TRUE(t.IsTrue()); + + EXPECT_TRUE(doc.HasMember("f")); + const ValueType& f = doc["f"]; + EXPECT_TRUE(f.IsFalse()); + + EXPECT_TRUE(doc.HasMember("n")); + const ValueType& n = doc["n"]; + EXPECT_TRUE(n.IsNull()); + + EXPECT_TRUE(doc.HasMember("i")); + const ValueType& i = doc["i"]; + EXPECT_TRUE(i.IsNumber()); + EXPECT_EQ(123, i.GetInt()); + + EXPECT_TRUE(doc.HasMember("pi")); + const ValueType& pi = doc["pi"]; + EXPECT_TRUE(pi.IsNumber()); + EXPECT_DOUBLE_EQ(3.1416, pi.GetDouble()); + + EXPECT_TRUE(doc.HasMember("a")); + const ValueType& a = doc["a"]; + EXPECT_TRUE(a.IsArray()); + EXPECT_EQ(4u, a.Size()); + for (SizeType j = 0; j < 4; j++) + EXPECT_EQ(j + 1, a[j].GetUint()); +} + +template <typename Allocator, typename StackAllocator> +void ParseTest() { + typedef GenericDocument<UTF8<>, Allocator, StackAllocator> DocumentType; + DocumentType doc; + + const char* json = " { \"hello\" : \"world\", \"t\" : true , \"f\" : false, \"n\": null, \"i\":123, \"pi\": 3.1416, \"a\":[1, 2, 3, 4] } "; + + doc.Parse(json); + ParseCheck(doc); + + doc.SetNull(); + StringStream s(json); + doc.template ParseStream<0>(s); + ParseCheck(doc); + + doc.SetNull(); + char *buffer = strdup(json); + doc.ParseInsitu(buffer); + ParseCheck(doc); + free(buffer); + + // Parse(const Ch*, size_t) + size_t length = strlen(json); + buffer = reinterpret_cast<char*>(malloc(length * 2)); + memcpy(buffer, json, length); + memset(buffer + length, 'X', length); +#if RAPIDJSON_HAS_STDSTRING + std::string s2(buffer, length); // backup buffer +#endif + doc.SetNull(); + doc.Parse(buffer, length); + free(buffer); + ParseCheck(doc); + +#if RAPIDJSON_HAS_STDSTRING + // Parse(std::string) + doc.SetNull(); + doc.Parse(s2); + ParseCheck(doc); +#endif +} + +TEST(Document, Parse) { + ParseTest<MemoryPoolAllocator<>, CrtAllocator>(); + ParseTest<MemoryPoolAllocator<>, MemoryPoolAllocator<> >(); + ParseTest<CrtAllocator, MemoryPoolAllocator<> >(); + ParseTest<CrtAllocator, CrtAllocator>(); +} + +TEST(Document, UnchangedOnParseError) { + Document doc; + doc.SetArray().PushBack(0, doc.GetAllocator()); + + ParseResult err = doc.Parse("{]"); + EXPECT_TRUE(doc.HasParseError()); + EXPECT_EQ(err.Code(), doc.GetParseError()); + EXPECT_EQ(err.Offset(), doc.GetErrorOffset()); + EXPECT_TRUE(doc.IsArray()); + EXPECT_EQ(doc.Size(), 1u); + + err = doc.Parse("{}"); + EXPECT_FALSE(doc.HasParseError()); + EXPECT_FALSE(err.IsError()); + EXPECT_EQ(err.Code(), doc.GetParseError()); + EXPECT_EQ(err.Offset(), doc.GetErrorOffset()); + EXPECT_TRUE(doc.IsObject()); + EXPECT_EQ(doc.MemberCount(), 0u); +} + +static FILE* OpenEncodedFile(const char* filename) { + const char *paths[] = { + "encodings", + "bin/encodings", + "../bin/encodings", + "../../bin/encodings", + "../../../bin/encodings" + }; + char buffer[1024]; + for (size_t i = 0; i < sizeof(paths) / sizeof(paths[0]); i++) { + sprintf(buffer, "%s/%s", paths[i], filename); + FILE *fp = fopen(buffer, "rb"); + if (fp) + return fp; + } + return 0; +} + +TEST(Document, Parse_Encoding) { + const char* json = " { \"hello\" : \"world\", \"t\" : true , \"f\" : false, \"n\": null, \"i\":123, \"pi\": 3.1416, \"a\":[1, 2, 3, 4] } "; + + typedef GenericDocument<UTF16<> > DocumentType; + DocumentType doc; + + // Parse<unsigned, SourceEncoding>(const SourceEncoding::Ch*) + // doc.Parse<kParseDefaultFlags, UTF8<> >(json); + // EXPECT_FALSE(doc.HasParseError()); + // EXPECT_EQ(0, StrCmp(doc[L"hello"].GetString(), L"world")); + + // Parse<unsigned, SourceEncoding>(const SourceEncoding::Ch*, size_t) + size_t length = strlen(json); + char* buffer = reinterpret_cast<char*>(malloc(length * 2)); + memcpy(buffer, json, length); + memset(buffer + length, 'X', length); +#if RAPIDJSON_HAS_STDSTRING + std::string s2(buffer, length); // backup buffer +#endif + doc.SetNull(); + doc.Parse<kParseDefaultFlags, UTF8<> >(buffer, length); + free(buffer); + EXPECT_FALSE(doc.HasParseError()); + if (doc.HasParseError()) + printf("Error: %d at %zu\n", static_cast<int>(doc.GetParseError()), doc.GetErrorOffset()); + EXPECT_EQ(0, StrCmp(doc[L"hello"].GetString(), L"world")); + +#if RAPIDJSON_HAS_STDSTRING + // Parse<unsigned, SourceEncoding>(std::string) + doc.SetNull(); + +#if defined(_MSC_VER) && _MSC_VER < 1800 + doc.Parse<kParseDefaultFlags, UTF8<> >(s2.c_str()); // VS2010 or below cannot handle templated function overloading. Use const char* instead. +#else + doc.Parse<kParseDefaultFlags, UTF8<> >(s2); +#endif + EXPECT_FALSE(doc.HasParseError()); + EXPECT_EQ(0, StrCmp(doc[L"hello"].GetString(), L"world")); +#endif +} + +TEST(Document, ParseStream_EncodedInputStream) { + // UTF8 -> UTF16 + FILE* fp = OpenEncodedFile("utf8.json"); + char buffer[256]; + FileReadStream bis(fp, buffer, sizeof(buffer)); + EncodedInputStream<UTF8<>, FileReadStream> eis(bis); + + GenericDocument<UTF16<> > d; + d.ParseStream<0, UTF8<> >(eis); + EXPECT_FALSE(d.HasParseError()); + + fclose(fp); + + wchar_t expected[] = L"I can eat glass and it doesn't hurt me."; + GenericValue<UTF16<> >& v = d[L"en"]; + EXPECT_TRUE(v.IsString()); + EXPECT_EQ(sizeof(expected) / sizeof(wchar_t) - 1, v.GetStringLength()); + EXPECT_EQ(0, StrCmp(expected, v.GetString())); + + // UTF16 -> UTF8 in memory + StringBuffer bos; + typedef EncodedOutputStream<UTF8<>, StringBuffer> OutputStream; + OutputStream eos(bos, false); // Not writing BOM + { + Writer<OutputStream, UTF16<>, UTF8<> > writer(eos); + d.Accept(writer); + } + + // Condense the original file and compare. + fp = OpenEncodedFile("utf8.json"); + FileReadStream is(fp, buffer, sizeof(buffer)); + Reader reader; + StringBuffer bos2; + Writer<StringBuffer> writer2(bos2); + reader.Parse(is, writer2); + fclose(fp); + + EXPECT_EQ(bos.GetSize(), bos2.GetSize()); + EXPECT_EQ(0, memcmp(bos.GetString(), bos2.GetString(), bos2.GetSize())); +} + +TEST(Document, ParseStream_AutoUTFInputStream) { + // Any -> UTF8 + FILE* fp = OpenEncodedFile("utf32be.json"); + char buffer[256]; + FileReadStream bis(fp, buffer, sizeof(buffer)); + AutoUTFInputStream<unsigned, FileReadStream> eis(bis); + + Document d; + d.ParseStream<0, AutoUTF<unsigned> >(eis); + EXPECT_FALSE(d.HasParseError()); + + fclose(fp); + + char expected[] = "I can eat glass and it doesn't hurt me."; + Value& v = d["en"]; + EXPECT_TRUE(v.IsString()); + EXPECT_EQ(sizeof(expected) - 1, v.GetStringLength()); + EXPECT_EQ(0, StrCmp(expected, v.GetString())); + + // UTF8 -> UTF8 in memory + StringBuffer bos; + Writer<StringBuffer> writer(bos); + d.Accept(writer); + + // Condense the original file and compare. + fp = OpenEncodedFile("utf8.json"); + FileReadStream is(fp, buffer, sizeof(buffer)); + Reader reader; + StringBuffer bos2; + Writer<StringBuffer> writer2(bos2); + reader.Parse(is, writer2); + fclose(fp); + + EXPECT_EQ(bos.GetSize(), bos2.GetSize()); + EXPECT_EQ(0, memcmp(bos.GetString(), bos2.GetString(), bos2.GetSize())); +} + +TEST(Document, Swap) { + Document d1; + Document::AllocatorType& a = d1.GetAllocator(); + + d1.SetArray().PushBack(1, a).PushBack(2, a); + + Value o; + o.SetObject().AddMember("a", 1, a); + + // Swap between Document and Value + // d1.Swap(o); // doesn't compile + o.Swap(d1); + EXPECT_TRUE(d1.IsObject()); + EXPECT_TRUE(o.IsArray()); + + // Swap between Document and Document + Document d2; + d2.SetArray().PushBack(3, a); + d1.Swap(d2); + EXPECT_TRUE(d1.IsArray()); + EXPECT_TRUE(d2.IsObject()); + EXPECT_EQ(&d2.GetAllocator(), &a); + + // reset value + Value().Swap(d1); + EXPECT_TRUE(d1.IsNull()); + + // reset document, including allocator + Document().Swap(d2); + EXPECT_TRUE(d2.IsNull()); + EXPECT_NE(&d2.GetAllocator(), &a); + + // testing std::swap compatibility + d1.SetBool(true); + using std::swap; + swap(d1, d2); + EXPECT_TRUE(d1.IsNull()); + EXPECT_TRUE(d2.IsTrue()); + + swap(o, d2); + EXPECT_TRUE(o.IsTrue()); + EXPECT_TRUE(d2.IsArray()); +} + + +// This should be slow due to assignment in inner-loop. +struct OutputStringStream : public std::ostringstream { + typedef char Ch; + + virtual ~OutputStringStream(); + + void Put(char c) { + put(c); + } + void Flush() {} +}; + +OutputStringStream::~OutputStringStream() {} + +TEST(Document, AcceptWriter) { + Document doc; + doc.Parse(" { \"hello\" : \"world\", \"t\" : true , \"f\" : false, \"n\": null, \"i\":123, \"pi\": 3.1416, \"a\":[1, 2, 3, 4] } "); + + OutputStringStream os; + Writer<OutputStringStream> writer(os); + doc.Accept(writer); + + EXPECT_EQ("{\"hello\":\"world\",\"t\":true,\"f\":false,\"n\":null,\"i\":123,\"pi\":3.1416,\"a\":[1,2,3,4]}", os.str()); +} + +TEST(Document, UserBuffer) { + typedef GenericDocument<UTF8<>, MemoryPoolAllocator<>, MemoryPoolAllocator<> > DocumentType; + char valueBuffer[4096]; + char parseBuffer[1024]; + MemoryPoolAllocator<> valueAllocator(valueBuffer, sizeof(valueBuffer)); + MemoryPoolAllocator<> parseAllocator(parseBuffer, sizeof(parseBuffer)); + DocumentType doc(&valueAllocator, sizeof(parseBuffer) / 2, &parseAllocator); + doc.Parse(" { \"hello\" : \"world\", \"t\" : true , \"f\" : false, \"n\": null, \"i\":123, \"pi\": 3.1416, \"a\":[1, 2, 3, 4] } "); + EXPECT_FALSE(doc.HasParseError()); + EXPECT_LE(valueAllocator.Size(), sizeof(valueBuffer)); + EXPECT_LE(parseAllocator.Size(), sizeof(parseBuffer)); + + // Cover MemoryPoolAllocator::Capacity() + EXPECT_LE(valueAllocator.Size(), valueAllocator.Capacity()); + EXPECT_LE(parseAllocator.Size(), parseAllocator.Capacity()); +} + +// Issue 226: Value of string type should not point to NULL +TEST(Document, AssertAcceptInvalidNameType) { + Document doc; + doc.SetObject(); + doc.AddMember("a", 0, doc.GetAllocator()); + doc.FindMember("a")->name.SetNull(); // Change name to non-string type. + + OutputStringStream os; + Writer<OutputStringStream> writer(os); + ASSERT_THROW(doc.Accept(writer), AssertException); +} + +// Issue 44: SetStringRaw doesn't work with wchar_t +TEST(Document, UTF16_Document) { + GenericDocument< UTF16<> > json; + json.Parse<kParseValidateEncodingFlag>(L"[{\"created_at\":\"Wed Oct 30 17:13:20 +0000 2012\"}]"); + + ASSERT_TRUE(json.IsArray()); + GenericValue< UTF16<> >& v = json[0]; + ASSERT_TRUE(v.IsObject()); + + GenericValue< UTF16<> >& s = v[L"created_at"]; + ASSERT_TRUE(s.IsString()); + + EXPECT_EQ(0, memcmp(L"Wed Oct 30 17:13:20 +0000 2012", s.GetString(), (s.GetStringLength() + 1) * sizeof(wchar_t))); +} + +#if RAPIDJSON_HAS_CXX11_RVALUE_REFS + +#if 0 // Many old compiler does not support these. Turn it off temporaily. + +#include <type_traits> + +TEST(Document, Traits) { + static_assert(std::is_constructible<Document>::value, ""); + static_assert(std::is_default_constructible<Document>::value, ""); +#ifndef _MSC_VER + static_assert(!std::is_copy_constructible<Document>::value, ""); +#endif + static_assert(std::is_move_constructible<Document>::value, ""); + + static_assert(!std::is_nothrow_constructible<Document>::value, ""); + static_assert(!std::is_nothrow_default_constructible<Document>::value, ""); +#ifndef _MSC_VER + static_assert(!std::is_nothrow_copy_constructible<Document>::value, ""); + static_assert(std::is_nothrow_move_constructible<Document>::value, ""); +#endif + + static_assert(std::is_assignable<Document,Document>::value, ""); +#ifndef _MSC_VER + static_assert(!std::is_copy_assignable<Document>::value, ""); +#endif + static_assert(std::is_move_assignable<Document>::value, ""); + +#ifndef _MSC_VER + static_assert(std::is_nothrow_assignable<Document, Document>::value, ""); +#endif + static_assert(!std::is_nothrow_copy_assignable<Document>::value, ""); +#ifndef _MSC_VER + static_assert(std::is_nothrow_move_assignable<Document>::value, ""); +#endif + + static_assert( std::is_destructible<Document>::value, ""); +#ifndef _MSC_VER + static_assert(std::is_nothrow_destructible<Document>::value, ""); +#endif +} + +#endif + +template <typename Allocator> +struct DocumentMove: public ::testing::Test { +}; + +typedef ::testing::Types< CrtAllocator, MemoryPoolAllocator<> > MoveAllocatorTypes; +TYPED_TEST_CASE(DocumentMove, MoveAllocatorTypes); + +TYPED_TEST(DocumentMove, MoveConstructor) { + typedef TypeParam Allocator; + typedef GenericDocument<UTF8<>, Allocator> D; + Allocator allocator; + + D a(&allocator); + a.Parse("[\"one\", \"two\", \"three\"]"); + EXPECT_FALSE(a.HasParseError()); + EXPECT_TRUE(a.IsArray()); + EXPECT_EQ(3u, a.Size()); + EXPECT_EQ(&a.GetAllocator(), &allocator); + + // Document b(a); // does not compile (!is_copy_constructible) + D b(std::move(a)); + EXPECT_TRUE(a.IsNull()); + EXPECT_TRUE(b.IsArray()); + EXPECT_EQ(3u, b.Size()); + EXPECT_THROW(a.GetAllocator(), AssertException); + EXPECT_EQ(&b.GetAllocator(), &allocator); + + b.Parse("{\"Foo\": \"Bar\", \"Baz\": 42}"); + EXPECT_FALSE(b.HasParseError()); + EXPECT_TRUE(b.IsObject()); + EXPECT_EQ(2u, b.MemberCount()); + + // Document c = a; // does not compile (!is_copy_constructible) + D c = std::move(b); + EXPECT_TRUE(b.IsNull()); + EXPECT_TRUE(c.IsObject()); + EXPECT_EQ(2u, c.MemberCount()); + EXPECT_THROW(b.GetAllocator(), AssertException); + EXPECT_EQ(&c.GetAllocator(), &allocator); +} + +TYPED_TEST(DocumentMove, MoveConstructorParseError) { + typedef TypeParam Allocator; + typedef GenericDocument<UTF8<>, Allocator> D; + + ParseResult noError; + D a; + a.Parse("{ 4 = 4]"); + ParseResult error(a.GetParseError(), a.GetErrorOffset()); + EXPECT_TRUE(a.HasParseError()); + EXPECT_NE(error.Code(), noError.Code()); + EXPECT_NE(error.Offset(), noError.Offset()); + + D b(std::move(a)); + EXPECT_FALSE(a.HasParseError()); + EXPECT_TRUE(b.HasParseError()); + EXPECT_EQ(a.GetParseError(), noError.Code()); + EXPECT_EQ(b.GetParseError(), error.Code()); + EXPECT_EQ(a.GetErrorOffset(), noError.Offset()); + EXPECT_EQ(b.GetErrorOffset(), error.Offset()); + + D c(std::move(b)); + EXPECT_FALSE(b.HasParseError()); + EXPECT_TRUE(c.HasParseError()); + EXPECT_EQ(b.GetParseError(), noError.Code()); + EXPECT_EQ(c.GetParseError(), error.Code()); + EXPECT_EQ(b.GetErrorOffset(), noError.Offset()); + EXPECT_EQ(c.GetErrorOffset(), error.Offset()); +} + +// This test does not properly use parsing, just for testing. +// It must call ClearStack() explicitly to prevent memory leak. +// But here we cannot as ClearStack() is private. +#if 0 +TYPED_TEST(DocumentMove, MoveConstructorStack) { + typedef TypeParam Allocator; + typedef UTF8<> Encoding; + typedef GenericDocument<Encoding, Allocator> Document; + + Document a; + size_t defaultCapacity = a.GetStackCapacity(); + + // Trick Document into getting GetStackCapacity() to return non-zero + typedef GenericReader<Encoding, Encoding, Allocator> Reader; + Reader reader(&a.GetAllocator()); + GenericStringStream<Encoding> is("[\"one\", \"two\", \"three\"]"); + reader.template Parse<kParseDefaultFlags>(is, a); + size_t capacity = a.GetStackCapacity(); + EXPECT_GT(capacity, 0u); + + Document b(std::move(a)); + EXPECT_EQ(a.GetStackCapacity(), defaultCapacity); + EXPECT_EQ(b.GetStackCapacity(), capacity); + + Document c = std::move(b); + EXPECT_EQ(b.GetStackCapacity(), defaultCapacity); + EXPECT_EQ(c.GetStackCapacity(), capacity); +} +#endif + +TYPED_TEST(DocumentMove, MoveAssignment) { + typedef TypeParam Allocator; + typedef GenericDocument<UTF8<>, Allocator> D; + Allocator allocator; + + D a(&allocator); + a.Parse("[\"one\", \"two\", \"three\"]"); + EXPECT_FALSE(a.HasParseError()); + EXPECT_TRUE(a.IsArray()); + EXPECT_EQ(3u, a.Size()); + EXPECT_EQ(&a.GetAllocator(), &allocator); + + // Document b; b = a; // does not compile (!is_copy_assignable) + D b; + b = std::move(a); + EXPECT_TRUE(a.IsNull()); + EXPECT_TRUE(b.IsArray()); + EXPECT_EQ(3u, b.Size()); + EXPECT_THROW(a.GetAllocator(), AssertException); + EXPECT_EQ(&b.GetAllocator(), &allocator); + + b.Parse("{\"Foo\": \"Bar\", \"Baz\": 42}"); + EXPECT_FALSE(b.HasParseError()); + EXPECT_TRUE(b.IsObject()); + EXPECT_EQ(2u, b.MemberCount()); + + // Document c; c = a; // does not compile (see static_assert) + D c; + c = std::move(b); + EXPECT_TRUE(b.IsNull()); + EXPECT_TRUE(c.IsObject()); + EXPECT_EQ(2u, c.MemberCount()); + EXPECT_THROW(b.GetAllocator(), AssertException); + EXPECT_EQ(&c.GetAllocator(), &allocator); +} + +TYPED_TEST(DocumentMove, MoveAssignmentParseError) { + typedef TypeParam Allocator; + typedef GenericDocument<UTF8<>, Allocator> D; + + ParseResult noError; + D a; + a.Parse("{ 4 = 4]"); + ParseResult error(a.GetParseError(), a.GetErrorOffset()); + EXPECT_TRUE(a.HasParseError()); + EXPECT_NE(error.Code(), noError.Code()); + EXPECT_NE(error.Offset(), noError.Offset()); + + D b; + b = std::move(a); + EXPECT_FALSE(a.HasParseError()); + EXPECT_TRUE(b.HasParseError()); + EXPECT_EQ(a.GetParseError(), noError.Code()); + EXPECT_EQ(b.GetParseError(), error.Code()); + EXPECT_EQ(a.GetErrorOffset(), noError.Offset()); + EXPECT_EQ(b.GetErrorOffset(), error.Offset()); + + D c; + c = std::move(b); + EXPECT_FALSE(b.HasParseError()); + EXPECT_TRUE(c.HasParseError()); + EXPECT_EQ(b.GetParseError(), noError.Code()); + EXPECT_EQ(c.GetParseError(), error.Code()); + EXPECT_EQ(b.GetErrorOffset(), noError.Offset()); + EXPECT_EQ(c.GetErrorOffset(), error.Offset()); +} + +// This test does not properly use parsing, just for testing. +// It must call ClearStack() explicitly to prevent memory leak. +// But here we cannot as ClearStack() is private. +#if 0 +TYPED_TEST(DocumentMove, MoveAssignmentStack) { + typedef TypeParam Allocator; + typedef UTF8<> Encoding; + typedef GenericDocument<Encoding, Allocator> D; + + D a; + size_t defaultCapacity = a.GetStackCapacity(); + + // Trick Document into getting GetStackCapacity() to return non-zero + typedef GenericReader<Encoding, Encoding, Allocator> Reader; + Reader reader(&a.GetAllocator()); + GenericStringStream<Encoding> is("[\"one\", \"two\", \"three\"]"); + reader.template Parse<kParseDefaultFlags>(is, a); + size_t capacity = a.GetStackCapacity(); + EXPECT_GT(capacity, 0u); + + D b; + b = std::move(a); + EXPECT_EQ(a.GetStackCapacity(), defaultCapacity); + EXPECT_EQ(b.GetStackCapacity(), capacity); + + D c; + c = std::move(b); + EXPECT_EQ(b.GetStackCapacity(), defaultCapacity); + EXPECT_EQ(c.GetStackCapacity(), capacity); +} +#endif + +#endif // RAPIDJSON_HAS_CXX11_RVALUE_REFS + +// Issue 22: Memory corruption via operator= +// Fixed by making unimplemented assignment operator private. +//TEST(Document, Assignment) { +// Document d1; +// Document d2; +// d1 = d2; +//} + +#ifdef __clang__ +RAPIDJSON_DIAG_POP +#endif diff --git a/src/rapidjson/test/unittest/dtoatest.cpp b/src/rapidjson/test/unittest/dtoatest.cpp new file mode 100644 index 000000000..afd76eb09 --- /dev/null +++ b/src/rapidjson/test/unittest/dtoatest.cpp @@ -0,0 +1,98 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#include "unittest.h" +#include "rapidjson/internal/dtoa.h" + +#ifdef __GNUC__ +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(type-limits) +#endif + +using namespace rapidjson::internal; + +TEST(dtoa, normal) { + char buffer[30]; + +#define TEST_DTOA(d, a)\ + *dtoa(d, buffer) = '\0';\ + EXPECT_STREQ(a, buffer) + + TEST_DTOA(0.0, "0.0"); + TEST_DTOA(-0.0, "-0.0"); + TEST_DTOA(1.0, "1.0"); + TEST_DTOA(-1.0, "-1.0"); + TEST_DTOA(1.2345, "1.2345"); + TEST_DTOA(1.2345678, "1.2345678"); + TEST_DTOA(0.123456789012, "0.123456789012"); + TEST_DTOA(1234567.8, "1234567.8"); + TEST_DTOA(-79.39773355813419, "-79.39773355813419"); + TEST_DTOA(0.000001, "0.000001"); + TEST_DTOA(0.0000001, "1e-7"); + TEST_DTOA(1e30, "1e30"); + TEST_DTOA(1.234567890123456e30, "1.234567890123456e30"); + TEST_DTOA(5e-324, "5e-324"); // Min subnormal positive double + TEST_DTOA(2.225073858507201e-308, "2.225073858507201e-308"); // Max subnormal positive double + TEST_DTOA(2.2250738585072014e-308, "2.2250738585072014e-308"); // Min normal positive double + TEST_DTOA(1.7976931348623157e308, "1.7976931348623157e308"); // Max double + +#undef TEST_DTOA +} + +TEST(dtoa, maxDecimalPlaces) { + char buffer[30]; + +#define TEST_DTOA(m, d, a)\ + *dtoa(d, buffer, m) = '\0';\ + EXPECT_STREQ(a, buffer) + + TEST_DTOA(3, 0.0, "0.0"); + TEST_DTOA(1, 0.0, "0.0"); + TEST_DTOA(3, -0.0, "-0.0"); + TEST_DTOA(3, 1.0, "1.0"); + TEST_DTOA(3, -1.0, "-1.0"); + TEST_DTOA(3, 1.2345, "1.234"); + TEST_DTOA(2, 1.2345, "1.23"); + TEST_DTOA(1, 1.2345, "1.2"); + TEST_DTOA(3, 1.2345678, "1.234"); + TEST_DTOA(3, 1.0001, "1.0"); + TEST_DTOA(2, 1.0001, "1.0"); + TEST_DTOA(1, 1.0001, "1.0"); + TEST_DTOA(3, 0.123456789012, "0.123"); + TEST_DTOA(2, 0.123456789012, "0.12"); + TEST_DTOA(1, 0.123456789012, "0.1"); + TEST_DTOA(4, 0.0001, "0.0001"); + TEST_DTOA(3, 0.0001, "0.0"); + TEST_DTOA(2, 0.0001, "0.0"); + TEST_DTOA(1, 0.0001, "0.0"); + TEST_DTOA(3, 1234567.8, "1234567.8"); + TEST_DTOA(3, 1e30, "1e30"); + TEST_DTOA(3, 5e-324, "0.0"); // Min subnormal positive double + TEST_DTOA(3, 2.225073858507201e-308, "0.0"); // Max subnormal positive double + TEST_DTOA(3, 2.2250738585072014e-308, "0.0"); // Min normal positive double + TEST_DTOA(3, 1.7976931348623157e308, "1.7976931348623157e308"); // Max double + TEST_DTOA(5, -0.14000000000000001, "-0.14"); + TEST_DTOA(4, -0.14000000000000001, "-0.14"); + TEST_DTOA(3, -0.14000000000000001, "-0.14"); + TEST_DTOA(3, -0.10000000000000001, "-0.1"); + TEST_DTOA(2, -0.10000000000000001, "-0.1"); + TEST_DTOA(1, -0.10000000000000001, "-0.1"); + +#undef TEST_DTOA +} + + +#ifdef __GNUC__ +RAPIDJSON_DIAG_POP +#endif diff --git a/src/rapidjson/test/unittest/encodedstreamtest.cpp b/src/rapidjson/test/unittest/encodedstreamtest.cpp new file mode 100644 index 000000000..bc234d3ba --- /dev/null +++ b/src/rapidjson/test/unittest/encodedstreamtest.cpp @@ -0,0 +1,313 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#include "unittest.h" +#include "rapidjson/filereadstream.h" +#include "rapidjson/filewritestream.h" +#include "rapidjson/encodedstream.h" +#include "rapidjson/stringbuffer.h" +#include "rapidjson/memorystream.h" +#include "rapidjson/memorybuffer.h" + +using namespace rapidjson; + +class EncodedStreamTest : public ::testing::Test { +public: + EncodedStreamTest() : json_(), length_() {} + virtual ~EncodedStreamTest(); + + virtual void SetUp() { + json_ = ReadFile("utf8.json", true, &length_); + } + + virtual void TearDown() { + free(json_); + json_ = 0; + } + +private: + EncodedStreamTest(const EncodedStreamTest&); + EncodedStreamTest& operator=(const EncodedStreamTest&); + +protected: + static FILE* Open(const char* filename) { + const char *paths[] = { + "encodings", + "bin/encodings", + "../bin/encodings", + "../../bin/encodings", + "../../../bin/encodings" + }; + char buffer[1024]; + for (size_t i = 0; i < sizeof(paths) / sizeof(paths[0]); i++) { + sprintf(buffer, "%s/%s", paths[i], filename); + FILE *fp = fopen(buffer, "rb"); + if (fp) + return fp; + } + return 0; + } + + static char *ReadFile(const char* filename, bool appendPath, size_t* outLength) { + FILE *fp = appendPath ? Open(filename) : fopen(filename, "rb"); + + if (!fp) { + *outLength = 0; + return 0; + } + + fseek(fp, 0, SEEK_END); + *outLength = static_cast<size_t>(ftell(fp)); + fseek(fp, 0, SEEK_SET); + char* buffer = static_cast<char*>(malloc(*outLength + 1)); + size_t readLength = fread(buffer, 1, *outLength, fp); + buffer[readLength] = '\0'; + fclose(fp); + return buffer; + } + + template <typename FileEncoding, typename MemoryEncoding> + void TestEncodedInputStream(const char* filename) { + // Test FileReadStream + { + char buffer[16]; + FILE *fp = Open(filename); + ASSERT_TRUE(fp != 0); + FileReadStream fs(fp, buffer, sizeof(buffer)); + EncodedInputStream<FileEncoding, FileReadStream> eis(fs); + StringStream s(json_); + + while (eis.Peek() != '\0') { + unsigned expected, actual; + EXPECT_TRUE(UTF8<>::Decode(s, &expected)); + EXPECT_TRUE(MemoryEncoding::Decode(eis, &actual)); + EXPECT_EQ(expected, actual); + } + EXPECT_EQ('\0', s.Peek()); + fclose(fp); + } + + // Test MemoryStream + { + size_t size; + char* data = ReadFile(filename, true, &size); + MemoryStream ms(data, size); + EncodedInputStream<FileEncoding, MemoryStream> eis(ms); + StringStream s(json_); + + while (eis.Peek() != '\0') { + unsigned expected, actual; + EXPECT_TRUE(UTF8<>::Decode(s, &expected)); + EXPECT_TRUE(MemoryEncoding::Decode(eis, &actual)); + EXPECT_EQ(expected, actual); + } + EXPECT_EQ('\0', s.Peek()); + free(data); + EXPECT_EQ(size, eis.Tell()); + } + } + + void TestAutoUTFInputStream(const char *filename, bool expectHasBOM) { + // Test FileReadStream + { + char buffer[16]; + FILE *fp = Open(filename); + ASSERT_TRUE(fp != 0); + FileReadStream fs(fp, buffer, sizeof(buffer)); + AutoUTFInputStream<unsigned, FileReadStream> eis(fs); + EXPECT_EQ(expectHasBOM, eis.HasBOM()); + StringStream s(json_); + while (eis.Peek() != '\0') { + unsigned expected, actual; + EXPECT_TRUE(UTF8<>::Decode(s, &expected)); + EXPECT_TRUE(AutoUTF<unsigned>::Decode(eis, &actual)); + EXPECT_EQ(expected, actual); + } + EXPECT_EQ('\0', s.Peek()); + fclose(fp); + } + + // Test MemoryStream + { + size_t size; + char* data = ReadFile(filename, true, &size); + MemoryStream ms(data, size); + AutoUTFInputStream<unsigned, MemoryStream> eis(ms); + EXPECT_EQ(expectHasBOM, eis.HasBOM()); + StringStream s(json_); + + while (eis.Peek() != '\0') { + unsigned expected, actual; + EXPECT_TRUE(UTF8<>::Decode(s, &expected)); + EXPECT_TRUE(AutoUTF<unsigned>::Decode(eis, &actual)); + EXPECT_EQ(expected, actual); + } + EXPECT_EQ('\0', s.Peek()); + free(data); + EXPECT_EQ(size, eis.Tell()); + } + } + + template <typename FileEncoding, typename MemoryEncoding> + void TestEncodedOutputStream(const char* expectedFilename, bool putBOM) { + // Test FileWriteStream + { + char filename[L_tmpnam]; + FILE* fp = TempFile(filename); + char buffer[16]; + FileWriteStream os(fp, buffer, sizeof(buffer)); + EncodedOutputStream<FileEncoding, FileWriteStream> eos(os, putBOM); + StringStream s(json_); + while (s.Peek() != '\0') { + bool success = Transcoder<UTF8<>, MemoryEncoding>::Transcode(s, eos); + EXPECT_TRUE(success); + } + eos.Flush(); + fclose(fp); + EXPECT_TRUE(CompareFile(filename, expectedFilename)); + remove(filename); + } + + // Test MemoryBuffer + { + MemoryBuffer mb; + EncodedOutputStream<FileEncoding, MemoryBuffer> eos(mb, putBOM); + StringStream s(json_); + while (s.Peek() != '\0') { + bool success = Transcoder<UTF8<>, MemoryEncoding>::Transcode(s, eos); + EXPECT_TRUE(success); + } + eos.Flush(); + EXPECT_TRUE(CompareBufferFile(mb.GetBuffer(), mb.GetSize(), expectedFilename)); + } + } + + void TestAutoUTFOutputStream(UTFType type, bool putBOM, const char *expectedFilename) { + // Test FileWriteStream + { + char filename[L_tmpnam]; + FILE* fp = TempFile(filename); + + char buffer[16]; + FileWriteStream os(fp, buffer, sizeof(buffer)); + AutoUTFOutputStream<unsigned, FileWriteStream> eos(os, type, putBOM); + StringStream s(json_); + while (s.Peek() != '\0') { + bool success = Transcoder<UTF8<>, AutoUTF<unsigned> >::Transcode(s, eos); + EXPECT_TRUE(success); + } + eos.Flush(); + fclose(fp); + EXPECT_TRUE(CompareFile(filename, expectedFilename)); + remove(filename); + } + + // Test MemoryBuffer + { + MemoryBuffer mb; + AutoUTFOutputStream<unsigned, MemoryBuffer> eos(mb, type, putBOM); + StringStream s(json_); + while (s.Peek() != '\0') { + bool success = Transcoder<UTF8<>, AutoUTF<unsigned> >::Transcode(s, eos); + EXPECT_TRUE(success); + } + eos.Flush(); + EXPECT_TRUE(CompareBufferFile(mb.GetBuffer(), mb.GetSize(), expectedFilename)); + } + } + + bool CompareFile(const char* filename, const char* expectedFilename) { + size_t actualLength, expectedLength; + char* actualBuffer = ReadFile(filename, false, &actualLength); + char* expectedBuffer = ReadFile(expectedFilename, true, &expectedLength); + bool ret = (expectedLength == actualLength) && memcmp(expectedBuffer, actualBuffer, actualLength) == 0; + free(actualBuffer); + free(expectedBuffer); + return ret; + } + + bool CompareBufferFile(const char* actualBuffer, size_t actualLength, const char* expectedFilename) { + size_t expectedLength; + char* expectedBuffer = ReadFile(expectedFilename, true, &expectedLength); + bool ret = (expectedLength == actualLength) && memcmp(expectedBuffer, actualBuffer, actualLength) == 0; + free(expectedBuffer); + return ret; + } + + char *json_; + size_t length_; +}; + +EncodedStreamTest::~EncodedStreamTest() {} + +TEST_F(EncodedStreamTest, EncodedInputStream) { + TestEncodedInputStream<UTF8<>, UTF8<> >("utf8.json"); + TestEncodedInputStream<UTF8<>, UTF8<> >("utf8bom.json"); + TestEncodedInputStream<UTF16LE<>, UTF16<> >("utf16le.json"); + TestEncodedInputStream<UTF16LE<>, UTF16<> >("utf16lebom.json"); + TestEncodedInputStream<UTF16BE<>, UTF16<> >("utf16be.json"); + TestEncodedInputStream<UTF16BE<>, UTF16<> >("utf16bebom.json"); + TestEncodedInputStream<UTF32LE<>, UTF32<> >("utf32le.json"); + TestEncodedInputStream<UTF32LE<>, UTF32<> >("utf32lebom.json"); + TestEncodedInputStream<UTF32BE<>, UTF32<> >("utf32be.json"); + TestEncodedInputStream<UTF32BE<>, UTF32<> >("utf32bebom.json"); +} + +TEST_F(EncodedStreamTest, AutoUTFInputStream) { + TestAutoUTFInputStream("utf8.json", false); + TestAutoUTFInputStream("utf8bom.json", true); + TestAutoUTFInputStream("utf16le.json", false); + TestAutoUTFInputStream("utf16lebom.json",true); + TestAutoUTFInputStream("utf16be.json", false); + TestAutoUTFInputStream("utf16bebom.json",true); + TestAutoUTFInputStream("utf32le.json", false); + TestAutoUTFInputStream("utf32lebom.json",true); + TestAutoUTFInputStream("utf32be.json", false); + TestAutoUTFInputStream("utf32bebom.json", true); + + { + // Auto detection fail, use user defined UTF type + const char json[] = "{ }"; + MemoryStream ms(json, sizeof(json)); + AutoUTFInputStream<unsigned, MemoryStream> eis(ms, kUTF8); + EXPECT_FALSE(eis.HasBOM()); + EXPECT_EQ(kUTF8, eis.GetType()); + } +} + +TEST_F(EncodedStreamTest, EncodedOutputStream) { + TestEncodedOutputStream<UTF8<>, UTF8<> >("utf8.json", false); + TestEncodedOutputStream<UTF8<>, UTF8<> >("utf8bom.json", true); + TestEncodedOutputStream<UTF16LE<>, UTF16<> >("utf16le.json", false); + TestEncodedOutputStream<UTF16LE<>, UTF16<> >("utf16lebom.json",true); + TestEncodedOutputStream<UTF16BE<>, UTF16<> >("utf16be.json", false); + TestEncodedOutputStream<UTF16BE<>, UTF16<> >("utf16bebom.json",true); + TestEncodedOutputStream<UTF32LE<>, UTF32<> >("utf32le.json", false); + TestEncodedOutputStream<UTF32LE<>, UTF32<> >("utf32lebom.json",true); + TestEncodedOutputStream<UTF32BE<>, UTF32<> >("utf32be.json", false); + TestEncodedOutputStream<UTF32BE<>, UTF32<> >("utf32bebom.json",true); +} + +TEST_F(EncodedStreamTest, AutoUTFOutputStream) { + TestAutoUTFOutputStream(kUTF8, false, "utf8.json"); + TestAutoUTFOutputStream(kUTF8, true, "utf8bom.json"); + TestAutoUTFOutputStream(kUTF16LE, false, "utf16le.json"); + TestAutoUTFOutputStream(kUTF16LE, true, "utf16lebom.json"); + TestAutoUTFOutputStream(kUTF16BE, false, "utf16be.json"); + TestAutoUTFOutputStream(kUTF16BE, true, "utf16bebom.json"); + TestAutoUTFOutputStream(kUTF32LE, false, "utf32le.json"); + TestAutoUTFOutputStream(kUTF32LE, true, "utf32lebom.json"); + TestAutoUTFOutputStream(kUTF32BE, false, "utf32be.json"); + TestAutoUTFOutputStream(kUTF32BE, true, "utf32bebom.json"); +} diff --git a/src/rapidjson/test/unittest/encodingstest.cpp b/src/rapidjson/test/unittest/encodingstest.cpp new file mode 100644 index 000000000..67b0391ed --- /dev/null +++ b/src/rapidjson/test/unittest/encodingstest.cpp @@ -0,0 +1,451 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#include "unittest.h" +#include "rapidjson/filereadstream.h" +#include "rapidjson/filewritestream.h" +#include "rapidjson/encodedstream.h" +#include "rapidjson/stringbuffer.h" + +using namespace rapidjson; + +// Verification of encoders/decoders with Hoehrmann's UTF8 decoder + +// http://www.unicode.org/Public/UNIDATA/Blocks.txt +static const unsigned kCodepointRanges[] = { + 0x0000, 0x007F, // Basic Latin + 0x0080, 0x00FF, // Latin-1 Supplement + 0x0100, 0x017F, // Latin Extended-A + 0x0180, 0x024F, // Latin Extended-B + 0x0250, 0x02AF, // IPA Extensions + 0x02B0, 0x02FF, // Spacing Modifier Letters + 0x0300, 0x036F, // Combining Diacritical Marks + 0x0370, 0x03FF, // Greek and Coptic + 0x0400, 0x04FF, // Cyrillic + 0x0500, 0x052F, // Cyrillic Supplement + 0x0530, 0x058F, // Armenian + 0x0590, 0x05FF, // Hebrew + 0x0600, 0x06FF, // Arabic + 0x0700, 0x074F, // Syriac + 0x0750, 0x077F, // Arabic Supplement + 0x0780, 0x07BF, // Thaana + 0x07C0, 0x07FF, // NKo + 0x0800, 0x083F, // Samaritan + 0x0840, 0x085F, // Mandaic + 0x0900, 0x097F, // Devanagari + 0x0980, 0x09FF, // Bengali + 0x0A00, 0x0A7F, // Gurmukhi + 0x0A80, 0x0AFF, // Gujarati + 0x0B00, 0x0B7F, // Oriya + 0x0B80, 0x0BFF, // Tamil + 0x0C00, 0x0C7F, // Telugu + 0x0C80, 0x0CFF, // Kannada + 0x0D00, 0x0D7F, // Malayalam + 0x0D80, 0x0DFF, // Sinhala + 0x0E00, 0x0E7F, // Thai + 0x0E80, 0x0EFF, // Lao + 0x0F00, 0x0FFF, // Tibetan + 0x1000, 0x109F, // Myanmar + 0x10A0, 0x10FF, // Georgian + 0x1100, 0x11FF, // Hangul Jamo + 0x1200, 0x137F, // Ethiopic + 0x1380, 0x139F, // Ethiopic Supplement + 0x13A0, 0x13FF, // Cherokee + 0x1400, 0x167F, // Unified Canadian Aboriginal Syllabics + 0x1680, 0x169F, // Ogham + 0x16A0, 0x16FF, // Runic + 0x1700, 0x171F, // Tagalog + 0x1720, 0x173F, // Hanunoo + 0x1740, 0x175F, // Buhid + 0x1760, 0x177F, // Tagbanwa + 0x1780, 0x17FF, // Khmer + 0x1800, 0x18AF, // Mongolian + 0x18B0, 0x18FF, // Unified Canadian Aboriginal Syllabics Extended + 0x1900, 0x194F, // Limbu + 0x1950, 0x197F, // Tai Le + 0x1980, 0x19DF, // New Tai Lue + 0x19E0, 0x19FF, // Khmer Symbols + 0x1A00, 0x1A1F, // Buginese + 0x1A20, 0x1AAF, // Tai Tham + 0x1B00, 0x1B7F, // Balinese + 0x1B80, 0x1BBF, // Sundanese + 0x1BC0, 0x1BFF, // Batak + 0x1C00, 0x1C4F, // Lepcha + 0x1C50, 0x1C7F, // Ol Chiki + 0x1CD0, 0x1CFF, // Vedic Extensions + 0x1D00, 0x1D7F, // Phonetic Extensions + 0x1D80, 0x1DBF, // Phonetic Extensions Supplement + 0x1DC0, 0x1DFF, // Combining Diacritical Marks Supplement + 0x1E00, 0x1EFF, // Latin Extended Additional + 0x1F00, 0x1FFF, // Greek Extended + 0x2000, 0x206F, // General Punctuation + 0x2070, 0x209F, // Superscripts and Subscripts + 0x20A0, 0x20CF, // Currency Symbols + 0x20D0, 0x20FF, // Combining Diacritical Marks for Symbols + 0x2100, 0x214F, // Letterlike Symbols + 0x2150, 0x218F, // Number Forms + 0x2190, 0x21FF, // Arrows + 0x2200, 0x22FF, // Mathematical Operators + 0x2300, 0x23FF, // Miscellaneous Technical + 0x2400, 0x243F, // Control Pictures + 0x2440, 0x245F, // Optical Character Recognition + 0x2460, 0x24FF, // Enclosed Alphanumerics + 0x2500, 0x257F, // Box Drawing + 0x2580, 0x259F, // Block Elements + 0x25A0, 0x25FF, // Geometric Shapes + 0x2600, 0x26FF, // Miscellaneous Symbols + 0x2700, 0x27BF, // Dingbats + 0x27C0, 0x27EF, // Miscellaneous Mathematical Symbols-A + 0x27F0, 0x27FF, // Supplemental Arrows-A + 0x2800, 0x28FF, // Braille Patterns + 0x2900, 0x297F, // Supplemental Arrows-B + 0x2980, 0x29FF, // Miscellaneous Mathematical Symbols-B + 0x2A00, 0x2AFF, // Supplemental Mathematical Operators + 0x2B00, 0x2BFF, // Miscellaneous Symbols and Arrows + 0x2C00, 0x2C5F, // Glagolitic + 0x2C60, 0x2C7F, // Latin Extended-C + 0x2C80, 0x2CFF, // Coptic + 0x2D00, 0x2D2F, // Georgian Supplement + 0x2D30, 0x2D7F, // Tifinagh + 0x2D80, 0x2DDF, // Ethiopic Extended + 0x2DE0, 0x2DFF, // Cyrillic Extended-A + 0x2E00, 0x2E7F, // Supplemental Punctuation + 0x2E80, 0x2EFF, // CJK Radicals Supplement + 0x2F00, 0x2FDF, // Kangxi Radicals + 0x2FF0, 0x2FFF, // Ideographic Description Characters + 0x3000, 0x303F, // CJK Symbols and Punctuation + 0x3040, 0x309F, // Hiragana + 0x30A0, 0x30FF, // Katakana + 0x3100, 0x312F, // Bopomofo + 0x3130, 0x318F, // Hangul Compatibility Jamo + 0x3190, 0x319F, // Kanbun + 0x31A0, 0x31BF, // Bopomofo Extended + 0x31C0, 0x31EF, // CJK Strokes + 0x31F0, 0x31FF, // Katakana Phonetic Extensions + 0x3200, 0x32FF, // Enclosed CJK Letters and Months + 0x3300, 0x33FF, // CJK Compatibility + 0x3400, 0x4DBF, // CJK Unified Ideographs Extension A + 0x4DC0, 0x4DFF, // Yijing Hexagram Symbols + 0x4E00, 0x9FFF, // CJK Unified Ideographs + 0xA000, 0xA48F, // Yi Syllables + 0xA490, 0xA4CF, // Yi Radicals + 0xA4D0, 0xA4FF, // Lisu + 0xA500, 0xA63F, // Vai + 0xA640, 0xA69F, // Cyrillic Extended-B + 0xA6A0, 0xA6FF, // Bamum + 0xA700, 0xA71F, // Modifier Tone Letters + 0xA720, 0xA7FF, // Latin Extended-D + 0xA800, 0xA82F, // Syloti Nagri + 0xA830, 0xA83F, // Common Indic Number Forms + 0xA840, 0xA87F, // Phags-pa + 0xA880, 0xA8DF, // Saurashtra + 0xA8E0, 0xA8FF, // Devanagari Extended + 0xA900, 0xA92F, // Kayah Li + 0xA930, 0xA95F, // Rejang + 0xA960, 0xA97F, // Hangul Jamo Extended-A + 0xA980, 0xA9DF, // Javanese + 0xAA00, 0xAA5F, // Cham + 0xAA60, 0xAA7F, // Myanmar Extended-A + 0xAA80, 0xAADF, // Tai Viet + 0xAB00, 0xAB2F, // Ethiopic Extended-A + 0xABC0, 0xABFF, // Meetei Mayek + 0xAC00, 0xD7AF, // Hangul Syllables + 0xD7B0, 0xD7FF, // Hangul Jamo Extended-B + //0xD800, 0xDB7F, // High Surrogates + //0xDB80, 0xDBFF, // High Private Use Surrogates + //0xDC00, 0xDFFF, // Low Surrogates + 0xE000, 0xF8FF, // Private Use Area + 0xF900, 0xFAFF, // CJK Compatibility Ideographs + 0xFB00, 0xFB4F, // Alphabetic Presentation Forms + 0xFB50, 0xFDFF, // Arabic Presentation Forms-A + 0xFE00, 0xFE0F, // Variation Selectors + 0xFE10, 0xFE1F, // Vertical Forms + 0xFE20, 0xFE2F, // Combining Half Marks + 0xFE30, 0xFE4F, // CJK Compatibility Forms + 0xFE50, 0xFE6F, // Small Form Variants + 0xFE70, 0xFEFF, // Arabic Presentation Forms-B + 0xFF00, 0xFFEF, // Halfwidth and Fullwidth Forms + 0xFFF0, 0xFFFF, // Specials + 0x10000, 0x1007F, // Linear B Syllabary + 0x10080, 0x100FF, // Linear B Ideograms + 0x10100, 0x1013F, // Aegean Numbers + 0x10140, 0x1018F, // Ancient Greek Numbers + 0x10190, 0x101CF, // Ancient Symbols + 0x101D0, 0x101FF, // Phaistos Disc + 0x10280, 0x1029F, // Lycian + 0x102A0, 0x102DF, // Carian + 0x10300, 0x1032F, // Old Italic + 0x10330, 0x1034F, // Gothic + 0x10380, 0x1039F, // Ugaritic + 0x103A0, 0x103DF, // Old Persian + 0x10400, 0x1044F, // Deseret + 0x10450, 0x1047F, // Shavian + 0x10480, 0x104AF, // Osmanya + 0x10800, 0x1083F, // Cypriot Syllabary + 0x10840, 0x1085F, // Imperial Aramaic + 0x10900, 0x1091F, // Phoenician + 0x10920, 0x1093F, // Lydian + 0x10A00, 0x10A5F, // Kharoshthi + 0x10A60, 0x10A7F, // Old South Arabian + 0x10B00, 0x10B3F, // Avestan + 0x10B40, 0x10B5F, // Inscriptional Parthian + 0x10B60, 0x10B7F, // Inscriptional Pahlavi + 0x10C00, 0x10C4F, // Old Turkic + 0x10E60, 0x10E7F, // Rumi Numeral Symbols + 0x11000, 0x1107F, // Brahmi + 0x11080, 0x110CF, // Kaithi + 0x12000, 0x123FF, // Cuneiform + 0x12400, 0x1247F, // Cuneiform Numbers and Punctuation + 0x13000, 0x1342F, // Egyptian Hieroglyphs + 0x16800, 0x16A3F, // Bamum Supplement + 0x1B000, 0x1B0FF, // Kana Supplement + 0x1D000, 0x1D0FF, // Byzantine Musical Symbols + 0x1D100, 0x1D1FF, // Musical Symbols + 0x1D200, 0x1D24F, // Ancient Greek Musical Notation + 0x1D300, 0x1D35F, // Tai Xuan Jing Symbols + 0x1D360, 0x1D37F, // Counting Rod Numerals + 0x1D400, 0x1D7FF, // Mathematical Alphanumeric Symbols + 0x1F000, 0x1F02F, // Mahjong Tiles + 0x1F030, 0x1F09F, // Domino Tiles + 0x1F0A0, 0x1F0FF, // Playing Cards + 0x1F100, 0x1F1FF, // Enclosed Alphanumeric Supplement + 0x1F200, 0x1F2FF, // Enclosed Ideographic Supplement + 0x1F300, 0x1F5FF, // Miscellaneous Symbols And Pictographs + 0x1F600, 0x1F64F, // Emoticons + 0x1F680, 0x1F6FF, // Transport And Map Symbols + 0x1F700, 0x1F77F, // Alchemical Symbols + 0x20000, 0x2A6DF, // CJK Unified Ideographs Extension B + 0x2A700, 0x2B73F, // CJK Unified Ideographs Extension C + 0x2B740, 0x2B81F, // CJK Unified Ideographs Extension D + 0x2F800, 0x2FA1F, // CJK Compatibility Ideographs Supplement + 0xE0000, 0xE007F, // Tags + 0xE0100, 0xE01EF, // Variation Selectors Supplement + 0xF0000, 0xFFFFF, // Supplementary Private Use Area-A + 0x100000, 0x10FFFF, // Supplementary Private Use Area-B + 0xFFFFFFFF +}; + +// Copyright (c) 2008-2010 Bjoern Hoehrmann <bjoern@hoehrmann.de> +// See http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ for details. + +#define UTF8_ACCEPT 0u + +static const unsigned char utf8d[] = { + // The first part of the table maps bytes to character classes that + // to reduce the size of the transition table and create bitmasks. + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 8,8,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, + 10,3,3,3,3,3,3,3,3,3,3,3,3,4,3,3, 11,6,6,6,5,8,8,8,8,8,8,8,8,8,8,8, + + // The second part is a transition table that maps a combination + // of a state of the automaton and a character class to a state. + 0,12,24,36,60,96,84,12,12,12,48,72, 12,12,12,12,12,12,12,12,12,12,12,12, + 12, 0,12,12,12,12,12, 0,12, 0,12,12, 12,24,12,12,12,12,12,24,12,24,12,12, + 12,12,12,12,12,12,12,24,12,12,12,12, 12,24,12,12,12,12,12,12,12,24,12,12, + 12,12,12,12,12,12,12,36,12,36,12,12, 12,36,12,12,12,12,12,36,12,36,12,12, + 12,36,12,12,12,12,12,12,12,12,12,12, +}; + +static unsigned inline decode(unsigned* state, unsigned* codep, unsigned byte) { + unsigned type = utf8d[byte]; + + *codep = (*state != UTF8_ACCEPT) ? + (byte & 0x3fu) | (*codep << 6) : + (0xff >> type) & (byte); + + *state = utf8d[256 + *state + type]; + return *state; +} + +//static bool IsUTF8(unsigned char* s) { +// unsigned codepoint, state = 0; +// +// while (*s) +// decode(&state, &codepoint, *s++); +// +// return state == UTF8_ACCEPT; +//} + +TEST(EncodingsTest, UTF8) { + StringBuffer os, os2; + for (const unsigned* range = kCodepointRanges; *range != 0xFFFFFFFF; range += 2) { + for (unsigned codepoint = range[0]; codepoint <= range[1]; ++codepoint) { + os.Clear(); + UTF8<>::Encode(os, codepoint); + const char* encodedStr = os.GetString(); + + // Decode with Hoehrmann + { + unsigned decodedCodepoint = 0; + unsigned state = 0; + + unsigned decodedCount = 0; + for (const char* s = encodedStr; *s; ++s) + if (!decode(&state, &decodedCodepoint, static_cast<unsigned char>(*s))) { + EXPECT_EQ(codepoint, decodedCodepoint); + decodedCount++; + } + + if (*encodedStr) { // This decoder cannot handle U+0000 + EXPECT_EQ(1u, decodedCount); // Should only contain one code point + } + + EXPECT_EQ(UTF8_ACCEPT, state); + if (UTF8_ACCEPT != state) + std::cout << std::hex << codepoint << " " << decodedCodepoint << std::endl; + } + + // Decode + { + StringStream is(encodedStr); + unsigned decodedCodepoint; + bool result = UTF8<>::Decode(is, &decodedCodepoint); + EXPECT_TRUE(result); + EXPECT_EQ(codepoint, decodedCodepoint); + if (!result || codepoint != decodedCodepoint) + std::cout << std::hex << codepoint << " " << decodedCodepoint << std::endl; + } + + // Validate + { + StringStream is(encodedStr); + os2.Clear(); + bool result = UTF8<>::Validate(is, os2); + EXPECT_TRUE(result); + EXPECT_EQ(0, StrCmp(encodedStr, os2.GetString())); + } + } + } +} + +TEST(EncodingsTest, UTF16) { + GenericStringBuffer<UTF16<> > os, os2; + GenericStringBuffer<UTF8<> > utf8os; + for (const unsigned* range = kCodepointRanges; *range != 0xFFFFFFFF; range += 2) { + for (unsigned codepoint = range[0]; codepoint <= range[1]; ++codepoint) { + os.Clear(); + UTF16<>::Encode(os, codepoint); + const UTF16<>::Ch* encodedStr = os.GetString(); + + // Encode with Hoehrmann's code + if (codepoint != 0) // cannot handle U+0000 + { + // encode with UTF8<> first + utf8os.Clear(); + UTF8<>::Encode(utf8os, codepoint); + + // transcode from UTF8 to UTF16 with Hoehrmann's code + unsigned decodedCodepoint = 0; + unsigned state = 0; + UTF16<>::Ch buffer[3], *p = &buffer[0]; + for (const char* s = utf8os.GetString(); *s; ++s) { + if (!decode(&state, &decodedCodepoint, static_cast<unsigned char>(*s))) + break; + } + + if (codepoint <= 0xFFFF) + *p++ = static_cast<UTF16<>::Ch>(decodedCodepoint); + else { + // Encode code points above U+FFFF as surrogate pair. + *p++ = static_cast<UTF16<>::Ch>(0xD7C0 + (decodedCodepoint >> 10)); + *p++ = static_cast<UTF16<>::Ch>(0xDC00 + (decodedCodepoint & 0x3FF)); + } + *p++ = '\0'; + + EXPECT_EQ(0, StrCmp(buffer, encodedStr)); + } + + // Decode + { + GenericStringStream<UTF16<> > is(encodedStr); + unsigned decodedCodepoint; + bool result = UTF16<>::Decode(is, &decodedCodepoint); + EXPECT_TRUE(result); + EXPECT_EQ(codepoint, decodedCodepoint); + if (!result || codepoint != decodedCodepoint) + std::cout << std::hex << codepoint << " " << decodedCodepoint << std::endl; + } + + // Validate + { + GenericStringStream<UTF16<> > is(encodedStr); + os2.Clear(); + bool result = UTF16<>::Validate(is, os2); + EXPECT_TRUE(result); + EXPECT_EQ(0, StrCmp(encodedStr, os2.GetString())); + } + } + } +} + +TEST(EncodingsTest, UTF32) { + GenericStringBuffer<UTF32<> > os, os2; + for (const unsigned* range = kCodepointRanges; *range != 0xFFFFFFFF; range += 2) { + for (unsigned codepoint = range[0]; codepoint <= range[1]; ++codepoint) { + os.Clear(); + UTF32<>::Encode(os, codepoint); + const UTF32<>::Ch* encodedStr = os.GetString(); + + // Decode + { + GenericStringStream<UTF32<> > is(encodedStr); + unsigned decodedCodepoint; + bool result = UTF32<>::Decode(is, &decodedCodepoint); + EXPECT_TRUE(result); + EXPECT_EQ(codepoint, decodedCodepoint); + if (!result || codepoint != decodedCodepoint) + std::cout << std::hex << codepoint << " " << decodedCodepoint << std::endl; + } + + // Validate + { + GenericStringStream<UTF32<> > is(encodedStr); + os2.Clear(); + bool result = UTF32<>::Validate(is, os2); + EXPECT_TRUE(result); + EXPECT_EQ(0, StrCmp(encodedStr, os2.GetString())); + } + } + } +} + +TEST(EncodingsTest, ASCII) { + StringBuffer os, os2; + for (unsigned codepoint = 0; codepoint < 128; codepoint++) { + os.Clear(); + ASCII<>::Encode(os, codepoint); + const ASCII<>::Ch* encodedStr = os.GetString(); + { + StringStream is(encodedStr); + unsigned decodedCodepoint; + bool result = ASCII<>::Decode(is, &decodedCodepoint); + if (!result || codepoint != decodedCodepoint) + std::cout << std::hex << codepoint << " " << decodedCodepoint << std::endl; + } + + // Validate + { + StringStream is(encodedStr); + os2.Clear(); + bool result = ASCII<>::Validate(is, os2); + EXPECT_TRUE(result); + EXPECT_EQ(0, StrCmp(encodedStr, os2.GetString())); + } + } +} diff --git a/src/rapidjson/test/unittest/filestreamtest.cpp b/src/rapidjson/test/unittest/filestreamtest.cpp new file mode 100644 index 000000000..a38133fa7 --- /dev/null +++ b/src/rapidjson/test/unittest/filestreamtest.cpp @@ -0,0 +1,112 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#include "unittest.h" +#include "rapidjson/filereadstream.h" +#include "rapidjson/filewritestream.h" +#include "rapidjson/encodedstream.h" + +using namespace rapidjson; + +class FileStreamTest : public ::testing::Test { +public: + FileStreamTest() : filename_(), json_(), length_() {} + virtual ~FileStreamTest(); + + virtual void SetUp() { + const char *paths[] = { + "data/sample.json", + "bin/data/sample.json", + "../bin/data/sample.json", + "../../bin/data/sample.json", + "../../../bin/data/sample.json" + }; + FILE* fp = 0; + for (size_t i = 0; i < sizeof(paths) / sizeof(paths[0]); i++) { + fp = fopen(paths[i], "rb"); + if (fp) { + filename_ = paths[i]; + break; + } + } + ASSERT_TRUE(fp != 0); + + fseek(fp, 0, SEEK_END); + length_ = static_cast<size_t>(ftell(fp)); + fseek(fp, 0, SEEK_SET); + json_ = static_cast<char*>(malloc(length_ + 1)); + size_t readLength = fread(json_, 1, length_, fp); + json_[readLength] = '\0'; + fclose(fp); + } + + virtual void TearDown() { + free(json_); + json_ = 0; + } + +private: + FileStreamTest(const FileStreamTest&); + FileStreamTest& operator=(const FileStreamTest&); + +protected: + const char* filename_; + char *json_; + size_t length_; +}; + +FileStreamTest::~FileStreamTest() {} + +TEST_F(FileStreamTest, FileReadStream) { + FILE *fp = fopen(filename_, "rb"); + ASSERT_TRUE(fp != 0); + char buffer[65536]; + FileReadStream s(fp, buffer, sizeof(buffer)); + + for (size_t i = 0; i < length_; i++) { + EXPECT_EQ(json_[i], s.Peek()); + EXPECT_EQ(json_[i], s.Peek()); // 2nd time should be the same + EXPECT_EQ(json_[i], s.Take()); + } + + EXPECT_EQ(length_, s.Tell()); + EXPECT_EQ('\0', s.Peek()); + + fclose(fp); +} + +TEST_F(FileStreamTest, FileWriteStream) { + char filename[L_tmpnam]; + FILE* fp = TempFile(filename); + + char buffer[65536]; + FileWriteStream os(fp, buffer, sizeof(buffer)); + for (size_t i = 0; i < length_; i++) + os.Put(json_[i]); + os.Flush(); + fclose(fp); + + // Read it back to verify + fp = fopen(filename, "rb"); + FileReadStream is(fp, buffer, sizeof(buffer)); + + for (size_t i = 0; i < length_; i++) + EXPECT_EQ(json_[i], is.Take()); + + EXPECT_EQ(length_, is.Tell()); + fclose(fp); + + //std::cout << filename << std::endl; + remove(filename); +} diff --git a/src/rapidjson/test/unittest/fwdtest.cpp b/src/rapidjson/test/unittest/fwdtest.cpp new file mode 100644 index 000000000..4f3268461 --- /dev/null +++ b/src/rapidjson/test/unittest/fwdtest.cpp @@ -0,0 +1,227 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#include "unittest.h" + +// Using forward declared types here. + +#include "rapidjson/fwd.h" + +#ifdef __GNUC__ +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(effc++) +#endif + +using namespace rapidjson; + +struct Foo { + Foo(); + ~Foo(); + + // encodings.h + UTF8<char>* utf8; + UTF16<wchar_t>* utf16; + UTF16BE<wchar_t>* utf16be; + UTF16LE<wchar_t>* utf16le; + UTF32<unsigned>* utf32; + UTF32BE<unsigned>* utf32be; + UTF32LE<unsigned>* utf32le; + ASCII<char>* ascii; + AutoUTF<unsigned>* autoutf; + Transcoder<UTF8<char>, UTF8<char> >* transcoder; + + // allocators.h + CrtAllocator* crtallocator; + MemoryPoolAllocator<CrtAllocator>* memorypoolallocator; + + // stream.h + StringStream* stringstream; + InsituStringStream* insitustringstream; + + // stringbuffer.h + StringBuffer* stringbuffer; + + // // filereadstream.h + // FileReadStream* filereadstream; + + // // filewritestream.h + // FileWriteStream* filewritestream; + + // memorybuffer.h + MemoryBuffer* memorybuffer; + + // memorystream.h + MemoryStream* memorystream; + + // reader.h + BaseReaderHandler<UTF8<char>, void>* basereaderhandler; + Reader* reader; + + // writer.h + Writer<StringBuffer, UTF8<char>, UTF8<char>, CrtAllocator, 0>* writer; + + // prettywriter.h + PrettyWriter<StringBuffer, UTF8<char>, UTF8<char>, CrtAllocator, 0>* prettywriter; + + // document.h + Value* value; + Document* document; + + // pointer.h + Pointer* pointer; + + // schema.h + SchemaDocument* schemadocument; + SchemaValidator* schemavalidator; + + // char buffer[16]; +}; + +// Using type definitions here. + +#include "rapidjson/stringbuffer.h" +#include "rapidjson/filereadstream.h" +#include "rapidjson/filewritestream.h" +#include "rapidjson/memorybuffer.h" +#include "rapidjson/memorystream.h" +#include "rapidjson/document.h" // -> reader.h +#include "rapidjson/writer.h" +#include "rapidjson/prettywriter.h" +#include "rapidjson/schema.h" // -> pointer.h + +Foo::Foo() : + // encodings.h + utf8(RAPIDJSON_NEW(UTF8<>)), + utf16(RAPIDJSON_NEW(UTF16<>)), + utf16be(RAPIDJSON_NEW(UTF16BE<>)), + utf16le(RAPIDJSON_NEW(UTF16LE<>)), + utf32(RAPIDJSON_NEW(UTF32<>)), + utf32be(RAPIDJSON_NEW(UTF32BE<>)), + utf32le(RAPIDJSON_NEW(UTF32LE<>)), + ascii(RAPIDJSON_NEW(ASCII<>)), + autoutf(RAPIDJSON_NEW(AutoUTF<unsigned>)), + transcoder(RAPIDJSON_NEW((Transcoder<UTF8<>, UTF8<> >))), + + // allocators.h + crtallocator(RAPIDJSON_NEW(CrtAllocator)), + memorypoolallocator(RAPIDJSON_NEW(MemoryPoolAllocator<>)), + + // stream.h + stringstream(RAPIDJSON_NEW(StringStream(0))), + insitustringstream(RAPIDJSON_NEW(InsituStringStream(0))), + + // stringbuffer.h + stringbuffer(RAPIDJSON_NEW(StringBuffer)), + + // // filereadstream.h + // filereadstream(RAPIDJSON_NEW(FileReadStream(stdout, buffer, sizeof(buffer)))), + + // // filewritestream.h + // filewritestream(RAPIDJSON_NEW(FileWriteStream(stdout, buffer, sizeof(buffer)))), + + // memorybuffer.h + memorybuffer(RAPIDJSON_NEW(MemoryBuffer)), + + // memorystream.h + memorystream(RAPIDJSON_NEW(MemoryStream(0, 0))), + + // reader.h + basereaderhandler(RAPIDJSON_NEW((BaseReaderHandler<UTF8<>, void>))), + reader(RAPIDJSON_NEW(Reader)), + + // writer.h + writer(RAPIDJSON_NEW((Writer<StringBuffer>))), + + // prettywriter.h + prettywriter(RAPIDJSON_NEW((PrettyWriter<StringBuffer>))), + + // document.h + value(RAPIDJSON_NEW(Value)), + document(RAPIDJSON_NEW(Document)), + + // pointer.h + pointer(RAPIDJSON_NEW(Pointer)), + + // schema.h + schemadocument(RAPIDJSON_NEW(SchemaDocument(*document))), + schemavalidator(RAPIDJSON_NEW(SchemaValidator(*schemadocument))) +{ + +} + +Foo::~Foo() { + // encodings.h + RAPIDJSON_DELETE(utf8); + RAPIDJSON_DELETE(utf16); + RAPIDJSON_DELETE(utf16be); + RAPIDJSON_DELETE(utf16le); + RAPIDJSON_DELETE(utf32); + RAPIDJSON_DELETE(utf32be); + RAPIDJSON_DELETE(utf32le); + RAPIDJSON_DELETE(ascii); + RAPIDJSON_DELETE(autoutf); + RAPIDJSON_DELETE(transcoder); + + // allocators.h + RAPIDJSON_DELETE(crtallocator); + RAPIDJSON_DELETE(memorypoolallocator); + + // stream.h + RAPIDJSON_DELETE(stringstream); + RAPIDJSON_DELETE(insitustringstream); + + // stringbuffer.h + RAPIDJSON_DELETE(stringbuffer); + + // // filereadstream.h + // RAPIDJSON_DELETE(filereadstream); + + // // filewritestream.h + // RAPIDJSON_DELETE(filewritestream); + + // memorybuffer.h + RAPIDJSON_DELETE(memorybuffer); + + // memorystream.h + RAPIDJSON_DELETE(memorystream); + + // reader.h + RAPIDJSON_DELETE(basereaderhandler); + RAPIDJSON_DELETE(reader); + + // writer.h + RAPIDJSON_DELETE(writer); + + // prettywriter.h + RAPIDJSON_DELETE(prettywriter); + + // document.h + RAPIDJSON_DELETE(value); + RAPIDJSON_DELETE(document); + + // pointer.h + RAPIDJSON_DELETE(pointer); + + // schema.h + RAPIDJSON_DELETE(schemadocument); + RAPIDJSON_DELETE(schemavalidator); +} + +TEST(Fwd, Fwd) { + Foo f; +} + +#ifdef __GNUC__ +RAPIDJSON_DIAG_POP +#endif diff --git a/src/rapidjson/test/unittest/istreamwrappertest.cpp b/src/rapidjson/test/unittest/istreamwrappertest.cpp new file mode 100644 index 000000000..9d6fbcff0 --- /dev/null +++ b/src/rapidjson/test/unittest/istreamwrappertest.cpp @@ -0,0 +1,181 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#include "unittest.h" + +#include "rapidjson/istreamwrapper.h" +#include "rapidjson/encodedstream.h" +#include "rapidjson/document.h" +#include <sstream> +#include <fstream> + +#ifdef _MSC_VER +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(4702) // unreachable code +#endif + +using namespace rapidjson; +using namespace std; + +template <typename StringStreamType> +static void TestStringStream() { + typedef typename StringStreamType::char_type Ch; + + { + StringStreamType iss; + BasicIStreamWrapper<StringStreamType> is(iss); + EXPECT_EQ(0, is.Tell()); + if (sizeof(Ch) == 1) { + EXPECT_EQ(0, is.Peek4()); + EXPECT_EQ(0, is.Tell()); + } + EXPECT_EQ(0, is.Peek()); + EXPECT_EQ(0, is.Take()); + EXPECT_EQ(0, is.Tell()); + } + + { + Ch s[] = { 'A', 'B', 'C', '\0' }; + StringStreamType iss(s); + BasicIStreamWrapper<StringStreamType> is(iss); + EXPECT_EQ(0, is.Tell()); + if (sizeof(Ch) == 1) { + EXPECT_EQ(0, is.Peek4()); // less than 4 bytes + } + for (int i = 0; i < 3; i++) { + EXPECT_EQ(static_cast<size_t>(i), is.Tell()); + EXPECT_EQ('A' + i, is.Peek()); + EXPECT_EQ('A' + i, is.Peek()); + EXPECT_EQ('A' + i, is.Take()); + } + EXPECT_EQ(3, is.Tell()); + EXPECT_EQ(0, is.Peek()); + EXPECT_EQ(0, is.Take()); + } + + { + Ch s[] = { 'A', 'B', 'C', 'D', 'E', '\0' }; + StringStreamType iss(s); + BasicIStreamWrapper<StringStreamType> is(iss); + if (sizeof(Ch) == 1) { + const Ch* c = is.Peek4(); + for (int i = 0; i < 4; i++) + EXPECT_EQ('A' + i, c[i]); + EXPECT_EQ(0, is.Tell()); + } + for (int i = 0; i < 5; i++) { + EXPECT_EQ(static_cast<size_t>(i), is.Tell()); + EXPECT_EQ('A' + i, is.Peek()); + EXPECT_EQ('A' + i, is.Peek()); + EXPECT_EQ('A' + i, is.Take()); + } + EXPECT_EQ(5, is.Tell()); + EXPECT_EQ(0, is.Peek()); + EXPECT_EQ(0, is.Take()); + } +} + +TEST(IStreamWrapper, istringstream) { + TestStringStream<istringstream>(); +} + +TEST(IStreamWrapper, stringstream) { + TestStringStream<stringstream>(); +} + +TEST(IStreamWrapper, wistringstream) { + TestStringStream<wistringstream>(); +} + +TEST(IStreamWrapper, wstringstream) { + TestStringStream<wstringstream>(); +} + +template <typename FileStreamType> +static bool Open(FileStreamType& fs, const char* filename) { + const char *paths[] = { + "encodings", + "bin/encodings", + "../bin/encodings", + "../../bin/encodings", + "../../../bin/encodings" + }; + char buffer[1024]; + for (size_t i = 0; i < sizeof(paths) / sizeof(paths[0]); i++) { + sprintf(buffer, "%s/%s", paths[i], filename); + fs.open(buffer, ios_base::in | ios_base::binary); + if (fs.is_open()) + return true; + } + return false; +} + +TEST(IStreamWrapper, ifstream) { + ifstream ifs; + ASSERT_TRUE(Open(ifs, "utf8bom.json")); + IStreamWrapper isw(ifs); + EncodedInputStream<UTF8<>, IStreamWrapper> eis(isw); + Document d; + EXPECT_TRUE(!d.ParseStream(eis).HasParseError()); + EXPECT_TRUE(d.IsObject()); + EXPECT_EQ(5, d.MemberCount()); +} + +TEST(IStreamWrapper, fstream) { + fstream fs; + ASSERT_TRUE(Open(fs, "utf8bom.json")); + IStreamWrapper isw(fs); + EncodedInputStream<UTF8<>, IStreamWrapper> eis(isw); + Document d; + EXPECT_TRUE(!d.ParseStream(eis).HasParseError()); + EXPECT_TRUE(d.IsObject()); + EXPECT_EQ(5, d.MemberCount()); +} + +// wifstream/wfstream only works on C++11 with codecvt_utf16 +// But many C++11 library still not have it. +#if 0 +#include <codecvt> + +TEST(IStreamWrapper, wifstream) { + wifstream ifs; + ASSERT_TRUE(Open(ifs, "utf16bebom.json")); + ifs.imbue(std::locale(ifs.getloc(), + new std::codecvt_utf16<wchar_t, 0x10ffff, std::consume_header>)); + WIStreamWrapper isw(ifs); + GenericDocument<UTF16<> > d; + d.ParseStream<kParseDefaultFlags, UTF16<>, WIStreamWrapper>(isw); + EXPECT_TRUE(!d.HasParseError()); + EXPECT_TRUE(d.IsObject()); + EXPECT_EQ(5, d.MemberCount()); +} + +TEST(IStreamWrapper, wfstream) { + wfstream fs; + ASSERT_TRUE(Open(fs, "utf16bebom.json")); + fs.imbue(std::locale(fs.getloc(), + new std::codecvt_utf16<wchar_t, 0x10ffff, std::consume_header>)); + WIStreamWrapper isw(fs); + GenericDocument<UTF16<> > d; + d.ParseStream<kParseDefaultFlags, UTF16<>, WIStreamWrapper>(isw); + EXPECT_TRUE(!d.HasParseError()); + EXPECT_TRUE(d.IsObject()); + EXPECT_EQ(5, d.MemberCount()); +} + +#endif + +#ifdef _MSC_VER +RAPIDJSON_DIAG_POP +#endif diff --git a/src/rapidjson/test/unittest/itoatest.cpp b/src/rapidjson/test/unittest/itoatest.cpp new file mode 100644 index 000000000..b752a6a26 --- /dev/null +++ b/src/rapidjson/test/unittest/itoatest.cpp @@ -0,0 +1,160 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#include "unittest.h" +#include "rapidjson/internal/itoa.h" + +#ifdef __GNUC__ +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(type-limits) +#endif + +using namespace rapidjson::internal; + +template <typename T> +struct Traits { +}; + +template <> +struct Traits<uint32_t> { + enum { kBufferSize = 11 }; + enum { kMaxDigit = 10 }; + static uint32_t Negate(uint32_t x) { return x; } +}; + +template <> +struct Traits<int32_t> { + enum { kBufferSize = 12 }; + enum { kMaxDigit = 10 }; + static int32_t Negate(int32_t x) { return -x; } +}; + +template <> +struct Traits<uint64_t> { + enum { kBufferSize = 21 }; + enum { kMaxDigit = 20 }; + static uint64_t Negate(uint64_t x) { return x; } +}; + +template <> +struct Traits<int64_t> { + enum { kBufferSize = 22 }; + enum { kMaxDigit = 20 }; + static int64_t Negate(int64_t x) { return -x; } +}; + +template <typename T> +static void VerifyValue(T value, void(*f)(T, char*), char* (*g)(T, char*)) { + char buffer1[Traits<T>::kBufferSize]; + char buffer2[Traits<T>::kBufferSize]; + + f(value, buffer1); + *g(value, buffer2) = '\0'; + + + EXPECT_STREQ(buffer1, buffer2); +} + +template <typename T> +static void Verify(void(*f)(T, char*), char* (*g)(T, char*)) { + // Boundary cases + VerifyValue<T>(0, f, g); + VerifyValue<T>(std::numeric_limits<T>::min(), f, g); + VerifyValue<T>(std::numeric_limits<T>::max(), f, g); + + // 2^n - 1, 2^n, 10^n - 1, 10^n until overflow + for (uint32_t power = 2; power <= 10; power += 8) { + T i = 1, last; + do { + VerifyValue<T>(i - 1, f, g); + VerifyValue<T>(i, f, g); + if (std::numeric_limits<T>::min() < 0) { + VerifyValue<T>(Traits<T>::Negate(i), f, g); + VerifyValue<T>(Traits<T>::Negate(i + 1), f, g); + } + last = i; + if (i > static_cast<T>(std::numeric_limits<T>::max() / static_cast<T>(power))) + break; + i *= power; + } while (last < i); + } +} + +static void u32toa_naive(uint32_t value, char* buffer) { + char temp[10]; + char *p = temp; + do { + *p++ = static_cast<char>(char(value % 10) + '0'); + value /= 10; + } while (value > 0); + + do { + *buffer++ = *--p; + } while (p != temp); + + *buffer = '\0'; +} + +static void i32toa_naive(int32_t value, char* buffer) { + uint32_t u = static_cast<uint32_t>(value); + if (value < 0) { + *buffer++ = '-'; + u = ~u + 1; + } + u32toa_naive(u, buffer); +} + +static void u64toa_naive(uint64_t value, char* buffer) { + char temp[20]; + char *p = temp; + do { + *p++ = static_cast<char>(char(value % 10) + '0'); + value /= 10; + } while (value > 0); + + do { + *buffer++ = *--p; + } while (p != temp); + + *buffer = '\0'; +} + +static void i64toa_naive(int64_t value, char* buffer) { + uint64_t u = static_cast<uint64_t>(value); + if (value < 0) { + *buffer++ = '-'; + u = ~u + 1; + } + u64toa_naive(u, buffer); +} + +TEST(itoa, u32toa) { + Verify(u32toa_naive, u32toa); +} + +TEST(itoa, i32toa) { + Verify(i32toa_naive, i32toa); +} + +TEST(itoa, u64toa) { + Verify(u64toa_naive, u64toa); +} + +TEST(itoa, i64toa) { + Verify(i64toa_naive, i64toa); +} + +#ifdef __GNUC__ +RAPIDJSON_DIAG_POP +#endif diff --git a/src/rapidjson/test/unittest/jsoncheckertest.cpp b/src/rapidjson/test/unittest/jsoncheckertest.cpp new file mode 100644 index 000000000..bea788d26 --- /dev/null +++ b/src/rapidjson/test/unittest/jsoncheckertest.cpp @@ -0,0 +1,99 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#include "unittest.h" + +#include "rapidjson/document.h" + +using namespace rapidjson; + +static char* ReadFile(const char* filename, size_t& length) { + const char *paths[] = { + "jsonchecker", + "bin/jsonchecker", + "../bin/jsonchecker", + "../../bin/jsonchecker", + "../../../bin/jsonchecker" + }; + char buffer[1024]; + FILE *fp = 0; + for (size_t i = 0; i < sizeof(paths) / sizeof(paths[0]); i++) { + sprintf(buffer, "%s/%s", paths[i], filename); + fp = fopen(buffer, "rb"); + if (fp) + break; + } + + if (!fp) + return 0; + + fseek(fp, 0, SEEK_END); + length = static_cast<size_t>(ftell(fp)); + fseek(fp, 0, SEEK_SET); + char* json = static_cast<char*>(malloc(length + 1)); + size_t readLength = fread(json, 1, length, fp); + json[readLength] = '\0'; + fclose(fp); + return json; +} + +TEST(JsonChecker, Reader) { + char filename[256]; + + // jsonchecker/failXX.json + for (int i = 1; i <= 33; i++) { + if (i == 1) // fail1.json is valid in rapidjson, which has no limitation on type of root element (RFC 7159). + continue; + if (i == 18) // fail18.json is valid in rapidjson, which has no limitation on depth of nesting. + continue; + + sprintf(filename, "fail%d.json", i); + size_t length; + char* json = ReadFile(filename, length); + if (!json) { + printf("jsonchecker file %s not found", filename); + ADD_FAILURE(); + continue; + } + + GenericDocument<UTF8<>, CrtAllocator> document; // Use Crt allocator to check exception-safety (no memory leak) + document.Parse(json); + EXPECT_TRUE(document.HasParseError()); + + document.Parse<kParseIterativeFlag>(json); + EXPECT_TRUE(document.HasParseError()); + + free(json); + } + + // passX.json + for (int i = 1; i <= 3; i++) { + sprintf(filename, "pass%d.json", i); + size_t length; + char* json = ReadFile(filename, length); + if (!json) { + printf("jsonchecker file %s not found", filename); + continue; + } + + GenericDocument<UTF8<>, CrtAllocator> document; // Use Crt allocator to check exception-safety (no memory leak) + document.Parse(json); + EXPECT_FALSE(document.HasParseError()); + + document.Parse<kParseIterativeFlag>(json); + EXPECT_FALSE(document.HasParseError()); + + free(json); + } +} diff --git a/src/rapidjson/test/unittest/namespacetest.cpp b/src/rapidjson/test/unittest/namespacetest.cpp new file mode 100644 index 000000000..1814724ae --- /dev/null +++ b/src/rapidjson/test/unittest/namespacetest.cpp @@ -0,0 +1,70 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#include "unittest.h" + +// test another instantiation of RapidJSON in a different namespace + +#define RAPIDJSON_NAMESPACE my::rapid::json +#define RAPIDJSON_NAMESPACE_BEGIN namespace my { namespace rapid { namespace json { +#define RAPIDJSON_NAMESPACE_END } } } + +// include lots of RapidJSON files + +#include "rapidjson/document.h" +#include "rapidjson/writer.h" +#include "rapidjson/filereadstream.h" +#include "rapidjson/filewritestream.h" +#include "rapidjson/encodedstream.h" +#include "rapidjson/stringbuffer.h" + +static const char json[] = "{\"hello\":\"world\",\"t\":true,\"f\":false,\"n\":null,\"i\":123,\"pi\":3.1416,\"a\":[1,2,3,4]}"; + +TEST(NamespaceTest,Using) { + using namespace RAPIDJSON_NAMESPACE; + typedef GenericDocument<UTF8<>, CrtAllocator> DocumentType; + DocumentType doc; + + doc.Parse(json); + EXPECT_TRUE(!doc.HasParseError()); +} + +TEST(NamespaceTest,Direct) { + typedef RAPIDJSON_NAMESPACE::Document Document; + typedef RAPIDJSON_NAMESPACE::Reader Reader; + typedef RAPIDJSON_NAMESPACE::StringStream StringStream; + typedef RAPIDJSON_NAMESPACE::StringBuffer StringBuffer; + typedef RAPIDJSON_NAMESPACE::Writer<StringBuffer> WriterType; + + StringStream s(json); + StringBuffer buffer; + WriterType writer(buffer); + buffer.ShrinkToFit(); + Reader reader; + reader.Parse(s, writer); + + EXPECT_STREQ(json, buffer.GetString()); + EXPECT_EQ(sizeof(json)-1, buffer.GetSize()); + EXPECT_TRUE(writer.IsComplete()); + + Document doc; + doc.Parse(buffer.GetString()); + EXPECT_TRUE(!doc.HasParseError()); + + buffer.Clear(); + writer.Reset(buffer); + doc.Accept(writer); + EXPECT_STREQ(json, buffer.GetString()); + EXPECT_TRUE(writer.IsComplete()); +} diff --git a/src/rapidjson/test/unittest/ostreamwrappertest.cpp b/src/rapidjson/test/unittest/ostreamwrappertest.cpp new file mode 100644 index 000000000..b1d1cd827 --- /dev/null +++ b/src/rapidjson/test/unittest/ostreamwrappertest.cpp @@ -0,0 +1,91 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#include "unittest.h" + +#include "rapidjson/ostreamwrapper.h" +#include "rapidjson/encodedstream.h" +#include "rapidjson/document.h" +#include <sstream> +#include <fstream> + +using namespace rapidjson; +using namespace std; + +template <typename StringStreamType> +static void TestStringStream() { + typedef typename StringStreamType::char_type Ch; + + Ch s[] = { 'A', 'B', 'C', '\0' }; + StringStreamType oss(s); + BasicOStreamWrapper<StringStreamType> os(oss); + for (size_t i = 0; i < 3; i++) + os.Put(s[i]); + os.Flush(); + for (size_t i = 0; i < 3; i++) + EXPECT_EQ(s[i], oss.str()[i]); +} + +TEST(OStreamWrapper, ostringstream) { + TestStringStream<ostringstream>(); +} + +TEST(OStreamWrapper, stringstream) { + TestStringStream<stringstream>(); +} + +TEST(OStreamWrapper, wostringstream) { + TestStringStream<wostringstream>(); +} + +TEST(OStreamWrapper, wstringstream) { + TestStringStream<wstringstream>(); +} + +TEST(OStreamWrapper, cout) { + OStreamWrapper os(cout); + const char* s = "Hello World!\n"; + while (*s) + os.Put(*s++); + os.Flush(); +} + +template <typename FileStreamType> +static void TestFileStream() { + char filename[L_tmpnam]; + FILE* fp = TempFile(filename); + fclose(fp); + + const char* s = "Hello World!\n"; + { + ofstream ofs(filename, ios::out | ios::binary); + BasicOStreamWrapper<ofstream> osw(ofs); + for (const char* p = s; *p; p++) + osw.Put(*p); + osw.Flush(); + } + + fp = fopen(filename, "r"); + for (const char* p = s; *p; p++) + EXPECT_EQ(*p, static_cast<char>(fgetc(fp))); + fclose(fp); +} + +TEST(OStreamWrapper, ofstream) { + TestFileStream<ofstream>(); +} + +TEST(OStreamWrapper, fstream) { + TestFileStream<fstream>(); +} diff --git a/src/rapidjson/test/unittest/pointertest.cpp b/src/rapidjson/test/unittest/pointertest.cpp new file mode 100644 index 000000000..dbddbedee --- /dev/null +++ b/src/rapidjson/test/unittest/pointertest.cpp @@ -0,0 +1,1524 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#include "unittest.h" +#include "rapidjson/pointer.h" +#include "rapidjson/stringbuffer.h" +#include <sstream> + +using namespace rapidjson; + +static const char kJson[] = "{\n" +" \"foo\":[\"bar\", \"baz\"],\n" +" \"\" : 0,\n" +" \"a/b\" : 1,\n" +" \"c%d\" : 2,\n" +" \"e^f\" : 3,\n" +" \"g|h\" : 4,\n" +" \"i\\\\j\" : 5,\n" +" \"k\\\"l\" : 6,\n" +" \" \" : 7,\n" +" \"m~n\" : 8\n" +"}"; + +TEST(Pointer, DefaultConstructor) { + Pointer p; + EXPECT_TRUE(p.IsValid()); + EXPECT_EQ(0u, p.GetTokenCount()); +} + +TEST(Pointer, Parse) { + { + Pointer p(""); + EXPECT_TRUE(p.IsValid()); + EXPECT_EQ(0u, p.GetTokenCount()); + } + + { + Pointer p("/"); + EXPECT_TRUE(p.IsValid()); + EXPECT_EQ(1u, p.GetTokenCount()); + EXPECT_EQ(0u, p.GetTokens()[0].length); + EXPECT_STREQ("", p.GetTokens()[0].name); + EXPECT_EQ(kPointerInvalidIndex, p.GetTokens()[0].index); + } + + { + Pointer p("/foo"); + EXPECT_TRUE(p.IsValid()); + EXPECT_EQ(1u, p.GetTokenCount()); + EXPECT_EQ(3u, p.GetTokens()[0].length); + EXPECT_STREQ("foo", p.GetTokens()[0].name); + EXPECT_EQ(kPointerInvalidIndex, p.GetTokens()[0].index); + } + + #if RAPIDJSON_HAS_STDSTRING + { + Pointer p(std::string("/foo")); + EXPECT_TRUE(p.IsValid()); + EXPECT_EQ(1u, p.GetTokenCount()); + EXPECT_EQ(3u, p.GetTokens()[0].length); + EXPECT_STREQ("foo", p.GetTokens()[0].name); + EXPECT_EQ(kPointerInvalidIndex, p.GetTokens()[0].index); + } + #endif + + { + Pointer p("/foo/0"); + EXPECT_TRUE(p.IsValid()); + EXPECT_EQ(2u, p.GetTokenCount()); + EXPECT_EQ(3u, p.GetTokens()[0].length); + EXPECT_STREQ("foo", p.GetTokens()[0].name); + EXPECT_EQ(kPointerInvalidIndex, p.GetTokens()[0].index); + EXPECT_EQ(1u, p.GetTokens()[1].length); + EXPECT_STREQ("0", p.GetTokens()[1].name); + EXPECT_EQ(0u, p.GetTokens()[1].index); + } + + { + // Unescape ~1 + Pointer p("/a~1b"); + EXPECT_TRUE(p.IsValid()); + EXPECT_EQ(1u, p.GetTokenCount()); + EXPECT_EQ(3u, p.GetTokens()[0].length); + EXPECT_STREQ("a/b", p.GetTokens()[0].name); + } + + { + // Unescape ~0 + Pointer p("/m~0n"); + EXPECT_TRUE(p.IsValid()); + EXPECT_EQ(1u, p.GetTokenCount()); + EXPECT_EQ(3u, p.GetTokens()[0].length); + EXPECT_STREQ("m~n", p.GetTokens()[0].name); + } + + { + // empty name + Pointer p("/"); + EXPECT_TRUE(p.IsValid()); + EXPECT_EQ(1u, p.GetTokenCount()); + EXPECT_EQ(0u, p.GetTokens()[0].length); + EXPECT_STREQ("", p.GetTokens()[0].name); + } + + { + // empty and non-empty name + Pointer p("//a"); + EXPECT_TRUE(p.IsValid()); + EXPECT_EQ(2u, p.GetTokenCount()); + EXPECT_EQ(0u, p.GetTokens()[0].length); + EXPECT_STREQ("", p.GetTokens()[0].name); + EXPECT_EQ(1u, p.GetTokens()[1].length); + EXPECT_STREQ("a", p.GetTokens()[1].name); + } + + { + // Null characters + Pointer p("/\0\0", 3); + EXPECT_TRUE(p.IsValid()); + EXPECT_EQ(1u, p.GetTokenCount()); + EXPECT_EQ(2u, p.GetTokens()[0].length); + EXPECT_EQ('\0', p.GetTokens()[0].name[0]); + EXPECT_EQ('\0', p.GetTokens()[0].name[1]); + EXPECT_EQ('\0', p.GetTokens()[0].name[2]); + } + + { + // Valid index + Pointer p("/123"); + EXPECT_TRUE(p.IsValid()); + EXPECT_EQ(1u, p.GetTokenCount()); + EXPECT_STREQ("123", p.GetTokens()[0].name); + EXPECT_EQ(123u, p.GetTokens()[0].index); + } + + { + // Invalid index (with leading zero) + Pointer p("/01"); + EXPECT_TRUE(p.IsValid()); + EXPECT_EQ(1u, p.GetTokenCount()); + EXPECT_STREQ("01", p.GetTokens()[0].name); + EXPECT_EQ(kPointerInvalidIndex, p.GetTokens()[0].index); + } + + if (sizeof(SizeType) == 4) { + // Invalid index (overflow) + Pointer p("/4294967296"); + EXPECT_TRUE(p.IsValid()); + EXPECT_EQ(1u, p.GetTokenCount()); + EXPECT_STREQ("4294967296", p.GetTokens()[0].name); + EXPECT_EQ(kPointerInvalidIndex, p.GetTokens()[0].index); + } + + { + // kPointerParseErrorTokenMustBeginWithSolidus + Pointer p(" "); + EXPECT_FALSE(p.IsValid()); + EXPECT_EQ(kPointerParseErrorTokenMustBeginWithSolidus, p.GetParseErrorCode()); + EXPECT_EQ(0u, p.GetParseErrorOffset()); + } + + { + // kPointerParseErrorInvalidEscape + Pointer p("/~"); + EXPECT_FALSE(p.IsValid()); + EXPECT_EQ(kPointerParseErrorInvalidEscape, p.GetParseErrorCode()); + EXPECT_EQ(2u, p.GetParseErrorOffset()); + } + + { + // kPointerParseErrorInvalidEscape + Pointer p("/~2"); + EXPECT_FALSE(p.IsValid()); + EXPECT_EQ(kPointerParseErrorInvalidEscape, p.GetParseErrorCode()); + EXPECT_EQ(2u, p.GetParseErrorOffset()); + } +} + +TEST(Pointer, Parse_URIFragment) { + { + Pointer p("#"); + EXPECT_TRUE(p.IsValid()); + EXPECT_EQ(0u, p.GetTokenCount()); + } + + { + Pointer p("#/foo"); + EXPECT_TRUE(p.IsValid()); + EXPECT_EQ(1u, p.GetTokenCount()); + EXPECT_EQ(3u, p.GetTokens()[0].length); + EXPECT_STREQ("foo", p.GetTokens()[0].name); + } + + { + Pointer p("#/foo/0"); + EXPECT_TRUE(p.IsValid()); + EXPECT_EQ(2u, p.GetTokenCount()); + EXPECT_EQ(3u, p.GetTokens()[0].length); + EXPECT_STREQ("foo", p.GetTokens()[0].name); + EXPECT_EQ(1u, p.GetTokens()[1].length); + EXPECT_STREQ("0", p.GetTokens()[1].name); + EXPECT_EQ(0u, p.GetTokens()[1].index); + } + + { + // Unescape ~1 + Pointer p("#/a~1b"); + EXPECT_TRUE(p.IsValid()); + EXPECT_EQ(1u, p.GetTokenCount()); + EXPECT_EQ(3u, p.GetTokens()[0].length); + EXPECT_STREQ("a/b", p.GetTokens()[0].name); + } + + { + // Unescape ~0 + Pointer p("#/m~0n"); + EXPECT_TRUE(p.IsValid()); + EXPECT_EQ(1u, p.GetTokenCount()); + EXPECT_EQ(3u, p.GetTokens()[0].length); + EXPECT_STREQ("m~n", p.GetTokens()[0].name); + } + + { + // empty name + Pointer p("#/"); + EXPECT_TRUE(p.IsValid()); + EXPECT_EQ(1u, p.GetTokenCount()); + EXPECT_EQ(0u, p.GetTokens()[0].length); + EXPECT_STREQ("", p.GetTokens()[0].name); + } + + { + // empty and non-empty name + Pointer p("#//a"); + EXPECT_TRUE(p.IsValid()); + EXPECT_EQ(2u, p.GetTokenCount()); + EXPECT_EQ(0u, p.GetTokens()[0].length); + EXPECT_STREQ("", p.GetTokens()[0].name); + EXPECT_EQ(1u, p.GetTokens()[1].length); + EXPECT_STREQ("a", p.GetTokens()[1].name); + } + + { + // Null characters + Pointer p("#/%00%00"); + EXPECT_TRUE(p.IsValid()); + EXPECT_EQ(1u, p.GetTokenCount()); + EXPECT_EQ(2u, p.GetTokens()[0].length); + EXPECT_EQ('\0', p.GetTokens()[0].name[0]); + EXPECT_EQ('\0', p.GetTokens()[0].name[1]); + EXPECT_EQ('\0', p.GetTokens()[0].name[2]); + } + + { + // Percentage Escapes + EXPECT_STREQ("c%d", Pointer("#/c%25d").GetTokens()[0].name); + EXPECT_STREQ("e^f", Pointer("#/e%5Ef").GetTokens()[0].name); + EXPECT_STREQ("g|h", Pointer("#/g%7Ch").GetTokens()[0].name); + EXPECT_STREQ("i\\j", Pointer("#/i%5Cj").GetTokens()[0].name); + EXPECT_STREQ("k\"l", Pointer("#/k%22l").GetTokens()[0].name); + EXPECT_STREQ(" ", Pointer("#/%20").GetTokens()[0].name); + } + + { + // Valid index + Pointer p("#/123"); + EXPECT_TRUE(p.IsValid()); + EXPECT_EQ(1u, p.GetTokenCount()); + EXPECT_STREQ("123", p.GetTokens()[0].name); + EXPECT_EQ(123u, p.GetTokens()[0].index); + } + + { + // Invalid index (with leading zero) + Pointer p("#/01"); + EXPECT_TRUE(p.IsValid()); + EXPECT_EQ(1u, p.GetTokenCount()); + EXPECT_STREQ("01", p.GetTokens()[0].name); + EXPECT_EQ(kPointerInvalidIndex, p.GetTokens()[0].index); + } + + if (sizeof(SizeType) == 4) { + // Invalid index (overflow) + Pointer p("#/4294967296"); + EXPECT_TRUE(p.IsValid()); + EXPECT_EQ(1u, p.GetTokenCount()); + EXPECT_STREQ("4294967296", p.GetTokens()[0].name); + EXPECT_EQ(kPointerInvalidIndex, p.GetTokens()[0].index); + } + + { + // Decode UTF-8 perecent encoding to UTF-8 + Pointer p("#/%C2%A2"); + EXPECT_TRUE(p.IsValid()); + EXPECT_EQ(1u, p.GetTokenCount()); + EXPECT_STREQ("\xC2\xA2", p.GetTokens()[0].name); + } + + { + // Decode UTF-8 perecent encoding to UTF-16 + GenericPointer<GenericValue<UTF16<> > > p(L"#/%C2%A2"); + EXPECT_TRUE(p.IsValid()); + EXPECT_EQ(1u, p.GetTokenCount()); + EXPECT_EQ(static_cast<UTF16<>::Ch>(0x00A2), p.GetTokens()[0].name[0]); + EXPECT_EQ(1u, p.GetTokens()[0].length); + } + + { + // Decode UTF-8 perecent encoding to UTF-16 + GenericPointer<GenericValue<UTF16<> > > p(L"#/%E2%82%AC"); + EXPECT_TRUE(p.IsValid()); + EXPECT_EQ(1u, p.GetTokenCount()); + EXPECT_EQ(static_cast<UTF16<>::Ch>(0x20AC), p.GetTokens()[0].name[0]); + EXPECT_EQ(1u, p.GetTokens()[0].length); + } + + { + // kPointerParseErrorTokenMustBeginWithSolidus + Pointer p("# "); + EXPECT_FALSE(p.IsValid()); + EXPECT_EQ(kPointerParseErrorTokenMustBeginWithSolidus, p.GetParseErrorCode()); + EXPECT_EQ(1u, p.GetParseErrorOffset()); + } + + { + // kPointerParseErrorInvalidEscape + Pointer p("#/~"); + EXPECT_FALSE(p.IsValid()); + EXPECT_EQ(kPointerParseErrorInvalidEscape, p.GetParseErrorCode()); + EXPECT_EQ(3u, p.GetParseErrorOffset()); + } + + { + // kPointerParseErrorInvalidEscape + Pointer p("#/~2"); + EXPECT_FALSE(p.IsValid()); + EXPECT_EQ(kPointerParseErrorInvalidEscape, p.GetParseErrorCode()); + EXPECT_EQ(3u, p.GetParseErrorOffset()); + } + + { + // kPointerParseErrorInvalidPercentEncoding + Pointer p("#/%"); + EXPECT_FALSE(p.IsValid()); + EXPECT_EQ(kPointerParseErrorInvalidPercentEncoding, p.GetParseErrorCode()); + EXPECT_EQ(2u, p.GetParseErrorOffset()); + } + + { + // kPointerParseErrorInvalidPercentEncoding (invalid hex) + Pointer p("#/%g0"); + EXPECT_FALSE(p.IsValid()); + EXPECT_EQ(kPointerParseErrorInvalidPercentEncoding, p.GetParseErrorCode()); + EXPECT_EQ(2u, p.GetParseErrorOffset()); + } + + { + // kPointerParseErrorInvalidPercentEncoding (invalid hex) + Pointer p("#/%0g"); + EXPECT_FALSE(p.IsValid()); + EXPECT_EQ(kPointerParseErrorInvalidPercentEncoding, p.GetParseErrorCode()); + EXPECT_EQ(2u, p.GetParseErrorOffset()); + } + + { + // kPointerParseErrorInvalidPercentEncoding (incomplete UTF-8 sequence) + Pointer p("#/%C2"); + EXPECT_FALSE(p.IsValid()); + EXPECT_EQ(kPointerParseErrorInvalidPercentEncoding, p.GetParseErrorCode()); + EXPECT_EQ(2u, p.GetParseErrorOffset()); + } + + { + // kPointerParseErrorCharacterMustPercentEncode + Pointer p("#/ "); + EXPECT_FALSE(p.IsValid()); + EXPECT_EQ(kPointerParseErrorCharacterMustPercentEncode, p.GetParseErrorCode()); + EXPECT_EQ(2u, p.GetParseErrorOffset()); + } + + { + // kPointerParseErrorCharacterMustPercentEncode + Pointer p("#/\n"); + EXPECT_FALSE(p.IsValid()); + EXPECT_EQ(kPointerParseErrorCharacterMustPercentEncode, p.GetParseErrorCode()); + EXPECT_EQ(2u, p.GetParseErrorOffset()); + } +} + +TEST(Pointer, Stringify) { + // Test by roundtrip + const char* sources[] = { + "", + "/foo", + "/foo/0", + "/", + "/a~1b", + "/c%d", + "/e^f", + "/g|h", + "/i\\j", + "/k\"l", + "/ ", + "/m~0n", + "/\xC2\xA2", + "/\xE2\x82\xAC", + "/\xF0\x9D\x84\x9E" + }; + + for (size_t i = 0; i < sizeof(sources) / sizeof(sources[0]); i++) { + Pointer p(sources[i]); + StringBuffer s; + EXPECT_TRUE(p.Stringify(s)); + EXPECT_STREQ(sources[i], s.GetString()); + + // Stringify to URI fragment + StringBuffer s2; + EXPECT_TRUE(p.StringifyUriFragment(s2)); + Pointer p2(s2.GetString(), s2.GetSize()); + EXPECT_TRUE(p2.IsValid()); + EXPECT_TRUE(p == p2); + } + + { + // Strigify to URI fragment with an invalid UTF-8 sequence + Pointer p("/\xC2"); + StringBuffer s; + EXPECT_FALSE(p.StringifyUriFragment(s)); + } +} + +// Construct a Pointer with static tokens, no dynamic allocation involved. +#define NAME(s) { s, sizeof(s) / sizeof(s[0]) - 1, kPointerInvalidIndex } +#define INDEX(i) { #i, sizeof(#i) - 1, i } + +static const Pointer::Token kTokens[] = { NAME("foo"), INDEX(0) }; // equivalent to "/foo/0" + +#undef NAME +#undef INDEX + +TEST(Pointer, ConstructorWithToken) { + Pointer p(kTokens, sizeof(kTokens) / sizeof(kTokens[0])); + EXPECT_TRUE(p.IsValid()); + EXPECT_EQ(2u, p.GetTokenCount()); + EXPECT_EQ(3u, p.GetTokens()[0].length); + EXPECT_STREQ("foo", p.GetTokens()[0].name); + EXPECT_EQ(1u, p.GetTokens()[1].length); + EXPECT_STREQ("0", p.GetTokens()[1].name); + EXPECT_EQ(0u, p.GetTokens()[1].index); +} + +TEST(Pointer, CopyConstructor) { + { + Pointer p("/foo/0"); + Pointer q(p); + EXPECT_TRUE(q.IsValid()); + EXPECT_EQ(2u, q.GetTokenCount()); + EXPECT_EQ(3u, q.GetTokens()[0].length); + EXPECT_STREQ("foo", q.GetTokens()[0].name); + EXPECT_EQ(1u, q.GetTokens()[1].length); + EXPECT_STREQ("0", q.GetTokens()[1].name); + EXPECT_EQ(0u, q.GetTokens()[1].index); + } + + // Static tokens + { + Pointer p(kTokens, sizeof(kTokens) / sizeof(kTokens[0])); + Pointer q(p); + EXPECT_TRUE(q.IsValid()); + EXPECT_EQ(2u, q.GetTokenCount()); + EXPECT_EQ(3u, q.GetTokens()[0].length); + EXPECT_STREQ("foo", q.GetTokens()[0].name); + EXPECT_EQ(1u, q.GetTokens()[1].length); + EXPECT_STREQ("0", q.GetTokens()[1].name); + EXPECT_EQ(0u, q.GetTokens()[1].index); + } +} + +TEST(Pointer, Assignment) { + { + Pointer p("/foo/0"); + Pointer q; + q = p; + EXPECT_TRUE(q.IsValid()); + EXPECT_EQ(2u, q.GetTokenCount()); + EXPECT_EQ(3u, q.GetTokens()[0].length); + EXPECT_STREQ("foo", q.GetTokens()[0].name); + EXPECT_EQ(1u, q.GetTokens()[1].length); + EXPECT_STREQ("0", q.GetTokens()[1].name); + EXPECT_EQ(0u, q.GetTokens()[1].index); + q = q; + EXPECT_TRUE(q.IsValid()); + EXPECT_EQ(2u, q.GetTokenCount()); + EXPECT_EQ(3u, q.GetTokens()[0].length); + EXPECT_STREQ("foo", q.GetTokens()[0].name); + EXPECT_EQ(1u, q.GetTokens()[1].length); + EXPECT_STREQ("0", q.GetTokens()[1].name); + EXPECT_EQ(0u, q.GetTokens()[1].index); + } + + // Static tokens + { + Pointer p(kTokens, sizeof(kTokens) / sizeof(kTokens[0])); + Pointer q; + q = p; + EXPECT_TRUE(q.IsValid()); + EXPECT_EQ(2u, q.GetTokenCount()); + EXPECT_EQ(3u, q.GetTokens()[0].length); + EXPECT_STREQ("foo", q.GetTokens()[0].name); + EXPECT_EQ(1u, q.GetTokens()[1].length); + EXPECT_STREQ("0", q.GetTokens()[1].name); + EXPECT_EQ(0u, q.GetTokens()[1].index); + } +} + +TEST(Pointer, Append) { + { + Pointer p; + Pointer q = p.Append("foo"); + EXPECT_TRUE(Pointer("/foo") == q); + q = q.Append(1234); + EXPECT_TRUE(Pointer("/foo/1234") == q); + q = q.Append(""); + EXPECT_TRUE(Pointer("/foo/1234/") == q); + } + + { + Pointer p; + Pointer q = p.Append(Value("foo").Move()); + EXPECT_TRUE(Pointer("/foo") == q); + q = q.Append(Value(1234).Move()); + EXPECT_TRUE(Pointer("/foo/1234") == q); + q = q.Append(Value(kStringType).Move()); + EXPECT_TRUE(Pointer("/foo/1234/") == q); + } + +#if RAPIDJSON_HAS_STDSTRING + { + Pointer p; + Pointer q = p.Append(std::string("foo")); + EXPECT_TRUE(Pointer("/foo") == q); + } +#endif +} + +TEST(Pointer, Equality) { + EXPECT_TRUE(Pointer("/foo/0") == Pointer("/foo/0")); + EXPECT_FALSE(Pointer("/foo/0") == Pointer("/foo/1")); + EXPECT_FALSE(Pointer("/foo/0") == Pointer("/foo/0/1")); + EXPECT_FALSE(Pointer("/foo/0") == Pointer("a")); + EXPECT_FALSE(Pointer("a") == Pointer("a")); // Invalid always not equal +} + +TEST(Pointer, Inequality) { + EXPECT_FALSE(Pointer("/foo/0") != Pointer("/foo/0")); + EXPECT_TRUE(Pointer("/foo/0") != Pointer("/foo/1")); + EXPECT_TRUE(Pointer("/foo/0") != Pointer("/foo/0/1")); + EXPECT_TRUE(Pointer("/foo/0") != Pointer("a")); + EXPECT_TRUE(Pointer("a") != Pointer("a")); // Invalid always not equal +} + +TEST(Pointer, Create) { + Document d; + { + Value* v = &Pointer("").Create(d, d.GetAllocator()); + EXPECT_EQ(&d, v); + } + { + Value* v = &Pointer("/foo").Create(d, d.GetAllocator()); + EXPECT_EQ(&d["foo"], v); + } + { + Value* v = &Pointer("/foo/0").Create(d, d.GetAllocator()); + EXPECT_EQ(&d["foo"][0], v); + } + { + Value* v = &Pointer("/foo/-").Create(d, d.GetAllocator()); + EXPECT_EQ(&d["foo"][1], v); + } + + { + Value* v = &Pointer("/foo/-/-").Create(d, d.GetAllocator()); + // "foo/-" is a newly created null value x. + // "foo/-/-" finds that x is not an array, it converts x to empty object + // and treats - as "-" member name + EXPECT_EQ(&d["foo"][2]["-"], v); + } + + { + // Document with no allocator + Value* v = &Pointer("/foo/-").Create(d); + EXPECT_EQ(&d["foo"][3], v); + } + + { + // Value (not document) must give allocator + Value* v = &Pointer("/-").Create(d["foo"], d.GetAllocator()); + EXPECT_EQ(&d["foo"][4], v); + } +} + +TEST(Pointer, Get) { + Document d; + d.Parse(kJson); + + EXPECT_EQ(&d, Pointer("").Get(d)); + EXPECT_EQ(&d["foo"], Pointer("/foo").Get(d)); + EXPECT_EQ(&d["foo"][0], Pointer("/foo/0").Get(d)); + EXPECT_EQ(&d[""], Pointer("/").Get(d)); + EXPECT_EQ(&d["a/b"], Pointer("/a~1b").Get(d)); + EXPECT_EQ(&d["c%d"], Pointer("/c%d").Get(d)); + EXPECT_EQ(&d["e^f"], Pointer("/e^f").Get(d)); + EXPECT_EQ(&d["g|h"], Pointer("/g|h").Get(d)); + EXPECT_EQ(&d["i\\j"], Pointer("/i\\j").Get(d)); + EXPECT_EQ(&d["k\"l"], Pointer("/k\"l").Get(d)); + EXPECT_EQ(&d[" "], Pointer("/ ").Get(d)); + EXPECT_EQ(&d["m~n"], Pointer("/m~0n").Get(d)); + EXPECT_TRUE(Pointer("/abc").Get(d) == 0); + size_t unresolvedTokenIndex; + EXPECT_TRUE(Pointer("/foo/2").Get(d, &unresolvedTokenIndex) == 0); // Out of boundary + EXPECT_EQ(1, unresolvedTokenIndex); + EXPECT_TRUE(Pointer("/foo/a").Get(d, &unresolvedTokenIndex) == 0); // "/foo" is an array, cannot query by "a" + EXPECT_EQ(1, unresolvedTokenIndex); + EXPECT_TRUE(Pointer("/foo/0/0").Get(d, &unresolvedTokenIndex) == 0); // "/foo/0" is an string, cannot further query + EXPECT_EQ(2, unresolvedTokenIndex); + EXPECT_TRUE(Pointer("/foo/0/a").Get(d, &unresolvedTokenIndex) == 0); // "/foo/0" is an string, cannot further query + EXPECT_EQ(2, unresolvedTokenIndex); +} + +TEST(Pointer, GetWithDefault) { + Document d; + d.Parse(kJson); + + // Value version + Document::AllocatorType& a = d.GetAllocator(); + const Value v("qux"); + EXPECT_TRUE(Value("bar") == Pointer("/foo/0").GetWithDefault(d, v, a)); + EXPECT_TRUE(Value("baz") == Pointer("/foo/1").GetWithDefault(d, v, a)); + EXPECT_TRUE(Value("qux") == Pointer("/foo/2").GetWithDefault(d, v, a)); + EXPECT_TRUE(Value("last") == Pointer("/foo/-").GetWithDefault(d, Value("last").Move(), a)); + EXPECT_STREQ("last", d["foo"][3].GetString()); + + EXPECT_TRUE(Pointer("/foo/null").GetWithDefault(d, Value().Move(), a).IsNull()); + EXPECT_TRUE(Pointer("/foo/null").GetWithDefault(d, "x", a).IsNull()); + + // Generic version + EXPECT_EQ(-1, Pointer("/foo/int").GetWithDefault(d, -1, a).GetInt()); + EXPECT_EQ(-1, Pointer("/foo/int").GetWithDefault(d, -2, a).GetInt()); + EXPECT_EQ(0x87654321, Pointer("/foo/uint").GetWithDefault(d, 0x87654321, a).GetUint()); + EXPECT_EQ(0x87654321, Pointer("/foo/uint").GetWithDefault(d, 0x12345678, a).GetUint()); + + const int64_t i64 = static_cast<int64_t>(RAPIDJSON_UINT64_C2(0x80000000, 0)); + EXPECT_EQ(i64, Pointer("/foo/int64").GetWithDefault(d, i64, a).GetInt64()); + EXPECT_EQ(i64, Pointer("/foo/int64").GetWithDefault(d, i64 + 1, a).GetInt64()); + + const uint64_t u64 = RAPIDJSON_UINT64_C2(0xFFFFFFFFF, 0xFFFFFFFFF); + EXPECT_EQ(u64, Pointer("/foo/uint64").GetWithDefault(d, u64, a).GetUint64()); + EXPECT_EQ(u64, Pointer("/foo/uint64").GetWithDefault(d, u64 - 1, a).GetUint64()); + + EXPECT_TRUE(Pointer("/foo/true").GetWithDefault(d, true, a).IsTrue()); + EXPECT_TRUE(Pointer("/foo/true").GetWithDefault(d, false, a).IsTrue()); + + EXPECT_TRUE(Pointer("/foo/false").GetWithDefault(d, false, a).IsFalse()); + EXPECT_TRUE(Pointer("/foo/false").GetWithDefault(d, true, a).IsFalse()); + + // StringRef version + EXPECT_STREQ("Hello", Pointer("/foo/hello").GetWithDefault(d, "Hello", a).GetString()); + + // Copy string version + { + char buffer[256]; + strcpy(buffer, "World"); + EXPECT_STREQ("World", Pointer("/foo/world").GetWithDefault(d, buffer, a).GetString()); + memset(buffer, 0, sizeof(buffer)); + } + EXPECT_STREQ("World", GetValueByPointer(d, "/foo/world")->GetString()); + +#if RAPIDJSON_HAS_STDSTRING + EXPECT_STREQ("C++", Pointer("/foo/C++").GetWithDefault(d, std::string("C++"), a).GetString()); +#endif +} + +TEST(Pointer, GetWithDefault_NoAllocator) { + Document d; + d.Parse(kJson); + + // Value version + const Value v("qux"); + EXPECT_TRUE(Value("bar") == Pointer("/foo/0").GetWithDefault(d, v)); + EXPECT_TRUE(Value("baz") == Pointer("/foo/1").GetWithDefault(d, v)); + EXPECT_TRUE(Value("qux") == Pointer("/foo/2").GetWithDefault(d, v)); + EXPECT_TRUE(Value("last") == Pointer("/foo/-").GetWithDefault(d, Value("last").Move())); + EXPECT_STREQ("last", d["foo"][3].GetString()); + + EXPECT_TRUE(Pointer("/foo/null").GetWithDefault(d, Value().Move()).IsNull()); + EXPECT_TRUE(Pointer("/foo/null").GetWithDefault(d, "x").IsNull()); + + // Generic version + EXPECT_EQ(-1, Pointer("/foo/int").GetWithDefault(d, -1).GetInt()); + EXPECT_EQ(-1, Pointer("/foo/int").GetWithDefault(d, -2).GetInt()); + EXPECT_EQ(0x87654321, Pointer("/foo/uint").GetWithDefault(d, 0x87654321).GetUint()); + EXPECT_EQ(0x87654321, Pointer("/foo/uint").GetWithDefault(d, 0x12345678).GetUint()); + + const int64_t i64 = static_cast<int64_t>(RAPIDJSON_UINT64_C2(0x80000000, 0)); + EXPECT_EQ(i64, Pointer("/foo/int64").GetWithDefault(d, i64).GetInt64()); + EXPECT_EQ(i64, Pointer("/foo/int64").GetWithDefault(d, i64 + 1).GetInt64()); + + const uint64_t u64 = RAPIDJSON_UINT64_C2(0xFFFFFFFFF, 0xFFFFFFFFF); + EXPECT_EQ(u64, Pointer("/foo/uint64").GetWithDefault(d, u64).GetUint64()); + EXPECT_EQ(u64, Pointer("/foo/uint64").GetWithDefault(d, u64 - 1).GetUint64()); + + EXPECT_TRUE(Pointer("/foo/true").GetWithDefault(d, true).IsTrue()); + EXPECT_TRUE(Pointer("/foo/true").GetWithDefault(d, false).IsTrue()); + + EXPECT_TRUE(Pointer("/foo/false").GetWithDefault(d, false).IsFalse()); + EXPECT_TRUE(Pointer("/foo/false").GetWithDefault(d, true).IsFalse()); + + // StringRef version + EXPECT_STREQ("Hello", Pointer("/foo/hello").GetWithDefault(d, "Hello").GetString()); + + // Copy string version + { + char buffer[256]; + strcpy(buffer, "World"); + EXPECT_STREQ("World", Pointer("/foo/world").GetWithDefault(d, buffer).GetString()); + memset(buffer, 0, sizeof(buffer)); + } + EXPECT_STREQ("World", GetValueByPointer(d, "/foo/world")->GetString()); + +#if RAPIDJSON_HAS_STDSTRING + EXPECT_STREQ("C++", Pointer("/foo/C++").GetWithDefault(d, std::string("C++")).GetString()); +#endif +} + +TEST(Pointer, Set) { + Document d; + d.Parse(kJson); + Document::AllocatorType& a = d.GetAllocator(); + + // Value version + Pointer("/foo/0").Set(d, Value(123).Move(), a); + EXPECT_EQ(123, d["foo"][0].GetInt()); + + Pointer("/foo/-").Set(d, Value(456).Move(), a); + EXPECT_EQ(456, d["foo"][2].GetInt()); + + Pointer("/foo/null").Set(d, Value().Move(), a); + EXPECT_TRUE(GetValueByPointer(d, "/foo/null")->IsNull()); + + // Const Value version + const Value foo(d["foo"], a); + Pointer("/clone").Set(d, foo, a); + EXPECT_EQ(foo, *GetValueByPointer(d, "/clone")); + + // Generic version + Pointer("/foo/int").Set(d, -1, a); + EXPECT_EQ(-1, GetValueByPointer(d, "/foo/int")->GetInt()); + + Pointer("/foo/uint").Set(d, 0x87654321, a); + EXPECT_EQ(0x87654321, GetValueByPointer(d, "/foo/uint")->GetUint()); + + const int64_t i64 = static_cast<int64_t>(RAPIDJSON_UINT64_C2(0x80000000, 0)); + Pointer("/foo/int64").Set(d, i64, a); + EXPECT_EQ(i64, GetValueByPointer(d, "/foo/int64")->GetInt64()); + + const uint64_t u64 = RAPIDJSON_UINT64_C2(0xFFFFFFFFF, 0xFFFFFFFFF); + Pointer("/foo/uint64").Set(d, u64, a); + EXPECT_EQ(u64, GetValueByPointer(d, "/foo/uint64")->GetUint64()); + + Pointer("/foo/true").Set(d, true, a); + EXPECT_TRUE(GetValueByPointer(d, "/foo/true")->IsTrue()); + + Pointer("/foo/false").Set(d, false, a); + EXPECT_TRUE(GetValueByPointer(d, "/foo/false")->IsFalse()); + + // StringRef version + Pointer("/foo/hello").Set(d, "Hello", a); + EXPECT_STREQ("Hello", GetValueByPointer(d, "/foo/hello")->GetString()); + + // Copy string version + { + char buffer[256]; + strcpy(buffer, "World"); + Pointer("/foo/world").Set(d, buffer, a); + memset(buffer, 0, sizeof(buffer)); + } + EXPECT_STREQ("World", GetValueByPointer(d, "/foo/world")->GetString()); + +#if RAPIDJSON_HAS_STDSTRING + Pointer("/foo/c++").Set(d, std::string("C++"), a); + EXPECT_STREQ("C++", GetValueByPointer(d, "/foo/c++")->GetString()); +#endif +} + +TEST(Pointer, Set_NoAllocator) { + Document d; + d.Parse(kJson); + + // Value version + Pointer("/foo/0").Set(d, Value(123).Move()); + EXPECT_EQ(123, d["foo"][0].GetInt()); + + Pointer("/foo/-").Set(d, Value(456).Move()); + EXPECT_EQ(456, d["foo"][2].GetInt()); + + Pointer("/foo/null").Set(d, Value().Move()); + EXPECT_TRUE(GetValueByPointer(d, "/foo/null")->IsNull()); + + // Const Value version + const Value foo(d["foo"], d.GetAllocator()); + Pointer("/clone").Set(d, foo); + EXPECT_EQ(foo, *GetValueByPointer(d, "/clone")); + + // Generic version + Pointer("/foo/int").Set(d, -1); + EXPECT_EQ(-1, GetValueByPointer(d, "/foo/int")->GetInt()); + + Pointer("/foo/uint").Set(d, 0x87654321); + EXPECT_EQ(0x87654321, GetValueByPointer(d, "/foo/uint")->GetUint()); + + const int64_t i64 = static_cast<int64_t>(RAPIDJSON_UINT64_C2(0x80000000, 0)); + Pointer("/foo/int64").Set(d, i64); + EXPECT_EQ(i64, GetValueByPointer(d, "/foo/int64")->GetInt64()); + + const uint64_t u64 = RAPIDJSON_UINT64_C2(0xFFFFFFFFF, 0xFFFFFFFFF); + Pointer("/foo/uint64").Set(d, u64); + EXPECT_EQ(u64, GetValueByPointer(d, "/foo/uint64")->GetUint64()); + + Pointer("/foo/true").Set(d, true); + EXPECT_TRUE(GetValueByPointer(d, "/foo/true")->IsTrue()); + + Pointer("/foo/false").Set(d, false); + EXPECT_TRUE(GetValueByPointer(d, "/foo/false")->IsFalse()); + + // StringRef version + Pointer("/foo/hello").Set(d, "Hello"); + EXPECT_STREQ("Hello", GetValueByPointer(d, "/foo/hello")->GetString()); + + // Copy string version + { + char buffer[256]; + strcpy(buffer, "World"); + Pointer("/foo/world").Set(d, buffer); + memset(buffer, 0, sizeof(buffer)); + } + EXPECT_STREQ("World", GetValueByPointer(d, "/foo/world")->GetString()); + +#if RAPIDJSON_HAS_STDSTRING + Pointer("/foo/c++").Set(d, std::string("C++")); + EXPECT_STREQ("C++", GetValueByPointer(d, "/foo/c++")->GetString()); +#endif +} + +TEST(Pointer, Swap) { + Document d; + d.Parse(kJson); + Document::AllocatorType& a = d.GetAllocator(); + Pointer("/foo/0").Swap(d, *Pointer("/foo/1").Get(d), a); + EXPECT_STREQ("baz", d["foo"][0].GetString()); + EXPECT_STREQ("bar", d["foo"][1].GetString()); +} + +TEST(Pointer, Swap_NoAllocator) { + Document d; + d.Parse(kJson); + Pointer("/foo/0").Swap(d, *Pointer("/foo/1").Get(d)); + EXPECT_STREQ("baz", d["foo"][0].GetString()); + EXPECT_STREQ("bar", d["foo"][1].GetString()); +} + +TEST(Pointer, Erase) { + Document d; + d.Parse(kJson); + + EXPECT_FALSE(Pointer("").Erase(d)); + EXPECT_FALSE(Pointer("/nonexist").Erase(d)); + EXPECT_FALSE(Pointer("/nonexist/nonexist").Erase(d)); + EXPECT_FALSE(Pointer("/foo/nonexist").Erase(d)); + EXPECT_FALSE(Pointer("/foo/nonexist/nonexist").Erase(d)); + EXPECT_FALSE(Pointer("/foo/0/nonexist").Erase(d)); + EXPECT_FALSE(Pointer("/foo/0/nonexist/nonexist").Erase(d)); + EXPECT_FALSE(Pointer("/foo/2/nonexist").Erase(d)); + EXPECT_TRUE(Pointer("/foo/0").Erase(d)); + EXPECT_EQ(1u, d["foo"].Size()); + EXPECT_STREQ("baz", d["foo"][0].GetString()); + EXPECT_TRUE(Pointer("/foo/0").Erase(d)); + EXPECT_TRUE(d["foo"].Empty()); + EXPECT_TRUE(Pointer("/foo").Erase(d)); + EXPECT_TRUE(Pointer("/foo").Get(d) == 0); + + Pointer("/a/0/b/0").Create(d); + + EXPECT_TRUE(Pointer("/a/0/b/0").Get(d) != 0); + EXPECT_TRUE(Pointer("/a/0/b/0").Erase(d)); + EXPECT_TRUE(Pointer("/a/0/b/0").Get(d) == 0); + + EXPECT_TRUE(Pointer("/a/0/b").Get(d) != 0); + EXPECT_TRUE(Pointer("/a/0/b").Erase(d)); + EXPECT_TRUE(Pointer("/a/0/b").Get(d) == 0); + + EXPECT_TRUE(Pointer("/a/0").Get(d) != 0); + EXPECT_TRUE(Pointer("/a/0").Erase(d)); + EXPECT_TRUE(Pointer("/a/0").Get(d) == 0); + + EXPECT_TRUE(Pointer("/a").Get(d) != 0); + EXPECT_TRUE(Pointer("/a").Erase(d)); + EXPECT_TRUE(Pointer("/a").Get(d) == 0); +} + +TEST(Pointer, CreateValueByPointer) { + Document d; + Document::AllocatorType& a = d.GetAllocator(); + + { + Value& v = CreateValueByPointer(d, Pointer("/foo/0"), a); + EXPECT_EQ(&d["foo"][0], &v); + } + { + Value& v = CreateValueByPointer(d, "/foo/1", a); + EXPECT_EQ(&d["foo"][1], &v); + } +} + +TEST(Pointer, CreateValueByPointer_NoAllocator) { + Document d; + + { + Value& v = CreateValueByPointer(d, Pointer("/foo/0")); + EXPECT_EQ(&d["foo"][0], &v); + } + { + Value& v = CreateValueByPointer(d, "/foo/1"); + EXPECT_EQ(&d["foo"][1], &v); + } +} + +TEST(Pointer, GetValueByPointer) { + Document d; + d.Parse(kJson); + + EXPECT_EQ(&d["foo"][0], GetValueByPointer(d, Pointer("/foo/0"))); + EXPECT_EQ(&d["foo"][0], GetValueByPointer(d, "/foo/0")); + + size_t unresolvedTokenIndex; + EXPECT_TRUE(GetValueByPointer(d, "/foo/2", &unresolvedTokenIndex) == 0); // Out of boundary + EXPECT_EQ(1, unresolvedTokenIndex); + EXPECT_TRUE(GetValueByPointer(d, "/foo/a", &unresolvedTokenIndex) == 0); // "/foo" is an array, cannot query by "a" + EXPECT_EQ(1, unresolvedTokenIndex); + EXPECT_TRUE(GetValueByPointer(d, "/foo/0/0", &unresolvedTokenIndex) == 0); // "/foo/0" is an string, cannot further query + EXPECT_EQ(2, unresolvedTokenIndex); + EXPECT_TRUE(GetValueByPointer(d, "/foo/0/a", &unresolvedTokenIndex) == 0); // "/foo/0" is an string, cannot further query + EXPECT_EQ(2, unresolvedTokenIndex); + + // const version + const Value& v = d; + EXPECT_EQ(&d["foo"][0], GetValueByPointer(v, Pointer("/foo/0"))); + EXPECT_EQ(&d["foo"][0], GetValueByPointer(v, "/foo/0")); + + EXPECT_TRUE(GetValueByPointer(v, "/foo/2", &unresolvedTokenIndex) == 0); // Out of boundary + EXPECT_EQ(1, unresolvedTokenIndex); + EXPECT_TRUE(GetValueByPointer(v, "/foo/a", &unresolvedTokenIndex) == 0); // "/foo" is an array, cannot query by "a" + EXPECT_EQ(1, unresolvedTokenIndex); + EXPECT_TRUE(GetValueByPointer(v, "/foo/0/0", &unresolvedTokenIndex) == 0); // "/foo/0" is an string, cannot further query + EXPECT_EQ(2, unresolvedTokenIndex); + EXPECT_TRUE(GetValueByPointer(v, "/foo/0/a", &unresolvedTokenIndex) == 0); // "/foo/0" is an string, cannot further query + EXPECT_EQ(2, unresolvedTokenIndex); + +} + +TEST(Pointer, GetValueByPointerWithDefault_Pointer) { + Document d; + d.Parse(kJson); + + Document::AllocatorType& a = d.GetAllocator(); + const Value v("qux"); + EXPECT_TRUE(Value("bar") == GetValueByPointerWithDefault(d, Pointer("/foo/0"), v, a)); + EXPECT_TRUE(Value("bar") == GetValueByPointerWithDefault(d, Pointer("/foo/0"), v, a)); + EXPECT_TRUE(Value("baz") == GetValueByPointerWithDefault(d, Pointer("/foo/1"), v, a)); + EXPECT_TRUE(Value("qux") == GetValueByPointerWithDefault(d, Pointer("/foo/2"), v, a)); + EXPECT_TRUE(Value("last") == GetValueByPointerWithDefault(d, Pointer("/foo/-"), Value("last").Move(), a)); + EXPECT_STREQ("last", d["foo"][3].GetString()); + + EXPECT_TRUE(GetValueByPointerWithDefault(d, Pointer("/foo/null"), Value().Move(), a).IsNull()); + EXPECT_TRUE(GetValueByPointerWithDefault(d, Pointer("/foo/null"), "x", a).IsNull()); + + // Generic version + EXPECT_EQ(-1, GetValueByPointerWithDefault(d, Pointer("/foo/int"), -1, a).GetInt()); + EXPECT_EQ(-1, GetValueByPointerWithDefault(d, Pointer("/foo/int"), -2, a).GetInt()); + EXPECT_EQ(0x87654321, GetValueByPointerWithDefault(d, Pointer("/foo/uint"), 0x87654321, a).GetUint()); + EXPECT_EQ(0x87654321, GetValueByPointerWithDefault(d, Pointer("/foo/uint"), 0x12345678, a).GetUint()); + + const int64_t i64 = static_cast<int64_t>(RAPIDJSON_UINT64_C2(0x80000000, 0)); + EXPECT_EQ(i64, GetValueByPointerWithDefault(d, Pointer("/foo/int64"), i64, a).GetInt64()); + EXPECT_EQ(i64, GetValueByPointerWithDefault(d, Pointer("/foo/int64"), i64 + 1, a).GetInt64()); + + const uint64_t u64 = RAPIDJSON_UINT64_C2(0xFFFFFFFFF, 0xFFFFFFFFF); + EXPECT_EQ(u64, GetValueByPointerWithDefault(d, Pointer("/foo/uint64"), u64, a).GetUint64()); + EXPECT_EQ(u64, GetValueByPointerWithDefault(d, Pointer("/foo/uint64"), u64 - 1, a).GetUint64()); + + EXPECT_TRUE(GetValueByPointerWithDefault(d, Pointer("/foo/true"), true, a).IsTrue()); + EXPECT_TRUE(GetValueByPointerWithDefault(d, Pointer("/foo/true"), false, a).IsTrue()); + + EXPECT_TRUE(GetValueByPointerWithDefault(d, Pointer("/foo/false"), false, a).IsFalse()); + EXPECT_TRUE(GetValueByPointerWithDefault(d, Pointer("/foo/false"), true, a).IsFalse()); + + // StringRef version + EXPECT_STREQ("Hello", GetValueByPointerWithDefault(d, Pointer("/foo/hello"), "Hello", a).GetString()); + + // Copy string version + { + char buffer[256]; + strcpy(buffer, "World"); + EXPECT_STREQ("World", GetValueByPointerWithDefault(d, Pointer("/foo/world"), buffer, a).GetString()); + memset(buffer, 0, sizeof(buffer)); + } + EXPECT_STREQ("World", GetValueByPointer(d, Pointer("/foo/world"))->GetString()); + +#if RAPIDJSON_HAS_STDSTRING + EXPECT_STREQ("C++", GetValueByPointerWithDefault(d, Pointer("/foo/C++"), std::string("C++"), a).GetString()); +#endif +} + +TEST(Pointer, GetValueByPointerWithDefault_String) { + Document d; + d.Parse(kJson); + + Document::AllocatorType& a = d.GetAllocator(); + const Value v("qux"); + EXPECT_TRUE(Value("bar") == GetValueByPointerWithDefault(d, "/foo/0", v, a)); + EXPECT_TRUE(Value("bar") == GetValueByPointerWithDefault(d, "/foo/0", v, a)); + EXPECT_TRUE(Value("baz") == GetValueByPointerWithDefault(d, "/foo/1", v, a)); + EXPECT_TRUE(Value("qux") == GetValueByPointerWithDefault(d, "/foo/2", v, a)); + EXPECT_TRUE(Value("last") == GetValueByPointerWithDefault(d, "/foo/-", Value("last").Move(), a)); + EXPECT_STREQ("last", d["foo"][3].GetString()); + + EXPECT_TRUE(GetValueByPointerWithDefault(d, "/foo/null", Value().Move(), a).IsNull()); + EXPECT_TRUE(GetValueByPointerWithDefault(d, "/foo/null", "x", a).IsNull()); + + // Generic version + EXPECT_EQ(-1, GetValueByPointerWithDefault(d, "/foo/int", -1, a).GetInt()); + EXPECT_EQ(-1, GetValueByPointerWithDefault(d, "/foo/int", -2, a).GetInt()); + EXPECT_EQ(0x87654321, GetValueByPointerWithDefault(d, "/foo/uint", 0x87654321, a).GetUint()); + EXPECT_EQ(0x87654321, GetValueByPointerWithDefault(d, "/foo/uint", 0x12345678, a).GetUint()); + + const int64_t i64 = static_cast<int64_t>(RAPIDJSON_UINT64_C2(0x80000000, 0)); + EXPECT_EQ(i64, GetValueByPointerWithDefault(d, "/foo/int64", i64, a).GetInt64()); + EXPECT_EQ(i64, GetValueByPointerWithDefault(d, "/foo/int64", i64 + 1, a).GetInt64()); + + const uint64_t u64 = RAPIDJSON_UINT64_C2(0xFFFFFFFFF, 0xFFFFFFFFF); + EXPECT_EQ(u64, GetValueByPointerWithDefault(d, "/foo/uint64", u64, a).GetUint64()); + EXPECT_EQ(u64, GetValueByPointerWithDefault(d, "/foo/uint64", u64 - 1, a).GetUint64()); + + EXPECT_TRUE(GetValueByPointerWithDefault(d, "/foo/true", true, a).IsTrue()); + EXPECT_TRUE(GetValueByPointerWithDefault(d, "/foo/true", false, a).IsTrue()); + + EXPECT_TRUE(GetValueByPointerWithDefault(d, "/foo/false", false, a).IsFalse()); + EXPECT_TRUE(GetValueByPointerWithDefault(d, "/foo/false", true, a).IsFalse()); + + // StringRef version + EXPECT_STREQ("Hello", GetValueByPointerWithDefault(d, "/foo/hello", "Hello", a).GetString()); + + // Copy string version + { + char buffer[256]; + strcpy(buffer, "World"); + EXPECT_STREQ("World", GetValueByPointerWithDefault(d, "/foo/world", buffer, a).GetString()); + memset(buffer, 0, sizeof(buffer)); + } + EXPECT_STREQ("World", GetValueByPointer(d, "/foo/world")->GetString()); + +#if RAPIDJSON_HAS_STDSTRING + EXPECT_STREQ("C++", GetValueByPointerWithDefault(d, "/foo/C++", std::string("C++"), a).GetString()); +#endif +} + +TEST(Pointer, GetValueByPointerWithDefault_Pointer_NoAllocator) { + Document d; + d.Parse(kJson); + + const Value v("qux"); + EXPECT_TRUE(Value("bar") == GetValueByPointerWithDefault(d, Pointer("/foo/0"), v)); + EXPECT_TRUE(Value("bar") == GetValueByPointerWithDefault(d, Pointer("/foo/0"), v)); + EXPECT_TRUE(Value("baz") == GetValueByPointerWithDefault(d, Pointer("/foo/1"), v)); + EXPECT_TRUE(Value("qux") == GetValueByPointerWithDefault(d, Pointer("/foo/2"), v)); + EXPECT_TRUE(Value("last") == GetValueByPointerWithDefault(d, Pointer("/foo/-"), Value("last").Move())); + EXPECT_STREQ("last", d["foo"][3].GetString()); + + EXPECT_TRUE(GetValueByPointerWithDefault(d, Pointer("/foo/null"), Value().Move()).IsNull()); + EXPECT_TRUE(GetValueByPointerWithDefault(d, Pointer("/foo/null"), "x").IsNull()); + + // Generic version + EXPECT_EQ(-1, GetValueByPointerWithDefault(d, Pointer("/foo/int"), -1).GetInt()); + EXPECT_EQ(-1, GetValueByPointerWithDefault(d, Pointer("/foo/int"), -2).GetInt()); + EXPECT_EQ(0x87654321, GetValueByPointerWithDefault(d, Pointer("/foo/uint"), 0x87654321).GetUint()); + EXPECT_EQ(0x87654321, GetValueByPointerWithDefault(d, Pointer("/foo/uint"), 0x12345678).GetUint()); + + const int64_t i64 = static_cast<int64_t>(RAPIDJSON_UINT64_C2(0x80000000, 0)); + EXPECT_EQ(i64, GetValueByPointerWithDefault(d, Pointer("/foo/int64"), i64).GetInt64()); + EXPECT_EQ(i64, GetValueByPointerWithDefault(d, Pointer("/foo/int64"), i64 + 1).GetInt64()); + + const uint64_t u64 = RAPIDJSON_UINT64_C2(0xFFFFFFFFF, 0xFFFFFFFFF); + EXPECT_EQ(u64, GetValueByPointerWithDefault(d, Pointer("/foo/uint64"), u64).GetUint64()); + EXPECT_EQ(u64, GetValueByPointerWithDefault(d, Pointer("/foo/uint64"), u64 - 1).GetUint64()); + + EXPECT_TRUE(GetValueByPointerWithDefault(d, Pointer("/foo/true"), true).IsTrue()); + EXPECT_TRUE(GetValueByPointerWithDefault(d, Pointer("/foo/true"), false).IsTrue()); + + EXPECT_TRUE(GetValueByPointerWithDefault(d, Pointer("/foo/false"), false).IsFalse()); + EXPECT_TRUE(GetValueByPointerWithDefault(d, Pointer("/foo/false"), true).IsFalse()); + + // StringRef version + EXPECT_STREQ("Hello", GetValueByPointerWithDefault(d, Pointer("/foo/hello"), "Hello").GetString()); + + // Copy string version + { + char buffer[256]; + strcpy(buffer, "World"); + EXPECT_STREQ("World", GetValueByPointerWithDefault(d, Pointer("/foo/world"), buffer).GetString()); + memset(buffer, 0, sizeof(buffer)); + } + EXPECT_STREQ("World", GetValueByPointer(d, Pointer("/foo/world"))->GetString()); + +#if RAPIDJSON_HAS_STDSTRING + EXPECT_STREQ("C++", GetValueByPointerWithDefault(d, Pointer("/foo/C++"), std::string("C++")).GetString()); +#endif +} + +TEST(Pointer, GetValueByPointerWithDefault_String_NoAllocator) { + Document d; + d.Parse(kJson); + + const Value v("qux"); + EXPECT_TRUE(Value("bar") == GetValueByPointerWithDefault(d, "/foo/0", v)); + EXPECT_TRUE(Value("bar") == GetValueByPointerWithDefault(d, "/foo/0", v)); + EXPECT_TRUE(Value("baz") == GetValueByPointerWithDefault(d, "/foo/1", v)); + EXPECT_TRUE(Value("qux") == GetValueByPointerWithDefault(d, "/foo/2", v)); + EXPECT_TRUE(Value("last") == GetValueByPointerWithDefault(d, "/foo/-", Value("last").Move())); + EXPECT_STREQ("last", d["foo"][3].GetString()); + + EXPECT_TRUE(GetValueByPointerWithDefault(d, "/foo/null", Value().Move()).IsNull()); + EXPECT_TRUE(GetValueByPointerWithDefault(d, "/foo/null", "x").IsNull()); + + // Generic version + EXPECT_EQ(-1, GetValueByPointerWithDefault(d, "/foo/int", -1).GetInt()); + EXPECT_EQ(-1, GetValueByPointerWithDefault(d, "/foo/int", -2).GetInt()); + EXPECT_EQ(0x87654321, GetValueByPointerWithDefault(d, "/foo/uint", 0x87654321).GetUint()); + EXPECT_EQ(0x87654321, GetValueByPointerWithDefault(d, "/foo/uint", 0x12345678).GetUint()); + + const int64_t i64 = static_cast<int64_t>(RAPIDJSON_UINT64_C2(0x80000000, 0)); + EXPECT_EQ(i64, GetValueByPointerWithDefault(d, "/foo/int64", i64).GetInt64()); + EXPECT_EQ(i64, GetValueByPointerWithDefault(d, "/foo/int64", i64 + 1).GetInt64()); + + const uint64_t u64 = RAPIDJSON_UINT64_C2(0xFFFFFFFFF, 0xFFFFFFFFF); + EXPECT_EQ(u64, GetValueByPointerWithDefault(d, "/foo/uint64", u64).GetUint64()); + EXPECT_EQ(u64, GetValueByPointerWithDefault(d, "/foo/uint64", u64 - 1).GetUint64()); + + EXPECT_TRUE(GetValueByPointerWithDefault(d, "/foo/true", true).IsTrue()); + EXPECT_TRUE(GetValueByPointerWithDefault(d, "/foo/true", false).IsTrue()); + + EXPECT_TRUE(GetValueByPointerWithDefault(d, "/foo/false", false).IsFalse()); + EXPECT_TRUE(GetValueByPointerWithDefault(d, "/foo/false", true).IsFalse()); + + // StringRef version + EXPECT_STREQ("Hello", GetValueByPointerWithDefault(d, "/foo/hello", "Hello").GetString()); + + // Copy string version + { + char buffer[256]; + strcpy(buffer, "World"); + EXPECT_STREQ("World", GetValueByPointerWithDefault(d, "/foo/world", buffer).GetString()); + memset(buffer, 0, sizeof(buffer)); + } + EXPECT_STREQ("World", GetValueByPointer(d, "/foo/world")->GetString()); + +#if RAPIDJSON_HAS_STDSTRING + EXPECT_STREQ("C++", GetValueByPointerWithDefault(d, Pointer("/foo/C++"), std::string("C++")).GetString()); +#endif +} + +TEST(Pointer, SetValueByPointer_Pointer) { + Document d; + d.Parse(kJson); + Document::AllocatorType& a = d.GetAllocator(); + + // Value version + SetValueByPointer(d, Pointer("/foo/0"), Value(123).Move(), a); + EXPECT_EQ(123, d["foo"][0].GetInt()); + + SetValueByPointer(d, Pointer("/foo/null"), Value().Move(), a); + EXPECT_TRUE(GetValueByPointer(d, "/foo/null")->IsNull()); + + // Const Value version + const Value foo(d["foo"], d.GetAllocator()); + SetValueByPointer(d, Pointer("/clone"), foo, a); + EXPECT_EQ(foo, *GetValueByPointer(d, "/clone")); + + // Generic version + SetValueByPointer(d, Pointer("/foo/int"), -1, a); + EXPECT_EQ(-1, GetValueByPointer(d, "/foo/int")->GetInt()); + + SetValueByPointer(d, Pointer("/foo/uint"), 0x87654321, a); + EXPECT_EQ(0x87654321, GetValueByPointer(d, "/foo/uint")->GetUint()); + + const int64_t i64 = static_cast<int64_t>(RAPIDJSON_UINT64_C2(0x80000000, 0)); + SetValueByPointer(d, Pointer("/foo/int64"), i64, a); + EXPECT_EQ(i64, GetValueByPointer(d, "/foo/int64")->GetInt64()); + + const uint64_t u64 = RAPIDJSON_UINT64_C2(0xFFFFFFFFF, 0xFFFFFFFFF); + SetValueByPointer(d, Pointer("/foo/uint64"), u64, a); + EXPECT_EQ(u64, GetValueByPointer(d, "/foo/uint64")->GetUint64()); + + SetValueByPointer(d, Pointer("/foo/true"), true, a); + EXPECT_TRUE(GetValueByPointer(d, "/foo/true")->IsTrue()); + + SetValueByPointer(d, Pointer("/foo/false"), false, a); + EXPECT_TRUE(GetValueByPointer(d, "/foo/false")->IsFalse()); + + // StringRef version + SetValueByPointer(d, Pointer("/foo/hello"), "Hello", a); + EXPECT_STREQ("Hello", GetValueByPointer(d, "/foo/hello")->GetString()); + + // Copy string version + { + char buffer[256]; + strcpy(buffer, "World"); + SetValueByPointer(d, Pointer("/foo/world"), buffer, a); + memset(buffer, 0, sizeof(buffer)); + } + EXPECT_STREQ("World", GetValueByPointer(d, "/foo/world")->GetString()); + +#if RAPIDJSON_HAS_STDSTRING + SetValueByPointer(d, Pointer("/foo/c++"), std::string("C++"), a); + EXPECT_STREQ("C++", GetValueByPointer(d, "/foo/c++")->GetString()); +#endif +} + +TEST(Pointer, SetValueByPointer_String) { + Document d; + d.Parse(kJson); + Document::AllocatorType& a = d.GetAllocator(); + + // Value version + SetValueByPointer(d, "/foo/0", Value(123).Move(), a); + EXPECT_EQ(123, d["foo"][0].GetInt()); + + SetValueByPointer(d, "/foo/null", Value().Move(), a); + EXPECT_TRUE(GetValueByPointer(d, "/foo/null")->IsNull()); + + // Const Value version + const Value foo(d["foo"], d.GetAllocator()); + SetValueByPointer(d, "/clone", foo, a); + EXPECT_EQ(foo, *GetValueByPointer(d, "/clone")); + + // Generic version + SetValueByPointer(d, "/foo/int", -1, a); + EXPECT_EQ(-1, GetValueByPointer(d, "/foo/int")->GetInt()); + + SetValueByPointer(d, "/foo/uint", 0x87654321, a); + EXPECT_EQ(0x87654321, GetValueByPointer(d, "/foo/uint")->GetUint()); + + const int64_t i64 = static_cast<int64_t>(RAPIDJSON_UINT64_C2(0x80000000, 0)); + SetValueByPointer(d, "/foo/int64", i64, a); + EXPECT_EQ(i64, GetValueByPointer(d, "/foo/int64")->GetInt64()); + + const uint64_t u64 = RAPIDJSON_UINT64_C2(0xFFFFFFFFF, 0xFFFFFFFFF); + SetValueByPointer(d, "/foo/uint64", u64, a); + EXPECT_EQ(u64, GetValueByPointer(d, "/foo/uint64")->GetUint64()); + + SetValueByPointer(d, "/foo/true", true, a); + EXPECT_TRUE(GetValueByPointer(d, "/foo/true")->IsTrue()); + + SetValueByPointer(d, "/foo/false", false, a); + EXPECT_TRUE(GetValueByPointer(d, "/foo/false")->IsFalse()); + + // StringRef version + SetValueByPointer(d, "/foo/hello", "Hello", a); + EXPECT_STREQ("Hello", GetValueByPointer(d, "/foo/hello")->GetString()); + + // Copy string version + { + char buffer[256]; + strcpy(buffer, "World"); + SetValueByPointer(d, "/foo/world", buffer, a); + memset(buffer, 0, sizeof(buffer)); + } + EXPECT_STREQ("World", GetValueByPointer(d, "/foo/world")->GetString()); + +#if RAPIDJSON_HAS_STDSTRING + SetValueByPointer(d, "/foo/c++", std::string("C++"), a); + EXPECT_STREQ("C++", GetValueByPointer(d, "/foo/c++")->GetString()); +#endif +} + +TEST(Pointer, SetValueByPointer_Pointer_NoAllocator) { + Document d; + d.Parse(kJson); + + // Value version + SetValueByPointer(d, Pointer("/foo/0"), Value(123).Move()); + EXPECT_EQ(123, d["foo"][0].GetInt()); + + SetValueByPointer(d, Pointer("/foo/null"), Value().Move()); + EXPECT_TRUE(GetValueByPointer(d, "/foo/null")->IsNull()); + + // Const Value version + const Value foo(d["foo"], d.GetAllocator()); + SetValueByPointer(d, Pointer("/clone"), foo); + EXPECT_EQ(foo, *GetValueByPointer(d, "/clone")); + + // Generic version + SetValueByPointer(d, Pointer("/foo/int"), -1); + EXPECT_EQ(-1, GetValueByPointer(d, "/foo/int")->GetInt()); + + SetValueByPointer(d, Pointer("/foo/uint"), 0x87654321); + EXPECT_EQ(0x87654321, GetValueByPointer(d, "/foo/uint")->GetUint()); + + const int64_t i64 = static_cast<int64_t>(RAPIDJSON_UINT64_C2(0x80000000, 0)); + SetValueByPointer(d, Pointer("/foo/int64"), i64); + EXPECT_EQ(i64, GetValueByPointer(d, "/foo/int64")->GetInt64()); + + const uint64_t u64 = RAPIDJSON_UINT64_C2(0xFFFFFFFFF, 0xFFFFFFFFF); + SetValueByPointer(d, Pointer("/foo/uint64"), u64); + EXPECT_EQ(u64, GetValueByPointer(d, "/foo/uint64")->GetUint64()); + + SetValueByPointer(d, Pointer("/foo/true"), true); + EXPECT_TRUE(GetValueByPointer(d, "/foo/true")->IsTrue()); + + SetValueByPointer(d, Pointer("/foo/false"), false); + EXPECT_TRUE(GetValueByPointer(d, "/foo/false")->IsFalse()); + + // StringRef version + SetValueByPointer(d, Pointer("/foo/hello"), "Hello"); + EXPECT_STREQ("Hello", GetValueByPointer(d, "/foo/hello")->GetString()); + + // Copy string version + { + char buffer[256]; + strcpy(buffer, "World"); + SetValueByPointer(d, Pointer("/foo/world"), buffer); + memset(buffer, 0, sizeof(buffer)); + } + EXPECT_STREQ("World", GetValueByPointer(d, "/foo/world")->GetString()); + +#if RAPIDJSON_HAS_STDSTRING + SetValueByPointer(d, Pointer("/foo/c++"), std::string("C++")); + EXPECT_STREQ("C++", GetValueByPointer(d, "/foo/c++")->GetString()); +#endif +} + +TEST(Pointer, SetValueByPointer_String_NoAllocator) { + Document d; + d.Parse(kJson); + + // Value version + SetValueByPointer(d, "/foo/0", Value(123).Move()); + EXPECT_EQ(123, d["foo"][0].GetInt()); + + SetValueByPointer(d, "/foo/null", Value().Move()); + EXPECT_TRUE(GetValueByPointer(d, "/foo/null")->IsNull()); + + // Const Value version + const Value foo(d["foo"], d.GetAllocator()); + SetValueByPointer(d, "/clone", foo); + EXPECT_EQ(foo, *GetValueByPointer(d, "/clone")); + + // Generic version + SetValueByPointer(d, "/foo/int", -1); + EXPECT_EQ(-1, GetValueByPointer(d, "/foo/int")->GetInt()); + + SetValueByPointer(d, "/foo/uint", 0x87654321); + EXPECT_EQ(0x87654321, GetValueByPointer(d, "/foo/uint")->GetUint()); + + const int64_t i64 = static_cast<int64_t>(RAPIDJSON_UINT64_C2(0x80000000, 0)); + SetValueByPointer(d, "/foo/int64", i64); + EXPECT_EQ(i64, GetValueByPointer(d, "/foo/int64")->GetInt64()); + + const uint64_t u64 = RAPIDJSON_UINT64_C2(0xFFFFFFFFF, 0xFFFFFFFFF); + SetValueByPointer(d, "/foo/uint64", u64); + EXPECT_EQ(u64, GetValueByPointer(d, "/foo/uint64")->GetUint64()); + + SetValueByPointer(d, "/foo/true", true); + EXPECT_TRUE(GetValueByPointer(d, "/foo/true")->IsTrue()); + + SetValueByPointer(d, "/foo/false", false); + EXPECT_TRUE(GetValueByPointer(d, "/foo/false")->IsFalse()); + + // StringRef version + SetValueByPointer(d, "/foo/hello", "Hello"); + EXPECT_STREQ("Hello", GetValueByPointer(d, "/foo/hello")->GetString()); + + // Copy string version + { + char buffer[256]; + strcpy(buffer, "World"); + SetValueByPointer(d, "/foo/world", buffer); + memset(buffer, 0, sizeof(buffer)); + } + EXPECT_STREQ("World", GetValueByPointer(d, "/foo/world")->GetString()); + +#if RAPIDJSON_HAS_STDSTRING + SetValueByPointer(d, "/foo/c++", std::string("C++")); + EXPECT_STREQ("C++", GetValueByPointer(d, "/foo/c++")->GetString()); +#endif +} + +TEST(Pointer, SwapValueByPointer) { + Document d; + d.Parse(kJson); + Document::AllocatorType& a = d.GetAllocator(); + SwapValueByPointer(d, Pointer("/foo/0"), *GetValueByPointer(d, "/foo/1"), a); + EXPECT_STREQ("baz", d["foo"][0].GetString()); + EXPECT_STREQ("bar", d["foo"][1].GetString()); + + SwapValueByPointer(d, "/foo/0", *GetValueByPointer(d, "/foo/1"), a); + EXPECT_STREQ("bar", d["foo"][0].GetString()); + EXPECT_STREQ("baz", d["foo"][1].GetString()); +} + +TEST(Pointer, SwapValueByPointer_NoAllocator) { + Document d; + d.Parse(kJson); + SwapValueByPointer(d, Pointer("/foo/0"), *GetValueByPointer(d, "/foo/1")); + EXPECT_STREQ("baz", d["foo"][0].GetString()); + EXPECT_STREQ("bar", d["foo"][1].GetString()); + + SwapValueByPointer(d, "/foo/0", *GetValueByPointer(d, "/foo/1")); + EXPECT_STREQ("bar", d["foo"][0].GetString()); + EXPECT_STREQ("baz", d["foo"][1].GetString()); +} + +TEST(Pointer, EraseValueByPointer_Pointer) { + Document d; + d.Parse(kJson); + + EXPECT_FALSE(EraseValueByPointer(d, Pointer(""))); + EXPECT_FALSE(Pointer("/foo/nonexist").Erase(d)); + EXPECT_TRUE(EraseValueByPointer(d, Pointer("/foo/0"))); + EXPECT_EQ(1u, d["foo"].Size()); + EXPECT_STREQ("baz", d["foo"][0].GetString()); + EXPECT_TRUE(EraseValueByPointer(d, Pointer("/foo/0"))); + EXPECT_TRUE(d["foo"].Empty()); + EXPECT_TRUE(EraseValueByPointer(d, Pointer("/foo"))); + EXPECT_TRUE(Pointer("/foo").Get(d) == 0); +} + +TEST(Pointer, EraseValueByPointer_String) { + Document d; + d.Parse(kJson); + + EXPECT_FALSE(EraseValueByPointer(d, "")); + EXPECT_FALSE(Pointer("/foo/nonexist").Erase(d)); + EXPECT_TRUE(EraseValueByPointer(d, "/foo/0")); + EXPECT_EQ(1u, d["foo"].Size()); + EXPECT_STREQ("baz", d["foo"][0].GetString()); + EXPECT_TRUE(EraseValueByPointer(d, "/foo/0")); + EXPECT_TRUE(d["foo"].Empty()); + EXPECT_TRUE(EraseValueByPointer(d, "/foo")); + EXPECT_TRUE(Pointer("/foo").Get(d) == 0); +} + +TEST(Pointer, Ambiguity) { + { + Document d; + d.Parse("{\"0\" : [123]}"); + EXPECT_EQ(123, Pointer("/0/0").Get(d)->GetInt()); + Pointer("/0/a").Set(d, 456); // Change array [123] to object {456} + EXPECT_EQ(456, Pointer("/0/a").Get(d)->GetInt()); + } + + { + Document d; + EXPECT_FALSE(d.Parse("[{\"0\": 123}]").HasParseError()); + EXPECT_EQ(123, Pointer("/0/0").Get(d)->GetInt()); + Pointer("/0/1").Set(d, 456); // 1 is treated as "1" to index object + EXPECT_EQ(123, Pointer("/0/0").Get(d)->GetInt()); + EXPECT_EQ(456, Pointer("/0/1").Get(d)->GetInt()); + } +} + +// https://github.com/miloyip/rapidjson/issues/483 +namespace myjson { + +class MyAllocator +{ +public: + static const bool kNeedFree = true; + void * Malloc(size_t _size) { return malloc(_size); } + void * Realloc(void *_org_p, size_t _org_size, size_t _new_size) { (void)_org_size; return realloc(_org_p, _new_size); } + static void Free(void *_p) { return free(_p); } +}; + +typedef rapidjson::GenericDocument< + rapidjson::UTF8<>, + rapidjson::MemoryPoolAllocator< MyAllocator >, + MyAllocator + > Document; + +typedef rapidjson::GenericPointer< + ::myjson::Document::ValueType, + MyAllocator + > Pointer; + +typedef ::myjson::Document::ValueType Value; + +} + +TEST(Pointer, Issue483) { + std::string mystr, path; + myjson::Document document; + myjson::Value value(rapidjson::kStringType); + value.SetString(mystr.c_str(), static_cast<SizeType>(mystr.length()), document.GetAllocator()); + myjson::Pointer(path.c_str()).Set(document, value, document.GetAllocator()); +} diff --git a/src/rapidjson/test/unittest/prettywritertest.cpp b/src/rapidjson/test/unittest/prettywritertest.cpp new file mode 100644 index 000000000..a372f7986 --- /dev/null +++ b/src/rapidjson/test/unittest/prettywritertest.cpp @@ -0,0 +1,203 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#include "unittest.h" +#include "rapidjson/reader.h" +#include "rapidjson/prettywriter.h" +#include "rapidjson/stringbuffer.h" +#include "rapidjson/filewritestream.h" + +using namespace rapidjson; + +static const char kJson[] = "{\"hello\":\"world\",\"t\":true,\"f\":false,\"n\":null,\"i\":123,\"pi\":3.1416,\"a\":[1,2,3,-1],\"u64\":1234567890123456789,\"i64\":-1234567890123456789}"; +static const char kPrettyJson[] = +"{\n" +" \"hello\": \"world\",\n" +" \"t\": true,\n" +" \"f\": false,\n" +" \"n\": null,\n" +" \"i\": 123,\n" +" \"pi\": 3.1416,\n" +" \"a\": [\n" +" 1,\n" +" 2,\n" +" 3,\n" +" -1\n" +" ],\n" +" \"u64\": 1234567890123456789,\n" +" \"i64\": -1234567890123456789\n" +"}"; + +static const char kPrettyJson_FormatOptions_SLA[] = +"{\n" +" \"hello\": \"world\",\n" +" \"t\": true,\n" +" \"f\": false,\n" +" \"n\": null,\n" +" \"i\": 123,\n" +" \"pi\": 3.1416,\n" +" \"a\": [1, 2, 3, -1],\n" +" \"u64\": 1234567890123456789,\n" +" \"i64\": -1234567890123456789\n" +"}"; + +TEST(PrettyWriter, Basic) { + StringBuffer buffer; + PrettyWriter<StringBuffer> writer(buffer); + Reader reader; + StringStream s(kJson); + reader.Parse(s, writer); + EXPECT_STREQ(kPrettyJson, buffer.GetString()); +} + +TEST(PrettyWriter, FormatOptions) { + StringBuffer buffer; + PrettyWriter<StringBuffer> writer(buffer); + writer.SetFormatOptions(kFormatSingleLineArray); + Reader reader; + StringStream s(kJson); + reader.Parse(s, writer); + EXPECT_STREQ(kPrettyJson_FormatOptions_SLA, buffer.GetString()); +} + +TEST(PrettyWriter, SetIndent) { + StringBuffer buffer; + PrettyWriter<StringBuffer> writer(buffer); + writer.SetIndent('\t', 1); + Reader reader; + StringStream s(kJson); + reader.Parse(s, writer); + EXPECT_STREQ( + "{\n" + "\t\"hello\": \"world\",\n" + "\t\"t\": true,\n" + "\t\"f\": false,\n" + "\t\"n\": null,\n" + "\t\"i\": 123,\n" + "\t\"pi\": 3.1416,\n" + "\t\"a\": [\n" + "\t\t1,\n" + "\t\t2,\n" + "\t\t3,\n" + "\t\t-1\n" + "\t],\n" + "\t\"u64\": 1234567890123456789,\n" + "\t\"i64\": -1234567890123456789\n" + "}", + buffer.GetString()); +} + +TEST(PrettyWriter, String) { + StringBuffer buffer; + PrettyWriter<StringBuffer> writer(buffer); + EXPECT_TRUE(writer.StartArray()); + EXPECT_TRUE(writer.String("Hello\n")); + EXPECT_TRUE(writer.EndArray()); + EXPECT_STREQ("[\n \"Hello\\n\"\n]", buffer.GetString()); +} + +#if RAPIDJSON_HAS_STDSTRING +TEST(PrettyWriter, String_STDSTRING) { + StringBuffer buffer; + PrettyWriter<StringBuffer> writer(buffer); + EXPECT_TRUE(writer.StartArray()); + EXPECT_TRUE(writer.String(std::string("Hello\n"))); + EXPECT_TRUE(writer.EndArray()); + EXPECT_STREQ("[\n \"Hello\\n\"\n]", buffer.GetString()); +} +#endif + +#include <sstream> + +class OStreamWrapper { +public: + typedef char Ch; + + OStreamWrapper(std::ostream& os) : os_(os) {} + + Ch Peek() const { assert(false); return '\0'; } + Ch Take() { assert(false); return '\0'; } + size_t Tell() const { return 0; } + + Ch* PutBegin() { assert(false); return 0; } + void Put(Ch c) { os_.put(c); } + void Flush() { os_.flush(); } + size_t PutEnd(Ch*) { assert(false); return 0; } + +private: + OStreamWrapper(const OStreamWrapper&); + OStreamWrapper& operator=(const OStreamWrapper&); + + std::ostream& os_; +}; + +// For covering PutN() generic version +TEST(PrettyWriter, OStreamWrapper) { + StringStream s(kJson); + + std::stringstream ss; + OStreamWrapper os(ss); + + PrettyWriter<OStreamWrapper> writer(os); + + Reader reader; + reader.Parse(s, writer); + + std::string actual = ss.str(); + EXPECT_STREQ(kPrettyJson, actual.c_str()); +} + +// For covering FileWriteStream::PutN() +TEST(PrettyWriter, FileWriteStream) { + char filename[L_tmpnam]; + FILE* fp = TempFile(filename); + char buffer[16]; + FileWriteStream os(fp, buffer, sizeof(buffer)); + PrettyWriter<FileWriteStream> writer(os); + Reader reader; + StringStream s(kJson); + reader.Parse(s, writer); + fclose(fp); + + fp = fopen(filename, "rb"); + fseek(fp, 0, SEEK_END); + size_t size = static_cast<size_t>(ftell(fp)); + fseek(fp, 0, SEEK_SET); + char* json = static_cast<char*>(malloc(size + 1)); + size_t readLength = fread(json, 1, size, fp); + json[readLength] = '\0'; + fclose(fp); + remove(filename); + EXPECT_STREQ(kPrettyJson, json); + free(json); +} + +TEST(PrettyWriter, RawValue) { + StringBuffer buffer; + PrettyWriter<StringBuffer> writer(buffer); + writer.StartObject(); + writer.Key("a"); + writer.Int(1); + writer.Key("raw"); + const char json[] = "[\"Hello\\nWorld\", 123.456]"; + writer.RawValue(json, strlen(json), kArrayType); + writer.EndObject(); + EXPECT_TRUE(writer.IsComplete()); + EXPECT_STREQ( + "{\n" + " \"a\": 1,\n" + " \"raw\": [\"Hello\\nWorld\", 123.456]\n" // no indentation within raw value + "}", + buffer.GetString()); +} diff --git a/src/rapidjson/test/unittest/readertest.cpp b/src/rapidjson/test/unittest/readertest.cpp new file mode 100644 index 000000000..64a1f9c3c --- /dev/null +++ b/src/rapidjson/test/unittest/readertest.cpp @@ -0,0 +1,1844 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#include "unittest.h" + +#include "rapidjson/reader.h" +#include "rapidjson/internal/dtoa.h" +#include "rapidjson/internal/itoa.h" +#include "rapidjson/memorystream.h" + +#include <limits> + +using namespace rapidjson; + +RAPIDJSON_DIAG_PUSH +#ifdef __GNUC__ +RAPIDJSON_DIAG_OFF(effc++) +RAPIDJSON_DIAG_OFF(float-equal) +RAPIDJSON_DIAG_OFF(missing-noreturn) +#if __GNUC__ >= 7 +RAPIDJSON_DIAG_OFF(dangling-else) +#endif +#endif // __GNUC__ + +#ifdef __clang__ +RAPIDJSON_DIAG_OFF(variadic-macros) +RAPIDJSON_DIAG_OFF(c++98-compat-pedantic) +#endif + +template<bool expect> +struct ParseBoolHandler : BaseReaderHandler<UTF8<>, ParseBoolHandler<expect> > { + ParseBoolHandler() : step_(0) {} + bool Default() { ADD_FAILURE(); return false; } + // gcc 4.8.x generates warning in EXPECT_EQ(bool, bool) on this gtest version. + // Workaround with EXPECT_TRUE(). + bool Bool(bool b) { /*EXPECT_EQ(expect, b); */EXPECT_TRUE(expect == b); ++step_; return true; } + + unsigned step_; +}; + +TEST(Reader, ParseTrue) { + StringStream s("true"); + ParseBoolHandler<true> h; + Reader reader; + reader.Parse(s, h); + EXPECT_EQ(1u, h.step_); +} + +TEST(Reader, ParseFalse) { + StringStream s("false"); + ParseBoolHandler<false> h; + Reader reader; + reader.Parse(s, h); + EXPECT_EQ(1u, h.step_); +} + +struct ParseIntHandler : BaseReaderHandler<UTF8<>, ParseIntHandler> { + ParseIntHandler() : step_(0), actual_() {} + bool Default() { ADD_FAILURE(); return false; } + bool Int(int i) { actual_ = i; step_++; return true; } + + unsigned step_; + int actual_; +}; + +struct ParseUintHandler : BaseReaderHandler<UTF8<>, ParseUintHandler> { + ParseUintHandler() : step_(0), actual_() {} + bool Default() { ADD_FAILURE(); return false; } + bool Uint(unsigned i) { actual_ = i; step_++; return true; } + + unsigned step_; + unsigned actual_; +}; + +struct ParseInt64Handler : BaseReaderHandler<UTF8<>, ParseInt64Handler> { + ParseInt64Handler() : step_(0), actual_() {} + bool Default() { ADD_FAILURE(); return false; } + bool Int64(int64_t i) { actual_ = i; step_++; return true; } + + unsigned step_; + int64_t actual_; +}; + +struct ParseUint64Handler : BaseReaderHandler<UTF8<>, ParseUint64Handler> { + ParseUint64Handler() : step_(0), actual_() {} + bool Default() { ADD_FAILURE(); return false; } + bool Uint64(uint64_t i) { actual_ = i; step_++; return true; } + + unsigned step_; + uint64_t actual_; +}; + +struct ParseDoubleHandler : BaseReaderHandler<UTF8<>, ParseDoubleHandler> { + ParseDoubleHandler() : step_(0), actual_() {} + bool Default() { ADD_FAILURE(); return false; } + bool Double(double d) { actual_ = d; step_++; return true; } + + unsigned step_; + double actual_; +}; + +TEST(Reader, ParseNumber_Integer) { +#define TEST_INTEGER(Handler, str, x) \ + { \ + StringStream s(str); \ + Handler h; \ + Reader reader; \ + reader.Parse(s, h); \ + EXPECT_EQ(1u, h.step_); \ + EXPECT_EQ(x, h.actual_); \ + } + + TEST_INTEGER(ParseUintHandler, "0", 0u); + TEST_INTEGER(ParseUintHandler, "123", 123u); + TEST_INTEGER(ParseUintHandler, "2147483648", 2147483648u); // 2^31 - 1 (cannot be stored in int) + TEST_INTEGER(ParseUintHandler, "4294967295", 4294967295u); + + TEST_INTEGER(ParseIntHandler, "-123", -123); + TEST_INTEGER(ParseIntHandler, "-2147483648", static_cast<int32_t>(0x80000000)); // -2^31 (min of int) + + TEST_INTEGER(ParseUint64Handler, "4294967296", RAPIDJSON_UINT64_C2(1, 0)); // 2^32 (max of unsigned + 1, force to use uint64_t) + TEST_INTEGER(ParseUint64Handler, "18446744073709551615", RAPIDJSON_UINT64_C2(0xFFFFFFFF, 0xFFFFFFFF)); // 2^64 - 1 (max of uint64_t) + + TEST_INTEGER(ParseInt64Handler, "-2147483649", static_cast<int64_t>(RAPIDJSON_UINT64_C2(0xFFFFFFFF, 0x7FFFFFFF))); // -2^31 -1 (min of int - 1, force to use int64_t) + TEST_INTEGER(ParseInt64Handler, "-9223372036854775808", static_cast<int64_t>(RAPIDJSON_UINT64_C2(0x80000000, 0x00000000))); // -2^63 (min of int64_t) + + // Random test for uint32_t/int32_t + { + union { + uint32_t u; + int32_t i; + }u; + Random r; + + for (unsigned i = 0; i < 100000; i++) { + u.u = r(); + + char buffer[32]; + *internal::u32toa(u.u, buffer) = '\0'; + TEST_INTEGER(ParseUintHandler, buffer, u.u); + + if (u.i < 0) { + *internal::i32toa(u.i, buffer) = '\0'; + TEST_INTEGER(ParseIntHandler, buffer, u.i); + } + } + } + + // Random test for uint64_t/int64_t + { + union { + uint64_t u; + int64_t i; + }u; + Random r; + + for (unsigned i = 0; i < 100000; i++) { + u.u = uint64_t(r()) << 32; + u.u |= r(); + + char buffer[32]; + if (u.u > uint64_t(4294967295u)) { + *internal::u64toa(u.u, buffer) = '\0'; + TEST_INTEGER(ParseUint64Handler, buffer, u.u); + } + + if (u.i < -int64_t(2147483648u)) { + *internal::i64toa(u.i, buffer) = '\0'; + TEST_INTEGER(ParseInt64Handler, buffer, u.i); + } + } + } +#undef TEST_INTEGER +} + +template<bool fullPrecision> +static void TestParseDouble() { +#define TEST_DOUBLE(fullPrecision, str, x) \ + { \ + StringStream s(str); \ + ParseDoubleHandler h; \ + Reader reader; \ + ASSERT_EQ(kParseErrorNone, reader.Parse<fullPrecision ? kParseFullPrecisionFlag : 0>(s, h).Code()); \ + EXPECT_EQ(1u, h.step_); \ + internal::Double e(x), a(h.actual_); \ + if (fullPrecision) { \ + EXPECT_EQ(e.Uint64Value(), a.Uint64Value()); \ + if (e.Uint64Value() != a.Uint64Value()) \ + printf(" String: %s\n Actual: %.17g\nExpected: %.17g\n", str, h.actual_, x); \ + } \ + else { \ + EXPECT_EQ(e.Sign(), a.Sign()); /* for 0.0 != -0.0 */ \ + EXPECT_DOUBLE_EQ(x, h.actual_); \ + } \ + } + + TEST_DOUBLE(fullPrecision, "0.0", 0.0); + TEST_DOUBLE(fullPrecision, "-0.0", -0.0); // For checking issue #289 + TEST_DOUBLE(fullPrecision, "1.0", 1.0); + TEST_DOUBLE(fullPrecision, "-1.0", -1.0); + TEST_DOUBLE(fullPrecision, "1.5", 1.5); + TEST_DOUBLE(fullPrecision, "-1.5", -1.5); + TEST_DOUBLE(fullPrecision, "3.1416", 3.1416); + TEST_DOUBLE(fullPrecision, "1E10", 1E10); + TEST_DOUBLE(fullPrecision, "1e10", 1e10); + TEST_DOUBLE(fullPrecision, "1E+10", 1E+10); + TEST_DOUBLE(fullPrecision, "1E-10", 1E-10); + TEST_DOUBLE(fullPrecision, "-1E10", -1E10); + TEST_DOUBLE(fullPrecision, "-1e10", -1e10); + TEST_DOUBLE(fullPrecision, "-1E+10", -1E+10); + TEST_DOUBLE(fullPrecision, "-1E-10", -1E-10); + TEST_DOUBLE(fullPrecision, "1.234E+10", 1.234E+10); + TEST_DOUBLE(fullPrecision, "1.234E-10", 1.234E-10); + TEST_DOUBLE(fullPrecision, "1.79769e+308", 1.79769e+308); + TEST_DOUBLE(fullPrecision, "2.22507e-308", 2.22507e-308); + TEST_DOUBLE(fullPrecision, "-1.79769e+308", -1.79769e+308); + TEST_DOUBLE(fullPrecision, "-2.22507e-308", -2.22507e-308); + TEST_DOUBLE(fullPrecision, "4.9406564584124654e-324", 4.9406564584124654e-324); // minimum denormal + TEST_DOUBLE(fullPrecision, "2.2250738585072009e-308", 2.2250738585072009e-308); // Max subnormal double + TEST_DOUBLE(fullPrecision, "2.2250738585072014e-308", 2.2250738585072014e-308); // Min normal positive double + TEST_DOUBLE(fullPrecision, "1.7976931348623157e+308", 1.7976931348623157e+308); // Max double + TEST_DOUBLE(fullPrecision, "1e-10000", 0.0); // must underflow + TEST_DOUBLE(fullPrecision, "18446744073709551616", 18446744073709551616.0); // 2^64 (max of uint64_t + 1, force to use double) + TEST_DOUBLE(fullPrecision, "-9223372036854775809", -9223372036854775809.0); // -2^63 - 1(min of int64_t + 1, force to use double) + TEST_DOUBLE(fullPrecision, "0.9868011474609375", 0.9868011474609375); // https://github.com/miloyip/rapidjson/issues/120 + TEST_DOUBLE(fullPrecision, "123e34", 123e34); // Fast Path Cases In Disguise + TEST_DOUBLE(fullPrecision, "45913141877270640000.0", 45913141877270640000.0); + TEST_DOUBLE(fullPrecision, "2.2250738585072011e-308", 2.2250738585072011e-308); // http://www.exploringbinary.com/php-hangs-on-numeric-value-2-2250738585072011e-308/ + TEST_DOUBLE(fullPrecision, "1e-00011111111111", 0.0); // Issue #313 + TEST_DOUBLE(fullPrecision, "-1e-00011111111111", -0.0); + TEST_DOUBLE(fullPrecision, "1e-214748363", 0.0); // Maximum supported negative exponent + TEST_DOUBLE(fullPrecision, "1e-214748364", 0.0); + TEST_DOUBLE(fullPrecision, "1e-21474836311", 0.0); + TEST_DOUBLE(fullPrecision, "0.017976931348623157e+310", 1.7976931348623157e+308); // Max double in another form + + // Since + // abs((2^-1022 - 2^-1074) - 2.2250738585072012e-308) = 3.109754131239141401123495768877590405345064751974375599... �� 10^-324 + // abs((2^-1022) - 2.2250738585072012e-308) = 1.830902327173324040642192159804623318305533274168872044... �� 10 ^ -324 + // So 2.2250738585072012e-308 should round to 2^-1022 = 2.2250738585072014e-308 + TEST_DOUBLE(fullPrecision, "2.2250738585072012e-308", 2.2250738585072014e-308); // http://www.exploringbinary.com/java-hangs-when-converting-2-2250738585072012e-308/ + + // More closer to normal/subnormal boundary + // boundary = 2^-1022 - 2^-1075 = 2.225073858507201136057409796709131975934819546351645648... �� 10^-308 + TEST_DOUBLE(fullPrecision, "2.22507385850720113605740979670913197593481954635164564e-308", 2.2250738585072009e-308); + TEST_DOUBLE(fullPrecision, "2.22507385850720113605740979670913197593481954635164565e-308", 2.2250738585072014e-308); + + // 1.0 is in (1.0 - 2^-54, 1.0 + 2^-53) + // 1.0 - 2^-54 = 0.999999999999999944488848768742172978818416595458984375 + TEST_DOUBLE(fullPrecision, "0.999999999999999944488848768742172978818416595458984375", 1.0); // round to even + TEST_DOUBLE(fullPrecision, "0.999999999999999944488848768742172978818416595458984374", 0.99999999999999989); // previous double + TEST_DOUBLE(fullPrecision, "0.999999999999999944488848768742172978818416595458984376", 1.0); // next double + // 1.0 + 2^-53 = 1.00000000000000011102230246251565404236316680908203125 + TEST_DOUBLE(fullPrecision, "1.00000000000000011102230246251565404236316680908203125", 1.0); // round to even + TEST_DOUBLE(fullPrecision, "1.00000000000000011102230246251565404236316680908203124", 1.0); // previous double + TEST_DOUBLE(fullPrecision, "1.00000000000000011102230246251565404236316680908203126", 1.00000000000000022); // next double + + // Numbers from https://github.com/floitsch/double-conversion/blob/master/test/cctest/test-strtod.cc + + TEST_DOUBLE(fullPrecision, "72057594037927928.0", 72057594037927928.0); + TEST_DOUBLE(fullPrecision, "72057594037927936.0", 72057594037927936.0); + TEST_DOUBLE(fullPrecision, "72057594037927932.0", 72057594037927936.0); + TEST_DOUBLE(fullPrecision, "7205759403792793199999e-5", 72057594037927928.0); + TEST_DOUBLE(fullPrecision, "7205759403792793200001e-5", 72057594037927936.0); + + TEST_DOUBLE(fullPrecision, "9223372036854774784.0", 9223372036854774784.0); + TEST_DOUBLE(fullPrecision, "9223372036854775808.0", 9223372036854775808.0); + TEST_DOUBLE(fullPrecision, "9223372036854775296.0", 9223372036854775808.0); + TEST_DOUBLE(fullPrecision, "922337203685477529599999e-5", 9223372036854774784.0); + TEST_DOUBLE(fullPrecision, "922337203685477529600001e-5", 9223372036854775808.0); + + TEST_DOUBLE(fullPrecision, "10141204801825834086073718800384", 10141204801825834086073718800384.0); + TEST_DOUBLE(fullPrecision, "10141204801825835211973625643008", 10141204801825835211973625643008.0); + TEST_DOUBLE(fullPrecision, "10141204801825834649023672221696", 10141204801825835211973625643008.0); + TEST_DOUBLE(fullPrecision, "1014120480182583464902367222169599999e-5", 10141204801825834086073718800384.0); + TEST_DOUBLE(fullPrecision, "1014120480182583464902367222169600001e-5", 10141204801825835211973625643008.0); + + TEST_DOUBLE(fullPrecision, "5708990770823838890407843763683279797179383808", 5708990770823838890407843763683279797179383808.0); + TEST_DOUBLE(fullPrecision, "5708990770823839524233143877797980545530986496", 5708990770823839524233143877797980545530986496.0); + TEST_DOUBLE(fullPrecision, "5708990770823839207320493820740630171355185152", 5708990770823839524233143877797980545530986496.0); + TEST_DOUBLE(fullPrecision, "5708990770823839207320493820740630171355185151999e-3", 5708990770823838890407843763683279797179383808.0); + TEST_DOUBLE(fullPrecision, "5708990770823839207320493820740630171355185152001e-3", 5708990770823839524233143877797980545530986496.0); + + { + char n1e308[310]; // '1' followed by 308 '0' + n1e308[0] = '1'; + for (int i = 1; i < 309; i++) + n1e308[i] = '0'; + n1e308[309] = '\0'; + TEST_DOUBLE(fullPrecision, n1e308, 1E308); + } + + // Cover trimming + TEST_DOUBLE(fullPrecision, +"2.22507385850720113605740979670913197593481954635164564802342610972482222202107694551652952390813508" +"7914149158913039621106870086438694594645527657207407820621743379988141063267329253552286881372149012" +"9811224514518898490572223072852551331557550159143974763979834118019993239625482890171070818506906306" +"6665599493827577257201576306269066333264756530000924588831643303777979186961204949739037782970490505" +"1080609940730262937128958950003583799967207254304360284078895771796150945516748243471030702609144621" +"5722898802581825451803257070188608721131280795122334262883686223215037756666225039825343359745688844" +"2390026549819838548794829220689472168983109969836584681402285424333066033985088644580400103493397042" +"7567186443383770486037861622771738545623065874679014086723327636718751234567890123456789012345678901" +"e-308", + 2.2250738585072014e-308); + + { + static const unsigned count = 100; // Tested with 1000000 locally + Random r; + Reader reader; // Reusing reader to prevent heap allocation + + // Exhaustively test different exponents with random significant + for (uint64_t exp = 0; exp < 2047; exp++) { + ; + for (unsigned i = 0; i < count; i++) { + // Need to call r() in two statements for cross-platform coherent sequence. + uint64_t u = (exp << 52) | uint64_t(r() & 0x000FFFFF) << 32; + u |= uint64_t(r()); + internal::Double d = internal::Double(u); + + char buffer[32]; + *internal::dtoa(d.Value(), buffer) = '\0'; + + StringStream s(buffer); + ParseDoubleHandler h; + ASSERT_EQ(kParseErrorNone, reader.Parse<fullPrecision ? kParseFullPrecisionFlag : 0>(s, h).Code()); + EXPECT_EQ(1u, h.step_); + internal::Double a(h.actual_); + if (fullPrecision) { + EXPECT_EQ(d.Uint64Value(), a.Uint64Value()); + if (d.Uint64Value() != a.Uint64Value()) + printf(" String: %s\n Actual: %.17g\nExpected: %.17g\n", buffer, h.actual_, d.Value()); + } + else { + EXPECT_EQ(d.Sign(), a.Sign()); // for 0.0 != -0.0 + EXPECT_DOUBLE_EQ(d.Value(), h.actual_); + } + } + } + } + + // Issue #340 + TEST_DOUBLE(fullPrecision, "7.450580596923828e-9", 7.450580596923828e-9); + { + internal::Double d(1.0); + for (int i = 0; i < 324; i++) { + char buffer[32]; + *internal::dtoa(d.Value(), buffer) = '\0'; + + StringStream s(buffer); + ParseDoubleHandler h; + Reader reader; + ASSERT_EQ(kParseErrorNone, reader.Parse<fullPrecision ? kParseFullPrecisionFlag : 0>(s, h).Code()); + EXPECT_EQ(1u, h.step_); + internal::Double a(h.actual_); + if (fullPrecision) { + EXPECT_EQ(d.Uint64Value(), a.Uint64Value()); + if (d.Uint64Value() != a.Uint64Value()) + printf(" String: %s\n Actual: %.17g\nExpected: %.17g\n", buffer, h.actual_, d.Value()); + } + else { + EXPECT_EQ(d.Sign(), a.Sign()); // for 0.0 != -0.0 + EXPECT_DOUBLE_EQ(d.Value(), h.actual_); + } + + + d = d.Value() * 0.5; + } + } +#undef TEST_DOUBLE +} + +TEST(Reader, ParseNumber_NormalPrecisionDouble) { + TestParseDouble<false>(); +} + +TEST(Reader, ParseNumber_FullPrecisionDouble) { + TestParseDouble<true>(); +} + +TEST(Reader, ParseNumber_NormalPrecisionError) { + static unsigned count = 1000000; + Random r; + + double ulpSum = 0.0; + double ulpMax = 0.0; + for (unsigned i = 0; i < count; i++) { + internal::Double e, a; + do { + // Need to call r() in two statements for cross-platform coherent sequence. + uint64_t u = uint64_t(r()) << 32; + u |= uint64_t(r()); + e = u; + } while (e.IsNan() || e.IsInf() || !e.IsNormal()); + + char buffer[32]; + *internal::dtoa(e.Value(), buffer) = '\0'; + + StringStream s(buffer); + ParseDoubleHandler h; + Reader reader; + ASSERT_EQ(kParseErrorNone, reader.Parse(s, h).Code()); + EXPECT_EQ(1u, h.step_); + + a = h.actual_; + uint64_t bias1 = e.ToBias(); + uint64_t bias2 = a.ToBias(); + double ulp = static_cast<double>(bias1 >= bias2 ? bias1 - bias2 : bias2 - bias1); + ulpMax = std::max(ulpMax, ulp); + ulpSum += ulp; + } + printf("ULP Average = %g, Max = %g \n", ulpSum / count, ulpMax); +} + +TEST(Reader, ParseNumber_Error) { +#define TEST_NUMBER_ERROR(errorCode, str, errorOffset, streamPos) \ + { \ + char buffer[1001]; \ + sprintf(buffer, "%s", str); \ + InsituStringStream s(buffer); \ + BaseReaderHandler<> h; \ + Reader reader; \ + EXPECT_FALSE(reader.Parse(s, h)); \ + EXPECT_EQ(errorCode, reader.GetParseErrorCode());\ + EXPECT_EQ(errorOffset, reader.GetErrorOffset());\ + EXPECT_EQ(streamPos, s.Tell());\ + } + + // Number too big to be stored in double. + { + char n1e309[311]; // '1' followed by 309 '0' + n1e309[0] = '1'; + for (int i = 1; i < 310; i++) + n1e309[i] = '0'; + n1e309[310] = '\0'; + TEST_NUMBER_ERROR(kParseErrorNumberTooBig, n1e309, 0, 309); + } + TEST_NUMBER_ERROR(kParseErrorNumberTooBig, "1e309", 0, 5); + + // Miss fraction part in number. + TEST_NUMBER_ERROR(kParseErrorNumberMissFraction, "1.", 2, 2); + TEST_NUMBER_ERROR(kParseErrorNumberMissFraction, "1.a", 2, 2); + + // Miss exponent in number. + TEST_NUMBER_ERROR(kParseErrorNumberMissExponent, "1e", 2, 2); + TEST_NUMBER_ERROR(kParseErrorNumberMissExponent, "1e_", 2, 2); + +#undef TEST_NUMBER_ERROR +} + +template <typename Encoding> +struct ParseStringHandler : BaseReaderHandler<Encoding, ParseStringHandler<Encoding> > { + ParseStringHandler() : str_(0), length_(0), copy_() {} + ~ParseStringHandler() { EXPECT_TRUE(str_ != 0); if (copy_) free(const_cast<typename Encoding::Ch*>(str_)); } + + ParseStringHandler(const ParseStringHandler&); + ParseStringHandler& operator=(const ParseStringHandler&); + + bool Default() { ADD_FAILURE(); return false; } + bool String(const typename Encoding::Ch* str, size_t length, bool copy) { + EXPECT_EQ(0, str_); + if (copy) { + str_ = static_cast<typename Encoding::Ch*>(malloc((length + 1) * sizeof(typename Encoding::Ch))); + memcpy(const_cast<typename Encoding::Ch*>(str_), str, (length + 1) * sizeof(typename Encoding::Ch)); + } + else + str_ = str; + length_ = length; + copy_ = copy; + return true; + } + + const typename Encoding::Ch* str_; + size_t length_; + bool copy_; +}; + +TEST(Reader, ParseString) { +#define TEST_STRING(Encoding, e, x) \ + { \ + Encoding::Ch* buffer = StrDup(x); \ + GenericInsituStringStream<Encoding> is(buffer); \ + ParseStringHandler<Encoding> h; \ + GenericReader<Encoding, Encoding> reader; \ + reader.Parse<kParseInsituFlag | kParseValidateEncodingFlag>(is, h); \ + EXPECT_EQ(0, StrCmp<Encoding::Ch>(e, h.str_)); \ + EXPECT_EQ(StrLen(e), h.length_); \ + free(buffer); \ + GenericStringStream<Encoding> s(x); \ + ParseStringHandler<Encoding> h2; \ + GenericReader<Encoding, Encoding> reader2; \ + reader2.Parse(s, h2); \ + EXPECT_EQ(0, StrCmp<Encoding::Ch>(e, h2.str_)); \ + EXPECT_EQ(StrLen(e), h2.length_); \ + } + + // String constant L"\xXX" can only specify character code in bytes, which is not endianness-neutral. + // And old compiler does not support u"" and U"" string literal. So here specify string literal by array of Ch. + // In addition, GCC 4.8 generates -Wnarrowing warnings when character code >= 128 are assigned to signed integer types. + // Therefore, utype is added for declaring unsigned array, and then cast it to Encoding::Ch. +#define ARRAY(...) { __VA_ARGS__ } +#define TEST_STRINGARRAY(Encoding, utype, array, x) \ + { \ + static const utype ue[] = array; \ + static const Encoding::Ch* e = reinterpret_cast<const Encoding::Ch *>(&ue[0]); \ + TEST_STRING(Encoding, e, x); \ + } + +#define TEST_STRINGARRAY2(Encoding, utype, earray, xarray) \ + { \ + static const utype ue[] = earray; \ + static const utype xe[] = xarray; \ + static const Encoding::Ch* e = reinterpret_cast<const Encoding::Ch *>(&ue[0]); \ + static const Encoding::Ch* x = reinterpret_cast<const Encoding::Ch *>(&xe[0]); \ + TEST_STRING(Encoding, e, x); \ + } + + TEST_STRING(UTF8<>, "", "\"\""); + TEST_STRING(UTF8<>, "Hello", "\"Hello\""); + TEST_STRING(UTF8<>, "Hello\nWorld", "\"Hello\\nWorld\""); + TEST_STRING(UTF8<>, "\"\\/\b\f\n\r\t", "\"\\\"\\\\/\\b\\f\\n\\r\\t\""); + TEST_STRING(UTF8<>, "\x24", "\"\\u0024\""); // Dollar sign U+0024 + TEST_STRING(UTF8<>, "\xC2\xA2", "\"\\u00A2\""); // Cents sign U+00A2 + TEST_STRING(UTF8<>, "\xE2\x82\xAC", "\"\\u20AC\""); // Euro sign U+20AC + TEST_STRING(UTF8<>, "\xF0\x9D\x84\x9E", "\"\\uD834\\uDD1E\""); // G clef sign U+1D11E + + // UTF16 + TEST_STRING(UTF16<>, L"", L"\"\""); + TEST_STRING(UTF16<>, L"Hello", L"\"Hello\""); + TEST_STRING(UTF16<>, L"Hello\nWorld", L"\"Hello\\nWorld\""); + TEST_STRING(UTF16<>, L"\"\\/\b\f\n\r\t", L"\"\\\"\\\\/\\b\\f\\n\\r\\t\""); + TEST_STRINGARRAY(UTF16<>, wchar_t, ARRAY(0x0024, 0x0000), L"\"\\u0024\""); + TEST_STRINGARRAY(UTF16<>, wchar_t, ARRAY(0x00A2, 0x0000), L"\"\\u00A2\""); // Cents sign U+00A2 + TEST_STRINGARRAY(UTF16<>, wchar_t, ARRAY(0x20AC, 0x0000), L"\"\\u20AC\""); // Euro sign U+20AC + TEST_STRINGARRAY(UTF16<>, wchar_t, ARRAY(0xD834, 0xDD1E, 0x0000), L"\"\\uD834\\uDD1E\""); // G clef sign U+1D11E + + // UTF32 + TEST_STRINGARRAY2(UTF32<>, unsigned, ARRAY('\0'), ARRAY('\"', '\"', '\0')); + TEST_STRINGARRAY2(UTF32<>, unsigned, ARRAY('H', 'e', 'l', 'l', 'o', '\0'), ARRAY('\"', 'H', 'e', 'l', 'l', 'o', '\"', '\0')); + TEST_STRINGARRAY2(UTF32<>, unsigned, ARRAY('H', 'e', 'l', 'l', 'o', '\n', 'W', 'o', 'r', 'l', 'd', '\0'), ARRAY('\"', 'H', 'e', 'l', 'l', 'o', '\\', 'n', 'W', 'o', 'r', 'l', 'd', '\"', '\0')); + TEST_STRINGARRAY2(UTF32<>, unsigned, ARRAY('\"', '\\', '/', '\b', '\f', '\n', '\r', '\t', '\0'), ARRAY('\"', '\\', '\"', '\\', '\\', '/', '\\', 'b', '\\', 'f', '\\', 'n', '\\', 'r', '\\', 't', '\"', '\0')); + TEST_STRINGARRAY2(UTF32<>, unsigned, ARRAY(0x00024, 0x0000), ARRAY('\"', '\\', 'u', '0', '0', '2', '4', '\"', '\0')); + TEST_STRINGARRAY2(UTF32<>, unsigned, ARRAY(0x000A2, 0x0000), ARRAY('\"', '\\', 'u', '0', '0', 'A', '2', '\"', '\0')); // Cents sign U+00A2 + TEST_STRINGARRAY2(UTF32<>, unsigned, ARRAY(0x020AC, 0x0000), ARRAY('\"', '\\', 'u', '2', '0', 'A', 'C', '\"', '\0')); // Euro sign U+20AC + TEST_STRINGARRAY2(UTF32<>, unsigned, ARRAY(0x1D11E, 0x0000), ARRAY('\"', '\\', 'u', 'D', '8', '3', '4', '\\', 'u', 'D', 'D', '1', 'E', '\"', '\0')); // G clef sign U+1D11E + +#undef TEST_STRINGARRAY +#undef ARRAY +#undef TEST_STRING + + // Support of null character in string + { + StringStream s("\"Hello\\u0000World\""); + const char e[] = "Hello\0World"; + ParseStringHandler<UTF8<> > h; + Reader reader; + reader.Parse(s, h); + EXPECT_EQ(0, memcmp(e, h.str_, h.length_ + 1)); + EXPECT_EQ(11u, h.length_); + } +} + +TEST(Reader, ParseString_Transcoding) { + const char* x = "\"Hello\""; + const wchar_t* e = L"Hello"; + GenericStringStream<UTF8<> > is(x); + GenericReader<UTF8<>, UTF16<> > reader; + ParseStringHandler<UTF16<> > h; + reader.Parse(is, h); + EXPECT_EQ(0, StrCmp<UTF16<>::Ch>(e, h.str_)); + EXPECT_EQ(StrLen(e), h.length_); +} + +TEST(Reader, ParseString_TranscodingWithValidation) { + const char* x = "\"Hello\""; + const wchar_t* e = L"Hello"; + GenericStringStream<UTF8<> > is(x); + GenericReader<UTF8<>, UTF16<> > reader; + ParseStringHandler<UTF16<> > h; + reader.Parse<kParseValidateEncodingFlag>(is, h); + EXPECT_EQ(0, StrCmp<UTF16<>::Ch>(e, h.str_)); + EXPECT_EQ(StrLen(e), h.length_); +} + +TEST(Reader, ParseString_NonDestructive) { + StringStream s("\"Hello\\nWorld\""); + ParseStringHandler<UTF8<> > h; + Reader reader; + reader.Parse(s, h); + EXPECT_EQ(0, StrCmp("Hello\nWorld", h.str_)); + EXPECT_EQ(11u, h.length_); +} + +template <typename Encoding> +ParseErrorCode TestString(const typename Encoding::Ch* str) { + GenericStringStream<Encoding> s(str); + BaseReaderHandler<Encoding> h; + GenericReader<Encoding, Encoding> reader; + reader.template Parse<kParseValidateEncodingFlag>(s, h); + return reader.GetParseErrorCode(); +} + +TEST(Reader, ParseString_Error) { +#define TEST_STRING_ERROR(errorCode, str, errorOffset, streamPos)\ +{\ + GenericStringStream<UTF8<> > s(str);\ + BaseReaderHandler<UTF8<> > h;\ + GenericReader<UTF8<> , UTF8<> > reader;\ + reader.Parse<kParseValidateEncodingFlag>(s, h);\ + EXPECT_EQ(errorCode, reader.GetParseErrorCode());\ + EXPECT_EQ(errorOffset, reader.GetErrorOffset());\ + EXPECT_EQ(streamPos, s.Tell());\ +} + +#define ARRAY(...) { __VA_ARGS__ } +#define TEST_STRINGENCODING_ERROR(Encoding, TargetEncoding, utype, array) \ + { \ + static const utype ue[] = array; \ + static const Encoding::Ch* e = reinterpret_cast<const Encoding::Ch *>(&ue[0]); \ + EXPECT_EQ(kParseErrorStringInvalidEncoding, TestString<Encoding>(e));\ + /* decode error */\ + GenericStringStream<Encoding> s(e);\ + BaseReaderHandler<TargetEncoding> h;\ + GenericReader<Encoding, TargetEncoding> reader;\ + reader.Parse(s, h);\ + EXPECT_EQ(kParseErrorStringInvalidEncoding, reader.GetParseErrorCode());\ + } + + // Invalid escape character in string. + TEST_STRING_ERROR(kParseErrorStringEscapeInvalid, "[\"\\a\"]", 2, 3); + + // Incorrect hex digit after \\u escape in string. + TEST_STRING_ERROR(kParseErrorStringUnicodeEscapeInvalidHex, "[\"\\uABCG\"]", 2, 7); + + // Quotation in \\u escape in string (Issue #288) + TEST_STRING_ERROR(kParseErrorStringUnicodeEscapeInvalidHex, "[\"\\uaaa\"]", 2, 7); + TEST_STRING_ERROR(kParseErrorStringUnicodeEscapeInvalidHex, "[\"\\uD800\\uFFF\"]", 2, 13); + + // The surrogate pair in string is invalid. + TEST_STRING_ERROR(kParseErrorStringUnicodeSurrogateInvalid, "[\"\\uD800X\"]", 2, 8); + TEST_STRING_ERROR(kParseErrorStringUnicodeSurrogateInvalid, "[\"\\uD800\\uFFFF\"]", 2, 14); + + // Missing a closing quotation mark in string. + TEST_STRING_ERROR(kParseErrorStringMissQuotationMark, "[\"Test]", 7, 7); + + // http://www.cl.cam.ac.uk/~mgk25/ucs/examples/UTF-8-test.txt + + // 3 Malformed sequences + + // 3.1 Unexpected continuation bytes + { + char e[] = { '[', '\"', 0, '\"', ']', '\0' }; + for (unsigned char c = 0x80u; c <= 0xBFu; c++) { + e[2] = static_cast<char>(c); + ParseErrorCode error = TestString<UTF8<> >(e); + EXPECT_EQ(kParseErrorStringInvalidEncoding, error); + if (error != kParseErrorStringInvalidEncoding) + std::cout << static_cast<unsigned>(c) << std::endl; + } + } + + // 3.2 Lonely start characters, 3.5 Impossible bytes + { + char e[] = { '[', '\"', 0, ' ', '\"', ']', '\0' }; + for (unsigned c = 0xC0u; c <= 0xFFu; c++) { + e[2] = static_cast<char>(c); + int streamPos; + if (c <= 0xC1u) + streamPos = 3; // 0xC0 - 0xC1 + else if (c <= 0xDFu) + streamPos = 4; // 0xC2 - 0xDF + else if (c <= 0xEFu) + streamPos = 5; // 0xE0 - 0xEF + else if (c <= 0xF4u) + streamPos = 6; // 0xF0 - 0xF4 + else + streamPos = 3; // 0xF5 - 0xFF + TEST_STRING_ERROR(kParseErrorStringInvalidEncoding, e, 2, streamPos); + } + } + + // 4 Overlong sequences + + // 4.1 Examples of an overlong ASCII character + TEST_STRINGENCODING_ERROR(UTF8<>, UTF16<>, unsigned char, ARRAY('[', '\"', 0xC0u, 0xAFu, '\"', ']', '\0')); + TEST_STRINGENCODING_ERROR(UTF8<>, UTF16<>, unsigned char, ARRAY('[', '\"', 0xE0u, 0x80u, 0xAFu, '\"', ']', '\0')); + TEST_STRINGENCODING_ERROR(UTF8<>, UTF16<>, unsigned char, ARRAY('[', '\"', 0xF0u, 0x80u, 0x80u, 0xAFu, '\"', ']', '\0')); + + // 4.2 Maximum overlong sequences + TEST_STRINGENCODING_ERROR(UTF8<>, UTF16<>, unsigned char, ARRAY('[', '\"', 0xC1u, 0xBFu, '\"', ']', '\0')); + TEST_STRINGENCODING_ERROR(UTF8<>, UTF16<>, unsigned char, ARRAY('[', '\"', 0xE0u, 0x9Fu, 0xBFu, '\"', ']', '\0')); + TEST_STRINGENCODING_ERROR(UTF8<>, UTF16<>, unsigned char, ARRAY('[', '\"', 0xF0u, 0x8Fu, 0xBFu, 0xBFu, '\"', ']', '\0')); + + // 4.3 Overlong representation of the NUL character + TEST_STRINGENCODING_ERROR(UTF8<>, UTF16<>, unsigned char, ARRAY('[', '\"', 0xC0u, 0x80u, '\"', ']', '\0')); + TEST_STRINGENCODING_ERROR(UTF8<>, UTF16<>, unsigned char, ARRAY('[', '\"', 0xE0u, 0x80u, 0x80u, '\"', ']', '\0')); + TEST_STRINGENCODING_ERROR(UTF8<>, UTF16<>, unsigned char, ARRAY('[', '\"', 0xF0u, 0x80u, 0x80u, 0x80u, '\"', ']', '\0')); + + // 5 Illegal code positions + + // 5.1 Single UTF-16 surrogates + TEST_STRINGENCODING_ERROR(UTF8<>, UTF16<>, unsigned char, ARRAY('[', '\"', 0xEDu, 0xA0u, 0x80u, '\"', ']', '\0')); + TEST_STRINGENCODING_ERROR(UTF8<>, UTF16<>, unsigned char, ARRAY('[', '\"', 0xEDu, 0xADu, 0xBFu, '\"', ']', '\0')); + TEST_STRINGENCODING_ERROR(UTF8<>, UTF16<>, unsigned char, ARRAY('[', '\"', 0xEDu, 0xAEu, 0x80u, '\"', ']', '\0')); + TEST_STRINGENCODING_ERROR(UTF8<>, UTF16<>, unsigned char, ARRAY('[', '\"', 0xEDu, 0xAFu, 0xBFu, '\"', ']', '\0')); + TEST_STRINGENCODING_ERROR(UTF8<>, UTF16<>, unsigned char, ARRAY('[', '\"', 0xEDu, 0xB0u, 0x80u, '\"', ']', '\0')); + TEST_STRINGENCODING_ERROR(UTF8<>, UTF16<>, unsigned char, ARRAY('[', '\"', 0xEDu, 0xBEu, 0x80u, '\"', ']', '\0')); + TEST_STRINGENCODING_ERROR(UTF8<>, UTF16<>, unsigned char, ARRAY('[', '\"', 0xEDu, 0xBFu, 0xBFu, '\"', ']', '\0')); + + // Malform UTF-16 sequences + TEST_STRINGENCODING_ERROR(UTF16<>, UTF8<>, wchar_t, ARRAY('[', '\"', 0xDC00, 0xDC00, '\"', ']', '\0')); + TEST_STRINGENCODING_ERROR(UTF16<>, UTF8<>, wchar_t, ARRAY('[', '\"', 0xD800, 0xD800, '\"', ']', '\0')); + + // Malform UTF-32 sequence + TEST_STRINGENCODING_ERROR(UTF32<>, UTF8<>, unsigned, ARRAY('[', '\"', 0x110000, '\"', ']', '\0')); + + // Malform ASCII sequence + TEST_STRINGENCODING_ERROR(ASCII<>, UTF8<>, char, ARRAY('[', '\"', char(0x80u), '\"', ']', '\0')); + +#undef ARRAY +#undef TEST_STRINGARRAY_ERROR +} + +template <unsigned count> +struct ParseArrayHandler : BaseReaderHandler<UTF8<>, ParseArrayHandler<count> > { + ParseArrayHandler() : step_(0) {} + + bool Default() { ADD_FAILURE(); return false; } + bool Uint(unsigned i) { EXPECT_EQ(step_, i); step_++; return true; } + bool StartArray() { EXPECT_EQ(0u, step_); step_++; return true; } + bool EndArray(SizeType) { step_++; return true; } + + unsigned step_; +}; + +TEST(Reader, ParseEmptyArray) { + char *json = StrDup("[ ] "); + InsituStringStream s(json); + ParseArrayHandler<0> h; + Reader reader; + reader.Parse(s, h); + EXPECT_EQ(2u, h.step_); + free(json); +} + +TEST(Reader, ParseArray) { + char *json = StrDup("[1, 2, 3, 4]"); + InsituStringStream s(json); + ParseArrayHandler<4> h; + Reader reader; + reader.Parse(s, h); + EXPECT_EQ(6u, h.step_); + free(json); +} + +TEST(Reader, ParseArray_Error) { +#define TEST_ARRAY_ERROR(errorCode, str, errorOffset) \ + { \ + int streamPos = errorOffset; \ + char buffer[1001]; \ + strncpy(buffer, str, 1000); \ + InsituStringStream s(buffer); \ + BaseReaderHandler<> h; \ + GenericReader<UTF8<>, UTF8<>, CrtAllocator> reader; \ + EXPECT_FALSE(reader.Parse(s, h)); \ + EXPECT_EQ(errorCode, reader.GetParseErrorCode());\ + EXPECT_EQ(errorOffset, reader.GetErrorOffset());\ + EXPECT_EQ(streamPos, s.Tell());\ + } + + // Missing a comma or ']' after an array element. + TEST_ARRAY_ERROR(kParseErrorArrayMissCommaOrSquareBracket, "[1", 2); + TEST_ARRAY_ERROR(kParseErrorArrayMissCommaOrSquareBracket, "[1}", 2); + TEST_ARRAY_ERROR(kParseErrorArrayMissCommaOrSquareBracket, "[1 2]", 3); + + // Array cannot have a trailing comma (without kParseTrailingCommasFlag); + // a value must follow a comma + TEST_ARRAY_ERROR(kParseErrorValueInvalid, "[1,]", 3); + +#undef TEST_ARRAY_ERROR +} + +struct ParseObjectHandler : BaseReaderHandler<UTF8<>, ParseObjectHandler> { + ParseObjectHandler() : step_(0) {} + + bool Default() { ADD_FAILURE(); return false; } + bool Null() { EXPECT_EQ(8u, step_); step_++; return true; } + bool Bool(bool b) { + switch(step_) { + case 4: EXPECT_TRUE(b); step_++; return true; + case 6: EXPECT_FALSE(b); step_++; return true; + default: ADD_FAILURE(); return false; + } + } + bool Int(int i) { + switch(step_) { + case 10: EXPECT_EQ(123, i); step_++; return true; + case 15: EXPECT_EQ(1, i); step_++; return true; + case 16: EXPECT_EQ(2, i); step_++; return true; + case 17: EXPECT_EQ(3, i); step_++; return true; + default: ADD_FAILURE(); return false; + } + } + bool Uint(unsigned i) { return Int(static_cast<int>(i)); } + bool Double(double d) { EXPECT_EQ(12u, step_); EXPECT_DOUBLE_EQ(3.1416, d); step_++; return true; } + bool String(const char* str, size_t, bool) { + switch(step_) { + case 1: EXPECT_STREQ("hello", str); step_++; return true; + case 2: EXPECT_STREQ("world", str); step_++; return true; + case 3: EXPECT_STREQ("t", str); step_++; return true; + case 5: EXPECT_STREQ("f", str); step_++; return true; + case 7: EXPECT_STREQ("n", str); step_++; return true; + case 9: EXPECT_STREQ("i", str); step_++; return true; + case 11: EXPECT_STREQ("pi", str); step_++; return true; + case 13: EXPECT_STREQ("a", str); step_++; return true; + default: ADD_FAILURE(); return false; + } + } + bool StartObject() { EXPECT_EQ(0u, step_); step_++; return true; } + bool EndObject(SizeType memberCount) { EXPECT_EQ(19u, step_); EXPECT_EQ(7u, memberCount); step_++; return true; } + bool StartArray() { EXPECT_EQ(14u, step_); step_++; return true; } + bool EndArray(SizeType elementCount) { EXPECT_EQ(18u, step_); EXPECT_EQ(3u, elementCount); step_++; return true; } + + unsigned step_; +}; + +TEST(Reader, ParseObject) { + const char* json = "{ \"hello\" : \"world\", \"t\" : true , \"f\" : false, \"n\": null, \"i\":123, \"pi\": 3.1416, \"a\":[1, 2, 3] } "; + + // Insitu + { + char* json2 = StrDup(json); + InsituStringStream s(json2); + ParseObjectHandler h; + Reader reader; + reader.Parse<kParseInsituFlag>(s, h); + EXPECT_EQ(20u, h.step_); + free(json2); + } + + // Normal + { + StringStream s(json); + ParseObjectHandler h; + Reader reader; + reader.Parse(s, h); + EXPECT_EQ(20u, h.step_); + } +} + +struct ParseEmptyObjectHandler : BaseReaderHandler<UTF8<>, ParseEmptyObjectHandler> { + ParseEmptyObjectHandler() : step_(0) {} + + bool Default() { ADD_FAILURE(); return false; } + bool StartObject() { EXPECT_EQ(0u, step_); step_++; return true; } + bool EndObject(SizeType) { EXPECT_EQ(1u, step_); step_++; return true; } + + unsigned step_; +}; + +TEST(Reader, Parse_EmptyObject) { + StringStream s("{ } "); + ParseEmptyObjectHandler h; + Reader reader; + reader.Parse(s, h); + EXPECT_EQ(2u, h.step_); +} + +struct ParseMultipleRootHandler : BaseReaderHandler<UTF8<>, ParseMultipleRootHandler> { + ParseMultipleRootHandler() : step_(0) {} + + bool Default() { ADD_FAILURE(); return false; } + bool StartObject() { EXPECT_EQ(0u, step_); step_++; return true; } + bool EndObject(SizeType) { EXPECT_EQ(1u, step_); step_++; return true; } + bool StartArray() { EXPECT_EQ(2u, step_); step_++; return true; } + bool EndArray(SizeType) { EXPECT_EQ(3u, step_); step_++; return true; } + + unsigned step_; +}; + +template <unsigned parseFlags> +void TestMultipleRoot() { + StringStream s("{}[] a"); + ParseMultipleRootHandler h; + Reader reader; + EXPECT_TRUE(reader.Parse<parseFlags>(s, h)); + EXPECT_EQ(2u, h.step_); + EXPECT_TRUE(reader.Parse<parseFlags>(s, h)); + EXPECT_EQ(4u, h.step_); + EXPECT_EQ(' ', s.Take()); + EXPECT_EQ('a', s.Take()); +} + +TEST(Reader, Parse_MultipleRoot) { + TestMultipleRoot<kParseStopWhenDoneFlag>(); +} + +TEST(Reader, ParseIterative_MultipleRoot) { + TestMultipleRoot<kParseIterativeFlag | kParseStopWhenDoneFlag>(); +} + +template <unsigned parseFlags> +void TestInsituMultipleRoot() { + char* buffer = strdup("{}[] a"); + InsituStringStream s(buffer); + ParseMultipleRootHandler h; + Reader reader; + EXPECT_TRUE(reader.Parse<kParseInsituFlag | parseFlags>(s, h)); + EXPECT_EQ(2u, h.step_); + EXPECT_TRUE(reader.Parse<kParseInsituFlag | parseFlags>(s, h)); + EXPECT_EQ(4u, h.step_); + EXPECT_EQ(' ', s.Take()); + EXPECT_EQ('a', s.Take()); + free(buffer); +} + +TEST(Reader, ParseInsitu_MultipleRoot) { + TestInsituMultipleRoot<kParseStopWhenDoneFlag>(); +} + +TEST(Reader, ParseInsituIterative_MultipleRoot) { + TestInsituMultipleRoot<kParseIterativeFlag | kParseStopWhenDoneFlag>(); +} + +#define TEST_ERROR(errorCode, str, errorOffset) \ + { \ + int streamPos = errorOffset; \ + char buffer[1001]; \ + strncpy(buffer, str, 1000); \ + InsituStringStream s(buffer); \ + BaseReaderHandler<> h; \ + Reader reader; \ + EXPECT_FALSE(reader.Parse(s, h)); \ + EXPECT_EQ(errorCode, reader.GetParseErrorCode());\ + EXPECT_EQ(errorOffset, reader.GetErrorOffset());\ + EXPECT_EQ(streamPos, s.Tell());\ + } + +TEST(Reader, ParseDocument_Error) { + // The document is empty. + TEST_ERROR(kParseErrorDocumentEmpty, "", 0); + TEST_ERROR(kParseErrorDocumentEmpty, " ", 1); + TEST_ERROR(kParseErrorDocumentEmpty, " \n", 2); + + // The document root must not follow by other values. + TEST_ERROR(kParseErrorDocumentRootNotSingular, "[] 0", 3); + TEST_ERROR(kParseErrorDocumentRootNotSingular, "{} 0", 3); + TEST_ERROR(kParseErrorDocumentRootNotSingular, "null []", 5); + TEST_ERROR(kParseErrorDocumentRootNotSingular, "0 {}", 2); +} + +TEST(Reader, ParseValue_Error) { + // Invalid value. + TEST_ERROR(kParseErrorValueInvalid, "nulL", 3); + TEST_ERROR(kParseErrorValueInvalid, "truE", 3); + TEST_ERROR(kParseErrorValueInvalid, "falsE", 4); + TEST_ERROR(kParseErrorValueInvalid, "a]", 0); + TEST_ERROR(kParseErrorValueInvalid, ".1", 0); +} + +TEST(Reader, ParseObject_Error) { + // Missing a name for object member. + TEST_ERROR(kParseErrorObjectMissName, "{1}", 1); + TEST_ERROR(kParseErrorObjectMissName, "{:1}", 1); + TEST_ERROR(kParseErrorObjectMissName, "{null:1}", 1); + TEST_ERROR(kParseErrorObjectMissName, "{true:1}", 1); + TEST_ERROR(kParseErrorObjectMissName, "{false:1}", 1); + TEST_ERROR(kParseErrorObjectMissName, "{1:1}", 1); + TEST_ERROR(kParseErrorObjectMissName, "{[]:1}", 1); + TEST_ERROR(kParseErrorObjectMissName, "{{}:1}", 1); + TEST_ERROR(kParseErrorObjectMissName, "{xyz:1}", 1); + + // Missing a colon after a name of object member. + TEST_ERROR(kParseErrorObjectMissColon, "{\"a\" 1}", 5); + TEST_ERROR(kParseErrorObjectMissColon, "{\"a\",1}", 4); + + // Must be a comma or '}' after an object member + TEST_ERROR(kParseErrorObjectMissCommaOrCurlyBracket, "{\"a\":1]", 6); + + // Object cannot have a trailing comma (without kParseTrailingCommasFlag); + // an object member name must follow a comma + TEST_ERROR(kParseErrorObjectMissName, "{\"a\":1,}", 7); + + // This tests that MemoryStream is checking the length in Peek(). + { + MemoryStream ms("{\"a\"", 1); + BaseReaderHandler<> h; + Reader reader; + EXPECT_FALSE(reader.Parse<kParseStopWhenDoneFlag>(ms, h)); + EXPECT_EQ(kParseErrorObjectMissName, reader.GetParseErrorCode()); + } +} + +#undef TEST_ERROR + +TEST(Reader, SkipWhitespace) { + StringStream ss(" A \t\tB\n \n\nC\r\r \rD \t\n\r E"); + const char* expected = "ABCDE"; + for (size_t i = 0; i < 5; i++) { + SkipWhitespace(ss); + EXPECT_EQ(expected[i], ss.Take()); + } +} + +// Test implementing a stream without copy stream optimization. +// Clone from GenericStringStream except that copy constructor is disabled. +template <typename Encoding> +class CustomStringStream { +public: + typedef typename Encoding::Ch Ch; + + CustomStringStream(const Ch *src) : src_(src), head_(src) {} + + Ch Peek() const { return *src_; } + Ch Take() { return *src_++; } + size_t Tell() const { return static_cast<size_t>(src_ - head_); } + + Ch* PutBegin() { RAPIDJSON_ASSERT(false); return 0; } + void Put(Ch) { RAPIDJSON_ASSERT(false); } + void Flush() { RAPIDJSON_ASSERT(false); } + size_t PutEnd(Ch*) { RAPIDJSON_ASSERT(false); return 0; } + +private: + // Prohibit copy constructor & assignment operator. + CustomStringStream(const CustomStringStream&); + CustomStringStream& operator=(const CustomStringStream&); + + const Ch* src_; //!< Current read position. + const Ch* head_; //!< Original head of the string. +}; + +// If the following code is compiled, it should generate compilation error as predicted. +// Because CustomStringStream<> is not copyable via making copy constructor private. +#if 0 +namespace rapidjson { + +template <typename Encoding> +struct StreamTraits<CustomStringStream<Encoding> > { + enum { copyOptimization = 1 }; +}; + +} // namespace rapidjson +#endif + +TEST(Reader, CustomStringStream) { + const char* json = "{ \"hello\" : \"world\", \"t\" : true , \"f\" : false, \"n\": null, \"i\":123, \"pi\": 3.1416, \"a\":[1, 2, 3] } "; + CustomStringStream<UTF8<char> > s(json); + ParseObjectHandler h; + Reader reader; + reader.Parse(s, h); + EXPECT_EQ(20u, h.step_); +} + +#include <sstream> + +class IStreamWrapper { +public: + typedef char Ch; + + IStreamWrapper(std::istream& is) : is_(is) {} + + Ch Peek() const { + int c = is_.peek(); + return c == std::char_traits<char>::eof() ? '\0' : static_cast<Ch>(c); + } + + Ch Take() { + int c = is_.get(); + return c == std::char_traits<char>::eof() ? '\0' : static_cast<Ch>(c); + } + + size_t Tell() const { return static_cast<size_t>(is_.tellg()); } + + Ch* PutBegin() { assert(false); return 0; } + void Put(Ch) { assert(false); } + void Flush() { assert(false); } + size_t PutEnd(Ch*) { assert(false); return 0; } + +private: + IStreamWrapper(const IStreamWrapper&); + IStreamWrapper& operator=(const IStreamWrapper&); + + std::istream& is_; +}; + +TEST(Reader, Parse_IStreamWrapper_StringStream) { + const char* json = "[1,2,3,4]"; + + std::stringstream ss(json); + IStreamWrapper is(ss); + + Reader reader; + ParseArrayHandler<4> h; + reader.Parse(is, h); + EXPECT_FALSE(reader.HasParseError()); +} + +// Test iterative parsing. + +#define TESTERRORHANDLING(text, errorCode, offset)\ +{\ + int streamPos = offset; \ + StringStream json(text); \ + BaseReaderHandler<> handler; \ + Reader reader; \ + reader.Parse<kParseIterativeFlag>(json, handler); \ + EXPECT_TRUE(reader.HasParseError()); \ + EXPECT_EQ(errorCode, reader.GetParseErrorCode()); \ + EXPECT_EQ(offset, reader.GetErrorOffset()); \ + EXPECT_EQ(streamPos, json.Tell()); \ +} + +TEST(Reader, IterativeParsing_ErrorHandling) { + TESTERRORHANDLING("{\"a\": a}", kParseErrorValueInvalid, 6u); + + TESTERRORHANDLING("", kParseErrorDocumentEmpty, 0u); + TESTERRORHANDLING("{}{}", kParseErrorDocumentRootNotSingular, 2u); + + TESTERRORHANDLING("{1}", kParseErrorObjectMissName, 1u); + TESTERRORHANDLING("{\"a\", 1}", kParseErrorObjectMissColon, 4u); + TESTERRORHANDLING("{\"a\"}", kParseErrorObjectMissColon, 4u); + TESTERRORHANDLING("{\"a\": 1", kParseErrorObjectMissCommaOrCurlyBracket, 7u); + TESTERRORHANDLING("[1 2 3]", kParseErrorArrayMissCommaOrSquareBracket, 3u); + TESTERRORHANDLING("{\"a: 1", kParseErrorStringMissQuotationMark, 6u); + TESTERRORHANDLING("{\"a\":}", kParseErrorValueInvalid, 5u); + TESTERRORHANDLING("{\"a\":]", kParseErrorValueInvalid, 5u); + TESTERRORHANDLING("[1,2,}", kParseErrorValueInvalid, 5u); + TESTERRORHANDLING("[}]", kParseErrorValueInvalid, 1u); + TESTERRORHANDLING("[,]", kParseErrorValueInvalid, 1u); + TESTERRORHANDLING("[1,,]", kParseErrorValueInvalid, 3u); + + // Trailing commas are not allowed without kParseTrailingCommasFlag + TESTERRORHANDLING("{\"a\": 1,}", kParseErrorObjectMissName, 8u); + TESTERRORHANDLING("[1,2,3,]", kParseErrorValueInvalid, 7u); + + // Any JSON value can be a valid root element in RFC7159. + TESTERRORHANDLING("\"ab", kParseErrorStringMissQuotationMark, 3u); + TESTERRORHANDLING("truE", kParseErrorValueInvalid, 3u); + TESTERRORHANDLING("False", kParseErrorValueInvalid, 0u); + TESTERRORHANDLING("true, false", kParseErrorDocumentRootNotSingular, 4u); + TESTERRORHANDLING("false, false", kParseErrorDocumentRootNotSingular, 5u); + TESTERRORHANDLING("nulL", kParseErrorValueInvalid, 3u); + TESTERRORHANDLING("null , null", kParseErrorDocumentRootNotSingular, 5u); + TESTERRORHANDLING("1a", kParseErrorDocumentRootNotSingular, 1u); +} + +template<typename Encoding = UTF8<> > +struct IterativeParsingReaderHandler { + typedef typename Encoding::Ch Ch; + + const static int LOG_NULL = -1; + const static int LOG_BOOL = -2; + const static int LOG_INT = -3; + const static int LOG_UINT = -4; + const static int LOG_INT64 = -5; + const static int LOG_UINT64 = -6; + const static int LOG_DOUBLE = -7; + const static int LOG_STRING = -8; + const static int LOG_STARTOBJECT = -9; + const static int LOG_KEY = -10; + const static int LOG_ENDOBJECT = -11; + const static int LOG_STARTARRAY = -12; + const static int LOG_ENDARRAY = -13; + + const static size_t LogCapacity = 256; + int Logs[LogCapacity]; + size_t LogCount; + + IterativeParsingReaderHandler() : LogCount(0) { + } + + bool Null() { RAPIDJSON_ASSERT(LogCount < LogCapacity); Logs[LogCount++] = LOG_NULL; return true; } + + bool Bool(bool) { RAPIDJSON_ASSERT(LogCount < LogCapacity); Logs[LogCount++] = LOG_BOOL; return true; } + + bool Int(int) { RAPIDJSON_ASSERT(LogCount < LogCapacity); Logs[LogCount++] = LOG_INT; return true; } + + bool Uint(unsigned) { RAPIDJSON_ASSERT(LogCount < LogCapacity); Logs[LogCount++] = LOG_INT; return true; } + + bool Int64(int64_t) { RAPIDJSON_ASSERT(LogCount < LogCapacity); Logs[LogCount++] = LOG_INT64; return true; } + + bool Uint64(uint64_t) { RAPIDJSON_ASSERT(LogCount < LogCapacity); Logs[LogCount++] = LOG_UINT64; return true; } + + bool Double(double) { RAPIDJSON_ASSERT(LogCount < LogCapacity); Logs[LogCount++] = LOG_DOUBLE; return true; } + + bool RawNumber(const Ch*, SizeType, bool) { RAPIDJSON_ASSERT(LogCount < LogCapacity); Logs[LogCount++] = LOG_STRING; return true; } + + bool String(const Ch*, SizeType, bool) { RAPIDJSON_ASSERT(LogCount < LogCapacity); Logs[LogCount++] = LOG_STRING; return true; } + + bool StartObject() { RAPIDJSON_ASSERT(LogCount < LogCapacity); Logs[LogCount++] = LOG_STARTOBJECT; return true; } + + bool Key (const Ch*, SizeType, bool) { RAPIDJSON_ASSERT(LogCount < LogCapacity); Logs[LogCount++] = LOG_KEY; return true; } + + bool EndObject(SizeType c) { + RAPIDJSON_ASSERT(LogCount < LogCapacity); + Logs[LogCount++] = LOG_ENDOBJECT; + Logs[LogCount++] = static_cast<int>(c); + return true; + } + + bool StartArray() { RAPIDJSON_ASSERT(LogCount < LogCapacity); Logs[LogCount++] = LOG_STARTARRAY; return true; } + + bool EndArray(SizeType c) { + RAPIDJSON_ASSERT(LogCount < LogCapacity); + Logs[LogCount++] = LOG_ENDARRAY; + Logs[LogCount++] = static_cast<int>(c); + return true; + } +}; + +TEST(Reader, IterativeParsing_General) { + { + StringStream is("[1, {\"k\": [1, 2]}, null, false, true, \"string\", 1.2]"); + Reader reader; + IterativeParsingReaderHandler<> handler; + + ParseResult r = reader.Parse<kParseIterativeFlag>(is, handler); + + EXPECT_FALSE(r.IsError()); + EXPECT_FALSE(reader.HasParseError()); + + int e[] = { + handler.LOG_STARTARRAY, + handler.LOG_INT, + handler.LOG_STARTOBJECT, + handler.LOG_KEY, + handler.LOG_STARTARRAY, + handler.LOG_INT, + handler.LOG_INT, + handler.LOG_ENDARRAY, 2, + handler.LOG_ENDOBJECT, 1, + handler.LOG_NULL, + handler.LOG_BOOL, + handler.LOG_BOOL, + handler.LOG_STRING, + handler.LOG_DOUBLE, + handler.LOG_ENDARRAY, 7 + }; + + EXPECT_EQ(sizeof(e) / sizeof(int), handler.LogCount); + + for (size_t i = 0; i < handler.LogCount; ++i) { + EXPECT_EQ(e[i], handler.Logs[i]) << "i = " << i; + } + } +} + +TEST(Reader, IterativeParsing_Count) { + { + StringStream is("[{}, {\"k\": 1}, [1], []]"); + Reader reader; + IterativeParsingReaderHandler<> handler; + + ParseResult r = reader.Parse<kParseIterativeFlag>(is, handler); + + EXPECT_FALSE(r.IsError()); + EXPECT_FALSE(reader.HasParseError()); + + int e[] = { + handler.LOG_STARTARRAY, + handler.LOG_STARTOBJECT, + handler.LOG_ENDOBJECT, 0, + handler.LOG_STARTOBJECT, + handler.LOG_KEY, + handler.LOG_INT, + handler.LOG_ENDOBJECT, 1, + handler.LOG_STARTARRAY, + handler.LOG_INT, + handler.LOG_ENDARRAY, 1, + handler.LOG_STARTARRAY, + handler.LOG_ENDARRAY, 0, + handler.LOG_ENDARRAY, 4 + }; + + EXPECT_EQ(sizeof(e) / sizeof(int), handler.LogCount); + + for (size_t i = 0; i < handler.LogCount; ++i) { + EXPECT_EQ(e[i], handler.Logs[i]) << "i = " << i; + } + } +} + +// Test iterative parsing on kParseErrorTermination. +struct HandlerTerminateAtStartObject : public IterativeParsingReaderHandler<> { + bool StartObject() { return false; } +}; + +struct HandlerTerminateAtStartArray : public IterativeParsingReaderHandler<> { + bool StartArray() { return false; } +}; + +struct HandlerTerminateAtEndObject : public IterativeParsingReaderHandler<> { + bool EndObject(SizeType) { return false; } +}; + +struct HandlerTerminateAtEndArray : public IterativeParsingReaderHandler<> { + bool EndArray(SizeType) { return false; } +}; + +TEST(Reader, IterativeParsing_ShortCircuit) { + { + HandlerTerminateAtStartObject handler; + Reader reader; + StringStream is("[1, {}]"); + + ParseResult r = reader.Parse<kParseIterativeFlag>(is, handler); + + EXPECT_TRUE(reader.HasParseError()); + EXPECT_EQ(kParseErrorTermination, r.Code()); + EXPECT_EQ(4u, r.Offset()); + } + + { + HandlerTerminateAtStartArray handler; + Reader reader; + StringStream is("{\"a\": []}"); + + ParseResult r = reader.Parse<kParseIterativeFlag>(is, handler); + + EXPECT_TRUE(reader.HasParseError()); + EXPECT_EQ(kParseErrorTermination, r.Code()); + EXPECT_EQ(6u, r.Offset()); + } + + { + HandlerTerminateAtEndObject handler; + Reader reader; + StringStream is("[1, {}]"); + + ParseResult r = reader.Parse<kParseIterativeFlag>(is, handler); + + EXPECT_TRUE(reader.HasParseError()); + EXPECT_EQ(kParseErrorTermination, r.Code()); + EXPECT_EQ(5u, r.Offset()); + } + + { + HandlerTerminateAtEndArray handler; + Reader reader; + StringStream is("{\"a\": []}"); + + ParseResult r = reader.Parse<kParseIterativeFlag>(is, handler); + + EXPECT_TRUE(reader.HasParseError()); + EXPECT_EQ(kParseErrorTermination, r.Code()); + EXPECT_EQ(7u, r.Offset()); + } +} + +// For covering BaseReaderHandler default functions +TEST(Reader, BaseReaderHandler_Default) { + BaseReaderHandler<> h; + Reader reader; + StringStream is("[null, true, -1, 1, -1234567890123456789, 1234567890123456789, 3.14, \"s\", { \"a\" : 1 }]"); + EXPECT_TRUE(reader.Parse(is, h)); +} + +template <int e> +struct TerminateHandler { + bool Null() { return e != 0; } + bool Bool(bool) { return e != 1; } + bool Int(int) { return e != 2; } + bool Uint(unsigned) { return e != 3; } + bool Int64(int64_t) { return e != 4; } + bool Uint64(uint64_t) { return e != 5; } + bool Double(double) { return e != 6; } + bool RawNumber(const char*, SizeType, bool) { return e != 7; } + bool String(const char*, SizeType, bool) { return e != 8; } + bool StartObject() { return e != 9; } + bool Key(const char*, SizeType, bool) { return e != 10; } + bool EndObject(SizeType) { return e != 11; } + bool StartArray() { return e != 12; } + bool EndArray(SizeType) { return e != 13; } +}; + +#define TEST_TERMINATION(e, json)\ +{\ + Reader reader;\ + TerminateHandler<e> h;\ + StringStream is(json);\ + EXPECT_FALSE(reader.Parse(is, h));\ + EXPECT_EQ(kParseErrorTermination, reader.GetParseErrorCode());\ +} + +TEST(Reader, ParseTerminationByHandler) { + TEST_TERMINATION(0, "[null"); + TEST_TERMINATION(1, "[true"); + TEST_TERMINATION(1, "[false"); + TEST_TERMINATION(2, "[-1"); + TEST_TERMINATION(3, "[1"); + TEST_TERMINATION(4, "[-1234567890123456789"); + TEST_TERMINATION(5, "[1234567890123456789"); + TEST_TERMINATION(6, "[0.5]"); + // RawNumber() is never called + TEST_TERMINATION(8, "[\"a\""); + TEST_TERMINATION(9, "[{"); + TEST_TERMINATION(10, "[{\"a\""); + TEST_TERMINATION(11, "[{}"); + TEST_TERMINATION(11, "[{\"a\":1}"); // non-empty object + TEST_TERMINATION(12, "{\"a\":["); + TEST_TERMINATION(13, "{\"a\":[]"); + TEST_TERMINATION(13, "{\"a\":[1]"); // non-empty array +} + +TEST(Reader, ParseComments) { + const char* json = + "// Here is a one-line comment.\n" + "{// And here's another one\n" + " /*And here's an in-line one.*/\"hello\" : \"world\"," + " \"t\" :/* And one with '*' symbol*/true ," + "/* A multiline comment\n" + " goes here*/" + " \"f\" : false, \"n\": null, \"i\":123, \"pi\": 3.1416, \"a\":[1, 2, 3]" + "}/*And the last one to be sure */"; + + StringStream s(json); + ParseObjectHandler h; + Reader reader; + EXPECT_TRUE(reader.Parse<kParseCommentsFlag>(s, h)); + EXPECT_EQ(20u, h.step_); +} + +TEST(Reader, ParseEmptyInlineComment) { + const char* json = "{/**/\"hello\" : \"world\", \"t\" : true, \"f\" : false, \"n\": null, \"i\":123, \"pi\": 3.1416, \"a\":[1, 2, 3] }"; + + StringStream s(json); + ParseObjectHandler h; + Reader reader; + EXPECT_TRUE(reader.Parse<kParseCommentsFlag>(s, h)); + EXPECT_EQ(20u, h.step_); +} + +TEST(Reader, ParseEmptyOnelineComment) { + const char* json = "{//\n\"hello\" : \"world\", \"t\" : true, \"f\" : false, \"n\": null, \"i\":123, \"pi\": 3.1416, \"a\":[1, 2, 3] }"; + + StringStream s(json); + ParseObjectHandler h; + Reader reader; + EXPECT_TRUE(reader.Parse<kParseCommentsFlag>(s, h)); + EXPECT_EQ(20u, h.step_); +} + +TEST(Reader, ParseMultipleCommentsInARow) { + const char* json = + "{/* first comment *//* second */\n" + "/* third */ /*fourth*/// last one\n" + "\"hello\" : \"world\", \"t\" : true, \"f\" : false, \"n\": null, \"i\":123, \"pi\": 3.1416, \"a\":[1, 2, 3] }"; + + StringStream s(json); + ParseObjectHandler h; + Reader reader; + EXPECT_TRUE(reader.Parse<kParseCommentsFlag>(s, h)); + EXPECT_EQ(20u, h.step_); +} + +TEST(Reader, InlineCommentsAreDisabledByDefault) { + { + const char* json = "{/* Inline comment. */\"hello\" : \"world\", \"t\" : true, \"f\" : false, \"n\": null, \"i\":123, \"pi\": 3.1416, \"a\":[1, 2, 3] }"; + + StringStream s(json); + ParseObjectHandler h; + Reader reader; + EXPECT_FALSE(reader.Parse<kParseDefaultFlags>(s, h)); + } + + { + const char* json = + "{\"hello\" : /* Multiline comment starts here\n" + " continues here\n" + " and ends here */\"world\", \"t\" :true , \"f\" : false, \"n\": null, \"i\":123, \"pi\": 3.1416, \"a\":[1, 2, 3] }"; + + StringStream s(json); + ParseObjectHandler h; + Reader reader; + EXPECT_FALSE(reader.Parse<kParseDefaultFlags>(s, h)); + } +} + +TEST(Reader, OnelineCommentsAreDisabledByDefault) { + const char* json = "{// One-line comment\n\"hello\" : \"world\", \"t\" : true , \"f\" : false, \"n\": null, \"i\":123, \"pi\": 3.1416, \"a\":[1, 2, 3] }"; + + StringStream s(json); + ParseObjectHandler h; + Reader reader; + EXPECT_FALSE(reader.Parse<kParseDefaultFlags>(s, h)); +} + +TEST(Reader, EofAfterOneLineComment) { + const char* json = "{\"hello\" : \"world\" // EOF is here -->\0 \n}"; + + StringStream s(json); + ParseObjectHandler h; + Reader reader; + EXPECT_FALSE(reader.Parse<kParseCommentsFlag>(s, h)); + EXPECT_EQ(kParseErrorObjectMissCommaOrCurlyBracket, reader.GetParseErrorCode()); +} + +TEST(Reader, IncompleteMultilineComment) { + const char* json = "{\"hello\" : \"world\" /* EOF is here -->\0 */}"; + + StringStream s(json); + ParseObjectHandler h; + Reader reader; + EXPECT_FALSE(reader.Parse<kParseCommentsFlag>(s, h)); + EXPECT_EQ(kParseErrorUnspecificSyntaxError, reader.GetParseErrorCode()); +} + +TEST(Reader, IncompleteMultilineComment2) { + const char* json = "{\"hello\" : \"world\" /* *\0 */}"; + + StringStream s(json); + ParseObjectHandler h; + Reader reader; + EXPECT_FALSE(reader.Parse<kParseCommentsFlag>(s, h)); + EXPECT_EQ(kParseErrorUnspecificSyntaxError, reader.GetParseErrorCode()); +} + +TEST(Reader, UnrecognizedComment) { + const char* json = "{\"hello\" : \"world\" /! }"; + + StringStream s(json); + ParseObjectHandler h; + Reader reader; + EXPECT_FALSE(reader.Parse<kParseCommentsFlag>(s, h)); + EXPECT_EQ(kParseErrorUnspecificSyntaxError, reader.GetParseErrorCode()); +} + +struct NumbersAsStringsHandler { + bool Null() { return true; } + bool Bool(bool) { return true; } + bool Int(int) { return true; } + bool Uint(unsigned) { return true; } + bool Int64(int64_t) { return true; } + bool Uint64(uint64_t) { return true; } + bool Double(double) { return true; } + // 'str' is not null-terminated + bool RawNumber(const char* str, SizeType length, bool) { + EXPECT_TRUE(str != 0); + EXPECT_TRUE(expected_len_ == length); + EXPECT_TRUE(strncmp(str, expected_, length) == 0); + return true; + } + bool String(const char*, SizeType, bool) { return true; } + bool StartObject() { return true; } + bool Key(const char*, SizeType, bool) { return true; } + bool EndObject(SizeType) { return true; } + bool StartArray() { return true; } + bool EndArray(SizeType) { return true; } + + NumbersAsStringsHandler(const char* expected) + : expected_(expected) + , expected_len_(strlen(expected)) {} + + const char* expected_; + size_t expected_len_; +}; + +TEST(Reader, NumbersAsStrings) { + { + const char* json = "{ \"pi\": 3.1416 } "; + StringStream s(json); + NumbersAsStringsHandler h("3.1416"); + Reader reader; + EXPECT_TRUE(reader.Parse<kParseNumbersAsStringsFlag>(s, h)); + } + { + char* json = StrDup("{ \"pi\": 3.1416 } "); + InsituStringStream s(json); + NumbersAsStringsHandler h("3.1416"); + Reader reader; + EXPECT_TRUE(reader.Parse<kParseInsituFlag|kParseNumbersAsStringsFlag>(s, h)); + free(json); + } + { + const char* json = "{ \"gigabyte\": 1.0e9 } "; + StringStream s(json); + NumbersAsStringsHandler h("1.0e9"); + Reader reader; + EXPECT_TRUE(reader.Parse<kParseNumbersAsStringsFlag>(s, h)); + } + { + char* json = StrDup("{ \"gigabyte\": 1.0e9 } "); + InsituStringStream s(json); + NumbersAsStringsHandler h("1.0e9"); + Reader reader; + EXPECT_TRUE(reader.Parse<kParseInsituFlag|kParseNumbersAsStringsFlag>(s, h)); + free(json); + } + { + const char* json = "{ \"pi\": 314.159e-2 } "; + StringStream s(json); + NumbersAsStringsHandler h("314.159e-2"); + Reader reader; + EXPECT_TRUE(reader.Parse<kParseNumbersAsStringsFlag>(s, h)); + } + { + char* json = StrDup("{ \"gigabyte\": 314.159e-2 } "); + InsituStringStream s(json); + NumbersAsStringsHandler h("314.159e-2"); + Reader reader; + EXPECT_TRUE(reader.Parse<kParseInsituFlag|kParseNumbersAsStringsFlag>(s, h)); + free(json); + } + { + const char* json = "{ \"negative\": -1.54321 } "; + StringStream s(json); + NumbersAsStringsHandler h("-1.54321"); + Reader reader; + EXPECT_TRUE(reader.Parse<kParseNumbersAsStringsFlag>(s, h)); + } + { + char* json = StrDup("{ \"negative\": -1.54321 } "); + InsituStringStream s(json); + NumbersAsStringsHandler h("-1.54321"); + Reader reader; + EXPECT_TRUE(reader.Parse<kParseInsituFlag|kParseNumbersAsStringsFlag>(s, h)); + free(json); + } + { + const char* json = "{ \"pi\": 314.159e-2 } "; + std::stringstream ss(json); + IStreamWrapper s(ss); + NumbersAsStringsHandler h("314.159e-2"); + Reader reader; + EXPECT_TRUE(reader.Parse<kParseNumbersAsStringsFlag>(s, h)); + } +} + +template <unsigned extraFlags> +void TestTrailingCommas() { + { + StringStream s("[1,2,3,]"); + ParseArrayHandler<3> h; + Reader reader; + EXPECT_TRUE(reader.Parse<extraFlags|kParseTrailingCommasFlag>(s, h)); + EXPECT_EQ(5u, h.step_); + } + { + const char* json = "{ \"hello\" : \"world\", \"t\" : true , \"f\" : false," + "\"n\": null, \"i\":123, \"pi\": 3.1416, \"a\":[1, 2, 3],}"; + StringStream s(json); + ParseObjectHandler h; + Reader reader; + EXPECT_TRUE(reader.Parse<extraFlags|kParseTrailingCommasFlag>(s, h)); + EXPECT_EQ(20u, h.step_); + } + { + // whitespace around trailing commas + const char* json = "{ \"hello\" : \"world\", \"t\" : true , \"f\" : false," + "\"n\": null, \"i\":123, \"pi\": 3.1416, \"a\":[1, 2, 3\n,\n]\n,\n} "; + StringStream s(json); + ParseObjectHandler h; + Reader reader; + EXPECT_TRUE(reader.Parse<extraFlags|kParseTrailingCommasFlag>(s, h)); + EXPECT_EQ(20u, h.step_); + } + { + // comments around trailing commas + const char* json = "{ \"hello\" : \"world\", \"t\" : true , \"f\" : false, \"n\": null," + "\"i\":123, \"pi\": 3.1416, \"a\":[1, 2, 3/*test*/,/*test*/]/*test*/,/*test*/}"; + StringStream s(json); + ParseObjectHandler h; + Reader reader; + EXPECT_TRUE(reader.Parse<extraFlags|kParseTrailingCommasFlag|kParseCommentsFlag>(s, h)); + EXPECT_EQ(20u, h.step_); + } +} + +TEST(Reader, TrailingCommas) { + TestTrailingCommas<kParseNoFlags>(); +} + +TEST(Reader, TrailingCommasIterative) { + TestTrailingCommas<kParseIterativeFlag>(); +} + +template <unsigned extraFlags> +void TestMultipleTrailingCommaErrors() { + // only a single trailing comma is allowed. + { + StringStream s("[1,2,3,,]"); + ParseArrayHandler<3> h; + Reader reader; + ParseResult r = reader.Parse<extraFlags|kParseTrailingCommasFlag>(s, h); + EXPECT_TRUE(reader.HasParseError()); + EXPECT_EQ(kParseErrorValueInvalid, r.Code()); + EXPECT_EQ(7u, r.Offset()); + } + { + const char* json = "{ \"hello\" : \"world\", \"t\" : true , \"f\" : false," + "\"n\": null, \"i\":123, \"pi\": 3.1416, \"a\":[1, 2, 3,],,}"; + StringStream s(json); + ParseObjectHandler h; + Reader reader; + ParseResult r = reader.Parse<extraFlags|kParseTrailingCommasFlag>(s, h); + EXPECT_TRUE(reader.HasParseError()); + EXPECT_EQ(kParseErrorObjectMissName, r.Code()); + EXPECT_EQ(95u, r.Offset()); + } +} + +TEST(Reader, MultipleTrailingCommaErrors) { + TestMultipleTrailingCommaErrors<kParseNoFlags>(); +} + +TEST(Reader, MultipleTrailingCommaErrorsIterative) { + TestMultipleTrailingCommaErrors<kParseIterativeFlag>(); +} + +template <unsigned extraFlags> +void TestEmptyExceptForCommaErrors() { + // not allowed even with trailing commas enabled; the + // trailing comma must follow a value. + { + StringStream s("[,]"); + ParseArrayHandler<3> h; + Reader reader; + ParseResult r = reader.Parse<extraFlags|kParseTrailingCommasFlag>(s, h); + EXPECT_TRUE(reader.HasParseError()); + EXPECT_EQ(kParseErrorValueInvalid, r.Code()); + EXPECT_EQ(1u, r.Offset()); + } + { + StringStream s("{,}"); + ParseObjectHandler h; + Reader reader; + ParseResult r = reader.Parse<extraFlags|kParseTrailingCommasFlag>(s, h); + EXPECT_TRUE(reader.HasParseError()); + EXPECT_EQ(kParseErrorObjectMissName, r.Code()); + EXPECT_EQ(1u, r.Offset()); + } +} + +TEST(Reader, EmptyExceptForCommaErrors) { + TestEmptyExceptForCommaErrors<kParseNoFlags>(); +} + +TEST(Reader, EmptyExceptForCommaErrorsIterative) { + TestEmptyExceptForCommaErrors<kParseIterativeFlag>(); +} + +template <unsigned extraFlags> +void TestTrailingCommaHandlerTermination() { + { + HandlerTerminateAtEndArray h; + Reader reader; + StringStream s("[1,2,3,]"); + ParseResult r = reader.Parse<extraFlags|kParseTrailingCommasFlag>(s, h); + EXPECT_TRUE(reader.HasParseError()); + EXPECT_EQ(kParseErrorTermination, r.Code()); + EXPECT_EQ(7u, r.Offset()); + } + { + HandlerTerminateAtEndObject h; + Reader reader; + StringStream s("{\"t\": true, \"f\": false,}"); + ParseResult r = reader.Parse<extraFlags|kParseTrailingCommasFlag>(s, h); + EXPECT_TRUE(reader.HasParseError()); + EXPECT_EQ(kParseErrorTermination, r.Code()); + EXPECT_EQ(23u, r.Offset()); + } +} + +TEST(Reader, TrailingCommaHandlerTermination) { + TestTrailingCommaHandlerTermination<kParseNoFlags>(); +} + +TEST(Reader, TrailingCommaHandlerTerminationIterative) { + TestTrailingCommaHandlerTermination<kParseIterativeFlag>(); +} + +TEST(Reader, ParseNanAndInfinity) { +#define TEST_NAN_INF(str, x) \ + { \ + { \ + StringStream s(str); \ + ParseDoubleHandler h; \ + Reader reader; \ + ASSERT_EQ(kParseErrorNone, reader.Parse<kParseNanAndInfFlag>(s, h).Code()); \ + EXPECT_EQ(1u, h.step_); \ + internal::Double e(x), a(h.actual_); \ + EXPECT_EQ(e.IsNan(), a.IsNan()); \ + EXPECT_EQ(e.IsInf(), a.IsInf()); \ + if (!e.IsNan()) \ + EXPECT_EQ(e.Sign(), a.Sign()); \ + } \ + { \ + const char* json = "{ \"naninfdouble\": " str " } "; \ + StringStream s(json); \ + NumbersAsStringsHandler h(str); \ + Reader reader; \ + EXPECT_TRUE(reader.Parse<kParseNumbersAsStringsFlag|kParseNanAndInfFlag>(s, h)); \ + } \ + { \ + char* json = StrDup("{ \"naninfdouble\": " str " } "); \ + InsituStringStream s(json); \ + NumbersAsStringsHandler h(str); \ + Reader reader; \ + EXPECT_TRUE(reader.Parse<kParseInsituFlag|kParseNumbersAsStringsFlag|kParseNanAndInfFlag>(s, h)); \ + free(json); \ + } \ + } +#define TEST_NAN_INF_ERROR(errorCode, str, errorOffset) \ + { \ + int streamPos = errorOffset; \ + char buffer[1001]; \ + strncpy(buffer, str, 1000); \ + InsituStringStream s(buffer); \ + BaseReaderHandler<> h; \ + Reader reader; \ + EXPECT_FALSE(reader.Parse<kParseNanAndInfFlag>(s, h)); \ + EXPECT_EQ(errorCode, reader.GetParseErrorCode());\ + EXPECT_EQ(errorOffset, reader.GetErrorOffset());\ + EXPECT_EQ(streamPos, s.Tell());\ + } + + double nan = std::numeric_limits<double>::quiet_NaN(); + double inf = std::numeric_limits<double>::infinity(); + + TEST_NAN_INF("NaN", nan); + TEST_NAN_INF("-NaN", nan); + TEST_NAN_INF("Inf", inf); + TEST_NAN_INF("Infinity", inf); + TEST_NAN_INF("-Inf", -inf); + TEST_NAN_INF("-Infinity", -inf); + TEST_NAN_INF_ERROR(kParseErrorValueInvalid, "nan", 1); + TEST_NAN_INF_ERROR(kParseErrorValueInvalid, "-nan", 1); + TEST_NAN_INF_ERROR(kParseErrorValueInvalid, "NAN", 1); + TEST_NAN_INF_ERROR(kParseErrorValueInvalid, "-Infinty", 6); + +#undef TEST_NAN_INF_ERROR +#undef TEST_NAN_INF +} + +RAPIDJSON_DIAG_POP diff --git a/src/rapidjson/test/unittest/regextest.cpp b/src/rapidjson/test/unittest/regextest.cpp new file mode 100644 index 000000000..4fb5b222e --- /dev/null +++ b/src/rapidjson/test/unittest/regextest.cpp @@ -0,0 +1,592 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#include "unittest.h" +#include "rapidjson/internal/regex.h" + +using namespace rapidjson::internal; + +TEST(Regex, Single) { + Regex re("a"); + ASSERT_TRUE(re.IsValid()); + EXPECT_TRUE(re.Match("a")); + EXPECT_FALSE(re.Match("")); + EXPECT_FALSE(re.Match("b")); +} + +TEST(Regex, Concatenation) { + Regex re("abc"); + ASSERT_TRUE(re.IsValid()); + EXPECT_TRUE(re.Match("abc")); + EXPECT_FALSE(re.Match("")); + EXPECT_FALSE(re.Match("a")); + EXPECT_FALSE(re.Match("b")); + EXPECT_FALSE(re.Match("ab")); + EXPECT_FALSE(re.Match("abcd")); +} + +TEST(Regex, Alternation1) { + Regex re("abab|abbb"); + ASSERT_TRUE(re.IsValid()); + EXPECT_TRUE(re.Match("abab")); + EXPECT_TRUE(re.Match("abbb")); + EXPECT_FALSE(re.Match("")); + EXPECT_FALSE(re.Match("ab")); + EXPECT_FALSE(re.Match("ababa")); + EXPECT_FALSE(re.Match("abb")); + EXPECT_FALSE(re.Match("abbbb")); +} + +TEST(Regex, Alternation2) { + Regex re("a|b|c"); + ASSERT_TRUE(re.IsValid()); + EXPECT_TRUE(re.Match("a")); + EXPECT_TRUE(re.Match("b")); + EXPECT_TRUE(re.Match("c")); + EXPECT_FALSE(re.Match("")); + EXPECT_FALSE(re.Match("aa")); + EXPECT_FALSE(re.Match("ab")); +} + +TEST(Regex, Parenthesis1) { + Regex re("(ab)c"); + ASSERT_TRUE(re.IsValid()); + EXPECT_TRUE(re.Match("abc")); + EXPECT_FALSE(re.Match("")); + EXPECT_FALSE(re.Match("a")); + EXPECT_FALSE(re.Match("b")); + EXPECT_FALSE(re.Match("ab")); + EXPECT_FALSE(re.Match("abcd")); +} + +TEST(Regex, Parenthesis2) { + Regex re("a(bc)"); + ASSERT_TRUE(re.IsValid()); + EXPECT_TRUE(re.Match("abc")); + EXPECT_FALSE(re.Match("")); + EXPECT_FALSE(re.Match("a")); + EXPECT_FALSE(re.Match("b")); + EXPECT_FALSE(re.Match("ab")); + EXPECT_FALSE(re.Match("abcd")); +} + +TEST(Regex, Parenthesis3) { + Regex re("(a|b)(c|d)"); + ASSERT_TRUE(re.IsValid()); + EXPECT_TRUE(re.Match("ac")); + EXPECT_TRUE(re.Match("ad")); + EXPECT_TRUE(re.Match("bc")); + EXPECT_TRUE(re.Match("bd")); + EXPECT_FALSE(re.Match("")); + EXPECT_FALSE(re.Match("ab")); + EXPECT_FALSE(re.Match("cd")); +} + +TEST(Regex, ZeroOrOne1) { + Regex re("a?"); + ASSERT_TRUE(re.IsValid()); + EXPECT_TRUE(re.Match("")); + EXPECT_TRUE(re.Match("a")); + EXPECT_FALSE(re.Match("aa")); +} + +TEST(Regex, ZeroOrOne2) { + Regex re("a?b"); + ASSERT_TRUE(re.IsValid()); + EXPECT_TRUE(re.Match("b")); + EXPECT_TRUE(re.Match("ab")); + EXPECT_FALSE(re.Match("a")); + EXPECT_FALSE(re.Match("aa")); + EXPECT_FALSE(re.Match("bb")); + EXPECT_FALSE(re.Match("ba")); +} + +TEST(Regex, ZeroOrOne3) { + Regex re("ab?"); + ASSERT_TRUE(re.IsValid()); + EXPECT_TRUE(re.Match("a")); + EXPECT_TRUE(re.Match("ab")); + EXPECT_FALSE(re.Match("b")); + EXPECT_FALSE(re.Match("aa")); + EXPECT_FALSE(re.Match("bb")); + EXPECT_FALSE(re.Match("ba")); +} + +TEST(Regex, ZeroOrOne4) { + Regex re("a?b?"); + ASSERT_TRUE(re.IsValid()); + EXPECT_TRUE(re.Match("")); + EXPECT_TRUE(re.Match("a")); + EXPECT_TRUE(re.Match("b")); + EXPECT_TRUE(re.Match("ab")); + EXPECT_FALSE(re.Match("aa")); + EXPECT_FALSE(re.Match("bb")); + EXPECT_FALSE(re.Match("ba")); + EXPECT_FALSE(re.Match("abc")); +} + +TEST(Regex, ZeroOrOne5) { + Regex re("a(ab)?b"); + ASSERT_TRUE(re.IsValid()); + EXPECT_TRUE(re.Match("ab")); + EXPECT_TRUE(re.Match("aabb")); + EXPECT_FALSE(re.Match("aab")); + EXPECT_FALSE(re.Match("abb")); +} + +TEST(Regex, ZeroOrMore1) { + Regex re("a*"); + ASSERT_TRUE(re.IsValid()); + EXPECT_TRUE(re.Match("")); + EXPECT_TRUE(re.Match("a")); + EXPECT_TRUE(re.Match("aa")); + EXPECT_FALSE(re.Match("b")); + EXPECT_FALSE(re.Match("ab")); +} + +TEST(Regex, ZeroOrMore2) { + Regex re("a*b"); + ASSERT_TRUE(re.IsValid()); + EXPECT_TRUE(re.Match("b")); + EXPECT_TRUE(re.Match("ab")); + EXPECT_TRUE(re.Match("aab")); + EXPECT_FALSE(re.Match("")); + EXPECT_FALSE(re.Match("bb")); +} + +TEST(Regex, ZeroOrMore3) { + Regex re("a*b*"); + ASSERT_TRUE(re.IsValid()); + EXPECT_TRUE(re.Match("")); + EXPECT_TRUE(re.Match("a")); + EXPECT_TRUE(re.Match("aa")); + EXPECT_TRUE(re.Match("b")); + EXPECT_TRUE(re.Match("bb")); + EXPECT_TRUE(re.Match("ab")); + EXPECT_TRUE(re.Match("aabb")); + EXPECT_FALSE(re.Match("ba")); +} + +TEST(Regex, ZeroOrMore4) { + Regex re("a(ab)*b"); + ASSERT_TRUE(re.IsValid()); + EXPECT_TRUE(re.Match("ab")); + EXPECT_TRUE(re.Match("aabb")); + EXPECT_TRUE(re.Match("aababb")); + EXPECT_FALSE(re.Match("")); + EXPECT_FALSE(re.Match("aa")); +} + +TEST(Regex, OneOrMore1) { + Regex re("a+"); + ASSERT_TRUE(re.IsValid()); + EXPECT_TRUE(re.Match("a")); + EXPECT_TRUE(re.Match("aa")); + EXPECT_FALSE(re.Match("")); + EXPECT_FALSE(re.Match("b")); + EXPECT_FALSE(re.Match("ab")); +} + +TEST(Regex, OneOrMore2) { + Regex re("a+b"); + ASSERT_TRUE(re.IsValid()); + EXPECT_TRUE(re.Match("ab")); + EXPECT_TRUE(re.Match("aab")); + EXPECT_FALSE(re.Match("")); + EXPECT_FALSE(re.Match("b")); +} + +TEST(Regex, OneOrMore3) { + Regex re("a+b+"); + ASSERT_TRUE(re.IsValid()); + EXPECT_TRUE(re.Match("ab")); + EXPECT_TRUE(re.Match("aab")); + EXPECT_TRUE(re.Match("abb")); + EXPECT_TRUE(re.Match("aabb")); + EXPECT_FALSE(re.Match("")); + EXPECT_FALSE(re.Match("b")); + EXPECT_FALSE(re.Match("ba")); +} + +TEST(Regex, OneOrMore4) { + Regex re("a(ab)+b"); + ASSERT_TRUE(re.IsValid()); + EXPECT_TRUE(re.Match("aabb")); + EXPECT_TRUE(re.Match("aababb")); + EXPECT_FALSE(re.Match("")); + EXPECT_FALSE(re.Match("ab")); +} + +TEST(Regex, QuantifierExact1) { + Regex re("ab{3}c"); + ASSERT_TRUE(re.IsValid()); + EXPECT_TRUE(re.Match("abbbc")); + EXPECT_FALSE(re.Match("ac")); + EXPECT_FALSE(re.Match("abc")); + EXPECT_FALSE(re.Match("abbc")); + EXPECT_FALSE(re.Match("abbbbc")); +} + +TEST(Regex, QuantifierExact2) { + Regex re("a(bc){3}d"); + ASSERT_TRUE(re.IsValid()); + EXPECT_TRUE(re.Match("abcbcbcd")); + EXPECT_FALSE(re.Match("ad")); + EXPECT_FALSE(re.Match("abcd")); + EXPECT_FALSE(re.Match("abcbcd")); + EXPECT_FALSE(re.Match("abcbcbcbcd")); +} + +TEST(Regex, QuantifierExact3) { + Regex re("a(b|c){3}d"); + ASSERT_TRUE(re.IsValid()); + EXPECT_TRUE(re.Match("abbbd")); + EXPECT_TRUE(re.Match("acccd")); + EXPECT_TRUE(re.Match("abcbd")); + EXPECT_FALSE(re.Match("ad")); + EXPECT_FALSE(re.Match("abbd")); + EXPECT_FALSE(re.Match("accccd")); + EXPECT_FALSE(re.Match("abbbbd")); +} + +TEST(Regex, QuantifierMin1) { + Regex re("ab{3,}c"); + ASSERT_TRUE(re.IsValid()); + EXPECT_TRUE(re.Match("abbbc")); + EXPECT_TRUE(re.Match("abbbbc")); + EXPECT_TRUE(re.Match("abbbbbc")); + EXPECT_FALSE(re.Match("ac")); + EXPECT_FALSE(re.Match("abc")); + EXPECT_FALSE(re.Match("abbc")); +} + +TEST(Regex, QuantifierMin2) { + Regex re("a(bc){3,}d"); + ASSERT_TRUE(re.IsValid()); + EXPECT_TRUE(re.Match("abcbcbcd")); + EXPECT_TRUE(re.Match("abcbcbcbcd")); + EXPECT_FALSE(re.Match("ad")); + EXPECT_FALSE(re.Match("abcd")); + EXPECT_FALSE(re.Match("abcbcd")); +} + +TEST(Regex, QuantifierMin3) { + Regex re("a(b|c){3,}d"); + ASSERT_TRUE(re.IsValid()); + EXPECT_TRUE(re.Match("abbbd")); + EXPECT_TRUE(re.Match("acccd")); + EXPECT_TRUE(re.Match("abcbd")); + EXPECT_TRUE(re.Match("accccd")); + EXPECT_TRUE(re.Match("abbbbd")); + EXPECT_FALSE(re.Match("ad")); + EXPECT_FALSE(re.Match("abbd")); +} + +TEST(Regex, QuantifierMinMax1) { + Regex re("ab{3,5}c"); + ASSERT_TRUE(re.IsValid()); + EXPECT_TRUE(re.Match("abbbc")); + EXPECT_TRUE(re.Match("abbbbc")); + EXPECT_TRUE(re.Match("abbbbbc")); + EXPECT_FALSE(re.Match("ac")); + EXPECT_FALSE(re.Match("abc")); + EXPECT_FALSE(re.Match("abbc")); + EXPECT_FALSE(re.Match("abbbbbbc")); +} + +TEST(Regex, QuantifierMinMax2) { + Regex re("a(bc){3,5}d"); + ASSERT_TRUE(re.IsValid()); + EXPECT_TRUE(re.Match("abcbcbcd")); + EXPECT_TRUE(re.Match("abcbcbcbcd")); + EXPECT_TRUE(re.Match("abcbcbcbcbcd")); + EXPECT_FALSE(re.Match("ad")); + EXPECT_FALSE(re.Match("abcd")); + EXPECT_FALSE(re.Match("abcbcd")); + EXPECT_FALSE(re.Match("abcbcbcbcbcbcd")); +} + +TEST(Regex, QuantifierMinMax3) { + Regex re("a(b|c){3,5}d"); + ASSERT_TRUE(re.IsValid()); + EXPECT_TRUE(re.Match("abbbd")); + EXPECT_TRUE(re.Match("acccd")); + EXPECT_TRUE(re.Match("abcbd")); + EXPECT_TRUE(re.Match("accccd")); + EXPECT_TRUE(re.Match("abbbbd")); + EXPECT_TRUE(re.Match("acccccd")); + EXPECT_TRUE(re.Match("abbbbbd")); + EXPECT_FALSE(re.Match("ad")); + EXPECT_FALSE(re.Match("abbd")); + EXPECT_FALSE(re.Match("accccccd")); + EXPECT_FALSE(re.Match("abbbbbbd")); +} + +// Issue538 +TEST(Regex, QuantifierMinMax4) { + Regex re("a(b|c){0,3}d"); + ASSERT_TRUE(re.IsValid()); + EXPECT_TRUE(re.Match("ad")); + EXPECT_TRUE(re.Match("abd")); + EXPECT_TRUE(re.Match("acd")); + EXPECT_TRUE(re.Match("abbd")); + EXPECT_TRUE(re.Match("accd")); + EXPECT_TRUE(re.Match("abcd")); + EXPECT_TRUE(re.Match("abbbd")); + EXPECT_TRUE(re.Match("acccd")); + EXPECT_FALSE(re.Match("abbbbd")); + EXPECT_FALSE(re.Match("add")); + EXPECT_FALSE(re.Match("accccd")); + EXPECT_FALSE(re.Match("abcbcd")); +} + +// Issue538 +TEST(Regex, QuantifierMinMax5) { + Regex re("a(b|c){0,}d"); + ASSERT_TRUE(re.IsValid()); + EXPECT_TRUE(re.Match("ad")); + EXPECT_TRUE(re.Match("abd")); + EXPECT_TRUE(re.Match("acd")); + EXPECT_TRUE(re.Match("abbd")); + EXPECT_TRUE(re.Match("accd")); + EXPECT_TRUE(re.Match("abcd")); + EXPECT_TRUE(re.Match("abbbd")); + EXPECT_TRUE(re.Match("acccd")); + EXPECT_TRUE(re.Match("abbbbd")); + EXPECT_TRUE(re.Match("accccd")); + EXPECT_TRUE(re.Match("abcbcd")); + EXPECT_FALSE(re.Match("add")); + EXPECT_FALSE(re.Match("aad")); +} + +#define EURO "\xE2\x82\xAC" // "\xE2\x82\xAC" is UTF-8 sequence of Euro sign U+20AC + +TEST(Regex, Unicode) { + Regex re("a" EURO "+b"); + ASSERT_TRUE(re.IsValid()); + EXPECT_TRUE(re.Match("a" EURO "b")); + EXPECT_TRUE(re.Match("a" EURO EURO "b")); + EXPECT_FALSE(re.Match("a?b")); + EXPECT_FALSE(re.Match("a" EURO "\xAC" "b")); // unaware of UTF-8 will match +} + +TEST(Regex, AnyCharacter) { + Regex re("."); + ASSERT_TRUE(re.IsValid()); + EXPECT_TRUE(re.Match("a")); + EXPECT_TRUE(re.Match("b")); + EXPECT_TRUE(re.Match(EURO)); + EXPECT_FALSE(re.Match("")); + EXPECT_FALSE(re.Match("aa")); +} + +TEST(Regex, CharacterRange1) { + Regex re("[abc]"); + ASSERT_TRUE(re.IsValid()); + EXPECT_TRUE(re.Match("a")); + EXPECT_TRUE(re.Match("b")); + EXPECT_TRUE(re.Match("c")); + EXPECT_FALSE(re.Match("")); + EXPECT_FALSE(re.Match("`")); + EXPECT_FALSE(re.Match("d")); + EXPECT_FALSE(re.Match("aa")); +} + +TEST(Regex, CharacterRange2) { + Regex re("[^abc]"); + ASSERT_TRUE(re.IsValid()); + EXPECT_TRUE(re.Match("`")); + EXPECT_TRUE(re.Match("d")); + EXPECT_FALSE(re.Match("a")); + EXPECT_FALSE(re.Match("b")); + EXPECT_FALSE(re.Match("c")); + EXPECT_FALSE(re.Match("")); + EXPECT_FALSE(re.Match("aa")); +} + +TEST(Regex, CharacterRange3) { + Regex re("[a-c]"); + ASSERT_TRUE(re.IsValid()); + EXPECT_TRUE(re.Match("a")); + EXPECT_TRUE(re.Match("b")); + EXPECT_TRUE(re.Match("c")); + EXPECT_FALSE(re.Match("")); + EXPECT_FALSE(re.Match("`")); + EXPECT_FALSE(re.Match("d")); + EXPECT_FALSE(re.Match("aa")); +} + +TEST(Regex, CharacterRange4) { + Regex re("[^a-c]"); + ASSERT_TRUE(re.IsValid()); + EXPECT_TRUE(re.Match("`")); + EXPECT_TRUE(re.Match("d")); + EXPECT_FALSE(re.Match("a")); + EXPECT_FALSE(re.Match("b")); + EXPECT_FALSE(re.Match("c")); + EXPECT_FALSE(re.Match("")); + EXPECT_FALSE(re.Match("aa")); +} + +TEST(Regex, CharacterRange5) { + Regex re("[-]"); + ASSERT_TRUE(re.IsValid()); + EXPECT_TRUE(re.Match("-")); + EXPECT_FALSE(re.Match("")); + EXPECT_FALSE(re.Match("a")); +} + +TEST(Regex, CharacterRange6) { + Regex re("[a-]"); + ASSERT_TRUE(re.IsValid()); + EXPECT_TRUE(re.Match("a")); + EXPECT_TRUE(re.Match("-")); + EXPECT_FALSE(re.Match("")); + EXPECT_FALSE(re.Match("`")); + EXPECT_FALSE(re.Match("b")); +} + +TEST(Regex, CharacterRange7) { + Regex re("[-a]"); + ASSERT_TRUE(re.IsValid()); + EXPECT_TRUE(re.Match("a")); + EXPECT_TRUE(re.Match("-")); + EXPECT_FALSE(re.Match("")); + EXPECT_FALSE(re.Match("`")); + EXPECT_FALSE(re.Match("b")); +} + +TEST(Regex, CharacterRange8) { + Regex re("[a-zA-Z0-9]*"); + ASSERT_TRUE(re.IsValid()); + EXPECT_TRUE(re.Match("Milo")); + EXPECT_TRUE(re.Match("MT19937")); + EXPECT_TRUE(re.Match("43")); + EXPECT_FALSE(re.Match("a_b")); + EXPECT_FALSE(re.Match("!")); +} + +TEST(Regex, Search) { + Regex re("abc"); + ASSERT_TRUE(re.IsValid()); + EXPECT_TRUE(re.Search("abc")); + EXPECT_TRUE(re.Search("_abc")); + EXPECT_TRUE(re.Search("abc_")); + EXPECT_TRUE(re.Search("_abc_")); + EXPECT_TRUE(re.Search("__abc__")); + EXPECT_TRUE(re.Search("abcabc")); + EXPECT_FALSE(re.Search("a")); + EXPECT_FALSE(re.Search("ab")); + EXPECT_FALSE(re.Search("bc")); + EXPECT_FALSE(re.Search("cba")); +} + +TEST(Regex, Search_BeginAnchor) { + Regex re("^abc"); + ASSERT_TRUE(re.IsValid()); + EXPECT_TRUE(re.Search("abc")); + EXPECT_TRUE(re.Search("abc_")); + EXPECT_TRUE(re.Search("abcabc")); + EXPECT_FALSE(re.Search("_abc")); + EXPECT_FALSE(re.Search("_abc_")); + EXPECT_FALSE(re.Search("a")); + EXPECT_FALSE(re.Search("ab")); + EXPECT_FALSE(re.Search("bc")); + EXPECT_FALSE(re.Search("cba")); +} + +TEST(Regex, Search_EndAnchor) { + Regex re("abc$"); + ASSERT_TRUE(re.IsValid()); + EXPECT_TRUE(re.Search("abc")); + EXPECT_TRUE(re.Search("_abc")); + EXPECT_TRUE(re.Search("abcabc")); + EXPECT_FALSE(re.Search("abc_")); + EXPECT_FALSE(re.Search("_abc_")); + EXPECT_FALSE(re.Search("a")); + EXPECT_FALSE(re.Search("ab")); + EXPECT_FALSE(re.Search("bc")); + EXPECT_FALSE(re.Search("cba")); +} + +TEST(Regex, Search_BothAnchor) { + Regex re("^abc$"); + ASSERT_TRUE(re.IsValid()); + EXPECT_TRUE(re.Search("abc")); + EXPECT_FALSE(re.Search("")); + EXPECT_FALSE(re.Search("a")); + EXPECT_FALSE(re.Search("b")); + EXPECT_FALSE(re.Search("ab")); + EXPECT_FALSE(re.Search("abcd")); +} + +TEST(Regex, Escape) { + const char* s = "\\^\\$\\|\\(\\)\\?\\*\\+\\.\\[\\]\\{\\}\\\\\\f\\n\\r\\t\\v[\\b][\\[][\\]]"; + Regex re(s); + ASSERT_TRUE(re.IsValid()); + EXPECT_TRUE(re.Match("^$|()?*+.[]{}\\\x0C\n\r\t\x0B\b[]")); + EXPECT_FALSE(re.Match(s)); // Not escaping +} + +TEST(Regex, Invalid) { +#define TEST_INVALID(s) \ + {\ + Regex re(s);\ + EXPECT_FALSE(re.IsValid());\ + } + + TEST_INVALID(""); + TEST_INVALID("a|"); + TEST_INVALID("()"); + TEST_INVALID(")"); + TEST_INVALID("(a))"); + TEST_INVALID("(a|)"); + TEST_INVALID("(a||b)"); + TEST_INVALID("(|b)"); + TEST_INVALID("?"); + TEST_INVALID("*"); + TEST_INVALID("+"); + TEST_INVALID("{"); + TEST_INVALID("{}"); + TEST_INVALID("a{a}"); + TEST_INVALID("a{0}"); + TEST_INVALID("a{-1}"); + TEST_INVALID("a{}"); + // TEST_INVALID("a{0,}"); // Support now + TEST_INVALID("a{,0}"); + TEST_INVALID("a{1,0}"); + TEST_INVALID("a{-1,0}"); + TEST_INVALID("a{-1,1}"); + TEST_INVALID("a{4294967296}"); // overflow of unsigned + TEST_INVALID("a{1a}"); + TEST_INVALID("["); + TEST_INVALID("[]"); + TEST_INVALID("[^]"); + TEST_INVALID("[\\a]"); + TEST_INVALID("\\a"); + +#undef TEST_INVALID +} + +TEST(Regex, Issue538) { + Regex re("^[0-9]+(\\\\.[0-9]+){0,2}"); + EXPECT_TRUE(re.IsValid()); +} + +TEST(Regex, Issue583) { + Regex re("[0-9]{99999}"); + ASSERT_TRUE(re.IsValid()); +} + +#undef EURO diff --git a/src/rapidjson/test/unittest/schematest.cpp b/src/rapidjson/test/unittest/schematest.cpp new file mode 100644 index 000000000..d75b1e593 --- /dev/null +++ b/src/rapidjson/test/unittest/schematest.cpp @@ -0,0 +1,1313 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#include "unittest.h" +#include "rapidjson/schema.h" +#include "rapidjson/stringbuffer.h" +#include "rapidjson/writer.h" + +#ifdef __clang__ +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(variadic-macros) +#endif + +using namespace rapidjson; + +#define TEST_HASHER(json1, json2, expected) \ +{\ + Document d1, d2;\ + d1.Parse(json1);\ + ASSERT_FALSE(d1.HasParseError());\ + d2.Parse(json2);\ + ASSERT_FALSE(d2.HasParseError());\ + internal::Hasher<Value, CrtAllocator> h1, h2;\ + d1.Accept(h1);\ + d2.Accept(h2);\ + ASSERT_TRUE(h1.IsValid());\ + ASSERT_TRUE(h2.IsValid());\ + /*printf("%s: 0x%016llx\n%s: 0x%016llx\n\n", json1, h1.GetHashCode(), json2, h2.GetHashCode());*/\ + EXPECT_TRUE(expected == (h1.GetHashCode() == h2.GetHashCode()));\ +} + +TEST(SchemaValidator, Hasher) { + TEST_HASHER("null", "null", true); + + TEST_HASHER("true", "true", true); + TEST_HASHER("false", "false", true); + TEST_HASHER("true", "false", false); + TEST_HASHER("false", "true", false); + TEST_HASHER("true", "null", false); + TEST_HASHER("false", "null", false); + + TEST_HASHER("1", "1", true); + TEST_HASHER("2147483648", "2147483648", true); // 2^31 can only be fit in unsigned + TEST_HASHER("-2147483649", "-2147483649", true); // -2^31 - 1 can only be fit in int64_t + TEST_HASHER("2147483648", "2147483648", true); // 2^31 can only be fit in unsigned + TEST_HASHER("4294967296", "4294967296", true); // 2^32 can only be fit in int64_t + TEST_HASHER("9223372036854775808", "9223372036854775808", true); // 2^63 can only be fit in uint64_t + TEST_HASHER("1.5", "1.5", true); + TEST_HASHER("1", "1.0", true); + TEST_HASHER("1", "-1", false); + TEST_HASHER("0.0", "-0.0", false); + TEST_HASHER("1", "true", false); + TEST_HASHER("0", "false", false); + TEST_HASHER("0", "null", false); + + TEST_HASHER("\"\"", "\"\"", true); + TEST_HASHER("\"\"", "\"\\u0000\"", false); + TEST_HASHER("\"Hello\"", "\"Hello\"", true); + TEST_HASHER("\"Hello\"", "\"World\"", false); + TEST_HASHER("\"Hello\"", "null", false); + TEST_HASHER("\"Hello\\u0000\"", "\"Hello\"", false); + TEST_HASHER("\"\"", "null", false); + TEST_HASHER("\"\"", "true", false); + TEST_HASHER("\"\"", "false", false); + + TEST_HASHER("[]", "[ ]", true); + TEST_HASHER("[1, true, false]", "[1, true, false]", true); + TEST_HASHER("[1, true, false]", "[1, true]", false); + TEST_HASHER("[1, 2]", "[2, 1]", false); + TEST_HASHER("[[1], 2]", "[[1, 2]]", false); + TEST_HASHER("[1, 2]", "[1, [2]]", false); + TEST_HASHER("[]", "null", false); + TEST_HASHER("[]", "true", false); + TEST_HASHER("[]", "false", false); + TEST_HASHER("[]", "0", false); + TEST_HASHER("[]", "0.0", false); + TEST_HASHER("[]", "\"\"", false); + + TEST_HASHER("{}", "{ }", true); + TEST_HASHER("{\"a\":1}", "{\"a\":1}", true); + TEST_HASHER("{\"a\":1}", "{\"b\":1}", false); + TEST_HASHER("{\"a\":1}", "{\"a\":2}", false); + TEST_HASHER("{\"a\":1, \"b\":2}", "{\"b\":2, \"a\":1}", true); // Member order insensitive + TEST_HASHER("{}", "null", false); + TEST_HASHER("{}", "false", false); + TEST_HASHER("{}", "true", false); + TEST_HASHER("{}", "0", false); + TEST_HASHER("{}", "0.0", false); + TEST_HASHER("{}", "\"\"", false); +} + +// Test cases following http://spacetelescope.github.io/understanding-json-schema + +#define VALIDATE(schema, json, expected) \ +{\ + SchemaValidator validator(schema);\ + Document d;\ + /*printf("\n%s\n", json);*/\ + d.Parse(json);\ + EXPECT_FALSE(d.HasParseError());\ + EXPECT_TRUE(expected == d.Accept(validator));\ + EXPECT_TRUE(expected == validator.IsValid());\ + if ((expected) && !validator.IsValid()) {\ + StringBuffer sb;\ + validator.GetInvalidSchemaPointer().StringifyUriFragment(sb);\ + printf("Invalid schema: %s\n", sb.GetString());\ + printf("Invalid keyword: %s\n", validator.GetInvalidSchemaKeyword());\ + sb.Clear();\ + validator.GetInvalidDocumentPointer().StringifyUriFragment(sb);\ + printf("Invalid document: %s\n", sb.GetString());\ + }\ +} + +#define INVALIDATE(schema, json, invalidSchemaPointer, invalidSchemaKeyword, invalidDocumentPointer) \ +{\ + SchemaValidator validator(schema);\ + Document d;\ + /*printf("\n%s\n", json);*/\ + d.Parse(json);\ + EXPECT_FALSE(d.HasParseError());\ + EXPECT_FALSE(d.Accept(validator));\ + EXPECT_FALSE(validator.IsValid());\ + if (validator.GetInvalidSchemaPointer() != Pointer(invalidSchemaPointer)) {\ + StringBuffer sb;\ + validator.GetInvalidSchemaPointer().Stringify(sb);\ + printf("GetInvalidSchemaPointer() Expected: %s Actual: %s\n", invalidSchemaPointer, sb.GetString());\ + ADD_FAILURE();\ + }\ + ASSERT_TRUE(validator.GetInvalidSchemaKeyword() != 0);\ + if (strcmp(validator.GetInvalidSchemaKeyword(), invalidSchemaKeyword) != 0) {\ + printf("GetInvalidSchemaKeyword() Expected: %s Actual %s\n", invalidSchemaKeyword, validator.GetInvalidSchemaKeyword());\ + ADD_FAILURE();\ + }\ + if (validator.GetInvalidDocumentPointer() != Pointer(invalidDocumentPointer)) {\ + StringBuffer sb;\ + validator.GetInvalidDocumentPointer().Stringify(sb);\ + printf("GetInvalidDocumentPointer() Expected: %s Actual: %s\n", invalidDocumentPointer, sb.GetString());\ + ADD_FAILURE();\ + }\ +} + +TEST(SchemaValidator, Typeless) { + Document sd; + sd.Parse("{}"); + SchemaDocument s(sd); + + VALIDATE(s, "42", true); + VALIDATE(s, "\"I'm a string\"", true); + VALIDATE(s, "{ \"an\": [ \"arbitrarily\", \"nested\" ], \"data\": \"structure\" }", true); +} + +TEST(SchemaValidator, MultiType) { + Document sd; + sd.Parse("{ \"type\": [\"number\", \"string\"] }"); + SchemaDocument s(sd); + + VALIDATE(s, "42", true); + VALIDATE(s, "\"Life, the universe, and everything\"", true); + INVALIDATE(s, "[\"Life\", \"the universe\", \"and everything\"]", "", "type", ""); +} + +TEST(SchemaValidator, Enum_Typed) { + Document sd; + sd.Parse("{ \"type\": \"string\", \"enum\" : [\"red\", \"amber\", \"green\"] }"); + SchemaDocument s(sd); + + VALIDATE(s, "\"red\"", true); + INVALIDATE(s, "\"blue\"", "", "enum", ""); +} + +TEST(SchemaValidator, Enum_Typless) { + Document sd; + sd.Parse("{ \"enum\": [\"red\", \"amber\", \"green\", null, 42] }"); + SchemaDocument s(sd); + + VALIDATE(s, "\"red\"", true); + VALIDATE(s, "null", true); + VALIDATE(s, "42", true); + INVALIDATE(s, "0", "", "enum", ""); +} + +TEST(SchemaValidator, Enum_InvalidType) { + Document sd; + sd.Parse("{ \"type\": \"string\", \"enum\": [\"red\", \"amber\", \"green\", null] }"); + SchemaDocument s(sd); + + VALIDATE(s, "\"red\"", true); + INVALIDATE(s, "null", "", "type", ""); +} + +TEST(SchemaValidator, AllOf) { + { + Document sd; + sd.Parse("{\"allOf\": [{ \"type\": \"string\" }, { \"type\": \"string\", \"maxLength\": 5 }]}"); + SchemaDocument s(sd); + + VALIDATE(s, "\"ok\"", true); + INVALIDATE(s, "\"too long\"", "", "allOf", ""); + } + { + Document sd; + sd.Parse("{\"allOf\": [{ \"type\": \"string\" }, { \"type\": \"number\" } ] }"); + SchemaDocument s(sd); + + VALIDATE(s, "\"No way\"", false); + INVALIDATE(s, "-1", "", "allOf", ""); + } +} + +TEST(SchemaValidator, AnyOf) { + Document sd; + sd.Parse("{\"anyOf\": [{ \"type\": \"string\" }, { \"type\": \"number\" } ] }"); + SchemaDocument s(sd); + + VALIDATE(s, "\"Yes\"", true); + VALIDATE(s, "42", true); + INVALIDATE(s, "{ \"Not a\": \"string or number\" }", "", "anyOf", ""); +} + +TEST(SchemaValidator, OneOf) { + Document sd; + sd.Parse("{\"oneOf\": [{ \"type\": \"number\", \"multipleOf\": 5 }, { \"type\": \"number\", \"multipleOf\": 3 } ] }"); + SchemaDocument s(sd); + + VALIDATE(s, "10", true); + VALIDATE(s, "9", true); + INVALIDATE(s, "2", "", "oneOf", ""); + INVALIDATE(s, "15", "", "oneOf", ""); +} + +TEST(SchemaValidator, Not) { + Document sd; + sd.Parse("{\"not\":{ \"type\": \"string\"}}"); + SchemaDocument s(sd); + + VALIDATE(s, "42", true); + VALIDATE(s, "{ \"key\": \"value\" }", true); + INVALIDATE(s, "\"I am a string\"", "", "not", ""); +} + +TEST(SchemaValidator, Ref) { + Document sd; + sd.Parse( + "{" + " \"$schema\": \"http://json-schema.org/draft-04/schema#\"," + "" + " \"definitions\": {" + " \"address\": {" + " \"type\": \"object\"," + " \"properties\": {" + " \"street_address\": { \"type\": \"string\" }," + " \"city\": { \"type\": \"string\" }," + " \"state\": { \"type\": \"string\" }" + " }," + " \"required\": [\"street_address\", \"city\", \"state\"]" + " }" + " }," + " \"type\": \"object\"," + " \"properties\": {" + " \"billing_address\": { \"$ref\": \"#/definitions/address\" }," + " \"shipping_address\": { \"$ref\": \"#/definitions/address\" }" + " }" + "}"); + SchemaDocument s(sd); + + VALIDATE(s, "{\"shipping_address\": {\"street_address\": \"1600 Pennsylvania Avenue NW\", \"city\": \"Washington\", \"state\": \"DC\"}, \"billing_address\": {\"street_address\": \"1st Street SE\", \"city\": \"Washington\", \"state\": \"DC\"} }", true); +} + +TEST(SchemaValidator, Ref_AllOf) { + Document sd; + sd.Parse( + "{" + " \"$schema\": \"http://json-schema.org/draft-04/schema#\"," + "" + " \"definitions\": {" + " \"address\": {" + " \"type\": \"object\"," + " \"properties\": {" + " \"street_address\": { \"type\": \"string\" }," + " \"city\": { \"type\": \"string\" }," + " \"state\": { \"type\": \"string\" }" + " }," + " \"required\": [\"street_address\", \"city\", \"state\"]" + " }" + " }," + " \"type\": \"object\"," + " \"properties\": {" + " \"billing_address\": { \"$ref\": \"#/definitions/address\" }," + " \"shipping_address\": {" + " \"allOf\": [" + " { \"$ref\": \"#/definitions/address\" }," + " { \"properties\":" + " { \"type\": { \"enum\": [ \"residential\", \"business\" ] } }," + " \"required\": [\"type\"]" + " }" + " ]" + " }" + " }" + "}"); + SchemaDocument s(sd); + + INVALIDATE(s, "{\"shipping_address\": {\"street_address\": \"1600 Pennsylvania Avenue NW\", \"city\": \"Washington\", \"state\": \"DC\"} }", "/properties/shipping_address", "allOf", "/shipping_address"); + VALIDATE(s, "{\"shipping_address\": {\"street_address\": \"1600 Pennsylvania Avenue NW\", \"city\": \"Washington\", \"state\": \"DC\", \"type\": \"business\"} }", true); +} + +TEST(SchemaValidator, String) { + Document sd; + sd.Parse("{\"type\":\"string\"}"); + SchemaDocument s(sd); + + VALIDATE(s, "\"I'm a string\"", true); + INVALIDATE(s, "42", "", "type", ""); + INVALIDATE(s, "2147483648", "", "type", ""); // 2^31 can only be fit in unsigned + INVALIDATE(s, "-2147483649", "", "type", ""); // -2^31 - 1 can only be fit in int64_t + INVALIDATE(s, "4294967296", "", "type", ""); // 2^32 can only be fit in int64_t + INVALIDATE(s, "3.1415926", "", "type", ""); +} + +TEST(SchemaValidator, String_LengthRange) { + Document sd; + sd.Parse("{\"type\":\"string\",\"minLength\":2,\"maxLength\":3}"); + SchemaDocument s(sd); + + INVALIDATE(s, "\"A\"", "", "minLength", ""); + VALIDATE(s, "\"AB\"", true); + VALIDATE(s, "\"ABC\"", true); + INVALIDATE(s, "\"ABCD\"", "", "maxLength", ""); +} + +#if RAPIDJSON_SCHEMA_HAS_REGEX +TEST(SchemaValidator, String_Pattern) { + Document sd; + sd.Parse("{\"type\":\"string\",\"pattern\":\"^(\\\\([0-9]{3}\\\\))?[0-9]{3}-[0-9]{4}$\"}"); + SchemaDocument s(sd); + + VALIDATE(s, "\"555-1212\"", true); + VALIDATE(s, "\"(888)555-1212\"", true); + INVALIDATE(s, "\"(888)555-1212 ext. 532\"", "", "pattern", ""); + INVALIDATE(s, "\"(800)FLOWERS\"", "", "pattern", ""); +} + +TEST(SchemaValidator, String_Pattern_Invalid) { + Document sd; + sd.Parse("{\"type\":\"string\",\"pattern\":\"a{0}\"}"); // TODO: report regex is invalid somehow + SchemaDocument s(sd); + + VALIDATE(s, "\"\"", true); + VALIDATE(s, "\"a\"", true); + VALIDATE(s, "\"aa\"", true); +} +#endif + +TEST(SchemaValidator, Integer) { + Document sd; + sd.Parse("{\"type\":\"integer\"}"); + SchemaDocument s(sd); + + VALIDATE(s, "42", true); + VALIDATE(s, "-1", true); + VALIDATE(s, "2147483648", true); // 2^31 can only be fit in unsigned + VALIDATE(s, "-2147483649", true); // -2^31 - 1 can only be fit in int64_t + VALIDATE(s, "2147483648", true); // 2^31 can only be fit in unsigned + VALIDATE(s, "4294967296", true); // 2^32 can only be fit in int64_t + INVALIDATE(s, "3.1415926", "", "type", ""); + INVALIDATE(s, "\"42\"", "", "type", ""); +} + +TEST(SchemaValidator, Integer_Range) { + Document sd; + sd.Parse("{\"type\":\"integer\",\"minimum\":0,\"maximum\":100,\"exclusiveMaximum\":true}"); + SchemaDocument s(sd); + + INVALIDATE(s, "-1", "", "minimum", ""); + VALIDATE(s, "0", true); + VALIDATE(s, "10", true); + VALIDATE(s, "99", true); + INVALIDATE(s, "100", "", "maximum", ""); + INVALIDATE(s, "101", "", "maximum", ""); +} + +TEST(SchemaValidator, Integer_Range64Boundary) { + Document sd; + sd.Parse("{\"type\":\"integer\",\"minimum\":-9223372036854775807,\"maximum\":9223372036854775806}"); + SchemaDocument s(sd); + + INVALIDATE(s, "-9223372036854775808", "", "minimum", ""); + VALIDATE(s, "-9223372036854775807", true); + VALIDATE(s, "-2147483648", true); // int min + VALIDATE(s, "0", true); + VALIDATE(s, "2147483647", true); // int max + VALIDATE(s, "2147483648", true); // unsigned first + VALIDATE(s, "4294967295", true); // unsigned max + VALIDATE(s, "9223372036854775806", true); + INVALIDATE(s, "9223372036854775807", "", "maximum", ""); + INVALIDATE(s, "18446744073709551615", "", "maximum", ""); // uint64_t max +} + +TEST(SchemaValidator, Integer_RangeU64Boundary) { + Document sd; + sd.Parse("{\"type\":\"integer\",\"minimum\":9223372036854775808,\"maximum\":18446744073709551614}"); + SchemaDocument s(sd); + + INVALIDATE(s, "-9223372036854775808", "", "minimum", ""); + INVALIDATE(s, "9223372036854775807", "", "minimum", ""); + INVALIDATE(s, "-2147483648", "", "minimum", ""); // int min + INVALIDATE(s, "0", "", "minimum", ""); + INVALIDATE(s, "2147483647", "", "minimum", ""); // int max + INVALIDATE(s, "2147483648", "", "minimum", ""); // unsigned first + INVALIDATE(s, "4294967295", "", "minimum", ""); // unsigned max + VALIDATE(s, "9223372036854775808", true); + VALIDATE(s, "18446744073709551614", true); + INVALIDATE(s, "18446744073709551615", "", "maximum", ""); +} + +TEST(SchemaValidator, Integer_Range64BoundaryExclusive) { + Document sd; + sd.Parse("{\"type\":\"integer\",\"minimum\":-9223372036854775808,\"maximum\":18446744073709551615,\"exclusiveMinimum\":true,\"exclusiveMaximum\":true}"); + SchemaDocument s(sd); + + INVALIDATE(s, "-9223372036854775808", "", "minimum", ""); + VALIDATE(s, "-9223372036854775807", true); + VALIDATE(s, "18446744073709551614", true); + INVALIDATE(s, "18446744073709551615", "", "maximum", ""); +} + +TEST(SchemaValidator, Integer_MultipleOf) { + Document sd; + sd.Parse("{\"type\":\"integer\",\"multipleOf\":10}"); + SchemaDocument s(sd); + + VALIDATE(s, "0", true); + VALIDATE(s, "10", true); + VALIDATE(s, "-10", true); + VALIDATE(s, "20", true); + INVALIDATE(s, "23", "", "multipleOf", ""); + INVALIDATE(s, "-23", "", "multipleOf", ""); +} + +TEST(SchemaValidator, Integer_MultipleOf64Boundary) { + Document sd; + sd.Parse("{\"type\":\"integer\",\"multipleOf\":18446744073709551615}"); + SchemaDocument s(sd); + + VALIDATE(s, "0", true); + VALIDATE(s, "18446744073709551615", true); + INVALIDATE(s, "18446744073709551614", "", "multipleOf", ""); +} + +TEST(SchemaValidator, Number_Range) { + Document sd; + sd.Parse("{\"type\":\"number\",\"minimum\":0,\"maximum\":100,\"exclusiveMaximum\":true}"); + SchemaDocument s(sd); + + INVALIDATE(s, "-1", "", "minimum", ""); + VALIDATE(s, "0", true); + VALIDATE(s, "0.1", true); + VALIDATE(s, "10", true); + VALIDATE(s, "99", true); + VALIDATE(s, "99.9", true); + INVALIDATE(s, "100", "", "maximum", ""); + INVALIDATE(s, "100.0", "", "maximum", ""); + INVALIDATE(s, "101.5", "", "maximum", ""); +} + +TEST(SchemaValidator, Number_RangeInt) { + Document sd; + sd.Parse("{\"type\":\"number\",\"minimum\":-100,\"maximum\":-1,\"exclusiveMaximum\":true}"); + SchemaDocument s(sd); + + INVALIDATE(s, "-101", "", "minimum", ""); + INVALIDATE(s, "-100.1", "", "minimum", ""); + VALIDATE(s, "-100", true); + VALIDATE(s, "-2", true); + INVALIDATE(s, "-1", "", "maximum", ""); + INVALIDATE(s, "-0.9", "", "maximum", ""); + INVALIDATE(s, "0", "", "maximum", ""); + INVALIDATE(s, "2147483647", "", "maximum", ""); // int max + INVALIDATE(s, "2147483648", "", "maximum", ""); // unsigned first + INVALIDATE(s, "4294967295", "", "maximum", ""); // unsigned max + INVALIDATE(s, "9223372036854775808", "", "maximum", ""); + INVALIDATE(s, "18446744073709551614", "", "maximum", ""); + INVALIDATE(s, "18446744073709551615", "", "maximum", ""); +} + +TEST(SchemaValidator, Number_RangeDouble) { + Document sd; + sd.Parse("{\"type\":\"number\",\"minimum\":0.1,\"maximum\":100.1,\"exclusiveMaximum\":true}"); + SchemaDocument s(sd); + + INVALIDATE(s, "-9223372036854775808", "", "minimum", ""); + INVALIDATE(s, "-2147483648", "", "minimum", ""); // int min + INVALIDATE(s, "-1", "", "minimum", ""); + VALIDATE(s, "0.1", true); + VALIDATE(s, "10", true); + VALIDATE(s, "99", true); + VALIDATE(s, "100", true); + INVALIDATE(s, "101", "", "maximum", ""); + INVALIDATE(s, "101.5", "", "maximum", ""); + INVALIDATE(s, "18446744073709551614", "", "maximum", ""); + INVALIDATE(s, "18446744073709551615", "", "maximum", ""); + INVALIDATE(s, "2147483647", "", "maximum", ""); // int max + INVALIDATE(s, "2147483648", "", "maximum", ""); // unsigned first + INVALIDATE(s, "4294967295", "", "maximum", ""); // unsigned max + INVALIDATE(s, "9223372036854775808", "", "maximum", ""); + INVALIDATE(s, "18446744073709551614", "", "maximum", ""); + INVALIDATE(s, "18446744073709551615", "", "maximum", ""); +} + +TEST(SchemaValidator, Number_RangeDoubleU64Boundary) { + Document sd; + sd.Parse("{\"type\":\"number\",\"minimum\":9223372036854775808.0,\"maximum\":18446744073709550000.0}"); + SchemaDocument s(sd); + + INVALIDATE(s, "-9223372036854775808", "", "minimum", ""); + INVALIDATE(s, "-2147483648", "", "minimum", ""); // int min + INVALIDATE(s, "0", "", "minimum", ""); + INVALIDATE(s, "2147483647", "", "minimum", ""); // int max + INVALIDATE(s, "2147483648", "", "minimum", ""); // unsigned first + INVALIDATE(s, "4294967295", "", "minimum", ""); // unsigned max + VALIDATE(s, "9223372036854775808", true); + VALIDATE(s, "18446744073709540000", true); + INVALIDATE(s, "18446744073709551615", "", "maximum", ""); +} + +TEST(SchemaValidator, Number_MultipleOf) { + Document sd; + sd.Parse("{\"type\":\"number\",\"multipleOf\":10.0}"); + SchemaDocument s(sd); + + VALIDATE(s, "0", true); + VALIDATE(s, "10", true); + VALIDATE(s, "-10", true); + VALIDATE(s, "20", true); + INVALIDATE(s, "23", "", "multipleOf", ""); + INVALIDATE(s, "-2147483648", "", "multipleOf", ""); // int min + VALIDATE(s, "-2147483640", true); + INVALIDATE(s, "2147483647", "", "multipleOf", ""); // int max + INVALIDATE(s, "2147483648", "", "multipleOf", ""); // unsigned first + VALIDATE(s, "2147483650", true); + INVALIDATE(s, "4294967295", "", "multipleOf", ""); // unsigned max + VALIDATE(s, "4294967300", true); +} + +TEST(SchemaValidator, Number_MultipleOfOne) { + Document sd; + sd.Parse("{\"type\":\"number\",\"multipleOf\":1}"); + SchemaDocument s(sd); + + VALIDATE(s, "42", true); + VALIDATE(s, "42.0", true); + INVALIDATE(s, "3.1415926", "", "multipleOf", ""); +} + +TEST(SchemaValidator, Object) { + Document sd; + sd.Parse("{\"type\":\"object\"}"); + SchemaDocument s(sd); + + VALIDATE(s, "{\"key\":\"value\",\"another_key\":\"another_value\"}", true); + VALIDATE(s, "{\"Sun\":1.9891e30,\"Jupiter\":1.8986e27,\"Saturn\":5.6846e26,\"Neptune\":10.243e25,\"Uranus\":8.6810e25,\"Earth\":5.9736e24,\"Venus\":4.8685e24,\"Mars\":6.4185e23,\"Mercury\":3.3022e23,\"Moon\":7.349e22,\"Pluto\":1.25e22}", true); + INVALIDATE(s, "[\"An\", \"array\", \"not\", \"an\", \"object\"]", "", "type", ""); + INVALIDATE(s, "\"Not an object\"", "", "type", ""); +} + +TEST(SchemaValidator, Object_Properties) { + Document sd; + sd.Parse( + "{" + " \"type\": \"object\"," + " \"properties\" : {" + " \"number\": { \"type\": \"number\" }," + " \"street_name\" : { \"type\": \"string\" }," + " \"street_type\" : { \"type\": \"string\", \"enum\" : [\"Street\", \"Avenue\", \"Boulevard\"] }" + " }" + "}"); + + SchemaDocument s(sd); + + VALIDATE(s, "{ \"number\": 1600, \"street_name\": \"Pennsylvania\", \"street_type\": \"Avenue\" }", true); + INVALIDATE(s, "{ \"number\": \"1600\", \"street_name\": \"Pennsylvania\", \"street_type\": \"Avenue\" }", "/properties/number", "type", "/number"); + VALIDATE(s, "{ \"number\": 1600, \"street_name\": \"Pennsylvania\" }", true); + VALIDATE(s, "{}", true); + VALIDATE(s, "{ \"number\": 1600, \"street_name\": \"Pennsylvania\", \"street_type\": \"Avenue\", \"direction\": \"NW\" }", true); +} + +TEST(SchemaValidator, Object_AdditionalPropertiesBoolean) { + Document sd; + sd.Parse( + "{" + " \"type\": \"object\"," + " \"properties\" : {" + " \"number\": { \"type\": \"number\" }," + " \"street_name\" : { \"type\": \"string\" }," + " \"street_type\" : { \"type\": \"string\"," + " \"enum\" : [\"Street\", \"Avenue\", \"Boulevard\"]" + " }" + " }," + " \"additionalProperties\": false" + "}"); + + SchemaDocument s(sd); + + VALIDATE(s, "{ \"number\": 1600, \"street_name\": \"Pennsylvania\", \"street_type\": \"Avenue\" }", true); + INVALIDATE(s, "{ \"number\": 1600, \"street_name\": \"Pennsylvania\", \"street_type\": \"Avenue\", \"direction\": \"NW\" }", "", "additionalProperties", "/direction"); +} + +TEST(SchemaValidator, Object_AdditionalPropertiesObject) { + Document sd; + sd.Parse( + "{" + " \"type\": \"object\"," + " \"properties\" : {" + " \"number\": { \"type\": \"number\" }," + " \"street_name\" : { \"type\": \"string\" }," + " \"street_type\" : { \"type\": \"string\"," + " \"enum\" : [\"Street\", \"Avenue\", \"Boulevard\"]" + " }" + " }," + " \"additionalProperties\": { \"type\": \"string\" }" + "}"); + SchemaDocument s(sd); + + VALIDATE(s, "{ \"number\": 1600, \"street_name\": \"Pennsylvania\", \"street_type\": \"Avenue\" }", true); + VALIDATE(s, "{ \"number\": 1600, \"street_name\": \"Pennsylvania\", \"street_type\": \"Avenue\", \"direction\": \"NW\" }", true); + INVALIDATE(s, "{ \"number\": 1600, \"street_name\": \"Pennsylvania\", \"street_type\": \"Avenue\", \"office_number\": 201 }", "/additionalProperties", "type", "/office_number"); +} + +TEST(SchemaValidator, Object_Required) { + Document sd; + sd.Parse( + "{" + " \"type\": \"object\"," + " \"properties\" : {" + " \"name\": { \"type\": \"string\" }," + " \"email\" : { \"type\": \"string\" }," + " \"address\" : { \"type\": \"string\" }," + " \"telephone\" : { \"type\": \"string\" }" + " }," + " \"required\":[\"name\", \"email\"]" + "}"); + SchemaDocument s(sd); + + VALIDATE(s, "{ \"name\": \"William Shakespeare\", \"email\" : \"bill@stratford-upon-avon.co.uk\" }", true); + VALIDATE(s, "{ \"name\": \"William Shakespeare\", \"email\" : \"bill@stratford-upon-avon.co.uk\", \"address\" : \"Henley Street, Stratford-upon-Avon, Warwickshire, England\", \"authorship\" : \"in question\"}", true); + INVALIDATE(s, "{ \"name\": \"William Shakespeare\", \"address\" : \"Henley Street, Stratford-upon-Avon, Warwickshire, England\" }", "", "required", ""); +} + + +TEST(SchemaValidator, Object_PropertiesRange) { + Document sd; + sd.Parse("{\"type\":\"object\", \"minProperties\":2, \"maxProperties\":3}"); + SchemaDocument s(sd); + + INVALIDATE(s, "{}", "", "minProperties", ""); + INVALIDATE(s, "{\"a\":0}", "", "minProperties", ""); + VALIDATE(s, "{\"a\":0,\"b\":1}", true); + VALIDATE(s, "{\"a\":0,\"b\":1,\"c\":2}", true); + INVALIDATE(s, "{\"a\":0,\"b\":1,\"c\":2,\"d\":3}", "", "maxProperties", ""); +} + +TEST(SchemaValidator, Object_PropertyDependencies) { + Document sd; + sd.Parse( + "{" + " \"type\": \"object\"," + " \"properties\": {" + " \"name\": { \"type\": \"string\" }," + " \"credit_card\": { \"type\": \"number\" }," + " \"billing_address\": { \"type\": \"string\" }" + " }," + " \"required\": [\"name\"]," + " \"dependencies\": {" + " \"credit_card\": [\"billing_address\"]" + " }" + "}"); + SchemaDocument s(sd); + + VALIDATE(s, "{ \"name\": \"John Doe\", \"credit_card\": 5555555555555555, \"billing_address\": \"555 Debtor's Lane\" }", true); + INVALIDATE(s, "{ \"name\": \"John Doe\", \"credit_card\": 5555555555555555 }", "", "dependencies", ""); + VALIDATE(s, "{ \"name\": \"John Doe\"}", true); + VALIDATE(s, "{ \"name\": \"John Doe\", \"billing_address\": \"555 Debtor's Lane\" }", true); +} + +TEST(SchemaValidator, Object_SchemaDependencies) { + Document sd; + sd.Parse( + "{" + " \"type\": \"object\"," + " \"properties\" : {" + " \"name\": { \"type\": \"string\" }," + " \"credit_card\" : { \"type\": \"number\" }" + " }," + " \"required\" : [\"name\"]," + " \"dependencies\" : {" + " \"credit_card\": {" + " \"properties\": {" + " \"billing_address\": { \"type\": \"string\" }" + " }," + " \"required\" : [\"billing_address\"]" + " }" + " }" + "}"); + SchemaDocument s(sd); + + VALIDATE(s, "{\"name\": \"John Doe\", \"credit_card\" : 5555555555555555,\"billing_address\" : \"555 Debtor's Lane\"}", true); + INVALIDATE(s, "{\"name\": \"John Doe\", \"credit_card\" : 5555555555555555 }", "", "dependencies", ""); + VALIDATE(s, "{\"name\": \"John Doe\", \"billing_address\" : \"555 Debtor's Lane\"}", true); +} + +#if RAPIDJSON_SCHEMA_HAS_REGEX +TEST(SchemaValidator, Object_PatternProperties) { + Document sd; + sd.Parse( + "{" + " \"type\": \"object\"," + " \"patternProperties\": {" + " \"^S_\": { \"type\": \"string\" }," + " \"^I_\": { \"type\": \"integer\" }" + " }" + "}"); + SchemaDocument s(sd); + + VALIDATE(s, "{ \"S_25\": \"This is a string\" }", true); + VALIDATE(s, "{ \"I_0\": 42 }", true); + INVALIDATE(s, "{ \"S_0\": 42 }", "", "patternProperties", "/S_0"); + INVALIDATE(s, "{ \"I_42\": \"This is a string\" }", "", "patternProperties", "/I_42"); + VALIDATE(s, "{ \"keyword\": \"value\" }", true); +} + +TEST(SchemaValidator, Object_PatternProperties_AdditionalProperties) { + Document sd; + sd.Parse( + "{" + " \"type\": \"object\"," + " \"properties\": {" + " \"builtin\": { \"type\": \"number\" }" + " }," + " \"patternProperties\": {" + " \"^S_\": { \"type\": \"string\" }," + " \"^I_\": { \"type\": \"integer\" }" + " }," + " \"additionalProperties\": { \"type\": \"string\" }" + "}"); + SchemaDocument s(sd); + + VALIDATE(s, "{ \"builtin\": 42 }", true); + VALIDATE(s, "{ \"keyword\": \"value\" }", true); + INVALIDATE(s, "{ \"keyword\": 42 }", "/additionalProperties", "type", "/keyword"); +} +#endif + +TEST(SchemaValidator, Array) { + Document sd; + sd.Parse("{\"type\":\"array\"}"); + SchemaDocument s(sd); + + VALIDATE(s, "[1, 2, 3, 4, 5]", true); + VALIDATE(s, "[3, \"different\", { \"types\" : \"of values\" }]", true); + INVALIDATE(s, "{\"Not\": \"an array\"}", "", "type", ""); +} + +TEST(SchemaValidator, Array_ItemsList) { + Document sd; + sd.Parse( + "{" + " \"type\": \"array\"," + " \"items\" : {" + " \"type\": \"number\"" + " }" + "}"); + SchemaDocument s(sd); + + VALIDATE(s, "[1, 2, 3, 4, 5]", true); + INVALIDATE(s, "[1, 2, \"3\", 4, 5]", "/items", "type", "/2"); + VALIDATE(s, "[]", true); +} + +TEST(SchemaValidator, Array_ItemsTuple) { + Document sd; + sd.Parse( + "{" + " \"type\": \"array\"," + " \"items\": [" + " {" + " \"type\": \"number\"" + " }," + " {" + " \"type\": \"string\"" + " }," + " {" + " \"type\": \"string\"," + " \"enum\": [\"Street\", \"Avenue\", \"Boulevard\"]" + " }," + " {" + " \"type\": \"string\"," + " \"enum\": [\"NW\", \"NE\", \"SW\", \"SE\"]" + " }" + " ]" + "}"); + SchemaDocument s(sd); + + VALIDATE(s, "[1600, \"Pennsylvania\", \"Avenue\", \"NW\"]", true); + INVALIDATE(s, "[24, \"Sussex\", \"Drive\"]", "/items/2", "enum", "/2"); + INVALIDATE(s, "[\"Palais de l'Elysee\"]", "/items/0", "type", "/0"); + VALIDATE(s, "[10, \"Downing\", \"Street\"]", true); + VALIDATE(s, "[1600, \"Pennsylvania\", \"Avenue\", \"NW\", \"Washington\"]", true); +} + +TEST(SchemaValidator, Array_AdditionalItmes) { + Document sd; + sd.Parse( + "{" + " \"type\": \"array\"," + " \"items\": [" + " {" + " \"type\": \"number\"" + " }," + " {" + " \"type\": \"string\"" + " }," + " {" + " \"type\": \"string\"," + " \"enum\": [\"Street\", \"Avenue\", \"Boulevard\"]" + " }," + " {" + " \"type\": \"string\"," + " \"enum\": [\"NW\", \"NE\", \"SW\", \"SE\"]" + " }" + " ]," + " \"additionalItems\": false" + "}"); + SchemaDocument s(sd); + + VALIDATE(s, "[1600, \"Pennsylvania\", \"Avenue\", \"NW\"]", true); + VALIDATE(s, "[1600, \"Pennsylvania\", \"Avenue\"]", true); + INVALIDATE(s, "[1600, \"Pennsylvania\", \"Avenue\", \"NW\", \"Washington\"]", "", "items", "/4"); +} + +TEST(SchemaValidator, Array_ItemsRange) { + Document sd; + sd.Parse("{\"type\": \"array\",\"minItems\": 2,\"maxItems\" : 3}"); + SchemaDocument s(sd); + + INVALIDATE(s, "[]", "", "minItems", ""); + INVALIDATE(s, "[1]", "", "minItems", ""); + VALIDATE(s, "[1, 2]", true); + VALIDATE(s, "[1, 2, 3]", true); + INVALIDATE(s, "[1, 2, 3, 4]", "", "maxItems", ""); +} + +TEST(SchemaValidator, Array_UniqueItems) { + Document sd; + sd.Parse("{\"type\": \"array\", \"uniqueItems\": true}"); + SchemaDocument s(sd); + + VALIDATE(s, "[1, 2, 3, 4, 5]", true); + INVALIDATE(s, "[1, 2, 3, 3, 4]", "", "uniqueItems", "/3"); + VALIDATE(s, "[]", true); +} + +TEST(SchemaValidator, Boolean) { + Document sd; + sd.Parse("{\"type\":\"boolean\"}"); + SchemaDocument s(sd); + + VALIDATE(s, "true", true); + VALIDATE(s, "false", true); + INVALIDATE(s, "\"true\"", "", "type", ""); + INVALIDATE(s, "0", "", "type", ""); +} + +TEST(SchemaValidator, Null) { + Document sd; + sd.Parse("{\"type\":\"null\"}"); + SchemaDocument s(sd); + + VALIDATE(s, "null", true); + INVALIDATE(s, "false", "", "type", ""); + INVALIDATE(s, "0", "", "type", ""); + INVALIDATE(s, "\"\"", "", "type", ""); +} + +// Additional tests + +TEST(SchemaValidator, ObjectInArray) { + Document sd; + sd.Parse("{\"type\":\"array\", \"items\": { \"type\":\"string\" }}"); + SchemaDocument s(sd); + + VALIDATE(s, "[\"a\"]", true); + INVALIDATE(s, "[1]", "/items", "type", "/0"); + INVALIDATE(s, "[{}]", "/items", "type", "/0"); +} + +TEST(SchemaValidator, MultiTypeInObject) { + Document sd; + sd.Parse( + "{" + " \"type\":\"object\"," + " \"properties\": {" + " \"tel\" : {" + " \"type\":[\"integer\", \"string\"]" + " }" + " }" + "}"); + SchemaDocument s(sd); + + VALIDATE(s, "{ \"tel\": 999 }", true); + VALIDATE(s, "{ \"tel\": \"123-456\" }", true); + INVALIDATE(s, "{ \"tel\": true }", "/properties/tel", "type", "/tel"); +} + +TEST(SchemaValidator, MultiTypeWithObject) { + Document sd; + sd.Parse( + "{" + " \"type\": [\"object\",\"string\"]," + " \"properties\": {" + " \"tel\" : {" + " \"type\": \"integer\"" + " }" + " }" + "}"); + SchemaDocument s(sd); + + VALIDATE(s, "\"Hello\"", true); + VALIDATE(s, "{ \"tel\": 999 }", true); + INVALIDATE(s, "{ \"tel\": \"fail\" }", "/properties/tel", "type", "/tel"); +} + +TEST(SchemaValidator, AllOf_Nested) { + Document sd; + sd.Parse( + "{" + " \"allOf\": [" + " { \"type\": \"string\", \"minLength\": 2 }," + " { \"type\": \"string\", \"maxLength\": 5 }," + " { \"allOf\": [ { \"enum\" : [\"ok\", \"okay\", \"OK\", \"o\"] }, { \"enum\" : [\"ok\", \"OK\", \"o\"]} ] }" + " ]" + "}"); + SchemaDocument s(sd); + + VALIDATE(s, "\"ok\"", true); + VALIDATE(s, "\"OK\"", true); + INVALIDATE(s, "\"okay\"", "", "allOf", ""); + INVALIDATE(s, "\"o\"", "", "allOf", ""); + INVALIDATE(s, "\"n\"", "", "allOf", ""); + INVALIDATE(s, "\"too long\"", "", "allOf", ""); + INVALIDATE(s, "123", "", "allOf", ""); +} + +TEST(SchemaValidator, EscapedPointer) { + Document sd; + sd.Parse( + "{" + " \"type\": \"object\"," + " \"properties\": {" + " \"~/\": { \"type\": \"number\" }" + " }" + "}"); + SchemaDocument s(sd); + INVALIDATE(s, "{\"~/\":true}", "/properties/~0~1", "type", "/~0~1"); +} + +template <typename Allocator> +static char* ReadFile(const char* filename, Allocator& allocator) { + const char *paths[] = { + "", + "bin/", + "../bin/", + "../../bin/", + "../../../bin/" + }; + char buffer[1024]; + FILE *fp = 0; + for (size_t i = 0; i < sizeof(paths) / sizeof(paths[0]); i++) { + sprintf(buffer, "%s%s", paths[i], filename); + fp = fopen(buffer, "rb"); + if (fp) + break; + } + + if (!fp) + return 0; + + fseek(fp, 0, SEEK_END); + size_t length = static_cast<size_t>(ftell(fp)); + fseek(fp, 0, SEEK_SET); + char* json = reinterpret_cast<char*>(allocator.Malloc(length + 1)); + size_t readLength = fread(json, 1, length, fp); + json[readLength] = '\0'; + fclose(fp); + return json; +} + +TEST(SchemaValidator, ValidateMetaSchema) { + CrtAllocator allocator; + char* json = ReadFile("draft-04/schema", allocator); + Document d; + d.Parse(json); + ASSERT_FALSE(d.HasParseError()); + SchemaDocument sd(d); + SchemaValidator validator(sd); + if (!d.Accept(validator)) { + StringBuffer sb; + validator.GetInvalidSchemaPointer().StringifyUriFragment(sb); + printf("Invalid schema: %s\n", sb.GetString()); + printf("Invalid keyword: %s\n", validator.GetInvalidSchemaKeyword()); + sb.Clear(); + validator.GetInvalidDocumentPointer().StringifyUriFragment(sb); + printf("Invalid document: %s\n", sb.GetString()); + ADD_FAILURE(); + } + CrtAllocator::Free(json); +} + +TEST(SchemaValidator, ValidateMetaSchema_UTF16) { + typedef GenericDocument<UTF16<> > D; + typedef GenericSchemaDocument<D::ValueType> SD; + typedef GenericSchemaValidator<SD> SV; + + CrtAllocator allocator; + char* json = ReadFile("draft-04/schema", allocator); + + D d; + StringStream ss(json); + d.ParseStream<0, UTF8<> >(ss); + ASSERT_FALSE(d.HasParseError()); + SD sd(d); + SV validator(sd); + if (!d.Accept(validator)) { + GenericStringBuffer<UTF16<> > sb; + validator.GetInvalidSchemaPointer().StringifyUriFragment(sb); + wprintf(L"Invalid schema: %ls\n", sb.GetString()); + wprintf(L"Invalid keyword: %ls\n", validator.GetInvalidSchemaKeyword()); + sb.Clear(); + validator.GetInvalidDocumentPointer().StringifyUriFragment(sb); + wprintf(L"Invalid document: %ls\n", sb.GetString()); + ADD_FAILURE(); + } + CrtAllocator::Free(json); +} + +template <typename SchemaDocumentType = SchemaDocument> +class RemoteSchemaDocumentProvider : public IGenericRemoteSchemaDocumentProvider<SchemaDocumentType> { +public: + RemoteSchemaDocumentProvider() : + documentAllocator_(documentBuffer_, sizeof(documentBuffer_)), + schemaAllocator_(schemaBuffer_, sizeof(schemaBuffer_)) + { + const char* filenames[kCount] = { + "jsonschema/remotes/integer.json", + "jsonschema/remotes/subSchemas.json", + "jsonschema/remotes/folder/folderInteger.json", + "draft-04/schema" + }; + + for (size_t i = 0; i < kCount; i++) { + sd_[i] = 0; + + char jsonBuffer[8192]; + MemoryPoolAllocator<> jsonAllocator(jsonBuffer, sizeof(jsonBuffer)); + char* json = ReadFile(filenames[i], jsonAllocator); + if (!json) { + printf("json remote file %s not found", filenames[i]); + ADD_FAILURE(); + } + else { + char stackBuffer[4096]; + MemoryPoolAllocator<> stackAllocator(stackBuffer, sizeof(stackBuffer)); + DocumentType d(&documentAllocator_, 1024, &stackAllocator); + d.Parse(json); + sd_[i] = new SchemaDocumentType(d, 0, &schemaAllocator_); + MemoryPoolAllocator<>::Free(json); + } + }; + } + + ~RemoteSchemaDocumentProvider() { + for (size_t i = 0; i < kCount; i++) + delete sd_[i]; + } + + virtual const SchemaDocumentType* GetRemoteDocument(const char* uri, SizeType length) { + const char* uris[kCount] = { + "http://localhost:1234/integer.json", + "http://localhost:1234/subSchemas.json", + "http://localhost:1234/folder/folderInteger.json", + "http://json-schema.org/draft-04/schema" + }; + + for (size_t i = 0; i < kCount; i++) + if (strncmp(uri, uris[i], length) == 0) + return sd_[i]; + return 0; + } + +private: + typedef GenericDocument<typename SchemaDocumentType::EncodingType, MemoryPoolAllocator<>, MemoryPoolAllocator<> > DocumentType; + + RemoteSchemaDocumentProvider(const RemoteSchemaDocumentProvider&); + RemoteSchemaDocumentProvider& operator=(const RemoteSchemaDocumentProvider&); + + static const size_t kCount = 4; + SchemaDocumentType* sd_[kCount]; + typename DocumentType::AllocatorType documentAllocator_; + typename SchemaDocumentType::AllocatorType schemaAllocator_; + char documentBuffer_[16384]; + char schemaBuffer_[128 * 1024]; +}; + +TEST(SchemaValidator, TestSuite) { + const char* filenames[] = { + "additionalItems.json", + "additionalProperties.json", + "allOf.json", + "anyOf.json", + "default.json", + "definitions.json", + "dependencies.json", + "enum.json", + "items.json", + "maximum.json", + "maxItems.json", + "maxLength.json", + "maxProperties.json", + "minimum.json", + "minItems.json", + "minLength.json", + "minProperties.json", + "multipleOf.json", + "not.json", + "oneOf.json", + "pattern.json", + "patternProperties.json", + "properties.json", + "ref.json", + "refRemote.json", + "required.json", + "type.json", + "uniqueItems.json" + }; + + const char* onlyRunDescription = 0; + //const char* onlyRunDescription = "a string is a string"; + + unsigned testCount = 0; + unsigned passCount = 0; + + typedef GenericSchemaDocument<Value, MemoryPoolAllocator<> > SchemaDocumentType; + RemoteSchemaDocumentProvider<SchemaDocumentType> provider; + + char jsonBuffer[65536]; + char documentBuffer[65536]; + char documentStackBuffer[65536]; + char schemaBuffer[65536]; + char validatorBuffer[65536]; + MemoryPoolAllocator<> jsonAllocator(jsonBuffer, sizeof(jsonBuffer)); + MemoryPoolAllocator<> documentAllocator(documentBuffer, sizeof(documentBuffer)); + MemoryPoolAllocator<> documentStackAllocator(documentStackBuffer, sizeof(documentStackBuffer)); + MemoryPoolAllocator<> schemaAllocator(schemaBuffer, sizeof(schemaBuffer)); + MemoryPoolAllocator<> validatorAllocator(validatorBuffer, sizeof(validatorBuffer)); + + for (size_t i = 0; i < sizeof(filenames) / sizeof(filenames[0]); i++) { + char filename[FILENAME_MAX]; + sprintf(filename, "jsonschema/tests/draft4/%s", filenames[i]); + char* json = ReadFile(filename, jsonAllocator); + if (!json) { + printf("json test suite file %s not found", filename); + ADD_FAILURE(); + } + else { + GenericDocument<UTF8<>, MemoryPoolAllocator<>, MemoryPoolAllocator<> > d(&documentAllocator, 1024, &documentStackAllocator); + d.Parse(json); + if (d.HasParseError()) { + printf("json test suite file %s has parse error", filename); + ADD_FAILURE(); + } + else { + for (Value::ConstValueIterator schemaItr = d.Begin(); schemaItr != d.End(); ++schemaItr) { + { + SchemaDocumentType schema((*schemaItr)["schema"], &provider, &schemaAllocator); + GenericSchemaValidator<SchemaDocumentType, BaseReaderHandler<UTF8<> >, MemoryPoolAllocator<> > validator(schema, &validatorAllocator); + const char* description1 = (*schemaItr)["description"].GetString(); + const Value& tests = (*schemaItr)["tests"]; + for (Value::ConstValueIterator testItr = tests.Begin(); testItr != tests.End(); ++testItr) { + const char* description2 = (*testItr)["description"].GetString(); + if (!onlyRunDescription || strcmp(description2, onlyRunDescription) == 0) { + const Value& data = (*testItr)["data"]; + bool expected = (*testItr)["valid"].GetBool(); + testCount++; + validator.Reset(); + bool actual = data.Accept(validator); + if (expected != actual) + printf("Fail: %30s \"%s\" \"%s\"\n", filename, description1, description2); + else + passCount++; + } + } + //printf("%zu %zu %zu\n", documentAllocator.Size(), schemaAllocator.Size(), validatorAllocator.Size()); + } + schemaAllocator.Clear(); + validatorAllocator.Clear(); + } + } + } + documentAllocator.Clear(); + MemoryPoolAllocator<>::Free(json); + jsonAllocator.Clear(); + } + printf("%d / %d passed (%2d%%)\n", passCount, testCount, passCount * 100 / testCount); + // if (passCount != testCount) + // ADD_FAILURE(); +} + +TEST(SchemaValidatingReader, Simple) { + Document sd; + sd.Parse("{ \"type\": \"string\", \"enum\" : [\"red\", \"amber\", \"green\"] }"); + SchemaDocument s(sd); + + Document d; + StringStream ss("\"red\""); + SchemaValidatingReader<kParseDefaultFlags, StringStream, UTF8<> > reader(ss, s); + d.Populate(reader); + EXPECT_TRUE(reader.GetParseResult()); + EXPECT_TRUE(reader.IsValid()); + EXPECT_TRUE(d.IsString()); + EXPECT_STREQ("red", d.GetString()); +} + +TEST(SchemaValidatingReader, Invalid) { + Document sd; + sd.Parse("{\"type\":\"string\",\"minLength\":2,\"maxLength\":3}"); + SchemaDocument s(sd); + + Document d; + StringStream ss("\"ABCD\""); + SchemaValidatingReader<kParseDefaultFlags, StringStream, UTF8<> > reader(ss, s); + d.Populate(reader); + EXPECT_FALSE(reader.GetParseResult()); + EXPECT_FALSE(reader.IsValid()); + EXPECT_EQ(kParseErrorTermination, reader.GetParseResult().Code()); + EXPECT_STREQ("maxLength", reader.GetInvalidSchemaKeyword()); + EXPECT_TRUE(reader.GetInvalidSchemaPointer() == SchemaDocument::PointerType("")); + EXPECT_TRUE(reader.GetInvalidDocumentPointer() == SchemaDocument::PointerType("")); + EXPECT_TRUE(d.IsNull()); +} + +TEST(SchemaValidatingWriter, Simple) { + Document sd; + sd.Parse("{\"type\":\"string\",\"minLength\":2,\"maxLength\":3}"); + SchemaDocument s(sd); + + Document d; + StringBuffer sb; + Writer<StringBuffer> writer(sb); + GenericSchemaValidator<SchemaDocument, Writer<StringBuffer> > validator(s, writer); + + d.Parse("\"red\""); + EXPECT_TRUE(d.Accept(validator)); + EXPECT_TRUE(validator.IsValid()); + EXPECT_STREQ("\"red\"", sb.GetString()); + + sb.Clear(); + validator.Reset(); + d.Parse("\"ABCD\""); + EXPECT_FALSE(d.Accept(validator)); + EXPECT_FALSE(validator.IsValid()); + EXPECT_TRUE(validator.GetInvalidSchemaPointer() == SchemaDocument::PointerType("")); + EXPECT_TRUE(validator.GetInvalidDocumentPointer() == SchemaDocument::PointerType("")); +} + +#if RAPIDJSON_HAS_CXX11_RVALUE_REFS + +static SchemaDocument ReturnSchemaDocument() { + Document sd; + sd.Parse("{ \"type\": [\"number\", \"string\"] }"); + SchemaDocument s(sd); + return s; +} + +TEST(Schema, Issue552) { + SchemaDocument s = ReturnSchemaDocument(); + VALIDATE(s, "42", true); + VALIDATE(s, "\"Life, the universe, and everything\"", true); + INVALIDATE(s, "[\"Life\", \"the universe\", \"and everything\"]", "", "type", ""); +} + +#endif // RAPIDJSON_HAS_CXX11_RVALUE_REFS + +TEST(SchemaValidator, Issue608) { + Document sd; + sd.Parse("{\"required\": [\"a\", \"b\"] }"); + SchemaDocument s(sd); + + VALIDATE(s, "{\"a\" : null, \"b\": null}", true); + INVALIDATE(s, "{\"a\" : null, \"a\" : null}", "", "required", ""); +} + +#ifdef __clang__ +RAPIDJSON_DIAG_POP +#endif diff --git a/src/rapidjson/test/unittest/simdtest.cpp b/src/rapidjson/test/unittest/simdtest.cpp new file mode 100644 index 000000000..b01b559f4 --- /dev/null +++ b/src/rapidjson/test/unittest/simdtest.cpp @@ -0,0 +1,215 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +// Since Travis CI installs old Valgrind 3.7.0, which fails with some SSE4.2 +// The unit tests prefix with SIMD should be skipped by Valgrind test + +// __SSE2__ and __SSE4_2__ are recognized by gcc, clang, and the Intel compiler. +// We use -march=native with gmake to enable -msse2 and -msse4.2, if supported. +#if defined(__SSE4_2__) +# define RAPIDJSON_SSE42 +#elif defined(__SSE2__) +# define RAPIDJSON_SSE2 +#endif + +#define RAPIDJSON_NAMESPACE rapidjson_simd + +#include "unittest.h" + +#include "rapidjson/reader.h" +#include "rapidjson/writer.h" + +#ifdef __GNUC__ +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(effc++) +#endif + +using namespace rapidjson_simd; + +#ifdef RAPIDJSON_SSE2 +#define SIMD_SUFFIX(name) name##_SSE2 +#elif defined(RAPIDJSON_SSE42) +#define SIMD_SUFFIX(name) name##_SSE42 +#else +#define SIMD_SUFFIX(name) name +#endif + +template <typename StreamType> +void TestSkipWhitespace() { + for (size_t step = 1; step < 32; step++) { + char buffer[1025]; + for (size_t i = 0; i < 1024; i++) + buffer[i] = " \t\r\n"[i % 4]; + for (size_t i = 0; i < 1024; i += step) + buffer[i] = 'X'; + buffer[1024] = '\0'; + + StreamType s(buffer); + size_t i = 0; + for (;;) { + SkipWhitespace(s); + if (s.Peek() == '\0') + break; + EXPECT_EQ(i, s.Tell()); + EXPECT_EQ('X', s.Take()); + i += step; + } + } +} + +TEST(SIMD, SIMD_SUFFIX(SkipWhitespace)) { + TestSkipWhitespace<StringStream>(); + TestSkipWhitespace<InsituStringStream>(); +} + +TEST(SIMD, SIMD_SUFFIX(SkipWhitespace_EncodedMemoryStream)) { + for (size_t step = 1; step < 32; step++) { + char buffer[1024]; + for (size_t i = 0; i < 1024; i++) + buffer[i] = " \t\r\n"[i % 4]; + for (size_t i = 0; i < 1024; i += step) + buffer[i] = 'X'; + + MemoryStream ms(buffer, 1024); + EncodedInputStream<UTF8<>, MemoryStream> s(ms); + size_t i = 0; + for (;;) { + SkipWhitespace(s); + if (s.Peek() == '\0') + break; + //EXPECT_EQ(i, s.Tell()); + EXPECT_EQ('X', s.Take()); + i += step; + } + } +} + +struct ScanCopyUnescapedStringHandler : BaseReaderHandler<UTF8<>, ScanCopyUnescapedStringHandler> { + bool String(const char* str, size_t length, bool) { + memcpy(buffer, str, length + 1); + return true; + } + char buffer[1024 + 5 + 32]; +}; + +template <unsigned parseFlags, typename StreamType> +void TestScanCopyUnescapedString() { + char buffer[1024 + 5 + 32]; + char backup[1024 + 5 + 32]; + + // Test "ABCDABCD...\\" + for (size_t offset = 0; offset < 32; offset++) { + for (size_t step = 0; step < 1024; step++) { + char* json = buffer + offset; + char *p = json; + *p++ = '\"'; + for (size_t i = 0; i < step; i++) + *p++ = "ABCD"[i % 4]; + *p++ = '\\'; + *p++ = '\\'; + *p++ = '\"'; + *p++ = '\0'; + strcpy(backup, json); // insitu parsing will overwrite buffer, so need to backup first + + StreamType s(json); + Reader reader; + ScanCopyUnescapedStringHandler h; + reader.Parse<parseFlags>(s, h); + EXPECT_TRUE(memcmp(h.buffer, backup + 1, step) == 0); + EXPECT_EQ('\\', h.buffer[step]); // escaped + EXPECT_EQ('\0', h.buffer[step + 1]); + } + } + + // Test "\\ABCDABCD..." + for (size_t offset = 0; offset < 32; offset++) { + for (size_t step = 0; step < 1024; step++) { + char* json = buffer + offset; + char *p = json; + *p++ = '\"'; + *p++ = '\\'; + *p++ = '\\'; + for (size_t i = 0; i < step; i++) + *p++ = "ABCD"[i % 4]; + *p++ = '\"'; + *p++ = '\0'; + strcpy(backup, json); // insitu parsing will overwrite buffer, so need to backup first + + StreamType s(json); + Reader reader; + ScanCopyUnescapedStringHandler h; + reader.Parse<parseFlags>(s, h); + EXPECT_TRUE(memcmp(h.buffer + 1, backup + 3, step) == 0); + EXPECT_EQ('\\', h.buffer[0]); // escaped + EXPECT_EQ('\0', h.buffer[step + 1]); + } + } +} + +TEST(SIMD, SIMD_SUFFIX(ScanCopyUnescapedString)) { + TestScanCopyUnescapedString<kParseDefaultFlags, StringStream>(); + TestScanCopyUnescapedString<kParseInsituFlag, InsituStringStream>(); +} + +TEST(SIMD, SIMD_SUFFIX(ScanWriteUnescapedString)) { + char buffer[2048 + 1 + 32]; + for (size_t offset = 0; offset < 32; offset++) { + for (size_t step = 0; step < 1024; step++) { + char* s = buffer + offset; + char* p = s; + for (size_t i = 0; i < step; i++) + *p++ = "ABCD"[i % 4]; + char escape = "\0\n\\\""[step % 4]; + *p++ = escape; + for (size_t i = 0; i < step; i++) + *p++ = "ABCD"[i % 4]; + + StringBuffer sb; + Writer<StringBuffer> writer(sb); + writer.String(s, SizeType(step * 2 + 1)); + const char* q = sb.GetString(); + EXPECT_EQ('\"', *q++); + for (size_t i = 0; i < step; i++) + EXPECT_EQ("ABCD"[i % 4], *q++); + if (escape == '\0') { + EXPECT_EQ('\\', *q++); + EXPECT_EQ('u', *q++); + EXPECT_EQ('0', *q++); + EXPECT_EQ('0', *q++); + EXPECT_EQ('0', *q++); + EXPECT_EQ('0', *q++); + } + else if (escape == '\n') { + EXPECT_EQ('\\', *q++); + EXPECT_EQ('n', *q++); + } + else if (escape == '\\') { + EXPECT_EQ('\\', *q++); + EXPECT_EQ('\\', *q++); + } + else if (escape == '\"') { + EXPECT_EQ('\\', *q++); + EXPECT_EQ('\"', *q++); + } + for (size_t i = 0; i < step; i++) + EXPECT_EQ("ABCD"[i % 4], *q++); + EXPECT_EQ('\"', *q++); + EXPECT_EQ('\0', *q++); + } + } +} + +#ifdef __GNUC__ +RAPIDJSON_DIAG_POP +#endif diff --git a/src/rapidjson/test/unittest/strfunctest.cpp b/src/rapidjson/test/unittest/strfunctest.cpp new file mode 100644 index 000000000..cc1bb22f0 --- /dev/null +++ b/src/rapidjson/test/unittest/strfunctest.cpp @@ -0,0 +1,30 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#include "unittest.h" +#include "rapidjson/internal/strfunc.h" + +using namespace rapidjson; +using namespace rapidjson::internal; + +TEST(StrFunc, CountStringCodePoint) { + SizeType count; + EXPECT_TRUE(CountStringCodePoint<UTF8<> >("", 0, &count)); + EXPECT_EQ(0u, count); + EXPECT_TRUE(CountStringCodePoint<UTF8<> >("Hello", 5, &count)); + EXPECT_EQ(5u, count); + EXPECT_TRUE(CountStringCodePoint<UTF8<> >("\xC2\xA2\xE2\x82\xAC\xF0\x9D\x84\x9E", 9, &count)); // cents euro G-clef + EXPECT_EQ(3u, count); + EXPECT_FALSE(CountStringCodePoint<UTF8<> >("\xC2\xA2\xE2\x82\xAC\xF0\x9D\x84\x9E\x80", 10, &count)); +} diff --git a/src/rapidjson/test/unittest/stringbuffertest.cpp b/src/rapidjson/test/unittest/stringbuffertest.cpp new file mode 100644 index 000000000..ded513cdd --- /dev/null +++ b/src/rapidjson/test/unittest/stringbuffertest.cpp @@ -0,0 +1,170 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#include "unittest.h" +#include "rapidjson/stringbuffer.h" +#include "rapidjson/writer.h" + +#ifdef __clang__ +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(c++98-compat) +#endif + +using namespace rapidjson; + +TEST(StringBuffer, InitialSize) { + StringBuffer buffer; + EXPECT_EQ(0u, buffer.GetSize()); + EXPECT_STREQ("", buffer.GetString()); +} + +TEST(StringBuffer, Put) { + StringBuffer buffer; + buffer.Put('A'); + + EXPECT_EQ(1u, buffer.GetSize()); + EXPECT_STREQ("A", buffer.GetString()); +} + +TEST(StringBuffer, PutN_Issue672) { + GenericStringBuffer<UTF8<>, MemoryPoolAllocator<> > buffer; + EXPECT_EQ(0, buffer.GetSize()); + rapidjson::PutN(buffer, ' ', 1); + EXPECT_EQ(1, buffer.GetSize()); +} + +TEST(StringBuffer, Clear) { + StringBuffer buffer; + buffer.Put('A'); + buffer.Put('B'); + buffer.Put('C'); + buffer.Clear(); + + EXPECT_EQ(0u, buffer.GetSize()); + EXPECT_STREQ("", buffer.GetString()); +} + +TEST(StringBuffer, Push) { + StringBuffer buffer; + buffer.Push(5); + + EXPECT_EQ(5u, buffer.GetSize()); + + // Causes sudden expansion to make the stack's capacity equal to size + buffer.Push(65536u); + EXPECT_EQ(5u + 65536u, buffer.GetSize()); +} + +TEST(StringBuffer, Pop) { + StringBuffer buffer; + buffer.Put('A'); + buffer.Put('B'); + buffer.Put('C'); + buffer.Put('D'); + buffer.Put('E'); + buffer.Pop(3); + + EXPECT_EQ(2u, buffer.GetSize()); + EXPECT_STREQ("AB", buffer.GetString()); +} + +#if RAPIDJSON_HAS_CXX11_RVALUE_REFS + +#if 0 // Many old compiler does not support these. Turn it off temporaily. + +#include <type_traits> + +TEST(StringBuffer, Traits) { + static_assert( std::is_constructible<StringBuffer>::value, ""); + static_assert( std::is_default_constructible<StringBuffer>::value, ""); +#ifndef _MSC_VER + static_assert(!std::is_copy_constructible<StringBuffer>::value, ""); +#endif + static_assert( std::is_move_constructible<StringBuffer>::value, ""); + + static_assert(!std::is_nothrow_constructible<StringBuffer>::value, ""); + static_assert(!std::is_nothrow_default_constructible<StringBuffer>::value, ""); + +#if !defined(_MSC_VER) || _MSC_VER >= 1800 + static_assert(!std::is_nothrow_copy_constructible<StringBuffer>::value, ""); + static_assert(!std::is_nothrow_move_constructible<StringBuffer>::value, ""); +#endif + + static_assert( std::is_assignable<StringBuffer,StringBuffer>::value, ""); +#ifndef _MSC_VER + static_assert(!std::is_copy_assignable<StringBuffer>::value, ""); +#endif + static_assert( std::is_move_assignable<StringBuffer>::value, ""); + +#if !defined(_MSC_VER) || _MSC_VER >= 1800 + static_assert(!std::is_nothrow_assignable<StringBuffer, StringBuffer>::value, ""); +#endif + + static_assert(!std::is_nothrow_copy_assignable<StringBuffer>::value, ""); + static_assert(!std::is_nothrow_move_assignable<StringBuffer>::value, ""); + + static_assert( std::is_destructible<StringBuffer>::value, ""); +#ifndef _MSC_VER + static_assert(std::is_nothrow_destructible<StringBuffer>::value, ""); +#endif +} + +#endif + +TEST(StringBuffer, MoveConstructor) { + StringBuffer x; + x.Put('A'); + x.Put('B'); + x.Put('C'); + x.Put('D'); + + EXPECT_EQ(4u, x.GetSize()); + EXPECT_STREQ("ABCD", x.GetString()); + + // StringBuffer y(x); // does not compile (!is_copy_constructible) + StringBuffer y(std::move(x)); + EXPECT_EQ(0u, x.GetSize()); + EXPECT_EQ(4u, y.GetSize()); + EXPECT_STREQ("ABCD", y.GetString()); + + // StringBuffer z = y; // does not compile (!is_copy_assignable) + StringBuffer z = std::move(y); + EXPECT_EQ(0u, y.GetSize()); + EXPECT_EQ(4u, z.GetSize()); + EXPECT_STREQ("ABCD", z.GetString()); +} + +TEST(StringBuffer, MoveAssignment) { + StringBuffer x; + x.Put('A'); + x.Put('B'); + x.Put('C'); + x.Put('D'); + + EXPECT_EQ(4u, x.GetSize()); + EXPECT_STREQ("ABCD", x.GetString()); + + StringBuffer y; + // y = x; // does not compile (!is_copy_assignable) + y = std::move(x); + EXPECT_EQ(0u, x.GetSize()); + EXPECT_EQ(4u, y.GetSize()); + EXPECT_STREQ("ABCD", y.GetString()); +} + +#endif // RAPIDJSON_HAS_CXX11_RVALUE_REFS + +#ifdef __clang__ +RAPIDJSON_DIAG_POP +#endif diff --git a/src/rapidjson/test/unittest/strtodtest.cpp b/src/rapidjson/test/unittest/strtodtest.cpp new file mode 100644 index 000000000..cde836c7e --- /dev/null +++ b/src/rapidjson/test/unittest/strtodtest.cpp @@ -0,0 +1,132 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#include "unittest.h" + +#include "rapidjson/internal/strtod.h" + +#ifdef __clang__ +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(unreachable-code) +#endif + +#define BIGINTEGER_LITERAL(s) BigInteger(s, sizeof(s) - 1) + +using namespace rapidjson::internal; + +TEST(Strtod, CheckApproximationCase) { + static const int kSignificandSize = 52; + static const int kExponentBias = 0x3FF; + static const uint64_t kExponentMask = RAPIDJSON_UINT64_C2(0x7FF00000, 0x00000000); + static const uint64_t kSignificandMask = RAPIDJSON_UINT64_C2(0x000FFFFF, 0xFFFFFFFF); + static const uint64_t kHiddenBit = RAPIDJSON_UINT64_C2(0x00100000, 0x00000000); + + // http://www.exploringbinary.com/using-integers-to-check-a-floating-point-approximation/ + // Let b = 0x1.465a72e467d88p-149 + // = 5741268244528520 x 2^-201 + union { + double d; + uint64_t u; + }u; + u.u = 0x465a72e467d88 | ((static_cast<uint64_t>(-149 + kExponentBias)) << kSignificandSize); + const double b = u.d; + const uint64_t bInt = (u.u & kSignificandMask) | kHiddenBit; + const int bExp = static_cast<int>(((u.u & kExponentMask) >> kSignificandSize) - kExponentBias - kSignificandSize); + EXPECT_DOUBLE_EQ(1.7864e-45, b); + EXPECT_EQ(RAPIDJSON_UINT64_C2(0x001465a7, 0x2e467d88), bInt); + EXPECT_EQ(-201, bExp); + + // Let d = 17864 x 10-49 + const char dInt[] = "17864"; + const int dExp = -49; + + // Let h = 2^(bExp-1) + const int hExp = bExp - 1; + EXPECT_EQ(-202, hExp); + + int dS_Exp2 = 0; + int dS_Exp5 = 0; + int bS_Exp2 = 0; + int bS_Exp5 = 0; + int hS_Exp2 = 0; + int hS_Exp5 = 0; + + // Adjust for decimal exponent + if (dExp >= 0) { + dS_Exp2 += dExp; + dS_Exp5 += dExp; + } + else { + bS_Exp2 -= dExp; + bS_Exp5 -= dExp; + hS_Exp2 -= dExp; + hS_Exp5 -= dExp; + } + + // Adjust for binary exponent + if (bExp >= 0) + bS_Exp2 += bExp; + else { + dS_Exp2 -= bExp; + hS_Exp2 -= bExp; + } + + // Adjust for half ulp exponent + if (hExp >= 0) + hS_Exp2 += hExp; + else { + dS_Exp2 -= hExp; + bS_Exp2 -= hExp; + } + + // Remove common power of two factor from all three scaled values + int common_Exp2 = std::min(dS_Exp2, std::min(bS_Exp2, hS_Exp2)); + dS_Exp2 -= common_Exp2; + bS_Exp2 -= common_Exp2; + hS_Exp2 -= common_Exp2; + + EXPECT_EQ(153, dS_Exp2); + EXPECT_EQ(0, dS_Exp5); + EXPECT_EQ(1, bS_Exp2); + EXPECT_EQ(49, bS_Exp5); + EXPECT_EQ(0, hS_Exp2); + EXPECT_EQ(49, hS_Exp5); + + BigInteger dS = BIGINTEGER_LITERAL(dInt); + dS.MultiplyPow5(static_cast<unsigned>(dS_Exp5)) <<= static_cast<size_t>(dS_Exp2); + + BigInteger bS(bInt); + bS.MultiplyPow5(static_cast<unsigned>(bS_Exp5)) <<= static_cast<size_t>(bS_Exp2); + + BigInteger hS(1); + hS.MultiplyPow5(static_cast<unsigned>(hS_Exp5)) <<= static_cast<size_t>(hS_Exp2); + + EXPECT_TRUE(BIGINTEGER_LITERAL("203970822259994138521801764465966248930731085529088") == dS); + EXPECT_TRUE(BIGINTEGER_LITERAL("203970822259994122305215569213032722473144531250000") == bS); + EXPECT_TRUE(BIGINTEGER_LITERAL("17763568394002504646778106689453125") == hS); + + EXPECT_EQ(1, dS.Compare(bS)); + + BigInteger delta(0); + EXPECT_FALSE(dS.Difference(bS, &delta)); + EXPECT_TRUE(BIGINTEGER_LITERAL("16216586195252933526457586554279088") == delta); + EXPECT_TRUE(bS.Difference(dS, &delta)); + EXPECT_TRUE(BIGINTEGER_LITERAL("16216586195252933526457586554279088") == delta); + + EXPECT_EQ(-1, delta.Compare(hS)); +} + +#ifdef __clang__ +RAPIDJSON_DIAG_POP +#endif diff --git a/src/rapidjson/test/unittest/unittest.cpp b/src/rapidjson/test/unittest/unittest.cpp new file mode 100644 index 000000000..b754563ea --- /dev/null +++ b/src/rapidjson/test/unittest/unittest.cpp @@ -0,0 +1,51 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#include "unittest.h" +#include "rapidjson/rapidjson.h" + +#ifdef __clang__ +#pragma GCC diagnostic push +#if __has_warning("-Wdeprecated") +#pragma GCC diagnostic ignored "-Wdeprecated" +#endif +#endif + +AssertException::~AssertException() throw() {} + +#ifdef __clang__ +#pragma GCC diagnostic pop +#endif + +int main(int argc, char **argv) { + ::testing::InitGoogleTest(&argc, argv); + + std::cout << "RapidJSON v" << RAPIDJSON_VERSION_STRING << std::endl; + +#ifdef _MSC_VER + _CrtMemState memoryState = { 0 }; + (void)memoryState; + _CrtMemCheckpoint(&memoryState); + //_CrtSetBreakAlloc(X); + //void *testWhetherMemoryLeakDetectionWorks = malloc(1); +#endif + + int ret = RUN_ALL_TESTS(); + +#ifdef _MSC_VER + // Current gtest constantly leak 2 blocks at exit + _CrtMemDumpAllObjectsSince(&memoryState); +#endif + return ret; +} diff --git a/src/rapidjson/test/unittest/unittest.h b/src/rapidjson/test/unittest/unittest.h new file mode 100644 index 000000000..e125bf88d --- /dev/null +++ b/src/rapidjson/test/unittest/unittest.h @@ -0,0 +1,135 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef UNITTEST_H_ +#define UNITTEST_H_ + +// gtest indirectly included inttypes.h, without __STDC_CONSTANT_MACROS. +#ifndef __STDC_CONSTANT_MACROS +#ifdef __clang__ +#pragma GCC diagnostic push +#if __has_warning("-Wreserved-id-macro") +#pragma GCC diagnostic ignored "-Wreserved-id-macro" +#endif +#endif + +# define __STDC_CONSTANT_MACROS 1 // required by C++ standard + +#ifdef __clang__ +#pragma GCC diagnostic pop +#endif +#endif + +#ifdef _MSC_VER +#define _CRTDBG_MAP_ALLOC +#include <crtdbg.h> +#pragma warning(disable : 4996) // 'function': was declared deprecated +#endif + +#if defined(__clang__) || defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 2)) +#if defined(__clang__) || (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)) +#pragma GCC diagnostic push +#endif +#pragma GCC diagnostic ignored "-Weffc++" +#endif + +#include "gtest/gtest.h" +#include <stdexcept> + +#if defined(__clang__) || defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)) +#pragma GCC diagnostic pop +#endif + +#ifdef __clang__ +// All TEST() macro generated this warning, disable globally +#pragma GCC diagnostic ignored "-Wglobal-constructors" +#endif + +template <typename Ch> +inline unsigned StrLen(const Ch* s) { + const Ch* p = s; + while (*p) p++; + return unsigned(p - s); +} + +template<typename Ch> +inline int StrCmp(const Ch* s1, const Ch* s2) { + while(*s1 && (*s1 == *s2)) { s1++; s2++; } + return static_cast<unsigned>(*s1) < static_cast<unsigned>(*s2) ? -1 : static_cast<unsigned>(*s1) > static_cast<unsigned>(*s2); +} + +template <typename Ch> +inline Ch* StrDup(const Ch* str) { + size_t bufferSize = sizeof(Ch) * (StrLen(str) + 1); + Ch* buffer = static_cast<Ch*>(malloc(bufferSize)); + memcpy(buffer, str, bufferSize); + return buffer; +} + +inline FILE* TempFile(char *filename) { +#ifdef _MSC_VER + filename = tmpnam(filename); + + // For Visual Studio, tmpnam() adds a backslash in front. Remove it. + if (filename[0] == '\\') + for (int i = 0; filename[i] != '\0'; i++) + filename[i] = filename[i + 1]; + + return fopen(filename, "wb"); +#else + strcpy(filename, "/tmp/fileXXXXXX"); + int fd = mkstemp(filename); + return fdopen(fd, "w"); +#endif +} + +// Use exception for catching assert +#ifdef _MSC_VER +#pragma warning(disable : 4127) +#endif + +#ifdef __clang__ +#pragma GCC diagnostic push +#if __has_warning("-Wdeprecated") +#pragma GCC diagnostic ignored "-Wdeprecated" +#endif +#endif + +class AssertException : public std::logic_error { +public: + AssertException(const char* w) : std::logic_error(w) {} + AssertException(const AssertException& rhs) : std::logic_error(rhs) {} + virtual ~AssertException() throw(); +}; + +#ifdef __clang__ +#pragma GCC diagnostic pop +#endif + +#define RAPIDJSON_ASSERT(x) if (!(x)) throw AssertException(RAPIDJSON_STRINGIFY(x)) + +class Random { +public: + Random(unsigned seed = 0) : mSeed(seed) {} + + unsigned operator()() { + mSeed = 214013 * mSeed + 2531011; + return mSeed; + } + +private: + unsigned mSeed; +}; + +#endif // UNITTEST_H_ diff --git a/src/rapidjson/test/unittest/valuetest.cpp b/src/rapidjson/test/unittest/valuetest.cpp new file mode 100644 index 000000000..fefc001d4 --- /dev/null +++ b/src/rapidjson/test/unittest/valuetest.cpp @@ -0,0 +1,1792 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#include "unittest.h" +#include "rapidjson/document.h" +#include <algorithm> + +#ifdef __clang__ +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(c++98-compat) +#endif + +using namespace rapidjson; + +TEST(Value, Size) { + if (sizeof(SizeType) == 4) { +#if RAPIDJSON_48BITPOINTER_OPTIMIZATION + EXPECT_EQ(16, sizeof(Value)); +#elif RAPIDJSON_64BIT + EXPECT_EQ(24, sizeof(Value)); +#else + EXPECT_EQ(16, sizeof(Value)); +#endif + } +} + +TEST(Value, DefaultConstructor) { + Value x; + EXPECT_EQ(kNullType, x.GetType()); + EXPECT_TRUE(x.IsNull()); + + //std::cout << "sizeof(Value): " << sizeof(x) << std::endl; +} + +// Should not pass compilation +//TEST(Value, copy_constructor) { +// Value x(1234); +// Value y = x; +//} + +#if RAPIDJSON_HAS_CXX11_RVALUE_REFS + +#if 0 // Many old compiler does not support these. Turn it off temporaily. + +#include <type_traits> + +TEST(Value, Traits) { + typedef GenericValue<UTF8<>, CrtAllocator> Value; + static_assert(std::is_constructible<Value>::value, ""); + static_assert(std::is_default_constructible<Value>::value, ""); +#ifndef _MSC_VER + static_assert(!std::is_copy_constructible<Value>::value, ""); +#endif + static_assert(std::is_move_constructible<Value>::value, ""); + +#ifndef _MSC_VER + static_assert(std::is_nothrow_constructible<Value>::value, ""); + static_assert(std::is_nothrow_default_constructible<Value>::value, ""); + static_assert(!std::is_nothrow_copy_constructible<Value>::value, ""); + static_assert(std::is_nothrow_move_constructible<Value>::value, ""); +#endif + + static_assert(std::is_assignable<Value,Value>::value, ""); +#ifndef _MSC_VER + static_assert(!std::is_copy_assignable<Value>::value, ""); +#endif + static_assert(std::is_move_assignable<Value>::value, ""); + +#ifndef _MSC_VER + static_assert(std::is_nothrow_assignable<Value, Value>::value, ""); +#endif + static_assert(!std::is_nothrow_copy_assignable<Value>::value, ""); +#ifndef _MSC_VER + static_assert(std::is_nothrow_move_assignable<Value>::value, ""); +#endif + + static_assert(std::is_destructible<Value>::value, ""); +#ifndef _MSC_VER + static_assert(std::is_nothrow_destructible<Value>::value, ""); +#endif +} + +#endif + +TEST(Value, MoveConstructor) { + typedef GenericValue<UTF8<>, CrtAllocator> V; + V::AllocatorType allocator; + + V x((V(kArrayType))); + x.Reserve(4u, allocator); + x.PushBack(1, allocator).PushBack(2, allocator).PushBack(3, allocator).PushBack(4, allocator); + EXPECT_TRUE(x.IsArray()); + EXPECT_EQ(4u, x.Size()); + + // Value y(x); // does not compile (!is_copy_constructible) + V y(std::move(x)); + EXPECT_TRUE(x.IsNull()); + EXPECT_TRUE(y.IsArray()); + EXPECT_EQ(4u, y.Size()); + + // Value z = y; // does not compile (!is_copy_assignable) + V z = std::move(y); + EXPECT_TRUE(y.IsNull()); + EXPECT_TRUE(z.IsArray()); + EXPECT_EQ(4u, z.Size()); +} + +#endif // RAPIDJSON_HAS_CXX11_RVALUE_REFS + +TEST(Value, AssignmentOperator) { + Value x(1234); + Value y; + y = x; + EXPECT_TRUE(x.IsNull()); // move semantic + EXPECT_EQ(1234, y.GetInt()); + + y = 5678; + EXPECT_TRUE(y.IsInt()); + EXPECT_EQ(5678, y.GetInt()); + + x = "Hello"; + EXPECT_TRUE(x.IsString()); + EXPECT_STREQ(x.GetString(),"Hello"); + + y = StringRef(x.GetString(),x.GetStringLength()); + EXPECT_TRUE(y.IsString()); + EXPECT_EQ(y.GetString(),x.GetString()); + EXPECT_EQ(y.GetStringLength(),x.GetStringLength()); + + static char mstr[] = "mutable"; + // y = mstr; // should not compile + y = StringRef(mstr); + EXPECT_TRUE(y.IsString()); + EXPECT_EQ(y.GetString(),mstr); + +#if RAPIDJSON_HAS_CXX11_RVALUE_REFS + // C++11 move assignment + x = Value("World"); + EXPECT_TRUE(x.IsString()); + EXPECT_STREQ("World", x.GetString()); + + x = std::move(y); + EXPECT_TRUE(y.IsNull()); + EXPECT_TRUE(x.IsString()); + EXPECT_EQ(x.GetString(), mstr); + + y = std::move(Value().SetInt(1234)); + EXPECT_TRUE(y.IsInt()); + EXPECT_EQ(1234, y); +#endif // RAPIDJSON_HAS_CXX11_RVALUE_REFS +} + +template <typename A, typename B> +void TestEqual(const A& a, const B& b) { + EXPECT_TRUE (a == b); + EXPECT_FALSE(a != b); + EXPECT_TRUE (b == a); + EXPECT_FALSE(b != a); +} + +template <typename A, typename B> +void TestUnequal(const A& a, const B& b) { + EXPECT_FALSE(a == b); + EXPECT_TRUE (a != b); + EXPECT_FALSE(b == a); + EXPECT_TRUE (b != a); +} + +TEST(Value, EqualtoOperator) { + Value::AllocatorType allocator; + Value x(kObjectType); + x.AddMember("hello", "world", allocator) + .AddMember("t", Value(true).Move(), allocator) + .AddMember("f", Value(false).Move(), allocator) + .AddMember("n", Value(kNullType).Move(), allocator) + .AddMember("i", 123, allocator) + .AddMember("pi", 3.14, allocator) + .AddMember("a", Value(kArrayType).Move().PushBack(1, allocator).PushBack(2, allocator).PushBack(3, allocator), allocator); + + // Test templated operator==() and operator!=() + TestEqual(x["hello"], "world"); + const char* cc = "world"; + TestEqual(x["hello"], cc); + char* c = strdup("world"); + TestEqual(x["hello"], c); + free(c); + + TestEqual(x["t"], true); + TestEqual(x["f"], false); + TestEqual(x["i"], 123); + TestEqual(x["pi"], 3.14); + + // Test operator==() (including different allocators) + CrtAllocator crtAllocator; + GenericValue<UTF8<>, CrtAllocator> y; + GenericDocument<UTF8<>, CrtAllocator> z(&crtAllocator); + y.CopyFrom(x, crtAllocator); + z.CopyFrom(y, z.GetAllocator()); + TestEqual(x, y); + TestEqual(y, z); + TestEqual(z, x); + + // Swapping member order should be fine. + EXPECT_TRUE(y.RemoveMember("t")); + TestUnequal(x, y); + TestUnequal(z, y); + EXPECT_TRUE(z.RemoveMember("t")); + TestUnequal(x, z); + TestEqual(y, z); + y.AddMember("t", false, crtAllocator); + z.AddMember("t", false, z.GetAllocator()); + TestUnequal(x, y); + TestUnequal(z, x); + y["t"] = true; + z["t"] = true; + TestEqual(x, y); + TestEqual(y, z); + TestEqual(z, x); + + // Swapping element order is not OK + x["a"][0].Swap(x["a"][1]); + TestUnequal(x, y); + x["a"][0].Swap(x["a"][1]); + TestEqual(x, y); + + // Array of different size + x["a"].PushBack(4, allocator); + TestUnequal(x, y); + x["a"].PopBack(); + TestEqual(x, y); + + // Issue #129: compare Uint64 + x.SetUint64(RAPIDJSON_UINT64_C2(0xFFFFFFFF, 0xFFFFFFF0)); + y.SetUint64(RAPIDJSON_UINT64_C2(0xFFFFFFFF, 0xFFFFFFFF)); + TestUnequal(x, y); +} + +template <typename Value> +void TestCopyFrom() { + typename Value::AllocatorType a; + Value v1(1234); + Value v2(v1, a); // deep copy constructor + EXPECT_TRUE(v1.GetType() == v2.GetType()); + EXPECT_EQ(v1.GetInt(), v2.GetInt()); + + v1.SetString("foo"); + v2.CopyFrom(v1, a); + EXPECT_TRUE(v1.GetType() == v2.GetType()); + EXPECT_STREQ(v1.GetString(), v2.GetString()); + EXPECT_EQ(v1.GetString(), v2.GetString()); // string NOT copied + + v1.SetString("bar", a); // copy string + v2.CopyFrom(v1, a); + EXPECT_TRUE(v1.GetType() == v2.GetType()); + EXPECT_STREQ(v1.GetString(), v2.GetString()); + EXPECT_NE(v1.GetString(), v2.GetString()); // string copied + + + v1.SetArray().PushBack(1234, a); + v2.CopyFrom(v1, a); + EXPECT_TRUE(v2.IsArray()); + EXPECT_EQ(v1.Size(), v2.Size()); + + v1.PushBack(Value().SetString("foo", a), a); // push string copy + EXPECT_TRUE(v1.Size() != v2.Size()); + v2.CopyFrom(v1, a); + EXPECT_TRUE(v1.Size() == v2.Size()); + EXPECT_STREQ(v1[1].GetString(), v2[1].GetString()); + EXPECT_NE(v1[1].GetString(), v2[1].GetString()); // string got copied +} + +TEST(Value, CopyFrom) { + TestCopyFrom<Value>(); + TestCopyFrom<GenericValue<UTF8<>, CrtAllocator> >(); +} + +TEST(Value, Swap) { + Value v1(1234); + Value v2(kObjectType); + + EXPECT_EQ(&v1, &v1.Swap(v2)); + EXPECT_TRUE(v1.IsObject()); + EXPECT_TRUE(v2.IsInt()); + EXPECT_EQ(1234, v2.GetInt()); + + // testing std::swap compatibility + using std::swap; + swap(v1, v2); + EXPECT_TRUE(v1.IsInt()); + EXPECT_TRUE(v2.IsObject()); +} + +TEST(Value, Null) { + // Default constructor + Value x; + EXPECT_EQ(kNullType, x.GetType()); + EXPECT_TRUE(x.IsNull()); + + EXPECT_FALSE(x.IsTrue()); + EXPECT_FALSE(x.IsFalse()); + EXPECT_FALSE(x.IsNumber()); + EXPECT_FALSE(x.IsString()); + EXPECT_FALSE(x.IsObject()); + EXPECT_FALSE(x.IsArray()); + + // Constructor with type + Value y(kNullType); + EXPECT_TRUE(y.IsNull()); + + // SetNull(); + Value z(true); + z.SetNull(); + EXPECT_TRUE(z.IsNull()); +} + +TEST(Value, True) { + // Constructor with bool + Value x(true); + EXPECT_EQ(kTrueType, x.GetType()); + EXPECT_TRUE(x.GetBool()); + EXPECT_TRUE(x.IsBool()); + EXPECT_TRUE(x.IsTrue()); + + EXPECT_FALSE(x.IsNull()); + EXPECT_FALSE(x.IsFalse()); + EXPECT_FALSE(x.IsNumber()); + EXPECT_FALSE(x.IsString()); + EXPECT_FALSE(x.IsObject()); + EXPECT_FALSE(x.IsArray()); + + // Constructor with type + Value y(kTrueType); + EXPECT_TRUE(y.IsTrue()); + + // SetBool() + Value z; + z.SetBool(true); + EXPECT_TRUE(z.IsTrue()); + + // Templated functions + EXPECT_TRUE(z.Is<bool>()); + EXPECT_TRUE(z.Get<bool>()); + EXPECT_FALSE(z.Set<bool>(false).Get<bool>()); + EXPECT_TRUE(z.Set(true).Get<bool>()); +} + +TEST(Value, False) { + // Constructor with bool + Value x(false); + EXPECT_EQ(kFalseType, x.GetType()); + EXPECT_TRUE(x.IsBool()); + EXPECT_TRUE(x.IsFalse()); + + EXPECT_FALSE(x.IsNull()); + EXPECT_FALSE(x.IsTrue()); + EXPECT_FALSE(x.GetBool()); + //EXPECT_FALSE((bool)x); + EXPECT_FALSE(x.IsNumber()); + EXPECT_FALSE(x.IsString()); + EXPECT_FALSE(x.IsObject()); + EXPECT_FALSE(x.IsArray()); + + // Constructor with type + Value y(kFalseType); + EXPECT_TRUE(y.IsFalse()); + + // SetBool() + Value z; + z.SetBool(false); + EXPECT_TRUE(z.IsFalse()); +} + +TEST(Value, Int) { + // Constructor with int + Value x(1234); + EXPECT_EQ(kNumberType, x.GetType()); + EXPECT_EQ(1234, x.GetInt()); + EXPECT_EQ(1234u, x.GetUint()); + EXPECT_EQ(1234, x.GetInt64()); + EXPECT_EQ(1234u, x.GetUint64()); + EXPECT_NEAR(1234.0, x.GetDouble(), 0.0); + //EXPECT_EQ(1234, (int)x); + //EXPECT_EQ(1234, (unsigned)x); + //EXPECT_EQ(1234, (int64_t)x); + //EXPECT_EQ(1234, (uint64_t)x); + //EXPECT_EQ(1234, (double)x); + EXPECT_TRUE(x.IsNumber()); + EXPECT_TRUE(x.IsInt()); + EXPECT_TRUE(x.IsUint()); + EXPECT_TRUE(x.IsInt64()); + EXPECT_TRUE(x.IsUint64()); + + EXPECT_FALSE(x.IsDouble()); + EXPECT_FALSE(x.IsFloat()); + EXPECT_FALSE(x.IsNull()); + EXPECT_FALSE(x.IsBool()); + EXPECT_FALSE(x.IsFalse()); + EXPECT_FALSE(x.IsTrue()); + EXPECT_FALSE(x.IsString()); + EXPECT_FALSE(x.IsObject()); + EXPECT_FALSE(x.IsArray()); + + Value nx(-1234); + EXPECT_EQ(-1234, nx.GetInt()); + EXPECT_EQ(-1234, nx.GetInt64()); + EXPECT_TRUE(nx.IsInt()); + EXPECT_TRUE(nx.IsInt64()); + EXPECT_FALSE(nx.IsUint()); + EXPECT_FALSE(nx.IsUint64()); + + // Constructor with type + Value y(kNumberType); + EXPECT_TRUE(y.IsNumber()); + EXPECT_TRUE(y.IsInt()); + EXPECT_EQ(0, y.GetInt()); + + // SetInt() + Value z; + z.SetInt(1234); + EXPECT_EQ(1234, z.GetInt()); + + // operator=(int) + z = 5678; + EXPECT_EQ(5678, z.GetInt()); + + // Templated functions + EXPECT_TRUE(z.Is<int>()); + EXPECT_EQ(5678, z.Get<int>()); + EXPECT_EQ(5679, z.Set(5679).Get<int>()); + EXPECT_EQ(5680, z.Set<int>(5680).Get<int>()); +} + +TEST(Value, Uint) { + // Constructor with int + Value x(1234u); + EXPECT_EQ(kNumberType, x.GetType()); + EXPECT_EQ(1234, x.GetInt()); + EXPECT_EQ(1234u, x.GetUint()); + EXPECT_EQ(1234, x.GetInt64()); + EXPECT_EQ(1234u, x.GetUint64()); + EXPECT_TRUE(x.IsNumber()); + EXPECT_TRUE(x.IsInt()); + EXPECT_TRUE(x.IsUint()); + EXPECT_TRUE(x.IsInt64()); + EXPECT_TRUE(x.IsUint64()); + EXPECT_NEAR(1234.0, x.GetDouble(), 0.0); // Number can always be cast as double but !IsDouble(). + + EXPECT_FALSE(x.IsDouble()); + EXPECT_FALSE(x.IsFloat()); + EXPECT_FALSE(x.IsNull()); + EXPECT_FALSE(x.IsBool()); + EXPECT_FALSE(x.IsFalse()); + EXPECT_FALSE(x.IsTrue()); + EXPECT_FALSE(x.IsString()); + EXPECT_FALSE(x.IsObject()); + EXPECT_FALSE(x.IsArray()); + + // SetUint() + Value z; + z.SetUint(1234); + EXPECT_EQ(1234u, z.GetUint()); + + // operator=(unsigned) + z = 5678u; + EXPECT_EQ(5678u, z.GetUint()); + + z = 2147483648u; // 2^31, cannot cast as int + EXPECT_EQ(2147483648u, z.GetUint()); + EXPECT_FALSE(z.IsInt()); + EXPECT_TRUE(z.IsInt64()); // Issue 41: Incorrect parsing of unsigned int number types + + // Templated functions + EXPECT_TRUE(z.Is<unsigned>()); + EXPECT_EQ(2147483648u, z.Get<unsigned>()); + EXPECT_EQ(2147483649u, z.Set(2147483649u).Get<unsigned>()); + EXPECT_EQ(2147483650u, z.Set<unsigned>(2147483650u).Get<unsigned>()); +} + +TEST(Value, Int64) { + // Constructor with int + Value x(int64_t(1234)); + EXPECT_EQ(kNumberType, x.GetType()); + EXPECT_EQ(1234, x.GetInt()); + EXPECT_EQ(1234u, x.GetUint()); + EXPECT_EQ(1234, x.GetInt64()); + EXPECT_EQ(1234u, x.GetUint64()); + EXPECT_TRUE(x.IsNumber()); + EXPECT_TRUE(x.IsInt()); + EXPECT_TRUE(x.IsUint()); + EXPECT_TRUE(x.IsInt64()); + EXPECT_TRUE(x.IsUint64()); + + EXPECT_FALSE(x.IsDouble()); + EXPECT_FALSE(x.IsFloat()); + EXPECT_FALSE(x.IsNull()); + EXPECT_FALSE(x.IsBool()); + EXPECT_FALSE(x.IsFalse()); + EXPECT_FALSE(x.IsTrue()); + EXPECT_FALSE(x.IsString()); + EXPECT_FALSE(x.IsObject()); + EXPECT_FALSE(x.IsArray()); + + Value nx(int64_t(-1234)); + EXPECT_EQ(-1234, nx.GetInt()); + EXPECT_EQ(-1234, nx.GetInt64()); + EXPECT_TRUE(nx.IsInt()); + EXPECT_TRUE(nx.IsInt64()); + EXPECT_FALSE(nx.IsUint()); + EXPECT_FALSE(nx.IsUint64()); + + // SetInt64() + Value z; + z.SetInt64(1234); + EXPECT_EQ(1234, z.GetInt64()); + + z.SetInt64(2147483648u); // 2^31, cannot cast as int + EXPECT_FALSE(z.IsInt()); + EXPECT_TRUE(z.IsUint()); + EXPECT_NEAR(2147483648.0, z.GetDouble(), 0.0); + + z.SetInt64(int64_t(4294967295u) + 1); // 2^32, cannot cast as uint + EXPECT_FALSE(z.IsInt()); + EXPECT_FALSE(z.IsUint()); + EXPECT_NEAR(4294967296.0, z.GetDouble(), 0.0); + + z.SetInt64(-int64_t(2147483648u) - 1); // -2^31-1, cannot cast as int + EXPECT_FALSE(z.IsInt()); + EXPECT_NEAR(-2147483649.0, z.GetDouble(), 0.0); + + int64_t i = static_cast<int64_t>(RAPIDJSON_UINT64_C2(0x80000000, 00000000)); + z.SetInt64(i); + EXPECT_DOUBLE_EQ(-9223372036854775808.0, z.GetDouble()); + + // Templated functions + EXPECT_TRUE(z.Is<int64_t>()); + EXPECT_EQ(i, z.Get<int64_t>()); +#if 0 // signed integer underflow is undefined behaviour + EXPECT_EQ(i - 1, z.Set(i - 1).Get<int64_t>()); + EXPECT_EQ(i - 2, z.Set<int64_t>(i - 2).Get<int64_t>()); +#endif +} + +TEST(Value, Uint64) { + // Constructor with int + Value x(uint64_t(1234)); + EXPECT_EQ(kNumberType, x.GetType()); + EXPECT_EQ(1234, x.GetInt()); + EXPECT_EQ(1234u, x.GetUint()); + EXPECT_EQ(1234, x.GetInt64()); + EXPECT_EQ(1234u, x.GetUint64()); + EXPECT_TRUE(x.IsNumber()); + EXPECT_TRUE(x.IsInt()); + EXPECT_TRUE(x.IsUint()); + EXPECT_TRUE(x.IsInt64()); + EXPECT_TRUE(x.IsUint64()); + + EXPECT_FALSE(x.IsDouble()); + EXPECT_FALSE(x.IsFloat()); + EXPECT_FALSE(x.IsNull()); + EXPECT_FALSE(x.IsBool()); + EXPECT_FALSE(x.IsFalse()); + EXPECT_FALSE(x.IsTrue()); + EXPECT_FALSE(x.IsString()); + EXPECT_FALSE(x.IsObject()); + EXPECT_FALSE(x.IsArray()); + + // SetUint64() + Value z; + z.SetUint64(1234); + EXPECT_EQ(1234u, z.GetUint64()); + + z.SetUint64(uint64_t(2147483648u)); // 2^31, cannot cast as int + EXPECT_FALSE(z.IsInt()); + EXPECT_TRUE(z.IsUint()); + EXPECT_TRUE(z.IsInt64()); + + z.SetUint64(uint64_t(4294967295u) + 1); // 2^32, cannot cast as uint + EXPECT_FALSE(z.IsInt()); + EXPECT_FALSE(z.IsUint()); + EXPECT_TRUE(z.IsInt64()); + + uint64_t u = RAPIDJSON_UINT64_C2(0x80000000, 0x00000000); + z.SetUint64(u); // 2^63 cannot cast as int64 + EXPECT_FALSE(z.IsInt64()); + EXPECT_EQ(u, z.GetUint64()); // Issue 48 + EXPECT_DOUBLE_EQ(9223372036854775808.0, z.GetDouble()); + + // Templated functions + EXPECT_TRUE(z.Is<uint64_t>()); + EXPECT_EQ(u, z.Get<uint64_t>()); + EXPECT_EQ(u + 1, z.Set(u + 1).Get<uint64_t>()); + EXPECT_EQ(u + 2, z.Set<uint64_t>(u + 2).Get<uint64_t>()); +} + +TEST(Value, Double) { + // Constructor with double + Value x(12.34); + EXPECT_EQ(kNumberType, x.GetType()); + EXPECT_NEAR(12.34, x.GetDouble(), 0.0); + EXPECT_TRUE(x.IsNumber()); + EXPECT_TRUE(x.IsDouble()); + + EXPECT_FALSE(x.IsInt()); + EXPECT_FALSE(x.IsNull()); + EXPECT_FALSE(x.IsBool()); + EXPECT_FALSE(x.IsFalse()); + EXPECT_FALSE(x.IsTrue()); + EXPECT_FALSE(x.IsString()); + EXPECT_FALSE(x.IsObject()); + EXPECT_FALSE(x.IsArray()); + + // SetDouble() + Value z; + z.SetDouble(12.34); + EXPECT_NEAR(12.34, z.GetDouble(), 0.0); + + z = 56.78; + EXPECT_NEAR(56.78, z.GetDouble(), 0.0); + + // Templated functions + EXPECT_TRUE(z.Is<double>()); + EXPECT_EQ(56.78, z.Get<double>()); + EXPECT_EQ(57.78, z.Set(57.78).Get<double>()); + EXPECT_EQ(58.78, z.Set<double>(58.78).Get<double>()); +} + +TEST(Value, Float) { + // Constructor with double + Value x(12.34f); + EXPECT_EQ(kNumberType, x.GetType()); + EXPECT_NEAR(12.34f, x.GetFloat(), 0.0); + EXPECT_TRUE(x.IsNumber()); + EXPECT_TRUE(x.IsDouble()); + EXPECT_TRUE(x.IsFloat()); + + EXPECT_FALSE(x.IsInt()); + EXPECT_FALSE(x.IsNull()); + EXPECT_FALSE(x.IsBool()); + EXPECT_FALSE(x.IsFalse()); + EXPECT_FALSE(x.IsTrue()); + EXPECT_FALSE(x.IsString()); + EXPECT_FALSE(x.IsObject()); + EXPECT_FALSE(x.IsArray()); + + // SetFloat() + Value z; + z.SetFloat(12.34f); + EXPECT_NEAR(12.34f, z.GetFloat(), 0.0f); + + // Issue 573 + z.SetInt(0); + EXPECT_EQ(0.0f, z.GetFloat()); + + z = 56.78f; + EXPECT_NEAR(56.78f, z.GetFloat(), 0.0f); + + // Templated functions + EXPECT_TRUE(z.Is<float>()); + EXPECT_EQ(56.78f, z.Get<float>()); + EXPECT_EQ(57.78f, z.Set(57.78f).Get<float>()); + EXPECT_EQ(58.78f, z.Set<float>(58.78f).Get<float>()); +} + +TEST(Value, IsLosslessDouble) { + EXPECT_TRUE(Value(0.0).IsLosslessDouble()); + EXPECT_TRUE(Value(12.34).IsLosslessDouble()); + EXPECT_TRUE(Value(-123).IsLosslessDouble()); + EXPECT_TRUE(Value(2147483648u).IsLosslessDouble()); + EXPECT_TRUE(Value(-static_cast<int64_t>(RAPIDJSON_UINT64_C2(0x40000000, 0x00000000))).IsLosslessDouble()); +#if !(defined(_MSC_VER) && _MSC_VER < 1800) // VC2010 has problem + EXPECT_TRUE(Value(RAPIDJSON_UINT64_C2(0xA0000000, 0x00000000)).IsLosslessDouble()); +#endif + + EXPECT_FALSE(Value(static_cast<int64_t>(RAPIDJSON_UINT64_C2(0x7FFFFFFF, 0xFFFFFFFF))).IsLosslessDouble()); // INT64_MAX + EXPECT_FALSE(Value(-static_cast<int64_t>(RAPIDJSON_UINT64_C2(0x7FFFFFFF, 0xFFFFFFFF))).IsLosslessDouble()); // -INT64_MAX + EXPECT_TRUE(Value(-static_cast<int64_t>(RAPIDJSON_UINT64_C2(0x7FFFFFFF, 0xFFFFFFFF)) - 1).IsLosslessDouble()); // INT64_MIN + EXPECT_FALSE(Value(RAPIDJSON_UINT64_C2(0xFFFFFFFF, 0xFFFFFFFF)).IsLosslessDouble()); // UINT64_MAX + + EXPECT_TRUE(Value(3.4028234e38f).IsLosslessDouble()); // FLT_MAX + EXPECT_TRUE(Value(-3.4028234e38f).IsLosslessDouble()); // -FLT_MAX + EXPECT_TRUE(Value(1.17549435e-38f).IsLosslessDouble()); // FLT_MIN + EXPECT_TRUE(Value(-1.17549435e-38f).IsLosslessDouble()); // -FLT_MIN + EXPECT_TRUE(Value(1.7976931348623157e+308).IsLosslessDouble()); // DBL_MAX + EXPECT_TRUE(Value(-1.7976931348623157e+308).IsLosslessDouble()); // -DBL_MAX + EXPECT_TRUE(Value(2.2250738585072014e-308).IsLosslessDouble()); // DBL_MIN + EXPECT_TRUE(Value(-2.2250738585072014e-308).IsLosslessDouble()); // -DBL_MIN +} + +TEST(Value, IsLosslessFloat) { + EXPECT_TRUE(Value(12.25).IsLosslessFloat()); + EXPECT_TRUE(Value(-123).IsLosslessFloat()); + EXPECT_TRUE(Value(2147483648u).IsLosslessFloat()); + EXPECT_TRUE(Value(3.4028234e38f).IsLosslessFloat()); + EXPECT_TRUE(Value(-3.4028234e38f).IsLosslessFloat()); + EXPECT_FALSE(Value(3.4028235e38).IsLosslessFloat()); + EXPECT_FALSE(Value(0.3).IsLosslessFloat()); +} + +TEST(Value, String) { + // Construction with const string + Value x("Hello", 5); // literal + EXPECT_EQ(kStringType, x.GetType()); + EXPECT_TRUE(x.IsString()); + EXPECT_STREQ("Hello", x.GetString()); + EXPECT_EQ(5u, x.GetStringLength()); + + EXPECT_FALSE(x.IsNumber()); + EXPECT_FALSE(x.IsNull()); + EXPECT_FALSE(x.IsBool()); + EXPECT_FALSE(x.IsFalse()); + EXPECT_FALSE(x.IsTrue()); + EXPECT_FALSE(x.IsObject()); + EXPECT_FALSE(x.IsArray()); + + static const char cstr[] = "World"; // const array + Value(cstr).Swap(x); + EXPECT_TRUE(x.IsString()); + EXPECT_EQ(x.GetString(), cstr); + EXPECT_EQ(x.GetStringLength(), sizeof(cstr)-1); + + static char mstr[] = "Howdy"; // non-const array + // Value(mstr).Swap(x); // should not compile + Value(StringRef(mstr)).Swap(x); + EXPECT_TRUE(x.IsString()); + EXPECT_EQ(x.GetString(), mstr); + EXPECT_EQ(x.GetStringLength(), sizeof(mstr)-1); + strncpy(mstr,"Hello", sizeof(mstr)); + EXPECT_STREQ(x.GetString(), "Hello"); + + const char* pstr = cstr; + //Value(pstr).Swap(x); // should not compile + Value(StringRef(pstr)).Swap(x); + EXPECT_TRUE(x.IsString()); + EXPECT_EQ(x.GetString(), cstr); + EXPECT_EQ(x.GetStringLength(), sizeof(cstr)-1); + + char* mpstr = mstr; + Value(StringRef(mpstr,sizeof(mstr)-1)).Swap(x); + EXPECT_TRUE(x.IsString()); + EXPECT_EQ(x.GetString(), mstr); + EXPECT_EQ(x.GetStringLength(), 5u); + EXPECT_STREQ(x.GetString(), "Hello"); + + // Constructor with copy string + MemoryPoolAllocator<> allocator; + Value c(x.GetString(), x.GetStringLength(), allocator); + EXPECT_NE(x.GetString(), c.GetString()); + EXPECT_EQ(x.GetStringLength(), c.GetStringLength()); + EXPECT_STREQ(x.GetString(), c.GetString()); + //x.SetString("World"); + x.SetString("World", 5); + EXPECT_STREQ("Hello", c.GetString()); + EXPECT_EQ(5u, c.GetStringLength()); + + // Constructor with type + Value y(kStringType); + EXPECT_TRUE(y.IsString()); + EXPECT_STREQ("", y.GetString()); // Empty string should be "" instead of 0 (issue 226) + EXPECT_EQ(0u, y.GetStringLength()); + + // SetConsttring() + Value z; + z.SetString("Hello"); + EXPECT_TRUE(x.IsString()); + z.SetString("Hello", 5); + EXPECT_STREQ("Hello", z.GetString()); + EXPECT_STREQ("Hello", z.GetString()); + EXPECT_EQ(5u, z.GetStringLength()); + + z.SetString("Hello"); + EXPECT_TRUE(z.IsString()); + EXPECT_STREQ("Hello", z.GetString()); + + //z.SetString(mstr); // should not compile + //z.SetString(pstr); // should not compile + z.SetString(StringRef(mstr)); + EXPECT_TRUE(z.IsString()); + EXPECT_STREQ(z.GetString(), mstr); + + z.SetString(cstr); + EXPECT_TRUE(z.IsString()); + EXPECT_EQ(cstr, z.GetString()); + + z = cstr; + EXPECT_TRUE(z.IsString()); + EXPECT_EQ(cstr, z.GetString()); + + // SetString() + char s[] = "World"; + Value w; + w.SetString(s, static_cast<SizeType>(strlen(s)), allocator); + s[0] = '\0'; + EXPECT_STREQ("World", w.GetString()); + EXPECT_EQ(5u, w.GetStringLength()); + + // templated functions + EXPECT_TRUE(z.Is<const char*>()); + EXPECT_STREQ(cstr, z.Get<const char*>()); + EXPECT_STREQ("Apple", z.Set<const char*>("Apple").Get<const char*>()); + +#if RAPIDJSON_HAS_STDSTRING + { + std::string str = "Hello World"; + str[5] = '\0'; + EXPECT_STREQ(str.data(),"Hello"); // embedded '\0' + EXPECT_EQ(str.size(), 11u); + + // no copy + Value vs0(StringRef(str)); + EXPECT_TRUE(vs0.IsString()); + EXPECT_EQ(vs0.GetString(), str.data()); + EXPECT_EQ(vs0.GetStringLength(), str.size()); + TestEqual(vs0, str); + + // do copy + Value vs1(str, allocator); + EXPECT_TRUE(vs1.IsString()); + EXPECT_NE(vs1.GetString(), str.data()); + EXPECT_NE(vs1.GetString(), str); // not equal due to embedded '\0' + EXPECT_EQ(vs1.GetStringLength(), str.size()); + TestEqual(vs1, str); + + // SetString + str = "World"; + vs0.SetNull().SetString(str, allocator); + EXPECT_TRUE(vs0.IsString()); + EXPECT_STREQ(vs0.GetString(), str.c_str()); + EXPECT_EQ(vs0.GetStringLength(), str.size()); + TestEqual(str, vs0); + TestUnequal(str, vs1); + + // vs1 = str; // should not compile + vs1 = StringRef(str); + TestEqual(str, vs1); + TestEqual(vs0, vs1); + + // Templated function. + EXPECT_TRUE(vs0.Is<std::string>()); + EXPECT_EQ(str, vs0.Get<std::string>()); + vs0.Set<std::string>(std::string("Apple"), allocator); + EXPECT_EQ(std::string("Apple"), vs0.Get<std::string>()); + vs0.Set(std::string("Orange"), allocator); + EXPECT_EQ(std::string("Orange"), vs0.Get<std::string>()); + } +#endif // RAPIDJSON_HAS_STDSTRING +} + +// Issue 226: Value of string type should not point to NULL +TEST(Value, SetStringNullException) { + Value v; + EXPECT_THROW(v.SetString(0, 0), AssertException); +} + +template <typename T, typename Allocator> +static void TestArray(T& x, Allocator& allocator) { + const T& y = x; + + // PushBack() + Value v; + x.PushBack(v, allocator); + v.SetBool(true); + x.PushBack(v, allocator); + v.SetBool(false); + x.PushBack(v, allocator); + v.SetInt(123); + x.PushBack(v, allocator); + //x.PushBack((const char*)"foo", allocator); // should not compile + x.PushBack("foo", allocator); + + EXPECT_FALSE(x.Empty()); + EXPECT_EQ(5u, x.Size()); + EXPECT_FALSE(y.Empty()); + EXPECT_EQ(5u, y.Size()); + EXPECT_TRUE(x[SizeType(0)].IsNull()); + EXPECT_TRUE(x[1].IsTrue()); + EXPECT_TRUE(x[2].IsFalse()); + EXPECT_TRUE(x[3].IsInt()); + EXPECT_EQ(123, x[3].GetInt()); + EXPECT_TRUE(y[SizeType(0)].IsNull()); + EXPECT_TRUE(y[1].IsTrue()); + EXPECT_TRUE(y[2].IsFalse()); + EXPECT_TRUE(y[3].IsInt()); + EXPECT_EQ(123, y[3].GetInt()); + EXPECT_TRUE(y[4].IsString()); + EXPECT_STREQ("foo", y[4].GetString()); + +#if RAPIDJSON_HAS_CXX11_RVALUE_REFS + // PushBack(GenericValue&&, Allocator&); + { + Value y2(kArrayType); + y2.PushBack(Value(true), allocator); + y2.PushBack(std::move(Value(kArrayType).PushBack(Value(1), allocator).PushBack("foo", allocator)), allocator); + EXPECT_EQ(2u, y2.Size()); + EXPECT_TRUE(y2[0].IsTrue()); + EXPECT_TRUE(y2[1].IsArray()); + EXPECT_EQ(2u, y2[1].Size()); + EXPECT_TRUE(y2[1][0].IsInt()); + EXPECT_TRUE(y2[1][1].IsString()); + } +#endif + + // iterator + typename T::ValueIterator itr = x.Begin(); + EXPECT_TRUE(itr != x.End()); + EXPECT_TRUE(itr->IsNull()); + ++itr; + EXPECT_TRUE(itr != x.End()); + EXPECT_TRUE(itr->IsTrue()); + ++itr; + EXPECT_TRUE(itr != x.End()); + EXPECT_TRUE(itr->IsFalse()); + ++itr; + EXPECT_TRUE(itr != x.End()); + EXPECT_TRUE(itr->IsInt()); + EXPECT_EQ(123, itr->GetInt()); + ++itr; + EXPECT_TRUE(itr != x.End()); + EXPECT_TRUE(itr->IsString()); + EXPECT_STREQ("foo", itr->GetString()); + + // const iterator + typename T::ConstValueIterator citr = y.Begin(); + EXPECT_TRUE(citr != y.End()); + EXPECT_TRUE(citr->IsNull()); + ++citr; + EXPECT_TRUE(citr != y.End()); + EXPECT_TRUE(citr->IsTrue()); + ++citr; + EXPECT_TRUE(citr != y.End()); + EXPECT_TRUE(citr->IsFalse()); + ++citr; + EXPECT_TRUE(citr != y.End()); + EXPECT_TRUE(citr->IsInt()); + EXPECT_EQ(123, citr->GetInt()); + ++citr; + EXPECT_TRUE(citr != y.End()); + EXPECT_TRUE(citr->IsString()); + EXPECT_STREQ("foo", citr->GetString()); + + // PopBack() + x.PopBack(); + EXPECT_EQ(4u, x.Size()); + EXPECT_TRUE(y[SizeType(0)].IsNull()); + EXPECT_TRUE(y[1].IsTrue()); + EXPECT_TRUE(y[2].IsFalse()); + EXPECT_TRUE(y[3].IsInt()); + + // Clear() + x.Clear(); + EXPECT_TRUE(x.Empty()); + EXPECT_EQ(0u, x.Size()); + EXPECT_TRUE(y.Empty()); + EXPECT_EQ(0u, y.Size()); + + // Erase(ValueIterator) + + // Use array of array to ensure removed elements' destructor is called. + // [[0],[1],[2],...] + for (int i = 0; i < 10; i++) + x.PushBack(Value(kArrayType).PushBack(i, allocator).Move(), allocator); + + // Erase the first + itr = x.Erase(x.Begin()); + EXPECT_EQ(x.Begin(), itr); + EXPECT_EQ(9u, x.Size()); + for (int i = 0; i < 9; i++) + EXPECT_EQ(i + 1, x[static_cast<SizeType>(i)][0].GetInt()); + + // Ease the last + itr = x.Erase(x.End() - 1); + EXPECT_EQ(x.End(), itr); + EXPECT_EQ(8u, x.Size()); + for (int i = 0; i < 8; i++) + EXPECT_EQ(i + 1, x[static_cast<SizeType>(i)][0].GetInt()); + + // Erase the middle + itr = x.Erase(x.Begin() + 4); + EXPECT_EQ(x.Begin() + 4, itr); + EXPECT_EQ(7u, x.Size()); + for (int i = 0; i < 4; i++) + EXPECT_EQ(i + 1, x[static_cast<SizeType>(i)][0].GetInt()); + for (int i = 4; i < 7; i++) + EXPECT_EQ(i + 2, x[static_cast<SizeType>(i)][0].GetInt()); + + // Erase(ValueIterator, ValueIterator) + // Exhaustive test with all 0 <= first < n, first <= last <= n cases + const unsigned n = 10; + for (unsigned first = 0; first < n; first++) { + for (unsigned last = first; last <= n; last++) { + x.Clear(); + for (unsigned i = 0; i < n; i++) + x.PushBack(Value(kArrayType).PushBack(i, allocator).Move(), allocator); + + itr = x.Erase(x.Begin() + first, x.Begin() + last); + if (last == n) + EXPECT_EQ(x.End(), itr); + else + EXPECT_EQ(x.Begin() + first, itr); + + size_t removeCount = last - first; + EXPECT_EQ(n - removeCount, x.Size()); + for (unsigned i = 0; i < first; i++) + EXPECT_EQ(i, x[i][0].GetUint()); + for (unsigned i = first; i < n - removeCount; i++) + EXPECT_EQ(i + removeCount, x[static_cast<SizeType>(i)][0].GetUint()); + } + } +} + +TEST(Value, Array) { + Value x(kArrayType); + const Value& y = x; + Value::AllocatorType allocator; + + EXPECT_EQ(kArrayType, x.GetType()); + EXPECT_TRUE(x.IsArray()); + EXPECT_TRUE(x.Empty()); + EXPECT_EQ(0u, x.Size()); + EXPECT_TRUE(y.IsArray()); + EXPECT_TRUE(y.Empty()); + EXPECT_EQ(0u, y.Size()); + + EXPECT_FALSE(x.IsNull()); + EXPECT_FALSE(x.IsBool()); + EXPECT_FALSE(x.IsFalse()); + EXPECT_FALSE(x.IsTrue()); + EXPECT_FALSE(x.IsString()); + EXPECT_FALSE(x.IsObject()); + + TestArray(x, allocator); + + // Working in gcc without C++11, but VS2013 cannot compile. To be diagnosed. + // http://en.wikipedia.org/wiki/Erase-remove_idiom + x.Clear(); + for (int i = 0; i < 10; i++) + if (i % 2 == 0) + x.PushBack(i, allocator); + else + x.PushBack(Value(kNullType).Move(), allocator); + + const Value null(kNullType); + x.Erase(std::remove(x.Begin(), x.End(), null), x.End()); + EXPECT_EQ(5u, x.Size()); + for (int i = 0; i < 5; i++) + EXPECT_EQ(i * 2, x[static_cast<SizeType>(i)]); + + // SetArray() + Value z; + z.SetArray(); + EXPECT_TRUE(z.IsArray()); + EXPECT_TRUE(z.Empty()); +} + +TEST(Value, ArrayHelper) { + Value::AllocatorType allocator; + { + Value x(kArrayType); + Value::Array a = x.GetArray(); + TestArray(a, allocator); + } + + { + Value x(kArrayType); + Value::Array a = x.GetArray(); + a.PushBack(1, allocator); + + Value::Array a2(a); // copy constructor + EXPECT_EQ(1, a2.Size()); + + Value::Array a3 = a; + EXPECT_EQ(1, a3.Size()); + + Value::ConstArray y = static_cast<const Value&>(x).GetArray(); + (void)y; + // y.PushBack(1, allocator); // should not compile + + // Templated functions + x.Clear(); + EXPECT_TRUE(x.Is<Value::Array>()); + EXPECT_TRUE(x.Is<Value::ConstArray>()); + a.PushBack(1, allocator); + EXPECT_EQ(1, x.Get<Value::Array>()[0].GetInt()); + EXPECT_EQ(1, x.Get<Value::ConstArray>()[0].GetInt()); + + Value x2; + x2.Set<Value::Array>(a); + EXPECT_TRUE(x.IsArray()); // IsArray() is invariant after moving. + EXPECT_EQ(1, x2.Get<Value::Array>()[0].GetInt()); + } + + { + Value y(kArrayType); + y.PushBack(123, allocator); + + Value x(y.GetArray()); // Construct value form array. + EXPECT_TRUE(x.IsArray()); + EXPECT_EQ(123, x[0].GetInt()); + EXPECT_TRUE(y.IsArray()); // Invariant + EXPECT_TRUE(y.Empty()); + } + + { + Value x(kArrayType); + Value y(kArrayType); + y.PushBack(123, allocator); + x.PushBack(y.GetArray(), allocator); // Implicit constructor to convert Array to GenericValue + + EXPECT_EQ(1, x.Size()); + EXPECT_EQ(123, x[0][0].GetInt()); + EXPECT_TRUE(y.IsArray()); + EXPECT_TRUE(y.Empty()); + } +} + +#if RAPIDJSON_HAS_CXX11_RANGE_FOR +TEST(Value, ArrayHelperRangeFor) { + Value::AllocatorType allocator; + Value x(kArrayType); + + for (int i = 0; i < 10; i++) + x.PushBack(i, allocator); + + { + int i = 0; + for (auto& v : x.GetArray()) { + EXPECT_EQ(i, v.GetInt()); + i++; + } + EXPECT_EQ(i, 10); + } + { + int i = 0; + for (const auto& v : const_cast<const Value&>(x).GetArray()) { + EXPECT_EQ(i, v.GetInt()); + i++; + } + EXPECT_EQ(i, 10); + } + + // Array a = x.GetArray(); + // Array ca = const_cast<const Value&>(x).GetArray(); +} +#endif + +template <typename T, typename Allocator> +static void TestObject(T& x, Allocator& allocator) { + const T& y = x; // const version + + // AddMember() + x.AddMember("A", "Apple", allocator); + EXPECT_FALSE(x.ObjectEmpty()); + EXPECT_EQ(1u, x.MemberCount()); + + Value value("Banana", 6); + x.AddMember("B", "Banana", allocator); + EXPECT_EQ(2u, x.MemberCount()); + + // AddMember<T>(StringRefType, T, Allocator) + { + Value o(kObjectType); + o.AddMember("true", true, allocator); + o.AddMember("false", false, allocator); + o.AddMember("int", -1, allocator); + o.AddMember("uint", 1u, allocator); + o.AddMember("int64", int64_t(-4294967296), allocator); + o.AddMember("uint64", uint64_t(4294967296), allocator); + o.AddMember("double", 3.14, allocator); + o.AddMember("string", "Jelly", allocator); + + EXPECT_TRUE(o["true"].GetBool()); + EXPECT_FALSE(o["false"].GetBool()); + EXPECT_EQ(-1, o["int"].GetInt()); + EXPECT_EQ(1u, o["uint"].GetUint()); + EXPECT_EQ(int64_t(-4294967296), o["int64"].GetInt64()); + EXPECT_EQ(uint64_t(4294967296), o["uint64"].GetUint64()); + EXPECT_STREQ("Jelly",o["string"].GetString()); + EXPECT_EQ(8u, o.MemberCount()); + } + + // AddMember<T>(Value&, T, Allocator) + { + Value o(kObjectType); + + Value n("s"); + o.AddMember(n, "string", allocator); + EXPECT_EQ(1u, o.MemberCount()); + + Value count("#"); + o.AddMember(count, o.MemberCount(), allocator); + EXPECT_EQ(2u, o.MemberCount()); + } + +#if RAPIDJSON_HAS_STDSTRING + { + // AddMember(StringRefType, const std::string&, Allocator) + Value o(kObjectType); + o.AddMember("b", std::string("Banana"), allocator); + EXPECT_STREQ("Banana", o["b"].GetString()); + + // RemoveMember(const std::string&) + o.RemoveMember(std::string("b")); + EXPECT_TRUE(o.ObjectEmpty()); + } +#endif + +#if RAPIDJSON_HAS_CXX11_RVALUE_REFS + // AddMember(GenericValue&&, ...) variants + { + Value o(kObjectType); + o.AddMember(Value("true"), Value(true), allocator); + o.AddMember(Value("false"), Value(false).Move(), allocator); // value is lvalue ref + o.AddMember(Value("int").Move(), Value(-1), allocator); // name is lvalue ref + o.AddMember("uint", std::move(Value().SetUint(1u)), allocator); // name is literal, value is rvalue + EXPECT_TRUE(o["true"].GetBool()); + EXPECT_FALSE(o["false"].GetBool()); + EXPECT_EQ(-1, o["int"].GetInt()); + EXPECT_EQ(1u, o["uint"].GetUint()); + EXPECT_EQ(4u, o.MemberCount()); + } +#endif + + // Tests a member with null character + Value name; + const Value C0D("C\0D", 3); + name.SetString(C0D.GetString(), 3); + value.SetString("CherryD", 7); + x.AddMember(name, value, allocator); + + // HasMember() + EXPECT_TRUE(x.HasMember("A")); + EXPECT_TRUE(x.HasMember("B")); + EXPECT_TRUE(y.HasMember("A")); + EXPECT_TRUE(y.HasMember("B")); + +#if RAPIDJSON_HAS_STDSTRING + EXPECT_TRUE(x.HasMember(std::string("A"))); +#endif + + name.SetString("C\0D"); + EXPECT_TRUE(x.HasMember(name)); + EXPECT_TRUE(y.HasMember(name)); + + GenericValue<UTF8<>, CrtAllocator> othername("A"); + EXPECT_TRUE(x.HasMember(othername)); + EXPECT_TRUE(y.HasMember(othername)); + othername.SetString("C\0D"); + EXPECT_TRUE(x.HasMember(othername)); + EXPECT_TRUE(y.HasMember(othername)); + + // operator[] + EXPECT_STREQ("Apple", x["A"].GetString()); + EXPECT_STREQ("Banana", x["B"].GetString()); + EXPECT_STREQ("CherryD", x[C0D].GetString()); + EXPECT_STREQ("CherryD", x[othername].GetString()); + EXPECT_THROW(x["nonexist"], AssertException); + + // const operator[] + EXPECT_STREQ("Apple", y["A"].GetString()); + EXPECT_STREQ("Banana", y["B"].GetString()); + EXPECT_STREQ("CherryD", y[C0D].GetString()); + +#if RAPIDJSON_HAS_STDSTRING + EXPECT_STREQ("Apple", x["A"].GetString()); + EXPECT_STREQ("Apple", y[std::string("A")].GetString()); +#endif + + // member iterator + Value::MemberIterator itr = x.MemberBegin(); + EXPECT_TRUE(itr != x.MemberEnd()); + EXPECT_STREQ("A", itr->name.GetString()); + EXPECT_STREQ("Apple", itr->value.GetString()); + ++itr; + EXPECT_TRUE(itr != x.MemberEnd()); + EXPECT_STREQ("B", itr->name.GetString()); + EXPECT_STREQ("Banana", itr->value.GetString()); + ++itr; + EXPECT_TRUE(itr != x.MemberEnd()); + EXPECT_TRUE(memcmp(itr->name.GetString(), "C\0D", 4) == 0); + EXPECT_STREQ("CherryD", itr->value.GetString()); + ++itr; + EXPECT_FALSE(itr != x.MemberEnd()); + + // const member iterator + Value::ConstMemberIterator citr = y.MemberBegin(); + EXPECT_TRUE(citr != y.MemberEnd()); + EXPECT_STREQ("A", citr->name.GetString()); + EXPECT_STREQ("Apple", citr->value.GetString()); + ++citr; + EXPECT_TRUE(citr != y.MemberEnd()); + EXPECT_STREQ("B", citr->name.GetString()); + EXPECT_STREQ("Banana", citr->value.GetString()); + ++citr; + EXPECT_TRUE(citr != y.MemberEnd()); + EXPECT_TRUE(memcmp(citr->name.GetString(), "C\0D", 4) == 0); + EXPECT_STREQ("CherryD", citr->value.GetString()); + ++citr; + EXPECT_FALSE(citr != y.MemberEnd()); + + // member iterator conversions/relations + itr = x.MemberBegin(); + citr = x.MemberBegin(); // const conversion + TestEqual(itr, citr); + EXPECT_TRUE(itr < x.MemberEnd()); + EXPECT_FALSE(itr > y.MemberEnd()); + EXPECT_TRUE(citr < x.MemberEnd()); + EXPECT_FALSE(citr > y.MemberEnd()); + ++citr; + TestUnequal(itr, citr); + EXPECT_FALSE(itr < itr); + EXPECT_TRUE(itr < citr); + EXPECT_FALSE(itr > itr); + EXPECT_TRUE(citr > itr); + EXPECT_EQ(1, citr - x.MemberBegin()); + EXPECT_EQ(0, itr - y.MemberBegin()); + itr += citr - x.MemberBegin(); + EXPECT_EQ(1, itr - y.MemberBegin()); + TestEqual(citr, itr); + EXPECT_TRUE(itr <= citr); + EXPECT_TRUE(citr <= itr); + itr++; + EXPECT_TRUE(itr >= citr); + EXPECT_FALSE(citr >= itr); + + // RemoveMember() + EXPECT_TRUE(x.RemoveMember("A")); + EXPECT_FALSE(x.HasMember("A")); + + EXPECT_TRUE(x.RemoveMember("B")); + EXPECT_FALSE(x.HasMember("B")); + + EXPECT_FALSE(x.RemoveMember("nonexist")); + + EXPECT_TRUE(x.RemoveMember(othername)); + EXPECT_FALSE(x.HasMember(name)); + + EXPECT_TRUE(x.MemberBegin() == x.MemberEnd()); + + // EraseMember(ConstMemberIterator) + + // Use array members to ensure removed elements' destructor is called. + // { "a": [0], "b": [1],[2],...] + const char keys[][2] = { "a", "b", "c", "d", "e", "f", "g", "h", "i", "j" }; + for (int i = 0; i < 10; i++) + x.AddMember(keys[i], Value(kArrayType).PushBack(i, allocator), allocator); + + // MemberCount, iterator difference + EXPECT_EQ(x.MemberCount(), SizeType(x.MemberEnd() - x.MemberBegin())); + + // Erase the first + itr = x.EraseMember(x.MemberBegin()); + EXPECT_FALSE(x.HasMember(keys[0])); + EXPECT_EQ(x.MemberBegin(), itr); + EXPECT_EQ(9u, x.MemberCount()); + for (; itr != x.MemberEnd(); ++itr) { + size_t i = static_cast<size_t>((itr - x.MemberBegin())) + 1; + EXPECT_STREQ(itr->name.GetString(), keys[i]); + EXPECT_EQ(i, itr->value[0].GetInt()); + } + + // Erase the last + itr = x.EraseMember(x.MemberEnd() - 1); + EXPECT_FALSE(x.HasMember(keys[9])); + EXPECT_EQ(x.MemberEnd(), itr); + EXPECT_EQ(8u, x.MemberCount()); + for (; itr != x.MemberEnd(); ++itr) { + size_t i = static_cast<size_t>(itr - x.MemberBegin()) + 1; + EXPECT_STREQ(itr->name.GetString(), keys[i]); + EXPECT_EQ(i, itr->value[0].GetInt()); + } + + // Erase the middle + itr = x.EraseMember(x.MemberBegin() + 4); + EXPECT_FALSE(x.HasMember(keys[5])); + EXPECT_EQ(x.MemberBegin() + 4, itr); + EXPECT_EQ(7u, x.MemberCount()); + for (; itr != x.MemberEnd(); ++itr) { + size_t i = static_cast<size_t>(itr - x.MemberBegin()); + i += (i < 4) ? 1 : 2; + EXPECT_STREQ(itr->name.GetString(), keys[i]); + EXPECT_EQ(i, itr->value[0].GetInt()); + } + + // EraseMember(ConstMemberIterator, ConstMemberIterator) + // Exhaustive test with all 0 <= first < n, first <= last <= n cases + const unsigned n = 10; + for (unsigned first = 0; first < n; first++) { + for (unsigned last = first; last <= n; last++) { + x.RemoveAllMembers(); + for (unsigned i = 0; i < n; i++) + x.AddMember(keys[i], Value(kArrayType).PushBack(i, allocator), allocator); + + itr = x.EraseMember(x.MemberBegin() + static_cast<int>(first), x.MemberBegin() + static_cast<int>(last)); + if (last == n) + EXPECT_EQ(x.MemberEnd(), itr); + else + EXPECT_EQ(x.MemberBegin() + static_cast<int>(first), itr); + + size_t removeCount = last - first; + EXPECT_EQ(n - removeCount, x.MemberCount()); + for (unsigned i = 0; i < first; i++) + EXPECT_EQ(i, x[keys[i]][0].GetUint()); + for (unsigned i = first; i < n - removeCount; i++) + EXPECT_EQ(i + removeCount, x[keys[i+removeCount]][0].GetUint()); + } + } + + // RemoveAllMembers() + x.RemoveAllMembers(); + EXPECT_TRUE(x.ObjectEmpty()); + EXPECT_EQ(0u, x.MemberCount()); +} + +TEST(Value, Object) { + Value x(kObjectType); + const Value& y = x; // const version + Value::AllocatorType allocator; + + EXPECT_EQ(kObjectType, x.GetType()); + EXPECT_TRUE(x.IsObject()); + EXPECT_TRUE(x.ObjectEmpty()); + EXPECT_EQ(0u, x.MemberCount()); + EXPECT_EQ(kObjectType, y.GetType()); + EXPECT_TRUE(y.IsObject()); + EXPECT_TRUE(y.ObjectEmpty()); + EXPECT_EQ(0u, y.MemberCount()); + + TestObject(x, allocator); + + // SetObject() + Value z; + z.SetObject(); + EXPECT_TRUE(z.IsObject()); +} + +TEST(Value, ObjectHelper) { + Value::AllocatorType allocator; + { + Value x(kObjectType); + Value::Object o = x.GetObject(); + TestObject(o, allocator); + } + + { + Value x(kObjectType); + Value::Object o = x.GetObject(); + o.AddMember("1", 1, allocator); + + Value::Object o2(o); // copy constructor + EXPECT_EQ(1, o2.MemberCount()); + + Value::Object o3 = o; + EXPECT_EQ(1, o3.MemberCount()); + + Value::ConstObject y = static_cast<const Value&>(x).GetObject(); + (void)y; + // y.AddMember("1", 1, allocator); // should not compile + + // Templated functions + x.RemoveAllMembers(); + EXPECT_TRUE(x.Is<Value::Object>()); + EXPECT_TRUE(x.Is<Value::ConstObject>()); + o.AddMember("1", 1, allocator); + EXPECT_EQ(1, x.Get<Value::Object>()["1"].GetInt()); + EXPECT_EQ(1, x.Get<Value::ConstObject>()["1"].GetInt()); + + Value x2; + x2.Set<Value::Object>(o); + EXPECT_TRUE(x.IsObject()); // IsObject() is invariant after moving + EXPECT_EQ(1, x2.Get<Value::Object>()["1"].GetInt()); + } + + { + Value x(kObjectType); + x.AddMember("a", "apple", allocator); + Value y(x.GetObject()); + EXPECT_STREQ("apple", y["a"].GetString()); + EXPECT_TRUE(x.IsObject()); // Invariant + } + + { + Value x(kObjectType); + x.AddMember("a", "apple", allocator); + Value y(kObjectType); + y.AddMember("fruits", x.GetObject(), allocator); + EXPECT_STREQ("apple", y["fruits"]["a"].GetString()); + EXPECT_TRUE(x.IsObject()); // Invariant + } +} + +#if RAPIDJSON_HAS_CXX11_RANGE_FOR +TEST(Value, ObjectHelperRangeFor) { + Value::AllocatorType allocator; + Value x(kObjectType); + + for (int i = 0; i < 10; i++) { + char name[10]; + Value n(name, static_cast<SizeType>(sprintf(name, "%d", i)), allocator); + x.AddMember(n, i, allocator); + } + + { + int i = 0; + for (auto& m : x.GetObject()) { + char name[10]; + sprintf(name, "%d", i); + EXPECT_STREQ(name, m.name.GetString()); + EXPECT_EQ(i, m.value.GetInt()); + i++; + } + EXPECT_EQ(i, 10); + } + { + int i = 0; + for (const auto& m : const_cast<const Value&>(x).GetObject()) { + char name[10]; + sprintf(name, "%d", i); + EXPECT_STREQ(name, m.name.GetString()); + EXPECT_EQ(i, m.value.GetInt()); + i++; + } + EXPECT_EQ(i, 10); + } + + // Object a = x.GetObject(); + // Object ca = const_cast<const Value&>(x).GetObject(); +} +#endif + +TEST(Value, EraseMember_String) { + Value::AllocatorType allocator; + Value x(kObjectType); + x.AddMember("A", "Apple", allocator); + x.AddMember("B", "Banana", allocator); + + EXPECT_TRUE(x.EraseMember("B")); + EXPECT_FALSE(x.HasMember("B")); + + EXPECT_FALSE(x.EraseMember("nonexist")); + + GenericValue<UTF8<>, CrtAllocator> othername("A"); + EXPECT_TRUE(x.EraseMember(othername)); + EXPECT_FALSE(x.HasMember("A")); + + EXPECT_TRUE(x.MemberBegin() == x.MemberEnd()); +} + +TEST(Value, BigNestedArray) { + MemoryPoolAllocator<> allocator; + Value x(kArrayType); + static const SizeType n = 200; + + for (SizeType i = 0; i < n; i++) { + Value y(kArrayType); + for (SizeType j = 0; j < n; j++) { + Value number(static_cast<int>(i * n + j)); + y.PushBack(number, allocator); + } + x.PushBack(y, allocator); + } + + for (SizeType i = 0; i < n; i++) + for (SizeType j = 0; j < n; j++) { + EXPECT_TRUE(x[i][j].IsInt()); + EXPECT_EQ(static_cast<int>(i * n + j), x[i][j].GetInt()); + } +} + +TEST(Value, BigNestedObject) { + MemoryPoolAllocator<> allocator; + Value x(kObjectType); + static const SizeType n = 200; + + for (SizeType i = 0; i < n; i++) { + char name1[10]; + sprintf(name1, "%d", i); + + // Value name(name1); // should not compile + Value name(name1, static_cast<SizeType>(strlen(name1)), allocator); + Value object(kObjectType); + + for (SizeType j = 0; j < n; j++) { + char name2[10]; + sprintf(name2, "%d", j); + + Value name3(name2, static_cast<SizeType>(strlen(name2)), allocator); + Value number(static_cast<int>(i * n + j)); + object.AddMember(name3, number, allocator); + } + + // x.AddMember(name1, object, allocator); // should not compile + x.AddMember(name, object, allocator); + } + + for (SizeType i = 0; i < n; i++) { + char name1[10]; + sprintf(name1, "%d", i); + + for (SizeType j = 0; j < n; j++) { + char name2[10]; + sprintf(name2, "%d", j); + x[name1]; + EXPECT_EQ(static_cast<int>(i * n + j), x[name1][name2].GetInt()); + } + } +} + +// Issue 18: Error removing last element of object +// http://code.google.com/p/rapidjson/issues/detail?id=18 +TEST(Value, RemoveLastElement) { + rapidjson::Document doc; + rapidjson::Document::AllocatorType& allocator = doc.GetAllocator(); + rapidjson::Value objVal(rapidjson::kObjectType); + objVal.AddMember("var1", 123, allocator); + objVal.AddMember("var2", "444", allocator); + objVal.AddMember("var3", 555, allocator); + EXPECT_TRUE(objVal.HasMember("var3")); + objVal.RemoveMember("var3"); // Assertion here in r61 + EXPECT_FALSE(objVal.HasMember("var3")); +} + +// Issue 38: Segmentation fault with CrtAllocator +TEST(Document, CrtAllocator) { + typedef GenericValue<UTF8<>, CrtAllocator> V; + + V::AllocatorType allocator; + V o(kObjectType); + o.AddMember("x", 1, allocator); // Should not call destructor on uninitialized name/value of newly allocated members. + + V a(kArrayType); + a.PushBack(1, allocator); // Should not call destructor on uninitialized Value of newly allocated elements. +} + +static void TestShortStringOptimization(const char* str) { + const rapidjson::SizeType len = static_cast<rapidjson::SizeType>(strlen(str)); + + rapidjson::Document doc; + rapidjson::Value val; + val.SetString(str, len, doc.GetAllocator()); + + EXPECT_EQ(val.GetStringLength(), len); + EXPECT_STREQ(val.GetString(), str); +} + +TEST(Value, AllocateShortString) { + TestShortStringOptimization(""); // edge case: empty string + TestShortStringOptimization("12345678"); // regular case for short strings: 8 chars + TestShortStringOptimization("12345678901"); // edge case: 11 chars in 32-bit mode (=> short string) + TestShortStringOptimization("123456789012"); // edge case: 12 chars in 32-bit mode (=> regular string) + TestShortStringOptimization("123456789012345"); // edge case: 15 chars in 64-bit mode (=> short string) + TestShortStringOptimization("1234567890123456"); // edge case: 16 chars in 64-bit mode (=> regular string) +} + +template <int e> +struct TerminateHandler { + bool Null() { return e != 0; } + bool Bool(bool) { return e != 1; } + bool Int(int) { return e != 2; } + bool Uint(unsigned) { return e != 3; } + bool Int64(int64_t) { return e != 4; } + bool Uint64(uint64_t) { return e != 5; } + bool Double(double) { return e != 6; } + bool RawNumber(const char*, SizeType, bool) { return e != 7; } + bool String(const char*, SizeType, bool) { return e != 8; } + bool StartObject() { return e != 9; } + bool Key(const char*, SizeType, bool) { return e != 10; } + bool EndObject(SizeType) { return e != 11; } + bool StartArray() { return e != 12; } + bool EndArray(SizeType) { return e != 13; } +}; + +#define TEST_TERMINATION(e, json)\ +{\ + Document d; \ + EXPECT_FALSE(d.Parse(json).HasParseError()); \ + Reader reader; \ + TerminateHandler<e> h;\ + EXPECT_FALSE(d.Accept(h));\ +} + +TEST(Value, AcceptTerminationByHandler) { + TEST_TERMINATION(0, "[null]"); + TEST_TERMINATION(1, "[true]"); + TEST_TERMINATION(1, "[false]"); + TEST_TERMINATION(2, "[-1]"); + TEST_TERMINATION(3, "[2147483648]"); + TEST_TERMINATION(4, "[-1234567890123456789]"); + TEST_TERMINATION(5, "[9223372036854775808]"); + TEST_TERMINATION(6, "[0.5]"); + // RawNumber() is never called + TEST_TERMINATION(8, "[\"a\"]"); + TEST_TERMINATION(9, "[{}]"); + TEST_TERMINATION(10, "[{\"a\":1}]"); + TEST_TERMINATION(11, "[{}]"); + TEST_TERMINATION(12, "{\"a\":[]}"); + TEST_TERMINATION(13, "{\"a\":[]}"); +} + +struct ValueIntComparer { + bool operator()(const Value& lhs, const Value& rhs) const { + return lhs.GetInt() < rhs.GetInt(); + } +}; + +#if RAPIDJSON_HAS_CXX11_RVALUE_REFS +TEST(Value, Sorting) { + Value::AllocatorType allocator; + Value a(kArrayType); + a.PushBack(5, allocator); + a.PushBack(1, allocator); + a.PushBack(3, allocator); + std::sort(a.Begin(), a.End(), ValueIntComparer()); + EXPECT_EQ(1, a[0].GetInt()); + EXPECT_EQ(3, a[1].GetInt()); + EXPECT_EQ(5, a[2].GetInt()); +} +#endif + +// http://stackoverflow.com/questions/35222230/ + +static void MergeDuplicateKey(Value& v, Value::AllocatorType& a) { + if (v.IsObject()) { + // Convert all key:value into key:[value] + for (Value::MemberIterator itr = v.MemberBegin(); itr != v.MemberEnd(); ++itr) + itr->value = Value(kArrayType).Move().PushBack(itr->value, a); + + // Merge arrays if key is duplicated + for (Value::MemberIterator itr = v.MemberBegin(); itr != v.MemberEnd();) { + Value::MemberIterator itr2 = v.FindMember(itr->name); + if (itr != itr2) { + itr2->value.PushBack(itr->value[0], a); + itr = v.EraseMember(itr); + } + else + ++itr; + } + + // Convert key:[values] back to key:value if there is only one value + for (Value::MemberIterator itr = v.MemberBegin(); itr != v.MemberEnd(); ++itr) { + if (itr->value.Size() == 1) + itr->value = itr->value[0]; + MergeDuplicateKey(itr->value, a); // Recursion on the value + } + } + else if (v.IsArray()) + for (Value::ValueIterator itr = v.Begin(); itr != v.End(); ++itr) + MergeDuplicateKey(*itr, a); +} + +TEST(Value, MergeDuplicateKey) { + Document d; + d.Parse( + "{" + " \"key1\": {" + " \"a\": \"asdf\"," + " \"b\": \"foo\"," + " \"b\": \"bar\"," + " \"c\": \"fdas\"" + " }" + "}"); + + Document d2; + d2.Parse( + "{" + " \"key1\": {" + " \"a\": \"asdf\"," + " \"b\": [" + " \"foo\"," + " \"bar\"" + " ]," + " \"c\": \"fdas\"" + " }" + "}"); + + EXPECT_NE(d2, d); + MergeDuplicateKey(d, d.GetAllocator()); + EXPECT_EQ(d2, d); +} + +#ifdef __clang__ +RAPIDJSON_DIAG_POP +#endif diff --git a/src/rapidjson/test/unittest/writertest.cpp b/src/rapidjson/test/unittest/writertest.cpp new file mode 100644 index 000000000..29f762609 --- /dev/null +++ b/src/rapidjson/test/unittest/writertest.cpp @@ -0,0 +1,497 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#include "unittest.h" + +#include "rapidjson/document.h" +#include "rapidjson/reader.h" +#include "rapidjson/writer.h" +#include "rapidjson/stringbuffer.h" +#include "rapidjson/memorybuffer.h" + +using namespace rapidjson; + +TEST(Writer, Compact) { + StringStream s("{ \"hello\" : \"world\", \"t\" : true , \"f\" : false, \"n\": null, \"i\":123, \"pi\": 3.1416, \"a\":[1, 2, 3] } "); + StringBuffer buffer; + Writer<StringBuffer> writer(buffer); + buffer.ShrinkToFit(); + Reader reader; + reader.Parse<0>(s, writer); + EXPECT_STREQ("{\"hello\":\"world\",\"t\":true,\"f\":false,\"n\":null,\"i\":123,\"pi\":3.1416,\"a\":[1,2,3]}", buffer.GetString()); + EXPECT_EQ(77u, buffer.GetSize()); + EXPECT_TRUE(writer.IsComplete()); +} + +// json -> parse -> writer -> json +#define TEST_ROUNDTRIP(json) \ + { \ + StringStream s(json); \ + StringBuffer buffer; \ + Writer<StringBuffer> writer(buffer); \ + Reader reader; \ + reader.Parse<kParseFullPrecisionFlag>(s, writer); \ + EXPECT_STREQ(json, buffer.GetString()); \ + EXPECT_TRUE(writer.IsComplete()); \ + } + +TEST(Writer, Root) { + TEST_ROUNDTRIP("null"); + TEST_ROUNDTRIP("true"); + TEST_ROUNDTRIP("false"); + TEST_ROUNDTRIP("0"); + TEST_ROUNDTRIP("\"foo\""); + TEST_ROUNDTRIP("[]"); + TEST_ROUNDTRIP("{}"); +} + +TEST(Writer, Int) { + TEST_ROUNDTRIP("[-1]"); + TEST_ROUNDTRIP("[-123]"); + TEST_ROUNDTRIP("[-2147483648]"); +} + +TEST(Writer, UInt) { + TEST_ROUNDTRIP("[0]"); + TEST_ROUNDTRIP("[1]"); + TEST_ROUNDTRIP("[123]"); + TEST_ROUNDTRIP("[2147483647]"); + TEST_ROUNDTRIP("[4294967295]"); +} + +TEST(Writer, Int64) { + TEST_ROUNDTRIP("[-1234567890123456789]"); + TEST_ROUNDTRIP("[-9223372036854775808]"); +} + +TEST(Writer, Uint64) { + TEST_ROUNDTRIP("[1234567890123456789]"); + TEST_ROUNDTRIP("[9223372036854775807]"); +} + +TEST(Writer, String) { + TEST_ROUNDTRIP("[\"Hello\"]"); + TEST_ROUNDTRIP("[\"Hello\\u0000World\"]"); + TEST_ROUNDTRIP("[\"\\\"\\\\/\\b\\f\\n\\r\\t\"]"); + +#if RAPIDJSON_HAS_STDSTRING + { + StringBuffer buffer; + Writer<StringBuffer> writer(buffer); + writer.String(std::string("Hello\n")); + EXPECT_STREQ("\"Hello\\n\"", buffer.GetString()); + } +#endif +} + +TEST(Writer, ScanWriteUnescapedString) { + const char json[] = "[\" \\\"0123456789ABCDEF\"]"; + // ^ scanning stops here. + char buffer2[sizeof(json) + 32]; + + // Use different offset to test different alignments + for (int i = 0; i < 32; i++) { + char* p = buffer2 + i; + memcpy(p, json, sizeof(json)); + TEST_ROUNDTRIP(p); + } +} + +TEST(Writer, Double) { + TEST_ROUNDTRIP("[1.2345,1.2345678,0.123456789012,1234567.8]"); + TEST_ROUNDTRIP("0.0"); + TEST_ROUNDTRIP("-0.0"); // Issue #289 + TEST_ROUNDTRIP("1e30"); + TEST_ROUNDTRIP("1.0"); + TEST_ROUNDTRIP("5e-324"); // Min subnormal positive double + TEST_ROUNDTRIP("2.225073858507201e-308"); // Max subnormal positive double + TEST_ROUNDTRIP("2.2250738585072014e-308"); // Min normal positive double + TEST_ROUNDTRIP("1.7976931348623157e308"); // Max double + +} + +// UTF8 -> TargetEncoding -> UTF8 +template <typename TargetEncoding> +void TestTranscode(const char* json) { + StringStream s(json); + GenericStringBuffer<TargetEncoding> buffer; + Writer<GenericStringBuffer<TargetEncoding>, UTF8<>, TargetEncoding> writer(buffer); + Reader reader; + reader.Parse(s, writer); + + StringBuffer buffer2; + Writer<StringBuffer> writer2(buffer2); + GenericReader<TargetEncoding, UTF8<> > reader2; + GenericStringStream<TargetEncoding> s2(buffer.GetString()); + reader2.Parse(s2, writer2); + + EXPECT_STREQ(json, buffer2.GetString()); +} + +TEST(Writer, Transcode) { + const char json[] = "{\"hello\":\"world\",\"t\":true,\"f\":false,\"n\":null,\"i\":123,\"pi\":3.1416,\"a\":[1,2,3],\"dollar\":\"\x24\",\"cents\":\"\xC2\xA2\",\"euro\":\"\xE2\x82\xAC\",\"gclef\":\"\xF0\x9D\x84\x9E\"}"; + + // UTF8 -> UTF16 -> UTF8 + TestTranscode<UTF8<> >(json); + + // UTF8 -> ASCII -> UTF8 + TestTranscode<ASCII<> >(json); + + // UTF8 -> UTF16 -> UTF8 + TestTranscode<UTF16<> >(json); + + // UTF8 -> UTF32 -> UTF8 + TestTranscode<UTF32<> >(json); + + // UTF8 -> AutoUTF -> UTF8 + UTFType types[] = { kUTF8, kUTF16LE , kUTF16BE, kUTF32LE , kUTF32BE }; + for (size_t i = 0; i < 5; i++) { + StringStream s(json); + MemoryBuffer buffer; + AutoUTFOutputStream<unsigned, MemoryBuffer> os(buffer, types[i], true); + Writer<AutoUTFOutputStream<unsigned, MemoryBuffer>, UTF8<>, AutoUTF<unsigned> > writer(os); + Reader reader; + reader.Parse(s, writer); + + StringBuffer buffer2; + Writer<StringBuffer> writer2(buffer2); + GenericReader<AutoUTF<unsigned>, UTF8<> > reader2; + MemoryStream s2(buffer.GetBuffer(), buffer.GetSize()); + AutoUTFInputStream<unsigned, MemoryStream> is(s2); + reader2.Parse(is, writer2); + + EXPECT_STREQ(json, buffer2.GetString()); + } + +} + +#include <sstream> + +class OStreamWrapper { +public: + typedef char Ch; + + OStreamWrapper(std::ostream& os) : os_(os) {} + + Ch Peek() const { assert(false); return '\0'; } + Ch Take() { assert(false); return '\0'; } + size_t Tell() const { return 0; } + + Ch* PutBegin() { assert(false); return 0; } + void Put(Ch c) { os_.put(c); } + void Flush() { os_.flush(); } + size_t PutEnd(Ch*) { assert(false); return 0; } + +private: + OStreamWrapper(const OStreamWrapper&); + OStreamWrapper& operator=(const OStreamWrapper&); + + std::ostream& os_; +}; + +TEST(Writer, OStreamWrapper) { + StringStream s("{ \"hello\" : \"world\", \"t\" : true , \"f\" : false, \"n\": null, \"i\":123, \"pi\": 3.1416, \"a\":[1, 2, 3], \"u64\": 1234567890123456789, \"i64\":-1234567890123456789 } "); + + std::stringstream ss; + OStreamWrapper os(ss); + + Writer<OStreamWrapper> writer(os); + + Reader reader; + reader.Parse<0>(s, writer); + + std::string actual = ss.str(); + EXPECT_STREQ("{\"hello\":\"world\",\"t\":true,\"f\":false,\"n\":null,\"i\":123,\"pi\":3.1416,\"a\":[1,2,3],\"u64\":1234567890123456789,\"i64\":-1234567890123456789}", actual.c_str()); +} + +TEST(Writer, AssertRootMayBeAnyValue) { +#define T(x)\ + {\ + StringBuffer buffer;\ + Writer<StringBuffer> writer(buffer);\ + EXPECT_TRUE(x);\ + } + T(writer.Bool(false)); + T(writer.Bool(true)); + T(writer.Null()); + T(writer.Int(0)); + T(writer.Uint(0)); + T(writer.Int64(0)); + T(writer.Uint64(0)); + T(writer.Double(0)); + T(writer.String("foo")); +#undef T +} + +TEST(Writer, AssertIncorrectObjectLevel) { + StringBuffer buffer; + Writer<StringBuffer> writer(buffer); + writer.StartObject(); + writer.EndObject(); + ASSERT_THROW(writer.EndObject(), AssertException); +} + +TEST(Writer, AssertIncorrectArrayLevel) { + StringBuffer buffer; + Writer<StringBuffer> writer(buffer); + writer.StartArray(); + writer.EndArray(); + ASSERT_THROW(writer.EndArray(), AssertException); +} + +TEST(Writer, AssertIncorrectEndObject) { + StringBuffer buffer; + Writer<StringBuffer> writer(buffer); + writer.StartObject(); + ASSERT_THROW(writer.EndArray(), AssertException); +} + +TEST(Writer, AssertIncorrectEndArray) { + StringBuffer buffer; + Writer<StringBuffer> writer(buffer); + writer.StartObject(); + ASSERT_THROW(writer.EndArray(), AssertException); +} + +TEST(Writer, AssertObjectKeyNotString) { +#define T(x)\ + {\ + StringBuffer buffer;\ + Writer<StringBuffer> writer(buffer);\ + writer.StartObject();\ + ASSERT_THROW(x, AssertException); \ + } + T(writer.Bool(false)); + T(writer.Bool(true)); + T(writer.Null()); + T(writer.Int(0)); + T(writer.Uint(0)); + T(writer.Int64(0)); + T(writer.Uint64(0)); + T(writer.Double(0)); + T(writer.StartObject()); + T(writer.StartArray()); +#undef T +} + +TEST(Writer, AssertMultipleRoot) { + StringBuffer buffer; + Writer<StringBuffer> writer(buffer); + + writer.StartObject(); + writer.EndObject(); + ASSERT_THROW(writer.StartObject(), AssertException); + + writer.Reset(buffer); + writer.Null(); + ASSERT_THROW(writer.Int(0), AssertException); + + writer.Reset(buffer); + writer.String("foo"); + ASSERT_THROW(writer.StartArray(), AssertException); + + writer.Reset(buffer); + writer.StartArray(); + writer.EndArray(); + //ASSERT_THROW(writer.Double(3.14), AssertException); +} + +TEST(Writer, RootObjectIsComplete) { + StringBuffer buffer; + Writer<StringBuffer> writer(buffer); + EXPECT_FALSE(writer.IsComplete()); + writer.StartObject(); + EXPECT_FALSE(writer.IsComplete()); + writer.String("foo"); + EXPECT_FALSE(writer.IsComplete()); + writer.Int(1); + EXPECT_FALSE(writer.IsComplete()); + writer.EndObject(); + EXPECT_TRUE(writer.IsComplete()); +} + +TEST(Writer, RootArrayIsComplete) { + StringBuffer buffer; + Writer<StringBuffer> writer(buffer); + EXPECT_FALSE(writer.IsComplete()); + writer.StartArray(); + EXPECT_FALSE(writer.IsComplete()); + writer.String("foo"); + EXPECT_FALSE(writer.IsComplete()); + writer.Int(1); + EXPECT_FALSE(writer.IsComplete()); + writer.EndArray(); + EXPECT_TRUE(writer.IsComplete()); +} + +TEST(Writer, RootValueIsComplete) { +#define T(x)\ + {\ + StringBuffer buffer;\ + Writer<StringBuffer> writer(buffer);\ + EXPECT_FALSE(writer.IsComplete()); \ + x; \ + EXPECT_TRUE(writer.IsComplete()); \ + } + T(writer.Null()); + T(writer.Bool(true)); + T(writer.Bool(false)); + T(writer.Int(0)); + T(writer.Uint(0)); + T(writer.Int64(0)); + T(writer.Uint64(0)); + T(writer.Double(0)); + T(writer.String("")); +#undef T +} + +TEST(Writer, InvalidEncoding) { + // Fail in decoding invalid UTF-8 sequence http://www.cl.cam.ac.uk/~mgk25/ucs/examples/UTF-8-test.txt + { + GenericStringBuffer<UTF16<> > buffer; + Writer<GenericStringBuffer<UTF16<> >, UTF8<>, UTF16<> > writer(buffer); + writer.StartArray(); + EXPECT_FALSE(writer.String("\xfe")); + EXPECT_FALSE(writer.String("\xff")); + EXPECT_FALSE(writer.String("\xfe\xfe\xff\xff")); + writer.EndArray(); + } + + // Fail in encoding + { + StringBuffer buffer; + Writer<StringBuffer, UTF32<> > writer(buffer); + static const UTF32<>::Ch s[] = { 0x110000, 0 }; // Out of U+0000 to U+10FFFF + EXPECT_FALSE(writer.String(s)); + } + + // Fail in unicode escaping in ASCII output + { + StringBuffer buffer; + Writer<StringBuffer, UTF32<>, ASCII<> > writer(buffer); + static const UTF32<>::Ch s[] = { 0x110000, 0 }; // Out of U+0000 to U+10FFFF + EXPECT_FALSE(writer.String(s)); + } +} + +TEST(Writer, ValidateEncoding) { + { + StringBuffer buffer; + Writer<StringBuffer, UTF8<>, UTF8<>, CrtAllocator, kWriteValidateEncodingFlag> writer(buffer); + writer.StartArray(); + EXPECT_TRUE(writer.String("\x24")); // Dollar sign U+0024 + EXPECT_TRUE(writer.String("\xC2\xA2")); // Cents sign U+00A2 + EXPECT_TRUE(writer.String("\xE2\x82\xAC")); // Euro sign U+20AC + EXPECT_TRUE(writer.String("\xF0\x9D\x84\x9E")); // G clef sign U+1D11E + writer.EndArray(); + EXPECT_STREQ("[\"\x24\",\"\xC2\xA2\",\"\xE2\x82\xAC\",\"\xF0\x9D\x84\x9E\"]", buffer.GetString()); + } + + // Fail in decoding invalid UTF-8 sequence http://www.cl.cam.ac.uk/~mgk25/ucs/examples/UTF-8-test.txt + { + StringBuffer buffer; + Writer<StringBuffer, UTF8<>, UTF8<>, CrtAllocator, kWriteValidateEncodingFlag> writer(buffer); + writer.StartArray(); + EXPECT_FALSE(writer.String("\xfe")); + EXPECT_FALSE(writer.String("\xff")); + EXPECT_FALSE(writer.String("\xfe\xfe\xff\xff")); + writer.EndArray(); + } +} + +TEST(Writer, InvalidEventSequence) { + // {] + { + StringBuffer buffer; + Writer<StringBuffer> writer(buffer); + writer.StartObject(); + EXPECT_THROW(writer.EndArray(), AssertException); + EXPECT_FALSE(writer.IsComplete()); + } + + // [} + { + StringBuffer buffer; + Writer<StringBuffer> writer(buffer); + writer.StartArray(); + EXPECT_THROW(writer.EndObject(), AssertException); + EXPECT_FALSE(writer.IsComplete()); + } + + // { 1: + { + StringBuffer buffer; + Writer<StringBuffer> writer(buffer); + writer.StartObject(); + EXPECT_THROW(writer.Int(1), AssertException); + EXPECT_FALSE(writer.IsComplete()); + } +} + +TEST(Writer, NaN) { + double nan = std::numeric_limits<double>::quiet_NaN(); + + EXPECT_TRUE(internal::Double(nan).IsNan()); + StringBuffer buffer; + { + Writer<StringBuffer> writer(buffer); + EXPECT_FALSE(writer.Double(nan)); + } + { + Writer<StringBuffer, UTF8<>, UTF8<>, CrtAllocator, kWriteNanAndInfFlag> writer(buffer); + EXPECT_TRUE(writer.Double(nan)); + EXPECT_STREQ("NaN", buffer.GetString()); + } + GenericStringBuffer<UTF16<> > buffer2; + Writer<GenericStringBuffer<UTF16<> > > writer2(buffer2); + EXPECT_FALSE(writer2.Double(nan)); +} + +TEST(Writer, Inf) { + double inf = std::numeric_limits<double>::infinity(); + + EXPECT_TRUE(internal::Double(inf).IsInf()); + StringBuffer buffer; + { + Writer<StringBuffer> writer(buffer); + EXPECT_FALSE(writer.Double(inf)); + } + { + Writer<StringBuffer> writer(buffer); + EXPECT_FALSE(writer.Double(-inf)); + } + { + Writer<StringBuffer, UTF8<>, UTF8<>, CrtAllocator, kWriteNanAndInfFlag> writer(buffer); + EXPECT_TRUE(writer.Double(inf)); + } + { + Writer<StringBuffer, UTF8<>, UTF8<>, CrtAllocator, kWriteNanAndInfFlag> writer(buffer); + EXPECT_TRUE(writer.Double(-inf)); + } + EXPECT_STREQ("Infinity-Infinity", buffer.GetString()); +} + +TEST(Writer, RawValue) { + StringBuffer buffer; + Writer<StringBuffer> writer(buffer); + writer.StartObject(); + writer.Key("a"); + writer.Int(1); + writer.Key("raw"); + const char json[] = "[\"Hello\\nWorld\", 123.456]"; + writer.RawValue(json, strlen(json), kArrayType); + writer.EndObject(); + EXPECT_TRUE(writer.IsComplete()); + EXPECT_STREQ("{\"a\":1,\"raw\":[\"Hello\\nWorld\", 123.456]}", buffer.GetString()); +} |