Convert teamcity logger to use QTestCharBuffer

All our other loggers use it and teamcity's use of QString implied
lots of conversions from UTF-8 and back, or in some cases to native
encoding, that ended up failing to correctly propagate Unicode tokens
that the other test loggers do deal with correctly. This does not
change the generated output on Linux.

Change-Id: I036d8043d7b3b19e7a0e1f296a23628f98223563
Reviewed-by: Jason McDonald <macadder1@gmail.com>
This commit is contained in:
Edward Welbourne 2022-08-16 11:54:26 +02:00
parent d19d918c60
commit ceba44a3bf
2 changed files with 113 additions and 74 deletions

View File

@ -7,6 +7,8 @@
#include <QtTest/private/qtestlog_p.h>
#include <QtTest/private/qteamcitylogger_p.h>
#include <QtCore/qbytearray.h>
#include <cctype>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
@ -83,16 +85,20 @@ void QTeamCityLogger::startLogging()
{
QAbstractTestLogger::startLogging();
flowID = tcEscapedString(QString::fromUtf8(QTestResult::currentTestObjectName()));
tcEscapedString(&flowID, QTestResult::currentTestObjectName());
QString str = "##teamcity[testSuiteStarted name='%1' flowId='%1']\n"_L1.arg(flowID);
outputString(qPrintable(str));
QTestCharBuffer buf;
QTest::qt_asprintf(&buf, "##teamcity[testSuiteStarted name='%s' flowId='%s']\n",
flowID.constData(), flowID.constData());
outputString(buf.constData());
}
void QTeamCityLogger::stopLogging()
{
QString str = "##teamcity[testSuiteFinished name='%1' flowId='%1']\n"_L1.arg(flowID);
outputString(qPrintable(str));
QTestCharBuffer buf;
QTest::qt_asprintf(&buf, "##teamcity[testSuiteFinished name='%s' flowId='%s']\n",
flowID.constData(), flowID.constData());
outputString(buf.constData());
QAbstractTestLogger::stopLogging();
}
@ -114,56 +120,66 @@ void QTeamCityLogger::addIncident(IncidentTypes type, const char *description,
if ((type == Pass || type == XFail || type == BlacklistedPass || type == BlacklistedXFail) && QTestLog::verboseLevel() < 0)
return;
QString buf;
QTestCharBuffer buf;
QTestCharBuffer tmpFuncName;
escapedTestFuncName(&tmpFuncName);
QString tmpFuncName = escapedTestFuncName();
if (qstrcmp(tmpFuncName.constData(), currTestFuncName.constData()) != 0) {
QTest::qt_asprintf(&buf, "##teamcity[testStarted name='%s' flowId='%s']\n",
tmpFuncName.constData(), flowID.constData());
outputString(buf.constData());
if (tmpFuncName != currTestFuncName) {
buf = "##teamcity[testStarted name='%1' flowId='%2']\n"_L1.arg(tmpFuncName, flowID);
outputString(qPrintable(buf));
currTestFuncName.clear();
QTestPrivate::appendCharBuffer(&currTestFuncName, tmpFuncName);
}
currTestFuncName = tmpFuncName;
if (type == QAbstractTestLogger::XFail) {
addPendingMessage(QTest::incidentType2String(type), QString::fromUtf8(description), file, line);
addPendingMessage(QTest::incidentType2String(type), description, file, line);
return;
}
QString detailedText = tcEscapedString(QString::fromUtf8(description));
QTestCharBuffer detailedText;
tcEscapedString(&detailedText, description);
// Test failed
if (type == Fail || type == XPass) {
QString messageText(u"Failure!"_s);
QTestCharBuffer messageText;
if (file)
messageText += " |[Loc: %1(%2)|]"_L1.arg(QString::fromUtf8(file)).arg(line);
QTest::qt_asprintf(&messageText, "Failure! |[Loc: %s(%d)|]", file, line);
else
QTest::qt_asprintf(&messageText, "Failure!");
buf = "##teamcity[testFailed name='%1' message='%2' details='%3' flowId='%4']\n"_L1
.arg(tmpFuncName, messageText, detailedText, flowID);
QTest::qt_asprintf(&buf, "##teamcity[testFailed name='%s' message='%s' details='%s'"
" flowId='%s']\n", tmpFuncName.constData(), messageText.constData(),
detailedText.constData(), flowID.constData());
outputString(qPrintable(buf));
outputString(buf.constData());
} else if (type == Skip) {
if (file)
detailedText.append(" |[Loc: %1(%2)|]"_L1.arg(QString::fromUtf8(file)).arg(line));
if (file) {
QTestCharBuffer detail;
QTest::qt_asprintf(&detail, " |[Loc: %s(%d)|]", file, line);
QTestPrivate::appendCharBuffer(&detailedText, detail);
}
buf = "##teamcity[testIgnored name='%1' message='%2' flowId='%3']\n"_L1
.arg(escapedTestFuncName(), detailedText, flowID);
QTest::qt_asprintf(&buf, "##teamcity[testIgnored name='%s' message='%s' flowId='%s']\n",
currTestFuncName.constData(), detailedText.constData(),
flowID.constData());
outputString(qPrintable(buf));
outputString(buf.constData());
}
if (!pendingMessages.isEmpty()) {
buf = "##teamcity[testStdOut name='%1' out='%2' flowId='%3']\n"_L1
.arg(tmpFuncName, pendingMessages, flowID);
outputString(qPrintable(buf));
QTest::qt_asprintf(&buf, "##teamcity[testStdOut name='%s' out='%s' flowId='%s']\n",
tmpFuncName.constData(), pendingMessages.constData(),
flowID.constData());
outputString(buf.constData());
pendingMessages.clear();
}
buf = "##teamcity[testFinished name='%1' flowId='%2']\n"_L1.arg(tmpFuncName, flowID);
outputString(qPrintable(buf));
QTest::qt_asprintf(&buf, "##teamcity[testFinished name='%s' flowId='%s']\n",
tmpFuncName.constData(), flowID.constData());
outputString(buf.constData());
}
void QTeamCityLogger::addBenchmarkResult(const QBenchmarkResult &)
@ -178,68 +194,91 @@ void QTeamCityLogger::addMessage(MessageTypes type, const QString &message,
if (type != QFatal && QTestLog::verboseLevel() < 0)
return;
QString escapedMessage = tcEscapedString(message);
addPendingMessage(QTest::messageType2String(type), escapedMessage, file, line);
QTestCharBuffer escapedMessage;
tcEscapedString(&escapedMessage, qUtf8Printable(message));
addPendingMessage(QTest::messageType2String(type), escapedMessage.constData(), file, line);
}
QString QTeamCityLogger::tcEscapedString(const QString &str) const
void QTeamCityLogger::tcEscapedString(QTestCharBuffer *buf, const char *str) const
{
QString formattedString;
{
size_t size = qstrlen(str) + 1;
for (const char *p = str; *p; ++p) {
if (strchr("\n\r|[]'", *p))
++size;
}
Q_ASSERT(size <= size_t(INT_MAX));
buf->resize(int(size));
}
for (QChar ch : str) {
switch (ch.toLatin1()) {
bool swallowSpace = true;
char *p = buf->data();
for (; *str; ++str) {
char ch = *str;
switch (ch) {
case '\n':
formattedString.append("|n"_L1);
p++[0] = '|';
ch = 'n';
swallowSpace = false;
break;
case '\r':
formattedString.append("|r"_L1);
p++[0] = '|';
ch = 'r';
swallowSpace = false;
break;
case '|':
formattedString.append("||"_L1);
break;
case '[':
formattedString.append("|["_L1);
break;
case ']':
formattedString.append("|]"_L1);
break;
case '\'':
formattedString.append("|'"_L1);
p++[0] = '|';
swallowSpace = false;
break;
default:
formattedString.append(ch);
if (std::isspace(ch)) {
if (swallowSpace)
continue;
swallowSpace = true;
ch = ' ';
} else {
swallowSpace = false;
}
break;
}
p++[0] = ch;
}
return std::move(formattedString).simplified();
Q_ASSERT(p < buf->data() + buf->size());
if (swallowSpace && p > buf->data()) {
Q_ASSERT(p[-1] == ' ');
--p;
}
Q_ASSERT(p == buf->data() || !std::isspace(p[-1]));
*p = '\0';
}
QString QTeamCityLogger::escapedTestFuncName() const
void QTeamCityLogger::escapedTestFuncName(QTestCharBuffer *buf) const
{
const char *fn = QTestResult::currentTestFunction() ? QTestResult::currentTestFunction()
: "UnknownTestFunc";
const char *tag = QTestResult::currentDataTag() ? QTestResult::currentDataTag() : "";
QTestCharBuffer fn, tag;
const char *raw = QTestResult::currentTestFunction();
tcEscapedString(&fn, raw ? raw : "UnknownTestFunc");
raw = QTestResult::currentDataTag();
if (raw)
tcEscapedString(&tag, raw);
return tcEscapedString(QString::asprintf("%s(%s)", fn, tag));
QTest::qt_asprintf(buf, "%s(%s)", fn.constData(), tag.constData());
}
void QTeamCityLogger::addPendingMessage(const char *type, const QString &msg, const char *file, int line)
void QTeamCityLogger::addPendingMessage(const char *type, const char *msg,
const char *file, int line)
{
QString pendMessage;
const char *pad = pendingMessages.isEmpty() ? "" : "|n";
if (!pendingMessages.isEmpty())
pendMessage += "|n"_L1;
QTestCharBuffer newMessage;
if (file)
QTest::qt_asprintf(&newMessage, "%s%s |[Loc: %s(%d)|]: %s", pad, type, file, line, msg);
else
QTest::qt_asprintf(&newMessage, "%s%s: %s", pad, type, msg);
if (file) {
pendMessage += "%1 |[Loc: %2(%3)|]: %4"_L1
.arg(QString::fromUtf8(type), QString::fromUtf8(file),
QString::number(line), msg);
} else {
pendMessage += "%1: %2"_L1.arg(QString::fromUtf8(type), msg);
}
pendingMessages.append(pendMessage);
QTestPrivate::appendCharBuffer(&pendingMessages, newMessage);
}
QT_END_NAMESPACE

View File

@ -41,13 +41,13 @@ public:
const char *file = nullptr, int line = 0) override;
private:
QString currTestFuncName;
QString pendingMessages;
QString flowID;
QTestCharBuffer currTestFuncName;
QTestCharBuffer pendingMessages;
QTestCharBuffer flowID;
QString tcEscapedString(const QString &str) const;
QString escapedTestFuncName() const;
void addPendingMessage(const char *type, const QString &msg, const char *file, int line);
void tcEscapedString(QTestCharBuffer *buf, const char *str) const;
void escapedTestFuncName(QTestCharBuffer *buf) const;
void addPendingMessage(const char *type, const char *msg, const char *file, int line);
};
QT_END_NAMESPACE