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/qtestresult_p.h>
41#include <QtTest/qtestassert.h>
42#include <QtTest/private/qtestlog_p.h>
43#include <QtTest/private/qplaintestlogger_p.h>
44#include <QtTest/private/qbenchmark_p.h>
45#include <QtTest/private/qbenchmarkmetric_p.h>
46
47#include <QtCore/private/qlogging_p.h>
48
49#include <stdarg.h>
50#include <stdio.h>
51#include <stdlib.h>
52#include <string.h>
53
54#ifdef min // windows.h without NOMINMAX is included by the benchmark headers.
55# undef min
56#endif
57#ifdef max
58# undef max
59#endif
60
61#include <QtCore/QByteArray>
62#include <QtCore/qmath.h>
63#include <QtCore/QLibraryInfo>
64
65#ifdef Q_OS_ANDROID
66# include <android/log.h>
67#endif
68
69#ifdef Q_OS_WIN
70# include <qt_windows.h>
71#endif
72
73QT_BEGIN_NAMESPACE
74
75namespace QTest {
76
77 static const char *incidentType2String(QAbstractTestLogger::IncidentTypes type)
78 {
79 switch (type) {
80 case QAbstractTestLogger::Pass:
81 return "PASS ";
82 case QAbstractTestLogger::XFail:
83 return "XFAIL ";
84 case QAbstractTestLogger::Fail:
85 return "FAIL! ";
86 case QAbstractTestLogger::XPass:
87 return "XPASS ";
88 case QAbstractTestLogger::BlacklistedPass:
89 return "BPASS ";
90 case QAbstractTestLogger::BlacklistedFail:
91 return "BFAIL ";
92 case QAbstractTestLogger::BlacklistedXPass:
93 return "BXPASS ";
94 case QAbstractTestLogger::BlacklistedXFail:
95 return "BXFAIL ";
96 }
97 return "??????";
98 }
99
100 static const char *benchmarkResult2String()
101 {
102 return "RESULT ";
103 }
104
105 static const char *messageType2String(QAbstractTestLogger::MessageTypes type)
106 {
107 switch (type) {
108 case QAbstractTestLogger::Skip:
109 return "SKIP ";
110 case QAbstractTestLogger::Warn:
111 return "WARNING";
112 case QAbstractTestLogger::QWarning:
113 return "QWARN ";
114 case QAbstractTestLogger::QDebug:
115 return "QDEBUG ";
116 case QAbstractTestLogger::QInfo:
117 return "QINFO ";
118 case QAbstractTestLogger::QSystem:
119 return "QSYSTEM";
120 case QAbstractTestLogger::QFatal:
121 return "QFATAL ";
122 case QAbstractTestLogger::Info:
123 return "INFO ";
124 }
125 return "??????";
126 }
127
128 template <typename T>
129 static int countSignificantDigits(T num)
130 {
131 if (num <= 0)
132 return 0;
133
134 int digits = 0;
135 qreal divisor = 1;
136
137 while (num / divisor >= 1) {
138 divisor *= 10;
139 ++digits;
140 }
141
142 return digits;
143 }
144
145 // Pretty-prints a benchmark result using the given number of digits.
146 template <typename T> QString formatResult(T number, int significantDigits)
147 {
148 if (number < T(0))
149 return QLatin1String("NAN");
150 if (number == T(0))
151 return QLatin1String("0");
152
153 QString beforeDecimalPoint = QString::number(qint64(number), f: 'f', prec: 0);
154 QString afterDecimalPoint = QString::number(number, 'f', 20);
155 afterDecimalPoint.remove(i: 0, len: beforeDecimalPoint.count() + 1);
156
157 int beforeUse = qMin(a: beforeDecimalPoint.count(), b: significantDigits);
158 int beforeRemove = beforeDecimalPoint.count() - beforeUse;
159
160 // Replace insignificant digits before the decimal point with zeros.
161 beforeDecimalPoint.chop(n: beforeRemove);
162 for (int i = 0; i < beforeRemove; ++i) {
163 beforeDecimalPoint.append(c: QLatin1Char('0'));
164 }
165
166 int afterUse = significantDigits - beforeUse;
167
168 // leading zeroes after the decimal point does not count towards the digit use.
169 if (beforeDecimalPoint == QLatin1String("0") && afterDecimalPoint.isEmpty() == false) {
170 ++afterUse;
171
172 int i = 0;
173 while (i < afterDecimalPoint.count() && afterDecimalPoint.at(i) == QLatin1Char('0')) {
174 ++i;
175 }
176
177 afterUse += i;
178 }
179
180 int afterRemove = afterDecimalPoint.count() - afterUse;
181 afterDecimalPoint.chop(n: afterRemove);
182
183 QChar separator = QLatin1Char(',');
184 QChar decimalPoint = QLatin1Char('.');
185
186 // insert thousands separators
187 int length = beforeDecimalPoint.length();
188 for (int i = beforeDecimalPoint.length() -1; i >= 1; --i) {
189 if ((length - i) % 3 == 0)
190 beforeDecimalPoint.insert(i, c: separator);
191 }
192
193 QString print;
194 print = beforeDecimalPoint;
195 if (afterUse > 0)
196 print.append(c: decimalPoint);
197
198 print += afterDecimalPoint;
199
200
201 return print;
202 }
203
204 template <typename T>
205 int formatResult(char * buffer, int bufferSize, T number, int significantDigits)
206 {
207 QString result = formatResult(number, significantDigits);
208 int size = result.count();
209 qstrncpy(dst: buffer, src: std::move(result).toLatin1().constData(), len: bufferSize);
210 return size;
211 }
212}
213
214void QPlainTestLogger::outputMessage(const char *str)
215{
216#if defined(Q_OS_WIN)
217 // Log to system log only if output is not redirected and stderr not preferred
218 if (stream == stdout && !QtPrivate::shouldLogToStderr()) {
219 OutputDebugStringA(str);
220 return;
221 }
222#elif defined(Q_OS_ANDROID)
223 __android_log_write(ANDROID_LOG_INFO, "QTestLib", str);
224#endif
225 outputString(msg: str);
226}
227
228void QPlainTestLogger::printMessage(MessageSource source, const char *type, const char *msg,
229 const char *file, int line)
230{
231 QTEST_ASSERT(type);
232 QTEST_ASSERT(msg);
233
234 QTestCharBuffer messagePrefix;
235
236 QTestCharBuffer failureLocation;
237#ifdef Q_OS_WIN
238 constexpr const char *INCIDENT_LOCATION_STR = "\n%s(%d) : failure location";
239 constexpr const char *OTHER_LOCATION_STR = "\n%s(%d) : message location";
240#else
241 constexpr const char *INCIDENT_LOCATION_STR = "\n Loc: [%s(%d)]";
242 constexpr const char *OTHER_LOCATION_STR = INCIDENT_LOCATION_STR;
243#endif
244
245 if (file) {
246 switch (source) {
247 case MessageSource::Incident:
248 QTest::qt_asprintf(buf: &failureLocation, format: INCIDENT_LOCATION_STR, file, line);
249 break;
250 case MessageSource::Other:
251 QTest::qt_asprintf(buf: &failureLocation, format: OTHER_LOCATION_STR, file, line);
252 break;
253 }
254 }
255
256 const char *msgFiller = msg[0] ? " " : "";
257 QTestCharBuffer testIdentifier;
258 QTestPrivate::generateTestIdentifier(identifier: &testIdentifier);
259 QTest::qt_asprintf(buf: &messagePrefix, format: "%s: %s%s%s%s\n",
260 type, testIdentifier.data(), msgFiller, msg, failureLocation.data());
261
262 // In colored mode, printf above stripped our nonprintable control characters.
263 // Put them back.
264 memcpy(dest: messagePrefix.data(), src: type, n: strlen(s: type));
265
266 outputMessage(str: messagePrefix.data());
267}
268
269void QPlainTestLogger::printBenchmarkResult(const QBenchmarkResult &result)
270{
271 const char *bmtag = QTest::benchmarkResult2String();
272
273 char buf1[1024];
274 qsnprintf(
275 str: buf1, n: sizeof(buf1), fmt: "%s: %s::%s",
276 bmtag,
277 QTestResult::currentTestObjectName(),
278 result.context.slotName.toLatin1().data());
279
280 char bufTag[1024];
281 bufTag[0] = 0;
282 QByteArray tag = result.context.tag.toLocal8Bit();
283 if (tag.isEmpty() == false) {
284 qsnprintf(str: bufTag, n: sizeof(bufTag), fmt: ":\"%s\"", tag.data());
285 }
286
287
288 char fillFormat[8];
289 int fillLength = 5;
290 qsnprintf(str: fillFormat, n: sizeof(fillFormat), fmt: ":\n%%%ds", fillLength);
291 char fill[1024];
292 qsnprintf(str: fill, n: sizeof(fill), fmt: fillFormat, "");
293
294 const char * unitText = QTest::benchmarkMetricUnit(metric: result.metric);
295
296 qreal valuePerIteration = qreal(result.value) / qreal(result.iterations);
297 char resultBuffer[100] = "";
298 QTest::formatResult(buffer: resultBuffer, bufferSize: 100, number: valuePerIteration, significantDigits: QTest::countSignificantDigits(num: result.value));
299
300 char buf2[1024];
301 qsnprintf(str: buf2, n: sizeof(buf2), fmt: "%s %s", resultBuffer, unitText);
302
303 char buf2_[1024];
304 QByteArray iterationText = " per iteration";
305 Q_ASSERT(result.iterations > 0);
306 qsnprintf(str: buf2_, n: sizeof(buf2_), fmt: "%s", iterationText.data());
307
308 char buf3[1024];
309 Q_ASSERT(result.iterations > 0);
310 QTest::formatResult(buffer: resultBuffer, bufferSize: 100, number: result.value, significantDigits: QTest::countSignificantDigits(num: result.value));
311 qsnprintf(str: buf3, n: sizeof(buf3), fmt: " (total: %s, iterations: %d)", resultBuffer, result.iterations);
312
313 char buf[1024];
314
315 if (result.setByMacro) {
316 qsnprintf(str: buf, n: sizeof(buf), fmt: "%s%s%s%s%s%s\n", buf1, bufTag, fill, buf2, buf2_, buf3);
317 } else {
318 qsnprintf(str: buf, n: sizeof(buf), fmt: "%s%s%s%s\n", buf1, bufTag, fill, buf2);
319 }
320
321 memcpy(dest: buf, src: bmtag, n: strlen(s: bmtag));
322 outputMessage(str: buf);
323}
324
325QPlainTestLogger::QPlainTestLogger(const char *filename)
326 : QAbstractTestLogger(filename)
327{
328}
329
330QPlainTestLogger::~QPlainTestLogger() = default;
331
332void QPlainTestLogger::startLogging()
333{
334 QAbstractTestLogger::startLogging();
335
336 char buf[1024];
337 if (QTestLog::verboseLevel() < 0) {
338 qsnprintf(str: buf, n: sizeof(buf), fmt: "Testing %s\n", QTestResult::currentTestObjectName());
339 } else {
340 qsnprintf(str: buf, n: sizeof(buf),
341 fmt: "********* Start testing of %s *********\n"
342 "Config: Using QtTest library " QTEST_VERSION_STR
343 ", %s, %s %s\n", QTestResult::currentTestObjectName(), QLibraryInfo::build(),
344 qPrintable(QSysInfo::productType()), qPrintable(QSysInfo::productVersion()));
345 }
346 outputMessage(str: buf);
347}
348
349void QPlainTestLogger::stopLogging()
350{
351 char buf[1024];
352 const int timeMs = qRound(d: QTestLog::msecsTotalTime());
353 if (QTestLog::verboseLevel() < 0) {
354 qsnprintf(str: buf, n: sizeof(buf), fmt: "Totals: %d passed, %d failed, %d skipped, %d blacklisted, %dms\n",
355 QTestLog::passCount(), QTestLog::failCount(),
356 QTestLog::skipCount(), QTestLog::blacklistCount(), timeMs);
357 } else {
358 qsnprintf(str: buf, n: sizeof(buf),
359 fmt: "Totals: %d passed, %d failed, %d skipped, %d blacklisted, %dms\n"
360 "********* Finished testing of %s *********\n",
361 QTestLog::passCount(), QTestLog::failCount(),
362 QTestLog::skipCount(), QTestLog::blacklistCount(), timeMs,
363 QTestResult::currentTestObjectName());
364 }
365 outputMessage(str: buf);
366
367 QAbstractTestLogger::stopLogging();
368}
369
370
371void QPlainTestLogger::enterTestFunction(const char * /*function*/)
372{
373 if (QTestLog::verboseLevel() >= 1)
374 printMessage(source: MessageSource::Other, type: QTest::messageType2String(type: Info), msg: "entering");
375}
376
377void QPlainTestLogger::leaveTestFunction()
378{
379}
380
381void QPlainTestLogger::addIncident(IncidentTypes type, const char *description,
382 const char *file, int line)
383{
384 // suppress PASS and XFAIL in silent mode
385 if ((type == QAbstractTestLogger::Pass || type == QAbstractTestLogger::XFail)
386 && QTestLog::verboseLevel() < 0)
387 return;
388
389 printMessage(source: MessageSource::Incident, type: QTest::incidentType2String(type), msg: description, file, line);
390}
391
392void QPlainTestLogger::addBenchmarkResult(const QBenchmarkResult &result)
393{
394 // suppress benchmark results in silent mode
395 if (QTestLog::verboseLevel() < 0)
396 return;
397
398 printBenchmarkResult(result);
399}
400
401void QPlainTestLogger::addMessage(QtMsgType type, const QMessageLogContext &context, const QString &message)
402{
403 QAbstractTestLogger::addMessage(type, context, message);
404}
405
406void QPlainTestLogger::addMessage(MessageTypes type, const QString &message,
407 const char *file, int line)
408{
409 // suppress non-fatal messages in silent mode
410 if (type != QAbstractTestLogger::QFatal && QTestLog::verboseLevel() < 0)
411 return;
412
413 printMessage(source: MessageSource::Other, type: QTest::messageType2String(type), qPrintable(message), file, line);
414}
415
416QT_END_NAMESPACE
417

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