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