QDebug: pretty-print QStrings and QStringRefs
[ChangeLog][QtCore][QDebug] Printing of QStrings and QStringRefs whenever "noquote" is not active now prints the strings in a format that can be copied back to C++ code. All characters that aren't printable in US-ASCII are escaped (this includes printable Unicode characters outside of US-ASCII). Pretty-printing will not respect QTextFormat padding or field widths. Change-Id: I169a8a0508e24693f5652f0129defe7f709e5d08 Reviewed-by: Oswald Buddenhagen <oswald.buddenhagen@theqtcompany.com> Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
This commit is contained in:
parent
e00d8ad86b
commit
2c01d402e1
@ -1,6 +1,7 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies).
|
||||
** Copyright (C) 2014 Intel Corporation.
|
||||
** Contact: http://www.qt-project.org/legal
|
||||
**
|
||||
** This file is part of the QtCore module of the Qt Toolkit.
|
||||
@ -40,9 +41,12 @@
|
||||
|
||||
#include "qdebug.h"
|
||||
#include <private/qtextstream_p.h>
|
||||
#include <private/qtools_p.h>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
|
||||
using QtMiscUtils::toHexUpper;
|
||||
|
||||
// This file is needed to force compilation of QDebug into the kernel library.
|
||||
|
||||
/*!
|
||||
@ -168,6 +172,105 @@ void QDebug::putUcs4(uint ucs4)
|
||||
maybeQuote('\'');
|
||||
}
|
||||
|
||||
template <typename Char>
|
||||
static inline void putEscapedString(QTextStreamPrivate *d, const Char *begin, int length)
|
||||
{
|
||||
QChar quote(QLatin1Char('"'));
|
||||
d->write("e, 1);
|
||||
|
||||
const Char *end = begin + length;
|
||||
for (const Char *p = begin; p != end; ++p) {
|
||||
if (sizeof(Char) == sizeof(QChar)) {
|
||||
int runLength = 0;
|
||||
while (p + runLength != end &&
|
||||
p[runLength] < 0x7f && p[runLength] >= 0x20 && p[runLength] != '\\' && p[runLength] != '"')
|
||||
++runLength;
|
||||
if (runLength) {
|
||||
d->write(reinterpret_cast<const QChar *>(p), runLength);
|
||||
p += runLength - 1;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// print as an escape sequence
|
||||
int buflen = 2;
|
||||
ushort buf[sizeof "\\U12345678" - 1];
|
||||
buf[0] = '\\';
|
||||
|
||||
switch (*p) {
|
||||
case '"':
|
||||
case '\\':
|
||||
buf[1] = *p;
|
||||
break;
|
||||
case '\b':
|
||||
buf[1] = 'b';
|
||||
break;
|
||||
case '\f':
|
||||
buf[1] = 'f';
|
||||
break;
|
||||
case '\n':
|
||||
buf[1] = 'n';
|
||||
break;
|
||||
case '\r':
|
||||
buf[1] = 'r';
|
||||
break;
|
||||
case '\t':
|
||||
buf[1] = 't';
|
||||
break;
|
||||
default:
|
||||
if (QChar::isHighSurrogate(*p)) {
|
||||
if ((p + 1) != end && QChar::isLowSurrogate(p[1])) {
|
||||
// properly-paired surrogates
|
||||
uint ucs4 = QChar::surrogateToUcs4(*p, p[1]);
|
||||
++p;
|
||||
buf[1] = 'U';
|
||||
buf[2] = '0'; // toHexUpper(ucs4 >> 32);
|
||||
buf[3] = '0'; // toHexUpper(ucs4 >> 28);
|
||||
buf[4] = toHexUpper(ucs4 >> 20);
|
||||
buf[5] = toHexUpper(ucs4 >> 16);
|
||||
buf[6] = toHexUpper(ucs4 >> 12);
|
||||
buf[7] = toHexUpper(ucs4 >> 8);
|
||||
buf[8] = toHexUpper(ucs4 >> 4);
|
||||
buf[9] = toHexUpper(ucs4);
|
||||
buflen = 10;
|
||||
break;
|
||||
}
|
||||
// improperly-paired surrogates, fall through
|
||||
}
|
||||
buf[1] = 'u';
|
||||
if (sizeof(Char) == 1) {
|
||||
buf[2] = buf[3] = '0';
|
||||
} else {
|
||||
buf[2] = toHexUpper(*p >> 12);
|
||||
buf[3] = toHexUpper(*p >> 8);
|
||||
}
|
||||
buf[4] = toHexUpper(*p >> 4);
|
||||
buf[5] = toHexUpper(*p);
|
||||
buflen = 6;
|
||||
}
|
||||
d->write(reinterpret_cast<QChar *>(buf), buflen);
|
||||
}
|
||||
|
||||
d->write("e, 1);
|
||||
}
|
||||
|
||||
/*!
|
||||
\internal
|
||||
Duplicated from QtTest::toPrettyUnicode().
|
||||
*/
|
||||
void QDebug::putString(const QChar *begin, size_t length)
|
||||
{
|
||||
if (stream->testFlag(Stream::NoQuotes)) {
|
||||
// no quotes, write the string directly too (no pretty-printing)
|
||||
// this respects the QTextStream state, though
|
||||
stream->ts.d_ptr->putString(begin, length);
|
||||
} else {
|
||||
// we'll reset the QTextStream formatting mechanisms, so save the state
|
||||
QDebugStateSaver saver(*this);
|
||||
stream->ts.d_ptr->params.reset();
|
||||
putEscapedString(stream->ts.d_ptr.data(), reinterpret_cast<const ushort *>(begin), length);
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
\fn QDebug::swap(QDebug &other)
|
||||
|
@ -78,6 +78,7 @@ class Q_CORE_EXPORT QDebug
|
||||
} *stream;
|
||||
|
||||
void putUcs4(uint ucs4);
|
||||
void putString(const QChar *begin, size_t length);
|
||||
public:
|
||||
inline QDebug(QIODevice *device) : stream(new Stream(device)) {}
|
||||
inline QDebug(QString *string) : stream(new Stream(string)) {}
|
||||
@ -118,8 +119,8 @@ public:
|
||||
inline QDebug &operator<<(float t) { stream->ts << t; return maybeSpace(); }
|
||||
inline QDebug &operator<<(double t) { stream->ts << t; return maybeSpace(); }
|
||||
inline QDebug &operator<<(const char* t) { stream->ts << QString::fromUtf8(t); return maybeSpace(); }
|
||||
inline QDebug &operator<<(const QString & t) { maybeQuote(); stream->ts << t; maybeQuote(); return maybeSpace(); }
|
||||
inline QDebug &operator<<(const QStringRef & t) { return operator<<(t.toString()); }
|
||||
inline QDebug &operator<<(const QString & t) { putString(t.constData(), uint(t.length())); return maybeSpace(); }
|
||||
inline QDebug &operator<<(const QStringRef & t) { putString(t.constData(), uint(t.length())); return maybeSpace(); }
|
||||
inline QDebug &operator<<(QLatin1String t) { maybeQuote(); stream->ts << t; maybeQuote(); return maybeSpace(); }
|
||||
inline QDebug &operator<<(const QByteArray & t) { maybeQuote(); stream->ts << t; maybeQuote(); return maybeSpace(); }
|
||||
inline QDebug &operator<<(const void * t) { stream->ts << t; return maybeSpace(); }
|
||||
|
@ -806,7 +806,7 @@ inline void QTextStreamPrivate::restoreToSavedConverterState()
|
||||
/*!
|
||||
\internal
|
||||
*/
|
||||
inline void QTextStreamPrivate::write(const QChar *data, int len)
|
||||
void QTextStreamPrivate::write(const QChar *data, int len)
|
||||
{
|
||||
if (string) {
|
||||
// ### What about seek()??
|
||||
@ -885,7 +885,7 @@ inline void QTextStreamPrivate::putChar(QChar ch)
|
||||
/*!
|
||||
\internal
|
||||
*/
|
||||
inline void QTextStreamPrivate::putString(const QChar *data, int len, bool number)
|
||||
void QTextStreamPrivate::putString(const QChar *data, int len, bool number)
|
||||
{
|
||||
QString pad;
|
||||
int padLeft = 0, padRight = 0;
|
||||
|
@ -185,6 +185,7 @@ public:
|
||||
private:
|
||||
Q_DISABLE_COPY(QTextStream)
|
||||
friend class QDebugStateSaverPrivate;
|
||||
friend class QDebug;
|
||||
|
||||
QScopedPointer<QTextStreamPrivate> d_ptr;
|
||||
};
|
||||
|
@ -2146,6 +2146,8 @@ char *toHexRepresentation(const char *ba, int length)
|
||||
\internal
|
||||
Returns the same QString but with only the ASCII characters still shown;
|
||||
everything else is replaced with \c {\uXXXX}.
|
||||
|
||||
Similar to QDebug::putString().
|
||||
*/
|
||||
char *toPrettyUnicode(const ushort *p, int length)
|
||||
{
|
||||
|
@ -52,6 +52,7 @@ private slots:
|
||||
void stateSaver() const;
|
||||
void veryLongWarningMessage() const;
|
||||
void qDebugQChar() const;
|
||||
void qDebugQString() const;
|
||||
void qDebugQStringRef() const;
|
||||
void qDebugQLatin1String() const;
|
||||
void qDebugQByteArray() const;
|
||||
@ -344,6 +345,54 @@ void tst_QDebug::qDebugQChar() const
|
||||
|
||||
}
|
||||
|
||||
void tst_QDebug::qDebugQString() const
|
||||
{
|
||||
/* Use a basic string. */
|
||||
{
|
||||
QString file, function;
|
||||
int line = 0;
|
||||
const QString in(QLatin1String("input"));
|
||||
const QStringRef inRef(&in);
|
||||
|
||||
MessageHandlerSetter mhs(myMessageHandler);
|
||||
{ qDebug() << inRef; }
|
||||
#ifndef QT_NO_MESSAGELOGCONTEXT
|
||||
file = __FILE__; line = __LINE__ - 2; function = Q_FUNC_INFO;
|
||||
#endif
|
||||
QCOMPARE(s_msgType, QtDebugMsg);
|
||||
QCOMPARE(s_msg, QString::fromLatin1("\"input\""));
|
||||
QCOMPARE(QString::fromLatin1(s_file), file);
|
||||
QCOMPARE(s_line, line);
|
||||
QCOMPARE(QString::fromLatin1(s_function), function);
|
||||
}
|
||||
|
||||
/* simpler tests from now on */
|
||||
MessageHandlerSetter mhs(myMessageHandler);
|
||||
|
||||
QString string = "Hello";
|
||||
qDebug() << string;
|
||||
QCOMPARE(s_msg, QString("\"Hello\""));
|
||||
|
||||
qDebug().noquote().nospace() << string;
|
||||
QCOMPARE(s_msg, string);
|
||||
|
||||
qDebug().noquote().nospace() << qSetFieldWidth(8) << string;
|
||||
QCOMPARE(s_msg, " " + string);
|
||||
|
||||
string = QLatin1String("\nSm\xF8rg\xE5sbord\\");
|
||||
qDebug().noquote().nospace() << string;
|
||||
QCOMPARE(s_msg, string);
|
||||
|
||||
qDebug() << string;
|
||||
QCOMPARE(s_msg, QString("\"\\nSm\\u00F8rg\\u00E5sbord\\\\\""));
|
||||
|
||||
// surrogate pairs (including broken pairings)
|
||||
ushort utf16[] = { 0xDC00, 0xD800, 0xDC00, 'x', 0xD800, 0xDC00, 0xD800, 0 };
|
||||
string = QString::fromUtf16(utf16);
|
||||
qDebug() << string;
|
||||
QCOMPARE(s_msg, QString("\"\\uDC00\\U00010000x\\U00010000\\uD800\""));
|
||||
}
|
||||
|
||||
void tst_QDebug::qDebugQStringRef() const
|
||||
{
|
||||
/* Use a basic string. */
|
||||
@ -403,6 +452,24 @@ void tst_QDebug::qDebugQLatin1String() const
|
||||
QCOMPARE(QString::fromLatin1(s_file), file);
|
||||
QCOMPARE(s_line, line);
|
||||
QCOMPARE(QString::fromLatin1(s_function), function);
|
||||
|
||||
/* simpler tests from now on */
|
||||
QLatin1String string("\"Hello\"");
|
||||
qDebug() << string;
|
||||
QCOMPARE(s_msg, QString("\"\\\"Hello\\\"\""));
|
||||
|
||||
qDebug().noquote().nospace() << string;
|
||||
QCOMPARE(s_msg, QString(string));
|
||||
|
||||
qDebug().noquote().nospace() << qSetFieldWidth(8) << string;
|
||||
QCOMPARE(s_msg, " " + QString(string));
|
||||
|
||||
string = QLatin1String("\nSm\xF8rg\xE5sbord\\");
|
||||
qDebug().noquote().nospace() << string;
|
||||
QCOMPARE(s_msg, QString(string));
|
||||
|
||||
qDebug() << string;
|
||||
QCOMPARE(s_msg, QString("\"\\nSm\\u00F8rg\\u00E5sbord\\\\\""));
|
||||
}
|
||||
|
||||
void tst_QDebug::qDebugQByteArray() const
|
||||
|
@ -113,7 +113,7 @@
|
||||
</TestFunction>
|
||||
<TestFunction name="encoding">
|
||||
<Message type="qdebug" file="" line="0">
|
||||
<Description><![CDATA["Ülrich Ümläut"]]></Description>
|
||||
<Description><![CDATA["\u00DClrich \u00DCml\u00E4ut"]]></Description>
|
||||
</Message>
|
||||
<Incident type="pass" file="" line="0" />
|
||||
<Duration msecs="0"/>
|
||||
|
@ -115,7 +115,7 @@
|
||||
</TestFunction>
|
||||
<TestFunction name="encoding">
|
||||
<Message type="qdebug" file="" line="0">
|
||||
<Description><![CDATA["Ülrich Ümläut"]]></Description>
|
||||
<Description><![CDATA["\u00DClrich \u00DCml\u00E4ut"]]></Description>
|
||||
</Message>
|
||||
<Incident type="pass" file="" line="0" />
|
||||
<Duration msecs="0"/>
|
||||
|
@ -30,7 +30,7 @@
|
||||
<failure message="failure message" result="fail"/>
|
||||
</testcase>
|
||||
<testcase result="pass" name="encoding">
|
||||
<!-- message=""Ülrich Ümläut"" type="qdebug" -->
|
||||
<!-- message=""\u00DClrich \u00DCml\u00E4ut"" type="qdebug" -->
|
||||
</testcase>
|
||||
<testcase result="pass" name="cleanupTestCase"/>
|
||||
<system-err>
|
||||
@ -46,6 +46,6 @@
|
||||
<![CDATA[quotes " text" more text]]>
|
||||
<![CDATA[xml close > open < tags < text]]>
|
||||
<![CDATA[all > " mixed ]]]><![CDATA[]> up > " in < the ]]]><![CDATA[]> hopes < of triggering "< ]]]><![CDATA[]> bugs]]>
|
||||
<![CDATA["Ülrich Ümläut"]]>
|
||||
<![CDATA["\u00DClrich \u00DCml\u00E4ut"]]>
|
||||
</system-err>
|
||||
</testsuite>
|
||||
|
Loading…
x
Reference in New Issue
Block a user