1 | /**************************************************************************** |
2 | ** |
3 | ** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). |
4 | ** Contact: http://www.qt-project.org/legal |
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 Digia. For licensing terms and |
14 | ** conditions see http://qt.digia.com/licensing. For further information |
15 | ** use the contact form at http://qt.digia.com/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 2.1 as published by the Free Software |
20 | ** Foundation and appearing in the file LICENSE.LGPL included in the |
21 | ** packaging of this file. Please review the following information to |
22 | ** ensure the GNU Lesser General Public License version 2.1 requirements |
23 | ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. |
24 | ** |
25 | ** In addition, as a special exception, Digia gives you certain additional |
26 | ** rights. These rights are described in the Digia Qt LGPL Exception |
27 | ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. |
28 | ** |
29 | ** GNU General Public License Usage |
30 | ** Alternatively, this file may be used under the terms of the GNU |
31 | ** General Public License version 3.0 as published by the Free Software |
32 | ** Foundation and appearing in the file LICENSE.GPL included in the |
33 | ** packaging of this file. Please review the following information to |
34 | ** ensure the GNU General Public License version 3.0 requirements will be |
35 | ** met: http://www.gnu.org/copyleft/gpl.html. |
36 | ** |
37 | ** |
38 | ** $QT_END_LICENSE$ |
39 | ** |
40 | ****************************************************************************/ |
41 | |
42 | #include "QtTest/private/qtestresult_p.h" |
43 | #include "QtTest/qtestassert.h" |
44 | #include "QtTest/private/qtestlog_p.h" |
45 | #include "QtTest/private/qplaintestlogger_p.h" |
46 | #include "QtTest/private/qbenchmark_p.h" |
47 | #include "QtTest/private/qbenchmarkmetric_p.h" |
48 | |
49 | #include <stdarg.h> |
50 | #include <stdio.h> |
51 | #include <stdlib.h> |
52 | #include <string.h> |
53 | |
54 | #ifdef Q_OS_WIN |
55 | #include "windows.h" |
56 | #endif |
57 | |
58 | #if defined(Q_OS_SYMBIAN) |
59 | #include <e32debug.h> |
60 | #endif |
61 | |
62 | #ifdef Q_OS_WINCE |
63 | #include <QtCore/QString> |
64 | #endif |
65 | |
66 | #include <QtCore/QByteArray> |
67 | #include <QtCore/qmath.h> |
68 | |
69 | QT_BEGIN_NAMESPACE |
70 | |
71 | namespace QTest { |
72 | |
73 | #if defined(Q_OS_WIN) && !defined(Q_OS_WINCE) |
74 | |
75 | static CRITICAL_SECTION outputCriticalSection; |
76 | static HANDLE hConsole = INVALID_HANDLE_VALUE; |
77 | static WORD consoleAttributes = 0; |
78 | |
79 | static const char *qWinColoredMsg(int prefix, int color, const char *msg) |
80 | { |
81 | if (!hConsole) |
82 | return msg; |
83 | |
84 | WORD attr = consoleAttributes & ~(FOREGROUND_GREEN | FOREGROUND_BLUE |
85 | | FOREGROUND_RED | FOREGROUND_INTENSITY); |
86 | if (prefix) |
87 | attr |= FOREGROUND_INTENSITY; |
88 | if (color == 32) |
89 | attr |= FOREGROUND_GREEN; |
90 | if (color == 36) |
91 | attr |= FOREGROUND_BLUE | FOREGROUND_GREEN; |
92 | if (color == 31) |
93 | attr |= FOREGROUND_RED | FOREGROUND_INTENSITY; |
94 | if (color == 37) |
95 | attr |= FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE; |
96 | if (color == 33) |
97 | attr |= FOREGROUND_RED | FOREGROUND_GREEN; |
98 | SetConsoleTextAttribute(hConsole, attr); |
99 | printf(msg); |
100 | SetConsoleTextAttribute(hConsole, consoleAttributes); |
101 | return "" ; |
102 | } |
103 | |
104 | # define COLORED_MSG(prefix, color, msg) colored ? qWinColoredMsg(prefix, color, msg) : msg |
105 | #else |
106 | # define COLORED_MSG(prefix, color, msg) colored && QAbstractTestLogger::isTtyOutput() ? "\033["#prefix";"#color"m" msg "\033[0m" : msg |
107 | #endif |
108 | |
109 | static const char *incidentType2String(QAbstractTestLogger::IncidentTypes type) |
110 | { |
111 | static bool colored = (!qgetenv("QTEST_COLORED" ).isEmpty()); |
112 | switch (type) { |
113 | case QAbstractTestLogger::Pass: |
114 | return COLORED_MSG(0, 32, "PASS " ); //green |
115 | case QAbstractTestLogger::XFail: |
116 | return COLORED_MSG(1, 32, "XFAIL " ); //light green |
117 | case QAbstractTestLogger::Fail: |
118 | return COLORED_MSG(0, 31, "FAIL! " ); //red |
119 | case QAbstractTestLogger::XPass: |
120 | return COLORED_MSG(0, 31, "XPASS " ); //red, too |
121 | } |
122 | return "??????" ; |
123 | } |
124 | |
125 | static const char *benchmarkResult2String() |
126 | { |
127 | static bool colored = (!qgetenv("QTEST_COLORED" ).isEmpty()); |
128 | return COLORED_MSG(0, 36, "RESULT " ); // cyan |
129 | } |
130 | |
131 | static const char *messageType2String(QAbstractTestLogger::MessageTypes type) |
132 | { |
133 | #ifdef Q_OS_WIN |
134 | static bool colored = (!qgetenv("QTEST_COLORED" ).isEmpty()); |
135 | #else |
136 | static bool colored = ::getenv("QTEST_COLORED" ); |
137 | #endif |
138 | switch (type) { |
139 | case QAbstractTestLogger::Skip: |
140 | return COLORED_MSG(0, 37, "SKIP " ); //white |
141 | case QAbstractTestLogger::Warn: |
142 | return COLORED_MSG(0, 33, "WARNING" ); // yellow |
143 | case QAbstractTestLogger::QWarning: |
144 | return COLORED_MSG(1, 33, "QWARN " ); |
145 | case QAbstractTestLogger::QDebug: |
146 | return COLORED_MSG(1, 33, "QDEBUG " ); |
147 | case QAbstractTestLogger::QSystem: |
148 | return COLORED_MSG(1, 33, "QSYSTEM" ); |
149 | case QAbstractTestLogger::QFatal: |
150 | return COLORED_MSG(0, 31, "QFATAL " ); // red |
151 | case QAbstractTestLogger::Info: |
152 | return "INFO " ; // no coloring |
153 | } |
154 | return "??????" ; |
155 | } |
156 | |
157 | static void outputMessage(const char *str) |
158 | { |
159 | #if defined(Q_OS_WINCE) |
160 | QString strUtf16 = QString::fromLatin1(str); |
161 | const int maxOutputLength = 255; |
162 | do { |
163 | QString tmp = strUtf16.left(maxOutputLength); |
164 | OutputDebugString((wchar_t*)tmp.utf16()); |
165 | strUtf16.remove(0, maxOutputLength); |
166 | } while (!strUtf16.isEmpty()); |
167 | if (QTestLog::outputFileName()) |
168 | #elif defined(Q_OS_WIN) |
169 | EnterCriticalSection(&outputCriticalSection); |
170 | // OutputDebugString is not threadsafe |
171 | OutputDebugStringA(str); |
172 | LeaveCriticalSection(&outputCriticalSection); |
173 | #elif defined(Q_OS_SYMBIAN) |
174 | // RDebug::Print has a cap of 256 characters so break it up |
175 | TPtrC8 ptr(reinterpret_cast<const TUint8*>(str)); |
176 | _LIT(format, "[QTestLib] %S" ); |
177 | const int maxBlockSize = 256 - ((const TDesC &)format).Length(); |
178 | HBufC* hbuffer = HBufC::New(maxBlockSize); |
179 | if(hbuffer) { |
180 | for (int i = 0; i < ptr.Length(); i += maxBlockSize) { |
181 | int size = Min(maxBlockSize, ptr.Length() - i); |
182 | hbuffer->Des().Copy(ptr.Mid(i, size)); |
183 | RDebug::Print(format, hbuffer); |
184 | } |
185 | delete hbuffer; |
186 | } |
187 | else { |
188 | // fast, no allocations, but truncates silently |
189 | RDebug::RawPrint(format); |
190 | TPtrC8 ptr(reinterpret_cast<const TUint8*>(str)); |
191 | RDebug::RawPrint(ptr); |
192 | RDebug::RawPrint(_L8("\n" )); |
193 | } |
194 | #endif |
195 | QAbstractTestLogger::outputString(str); |
196 | } |
197 | |
198 | static void printMessage(const char *type, const char *msg, const char *file = 0, int line = 0) |
199 | { |
200 | QTEST_ASSERT(type); |
201 | QTEST_ASSERT(msg); |
202 | |
203 | QTestCharBuffer buf; |
204 | |
205 | const char *fn = QTestResult::currentTestFunction() ? QTestResult::currentTestFunction() |
206 | : "UnknownTestFunc" ; |
207 | const char *tag = QTestResult::currentDataTag() ? QTestResult::currentDataTag() : "" ; |
208 | const char *gtag = QTestResult::currentGlobalDataTag() |
209 | ? QTestResult::currentGlobalDataTag() |
210 | : "" ; |
211 | const char *filler = (tag[0] && gtag[0]) ? ":" : "" ; |
212 | if (file) { |
213 | QTest::qt_asprintf(&buf, "%s: %s::%s(%s%s%s)%s%s\n" |
214 | #ifdef Q_OS_WIN |
215 | "%s(%d) : failure location\n" |
216 | #else |
217 | " Loc: [%s(%d)]\n" |
218 | #endif |
219 | , type, QTestResult::currentTestObjectName(), fn, gtag, filler, tag, |
220 | msg[0] ? " " : "" , msg, file, line); |
221 | } else { |
222 | QTest::qt_asprintf(&buf, "%s: %s::%s(%s%s%s)%s%s\n" , |
223 | type, QTestResult::currentTestObjectName(), fn, gtag, filler, tag, |
224 | msg[0] ? " " : "" , msg); |
225 | } |
226 | // In colored mode, printf above stripped our nonprintable control characters. |
227 | // Put them back. |
228 | memcpy(buf.data(), type, strlen(type)); |
229 | outputMessage(buf.data()); |
230 | } |
231 | |
232 | template <typename T> |
233 | static int countSignificantDigits(T num) |
234 | { |
235 | if (num <= 0) |
236 | return 0; |
237 | |
238 | int digits = 0; |
239 | qreal divisor = 1; |
240 | |
241 | while (num / divisor >= 1) { |
242 | divisor *= 10; |
243 | ++digits; |
244 | } |
245 | |
246 | return digits; |
247 | } |
248 | |
249 | // Pretty-prints a benchmark result using the given number of digits. |
250 | template <typename T> QString formatResult(T number, int significantDigits) |
251 | { |
252 | if (number < T(0)) |
253 | return QLatin1String("NAN" ); |
254 | if (number == T(0)) |
255 | return QLatin1String("0" ); |
256 | |
257 | QString beforeDecimalPoint = QString::number(qint64(number), 'f', 0); |
258 | QString afterDecimalPoint = QString::number(number, 'f', 20); |
259 | afterDecimalPoint.remove(0, beforeDecimalPoint.count() + 1); |
260 | |
261 | int beforeUse = qMin(beforeDecimalPoint.count(), significantDigits); |
262 | int beforeRemove = beforeDecimalPoint.count() - beforeUse; |
263 | |
264 | // Replace insignificant digits before the decimal point with zeros. |
265 | beforeDecimalPoint.chop(beforeRemove); |
266 | for (int i = 0; i < beforeRemove; ++i) { |
267 | beforeDecimalPoint.append(QLatin1Char('0')); |
268 | } |
269 | |
270 | int afterUse = significantDigits - beforeUse; |
271 | |
272 | // leading zeroes after the decimal point does not count towards the digit use. |
273 | if (beforeDecimalPoint == QLatin1String("0" ) && afterDecimalPoint.isEmpty() == false) { |
274 | ++afterUse; |
275 | |
276 | int i = 0; |
277 | while (i < afterDecimalPoint.count() && afterDecimalPoint.at(i) == QLatin1Char('0')) { |
278 | ++i; |
279 | } |
280 | |
281 | afterUse += i; |
282 | } |
283 | |
284 | int afterRemove = afterDecimalPoint.count() - afterUse; |
285 | afterDecimalPoint.chop(afterRemove); |
286 | |
287 | QChar separator = QLatin1Char(','); |
288 | QChar decimalPoint = QLatin1Char('.'); |
289 | |
290 | // insert thousands separators |
291 | int length = beforeDecimalPoint.length(); |
292 | for (int i = beforeDecimalPoint.length() -1; i >= 1; --i) { |
293 | if ((length - i) % 3 == 0) |
294 | beforeDecimalPoint.insert(i, separator); |
295 | } |
296 | |
297 | QString print; |
298 | print = beforeDecimalPoint; |
299 | if (afterUse > 0) |
300 | print.append(decimalPoint); |
301 | |
302 | print += afterDecimalPoint; |
303 | |
304 | |
305 | return print; |
306 | } |
307 | |
308 | template <typename T> |
309 | int formatResult(char * buffer, int bufferSize, T number, int significantDigits) |
310 | { |
311 | QString result = formatResult(number, significantDigits); |
312 | qstrncpy(buffer, result.toAscii().constData(), bufferSize); |
313 | int size = result.count(); |
314 | return size; |
315 | } |
316 | |
317 | // static void printBenchmarkResult(const char *bmtag, int value, int iterations) |
318 | static void printBenchmarkResult(const QBenchmarkResult &result) |
319 | { |
320 | const char *bmtag = QTest::benchmarkResult2String(); |
321 | |
322 | char buf1[1024]; |
323 | QTest::qt_snprintf( |
324 | buf1, sizeof(buf1), "%s: %s::%s" , |
325 | bmtag, |
326 | QTestResult::currentTestObjectName(), |
327 | result.context.slotName.toAscii().data()); |
328 | |
329 | char bufTag[1024]; |
330 | bufTag[0] = 0; |
331 | QByteArray tag = result.context.tag.toAscii(); |
332 | if (tag.isEmpty() == false) { |
333 | QTest::qt_snprintf(bufTag, sizeof(bufTag), ":\"%s\"" , tag.data()); |
334 | } |
335 | |
336 | |
337 | char fillFormat[8]; |
338 | int fillLength = 5; |
339 | QTest::qt_snprintf( |
340 | fillFormat, sizeof(fillFormat), ":\n%%%ds" , fillLength); |
341 | char fill[1024]; |
342 | QTest::qt_snprintf(fill, sizeof(fill), fillFormat, "" ); |
343 | |
344 | const char * unitText = QTest::benchmarkMetricUnit(result.metric); |
345 | |
346 | qreal valuePerIteration = qreal(result.value) / qreal(result.iterations); |
347 | char resultBuffer[100] = "" ; |
348 | formatResult(resultBuffer, 100, valuePerIteration, countSignificantDigits(result.value)); |
349 | |
350 | char buf2[1024]; |
351 | QTest::qt_snprintf( |
352 | buf2, sizeof(buf2), "%s %s" , |
353 | resultBuffer, |
354 | unitText); |
355 | |
356 | char buf2_[1024]; |
357 | QByteArray iterationText = " per iteration" ; |
358 | Q_ASSERT(result.iterations > 0); |
359 | QTest::qt_snprintf( |
360 | buf2_, |
361 | sizeof(buf2_), "%s" , |
362 | iterationText.data()); |
363 | |
364 | char buf3[1024]; |
365 | Q_ASSERT(result.iterations > 0); |
366 | formatResult(resultBuffer, 100, result.value, countSignificantDigits(result.value)); |
367 | QTest::qt_snprintf( |
368 | buf3, sizeof(buf3), " (total: %s, iterations: %d)" , |
369 | resultBuffer, |
370 | result.iterations); |
371 | |
372 | char buf[1024]; |
373 | |
374 | if (result.setByMacro) { |
375 | QTest::qt_snprintf( |
376 | buf, sizeof(buf), "%s%s%s%s%s%s\n" , buf1, bufTag, fill, buf2, buf2_, buf3); |
377 | } else { |
378 | QTest::qt_snprintf(buf, sizeof(buf), "%s%s%s%s\n" , buf1, bufTag, fill, buf2); |
379 | } |
380 | |
381 | memcpy(buf, bmtag, strlen(bmtag)); |
382 | outputMessage(buf); |
383 | } |
384 | } |
385 | |
386 | QPlainTestLogger::QPlainTestLogger() |
387 | : randomSeed(9), hasRandomSeed(false) |
388 | { |
389 | #if defined(Q_OS_WIN) && !defined(Q_OS_WINCE) |
390 | InitializeCriticalSection(&QTest::outputCriticalSection); |
391 | QTest::hConsole = GetStdHandle(STD_OUTPUT_HANDLE); |
392 | if (QTest::hConsole != INVALID_HANDLE_VALUE) { |
393 | CONSOLE_SCREEN_BUFFER_INFO info; |
394 | if (GetConsoleScreenBufferInfo(QTest::hConsole, &info)) { |
395 | QTest::consoleAttributes = info.wAttributes; |
396 | } else { |
397 | QTest::hConsole = INVALID_HANDLE_VALUE; |
398 | } |
399 | } |
400 | #endif |
401 | } |
402 | |
403 | QPlainTestLogger::~QPlainTestLogger() |
404 | { |
405 | #if defined(Q_OS_WIN) && !defined(Q_OS_WINCE) |
406 | DeleteCriticalSection(&QTest::outputCriticalSection); |
407 | #endif |
408 | } |
409 | |
410 | void QPlainTestLogger::startLogging() |
411 | { |
412 | QAbstractTestLogger::startLogging(); |
413 | |
414 | char buf[1024]; |
415 | if (QTestLog::verboseLevel() < 0) { |
416 | QTest::qt_snprintf(buf, sizeof(buf), "Testing %s\n" , |
417 | QTestResult::currentTestObjectName()); |
418 | } else { |
419 | if (hasRandomSeed) { |
420 | QTest::qt_snprintf(buf, sizeof(buf), |
421 | "********* Start testing of %s *********\n" |
422 | "Config: Using QTest library " QTEST_VERSION_STR |
423 | ", Qt %s, Random seed %d\n" , QTestResult::currentTestObjectName(), qVersion(), randomSeed); |
424 | } else { |
425 | QTest::qt_snprintf(buf, sizeof(buf), |
426 | "********* Start testing of %s *********\n" |
427 | "Config: Using QTest library " QTEST_VERSION_STR |
428 | ", Qt %s\n" , QTestResult::currentTestObjectName(), qVersion()); |
429 | } |
430 | } |
431 | QTest::outputMessage(buf); |
432 | } |
433 | |
434 | void QPlainTestLogger::stopLogging() |
435 | { |
436 | char buf[1024]; |
437 | if (QTestLog::verboseLevel() < 0) { |
438 | QTest::qt_snprintf(buf, sizeof(buf), "Totals: %d passed, %d failed, %d skipped\n" , |
439 | QTestResult::passCount(), QTestResult::failCount(), |
440 | QTestResult::skipCount()); |
441 | } else { |
442 | QTest::qt_snprintf(buf, sizeof(buf), |
443 | "Totals: %d passed, %d failed, %d skipped\n" |
444 | "********* Finished testing of %s *********\n" , |
445 | QTestResult::passCount(), QTestResult::failCount(), |
446 | QTestResult::skipCount(), QTestResult::currentTestObjectName()); |
447 | } |
448 | QTest::outputMessage(buf); |
449 | |
450 | QAbstractTestLogger::stopLogging(); |
451 | } |
452 | |
453 | |
454 | void QPlainTestLogger::enterTestFunction(const char * /*function*/) |
455 | { |
456 | if (QTestLog::verboseLevel() >= 1) |
457 | QTest::printMessage(QTest::messageType2String(Info), "entering" ); |
458 | } |
459 | |
460 | void QPlainTestLogger::leaveTestFunction() |
461 | { |
462 | } |
463 | |
464 | void QPlainTestLogger::addIncident(IncidentTypes type, const char *description, |
465 | const char *file, int line) |
466 | { |
467 | // suppress PASS in silent mode |
468 | if (type == QAbstractTestLogger::Pass && QTestLog::verboseLevel() < 0) |
469 | return; |
470 | |
471 | QTest::printMessage(QTest::incidentType2String(type), description, file, line); |
472 | } |
473 | |
474 | void QPlainTestLogger::addBenchmarkResult(const QBenchmarkResult &result) |
475 | { |
476 | // QTest::printBenchmarkResult(QTest::benchmarkResult2String(), value, iterations); |
477 | QTest::printBenchmarkResult(result); |
478 | } |
479 | |
480 | void QPlainTestLogger::addMessage(MessageTypes type, const char *message, |
481 | const char *file, int line) |
482 | { |
483 | // suppress PASS in silent mode |
484 | if ((type == QAbstractTestLogger::Skip || type == QAbstractTestLogger::Info) |
485 | && QTestLog::verboseLevel() < 0) |
486 | return; |
487 | |
488 | QTest::printMessage(QTest::messageType2String(type), message, file, line); |
489 | } |
490 | |
491 | void QPlainTestLogger::registerRandomSeed(unsigned int seed) |
492 | { |
493 | randomSeed = seed; |
494 | hasRandomSeed = true; |
495 | } |
496 | |
497 | QT_END_NAMESPACE |
498 | |