1
0
Fork 0
mirror of https://github.com/transmission/transmission synced 2024-12-30 19:46:56 +00:00
transmission/tests/libtransmission/json-test.cc
Mike Gelfand e8fcb025a0
fix: use user-preferred locale (#5444)
* Use user-preferred locale

Previous fix adding `L` format specifier was correct but only fixed half
of the problem, as C++ locale is set up to be "C" by default. GTK client
used to call `setlocale(LC_ALL, "")` to set up user-preferred locale
which only affected C functions and `std::locale` (used by libfmt) was
unaware of those changes.

Apply the fix to all the binaries since they're all doing some sort of
output to the user and calling libtransmission helpers, as well as using
libfmt directly.

* Improve libtransmission's json-test

Set the locale C++ way to avoid any sort of inconsistencies, and also
restore it to the old one once finished testing.

* Improve transmission-show test runner script

Quote outputs to avoid CMake error about `message()` being called with
no arguments. Capture stderr to the same output file. Fallback to `git
diff` if `diff` wasn't found. A few other minor changes.
2023-04-27 09:10:33 -05:00

260 lines
8.2 KiB
C++

// 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 <locale>
#include <optional>
#include <string>
#include <string_view>
#include <tuple>
#include <libtransmission/transmission.h>
#include <libtransmission/variant.h>
#include <libtransmission/variant-common.h>
#include "gtest/gtest.h"
using namespace std::literals;
class JSONTest : public ::testing::TestWithParam<char const*>
{
protected:
void SetUp() override
{
auto const* locale_str = GetParam();
try
{
old_locale_ = std::locale::global(std::locale{ {}, new std::numpunct_byname<char>{ locale_str } });
}
catch (std::runtime_error const&)
{
GTEST_SKIP();
}
}
void TearDown() override
{
if (old_locale_)
{
std::ignore = std::locale::global(*old_locale_);
}
}
private:
std::optional<std::locale> old_locale_;
};
TEST_P(JSONTest, testElements)
{
auto const in = std::string{
"{ \"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 }"
};
tr_variant top;
EXPECT_TRUE(tr_variantFromBuf(&top, TR_VARIANT_PARSE_JSON | TR_VARIANT_PARSE_INPLACE, in));
EXPECT_TRUE(tr_variantIsDict(&top));
auto sv = std::string_view{};
auto key = tr_quark_new("string"sv);
EXPECT_TRUE(tr_variantDictFindStrView(&top, key, &sv));
EXPECT_EQ("hello world"sv, sv);
EXPECT_TRUE(tr_variantDictFindStrView(&top, tr_quark_new("escaped"sv), &sv));
EXPECT_EQ("bell \b formfeed \f linefeed \n carriage return \r tab \t"sv, sv);
auto i = int64_t{};
EXPECT_TRUE(tr_variantDictFindInt(&top, tr_quark_new("int"sv), &i));
EXPECT_EQ(5, i);
auto d = double{};
EXPECT_TRUE(tr_variantDictFindReal(&top, tr_quark_new("float"sv), &d));
EXPECT_EQ(65, int(d * 10));
auto f = bool{};
EXPECT_TRUE(tr_variantDictFindBool(&top, tr_quark_new("true"sv), &f));
EXPECT_TRUE(f);
EXPECT_TRUE(tr_variantDictFindBool(&top, tr_quark_new("false"sv), &f));
EXPECT_FALSE(f);
EXPECT_TRUE(tr_variantDictFindStrView(&top, tr_quark_new("null"sv), &sv));
EXPECT_EQ(""sv, sv);
tr_variantClear(&top);
}
TEST_P(JSONTest, testUtf8)
{
auto in = "{ \"key\": \"Letöltések\" }"sv;
tr_variant top;
auto sv = std::string_view{};
tr_quark const key = tr_quark_new("key"sv);
EXPECT_TRUE(tr_variantFromBuf(&top, TR_VARIANT_PARSE_JSON | TR_VARIANT_PARSE_INPLACE, in));
EXPECT_TRUE(tr_variantIsDict(&top));
EXPECT_TRUE(tr_variantDictFindStrView(&top, key, &sv));
EXPECT_EQ("Letöltések"sv, sv);
tr_variantClear(&top);
in = R"({ "key": "\u005C" })"sv;
EXPECT_TRUE(tr_variantFromBuf(&top, TR_VARIANT_PARSE_JSON | TR_VARIANT_PARSE_INPLACE, in));
EXPECT_TRUE(tr_variantIsDict(&top));
EXPECT_TRUE(tr_variantDictFindStrView(&top, key, &sv));
EXPECT_EQ("\\"sv, sv);
tr_variantClear(&top);
/**
* 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 JSON-escaped.
* 5. Dogfood that result back into the parser.
* 6. Confirm that the result is UTF-8.
*/
in = R"({ "key": "Let\u00f6lt\u00e9sek" })"sv;
EXPECT_TRUE(tr_variantFromBuf(&top, TR_VARIANT_PARSE_JSON | TR_VARIANT_PARSE_INPLACE, in));
EXPECT_TRUE(tr_variantIsDict(&top));
EXPECT_TRUE(tr_variantDictFindStrView(&top, key, &sv));
EXPECT_EQ("Letöltések"sv, sv);
auto json = tr_variantToStr(&top, TR_VARIANT_FMT_JSON);
tr_variantClear(&top);
EXPECT_FALSE(std::empty(json));
EXPECT_NE(std::string::npos, json.find("\\u00f6"));
EXPECT_NE(std::string::npos, json.find("\\u00e9"));
EXPECT_TRUE(tr_variantFromBuf(&top, TR_VARIANT_PARSE_JSON | TR_VARIANT_PARSE_INPLACE, json));
EXPECT_TRUE(tr_variantIsDict(&top));
EXPECT_TRUE(tr_variantDictFindStrView(&top, key, &sv));
EXPECT_EQ("Letöltések"sv, sv);
tr_variantClear(&top);
}
TEST_P(JSONTest, testUtf16Surrogates)
{
static auto constexpr ThinkingFaceEmojiUtf8 = "\xf0\x9f\xa4\x94"sv;
auto top = tr_variant{};
tr_variantInitDict(&top, 1);
auto const key = tr_quark_new("key"sv);
tr_variantDictAddStr(&top, key, ThinkingFaceEmojiUtf8);
auto const json = tr_variantToStr(&top, TR_VARIANT_FMT_JSON_LEAN);
EXPECT_NE(std::string::npos, json.find("ud83e"));
EXPECT_NE(std::string::npos, json.find("udd14"));
tr_variantClear(&top);
auto parsed = tr_variant{};
EXPECT_TRUE(tr_variantFromBuf(&parsed, TR_VARIANT_PARSE_JSON | TR_VARIANT_PARSE_INPLACE, json));
EXPECT_TRUE(tr_variantIsDict(&parsed));
auto value = std::string_view{};
EXPECT_TRUE(tr_variantDictFindStrView(&parsed, key, &value));
EXPECT_EQ(ThinkingFaceEmojiUtf8, value);
tr_variantClear(&parsed);
}
TEST_P(JSONTest, test1)
{
auto const in = std::string{
"{\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"
};
tr_variant top;
EXPECT_TRUE(tr_variantFromBuf(&top, TR_VARIANT_PARSE_JSON | TR_VARIANT_PARSE_INPLACE, in));
auto sv = std::string_view{};
auto i = int64_t{};
EXPECT_TRUE(tr_variantIsDict(&top));
auto* headers = tr_variantDictFind(&top, tr_quark_new("headers"sv));
EXPECT_NE(nullptr, headers);
EXPECT_TRUE(tr_variantIsDict(headers));
EXPECT_TRUE(tr_variantDictFindStrView(headers, tr_quark_new("type"sv), &sv));
EXPECT_EQ("request"sv, sv);
EXPECT_TRUE(tr_variantDictFindInt(headers, TR_KEY_tag, &i));
EXPECT_EQ(666, i);
auto* body = tr_variantDictFind(&top, tr_quark_new("body"sv));
EXPECT_NE(nullptr, body);
EXPECT_TRUE(tr_variantDictFindStrView(body, TR_KEY_name, &sv));
EXPECT_EQ("torrent-info"sv, sv);
auto* args = tr_variantDictFind(body, tr_quark_new("arguments"sv));
EXPECT_NE(nullptr, args);
EXPECT_TRUE(tr_variantIsDict(args));
auto* ids = tr_variantDictFind(args, TR_KEY_ids);
EXPECT_NE(nullptr, ids);
EXPECT_TRUE(tr_variantIsList(ids));
EXPECT_EQ(2U, tr_variantListSize(ids));
EXPECT_TRUE(tr_variantGetInt(tr_variantListChild(ids, 0), &i));
EXPECT_EQ(7, i);
EXPECT_TRUE(tr_variantGetInt(tr_variantListChild(ids, 1), &i));
EXPECT_EQ(10, i);
tr_variantClear(&top);
}
TEST_P(JSONTest, test2)
{
tr_variant top;
auto const in = std::string{ " " };
top.type = 0;
EXPECT_FALSE(tr_variantFromBuf(&top, TR_VARIANT_PARSE_JSON | TR_VARIANT_PARSE_INPLACE, in));
EXPECT_FALSE(tr_variantIsDict(&top));
}
TEST_P(JSONTest, test3)
{
auto const
in = "{ \"error\": 2,"
" \"errorString\": \"torrent not registered with this tracker 6UHsVW'*C\","
" \"eta\": 262792,"
" \"id\": 25,"
" \"leftUntilDone\": 2275655680 }"sv;
tr_variant top;
EXPECT_TRUE(tr_variantFromBuf(&top, TR_VARIANT_PARSE_JSON | TR_VARIANT_PARSE_INPLACE, in));
auto sv = std::string_view{};
EXPECT_TRUE(tr_variantDictFindStrView(&top, TR_KEY_errorString, &sv));
EXPECT_EQ("torrent not registered with this tracker 6UHsVW'*C"sv, sv);
tr_variantClear(&top);
}
TEST_P(JSONTest, unescape)
{
tr_variant top;
auto const in = std::string{ R"({ "string-1": "\/usr\/lib" })" };
EXPECT_TRUE(tr_variantFromBuf(&top, TR_VARIANT_PARSE_JSON | TR_VARIANT_PARSE_INPLACE, in));
auto sv = std::string_view{};
EXPECT_TRUE(tr_variantDictFindStrView(&top, tr_quark_new("string-1"sv), &sv));
EXPECT_EQ("/usr/lib"sv, sv);
tr_variantClear(&top);
}
INSTANTIATE_TEST_SUITE_P( //
JSON,
JSONTest,
::testing::Values( //
"C",
"da_DK.UTF-8",
"fr_FR.UTF-8",
"ru_RU.UTF-8"));