1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of the QtTest module of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see https://www.qt.io/terms-conditions. For further
15 ** information use the contact form at https://www.qt.io/contact-us.
16 **
17 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 3 as published by the Free Software
20 ** Foundation and appearing in the file LICENSE.LGPL3 included in the
21 ** packaging of this file. Please review the following information to
22 ** ensure the GNU Lesser General Public License version 3 requirements
23 ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24 **
25 ** GNU General Public License Usage
26 ** Alternatively, this file may be used under the terms of the GNU
27 ** General Public License version 2.0 or (at your option) the GNU General
28 ** Public license version 3 or any later version approved by the KDE Free
29 ** Qt Foundation. The licenses are as published by the Free Software
30 ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31 ** included in the packaging of this file. Please review the following
32 ** information to ensure the GNU General Public License requirements will
33 ** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34 ** https://www.gnu.org/licenses/gpl-3.0.html.
35 **
36 ** $QT_END_LICENSE$
37 **
38 ****************************************************************************/
39 
40 #include <QtTest/private/qtestresult_p.h>
41 #include <QtTest/qtestassert.h>
42 #include <QtTest/private/qtestlog_p.h>
43 #include <QtTest/private/qplaintestlogger_p.h>
44 #include <QtTest/private/qbenchmark_p.h>
45 #include <QtTest/private/qbenchmarkmetric_p.h>
46 
47 #include <QtCore/private/qlogging_p.h>
48 
49 #include <stdarg.h>
50 #include <stdio.h>
51 #include <stdlib.h>
52 #include <string.h>
53 
54 #ifdef min // windows.h without NOMINMAX is included by the benchmark headers.
55 #  undef min
56 #endif
57 #ifdef max
58 #  undef max
59 #endif
60 
61 #include <QtCore/QByteArray>
62 #include <QtCore/qmath.h>
63 #include <QtCore/QLibraryInfo>
64 
65 #ifdef Q_OS_ANDROID
66 #  include <android/log.h>
67 #endif
68 
69 #ifdef Q_OS_WIN
70 #  include <qt_windows.h>
71 #endif
72 
73 QT_BEGIN_NAMESPACE
74 
75 namespace QTest {
76 
incidentType2String(QAbstractTestLogger::IncidentTypes type)77     static const char *incidentType2String(QAbstractTestLogger::IncidentTypes type)
78     {
79         switch (type) {
80         case QAbstractTestLogger::Pass:
81             return "PASS   ";
82         case QAbstractTestLogger::XFail:
83             return "XFAIL  ";
84         case QAbstractTestLogger::Fail:
85             return "FAIL!  ";
86         case QAbstractTestLogger::XPass:
87             return "XPASS  ";
88         case QAbstractTestLogger::BlacklistedPass:
89             return "BPASS  ";
90         case QAbstractTestLogger::BlacklistedFail:
91             return "BFAIL  ";
92         case QAbstractTestLogger::BlacklistedXPass:
93             return "BXPASS ";
94         case QAbstractTestLogger::BlacklistedXFail:
95             return "BXFAIL ";
96         }
97         return "??????";
98     }
99 
benchmarkResult2String()100     static const char *benchmarkResult2String()
101     {
102         return "RESULT ";
103     }
104 
messageType2String(QAbstractTestLogger::MessageTypes type)105     static const char *messageType2String(QAbstractTestLogger::MessageTypes type)
106     {
107         switch (type) {
108         case QAbstractTestLogger::Skip:
109             return "SKIP   ";
110         case QAbstractTestLogger::Warn:
111             return "WARNING";
112         case QAbstractTestLogger::QWarning:
113             return "QWARN  ";
114         case QAbstractTestLogger::QDebug:
115             return "QDEBUG ";
116         case QAbstractTestLogger::QInfo:
117             return "QINFO  ";
118         case QAbstractTestLogger::QSystem:
119             return "QSYSTEM";
120         case QAbstractTestLogger::QFatal:
121             return "QFATAL ";
122         case QAbstractTestLogger::Info:
123             return "INFO   ";
124         }
125         return "??????";
126     }
127 
128     template <typename T>
countSignificantDigits(T num)129     static int countSignificantDigits(T num)
130     {
131         if (num <= 0)
132             return 0;
133 
134         int digits = 0;
135         qreal divisor = 1;
136 
137         while (num / divisor >= 1) {
138             divisor *= 10;
139             ++digits;
140         }
141 
142         return digits;
143     }
144 
145     // Pretty-prints a benchmark result using the given number of digits.
formatResult(T number,int significantDigits)146     template <typename T> QString formatResult(T number, int significantDigits)
147     {
148         if (number < T(0))
149             return QLatin1String("NAN");
150         if (number == T(0))
151             return QLatin1String("0");
152 
153         QString beforeDecimalPoint = QString::number(qint64(number), 'f', 0);
154         QString afterDecimalPoint = QString::number(number, 'f', 20);
155         afterDecimalPoint.remove(0, beforeDecimalPoint.count() + 1);
156 
157         int beforeUse = qMin(beforeDecimalPoint.count(), significantDigits);
158         int beforeRemove = beforeDecimalPoint.count() - beforeUse;
159 
160         // Replace insignificant digits before the decimal point with zeros.
161         beforeDecimalPoint.chop(beforeRemove);
162         for (int i = 0; i < beforeRemove; ++i) {
163             beforeDecimalPoint.append(QLatin1Char('0'));
164         }
165 
166         int afterUse = significantDigits - beforeUse;
167 
168         // leading zeroes after the decimal point does not count towards the digit use.
169         if (beforeDecimalPoint == QLatin1String("0") && afterDecimalPoint.isEmpty() == false) {
170             ++afterUse;
171 
172             int i = 0;
173             while (i < afterDecimalPoint.count() && afterDecimalPoint.at(i) == QLatin1Char('0')) {
174                 ++i;
175             }
176 
177             afterUse += i;
178         }
179 
180         int afterRemove = afterDecimalPoint.count() - afterUse;
181         afterDecimalPoint.chop(afterRemove);
182 
183         QChar separator = QLatin1Char(',');
184         QChar decimalPoint = QLatin1Char('.');
185 
186         // insert thousands separators
187         int length = beforeDecimalPoint.length();
188         for (int i = beforeDecimalPoint.length() -1; i >= 1; --i) {
189             if ((length - i) % 3 == 0)
190                 beforeDecimalPoint.insert(i, separator);
191         }
192 
193         QString print;
194         print = beforeDecimalPoint;
195         if (afterUse > 0)
196             print.append(decimalPoint);
197 
198         print += afterDecimalPoint;
199 
200 
201         return print;
202     }
203 
204     template <typename T>
formatResult(char * buffer,int bufferSize,T number,int significantDigits)205     int formatResult(char * buffer, int bufferSize, T number, int significantDigits)
206     {
207         QString result = formatResult(number, significantDigits);
208         int size = result.count();
209         qstrncpy(buffer, std::move(result).toLatin1().constData(), bufferSize);
210         return size;
211     }
212 }
213 
outputMessage(const char * str)214 void QPlainTestLogger::outputMessage(const char *str)
215 {
216 #if defined(Q_OS_WIN)
217     // Log to system log only if output is not redirected and stderr not preferred
218     if (stream == stdout && !QtPrivate::shouldLogToStderr()) {
219         OutputDebugStringA(str);
220         return;
221     }
222 #elif defined(Q_OS_ANDROID)
223     __android_log_write(ANDROID_LOG_INFO, "QTestLib", str);
224 #endif
225     outputString(str);
226 }
227 
printMessage(const char * type,const char * msg,const char * file,int line)228 void QPlainTestLogger::printMessage(const char *type, const char *msg, const char *file, int line)
229 {
230     QTEST_ASSERT(type);
231     QTEST_ASSERT(msg);
232 
233     QTestCharBuffer messagePrefix;
234 
235     QTestCharBuffer failureLocation;
236     if (file) {
237 #ifdef Q_OS_WIN
238 #define FAILURE_LOCATION_STR "\n%s(%d) : failure location"
239 #else
240 #define FAILURE_LOCATION_STR "\n   Loc: [%s(%d)]"
241 #endif
242         QTest::qt_asprintf(&failureLocation, FAILURE_LOCATION_STR, file, line);
243     }
244 
245     const char *msgFiller = msg[0] ? " " : "";
246     QTestCharBuffer testIdentifier;
247     QTestPrivate::generateTestIdentifier(&testIdentifier);
248     QTest::qt_asprintf(&messagePrefix, "%s: %s%s%s%s\n",
249                        type, testIdentifier.data(), msgFiller, msg, failureLocation.data());
250 
251     // In colored mode, printf above stripped our nonprintable control characters.
252     // Put them back.
253     memcpy(messagePrefix.data(), type, strlen(type));
254 
255     outputMessage(messagePrefix.data());
256 }
257 
printBenchmarkResult(const QBenchmarkResult & result)258 void QPlainTestLogger::printBenchmarkResult(const QBenchmarkResult &result)
259 {
260     const char *bmtag = QTest::benchmarkResult2String();
261 
262     char buf1[1024];
263     qsnprintf(
264         buf1, sizeof(buf1), "%s: %s::%s",
265         bmtag,
266         QTestResult::currentTestObjectName(),
267         result.context.slotName.toLatin1().data());
268 
269     char bufTag[1024];
270     bufTag[0] = 0;
271     QByteArray tag = result.context.tag.toLocal8Bit();
272     if (tag.isEmpty() == false) {
273         qsnprintf(bufTag, sizeof(bufTag), ":\"%s\"", tag.data());
274     }
275 
276 
277     char fillFormat[8];
278     int fillLength = 5;
279     qsnprintf(fillFormat, sizeof(fillFormat), ":\n%%%ds", fillLength);
280     char fill[1024];
281     qsnprintf(fill, sizeof(fill), fillFormat, "");
282 
283     const char * unitText = QTest::benchmarkMetricUnit(result.metric);
284 
285     qreal valuePerIteration = qreal(result.value) / qreal(result.iterations);
286     char resultBuffer[100] = "";
287     QTest::formatResult(resultBuffer, 100, valuePerIteration, QTest::countSignificantDigits(result.value));
288 
289     char buf2[1024];
290     qsnprintf(buf2, sizeof(buf2), "%s %s", resultBuffer, unitText);
291 
292     char buf2_[1024];
293     QByteArray iterationText = " per iteration";
294     Q_ASSERT(result.iterations > 0);
295     qsnprintf(buf2_, sizeof(buf2_), "%s", iterationText.data());
296 
297     char buf3[1024];
298     Q_ASSERT(result.iterations > 0);
299     QTest::formatResult(resultBuffer, 100, result.value, QTest::countSignificantDigits(result.value));
300     qsnprintf(buf3, sizeof(buf3), " (total: %s, iterations: %d)", resultBuffer, result.iterations);
301 
302     char buf[1024];
303 
304     if (result.setByMacro) {
305         qsnprintf(buf, sizeof(buf), "%s%s%s%s%s%s\n", buf1, bufTag, fill, buf2, buf2_, buf3);
306     } else {
307         qsnprintf(buf, sizeof(buf), "%s%s%s%s\n", buf1, bufTag, fill, buf2);
308     }
309 
310     memcpy(buf, bmtag, strlen(bmtag));
311     outputMessage(buf);
312 }
313 
QPlainTestLogger(const char * filename)314 QPlainTestLogger::QPlainTestLogger(const char *filename)
315     : QAbstractTestLogger(filename)
316 {
317 }
318 
319 QPlainTestLogger::~QPlainTestLogger() = default;
320 
startLogging()321 void QPlainTestLogger::startLogging()
322 {
323     QAbstractTestLogger::startLogging();
324 
325     char buf[1024];
326     if (QTestLog::verboseLevel() < 0) {
327         qsnprintf(buf, sizeof(buf), "Testing %s\n", QTestResult::currentTestObjectName());
328     } else {
329         qsnprintf(buf, sizeof(buf),
330                   "********* Start testing of %s *********\n"
331                   "Config: Using QtTest library " QTEST_VERSION_STR
332                   ", %s, %s %s\n", QTestResult::currentTestObjectName(), QLibraryInfo::build(),
333                   qPrintable(QSysInfo::productType()), qPrintable(QSysInfo::productVersion()));
334     }
335     outputMessage(buf);
336 }
337 
stopLogging()338 void QPlainTestLogger::stopLogging()
339 {
340     char buf[1024];
341     const int timeMs = qRound(QTestLog::msecsTotalTime());
342     if (QTestLog::verboseLevel() < 0) {
343         qsnprintf(buf, sizeof(buf), "Totals: %d passed, %d failed, %d skipped, %d blacklisted, %dms\n",
344                   QTestLog::passCount(), QTestLog::failCount(),
345                   QTestLog::skipCount(), QTestLog::blacklistCount(), timeMs);
346     } else {
347         qsnprintf(buf, sizeof(buf),
348                   "Totals: %d passed, %d failed, %d skipped, %d blacklisted, %dms\n"
349                   "********* Finished testing of %s *********\n",
350                   QTestLog::passCount(), QTestLog::failCount(),
351                   QTestLog::skipCount(), QTestLog::blacklistCount(), timeMs,
352                   QTestResult::currentTestObjectName());
353     }
354     outputMessage(buf);
355 
356     QAbstractTestLogger::stopLogging();
357 }
358 
359 
enterTestFunction(const char *)360 void QPlainTestLogger::enterTestFunction(const char * /*function*/)
361 {
362     if (QTestLog::verboseLevel() >= 1)
363         printMessage(QTest::messageType2String(Info), "entering");
364 }
365 
leaveTestFunction()366 void QPlainTestLogger::leaveTestFunction()
367 {
368 }
369 
addIncident(IncidentTypes type,const char * description,const char * file,int line)370 void QPlainTestLogger::addIncident(IncidentTypes type, const char *description,
371                                    const char *file, int line)
372 {
373     // suppress PASS and XFAIL in silent mode
374     if ((type == QAbstractTestLogger::Pass || type == QAbstractTestLogger::XFail)
375         && QTestLog::verboseLevel() < 0)
376         return;
377 
378     printMessage(QTest::incidentType2String(type), description, file, line);
379 }
380 
addBenchmarkResult(const QBenchmarkResult & result)381 void QPlainTestLogger::addBenchmarkResult(const QBenchmarkResult &result)
382 {
383     // suppress benchmark results in silent mode
384     if (QTestLog::verboseLevel() < 0)
385         return;
386 
387     printBenchmarkResult(result);
388 }
389 
addMessage(QtMsgType type,const QMessageLogContext & context,const QString & message)390 void QPlainTestLogger::addMessage(QtMsgType type, const QMessageLogContext &context, const QString &message)
391 {
392     QAbstractTestLogger::addMessage(type, context, message);
393 }
394 
addMessage(MessageTypes type,const QString & message,const char * file,int line)395 void QPlainTestLogger::addMessage(MessageTypes type, const QString &message,
396                                   const char *file, int line)
397 {
398     // suppress non-fatal messages in silent mode
399     if (type != QAbstractTestLogger::QFatal && QTestLog::verboseLevel() < 0)
400         return;
401 
402     printMessage(QTest::messageType2String(type), qPrintable(message), file, line);
403 }
404 
405 QT_END_NAMESPACE
406