Skip to content

Commit 75e4044

Browse files
authored
Add gtest macros and matchers for Result<T> and Error.
Differential Revision: D77409312 Pull Request resolved: #12038
1 parent 00df64c commit 75e4044

File tree

8 files changed

+565
-0
lines changed

8 files changed

+565
-0
lines changed

runtime/core/error.h

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,48 @@ enum class Error : error_code_t {
9898

9999
};
100100

101+
// Stringify the Error enum.
102+
constexpr const char* to_string(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+
101143
} // namespace runtime
102144
} // namespace executorch
103145

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::to_string(error);
20+
}
21+
22+
} // namespace runtime
23+
} // namespace executorch
Lines changed: 303 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,303 @@
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+
101+
// Helper function to get the error from a Result
102+
template <typename T>
103+
inline Error GetError(const Result<T>& result) {
104+
return result.error();
105+
}
106+
107+
// Helper function to get the error from a raw Error (identity function)
108+
inline Error GetError(const Error& error) {
109+
return error;
110+
}
111+
112+
////////////////////////////////////////////////////////////
113+
// Implementation of IsOkAndHolds().
114+
115+
// Monomorphic implementation of matcher IsOkAndHolds(m). ResultType is a
116+
// reference to Result<T>.
117+
template <typename ResultType>
118+
class IsOkAndHoldsMatcherImpl : public ::testing::MatcherInterface<ResultType> {
119+
public:
120+
typedef
121+
typename std::remove_reference<ResultType>::type::value_type value_type;
122+
123+
template <typename InnerMatcher>
124+
explicit IsOkAndHoldsMatcherImpl(InnerMatcher&& inner_matcher)
125+
: inner_matcher_(::testing::SafeMatcherCast<const value_type&>(
126+
std::forward<InnerMatcher>(inner_matcher))) {}
127+
128+
void DescribeTo(std::ostream* os) const override {
129+
*os << "is OK and has a value that ";
130+
inner_matcher_.DescribeTo(os);
131+
}
132+
133+
void DescribeNegationTo(std::ostream* os) const override {
134+
*os << "isn't OK or has a value that ";
135+
inner_matcher_.DescribeNegationTo(os);
136+
}
137+
138+
bool MatchAndExplain(
139+
ResultType actual_value,
140+
::testing::MatchResultListener* result_listener) const override {
141+
if (!actual_value.ok()) {
142+
*result_listener << "which has error "
143+
<< ::executorch::runtime::to_string(
144+
GetError(actual_value));
145+
return false;
146+
}
147+
148+
// Call through to the inner matcher.
149+
return inner_matcher_.MatchAndExplain(*actual_value, result_listener);
150+
}
151+
152+
private:
153+
const ::testing::Matcher<const value_type&> inner_matcher_;
154+
};
155+
156+
// Implements IsOkAndHolds(m) as a polymorphic matcher.
157+
template <typename InnerMatcher>
158+
class IsOkAndHoldsMatcher {
159+
public:
160+
explicit IsOkAndHoldsMatcher(InnerMatcher inner_matcher)
161+
: inner_matcher_(std::forward<InnerMatcher>(inner_matcher)) {}
162+
163+
// Converts this polymorphic matcher to a monomorphic matcher of the
164+
// given type. ResultType can be either Result<T> or a
165+
// reference to Result<T>.
166+
template <typename ResultType>
167+
operator ::testing::Matcher<ResultType>() const { // NOLINT
168+
return ::testing::Matcher<ResultType>(
169+
new IsOkAndHoldsMatcherImpl<const ResultType&>(inner_matcher_));
170+
}
171+
172+
private:
173+
const InnerMatcher inner_matcher_;
174+
};
175+
176+
////////////////////////////////////////////////////////////
177+
// Implementation of IsOk().
178+
179+
// Monomorphic implementation of matcher IsOk() for a given type T.
180+
// T can be Result<U>, Error, or references to either.
181+
template <typename T>
182+
class MonoIsOkMatcherImpl : public ::testing::MatcherInterface<T> {
183+
public:
184+
void DescribeTo(std::ostream* os) const override {
185+
*os << "is OK";
186+
}
187+
void DescribeNegationTo(std::ostream* os) const override {
188+
*os << "is not OK";
189+
}
190+
bool MatchAndExplain(
191+
T actual_value,
192+
::testing::MatchResultListener* result_listener) const override {
193+
const Error error = GetError(actual_value);
194+
if (error != Error::Ok) {
195+
*result_listener << "which has error "
196+
<< ::executorch::runtime::to_string(error);
197+
return false;
198+
}
199+
return true;
200+
}
201+
};
202+
203+
// Implements IsOk() as a polymorphic matcher.
204+
class IsOkMatcher {
205+
public:
206+
template <typename T>
207+
operator ::testing::Matcher<T>() const { // NOLINT
208+
return ::testing::Matcher<T>(new MonoIsOkMatcherImpl<const T&>());
209+
}
210+
};
211+
212+
////////////////////////////////////////////////////////////
213+
// Implementation of ErrorIs().
214+
215+
// Monomorphic implementation of matcher ErrorIs() for a given type T.
216+
// T can be Result<U> or a reference to Result<U>.
217+
template <typename T>
218+
class MonoErrorIsMatcherImpl : public ::testing::MatcherInterface<T> {
219+
public:
220+
explicit MonoErrorIsMatcherImpl(::testing::Matcher<Error> error_matcher)
221+
: error_matcher_(std::move(error_matcher)) {}
222+
223+
void DescribeTo(std::ostream* os) const override {
224+
*os << "has an error that ";
225+
error_matcher_.DescribeTo(os);
226+
}
227+
228+
void DescribeNegationTo(std::ostream* os) const override {
229+
*os << "does not have an error that ";
230+
error_matcher_.DescribeNegationTo(os);
231+
}
232+
233+
bool MatchAndExplain(
234+
T actual_value,
235+
::testing::MatchResultListener* result_listener) const override {
236+
Error actual_error = GetError(actual_value);
237+
*result_listener << "which has error "
238+
<< ::executorch::runtime::to_string(actual_error);
239+
return error_matcher_.MatchAndExplain(actual_error, result_listener);
240+
}
241+
242+
private:
243+
const ::testing::Matcher<Error> error_matcher_;
244+
};
245+
246+
// Implements ErrorIs() as a polymorphic matcher.
247+
template <typename ErrorMatcher>
248+
class ErrorIsMatcher {
249+
public:
250+
explicit ErrorIsMatcher(ErrorMatcher error_matcher)
251+
: error_matcher_(std::forward<ErrorMatcher>(error_matcher)) {}
252+
253+
// Converts this polymorphic matcher to a monomorphic matcher of the
254+
// given type. T can be Result<U> or a reference to Result<U>.
255+
template <typename T>
256+
operator ::testing::Matcher<T>() const { // NOLINT
257+
return ::testing::Matcher<T>(new MonoErrorIsMatcherImpl<const T&>(
258+
::testing::MatcherCast<Error>(error_matcher_)));
259+
}
260+
261+
private:
262+
const ErrorMatcher error_matcher_;
263+
};
264+
265+
} // namespace internal
266+
267+
// Returns a gMock matcher that matches a Result<> whose error is
268+
// OK and whose value matches the inner matcher.
269+
template <typename InnerMatcherT>
270+
internal::IsOkAndHoldsMatcher<typename std::decay<InnerMatcherT>::type>
271+
IsOkAndHolds(InnerMatcherT&& inner_matcher) {
272+
return internal::IsOkAndHoldsMatcher<
273+
typename std::decay<InnerMatcherT>::type>(
274+
std::forward<InnerMatcherT>(inner_matcher));
275+
}
276+
277+
// Returns a gMock matcher that matches a Result<> whose error matches
278+
// the given error matcher.
279+
template <typename ErrorMatcherT>
280+
internal::ErrorIsMatcher<typename std::decay<ErrorMatcherT>::type> ErrorIs(
281+
ErrorMatcherT&& error_matcher) {
282+
return internal::ErrorIsMatcher<typename std::decay<ErrorMatcherT>::type>(
283+
std::forward<ErrorMatcherT>(error_matcher));
284+
}
285+
286+
// Returns a gMock matcher that matches a Result<> which is OK.
287+
inline internal::IsOkMatcher IsOk() {
288+
return internal::IsOkMatcher();
289+
}
290+
291+
} // namespace testing
292+
} // namespace runtime
293+
} // namespace executorch
294+
295+
namespace executorch {
296+
namespace runtime {
297+
298+
// This needs to be defined in the SAME namespace that defines Error.
299+
// C++'s look-up rules rely on that.
300+
void PrintTo(const Error& error, std::ostream* os);
301+
302+
} // namespace runtime
303+
} // namespace executorch

0 commit comments

Comments
 (0)