Skip to content

Commit 7151468

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 2a2d757 commit 7151468

File tree

7 files changed

+561
-0
lines changed

7 files changed

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

0 commit comments

Comments
 (0)