|
|
/**
* 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) );
|