Qt
Internal/Contributor docs for the Qt SDK. <b>Note:</b> These are NOT official API docs; those are found <a href='https://doc.qt.io/'>here</a>.
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
15#include <stdio.h>
16#include <stdlib.h>
17#include <string.h>
18
19#include <QtCore/QByteArray>
20#include <QtCore/qmath.h>
21#include <QtCore/QLibraryInfo>
22
23#ifdef Q_OS_ANDROID
24# include <android/log.h>
25#endif
26
27#ifdef Q_OS_WIN
28# include <qt_windows.h>
29#endif
30
32
33using namespace Qt::StringLiterals;
34
35namespace {
36static const char multiplePrefixes[] = "\0kMGTPE"; // kilo, mega, giga, tera, peta, exa
37static const char submultiplePrefixes[] = "afpnum"; // atto, femto, pico, nano, micro, milli
38
39template <int N> struct FixedBufString
40{
41 static constexpr size_t MaxSize = N;
42 size_t used = 0;
43 std::array<char, N + 2> buf; // for the newline and terminating null
44 FixedBufString()
45 {
46 clear();
47 }
48 void clear()
49 {
50 used = 0;
51 buf[0] = '\0';
52 }
53
54 operator const char *() const
55 {
56 return buf.data();
57 }
58
59 void append(const char *text)
60 {
61 size_t len = qMin(strlen(text), MaxSize - used);
62 memcpy(buf.data() + used, text, len);
63 used += len;
64 buf[used] = '\0';
65 }
66
67 template <typename... Args> void appendf(const char *format, Args &&... args)
68 {
69 // vsnprintf includes the terminating null
70 used += qsnprintf(buf.data() + used, MaxSize - used + 1, format,
71 std::forward<Args>(args)...);
72 }
73
74 template <int Power = 1000> void appendScaled(qreal value, const char *unit)
75 {
76 char prefix[2] = {};
77 qreal v = qAbs(value);
78 qint64 ratio;
79 if (v < 1 && Power == 1000) {
80 const char *prefixes = submultiplePrefixes;
81 ratio = qreal(std::atto::num) / std::atto::den;
82 while (value * ratio > 1000 && *prefixes) {
83 ++prefixes;
84 ratio *= 1000;
85 }
86 prefix[0] = *prefixes;
87 } else {
88 const char *prefixes = multiplePrefixes;
89 ratio = 1;
90 while (value > 1000 * ratio) { // yes, even for binary
91 ++prefixes;
92 ratio *= Power;
93 }
94 prefix[0] = *prefixes;
95 }
96
97 // adjust the value by the ratio
98 value /= ratio;
99 appendf(", %.3g %s%s", value, prefix, unit);
100 }
101};
102} // unnamed namespace
103
104namespace QTest {
105
107 {
108 switch (type) {
110 return "SKIP ";
112 return "PASS ";
114 return "XFAIL ";
116 return "FAIL! ";
118 return "XPASS ";
120 return "BPASS ";
122 return "BFAIL ";
124 return "BXPASS ";
126 return "BXFAIL ";
127 }
128 Q_UNREACHABLE_RETURN(nullptr);
129 }
130
131 static const char *benchmarkResult2String()
132 {
133 return "RESULT ";
134 }
135
137 {
138 switch (type) {
140 return "QDEBUG ";
142 return "QINFO ";
144 return "QWARN ";
146 return "QCRITICAL";
148 return "QFATAL ";
150 return "INFO ";
152 return "WARNING";
153 }
154 Q_UNREACHABLE_RETURN(nullptr);
155 }
156
157 template <typename T>
159 {
160 if (num <= 0)
161 return 0;
162
163 int digits = 0;
164 qreal divisor = 1;
165
166 while (num / divisor >= 1) {
167 divisor *= 10;
168 ++digits;
169 }
170
171 return digits;
172 }
173
174 // Pretty-prints a benchmark result using the given number of digits.
175 template <typename T> QByteArray formatResult(T number, int significantDigits)
176 {
177 if (number < T(0))
178 return "NAN";
179 if (number == T(0))
180 return "0";
181
182 QByteArray beforeDecimalPoint = QByteArray::number(qint64(number), 'f', 0);
183 QByteArray afterDecimalPoint = QByteArray::number(number, 'f', 20);
184 afterDecimalPoint.remove(0, beforeDecimalPoint.size() + 1);
185
186 int beforeUse = qMin(beforeDecimalPoint.size(), significantDigits);
187 int beforeRemove = beforeDecimalPoint.size() - beforeUse;
188
189 // Replace insignificant digits before the decimal point with zeros.
190 beforeDecimalPoint.chop(beforeRemove);
191 for (int i = 0; i < beforeRemove; ++i) {
192 beforeDecimalPoint.append(u'0');
193 }
194
195 int afterUse = significantDigits - beforeUse;
196
197 // leading zeroes after the decimal point does not count towards the digit use.
198 if (beforeDecimalPoint == "0" && !afterDecimalPoint.isEmpty()) {
199 ++afterUse;
200
201 int i = 0;
202 while (i < afterDecimalPoint.size() && afterDecimalPoint.at(i) == '0')
203 ++i;
204
205 afterUse += i;
206 }
207
208 int afterRemove = afterDecimalPoint.size() - afterUse;
209 afterDecimalPoint.chop(afterRemove);
210
211 char separator = ',';
212 char decimalPoint = '.';
213
214 // insert thousands separators
215 int length = beforeDecimalPoint.size();
216 for (int i = beforeDecimalPoint.size() -1; i >= 1; --i) {
217 if ((length - i) % 3 == 0)
218 beforeDecimalPoint.insert(i, separator);
219 }
220
221 QByteArray print;
222 print = beforeDecimalPoint;
223 if (afterUse > 0)
224 print.append(decimalPoint);
225
226 print += afterDecimalPoint;
227
228
229 return print;
230 }
231}
232
242void QPlainTestLogger::outputMessage(const char *str)
243{
244#if defined(Q_OS_WIN)
245 // Log to system log only if output is not redirected and stderr not preferred
246 if (stream == stdout && !QtPrivate::shouldLogToStderr()) {
247 OutputDebugStringA(str);
248 return;
249 }
250#elif defined(Q_OS_ANDROID)
251 __android_log_write(ANDROID_LOG_INFO, "QTestLib", str);
252#endif
254}
255
256void QPlainTestLogger::printMessage(MessageSource source, const char *type, const char *msg,
257 const char *file, int line)
258{
260 QTEST_ASSERT(msg);
261
262 QTestCharBuffer messagePrefix;
263
264 QTestCharBuffer messageLocation;
265#ifdef Q_OS_WIN
266 constexpr const char *INCIDENT_LOCATION_STR = "\n%s(%d) : failure location";
267 constexpr const char *OTHER_LOCATION_STR = "\n%s(%d) : message location";
268#else
269 constexpr const char *INCIDENT_LOCATION_STR = "\n Loc: [%s(%d)]";
270 constexpr const char *OTHER_LOCATION_STR = INCIDENT_LOCATION_STR;
271#endif
272
273 if (file) {
274 switch (source) {
275 case MessageSource::Incident:
276 QTest::qt_asprintf(&messageLocation, INCIDENT_LOCATION_STR, file, line);
277 break;
278 case MessageSource::Other:
279 QTest::qt_asprintf(&messageLocation, OTHER_LOCATION_STR, file, line);
280 break;
281 }
282 }
283
284 const char *msgFiller = msg[0] ? " " : "";
285 QTestCharBuffer testIdentifier;
287 QTest::qt_asprintf(&messagePrefix, "%s: %s%s%s%s\n",
288 type, testIdentifier.data(), msgFiller, msg, messageLocation.data());
289
290 // In colored mode, printf above stripped our nonprintable control characters.
291 // Put them back.
292 memcpy(messagePrefix.data(), type, strlen(type));
293
294 outputMessage(messagePrefix.data());
295}
296
297void QPlainTestLogger::printBenchmarkResultsHeader(const QBenchmarkResult &result)
298{
299 FixedBufString<1022> buf;
300 buf.appendf("%s: %s::%s", QTest::benchmarkResult2String(),
301 QTestResult::currentTestObjectName(), result.context.slotName.toLatin1().data());
302
303 if (QByteArray tag = result.context.tag.toLocal8Bit(); !tag.isEmpty())
304 buf.appendf(":\"%s\":\n", tag.data());
305 else
306 buf.append(":\n");
307 outputMessage(buf);
308}
309
310void QPlainTestLogger::printBenchmarkResults(const QList<QBenchmarkResult> &results)
311{
312 using namespace std::chrono;
313 FixedBufString<1022> buf;
314 auto findResultFor = [&results](QTest::QBenchmarkMetric metric) -> std::optional<qreal> {
315 for (const QBenchmarkResult &result : results) {
316 if (result.measurement.metric == metric)
317 return result.measurement.value;
318 }
319 return std::nullopt;
320 };
321
322 // we need the execution time quite often, so find it first
323 qreal executionTime = 0;
324 if (auto ns = findResultFor(QTest::WalltimeNanoseconds))
325 executionTime = *ns / (1000 * 1000 * 1000);
326 else if (auto ms = findResultFor(QTest::WalltimeMilliseconds))
327 executionTime = *ms / 1000;
328
329 for (const QBenchmarkResult &result : results) {
330 buf.clear();
331
332 const char * unitText = QTest::benchmarkMetricUnit(result.measurement.metric);
333 int significantDigits = QTest::countSignificantDigits(result.measurement.value);
334 qreal valuePerIteration = qreal(result.measurement.value) / qreal(result.iterations);
335 buf.appendf(" %s %s%s", QTest::formatResult(valuePerIteration, significantDigits).constData(),
336 unitText, result.setByMacro ? " per iteration" : "");
337
338 switch (result.measurement.metric) {
340 // for bits/s, we'll use powers of 10 (1 Mbit/s = 1000 kbit/s = 1000000 bit/s)
341 buf.appendScaled<1000>(result.measurement.value, "bit/s");
342 break;
344 // for B/s, we'll use powers of 2 (1 MB/s = 1024 kB/s = 1048576 B/s)
345 buf.appendScaled<1024>(result.measurement.value, "B/s");
346 break;
347
348 case QTest::CPUCycles:
350 if (!qIsNull(executionTime))
351 buf.appendScaled(result.measurement.value / executionTime, "Hz");
352 break;
353
355 if (auto cycles = findResultFor(QTest::CPUCycles)) {
356 buf.appendf(", %.3f instr/cycle", result.measurement.value / *cycles);
357 break;
358 }
360
362 case QTest::Events:
365 case QTest::BusCycles:
383 if (!qIsNull(executionTime))
384 buf.appendScaled(result.measurement.value / executionTime, "/sec");
385 break;
386
388 case QTest::CPUTicks:
391 break; // no additional information
392 }
393
394 Q_ASSERT(result.iterations > 0);
395 buf.appendf(" (total: %s, iterations: %d)\n",
396 QTest::formatResult(result.measurement.value, significantDigits).constData(),
397 result.iterations);
398
399 outputMessage(buf);
400 }
401}
402
404 : QAbstractTestLogger(filename)
405{
406}
407
409
411{
413
414 char buf[1024];
415 if (QTestLog::verboseLevel() < 0) {
416 qsnprintf(buf, sizeof(buf), "Testing %s\n", QTestResult::currentTestObjectName());
417 } else {
418 qsnprintf(buf, sizeof(buf),
419 "********* Start testing of %s *********\n"
420 "Config: Using QtTest library " QTEST_VERSION_STR
423 }
424 outputMessage(buf);
425}
426
428{
429 char buf[1024];
430 const int timeMs = qRound(QTestLog::msecsTotalTime());
431 if (QTestLog::verboseLevel() < 0) {
432 qsnprintf(buf, sizeof(buf), "Totals: %d passed, %d failed, %d skipped, %d blacklisted, %dms\n",
435 } else {
436 qsnprintf(buf, sizeof(buf),
437 "Totals: %d passed, %d failed, %d skipped, %d blacklisted, %dms\n"
438 "********* Finished testing of %s *********\n",
442 }
443 outputMessage(buf);
444
446}
447
448
449void QPlainTestLogger::enterTestFunction(const char * /*function*/)
450{
451 if (QTestLog::verboseLevel() >= 1)
452 printMessage(MessageSource::Other, QTest::ptMessageType2String(Info), "entering");
453}
454
458
460 const char *file, int line)
461{
462 // suppress B?PASS and B?XFAIL in silent mode
463 if ((type == Pass || type == BlacklistedPass || type == XFail || type == BlacklistedXFail)
464 && QTestLog::verboseLevel() < 0)
465 return;
466
467 printMessage(MessageSource::Incident, QTest::ptIncidentType2String(type), description, file, line);
468}
469
470void QPlainTestLogger::addBenchmarkResults(const QList<QBenchmarkResult> &results)
471{
472 // suppress benchmark results in silent mode
474 return;
475
476 printBenchmarkResultsHeader(results.first());
477 printBenchmarkResults(results);
478}
479
484
486 const char *file, int line)
487{
488 // suppress non-fatal messages in silent mode
489 if (type != QFatal && QTestLog::verboseLevel() < 0)
490 return;
491
492 printMessage(MessageSource::Other, QTest::ptMessageType2String(type), qPrintable(message), file, line);
493}
494
496{
497 // The plain text logger creates unstructured reports. Such reports are not
498 // parser friendly, and are unlikely to be parsed by any test reporting
499 // tools. We can therefore allow repeated test runs with minimum risk that
500 // any parsers fails to handle repeated test names.
501 return true;
502}
503
Base class for test loggers.
void outputString(const char *msg)
Convenience method to write msg to the output stream.
IncidentTypes
\value Pass The test ran to completion successfully.
virtual void startLogging()
Called before the start of a test run.
virtual void addMessage(QtMsgType, const QMessageLogContext &, const QString &)
This is an overloaded member function, provided for convenience. It differs from the above function o...
MessageTypes
The members whose names begin with Q describe messages that originate in calls, by the test or code u...
virtual void stopLogging()
Called after the end of a test run.
\inmodule QtCore
Definition qbytearray.h:57
const char * constData() const noexcept
Returns a pointer to the const data stored in the byte array.
Definition qbytearray.h:124
static QByteArray number(int, int base=10)
Returns a byte-array representing the whole number n as text.
QByteArray & append(char c)
This is an overloaded member function, provided for convenience. It differs from the above function o...
static const char * build() noexcept
Returns a string describing how this version of Qt was built.
bool isEmpty() const noexcept
Definition qlist.h:401
T & first()
Definition qlist.h:645
\inmodule QtCore
Definition qlogging.h:42
void stopLogging() override
Called after the end of a test run.
void leaveTestFunction() override
This virtual method is called after a test function has completed, to match \l enterTestFunction().
void addMessage(QtMsgType, const QMessageLogContext &, const QString &) override
This is an overloaded member function, provided for convenience. It differs from the above function o...
void addIncident(IncidentTypes type, const char *description, const char *file=nullptr, int line=0) override
This virtual method is called when an event occurs that relates to the resolution of the test.
void startLogging() override
Called before the start of a test run.
void enterTestFunction(const char *function) override
This virtual method is called before each test function is invoked.
void addBenchmarkResults(const QList< QBenchmarkResult > &results) override
bool isRepeatSupported() const override
Returns true if the logger supports repeated test runs.
QPlainTestLogger(const char *filename)
\macro QT_RESTRICTED_CAST_FROM_ASCII
Definition qstring.h:129
static QString productType()
Definition qsysinfo.cpp:769
static QString productVersion()
Definition qsysinfo.cpp:839
static int verboseLevel()
Definition qtestlog.cpp:609
static int passCount()
Definition qtestlog.cpp:663
static int failCount()
Definition qtestlog.cpp:668
static int blacklistCount()
Definition qtestlog.cpp:678
static int skipCount()
Definition qtestlog.cpp:673
static qreal msecsTotalTime()
Definition qtestlog_p.h:117
static const char * currentTestObjectName()
QString str
[2]
b clear()
QString text
list append(new Employee("Blackpool", "Stephen"))
Combined button and popup list for selecting options.
void generateTestIdentifier(QTestCharBuffer *identifier, int parts)
int qt_asprintf(QTestCharBuffer *str, const char *format,...)
static const char * ptMessageType2String(QAbstractTestLogger::MessageTypes type)
static const char * ptIncidentType2String(QAbstractTestLogger::IncidentTypes type)
static int countSignificantDigits(T num)
const char * benchmarkMetricUnit(QBenchmarkMetric metric)
static const char * benchmarkResult2String()
@ WalltimeNanoseconds
@ CachePrefetchMisses
@ WalltimeMilliseconds
@ BranchInstructions
QByteArray formatResult(T number, int significantDigits)
bool shouldLogToStderr()
Returns true if logging stderr should be ensured.
Definition qlogging.cpp:306
static void * context
Q_CORE_EXPORT int qsnprintf(char *str, size_t n, const char *fmt,...)
#define Q_FALLTHROUGH()
AudioChannelLayoutTag tag
EGLStreamKHR stream
EGLOutputLayerEXT EGLint EGLAttrib value
[5]
bool qIsNull(qfloat16 f) noexcept
Definition qfloat16.h:354
int qRound(qfloat16 d) noexcept
Definition qfloat16.h:327
static constexpr int digits(int number)
QtMsgType
Definition qlogging.h:29
constexpr const T & qMin(const T &a, const T &b)
Definition qminmax.h:40
constexpr T qAbs(const T &t)
Definition qnumeric.h:328
GLsizei const GLfloat * v
[13]
GLuint divisor
GLenum GLuint GLenum GLsizei length
GLenum type
GLenum GLuint GLenum GLsizei const GLchar * buf
GLuint GLsizei const GLchar * message
GLint GLsizei GLsizei GLenum format
GLsizei GLsizei GLchar * source
GLuint64EXT * result
[6]
GLenum GLsizei len
GLuint num
#define Q_ASSERT(cond)
Definition qrandom.cpp:47
#define qPrintable(string)
Definition qstring.h:1531
#define QTEST_ASSERT(cond)
Definition qtestassert.h:11
#define QTEST_VERSION_STR
long long qint64
Definition qtypes.h:60
double qreal
Definition qtypes.h:187
QFile file
[0]
QJSValueList args