mirror of
https://github.com/boostorg/nowide.git
synced 2026-01-19 04:22:12 +00:00
326 lines
12 KiB
C++
326 lines
12 KiB
C++
// 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 <boost/nowide/filebuf.hpp>
|
|
|
|
#include "file_test_helpers.hpp"
|
|
#include "test.hpp"
|
|
#include <cstdint>
|
|
#include <random>
|
|
#include <string>
|
|
#include <type_traits>
|
|
|
|
namespace nw = boost::nowide;
|
|
using namespace boost::nowide::test;
|
|
|
|
// Check member types
|
|
static_assert(std::is_same<nw::filebuf::char_type, char>::value, "!");
|
|
static_assert(std::is_same<nw::filebuf::traits_type::char_type, char>::value, "!");
|
|
static_assert(std::is_same<nw::filebuf::int_type, nw::filebuf::traits_type::int_type>::value, "!");
|
|
static_assert(std::is_same<nw::filebuf::pos_type, nw::filebuf::traits_type::pos_type>::value, "!");
|
|
static_assert(std::is_same<nw::filebuf::off_type, nw::filebuf::traits_type::off_type>::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<size_t> distr(0, static_cast<size_t>(eofPos));
|
|
using traits = nw::filebuf::traits_type;
|
|
|
|
const auto getData = [&](pos_type pos) { return traits::to_int_type(data[static_cast<size_t>(pos)]); };
|
|
|
|
for(int i = 0; i < 100; i++)
|
|
{
|
|
const pos_type pos = distr(rng);
|
|
TEST(buf.pubseekpos(pos) == pos);
|
|
if(pos == eofPos)
|
|
TEST(buf.sgetc() == traits::eof());
|
|
else
|
|
TEST(buf.sgetc() == getData(pos));
|
|
}
|
|
// Seek to first and last as corner case tests
|
|
TEST(buf.pubseekpos(0) == pos_type(0));
|
|
TEST(buf.sgetc() == traits::to_int_type(data[0]));
|
|
TEST(buf.pubseekpos(eofPos) == eofPos);
|
|
TEST(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<size_t> distr(0, static_cast<size_t>(eofPos));
|
|
using traits = nw::filebuf::traits_type;
|
|
|
|
const auto getData = [&](pos_type pos) { return traits::to_int_type(data[static_cast<size_t>(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(buf.pubseekoff(pos, std::ios_base::beg) == pos);
|
|
TEST(tellg() == pos);
|
|
if(pos == eofPos)
|
|
TEST(buf.sgetc() == traits::eof());
|
|
else
|
|
TEST(buf.sgetc() == getData(pos));
|
|
// cur
|
|
off_type diff = static_cast<pos_type>(distr(rng)) - pos;
|
|
pos += diff;
|
|
TEST(buf.pubseekoff(diff, std::ios_base::cur) == pos);
|
|
TEST(tellg() == pos);
|
|
if(pos == eofPos)
|
|
TEST(buf.sgetc() == traits::eof());
|
|
else
|
|
TEST(buf.sgetc() == getData(pos));
|
|
// end
|
|
diff = static_cast<pos_type>(distr(rng)) - eofPos;
|
|
pos = eofPos + diff;
|
|
TEST(buf.pubseekoff(diff, std::ios_base::end) == pos);
|
|
TEST(tellg() == pos);
|
|
if(pos == eofPos)
|
|
TEST(buf.sgetc() == traits::eof());
|
|
else
|
|
TEST(buf.sgetc() == getData(pos));
|
|
}
|
|
// Seek to first and last as corner case tests
|
|
TEST(buf.pubseekoff(0, std::ios_base::beg) == pos_type(0));
|
|
TEST(tellg() == pos_type(0));
|
|
TEST(buf.sgetc() == traits::to_int_type(data[0]));
|
|
TEST(buf.pubseekoff(0, std::ios_base::end) == eofPos);
|
|
TEST(tellg() == eofPos);
|
|
TEST(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<nw::filebuf::off_type>::type;
|
|
nw::filebuf::off_type offset = static_cast<unsigned_off_type>(std::uint64_t(1) << 33u);
|
|
|
|
#ifdef BOOST_MSVC
|
|
#pragma warning(push)
|
|
#pragma warning(disable : 4127)
|
|
#endif
|
|
// coverity[result_independent_of_operands]
|
|
if(offset == nw::filebuf::off_type(0))
|
|
return; // Can't use 64 bit offsets throught the API, so don't test anything
|
|
#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(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(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(newPos == offset + knownPos);
|
|
TEST(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(read_file(filepath) == "FooBar");
|
|
TEST(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(buf1.sputc('B') == 'B');
|
|
TEST(buf2.sbumpc() == 'H');
|
|
buf1.swap(buf2);
|
|
// Trying to read in write mode or other way round should fail
|
|
TEST(buf1.sputc('x') == eof);
|
|
TEST(buf2.sbumpc() == eof);
|
|
TEST(buf1.sbumpc() == 'e');
|
|
TEST(buf2.sputc('a') == 'a');
|
|
buf2.swap(buf1);
|
|
TEST(buf2.sputc('x') == eof);
|
|
TEST(buf1.sbumpc() == eof);
|
|
TEST(buf2.sbumpc() == 'l');
|
|
TEST(buf1.sputn("zXYZ", 4) == 4);
|
|
swap(buf2, buf1);
|
|
buf1.close();
|
|
buf2.close();
|
|
TEST(read_file(filepath) == "BazXYZ");
|
|
TEST(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(buf1.sgetc() == 'B');
|
|
TEST(buf2.sgetc() == 'H');
|
|
swap(buf1, buf2);
|
|
TEST(buf2.sgetc() == 'B');
|
|
TEST(buf1.sgetc() == 'H');
|
|
// Advance
|
|
TEST(buf2.sbumpc() == 'B');
|
|
TEST(buf1.sbumpc() == 'H');
|
|
TEST(buf2.sbumpc() == 'a');
|
|
TEST(buf1.sbumpc() == 'e');
|
|
swap(buf1, buf2);
|
|
TEST(buf1.sbumpc() == 'z');
|
|
TEST(buf2.sbumpc() == 'l');
|
|
swap(buf1, buf2);
|
|
TEST(buf2.sgetc() == 'X');
|
|
TEST(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(buf1.sputc('1') == '1');
|
|
TEST(buf2.sputc('a') == 'a');
|
|
swap(buf1, buf2);
|
|
// buf1: filepath2, buf2: filepath
|
|
TEST(buf1.sputc('b') == 'b');
|
|
TEST(buf2.sputc('2') == '2');
|
|
// Sync and check if file was written
|
|
TEST(buf1.pubsync() == 0);
|
|
TEST(read_file(filepath2) == "ab");
|
|
TEST(buf2.pubsync() == 0);
|
|
TEST(read_file(filepath) == "12");
|
|
swap(buf1, buf2);
|
|
// buf1: filepath, buf2: filepath2
|
|
TEST(buf1.pubsync() == 0);
|
|
TEST(read_file(filepath) == "12");
|
|
TEST(buf2.pubsync() == 0);
|
|
TEST(read_file(filepath2) == "ab");
|
|
TEST(buf1.sputc('3') == '3');
|
|
TEST(buf2.sputc('c') == 'c');
|
|
swap(buf1, buf2);
|
|
// buf1: filepath2, buf2: filepath
|
|
TEST(buf1.pubsync() == 0);
|
|
TEST(read_file(filepath2) == "abc");
|
|
TEST(buf2.pubsync() == 0);
|
|
TEST(read_file(filepath) == "123");
|
|
}
|
|
}
|
|
|
|
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
|
|
}
|