diff --git a/src/corelib/global/qhooks.cpp b/src/corelib/global/qhooks.cpp index 40a7c88f13c1c895fadd754e6be4a9ee20f27757..88df9c2be9835f31fffac88e4209f1ec80fb9d92 100644 --- a/src/corelib/global/qhooks.cpp +++ b/src/corelib/global/qhooks.cpp @@ -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])); diff --git a/src/corelib/io/qfiledevice.cpp b/src/corelib/io/qfiledevice.cpp index 4c5ed0aef6619c94b0e7ca2e353a29c07bca5514..e94da6de64c20c1da78c88ce121f058774160260 100644 --- a/src/corelib/io/qfiledevice.cpp +++ b/src/corelib/io/qfiledevice.cpp @@ -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()) diff --git a/src/corelib/io/qiodevice.cpp b/src/corelib/io/qiodevice.cpp index 64078b5c54044e25b0056e4ad09e445e44810603..00b50af5cabd6a05cc8d167f1d3c937519c59a19 100644 --- a/src/corelib/io/qiodevice.cpp +++ b/src/corelib/io/qiodevice.cpp @@ -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) + 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 (transactionStarted) { + const qint64 savedTransactionPos = transactionPos; + const qint64 savedPos = pos; + + QByteArray result = q->read(maxSize); - if (result.isEmpty()) + // 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; } diff --git a/src/corelib/io/qiodevice.h b/src/corelib/io/qiodevice.h index b62c8d266c20649a157402b70426caea6e0121c3..49f6848540c0e73e3f5027ee253b31c6f15fd411 100644 --- a/src/corelib/io/qiodevice.h +++ b/src/corelib/io/qiodevice.h @@ -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) diff --git a/src/corelib/io/qiodevice_p.h b/src/corelib/io/qiodevice_p.h index 56a89ab680ac52da218cf0207b2115c1e6dab162..45f5219240cb4951f31943cfbef3ad7d236563a2 100644 --- a/src/corelib/io/qiodevice_p.h +++ b/src/corelib/io/qiodevice_p.h @@ -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); diff --git a/src/corelib/io/qprocess.cpp b/src/corelib/io/qprocess.cpp index 823dc4c144a36b17b7b383ca847da2809b5b885f..2392f7cbfd90f94c60a874637efde35eb02c38db 100644 --- a/src/corelib/io/qprocess.cpp +++ b/src/corelib/io/qprocess.cpp @@ -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; } diff --git a/src/network/ssl/qsslsocket.cpp b/src/network/ssl/qsslsocket.cpp index 1dfd87a0f85721f66b1780c244da7e33c7ac98da..6f3ed58e189f294ef42394fc35a930ed6677d19e 100644 --- a/src/network/ssl/qsslsocket.cpp +++ b/src/network/ssl/qsslsocket.cpp @@ -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 diff --git a/tests/auto/corelib/io/qiodevice/tst_qiodevice.cpp b/tests/auto/corelib/io/qiodevice/tst_qiodevice.cpp index 334f5aba05ad631bf679610aa870b94b7a7715a7..9271630b326d00a3a1506987c46da3c51a346ccc 100644 --- a/tests/auto/corelib/io/qiodevice/tst_qiodevice.cpp +++ b/tests/auto/corelib/io/qiodevice/tst_qiodevice.cpp @@ -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" diff --git a/tests/auto/other/toolsupport/tst_toolsupport.cpp b/tests/auto/other/toolsupport/tst_toolsupport.cpp index 7942a8461524dc54ff2127b732baa294b0c5f1bd..93cbb2809d797d7405835d430bf8a98323da706e 100644 --- a/tests/auto/other/toolsupport/tst_toolsupport.cpp +++ b/tests/auto/other/toolsupport/tst_toolsupport.cpp @@ -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