2
0
mirror of https://github.com/boostorg/nowide.git synced 2026-02-14 12:52:17 +00:00
Files
nowide/test/test_fstream.cpp
Alexander Grund 6afb3f15ab Deduplicate tests and improve coverage
Every test had the same pattern of a main catching exceptions and
returning 0 or 1 based on that. Factor that into test.hpp.
As most code in test.hpp is only executed on failure and those should
not occur this file is excluded from coverage.

Finally test.hpp is included last and the header to test first
consistently to check for self-sufficient includes.
2020-04-03 16:28:28 +02:00

514 lines
14 KiB
C++

//
// Copyright (c) 2015 Artyom Beilis (Tonkikh)
// Copyright (c) 2019 Alexander Grund
//
// Distributed under the Boost Software License, Version 1.0. (See
// accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)
//
#include <boost/nowide/fstream.hpp>
#include <boost/nowide/convert.hpp>
#include <boost/nowide/cstdio.hpp>
#include <fstream>
#include <iostream>
#include <string>
#include "test.hpp"
namespace nw = boost::nowide;
void make_empty_file(const char* filepath)
{
nw::ofstream f(filepath, std::ios_base::out | std::ios::trunc);
TEST(f);
}
bool file_exists(const char* filepath)
{
FILE* f = nw::fopen(filepath, "r");
if(f)
{
std::fclose(f);
return true;
} else
return false;
}
std::string read_file(const char* filepath, bool binary_mode = false)
{
FILE* f = nw::fopen(filepath, binary_mode ? "rb" : "r");
TEST(f);
std::string content;
int c;
while((c = std::fgetc(f)) != EOF)
content.push_back(static_cast<char>(c));
std::fclose(f);
return content;
}
void test_with_different_buffer_sizes(const char* filepath)
{
/* Important part of the standard for mixing input with output:
However, output shall not be directly followed by input without an intervening call to the fflush function
or to a file positioning function (fseek, fsetpos, or rewind),
and input shall not be directly followed by output without an intervening call to a file positioning function,
unless the input operation encounters end-of-file.
*/
for(int i = -1; i < 16; i++)
{
std::cout << "Buffer size = " << i << std::endl;
char buf[16];
nw::fstream f;
// Different conditions when setbuf might be called: Usually before opening a file is OK
if(i >= 0)
f.rdbuf()->pubsetbuf((i == 0) ? NULL : buf, i);
f.open(filepath, std::ios::in | std::ios::out | std::ios::trunc | std::ios::binary);
TEST(f);
// Add 'abcdefg'
TEST(f.put('a'));
TEST(f.put('b'));
TEST(f.put('c'));
TEST(f.put('d'));
TEST(f.put('e'));
TEST(f.put('f'));
TEST(f.put('g'));
// Read first char
TEST(f.seekg(0));
TEST(f.get() == 'a');
TEST(f.gcount() == 1u);
// Skip next char
TEST(f.seekg(1, std::ios::cur));
TEST(f.get() == 'c');
TEST(f.gcount() == 1u);
// Go back 1 char
TEST(f.seekg(-1, std::ios::cur));
TEST(f.get() == 'c');
TEST(f.gcount() == 1u);
// Test switching between read->write->read
// case 1) overwrite, flush, read
TEST(f.seekg(1));
TEST(f.put('B'));
TEST(f.flush()); // Flush when changing out->in
TEST(f.get() == 'c');
TEST(f.gcount() == 1u);
TEST(f.seekg(1));
TEST(f.get() == 'B');
TEST(f.gcount() == 1u);
// case 2) overwrite, seek, read
TEST(f.seekg(2));
TEST(f.put('C'));
TEST(f.seekg(3)); // Seek when changing out->in
TEST(f.get() == 'd');
TEST(f.gcount() == 1u);
// Check that sequence from start equals expected
TEST(f.seekg(0));
TEST(f.get() == 'a');
TEST(f.get() == 'B');
TEST(f.get() == 'C');
TEST(f.get() == 'd');
TEST(f.get() == 'e');
// Putback after flush is implementation defined
// Boost.Nowide: Works
#if BOOST_NOWIDE_USE_FILEBUF_REPLACEMENT
TEST(f << std::flush);
TEST(f.putback('e'));
TEST(f.putback('d'));
TEST(f.get() == 'd');
TEST(f.get() == 'e');
#endif
// Rest of sequence
TEST(f.get() == 'f');
TEST(f.get() == 'g');
TEST(f.get() == EOF);
// Put back until front of file is reached
f.clear();
TEST(f.seekg(1));
TEST(f.get() == 'B');
TEST(f.putback('B'));
// Putting back multiple chars is not possible on all implementations after a seek/flush
#if BOOST_NOWIDE_USE_FILEBUF_REPLACEMENT
TEST(f.putback('a'));
TEST(!f.putback('x')); // At beginning of file -> No putback possible
// Get characters that were putback to avoid MSVC bug https://github.com/microsoft/STL/issues/342
f.clear();
TEST(f.get() == 'a');
#endif
TEST(f.get() == 'B');
f.close();
TEST(nw::remove(filepath) == 0);
}
}
void test_close(const char* filepath)
{
const std::string filepath2 = std::string(filepath) + ".2";
// Make sure file does not exist yet
TEST(!file_exists(filepath2.c_str()) || nw::remove(filepath2.c_str()) == 0);
TEST(!file_exists(filepath2.c_str()));
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.c_str(), std::ios_base::out) == NULL);
// Still open
TEST(buf.is_open());
TEST(buf.close() == &buf);
// Failed opening did not create file
TEST(!file_exists(filepath2.c_str()));
// But it should work now:
TEST(buf.open(filepath2.c_str(), std::ios_base::out) == &buf);
TEST(buf.close() == &buf);
TEST(file_exists(filepath2.c_str()));
TEST(nw::remove(filepath) == 0);
TEST(nw::remove(filepath2.c_str()) == 0);
}
template<typename IFStream, typename OFStream>
void test_flush(const char* filepath)
{
OFStream fo(filepath, std::ios_base::out | std::ios::trunc);
TEST(fo);
std::string curValue;
for(int repeat = 0; repeat < 2; repeat++)
{
for(size_t len = 1; len <= 1024; len *= 2)
{
char c = static_cast<char>(len % 13 + repeat + 'a'); // semi-random char
std::string input(len, c);
fo << input;
curValue += input;
TEST(fo.flush());
std::string s;
// Note: Flush on read area is implementation defined, so check whole file instead
IFStream fi(filepath);
TEST(fi >> s);
// coverity[tainted_data]
TEST(s == curValue);
}
}
}
void test_ofstream_creates_file(const char* filename)
{
TEST(!file_exists(filename) || nw::remove(filename) == 0);
TEST(!file_exists(filename));
// Ctor
{
nw::ofstream fo(filename);
TEST(fo);
}
TEST(file_exists(filename));
TEST(read_file(filename).empty());
TEST(nw::remove(filename) == 0);
// Open
{
nw::ofstream fo;
fo.open(filename);
TEST(fo);
}
TEST(file_exists(filename));
TEST(read_file(filename).empty());
TEST(nw::remove(filename) == 0);
}
// Create filename file with content "test\n"
void test_ofstream_write(const char* filename)
{
// char* ctor
{
nw::ofstream fo(filename);
TEST(fo << "test" << 2 << std::endl);
}
// char* open
TEST(read_file(filename) == "test2\n");
TEST(nw::remove(filename) == 0);
{
nw::ofstream fo;
fo.open(filename);
TEST(fo << "test" << 2 << std::endl);
}
TEST(read_file(filename) == "test2\n");
TEST(nw::remove(filename) == 0);
// string ctor
{
std::string name = filename;
nw::ofstream fo(name);
TEST(fo << "test" << 2 << std::endl);
}
TEST(read_file(filename) == "test2\n");
TEST(nw::remove(filename) == 0);
// string open
{
nw::ofstream fo;
fo.open(std::string(filename));
TEST(fo << "test" << 2 << std::endl);
}
TEST(read_file(filename) == "test2\n");
TEST(nw::remove(filename) == 0);
// Binary mode
{
nw::ofstream fo(filename, std::ios::binary);
TEST(fo << "test" << 2 << std::endl);
}
TEST(read_file(filename, true) == "test2\n");
TEST(nw::remove(filename) == 0);
// At end
{
{
nw::ofstream fo(filename);
TEST(fo << "test" << 2 << std::endl);
}
nw::ofstream fo(filename, std::ios::ate | std::ios::in);
fo << "second" << 2 << std::endl;
}
TEST(read_file(filename) == "test2\nsecond2\n");
TEST(nw::remove(filename) == 0);
}
void test_ifstream_open_read(const char* filename)
{
// Create test file
{
nw::ofstream fo(filename);
TEST(fo << "test" << std::endl);
}
// char* Ctor
{
nw::ifstream fi(filename);
TEST(fi);
std::string tmp;
TEST(fi >> tmp);
TEST(tmp == "test");
}
// char* open
{
nw::ifstream fi;
fi.open(filename);
TEST(fi);
std::string tmp;
TEST(fi >> tmp);
TEST(tmp == "test");
}
// string ctor
{
std::string name = filename;
nw::ifstream fi(name);
TEST(fi);
std::string tmp;
TEST(fi >> tmp);
TEST(tmp == "test");
}
// string open
{
nw::ifstream fi;
fi.open(std::string(filename));
TEST(fi);
std::string tmp;
TEST(fi >> tmp);
TEST(tmp == "test");
}
// Binary mode
{
nw::ifstream fi(filename, std::ios::binary);
TEST(fi);
std::string tmp;
TEST(fi >> tmp);
TEST(tmp == "test");
}
// At end
{
// Need binary file or position check might be throw off by newline conversion
{
nw::ofstream fo(filename, nw::fstream::binary);
TEST(fo << "test");
}
nw::ifstream fi(filename, nw::fstream::ate | nw::fstream::binary);
TEST(fi);
TEST(fi.tellg() == std::streampos(4));
fi.seekg(-2, std::ios_base::cur);
std::string tmp;
TEST(fi >> tmp);
TEST(tmp == "st");
}
// Fail on non-existing file
TEST(nw::remove(filename) == 0);
{
nw::ifstream fi(filename);
TEST(!fi);
}
}
void test_fstream(const char* filename)
{
const std::string sFilename = filename;
TEST(!file_exists(filename) || nw::remove(filename) == 0);
TEST(!file_exists(filename));
// Fail on non-existing file
{
nw::fstream f(filename);
TEST(!f);
nw::fstream f2(sFilename);
TEST(!f2);
}
{
nw::fstream f;
f.open(filename);
TEST(!f);
f.open(sFilename);
TEST(!f);
}
TEST(!file_exists(filename));
// Create empty file (Ctor)
{
nw::fstream f(filename, std::ios::out);
TEST(f);
}
TEST(read_file(filename).empty());
// Char* ctor
{
nw::fstream f(filename);
TEST(f);
TEST(f << "test");
std::string tmp;
TEST(f.seekg(0));
TEST(f >> tmp);
TEST(tmp == "test");
}
TEST(read_file(filename) == "test");
// String ctor
{
nw::fstream f(sFilename);
TEST(f);
TEST(f << "string_ctor");
std::string tmp;
TEST(f.seekg(0));
TEST(f >> tmp);
TEST(tmp == "string_ctor");
}
TEST(read_file(filename) == "string_ctor");
TEST(nw::remove(filename) == 0);
// Create empty file (open)
{
nw::fstream f;
f.open(filename, std::ios::out);
TEST(f);
}
TEST(read_file(filename).empty());
// Open
{
nw::fstream f;
f.open(filename);
TEST(f);
TEST(f << "test");
std::string tmp;
TEST(f.seekg(0));
TEST(f >> tmp);
TEST(tmp == "test");
}
TEST(read_file(filename) == "test");
// Ctor existing file
{
nw::fstream f(filename);
TEST(f);
std::string tmp;
TEST(f >> tmp);
TEST(tmp == "test");
TEST(f.eof());
f.clear();
TEST(f << "second");
}
TEST(read_file(filename) == "testsecond");
// Trunc & binary
{
nw::fstream f(filename, std::ios::in | std::ios::out | std::ios::trunc | std::ios::binary);
TEST(f);
TEST(f << "test2");
std::string tmp;
TEST(f.seekg(0));
TEST(f >> tmp);
TEST(tmp == "test2");
}
TEST(read_file(filename) == "test2");
// Reading in write mode fails (existing file!)
{
nw::fstream f(filename, std::ios::out);
std::string tmp;
TEST(!(f >> tmp));
f.clear();
TEST(f << "foo");
TEST(f.seekg(0));
TEST(!(f >> tmp));
}
TEST(read_file(filename) == "foo");
// Writing in read mode fails (existing file!)
{
nw::fstream f(filename, std::ios::in);
TEST(!(f << "bar"));
f.clear();
std::string tmp;
TEST(f >> tmp);
TEST(tmp == "foo");
}
TEST(read_file(filename) == "foo");
TEST(nw::remove(filename) == 0);
}
template<typename T>
bool is_open(T& stream)
{
// There are const and non const versions of is_open, so test both
TEST(stream.is_open() == const_cast<const T&>(stream).is_open());
return stream.is_open();
}
template<typename T>
void do_test_is_open(const char* filename)
{
T f;
TEST(!is_open(f));
f.open(filename);
TEST(f);
TEST(is_open(f));
f.close();
TEST(f);
TEST(!is_open(f));
}
void test_is_open(const char* filename)
{
// Note the order: Output before input so file exists
do_test_is_open<nw::ofstream>(filename);
do_test_is_open<nw::ifstream>(filename);
do_test_is_open<nw::fstream>(filename);
TEST(nw::remove(filename) == 0);
}
void test_main(int, char** argv, char**)
{
const std::string exampleFilename = std::string(argv[0]) + "-\xd7\xa9-\xd0\xbc-\xce\xbd.txt";
std::cout << "Testing fstream" << std::endl;
test_ofstream_creates_file(exampleFilename.c_str());
test_ofstream_write(exampleFilename.c_str());
test_ifstream_open_read(exampleFilename.c_str());
test_fstream(exampleFilename.c_str());
test_is_open(exampleFilename.c_str());
std::cout << "Complex IO" << std::endl;
test_with_different_buffer_sizes(exampleFilename.c_str());
std::cout << "filebuf::close" << std::endl;
test_close(exampleFilename.c_str());
std::cout << "Flush - Sanity Check" << std::endl;
test_flush<std::ifstream, std::ofstream>(exampleFilename.c_str());
std::cout << "Flush - Test" << std::endl;
test_flush<nw::ifstream, nw::ofstream>(exampleFilename.c_str());
}