Skip to content

Commit 3ee5d59

Browse files
hsharma35facebook-github-bot
authored andcommitted
Add gtest macros and matchers for Result<T> and Error. (#12038)
Summary: Adds matcher support for Result and Error types, similar to absl::Status. Differential Revision: D77409312
1 parent 083663b commit 3ee5d59

File tree

7 files changed

+563
-0
lines changed

7 files changed

+563
-0
lines changed

runtime/core/testing_util/TARGETS

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# Any targets that should be shared between fbcode and xplat must be defined in
2+
# targets.bzl. This file can contain fbcode-only targets.
3+
4+
load(":targets.bzl", "define_common_targets")
5+
6+
oncall("executorch")
7+
8+
define_common_targets()
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/*
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the BSD-style license found in the
6+
* LICENSE file in the root directory of this source tree.
7+
*/
8+
9+
#include <executorch/runtime/core/testing_util/error_matchers.h>
10+
11+
#include <executorch/runtime/core/error.h>
12+
13+
namespace executorch {
14+
namespace runtime {
15+
16+
// This needs to be defined in the SAME namespace that defines Error.
17+
// C++'s look-up rules rely on that.
18+
void PrintTo(const Error& error, std::ostream* os) {
19+
*os << ::executorch::runtime::testing::internal::toString(error);
20+
}
21+
22+
} // namespace runtime
23+
} // namespace executorch
Lines changed: 343 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,343 @@
1+
/*
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the BSD-style license found in the
6+
* LICENSE file in the root directory of this source tree.
7+
*/
8+
9+
/**
10+
* @file
11+
* Testing utilities for working with `executorch::runtime::Result<T>` and
12+
* `executorch::runtime::Error`. Provides matchers similar to `absl::StatusOr`
13+
* and `absl::Status`.
14+
*
15+
* Defines the following utilities:
16+
*
17+
* ===============
18+
* `IsOkAndHolds(m)`
19+
* ===============
20+
*
21+
* This gMock matcher matches a Result<T> value whose error is Ok
22+
* and whose inner value matches matcher m. Example:
23+
*
24+
* ```
25+
* using ::testing::MatchesRegex;
26+
* using ::executorch::runtime::testing::IsOkAndHolds;
27+
* ...
28+
* executorch::runtime::Result<string> maybe_name = ...;
29+
* EXPECT_THAT(maybe_name, IsOkAndHolds(MatchesRegex("John .*")));
30+
* ```
31+
*
32+
* ===============
33+
* `ErrorIs(Error::error_code)`
34+
* ===============
35+
*
36+
* This gMock matcher matches a Result<T> value whose error matches
37+
* the given error matcher. Example:
38+
*
39+
* ```
40+
* using ::executorch::runtime::testing::ErrorIs;
41+
* ...
42+
* executorch::runtime::Result<string> maybe_name = ...;
43+
* EXPECT_THAT(maybe_name, ErrorIs(Error::InvalidArgument));
44+
* ```
45+
*
46+
* ===============
47+
* `IsOk()`
48+
* ===============
49+
*
50+
* Matches an `executorch::runtime::Result<T>` value whose error value
51+
* is `executorch::runtime::Error::Ok`.
52+
*
53+
* Example:
54+
* ```
55+
* using ::executorch::runtime::testing::IsOk;
56+
* ...
57+
* executorch::runtime::Result<string> maybe_name = ...;
58+
* EXPECT_THAT(maybe_name, IsOk());
59+
* ```
60+
*/
61+
62+
#pragma once
63+
64+
#include <ostream>
65+
#include <utility>
66+
67+
#include <gmock/gmock-matchers.h>
68+
#include <gtest/gtest-matchers.h>
69+
70+
#include <executorch/runtime/core/error.h>
71+
#include <executorch/runtime/core/result.h>
72+
73+
/**
74+
* Unwrap a Result to obtain its value. If the Result contains an error,
75+
* fail the test with ASSERT_TRUE.
76+
*
77+
* This macro is useful for test code where you want to extract the value
78+
* from a Result and fail the test if the Result contains an error.
79+
*
80+
* Example usage:
81+
* ```
82+
* Result<int> maybe_value = GetSomeValue();
83+
* int value = ASSERT_OK_AND_UNWRAP(maybe_value);
84+
* // Use value...
85+
* ```
86+
*
87+
* @param[in] result__ Expression yielding the Result to unwrap.
88+
*/
89+
#define ASSERT_OK_AND_UNWRAP(result__) \
90+
({ \
91+
auto&& et_result__ = (result__); \
92+
ASSERT_TRUE(et_result__.ok()); \
93+
std::move(*et_result__); \
94+
})
95+
96+
namespace executorch {
97+
namespace runtime {
98+
namespace testing {
99+
namespace internal {
100+
namespace {
101+
102+
constexpr const char* toString(const Error error) {
103+
switch (error) {
104+
case Error::Ok:
105+
return "Error::Ok";
106+
case Error::Internal:
107+
return "Error::Internal";
108+
case Error::InvalidState:
109+
return "Error::InvalidState";
110+
case Error::EndOfMethod:
111+
return "Error::EndOfMethod";
112+
case Error::NotSupported:
113+
return "Error::NotSupported";
114+
case Error::NotImplemented:
115+
return "Error::NotImplemented";
116+
case Error::InvalidArgument:
117+
return "Error::InvalidArgument";
118+
case Error::InvalidType:
119+
return "Error::InvalidType";
120+
case Error::OperatorMissing:
121+
return "Error::OperatorMissing";
122+
case Error::NotFound:
123+
return "Error::NotFound";
124+
case Error::MemoryAllocationFailed:
125+
return "Error::MemoryAllocationFailed";
126+
case Error::AccessFailed:
127+
return "Error::AccessFailed";
128+
case Error::InvalidProgram:
129+
return "Error::InvalidProgram";
130+
case Error::InvalidExternalData:
131+
return "Error::InvalidExternalData";
132+
case Error::OutOfResources:
133+
return "Error::OutOfResources";
134+
case Error::DelegateInvalidCompatibility:
135+
return "Error::DelegateInvalidCompatibility";
136+
case Error::DelegateMemoryAllocationFailed:
137+
return "Error::DelegateMemoryAllocationFailed";
138+
case Error::DelegateInvalidHandle:
139+
return "Error::DelegateInvalidHandle";
140+
}
141+
}
142+
} // namespace
143+
144+
// Helper function to get the error from a Result
145+
template <typename T>
146+
inline Error GetError(const Result<T>& result) {
147+
return result.error();
148+
}
149+
150+
// Helper function to get the error from a raw Error (identity function)
151+
inline Error GetError(const Error& error) {
152+
return error;
153+
}
154+
155+
////////////////////////////////////////////////////////////
156+
// Implementation of IsOkAndHolds().
157+
158+
// Monomorphic implementation of matcher IsOkAndHolds(m). ResultType is a
159+
// reference to Result<T>.
160+
template <typename ResultType>
161+
class IsOkAndHoldsMatcherImpl : public ::testing::MatcherInterface<ResultType> {
162+
public:
163+
typedef
164+
typename std::remove_reference<ResultType>::type::value_type value_type;
165+
166+
template <typename InnerMatcher>
167+
explicit IsOkAndHoldsMatcherImpl(InnerMatcher&& inner_matcher)
168+
: inner_matcher_(::testing::SafeMatcherCast<const value_type&>(
169+
std::forward<InnerMatcher>(inner_matcher))) {}
170+
171+
void DescribeTo(std::ostream* os) const override {
172+
*os << "is OK and has a value that ";
173+
inner_matcher_.DescribeTo(os);
174+
}
175+
176+
void DescribeNegationTo(std::ostream* os) const override {
177+
*os << "isn't OK or has a value that ";
178+
inner_matcher_.DescribeNegationTo(os);
179+
}
180+
181+
bool MatchAndExplain(
182+
ResultType actual_value,
183+
::testing::MatchResultListener* result_listener) const override {
184+
if (!actual_value.ok()) {
185+
*result_listener << "which has error "
186+
<< toString(GetError(actual_value));
187+
return false;
188+
}
189+
190+
// Call through to the inner matcher.
191+
return inner_matcher_.MatchAndExplain(*actual_value, result_listener);
192+
}
193+
194+
private:
195+
const ::testing::Matcher<const value_type&> inner_matcher_;
196+
};
197+
198+
// Implements IsOkAndHolds(m) as a polymorphic matcher.
199+
template <typename InnerMatcher>
200+
class IsOkAndHoldsMatcher {
201+
public:
202+
explicit IsOkAndHoldsMatcher(InnerMatcher inner_matcher)
203+
: inner_matcher_(std::forward<InnerMatcher>(inner_matcher)) {}
204+
205+
// Converts this polymorphic matcher to a monomorphic matcher of the
206+
// given type. ResultType can be either Result<T> or a
207+
// reference to Result<T>.
208+
template <typename ResultType>
209+
operator ::testing::Matcher<ResultType>() const { // NOLINT
210+
return ::testing::Matcher<ResultType>(
211+
new IsOkAndHoldsMatcherImpl<const ResultType&>(inner_matcher_));
212+
}
213+
214+
private:
215+
const InnerMatcher inner_matcher_;
216+
};
217+
218+
////////////////////////////////////////////////////////////
219+
// Implementation of IsOk().
220+
221+
// Monomorphic implementation of matcher IsOk() for a given type T.
222+
// T can be Result<U>, Error, or references to either.
223+
template <typename T>
224+
class MonoIsOkMatcherImpl : public ::testing::MatcherInterface<T> {
225+
public:
226+
void DescribeTo(std::ostream* os) const override {
227+
*os << "is OK";
228+
}
229+
void DescribeNegationTo(std::ostream* os) const override {
230+
*os << "is not OK";
231+
}
232+
bool MatchAndExplain(
233+
T actual_value,
234+
::testing::MatchResultListener* result_listener) const override {
235+
const Error error = GetError(actual_value);
236+
if (error != Error::Ok) {
237+
*result_listener << "which has error " << toString(error);
238+
return false;
239+
}
240+
return true;
241+
}
242+
};
243+
244+
// Implements IsOk() as a polymorphic matcher.
245+
class IsOkMatcher {
246+
public:
247+
template <typename T>
248+
operator ::testing::Matcher<T>() const { // NOLINT
249+
return ::testing::Matcher<T>(new MonoIsOkMatcherImpl<const T&>());
250+
}
251+
};
252+
253+
////////////////////////////////////////////////////////////
254+
// Implementation of ErrorIs().
255+
256+
// Monomorphic implementation of matcher ErrorIs() for a given type T.
257+
// T can be Result<U> or a reference to Result<U>.
258+
template <typename T>
259+
class MonoErrorIsMatcherImpl : public ::testing::MatcherInterface<T> {
260+
public:
261+
explicit MonoErrorIsMatcherImpl(::testing::Matcher<Error> error_matcher)
262+
: error_matcher_(std::move(error_matcher)) {}
263+
264+
void DescribeTo(std::ostream* os) const override {
265+
*os << "has an error that ";
266+
error_matcher_.DescribeTo(os);
267+
}
268+
269+
void DescribeNegationTo(std::ostream* os) const override {
270+
*os << "does not have an error that ";
271+
error_matcher_.DescribeNegationTo(os);
272+
}
273+
274+
bool MatchAndExplain(
275+
T actual_value,
276+
::testing::MatchResultListener* result_listener) const override {
277+
Error actual_error = GetError(actual_value);
278+
*result_listener << "which has error " << toString(actual_error);
279+
return error_matcher_.MatchAndExplain(actual_error, result_listener);
280+
}
281+
282+
private:
283+
const ::testing::Matcher<Error> error_matcher_;
284+
};
285+
286+
// Implements ErrorIs() as a polymorphic matcher.
287+
template <typename ErrorMatcher>
288+
class ErrorIsMatcher {
289+
public:
290+
explicit ErrorIsMatcher(ErrorMatcher error_matcher)
291+
: error_matcher_(std::forward<ErrorMatcher>(error_matcher)) {}
292+
293+
// Converts this polymorphic matcher to a monomorphic matcher of the
294+
// given type. T can be Result<U> or a reference to Result<U>.
295+
template <typename T>
296+
operator ::testing::Matcher<T>() const { // NOLINT
297+
return ::testing::Matcher<T>(new MonoErrorIsMatcherImpl<const T&>(
298+
::testing::MatcherCast<Error>(error_matcher_)));
299+
}
300+
301+
private:
302+
const ErrorMatcher error_matcher_;
303+
};
304+
305+
} // namespace internal
306+
307+
// Returns a gMock matcher that matches a Result<> whose error is
308+
// OK and whose value matches the inner matcher.
309+
template <typename InnerMatcherT>
310+
internal::IsOkAndHoldsMatcher<typename std::decay<InnerMatcherT>::type>
311+
IsOkAndHolds(InnerMatcherT&& inner_matcher) {
312+
return internal::IsOkAndHoldsMatcher<
313+
typename std::decay<InnerMatcherT>::type>(
314+
std::forward<InnerMatcherT>(inner_matcher));
315+
}
316+
317+
// Returns a gMock matcher that matches a Result<> whose error matches
318+
// the given error matcher.
319+
template <typename ErrorMatcherT>
320+
internal::ErrorIsMatcher<typename std::decay<ErrorMatcherT>::type> ErrorIs(
321+
ErrorMatcherT&& error_matcher) {
322+
return internal::ErrorIsMatcher<typename std::decay<ErrorMatcherT>::type>(
323+
std::forward<ErrorMatcherT>(error_matcher));
324+
}
325+
326+
// Returns a gMock matcher that matches a Result<> which is OK.
327+
inline internal::IsOkMatcher IsOk() {
328+
return internal::IsOkMatcher();
329+
}
330+
331+
} // namespace testing
332+
} // namespace runtime
333+
} // namespace executorch
334+
335+
namespace executorch {
336+
namespace runtime {
337+
338+
// This needs to be defined in the SAME namespace that defines Error.
339+
// C++'s look-up rules rely on that.
340+
void PrintTo(const Error& error, std::ostream* os);
341+
342+
} // namespace runtime
343+
} // namespace executorch

0 commit comments

Comments
 (0)