/** * Convenience macros for creating sequences. * * For example, given: * * MAKE_SEQUENCE_FOR_2( APRPoolCreate * , apr_status_t * , apr_pool_create * , apr_pool_t**, new_pool * , apr_pool_t*, parent_pool * , apr_pool_t * , int ) * * The result is: * * struct APRPoolCreateArgs { apr_pool_t** new_pool; apr_pool_t* parent_pool; }; * * typedef Returner APRPoolCreateReturner; * typedef CallSequence APRPoolCreateSequence; * * struct APRPoolCreateFunction { * apr_status_t operator()(apr_pool_t** new_pool, apr_pool_t* parent_pool) { * return apr_pool_create(new_pool, parent_pool); * } * }; * * extern "C" { * apr_status_t override_apr_pool_create(apr_pool_t** new_pool, apr_pool_t* parent_pool); * } * * Importantly, you _must_ still do the actual override * #define yourself due to CPP limitations. And, naturally, * define the function itself in the .cpp. The defines * _must_ come after the macro because otherwise you will * override the function there as well (remember to use * extern "C".) So: * * // .hpp * * MAKE_SEQUENCE_FOR_2(...) * * #undef apr_pool_create * #define apr_pool_create(a, b) override_apr_pool_create((a), (b)) * * // .cpp * * extern "C" { * * } */ /* * So if I make the sequence for recv(), I can then do a few things * depending on what the override function itself does. For example, * here is the basic version: */ int override_recv(int fd, void* buffer, std::size_t length, int options) { RecvArgs r = { fd, buffer, length, options }; const RecvSequence::ReturnType& ret = RecvSequence::called_with(r); if( ret.return_value() == -1 ) { // Set errno to allow testing EINTR etc. if requested if( ret.error_number() > 0 ) { errno = ret.error_number(); } } // Fake the write to the buffer if data given else if ( ret.payload() ) { ret.payload()->copy(static_cast(buffer), ret.return_value()); } return ret.return_value(); } /* * So as you see, we can simulate error conditions, optionally set * errno using the info from the Returner instantiation instance. * OR we can fake a successful call which optionally allows returning * some predefined "data" for the caller of recv() to use. The Returners * are set up by defining a _sequence_ of returns (which may be zero * or more returns, the count is also checked.) * * For example: */ // Simulate single failed read RecvSequence::clear(); RecvSequence::next_return_sequence_is(-1, ECONNRESET); // Implicit coercion // Simulate two partial reads and then EOS RecvSequence::clear(); std::string foo("Hello "); std::string bar("world\n"); RecvSequence::next_return_sequence_is( RecvReturner(foo.length(), &foo) , RecvReturner(bar.length(), &bar) , RecvReturner(0) );