summaryrefslogtreecommitdiffstats
path: root/src/testlib/qxmltestlogger.cpp
blob: 27da73ba52da41a43dda8baff77ecef0c38c7550 (plain)
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460
// Copyright (C) 2022 The Qt Company Ltd.// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only#include <stdio.h>#include <string.h>#include <QtCore/qglobal.h>#include <QtCore/qlibraryinfo.h>#include <QtTest/private/qtestlog_p.h>#include <QtTest/private/qxmltestlogger_p.h>#include <QtTest/private/qtestresult_p.h>#include <QtTest/private/qbenchmark_p.h>#include <QtTest/private/qbenchmarkmetric_p.h>#include <QtTest/qtestcase.h> QT_BEGIN_NAMESPACE namespace QTest {static const char*xmlMessageType2String(QAbstractTestLogger::MessageTypes type){switch(type) {caseQAbstractTestLogger::QDebug:return"qdebug";caseQAbstractTestLogger::QInfo:return"qinfo";caseQAbstractTestLogger::QWarning:return"qwarn";caseQAbstractTestLogger::QCritical:return"qcritical";caseQAbstractTestLogger::QFatal:return"qfatal";caseQAbstractTestLogger::Info:return"info";caseQAbstractTestLogger::Warn:return"warn";}return"??????";}static const char*xmlIncidentType2String(QAbstractTestLogger::IncidentTypes type){switch(type) {caseQAbstractTestLogger::Skip:return"skip";caseQAbstractTestLogger::Pass:return"pass";caseQAbstractTestLogger::XFail:return"xfail";caseQAbstractTestLogger::Fail:return"fail";caseQAbstractTestLogger::XPass:return"xpass";caseQAbstractTestLogger::BlacklistedPass:return"bpass";caseQAbstractTestLogger::BlacklistedFail:return"bfail";caseQAbstractTestLogger::BlacklistedXPass:return"bxpass";caseQAbstractTestLogger::BlacklistedXFail:return"bxfail";}return"??????";}}/*! \internal \class QXmlTestLogger \inmodule QtTest QXmlTestLogger implements two XML formats specific to Qt. The two formats are distinguished by the XmlMode enum.*//*! \internal \enum QXmlTestLogger::XmlMode This enumerated type selects the type of XML output to produce. \value Complete A full self-contained XML document \value Light XML content suitable for embedding in an XML document The Complete form wraps the Light form in a <TestCase> element whose name attribute identifies the test class whose private slots are to be run. It also includes the usual <?xml ...> preamble.*/QXmlTestLogger::QXmlTestLogger(XmlMode mode,const char*filename):QAbstractTestLogger(filename),xmlmode(mode){}QXmlTestLogger::~QXmlTestLogger() =default;voidQXmlTestLogger::startLogging(){QAbstractTestLogger::startLogging(); QTestCharBuffer buf;if(xmlmode ==QXmlTestLogger::Complete) { QTestCharBuffer quotedTc;QTest::qt_asprintf(&buf,"<?xml version=\"1.0\"encoding=\"UTF-8\"?>\n");outputString(buf.constData());if(xmlQuote(&quotedTc,QTestResult::currentTestObjectName())) {QTest::qt_asprintf(&buf,"<TestCase name=\"%s\">\n", quotedTc.constData());outputString(buf.constData());}else{// Unconditional end-tag => omitting the start tag is bad.Q_ASSERT_X(false,"QXmlTestLogger::startLogging","Insanely long test-case name or OOM issue");}} QTestCharBuffer quotedBuild;if(!QLibraryInfo::build() ||xmlQuote(&quotedBuild,QLibraryInfo::build())) {QTest::qt_asprintf(&buf," <Environment>\n"" <QtVersion>%s</QtVersion>\n"" <QtBuild>%s</QtBuild>\n"" <QTestVersion>" QTEST_VERSION_STR "</QTestVersion>\n"" </Environment>\n",qVersion(), quotedBuild.constData());outputString(buf.constData());}}voidQXmlTestLogger::stopLogging(){ QTestCharBuffer buf;QTest::qt_asprintf(&buf," <Duration msecs=\"%s\"/>\n",QString::number(QTestLog::msecsTotalTime()).toUtf8().constData());outputString(buf.constData());if(xmlmode ==QXmlTestLogger::Complete)outputString("</TestCase>\n");QAbstractTestLogger::stopLogging();}voidQXmlTestLogger::enterTestFunction(const char*function){ QTestCharBuffer quotedFunction;if(xmlQuote(&quotedFunction, function)) { QTestCharBuffer buf;QTest::qt_asprintf(&buf," <TestFunction name=\"%s\">\n", quotedFunction.constData());outputString(buf.constData());}else{// Unconditional end-tag => omitting the start tag is bad.Q_ASSERT_X(false,"QXmlTestLogger::enterTestFunction","Insanely long test-function name or OOM issue");}}voidQXmlTestLogger::leaveTestFunction(){ QTestCharBuffer buf;QTest::qt_asprintf(&buf," <Duration msecs=\"%s\"/>\n"" </TestFunction>\n",QString::number(QTestLog::msecsFunctionTime()).toUtf8().constData());outputString(buf.constData());}namespace QTest {inlinestatic boolisEmpty(const char*str){return!str || !str[0];}static const char*incidentFormatString(bool noDescription,bool noTag){if(noDescription) {return noTag ?" <Incident type=\"%s\"file=\"%s\"line=\"%d\"/>\n":" <Incident type=\"%s\"file=\"%s\"line=\"%d\">\n"" <DataTag><![CDATA[%s%s%s%s]]></DataTag>\n"" </Incident>\n";}return noTag ?" <Incident type=\"%s\"file=\"%s\"line=\"%d\">\n"" <Description><![CDATA[%s%s%s%s]]></Description>\n"" </Incident>\n":" <Incident type=\"%s\"file=\"%s\"line=\"%d\">\n"" <DataTag><![CDATA[%s%s%s]]></DataTag>\n"" <Description><![CDATA[%s]]></Description>\n"" </Incident>\n";}static const char*benchmarkResultFormatString(){return" <BenchmarkResult metric=\"%s\"tag=\"%s\"value=\"%.6g\"iterations=\"%d\"/>\n";}static const char*messageFormatString(bool noDescription,bool noTag){if(noDescription) {if(noTag)return" <Message type=\"%s\"file=\"%s\"line=\"%d\"/>\n";elsereturn" <Message type=\"%s\"file=\"%s\"line=\"%d\">\n"" <DataTag><![CDATA[%s%s%s%s]]></DataTag>\n"" </Message>\n";}else{if(noTag)return" <Message type=\"%s\"file=\"%s\"line=\"%d\">\n"" <Description><![CDATA[%s%s%s%s]]></Description>\n"" </Message>\n";elsereturn" <Message type=\"%s\"file=\"%s\"line=\"%d\">\n"" <DataTag><![CDATA[%s%s%s]]></DataTag>\n"" <Description><![CDATA[%s]]></Description>\n"" </Message>\n";}}}// namespacevoidQXmlTestLogger::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;if(xmlQuote(&quotedFile, 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());}}voidQXmlTestLogger::addBenchmarkResult(const QBenchmarkResult &result){ QTestCharBuffer quotedMetric; QTestCharBuffer quotedTag;if(xmlQuote(&quotedMetric,benchmarkMetricName(result.measurement.metric))&&xmlQuote(&quotedTag, result.context.tag.toUtf8().constData())) { QTestCharBuffer buf;QTest::qt_asprintf(&buf,QTest::benchmarkResultFormatString(), quotedMetric.constData(), quotedTag.constData(), result.measurement.value /double(result.iterations), result.iterations);outputString(buf.constData());}}voidQXmlTestLogger::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;if(xmlQuote(&quotedFile, 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());}}intQXmlTestLogger::xmlQuote(QTestCharBuffer *destBuf,char const*src, qsizetype n){// QTestCharBuffer initially has size 512, with '\0' at the start of its// data; and we only grow it.Q_ASSERT(n >=512&& destBuf->size() == n);char*dest = destBuf->data();if(!src || !*src) {Q_ASSERT(!dest[0]);return0;}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('>',"&gt;");MAP_ENTITY('<',"&lt;");MAP_ENTITY('\'',"&apos;");MAP_ENTITY('"',"&quot;");MAP_ENTITY('&',"&amp;");// Not strictly necessary, but allows handling of comments without// having to explicitly look for `--'MAP_ENTITY('-',"&#x002D;");#undef MAP_ENTITYcase'\0':*dest ='\0';return dest - begin;default:*dest = *src;++dest;++src;break;}}// If we get here, dest was completely filled:Q_ASSERT(dest == end && end > begin); dest[-1] ='\0';// hygiene, but it'll be ignoredreturn n;}intQXmlTestLogger::xmlCdata(QTestCharBuffer *destBuf,char const*src, qsizetype n){Q_ASSERT(n >=512&& destBuf->size() == n);char*dest = destBuf->data();if(!src || !*src) {Q_ASSERT(!dest[0]);return0;}static char const CDATA_END[] ="]]>";static char const CDATA_END_ESCAPED[] ="]]]><![CDATA[]>";const size_t CDATA_END_LEN =sizeof(CDATA_END) -1;char*begin = dest;char*end = dest + n;while(dest < end) {if(!*src) {*dest ='\0';return dest - begin;}if(!strncmp(src, CDATA_END, CDATA_END_LEN)) {if(dest +sizeof(CDATA_END_ESCAPED) < end) {strcpy(dest, CDATA_END_ESCAPED); src += CDATA_END_LEN; 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; caller shall grow and retry:Q_ASSERT(dest == end && end > begin); dest[-1] ='\0';// hygiene, but it'll be ignoredreturn n;}typedefint(*StringFormatFunction)(QTestCharBuffer *,char const*, qsizetype);/* A wrapper for string functions written to work with a fixed size buffer so they can be called with a dynamically allocated buffer.*/static boolallocateStringFn(QTestCharBuffer *str,char const*src, StringFormatFunction func){constexprint MAXSIZE =1024*1024*2;int size = str->size();Q_ASSERT(size >=512&& !str->data()[0]);do{const int res =func(str, src, size);if(res < size) {// SuccessQ_ASSERT(res >0|| (!res && (!src || !src[0])));return true;}// Buffer wasn't big enough, try again, if not too big: size *=2;}while(size <= MAXSIZE && str->reset(size));return false;}/* Copy from \a src into \a destBuf, escaping any special XML characters as necessary so that destBuf is suitable for use in an XML quoted attribute string. Expands \a destBuf as needed to make room, up to a size of 2 MiB. Input requiring more than that much space for output is considered invalid. Returns 0 on invalid or empty input, the actual length written on success.*/boolQXmlTestLogger::xmlQuote(QTestCharBuffer *str,char const*src){returnallocateStringFn(str, src,QXmlTestLogger::xmlQuote);}/* Copy from \a src into \a destBuf, escaping any special strings such that destBuf is suitable for use in an XML CDATA section. Expands \a destBuf as needed to make room, up to a size of 2 MiB. Input requiring more than that much space for output is considered invalid. Returns 0 on invalid or empty input, the actual length written on success.*/boolQXmlTestLogger::xmlCdata(QTestCharBuffer *str,char const*src){returnallocateStringFn(str, src,QXmlTestLogger::xmlCdata);} QT_END_NAMESPACE 
close