1 /* Unit testing for outcomes
2 (C) 2013-2020 Niall Douglas <http://www.nedproductions.biz/> (30 commits)
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;
payloadpayload48 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 {
payload_exceptionpayload_exception56 explicit payload_exception(const char *what)
57 : std::runtime_error(what)
58 {
59 }
60 };
make_error_code(const payload & p)61 inline const boost::system::error_code &make_error_code(const payload &p)
62 {
63 return p.ec;
64 }
outcome_throw_as_system_error_with_payload(const payload & p)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 #if defined(__clang__) || !defined(__GNUC__) || __GNUC__ >= 9 // GCCs before 9 barf on this
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");
85 #endif
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");
89 #if defined(__clang__) || !defined(__GNUC__) || __GNUC__ >= 9 // GCCs before 9 barf on this
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");
92 #endif
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");
98 #if defined(__clang__) || !defined(__GNUC__) || __GNUC__ >= 9 // GCCs before 9 barf on this
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");
100 #endif
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
__anonda03bb350102() 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;
udtudt226 udt(udt &&o) noexcept : _v(o._v) {}
udtudt227 udt(const udt &o) // NOLINT
228 : _v(o._v)
229 {
230 }
operator =udt231 udt &operator=(udt &&o) noexcept
232 {
233 _v = o._v;
234 return *this;
235 }
operator =udt236 udt &operator=(const udt &o) // NOLINT
237 {
238 _v = o._v;
239 return *this;
240 }
~udtudt241 ~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;
udt2udt2251 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;
udt3udt3262 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 }
377