diff --git a/libcxx/docs/FeatureTestMacroTable.rst b/libcxx/docs/FeatureTestMacroTable.rst --- a/libcxx/docs/FeatureTestMacroTable.rst +++ b/libcxx/docs/FeatureTestMacroTable.rst @@ -338,6 +338,8 @@ ------------------------------------------------- ----------------- ``__cpp_lib_out_ptr`` *unimplemented* ------------------------------------------------- ----------------- + ``__cpp_lib_print`` *unimplemented* + ------------------------------------------------- ----------------- ``__cpp_lib_ranges_as_rvalue`` ``202207L`` ------------------------------------------------- ----------------- ``__cpp_lib_ranges_chunk`` *unimplemented* diff --git a/libcxx/docs/Status/Cxx2bPapers.csv b/libcxx/docs/Status/Cxx2bPapers.csv --- a/libcxx/docs/Status/Cxx2bPapers.csv +++ b/libcxx/docs/Status/Cxx2bPapers.csv @@ -59,7 +59,7 @@ "`P1467R9 `__","LWG","Extended ``floating-point`` types and standard names","July 2022","","" "`P1642R11 `__","LWG","Freestanding ``[utilities]``, ``[ranges]``, and ``[iterators]``","July 2022","","" "`P1899R3 `__","LWG","``stride_view``","July 2022","","","|ranges|" -"`P2093R14 `__","LWG","Formatted output","July 2022","","" +"`P2093R14 `__","LWG","Formatted output","July 2022","","|In progress|" "`P2165R4 `__","LWG","Compatibility between ``tuple``, ``pair`` and ``tuple-like`` objects","July 2022","","" "`P2278R4 `__","LWG","``cbegin`` should always return a constant iterator","July 2022","","","|ranges|" "`P2286R8 `__","LWG","Formatting Ranges","July 2022","|Complete|","16.0","|format| |ranges|" @@ -101,7 +101,7 @@ "`P2167R3 `__","LWG", "Improved Proposed Wording for LWG 2114", "November 2022","","","" "`P2396R1 `__","LWG", "Concurrency TS 2 fixes ", "November 2022","","","|concurrency TS|" "`P2505R5 `__","LWG", "Monadic Functions for ``std::expected``", "November 2022","","","" -"`P2539R4 `__","LWG", "Should the output of ``std::print`` to a terminal be synchronized with the underlying stream?", "November 2022","","","|format|" +"`P2539R4 `__","LWG", "Should the output of ``std::print`` to a terminal be synchronized with the underlying stream?", "November 2022","|In progress|","","|format|" "`P2602R2 `__","LWG", "Poison Pills are Too Toxic", "November 2022","","","|ranges|" "`P2708R1 `__","LWG", "No Further Fundamentals TSes", "November 2022","|Nothing to do|","","" "","","","","","","" diff --git a/libcxx/docs/Status/FormatIssues.csv b/libcxx/docs/Status/FormatIssues.csv --- a/libcxx/docs/Status/FormatIssues.csv +++ b/libcxx/docs/Status/FormatIssues.csv @@ -5,11 +5,11 @@ `P1868 `_,"width: clarifying units of width and precision in std::format (Implements the unicode support.)","C++20",Mark de Wever,|Complete|,Clang 14 `P2216 `_,"std::format improvements","C++20",Mark de Wever,|Complete|,Clang 15 `P2418 `__,"Add support for ``std::generator``-like types to ``std::format``","C++20",Mark de Wever,|Complete|, Clang 15 -"`P2093R14 `__","Formatted output","C++23",Mark de Wever,|In Progress|, +"`P2093R14 `__","Formatted output","C++23",Mark de Wever,|In progress| "`P2286R8 `__","Formatting Ranges","C++23","Mark de Wever","|Complete|",Clang 16 "`P2508R1 `__","Exposing ``std::basic-format-string``","C++23","Mark de Wever","|Complete|", Clang 15 "`P2585R0 `__","Improving default container formatting","C++23","Mark de Wever","|Complete|", Clang 17 -"`P2539R4 `__","Should the output of ``std::print`` to a terminal be synchronized with the underlying stream?","C++23","Mark de Wever" +"`P2539R4 `__","Should the output of ``std::print`` to a terminal be synchronized with the underlying stream?","C++23","Mark de Wever","|In progress|" "`P2713R1 `__","Escaping improvements in ``std::format``","C++23","Mark de Wever","" "`P2675R1 `__","``format``'s width estimation is too approximate and not forward compatible","C++23","Mark de Wever","|Complete|", Clang 17 "`P2572R1 `__","``std::format`` fill character allowances","C++23","Mark de Wever","|In progress|" diff --git a/libcxx/docs/Status/FormatPaper.csv b/libcxx/docs/Status/FormatPaper.csv --- a/libcxx/docs/Status/FormatPaper.csv +++ b/libcxx/docs/Status/FormatPaper.csv @@ -47,6 +47,6 @@ `[stacktrace.format] `_,"Formatting ``stacktrace``",A ```` implementation,Mark de Wever,, "`P2093R14 `__","Formatted output" -`[print.fun] `__,"Output to ``stdout``",,Mark de Wever,|In Progress|, -`[print.fun] `__,"Output to ``FILE*``",,Mark de Wever,, +`[print.fun] `__,"Output to ``stdout``",,Mark de Wever,|In progress|, +`[print.fun] `__,"Output to ``FILE*``",,Mark de Wever,|Complete|, Clang 17 `[ostream.formatted.print] `__,"Output to ``ostream``",,Mark de Wever diff --git a/libcxx/include/__format/buffer.h b/libcxx/include/__format/buffer.h --- a/libcxx/include/__format/buffer.h +++ b/libcxx/include/__format/buffer.h @@ -529,6 +529,7 @@ struct __iterator { using difference_type = ptrdiff_t; + using value_type = _CharT; _LIBCPP_HIDE_FROM_ABI constexpr explicit __iterator(__retarget_buffer& __buffer) : __buffer_(std::addressof(__buffer)) {} @@ -551,7 +552,14 @@ __retarget_buffer& operator=(const __retarget_buffer&) = delete; _LIBCPP_HIDE_FROM_ABI explicit __retarget_buffer(size_t __size_hint) { - auto __result = std::__allocate_at_least(__alloc_, __size_hint ? __size_hint : 256 / sizeof(_CharT)); + // When the initial size is very small a lot of resizes happen + // when elements added. So use a hard-coded minimum size. + // + // Note a size < 2 will not work + // - 0 there is no buffer, while push_back requires 1 empty element. + // - 1 multiplied by the grow factor is 1 and thus the buffer never + // grows. + auto __result = std::__allocate_at_least(__alloc_, std::max(__size_hint, 256 / sizeof(_CharT))); __ptr_ = __result.ptr; __capacity_ = __result.count; } diff --git a/libcxx/include/print b/libcxx/include/print --- a/libcxx/include/print +++ b/libcxx/include/print @@ -10,6 +10,21 @@ #ifndef _LIBCPP_PRINT #define _LIBCPP_PRINT +/* +namespace std { + // [print.fun], print functions + template + void print(FILE* stream, format_string fmt, Args&&... args); + + template + void println(FILE* stream, format_string fmt, Args&&... args); + + void vprint_unicode(FILE* stream, string_view fmt, format_args args); + + void vprint_nonunicode(FILE* stream, string_view fmt, format_args args); +} +*/ + #include <__assert> // all public C++ headers provide the assertion handler // Make sure all feature-test macros are available. #include @@ -18,15 +33,44 @@ # include <__concepts/same_as.h> # include <__config> +# include <__format/buffer.h> +# include <__format/format_arg_store.h> +# include <__format/format_args.h> +# include <__format/format_error.h> +# include <__format/format_functions.h> # include <__format/unicode.h> +# include <__system_error/system_error.h> +# include <__utility/forward.h> +# include +# include +# include # include +# ifdef _WIN32 +# include +# else +# include +# endif + # if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER) # pragma GCC system_header # endif _LIBCPP_BEGIN_NAMESPACE_STD +# ifndef _LIBCPP_HAS_NO_WIDE_CHARACTERS +// A wrapper for WriteConsoleW which is used to write to the Windows +// console. This function is in the dylib to avoid pulling in windows.h +// in the library headers. The function itself uses some private parts +// of the dylib too. +// +// The function does not depend on the language standard used. Guarding +// it with C++23 would fail since the dylib is currently build using C++20. +// +// Note the function is only implemented on the Windows platform. +void _LIBCPP_FUNC_VIS __write_to_windows_console(FILE* __steam, wstring_view __data); +# endif // _LIBCPP_HAS_NO_WIDE_CHARACTERS + # if _LIBCPP_STD_VER >= 23 # ifndef _LIBCPP_HAS_NO_UNICODE @@ -85,6 +129,9 @@ // For example when processing a 1 or 2 code unit UTF-8 code point the // result will always be a encoded to 1 UTF-16 code unit. (The // surrogate range requires 3 code units.) + + // TODO FMT Validate with clang-tidy + // NOLINTNEXTLINE(bugprone-dangling-handle) basic_string_view> __data{__first, __last}; __code_point_view> __view{__data.begin(), __data.end()}; while (!__view.__at_end()) @@ -96,7 +143,198 @@ # endif // _LIBCPP_HAS_NO_UNICODE -# endif // _LIBCPP_STD_VER >= 23 +namespace __print { + +// [print.fun]/2 +// Effects: If the ordinary literal encoding ([lex.charset]) is UTF-8, equivalent to: +// vprint_unicode(stream, fmt.str, make_format_args(std::forward(args)...)); +// Otherwise, equivalent to: +// vprint_nonunicode(stream, fmt.str, make_format_args(std::forward(args)...)); +// +// Based on the compiler and its compilation flags this value is or is +// not true. As mentioned in P2093R14 this only affects Windows. The +// test below could also be done for +// - GCC using __GNUC_EXECUTION_CHARSET_NAME +// https://gcc.gnu.org/onlinedocs/cpp/Common-Predefined-Macros.html +// - Clang using __clang_literal_encoding__ +// https://clang.llvm.org/docs/LanguageExtensions.html#builtin-macros +// (note at the time of writing Clang is hard-coded to UTF-8.) +// + +# ifndef _LIBCPP_HAS_NO_UNICODE +# ifdef _MSVC_EXECUTION_CHARACTER_SET +// This is the same test MSVC STL uses in their implementation of +// +// See: https://learn.microsoft.com/en-us/windows/win32/intl/code-page-identifiers +inline constexpr bool __use_unicode = _MSVC_EXECUTION_CHARACTER_SET == 65001; // Unicode (UTF-8) == 65001 +# else +inline constexpr bool __use_unicode = true; +# endif +# else // _LIBCPP_HAS_NO_UNICODE +inline constexpr bool __use_unicode = false; +# endif // _LIBCPP_HAS_NO_UNICODE + +_LIBCPP_HIDE_FROM_ABI inline bool __is_terminal(FILE* __stream) { +# ifdef _LIBCPP_TESTING_PRINT_IS_TERMINAL_FUNCTION + return _LIBCPP_TESTING_PRINT_IS_TERMINAL_FUNCTION(__stream); +# elif !defined(_WIN32) + return isatty(fileno(__stream)); +# else + // Note the Standard does this in one call, but it's unclear whether + // an invalid handle is allowed when calling GetConsoleMode. + // + // https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/get-osfhandle?view=msvc-170 + // https://learn.microsoft.com/en-us/windows/console/getconsolemode + intptr_t __handle = _get_osfhandle(fileno(__stream)); + if (__handle == -1) + return false; + + DWORD __mode; + return GetConsoleMode(__handle, &__mode); +# endif +} + +_LIBCPP_HIDE_FROM_ABI inline void +__vprint_nonunicode(FILE* __stream, string_view __fmt, format_args __args, bool __write_nl) { + _LIBCPP_ASSERT(__stream, "__stream is a valid pointer to an output C stream"); + string __str = std::vformat(__fmt, __args); + if (__write_nl) + __str.push_back('\n'); + + size_t __size = fwrite(__str.data(), 1, __str.size(), __stream); + if (__size < __str.size()) { + if (std::feof(__stream)) + std::__throw_system_error(EIO, "EOF while writing the formatted output"); + std::__throw_system_error(std::ferror(__stream), "failed to write formatted output"); + } +} + +# ifndef _LIBCPP_HAS_NO_UNICODE + +// Note these helper functions are mainly used to aid testing. +// On POSIX systems and Windows the output is no longer considered a +// terminal when the output is redirected. Typically during testing the +// output is redirected to be able to capture it. This makes is hard to +// test this code path. +_LIBCPP_HIDE_FROM_ABI inline void +__vprint_unicode_posix(FILE* __stream, string_view __fmt, format_args __args, bool __write_nl, bool __is_terminal) { + // TODO FMT Should flush errors throw too? + if (__is_terminal) + std::fflush(__stream); + + __print::__vprint_nonunicode(__stream, __fmt, __args, __write_nl); +} + +# ifndef _LIBCPP_HAS_NO_WIDE_CHARACTERS +_LIBCPP_HIDE_FROM_ABI inline void +__vprint_unicode_windows(FILE* __stream, string_view __fmt, format_args __args, bool __write_nl, bool __is_terminal) { + if (!__is_terminal) + return __print::__vprint_nonunicode(__stream, __fmt, __args, __write_nl); + + // TODO FMT Should flush errors throw too? + std::fflush(__stream); + + string __str = std::vformat(__fmt, __args); + // UTF-16 uses the same number of less code units than UTF-8. + // However the size of the code unit 16 bits instead of 8 bits. + // + // The buffer uses the worse-case estimate and should never resize. + // However when the string is large this could lead to OOM. Using a + // smaller size might work, but since the buffer uses a grow factor + // the final size might be larger when the estimate is wrong. + // + // TODO FMT profile and improve the speed of this code. + __format::__retarget_buffer __buffer{__str.size()}; + __unicode::__transcode(__str.begin(), __str.end(), __buffer.__make_output_iterator()); + if (__write_nl) + __buffer.push_back(L'\n'); + + wstring_view __view = __buffer.__view(); + +# ifndef _LIBCPP_TESTING_PRINT_WRITE_TO_WINDOWS_CONSOLE_FUNCTION + std::__write_to_windows_console(__stream, __view); +# else + _LIBCPP_TESTING_PRINT_WRITE_TO_WINDOWS_CONSOLE_FUNCTION(__stream, __view); +# endif +} +# endif // _LIBCPP_HAS_NO_WIDE_CHARACTERS + +_LIBCPP_HIDE_FROM_ABI inline void __vprint_unicode( + [[maybe_unused]] eFILE* __stream, + [[maybe_unused]] string_view __fmt, + [[maybe_unused]] format_args __args, + [[maybe_unused]] bool __write_nl) { + _LIBCPP_ASSERT(__stream, "__stream is a valid pointer to an output C stream"); + + // [print.fun] + // 7 - Effects: If stream refers to a terminal capable of displaying + // Unicode, writes out to the terminal using the native Unicode + // API; if out contains invalid code units, the behavior is + // undefined and implementations are encouraged to diagnose it. + // Otherwise writes out to stream unchanged. If the native + // Unicode API is used, the function flushes stream before + // writing out. + // 8 - Throws: Any exception thrown by the call to vformat + // ([format.err.report]). system_error if writing to the terminal + // or stream fails. May throw bad_alloc. + // 9 - Recommended practice: If invoking the native Unicode API + // requires transcoding, implementations should substitute + // invalid code units with U+FFFD replacement character per the + // Unicode Standard, Chapter 3.9 U+FFFD Substitution in + // Conversion. + + // On non-Windows platforms the Unicode API is the normal file I/O API + // so there the call can be forwarded to the non_unicode API. On + // Windows there is a different API. This API requires transcoding. + +# ifndef _WIN32 + __print::__vprint_unicode_posix(__stream, __fmt, __args, __write_nl, __print::__is_terminal(__stream)); +# elif !defined(_LIBCPP_HAS_NO_WIDE_CHARACTERS) + __print::__vprint_unicode_windows(__stream, __fmt, __args, __write_nl, __print::__is_terminal(__stream)); +# else + // Note libc++ does not test Windows with wchar_t disabled. + std::__throw_format_error("Unable to convert UTF-8 to UTF-16 for the Windows Console"); +# endif +} + +# else // _LIBCPP_HAS_NO_UNICODE +_LIBCPP_HIDE_FROM_ABI inline void +__vprint_unicode(FILE* __stream, string_view __fmt, format_args __args, bool __write_nl) { + __print::__vprint_nonunicode(__stream, __fmt, __args, __write_nl); +} +# endif // _LIBCPP_HAS_NO_UNICODE + +} // namespace __print + +template +_LIBCPP_HIDE_FROM_ABI void print(FILE* __stream, format_string<_Args...> __fmt, _Args&&... __args) { + // TODO FMT format doesn't forward to make_format_args, but print does. + if constexpr (__print::__use_unicode) + __print::__vprint_unicode(__stream, __fmt.get(), std::make_format_args(std::forward<_Args>(__args)...), false); + else + __print::__vprint_nonunicode(__stream, __fmt.get(), std::make_format_args(std::forward<_Args>(__args)...), false); +} + +template +_LIBCPP_HIDE_FROM_ABI void println(FILE* __stream, format_string<_Args...> __fmt, _Args&&... __args) { + // Note the wording in the Standard is inefficient. The output of + // std::format is a std::string which is then copied. This solution + // just appends a newline at the end of the output. + if constexpr (__print::__use_unicode) + __print::__vprint_unicode(__stream, __fmt.get(), std::make_format_args(std::forward<_Args>(__args)...), true); + else + __print::__vprint_nonunicode(__stream, __fmt.get(), std::make_format_args(std::forward<_Args>(__args)...), true); // +} + +_LIBCPP_HIDE_FROM_ABI inline void vprint_unicode(FILE* __stream, string_view __fmt, format_args __args) { + __print::__vprint_unicode(__stream, __fmt, __args, false); +} + +_LIBCPP_HIDE_FROM_ABI inline void vprint_nonunicode(FILE* __stream, string_view __fmt, format_args __args) { + __print::__vprint_nonunicode(__stream, __fmt, __args, false); +} + +# endif // _LIBCPP_STD_VER >= 23 _LIBCPP_END_NAMESPACE_STD diff --git a/libcxx/include/version b/libcxx/include/version --- a/libcxx/include/version +++ b/libcxx/include/version @@ -139,6 +139,7 @@ __cpp_lib_out_ptr 202106L __cpp_lib_parallel_algorithm 201603L __cpp_lib_polymorphic_allocator 201902L +__cpp_lib_print 202207L __cpp_lib_quoted_string_io 201304L __cpp_lib_ranges 202106L @@ -412,6 +413,9 @@ # undef __cpp_lib_optional # define __cpp_lib_optional 202110L // # define __cpp_lib_out_ptr 202106L +# if !defined(_LIBCPP_HAS_NO_INCOMPLETE_FORMAT) +// # define __cpp_lib_print 202207L +# endif # define __cpp_lib_ranges_as_rvalue 202207L // # define __cpp_lib_ranges_chunk 202202L // # define __cpp_lib_ranges_chunk_by 202202L diff --git a/libcxx/src/CMakeLists.txt b/libcxx/src/CMakeLists.txt --- a/libcxx/src/CMakeLists.txt +++ b/libcxx/src/CMakeLists.txt @@ -318,6 +318,7 @@ set(LIBCXX_EXPERIMENTAL_SOURCES experimental/memory_resource.cpp format.cpp + print.cpp ) add_library(cxx_experimental STATIC ${LIBCXX_EXPERIMENTAL_SOURCES}) diff --git a/libcxx/src/print.cpp b/libcxx/src/print.cpp new file mode 100644 --- /dev/null +++ b/libcxx/src/print.cpp @@ -0,0 +1,35 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#include + +#include <__system_error/system_error.h> + +#if defined(_LIBCPP_WIN32API) +# define WIN32_LEAN_AND_MEAN +# define NOMINMAX +# include +#endif + +#include "filesystem/filesystem_common.h" // make_windows_error + +_LIBCPP_BEGIN_NAMESPACE_STD + +#ifndef _LIBCPP_HAS_NO_WIDE_CHARACTERS +// Make this a weak function so tests can override it. +void _LIBCPP_FUNC_VIS __write_to_windows_console([[maybe_unused]] FILE* __steam, [[maybe_unused]] wstring_view __data) { +# if defined(_LIBCPP_WIN32API) + // https://learn.microsoft.com/en-us/windows/console/writeconsole + if (WriteConsoleW( + reinterpret_cast(_get_osfhandle(__stream)), __view.data(), __view.size(), nullptr, nullptr) == 0) + std::__throw_system_error(detail::make_windows_error(GetLastError()), "failed to write formatted output"); +# endif // defined(_LIBCPP_WIN32API) +} +#endif // _LIBCPP_HAS_NO_WIDE_CHARACTERS + +_LIBCPP_END_NAMESPACE_STD diff --git a/libcxx/test/libcxx/input.output/iostream.format/print.fun/transcoding.pass.cpp b/libcxx/test/libcxx/input.output/iostream.format/print.fun/transcoding.pass.cpp --- a/libcxx/test/libcxx/input.output/iostream.format/print.fun/transcoding.pass.cpp +++ b/libcxx/test/libcxx/input.output/iostream.format/print.fun/transcoding.pass.cpp @@ -37,8 +37,10 @@ assert(std::basic_string_view(buffer.data(), out) == expected); +#ifdef TEST_HAS_GLIBC out = std::find_if(out, buffer.end(), [](CharT c) { return c != CharT('*'); }); assert(out == buffer.end()); +#endif } template diff --git a/libcxx/test/libcxx/input.output/iostream.format/print.fun/vprint_unicode_posix.pass.cpp b/libcxx/test/libcxx/input.output/iostream.format/print.fun/vprint_unicode_posix.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/libcxx/input.output/iostream.format/print.fun/vprint_unicode_posix.pass.cpp @@ -0,0 +1,66 @@ +//===----------------------------------------------------------------------===// +// 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, c++17, c++20 +// UNSUPPORTED: libcpp-has-no-incomplete-format + +// REQUIRES: has-unix-headers + +// + +// Tests the implementation of +// void __print::__vprint_unicode_posix(FILE* __stream, string_view __fmt, +// format_args __args, bool __write_nl, +// bool __is_terminal); +// +// In the library when the stdout is redirected to a file it is no +// longer considered a terminal and the special terminal handling is no +// longer executed. By testing this function we can "force" emulate a +// terminal. +// Note __write_nl is tested by the public API. + +#include +#include +#include +#include +#include + +#include "test_macros.h" + +int main(int, char**) { + std::array buffer; + std::ranges::fill(buffer, '*'); + + FILE* file = fmemopen(buffer.data(), buffer.size(), "wb"); + assert(file); + + // Test the file is buffered. + fprintf(file, "Hello"); + assert(std::ftell(file) == 5); +#ifdef TEST_HAS_GLIBC + assert(std::ranges::find_if(buffer, [](char c) { return c != '*'; }) == buffer.end()); +#endif + + // Test writing to a "non-terminal" stream does not flush. + std::__print::__vprint_unicode_posix(file, " world", std::make_format_args(), false, false); + assert(std::ftell(file) == 11); +#ifdef TEST_HAS_GLIBC + assert(std::ranges::find_if(buffer, [](char c) { return c != '*'; }) == buffer.end()); +#endif + + // Test writing to a "terminal" stream flushes before writing. + std::__print::__vprint_unicode_posix(file, "!", std::make_format_args(), false, true); + assert(std::ftell(file) == 12); + assert(std::string_view(buffer.data(), buffer.data() + 11) == "Hello world"); + assert(buffer[11] != '!'); + + // Test everything is written when closing the stream. + fclose(file); + assert(std::string_view(buffer.data(), buffer.data() + 12) == "Hello world!"); + + return 0; +} diff --git a/libcxx/test/libcxx/input.output/iostream.format/print.fun/vprint_unicode_windows.pass.cpp b/libcxx/test/libcxx/input.output/iostream.format/print.fun/vprint_unicode_windows.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/libcxx/input.output/iostream.format/print.fun/vprint_unicode_windows.pass.cpp @@ -0,0 +1,130 @@ +//===----------------------------------------------------------------------===// +// 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, c++17, c++20 +// UNSUPPORTED: libcpp-has-no-incomplete-format +// UNSUPPORTED: no-wide-characters + +// + +// Tests the implementation of +// void __print::__vprint_unicode_windows(FILE* __stream, string_view __fmt, +// format_args __args, bool __write_nl, +// bool __is_terminal); +// +// In the library when the stdout is redirected to a file it is no +// longer considered a terminal and the special terminal handling is no +// longer executed. By testing this function we can "force" emulate a +// terminal. +// Note __write_nl is tested by the public API. + +#include +#include +#include +#include + +void write_to_console(FILE*, std::wstring_view data); +#define _LIBCPP_TESTING_PRINT_WRITE_TO_WINDOWS_CONSOLE_FUNCTION ::write_to_console +#include + +#include "test_macros.h" +#include "filesystem_test_helper.h" +#include "make_string.h" + +TEST_GCC_DIAGNOSTIC_IGNORED("-Wuse-after-free") + +#define SV(S) MAKE_STRING_VIEW(wchar_t, S) + +bool calling = false; +std::wstring_view expected = L" world"; + +void write_to_console(FILE*, std::wstring_view data) { + assert(calling); + assert(errno == EBADF); // Flush called on a closed stream. + assert(data == expected); +} + +scoped_test_env env; +std::string filename = env.create_file("output.txt"); + +static void test_basics() { + FILE* file = fopen(filename.c_str(), "wb"); + assert(file); + + // Test writing to a "non-terminal" stream does not call WriteConsoleW. + std::__print::__vprint_unicode_windows(file, "Hello", std::make_format_args(), false, false); + assert(std::ftell(file) == 5); + + // Test writing to a "terminal" stream flushes before writing. + // There is no reliable way to determine whether flush is called, rely + // on fflush setting errno on a closed file descriptor. + fclose(file); + errno = 0; + calling = true; + std::__print::__vprint_unicode_windows(file, " world", std::make_format_args(), false, true); +} + +// When the output is a file the data is written as-is. +// When the output is a "terminal" invalid UTF-8 input is flagged. +static void test(std::wstring_view output, std::string_view input) { + // *** File *** + FILE* file = fopen(filename.c_str(), "wb"); + assert(file); + + std::__print::__vprint_unicode_windows(file, input, std::make_format_args(), false, false); + assert(std::ftell(file) == static_cast(input.size())); + fclose(file); + + file = fopen(filename.c_str(), "rb"); + assert(file); + + std::vector buffer(input.size()); + size_t read = fread(buffer.data(), 1, buffer.size(), file); + assert(read == input.size()); + assert(input == std::string_view(buffer.begin(), buffer.end())); + fclose(file); + + // *** Terminal *** + expected = output; + std::__print::__vprint_unicode_windows(file, input, std::make_format_args(), false, true); +} + +static void test() { + // *** Test valid UTF-8 *** +#define TEST(S) test(SV(S), S) + TEST("hello world"); + + // copied from benchmarks/std_format_spec_string_unicode.bench.cpp + TEST("Lorem ipsum dolor sit amet, ne sensibus evertitur aliquando his. Iuvaret fabulas qui ex."); + TEST("Lōrem ipsūm dolor sīt æmeÞ, ea vel nostrud feuġǣit, muciūs tēmporiȝusrefērrēnÞur no mel."); + TEST("Лорем ипсум долор сит амет, еу диам тамяуам принципес вис, еяуидем цонцептам диспутандо"); + TEST("入ト年媛ろ舗学ラロ準募ケカ社金スノ屋検れう策他セヲシ引口ぎ集7独ぱクふ出車ぽでぱ円輪ルノ受打わ。"); + TEST("\U0001f636\u200d\U0001f32b\ufe0f"); +#undef TEST + + // *** Test invalid utf-8 *** + test(SV("\ufffd"), "\xc3"); + test(SV("\ufffd("), "\xc3\x28"); + + // surrogate range + test(SV("\ufffd"), "\xed\xa0\x80"); // U+D800 + test(SV("\ufffd"), "\xed\xaf\xbf"); // U+DBFF + test(SV("\ufffd"), "\xed\xbf\x80"); // U+DC00 + test(SV("\ufffd"), "\xed\xbf\xbf"); // U+DFFF + + // beyond valid values + test(SV("\ufffd"), "\xf4\x90\x80\x80"); // U+110000 + test(SV("\ufffd"), "\xf4\xbf\xbf\xbf"); // U+11FFFF + + // Validates http://unicode.org/review/pr-121.html option 3. + test(SV("\u0061\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\u0062"), "\x61\xf1\x80\x80\xe1\x80\xc2\x62"); +} + +int main(int, char**) { + test_basics(); + test(); +} diff --git a/libcxx/test/libcxx/transitive_includes/cxx03.csv b/libcxx/test/libcxx/transitive_includes/cxx03.csv --- a/libcxx/test/libcxx/transitive_includes/cxx03.csv +++ b/libcxx/test/libcxx/transitive_includes/cxx03.csv @@ -643,11 +643,21 @@ ostream type_traits ostream typeinfo ostream version +print array +print cerrno +print cmath print cstddef print cstdint +print cstdio +print cstdlib print initializer_list print limits +print locale +print optional +print stdexcept +print string print string_view +print tuple print version queue compare queue concepts diff --git a/libcxx/test/libcxx/transitive_includes/cxx11.csv b/libcxx/test/libcxx/transitive_includes/cxx11.csv --- a/libcxx/test/libcxx/transitive_includes/cxx11.csv +++ b/libcxx/test/libcxx/transitive_includes/cxx11.csv @@ -644,11 +644,21 @@ ostream type_traits ostream typeinfo ostream version +print array +print cerrno +print cmath print cstddef print cstdint +print cstdio +print cstdlib print initializer_list print limits +print locale +print optional +print stdexcept +print string print string_view +print tuple print version queue compare queue concepts diff --git a/libcxx/test/libcxx/transitive_includes/cxx14.csv b/libcxx/test/libcxx/transitive_includes/cxx14.csv --- a/libcxx/test/libcxx/transitive_includes/cxx14.csv +++ b/libcxx/test/libcxx/transitive_includes/cxx14.csv @@ -646,11 +646,21 @@ ostream type_traits ostream typeinfo ostream version +print array +print cerrno +print cmath print cstddef print cstdint +print cstdio +print cstdlib print initializer_list print limits +print locale +print optional +print stdexcept +print string print string_view +print tuple print version queue compare queue concepts diff --git a/libcxx/test/libcxx/transitive_includes/cxx17.csv b/libcxx/test/libcxx/transitive_includes/cxx17.csv --- a/libcxx/test/libcxx/transitive_includes/cxx17.csv +++ b/libcxx/test/libcxx/transitive_includes/cxx17.csv @@ -646,11 +646,21 @@ ostream type_traits ostream typeinfo ostream version +print array +print cerrno +print cmath print cstddef print cstdint +print cstdio +print cstdlib print initializer_list print limits +print locale +print optional +print stdexcept +print string print string_view +print tuple print version queue compare queue concepts diff --git a/libcxx/test/libcxx/transitive_includes/cxx20.csv b/libcxx/test/libcxx/transitive_includes/cxx20.csv --- a/libcxx/test/libcxx/transitive_includes/cxx20.csv +++ b/libcxx/test/libcxx/transitive_includes/cxx20.csv @@ -652,11 +652,21 @@ ostream type_traits ostream typeinfo ostream version +print array +print cerrno +print cmath print cstddef print cstdint +print cstdio +print cstdlib print initializer_list print limits +print locale +print optional +print stdexcept +print string print string_view +print tuple print version queue compare queue concepts diff --git a/libcxx/test/libcxx/transitive_includes/cxx2b.csv b/libcxx/test/libcxx/transitive_includes/cxx2b.csv --- a/libcxx/test/libcxx/transitive_includes/cxx2b.csv +++ b/libcxx/test/libcxx/transitive_includes/cxx2b.csv @@ -436,11 +436,22 @@ ostream string ostream typeinfo ostream version +print array +print cerrno +print cmath print cstddef print cstdint +print cstdio +print cstdlib print initializer_list print limits +print locale +print new +print optional +print stdexcept +print string print string_view +print tuple print version queue compare queue cstddef diff --git a/libcxx/test/std/input.output/iostream.format/print.fun/print.file.pass.cpp b/libcxx/test/std/input.output/iostream.format/print.fun/print.file.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/input.output/iostream.format/print.fun/print.file.pass.cpp @@ -0,0 +1,154 @@ +//===----------------------------------------------------------------------===// +// 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, c++17, c++20 +// UNSUPPORTED: libcpp-has-no-incomplete-format +// UNSUPPORTED: no-fstream + +// XFAIL: availability-fp_to_chars-missing + +// + +// template +// void print(FILE* stream, format_string fmt, Args&&... args); + +// In the library when the stdout is redirected to a file it is no +// longer considered a terminal and the special terminal handling is no +// longer executed. There are tests in +// libcxx/test/libcxx/input.output/iostream.format/print.fun/ +// to validate that behaviour + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "assert_macros.h" +#include "concat_macros.h" +#include "filesystem_test_helper.h" +#include "print_tests.h" +#include "test_format_string.h" +#include "test_macros.h" + +scoped_test_env env; +std::string filename = env.create_file("output.txt"); + +auto test_file = [](std::string_view expected, test_format_string fmt, Args&&... args) { + FILE* file = fopen(filename.c_str(), "wb"); + assert(file); + + std::print(file, fmt, std::forward(args)...); + std::fclose(file); + + std::ifstream stream{filename.c_str(), std::ios_base::in | std::ios_base::binary}; + std::string out(std::istreambuf_iterator{stream}, {}); + TEST_REQUIRE(out == expected, + TEST_WRITE_CONCATENATED( + "\nFormat string ", fmt.get(), "\nExpected output ", expected, "\nActual output ", out, '\n')); +}; + +auto test_exception = [](std::string_view, std::string_view, Args&&...) { + // After P2216 most exceptions thrown by std::format become ill-formed. + // Therefore this tests does nothing. + // A basic ill-formed test is done in format.verify.cpp + // The exceptions are tested by other functions that don't use the basic-format-string as fmt argument. +}; + +#if defined(_POSIX_C_SOURCE) && _POSIX_C_SOURCE >= 200809L +// The FILE returned by fmemopen does not have file descriptor. +// This means the test could fail when the implementation uses a +// function that requires a file decriptor, for example write. +static void test_no_file_descriptor() { + std::array buffer{0}; + + FILE* file = fmemopen(buffer.data(), buffer.size(), "wb"); + assert(file); + + std::print(file, "hello world{}", '!'); + long pos = std::ftell(file); + std::fclose(file); + + assert(pos > 0); + assert(std::string_view(buffer.data(), pos) == "hello world!"); +} +#endif // defined(_POSIX_C_SOURCE) && _POSIX_C_SOURCE >= 200809L + +static void test_wide_stream() { + FILE* file = fopen(filename.c_str(), "wb"); + assert(file); + + int mode = std::fwide(file, 1); + assert(mode > 0); + + TEST_VALIDATE_EXCEPTION( + std::system_error, + [&]([[maybe_unused]] const std::system_error& e) { + [[maybe_unused]] std::string_view what{"failed to write formatted output"}; + TEST_LIBCPP_REQUIRE( + e.what() == what, + TEST_WRITE_CONCATENATED("\nExpected exception ", what, "\nActual exception ", e.what(), '\n')); + }, + std::print(file, "hello")); +} + +static void test_read_only() { + FILE* file = fopen(filename.c_str(), "r"); + assert(file); + + TEST_VALIDATE_EXCEPTION( + std::system_error, + [&]([[maybe_unused]] const std::system_error& e) { + [[maybe_unused]] std::string_view what{"failed to write formatted output: Operation not permitted"}; + TEST_LIBCPP_REQUIRE( + e.what() == what, + TEST_WRITE_CONCATENATED("\nExpected exception ", what, "\nActual exception ", e.what(), '\n')); + }, + std::print(file, "hello")); +} + +static void test_new_line() { + // Text does newline translation. + { + FILE* file = fopen(filename.c_str(), "w"); + assert(file); + + std::print(file, "\n"); +#ifndef _WIN32 + assert(std::ftell(file) == 1); +#else + assert(std::ftell(file) == 2); +#endif + } + // Binary no newline translation. + { + FILE* file = fopen(filename.c_str(), "wb"); + assert(file); + + std::print(file, "\n"); + assert(std::ftell(file) == 1); + } +} + +int main(int, char**) { + print_tests(test_file, test_exception); + +#if defined(_POSIX_C_SOURCE) && _POSIX_C_SOURCE >= 200809L + test_no_file_descriptor(); +#endif + + test_wide_stream(); + test_read_only(); + test_new_line(); + + return 0; +} diff --git a/libcxx/test/std/input.output/iostream.format/print.fun/print_tests.h b/libcxx/test/std/input.output/iostream.format/print.fun/print_tests.h new file mode 100644 --- /dev/null +++ b/libcxx/test/std/input.output/iostream.format/print.fun/print_tests.h @@ -0,0 +1,70 @@ +//===----------------------------------------------------------------------===// +// 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 +// +//===----------------------------------------------------------------------===// + +#ifndef TEST_STD_INPUT_OUTPUT_IOSTREAM_FORMAT_PRINT_FUN_PRINT_TESTS_H +#define TEST_STD_INPUT_OUTPUT_IOSTREAM_FORMAT_PRINT_FUN_PRINT_TESTS_H + +template +void print_tests(TestFunction check, ExceptionTest check_exception) { + // *** Test escaping *** + + check("{", "{{"); + check("{:^}", "{{:^}}"); + check("{: ^}", "{{:{}^}}", ' '); + check("{:{}^}", "{{:{{}}^}}"); + check("{:{ }^}", "{{:{{{}}}^}}", ' '); + + // *** Test argument ID *** + check("hello false true", "hello {0:} {1:}", false, true); + check("hello true false", "hello {1:} {0:}", false, true); + + // *** Test many arguments *** + check( + "1234567890\t1234567890", + "{}{}{}{}{}{}{}{}{}{}\t{}{}{}{}{}{}{}{}{}{}", + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 0); + + // *** Test embedded NUL character *** + using namespace std::literals; + check("hello\0world"sv, "hello{}{}", '\0', "world"); + check("hello\0world"sv, "hello\0{}"sv, "world"); + check("hello\0world"sv, "hello{}", "\0world"sv); + + // ** Test invalid format strings *** + check_exception("The format string terminates at a '{'", "{"); + check_exception("The replacement field misses a terminating '}'", "{:", 42); + + check_exception("The format string contains an invalid escape sequence", "}"); + check_exception("The format string contains an invalid escape sequence", "{:}-}", 42); + + check_exception("The format string contains an invalid escape sequence", "} "); + check_exception("The arg-id of the format-spec starts with an invalid character", "{-", 42); + check_exception("Argument index out of bounds", "hello {}"); + check_exception("Argument index out of bounds", "hello {0}"); + check_exception("Argument index out of bounds", "hello {1}", 42); +} + +#endif // TEST_STD_INPUT_OUTPUT_IOSTREAM_FORMAT_PRINT_FUN_PRINT_TESTS_H diff --git a/libcxx/test/std/input.output/iostream.format/print.fun/println.file.pass.cpp b/libcxx/test/std/input.output/iostream.format/print.fun/println.file.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/input.output/iostream.format/print.fun/println.file.pass.cpp @@ -0,0 +1,156 @@ +//===----------------------------------------------------------------------===// +// 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, c++17, c++20 +// UNSUPPORTED: libcpp-has-no-incomplete-format +// UNSUPPORTED: no-fstream + +// XFAIL: availability-fp_to_chars-missing + +// + +// template +// void println(FILE* stream, format_string fmt, Args&&... args); + +// In the library when the stdout is redirected to a file it is no +// longer considered a terminal and the special terminal handling is no +// longer executed. There are tests in +// libcxx/test/libcxx/input.output/iostream.format/print.fun/ +// to validate that behaviour + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "assert_macros.h" +#include "concat_macros.h" +#include "filesystem_test_helper.h" +#include "print_tests.h" +#include "test_format_string.h" +#include "test_macros.h" + +scoped_test_env env; +std::string filename = env.create_file("output.txt"); + +auto test_file = [](std::string_view e, test_format_string fmt, Args&&... args) { + std::string expected = std::string{e} + '\n'; + + FILE* file = fopen(filename.c_str(), "wb"); + assert(file); + + std::println(file, fmt, std::forward(args)...); + std::fclose(file); + + std::ifstream stream{filename.c_str(), std::ios_base::in | std::ios_base::binary}; + std::string out(std::istreambuf_iterator{stream}, {}); + TEST_REQUIRE(out == expected, + TEST_WRITE_CONCATENATED( + "\nFormat string ", fmt.get(), "\nExpected output ", expected, "\nActual output ", out, '\n')); +}; + +auto test_exception = [](std::string_view, std::string_view, Args&&...) { + // After P2216 most exceptions thrown by std::format become ill-formed. + // Therefore this tests does nothing. + // A basic ill-formed test is done in format.verify.cpp + // The exceptions are tested by other functions that don't use the basic-format-string as fmt argument. +}; + +#if defined(_POSIX_C_SOURCE) && _POSIX_C_SOURCE >= 200809L +// The FILE returned by fmemopen does not have file descriptor. +// This means the test could fail when the implementation uses a +// function that requires a file decriptor, for example write. +static void test_no_file_descriptor() { + std::array buffer{0}; + + FILE* file = fmemopen(buffer.data(), buffer.size(), "wb"); + assert(file); + + std::println(file, "hello world{}", '!'); + long pos = std::ftell(file); + std::fclose(file); + + assert(pos > 0); + assert(std::string_view(buffer.data(), pos) == "hello world!\n"); +} +#endif // defined(_POSIX_C_SOURCE) && _POSIX_C_SOURCE >= 200809L + +static void test_wide_stream() { + FILE* file = fopen(filename.c_str(), "wb"); + assert(file); + + int mode = std::fwide(file, 1); + assert(mode > 0); + + TEST_VALIDATE_EXCEPTION( + std::system_error, + [&]([[maybe_unused]] const std::system_error& e) { + [[maybe_unused]] std::string_view what{"failed to write formatted output"}; + TEST_LIBCPP_REQUIRE( + e.what() == what, + TEST_WRITE_CONCATENATED("\nExpected exception ", what, "\nActual exception ", e.what(), '\n')); + }, + std::println(file, "hello")); +} + +static void test_read_only() { + FILE* file = fopen(filename.c_str(), "r"); + assert(file); + + TEST_VALIDATE_EXCEPTION( + std::system_error, + [&]([[maybe_unused]] const std::system_error& e) { + [[maybe_unused]] std::string_view what{"failed to write formatted output: Operation not permitted"}; + TEST_LIBCPP_REQUIRE( + e.what() == what, + TEST_WRITE_CONCATENATED("\nExpected exception ", what, "\nActual exception ", e.what(), '\n')); + }, + std::println(file, "hello")); +} + +static void test_new_line() { + // Text does newline translation. + { + FILE* file = fopen(filename.c_str(), "w"); + assert(file); + + std::println(file, ""); +#ifndef _WIN32 + assert(std::ftell(file) == 1); +#else + assert(std::ftell(file) == 2); +#endif + } + // Binary no newline translation. + { + FILE* file = fopen(filename.c_str(), "wb"); + assert(file); + + std::println(file, ""); + assert(std::ftell(file) == 1); + } +} + +int main(int, char**) { + print_tests(test_file, test_exception); + +#if defined(_POSIX_C_SOURCE) && _POSIX_C_SOURCE >= 200809L + test_no_file_descriptor(); +#endif + + test_wide_stream(); + test_read_only(); + test_new_line(); + + return 0; +} diff --git a/libcxx/test/std/input.output/iostream.format/print.fun/vprint_nonunicode.file.pass.cpp b/libcxx/test/std/input.output/iostream.format/print.fun/vprint_nonunicode.file.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/input.output/iostream.format/print.fun/vprint_nonunicode.file.pass.cpp @@ -0,0 +1,158 @@ +//===----------------------------------------------------------------------===// +// 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, c++17, c++20 +// UNSUPPORTED: libcpp-has-no-incomplete-format +// UNSUPPORTED: no-fstream + +// XFAIL: availability-fp_to_chars-missing + +// + +// void vprint_nonunicode(FILE* stream, string_view fmt, format_args args); + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "assert_macros.h" +#include "concat_macros.h" +#include "filesystem_test_helper.h" +#include "print_tests.h" +#include "test_macros.h" + +scoped_test_env env; +std::string filename = env.create_file("output.txt"); + +auto test_file = [](std::string_view expected, std::string_view fmt, Args&&... args) { + FILE* file = fopen(filename.c_str(), "wb"); + assert(file); + + std::vprint_nonunicode(file, fmt, std::make_format_args(args...)); + std::fclose(file); + + std::ifstream stream{filename.c_str(), std::ios_base::in | std::ios_base::binary}; + std::string out(std::istreambuf_iterator{stream}, {}); + TEST_REQUIRE(out == expected, + TEST_WRITE_CONCATENATED( + "\nFormat string ", fmt, "\nExpected output ", expected, "\nActual output ", out, '\n')); +}; + +auto test_exception = []([[maybe_unused]] std::string_view what, + [[maybe_unused]] std::string_view fmt, + [[maybe_unused]] Args&&... args) { + FILE* file = fopen(filename.c_str(), "wb"); + assert(file); + + TEST_VALIDATE_EXCEPTION( + std::format_error, + [&]([[maybe_unused]] const std::format_error& e) { + TEST_LIBCPP_REQUIRE( + e.what() == what, + TEST_WRITE_CONCATENATED( + "\nFormat string ", fmt, "\nExpected exception ", what, "\nActual exception ", e.what(), '\n')); + }, + std::vprint_nonunicode(file, fmt, std::make_format_args(args...))); + + fclose(file); +}; + +#if defined(_POSIX_C_SOURCE) && _POSIX_C_SOURCE >= 200809L +// The FILE returned by fmemopen does not have file descriptor. +// This means the test could fail when the implementation uses a +// function that requires a file decriptor, for example write. +static void test_no_file_descriptor() { + std::array buffer{0}; + + FILE* file = fmemopen(buffer.data(), buffer.size(), "wb"); + assert(file); + + std::vprint_nonunicode(file, "hello world{}", std::make_format_args('!')); + long pos = std::ftell(file); + std::fclose(file); + + assert(pos > 0); + assert(std::string_view(buffer.data(), pos) == "hello world!"); +} +#endif // defined(_POSIX_C_SOURCE) && _POSIX_C_SOURCE >= 200809L + +static void test_wide_stream() { + FILE* file = fopen(filename.c_str(), "wb"); + assert(file); + + int mode = std::fwide(file, 1); + assert(mode > 0); + + TEST_VALIDATE_EXCEPTION( + std::system_error, + [&]([[maybe_unused]] const std::system_error& e) { + [[maybe_unused]] std::string_view what{"failed to write formatted output"}; + TEST_LIBCPP_REQUIRE( + e.what() == what, + TEST_WRITE_CONCATENATED("\nExpected exception ", what, "\nActual exception ", e.what(), '\n')); + }, + std::vprint_nonunicode(file, "hello", std::make_format_args())); +} + +static void test_read_only() { + FILE* file = fopen(filename.c_str(), "r"); + assert(file); + + TEST_VALIDATE_EXCEPTION( + std::system_error, + [&]([[maybe_unused]] const std::system_error& e) { + [[maybe_unused]] std::string_view what{"failed to write formatted output: Operation not permitted"}; + TEST_LIBCPP_REQUIRE( + e.what() == what, + TEST_WRITE_CONCATENATED("\nExpected exception ", what, "\nActual exception ", e.what(), '\n')); + }, + std::vprint_nonunicode(file, "hello", std::make_format_args())); +} + +static void test_new_line() { + // Text does newline translation. + { + FILE* file = fopen(filename.c_str(), "w"); + assert(file); + + std::vprint_nonunicode(file, "\n", std::make_format_args()); +#ifndef _WIN32 + assert(std::ftell(file) == 1); +#else + assert(std::ftell(file) == 2); +#endif + } + // Binary no newline translation. + { + FILE* file = fopen(filename.c_str(), "wb"); + assert(file); + + std::vprint_nonunicode(file, "\n", std::make_format_args()); + assert(std::ftell(file) == 1); + } +} + +int main(int, char**) { + print_tests(test_file, test_exception); + +#if defined(_POSIX_C_SOURCE) && _POSIX_C_SOURCE >= 200809L + test_no_file_descriptor(); +#endif + + test_wide_stream(); + test_read_only(); + test_new_line(); + + return 0; +} diff --git a/libcxx/test/std/input.output/iostream.format/print.fun/vprint_unicode.file.pass.cpp b/libcxx/test/std/input.output/iostream.format/print.fun/vprint_unicode.file.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/input.output/iostream.format/print.fun/vprint_unicode.file.pass.cpp @@ -0,0 +1,164 @@ +//===----------------------------------------------------------------------===// +// 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, c++17, c++20 +// UNSUPPORTED: libcpp-has-no-incomplete-format +// UNSUPPORTED: no-fstream + +// XFAIL: availability-fp_to_chars-missing + +// + +// void vprint_unicode(FILE* stream, string_view fmt, format_args args); + +// In the library when the stdout is redirected to a file it is no +// longer considered a terminal and the special terminal handling is no +// longer executed. There are tests in +// libcxx/test/libcxx/input.output/iostream.format/print.fun/ +// to validate that behaviour + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "assert_macros.h" +#include "concat_macros.h" +#include "filesystem_test_helper.h" +#include "print_tests.h" +#include "test_macros.h" + +scoped_test_env env; +std::string filename = env.create_file("output.txt"); + +auto test_file = [](std::string_view expected, std::string_view fmt, Args&&... args) { + FILE* file = fopen(filename.c_str(), "wb"); + assert(file); + + std::vprint_unicode(file, fmt, std::make_format_args(args...)); + std::fclose(file); + + std::ifstream stream{filename.c_str(), std::ios_base::in | std::ios_base::binary}; + std::string out(std::istreambuf_iterator{stream}, {}); + TEST_REQUIRE(out == expected, + TEST_WRITE_CONCATENATED( + "\nFormat string ", fmt, "\nExpected output ", expected, "\nActual output ", out, '\n')); +}; + +auto test_exception = []([[maybe_unused]] std::string_view what, + [[maybe_unused]] std::string_view fmt, + [[maybe_unused]] Args&&... args) { + FILE* file = fopen(filename.c_str(), "wb"); + assert(file); + + TEST_VALIDATE_EXCEPTION( + std::format_error, + [&]([[maybe_unused]] const std::format_error& e) { + TEST_LIBCPP_REQUIRE( + e.what() == what, + TEST_WRITE_CONCATENATED( + "\nFormat string ", fmt, "\nExpected exception ", what, "\nActual exception ", e.what(), '\n')); + }, + std::vprint_unicode(file, fmt, std::make_format_args(args...))); + + fclose(file); +}; + +#if defined(_POSIX_C_SOURCE) && _POSIX_C_SOURCE >= 200809L +// The FILE returned by fmemopen does not have file descriptor. +// This means the test could fail when the implementation uses a +// function that requires a file decriptor, for example write. +static void test_no_file_descriptor() { + std::array buffer{0}; + + FILE* file = fmemopen(buffer.data(), buffer.size(), "wb"); + assert(file); + + std::vprint_unicode(file, "hello world{}", std::make_format_args('!')); + long pos = std::ftell(file); + std::fclose(file); + + assert(pos > 0); + assert(std::string_view(buffer.data(), pos) == "hello world!"); +} +#endif // defined(_POSIX_C_SOURCE) && _POSIX_C_SOURCE >= 200809L + +static void test_wide_stream() { + FILE* file = fopen(filename.c_str(), "wb"); + assert(file); + + int mode = std::fwide(file, 1); + assert(mode > 0); + + TEST_VALIDATE_EXCEPTION( + std::system_error, + [&]([[maybe_unused]] const std::system_error& e) { + [[maybe_unused]] std::string_view what{"failed to write formatted output"}; + TEST_LIBCPP_REQUIRE( + e.what() == what, + TEST_WRITE_CONCATENATED("\nExpected exception ", what, "\nActual exception ", e.what(), '\n')); + }, + std::vprint_unicode(file, "hello", std::make_format_args())); +} + +static void test_read_only() { + FILE* file = fopen(filename.c_str(), "r"); + assert(file); + + TEST_VALIDATE_EXCEPTION( + std::system_error, + [&]([[maybe_unused]] const std::system_error& e) { + [[maybe_unused]] std::string_view what{"failed to write formatted output: Operation not permitted"}; + TEST_LIBCPP_REQUIRE( + e.what() == what, + TEST_WRITE_CONCATENATED("\nExpected exception ", what, "\nActual exception ", e.what(), '\n')); + }, + std::vprint_unicode(file, "hello", std::make_format_args())); +} + +static void test_new_line() { + // Text does newline translation. + { + FILE* file = fopen(filename.c_str(), "w"); + assert(file); + + std::vprint_unicode(file, "\n", std::make_format_args()); +#ifndef _WIN32 + assert(std::ftell(file) == 1); +#else + assert(std::ftell(file) == 2); +#endif + } + // Binary no newline translation. + { + FILE* file = fopen(filename.c_str(), "wb"); + assert(file); + + std::vprint_unicode(file, "\n", std::make_format_args()); + assert(std::ftell(file) == 1); + } +} + +int main(int, char**) { + print_tests(test_file, test_exception); + +#if defined(_POSIX_C_SOURCE) && _POSIX_C_SOURCE >= 200809L + test_no_file_descriptor(); +#endif + + test_wide_stream(); + test_read_only(); + test_new_line(); + + return 0; +} diff --git a/libcxx/test/std/language.support/support.limits/support.limits.general/ostream.version.compile.pass.cpp b/libcxx/test/std/language.support/support.limits/support.limits.general/ostream.version.compile.pass.cpp --- a/libcxx/test/std/language.support/support.limits/support.limits.general/ostream.version.compile.pass.cpp +++ b/libcxx/test/std/language.support/support.limits/support.limits.general/ostream.version.compile.pass.cpp @@ -19,6 +19,7 @@ /* Constant Value __cpp_lib_char8_t 201907L [C++20] + __cpp_lib_print 202207L [C++2b] */ #include @@ -30,18 +31,30 @@ # error "__cpp_lib_char8_t should not be defined before c++20" # endif +# ifdef __cpp_lib_print +# error "__cpp_lib_print should not be defined before c++2b" +# endif + #elif TEST_STD_VER == 14 # ifdef __cpp_lib_char8_t # error "__cpp_lib_char8_t should not be defined before c++20" # endif +# ifdef __cpp_lib_print +# error "__cpp_lib_print should not be defined before c++2b" +# endif + #elif TEST_STD_VER == 17 # ifdef __cpp_lib_char8_t # error "__cpp_lib_char8_t should not be defined before c++20" # endif +# ifdef __cpp_lib_print +# error "__cpp_lib_print should not be defined before c++2b" +# endif + #elif TEST_STD_VER == 20 # if defined(__cpp_char8_t) @@ -57,6 +70,10 @@ # endif # endif +# ifdef __cpp_lib_print +# error "__cpp_lib_print should not be defined before c++2b" +# endif + #elif TEST_STD_VER > 20 # if defined(__cpp_char8_t) @@ -72,5 +89,18 @@ # endif # endif +# if !defined(_LIBCPP_VERSION) +# ifndef __cpp_lib_print +# error "__cpp_lib_print should be defined in c++2b" +# endif +# if __cpp_lib_print != 202207L +# error "__cpp_lib_print should have the value 202207L in c++2b" +# endif +# else // _LIBCPP_VERSION +# ifdef __cpp_lib_print +# error "__cpp_lib_print should not be defined because it is unimplemented in libc++!" +# endif +# endif + #endif // TEST_STD_VER > 20 diff --git a/libcxx/test/std/language.support/support.limits/support.limits.general/print.version.compile.pass.cpp b/libcxx/test/std/language.support/support.limits/support.limits.general/print.version.compile.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/language.support/support.limits/support.limits.general/print.version.compile.pass.cpp @@ -0,0 +1,65 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// +// +// WARNING: This test was generated by generate_feature_test_macro_components.py +// and should not be edited manually. +// +// clang-format off + +// + +// Test the feature test macros defined by + +/* Constant Value + __cpp_lib_print 202207L [C++2b] +*/ + +#include +#include "test_macros.h" + +#if TEST_STD_VER < 14 + +# ifdef __cpp_lib_print +# error "__cpp_lib_print should not be defined before c++2b" +# endif + +#elif TEST_STD_VER == 14 + +# ifdef __cpp_lib_print +# error "__cpp_lib_print should not be defined before c++2b" +# endif + +#elif TEST_STD_VER == 17 + +# ifdef __cpp_lib_print +# error "__cpp_lib_print should not be defined before c++2b" +# endif + +#elif TEST_STD_VER == 20 + +# ifdef __cpp_lib_print +# error "__cpp_lib_print should not be defined before c++2b" +# endif + +#elif TEST_STD_VER > 20 + +# if !defined(_LIBCPP_VERSION) +# ifndef __cpp_lib_print +# error "__cpp_lib_print should be defined in c++2b" +# endif +# if __cpp_lib_print != 202207L +# error "__cpp_lib_print should have the value 202207L in c++2b" +# endif +# else // _LIBCPP_VERSION +# ifdef __cpp_lib_print +# error "__cpp_lib_print should not be defined because it is unimplemented in libc++!" +# endif +# endif + +#endif // TEST_STD_VER > 20 + diff --git a/libcxx/test/std/language.support/support.limits/support.limits.general/version.version.compile.pass.cpp b/libcxx/test/std/language.support/support.limits/support.limits.general/version.version.compile.pass.cpp --- a/libcxx/test/std/language.support/support.limits/support.limits.general/version.version.compile.pass.cpp +++ b/libcxx/test/std/language.support/support.limits/support.limits.general/version.version.compile.pass.cpp @@ -129,6 +129,7 @@ __cpp_lib_out_ptr 202106L [C++2b] __cpp_lib_parallel_algorithm 201603L [C++17] __cpp_lib_polymorphic_allocator 201902L [C++20] + __cpp_lib_print 202207L [C++2b] __cpp_lib_quoted_string_io 201304L [C++14] __cpp_lib_ranges 202106L [C++20] __cpp_lib_ranges_as_rvalue 202207L [C++2b] @@ -629,6 +630,10 @@ # error "__cpp_lib_polymorphic_allocator should not be defined before c++20" # endif +# ifdef __cpp_lib_print +# error "__cpp_lib_print should not be defined before c++2b" +# endif + # ifdef __cpp_lib_quoted_string_io # error "__cpp_lib_quoted_string_io should not be defined before c++14" # endif @@ -1316,6 +1321,10 @@ # error "__cpp_lib_polymorphic_allocator should not be defined before c++20" # endif +# ifdef __cpp_lib_print +# error "__cpp_lib_print should not be defined before c++2b" +# endif + # ifndef __cpp_lib_quoted_string_io # error "__cpp_lib_quoted_string_io should be defined in c++14" # endif @@ -2171,6 +2180,10 @@ # error "__cpp_lib_polymorphic_allocator should not be defined before c++20" # endif +# ifdef __cpp_lib_print +# error "__cpp_lib_print should not be defined before c++2b" +# endif + # ifndef __cpp_lib_quoted_string_io # error "__cpp_lib_quoted_string_io should be defined in c++17" # endif @@ -3293,6 +3306,10 @@ # error "__cpp_lib_polymorphic_allocator should have the value 201902L in c++20" # endif +# ifdef __cpp_lib_print +# error "__cpp_lib_print should not be defined before c++2b" +# endif + # ifndef __cpp_lib_quoted_string_io # error "__cpp_lib_quoted_string_io should be defined in c++20" # endif @@ -4583,6 +4600,19 @@ # error "__cpp_lib_polymorphic_allocator should have the value 201902L in c++2b" # endif +# if !defined(_LIBCPP_VERSION) +# ifndef __cpp_lib_print +# error "__cpp_lib_print should be defined in c++2b" +# endif +# if __cpp_lib_print != 202207L +# error "__cpp_lib_print should have the value 202207L in c++2b" +# endif +# else // _LIBCPP_VERSION +# ifdef __cpp_lib_print +# error "__cpp_lib_print should not be defined because it is unimplemented in libc++!" +# endif +# endif + # ifndef __cpp_lib_quoted_string_io # error "__cpp_lib_quoted_string_io should be defined in c++2b" # endif diff --git a/libcxx/utils/ci/run-buildbot b/libcxx/utils/ci/run-buildbot --- a/libcxx/utils/ci/run-buildbot +++ b/libcxx/utils/ci/run-buildbot @@ -237,6 +237,7 @@ --exclude 'ostream.pass.cpp' \ --exclude 'std_format_spec_string_unicode.bench.cpp' \ --exclude 'transcoding.pass.cpp' \ + --exclude 'vprint_unicode_windows.pass.cpp' \ --exclude 'underflow.pass.cpp' \ || false diff --git a/libcxx/utils/data/ignore_format.txt b/libcxx/utils/data/ignore_format.txt --- a/libcxx/utils/data/ignore_format.txt +++ b/libcxx/utils/data/ignore_format.txt @@ -824,6 +824,7 @@ libcxx/src/mutex_destructor.cpp libcxx/src/new.cpp libcxx/src/optional.cpp +libcxx/src/print.cpp libcxx/src/random.cpp libcxx/src/random_shuffle.cpp libcxx/src/regex.cpp diff --git a/libcxx/utils/generate_feature_test_macro_components.py b/libcxx/utils/generate_feature_test_macro_components.py --- a/libcxx/utils/generate_feature_test_macro_components.py +++ b/libcxx/utils/generate_feature_test_macro_components.py @@ -541,6 +541,13 @@ "name": "__cpp_lib_polymorphic_allocator", "values": { "c++20": 201902 }, "headers": ["memory_resource"], + }, { + "name": "__cpp_lib_print", + "values": { "c++2b": 202207, }, + "headers": ["ostream", "print"], + "test_suite_guard": "!defined(_LIBCPP_HAS_NO_INCOMPLETE_FORMAT)", + "libcxx_guard": "!defined(_LIBCPP_HAS_NO_INCOMPLETE_FORMAT)", + "unimplemented": True, }, { "name": "__cpp_lib_quoted_string_io", "values": { "c++14": 201304 },