SFML/test/System/String.test.cpp
Chris Thrasher 9022d9564d Define character traits for std::uint8_t
Character traits are only standardized for character types of which
std::uint8_t is not. All major C++ implementations happen to define
this specialization but because it is not standard C++ they are
allowed to remove it as LLVM has done by deprecating this specialization
in LLVM 18. It is slated for removal in LLVM 19. To avoid compilation
errors and to get ahead of any deprecation warnings when LLVM 18 ships
we need to define our own std::uint8_t character traits.

SFML 4 will have access to C++20's std::u8string which should let us
remove this code.
2024-01-28 10:24:02 -07:00

562 lines
20 KiB
C++

#include <SFML/System/String.hpp>
#include <catch2/catch_test_macros.hpp>
#include <GraphicsUtil.hpp>
#include <array>
#include <iomanip>
#include <sstream>
#include <type_traits>
#include <cassert>
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 <typename T>
auto select(const std::basic_string<T>& string16, const std::basic_string<T>& string32)
{
assert(string16 != string32 && "Invalid to select between identical inputs");
if constexpr (sizeof(wchar_t) == 2)
return string16;
else
return string32;
}
template <typename CharT>
auto toHex(const CharT character)
{
std::ostringstream stream;
stream << "[\\x" << std::uppercase << std::hex << static_cast<std::uint32_t>(character) << ']';
return stream.str();
}
} // namespace
// Specialize StringMaker for alternative std::basic_string<T> specializations
// std::string's string conversion cannot be specialized but all other string types get special treatment
// https://github.com/catchorg/Catch2/blob/devel/docs/tostring.md#catchstringmaker-specialisation
namespace Catch
{
template <>
struct StringMaker<sf::U8String>
{
static std::string convert(const sf::U8String& string)
{
std::ostringstream output;
for (const auto character : string)
{
if (character >= 32 && character < 127)
output << std::string(1, static_cast<char>(character));
else
output << toHex(character);
}
return output.str();
}
};
} // namespace Catch
TEST_CASE("[System] sf::U8StringCharTraits")
{
SECTION("Type traits")
{
STATIC_CHECK(std::is_copy_constructible_v<sf::U8StringCharTraits>);
STATIC_CHECK(std::is_copy_assignable_v<sf::U8StringCharTraits>);
STATIC_CHECK(std::is_nothrow_move_constructible_v<sf::U8StringCharTraits>);
STATIC_CHECK(std::is_nothrow_move_assignable_v<sf::U8StringCharTraits>);
}
SECTION("assign(char_type&, const char_type&)")
{
sf::U8StringCharTraits::char_type c1 = 'a';
const sf::U8StringCharTraits::char_type c2 = 'b';
sf::U8StringCharTraits::assign(c1, c2);
CHECK(c1 == 'b');
CHECK(c2 == 'b');
}
SECTION("assign(char_type*, std::size_t, char_type)")
{
sf::U8StringCharTraits::char_type s[] = {'a', 'b', 'c', '\0'};
CHECK(sf::U8StringCharTraits::assign(s, 2, 'd') == &s[0]);
CHECK(s[0] == 'd');
CHECK(s[1] == 'd');
CHECK(s[2] == 'c');
}
SECTION("eq()")
{
CHECK(sf::U8StringCharTraits::eq(0, 0));
CHECK(!sf::U8StringCharTraits::eq(1, 0));
CHECK(!sf::U8StringCharTraits::eq(0, 1));
CHECK(sf::U8StringCharTraits::eq(1, 1));
}
SECTION("lt()")
{
CHECK(!sf::U8StringCharTraits::lt(0, 0));
CHECK(!sf::U8StringCharTraits::lt(1, 0));
CHECK(sf::U8StringCharTraits::lt(0, 1));
CHECK(!sf::U8StringCharTraits::lt(1, 1));
}
SECTION("move()")
{
sf::U8StringCharTraits::char_type s1[] = {'a', 'b', 'c', '\0'};
const sf::U8StringCharTraits::char_type s2[] = {'d', 'e', 'f', '\0'};
CHECK(sf::U8StringCharTraits::move(s1, s2, std::size(s2)) == s1);
CHECK(s1[0] == 'd');
CHECK(s1[1] == 'e');
CHECK(s1[2] == 'f');
CHECK(s2[0] == 'd');
CHECK(s2[1] == 'e');
CHECK(s2[2] == 'f');
}
SECTION("copy()")
{
sf::U8StringCharTraits::char_type s1[] = {'a', 'b', 'c', '\0'};
const sf::U8StringCharTraits::char_type s2[] = {'d', 'e', 'f', '\0'};
CHECK(sf::U8StringCharTraits::copy(s1, s2, std::size(s2)) == s1);
CHECK(s1[0] == 'd');
CHECK(s1[1] == 'e');
CHECK(s1[2] == 'f');
CHECK(s2[0] == 'd');
CHECK(s2[1] == 'e');
CHECK(s2[2] == 'f');
}
SECTION("compare()")
{
const sf::U8StringCharTraits::char_type s1[] = {'a', 'b', 'c', '\0'};
const sf::U8StringCharTraits::char_type s2[] = {'a', 'b', 'c', '\0'};
const sf::U8StringCharTraits::char_type s3[] = {'d', 'e', 'f', '\0'};
CHECK(sf::U8StringCharTraits::compare(s1, s2, std::size(s1)) == 0);
CHECK(sf::U8StringCharTraits::compare(s1, s3, std::size(s1)) < 0);
CHECK(sf::U8StringCharTraits::compare(s3, s1, std::size(s3)) > 0);
}
SECTION("length()")
{
const sf::U8StringCharTraits::char_type s1[] = {'a', '\0'};
const sf::U8StringCharTraits::char_type s2[] = {'a', 'b', 'c', 'd', 'e', '\0'};
CHECK(sf::U8StringCharTraits::length(s1) == 1);
CHECK(sf::U8StringCharTraits::length(s2) == 5);
}
SECTION("find()")
{
const sf::U8StringCharTraits::char_type s[] = {'a', 'b', 'c', 'd', 'e'};
CHECK(*sf::U8StringCharTraits::find(s, std::size(s), 'a') == 'a');
CHECK(sf::U8StringCharTraits::find(s, std::size(s), 'f') == nullptr);
}
SECTION("to_char_type()")
{
CHECK(sf::U8StringCharTraits::to_char_type(sf::U8StringCharTraits::int_type{0}) ==
sf::U8StringCharTraits::char_type{0});
CHECK(sf::U8StringCharTraits::to_char_type(sf::U8StringCharTraits::int_type{1}) ==
sf::U8StringCharTraits::char_type{1});
CHECK(sf::U8StringCharTraits::to_char_type(sf::U8StringCharTraits::int_type{10}) ==
sf::U8StringCharTraits::char_type{10});
}
SECTION("to_int_type()")
{
CHECK(sf::U8StringCharTraits::to_int_type(sf::U8StringCharTraits::char_type{0}) ==
sf::U8StringCharTraits::int_type{0});
CHECK(sf::U8StringCharTraits::to_int_type(sf::U8StringCharTraits::char_type{1}) ==
sf::U8StringCharTraits::int_type{1});
CHECK(sf::U8StringCharTraits::to_int_type(sf::U8StringCharTraits::char_type{10}) ==
sf::U8StringCharTraits::int_type{10});
}
SECTION("eq_int_type()")
{
CHECK(sf::U8StringCharTraits::eq_int_type(sf::U8StringCharTraits::int_type{0}, sf::U8StringCharTraits::int_type{0}));
CHECK(sf::U8StringCharTraits::eq_int_type(sf::U8StringCharTraits::int_type{1}, sf::U8StringCharTraits::int_type{1}));
CHECK(sf::U8StringCharTraits::eq_int_type(sf::U8StringCharTraits::int_type{10},
sf::U8StringCharTraits::int_type{10}));
}
SECTION("eof()")
{
CHECK(sf::U8StringCharTraits::eof() == ~sf::U8StringCharTraits::int_type{0});
}
SECTION("not_eof()")
{
CHECK(sf::U8StringCharTraits::not_eof(sf::U8StringCharTraits::int_type{0}) == sf::U8StringCharTraits::int_type{0});
CHECK(sf::U8StringCharTraits::not_eof(sf::U8StringCharTraits::int_type{1}) == sf::U8StringCharTraits::int_type{1});
CHECK(sf::U8StringCharTraits::not_eof(sf::U8StringCharTraits::int_type{10}) == sf::U8StringCharTraits::int_type{10});
CHECK(sf::U8StringCharTraits::not_eof(sf::U8StringCharTraits::eof()) != sf::U8StringCharTraits::eof());
}
}
TEST_CASE("[System] sf::String")
{
using namespace std::string_literals;
SECTION("Type traits")
{
STATIC_CHECK(std::is_copy_constructible_v<sf::String>);
STATIC_CHECK(std::is_copy_assignable_v<sf::String>);
STATIC_CHECK(std::is_nothrow_move_constructible_v<sf::String>);
STATIC_CHECK(std::is_nothrow_move_assignable_v<sf::String>);
}
SECTION("Construction")
{
SECTION("Default constructor")
{
const sf::String string;
CHECK(std::string(string).empty());
CHECK(std::wstring(string).empty());
CHECK(string.toAnsiString().empty());
CHECK(string.toWideString().empty());
CHECK(string.toUtf8().empty());
CHECK(string.toUtf16().empty());
CHECK(string.toUtf32().empty());
CHECK(string.getSize() == 0);
CHECK(string.isEmpty());
CHECK(string.getData() != nullptr);
}
SECTION("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() == sf::U8String{'a'});
CHECK(string.toUtf16() == u"a"s);
CHECK(string.toUtf32() == U"a"s);
CHECK(string.getSize() == 1);
CHECK(!string.isEmpty());
CHECK(string.getData() != nullptr);
}
SECTION("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() == sf::U8String{'d', 'e', 'f'});
CHECK(string.toUtf16() == u"def"s);
CHECK(string.toUtf32() == U"def"s);
CHECK(string.getSize() == 3);
CHECK(!string.isEmpty());
CHECK(string.getData() != nullptr);
}
SECTION("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() == sf::U8String{'g', 'h', 'i'});
CHECK(string.toUtf16() == u"ghi"s);
CHECK(string.toUtf32() == U"ghi"s);
CHECK(string.getSize() == 3);
CHECK(!string.isEmpty());
CHECK(string.getData() != nullptr);
}
SECTION("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() == sf::U8String{0xC3, 0xBA});
CHECK(string.toUtf16() == u"\xFA"s);
CHECK(string.toUtf32() == U"\xFA"s);
CHECK(string.getSize() == 1);
CHECK(!string.isEmpty());
CHECK(string.getData() != nullptr);
}
SECTION("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() == sf::U8String{'j', 0xC3, 0xBA, 'l'});
CHECK(string.toUtf16() == u"j\xFAl"s);
CHECK(string.toUtf32() == U"j\xFAl"s);
CHECK(string.getSize() == 3);
CHECK(!string.isEmpty());
CHECK(string.getData() != nullptr);
}
SECTION("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() == sf::U8String{'m', 'n', 'o', 0xC3, 0XBA});
CHECK(string.toUtf16() == u"mno\xFA"s);
CHECK(string.toUtf32() == U"mno\xFA"s);
CHECK(string.getSize() == 4);
CHECK(!string.isEmpty());
CHECK(string.getData() != nullptr);
}
SECTION("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() == sf::U8String{0xF4, 0x8A, 0xBE, 0xAF});
CHECK(string.toUtf16() == u"\U0010AFAF"s);
CHECK(string.toUtf32() == U"\U0010AFAF"s);
CHECK(string.getSize() == 1);
CHECK(!string.isEmpty());
CHECK(string.getData() != nullptr);
}
SECTION("UTF-32 C string constructor")
{
const sf::String string = 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() == sf::U8String{0xF4, 0x8A, 0xAF, 0x8D, 'r', 's'});
CHECK(string.toUtf16() == u"\U0010ABCDrs"s);
CHECK(string.toUtf32() == U"\U0010ABCDrs"s);
CHECK(string.getSize() == 3);
CHECK(!string.isEmpty());
CHECK(string.getData() != nullptr);
}
SECTION("UTF-32 string constructor")
{
const sf::String string = U"tuv\U00104321"s;
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() == sf::U8String{'t', 'u', 'v', 0xF4, 0x84, 0x8C, 0xA1});
CHECK(string.toUtf16() == u"tuv\U00104321"s);
CHECK(string.toUtf32() == U"tuv\U00104321"s);
CHECK(string.getSize() == 4);
CHECK(!string.isEmpty());
CHECK(string.getData() != nullptr);
}
}
SECTION("fromUtf8()")
{
SECTION("Nominal")
{
constexpr std::array<std::uint8_t, 4> 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() == sf::U8String{'w', 'x', 'y', 'z'});
CHECK(string.toUtf16() == u"wxyz"s);
CHECK(string.toUtf32() == U"wxyz"s);
CHECK(string.getSize() == 4);
CHECK(!string.isEmpty());
CHECK(string.getData() != nullptr);
}
SECTION("Insufficient input")
{
constexpr std::array<std::uint8_t, 1> characters{251};
const sf::String string = sf::String::fromUtf8(characters.begin(), characters.end());
constexpr char32_t defaultReplacementCharacter = 0;
CHECK(string.getSize() == 1);
CHECK(string[0] == defaultReplacementCharacter);
}
}
SECTION("fromUtf16()")
{
constexpr std::array<std::uint16_t, 4> 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() == sf::U8String{0xC3, 0xB1, 'x', 'y', 'z'});
CHECK(string.toUtf16() == u"\xF1xyz"s);
CHECK(string.toUtf32() == U"\xF1xyz"s);
CHECK(string.getSize() == 4);
CHECK(!string.isEmpty());
CHECK(string.getData() != nullptr);
}
SECTION("fromUtf32()")
{
constexpr std::array<std::uint32_t, 4> 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() == sf::U8String{'w', 0xF4, 0x84, 0x8C, 0xA1, 'y', 'z'});
CHECK(string.toUtf16() == u"w\U00104321yz"s);
CHECK(string.toUtf32() == U"w\U00104321yz"s);
CHECK(string.getSize() == 4);
CHECK(!string.isEmpty());
CHECK(string.getData() != nullptr);
}
SECTION("clear()")
{
sf::String string("you'll never guess what happens when you call clear()");
string.clear();
CHECK(string == sf::String());
CHECK(string.getSize() == 0);
}
SECTION("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);
}
SECTION("insert()")
{
sf::String string("please insert text");
string.insert(7, "don't ");
CHECK(string == "please don't insert text");
CHECK(string.getSize() == 24);
}
SECTION("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);
}
SECTION("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!");
}
SECTION("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);
}
SECTION("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);
}
SECTION("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");
}
SECTION("Operators")
{
SECTION("operator+=")
{
sf::String string;
string += sf::String("xyz");
CHECK(string.toAnsiString() == "xyz"s);
}
SECTION("operator[] const")
{
const sf::String string("the quick brown fox");
CHECK(string[0] == 't');
CHECK(string[10] == 'b');
}
SECTION("operator[]")
{
sf::String string("the quick brown fox");
CHECK(string[0] == 't');
string[1] = 'x';
CHECK(string[1] == 'x');
}
SECTION("operator==")
{
CHECK(sf::String() == sf::String());
CHECK_FALSE(sf::String() == sf::String(' '));
}
SECTION("operator!=")
{
CHECK(sf::String() != sf::String(' '));
CHECK_FALSE(sf::String() != sf::String());
}
SECTION("operator<")
{
CHECK(sf::String('a') < sf::String('b'));
CHECK_FALSE(sf::String() < sf::String());
}
SECTION("operator>")
{
CHECK(sf::String('b') > sf::String('a'));
CHECK_FALSE(sf::String() > sf::String());
}
SECTION("operator<=")
{
CHECK(sf::String() <= sf::String());
CHECK(sf::String('a') <= sf::String('b'));
}
SECTION("operator>=")
{
CHECK(sf::String() >= sf::String());
CHECK(sf::String('b') >= sf::String('a'));
}
SECTION("operator+")
{
CHECK(sf::String() + sf::String() == sf::String());
CHECK(sf::String("abc") + sf::String("def") == sf::String("abcdef"));
}
}
}