thrift/lib/cpp/test/TransportTest.cpp

1050 lines
34 KiB
C++
Raw Normal View History

/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#ifndef _GNU_SOURCE
#define _GNU_SOURCE // needed for getopt_long
#endif
#include <stdlib.h>
#include <time.h>
#include <unistd.h>
#include <getopt.h>
THRIFT-929. cpp: Add tests to verify blocking read behavior Add tests that check to see whether or not read() and borrow() block when called with a length larger than the amount of data currently available. At the moment, not all of the transports behave the same way. I believe the desired behavior is: When M bytes are available, and 0 < M < N: - read(N): return M bytes immediately - borrow(N): return NULL immediately When 0 bytes are available: - read(N): In this case, it is acceptable either to immediately return 0, or to block until some data is available. If the transport blocks, it returns immediately when some date becomes available, even if less than N bytes are available. - borrow(N): return NULL immediately - The borrow() tests fail when using TBufferedTransport. TBufferedTransport incorrectly blocks until the amount of data requested is available. - test_read_none_available() fails when using TFramedTransport. Calling read() on a TFramedTransport when no data is available throws an exception instead of returning 0. - test_read_none_available() fails when using TFDTransport. This is partly just an artifact of the fact that I use SIGALRM as part of this test. Unlike TSocket, TFDTransport doesn't retry after EINTR. - test_read_part_available() fails when using TZlibTransport around a transport that has blocking read() behavior. TZlibTransport::read() loops calling read() on the underlying transport. It should probably break out of the loop and return to the caller as soon as it has uncompressed any data, even if it is less than requested and more might be available. Once some data has been uncompressed, TZlibTransport cannot risk calling read() again since it might block. Will commit fixes for these separately. git-svn-id: https://svn.apache.org/repos/asf/incubator/thrift/trunk@1005160 13f79535-47bb-0310-9956-ffa450edef68
2010-10-06 17:10:38 +00:00
#include <signal.h>
#include <sstream>
#include <tr1/functional>
#include <boost/mpl/list.hpp>
#include <boost/shared_array.hpp>
#include <boost/random.hpp>
#include <boost/type_traits.hpp>
#include <boost/test/unit_test.hpp>
#include <transport/TBufferTransports.h>
#include <transport/TFDTransport.h>
#include <transport/TFileTransport.h>
#include <transport/TZlibTransport.h>
#include <transport/TSocket.h>
using namespace apache::thrift::transport;
static boost::mt19937 rng;
static const char* tmp_dir = "/tmp";
void initrand(unsigned int seed) {
rng.seed(seed);
}
class SizeGenerator {
public:
virtual ~SizeGenerator() {}
virtual uint32_t nextSize() = 0;
virtual std::string describe() const = 0;
};
class ConstantSizeGenerator : public SizeGenerator {
public:
ConstantSizeGenerator(uint32_t value) : value_(value) {}
uint32_t nextSize() { return value_; }
std::string describe() const {
std::ostringstream desc;
desc << value_;
return desc.str();
}
private:
uint32_t value_;
};
class RandomSizeGenerator : public SizeGenerator {
public:
RandomSizeGenerator(uint32_t min, uint32_t max) :
generator_(rng, boost::uniform_int<int>(min, max)) {}
uint32_t nextSize() { return generator_(); }
std::string describe() const {
std::ostringstream desc;
desc << "rand(" << getMin() << ", " << getMax() << ")";
return desc.str();
}
uint32_t getMin() const { return generator_.distribution().min(); }
uint32_t getMax() const { return generator_.distribution().max(); }
private:
boost::variate_generator< boost::mt19937&, boost::uniform_int<int> >
generator_;
};
/**
* This class exists solely to make the TEST_RW() macro easier to use.
* - it can be constructed implicitly from an integer
* - it can contain either a ConstantSizeGenerator or a RandomSizeGenerator
* (TEST_RW can't take a SizeGenerator pointer or reference, since it needs
* to make a copy of the generator to bind it to the test function.)
*/
class GenericSizeGenerator : public SizeGenerator {
public:
GenericSizeGenerator(uint32_t value) :
generator_(new ConstantSizeGenerator(value)) {}
GenericSizeGenerator(uint32_t min, uint32_t max) :
generator_(new RandomSizeGenerator(min, max)) {}
uint32_t nextSize() { return generator_->nextSize(); }
std::string describe() const { return generator_->describe(); }
private:
boost::shared_ptr<SizeGenerator> generator_;
};
/**************************************************************************
* Classes to set up coupled transports
**************************************************************************/
/**
* Helper class to represent a coupled pair of transports.
*
* Data written to the out transport can be read from the in transport.
*
* This is used as the base class for the various coupled transport
* implementations. It shouldn't be instantiated directly.
*/
template <class Transport_>
class CoupledTransports {
public:
typedef Transport_ TransportType;
CoupledTransports() : in(), out() {}
boost::shared_ptr<Transport_> in;
boost::shared_ptr<Transport_> out;
private:
CoupledTransports(const CoupledTransports&);
CoupledTransports &operator=(const CoupledTransports&);
};
/**
* Coupled TMemoryBuffers
*/
class CoupledMemoryBuffers : public CoupledTransports<TMemoryBuffer> {
public:
CoupledMemoryBuffers() :
buf(new TMemoryBuffer) {
in = buf;
out = buf;
}
boost::shared_ptr<TMemoryBuffer> buf;
};
/**
* Helper template class for creating coupled transports that wrap
* another transport.
*/
template <class WrapperTransport_, class InnerCoupledTransports_>
class CoupledWrapperTransportsT : public CoupledTransports<WrapperTransport_> {
public:
CoupledWrapperTransportsT() {
if (inner_.in) {
this->in.reset(new WrapperTransport_(inner_.in));
}
if (inner_.out) {
this->out.reset(new WrapperTransport_(inner_.out));
}
}
InnerCoupledTransports_ inner_;
};
/**
* Coupled TBufferedTransports.
*/
template <class InnerTransport_>
class CoupledBufferedTransportsT :
public CoupledWrapperTransportsT<TBufferedTransport, InnerTransport_> {
};
typedef CoupledBufferedTransportsT<CoupledMemoryBuffers>
CoupledBufferedTransports;
/**
* Coupled TFramedTransports.
*/
template <class InnerTransport_>
class CoupledFramedTransportsT :
public CoupledWrapperTransportsT<TFramedTransport, InnerTransport_> {
};
typedef CoupledFramedTransportsT<CoupledMemoryBuffers>
CoupledFramedTransports;
/**
* Coupled TZlibTransports.
*/
template <class InnerTransport_>
class CoupledZlibTransportsT :
public CoupledWrapperTransportsT<TZlibTransport, InnerTransport_> {
};
typedef CoupledZlibTransportsT<CoupledMemoryBuffers>
CoupledZlibTransports;
/**
* Coupled TFDTransports.
*/
class CoupledFDTransports : public CoupledTransports<TFDTransport> {
public:
CoupledFDTransports() {
int pipes[2];
if (pipe(pipes) != 0) {
return;
}
in.reset(new TFDTransport(pipes[0], TFDTransport::CLOSE_ON_DESTROY));
out.reset(new TFDTransport(pipes[1], TFDTransport::CLOSE_ON_DESTROY));
}
};
/**
* Coupled TSockets
*/
class CoupledSocketTransports : public CoupledTransports<TSocket> {
public:
CoupledSocketTransports() {
int sockets[2];
if (socketpair(PF_UNIX, SOCK_STREAM, 0, sockets) != 0) {
return;
}
in.reset(new TSocket(sockets[0]));
out.reset(new TSocket(sockets[1]));
}
};
/**
* Coupled TFileTransports
*/
class CoupledFileTransports : public CoupledTransports<TFileTransport> {
public:
CoupledFileTransports() {
// Create a temporary file to use
size_t filename_len = strlen(tmp_dir) + 32;
filename = new char[filename_len];
snprintf(filename, filename_len,
"%s/thrift.transport_test.XXXXXX", tmp_dir);
fd = mkstemp(filename);
if (fd < 0) {
return;
}
in.reset(new TFileTransport(filename, true));
out.reset(new TFileTransport(filename));
}
~CoupledFileTransports() {
if (fd >= 0) {
close(fd);
unlink(filename);
}
delete[] filename;
}
char* filename;
int fd;
};
/**
* Wrapper around another CoupledTransports implementation that exposes the
* transports as TTransport pointers.
*
* This is used since accessing a transport via a "TTransport*" exercises a
* different code path than using the base pointer class. As part of the
* template code changes, most transport methods are no longer virtual.
*/
template <class CoupledTransports_>
class CoupledTTransports : public CoupledTransports<TTransport> {
public:
CoupledTTransports() : transports() {
in = transports.in;
out = transports.out;
}
CoupledTransports_ transports;
};
/**
* Wrapper around another CoupledTransports implementation that exposes the
* transports as TBufferBase pointers.
*
* This can only be instantiated with a transport type that is a subclass of
* TBufferBase.
*/
template <class CoupledTransports_>
class CoupledBufferBases : public CoupledTransports<TBufferBase> {
public:
CoupledBufferBases() : transports() {
in = transports.in;
out = transports.out;
}
CoupledTransports_ transports;
};
/**************************************************************************
THRIFT-929. cpp: Add tests to verify blocking read behavior Add tests that check to see whether or not read() and borrow() block when called with a length larger than the amount of data currently available. At the moment, not all of the transports behave the same way. I believe the desired behavior is: When M bytes are available, and 0 < M < N: - read(N): return M bytes immediately - borrow(N): return NULL immediately When 0 bytes are available: - read(N): In this case, it is acceptable either to immediately return 0, or to block until some data is available. If the transport blocks, it returns immediately when some date becomes available, even if less than N bytes are available. - borrow(N): return NULL immediately - The borrow() tests fail when using TBufferedTransport. TBufferedTransport incorrectly blocks until the amount of data requested is available. - test_read_none_available() fails when using TFramedTransport. Calling read() on a TFramedTransport when no data is available throws an exception instead of returning 0. - test_read_none_available() fails when using TFDTransport. This is partly just an artifact of the fact that I use SIGALRM as part of this test. Unlike TSocket, TFDTransport doesn't retry after EINTR. - test_read_part_available() fails when using TZlibTransport around a transport that has blocking read() behavior. TZlibTransport::read() loops calling read() on the underlying transport. It should probably break out of the loop and return to the caller as soon as it has uncompressed any data, even if it is less than requested and more might be available. Once some data has been uncompressed, TZlibTransport cannot risk calling read() again since it might block. Will commit fixes for these separately. git-svn-id: https://svn.apache.org/repos/asf/incubator/thrift/trunk@1005160 13f79535-47bb-0310-9956-ffa450edef68
2010-10-06 17:10:38 +00:00
* Alarm handling code for use in tests that check the transport blocking
* semantics.
*
* If the transport ends up blocking, we don't want to hang forever. We use
* SIGALRM to fire schedule signal to wake up and try to write data so the
* transport will unblock.
*
* It isn't really the safest thing in the world to be mucking around with
* complicated global data structures in a signal handler. It should probably
* be okay though, since we know the main thread should always be blocked in a
* read() request when the signal handler is running.
**************************************************************************/
struct TriggerInfo {
TriggerInfo(int seconds, const boost::shared_ptr<TTransport>& transport,
uint32_t writeLength) :
timeoutSeconds(seconds),
transport(transport),
writeLength(writeLength),
next(NULL) {}
int timeoutSeconds;
boost::shared_ptr<TTransport> transport;
uint32_t writeLength;
TriggerInfo* next;
};
TriggerInfo* triggerInfo;
unsigned int numTriggersFired;
void set_alarm();
void alarm_handler(int signum) {
// The alarm timed out, which almost certainly means we're stuck
// on a transport that is incorrectly blocked.
++numTriggersFired;
// Note: we print messages to stdout instead of stderr, since
// tools/test/runner only records stdout messages in the failure messages for
// boost tests. (boost prints its test info to stdout.)
printf("Timeout alarm expired; attempting to unblock transport\n");
if (triggerInfo == NULL) {
printf(" trigger stack is empty!\n");
}
// Pop off the first TriggerInfo.
// If there is another one, schedule an alarm for it.
TriggerInfo* info = triggerInfo;
triggerInfo = info->next;
set_alarm();
// Write some data to the transport to hopefully unblock it.
uint8_t* buf = new uint8_t[info->writeLength];
THRIFT-929. cpp: Add tests to verify blocking read behavior Add tests that check to see whether or not read() and borrow() block when called with a length larger than the amount of data currently available. At the moment, not all of the transports behave the same way. I believe the desired behavior is: When M bytes are available, and 0 < M < N: - read(N): return M bytes immediately - borrow(N): return NULL immediately When 0 bytes are available: - read(N): In this case, it is acceptable either to immediately return 0, or to block until some data is available. If the transport blocks, it returns immediately when some date becomes available, even if less than N bytes are available. - borrow(N): return NULL immediately - The borrow() tests fail when using TBufferedTransport. TBufferedTransport incorrectly blocks until the amount of data requested is available. - test_read_none_available() fails when using TFramedTransport. Calling read() on a TFramedTransport when no data is available throws an exception instead of returning 0. - test_read_none_available() fails when using TFDTransport. This is partly just an artifact of the fact that I use SIGALRM as part of this test. Unlike TSocket, TFDTransport doesn't retry after EINTR. - test_read_part_available() fails when using TZlibTransport around a transport that has blocking read() behavior. TZlibTransport::read() loops calling read() on the underlying transport. It should probably break out of the loop and return to the caller as soon as it has uncompressed any data, even if it is less than requested and more might be available. Once some data has been uncompressed, TZlibTransport cannot risk calling read() again since it might block. Will commit fixes for these separately. git-svn-id: https://svn.apache.org/repos/asf/incubator/thrift/trunk@1005160 13f79535-47bb-0310-9956-ffa450edef68
2010-10-06 17:10:38 +00:00
memset(buf, 'b', info->writeLength);
boost::scoped_array<uint8_t> array(buf);
THRIFT-929. cpp: Add tests to verify blocking read behavior Add tests that check to see whether or not read() and borrow() block when called with a length larger than the amount of data currently available. At the moment, not all of the transports behave the same way. I believe the desired behavior is: When M bytes are available, and 0 < M < N: - read(N): return M bytes immediately - borrow(N): return NULL immediately When 0 bytes are available: - read(N): In this case, it is acceptable either to immediately return 0, or to block until some data is available. If the transport blocks, it returns immediately when some date becomes available, even if less than N bytes are available. - borrow(N): return NULL immediately - The borrow() tests fail when using TBufferedTransport. TBufferedTransport incorrectly blocks until the amount of data requested is available. - test_read_none_available() fails when using TFramedTransport. Calling read() on a TFramedTransport when no data is available throws an exception instead of returning 0. - test_read_none_available() fails when using TFDTransport. This is partly just an artifact of the fact that I use SIGALRM as part of this test. Unlike TSocket, TFDTransport doesn't retry after EINTR. - test_read_part_available() fails when using TZlibTransport around a transport that has blocking read() behavior. TZlibTransport::read() loops calling read() on the underlying transport. It should probably break out of the loop and return to the caller as soon as it has uncompressed any data, even if it is less than requested and more might be available. Once some data has been uncompressed, TZlibTransport cannot risk calling read() again since it might block. Will commit fixes for these separately. git-svn-id: https://svn.apache.org/repos/asf/incubator/thrift/trunk@1005160 13f79535-47bb-0310-9956-ffa450edef68
2010-10-06 17:10:38 +00:00
info->transport->write(buf, info->writeLength);
info->transport->flush();
delete info;
}
void set_alarm() {
if (triggerInfo == NULL) {
// clear any alarm
alarm(0);
return;
}
struct sigaction action;
memset(&action, 0, sizeof(action));
action.sa_handler = alarm_handler;
action.sa_flags = SA_RESETHAND;
THRIFT-929. cpp: Add tests to verify blocking read behavior Add tests that check to see whether or not read() and borrow() block when called with a length larger than the amount of data currently available. At the moment, not all of the transports behave the same way. I believe the desired behavior is: When M bytes are available, and 0 < M < N: - read(N): return M bytes immediately - borrow(N): return NULL immediately When 0 bytes are available: - read(N): In this case, it is acceptable either to immediately return 0, or to block until some data is available. If the transport blocks, it returns immediately when some date becomes available, even if less than N bytes are available. - borrow(N): return NULL immediately - The borrow() tests fail when using TBufferedTransport. TBufferedTransport incorrectly blocks until the amount of data requested is available. - test_read_none_available() fails when using TFramedTransport. Calling read() on a TFramedTransport when no data is available throws an exception instead of returning 0. - test_read_none_available() fails when using TFDTransport. This is partly just an artifact of the fact that I use SIGALRM as part of this test. Unlike TSocket, TFDTransport doesn't retry after EINTR. - test_read_part_available() fails when using TZlibTransport around a transport that has blocking read() behavior. TZlibTransport::read() loops calling read() on the underlying transport. It should probably break out of the loop and return to the caller as soon as it has uncompressed any data, even if it is less than requested and more might be available. Once some data has been uncompressed, TZlibTransport cannot risk calling read() again since it might block. Will commit fixes for these separately. git-svn-id: https://svn.apache.org/repos/asf/incubator/thrift/trunk@1005160 13f79535-47bb-0310-9956-ffa450edef68
2010-10-06 17:10:38 +00:00
sigemptyset(&action.sa_mask);
sigaction(SIGALRM, &action, NULL);
alarm(triggerInfo->timeoutSeconds);
}
/**
* Add a trigger to be scheduled "seconds" seconds after the
* last currently scheduled trigger.
*
* (Note that this is not "seconds" from now. That might be more logical, but
* would require slightly more complicated sorting, rather than just appending
* to the end.)
*/
void add_trigger(unsigned int seconds,
const boost::shared_ptr<TTransport> &transport,
uint32_t write_len) {
TriggerInfo* info = new TriggerInfo(seconds, transport, write_len);
if (triggerInfo == NULL) {
// This is the first trigger.
// Set triggerInfo, and schedule the alarm
triggerInfo = info;
set_alarm();
} else {
// Add this trigger to the end of the list
TriggerInfo* prev = triggerInfo;
while (prev->next) {
prev = prev->next;
}
prev->next = info;
}
}
void clear_triggers() {
TriggerInfo *info = triggerInfo;
alarm(0);
triggerInfo = NULL;
numTriggersFired = 0;
while (info != NULL) {
TriggerInfo* next = info->next;
delete info;
info = next;
}
}
void set_trigger(unsigned int seconds,
const boost::shared_ptr<TTransport> &transport,
uint32_t write_len) {
clear_triggers();
add_trigger(seconds, transport, write_len);
}
/**************************************************************************
* Test functions
**************************************************************************/
/**
* Test interleaved write and read calls.
*
* Generates a buffer totalSize bytes long, then writes it to the transport,
* and verifies the written data can be read back correctly.
*
* Mode of operation:
* - call wChunkGenerator to figure out how large of a chunk to write
* - call wSizeGenerator to get the size for individual write() calls,
* and do this repeatedly until the entire chunk is written.
* - call rChunkGenerator to figure out how large of a chunk to read
* - call rSizeGenerator to get the size for individual read() calls,
* and do this repeatedly until the entire chunk is read.
* - repeat until the full buffer is written and read back,
* then compare the data read back against the original buffer
*
*
* - If any of the size generators return 0, this means to use the maximum
* possible size.
*
* - If maxOutstanding is non-zero, write chunk sizes will be chosen such that
* there are never more than maxOutstanding bytes waiting to be read back.
*/
template <class CoupledTransports>
void test_rw(uint32_t totalSize,
SizeGenerator& wSizeGenerator,
SizeGenerator& rSizeGenerator,
SizeGenerator& wChunkGenerator,
SizeGenerator& rChunkGenerator,
uint32_t maxOutstanding) {
CoupledTransports transports;
BOOST_REQUIRE(transports.in != NULL);
BOOST_REQUIRE(transports.out != NULL);
boost::shared_array<uint8_t> wbuf =
boost::shared_array<uint8_t>(new uint8_t[totalSize]);
boost::shared_array<uint8_t> rbuf =
boost::shared_array<uint8_t>(new uint8_t[totalSize]);
// store some data in wbuf
for (uint32_t n = 0; n < totalSize; ++n) {
wbuf[n] = (n & 0xff);
}
// clear rbuf
memset(rbuf.get(), 0, totalSize);
uint32_t total_written = 0;
uint32_t total_read = 0;
while (total_read < totalSize) {
// Determine how large a chunk of data to write
uint32_t wchunk_size = wChunkGenerator.nextSize();
if (wchunk_size == 0 || wchunk_size > totalSize - total_written) {
wchunk_size = totalSize - total_written;
}
// Make sure (total_written - total_read) + wchunk_size
// is less than maxOutstanding
if (maxOutstanding > 0 &&
wchunk_size > maxOutstanding - (total_written - total_read)) {
wchunk_size = maxOutstanding - (total_written - total_read);
}
// Write the chunk
uint32_t chunk_written = 0;
while (chunk_written < wchunk_size) {
uint32_t write_size = wSizeGenerator.nextSize();
if (write_size == 0 || write_size > wchunk_size - chunk_written) {
write_size = wchunk_size - chunk_written;
}
transports.out->write(wbuf.get() + total_written, write_size);
chunk_written += write_size;
total_written += write_size;
}
// Flush the data, so it will be available in the read transport
// Don't flush if wchunk_size is 0. (This should only happen if
// total_written == totalSize already, and we're only reading now.)
if (wchunk_size > 0) {
transports.out->flush();
}
// Determine how large a chunk of data to read back
uint32_t rchunk_size = rChunkGenerator.nextSize();
if (rchunk_size == 0 || rchunk_size > total_written - total_read) {
rchunk_size = total_written - total_read;
}
// Read the chunk
uint32_t chunk_read = 0;
while (chunk_read < rchunk_size) {
uint32_t read_size = rSizeGenerator.nextSize();
if (read_size == 0 || read_size > rchunk_size - chunk_read) {
read_size = rchunk_size - chunk_read;
}
int bytes_read = -1;
try {
bytes_read = transports.in->read(rbuf.get() + total_read, read_size);
} catch (TTransportException& e) {
BOOST_FAIL("read(pos=" << total_read << ", size=" << read_size <<
") threw exception \"" << e.what() <<
"\"; written so far: " << total_written << " / " <<
totalSize << " bytes");
}
BOOST_REQUIRE_MESSAGE(bytes_read > 0,
"read(pos=" << total_read << ", size=" <<
read_size << ") returned " << bytes_read <<
"; written so far: " << total_written << " / " <<
totalSize << " bytes");
chunk_read += bytes_read;
total_read += bytes_read;
}
}
// make sure the data read back is identical to the data written
BOOST_CHECK_EQUAL(memcmp(rbuf.get(), wbuf.get(), totalSize), 0);
}
THRIFT-929. cpp: Add tests to verify blocking read behavior Add tests that check to see whether or not read() and borrow() block when called with a length larger than the amount of data currently available. At the moment, not all of the transports behave the same way. I believe the desired behavior is: When M bytes are available, and 0 < M < N: - read(N): return M bytes immediately - borrow(N): return NULL immediately When 0 bytes are available: - read(N): In this case, it is acceptable either to immediately return 0, or to block until some data is available. If the transport blocks, it returns immediately when some date becomes available, even if less than N bytes are available. - borrow(N): return NULL immediately - The borrow() tests fail when using TBufferedTransport. TBufferedTransport incorrectly blocks until the amount of data requested is available. - test_read_none_available() fails when using TFramedTransport. Calling read() on a TFramedTransport when no data is available throws an exception instead of returning 0. - test_read_none_available() fails when using TFDTransport. This is partly just an artifact of the fact that I use SIGALRM as part of this test. Unlike TSocket, TFDTransport doesn't retry after EINTR. - test_read_part_available() fails when using TZlibTransport around a transport that has blocking read() behavior. TZlibTransport::read() loops calling read() on the underlying transport. It should probably break out of the loop and return to the caller as soon as it has uncompressed any data, even if it is less than requested and more might be available. Once some data has been uncompressed, TZlibTransport cannot risk calling read() again since it might block. Will commit fixes for these separately. git-svn-id: https://svn.apache.org/repos/asf/incubator/thrift/trunk@1005160 13f79535-47bb-0310-9956-ffa450edef68
2010-10-06 17:10:38 +00:00
template <class CoupledTransports>
void test_read_part_available() {
CoupledTransports transports;
BOOST_REQUIRE(transports.in != NULL);
BOOST_REQUIRE(transports.out != NULL);
uint8_t write_buf[16];
uint8_t read_buf[16];
memset(write_buf, 'a', sizeof(write_buf));
// Attemping to read 10 bytes when only 9 are available should return 9
// immediately.
transports.out->write(write_buf, 9);
transports.out->flush();
set_trigger(3, transports.out, 1);
uint32_t bytes_read = transports.in->read(read_buf, 10);
BOOST_CHECK_EQUAL(numTriggersFired, (unsigned int) 0);
BOOST_CHECK_EQUAL(bytes_read, (uint32_t) 9);
THRIFT-929. cpp: Add tests to verify blocking read behavior Add tests that check to see whether or not read() and borrow() block when called with a length larger than the amount of data currently available. At the moment, not all of the transports behave the same way. I believe the desired behavior is: When M bytes are available, and 0 < M < N: - read(N): return M bytes immediately - borrow(N): return NULL immediately When 0 bytes are available: - read(N): In this case, it is acceptable either to immediately return 0, or to block until some data is available. If the transport blocks, it returns immediately when some date becomes available, even if less than N bytes are available. - borrow(N): return NULL immediately - The borrow() tests fail when using TBufferedTransport. TBufferedTransport incorrectly blocks until the amount of data requested is available. - test_read_none_available() fails when using TFramedTransport. Calling read() on a TFramedTransport when no data is available throws an exception instead of returning 0. - test_read_none_available() fails when using TFDTransport. This is partly just an artifact of the fact that I use SIGALRM as part of this test. Unlike TSocket, TFDTransport doesn't retry after EINTR. - test_read_part_available() fails when using TZlibTransport around a transport that has blocking read() behavior. TZlibTransport::read() loops calling read() on the underlying transport. It should probably break out of the loop and return to the caller as soon as it has uncompressed any data, even if it is less than requested and more might be available. Once some data has been uncompressed, TZlibTransport cannot risk calling read() again since it might block. Will commit fixes for these separately. git-svn-id: https://svn.apache.org/repos/asf/incubator/thrift/trunk@1005160 13f79535-47bb-0310-9956-ffa450edef68
2010-10-06 17:10:38 +00:00
clear_triggers();
}
THRIFT-926. cpp: Fix inconsistencies in transport read() behavior - TBufferedTransport::borrow() could block if not enough data was available. Now it returns NULL immediately in this case, like all other transports. - TBufferedTransport::read() could block some data was available in the readahead buffer, but not enough to satisfy the request. It would attempt to call read() on the underlying transport, but this might block. Now it just returns the remaining data in the readahead buffer. The caller is responsible for calling read() again to get the rest of the data they want. - TFrameTransport::read() threw an exception if read() on the underlying transport returned 0 when looking for a frame header. Now TFrameTransport::read() returns 0, too. (It still throws an exception if the underlying transport returns 0 after a partial frame or frame header has been read.) - TFDTransport::read() threw an exception on EINTR. Now it retries up to 5 times, similarly to the way TSocket::read() behaves. - TZlibTransport::read() could block when less data than was requested is available. Now it only calls read() on the underlying transport when it would otherwise have nothing to return. This does mean that TZlibTransport::read() now often returns less data than is actually available at the time. This required updating several of the ZlibTest tests to use readAll() instead of read(), since they previously assumed read() would return all available data. git-svn-id: https://svn.apache.org/repos/asf/incubator/thrift/trunk@1005161 13f79535-47bb-0310-9956-ffa450edef68
2010-10-06 17:10:40 +00:00
template <class CoupledTransports>
void test_read_partial_midframe() {
CoupledTransports transports;
BOOST_REQUIRE(transports.in != NULL);
BOOST_REQUIRE(transports.out != NULL);
uint8_t write_buf[16];
uint8_t read_buf[16];
memset(write_buf, 'a', sizeof(write_buf));
// Attempt to read 10 bytes, when only 9 are available, but after we have
// already read part of the data that is available. This exercises a
// different code path for several of the transports.
//
// For transports that add their own framing (e.g., TFramedTransport and
// TFileTransport), the two flush calls break up the data in to a 10 byte
// frame and a 3 byte frame. The first read then puts us partway through the
// first frame, and then we attempt to read past the end of that frame, and
// through the next frame, too.
//
// For buffered transports that perform read-ahead (e.g.,
// TBufferedTransport), the read-ahead will most likely see all 13 bytes
// written on the first read. The next read will then attempt to read past
// the end of the read-ahead buffer.
//
// Flush 10 bytes, then 3 bytes. This creates 2 separate frames for
// transports that track framing internally.
transports.out->write(write_buf, 10);
transports.out->flush();
transports.out->write(write_buf, 3);
transports.out->flush();
// Now read 4 bytes, so that we are partway through the written data.
uint32_t bytes_read = transports.in->read(read_buf, 4);
BOOST_CHECK_EQUAL(bytes_read, (uint32_t) 4);
THRIFT-926. cpp: Fix inconsistencies in transport read() behavior - TBufferedTransport::borrow() could block if not enough data was available. Now it returns NULL immediately in this case, like all other transports. - TBufferedTransport::read() could block some data was available in the readahead buffer, but not enough to satisfy the request. It would attempt to call read() on the underlying transport, but this might block. Now it just returns the remaining data in the readahead buffer. The caller is responsible for calling read() again to get the rest of the data they want. - TFrameTransport::read() threw an exception if read() on the underlying transport returned 0 when looking for a frame header. Now TFrameTransport::read() returns 0, too. (It still throws an exception if the underlying transport returns 0 after a partial frame or frame header has been read.) - TFDTransport::read() threw an exception on EINTR. Now it retries up to 5 times, similarly to the way TSocket::read() behaves. - TZlibTransport::read() could block when less data than was requested is available. Now it only calls read() on the underlying transport when it would otherwise have nothing to return. This does mean that TZlibTransport::read() now often returns less data than is actually available at the time. This required updating several of the ZlibTest tests to use readAll() instead of read(), since they previously assumed read() would return all available data. git-svn-id: https://svn.apache.org/repos/asf/incubator/thrift/trunk@1005161 13f79535-47bb-0310-9956-ffa450edef68
2010-10-06 17:10:40 +00:00
// Now attempt to read 10 bytes. Only 9 more are available.
//
// We should be able to get all 9 bytes, but it might take multiple read
// calls, since it is valid for read() to return fewer bytes than requested.
// (Most transports do immediately return 9 bytes, but the framing transports
// tend to only return to the end of the current frame, which is 6 bytes in
// this case.)
uint32_t total_read = 0;
while (total_read < 9) {
set_trigger(3, transports.out, 1);
bytes_read = transports.in->read(read_buf, 10);
BOOST_REQUIRE_EQUAL(numTriggersFired, (unsigned int) 0);
BOOST_REQUIRE_GT(bytes_read, (uint32_t) 0);
THRIFT-926. cpp: Fix inconsistencies in transport read() behavior - TBufferedTransport::borrow() could block if not enough data was available. Now it returns NULL immediately in this case, like all other transports. - TBufferedTransport::read() could block some data was available in the readahead buffer, but not enough to satisfy the request. It would attempt to call read() on the underlying transport, but this might block. Now it just returns the remaining data in the readahead buffer. The caller is responsible for calling read() again to get the rest of the data they want. - TFrameTransport::read() threw an exception if read() on the underlying transport returned 0 when looking for a frame header. Now TFrameTransport::read() returns 0, too. (It still throws an exception if the underlying transport returns 0 after a partial frame or frame header has been read.) - TFDTransport::read() threw an exception on EINTR. Now it retries up to 5 times, similarly to the way TSocket::read() behaves. - TZlibTransport::read() could block when less data than was requested is available. Now it only calls read() on the underlying transport when it would otherwise have nothing to return. This does mean that TZlibTransport::read() now often returns less data than is actually available at the time. This required updating several of the ZlibTest tests to use readAll() instead of read(), since they previously assumed read() would return all available data. git-svn-id: https://svn.apache.org/repos/asf/incubator/thrift/trunk@1005161 13f79535-47bb-0310-9956-ffa450edef68
2010-10-06 17:10:40 +00:00
total_read += bytes_read;
BOOST_REQUIRE_LE(total_read, (uint32_t) 9);
THRIFT-926. cpp: Fix inconsistencies in transport read() behavior - TBufferedTransport::borrow() could block if not enough data was available. Now it returns NULL immediately in this case, like all other transports. - TBufferedTransport::read() could block some data was available in the readahead buffer, but not enough to satisfy the request. It would attempt to call read() on the underlying transport, but this might block. Now it just returns the remaining data in the readahead buffer. The caller is responsible for calling read() again to get the rest of the data they want. - TFrameTransport::read() threw an exception if read() on the underlying transport returned 0 when looking for a frame header. Now TFrameTransport::read() returns 0, too. (It still throws an exception if the underlying transport returns 0 after a partial frame or frame header has been read.) - TFDTransport::read() threw an exception on EINTR. Now it retries up to 5 times, similarly to the way TSocket::read() behaves. - TZlibTransport::read() could block when less data than was requested is available. Now it only calls read() on the underlying transport when it would otherwise have nothing to return. This does mean that TZlibTransport::read() now often returns less data than is actually available at the time. This required updating several of the ZlibTest tests to use readAll() instead of read(), since they previously assumed read() would return all available data. git-svn-id: https://svn.apache.org/repos/asf/incubator/thrift/trunk@1005161 13f79535-47bb-0310-9956-ffa450edef68
2010-10-06 17:10:40 +00:00
}
BOOST_CHECK_EQUAL(total_read, (uint32_t) 9);
THRIFT-926. cpp: Fix inconsistencies in transport read() behavior - TBufferedTransport::borrow() could block if not enough data was available. Now it returns NULL immediately in this case, like all other transports. - TBufferedTransport::read() could block some data was available in the readahead buffer, but not enough to satisfy the request. It would attempt to call read() on the underlying transport, but this might block. Now it just returns the remaining data in the readahead buffer. The caller is responsible for calling read() again to get the rest of the data they want. - TFrameTransport::read() threw an exception if read() on the underlying transport returned 0 when looking for a frame header. Now TFrameTransport::read() returns 0, too. (It still throws an exception if the underlying transport returns 0 after a partial frame or frame header has been read.) - TFDTransport::read() threw an exception on EINTR. Now it retries up to 5 times, similarly to the way TSocket::read() behaves. - TZlibTransport::read() could block when less data than was requested is available. Now it only calls read() on the underlying transport when it would otherwise have nothing to return. This does mean that TZlibTransport::read() now often returns less data than is actually available at the time. This required updating several of the ZlibTest tests to use readAll() instead of read(), since they previously assumed read() would return all available data. git-svn-id: https://svn.apache.org/repos/asf/incubator/thrift/trunk@1005161 13f79535-47bb-0310-9956-ffa450edef68
2010-10-06 17:10:40 +00:00
clear_triggers();
}
THRIFT-929. cpp: Add tests to verify blocking read behavior Add tests that check to see whether or not read() and borrow() block when called with a length larger than the amount of data currently available. At the moment, not all of the transports behave the same way. I believe the desired behavior is: When M bytes are available, and 0 < M < N: - read(N): return M bytes immediately - borrow(N): return NULL immediately When 0 bytes are available: - read(N): In this case, it is acceptable either to immediately return 0, or to block until some data is available. If the transport blocks, it returns immediately when some date becomes available, even if less than N bytes are available. - borrow(N): return NULL immediately - The borrow() tests fail when using TBufferedTransport. TBufferedTransport incorrectly blocks until the amount of data requested is available. - test_read_none_available() fails when using TFramedTransport. Calling read() on a TFramedTransport when no data is available throws an exception instead of returning 0. - test_read_none_available() fails when using TFDTransport. This is partly just an artifact of the fact that I use SIGALRM as part of this test. Unlike TSocket, TFDTransport doesn't retry after EINTR. - test_read_part_available() fails when using TZlibTransport around a transport that has blocking read() behavior. TZlibTransport::read() loops calling read() on the underlying transport. It should probably break out of the loop and return to the caller as soon as it has uncompressed any data, even if it is less than requested and more might be available. Once some data has been uncompressed, TZlibTransport cannot risk calling read() again since it might block. Will commit fixes for these separately. git-svn-id: https://svn.apache.org/repos/asf/incubator/thrift/trunk@1005160 13f79535-47bb-0310-9956-ffa450edef68
2010-10-06 17:10:38 +00:00
template <class CoupledTransports>
void test_borrow_part_available() {
CoupledTransports transports;
BOOST_REQUIRE(transports.in != NULL);
BOOST_REQUIRE(transports.out != NULL);
uint8_t write_buf[16];
uint8_t read_buf[16];
memset(write_buf, 'a', sizeof(write_buf));
// Attemping to borrow 10 bytes when only 9 are available should return NULL
// immediately.
transports.out->write(write_buf, 9);
transports.out->flush();
set_trigger(3, transports.out, 1);
uint32_t borrow_len = 10;
const uint8_t* borrowed_buf = transports.in->borrow(read_buf, &borrow_len);
BOOST_CHECK_EQUAL(numTriggersFired, (unsigned int) 0);
THRIFT-929. cpp: Add tests to verify blocking read behavior Add tests that check to see whether or not read() and borrow() block when called with a length larger than the amount of data currently available. At the moment, not all of the transports behave the same way. I believe the desired behavior is: When M bytes are available, and 0 < M < N: - read(N): return M bytes immediately - borrow(N): return NULL immediately When 0 bytes are available: - read(N): In this case, it is acceptable either to immediately return 0, or to block until some data is available. If the transport blocks, it returns immediately when some date becomes available, even if less than N bytes are available. - borrow(N): return NULL immediately - The borrow() tests fail when using TBufferedTransport. TBufferedTransport incorrectly blocks until the amount of data requested is available. - test_read_none_available() fails when using TFramedTransport. Calling read() on a TFramedTransport when no data is available throws an exception instead of returning 0. - test_read_none_available() fails when using TFDTransport. This is partly just an artifact of the fact that I use SIGALRM as part of this test. Unlike TSocket, TFDTransport doesn't retry after EINTR. - test_read_part_available() fails when using TZlibTransport around a transport that has blocking read() behavior. TZlibTransport::read() loops calling read() on the underlying transport. It should probably break out of the loop and return to the caller as soon as it has uncompressed any data, even if it is less than requested and more might be available. Once some data has been uncompressed, TZlibTransport cannot risk calling read() again since it might block. Will commit fixes for these separately. git-svn-id: https://svn.apache.org/repos/asf/incubator/thrift/trunk@1005160 13f79535-47bb-0310-9956-ffa450edef68
2010-10-06 17:10:38 +00:00
BOOST_CHECK(borrowed_buf == NULL);
clear_triggers();
}
template <class CoupledTransports>
void test_read_none_available() {
CoupledTransports transports;
BOOST_REQUIRE(transports.in != NULL);
BOOST_REQUIRE(transports.out != NULL);
uint8_t write_buf[16];
uint8_t read_buf[16];
memset(write_buf, 'a', sizeof(write_buf));
// Attempting to read when no data is available should either block until
// some data is available, or fail immediately. (e.g., TSocket blocks,
// TMemoryBuffer just fails.)
//
// If the transport blocks, it should succeed once some data is available,
// even if less than the amount requested becomes available.
set_trigger(1, transports.out, 2);
add_trigger(1, transports.out, 8);
uint32_t bytes_read = transports.in->read(read_buf, 10);
if (bytes_read == 0) {
BOOST_CHECK_EQUAL(numTriggersFired, (unsigned int) 0);
THRIFT-929. cpp: Add tests to verify blocking read behavior Add tests that check to see whether or not read() and borrow() block when called with a length larger than the amount of data currently available. At the moment, not all of the transports behave the same way. I believe the desired behavior is: When M bytes are available, and 0 < M < N: - read(N): return M bytes immediately - borrow(N): return NULL immediately When 0 bytes are available: - read(N): In this case, it is acceptable either to immediately return 0, or to block until some data is available. If the transport blocks, it returns immediately when some date becomes available, even if less than N bytes are available. - borrow(N): return NULL immediately - The borrow() tests fail when using TBufferedTransport. TBufferedTransport incorrectly blocks until the amount of data requested is available. - test_read_none_available() fails when using TFramedTransport. Calling read() on a TFramedTransport when no data is available throws an exception instead of returning 0. - test_read_none_available() fails when using TFDTransport. This is partly just an artifact of the fact that I use SIGALRM as part of this test. Unlike TSocket, TFDTransport doesn't retry after EINTR. - test_read_part_available() fails when using TZlibTransport around a transport that has blocking read() behavior. TZlibTransport::read() loops calling read() on the underlying transport. It should probably break out of the loop and return to the caller as soon as it has uncompressed any data, even if it is less than requested and more might be available. Once some data has been uncompressed, TZlibTransport cannot risk calling read() again since it might block. Will commit fixes for these separately. git-svn-id: https://svn.apache.org/repos/asf/incubator/thrift/trunk@1005160 13f79535-47bb-0310-9956-ffa450edef68
2010-10-06 17:10:38 +00:00
clear_triggers();
} else {
BOOST_CHECK_EQUAL(numTriggersFired, (unsigned int) 1);
BOOST_CHECK_EQUAL(bytes_read, (uint32_t) 2);
THRIFT-929. cpp: Add tests to verify blocking read behavior Add tests that check to see whether or not read() and borrow() block when called with a length larger than the amount of data currently available. At the moment, not all of the transports behave the same way. I believe the desired behavior is: When M bytes are available, and 0 < M < N: - read(N): return M bytes immediately - borrow(N): return NULL immediately When 0 bytes are available: - read(N): In this case, it is acceptable either to immediately return 0, or to block until some data is available. If the transport blocks, it returns immediately when some date becomes available, even if less than N bytes are available. - borrow(N): return NULL immediately - The borrow() tests fail when using TBufferedTransport. TBufferedTransport incorrectly blocks until the amount of data requested is available. - test_read_none_available() fails when using TFramedTransport. Calling read() on a TFramedTransport when no data is available throws an exception instead of returning 0. - test_read_none_available() fails when using TFDTransport. This is partly just an artifact of the fact that I use SIGALRM as part of this test. Unlike TSocket, TFDTransport doesn't retry after EINTR. - test_read_part_available() fails when using TZlibTransport around a transport that has blocking read() behavior. TZlibTransport::read() loops calling read() on the underlying transport. It should probably break out of the loop and return to the caller as soon as it has uncompressed any data, even if it is less than requested and more might be available. Once some data has been uncompressed, TZlibTransport cannot risk calling read() again since it might block. Will commit fixes for these separately. git-svn-id: https://svn.apache.org/repos/asf/incubator/thrift/trunk@1005160 13f79535-47bb-0310-9956-ffa450edef68
2010-10-06 17:10:38 +00:00
}
clear_triggers();
}
template <class CoupledTransports>
void test_borrow_none_available() {
CoupledTransports transports;
BOOST_REQUIRE(transports.in != NULL);
BOOST_REQUIRE(transports.out != NULL);
uint8_t write_buf[16];
memset(write_buf, 'a', sizeof(write_buf));
// Attempting to borrow when no data is available should fail immediately
set_trigger(1, transports.out, 10);
uint32_t borrow_len = 10;
const uint8_t* borrowed_buf = transports.in->borrow(NULL, &borrow_len);
BOOST_CHECK(borrowed_buf == NULL);
BOOST_CHECK_EQUAL(numTriggersFired, (unsigned int) 0);
THRIFT-929. cpp: Add tests to verify blocking read behavior Add tests that check to see whether or not read() and borrow() block when called with a length larger than the amount of data currently available. At the moment, not all of the transports behave the same way. I believe the desired behavior is: When M bytes are available, and 0 < M < N: - read(N): return M bytes immediately - borrow(N): return NULL immediately When 0 bytes are available: - read(N): In this case, it is acceptable either to immediately return 0, or to block until some data is available. If the transport blocks, it returns immediately when some date becomes available, even if less than N bytes are available. - borrow(N): return NULL immediately - The borrow() tests fail when using TBufferedTransport. TBufferedTransport incorrectly blocks until the amount of data requested is available. - test_read_none_available() fails when using TFramedTransport. Calling read() on a TFramedTransport when no data is available throws an exception instead of returning 0. - test_read_none_available() fails when using TFDTransport. This is partly just an artifact of the fact that I use SIGALRM as part of this test. Unlike TSocket, TFDTransport doesn't retry after EINTR. - test_read_part_available() fails when using TZlibTransport around a transport that has blocking read() behavior. TZlibTransport::read() loops calling read() on the underlying transport. It should probably break out of the loop and return to the caller as soon as it has uncompressed any data, even if it is less than requested and more might be available. Once some data has been uncompressed, TZlibTransport cannot risk calling read() again since it might block. Will commit fixes for these separately. git-svn-id: https://svn.apache.org/repos/asf/incubator/thrift/trunk@1005160 13f79535-47bb-0310-9956-ffa450edef68
2010-10-06 17:10:38 +00:00
clear_triggers();
}
/**************************************************************************
* Test case generation
*
* Pretty ugly and annoying. This would be much easier if we the unit test
* framework didn't force each test to be a separate function.
* - Writing a completely separate function definition for each of these would
* result in a lot of repetitive boilerplate code.
* - Combining many tests into a single function makes it more difficult to
* tell precisely which tests failed. It also means you can't get a progress
* update after each test, and the tests are already fairly slow.
* - Similar registration could be acheived with BOOST_TEST_CASE_TEMPLATE,
* but it requires a lot of awkward MPL code, and results in useless test
* case names. (The names are generated from std::type_info::name(), which
* is compiler-dependent. gcc returns mangled names.)
**************************************************************************/
THRIFT-929. cpp: Add tests to verify blocking read behavior Add tests that check to see whether or not read() and borrow() block when called with a length larger than the amount of data currently available. At the moment, not all of the transports behave the same way. I believe the desired behavior is: When M bytes are available, and 0 < M < N: - read(N): return M bytes immediately - borrow(N): return NULL immediately When 0 bytes are available: - read(N): In this case, it is acceptable either to immediately return 0, or to block until some data is available. If the transport blocks, it returns immediately when some date becomes available, even if less than N bytes are available. - borrow(N): return NULL immediately - The borrow() tests fail when using TBufferedTransport. TBufferedTransport incorrectly blocks until the amount of data requested is available. - test_read_none_available() fails when using TFramedTransport. Calling read() on a TFramedTransport when no data is available throws an exception instead of returning 0. - test_read_none_available() fails when using TFDTransport. This is partly just an artifact of the fact that I use SIGALRM as part of this test. Unlike TSocket, TFDTransport doesn't retry after EINTR. - test_read_part_available() fails when using TZlibTransport around a transport that has blocking read() behavior. TZlibTransport::read() loops calling read() on the underlying transport. It should probably break out of the loop and return to the caller as soon as it has uncompressed any data, even if it is less than requested and more might be available. Once some data has been uncompressed, TZlibTransport cannot risk calling read() again since it might block. Will commit fixes for these separately. git-svn-id: https://svn.apache.org/repos/asf/incubator/thrift/trunk@1005160 13f79535-47bb-0310-9956-ffa450edef68
2010-10-06 17:10:38 +00:00
#define ADD_TEST_RW(CoupledTransports, totalSize, ...) \
addTestRW< CoupledTransports >(BOOST_STRINGIZE(CoupledTransports), \
totalSize, ## __VA_ARGS__);
#define TEST_RW(CoupledTransports, totalSize, ...) \
do { \
/* Add the test as specified, to test the non-virtual function calls */ \
THRIFT-929. cpp: Add tests to verify blocking read behavior Add tests that check to see whether or not read() and borrow() block when called with a length larger than the amount of data currently available. At the moment, not all of the transports behave the same way. I believe the desired behavior is: When M bytes are available, and 0 < M < N: - read(N): return M bytes immediately - borrow(N): return NULL immediately When 0 bytes are available: - read(N): In this case, it is acceptable either to immediately return 0, or to block until some data is available. If the transport blocks, it returns immediately when some date becomes available, even if less than N bytes are available. - borrow(N): return NULL immediately - The borrow() tests fail when using TBufferedTransport. TBufferedTransport incorrectly blocks until the amount of data requested is available. - test_read_none_available() fails when using TFramedTransport. Calling read() on a TFramedTransport when no data is available throws an exception instead of returning 0. - test_read_none_available() fails when using TFDTransport. This is partly just an artifact of the fact that I use SIGALRM as part of this test. Unlike TSocket, TFDTransport doesn't retry after EINTR. - test_read_part_available() fails when using TZlibTransport around a transport that has blocking read() behavior. TZlibTransport::read() loops calling read() on the underlying transport. It should probably break out of the loop and return to the caller as soon as it has uncompressed any data, even if it is less than requested and more might be available. Once some data has been uncompressed, TZlibTransport cannot risk calling read() again since it might block. Will commit fixes for these separately. git-svn-id: https://svn.apache.org/repos/asf/incubator/thrift/trunk@1005160 13f79535-47bb-0310-9956-ffa450edef68
2010-10-06 17:10:38 +00:00
ADD_TEST_RW(CoupledTransports, totalSize, ## __VA_ARGS__); \
/* \
* Also test using the transport as a TTransport*, to test \
* the read_virt()/write_virt() calls \
*/ \
THRIFT-929. cpp: Add tests to verify blocking read behavior Add tests that check to see whether or not read() and borrow() block when called with a length larger than the amount of data currently available. At the moment, not all of the transports behave the same way. I believe the desired behavior is: When M bytes are available, and 0 < M < N: - read(N): return M bytes immediately - borrow(N): return NULL immediately When 0 bytes are available: - read(N): In this case, it is acceptable either to immediately return 0, or to block until some data is available. If the transport blocks, it returns immediately when some date becomes available, even if less than N bytes are available. - borrow(N): return NULL immediately - The borrow() tests fail when using TBufferedTransport. TBufferedTransport incorrectly blocks until the amount of data requested is available. - test_read_none_available() fails when using TFramedTransport. Calling read() on a TFramedTransport when no data is available throws an exception instead of returning 0. - test_read_none_available() fails when using TFDTransport. This is partly just an artifact of the fact that I use SIGALRM as part of this test. Unlike TSocket, TFDTransport doesn't retry after EINTR. - test_read_part_available() fails when using TZlibTransport around a transport that has blocking read() behavior. TZlibTransport::read() loops calling read() on the underlying transport. It should probably break out of the loop and return to the caller as soon as it has uncompressed any data, even if it is less than requested and more might be available. Once some data has been uncompressed, TZlibTransport cannot risk calling read() again since it might block. Will commit fixes for these separately. git-svn-id: https://svn.apache.org/repos/asf/incubator/thrift/trunk@1005160 13f79535-47bb-0310-9956-ffa450edef68
2010-10-06 17:10:38 +00:00
ADD_TEST_RW(CoupledTTransports<CoupledTransports>, \
totalSize, ## __VA_ARGS__); \
/* Test wrapping the transport with TBufferedTransport */ \
THRIFT-929. cpp: Add tests to verify blocking read behavior Add tests that check to see whether or not read() and borrow() block when called with a length larger than the amount of data currently available. At the moment, not all of the transports behave the same way. I believe the desired behavior is: When M bytes are available, and 0 < M < N: - read(N): return M bytes immediately - borrow(N): return NULL immediately When 0 bytes are available: - read(N): In this case, it is acceptable either to immediately return 0, or to block until some data is available. If the transport blocks, it returns immediately when some date becomes available, even if less than N bytes are available. - borrow(N): return NULL immediately - The borrow() tests fail when using TBufferedTransport. TBufferedTransport incorrectly blocks until the amount of data requested is available. - test_read_none_available() fails when using TFramedTransport. Calling read() on a TFramedTransport when no data is available throws an exception instead of returning 0. - test_read_none_available() fails when using TFDTransport. This is partly just an artifact of the fact that I use SIGALRM as part of this test. Unlike TSocket, TFDTransport doesn't retry after EINTR. - test_read_part_available() fails when using TZlibTransport around a transport that has blocking read() behavior. TZlibTransport::read() loops calling read() on the underlying transport. It should probably break out of the loop and return to the caller as soon as it has uncompressed any data, even if it is less than requested and more might be available. Once some data has been uncompressed, TZlibTransport cannot risk calling read() again since it might block. Will commit fixes for these separately. git-svn-id: https://svn.apache.org/repos/asf/incubator/thrift/trunk@1005160 13f79535-47bb-0310-9956-ffa450edef68
2010-10-06 17:10:38 +00:00
ADD_TEST_RW(CoupledBufferedTransportsT<CoupledTransports>, \
totalSize, ## __VA_ARGS__); \
/* Test wrapping the transport with TFramedTransports */ \
THRIFT-929. cpp: Add tests to verify blocking read behavior Add tests that check to see whether or not read() and borrow() block when called with a length larger than the amount of data currently available. At the moment, not all of the transports behave the same way. I believe the desired behavior is: When M bytes are available, and 0 < M < N: - read(N): return M bytes immediately - borrow(N): return NULL immediately When 0 bytes are available: - read(N): In this case, it is acceptable either to immediately return 0, or to block until some data is available. If the transport blocks, it returns immediately when some date becomes available, even if less than N bytes are available. - borrow(N): return NULL immediately - The borrow() tests fail when using TBufferedTransport. TBufferedTransport incorrectly blocks until the amount of data requested is available. - test_read_none_available() fails when using TFramedTransport. Calling read() on a TFramedTransport when no data is available throws an exception instead of returning 0. - test_read_none_available() fails when using TFDTransport. This is partly just an artifact of the fact that I use SIGALRM as part of this test. Unlike TSocket, TFDTransport doesn't retry after EINTR. - test_read_part_available() fails when using TZlibTransport around a transport that has blocking read() behavior. TZlibTransport::read() loops calling read() on the underlying transport. It should probably break out of the loop and return to the caller as soon as it has uncompressed any data, even if it is less than requested and more might be available. Once some data has been uncompressed, TZlibTransport cannot risk calling read() again since it might block. Will commit fixes for these separately. git-svn-id: https://svn.apache.org/repos/asf/incubator/thrift/trunk@1005160 13f79535-47bb-0310-9956-ffa450edef68
2010-10-06 17:10:38 +00:00
ADD_TEST_RW(CoupledFramedTransportsT<CoupledTransports>, \
totalSize, ## __VA_ARGS__); \
/* Test wrapping the transport with TZlibTransport */ \
THRIFT-929. cpp: Add tests to verify blocking read behavior Add tests that check to see whether or not read() and borrow() block when called with a length larger than the amount of data currently available. At the moment, not all of the transports behave the same way. I believe the desired behavior is: When M bytes are available, and 0 < M < N: - read(N): return M bytes immediately - borrow(N): return NULL immediately When 0 bytes are available: - read(N): In this case, it is acceptable either to immediately return 0, or to block until some data is available. If the transport blocks, it returns immediately when some date becomes available, even if less than N bytes are available. - borrow(N): return NULL immediately - The borrow() tests fail when using TBufferedTransport. TBufferedTransport incorrectly blocks until the amount of data requested is available. - test_read_none_available() fails when using TFramedTransport. Calling read() on a TFramedTransport when no data is available throws an exception instead of returning 0. - test_read_none_available() fails when using TFDTransport. This is partly just an artifact of the fact that I use SIGALRM as part of this test. Unlike TSocket, TFDTransport doesn't retry after EINTR. - test_read_part_available() fails when using TZlibTransport around a transport that has blocking read() behavior. TZlibTransport::read() loops calling read() on the underlying transport. It should probably break out of the loop and return to the caller as soon as it has uncompressed any data, even if it is less than requested and more might be available. Once some data has been uncompressed, TZlibTransport cannot risk calling read() again since it might block. Will commit fixes for these separately. git-svn-id: https://svn.apache.org/repos/asf/incubator/thrift/trunk@1005160 13f79535-47bb-0310-9956-ffa450edef68
2010-10-06 17:10:38 +00:00
ADD_TEST_RW(CoupledZlibTransportsT<CoupledTransports>, \
totalSize, ## __VA_ARGS__); \
} while (0)
THRIFT-929. cpp: Add tests to verify blocking read behavior Add tests that check to see whether or not read() and borrow() block when called with a length larger than the amount of data currently available. At the moment, not all of the transports behave the same way. I believe the desired behavior is: When M bytes are available, and 0 < M < N: - read(N): return M bytes immediately - borrow(N): return NULL immediately When 0 bytes are available: - read(N): In this case, it is acceptable either to immediately return 0, or to block until some data is available. If the transport blocks, it returns immediately when some date becomes available, even if less than N bytes are available. - borrow(N): return NULL immediately - The borrow() tests fail when using TBufferedTransport. TBufferedTransport incorrectly blocks until the amount of data requested is available. - test_read_none_available() fails when using TFramedTransport. Calling read() on a TFramedTransport when no data is available throws an exception instead of returning 0. - test_read_none_available() fails when using TFDTransport. This is partly just an artifact of the fact that I use SIGALRM as part of this test. Unlike TSocket, TFDTransport doesn't retry after EINTR. - test_read_part_available() fails when using TZlibTransport around a transport that has blocking read() behavior. TZlibTransport::read() loops calling read() on the underlying transport. It should probably break out of the loop and return to the caller as soon as it has uncompressed any data, even if it is less than requested and more might be available. Once some data has been uncompressed, TZlibTransport cannot risk calling read() again since it might block. Will commit fixes for these separately. git-svn-id: https://svn.apache.org/repos/asf/incubator/thrift/trunk@1005160 13f79535-47bb-0310-9956-ffa450edef68
2010-10-06 17:10:38 +00:00
#define ADD_TEST_BLOCKING(CoupledTransports) \
addTestBlocking< CoupledTransports >(BOOST_STRINGIZE(CoupledTransports));
#define TEST_BLOCKING_BEHAVIOR(CoupledTransports) \
ADD_TEST_BLOCKING(CoupledTransports); \
ADD_TEST_BLOCKING(CoupledTTransports<CoupledTransports>); \
ADD_TEST_BLOCKING(CoupledBufferedTransportsT<CoupledTransports>); \
ADD_TEST_BLOCKING(CoupledFramedTransportsT<CoupledTransports>); \
ADD_TEST_BLOCKING(CoupledZlibTransportsT<CoupledTransports>);
class TransportTestGen {
public:
TransportTestGen(boost::unit_test::test_suite* suite,
float sizeMultiplier) :
suite_(suite),
sizeMultiplier_(sizeMultiplier) {}
void generate() {
GenericSizeGenerator rand4k(1, 4096);
/*
* We do the basically the same set of tests for each transport type,
* although we tweak the parameters in some places.
*/
// TMemoryBuffer tests
TEST_RW(CoupledMemoryBuffers, 1024*1024, 0, 0);
TEST_RW(CoupledMemoryBuffers, 1024*256, rand4k, rand4k);
TEST_RW(CoupledMemoryBuffers, 1024*256, 167, 163);
TEST_RW(CoupledMemoryBuffers, 1024*16, 1, 1);
TEST_RW(CoupledMemoryBuffers, 1024*256, 0, 0, rand4k, rand4k);
TEST_RW(CoupledMemoryBuffers, 1024*256, rand4k, rand4k, rand4k, rand4k);
TEST_RW(CoupledMemoryBuffers, 1024*256, 167, 163, rand4k, rand4k);
TEST_RW(CoupledMemoryBuffers, 1024*16, 1, 1, rand4k, rand4k);
THRIFT-929. cpp: Add tests to verify blocking read behavior Add tests that check to see whether or not read() and borrow() block when called with a length larger than the amount of data currently available. At the moment, not all of the transports behave the same way. I believe the desired behavior is: When M bytes are available, and 0 < M < N: - read(N): return M bytes immediately - borrow(N): return NULL immediately When 0 bytes are available: - read(N): In this case, it is acceptable either to immediately return 0, or to block until some data is available. If the transport blocks, it returns immediately when some date becomes available, even if less than N bytes are available. - borrow(N): return NULL immediately - The borrow() tests fail when using TBufferedTransport. TBufferedTransport incorrectly blocks until the amount of data requested is available. - test_read_none_available() fails when using TFramedTransport. Calling read() on a TFramedTransport when no data is available throws an exception instead of returning 0. - test_read_none_available() fails when using TFDTransport. This is partly just an artifact of the fact that I use SIGALRM as part of this test. Unlike TSocket, TFDTransport doesn't retry after EINTR. - test_read_part_available() fails when using TZlibTransport around a transport that has blocking read() behavior. TZlibTransport::read() loops calling read() on the underlying transport. It should probably break out of the loop and return to the caller as soon as it has uncompressed any data, even if it is less than requested and more might be available. Once some data has been uncompressed, TZlibTransport cannot risk calling read() again since it might block. Will commit fixes for these separately. git-svn-id: https://svn.apache.org/repos/asf/incubator/thrift/trunk@1005160 13f79535-47bb-0310-9956-ffa450edef68
2010-10-06 17:10:38 +00:00
TEST_BLOCKING_BEHAVIOR(CoupledMemoryBuffers);
// TFDTransport tests
// Since CoupledFDTransports tests with a pipe, writes will block
// if there is too much outstanding unread data in the pipe.
uint32_t fd_max_outstanding = 4096;
TEST_RW(CoupledFDTransports, 1024*1024, 0, 0,
0, 0, fd_max_outstanding);
TEST_RW(CoupledFDTransports, 1024*256, rand4k, rand4k,
0, 0, fd_max_outstanding);
TEST_RW(CoupledFDTransports, 1024*256, 167, 163,
0, 0, fd_max_outstanding);
TEST_RW(CoupledFDTransports, 1024*16, 1, 1,
0, 0, fd_max_outstanding);
TEST_RW(CoupledFDTransports, 1024*256, 0, 0,
rand4k, rand4k, fd_max_outstanding);
TEST_RW(CoupledFDTransports, 1024*256, rand4k, rand4k,
rand4k, rand4k, fd_max_outstanding);
TEST_RW(CoupledFDTransports, 1024*256, 167, 163,
rand4k, rand4k, fd_max_outstanding);
TEST_RW(CoupledFDTransports, 1024*16, 1, 1,
rand4k, rand4k, fd_max_outstanding);
THRIFT-929. cpp: Add tests to verify blocking read behavior Add tests that check to see whether or not read() and borrow() block when called with a length larger than the amount of data currently available. At the moment, not all of the transports behave the same way. I believe the desired behavior is: When M bytes are available, and 0 < M < N: - read(N): return M bytes immediately - borrow(N): return NULL immediately When 0 bytes are available: - read(N): In this case, it is acceptable either to immediately return 0, or to block until some data is available. If the transport blocks, it returns immediately when some date becomes available, even if less than N bytes are available. - borrow(N): return NULL immediately - The borrow() tests fail when using TBufferedTransport. TBufferedTransport incorrectly blocks until the amount of data requested is available. - test_read_none_available() fails when using TFramedTransport. Calling read() on a TFramedTransport when no data is available throws an exception instead of returning 0. - test_read_none_available() fails when using TFDTransport. This is partly just an artifact of the fact that I use SIGALRM as part of this test. Unlike TSocket, TFDTransport doesn't retry after EINTR. - test_read_part_available() fails when using TZlibTransport around a transport that has blocking read() behavior. TZlibTransport::read() loops calling read() on the underlying transport. It should probably break out of the loop and return to the caller as soon as it has uncompressed any data, even if it is less than requested and more might be available. Once some data has been uncompressed, TZlibTransport cannot risk calling read() again since it might block. Will commit fixes for these separately. git-svn-id: https://svn.apache.org/repos/asf/incubator/thrift/trunk@1005160 13f79535-47bb-0310-9956-ffa450edef68
2010-10-06 17:10:38 +00:00
TEST_BLOCKING_BEHAVIOR(CoupledFDTransports);
// TSocket tests
uint32_t socket_max_outstanding = 4096;
TEST_RW(CoupledSocketTransports, 1024*1024, 0, 0,
0, 0, socket_max_outstanding);
TEST_RW(CoupledSocketTransports, 1024*256, rand4k, rand4k,
0, 0, socket_max_outstanding);
TEST_RW(CoupledSocketTransports, 1024*256, 167, 163,
0, 0, socket_max_outstanding);
// Doh. Apparently writing to a socket has some additional overhead for
// each send() call. If we have more than ~400 outstanding 1-byte write
// requests, additional send() calls start blocking.
TEST_RW(CoupledSocketTransports, 1024*16, 1, 1,
0, 0, 400);
TEST_RW(CoupledSocketTransports, 1024*256, 0, 0,
rand4k, rand4k, socket_max_outstanding);
TEST_RW(CoupledSocketTransports, 1024*256, rand4k, rand4k,
rand4k, rand4k, socket_max_outstanding);
TEST_RW(CoupledSocketTransports, 1024*256, 167, 163,
rand4k, rand4k, socket_max_outstanding);
TEST_RW(CoupledSocketTransports, 1024*16, 1, 1,
rand4k, rand4k, 400);
THRIFT-929. cpp: Add tests to verify blocking read behavior Add tests that check to see whether or not read() and borrow() block when called with a length larger than the amount of data currently available. At the moment, not all of the transports behave the same way. I believe the desired behavior is: When M bytes are available, and 0 < M < N: - read(N): return M bytes immediately - borrow(N): return NULL immediately When 0 bytes are available: - read(N): In this case, it is acceptable either to immediately return 0, or to block until some data is available. If the transport blocks, it returns immediately when some date becomes available, even if less than N bytes are available. - borrow(N): return NULL immediately - The borrow() tests fail when using TBufferedTransport. TBufferedTransport incorrectly blocks until the amount of data requested is available. - test_read_none_available() fails when using TFramedTransport. Calling read() on a TFramedTransport when no data is available throws an exception instead of returning 0. - test_read_none_available() fails when using TFDTransport. This is partly just an artifact of the fact that I use SIGALRM as part of this test. Unlike TSocket, TFDTransport doesn't retry after EINTR. - test_read_part_available() fails when using TZlibTransport around a transport that has blocking read() behavior. TZlibTransport::read() loops calling read() on the underlying transport. It should probably break out of the loop and return to the caller as soon as it has uncompressed any data, even if it is less than requested and more might be available. Once some data has been uncompressed, TZlibTransport cannot risk calling read() again since it might block. Will commit fixes for these separately. git-svn-id: https://svn.apache.org/repos/asf/incubator/thrift/trunk@1005160 13f79535-47bb-0310-9956-ffa450edef68
2010-10-06 17:10:38 +00:00
TEST_BLOCKING_BEHAVIOR(CoupledSocketTransports);
// TFileTransport tests
// We use smaller buffer sizes here, since TFileTransport is fairly slow.
//
// TFileTransport can't write more than 16MB at once
uint32_t max_write_at_once = 1024*1024*16 - 4;
TEST_RW(CoupledFileTransports, 1024*1024, max_write_at_once, 0);
TEST_RW(CoupledFileTransports, 1024*128, rand4k, rand4k);
TEST_RW(CoupledFileTransports, 1024*128, 167, 163);
TEST_RW(CoupledFileTransports, 1024*2, 1, 1);
TEST_RW(CoupledFileTransports, 1024*64, 0, 0, rand4k, rand4k);
TEST_RW(CoupledFileTransports, 1024*64,
rand4k, rand4k, rand4k, rand4k);
TEST_RW(CoupledFileTransports, 1024*64, 167, 163, rand4k, rand4k);
TEST_RW(CoupledFileTransports, 1024*2, 1, 1, rand4k, rand4k);
THRIFT-929. cpp: Add tests to verify blocking read behavior Add tests that check to see whether or not read() and borrow() block when called with a length larger than the amount of data currently available. At the moment, not all of the transports behave the same way. I believe the desired behavior is: When M bytes are available, and 0 < M < N: - read(N): return M bytes immediately - borrow(N): return NULL immediately When 0 bytes are available: - read(N): In this case, it is acceptable either to immediately return 0, or to block until some data is available. If the transport blocks, it returns immediately when some date becomes available, even if less than N bytes are available. - borrow(N): return NULL immediately - The borrow() tests fail when using TBufferedTransport. TBufferedTransport incorrectly blocks until the amount of data requested is available. - test_read_none_available() fails when using TFramedTransport. Calling read() on a TFramedTransport when no data is available throws an exception instead of returning 0. - test_read_none_available() fails when using TFDTransport. This is partly just an artifact of the fact that I use SIGALRM as part of this test. Unlike TSocket, TFDTransport doesn't retry after EINTR. - test_read_part_available() fails when using TZlibTransport around a transport that has blocking read() behavior. TZlibTransport::read() loops calling read() on the underlying transport. It should probably break out of the loop and return to the caller as soon as it has uncompressed any data, even if it is less than requested and more might be available. Once some data has been uncompressed, TZlibTransport cannot risk calling read() again since it might block. Will commit fixes for these separately. git-svn-id: https://svn.apache.org/repos/asf/incubator/thrift/trunk@1005160 13f79535-47bb-0310-9956-ffa450edef68
2010-10-06 17:10:38 +00:00
TEST_BLOCKING_BEHAVIOR(CoupledFileTransports);
// Add some tests that access TBufferedTransport and TFramedTransport
// via TTransport pointers and TBufferBase pointers.
THRIFT-929. cpp: Add tests to verify blocking read behavior Add tests that check to see whether or not read() and borrow() block when called with a length larger than the amount of data currently available. At the moment, not all of the transports behave the same way. I believe the desired behavior is: When M bytes are available, and 0 < M < N: - read(N): return M bytes immediately - borrow(N): return NULL immediately When 0 bytes are available: - read(N): In this case, it is acceptable either to immediately return 0, or to block until some data is available. If the transport blocks, it returns immediately when some date becomes available, even if less than N bytes are available. - borrow(N): return NULL immediately - The borrow() tests fail when using TBufferedTransport. TBufferedTransport incorrectly blocks until the amount of data requested is available. - test_read_none_available() fails when using TFramedTransport. Calling read() on a TFramedTransport when no data is available throws an exception instead of returning 0. - test_read_none_available() fails when using TFDTransport. This is partly just an artifact of the fact that I use SIGALRM as part of this test. Unlike TSocket, TFDTransport doesn't retry after EINTR. - test_read_part_available() fails when using TZlibTransport around a transport that has blocking read() behavior. TZlibTransport::read() loops calling read() on the underlying transport. It should probably break out of the loop and return to the caller as soon as it has uncompressed any data, even if it is less than requested and more might be available. Once some data has been uncompressed, TZlibTransport cannot risk calling read() again since it might block. Will commit fixes for these separately. git-svn-id: https://svn.apache.org/repos/asf/incubator/thrift/trunk@1005160 13f79535-47bb-0310-9956-ffa450edef68
2010-10-06 17:10:38 +00:00
ADD_TEST_RW(CoupledTTransports<CoupledBufferedTransports>,
1024*1024, rand4k, rand4k, rand4k, rand4k);
ADD_TEST_RW(CoupledBufferBases<CoupledBufferedTransports>,
1024*1024, rand4k, rand4k, rand4k, rand4k);
ADD_TEST_RW(CoupledTTransports<CoupledFramedTransports>,
1024*1024, rand4k, rand4k, rand4k, rand4k);
ADD_TEST_RW(CoupledBufferBases<CoupledFramedTransports>,
1024*1024, rand4k, rand4k, rand4k, rand4k);
// Test using TZlibTransport via a TTransport pointer
THRIFT-929. cpp: Add tests to verify blocking read behavior Add tests that check to see whether or not read() and borrow() block when called with a length larger than the amount of data currently available. At the moment, not all of the transports behave the same way. I believe the desired behavior is: When M bytes are available, and 0 < M < N: - read(N): return M bytes immediately - borrow(N): return NULL immediately When 0 bytes are available: - read(N): In this case, it is acceptable either to immediately return 0, or to block until some data is available. If the transport blocks, it returns immediately when some date becomes available, even if less than N bytes are available. - borrow(N): return NULL immediately - The borrow() tests fail when using TBufferedTransport. TBufferedTransport incorrectly blocks until the amount of data requested is available. - test_read_none_available() fails when using TFramedTransport. Calling read() on a TFramedTransport when no data is available throws an exception instead of returning 0. - test_read_none_available() fails when using TFDTransport. This is partly just an artifact of the fact that I use SIGALRM as part of this test. Unlike TSocket, TFDTransport doesn't retry after EINTR. - test_read_part_available() fails when using TZlibTransport around a transport that has blocking read() behavior. TZlibTransport::read() loops calling read() on the underlying transport. It should probably break out of the loop and return to the caller as soon as it has uncompressed any data, even if it is less than requested and more might be available. Once some data has been uncompressed, TZlibTransport cannot risk calling read() again since it might block. Will commit fixes for these separately. git-svn-id: https://svn.apache.org/repos/asf/incubator/thrift/trunk@1005160 13f79535-47bb-0310-9956-ffa450edef68
2010-10-06 17:10:38 +00:00
ADD_TEST_RW(CoupledTTransports<CoupledZlibTransports>,
1024*1024, rand4k, rand4k, rand4k, rand4k);
}
private:
template <class CoupledTransports>
THRIFT-929. cpp: Add tests to verify blocking read behavior Add tests that check to see whether or not read() and borrow() block when called with a length larger than the amount of data currently available. At the moment, not all of the transports behave the same way. I believe the desired behavior is: When M bytes are available, and 0 < M < N: - read(N): return M bytes immediately - borrow(N): return NULL immediately When 0 bytes are available: - read(N): In this case, it is acceptable either to immediately return 0, or to block until some data is available. If the transport blocks, it returns immediately when some date becomes available, even if less than N bytes are available. - borrow(N): return NULL immediately - The borrow() tests fail when using TBufferedTransport. TBufferedTransport incorrectly blocks until the amount of data requested is available. - test_read_none_available() fails when using TFramedTransport. Calling read() on a TFramedTransport when no data is available throws an exception instead of returning 0. - test_read_none_available() fails when using TFDTransport. This is partly just an artifact of the fact that I use SIGALRM as part of this test. Unlike TSocket, TFDTransport doesn't retry after EINTR. - test_read_part_available() fails when using TZlibTransport around a transport that has blocking read() behavior. TZlibTransport::read() loops calling read() on the underlying transport. It should probably break out of the loop and return to the caller as soon as it has uncompressed any data, even if it is less than requested and more might be available. Once some data has been uncompressed, TZlibTransport cannot risk calling read() again since it might block. Will commit fixes for these separately. git-svn-id: https://svn.apache.org/repos/asf/incubator/thrift/trunk@1005160 13f79535-47bb-0310-9956-ffa450edef68
2010-10-06 17:10:38 +00:00
void addTestRW(const char* transport_name, uint32_t totalSize,
GenericSizeGenerator wSizeGen, GenericSizeGenerator rSizeGen,
GenericSizeGenerator wChunkSizeGen = 0,
GenericSizeGenerator rChunkSizeGen = 0,
uint32_t maxOutstanding = 0,
uint32_t expectedFailures = 0) {
// adjust totalSize by the specified sizeMultiplier_ first
totalSize = static_cast<uint32_t>(totalSize * sizeMultiplier_);
std::ostringstream name;
name << transport_name << "::test_rw(" << totalSize << ", " <<
wSizeGen.describe() << ", " << rSizeGen.describe() << ", " <<
wChunkSizeGen.describe() << ", " << rChunkSizeGen.describe() << ", " <<
maxOutstanding << ")";
boost::unit_test::callback0<> test_func =
std::tr1::bind(test_rw<CoupledTransports>, totalSize,
wSizeGen, rSizeGen, wChunkSizeGen, rChunkSizeGen,
maxOutstanding);
boost::unit_test::test_case* tc =
boost::unit_test::make_test_case(test_func, name.str());
suite_->add(tc, expectedFailures);
}
THRIFT-929. cpp: Add tests to verify blocking read behavior Add tests that check to see whether or not read() and borrow() block when called with a length larger than the amount of data currently available. At the moment, not all of the transports behave the same way. I believe the desired behavior is: When M bytes are available, and 0 < M < N: - read(N): return M bytes immediately - borrow(N): return NULL immediately When 0 bytes are available: - read(N): In this case, it is acceptable either to immediately return 0, or to block until some data is available. If the transport blocks, it returns immediately when some date becomes available, even if less than N bytes are available. - borrow(N): return NULL immediately - The borrow() tests fail when using TBufferedTransport. TBufferedTransport incorrectly blocks until the amount of data requested is available. - test_read_none_available() fails when using TFramedTransport. Calling read() on a TFramedTransport when no data is available throws an exception instead of returning 0. - test_read_none_available() fails when using TFDTransport. This is partly just an artifact of the fact that I use SIGALRM as part of this test. Unlike TSocket, TFDTransport doesn't retry after EINTR. - test_read_part_available() fails when using TZlibTransport around a transport that has blocking read() behavior. TZlibTransport::read() loops calling read() on the underlying transport. It should probably break out of the loop and return to the caller as soon as it has uncompressed any data, even if it is less than requested and more might be available. Once some data has been uncompressed, TZlibTransport cannot risk calling read() again since it might block. Will commit fixes for these separately. git-svn-id: https://svn.apache.org/repos/asf/incubator/thrift/trunk@1005160 13f79535-47bb-0310-9956-ffa450edef68
2010-10-06 17:10:38 +00:00
template <class CoupledTransports>
void addTestBlocking(const char* transportName,
uint32_t expectedFailures = 0) {
char name[1024];
boost::unit_test::test_case* tc;
snprintf(name, sizeof(name), "%s::test_read_part_available()",
transportName);
tc = boost::unit_test::make_test_case(
test_read_part_available<CoupledTransports>, name);
suite_->add(tc, expectedFailures);
THRIFT-926. cpp: Fix inconsistencies in transport read() behavior - TBufferedTransport::borrow() could block if not enough data was available. Now it returns NULL immediately in this case, like all other transports. - TBufferedTransport::read() could block some data was available in the readahead buffer, but not enough to satisfy the request. It would attempt to call read() on the underlying transport, but this might block. Now it just returns the remaining data in the readahead buffer. The caller is responsible for calling read() again to get the rest of the data they want. - TFrameTransport::read() threw an exception if read() on the underlying transport returned 0 when looking for a frame header. Now TFrameTransport::read() returns 0, too. (It still throws an exception if the underlying transport returns 0 after a partial frame or frame header has been read.) - TFDTransport::read() threw an exception on EINTR. Now it retries up to 5 times, similarly to the way TSocket::read() behaves. - TZlibTransport::read() could block when less data than was requested is available. Now it only calls read() on the underlying transport when it would otherwise have nothing to return. This does mean that TZlibTransport::read() now often returns less data than is actually available at the time. This required updating several of the ZlibTest tests to use readAll() instead of read(), since they previously assumed read() would return all available data. git-svn-id: https://svn.apache.org/repos/asf/incubator/thrift/trunk@1005161 13f79535-47bb-0310-9956-ffa450edef68
2010-10-06 17:10:40 +00:00
snprintf(name, sizeof(name), "%s::test_read_partial_midframe()",
transportName);
tc = boost::unit_test::make_test_case(
test_read_partial_midframe<CoupledTransports>, name);
suite_->add(tc, expectedFailures);
THRIFT-929. cpp: Add tests to verify blocking read behavior Add tests that check to see whether or not read() and borrow() block when called with a length larger than the amount of data currently available. At the moment, not all of the transports behave the same way. I believe the desired behavior is: When M bytes are available, and 0 < M < N: - read(N): return M bytes immediately - borrow(N): return NULL immediately When 0 bytes are available: - read(N): In this case, it is acceptable either to immediately return 0, or to block until some data is available. If the transport blocks, it returns immediately when some date becomes available, even if less than N bytes are available. - borrow(N): return NULL immediately - The borrow() tests fail when using TBufferedTransport. TBufferedTransport incorrectly blocks until the amount of data requested is available. - test_read_none_available() fails when using TFramedTransport. Calling read() on a TFramedTransport when no data is available throws an exception instead of returning 0. - test_read_none_available() fails when using TFDTransport. This is partly just an artifact of the fact that I use SIGALRM as part of this test. Unlike TSocket, TFDTransport doesn't retry after EINTR. - test_read_part_available() fails when using TZlibTransport around a transport that has blocking read() behavior. TZlibTransport::read() loops calling read() on the underlying transport. It should probably break out of the loop and return to the caller as soon as it has uncompressed any data, even if it is less than requested and more might be available. Once some data has been uncompressed, TZlibTransport cannot risk calling read() again since it might block. Will commit fixes for these separately. git-svn-id: https://svn.apache.org/repos/asf/incubator/thrift/trunk@1005160 13f79535-47bb-0310-9956-ffa450edef68
2010-10-06 17:10:38 +00:00
snprintf(name, sizeof(name), "%s::test_read_none_available()",
transportName);
tc = boost::unit_test::make_test_case(
test_read_none_available<CoupledTransports>, name);
suite_->add(tc, expectedFailures);
snprintf(name, sizeof(name), "%s::test_borrow_part_available()",
transportName);
tc = boost::unit_test::make_test_case(
test_borrow_part_available<CoupledTransports>, name);
suite_->add(tc, expectedFailures);
snprintf(name, sizeof(name), "%s::test_borrow_none_available()",
transportName);
tc = boost::unit_test::make_test_case(
test_borrow_none_available<CoupledTransports>, name);
suite_->add(tc, expectedFailures);
}
boost::unit_test::test_suite* suite_;
// sizeMultiplier_ is configurable via the command line, and allows the
// user to adjust between smaller buffers that can be tested quickly,
// or larger buffers that more thoroughly exercise the code, but take
// longer.
float sizeMultiplier_;
};
/**************************************************************************
* General Initialization
**************************************************************************/
void print_usage(FILE* f, const char* argv0) {
fprintf(f, "Usage: %s [boost_options] [options]\n", argv0);
fprintf(f, "Options:\n");
fprintf(f, " --seed=<N>, -s <N>\n");
fprintf(f, " --tmp-dir=DIR, -t DIR\n");
fprintf(f, " --help\n");
}
struct Options {
int seed;
bool haveSeed;
float sizeMultiplier;
};
void parse_args(int argc, char* argv[], Options* options) {
bool have_seed = false;
options->sizeMultiplier = 1;
struct option long_opts[] = {
{ "help", false, NULL, 'h' },
{ "seed", true, NULL, 's' },
{ "tmp-dir", true, NULL, 't' },
{ "size-multiplier", true, NULL, 'x' },
{ NULL, 0, NULL, 0 }
};
while (true) {
optopt = 1;
int optchar = getopt_long(argc, argv, "hs:t:x:", long_opts, NULL);
if (optchar == -1) {
break;
}
switch (optchar) {
case 't':
tmp_dir = optarg;
break;
case 's': {
char *endptr;
options->seed = strtol(optarg, &endptr, 0);
if (endptr == optarg || *endptr != '\0') {
fprintf(stderr, "invalid seed value \"%s\": must be an integer\n",
optarg);
exit(1);
}
have_seed = true;
break;
}
case 'h':
print_usage(stdout, argv[0]);
exit(0);
case 'x': {
char *endptr;
options->sizeMultiplier = strtof(optarg, &endptr);
if (endptr == optarg || *endptr != '\0') {
fprintf(stderr, "invalid size multiplier \"%s\": must be a number\n",
optarg);
exit(1);
}
if (options->sizeMultiplier < 0) {
fprintf(stderr, "invalid size multiplier \"%s\": "
"must be non-negative\n", optarg);
exit(1);
}
break;
}
case '?':
exit(1);
default:
// Only happens if someone adds another option to the optarg string,
// but doesn't update the switch statement to handle it.
fprintf(stderr, "unknown option \"-%c\"\n", optchar);
exit(1);
}
}
if (!have_seed) {
// choose a seed now if the user didn't specify one
struct timeval tv;
struct timezone tz;
gettimeofday(&tv, &tz);
options->seed = tv.tv_sec ^ tv.tv_usec;
}
}
boost::unit_test::test_suite* init_unit_test_suite(int argc, char* argv[]) {
// Parse arguments
Options options;
parse_args(argc, argv, &options);
initrand(options.seed);
boost::unit_test::test_suite* suite =
&boost::unit_test::framework::master_test_suite();
suite->p_name.value = "TransportTest";
TransportTestGen transport_test_generator(suite, options.sizeMultiplier);
transport_test_generator.generate();
return NULL;
}