Skip to content

Commit a6df1fe

Browse files
Hardik Sharmafacebook-github-bot
authored andcommitted
Add gtest macros and matchers for Result<T> and Error.
Summary: Adds matcher support for Result and Error types, similar to absl::Status. Differential Revision: D77409312
1 parent 2a2d757 commit a6df1fe

File tree

6 files changed

+482
-0
lines changed

6 files changed

+482
-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: 287 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,287 @@
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 <type_traits>
64+
#include <utility>
65+
66+
#include "executorch/runtime/core/result.h"
67+
#include "gmock/gmock.h"
68+
69+
/**
70+
* Unwrap a Result to obtain its value. If the Result contains an error,
71+
* fail the test with ASSERT_TRUE.
72+
*
73+
* This macro is useful for test code where you want to extract the value
74+
* from a Result and fail the test if the Result contains an error.
75+
*
76+
* Example usage:
77+
* ```
78+
* Result<int> maybe_value = GetSomeValue();
79+
* int value = ASSERT_OK_AND_UNWRAP(maybe_value);
80+
* // Use value...
81+
* ```
82+
*
83+
* @param[in] result__ Expression yielding the Result to unwrap.
84+
*/
85+
#define ASSERT_OK_AND_UNWRAP(result__) \
86+
({ \
87+
auto&& et_result__ = (result__); \
88+
ASSERT_TRUE(et_result__.ok()); \
89+
std::move(*et_result__); \
90+
})
91+
92+
namespace executorch {
93+
namespace runtime {
94+
namespace testing {
95+
96+
namespace internal {
97+
98+
// Helper function to get the error from a Result
99+
template <typename T>
100+
inline Error GetError(const Result<T>& result) {
101+
return result.error();
102+
}
103+
104+
// Helper function to get the error from a raw Error (identity function)
105+
inline Error GetError(const Error& error) {
106+
return error;
107+
}
108+
109+
////////////////////////////////////////////////////////////
110+
// Implementation of IsOkAndHolds().
111+
112+
// Monomorphic implementation of matcher IsOkAndHolds(m). ResultType is a
113+
// reference to Result<T>.
114+
template <typename ResultType>
115+
class IsOkAndHoldsMatcherImpl : public ::testing::MatcherInterface<ResultType> {
116+
public:
117+
typedef
118+
typename std::remove_reference<ResultType>::type::value_type value_type;
119+
120+
template <typename InnerMatcher>
121+
explicit IsOkAndHoldsMatcherImpl(InnerMatcher&& inner_matcher)
122+
: inner_matcher_(::testing::SafeMatcherCast<const value_type&>(
123+
std::forward<InnerMatcher>(inner_matcher))) {}
124+
125+
void DescribeTo(std::ostream* os) const override {
126+
*os << "is OK and has a value that ";
127+
inner_matcher_.DescribeTo(os);
128+
}
129+
130+
void DescribeNegationTo(std::ostream* os) const override {
131+
*os << "isn't OK or has a value that ";
132+
inner_matcher_.DescribeNegationTo(os);
133+
}
134+
135+
bool MatchAndExplain(
136+
ResultType actual_value,
137+
::testing::MatchResultListener* result_listener) const override {
138+
if (!actual_value.ok()) {
139+
*result_listener << "which has error "
140+
<< static_cast<int>(GetError(actual_value));
141+
return false;
142+
}
143+
144+
// Call through to the inner matcher.
145+
return inner_matcher_.MatchAndExplain(*actual_value, result_listener);
146+
}
147+
148+
private:
149+
const ::testing::Matcher<const value_type&> inner_matcher_;
150+
};
151+
152+
// Implements IsOkAndHolds(m) as a polymorphic matcher.
153+
template <typename InnerMatcher>
154+
class IsOkAndHoldsMatcher {
155+
public:
156+
explicit IsOkAndHoldsMatcher(InnerMatcher inner_matcher)
157+
: inner_matcher_(std::forward<InnerMatcher>(inner_matcher)) {}
158+
159+
// Converts this polymorphic matcher to a monomorphic matcher of the
160+
// given type. ResultType can be either Result<T> or a
161+
// reference to Result<T>.
162+
template <typename ResultType>
163+
operator ::testing::Matcher<ResultType>() const { // NOLINT
164+
return ::testing::Matcher<ResultType>(
165+
new IsOkAndHoldsMatcherImpl<const ResultType&>(inner_matcher_));
166+
}
167+
168+
private:
169+
const InnerMatcher inner_matcher_;
170+
};
171+
172+
////////////////////////////////////////////////////////////
173+
// Implementation of IsOk().
174+
175+
// Monomorphic implementation of matcher IsOk() for a given type T.
176+
// T can be Result<U>, Error, or references to either.
177+
template <typename T>
178+
class MonoIsOkMatcherImpl : public ::testing::MatcherInterface<T> {
179+
public:
180+
void DescribeTo(std::ostream* os) const override {
181+
*os << "is OK";
182+
}
183+
void DescribeNegationTo(std::ostream* os) const override {
184+
*os << "is not OK";
185+
}
186+
bool MatchAndExplain(
187+
T actual_value,
188+
::testing::MatchResultListener* result_listener) const override {
189+
const Error error = GetError(actual_value);
190+
if (error != Error::Ok) {
191+
*result_listener << "which has error " << static_cast<int>(error);
192+
return false;
193+
}
194+
return true;
195+
}
196+
};
197+
198+
// Implements IsOk() as a polymorphic matcher.
199+
class IsOkMatcher {
200+
public:
201+
template <typename T>
202+
operator ::testing::Matcher<T>() const { // NOLINT
203+
return ::testing::Matcher<T>(new MonoIsOkMatcherImpl<const T&>());
204+
}
205+
};
206+
207+
////////////////////////////////////////////////////////////
208+
// Implementation of ErrorIs().
209+
210+
// Monomorphic implementation of matcher ErrorIs() for a given type T.
211+
// T can be Result<U> or a reference to Result<U>.
212+
template <typename T>
213+
class MonoErrorIsMatcherImpl : public ::testing::MatcherInterface<T> {
214+
public:
215+
explicit MonoErrorIsMatcherImpl(::testing::Matcher<Error> error_matcher)
216+
: error_matcher_(std::move(error_matcher)) {}
217+
218+
void DescribeTo(std::ostream* os) const override {
219+
*os << "has an error that ";
220+
error_matcher_.DescribeTo(os);
221+
}
222+
223+
void DescribeNegationTo(std::ostream* os) const override {
224+
*os << "does not have an error that ";
225+
error_matcher_.DescribeNegationTo(os);
226+
}
227+
228+
bool MatchAndExplain(
229+
T actual_value,
230+
::testing::MatchResultListener* result_listener) const override {
231+
Error actual_error = GetError(actual_value);
232+
*result_listener << "which has error " << static_cast<int>(actual_error);
233+
return error_matcher_.MatchAndExplain(actual_error, result_listener);
234+
}
235+
236+
private:
237+
const ::testing::Matcher<Error> error_matcher_;
238+
};
239+
240+
// Implements ErrorIs() as a polymorphic matcher.
241+
template <typename ErrorMatcher>
242+
class ErrorIsMatcher {
243+
public:
244+
explicit ErrorIsMatcher(ErrorMatcher error_matcher)
245+
: error_matcher_(std::forward<ErrorMatcher>(error_matcher)) {}
246+
247+
// Converts this polymorphic matcher to a monomorphic matcher of the
248+
// given type. T can be Result<U> or a reference to Result<U>.
249+
template <typename T>
250+
operator ::testing::Matcher<T>() const { // NOLINT
251+
return ::testing::Matcher<T>(new MonoErrorIsMatcherImpl<const T&>(
252+
::testing::MatcherCast<Error>(error_matcher_)));
253+
}
254+
255+
private:
256+
const ErrorMatcher error_matcher_;
257+
};
258+
259+
} // namespace internal
260+
261+
// Returns a gMock matcher that matches a Result<> whose error is
262+
// OK and whose value matches the inner matcher.
263+
template <typename InnerMatcherT>
264+
internal::IsOkAndHoldsMatcher<typename std::decay<InnerMatcherT>::type>
265+
IsOkAndHolds(InnerMatcherT&& inner_matcher) {
266+
return internal::IsOkAndHoldsMatcher<
267+
typename std::decay<InnerMatcherT>::type>(
268+
std::forward<InnerMatcherT>(inner_matcher));
269+
}
270+
271+
// Returns a gMock matcher that matches a Result<> whose error matches
272+
// the given error matcher.
273+
template <typename ErrorMatcherT>
274+
internal::ErrorIsMatcher<typename std::decay<ErrorMatcherT>::type> ErrorIs(
275+
ErrorMatcherT&& error_matcher) {
276+
return internal::ErrorIsMatcher<typename std::decay<ErrorMatcherT>::type>(
277+
std::forward<ErrorMatcherT>(error_matcher));
278+
}
279+
280+
// Returns a gMock matcher that matches a Result<> which is OK.
281+
inline internal::IsOkMatcher IsOk() {
282+
return internal::IsOkMatcher();
283+
}
284+
285+
} // namespace testing
286+
} // namespace runtime
287+
} // namespace executorch
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
2+
load("@fbsource//xplat/executorch/build:runtime_wrapper.bzl", "runtime")
3+
4+
def define_common_targets():
5+
"""Defines targets that should be shared between fbcode and xplat.
6+
7+
The directory containing this targets.bzl file should also contain both
8+
TARGETS and BUCK files that call this function.
9+
"""
10+
runtime.cxx_library(
11+
name = "error_matchers",
12+
exported_headers = [
13+
"error_matchers.h",
14+
],
15+
visibility = [
16+
"//executorch/runtime/core/testing_util/test/...",
17+
"@EXECUTORCH_CLIENTS",
18+
],
19+
compiler_flags = [
20+
"-Wno-unneeded-internal-declaration",
21+
],
22+
exported_external_deps = [
23+
"gmock",
24+
],
25+
)
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()

0 commit comments

Comments
 (0)