1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
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/qjunittestlogger_p.h>
41#include <QtTest/private/qtestelement_p.h>
42#include <QtTest/private/qtestjunitstreamer_p.h>
43#include <QtTest/qtestcase.h>
44#include <QtTest/private/qtestresult_p.h>
45#include <QtTest/private/qbenchmark_p.h>
46#include <QtTest/private/qtestlog_p.h>
47
48#ifdef min // windows.h without NOMINMAX is included by the benchmark headers.
49# undef min
50#endif
51#ifdef max
52# undef max
53#endif
54
55#include <QtCore/qlibraryinfo.h>
56
57#include <string.h>
58
59QT_BEGIN_NAMESPACE
60
61QJUnitTestLogger::QJUnitTestLogger(const char *filename)
62 : QAbstractTestLogger(filename)
63{
64}
65
66QJUnitTestLogger::~QJUnitTestLogger()
67{
68 Q_ASSERT(!currentTestSuite);
69 delete logFormatter;
70}
71
72// We track test timing per test case, so we
73// need to maintain our own elapsed timer.
74static QElapsedTimer elapsedTestcaseTime;
75static qreal elapsedTestCaseSeconds()
76{
77 return elapsedTestcaseTime.nsecsElapsed() / 1e9;
78}
79
80static QByteArray toSecondsFormat(qreal ms)
81{
82 return QByteArray::number(ms / 1000, 'f', 3);
83}
84
85void QJUnitTestLogger::startLogging()
86{
87 QAbstractTestLogger::startLogging();
88
89 logFormatter = new QTestJUnitStreamer(this);
90
91 Q_ASSERT(!currentTestSuite);
92 currentTestSuite = new QTestElement(QTest::LET_TestSuite);
93 currentTestSuite->addAttribute(QTest::AI_Name, QTestResult::currentTestObjectName());
94
95 auto localTime = QDateTime::currentDateTime();
96 currentTestSuite->addAttribute(QTest::AI_Timestamp,
97 localTime.toString(Qt::ISODate).toUtf8().constData());
98
99 currentTestSuite->addAttribute(QTest::AI_Hostname,
100 QSysInfo::machineHostName().toUtf8().constData());
101
102 QTestElement *property;
103 QTestElement *properties = new QTestElement(QTest::LET_Properties);
104
105 property = new QTestElement(QTest::LET_Property);
106 property->addAttribute(QTest::AI_Name, "QTestVersion");
107 property->addAttribute(QTest::AI_PropertyValue, QTEST_VERSION_STR);
108 properties->addChild(property);
109
110 property = new QTestElement(QTest::LET_Property);
111 property->addAttribute(QTest::AI_Name, "QtVersion");
112 property->addAttribute(QTest::AI_PropertyValue, qVersion());
113 properties->addChild(property);
114
115 property = new QTestElement(QTest::LET_Property);
116 property->addAttribute(QTest::AI_Name, "QtBuild");
117 property->addAttribute(QTest::AI_PropertyValue, QLibraryInfo::build());
118 properties->addChild(property);
119
120 currentTestSuite->addChild(properties);
121
122 elapsedTestcaseTime.start();
123}
124
125void QJUnitTestLogger::stopLogging()
126{
127 char buf[10];
128
129 qsnprintf(buf, sizeof(buf), "%i", testCounter);
130 currentTestSuite->addAttribute(QTest::AI_Tests, buf);
131
132 qsnprintf(buf, sizeof(buf), "%i", failureCounter);
133 currentTestSuite->addAttribute(QTest::AI_Failures, buf);
134
135 qsnprintf(buf, sizeof(buf), "%i", errorCounter);
136 currentTestSuite->addAttribute(QTest::AI_Errors, buf);
137
138 qsnprintf(buf, sizeof(buf), "%i", QTestLog::skipCount());
139 currentTestSuite->addAttribute(QTest::AI_Skipped, buf);
140
141 currentTestSuite->addAttribute(QTest::AI_Time,
142 toSecondsFormat(QTestLog::msecsTotalTime()).constData());
143
144 for (auto *testCase : listOfTestcases)
145 currentTestSuite->addChild(testCase);
146 listOfTestcases.clear();
147
148 logFormatter->output(currentTestSuite);
149
150 delete currentTestSuite;
151 currentTestSuite = nullptr;
152
153 QAbstractTestLogger::stopLogging();
154}
155
156void QJUnitTestLogger::enterTestFunction(const char *function)
157{
158 enterTestCase(function);
159}
160
161void QJUnitTestLogger::enterTestCase(const char *name)
162{
163 currentTestCase = new QTestElement(QTest::LET_TestCase);
164 currentTestCase->addAttribute(QTest::AI_Name, name);
165 currentTestCase->addAttribute(QTest::AI_Classname, QTestResult::currentTestObjectName());
166 listOfTestcases.push_back(currentTestCase);
167
168 Q_ASSERT(!systemOutputElement && !systemErrorElement);
169 systemOutputElement = new QTestElement(QTest::LET_SystemOutput);
170 systemErrorElement = new QTestElement(QTest::LET_SystemError);
171
172 // The element will be deleted when the suite is deleted
173
174 ++testCounter;
175
176 elapsedTestcaseTime.restart();
177}
178
179void QJUnitTestLogger::enterTestData(QTestData *)
180{
181 QTestCharBuffer testIdentifier;
182 QTestPrivate::generateTestIdentifier(&testIdentifier,
183 QTestPrivate::TestFunction | QTestPrivate::TestDataTag);
184
185 static const char *lastTestFunction = nullptr;
186 if (QTestResult::currentTestFunction() != lastTestFunction) {
187 // Adopt existing testcase for the initial test data
188 auto *name = const_cast<QTestElementAttribute*>(
189 currentTestCase->attribute(QTest::AI_Name));
190 name->setPair(QTest::AI_Name, testIdentifier.data());
191 lastTestFunction = QTestResult::currentTestFunction();
192 elapsedTestcaseTime.restart();
193 } else {
194 // Create new test cases for remaining test data
195 leaveTestCase();
196 enterTestCase(testIdentifier.data());
197 }
198}
199
200void QJUnitTestLogger::leaveTestFunction()
201{
202 leaveTestCase();
203}
204
205void QJUnitTestLogger::leaveTestCase()
206{
207 currentTestCase->addAttribute(QTest::AI_Time,
208 toSecondsFormat(elapsedTestCaseSeconds()).constData());
209
210 if (!systemOutputElement->childElements().empty())
211 currentTestCase->addChild(systemOutputElement);
212 else
213 delete systemOutputElement;
214
215 if (!systemErrorElement->childElements().empty())
216 currentTestCase->addChild(systemErrorElement);
217 else
218 delete systemErrorElement;
219
220 systemOutputElement = nullptr;
221 systemErrorElement = nullptr;
222}
223
224void QJUnitTestLogger::addIncident(IncidentTypes type, const char *description,
225 const char *file, int line)
226{
227 if (type == QAbstractTestLogger::Fail || type == QAbstractTestLogger::XPass) {
228 auto failureType = [&]() {
229 switch (type) {
230 case QAbstractTestLogger::Fail: return "fail";
231 case QAbstractTestLogger::XPass: return "xpass";
232 default: Q_UNREACHABLE();
233 }
234 }();
235
236 addFailure(QTest::LET_Failure, failureType, QString::fromUtf8(description));
237 } else if (type == QAbstractTestLogger::XFail) {
238 // Since XFAIL does not add a failure to the testlog in JUnit XML we add a
239 // message, so we still have some information about the expected failure.
240 addMessage(QAbstractTestLogger::Info, QString::fromUtf8(description), file, line);
241 }
242}
243
244void QJUnitTestLogger::addFailure(QTest::LogElementType elementType,
245 const char *failureType, const QString &failureDescription)
246{
247 if (elementType == QTest::LET_Failure) {
248 // Make sure we're not adding failure when we already have error,
249 // or adding additional failures when we already have a failure.
250 for (auto *childElement : currentTestCase->childElements()) {
251 if (childElement->elementType() == QTest::LET_Error ||
252 childElement->elementType() == QTest::LET_Failure)
253 return;
254 }
255 }
256
257 QTestElement *failureElement = new QTestElement(elementType);
258 failureElement->addAttribute(QTest::AI_Type, failureType);
259
260 // Assume the first line is the message, and the remainder are details
261 QString message = failureDescription.section(QLatin1Char('\n'), 0, 0);
262 QString details = failureDescription.section(QLatin1Char('\n'), 1);
263
264 failureElement->addAttribute(QTest::AI_Message, message.toUtf8().constData());
265
266 if (!details.isEmpty()) {
267 auto textNode = new QTestElement(QTest::LET_Text);
268 textNode->addAttribute(QTest::AI_Value, details.toUtf8().constData());
269 failureElement->addChild(textNode);
270 }
271
272 currentTestCase->addChild(failureElement);
273
274 switch (elementType) {
275 case QTest::LET_Failure: ++failureCounter; break;
276 case QTest::LET_Error: ++errorCounter; break;
277 default: Q_UNREACHABLE();
278 }
279}
280
281void QJUnitTestLogger::addMessage(MessageTypes type, const QString &message, const char *file, int line)
282{
283 Q_UNUSED(file);
284 Q_UNUSED(line);
285
286 if (type == QAbstractTestLogger::Skip) {
287 auto skippedElement = new QTestElement(QTest::LET_Skipped);
288 skippedElement->addAttribute(QTest::AI_Message, message.toUtf8().constData());
289 currentTestCase->addChild(skippedElement);
290 return;
291 } else if (type == QAbstractTestLogger::QFatal) {
292 addFailure(QTest::LET_Error, "qfatal", message);
293 return;
294 }
295
296 auto systemLogElement = [&]() {
297 switch (type) {
298 case QAbstractTestLogger::QDebug:
299 case QAbstractTestLogger::Info:
300 case QAbstractTestLogger::QInfo:
301 return systemOutputElement;
302 case QAbstractTestLogger::Warn:
303 case QAbstractTestLogger::QWarning:
304 case QAbstractTestLogger::QCritical:
305 return systemErrorElement;
306 default:
307 Q_UNREACHABLE();
308 }
309 }();
310
311 if (!systemLogElement)
312 return; // FIXME: Handle messages outside of test functions
313
314 auto textNode = new QTestElement(QTest::LET_Text);
315 textNode->addAttribute(QTest::AI_Value, message.toUtf8().constData());
316 systemLogElement->addChild(textNode);
317}
318
319QT_END_NAMESPACE
320
321

source code of qtbase/src/testlib/qjunittestlogger.cpp