Implement EXPECT_STDOUT and EXPECT_STDERR using pipes.
This commit is contained in:
parent
dd0120c189
commit
1a2d7be3f5
@ -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")
|
||||||
|
28
format.cc
28
format.cc
@ -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()(
|
||||||
|
17
format.h
17
format.h
@ -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:
|
||||||
|
@ -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
|
|
@ -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
313
test/gtest-extra-test.cc
Normal 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
152
test/gtest-extra.cc
Normal 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
|
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user