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