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