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#include <optional>
16
17#include <stdio.h>
18#include <stdlib.h>
19#include <string.h>
20
21#include <QtCore/QByteArray>
22#include <QtCore/qmath.h>
23#include <QtCore/QLibraryInfo>
24
25#ifdef Q_OS_ANDROID
26# include <android/log.h>
27#endif
28
29#ifdef Q_OS_WIN
30# include <qt_windows.h>
31#endif
32
33#ifdef Q_OS_HARMONY
34# include <QtCore/private/qohoslogger_p.h>
35#endif
36
37QT_BEGIN_NAMESPACE
38
39using namespace Qt::StringLiterals;
40
41namespace {
42static const char multiplePrefixes[] = "\0kMGTPE"; // kilo, mega, giga, tera, peta, exa
43static const char submultiplePrefixes[] = "afpnum"; // atto, femto, pico, nano, micro, milli
44
45template <int N> struct FixedBufString
46{
47 static constexpr size_t MaxSize = N;
48 size_t used = 0;
49 std::array<char, N + 2> buf; // for the newline and terminating null
50 FixedBufString()
51 {
52 clear();
53 }
54 void clear()
55 {
56 used = 0;
57 buf[0] = '\0';
58 }
59
60 operator const char *() const
61 {
62 return buf.data();
63 }
64
65 void append(const char *text)
66 {
67 size_t len = qMin(strlen(text), MaxSize - used);
68 memcpy(buf.data() + used, text, len);
69 used += len;
70 buf[used] = '\0';
71 }
72
73 template <typename... Args> void appendf(const char *format, Args... args)
74 {
75 // vsnprintf includes the terminating null
76 used += std::snprintf(buf.data() + used, MaxSize - used + 1, format,
77 args...);
78 }
79
80 template <int Power = 1000> void appendScaled(qreal value, const char *unit)
81 {
82 char prefix[2] = {};
83 qreal v = qAbs(value);
84 qint64 ratio;
85 if (v < 1 && Power == 1000) {
86 const char *prefixes = submultiplePrefixes;
87 ratio = qreal(std::atto::num) / qreal(std::atto::den);
88 while (value * ratio > 1000 && *prefixes) {
89 ++prefixes;
90 ratio *= 1000;
91 }
92 prefix[0] = *prefixes;
93 } else {
94 const char *prefixes = multiplePrefixes;
95 ratio = 1;
96 while (value > 1000 * ratio) { // yes, even for binary
97 ++prefixes;
98 ratio *= Power;
99 }
100 prefix[0] = *prefixes;
101 }
102
103 // adjust the value by the ratio
104 value /= ratio;
105 appendf(", %.3g %s%s", value, prefix, unit);
106 }
107};
108} // unnamed namespace
109
110namespace QTest {
111
113 {
114 switch (type) {
116 return "SKIP ";
118 return "PASS ";
120 return "XFAIL ";
122 return "FAIL! ";
124 return "XPASS ";
126 return "BPASS ";
128 return "BFAIL ";
130 return "BXPASS ";
132 return "BXFAIL ";
133 }
134 Q_UNREACHABLE_RETURN(nullptr);
135 }
136
137 static const char *benchmarkResult2String()
138 {
139 return "RESULT ";
140 }
141
143 {
144 switch (type) {
146 return "QDEBUG ";
148 return "QINFO ";
150 return "QWARN ";
152 return "QCRITICAL";
154 return "QFATAL ";
156 return "INFO ";
158 return "WARNING";
159 }
160 Q_UNREACHABLE_RETURN(nullptr);
161 }
162
163 template <typename T>
164 static int countSignificantDigits(T num)
165 {
166 if (num <= 0)
167 return 0;
168
169 int digits = 0;
170 qreal divisor = 1;
171
172 while (num / divisor >= 1) {
173 divisor *= 10;
174 ++digits;
175 }
176
177 return digits;
178 }
179
180 template <typename T> QByteArray specialValue(T number)
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 return {};
189 }
190
191 // To use the same scale as mean, standard deviation passes a value for scale.
192 // Use a multiple of 3 as exponent, with 0.1 <= mantissa < 1000.
193 auto formatResult(qreal number, int significantDigits, std::optional<int> scale = {})
194 {
195 struct R {
196 QByteArray text;
197 std::optional<int> scale;
198 } res = {{}, scale};
199
200 if (QByteArray special = specialValue(number); !special.isEmpty()) {
201 res.text = special;
202 return res;
203 }
204
205 int decimals = significantDigits;
206 qreal mantissa = number;
207 int thous = 0;
208 if (scale) {
209 // Imposed scale (to use same for standard deviation and mean): this
210 // may give us a mantissa outside the 0.1-to-100 range. Allow that
211 // to give more, but not less, significant digits than asked for.
212 Q_ASSERT(*scale % 3 == 0);
213 thous = *scale / 3;
214 int i = thous;
215 for (; i < 0; ++i)
216 mantissa *= 1e3;
217 for (; i > 0; --i)
218 mantissa /= 1e3;
219 // When mean's mantissa was < 1, if standard deviation is small
220 // (less than .1 at the same scale), allow for its leading zeros:
221 for (qreal extra = 1.0; mantissa * extra < 0.1; extra *= 10)
222 ++decimals;
223 } else {
224 // Find the nearest-zeroth power of 1000 for which 0.1 <= number /
225 // 1e{3*thous} <= 1000. (This leaves no exponent from 0.1 to
226 // 999.49...9, with positive exponents getting 1 <= mantissa < 1000
227 // and negative exponents getting 0.1 <= mantissa < 100; a slight
228 // asymmetry, to make exponent-free more common.) Iterative, because
229 // we expect values at scales between nano and mega, so few
230 // iterations are needed. (Theoretical worst case for double would
231 // be about 100 iterations; for long double about 1650 - but
232 // measurements with precision outside float's dozen-iteration range
233 // are unrealistic.)
234 while (mantissa >= 1000) {
235 mantissa /= 1e3;
236 ++thous;
237 }
238 while (mantissa < 0.1) {
239 mantissa *= 1e3;
240 --thous;
241 }
242 }
243 res.scale = 3 * thous;
244
245 // Format mantissa:
246 // 'f' format's precision is the number of digits in the fractional
247 // part; we need to correct for earlier significant digits:
248 if (mantissa >= 100)
249 decimals -= 3;
250 else if (mantissa >= 10)
251 decimals -= 2;
252 else if (mantissa >= 1)
253 --decimals;
254 // When scale is imposed, that might take decimals < 0 (e.g. if std.dev
255 // is bigger than mean), which is ignored, so clip to 0:
256 res.text = QByteArray::number(mantissa, 'f', qMax(decimals, 0));
257
258 if (thous) {
259 // Format exponent and append it:
260 QByteArray tail = QByteArray::number(*res.scale);
261 if (tail.startsWith('-')) {
262 if (tail.size() < 3) // zero-pad after the sign:
263 tail.insert(1, '0');
264 } else {
265 if (tail.size() < 2) // zero-pad at start:
266 tail.prepend('0');
267 tail.prepend('+'); // always include a sign
268 }
269 tail.prepend('e');
270 res.text += tail;
271 }
272
273 return res;
274 }
275}
276
277/*! \internal
278 \class QPlainTestLogger
279 \inmodule QtTest
280
281 QPlainTestLogger implements basic logging of test results.
282
283 The format is Qt-specific and aims to be easy to read.
284*/
285
286void QPlainTestLogger::outputMessage(const char *str)
287{
288#if defined(Q_OS_WIN)
289 // Log to system log only if output is not redirected and stderr not preferred
290 if (stream == stdout && !QtPrivate::shouldLogToStderr()) {
291 OutputDebugStringA(str);
292 return;
293 }
294#elif defined(Q_OS_ANDROID)
295 __android_log_write(ANDROID_LOG_INFO, "QTestLib", str);
296#elif defined(Q_OS_HARMONY)
297 qOhosLogMessage(LOG_INFO, "QTestLib", str);
298#endif
299 outputString(str);
300}
301
302void QPlainTestLogger::printMessage(MessageSource source, const char *type, const char *msg,
303 const char *file, int line)
304{
305 QTEST_ASSERT(type);
306 QTEST_ASSERT(msg);
307
308 QTestCharBuffer messagePrefix;
309
310 QTestCharBuffer messageLocation;
311#ifdef Q_OS_WIN
312 constexpr const char *INCIDENT_LOCATION_STR = "\n%s(%d) : failure location";
313 constexpr const char *OTHER_LOCATION_STR = "\n%s(%d) : message location";
314#else
315 constexpr const char *INCIDENT_LOCATION_STR = "\n Loc: [%s(%d)]";
316 constexpr const char *OTHER_LOCATION_STR = INCIDENT_LOCATION_STR;
317#endif
318
319 if (file) {
320 switch (source) {
321 case MessageSource::Incident:
322 QTest::qt_asprintf(&messageLocation, INCIDENT_LOCATION_STR, file, line);
323 break;
324 case MessageSource::Other:
325 QTest::qt_asprintf(&messageLocation, OTHER_LOCATION_STR, file, line);
326 break;
327 }
328 }
329
330 const char *msgFiller = msg[0] ? " " : "";
331 QTestCharBuffer testIdentifier;
332 QTestPrivate::generateTestIdentifier(&testIdentifier);
333 QTest::qt_asprintf(&messagePrefix, "%s: %s%s%s%s\n",
334 type, testIdentifier.data(), msgFiller, msg, messageLocation.data());
335
336 // In colored mode, printf above stripped our nonprintable control characters.
337 // Put them back.
338 memcpy(messagePrefix.data(), type, strlen(type));
339
340 outputMessage(messagePrefix.data());
341}
342
343void QPlainTestLogger::printBenchmarkResultsHeader(const QBenchmarkResult &result)
344{
345 FixedBufString<1022> buf;
346 buf.appendf("%s: %s::%s", QTest::benchmarkResult2String(),
347 QTestResult::currentTestObjectName(), result.context.slotName.toLatin1().data());
348
349 QByteArray tag = QTestResult::currentDataTag();
350 QByteArray gtag = QTestResult::currentGlobalDataTag();
351
352 if (!gtag.isEmpty() && !tag.isEmpty())
353 buf.appendf(":\"%s:%s\":\n", gtag.constData(), tag.constData());
354 else if (!gtag.isEmpty())
355 buf.appendf(":\"%s\":\n", gtag.constData());
356 else if (!tag.isEmpty())
357 buf.appendf(":\"%s\":\n", tag.constData());
358 else
359 buf.append(":\n");
360 outputMessage(buf);
361}
362
363void QPlainTestLogger::printBenchmarkResults(const QList<QBenchmarkResult> &results)
364{
365 using namespace std::chrono;
366 FixedBufString<1022> buf;
367 auto findResultFor = [&results](QTest::QBenchmarkMetric metric) -> std::optional<qreal> {
368 for (const QBenchmarkResult &result : results) {
369 if (result.measurement.metric == metric)
370 return result.measurement.value;
371 }
372 return std::nullopt;
373 };
374
375 // we need the execution time quite often, so find it first
376 qreal executionTime = 0;
377 if (auto ns = findResultFor(QTest::WalltimeNanoseconds))
378 executionTime = *ns / (1000 * 1000 * 1000);
379 else if (auto ms = findResultFor(QTest::WalltimeMilliseconds))
380 executionTime = *ms / 1000;
381
382 for (const QBenchmarkResult &result : results) {
383 buf.clear();
384
385 const char * unitText = QTest::benchmarkMetricUnit(result.measurement.metric);
386 int significantDigits = QTest::countSignificantDigits(result.measurement.value);
387 qreal valuePerIteration = qreal(result.measurement.value) / qreal(result.iterations);
388 auto midForm = QTest::formatResult(valuePerIteration, significantDigits);
389 buf.appendf(" %s %s%s", midForm.text.constData(), unitText,
390 result.setByMacro ? " per iteration" : "");
391
392 switch (result.measurement.metric) {
393 case QTest::BitsPerSecond:
394 // for bits/s, we'll use powers of 10 (1 Mbit/s = 1000 kbit/s = 1000000 bit/s)
395 buf.appendScaled<1000>(result.measurement.value, "bit/s");
396 break;
397 case QTest::BytesPerSecond:
398 // for B/s, we'll use powers of 2 (1 MB/s = 1024 kB/s = 1048576 B/s)
399 buf.appendScaled<1024>(result.measurement.value, "B/s");
400 break;
401
402 case QTest::CPUCycles:
403 case QTest::RefCPUCycles:
404 if (!qIsNull(executionTime))
405 buf.appendScaled(result.measurement.value / executionTime, "Hz");
406 break;
407
408 case QTest::Instructions:
409 if (auto cycles = findResultFor(QTest::CPUCycles)) {
410 buf.appendf(", %.3f instr/cycle", result.measurement.value / *cycles);
411 break;
412 }
413 Q_FALLTHROUGH();
414
415 case QTest::InstructionReads:
416 case QTest::Events:
417 case QTest::BytesAllocated:
418 case QTest::CPUMigrations:
419 case QTest::BusCycles:
420 case QTest::StalledCycles:
421 case QTest::BranchInstructions:
422 case QTest::BranchMisses:
423 case QTest::CacheReferences:
424 case QTest::CacheReads:
425 case QTest::CacheWrites:
426 case QTest::CachePrefetches:
427 case QTest::CacheMisses:
428 case QTest::CacheReadMisses:
429 case QTest::CacheWriteMisses:
430 case QTest::CachePrefetchMisses:
431 case QTest::ContextSwitches:
432 case QTest::PageFaults:
433 case QTest::MinorPageFaults:
434 case QTest::MajorPageFaults:
435 case QTest::AlignmentFaults:
436 case QTest::EmulationFaults:
437 if (!qIsNull(executionTime))
438 buf.appendScaled(result.measurement.value / executionTime, "/sec");
439 break;
440
441 case QTest::FramesPerSecond:
442 case QTest::CPUTicks:
443 case QTest::WalltimeMilliseconds:
444 case QTest::WalltimeNanoseconds:
445 break; // no additional information
446 }
447
448 Q_ASSERT(result.iterations > 0);
449 auto totForm = QTest::formatResult(result.measurement.value, significantDigits);
450 if (result.measurement.variance) {
451 auto stdForm = QTest::formatResult(sqrt(result.measurement.variance),
452 significantDigits, midForm.scale);
453 buf.appendf(" (total: %s %s, std.dev: %s %s, iterations: %d)\n",
454 totForm.text.constData(), unitText,
455 stdForm.text.constData(), unitText,
456 result.iterations);
457 } else {
458 buf.appendf(" (total: %s %s, iterations: %d)\n",
459 totForm.text.constData(), unitText,
460 result.iterations);
461 }
462 outputMessage(buf);
463 }
464}
465
466QPlainTestLogger::QPlainTestLogger(const char *filename)
467 : QAbstractTestLogger(filename)
468{
469}
470
471QPlainTestLogger::~QPlainTestLogger() = default;
472
473void QPlainTestLogger::startLogging()
474{
475 QAbstractTestLogger::startLogging();
476
477 char buf[1024];
478 if (QTestLog::verboseLevel() < 0) {
479 std::snprintf(buf, sizeof(buf), "Testing %s\n", QTestResult::currentTestObjectName());
480 } else {
481 std::snprintf(buf, sizeof(buf),
482 "********* Start testing of %s *********\n"
483 "Config: Using QtTest library " QTEST_VERSION_STR
484 ", %s, %s %s\n", QTestResult::currentTestObjectName(), QLibraryInfo::build(),
485 qPrintable(QSysInfo::productType()), qPrintable(QSysInfo::productVersion()));
486 }
487 outputMessage(buf);
488}
489
490void QPlainTestLogger::stopLogging()
491{
492 char buf[1024];
493 const int timeMs = qRound(QTestLog::msecsTotalTime());
494 if (QTestLog::verboseLevel() < 0) {
495 std::snprintf(buf, sizeof(buf),
496 "Totals: %d passed, %d failed, %d skipped, %d blacklisted, %dms\n",
497 QTestLog::passCount(), QTestLog::failCount(),
498 QTestLog::skipCount(), QTestLog::blacklistCount(), timeMs);
499 } else {
500 std::snprintf(buf, sizeof(buf),
501 "Totals: %d passed, %d failed, %d skipped, %d blacklisted, %dms\n"
502 "********* Finished testing of %s *********\n",
503 QTestLog::passCount(), QTestLog::failCount(),
504 QTestLog::skipCount(), QTestLog::blacklistCount(), timeMs,
505 QTestResult::currentTestObjectName());
506 }
507 outputMessage(buf);
508
509 QAbstractTestLogger::stopLogging();
510}
511
512
513void QPlainTestLogger::enterTestFunction(const char * /*function*/)
514{
515 if (QTestLog::verboseLevel() >= 1)
516 printMessage(MessageSource::Other, QTest::ptMessageType2String(Info), "entering");
517}
518
519void QPlainTestLogger::leaveTestFunction()
520{
521}
522
523void QPlainTestLogger::addIncident(IncidentTypes type, const char *description,
524 const char *file, int line)
525{
526 // suppress B?PASS and B?XFAIL in silent mode
527 if ((type == Pass || type == BlacklistedPass || type == XFail || type == BlacklistedXFail)
528 && QTestLog::verboseLevel() < 0)
529 return;
530
531 printMessage(MessageSource::Incident, QTest::ptIncidentType2String(type), description, file, line);
532}
533
534void QPlainTestLogger::addBenchmarkResults(const QList<QBenchmarkResult> &results)
535{
536 // suppress benchmark results in silent mode
537 if (QTestLog::verboseLevel() < 0 || results.isEmpty())
538 return;
539
540 printBenchmarkResultsHeader(results.first());
541 printBenchmarkResults(results);
542}
543
544void QPlainTestLogger::addMessage(QtMsgType type, const QMessageLogContext &context, const QString &message)
545{
546 QAbstractTestLogger::addMessage(type, context, message);
547}
548
549void QPlainTestLogger::addMessage(MessageTypes type, const QString &message,
550 const char *file, int line)
551{
552 // suppress non-fatal messages in silent mode
553 if (type != QFatal && QTestLog::verboseLevel() < 0)
554 return;
555
556 printMessage(MessageSource::Other, QTest::ptMessageType2String(type), qPrintable(message), file, line);
557}
558
559bool QPlainTestLogger::isRepeatSupported() const
560{
561 // The plain text logger creates unstructured reports. Such reports are not
562 // parser friendly, and are unlikely to be parsed by any test reporting
563 // tools. We can therefore allow repeated test runs with minimum risk that
564 // any parsers fails to handle repeated test names.
565 return true;
566}
567
568QT_END_NAMESPACE
static const char * ptMessageType2String(QAbstractTestLogger::MessageTypes type)
static const char * ptIncidentType2String(QAbstractTestLogger::IncidentTypes type)
static int countSignificantDigits(T num)
QByteArray specialValue(T number)
static const char * benchmarkResult2String()
auto formatResult(qreal number, int significantDigits, std::optional< int > scale={})