QDataStream: handle incomplete reads from QIODevice

This adds a way to resume reading from a stream after a ReadPastEnd error.
This is done by introducing a stream read transaction mechanism that keeps
read data in an internal buffer and rolls it back on failure.

[ChangeLog][QtCore] Added QDataStream startTransaction(),
commitTransaction(), rollbackTransaction(), abortTransaction()
functions to support read transactions.

Task-number: QTBUG-44418
Change-Id: Ibf946e1939a5573c4182fea7e26608947218c2d9
Reviewed-by: Oswald Buddenhagen <oswald.buddenhagen@theqtcompany.com>
This commit is contained in:
Alex Trotsenko 2015-03-05 10:52:17 +02:00
parent ca6f11dcf2
commit 184d66caa5
13 changed files with 435 additions and 129 deletions

View File

@ -96,37 +96,27 @@ void FortuneThread::run()
emit error(socket.error(), socket.errorString());
return;
}
//! [8] //! [9]
//! [8] //! [11]
while (socket.bytesAvailable() < (int)sizeof(quint16)) {
if (!socket.waitForReadyRead(Timeout)) {
emit error(socket.error(), socket.errorString());
return;
}
//! [9] //! [10]
}
//! [10] //! [11]
quint16 blockSize;
QDataStream in(&socket);
in.setVersion(QDataStream::Qt_4_0);
in >> blockSize;
QString fortune;
//! [11] //! [12]
while (socket.bytesAvailable() < blockSize) {
do {
if (!socket.waitForReadyRead(Timeout)) {
emit error(socket.error(), socket.errorString());
return;
}
//! [12] //! [13]
}
//! [13] //! [14]
in.startTransaction();
in >> fortune;
} while (!in.commitTransaction());
//! [12] //! [15]
mutex.lock();
QString fortune;
in >> fortune;
emit newFortune(fortune);
//! [7] //! [14] //! [15]
//! [7]
cond.wait(&mutex);
serverName = hostName;

View File

@ -125,39 +125,22 @@
other \c waitFor...() functions, is part of QTcpSocket's \e{blocking
API}.
After this statement, we have a connected socket to work with. Now it's
time to see what the fortune server has sent us.
\snippet blockingfortuneclient/fortunethread.cpp 9
\snippet blockingfortuneclient/fortunethread.cpp 10
This step is to read the size of the packet. Although we are only reading
two bytes here, and the \c while loop may seem to overdo it, we present this
code to demonstrate a good pattern for waiting for data using
QTcpSocket::waitForReadyRead(). It goes like this: For as long as we still
need more data, we call waitForReadyRead(). If it returns false,
we abort the operation. After this statement, we know that we have received
enough data.
After this statement, we have a connected socket to work with.
\snippet blockingfortuneclient/fortunethread.cpp 11
Now we can create a QDataStream object, passing the socket to
QDataStream's constructor, and as in the other client examples we set
the stream protocol version to QDataStream::Qt_4_0, and read the size
of the packet.
the stream protocol version to QDataStream::Qt_4_0.
\snippet blockingfortuneclient/fortunethread.cpp 12
\snippet blockingfortuneclient/fortunethread.cpp 13
Again, we'll use a loop that waits for more data by calling
QTcpSocket::waitForReadyRead(). In this loop, we're waiting until
QTcpSocket::bytesAvailable() returns the full packet size.
\snippet blockingfortuneclient/fortunethread.cpp 14
Now that we have all the data that we need, we can use QDataStream to
read the fortune string from the packet. The resulting fortune is
delivered by emitting newFortune().
We proceed by initiating a loop that waits for the fortune string data by
calling QTcpSocket::waitForReadyRead(). If it returns false, we abort the
operation. After this statement, we start a stream read transaction. We
exit the loop when QDataStream::commitTransaction() returns true, which
means successful fortune string loading. The resulting fortune is
delivered by emitting newFortune():
\snippet blockingfortuneclient/fortunethread.cpp 15

View File

@ -41,8 +41,7 @@
request a line of text from a fortune server (from the
\l{fortuneserver}{Fortune Server} example). The client requests a
fortune by simply connecting to the server. The server then responds with
a 16-bit (quint16) integer containing the length of the fortune text,
followed by a QString.
a QString which contains the fortune text.
QTcpSocket supports two general approaches to network programming:
@ -71,8 +70,8 @@
\snippet fortuneclient/client.h 0
Other than the widgets that make up the GUI, the data members include a
QTcpSocket pointer, a copy of the fortune text currently displayed, and
the size of the packet we are currently reading (more on this later).
QTcpSocket pointer, a QDataStream object that operates on the socket, and
a copy of the fortune text currently displayed.
The socket is initialized in the Client constructor. We'll pass the main
widget as parent, so that we won't have to worry about deleting the
@ -82,6 +81,12 @@
\dots
\snippet fortuneclient/client.cpp 1
The protocol is based on QDataStream, so we set the stream device to the
newly created socket. We then explicitly set the protocol version of the
stream to QDataStream::Qt_4_0 to ensure that we're using the same version
as the fortune server, no matter which version of Qt the client and
server use.
The only QTcpSocket signals we need in this example are
QTcpSocket::readyRead(), signifying that data has been received, and
QTcpSocket::error(), which we will use to catch any connection errors:
@ -96,8 +101,7 @@
\snippet fortuneclient/client.cpp 6
In this slot, we initialize \c blockSize to 0, preparing to read a new block
of data. Because we allow the user to click \uicontrol{Get Fortune} before the
Because we allow the user to click \uicontrol{Get Fortune} before the
previous connection finished closing, we start off by aborting the
previous connection by calling QTcpSocket::abort(). (On an unconnected
socket, this function does nothing.) We then proceed to connecting to the
@ -131,31 +135,26 @@
signal is connected to \c Client::readFortune():
\snippet fortuneclient/client.cpp 8
\codeline
\snippet fortuneclient/client.cpp 10
The protocol is based on QDataStream, so we start by creating a stream
object, passing the socket to QDataStream's constructor. We then
explicitly set the protocol version of the stream to QDataStream::Qt_4_0
to ensure that we're using the same version as the fortune server, no
matter which version of Qt the client and server use.
Now, TCP is based on sending a stream of data, so we cannot expect to get
the entire fortune in one go. Especially on a slow network, the data can
be received in several small fragments. QTcpSocket buffers up all incoming
data and emits \l{QTcpSocket::readyRead()}{readyRead()} for every new
block that arrives, and it is our job to ensure that we have received all
the data we need before we start parsing. The server's response starts
with the size of the packet, so first we need to ensure that we can read
the size, then we will wait until QTcpSocket has received the full packet.
\snippet fortuneclient/client.cpp 11
\codeline
\snippet fortuneclient/client.cpp 12
the data we need before we start parsing.
For this purpose we use a QDataStream read transaction. It keeps reading
stream data into an internal buffer and rolls it back in case of an
incomplete read. We start by calling startTransaction() which also resets
the stream status to indicate that new data was received on the socket.
We proceed by using QDataStream's streaming operator to read the fortune
from the socket into a QString. Once read, we can call QLabel::setText()
to display the fortune.
from the socket into a QString. Once read, we complete the transaction by
calling QDataStream::commitTransaction(). If we did not receive a full
packet, this function restores the stream data to the initial position,
after which we can wait for a new readyRead() signal.
After a successful read transaction, we call QLabel::setText() to display
the fortune.
\sa {Fortune Server Example}, {Blocking Fortune Client Example}
*/

View File

@ -73,17 +73,8 @@
using QTcpSocket. First we create a QByteArray and a QDataStream object,
passing the bytearray to QDataStream's constructor. We then explicitly set
the protocol version of QDataStream to QDataStream::Qt_4_0 to ensure that
we can communicate with clients from future versions of Qt. (See
QDataStream::setVersion().)
\snippet fortuneserver/server.cpp 6
At the start of our QByteArray, we reserve space for a 16 bit integer that
will contain the total size of the data block we are sending. We continue
by streaming in a random fortune. Then we seek back to the beginning of
the QByteArray, and overwrite the reserved 16 bit integer value with the
total size of the array. By doing this, we provide a way for clients to
verify how much data they can expect before reading the whole packet.
we can communicate with clients from future versions of Qt (see
QDataStream::setVersion()). We continue by streaming in a random fortune.
\snippet fortuneserver/server.cpp 7

View File

@ -49,10 +49,7 @@ Client::Client(QWidget *parent)
, hostCombo(new QComboBox)
, portLineEdit(new QLineEdit)
, getFortuneButton(new QPushButton(tr("Get Fortune")))
//! [1]
, tcpSocket(new QTcpSocket(this))
//! [1]
, blockSize(0)
, networkSession(Q_NULLPTR)
{
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
@ -100,6 +97,11 @@ Client::Client(QWidget *parent)
buttonBox->addButton(getFortuneButton, QDialogButtonBox::ActionRole);
buttonBox->addButton(quitButton, QDialogButtonBox::RejectRole);
//! [1]
in.setDevice(tcpSocket);
in.setVersion(QDataStream::Qt_4_0);
//! [1]
connect(hostCombo, &QComboBox::editTextChanged,
this, &Client::enableGetFortuneButton);
connect(portLineEdit, &QLineEdit::textChanged,
@ -171,7 +173,6 @@ Client::Client(QWidget *parent)
void Client::requestNewFortune()
{
getFortuneButton->setEnabled(false);
blockSize = 0;
tcpSocket->abort();
//! [7]
tcpSocket->connectToHost(hostCombo->currentText(),
@ -183,39 +184,24 @@ void Client::requestNewFortune()
//! [8]
void Client::readFortune()
{
//! [9]
QDataStream in(tcpSocket);
in.setVersion(QDataStream::Qt_4_0);
if (blockSize == 0) {
if (tcpSocket->bytesAvailable() < (int)sizeof(quint16))
return;
//! [8]
//! [10]
in >> blockSize;
}
if (tcpSocket->bytesAvailable() < blockSize)
return;
//! [10] //! [11]
in.startTransaction();
QString nextFortune;
in >> nextFortune;
if (!in.commitTransaction())
return;
if (nextFortune == currentFortune) {
QTimer::singleShot(0, this, &Client::requestNewFortune);
return;
}
//! [11]
//! [12]
currentFortune = nextFortune;
//! [9]
statusLabel->setText(currentFortune);
getFortuneButton->setEnabled(true);
}
//! [12]
//! [8]
//! [13]
void Client::displayError(QAbstractSocket::SocketError socketError)

View File

@ -43,6 +43,7 @@
#include <QDialog>
#include <QTcpSocket>
#include <QDataStream>
QT_BEGIN_NAMESPACE
class QComboBox;
@ -75,8 +76,8 @@ private:
QPushButton *getFortuneButton;
QTcpSocket *tcpSocket;
QDataStream in;
QString currentFortune;
quint16 blockSize;
QNetworkSession *networkSession;
};

View File

@ -174,12 +174,9 @@ void Server::sendFortune()
QByteArray block;
QDataStream out(&block, QIODevice::WriteOnly);
out.setVersion(QDataStream::Qt_4_0);
//! [4] //! [6]
out << (quint16)0;
out << fortunes.at(qrand() % fortunes.size());
out.device()->seek(0);
out << (quint16)(block.size() - sizeof(quint16));
//! [6] //! [7]
//! [4] //! [7]
QTcpSocket *clientConnection = tcpServer->nextPendingConnection();
connect(clientConnection, &QAbstractSocket::disconnected,

View File

@ -63,10 +63,7 @@ void FortuneThread::run()
QByteArray block;
QDataStream out(&block, QIODevice::WriteOnly);
out.setVersion(QDataStream::Qt_4_0);
out << (quint16)0;
out << text;
out.device()->seek(0);
out << (quint16)(block.size() - sizeof(quint16));
//! [3] //! [4]
tcpSocket.write(block);

View File

@ -118,4 +118,14 @@ QDataStream out(file);
out.setVersion(QDataStream::Qt_4_0);
//! [5]
//! [6]
in.startTransaction();
QString str;
qint32 a;
in >> str >> a; // try to read packet atomically
if (!in.commitTransaction())
return; // wait for more data
//! [6]
}

View File

@ -180,6 +180,20 @@ QT_BEGIN_NAMESPACE
defined, check the \b {Related Non-Members} section of the
class's documentation page.
\section1 Using Read Transactions
When a data stream operates on an asynchronous device, the chunks of data
can arrive at arbitrary points in time. The QDataStream class implements
a transaction mechanism that provides the ability to read the data
atomically with a series of stream operators. As an example, you can
handle incomplete reads from a socket by using a transaction in a slot
connected to the readyRead() signal:
\snippet code/src_corelib_io_qdatastream.cpp 6
If no full packet is received, this code restores the stream to the
initial position, after which you need to wait for more data to arrive.
\sa QTextStream, QVariant
*/
@ -223,6 +237,8 @@ QT_BEGIN_NAMESPACE
QDataStream member functions
*****************************************************************************/
#define Q_VOID
#undef CHECK_STREAM_PRECOND
#ifndef QT_NO_DEBUG
#define CHECK_STREAM_PRECOND(retVal) \
@ -242,6 +258,12 @@ QT_BEGIN_NAMESPACE
if (q_status != Ok) \
return retVal;
#define CHECK_STREAM_TRANSACTION_PRECOND(retVal) \
if (!d || d->transactionDepth == 0) { \
qWarning("QDataStream: No transaction in progress"); \
return retVal; \
}
/*!
Constructs a data stream that has no I/O device.
@ -567,10 +589,175 @@ void QDataStream::setByteOrder(ByteOrder bo)
\sa version(), Version
*/
/*!
\since 5.7
Starts a new read transaction on the stream.
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 of the stream. Call
commitTransaction(), rollbackTransaction(), or abortTransaction() to
finish the current transaction.
Once a transaction is started, subsequent calls to this function will make
the transaction recursive. Inner transactions act as agents of the
outermost transaction (i.e., report the status of read operations to the
outermost transaction, which can restore the position of the stream).
\note Restoring to the point of the nested startTransaction() call is not
supported.
When an error occurs during a transaction (including an inner transaction
failing), reading from the data stream is suspended (all subsequent read
operations return empty/zero values) and subsequent inner transactions are
forced to fail. Starting a new outermost transaction recovers from this
state. This behavior makes it unnecessary to error-check every read
operation separately.
\sa commitTransaction(), rollbackTransaction(), abortTransaction()
*/
void QDataStream::startTransaction()
{
CHECK_STREAM_PRECOND(Q_VOID)
if (d == 0)
d.reset(new QDataStreamPrivate());
if (++d->transactionDepth == 1) {
dev->startTransaction();
resetStatus();
}
}
/*!
\since 5.7
Completes a read transaction. Returns \c true if no read errors have
occurred during the transaction; otherwise returns \c false.
If called on an inner transaction, committing will be postponed until
the outermost commitTransaction(), rollbackTransaction(), or
abortTransaction() call occurs.
Otherwise, if the stream status indicates reading past the end of the
data, this function restores the stream data to the point of the
startTransaction() call. When this situation occurs, you need to wait for
more data to arrive, after which you start a new transaction. If the data
stream has read corrupt data or any of the inner transactions was aborted,
this function aborts the transaction.
\sa startTransaction(), rollbackTransaction(), abortTransaction()
*/
bool QDataStream::commitTransaction()
{
CHECK_STREAM_TRANSACTION_PRECOND(false)
if (--d->transactionDepth == 0) {
CHECK_STREAM_PRECOND(false)
if (q_status == ReadPastEnd) {
dev->rollbackTransaction();
return false;
}
dev->commitTransaction();
}
return q_status == Ok;
}
/*!
\since 5.7
Reverts a read transaction.
This function is commonly used to rollback the transaction when an
incomplete read was detected prior to committing the transaction.
If called on an inner transaction, reverting is delegated to the outermost
transaction, and subsequently started inner transactions are forced to
fail.
For the outermost transaction, restores the stream data to the point of
the startTransaction() call. If the data stream has read corrupt data or
any of the inner transactions was aborted, this function aborts the
transaction.
If the preceding stream operations were successful, sets the status of the
data stream to \value ReadPastEnd.
\sa startTransaction(), commitTransaction(), abortTransaction()
*/
void QDataStream::rollbackTransaction()
{
setStatus(ReadPastEnd);
CHECK_STREAM_TRANSACTION_PRECOND(Q_VOID)
if (--d->transactionDepth != 0)
return;
CHECK_STREAM_PRECOND(Q_VOID)
if (q_status == ReadPastEnd)
dev->rollbackTransaction();
else
dev->commitTransaction();
}
/*!
\since 5.7
Aborts a read transaction.
This function is commonly used to discard the transaction after
higher-level protocol errors or loss of stream synchronization.
If called on an inner transaction, aborting is delegated to the outermost
transaction, and subsequently started inner transactions are forced to
fail.
For the outermost transaction, discards the restoration point and any
internally duplicated data of the stream. Will not affect the current
read position of the stream.
Sets the status of the data stream to \value ReadCorruptData.
\sa startTransaction(), commitTransaction(), rollbackTransaction()
*/
void QDataStream::abortTransaction()
{
q_status = ReadCorruptData;
CHECK_STREAM_TRANSACTION_PRECOND(Q_VOID)
if (--d->transactionDepth != 0)
return;
CHECK_STREAM_PRECOND(Q_VOID)
dev->commitTransaction();
}
/*****************************************************************************
QDataStream read functions
*****************************************************************************/
/*!
\internal
*/
int QDataStream::readBlock(char *data, int len)
{
// Disable reads on failure in transacted stream
if (q_status != Ok && dev->isTransactionStarted())
return -1;
const int readResult = dev->read(data, len);
if (readResult != len)
setStatus(ReadPastEnd);
return readResult;
}
/*!
\fn QDataStream &QDataStream::operator>>(quint8 &i)
\overload
@ -589,9 +776,7 @@ QDataStream &QDataStream::operator>>(qint8 &i)
i = 0;
CHECK_STREAM_PRECOND(*this)
char c;
if (!dev->getChar(&c))
setStatus(ReadPastEnd);
else
if (readBlock(&c, 1) == 1)
i = qint8(c);
return *this;
}
@ -616,9 +801,8 @@ QDataStream &QDataStream::operator>>(qint16 &i)
{
i = 0;
CHECK_STREAM_PRECOND(*this)
if (dev->read((char *)&i, 2) != 2) {
if (readBlock(reinterpret_cast<char *>(&i), 2) != 2) {
i = 0;
setStatus(ReadPastEnd);
} else {
if (!noswap) {
i = qbswap(i);
@ -647,9 +831,8 @@ QDataStream &QDataStream::operator>>(qint32 &i)
{
i = 0;
CHECK_STREAM_PRECOND(*this)
if (dev->read((char *)&i, 4) != 4) {
if (readBlock(reinterpret_cast<char *>(&i), 4) != 4) {
i = 0;
setStatus(ReadPastEnd);
} else {
if (!noswap) {
i = qbswap(i);
@ -682,9 +865,8 @@ QDataStream &QDataStream::operator>>(qint64 &i)
*this >> i2 >> i1;
i = ((quint64)i1 << 32) + i2;
} else {
if (dev->read((char *)&i, 8) != 8) {
if (readBlock(reinterpret_cast<char *>(&i), 8) != 8) {
i = qint64(0);
setStatus(ReadPastEnd);
} else {
if (!noswap) {
i = qbswap(i);
@ -728,9 +910,8 @@ QDataStream &QDataStream::operator>>(float &f)
f = 0.0f;
CHECK_STREAM_PRECOND(*this)
if (dev->read((char *)&f, 4) != 4) {
if (readBlock(reinterpret_cast<char *>(&f), 4) != 4) {
f = 0.0f;
setStatus(ReadPastEnd);
} else {
if (!noswap) {
union {
@ -766,9 +947,8 @@ QDataStream &QDataStream::operator>>(double &f)
f = 0.0;
CHECK_STREAM_PRECOND(*this)
if (dev->read((char *)&f, 8) != 8) {
if (readBlock(reinterpret_cast<char *>(&f), 8) != 8) {
f = 0.0;
setStatus(ReadPastEnd);
} else {
if (!noswap) {
union {
@ -845,9 +1025,8 @@ QDataStream &QDataStream::readBytes(char *&s, uint &l)
memcpy(curBuf, prevBuf, allocated);
delete [] prevBuf;
}
if (dev->read(curBuf + allocated, blockSize) != blockSize) {
if (readBlock(curBuf + allocated, blockSize) != blockSize) {
delete [] curBuf;
setStatus(ReadPastEnd);
return *this;
}
allocated += blockSize;
@ -871,7 +1050,7 @@ QDataStream &QDataStream::readBytes(char *&s, uint &l)
int QDataStream::readRawData(char *s, int len)
{
CHECK_STREAM_PRECOND(-1)
return dev->read(s, len);
return readBlock(s, len);
}
@ -1154,7 +1333,7 @@ int QDataStream::skipRawData(int len)
while (len > 0) {
int blockSize = qMin(len, (int)sizeof(buf));
int n = dev->read(buf, blockSize);
int n = readBlock(buf, blockSize);
if (n == -1)
return -1;
if (n == 0)

View File

@ -168,6 +168,11 @@ public:
int skipRawData(int len);
void startTransaction();
bool commitTransaction();
void rollbackTransaction();
void abortTransaction();
private:
Q_DISABLE_COPY(QDataStream)
@ -179,6 +184,8 @@ private:
ByteOrder byteorder;
int ver;
Status q_status;
int readBlock(char *data, int len);
};

View File

@ -53,9 +53,11 @@ QT_BEGIN_NAMESPACE
class QDataStreamPrivate
{
public:
QDataStreamPrivate() : floatingPointPrecision(QDataStream::DoublePrecision) { }
QDataStreamPrivate() : floatingPointPrecision(QDataStream::DoublePrecision),
transactionDepth(0) { }
QDataStream::FloatingPointPrecision floatingPointPrecision;
int transactionDepth;
};
#endif

View File

@ -186,6 +186,11 @@ private slots:
void floatingPointNaN();
void transaction_data();
void transaction();
void nestedTransactionsResult_data();
void nestedTransactionsResult();
private:
void writebool(QDataStream *s);
void writeQBitArray(QDataStream *s);
@ -3190,6 +3195,165 @@ void tst_QDataStream::floatingPointPrecision()
}
void tst_QDataStream::transaction_data()
{
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");
QTest::addColumn<QByteArray>("rawData");
QTest::newRow("1") << qint8(1) << qint16(2) << qint32(3) << qint64(4) << true << 5.0f
<< double(6.0) << QByteArray("Hello world!") << QByteArray("Qt rocks!");
QTest::newRow("2") << qint8(1 << 6) << qint16(1 << 14) << qint32(1 << 30) << qint64Data(3) << false << 123.0f
<< double(234.0) << stringData(5).toUtf8() << stringData(6).toUtf8();
QTest::newRow("3") << qint8(-1) << qint16(-2) << qint32(-3) << qint64(-4) << true << -123.0f
<< double(-234.0) << stringData(3).toUtf8() << stringData(4).toUtf8();
}
void tst_QDataStream::transaction()
{
QByteArray testBuffer;
QFETCH(qint8, i8Data);
QFETCH(qint16, i16Data);
QFETCH(qint32, i32Data);
QFETCH(qint64, i64Data);
QFETCH(bool, bData);
QFETCH(float, fData);
QFETCH(double, dData);
QFETCH(QByteArray, strData);
QFETCH(QByteArray, rawData);
{
QDataStream stream(&testBuffer, QIODevice::WriteOnly);
stream << i8Data << i16Data << i32Data << i64Data
<< bData << fData << dData << strData.constData();
stream.writeRawData(rawData.constData(), rawData.size());
}
for (int splitPos = 0; splitPos <= testBuffer.size(); ++splitPos) {
QByteArray readBuffer(testBuffer.left(splitPos));
SequentialBuffer dev(&readBuffer);
dev.open(QIODevice::ReadOnly);
QDataStream stream(&dev);
qint8 i8;
qint16 i16;
qint32 i32;
qint64 i64;
bool b;
float f;
double d;
char *str;
QByteArray raw(rawData.size(), 0);
forever {
stream.startTransaction();
stream >> i8 >> i16 >> i32 >> i64 >> b >> f >> d >> str;
stream.readRawData(raw.data(), raw.size());
if (stream.commitTransaction())
break;
QVERIFY(stream.status() == QDataStream::ReadPastEnd);
QVERIFY(splitPos == 0 || !stream.atEnd());
QVERIFY(readBuffer.size() < testBuffer.size());
delete [] str;
raw.fill(0);
readBuffer.append(testBuffer.right(testBuffer.size() - splitPos));
}
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;
QCOMPARE(raw, rawData);
}
}
void tst_QDataStream::nestedTransactionsResult_data()
{
QTest::addColumn<bool>("commitFirst");
QTest::addColumn<bool>("rollbackFirst");
QTest::addColumn<bool>("commitSecond");
QTest::addColumn<bool>("rollbackSecond");
QTest::addColumn<bool>("successExpected");
QTest::addColumn<bool>("expectedAtEnd");
QTest::addColumn<int>("expectedStatus");
QTest::newRow("1") << false << false << false << false
<< false << true << int(QDataStream::ReadCorruptData);
QTest::newRow("2") << false << false << false << true
<< false << true << int(QDataStream::ReadCorruptData);
QTest::newRow("3") << false << false << true << false
<< false << true << int(QDataStream::ReadCorruptData);
QTest::newRow("4") << false << true << false << false
<< false << true << int(QDataStream::ReadCorruptData);
QTest::newRow("5") << false << true << false << true
<< false << false << int(QDataStream::ReadPastEnd);
QTest::newRow("6") << false << true << true << false
<< false << false << int(QDataStream::ReadPastEnd);
QTest::newRow("7") << true << false << false << false
<< false << true << int(QDataStream::ReadCorruptData);
QTest::newRow("8") << true << false << false << true
<< false << false << int(QDataStream::ReadPastEnd);
QTest::newRow("9") << true << false << true << false
<< true << true << int(QDataStream::Ok);
}
void tst_QDataStream::nestedTransactionsResult()
{
QByteArray testBuffer(1, 0);
QDataStream stream(&testBuffer, QIODevice::ReadOnly);
uchar c;
QFETCH(bool, commitFirst);
QFETCH(bool, rollbackFirst);
QFETCH(bool, commitSecond);
QFETCH(bool, rollbackSecond);
QFETCH(bool, successExpected);
QFETCH(bool, expectedAtEnd);
QFETCH(int, expectedStatus);
stream.startTransaction();
stream.startTransaction();
stream >> c;
if (commitFirst)
QVERIFY(stream.commitTransaction());
else if (rollbackFirst)
stream.rollbackTransaction();
else
stream.abortTransaction();
stream.startTransaction();
if (commitSecond)
QCOMPARE(stream.commitTransaction(), commitFirst);
else if (rollbackSecond)
stream.rollbackTransaction();
else
stream.abortTransaction();
QCOMPARE(stream.commitTransaction(), successExpected);
QCOMPARE(stream.atEnd(), expectedAtEnd);
QCOMPARE(int(stream.status()), expectedStatus);
}
QTEST_MAIN(tst_QDataStream)
#include "tst_qdatastream.moc"