Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qplaintestlogger.cpp
Go to the documentation of this file.
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/qtestresult_p.h>
5#include <QtTest/qtestassert.h>
6#include <QtTest/private/qtestlog_p.h>
7#include <QtTest/private/qplaintestlogger_p.h>
8#include <QtTest/private/qbenchmark_p.h>
9#include <QtTest/private/qbenchmarkmetric_p.h>
10
11#include <QtCore/private/qlogging_p.h>
12
13#include <array>
14#include <cstdio>
15
16#include <stdio.h>
17#include <stdlib.h>
18#include <string.h>
19
20#include <QtCore/QByteArray>
21#include <QtCore/qmath.h>
22#include <QtCore/QLibraryInfo>
23
24#ifdef Q_OS_ANDROID
25# include <android/log.h>
26#endif
27
28#ifdef Q_OS_WIN
29# include <qt_windows.h>
30#endif
31
32QT_BEGIN_NAMESPACE
33
34using namespace Qt::StringLiterals;
35
36namespace {
37static const char multiplePrefixes[] = "\0kMGTPE"; // kilo, mega, giga, tera, peta, exa
38static const char submultiplePrefixes[] = "afpnum"; // atto, femto, pico, nano, micro, milli
39
40template <int N> struct FixedBufString
41{
42 static constexpr size_t MaxSize = N;
43 size_t used = 0;
44 std::array<char, N + 2> buf; // for the newline and terminating null
45 FixedBufString()
46 {
47 clear();
48 }
49 void clear()
50 {
51 used = 0;
52 buf[0] = '\0';
53 }
54
55 operator const char *() const
56 {
57 return buf.data();
58 }
59
60 void append(const char *text)
61 {
62 size_t len = qMin(strlen(text), MaxSize - used);
63 memcpy(buf.data() + used, text, len);
64 used += len;
65 buf[used] = '\0';
66 }
67
68 template <typename... Args> void appendf(const char *format, Args... args)
69 {
70 // vsnprintf includes the terminating null
71 used += std::snprintf(buf.data() + used, MaxSize - used + 1, format,
72 args...);
73 }
74
75 template <int Power = 1000> void appendScaled(qreal value, const char *unit)
76 {
77 char prefix[2] = {};
78 qreal v = qAbs(value);
79 qint64 ratio;
80 if (v < 1 && Power == 1000) {
81 const char *prefixes = submultiplePrefixes;
82 ratio = qreal(std::atto::num) / qreal(std::atto::den);
83 while (value * ratio > 1000 && *prefixes) {
84 ++prefixes;
85 ratio *= 1000;
86 }
87 prefix[0] = *prefixes;
88 } else {
89 const char *prefixes = multiplePrefixes;
90 ratio = 1;
91 while (value > 1000 * ratio) { // yes, even for binary
92 ++prefixes;
93 ratio *= Power;
94 }
95 prefix[0] = *prefixes;
96 }
97
98 // adjust the value by the ratio
99 value /= ratio;
100 appendf(", %.3g %s%s", value, prefix, unit);
101 }
102};
103} // unnamed namespace
104
105namespace QTest {
106
107 static const char *ptIncidentType2String(QAbstractTestLogger::IncidentTypes type)
108 {
109 switch (type) {
110 case QAbstractTestLogger::Skip:
111 return "SKIP ";
112 case QAbstractTestLogger::Pass:
113 return "PASS ";
114 case QAbstractTestLogger::XFail:
115 return "XFAIL ";
116 case QAbstractTestLogger::Fail:
117 return "FAIL! ";
118 case QAbstractTestLogger::XPass:
119 return "XPASS ";
120 case QAbstractTestLogger::BlacklistedPass:
121 return "BPASS ";
122 case QAbstractTestLogger::BlacklistedFail:
123 return "BFAIL ";
124 case QAbstractTestLogger::BlacklistedXPass:
125 return "BXPASS ";
126 case QAbstractTestLogger::BlacklistedXFail:
127 return "BXFAIL ";
128 }
129 Q_UNREACHABLE_RETURN(nullptr);
130 }
131
132 static const char *benchmarkResult2String()
133 {
134 return "RESULT ";
135 }
136
137 static const char *ptMessageType2String(QAbstractTestLogger::MessageTypes type)
138 {
139 switch (type) {
140 case QAbstractTestLogger::QDebug:
141 return "QDEBUG ";
142 case QAbstractTestLogger::QInfo:
143 return "QINFO ";
144 case QAbstractTestLogger::QWarning:
145 return "QWARN ";
146 case QAbstractTestLogger::QCritical:
147 return "QCRITICAL";
148 case QAbstractTestLogger::QFatal:
149 return "QFATAL ";
150 case QAbstractTestLogger::Info:
151 return "INFO ";
152 case QAbstractTestLogger::Warn:
153 return "WARNING";
154 }
155 Q_UNREACHABLE_RETURN(nullptr);
156 }
157
158 template <typename T>
159 static int countSignificantDigits(T num)
160 {
161 if (num <= 0)
162 return 0;
163
164 int digits = 0;
165 qreal divisor = 1;
166
167 while (num / divisor >= 1) {
168 divisor *= 10;
169 ++digits;
170 }
171
172 return digits;
173 }
174
175 // Pretty-prints a benchmark result using the given number of digits.
176 template <typename T> QByteArray formatResult(T number, int significantDigits)
177 {
178 if (number < T(0))
179 return "NAN";
180 if (number == T(0))
181 return "0";
182
183 QByteArray beforeDecimalPoint = QByteArray::number(qint64(number), 'f', 0);
184 QByteArray afterDecimalPoint = QByteArray::number(number, 'f', 20);
185 afterDecimalPoint.remove(0, beforeDecimalPoint.size() + 1);
186
187 int beforeUse = qMin(beforeDecimalPoint.size(), significantDigits);
188 int beforeRemove = beforeDecimalPoint.size() - beforeUse;
189
190 // Replace insignificant digits before the decimal point with zeros.
191 beforeDecimalPoint.chop(beforeRemove);
192 for (int i = 0; i < beforeRemove; ++i) {
193 beforeDecimalPoint.append(u'0');
194 }
195
196 int afterUse = significantDigits - beforeUse;
197
198 // leading zeroes after the decimal point does not count towards the digit use.
199 if (beforeDecimalPoint == "0" && !afterDecimalPoint.isEmpty()) {
200 ++afterUse;
201
202 int i = 0;
203 while (i < afterDecimalPoint.size() && afterDecimalPoint.at(i) == '0')
204 ++i;
205
206 afterUse += i;
207 }
208
209 int afterRemove = afterDecimalPoint.size() - afterUse;
210 afterDecimalPoint.chop(afterRemove);
211
212 char separator = ',';
213 char decimalPoint = '.';
214
215 // insert thousands separators
216 int length = beforeDecimalPoint.size();
217 for (int i = beforeDecimalPoint.size() -1; i >= 1; --i) {
218 if ((length - i) % 3 == 0)
219 beforeDecimalPoint.insert(i, separator);
220 }
221
222 QByteArray print;
223 print = beforeDecimalPoint;
224 if (afterUse > 0)
225 print.append(decimalPoint);
226
227 print += afterDecimalPoint;
228
229
230 return print;
231 }
232}
233
234/*! \internal
235 \class QPlainTestLogger
236 \inmodule QtTest
237
238 QPlainTestLogger implements basic logging of test results.
239
240 The format is Qt-specific and aims to be easy to read.
241*/
242
243void QPlainTestLogger::outputMessage(const char *str)
244{
245#if defined(Q_OS_WIN)
246 // Log to system log only if output is not redirected and stderr not preferred
247 if (stream == stdout && !QtPrivate::shouldLogToStderr()) {
248 OutputDebugStringA(str);
249 return;
250 }
251#elif defined(Q_OS_ANDROID)
252 __android_log_write(ANDROID_LOG_INFO, "QTestLib", str);
253#endif
254 outputString(str);
255}
256
257void QPlainTestLogger::printMessage(MessageSource source, const char *type, const char *msg,
258 const char *file, int line)
259{
260 QTEST_ASSERT(type);
261 QTEST_ASSERT(msg);
262
263 QTestCharBuffer messagePrefix;
264
265 QTestCharBuffer messageLocation;
266#ifdef Q_OS_WIN
267 constexpr const char *INCIDENT_LOCATION_STR = "\n%s(%d) : failure location";
268 constexpr const char *OTHER_LOCATION_STR = "\n%s(%d) : message location";
269#else
270 constexpr const char *INCIDENT_LOCATION_STR = "\n Loc: [%s(%d)]";
271 constexpr const char *OTHER_LOCATION_STR = INCIDENT_LOCATION_STR;
272#endif
273
274 if (file) {
275 switch (source) {
276 case MessageSource::Incident:
277 QTest::qt_asprintf(&messageLocation, INCIDENT_LOCATION_STR, file, line);
278 break;
279 case MessageSource::Other:
280 QTest::qt_asprintf(&messageLocation, OTHER_LOCATION_STR, file, line);
281 break;
282 }
283 }
284
285 const char *msgFiller = msg[0] ? " " : "";
286 QTestCharBuffer testIdentifier;
287 QTestPrivate::generateTestIdentifier(&testIdentifier);
288 QTest::qt_asprintf(&messagePrefix, "%s: %s%s%s%s\n",
289 type, testIdentifier.data(), msgFiller, msg, messageLocation.data());
290
291 // In colored mode, printf above stripped our nonprintable control characters.
292 // Put them back.
293 memcpy(messagePrefix.data(), type, strlen(type));
294
295 outputMessage(messagePrefix.data());
296}
297
298void QPlainTestLogger::printBenchmarkResultsHeader(const QBenchmarkResult &result)
299{
300 FixedBufString<1022> buf;
301 buf.appendf("%s: %s::%s", QTest::benchmarkResult2String(),
302 QTestResult::currentTestObjectName(), result.context.slotName.toLatin1().data());
303
304 if (QByteArray tag = result.context.tag.toLocal8Bit(); !tag.isEmpty())
305 buf.appendf(":\"%s\":\n", tag.data());
306 else
307 buf.append(":\n");
308 outputMessage(buf);
309}
310
311void QPlainTestLogger::printBenchmarkResults(const QList<QBenchmarkResult> &results)
312{
313 using namespace std::chrono;
314 FixedBufString<1022> buf;
315 auto findResultFor = [&results](QTest::QBenchmarkMetric metric) -> std::optional<qreal> {
316 for (const QBenchmarkResult &result : results) {
317 if (result.measurement.metric == metric)
318 return result.measurement.value;
319 }
320 return std::nullopt;
321 };
322
323 // we need the execution time quite often, so find it first
324 qreal executionTime = 0;
325 if (auto ns = findResultFor(QTest::WalltimeNanoseconds))
326 executionTime = *ns / (1000 * 1000 * 1000);
327 else if (auto ms = findResultFor(QTest::WalltimeMilliseconds))
328 executionTime = *ms / 1000;
329
330 for (const QBenchmarkResult &result : results) {
331 buf.clear();
332
333 const char * unitText = QTest::benchmarkMetricUnit(result.measurement.metric);
334 int significantDigits = QTest::countSignificantDigits(result.measurement.value);
335 qreal valuePerIteration = qreal(result.measurement.value) / qreal(result.iterations);
336 buf.appendf(" %s %s%s", QTest::formatResult(valuePerIteration, significantDigits).constData(),
337 unitText, result.setByMacro ? " per iteration" : "");
338
339 switch (result.measurement.metric) {
340 case QTest::BitsPerSecond:
341 // for bits/s, we'll use powers of 10 (1 Mbit/s = 1000 kbit/s = 1000000 bit/s)
342 buf.appendScaled<1000>(result.measurement.value, "bit/s");
343 break;
344 case QTest::BytesPerSecond:
345 // for B/s, we'll use powers of 2 (1 MB/s = 1024 kB/s = 1048576 B/s)
346 buf.appendScaled<1024>(result.measurement.value, "B/s");
347 break;
348
349 case QTest::CPUCycles:
350 case QTest::RefCPUCycles:
351 if (!qIsNull(executionTime))
352 buf.appendScaled(result.measurement.value / executionTime, "Hz");
353 break;
354
355 case QTest::Instructions:
356 if (auto cycles = findResultFor(QTest::CPUCycles)) {
357 buf.appendf(", %.3f instr/cycle", result.measurement.value / *cycles);
358 break;
359 }
360 Q_FALLTHROUGH();
361
362 case QTest::InstructionReads:
363 case QTest::Events:
364 case QTest::BytesAllocated:
365 case QTest::CPUMigrations:
366 case QTest::BusCycles:
367 case QTest::StalledCycles:
368 case QTest::BranchInstructions:
369 case QTest::BranchMisses:
370 case QTest::CacheReferences:
371 case QTest::CacheReads:
372 case QTest::CacheWrites:
373 case QTest::CachePrefetches:
374 case QTest::CacheMisses:
375 case QTest::CacheReadMisses:
376 case QTest::CacheWriteMisses:
377 case QTest::CachePrefetchMisses:
378 case QTest::ContextSwitches:
379 case QTest::PageFaults:
380 case QTest::MinorPageFaults:
381 case QTest::MajorPageFaults:
382 case QTest::AlignmentFaults:
383 case QTest::EmulationFaults:
384 if (!qIsNull(executionTime))
385 buf.appendScaled(result.measurement.value / executionTime, "/sec");
386 break;
387
388 case QTest::FramesPerSecond:
389 case QTest::CPUTicks:
390 case QTest::WalltimeMilliseconds:
391 case QTest::WalltimeNanoseconds:
392 break; // no additional information
393 }
394
395 Q_ASSERT(result.iterations > 0);
396 buf.appendf(" (total: %s, iterations: %d)\n",
397 QTest::formatResult(result.measurement.value, significantDigits).constData(),
398 result.iterations);
399
400 outputMessage(buf);
401 }
402}
403
404QPlainTestLogger::QPlainTestLogger(const char *filename)
405 : QAbstractTestLogger(filename)
406{
407}
408
409QPlainTestLogger::~QPlainTestLogger() = default;
410
411void QPlainTestLogger::startLogging()
412{
413 QAbstractTestLogger::startLogging();
414
415 char buf[1024];
416 if (QTestLog::verboseLevel() < 0) {
417 std::snprintf(buf, sizeof(buf), "Testing %s\n", QTestResult::currentTestObjectName());
418 } else {
419 std::snprintf(buf, sizeof(buf),
420 "********* Start testing of %s *********\n"
421 "Config: Using QtTest library " QTEST_VERSION_STR
422 ", %s, %s %s\n", QTestResult::currentTestObjectName(), QLibraryInfo::build(),
423 qPrintable(QSysInfo::productType()), qPrintable(QSysInfo::productVersion()));
424 }
425 outputMessage(buf);
426}
427
428void QPlainTestLogger::stopLogging()
429{
430 char buf[1024];
431 const int timeMs = qRound(QTestLog::msecsTotalTime());
432 if (QTestLog::verboseLevel() < 0) {
433 std::snprintf(buf, sizeof(buf),
434 "Totals: %d passed, %d failed, %d skipped, %d blacklisted, %dms\n",
435 QTestLog::passCount(), QTestLog::failCount(),
436 QTestLog::skipCount(), QTestLog::blacklistCount(), timeMs);
437 } else {
438 std::snprintf(buf, sizeof(buf),
439 "Totals: %d passed, %d failed, %d skipped, %d blacklisted, %dms\n"
440 "********* Finished testing of %s *********\n",
441 QTestLog::passCount(), QTestLog::failCount(),
442 QTestLog::skipCount(), QTestLog::blacklistCount(), timeMs,
443 QTestResult::currentTestObjectName());
444 }
445 outputMessage(buf);
446
447 QAbstractTestLogger::stopLogging();
448}
449
450
451void QPlainTestLogger::enterTestFunction(const char * /*function*/)
452{
453 if (QTestLog::verboseLevel() >= 1)
454 printMessage(MessageSource::Other, QTest::ptMessageType2String(Info), "entering");
455}
456
457void QPlainTestLogger::leaveTestFunction()
458{
459}
460
461void QPlainTestLogger::addIncident(IncidentTypes type, const char *description,
462 const char *file, int line)
463{
464 // suppress B?PASS and B?XFAIL in silent mode
465 if ((type == Pass || type == BlacklistedPass || type == XFail || type == BlacklistedXFail)
466 && QTestLog::verboseLevel() < 0)
467 return;
468
469 printMessage(MessageSource::Incident, QTest::ptIncidentType2String(type), description, file, line);
470}
471
472void QPlainTestLogger::addBenchmarkResults(const QList<QBenchmarkResult> &results)
473{
474 // suppress benchmark results in silent mode
475 if (QTestLog::verboseLevel() < 0 || results.isEmpty())
476 return;
477
478 printBenchmarkResultsHeader(results.first());
479 printBenchmarkResults(results);
480}
481
482void QPlainTestLogger::addMessage(QtMsgType type, const QMessageLogContext &context, const QString &message)
483{
484 QAbstractTestLogger::addMessage(type, context, message);
485}
486
487void QPlainTestLogger::addMessage(MessageTypes type, const QString &message,
488 const char *file, int line)
489{
490 // suppress non-fatal messages in silent mode
491 if (type != QFatal && QTestLog::verboseLevel() < 0)
492 return;
493
494 printMessage(MessageSource::Other, QTest::ptMessageType2String(type), qPrintable(message), file, line);
495}
496
497bool QPlainTestLogger::isRepeatSupported() const
498{
499 // The plain text logger creates unstructured reports. Such reports are not
500 // parser friendly, and are unlikely to be parsed by any test reporting
501 // tools. We can therefore allow repeated test runs with minimum risk that
502 // any parsers fails to handle repeated test names.
503 return true;
504}
505
506QT_END_NAMESPACE
static const char * ptMessageType2String(QAbstractTestLogger::MessageTypes type)
static const char * ptIncidentType2String(QAbstractTestLogger::IncidentTypes type)
static int countSignificantDigits(T num)
static const char * benchmarkResult2String()
QByteArray formatResult(T number, int significantDigits)