diff --git a/src/plugins/autotest/autotestplugin.cpp b/src/plugins/autotest/autotestplugin.cpp index ae03dc91d1d..faf4da67173 100644 --- a/src/plugins/autotest/autotestplugin.cpp +++ b/src/plugins/autotest/autotestplugin.cpp @@ -387,9 +387,44 @@ static QList testItemsToTestConfigurations(const QListtextDocument(), return); + const int line = currentEditor->currentLine(); + const FilePath filePath = currentEditor->textDocument()->filePath(); + + const CPlusPlus::Snapshot snapshot = CppEditor::CppModelManager::instance()->snapshot(); + const CPlusPlus::Document::Ptr doc = snapshot.document(filePath); + if (doc.isNull()) // not part of C++ snapshot + return; + + CPlusPlus::Scope *scope = doc->scopeAt(line, currentEditor->currentColumn()); QTextCursor cursor = currentEditor->editorWidget()->textCursor(); cursor.select(QTextCursor::WordUnderCursor); const QString text = cursor.selectedText(); + + while (scope && scope->asBlock()) + scope = scope->enclosingScope(); + if (scope && scope->asFunction()) { // class, namespace for further stuff? + const QList fullName + = CPlusPlus::LookupContext::fullyQualifiedName(scope); + const QString funcName = CPlusPlus::Overview().prettyName(fullName); + const TestFrameworks active = AutotestPlugin::activeTestFrameworks(); + for (auto framework : active) { + const QStringList testName = framework->testNameForSymbolName(funcName); + if (!testName.size()) + continue; + TestTreeItem *it = framework->rootNode()->findTestByNameAndFile(testName, filePath); + if (it) { + const QList testsToRun + = testItemsToTestConfigurations({ it }, mode); + if (!testsToRun.isEmpty()) { + m_testRunner.runTests(mode, testsToRun); + return; + } + } + } + } + + // general approach if (text.isEmpty()) return; // Do not trigger when no name under cursor @@ -398,28 +433,22 @@ void AutotestPluginPrivate::onRunUnderCursorTriggered(TestRunMode mode) return; // Wrong location triggered // check whether we have been triggered on a test function definition - const int line = currentEditor->currentLine(); - const FilePath &filePath = currentEditor->textDocument()->filePath(); QList filteredItems = Utils::filtered(testsItems, [&](ITestTreeItem *it){ return it->line() == line && it->filePath() == filePath; }); if (filteredItems.size() == 0 && testsItems.size() > 1) { - const CPlusPlus::Snapshot snapshot = CppEditor::CppModelManager::instance()->snapshot(); - const CPlusPlus::Document::Ptr doc = snapshot.document(filePath); - if (!doc.isNull()) { - CPlusPlus::Scope *scope = doc->scopeAt(line, currentEditor->currentColumn()); - if (scope->asClass()) { - const QList fullName - = CPlusPlus::LookupContext::fullyQualifiedName(scope); - const QString className = CPlusPlus::Overview().prettyName(fullName); + CPlusPlus::Scope *scope = doc->scopeAt(line, currentEditor->currentColumn()); + if (scope->asClass()) { + const QList fullName + = CPlusPlus::LookupContext::fullyQualifiedName(scope); + const QString className = CPlusPlus::Overview().prettyName(fullName); - filteredItems = Utils::filtered(testsItems, - [&text, &className](ITestTreeItem *it){ - return it->name() == text - && static_cast(it->parent())->name() == className; - }); - } + filteredItems = Utils::filtered(testsItems, + [&text, &className](ITestTreeItem *it){ + return it->name() == text + && static_cast(it->parent())->name() == className; + }); } } if ((filteredItems.size() != 1 && testsItems.size() > 1) diff --git a/src/plugins/autotest/gtest/gtestframework.cpp b/src/plugins/autotest/gtest/gtestframework.cpp index cbfd29b66df..af0a6eaa46c 100644 --- a/src/plugins/autotest/gtest/gtestframework.cpp +++ b/src/plugins/autotest/gtest/gtestframework.cpp @@ -7,6 +7,8 @@ #include "gtesttreeitem.h" #include "gtestparser.h" +#include + namespace Autotest { namespace Internal { @@ -59,5 +61,15 @@ GTest::Constants::GroupMode GTestFramework::groupMode() return GTest::Constants::GroupMode(g_settings->groupMode.itemValue().toInt()); } +QStringList GTestFramework::testNameForSymbolName(const QString &symbolName) const +{ + static const QRegularExpression r("^(.+::)?((DISABLED_)?.+?)_((DISABLED_)?.+)_Test::TestBody$"); + const QRegularExpressionMatch match = r.match(symbolName); + if (!match.hasMatch()) + return {}; + + return { match.captured(2), match.captured(4) }; +} + } // namespace Internal } // namespace Autotest diff --git a/src/plugins/autotest/gtest/gtestframework.h b/src/plugins/autotest/gtest/gtestframework.h index a67470f937f..c9e8cbddc07 100644 --- a/src/plugins/autotest/gtest/gtestframework.h +++ b/src/plugins/autotest/gtest/gtestframework.h @@ -18,6 +18,7 @@ public: static GTest::Constants::GroupMode groupMode(); static QString currentGTestFilter(); + QStringList testNameForSymbolName(const QString &symbolName) const override; private: const char *name() const override; QString displayName() const override; diff --git a/src/plugins/autotest/itestframework.cpp b/src/plugins/autotest/itestframework.cpp index 093bb42aaaf..0f1919b2af8 100644 --- a/src/plugins/autotest/itestframework.cpp +++ b/src/plugins/autotest/itestframework.cpp @@ -63,6 +63,11 @@ ITestParser *ITestFramework::testParser() return m_testParser; } +QStringList ITestFramework::testNameForSymbolName(const QString &) const +{ + return {}; +} + ITestTool::ITestTool(bool activeByDefault) : ITestBase(activeByDefault, ITestBase::Tool) {} diff --git a/src/plugins/autotest/itestframework.h b/src/plugins/autotest/itestframework.h index a2570eb6170..d7a4d7927da 100644 --- a/src/plugins/autotest/itestframework.h +++ b/src/plugins/autotest/itestframework.h @@ -75,6 +75,8 @@ public: virtual QString groupingToolTip() const { return {}; } ITestFramework *asFramework() final { return this; } + // helper for matching a function symbol's name to a test of this framework + virtual QStringList testNameForSymbolName(const QString &symbolName) const; protected: virtual ITestParser *createTestParser() = 0; diff --git a/src/plugins/autotest/qtest/qttestframework.cpp b/src/plugins/autotest/qtest/qttestframework.cpp index 75a913ebdfe..d13470c8569 100644 --- a/src/plugins/autotest/qtest/qttestframework.cpp +++ b/src/plugins/autotest/qtest/qttestframework.cpp @@ -36,5 +36,13 @@ unsigned QtTestFramework::priority() const return QtTest::Constants::FRAMEWORK_PRIORITY; } +QStringList QtTestFramework::testNameForSymbolName(const QString &symbolName) const +{ + int index = symbolName.lastIndexOf("::"); + if (index == -1) + return {}; + return { symbolName.left(index), symbolName.mid(index + 2) }; +} + } // namespace Internal } // namespace Autotest diff --git a/src/plugins/autotest/qtest/qttestframework.h b/src/plugins/autotest/qtest/qttestframework.h index 78ac0ad0413..4fa765ab192 100644 --- a/src/plugins/autotest/qtest/qttestframework.h +++ b/src/plugins/autotest/qtest/qttestframework.h @@ -15,6 +15,7 @@ class QtTestFramework : public ITestFramework public: QtTestFramework() : ITestFramework(true) {} + QStringList testNameForSymbolName(const QString &symbolName) const override; private: const char *name() const override; QString displayName() const override; diff --git a/src/plugins/autotest/testtreeitem.cpp b/src/plugins/autotest/testtreeitem.cpp index bd6a4a14824..884544e3b24 100644 --- a/src/plugins/autotest/testtreeitem.cpp +++ b/src/plugins/autotest/testtreeitem.cpp @@ -271,6 +271,48 @@ TestTreeItem *TestTreeItem::findChildByNameAndFile(const QString &name, const Fi }); } +static TestTreeItem *findMatchingTestAt(TestTreeItem *parent, const QStringList &testName, + const FilePath &filePath) +{ + const QString &first = testName.first(); + const QString &last = testName.last(); + for (int i = 0, iEnd = parent->childCount(); i < iEnd; ++i) { + auto it = parent->childItem(i); + if (it->name() != first) + continue; + + for (int j = 0, jEnd = it->childCount(); j < jEnd; ++j) { + auto grandchild = it->childItem(j); + if (grandchild->name() != last) + continue; + + if (it->filePath() == filePath || grandchild->filePath() == filePath) + return grandchild; + } + } + return nullptr; +} + +TestTreeItem *TestTreeItem::findTestByNameAndFile(const QStringList &testName, + const FilePath &filePath) +{ + QTC_ASSERT(type() == Root, return nullptr); + QTC_ASSERT(testName.size() == 2, return nullptr); + + if (!childCount()) + return nullptr; + + if (childAt(0)->type() != GroupNode) // process root's children directly + return findMatchingTestAt(this, testName, filePath); + + // process children of groups instead of root + for (int i = 0, end = childCount(); i < end; ++i) { + if (TestTreeItem *found = findMatchingTestAt(childItem(i), testName, filePath)) + return found; + } + return nullptr; +} + ITestConfiguration *TestTreeItem::asConfiguration(TestRunMode mode) const { switch (mode) { diff --git a/src/plugins/autotest/testtreeitem.h b/src/plugins/autotest/testtreeitem.h index f5f0723ca8c..e51f48b211c 100644 --- a/src/plugins/autotest/testtreeitem.h +++ b/src/plugins/autotest/testtreeitem.h @@ -127,6 +127,15 @@ public: TestTreeItem *findChildByFileAndType(const Utils::FilePath &filePath, Type type); TestTreeItem *findChildByNameAndFile(const QString &name, const Utils::FilePath &filePath); + // search children for a test (function, ...) that matches the given testName + // e.g. testName = { "Foo", "tst_bar" } will return a (grand)child item where name() matches + // "tst_bar" and its parent matches "Foo" + // filePath is used to distinguish this even more - if any of the items (item and parent) has + // its file path set to filePath the found result is considered valid + // function is expected to be called on a root node of a test framework + TestTreeItem *findTestByNameAndFile(const QStringList &testName, + const Utils::FilePath &filePath); // make virtual for other frameworks? + virtual ITestConfiguration *debugConfiguration() const { return nullptr; } virtual bool canProvideDebugConfiguration() const { return false; } ITestConfiguration *asConfiguration(TestRunMode mode) const final;