/**************************************************************************** ** ** Copyright (C) 2015 The Qt Company Ltd ** All rights reserved. ** For any questions to The Qt Company, please use contact form at ** http://www.qt.io/contact-us ** ** This file is part of the Qt Creator Enterprise Auto Test Add-on. ** ** Licensees holding valid Qt Enterprise licenses may use this file in ** accordance with the Qt Enterprise License Agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. ** ** If you have questions regarding the use of this file, please use ** contact form at http://www.qt.io/contact-us ** ****************************************************************************/ #include "testxmloutputreader.h" #include "testresult.h" #include #include #include #include #include namespace Autotest { namespace Internal { static QString decode(const QString& original) { QString result(original); static QRegExp regex(QLatin1String("&#((x[0-9A-F]+)|([0-9]+));"), Qt::CaseInsensitive); regex.setMinimal(true); int pos = 0; while ((pos = regex.indexIn(original, pos)) != -1) { const QString value = regex.cap(1); if (value.startsWith(QLatin1Char('x'))) result.replace(regex.cap(0), QChar(value.mid(1).toInt(0, 16))); else result.replace(regex.cap(0), QChar(value.toInt(0, 10))); pos += regex.matchedLength(); } return result; } static QString constructSourceFilePath(const QString &path, const QString &filePath, const QString &app) { if (Utils::HostOsInfo::isMacHost() && !app.isEmpty()) { const QString fileName(QFileInfo(app).fileName()); return QFileInfo(path.left(path.lastIndexOf(fileName + QLatin1String(".app"))), filePath) .canonicalFilePath(); } return QFileInfo(path, filePath).canonicalFilePath(); } static bool xmlStartsWith(const QString &code, const QString &start, QString &result) { if (code.startsWith(start)) { result = code.mid(start.length()); result = result.left(result.indexOf(QLatin1Char('"'))); result = result.left(result.indexOf(QLatin1String(""), index) - index); return !result.isEmpty(); } return false; } static bool xmlExtractTypeFileLine(const QString &code, const QString &tagStart, Result::Type &result, QString &file, int &line) { if (code.startsWith(tagStart)) { int start = code.indexOf(QLatin1String(" type=\"")) + 7; result = TestResult::resultFromString( code.mid(start, code.indexOf(QLatin1Char('"'), start) - start)); start = code.indexOf(QLatin1String(" file=\"")) + 7; file = decode(code.mid(start, code.indexOf(QLatin1Char('"'), start) - start)); start = code.indexOf(QLatin1String(" line=\"")) + 7; line = code.mid(start, code.indexOf(QLatin1Char('"'), start) - start).toInt(); return true; } return false; } // adapted from qplaintestlogger.cpp static QString formatResult(double value) { //NAN is not supported with visual studio 2010 if (value < 0)// || value == NAN) return QLatin1String("NAN"); if (value == 0) return QLatin1String("0"); int significantDigits = 0; qreal divisor = 1; while (value / divisor >= 1) { divisor *= 10; ++significantDigits; } QString beforeDecimalPoint = QString::number(value, 'f', 0); QString afterDecimalPoint = QString::number(value, 'f', 20); afterDecimalPoint.remove(0, beforeDecimalPoint.count() + 1); const int beforeUse = qMin(beforeDecimalPoint.count(), significantDigits); const int beforeRemove = beforeDecimalPoint.count() - beforeUse; beforeDecimalPoint.chop(beforeRemove); for (int i = 0; i < beforeRemove; ++i) beforeDecimalPoint.append(QLatin1Char('0')); int afterUse = significantDigits - beforeUse; if (beforeDecimalPoint == QLatin1String("0") && !afterDecimalPoint.isEmpty()) { ++afterUse; int i = 0; while (i < afterDecimalPoint.count() && afterDecimalPoint.at(i) == QLatin1Char('0')) ++i; afterUse += i; } const int afterRemove = afterDecimalPoint.count() - afterUse; afterDecimalPoint.chop(afterRemove); QString result = beforeDecimalPoint; if (afterUse > 0) result.append(QLatin1Char('.')); result += afterDecimalPoint; return result; } static bool xmlExtractBenchmarkInformation(const QString &code, const QString &tagStart, QString &description) { if (code.startsWith(tagStart)) { int start = code.indexOf(QLatin1String(" metric=\"")) + 9; const QString metric = code.mid(start, code.indexOf(QLatin1Char('"'), start) - start); start = code.indexOf(QLatin1String(" value=\"")) + 8; const double value = code.mid(start, code.indexOf(QLatin1Char('"'), start) - start).toDouble(); start = code.indexOf(QLatin1String(" iterations=\"")) + 13; const int iterations = code.mid(start, code.indexOf(QLatin1Char('"'), start) - start).toInt(); QString metricsText; if (metric == QLatin1String("WalltimeMilliseconds")) // default metricsText = QLatin1String("msecs"); else if (metric == QLatin1String("CPUTicks")) // -tickcounter metricsText = QLatin1String("CPU ticks"); else if (metric == QLatin1String("Events")) // -eventcounter metricsText = QLatin1String("events"); else if (metric == QLatin1String("InstructionReads")) // -callgrind metricsText = QLatin1String("instruction reads"); else if (metric == QLatin1String("CPUCycles")) // -perf metricsText = QLatin1String("CPU cycles"); description = QObject::tr("%1 %2 per iteration (total: %3, iterations: %4)") .arg(formatResult(value)) .arg(metricsText) .arg(formatResult(value * (double)iterations)) .arg(iterations); return true; } return false; } TestXmlOutputReader::TestXmlOutputReader(QProcess *testApplication) :m_testApplication(testApplication) { connect(m_testApplication, &QProcess::readyReadStandardOutput, this, &TestXmlOutputReader::processOutput); } TestXmlOutputReader::~TestXmlOutputReader() { } void TestXmlOutputReader::processOutput() { if (!m_testApplication || m_testApplication->state() != QProcess::Running) return; static QString className; static QString testCase; static QString dataTag; static Result::Type result = Result::UNKNOWN; static QString description; static QString file; static int lineNumber = 0; static QString duration; static bool readingDescription = false; static QString qtVersion; static QString qtestVersion; static QString benchmarkDescription; while (m_testApplication->canReadLine()) { // TODO Qt5 uses UTF-8 - while Qt4 uses ISO-8859-1 - could this be a problem? const QString line = QString::fromUtf8(m_testApplication->readLine()).trimmed(); if (line.isEmpty() || xmlStartsWith(line, QLatin1String(""), dataTag)) continue; if (xmlCData(line, QLatin1String(""), description)) { if (!line.endsWith(QLatin1String(""))) readingDescription = true; continue; } if (xmlExtractTypeFileLine(line, QLatin1String(""))) { TestResult testResult(className, testCase, dataTag, result, description); if (!file.isEmpty()) file = constructSourceFilePath(m_testApplication->workingDirectory(), file, m_testApplication->program()); testResult.setFileName(file); testResult.setLine(lineNumber); testResultCreated(testResult); } continue; } if (xmlExtractBenchmarkInformation(line, QLatin1String("") || line == QLatin1String("")) { TestResult testResult(className, testCase, dataTag, result, description); if (!file.isEmpty()) file = constructSourceFilePath(m_testApplication->workingDirectory(), file, m_testApplication->program()); testResult.setFileName(file); testResult.setLine(lineNumber); testResultCreated(testResult); description = QString(); } else if (line == QLatin1String("") && !duration.isEmpty()) { TestResult testResult(className, testCase, QString(), Result::MESSAGE_INTERNAL, QObject::tr("Execution took %1 ms.").arg(duration)); testResultCreated(testResult); emit increaseProgress(); } else if (line == QLatin1String("") && !duration.isEmpty()) { TestResult testResult(className, QString(), QString(), Result::MESSAGE_INTERNAL, QObject::tr("Test execution took %1 ms.").arg(duration)); testResultCreated(testResult); } else if (readingDescription) { if (line.endsWith(QLatin1String("]]>"))) { description.append(QLatin1Char('\n')); description.append(line.left(line.indexOf(QLatin1String("]]>")))); readingDescription = false; } else { description.append(QLatin1Char('\n')); description.append(line); } } else if (xmlStartsWith(line, QLatin1String(""), qtVersion)) { testResultCreated(FaultyTestResult(Result::MESSAGE_INTERNAL, QObject::tr("Qt version: %1").arg(qtVersion))); } else if (xmlStartsWith(line, QLatin1String(""), qtestVersion)) { testResultCreated(FaultyTestResult(Result::MESSAGE_INTERNAL, QObject::tr("QTest version: %1").arg(qtestVersion))); } else { // qDebug() << "Unhandled line:" << line; // TODO remove } } } } // namespace Internal } // namespace Autotest