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