diff --git a/plugins/autotest/autotest_utils.h b/plugins/autotest/autotest_utils.h index 00a2fced864..ee3c464c6a2 100644 --- a/plugins/autotest/autotest_utils.h +++ b/plugins/autotest/autotest_utils.h @@ -36,6 +36,11 @@ public: return valid.contains(macro); } + static bool isGTestParameterized(const QString ¯o) + { + return macro == QStringLiteral("TEST_P"); + } + static bool isQTestMacro(const QByteArray ¯o) { static QByteArrayList valid = {"QTEST_MAIN", "QTEST_APPLESS_MAIN", "QTEST_GUILESS_MAIN"}; diff --git a/plugins/autotest/testcodeparser.cpp b/plugins/autotest/testcodeparser.cpp index 8ba6768078b..af48f475682 100644 --- a/plugins/autotest/testcodeparser.cpp +++ b/plugins/autotest/testcodeparser.cpp @@ -317,9 +317,8 @@ static QString quickTestName(const CPlusPlus::Document::Ptr &doc) return QString(); } -static QSet testNames(CPlusPlus::Document::Ptr &document) +static bool hasGTestNames(CPlusPlus::Document::Ptr &document) { - QSet result; foreach (const CPlusPlus::Document::MacroUse ¯o, document->macroUses()) { if (!macro.isFunctionLike()) continue; @@ -327,12 +326,10 @@ static QSet testNames(CPlusPlus::Document::Ptr &document) const QVector args = macro.arguments(); if (args.size() != 2) continue; - const CPlusPlus::Document::Block name = args.first(); - result.insert(QLatin1String(getFileContent(document->fileName()) - .mid(name.bytesBegin(), name.bytesEnd() - name.bytesBegin()))); + return true; } } - return result; + return false; } static QList scanDirectoryForQuickTestQmlFiles(const QString &srcDir) @@ -457,12 +454,14 @@ static TestTreeItem *constructTestTreeItem(const QString &fileName, return treeItem; } -static TestTreeItem *constructGTestTreeItem(const QString &filePath, const QString &caseName, +static TestTreeItem *constructGTestTreeItem(const QString &filePath, const GTestCaseSpec &caseSpec, const QString &proFile, - const TestCodeLocationList &testNames) + const TestCodeLocationList &testSets) { - TestTreeItem *item = new TestTreeItem(caseName, QString(), TestTreeItem::GTestCase); - foreach (const TestCodeLocationAndType &locationAndType, testNames) { + TestTreeItem *item = new TestTreeItem(caseSpec.testCaseName, QString(), + caseSpec.parameterized ? TestTreeItem::GTestCaseParameterized + : TestTreeItem::GTestCase); + foreach (const TestCodeLocationAndType &locationAndType, testSets) { TestTreeItem *treeItemChild = new TestTreeItem(locationAndType.m_name, filePath, locationAndType.m_type); treeItemChild->setLine(locationAndType.m_line); @@ -549,9 +548,8 @@ void TestCodeParser::checkDocumentForTestCode(CPlusPlus::Document::Ptr document) } if (includesGTest(document, modelManager)) { - const QSet &names = testNames(document); - if (!names.isEmpty()) { - handleGTest(document->fileName(), names); + if (hasGTestNames(document)) { + handleGTest(document->fileName()); return; } } @@ -609,7 +607,7 @@ void TestCodeParser::handleQtQuickTest(CPlusPlus::Document::Ptr document) } } -void TestCodeParser::handleGTest(const QString &filePath, const QSet &names) +void TestCodeParser::handleGTest(const QString &filePath) { const QByteArray &fileContent = getFileContent(filePath); const CPlusPlus::Snapshot snapshot = CPlusPlus::CppModelManagerBase::instance()->snapshot(); @@ -619,8 +617,7 @@ void TestCodeParser::handleGTest(const QString &filePath, const QSet &n GTestVisitor visitor(document); visitor.accept(ast); - QMap result = visitor.gtestFunctions(); - QTC_CHECK(names.contains(result.keys().toSet())); + QMap result = visitor.gtestFunctions(); updateGTests(document, result); } @@ -1018,7 +1015,7 @@ void TestCodeParser::updateModelAndQuickDocMap(QmlJS::Document::Ptr document, } void TestCodeParser::updateGTests(const CPlusPlus::Document::Ptr &doc, - const QMap &tests) + const QMap &tests) { const QString &fileName = doc->fileName(); removeGTestsByName(fileName); @@ -1029,12 +1026,12 @@ void TestCodeParser::updateGTests(const CPlusPlus::Document::Ptr &doc, if (ppList.size()) proFile = ppList.at(0)->projectFile; - foreach (const QString &testName, tests.keys()) { - TestTreeItem *item = constructGTestTreeItem(fileName, testName, proFile, tests.value(testName)); + foreach (const GTestCaseSpec &testSpec, tests.keys()) { + TestTreeItem *item = constructGTestTreeItem(fileName, testSpec, proFile, tests.value(testSpec)); TestInfo info(item->name(), item->getChildNames(), doc->revision(), doc->editorRevision()); info.setProFile(proFile); - foreach (const TestCodeLocationAndType &testSet, tests.value(testName)) { - GTestInfo gtestInfo(testName, testSet.m_name, fileName); + foreach (const TestCodeLocationAndType &testSet, tests.value(testSpec)) { + GTestInfo gtestInfo(testSpec.testCaseName, testSet.m_name, fileName); if (testSet.m_type == TestTreeItem::GTestNameDisabled) gtestInfo.setEnabled(false); m_gtestDocList.append(gtestInfo); diff --git a/plugins/autotest/testcodeparser.h b/plugins/autotest/testcodeparser.h index 2889b5a041a..ef2c555ba94 100644 --- a/plugins/autotest/testcodeparser.h +++ b/plugins/autotest/testcodeparser.h @@ -41,6 +41,7 @@ struct TestCodeLocationAndType; class TestInfo; class UnnamedQuickTestInfo; class GTestInfo; +struct GTestCaseSpec; class TestCodeParser : public QObject { @@ -84,7 +85,7 @@ public slots: void updateTestTree(); void checkDocumentForTestCode(CPlusPlus::Document::Ptr document); void handleQtQuickTest(CPlusPlus::Document::Ptr document); - void handleGTest(const QString &filePath, const QSet &names); + void handleGTest(const QString &filePath); void onCppDocumentUpdated(const CPlusPlus::Document::Ptr &document); void onQmlDocumentUpdated(const QmlJS::Document::Ptr &document); @@ -109,7 +110,7 @@ private: void updateModelAndQuickDocMap(QmlJS::Document::Ptr document, const QString &referencingFile, TestTreeItem *testItem); void updateGTests(const CPlusPlus::Document::Ptr &doc, - const QMap &tests); + const QMap &tests); void removeUnnamedQuickTestsByName(const QString &fileName); void removeGTestsByName(const QString &fileName); diff --git a/plugins/autotest/testnavigationwidget.cpp b/plugins/autotest/testnavigationwidget.cpp index bff1b4de8d6..914290e36b9 100644 --- a/plugins/autotest/testnavigationwidget.cpp +++ b/plugins/autotest/testnavigationwidget.cpp @@ -108,6 +108,7 @@ void TestNavigationWidget::contextMenuEvent(QContextMenuEvent *event) || (type == TestTreeItem::TestClass && index.data().toString() != unnamed) || (type == TestTreeItem::TestDataTag) || (type == TestTreeItem::GTestCase) + || (type == TestTreeItem::GTestCaseParameterized) || (type == TestTreeItem::GTestName)) { runThisTest = new QAction(tr("Run This Test"), &menu); runThisTest->setEnabled(enabled); @@ -255,6 +256,7 @@ void TestNavigationWidget::onRunThisTestTriggered() if (item->type() == TestTreeItem::TestClass || item->type() == TestTreeItem::TestFunction || item->type() == TestTreeItem::TestDataTag || item->type() == TestTreeItem::GTestCase + || item->type() == TestTreeItem::GTestCaseParameterized || item->type() == TestTreeItem::GTestName) { if (TestConfiguration *configuration = m_model->getTestConfiguration(item)) { TestRunner *runner = TestRunner::instance(); diff --git a/plugins/autotest/testtreeitem.cpp b/plugins/autotest/testtreeitem.cpp index afba068ac47..59318bda5e8 100644 --- a/plugins/autotest/testtreeitem.cpp +++ b/plugins/autotest/testtreeitem.cpp @@ -40,6 +40,7 @@ TestTreeItem::TestTreeItem(const QString &name, const QString &filePath, Type ty case TestClass: case TestFunction: case GTestCase: + case GTestCaseParameterized: case GTestName: case GTestNameDisabled: m_checked = Qt::Checked; @@ -76,7 +77,7 @@ static QIcon testTreeIcon(TestTreeItem::Type type) QIcon(QLatin1String(":/images/func.png")), QIcon(QLatin1String(":/images/data.png")) }; - if (type == TestTreeItem::GTestCase) + if (type == TestTreeItem::GTestCase || type == TestTreeItem::GTestCaseParameterized) return icons[1]; if (int(type) >= int(sizeof icons / sizeof *icons)) @@ -111,6 +112,7 @@ QVariant TestTreeItem::data(int /*column*/, int role) const return QVariant(); case TestClass: case GTestCase: + case GTestCaseParameterized: return m_name.isEmpty() ? QVariant() : checked(); case TestFunction: case GTestName: @@ -191,7 +193,8 @@ void TestTreeItem::setChecked(const Qt::CheckState checkState) break; } case TestClass: - case GTestCase: { + case GTestCase: + case GTestCaseParameterized: { Qt::CheckState usedState = (checkState == Qt::Unchecked ? Qt::Unchecked : Qt::Checked); for (int row = 0, count = childCount(); row < count; ++row) childItem(row)->setChecked(usedState); @@ -208,6 +211,7 @@ Qt::CheckState TestTreeItem::checked() const case TestClass: case TestFunction: case GTestCase: + case GTestCaseParameterized: case GTestName: case GTestNameDisabled: return m_checked; diff --git a/plugins/autotest/testtreeitem.h b/plugins/autotest/testtreeitem.h index dbd9b4a8ab4..ac4134ee36a 100644 --- a/plugins/autotest/testtreeitem.h +++ b/plugins/autotest/testtreeitem.h @@ -48,7 +48,8 @@ public: TestDataTag, TestDataFunction, TestSpecialFunction, - GTestCase, // should we distinguish between Case and Fixture? + GTestCase, + GTestCaseParameterized, GTestName, GTestNameDisabled }; diff --git a/plugins/autotest/testtreemodel.cpp b/plugins/autotest/testtreemodel.cpp index 49e2070839d..e79d2325e98 100644 --- a/plugins/autotest/testtreemodel.cpp +++ b/plugins/autotest/testtreemodel.cpp @@ -140,6 +140,7 @@ bool TestTreeModel::setData(const QModelIndex &index, const QVariant &value, int switch (item->type()) { case TestTreeItem::TestClass: case TestTreeItem::GTestCase: + case TestTreeItem::GTestCaseParameterized: if (item->childCount() > 0) emit dataChanged(index.child(0, 0), index.child(item->childCount() - 1, 0)); break; @@ -166,6 +167,7 @@ Qt::ItemFlags TestTreeModel::flags(const QModelIndex &index) const switch(item->type()) { case TestTreeItem::TestClass: case TestTreeItem::GTestCase: + case TestTreeItem::GTestCaseParameterized: if (item->name().isEmpty()) return Qt::ItemIsEnabled | Qt::ItemIsSelectable; return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsTristate | Qt::ItemIsUserCheckable; @@ -389,8 +391,14 @@ QList TestTreeModel::getSelectedTests() const const TestTreeItem *grandChild = child->childItem(grandChildRow); const QString &proFile = grandChild->mainFile(); QStringList enabled = proFilesWithEnabledTestSets.value(proFile); - if (grandChild->checked() == Qt::Checked) - enabled << child->name() + QLatin1Char('.') + grandChild->name(); + if (grandChild->checked() == Qt::Checked) { + QString testSpecifier = child->name() + QLatin1Char('.') + grandChild->name(); + if (child->type() == TestTreeItem::GTestCaseParameterized) { + testSpecifier.prepend(QLatin1String("*/")); + testSpecifier.append(QLatin1String("/*")); + } + enabled << testSpecifier; + } proFilesWithEnabledTestSets.insert(proFile, enabled); } } @@ -461,10 +469,14 @@ TestConfiguration *TestTreeModel::getTestConfiguration(const TestTreeItem *item) config->setProject(project); break; } - case TestTreeItem::GTestCase: { + case TestTreeItem::GTestCase: + case TestTreeItem::GTestCaseParameterized: { + QString testSpecifier = item->name() + QLatin1String(".*"); + if (item->type() == TestTreeItem::GTestCaseParameterized) + testSpecifier.prepend(QLatin1String("*/")); + if (int childCount = item->childCount()) { - config = new TestConfiguration(QString(), - QStringList(item->name() + QLatin1String(".*"))); + config = new TestConfiguration(QString(), QStringList(testSpecifier)); config->setTestCaseCount(childCount); config->setProFile(item->childItem(0)->mainFile()); config->setProject(project); @@ -474,8 +486,13 @@ TestConfiguration *TestTreeModel::getTestConfiguration(const TestTreeItem *item) } case TestTreeItem::GTestName: { const TestTreeItem *parent = item->parentItem(); - config = new TestConfiguration(QString(), - QStringList(parent->name() + QLatin1Char('.') + item->name())); + QString testSpecifier = parent->name() + QLatin1Char('.') + item->name(); + + if (parent->type() == TestTreeItem::GTestCaseParameterized) { + testSpecifier.prepend(QLatin1String("*/")); + testSpecifier.append(QLatin1String("/*")); + } + config = new TestConfiguration(QString(), QStringList(testSpecifier)); config->setProFile(item->mainFile()); config->setProject(project); config->setTestType(TestTypeGTest); @@ -578,7 +595,7 @@ void TestTreeModel::addTestTreeItem(TestTreeItem *item, TestTreeModel::Type type TestTreeItem *toBeUpdated = 0; for (int row = 0, count = parent->childCount(); row < count; ++row) { TestTreeItem *current = parent->childItem(row); - if (current->name() == item->name()) { + if (current->name() == item->name() && current->type() == item->type()) { toBeUpdated = current; break; } diff --git a/plugins/autotest/testvisitor.cpp b/plugins/autotest/testvisitor.cpp index 67fdecd6092..9c4d4d272d8 100644 --- a/plugins/autotest/testvisitor.cpp +++ b/plugins/autotest/testvisitor.cpp @@ -380,7 +380,10 @@ bool GTestVisitor::visit(CPlusPlus::FunctionDefinitionAST *ast) locationAndType.m_column = column - 1; locationAndType.m_type = disabled ? TestTreeItem::GTestNameDisabled : TestTreeItem::GTestName; - m_gtestFunctions[testCaseName].append(locationAndType); + GTestCaseSpec spec; + spec.testCaseName = testCaseName; + spec.parameterized = TestUtils::isGTestParameterized(prettyName); + m_gtestFunctions[spec].append(locationAndType); } return false; diff --git a/plugins/autotest/testvisitor.h b/plugins/autotest/testvisitor.h index bbccffd4e18..ec01686b6b9 100644 --- a/plugins/autotest/testvisitor.h +++ b/plugins/autotest/testvisitor.h @@ -123,18 +123,31 @@ private: }; +struct GTestCaseSpec +{ + QString testCaseName; + bool parameterized; +}; + +inline bool operator<(const GTestCaseSpec &spec1, const GTestCaseSpec &spec2) +{ + if (spec1.testCaseName != spec2.testCaseName) + return spec1.testCaseName < spec2.testCaseName; + return !spec1.parameterized; +} + class GTestVisitor : public CPlusPlus::ASTVisitor { public: GTestVisitor(CPlusPlus::Document::Ptr doc); bool visit(CPlusPlus::FunctionDefinitionAST *ast); - QMap gtestFunctions() const { return m_gtestFunctions; } + QMap gtestFunctions() const { return m_gtestFunctions; } private: CPlusPlus::Document::Ptr m_document; CPlusPlus::Overview m_overview; - QMap m_gtestFunctions; + QMap m_gtestFunctions; };