summaryrefslogtreecommitdiffstats
path: root/src/testlib/qjunittestlogger.cpp
blob: ef4f1561c157fcb4390fcaee21d296a8e9e025ab (plain)
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294
// 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 <QtTest/private/qjunittestlogger_p.h>#include <QtTest/private/qtestelement_p.h>#include <QtTest/private/qtestjunitstreamer_p.h>#include <QtTest/qtestcase.h>#include <QtTest/private/qtestresult_p.h>#include <QtTest/private/qbenchmark_p.h>#include <QtTest/private/qtestlog_p.h>#include <QtCore/qelapsedtimer.h>#include <QtCore/qlibraryinfo.h>#include <cstdio>#include <string.h> QT_BEGIN_NAMESPACE /*! \internal \class QJUnitTestLogger \inmodule QtTest QJUnitTestLogger implements logging in a JUnit-compatible XML format. The \l{JUnit XML} format was originally developed for Java testing. It is supported by \l{Test Center}.*/// QTBUG-95424 links to further useful documentation.QJUnitTestLogger::QJUnitTestLogger(const char*filename):QAbstractTestLogger(filename){}QJUnitTestLogger::~QJUnitTestLogger(){Q_ASSERT(!currentTestSuite);delete logFormatter;}// We track test timing per test case, so we// need to maintain our own elapsed timer. Q_CONSTINIT static QElapsedTimer elapsedTestcaseTime;static qreal elapsedTestCaseSeconds(){return elapsedTestcaseTime.nsecsElapsed() /1e9;}static QByteArray toSecondsFormat(qreal ms){returnQByteArray::number(ms /1000,'f',3);}voidQJUnitTestLogger::startLogging(){QAbstractTestLogger::startLogging(); logFormatter =newQTestJUnitStreamer(this);Q_ASSERT(!currentTestSuite); currentTestSuite =newQTestElement(QTest::LET_TestSuite); currentTestSuite->addAttribute(QTest::AI_Name,QTestResult::currentTestObjectName());auto localTime =QDateTime::currentDateTime(); currentTestSuite->addAttribute(QTest::AI_Timestamp, localTime.toString(Qt::ISODate).toUtf8().constData()); currentTestSuite->addAttribute(QTest::AI_Hostname,QSysInfo::machineHostName().toUtf8().constData()); QTestElement *property; QTestElement *properties =newQTestElement(QTest::LET_Properties); property =newQTestElement(QTest::LET_Property); property->addAttribute(QTest::AI_Name,"QTestVersion"); property->addAttribute(QTest::AI_PropertyValue, QTEST_VERSION_STR); properties->addChild(property); property =newQTestElement(QTest::LET_Property); property->addAttribute(QTest::AI_Name,"QtVersion"); property->addAttribute(QTest::AI_PropertyValue,qVersion()); properties->addChild(property); property =newQTestElement(QTest::LET_Property); property->addAttribute(QTest::AI_Name,"QtBuild"); property->addAttribute(QTest::AI_PropertyValue,QLibraryInfo::build()); properties->addChild(property); currentTestSuite->addChild(properties); elapsedTestcaseTime.start();}voidQJUnitTestLogger::stopLogging(){char buf[10];std::snprintf(buf,sizeof(buf),"%i", testCounter); currentTestSuite->addAttribute(QTest::AI_Tests, buf);std::snprintf(buf,sizeof(buf),"%i", failureCounter); currentTestSuite->addAttribute(QTest::AI_Failures, buf);std::snprintf(buf,sizeof(buf),"%i", errorCounter); currentTestSuite->addAttribute(QTest::AI_Errors, buf);std::snprintf(buf,sizeof(buf),"%i",QTestLog::skipCount()); currentTestSuite->addAttribute(QTest::AI_Skipped, buf); currentTestSuite->addAttribute(QTest::AI_Time,toSecondsFormat(QTestLog::msecsTotalTime()).constData());for(auto*testCase : listOfTestcases) currentTestSuite->addChild(testCase); listOfTestcases.clear(); logFormatter->output(currentTestSuite);delete currentTestSuite; currentTestSuite =nullptr;QAbstractTestLogger::stopLogging();}voidQJUnitTestLogger::enterTestFunction(const char*function){enterTestCase(function);}voidQJUnitTestLogger::enterTestCase(const char*name){{ QMutexLocker locker(&mutex); currentTestCase =newQTestElement(QTest::LET_TestCase); currentTestCase->addAttribute(QTest::AI_Name, name); currentTestCase->addAttribute(QTest::AI_Classname,QTestResult::currentTestObjectName()); listOfTestcases.push_back(currentTestCase);Q_ASSERT(!systemOutputElement && !systemErrorElement); systemOutputElement =newQTestElement(QTest::LET_SystemOutput); systemErrorElement =newQTestElement(QTest::LET_SystemError);}// The element will be deleted when the suite is deleted++testCounter; elapsedTestcaseTime.start();}voidQJUnitTestLogger::enterTestData(QTestData *){ QTestCharBuffer testIdentifier;QTestPrivate::generateTestIdentifier(&testIdentifier,QTestPrivate::TestFunction |QTestPrivate::TestDataTag);static const char*lastTestFunction =nullptr;if(QTestResult::currentTestFunction() != lastTestFunction) {// Adopt existing testcase for the initial test dataauto*name =const_cast<QTestElementAttribute*>( currentTestCase->attribute(QTest::AI_Name)); name->setPair(QTest::AI_Name, testIdentifier.data()); lastTestFunction =QTestResult::currentTestFunction(); elapsedTestcaseTime.start();}else{// Create new test cases for remaining test dataleaveTestCase();enterTestCase(testIdentifier.data());}}voidQJUnitTestLogger::leaveTestFunction(){leaveTestCase();}voidQJUnitTestLogger::leaveTestCase(){ QMutexLocker locker(&mutex); currentTestCase->addAttribute(QTest::AI_Time,toSecondsFormat(elapsedTestCaseSeconds() *1000).constData());if(!systemOutputElement->childElements().empty()) currentTestCase->addChild(systemOutputElement);elsedelete systemOutputElement;if(!systemErrorElement->childElements().empty()) currentTestCase->addChild(systemErrorElement);elsedelete systemErrorElement; systemOutputElement =nullptr; systemErrorElement =nullptr;}voidQJUnitTestLogger::addIncident(IncidentTypes type,const char*description,const char*file,int line){if(type == Fail || type == XPass) {auto failureType = [&]() {switch(type) {caseQAbstractTestLogger::Fail:return"fail";caseQAbstractTestLogger::XPass:return"xpass";default:Q_UNREACHABLE();}}();addFailure(QTest::LET_Failure, failureType,QString::fromUtf8(description));}else if(type == XFail) {// Since XFAIL does not add a failure to the testlog in JUnit XML we add a// message, so we still have some information about the expected failure.addMessage(Info,QString::fromUtf8(description), file, line);}else if(type == Skip) {auto skippedElement =newQTestElement(QTest::LET_Skipped); skippedElement->addAttribute(QTest::AI_Message, description); currentTestCase->addChild(skippedElement);}}voidQJUnitTestLogger::addFailure(QTest::LogElementType elementType,const char*failureType,const QString &failureDescription){if(elementType ==QTest::LET_Failure) {// Make sure we're not adding failure when we already have error,// or adding additional failures when we already have a failure.for(auto*childElement : currentTestCase->childElements()) {if(childElement->elementType() ==QTest::LET_Error || childElement->elementType() ==QTest::LET_Failure)return;}} QTestElement *failureElement =newQTestElement(elementType); failureElement->addAttribute(QTest::AI_Type, failureType);// Assume the first line is the message, and the remainder are details QString message = failureDescription.section(u'\n',0,0); QString details = failureDescription.section(u'\n',1); failureElement->addAttribute(QTest::AI_Message, message.toUtf8().constData());if(!details.isEmpty()) {auto textNode =newQTestElement(QTest::LET_Text); textNode->addAttribute(QTest::AI_Value, details.toUtf8().constData()); failureElement->addChild(textNode);} currentTestCase->addChild(failureElement);switch(elementType) {caseQTest::LET_Failure: ++failureCounter;break;caseQTest::LET_Error: ++errorCounter;break;default:Q_UNREACHABLE();}}voidQJUnitTestLogger::addMessage(MessageTypes type,const QString &message,const char*file,int line){Q_UNUSED(file);Q_UNUSED(line); QMutexLocker locker(&mutex);if(type == QFatal) {addFailure(QTest::LET_Error,"qfatal", message);return;}auto systemLogElement = [&]() {switch(type) {caseQAbstractTestLogger::QDebug:caseQAbstractTestLogger::Info:caseQAbstractTestLogger::QInfo:return systemOutputElement;caseQAbstractTestLogger::Warn:caseQAbstractTestLogger::QWarning:caseQAbstractTestLogger::QCritical:return systemErrorElement;default:Q_UNREACHABLE();}}();if(!systemLogElement)return;// FIXME: Handle messages outside of test functionsauto textNode =newQTestElement(QTest::LET_Text); textNode->addAttribute(QTest::AI_Value, message.toUtf8().constData()); systemLogElement->addChild(textNode);} QT_END_NAMESPACE 
close