mirror of
https://github.com/OpenRCT2/OpenRCT2
synced 2025-12-12 10:32:26 +01:00
353 lines
12 KiB
C++
353 lines
12 KiB
C++
/*****************************************************************************
|
||
* Copyright (c) 2014-2025 OpenRCT2 developers
|
||
*
|
||
* For a complete list of all authors, please refer to contributors.md
|
||
* Interested in contributing? Visit https://github.com/OpenRCT2/OpenRCT2
|
||
*
|
||
* OpenRCT2 is licensed under the GNU General Public License version 3.
|
||
*****************************************************************************/
|
||
|
||
#include "AssertHelpers.hpp"
|
||
#include "helpers/StringHelpers.hpp"
|
||
|
||
#include <algorithm>
|
||
#include <gtest/gtest.h>
|
||
#include <openrct2/core/CodepointView.hpp>
|
||
#include <openrct2/core/EnumUtils.hpp>
|
||
#include <openrct2/core/String.hpp>
|
||
#include <string>
|
||
#include <tuple>
|
||
#include <utility>
|
||
#include <vector>
|
||
|
||
using namespace OpenRCT2;
|
||
|
||
using TCase = std::tuple<std::string, std::string, std::string>;
|
||
|
||
class StringTest : public testing::TestWithParam<TCase>
|
||
{
|
||
};
|
||
|
||
///////////////////////////////////////////////////////////////////////////////
|
||
// Tests for String::Trim
|
||
///////////////////////////////////////////////////////////////////////////////
|
||
|
||
INSTANTIATE_TEST_SUITE_P(
|
||
TrimData, StringTest,
|
||
testing::Values(
|
||
// input after Trim after TrimStart
|
||
TCase("string", "string", "string"), TCase(" string", "string", "string"), TCase("string ", "string", "string "),
|
||
TCase(" some string ", "some string", "some string "), TCase(" ", "", ""),
|
||
TCase(" ストリング", "ストリング", "ストリング"), TCase("ストリング ", "ストリング", "ストリング "),
|
||
TCase(" ストリング ", "ストリング", "ストリング "), TCase(" ", "", ""), TCase("", "", ""),
|
||
TCase("\n", "", ""), TCase("\n\n\n\r\n", "", ""), TCase("\n\n\n\r\nstring\n\n", "string", "string\n\n")));
|
||
TEST_P(StringTest, Trim)
|
||
{
|
||
auto testCase = GetParam();
|
||
std::string input = std::get<0>(testCase);
|
||
std::string expected = std::get<1>(testCase);
|
||
std::string actual = String::trim(input);
|
||
ASSERT_EQ(expected, actual);
|
||
}
|
||
|
||
TEST_P(StringTest, TrimStart)
|
||
{
|
||
auto testCase = GetParam();
|
||
std::string input = std::get<0>(testCase);
|
||
std::string expected = std::get<2>(testCase);
|
||
std::string actual = String::trimStart(input);
|
||
ASSERT_EQ(expected, actual);
|
||
}
|
||
|
||
///////////////////////////////////////////////////////////////////////////////
|
||
// Tests for String::split
|
||
///////////////////////////////////////////////////////////////////////////////
|
||
|
||
TEST_F(StringTest, Split_ByComma)
|
||
{
|
||
auto actual = String::split("a,bb,ccc,dd", ",");
|
||
AssertVector<std::string_view>(actual, { "a", "bb", "ccc", "dd" });
|
||
}
|
||
TEST_F(StringTest, Split_ByColonColon)
|
||
{
|
||
auto actual = String::split("a::bb:ccc:::::dd", "::");
|
||
AssertVector<std::string_view>(actual, { "a", "bb:ccc", "", ":dd" });
|
||
}
|
||
TEST_F(StringTest, Split_Empty)
|
||
{
|
||
auto actual = String::split("", ".");
|
||
AssertVector<std::string_view>(actual, {});
|
||
}
|
||
TEST_F(StringTest, Split_ByEmpty)
|
||
{
|
||
EXPECT_THROW(String::split("string", ""), std::invalid_argument);
|
||
}
|
||
|
||
///////////////////////////////////////////////////////////////////////////////
|
||
// Tests for String::Convert
|
||
///////////////////////////////////////////////////////////////////////////////
|
||
|
||
TEST_F(StringTest, Convert_950_to_UTF8)
|
||
{
|
||
auto input = StringFromHex("a7d6b374aabab4c4a6e2aab0af57");
|
||
auto expected = u8"快速的棕色狐狸";
|
||
auto actual = String::convertToUtf8(input, OpenRCT2::CodePage::CP_950);
|
||
ASSERT_EQ(expected, actual);
|
||
}
|
||
|
||
TEST_F(StringTest, Convert_UTF8_to_UTF8)
|
||
{
|
||
auto input = u8"سريع|brown|ثعلب";
|
||
auto expected = input;
|
||
auto actual = String::convertToUtf8(input, OpenRCT2::CodePage::UTF8);
|
||
ASSERT_EQ(expected, actual);
|
||
}
|
||
|
||
TEST_F(StringTest, Convert_Empty)
|
||
{
|
||
auto input = "";
|
||
auto expected = input;
|
||
auto actual = String::convertToUtf8(input, OpenRCT2::CodePage::CP_1252);
|
||
ASSERT_EQ(expected, actual);
|
||
}
|
||
|
||
///////////////////////////////////////////////////////////////////////////////
|
||
// Tests for String::toUpper
|
||
///////////////////////////////////////////////////////////////////////////////
|
||
|
||
TEST_F(StringTest, ToUpper_Basic)
|
||
{
|
||
auto actual = String::toUpper("test TEST tEsT 1234");
|
||
ASSERT_STREQ(actual.c_str(), "TEST TEST TEST 1234");
|
||
}
|
||
TEST_F(StringTest, ToUpper_Dutch)
|
||
{
|
||
auto actual = String::toUpper(u8"fijntjes puffend fietsen");
|
||
ASSERT_STREQ(actual.c_str(), u8"FIJNTJES PUFFEND FIETSEN");
|
||
}
|
||
TEST_F(StringTest, ToUpper_French)
|
||
{
|
||
auto actual = String::toUpper(u8"jusqu'à 2500 carrés de côté");
|
||
ASSERT_STREQ(actual.c_str(), u8"JUSQU'À 2500 CARRÉS DE CÔTÉ");
|
||
}
|
||
TEST_F(StringTest, ToUpper_Greek)
|
||
{
|
||
auto actual = String::toUpper(u8"μέχρι 2500 τετράγωνα στην άκρη");
|
||
ASSERT_STREQ(actual.c_str(), u8"ΜΈΧΡΙ 2500 ΤΕΤΡΆΓΩΝΑ ΣΤΗΝ ΆΚΡΗ");
|
||
}
|
||
TEST_F(StringTest, ToUpper_Russian)
|
||
{
|
||
auto actual = String::toUpper(u8"до 2500 квадратов в сторону");
|
||
ASSERT_STREQ(actual.c_str(), u8"ДО 2500 КВАДРАТОВ В СТОРОНУ");
|
||
}
|
||
TEST_F(StringTest, ToUpper_Japanese)
|
||
{
|
||
auto actual = String::toUpper(u8"日本語で大文字がなし");
|
||
ASSERT_STREQ(actual.c_str(), u8"日本語で大文字がなし");
|
||
}
|
||
|
||
TEST_F(StringTest, StrLogicalCmp)
|
||
{
|
||
auto res_logical_1 = String::logicalCmp("foo1", "foo1_2");
|
||
auto res_logical_2 = String::logicalCmp("foo1_2", "foo1");
|
||
auto res_1 = strcmp("foo1", "foo1_2");
|
||
auto res_2 = strcmp("foo1_2", "foo1");
|
||
// We only care if sign is correct, actual values might not be.
|
||
EXPECT_GE(res_1 * res_logical_1, 1);
|
||
EXPECT_GE(res_2 * res_logical_2, 1);
|
||
EXPECT_NE(res_logical_1, res_logical_2);
|
||
|
||
EXPECT_GT(String::logicalCmp("foo12", "foo1"), 0);
|
||
EXPECT_LT(String::logicalCmp("foo12", "foo13"), 0);
|
||
EXPECT_EQ(String::logicalCmp("foo13", "foo13"), 0);
|
||
|
||
EXPECT_EQ(String::logicalCmp("foo13", "FOO13"), 0);
|
||
|
||
EXPECT_LT(String::logicalCmp("A", "b"), 0);
|
||
EXPECT_LT(String::logicalCmp("a", "B"), 0);
|
||
EXPECT_GT(String::logicalCmp("B", "a"), 0);
|
||
EXPECT_GT(String::logicalCmp("b", "A"), 0);
|
||
|
||
// ^ is used at the start of a ride name to move it to the end of the list
|
||
EXPECT_LT(String::logicalCmp("A", "^"), 0);
|
||
EXPECT_LT(String::logicalCmp("a", "^"), 0);
|
||
EXPECT_LT(String::logicalCmp("!", "A"), 0);
|
||
EXPECT_LT(String::logicalCmp("!", "a"), 0);
|
||
}
|
||
|
||
TEST_F(StringTest, IEqualsU8String)
|
||
{
|
||
EXPECT_TRUE(String::iequals(u8string{ u8"" }, u8string{ u8"" }));
|
||
EXPECT_TRUE(String::iequals(u8string{ u8"Test" }, u8string{ u8"Test" }));
|
||
EXPECT_TRUE(String::iequals(u8string{ u8"TesT" }, u8string{ u8"Test" }));
|
||
EXPECT_TRUE(String::iequals(u8string{ u8"TEsT" }, u8string{ u8"Test" }));
|
||
|
||
EXPECT_FALSE(String::iequals(u8string{ u8"Test" }, u8string{ u8"Message" }));
|
||
EXPECT_FALSE(String::iequals(u8string{ u8"Test" }, u8string{ u8"TestMessage" }));
|
||
EXPECT_FALSE(String::iequals(u8string{ u8"" }, u8string{ u8"Test" }));
|
||
EXPECT_FALSE(String::iequals(u8string{ u8"Test" }, u8string{ u8"" }));
|
||
}
|
||
|
||
TEST_F(StringTest, IEqualsU8StringView)
|
||
{
|
||
EXPECT_TRUE(String::iequals(u8string_view{ u8"" }, u8string_view{ u8"" }));
|
||
EXPECT_TRUE(String::iequals(u8string_view{ u8"Test" }, u8string_view{ u8"Test" }));
|
||
EXPECT_TRUE(String::iequals(u8string_view{ u8"TesT" }, u8string_view{ u8"Test" }));
|
||
EXPECT_TRUE(String::iequals(u8string_view{ u8"TEsT" }, u8string_view{ u8"Test" }));
|
||
|
||
EXPECT_FALSE(String::iequals(u8string_view{ u8"Test" }, u8string_view{ u8"Message" }));
|
||
EXPECT_FALSE(String::iequals(u8string_view{ u8"Test" }, u8string_view{ u8"TestMessage" }));
|
||
EXPECT_FALSE(String::iequals(u8string_view{ u8"" }, u8string_view{ u8"Test" }));
|
||
EXPECT_FALSE(String::iequals(u8string_view{ u8"Test" }, u8string_view{ u8"" }));
|
||
}
|
||
|
||
TEST_F(StringTest, EqualsU8String)
|
||
{
|
||
EXPECT_TRUE(String::equals(u8string{ u8"" }, u8string{ u8"" }));
|
||
EXPECT_TRUE(String::equals(u8string{ u8"Test" }, u8string{ u8"Test" }));
|
||
|
||
EXPECT_FALSE(String::equals(u8string{ u8"TesT" }, u8string{ u8"Test" }));
|
||
EXPECT_FALSE(String::equals(u8string{ u8"TEsT" }, u8string{ u8"Test" }));
|
||
EXPECT_FALSE(String::equals(u8string{ u8"Test" }, u8string{ u8"Message" }));
|
||
EXPECT_FALSE(String::equals(u8string{ u8"Test" }, u8string{ u8"TestMessage" }));
|
||
EXPECT_FALSE(String::equals(u8string{ u8"" }, u8string{ u8"Test" }));
|
||
EXPECT_FALSE(String::equals(u8string{ u8"Test" }, u8string{ u8"" }));
|
||
}
|
||
|
||
TEST_F(StringTest, EqualsU8StringView)
|
||
{
|
||
EXPECT_TRUE(String::equals(u8string_view{ u8"" }, u8string_view{ u8"" }));
|
||
EXPECT_TRUE(String::equals(u8string_view{ u8"Test" }, u8string_view{ u8"Test" }));
|
||
|
||
EXPECT_FALSE(String::equals(u8string_view{ u8"TesT" }, u8string_view{ u8"Test" }));
|
||
EXPECT_FALSE(String::equals(u8string_view{ u8"TEsT" }, u8string_view{ u8"Test" }));
|
||
EXPECT_FALSE(String::equals(u8string_view{ u8"Test" }, u8string_view{ u8"Message" }));
|
||
EXPECT_FALSE(String::equals(u8string_view{ u8"Test" }, u8string_view{ u8"TestMessage" }));
|
||
EXPECT_FALSE(String::equals(u8string_view{ u8"" }, u8string_view{ u8"Test" }));
|
||
EXPECT_FALSE(String::equals(u8string_view{ u8"Test" }, u8string_view{ u8"" }));
|
||
}
|
||
|
||
class CodepointViewTest : public testing::Test
|
||
{
|
||
};
|
||
|
||
static std::vector<char32_t> ToVector(std::string_view s)
|
||
{
|
||
std::vector<char32_t> codepoints;
|
||
for (auto codepoint : CodepointView(s))
|
||
{
|
||
codepoints.push_back(codepoint);
|
||
}
|
||
return codepoints;
|
||
}
|
||
|
||
static void AssertCodepoints(std::string_view s, const std::vector<char32_t>& expected)
|
||
{
|
||
ASSERT_EQ(ToVector(s), expected);
|
||
}
|
||
|
||
TEST_F(CodepointViewTest, CodepointView_iterate)
|
||
{
|
||
AssertCodepoints("test", { 't', 'e', 's', 't' });
|
||
AssertCodepoints("ゲスト", { U'ゲ', U'ス', U'ト' });
|
||
AssertCodepoints("<🎢>", { U'<', U'🎢', U'>' });
|
||
}
|
||
|
||
TEST_F(StringTest, LogicalCompare)
|
||
{
|
||
std::vector<std::string> expected = {
|
||
"1001 Troubles", "3D Cinema 1", "Aerial Cycles", "Batflyer", "bpb",
|
||
"bpb.sv6", "Drive-by", "foo", "foobar", "Guest 10",
|
||
"Guest 99", "Guest 100", "John v2.0", "John v2.1", "River of the Damned",
|
||
"Terror-dactyl",
|
||
};
|
||
|
||
std::vector<std::string> inputs = {
|
||
"Guest 99",
|
||
"Batflyer",
|
||
"John v2.1",
|
||
"bpb",
|
||
"3D Cinema 1",
|
||
"Drive-by",
|
||
"John v2.0",
|
||
"Guest 10",
|
||
"Terror-dactyl",
|
||
"Aerial Cycles",
|
||
"foobar",
|
||
"1001 Troubles",
|
||
"River of the Damned",
|
||
"bpb.sv6",
|
||
"Guest 100",
|
||
"foo",
|
||
};
|
||
|
||
std::sort(inputs.begin(), inputs.end(), [](const auto& a, const auto& b) {
|
||
return String::logicalCmp(a.c_str(), b.c_str()) < 0;
|
||
});
|
||
|
||
AssertVector<std::string>(inputs, expected);
|
||
}
|
||
|
||
///////////////////////////////////////////////////////////////////////////////
|
||
// Tests for String::parse
|
||
///////////////////////////////////////////////////////////////////////////////
|
||
|
||
TEST_F(StringTest, Parse_Basic)
|
||
{
|
||
auto actual = String::tryParse<std::int32_t>("123");
|
||
ASSERT_TRUE(actual.has_value());
|
||
ASSERT_EQ(*actual, 123);
|
||
}
|
||
|
||
TEST_F(StringTest, Parse_Empty)
|
||
{
|
||
auto actual = String::tryParse<std::int32_t>("");
|
||
ASSERT_FALSE(actual.has_value());
|
||
}
|
||
|
||
TEST_F(StringTest, Parse_Zero)
|
||
{
|
||
auto actual = String::tryParse<std::int32_t>("0");
|
||
ASSERT_TRUE(actual.has_value());
|
||
ASSERT_EQ(*actual, 0);
|
||
}
|
||
|
||
TEST_F(StringTest, Parse_LeadingZero)
|
||
{
|
||
auto actual = String::tryParse<std::int32_t>("00123");
|
||
ASSERT_TRUE(actual.has_value());
|
||
ASSERT_EQ(*actual, 123);
|
||
}
|
||
|
||
TEST_F(StringTest, Parse_InvalidChar)
|
||
{
|
||
auto actual = String::tryParse<std::int32_t>("12a3");
|
||
ASSERT_FALSE(actual.has_value());
|
||
}
|
||
|
||
TEST_F(StringTest, Parse_LeadingNonDigit)
|
||
{
|
||
auto actual = String::tryParse<std::int32_t>("a123");
|
||
ASSERT_FALSE(actual.has_value());
|
||
}
|
||
|
||
TEST_F(StringTest, Parse_Negative)
|
||
{
|
||
auto actual = String::tryParse<std::int32_t>("-123");
|
||
ASSERT_TRUE(actual.has_value());
|
||
ASSERT_EQ(*actual, -123);
|
||
}
|
||
|
||
TEST_F(StringTest, Parse_Overflow)
|
||
{
|
||
auto actual = String::tryParse<std::int32_t>("2147483648");
|
||
ASSERT_FALSE(actual.has_value());
|
||
}
|
||
|
||
TEST_F(StringTest, Parse_LargeNumber)
|
||
{
|
||
auto actual = String::tryParse<std::int64_t>("9223372036854775807");
|
||
ASSERT_TRUE(actual.has_value());
|
||
ASSERT_EQ(*actual, 9223372036854775807LL);
|
||
}
|