blob: 9684840c793185e993edab1152a8c27287a0b8eb [file] [log] [blame]
Luca Versaric21d92f2022-05-25 00:56:30 -07001// Part of the Crubit project, under the Apache License v2.0 with LLVM
2// Exceptions. See /LICENSE for license information.
3// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
4
Dmitri Gribenkoefc86642023-07-10 04:49:39 -07005#ifndef CRUBIT_COMMON_STATUS_TEST_MATCHERS_H_
6#define CRUBIT_COMMON_STATUS_TEST_MATCHERS_H_
Luca Versaric21d92f2022-05-25 00:56:30 -07007
Dmitri Gribenkoecf5db32023-07-10 08:56:17 -07008#include <ostream>
9#include <string>
10#include <type_traits>
11
Luca Versaric21d92f2022-05-25 00:56:30 -070012#include "gmock/gmock.h"
13#include "gtest/gtest.h"
14#include "absl/status/status.h"
15#include "absl/status/statusor.h"
16
17namespace crubit {
18
19namespace detail {
20inline const ::absl::Status& GetStatus(const ::absl::Status& status) {
21 return status;
22}
23
24template <typename T>
25inline const ::absl::Status& GetStatus(const ::absl::StatusOr<T>& status) {
26 return status.status();
27}
28
29// Monomorphic implementation of matcher IsOkAndHolds(m). StatusOrType is a
30// reference to StatusOr<T>.
31template <typename StatusOrType>
32class IsOkAndHoldsMatcherImpl
33 : public ::testing::MatcherInterface<StatusOrType> {
34 public:
35 typedef
36 typename std::remove_reference<StatusOrType>::type::value_type value_type;
37
38 template <typename InnerMatcher>
39 explicit IsOkAndHoldsMatcherImpl(InnerMatcher&& inner_matcher)
40 : inner_matcher_(::testing::SafeMatcherCast<const value_type&>(
41 std::forward<InnerMatcher>(inner_matcher))) {}
42
43 void DescribeTo(std::ostream* os) const override {
44 *os << "is OK and has a value that ";
45 inner_matcher_.DescribeTo(os);
46 }
47
48 void DescribeNegationTo(std::ostream* os) const override {
49 *os << "isn't OK or has a value that ";
50 inner_matcher_.DescribeNegationTo(os);
51 }
52
53 bool MatchAndExplain(
54 StatusOrType actual_value,
55 ::testing::MatchResultListener* result_listener) const override {
56 if (!actual_value.ok()) {
57 *result_listener << "which has status " << actual_value.status();
58 return false;
59 }
60
61 ::testing::StringMatchResultListener inner_listener;
62 const bool matches =
63 inner_matcher_.MatchAndExplain(*actual_value, &inner_listener);
64 const std::string inner_explanation = inner_listener.str();
65 if (!inner_explanation.empty()) {
66 *result_listener << "which contains value "
67 << ::testing::PrintToString(*actual_value) << ", "
68 << inner_explanation;
69 }
70 return matches;
71 }
72
73 private:
74 const ::testing::Matcher<const value_type&> inner_matcher_;
75};
76
77// Implements IsOkAndHolds(m) as a polymorphic matcher.
78template <typename InnerMatcher>
79class IsOkAndHoldsMatcher {
80 public:
81 explicit IsOkAndHoldsMatcher(InnerMatcher inner_matcher)
82 : inner_matcher_(std::move(inner_matcher)) {}
83
84 // Converts this polymorphic matcher to a monomorphic matcher of the
85 // given type. StatusOrType can be either StatusOr<T> or a
86 // reference to StatusOr<T>.
87 template <typename StatusOrType>
88 operator ::testing::Matcher<StatusOrType>() const { // NOLINT
89 return ::testing::Matcher<StatusOrType>(
90 new IsOkAndHoldsMatcherImpl<const StatusOrType&>(inner_matcher_));
91 }
92
93 private:
94 const InnerMatcher inner_matcher_;
95};
96
97// Monomorphic implementation of matcher IsOk() for a given type T.
98// T can be Status, StatusOr<>, or a reference to either of them.
99template <typename T>
100class MonoIsOkMatcherImpl : public ::testing::MatcherInterface<T> {
101 public:
102 void DescribeTo(std::ostream* os) const override { *os << "is OK"; }
103 void DescribeNegationTo(std::ostream* os) const override {
104 *os << "is not OK";
105 }
106 bool MatchAndExplain(T actual_value,
107 ::testing::MatchResultListener*) const override {
108 return GetStatus(actual_value).ok();
109 }
110};
111
112// Implements IsOk() as a polymorphic matcher.
113class IsOkMatcher {
114 public:
115 template <typename T>
116 operator ::testing::Matcher<T>() const { // NOLINT
117 return ::testing::Matcher<T>(new MonoIsOkMatcherImpl<T>());
118 }
119};
120
121class StatusIsMatcher {
122 public:
123 StatusIsMatcher(absl::StatusCode expected_code,
124 testing::Matcher<const std::string&> message_matcher)
125 : expected_code_(expected_code), message_matcher_(message_matcher) {}
126
127 void DescribeTo(std::ostream* os) const {
128 *os << "has status code that is equal to "
129 << absl::StatusCodeToString(expected_code_);
130 *os << " and has an error message that ";
131 message_matcher_.DescribeTo(os);
132 }
133
134 void DescribeNegationTo(std::ostream* os) const {
135 *os << "has status code that is not equal to "
136 << absl::StatusCodeToString(expected_code_);
137 *os << " or has an error message that ";
138 message_matcher_.DescribeNegationTo(os);
139 }
140
141 template <typename StatusType>
142 bool MatchAndExplain(const StatusType& status,
143 testing::MatchResultListener* result_listener) const {
144 if (GetStatus(status).code() != expected_code_) {
145 *result_listener << "whose canonical status code is not equal to"
146 << absl::StatusCodeToString(expected_code_);
147 return false;
148 }
149 if (!message_matcher_.Matches(std::string(GetStatus(status).message()))) {
150 *result_listener << "whose error message is wrong";
151 return false;
152 }
153 return true;
154 }
155
156 private:
157 const absl::StatusCode expected_code_;
158 const testing::Matcher<const std::string&> message_matcher_;
159};
160
161} // namespace detail
162
163// Returns a gMock matcher that matches a StatusOr<> whose status is
164// OK and whose value matches the inner matcher.
165template <typename InnerMatcher>
166detail::IsOkAndHoldsMatcher<typename std::decay<InnerMatcher>::type>
167IsOkAndHolds(InnerMatcher&& inner_matcher) {
168 return detail::IsOkAndHoldsMatcher<typename std::decay<InnerMatcher>::type>(
169 std::forward<InnerMatcher>(inner_matcher));
170}
171
172// Returns a gMock matcher that matches a Status or StatusOr<> which is OK.
173inline detail::IsOkMatcher IsOk() { return detail::IsOkMatcher(); }
174
175// Status matcher that checks the StatusCode for an expected value.
176inline testing::PolymorphicMatcher<detail::StatusIsMatcher> StatusIs(
177 const absl::StatusCode& code) {
178 return testing::MakePolymorphicMatcher(
179 detail::StatusIsMatcher(code, testing::_));
180}
181
182// Status matcher that checks the StatusCode and message for expected values.
183template <typename MessageMatcher>
184testing::PolymorphicMatcher<detail::StatusIsMatcher> StatusIs(
185 const absl::StatusCode& code, const MessageMatcher& message) {
186 return testing::MakePolymorphicMatcher(detail::StatusIsMatcher(
187 code, testing::MatcherCast<const std::string&>(message)));
188}
189
190#define ASSERT_OK(expression) ASSERT_THAT(expression, ::crubit::IsOk())
191
192#define PASTE_INNER(X, Y) X##Y
193#define PASTE(X, Y) PASTE_INNER(X, Y)
194
195#define ASSERT_OK_AND_ASSIGN(lhs, rexpr) \
196 auto PASTE(_status, __LINE__) = rexpr; \
197 ASSERT_OK(PASTE(_status, __LINE__)); \
198 lhs = std::move(PASTE(_status, __LINE__)).value();
199} // namespace crubit
200
Dmitri Gribenkoefc86642023-07-10 04:49:39 -0700201#endif // CRUBIT_COMMON_STATUS_TEST_MATCHERS_H_