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: 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..e17c940 100644 --- a/tests/file.cpp +++ b/tests/file.cpp @@ -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(file.rdbuf()); - return filebuf != nullptr && filebuf->is_open(); -} +/// Test fixture for `basic_file` tests +template class file : public testing::Test { +public: + /// 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 -/// @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::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 convert_string(std::string_view string) { + std::basic_string result; + + for (char character : string) { + result += static_cast(character); + } + + return result; + } +}; + +/// 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, name_generator); /// 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; + 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 -TEST(file, create) { - file tmpfile = file(); - EXPECT_TRUE(is_open(tmpfile)); - EXPECT_TRUE(is_open(tmpfile.native_handle())); +TYPED_TEST(file, create) { + basic_file tmpfile = basic_file(); + 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 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,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::native_handle_type handle; { - file tmpfile = file(); + basic_file tmpfile = basic_file(); 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 fst = basic_file(); fst << "Hello!"; - file snd = file(std::move(fst)); + basic_file snd = basic_file(std::move(fst)); - EXPECT_TRUE(is_open(snd)); + EXPECT_TRUE(TestFixture::is_open(snd.rdbuf())); snd.seekg(0); - std::string content; + std::basic_string 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 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 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 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 fst = basic_file(); + basic_file snd = basic_file(); - 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