LCOV - code coverage report
Current view: top level - boost/capy/test - fuse.hpp (source / functions) Coverage Total Hit
Test: coverage_filtered.info Lines: 67.3 % 168 113
Test Date: 2026-01-30 23:43:15 Functions: 100.0 % 214 214

            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
        

Generated by: LCOV version 2.3