| 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, c++17, c++20 |
| 10 | // UNSUPPORTED: no-filesystem |
| 11 | // UNSUPPORTED: libcpp-has-no-unicode |
| 12 | // UNSUPPORTED: GCC-ALWAYS_INLINE-FIXME |
| 13 | |
| 14 | // TODO PRINT Enable again |
| 15 | // https://reviews.llvm.org/D150044 |
| 16 | // https://lab.llvm.org/buildbot/#/builders/237/builds/3578 |
| 17 | // UNSUPPORTED: asan, hwasan, msan |
| 18 | |
| 19 | // XFAIL: availability-fp_to_chars-missing |
| 20 | |
| 21 | // The error exception has no system error string. |
| 22 | // XFAIL: LIBCXX-ANDROID-FIXME |
| 23 | |
| 24 | // <print> |
| 25 | |
| 26 | // void vprint_unicode(FILE* stream, string_view fmt, format_args args); |
| 27 | |
| 28 | // In the library when the stdout is redirected to a file it is no |
| 29 | // longer considered a terminal and the special terminal handling is no |
| 30 | // longer executed. There are tests in |
| 31 | // libcxx/test/libcxx/input.output/iostream.format/print.fun/ |
| 32 | // to validate that behaviour |
| 33 | |
| 34 | #include <algorithm> |
| 35 | #include <array> |
| 36 | #include <cassert> |
| 37 | #include <cstddef> |
| 38 | #include <cstdio> |
| 39 | #include <fstream> |
| 40 | #include <iterator> |
| 41 | #include <print> |
| 42 | #include <string_view> |
| 43 | |
| 44 | #include "assert_macros.h" |
| 45 | #include "concat_macros.h" |
| 46 | #include "filesystem_test_helper.h" |
| 47 | #include "print_tests.h" |
| 48 | #include "test_macros.h" |
| 49 | |
| 50 | scoped_test_env env; |
| 51 | std::string filename = env.create_file("output.txt" ); |
| 52 | |
| 53 | auto test_file = []<class... Args>(std::string_view expected, std::string_view fmt, Args&&... args) { |
| 54 | FILE* file = fopen(filename: filename.c_str(), modes: "wb" ); |
| 55 | assert(file); |
| 56 | |
| 57 | std::vprint_unicode(file, fmt, std::make_format_args(args...)); |
| 58 | std::fclose(stream: file); |
| 59 | |
| 60 | std::ifstream stream{filename.c_str(), std::ios_base::in | std::ios_base::binary}; |
| 61 | std::string out(std::istreambuf_iterator<char>{stream}, {}); |
| 62 | TEST_REQUIRE(out == expected, |
| 63 | TEST_WRITE_CONCATENATED( |
| 64 | "\nFormat string " , fmt, "\nExpected output " , expected, "\nActual output " , out, '\n')); |
| 65 | }; |
| 66 | |
| 67 | auto test_exception = []<class... Args>([[maybe_unused]] std::string_view what, |
| 68 | [[maybe_unused]] std::string_view fmt, |
| 69 | [[maybe_unused]] Args&&... args) { |
| 70 | FILE* file = fopen(filename: filename.c_str(), modes: "wb" ); |
| 71 | assert(file); |
| 72 | |
| 73 | TEST_VALIDATE_EXCEPTION( |
| 74 | std::format_error, |
| 75 | [&]([[maybe_unused]] const std::format_error& e) { |
| 76 | TEST_LIBCPP_REQUIRE( |
| 77 | e.what() == what, |
| 78 | TEST_WRITE_CONCATENATED( |
| 79 | "\nFormat string " , fmt, "\nExpected exception " , what, "\nActual exception " , e.what(), '\n')); |
| 80 | }, |
| 81 | std::vprint_unicode(file, fmt, std::make_format_args(args...))); |
| 82 | |
| 83 | fclose(stream: file); |
| 84 | }; |
| 85 | |
| 86 | // Glibc fails writing to a wide stream. |
| 87 | #if defined(TEST_HAS_GLIBC) && !defined(TEST_HAS_NO_WIDE_CHARACTERS) |
| 88 | static void test_wide_stream() { |
| 89 | FILE* file = fopen(filename.c_str(), "wb" ); |
| 90 | assert(file); |
| 91 | |
| 92 | int mode = std::fwide(file, 1); |
| 93 | assert(mode > 0); |
| 94 | |
| 95 | TEST_VALIDATE_EXCEPTION( |
| 96 | std::system_error, |
| 97 | [&]([[maybe_unused]] const std::system_error& e) { |
| 98 | [[maybe_unused]] std::string_view what{"failed to write formatted output" }; |
| 99 | TEST_LIBCPP_REQUIRE( |
| 100 | e.what() == what, |
| 101 | TEST_WRITE_CONCATENATED("\nExpected exception " , what, "\nActual exception " , e.what(), '\n')); |
| 102 | }, |
| 103 | std::vprint_unicode(file, "hello" , std::make_format_args())); |
| 104 | } |
| 105 | #endif // defined(TEST_HAS_GLIBC) && !defined(TEST_HAS_NO_WIDE_CHARACTERS) |
| 106 | |
| 107 | static void test_read_only() { |
| 108 | FILE* file = fopen(filename: filename.c_str(), modes: "r" ); |
| 109 | assert(file); |
| 110 | |
| 111 | TEST_VALIDATE_EXCEPTION( |
| 112 | std::system_error, |
| 113 | [&]([[maybe_unused]] const std::system_error& e) { |
| 114 | [[maybe_unused]] std::string_view what{ |
| 115 | "failed to write formatted output: " TEST_IF_AIX("Broken pipe" , "Operation not permitted" )}; |
| 116 | TEST_LIBCPP_REQUIRE( |
| 117 | e.what() == what, |
| 118 | TEST_WRITE_CONCATENATED("\nExpected exception " , what, "\nActual exception " , e.what(), '\n')); |
| 119 | }, |
| 120 | std::vprint_unicode(file, "hello" , std::make_format_args())); |
| 121 | } |
| 122 | |
| 123 | static void test_new_line() { |
| 124 | // Text does newline translation. |
| 125 | { |
| 126 | FILE* file = fopen(filename: filename.c_str(), modes: "w" ); |
| 127 | assert(file); |
| 128 | |
| 129 | std::vprint_unicode(file, "\n" , std::make_format_args()); |
| 130 | #ifndef _WIN32 |
| 131 | assert(std::ftell(file) == 1); |
| 132 | #else |
| 133 | assert(std::ftell(file) == 2); |
| 134 | #endif |
| 135 | } |
| 136 | // Binary no newline translation. |
| 137 | { |
| 138 | FILE* file = fopen(filename: filename.c_str(), modes: "wb" ); |
| 139 | assert(file); |
| 140 | |
| 141 | std::vprint_unicode(file, "\n" , std::make_format_args()); |
| 142 | assert(std::ftell(file) == 1); |
| 143 | } |
| 144 | } |
| 145 | |
| 146 | int main(int, char**) { |
| 147 | print_tests(check: test_file, check_exception: test_exception); |
| 148 | |
| 149 | #if defined(TEST_HAS_GLIBC) && !defined(TEST_HAS_NO_WIDE_CHARACTERS) |
| 150 | test_wide_stream(); |
| 151 | #endif |
| 152 | test_read_only(); |
| 153 | test_new_line(); |
| 154 | |
| 155 | return 0; |
| 156 | } |
| 157 | |