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