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
108 {
109 switch (type) {
111 return "SKIP ";
113 return "PASS ";
115 return "XFAIL ";
117 return "FAIL! ";
119 return "XPASS ";
121 return "BPASS ";
123 return "BFAIL ";
125 return "BXPASS ";
127 return "BXFAIL ";
128 }
129 Q_UNREACHABLE_RETURN(nullptr);
130 }
131
132 static const char *benchmarkResult2String()
133 {
134 return "RESULT ";
135 }
136
138 {
139 switch (type) {
141 return "QDEBUG ";
143 return "QINFO ";
145 return "QWARN ";
147 return "QCRITICAL";
149 return "QFATAL ";
151 return "INFO ";
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 significant digits.
176 template <typename T> QByteArray formatResult(T number, int significantDigits)
177 {
178 if (qIsNaN(number) || number < T(0)) // (includes -ve infinity)
179 return "NaN";
180 if (qIsInf(number))
181 return "INF";
182 if (number == T(0))
183 return "0";
184
185 return QByteArray::number(number, 'e', qMax(1, significantDigits - 1));
186 }
187}
188
189/*! \internal
190 \class QPlainTestLogger
191 \inmodule QtTest
192
193 QPlainTestLogger implements basic logging of test results.
194
195 The format is Qt-specific and aims to be easy to read.
196*/
197
198void QPlainTestLogger::outputMessage(const char *str)
199{
200#if defined(Q_OS_WIN)
201 // Log to system log only if output is not redirected and stderr not preferred
202 if (stream == stdout && !QtPrivate::shouldLogToStderr()) {
203 OutputDebugStringA(str);
204 return;
205 }
206#elif defined(Q_OS_ANDROID)
207 __android_log_write(ANDROID_LOG_INFO, "QTestLib", str);
208#endif
209 outputString(str);
210}
211
212void QPlainTestLogger::printMessage(MessageSource source, const char *type, const char *msg,
213 const char *file, int line)
214{
215 QTEST_ASSERT(type);
216 QTEST_ASSERT(msg);
217
218 QTestCharBuffer messagePrefix;
219
220 QTestCharBuffer messageLocation;
221#ifdef Q_OS_WIN
222 constexpr const char *INCIDENT_LOCATION_STR = "\n%s(%d) : failure location";
223 constexpr const char *OTHER_LOCATION_STR = "\n%s(%d) : message location";
224#else
225 constexpr const char *INCIDENT_LOCATION_STR = "\n Loc: [%s(%d)]";
226 constexpr const char *OTHER_LOCATION_STR = INCIDENT_LOCATION_STR;
227#endif
228
229 if (file) {
230 switch (source) {
231 case MessageSource::Incident:
232 QTest::qt_asprintf(&messageLocation, INCIDENT_LOCATION_STR, file, line);
233 break;
234 case MessageSource::Other:
235 QTest::qt_asprintf(&messageLocation, OTHER_LOCATION_STR, file, line);
236 break;
237 }
238 }
239
240 const char *msgFiller = msg[0] ? " " : "";
241 QTestCharBuffer testIdentifier;
242 QTestPrivate::generateTestIdentifier(&testIdentifier);
243 QTest::qt_asprintf(&messagePrefix, "%s: %s%s%s%s\n",
244 type, testIdentifier.data(), msgFiller, msg, messageLocation.data());
245
246 // In colored mode, printf above stripped our nonprintable control characters.
247 // Put them back.
248 memcpy(messagePrefix.data(), type, strlen(type));
249
250 outputMessage(messagePrefix.data());
251}
252
253void QPlainTestLogger::printBenchmarkResultsHeader(const QBenchmarkResult &result)
254{
255 FixedBufString<1022> buf;
256 buf.appendf("%s: %s::%s", QTest::benchmarkResult2String(),
257 QTestResult::currentTestObjectName(), result.context.slotName.toLatin1().data());
258
259 QByteArray tag = QTestResult::currentDataTag();
260 QByteArray gtag = QTestResult::currentGlobalDataTag();
261
262 if (!gtag.isEmpty() && !tag.isEmpty())
263 buf.appendf(":\"%s:%s\":\n", gtag.constData(), tag.constData());
264 else if (!gtag.isEmpty())
265 buf.appendf(":\"%s\":\n", gtag.constData());
266 else if (!tag.isEmpty())
267 buf.appendf(":\"%s\":\n", tag.constData());
268 else
269 buf.append(":\n");
270 outputMessage(buf);
271}
272
273void QPlainTestLogger::printBenchmarkResults(const QList<QBenchmarkResult> &results)
274{
275 using namespace std::chrono;
276 FixedBufString<1022> buf;
277 auto findResultFor = [&results](QTest::QBenchmarkMetric metric) -> std::optional<qreal> {
278 for (const QBenchmarkResult &result : results) {
279 if (result.measurement.metric == metric)
280 return result.measurement.value;
281 }
282 return std::nullopt;
283 };
284
285 // we need the execution time quite often, so find it first
286 qreal executionTime = 0;
287 if (auto ns = findResultFor(QTest::WalltimeNanoseconds))
288 executionTime = *ns / (1000 * 1000 * 1000);
289 else if (auto ms = findResultFor(QTest::WalltimeMilliseconds))
290 executionTime = *ms / 1000;
291
292 for (const QBenchmarkResult &result : results) {
293 buf.clear();
294
295 const char * unitText = QTest::benchmarkMetricUnit(result.measurement.metric);
296 int significantDigits = QTest::countSignificantDigits(result.measurement.value);
297 qreal valuePerIteration = qreal(result.measurement.value) / qreal(result.iterations);
298 buf.appendf(" %s %s%s", QTest::formatResult(valuePerIteration, significantDigits).constData(),
299 unitText, result.setByMacro ? " per iteration" : "");
300
301 switch (result.measurement.metric) {
302 case QTest::BitsPerSecond:
303 // for bits/s, we'll use powers of 10 (1 Mbit/s = 1000 kbit/s = 1000000 bit/s)
304 buf.appendScaled<1000>(result.measurement.value, "bit/s");
305 break;
306 case QTest::BytesPerSecond:
307 // for B/s, we'll use powers of 2 (1 MB/s = 1024 kB/s = 1048576 B/s)
308 buf.appendScaled<1024>(result.measurement.value, "B/s");
309 break;
310
311 case QTest::CPUCycles:
312 case QTest::RefCPUCycles:
313 if (!qIsNull(executionTime))
314 buf.appendScaled(result.measurement.value / executionTime, "Hz");
315 break;
316
317 case QTest::Instructions:
318 if (auto cycles = findResultFor(QTest::CPUCycles)) {
319 buf.appendf(", %.3f instr/cycle", result.measurement.value / *cycles);
320 break;
321 }
322 Q_FALLTHROUGH();
323
324 case QTest::InstructionReads:
325 case QTest::Events:
326 case QTest::BytesAllocated:
327 case QTest::CPUMigrations:
328 case QTest::BusCycles:
329 case QTest::StalledCycles:
330 case QTest::BranchInstructions:
331 case QTest::BranchMisses:
332 case QTest::CacheReferences:
333 case QTest::CacheReads:
334 case QTest::CacheWrites:
335 case QTest::CachePrefetches:
336 case QTest::CacheMisses:
337 case QTest::CacheReadMisses:
338 case QTest::CacheWriteMisses:
339 case QTest::CachePrefetchMisses:
340 case QTest::ContextSwitches:
341 case QTest::PageFaults:
342 case QTest::MinorPageFaults:
343 case QTest::MajorPageFaults:
344 case QTest::AlignmentFaults:
345 case QTest::EmulationFaults:
346 if (!qIsNull(executionTime))
347 buf.appendScaled(result.measurement.value / executionTime, "/sec");
348 break;
349
350 case QTest::FramesPerSecond:
351 case QTest::CPUTicks:
352 case QTest::WalltimeMilliseconds:
353 case QTest::WalltimeNanoseconds:
354 break; // no additional information
355 }
356
357 Q_ASSERT(result.iterations > 0);
358 buf.appendf(" (total: %s, iterations: %d)\n",
359 QTest::formatResult(result.measurement.value, significantDigits).constData(),
360 result.iterations);
361
362 outputMessage(buf);
363 }
364}
365
366QPlainTestLogger::QPlainTestLogger(const char *filename)
367 : QAbstractTestLogger(filename)
368{
369}
370
371QPlainTestLogger::~QPlainTestLogger() = default;
372
373void QPlainTestLogger::startLogging()
374{
375 QAbstractTestLogger::startLogging();
376
377 char buf[1024];
378 if (QTestLog::verboseLevel() < 0) {
379 std::snprintf(buf, sizeof(buf), "Testing %s\n", QTestResult::currentTestObjectName());
380 } else {
381 std::snprintf(buf, sizeof(buf),
382 "********* Start testing of %s *********\n"
383 "Config: Using QtTest library " QTEST_VERSION_STR
384 ", %s, %s %s\n", QTestResult::currentTestObjectName(), QLibraryInfo::build(),
385 qPrintable(QSysInfo::productType()), qPrintable(QSysInfo::productVersion()));
386 }
387 outputMessage(buf);
388}
389
390void QPlainTestLogger::stopLogging()
391{
392 char buf[1024];
393 const int timeMs = qRound(QTestLog::msecsTotalTime());
394 if (QTestLog::verboseLevel() < 0) {
395 std::snprintf(buf, sizeof(buf),
396 "Totals: %d passed, %d failed, %d skipped, %d blacklisted, %dms\n",
397 QTestLog::passCount(), QTestLog::failCount(),
398 QTestLog::skipCount(), QTestLog::blacklistCount(), timeMs);
399 } else {
400 std::snprintf(buf, sizeof(buf),
401 "Totals: %d passed, %d failed, %d skipped, %d blacklisted, %dms\n"
402 "********* Finished testing of %s *********\n",
403 QTestLog::passCount(), QTestLog::failCount(),
404 QTestLog::skipCount(), QTestLog::blacklistCount(), timeMs,
405 QTestResult::currentTestObjectName());
406 }
407 outputMessage(buf);
408
409 QAbstractTestLogger::stopLogging();
410}
411
412
413void QPlainTestLogger::enterTestFunction(const char * /*function*/)
414{
415 if (QTestLog::verboseLevel() >= 1)
416 printMessage(MessageSource::Other, QTest::ptMessageType2String(Info), "entering");
417}
418
419void QPlainTestLogger::leaveTestFunction()
420{
421}
422
423void QPlainTestLogger::addIncident(IncidentTypes type, const char *description,
424 const char *file, int line)
425{
426 // suppress B?PASS and B?XFAIL in silent mode
427 if ((type == Pass || type == BlacklistedPass || type == XFail || type == BlacklistedXFail)
428 && QTestLog::verboseLevel() < 0)
429 return;
430
431 printMessage(MessageSource::Incident, QTest::ptIncidentType2String(type), description, file, line);
432}
433
434void QPlainTestLogger::addBenchmarkResults(const QList<QBenchmarkResult> &results)
435{
436 // suppress benchmark results in silent mode
437 if (QTestLog::verboseLevel() < 0 || results.isEmpty())
438 return;
439
440 printBenchmarkResultsHeader(results.first());
441 printBenchmarkResults(results);
442}
443
444void QPlainTestLogger::addMessage(QtMsgType type, const QMessageLogContext &context, const QString &message)
445{
446 QAbstractTestLogger::addMessage(type, context, message);
447}
448
449void QPlainTestLogger::addMessage(MessageTypes type, const QString &message,
450 const char *file, int line)
451{
452 // suppress non-fatal messages in silent mode
453 if (type != QFatal && QTestLog::verboseLevel() < 0)
454 return;
455
456 printMessage(MessageSource::Other, QTest::ptMessageType2String(type), qPrintable(message), file, line);
457}
458
459bool QPlainTestLogger::isRepeatSupported() const
460{
461 // The plain text logger creates unstructured reports. Such reports are not
462 // parser friendly, and are unlikely to be parsed by any test reporting
463 // tools. We can therefore allow repeated test runs with minimum risk that
464 // any parsers fails to handle repeated test names.
465 return true;
466}
467
468QT_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)