1  
//
1  
//
2  
// Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com)
2  
// Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com)
3  
//
3  
//
4  
// Distributed under the Boost Software License, Version 1.0. (See accompanying
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)
5  
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6  
//
6  
//
7  
// Official repository: https://github.com/cppalliance/capy
7  
// Official repository: https://github.com/cppalliance/capy
8  
//
8  
//
9  

9  

10  
#ifndef BOOST_CAPY_TEST_FUSE_HPP
10  
#ifndef BOOST_CAPY_TEST_FUSE_HPP
11  
#define BOOST_CAPY_TEST_FUSE_HPP
11  
#define BOOST_CAPY_TEST_FUSE_HPP
12  

12  

13  
#include <boost/capy/detail/config.hpp>
13  
#include <boost/capy/detail/config.hpp>
14  
#include <boost/capy/concept/io_launchable_task.hpp>
14  
#include <boost/capy/concept/io_launchable_task.hpp>
15  
#include <boost/capy/error.hpp>
15  
#include <boost/capy/error.hpp>
16  
#include <boost/capy/test/run_blocking.hpp>
16  
#include <boost/capy/test/run_blocking.hpp>
17  
#include <system_error>
17  
#include <system_error>
18  
#include <cstddef>
18  
#include <cstddef>
19  
#include <exception>
19  
#include <exception>
20  
#include <limits>
20  
#include <limits>
21  
#include <memory>
21  
#include <memory>
22  
#include <source_location>
22  
#include <source_location>
23  
#include <type_traits>
23  
#include <type_traits>
24  

24  

25  
/*
25  
/*
26  
    LLM/AI Instructions for fuse-based test patterns:
26  
    LLM/AI Instructions for fuse-based test patterns:
27  

27  

28  
    When f.armed() runs a test, it injects errors at successive points
28  
    When f.armed() runs a test, it injects errors at successive points
29  
    via maybe_fail(). Operations like read_stream::read_some() and
29  
    via maybe_fail(). Operations like read_stream::read_some() and
30  
    write_stream::write_some() call maybe_fail() internally.
30  
    write_stream::write_some() call maybe_fail() internally.
31  

31  

32  
    CORRECT pattern - early return on injected error:
32  
    CORRECT pattern - early return on injected error:
33  

33  

34  
        auto [ec, n] = co_await rs.read_some(buf);
34  
        auto [ec, n] = co_await rs.read_some(buf);
35  
        if(ec)
35  
        if(ec)
36  
            co_return;  // fuse injected error, exit gracefully
36  
            co_return;  // fuse injected error, exit gracefully
37  
        // ... continue with success path
37  
        // ... continue with success path
38  

38  

39  
    WRONG pattern - asserting success unconditionally:
39  
    WRONG pattern - asserting success unconditionally:
40  

40  

41  
        auto [ec, n] = co_await rs.read_some(buf);
41  
        auto [ec, n] = co_await rs.read_some(buf);
42  
        BOOST_TEST(! ec);  // FAILS when fuse injects error!
42  
        BOOST_TEST(! ec);  // FAILS when fuse injects error!
43  

43  

44  
    The fuse mechanism tests error handling by failing at each point
44  
    The fuse mechanism tests error handling by failing at each point
45  
    in sequence. Tests must handle injected errors by returning early,
45  
    in sequence. Tests must handle injected errors by returning early,
46  
    not by asserting that operations always succeed.
46  
    not by asserting that operations always succeed.
47  
*/
47  
*/
48  

48  

49  
namespace boost {
49  
namespace boost {
50  
namespace capy {
50  
namespace capy {
51  
namespace test {
51  
namespace test {
52  

52  

53  
/** A test utility for systematic error injection.
53  
/** A test utility for systematic error injection.
54  

54  

55  
    This class enables exhaustive testing of error handling
55  
    This class enables exhaustive testing of error handling
56  
    paths by injecting failures at successive points in code.
56  
    paths by injecting failures at successive points in code.
57  
    Each iteration fails at a later point until the code path
57  
    Each iteration fails at a later point until the code path
58  
    completes without encountering a failure. The @ref armed
58  
    completes without encountering a failure. The @ref armed
59  
    method runs in two phases: first with error codes, then
59  
    method runs in two phases: first with error codes, then
60  
    with exceptions. The @ref inert method runs once without
60  
    with exceptions. The @ref inert method runs once without
61  
    automatic failure injection.
61  
    automatic failure injection.
62  

62  

63  
    @par Thread Safety
63  
    @par Thread Safety
64  

64  

65  
    @b Not @b thread @b safe. Instances must not be accessed
65  
    @b Not @b thread @b safe. Instances must not be accessed
66  
    from different logical threads of operation concurrently.
66  
    from different logical threads of operation concurrently.
67  
    This includes coroutines - accessing the same fuse from
67  
    This includes coroutines - accessing the same fuse from
68  
    multiple concurrent coroutines causes non-deterministic
68  
    multiple concurrent coroutines causes non-deterministic
69  
    test behavior.
69  
    test behavior.
70  

70  

71  
    @par Basic Inline Usage
71  
    @par Basic Inline Usage
72  

72  

73  
    @code
73  
    @code
74  
    fuse()([](fuse& f) {
74  
    fuse()([](fuse& f) {
75  
        auto ec = f.maybe_fail();
75  
        auto ec = f.maybe_fail();
76  
        if(ec)
76  
        if(ec)
77  
            return;
77  
            return;
78  

78  

79  
        ec = f.maybe_fail();
79  
        ec = f.maybe_fail();
80  
        if(ec)
80  
        if(ec)
81  
            return;
81  
            return;
82  
    });
82  
    });
83  
    @endcode
83  
    @endcode
84  

84  

85  
    @par Named Fuse with armed()
85  
    @par Named Fuse with armed()
86  

86  

87  
    @code
87  
    @code
88  
    fuse f;
88  
    fuse f;
89  
    MyObject obj(f);
89  
    MyObject obj(f);
90  
    auto r = f.armed([&](fuse&) {
90  
    auto r = f.armed([&](fuse&) {
91  
        obj.do_something();
91  
        obj.do_something();
92  
    });
92  
    });
93  
    @endcode
93  
    @endcode
94  

94  

95  
    @par Using inert() for Single-Run Tests
95  
    @par Using inert() for Single-Run Tests
96  

96  

97  
    @code
97  
    @code
98  
    fuse f;
98  
    fuse f;
99  
    auto r = f.inert([](fuse& f) {
99  
    auto r = f.inert([](fuse& f) {
100  
        auto ec = f.maybe_fail();  // Always succeeds
100  
        auto ec = f.maybe_fail();  // Always succeeds
101  
        if(some_condition)
101  
        if(some_condition)
102  
            f.fail();  // Only way to signal failure
102  
            f.fail();  // Only way to signal failure
103  
    });
103  
    });
104  
    @endcode
104  
    @endcode
105  

105  

106  
    @par Dependency Injection (Standalone Usage)
106  
    @par Dependency Injection (Standalone Usage)
107  

107  

108  
    A default-constructed fuse is a no-op when used outside
108  
    A default-constructed fuse is a no-op when used outside
109  
    of @ref armed or @ref inert. This enables passing a fuse
109  
    of @ref armed or @ref inert. This enables passing a fuse
110  
    to classes for dependency injection without affecting
110  
    to classes for dependency injection without affecting
111  
    normal operation.
111  
    normal operation.
112  

112  

113  
    @code
113  
    @code
114  
    class MyService
114  
    class MyService
115  
    {
115  
    {
116  
        fuse& f_;
116  
        fuse& f_;
117  
    public:
117  
    public:
118  
        explicit MyService(fuse& f) : f_(f) {}
118  
        explicit MyService(fuse& f) : f_(f) {}
119  

119  

120  
        std::error_code do_work()
120  
        std::error_code do_work()
121  
        {
121  
        {
122  
            auto ec = f_.maybe_fail();  // No-op outside armed/inert
122  
            auto ec = f_.maybe_fail();  // No-op outside armed/inert
123  
            if(ec)
123  
            if(ec)
124  
                return ec;
124  
                return ec;
125  
            // ... actual work ...
125  
            // ... actual work ...
126  
            return {};
126  
            return {};
127  
        }
127  
        }
128  
    };
128  
    };
129  

129  

130  
    // Production usage - fuse is no-op
130  
    // Production usage - fuse is no-op
131  
    fuse f;
131  
    fuse f;
132  
    MyService svc(f);
132  
    MyService svc(f);
133  
    svc.do_work();  // maybe_fail() returns {} always
133  
    svc.do_work();  // maybe_fail() returns {} always
134  

134  

135  
    // Test usage - failures are injected
135  
    // Test usage - failures are injected
136  
    auto r = f.armed([&](fuse&) {
136  
    auto r = f.armed([&](fuse&) {
137  
        svc.do_work();  // maybe_fail() triggers failures
137  
        svc.do_work();  // maybe_fail() triggers failures
138  
    });
138  
    });
139  
    @endcode
139  
    @endcode
140  

140  

141  
    @par Custom Error Code
141  
    @par Custom Error Code
142  

142  

143  
    @code
143  
    @code
144  
    auto custom_ec = make_error_code(
144  
    auto custom_ec = make_error_code(
145  
        std::errc::operation_canceled);
145  
        std::errc::operation_canceled);
146  
    fuse f(custom_ec);
146  
    fuse f(custom_ec);
147  
    auto r = f.armed([](fuse& f) {
147  
    auto r = f.armed([](fuse& f) {
148  
        auto ec = f.maybe_fail();
148  
        auto ec = f.maybe_fail();
149  
        if(ec)
149  
        if(ec)
150  
            return;
150  
            return;
151  
    });
151  
    });
152  
    @endcode
152  
    @endcode
153  

153  

154  
    @par Checking the Result
154  
    @par Checking the Result
155  

155  

156  
    @code
156  
    @code
157  
    fuse f;
157  
    fuse f;
158  
    auto r = f([](fuse& f) {
158  
    auto r = f([](fuse& f) {
159  
        auto ec = f.maybe_fail();
159  
        auto ec = f.maybe_fail();
160  
        if(ec)
160  
        if(ec)
161  
            return;
161  
            return;
162  
    });
162  
    });
163  

163  

164  
    if(!r)
164  
    if(!r)
165  
    {
165  
    {
166  
        std::cerr << "Failure at "
166  
        std::cerr << "Failure at "
167  
            << r.loc.file_name() << ":"
167  
            << r.loc.file_name() << ":"
168  
            << r.loc.line() << "\n";
168  
            << r.loc.line() << "\n";
169  
    }
169  
    }
170  
    @endcode
170  
    @endcode
171  

171  

172  
    @par Test Framework Integration
172  
    @par Test Framework Integration
173  

173  

174  
    @code
174  
    @code
175  
    fuse f;
175  
    fuse f;
176  
    auto r = f([](fuse& f) {
176  
    auto r = f([](fuse& f) {
177  
        auto ec = f.maybe_fail();
177  
        auto ec = f.maybe_fail();
178  
        if(ec)
178  
        if(ec)
179  
            return;
179  
            return;
180  
    });
180  
    });
181  

181  

182  
    // Boost.Test
182  
    // Boost.Test
183  
    BOOST_TEST(r.success);
183  
    BOOST_TEST(r.success);
184  
    if(!r)
184  
    if(!r)
185  
        BOOST_TEST_MESSAGE("Failed at " << r.loc.file_name()
185  
        BOOST_TEST_MESSAGE("Failed at " << r.loc.file_name()
186  
            << ":" << r.loc.line());
186  
            << ":" << r.loc.line());
187  

187  

188  
    // Catch2
188  
    // Catch2
189  
    REQUIRE(r.success);
189  
    REQUIRE(r.success);
190  
    if(!r)
190  
    if(!r)
191  
        INFO("Failed at " << r.loc.file_name()
191  
        INFO("Failed at " << r.loc.file_name()
192  
            << ":" << r.loc.line());
192  
            << ":" << r.loc.line());
193  
    @endcode
193  
    @endcode
194  
*/
194  
*/
195  
class fuse
195  
class fuse
196  
{
196  
{
197  
    struct state
197  
    struct state
198  
    {
198  
    {
199  
        std::size_t n = (std::numeric_limits<std::size_t>::max)();
199  
        std::size_t n = (std::numeric_limits<std::size_t>::max)();
200  
        std::size_t i = 0;
200  
        std::size_t i = 0;
201  
        bool triggered = false;
201  
        bool triggered = false;
202  
        bool throws = false;
202  
        bool throws = false;
203  
        bool stopped = false;
203  
        bool stopped = false;
204  
        bool inert = true;
204  
        bool inert = true;
205  
        std::error_code ec;
205  
        std::error_code ec;
206  
        std::source_location loc;
206  
        std::source_location loc;
207  
        std::exception_ptr ep;
207  
        std::exception_ptr ep;
208  
    };
208  
    };
209  

209  

210  
    std::shared_ptr<state> p_;
210  
    std::shared_ptr<state> p_;
211  

211  

212  
    /** Return true if testing should continue.
212  
    /** Return true if testing should continue.
213  

213  

214  
        On the first call, initializes the failure point to 0.
214  
        On the first call, initializes the failure point to 0.
215  
        After a triggered failure, increments the failure point
215  
        After a triggered failure, increments the failure point
216  
        and resets for the next iteration. Returns false when
216  
        and resets for the next iteration. Returns false when
217  
        the test completes without triggering a failure.
217  
        the test completes without triggering a failure.
218  
    */
218  
    */
219  
    explicit operator bool() const noexcept
219  
    explicit operator bool() const noexcept
220  
    {
220  
    {
221  
        auto& s = *p_;
221  
        auto& s = *p_;
222  
        if(s.n == (std::numeric_limits<std::size_t>::max)())
222  
        if(s.n == (std::numeric_limits<std::size_t>::max)())
223  
        {
223  
        {
224  
            // First call: start round 0
224  
            // First call: start round 0
225  
            s.n = 0;
225  
            s.n = 0;
226  
            return true;
226  
            return true;
227  
        }
227  
        }
228  
        if(s.triggered)
228  
        if(s.triggered)
229  
        {
229  
        {
230  
            // Previous round triggered, try next failure point
230  
            // Previous round triggered, try next failure point
231  
            s.n++;
231  
            s.n++;
232  
            s.i = 0;
232  
            s.i = 0;
233  
            s.triggered = false;
233  
            s.triggered = false;
234  
            return true;
234  
            return true;
235  
        }
235  
        }
236  
        // Test completed without trigger: success
236  
        // Test completed without trigger: success
237  
        return false;
237  
        return false;
238  
    }
238  
    }
239  

239  

240  
public:
240  
public:
241  
    /** Result of a fuse operation.
241  
    /** Result of a fuse operation.
242  

242  

243  
        Contains the outcome of @ref armed or @ref inert
243  
        Contains the outcome of @ref armed or @ref inert
244  
        and, on failure, the source location of the failing
244  
        and, on failure, the source location of the failing
245  
        point. Converts to `bool` for convenient success
245  
        point. Converts to `bool` for convenient success
246  
        checking.
246  
        checking.
247  

247  

248  
        @par Example
248  
        @par Example
249  

249  

250  
        @code
250  
        @code
251  
        fuse f;
251  
        fuse f;
252  
        auto r = f([](fuse& f) {
252  
        auto r = f([](fuse& f) {
253  
            auto ec = f.maybe_fail();
253  
            auto ec = f.maybe_fail();
254  
            if(ec)
254  
            if(ec)
255  
                return;
255  
                return;
256  
        });
256  
        });
257  

257  

258  
        if(!r)
258  
        if(!r)
259  
        {
259  
        {
260  
            std::cerr << "Failure at "
260  
            std::cerr << "Failure at "
261  
                << r.loc.file_name() << ":"
261  
                << r.loc.file_name() << ":"
262  
                << r.loc.line() << "\n";
262  
                << r.loc.line() << "\n";
263  
        }
263  
        }
264  
        @endcode
264  
        @endcode
265  
    */
265  
    */
266  
    struct result
266  
    struct result
267  
    {
267  
    {
268  
        std::source_location loc = {};
268  
        std::source_location loc = {};
269  
        std::exception_ptr ep = nullptr;
269  
        std::exception_ptr ep = nullptr;
270  
        bool success = true;
270  
        bool success = true;
271  

271  

272  
        constexpr explicit operator bool() const noexcept
272  
        constexpr explicit operator bool() const noexcept
273  
        {
273  
        {
274  
            return success;
274  
            return success;
275  
        }
275  
        }
276  
    };
276  
    };
277  

277  

278  
    /** Construct a fuse with a custom error code.
278  
    /** Construct a fuse with a custom error code.
279  

279  

280  
        @par Example
280  
        @par Example
281  

281  

282  
        @code
282  
        @code
283  
        auto custom_ec = make_error_code(
283  
        auto custom_ec = make_error_code(
284  
            std::errc::operation_canceled);
284  
            std::errc::operation_canceled);
285  
        fuse f(custom_ec);
285  
        fuse f(custom_ec);
286  

286  

287  
        std::error_code captured_ec;
287  
        std::error_code captured_ec;
288  
        auto r = f([&](fuse& f) {
288  
        auto r = f([&](fuse& f) {
289  
            auto ec = f.maybe_fail();
289  
            auto ec = f.maybe_fail();
290  
            if(ec)
290  
            if(ec)
291  
            {
291  
            {
292  
                captured_ec = ec;
292  
                captured_ec = ec;
293  
                return;
293  
                return;
294  
            }
294  
            }
295  
        });
295  
        });
296  

296  

297  
        assert(captured_ec == custom_ec);
297  
        assert(captured_ec == custom_ec);
298  
        @endcode
298  
        @endcode
299  

299  

300  
        @param ec The error code to deliver at failure points.
300  
        @param ec The error code to deliver at failure points.
301  
    */
301  
    */
302  
    explicit fuse(std::error_code ec)
302  
    explicit fuse(std::error_code ec)
303  
        : p_(std::make_shared<state>())
303  
        : p_(std::make_shared<state>())
304  
    {
304  
    {
305  
        p_->ec = ec;
305  
        p_->ec = ec;
306  
    }
306  
    }
307  

307  

308  
    /** Construct a fuse with the default error code.
308  
    /** Construct a fuse with the default error code.
309  

309  

310  
        The default error code is `error::test_failure`.
310  
        The default error code is `error::test_failure`.
311  

311  

312  
        @par Example
312  
        @par Example
313  

313  

314  
        @code
314  
        @code
315  
        fuse f;
315  
        fuse f;
316  
        std::error_code captured_ec;
316  
        std::error_code captured_ec;
317  

317  

318  
        auto r = f([&](fuse& f) {
318  
        auto r = f([&](fuse& f) {
319  
            auto ec = f.maybe_fail();
319  
            auto ec = f.maybe_fail();
320  
            if(ec)
320  
            if(ec)
321  
            {
321  
            {
322  
                captured_ec = ec;
322  
                captured_ec = ec;
323  
                return;
323  
                return;
324  
            }
324  
            }
325  
        });
325  
        });
326  

326  

327  
        assert(captured_ec == error::test_failure);
327  
        assert(captured_ec == error::test_failure);
328  
        @endcode
328  
        @endcode
329  
    */
329  
    */
330  
    fuse()
330  
    fuse()
331  
        : fuse(error::test_failure)
331  
        : fuse(error::test_failure)
332  
    {
332  
    {
333  
    }
333  
    }
334  

334  

335  
    /** Return an error or throw at the current failure point.
335  
    /** Return an error or throw at the current failure point.
336  

336  

337  
        When running under @ref armed, increments the internal
337  
        When running under @ref armed, increments the internal
338  
        counter. When the counter reaches the current failure
338  
        counter. When the counter reaches the current failure
339  
        point, returns the stored error code (or throws
339  
        point, returns the stored error code (or throws
340  
        `std::system_error` in exception mode) and records
340  
        `std::system_error` in exception mode) and records
341  
        the source location.
341  
        the source location.
342  

342  

343  
        When called outside of @ref armed or @ref inert (standalone
343  
        When called outside of @ref armed or @ref inert (standalone
344  
        usage), or when running under @ref inert, always returns
344  
        usage), or when running under @ref inert, always returns
345  
        an empty error code. This enables dependency injection
345  
        an empty error code. This enables dependency injection
346  
        where the fuse is a no-op in production code.
346  
        where the fuse is a no-op in production code.
347  

347  

348  
        @par Example
348  
        @par Example
349  

349  

350  
        @code
350  
        @code
351  
        fuse f;
351  
        fuse f;
352  
        auto r = f([](fuse& f) {
352  
        auto r = f([](fuse& f) {
353  
            // Error code mode: returns the error
353  
            // Error code mode: returns the error
354  
            auto ec = f.maybe_fail();
354  
            auto ec = f.maybe_fail();
355  
            if(ec)
355  
            if(ec)
356  
                return;
356  
                return;
357  

357  

358  
            // Exception mode: throws system_error
358  
            // Exception mode: throws system_error
359  
            ec = f.maybe_fail();
359  
            ec = f.maybe_fail();
360  
            if(ec)
360  
            if(ec)
361  
                return;
361  
                return;
362  
        });
362  
        });
363  
        @endcode
363  
        @endcode
364  

364  

365  
        @par Standalone Usage
365  
        @par Standalone Usage
366  

366  

367  
        @code
367  
        @code
368  
        fuse f;
368  
        fuse f;
369  
        auto ec = f.maybe_fail();  // Always returns {} (no-op)
369  
        auto ec = f.maybe_fail();  // Always returns {} (no-op)
370  
        @endcode
370  
        @endcode
371  

371  

372  
        @param loc The source location of the call site,
372  
        @param loc The source location of the call site,
373  
        captured automatically.
373  
        captured automatically.
374  

374  

375  
        @return The stored error code if at the failure point,
375  
        @return The stored error code if at the failure point,
376  
        otherwise an empty error code. In exception mode,
376  
        otherwise an empty error code. In exception mode,
377  
        throws instead of returning an error. When called
377  
        throws instead of returning an error. When called
378  
        outside @ref armed, or when running under @ref inert,
378  
        outside @ref armed, or when running under @ref inert,
379  
        always returns an empty error code.
379  
        always returns an empty error code.
380  

380  

381  
        @throws std::system_error When in exception mode
381  
        @throws std::system_error When in exception mode
382  
        and at the failure point (not thrown outside @ref armed).
382  
        and at the failure point (not thrown outside @ref armed).
383  
    */
383  
    */
384  
    std::error_code
384  
    std::error_code
385  
    maybe_fail(
385  
    maybe_fail(
386  
        std::source_location loc = std::source_location::current())
386  
        std::source_location loc = std::source_location::current())
387  
    {
387  
    {
388  
        auto& s = *p_;
388  
        auto& s = *p_;
389  
        if(s.inert)
389  
        if(s.inert)
390  
            return {};
390  
            return {};
391  
        if(s.i < s.n)
391  
        if(s.i < s.n)
392  
            ++s.i;
392  
            ++s.i;
393  
        if(s.i == s.n)
393  
        if(s.i == s.n)
394  
        {
394  
        {
395  
            s.triggered = true;
395  
            s.triggered = true;
396  
            s.loc = loc;
396  
            s.loc = loc;
397  
            if(s.throws)
397  
            if(s.throws)
398  
                throw std::system_error(s.ec);
398  
                throw std::system_error(s.ec);
399  
            return s.ec;
399  
            return s.ec;
400  
        }
400  
        }
401  
        return {};
401  
        return {};
402  
    }
402  
    }
403  

403  

404  
    /** Signal a test failure and stop execution.
404  
    /** Signal a test failure and stop execution.
405  

405  

406  
        Call this from the test function to indicate a failure
406  
        Call this from the test function to indicate a failure
407  
        condition. Both @ref armed and @ref inert will return
407  
        condition. Both @ref armed and @ref inert will return
408  
        a failed @ref result immediately.
408  
        a failed @ref result immediately.
409  

409  

410  
        @par Example
410  
        @par Example
411  

411  

412  
        @code
412  
        @code
413  
        fuse f;
413  
        fuse f;
414  
        auto r = f([](fuse& f) {
414  
        auto r = f([](fuse& f) {
415  
            auto ec = f.maybe_fail();
415  
            auto ec = f.maybe_fail();
416  
            if(ec)
416  
            if(ec)
417  
                return;
417  
                return;
418  

418  

419  
            // Explicit failure when a condition is not met
419  
            // Explicit failure when a condition is not met
420  
            if(some_value != expected)
420  
            if(some_value != expected)
421  
            {
421  
            {
422  
                f.fail();
422  
                f.fail();
423  
                return;
423  
                return;
424  
            }
424  
            }
425  
        });
425  
        });
426  

426  

427  
        if(!r)
427  
        if(!r)
428  
        {
428  
        {
429  
            std::cerr << "Test failed at "
429  
            std::cerr << "Test failed at "
430  
                << r.loc.file_name() << ":"
430  
                << r.loc.file_name() << ":"
431  
                << r.loc.line() << "\n";
431  
                << r.loc.line() << "\n";
432  
        }
432  
        }
433  
        @endcode
433  
        @endcode
434  

434  

435  
        @param loc The source location of the call site,
435  
        @param loc The source location of the call site,
436  
        captured automatically.
436  
        captured automatically.
437  
    */
437  
    */
438  
    void
438  
    void
439  
    fail(
439  
    fail(
440  
        std::source_location loc =
440  
        std::source_location loc =
441  
            std::source_location::current()) noexcept
441  
            std::source_location::current()) noexcept
442  
    {
442  
    {
443  
        p_->loc = loc;
443  
        p_->loc = loc;
444  
        p_->stopped = true;
444  
        p_->stopped = true;
445  
    }
445  
    }
446  

446  

447  
    /** Signal a test failure with an exception and stop execution.
447  
    /** Signal a test failure with an exception and stop execution.
448  

448  

449  
        Call this from the test function to indicate a failure
449  
        Call this from the test function to indicate a failure
450  
        condition with an associated exception. Both @ref armed
450  
        condition with an associated exception. Both @ref armed
451  
        and @ref inert will return a failed @ref result with
451  
        and @ref inert will return a failed @ref result with
452  
        the captured exception pointer.
452  
        the captured exception pointer.
453  

453  

454  
        @par Example
454  
        @par Example
455  

455  

456  
        @code
456  
        @code
457  
        fuse f;
457  
        fuse f;
458  
        auto r = f([](fuse& f) {
458  
        auto r = f([](fuse& f) {
459  
            try
459  
            try
460  
            {
460  
            {
461  
                do_something();
461  
                do_something();
462  
            }
462  
            }
463  
            catch(...)
463  
            catch(...)
464  
            {
464  
            {
465  
                f.fail(std::current_exception());
465  
                f.fail(std::current_exception());
466  
                return;
466  
                return;
467  
            }
467  
            }
468  
        });
468  
        });
469  

469  

470  
        if(!r)
470  
        if(!r)
471  
        {
471  
        {
472  
            try
472  
            try
473  
            {
473  
            {
474  
                if(r.ep)
474  
                if(r.ep)
475  
                    std::rethrow_exception(r.ep);
475  
                    std::rethrow_exception(r.ep);
476  
            }
476  
            }
477  
            catch(std::exception const& e)
477  
            catch(std::exception const& e)
478  
            {
478  
            {
479  
                std::cerr << "Exception: " << e.what() << "\n";
479  
                std::cerr << "Exception: " << e.what() << "\n";
480  
            }
480  
            }
481  
        }
481  
        }
482  
        @endcode
482  
        @endcode
483  

483  

484  
        @param ep The exception pointer to capture.
484  
        @param ep The exception pointer to capture.
485  

485  

486  
        @param loc The source location of the call site,
486  
        @param loc The source location of the call site,
487  
        captured automatically.
487  
        captured automatically.
488  
    */
488  
    */
489  
    void
489  
    void
490  
    fail(
490  
    fail(
491  
        std::exception_ptr ep,
491  
        std::exception_ptr ep,
492  
        std::source_location loc =
492  
        std::source_location loc =
493  
            std::source_location::current()) noexcept
493  
            std::source_location::current()) noexcept
494  
    {
494  
    {
495  
        p_->ep = ep;
495  
        p_->ep = ep;
496  
        p_->loc = loc;
496  
        p_->loc = loc;
497  
        p_->stopped = true;
497  
        p_->stopped = true;
498  
    }
498  
    }
499  

499  

500  
    /** Run a test function with systematic failure injection.
500  
    /** Run a test function with systematic failure injection.
501  

501  

502  
        Repeatedly invokes the provided function, failing at
502  
        Repeatedly invokes the provided function, failing at
503  
        successive points until the function completes without
503  
        successive points until the function completes without
504  
        encountering a failure. First runs the complete loop
504  
        encountering a failure. First runs the complete loop
505  
        using error codes, then runs using exceptions.
505  
        using error codes, then runs using exceptions.
506  

506  

507  
        @par Example
507  
        @par Example
508  

508  

509  
        @code
509  
        @code
510  
        fuse f;
510  
        fuse f;
511  
        auto r = f.armed([](fuse& f) {
511  
        auto r = f.armed([](fuse& f) {
512  
            auto ec = f.maybe_fail();
512  
            auto ec = f.maybe_fail();
513  
            if(ec)
513  
            if(ec)
514  
                return;
514  
                return;
515  

515  

516  
            ec = f.maybe_fail();
516  
            ec = f.maybe_fail();
517  
            if(ec)
517  
            if(ec)
518  
                return;
518  
                return;
519  
        });
519  
        });
520  

520  

521  
        if(!r)
521  
        if(!r)
522  
        {
522  
        {
523  
            std::cerr << "Failure at "
523  
            std::cerr << "Failure at "
524  
                << r.loc.file_name() << ":"
524  
                << r.loc.file_name() << ":"
525  
                << r.loc.line() << "\n";
525  
                << r.loc.line() << "\n";
526  
        }
526  
        }
527  
        @endcode
527  
        @endcode
528  

528  

529  
        @param fn The test function to invoke. It receives
529  
        @param fn The test function to invoke. It receives
530  
        a reference to the fuse and should call @ref maybe_fail
530  
        a reference to the fuse and should call @ref maybe_fail
531  
        at each potential failure point.
531  
        at each potential failure point.
532  

532  

533  
        @return A @ref result indicating success or failure.
533  
        @return A @ref result indicating success or failure.
534  
        On failure, `result::loc` contains the source location
534  
        On failure, `result::loc` contains the source location
535  
        of the last @ref maybe_fail or @ref fail call.
535  
        of the last @ref maybe_fail or @ref fail call.
536  
    */
536  
    */
537  
    template<class F>
537  
    template<class F>
538  
    result
538  
    result
539  
    armed(F&& fn)
539  
    armed(F&& fn)
540  
    {
540  
    {
541  
        result r;
541  
        result r;
542  

542  

543  
        // Phase 1: error code mode
543  
        // Phase 1: error code mode
544  
        p_->throws = false;
544  
        p_->throws = false;
545  
        p_->inert = false;
545  
        p_->inert = false;
546  
        p_->n = (std::numeric_limits<std::size_t>::max)();
546  
        p_->n = (std::numeric_limits<std::size_t>::max)();
547  
        while(*this)
547  
        while(*this)
548  
        {
548  
        {
549  
            try
549  
            try
550  
            {
550  
            {
551  
                fn(*this);
551  
                fn(*this);
552  
            }
552  
            }
553  
            catch(...)
553  
            catch(...)
554  
            {
554  
            {
555  
                r.success = false;
555  
                r.success = false;
556  
                r.loc = p_->loc;
556  
                r.loc = p_->loc;
557  
                r.ep = p_->ep;
557  
                r.ep = p_->ep;
558  
                p_->inert = true;
558  
                p_->inert = true;
559  
                return r;
559  
                return r;
560  
            }
560  
            }
561  
            if(p_->stopped)
561  
            if(p_->stopped)
562  
            {
562  
            {
563  
                r.success = false;
563  
                r.success = false;
564  
                r.loc = p_->loc;
564  
                r.loc = p_->loc;
565  
                r.ep = p_->ep;
565  
                r.ep = p_->ep;
566  
                p_->inert = true;
566  
                p_->inert = true;
567  
                return r;
567  
                return r;
568  
            }
568  
            }
569  
        }
569  
        }
570  

570  

571  
        // Phase 2: exception mode
571  
        // Phase 2: exception mode
572  
        p_->throws = true;
572  
        p_->throws = true;
573  
        p_->n = (std::numeric_limits<std::size_t>::max)();
573  
        p_->n = (std::numeric_limits<std::size_t>::max)();
574  
        p_->i = 0;
574  
        p_->i = 0;
575  
        p_->triggered = false;
575  
        p_->triggered = false;
576  
        while(*this)
576  
        while(*this)
577  
        {
577  
        {
578  
            try
578  
            try
579  
            {
579  
            {
580  
                fn(*this);
580  
                fn(*this);
581  
            }
581  
            }
582  
            catch(std::system_error const& ex)
582  
            catch(std::system_error const& ex)
583  
            {
583  
            {
584  
                if(ex.code() != p_->ec)
584  
                if(ex.code() != p_->ec)
585  
                {
585  
                {
586  
                    r.success = false;
586  
                    r.success = false;
587  
                    r.loc = p_->loc;
587  
                    r.loc = p_->loc;
588  
                    r.ep = p_->ep;
588  
                    r.ep = p_->ep;
589  
                    p_->inert = true;
589  
                    p_->inert = true;
590  
                    return r;
590  
                    return r;
591  
                }
591  
                }
592  
            }
592  
            }
593  
            catch(...)
593  
            catch(...)
594  
            {
594  
            {
595  
                r.success = false;
595  
                r.success = false;
596  
                r.loc = p_->loc;
596  
                r.loc = p_->loc;
597  
                r.ep = p_->ep;
597  
                r.ep = p_->ep;
598  
                p_->inert = true;
598  
                p_->inert = true;
599  
                return r;
599  
                return r;
600  
            }
600  
            }
601  
            if(p_->stopped)
601  
            if(p_->stopped)
602  
            {
602  
            {
603  
                r.success = false;
603  
                r.success = false;
604  
                r.loc = p_->loc;
604  
                r.loc = p_->loc;
605  
                r.ep = p_->ep;
605  
                r.ep = p_->ep;
606  
                p_->inert = true;
606  
                p_->inert = true;
607  
                return r;
607  
                return r;
608  
            }
608  
            }
609  
        }
609  
        }
610  
        p_->inert = true;
610  
        p_->inert = true;
611  
        return r;
611  
        return r;
612  
    }
612  
    }
613  

613  

614  
    /** Run a coroutine test function with systematic failure injection.
614  
    /** Run a coroutine test function with systematic failure injection.
615  

615  

616  
        Repeatedly invokes the provided coroutine function, failing at
616  
        Repeatedly invokes the provided coroutine function, failing at
617  
        successive points until the function completes without
617  
        successive points until the function completes without
618  
        encountering a failure. First runs the complete loop
618  
        encountering a failure. First runs the complete loop
619  
        using error codes, then runs using exceptions.
619  
        using error codes, then runs using exceptions.
620  

620  

621  
        This overload handles lambdas that return an @ref IoLaunchableTask
621  
        This overload handles lambdas that return an @ref IoLaunchableTask
622  
        (such as `task<void>`), executing them synchronously via
622  
        (such as `task<void>`), executing them synchronously via
623  
        @ref run_blocking.
623  
        @ref run_blocking.
624  

624  

625  
        @par Example
625  
        @par Example
626  

626  

627  
        @code
627  
        @code
628  
        fuse f;
628  
        fuse f;
629  
        auto r = f.armed([&](fuse&) -> task<void> {
629  
        auto r = f.armed([&](fuse&) -> task<void> {
630  
            auto ec = f.maybe_fail();
630  
            auto ec = f.maybe_fail();
631  
            if(ec)
631  
            if(ec)
632  
                co_return;
632  
                co_return;
633  

633  

634  
            ec = f.maybe_fail();
634  
            ec = f.maybe_fail();
635  
            if(ec)
635  
            if(ec)
636  
                co_return;
636  
                co_return;
637  
        });
637  
        });
638  

638  

639  
        if(!r)
639  
        if(!r)
640  
        {
640  
        {
641  
            std::cerr << "Failure at "
641  
            std::cerr << "Failure at "
642  
                << r.loc.file_name() << ":"
642  
                << r.loc.file_name() << ":"
643  
                << r.loc.line() << "\n";
643  
                << r.loc.line() << "\n";
644  
        }
644  
        }
645  
        @endcode
645  
        @endcode
646  

646  

647  
        @param fn The coroutine test function to invoke. It receives
647  
        @param fn The coroutine test function to invoke. It receives
648  
        a reference to the fuse and should call @ref maybe_fail
648  
        a reference to the fuse and should call @ref maybe_fail
649  
        at each potential failure point.
649  
        at each potential failure point.
650  

650  

651  
        @return A @ref result indicating success or failure.
651  
        @return A @ref result indicating success or failure.
652  
        On failure, `result::loc` contains the source location
652  
        On failure, `result::loc` contains the source location
653  
        of the last @ref maybe_fail or @ref fail call.
653  
        of the last @ref maybe_fail or @ref fail call.
654  
    */
654  
    */
655  
    template<class F>
655  
    template<class F>
656  
        requires IoLaunchableTask<std::invoke_result_t<F, fuse&>>
656  
        requires IoLaunchableTask<std::invoke_result_t<F, fuse&>>
657  
    result
657  
    result
658  
    armed(F&& fn)
658  
    armed(F&& fn)
659  
    {
659  
    {
660  
        result r;
660  
        result r;
661  

661  

662  
        // Phase 1: error code mode
662  
        // Phase 1: error code mode
663  
        p_->throws = false;
663  
        p_->throws = false;
664  
        p_->inert = false;
664  
        p_->inert = false;
665  
        p_->n = (std::numeric_limits<std::size_t>::max)();
665  
        p_->n = (std::numeric_limits<std::size_t>::max)();
666  
        while(*this)
666  
        while(*this)
667  
        {
667  
        {
668  
            try
668  
            try
669  
            {
669  
            {
670  
                run_blocking()(fn(*this));
670  
                run_blocking()(fn(*this));
671  
            }
671  
            }
672  
            catch(...)
672  
            catch(...)
673  
            {
673  
            {
674  
                r.success = false;
674  
                r.success = false;
675  
                r.loc = p_->loc;
675  
                r.loc = p_->loc;
676  
                r.ep = p_->ep;
676  
                r.ep = p_->ep;
677  
                p_->inert = true;
677  
                p_->inert = true;
678  
                return r;
678  
                return r;
679  
            }
679  
            }
680  
            if(p_->stopped)
680  
            if(p_->stopped)
681  
            {
681  
            {
682  
                r.success = false;
682  
                r.success = false;
683  
                r.loc = p_->loc;
683  
                r.loc = p_->loc;
684  
                r.ep = p_->ep;
684  
                r.ep = p_->ep;
685  
                p_->inert = true;
685  
                p_->inert = true;
686  
                return r;
686  
                return r;
687  
            }
687  
            }
688  
        }
688  
        }
689  

689  

690  
        // Phase 2: exception mode
690  
        // Phase 2: exception mode
691  
        p_->throws = true;
691  
        p_->throws = true;
692  
        p_->n = (std::numeric_limits<std::size_t>::max)();
692  
        p_->n = (std::numeric_limits<std::size_t>::max)();
693  
        p_->i = 0;
693  
        p_->i = 0;
694  
        p_->triggered = false;
694  
        p_->triggered = false;
695  
        while(*this)
695  
        while(*this)
696  
        {
696  
        {
697  
            try
697  
            try
698  
            {
698  
            {
699  
                run_blocking()(fn(*this));
699  
                run_blocking()(fn(*this));
700  
            }
700  
            }
701  
            catch(std::system_error const& ex)
701  
            catch(std::system_error const& ex)
702  
            {
702  
            {
703  
                if(ex.code() != p_->ec)
703  
                if(ex.code() != p_->ec)
704  
                {
704  
                {
705  
                    r.success = false;
705  
                    r.success = false;
706  
                    r.loc = p_->loc;
706  
                    r.loc = p_->loc;
707  
                    r.ep = p_->ep;
707  
                    r.ep = p_->ep;
708  
                    p_->inert = true;
708  
                    p_->inert = true;
709  
                    return r;
709  
                    return r;
710  
                }
710  
                }
711  
            }
711  
            }
712  
            catch(...)
712  
            catch(...)
713  
            {
713  
            {
714  
                r.success = false;
714  
                r.success = false;
715  
                r.loc = p_->loc;
715  
                r.loc = p_->loc;
716  
                r.ep = p_->ep;
716  
                r.ep = p_->ep;
717  
                p_->inert = true;
717  
                p_->inert = true;
718  
                return r;
718  
                return r;
719  
            }
719  
            }
720  
            if(p_->stopped)
720  
            if(p_->stopped)
721  
            {
721  
            {
722  
                r.success = false;
722  
                r.success = false;
723  
                r.loc = p_->loc;
723  
                r.loc = p_->loc;
724  
                r.ep = p_->ep;
724  
                r.ep = p_->ep;
725  
                p_->inert = true;
725  
                p_->inert = true;
726  
                return r;
726  
                return r;
727  
            }
727  
            }
728  
        }
728  
        }
729  
        p_->inert = true;
729  
        p_->inert = true;
730  
        return r;
730  
        return r;
731  
    }
731  
    }
732  

732  

733  
    /** Alias for @ref armed.
733  
    /** Alias for @ref armed.
734  

734  

735  
        Allows the fuse to be invoked directly as a function
735  
        Allows the fuse to be invoked directly as a function
736  
        object for more concise syntax.
736  
        object for more concise syntax.
737  

737  

738  
        @par Example
738  
        @par Example
739  

739  

740  
        @code
740  
        @code
741  
        // These are equivalent:
741  
        // These are equivalent:
742  
        fuse f;
742  
        fuse f;
743  
        auto r1 = f.armed([](fuse& f) { ... });
743  
        auto r1 = f.armed([](fuse& f) { ... });
744  
        auto r2 = f([](fuse& f) { ... });
744  
        auto r2 = f([](fuse& f) { ... });
745  

745  

746  
        // Inline usage:
746  
        // Inline usage:
747  
        auto r3 = fuse()([](fuse& f) {
747  
        auto r3 = fuse()([](fuse& f) {
748  
            auto ec = f.maybe_fail();
748  
            auto ec = f.maybe_fail();
749  
            if(ec)
749  
            if(ec)
750  
                return;
750  
                return;
751  
        });
751  
        });
752  
        @endcode
752  
        @endcode
753  

753  

754  
        @see armed
754  
        @see armed
755  
    */
755  
    */
756  
    template<class F>
756  
    template<class F>
757  
    result
757  
    result
758  
    operator()(F&& fn)
758  
    operator()(F&& fn)
759  
    {
759  
    {
760  
        return armed(std::forward<F>(fn));
760  
        return armed(std::forward<F>(fn));
761  
    }
761  
    }
762  

762  

763  
    /** Alias for @ref armed (coroutine overload).
763  
    /** Alias for @ref armed (coroutine overload).
764  

764  

765  
        @see armed
765  
        @see armed
766  
    */
766  
    */
767  
    template<class F>
767  
    template<class F>
768  
        requires IoLaunchableTask<std::invoke_result_t<F, fuse&>>
768  
        requires IoLaunchableTask<std::invoke_result_t<F, fuse&>>
769  
    result
769  
    result
770  
    operator()(F&& fn)
770  
    operator()(F&& fn)
771  
    {
771  
    {
772  
        return armed(std::forward<F>(fn));
772  
        return armed(std::forward<F>(fn));
773  
    }
773  
    }
774  

774  

775  
    /** Run a test function once without failure injection.
775  
    /** Run a test function once without failure injection.
776  

776  

777  
        Invokes the provided function exactly once. Calls to
777  
        Invokes the provided function exactly once. Calls to
778  
        @ref maybe_fail always return an empty error code and
778  
        @ref maybe_fail always return an empty error code and
779  
        never throw. Only explicit calls to @ref fail can
779  
        never throw. Only explicit calls to @ref fail can
780  
        signal a test failure.
780  
        signal a test failure.
781  

781  

782  
        This is useful for running tests where you want to
782  
        This is useful for running tests where you want to
783  
        manually control failures, or for quick single-run
783  
        manually control failures, or for quick single-run
784  
        tests without systematic error injection.
784  
        tests without systematic error injection.
785  

785  

786  
        @par Example
786  
        @par Example
787  

787  

788  
        @code
788  
        @code
789  
        fuse f;
789  
        fuse f;
790  
        auto r = f.inert([](fuse& f) {
790  
        auto r = f.inert([](fuse& f) {
791  
            auto ec = f.maybe_fail();  // Always succeeds
791  
            auto ec = f.maybe_fail();  // Always succeeds
792  
            assert(!ec);
792  
            assert(!ec);
793  

793  

794  
            // Only way to signal failure:
794  
            // Only way to signal failure:
795  
            if(some_condition)
795  
            if(some_condition)
796  
            {
796  
            {
797  
                f.fail();
797  
                f.fail();
798  
                return;
798  
                return;
799  
            }
799  
            }
800  
        });
800  
        });
801  

801  

802  
        if(!r)
802  
        if(!r)
803  
        {
803  
        {
804  
            std::cerr << "Test failed at "
804  
            std::cerr << "Test failed at "
805  
                << r.loc.file_name() << ":"
805  
                << r.loc.file_name() << ":"
806  
                << r.loc.line() << "\n";
806  
                << r.loc.line() << "\n";
807  
        }
807  
        }
808  
        @endcode
808  
        @endcode
809  

809  

810  
        @param fn The test function to invoke. It receives
810  
        @param fn The test function to invoke. It receives
811  
        a reference to the fuse. Calls to @ref maybe_fail
811  
        a reference to the fuse. Calls to @ref maybe_fail
812  
        will always succeed.
812  
        will always succeed.
813  

813  

814  
        @return A @ref result indicating success or failure.
814  
        @return A @ref result indicating success or failure.
815  
        On failure, `result::loc` contains the source location
815  
        On failure, `result::loc` contains the source location
816  
        of the @ref fail call.
816  
        of the @ref fail call.
817  
    */
817  
    */
818  
    template<class F>
818  
    template<class F>
819  
    result
819  
    result
820  
    inert(F&& fn)
820  
    inert(F&& fn)
821  
    {
821  
    {
822  
        result r;
822  
        result r;
823  
        p_->inert = true;
823  
        p_->inert = true;
824  
        try
824  
        try
825  
        {
825  
        {
826  
            fn(*this);
826  
            fn(*this);
827  
        }
827  
        }
828  
        catch(...)
828  
        catch(...)
829  
        {
829  
        {
830  
            r.success = false;
830  
            r.success = false;
831  
            r.loc = p_->loc;
831  
            r.loc = p_->loc;
832  
            r.ep = std::current_exception();
832  
            r.ep = std::current_exception();
833  
            return r;
833  
            return r;
834  
        }
834  
        }
835  
        if(p_->stopped)
835  
        if(p_->stopped)
836  
        {
836  
        {
837  
            r.success = false;
837  
            r.success = false;
838  
            r.loc = p_->loc;
838  
            r.loc = p_->loc;
839  
            r.ep = p_->ep;
839  
            r.ep = p_->ep;
840  
        }
840  
        }
841  
        return r;
841  
        return r;
842  
    }
842  
    }
843  

843  

844  
    /** Run a coroutine test function once without failure injection.
844  
    /** Run a coroutine test function once without failure injection.
845  

845  

846  
        Invokes the provided coroutine function exactly once using
846  
        Invokes the provided coroutine function exactly once using
847  
        @ref run_blocking. Calls to @ref maybe_fail always return
847  
        @ref run_blocking. Calls to @ref maybe_fail always return
848  
        an empty error code and never throw. Only explicit calls
848  
        an empty error code and never throw. Only explicit calls
849  
        to @ref fail can signal a test failure.
849  
        to @ref fail can signal a test failure.
850  

850  

851  
        @par Example
851  
        @par Example
852  

852  

853  
        @code
853  
        @code
854  
        fuse f;
854  
        fuse f;
855  
        auto r = f.inert([](fuse& f) -> task<void> {
855  
        auto r = f.inert([](fuse& f) -> task<void> {
856  
            auto ec = f.maybe_fail();  // Always succeeds
856  
            auto ec = f.maybe_fail();  // Always succeeds
857  
            assert(!ec);
857  
            assert(!ec);
858  

858  

859  
            // Only way to signal failure:
859  
            // Only way to signal failure:
860  
            if(some_condition)
860  
            if(some_condition)
861  
            {
861  
            {
862  
                f.fail();
862  
                f.fail();
863  
                co_return;
863  
                co_return;
864  
            }
864  
            }
865  
        });
865  
        });
866  

866  

867  
        if(!r)
867  
        if(!r)
868  
        {
868  
        {
869  
            std::cerr << "Test failed at "
869  
            std::cerr << "Test failed at "
870  
                << r.loc.file_name() << ":"
870  
                << r.loc.file_name() << ":"
871  
                << r.loc.line() << "\n";
871  
                << r.loc.line() << "\n";
872  
        }
872  
        }
873  
        @endcode
873  
        @endcode
874  

874  

875  
        @param fn The coroutine test function to invoke. It receives
875  
        @param fn The coroutine test function to invoke. It receives
876  
        a reference to the fuse. Calls to @ref maybe_fail
876  
        a reference to the fuse. Calls to @ref maybe_fail
877  
        will always succeed.
877  
        will always succeed.
878  

878  

879  
        @return A @ref result indicating success or failure.
879  
        @return A @ref result indicating success or failure.
880  
        On failure, `result::loc` contains the source location
880  
        On failure, `result::loc` contains the source location
881  
        of the @ref fail call.
881  
        of the @ref fail call.
882  
    */
882  
    */
883  
    template<class F>
883  
    template<class F>
884  
        requires IoLaunchableTask<std::invoke_result_t<F, fuse&>>
884  
        requires IoLaunchableTask<std::invoke_result_t<F, fuse&>>
885  
    result
885  
    result
886  
    inert(F&& fn)
886  
    inert(F&& fn)
887  
    {
887  
    {
888  
        result r;
888  
        result r;
889  
        p_->inert = true;
889  
        p_->inert = true;
890  
        try
890  
        try
891  
        {
891  
        {
892  
            run_blocking()(fn(*this));
892  
            run_blocking()(fn(*this));
893  
        }
893  
        }
894  
        catch(...)
894  
        catch(...)
895  
        {
895  
        {
896  
            r.success = false;
896  
            r.success = false;
897  
            r.loc = p_->loc;
897  
            r.loc = p_->loc;
898  
            r.ep = std::current_exception();
898  
            r.ep = std::current_exception();
899  
            return r;
899  
            return r;
900  
        }
900  
        }
901  
        if(p_->stopped)
901  
        if(p_->stopped)
902  
        {
902  
        {
903  
            r.success = false;
903  
            r.success = false;
904  
            r.loc = p_->loc;
904  
            r.loc = p_->loc;
905  
            r.ep = p_->ep;
905  
            r.ep = p_->ep;
906  
        }
906  
        }
907  
        return r;
907  
        return r;
908  
    }
908  
    }
909  
};
909  
};
910  

910  

911  
} // test
911  
} // test
912  
} // capy
912  
} // capy
913  
} // boost
913  
} // boost
914  

914  

915  
#endif
915  
#endif