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