LCOV - code coverage report
Current view: top level - capy/test - fuse.hpp (source / functions) Coverage Total Hit Missed
Test: coverage_remapped.info Lines: 67.3 % 168 113 55
Test Date: 2026-02-17 18:14:47 Functions: 100.0 % 418 418

           TLA  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_runnable.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 HIT        3493 :     explicit operator bool() const noexcept
     220                 :     {
     221            3493 :         auto& s = *p_;
     222            3493 :         if(s.n == (std::numeric_limits<std::size_t>::max)())
     223                 :         {
     224                 :             // First call: start round 0
     225             736 :             s.n = 0;
     226             736 :             return true;
     227                 :         }
     228            2757 :         if(s.triggered)
     229                 :         {
     230                 :             // Previous round triggered, try next failure point
     231            2027 :             s.n++;
     232            2027 :             s.i = 0;
     233            2027 :             s.triggered = false;
     234            2027 :             return true;
     235                 :         }
     236                 :         // Test completed without trigger: success
     237             730 :         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             432 :     explicit fuse(std::error_code ec)
     303             432 :         : p_(std::make_shared<state>())
     304                 :     {
     305             432 :         p_->ec = ec;
     306             432 :     }
     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             430 :     fuse()
     331             430 :         : fuse(error::test_failure)
     332                 :     {
     333             430 :     }
     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            6107 :     maybe_fail(
     386                 :         std::source_location loc = std::source_location::current())
     387                 :     {
     388            6107 :         auto& s = *p_;
     389            6107 :         if(s.inert)
     390             231 :             return {};
     391            5876 :         if(s.i < s.n)
     392            5228 :             ++s.i;
     393            5876 :         if(s.i == s.n)
     394                 :         {
     395            2027 :             s.triggered = true;
     396            2027 :             s.loc = loc;
     397            2027 :             if(s.throws)
     398            1008 :                 throw std::system_error(s.ec);
     399            1019 :             return s.ec;
     400                 :         }
     401            3849 :         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              32 :     armed(F&& fn)
     540                 :     {
     541              32 :         result r;
     542                 : 
     543                 :         // Phase 1: error code mode
     544              32 :         p_->throws = false;
     545              32 :         p_->inert = false;
     546              32 :         p_->n = (std::numeric_limits<std::size_t>::max)();
     547              97 :         while(*this)
     548                 :         {
     549                 :             try
     550                 :             {
     551              71 :                 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              68 :             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              26 :         p_->throws = true;
     573              26 :         p_->n = (std::numeric_limits<std::size_t>::max)();
     574              26 :         p_->i = 0;
     575              26 :         p_->triggered = false;
     576              80 :         while(*this)
     577                 :         {
     578                 :             try
     579                 :             {
     580              54 :                 fn(*this);
     581                 :             }
     582              56 :             catch(std::system_error const& ex)
     583                 :             {
     584              28 :                 if(ex.code() != p_->ec)
     585                 :                 {
     586 MIS           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 HIT          54 :             if(p_->stopped)
     602                 :             {
     603 MIS           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 HIT          26 :         p_->inert = true;
     611              26 :         return r;
     612 MIS           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 IoRunnable
     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 IoRunnable<std::invoke_result_t<F, fuse&>>
     657                 :     result
     658 HIT         339 :     armed(F&& fn)
     659                 :     {
     660             339 :         result r;
     661                 : 
     662                 :         // Phase 1: error code mode
     663             339 :         p_->throws = false;
     664             339 :         p_->inert = false;
     665             339 :         p_->n = (std::numeric_limits<std::size_t>::max)();
     666            1658 :         while(*this)
     667                 :         {
     668                 :             try
     669                 :             {
     670            1319 :                 run_blocking()(fn(*this));
     671                 :             }
     672 MIS           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 HIT        1319 :             if(p_->stopped)
     681                 :             {
     682 MIS           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 HIT         339 :         p_->throws = true;
     692             339 :         p_->n = (std::numeric_limits<std::size_t>::max)();
     693             339 :         p_->i = 0;
     694             339 :         p_->triggered = false;
     695            1658 :         while(*this)
     696                 :         {
     697                 :             try
     698                 :             {
     699            3279 :                 run_blocking()(fn(*this));
     700                 :             }
     701            1960 :             catch(std::system_error const& ex)
     702                 :             {
     703             980 :                 if(ex.code() != p_->ec)
     704                 :                 {
     705 MIS           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 HIT        1319 :             if(p_->stopped)
     721                 :             {
     722 MIS           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 HIT         339 :         p_->inert = true;
     730             339 :         return r;
     731 MIS           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 HIT          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 IoRunnable<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 MIS           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 IoRunnable<std::invoke_result_t<F, fuse&>>
     885                 :     result
     886 HIT          17 :     inert(F&& fn)
     887                 :     {
     888              17 :         result r;
     889              17 :         p_->inert = true;
     890                 :         try
     891                 :         {
     892              17 :             run_blocking()(fn(*this));
     893                 :         }
     894 MIS           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 HIT          17 :         if(p_->stopped)
     902                 :         {
     903 MIS           0 :             r.success = false;
     904               0 :             r.loc = p_->loc;
     905               0 :             r.ep = p_->ep;
     906                 :         }
     907 HIT          17 :         return r;
     908 MIS           0 :     }
     909                 : };
     910                 : 
     911                 : } // test
     912                 : } // capy
     913                 : } // boost
     914                 : 
     915                 : #endif
        

Generated by: LCOV version 2.3