Implement EXPECT_STDOUT and EXPECT_STDERR using pipes.

This commit is contained in:
Victor Zverovich 2014-05-03 09:48:54 -07:00
parent dd0120c189
commit 1a2d7be3f5
8 changed files with 709 additions and 189 deletions

View File

@ -69,22 +69,24 @@ endif ()
include(CheckSymbolExists) include(CheckSymbolExists)
if (WIN32) if (WIN32)
check_symbol_exists(dup io.h HAVE_DUP) check_symbol_exists(open io.h HAVE_OPEN)
else () else ()
check_symbol_exists(dup unistd.h HAVE_DUP) check_symbol_exists(open fcntl.h HAVE_OPEN)
endif () endif ()
if (HAVE_DUP) if (HAVE_OPEN)
add_definitions(-DFMT_USE_DUP=1) add_definitions(-DFMT_USE_FILE_DESCRIPTORS=1)
endif () endif ()
enable_testing() enable_testing()
include_directories(.) include_directories(.)
add_library(test-main test/test-main.cc) add_library(test-main
test/test-main.cc test/gtest-extra.cc test/gtest-extra.h)
target_link_libraries(test-main gtest format)
cxx_test(assert-test "gtest;test-main") cxx_test(gtest-extra-test test-main)
cxx_test(format-test "format;gtest;test-main") cxx_test(format-test test-main)
if (CMAKE_COMPILER_IS_GNUCXX) if (CMAKE_COMPILER_IS_GNUCXX)
set_target_properties(format-test PROPERTIES COMPILE_FLAGS set_target_properties(format-test PROPERTIES COMPILE_FLAGS
"-Wall -Wextra -pedantic -Wno-long-long -Wno-variadic-macros") "-Wall -Wextra -pedantic -Wno-long-long -Wno-variadic-macros")

View File

@ -1,5 +1,5 @@
/* /*
String formatting library for C++ Formatting library for C++
Copyright (c) 2012, Victor Zverovich Copyright (c) 2012, Victor Zverovich
All rights reserved. All rights reserved.
@ -101,7 +101,19 @@ inline int FMT_SNPRINTF(char *buffer, size_t size, const char *format, ...) {
#endif // _MSC_VER #endif // _MSC_VER
const char RESET_COLOR[] = "\x1b[0m"; const char RESET_COLOR[] = "\x1b[0m";
typedef void (*FormatFunc)(fmt::Writer &, int , fmt::StringRef);
void ReportError(FormatFunc func,
int error_code, fmt::StringRef message) FMT_NOEXCEPT(true) {
try {
fmt::Writer full_message;
func(full_message, error_code, message); // TODO: this may throw?
std::fwrite(full_message.c_str(), full_message.size(), 1, stderr);
std::fputc('\n', stderr);
} catch (...) {}
} }
} // namespace
template <typename T> template <typename T>
int fmt::internal::CharTraits<char>::FormatFloat( int fmt::internal::CharTraits<char>::FormatFloat(
@ -206,7 +218,7 @@ int fmt::internal::UTF16ToUTF8::Convert(fmt::WStringRef s) {
#endif #endif
int fmt::internal::StrError( int fmt::internal::StrError(
int error_code, char *&buffer, std::size_t buffer_size) { int error_code, char *&buffer, std::size_t buffer_size) FMT_NOEXCEPT(true) {
assert(buffer != 0 && buffer_size != 0); assert(buffer != 0 && buffer_size != 0);
int result = 0; int result = 0;
#ifdef _GNU_SOURCE #ifdef _GNU_SOURCE
@ -809,12 +821,24 @@ void fmt::SystemErrorSink::operator()(const fmt::Writer &w) const {
throw SystemError(message.c_str(), error_code_); throw SystemError(message.c_str(), error_code_);
} }
void fmt::ReportSystemError(
int error_code, fmt::StringRef message) FMT_NOEXCEPT(true) {
// FIXME: FormatSystemErrorMessage may throw
ReportError(internal::FormatSystemErrorMessage, error_code, message);
}
#ifdef _WIN32 #ifdef _WIN32
void fmt::WinErrorSink::operator()(const Writer &w) const { void fmt::WinErrorSink::operator()(const Writer &w) const {
Writer message; Writer message;
internal::FormatWinErrorMessage(message, error_code_, w.c_str()); internal::FormatWinErrorMessage(message, error_code_, w.c_str());
throw SystemError(message.c_str(), error_code_); throw SystemError(message.c_str(), error_code_);
} }
void fmt::ReportWinError(
int error_code, fmt::StringRef message) FMT_NOEXCEPT(true) {
// FIXME: FormatWinErrorMessage may throw
ReportError(internal::FormatWinErrorMessage, error_code, message);
}
#endif #endif
void fmt::ANSITerminalSink::operator()( void fmt::ANSITerminalSink::operator()(

View File

@ -1,5 +1,5 @@
/* /*
String formatting library for C++ Formatting library for C++
Copyright (c) 2012, Victor Zverovich Copyright (c) 2012, Victor Zverovich
All rights reserved. All rights reserved.
@ -498,7 +498,8 @@ class UTF16ToUTF8 {
// ERANGE - buffer is not large enough to store the error message // ERANGE - buffer is not large enough to store the error message
// other - failure // other - failure
// Buffer should be at least of size 1. // Buffer should be at least of size 1.
int StrError(int error_code, char *&buffer, std::size_t buffer_size); int StrError(int error_code,
char *&buffer, std::size_t buffer_size) FMT_NOEXCEPT(true);
void FormatSystemErrorMessage( void FormatSystemErrorMessage(
fmt::Writer &out, int error_code, fmt::StringRef message); fmt::Writer &out, int error_code, fmt::StringRef message);
@ -1563,6 +1564,12 @@ inline Formatter<SystemErrorSink> ThrowSystemError(
return f; return f;
} }
// Reports a system error without throwing an exception.
// Can be used to report errors from destructors.
void ReportSystemError(int error_code, StringRef message) FMT_NOEXCEPT(true);
#ifdef _WIN32
/** /**
A sink that gets the error message corresponding to a Windows error code A sink that gets the error message corresponding to a Windows error code
as given by GetLastError and throws SystemError. as given by GetLastError and throws SystemError.
@ -1588,6 +1595,12 @@ inline Formatter<WinErrorSink> ThrowWinError(int error_code, StringRef format) {
return f; return f;
} }
// Reports a Windows error without throwing an exception.
// Can be used to report errors from destructors.
void ReportWinError(int error_code, StringRef message) FMT_NOEXCEPT(true);
#endif
/** A sink that writes output to a file. */ /** A sink that writes output to a file. */
class FileSink { class FileSink {
private: private:

View File

@ -1,130 +0,0 @@
/*
Tests of custom Google Test assertions.
Copyright (c) 2012-2014, 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 "gtest-extra.h"
#include <stdexcept>
#include <gtest/gtest-spi.h>
namespace {
// Tests that assertion macros evaluate their arguments exactly once.
class SingleEvaluationTest : public ::testing::Test {
protected:
SingleEvaluationTest() {
a_ = 0;
}
static int a_;
};
int SingleEvaluationTest::a_;
void ThrowNothing() {}
void ThrowException() {
throw std::runtime_error("test");
}
// Tests that assertion arguments are evaluated exactly once.
TEST_F(SingleEvaluationTest, ExceptionTests) {
// successful EXPECT_THROW_MSG
EXPECT_THROW_MSG({ // NOLINT
a_++;
ThrowException();
}, std::exception, "test");
EXPECT_EQ(1, a_);
// failed EXPECT_THROW_MSG, throws different type
EXPECT_NONFATAL_FAILURE(EXPECT_THROW_MSG({ // NOLINT
a_++;
ThrowException();
}, std::logic_error, "test"), "throws a different type");
EXPECT_EQ(2, a_);
// failed EXPECT_THROW_MSG, throws an exception with different message
EXPECT_NONFATAL_FAILURE(EXPECT_THROW_MSG({ // NOLINT
a_++;
ThrowException();
}, std::exception, "other"), "throws an exception with a different message");
EXPECT_EQ(3, a_);
// failed EXPECT_THROW_MSG, throws nothing
EXPECT_NONFATAL_FAILURE(
EXPECT_THROW_MSG(a_++, std::exception, "test"), "throws nothing");
EXPECT_EQ(4, a_);
}
// Tests that the compiler will not complain about unreachable code in the
// EXPECT_THROW_MSG macro.
TEST(ExpectThrowTest, DoesNotGenerateUnreachableCodeWarning) {
int n = 0;
using std::runtime_error;
EXPECT_THROW_MSG(throw runtime_error(""), runtime_error, "");
EXPECT_NONFATAL_FAILURE(EXPECT_THROW_MSG(n++, runtime_error, ""), "");
EXPECT_NONFATAL_FAILURE(EXPECT_THROW_MSG(throw 1, runtime_error, ""), "");
EXPECT_NONFATAL_FAILURE(EXPECT_THROW_MSG(
throw runtime_error("a"), runtime_error, "b"), "");
}
TEST(AssertionSyntaxTest, ExceptionAssertionsBehavesLikeSingleStatement) {
if (::testing::internal::AlwaysFalse())
EXPECT_THROW_MSG(ThrowNothing(), std::exception, "");
if (::testing::internal::AlwaysTrue())
EXPECT_THROW_MSG(ThrowException(), std::exception, "test");
else
; // NOLINT
}
// Tests EXPECT_THROW_MSG.
TEST(ExpectTest, EXPECT_THROW_MSG) {
EXPECT_THROW_MSG(ThrowException(), std::exception, "test");
EXPECT_NONFATAL_FAILURE(
EXPECT_THROW_MSG(ThrowException(), std::logic_error, "test"),
"Expected: ThrowException() throws an exception of "
"type std::logic_error.\n Actual: it throws a different type.");
EXPECT_NONFATAL_FAILURE(
EXPECT_THROW_MSG(ThrowNothing(), std::exception, "test"),
"Expected: ThrowNothing() throws an exception of type std::exception.\n"
" Actual: it throws nothing.");
EXPECT_NONFATAL_FAILURE(
EXPECT_THROW_MSG(ThrowException(), std::exception, "other"),
"ThrowException() throws an exception with a different message.\n"
"Expected: other\n"
" Actual: test");
}
TEST(StreamingAssertionsTest, ThrowMsg) {
EXPECT_THROW_MSG(ThrowException(), std::exception, "test")
<< "unexpected failure";
EXPECT_NONFATAL_FAILURE(
EXPECT_THROW_MSG(ThrowException(), std::exception, "other")
<< "expected failure", "expected failure");
}
} // namespace

View File

@ -40,37 +40,6 @@
# include <windows.h> # include <windows.h>
#endif #endif
#if FMT_USE_DUP
# include <sys/types.h>
# include <sys/stat.h>
# include <fcntl.h>
# ifdef _WIN32
# include <io.h>
# define O_WRONLY _O_WRONLY
# define O_CREAT _O_CREAT
# define O_TRUNC _O_TRUNC
# define S_IRUSR _S_IREAD
# define S_IWUSR _S_IWRITE
# define close _close
# define dup _dup
# define dup2 _dup2
namespace {
int open(const char *path, int oflag, int pmode) {
int fd = -1;
_sopen_s(&fd, path, oflag, _SH_DENYNO, pmode);
return fd;
}
}
# else
# include <unistd.h>
# endif
#endif
#include "format.h" #include "format.h"
#include "gtest-extra.h" #include "gtest-extra.h"
@ -1830,25 +1799,12 @@ TEST(FormatIntTest, FormatDec) {
EXPECT_EQ("42", FormatDec(42ull)); EXPECT_EQ("42", FormatDec(42ull));
} }
#ifdef FMT_USE_DUP #ifdef FMT_USE_FILE_DESCRIPTORS
// TODO: implement EXPECT_PRINT
TEST(FormatTest, PrintColored) { TEST(FormatTest, PrintColored) {
// Temporarily redirect stdout to a file and check if PrintColored adds EXPECT_STDOUT(
// necessary ANSI escape sequences. fmt::PrintColored(fmt::RED, "Hello, {}!\n") << "world",
std::fflush(stdout); "\x1b[31mHello, world!\n\x1b[0m");
int saved_stdio = dup(1);
EXPECT_NE(-1, saved_stdio);
int out = open("out", O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR);
EXPECT_NE(-1, out);
EXPECT_NE(-1, dup2(out, 1));
close(out);
fmt::PrintColored(fmt::RED, "Hello, {}!\n") << "world";
std::fflush(stdout);
EXPECT_NE(-1, dup2(saved_stdio, 1));
close(saved_stdio);
EXPECT_EQ("\x1b[31mHello, world!\n\x1b[0m", ReadFile("out"));
} }
#endif #endif

313
test/gtest-extra-test.cc Normal file
View File

@ -0,0 +1,313 @@
/*
Tests of custom Google Test assertions.
Copyright (c) 2012-2014, 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 "gtest-extra.h"
#include <algorithm>
#include <stdexcept>
#include <gtest/gtest-spi.h>
namespace {
// Tests that assertion macros evaluate their arguments exactly once.
class SingleEvaluationTest : public ::testing::Test {
protected:
SingleEvaluationTest() {
a_ = 0;
}
static int a_;
};
int SingleEvaluationTest::a_;
void ThrowNothing() {}
void ThrowException() {
throw std::runtime_error("test");
}
// Tests that assertion arguments are evaluated exactly once.
TEST_F(SingleEvaluationTest, ExceptionTests) {
// successful EXPECT_THROW_MSG
EXPECT_THROW_MSG({ // NOLINT
a_++;
ThrowException();
}, std::exception, "test");
EXPECT_EQ(1, a_);
// failed EXPECT_THROW_MSG, throws different type
EXPECT_NONFATAL_FAILURE(EXPECT_THROW_MSG({ // NOLINT
a_++;
ThrowException();
}, std::logic_error, "test"), "throws a different type");
EXPECT_EQ(2, a_);
// failed EXPECT_THROW_MSG, throws an exception with different message
EXPECT_NONFATAL_FAILURE(EXPECT_THROW_MSG({ // NOLINT
a_++;
ThrowException();
}, std::exception, "other"), "throws an exception with a different message");
EXPECT_EQ(3, a_);
// failed EXPECT_THROW_MSG, throws nothing
EXPECT_NONFATAL_FAILURE(
EXPECT_THROW_MSG(a_++, std::exception, "test"), "throws nothing");
EXPECT_EQ(4, a_);
}
// Tests that the compiler will not complain about unreachable code in the
// EXPECT_THROW_MSG macro.
TEST(ExpectThrowTest, DoesNotGenerateUnreachableCodeWarning) {
int n = 0;
using std::runtime_error;
EXPECT_THROW_MSG(throw runtime_error(""), runtime_error, "");
EXPECT_NONFATAL_FAILURE(EXPECT_THROW_MSG(n++, runtime_error, ""), "");
EXPECT_NONFATAL_FAILURE(EXPECT_THROW_MSG(throw 1, runtime_error, ""), "");
EXPECT_NONFATAL_FAILURE(EXPECT_THROW_MSG(
throw runtime_error("a"), runtime_error, "b"), "");
}
TEST(AssertionSyntaxTest, ExceptionAssertionsBehavesLikeSingleStatement) {
if (::testing::internal::AlwaysFalse())
EXPECT_THROW_MSG(ThrowNothing(), std::exception, "");
if (::testing::internal::AlwaysTrue())
EXPECT_THROW_MSG(ThrowException(), std::exception, "test");
else
; // NOLINT
}
// Tests EXPECT_THROW_MSG.
TEST(ExpectTest, EXPECT_THROW_MSG) {
EXPECT_THROW_MSG(ThrowException(), std::exception, "test");
EXPECT_NONFATAL_FAILURE(
EXPECT_THROW_MSG(ThrowException(), std::logic_error, "test"),
"Expected: ThrowException() throws an exception of "
"type std::logic_error.\n Actual: it throws a different type.");
EXPECT_NONFATAL_FAILURE(
EXPECT_THROW_MSG(ThrowNothing(), std::exception, "test"),
"Expected: ThrowNothing() throws an exception of type std::exception.\n"
" Actual: it throws nothing.");
EXPECT_NONFATAL_FAILURE(
EXPECT_THROW_MSG(ThrowException(), std::exception, "other"),
"ThrowException() throws an exception with a different message.\n"
"Expected: other\n"
" Actual: test");
}
TEST(StreamingAssertionsTest, ThrowMsg) {
EXPECT_THROW_MSG(ThrowException(), std::exception, "test")
<< "unexpected failure";
EXPECT_NONFATAL_FAILURE(
EXPECT_THROW_MSG(ThrowException(), std::exception, "other")
<< "expected failure", "expected failure");
}
#if FMT_USE_FILE_DESCRIPTORS
TEST(ErrorCodeTest, Ctor) {
EXPECT_EQ(0, ErrorCode().get());
EXPECT_EQ(42, ErrorCode(42).get());
}
TEST(FileDescriptorTest, DefaultCtor) {
FileDescriptor fd;
EXPECT_EQ(-1, fd.get());
}
TEST(FileDescriptorTest, OpenFileInCtor) {
FILE *f = 0;
{
FileDescriptor fd(".travis.yml", FileDescriptor::RDONLY);
f = fdopen(fd.get(), "r");
ASSERT_TRUE(f != 0);
}
// Make sure fclose is called after the file descriptor is destroyed.
// Otherwise the destructor will report an error because fclose has
// already closed the file.
fclose(f);
}
TEST(FileDescriptorTest, OpenFileError) {
fmt::Writer message;
fmt::internal::FormatSystemErrorMessage(
message, ENOENT, "cannot open file nonexistent");
EXPECT_THROW_MSG(FileDescriptor("nonexistent", FileDescriptor::RDONLY),
fmt::SystemError, str(message));
}
TEST(FileDescriptorTest, MoveCtor) {
FileDescriptor fd(".travis.yml", FileDescriptor::RDONLY);
int fd_value = fd.get();
EXPECT_NE(-1, fd_value);
FileDescriptor fd2(std::move(fd));
EXPECT_EQ(fd_value, fd2.get());
EXPECT_EQ(-1, fd.get());
}
TEST(FileDescriptorTest, MoveAssignment) {
FileDescriptor fd(".travis.yml", FileDescriptor::RDONLY);
int fd_value = fd.get();
EXPECT_NE(-1, fd_value);
FileDescriptor fd2;
fd2 = std::move(fd);
EXPECT_EQ(fd_value, fd2.get());
EXPECT_EQ(-1, fd.get());
}
bool IsClosed(int fd) {
char buffer[1];
ssize_t result = read(fd, buffer, sizeof(buffer));
return result == -1 && errno == EBADF;
}
TEST(FileDescriptorTest, MoveAssignmentClosesFile) {
FileDescriptor fd(".travis.yml", FileDescriptor::RDONLY);
FileDescriptor fd2("CMakeLists.txt", FileDescriptor::RDONLY);
int old_fd = fd2.get();
fd2 = std::move(fd);
EXPECT_TRUE(IsClosed(old_fd));
}
FileDescriptor OpenFile(int &fd_value) {
FileDescriptor fd(".travis.yml", FileDescriptor::RDONLY);
fd_value = fd.get();
return std::move(fd);
}
TEST(FileDescriptorTest, MoveFromTemporaryInCtor) {
int fd_value = 0xdeadbeef;
FileDescriptor fd(OpenFile(fd_value));
EXPECT_EQ(fd_value, fd.get());
}
TEST(FileDescriptorTest, MoveFromTemporaryInAssignment) {
int fd_value = 0xdeadbeef;
FileDescriptor fd;
fd = OpenFile(fd_value);
EXPECT_EQ(fd_value, fd.get());
}
TEST(FileDescriptorTest, MoveFromTemporaryInAssignmentClosesFile) {
int fd_value = 0xdeadbeef;
FileDescriptor fd(".travis.yml", FileDescriptor::RDONLY);
int old_fd = fd.get();
fd = OpenFile(fd_value);
EXPECT_TRUE(IsClosed(old_fd));
}
TEST(FileDescriptorTest, CloseFileInDtor) {
int fd_value = 0;
{
FileDescriptor fd(".travis.yml", FileDescriptor::RDONLY);
fd_value = fd.get();
}
FILE *f = fdopen(fd_value, "r");
int error_code = errno;
if (f)
fclose(f);
EXPECT_TRUE(f == 0);
EXPECT_EQ(EBADF, error_code);
}
TEST(FileDescriptorTest, CloseError) {
FileDescriptor *fd =
new FileDescriptor(".travis.yml", FileDescriptor::RDONLY);
fmt::Writer message;
fmt::internal::FormatSystemErrorMessage(message, EBADF, "cannot close file");
EXPECT_STDERR(close(fd->get()); delete fd, str(message) + "\n");
}
std::string ReadLine(FileDescriptor &fd) {
enum { BUFFER_SIZE = 100 };
char buffer[BUFFER_SIZE];
ssize_t result = read(fd.get(), buffer, BUFFER_SIZE);
if (result == -1)
fmt::ThrowSystemError(errno, "cannot read file");
buffer[std::min<ssize_t>(BUFFER_SIZE - 1, result)] = '\0';
if (char *end = strchr(buffer, '\n'))
*end = '\0';
return buffer;
}
TEST(FileDescriptorTest, Dup) {
FileDescriptor fd(".travis.yml", FileDescriptor::RDONLY);
FileDescriptor dup = FileDescriptor::dup(fd.get());
EXPECT_NE(fd.get(), dup.get());
EXPECT_EQ("language: cpp", ReadLine(dup));
}
TEST(FileDescriptorTest, DupError) {
fmt::Writer message;
fmt::internal::FormatSystemErrorMessage(
message, EBADF, "cannot duplicate file descriptor -1");
EXPECT_THROW_MSG(FileDescriptor::dup(-1), fmt::SystemError, str(message));
}
TEST(FileDescriptorTest, Dup2) {
FileDescriptor fd(".travis.yml", FileDescriptor::RDONLY);
FileDescriptor dup("CMakeLists.txt", FileDescriptor::RDONLY);
fd.dup2(dup.get());
EXPECT_NE(fd.get(), dup.get());
EXPECT_EQ("language: cpp", ReadLine(dup));
}
TEST(FileDescriptorTest, Dup2Error) {
FileDescriptor fd(".travis.yml", FileDescriptor::RDONLY);
fmt::Writer message;
fmt::internal::FormatSystemErrorMessage(message, EBADF,
fmt::Format("cannot duplicate file descriptor {} to -1") << fd.get());
EXPECT_THROW_MSG(fd.dup2(-1), fmt::SystemError, str(message));
}
TEST(FileDescriptorTest, Dup2NoExcept) {
FileDescriptor fd(".travis.yml", FileDescriptor::RDONLY);
FileDescriptor dup("CMakeLists.txt", FileDescriptor::RDONLY);
ErrorCode ec;
fd.dup2(dup.get(), ec);
EXPECT_EQ(0, ec.get());
EXPECT_NE(fd.get(), dup.get());
EXPECT_EQ("language: cpp", ReadLine(dup));
}
TEST(FileDescriptorTest, Dup2NoExceptError) {
FileDescriptor fd(".travis.yml", FileDescriptor::RDONLY);
ErrorCode ec;
fd.dup2(-1, ec);
EXPECT_EQ(EBADF, ec.get());
}
// TODO: test pipe
// TODO: compile both with C++11 & C++98 mode
#endif
// TODO: test OutputRedirector
} // namespace

152
test/gtest-extra.cc Normal file
View File

@ -0,0 +1,152 @@
/*
Custom Google Test assertions.
Copyright (c) 2012-2014, 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 "gtest-extra.h"
#if FMT_USE_FILE_DESCRIPTORS
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#ifndef _WIN32
# include <unistd.h>
#else
# include <io.h>
# define O_CREAT _O_CREAT
# define O_TRUNC _O_TRUNC
# define S_IRUSR _S_IREAD
# define S_IWUSR _S_IWRITE
# define close _close
# define dup _dup
# define dup2 _dup2
#endif // _WIN32
// Retries the expression while it evaluates to -1 and error equals to EINTR.
#define FMT_RETRY(result, expression) \
do { \
result = (expression); \
} while (result == -1 && errno == EINTR)
FileDescriptor::FileDescriptor(const char *path, int oflag) {
int mode = S_IRUSR | S_IWUSR;
#ifdef _WIN32
fd_ = -1;
_sopen_s(&fd, path, oflag, _SH_DENYNO, mode);
#else
FMT_RETRY(fd_, open(path, oflag, mode));
#endif
if (fd_ == -1)
fmt::ThrowSystemError(errno, "cannot open file {}") << path;
}
void FileDescriptor::close() {
if (fd_ == -1)
return;
// Don't need to retry close in case of EINTR.
// See http://linux.derkeiler.com/Mailing-Lists/Kernel/2005-09/3000.html
if (::close(fd_) != 0)
fmt::ReportSystemError(errno, "cannot close file");
}
FileDescriptor FileDescriptor::dup(int fd) {
int new_fd = 0;
FMT_RETRY(new_fd, ::dup(fd));
if (new_fd == -1)
fmt::ThrowSystemError(errno, "cannot duplicate file descriptor {}") << fd;
return FileDescriptor(new_fd);
}
void FileDescriptor::dup2(int fd) {
int result = 0;
FMT_RETRY(result, ::dup2(fd_, fd));
if (result == -1) {
fmt::ThrowSystemError(errno,
"cannot duplicate file descriptor {} to {}") << fd_ << fd;
}
}
void FileDescriptor::dup2(int fd, ErrorCode &ec) FMT_NOEXCEPT(true) {
int result = 0;
FMT_RETRY(result, ::dup2(fd_, fd));
if (result == -1)
ec = ErrorCode(errno);
}
void FileDescriptor::pipe(FileDescriptor &read_fd, FileDescriptor &write_fd) {
// Close the descriptors first to make sure that assignments don't throw
// and there are no leaks.
read_fd.close();
write_fd.close();
int fds[2] = {};
if (::pipe(fds) != 0)
fmt::ThrowSystemError(errno, "cannot create pipe");
// The following assignments don't throw because read_fd and write_fd
// are closed.
read_fd = FileDescriptor(fds[0]);
write_fd = FileDescriptor(fds[1]);
}
OutputRedirector::OutputRedirector(FILE *file) : file_(file) {
if (std::fflush(file) != 0)
fmt::ThrowSystemError(errno, "cannot flush stream");
int fd = fileno(file);
saved_fd_ = FileDescriptor::dup(fd);
FileDescriptor write_fd;
FileDescriptor::pipe(read_fd_, write_fd);
write_fd.dup2(fd);
}
OutputRedirector::~OutputRedirector() {
if (std::fflush(file_) != 0)
fmt::ReportSystemError(errno, "cannot flush stream");
ErrorCode ec;
saved_fd_.dup2(fileno(file_), ec);
if (ec.get())
fmt::ReportSystemError(errno, "cannot restore output");
}
std::string OutputRedirector::Read() {
// Restore output.
if (std::fflush(file_) != 0)
fmt::ThrowSystemError(errno, "cannot flush stream");
saved_fd_.dup2(fileno(file_));
// TODO: move to FileDescriptor
enum { BUFFER_SIZE = 100 };
char buffer[BUFFER_SIZE];
ssize_t result = read(read_fd_.get(), buffer, BUFFER_SIZE);
if (result == -1)
fmt::ThrowSystemError(errno, "cannot read file");
buffer[std::min<ssize_t>(BUFFER_SIZE - 1, result)] = '\0';
return buffer;
}
// TODO: test EXPECT_STDOUT and EXPECT_STDERR
#endif // FMT_USE_FILE_DESCRIPTORS

View File

@ -28,8 +28,14 @@
#ifndef FMT_GTEST_EXTRA_H #ifndef FMT_GTEST_EXTRA_H
#define FMT_GTEST_EXTRA_H #define FMT_GTEST_EXTRA_H
#if FMT_USE_FILE_DESCRIPTORS
# include <fcntl.h>
#endif
#include <gtest/gtest.h> #include <gtest/gtest.h>
#include "format.h"
#define FMT_TEST_THROW_(statement, expected_exception, expected_message, fail) \ #define FMT_TEST_THROW_(statement, expected_exception, expected_message, fail) \
GTEST_AMBIGUOUS_ELSE_BLOCKER_ \ GTEST_AMBIGUOUS_ELSE_BLOCKER_ \
if (::testing::AssertionResult gtest_ar = ::testing::AssertionSuccess()) { \ if (::testing::AssertionResult gtest_ar = ::testing::AssertionSuccess()) { \
@ -69,4 +75,188 @@
FMT_TEST_THROW_(statement, expected_exception, \ FMT_TEST_THROW_(statement, expected_exception, \
expected_message, GTEST_NONFATAL_FAILURE_) expected_message, GTEST_NONFATAL_FAILURE_)
#ifndef FMT_USE_FILE_DESCRIPTORS
# define FMT_USE_FILE_DESCRIPTORS 0
#endif
#if FMT_USE_FILE_DESCRIPTORS
#ifdef _WIN32
// Fix warnings about deprecated symbols.
# define FMT_POSIX(name) _##name
#else
# define FMT_POSIX(name) name
#endif
// An error code.
class ErrorCode {
private:
int value_;
public:
explicit ErrorCode(int value = 0) FMT_NOEXCEPT(true) : value_(value) {}
int get() const FMT_NOEXCEPT(true) { return value_; }
};
// A RAII class for file descriptors.
class FileDescriptor {
private:
int fd_;
// Closes the file if its descriptor is not -1.
void close();
// Constructs a FileDescriptor object with a given descriptor.
explicit FileDescriptor(int fd) : fd_(fd) {}
public:
// Possible values for the oflag argument to the constructor.
enum {
RDONLY = FMT_POSIX(O_RDONLY), // Open for reading only.
WRONLY = FMT_POSIX(O_WRONLY), // Open for writing only.
RDWR = FMT_POSIX(O_RDWR) // Open for reading and writing.
};
// Constructs a FileDescriptor object with a descriptor of -1 which
// is ignored by the destructor.
FileDescriptor() FMT_NOEXCEPT(true) : fd_(-1) {}
// Opens a file and constructs a FileDescriptor object with the descriptor
// of the opened file. Throws fmt::SystemError on error.
FileDescriptor(const char *path, int oflag);
#if !FMT_USE_RVALUE_REFERENCES
// Emulate a move constructor and a move assignment operator if rvalue
// references are not supported.
private:
// A proxy object to emulate a move constructor.
// It is private to make it impossible call operator Proxy directly.
struct Proxy {
int fd;
};
public:
// A "move" constructor for moving from a temporary.
FileDescriptor(Proxy p) FMT_NOEXCEPT(true) : fd_(p.fd) {}
// A "move" constructor for for moving from an lvalue.
FileDescriptor(FileDescriptor &other) FMT_NOEXCEPT(true) : fd_(other.fd_) {
other.fd_ = -1;
}
// A "move" assignment operator for moving from a temporary.
FileDescriptor &operator=(Proxy p) {
close();
fd_ = p.fd;
return *this;
}
// A "move" assignment operator for moving from an lvalue.
FileDescriptor &operator=(FileDescriptor &other) {
close();
fd_ = other.fd_;
other.fd_ = -1;
return *this;
}
// Returns a proxy object for moving from a temporary:
// FileDescriptor fd = FileDescriptor(...);
operator Proxy() FMT_NOEXCEPT(true) {
Proxy p = {fd_};
fd_ = -1;
return p;
}
#else
private:
GTEST_DISALLOW_COPY_AND_ASSIGN_(FileDescriptor);
public:
FileDescriptor(FileDescriptor &&other) FMT_NOEXCEPT(true) : fd_(other.fd_) {
other.fd_ = -1;
}
FileDescriptor& operator=(FileDescriptor &&other) FMT_NOEXCEPT(true) {
fd_ = other.fd_;
other.fd_ = -1;
return *this;
}
#endif
// Closes the file if its descriptor is not -1 and destroys the object.
~FileDescriptor() { close(); }
// Returns the file descriptor.
int get() const FMT_NOEXCEPT(true) { return fd_; }
// Duplicates a file descriptor with the dup function and returns
// the duplicate. Throws fmt::SystemError on error.
static FileDescriptor dup(int fd);
// Makes fd be the copy of this file descriptor, closing fd first if
// necessary. Throws fmt::SystemError on error.
void dup2(int fd);
// Makes fd be the copy of this file descriptor, closing fd first if
// necessary.
void dup2(int fd, ErrorCode &ec) FMT_NOEXCEPT(true);
static void pipe(FileDescriptor &read_fd, FileDescriptor &write_fd);
};
#if !FMT_USE_RVALUE_REFERENCES
namespace std {
// For compatibility with C++98.
inline FileDescriptor &move(FileDescriptor &fd) { return fd; }
}
#endif
// Redirect file output to a pipe.
class OutputRedirector {
private:
FILE *file_;
FileDescriptor saved_fd_; // Saved file descriptor created with dup.
FileDescriptor read_fd_; // Read end of the pipe where the output is
// redirected.
GTEST_DISALLOW_COPY_AND_ASSIGN_(OutputRedirector);
public:
explicit OutputRedirector(FILE *file);
~OutputRedirector();
std::string Read();
};
#define FMT_TEST_PRINT_(statement, expected_output, file, fail) \
GTEST_AMBIGUOUS_ELSE_BLOCKER_ \
if (::testing::AssertionResult gtest_ar = ::testing::AssertionSuccess()) { \
std::string output; \
{ \
OutputRedirector redir(file); \
GTEST_SUPPRESS_UNREACHABLE_CODE_WARNING_BELOW_(statement); \
output = redir.Read(); \
} \
if (output != expected_output) { \
gtest_ar \
<< #statement " produces different output.\n" \
<< "Expected: " << expected_output << "\n" \
<< " Actual: " << output; \
goto GTEST_CONCAT_TOKEN_(gtest_label_testthrow_, __LINE__); \
} \
} else \
GTEST_CONCAT_TOKEN_(gtest_label_testthrow_, __LINE__): \
fail(gtest_ar.failure_message())
// Tests that the statement prints the expected output to stdout.
#define EXPECT_STDOUT(statement, expected_output) \
FMT_TEST_PRINT_(statement, expected_output, stdout, GTEST_NONFATAL_FAILURE_)
// Tests that the statement prints the expected output to stderr.
#define EXPECT_STDERR(statement, expected_output) \
FMT_TEST_PRINT_(statement, expected_output, stderr, GTEST_NONFATAL_FAILURE_)
#endif // FMT_USE_FILE_DESCRIPTORS
#endif // FMT_GTEST_EXTRA_H #endif // FMT_GTEST_EXTRA_H