Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/windows.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ name: windows
pull_request:

env:
CXXFLAGS: /WX /W3 /wd4251
CXXFLAGS: /WX /W3 /wd4244

jobs:
cmake:
Expand Down
35 changes: 20 additions & 15 deletions include/tmp/file
Original file line number Diff line number Diff line change
Expand Up @@ -64,37 +64,38 @@ extern "C++" file_native_handle get_native_handle(std::FILE* file) noexcept;
/// }
/// }
/// @endcode
class file : public std::iostream {
template<class charT, class traits = std::char_traits<charT>>
class basic_file : public std::basic_iostream<charT, traits> {
public:
/// Implementation-defined handle type to the file
using native_handle_type = file_native_handle;

/// Creates and opens a binary temporary file as if by POSIX `tmpfile`
/// @throws std::filesystem::filesystem_error if cannot create a file
explicit file()
: std::iostream(std::addressof(sb)),
explicit basic_file()
: std::basic_iostream<charT, traits>(std::addressof(sb)),
underlying(create_file(), &std::fclose),
sb(open_filebuf(underlying.get())) {}

file(const file&) = delete;
file& operator=(const file&) = delete;
basic_file(const basic_file&) = delete;
basic_file& operator=(const basic_file&) = delete;

/// Moves the ownership of the file managed by `other` to a new handle
/// @note After the move, `other` is not associated with a file
/// @param other Another file that will be moved from
file(file&& other) noexcept
: std::iostream(std::move(other)),
basic_file(basic_file&& other) noexcept
: std::basic_iostream<charT, traits>(std::move(other)),
underlying(std::move(other.underlying)),
sb(std::move(other.sb)) {
set_rdbuf(std::addressof(sb));
std::basic_iostream<charT, traits>::set_rdbuf(std::addressof(sb));
}

/// Deletes and closes the managed temporary file, then moves the ownership
/// of the file managed by `other` to `this`
/// @note After the assignment, `other` is not associated with a file
/// @param other Another file that will be moved from
/// @returns `*this`
file& operator=(file&& other) = default;
basic_file& operator=(basic_file&& other) = default;

/// Returns an implementation-defined handle to this file
/// @returns The underlying implementation-defined handle
Expand All @@ -103,32 +104,33 @@ public:
}

/// Closes and deletes this file
~file() noexcept override = default;
~basic_file() noexcept override = default;

private:
/// The underlying C file stream
std::unique_ptr<std::FILE, int (*)(std::FILE*)> underlying;

#ifdef __GLIBCXX__
/// The underlying raw file device object
mutable __gnu_cxx::stdio_filebuf<char> sb;
mutable __gnu_cxx::stdio_filebuf<charT, traits> sb;
#else
/// The underlying raw file device object
std::filebuf sb;
std::basic_filebuf<charT, traits> sb;
#endif

/// Returns a file device for the given file stream
/// @param file The file stream
/// @returns The new file device
/// @throws std::filesystem::filesystem_error if cannot open the file stream
decltype(sb) open_filebuf(std::FILE* file) {
constexpr auto mode = std::ios::binary | std::ios::in | std::ios::out;
decltype(sb) sb;
#if defined(_MSC_VER)
sb = std::filebuf(file);
sb = std::basic_filebuf<charT, traits>(file);
#elif defined(_LIBCPP_VERSION)
sb.__open(get_native_handle(file), binary | in | out);
sb.__open(get_native_handle(file), mode);
#else
sb = __gnu_cxx::stdio_filebuf<char>(file, binary | in | out);
sb = __gnu_cxx::stdio_filebuf<charT, traits>(file, mode);
#endif

if (!sb.is_open()) {
Expand All @@ -140,6 +142,9 @@ private:
return sb;
}
};

using file = basic_file<char>;
using wfile = basic_file<wchar_t>;
} // namespace tmp

#endif // TMP_FILE_H
141 changes: 83 additions & 58 deletions tests/file.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,54 +25,79 @@
namespace tmp {
namespace {

/// Returns whether the underlying raw file device object is open
bool is_open(const file& file) {
std::filebuf* filebuf = dynamic_cast<std::filebuf*>(file.rdbuf());
return filebuf != nullptr && filebuf->is_open();
}
/// Test fixture for `basic_file` tests
template<class charT> class file : public testing::Test {
public:
/// Returns whether the file device object is open
bool is_open(const std::basic_streambuf<charT>* streambuf) {
auto filebuf = dynamic_cast<const std::basic_filebuf<charT>*>(streambuf);
return filebuf != nullptr && filebuf->is_open();
}

/// Checks if the given file handle is valid
/// @param handle handle to check
/// @returns whether the handle is valid
bool is_open(file::native_handle_type handle) {
/// Returns whether the given file handle is open
bool is_open(typename basic_file<charT>::native_handle_type handle) {
#ifdef _WIN32
BY_HANDLE_FILE_INFORMATION info;
return GetFileInformationByHandle(handle, &info);
BY_HANDLE_FILE_INFORMATION info;
return GetFileInformationByHandle(handle, &info);
#else
return fcntl(handle, F_GETFD) != -1;
return fcntl(handle, F_GETFD) != -1;
#endif
}
}

/// Converts the string to the `charT` string type
constexpr std::basic_string<charT> convert_string(std::string_view string) {
std::basic_string<charT> result;

for (char character : string) {
result += static_cast<charT>(character);
}

return result;
}
};

/// Name generator for typed test suite
struct name_generator {
template<typename T> static std::string GetName(int) {
return typeid(T).name();
}
};

using char_types = testing::Types<char, wchar_t>;
TYPED_TEST_SUITE(file, char_types, name_generator);

/// Tests file type traits and member types
TEST(file, type_traits) {
using traits = std::char_traits<char>;

static_assert(std::is_base_of_v<std::basic_iostream<char>, file>);
static_assert(std::is_same_v<file::char_type, char>);
static_assert(std::is_same_v<file::traits_type, traits>);
static_assert(std::is_same_v<file::int_type, traits::int_type>);
static_assert(std::is_same_v<file::pos_type, traits::pos_type>);
static_assert(std::is_same_v<file::off_type, traits::off_type>);
TYPED_TEST(file, type_traits) {
using traits = std::char_traits<TypeParam>;
using file_t = basic_file<TypeParam>;
using testing::StaticAssertTypeEq;

static_assert(std::is_base_of_v<std::basic_iostream<TypeParam>, file_t>);
StaticAssertTypeEq<typename file_t::char_type, TypeParam>();
StaticAssertTypeEq<typename file_t::traits_type, traits>();
StaticAssertTypeEq<typename file_t::int_type, typename traits::int_type>();
StaticAssertTypeEq<typename file_t::pos_type, typename traits::pos_type>();
StaticAssertTypeEq<typename file_t::off_type, typename traits::off_type>();
}

/// Tests file creation
TEST(file, create) {
file tmpfile = file();
EXPECT_TRUE(is_open(tmpfile));
EXPECT_TRUE(is_open(tmpfile.native_handle()));
TYPED_TEST(file, create) {
basic_file<TypeParam> tmpfile = basic_file<TypeParam>();
EXPECT_TRUE(TestFixture::is_open(tmpfile.rdbuf()));
EXPECT_TRUE(TestFixture::is_open(tmpfile.native_handle()));
}

/// Tests multiple file creation
TEST(file, create_multiple) {
file fst = file();
file snd = file();
TYPED_TEST(file, create_multiple) {
basic_file<TypeParam> fst = basic_file<TypeParam>();
basic_file<TypeParam> snd = basic_file<TypeParam>();

EXPECT_NE(fst.native_handle(), snd.native_handle());
}

/// Tests file open mode
TEST(file, openmode) {
file tmpfile = file();
TYPED_TEST(file, openmode) {
basic_file<TypeParam> tmpfile = basic_file<TypeParam>();
tmpfile << "Hello, World!" << std::flush;
tmpfile.seekg(0);
tmpfile << "Goodbye!" << std::flush;
Expand All @@ -83,72 +108,72 @@ TEST(file, openmode) {
}

/// Tests that destructor removes a file
TEST(file, destructor) {
file::native_handle_type handle;
TYPED_TEST(file, destructor) {
typename basic_file<TypeParam>::native_handle_type handle;

{
file tmpfile = file();
basic_file<TypeParam> tmpfile = basic_file<TypeParam>();
handle = tmpfile.native_handle();
}

EXPECT_FALSE(is_open(handle));
EXPECT_FALSE(TestFixture::is_open(handle));
}

/// Tests file move constructor
TEST(file, move_constructor) {
file fst = file();
TYPED_TEST(file, move_constructor) {
basic_file<TypeParam> fst = basic_file<TypeParam>();
fst << "Hello!";

file snd = file(std::move(fst));
basic_file<TypeParam> snd = basic_file<TypeParam>(std::move(fst));

EXPECT_TRUE(is_open(snd));
EXPECT_TRUE(TestFixture::is_open(snd.rdbuf()));

snd.seekg(0);
std::string content;
std::basic_string<TypeParam> content;
snd >> content;
EXPECT_EQ(content, "Hello!");
EXPECT_EQ(content, TestFixture::convert_string("Hello!"));
}

/// Tests file move assignment operator
TEST(file, move_assignment) {
file fst = file();
TYPED_TEST(file, move_assignment) {
basic_file<TypeParam> fst = basic_file<TypeParam>();

{
file snd = file();
basic_file<TypeParam> snd = basic_file<TypeParam>();
snd << "Hello!";

file::native_handle_type fst_handle = fst.native_handle();
file::native_handle_type snd_handle = snd.native_handle();
typename decltype(fst)::native_handle_type fst_handle = fst.native_handle();
typename decltype(snd)::native_handle_type snd_handle = snd.native_handle();

fst = std::move(snd);

EXPECT_FALSE(is_open(fst_handle));
EXPECT_TRUE(is_open(snd_handle));
EXPECT_FALSE(TestFixture::is_open(fst_handle));
EXPECT_TRUE(TestFixture::is_open(snd_handle));
EXPECT_EQ(fst.native_handle(), snd_handle);
}

EXPECT_TRUE(is_open(fst));
EXPECT_TRUE(TestFixture::is_open(fst.rdbuf()));

fst.seekg(0);
std::string content;
std::basic_string<TypeParam> content;
fst >> content;
EXPECT_EQ(content, "Hello!");
EXPECT_EQ(content, TestFixture::convert_string("Hello!"));
}

/// Tests file swapping
TEST(file, swap) {
file fst = file();
file snd = file();
TYPED_TEST(file, swap) {
basic_file<TypeParam> fst = basic_file<TypeParam>();
basic_file<TypeParam> snd = basic_file<TypeParam>();

file::native_handle_type fst_handle = fst.native_handle();
file::native_handle_type snd_handle = snd.native_handle();
typename decltype(fst)::native_handle_type fst_handle = fst.native_handle();
typename decltype(snd)::native_handle_type snd_handle = snd.native_handle();

std::swap(fst, snd);

EXPECT_EQ(fst.native_handle(), snd_handle);
EXPECT_EQ(snd.native_handle(), fst_handle);
EXPECT_TRUE(is_open(fst));
EXPECT_TRUE(is_open(snd));
EXPECT_TRUE(TestFixture::is_open(fst.rdbuf()));
EXPECT_TRUE(TestFixture::is_open(snd.rdbuf()));
}
} // namespace
} // namespace tmp