mirror of
https://github.com/transmission/transmission
synced 2024-12-30 19:46:56 +00:00
e8fcb025a0
* 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.
260 lines
8.2 KiB
C++
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"));
|