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