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