From 0a7e5141c17bb629c36b4ff14f4284dcc7baee3f Mon Sep 17 00:00:00 2001 From: vitaut Date: Wed, 24 Jun 2015 09:16:03 -0700 Subject: [PATCH] Move posix tests from gtest-extra-test to a separate test --- test/CMakeLists.txt | 7 +- test/gtest-extra-test.cc | 405 +-------------------------------------- test/gtest-extra.cc | 13 ++ test/gtest-extra.h | 37 ++++ test/posix-mock-test.cc | 1 + test/posix-test.cc | 376 ++++++++++++++++++++++++++++++++++++ test/util.cc | 13 ++ test/util.h | 7 + 8 files changed, 455 insertions(+), 404 deletions(-) create mode 100644 test/posix-test.cc diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 73ac11d2..b23d4cc4 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -62,9 +62,10 @@ add_executable(macro-test macro-test.cc ${FMT_TEST_SOURCES} ${TEST_MAIN_SRC}) target_link_libraries(macro-test gmock) if (HAVE_OPEN) - add_executable(posix-test posix-mock-test.cc ../format.cc ${TEST_MAIN_SRC}) - target_link_libraries(posix-test gmock) - add_test(NAME posix-test COMMAND posix-test) + add_executable(posix-mock-test posix-mock-test.cc ../format.cc ${TEST_MAIN_SRC}) + target_link_libraries(posix-mock-test gmock) + add_test(NAME posix-mock-test COMMAND posix-mock-test) + add_fmt_test(posix-test) endif () add_executable(header-only-test diff --git a/test/gtest-extra-test.cc b/test/gtest-extra-test.cc index 6d8918b3..622dcdcb 100644 --- a/test/gtest-extra-test.cc +++ b/test/gtest-extra-test.cc @@ -36,37 +36,15 @@ # include // for _CrtSetReportMode #endif // _WIN32 +#include "util.h" + #ifdef __MINGW32__ # undef fileno #endif namespace { -#if defined(_WIN32) && !defined(__MINGW32__) - -// Suppresses Windows assertions on invalid file descriptors, making -// POSIX functions return proper error codes instead of crashing on Windows. -class SuppressAssert { - private: - _invalid_parameter_handler original_handler_; - int original_report_mode_; - - static void handle_invalid_parameter(const wchar_t *, - const wchar_t *, const wchar_t *, unsigned , uintptr_t) {} - - public: - SuppressAssert() - : original_handler_(_set_invalid_parameter_handler(handle_invalid_parameter)), - original_report_mode_(_CrtSetReportMode(_CRT_ASSERT, 0)) { - } - ~SuppressAssert() { - _set_invalid_parameter_handler(original_handler_); - _CrtSetReportMode(_CRT_ASSERT, original_report_mode_); - } -}; - -# define SUPPRESS_ASSERT(statement) { SuppressAssert sa; statement; } - +#ifdef _MSC_VER // Fix "secure" warning about using fopen without defining // _CRT_SECURE_NO_WARNINGS. FILE *safe_fopen(const char *filename, const char *mode) { @@ -76,12 +54,8 @@ FILE *safe_fopen(const char *filename, const char *mode) { } #define fopen safe_fopen #else -# define SUPPRESS_ASSERT(statement) statement using std::fopen; -#endif // _WIN32 - -#define EXPECT_SYSTEM_ERROR_NOASSERT(statement, error_code, message) \ - EXPECT_SYSTEM_ERROR(SUPPRESS_ASSERT(statement), error_code, message) +#endif // _MSC_VER // Tests that assertion macros evaluate their arguments exactly once. class SingleEvaluationTest : public ::testing::Test { @@ -363,382 +337,11 @@ using fmt::BufferedFile; using fmt::ErrorCode; using fmt::File; -// Checks if the file is open by reading one character from it. -bool isopen(int fd) { - char buffer; - return FMT_POSIX(read(fd, &buffer, 1)) == 1; -} - -bool isclosed(int fd) { - char buffer; - std::streamsize result = 0; - SUPPRESS_ASSERT(result = FMT_POSIX(read(fd, &buffer, 1))); - return result == -1 && errno == EBADF; -} - -// Attempts to read count characters from a file. -std::string read(File &f, std::size_t count) { - std::string buffer(count, '\0'); - std::streamsize n = 0; - std::size_t offset = 0; - do { - n = f.read(&buffer[offset], count - offset); - // We can't read more than size_t bytes since count has type size_t. - offset += static_cast(n); - } while (offset < count && n != 0); - buffer.resize(offset); - return buffer; -} - -// Attempts to write a string to a file. -void write(File &f, fmt::StringRef s) { - std::size_t num_chars_left = s.size(); - const char *ptr = s.c_str(); - do { - std::streamsize count = f.write(ptr, num_chars_left); - ptr += count; - // We can't write more than size_t bytes since num_chars_left - // has type size_t. - num_chars_left -= static_cast(count); - } while (num_chars_left != 0); -} - -#define EXPECT_READ(file, expected_content) \ - EXPECT_EQ(expected_content, read(file, std::strlen(expected_content))) - TEST(ErrorCodeTest, Ctor) { EXPECT_EQ(0, ErrorCode().get()); EXPECT_EQ(42, ErrorCode(42).get()); } -const char FILE_CONTENT[] = "Don't panic!"; - -// Opens a file for reading. -File open_file() { - File read_end, write_end; - File::pipe(read_end, write_end); - write_end.write(FILE_CONTENT, sizeof(FILE_CONTENT) - 1); - write_end.close(); - return read_end; -} - -// Opens a buffered file for reading. -BufferedFile open_buffered_file(FILE **fp = 0) { - File read_end, write_end; - File::pipe(read_end, write_end); - write_end.write(FILE_CONTENT, sizeof(FILE_CONTENT) - 1); - write_end.close(); - BufferedFile f = read_end.fdopen("r"); - if (fp) - *fp = f.get(); - return f; -} - -TEST(BufferedFileTest, DefaultCtor) { - BufferedFile f; - EXPECT_TRUE(f.get() == 0); -} - -TEST(BufferedFileTest, MoveCtor) { - BufferedFile bf = open_buffered_file(); - FILE *fp = bf.get(); - EXPECT_TRUE(fp != 0); - BufferedFile bf2(std::move(bf)); - EXPECT_EQ(fp, bf2.get()); - EXPECT_TRUE(bf.get() == 0); -} - -TEST(BufferedFileTest, MoveAssignment) { - BufferedFile bf = open_buffered_file(); - FILE *fp = bf.get(); - EXPECT_TRUE(fp != 0); - BufferedFile bf2; - bf2 = std::move(bf); - EXPECT_EQ(fp, bf2.get()); - EXPECT_TRUE(bf.get() == 0); -} - -TEST(BufferedFileTest, MoveAssignmentClosesFile) { - BufferedFile bf = open_buffered_file(); - BufferedFile bf2 = open_buffered_file(); - int old_fd = bf2.fileno(); - bf2 = std::move(bf); - EXPECT_TRUE(isclosed(old_fd)); -} - -TEST(BufferedFileTest, MoveFromTemporaryInCtor) { - FILE *fp = 0; - BufferedFile f(open_buffered_file(&fp)); - EXPECT_EQ(fp, f.get()); -} - -TEST(BufferedFileTest, MoveFromTemporaryInAssignment) { - FILE *fp = 0; - BufferedFile f; - f = open_buffered_file(&fp); - EXPECT_EQ(fp, f.get()); -} - -TEST(BufferedFileTest, MoveFromTemporaryInAssignmentClosesFile) { - BufferedFile f = open_buffered_file(); - int old_fd = f.fileno(); - f = open_buffered_file(); - EXPECT_TRUE(isclosed(old_fd)); -} - -TEST(BufferedFileTest, CloseFileInDtor) { - int fd = 0; - { - BufferedFile f = open_buffered_file(); - fd = f.fileno(); - } - EXPECT_TRUE(isclosed(fd)); -} - -TEST(BufferedFileTest, CloseErrorInDtor) { - BufferedFile *f = new BufferedFile(open_buffered_file()); - EXPECT_WRITE(stderr, { - // The close function must be called inside EXPECT_WRITE, otherwise - // the system may recycle closed file descriptor when redirecting the - // output in EXPECT_STDERR and the second close will break output - // redirection. - FMT_POSIX(close(f->fileno())); - SUPPRESS_ASSERT(delete f); - }, format_system_error(EBADF, "cannot close file") + "\n"); -} - -TEST(BufferedFileTest, Close) { - BufferedFile f = open_buffered_file(); - int fd = f.fileno(); - f.close(); - EXPECT_TRUE(f.get() == 0); - EXPECT_TRUE(isclosed(fd)); -} - -TEST(BufferedFileTest, CloseError) { - BufferedFile f = open_buffered_file(); - FMT_POSIX(close(f.fileno())); - EXPECT_SYSTEM_ERROR_NOASSERT(f.close(), EBADF, "cannot close file"); - EXPECT_TRUE(f.get() == 0); -} - -TEST(BufferedFileTest, Fileno) { - BufferedFile f; - // fileno on a null FILE pointer either crashes or returns an error. - EXPECT_DEATH_IF_SUPPORTED({ - try { - f.fileno(); - } catch (fmt::SystemError) { - std::exit(1); - } - }, ""); - f = open_buffered_file(); - EXPECT_TRUE(f.fileno() != -1); - File copy = File::dup(f.fileno()); - EXPECT_READ(copy, FILE_CONTENT); -} - -TEST(FileTest, DefaultCtor) { - File f; - EXPECT_EQ(-1, f.descriptor()); -} - -TEST(FileTest, OpenBufferedFileInCtor) { - FILE *fp = fopen("test-file", "w"); - std::fputs(FILE_CONTENT, fp); - std::fclose(fp); - File f("test-file", File::RDONLY); - ASSERT_TRUE(isopen(f.descriptor())); -} - -TEST(FileTest, OpenBufferedFileError) { - EXPECT_SYSTEM_ERROR(File("nonexistent", File::RDONLY), - ENOENT, "cannot open file nonexistent"); -} - -TEST(FileTest, MoveCtor) { - File f = open_file(); - int fd = f.descriptor(); - EXPECT_NE(-1, fd); - File f2(std::move(f)); - EXPECT_EQ(fd, f2.descriptor()); - EXPECT_EQ(-1, f.descriptor()); -} - -TEST(FileTest, MoveAssignment) { - File f = open_file(); - int fd = f.descriptor(); - EXPECT_NE(-1, fd); - File f2; - f2 = std::move(f); - EXPECT_EQ(fd, f2.descriptor()); - EXPECT_EQ(-1, f.descriptor()); -} - -TEST(FileTest, MoveAssignmentClosesFile) { - File f = open_file(); - File f2 = open_file(); - int old_fd = f2.descriptor(); - f2 = std::move(f); - EXPECT_TRUE(isclosed(old_fd)); -} - -File OpenBufferedFile(int &fd) { - File f = open_file(); - fd = f.descriptor(); - return std::move(f); -} - -TEST(FileTest, MoveFromTemporaryInCtor) { - int fd = 0xdeadbeef; - File f(OpenBufferedFile(fd)); - EXPECT_EQ(fd, f.descriptor()); -} - -TEST(FileTest, MoveFromTemporaryInAssignment) { - int fd = 0xdeadbeef; - File f; - f = OpenBufferedFile(fd); - EXPECT_EQ(fd, f.descriptor()); -} - -TEST(FileTest, MoveFromTemporaryInAssignmentClosesFile) { - int fd = 0xdeadbeef; - File f = open_file(); - int old_fd = f.descriptor(); - f = OpenBufferedFile(fd); - EXPECT_TRUE(isclosed(old_fd)); -} - -TEST(FileTest, CloseFileInDtor) { - int fd = 0; - { - File f = open_file(); - fd = f.descriptor(); - } - EXPECT_TRUE(isclosed(fd)); -} - -TEST(FileTest, CloseErrorInDtor) { - File *f = new File(open_file()); - EXPECT_WRITE(stderr, { - // The close function must be called inside EXPECT_WRITE, otherwise - // the system may recycle closed file descriptor when redirecting the - // output in EXPECT_STDERR and the second close will break output - // redirection. - FMT_POSIX(close(f->descriptor())); - SUPPRESS_ASSERT(delete f); - }, format_system_error(EBADF, "cannot close file") + "\n"); -} - -TEST(FileTest, Close) { - File f = open_file(); - int fd = f.descriptor(); - f.close(); - EXPECT_EQ(-1, f.descriptor()); - EXPECT_TRUE(isclosed(fd)); -} - -TEST(FileTest, CloseError) { - File f = open_file(); - FMT_POSIX(close(f.descriptor())); - EXPECT_SYSTEM_ERROR_NOASSERT(f.close(), EBADF, "cannot close file"); - EXPECT_EQ(-1, f.descriptor()); -} - -TEST(FileTest, Read) { - File f = open_file(); - EXPECT_READ(f, FILE_CONTENT); -} - -TEST(FileTest, ReadError) { - File read_end, write_end; - File::pipe(read_end, write_end); - char buf; - // We intentionally read from write_end to cause error. - EXPECT_SYSTEM_ERROR(write_end.read(&buf, 1), EBADF, "cannot read from file"); -} - -TEST(FileTest, Write) { - File read_end, write_end; - File::pipe(read_end, write_end); - write(write_end, "test"); - write_end.close(); - EXPECT_READ(read_end, "test"); -} - -TEST(FileTest, WriteError) { - File read_end, write_end; - File::pipe(read_end, write_end); - // We intentionally write to read_end to cause error. - EXPECT_SYSTEM_ERROR(read_end.write(" ", 1), EBADF, "cannot write to file"); -} - -TEST(FileTest, Dup) { - File f = open_file(); - File copy = File::dup(f.descriptor()); - EXPECT_NE(f.descriptor(), copy.descriptor()); - EXPECT_EQ(FILE_CONTENT, read(copy, sizeof(FILE_CONTENT) - 1)); -} - -TEST(FileTest, DupError) { - EXPECT_SYSTEM_ERROR_NOASSERT(File::dup(-1), - EBADF, "cannot duplicate file descriptor -1"); -} - -TEST(FileTest, Dup2) { - File f = open_file(); - File copy = open_file(); - f.dup2(copy.descriptor()); - EXPECT_NE(f.descriptor(), copy.descriptor()); - EXPECT_READ(copy, FILE_CONTENT); -} - -TEST(FileTest, Dup2Error) { - File f = open_file(); - EXPECT_SYSTEM_ERROR_NOASSERT(f.dup2(-1), EBADF, - fmt::format("cannot duplicate file descriptor {} to -1", f.descriptor())); -} - -TEST(FileTest, Dup2NoExcept) { - File f = open_file(); - File copy = open_file(); - ErrorCode ec; - f.dup2(copy.descriptor(), ec); - EXPECT_EQ(0, ec.get()); - EXPECT_NE(f.descriptor(), copy.descriptor()); - EXPECT_READ(copy, FILE_CONTENT); -} - -TEST(FileTest, Dup2NoExceptError) { - File f = open_file(); - ErrorCode ec; - SUPPRESS_ASSERT(f.dup2(-1, ec)); - EXPECT_EQ(EBADF, ec.get()); -} - -TEST(FileTest, Pipe) { - File read_end, write_end; - File::pipe(read_end, write_end); - EXPECT_NE(-1, read_end.descriptor()); - EXPECT_NE(-1, write_end.descriptor()); - write(write_end, "test"); - EXPECT_READ(read_end, "test"); -} - -TEST(FileTest, Fdopen) { - File read_end, write_end; - File::pipe(read_end, write_end); - int read_fd = read_end.descriptor(); - EXPECT_EQ(read_fd, FMT_POSIX(fileno(read_end.fdopen("r").get()))); -} - -TEST(FileTest, FdopenError) { - File f; - EXPECT_SYSTEM_ERROR_NOASSERT( - f.fdopen("r"), EBADF, "cannot associate stream with file descriptor"); -} - TEST(OutputRedirectTest, ScopedRedirect) { File read_end, write_end; File::pipe(read_end, write_end); diff --git a/test/gtest-extra.cc b/test/gtest-extra.cc index 93ca2193..c8c4ac54 100644 --- a/test/gtest-extra.cc +++ b/test/gtest-extra.cc @@ -89,6 +89,19 @@ std::string OutputRedirect::restore_and_read() { return content; } +std::string read(File &f, std::size_t count) { + std::string buffer(count, '\0'); + std::streamsize n = 0; + std::size_t offset = 0; + do { + n = f.read(&buffer[offset], count - offset); + // We can't read more than size_t bytes since count has type size_t. + offset += static_cast(n); + } while (offset < count && n != 0); + buffer.resize(offset); + return buffer; +} + #endif // FMT_USE_FILE_DESCRIPTORS std::string format_system_error(int error_code, fmt::StringRef message) { diff --git a/test/gtest-extra.h b/test/gtest-extra.h index df9cb7f0..5d93f9c9 100644 --- a/test/gtest-extra.h +++ b/test/gtest-extra.h @@ -133,6 +133,43 @@ class OutputRedirect { #define EXPECT_WRITE(file, statement, expected_output) \ FMT_TEST_WRITE_(statement, expected_output, file, GTEST_NONFATAL_FAILURE_) +#ifdef _MSC_VER + +// Suppresses Windows assertions on invalid file descriptors, making +// POSIX functions return proper error codes instead of crashing on Windows. +class SuppressAssert { + private: + _invalid_parameter_handler original_handler_; + int original_report_mode_; + + static void handle_invalid_parameter(const wchar_t *, + const wchar_t *, const wchar_t *, unsigned , uintptr_t) {} + + public: + SuppressAssert() + : original_handler_(_set_invalid_parameter_handler(handle_invalid_parameter)), + original_report_mode_(_CrtSetReportMode(_CRT_ASSERT, 0)) { + } + ~SuppressAssert() { + _set_invalid_parameter_handler(original_handler_); + _CrtSetReportMode(_CRT_ASSERT, original_report_mode_); + } +}; + +# define SUPPRESS_ASSERT(statement) { SuppressAssert sa; statement; } +#else +# define SUPPRESS_ASSERT(statement) statement +#endif // _MSC_VER + +#define EXPECT_SYSTEM_ERROR_NOASSERT(statement, error_code, message) \ + EXPECT_SYSTEM_ERROR(SUPPRESS_ASSERT(statement), error_code, message) + +// Attempts to read count characters from a file. +std::string read(fmt::File &f, std::size_t count); + +#define EXPECT_READ(file, expected_content) \ + EXPECT_EQ(expected_content, read(file, std::strlen(expected_content))) + #endif // FMT_USE_FILE_DESCRIPTORS #endif // FMT_GTEST_EXTRA_H_ diff --git a/test/posix-mock-test.cc b/test/posix-mock-test.cc index efd47e21..0c513403 100644 --- a/test/posix-mock-test.cc +++ b/test/posix-mock-test.cc @@ -42,6 +42,7 @@ #endif #include "gtest-extra.h" +#include "util.h" using fmt::BufferedFile; using fmt::ErrorCode; diff --git a/test/posix-test.cc b/test/posix-test.cc new file mode 100644 index 00000000..3f897741 --- /dev/null +++ b/test/posix-test.cc @@ -0,0 +1,376 @@ +/* + Tests of the C++ interface to POSIX functions + + Copyright (c) 2015, Victor Zverovich + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include + +#include "gtest-extra.h" +#include "posix.h" +#include "util.h" + +using fmt::BufferedFile; +using fmt::ErrorCode; +using fmt::File; + +// Checks if the file is open by reading one character from it. +bool isopen(int fd) { + char buffer; + return FMT_POSIX(read(fd, &buffer, 1)) == 1; +} + +bool isclosed(int fd) { + char buffer; + std::streamsize result = 0; + SUPPRESS_ASSERT(result = FMT_POSIX(read(fd, &buffer, 1))); + return result == -1 && errno == EBADF; +} + +// Opens a file for reading. +File open_file() { + File read_end, write_end; + File::pipe(read_end, write_end); + write_end.write(FILE_CONTENT, std::strlen(FILE_CONTENT)); + write_end.close(); + return read_end; +} + +// Attempts to write a string to a file. +void write(File &f, fmt::StringRef s) { + std::size_t num_chars_left = s.size(); + const char *ptr = s.c_str(); + do { + std::streamsize count = f.write(ptr, num_chars_left); + ptr += count; + // We can't write more than size_t bytes since num_chars_left + // has type size_t. + num_chars_left -= static_cast(count); + } while (num_chars_left != 0); +} + +TEST(BufferedFileTest, DefaultCtor) { + BufferedFile f; + EXPECT_TRUE(f.get() == 0); +} + +TEST(BufferedFileTest, MoveCtor) { + BufferedFile bf = open_buffered_file(); + FILE *fp = bf.get(); + EXPECT_TRUE(fp != 0); + BufferedFile bf2(std::move(bf)); + EXPECT_EQ(fp, bf2.get()); + EXPECT_TRUE(bf.get() == 0); +} + +TEST(BufferedFileTest, MoveAssignment) { + BufferedFile bf = open_buffered_file(); + FILE *fp = bf.get(); + EXPECT_TRUE(fp != 0); + BufferedFile bf2; + bf2 = std::move(bf); + EXPECT_EQ(fp, bf2.get()); + EXPECT_TRUE(bf.get() == 0); +} + +TEST(BufferedFileTest, MoveAssignmentClosesFile) { + BufferedFile bf = open_buffered_file(); + BufferedFile bf2 = open_buffered_file(); + int old_fd = bf2.fileno(); + bf2 = std::move(bf); + EXPECT_TRUE(isclosed(old_fd)); +} + +TEST(BufferedFileTest, MoveFromTemporaryInCtor) { + FILE *fp = 0; + BufferedFile f(open_buffered_file(&fp)); + EXPECT_EQ(fp, f.get()); +} + +TEST(BufferedFileTest, MoveFromTemporaryInAssignment) { + FILE *fp = 0; + BufferedFile f; + f = open_buffered_file(&fp); + EXPECT_EQ(fp, f.get()); +} + +TEST(BufferedFileTest, MoveFromTemporaryInAssignmentClosesFile) { + BufferedFile f = open_buffered_file(); + int old_fd = f.fileno(); + f = open_buffered_file(); + EXPECT_TRUE(isclosed(old_fd)); +} + +TEST(BufferedFileTest, CloseFileInDtor) { + int fd = 0; + { + BufferedFile f = open_buffered_file(); + fd = f.fileno(); + } + EXPECT_TRUE(isclosed(fd)); +} + +TEST(BufferedFileTest, CloseErrorInDtor) { + BufferedFile *f = new BufferedFile(open_buffered_file()); + EXPECT_WRITE(stderr, { + // The close function must be called inside EXPECT_WRITE, otherwise + // the system may recycle closed file descriptor when redirecting the + // output in EXPECT_STDERR and the second close will break output + // redirection. + FMT_POSIX(close(f->fileno())); + SUPPRESS_ASSERT(delete f); + }, format_system_error(EBADF, "cannot close file") + "\n"); +} + +TEST(BufferedFileTest, Close) { + BufferedFile f = open_buffered_file(); + int fd = f.fileno(); + f.close(); + EXPECT_TRUE(f.get() == 0); + EXPECT_TRUE(isclosed(fd)); +} + +TEST(BufferedFileTest, CloseError) { + BufferedFile f = open_buffered_file(); + FMT_POSIX(close(f.fileno())); + EXPECT_SYSTEM_ERROR_NOASSERT(f.close(), EBADF, "cannot close file"); + EXPECT_TRUE(f.get() == 0); +} + +TEST(BufferedFileTest, Fileno) { + BufferedFile f; + // fileno on a null FILE pointer either crashes or returns an error. + EXPECT_DEATH_IF_SUPPORTED({ + try { + f.fileno(); + } catch (fmt::SystemError) { + std::exit(1); + } + }, ""); + f = open_buffered_file(); + EXPECT_TRUE(f.fileno() != -1); + File copy = File::dup(f.fileno()); + EXPECT_READ(copy, FILE_CONTENT); +} + +TEST(FileTest, DefaultCtor) { + File f; + EXPECT_EQ(-1, f.descriptor()); +} + +TEST(FileTest, OpenBufferedFileInCtor) { + FILE *fp = fopen("test-file", "w"); + std::fputs(FILE_CONTENT, fp); + std::fclose(fp); + File f("test-file", File::RDONLY); + ASSERT_TRUE(isopen(f.descriptor())); +} + +TEST(FileTest, OpenBufferedFileError) { + EXPECT_SYSTEM_ERROR(File("nonexistent", File::RDONLY), + ENOENT, "cannot open file nonexistent"); +} + +TEST(FileTest, MoveCtor) { + File f = open_file(); + int fd = f.descriptor(); + EXPECT_NE(-1, fd); + File f2(std::move(f)); + EXPECT_EQ(fd, f2.descriptor()); + EXPECT_EQ(-1, f.descriptor()); +} + +TEST(FileTest, MoveAssignment) { + File f = open_file(); + int fd = f.descriptor(); + EXPECT_NE(-1, fd); + File f2; + f2 = std::move(f); + EXPECT_EQ(fd, f2.descriptor()); + EXPECT_EQ(-1, f.descriptor()); +} + +TEST(FileTest, MoveAssignmentClosesFile) { + File f = open_file(); + File f2 = open_file(); + int old_fd = f2.descriptor(); + f2 = std::move(f); + EXPECT_TRUE(isclosed(old_fd)); +} + +File OpenBufferedFile(int &fd) { + File f = open_file(); + fd = f.descriptor(); + return std::move(f); +} + +TEST(FileTest, MoveFromTemporaryInCtor) { + int fd = 0xdeadbeef; + File f(OpenBufferedFile(fd)); + EXPECT_EQ(fd, f.descriptor()); +} + +TEST(FileTest, MoveFromTemporaryInAssignment) { + int fd = 0xdeadbeef; + File f; + f = OpenBufferedFile(fd); + EXPECT_EQ(fd, f.descriptor()); +} + +TEST(FileTest, MoveFromTemporaryInAssignmentClosesFile) { + int fd = 0xdeadbeef; + File f = open_file(); + int old_fd = f.descriptor(); + f = OpenBufferedFile(fd); + EXPECT_TRUE(isclosed(old_fd)); +} + +TEST(FileTest, CloseFileInDtor) { + int fd = 0; + { + File f = open_file(); + fd = f.descriptor(); + } + EXPECT_TRUE(isclosed(fd)); +} + +TEST(FileTest, CloseErrorInDtor) { + File *f = new File(open_file()); + EXPECT_WRITE(stderr, { + // The close function must be called inside EXPECT_WRITE, otherwise + // the system may recycle closed file descriptor when redirecting the + // output in EXPECT_STDERR and the second close will break output + // redirection. + FMT_POSIX(close(f->descriptor())); + SUPPRESS_ASSERT(delete f); + }, format_system_error(EBADF, "cannot close file") + "\n"); +} + +TEST(FileTest, Close) { + File f = open_file(); + int fd = f.descriptor(); + f.close(); + EXPECT_EQ(-1, f.descriptor()); + EXPECT_TRUE(isclosed(fd)); +} + +TEST(FileTest, CloseError) { + File f = open_file(); + FMT_POSIX(close(f.descriptor())); + EXPECT_SYSTEM_ERROR_NOASSERT(f.close(), EBADF, "cannot close file"); + EXPECT_EQ(-1, f.descriptor()); +} + +TEST(FileTest, Read) { + File f = open_file(); + EXPECT_READ(f, FILE_CONTENT); +} + +TEST(FileTest, ReadError) { + File read_end, write_end; + File::pipe(read_end, write_end); + char buf; + // We intentionally read from write_end to cause error. + EXPECT_SYSTEM_ERROR(write_end.read(&buf, 1), EBADF, "cannot read from file"); +} + +TEST(FileTest, Write) { + File read_end, write_end; + File::pipe(read_end, write_end); + write(write_end, "test"); + write_end.close(); + EXPECT_READ(read_end, "test"); +} + +TEST(FileTest, WriteError) { + File read_end, write_end; + File::pipe(read_end, write_end); + // We intentionally write to read_end to cause error. + EXPECT_SYSTEM_ERROR(read_end.write(" ", 1), EBADF, "cannot write to file"); +} + +TEST(FileTest, Dup) { + File f = open_file(); + File copy = File::dup(f.descriptor()); + EXPECT_NE(f.descriptor(), copy.descriptor()); + EXPECT_EQ(FILE_CONTENT, read(copy, std::strlen(FILE_CONTENT))); +} + +TEST(FileTest, DupError) { + EXPECT_SYSTEM_ERROR_NOASSERT(File::dup(-1), + EBADF, "cannot duplicate file descriptor -1"); +} + +TEST(FileTest, Dup2) { + File f = open_file(); + File copy = open_file(); + f.dup2(copy.descriptor()); + EXPECT_NE(f.descriptor(), copy.descriptor()); + EXPECT_READ(copy, FILE_CONTENT); +} + +TEST(FileTest, Dup2Error) { + File f = open_file(); + EXPECT_SYSTEM_ERROR_NOASSERT(f.dup2(-1), EBADF, + fmt::format("cannot duplicate file descriptor {} to -1", f.descriptor())); +} + +TEST(FileTest, Dup2NoExcept) { + File f = open_file(); + File copy = open_file(); + ErrorCode ec; + f.dup2(copy.descriptor(), ec); + EXPECT_EQ(0, ec.get()); + EXPECT_NE(f.descriptor(), copy.descriptor()); + EXPECT_READ(copy, FILE_CONTENT); +} + +TEST(FileTest, Dup2NoExceptError) { + File f = open_file(); + ErrorCode ec; + SUPPRESS_ASSERT(f.dup2(-1, ec)); + EXPECT_EQ(EBADF, ec.get()); +} + +TEST(FileTest, Pipe) { + File read_end, write_end; + File::pipe(read_end, write_end); + EXPECT_NE(-1, read_end.descriptor()); + EXPECT_NE(-1, write_end.descriptor()); + write(write_end, "test"); + EXPECT_READ(read_end, "test"); +} + +TEST(FileTest, Fdopen) { + File read_end, write_end; + File::pipe(read_end, write_end); + int read_fd = read_end.descriptor(); + EXPECT_EQ(read_fd, FMT_POSIX(fileno(read_end.fdopen("r").get()))); +} + +TEST(FileTest, FdopenError) { + File f; + EXPECT_SYSTEM_ERROR_NOASSERT( + f.fdopen("r"), EBADF, "cannot associate stream with file descriptor"); +} diff --git a/test/util.cc b/test/util.cc index 24cea433..ab9667c2 100644 --- a/test/util.cc +++ b/test/util.cc @@ -49,3 +49,16 @@ std::string get_system_error(int error_code) { return buffer; #endif } + +const char FILE_CONTENT[] = "Don't panic!"; + +fmt::BufferedFile open_buffered_file(FILE **fp) { + fmt::File read_end, write_end; + fmt::File::pipe(read_end, write_end); + write_end.write(FILE_CONTENT, sizeof(FILE_CONTENT) - 1); + write_end.close(); + fmt::BufferedFile f = read_end.fdopen("r"); + if (fp) + *fp = f.get(); + return f; +} diff --git a/test/util.h b/test/util.h index fe84a906..e6e0982a 100644 --- a/test/util.h +++ b/test/util.h @@ -29,6 +29,8 @@ #include #include +#include "posix.h" + enum {BUFFER_SIZE = 256}; #ifdef _MSC_VER @@ -49,3 +51,8 @@ void safe_sprintf(char (&buffer)[SIZE], const char *format, ...) { void increment(char *s); std::string get_system_error(int error_code); + +extern const char FILE_CONTENT[]; + +// Opens a buffered file for reading. +fmt::BufferedFile open_buffered_file(FILE **fp = 0);