AimRT/_deps/boost-src/libs/nowide/test/test_fstream_special.cpp
2025-01-12 20:41:24 +08:00

287 lines
8.9 KiB
C++

// Copyright (c) 2015 Artyom Beilis (Tonkikh)
// Copyright (c) 2019-2021 Alexander Grund
//
// Distributed under the Boost Software License, Version 1.0.
// https://www.boost.org/LICENSE_1_0.txt
#include <boost/nowide/fstream.hpp>
#include <boost/nowide/convert.hpp>
#include <boost/nowide/cstdio.hpp>
#include "file_test_helpers.hpp"
#include "test.hpp"
#include <fstream>
#include <iostream>
#include <string>
namespace nw = boost::nowide;
using namespace boost::nowide::test;
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++)
{
remove_file_at_exit _(filepath);
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) ? nullptr : 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.write("defg", 4));
// Read first char
TEST(f.seekg(0));
TEST_EQ(f.get(), 'a');
TEST_EQ(f.gcount(), std::streamsize(1));
// Skip next char
TEST(f.seekg(1, std::ios::cur));
TEST_EQ(f.get(), 'c');
TEST_EQ(f.gcount(), std::streamsize(1));
// Go back 1 char
TEST(f.seekg(-1, std::ios::cur));
TEST_EQ(f.get(), 'c');
TEST_EQ(f.gcount(), std::streamsize(1));
// 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_EQ(f.get(), 'c');
TEST_EQ(f.gcount(), std::streamsize(1));
TEST(f.seekg(1));
TEST_EQ(f.get(), 'B');
TEST_EQ(f.gcount(), std::streamsize(1));
// case 2) overwrite, seek, read
TEST(f.seekg(2));
TEST(f.put('C'));
TEST(f.seekg(3)); // Seek when changing out->in
TEST_EQ(f.get(), 'd');
TEST_EQ(f.gcount(), std::streamsize(1));
// Check that sequence from start equals expected
TEST(f.seekg(0));
TEST_EQ(f.get(), 'a');
TEST_EQ(f.get(), 'B');
TEST_EQ(f.get(), 'C');
TEST_EQ(f.get(), 'd');
TEST_EQ(f.get(), 'e');
// Putback after flush is implementation defined
TEST(f << std::flush);
if(f.putback('e'))
{
if(f.putback('d'))
TEST_EQ(f.get(), 'd');
else
f.clear(); // LCOV_EXCL_LINE
TEST_EQ(f.get(), 'e');
} else
f.clear();
TEST(f << std::flush);
if(f.unget())
TEST_EQ(f.get(), 'e');
else
f.clear();
// Put back different char
TEST(f.seekg(-1, std::ios::cur));
TEST_EQ(f.get(), 'e');
TEST(f.putback('x'));
TEST_EQ(f.get(), 'x');
// Rest of sequence
TEST_EQ(f.get(), 'f');
TEST_EQ(f.get(), 'g');
TEST_EQ(f.get(), EOF);
// Put back until front of file is reached
f.clear();
TEST(f.seekg(1));
TEST_EQ(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
if(f.putback('a'))
{
// At beginning of file -> No putback possible
TEST(!f.putback('x')); // LCOV_EXCL_LINE
f.clear(); // LCOV_EXCL_LINE
// Get characters that were putback to avoid MSVC bug https://github.com/microsoft/STL/issues/342
TEST_EQ(f.get(), 'a'); // LCOV_EXCL_LINE
} else
f.clear();
#endif
TEST_EQ(f.get(), 'B');
f.close();
}
}
void test_switch_to_custom_buffer(const std::string& filename)
{
// Switching the buffer after file stream was used is not always defined. So only test custom stream
#if BOOST_NOWIDE_USE_FILEBUF_REPLACEMENT
nw::test::create_file(filename, "HelloWorld");
nw::ifstream f(filename, std::ios::binary);
std::string s(5, '\0');
TEST(f.read(&s.front(), s.size()));
TEST_EQ(s, "Hello");
// Switch buffer
std::string buffer(10, '\0');
TEST_EQ(f.sync(), 0);
TEST(f.rdbuf()->pubsetbuf(&buffer.front(), buffer.size()) == f.rdbuf());
TEST(f >> s);
TEST_EQ(s, "World");
TEST_EQ(s, buffer.c_str()); // same should be in buffer and some trailing NULL bytes
#else
(void)filename; // Suppress unused warning
#endif
}
// Reproducer for https://github.com/boostorg/nowide/issues/126
void test_getline_and_tellg(const char* filename)
{
{
nw::ofstream f(filename);
f << "Line 1" << std::endl;
f << "Line 2" << std::endl;
f << "Line 3" << std::endl;
}
remove_file_at_exit _(filename);
nw::fstream f;
// Open file in text mode, to read
f.open(filename, std::ios_base::in);
TEST(f);
std::string line1, line2, line3;
TEST(getline(f, line1));
TEST_EQ(line1, "Line 1");
const auto tg = f.tellg(); // This may cause issues
TEST(tg > 0u);
TEST(getline(f, line2));
TEST_EQ(line2, "Line 2");
TEST(getline(f, line3));
TEST_EQ(line3, "Line 3");
}
// Test that a sync after a peek does not swallow newlines
// This can happen because peek reads a char which needs to be "unread" on sync which may loose a converted newline
void test_peek_sync_get(const char* filename)
{
{
nw::ofstream f(filename);
f << "Line 1" << std::endl;
f << "Line 2" << std::endl;
}
remove_file_at_exit _(filename);
nw::ifstream f(filename);
TEST(f);
while(f)
{
const int curChar = f.peek();
if(curChar == std::char_traits<char>::eof())
break;
f.sync();
TEST_EQ(f.get(), char(curChar));
}
}
/// Test swapping at many possible positions within a stream to shake out missed state
void test_swap(const char* filename, const char* filename2)
{
remove_file_at_exit _(filename);
remove_file_at_exit _2(filename2);
{
nw::ofstream f(filename);
f << create_random_data(BUFSIZ * 2, data_type::text);
f.close();
f.open(filename2);
f << create_random_data(BUFSIZ * 3, data_type::text);
}
nw::ifstream f1(filename);
nw::ifstream f2(filename2);
TEST(f1);
TEST(f2);
unsigned ctr = 0;
while(f1 && f2)
{
const int curChar1 = f1.peek();
const int curChar2 = f2.peek();
TEST_CONTEXT("ctr " << ctr << ": c1=" << curChar1 << " c2=" << curChar2);
// Randomly do a no-op seek of either or both streams to flush internal buffer
if(ctr % 10 == 0)
TEST(f1.seekg(f1.tellg()));
else if(ctr % 15 == 0)
TEST(f2.seekg(f2.tellg()));
f1.swap(f2);
TEST_EQ(f1.peek(), curChar2);
TEST_EQ(f2.peek(), curChar1);
if(ctr % 10 == 4)
TEST(f1.seekg(f1.tellg()));
else if(ctr % 15 == 4)
TEST(f2.seekg(f2.tellg()));
TEST_EQ(f1.get(), curChar2);
f1.swap(f2);
TEST_EQ(f1.get(), curChar1);
++ctr;
}
}
void testPutback(const char* filename)
{
nw::test::create_file(filename, "abc");
// Does work for ifstreams
{
nw::ifstream f(filename);
const int c = f.get();
TEST(f.putback(static_cast<char>(c)));
TEST_EQ(f.get(), c);
}
// Does work for io fstreams
{
nw::fstream f(filename);
const int c = f.get();
TEST(f.putback(static_cast<char>(c)));
TEST_EQ(f.get(), c);
}
// Doesn't work for output fstreams
{
nw::fstream f(filename, std::ios::out);
TEST(!f.putback('x'));
}
}
// 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";
const std::string exampleFilename2 = std::string(argv[0]) + "-\xd7\xa9-\xd0\xbc-\xce\xbd 2.txt";
std::cout << "Putback" << std::endl;
testPutback(exampleFilename.c_str());
std::cout << "Complex IO" << std::endl;
test_with_different_buffer_sizes(exampleFilename.c_str());
test_switch_to_custom_buffer(exampleFilename.c_str());
std::cout << "Regression tests" << std::endl;
test_getline_and_tellg(exampleFilename.c_str());
test_peek_sync_get(exampleFilename.c_str());
test_swap(exampleFilename.c_str(), exampleFilename2.c_str());
}