Strings.cpp (14564B)
1 // Copyright (c) Microsoft Corporation. 2 // Licensed under the MIT License. 3 #include "pch.h" 4 #include "TestCommon.h" 5 #include <AppInstallerStrings.h> 6 #include <AppInstallerSHA256.h> 7 #include <ExecutionReporter.h> 8 9 using namespace std::string_view_literals; 10 using namespace AppInstaller::Utility; 11 using namespace AppInstaller::Utility::literals; 12 13 TEST_CASE("UTF8Length", "[strings]") 14 { 15 REQUIRE(UTF8Length("") == 0); 16 REQUIRE(UTF8Length("a") == 1); 17 REQUIRE(UTF8Length(" a b c ") == 7); 18 REQUIRE(UTF8Length("K\xC3\xA4se") == 4); // "Käse" 19 REQUIRE(UTF8Length("bye\xE2\x80\xA6") == 4); // "bye…" 20 REQUIRE(UTF8Length("\xf0\x9f\xa6\x86") == 1); // [duck emoji] 21 REQUIRE(UTF8Length("\xf0\x9d\x85\xa0\xf0\x9d\x85\xa0") == 2); // [8th note][8th note] 22 } 23 24 TEST_CASE("UTF8Substring", "[strings]") 25 { 26 REQUIRE(UTF8Substring("", 0, 0) == ""); 27 REQUIRE(UTF8Substring("abcd", 0, 4) == "abcd"); 28 REQUIRE(UTF8Substring("abcd", 0, 5) == "abcd"); 29 REQUIRE(UTF8Substring("abcd", 0, 2) == "ab"); 30 REQUIRE(UTF8Substring("abcd", 1, 0) == ""); 31 REQUIRE(UTF8Substring("abcd", 1, 1) == "b"); 32 REQUIRE(UTF8Substring("abcd", 1, 3) == "bcd"); 33 REQUIRE(UTF8Substring("abcd", 4, 0) == ""); 34 35 const char* s = "\xf0\x9f\xa6\x86s like \xf0\x9f\x8c\x8a"; // [duck emoji]s like [wave emoji] 36 REQUIRE(UTF8Substring(s, 0, 9) == "\xf0\x9f\xa6\x86s like \xf0\x9f\x8c\x8a"); 37 REQUIRE(UTF8Substring(s, 0, 1) == "\xf0\x9f\xa6\x86"); 38 REQUIRE(UTF8Substring(s, 0, 2) == "\xf0\x9f\xa6\x86s"); 39 REQUIRE(UTF8Substring(s, 1, 7) == "s like "); 40 REQUIRE(UTF8Substring(s, 1, 8) == "s like \xf0\x9f\x8c\x8a"); 41 } 42 43 TEST_CASE("UTF8ColumnWidth", "[strings]") 44 { 45 REQUIRE(UTF8ColumnWidth("") == 0); 46 REQUIRE(UTF8ColumnWidth("a") == 1); 47 REQUIRE(UTF8ColumnWidth(" a b c ") == 7); 48 REQUIRE(UTF8ColumnWidth("K\xC3\xA4se") == 4); // "Käse" 49 REQUIRE(UTF8ColumnWidth("bye\xE2\x80\xA6") == 4); // "bye…" 50 REQUIRE(UTF8ColumnWidth("fi\xEF\xAC\x81") == 3); // "fi[fi]" [fi] is not decoupled 51 REQUIRE(UTF8ColumnWidth("\xf0\x9f\xa6\x86") == 2); // [duck emoji] 52 REQUIRE(UTF8ColumnWidth("\xf0\x9d\x85\xa0\xf0\x9d\x85\xa0") == 2); // [8th note][8th note] 53 REQUIRE(UTF8ColumnWidth("\xe6\xb5\x8b\xe8\xaf\x95") == 4); // 测试 54 REQUIRE(UTF8ColumnWidth("te\xe6\xb5\x8bs\xe8\xaf\x95t") == 8); // te测s试t 55 } 56 57 TEST_CASE("UTF8TrimRightToColumnWidth", "[strings]") 58 { 59 size_t actualWidth; 60 REQUIRE((UTF8TrimRightToColumnWidth("", 0, actualWidth) == "" && actualWidth == 0)); 61 REQUIRE((UTF8TrimRightToColumnWidth("abcd", 4, actualWidth) == "abcd" && actualWidth == 4)); 62 REQUIRE((UTF8TrimRightToColumnWidth("abcd", 5, actualWidth) == "abcd" && actualWidth == 4)); 63 REQUIRE((UTF8TrimRightToColumnWidth("abcd", 2, actualWidth) == "ab" && actualWidth == 2)); 64 65 NormalizedString s{ "te\xe6\xb5\x8bs\xe8\xaf\x95t" }; // // te测s试t 66 REQUIRE((UTF8TrimRightToColumnWidth(s, 0, actualWidth) == "" && actualWidth == 0)); 67 REQUIRE((UTF8TrimRightToColumnWidth(s, 2, actualWidth) == "te" && actualWidth == 2)); 68 REQUIRE((UTF8TrimRightToColumnWidth(s, 3, actualWidth) == "te" && actualWidth == 2)); 69 REQUIRE((UTF8TrimRightToColumnWidth(s, 4, actualWidth) == "te\xe6\xb5\x8b" && actualWidth == 4)); 70 REQUIRE((UTF8TrimRightToColumnWidth(s, 8, actualWidth) == "te\xe6\xb5\x8bs\xe8\xaf\x95t" && actualWidth == 8)); 71 REQUIRE((UTF8TrimRightToColumnWidth(s, 10, actualWidth) == "te\xe6\xb5\x8bs\xe8\xaf\x95t" && actualWidth == 8)); 72 } 73 74 TEST_CASE("Normalize", "[strings]") 75 { 76 REQUIRE(Normalize("test") == "test"); 77 78 // A + combining Dieresis => single A with umlaut char 79 REQUIRE(Normalize(L"\x41\x308") == L"\xC4"); 80 // This will stop working in C++20, sigh. 81 REQUIRE(Normalize(u8"\x41\x308") == u8"\xC4"); 82 83 // Ligature fi => f + i 84 REQUIRE(Normalize(u8"\xFB01") == u8"fi"); 85 } 86 87 TEST_CASE("NormalizedString", "[strings]") 88 { 89 REQUIRE(NormalizedString("test") == "test"); 90 std::string input = "test"; 91 REQUIRE(NormalizedString(input) == input); 92 93 // A + combining Dieresis => single A with umlaut char 94 REQUIRE(NormalizedString(std::wstring_view(L"\x41\x308")) == u8"\xC4"); 95 // This will stop working in C++20, sigh. 96 input = u8"\x41\x308"; 97 REQUIRE(NormalizedString(input) == u8"\xC4"); 98 99 // Ligature fi => f + i 100 std::string_view input2 = u8"\xFB01"; 101 REQUIRE(NormalizedString(input2) == u8"fi"); 102 103 // Embedded null 104 std::string_view input3{ "Test\0Case", 9 }; 105 REQUIRE(NormalizedString(input3) == "Test Case"); 106 } 107 108 TEST_CASE("Trim", "[strings]") 109 { 110 std::string str; 111 REQUIRE(Trim(str.assign("")) == ""); 112 REQUIRE(Trim(str.assign(" ")) == ""); 113 REQUIRE(Trim(str.assign(" \t ")) == ""); 114 REQUIRE(Trim(str.assign(" a")) == "a"); 115 REQUIRE(Trim(str.assign("bght ")) == "bght"); 116 REQUIRE(Trim(str.assign("\tStuff\f")) == "Stuff"); 117 REQUIRE(Trim(str.assign("Multiple words")) == "Multiple words"); 118 REQUIRE(Trim(str.assign(" Multiple words")) == "Multiple words"); 119 REQUIRE(Trim(str.assign("Much after is taken \f\n\r\t\v\v\t\r\n\f ")) == "Much after is taken"); 120 } 121 122 TEST_CASE("CaseInsensitiveStartsWith", "[strings]") 123 { 124 REQUIRE(CaseInsensitiveStartsWith("startswith", "starts")); 125 REQUIRE(CaseInsensitiveStartsWith("startswith", "STAR")); 126 REQUIRE(CaseInsensitiveStartsWith("startswith", "startSWITH")); 127 REQUIRE(CaseInsensitiveStartsWith("startswith", "")); 128 129 REQUIRE(!CaseInsensitiveStartsWith("starts", "startswith")); 130 REQUIRE(!CaseInsensitiveStartsWith("", "nuffing")); 131 REQUIRE(!CaseInsensitiveStartsWith("withstarts", "starts")); 132 REQUIRE(!CaseInsensitiveStartsWith(" starts", "starts")); 133 } 134 135 TEST_CASE("FoldCase", "[strings]") 136 { 137 REQUIRE(FoldCase(""sv) == FoldCase(""sv)); 138 REQUIRE(FoldCase("foldcase"sv) == FoldCase("FOLDCASE"sv)); 139 REQUIRE(FoldCase(u8"f\xF6ldcase"sv) == FoldCase(u8"F\xD6LDCASE"sv)); 140 REQUIRE(FoldCase(u8"foldc\x430se"sv) == FoldCase(u8"FOLDC\x410SE"sv)); 141 } 142 143 TEST_CASE("ExpandEnvironmentVariables", "[strings]") 144 { 145 wchar_t buffer[MAX_PATH]; 146 GetTempPathW(ARRAYSIZE(buffer), buffer); 147 148 std::wstring tempPath = buffer; 149 if (!tempPath.empty() && tempPath.back() == '\\') 150 { 151 tempPath.resize(tempPath.size() - 1); 152 } 153 154 REQUIRE(ExpandEnvironmentVariables(L"%TEMP%") == tempPath); 155 } 156 157 TEST_CASE("PathOutput", "[strings]") 158 { 159 std::string original = "\xe6\xb5\x8b\xe8\xaf\x95"; 160 std::filesystem::path path = ConvertToUTF16(original); 161 AICLI_LOG(Test, Info, << path); 162 163 std::istringstream in; 164 std::ostringstream out; 165 AppInstaller::CLI::Execution::Reporter reporter{ out, in }; 166 167 reporter.Info() << path; 168 169 std::string output = out.str(); 170 REQUIRE(output.substr(output.size() - original.size()) == original); 171 } 172 173 TEST_CASE("ReplaceWhileCopying", "[strings]") 174 { 175 REQUIRE(ReplaceWhileCopying(L"A red apple", L"red", L"green") == L"A green apple"); 176 REQUIRE(ReplaceWhileCopying(L"A red, red apple", L"red", L"green") == L"A green, green apple"); 177 REQUIRE(ReplaceWhileCopying(L"A red, red apple", L"ed", L"ad") == L"A rad, rad apple"); 178 REQUIRE(ReplaceWhileCopying(L"A red apple", L"p", L"f") == L"A red affle"); 179 REQUIRE(ReplaceWhileCopying(L"A red apple", L"", L"green") == L"A red apple"); 180 } 181 182 TEST_CASE("MakeSuitablePathPart", "[strings]") 183 { 184 REQUIRE(MakeSuitablePathPart("A\\B") == "A_B"); 185 REQUIRE(MakeSuitablePathPart("A\\B/") == "A_B_"); 186 REQUIRE(MakeSuitablePathPart("*AB") == "_AB"); 187 REQUIRE(MakeSuitablePathPart(u8"f*\xF6*ldcase") == u8"f_\xF6_ldcase"); 188 REQUIRE(MakeSuitablePathPart(".") == "_"); 189 REQUIRE(MakeSuitablePathPart("..") == "._"); 190 REQUIRE(MakeSuitablePathPart(std::string(300, ' ')) == SHA256::ConvertToString(SHA256::ComputeHash(std::string(300, ' ')))); 191 REQUIRE_THROWS_HR(MakeSuitablePathPart("COM1"), E_INVALIDARG); 192 REQUIRE_THROWS_HR(MakeSuitablePathPart("NUL.txt"), E_INVALIDARG); 193 } 194 195 TEST_CASE("GetFileNameFromURI", "[strings]") 196 { 197 REQUIRE(GetFileNameFromURI("https://github.com/microsoft/winget-cli/pull/1722").u8string() == "1722"); 198 REQUIRE(GetFileNameFromURI("https://github.com/microsoft/winget-cli/README.md").u8string() == "README.md"); 199 REQUIRE(GetFileNameFromURI("https://microsoft.com/").u8string() == ""); 200 } 201 202 void ValidateSplitFileName(std::string_view uri, std::string_view base, std::string_view fileName) 203 { 204 auto split = SplitFileNameFromURI(uri); 205 REQUIRE(split.first == base); 206 REQUIRE(split.second.u8string() == fileName); 207 } 208 209 TEST_CASE("SplitFileNameFromURI", "[strings]") 210 { 211 ValidateSplitFileName("https://github.com/microsoft/winget-cli/pull/1722", "https://github.com/microsoft/winget-cli/pull/", "1722"); 212 ValidateSplitFileName("https://github.com/microsoft/winget-cli/README.md", "https://github.com/microsoft/winget-cli/", "README.md"); 213 ValidateSplitFileName("https://microsoft.com/", "https://microsoft.com/", ""); 214 } 215 216 TEST_CASE("SplitIntoWords", "[strings]") 217 { 218 REQUIRE(SplitIntoWords("A B") == std::vector<std::string>{ "A", "B" }); 219 REQUIRE(SplitIntoWords("Some-Thing") == std::vector<std::string>{ "Some", "Thing" }); 220 221 // 私のテスト = "My test" according to an online translator 222 // Split as "私" "の" "テスト" 223 REQUIRE(SplitIntoWords("\xe7\xa7\x81\xe3\x81\xae\xe3\x83\x86\xe3\x82\xb9\xe3\x83\x88") == std::vector<std::string>{ "\xe7\xa7\x81", "\xe3\x81\xae", "\xe3\x83\x86\xe3\x82\xb9\xe3\x83\x88" }); 224 } 225 226 TEST_CASE("ReplaceEmbeddedNullCharacters", "[strings]") 227 { 228 std::string test = "Test Parts"; 229 test[4] = '\0'; 230 ReplaceEmbeddedNullCharacters(test); 231 REQUIRE(test == "Test Parts"); 232 } 233 234 TEST_CASE("HexStrings", "[strings]") 235 { 236 std::vector<uint8_t> buffer{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 }; 237 std::string value = "000102030405060708090a0b0c0d0e0f"; 238 239 REQUIRE(value == ConvertToHexString(buffer)); 240 REQUIRE(std::equal(buffer.begin(), buffer.end(), ParseFromHexString(value).begin())); 241 } 242 243 TEST_CASE("Join", "[strings]") 244 { 245 std::vector<LocIndString> list_0{ }; 246 std::vector<LocIndString> list_1{ "A"_lis }; 247 std::vector<LocIndString> list_2{ "A"_lis, "B"_lis }; 248 249 REQUIRE(""_lis == Join(", "_liv, list_0)); 250 REQUIRE("A"_lis == Join(", "_liv, list_1)); 251 REQUIRE("A, B"_lis == Join(", "_liv, list_2)); 252 REQUIRE("AB"_lis == Join(""_liv, list_2)); 253 } 254 255 TEST_CASE("Format", "[strings]") 256 { 257 REQUIRE("First Second" == Format("{0} {1}", "First", "Second")); 258 REQUIRE("First Second" == Format("{1} {0}", "Second", "First")); 259 REQUIRE("First Second" == Format("{0} {1}", "First", "Second", "(Extra", "Input", "Ignored)")); 260 REQUIRE("First Second First Second" == Format("{0} {1} {0} {1}", "First", "Second")); 261 262 // Note: C++20 std::format will throw an exception for this test case 263 REQUIRE("First {1}" == Format("{0} {1}", "First")); 264 } 265 266 TEST_CASE("SplitIntoLines", "[strings]") 267 { 268 REQUIRE(SplitIntoLines("Boring test") == std::vector<std::string>{ "Boring test" }); 269 REQUIRE(SplitIntoLines( 270 "I'm Luffy! The Man Who Will Become the Pirate King!\r-Monkey D. Luffy") == std::vector<std::string>{ "I'm Luffy! The Man Who Will Become the Pirate King!", "-Monkey D. Luffy" }); 271 REQUIRE(SplitIntoLines( 272 "I want live!\n-Nico Robin") == std::vector<std::string>{ "I want live!", "-Nico Robin" }); 273 REQUIRE(SplitIntoLines( 274 "You want my treasure?\rYou can have it!\nI left everything I gathered in one place!\r\nYou just have to find it!") 275 == std::vector<std::string>{ "You want my treasure?", "You can have it!", "I left everything I gathered in one place!", "You just have to find it!" }); 276 } 277 278 TEST_CASE("SplitWithSeparator", "[strings]") 279 { 280 std::vector<std::string> test1 = Split("first;second;third", ';'); 281 REQUIRE(test1.size() == 3); 282 REQUIRE(test1[0] == "first"); 283 REQUIRE(test1[1] == "second"); 284 REQUIRE(test1[2] == "third"); 285 286 std::vector<std::string> test2 = Split("two spaces", ' '); 287 REQUIRE(test2.size() == 3); 288 REQUIRE(test2[0] == "two"); 289 REQUIRE(test2[1] == ""); 290 REQUIRE(test2[2] == "spaces"); 291 292 std::vector<std::string> test3 = Split("test", '.'); 293 REQUIRE(test3.size() == 1); 294 REQUIRE(test3[0] == "test"); 295 296 std::vector<std::string> test4 = Split(" trim | spaces ", '|', true); 297 REQUIRE(test4.size() == 2); 298 REQUIRE(test4[0] == "trim"); 299 REQUIRE(test4[1] == "spaces"); 300 } 301 302 TEST_CASE("ConvertGuid", "[strings]") 303 { 304 std::string validGuidString = "{4d1e55b2-f16f-11cf-88cb-001111000030}"; 305 GUID guid = { 0x4d1e55b2, 0xf16f, 0x11cf, 0x88, 0xcb, 0x00, 0x11, 0x11, 0x00, 0x00, 0x30 }; 306 307 REQUIRE(CaseInsensitiveEquals(ConvertGuidToString(guid), validGuidString)); 308 } 309 310 TEST_CASE("FindControlCodeToConvert", "[strings]") 311 { 312 REQUIRE(FindControlCodeToConvert("No codes") == std::string::npos); 313 REQUIRE(FindControlCodeToConvert("Allowed codes: \t\r\n") == std::string::npos); 314 REQUIRE(FindControlCodeToConvert("\x1bSkipped code", 1) == std::string::npos); 315 316 REQUIRE(FindControlCodeToConvert("\x1bUnskipped code") == 0); 317 REQUIRE(FindControlCodeToConvert("Escape code: \x1b") == 13); 318 319 std::string_view allCodes{ "\x0\x1\x2\x3\x4\x5\x6\x7\x8\xb\xc\xe\xf\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x7f"sv }; 320 for (size_t i = 0; i < allCodes.length(); ++i) 321 { 322 REQUIRE(FindControlCodeToConvert(allCodes, i) == i); 323 } 324 } 325 326 TEST_CASE("ConvertControlCodesToPictures", "[strings]") 327 { 328 REQUIRE(ConvertControlCodesToPictures("No codes") == "No codes"); 329 REQUIRE(ConvertControlCodesToPictures("Allowed codes: \t\r\n") == "Allowed codes: \t\r\n"); 330 331 REQUIRE(ConvertControlCodesToPictures("\x1b Code First") == ConvertToUTF8(L"\x241b Code First")); 332 REQUIRE(ConvertControlCodesToPictures("Escape code: \x1b") == ConvertToUTF8(L"Escape code: \x241b")); 333 334 std::string_view allCodes{ "\x0\x1\x2\x3\x4\x5\x6\x7\x8\xb\xc\xe\xf\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x7f"sv }; 335 std::wstring_view allPictures{ L"\x2400\x2401\x2402\x2403\x2404\x2405\x2406\x2407\x2408\x240b\x240c\x240e\x240f\x2410\x2411\x2412\x2413\x2414\x2415\x2416\x2417\x2418\x2419\x241a\x241b\x241c\x241d\x241e\x241f\x2421"sv }; 336 337 REQUIRE(ConvertControlCodesToPictures(allCodes) == ConvertToUTF8(allPictures)); 338 }