diff --git a/plugins/autotest/autotest.pro b/plugins/autotest/autotest.pro index f360aac5b8e..45ab5582909 100644 --- a/plugins/autotest/autotest.pro +++ b/plugins/autotest/autotest.pro @@ -27,7 +27,7 @@ SOURCES += \ testsettings.cpp \ testsettingspage.cpp \ testnavigationwidget.cpp \ - testxmloutputreader.cpp + testoutputreader.cpp HEADERS += \ testtreeview.h \ @@ -50,7 +50,7 @@ HEADERS += \ testsettings.h \ testsettingspage.h \ testnavigationwidget.h \ - testxmloutputreader.h \ + testoutputreader.h \ autotesticons.h RESOURCES += \ diff --git a/plugins/autotest/autotest.qbs b/plugins/autotest/autotest.qbs index b5696184e7e..17b94e0c45e 100644 --- a/plugins/autotest/autotest.qbs +++ b/plugins/autotest/autotest.qbs @@ -69,8 +69,8 @@ QtcCommercialPlugin { "testtreeview.h", "testvisitor.cpp", "testvisitor.h", - "testxmloutputreader.cpp", - "testxmloutputreader.h", + "testoutputreader.cpp", + "testoutputreader.h", ] Group { diff --git a/plugins/autotest/testcodeparser.cpp b/plugins/autotest/testcodeparser.cpp index 1bc78f25b3a..dea7d886be4 100644 --- a/plugins/autotest/testcodeparser.cpp +++ b/plugins/autotest/testcodeparser.cpp @@ -460,6 +460,7 @@ static TestTreeItem *constructTestTreeItem(const QString &fileName, } static TestTreeItem *constructGTestTreeItem(const QString &filePath, const QString &caseName, + const QString &proFile, const TestCodeLocationList &testNames) { TestTreeItem *item = new TestTreeItem(caseName, QString(), TestTreeItem::GTestCase); @@ -468,6 +469,7 @@ static TestTreeItem *constructGTestTreeItem(const QString &filePath, const QStri locationAndType.m_type); treeItemChild->setLine(locationAndType.m_line); treeItemChild->setColumn(locationAndType.m_column); + treeItemChild->setMainFile(proFile); item->appendChild(treeItemChild); } return item; @@ -1030,7 +1032,7 @@ void TestCodeParser::updateGTests(const CPlusPlus::Document::Ptr &doc, proFile = ppList.at(0)->projectFile; foreach (const QString &testName, tests.keys()) { - TestTreeItem *item = constructGTestTreeItem(fileName, testName, tests.value(testName)); + TestTreeItem *item = constructGTestTreeItem(fileName, testName, proFile, tests.value(testName)); TestInfo info(item->name(), item->getChildNames(), doc->revision(), doc->editorRevision()); info.setProfile(proFile); foreach (const TestCodeLocationAndType &testSet, tests.value(testName)) { diff --git a/plugins/autotest/testconfiguration.cpp b/plugins/autotest/testconfiguration.cpp index 4b294f47820..3cf60556586 100644 --- a/plugins/autotest/testconfiguration.cpp +++ b/plugins/autotest/testconfiguration.cpp @@ -42,7 +42,8 @@ TestConfiguration::TestConfiguration(const QString &testClass, const QStringList m_testCaseCount(testCaseCount), m_unnamedOnly(false), m_project(0), - m_guessedConfiguration(false) + m_guessedConfiguration(false), + m_type(Qt) { if (testCases.size() != 0) m_testCaseCount = testCases.size(); @@ -71,6 +72,21 @@ void basicProjectInformation(Project *project, const QString &mainFilePath, QStr } } +void basicProjectInformation(Project *project, const QString &proFile, QString *displayName, + Project **targetProject) +{ + CppTools::CppModelManager *cppMM = CppTools::CppModelManager::instance(); + QList projParts = cppMM->projectInfo(project).projectParts(); + + foreach (const CppTools::ProjectPart::Ptr &part, projParts) { + if (part->projectFile == proFile) { + *displayName = part->displayName; + *targetProject = part->project; + return; + } + } +} + void extractEnvironmentInformation(LocalApplicationRunConfiguration *localRunConfiguration, QString *workDir, Utils::Environment *env) { @@ -81,7 +97,7 @@ void extractEnvironmentInformation(LocalApplicationRunConfiguration *localRunCon void TestConfiguration::completeTestInformation() { - QTC_ASSERT(!m_mainFilePath.isEmpty(), return); + QTC_ASSERT(!m_mainFilePath.isEmpty() || !m_proFile.isEmpty(), return); typedef LocalApplicationRunConfiguration LocalRunConfig; @@ -92,7 +108,7 @@ void TestConfiguration::completeTestInformation() QString targetFile; QString targetName; QString workDir; - QString proFile; + QString proFile = m_proFile; QString displayName; Project *targetProject = 0; Utils::Environment env; @@ -100,7 +116,10 @@ void TestConfiguration::completeTestInformation() bool guessedRunConfiguration = false; setProject(0); - basicProjectInformation(project, m_mainFilePath, &proFile, &displayName, &targetProject); + if (m_proFile.isEmpty()) + basicProjectInformation(project, m_mainFilePath, &proFile, &displayName, &targetProject); + else + basicProjectInformation(project, proFile, &displayName, &targetProject); Target *target = project->activeTarget(); if (!target) @@ -220,5 +239,10 @@ void TestConfiguration::setGuessedConfiguration(bool guessed) m_guessedConfiguration = guessed; } +void TestConfiguration::setTestType(TestType type) +{ + m_type = type; +} + } // namespace Internal } // namespace Autotest diff --git a/plugins/autotest/testconfiguration.h b/plugins/autotest/testconfiguration.h index e1d3bc000e6..1b2d34e0873 100644 --- a/plugins/autotest/testconfiguration.h +++ b/plugins/autotest/testconfiguration.h @@ -37,6 +37,11 @@ class TestConfiguration : public QObject { Q_OBJECT public: + enum TestType { + Qt, + GTest + }; + explicit TestConfiguration(const QString &testClass, const QStringList &testCases, int testCaseCount = 0, QObject *parent = 0); ~TestConfiguration(); @@ -55,6 +60,7 @@ public: void setProject(ProjectExplorer::Project *project); void setUnnamedOnly(bool unnamedOnly); void setGuessedConfiguration(bool guessed); + void setTestType(TestType type); QString testClass() const { return m_testClass; } QStringList testCases() const { return m_testCases; } @@ -68,6 +74,7 @@ public: ProjectExplorer::Project *project() const { return m_project; } bool unnamedOnly() const { return m_unnamedOnly; } bool guessedConfiguration() const { return m_guessedConfiguration; } + TestType testType() const { return m_type; } private: QString m_testClass; @@ -83,6 +90,7 @@ private: Utils::Environment m_environment; ProjectExplorer::Project *m_project; bool m_guessedConfiguration; + TestType m_type; }; } // namespace Internal diff --git a/plugins/autotest/testxmloutputreader.cpp b/plugins/autotest/testoutputreader.cpp similarity index 70% rename from plugins/autotest/testxmloutputreader.cpp rename to plugins/autotest/testoutputreader.cpp index d65beee864e..a6aa10d3b34 100644 --- a/plugins/autotest/testxmloutputreader.cpp +++ b/plugins/autotest/testoutputreader.cpp @@ -17,7 +17,7 @@ ** ****************************************************************************/ -#include "testxmloutputreader.h" +#include "testoutputreader.h" #include "testresult.h" #include @@ -28,6 +28,7 @@ #include #include #include +#include namespace Autotest { namespace Internal { @@ -130,11 +131,10 @@ static QString constructBenchmarkInformation(const QString &metric, double value .arg(iterations); } -TestXmlOutputReader::TestXmlOutputReader(QProcess *testApplication) - :m_testApplication(testApplication) +TestOutputReader::TestOutputReader(QProcess *testApplication, OutputType type) + : m_testApplication(testApplication) + , m_type(type) { - connect(m_testApplication, &QProcess::readyReadStandardOutput, - this, &TestXmlOutputReader::processOutput); } enum CDATAMode { @@ -146,7 +146,7 @@ enum CDATAMode { QTestVersion }; -void TestXmlOutputReader::processOutput() +void TestOutputReader::processOutput() { if (!m_testApplication || m_testApplication->state() != QProcess::Running) return; @@ -306,5 +306,110 @@ void TestXmlOutputReader::processOutput() } } +void TestOutputReader::processGTestOutput() +{ + if (!m_testApplication || m_testApplication->state() != QProcess::Running) + return; + + static QRegExp newTestStarts(QStringLiteral("^\\[-{10}\\] \\d+ tests? from (.*)$")); + static QRegExp testEnds(QStringLiteral("^\\[-{10}\\] \\d+ tests? from (.*) \\((.*)\\)$")); + static QRegExp newTestSetStarts(QStringLiteral("^\\[ RUN \\] (.*)$")); + static QRegExp testSetSuccess(QStringLiteral("^\\[ OK \\] (.*) \\((.*)\\)$")); + static QRegExp testSetFail(QStringLiteral("^\\\[ FAILED \\] (.*) \\((.*)\\)$")); + static QRegExp disabledTests(QStringLiteral("^ YOU HAVE (\\d+) DISABLED TESTS?$")); + + static QString currentTestName; + static QString currentTestSet; + static QString description; + static QByteArray unprocessed; + + while (m_testApplication->canReadLine()) + unprocessed.append(m_testApplication->readLine()); + + int lineBreak; + while ((lineBreak = unprocessed.indexOf('\n')) != -1) { + const QString line = QLatin1String(unprocessed.left(lineBreak)); + unprocessed.remove(0, lineBreak + 1); + if (line.isEmpty()) { + continue; + } + if (!line.startsWith(QLatin1Char('['))) { + description.append(line).append(QLatin1Char('\n')); + if (line.startsWith(QStringLiteral("Note:"))) { + auto testResult = new TestResult(); + testResult->setResult(Result::MessageInternal); + testResult->setDescription(line); + testResultCreated(testResult); + description.clear(); + } else if (disabledTests.exactMatch(line)) { + auto testResult = new TestResult(); + testResult->setResult(Result::MessageInternal); + int disabled = disabledTests.cap(1).toInt(); + testResult->setDescription(tr("You have %n disabled test(s).", 0, disabled)); + testResultCreated(testResult); + description.clear(); + } + continue; + } + + if (testEnds.exactMatch(line)) { + auto testResult = new TestResult(currentTestName); + testResult->setTestCase(currentTestSet); + testResult->setResult(Result::MessageTestCaseEnd); + testResult->setDescription(tr("Test execution took %1").arg(testEnds.cap(2))); + testResultCreated(testResult); + currentTestName.clear(); + currentTestSet.clear(); + } else if (newTestStarts.exactMatch(line)) { + currentTestName = newTestStarts.cap(1); + auto testResult = new TestResult(currentTestName); + testResult->setResult(Result::MessageTestCaseStart); + testResult->setDescription(tr("Executing test case %1").arg(currentTestName)); + testResultCreated(testResult); + } else if (newTestSetStarts.exactMatch(line)) { + currentTestSet = newTestSetStarts.cap(1); + auto testResult = new TestResult(); + testResult->setResult(Result::MessageCurrentTest); + testResult->setDescription(tr("Entering test set %1").arg(currentTestSet)); + testResultCreated(testResult); + } else if (testSetSuccess.exactMatch(line)) { + auto testResult = new TestResult(currentTestName); + testResult->setTestCase(currentTestSet); + testResult->setResult(Result::Pass); + testResultCreated(testResult); + testResult = new TestResult(currentTestName); + testResult->setTestCase(currentTestSet); + testResult->setResult(Result::MessageInternal); + testResult->setDescription(tr("Execution took %1.").arg(testSetSuccess.cap(2))); + testResultCreated(testResult); + emit increaseProgress(); + } else if (testSetFail.exactMatch(line)) { + auto testResult = new TestResult(currentTestName); + testResult->setTestCase(currentTestSet); + testResult->setResult(Result::Fail); + description.chop(1); + testResult->setDescription(description); + int firstColon = description.indexOf(QLatin1Char(':')); + if (firstColon != -1) { + int secondColon = description.indexOf(QLatin1Char(':'), firstColon + 1); + QString file = constructSourceFilePath(m_testApplication->workingDirectory(), + description.left(firstColon), + m_testApplication->program()); + QString line = description.mid(firstColon + 1, secondColon - firstColon - 1); + testResult->setFileName(file); + testResult->setLine(line.toInt()); + } + testResultCreated(testResult); + description.clear(); + testResult = new TestResult(currentTestName); + testResult->setTestCase(currentTestSet); + testResult->setResult(Result::MessageInternal); + testResult->setDescription(tr("Execution took %1.").arg(testSetFail.cap(2))); + testResultCreated(testResult); + emit increaseProgress(); + } + } +} + } // namespace Internal } // namespace Autotest diff --git a/plugins/autotest/testxmloutputreader.h b/plugins/autotest/testoutputreader.h similarity index 83% rename from plugins/autotest/testxmloutputreader.h rename to plugins/autotest/testoutputreader.h index feb99c46b21..532e081a03f 100644 --- a/plugins/autotest/testxmloutputreader.h +++ b/plugins/autotest/testoutputreader.h @@ -24,7 +24,6 @@ #include #include -#include QT_BEGIN_NAMESPACE class QProcess; @@ -33,14 +32,20 @@ QT_END_NAMESPACE namespace Autotest { namespace Internal { -class TestXmlOutputReader : public QObject +class TestOutputReader : public QObject { Q_OBJECT public: - TestXmlOutputReader(QProcess *testApplication); + enum OutputType { + Qt, + GTest + }; + + TestOutputReader(QProcess *testApplication, OutputType type = Qt); public slots: void processOutput(); + void processGTestOutput(); signals: void testResultCreated(TestResult *testResult); @@ -48,9 +53,10 @@ signals: private: QProcess *m_testApplication; // not owned + OutputType m_type; }; } // namespace Internal } // namespace Autotest -#endif // TESTXMLOUTPUTREADER_H +#endif // TESTOUTPUTREADER_H diff --git a/plugins/autotest/testresultmodel.cpp b/plugins/autotest/testresultmodel.cpp index 65afcdd5e8e..8ab0aa8a14c 100644 --- a/plugins/autotest/testresultmodel.cpp +++ b/plugins/autotest/testresultmodel.cpp @@ -160,18 +160,20 @@ void TestResultModel::addTestResult(TestResult *testResult, bool autoExpand) TestResultItem *newItem = new TestResultItem(testResult); // FIXME this might be totally wrong... we need some more unique information! - for (int row = lastRow; row >= 0; --row) { - TestResultItem *current = static_cast(topLevelItems.at(row)); - const TestResult *result = current->testResult(); - if (result && result->className() == testResult->className()) { - current->appendChild(newItem); - if (autoExpand) - current->expand(); - if (testResult->result() == Result::MessageTestCaseEnd) { - current->updateResult(); - emit dataChanged(current->index(), current->index()); + if (!testResult->className().isEmpty()) { + for (int row = lastRow; row >= 0; --row) { + TestResultItem *current = static_cast(topLevelItems.at(row)); + const TestResult *result = current->testResult(); + if (result && result->className() == testResult->className()) { + current->appendChild(newItem); + if (autoExpand) + current->expand(); + if (testResult->result() == Result::MessageTestCaseEnd) { + current->updateResult(); + emit dataChanged(current->index(), current->index()); + } + return; } - return; } } // if we have a MessageCurrentTest present, add the new top level item before it diff --git a/plugins/autotest/testrunner.cpp b/plugins/autotest/testrunner.cpp index aef4b53a9f2..a8ceaa34612 100644 --- a/plugins/autotest/testrunner.cpp +++ b/plugins/autotest/testrunner.cpp @@ -22,7 +22,7 @@ #include "autotestplugin.h" #include "testresultspane.h" #include "testsettings.h" -#include "testxmloutputreader.h" +#include "testoutputreader.h" #include #include @@ -36,6 +36,7 @@ #include #include +#include #include namespace Autotest { @@ -109,10 +110,14 @@ void performTestRun(QFutureInterface &futureInterface, const QString metricsOption, TestRunner* testRunner) { int testCaseCount = 0; + bool hasQtTests = false; + bool hasGTests = false; foreach (TestConfiguration *config, selectedTests) { config->completeTestInformation(); if (config->project()) { testCaseCount += config->testCaseCount(); + hasQtTests |= config->testType() == TestConfiguration::Qt; + hasGTests |= config->testType() == TestConfiguration::GTest; } else { emitTestResultCreated(new FaultyTestResult(Result::MessageWarn, QObject::tr("Project is null for \"%1\". Removing from test run.\n" @@ -127,18 +132,28 @@ void performTestRun(QFutureInterface &futureInterface, futureInterface.cancel(); // this kills the process if that is still in the running loop }); - TestXmlOutputReader xmlReader(&testProcess); - QObject::connect(&xmlReader, &TestXmlOutputReader::increaseProgress, [&] () { + TestOutputReader outputReader(&testProcess); + QObject::connect(&outputReader, &TestOutputReader::increaseProgress, [&] () { futureInterface.setProgressValue(futureInterface.progressValue() + 1); }); - QObject::connect(&xmlReader, &TestXmlOutputReader::testResultCreated, &emitTestResultCreated); - - QObject::connect(&testProcess, &QProcess::readyRead, &xmlReader, &TestXmlOutputReader::processOutput); + QObject::connect(&outputReader, &TestOutputReader::testResultCreated, &emitTestResultCreated); futureInterface.setProgressRange(0, testCaseCount); futureInterface.setProgressValue(0); + QMetaObject::Connection connection; foreach (const TestConfiguration *testConfiguration, selectedTests) { + if (connection) + QObject::disconnect(connection); + + TestConfiguration::TestType testType = testConfiguration->testType(); + if (testType == TestConfiguration::Qt) { + connection = QObject::connect(&testProcess, &QProcess::readyRead, &outputReader, + &TestOutputReader::processOutput); + } else { + connection = QObject::connect(&testProcess, &QProcess::readyRead, &outputReader, + &TestOutputReader::processGTestOutput); + } if (futureInterface.isCanceled()) break; @@ -155,12 +170,14 @@ void performTestRun(QFutureInterface &futureInterface, continue; } - QStringList argumentList(QLatin1String("-xml")); - if (!metricsOption.isEmpty()) - argumentList << metricsOption; - if (testConfiguration->testCases().count()) - argumentList << testConfiguration->testCases(); - testProcess.setArguments(argumentList); + if (testType == TestConfiguration::Qt) { + QStringList argumentList(QLatin1String("-xml")); + if (!metricsOption.isEmpty()) + argumentList << metricsOption; + if (testConfiguration->testCases().count()) + argumentList << testConfiguration->testCases(); + testProcess.setArguments(argumentList); + } testProcess.setWorkingDirectory(testConfiguration->workingDirectory()); if (Utils::HostOsInfo::isWindowsHost()) diff --git a/plugins/autotest/testtreeitem.h b/plugins/autotest/testtreeitem.h index c228b325a6d..dbd9b4a8ab4 100644 --- a/plugins/autotest/testtreeitem.h +++ b/plugins/autotest/testtreeitem.h @@ -86,7 +86,7 @@ private: Type m_type; unsigned m_line; unsigned m_column; - QString m_mainFile; + QString m_mainFile; // main for Quick tests, project file for gtest }; struct TestCodeLocationAndType { diff --git a/plugins/autotest/testtreemodel.cpp b/plugins/autotest/testtreemodel.cpp index 342ef7a184a..6addc97ca19 100644 --- a/plugins/autotest/testtreemodel.cpp +++ b/plugins/autotest/testtreemodel.cpp @@ -236,6 +236,27 @@ QList TestTreeModel::getAllTestCases() const result << tc; } + foundMains.clear(); + + // get all Google Tests + for (int row = 0, count = m_googleTestRootItem->childCount(); row < count; ++row) { + const TestTreeItem *child = m_googleTestRootItem->childItem(row); + for (int childRow = 0, childCount = child->childCount(); childRow < childCount; ++childRow) { + const QString &proFilePath = child->childItem(childRow)->mainFile(); + foundMains.insert(proFilePath, foundMains.contains(proFilePath) + ? foundMains.value(proFilePath) + 1 : 1); + } + } + + foreach (const QString &proFile, foundMains.keys()) { + TestConfiguration *tc = new TestConfiguration(QString(), QStringList(), + foundMains.value(proFile)); + tc->setProFile(proFile); + tc->setProject(project); + tc->setTestType(TestConfiguration::GTest); + result << tc; + } + return result; }