#ifndef CHROMECAST_CAST_CORE_GRPC_STATUS_MATCHERS_H_
#define CHROMECAST_CAST_CORE_GRPC_STATUS_MATCHERS_H_
#include <string>
#include <type_traits>
#include "testing/gmock/include/gmock/gmock.h"
namespace cast {
namespace test {
namespace internal {
template <typename T>
class has_status_api {
struct no {};
template <typename C>
static decltype(std::declval<C>().status()) test(int);
template <typename C>
static no test(...);
public:
enum { value = !std::is_same<decltype(test<T>(0)), no>::value };
};
template <typename T>
class has_code_api {
struct no {};
template <typename C>
static decltype(&C::code) test(int);
template <typename C>
static no test(...);
public:
enum { value = !std::is_same<decltype(test<T>(0)), no>::value };
};
template <typename T>
class has_error_code_api {
struct no {};
template <typename C>
static decltype(&C::error_code) test(int);
template <typename C>
static no test(...);
public:
enum { value = !std::is_same<decltype(test<T>(0)), no>::value };
};
class StatusResolver {
public:
struct StatusInfo {
int code;
std::string message;
};
template <typename TStatus>
explicit StatusResolver(const TStatus& status)
: status_info_(ResolveStatusInfo(ResolveStatus(status))) {}
~StatusResolver();
const StatusInfo& status_info() const { return status_info_; }
bool ok() const { return status_info_.code == 0; }
private:
template <typename TWrappedStatus,
typename std::enable_if<has_status_api<TWrappedStatus>::value,
TWrappedStatus>::type* = nullptr>
static auto ResolveStatus(const TWrappedStatus& wrapped_status)
-> decltype(std::declval<const TWrappedStatus>().status()) {
return wrapped_status.status();
}
template <typename TStatus,
typename std::enable_if<!has_status_api<TStatus>::value,
TStatus>::type* = nullptr>
static auto ResolveStatus(const TStatus& status) -> TStatus {
return status;
}
template <typename TStatus,
typename std::enable_if<has_code_api<TStatus>::value,
TStatus>::type* = nullptr>
static StatusInfo ResolveStatusInfo(const TStatus& status) {
return StatusInfo(
{static_cast<int>(status.code()), std::string(status.message())});
}
template <typename TStatus,
typename std::enable_if<has_error_code_api<TStatus>::value,
TStatus>::type* = nullptr>
static StatusInfo ResolveStatusInfo(const TStatus& status) {
return StatusInfo({static_cast<int>(status.error_code()),
std::string(status.error_message())});
}
const StatusInfo status_info_;
};
template <typename TStatus>
class StatusIsImpl : public ::testing::MatcherInterface<TStatus> {
public:
StatusIsImpl(::testing::Matcher<int> code,
::testing::Matcher<std::string> message)
: code_(std::move(code)), message_(std::move(message)) {}
void DescribeTo(std::ostream* os) const override {
*os << "has code() that ";
code_.DescribeTo(os);
*os << " and message() that ";
message_.DescribeTo(os);
}
void DescribeNegationTo(std::ostream* os) const override {
*os << "has code() that ";
code_.DescribeNegationTo(os);
*os << " and message() that ";
message_.DescribeNegationTo(os);
}
bool MatchAndExplain(
TStatus status,
::testing::MatchResultListener* result_listener) const override {
::testing::StringMatchResultListener listener;
StatusResolver status_resolver(status);
auto status_info = status_resolver.status_info();
if (!code_.Matches(status_info.code)) {
*result_listener << "has wrong status code " << status_info.code;
return false;
}
if (!message_.Matches(status_info.message)) {
*result_listener << "has wrong error message " << status_info.message;
return false;
}
return true;
}
private:
const ::testing::Matcher<int> code_;
const ::testing::Matcher<std::string> message_;
};
class StatusIsPolymorphicWrapper {
public:
template <typename TStatusCode>
StatusIsPolymorphicWrapper(TStatusCode code,
::testing::Matcher<std::string>&& message)
: code_(static_cast<int>(code)), message_(std::move(message)) {}
StatusIsPolymorphicWrapper(const StatusIsPolymorphicWrapper&);
~StatusIsPolymorphicWrapper();
template <typename TStatus>
operator ::testing::Matcher<TStatus>() const {
return ::testing::Matcher<TStatus>(
new StatusIsImpl<TStatus>(std::move(code_), std::move(message_)));
}
private:
::testing::Matcher<int> code_;
::testing::Matcher<std::string> message_;
};
template <typename TStatus>
class IsOkImpl : public ::testing::MatcherInterface<TStatus> {
public:
void DescribeTo(std::ostream* os) const override {
*os << "is OK";
}
void DescribeNegationTo(std::ostream* os) const override {
*os << "is not OK";
}
bool MatchAndExplain(TStatus actual_value,
::testing::MatchResultListener*) const override {
return StatusResolver(actual_value).ok();
}
};
class IsOkPolymorphicWrapper {
public:
template <typename TStatus>
operator ::testing::Matcher<TStatus>() const {
return ::testing::Matcher<TStatus>(new IsOkImpl<const TStatus&>());
}
};
}
template <typename TStatusCode>
inline internal::StatusIsPolymorphicWrapper StatusIs(TStatusCode code) {
return internal::StatusIsPolymorphicWrapper(code, ::testing::_);
}
template <typename TStatusCode>
inline internal::StatusIsPolymorphicWrapper StatusIs(
TStatusCode code,
::testing::Matcher<std::string>&& message_matcher) {
return internal::StatusIsPolymorphicWrapper(code, std::move(message_matcher));
}
inline internal::IsOkPolymorphicWrapper IsOk() {
return internal::IsOkPolymorphicWrapper();
}
#define CU_EXPECT_OK(expression) EXPECT_THAT(expression, ::cast::test::IsOk())
#define CU_ASSERT_OK(expression) ASSERT_THAT(expression, ::cast::test::IsOk())
#define CU_CHECK_OK(expression) \
CHECK(::cast::test::internal::StatusResolver(expression).ok())
}
}
#endif