]>
Commit | Line | Data |
---|---|---|
92f5a8d4 | 1 | /* Unit testing for outcomes |
f67539c2 | 2 | (C) 2013-2020 Niall Douglas <http://www.nedproductions.biz/> (5 commits) |
92f5a8d4 TL |
3 | |
4 | ||
5 | Boost Software License - Version 1.0 - August 17th, 2003 | |
6 | ||
7 | Permission is hereby granted, free of charge, to any person or organization | |
8 | obtaining a copy of the software and accompanying documentation covered by | |
9 | this license (the "Software") to use, reproduce, display, distribute, | |
10 | execute, and transmit the Software, and to prepare derivative works of the | |
11 | Software, and to permit third-parties to whom the Software is furnished to | |
12 | do so, all subject to the following: | |
13 | ||
14 | The copyright notices in the Software and this entire statement, including | |
15 | the above license grant, this restriction and the following disclaimer, | |
16 | must be included in all copies of the Software, in whole or in part, and | |
17 | all derivative works of the Software, unless such copies or derivative | |
18 | works are solely in the form of machine-executable object code generated by | |
19 | a source language processor. | |
20 | ||
21 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
22 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
23 | FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT | |
24 | SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE | |
25 | FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, | |
26 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER | |
27 | DEALINGS IN THE SOFTWARE. | |
28 | */ | |
29 | ||
30 | #include <boost/outcome/outcome.hpp> | |
31 | #include <boost/test/unit_test.hpp> | |
32 | #include <boost/test/unit_test_monitor.hpp> | |
33 | ||
f67539c2 TL |
34 | /* Should be this: |
35 | ||
36 | 78 move constructor count = 2 | |
37 | 65 move assignment count = 3 | |
38 | 78 move assignment count = 1 | |
39 | 65 move constructor count = 2 | |
40 | 78 move assignment count = 0 | |
41 | 65 move assignment count = 1 | |
42 | ||
43 | 78 move constructor count = 1 | |
44 | 65 move assignment count = 2 | |
45 | 78 move assignment count = 0 | |
46 | 65 move assignment count = 1 | |
47 | 78 move assignment count = 0 | |
48 | ||
49 | 78 move constructor count = 2 | |
50 | 65 move assignment count = 3 | |
51 | 78 move assignment count = 1 | |
52 | 65 move constructor count = 2 | |
53 | 78 move assignment count = 0 | |
54 | 65 move assignment count = 1 | |
55 | ||
56 | 78 move constructor count = 1 | |
57 | 65 move assignment count = 2 | |
58 | 78 move assignment count = 0 | |
59 | 65 move assignment count = 1 | |
60 | 78 move assignment count = 0 | |
61 | ||
62 | 78 move constructor count = 2 | |
63 | 65 move assignment count = 3 | |
64 | 78 move assignment count = 1 | |
65 | 65 move constructor count = 2 | |
66 | 78 move assignment count = 0 | |
67 | 65 move assignment count = 1 | |
68 | ||
69 | 78 move constructor count = 1 | |
70 | 65 move assignment count = 2 | |
71 | 78 move assignment count = 0 | |
72 | 65 move assignment count = 1 | |
73 | 78 move assignment count = 0 | |
74 | ||
75 | 78 move constructor count = 2 | |
76 | 65 move assignment count = 3 | |
77 | 78 move assignment count = 1 | |
78 | 65 move constructor count = 2 | |
79 | 78 move assignment count = 0 | |
80 | 65 move assignment count = 1 | |
81 | ||
82 | 78 move constructor count = 1 | |
83 | 65 move assignment count = 2 | |
84 | 78 move assignment count = 0 | |
85 | 65 move assignment count = 1 | |
86 | 78 move assignment count = 0 | |
87 | */ | |
88 | ||
92f5a8d4 TL |
89 | #ifndef BOOST_NO_EXCEPTIONS |
90 | #ifdef _MSC_VER | |
91 | #pragma warning(push) | |
92 | #pragma warning(disable: 4297) // function assumed not to throw an exception but does | |
93 | #endif | |
94 | #if defined(__GNUC__) && !defined(__clang__) | |
95 | #pragma GCC diagnostic push | |
96 | #pragma GCC diagnostic ignored "-Wterminate" | |
97 | #endif | |
98 | template <bool mc, bool ma> struct Throwy | |
99 | { | |
100 | int count{0}, inc{0}, id{0}; | |
101 | Throwy() = default; | |
102 | Throwy(int c, int d, int i = 1) noexcept | |
103 | : count(c) | |
104 | , inc(i) | |
105 | , id(d) | |
106 | { | |
107 | } | |
108 | Throwy(const Throwy &) = delete; | |
109 | Throwy &operator=(const Throwy &) = delete; | |
110 | Throwy(Throwy &&o) noexcept(!mc) | |
111 | : count(o.count - o.inc) | |
112 | , inc(o.inc) | |
113 | , id(o.id) // NOLINT | |
114 | { | |
115 | if(mc) | |
116 | { | |
f67539c2 | 117 | std::cout << " " << id << " move constructor count = " << count << std::endl; |
92f5a8d4 TL |
118 | if(!count) |
119 | { | |
f67539c2 | 120 | std::cout << " " << id << " move constructor throws!" << std::endl; |
92f5a8d4 TL |
121 | throw std::bad_alloc(); |
122 | } | |
123 | } | |
124 | o.count = 0; | |
125 | o.inc = 0; | |
126 | o.id = 0; | |
127 | } | |
128 | Throwy &operator=(Throwy &&o) noexcept(!ma) | |
129 | { | |
130 | count = o.count - o.inc; | |
131 | if(ma) | |
132 | { | |
f67539c2 | 133 | std::cout << " " << o.id << " move assignment count = " << count << std::endl; |
92f5a8d4 TL |
134 | if(!count) |
135 | { | |
f67539c2 | 136 | std::cout << " " << o.id << " move assignment throws!" << std::endl; |
92f5a8d4 TL |
137 | throw std::bad_alloc(); |
138 | } | |
139 | } | |
140 | inc = o.inc; | |
141 | id = o.id; | |
142 | o.count = 0; | |
143 | o.inc = 0; | |
144 | o.id = 0; | |
145 | return *this; | |
146 | } | |
147 | ~Throwy() = default; | |
148 | }; | |
149 | #if defined(__GNUC__) && !defined(__clang__) | |
150 | #pragma GCC diagnostic pop | |
151 | #endif | |
152 | #ifdef _MSC_VER | |
153 | #pragma warning(pop) | |
154 | #endif | |
155 | enum class ErrorCode | |
156 | { | |
157 | dummy | |
158 | }; | |
159 | enum class ErrorCode2 | |
160 | { | |
161 | dummy | |
162 | }; | |
163 | template <bool mc, bool ma> using resulty1 = BOOST_OUTCOME_V2_NAMESPACE::result<Throwy<mc, ma>, ErrorCode, BOOST_OUTCOME_V2_NAMESPACE::policy::all_narrow>; | |
164 | template <bool mc, bool ma> using resulty2 = BOOST_OUTCOME_V2_NAMESPACE::result<ErrorCode, Throwy<mc, ma>, BOOST_OUTCOME_V2_NAMESPACE::policy::all_narrow>; | |
165 | template <bool mc, bool ma> using outcomey1 = BOOST_OUTCOME_V2_NAMESPACE::outcome<ErrorCode, Throwy<mc, ma>, ErrorCode2, BOOST_OUTCOME_V2_NAMESPACE::policy::all_narrow>; | |
166 | template <bool mc, bool ma> using outcomey2 = BOOST_OUTCOME_V2_NAMESPACE::outcome<ErrorCode, ErrorCode2, Throwy<mc, ma>, BOOST_OUTCOME_V2_NAMESPACE::policy::all_narrow>; | |
167 | #endif | |
168 | ||
169 | BOOST_OUTCOME_AUTO_TEST_CASE(works_outcome_swap, "Tests that the outcome swaps as intended") | |
170 | { | |
171 | using namespace BOOST_OUTCOME_V2_NAMESPACE; | |
172 | { // Does swap actually swap? | |
173 | outcome<std::string> a("niall"), b("douglas"); | |
174 | BOOST_CHECK(a.value() == "niall"); | |
175 | BOOST_CHECK(b.value() == "douglas"); | |
176 | swap(a, b); | |
177 | BOOST_CHECK(a.value() == "douglas"); | |
178 | BOOST_CHECK(b.value() == "niall"); | |
179 | a = boost::system::errc::not_enough_memory; | |
180 | swap(a, b); | |
181 | BOOST_CHECK(a.value() == "niall"); | |
182 | BOOST_CHECK(b.error() == boost::system::errc::not_enough_memory); | |
183 | BOOST_CHECK(!a.has_lost_consistency()); | |
184 | BOOST_CHECK(!b.has_lost_consistency()); | |
185 | } | |
186 | #ifndef BOOST_NO_EXCEPTIONS | |
187 | { // Is noexcept propagated? | |
188 | using nothrow_t = Throwy<false, false>; | |
189 | using nothrow = resulty1<false, false>; | |
190 | static_assert(std::is_nothrow_move_constructible<nothrow_t>::value, "throwy not correct!"); | |
191 | static_assert(std::is_nothrow_move_assignable<nothrow_t>::value, "throwy not correct!"); | |
192 | static_assert(std::is_nothrow_move_constructible<nothrow>::value, "type not correct!"); | |
193 | static_assert(std::is_nothrow_move_assignable<nothrow>::value, "type not correct!"); | |
194 | ||
195 | static_assert(detail::is_nothrow_swappable<nothrow_t>::value, "is_nothrow_swappable is not correct!"); | |
196 | ||
197 | static_assert(noexcept(nothrow(0, 0)), "type has a throwing value constructor!"); | |
198 | nothrow a(1, 78), b(1, 65); | |
199 | a.swap(b); | |
200 | static_assert(noexcept(a.swap(b)), "type has a throwing swap!"); | |
201 | } | |
202 | { // Is noexcept propagated? | |
203 | using nothrow_t = Throwy<false, false>; | |
204 | using nothrow = resulty2<false, false>; | |
205 | static_assert(std::is_nothrow_move_constructible<nothrow_t>::value, "throwy not correct!"); | |
206 | static_assert(std::is_nothrow_move_assignable<nothrow_t>::value, "throwy not correct!"); | |
207 | static_assert(std::is_nothrow_move_constructible<nothrow>::value, "type not correct!"); | |
208 | static_assert(std::is_nothrow_move_assignable<nothrow>::value, "type not correct!"); | |
209 | ||
210 | static_assert(detail::is_nothrow_swappable<nothrow_t>::value, "is_nothrow_swappable is not correct!"); | |
211 | ||
212 | static_assert(noexcept(nothrow(0, 0)), "type has a throwing value constructor!"); | |
213 | nothrow a(1, 78), b(1, 65); | |
214 | a.swap(b); | |
215 | static_assert(noexcept(a.swap(b)), "type has a throwing swap!"); | |
216 | } | |
217 | ||
218 | { // Does swap implement the strong guarantee? | |
219 | using throwy_t = Throwy<true, true>; | |
220 | using throwy = resulty1<true, true>; | |
221 | static_assert(!std::is_nothrow_move_constructible<throwy_t>::value, "throwy not correct!"); | |
222 | static_assert(!std::is_nothrow_move_assignable<throwy_t>::value, "throwy not correct!"); | |
223 | static_assert(!std::is_nothrow_move_constructible<throwy>::value, "type not correct!"); | |
224 | static_assert(!std::is_nothrow_move_assignable<throwy>::value, "type not correct!"); | |
225 | ||
226 | static_assert(!detail::is_nothrow_swappable<throwy_t>::value, "is_nothrow_swappable is not correct!"); | |
227 | ||
f67539c2 | 228 | std::cout << "Result value first swap succeeds, second swap second move assignment throws:" << std::endl; |
92f5a8d4 TL |
229 | { |
230 | throwy a(3, 78), b(4, 65); | |
231 | a.swap(b); | |
232 | static_assert(!noexcept(a.swap(b)), "type has a non-throwing swap!"); | |
233 | BOOST_CHECK(a.value().id == 65); | |
234 | BOOST_CHECK(b.value().id == 78); | |
235 | try | |
236 | { | |
237 | a.swap(b); // fails on first assignment | |
238 | BOOST_REQUIRE(false); | |
239 | } | |
240 | catch(const std::bad_alloc & /*unused*/) | |
241 | { | |
242 | BOOST_CHECK(a.value().id == 65); // ensure it is perfectly restored | |
243 | BOOST_CHECK(b.value().id == 78); | |
244 | } | |
245 | BOOST_CHECK(!a.has_lost_consistency()); | |
246 | BOOST_CHECK(!b.has_lost_consistency()); | |
247 | } | |
f67539c2 | 248 | std::cout << "\nResult value second move assignment throws, on recover second move assignment throws:" << std::endl; |
92f5a8d4 TL |
249 | { |
250 | throwy a(2, 78), b(3, 65); // fails on second assignment, cannot restore | |
251 | try | |
252 | { | |
253 | a.swap(b); | |
254 | BOOST_REQUIRE(false); | |
255 | } | |
256 | catch(const std::bad_alloc & /*unused*/) | |
257 | { | |
258 | BOOST_CHECK(a.has_lost_consistency()); // both must be marked tainted | |
259 | BOOST_CHECK(b.has_lost_consistency()); | |
260 | } | |
261 | } | |
92f5a8d4 | 262 | } |
f67539c2 | 263 | std::cout << "\nResult error first swap succeeds, second swap first move assignment throws:" << std::endl; |
92f5a8d4 TL |
264 | { // Does swap implement the strong guarantee? |
265 | using throwy_t = Throwy<true, true>; | |
266 | using throwy = resulty2<true, true>; | |
267 | static_assert(!std::is_nothrow_move_constructible<throwy_t>::value, "throwy not correct!"); | |
268 | static_assert(!std::is_nothrow_move_assignable<throwy_t>::value, "throwy not correct!"); | |
269 | static_assert(!std::is_nothrow_move_constructible<throwy>::value, "type not correct!"); | |
270 | static_assert(!std::is_nothrow_move_assignable<throwy>::value, "type not correct!"); | |
271 | ||
272 | static_assert(!detail::is_nothrow_swappable<throwy_t>::value, "is_nothrow_swappable is not correct!"); | |
273 | ||
274 | { | |
275 | throwy a(3, 78), b(4, 65); | |
276 | a.swap(b); | |
277 | static_assert(!noexcept(a.swap(b)), "type has a non-throwing swap!"); | |
278 | BOOST_CHECK(a.error().id == 65); | |
279 | BOOST_CHECK(b.error().id == 78); | |
280 | try | |
281 | { | |
282 | a.swap(b); // fails on first assignment | |
283 | BOOST_REQUIRE(false); | |
284 | } | |
285 | catch(const std::bad_alloc & /*unused*/) | |
286 | { | |
287 | BOOST_CHECK(a.error().id == 65); // ensure it is perfectly restored | |
288 | BOOST_CHECK(b.error().id == 78); | |
289 | } | |
290 | BOOST_CHECK(!a.has_lost_consistency()); | |
291 | BOOST_CHECK(!b.has_lost_consistency()); | |
292 | } | |
f67539c2 | 293 | std::cout << "\nResult error second move assignment throws, on recover second move assignment throws:" << std::endl; |
92f5a8d4 TL |
294 | { |
295 | throwy a(2, 78), b(3, 65); // fails on second assignment, cannot restore | |
296 | try | |
297 | { | |
298 | a.swap(b); | |
299 | BOOST_REQUIRE(false); | |
300 | } | |
301 | catch(const std::bad_alloc & /*unused*/) | |
302 | { | |
303 | BOOST_CHECK(a.has_lost_consistency()); // both must be marked tainted | |
304 | BOOST_CHECK(b.has_lost_consistency()); | |
305 | } | |
306 | } | |
92f5a8d4 TL |
307 | } |
308 | ||
309 | { // Is noexcept propagated? | |
310 | using nothrow_t = Throwy<false, false>; | |
311 | using nothrow = outcomey1<false, false>; | |
312 | static_assert(std::is_nothrow_move_constructible<nothrow_t>::value, "throwy not correct!"); | |
313 | static_assert(std::is_nothrow_move_assignable<nothrow_t>::value, "throwy not correct!"); | |
314 | static_assert(std::is_nothrow_move_constructible<nothrow>::value, "type not correct!"); | |
315 | static_assert(std::is_nothrow_move_assignable<nothrow>::value, "type not correct!"); | |
316 | ||
317 | static_assert(detail::is_nothrow_swappable<nothrow_t>::value, "is_nothrow_swappable is not correct!"); | |
318 | ||
319 | static_assert(noexcept(nothrow(0, 0)), "type has a throwing value constructor!"); | |
320 | nothrow a(1, 78), b(1, 65); | |
321 | a.swap(b); | |
322 | static_assert(noexcept(a.swap(b)), "type has a throwing swap!"); | |
323 | } | |
324 | { // Is noexcept propagated? | |
325 | using nothrow_t = Throwy<false, false>; | |
326 | using nothrow = outcomey1<false, false>; | |
327 | static_assert(std::is_nothrow_move_constructible<nothrow_t>::value, "throwy not correct!"); | |
328 | static_assert(std::is_nothrow_move_assignable<nothrow_t>::value, "throwy not correct!"); | |
329 | static_assert(std::is_nothrow_move_constructible<nothrow>::value, "type not correct!"); | |
330 | static_assert(std::is_nothrow_move_assignable<nothrow>::value, "type not correct!"); | |
331 | ||
332 | static_assert(detail::is_nothrow_swappable<nothrow_t>::value, "is_nothrow_swappable is not correct!"); | |
333 | ||
334 | static_assert(noexcept(nothrow(0, 0)), "type has a throwing value constructor!"); | |
335 | nothrow a(1, 78), b(1, 65); | |
336 | a.swap(b); | |
337 | static_assert(noexcept(a.swap(b)), "type has a throwing swap!"); | |
338 | } | |
339 | ||
f67539c2 | 340 | std::cout << "\n\nOutcome value first swap succeeds, second swap first move assignment throws:" << std::endl; |
92f5a8d4 TL |
341 | { // Does swap implement the strong guarantee? |
342 | using throwy_t = Throwy<true, true>; | |
343 | using throwy = outcomey1<true, true>; | |
344 | static_assert(!std::is_nothrow_move_constructible<throwy_t>::value, "throwy not correct!"); | |
345 | static_assert(!std::is_nothrow_move_assignable<throwy_t>::value, "throwy not correct!"); | |
346 | static_assert(!std::is_nothrow_move_constructible<throwy>::value, "type not correct!"); | |
347 | static_assert(!std::is_nothrow_move_assignable<throwy>::value, "type not correct!"); | |
348 | ||
349 | static_assert(!detail::is_nothrow_swappable<throwy_t>::value, "is_nothrow_swappable is not correct!"); | |
350 | ||
351 | { | |
352 | throwy a(3, 78), b(4, 65); | |
353 | a.swap(b); | |
354 | static_assert(!noexcept(a.swap(b)), "type has a non-throwing swap!"); | |
355 | BOOST_CHECK(a.error().id == 65); | |
356 | BOOST_CHECK(b.error().id == 78); | |
357 | try | |
358 | { | |
359 | a.swap(b); // fails on first assignment | |
360 | BOOST_REQUIRE(false); | |
361 | } | |
362 | catch(const std::bad_alloc & /*unused*/) | |
363 | { | |
364 | BOOST_CHECK(a.error().id == 65); // ensure it is perfectly restored | |
365 | BOOST_CHECK(b.error().id == 78); | |
366 | } | |
367 | BOOST_CHECK(!a.has_lost_consistency()); | |
368 | BOOST_CHECK(!b.has_lost_consistency()); | |
369 | } | |
f67539c2 | 370 | std::cout << "\nOutcome value second move assignment throws, on recover second move assignment throws:" << std::endl; |
92f5a8d4 TL |
371 | { |
372 | throwy a(2, 78), b(3, 65); // fails on second assignment, cannot restore | |
373 | try | |
374 | { | |
375 | a.swap(b); | |
376 | BOOST_REQUIRE(false); | |
377 | } | |
378 | catch(const std::bad_alloc & /*unused*/) | |
379 | { | |
380 | BOOST_CHECK(a.has_lost_consistency()); // both must be marked tainted | |
381 | BOOST_CHECK(b.has_lost_consistency()); | |
382 | } | |
383 | } | |
92f5a8d4 | 384 | } |
f67539c2 | 385 | std::cout << "\nOutcome error first swap succeeds, second swap first move assignment throws:" << std::endl; |
92f5a8d4 TL |
386 | { // Does swap implement the strong guarantee? |
387 | using throwy_t = Throwy<true, true>; | |
388 | using throwy = outcomey2<true, true>; | |
389 | static_assert(!std::is_nothrow_move_constructible<throwy_t>::value, "throwy not correct!"); | |
390 | static_assert(!std::is_nothrow_move_assignable<throwy_t>::value, "throwy not correct!"); | |
391 | static_assert(!std::is_nothrow_move_constructible<throwy>::value, "type not correct!"); | |
392 | static_assert(!std::is_nothrow_move_assignable<throwy>::value, "type not correct!"); | |
393 | ||
394 | static_assert(!detail::is_nothrow_swappable<throwy_t>::value, "is_nothrow_swappable is not correct!"); | |
395 | ||
396 | { | |
397 | throwy a(3, 78), b(4, 65); | |
398 | a.swap(b); | |
399 | static_assert(!noexcept(a.swap(b)), "type has a non-throwing swap!"); | |
400 | BOOST_CHECK(a.exception().id == 65); | |
401 | BOOST_CHECK(b.exception().id == 78); | |
402 | try | |
403 | { | |
404 | a.swap(b); // fails on first assignment | |
405 | BOOST_REQUIRE(false); | |
406 | } | |
407 | catch(const std::bad_alloc & /*unused*/) | |
408 | { | |
409 | BOOST_CHECK(a.exception().id == 65); // ensure it is perfectly restored | |
410 | BOOST_CHECK(b.exception().id == 78); | |
411 | } | |
412 | BOOST_CHECK(!a.has_lost_consistency()); | |
413 | BOOST_CHECK(!b.has_lost_consistency()); | |
414 | } | |
f67539c2 | 415 | std::cout << "\nOutcome error second move assignment throws, on recover second move assignment throws:" << std::endl; |
92f5a8d4 TL |
416 | { |
417 | throwy a(2, 78), b(3, 65); // fails on second assignment, cannot restore | |
418 | try | |
419 | { | |
420 | a.swap(b); | |
421 | BOOST_REQUIRE(false); | |
422 | } | |
423 | catch(const std::bad_alloc & /*unused*/) | |
424 | { | |
425 | BOOST_CHECK(a.has_lost_consistency()); // both must be marked tainted | |
426 | BOOST_CHECK(b.has_lost_consistency()); | |
427 | } | |
428 | } | |
429 | std::cout << std::endl; | |
430 | } | |
431 | #endif | |
432 | } |