| 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 | // ADDITIONAL_COMPILE_FLAGS: -D_LIBCPP_DISABLE_DEPRECATION_WARNINGS -D_LIBCPP_ENABLE_CXX26_REMOVED_CODECVT |
| 10 | // MSVC warning C4242: '+=': conversion from 'const _Ty' to 'size_t', possible loss of data |
| 11 | // MSVC warning C4244: 'argument': conversion from 'std::streamsize' to 'size_t', possible loss of data |
| 12 | // ADDITIONAL_COMPILE_FLAGS(cl-style-warnings): /wd4242 /wd4244 |
| 13 | // UNSUPPORTED: c++03 |
| 14 | |
| 15 | // <fstream> |
| 16 | |
| 17 | // This test checks the behavior of writing payloads of different sizes in different patterns. |
| 18 | // In particular, it was written to exercise code paths that deal with buffering inside the fstream |
| 19 | // implementation. |
| 20 | // |
| 21 | // For each test, we test various behaviors w.r.t. how the buffer is handled: |
| 22 | // - Provide a user-managed buffer to the library. In this case, we test the following corner-cases: |
| 23 | // + A 0-sized buffer. |
| 24 | // + A buffer size greater than and smaller than the payload size, which causes multiple buffer effects. |
| 25 | // Important values are +/- 1 byte from the payload size. |
| 26 | // - Let the library manage a buffer of a user-provided size 'n'. In this case, we test the following corner-cases: |
| 27 | // + A 0-sized buffer. |
| 28 | // + A buffer size greater than and smaller than the payload size, which causes multiple buffer effects. |
| 29 | // Important values are +/- 1 or 2 bytes from the payload size. |
| 30 | // + A buffer size smaller than 8 bytes. If pubsetbuf() is called with less than 8 bytes, the library will |
| 31 | // use __extbuf_min_ with 8 bytes instead of allocating anything. |
| 32 | // - Let the library manage a buffer, without specifying any size. In this case, the library will use the default |
| 33 | // buffer size of 4096 bytes. |
| 34 | |
| 35 | #include <cassert> |
| 36 | #include <codecvt> |
| 37 | #include <fstream> |
| 38 | #include <locale> |
| 39 | #include <numeric> |
| 40 | #include <string> |
| 41 | #include <vector> |
| 42 | |
| 43 | #include "../types.h" |
| 44 | #include "assert_macros.h" |
| 45 | #include "platform_support.h" |
| 46 | #include "test_macros.h" |
| 47 | |
| 48 | template <class BufferPolicy> |
| 49 | void test_write(BufferPolicy policy, const std::vector<std::streamsize>& payload_sizes) { |
| 50 | std::size_t previously_written = 0; |
| 51 | std::streamsize total_size = std::accumulate(first: payload_sizes.begin(), last: payload_sizes.end(), init: std::streamsize{0}); |
| 52 | std::vector<char> data(total_size); |
| 53 | for (std::size_t i = 0; i < data.size(); ++i) { |
| 54 | data[i] = static_cast<char>(i % (1 << 8 * sizeof(char))); |
| 55 | } |
| 56 | std::string p = get_temp_file_name(); |
| 57 | { |
| 58 | std::ofstream ofs; |
| 59 | policy(ofs); |
| 60 | ofs.open(s: p, mode: std::ios::out | std::ios::binary); |
| 61 | assert(ofs.is_open()); |
| 62 | for (const auto& payload_sz : payload_sizes) { |
| 63 | ofs.write(s: data.data() + previously_written, n: payload_sz); |
| 64 | assert(!ofs.fail()); |
| 65 | // test that the user's out_buffer buffer was not modified by write() |
| 66 | for (std::streamsize j = 0; j < payload_sz; ++j) { |
| 67 | char exp = (previously_written + j) % (1 << 8 * sizeof(char)); |
| 68 | TEST_REQUIRE(data[previously_written + j] == exp, [&] { |
| 69 | test_eprintf( |
| 70 | "failed after write() at offset %zu (offset %zu in chunk size %zu): got=%x, expected=%x\n" , |
| 71 | previously_written + j, |
| 72 | j, |
| 73 | payload_sz, |
| 74 | data[previously_written + j], |
| 75 | exp); |
| 76 | }); |
| 77 | } |
| 78 | previously_written += payload_sz; |
| 79 | } |
| 80 | ofs.close(); |
| 81 | } |
| 82 | { // verify contents after reading the file back |
| 83 | std::ifstream ifs(p.c_str(), std::ios::ate | std::ios::binary); |
| 84 | const std::streamsize in_sz = ifs.tellg(); |
| 85 | TEST_REQUIRE(in_sz == total_size, [&] { test_eprintf("out_sz = %zu, in_sz = %ld\n" , total_size, in_sz); }); |
| 86 | std::vector<char> in_buffer(total_size); |
| 87 | ifs.seekg(0, std::ios::beg); |
| 88 | assert(ifs.read(in_buffer.data(), total_size)); |
| 89 | for (std::size_t i = 0; i < in_buffer.size(); ++i) { |
| 90 | char exp = i % (1 << 8 * sizeof(char)); |
| 91 | TEST_REQUIRE(in_buffer[i] == exp, [&] { |
| 92 | test_eprintf("failed after read() at offset %zu: got=%x, expected=%x\n" , i, in_buffer[i], exp); |
| 93 | }); |
| 94 | } |
| 95 | } |
| 96 | std::remove(filename: p.c_str()); |
| 97 | } |
| 98 | |
| 99 | #ifndef TEST_HAS_NO_WIDE_CHARACTERS |
| 100 | template <class BufferPolicy> |
| 101 | void test_write_codecvt(BufferPolicy policy, const std::vector<std::streamsize>& payload_sizes) { |
| 102 | std::size_t previously_written = 0; |
| 103 | std::streamsize total_size = std::accumulate(first: payload_sizes.begin(), last: payload_sizes.end(), init: std::streamsize{0}); |
| 104 | std::vector<wchar_t> data(total_size); |
| 105 | for (std::size_t i = 0; i < data.size(); ++i) { |
| 106 | data[i] = static_cast<wchar_t>(i); |
| 107 | } |
| 108 | std::string p = get_temp_file_name(); |
| 109 | { |
| 110 | std::wofstream ofs; |
| 111 | ofs.imbue(loc: std::locale(std::locale::classic(), new std::codecvt_utf8<wchar_t>)); |
| 112 | policy(ofs); |
| 113 | ofs.open(s: p, mode: std::ios::out | std::ios::binary); |
| 114 | assert(ofs.is_open()); |
| 115 | for (const auto& payload_sz : payload_sizes) { |
| 116 | ofs.write(s: data.data() + previously_written, n: payload_sz); |
| 117 | assert(!ofs.fail()); |
| 118 | // test that the user's out_buffer buffer was not modified by write() |
| 119 | for (std::streamsize j = 0; j < payload_sz; ++j) { |
| 120 | wchar_t exp = static_cast<wchar_t>(previously_written + j); |
| 121 | TEST_REQUIRE(data[previously_written + j] == exp, [&] { |
| 122 | test_eprintf( |
| 123 | "failed after write() at offset %zu (offset %zu in chunk size %zu): got=%x, expected=%x\n" , |
| 124 | previously_written + j, |
| 125 | j, |
| 126 | payload_sz, |
| 127 | data[previously_written + j], |
| 128 | exp); |
| 129 | }); |
| 130 | } |
| 131 | previously_written += payload_sz; |
| 132 | } |
| 133 | ofs.close(); |
| 134 | } |
| 135 | { // verify contents after reading the file back |
| 136 | std::wifstream ifs(p.c_str(), std::ios::in | std::ios::binary); |
| 137 | ifs.imbue(loc: std::locale(std::locale::classic(), new std::codecvt_utf8<wchar_t>)); |
| 138 | std::vector<wchar_t> in_buffer(total_size); |
| 139 | assert(ifs.read(in_buffer.data(), total_size)); |
| 140 | for (std::size_t i = 0; i < in_buffer.size(); ++i) { |
| 141 | wchar_t exp = static_cast<wchar_t>(i); |
| 142 | TEST_REQUIRE(in_buffer[i] == exp, [&] { |
| 143 | test_eprintf("failed after read() at offset %zu: got=%x, expected=%x\n" , i, in_buffer[i], exp); |
| 144 | }); |
| 145 | } |
| 146 | } |
| 147 | std::remove(filename: p.c_str()); |
| 148 | } |
| 149 | #endif |
| 150 | |
| 151 | const std::vector<std::streamsize> buffer_sizes{0L, 3L, 8L, 9L, 11L}; |
| 152 | const std::vector<std::streamsize> io_sizes{0L, 1L, 2L, 3L, 4L, 9L, 10L, 11L, 12L, 13L, 21L, 22L, 23L}; |
| 153 | const std::vector<std::streamsize> io_sizes_default{ |
| 154 | 0L, 1L, 2L, 3L, 4L, 4094L, 4095L, 4096L, 4097L, 4098L, 8190L, 8191L, 8192L, 8193L, 8194L}; |
| 155 | |
| 156 | // Test single write operations |
| 157 | void test_1_write() { |
| 158 | // with default library buffer size: 4096b |
| 159 | for (std::streamsize x : io_sizes_default) { |
| 160 | test_write(policy: LibraryDefaultBuffer(), payload_sizes: {x}); |
| 161 | #ifndef TEST_HAS_NO_WIDE_CHARACTERS |
| 162 | test_write_codecvt(policy: LibraryDefaultBuffer(), payload_sizes: {x}); |
| 163 | #endif |
| 164 | } |
| 165 | |
| 166 | // with the library-managed buffer of given size |
| 167 | for (std::streamsize b : buffer_sizes) { |
| 168 | for (std::streamsize x : io_sizes) { |
| 169 | test_write(policy: LibraryManagedBuffer(b), payload_sizes: {x}); |
| 170 | #ifndef TEST_HAS_NO_WIDE_CHARACTERS |
| 171 | test_write_codecvt(policy: LibraryManagedBuffer(b), payload_sizes: {x}); |
| 172 | #endif |
| 173 | } |
| 174 | } |
| 175 | |
| 176 | // with the user-managed buffer of given size |
| 177 | for (std::streamsize b : buffer_sizes) { |
| 178 | for (std::streamsize x : io_sizes) { |
| 179 | test_write(policy: UserManagedBuffer(b), payload_sizes: {x}); |
| 180 | #ifndef TEST_HAS_NO_WIDE_CHARACTERS |
| 181 | test_write_codecvt(policy: UserManagedBuffer(b), payload_sizes: {x}); |
| 182 | #endif |
| 183 | } |
| 184 | } |
| 185 | } |
| 186 | |
| 187 | // Test two write operations |
| 188 | void test_2_writes() { |
| 189 | // with default library buffer size: 4096b |
| 190 | for (std::streamsize a : io_sizes_default) { |
| 191 | for (std::streamsize b : io_sizes_default) { |
| 192 | test_write(policy: LibraryDefaultBuffer(), payload_sizes: {a, b}); |
| 193 | #ifndef TEST_HAS_NO_WIDE_CHARACTERS |
| 194 | test_write_codecvt(policy: LibraryDefaultBuffer(), payload_sizes: {a, b}); |
| 195 | #endif |
| 196 | } |
| 197 | } |
| 198 | |
| 199 | // with the library-managed buffer of given size |
| 200 | for (std::streamsize buf : buffer_sizes) { |
| 201 | for (std::streamsize a : io_sizes) { |
| 202 | for (std::streamsize b : io_sizes) { |
| 203 | test_write(policy: LibraryManagedBuffer(buf), payload_sizes: {a, b}); |
| 204 | #ifndef TEST_HAS_NO_WIDE_CHARACTERS |
| 205 | test_write_codecvt(policy: LibraryManagedBuffer(buf), payload_sizes: {a, b}); |
| 206 | #endif |
| 207 | } |
| 208 | } |
| 209 | } |
| 210 | |
| 211 | // with the user-managed buffer of given size |
| 212 | for (std::streamsize buf : buffer_sizes) { |
| 213 | for (std::streamsize a : io_sizes) { |
| 214 | for (std::streamsize b : io_sizes) { |
| 215 | test_write(policy: UserManagedBuffer(buf), payload_sizes: {a, b}); |
| 216 | #ifndef TEST_HAS_NO_WIDE_CHARACTERS |
| 217 | test_write_codecvt(policy: UserManagedBuffer(buf), payload_sizes: {a, b}); |
| 218 | #endif |
| 219 | } |
| 220 | } |
| 221 | } |
| 222 | } |
| 223 | |
| 224 | int main(int, char**) { |
| 225 | test_1_write(); |
| 226 | test_2_writes(); |
| 227 | return 0; |
| 228 | } |
| 229 | |