blob: 9684840c793185e993edab1152a8c27287a0b8eb [file] [log] [blame]
// Part of the Crubit project, under the Apache License v2.0 with LLVM
// Exceptions. See /LICENSE for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
#ifndef CRUBIT_COMMON_STATUS_TEST_MATCHERS_H_
#define CRUBIT_COMMON_STATUS_TEST_MATCHERS_H_
#include <ostream>
#include <string>
#include <type_traits>
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "absl/status/status.h"
#include "absl/status/statusor.h"
namespace crubit {
namespace detail {
inline const ::absl::Status& GetStatus(const ::absl::Status& status) {
return status;
}
template <typename T>
inline const ::absl::Status& GetStatus(const ::absl::StatusOr<T>& status) {
return status.status();
}
// Monomorphic implementation of matcher IsOkAndHolds(m). StatusOrType is a
// reference to StatusOr<T>.
template <typename StatusOrType>
class IsOkAndHoldsMatcherImpl
: public ::testing::MatcherInterface<StatusOrType> {
public:
typedef
typename std::remove_reference<StatusOrType>::type::value_type value_type;
template <typename InnerMatcher>
explicit IsOkAndHoldsMatcherImpl(InnerMatcher&& inner_matcher)
: inner_matcher_(::testing::SafeMatcherCast<const value_type&>(
std::forward<InnerMatcher>(inner_matcher))) {}
void DescribeTo(std::ostream* os) const override {
*os << "is OK and has a value that ";
inner_matcher_.DescribeTo(os);
}
void DescribeNegationTo(std::ostream* os) const override {
*os << "isn't OK or has a value that ";
inner_matcher_.DescribeNegationTo(os);
}
bool MatchAndExplain(
StatusOrType actual_value,
::testing::MatchResultListener* result_listener) const override {
if (!actual_value.ok()) {
*result_listener << "which has status " << actual_value.status();
return false;
}
::testing::StringMatchResultListener inner_listener;
const bool matches =
inner_matcher_.MatchAndExplain(*actual_value, &inner_listener);
const std::string inner_explanation = inner_listener.str();
if (!inner_explanation.empty()) {
*result_listener << "which contains value "
<< ::testing::PrintToString(*actual_value) << ", "
<< inner_explanation;
}
return matches;
}
private:
const ::testing::Matcher<const value_type&> inner_matcher_;
};
// Implements IsOkAndHolds(m) as a polymorphic matcher.
template <typename InnerMatcher>
class IsOkAndHoldsMatcher {
public:
explicit IsOkAndHoldsMatcher(InnerMatcher inner_matcher)
: inner_matcher_(std::move(inner_matcher)) {}
// Converts this polymorphic matcher to a monomorphic matcher of the
// given type. StatusOrType can be either StatusOr<T> or a
// reference to StatusOr<T>.
template <typename StatusOrType>
operator ::testing::Matcher<StatusOrType>() const { // NOLINT
return ::testing::Matcher<StatusOrType>(
new IsOkAndHoldsMatcherImpl<const StatusOrType&>(inner_matcher_));
}
private:
const InnerMatcher inner_matcher_;
};
// Monomorphic implementation of matcher IsOk() for a given type T.
// T can be Status, StatusOr<>, or a reference to either of them.
template <typename T>
class MonoIsOkMatcherImpl : public ::testing::MatcherInterface<T> {
public:
void DescribeTo(std::ostream* os) const override { *os << "is OK"; }
void DescribeNegationTo(std::ostream* os) const override {
*os << "is not OK";
}
bool MatchAndExplain(T actual_value,
::testing::MatchResultListener*) const override {
return GetStatus(actual_value).ok();
}
};
// Implements IsOk() as a polymorphic matcher.
class IsOkMatcher {
public:
template <typename T>
operator ::testing::Matcher<T>() const { // NOLINT
return ::testing::Matcher<T>(new MonoIsOkMatcherImpl<T>());
}
};
class StatusIsMatcher {
public:
StatusIsMatcher(absl::StatusCode expected_code,
testing::Matcher<const std::string&> message_matcher)
: expected_code_(expected_code), message_matcher_(message_matcher) {}
void DescribeTo(std::ostream* os) const {
*os << "has status code that is equal to "
<< absl::StatusCodeToString(expected_code_);
*os << " and has an error message that ";
message_matcher_.DescribeTo(os);
}
void DescribeNegationTo(std::ostream* os) const {
*os << "has status code that is not equal to "
<< absl::StatusCodeToString(expected_code_);
*os << " or has an error message that ";
message_matcher_.DescribeNegationTo(os);
}
template <typename StatusType>
bool MatchAndExplain(const StatusType& status,
testing::MatchResultListener* result_listener) const {
if (GetStatus(status).code() != expected_code_) {
*result_listener << "whose canonical status code is not equal to"
<< absl::StatusCodeToString(expected_code_);
return false;
}
if (!message_matcher_.Matches(std::string(GetStatus(status).message()))) {
*result_listener << "whose error message is wrong";
return false;
}
return true;
}
private:
const absl::StatusCode expected_code_;
const testing::Matcher<const std::string&> message_matcher_;
};
} // namespace detail
// Returns a gMock matcher that matches a StatusOr<> whose status is
// OK and whose value matches the inner matcher.
template <typename InnerMatcher>
detail::IsOkAndHoldsMatcher<typename std::decay<InnerMatcher>::type>
IsOkAndHolds(InnerMatcher&& inner_matcher) {
return detail::IsOkAndHoldsMatcher<typename std::decay<InnerMatcher>::type>(
std::forward<InnerMatcher>(inner_matcher));
}
// Returns a gMock matcher that matches a Status or StatusOr<> which is OK.
inline detail::IsOkMatcher IsOk() { return detail::IsOkMatcher(); }
// Status matcher that checks the StatusCode for an expected value.
inline testing::PolymorphicMatcher<detail::StatusIsMatcher> StatusIs(
const absl::StatusCode& code) {
return testing::MakePolymorphicMatcher(
detail::StatusIsMatcher(code, testing::_));
}
// Status matcher that checks the StatusCode and message for expected values.
template <typename MessageMatcher>
testing::PolymorphicMatcher<detail::StatusIsMatcher> StatusIs(
const absl::StatusCode& code, const MessageMatcher& message) {
return testing::MakePolymorphicMatcher(detail::StatusIsMatcher(
code, testing::MatcherCast<const std::string&>(message)));
}
#define ASSERT_OK(expression) ASSERT_THAT(expression, ::crubit::IsOk())
#define PASTE_INNER(X, Y) X##Y
#define PASTE(X, Y) PASTE_INNER(X, Y)
#define ASSERT_OK_AND_ASSIGN(lhs, rexpr) \
auto PASTE(_status, __LINE__) = rexpr; \
ASSERT_OK(PASTE(_status, __LINE__)); \
lhs = std::move(PASTE(_status, __LINE__)).value();
} // namespace crubit
#endif // CRUBIT_COMMON_STATUS_TEST_MATCHERS_H_