Support NativeFileStream via Boost.Iostreams

So it can also be enabled when not using libstdc++.
This commit is contained in:
Martchus 2018-09-29 18:05:02 +02:00
parent f60f79d6f3
commit 749eea2ab6
8 changed files with 361 additions and 61 deletions

View File

@ -143,10 +143,35 @@ set(META_VERSION_PATCH 1)
include(3rdParty)
use_iconv(AUTO_LINKAGE REQUIRED)
# configure use of native file buffer
# configure use of native file buffer and its backend implementation if enabled
option(USE_NATIVE_FILE_BUFFER "enables use of native file buffer, affects ABI" OFF)
option(FORCE_BOOST_IOSTREAMS_FOR_NATIVE_FILE_BUFFER "forces use of Boost.Iostreams for native file buffer" OFF)
if(USE_NATIVE_FILE_BUFFER)
list(APPEND META_PUBLIC_COMPILE_DEFINITIONS ${META_PROJECT_VARNAME}_USE_NATIVE_FILE_BUFFER)
# check whether __gnu_cxx::stdio_filebuf is available
try_compile(GNU_CXX_STDIO_FILEBUF_AVAILABLE ${CMAKE_CURRENT_BINARY_DIR}
SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/feature_detection/stdio_filebuf.cpp
OUTPUT_VARIABLE GNU_CXX_STDIO_FILEBUF_CHECK_LOG
)
# use __gnu_cxx::stdio_filebuf if available or fallback to boost::iostreams::stream_buffer
if(GNU_CXX_STDIO_FILEBUF_AVAILABLE AND NOT FORCE_BOOST_IOSTREAMS_FOR_NATIVE_FILE_BUFFER)
message(STATUS "Using __gnu_cxx::stdio_filebuf for NativeFileStream")
set_source_files_properties(
io/nativefilestream.cpp
PROPERTIES COMPILE_DEFINITIONS ${META_PROJECT_VARNAME}_USE_GNU_CXX_STDIO_FILEBUF
)
else()
message(STATUS "Using boost::iostreams::stream_buffer<boost::iostreams::file_descriptor_sink> for NativeFileStream")
use_external_library(boost_iostreams AUTO_LINKAGE REQUIRED)
set_source_files_properties(
io/nativefilestream.cpp
PROPERTIES COMPILE_DEFINITIONS ${META_PROJECT_VARNAME}_USE_BOOST_IOSTREAMS
)
endif()
else()
message(STATUS "Using std::fstream for NativeFileStream")
endif()
# configure forcing UTF-8 code page under Windows

View File

@ -236,7 +236,7 @@ macro(find_external_library_from_package NAME PKGNAME VERSION INCLUDE_VAR LIBRAR
endif()
# use the find_library approach first because it is less buggy when trying to detect static libraries
# caveat: this way include dirs are not detected - however those are mostly the the default anyways and
# caveat: this way include dirs are not detected - however those are mostly the default anyways and
# can also be set manually by the user in case the auto-detection is not sufficient
find_external_library("${NAME}" "${LINKAGE}" OPTIONAL)

View File

@ -0,0 +1,10 @@
// assume __gnu_cxx::stdio_filebuf is available if this program compiles
#include <ext/stdio_filebuf.h>
#include <iostream>
int main() {
const int fd = 0;
__gnu_cxx::stdio_filebuf<char> buf(fd, std::ios_base::in);
return buf.fd();
}

View File

@ -1,7 +1,7 @@
#include "./misc.h"
#include "./catchiofailure.h"
#include "./nativefilestream.h"
#include <fstream>
#include <streambuf>
using namespace std;
@ -15,7 +15,7 @@ namespace IoUtilities {
*/
string readFile(const string &path, std::string::size_type maxSize)
{
ifstream file;
NativeFileStream file;
file.exceptions(ios_base::failbit | ios_base::badbit);
file.open(path, ios_base::in | ios_base::binary);
file.seekg(0, ios_base::end);

View File

@ -1,44 +1,72 @@
#include "./nativefilestream.h"
#if defined(CPP_UTILITIES_USE_NATIVE_FILE_BUFFER) && (defined(PLATFORM_MINGW) || defined(PLATFORM_LINUX))
#ifdef CPP_UTILITIES_USE_NATIVE_FILE_BUFFER
#include "./catchiofailure.h"
#ifdef PLATFORM_MINGW
// include header files for file buffer implementation
#if defined(CPP_UTILITIES_USE_GNU_CXX_STDIO_FILEBUF)
#include <ext/stdio_filebuf.h>
#elif defined(CPP_UTILITIES_USE_BOOST_IOSTREAMS)
#include <boost/iostreams/device/file_descriptor.hpp>
#include <boost/iostreams/stream.hpp>
#else
#error "Configuration for NativeFileStream backend insufficient."
#endif
// include platform specific header
#if defined(PLATFORM_UNIX)
#include <cstdio>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#elif defined(PLATFORM_WINDOWS)
#include <fcntl.h>
#include <io.h>
#include <sys/stat.h> // yes, this is needed under Windows (https://msdn.microsoft.com/en-US/library/5yhhz3y7.aspx)
#include <windows.h>
#endif
#ifdef PLATFORM_LINUX
#include <cstdio>
#endif
#endif
using namespace std;
namespace IoUtilities {
#if defined(CPP_UTILITIES_USE_NATIVE_FILE_BUFFER) && (defined(PLATFORM_MINGW) || defined(PLATFORM_UNIX))
#ifdef CPP_UTILITIES_USE_NATIVE_FILE_BUFFER
#ifdef CPP_UTILITIES_USE_GNU_CXX_STDIO_FILEBUF
using StreamBuffer = __gnu_cxx::stdio_filebuf<char>;
#else // CPP_UTILITIES_USE_BOOST_IOSTREAMS
using StreamBuffer = boost::iostreams::stream_buffer<boost::iostreams::file_descriptor>;
#endif
struct NativeFileParams {
#ifdef PLATFORM_MINGW
#ifdef PLATFORM_WINDOWS
NativeFileParams(ios_base::openmode cppOpenMode)
: openMode(cppOpenMode & ios_base::binary ? _O_BINARY : 0)
, flags(cppOpenMode & ios_base::binary ? 0 : _O_TEXT)
, permissions(0)
, access(0)
, shareMode(0)
, creation(0)
{
if ((cppOpenMode & ios_base::out) && (cppOpenMode & ios_base::in)) {
openMode |= _O_RDWR;
access = GENERIC_READ | GENERIC_WRITE;
shareMode = FILE_SHARE_READ;
creation = OPEN_EXISTING;
} else if (cppOpenMode & ios_base::out) {
openMode |= _O_WRONLY | _O_CREAT;
permissions = _S_IREAD | _S_IWRITE;
access = GENERIC_WRITE;
creation = OPEN_ALWAYS;
} else if (cppOpenMode & ios_base::in) {
openMode |= _O_RDONLY;
flags |= _O_RDONLY;
access = GENERIC_READ;
shareMode = FILE_SHARE_READ;
creation = OPEN_EXISTING;
}
if (cppOpenMode & ios_base::app) {
openMode |= _O_APPEND;
@ -46,33 +74,44 @@ struct NativeFileParams {
}
if (cppOpenMode & ios_base::trunc) {
openMode |= _O_TRUNC;
creation = (cppOpenMode & ios_base::in) ? TRUNCATE_EXISTING : CREATE_ALWAYS;
}
}
int openMode;
int flags;
int permissions;
DWORD access;
DWORD shareMode;
DWORD creation;
#else
NativeFileParams(ios_base::openmode cppOpenMode)
: openFlags(0)
{
if ((cppOpenMode & ios_base::in) && (cppOpenMode & ios_base::out)) {
if (cppOpenMode & ios_base::app) {
openMode = "a+";
openFlags = O_RDWR | O_APPEND;
} else if (cppOpenMode & ios_base::trunc) {
openMode = "w+";
openFlags = O_RDWR | O_TRUNC;
} else {
openMode = "r+";
openFlags = O_RDWR;
}
} else if (cppOpenMode & ios_base::in) {
openMode = 'r';
openFlags = O_RDONLY;
} else if (cppOpenMode & ios_base::out) {
if (cppOpenMode & ios_base::app) {
openMode = 'a';
openFlags = O_WRONLY | O_APPEND;
} else if (cppOpenMode & ios_base::trunc) {
openMode = 'w';
openFlags = O_WRONLY | O_TRUNC | O_CREAT;
} else {
openMode = "r+";
openFlags = O_WRONLY | O_CREAT;
}
}
if (cppOpenMode & ios_base::binary) {
@ -81,62 +120,152 @@ struct NativeFileParams {
}
std::string openMode;
int openFlags;
#endif
};
/*!
* \brief Constructs a new NativeFileStream which is initially closed.
*/
NativeFileStream::NativeFileStream()
: m_filebuf(new __gnu_cxx::stdio_filebuf<char>)
: m_filebuf(make_unique<StreamBuffer>())
{
rdbuf(m_filebuf.get());
init(m_filebuf.get());
}
/*!
* \brief Moves the NativeFileStream.
*/
NativeFileStream::NativeFileStream(NativeFileStream &&other)
: m_filebuf(std::move(other.m_filebuf))
, m_cfile(other.m_cfile)
, m_fileHandle(other.m_fileHandle)
{
init(m_filebuf.get());
}
/*!
* \brief Destroys the NativeFileStream releasing all underlying resources.
*/
NativeFileStream::~NativeFileStream()
{
}
/*!
* \brief Returns whether the file is open.
*/
bool NativeFileStream::is_open() const
{
return m_filebuf && static_cast<const StreamBuffer *>(m_filebuf.get())->is_open();
}
/*!
* \brief Opens the file referenced by \a path with the specified \a openMode.
* \remarks
* Under Windows \a path is expected to be UTF-8 encoded. It is automatically converted so non-ASCII
* characters are treated correctly under Windows (in contrast to std::fstream::open() where only the
* current code page is supported).
*
* Under other platforms the \a path is just passed through so there are no assumptions made about its
* encoding.
* \throws Throws std::ios_base::failure in the error case.
* \todo Maybe use setstate() instead of throwing exceptions directly for consistent error handling
* with std::fstream::open(). However, that makes passing specific error messages difficult.
*/
void NativeFileStream::open(const string &path, ios_base::openmode openMode)
{
#ifdef PLATFORM_MINGW
// convert path to UTF-16
int requiredSize = MultiByteToWideChar(CP_UTF8, 0, path.data(), -1, nullptr, 0);
if (requiredSize <= 0) {
::IoUtilities::throwIoFailure("Unable to calculate buffer size for conversion of path to UTF-16");
}
auto widePath = make_unique<wchar_t[]>(static_cast<size_t>(requiredSize));
requiredSize = MultiByteToWideChar(CP_UTF8, 0, path.data(), -1, widePath.get(), requiredSize);
if (requiredSize <= 0) {
::IoUtilities::throwIoFailure("Unable to convert path to UTF-16");
}
setFileBuffer(makeFileBuffer(path, openMode));
}
// initialize stdio_filebuf
/*!
* \brief Opens the file from the specified \a fileDescriptor with the specified \a openMode.
* \throws Throws std::ios_base::failure in the error case.
* \todo
* - Maybe use setstate() instead of throwing exceptions directly for consistent error handling
* with std::fstream::open(). However, that makes passing specific error messages difficult.
* - Rename to open() in v5.
*/
void NativeFileStream::openFromFileDescriptor(int fileDescriptor, ios_base::openmode openMode)
{
setFileBuffer(makeFileBuffer(fileDescriptor, openMode));
}
/*!
* \brief Closes the file if opened; otherwise does nothing.
*/
void NativeFileStream::close()
{
if (m_filebuf) {
static_cast<StreamBuffer *>(m_filebuf.get())->close();
}
}
/*!
* \brief Internally called to assign the \a buffer taking. Takes ownership over \a buffer.
*/
void NativeFileStream::setFileBuffer(std::unique_ptr<std::basic_streambuf<char>> buffer)
{
rdbuf(buffer.get());
m_filebuf = std::move(buffer);
}
/*!
* \brief \brief Internally called by open().
*/
std::unique_ptr<std::basic_streambuf<char>> NativeFileStream::makeFileBuffer(const string &path, ios_base::openmode openMode)
{
#ifdef PLATFORM_WINDOWS
// convert path to UTF-16
const auto widePath(makeWidePath(path));
#endif
// compute native params
const NativeFileParams nativeParams(openMode);
#ifdef CPP_UTILITIES_USE_GNU_CXX_STDIO_FILEBUF
// open file handle to initialize stdio_filebuf
#ifdef PLATFORM_WINDOWS
const int fileHandle = _wopen(widePath.get(), nativeParams.openMode, nativeParams.permissions);
if (fileHandle == -1) {
::IoUtilities::throwIoFailure("_wopen failed");
}
#else
// initialize stdio_filebuf
const NativeFileParams nativeParams(openMode);
const auto fileHandle = fopen(path.data(), nativeParams.openMode.data());
if (!fileHandle) {
::IoUtilities::throwIoFailure("fopen failed");
}
#endif
m_filebuf = make_unique<__gnu_cxx::stdio_filebuf<char>>(fileHandle, openMode);
rdbuf(m_filebuf.get());
return make_unique<StreamBuffer>(fileHandle, openMode);
#else // CPP_UTILITIES_USE_BOOST_IOSTREAMS
// create raw file descriptor to initialize boost::iostreams::file_descriptor
#ifdef PLATFORM_WINDOWS
const auto fileDescriptor
= CreateFileW(widePath.get(), nativeParams.access, nativeParams.shareMode, nullptr, nativeParams.creation, FILE_ATTRIBUTE_NORMAL);
if (fileDescriptor == INVALID_HANDLE_VALUE) {
::IoUtilities::throwIoFailure("CreateFileW failed");
}
#else
const auto fileDescriptor = ::open(path.data(), nativeParams.openFlags);
if (fileDescriptor == -1) {
::IoUtilities::throwIoFailure("open failed");
}
#endif
return make_unique<StreamBuffer>(fileDescriptor, boost::iostreams::close_handle);
#endif
}
void NativeFileStream::openFromFileDescriptor(int fileDescriptor, ios_base::openmode openMode)
/*!
* \brief Internally called by openFromFileDescriptor().
*/
std::unique_ptr<std::basic_streambuf<char>> NativeFileStream::makeFileBuffer(int fileDescriptor, ios_base::openmode openMode)
{
// compute native params
const NativeFileParams nativeParams(openMode);
#ifdef PLATFORM_MINGW
#ifdef CPP_UTILITIES_USE_GNU_CXX_STDIO_FILEBUF
// open file handle to initialize stdio_filebuf
#ifdef PLATFORM_WINDOWS
const auto fileHandle = _get_osfhandle(fileDescriptor);
if (fileHandle == -1) {
::IoUtilities::throwIoFailure("_get_osfhandle failed");
@ -151,16 +280,29 @@ void NativeFileStream::openFromFileDescriptor(int fileDescriptor, ios_base::open
::IoUtilities::throwIoFailure("fdopen failed");
}
#endif
m_filebuf = make_unique<__gnu_cxx::stdio_filebuf<char>>(fileHandle, openMode);
rdbuf(m_filebuf.get());
return make_unique<StreamBuffer>(fileDescriptor, openMode);
#else // CPP_UTILITIES_USE_BOOST_IOSTREAMS
// initialize boost::iostreams::file_descriptor from the specified fileDescriptor
return make_unique<StreamBuffer>(fileDescriptor, boost::iostreams::close_handle);
#endif
}
void NativeFileStream::close()
#ifdef PLATFORM_WINDOWS
std::unique_ptr<wchar_t[]> NativeFileStream::makeWidePath(const std::string &path)
{
if (m_filebuf) {
m_filebuf->close();
int requiredSize = MultiByteToWideChar(CP_UTF8, 0, path.data(), -1, nullptr, 0);
if (requiredSize <= 0) {
::IoUtilities::throwIoFailure("Unable to calculate buffer size for conversion of path to UTF-16");
}
auto widePath = make_unique<wchar_t[]>(static_cast<size_t>(requiredSize));
requiredSize = MultiByteToWideChar(CP_UTF8, 0, path.data(), -1, widePath.get(), requiredSize);
if (requiredSize <= 0) {
::IoUtilities::throwIoFailure("Unable to convert path to UTF-16");
}
return widePath;
}
#endif
#else

View File

@ -3,27 +3,24 @@
#include "../global.h"
#if defined(CPP_UTILITIES_USE_NATIVE_FILE_BUFFER)
#if defined(PLATFORM_MINGW) || defined(PLATFORM_LINUX)
#include <ext/stdio_filebuf.h>
#ifdef CPP_UTILITIES_USE_NATIVE_FILE_BUFFER
#include <iostream>
#include <memory>
#include <streambuf>
#include <string>
#else
#error "Platform not supported by NativeFileStream."
#endif
#else
#include <fstream>
#endif
namespace IoUtilities {
#if defined(CPP_UTILITIES_USE_NATIVE_FILE_BUFFER) && (defined(PLATFORM_MINGW) || defined(PLATFORM_UNIX))
#ifdef CPP_UTILITIES_USE_NATIVE_FILE_BUFFER
class CPP_UTILITIES_EXPORT NativeFileStream : public std::iostream {
public:
NativeFileStream();
NativeFileStream(const std::string &path, std::ios_base::openmode openMode);
NativeFileStream(int fileDescriptor, std::ios_base::openmode openMode);
NativeFileStream(NativeFileStream &&);
~NativeFileStream();
@ -31,24 +28,42 @@ public:
void open(const std::string &path, std::ios_base::openmode openMode);
void openFromFileDescriptor(int fileDescriptor, std::ios_base::openmode openMode);
void close();
std::__c_file fileHandle();
FILE fileHandle();
static std::unique_ptr<std::basic_streambuf<char>> makeFileBuffer(const std::string &path, ios_base::openmode openMode);
static std::unique_ptr<std::basic_streambuf<char>> makeFileBuffer(int fileDescriptor, ios_base::openmode openMode);
#ifdef PLATFORM_WINDOWS
static std::unique_ptr<wchar_t[]> makeWidePath(const std::string &path);
#endif
private:
std::unique_ptr<__gnu_cxx::stdio_filebuf<char>> m_filebuf;
std::__c_file m_cfile;
void setFileBuffer(std::unique_ptr<std::basic_streambuf<char>> buffer);
std::unique_ptr<std::basic_streambuf<char>> m_filebuf;
FILE m_fileHandle;
};
inline bool NativeFileStream::is_open() const
inline NativeFileStream::NativeFileStream(const std::string &path, ios_base::openmode openMode)
{
return m_filebuf && m_filebuf->is_open();
open(path, openMode);
}
inline std::__c_file NativeFileStream::fileHandle()
inline NativeFileStream::NativeFileStream(int fileDescriptor, ios_base::openmode openMode)
{
return m_cfile;
openFromFileDescriptor(fileDescriptor, openMode);
}
#else
/*!
* \brief Returns the underlying file handle if possible; otherwise the behaviour is undefined.
* \deprecated Not implemented for any backend and will be removed in v5.
* \todo Remove in v5.
*/
inline FILE NativeFileStream::fileHandle()
{
return m_fileHandle;
}
#else // CPP_UTILITIES_USE_NATIVE_FILE_BUFFER
using NativeFileStream = std::fstream;

1
testfiles/täst.txt Normal file
View File

@ -0,0 +1 @@
file with non-ASCII character 'ä' in its name

View File

@ -1,6 +1,8 @@
#include "./testutils.h"
#include "../conversion/conversionexception.h"
#include "../conversion/stringbuilder.h"
#include "../io/ansiescapecodes.h"
#include "../io/binaryreader.h"
#include "../io/binarywriter.h"
@ -9,6 +11,7 @@
#include "../io/copy.h"
#include "../io/inifile.h"
#include "../io/misc.h"
#include "../io/nativefilestream.h"
#include "../io/path.h"
#include <cppunit/TestFixture.h>
@ -16,8 +19,14 @@
#include <algorithm>
#include <fstream>
#include <regex>
#include <sstream>
#ifdef PLATFORM_UNIX
#include <sys/fcntl.h>
#include <sys/types.h>
#endif
using namespace std;
using namespace IoUtilities;
using namespace TestUtilities;
@ -38,8 +47,11 @@ class IoTests : public TestFixture {
CPPUNIT_TEST(testPathUtilities);
CPPUNIT_TEST(testIniFile);
CPPUNIT_TEST(testCopy);
CPPUNIT_TEST(testMisc);
CPPUNIT_TEST(testReadFile);
CPPUNIT_TEST(testAnsiEscapeCodes);
#ifdef CPP_UTILITIES_USE_NATIVE_FILE_BUFFER
CPPUNIT_TEST(testNativeFileStream);
#endif
CPPUNIT_TEST_SUITE_END();
public:
@ -53,8 +65,9 @@ public:
void testPathUtilities();
void testIniFile();
void testCopy();
void testMisc();
void testReadFile();
void testAnsiEscapeCodes();
void testNativeFileStream();
};
CPPUNIT_TEST_SUITE_REGISTRATION(IoTests);
@ -383,10 +396,11 @@ void IoTests::testCopy()
}
/*!
* \brief Tests misc IO utilities.
* \brief Tests readFile().
*/
void IoTests::testMisc()
void IoTests::testReadFile()
{
// read a file successfully
const string iniFilePath(testFilePath("test.ini"));
CPPUNIT_ASSERT_EQUAL("# file for testing INI parser\n"
"key0=value 0\n"
@ -401,12 +415,19 @@ void IoTests::testMisc()
"#key5=value 5\n"
"key6=value 6\n"s,
readFile(iniFilePath));
// fail by exceeding max size
try {
readFile(iniFilePath, 10);
CPPUNIT_FAIL("no exception");
} catch (...) {
catchIoFailure();
}
// handle UTF-8 in path and file contents correctly via NativeFileStream
#if !defined(PLATFORM_WINDOWS) || defined(CPP_UTILITIES_USE_NATIVE_FILE_BUFFER)
CPPUNIT_ASSERT_EQUAL("file with non-ASCII character 'ä' in its name\n"s, readFile(testFilePath("täst.txt")));
#endif
}
void IoTests::testAnsiEscapeCodes()
@ -441,3 +462,89 @@ void IoTests::testAnsiEscapeCodes()
ss2 << EscapeCodes::Phrases::Info << "some info" << EscapeCodes::Phrases::End;
CPPUNIT_ASSERT_EQUAL("Info: some info\n"s, ss2.str());
}
#ifdef CPP_UTILITIES_USE_NATIVE_FILE_BUFFER
/*!
* \brief Tests the NativeFileStream class.
*/
void IoTests::testNativeFileStream()
{
// open file by path
const auto txtFilePath(workingCopyPath("täst.txt"));
NativeFileStream fileStream;
fileStream.exceptions(ios_base::badbit | ios_base::failbit);
CPPUNIT_ASSERT(!fileStream.is_open());
fileStream.open(txtFilePath, ios_base::in);
CPPUNIT_ASSERT(fileStream.is_open());
CPPUNIT_ASSERT_EQUAL(static_cast<char>(fileStream.get()), 'f');
fileStream.seekg(0, ios_base::end);
CPPUNIT_ASSERT_EQUAL(fileStream.tellg(), static_cast<NativeFileStream::pos_type>(47));
fileStream.close();
CPPUNIT_ASSERT(!fileStream.is_open());
try {
fileStream.open("non existing file", ios_base::in | ios_base::out | ios_base::binary);
CPPUNIT_FAIL("expected exception");
} catch (...) {
const string msg = catchIoFailure();
#if defined(PLATFORM_UNIX)
CPPUNIT_ASSERT_EQUAL(msg, "open failed: iostream error"s);
#elif defined(PLATFORM_WINDOWS)
CPPUNIT_ASSERT_EQUAL(msg, "CreateFileW failed: iostream error"s);
#endif
}
fileStream.clear();
// open file from file descriptor
#if defined(PLATFORM_UNIX)
auto readWriteFileDescriptor = open(txtFilePath.data(), O_RDWR);
CPPUNIT_ASSERT(readWriteFileDescriptor);
fileStream.openFromFileDescriptor(readWriteFileDescriptor, ios_base::in | ios_base::out | ios_base::binary);
CPPUNIT_ASSERT(fileStream.is_open());
CPPUNIT_ASSERT_EQUAL(static_cast<char>(fileStream.get()), 'f');
fileStream.seekg(0, ios_base::end);
CPPUNIT_ASSERT_EQUAL(fileStream.tellg(), static_cast<NativeFileStream::pos_type>(47));
fileStream.flush();
fileStream.close();
CPPUNIT_ASSERT(!fileStream.is_open());
#endif
try {
fileStream.openFromFileDescriptor(-1, ios_base::in | ios_base::out | ios_base::binary);
fileStream.get();
CPPUNIT_FAIL("expected exception");
} catch (...) {
const string msg = catchIoFailure();
TESTUTILS_ASSERT_LIKE("expected error message", "(fdopen failed|failed reading: Bad file descriptor): iostream error", msg);
}
fileStream.clear();
// append + write file via path
cout << "append + write file via path" << endl;
NativeFileStream fileStream2;
fileStream2.exceptions(ios_base::failbit | ios_base::badbit);
fileStream2.open(txtFilePath, ios_base::in | ios_base::out | ios_base::app);
CPPUNIT_ASSERT(fileStream2.is_open());
fileStream2 << "foo";
fileStream2.flush();
fileStream2.close();
CPPUNIT_ASSERT(!fileStream2.is_open());
CPPUNIT_ASSERT_EQUAL("file with non-ASCII character 'ä' in its name\nfoo"s, readFile(txtFilePath, 50));
// truncate + write file via path
fileStream2.open(txtFilePath, ios_base::out | ios_base::trunc);
CPPUNIT_ASSERT(fileStream2.is_open());
fileStream2 << "bar";
fileStream2.close();
CPPUNIT_ASSERT(!fileStream2.is_open());
CPPUNIT_ASSERT_EQUAL("bar"s, readFile(txtFilePath, 4));
// append + write via file descriptor from file handle
const auto appendFileHandle = fopen(txtFilePath.data(), "a");
CPPUNIT_ASSERT(appendFileHandle);
fileStream2.openFromFileDescriptor(fileno(appendFileHandle), ios_base::out | ios_base::app);
CPPUNIT_ASSERT(fileStream2.is_open());
fileStream2 << "foo";
fileStream2.close();
CPPUNIT_ASSERT(!fileStream2.is_open());
CPPUNIT_ASSERT_EQUAL("barfoo"s, readFile(txtFilePath, 7));
}
#endif