diff --git a/libcxx/include/fstream b/libcxx/include/fstream --- a/libcxx/include/fstream +++ b/libcxx/include/fstream @@ -912,7 +912,7 @@ if (!__always_noconv_) { __ibs_ = max(__n, sizeof(__extbuf_min_)); - if (__s && __ibs_ >= sizeof(__extbuf_min_)) + if (__s && __ibs_ > sizeof(__extbuf_min_)) { __intbuf_ = __s; __owns_ib_ = false; diff --git a/libcxx/test/std/input.output/file.streams/fstreams/ifstream.members/buffered_reads.pass.cpp b/libcxx/test/std/input.output/file.streams/fstreams/ifstream.members/buffered_reads.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/input.output/file.streams/fstreams/ifstream.members/buffered_reads.pass.cpp @@ -0,0 +1,227 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +// ADDITIONAL_COMPILE_FLAGS: -D_LIBCPP_DISABLE_DEPRECATION_WARNINGS +// UNSUPPORTED: c++03 + +// + +// This test checks the behavior of reading payloads of different sizes in different patterns. +// In particular, it was written to exercise code paths that deal with buffering inside the fstream +// implementation. +// +// For each test, we test various behaviors w.r.t. how the buffer is handled: +// - Provide a user-managed buffer to the library. In this case, we test the following corner-cases: +// + A 0-sized buffer. +// + A buffer size greater than and smaller than the payload size, which causes multiple buffer effects. +// Important values are +/- 1 byte from the payload size. +// - Let the library manage a buffer of a user-provided size 'n'. In this case, we test the following corner-cases: +// + A 0-sized buffer. +// + A buffer size greater than and smaller than the payload size, which causes multiple buffer effects. +// Important values are +/- 1 or 2 bytes from the payload size. +// + A buffer size smaller than 8 bytes. If pubsetbuf() is called with less than 8 bytes, the library will +// use __extbuf_min_ with 8 bytes instead of allocating anything. +// - Let the library manage a buffer, without specifying any size. In this case, the library will use the default +// buffer size of 4096 bytes. + +#include +#include +#include +#include +#include +#include +#include + +#include "../types.h" +#include "assert_macros.h" +#include "platform_support.h" +#include "test_macros.h" + +template +void test_read(BufferPolicy policy, const std::vector& payload_sizes) { + std::streamsize total_size = std::accumulate(payload_sizes.begin(), payload_sizes.end(), 0); + std::vector data(total_size); + for (std::size_t i = 0; i < data.size(); ++i) { + data[i] = static_cast(i % (1 << 8 * sizeof(char))); + } + std::string p = get_temp_file_name(); + { + std::ofstream ofs; + ofs.open(p); + assert(ofs.is_open()); + ofs.write(data.data(), data.size()); + assert(!ofs.fail()); + // test that the user's out_buffer buffer was not modified by write() + for (std::streamsize j = 0; j < total_size; ++j) { + char exp = j % (1 << 8 * sizeof(char)); + TEST_REQUIRE(data[j] == exp, [&] { + test_eprintf("failed after write() at offset %zu: got=%x, expected=%x\n", j, data[j], exp); + }); + } + ofs.close(); + } + { + std::ifstream ifs; + policy(ifs); + ifs.open(p, std::ios::ate); + assert(ifs.is_open()); + const std::streamsize in_sz = ifs.tellg(); + TEST_REQUIRE(in_sz == total_size, [&] { test_eprintf("out_sz = %zu, in_sz = %ld\n", total_size, in_sz); }); + ifs.seekg(0, std::ios::beg); + std::size_t previously_read = 0; + std::vector in_buffer(total_size); + for (const auto& payload_sz : payload_sizes) { + ifs.read(in_buffer.data() + previously_read, payload_sz); + assert(ifs); + for (std::streamsize j = 0; j < payload_sz; ++j) { + char exp = (previously_read + j) % (1 << 8 * sizeof(char)); + TEST_REQUIRE(in_buffer[previously_read + j] == exp, [&] { + test_eprintf( + "failed after read() at offset %zu (offset %zu in chunk size %zu): got=%x, expected=%x\n", + previously_read + j, + j, + payload_sz, + in_buffer[previously_read + j], + exp); + }); + } + previously_read += payload_sz; + } + } + std::remove(p.c_str()); +} + +#ifndef TEST_HAS_NO_WIDE_CHARACTERS +template +void test_read_codecvt(BufferPolicy policy, const std::vector& payload_sizes) { + std::streamsize total_size = std::accumulate(payload_sizes.begin(), payload_sizes.end(), 0); + std::vector data(total_size); + for (std::size_t i = 0; i < data.size(); ++i) { + data[i] = static_cast(i); + } + std::string p = get_temp_file_name(); + { + std::wofstream ofs; + ofs.imbue(std::locale(std::locale::classic(), new std::codecvt_utf8)); + ofs.open(p); + assert(ofs.is_open()); + ofs.write(data.data(), data.size()); + assert(!ofs.fail()); + // test that the user's out_buffer buffer was not modified by write() + for (std::streamsize j = 0; j < total_size; ++j) { + wchar_t exp = static_cast(j); + TEST_REQUIRE(data[j] == exp, [&] { + test_eprintf("failed after write() at offset %zu: got=%x, expected=%x\n", j, data[j], exp); + }); + } + ofs.close(); + } + { + std::wifstream ifs; + ifs.imbue(std::locale(std::locale::classic(), new std::codecvt_utf8)); + policy(ifs); + ifs.open(p); + assert(ifs.is_open()); + ifs.seekg(0, std::ios::beg); + std::size_t previously_read = 0; + std::vector in_buffer(total_size); + for (const auto& payload_sz : payload_sizes) { + assert(ifs.read(in_buffer.data() + previously_read, payload_sz)); + assert(ifs); + for (std::streamsize j = 0; j < payload_sz; ++j) { + wchar_t exp = static_cast(j + previously_read); + TEST_REQUIRE(in_buffer[j + previously_read] == exp, [&] { + test_eprintf( + "failed after read() at offset %zu: got=%x, expected=%x\n", j, in_buffer[j + previously_read], exp); + }); + } + previously_read += payload_sz; + } + } + std::remove(p.c_str()); +} +#endif + +const std::vector buffer_sizes{0L, 3L, 8L, 9L, 11L}; +const std::vector io_sizes{0L, 1L, 2L, 3L, 4L, 9L, 10L, 11L, 12L, 13L, 21L, 22L, 23L}; +const std::vector io_sizes_default{ + 0L, 1L, 2L, 3L, 4L, 4094L, 4095L, 4096L, 4097L, 4098L, 8190L, 8191L, 8192L, 8193L, 8194L}; + +// Test single read operations +void test_1_read() { + // with default library buffer size: 4096b + for (std::streamsize x : io_sizes_default) { + test_read(LibraryDefaultBuffer(), {x}); +#ifndef TEST_HAS_NO_WIDE_CHARACTERS + test_read_codecvt(LibraryDefaultBuffer(), {x}); +#endif + } + + // with the library-managed buffer of given size + for (std::streamsize b : buffer_sizes) { + for (std::streamsize x : io_sizes) { + test_read(LibraryManagedBuffer(b), {x}); +#ifndef TEST_HAS_NO_WIDE_CHARACTERS + test_read_codecvt(LibraryManagedBuffer(b), {x}); +#endif + } + } + + // with the user-managed buffer of given size + for (std::streamsize b : buffer_sizes) { + for (std::streamsize x : io_sizes) { + test_read(UserManagedBuffer(b), {x}); +#ifndef TEST_HAS_NO_WIDE_CHARACTERS + test_read_codecvt(UserManagedBuffer(b), {x}); +#endif + } + } +} + +// Test two read operations +void test_2_reads() { + // with default library buffer size: 4096b + for (std::streamsize a : io_sizes_default) { + for (std::streamsize b : io_sizes_default) { + test_read(LibraryDefaultBuffer(), {a, b}); +#ifndef TEST_HAS_NO_WIDE_CHARACTERS + test_read_codecvt(LibraryDefaultBuffer(), {a, b}); +#endif + } + } + + // with the library-managed buffer of given size + for (std::streamsize buf : buffer_sizes) { + for (std::streamsize a : io_sizes) { + for (std::streamsize b : io_sizes) { + test_read(LibraryManagedBuffer(buf), {a, b}); +#ifndef TEST_HAS_NO_WIDE_CHARACTERS + test_read_codecvt(LibraryManagedBuffer(buf), {a, b}); +#endif + } + } + } + + // with the user-managed buffer of given size + for (std::streamsize buf : buffer_sizes) { + for (std::streamsize a : io_sizes) { + for (std::streamsize b : io_sizes) { + test_read(UserManagedBuffer(buf), {a, b}); +#ifndef TEST_HAS_NO_WIDE_CHARACTERS + test_read_codecvt(UserManagedBuffer(buf), {a, b}); +#endif + } + } + } +} + +int main(int, char**) { + test_1_read(); + test_2_reads(); + return 0; +} diff --git a/libcxx/test/std/input.output/file.streams/fstreams/ofstream.members/buffered_writes.pass.cpp b/libcxx/test/std/input.output/file.streams/fstreams/ofstream.members/buffered_writes.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/input.output/file.streams/fstreams/ofstream.members/buffered_writes.pass.cpp @@ -0,0 +1,225 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +// ADDITIONAL_COMPILE_FLAGS: -D_LIBCPP_DISABLE_DEPRECATION_WARNINGS +// UNSUPPORTED: c++03 + +// + +// This test checks the behavior of writing payloads of different sizes in different patterns. +// In particular, it was written to exercise code paths that deal with buffering inside the fstream +// implementation. +// +// For each test, we test various behaviors w.r.t. how the buffer is handled: +// - Provide a user-managed buffer to the library. In this case, we test the following corner-cases: +// + A 0-sized buffer. +// + A buffer size greater than and smaller than the payload size, which causes multiple buffer effects. +// Important values are +/- 1 byte from the payload size. +// - Let the library manage a buffer of a user-provided size 'n'. In this case, we test the following corner-cases: +// + A 0-sized buffer. +// + A buffer size greater than and smaller than the payload size, which causes multiple buffer effects. +// Important values are +/- 1 or 2 bytes from the payload size. +// + A buffer size smaller than 8 bytes. If pubsetbuf() is called with less than 8 bytes, the library will +// use __extbuf_min_ with 8 bytes instead of allocating anything. +// - Let the library manage a buffer, without specifying any size. In this case, the library will use the default +// buffer size of 4096 bytes. + +#include +#include +#include +#include +#include +#include +#include + +#include "../types.h" +#include "assert_macros.h" +#include "platform_support.h" +#include "test_macros.h" + +template +void test_write(BufferPolicy policy, const std::vector& payload_sizes) { + std::size_t previously_written = 0; + std::streamsize total_size = std::accumulate(payload_sizes.begin(), payload_sizes.end(), 0); + std::vector data(total_size); + for (std::size_t i = 0; i < data.size(); ++i) { + data[i] = static_cast(i % (1 << 8 * sizeof(char))); + } + std::string p = get_temp_file_name(); + { + std::ofstream ofs; + policy(ofs); + ofs.open(p); + assert(ofs.is_open()); + for (const auto& payload_sz : payload_sizes) { + ofs.write(data.data() + previously_written, payload_sz); + assert(!ofs.fail()); + // test that the user's out_buffer buffer was not modified by write() + for (std::streamsize j = 0; j < payload_sz; ++j) { + char exp = (previously_written + j) % (1 << 8 * sizeof(char)); + TEST_REQUIRE(data[previously_written + j] == exp, [&] { + test_eprintf( + "failed after write() at offset %zu (offset %zu in chunk size %zu): got=%x, expected=%x\n", + previously_written + j, + j, + payload_sz, + data[previously_written + j], + exp); + }); + } + previously_written += payload_sz; + } + ofs.close(); + } + { // verify contents after reading the file back + std::ifstream ifs(p.c_str(), std::ios::ate); + const std::streamsize in_sz = ifs.tellg(); + TEST_REQUIRE(in_sz == total_size, [&] { test_eprintf("out_sz = %zu, in_sz = %ld\n", total_size, in_sz); }); + std::vector in_buffer(total_size); + ifs.seekg(0, std::ios::beg); + assert(ifs.read(in_buffer.data(), total_size)); + for (std::size_t i = 0; i < in_buffer.size(); ++i) { + char exp = i % (1 << 8 * sizeof(char)); + TEST_REQUIRE(in_buffer[i] == exp, [&] { + test_eprintf("failed after read() at offset %zu: got=%x, expected=%x\n", i, in_buffer[i], exp); + }); + } + } + std::remove(p.c_str()); +} + +#ifndef TEST_HAS_NO_WIDE_CHARACTERS +template +void test_write_codecvt(BufferPolicy policy, const std::vector& payload_sizes) { + std::size_t previously_written = 0; + std::streamsize total_size = std::accumulate(payload_sizes.begin(), payload_sizes.end(), 0); + std::vector data(total_size); + for (std::size_t i = 0; i < data.size(); ++i) { + data[i] = static_cast(i); + } + std::string p = get_temp_file_name(); + { + std::wofstream ofs; + ofs.imbue(std::locale(std::locale::classic(), new std::codecvt_utf8)); + policy(ofs); + ofs.open(p); + assert(ofs.is_open()); + for (const auto& payload_sz : payload_sizes) { + ofs.write(data.data() + previously_written, payload_sz); + assert(!ofs.fail()); + // test that the user's out_buffer buffer was not modified by write() + for (std::streamsize j = 0; j < payload_sz; ++j) { + wchar_t exp = static_cast(previously_written + j); + TEST_REQUIRE(data[previously_written + j] == exp, [&] { + test_eprintf( + "failed after write() at offset %zu (offset %zu in chunk size %zu): got=%x, expected=%x\n", + previously_written + j, + j, + payload_sz, + data[previously_written + j], + exp); + }); + } + previously_written += payload_sz; + } + ofs.close(); + } + { // verify contents after reading the file back + std::wifstream ifs(p.c_str()); + ifs.imbue(std::locale(std::locale::classic(), new std::codecvt_utf8)); + std::vector in_buffer(total_size); + assert(ifs.read(in_buffer.data(), total_size)); + for (std::size_t i = 0; i < in_buffer.size(); ++i) { + wchar_t exp = static_cast(i); + TEST_REQUIRE(in_buffer[i] == exp, [&] { + test_eprintf("failed after read() at offset %zu: got=%x, expected=%x\n", i, in_buffer[i], exp); + }); + } + } + std::remove(p.c_str()); +} +#endif + +const std::vector buffer_sizes{0L, 3L, 8L, 9L, 11L}; +const std::vector io_sizes{0L, 1L, 2L, 3L, 4L, 9L, 10L, 11L, 12L, 13L, 21L, 22L, 23L}; +const std::vector io_sizes_default{ + 0L, 1L, 2L, 3L, 4L, 4094L, 4095L, 4096L, 4097L, 4098L, 8190L, 8191L, 8192L, 8193L, 8194L}; + +// Test single write operations +void test_1_write() { + // with default library buffer size: 4096b + for (std::streamsize x : io_sizes_default) { + test_write(LibraryDefaultBuffer(), {x}); +#ifndef TEST_HAS_NO_WIDE_CHARACTERS + test_write_codecvt(LibraryDefaultBuffer(), {x}); +#endif + } + + // with the library-managed buffer of given size + for (std::streamsize b : buffer_sizes) { + for (std::streamsize x : io_sizes) { + test_write(LibraryManagedBuffer(b), {x}); +#ifndef TEST_HAS_NO_WIDE_CHARACTERS + test_write_codecvt(LibraryManagedBuffer(b), {x}); +#endif + } + } + + // with the user-managed buffer of given size + for (std::streamsize b : buffer_sizes) { + for (std::streamsize x : io_sizes) { + test_write(UserManagedBuffer(b), {x}); +#ifndef TEST_HAS_NO_WIDE_CHARACTERS + test_write_codecvt(UserManagedBuffer(b), {x}); +#endif + } + } +} + +// Test two write operations +void test_2_writes() { + // with default library buffer size: 4096b + for (std::streamsize a : io_sizes_default) { + for (std::streamsize b : io_sizes_default) { + test_write(LibraryDefaultBuffer(), {a, b}); +#ifndef TEST_HAS_NO_WIDE_CHARACTERS + test_write_codecvt(LibraryDefaultBuffer(), {a, b}); +#endif + } + } + + // with the library-managed buffer of given size + for (std::streamsize buf : buffer_sizes) { + for (std::streamsize a : io_sizes) { + for (std::streamsize b : io_sizes) { + test_write(LibraryManagedBuffer(buf), {a, b}); +#ifndef TEST_HAS_NO_WIDE_CHARACTERS + test_write_codecvt(LibraryManagedBuffer(buf), {a, b}); +#endif + } + } + } + + // with the user-managed buffer of given size + for (std::streamsize buf : buffer_sizes) { + for (std::streamsize a : io_sizes) { + for (std::streamsize b : io_sizes) { + test_write(UserManagedBuffer(buf), {a, b}); +#ifndef TEST_HAS_NO_WIDE_CHARACTERS + test_write_codecvt(UserManagedBuffer(buf), {a, b}); +#endif + } + } + } +} + +int main(int, char**) { + test_1_write(); + test_2_writes(); + return 0; +} diff --git a/libcxx/test/std/input.output/file.streams/fstreams/types.h b/libcxx/test/std/input.output/file.streams/fstreams/types.h new file mode 100644 --- /dev/null +++ b/libcxx/test/std/input.output/file.streams/fstreams/types.h @@ -0,0 +1,83 @@ +//===----------------------------------------------------------------------===// +// +// 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_FILE_STREAMS_FSTREAMS_TYPES_H +#define TEST_STD_INPUT_OUTPUT_FILE_STREAMS_FSTREAMS_TYPES_H + +#include +#include +#include + +#include "test_macros.h" + +struct UserManagedBuffer { + UserManagedBuffer(std::size_t size) : size_(size) {} + + UserManagedBuffer(UserManagedBuffer const&) = delete; + UserManagedBuffer(UserManagedBuffer&&) = default; + + void operator()(std::basic_ofstream& stream) { + buffers_.emplace_back(new char[size_]); + stream.rdbuf()->pubsetbuf(buffers_.back(), size_); + } + void operator()(std::basic_ifstream& stream) { + buffers_.emplace_back(new char[size_]); + stream.rdbuf()->pubsetbuf(buffers_.back(), size_); + } + +#ifndef TEST_HAS_NO_WIDE_CHARACTERS + void operator()(std::basic_ofstream& stream) { + wbuffers_.emplace_back(new wchar_t[size_]); + stream.rdbuf()->pubsetbuf(wbuffers_.back(), size_); + } + void operator()(std::basic_ifstream& stream) { + wbuffers_.emplace_back(new wchar_t[size_]); + stream.rdbuf()->pubsetbuf(wbuffers_.back(), size_); + } +#endif + ~UserManagedBuffer() { + for (char* p : buffers_) + delete[] p; + +#ifndef TEST_HAS_NO_WIDE_CHARACTERS + for (wchar_t* p : wbuffers_) + delete[] p; +#endif + } + +private: + std::size_t size_; + std::vector buffers_; + std::vector wbuffers_; +}; + +struct LibraryManagedBuffer { + LibraryManagedBuffer(std::size_t size) : size_(size) {} + template + void operator()(std::basic_ofstream& stream) const { + stream.rdbuf()->pubsetbuf(nullptr, size_); + } + + template + void operator()(std::basic_ifstream& stream) const { + stream.rdbuf()->pubsetbuf(nullptr, size_); + } + +private: + std::size_t size_; +}; + +struct LibraryDefaultBuffer { + LibraryDefaultBuffer() {} + template + void operator()(std::basic_ofstream&) const {} + template + void operator()(std::basic_ifstream&) const {} +}; + +#endif // TEST_STD_INPUT_OUTPUT_FILE_STREAMS_FSTREAMS_TYPES_H