blob: 42ab0b2bdfc4705ba984bdaa726b0231c9216623 [file]
/*
* Copyright (C) 2025 The Android Open Source Project
*
* Licensed 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 "src/trace_processor/util/json_writer.h"
#include <algorithm>
#include <cmath>
#include <limits>
#include <string>
#include "test/gtest_and_gmock.h"
namespace perfetto::trace_processor::json {
namespace {
// Helper function to strip all whitespace from a string for JSON comparison.
// This allows tests to use nicely formatted expected JSON strings.
std::string StripWhitespace(const std::string& str) {
std::string result;
result.reserve(str.size());
std::copy_if(str.begin(), str.end(), std::back_inserter(result), [](char c) {
return c != ' ' && c != '\n' && c != '\r' && c != '\t';
});
return result;
}
TEST(JsonWriterTest, WriteDictEmpty) {
std::string result = Write([](JsonValueWriter&& writer) {
std::move(writer).WriteDict([](JsonDictWriter&) {});
});
EXPECT_EQ(result, StripWhitespace("{}"));
}
TEST(JsonWriterTest, WriteDictPrimitives) {
std::string result = Write([](JsonValueWriter&& writer) {
std::move(writer).WriteDict([](JsonDictWriter& dict) {
dict.AddNull("null_value");
dict.AddBool("bool_true", true);
dict.AddBool("bool_false", false);
dict.AddInt("int64", static_cast<int64_t>(-42));
dict.AddInt("int64_min", static_cast<int64_t>(-9223372036854775807LL));
dict.AddUint("uint64", static_cast<uint64_t>(42));
dict.AddUint("uint64_max",
static_cast<uint64_t>(18446744073709551615ULL));
dict.AddDouble("double", 3.14159);
dict.AddString("string", "hello");
dict.AddString("string_view", std::string_view("world"));
dict.AddString("quotes", "say\"hello\"");
dict.AddString("backslash", "path\\to\\file");
dict.AddString("control", "\x01\x02\x1f");
// Escaped keys
dict.AddString("key\"with\"quotes", "value1");
dict.AddString("key\\with\\backslash", "value2");
dict.AddString("key\nwith\nnewline", "value3");
dict.AddString("key\twith\ttab", "value4");
dict.AddString("key\x01with_control", "value5");
});
});
std::string expected = R"({
"null_value": null,
"bool_true": true,
"bool_false": false,
"int64": -42,
"int64_min": -9223372036854775807,
"uint64": 42,
"uint64_max": 18446744073709551615,
"double": 3.141590,
"string": "hello",
"string_view": "world",
"quotes": "say\"hello\"",
"backslash": "path\\to\\file",
"control": "\u0001\u0002\u001f",
"key\"with\"quotes": "value1",
"key\\with\\backslash": "value2",
"key\nwith\nnewline": "value3",
"key\twith\ttab": "value4",
"key\u0001with_control": "value5"
})";
EXPECT_EQ(result, StripWhitespace(expected));
}
TEST(JsonWriterTest, WriteDictWhitespace) {
std::string result = Write([](JsonValueWriter&& writer) {
std::move(writer).WriteDict([](JsonDictWriter& dict) {
dict.AddString("newline", "line1\nline2");
dict.AddString("carriage", "line1\rline2");
dict.AddString("tab", "col1\tcol2");
});
});
std::string expected =
R"({"newline":"line1\nline2","carriage":"line1\rline2","tab":"col1\tcol2"})";
EXPECT_EQ(result, expected);
}
TEST(JsonWriterTest, WriteDictSpecialDoubles) {
std::string result = Write([](JsonValueWriter&& writer) {
std::move(writer).WriteDict([](JsonDictWriter& dict) {
dict.AddDouble("nan", std::nan(""));
dict.AddDouble("inf", std::numeric_limits<double>::infinity());
dict.AddDouble("neg_inf", -std::numeric_limits<double>::infinity());
});
});
std::string expected = R"({
"nan": "NaN",
"inf": "Infinity",
"neg_inf": "-Infinity"
})";
EXPECT_EQ(result, StripWhitespace(expected));
}
TEST(JsonWriterTest, WriteDictNested) {
std::string result = Write([](JsonValueWriter&& writer) {
std::move(writer).WriteDict([](JsonDictWriter& dict) {
dict.AddString("outer", "value");
dict.AddDict("nested", [](JsonDictWriter& nested) {
nested.AddInt("inner1", static_cast<int64_t>(42));
nested.AddString("inner2", "text");
});
dict.AddArray("items", [](JsonArrayWriter& arr) {
arr.AppendInt(static_cast<int64_t>(1));
arr.AppendInt(static_cast<int64_t>(2));
arr.AppendInt(static_cast<int64_t>(3));
});
});
});
std::string expected = R"({
"outer": "value",
"nested": {
"inner1": 42,
"inner2": "text"
},
"items": [1, 2, 3]
})";
EXPECT_EQ(result, StripWhitespace(expected));
}
TEST(JsonWriterTest, WriteArrayEmpty) {
std::string result = Write([](JsonValueWriter&& writer) {
std::move(writer).WriteArray([](JsonArrayWriter&) {});
});
EXPECT_EQ(result, StripWhitespace("[]"));
}
TEST(JsonWriterTest, WriteArrayPrimitives) {
std::string result = Write([](JsonValueWriter&& writer) {
std::move(writer).WriteArray([](JsonArrayWriter& arr) {
arr.AppendNull();
arr.AppendBool(true);
arr.AppendBool(false);
arr.AppendInt(static_cast<int64_t>(-42));
arr.AppendInt(static_cast<int64_t>(-9223372036854775807LL));
arr.AppendUint(static_cast<uint64_t>(42));
arr.AppendUint(static_cast<uint64_t>(18446744073709551615ULL));
arr.AppendDouble(3.14159);
arr.AppendString("hello");
arr.AppendString(std::string_view("world"));
});
});
std::string expected = R"([
null,
true,
false,
-42,
-9223372036854775807,
42,
18446744073709551615,
3.141590,
"hello",
"world"
])";
EXPECT_EQ(result, StripWhitespace(expected));
}
TEST(JsonWriterTest, WriteArrayNested) {
std::string result = Write([](JsonValueWriter&& writer) {
std::move(writer).WriteArray([](JsonArrayWriter& arr) {
arr.AppendString("first");
arr.AppendDict([](JsonDictWriter& dict) {
dict.AddInt("key1", static_cast<int64_t>(42));
dict.AddString("key2", "value");
});
arr.AppendInt(static_cast<int64_t>(1));
arr.AppendArray([](JsonArrayWriter& nested) {
nested.AppendInt(static_cast<int64_t>(2));
nested.AppendInt(static_cast<int64_t>(3));
});
arr.AppendInt(static_cast<int64_t>(4));
arr.AppendString("last");
});
});
std::string expected = R"([
"first",
{
"key1": 42,
"key2": "value"
},
1,
[2, 3],
4,
"last"
])";
EXPECT_EQ(result, StripWhitespace(expected));
}
TEST(JsonWriterTest, Complex) {
std::string result = Write([](JsonValueWriter&& writer) {
std::move(writer).WriteDict([](JsonDictWriter& dict) {
dict.AddString("name", "root");
dict.AddArray("items", [](JsonArrayWriter& arr) {
arr.AppendDict([](JsonDictWriter& obj1) {
obj1.AddInt("id", static_cast<int64_t>(1));
obj1.AddArray("tags", [](JsonArrayWriter& tags) {
tags.AppendString("tag1");
tags.AppendString("tag2");
});
});
arr.AppendDict([](JsonDictWriter& obj2) {
obj2.AddInt("id", static_cast<int64_t>(2));
obj2.AddNull("value");
});
});
dict.AddDict("metadata", [](JsonDictWriter& meta) {
meta.AddInt("version", static_cast<int64_t>(1));
meta.AddBool("created", true);
});
});
});
std::string expected = R"({
"name": "root",
"items": [
{
"id": 1,
"tags": ["tag1", "tag2"]
},
{
"id": 2,
"value": null
}
],
"metadata": {
"version": 1,
"created": true
}
})";
EXPECT_EQ(result, StripWhitespace(expected));
}
} // namespace
} // namespace perfetto::trace_processor::json