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-06-24 18:54:23 Functions: 100.0 % 426 426

           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        3551 :     explicit operator bool() const noexcept
     220                 :     {
     221            3551 :         auto& s = *p_;
     222            3551 :         if(s.n == (std::numeric_limits<std::size_t>::max)())
     223                 :         {
     224                 :             // First call: start round 0
     225             742 :             s.n = 0;
     226             742 :             return true;
     227                 :         }
     228            2809 :         if(s.triggered)
     229                 :         {
     230                 :             // Previous round triggered, try next failure point
     231            2073 :             s.n++;
     232            2073 :             s.i = 0;
     233            2073 :             s.triggered = false;
     234            2073 :             return true;
     235                 :         }
     236                 :         // Test completed without trigger: success
     237             736 :         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                 :         /// Source location of the failing point, set only on failure.
     269                 :         std::source_location loc = {};
     270                 : 
     271                 :         /// Exception captured by @ref fail, or null if none.
     272                 :         std::exception_ptr ep = nullptr;
     273                 : 
     274                 :         /// True if the test completed without a failure.
     275                 :         bool success = true;
     276                 : 
     277                 :         /// Return @ref success.
     278              64 :         constexpr explicit operator bool() const noexcept
     279                 :         {
     280              64 :             return success;
     281                 :         }
     282                 :     };
     283                 : 
     284                 :     /** Construct a fuse with a custom error code.
     285                 : 
     286                 :         @par Example
     287                 : 
     288                 :         @code
     289                 :         auto custom_ec = make_error_code(
     290                 :             std::errc::operation_canceled);
     291                 :         fuse f(custom_ec);
     292                 : 
     293                 :         std::error_code captured_ec;
     294                 :         auto r = f([&](fuse& f) {
     295                 :             auto ec = f.maybe_fail();
     296                 :             if(ec)
     297                 :             {
     298                 :                 captured_ec = ec;
     299                 :                 return;
     300                 :             }
     301                 :         });
     302                 : 
     303                 :         assert(captured_ec == custom_ec);
     304                 :         @endcode
     305                 : 
     306                 :         @param ec The error code to deliver at failure points.
     307                 :     */
     308             453 :     explicit fuse(std::error_code ec)
     309             453 :         : p_(std::make_shared<state>())
     310                 :     {
     311             453 :         p_->ec = ec;
     312             453 :     }
     313                 : 
     314                 :     /** Construct a fuse with the default error code.
     315                 : 
     316                 :         The default error code is `error::test_failure`.
     317                 : 
     318                 :         @par Example
     319                 : 
     320                 :         @code
     321                 :         fuse f;
     322                 :         std::error_code captured_ec;
     323                 : 
     324                 :         auto r = f([&](fuse& f) {
     325                 :             auto ec = f.maybe_fail();
     326                 :             if(ec)
     327                 :             {
     328                 :                 captured_ec = ec;
     329                 :                 return;
     330                 :             }
     331                 :         });
     332                 : 
     333                 :         assert(captured_ec == error::test_failure);
     334                 :         @endcode
     335                 :     */
     336             451 :     fuse()
     337             451 :         : fuse(error::test_failure)
     338                 :     {
     339             451 :     }
     340                 : 
     341                 :     /** Return an error or throw at the current failure point.
     342                 : 
     343                 :         When running under @ref armed, increments the internal
     344                 :         counter. When the counter reaches the current failure
     345                 :         point, returns the stored error code (or throws
     346                 :         `std::system_error` in exception mode) and records
     347                 :         the source location.
     348                 : 
     349                 :         When called outside of @ref armed or @ref inert (standalone
     350                 :         usage), or when running under @ref inert, always returns
     351                 :         an empty error code. This enables dependency injection
     352                 :         where the fuse is a no-op in production code.
     353                 : 
     354                 :         @par Example
     355                 : 
     356                 :         @code
     357                 :         fuse f;
     358                 :         auto r = f([](fuse& f) {
     359                 :             // Error code mode: returns the error
     360                 :             auto ec = f.maybe_fail();
     361                 :             if(ec)
     362                 :                 return;
     363                 : 
     364                 :             // Exception mode: throws system_error
     365                 :             ec = f.maybe_fail();
     366                 :             if(ec)
     367                 :                 return;
     368                 :         });
     369                 :         @endcode
     370                 : 
     371                 :         @par Standalone Usage
     372                 : 
     373                 :         @code
     374                 :         fuse f;
     375                 :         auto ec = f.maybe_fail();  // Always returns {} (no-op)
     376                 :         @endcode
     377                 : 
     378                 :         @param loc The source location of the call site,
     379                 :         captured automatically.
     380                 : 
     381                 :         @return The stored error code if at the failure point,
     382                 :         otherwise an empty error code. In exception mode,
     383                 :         throws instead of returning an error. When called
     384                 :         outside @ref armed, or when running under @ref inert,
     385                 :         always returns an empty error code.
     386                 : 
     387                 :         @throws std::system_error When in exception mode
     388                 :         and at the failure point (not thrown outside @ref armed).
     389                 :     */
     390                 :     std::error_code
     391            6435 :     maybe_fail(
     392                 :         std::source_location loc = std::source_location::current())
     393                 :     {
     394            6435 :         auto& s = *p_;
     395            6435 :         if(s.inert)
     396             233 :             return {};
     397            6202 :         if(s.i < s.n)
     398            5470 :             ++s.i;
     399            6202 :         if(s.i == s.n)
     400                 :         {
     401            2151 :             s.triggered = true;
     402            2151 :             s.loc = loc;
     403            2151 :             if(s.throws)
     404            1031 :                 throw std::system_error(s.ec);
     405            1120 :             return s.ec;
     406                 :         }
     407            4051 :         return {};
     408                 :     }
     409                 : 
     410                 :     /** Signal a test failure and stop execution.
     411                 : 
     412                 :         Call this from the test function to indicate a failure
     413                 :         condition. Both @ref armed and @ref inert will return
     414                 :         a failed @ref result immediately.
     415                 : 
     416                 :         @par Example
     417                 : 
     418                 :         @code
     419                 :         fuse f;
     420                 :         auto r = f([](fuse& f) {
     421                 :             auto ec = f.maybe_fail();
     422                 :             if(ec)
     423                 :                 return;
     424                 : 
     425                 :             // Explicit failure when a condition is not met
     426                 :             if(some_value != expected)
     427                 :             {
     428                 :                 f.fail();
     429                 :                 return;
     430                 :             }
     431                 :         });
     432                 : 
     433                 :         if(!r)
     434                 :         {
     435                 :             std::cerr << "Test failed at "
     436                 :                 << r.loc.file_name() << ":"
     437                 :                 << r.loc.line() << "\n";
     438                 :         }
     439                 :         @endcode
     440                 : 
     441                 :         @param loc The source location of the call site,
     442                 :         captured automatically.
     443                 :     */
     444                 :     void
     445               3 :     fail(
     446                 :         std::source_location loc =
     447                 :             std::source_location::current()) noexcept
     448                 :     {
     449               3 :         p_->loc = loc;
     450               3 :         p_->stopped = true;
     451               3 :     }
     452                 : 
     453                 :     /** Signal a test failure with an exception and stop execution.
     454                 : 
     455                 :         Call this from the test function to indicate a failure
     456                 :         condition with an associated exception. Both @ref armed
     457                 :         and @ref inert will return a failed @ref result with
     458                 :         the captured exception pointer.
     459                 : 
     460                 :         @par Example
     461                 : 
     462                 :         @code
     463                 :         fuse f;
     464                 :         auto r = f([](fuse& f) {
     465                 :             try
     466                 :             {
     467                 :                 do_something();
     468                 :             }
     469                 :             catch(...)
     470                 :             {
     471                 :                 f.fail(std::current_exception());
     472                 :                 return;
     473                 :             }
     474                 :         });
     475                 : 
     476                 :         if(!r)
     477                 :         {
     478                 :             try
     479                 :             {
     480                 :                 if(r.ep)
     481                 :                     std::rethrow_exception(r.ep);
     482                 :             }
     483                 :             catch(std::exception const& e)
     484                 :             {
     485                 :                 std::cerr << "Exception: " << e.what() << "\n";
     486                 :             }
     487                 :         }
     488                 :         @endcode
     489                 : 
     490                 :         @param ep The exception pointer to capture.
     491                 : 
     492                 :         @param loc The source location of the call site,
     493                 :         captured automatically.
     494                 :     */
     495                 :     void
     496               2 :     fail(
     497                 :         std::exception_ptr ep,
     498                 :         std::source_location loc =
     499                 :             std::source_location::current()) noexcept
     500                 :     {
     501               2 :         p_->ep = ep;
     502               2 :         p_->loc = loc;
     503               2 :         p_->stopped = true;
     504               2 :     }
     505                 : 
     506                 :     /** Run a test function with systematic failure injection.
     507                 : 
     508                 :         Repeatedly invokes the provided function, failing at
     509                 :         successive points until the function completes without
     510                 :         encountering a failure. First runs the complete loop
     511                 :         using error codes, then runs using exceptions.
     512                 : 
     513                 :         @par Example
     514                 : 
     515                 :         @code
     516                 :         fuse f;
     517                 :         auto r = f.armed([](fuse& f) {
     518                 :             auto ec = f.maybe_fail();
     519                 :             if(ec)
     520                 :                 return;
     521                 : 
     522                 :             ec = f.maybe_fail();
     523                 :             if(ec)
     524                 :                 return;
     525                 :         });
     526                 : 
     527                 :         if(!r)
     528                 :         {
     529                 :             std::cerr << "Failure at "
     530                 :                 << r.loc.file_name() << ":"
     531                 :                 << r.loc.line() << "\n";
     532                 :         }
     533                 :         @endcode
     534                 : 
     535                 :         @param fn The test function to invoke. It receives
     536                 :         a reference to the fuse and should call @ref maybe_fail
     537                 :         at each potential failure point.
     538                 : 
     539                 :         @return A @ref result indicating success or failure.
     540                 :         On failure, `result::loc` contains the source location
     541                 :         of the last @ref maybe_fail or @ref fail call.
     542                 :     */
     543                 :     template<class F>
     544                 :     result
     545              32 :     armed(F&& fn)
     546                 :     {
     547              32 :         result r;
     548                 : 
     549                 :         // Phase 1: error code mode
     550              32 :         p_->throws = false;
     551              32 :         p_->inert = false;
     552              32 :         p_->n = (std::numeric_limits<std::size_t>::max)();
     553              97 :         while(*this)
     554                 :         {
     555                 :             try
     556                 :             {
     557              71 :                 fn(*this);
     558                 :             }
     559               6 :             catch(...)
     560                 :             {
     561               3 :                 r.success = false;
     562               3 :                 r.loc = p_->loc;
     563               3 :                 r.ep = p_->ep;
     564               3 :                 p_->inert = true;
     565               3 :                 return r;
     566                 :             }
     567              68 :             if(p_->stopped)
     568                 :             {
     569               3 :                 r.success = false;
     570               3 :                 r.loc = p_->loc;
     571               3 :                 r.ep = p_->ep;
     572               3 :                 p_->inert = true;
     573               3 :                 return r;
     574                 :             }
     575                 :         }
     576                 : 
     577                 :         // Phase 2: exception mode
     578              26 :         p_->throws = true;
     579              26 :         p_->n = (std::numeric_limits<std::size_t>::max)();
     580              26 :         p_->i = 0;
     581              26 :         p_->triggered = false;
     582              80 :         while(*this)
     583                 :         {
     584                 :             try
     585                 :             {
     586              54 :                 fn(*this);
     587                 :             }
     588              56 :             catch(std::system_error const& ex)
     589                 :             {
     590              28 :                 if(ex.code() != p_->ec)
     591                 :                 {
     592 MIS           0 :                     r.success = false;
     593               0 :                     r.loc = p_->loc;
     594               0 :                     r.ep = p_->ep;
     595               0 :                     p_->inert = true;
     596               0 :                     return r;
     597                 :                 }
     598                 :             }
     599               0 :             catch(...)
     600                 :             {
     601               0 :                 r.success = false;
     602               0 :                 r.loc = p_->loc;
     603               0 :                 r.ep = p_->ep;
     604               0 :                 p_->inert = true;
     605               0 :                 return r;
     606                 :             }
     607 HIT          54 :             if(p_->stopped)
     608                 :             {
     609 MIS           0 :                 r.success = false;
     610               0 :                 r.loc = p_->loc;
     611               0 :                 r.ep = p_->ep;
     612               0 :                 p_->inert = true;
     613               0 :                 return r;
     614                 :             }
     615                 :         }
     616 HIT          26 :         p_->inert = true;
     617              26 :         return r;
     618 MIS           0 :     }
     619                 : 
     620                 :     /** Run a coroutine test function with systematic failure injection.
     621                 : 
     622                 :         Repeatedly invokes the provided coroutine function, failing at
     623                 :         successive points until the function completes without
     624                 :         encountering a failure. First runs the complete loop
     625                 :         using error codes, then runs using exceptions.
     626                 : 
     627                 :         This overload handles lambdas that return an @ref IoRunnable
     628                 :         (such as `task<void>`), executing them synchronously via
     629                 :         @ref run_blocking.
     630                 : 
     631                 :         @par Example
     632                 : 
     633                 :         @code
     634                 :         fuse f;
     635                 :         auto r = f.armed([&](fuse&) -> task<void> {
     636                 :             auto ec = f.maybe_fail();
     637                 :             if(ec)
     638                 :                 co_return;
     639                 : 
     640                 :             ec = f.maybe_fail();
     641                 :             if(ec)
     642                 :                 co_return;
     643                 :         });
     644                 : 
     645                 :         if(!r)
     646                 :         {
     647                 :             std::cerr << "Failure at "
     648                 :                 << r.loc.file_name() << ":"
     649                 :                 << r.loc.line() << "\n";
     650                 :         }
     651                 :         @endcode
     652                 : 
     653                 :         @param fn The coroutine test function to invoke. It receives
     654                 :         a reference to the fuse and should call @ref maybe_fail
     655                 :         at each potential failure point.
     656                 : 
     657                 :         @return A @ref result indicating success or failure.
     658                 :         On failure, `result::loc` contains the source location
     659                 :         of the last @ref maybe_fail or @ref fail call.
     660                 :     */
     661                 :     template<class F>
     662                 :         requires IoRunnable<std::invoke_result_t<F, fuse&>>
     663                 :     result
     664 HIT         342 :     armed(F&& fn)
     665                 :     {
     666             342 :         result r;
     667                 : 
     668                 :         // Phase 1: error code mode
     669             342 :         p_->throws = false;
     670             342 :         p_->inert = false;
     671             342 :         p_->n = (std::numeric_limits<std::size_t>::max)();
     672            1687 :         while(*this)
     673                 :         {
     674                 :             try
     675                 :             {
     676            1345 :                 run_blocking()(fn(*this));
     677                 :             }
     678 MIS           0 :             catch(...)
     679                 :             {
     680               0 :                 r.success = false;
     681               0 :                 r.loc = p_->loc;
     682               0 :                 r.ep = p_->ep;
     683               0 :                 p_->inert = true;
     684               0 :                 return r;
     685                 :             }
     686 HIT        1345 :             if(p_->stopped)
     687                 :             {
     688 MIS           0 :                 r.success = false;
     689               0 :                 r.loc = p_->loc;
     690               0 :                 r.ep = p_->ep;
     691               0 :                 p_->inert = true;
     692               0 :                 return r;
     693                 :             }
     694                 :         }
     695                 : 
     696                 :         // Phase 2: exception mode
     697 HIT         342 :         p_->throws = true;
     698             342 :         p_->n = (std::numeric_limits<std::size_t>::max)();
     699             342 :         p_->i = 0;
     700             342 :         p_->triggered = false;
     701            1687 :         while(*this)
     702                 :         {
     703                 :             try
     704                 :             {
     705            3351 :                 run_blocking()(fn(*this));
     706                 :             }
     707            2006 :             catch(std::system_error const& ex)
     708                 :             {
     709            1003 :                 if(ex.code() != p_->ec)
     710                 :                 {
     711 MIS           0 :                     r.success = false;
     712               0 :                     r.loc = p_->loc;
     713               0 :                     r.ep = p_->ep;
     714               0 :                     p_->inert = true;
     715               0 :                     return r;
     716                 :                 }
     717                 :             }
     718               0 :             catch(...)
     719                 :             {
     720               0 :                 r.success = false;
     721               0 :                 r.loc = p_->loc;
     722               0 :                 r.ep = p_->ep;
     723               0 :                 p_->inert = true;
     724               0 :                 return r;
     725                 :             }
     726 HIT        1345 :             if(p_->stopped)
     727                 :             {
     728 MIS           0 :                 r.success = false;
     729               0 :                 r.loc = p_->loc;
     730               0 :                 r.ep = p_->ep;
     731               0 :                 p_->inert = true;
     732               0 :                 return r;
     733                 :             }
     734                 :         }
     735 HIT         342 :         p_->inert = true;
     736             342 :         return r;
     737 MIS           0 :     }
     738                 : 
     739                 :     /** Alias for @ref armed.
     740                 : 
     741                 :         Allows the fuse to be invoked directly as a function
     742                 :         object for more concise syntax.
     743                 : 
     744                 :         @par Example
     745                 : 
     746                 :         @code
     747                 :         // These are equivalent:
     748                 :         fuse f;
     749                 :         auto r1 = f.armed([](fuse& f) { ... });
     750                 :         auto r2 = f([](fuse& f) { ... });
     751                 : 
     752                 :         // Inline usage:
     753                 :         auto r3 = fuse()([](fuse& f) {
     754                 :             auto ec = f.maybe_fail();
     755                 :             if(ec)
     756                 :                 return;
     757                 :         });
     758                 :         @endcode
     759                 : 
     760                 :         @see armed
     761                 :     */
     762                 :     template<class F>
     763                 :     result
     764 HIT          15 :     operator()(F&& fn)
     765                 :     {
     766              15 :         return armed(std::forward<F>(fn));
     767                 :     }
     768                 : 
     769                 :     /** Alias for @ref armed (coroutine overload).
     770                 : 
     771                 :         @see armed
     772                 :     */
     773                 :     template<class F>
     774                 :         requires IoRunnable<std::invoke_result_t<F, fuse&>>
     775                 :     result
     776                 :     operator()(F&& fn)
     777                 :     {
     778                 :         return armed(std::forward<F>(fn));
     779                 :     }
     780                 : 
     781                 :     /** Run a test function once without failure injection.
     782                 : 
     783                 :         Invokes the provided function exactly once. Calls to
     784                 :         @ref maybe_fail always return an empty error code and
     785                 :         never throw. Only explicit calls to @ref fail can
     786                 :         signal a test failure.
     787                 : 
     788                 :         This is useful for running tests where you want to
     789                 :         manually control failures, or for quick single-run
     790                 :         tests without systematic error injection.
     791                 : 
     792                 :         @par Example
     793                 : 
     794                 :         @code
     795                 :         fuse f;
     796                 :         auto r = f.inert([](fuse& f) {
     797                 :             auto ec = f.maybe_fail();  // Always succeeds
     798                 :             assert(!ec);
     799                 : 
     800                 :             // Only way to signal failure:
     801                 :             if(some_condition)
     802                 :             {
     803                 :                 f.fail();
     804                 :                 return;
     805                 :             }
     806                 :         });
     807                 : 
     808                 :         if(!r)
     809                 :         {
     810                 :             std::cerr << "Test failed at "
     811                 :                 << r.loc.file_name() << ":"
     812                 :                 << r.loc.line() << "\n";
     813                 :         }
     814                 :         @endcode
     815                 : 
     816                 :         @param fn The test function to invoke. It receives
     817                 :         a reference to the fuse. Calls to @ref maybe_fail
     818                 :         will always succeed.
     819                 : 
     820                 :         @return A @ref result indicating success or failure.
     821                 :         On failure, `result::loc` contains the source location
     822                 :         of the @ref fail call.
     823                 :     */
     824                 :     template<class F>
     825                 :     result
     826               8 :     inert(F&& fn)
     827                 :     {
     828               8 :         result r;
     829               8 :         p_->inert = true;
     830                 :         try
     831                 :         {
     832               8 :             fn(*this);
     833                 :         }
     834               2 :         catch(...)
     835                 :         {
     836               1 :             r.success = false;
     837               1 :             r.loc = p_->loc;
     838               1 :             r.ep = std::current_exception();
     839               1 :             return r;
     840                 :         }
     841               7 :         if(p_->stopped)
     842                 :         {
     843               2 :             r.success = false;
     844               2 :             r.loc = p_->loc;
     845               2 :             r.ep = p_->ep;
     846                 :         }
     847               7 :         return r;
     848 MIS           0 :     }
     849                 : 
     850                 :     /** Run a coroutine test function once without failure injection.
     851                 : 
     852                 :         Invokes the provided coroutine function exactly once using
     853                 :         @ref run_blocking. Calls to @ref maybe_fail always return
     854                 :         an empty error code and never throw. Only explicit calls
     855                 :         to @ref fail can signal a test failure.
     856                 : 
     857                 :         @par Example
     858                 : 
     859                 :         @code
     860                 :         fuse f;
     861                 :         auto r = f.inert([](fuse& f) -> task<void> {
     862                 :             auto ec = f.maybe_fail();  // Always succeeds
     863                 :             assert(!ec);
     864                 : 
     865                 :             // Only way to signal failure:
     866                 :             if(some_condition)
     867                 :             {
     868                 :                 f.fail();
     869                 :                 co_return;
     870                 :             }
     871                 :         });
     872                 : 
     873                 :         if(!r)
     874                 :         {
     875                 :             std::cerr << "Test failed at "
     876                 :                 << r.loc.file_name() << ":"
     877                 :                 << r.loc.line() << "\n";
     878                 :         }
     879                 :         @endcode
     880                 : 
     881                 :         @param fn The coroutine test function to invoke. It receives
     882                 :         a reference to the fuse. Calls to @ref maybe_fail
     883                 :         will always succeed.
     884                 : 
     885                 :         @return A @ref result indicating success or failure.
     886                 :         On failure, `result::loc` contains the source location
     887                 :         of the @ref fail call.
     888                 :     */
     889                 :     template<class F>
     890                 :         requires IoRunnable<std::invoke_result_t<F, fuse&>>
     891                 :     result
     892 HIT          22 :     inert(F&& fn)
     893                 :     {
     894              22 :         result r;
     895              22 :         p_->inert = true;
     896                 :         try
     897                 :         {
     898              22 :             run_blocking()(fn(*this));
     899                 :         }
     900 MIS           0 :         catch(...)
     901                 :         {
     902               0 :             r.success = false;
     903               0 :             r.loc = p_->loc;
     904               0 :             r.ep = std::current_exception();
     905               0 :             return r;
     906                 :         }
     907 HIT          22 :         if(p_->stopped)
     908                 :         {
     909 MIS           0 :             r.success = false;
     910               0 :             r.loc = p_->loc;
     911               0 :             r.ep = p_->ep;
     912                 :         }
     913 HIT          22 :         return r;
     914 MIS           0 :     }
     915                 : };
     916                 : 
     917                 : } // test
     918                 : } // capy
     919                 : } // boost
     920                 : 
     921                 : #endif
        

Generated by: LCOV version 2.3