1// Copyright (C) 2022 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include <QtTest/private/qjunittestlogger_p.h>
5#include <QtTest/private/qtestelement_p.h>
6#include <QtTest/private/qtestjunitstreamer_p.h>
7#include <QtTest/qtestcase.h>
8#include <QtTest/private/qtestresult_p.h>
9#include <QtTest/private/qbenchmark_p.h>
10#include <QtTest/private/qtestlog_p.h>
11
12#include <QtCore/qlibraryinfo.h>
13
14#include <cstdio>
15
16#include <string.h>
17
18QT_BEGIN_NAMESPACE
19/*! \internal
20 \class QJUnitTestLogger
21 \inmodule QtTest
22
23 QJUnitTestLogger implements logging in a JUnit-compatible XML format.
24
25 The \l{JUnit XML} format was originally developed for Java testing.
26 It is supported by \l{Test Center}.
27*/
28// QTBUG-95424 links to further useful documentation.
29
30QJUnitTestLogger::QJUnitTestLogger(const char *filename)
31 : QAbstractTestLogger(filename)
32{
33}
34
35QJUnitTestLogger::~QJUnitTestLogger()
36{
37 Q_ASSERT(!currentTestSuite);
38 delete logFormatter;
39}
40
41// We track test timing per test case, so we
42// need to maintain our own elapsed timer.
43Q_CONSTINIT static QElapsedTimer elapsedTestcaseTime;
44static qreal elapsedTestCaseSeconds()
45{
46 return elapsedTestcaseTime.nsecsElapsed() / 1e9;
47}
48
49static QByteArray toSecondsFormat(qreal ms)
50{
51 return QByteArray::number(ms / 1000, format: 'f', precision: 3);
52}
53
54void QJUnitTestLogger::startLogging()
55{
56 QAbstractTestLogger::startLogging();
57
58 logFormatter = new QTestJUnitStreamer(this);
59
60 Q_ASSERT(!currentTestSuite);
61 currentTestSuite = new QTestElement(QTest::LET_TestSuite);
62 currentTestSuite->addAttribute(attributeIndex: QTest::AI_Name, value: QTestResult::currentTestObjectName());
63
64 auto localTime = QDateTime::currentDateTime();
65 currentTestSuite->addAttribute(attributeIndex: QTest::AI_Timestamp,
66 value: localTime.toString(format: Qt::ISODate).toUtf8().constData());
67
68 currentTestSuite->addAttribute(attributeIndex: QTest::AI_Hostname,
69 value: QSysInfo::machineHostName().toUtf8().constData());
70
71 QTestElement *property;
72 QTestElement *properties = new QTestElement(QTest::LET_Properties);
73
74 property = new QTestElement(QTest::LET_Property);
75 property->addAttribute(attributeIndex: QTest::AI_Name, value: "QTestVersion");
76 property->addAttribute(attributeIndex: QTest::AI_PropertyValue, QTEST_VERSION_STR);
77 properties->addChild(element: property);
78
79 property = new QTestElement(QTest::LET_Property);
80 property->addAttribute(attributeIndex: QTest::AI_Name, value: "QtVersion");
81 property->addAttribute(attributeIndex: QTest::AI_PropertyValue, value: qVersion());
82 properties->addChild(element: property);
83
84 property = new QTestElement(QTest::LET_Property);
85 property->addAttribute(attributeIndex: QTest::AI_Name, value: "QtBuild");
86 property->addAttribute(attributeIndex: QTest::AI_PropertyValue, value: QLibraryInfo::build());
87 properties->addChild(element: property);
88
89 currentTestSuite->addChild(element: properties);
90
91 elapsedTestcaseTime.start();
92}
93
94void QJUnitTestLogger::stopLogging()
95{
96 char buf[10];
97
98 std::snprintf(s: buf, maxlen: sizeof(buf), format: "%i", testCounter);
99 currentTestSuite->addAttribute(attributeIndex: QTest::AI_Tests, value: buf);
100
101 std::snprintf(s: buf, maxlen: sizeof(buf), format: "%i", failureCounter);
102 currentTestSuite->addAttribute(attributeIndex: QTest::AI_Failures, value: buf);
103
104 std::snprintf(s: buf, maxlen: sizeof(buf), format: "%i", errorCounter);
105 currentTestSuite->addAttribute(attributeIndex: QTest::AI_Errors, value: buf);
106
107 std::snprintf(s: buf, maxlen: sizeof(buf), format: "%i", QTestLog::skipCount());
108 currentTestSuite->addAttribute(attributeIndex: QTest::AI_Skipped, value: buf);
109
110 currentTestSuite->addAttribute(attributeIndex: QTest::AI_Time,
111 value: toSecondsFormat(ms: QTestLog::msecsTotalTime()).constData());
112
113 for (auto *testCase : listOfTestcases)
114 currentTestSuite->addChild(element: testCase);
115 listOfTestcases.clear();
116
117 logFormatter->output(element: currentTestSuite);
118
119 delete currentTestSuite;
120 currentTestSuite = nullptr;
121
122 QAbstractTestLogger::stopLogging();
123}
124
125void QJUnitTestLogger::enterTestFunction(const char *function)
126{
127 enterTestCase(name: function);
128}
129
130void QJUnitTestLogger::enterTestCase(const char *name)
131{
132 currentTestCase = new QTestElement(QTest::LET_TestCase);
133 currentTestCase->addAttribute(attributeIndex: QTest::AI_Name, value: name);
134 currentTestCase->addAttribute(attributeIndex: QTest::AI_Classname, value: QTestResult::currentTestObjectName());
135 listOfTestcases.push_back(x: currentTestCase);
136
137 Q_ASSERT(!systemOutputElement && !systemErrorElement);
138 systemOutputElement = new QTestElement(QTest::LET_SystemOutput);
139 systemErrorElement = new QTestElement(QTest::LET_SystemError);
140
141 // The element will be deleted when the suite is deleted
142
143 ++testCounter;
144
145 elapsedTestcaseTime.restart();
146}
147
148void QJUnitTestLogger::enterTestData(QTestData *)
149{
150 QTestCharBuffer testIdentifier;
151 QTestPrivate::generateTestIdentifier(identifier: &testIdentifier,
152 parts: QTestPrivate::TestFunction | QTestPrivate::TestDataTag);
153
154 static const char *lastTestFunction = nullptr;
155 if (QTestResult::currentTestFunction() != lastTestFunction) {
156 // Adopt existing testcase for the initial test data
157 auto *name = const_cast<QTestElementAttribute*>(
158 currentTestCase->attribute(index: QTest::AI_Name));
159 name->setPair(attributeIndex: QTest::AI_Name, value: testIdentifier.data());
160 lastTestFunction = QTestResult::currentTestFunction();
161 elapsedTestcaseTime.restart();
162 } else {
163 // Create new test cases for remaining test data
164 leaveTestCase();
165 enterTestCase(name: testIdentifier.data());
166 }
167}
168
169void QJUnitTestLogger::leaveTestFunction()
170{
171 leaveTestCase();
172}
173
174void QJUnitTestLogger::leaveTestCase()
175{
176 currentTestCase->addAttribute(attributeIndex: QTest::AI_Time,
177 value: toSecondsFormat(ms: elapsedTestCaseSeconds() * 1000).constData());
178
179 if (!systemOutputElement->childElements().empty())
180 currentTestCase->addChild(element: systemOutputElement);
181 else
182 delete systemOutputElement;
183
184 if (!systemErrorElement->childElements().empty())
185 currentTestCase->addChild(element: systemErrorElement);
186 else
187 delete systemErrorElement;
188
189 systemOutputElement = nullptr;
190 systemErrorElement = nullptr;
191}
192
193void QJUnitTestLogger::addIncident(IncidentTypes type, const char *description,
194 const char *file, int line)
195{
196 if (type == Fail || type == XPass) {
197 auto failureType = [&]() {
198 switch (type) {
199 case QAbstractTestLogger::Fail: return "fail";
200 case QAbstractTestLogger::XPass: return "xpass";
201 default: Q_UNREACHABLE();
202 }
203 }();
204
205 addFailure(elementType: QTest::LET_Failure, failureType, failureDescription: QString::fromUtf8(utf8: description));
206 } else if (type == XFail) {
207 // Since XFAIL does not add a failure to the testlog in JUnit XML we add a
208 // message, so we still have some information about the expected failure.
209 addMessage(type: Info, message: QString::fromUtf8(utf8: description), file, line);
210 } else if (type == Skip) {
211 auto skippedElement = new QTestElement(QTest::LET_Skipped);
212 skippedElement->addAttribute(attributeIndex: QTest::AI_Message, value: description);
213 currentTestCase->addChild(element: skippedElement);
214 }
215}
216
217void QJUnitTestLogger::addFailure(QTest::LogElementType elementType,
218 const char *failureType, const QString &failureDescription)
219{
220 if (elementType == QTest::LET_Failure) {
221 // Make sure we're not adding failure when we already have error,
222 // or adding additional failures when we already have a failure.
223 for (auto *childElement : currentTestCase->childElements()) {
224 if (childElement->elementType() == QTest::LET_Error ||
225 childElement->elementType() == QTest::LET_Failure)
226 return;
227 }
228 }
229
230 QTestElement *failureElement = new QTestElement(elementType);
231 failureElement->addAttribute(attributeIndex: QTest::AI_Type, value: failureType);
232
233 // Assume the first line is the message, and the remainder are details
234 QString message = failureDescription.section(asep: u'\n', astart: 0, aend: 0);
235 QString details = failureDescription.section(asep: u'\n', astart: 1);
236
237 failureElement->addAttribute(attributeIndex: QTest::AI_Message, value: message.toUtf8().constData());
238
239 if (!details.isEmpty()) {
240 auto textNode = new QTestElement(QTest::LET_Text);
241 textNode->addAttribute(attributeIndex: QTest::AI_Value, value: details.toUtf8().constData());
242 failureElement->addChild(element: textNode);
243 }
244
245 currentTestCase->addChild(element: failureElement);
246
247 switch (elementType) {
248 case QTest::LET_Failure: ++failureCounter; break;
249 case QTest::LET_Error: ++errorCounter; break;
250 default: Q_UNREACHABLE();
251 }
252}
253
254void QJUnitTestLogger::addMessage(MessageTypes type, const QString &message, const char *file, int line)
255{
256 Q_UNUSED(file);
257 Q_UNUSED(line);
258
259 if (type == QFatal) {
260 addFailure(elementType: QTest::LET_Error, failureType: "qfatal", failureDescription: message);
261 return;
262 }
263
264 auto systemLogElement = [&]() {
265 switch (type) {
266 case QAbstractTestLogger::QDebug:
267 case QAbstractTestLogger::Info:
268 case QAbstractTestLogger::QInfo:
269 return systemOutputElement;
270 case QAbstractTestLogger::Warn:
271 case QAbstractTestLogger::QWarning:
272 case QAbstractTestLogger::QCritical:
273 return systemErrorElement;
274 default:
275 Q_UNREACHABLE();
276 }
277 }();
278
279 if (!systemLogElement)
280 return; // FIXME: Handle messages outside of test functions
281
282 auto textNode = new QTestElement(QTest::LET_Text);
283 textNode->addAttribute(attributeIndex: QTest::AI_Value, value: message.toUtf8().constData());
284 systemLogElement->addChild(element: textNode);
285}
286
287QT_END_NAMESPACE
288
289

Provided by KDAB

Privacy Policy
Start learning QML with our Intro Training
Find out more

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