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