1 /* Copyright 2021 The TensorFlow Authors. All Rights Reserved.
2
3 Licensed under the Apache License, Version 2.0 (the "License");
4 you may not use this file except in compliance with the License.
5 You may obtain a copy of the License at
6
7 http://www.apache.org/licenses/LICENSE-2.0
8
9 Unless required by applicable law or agreed to in writing, software
10 distributed under the License is distributed on an "AS IS" BASIS,
11 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 See the License for the specific language governing permissions and
13 limitations under the License.
14 ==============================================================================*/
15 #ifndef TENSORFLOW_CORE_PLATFORM_STATUS_MATCHERS_H_
16 #define TENSORFLOW_CORE_PLATFORM_STATUS_MATCHERS_H_
17
18 #include <ostream>
19 #include <string>
20 #include <utility>
21
22 #include "tensorflow/core/platform/status.h"
23 #include "tensorflow/core/platform/statusor.h"
24 #include "tensorflow/core/platform/test.h"
25 #include "tensorflow/core/protobuf/error_codes.pb.h"
26
27 // Defines the following utilities:
28 //
29 // ===============
30 // IsOkAndHolds(m)
31 // ===============
32 //
33 // This matcher matches a StatusOr<T> value whose status is OK and whose inner
34 // value matches matcher m. Example:
35 //
36 // using ::tensorflow::testing::IsOkAndHolds;
37 // using ::testing::HasSubstr;
38 // ...
39 // StatusOr<std::string> status_or_message("Hello, world");
40 // EXPECT_THAT(status_or_message, IsOkAndHolds("Hello, world")));
41 // EXPECT_THAT(status_or_message, IsOkAndHolds(HasSubstr("Hello,")));
42 //
43 // ===============================
44 // StatusIs(status_code_matcher,
45 // error_message_matcher)
46 // ===============================
47 //
48 // This matcher matches a Status or StatusOr<T> if the following are true:
49 //
50 // - the status's code() matches status_code_matcher, and
51 // - the status's error_message() matches error_message_matcher.
52 //
53 // Example:
54 //
55 // using ::tensorflow::testing::StatusIs;
56 // using ::testing::HasSubstr;
57 // using ::testing::MatchesRegex;
58 // using ::testing::Ne;
59 // using ::testing::_;
60 // StatusOr<std::string> GetMessage(int id);
61 // ...
62 //
63 // // The status code must be CANCELLED; the error message can be anything.
64 // EXPECT_THAT(GetName(42),
65 // StatusIs(tensorflow::error::CANCELLED, _));
66 //
67 // // The status code can be anything; the error message must match the regex.
68 // EXPECT_THAT(GetName(43),
69 // StatusIs(_, MatchesRegex("server.*time-out")));
70 //
71 // // The status code should not be CANCELLED; the error message can be
72 // // anything with "Cancelled" in it.
73 // EXPECT_THAT(GetName(44),
74 // StatusIs(Ne(tensorflow::error::CANCELLED),
75 // HasSubstr("Cancelled"))));
76 //
77 // =============================
78 // StatusIs(status_code_matcher)
79 // =============================
80 //
81 // This is a shorthand for
82 // StatusIs(status_code_matcher, ::testing::_)
83 //
84 // In other words, it's like the two-argument StatusIs(), except that it ignores
85 // error messages.
86 //
87 // ======
88 // IsOk()
89 // ======
90 //
91 // Matches a Status or StatusOr<T> whose status value is OK.
92 // Equivalent to 'StatusIs(error::OK)'.
93 //
94 // Example:
95 // ...
96 // StatusOr<std::string> message("Hello, world");
97 // EXPECT_THAT(message, IsOk());
98 // Status status = Status::OK();
99 // EXPECT_THAT(status, IsOk());
100
101 namespace tensorflow {
102
103 template <typename T>
PrintTo(const StatusOr<T> & status_or,std::ostream * os)104 void PrintTo(const StatusOr<T>& status_or, std::ostream* os) {
105 *os << ::testing::PrintToString(status_or.status());
106 if (status_or.ok()) {
107 *os << ": " << ::testing::PrintToString(status_or.ValueOrDie());
108 }
109 }
110
111 namespace error {
PrintTo(const tensorflow::error::Code code,std::ostream * os)112 inline void PrintTo(const tensorflow::error::Code code, std::ostream* os) {
113 *os << Code_Name(code);
114 }
115 } // namespace error
116
117 namespace testing {
118 namespace internal_status {
119
GetStatus(const Status & status)120 inline const Status& GetStatus(const Status& status) { return status; }
121
122 template <typename T>
GetStatus(const StatusOr<T> & status)123 inline const Status& GetStatus(const StatusOr<T>& status) {
124 return status.status();
125 }
126
127 ////////////////////////////////////////////////////////////
128 // Implementation of IsOkAndHolds().
129 //
130 // Monomorphic implementation of matcher IsOkAndHolds(m). StatusOrType is a
131 // reference to StatusOr<T>.
132 template <typename StatusOrType>
133 class IsOkAndHoldsMatcherImpl
134 : public ::testing::MatcherInterface<StatusOrType> {
135 public:
136 typedef
137 typename std::remove_reference<StatusOrType>::type::value_type value_type;
138
139 template <typename InnerMatcher>
IsOkAndHoldsMatcherImpl(InnerMatcher && inner_matcher)140 explicit IsOkAndHoldsMatcherImpl(InnerMatcher&& inner_matcher)
141 : inner_matcher_(::testing::SafeMatcherCast<const value_type&>(
142 std::forward<InnerMatcher>(inner_matcher))) {}
143
DescribeTo(std::ostream * os)144 void DescribeTo(std::ostream* os) const override {
145 *os << "is OK and has a value that ";
146 inner_matcher_.DescribeTo(os);
147 }
148
DescribeNegationTo(std::ostream * os)149 void DescribeNegationTo(std::ostream* os) const override {
150 *os << "isn't OK or has a value that ";
151 inner_matcher_.DescribeNegationTo(os);
152 }
153
MatchAndExplain(StatusOrType actual_value,::testing::MatchResultListener * result_listener)154 bool MatchAndExplain(
155 StatusOrType actual_value,
156 ::testing::MatchResultListener* result_listener) const override {
157 if (!actual_value.ok()) {
158 *result_listener << "which has status " << actual_value.status();
159 return false;
160 }
161
162 ::testing::StringMatchResultListener inner_listener;
163 const bool matches =
164 inner_matcher_.MatchAndExplain(*actual_value, &inner_listener);
165 const std::string inner_explanation = inner_listener.str();
166 if (!inner_explanation.empty()) {
167 *result_listener << "which contains value "
168 << ::testing::PrintToString(*actual_value) << ", "
169 << inner_explanation;
170 }
171 return matches;
172 }
173
174 private:
175 const ::testing::Matcher<const value_type&> inner_matcher_;
176 };
177
178 // Implements IsOkAndHolds(m) as a polymorphic matcher.
179 template <typename InnerMatcher>
180 class IsOkAndHoldsMatcher {
181 public:
IsOkAndHoldsMatcher(InnerMatcher inner_matcher)182 explicit IsOkAndHoldsMatcher(InnerMatcher inner_matcher)
183 : inner_matcher_(std::move(inner_matcher)) {}
184
185 // Converts this polymorphic matcher to a monomorphic matcher of the given
186 // type. StatusOrType can be either StatusOr<T> or a reference to StatusOr<T>.
187 template <typename StatusOrType>
188 operator ::testing::Matcher<StatusOrType>() const { // NOLINT
189 return ::testing::Matcher<StatusOrType>(
190 new IsOkAndHoldsMatcherImpl<const StatusOrType&>(inner_matcher_));
191 }
192
193 private:
194 const InnerMatcher inner_matcher_;
195 };
196
197 ////////////////////////////////////////////////////////////
198 // Implementation of StatusIs().
199 //
200 // StatusIs() is a polymorphic matcher. This class is the common
201 // implementation of it shared by all types T where StatusIs() can be used as
202 // a Matcher<T>.
203
204 class StatusIsMatcherCommonImpl {
205 public:
StatusIsMatcherCommonImpl(::testing::Matcher<const tensorflow::error::Code> code_matcher,::testing::Matcher<const std::string &> message_matcher)206 StatusIsMatcherCommonImpl(
207 ::testing::Matcher<const tensorflow::error::Code> code_matcher,
208 ::testing::Matcher<const std::string&> message_matcher)
209 : code_matcher_(std::move(code_matcher)),
210 message_matcher_(std::move(message_matcher)) {}
211
212 void DescribeTo(std::ostream* os) const;
213
214 void DescribeNegationTo(std::ostream* os) const;
215
216 bool MatchAndExplain(const Status& status,
217 ::testing::MatchResultListener* result_listener) const;
218
219 private:
220 const ::testing::Matcher<const tensorflow::error::Code> code_matcher_;
221 const ::testing::Matcher<const std::string&> message_matcher_;
222 };
223
224 // Monomorphic implementation of matcher StatusIs() for a given type T. T can
225 // be Status, StatusOr<>, or a reference to either of them.
226 template <typename T>
227 class MonoStatusIsMatcherImpl : public ::testing::MatcherInterface<T> {
228 public:
MonoStatusIsMatcherImpl(StatusIsMatcherCommonImpl common_impl)229 explicit MonoStatusIsMatcherImpl(StatusIsMatcherCommonImpl common_impl)
230 : common_impl_(std::move(common_impl)) {}
231
DescribeTo(std::ostream * os)232 void DescribeTo(std::ostream* os) const override {
233 common_impl_.DescribeTo(os);
234 }
235
DescribeNegationTo(std::ostream * os)236 void DescribeNegationTo(std::ostream* os) const override {
237 common_impl_.DescribeNegationTo(os);
238 }
239
MatchAndExplain(T actual_value,::testing::MatchResultListener * result_listener)240 bool MatchAndExplain(
241 T actual_value,
242 ::testing::MatchResultListener* result_listener) const override {
243 return common_impl_.MatchAndExplain(GetStatus(actual_value),
244 result_listener);
245 }
246
247 private:
248 StatusIsMatcherCommonImpl common_impl_;
249 };
250
251 // Implements StatusIs() as a polymorphic matcher.
252 class StatusIsMatcher {
253 public:
StatusIsMatcher(::testing::Matcher<const tensorflow::error::Code> code_matcher,::testing::Matcher<const std::string &> message_matcher)254 StatusIsMatcher(
255 ::testing::Matcher<const tensorflow::error::Code> code_matcher,
256 ::testing::Matcher<const std::string&> message_matcher)
257 : common_impl_(
258 ::testing::MatcherCast<const tensorflow::error::Code>(code_matcher),
259 ::testing::MatcherCast<const std::string&>(message_matcher)) {}
260
261 // Converts this polymorphic matcher to a monomorphic matcher of the given
262 // type. T can be StatusOr<>, Status, or a reference to either of them.
263 template <typename T>
264 operator ::testing::Matcher<T>() const { // NOLINT
265 return ::testing::MakeMatcher(new MonoStatusIsMatcherImpl<T>(common_impl_));
266 }
267
268 private:
269 const StatusIsMatcherCommonImpl common_impl_;
270 };
271
272 // Monomorphic implementation of matcher IsOk() for a given type T.
273 // T can be Status, StatusOr<>, or a reference to either of them.
274 template <typename T>
275 class MonoIsOkMatcherImpl : public ::testing::MatcherInterface<T> {
276 public:
DescribeTo(std::ostream * os)277 void DescribeTo(std::ostream* os) const override { *os << "is OK"; }
DescribeNegationTo(std::ostream * os)278 void DescribeNegationTo(std::ostream* os) const override {
279 *os << "is not OK";
280 }
MatchAndExplain(T actual_value,::testing::MatchResultListener *)281 bool MatchAndExplain(T actual_value,
282 ::testing::MatchResultListener*) const override {
283 return GetStatus(actual_value).ok();
284 }
285 };
286
287 // Implements IsOk() as a polymorphic matcher.
288 class IsOkMatcher {
289 public:
290 template <typename T>
291 operator ::testing::Matcher<T>() const { // NOLINT
292 return ::testing::Matcher<T>(new MonoIsOkMatcherImpl<const T&>());
293 }
294 };
295 } // namespace internal_status
296
297 // Returns a matcher that matches a StatusOr<> whose status is OK and whose
298 // value matches the inner matcher.
299 template <typename InnerMatcher>
300 internal_status::IsOkAndHoldsMatcher<typename std::decay<InnerMatcher>::type>
IsOkAndHolds(InnerMatcher && inner_matcher)301 IsOkAndHolds(InnerMatcher&& inner_matcher) {
302 return internal_status::IsOkAndHoldsMatcher<
303 typename std::decay<InnerMatcher>::type>(
304 std::forward<InnerMatcher>(inner_matcher));
305 }
306
307 // Returns a matcher that matches a Status or StatusOr<> whose status code
308 // matches code_matcher, and whose error message matches message_matcher.
309 template <typename CodeMatcher, typename MessageMatcher>
StatusIs(CodeMatcher code_matcher,MessageMatcher message_matcher)310 internal_status::StatusIsMatcher StatusIs(CodeMatcher code_matcher,
311 MessageMatcher message_matcher) {
312 return internal_status::StatusIsMatcher(std::move(code_matcher),
313 std::move(message_matcher));
314 }
315
316 // Returns a matcher that matches a Status or StatusOr<> whose status code
317 // matches code_matcher.
318 template <typename CodeMatcher>
StatusIs(CodeMatcher code_matcher)319 internal_status::StatusIsMatcher StatusIs(CodeMatcher code_matcher) {
320 return StatusIs(std::move(code_matcher), ::testing::_);
321 }
322
323 // Returns a matcher that matches a Status or StatusOr<> which is OK.
IsOk()324 inline internal_status::IsOkMatcher IsOk() {
325 return internal_status::IsOkMatcher();
326 }
327
328 } // namespace testing
329 } // namespace tensorflow
330
331 #endif // TENSORFLOW_CORE_PLATFORM_STATUS_MATCHERS_H_
332