From ad50489f295d558a8df9eca7feac2e265c433457 Mon Sep 17 00:00:00 2001 From: bugdea1er Date: Sun, 31 Aug 2025 15:34:06 +0300 Subject: [PATCH 1/4] Implement `basic_file` --- include/tmp/file | 35 ++++++++------- tests/file.cpp | 115 +++++++++++++++++++++++++++++++---------------- 2 files changed, 96 insertions(+), 54 deletions(-) diff --git a/include/tmp/file b/include/tmp/file index 9f802d0..0482b71 100644 --- a/include/tmp/file +++ b/include/tmp/file @@ -64,29 +64,30 @@ extern "C++" file_native_handle get_native_handle(std::FILE* file) noexcept; /// } /// } /// @endcode -class file : public std::iostream { +template> +class basic_file : public std::basic_iostream { 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(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(std::move(other)), underlying(std::move(other.underlying)), sb(std::move(other.sb)) { - set_rdbuf(std::addressof(sb)); + std::basic_iostream::set_rdbuf(std::addressof(sb)); } /// Deletes and closes the managed temporary file, then moves the ownership @@ -94,7 +95,7 @@ public: /// @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 @@ -103,7 +104,7 @@ public: } /// Closes and deletes this file - ~file() noexcept override = default; + ~basic_file() noexcept override = default; private: /// The underlying C file stream @@ -111,10 +112,10 @@ private: #ifdef __GLIBCXX__ /// The underlying raw file device object - mutable __gnu_cxx::stdio_filebuf sb; + mutable __gnu_cxx::stdio_filebuf sb; #else /// The underlying raw file device object - std::filebuf sb; + std::basic_filebuf sb; #endif /// Returns a file device for the given file stream @@ -122,13 +123,14 @@ private: /// @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(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(file, binary | in | out); + sb = __gnu_cxx::stdio_filebuf(file, mode); #endif if (!sb.is_open()) { @@ -140,6 +142,9 @@ private: return sb; } }; + +using file = basic_file; +using wfile = basic_file; } // namespace tmp #endif // TMP_FILE_H diff --git a/tests/file.cpp b/tests/file.cpp index b43db10..e408a52 100644 --- a/tests/file.cpp +++ b/tests/file.cpp @@ -26,8 +26,9 @@ namespace tmp { namespace { /// Returns whether the underlying raw file device object is open -bool is_open(const file& file) { - std::filebuf* filebuf = dynamic_cast(file.rdbuf()); +template +bool is_open(const basic_file& file) { + auto filebuf = dynamic_cast*>(file.rdbuf()); return filebuf != nullptr && filebuf->is_open(); } @@ -43,36 +44,68 @@ bool is_open(file::native_handle_type handle) { #endif } +template +constexpr std::basic_string convert_string(const char* string) { + if constexpr (std::is_same_v) { + return std::basic_string(string); + } + + if constexpr (std::is_same_v) { + std::mbstate_t state = std::mbstate_t(); + + std::basic_string result; + result.resize(std::mbsrtowcs(nullptr, &string, 0, &state)); + + std::size_t ret = + std::mbsrtowcs(result.data(), &string, result.size(), &state); + assert(ret == result.size()); + return result; + } + + throw std::invalid_argument("Unknown character type"); +} + +template class file : public testing::Test {}; + +using char_types = testing::Types; +TYPED_TEST_SUITE(file, char_types, testing::internal::DefaultNameGenerator); + /// Tests file type traits and member types -TEST(file, type_traits) { - using traits = std::char_traits; - - static_assert(std::is_base_of_v, file>); - static_assert(std::is_same_v); - static_assert(std::is_same_v); - static_assert(std::is_same_v); - static_assert(std::is_same_v); - static_assert(std::is_same_v); +TYPED_TEST(file, type_traits) { + using traits = std::char_traits; + + static_assert( + std::is_base_of_v, basic_file>); + static_assert( + std::is_same_v::char_type, TypeParam>); + static_assert( + std::is_same_v::traits_type, traits>); + static_assert(std::is_same_v::int_type, + typename traits::int_type>); + static_assert(std::is_same_v::pos_type, + typename traits::pos_type>); + static_assert(std::is_same_v::off_type, + typename traits::off_type>); } /// Tests file creation -TEST(file, create) { - file tmpfile = file(); +TYPED_TEST(file, create) { + basic_file tmpfile = basic_file(); EXPECT_TRUE(is_open(tmpfile)); EXPECT_TRUE(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 fst = basic_file(); + basic_file snd = basic_file(); EXPECT_NE(fst.native_handle(), snd.native_handle()); } /// Tests file open mode -TEST(file, openmode) { - file tmpfile = file(); +TYPED_TEST(file, openmode) { + basic_file tmpfile = basic_file(); tmpfile << "Hello, World!" << std::flush; tmpfile.seekg(0); tmpfile << "Goodbye!" << std::flush; @@ -83,11 +116,11 @@ TEST(file, openmode) { } /// Tests that destructor removes a file -TEST(file, destructor) { - file::native_handle_type handle; +TYPED_TEST(file, destructor) { + typename basic_file::native_handle_type handle; { - file tmpfile = file(); + basic_file tmpfile = basic_file(); handle = tmpfile.native_handle(); } @@ -95,30 +128,32 @@ TEST(file, destructor) { } /// Tests file move constructor -TEST(file, move_constructor) { - file fst = file(); +TYPED_TEST(file, move_constructor) { + basic_file fst = basic_file(); fst << "Hello!"; - file snd = file(std::move(fst)); + basic_file snd = basic_file(std::move(fst)); EXPECT_TRUE(is_open(snd)); snd.seekg(0); - std::string content; + std::basic_string content; snd >> content; - EXPECT_EQ(content, "Hello!"); + EXPECT_EQ(content, convert_string("Hello!")); } /// Tests file move assignment operator -TEST(file, move_assignment) { - file fst = file(); +TYPED_TEST(file, move_assignment) { + basic_file fst = basic_file(); { - file snd = file(); + basic_file snd = basic_file(); snd << "Hello!"; - file::native_handle_type fst_handle = fst.native_handle(); - file::native_handle_type snd_handle = snd.native_handle(); + typename basic_file::native_handle_type fst_handle = + fst.native_handle(); + typename basic_file::native_handle_type snd_handle = + snd.native_handle(); fst = std::move(snd); @@ -130,18 +165,20 @@ TEST(file, move_assignment) { EXPECT_TRUE(is_open(fst)); fst.seekg(0); - std::string content; + std::basic_string content; fst >> content; - EXPECT_EQ(content, "Hello!"); + EXPECT_EQ(content, convert_string("Hello!")); } /// Tests file swapping -TEST(file, swap) { - file fst = file(); - file snd = file(); - - file::native_handle_type fst_handle = fst.native_handle(); - file::native_handle_type snd_handle = snd.native_handle(); +TYPED_TEST(file, swap) { + basic_file fst = basic_file(); + basic_file snd = basic_file(); + + typename basic_file::native_handle_type fst_handle = + fst.native_handle(); + typename basic_file::native_handle_type snd_handle = + snd.native_handle(); std::swap(fst, snd); From ec31b211d6df0ebcb6f45799b79a30a44e0e3b27 Mon Sep 17 00:00:00 2001 From: bugdea1er Date: Sun, 31 Aug 2025 20:15:43 +0300 Subject: [PATCH 2/4] Rewrite tests --- tests/file.cpp | 114 ++++++++++++++++++++++--------------------------- 1 file changed, 51 insertions(+), 63 deletions(-) diff --git a/tests/file.cpp b/tests/file.cpp index e408a52..8021ae3 100644 --- a/tests/file.cpp +++ b/tests/file.cpp @@ -25,74 +25,66 @@ namespace tmp { namespace { -/// Returns whether the underlying raw file device object is open -template -bool is_open(const basic_file& file) { - auto filebuf = dynamic_cast*>(file.rdbuf()); - return filebuf != nullptr && filebuf->is_open(); -} +/// Test fixture for `basic_file` tests +template class file : public testing::Test { +public: + /// Returns whether the underlying raw file device object is open + bool is_open(const basic_file& file) { + auto filebuf = dynamic_cast*>(file.rdbuf()); + 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) { + /// Checks if the given file handle is valid + bool is_open(typename basic_file::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 -} - -template -constexpr std::basic_string convert_string(const char* string) { - if constexpr (std::is_same_v) { - return std::basic_string(string); } - if constexpr (std::is_same_v) { - std::mbstate_t state = std::mbstate_t(); - + /// Converts the string to the `charT` string type + constexpr std::basic_string convert_string(std::string_view string) { std::basic_string result; - result.resize(std::mbsrtowcs(nullptr, &string, 0, &state)); - std::size_t ret = - std::mbsrtowcs(result.data(), &string, result.size(), &state); - assert(ret == result.size()); + for (char character : string) { + result += static_cast(character); + } + return result; } +}; - throw std::invalid_argument("Unknown character type"); -} - -template class file : public testing::Test {}; +/// Name generator for typed test suite +struct name_generator { + template static std::string GetName(int) { + return typeid(T).name(); + } +}; using char_types = testing::Types; -TYPED_TEST_SUITE(file, char_types, testing::internal::DefaultNameGenerator); +TYPED_TEST_SUITE(file, char_types, name_generator); /// Tests file type traits and member types TYPED_TEST(file, type_traits) { using traits = std::char_traits; - - static_assert( - std::is_base_of_v, basic_file>); - static_assert( - std::is_same_v::char_type, TypeParam>); - static_assert( - std::is_same_v::traits_type, traits>); - static_assert(std::is_same_v::int_type, - typename traits::int_type>); - static_assert(std::is_same_v::pos_type, - typename traits::pos_type>); - static_assert(std::is_same_v::off_type, - typename traits::off_type>); + using file_t = basic_file; + using testing::StaticAssertTypeEq; + + static_assert(std::is_base_of_v, file_t>); + StaticAssertTypeEq(); + StaticAssertTypeEq(); + StaticAssertTypeEq(); + StaticAssertTypeEq(); + StaticAssertTypeEq(); } /// Tests file creation TYPED_TEST(file, create) { basic_file tmpfile = basic_file(); - EXPECT_TRUE(is_open(tmpfile)); - EXPECT_TRUE(is_open(tmpfile.native_handle())); + EXPECT_TRUE(TestFixture::is_open(tmpfile)); + EXPECT_TRUE(TestFixture::is_open(tmpfile.native_handle())); } /// Tests multiple file creation @@ -124,7 +116,7 @@ TYPED_TEST(file, destructor) { handle = tmpfile.native_handle(); } - EXPECT_FALSE(is_open(handle)); + EXPECT_FALSE(TestFixture::is_open(handle)); } /// Tests file move constructor @@ -134,12 +126,12 @@ TYPED_TEST(file, move_constructor) { basic_file snd = basic_file(std::move(fst)); - EXPECT_TRUE(is_open(snd)); + EXPECT_TRUE(TestFixture::is_open(snd)); snd.seekg(0); std::basic_string content; snd >> content; - EXPECT_EQ(content, convert_string("Hello!")); + EXPECT_EQ(content, TestFixture::convert_string("Hello!")); } /// Tests file move assignment operator @@ -150,24 +142,22 @@ TYPED_TEST(file, move_assignment) { basic_file snd = basic_file(); snd << "Hello!"; - typename basic_file::native_handle_type fst_handle = - fst.native_handle(); - typename basic_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)); fst.seekg(0); std::basic_string content; fst >> content; - EXPECT_EQ(content, convert_string("Hello!")); + EXPECT_EQ(content, TestFixture::convert_string("Hello!")); } /// Tests file swapping @@ -175,17 +165,15 @@ TYPED_TEST(file, swap) { basic_file fst = basic_file(); basic_file snd = basic_file(); - typename basic_file::native_handle_type fst_handle = - fst.native_handle(); - typename basic_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)); + EXPECT_TRUE(TestFixture::is_open(snd)); } } // namespace } // namespace tmp From 75cf9c302637215af9dc128d90e3569053d2d957 Mon Sep 17 00:00:00 2001 From: bugdea1er Date: Sun, 31 Aug 2025 20:37:08 +0300 Subject: [PATCH 3/4] Rewrite `is_open` functions --- tests/file.cpp | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/tests/file.cpp b/tests/file.cpp index 8021ae3..e17c940 100644 --- a/tests/file.cpp +++ b/tests/file.cpp @@ -28,13 +28,13 @@ namespace { /// Test fixture for `basic_file` tests template class file : public testing::Test { public: - /// Returns whether the underlying raw file device object is open - bool is_open(const basic_file& file) { - auto filebuf = dynamic_cast*>(file.rdbuf()); + /// Returns whether the file device object is open + bool is_open(const std::basic_streambuf* streambuf) { + auto filebuf = dynamic_cast*>(streambuf); return filebuf != nullptr && filebuf->is_open(); } - /// Checks if the given file handle is valid + /// Returns whether the given file handle is open bool is_open(typename basic_file::native_handle_type handle) { #ifdef _WIN32 BY_HANDLE_FILE_INFORMATION info; @@ -83,7 +83,7 @@ TYPED_TEST(file, type_traits) { /// Tests file creation TYPED_TEST(file, create) { basic_file tmpfile = basic_file(); - EXPECT_TRUE(TestFixture::is_open(tmpfile)); + EXPECT_TRUE(TestFixture::is_open(tmpfile.rdbuf())); EXPECT_TRUE(TestFixture::is_open(tmpfile.native_handle())); } @@ -126,7 +126,7 @@ TYPED_TEST(file, move_constructor) { basic_file snd = basic_file(std::move(fst)); - EXPECT_TRUE(TestFixture::is_open(snd)); + EXPECT_TRUE(TestFixture::is_open(snd.rdbuf())); snd.seekg(0); std::basic_string content; @@ -152,7 +152,7 @@ TYPED_TEST(file, move_assignment) { EXPECT_EQ(fst.native_handle(), snd_handle); } - EXPECT_TRUE(TestFixture::is_open(fst)); + EXPECT_TRUE(TestFixture::is_open(fst.rdbuf())); fst.seekg(0); std::basic_string content; @@ -172,8 +172,8 @@ TYPED_TEST(file, swap) { EXPECT_EQ(fst.native_handle(), snd_handle); EXPECT_EQ(snd.native_handle(), fst_handle); - EXPECT_TRUE(TestFixture::is_open(fst)); - EXPECT_TRUE(TestFixture::is_open(snd)); + EXPECT_TRUE(TestFixture::is_open(fst.rdbuf())); + EXPECT_TRUE(TestFixture::is_open(snd.rdbuf())); } } // namespace } // namespace tmp From 7366f25a5982398b2266eed16c809fb14c883d93 Mon Sep 17 00:00:00 2001 From: bugdea1er Date: Sun, 31 Aug 2025 20:41:36 +0300 Subject: [PATCH 4/4] Fix Windows build --- .github/workflows/windows.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 65642b5..777ac93 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -6,7 +6,7 @@ name: windows pull_request: env: - CXXFLAGS: /WX /W3 /wd4251 + CXXFLAGS: /WX /W3 /wd4244 jobs: cmake: