1 | //===- unittests/ADT/FallibleIteratorTest.cpp - fallible_iterator.h tests -===// |
2 | // |
3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
4 | // See https://llvm.org/LICENSE.txt for license information. |
5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
6 | // |
7 | //===----------------------------------------------------------------------===// |
8 | |
9 | #include "llvm/ADT/fallible_iterator.h" |
10 | #include "llvm/Testing/Support/Error.h" |
11 | |
12 | #include "gtest/gtest-spi.h" |
13 | #include "gtest/gtest.h" |
14 | |
15 | #include <utility> |
16 | #include <vector> |
17 | |
18 | using namespace llvm; |
19 | |
20 | namespace { |
21 | |
22 | using ItemValid = enum { ValidItem, InvalidItem }; |
23 | using LinkValid = enum { ValidLink, InvalidLink }; |
24 | |
25 | class Item { |
26 | public: |
27 | Item(ItemValid V) : V(V) {} |
28 | bool isValid() const { return V == ValidItem; } |
29 | |
30 | private: |
31 | ItemValid V; |
32 | }; |
33 | |
34 | // A utility to mock "bad collections". It supports both invalid items, |
35 | // where the dereference operator may return an Error, and bad links |
36 | // where the inc/dec operations may return an Error. |
37 | // Each element of the mock collection contains a pair of a (possibly broken) |
38 | // item and link. |
39 | using FallibleCollection = std::vector<std::pair<Item, LinkValid>>; |
40 | |
41 | class FallibleCollectionWalker { |
42 | public: |
43 | FallibleCollectionWalker(FallibleCollection &C, unsigned Idx) |
44 | : C(C), Idx(Idx) {} |
45 | |
46 | Item &operator*() { return C[Idx].first; } |
47 | |
48 | const Item &operator*() const { return C[Idx].first; } |
49 | |
50 | Error inc() { |
51 | assert(Idx != C.size() && "Walking off end of (mock) collection" ); |
52 | if (C[Idx].second == ValidLink) { |
53 | ++Idx; |
54 | return Error::success(); |
55 | } |
56 | return make_error<StringError>(Args: "cant get next object in (mock) collection" , |
57 | Args: inconvertibleErrorCode()); |
58 | } |
59 | |
60 | Error dec() { |
61 | assert(Idx != 0 && "Walking off start of (mock) collection" ); |
62 | --Idx; |
63 | if (C[Idx].second == ValidLink) |
64 | return Error::success(); |
65 | return make_error<StringError>(Args: "cant get prev object in (mock) collection" , |
66 | Args: inconvertibleErrorCode()); |
67 | } |
68 | |
69 | friend bool operator==(const FallibleCollectionWalker &LHS, |
70 | const FallibleCollectionWalker &RHS) { |
71 | assert(&LHS.C == &RHS.C && "Comparing iterators across collectionss." ); |
72 | return LHS.Idx == RHS.Idx; |
73 | } |
74 | |
75 | private: |
76 | FallibleCollection &C; |
77 | unsigned Idx; |
78 | }; |
79 | |
80 | class FallibleCollectionWalkerWithStructDeref |
81 | : public FallibleCollectionWalker { |
82 | public: |
83 | using FallibleCollectionWalker::FallibleCollectionWalker; |
84 | |
85 | Item *operator->() { return &this->operator*(); } |
86 | |
87 | const Item *operator->() const { return &this->operator*(); } |
88 | }; |
89 | |
90 | class FallibleCollectionWalkerWithFallibleDeref |
91 | : public FallibleCollectionWalker { |
92 | public: |
93 | using FallibleCollectionWalker::FallibleCollectionWalker; |
94 | |
95 | Expected<Item &> operator*() { |
96 | auto &I = FallibleCollectionWalker::operator*(); |
97 | if (!I.isValid()) |
98 | return make_error<StringError>(Args: "bad item" , Args: inconvertibleErrorCode()); |
99 | return I; |
100 | } |
101 | |
102 | Expected<const Item &> operator*() const { |
103 | const auto &I = FallibleCollectionWalker::operator*(); |
104 | if (!I.isValid()) |
105 | return make_error<StringError>(Args: "bad item" , Args: inconvertibleErrorCode()); |
106 | return I; |
107 | } |
108 | }; |
109 | |
110 | TEST(FallibleIteratorTest, BasicSuccess) { |
111 | |
112 | // Check that a basic use-case involing successful iteration over a |
113 | // "FallibleCollection" works. |
114 | |
115 | FallibleCollection C({{ValidItem, ValidLink}, {ValidItem, ValidLink}}); |
116 | |
117 | FallibleCollectionWalker begin(C, 0); |
118 | FallibleCollectionWalker end(C, 2); |
119 | |
120 | Error Err = Error::success(); |
121 | for (auto &Elem : |
122 | make_fallible_range<FallibleCollectionWalker>(I: begin, E: end, Err)) |
123 | EXPECT_TRUE(Elem.isValid()); |
124 | cantFail(Err: std::move(Err)); |
125 | } |
126 | |
127 | TEST(FallibleIteratorTest, BasicFailure) { |
128 | |
129 | // Check that a iteration failure (due to the InvalidLink state on element one |
130 | // of the fallible collection) breaks out of the loop and raises an Error. |
131 | |
132 | FallibleCollection C({{ValidItem, ValidLink}, {ValidItem, InvalidLink}}); |
133 | |
134 | FallibleCollectionWalker begin(C, 0); |
135 | FallibleCollectionWalker end(C, 2); |
136 | |
137 | Error Err = Error::success(); |
138 | for (auto &Elem : |
139 | make_fallible_range<FallibleCollectionWalker>(I: begin, E: end, Err)) |
140 | EXPECT_TRUE(Elem.isValid()); |
141 | |
142 | EXPECT_THAT_ERROR(std::move(Err), Failed()) << "Expected failure value" ; |
143 | } |
144 | |
145 | TEST(FallibleIteratorTest, NoRedundantErrorCheckOnEarlyExit) { |
146 | |
147 | // Check that an early return from the loop body does not require a redundant |
148 | // check of Err. |
149 | |
150 | FallibleCollection C({{ValidItem, ValidLink}, {ValidItem, ValidLink}}); |
151 | |
152 | FallibleCollectionWalker begin(C, 0); |
153 | FallibleCollectionWalker end(C, 2); |
154 | |
155 | Error Err = Error::success(); |
156 | for (auto &Elem : |
157 | make_fallible_range<FallibleCollectionWalker>(I: begin, E: end, Err)) { |
158 | (void)Elem; |
159 | return; |
160 | } |
161 | // Err not checked, but should be ok because we exit from the loop |
162 | // body. |
163 | } |
164 | |
165 | #if LLVM_ENABLE_ABI_BREAKING_CHECKS |
166 | TEST(FallibleIteratorTest, RegularLoopExitRequiresErrorCheck) { |
167 | |
168 | // Check that Err must be checked after a normal (i.e. not early) loop exit |
169 | // by failing to check and expecting program death (due to the unchecked |
170 | // error). |
171 | |
172 | EXPECT_DEATH( |
173 | { |
174 | FallibleCollection C({{ValidItem, ValidLink}, {ValidItem, ValidLink}}); |
175 | |
176 | FallibleCollectionWalker begin(C, 0); |
177 | FallibleCollectionWalker end(C, 2); |
178 | |
179 | Error Err = Error::success(); |
180 | for (auto &Elem : |
181 | make_fallible_range<FallibleCollectionWalker>(begin, end, Err)) |
182 | (void)Elem; |
183 | }, |
184 | "Program aborted due to an unhandled Error:" ) |
185 | << "Normal (i.e. not early) loop exit should require an error check" ; |
186 | } |
187 | #endif |
188 | |
189 | TEST(FallibleIteratorTest, RawIncrementAndDecrementBehavior) { |
190 | |
191 | // Check the exact behavior of increment / decrement. |
192 | |
193 | FallibleCollection C({{ValidItem, ValidLink}, |
194 | {ValidItem, InvalidLink}, |
195 | {ValidItem, ValidLink}, |
196 | {ValidItem, InvalidLink}}); |
197 | |
198 | { |
199 | // One increment from begin succeeds. |
200 | Error Err = Error::success(); |
201 | auto I = make_fallible_itr(I: FallibleCollectionWalker(C, 0), Err); |
202 | ++I; |
203 | EXPECT_THAT_ERROR(std::move(Err), Succeeded()); |
204 | } |
205 | |
206 | { |
207 | // Two increments from begin fail. |
208 | Error Err = Error::success(); |
209 | auto I = make_fallible_itr(I: FallibleCollectionWalker(C, 0), Err); |
210 | ++I; |
211 | EXPECT_THAT_ERROR(std::move(Err), Succeeded()); |
212 | ++I; |
213 | EXPECT_THAT_ERROR(std::move(Err), Failed()) << "Expected failure value" ; |
214 | } |
215 | |
216 | { |
217 | // One decement from element three succeeds. |
218 | Error Err = Error::success(); |
219 | auto I = make_fallible_itr(I: FallibleCollectionWalker(C, 3), Err); |
220 | --I; |
221 | EXPECT_THAT_ERROR(std::move(Err), Succeeded()); |
222 | } |
223 | |
224 | { |
225 | // One decement from element three succeeds. |
226 | Error Err = Error::success(); |
227 | auto I = make_fallible_itr(I: FallibleCollectionWalker(C, 3), Err); |
228 | --I; |
229 | EXPECT_THAT_ERROR(std::move(Err), Succeeded()); |
230 | --I; |
231 | EXPECT_THAT_ERROR(std::move(Err), Failed()); |
232 | } |
233 | } |
234 | |
235 | TEST(FallibleIteratorTest, CheckStructDerefOperatorSupport) { |
236 | // Check that the fallible_iterator wrapper forwards through to the |
237 | // underlying iterator's structure dereference operator if present. |
238 | |
239 | FallibleCollection C({{ValidItem, ValidLink}, |
240 | {ValidItem, ValidLink}, |
241 | {InvalidItem, InvalidLink}}); |
242 | |
243 | FallibleCollectionWalkerWithStructDeref begin(C, 0); |
244 | |
245 | { |
246 | Error Err = Error::success(); |
247 | auto I = make_fallible_itr(I: begin, Err); |
248 | EXPECT_TRUE(I->isValid()); |
249 | cantFail(Err: std::move(Err)); |
250 | } |
251 | |
252 | { |
253 | Error Err = Error::success(); |
254 | const auto I = make_fallible_itr(I: begin, Err); |
255 | EXPECT_TRUE(I->isValid()); |
256 | cantFail(Err: std::move(Err)); |
257 | } |
258 | } |
259 | |
260 | TEST(FallibleIteratorTest, CheckDerefToExpectedSupport) { |
261 | |
262 | // Check that the fallible_iterator wrapper forwards value types, in |
263 | // particular llvm::Expected, correctly. |
264 | |
265 | FallibleCollection C({{ValidItem, ValidLink}, |
266 | {InvalidItem, ValidLink}, |
267 | {ValidItem, ValidLink}}); |
268 | |
269 | FallibleCollectionWalkerWithFallibleDeref begin(C, 0); |
270 | FallibleCollectionWalkerWithFallibleDeref end(C, 3); |
271 | |
272 | Error Err = Error::success(); |
273 | auto I = make_fallible_itr(I: begin, Err); |
274 | auto E = make_fallible_end(E: end); |
275 | |
276 | Expected<Item> V1 = *I; |
277 | EXPECT_THAT_ERROR(V1.takeError(), Succeeded()); |
278 | ++I; |
279 | EXPECT_NE(I, E); // Implicitly check error. |
280 | Expected<Item> V2 = *I; |
281 | EXPECT_THAT_ERROR(V2.takeError(), Failed()); |
282 | ++I; |
283 | EXPECT_NE(I, E); // Implicitly check error. |
284 | Expected<Item> V3 = *I; |
285 | EXPECT_THAT_ERROR(V3.takeError(), Succeeded()); |
286 | ++I; |
287 | EXPECT_EQ(I, E); |
288 | cantFail(Err: std::move(Err)); |
289 | } |
290 | |
291 | } // namespace |
292 | |