]>
Commit | Line | Data |
---|---|---|
92f5a8d4 | 1 | /* Unit testing for outcomes |
1e59de90 | 2 | (C) 2013-2022 Niall Douglas <http://www.nedproductions.biz/> (30 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 | #ifdef TESTING_WG21_EXPERIMENTAL_RESULT | |
31 | #include <boost/outcome/experimental/result.hpp> | |
32 | #define BOOST_OUTCOME_AUTO_TEST_CASE(...) BOOST_AUTO_TEST_CASE(__VA_ARGS__) | |
33 | #else | |
34 | #include <boost/outcome/result.hpp> | |
35 | #endif | |
36 | #include <boost/test/unit_test.hpp> | |
37 | #include <boost/test/unit_test_monitor.hpp> | |
38 | ||
39 | #include <iostream> | |
40 | ||
41 | #ifndef BOOST_NO_EXCEPTIONS | |
42 | // Custom error type with payload | |
43 | struct payload | |
44 | { | |
45 | boost::system::error_code ec; | |
46 | const char *str{nullptr}; | |
47 | payload() = default; | |
48 | payload(boost::system::errc::errc_t _ec, const char *_str) | |
49 | : ec(make_error_code(_ec)) | |
50 | , str(_str) | |
51 | { | |
52 | } | |
53 | }; | |
54 | struct payload_exception : std::runtime_error | |
55 | { | |
56 | explicit payload_exception(const char *what) | |
57 | : std::runtime_error(what) | |
58 | { | |
59 | } | |
60 | }; | |
61 | inline const boost::system::error_code &make_error_code(const payload &p) | |
62 | { | |
63 | return p.ec; | |
64 | } | |
65 | inline void outcome_throw_as_system_error_with_payload(const payload &p) | |
66 | { | |
67 | throw payload_exception(p.str); | |
68 | } | |
69 | #endif | |
70 | ||
71 | BOOST_OUTCOME_AUTO_TEST_CASE(works_result, "Tests that the result works as intended") | |
72 | { | |
73 | #ifdef TESTING_WG21_EXPERIMENTAL_RESULT | |
74 | using namespace std::experimental; | |
75 | using std::in_place_type; | |
76 | #else | |
77 | using namespace BOOST_OUTCOME_V2_NAMESPACE; | |
78 | #endif | |
79 | ||
80 | static_assert(std::is_constructible<result<long>, int>::value, "Sanity check that monad can be constructed from a value_type"); | |
81 | 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"); | |
20effc67 | 82 | #if defined(__clang__) || !defined(__GNUC__) || __GNUC__ >= 9 // GCCs before 9 barf on this |
92f5a8d4 TL |
83 | 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"); |
84 | 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"); | |
20effc67 | 85 | #endif |
92f5a8d4 TL |
86 | |
87 | static_assert(std::is_constructible<result<int>, result<long>>::value, "Sanity check that compatible monads can be constructed from one another"); | |
88 | static_assert(std::is_constructible<result<result<int>>, result<long>>::value, "Sanity check that outer monad can be constructed from a compatible monad"); | |
20effc67 | 89 | #if defined(__clang__) || !defined(__GNUC__) || __GNUC__ >= 9 // GCCs before 9 barf on this |
92f5a8d4 TL |
90 | 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"); |
91 | 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"); | |
20effc67 | 92 | #endif |
92f5a8d4 TL |
93 | static_assert(!std::is_constructible<result<std::string>, result<int>>::value, "Sanity check that incompatible monads cannot be constructed from one another"); |
94 | ||
95 | #ifndef TESTING_WG21_EXPERIMENTAL_RESULT | |
96 | static_assert(std::is_constructible<result<int>, result<void>>::value, "Sanity check that all monads can be constructed from a void monad"); | |
97 | static_assert(std::is_constructible<result<result<int>>, result<void>>::value, "Sanity check that outer monad can be constructed from a compatible monad"); | |
20effc67 | 98 | #if defined(__clang__) || !defined(__GNUC__) || __GNUC__ >= 9 // GCCs before 9 barf on this |
92f5a8d4 | 99 | 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"); |
20effc67 | 100 | #endif |
92f5a8d4 TL |
101 | static_assert(!std::is_constructible<result<void>, result<int>>::value, "Sanity check that incompatible monads cannot be constructed from one another"); |
102 | #endif | |
103 | static_assert(std::is_void<result<void>::value_type>::value, "Sanity check that result<void> has a void value_type"); | |
104 | #ifndef TESTING_WG21_EXPERIMENTAL_RESULT | |
105 | static_assert(std::is_void<result<void, void>::error_type>::value, "Sanity check that result<void, void> has a void error_type"); | |
106 | #endif | |
107 | ||
108 | static_assert(std::is_same<result<int>::value_type, int>::value, "Sanity check that result<int> has a int value_type"); | |
109 | 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"); | |
110 | ||
111 | ||
112 | { // errored int | |
113 | result<int> m(boost::system::errc::bad_address); | |
114 | BOOST_CHECK(!m); | |
115 | BOOST_CHECK(!m.has_value()); | |
116 | BOOST_CHECK(m.has_error()); | |
117 | // BOOST_CHECK(!m.has_exception()); | |
118 | BOOST_CHECK_THROW(m.value(), boost::system::system_error); | |
119 | BOOST_CHECK_NO_THROW(m.error()); | |
120 | } | |
121 | { // errored void | |
122 | result<void> m(boost::system::errc::bad_address); | |
123 | BOOST_CHECK(!m); | |
124 | BOOST_CHECK(!m.has_value()); | |
125 | BOOST_CHECK(m.has_error()); | |
126 | // BOOST_CHECK(!m.has_exception()); | |
127 | #ifndef TESTING_WG21_EXPERIMENTAL_RESULT | |
128 | BOOST_CHECK_THROW(([&m]() -> void { return m.value(); }()), boost::system::system_error); | |
129 | #endif | |
130 | BOOST_CHECK_NO_THROW(m.error()); | |
131 | } | |
132 | { // valued int | |
133 | result<int> m(5); | |
134 | BOOST_CHECK(m); | |
135 | BOOST_CHECK(m.has_value()); | |
136 | BOOST_CHECK(!m.has_error()); | |
137 | // BOOST_CHECK(!m.has_exception()); | |
138 | BOOST_CHECK(m.value() == 5); | |
139 | m.value() = 6; | |
140 | BOOST_CHECK(m.value() == 6); | |
141 | BOOST_CHECK_THROW(m.error(), bad_result_access); | |
142 | } | |
143 | { // valued bool | |
144 | result<bool> m(false); | |
145 | BOOST_CHECK(m); | |
146 | BOOST_CHECK(m.has_value()); | |
147 | BOOST_CHECK(!m.has_error()); | |
148 | // BOOST_CHECK(!m.has_exception()); | |
149 | BOOST_CHECK(m.value() == false); | |
150 | m.value() = true; | |
151 | BOOST_CHECK(m.value() == true); | |
152 | BOOST_CHECK_THROW(m.error(), bad_result_access); | |
153 | } | |
154 | { // moves do not clear state | |
155 | result<std::string> m("niall"); | |
156 | BOOST_CHECK(m); | |
157 | BOOST_CHECK(m.has_value()); | |
158 | BOOST_CHECK(!m.has_error()); | |
159 | // BOOST_CHECK(!m.has_exception()); | |
160 | BOOST_CHECK(m.value() == "niall"); | |
161 | m.value() = "NIALL"; | |
162 | BOOST_CHECK(m.value() == "NIALL"); | |
163 | auto temp(std::move(m).value()); | |
164 | BOOST_CHECK(temp == "NIALL"); | |
165 | BOOST_CHECK(m.value().empty()); // NOLINT | |
166 | } | |
167 | { // valued void | |
168 | result<void> m(in_place_type<void>); | |
169 | BOOST_CHECK(m); | |
170 | BOOST_CHECK(m.has_value()); | |
171 | BOOST_CHECK(!m.has_error()); | |
172 | // BOOST_CHECK(!m.has_exception()); | |
173 | BOOST_CHECK_NO_THROW(m.value()); // works, but type returned is unusable | |
174 | BOOST_CHECK_THROW(m.error(), bad_result_access); | |
175 | } | |
176 | { // errored | |
177 | boost::system::error_code ec(5, boost::system::system_category()); | |
178 | result<int> m(ec); | |
179 | BOOST_CHECK(!m); | |
180 | BOOST_CHECK(!m.has_value()); | |
181 | BOOST_CHECK(m.has_error()); | |
182 | // BOOST_CHECK(!m.has_exception()); | |
183 | BOOST_CHECK_THROW(m.value(), boost::system::system_error); | |
184 | BOOST_CHECK(m.error() == ec); | |
185 | } | |
186 | #if !defined(__APPLE__) || defined(__cpp_exceptions) | |
187 | { // errored, custom | |
188 | boost::system::error_code ec(5, boost::system::system_category()); | |
189 | auto e = boost::copy_exception(boost::system::system_error(ec)); // NOLINT | |
190 | result<int, boost::exception_ptr> m(e); | |
191 | BOOST_CHECK(!m); | |
192 | BOOST_CHECK(!m.has_value()); | |
193 | BOOST_CHECK(m.has_error()); | |
194 | // BOOST_CHECK(!m.has_exception()); | |
195 | BOOST_CHECK_THROW(m.value(), boost::system::system_error); | |
196 | BOOST_CHECK(m.error() == e); | |
197 | } | |
198 | #endif | |
199 | #ifndef TESTING_WG21_EXPERIMENTAL_RESULT | |
200 | { // custom error type | |
201 | struct Foo | |
202 | { | |
203 | }; | |
204 | result<int, Foo> m(in_place_type<Foo>); | |
205 | BOOST_CHECK(!m); | |
206 | BOOST_CHECK(!m.has_value()); | |
207 | BOOST_CHECK(m.has_error()); | |
208 | // BOOST_CHECK(!m.has_exception()); | |
209 | // BOOST_CHECK_NO_THROW(m.value()); | |
210 | // BOOST_CHECK_NO_THROW(m.error()); | |
211 | } | |
212 | if(false) // NOLINT | |
213 | { // void, void is permitted, but is not constructible | |
214 | result<void, void> *m = nullptr; | |
215 | m->value(); | |
216 | m->error(); | |
217 | } | |
218 | #endif | |
219 | ||
220 | { | |
221 | // Deliberately define non-trivial operations | |
222 | struct udt | |
223 | { | |
224 | int _v{0}; | |
225 | udt() = default; | |
226 | udt(udt &&o) noexcept : _v(o._v) {} | |
227 | udt(const udt &o) // NOLINT | |
228 | : _v(o._v) | |
229 | { | |
230 | } | |
231 | udt &operator=(udt &&o) noexcept | |
232 | { | |
233 | _v = o._v; | |
234 | return *this; | |
235 | } | |
236 | udt &operator=(const udt &o) // NOLINT | |
237 | { | |
238 | _v = o._v; | |
239 | return *this; | |
240 | } | |
241 | ~udt() { _v = 0; } | |
242 | }; | |
243 | // No default construction, no copy nor move | |
244 | struct udt2 | |
245 | { | |
246 | udt2() = delete; | |
247 | udt2(udt2 &&) = delete; | |
248 | udt2(const udt2 &) = delete; | |
249 | udt2 &operator=(udt2 &&) = delete; | |
250 | udt2 &operator=(const udt2 &) = delete; | |
251 | explicit udt2(int /*unused*/) {} | |
252 | ~udt2() = default; | |
253 | }; | |
254 | // Can only be constructed via multiple args | |
255 | struct udt3 | |
256 | { | |
257 | udt3() = delete; | |
258 | udt3(udt3 &&) = delete; | |
259 | udt3(const udt3 &) = delete; | |
260 | udt3 &operator=(udt3 &&) = delete; | |
261 | udt3 &operator=(const udt3 &) = delete; | |
262 | explicit udt3(int /*unused*/, const char * /*unused*/, std::nullptr_t /*unused*/) {} | |
263 | ~udt3() = default; | |
264 | }; | |
265 | ||
266 | ||
267 | result<int> a(5); | |
268 | result<int> b(make_error_code(boost::system::errc::invalid_argument)); | |
269 | std::cout << sizeof(a) << std::endl; // 32 bytes | |
270 | if(false) // NOLINT | |
271 | { | |
272 | b.assume_value(); | |
273 | a.assume_error(); | |
274 | } | |
275 | #ifndef BOOST_NO_EXCEPTIONS | |
276 | try | |
277 | { | |
278 | b.value(); | |
279 | std::cerr << "fail" << std::endl; | |
280 | std::terminate(); | |
281 | } | |
282 | catch(const boost::system::system_error & /*unused*/) | |
283 | { | |
284 | } | |
285 | #endif | |
286 | static_assert(!std::is_default_constructible<decltype(a)>::value, ""); | |
287 | static_assert(!std::is_nothrow_default_constructible<decltype(a)>::value, ""); | |
288 | static_assert(std::is_copy_constructible<decltype(a)>::value, ""); | |
289 | // Quality of implementation of std::optional is poor :( | |
290 | #ifndef TESTING_WG21_EXPERIMENTAL_RESULT | |
291 | static_assert(std::is_trivially_copy_constructible<decltype(a)>::value, ""); | |
292 | static_assert(std::is_nothrow_copy_constructible<decltype(a)>::value, ""); | |
293 | static_assert(std::is_copy_assignable<decltype(a)>::value, ""); | |
294 | static_assert(std::is_trivially_copy_assignable<decltype(a)>::value, ""); | |
295 | static_assert(std::is_nothrow_copy_assignable<decltype(a)>::value, ""); | |
296 | #endif | |
297 | static_assert(std::is_trivially_destructible<decltype(a)>::value, ""); | |
298 | static_assert(std::is_nothrow_destructible<decltype(a)>::value, ""); | |
299 | ||
300 | // Test void compiles | |
301 | result<void> c(in_place_type<void>); | |
302 | result<void> c2(c); | |
303 | (void) c2; | |
304 | ||
305 | // Test a standard udt compiles | |
306 | result<udt> d(in_place_type<udt>); | |
307 | result<udt> d2(d); | |
308 | static_assert(!std::is_default_constructible<decltype(d)>::value, ""); | |
309 | static_assert(!std::is_nothrow_default_constructible<decltype(d)>::value, ""); | |
310 | static_assert(std::is_copy_constructible<decltype(d)>::value, ""); | |
311 | static_assert(!std::is_trivially_copy_constructible<decltype(d)>::value, ""); | |
312 | static_assert(!std::is_nothrow_copy_constructible<decltype(d)>::value, ""); | |
313 | static_assert(std::is_copy_assignable<decltype(d)>::value, ""); | |
314 | static_assert(!std::is_trivially_copy_assignable<decltype(d)>::value, ""); | |
315 | static_assert(!std::is_nothrow_copy_assignable<decltype(d)>::value, ""); | |
316 | static_assert(std::is_move_assignable<decltype(d)>::value, ""); | |
317 | static_assert(!std::is_trivially_move_assignable<decltype(d)>::value, ""); | |
318 | static_assert(std::is_nothrow_move_assignable<decltype(d)>::value, ""); | |
319 | static_assert(!std::is_trivially_destructible<decltype(d)>::value, ""); | |
320 | static_assert(std::is_nothrow_destructible<decltype(d)>::value, ""); | |
321 | ||
322 | // Test a highly pathological udt compiles | |
323 | result<udt2> e(in_place_type<udt2>, 5); | |
324 | // result<udt2> e2(e); | |
325 | static_assert(!std::is_default_constructible<decltype(e)>::value, ""); | |
326 | static_assert(!std::is_nothrow_default_constructible<decltype(e)>::value, ""); | |
327 | static_assert(!std::is_copy_constructible<decltype(e)>::value, ""); | |
328 | static_assert(!std::is_trivially_copy_constructible<decltype(e)>::value, ""); | |
329 | static_assert(!std::is_nothrow_copy_constructible<decltype(e)>::value, ""); | |
330 | static_assert(!std::is_copy_assignable<decltype(e)>::value, ""); | |
331 | static_assert(!std::is_trivially_copy_assignable<decltype(e)>::value, ""); | |
332 | static_assert(!std::is_nothrow_copy_assignable<decltype(e)>::value, ""); | |
333 | static_assert(!std::is_move_assignable<decltype(e)>::value, ""); | |
334 | static_assert(!std::is_trivially_move_assignable<decltype(e)>::value, ""); | |
335 | static_assert(!std::is_nothrow_move_assignable<decltype(e)>::value, ""); | |
336 | ||
337 | // Test a udt which can only be constructed in place compiles | |
338 | result<udt3> g(in_place_type<udt3>, 5, static_cast<const char *>("niall"), nullptr); | |
339 | // Does converting inplace construction also work? | |
340 | result<udt3> h(5, static_cast<const char *>("niall"), nullptr); | |
341 | result<udt3> i(ENOMEM, boost::system::generic_category()); | |
342 | BOOST_CHECK(h.has_value()); | |
343 | BOOST_CHECK(i.has_error()); | |
344 | } | |
345 | ||
346 | // Test direct use of error code enum works | |
347 | { | |
348 | constexpr result<int, boost::system::errc::errc_t> a(5), b(boost::system::errc::invalid_argument); | |
349 | static_assert(a.value() == 5, "a is not 5"); | |
350 | static_assert(b.error() == boost::system::errc::invalid_argument, "b is not errored"); | |
351 | BOOST_CHECK_THROW(b.value(), boost::system::system_error); | |
352 | } | |
353 | ||
354 | #ifndef TESTING_WG21_EXPERIMENTAL_RESULT | |
355 | #ifndef BOOST_NO_EXCEPTIONS | |
356 | // Test payload facility | |
357 | { | |
358 | const char *niall = "niall"; | |
359 | result<int, payload> b{boost::system::errc::invalid_argument, niall}; | |
360 | try | |
361 | { | |
362 | b.value(); | |
363 | BOOST_CHECK(false); | |
364 | } | |
365 | catch(const payload_exception &e) | |
366 | { | |
367 | BOOST_CHECK(!strcmp(e.what(), niall)); | |
368 | } | |
369 | catch(...) | |
370 | { | |
371 | BOOST_CHECK(false); | |
372 | } | |
373 | } | |
374 | #endif | |
375 | #endif | |
376 | } |