From ceba44a3bf916b5e01eca5852b12e3cfa9102df4 Mon Sep 17 00:00:00 2001 From: Edward Welbourne Date: Tue, 16 Aug 2022 11:54:26 +0200 Subject: [PATCH] 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 --- src/testlib/qteamcitylogger.cpp | 175 +++++++++++++++++++------------- src/testlib/qteamcitylogger_p.h | 12 +-- 2 files changed, 113 insertions(+), 74 deletions(-) diff --git a/src/testlib/qteamcitylogger.cpp b/src/testlib/qteamcitylogger.cpp index 20d15e8d3f..7fe58f4792 100644 --- a/src/testlib/qteamcitylogger.cpp +++ b/src/testlib/qteamcitylogger.cpp @@ -7,6 +7,8 @@ #include #include #include + +#include #include #include #include @@ -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 diff --git a/src/testlib/qteamcitylogger_p.h b/src/testlib/qteamcitylogger_p.h index 41df8bb7d1..406aa690e5 100644 --- a/src/testlib/qteamcitylogger_p.h +++ b/src/testlib/qteamcitylogger_p.h @@ -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