QIODevice: handle incomplete reads

Introduce a transaction mechanism that gives the ability to read the
data atomically. Current implementation supports transactions for both
types of devices. For sequential devices, it records the whole input
stream during transaction. For random-access devices, device position
is saved when transaction starts. If an error occurs, the application
may be able to recover the input stream by rolling back to the start
point.

Also, QIODevice::peek() was rewritten to make use of transactions
internally. The replacement of QIODevicePrivateLinearBuffer by
QRingBuffer is closely entangled with that, which makes it unfeasible
to do separately.

Bump the TypeInformationVersion field in qtHookData, to notify the
Qt Creator developers that the offset of QFilePrivate::fileName was
changed and dumpers should be adapted.

[ChangeLog][QtCore] Added QIODevice's startTransaction(),
commitTransaction(), rollbackTransaction(), isTransactionStarted()
functions to support the read transactions.

Task-number: QTBUG-44418
Change-Id: I3564b343ebeeaaf7c48a1dcdb7ef0a7ffec550f2
Reviewed-by: Joerg Bornemann <joerg.bornemann@theqtcompany.com>
Reviewed-by: Oswald Buddenhagen <oswald.buddenhagen@theqtcompany.com>
bb10
Alex Trotsenko 2015-06-02 22:01:44 +03:00
parent 98cea2b6c4
commit 7d257aab81
9 changed files with 360 additions and 251 deletions

View File

@ -61,7 +61,7 @@ quintptr Q_CORE_EXPORT qtHookData[] = {
// The required sizes and offsets are tested in tests/auto/other/toolsupport.
// When this fails and the change was intentional, adjust the test and
// adjust this value here.
0
1
};
Q_STATIC_ASSERT(QHooks::LastHookIndex == sizeof(qtHookData) / sizeof(qtHookData[0]));

View File

@ -355,7 +355,7 @@ bool QFileDevice::atEnd() const
Q_D(const QFileDevice);
// If there's buffered data left, we're not at the end.
if (!d->buffer.isEmpty())
if (!d->isBufferEmpty())
return false;
if (!isOpen())

View File

@ -143,7 +143,9 @@ static void checkWarnMessage(const QIODevice *device, const char *function, cons
*/
QIODevicePrivate::QIODevicePrivate()
: openMode(QIODevice::NotOpen), buffer(QIODEVICE_BUFFERSIZE),
pos(0), devicePos(0)
pos(0), devicePos(0),
transactionPos(0),
transactionStarted(false)
, baseReadLineDataCalled(false)
, accessMode(Unset)
#ifdef QT_NO_QOBJECT
@ -264,6 +266,12 @@ QIODevicePrivate::~QIODevicePrivate()
subclassing QIODevice, remember to bypass any buffer you may use
when the device is open in Unbuffered mode.
Usually, the incoming data stream from an asynchronous device is
fragmented, and chunks of data can arrive at arbitrary points in time.
To handle incomplete reads of data structures, use the transaction
mechanism implemented by QIODevice. See startTransaction() and related
functions for more details.
\sa QBuffer, QFile, QTcpSocket
*/
@ -588,6 +596,8 @@ void QIODevice::close()
d->openMode = NotOpen;
d->errorString.clear();
d->pos = 0;
d->transactionStarted = false;
d->transactionPos = 0;
d->buffer.clear();
}
@ -660,18 +670,8 @@ bool QIODevice::seek(qint64 pos)
this, pos, d->pos, d->buffer.size());
#endif
qint64 offset = pos - d->pos;
d->pos = pos;
d->devicePos = pos;
if (offset < 0 || offset >= d->buffer.size())
// When seeking backwards, an operation that is only allowed for
// random-access devices, the buffer is cleared. The next read
// operation will then refill the buffer. We can optimize this, if we
// find that seeking backwards becomes a significant performance hit.
d->buffer.clear();
else
d->buffer.skip(offset);
d->seekBuffer(pos);
#if defined QIODEVICE_DEBUG
printf("%p \tafter: d->pos == %lld, d->buffer.size() == %lld\n", this, d->pos,
@ -680,6 +680,24 @@ bool QIODevice::seek(qint64 pos)
return true;
}
/*!
\internal
*/
void QIODevicePrivate::seekBuffer(qint64 newPos)
{
const qint64 offset = newPos - pos;
pos = newPos;
if (offset < 0 || offset >= buffer.size()) {
// When seeking backwards, an operation that is only allowed for
// random-access devices, the buffer is cleared. The next read
// operation will then refill the buffer.
buffer.clear();
} else {
buffer.free(offset);
}
}
/*!
Returns \c true if the current read and write position is at the end
of the device (i.e. there is no more data available for reading on
@ -695,7 +713,7 @@ bool QIODevice::seek(qint64 pos)
bool QIODevice::atEnd() const
{
Q_D(const QIODevice);
const bool result = (d->openMode == NotOpen || (d->buffer.isEmpty()
const bool result = (d->openMode == NotOpen || (d->isBufferEmpty()
&& bytesAvailable() == 0));
#if defined QIODEVICE_DEBUG
printf("%p QIODevice::atEnd() returns %s, d->openMode == %d, d->pos == %lld\n", this,
@ -740,7 +758,7 @@ qint64 QIODevice::bytesAvailable() const
Q_D(const QIODevice);
if (!d->isSequential())
return qMax(size() - d->pos, qint64(0));
return d->buffer.size();
return d->buffer.size() - d->transactionPos;
}
/*! For buffered devices, this function returns the number of bytes
@ -777,9 +795,10 @@ qint64 QIODevice::read(char *data, qint64 maxSize)
#endif
const bool sequential = d->isSequential();
const bool keepDataInBuffer = sequential && d->transactionStarted;
// Short circuit for getChar()
if (maxSize == 1) {
if (maxSize == 1 && !keepDataInBuffer) {
int chint;
while ((chint = d->buffer.getChar()) != -1) {
if (!sequential)
@ -806,9 +825,13 @@ qint64 QIODevice::read(char *data, qint64 maxSize)
char *readPtr = data;
forever {
// Try reading from the buffer.
qint64 bufferReadChunkSize = d->buffer.read(data, maxSize);
qint64 bufferReadChunkSize = keepDataInBuffer
? d->buffer.peek(data, maxSize, d->transactionPos)
: d->buffer.read(data, maxSize);
if (bufferReadChunkSize > 0) {
if (!sequential)
if (keepDataInBuffer)
d->transactionPos += bufferReadChunkSize;
else if (!sequential)
d->pos += bufferReadChunkSize;
readSoFar += bufferReadChunkSize;
data += bufferReadChunkSize;
@ -826,7 +849,8 @@ qint64 QIODevice::read(char *data, qint64 maxSize)
// Make sure the device is positioned correctly.
if (sequential || d->pos == d->devicePos || seek(d->pos)) {
madeBufferReadsOnly = false; // fix readData attempt
if (maxSize >= QIODEVICE_BUFFERSIZE || (d->openMode & Unbuffered)) {
if ((maxSize >= QIODEVICE_BUFFERSIZE || (d->openMode & Unbuffered))
&& !keepDataInBuffer) {
// Read big chunk directly to output buffer
readFromDevice = readData(data, maxSize);
deviceAtEof = (readFromDevice != maxSize);
@ -844,7 +868,9 @@ qint64 QIODevice::read(char *data, qint64 maxSize)
}
}
} else {
const qint64 bytesToBuffer = QIODEVICE_BUFFERSIZE;
// Do not read more than maxSize on unbuffered devices
const qint64 bytesToBuffer = (d->openMode & Unbuffered)
? qMin(maxSize, QIODEVICE_BUFFERSIZE) : QIODEVICE_BUFFERSIZE;
// Try to fill QIODevice buffer by single read
readFromDevice = readData(d->buffer.reserve(bytesToBuffer), bytesToBuffer);
deviceAtEof = (readFromDevice != bytesToBuffer);
@ -907,10 +933,8 @@ qint64 QIODevice::read(char *data, qint64 maxSize)
debugBinaryString(data - readSoFar, readSoFar);
#endif
if (madeBufferReadsOnly && d->buffer.isEmpty()) {
d->buffer.clear();
if (madeBufferReadsOnly && d->isBufferEmpty())
readData(data, 0);
}
return readSoFar;
}
@ -992,7 +1016,9 @@ QByteArray QIODevice::readAll()
qint64 readBytes = (d->isSequential() ? Q_INT64_C(0) : size());
if (readBytes == 0) {
// Size is unknown, read incrementally.
qint64 readChunkSize = qMax(d->buffer.size(), QIODEVICE_BUFFERSIZE);
qint64 readChunkSize = qMax(QIODEVICE_BUFFERSIZE,
d->isSequential() ? (d->buffer.size() - d->transactionPos)
: d->buffer.size());
qint64 readResult;
do {
if (readBytes + readChunkSize >= MaxByteArraySize) {
@ -1077,21 +1103,35 @@ qint64 QIODevice::readLine(char *data, qint64 maxSize)
--maxSize;
const bool sequential = d->isSequential();
const bool keepDataInBuffer = sequential && d->transactionStarted;
qint64 readSoFar = 0;
if (!d->buffer.isEmpty()) {
readSoFar = d->buffer.readLine(data, maxSize);
if (keepDataInBuffer) {
if (d->transactionPos < d->buffer.size()) {
// Peek line from the specified position
const qint64 i = d->buffer.indexOf('\n', maxSize, d->transactionPos);
readSoFar = d->buffer.peek(data, i >= 0 ? (i - d->transactionPos + 1) : maxSize,
d->transactionPos);
d->transactionPos += readSoFar;
if (d->transactionPos == d->buffer.size())
readData(data, 0);
}
} else if (!d->buffer.isEmpty()) {
// QRingBuffer::readLine() terminates the line with '\0'
readSoFar = d->buffer.readLine(data, maxSize + 1);
if (d->buffer.isEmpty())
readData(data,0);
if (!sequential)
d->pos += readSoFar;
}
if (readSoFar) {
#if defined QIODEVICE_DEBUG
printf("%p \tread from buffer: %lld bytes, last character read: %hhx\n", this,
readSoFar, data[readSoFar - 1]);
if (readSoFar)
debugBinaryString(data, int(readSoFar));
debugBinaryString(data, int(readSoFar));
#endif
if (readSoFar && data[readSoFar - 1] == '\n') {
if (data[readSoFar - 1] == '\n') {
if (d->openMode & Text) {
// QRingBuffer::readLine() isn't Text aware.
if (readSoFar > 1 && data[readSoFar - 2] == '\r') {
@ -1107,7 +1147,11 @@ qint64 QIODevice::readLine(char *data, qint64 maxSize)
if (d->pos != d->devicePos && !sequential && !seek(d->pos))
return qint64(-1);
d->baseReadLineDataCalled = false;
qint64 readBytes = readLineData(data + readSoFar, maxSize - readSoFar);
// Force base implementation for transaction on sequential device
// as it stores the data in internal buffer automatically.
qint64 readBytes = keepDataInBuffer
? QIODevice::readLineData(data + readSoFar, maxSize - readSoFar)
: readLineData(data + readSoFar, maxSize - readSoFar);
#if defined QIODEVICE_DEBUG
printf("%p \tread from readLineData: %lld bytes, readSoFar = %lld bytes\n", this,
readBytes, readSoFar);
@ -1262,7 +1306,95 @@ qint64 QIODevice::readLineData(char *data, qint64 maxSize)
*/
bool QIODevice::canReadLine() const
{
return d_func()->buffer.canReadLine();
Q_D(const QIODevice);
return d->buffer.indexOf('\n', d->buffer.size(),
d->isSequential() ? d->transactionPos : Q_INT64_C(0)) >= 0;
}
/*!
\since 5.7
Starts a new read transaction on the device.
Defines a restorable point within the sequence of read operations. For
sequential devices, read data will be duplicated internally to allow
recovery in case of incomplete reads. For random-access devices,
this function saves the current position. Call commitTransaction() or
rollbackTransaction() to finish the transaction.
\note Nesting transactions is not supported.
\sa commitTransaction(), rollbackTransaction()
*/
void QIODevice::startTransaction()
{
Q_D(QIODevice);
if (d->transactionStarted) {
checkWarnMessage(this, "startTransaction", "Called while transaction already in progress");
return;
}
d->transactionPos = d->pos;
d->transactionStarted = true;
}
/*!
\since 5.7
Completes a read transaction.
For sequential devices, all data recorded in the internal buffer during
the transaction will be discarded.
\sa startTransaction(), rollbackTransaction()
*/
void QIODevice::commitTransaction()
{
Q_D(QIODevice);
if (!d->transactionStarted) {
checkWarnMessage(this, "commitTransaction", "Called while no transaction in progress");
return;
}
if (d->isSequential())
d->buffer.free(d->transactionPos);
d->transactionStarted = false;
d->transactionPos = 0;
}
/*!
\since 5.7
Rolls back a read transaction.
Restores the input stream to the point of the startTransaction() call.
This function is commonly used to rollback the transaction when an
incomplete read was detected prior to committing the transaction.
\sa startTransaction(), commitTransaction()
*/
void QIODevice::rollbackTransaction()
{
Q_D(QIODevice);
if (!d->transactionStarted) {
checkWarnMessage(this, "rollbackTransaction", "Called while no transaction in progress");
return;
}
if (!d->isSequential())
d->seekBuffer(d->transactionPos);
d->transactionStarted = false;
d->transactionPos = 0;
}
/*!
\since 5.7
Returns \c true if a transaction is in progress on the device, otherwise
\c false.
\sa startTransaction()
*/
bool QIODevice::isTransactionStarted() const
{
return d_func()->transactionStarted;
}
/*!
@ -1386,12 +1518,19 @@ qint64 QIODevice::write(const char *data)
If \a c was not previously read from the device, the behavior is
undefined.
\note This function is not available while a transaction is in progress.
*/
void QIODevice::ungetChar(char c)
{
Q_D(QIODevice);
CHECK_READABLE(read, Q_VOID);
if (d->transactionStarted) {
checkWarnMessage(this, "ungetChar", "Called while transaction is in progress");
return;
}
#if defined QIODEVICE_DEBUG
printf("%p QIODevice::ungetChar(0x%hhx '%c')\n", this, c, isprint(c) ? c : '?');
#endif
@ -1426,13 +1565,26 @@ bool QIODevicePrivate::putCharHelper(char c)
*/
qint64 QIODevicePrivate::peek(char *data, qint64 maxSize)
{
qint64 readBytes = q_func()->read(data, maxSize);
if (readBytes <= 0)
return readBytes;
Q_Q(QIODevice);
if (transactionStarted) {
const qint64 savedTransactionPos = transactionPos;
const qint64 savedPos = pos;
qint64 readBytes = q->read(data, maxSize);
// Restore initial position
if (isSequential())
transactionPos = savedTransactionPos;
else
seekBuffer(savedPos);
return readBytes;
}
q->startTransaction();
qint64 readBytes = q->read(data, maxSize);
q->rollbackTransaction();
buffer.ungetBlock(data, readBytes);
if (!isSequential())
pos -= readBytes;
return readBytes;
}
@ -1441,14 +1593,26 @@ qint64 QIODevicePrivate::peek(char *data, qint64 maxSize)
*/
QByteArray QIODevicePrivate::peek(qint64 maxSize)
{
QByteArray result = q_func()->read(maxSize);
Q_Q(QIODevice);
if (result.isEmpty())
if (transactionStarted) {
const qint64 savedTransactionPos = transactionPos;
const qint64 savedPos = pos;
QByteArray result = q->read(maxSize);
// Restore initial position
if (isSequential())
transactionPos = savedTransactionPos;
else
seekBuffer(savedPos);
return result;
}
q->startTransaction();
QByteArray result = q->read(maxSize);
q->rollbackTransaction();
buffer.ungetBlock(result.constData(), result.size());
if (!isSequential())
pos -= result.size();
return result;
}

View File

@ -111,6 +111,11 @@ public:
QByteArray readLine(qint64 maxlen = 0);
virtual bool canReadLine() const;
void startTransaction();
void commitTransaction();
void rollbackTransaction();
bool isTransactionStarted() const;
qint64 write(const char *data, qint64 len);
qint64 write(const char *data);
inline qint64 write(const QByteArray &data)

View File

@ -62,138 +62,6 @@ QT_BEGIN_NAMESPACE
Q_CORE_EXPORT int qt_subtract_from_timeout(int timeout, int elapsed);
// This is QIODevice's read buffer, optimized for read(), isEmpty() and getChar()
class QIODevicePrivateLinearBuffer
{
public:
QIODevicePrivateLinearBuffer(int) : len(0), first(0), buf(0), capacity(0) {
}
~QIODevicePrivateLinearBuffer() {
delete [] buf;
}
void clear() {
len = 0;
delete [] buf;
buf = 0;
first = buf;
capacity = 0;
}
qint64 size() const {
return len;
}
bool isEmpty() const {
return len == 0;
}
void skip(qint64 n) {
if (n >= len) {
clear();
} else {
len -= n;
first += n;
}
}
int getChar() {
if (len == 0)
return -1;
int ch = uchar(*first);
len--;
first++;
return ch;
}
qint64 read(char* target, qint64 size) {
qint64 r = qMin(size, len);
memcpy(target, first, r);
len -= r;
first += r;
return r;
}
qint64 peek(char* target, qint64 size) {
qint64 r = qMin(size, len);
memcpy(target, first, r);
return r;
}
char* reserve(qint64 size) {
makeSpace(size + len, freeSpaceAtEnd);
char* writePtr = first + len;
len += size;
return writePtr;
}
void chop(qint64 size) {
if (size >= len) {
clear();
} else {
len -= size;
}
}
QByteArray readAll() {
QByteArray retVal(first, len);
clear();
return retVal;
}
qint64 readLine(char* target, qint64 size) {
qint64 r = qMin(size, len);
char* eol = static_cast<char*>(memchr(first, '\n', r));
if (eol)
r = 1+(eol-first);
memcpy(target, first, r);
len -= r;
first += r;
return r;
}
bool canReadLine() const {
return memchr(first, '\n', len);
}
void ungetChar(char c) {
if (first == buf) {
// underflow, the existing valid data needs to move to the end of the (potentially bigger) buffer
makeSpace(len+1, freeSpaceAtStart);
}
first--;
len++;
*first = c;
}
void ungetBlock(const char* block, qint64 size) {
if ((first - buf) < size) {
// underflow, the existing valid data needs to move to the end of the (potentially bigger) buffer
makeSpace(len + size, freeSpaceAtStart);
}
first -= size;
len += size;
memcpy(first, block, size);
}
private:
enum FreeSpacePos {freeSpaceAtStart, freeSpaceAtEnd};
void makeSpace(size_t required, FreeSpacePos where) {
size_t newCapacity = qMax(capacity, size_t(QIODEVICE_BUFFERSIZE));
while (newCapacity < required)
newCapacity *= 2;
const size_t moveOffset = (where == freeSpaceAtEnd) ? 0 : newCapacity - size_t(len);
if (newCapacity > capacity) {
// allocate more space
char* newBuf = new char[newCapacity];
memmove(newBuf + moveOffset, first, len);
delete [] buf;
buf = newBuf;
capacity = newCapacity;
} else {
// shift any existing data to make space
memmove(buf + moveOffset, first, len);
}
first = buf + moveOffset;
}
private:
// length of the unread data
qint64 len;
// start of the unread data
char* first;
// the allocated buffer
char* buf;
// allocated buffer size
size_t capacity;
};
class Q_CORE_EXPORT QIODevicePrivate
#ifndef QT_NO_QOBJECT
: public QObjectPrivate
@ -208,9 +76,11 @@ public:
QIODevice::OpenMode openMode;
QString errorString;
QIODevicePrivateLinearBuffer buffer;
QRingBuffer buffer;
qint64 pos;
qint64 devicePos;
qint64 transactionPos;
bool transactionStarted;
bool baseReadLineDataCalled;
virtual bool putCharHelper(char c);
@ -228,6 +98,13 @@ public:
return accessMode == Sequential;
}
inline bool isBufferEmpty() const
{
return buffer.isEmpty() || (transactionStarted && isSequential()
&& transactionPos == buffer.size());
}
void seekBuffer(qint64 newPos);
virtual qint64 peek(char *data, qint64 maxSize);
virtual QByteArray peek(qint64 maxSize);

View File

@ -1395,15 +1395,17 @@ QProcess::ProcessChannel QProcess::readChannel() const
void QProcess::setReadChannel(ProcessChannel channel)
{
Q_D(QProcess);
if (d->transactionStarted) {
qWarning("QProcess::setReadChannel: Failed due to the active read transaction");
return;
}
if (d->processChannel != channel) {
QByteArray buf = d->buffer.readAll();
if (d->processChannel == QProcess::StandardOutput) {
for (int i = buf.size() - 1; i >= 0; --i)
d->stdoutChannel.buffer.ungetChar(buf.at(i));
} else {
for (int i = buf.size() - 1; i >= 0; --i)
d->stderrChannel.buffer.ungetChar(buf.at(i));
}
QRingBuffer *buffer = (d->processChannel == QProcess::StandardOutput)
? &d->stdoutChannel.buffer
: &d->stderrChannel.buffer;
d->buffer.read(buffer->reserveFront(d->buffer.size()), d->buffer.size());
}
d->processChannel = channel;
}

View File

@ -2531,7 +2531,7 @@ qint64 QSslSocketPrivate::peek(char *data, qint64 maxSize)
if (mode == QSslSocket::UnencryptedMode && !autoStartHandshake) {
//unencrypted mode - do not use QIODevice::peek, as it reads ahead data from the plain socket
//peek at data already in the QIODevice buffer (from a previous read)
qint64 r = buffer.peek(data, maxSize);
qint64 r = buffer.peek(data, maxSize, transactionPos);
if (r == maxSize)
return r;
data += r;
@ -2560,7 +2560,7 @@ QByteArray QSslSocketPrivate::peek(qint64 maxSize)
//peek at data already in the QIODevice buffer (from a previous read)
QByteArray ret;
ret.reserve(maxSize);
ret.resize(buffer.peek(ret.data(), maxSize));
ret.resize(buffer.peek(ret.data(), maxSize, transactionPos));
if (ret.length() == maxSize)
return ret;
//peek at data in the plain socket

View File

@ -57,9 +57,11 @@ private slots:
void readLine2_data();
void readLine2();
void peekBug();
void readAllKeepPosition();
void writeInTextMode();
void transaction_data();
void transaction();
};
void tst_QIODevice::initTestCase()
@ -528,77 +530,23 @@ void tst_QIODevice::readLine2()
}
}
class PeekBug : public QIODevice {
Q_OBJECT
public:
char alphabet[27];
qint64 counter;
PeekBug() : QIODevice(), counter(0) {
memcpy(alphabet,"abcdefghijklmnopqrstuvqxyz",27);
};
qint64 readData(char *data, qint64 maxlen) {
qint64 pos = 0;
while (pos < maxlen) {
*(data + pos) = alphabet[counter];
pos++;
counter++;
if (counter == 26)
counter = 0;
}
return maxlen;
}
qint64 writeData(const char * /* data */, qint64 /* maxlen */) {
return -1;
}
};
// This is a regression test for an old bug where peeking at
// more than one character failed to put them back.
void tst_QIODevice::peekBug()
{
PeekBug peekBug;
peekBug.open(QIODevice::ReadOnly | QIODevice::Unbuffered);
char onetwo[2];
peekBug.peek(onetwo, 2);
QCOMPARE(onetwo[0], 'a');
QCOMPARE(onetwo[1], 'b');
peekBug.read(onetwo, 1);
QCOMPARE(onetwo[0], 'a');
peekBug.peek(onetwo, 2);
QCOMPARE(onetwo[0], 'b');
QCOMPARE(onetwo[1], 'c');
peekBug.read(onetwo, 1);
QCOMPARE(onetwo[0], 'b');
peekBug.read(onetwo, 1);
QCOMPARE(onetwo[0], 'c');
peekBug.read(onetwo, 1);
QCOMPARE(onetwo[0], 'd');
peekBug.peek(onetwo, 2);
QCOMPARE(onetwo[0], 'e');
QCOMPARE(onetwo[1], 'f');
}
class SequentialReadBuffer : public QIODevice
{
public:
SequentialReadBuffer(const char *data) : QIODevice(), buf(data), offset(0) { }
SequentialReadBuffer(const char *data)
: QIODevice(), buf(new QByteArray(data)), offset(0), ownbuf(true) { }
SequentialReadBuffer(QByteArray *byteArray)
: QIODevice(), buf(byteArray), offset(0), ownbuf(false) { }
virtual ~SequentialReadBuffer() { if (ownbuf) delete buf; }
bool isSequential() const Q_DECL_OVERRIDE { return true; }
const QByteArray &buffer() const { return buf; }
const QByteArray &buffer() const { return *buf; }
protected:
qint64 readData(char *data, qint64 maxSize) Q_DECL_OVERRIDE
{
maxSize = qMin(maxSize, qint64(buf.size() - offset));
memcpy(data, buf.constData() + offset, maxSize);
maxSize = qMin(maxSize, qint64(buf->size() - offset));
memcpy(data, buf->constData() + offset, maxSize);
offset += maxSize;
return maxSize;
}
@ -608,8 +556,9 @@ protected:
}
private:
QByteArray buf;
QByteArray *buf;
int offset;
bool ownbuf;
};
// Test readAll() on position change for sequential device
@ -669,5 +618,117 @@ void tst_QIODevice::writeInTextMode()
#endif
}
void tst_QIODevice::transaction_data()
{
QTest::addColumn<bool>("sequential");
QTest::addColumn<qint8>("i8Data");
QTest::addColumn<qint16>("i16Data");
QTest::addColumn<qint32>("i32Data");
QTest::addColumn<qint64>("i64Data");
QTest::addColumn<bool>("bData");
QTest::addColumn<float>("fData");
QTest::addColumn<double>("dData");
QTest::addColumn<QByteArray>("strData");
bool sequential = true;
do {
QByteArray devName(sequential ? "sequential" : "random-access");
QTest::newRow(qPrintable(devName + '1')) << sequential << qint8(1) << qint16(2)
<< qint32(3) << qint64(4) << true
<< 5.0f << double(6.0)
<< QByteArray("Hello world!");
QTest::newRow(qPrintable(devName + '2')) << sequential << qint8(1 << 6) << qint16(1 << 14)
<< qint32(1 << 30) << (qint64(1) << 62) << false
<< 123.0f << double(234.0)
<< QByteArray("abcdefghijklmnopqrstuvwxyz");
QTest::newRow(qPrintable(devName + '3')) << sequential << qint8(-1) << qint16(-2)
<< qint32(-3) << qint64(-4) << true
<< -123.0f << double(-234.0)
<< QByteArray("Qt rocks!");
sequential = !sequential;
} while (!sequential);
}
// Test transaction integrity
void tst_QIODevice::transaction()
{
QByteArray testBuffer;
QFETCH(bool, sequential);
QFETCH(qint8, i8Data);
QFETCH(qint16, i16Data);
QFETCH(qint32, i32Data);
QFETCH(qint64, i64Data);
QFETCH(bool, bData);
QFETCH(float, fData);
QFETCH(double, dData);
QFETCH(QByteArray, strData);
{
QDataStream stream(&testBuffer, QIODevice::WriteOnly);
stream << i8Data << i16Data << i32Data << i64Data
<< bData << fData << dData << strData.constData();
}
for (int splitPos = 0; splitPos <= testBuffer.size(); ++splitPos) {
QByteArray readBuffer(testBuffer.left(splitPos));
QIODevice *dev = sequential ? (QIODevice *) new SequentialReadBuffer(&readBuffer)
: (QIODevice *) new QBuffer(&readBuffer);
dev->open(QIODevice::ReadOnly);
QDataStream stream(dev);
qint8 i8;
qint16 i16;
qint32 i32;
qint64 i64;
bool b;
float f;
double d;
char *str;
forever {
QVERIFY(!dev->isTransactionStarted());
dev->startTransaction();
QVERIFY(dev->isTransactionStarted());
// Try to read all data in one go. If the status of the data stream
// indicates an unsuccessful operation, restart a read transaction
// on the completed buffer.
stream >> i8 >> i16 >> i32 >> i64 >> b >> f >> d >> str;
QVERIFY(stream.atEnd());
if (stream.status() == QDataStream::Ok) {
dev->commitTransaction();
break;
}
dev->rollbackTransaction();
QVERIFY(splitPos == 0 || !stream.atEnd());
QCOMPARE(dev->pos(), Q_INT64_C(0));
QCOMPARE(dev->bytesAvailable(), qint64(readBuffer.size()));
QVERIFY(readBuffer.size() < testBuffer.size());
delete [] str;
readBuffer.append(testBuffer.right(testBuffer.size() - splitPos));
stream.resetStatus();
}
QVERIFY(!dev->isTransactionStarted());
QVERIFY(stream.atEnd());
QCOMPARE(i8, i8Data);
QCOMPARE(i16, i16Data);
QCOMPARE(i32, i32Data);
QCOMPARE(i64, i64Data);
QCOMPARE(b, bData);
QCOMPARE(f, fData);
QCOMPARE(d, dData);
QVERIFY(strData == str);
delete [] str;
stream.setDevice(0);
delete dev;
}
}
QTEST_MAIN(tst_QIODevice)
#include "tst_qiodevice.moc"

View File

@ -124,7 +124,7 @@ void tst_toolsupport::offsets_data()
{
QTestData &data = QTest::newRow("QFilePrivate::fileName")
<< pmm_to_offsetof(&QFilePrivate::fileName);
data << 168 << 248;
data << 184 << 256;
}
#endif