Skip to content

Commit dfd68f5

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. Reviewed By: larryliu0820 Differential Revision: D77409312
1 parent adb5318 commit dfd68f5

File tree

8 files changed

+562
-0
lines changed

8 files changed

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

0 commit comments

Comments
 (0)