diff options
Diffstat (limited to 'plugins')
-rw-r--r-- | plugins/autotest/autotest.qrc | 1 | ||||
-rw-r--r-- | plugins/autotest/images/data.png | bin | 0 -> 646 bytes | |||
-rw-r--r-- | plugins/autotest/testcodeparser.cpp | 54 | ||||
-rw-r--r-- | plugins/autotest/testtreeitem.h | 5 | ||||
-rw-r--r-- | plugins/autotest/testtreemodel.cpp | 25 | ||||
-rw-r--r-- | plugins/autotest/testvisitor.cpp | 137 | ||||
-rw-r--r-- | plugins/autotest/testvisitor.h | 27 |
7 files changed, 234 insertions, 15 deletions
diff --git a/plugins/autotest/autotest.qrc b/plugins/autotest/autotest.qrc index 2770aff5fe..8c70e3ae3f 100644 --- a/plugins/autotest/autotest.qrc +++ b/plugins/autotest/autotest.qrc @@ -21,5 +21,6 @@ <file>images/run.png</file> <file>images/runselected.png</file> <file>images/stop.png</file> + <file>images/data.png</file> </qresource> </RCC> diff --git a/plugins/autotest/images/data.png b/plugins/autotest/images/data.png Binary files differnew file mode 100644 index 0000000000..1f510c4042 --- /dev/null +++ b/plugins/autotest/images/data.png diff --git a/plugins/autotest/testcodeparser.cpp b/plugins/autotest/testcodeparser.cpp index d01fb3140f..b7a13b4ee8 100644 --- a/plugins/autotest/testcodeparser.cpp +++ b/plugins/autotest/testcodeparser.cpp @@ -355,11 +355,40 @@ static CPlusPlus::Document::Ptr declaringDocument(CPlusPlus::Document::Ptr doc, return declaringDoc; } +static bool hasFunctionWithDataTagUsage(const QMap<QString, TestCodeLocationAndType> &testFunctions) +{ + foreach (const QString &functionName, testFunctions.keys()) { + if (functionName.endsWith(QLatin1String("_data")) && + testFunctions.contains(functionName.left(functionName.size() - 5))) { + return true; + } + } + return false; +} + +static QMap<QString, TestCodeLocationList> checkForDataTags(const QString &fileName, + const QMap<QString, TestCodeLocationAndType> &testFunctions) +{ + if (hasFunctionWithDataTagUsage(testFunctions)) { + const CPlusPlus::Snapshot snapshot = CPlusPlus::CppModelManagerBase::instance()->snapshot(); + const QByteArray fileContent = getFileContent(fileName); + CPlusPlus::Document::Ptr document = snapshot.preprocessedDocument(fileContent, fileName); + document->check(); + CPlusPlus::AST *ast = document->translationUnit()->ast(); + TestDataFunctionVisitor visitor(document); + visitor.accept(ast); + return visitor.dataTags(); + } + return QMap<QString, TestCodeLocationList>(); +} + + static TestTreeItem constructTestTreeItem(const QString &fileName, const QString &mainFile, // used for Quick Tests only const QString &testCaseName, int line, int column, - const QMap<QString, TestCodeLocationAndType> functions) + const QMap<QString, TestCodeLocationAndType> functions, + const QMap<QString, TestCodeLocationList> dataTags = QMap<QString, TestCodeLocationList>()) { TestTreeItem treeItem(testCaseName, fileName, TestTreeItem::TEST_CLASS); treeItem.setMainFile(mainFile); // used for Quick Tests only @@ -368,10 +397,24 @@ static TestTreeItem constructTestTreeItem(const QString &fileName, foreach (const QString &functionName, functions.keys()) { const TestCodeLocationAndType locationAndType = functions.value(functionName); - TestTreeItem *treeItemChild = new TestTreeItem(functionName, locationAndType.m_fileName, + TestTreeItem *treeItemChild = new TestTreeItem(functionName, locationAndType.m_name, locationAndType.m_type, &treeItem); treeItemChild->setLine(locationAndType.m_line); treeItemChild->setColumn(locationAndType.m_column); + // check for data tags and if there are any for this function add them + const QString qualifiedFunctionName = testCaseName + QLatin1String("::") + functionName; + if (dataTags.contains(qualifiedFunctionName)) { + const TestCodeLocationList &tags = dataTags.value(qualifiedFunctionName); + foreach (const TestCodeLocationAndType &tagLocation, tags) { + TestTreeItem *tagTreeItem = new TestTreeItem(tagLocation.m_name, + locationAndType.m_name, + tagLocation.m_type, treeItemChild); + tagTreeItem->setLine(tagLocation.m_line); + tagTreeItem->setColumn(tagLocation.m_column); + treeItemChild->appendChild(tagTreeItem); + } + } + treeItem.appendChild(treeItemChild); } return treeItem; @@ -437,8 +480,11 @@ void TestCodeParser::checkDocumentForTestCode(CPlusPlus::Document::Ptr document) visitor.accept(declaringDoc->globalNamespace()); const QMap<QString, TestCodeLocationAndType> testFunctions = visitor.privateSlots(); + const QMap<QString, TestCodeLocationList> dataTags = + checkForDataTags(declaringDoc->fileName(), testFunctions); TestTreeItem item = constructTestTreeItem(declaringDoc->fileName(), QString(), - testCaseName, line, column, testFunctions); + testCaseName, line, column, testFunctions, + dataTags); updateModelAndCppDocMap(document, declaringDoc->fileName(), item); return; } @@ -487,7 +533,7 @@ void TestCodeParser::handleQtQuickTest(CPlusPlus::Document::Ptr document) // construct new/modified TestTreeItem TestTreeItem testTreeItem - = constructTestTreeItem(tcLocationAndType.m_fileName, cppFileName, testCaseName, + = constructTestTreeItem(tcLocationAndType.m_name, cppFileName, testCaseName, tcLocationAndType.m_line, tcLocationAndType.m_column, testFunctions); diff --git a/plugins/autotest/testtreeitem.h b/plugins/autotest/testtreeitem.h index 0cfa1aead1..8c7bf14307 100644 --- a/plugins/autotest/testtreeitem.h +++ b/plugins/autotest/testtreeitem.h @@ -35,6 +35,7 @@ public: ROOT, TEST_CLASS, TEST_FUNCTION, + TEST_DATATAG, TEST_DATAFUNCTION, TEST_SPECIALFUNCTION }; @@ -84,12 +85,14 @@ private: }; struct TestCodeLocationAndType { - QString m_fileName; + QString m_name; // tag name for m_type == TEST_DATATAG, file name for other values unsigned m_line; unsigned m_column; TestTreeItem::Type m_type; }; +typedef QVector<TestCodeLocationAndType> TestCodeLocationList; + } // namespace Internal } // namespace Autotest diff --git a/plugins/autotest/testtreemodel.cpp b/plugins/autotest/testtreemodel.cpp index 5eea7b7d98..e30b7f651c 100644 --- a/plugins/autotest/testtreemodel.cpp +++ b/plugins/autotest/testtreemodel.cpp @@ -180,12 +180,13 @@ int TestTreeModel::columnCount(const QModelIndex &) const static QIcon testTreeIcon(TestTreeItem::Type type) { - static QIcon icons[3] = { + static QIcon icons[] = { QIcon(), QIcon(QLatin1String(":/images/class.png")), - QIcon(QLatin1String(":/images/func.png")) + QIcon(QLatin1String(":/images/func.png")), + QIcon(QLatin1String(":/images/data.png")) }; - if (type >= 3) + if (type >= sizeof(icons) / sizeof(icons[0])) return icons[2]; return icons[type]; } @@ -204,7 +205,7 @@ QVariant TestTreeModel::data(const QModelIndex &index, int role) const || (item == m_quickTestRootItem && m_quickTestRootItem->childCount() == 0)) { return QString(item->name() + tr(" (none)")); } else { - if (item->name().isEmpty()) + if (item->name().isEmpty() && item->type() == TestTreeItem::TEST_CLASS) return tr(Constants::UNNAMED_QUICKTESTS); return item->name(); } @@ -222,6 +223,7 @@ QVariant TestTreeModel::data(const QModelIndex &index, int role) const case Qt::CheckStateRole: switch (item->type()) { case TestTreeItem::ROOT: + case TestTreeItem::TEST_DATATAG: case TestTreeItem::TEST_DATAFUNCTION: case TestTreeItem::TEST_SPECIALFUNCTION: return QVariant(); @@ -682,7 +684,7 @@ void TestTreeModel::updateUnnamedQuickTest(const QString &fileName, const QStrin foreach (const QString &functionName, functions.keys()) { const TestCodeLocationAndType locationAndType = functions.value(functionName); - TestTreeItem *testFunction = new TestTreeItem(functionName, locationAndType.m_fileName, + TestTreeItem *testFunction = new TestTreeItem(functionName, locationAndType.m_name, locationAndType.m_type, &unnamed); testFunction->setLine(locationAndType.m_line); testFunction->setColumn(locationAndType.m_column); @@ -819,6 +821,19 @@ void TestTreeModel::processChildren(QModelIndex &parentIndex, const TestTreeItem TestTreeItem *modifiedChild = newItem.child(row); if (toBeModifiedChild->modifyContent(modifiedChild)) emit dataChanged(child, child, modificationRoles); + + // handle data tags - just remove old and add them + if (modifiedChild->childCount() || toBeModifiedChild->childCount()) { + beginRemoveRows(child, 0, toBeModifiedChild->childCount()); + toBeModifiedChild->removeChildren(); + endRemoveRows(); + const int count = modifiedChild->childCount(); + beginInsertRows(child, 0, count); + for (int childRow = 0; childRow < count; ++childRow) + toBeModifiedChild->appendChild(new TestTreeItem(*modifiedChild->child(childRow))); + endInsertRows(); + } + if (checkStates.contains(toBeModifiedChild->name())) { Qt::CheckState state = checkStates.value(toBeModifiedChild->name()); if (state != toBeModifiedChild->checked()) { diff --git a/plugins/autotest/testvisitor.cpp b/plugins/autotest/testvisitor.cpp index d0179c1ca7..3fe84cb077 100644 --- a/plugins/autotest/testvisitor.cpp +++ b/plugins/autotest/testvisitor.cpp @@ -21,7 +21,6 @@ #include <cplusplus/FullySpecifiedType.h> #include <cplusplus/LookupContext.h> -#include <cplusplus/Overview.h> #include <cplusplus/Symbols.h> #include <cplusplus/TypeOfExpression.h> @@ -29,6 +28,8 @@ #include <qmljs/parser/qmljsast_p.h> +#include <utils/qtcassert.h> + #include <QList> namespace Autotest { @@ -73,11 +74,11 @@ bool TestVisitor::visit(CPlusPlus::Class *symbol) CPlusPlus::Function *functionDefinition = m_symbolFinder.findMatchingDefinition( func, CppTools::CppModelManager::instance()->snapshot(), true); if (functionDefinition) { - locationAndType.m_fileName = QString::fromUtf8(functionDefinition->fileName()); + locationAndType.m_name = QString::fromUtf8(functionDefinition->fileName()); locationAndType.m_line = functionDefinition->line(); locationAndType.m_column = functionDefinition->column() - 1; } else { // if we cannot find the definition use declaration as fallback - locationAndType.m_fileName = QString::fromUtf8(member->fileName()); + locationAndType.m_name = QString::fromUtf8(member->fileName()); locationAndType.m_line = member->line(); locationAndType.m_column = member->column() - 1; } @@ -145,6 +146,132 @@ bool TestAstVisitor::visit(CPlusPlus::CompoundStatementAST *ast) return true; } +/********************** Test Data Function AST Visitor ************************/ + +TestDataFunctionVisitor::TestDataFunctionVisitor(CPlusPlus::Document::Ptr doc) + : CPlusPlus::ASTVisitor(doc->translationUnit()), + m_currentDoc(doc), + m_currentAstDepth(0), + m_insideUsingQTestDepth(0), + m_insideUsingQTest(false) +{ +} + +TestDataFunctionVisitor::~TestDataFunctionVisitor() +{ +} + +bool TestDataFunctionVisitor::visit(CPlusPlus::UsingDirectiveAST *ast) +{ + if (auto nameAST = ast->name) { + if (m_overview.prettyName(nameAST->name) == QLatin1String("QTest")) { + m_insideUsingQTest = true; + // we need the surrounding AST depth as using directive is an AST itself + m_insideUsingQTestDepth = m_currentAstDepth - 1; + } + } + return true; +} + +bool TestDataFunctionVisitor::visit(CPlusPlus::FunctionDefinitionAST *ast) +{ + if (ast->declarator) { + CPlusPlus::DeclaratorIdAST *id = ast->declarator->core_declarator->asDeclaratorId(); + if (!id) + return false; + + const QString prettyName = m_overview.prettyName(id->name->name); + // do not handle functions that aren't real test data functions + if (!prettyName.endsWith(QLatin1String("_data")) || !ast->symbol + || ast->symbol->argumentCount() != 0) { + return false; + } + + m_currentFunction = prettyName.left(prettyName.size() - 5); + m_currentTags.clear(); + return true; + } + + return false; +} + +bool TestDataFunctionVisitor::visit(CPlusPlus::CallAST *ast) +{ + if (m_currentFunction.isEmpty()) + return true; + + unsigned firstToken; + if (newRowCallFound(ast, &firstToken)) { + if (const auto expressionListAST = ast->expression_list) { + // first argument is the one we need + if (const auto argumentExpressionAST = expressionListAST->value) { + if (const auto stringLiteral = argumentExpressionAST->asStringLiteral()) { + auto token = m_currentDoc->translationUnit()->tokenAt( + stringLiteral->literal_token); + if (token.isStringLiteral()) { + unsigned line = 0; + unsigned column = 0; + m_currentDoc->translationUnit()->getTokenStartPosition( + firstToken, &line, &column); + TestCodeLocationAndType locationAndType; + locationAndType.m_name = QString::fromUtf8(token.spell()); + locationAndType.m_column = column - 1; + locationAndType.m_line = line; + locationAndType.m_type = TestTreeItem::TEST_DATATAG; + m_currentTags.append(locationAndType); + } + } + } + } + } + return true; +} + +bool TestDataFunctionVisitor::preVisit(CPlusPlus::AST *) +{ + ++m_currentAstDepth; + return true; +} + +void TestDataFunctionVisitor::postVisit(CPlusPlus::AST *ast) +{ + --m_currentAstDepth; + m_insideUsingQTest &= m_currentAstDepth >= m_insideUsingQTestDepth; + + if (!ast->asFunctionDefinition()) + return; + + if (!m_currentFunction.isEmpty() && !m_currentTags.isEmpty()) + m_dataTags.insert(m_currentFunction, m_currentTags); + + m_currentFunction.clear(); + m_currentTags.clear(); +} + +bool TestDataFunctionVisitor::newRowCallFound(CPlusPlus::CallAST *ast, unsigned *firstToken) const +{ + QTC_ASSERT(firstToken, return false); + + if (!ast->base_expression) + return false; + + bool found = false; + + if (const CPlusPlus::IdExpressionAST *exp = ast->base_expression->asIdExpression()) { + if (!exp->name) + return false; + + if (const auto qualifiedNameAST = exp->name->asQualifiedName()) { + found = m_overview.prettyName(qualifiedNameAST->name) == QLatin1String("QTest::newRow"); + *firstToken = qualifiedNameAST->firstToken(); + } else if (m_insideUsingQTest) { + found = m_overview.prettyName(exp->name->name) == QLatin1String("newRow"); + *firstToken = exp->name->firstToken(); + } + } + return found; +} + /*************************** Quick Test AST Visitor ***************************/ TestQmlVisitor::TestQmlVisitor(QmlJS::Document::Ptr doc) @@ -164,7 +291,7 @@ bool TestQmlVisitor::visit(QmlJS::AST::UiObjectDefinition *ast) m_currentTestCaseName.clear(); const auto sourceLocation = ast->firstSourceLocation(); - m_testCaseLocation.m_fileName = m_currentDoc->fileName(); + m_testCaseLocation.m_name = m_currentDoc->fileName(); m_testCaseLocation.m_line = sourceLocation.startLine; m_testCaseLocation.m_column = sourceLocation.startColumn - 1; m_testCaseLocation.m_type = TestTreeItem::TEST_CLASS; @@ -192,7 +319,7 @@ bool TestQmlVisitor::visit(QmlJS::AST::FunctionDeclaration *ast) || specialFunctions.contains(name.toString())) { const auto sourceLocation = ast->firstSourceLocation(); TestCodeLocationAndType locationAndType; - locationAndType.m_fileName = m_currentDoc->fileName(); + locationAndType.m_name = m_currentDoc->fileName(); locationAndType.m_line = sourceLocation.startLine; locationAndType.m_column = sourceLocation.startColumn - 1; if (specialFunctions.contains(name.toString())) diff --git a/plugins/autotest/testvisitor.h b/plugins/autotest/testvisitor.h index 07d5835293..4441eb26f8 100644 --- a/plugins/autotest/testvisitor.h +++ b/plugins/autotest/testvisitor.h @@ -24,6 +24,7 @@ #include <cplusplus/ASTVisitor.h> #include <cplusplus/CppDocument.h> +#include <cplusplus/Overview.h> #include <cplusplus/Scope.h> #include <cplusplus/SymbolVisitor.h> @@ -72,6 +73,32 @@ private: }; +class TestDataFunctionVisitor : public CPlusPlus::ASTVisitor +{ +public: + TestDataFunctionVisitor(CPlusPlus::Document::Ptr doc); + virtual ~TestDataFunctionVisitor(); + + bool visit(CPlusPlus::UsingDirectiveAST *ast); + bool visit(CPlusPlus::FunctionDefinitionAST *ast); + bool visit(CPlusPlus::CallAST *ast); + bool preVisit(CPlusPlus::AST *ast); + void postVisit(CPlusPlus::AST *ast); + QMap<QString, TestCodeLocationList> dataTags() const { return m_dataTags; } + +private: + bool newRowCallFound(CPlusPlus::CallAST *ast, unsigned *firstToken) const; + + CPlusPlus::Document::Ptr m_currentDoc; + CPlusPlus::Overview m_overview; + QString m_currentFunction; + QMap<QString, TestCodeLocationList> m_dataTags; + TestCodeLocationList m_currentTags; + unsigned m_currentAstDepth; + unsigned m_insideUsingQTestDepth; + bool m_insideUsingQTest; +}; + class TestQmlVisitor : public QmlJS::AST::Visitor { public: |