From 650be0e496041a42ea78c27e6f3bd5ee01050f4c Mon Sep 17 00:00:00 2001 From: Christian Stenger Date: Wed, 9 Dec 2015 09:46:33 +0100 Subject: [PATCH] Add capability to run gtest related tests For now this only applies for 'Run All'. To be able to run only selected tests we first have to introduce the check state for gtest related items as well. Change-Id: I196b56b7fe426f846f2be0df7e21458c2733cbd1 Reviewed-by: Niels Weber --- plugins/autotest/autotest.pro | 4 +- plugins/autotest/autotest.qbs | 4 +- plugins/autotest/testcodeparser.cpp | 4 +- plugins/autotest/testconfiguration.cpp | 32 ++++- plugins/autotest/testconfiguration.h | 8 ++ ...loutputreader.cpp => testoutputreader.cpp} | 117 +++++++++++++++++- ...stxmloutputreader.h => testoutputreader.h} | 14 ++- plugins/autotest/testresultmodel.cpp | 24 ++-- plugins/autotest/testrunner.cpp | 41 ++++-- plugins/autotest/testtreeitem.h | 2 +- plugins/autotest/testtreemodel.cpp | 21 ++++ 11 files changed, 228 insertions(+), 43 deletions(-) rename plugins/autotest/{testxmloutputreader.cpp => testoutputreader.cpp} (70%) rename plugins/autotest/{testxmloutputreader.h => testoutputreader.h} (83%) 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; }