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