Files
transmission/tests/libtransmission/json-test.cc
Charles Kerr 27f0e2e20d test: always call tr_lib_init() in libtransmission tests (#7992)
* test: add TransmissionTest fixture that calls tr_lib_init()

* refactor: use std::call_once() in tr_lib_init()

* test: ensure quarks are sorted in RpcTest::sessionGet

* refactor: use ::libtransmission::test::TransmissionTest

prefer this over ::testing::Test to ensure tr_lib_init() is called once
2025-12-23 14:32:29 -06:00

309 lines
9.0 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// This file Copyright (C) 2013-2022 Mnemosyne LLC.
// It may be used under GPLv2 (SPDX: GPL-2.0-only), GPLv3 (SPDX: GPL-3.0-only),
// or any future license endorsed by Mnemosyne LLC.
// License text can be found in the licenses/ folder.
#define LIBTRANSMISSION_VARIANT_MODULE
#include <cstdint> // int64_t
#include <locale>
#include <optional>
#include <string>
#include <string_view>
#include <libtransmission/crypto-utils.h>
#include <libtransmission/quark.h>
#include <libtransmission/utils.h>
#include <libtransmission/variant.h>
#include "gtest/gtest.h"
using namespace std::literals;
class JSONTest : public ::testing::TestWithParam<char const*>
{
protected:
static void SetUpTestSuite()
{
tr_lib_init();
}
void SetUp() override
{
::testing::TestWithParam<char const*>::SetUp();
auto const* locale_str = GetParam();
old_locale_ = tr_locale_set_global(locale_str);
if (!old_locale_)
{
GTEST_SKIP();
}
}
void TearDown() override
{
if (old_locale_)
{
tr_locale_set_global(*old_locale_);
}
::testing::TestWithParam<char const*>::TearDown();
}
private:
std::optional<std::locale> old_locale_;
};
TEST_P(JSONTest, testElements)
{
static auto constexpr In = std::string_view{
"{ \"string\": \"hello world\","
" \"escaped\": \"bell \\b formfeed \\f linefeed \\n carriage return \\r tab \\t\","
" \"int\": 5, "
" \"float\": 6.5, "
" \"true\": true, "
" \"false\": false, "
" \"null\": null }"
};
// Same as In, just formatted differently
static auto constexpr Out = std::string_view{
// clang-format off
"{"
"\"escaped\":\"bell \\b formfeed \\f linefeed \\n carriage return \\r tab \\t\","
"\"false\":false,"
"\"float\":6.5,"
"\"int\":5,"
"\"null\":null,"
"\"string\":\"hello world\","
"\"true\":true"
"}"
// clang-format on
};
auto serde = tr_variant_serde::json().inplace().compact();
auto var = serde.parse(In).value_or(tr_variant{});
auto const* const map = var.get_if<tr_variant::Map>();
ASSERT_NE(map, nullptr);
auto sv = map->value_if<std::string_view>(tr_quark_new("string"sv));
ASSERT_TRUE(sv);
EXPECT_EQ("hello world"sv, *sv);
sv = map->value_if<std::string_view>(tr_quark_new("escaped"sv));
ASSERT_TRUE(sv);
EXPECT_EQ("bell \b formfeed \f linefeed \n carriage return \r tab \t"sv, *sv);
auto i = map->value_if<int64_t>(tr_quark_new("int"sv));
ASSERT_TRUE(i);
EXPECT_EQ(5, *i);
auto d = map->value_if<double>(tr_quark_new("float"sv));
ASSERT_TRUE(d);
EXPECT_EQ(65, int(*d * 10));
auto b = map->value_if<bool>(tr_quark_new("true"sv));
ASSERT_TRUE(b);
EXPECT_TRUE(*b);
b = map->value_if<bool>(tr_quark_new("false"sv));
ASSERT_TRUE(b);
EXPECT_FALSE(*b);
auto n = map->value_if<std::nullptr_t>(tr_quark_new("null"sv));
EXPECT_TRUE(n);
EXPECT_EQ(serde.to_string(var), Out);
}
TEST_P(JSONTest, testUtf8)
{
auto in = "{ \"key\": \"Letöltések\" }"sv;
tr_quark const key = tr_quark_new("key"sv);
auto serde = tr_variant_serde::json().inplace().compact();
auto var = serde.parse(in).value_or(tr_variant{});
auto* map = var.get_if<tr_variant::Map>();
ASSERT_NE(map, nullptr);
auto sv = map->value_if<std::string_view>(key);
ASSERT_TRUE(sv);
EXPECT_EQ("Letöltések"sv, *sv);
var.clear();
in = R"({ "key": "\u005C" })"sv;
var = serde.parse(in).value_or(tr_variant{});
map = var.get_if<tr_variant::Map>();
ASSERT_NE(map, nullptr);
sv = map->value_if<std::string_view>(key);
ASSERT_TRUE(sv);
EXPECT_EQ("\\"sv, *sv);
var.clear();
/**
* 1. Feed it JSON-escaped nonascii to the JSON decoder.
* 2. Confirm that the result is UTF-8.
* 3. Feed the same UTF-8 back into the JSON encoder.
* 4. Confirm that the result is UTF-8.
* 5. Dogfood that result back into the parser.
* 6. Confirm that the result is UTF-8.
*/
in = R"({ "key": "Let\u00f6lt\u00e9sek" })"sv;
var = serde.parse(in).value_or(tr_variant{});
map = var.get_if<tr_variant::Map>();
ASSERT_NE(map, nullptr);
sv = map->value_if<std::string_view>(key);
ASSERT_TRUE(sv);
EXPECT_EQ("Letöltések"sv, *sv);
auto json = serde.to_string(var);
var.clear();
EXPECT_FALSE(std::empty(json));
EXPECT_EQ(R"({"key":"Letöltések"})"sv, json);
var = serde.parse(json).value_or(tr_variant{});
map = var.get_if<tr_variant::Map>();
ASSERT_NE(map, nullptr);
sv = map->value_if<std::string_view>(key);
ASSERT_TRUE(sv);
EXPECT_EQ("Letöltések"sv, *sv);
// Test string known to be prone to locale issues
// https://github.com/transmission/transmission/issues/5967
var.clear();
var = tr_variant::make_map(1U);
map = var.get_if<tr_variant::Map>();
map->try_emplace(key, "Дыскаграфія"sv);
json = serde.to_string(var);
EXPECT_EQ(R"({"key":"Дыскаграфія"})"sv, json);
var = serde.parse(json).value_or(tr_variant{});
map = var.get_if<tr_variant::Map>();
ASSERT_NE(map, nullptr);
sv = map->value_if<std::string_view>(key);
ASSERT_TRUE(sv);
EXPECT_EQ("Дыскаграфія"sv, *sv);
// Thinking emoji 🤔
var.clear();
var = tr_variant::make_map(1U);
map = var.get_if<tr_variant::Map>();
map->try_emplace(key, "\xf0\x9f\xa4\x94"sv);
json = serde.to_string(var);
EXPECT_EQ("{\"key\":\"\xf0\x9f\xa4\x94\"}"sv, json);
var = serde.parse(json).value_or(tr_variant{});
map = var.get_if<tr_variant::Map>();
ASSERT_NE(map, nullptr);
sv = map->value_if<std::string_view>(key);
ASSERT_TRUE(sv);
EXPECT_EQ("\xf0\x9f\xa4\x94"sv, *sv);
}
TEST_P(JSONTest, test1)
{
static auto constexpr Input =
"{\n"
" \"headers\": {\n"
" \"type\": \"request\",\n"
" \"tag\": 666\n"
" },\n"
" \"body\": {\n"
" \"name\": \"torrent-info\",\n"
" \"arguments\": {\n"
" \"ids\": [ 7, 10 ]\n"
" }\n"
" }\n"
"}\n"sv;
auto serde = tr_variant_serde::json();
auto var = serde.inplace().parse(Input).value_or(tr_variant{});
auto* map = var.get_if<tr_variant::Map>();
ASSERT_NE(map, nullptr);
auto* headers = map->find_if<tr_variant::Map>(tr_quark_new("headers"sv));
ASSERT_NE(headers, nullptr);
auto sv = headers->value_if<std::string_view>(tr_quark_new("type"sv));
ASSERT_TRUE(sv);
EXPECT_EQ("request"sv, *sv);
auto i = headers->value_if<int64_t>(TR_KEY_tag);
ASSERT_TRUE(i);
EXPECT_EQ(666, *i);
auto* body = map->find_if<tr_variant::Map>(tr_quark_new("body"sv));
ASSERT_NE(body, nullptr);
sv = body->value_if<std::string_view>(TR_KEY_name);
ASSERT_TRUE(sv);
EXPECT_EQ("torrent-info"sv, *sv);
auto* args = body->find_if<tr_variant::Map>(tr_quark_new("arguments"sv));
ASSERT_NE(args, nullptr);
auto* ids = args->find_if<tr_variant::Vector>(TR_KEY_ids);
ASSERT_NE(ids, nullptr);
EXPECT_EQ(2U, std::size(*ids));
i = (*ids)[0].value_if<int64_t>();
ASSERT_TRUE(i);
EXPECT_EQ(7, *i);
i = (*ids)[1].value_if<int64_t>();
ASSERT_TRUE(i);
EXPECT_EQ(10, *i);
}
TEST_P(JSONTest, test2)
{
static auto constexpr Input = " "sv;
auto var = tr_variant_serde::json().inplace().parse(Input);
EXPECT_FALSE(var.has_value());
}
TEST_P(JSONTest, test3)
{
static auto constexpr Input =
"{ \"error\": 2,"
" \"error_string\": \"torrent not registered with this tracker 6UHsVW'*C\","
" \"eta\": 262792,"
" \"id\": 25,"
" \"left_until_done\": 2275655680 }"sv;
auto var = tr_variant_serde::json().inplace().parse(Input).value_or(tr_variant{});
auto* map = var.get_if<tr_variant::Map>();
ASSERT_NE(map, nullptr);
auto sv = map->value_if<std::string_view>(TR_KEY_error_string);
ASSERT_TRUE(sv);
EXPECT_EQ("torrent not registered with this tracker 6UHsVW'*C"sv, *sv);
}
TEST_P(JSONTest, unescape)
{
static auto constexpr Input = R"({ "string-1": "\/usr\/lib" })"sv;
auto var = tr_variant_serde::json().inplace().parse(Input).value_or(tr_variant{});
auto* map = var.get_if<tr_variant::Map>();
ASSERT_NE(map, nullptr);
auto sv = map->value_if<std::string_view>(tr_quark_new("string-1"sv));
ASSERT_TRUE(sv);
EXPECT_EQ("/usr/lib"sv, *sv);
}
TEST_P(JSONTest, parseJsonFuzz)
{
auto serde = tr_variant_serde::json().inplace();
auto var = serde.parse({ nullptr, 0U });
EXPECT_FALSE(var);
auto buf = std::vector<char>{};
for (size_t i = 0; i < 100000U; ++i)
{
buf.resize(tr_rand_int(1024U));
tr_rand_buffer(std::data(buf), std::size(buf));
(void)serde.parse({ std::data(buf), std::size(buf) });
}
}
INSTANTIATE_TEST_SUITE_P( //
JSON,
JSONTest,
::testing::Values( //
"C",
"da_DK.UTF-8",
"fr_FR.UTF-8",
"ru_RU.UTF-8"));