blob: 49d2c34c16d84607dfa7daf941a776bd920964da [file]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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 <gtest/gtest.h>
#include <tvm/ffi/container/array.h>
#include <tvm/ffi/container/map.h>
#include <tvm/ffi/extra/json.h>
#include <tvm/ffi/extra/structural_equal.h>
#include <cmath>
namespace {
using namespace tvm::ffi;
inline bool FastMathSafeIsNaN(double x) {
#ifdef __FAST_MATH__
// Bit-level NaN detection (IEEE 754 double)
// IEEE 754 standard: https://en.wikipedia.org/wiki/IEEE_754
// NaN is encoded as all 1s in the exponent and non-zero in the mantissa
static_assert(sizeof(double) == sizeof(uint64_t), "Unexpected double size");
uint64_t bits = *reinterpret_cast<const uint64_t*>(&x);
uint64_t exponent = (bits >> 52) & 0x7FF;
uint64_t mantissa = bits & 0xFFFFFFFFFFFFFull;
return (exponent == 0x7FF) && (mantissa != 0);
#else
// Safe to use std::isnan when fast-math is off
return std::isnan(x);
#endif
}
inline bool FastMathSafeIsInf(double x) {
#ifdef __FAST_MATH__
// IEEE 754 standard: https://en.wikipedia.org/wiki/IEEE_754
// Inf is encoded as all 1s in the exponent and zero in the mantissa
static_assert(sizeof(double) == sizeof(uint64_t), "Unexpected double size");
uint64_t bits = *reinterpret_cast<const uint64_t*>(&x);
uint64_t exponent = (bits >> 52) & 0x7FF;
uint64_t mantissa = bits & 0xFFFFFFFFFFFFFull;
// inf is encoded as all 1s in the exponent and zero in the mantissa
return (exponent == 0x7FF) && (mantissa == 0);
#else
return std::isinf(x);
#endif
}
TEST(JSONParser, BoolNull) {
// boolean value
EXPECT_EQ(json::Parse("true").cast<bool>(), true);
EXPECT_EQ(json::Parse("false").cast<bool>(), false);
EXPECT_EQ(json::Parse("null"), nullptr);
}
TEST(JSONParser, WrongBoolNull) {
String error_msg;
EXPECT_EQ(json::Parse("nul", &error_msg), nullptr);
EXPECT_EQ(error_msg, "Expecting value: line 1 column 1 (char 0)");
EXPECT_EQ(json::Parse("fals", &error_msg), nullptr);
EXPECT_EQ(error_msg, "Expecting value: line 1 column 1 (char 0)");
EXPECT_EQ(json::Parse("\n\nfx", &error_msg), nullptr);
EXPECT_EQ(error_msg, "Expecting value: line 3 column 1 (char 2)");
EXPECT_EQ(json::Parse("fx", &error_msg), nullptr);
EXPECT_EQ(error_msg, "Expecting value: line 1 column 1 (char 0)");
EXPECT_EQ(json::Parse("n1", &error_msg), nullptr);
EXPECT_EQ(error_msg, "Expecting value: line 1 column 1 (char 0)");
EXPECT_EQ(json::Parse("t1", &error_msg), nullptr);
EXPECT_EQ(error_msg, "Expecting value: line 1 column 1 (char 0)");
EXPECT_EQ(json::Parse("f1", &error_msg), nullptr);
EXPECT_EQ(error_msg, "Expecting value: line 1 column 1 (char 0)");
}
TEST(JSONParser, Number) {
// number
EXPECT_EQ(json::Parse("123").cast<int64_t>(), 123);
EXPECT_EQ(json::Parse("-124").cast<int64_t>(), -124);
EXPECT_EQ(json::Parse("123.456").cast<double>(), 123.456);
// parsing scientific notation
EXPECT_EQ(json::Parse("1.456e12").cast<double>(), 1.456e12);
// NaN
EXPECT_EQ(FastMathSafeIsNaN(json::Parse("NaN").cast<double>()), true);
// Infinity
EXPECT_EQ(FastMathSafeIsInf(json::Parse("Infinity").cast<double>()), true);
// -Infinity
EXPECT_EQ(FastMathSafeIsInf(-json::Parse("-Infinity").cast<double>()), true);
// Test zero variants
EXPECT_EQ(json::Parse("0").cast<int64_t>(), 0);
EXPECT_EQ(json::Parse("-0").cast<double>(), -0.0);
EXPECT_EQ(json::Parse("0.0").cast<double>(), 0.0);
// Test very large numbers
EXPECT_EQ(json::Parse("9223372036854775807").cast<int64_t>(),
std::numeric_limits<int64_t>::max());
EXPECT_EQ(json::Parse("-9223372036854775808").cast<int64_t>(),
std::numeric_limits<int64_t>::min());
// Test very small decimals
EXPECT_EQ(json::Parse("1e-10").cast<double>(), 1e-10);
EXPECT_EQ(json::Parse("-1e-10").cast<double>(), -1e-10);
// Test scientific notation edge cases
EXPECT_EQ(json::Parse("1E+10").cast<double>(), 1E+10);
EXPECT_EQ(json::Parse("1e+10").cast<double>(), 1e+10);
EXPECT_EQ(json::Parse("1E-10").cast<double>(), 1E-10);
EXPECT_EQ(json::Parse("123.456E+10").cast<double>(), 123.456E+10);
}
TEST(JSONParser, WrongNumber) {
String error_msg;
EXPECT_EQ(json::Parse("123.456.789", &error_msg), nullptr);
EXPECT_EQ(error_msg, "Expecting value: line 1 column 1 (char 0)");
// Test invalid number formats
EXPECT_EQ(json::Parse("123e", &error_msg), nullptr);
EXPECT_EQ(error_msg, "Expecting value: line 1 column 1 (char 0)");
EXPECT_EQ(json::Parse("123e+", &error_msg), nullptr);
EXPECT_EQ(error_msg, "Expecting value: line 1 column 1 (char 0)");
EXPECT_EQ(json::Parse("123E-", &error_msg), nullptr);
EXPECT_EQ(error_msg, "Expecting value: line 1 column 1 (char 0)");
}
TEST(JSONParser, String) {
EXPECT_EQ(json::Parse("\"hello\"").cast<String>(), "hello");
EXPECT_EQ(json::Parse("\n\t \"hello\"\n\r").cast<String>(), "hello");
EXPECT_EQ(json::Parse("\"hello\\nworld\"").cast<String>(), "hello\nworld");
EXPECT_EQ(json::Parse("\"\"").cast<String>(), "");
// test escape characters
EXPECT_EQ(json::Parse("\"\\ta\\n\\/\\f\\\"\\\\\"").cast<String>(), "\ta\n/\f\"\\");
// test unicode code point
EXPECT_EQ(json::Parse("\"\\u0041\"").cast<String>(), "A");
// test unicode surrogate pair
EXPECT_EQ(json::Parse("\"\\uD83D\\uDE04hello\"").cast<String>(), u8"\U0001F604hello");
}
TEST(JSONParser, WrongString) {
String error_msg;
EXPECT_EQ(json::Parse("\"hello", &error_msg), nullptr);
EXPECT_EQ(error_msg, "Unterminated string starting at: line 1 column 1 (char 0)");
EXPECT_EQ(json::Parse("\"hello\x01\"", &error_msg), nullptr);
EXPECT_EQ(error_msg, "Invalid control character at: line 1 column 7 (char 6)");
EXPECT_EQ(json::Parse("\"hello\\uxx\"", &error_msg), nullptr);
EXPECT_EQ(error_msg, "Invalid \\uXXXX escape: line 1 column 8 (char 7)");
EXPECT_EQ(json::Parse("\"hello\\uDC00\\uDE04\"", &error_msg), nullptr);
EXPECT_EQ(error_msg, "Invalid surrogate pair of \\uXXXX escapes: line 1 column 8 (char 7)");
EXPECT_EQ(json::Parse("\"hello\\uD800\"", &error_msg), nullptr);
EXPECT_EQ(error_msg, "Invalid surrogate pair of \\uXXXX escapes: line 1 column 8 (char 7)");
EXPECT_EQ(json::Parse("\"hello\\uD800\\uxx\"", &error_msg), nullptr);
EXPECT_EQ(error_msg, "Invalid \\uXXXX escape: line 1 column 15 (char 14)");
EXPECT_EQ(json::Parse("\"hello\\a\"", &error_msg), nullptr);
EXPECT_EQ(error_msg, "Invalid \\escape: line 1 column 8 (char 7)");
}
TEST(JSONParser, Array) {
EXPECT_TRUE(StructuralEqual()(json::Parse("[]"), json::Array{}));
EXPECT_TRUE(StructuralEqual()(json::Parse("[1, 2,\n\t\"a\"]"), json::Array{1, 2, "a"}));
}
TEST(JSONParser, WrongArray) {
String error_msg;
EXPECT_EQ(json::Parse("]", &error_msg), nullptr);
EXPECT_EQ(error_msg, "Expecting value: line 1 column 1 (char 0)");
EXPECT_EQ(json::Parse("[1,]", &error_msg), nullptr);
EXPECT_EQ(error_msg, "Expecting value: line 1 column 4 (char 3)");
EXPECT_EQ(json::Parse("[", &error_msg), nullptr);
EXPECT_EQ(error_msg, "Expecting value: line 1 column 2 (char 1)");
EXPECT_EQ(json::Parse("[1a", &error_msg), nullptr);
EXPECT_EQ(error_msg, "Expecting ',' delimiter: line 1 column 3 (char 2)");
EXPECT_EQ(json::Parse("[1,2,3", &error_msg), nullptr);
EXPECT_EQ(error_msg, "Expecting ',' delimiter: line 1 column 7 (char 6)");
EXPECT_EQ(json::Parse("[1] a", &error_msg), nullptr);
EXPECT_EQ(error_msg, "Extra data: line 1 column 6 (char 5)");
}
TEST(JSONParser, Object) {
EXPECT_TRUE(StructuralEqual()(json::Parse("{}"), json::Object{}));
EXPECT_TRUE(StructuralEqual()(json::Parse("{\"a\": 1, \n\"b\": \t\"c\"} "),
json::Object{{"a", 1}, {"b", "c"}}));
}
TEST(JSONParser, ObjectOrderPreserving) {
auto obj = json::Parse(R"({"c": 1, "a": 2, "b": 3} )");
json::Array keys;
for (auto& [key, value] : obj.cast<json::Object>()) {
keys.push_back(key);
}
EXPECT_TRUE(StructuralEqual()(keys, json::Array{"c", "a", "b"}));
}
TEST(JSONParser, WrongObject) {
String error_msg;
EXPECT_EQ(json::Parse("{\"a\":", &error_msg), nullptr);
EXPECT_EQ(error_msg, "Expecting value: line 1 column 6 (char 5)");
EXPECT_EQ(json::Parse("{", &error_msg), nullptr);
EXPECT_EQ(error_msg,
"Expecting property name enclosed in double quotes: line 1 column 2 (char 1)");
// Test incomplete structures
EXPECT_EQ(json::Parse("{\"incomplete\"", &error_msg), nullptr);
EXPECT_EQ(error_msg, "Expecting ':' delimiter: line 1 column 14 (char 13)");
}
TEST(JSONParser, NestedObject) {
EXPECT_TRUE(
StructuralEqual()(json::Parse("{\"a\": \t{\"b\": 1}, \n\"c\": [1, 2, 3]}"),
json::Object{{"a", json::Object{{"b", 1}}}, {"c", json::Array{1, 2, 3}}}));
EXPECT_TRUE(StructuralEqual()(
json::Parse("{\"a\": \t{\"b\": 1}, \n\"c\": [1, null, Infinity]}"),
json::Object{{"a", json::Object{{"b", 1}}},
{"c", json::Array{1, nullptr, std::numeric_limits<double>::infinity()}}}));
EXPECT_TRUE(StructuralEqual()(
json::Parse("[{}, {\"a\": [1.1, 1000000]}]"),
json::Array{json::Object{}, json::Object{{"a", json::Array{1.1, 1000000}}}}));
}
TEST(JSONParser, WrongNestedObject) {
String error_msg;
EXPECT_EQ(json::Parse("{\"a\":\n\n[1]", &error_msg), nullptr);
EXPECT_EQ(error_msg, "Expecting ',' delimiter: line 3 column 4 (char 10)");
EXPECT_EQ(json::Parse("{\"a\":\n\n[abc]}", &error_msg), nullptr);
EXPECT_EQ(error_msg, "Expecting value: line 3 column 2 (char 8)");
}
// edge cases
TEST(JSONParser, WhitespaceHandling) {
// Test various whitespace characters
EXPECT_EQ(json::Parse(" \t\n\r true \t\n\r ").cast<bool>(), true);
EXPECT_EQ(json::Parse("\n\n\n123\n\n\n").cast<int64_t>(), 123);
EXPECT_EQ(json::Parse(" \"hello world\" ").cast<String>(), "hello world");
// Test whitespace in arrays and objects
EXPECT_TRUE(StructuralEqual()(json::Parse(" [ 1 , 2 , 3 ] "), json::Array{1, 2, 3}));
EXPECT_TRUE(StructuralEqual()(json::Parse(" { \"a\" : 1 , \"b\" : 2 } "),
json::Object{{"a", 1}, {"b", 2}}));
}
TEST(JSONParser, WrongEmptyAndMinimalInputs) {
String error_msg;
// Test empty string
EXPECT_EQ(json::Parse("", &error_msg), nullptr);
EXPECT_EQ(error_msg, "Expecting value: line 1 column 1 (char 0)");
// Test only whitespace
EXPECT_EQ(json::Parse(" \t\n ", &error_msg), nullptr);
EXPECT_EQ(error_msg, "Expecting value: line 2 column 5 (char 9)");
}
TEST(JSONParser, UnicodeEdgeCases) {
// Test various unicode characters
EXPECT_EQ(json::Parse("\"\\u0000\"").cast<String>(), std::string("\0", 1));
// replace using \U to avoid encoding issues
EXPECT_EQ(json::Parse("\"\\u00FF\"").cast<String>(), u8"\U000000FF");
EXPECT_EQ(json::Parse("\"\\u4E2D\\u6587\"").cast<String>(), u8"\U00004E2D\U00006587");
// Test multiple surrogate pairs
EXPECT_EQ(json::Parse("\"\\uD83D\\uDE00\\uD83D\\uDE01\"").cast<String>(),
u8"\U0001F600\U0001F601");
}
TEST(JSONParser, RawUTF8Bytes) {
// Regression test: raw UTF-8 bytes (>= 0x80) must not be rejected as control characters.
// This failed when the parser used signed char comparison: *cur_ < ' '
EXPECT_EQ(json::Parse("\"\xE4\xB8\xAD\xE6\x96\x87\"").cast<String>(), "\xE4\xB8\xAD\xE6\x96\x87");
}
TEST(JSONParser, LargeInputs) {
// Test large array
std::string large_array = "[";
for (int i = 0; i < 1000; ++i) {
if (i > 0) large_array += ",";
large_array += std::to_string(i);
}
large_array += "]";
auto result = json::Parse(large_array);
EXPECT_TRUE(result != nullptr);
EXPECT_EQ(result.cast<json::Array>().size(), 1000);
// Test large object
std::string large_object = "{";
for (int i = 0; i < 500; ++i) {
if (i > 0) large_object += ",";
large_object += "\"key" + std::to_string(i) + "\":" + std::to_string(i);
}
large_object += "}";
result = json::Parse(large_object);
EXPECT_TRUE(result != nullptr);
EXPECT_EQ(result.cast<json::Object>().size(), 500);
}
TEST(JSONParser, MixedDataTypes) {
// Test complex nested structure with all data types
std::string complex_json = R"({
"null_value": null,
"boolean_true": true,
"boolean_false": false,
"integer": 42,
"negative_integer": -42,
"float": 3.14159,
"scientific": 1.23e-4,
"string": "hello world",
"unicode_string": "Hello \u4e16\u754c \ud83c\udf0d",
"empty_string": "",
"empty_array": [],
"empty_object": {},
"number_array": [1, 2, 3, 4, 5],
"mixed_array": [1, "two", true, null, 3.14],
"nested_object": {
"level1": {
"level2": {
"data": [1, 2, {"nested_array": [true, false]}]
}
}
}
})";
auto result = json::Parse(complex_json);
// Create expected structure for comparison
json::Object expected{
{"null_value", nullptr},
{"boolean_true", true},
{"boolean_false", false},
{"integer", 42},
{"negative_integer", -42},
{"float", 3.14159},
{"scientific", 1.23e-4},
{"string", "hello world"},
{"unicode_string", u8"Hello \U00004E16\U0000754C \U0001F30D"},
{"empty_string", ""},
{"empty_array", json::Array{}},
{"empty_object", json::Object{}},
{"number_array", json::Array{1, 2, 3, 4, 5}},
{"mixed_array", json::Array{1, "two", true, nullptr, 3.14}},
{"nested_object",
json::Object{
{"level1",
json::Object{
{"level2",
json::Object{
{"data",
json::Array{1, 2,
json::Object{{"nested_array", json::Array{true, false}}}}}}}}}}}};
EXPECT_TRUE(StructuralEqual()(result, expected));
}
TEST(JSONParser, WrongExtraData) {
String error_msg;
EXPECT_EQ(json::Parse("truee", &error_msg), nullptr);
EXPECT_EQ(error_msg, "Extra data: line 1 column 5 (char 4)");
EXPECT_EQ(json::Parse("true false", &error_msg), nullptr);
EXPECT_EQ(error_msg, "Extra data: line 1 column 6 (char 5)");
EXPECT_EQ(json::Parse("123 456", &error_msg), nullptr);
EXPECT_EQ(error_msg, "Extra data: line 1 column 5 (char 4)");
EXPECT_EQ(json::Parse("\"hello\" \"world\"", &error_msg), nullptr);
EXPECT_EQ(error_msg, "Extra data: line 1 column 9 (char 8)");
EXPECT_EQ(json::Parse("{} []", &error_msg), nullptr);
EXPECT_EQ(error_msg, "Extra data: line 1 column 4 (char 3)");
}
} // namespace