iceberg-cpp
Loading...
Searching...
No Matches
matchers.h
1/*
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License. You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied. See the License for the
16 * specific language governing permissions and limitations
17 * under the License.
18 */
19
20#pragma once
21
22#include <gmock/gmock.h>
23#include <gtest/gtest.h>
24
25#include "iceberg/result.h"
26#include "iceberg/util/macros.h"
27
28/*
29 * \brief Define custom matchers for expected<T, Error> values
30 *
31 * Example usage of these matchers:
32 *
33 * Basic assertions:
34 *
35 * // Check that a result is ok
36 * EXPECT_THAT(result, IsOk());
37 *
38 * // Check that a result is an error of a specific kind
39 * EXPECT_THAT(result, IsError(ErrorKind::kNoSuchTable));
40 *
41 * // Check that an error message contains a specific substring
42 * EXPECT_THAT(result, HasErrorMessage("table not found"));
43 *
44 * Value inspection:
45 *
46 * // Check that a result has a value that equals 42
47 * EXPECT_THAT(result, HasValue(42));
48 *
49 * // Check that a result has a value that satisfies a complex condition
50 * EXPECT_THAT(result, HasValue(AllOf(Gt(10), Lt(50))));
51 *
52 * Combined assertions:
53 *
54 * // Check that the result value has a specific property
55 * EXPECT_THAT(result, ResultIs(Property(&MyType::name, "example")));
56 *
57 * // Check that the error matches specific criteria
58 * EXPECT_THAT(result, ErrorIs(AllOf(
59 * Property(&Error::kind, ErrorKind::kNoSuchTable),
60 * Property(&Error::message, HasSubstr("table not found"))
61 * )));
62 */
63
64namespace iceberg {
65
66// IsOk matcher that checks if the expected value has a value (not an error)
67MATCHER(IsOk, "is an Ok result") {
68 if (arg.has_value()) {
69 return true;
70 }
71 *result_listener << "which contains error: " << arg.error().message;
72 return false;
73}
74
75// IsError matcher that checks if the expected value contains an error
76MATCHER_P(IsError, kind, "is an Error with the specified kind") {
77 if (!arg.has_value()) {
78 if (arg.error().kind == kind) {
79 return true;
80 }
81 *result_listener << "which contains error kind " << static_cast<int>(arg.error().kind)
82 << " but expected " << static_cast<int>(kind)
83 << ", message: " << arg.error().message;
84 return false;
85 }
86 *result_listener << "which is not an error but a value";
87 return false;
88}
89
90// HasErrorMessage matcher that checks if the expected value contains an error with a
91// specific message or substring
92MATCHER_P(HasErrorMessage, message_substr,
93 "is an Error with message containing the substring") {
94 if (!arg.has_value()) {
95 if (arg.error().message.find(message_substr) != std::string::npos) {
96 return true;
97 }
98 *result_listener << "which contains error with message '" << arg.error().message
99 << "' that doesn't contain '" << message_substr << "'";
100 return false;
101 }
102 *result_listener << "which is not an error but a value";
103 return false;
104}
105
106// HasValue matcher that checks if the expected value contains a value that matches the
107// given matcher
108template <typename MatcherT>
110 public:
111 explicit HasValueMatcher(MatcherT matcher) : matcher_(std::move(matcher)) {}
112
113 template <typename T>
114 bool MatchAndExplain(const T& value,
115 ::testing::MatchResultListener* result_listener) const {
116 if (!value.has_value()) {
117 *result_listener << "which is an error: " << value.error().message;
118 return false;
119 }
120
121 return ::testing::MatcherCast<const typename T::value_type&>(matcher_)
122 .MatchAndExplain(*value, result_listener);
123 }
124
125 void DescribeTo(std::ostream* os) const {
126 *os << "has a value that ";
127 matcher_.DescribeTo(os);
128 }
129
130 void DescribeNegationTo(std::ostream* os) const {
131 *os << "does not have a value that ";
132 matcher_.DescribeTo(os);
133 }
134
135 private:
136 MatcherT matcher_;
137};
138
139// Factory function for HasValueMatcher
140template <typename MatcherT>
141auto HasValue(MatcherT&& matcher) {
142 return ::testing::MakePolymorphicMatcher(
143 HasValueMatcher<std::remove_cvref_t<MatcherT>>(std::forward<MatcherT>(matcher)));
144}
145
146// Overload for the common case where we just want to check for presence of any value
147inline auto HasValue() { return IsOk(); }
148
149// Matcher that checks an expected value against an expected value and a matcher
150template <typename MatcherT>
152 public:
153 explicit ResultMatcher(bool should_have_value, MatcherT matcher)
154 : should_have_value_(should_have_value), matcher_(std::move(matcher)) {}
155
156 template <typename T>
157 bool MatchAndExplain(const T& value,
158 ::testing::MatchResultListener* result_listener) const {
159 if (value.has_value() != should_have_value_) {
160 if (should_have_value_) {
161 *result_listener << "which is an error: " << value.error().message;
162 } else {
163 *result_listener << "which is a value, not an error";
164 }
165 return false;
166 }
167
168 if (should_have_value_) {
169 return ::testing::MatcherCast<const typename T::value_type&>(matcher_)
170 .MatchAndExplain(*value, result_listener);
171 } else {
172 return ::testing::MatcherCast<const typename T::error_type&>(matcher_)
173 .MatchAndExplain(value.error(), result_listener);
174 }
175 }
176
177 void DescribeTo(std::ostream* os) const {
178 if (should_have_value_) {
179 *os << "has a value that ";
180 } else {
181 *os << "has an error that ";
182 }
183 matcher_.DescribeTo(os);
184 }
185
186 void DescribeNegationTo(std::ostream* os) const {
187 if (should_have_value_) {
188 *os << "does not have a value that ";
189 } else {
190 *os << "does not have an error that ";
191 }
192 matcher_.DescribeTo(os);
193 }
194
195 private:
196 bool should_have_value_;
197 MatcherT matcher_;
198};
199
200// Factory function for ResultMatcher for values
201template <typename MatcherT>
202auto ResultIs(MatcherT&& matcher) {
203 return ::testing::MakePolymorphicMatcher(ResultMatcher<std::remove_cvref_t<MatcherT>>(
204 true, std::forward<MatcherT>(matcher)));
205}
206
207// Factory function for ResultMatcher for errors
208template <typename MatcherT>
209auto ErrorIs(MatcherT&& matcher) {
210 return ::testing::MakePolymorphicMatcher(ResultMatcher<std::remove_cvref_t<MatcherT>>(
211 false, std::forward<MatcherT>(matcher)));
212}
213
214// Evaluate `rexpr` which should return a Result<T, Error>.
215// On success: assign the contained value to `lhs`.
216// On failure: fail the test with the error message.
217#define ICEBERG_UNWRAP_OR_FAIL_IMPL(result_name, lhs, rexpr) \
218 auto&& result_name = (rexpr); \
219 ASSERT_TRUE(result_name.has_value()) \
220 << "Operation failed: " << result_name.error().message; \
221 lhs = std::move(result_name.value());
222
223#define ICEBERG_UNWRAP_OR_FAIL(lhs, rexpr) \
224 ICEBERG_UNWRAP_OR_FAIL_IMPL(ICEBERG_ASSIGN_OR_RAISE_NAME(result_, __COUNTER__), lhs, \
225 rexpr)
226
227} // namespace iceberg
Definition matchers.h:109
Definition matchers.h:151