Add a PrintTo function for rtc::Optional to aid with testing.

gtest can print objects if they have an operator<< or a PrintTo
function in the same namespace as the object's class. Since
std::optional does not seem to have an operator<<, it'd be preferable
not to rely on rtc::Optional being printable through operator<<.

Currently, gtest errors will just dump the raw bytes of
rtc::Optionals, which make them really annoying to work with in tests.

BUG=webrtc:7196

Review-Url: https://codereview.webrtc.org/2704483002
Cr-Commit-Position: refs/heads/master@{#16717}
This commit is contained in:
ossu 2017-02-20 04:41:42 -08:00 committed by Commit bot
parent 6bb8e0efd3
commit e5c27a5db6
2 changed files with 156 additions and 1 deletions

View File

@ -15,6 +15,11 @@
#include <memory>
#include <utility>
#ifdef UNIT_TEST
#include <iomanip>
#include <ostream>
#endif // UNIT_TEST
#include "webrtc/base/array_view.h"
#include "webrtc/base/checks.h"
#include "webrtc/base/sanitizer.h"
@ -25,7 +30,9 @@ namespace optional_internal {
#if RTC_HAS_ASAN
// This is a non-inlined function. The optimizer can't see inside it.
// This is a non-inlined function. The optimizer can't see inside it. It
// prevents the compiler from generating optimized code that reads value_ even
// if it is unset. Although safe, this causes memory sanitizers to complain.
void* FunctionThatDoesNothingImpl(void*);
template <typename T>
@ -296,6 +303,98 @@ class Optional final {
};
};
#ifdef UNIT_TEST
namespace optional_internal {
// Checks if there's a valid PrintTo(const T&, std::ostream*) call for T.
template <typename T>
struct HasPrintTo {
private:
struct No {};
template <typename T2>
static auto Test(const T2& obj)
-> decltype(PrintTo(obj, std::declval<std::ostream*>()));
template <typename>
static No Test(...);
public:
static constexpr bool value =
!std::is_same<decltype(Test<T>(std::declval<const T&>())), No>::value;
};
// Checks if there's a valid operator<<(std::ostream&, const T&) call for T.
template <typename T>
struct HasOstreamOperator {
private:
struct No {};
template <typename T2>
static auto Test(const T2& obj)
-> decltype(std::declval<std::ostream&>() << obj);
template <typename>
static No Test(...);
public:
static constexpr bool value =
!std::is_same<decltype(Test<T>(std::declval<const T&>())), No>::value;
};
// Prefer using PrintTo to print the object.
template <typename T>
typename std::enable_if<HasPrintTo<T>::value, void>::type OptionalPrintToHelper(
const T& value,
std::ostream* os) {
PrintTo(value, os);
}
// Fall back to operator<<(std::ostream&, ...) if it exists.
template <typename T>
typename std::enable_if<HasOstreamOperator<T>::value && !HasPrintTo<T>::value,
void>::type
OptionalPrintToHelper(const T& value, std::ostream* os) {
*os << value;
}
inline void OptionalPrintObjectBytes(const unsigned char* bytes,
size_t size,
std::ostream* os) {
*os << "<optional with " << size << "-byte object [";
for (size_t i = 0; i != size; ++i) {
*os << (i == 0 ? "" : ((i & 1) ? "-" : " "));
*os << std::hex << std::setw(2) << std::setfill('0')
<< static_cast<int>(bytes[i]);
}
*os << "]>";
}
// As a final back-up, just print the contents of the objcets byte-wise.
template <typename T>
typename std::enable_if<!HasOstreamOperator<T>::value && !HasPrintTo<T>::value,
void>::type
OptionalPrintToHelper(const T& value, std::ostream* os) {
OptionalPrintObjectBytes(reinterpret_cast<const unsigned char*>(&value),
sizeof(value), os);
}
} // namespace optional_internal
// PrintTo is used by gtest to print out the results of tests. We want to ensure
// the object contained in an Optional can be printed out if it's set, while
// avoiding touching the object's storage if it is undefined.
template <typename T>
void PrintTo(const rtc::Optional<T>& opt, std::ostream* os) {
if (opt) {
optional_internal::OptionalPrintToHelper(*opt, os);
} else {
*os << "<empty optional>";
}
}
#endif // UNIT_TEST
} // namespace rtc
#endif // WEBRTC_BASE_OPTIONAL_H_

View File

@ -21,6 +21,34 @@ namespace rtc {
namespace {
struct MyUnprintableType {
int value;
};
struct MyPrintableType {
int value;
};
struct MyOstreamPrintableType {
int value;
};
void PrintTo(const MyPrintableType& mpt, std::ostream* os) {
*os << "The value is " << mpt.value;
}
std::ostream& operator<<(std::ostream& os,
const MyPrintableType& mpt) {
os << mpt.value;
return os;
}
std::ostream& operator<<(std::ostream& os,
const MyOstreamPrintableType& mpt) {
os << mpt.value;
return os;
}
// Class whose instances logs various method calls (constructor, destructor,
// etc.). Each instance has a unique ID (a simple global sequence number) and
// an origin ID. When a copy is made, the new object gets a fresh ID but copies
@ -740,4 +768,32 @@ TEST(OptionalTest, TestMoveValue) {
*log);
}
TEST(OptionalTest, TestPrintTo) {
constexpr char kEmptyOptionalMessage[] = "<empty optional>";
const Optional<MyUnprintableType> empty_unprintable;
const Optional<MyPrintableType> empty_printable;
const Optional<MyOstreamPrintableType> empty_ostream_printable;
EXPECT_EQ(kEmptyOptionalMessage, ::testing::PrintToString(empty_unprintable));
EXPECT_EQ(kEmptyOptionalMessage, ::testing::PrintToString(empty_printable));
EXPECT_EQ(kEmptyOptionalMessage,
::testing::PrintToString(empty_ostream_printable));
EXPECT_NE("1", ::testing::PrintToString(Optional<MyUnprintableType>({1})));
EXPECT_NE("1", ::testing::PrintToString(Optional<MyPrintableType>({1})));
EXPECT_EQ("The value is 1",
::testing::PrintToString(Optional<MyPrintableType>({1})));
EXPECT_EQ("1",
::testing::PrintToString(Optional<MyOstreamPrintableType>({1})));
}
void UnusedFunctionWorkaround() {
// These are here to ensure we don't get warnings about ostream and PrintTo
// for MyPrintableType never getting called.
const MyPrintableType dont_warn{17};
const MyOstreamPrintableType dont_warn2{18};
std::stringstream sstr;
sstr << dont_warn;
PrintTo(dont_warn, &sstr);
sstr << dont_warn2;
}
} // namespace rtc