/**************************************************************************** ** ** Copyright (C) 2016 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the QtTest module of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL$ ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see https://www.qt.io/terms-conditions. For further ** information use the contact form at https://www.qt.io/contact-us. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 3 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL3 included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 3 requirements ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 2.0 or (at your option) the GNU General ** Public license version 3 or any later version approved by the KDE Free ** Qt Foundation. The licenses are as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-2.0.html and ** https://www.gnu.org/licenses/gpl-3.0.html. ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include #include #include #include #include #include #include #include #include #include QT_BEGIN_NAMESPACE namespace QTest { static const char* xmlMessageType2String(QAbstractTestLogger::MessageTypes type) { switch (type) { case QAbstractTestLogger::Warn: return "warn"; case QAbstractTestLogger::QSystem: return "system"; case QAbstractTestLogger::QDebug: return "qdebug"; case QAbstractTestLogger::QInfo: return "qinfo"; case QAbstractTestLogger::QWarning: return "qwarn"; case QAbstractTestLogger::QFatal: return "qfatal"; case QAbstractTestLogger::Skip: return "skip"; case QAbstractTestLogger::Info: return "info"; } return "??????"; } static const char* xmlIncidentType2String(QAbstractTestLogger::IncidentTypes type) { switch (type) { case QAbstractTestLogger::Pass: return "pass"; case QAbstractTestLogger::XFail: return "xfail"; case QAbstractTestLogger::Fail: return "fail"; case QAbstractTestLogger::XPass: return "xpass"; case QAbstractTestLogger::BlacklistedPass: return "bpass"; case QAbstractTestLogger::BlacklistedFail: return "bfail"; } return "??????"; } } QXmlTestLogger::QXmlTestLogger(XmlMode mode, const char *filename) : QAbstractTestLogger(filename), xmlmode(mode) { } QXmlTestLogger::~QXmlTestLogger() { } void QXmlTestLogger::startLogging() { QAbstractTestLogger::startLogging(); QTestCharBuffer buf; if (xmlmode == QXmlTestLogger::Complete) { QTestCharBuffer quotedTc; xmlQuote("edTc, QTestResult::currentTestObjectName()); QTest::qt_asprintf(&buf, "\n" "\n", quotedTc.constData()); outputString(buf.constData()); } QTestCharBuffer quotedBuild; xmlQuote("edBuild, QLibraryInfo::build()); QTest::qt_asprintf(&buf, "\n" " %s\n" " %s\n" " " QTEST_VERSION_STR "\n" "\n", qVersion(), quotedBuild.constData()); outputString(buf.constData()); } void QXmlTestLogger::stopLogging() { QTestCharBuffer buf; QTest::qt_asprintf(&buf, "\n", QTestLog::msecsTotalTime()); outputString(buf.constData()); if (xmlmode == QXmlTestLogger::Complete) { outputString("\n"); } QAbstractTestLogger::stopLogging(); } void QXmlTestLogger::enterTestFunction(const char *function) { QTestCharBuffer buf; QTestCharBuffer quotedFunction; xmlQuote("edFunction, function); QTest::qt_asprintf(&buf, "\n", quotedFunction.constData()); outputString(buf.constData()); } void QXmlTestLogger::leaveTestFunction() { QTestCharBuffer buf; QTest::qt_asprintf(&buf, " \n" "\n", QTestLog::msecsFunctionTime()); outputString(buf.constData()); } namespace QTest { inline static bool isEmpty(const char *str) { return !str || !str[0]; } static const char *incidentFormatString(bool noDescription, bool noTag) { if (noDescription) { if (noTag) return "\n"; else return "\n" " \n" "\n"; } else { if (noTag) return "\n" " \n" "\n"; else return "\n" " \n" " \n" "\n"; } } static const char *benchmarkResultFormatString() { return "\n"; } static const char *messageFormatString(bool noDescription, bool noTag) { if (noDescription) { if (noTag) return "\n"; else return "\n" " \n" "\n"; } else { if (noTag) return "\n" " \n" "\n"; else return "\n" " \n" " \n" "\n"; } } } // namespace void QXmlTestLogger::addIncident(IncidentTypes type, const char *description, const char *file, int line) { QTestCharBuffer buf; const char *tag = QTestResult::currentDataTag(); const char *gtag = QTestResult::currentGlobalDataTag(); const char *filler = (tag && gtag) ? ":" : ""; const bool notag = QTest::isEmpty(tag) && QTest::isEmpty(gtag); QTestCharBuffer quotedFile; QTestCharBuffer cdataGtag; QTestCharBuffer cdataTag; QTestCharBuffer cdataDescription; xmlQuote("edFile, file); xmlCdata(&cdataGtag, gtag); xmlCdata(&cdataTag, tag); xmlCdata(&cdataDescription, description); QTest::qt_asprintf(&buf, QTest::incidentFormatString(QTest::isEmpty(description), notag), QTest::xmlIncidentType2String(type), quotedFile.constData(), line, cdataGtag.constData(), filler, cdataTag.constData(), cdataDescription.constData()); outputString(buf.constData()); } void QXmlTestLogger::addBenchmarkResult(const QBenchmarkResult &result) { QTestCharBuffer buf; QTestCharBuffer quotedMetric; QTestCharBuffer quotedTag; xmlQuote("edMetric, benchmarkMetricName(result.metric)); xmlQuote("edTag, result.context.tag.toUtf8().constData()); const qreal valuePerIteration = qreal(result.value) / qreal(result.iterations); QTest::qt_asprintf( &buf, QTest::benchmarkResultFormatString(), quotedMetric.constData(), quotedTag.constData(), QByteArray::number(valuePerIteration).constData(), //no 64-bit qsnprintf support result.iterations); outputString(buf.constData()); } void QXmlTestLogger::addMessage(MessageTypes type, const QString &message, const char *file, int line) { QTestCharBuffer buf; const char *tag = QTestResult::currentDataTag(); const char *gtag = QTestResult::currentGlobalDataTag(); const char *filler = (tag && gtag) ? ":" : ""; const bool notag = QTest::isEmpty(tag) && QTest::isEmpty(gtag); QTestCharBuffer quotedFile; QTestCharBuffer cdataGtag; QTestCharBuffer cdataTag; QTestCharBuffer cdataDescription; xmlQuote("edFile, file); xmlCdata(&cdataGtag, gtag); xmlCdata(&cdataTag, tag); xmlCdata(&cdataDescription, message.toUtf8().constData()); QTest::qt_asprintf(&buf, QTest::messageFormatString(message.isEmpty(), notag), QTest::xmlMessageType2String(type), quotedFile.constData(), line, cdataGtag.constData(), filler, cdataTag.constData(), cdataDescription.constData()); outputString(buf.constData()); } /* Copy up to n characters from the src string into dest, escaping any special XML characters as necessary so that dest is suitable for use in an XML quoted attribute string. */ int QXmlTestLogger::xmlQuote(QTestCharBuffer* destBuf, char const* src, size_t n) { if (n == 0) return 0; char *dest = destBuf->data(); *dest = 0; if (!src) return 0; char* begin = dest; char* end = dest + n; while (dest < end) { switch (*src) { #define MAP_ENTITY(chr, ent) \ case chr: \ if (dest + sizeof(ent) < end) { \ strcpy(dest, ent); \ dest += sizeof(ent) - 1; \ } \ else { \ *dest = 0; \ return (dest+sizeof(ent)-begin); \ } \ ++src; \ break; MAP_ENTITY('>', ">"); MAP_ENTITY('<', "<"); MAP_ENTITY('\'', "'"); MAP_ENTITY('"', """); MAP_ENTITY('&', "&"); // not strictly necessary, but allows handling of comments without // having to explicitly look for `--' MAP_ENTITY('-', "-"); #undef MAP_ENTITY case 0: *dest = 0; return (dest-begin); default: *dest = *src; ++dest; ++src; break; } } // If we get here, dest was completely filled (dest == end) *(dest-1) = 0; return (dest-begin); } /* Copy up to n characters from the src string into dest, escaping any special strings such that dest is suitable for use in an XML CDATA section. */ int QXmlTestLogger::xmlCdata(QTestCharBuffer *destBuf, char const* src, size_t n) { if (!n) return 0; char *dest = destBuf->data(); if (!src || n == 1) { *dest = 0; return 0; } static char const CDATA_END[] = "]]>"; static char const CDATA_END_ESCAPED[] = "]]]>"; char* begin = dest; char* end = dest + n; while (dest < end) { if (!*src) { *dest = 0; return (dest-begin); } if (!strncmp(src, CDATA_END, sizeof(CDATA_END)-1)) { if (dest + sizeof(CDATA_END_ESCAPED) < end) { strcpy(dest, CDATA_END_ESCAPED); src += sizeof(CDATA_END)-1; dest += sizeof(CDATA_END_ESCAPED) - 1; } else { *dest = 0; return (dest+sizeof(CDATA_END_ESCAPED)-begin); } continue; } *dest = *src; ++src; ++dest; } // If we get here, dest was completely filled (dest == end) *(dest-1) = 0; return (dest-begin); } typedef int (*StringFormatFunction)(QTestCharBuffer*,char const*,size_t); /* A wrapper for string functions written to work with a fixed size buffer so they can be called with a dynamically allocated buffer. */ int allocateStringFn(QTestCharBuffer* str, char const* src, StringFormatFunction func) { static const int MAXSIZE = 1024*1024*2; int size = str->size(); int res = 0; for (;;) { res = func(str, src, size); str->data()[size - 1] = '\0'; if (res < size) { // We succeeded or fatally failed break; } // buffer wasn't big enough, try again size *= 2; if (size > MAXSIZE) { break; } if (!str->reset(size)) break; // ran out of memory - bye } return res; } int QXmlTestLogger::xmlQuote(QTestCharBuffer* str, char const* src) { return allocateStringFn(str, src, QXmlTestLogger::xmlQuote); } int QXmlTestLogger::xmlCdata(QTestCharBuffer* str, char const* src) { return allocateStringFn(str, src, QXmlTestLogger::xmlCdata); } QT_END_NAMESPACE