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