From ebe4b3c43796dc06ea2f5f6bf9b5d864a85f1212 Mon Sep 17 00:00:00 2001 From: Chris Thrasher Date: Wed, 22 Mar 2023 21:40:17 -0600 Subject: [PATCH] Add tests for `sf::String` --- test/Graphics/Transform.test.cpp | 3 +- test/System/String.test.cpp | 398 +++++++++++++++++++++++++++++++ test/System/Time.test.cpp | 3 +- 3 files changed, 402 insertions(+), 2 deletions(-) diff --git a/test/Graphics/Transform.test.cpp b/test/Graphics/Transform.test.cpp index e38f15765..9f107cbf0 100644 --- a/test/Graphics/Transform.test.cpp +++ b/test/Graphics/Transform.test.cpp @@ -13,7 +13,8 @@ static_assert(std::is_copy_assignable_v); static_assert(std::is_nothrow_move_constructible_v); static_assert(std::is_nothrow_move_assignable_v); -// Use StringMaker to avoid opening namespace std +// Specialize StringMaker for std::vector +// https://github.com/doctest/doctest/blob/master/doc/markdown/stringification.md#docteststringmakert-specialisation namespace doctest { template <> diff --git a/test/System/String.test.cpp b/test/System/String.test.cpp index a6d3c01fd..d923c54a7 100644 --- a/test/System/String.test.cpp +++ b/test/System/String.test.cpp @@ -1,8 +1,406 @@ #include +#include + +#include +#include +#include +#include #include static_assert(std::is_copy_constructible_v); static_assert(std::is_copy_assignable_v); static_assert(std::is_nothrow_move_constructible_v); static_assert(std::is_nothrow_move_assignable_v); + +namespace +{ +// Return either argument depending on whether wchar_t is 16 or 32 bits +// Lets us write tests that work on both Windows where wchar_t is 16 bits +// and elsewhere where it is 32. Otherwise the tests would only work on +// one OS or the other. +template +auto select(const std::basic_string& string16, const std::basic_string& string32) +{ + assert(string16 != string32); + if constexpr (sizeof(wchar_t) == 2) + return string16; + else + return string32; +} +} // namespace + +// Specialize StringMaker for alternative std::basic_string specializations +// std::string's string conversion cannot be specialized but all other string types get special treatment +// https://github.com/doctest/doctest/blob/master/doc/markdown/stringification.md#docteststringmakert-specialisation +namespace doctest +{ +template +struct StringMaker> +{ + static std::string toHex(const CharT character) + { + std::ostringstream stream; + stream << "[\\x" << std::uppercase << std::hex << static_cast(character) << ']'; + return stream.str(); + } + + static String convert(const std::basic_string& string) + { + doctest::String output; + for (const auto character : string) + { + if (character >= 32 && character < 127) + output += std::string(1, static_cast(character)).c_str(); + else + output += toHex(character).c_str(); + } + return output; + } +}; +} // namespace doctest + +TEST_CASE("[System] sf::String") +{ + using namespace std::string_literals; + + SUBCASE("Construction") + { + SUBCASE("Default constructor") + { + const sf::String string; + CHECK(std::string(string) == ""s); + CHECK(std::wstring(string) == L""s); + CHECK(string.toAnsiString() == ""s); + CHECK(string.toWideString() == L""s); + CHECK(string.toUtf8() == std::basic_string()); + CHECK(string.toUtf16() == std::basic_string()); + CHECK(string.toUtf32() == std::basic_string()); + CHECK(string.getSize() == 0); + CHECK(string.isEmpty()); + CHECK(string.getData() != nullptr); + } + + SUBCASE("ANSI character constructor") + { + const sf::String string('a'); + CHECK(std::string(string) == "a"s); + CHECK(std::wstring(string) == L"a"s); + CHECK(string.toAnsiString() == "a"s); + CHECK(string.toWideString() == L"a"s); + CHECK(string.toUtf8() == std::basic_string{'a'}); + CHECK(string.toUtf16() == std::basic_string{'a'}); + CHECK(string.toUtf32() == std::basic_string{'a'}); + CHECK(string.getSize() == 1); + CHECK(!string.isEmpty()); + CHECK(string.getData() != nullptr); + } + + SUBCASE("ANSI C string constructor") + { + const sf::String string("def"); + CHECK(std::string(string) == "def"s); + CHECK(std::wstring(string) == L"def"s); + CHECK(string.toAnsiString() == "def"s); + CHECK(string.toWideString() == L"def"s); + CHECK(string.toUtf8() == std::basic_string{'d', 'e', 'f'}); + CHECK(string.toUtf16() == std::basic_string{'d', 'e', 'f'}); + CHECK(string.toUtf32() == std::basic_string{'d', 'e', 'f'}); + CHECK(string.getSize() == 3); + CHECK(!string.isEmpty()); + CHECK(string.getData() != nullptr); + } + + SUBCASE("ANSI string constructor") + { + const sf::String string("ghi"s); + CHECK(std::string(string) == "ghi"s); + CHECK(std::wstring(string) == L"ghi"s); + CHECK(string.toAnsiString() == "ghi"s); + CHECK(string.toWideString() == L"ghi"s); + CHECK(string.toUtf8() == std::basic_string{'g', 'h', 'i'}); + CHECK(string.toUtf16() == std::basic_string{'g', 'h', 'i'}); + CHECK(string.toUtf32() == std::basic_string{'g', 'h', 'i'}); + CHECK(string.getSize() == 3); + CHECK(!string.isEmpty()); + CHECK(string.getData() != nullptr); + } + + SUBCASE("Wide character constructor") + { + const sf::String string(L'\xFA'); + CHECK(std::string(string) == select("\xFA"s, "\0"s)); + CHECK(std::wstring(string) == L"\xFA"s); + CHECK(string.toAnsiString() == select("\xFA"s, "\0"s)); + CHECK(string.toWideString() == L"\xFA"s); + CHECK(string.toUtf8() == std::basic_string{0xC3, 0xBA}); + CHECK(string.toUtf16() == std::basic_string{0xFA}); + CHECK(string.toUtf32() == std::basic_string{0xFA}); + CHECK(string.getSize() == 1); + CHECK(!string.isEmpty()); + CHECK(string.getData() != nullptr); + } + + SUBCASE("Wide C string constructor") + { + const sf::String string(L"j\xFAl"); + CHECK(std::string(string) == select("j\xFAl"s, "j\0l"s)); + CHECK(std::wstring(string) == L"j\xFAl"s); + CHECK(string.toAnsiString() == select("j\xFAl"s, "j\0l"s)); + CHECK(string.toWideString() == L"j\xFAl"s); + CHECK(string.toUtf8() == std::basic_string{'j', 0xC3, 0xBA, 'l'}); + CHECK(string.toUtf16() == std::basic_string{'j', 0xFA, 'l'}); + CHECK(string.toUtf32() == std::basic_string{'j', 0xFA, 'l'}); + CHECK(string.getSize() == 3); + CHECK(!string.isEmpty()); + CHECK(string.getData() != nullptr); + } + + SUBCASE("Wide string constructor") + { + const sf::String string(L"mno\xFA"s); + CHECK(std::string(string) == select("mno\xFA"s, "mno\0"s)); + CHECK(std::wstring(string) == L"mno\xFA"s); + CHECK(string.toAnsiString() == select("mno\xFA"s, "mno\0"s)); + CHECK(string.toWideString() == L"mno\xFA"s); + CHECK(string.toUtf8() == std::basic_string{'m', 'n', 'o', 0xC3, 0XBA}); + CHECK(string.toUtf16() == std::basic_string{'m', 'n', 'o', 0xFA}); + CHECK(string.toUtf32() == std::basic_string{'m', 'n', 'o', 0xFA}); + CHECK(string.getSize() == 4); + CHECK(!string.isEmpty()); + CHECK(string.getData() != nullptr); + } + + SUBCASE("UTF-32 character constructor") + { + const sf::String string(U'\U0010AFAF'); + CHECK(std::string(string) == "\0"s); + CHECK(std::wstring(string) == select(L""s, L"\U0010AFAF"s)); + CHECK(string.toAnsiString() == "\0"s); + CHECK(string.toWideString() == select(L""s, L"\U0010AFAF"s)); + CHECK(string.toUtf8() == std::basic_string{0xF4, 0x8A, 0xBE, 0xAF}); + CHECK(string.toUtf16() == std::basic_string{0xDBEB, 0xDFAF}); + CHECK(string.toUtf32() == std::basic_string{0x10AFAF}); + CHECK(string.getSize() == 1); + CHECK(!string.isEmpty()); + CHECK(string.getData() != nullptr); + } + + SUBCASE("UTF-32 C string constructor") + { + const sf::String string(reinterpret_cast(U"\U0010ABCDrs")); + CHECK(std::string(string) == "\0rs"s); + CHECK(std::wstring(string) == select(L"rs"s, L"\U0010ABCDrs"s)); + CHECK(string.toAnsiString() == "\0rs"s); + CHECK(string.toWideString() == select(L"rs"s, L"\U0010ABCDrs"s)); + CHECK(string.toUtf8() == std::basic_string{0xF4, 0x8A, 0xAF, 0x8D, 'r', 's'}); + CHECK(string.toUtf16() == std::basic_string{0xDBEA, 0xDFCD, 'r', 's'}); + CHECK(string.toUtf32() == std::basic_string{0x10ABCD, 'r', 's'}); + CHECK(string.getSize() == 3); + CHECK(!string.isEmpty()); + CHECK(string.getData() != nullptr); + } + + SUBCASE("UTF-32 string constructor") + { + const sf::String string(std::basic_string{'t', 'u', 'v', 0x104321}); + CHECK(std::string(string) == "tuv\0"s); + CHECK(std::wstring(string) == select(L"tuv"s, L"tuv\U00104321"s)); + CHECK(string.toAnsiString() == "tuv\0"s); + CHECK(string.toWideString() == select(L"tuv"s, L"tuv\U00104321"s)); + CHECK(string.toUtf8() == std::basic_string{'t', 'u', 'v', 0xF4, 0x84, 0x8C, 0xA1}); + CHECK(string.toUtf16() == std::basic_string{'t', 'u', 'v', 0xDBD0, 0xDF21}); + CHECK(string.toUtf32() == std::basic_string{'t', 'u', 'v', 0x104321}); + CHECK(string.getSize() == 4); + CHECK(!string.isEmpty()); + CHECK(string.getData() != nullptr); + } + } + + SUBCASE("fromUtf8()") + { + constexpr std::array characters{'w', 'x', 'y', 'z'}; + const sf::String string = sf::String::fromUtf8(characters.begin(), characters.end()); + CHECK(std::string(string) == "wxyz"s); + CHECK(std::wstring(string) == L"wxyz"s); + CHECK(string.toAnsiString() == "wxyz"s); + CHECK(string.toWideString() == L"wxyz"s); + CHECK(string.toUtf8() == std::basic_string{'w', 'x', 'y', 'z'}); + CHECK(string.toUtf16() == std::basic_string{'w', 'x', 'y', 'z'}); + CHECK(string.toUtf32() == std::basic_string{'w', 'x', 'y', 'z'}); + CHECK(string.getSize() == 4); + CHECK(!string.isEmpty()); + CHECK(string.getData() != nullptr); + } + + SUBCASE("fromUtf16()") + { + constexpr std::array characters{0xF1, 'x', 'y', 'z'}; + const sf::String string = sf::String::fromUtf16(characters.begin(), characters.end()); + CHECK(std::string(string) == select("\xF1xyz"s, "\0xyz"s)); + CHECK(std::wstring(string) == L"\xF1xyz"s); + CHECK(string.toAnsiString() == select("\xF1xyz"s, "\0xyz"s)); + CHECK(string.toWideString() == L"\xF1xyz"s); + CHECK(string.toUtf8() == std::basic_string{0xC3, 0xB1, 'x', 'y', 'z'}); + CHECK(string.toUtf16() == std::basic_string{0xF1, 'x', 'y', 'z'}); + CHECK(string.toUtf32() == std::basic_string{0xF1, 'x', 'y', 'z'}); + CHECK(string.getSize() == 4); + CHECK(!string.isEmpty()); + CHECK(string.getData() != nullptr); + } + + SUBCASE("fromUtf32()") + { + constexpr std::array characters{'w', 0x104321, 'y', 'z'}; + const sf::String string = sf::String::fromUtf32(characters.begin(), characters.end()); + CHECK(std::string(string) == "w\0yz"s); + CHECK(std::wstring(string) == select(L"wyz"s, L"w\U00104321yz"s)); + CHECK(string.toAnsiString() == "w\0yz"s); + CHECK(string.toWideString() == select(L"wyz"s, L"w\U00104321yz"s)); + CHECK(string.toUtf8() == std::basic_string{'w', 0xF4, 0x84, 0x8C, 0xA1, 'y', 'z'}); + CHECK(string.toUtf16() == std::basic_string{'w', 0xDBD0, 0xDF21, 'y', 'z'}); + CHECK(string.toUtf32() == std::basic_string{'w', 0x104321, 'y', 'z'}); + CHECK(string.getSize() == 4); + CHECK(!string.isEmpty()); + CHECK(string.getData() != nullptr); + } + + SUBCASE("clear()") + { + sf::String string("you'll never guess what happens when you call clear()"); + string.clear(); + CHECK(string == sf::String()); + CHECK(string.getSize() == 0); + } + + SUBCASE("erase()") + { + sf::String string("what if i want a shorter string?"); + string.erase(0, 8); + string.erase(string.getSize() - 1, 1); + CHECK(string == "i want a shorter string"); + CHECK(string.getSize() == 23); + } + + SUBCASE("insert()") + { + sf::String string("please insert text"); + string.insert(7, "don't "); + CHECK(string == "please don't insert text"); + CHECK(string.getSize() == 24); + } + + SUBCASE("find()") + { + const sf::String string("a little bit of this and a little bit of that"); + CHECK(string.find("a little bit") == 0); + CHECK(string.find("a little bit", 15) == 25); + CHECK(string.find("a little bit", 1'000) == sf::String::InvalidPos); + CHECK(string.find("no way you find this") == sf::String::InvalidPos); + } + + SUBCASE("replace()") + { + sf::String string("sfml is the worst"); + string.replace(12, 5, "best!"); + CHECK(string == "sfml is the best!"); + string.replace("the", "THE"); + CHECK(string == "sfml is THE best!"); + } + + SUBCASE("substring()") + { + const sf::String string("let's get some substrings"); + CHECK(string.substring(0) == "let's get some substrings"); + CHECK(string.substring(10) == "some substrings"); + CHECK(string.substring(10, 4) == "some"); + CHECK_THROWS_AS((void)string.substring(1'000), std::out_of_range); + CHECK_THROWS_AS((void)string.substring(420, 69), std::out_of_range); + } + + SUBCASE("begin() and end() const") + { + const sf::String string("let's test the const iterators"); + CHECK(*string.begin() == 'l'); + CHECK(*(string.end() - 1) == 's'); + for (const auto character : string) + CHECK(character != 0); + } + + SUBCASE("begin() and end()") + { + sf::String string("let's test the iterators"); + CHECK(*string.begin() == 'l'); + CHECK(*(string.end() - 1) == 's'); + for (auto& character : string) + character = 'x'; + CHECK(string == "xxxxxxxxxxxxxxxxxxxxxxxx"); + } + + SUBCASE("Operators") + { + SUBCASE("operator+=") + { + sf::String string; + string += sf::String("xyz"); + CHECK(string.toAnsiString() == "xyz"s); + } + + SUBCASE("operator[] const") + { + const sf::String string("the quick brown fox"); + CHECK(string[0] == 't'); + CHECK(string[10] == 'b'); + } + + SUBCASE("operator[]") + { + sf::String string("the quick brown fox"); + CHECK(string[0] == 't'); + string[1] = 'x'; + CHECK(string[1] == 'x'); + } + + SUBCASE("operator==") + { + CHECK(sf::String() == sf::String()); + CHECK_FALSE(sf::String() == sf::String(' ')); + } + + SUBCASE("operator!=") + { + CHECK(sf::String() != sf::String(' ')); + CHECK_FALSE(sf::String() != sf::String()); + } + + SUBCASE("operator<") + { + CHECK(sf::String('a') < sf::String('b')); + CHECK_FALSE(sf::String() < sf::String()); + } + + SUBCASE("operator>") + { + CHECK(sf::String('b') > sf::String('a')); + CHECK_FALSE(sf::String() > sf::String()); + } + + SUBCASE("operator<=") + { + CHECK(sf::String() <= sf::String()); + CHECK(sf::String('a') <= sf::String('b')); + } + + SUBCASE("operator>=") + { + CHECK(sf::String() >= sf::String()); + CHECK(sf::String('b') >= sf::String('a')); + } + + SUBCASE("operator+") + { + CHECK(sf::String() + sf::String() == sf::String()); + CHECK(sf::String("abc") + sf::String("def") == sf::String("abcdef")); + } + } +} diff --git a/test/System/Time.test.cpp b/test/System/Time.test.cpp index ca9e99c1b..6bf66bde2 100644 --- a/test/System/Time.test.cpp +++ b/test/System/Time.test.cpp @@ -12,7 +12,8 @@ static_assert(std::is_nothrow_move_assignable_v); using namespace std::chrono_literals; -// Use StringMaker to avoid opening namespace std +// Specialize StringMaker for std::chrono::duration specializations +// https://github.com/doctest/doctest/blob/master/doc/markdown/stringification.md#docteststringmakert-specialisation namespace doctest { template