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-exception 6 // 7 //===----------------------------------------------------------------------===// 8 9 // UNSUPPORTED: c++03 10 11 // These tests require locale for non-char paths 12 // UNSUPPORTED: no-localization 13 14 // <filesystem> 15 16 // class path 17 18 // path& operator/=(path const&) 19 // template <class Source> 20 // path& operator/=(Source const&); 21 // template <class Source> 22 // path& append(Source const&); 23 // template <class InputIterator> 24 // path& append(InputIterator first, InputIterator last); 25 26 27 #include "filesystem_include.h" 28 #include <type_traits> 29 #include <string_view> 30 #include <cassert> 31 32 // On Windows, the append function converts all inputs (pointers, iterators) 33 // to an intermediate path object, causing allocations in cases where no 34 // allocations are done on other platforms. 35 36 #include "test_macros.h" 37 #include "test_iterators.h" 38 #include "count_new.h" 39 #include "filesystem_test_helper.h" 40 41 42 struct AppendOperatorTestcase { 43 MultiStringType lhs; 44 MultiStringType rhs; 45 MultiStringType expect_posix; 46 MultiStringType expect_windows; 47 48 MultiStringType const& expected_result() const { 49 #ifdef _WIN32 50 return expect_windows; 51 #else 52 return expect_posix; 53 #endif 54 } 55 }; 56 57 #define S(Str) MKSTR(Str) 58 const AppendOperatorTestcase Cases[] = 59 { 60 {S(""), S(""), S(""), S("")} 61 , {S("p1"), S("p2"), S("p1/p2"), S("p1\\p2")} 62 , {S("p1/"), S("p2"), S("p1/p2"), S("p1/p2")} 63 , {S("p1"), S("/p2"), S("/p2"), S("/p2")} 64 , {S("p1/"), S("/p2"), S("/p2"), S("/p2")} 65 , {S("p1"), S("\\p2"), S("p1/\\p2"), S("\\p2")} 66 , {S("p1\\"), S("p2"), S("p1\\/p2"), S("p1\\p2")} 67 , {S("p1\\"), S("\\p2"), S("p1\\/\\p2"), S("\\p2")} 68 , {S(""), S("p2"), S("p2"), S("p2")} 69 , {S("/p1"), S("p2"), S("/p1/p2"), S("/p1\\p2")} 70 , {S("/p1"), S("/p2"), S("/p2"), S("/p2")} 71 , {S("/p1/p3"), S("p2"), S("/p1/p3/p2"), S("/p1/p3\\p2")} 72 , {S("/p1/p3/"), S("p2"), S("/p1/p3/p2"), S("/p1/p3/p2")} 73 , {S("/p1/"), S("p2"), S("/p1/p2"), S("/p1/p2")} 74 , {S("/p1/p3/"), S("/p2/p4"), S("/p2/p4"), S("/p2/p4")} 75 , {S("/"), S(""), S("/"), S("/")} 76 , {S("/p1"), S("/p2/"), S("/p2/"), S("/p2/")} 77 , {S("p1"), S(""), S("p1/"), S("p1\\")} 78 , {S("p1/"), S(""), S("p1/"), S("p1/")} 79 80 , {S("//host"), S("foo"), S("//host/foo"), S("//host\\foo")} 81 , {S("//host/"), S("foo"), S("//host/foo"), S("//host/foo")} 82 , {S("//host"), S(""), S("//host/"), S("//host\\")} 83 84 , {S("foo"), S("C:/bar"), S("foo/C:/bar"), S("C:/bar")} 85 , {S("foo"), S("C:"), S("foo/C:"), S("C:")} 86 87 , {S("C:"), S(""), S("C:/"), S("C:")} 88 , {S("C:foo"), S("/bar"), S("/bar"), S("C:/bar")} 89 , {S("C:foo"), S("bar"), S("C:foo/bar"), S("C:foo\\bar")} 90 , {S("C:/foo"), S("bar"), S("C:/foo/bar"), S("C:/foo\\bar")} 91 , {S("C:/foo"), S("/bar"), S("/bar"), S("C:/bar")} 92 93 , {S("C:foo"), S("C:/bar"), S("C:foo/C:/bar"), S("C:/bar")} 94 , {S("C:foo"), S("C:bar"), S("C:foo/C:bar"), S("C:foo\\bar")} 95 , {S("C:/foo"), S("C:/bar"), S("C:/foo/C:/bar"), S("C:/bar")} 96 , {S("C:/foo"), S("C:bar"), S("C:/foo/C:bar"), S("C:/foo\\bar")} 97 98 , {S("C:foo"), S("c:/bar"), S("C:foo/c:/bar"), S("c:/bar")} 99 , {S("C:foo"), S("c:bar"), S("C:foo/c:bar"), S("c:bar")} 100 , {S("C:/foo"), S("c:/bar"), S("C:/foo/c:/bar"), S("c:/bar")} 101 , {S("C:/foo"), S("c:bar"), S("C:/foo/c:bar"), S("c:bar")} 102 103 , {S("C:/foo"), S("D:bar"), S("C:/foo/D:bar"), S("D:bar")} 104 }; 105 106 107 const AppendOperatorTestcase LongLHSCases[] = 108 { 109 {S("p1"), S("p2"), S("p1/p2"), S("p1\\p2")} 110 , {S("p1/"), S("p2"), S("p1/p2"), S("p1/p2")} 111 , {S("p1"), S("/p2"), S("/p2"), S("/p2")} 112 , {S("/p1"), S("p2"), S("/p1/p2"), S("/p1\\p2")} 113 }; 114 #undef S 115 116 117 // The append operator may need to allocate a temporary buffer before a code_cvt 118 // conversion. Test if this allocation occurs by: 119 // 1. Create a path, `LHS`, and reserve enough space to append `RHS`. 120 // This prevents `LHS` from allocating during the actual appending. 121 // 2. Create a `Source` object `RHS`, which represents a "large" string. 122 // (The string must not trigger the SSO) 123 // 3. Append `RHS` to `LHS` and check for the expected allocation behavior. 124 template <class CharT> 125 void doAppendSourceAllocTest(AppendOperatorTestcase const& TC) 126 { 127 using namespace fs; 128 using Ptr = CharT const*; 129 using Str = std::basic_string<CharT>; 130 using StrView = std::basic_string_view<CharT>; 131 using InputIter = cpp17_input_iterator<Ptr>; 132 133 const Ptr L = TC.lhs; 134 Str RShort = (Ptr)TC.rhs; 135 Str EShort = (Ptr)TC.expected_result(); 136 assert(RShort.size() >= 2); 137 CharT c = RShort.back(); 138 RShort.append(100, c); 139 EShort.append(100, c); 140 const Ptr R = RShort.data(); 141 const Str& E = EShort; 142 std::size_t ReserveSize = E.size() + 3; 143 // basic_string 144 { 145 path LHS(L); PathReserve(LHS, ReserveSize); 146 Str RHS(R); 147 { 148 TEST_NOT_WIN32(DisableAllocationGuard g); 149 LHS /= RHS; 150 } 151 assert(PathEq(LHS, E)); 152 } 153 // basic_string_view 154 { 155 path LHS(L); PathReserve(LHS, ReserveSize); 156 StrView RHS(R); 157 { 158 TEST_NOT_WIN32(DisableAllocationGuard g); 159 LHS /= RHS; 160 } 161 assert(PathEq(LHS, E)); 162 } 163 // CharT* 164 { 165 path LHS(L); PathReserve(LHS, ReserveSize); 166 Ptr RHS(R); 167 { 168 TEST_NOT_WIN32(DisableAllocationGuard g); 169 LHS /= RHS; 170 } 171 assert(PathEq(LHS, E)); 172 } 173 { 174 path LHS(L); PathReserve(LHS, ReserveSize); 175 Ptr RHS(R); 176 { 177 TEST_NOT_WIN32(DisableAllocationGuard g); 178 LHS.append(RHS, StrEnd(RHS)); 179 } 180 assert(PathEq(LHS, E)); 181 } 182 { 183 path LHS(L); PathReserve(LHS, ReserveSize); 184 path RHS(R); 185 { 186 DisableAllocationGuard g; 187 LHS /= RHS; 188 } 189 assert(PathEq(LHS, E)); 190 } 191 // input iterator - For non-native char types, appends needs to copy the 192 // iterator range into a contiguous block of memory before it can perform the 193 // code_cvt conversions. 194 // For "char" no allocations will be performed because no conversion is 195 // required. 196 // On Windows, the append method is more complex and uses intermediate 197 // path objects, which causes extra allocations. This is checked by comparing 198 // path::value_type with "char" - on Windows, it's wchar_t. 199 #if TEST_SUPPORTS_LIBRARY_INTERNAL_ALLOCATIONS 200 // Only check allocations if we can pick up allocations done within the 201 // library implementation. 202 bool ExpectNoAllocations = std::is_same<CharT, char>::value && 203 std::is_same<path::value_type, char>::value; 204 #endif 205 { 206 path LHS(L); PathReserve(LHS, ReserveSize); 207 InputIter RHS(R); 208 { 209 RequireAllocationGuard g(0); // require "at least zero" allocations by default 210 #if TEST_SUPPORTS_LIBRARY_INTERNAL_ALLOCATIONS 211 if (ExpectNoAllocations) 212 g.requireExactly(0); 213 #endif 214 LHS /= RHS; 215 } 216 assert(PathEq(LHS, E)); 217 } 218 { 219 path LHS(L); PathReserve(LHS, ReserveSize); 220 InputIter RHS(R); 221 InputIter REnd(StrEnd(R)); 222 { 223 RequireAllocationGuard g(0); // require "at least zero" allocations by default 224 #if TEST_SUPPORTS_LIBRARY_INTERNAL_ALLOCATIONS 225 if (ExpectNoAllocations) 226 g.requireExactly(0); 227 #endif 228 LHS.append(RHS, REnd); 229 } 230 assert(PathEq(LHS, E)); 231 } 232 } 233 234 template <class CharT> 235 void doAppendSourceTest(AppendOperatorTestcase const& TC) 236 { 237 using namespace fs; 238 using Ptr = CharT const*; 239 using Str = std::basic_string<CharT>; 240 using StrView = std::basic_string_view<CharT>; 241 using InputIter = cpp17_input_iterator<Ptr>; 242 const Ptr L = TC.lhs; 243 const Ptr R = TC.rhs; 244 const Ptr E = TC.expected_result(); 245 // basic_string 246 { 247 path Result(L); 248 Str RHS(R); 249 path& Ref = (Result /= RHS); 250 assert(Result == E); 251 assert(&Ref == &Result); 252 } 253 { 254 path LHS(L); 255 Str RHS(R); 256 path& Ref = LHS.append(RHS); 257 assert(PathEq(LHS, E)); 258 assert(&Ref == &LHS); 259 } 260 // basic_string_view 261 { 262 path LHS(L); 263 StrView RHS(R); 264 path& Ref = (LHS /= RHS); 265 assert(PathEq(LHS, E)); 266 assert(&Ref == &LHS); 267 } 268 { 269 path LHS(L); 270 StrView RHS(R); 271 path& Ref = LHS.append(RHS); 272 assert(PathEq(LHS, E)); 273 assert(&Ref == &LHS); 274 } 275 // Char* 276 { 277 path LHS(L); 278 Str RHS(R); 279 path& Ref = (LHS /= RHS); 280 assert(PathEq(LHS, E)); 281 assert(&Ref == &LHS); 282 } 283 { 284 path LHS(L); 285 Ptr RHS(R); 286 path& Ref = LHS.append(RHS); 287 assert(PathEq(LHS, E)); 288 assert(&Ref == &LHS); 289 } 290 { 291 path LHS(L); 292 Ptr RHS(R); 293 path& Ref = LHS.append(RHS, StrEnd(RHS)); 294 assert(PathEq(LHS, E)); 295 assert(&Ref == &LHS); 296 } 297 // iterators 298 { 299 path LHS(L); 300 InputIter RHS(R); 301 path& Ref = (LHS /= RHS); 302 assert(PathEq(LHS, E)); 303 assert(&Ref == &LHS); 304 } 305 { 306 path LHS(L); InputIter RHS(R); 307 path& Ref = LHS.append(RHS); 308 assert(PathEq(LHS, E)); 309 assert(&Ref == &LHS); 310 } 311 { 312 path LHS(L); 313 InputIter RHS(R); 314 InputIter REnd(StrEnd(R)); 315 path& Ref = LHS.append(RHS, REnd); 316 assert(PathEq(LHS, E)); 317 assert(&Ref == &LHS); 318 } 319 } 320 321 322 323 template <class It, class = decltype(fs::path{}.append(std::declval<It>()))> 324 constexpr bool has_append(int) { return true; } 325 template <class It> 326 constexpr bool has_append(long) { return false; } 327 328 template <class It, class = decltype(fs::path{}.operator/=(std::declval<It>()))> 329 constexpr bool has_append_op(int) { return true; } 330 template <class It> 331 constexpr bool has_append_op(long) { return false; } 332 333 template <class It> 334 constexpr bool has_append() { 335 static_assert(has_append<It>(0) == has_append_op<It>(0), "must be same"); 336 return has_append<It>(0) && has_append_op<It>(0); 337 } 338 339 void test_sfinae() 340 { 341 using namespace fs; 342 { 343 using It = const char* const; 344 static_assert(has_append<It>(), ""); 345 } 346 { 347 using It = cpp17_input_iterator<const char*>; 348 static_assert(has_append<It>(), ""); 349 } 350 { 351 struct Traits { 352 using iterator_category = std::input_iterator_tag; 353 using value_type = const char; 354 using pointer = const char*; 355 using reference = const char&; 356 using difference_type = std::ptrdiff_t; 357 }; 358 using It = cpp17_input_iterator<const char*, Traits>; 359 static_assert(has_append<It>(), ""); 360 } 361 { 362 using It = cpp17_output_iterator<const char*>; 363 static_assert(!has_append<It>(), ""); 364 365 } 366 { 367 static_assert(!has_append<int*>(), ""); 368 } 369 { 370 static_assert(!has_append<char>(), ""); 371 static_assert(!has_append<const char>(), ""); 372 } 373 } 374 375 int main(int, char**) 376 { 377 using namespace fs; 378 for (auto const & TC : Cases) { 379 { 380 const char* LHS_In = TC.lhs; 381 const char* RHS_In = TC.rhs; 382 path LHS(LHS_In); 383 path RHS(RHS_In); 384 path& Res = (LHS /= RHS); 385 assert(PathEq(Res, (const char*)TC.expected_result())); 386 assert(&Res == &LHS); 387 } 388 doAppendSourceTest<char> (TC); 389 #ifndef TEST_HAS_NO_WIDE_CHARACTERS 390 doAppendSourceTest<wchar_t> (TC); 391 #endif 392 doAppendSourceTest<char16_t>(TC); 393 doAppendSourceTest<char32_t>(TC); 394 } 395 for (auto const & TC : LongLHSCases) { 396 (void)TC; 397 LIBCPP_ONLY(doAppendSourceAllocTest<char>(TC)); 398 #ifndef TEST_HAS_NO_WIDE_CHARACTERS 399 LIBCPP_ONLY(doAppendSourceAllocTest<wchar_t>(TC)); 400 #endif 401 } 402 test_sfinae(); 403 404 return 0; 405 } 406