1/* Unit testing for outcomes
2(C) 2013-2024 Niall Douglas <http://www.nedproductions.biz/> (30 commits)
3
4
5Boost Software License - Version 1.0 - August 17th, 2003
6
7Permission is hereby granted, free of charge, to any person or organization
8obtaining a copy of the software and accompanying documentation covered by
9this license (the "Software") to use, reproduce, display, distribute,
10execute, and transmit the Software, and to prepare derivative works of the
11Software, and to permit third-parties to whom the Software is furnished to
12do so, all subject to the following:
13
14The copyright notices in the Software and this entire statement, including
15the above license grant, this restriction and the following disclaimer,
16must be included in all copies of the Software, in whole or in part, and
17all derivative works of the Software, unless such copies or derivative
18works are solely in the form of machine-executable object code generated by
19a source language processor.
20
21THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
24SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
25FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
26ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
27DEALINGS IN THE SOFTWARE.
28*/
29
30#define BOOST_OUTCOME_USE_STD_ADDRESSOF 1
31
32#ifdef TESTING_WG21_EXPERIMENTAL_RESULT
33#include <boost/outcome/experimental/result.hpp>
34#define BOOST_OUTCOME_AUTO_TEST_CASE(...) BOOST_AUTO_TEST_CASE(__VA_ARGS__)
35#else
36#include <boost/outcome/result.hpp>
37#endif
38#include <boost/test/unit_test.hpp>
39#include <boost/test/unit_test_monitor.hpp>
40
41#include <iostream>
42
43#ifndef BOOST_NO_EXCEPTIONS
44// Custom error type with payload
45struct payload
46{
47 boost::system::error_code ec;
48 const char *str{nullptr};
49 payload() = default;
50 payload(boost::system::errc::errc_t _ec, const char *_str)
51 : ec(make_error_code(e: _ec))
52 , str(_str)
53 {
54 }
55};
56struct payload_exception : std::runtime_error
57{
58 explicit payload_exception(const char *what)
59 : std::runtime_error(what)
60 {
61 }
62};
63inline const boost::system::error_code &make_error_code(const payload &p)
64{
65 return p.ec;
66}
67inline void outcome_throw_as_system_error_with_payload(const payload &p)
68{
69 throw payload_exception(p.str);
70}
71#endif
72
73BOOST_OUTCOME_AUTO_TEST_CASE(works_result, "Tests that the result works as intended")
74{
75#ifdef TESTING_WG21_EXPERIMENTAL_RESULT
76 using namespace std::experimental;
77 using std::in_place_type;
78#else
79 using namespace BOOST_OUTCOME_V2_NAMESPACE;
80#endif
81
82 static_assert(std::is_constructible<result<long>, int>::value, "Sanity check that monad can be constructed from a value_type");
83 static_assert(!std::is_constructible<result<result<long>>, int>::value, "Sanity check that outer monad can be constructed from an inner monad's value_type");
84#if defined(__clang__) || !defined(__GNUC__) || __GNUC__ >= 9 // GCCs before 9 barf on this
85 static_assert(!std::is_constructible<result<result<result<long>>>, int>::value, "Sanity check that outer monad can be constructed from an inner inner monad's value_type");
86 static_assert(!std::is_constructible<result<result<result<result<long>>>>, int>::value, "Sanity check that outer monad can be constructed from an inner inner monad's value_type");
87#endif
88
89 static_assert(std::is_constructible<result<int>, result<long>>::value, "Sanity check that compatible monads can be constructed from one another");
90 static_assert(std::is_constructible<result<result<int>>, result<long>>::value, "Sanity check that outer monad can be constructed from a compatible monad");
91#if defined(__clang__) || !defined(__GNUC__) || __GNUC__ >= 9 // GCCs before 9 barf on this
92 static_assert(!std::is_constructible<result<result<result<int>>>, result<long>>::value, "Sanity check that outer monad can be constructed from a compatible monad up to two nestings deep");
93 static_assert(!std::is_constructible<result<result<result<result<int>>>>, result<long>>::value, "Sanity check that outer monad can be constructed from a compatible monad three or more nestings deep");
94#endif
95 static_assert(!std::is_constructible<result<std::string>, result<int>>::value, "Sanity check that incompatible monads cannot be constructed from one another");
96
97#ifndef TESTING_WG21_EXPERIMENTAL_RESULT
98 static_assert(std::is_constructible<result<int>, result<void>>::value, "Sanity check that all monads can be constructed from a void monad");
99 static_assert(std::is_constructible<result<result<int>>, result<void>>::value, "Sanity check that outer monad can be constructed from a compatible monad");
100#if defined(__clang__) || !defined(__GNUC__) || __GNUC__ >= 9 // GCCs before 9 barf on this
101 static_assert(std::is_constructible<result<result<result<int>>>, result<void>>::value, "Sanity check that outer monad can be constructed from a compatible monad up to two nestings deep");
102#endif
103 static_assert(!std::is_constructible<result<void>, result<int>>::value, "Sanity check that incompatible monads cannot be constructed from one another");
104#endif
105 static_assert(std::is_void<result<void>::value_type>::value, "Sanity check that result<void> has a void value_type");
106#ifndef TESTING_WG21_EXPERIMENTAL_RESULT
107 static_assert(std::is_void<result<void, void>::error_type>::value, "Sanity check that result<void, void> has a void error_type");
108#endif
109
110 static_assert(std::is_same<result<int>::value_type, int>::value, "Sanity check that result<int> has a int value_type");
111 static_assert(std::is_same<result<int>::error_type, boost::system::error_code>::value, "Sanity check that result<int> has a error_code error_type");
112
113
114 { // errored int
115 result<int> m(boost::system::errc::bad_address);
116 BOOST_CHECK(!m);
117 BOOST_CHECK(!m.has_value());
118 BOOST_CHECK(m.has_error());
119 // BOOST_CHECK(!m.has_exception());
120 BOOST_CHECK_THROW(m.value(), boost::system::system_error);
121 BOOST_CHECK_NO_THROW(m.error());
122 }
123 { // errored void
124 result<void> m(boost::system::errc::bad_address);
125 BOOST_CHECK(!m);
126 BOOST_CHECK(!m.has_value());
127 BOOST_CHECK(m.has_error());
128// BOOST_CHECK(!m.has_exception());
129#ifndef TESTING_WG21_EXPERIMENTAL_RESULT
130 BOOST_CHECK_THROW(([&m]() -> void { return m.value(); }()), boost::system::system_error);
131#endif
132 BOOST_CHECK_NO_THROW(m.error());
133 }
134 { // valued int
135 result<int> m(5);
136 BOOST_CHECK(m);
137 BOOST_CHECK(m.has_value());
138 BOOST_CHECK(!m.has_error());
139 // BOOST_CHECK(!m.has_exception());
140 BOOST_CHECK(m.value() == 5);
141 m.value() = 6;
142 BOOST_CHECK(m.value() == 6);
143 BOOST_CHECK_THROW(m.error(), bad_result_access);
144 }
145 { // valued bool
146 result<bool> m(false);
147 BOOST_CHECK(m);
148 BOOST_CHECK(m.has_value());
149 BOOST_CHECK(!m.has_error());
150 // BOOST_CHECK(!m.has_exception());
151 BOOST_CHECK(m.value() == false);
152 m.value() = true;
153 BOOST_CHECK(m.value() == true);
154 BOOST_CHECK_THROW(m.error(), bad_result_access);
155 }
156 { // moves do not clear state
157 result<std::string> m("niall");
158 BOOST_CHECK(m);
159 BOOST_CHECK(m.has_value());
160 BOOST_CHECK(!m.has_error());
161 // BOOST_CHECK(!m.has_exception());
162 BOOST_CHECK(m.value() == "niall");
163 m.value() = "NIALL";
164 BOOST_CHECK(m.value() == "NIALL");
165 auto temp(std::move(m).value());
166 BOOST_CHECK(temp == "NIALL");
167 BOOST_CHECK(m.value().empty()); // NOLINT
168 }
169 { // valued void
170 result<void> m(in_place_type<void>);
171 BOOST_CHECK(m);
172 BOOST_CHECK(m.has_value());
173 BOOST_CHECK(!m.has_error());
174 // BOOST_CHECK(!m.has_exception());
175 BOOST_CHECK_NO_THROW(m.value()); // works, but type returned is unusable
176 BOOST_CHECK_THROW(m.error(), bad_result_access);
177 }
178 { // errored
179 boost::system::error_code ec(5, boost::system::system_category());
180 result<int> m(ec);
181 BOOST_CHECK(!m);
182 BOOST_CHECK(!m.has_value());
183 BOOST_CHECK(m.has_error());
184 // BOOST_CHECK(!m.has_exception());
185 BOOST_CHECK_THROW(m.value(), boost::system::system_error);
186 BOOST_CHECK(m.error() == ec);
187 }
188#if !defined(__APPLE__) || defined(__cpp_exceptions)
189 { // errored, custom
190 boost::system::error_code ec(5, boost::system::system_category());
191 auto e = boost::copy_exception(e: boost::system::system_error(ec)); // NOLINT
192 result<int, boost::exception_ptr> m(e);
193 BOOST_CHECK(!m);
194 BOOST_CHECK(!m.has_value());
195 BOOST_CHECK(m.has_error());
196 // BOOST_CHECK(!m.has_exception());
197 BOOST_CHECK_THROW(m.value(), boost::system::system_error);
198 BOOST_CHECK(m.error() == e);
199 }
200#endif
201#ifndef TESTING_WG21_EXPERIMENTAL_RESULT
202 { // custom error type
203 struct Foo
204 {
205 };
206 result<int, Foo> m(in_place_type<Foo>);
207 BOOST_CHECK(!m);
208 BOOST_CHECK(!m.has_value());
209 BOOST_CHECK(m.has_error());
210 // BOOST_CHECK(!m.has_exception());
211 // BOOST_CHECK_NO_THROW(m.value());
212 // BOOST_CHECK_NO_THROW(m.error());
213 }
214 if(false) // NOLINT
215 { // void, void is permitted, but is not constructible
216 result<void, void> *m = nullptr;
217 m->value();
218 m->error();
219 }
220#endif
221
222 {
223 // Deliberately define non-trivial operations
224 struct udt
225 {
226 int _v{0};
227 udt() = default;
228 udt(udt &&o) noexcept : _v(o._v) {}
229 udt(const udt &o) // NOLINT
230 : _v(o._v)
231 {
232 }
233 udt &operator=(udt &&o) noexcept
234 {
235 _v = o._v;
236 return *this;
237 }
238 udt &operator=(const udt &o) // NOLINT
239 {
240 _v = o._v;
241 return *this;
242 }
243 ~udt() { _v = 0; }
244 };
245 // No default construction, no copy nor move
246 struct udt2
247 {
248 udt2() = delete;
249 udt2(udt2 &&) = delete;
250 udt2(const udt2 &) = delete;
251 udt2 &operator=(udt2 &&) = delete;
252 udt2 &operator=(const udt2 &) = delete;
253 explicit udt2(int /*unused*/) {}
254 ~udt2() = default;
255 };
256 // Can only be constructed via multiple args
257 struct udt3
258 {
259 udt3() = delete;
260 udt3(udt3 &&) = delete;
261 udt3(const udt3 &) = delete;
262 udt3 &operator=(udt3 &&) = delete;
263 udt3 &operator=(const udt3 &) = delete;
264 explicit udt3(int /*unused*/, const char * /*unused*/, std::nullptr_t /*unused*/) {}
265 ~udt3() = default;
266 };
267 // Trivial with custom operator&
268 struct udt4
269 {
270 int _v{0};
271 udt4() = default;
272 udt4(udt4 &&) = default;
273 udt4(const udt4 &) = default;
274 udt4 &operator=(udt4 &&) = default;
275 udt4 &operator=(const udt4 &) = default;
276 void operator&() {}
277 ~udt4() = default;
278 };
279 // Non-trivial with custom operator&
280 struct udt5
281 {
282 int _v{0};
283 udt5() = default;
284 udt5(int v) : _v(v) {}
285 udt5(udt5 &&o) noexcept : _v(o._v) {}
286 udt5(const udt5 &o) // NOLINT
287 : _v(o._v)
288 {
289 }
290 udt5 &operator=(udt5 &&o) noexcept
291 {
292 _v = o._v;
293 return *this;
294 }
295 udt5 &operator=(const udt5 &o) // NOLINT
296 {
297 _v = o._v;
298 return *this;
299 }
300 void operator&() {}
301 ~udt5() { _v = 0; }
302 };
303
304
305 result<int> a(5);
306 result<int> b(make_error_code(e: boost::system::errc::invalid_argument));
307 std::cout << sizeof(a) << std::endl; // 32 bytes
308 if(false) // NOLINT
309 {
310 b.assume_value();
311 a.assume_error();
312 }
313#ifndef BOOST_NO_EXCEPTIONS
314 try
315 {
316 b.value();
317 std::cerr << "fail" << std::endl;
318 std::terminate();
319 }
320 catch(const boost::system::system_error & /*unused*/)
321 {
322 }
323#endif
324 static_assert(!std::is_default_constructible<decltype(a)>::value, "");
325 static_assert(!std::is_nothrow_default_constructible<decltype(a)>::value, "");
326 static_assert(std::is_copy_constructible<decltype(a)>::value, "");
327// Quality of implementation of std::optional is poor :(
328#ifndef TESTING_WG21_EXPERIMENTAL_RESULT
329 static_assert(std::is_trivially_copy_constructible<decltype(a)>::value, "");
330 static_assert(std::is_nothrow_copy_constructible<decltype(a)>::value, "");
331 static_assert(std::is_copy_assignable<decltype(a)>::value, "");
332 static_assert(std::is_trivially_copy_assignable<decltype(a)>::value, "");
333 static_assert(std::is_nothrow_copy_assignable<decltype(a)>::value, "");
334#endif
335 static_assert(std::is_trivially_destructible<decltype(a)>::value, "");
336 static_assert(std::is_nothrow_destructible<decltype(a)>::value, "");
337
338 // Test void compiles
339 result<void> c(in_place_type<void>);
340 result<void> c2(c);
341 (void) c2;
342
343 // Test a standard udt compiles
344 result<udt> d(in_place_type<udt>);
345 result<udt> d2(d);
346 static_assert(!std::is_default_constructible<decltype(d)>::value, "");
347 static_assert(!std::is_nothrow_default_constructible<decltype(d)>::value, "");
348 static_assert(std::is_copy_constructible<decltype(d)>::value, "");
349 static_assert(!std::is_trivially_copy_constructible<decltype(d)>::value, "");
350 static_assert(!std::is_nothrow_copy_constructible<decltype(d)>::value, "");
351 static_assert(std::is_copy_assignable<decltype(d)>::value, "");
352 static_assert(!std::is_trivially_copy_assignable<decltype(d)>::value, "");
353 static_assert(!std::is_nothrow_copy_assignable<decltype(d)>::value, "");
354 static_assert(std::is_move_assignable<decltype(d)>::value, "");
355 static_assert(!std::is_trivially_move_assignable<decltype(d)>::value, "");
356 static_assert(std::is_nothrow_move_assignable<decltype(d)>::value, "");
357 static_assert(!std::is_trivially_destructible<decltype(d)>::value, "");
358 static_assert(std::is_nothrow_destructible<decltype(d)>::value, "");
359
360 // Test a highly pathological udt compiles
361 result<udt2> e(in_place_type<udt2>, 5);
362 // result<udt2> e2(e);
363 static_assert(!std::is_default_constructible<decltype(e)>::value, "");
364 static_assert(!std::is_nothrow_default_constructible<decltype(e)>::value, "");
365 static_assert(!std::is_copy_constructible<decltype(e)>::value, "");
366 static_assert(!std::is_trivially_copy_constructible<decltype(e)>::value, "");
367 static_assert(!std::is_nothrow_copy_constructible<decltype(e)>::value, "");
368 static_assert(!std::is_copy_assignable<decltype(e)>::value, "");
369 static_assert(!std::is_trivially_copy_assignable<decltype(e)>::value, "");
370 static_assert(!std::is_nothrow_copy_assignable<decltype(e)>::value, "");
371 static_assert(!std::is_move_assignable<decltype(e)>::value, "");
372 static_assert(!std::is_trivially_move_assignable<decltype(e)>::value, "");
373 static_assert(!std::is_nothrow_move_assignable<decltype(e)>::value, "");
374
375 // Test a udt which can only be constructed in place compiles
376 result<udt3> g(in_place_type<udt3>, 5, static_cast<const char *>("niall"), nullptr);
377 // Does converting inplace construction also work?
378 result<udt3> h(5, static_cast<const char *>("niall"), nullptr);
379 result<udt3> i(ENOMEM, boost::system::generic_category());
380 BOOST_CHECK(h.has_value());
381 BOOST_CHECK(i.has_error());
382
383 // Test udt with custom operator&
384 udt4 j0;
385 result<udt4> j1(in_place_type<udt4>);
386 result<udt4> j2(j0);
387 result<udt4> j3(j1);
388 result<udt4> j4(std::move(j0));
389 result<udt4> j5(std::move(j1));
390 (void) j3;
391 (void) j5;
392 j2 = j0;
393 j2 = j1;
394 j2 = std::move(j0);
395 j2 = std::move(j1);
396 j1.swap(o&: j2);
397
398 udt5 k0;
399 result<udt5> k1(in_place_type<udt5>);
400 result<udt5> k2(k0);
401 result<udt5> k3(k1);
402 result<udt5> k4(std::move(k0));
403 result<udt5> k5(std::move(k1));
404 (void) k3;
405 (void) k5;
406 k2 = k0;
407 k2 = k1;
408 k2 = std::move(k0);
409 k2 = std::move(k1);
410 k1.swap(o&: k2);
411
412 result<void> k6(in_place_type<void>);
413 result<udt5> k7(k6);
414 result<udt5> k8(std::move(k6));
415
416 result<int, void> k9(in_place_type<int>);
417 result<udt5, int> k10(k9);
418 result<udt5, int> k11(std::move(k9));
419 }
420
421 // Test direct use of error code enum works
422 {
423 constexpr result<int, boost::system::errc::errc_t> a(5), b(boost::system::errc::invalid_argument);
424 static_assert(a.value() == 5, "a is not 5");
425 static_assert(b.error() == boost::system::errc::invalid_argument, "b is not errored");
426 BOOST_CHECK_THROW(b.value(), boost::system::system_error);
427 }
428
429#ifndef TESTING_WG21_EXPERIMENTAL_RESULT
430#ifndef BOOST_NO_EXCEPTIONS
431 // Test payload facility
432 {
433 const char *niall = "niall";
434 result<int, payload> b{boost::system::errc::invalid_argument, niall};
435 try
436 {
437 b.value();
438 BOOST_CHECK(false);
439 }
440 catch(const payload_exception &e)
441 {
442 BOOST_CHECK(!strcmp(e.what(), niall));
443 }
444 catch(...)
445 {
446 BOOST_CHECK(false);
447 }
448 }
449#endif
450#endif
451}
452

source code of boost/libs/outcome/test/tests/core-result.cpp