• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2023 The Pigweed Authors
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License"); you may not
4 // use this file except in compliance with the License. You may obtain a copy of
5 // the License at
6 //
7 //     https://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11 // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12 // License for the specific language governing permissions and limitations under
13 // the License.
14 #pragma once
15 
16 #include <type_traits>
17 #include <utility>
18 
19 #include "gmock/gmock-matchers.h"
20 #include "gtest/gtest.h"
21 #include "pw_result/result.h"
22 #include "pw_status/status.h"
23 #include "pw_status/status_with_size.h"
24 
25 namespace pw::unit_test {
26 namespace internal {
27 // Gets the pw::Status of different types of objects with a pw::Status for
28 // Matchers that check the status.
GetStatus(Status status)29 inline constexpr Status GetStatus(Status status) { return status; }
30 
GetStatus(StatusWithSize status_with_size)31 inline constexpr Status GetStatus(StatusWithSize status_with_size) {
32   return status_with_size.status();
33 }
34 
35 template <typename T>
GetStatus(const Result<T> & result)36 inline constexpr Status GetStatus(const Result<T>& result) {
37   return result.status();
38 }
39 
40 // Gets the value of an object whose value is guarded by a ``pw::OkStatus()``.
41 // Used by Matchers.
GetValue(StatusWithSize status_with_size)42 constexpr size_t GetValue(StatusWithSize status_with_size) {
43   return status_with_size.size();
44 }
45 
46 template <typename V>
GetValue(const Result<V> & result)47 constexpr const V& GetValue(const Result<V>& result) {
48   return result.value();
49 }
50 
51 template <typename V>
GetValue(Result<V> && result)52 constexpr V GetValue(Result<V>&& result) {
53   return std::move(result).value();
54 }
55 
56 // Implements IsOk().
57 class IsOkMatcher {
58  public:
59   using is_gtest_matcher = void;
60 
DescribeTo(std::ostream * os)61   void DescribeTo(std::ostream* os) const { *os << "is OK"; }
62 
DescribeNegationTo(std::ostream * os)63   void DescribeNegationTo(std::ostream* os) const { *os << "isn't OK"; }
64 
65   template <typename T>
MatchAndExplain(T && actual_value,::testing::MatchResultListener * listener)66   bool MatchAndExplain(T&& actual_value,
67                        ::testing::MatchResultListener* listener) const {
68     const auto status = GetStatus(actual_value);
69     if (!status.ok()) {
70       *listener << "which has status " << pw_StatusString(status);
71       return false;
72     }
73     return true;
74   }
75 };
76 
77 // Implements IsOkAndHolds(m) as a monomorphic matcher.
78 template <typename StatusType>
79 class IsOkAndHoldsMatcherImpl {
80  public:
81   using is_gtest_matcher = void;
82   using ValueType = decltype(GetValue(std::declval<StatusType>()));
83 
84   // NOLINTBEGIN(bugprone-forwarding-reference-overload)
85   template <typename InnerMatcher>
IsOkAndHoldsMatcherImpl(InnerMatcher && inner_matcher)86   explicit IsOkAndHoldsMatcherImpl(InnerMatcher&& inner_matcher)
87       : inner_matcher_(::testing::SafeMatcherCast<const ValueType&>(
88             std::forward<InnerMatcher>(inner_matcher))) {}
89   // NOLINTEND(bugprone-forwarding-reference-overload)
90 
DescribeTo(std::ostream * os)91   void DescribeTo(std::ostream* os) const {
92     *os << "is OK and has a value that ";
93     inner_matcher_.DescribeTo(os);
94   }
95 
DescribeNegationTo(std::ostream * os)96   void DescribeNegationTo(std::ostream* os) const {
97     *os << "isn't OK or has a value that ";
98     inner_matcher_.DescribeNegationTo(os);
99   }
100 
MatchAndExplain(const StatusType & actual_value,::testing::MatchResultListener * listener)101   bool MatchAndExplain(const StatusType& actual_value,
102                        ::testing::MatchResultListener* listener) const {
103     const auto& status = GetStatus(actual_value);
104     if (!status.ok()) {
105       *listener << "which has status " << pw_StatusString(status);
106       return false;
107     }
108 
109     const auto& value = GetValue(actual_value);
110     *listener << "which contains value " << ::testing::PrintToString(value);
111 
112     ::testing::StringMatchResultListener inner_listener;
113     const bool matches = inner_matcher_.MatchAndExplain(value, &inner_listener);
114     const std::string inner_explanation = inner_listener.str();
115     if (!inner_explanation.empty()) {
116       *listener << ", " << inner_explanation;
117     }
118 
119     return matches;
120   }
121 
122  private:
123   const ::testing::Matcher<const ValueType&> inner_matcher_;
124 };
125 
126 // Implements IsOkAndHolds(m) as a polymorphic matcher.
127 //
128 // We have to manually create it as a class instead of using the
129 // `::testing::MakePolymorphicMatcher()` helper because of the custom conversion
130 // to Matcher<T>.
131 template <typename InnerMatcher>
132 class IsOkAndHoldsMatcher {
133  public:
IsOkAndHoldsMatcher(InnerMatcher inner_matcher)134   explicit IsOkAndHoldsMatcher(InnerMatcher inner_matcher)
135       : inner_matcher_(std::move(inner_matcher)) {}
136 
137   // NOLINTBEGIN(google-explicit-constructor)
138   template <typename StatusType>
139   operator ::testing::Matcher<StatusType>() const {
140     return ::testing::Matcher<StatusType>(
141         internal::IsOkAndHoldsMatcherImpl<const StatusType&>(inner_matcher_));
142   }
143   // NOLINTEND(google-explicit-constructor)
144 
145  private:
146   const InnerMatcher inner_matcher_;
147 };
148 
149 // Implements StatusIs().
150 class StatusIsMatcher {
151  public:
StatusIsMatcher(Status expected_status)152   explicit StatusIsMatcher(Status expected_status)
153       : expected_status_(expected_status) {}
154 
DescribeTo(std::ostream * os)155   void DescribeTo(std::ostream* os) const {
156     *os << "has status " << pw_StatusString(expected_status_);
157   }
158 
DescribeNegationTo(std::ostream * os)159   void DescribeNegationTo(std::ostream* os) const {
160     *os << "does not have status " << pw_StatusString(expected_status_);
161   }
162 
163   template <typename T>
MatchAndExplain(T && actual_value,::testing::MatchResultListener * listener)164   bool MatchAndExplain(T&& actual_value,
165                        ::testing::MatchResultListener* listener) const {
166     const auto status = GetStatus(actual_value);
167     if (status != expected_status_) {
168       *listener << "which has status " << pw_StatusString(status);
169       return false;
170     }
171     return true;
172   }
173 
174  private:
175   const Status expected_status_;
176 };
177 
178 }  // namespace internal
179 
180 /// Macros for testing the results of functions that return ``pw::Status``,
181 /// ``pw::StatusWithSize``, or ``pw::Result<T>`` (for any T).
182 #define EXPECT_OK(expression) EXPECT_THAT(expression, ::pw::unit_test::IsOk())
183 #define ASSERT_OK(expression) ASSERT_THAT(expression, ::pw::unit_test::IsOk())
184 
185 /// Returns a gMock matcher that matches a `pw::Status`, `pw::StatusWithSize`,
186 /// or `pw::Result<T>` (for any T) which is OK.
IsOk()187 inline internal::IsOkMatcher IsOk() { return {}; }
188 
189 /// Returns a gMock matcher that matches a `pw::Status`, `pw::StatusWithSize`,
190 /// or `pw::Result<T>` (for any T) which has the given status.
StatusIs(Status expected_status)191 inline auto StatusIs(Status expected_status) {
192   return ::testing::MakePolymorphicMatcher(
193       internal::StatusIsMatcher(expected_status));
194 }
195 
196 /// Returns a gMock matcher that matches a `pw::StatusWithSize` or
197 /// `pw::Result<T>` (for any T) which is OK and holds a value matching the inner
198 /// matcher.
199 template <typename InnerMatcher>
IsOkAndHolds(InnerMatcher && inner_matcher)200 inline internal::IsOkAndHoldsMatcher<InnerMatcher> IsOkAndHolds(
201     InnerMatcher&& inner_matcher) {
202   return internal::IsOkAndHoldsMatcher<InnerMatcher>(
203       std::forward<InnerMatcher>(inner_matcher));
204 }
205 
206 /// Executes an expression that returns a `pw::Result` or `pw::StatusWithSize`
207 /// and assigns or moves that value to lhs if the error code is OK. If the
208 // status is non-OK, generates a test failure and returns from the current
209 /// function, which must have a void return type.
210 ///
211 /// The MOVE variant moves the content out of the `pw::Result` and into lhs.
212 /// This variant is required for move-only types.
213 //
214 /// Example: Declaring and initializing a new value. E.g.:
215 ///   ASSERT_OK_AND_ASSIGN(auto value, MaybeGetValue(arg));
216 ///   ASSERT_OK_AND_ASSIGN(const ValueType value, MaybeGetValue(arg));
217 ///   ASSERT_OK_AND_MOVE(auto ptr, MaybeGetUniquePtr(arg))
218 ///
219 /// Example: Assigning to an existing value
220 ///   ValueType value;
221 ///   ASSERT_OK_AND_ASSIGN(value, MaybeGetValue(arg));
222 ///
223 /// The value assignment example would expand into something like:
224 ///   auto status_or_value = MaybeGetValue(arg);
225 ///   ASSERT_OK(status_or_value.status());
226 ///   value = status_or_value.ValueOrDie();
227 ///
228 /// WARNING: ASSERT_OK_AND_ASSIGN (and the move variant) expand into multiple
229 ///   statements; it cannot be used in a single statement (e.g. as the body of
230 ///   an if statement without {})!
231 #define ASSERT_OK_AND_ASSIGN(lhs, rexpr) \
232   ASSERT_OK_AND_ASSIGN_DETAIL(UNIQUE_IDENTIFIER_DETAIL(__LINE__), lhs, rexpr)
233 
234 /// Deprecated. Prefer ASSERT_OK_AND_ASSIGN, which will move if the rexpr is an
235 /// rvalue (either an expression or `std::move(some_named_value)`).
236 #define ASSERT_OK_AND_MOVE(lhs, rexpr) \
237   ASSERT_OK_AND_MOVE_DETAIL(UNIQUE_IDENTIFIER_DETAIL(__LINE__), lhs, rexpr)
238 
239 // NOLINTBEGIN(bugprone-macro-parentheses)
240 // The suggestion would produce bad code.
241 #define ASSERT_OK_AND_ASSIGN_DETAIL(result, lhs, rexpr)                 \
242   auto result = (rexpr);                                                \
243   if (!result.ok()) {                                                   \
244     FAIL() << "`" << #rexpr << "` is not OK: "                          \
245            << pw_StatusString(::pw::internal::ConvertToStatus(result)); \
246   }                                                                     \
247   lhs = ::pw::internal::ConvertToValue(result);
248 
249 #define ASSERT_OK_AND_MOVE_DETAIL(result, lhs, rexpr)                   \
250   auto&& result = (rexpr);                                              \
251   if (!result.ok()) {                                                   \
252     FAIL() << "`" << #rexpr << "` is not OK: "                          \
253            << pw_StatusString(::pw::internal::ConvertToStatus(result)); \
254   }                                                                     \
255   lhs = ::pw::unit_test::internal::GetValue(std::move(result));
256 // NOLINTEND(bugprone-macro-parentheses)
257 
258 #define _PW_TRY_ASSIGN(result, lhs, expr)           \
259   auto result = (expr);                             \
260   if (!result.ok()) {                               \
261     return ::pw::internal::ConvertToStatus(result); \
262   }                                                 \
263   lhs = ::pw::internal::ConvertToValue(result);
264 
265 #define UNIQUE_IDENTIFIER_DETAIL(line) UNIQUE_IDENTIFIER_EXPANDED_DETAIL(line)
266 #define UNIQUE_IDENTIFIER_EXPANDED_DETAIL(line) \
267   _assert_ok_and_assign_unique_name_##line
268 
269 }  // namespace pw::unit_test
270