//===----------------------------------------------------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// // UNSUPPORTED: c++03, c++11, c++14 // UNSUPPORTED: availability-filesystem-missing // These tests require locale for non-char paths // UNSUPPORTED: no-localization // // class path // path& operator/=(path const&) // template // path& operator/=(Source const&); // template // path& append(Source const&); // template // path& append(InputIterator first, InputIterator last); #include #include #include #include // On Windows, the append function converts all inputs (pointers, iterators) // to an intermediate path object, causing allocations in cases where no // allocations are done on other platforms. #include "../path_helper.h" #include "count_new.h" #include "make_string.h" #include "test_iterators.h" #include "test_macros.h" namespace fs = std::filesystem; struct AppendOperatorTestcase { MultiStringType lhs; MultiStringType rhs; MultiStringType expect_posix; MultiStringType expect_windows; MultiStringType const& expected_result() const { #ifdef _WIN32 return expect_windows; #else return expect_posix; #endif } }; #define S(Str) MKSTR(Str) const AppendOperatorTestcase Cases[] = { {S(""), S(""), S(""), S("")} , {S("p1"), S("p2"), S("p1/p2"), S("p1\\p2")} , {S("p1/"), S("p2"), S("p1/p2"), S("p1/p2")} , {S("p1"), S("/p2"), S("/p2"), S("/p2")} , {S("p1/"), S("/p2"), S("/p2"), S("/p2")} , {S("p1"), S("\\p2"), S("p1/\\p2"), S("\\p2")} , {S("p1\\"), S("p2"), S("p1\\/p2"), S("p1\\p2")} , {S("p1\\"), S("\\p2"), S("p1\\/\\p2"), S("\\p2")} , {S(""), S("p2"), S("p2"), S("p2")} , {S("/p1"), S("p2"), S("/p1/p2"), S("/p1\\p2")} , {S("/p1"), S("/p2"), S("/p2"), S("/p2")} , {S("/p1/p3"), S("p2"), S("/p1/p3/p2"), S("/p1/p3\\p2")} , {S("/p1/p3/"), S("p2"), S("/p1/p3/p2"), S("/p1/p3/p2")} , {S("/p1/"), S("p2"), S("/p1/p2"), S("/p1/p2")} , {S("/p1/p3/"), S("/p2/p4"), S("/p2/p4"), S("/p2/p4")} , {S("/"), S(""), S("/"), S("/")} , {S("/p1"), S("/p2/"), S("/p2/"), S("/p2/")} , {S("p1"), S(""), S("p1/"), S("p1\\")} , {S("p1/"), S(""), S("p1/"), S("p1/")} , {S("//host"), S("foo"), S("//host/foo"), S("//host\\foo")} , {S("//host/"), S("foo"), S("//host/foo"), S("//host/foo")} , {S("//host"), S(""), S("//host/"), S("//host\\")} , {S("foo"), S("C:/bar"), S("foo/C:/bar"), S("C:/bar")} , {S("foo"), S("C:"), S("foo/C:"), S("C:")} , {S("C:"), S(""), S("C:/"), S("C:")} , {S("C:foo"), S("/bar"), S("/bar"), S("C:/bar")} , {S("C:foo"), S("bar"), S("C:foo/bar"), S("C:foo\\bar")} , {S("C:/foo"), S("bar"), S("C:/foo/bar"), S("C:/foo\\bar")} , {S("C:/foo"), S("/bar"), S("/bar"), S("C:/bar")} , {S("C:foo"), S("C:/bar"), S("C:foo/C:/bar"), S("C:/bar")} , {S("C:foo"), S("C:bar"), S("C:foo/C:bar"), S("C:foo\\bar")} , {S("C:/foo"), S("C:/bar"), S("C:/foo/C:/bar"), S("C:/bar")} , {S("C:/foo"), S("C:bar"), S("C:/foo/C:bar"), S("C:/foo\\bar")} , {S("C:foo"), S("c:/bar"), S("C:foo/c:/bar"), S("c:/bar")} , {S("C:foo"), S("c:bar"), S("C:foo/c:bar"), S("c:bar")} , {S("C:/foo"), S("c:/bar"), S("C:/foo/c:/bar"), S("c:/bar")} , {S("C:/foo"), S("c:bar"), S("C:/foo/c:bar"), S("c:bar")} , {S("C:/foo"), S("D:bar"), S("C:/foo/D:bar"), S("D:bar")} }; const AppendOperatorTestcase LongLHSCases[] = { {S("p1"), S("p2"), S("p1/p2"), S("p1\\p2")} , {S("p1/"), S("p2"), S("p1/p2"), S("p1/p2")} , {S("p1"), S("/p2"), S("/p2"), S("/p2")} , {S("/p1"), S("p2"), S("/p1/p2"), S("/p1\\p2")} }; #undef S // The append operator may need to allocate a temporary buffer before a code_cvt // conversion. Test if this allocation occurs by: // 1. Create a path, `LHS`, and reserve enough space to append `RHS`. // This prevents `LHS` from allocating during the actual appending. // 2. Create a `Source` object `RHS`, which represents a "large" string. // (The string must not trigger the SSO) // 3. Append `RHS` to `LHS` and check for the expected allocation behavior. template void doAppendSourceAllocTest(AppendOperatorTestcase const& TC) { using namespace fs; using Ptr = CharT const*; using Str = std::basic_string; using StrView = std::basic_string_view; using InputIter = cpp17_input_iterator; const Ptr L = TC.lhs; Str RShort = (Ptr)TC.rhs; Str EShort = (Ptr)TC.expected_result(); assert(RShort.size() >= 2); CharT c = RShort.back(); RShort.append(100, c); EShort.append(100, c); const Ptr R = RShort.data(); const Str& E = EShort; std::size_t ReserveSize = E.size() + 3; // basic_string { path LHS(L); PathReserve(LHS, ReserveSize); Str RHS(R); { TEST_NOT_WIN32(DisableAllocationGuard g); LHS /= RHS; } assert(PathEq(LHS, E)); } // basic_string_view { path LHS(L); PathReserve(LHS, ReserveSize); StrView RHS(R); { TEST_NOT_WIN32(DisableAllocationGuard g); LHS /= RHS; } assert(PathEq(LHS, E)); } // CharT* { path LHS(L); PathReserve(LHS, ReserveSize); Ptr RHS(R); { TEST_NOT_WIN32(DisableAllocationGuard g); LHS /= RHS; } assert(PathEq(LHS, E)); } { path LHS(L); PathReserve(LHS, ReserveSize); Ptr RHS(R); { TEST_NOT_WIN32(DisableAllocationGuard g); LHS.append(RHS, StrEnd(RHS)); } assert(PathEq(LHS, E)); } { path LHS(L); PathReserve(LHS, ReserveSize); path RHS(R); { DisableAllocationGuard g; LHS /= RHS; } assert(PathEq(LHS, E)); } // input iterator - For non-native char types, appends needs to copy the // iterator range into a contiguous block of memory before it can perform the // code_cvt conversions. // For "char" no allocations will be performed because no conversion is // required. // On Windows, the append method is more complex and uses intermediate // path objects, which causes extra allocations. This is checked by comparing // path::value_type with "char" - on Windows, it's wchar_t. #if TEST_SUPPORTS_LIBRARY_INTERNAL_ALLOCATIONS // Only check allocations if we can pick up allocations done within the // library implementation. bool ExpectNoAllocations = std::is_same::value && std::is_same::value; #endif { path LHS(L); PathReserve(LHS, ReserveSize); InputIter RHS(R); { RequireAllocationGuard g(0); // require "at least zero" allocations by default #if TEST_SUPPORTS_LIBRARY_INTERNAL_ALLOCATIONS if (ExpectNoAllocations) g.requireExactly(0); #endif LHS /= RHS; } assert(PathEq(LHS, E)); } { path LHS(L); PathReserve(LHS, ReserveSize); InputIter RHS(R); InputIter REnd(StrEnd(R)); { RequireAllocationGuard g(0); // require "at least zero" allocations by default #if TEST_SUPPORTS_LIBRARY_INTERNAL_ALLOCATIONS if (ExpectNoAllocations) g.requireExactly(0); #endif LHS.append(RHS, REnd); } assert(PathEq(LHS, E)); } } template void doAppendSourceTest(AppendOperatorTestcase const& TC) { using namespace fs; using Ptr = CharT const*; using Str = std::basic_string; using StrView = std::basic_string_view; using InputIter = cpp17_input_iterator; const Ptr L = TC.lhs; const Ptr R = TC.rhs; const Ptr E = TC.expected_result(); // basic_string { path Result(L); Str RHS(R); path& Ref = (Result /= RHS); assert(Result == E); assert(&Ref == &Result); } { path LHS(L); Str RHS(R); path& Ref = LHS.append(RHS); assert(PathEq(LHS, E)); assert(&Ref == &LHS); } // basic_string_view { path LHS(L); StrView RHS(R); path& Ref = (LHS /= RHS); assert(PathEq(LHS, E)); assert(&Ref == &LHS); } { path LHS(L); StrView RHS(R); path& Ref = LHS.append(RHS); assert(PathEq(LHS, E)); assert(&Ref == &LHS); } // Char* { path LHS(L); Str RHS(R); path& Ref = (LHS /= RHS); assert(PathEq(LHS, E)); assert(&Ref == &LHS); } { path LHS(L); Ptr RHS(R); path& Ref = LHS.append(RHS); assert(PathEq(LHS, E)); assert(&Ref == &LHS); } { path LHS(L); Ptr RHS(R); path& Ref = LHS.append(RHS, StrEnd(RHS)); assert(PathEq(LHS, E)); assert(&Ref == &LHS); } // iterators { path LHS(L); InputIter RHS(R); path& Ref = (LHS /= RHS); assert(PathEq(LHS, E)); assert(&Ref == &LHS); } { path LHS(L); InputIter RHS(R); path& Ref = LHS.append(RHS); assert(PathEq(LHS, E)); assert(&Ref == &LHS); } { path LHS(L); InputIter RHS(R); InputIter REnd(StrEnd(R)); path& Ref = LHS.append(RHS, REnd); assert(PathEq(LHS, E)); assert(&Ref == &LHS); } } template ()))> constexpr bool has_append(int) { return true; } template constexpr bool has_append(long) { return false; } template ()))> constexpr bool has_append_op(int) { return true; } template constexpr bool has_append_op(long) { return false; } template constexpr bool has_append() { static_assert(has_append(0) == has_append_op(0), "must be same"); return has_append(0) && has_append_op(0); } void test_sfinae() { using namespace fs; { using It = const char* const; static_assert(has_append(), ""); } { using It = cpp17_input_iterator; static_assert(has_append(), ""); } { struct Traits { using iterator_category = std::input_iterator_tag; using value_type = const char; using pointer = const char*; using reference = const char&; using difference_type = std::ptrdiff_t; }; using It = cpp17_input_iterator; static_assert(has_append(), ""); } { using It = cpp17_output_iterator; static_assert(!has_append(), ""); } { static_assert(!has_append(), ""); } { static_assert(!has_append(), ""); static_assert(!has_append(), ""); } } int main(int, char**) { using namespace fs; for (auto const & TC : Cases) { { const char* LHS_In = TC.lhs; const char* RHS_In = TC.rhs; path LHS(LHS_In); path RHS(RHS_In); path& Res = (LHS /= RHS); assert(PathEq(Res, (const char*)TC.expected_result())); assert(&Res == &LHS); } doAppendSourceTest (TC); #ifndef TEST_HAS_NO_WIDE_CHARACTERS doAppendSourceTest (TC); #endif doAppendSourceTest(TC); doAppendSourceTest(TC); } for (auto const & TC : LongLHSCases) { (void)TC; LIBCPP_ONLY(doAppendSourceAllocTest(TC)); #ifndef TEST_HAS_NO_WIDE_CHARACTERS LIBCPP_ONLY(doAppendSourceAllocTest(TC)); #endif } test_sfinae(); return 0; }