1 | /**************************************************************************** |
2 | ** |
3 | ** Copyright (C) 2016 Borgar Ovsthus |
4 | ** Contact: https://www.qt.io/licensing/ |
5 | ** |
6 | ** This file is part of the QtTest module of the Qt Toolkit. |
7 | ** |
8 | ** $QT_BEGIN_LICENSE:LGPL$ |
9 | ** Commercial License Usage |
10 | ** Licensees holding valid commercial Qt licenses may use this file in |
11 | ** accordance with the commercial license agreement provided with the |
12 | ** Software or, alternatively, in accordance with the terms contained in |
13 | ** a written agreement between you and The Qt Company. For licensing terms |
14 | ** and conditions see https://www.qt.io/terms-conditions. For further |
15 | ** information use the contact form at https://www.qt.io/contact-us. |
16 | ** |
17 | ** GNU Lesser General Public License Usage |
18 | ** Alternatively, this file may be used under the terms of the GNU Lesser |
19 | ** General Public License version 3 as published by the Free Software |
20 | ** Foundation and appearing in the file LICENSE.LGPL3 included in the |
21 | ** packaging of this file. Please review the following information to |
22 | ** ensure the GNU Lesser General Public License version 3 requirements |
23 | ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. |
24 | ** |
25 | ** GNU General Public License Usage |
26 | ** Alternatively, this file may be used under the terms of the GNU |
27 | ** General Public License version 2.0 or (at your option) the GNU General |
28 | ** Public license version 3 or any later version approved by the KDE Free |
29 | ** Qt Foundation. The licenses are as published by the Free Software |
30 | ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 |
31 | ** included in the packaging of this file. Please review the following |
32 | ** information to ensure the GNU General Public License requirements will |
33 | ** be met: https://www.gnu.org/licenses/gpl-2.0.html and |
34 | ** https://www.gnu.org/licenses/gpl-3.0.html. |
35 | ** |
36 | ** $QT_END_LICENSE$ |
37 | ** |
38 | ****************************************************************************/ |
39 | |
40 | #include <QtTest/private/qtestresult_p.h> |
41 | #include <QtTest/qtestassert.h> |
42 | #include <QtTest/private/qtestlog_p.h> |
43 | #include <QtTest/private/qteamcitylogger_p.h> |
44 | #include <QtCore/qbytearray.h> |
45 | #include <stdarg.h> |
46 | #include <stdio.h> |
47 | #include <stdlib.h> |
48 | #include <string.h> |
49 | |
50 | QT_BEGIN_NAMESPACE |
51 | |
52 | namespace QTest { |
53 | |
54 | static const char *incidentType2String(QAbstractTestLogger::IncidentTypes type) |
55 | { |
56 | switch (type) { |
57 | case QAbstractTestLogger::Pass: |
58 | return "PASS" ; |
59 | case QAbstractTestLogger::XFail: |
60 | return "XFAIL" ; |
61 | case QAbstractTestLogger::Fail: |
62 | return "FAIL!" ; |
63 | case QAbstractTestLogger::XPass: |
64 | return "XPASS" ; |
65 | case QAbstractTestLogger::BlacklistedPass: |
66 | return "BPASS" ; |
67 | case QAbstractTestLogger::BlacklistedFail: |
68 | return "BFAIL" ; |
69 | case QAbstractTestLogger::BlacklistedXPass: |
70 | return "BXPASS" ; |
71 | case QAbstractTestLogger::BlacklistedXFail: |
72 | return "BXFAIL" ; |
73 | } |
74 | return "??????" ; |
75 | } |
76 | |
77 | static const char *messageType2String(QAbstractTestLogger::MessageTypes type) |
78 | { |
79 | switch (type) { |
80 | case QAbstractTestLogger::Skip: |
81 | return "SKIP" ; |
82 | case QAbstractTestLogger::Warn: |
83 | return "WARNING" ; |
84 | case QAbstractTestLogger::QWarning: |
85 | return "QWARN" ; |
86 | case QAbstractTestLogger::QDebug: |
87 | return "QDEBUG" ; |
88 | case QAbstractTestLogger::QInfo: |
89 | return "QINFO" ; |
90 | case QAbstractTestLogger::QSystem: |
91 | return "QSYSTEM" ; |
92 | case QAbstractTestLogger::QFatal: |
93 | return "QFATAL" ; |
94 | case QAbstractTestLogger::Info: |
95 | return "INFO" ; |
96 | } |
97 | return "??????" ; |
98 | } |
99 | } |
100 | |
101 | QTeamCityLogger::QTeamCityLogger(const char *filename) |
102 | : QAbstractTestLogger(filename) |
103 | { |
104 | } |
105 | |
106 | QTeamCityLogger::~QTeamCityLogger() = default; |
107 | |
108 | void QTeamCityLogger::startLogging() |
109 | { |
110 | QAbstractTestLogger::startLogging(); |
111 | |
112 | flowID = tcEscapedString(QString::fromUtf8(QTestResult::currentTestObjectName())); |
113 | |
114 | QString str = QString(QLatin1String("##teamcity[testSuiteStarted name='%1' flowId='%1']\n" )).arg(flowID); |
115 | outputString(qPrintable(str)); |
116 | } |
117 | |
118 | void QTeamCityLogger::stopLogging() |
119 | { |
120 | QString str = QString(QLatin1String("##teamcity[testSuiteFinished name='%1' flowId='%1']\n" )).arg(flowID); |
121 | outputString(qPrintable(str)); |
122 | |
123 | QAbstractTestLogger::stopLogging(); |
124 | } |
125 | |
126 | void QTeamCityLogger::enterTestFunction(const char * /*function*/) |
127 | { |
128 | // don't print anything |
129 | } |
130 | |
131 | void QTeamCityLogger::leaveTestFunction() |
132 | { |
133 | // don't print anything |
134 | } |
135 | |
136 | void QTeamCityLogger::addIncident(IncidentTypes type, const char *description, |
137 | const char *file, int line) |
138 | { |
139 | // suppress PASS and XFAIL in silent mode |
140 | if ((type == QAbstractTestLogger::Pass || type == QAbstractTestLogger::XFail) && QTestLog::verboseLevel() < 0) |
141 | return; |
142 | |
143 | QString buf; |
144 | |
145 | QString tmpFuncName = escapedTestFuncName(); |
146 | |
147 | if (tmpFuncName != currTestFuncName) { |
148 | buf = QString(QLatin1String("##teamcity[testStarted name='%1' flowId='%2']\n" )).arg(tmpFuncName, flowID); |
149 | outputString(qPrintable(buf)); |
150 | } |
151 | |
152 | currTestFuncName = tmpFuncName; |
153 | |
154 | if (type == QAbstractTestLogger::XFail) { |
155 | addPendingMessage(QTest::incidentType2String(type), QString::fromUtf8(description), file, line); |
156 | return; |
157 | } |
158 | |
159 | QString detailedText = QString::fromUtf8(description); |
160 | detailedText = tcEscapedString(detailedText); |
161 | |
162 | // Test failed |
163 | if ((type == QAbstractTestLogger::Fail) || (type == QAbstractTestLogger::XPass)) { |
164 | QString messageText(QLatin1String("Failure!" )); |
165 | |
166 | if (file) |
167 | messageText += QString(QLatin1String(" |[Loc: %1(%2)|]" )).arg(QString::fromUtf8(file)).arg(line); |
168 | |
169 | buf = QString(QLatin1String("##teamcity[testFailed name='%1' message='%2' details='%3' flowId='%4']\n" )) |
170 | .arg(tmpFuncName, |
171 | messageText, |
172 | detailedText, |
173 | flowID); |
174 | |
175 | outputString(qPrintable(buf)); |
176 | } |
177 | |
178 | if (!pendingMessages.isEmpty()) { |
179 | buf = QString(QLatin1String("##teamcity[testStdOut name='%1' out='%2' flowId='%3']\n" )) |
180 | .arg(tmpFuncName, pendingMessages, flowID); |
181 | |
182 | outputString(qPrintable(buf)); |
183 | |
184 | pendingMessages.clear(); |
185 | } |
186 | |
187 | buf = QString(QLatin1String("##teamcity[testFinished name='%1' flowId='%2']\n" )).arg(tmpFuncName, flowID); |
188 | outputString(qPrintable(buf)); |
189 | } |
190 | |
191 | void QTeamCityLogger::addBenchmarkResult(const QBenchmarkResult &) |
192 | { |
193 | // don't print anything |
194 | } |
195 | |
196 | void QTeamCityLogger::addMessage(MessageTypes type, const QString &message, |
197 | const char *file, int line) |
198 | { |
199 | // suppress non-fatal messages in silent mode |
200 | if (type != QAbstractTestLogger::QFatal && QTestLog::verboseLevel() < 0) |
201 | return; |
202 | |
203 | QString escapedMessage = tcEscapedString(message); |
204 | |
205 | QString buf; |
206 | |
207 | if (type == QAbstractTestLogger::Skip) { |
208 | if (file) |
209 | escapedMessage.append(QString(QLatin1String(" |[Loc: %1(%2)|]" )).arg(QString::fromUtf8(file)).arg(line)); |
210 | |
211 | buf = QString(QLatin1String("##teamcity[testIgnored name='%1' message='%2' flowId='%3']\n" )) |
212 | .arg(escapedTestFuncName(), escapedMessage, flowID); |
213 | |
214 | outputString(qPrintable(buf)); |
215 | } |
216 | else { |
217 | addPendingMessage(QTest::messageType2String(type), escapedMessage, file, line); |
218 | } |
219 | } |
220 | |
221 | QString QTeamCityLogger::tcEscapedString(const QString &str) const |
222 | { |
223 | QString formattedString; |
224 | |
225 | for (QChar ch : str) { |
226 | switch (ch.toLatin1()) { |
227 | case '\n': |
228 | formattedString.append(QLatin1String("|n" )); |
229 | break; |
230 | case '\r': |
231 | formattedString.append(QLatin1String("|r" )); |
232 | break; |
233 | case '|': |
234 | formattedString.append(QLatin1String("||" )); |
235 | break; |
236 | case '[': |
237 | formattedString.append(QLatin1String("|[" )); |
238 | break; |
239 | case ']': |
240 | formattedString.append(QLatin1String("|]" )); |
241 | break; |
242 | case '\'': |
243 | formattedString.append(QLatin1String("|'" )); |
244 | break; |
245 | default: |
246 | formattedString.append(ch); |
247 | } |
248 | } |
249 | |
250 | return std::move(formattedString).simplified(); |
251 | } |
252 | |
253 | QString QTeamCityLogger::escapedTestFuncName() const |
254 | { |
255 | const char *fn = QTestResult::currentTestFunction() ? QTestResult::currentTestFunction() |
256 | : "UnknownTestFunc" ; |
257 | const char *tag = QTestResult::currentDataTag() ? QTestResult::currentDataTag() : "" ; |
258 | |
259 | return tcEscapedString(QString::asprintf("%s(%s)" , fn, tag)); |
260 | } |
261 | |
262 | void QTeamCityLogger::addPendingMessage(const char *type, const QString &msg, const char *file, int line) |
263 | { |
264 | QString pendMessage; |
265 | |
266 | if (!pendingMessages.isEmpty()) |
267 | pendMessage += QLatin1String("|n" ); |
268 | |
269 | if (file) { |
270 | pendMessage += QString(QLatin1String("%1 |[Loc: %2(%3)|]: %4" )) |
271 | .arg(QString::fromUtf8(type), QString::fromUtf8(file)) |
272 | .arg(line) |
273 | .arg(msg); |
274 | |
275 | } |
276 | else { |
277 | pendMessage += QString(QLatin1String("%1: %2" )) |
278 | .arg(QString::fromUtf8(type), msg); |
279 | } |
280 | |
281 | pendingMessages.append(pendMessage); |
282 | } |
283 | |
284 | QT_END_NAMESPACE |
285 | |