I am using qDebug(), qInfo() and so on in combination of qInstallMessageHandler to write my logfiles. I also get an output on my batch screen, when executing my application.
I only found QT_NO_DEBUG_OUTPUT, but I want to toggle this configuration while run-time. Is there a way to prevent Qt from writing to std output?
sadly no, you only get access to the messages, but cannot prevent from beeing written to std output.
That's false in Qt 5 at the very least. The message printing code looks as follows: you can clearly see that only the message handler is being used:
if (grabMessageHandler()) {
// prefer new message handler over the old one
if (msgHandler.load() == qDefaultMsgHandler
|| messageHandler.load() != qDefaultMessageHandler) {
(*messageHandler.load())(msgType, context, message);
} else {
(*msgHandler.load())(msgType, message.toLocal8Bit().constData());
}
ungrabMessageHandler();
} else {
fprintf(stderr, "%s\n", message.toLocal8Bit().constData());
}
As you can see, the fprintf(stderr, ...) is a fallback only if recursion is detected from within the messageHandler itself.
Thus, all you need to prevent any debug output is to implement and set your own messageHandler.
To completely turn off all the debug output in Qt, the following works:
#include <QtCore>
int main() {
qDebug() << "I'm not quiet at all yet";
qInstallMessageHandler(+[](QtMsgType, const QMessageLogContext &, const QString &){});
qDebug() << "I'm very, very quiet";
}
In any case, a sensible implementation of an application-wide file logger might look as follows - it doesn't recreate the QTextStream unnecessarily; it uses QString::toUtf8() instead, and explicitly writes line endings.
#include <QtCore>
class Logger {
static struct Data {
Logger *instance;
QtMessageHandler chainedHandler;
} d;
bool m_isOpen;
QFile m_logFile;
QtMessageHandler m_oldHandler = {};
static void handler(QtMsgType type, const QMessageLogContext &context, const QString &msg) {
if (d.instance)
d.instance->log(msg);
if (d.chainedHandler)
d.chainedHandler(type, context, msg);
}
public:
enum ChainMode { DontChain, Chain };
Logger() {
Q_ASSERT(!instance());
m_logFile.setFileName("myLog.txt");
m_isOpen = m_logFile.open(QIODevice::WriteOnly | QIODevice::Append | QIODevice::Text);
d.instance = this;
}
~Logger() { uninstallHandler(); }
bool isOpen() const { return m_isOpen; }
void installHandler(ChainMode mode) {
Q_ASSERT(!m_oldHandler);
m_oldHandler = qInstallMessageHandler(handler);
if (mode == Chain)
d.chainedHandler = m_oldHandler;
}
void uninstallHandler() {
if (m_oldHandler) {
m_oldHandler = nullptr;
d.chainedHandler = nullptr;
qInstallMessageHandler(m_oldHandler);
}
}
/// This method is *not* thread-safe. Use with a thread-safe wrapper such as `qDebug`.
void log(const QString & msg) {
if (isOpen()) {
m_logFile.write(msg.toUtf8());
m_logFile.write("\n", 1);
}
}
/// Closes the log file early - this is mostly used for testing.
void endLog() {
uninstallHandler();
m_logFile.close();
}
static Logger *instance() { return d.instance; }
};
Logger::Data Logger::d;
template <typename T> QByteArray streamOutputFor(const T &data) {
QBuffer buf;
buf.open(QIODevice::ReadWrite | QIODevice::Text);
QTextStream s(&buf);
s << data << endl;
buf.close();
return buf.data();
}
QByteArray readEnd(const QString &fileName, int count) {
QFile file(fileName);
if (file.open(QIODevice::ReadOnly | QIODevice::Text) && file.size() >= count) {
file.seek(file.size() - count);
return file.readAll();
}
return {};
}
void test() {
auto const entry = QDateTime::currentDateTime().toString().toUtf8();
Q_ASSERT(Logger::instance()->isOpen());
qDebug() << entry.data();
Logger::instance()->endLog();
auto reference = streamOutputFor(entry.data());
auto readback = readEnd("myLog.txt", reference.size());
Q_ASSERT(!reference.isEmpty());
Q_ASSERT(readback == reference);
}
int main() {
Logger logger;
logger.installHandler(Logger::DontChain);
qDebug() << "Something or else";
test();
}
Related
values on the output are duplicated
CODE:
enum class status_t
{
_success,
};
const char* set_status(status_t status)
{
switch(status)
{
case status_t::_success: return xorstr_("[SUCCESS]");
}
}
enum class action_t
{
_launch,
};
const char* set_action(action_t action)
{
switch (action)
{
case action_t::_launch: return xorstr_("[LAUNCH]");
}
}
void record(const char* status, const char* action, char* message)
{
const char* directory = "C:\\log.txt";
std::ofstream out(directory, std::ios_base::out | std::ios_base::app);
out << status << char(0x20) << action << char(0x20) << message << '\n';
out.close();
}
int main()
{
record(set_status(status_t::_success), set_action(action_t::_launch), "success");
}
output: [SUCCESS] [SUCCESS] success
what can cause such an unusual problem?
I do not know what stackoverflow wants me to write here, enough to understand the problem
Following is the function which I want to unit test:
void sampleFunc()
{
FILE *file = fopen(path, "rb");
if (!file) {
std::cout << "File couldn't be opened!" << std::endl;
}
const int bufSize = 32768;
unsigned char *buffer = (unsigned char*) malloc(bufSize);
if (!buffer) {
fclose(file);
std::cout << "Failed to allocate buffer for SHA256 computation." << std::endl;
}
// ..
// Read file into buffer
// ..
}
As shown in the code, my function uses many standard library functions. So am I supposed to mock standard library functions or make actual calls to functions?
What you might do to test your function:
class IFileManager
{
public:
virtual ~IFileManager() = default;
// You could even improve interface by returning RAII object
virtual FILE* fopen(const char* path, const char* mode) = 0;
virtual void fclose(FILE*) = 0;
// ...
};
class FileManager : public IFileManager
{
public:
FILE* fopen(const char* path, const char* mode) override { return ::fopen(path, mode); }
int fclose(FILE* f) override { return ::fclose(f); }
// ...
};
class IAllocator
{
public:
virtual ~IAllocator() = default;
virtual void* allocate(std::size_t) = 0;
virtual void deallocate(void*) = 0;
};
class Allocator : public IAllocator
{
public:
void* allocate(std::size_t size) override { return malloc(size); }
void deallocate(void* p) override { free(p); }
};
Your function becomes:
void sampleFunc(IFileManager& fileManager, IAllocator& allocator)
{
FILE *file = fileManager.fopen(path, "rb");
if(!file) {
std::cout << "File couldn't be opened!" << endl;
return;
}
const int bufSize = 32768;
unsigned char *buffer = (unsigned char*) allocator.allocate(bufSize);
if(!buffer) {
fileManager.fclose(file);
std::cout << "Failed to allocate buffer for SHA256 computation." << std::endl;
return;
}
// Read file into buffer
// ...
}
Finally, you can easily mock IFileManager and IAllocator.
Without that interface, you would have to make standard function behave as you expect, which is not necessary simple feasible (wrong path, limit memory (which limit))
Care also that implementation might have more limitations. that the one from interface (MAX_PATH, non-regular files, UNC path)
I'd like to save custom class to XML using QSettings. But I always get XML without structure members.
#include <QCoreApplication>
#include <QtCore/qdatastream.h>
#include <qxmlstream.h>
#include <qdebug.h>
#include <QtCore/QSettings>
#include <QMetaType>
struct Interface_struct
{
QString name;
QString ip;
};
Q_DECLARE_METATYPE(Interface_struct)
QDataStream& operator <<(QDataStream& out, const Interface_struct& s)
{
out << s.name << s.ip;
return out;
}
QDataStream& operator >>(QDataStream& in, Interface_struct& s)
{
in >> s.name;
in >> s.ip;
return in;
}
static bool readXmlFile(QIODevice &device, QSettings::SettingsMap &map)
{
qDebug()<< "read";
QXmlStreamReader reader(&device);
QString key;
while(!reader.atEnd())
{
reader.readNext();
if( reader.isStartElement() && reader.tokenString() != "Settings")
{
if( reader.text().isNull() )
{
// key = Settings
if(key.isEmpty())
{
key = reader.tokenString();
}
// key = Settings/Intervall
else
{
key += "/" + reader.tokenString();
}
}
else
{
map.insert(key, reader.text().data());
}
}
}
return true;
}
static bool writeXmlFile(QIODevice &device, const QSettings::SettingsMap &map)
{
qDebug()<< "write";
QXmlStreamWriter writer(&device);
writer.writeStartDocument("1.0");
writer.writeStartElement("Settings");
foreach(QString key, map.keys())
{
foreach(QString elementKey, key.split("/"))
{
writer.writeStartElement(elementKey);
}
writer.writeCharacters(map.value(key).toString());
writer.writeEndElement();
}
writer.writeEndElement();
writer.writeEndDocument();
return true;
}
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
qRegisterMetaType<Interface_struct>("Interface_struct");
qRegisterMetaTypeStreamOperators<Interface_struct>("Interface_struct");
{
Interface_struct s;
s.name = QString("br03000");
s.ip = QString("172.16.222.5");
const QSettings::Format xml_format =
QSettings::registerFormat("xml", readXmlFile, writeXmlFile);
if(xml_format == QSettings::InvalidFormat)
{
qDebug() << "InvalidFormat!";
return 0;
}
QSettings::setPath(xml_format, QSettings::UserScope, "/home/farit/test/");
QSettings settings(xml_format, QSettings::UserScope, "xml_cfg");
settings.setValue("network", QVariant::fromValue(s));
}
{
QSettings::Format xml_format =
QSettings::registerFormat("xml", readXmlFile, writeXmlFile);
QSettings::setPath(xml_format, QSettings::UserScope, "/home/farit/test/");
QSettings settings(xml_format, QSettings::UserScope, "xml_cfg");
QVariant value = settings.value("network");
Interface_struct interface = value.value<Interface_struct>();
qDebug() << "TEST: " << interface.name << interface.ip;
}
return 0;
}
I get this output:
read
write
read
TEST: "" ""
Press <RETURN> to close this window...
And XML looks like this:
<?xml version="1.0" encoding="UTF-8"?><Settings><network></network></Settings>
How can I save structure members of custom class to XML using QSettings?
UPDATE: I'm sorry, I forgot to mention, that is supposed to be done in Qt4.
Does Qt have a QIODevice pair that would work for intra-process point-to-point communications?
One could use the concrete QTCPSocket or QLocalSocket, but the server-side connection API is a bit cumbersome, and it seems wasteful to force the data through the OS.
The following is a usable, rudimentary implementation. It uses an internal signal-slot pair to push the data to the other endpoint. That way either end of the connection can live in any thread, and the ends can be moved between threads without losing data or inducing any races.
The private QRingBuffer is used in lieu of reinventing the wheel. Add QT += core-private to the .pro file to make that available. Qt PIMPL is used to gain access to the internal device buffers in Qt versions 5.7 and above.
If you wish to instantiate an open pipe, as may often be the case, you can pass the I/O mode to the constructor. Typical use:
int main(/*…*/)
{
/*…*/
AppPipe end1 { QIODevice::ReadWrite };
AppPipe end2 { &end1, QIODevice::ReadWrite };
AppPipe end3 { &end1, QIODevice::ReadOnly };
// the pipes are open ready to use
/*…*/
}
Whatever you write to one pipe, ends up as readable data in the other, connected pipes, and vice versa. In the above example, data written to end1 is readable from both end2 and end3 independently. Data written to end2 is readable from end1. end3 is effectively a listen-only pipe. Connection of additional pipes is cheap - there's no O(N) cost associated with sending big chunks of data to multiple pipes, because read QRingBuffers of connected pipes store shallow copies of entire byte arrays sent from the originating pipe.
All of the QIODevice semantics hold - you can connect to the readyRead signal, use the pipe with a QDataStream or a QTextStream, etc. As with any QIODevice, you can only use the class from its thread(), but the other endpoint can live in any thread, and both can be moved between threads as desired, without loss of data.
If the other pipe end is not open and readable, the writes are no-ops, even though they succeed. Closing the pipe clears the read and write buffers, so that it can be re-opened for reuse.
The pipe buffers write data by default, and the write buffer can be forcibly flushed using AppPipe::flush(), unless opened in QIODevice::Unbuffered mode.
The hasIncoming and hasOutgoing signals are useful in monitoring the data going over the pipe.
// https://github.com/KubaO/stackoverflown/tree/master/questions/local-pipe-32317081
// This project is compatible with Qt 4 and Qt 5
#include <QtTest>
#include <private/qiodevice_p.h>
#include <private/qringbuffer_p.h>
#include <algorithm>
#include <climits>
#ifndef Q_DECL_OVERRIDE
#define Q_DECL_OVERRIDE
#endif
class AppPipePrivate : public QIODevicePrivate {
public:
#if QT_VERSION < QT_VERSION_CHECK(5,7,0)
QRingBuffer buffer;
QRingBuffer writeBuffer;
int writeBufferChunkSize;
#endif
const QByteArray *writeData;
AppPipePrivate() : writeData(0) { writeBufferChunkSize = 4096; }
};
/// A simple point-to-point intra-process pipe. The other endpoint can live in any
/// thread.
class AppPipe : public QIODevice {
Q_OBJECT
Q_DECLARE_PRIVATE(AppPipe)
static inline int intLen(qint64 len) { return std::min(len, qint64(INT_MAX)); }
Q_SLOT void _a_write(const QByteArray &data) {
Q_D(AppPipe);
if (!(d->openMode & QIODevice::ReadOnly)) return; // We must be readable.
d->buffer.append(data); // This is a chunk shipped from the source.
emit hasIncoming(data);
emit readyRead();
}
void hasOutgoingLong(const char *data, qint64 len) {
while (len) {
int const size = intLen(len);
emit hasOutgoing(QByteArray(data, size));
data += size;
len -= size;
}
}
public:
AppPipe(QIODevice::OpenMode mode, QObject *parent = 0) :
QIODevice(*new AppPipePrivate, parent) {
open(mode);
}
AppPipe(AppPipe *other, QIODevice::OpenMode mode, QObject *parent = 0) :
QIODevice(*new AppPipePrivate, parent) {
open(mode);
addOther(other);
}
AppPipe(AppPipe *other, QObject *parent = 0) :
QIODevice(*new AppPipePrivate, parent) {
addOther(other);
}
~AppPipe() Q_DECL_OVERRIDE {}
void addOther(AppPipe *other) {
if (other) {
connect(this, SIGNAL(hasOutgoing(QByteArray)), other, SLOT(_a_write(QByteArray)), Qt::UniqueConnection);
connect(other, SIGNAL(hasOutgoing(QByteArray)), this, SLOT(_a_write(QByteArray)), Qt::UniqueConnection);
}
}
void removeOther(AppPipe *other) {
disconnect(this, SIGNAL(hasOutgoing(QByteArray)), other, SLOT(_a_write(QByteArray)));
disconnect(other, SIGNAL(hasOutgoing(QByteArray)), this, SLOT(_a_write(QByteArray)));
}
void flush() {
Q_D(AppPipe);
while (!d->writeBuffer.isEmpty()) {
QByteArray const data = d->writeBuffer.read();
emit hasOutgoing(data);
emit bytesWritten(data.size());
}
}
void close() Q_DECL_OVERRIDE {
Q_D(AppPipe);
flush();
QIODevice::close();
d->buffer.clear();
}
qint64 write(const QByteArray &data) { // This is an optional optimization. The base method works OK.
Q_D(AppPipe);
QScopedValueRollback<const QByteArray*> back(d->writeData);
if (!(d->openMode & Text))
d->writeData = &data;
return QIODevice::write(data);
}
qint64 writeData(const char *data, qint64 len) Q_DECL_OVERRIDE {
Q_D(AppPipe);
bool buffered = !(d->openMode & Unbuffered);
if (buffered && (d->writeBuffer.size() + len) > d->writeBufferChunkSize)
flush();
if (!buffered
|| len > d->writeBufferChunkSize
|| (len == d->writeBufferChunkSize && d->writeBuffer.isEmpty()))
{
if (d->writeData && d->writeData->data() == data && d->writeData->size() == len)
emit hasOutgoing(*d->writeData);
else
hasOutgoingLong(data, len);
}
else
memcpy(d->writeBuffer.reserve(len), data, len);
return len;
}
bool isSequential() const Q_DECL_OVERRIDE { return true; }
Q_SIGNAL void hasOutgoing(const QByteArray &);
Q_SIGNAL void hasIncoming(const QByteArray &);
#if QT_VERSION >= QT_VERSION_CHECK(5,7,0)
// all the data is in the read buffer already
qint64 readData(char *, qint64) Q_DECL_OVERRIDE { return 0; }
#else
qint64 readData(char *data, qint64 len) Q_DECL_OVERRIDE {
Q_D(AppPipe);
qint64 hadRead = 0;
while (len && !d->buffer.isEmpty()) {
int size = d->buffer.read(data, intLen(len));
hadRead += size;
data += size;
len -= size;
}
return hadRead;
}
bool canReadLine() const Q_DECL_OVERRIDE {
Q_D(const AppPipe);
return d->buffer.indexOf('\n') != -1 || QIODevice::canReadLine();
}
qint64 bytesAvailable() const Q_DECL_OVERRIDE {
Q_D(const AppPipe);
return QIODevice::bytesAvailable() + d->buffer.size();
}
qint64 bytesToWrite() const Q_DECL_OVERRIDE {
Q_D(const AppPipe);
return QIODevice::bytesToWrite() + d->writeBuffer.size();
}
#endif
};
// ...
#include "main.moc"
A minimal test harness:
class TestAppPipe : public QObject {
Q_OBJECT
QByteArray data1, data2;
struct PipePair {
AppPipe end1, end2;
PipePair(QIODevice::OpenMode mode = QIODevice::NotOpen) :
end1(QIODevice::ReadWrite | mode), end2(&end1, QIODevice::ReadWrite | mode) {}
};
Q_SLOT void initTestCase() {
data1 = randomData();
data2 = randomData();
}
Q_SLOT void sizes() {
QCOMPARE(sizeof(AppPipe), sizeof(QIODevice));
}
Q_SLOT void basic() {
PipePair p;
QVERIFY(p.end1.isOpen() && p.end1.isWritable() && p.end1.isReadable());
QVERIFY(p.end2.isOpen() && p.end2.isWritable() && p.end2.isReadable());
static const char hello[] = "Hello There!";
p.end1.write(hello);
p.end1.flush();
QCOMPARE(p.end2.readAll(), QByteArray(hello));
}
static QByteArray randomData(int const size = 1024*1024*32) {
QByteArray data;
data.resize(size);
char *const d = data.data();
for (char *p = d+data.size()-1; p >= d; --p)
*p = qrand();
Q_ASSERT(data.size() == size);
return data;
}
static void randomChunkWrite(AppPipe *dev, const QByteArray &payload) {
for (int written = 0, left = payload.size(); left; ) {
int const chunk = std::min(qrand() % 82931, left);
dev->write(payload.mid(written, chunk));
left -= chunk; written += chunk;
}
dev->flush();
}
void runBigData(PipePair &p) {
Q_ASSERT(!data1.isEmpty() && !data2.isEmpty());
randomChunkWrite(&p.end1, data1);
randomChunkWrite(&p.end2, data2);
QCOMPARE(p.end1.bytesAvailable(), qint64(data2.size()));
QCOMPARE(p.end2.bytesAvailable(), qint64(data1.size()));
QCOMPARE(p.end1.readAll(), data2);
QCOMPARE(p.end2.readAll(), data1);
}
Q_SLOT void bigDataBuffered() {
PipePair p;
runBigData(p);
}
Q_SLOT void bigDataUnbuffered() {
PipePair p(QIODevice::Unbuffered);
runBigData(p);
}
Q_SLOT void cleanupTestCase() {
data1.clear(); data2.clear();
}
};
QTEST_MAIN(TestAppPipe)
# local-pipe-32317081.pro
QT = core
greaterThan(QT_MAJOR_VERSION, 4): QT = core-private testlib
else: CONFIG += qtestlib
DEFINES += \
QT_DEPRECATED_WARNINGS \
QT_DISABLE_DEPRECATED_BEFORE=0x060000 \
QT_RESTRICTED_CAST_FROM_ASCII
CONFIG += console c++14
CONFIG -= app_bundle
TEMPLATE = app
SOURCES = main.cpp
Say I have a function that creates a QIODevice (e.g. a QFile), then returns a pointer to a QDataStream constructed from the QIODevice. What is the best way to deal with the memory allocation here? Clearly the QIODevice must be heap allocated to stay available to the QDataStream upon function termination, however the destruction of a QDataStream does not destroy or close the device. Is there a standard way of dealing with this seemingly common problem?
Ideally I want a function that returns an object (not a pointer to an object) that behaves like a QDataStream but upon destruction closes the device. Effectively a standard library input stream.
Example code:
QDataStream* getStream(const QString& filename) {
QFile* file = new QFile(filename); // needs to be explicitly deleted later
file->open(QIODevice::ReadOnly);
QDataStream* out = new QDataStream(&file); // same here
return out;
}
std::shared_ptr<QDataStream> getStream(const QString& filename)
{
QFile* file = new QFile(filename); // needs to be explicitly deleted later
file->open(QIODevice::ReadOnly);
std:shared_ptr<QDataStream> out(new QDataStream(&file), QDSDeleter);
return out;
}
void QDSDeleter(QDataStream* s)
{
QIODevice* device = s->device();
device->close();
delete device;
}
std::unique_ptr is another option depending on your needs; here's a reference for the former if you need it.
Edit: Qt also has the facility for this with its QSharedPointer class, where you can also supply a deleter as a constructor argument. Other pointer wrapper options are given there. Thanks #RA. for the correction.
QDataStream has a handy owndev private member that you can set to true to have the stream effectively own the device. The stream can also be easily made moveable - bringing it very close to your requirement of it behaving like a value. Ideally, you would modify your copy of Qt to implement it, but it can also be worked around.
// https://github.com/KubaO/stackoverflown/tree/master/questions/qdatastream-move-own-13039614
#include <QDataStream>
class DataStream : public QDataStream {
struct Proxy {
QScopedPointer<QDataStreamPrivate> d;
QIODevice *dev;
bool owndev;
bool noswap;
QDataStream::ByteOrder byteorder;
int ver;
QDataStream::Status q_status;
#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0)
virtual ~Proxy();
#endif
};
static Proxy *p(QDataStream *ds) { return reinterpret_cast<Proxy *>(ds); }
static const Proxy *p(const QDataStream *ds) {
return reinterpret_cast<const Proxy *>(ds);
}
#if defined(QT_TESTLIB_LIB) || defined(QT_MODULE_TEST)
friend class DataStreamTest;
#endif
public:
DataStream() = default;
using QDataStream::QDataStream;
DataStream(DataStream &&other) : DataStream(static_cast<QDataStream &&>(other)) {}
DataStream(QDataStream &&other) {
using std::swap;
Proxy &o = *p(&other);
Proxy &t = *p(this);
swap(t.d, o.d);
swap(t.dev, o.dev);
swap(t.owndev, o.owndev);
swap(t.noswap, o.noswap);
swap(t.byteorder, o.byteorder);
swap(t.ver, o.ver);
swap(t.q_status, o.q_status);
}
DataStream &operator=(DataStream &&other) {
return *this = static_cast<QDataStream &&>(other);
}
DataStream &operator=(QDataStream &&other) {
this->~DataStream();
new (this) DataStream(std::move(other));
return *this;
}
void setOwnedDevice(QIODevice *dev) {
setDevice(dev);
p(this)->owndev = true;
}
bool ownsDevice() const { return p(this)->owndev; }
static bool ownsDevice(const QDataStream *ds) { return p(ds)->owndev; }
};
Since QObject has a built-in reference count, we could also have the QDataStream act as a shared pointer for it if we wished to.
The following requirements are tested on both Qt 4.8 and 5.10:
PASS : DataStreamTest::isBinaryCompatible()
PASS : DataStreamTest::streams()
PASS : DataStreamTest::movesFromNotOwnedQDataStream()
PASS : DataStreamTest::movesFromNotOwnedDataStream()
PASS : DataStreamTest::assignsFromNotOwnedQDataStream()
PASS : DataStreamTest::assignsFromNotOwnedDataStream()
PASS : DataStreamTest::returnsFromNotOwnedQDataStream()
PASS : DataStreamTest::returnsFromNotOwnedDataStream()
PASS : DataStreamTest::movesFromOwnedQDataStream()
PASS : DataStreamTest::moveFromOwnedDataStream()
PASS : DataStreamTest::assignsFromOwnedQDataStream()
PASS : DataStreamTest::assignsFromOwnedDataStream()
PASS : DataStreamTest::returnsFromOwnedQDataStream()
PASS : DataStreamTest::returnsFromOwnedDataStream()
The test suite follows. The binary compatibility test is extensive and all but excludes the possibility that the UB we depend on is problematic. Note that QDataStream's layout cannot change within the major Qt version - so the above code will work on all future Qt 5 versions.
#include <QtTest>
class DataStreamTest : public QObject {
Q_OBJECT
static QObjectData *getD(QObject *obj) {
return static_cast<DataStreamTest *>(obj)->d_ptr.data();
}
static bool wasDeleted(QObject *obj) { return getD(obj)->wasDeleted; }
template <typename T, typename... Args>
DataStream make_stream(Args &&... args) {
return T(std::forward<Args>(args)...);
}
static QDataStream::ByteOrder flipped(QDataStream::ByteOrder o) {
return (o == QDataStream::BigEndian) ? QDataStream::LittleEndian
: QDataStream::BigEndian;
}
Q_SLOT void isBinaryCompatible() {
QCOMPARE(sizeof(DataStream), sizeof(QDataStream));
QCOMPARE(sizeof(DataStream::Proxy), sizeof(QDataStream));
struct Test {
QByteArray data;
QDataStream ds{&data, QIODevice::ReadWrite};
void check(int loc = 0) {
if (!loc) {
check(1);
ds.setDevice(nullptr);
check(1);
}
QCOMPARE(!!ds.device(), DataStream::ownsDevice(&ds));
QCOMPARE(ds.device(), DataStream::p(&ds)->dev);
if (!loc) check(2);
bool noswap = DataStream::p(&ds)->noswap;
QCOMPARE(noswap, DataStream::p(&ds)->noswap);
QCOMPARE(ds.byteOrder(), DataStream::p(&ds)->byteorder);
if (loc != 2) {
ds.setByteOrder(flipped(ds.byteOrder()));
noswap = !noswap;
}
if (!loc) check(2);
QCOMPARE(noswap, DataStream::p(&ds)->noswap);
if (!loc) check(3);
QCOMPARE(ds.version(), DataStream::p(&ds)->ver);
if (loc != 3) ds.setVersion(QDataStream::Qt_4_0);
if (!loc) check(3);
if (!loc) check(4);
QCOMPARE(ds.status(), DataStream::p(&ds)->q_status);
if (loc != 4) ds.setStatus(QDataStream::ReadPastEnd);
if (!loc) check(4);
}
} test;
test.check();
}
Q_SLOT void streams() {
QString str{"Hello, world"};
QVector<uint> ints{44, 0xDEADBEEF, 1};
QByteArray data;
DataStream ds(&data, QIODevice::ReadWrite);
ds << str << ints;
ds.device()->reset();
QString str2;
QVector<uint> ints2;
ds >> str2 >> ints2;
QCOMPARE(str2, str);
QCOMPARE(ints2, ints);
}
Q_SLOT void movesFromNotOwnedQDataStream() {
QBuffer buf;
QDataStream ds(&buf);
QVERIFY(ds.device() == &buf);
DataStream ds2(std::move(ds));
QVERIFY(!ds.device());
QVERIFY(ds2.device() == &buf);
QVERIFY(!wasDeleted(&buf));
}
Q_SLOT void movesFromNotOwnedDataStream() {
QBuffer buf;
DataStream ds(&buf);
QVERIFY(ds.device() == &buf);
DataStream ds2(std::move(ds));
QVERIFY(!ds.device());
QVERIFY(ds2.device() == &buf);
QVERIFY(!wasDeleted(&buf));
}
Q_SLOT void assignsFromNotOwnedQDataStream() {
QBuffer buf;
QDataStream ds(&buf);
QVERIFY(ds.device() == &buf);
DataStream ds2;
ds2 = std::move(ds);
QVERIFY(!ds.device());
QVERIFY(ds2.device() == &buf);
QVERIFY(!wasDeleted(&buf));
}
Q_SLOT void assignsFromNotOwnedDataStream() {
QBuffer buf;
DataStream ds(&buf);
QVERIFY(ds.device() == &buf);
DataStream ds2;
ds2 = std::move(ds);
QVERIFY(!ds.device());
QVERIFY(ds2.device() == &buf);
QVERIFY(!wasDeleted(&buf));
}
Q_SLOT void returnsFromNotOwnedQDataStream() {
QBuffer buf;
{
auto ds = make_stream<QDataStream>(&buf);
QVERIFY(ds.device());
QVERIFY(!ds.ownsDevice());
}
QVERIFY(!wasDeleted(&buf));
}
Q_SLOT void returnsFromNotOwnedDataStream() {
QBuffer buf;
buf.open(QIODevice::ReadWrite);
{
auto ds = make_stream<DataStream>(&buf);
QVERIFY(ds.device());
QVERIFY(!ds.ownsDevice());
}
QVERIFY(!wasDeleted(&buf));
}
Q_SLOT void movesFromOwnedQDataStream() {
QPointer<QIODevice> buf;
{
QByteArray data;
QDataStream ds(&data, QIODevice::ReadWrite);
QVERIFY(DataStream::ownsDevice(&ds));
buf = ds.device();
DataStream ds2(std::move(ds));
QVERIFY(!ds.device());
QVERIFY(ds2.device() == buf);
QVERIFY(buf);
}
QVERIFY(!buf);
}
Q_SLOT void moveFromOwnedDataStream() {
QPointer<QBuffer> buf(new QBuffer);
{
DataStream ds;
ds.setOwnedDevice(buf);
QVERIFY(ds.device() == buf);
DataStream ds2(std::move(ds));
QVERIFY(!ds.device());
QVERIFY(ds2.device() == buf);
QVERIFY(buf);
}
QVERIFY(!buf);
}
Q_SLOT void assignsFromOwnedQDataStream() {
QPointer<QIODevice> buf;
{
QByteArray data;
QDataStream ds(&data, QIODevice::ReadWrite);
QVERIFY(DataStream::ownsDevice(&ds));
buf = ds.device();
DataStream ds2;
ds2 = std::move(ds);
QVERIFY(!ds.device());
QVERIFY(ds2.device() == buf);
QVERIFY(buf);
}
QVERIFY(!buf);
}
Q_SLOT void assignsFromOwnedDataStream() {
QPointer<QBuffer> buf(new QBuffer);
{
DataStream ds;
ds.setOwnedDevice(buf);
QVERIFY(ds.device() == buf);
DataStream ds2;
ds2 = std::move(ds);
QVERIFY(!ds.device());
QVERIFY(ds2.device() == buf);
QVERIFY(buf);
}
QVERIFY(!buf);
}
Q_SLOT void returnsFromOwnedQDataStream() {
QPointer<QIODevice> dev;
QByteArray data;
{
auto ds = make_stream<QDataStream>(&data, QIODevice::ReadWrite);
dev = ds.device();
QVERIFY(ds.device());
QVERIFY(ds.ownsDevice());
}
QVERIFY(!dev);
}
Q_SLOT void returnsFromOwnedDataStream() {
QPointer<QIODevice> dev;
QByteArray data;
{
auto ds = make_stream<DataStream>(&data, QIODevice::ReadWrite);
dev = ds.device();
QVERIFY(ds.device());
QVERIFY(ds.ownsDevice());
}
QVERIFY(!dev);
}
};
QTEST_MAIN(DataStreamTest)
#include "main.moc"