// Copyright (c) 2015 Artyom Beilis (Tonkikh) // Copyright (c) 2019-2021 Alexander Grund // // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE or copy at // http://www.boost.org/LICENSE_1_0.txt) #include #include "file_test_helpers.hpp" #include "test.hpp" #include #include #include #include namespace nw = boost::nowide; using namespace boost::nowide::test; // Check member types static_assert(std::is_same::value, "!"); static_assert(std::is_same::value, "!"); static_assert(std::is_same::value, "!"); static_assert(std::is_same::value, "!"); static_assert(std::is_same::value, "!"); void test_open_close(const std::string& filepath) { const std::string filepath2 = filepath + ".2"; ensure_not_exists(filepath2); remove_file_at_exit _(filepath); remove_file_at_exit _2(filepath2); nw::filebuf buf; TEST(buf.open(filepath, std::ios_base::out) == &buf); TEST(buf.is_open()); // Opening when already open fails TEST(buf.open(filepath2, std::ios_base::out) == nullptr); // Still open TEST(buf.is_open()); TEST(buf.close() == &buf); // Failed opening did not create file TEST(!file_exists(filepath2)); // But it should work now: TEST(buf.open(filepath2, std::ios_base::out) == &buf); TEST(buf.close() == &buf); TEST(file_exists(filepath2)); } void test_pubseekpos(const std::string& filepath) { const std::string data = create_random_data(BUFSIZ * 4, data_type::binary); create_file(filepath, data, data_type::binary); nw::filebuf buf; TEST(buf.open(filepath, std::ios_base::in | std::ios_base::binary) == &buf); // Fuzzy test: Seek to a couple random positions std::minstd_rand rng(std::random_device{}()); using pos_type = nw::filebuf::pos_type; const auto eofPos = pos_type(data.size()); std::uniform_int_distribution distr(0, static_cast(eofPos) - 1); using traits = nw::filebuf::traits_type; const auto getData = [&](pos_type pos) { return traits::to_int_type(data[static_cast(pos)]); }; for(int i = 0; i < 100; i++) { const pos_type pos = distr(rng); TEST_EQ(buf.pubseekpos(pos), pos); TEST_EQ(buf.sgetc(), getData(pos)); } // Seek to first and last as corner case tests TEST_EQ(buf.pubseekpos(0), pos_type(0)); TEST_EQ(buf.sgetc(), traits::to_int_type(data[0])); TEST_EQ(buf.pubseekpos(eofPos), eofPos); TEST_EQ(buf.sgetc(), traits::eof()); } void test_pubseekoff(const std::string& filepath) { const std::string data = create_random_data(BUFSIZ * 4, data_type::binary); create_file(filepath, data, data_type::binary); nw::filebuf buf; TEST(buf.open(filepath, std::ios_base::in | std::ios_base::binary) == &buf); // Fuzzy test: Seek to a couple random positions std::minstd_rand rng(std::random_device{}()); using pos_type = nw::filebuf::pos_type; using off_type = nw::filebuf::off_type; const auto eofPos = pos_type(data.size()); std::uniform_int_distribution distr(0, static_cast(eofPos) - 1); using traits = nw::filebuf::traits_type; const auto getData = [&](pos_type pos) { return traits::to_int_type(data[static_cast(pos)]); }; // tellg/tellp function as called by basic_[io]fstream const auto tellg = [&]() { return buf.pubseekoff(0, std::ios_base::cur); }; for(int i = 0; i < 100; i++) { // beg pos_type pos = distr(rng); TEST_EQ(buf.pubseekoff(pos, std::ios_base::beg), pos); TEST_EQ(tellg(), pos); TEST_EQ(buf.sgetc(), getData(pos)); // cur off_type diff = static_cast(distr(rng)) - pos; pos += diff; TEST_EQ(buf.pubseekoff(diff, std::ios_base::cur), pos); TEST_EQ(tellg(), pos); TEST_EQ(buf.sgetc(), getData(pos)); // end diff = static_cast(distr(rng)) - eofPos; pos = eofPos + diff; TEST_EQ(buf.pubseekoff(diff, std::ios_base::end), pos); TEST_EQ(tellg(), pos); TEST_EQ(buf.sgetc(), getData(pos)); } // Seek to first and last as corner case tests TEST_EQ(buf.pubseekoff(0, std::ios_base::beg), pos_type(0)); TEST_EQ(tellg(), pos_type(0)); TEST_EQ(buf.sgetc(), traits::to_int_type(data[0])); TEST_EQ(buf.pubseekoff(0, std::ios_base::end), eofPos); TEST_EQ(tellg(), eofPos); TEST_EQ(buf.sgetc(), traits::eof()); } void test_64_bit_seek(const std::string& filepath) { // Create a value which does not fit into a 32 bit value. // Use an unsigned intermediate to have the truncation defined to wrap to 0 using unsigned_off_type = std::make_unsigned::type; nw::filebuf::off_type offset = static_cast(std::uint64_t(1) << 33u); #ifdef BOOST_MSVC #pragma warning(push) #pragma warning(disable : 4127) #endif // if we can't use 64 bit offsets through the API, don't test anything // coverity[result_independent_of_operands] if(offset == nw::filebuf::off_type(0)) { // coverity[dead_error_line] return; // LCOV_EXCL_LINE } #ifdef BOOST_MSVC #pragma warning(pop) #endif create_file(filepath, "test"); remove_file_at_exit _(filepath); nw::filebuf buf; TEST(buf.open(filepath, std::ios_base::in | std::ios_base::binary) == &buf); const std::streampos knownPos = 2; TEST_EQ(buf.pubseekpos(knownPos), knownPos); // Just to make sure we know where we are const std::streampos newPos = buf.pubseekoff(offset, std::ios_base::cur); // On 32 bit mode or when seek beyond EOF is not allowed, the current position should be unchanged if(newPos == std::streampos(-1)) TEST_EQ(buf.pubseekoff(0, std::ios_base::cur), knownPos); else { #if !BOOST_NOWIDE_USE_FILEBUF_REPLACEMENT // libc++ may truncate the 64 bit value when calling fseek which yields an offset of 0 if(newPos == knownPos) offset = 0; // LCOV_EXCL_LINE #endif TEST_EQ(newPos, offset + knownPos); TEST_EQ(buf.pubseekoff(0, std::ios_base::cur), newPos); } } void test_swap(const std::string& filepath) { const std::string filepath2 = filepath + ".2"; remove_file_at_exit _(filepath); remove_file_at_exit _2(filepath2); const auto eof = nw::filebuf::traits_type::eof(); // Note: Make sure to have en uneven number of swaps so the destructor runs on the others data // Check: FILE*, buffer, buffer_size { nw::filebuf buf1, buf2; char buffer1[3]{}, buffer2[5]{}; buf1.pubsetbuf(buffer1, sizeof(buffer1)); buf2.pubsetbuf(buffer2, sizeof(buffer2)); TEST(buf1.open(filepath, std::ios_base::out) == &buf1); buf1.swap(buf2); TEST(!buf1.is_open()); TEST(buf2.is_open()); TEST(buf1.open(filepath2, std::ios_base::out | std::ios_base::binary) == &buf1); // Write "FooBar" to filepath and "HelloWorld" to filepath2 buf1.sputc('H'); buf1.sputn("ello", 4); buf2.sputc('F'); buf2.sputn("oo", 2); buf2.swap(buf1); buf1.sputc('B'); buf1.sputn("ar", 2); buf2.sputc('W'); buf2.sputn("orld", 4); buf1.close(); TEST(!buf1.is_open()); TEST(buf2.is_open()); buf1.swap(buf2); TEST(buf1.is_open()); TEST(!buf2.is_open()); buf1.close(); TEST(!buf1.is_open()); TEST(!buf2.is_open()); TEST_EQ(read_file(filepath), "FooBar"); TEST_EQ(read_file(filepath2), "HelloWorld"); } // Check: mode, owns_buffer { nw::filebuf buf1, buf2; char buffer[3]{}; buf1.pubsetbuf(buffer, sizeof(buffer)); TEST(buf1.open(filepath, std::ios_base::out) == &buf1); TEST(buf2.open(filepath2, std::ios_base::in) == &buf2); TEST_EQ(buf1.sputc('B'), 'B'); TEST_EQ(buf2.sbumpc(), 'H'); buf1.swap(buf2); // Trying to read in write mode or other way round should fail TEST_EQ(buf1.sputc('x'), eof); TEST_EQ(buf2.sbumpc(), eof); TEST_EQ(buf1.sbumpc(), 'e'); TEST_EQ(buf2.sputc('a'), 'a'); buf2.swap(buf1); TEST_EQ(buf2.sputc('x'), eof); TEST_EQ(buf1.sbumpc(), eof); TEST_EQ(buf2.sbumpc(), 'l'); TEST_EQ(buf1.sputn("zXYZ", 4), 4); swap(buf2, buf1); buf1.close(); buf2.close(); TEST_EQ(read_file(filepath), "BazXYZ"); TEST_EQ(read_file(filepath2), "HelloWorld"); } // Check: last_char, gptr, eback { nw::filebuf buf1, buf2; // Need to disable buffering to use last_char, but only for 1 to detect wrong conditions buf1.pubsetbuf(0, 0); TEST(buf1.open(filepath, std::ios_base::in) == &buf1); TEST(buf2.open(filepath2, std::ios_base::in) == &buf2); // Peek TEST_EQ(buf1.sgetc(), 'B'); TEST_EQ(buf2.sgetc(), 'H'); swap(buf1, buf2); TEST_EQ(buf2.sgetc(), 'B'); TEST_EQ(buf1.sgetc(), 'H'); // Advance TEST_EQ(buf2.sbumpc(), 'B'); TEST_EQ(buf1.sbumpc(), 'H'); TEST_EQ(buf2.sbumpc(), 'a'); TEST_EQ(buf1.sbumpc(), 'e'); swap(buf1, buf2); TEST_EQ(buf1.sbumpc(), 'z'); TEST_EQ(buf2.sbumpc(), 'l'); swap(buf1, buf2); TEST_EQ(buf2.sgetc(), 'X'); TEST_EQ(buf1.sgetc(), 'l'); } // Check: pptr, epptr { nw::filebuf buf1, buf2; // Need to disable buffering to use last_char, but only for 1 to detect wrong conditions buf1.pubsetbuf(0, 0); TEST(buf1.open(filepath, std::ios_base::out) == &buf1); TEST(buf2.open(filepath2, std::ios_base::out) == &buf2); TEST_EQ(buf1.sputc('1'), '1'); TEST_EQ(buf2.sputc('a'), 'a'); swap(buf1, buf2); // buf1: filepath2, buf2: filepath TEST_EQ(buf1.sputc('b'), 'b'); TEST_EQ(buf2.sputc('2'), '2'); // Sync and check if file was written TEST_EQ(buf1.pubsync(), 0); TEST_EQ(read_file(filepath2), "ab"); TEST_EQ(buf2.pubsync(), 0); TEST_EQ(read_file(filepath), "12"); swap(buf1, buf2); // buf1: filepath, buf2: filepath2 TEST_EQ(buf1.pubsync(), 0); TEST_EQ(read_file(filepath), "12"); TEST_EQ(buf2.pubsync(), 0); TEST_EQ(read_file(filepath2), "ab"); TEST_EQ(buf1.sputc('3'), '3'); TEST_EQ(buf2.sputc('c'), 'c'); swap(buf1, buf2); // buf1: filepath2, buf2: filepath TEST_EQ(buf1.pubsync(), 0); TEST_EQ(read_file(filepath2), "abc"); TEST_EQ(buf2.pubsync(), 0); TEST_EQ(read_file(filepath), "123"); } } // coverity [root_function] void test_main(int, char** argv, char**) { const std::string exampleFilename = std::string(argv[0]) + "-\xd7\xa9-\xd0\xbc-\xce\xbd.txt"; test_open_close(exampleFilename); test_pubseekpos(exampleFilename); test_pubseekoff(exampleFilename); test_64_bit_seek(exampleFilename); // These tests are only useful for the nowide filebuf and are known to fail for // std::filebuf due to bugs in libc++ #if BOOST_NOWIDE_USE_FILEBUF_REPLACEMENT test_swap(exampleFilename); #endif }