xref: /aosp_15_r20/external/pigweed/pw_unit_test/public/pw_unit_test/googletest_test_matchers.h (revision 61c4878ac05f98d0ceed94b57d316916de578985)
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 /// Returns a gMock matcher that matches a `pw::Status`, `pw::StatusWithSize`,
181 /// or `pw::Result<T>` (for any T) which is OK.
IsOk()182 inline internal::IsOkMatcher IsOk() { return {}; }
183 
184 /// Returns a gMock matcher that matches a `pw::Status`, `pw::StatusWithSize`,
185 /// or `pw::Result<T>` (for any T) which has the given status.
StatusIs(Status expected_status)186 inline auto StatusIs(Status expected_status) {
187   return ::testing::MakePolymorphicMatcher(
188       internal::StatusIsMatcher(expected_status));
189 }
190 
191 /// Returns a gMock matcher that matches a `pw::StatusWithSize` or
192 /// `pw::Result<T>` (for any T) which is OK and holds a value matching the inner
193 /// matcher.
194 template <typename InnerMatcher>
IsOkAndHolds(InnerMatcher && inner_matcher)195 inline internal::IsOkAndHoldsMatcher<InnerMatcher> IsOkAndHolds(
196     InnerMatcher&& inner_matcher) {
197   return internal::IsOkAndHoldsMatcher<InnerMatcher>(
198       std::forward<InnerMatcher>(inner_matcher));
199 }
200 
201 }  // namespace pw::unit_test
202