Line data Source code
1 : //
2 : // Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com)
3 : //
4 : // Distributed under the Boost Software License, Version 1.0. (See accompanying
5 : // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6 : //
7 : // Official repository: https://github.com/cppalliance/capy
8 : //
9 :
10 : #ifndef BOOST_CAPY_TEST_FUSE_HPP
11 : #define BOOST_CAPY_TEST_FUSE_HPP
12 :
13 : #include <boost/capy/detail/config.hpp>
14 : #include <boost/capy/concept/io_launchable_task.hpp>
15 : #include <boost/capy/error.hpp>
16 : #include <boost/capy/test/run_blocking.hpp>
17 : #include <system_error>
18 : #include <cstddef>
19 : #include <exception>
20 : #include <limits>
21 : #include <memory>
22 : #include <source_location>
23 : #include <type_traits>
24 :
25 : /*
26 : LLM/AI Instructions for fuse-based test patterns:
27 :
28 : When f.armed() runs a test, it injects errors at successive points
29 : via maybe_fail(). Operations like read_stream::read_some() and
30 : write_stream::write_some() call maybe_fail() internally.
31 :
32 : CORRECT pattern - early return on injected error:
33 :
34 : auto [ec, n] = co_await rs.read_some(buf);
35 : if(ec)
36 : co_return; // fuse injected error, exit gracefully
37 : // ... continue with success path
38 :
39 : WRONG pattern - asserting success unconditionally:
40 :
41 : auto [ec, n] = co_await rs.read_some(buf);
42 : BOOST_TEST(! ec); // FAILS when fuse injects error!
43 :
44 : The fuse mechanism tests error handling by failing at each point
45 : in sequence. Tests must handle injected errors by returning early,
46 : not by asserting that operations always succeed.
47 : */
48 :
49 : namespace boost {
50 : namespace capy {
51 : namespace test {
52 :
53 : /** A test utility for systematic error injection.
54 :
55 : This class enables exhaustive testing of error handling
56 : paths by injecting failures at successive points in code.
57 : Each iteration fails at a later point until the code path
58 : completes without encountering a failure. The @ref armed
59 : method runs in two phases: first with error codes, then
60 : with exceptions. The @ref inert method runs once without
61 : automatic failure injection.
62 :
63 : @par Thread Safety
64 :
65 : @b Not @b thread @b safe. Instances must not be accessed
66 : from different logical threads of operation concurrently.
67 : This includes coroutines - accessing the same fuse from
68 : multiple concurrent coroutines causes non-deterministic
69 : test behavior.
70 :
71 : @par Basic Inline Usage
72 :
73 : @code
74 : fuse()([](fuse& f) {
75 : auto ec = f.maybe_fail();
76 : if(ec)
77 : return;
78 :
79 : ec = f.maybe_fail();
80 : if(ec)
81 : return;
82 : });
83 : @endcode
84 :
85 : @par Named Fuse with armed()
86 :
87 : @code
88 : fuse f;
89 : MyObject obj(f);
90 : auto r = f.armed([&](fuse&) {
91 : obj.do_something();
92 : });
93 : @endcode
94 :
95 : @par Using inert() for Single-Run Tests
96 :
97 : @code
98 : fuse f;
99 : auto r = f.inert([](fuse& f) {
100 : auto ec = f.maybe_fail(); // Always succeeds
101 : if(some_condition)
102 : f.fail(); // Only way to signal failure
103 : });
104 : @endcode
105 :
106 : @par Dependency Injection (Standalone Usage)
107 :
108 : A default-constructed fuse is a no-op when used outside
109 : of @ref armed or @ref inert. This enables passing a fuse
110 : to classes for dependency injection without affecting
111 : normal operation.
112 :
113 : @code
114 : class MyService
115 : {
116 : fuse& f_;
117 : public:
118 : explicit MyService(fuse& f) : f_(f) {}
119 :
120 : std::error_code do_work()
121 : {
122 : auto ec = f_.maybe_fail(); // No-op outside armed/inert
123 : if(ec)
124 : return ec;
125 : // ... actual work ...
126 : return {};
127 : }
128 : };
129 :
130 : // Production usage - fuse is no-op
131 : fuse f;
132 : MyService svc(f);
133 : svc.do_work(); // maybe_fail() returns {} always
134 :
135 : // Test usage - failures are injected
136 : auto r = f.armed([&](fuse&) {
137 : svc.do_work(); // maybe_fail() triggers failures
138 : });
139 : @endcode
140 :
141 : @par Custom Error Code
142 :
143 : @code
144 : auto custom_ec = make_error_code(
145 : std::errc::operation_canceled);
146 : fuse f(custom_ec);
147 : auto r = f.armed([](fuse& f) {
148 : auto ec = f.maybe_fail();
149 : if(ec)
150 : return;
151 : });
152 : @endcode
153 :
154 : @par Checking the Result
155 :
156 : @code
157 : fuse f;
158 : auto r = f([](fuse& f) {
159 : auto ec = f.maybe_fail();
160 : if(ec)
161 : return;
162 : });
163 :
164 : if(!r)
165 : {
166 : std::cerr << "Failure at "
167 : << r.loc.file_name() << ":"
168 : << r.loc.line() << "\n";
169 : }
170 : @endcode
171 :
172 : @par Test Framework Integration
173 :
174 : @code
175 : fuse f;
176 : auto r = f([](fuse& f) {
177 : auto ec = f.maybe_fail();
178 : if(ec)
179 : return;
180 : });
181 :
182 : // Boost.Test
183 : BOOST_TEST(r.success);
184 : if(!r)
185 : BOOST_TEST_MESSAGE("Failed at " << r.loc.file_name()
186 : << ":" << r.loc.line());
187 :
188 : // Catch2
189 : REQUIRE(r.success);
190 : if(!r)
191 : INFO("Failed at " << r.loc.file_name()
192 : << ":" << r.loc.line());
193 : @endcode
194 : */
195 : class fuse
196 : {
197 : struct state
198 : {
199 : std::size_t n = (std::numeric_limits<std::size_t>::max)();
200 : std::size_t i = 0;
201 : bool triggered = false;
202 : bool throws = false;
203 : bool stopped = false;
204 : bool inert = true;
205 : std::error_code ec;
206 : std::source_location loc;
207 : std::exception_ptr ep;
208 : };
209 :
210 : std::shared_ptr<state> p_;
211 :
212 : /** Return true if testing should continue.
213 :
214 : On the first call, initializes the failure point to 0.
215 : After a triggered failure, increments the failure point
216 : and resets for the next iteration. Returns false when
217 : the test completes without triggering a failure.
218 : */
219 1921 : explicit operator bool() const noexcept
220 : {
221 1921 : auto& s = *p_;
222 1921 : if(s.n == (std::numeric_limits<std::size_t>::max)())
223 : {
224 : // First call: start round 0
225 336 : s.n = 0;
226 336 : return true;
227 : }
228 1585 : if(s.triggered)
229 : {
230 : // Previous round triggered, try next failure point
231 1255 : s.n++;
232 1255 : s.i = 0;
233 1255 : s.triggered = false;
234 1255 : return true;
235 : }
236 : // Test completed without trigger: success
237 330 : return false;
238 : }
239 :
240 : public:
241 : /** Result of a fuse operation.
242 :
243 : Contains the outcome of @ref armed or @ref inert
244 : and, on failure, the source location of the failing
245 : point. Converts to `bool` for convenient success
246 : checking.
247 :
248 : @par Example
249 :
250 : @code
251 : fuse f;
252 : auto r = f([](fuse& f) {
253 : auto ec = f.maybe_fail();
254 : if(ec)
255 : return;
256 : });
257 :
258 : if(!r)
259 : {
260 : std::cerr << "Failure at "
261 : << r.loc.file_name() << ":"
262 : << r.loc.line() << "\n";
263 : }
264 : @endcode
265 : */
266 : struct result
267 : {
268 : std::source_location loc = {};
269 : std::exception_ptr ep = nullptr;
270 : bool success = true;
271 :
272 56 : constexpr explicit operator bool() const noexcept
273 : {
274 56 : return success;
275 : }
276 : };
277 :
278 : /** Construct a fuse with a custom error code.
279 :
280 : @par Example
281 :
282 : @code
283 : auto custom_ec = make_error_code(
284 : std::errc::operation_canceled);
285 : fuse f(custom_ec);
286 :
287 : std::error_code captured_ec;
288 : auto r = f([&](fuse& f) {
289 : auto ec = f.maybe_fail();
290 : if(ec)
291 : {
292 : captured_ec = ec;
293 : return;
294 : }
295 : });
296 :
297 : assert(captured_ec == custom_ec);
298 : @endcode
299 :
300 : @param ec The error code to deliver at failure points.
301 : */
302 208 : explicit fuse(std::error_code ec)
303 208 : : p_(std::make_shared<state>())
304 : {
305 208 : p_->ec = ec;
306 208 : }
307 :
308 : /** Construct a fuse with the default error code.
309 :
310 : The default error code is `error::test_failure`.
311 :
312 : @par Example
313 :
314 : @code
315 : fuse f;
316 : std::error_code captured_ec;
317 :
318 : auto r = f([&](fuse& f) {
319 : auto ec = f.maybe_fail();
320 : if(ec)
321 : {
322 : captured_ec = ec;
323 : return;
324 : }
325 : });
326 :
327 : assert(captured_ec == error::test_failure);
328 : @endcode
329 : */
330 206 : fuse()
331 206 : : fuse(error::test_failure)
332 : {
333 206 : }
334 :
335 : /** Return an error or throw at the current failure point.
336 :
337 : When running under @ref armed, increments the internal
338 : counter. When the counter reaches the current failure
339 : point, returns the stored error code (or throws
340 : `std::system_error` in exception mode) and records
341 : the source location.
342 :
343 : When called outside of @ref armed or @ref inert (standalone
344 : usage), or when running under @ref inert, always returns
345 : an empty error code. This enables dependency injection
346 : where the fuse is a no-op in production code.
347 :
348 : @par Example
349 :
350 : @code
351 : fuse f;
352 : auto r = f([](fuse& f) {
353 : // Error code mode: returns the error
354 : auto ec = f.maybe_fail();
355 : if(ec)
356 : return;
357 :
358 : // Exception mode: throws system_error
359 : ec = f.maybe_fail();
360 : if(ec)
361 : return;
362 : });
363 : @endcode
364 :
365 : @par Standalone Usage
366 :
367 : @code
368 : fuse f;
369 : auto ec = f.maybe_fail(); // Always returns {} (no-op)
370 : @endcode
371 :
372 : @param loc The source location of the call site,
373 : captured automatically.
374 :
375 : @return The stored error code if at the failure point,
376 : otherwise an empty error code. In exception mode,
377 : throws instead of returning an error. When called
378 : outside @ref armed, or when running under @ref inert,
379 : always returns an empty error code.
380 :
381 : @throws std::system_error When in exception mode
382 : and at the failure point (not thrown outside @ref armed).
383 : */
384 : std::error_code
385 4800 : maybe_fail(
386 : std::source_location loc = std::source_location::current())
387 : {
388 4800 : auto& s = *p_;
389 4800 : if(s.inert)
390 224 : return {};
391 4576 : if(s.i < s.n)
392 4256 : ++s.i;
393 4576 : if(s.i == s.n)
394 : {
395 1255 : s.triggered = true;
396 1255 : s.loc = loc;
397 1255 : if(s.throws)
398 622 : throw std::system_error(s.ec);
399 633 : return s.ec;
400 : }
401 3321 : return {};
402 : }
403 :
404 : /** Signal a test failure and stop execution.
405 :
406 : Call this from the test function to indicate a failure
407 : condition. Both @ref armed and @ref inert will return
408 : a failed @ref result immediately.
409 :
410 : @par Example
411 :
412 : @code
413 : fuse f;
414 : auto r = f([](fuse& f) {
415 : auto ec = f.maybe_fail();
416 : if(ec)
417 : return;
418 :
419 : // Explicit failure when a condition is not met
420 : if(some_value != expected)
421 : {
422 : f.fail();
423 : return;
424 : }
425 : });
426 :
427 : if(!r)
428 : {
429 : std::cerr << "Test failed at "
430 : << r.loc.file_name() << ":"
431 : << r.loc.line() << "\n";
432 : }
433 : @endcode
434 :
435 : @param loc The source location of the call site,
436 : captured automatically.
437 : */
438 : void
439 3 : fail(
440 : std::source_location loc =
441 : std::source_location::current()) noexcept
442 : {
443 3 : p_->loc = loc;
444 3 : p_->stopped = true;
445 3 : }
446 :
447 : /** Signal a test failure with an exception and stop execution.
448 :
449 : Call this from the test function to indicate a failure
450 : condition with an associated exception. Both @ref armed
451 : and @ref inert will return a failed @ref result with
452 : the captured exception pointer.
453 :
454 : @par Example
455 :
456 : @code
457 : fuse f;
458 : auto r = f([](fuse& f) {
459 : try
460 : {
461 : do_something();
462 : }
463 : catch(...)
464 : {
465 : f.fail(std::current_exception());
466 : return;
467 : }
468 : });
469 :
470 : if(!r)
471 : {
472 : try
473 : {
474 : if(r.ep)
475 : std::rethrow_exception(r.ep);
476 : }
477 : catch(std::exception const& e)
478 : {
479 : std::cerr << "Exception: " << e.what() << "\n";
480 : }
481 : }
482 : @endcode
483 :
484 : @param ep The exception pointer to capture.
485 :
486 : @param loc The source location of the call site,
487 : captured automatically.
488 : */
489 : void
490 2 : fail(
491 : std::exception_ptr ep,
492 : std::source_location loc =
493 : std::source_location::current()) noexcept
494 : {
495 2 : p_->ep = ep;
496 2 : p_->loc = loc;
497 2 : p_->stopped = true;
498 2 : }
499 :
500 : /** Run a test function with systematic failure injection.
501 :
502 : Repeatedly invokes the provided function, failing at
503 : successive points until the function completes without
504 : encountering a failure. First runs the complete loop
505 : using error codes, then runs using exceptions.
506 :
507 : @par Example
508 :
509 : @code
510 : fuse f;
511 : auto r = f.armed([](fuse& f) {
512 : auto ec = f.maybe_fail();
513 : if(ec)
514 : return;
515 :
516 : ec = f.maybe_fail();
517 : if(ec)
518 : return;
519 : });
520 :
521 : if(!r)
522 : {
523 : std::cerr << "Failure at "
524 : << r.loc.file_name() << ":"
525 : << r.loc.line() << "\n";
526 : }
527 : @endcode
528 :
529 : @param fn The test function to invoke. It receives
530 : a reference to the fuse and should call @ref maybe_fail
531 : at each potential failure point.
532 :
533 : @return A @ref result indicating success or failure.
534 : On failure, `result::loc` contains the source location
535 : of the last @ref maybe_fail or @ref fail call.
536 : */
537 : template<class F>
538 : result
539 19 : armed(F&& fn)
540 : {
541 19 : result r;
542 :
543 : // Phase 1: error code mode
544 19 : p_->throws = false;
545 19 : p_->inert = false;
546 19 : p_->n = (std::numeric_limits<std::size_t>::max)();
547 71 : while(*this)
548 : {
549 : try
550 : {
551 58 : fn(*this);
552 : }
553 6 : catch(...)
554 : {
555 3 : r.success = false;
556 3 : r.loc = p_->loc;
557 3 : r.ep = p_->ep;
558 3 : p_->inert = true;
559 3 : return r;
560 : }
561 55 : if(p_->stopped)
562 : {
563 3 : r.success = false;
564 3 : r.loc = p_->loc;
565 3 : r.ep = p_->ep;
566 3 : p_->inert = true;
567 3 : return r;
568 : }
569 : }
570 :
571 : // Phase 2: exception mode
572 13 : p_->throws = true;
573 13 : p_->n = (std::numeric_limits<std::size_t>::max)();
574 13 : p_->i = 0;
575 13 : p_->triggered = false;
576 54 : while(*this)
577 : {
578 : try
579 : {
580 41 : fn(*this);
581 : }
582 56 : catch(std::system_error const& ex)
583 : {
584 28 : if(ex.code() != p_->ec)
585 : {
586 0 : r.success = false;
587 0 : r.loc = p_->loc;
588 0 : r.ep = p_->ep;
589 0 : p_->inert = true;
590 0 : return r;
591 : }
592 : }
593 0 : catch(...)
594 : {
595 0 : r.success = false;
596 0 : r.loc = p_->loc;
597 0 : r.ep = p_->ep;
598 0 : p_->inert = true;
599 0 : return r;
600 : }
601 41 : if(p_->stopped)
602 : {
603 0 : r.success = false;
604 0 : r.loc = p_->loc;
605 0 : r.ep = p_->ep;
606 0 : p_->inert = true;
607 0 : return r;
608 : }
609 : }
610 13 : p_->inert = true;
611 13 : return r;
612 0 : }
613 :
614 : /** Run a coroutine test function with systematic failure injection.
615 :
616 : Repeatedly invokes the provided coroutine function, failing at
617 : successive points until the function completes without
618 : encountering a failure. First runs the complete loop
619 : using error codes, then runs using exceptions.
620 :
621 : This overload handles lambdas that return an @ref IoLaunchableTask
622 : (such as `task<void>`), executing them synchronously via
623 : @ref run_blocking.
624 :
625 : @par Example
626 :
627 : @code
628 : fuse f;
629 : auto r = f.armed([&](fuse&) -> task<void> {
630 : auto ec = f.maybe_fail();
631 : if(ec)
632 : co_return;
633 :
634 : ec = f.maybe_fail();
635 : if(ec)
636 : co_return;
637 : });
638 :
639 : if(!r)
640 : {
641 : std::cerr << "Failure at "
642 : << r.loc.file_name() << ":"
643 : << r.loc.line() << "\n";
644 : }
645 : @endcode
646 :
647 : @param fn The coroutine test function to invoke. It receives
648 : a reference to the fuse and should call @ref maybe_fail
649 : at each potential failure point.
650 :
651 : @return A @ref result indicating success or failure.
652 : On failure, `result::loc` contains the source location
653 : of the last @ref maybe_fail or @ref fail call.
654 : */
655 : template<class F>
656 : requires IoLaunchableTask<std::invoke_result_t<F, fuse&>>
657 : result
658 152 : armed(F&& fn)
659 : {
660 152 : result r;
661 :
662 : // Phase 1: error code mode
663 152 : p_->throws = false;
664 152 : p_->inert = false;
665 152 : p_->n = (std::numeric_limits<std::size_t>::max)();
666 898 : while(*this)
667 : {
668 : try
669 : {
670 746 : run_blocking()(fn(*this));
671 : }
672 0 : catch(...)
673 : {
674 0 : r.success = false;
675 0 : r.loc = p_->loc;
676 0 : r.ep = p_->ep;
677 0 : p_->inert = true;
678 0 : return r;
679 : }
680 746 : if(p_->stopped)
681 : {
682 0 : r.success = false;
683 0 : r.loc = p_->loc;
684 0 : r.ep = p_->ep;
685 0 : p_->inert = true;
686 0 : return r;
687 : }
688 : }
689 :
690 : // Phase 2: exception mode
691 152 : p_->throws = true;
692 152 : p_->n = (std::numeric_limits<std::size_t>::max)();
693 152 : p_->i = 0;
694 152 : p_->triggered = false;
695 898 : while(*this)
696 : {
697 : try
698 : {
699 1934 : run_blocking()(fn(*this));
700 : }
701 1188 : catch(std::system_error const& ex)
702 : {
703 594 : if(ex.code() != p_->ec)
704 : {
705 0 : r.success = false;
706 0 : r.loc = p_->loc;
707 0 : r.ep = p_->ep;
708 0 : p_->inert = true;
709 0 : return r;
710 : }
711 : }
712 0 : catch(...)
713 : {
714 0 : r.success = false;
715 0 : r.loc = p_->loc;
716 0 : r.ep = p_->ep;
717 0 : p_->inert = true;
718 0 : return r;
719 : }
720 746 : if(p_->stopped)
721 : {
722 0 : r.success = false;
723 0 : r.loc = p_->loc;
724 0 : r.ep = p_->ep;
725 0 : p_->inert = true;
726 0 : return r;
727 : }
728 : }
729 152 : p_->inert = true;
730 152 : return r;
731 0 : }
732 :
733 : /** Alias for @ref armed.
734 :
735 : Allows the fuse to be invoked directly as a function
736 : object for more concise syntax.
737 :
738 : @par Example
739 :
740 : @code
741 : // These are equivalent:
742 : fuse f;
743 : auto r1 = f.armed([](fuse& f) { ... });
744 : auto r2 = f([](fuse& f) { ... });
745 :
746 : // Inline usage:
747 : auto r3 = fuse()([](fuse& f) {
748 : auto ec = f.maybe_fail();
749 : if(ec)
750 : return;
751 : });
752 : @endcode
753 :
754 : @see armed
755 : */
756 : template<class F>
757 : result
758 15 : operator()(F&& fn)
759 : {
760 15 : return armed(std::forward<F>(fn));
761 : }
762 :
763 : /** Alias for @ref armed (coroutine overload).
764 :
765 : @see armed
766 : */
767 : template<class F>
768 : requires IoLaunchableTask<std::invoke_result_t<F, fuse&>>
769 : result
770 : operator()(F&& fn)
771 : {
772 : return armed(std::forward<F>(fn));
773 : }
774 :
775 : /** Run a test function once without failure injection.
776 :
777 : Invokes the provided function exactly once. Calls to
778 : @ref maybe_fail always return an empty error code and
779 : never throw. Only explicit calls to @ref fail can
780 : signal a test failure.
781 :
782 : This is useful for running tests where you want to
783 : manually control failures, or for quick single-run
784 : tests without systematic error injection.
785 :
786 : @par Example
787 :
788 : @code
789 : fuse f;
790 : auto r = f.inert([](fuse& f) {
791 : auto ec = f.maybe_fail(); // Always succeeds
792 : assert(!ec);
793 :
794 : // Only way to signal failure:
795 : if(some_condition)
796 : {
797 : f.fail();
798 : return;
799 : }
800 : });
801 :
802 : if(!r)
803 : {
804 : std::cerr << "Test failed at "
805 : << r.loc.file_name() << ":"
806 : << r.loc.line() << "\n";
807 : }
808 : @endcode
809 :
810 : @param fn The test function to invoke. It receives
811 : a reference to the fuse. Calls to @ref maybe_fail
812 : will always succeed.
813 :
814 : @return A @ref result indicating success or failure.
815 : On failure, `result::loc` contains the source location
816 : of the @ref fail call.
817 : */
818 : template<class F>
819 : result
820 8 : inert(F&& fn)
821 : {
822 8 : result r;
823 8 : p_->inert = true;
824 : try
825 : {
826 8 : fn(*this);
827 : }
828 2 : catch(...)
829 : {
830 1 : r.success = false;
831 1 : r.loc = p_->loc;
832 1 : r.ep = std::current_exception();
833 1 : return r;
834 : }
835 7 : if(p_->stopped)
836 : {
837 2 : r.success = false;
838 2 : r.loc = p_->loc;
839 2 : r.ep = p_->ep;
840 : }
841 7 : return r;
842 0 : }
843 :
844 : /** Run a coroutine test function once without failure injection.
845 :
846 : Invokes the provided coroutine function exactly once using
847 : @ref run_blocking. Calls to @ref maybe_fail always return
848 : an empty error code and never throw. Only explicit calls
849 : to @ref fail can signal a test failure.
850 :
851 : @par Example
852 :
853 : @code
854 : fuse f;
855 : auto r = f.inert([](fuse& f) -> task<void> {
856 : auto ec = f.maybe_fail(); // Always succeeds
857 : assert(!ec);
858 :
859 : // Only way to signal failure:
860 : if(some_condition)
861 : {
862 : f.fail();
863 : co_return;
864 : }
865 : });
866 :
867 : if(!r)
868 : {
869 : std::cerr << "Test failed at "
870 : << r.loc.file_name() << ":"
871 : << r.loc.line() << "\n";
872 : }
873 : @endcode
874 :
875 : @param fn The coroutine test function to invoke. It receives
876 : a reference to the fuse. Calls to @ref maybe_fail
877 : will always succeed.
878 :
879 : @return A @ref result indicating success or failure.
880 : On failure, `result::loc` contains the source location
881 : of the @ref fail call.
882 : */
883 : template<class F>
884 : requires IoLaunchableTask<std::invoke_result_t<F, fuse&>>
885 : result
886 13 : inert(F&& fn)
887 : {
888 13 : result r;
889 13 : p_->inert = true;
890 : try
891 : {
892 13 : run_blocking()(fn(*this));
893 : }
894 0 : catch(...)
895 : {
896 0 : r.success = false;
897 0 : r.loc = p_->loc;
898 0 : r.ep = std::current_exception();
899 0 : return r;
900 : }
901 13 : if(p_->stopped)
902 : {
903 0 : r.success = false;
904 0 : r.loc = p_->loc;
905 0 : r.ep = p_->ep;
906 : }
907 13 : return r;
908 0 : }
909 : };
910 :
911 : } // test
912 : } // capy
913 : } // boost
914 :
915 : #endif
|