|
| 1 | +// MIT License |
| 2 | +// |
| 3 | +// Copyright (c) 2026 Ioannis Kaliakatsos |
| 4 | +// |
| 5 | +// Permission is hereby granted, free of charge, to any person obtaining a copy |
| 6 | +// of this software and associated documentation files (the "Software"), to deal |
| 7 | +// in the Software without restriction, including without limitation the rights |
| 8 | +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
| 9 | +// copies of the Software, and to permit persons to whom the Software is |
| 10 | +// furnished to do so, subject to the following conditions: |
| 11 | +// |
| 12 | +// The above copyright notice and this permission notice shall be included in all |
| 13 | +// copies or substantial portions of the Software. |
| 14 | +// |
| 15 | +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
| 16 | +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
| 17 | +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
| 18 | +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
| 19 | +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
| 20 | +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
| 21 | +// SOFTWARE. |
| 22 | + |
| 23 | +#include <gtest/gtest.h> |
| 24 | + |
| 25 | +#include <string> |
| 26 | + |
| 27 | +#include "internal/string_utilities.h" |
| 28 | + |
| 29 | +using namespace sqlite_reflection; |
| 30 | + |
| 31 | +// Every wide literal in this file is written with universal-character escapes rather than |
| 32 | +// literal non-ASCII source bytes. MSVC is not passed /utf-8, so it decodes non-ASCII source |
| 33 | +// bytes in the system codepage; a test written with literal characters would compare a |
| 34 | +// mangled literal against the same mangled literal and pass while proving nothing. Escapes |
| 35 | +// are charset-independent, and the expected UTF-8 is asserted as exact bytes rather than by |
| 36 | +// round-tripping, so a conversion that is wrong in both directions cannot pass either. |
| 37 | + |
| 38 | +namespace { |
| 39 | +// A code point is encoded as a surrogate pair in a 16-bit wchar_t (Windows) and as a single |
| 40 | +// code unit in a 32-bit wchar_t (Linux, macOS). The expected UTF-8 is identical on both. |
| 41 | +const char* const kEmojiUtf8 = "\xF0\x9F\x98\x80"; // U+1F600 GRINNING FACE |
| 42 | +const char* const kFirstSupplementaryUtf8 = "\xF0\x90\x80\x80"; // U+10000, first non-BMP |
| 43 | +const char* const kLastCodePointUtf8 = "\xF4\x8F\xBF\xBF"; // U+10FFFF, highest valid |
| 44 | +const char* const kLastBmpUtf8 = "\xEF\xBF\xBF"; // U+FFFF, last BMP code point |
| 45 | + |
| 46 | +std::wstring FromUtf8(const std::string& utf8) { |
| 47 | + return StringUtilities::FromUtf8(utf8.data(), utf8.size()); |
| 48 | +} |
| 49 | +} // namespace |
| 50 | + |
| 51 | +TEST(Utf8Test, EncodesAsciiToExactBytes) { |
| 52 | + EXPECT_EQ(std::string("Appleseed"), StringUtilities::ToUtf8(L"Appleseed")); |
| 53 | +} |
| 54 | + |
| 55 | +TEST(Utf8Test, EncodesEmptyString) { |
| 56 | + EXPECT_EQ(std::string(), StringUtilities::ToUtf8(std::wstring())); |
| 57 | +} |
| 58 | + |
| 59 | +TEST(Utf8Test, EncodesBmpToExactBytes) { |
| 60 | + // U+03C0 GREEK SMALL LETTER PI, U+03B1 GREEK SMALL LETTER ALPHA: two-byte sequences. |
| 61 | + EXPECT_EQ(std::string("\xCF\x80\xCE\xB1"), StringUtilities::ToUtf8(L"\u03C0\u03B1")); |
| 62 | +} |
| 63 | + |
| 64 | +TEST(Utf8Test, EncodesLastBmpCodePointToExactBytes) { |
| 65 | + EXPECT_EQ(std::string(kLastBmpUtf8), StringUtilities::ToUtf8(L"\uFFFF")); |
| 66 | +} |
| 67 | + |
| 68 | +TEST(Utf8Test, EncodesFirstSupplementaryCodePointToExactBytes) { |
| 69 | + // U+10000 is the low side of the surrogate boundary: the first code point that needs a |
| 70 | + // surrogate pair in UTF-16 and a four-byte UTF-8 sequence. |
| 71 | + EXPECT_EQ(std::string(kFirstSupplementaryUtf8), StringUtilities::ToUtf8(L"\U00010000")); |
| 72 | +} |
| 73 | + |
| 74 | +TEST(Utf8Test, EncodesEmojiToExactBytes) { |
| 75 | + EXPECT_EQ(std::string(kEmojiUtf8), StringUtilities::ToUtf8(L"\U0001F600")); |
| 76 | +} |
| 77 | + |
| 78 | +TEST(Utf8Test, EncodesLastValidCodePointToExactBytes) { |
| 79 | + EXPECT_EQ(std::string(kLastCodePointUtf8), StringUtilities::ToUtf8(L"\U0010FFFF")); |
| 80 | +} |
| 81 | + |
| 82 | +TEST(Utf8Test, EncodesSupplementaryCodePointMixedWithAscii) { |
| 83 | + EXPECT_EQ(std::string("a") + kEmojiUtf8 + "b", StringUtilities::ToUtf8(L"a\U0001F600b")); |
| 84 | +} |
| 85 | + |
| 86 | +TEST(Utf8Test, DecodesAscii) { |
| 87 | + EXPECT_EQ(std::wstring(L"Appleseed"), FromUtf8("Appleseed")); |
| 88 | +} |
| 89 | + |
| 90 | +TEST(Utf8Test, DecodesEmptyString) { |
| 91 | + EXPECT_EQ(std::wstring(), FromUtf8(std::string())); |
| 92 | +} |
| 93 | + |
| 94 | +TEST(Utf8Test, DecodesBmp) { |
| 95 | + EXPECT_EQ(std::wstring(L"\u03C0\u03B1"), FromUtf8("\xCF\x80\xCE\xB1")); |
| 96 | +} |
| 97 | + |
| 98 | +TEST(Utf8Test, DecodesLastBmpCodePoint) { |
| 99 | + EXPECT_EQ(std::wstring(L"\uFFFF"), FromUtf8(kLastBmpUtf8)); |
| 100 | +} |
| 101 | + |
| 102 | +TEST(Utf8Test, DecodesFirstSupplementaryCodePoint) { |
| 103 | + EXPECT_EQ(std::wstring(L"\U00010000"), FromUtf8(kFirstSupplementaryUtf8)); |
| 104 | +} |
| 105 | + |
| 106 | +TEST(Utf8Test, DecodesEmoji) { |
| 107 | + EXPECT_EQ(std::wstring(L"\U0001F600"), FromUtf8(kEmojiUtf8)); |
| 108 | +} |
| 109 | + |
| 110 | +TEST(Utf8Test, DecodesLastValidCodePoint) { |
| 111 | + EXPECT_EQ(std::wstring(L"\U0010FFFF"), FromUtf8(kLastCodePointUtf8)); |
| 112 | +} |
| 113 | + |
| 114 | +TEST(Utf8Test, RoundTripsSupplementaryCodePoints) { |
| 115 | + const std::wstring original = L"\U0001F600\U00010000\U0010FFFF"; |
| 116 | + EXPECT_EQ(original, FromUtf8(StringUtilities::ToUtf8(original))); |
| 117 | +} |
| 118 | + |
| 119 | +TEST(Utf8Test, SupplementaryCodePointSurvivesLengthAndContent) { |
| 120 | + // Guards against a conversion that silently drops or truncates the pair rather than |
| 121 | + // producing a wrong-but-present result. |
| 122 | + const std::wstring decoded = FromUtf8(kEmojiUtf8); |
| 123 | + EXPECT_FALSE(decoded.empty()); |
| 124 | + EXPECT_EQ(std::wstring(L"\U0001F600"), decoded); |
| 125 | +} |
0 commit comments