415 lines · cpp
1//===----------------------------------------------------------------------===//2//3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.4// See https://llvm.org/LICENSE.txt for license information.5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception6//7//===----------------------------------------------------------------------===//8 9// UNSUPPORTED: no-localization10// UNSUPPORTED: c++03, c++11, c++1411// ADDITIONAL_COMPILE_FLAGS: -D_LIBCPP_DISABLE_DEPRECATION_WARNINGS12 13// <filesystem>14 15// class path16 17// Test constructors, accessors and modifiers that convert from/to various18// character encodings. Constructors and modifiers (append, concat,19// operator/=, operator+=) accept inputs with various character encodings,20// and accessors (*string(), string<>(), u8string()) export the string with21// various encodings.22//23// Some encodings are standardized; char16_t, char32_t and the u8string24// accessor and u8path constructor (and normal functions taking char8_t in25// C++20) convert from/to UTF-16, UTF-32 and UTF-8. wchar_t can be either26// UTF-16 or UTF-32 depending on the size of the wchar_t type, or can be27// left unimplemented.28//29// Plain char is implicitly UTF-8 on posix systems. On Windows, plain char30// is supposed to be in the same encoding as the platform's native file31// system APIs consumes in the functions that take narrow strings as path32// names.33 34#include <filesystem>35#include <type_traits>36#include <cassert>37 38#include "test_macros.h"39 40#ifdef _WIN3241# include <windows.h> // SetFileApisToANSI & friends42#endif43namespace fs = std::filesystem;44 45// Test conversion with strings that fit within the latin1 charset, that fit46// within one code point in UTF-16, and that can be expressible in certain47// one-byte code pages.48static void test_latin_unicode()49{50 const char16_t u16str[] = { 0xe5, 0xe4, 0xf6, 0x00 };51 const char32_t u32str[] = { 0xe5, 0xe4, 0xf6, 0x00 };52 const char str[] = { char(0xc3), char(0xa5), char(0xc3), char(0xa4), char(0xc3), char(0xb6), 0x00 }; // UTF8, in a regular char string53#if TEST_STD_VER > 17 && defined(__cpp_lib_char8_t)54 const char8_t u8str[] = { 0xc3, 0xa5, 0xc3, 0xa4, 0xc3, 0xb6, 0x00 };55#else56 const char u8str[] = { char(0xc3), char(0xa5), char(0xc3), char(0xa4), char(0xc3), char(0xb6), 0x00 };57#endif58#ifndef TEST_HAS_NO_WIDE_CHARACTERS59 const wchar_t wstr[] = { 0xe5, 0xe4, 0xf6, 0x00 };60#endif61 62 // Test well-defined conversion between UTF-8, UTF-16 and UTF-3263 {64 const fs::path p(u16str);65 assert(p.u8string() == u8str);66 assert(p.u16string() == u16str);67 assert(p.u32string() == u32str);68 assert(p.string<char16_t>() == u16str);69 assert(p.string<char32_t>() == u32str);70 }71 {72 const fs::path p(u32str);73 assert(p.u8string() == u8str);74 assert(p.u16string() == u16str);75 assert(p.u32string() == u32str);76 assert(p.string<char16_t>() == u16str);77 assert(p.string<char32_t>() == u32str);78 }79 {80 const fs::path p = fs::u8path(str);81 assert(p.u8string() == u8str);82 assert(p.u16string() == u16str);83 assert(p.u32string() == u32str);84 assert(p.string<char16_t>() == u16str);85 assert(p.string<char32_t>() == u32str);86 }87#if TEST_STD_VER > 17 && defined(__cpp_lib_char8_t)88 {89 // In C++20, the path constructor can unambiguously handle UTF-8 input,90 // even if the plain char constructor would treat it as something else.91 const fs::path p(u8str);92 assert(p.u8string() == u8str);93 assert(p.u16string() == u16str);94 assert(p.u32string() == u32str);95 assert(p.string<char8_t>() == u8str);96 assert(p.string<char16_t>() == u16str);97 assert(p.string<char32_t>() == u32str);98 }99 // Check reading various inputs with string<char8_t>()100 {101 const fs::path p(u16str);102 assert(p.string<char8_t>() == u8str);103 }104 {105 const fs::path p(u32str);106 assert(p.string<char8_t>() == u8str);107 }108 {109 const fs::path p = fs::u8path(str);110 assert(p.string<char8_t>() == u8str);111 }112#endif113#ifndef TEST_HAS_NO_WIDE_CHARACTERS114 // Test conversion to/from wchar_t.115 {116 const fs::path p(u16str);117 assert(p.wstring() == wstr);118 assert(p.string<wchar_t>() == wstr);119 }120 {121 const fs::path p = fs::u8path(str);122 assert(p.wstring() == wstr);123 assert(p.string<wchar_t>() == wstr);124 }125 {126 const fs::path p(wstr);127 assert(p.wstring() == wstr);128 assert(p.u8string() == u8str);129 assert(p.u16string() == u16str);130 assert(p.u32string() == u32str);131 assert(p.string<wchar_t>() == wstr);132 }133#endif // TEST_HAS_NO_WIDE_CHARACTERS134#ifndef _WIN32135 // Test conversion to/from regular char-based string. On POSIX, this136 // is implied to convert to/from UTF-8.137 {138 const fs::path p(str);139 assert(p.string() == str);140 assert(p.u16string() == u16str);141 assert(p.string<char>() == str);142 }143 {144 const fs::path p(u16str);145 assert(p.string() == str);146 assert(p.string<char>() == str);147 }148#else149 // On windows, the narrow char-based input/output is supposed to be150 // in the charset that narrow file IO APIs use. This can either be the151 // current active code page (ACP) or the OEM code page, exposed by152 // the AreFileApisANSI() function, and settable with SetFileApisToANSI() and153 // SetFileApisToOEM(). We can't set which codepage is active within154 // the process, but for some specific known ones, we can check if they155 // behave as expected.156 SetFileApisToANSI();157 if (GetACP() == 1252) {158 const char latin1[] = { char(0xe5), char(0xe4), char(0xf6), 0x00 };159 {160 const fs::path p(wstr);161 assert(p.string() == latin1);162 assert(p.string<char>() == latin1);163 }164 {165 const fs::path p(latin1);166 assert(p.string() == latin1);167 assert(p.wstring() == wstr);168 assert(p.u8string() == u8str);169 assert(p.u16string() == u16str);170 assert(p.string<char>() == latin1);171 assert(p.string<wchar_t>() == wstr);172 }173 }174 SetFileApisToOEM();175 if (GetOEMCP() == 850 || GetOEMCP() == 437) {176 // These chars are identical in both CP 850 and 437177 const char cp850[] = { char(0x86), char(0x84), char(0x94), 0x00 };178 {179 const fs::path p(wstr);180 assert(p.string() == cp850);181 assert(p.string<char>() == cp850);182 }183 {184 const fs::path p(cp850);185 assert(p.string() == cp850);186 assert(p.wstring() == wstr);187 assert(p.u8string() == u8str);188 assert(p.u16string() == u16str);189 assert(p.string<char>() == cp850);190 assert(p.string<wchar_t>() == wstr);191 }192 }193#endif194}195 196// Test conversion with strings that don't fit within one UTF-16 code point.197// Here, wchar_t can be either UTF-16 or UTF-32 depending on the size on the198// particular platform.199static void test_wide_unicode()200{201 const char16_t u16str[] = { 0xd801, 0xdc37, 0x00 };202 const char32_t u32str[] = { 0x10437, 0x00 };203#if TEST_STD_VER > 17 && defined(__cpp_lib_char8_t)204 const char8_t u8str[] = { 0xf0, 0x90, 0x90, 0xb7, 0x00 };205#else206 const char u8str[] = { char(0xf0), char(0x90), char(0x90), char(0xb7), 0x00 };207#endif208 const char str[] = { char(0xf0), char(0x90), char(0x90), char(0xb7), 0x00 };209 {210 const fs::path p = fs::u8path(str);211 assert(p.u8string() == u8str);212 assert(p.u16string() == u16str);213 assert(p.u32string() == u32str);214 }215 {216 const fs::path p(u16str);217 assert(p.u8string() == u8str);218 assert(p.u16string() == u16str);219 assert(p.u32string() == u32str);220 }221 {222 const fs::path p(u32str);223 assert(p.u8string() == u8str);224 assert(p.u16string() == u16str);225 assert(p.u32string() == u32str);226 }227#if !defined(TEST_HAS_NO_WIDE_CHARACTERS) && defined(__SIZEOF_WCHAR_T__)228# if __SIZEOF_WCHAR_T__ == 2229 const wchar_t wstr[] = { 0xd801, 0xdc37, 0x00 };230# else231 const wchar_t wstr[] = { 0x10437, 0x00 };232# endif233 // Test conversion to/from wchar_t.234 {235 const fs::path p = fs::u8path(str);236 assert(p.wstring() == wstr);237 }238 {239 const fs::path p(u16str);240 assert(p.wstring() == wstr);241 }242 {243 const fs::path p(u32str);244 assert(p.wstring() == wstr);245 }246 {247 const fs::path p(wstr);248 assert(p.u8string() == u8str);249 assert(p.u16string() == u16str);250 assert(p.u32string() == u32str);251 assert(p.wstring() == wstr);252 }253#endif // !defined(TEST_HAS_NO_WIDE_CHARACTERS) && defined(__SIZEOF_WCHAR_T__)254}255 256// Test appending paths in different encodings.257static void test_append()258{259 const char16_t u16str[] = { 0xd801, 0xdc37, 0x00 };260 const char32_t u32str[] = { 0x10437, 0x00 };261 const char32_t u32ref[] = { 0x10437, fs::path::preferred_separator, 0x10437, fs::path::preferred_separator, 0x10437, 0x00 };262 const char str[] = { char(0xf0), char(0x90), char(0x90), char(0xb7), 0x00 };263 {264 fs::path p = fs::u8path(str) / u16str / u32str;265 assert(p.u32string() == u32ref);266 p = fs::u8path(str).append(u16str).append(u32str);267 assert(p.u32string() == u32ref);268 p = fs::u8path(str);269 p /= u16str;270 p /= u32str;271 assert(p.u32string() == u32ref);272 }273#if !defined(TEST_HAS_NO_WIDE_CHARACTERS) && defined(__SIZEOF_WCHAR_T__)274# if __SIZEOF_WCHAR_T__ == 2275 const wchar_t wstr[] = { 0xd801, 0xdc37, 0x00 };276# else277 const wchar_t wstr[] = { 0x10437, 0x00 };278# endif279 // Test conversion from wchar_t.280 {281 fs::path p = fs::path(u16str) / wstr / u32str;282 assert(p.u32string() == u32ref);283 p = fs::path(u16str).append(wstr).append(u32str);284 assert(p.u32string() == u32ref);285 p = fs::path(u16str);286 p /= wstr;287 p /= u32str;288 assert(p.u32string() == u32ref);289 }290#endif // !defined(TEST_HAS_NO_WIDE_CHARACTERS) && defined(__SIZEOF_WCHAR_T__)291}292 293static void test_concat()294{295 const char16_t u16str[] = { 0xd801, 0xdc37, 0x00 };296 const char32_t u32str[] = { 0x10437, 0x00 };297 const char32_t u32ref[] = { 0x10437, 0x10437, 0x10437, 0x00 };298 const char str[] = { char(0xf0), char(0x90), char(0x90), char(0xb7), 0x00 };299 {300 fs::path p = fs::u8path(str);301 p += u16str;302 p += u32str;303 assert(p.u32string() == u32ref);304 p = fs::u8path(str).concat(u16str).concat(u32str);305 assert(p.u32string() == u32ref);306 }307#if !defined(TEST_HAS_NO_WIDE_CHARACTERS) && defined(__SIZEOF_WCHAR_T__)308# if __SIZEOF_WCHAR_T__ == 2309 const wchar_t wstr[] = { 0xd801, 0xdc37, 0x00 };310# else311 const wchar_t wstr[] = { 0x10437, 0x00 };312# endif313 // Test conversion from wchar_t.314 {315 fs::path p = fs::path(u16str);316 p += wstr;317 p += u32str;318 assert(p.u32string() == u32ref);319 p = fs::path(u16str).concat(wstr).concat(u32str);320 assert(p.u32string() == u32ref);321 }322#endif // !defined(TEST_HAS_NO_WIDE_CHARACTERS) && defined(__SIZEOF_WCHAR_T__)323}324 325static void test_append_concat_narrow()326{327 const char16_t u16str[] = { 0xe5, 0x00 };328 const char32_t u32ref_append[] = { 0xe5, fs::path::preferred_separator, 0xe5, 0x00 };329 const char32_t u32ref_concat[] = { 0xe5, 0xe5, 0x00 };330 331#if TEST_STD_VER > 17 && defined(__cpp_lib_char8_t)332 {333 const char8_t u8str[] = { 0xc3, 0xa5, 0x00 };334 // In C++20, appends of a char8_t string is unambiguously treated as335 // UTF-8.336 fs::path p = fs::path(u16str) / u8str;337 assert(p.u32string() == u32ref_append);338 p = fs::path(u16str).append(u8str);339 assert(p.u32string() == u32ref_append);340 p = fs::path(u16str);341 p /= u8str;342 assert(p.u32string() == u32ref_append);343 p = fs::path(u16str).concat(u8str);344 assert(p.u32string() == u32ref_concat);345 p = fs::path(u16str);346 p += u8str;347 assert(p.u32string() == u32ref_concat);348 }349#endif350#ifndef _WIN32351 // Test appending a regular char-based string. On POSIX, this352 // is implied to convert to/from UTF-8.353 {354 const char str[] = { char(0xc3), char(0xa5), 0x00 }; // UTF8, in a regular char string355 fs::path p = fs::path(u16str) / str;356 assert(p.u32string() == u32ref_append);357 p = fs::path(u16str).append(str);358 assert(p.u32string() == u32ref_append);359 p = fs::path(u16str);360 p /= str;361 assert(p.u32string() == u32ref_append);362 p = fs::path(u16str).concat(str);363 assert(p.u32string() == u32ref_concat);364 p = fs::path(u16str);365 p += str;366 assert(p.u32string() == u32ref_concat);367 }368#else369 SetFileApisToANSI();370 if (GetACP() == 1252) {371 const char latin1[] = { char(0xe5), 0x00 };372 fs::path p = fs::path(u16str) / latin1;373 assert(p.u32string() == u32ref_append);374 p = fs::path(u16str).append(latin1);375 assert(p.u32string() == u32ref_append);376 p = fs::path(u16str);377 p /= latin1;378 assert(p.u32string() == u32ref_append);379 p = fs::path(u16str).concat(latin1);380 assert(p.u32string() == u32ref_concat);381 p = fs::path(u16str);382 p += latin1;383 assert(p.u32string() == u32ref_concat);384 }385 SetFileApisToOEM();386 if (GetOEMCP() == 850 || GetOEMCP() == 437) {387 // This chars is identical in both CP 850 and 437388 const char cp850[] = { char(0x86), 0x00 };389 fs::path p = fs::path(u16str) / cp850;390 assert(p.u32string() == u32ref_append);391 p = fs::path(u16str).append(cp850);392 assert(p.u32string() == u32ref_append);393 p = fs::path(u16str);394 p /= cp850;395 assert(p.u32string() == u32ref_append);396 p = fs::path(u16str).concat(cp850);397 assert(p.u32string() == u32ref_concat);398 p = fs::path(u16str);399 p += cp850;400 assert(p.u32string() == u32ref_concat);401 }402#endif403}404 405int main(int, char**)406{407 test_latin_unicode();408 test_wide_unicode();409 test_append();410 test_concat();411 test_append_concat_narrow();412 413 return 0;414}415