diff options
author | Christian Stenger <christian.stenger@theqtcompany.com> | 2015-01-27 15:22:17 +0100 |
---|---|---|
committer | Christian Stenger <christian.stenger@theqtcompany.com> | 2015-02-05 08:25:11 +0200 |
commit | ca4b66e8adb8520098505b9be09fc72a6eb79856 (patch) | |
tree | f3708e9bbd5143efba2649bbd3ef3431de34cdf9 /plugins | |
parent | 834c52f48318caa9875c87914bcab9ab47e4eb2c (diff) | |
download | qt-creator-ca4b66e8adb8520098505b9be09fc72a6eb79856.tar.gz |
Refactor TestCodeParser
Change-Id: I5fdb6429e8509468b7a710414af250ea6464d92d
Reviewed-by: Tim Jenssen <tim.jenssen@theqtcompany.com>
Diffstat (limited to 'plugins')
-rw-r--r-- | plugins/autotest/testcodeparser.cpp | 596 | ||||
-rw-r--r-- | plugins/autotest/testcodeparser.h | 18 | ||||
-rw-r--r-- | plugins/autotest/testtreeitem.cpp | 8 | ||||
-rw-r--r-- | plugins/autotest/testtreeitem.h | 1 |
4 files changed, 333 insertions, 290 deletions
diff --git a/plugins/autotest/testcodeparser.cpp b/plugins/autotest/testcodeparser.cpp index 6faa7d160d..9d3772394b 100644 --- a/plugins/autotest/testcodeparser.cpp +++ b/plugins/autotest/testcodeparser.cpp @@ -53,10 +53,11 @@ TestCodeParser::TestCodeParser(TestTreeModel *parent) m_pendingUpdate(false) { // connect to ProgressManager to post-pone test parsing when CppModelManager is parsing - Core::ProgressManager *pm = qobject_cast<Core::ProgressManager *>( - Core::ProgressManager::instance()); - connect(pm, &Core::ProgressManager::taskStarted, this, &TestCodeParser::onTaskStarted); - connect(pm, &Core::ProgressManager::allTasksFinished, this, &TestCodeParser::onAllTasksFinished); + auto progressManager = qobject_cast<Core::ProgressManager *>(Core::ProgressManager::instance()); + connect(progressManager, &Core::ProgressManager::taskStarted, + this, &TestCodeParser::onTaskStarted); + connect(progressManager, &Core::ProgressManager::allTasksFinished, + this, &TestCodeParser::onAllTasksFinished); } TestCodeParser::~TestCodeParser() @@ -91,8 +92,8 @@ void TestCodeParser::updateTestTree() m_model->removeAllAutoTests(); m_model->removeAllQuickTests(); - if (ProjectExplorer::Project *proj = currentProject()) { - if (auto qmakeProject = qobject_cast<QmakeProjectManager::QmakeProject *>(proj)) { + if (ProjectExplorer::Project *project = currentProject()) { + if (auto qmakeProject = qobject_cast<QmakeProjectManager::QmakeProject *>(project)) { connect(qmakeProject, &QmakeProjectManager::QmakeProject::proFilesEvaluated, this, &TestCodeParser::onProFileEvaluated, Qt::UniqueConnection); } @@ -200,10 +201,11 @@ static QString quickTestSrcDir(const CppTools::CppModelManager *cppMM, return QString(); } -static QString testClass(const CPlusPlus::Document::Ptr &doc) +static QString testClass(const CppTools::CppModelManager *modelManager, + CPlusPlus::Document::Ptr &document) { static const QByteArray qtTestMacros[] = {"QTEST_MAIN", "QTEST_APPLESS_MAIN", "QTEST_GUILESS_MAIN"}; - const QList<CPlusPlus::Document::MacroUse> macros = doc->macroUses(); + const QList<CPlusPlus::Document::MacroUse> macros = document->macroUses(); foreach (const CPlusPlus::Document::MacroUse ¯o, macros) { if (!macro.isFunctionLike()) @@ -211,10 +213,19 @@ static QString testClass(const CPlusPlus::Document::Ptr &doc) const QByteArray name = macro.macro().name(); if (name == qtTestMacros[0] || name == qtTestMacros[1] || name == qtTestMacros[2]) { const CPlusPlus::Document::Block arg = macro.arguments().at(0); - return QLatin1String(getFileContent(doc->fileName()).mid(arg.bytesBegin(), arg.bytesEnd() - arg.bytesBegin())); + return QLatin1String(getFileContent(document->fileName()) + .mid(arg.bytesBegin(), arg.bytesEnd() - arg.bytesBegin())); } } - return QString(); + // check if one has used a self-defined macro or QTest::qExec() directly + const CPlusPlus::Snapshot snapshot = modelManager->snapshot(); + const QByteArray fileContent = getFileContent(document->fileName()); + document = snapshot.preprocessedDocument(fileContent, document->fileName()); + document->check(); + CPlusPlus::AST *ast = document->translationUnit()->ast(); + TestAstVisitor astVisitor(document); + astVisitor.accept(ast); + return astVisitor.className(); } static QString quickTestName(const CPlusPlus::Document::Ptr &doc) @@ -228,7 +239,8 @@ static QString quickTestName(const CPlusPlus::Document::Ptr &doc) const QByteArray name = macro.macro().name(); if (name == qtTestMacros[0] || name == qtTestMacros[1] || name == qtTestMacros[2]) { CPlusPlus::Document::Block arg = macro.arguments().at(0); - return QLatin1String(getFileContent(doc->fileName()).mid(arg.bytesBegin(), arg.bytesEnd() - arg.bytesBegin())); + return QLatin1String(getFileContent(doc->fileName()) + .mid(arg.bytesBegin(), arg.bytesEnd() - arg.bytesBegin())); } } return QString(); @@ -268,323 +280,186 @@ static QList<QmlJS::Document::Ptr> scanDirectoryForQuickTestQmlFiles(const QStri return foundDocs; } +static CPlusPlus::Document::Ptr declaringDocument(CPlusPlus::Document::Ptr doc, + const QString &testCaseName, + unsigned *line, unsigned *column) +{ + CPlusPlus::Document::Ptr declaringDoc = doc; + const CppTools::CppModelManager *cppMM = CppTools::CppModelManager::instance(); + CPlusPlus::TypeOfExpression typeOfExpr; + typeOfExpr.init(doc, cppMM->snapshot()); + + auto lookupItems = typeOfExpr(testCaseName.toUtf8(), doc->globalNamespace()); + if (lookupItems.size()) { + CPlusPlus::Class *toeClass = lookupItems.first().declaration()->asClass(); + if (toeClass) { + const QString declFileName = QLatin1String(toeClass->fileId()->chars(), + toeClass->fileId()->size()); + declaringDoc = cppMM->snapshot().document(declFileName); + *line = toeClass->line(); + *column = toeClass->column() - 1; + } + } + return declaringDoc; +} + +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, + TestTreeItem *rootItem) +{ + TestTreeItem *treeItem = new TestTreeItem(testCaseName, fileName, TestTreeItem::TEST_CLASS, rootItem); + treeItem->setMainFile(mainFile); // used for Quick Tests only + treeItem->setLine(line); + treeItem->setColumn(column); + + foreach (const QString &functionName, functions.keys()) { + const TestCodeLocationAndType locationAndType = functions.value(functionName); + TestTreeItem *treeItemChild = new TestTreeItem(functionName, locationAndType.m_fileName, + locationAndType.m_type, treeItem); + treeItemChild->setLine(locationAndType.m_line); + treeItemChild->setColumn(locationAndType.m_column); + treeItem->appendChild(treeItemChild); + } + return treeItem; +} + /****** end of helpers ******/ -void TestCodeParser::checkDocumentForTestCode(CPlusPlus::Document::Ptr doc) +void TestCodeParser::checkDocumentForTestCode(CPlusPlus::Document::Ptr document) { - const QString file = doc->fileName(); - const CppTools::CppModelManager *cppMM = CppTools::CppModelManager::instance(); + const QString fileName = document->fileName(); + const CppTools::CppModelManager *modelManager = CppTools::CppModelManager::instance(); - QList<CppTools::ProjectPart::Ptr> projParts = cppMM->projectPart(file); + QList<CppTools::ProjectPart::Ptr> projParts = modelManager->projectPart(fileName); if (projParts.size()) if (!projParts.at(0)->selectedForBuilding) { - removeTestsIfNecessary(file); + removeTestsIfNecessary(fileName); return; } - if (includesQtQuickTest(doc, cppMM)) { - handleQtQuickTest(doc); + if (includesQtQuickTest(document, modelManager)) { + handleQtQuickTest(document); return; } - if (includesQtTest(doc, cppMM) && qtTestLibDefined(cppMM, file)) { - QString tc(testClass(doc)); - if (tc.isEmpty()) { - // one might have used an approach without macros or defined own macros - const CPlusPlus::Snapshot snapshot = cppMM->snapshot(); - const QByteArray fileContent = getFileContent(file); - doc = snapshot.preprocessedDocument(fileContent, file); - doc->check(); - CPlusPlus::AST *ast = doc->translationUnit()->ast(); - TestAstVisitor astVisitor(doc); - astVisitor.accept(ast); - tc = astVisitor.className(); - } - if (!tc.isEmpty()) { - // construct the new/modified TestTreeItem + if (includesQtTest(document, modelManager) && qtTestLibDefined(modelManager, fileName)) { + QString testCaseName(testClass(modelManager, document)); + if (!testCaseName.isEmpty()) { + unsigned line = 0; + unsigned column = 0; + CPlusPlus::Document::Ptr declaringDoc = declaringDocument(document, testCaseName, + &line, &column); + if (declaringDoc.isNull()) + return; + + TestVisitor visitor(testCaseName); + visitor.accept(declaringDoc->globalNamespace()); + const QMap<QString, TestCodeLocationAndType> testFunctions = visitor.privateSlots(); + const QModelIndex autoTestRootIndex = m_model->index(0, 0); TestTreeItem *autoTestRootItem = static_cast<TestTreeItem *>(autoTestRootIndex.internalPointer()); - TestTreeItem *ttItem = new TestTreeItem(tc, file, TestTreeItem::TEST_CLASS, - autoTestRootItem); - QString declFileName; - CPlusPlus::TypeOfExpression toe; - toe.init(doc, cppMM->snapshot()); - CPlusPlus::Document::Ptr declaringDoc = doc; - const QList<CPlusPlus::LookupItem> toeItems = toe(tc.toUtf8(), doc->globalNamespace()); - if (toeItems.size()) { - CPlusPlus::Class *toeClass = toeItems.first().declaration()->asClass(); - if (toeClass) { - declFileName = QLatin1String(toeClass->fileId()->chars(), - toeClass->fileId()->size()); - declaringDoc = cppMM->snapshot().document(declFileName); - ttItem->setFilePath(declFileName); - ttItem->setLine(toeClass->line()); - ttItem->setColumn(toeClass->column() - 1); - } - } - if (declaringDoc.isNull()) { - delete ttItem; - return; - } - TestVisitor myVisitor(tc); - myVisitor.accept(declaringDoc->globalNamespace()); - const QMap<QString, TestCodeLocationAndType> privSlots = myVisitor.privateSlots(); - foreach (const QString &privS, privSlots.keys()) { - const TestCodeLocationAndType locationAndType = privSlots.value(privS); - TestTreeItem *ttSub = new TestTreeItem(privS, locationAndType.m_fileName, - locationAndType.m_type, ttItem); - ttSub->setLine(locationAndType.m_line); - ttSub->setColumn(locationAndType.m_column); - ttItem->appendChild(ttSub); - } - // TODO refactoring? - // update model and internal map - QString proFile; - QList<CppTools::ProjectPart::Ptr> ppList = cppMM->projectPart(file); - if (ppList.size()) - proFile = ppList.at(0)->projectFile; - - TestInfo info; - int count; - if (m_cppDocMap.contains(file)) { - info = m_cppDocMap.value(file); - count = autoTestRootItem->childCount(); - for (int i = 0; i < count; ++i) { - TestTreeItem *currentItem = autoTestRootItem->child(i); - if (currentItem->filePath() == file) { - m_model->modifyAutoTestSubtree(i, ttItem); - TestInfo ti(tc, privSlots.keys(), doc->revision(), doc->editorRevision()); - ti.setProfile(proFile); - m_cppDocMap.insert(file, ti); - break; - } - } - if (declFileName != file) { - info = m_cppDocMap.value(declFileName); - count = autoTestRootItem->childCount(); - for (int i = 0; i < count; ++i) { - TestTreeItem *currentItem = autoTestRootItem->child(i); - if (currentItem->filePath() == declFileName) { - m_model->modifyAutoTestSubtree(i, ttItem); - TestInfo ti(tc, privSlots.keys(), doc->revision(), - doc->editorRevision()); - ti.setReferencingFile(file); - ti.setProfile(proFile); - m_cppDocMap.insert(declFileName, ti); - break; - } - } - } - delete ttItem; - } else { - m_model->addAutoTest(ttItem); - TestInfo ti(tc, privSlots.keys(), doc->revision(), doc->editorRevision()); - ti.setProfile(proFile); - m_cppDocMap.insert(file, ti); - if (declFileName != file) { - TestInfo ti(tc, privSlots.keys(), doc->revision(), doc->editorRevision()); - ti.setReferencingFile(file); - ti.setProfile(proFile); - m_cppDocMap.insert(declFileName, ti); + TestTreeItem *ttItem = constructTestTreeItem(declaringDoc->fileName(), QString(), + testCaseName, line, column, testFunctions, + autoTestRootItem); + updateModelAndCppDocMap(document, declaringDoc->fileName(), ttItem, autoTestRootItem); + } else { + // could not find the class to test, but QTest is included and QT_TESTLIB_LIB defined + // maybe file is only a referenced file + if (m_cppDocMap.contains(fileName)) { + const TestInfo info = m_cppDocMap[fileName]; + CPlusPlus::Snapshot snapshot = modelManager->snapshot(); + if (snapshot.contains(info.referencingFile())) { + checkDocumentForTestCode(snapshot.find(info.referencingFile()).value()); + } else { // no referencing file too, so this test case is no more a test case + m_model->removeAutoTestSubtreeByFilePath(fileName); + m_cppDocMap.remove(fileName); } } } - } else { - // could not find the class to test, but QTest is included and QT_TESTLIB_LIB defined - // maybe file is only a referenced file - if (m_cppDocMap.contains(file)) { - const TestInfo info = m_cppDocMap[file]; - CPlusPlus::Snapshot snapshot = cppMM->snapshot(); - if (snapshot.contains(info.referencingFile())) { - checkDocumentForTestCode(snapshot.find(info.referencingFile()).value()); - } - } } } -void TestCodeParser::handleQtQuickTest(CPlusPlus::Document::Ptr doc) +void TestCodeParser::handleQtQuickTest(CPlusPlus::Document::Ptr document) { - const CppTools::CppModelManager *cppMM = CppTools::CppModelManager::instance(); + const CppTools::CppModelManager *modelManager = CppTools::CppModelManager::instance(); - if (quickTestName(doc).isEmpty()) + if (quickTestName(document).isEmpty()) return; - const QString srcDir = quickTestSrcDir(cppMM, doc->fileName()); + const QString fileName = document->fileName(); + const QString srcDir = quickTestSrcDir(modelManager, fileName); if (srcDir.isEmpty()) return; + const QModelIndex quickTestRootIndex = m_model->index(1, 0); + TestTreeItem *quickTestRootItem = static_cast<TestTreeItem *>(quickTestRootIndex.internalPointer()); + const QList<QmlJS::Document::Ptr> qmlDocs = scanDirectoryForQuickTestQmlFiles(srcDir); - foreach (const QmlJS::Document::Ptr &d, qmlDocs) { - QmlJS::AST::Node *ast = d->ast(); + foreach (const QmlJS::Document::Ptr &qmlJSDoc, qmlDocs) { + QmlJS::AST::Node *ast = qmlJSDoc->ast(); if (!ast) { - qDebug() << "ast is zero pointer" << d->fileName(); // should not happen + qDebug() << "ast is zero pointer" << qmlJSDoc->fileName(); // should not happen continue; } - TestQmlVisitor qmlVisitor(d); + TestQmlVisitor qmlVisitor(qmlJSDoc); QmlJS::AST::Node::accept(ast, &qmlVisitor); - const QString tcName = qmlVisitor.testCaseName(); + const QString testCaseName = qmlVisitor.testCaseName(); const TestCodeLocationAndType tcLocationAndType = qmlVisitor.testCaseLocation(); const QMap<QString, TestCodeLocationAndType> testFunctions = qmlVisitor.testFunctions(); - const QModelIndex quickTestRootIndex = m_model->index(1, 0); - TestTreeItem *quickTestRootItem = static_cast<TestTreeItem *>(quickTestRootIndex.internalPointer()); - - if (tcName.isEmpty()) { - // if this test case was named before remove it - if (m_quickDocMap.contains(d->fileName())) { - m_model->removeQuickTestSubtreeByFilePath(d->fileName()); - m_quickDocMap.remove(d->fileName()); - } - TestTreeItem *unnamedQTItem = m_model->unnamedQuickTests(); - if (unnamedQTItem) { - // remove unnamed quick tests that are already found for this qml file - if (m_model->removeUnnamedQuickTests(d->fileName())) { - // make sure m_quickDocMap does not have a inconsistent state now - TestInfo ti = m_quickDocMap[tr(Constants::UNNAMED_QUICKTESTS)]; - QStringList tiFunctions = ti.testFunctions(); - foreach (const QString &func, testFunctions.keys()) - tiFunctions.removeOne(func); - ti.setTestFunctions(tiFunctions); - if (tiFunctions.size() == 0) - m_quickDocMap.remove(tr(Constants::UNNAMED_QUICKTESTS)); - else - m_quickDocMap.insert(tr(Constants::UNNAMED_QUICKTESTS), ti); - } - // as removeUnnamedQuickTests() could delete this item itself try to get it again - unnamedQTItem = m_model->unnamedQuickTests(); - } - // construct new/modified TestTreeItem - TestTreeItem *ttItem = new TestTreeItem(QString(), QString(), - TestTreeItem::TEST_CLASS, - quickTestRootItem); - if (unnamedQTItem) { - for (int i = 0, count = unnamedQTItem->childCount(); i < count; ++i) { - TestTreeItem *child = new TestTreeItem(*unnamedQTItem->child(i)); - child->setParent(ttItem); - ttItem->appendChild(child); - } - } - - foreach (const QString &func, testFunctions.keys()) { - const TestCodeLocationAndType locationAndType = testFunctions.value(func); - TestTreeItem *ttSub = new TestTreeItem(func, locationAndType.m_fileName, - locationAndType.m_type, ttItem); - ttSub->setLine(locationAndType.m_line); - ttSub->setColumn(locationAndType.m_column); - ttSub->setMainFile(doc->fileName()); - ttItem->appendChild(ttSub); - } - TestInfo info = m_quickDocMap.contains(tr(Constants::UNNAMED_QUICKTESTS)) - ? m_quickDocMap[tr(Constants::UNNAMED_QUICKTESTS)] - : TestInfo(QString(), QStringList(), 666); - QStringList originalFunctions(info.testFunctions()); - foreach (const QString &func, testFunctions.keys()) - originalFunctions.append(func); - info.setTestFunctions(originalFunctions); - - if (unnamedQTItem) { - m_model->modifyQuickTestSubtree(unnamedQTItem->row(), ttItem); - delete ttItem; - } else { - m_model->addQuickTest(ttItem); - } - m_quickDocMap.insert(tr(Constants::UNNAMED_QUICKTESTS), info); - + if (testCaseName.isEmpty()) { + // remove found test functions before re-adding them + removeUnnamedQuickTests(qmlJSDoc->fileName(), testFunctions.keys()); + // re-create TestTreeItem for unnamed Quick Tests + recreateUnnamedQuickTest(testFunctions, fileName, quickTestRootItem); continue; } // end of handling test cases without name property // construct new/modified TestTreeItem - TestTreeItem *ttItem = new TestTreeItem(tcName, tcLocationAndType.m_fileName, - tcLocationAndType.m_type, quickTestRootItem); - ttItem->setLine(tcLocationAndType.m_line); - ttItem->setColumn(tcLocationAndType.m_column); - ttItem->setMainFile(doc->fileName()); - - foreach (const QString &func, testFunctions.keys()) { - const TestCodeLocationAndType locationAndType = testFunctions.value(func); - TestTreeItem *ttSub = new TestTreeItem(func, locationAndType.m_fileName, - locationAndType.m_type, ttItem); - ttSub->setLine(locationAndType.m_line); - ttSub->setColumn(locationAndType.m_column); - ttItem->appendChild(ttSub); - } + TestTreeItem *testTreeItem + = constructTestTreeItem(tcLocationAndType.m_fileName, fileName, testCaseName, + tcLocationAndType.m_line, tcLocationAndType.m_column, + testFunctions, quickTestRootItem); // update model and internal map - const QString fileName(tcLocationAndType.m_fileName); const QmlJS::Document::Ptr qmlDoc = - QmlJSTools::Internal::ModelManager::instance()->snapshot().document(fileName); - QString proFile; - QList<CppTools::ProjectPart::Ptr> ppList = cppMM->projectPart(doc->fileName()); - if (ppList.size()) - proFile = ppList.at(0)->projectFile; - - if (m_quickDocMap.contains(fileName)) { - for (int i = 0; i < quickTestRootItem->childCount(); ++i) { - if (quickTestRootItem->child(i)->filePath() == fileName) { - m_model->modifyQuickTestSubtree(i, ttItem); - TestInfo ti(tcName, testFunctions.keys(), 0, qmlDoc->editorRevision()); - ti.setReferencingFile(doc->fileName()); - ti.setProfile(proFile); - m_quickDocMap.insert(fileName, ti); - break; - } - } - delete ttItem; - } else { - // if it was formerly unnamed remove the respective items - if (m_quickDocMap.contains(tr(Constants::UNNAMED_QUICKTESTS))) { - if (m_model->removeUnnamedQuickTests(d->fileName())) { - // make sure m_quickDocMap does not have a inconsistent state now - TestInfo unnamedInfo = m_quickDocMap[tr(Constants::UNNAMED_QUICKTESTS)]; - QStringList functions = unnamedInfo.testFunctions(); - foreach (const QString &func, testFunctions.keys()) - if (functions.contains(func)) - functions.removeOne(func); - unnamedInfo.setTestFunctions(functions); - if (functions.size() == 0) - m_quickDocMap.remove(tr(Constants::UNNAMED_QUICKTESTS)); - else - m_quickDocMap.insert(tr(Constants::UNNAMED_QUICKTESTS), unnamedInfo); - } - } - - m_model->addQuickTest(ttItem); - TestInfo ti(tcName, testFunctions.keys(), 0, qmlDoc->editorRevision()); - ti.setReferencingFile(doc->fileName()); - ti.setProfile(proFile); - m_quickDocMap.insert(tcLocationAndType.m_fileName, ti); - } + QmlJSTools::Internal::ModelManager::instance()->snapshot().document(tcLocationAndType.m_fileName); + updateModelAndQuickDocMap(qmlDoc, qmlJSDoc->fileName(), fileName, testTreeItem, + quickTestRootItem); } } -void TestCodeParser::onCppDocumentUpdated(const CPlusPlus::Document::Ptr &doc) +void TestCodeParser::onCppDocumentUpdated(const CPlusPlus::Document::Ptr &document) { ProjectExplorer::Project *project = currentProject(); if (!project) return; - const QString fileName = doc->fileName(); + const QString fileName = document->fileName(); if (m_cppDocMap.contains(fileName)) { - if (m_cppDocMap[fileName].revision() == doc->revision() - && m_cppDocMap[fileName].editorRevision() == doc->editorRevision()) { + if (m_cppDocMap[fileName].revision() == document->revision() + && m_cppDocMap[fileName].editorRevision() == document->editorRevision()) { qDebug("Skipped due revision equality"); // added to verify if this ever happens.. return; } } else if (!project->files(ProjectExplorer::Project::AllFiles).contains(fileName)) { return; } - checkDocumentForTestCode(doc); + checkDocumentForTestCode(document); } -void TestCodeParser::onQmlDocumentUpdated(const QmlJS::Document::Ptr &doc) +void TestCodeParser::onQmlDocumentUpdated(const QmlJS::Document::Ptr &document) { ProjectExplorer::Project *project = currentProject(); if (!project) return; - const QString fileName = doc->fileName(); + const QString fileName = document->fileName(); if (m_quickDocMap.contains(fileName)) { - if ((int)m_quickDocMap[fileName].editorRevision() == doc->editorRevision()) { + if ((int)m_quickDocMap[fileName].editorRevision() == document->editorRevision()) { qDebug("Skipped due revision equality (QML)"); // added to verify this ever happens.... return; } @@ -692,29 +567,28 @@ void TestCodeParser::removeTestsIfNecessary(const QString &fileName) } } - void TestCodeParser::removeTestsIfNecessaryByProFile(const QString &proFile) { - QList<QString> fl; - foreach (const QString &fn, m_cppDocMap.keys()) { - if (m_cppDocMap[fn].proFile() == proFile) - fl.append(fn); + QList<QString> fList; + foreach (const QString &fileName, m_cppDocMap.keys()) { + if (m_cppDocMap[fileName].proFile() == proFile) + fList.append(fileName); } - foreach (const QString &fn, fl) { - m_cppDocMap.remove(fn); - m_model->removeAutoTestSubtreeByFilePath(fn); + foreach (const QString &fileName, fList) { + m_cppDocMap.remove(fileName); + m_model->removeAutoTestSubtreeByFilePath(fileName); } - fl.clear(); - foreach (const QString &fn, m_quickDocMap.keys()) { - if (m_quickDocMap[fn].proFile() == proFile) - fl.append(fn); + fList.clear(); + foreach (const QString &fileName, m_quickDocMap.keys()) { + if (m_quickDocMap[fileName].proFile() == proFile) + fList.append(fileName); } - foreach (const QString &fn, fl) { - m_quickDocMap.remove(fn); - m_model->removeQuickTestSubtreeByFilePath(fn); + foreach (const QString &fileName, fList) { + m_quickDocMap.remove(fileName); + m_model->removeQuickTestSubtreeByFilePath(fileName); } // handle unnamed Quick Tests - fl.clear(); // will now be re-used as function names storage + fList.clear(); // will now be re-used as function names storage QSet<QString> filePaths; CppTools::CppModelManager *cppMM = CppTools::CppModelManager::instance(); if (TestTreeItem *unnamedQT = m_model->unnamedQuickTests()) { @@ -723,18 +597,18 @@ void TestCodeParser::removeTestsIfNecessaryByProFile(const QString &proFile) QList<CppTools::ProjectPart::Ptr> ppList = cppMM->projectPart(child->mainFile()); if (ppList.size() && ppList.at(0)->projectFile == proFile) { filePaths.insert(child->filePath()); - fl.append(child->name()); + fList.append(child->name()); } } } - foreach (const QString &fp, filePaths) { - m_model->removeUnnamedQuickTests(fp); + foreach (const QString &filePath, filePaths) { + m_model->removeUnnamedQuickTests(filePath); } // update info map TestInfo unnamedInfo = m_quickDocMap[tr(Constants::UNNAMED_QUICKTESTS)]; QStringList functions = unnamedInfo.testFunctions(); - foreach (const QString &func, fl) - functions.removeOne(func); + foreach (const QString &function, fList) + functions.removeOne(function); unnamedInfo.setTestFunctions(functions); if (functions.size() == 0) m_quickDocMap.remove(tr(Constants::UNNAMED_QUICKTESTS)); @@ -742,6 +616,74 @@ void TestCodeParser::removeTestsIfNecessaryByProFile(const QString &proFile) m_quickDocMap.insert(tr(Constants::UNNAMED_QUICKTESTS), unnamedInfo); } +void TestCodeParser::removeUnnamedQuickTests(const QString &fileName, + const QStringList &testFunctions) +{ + // if this test case was named before remove it + if (m_quickDocMap.contains(fileName)) { + m_model->removeQuickTestSubtreeByFilePath(fileName); + m_quickDocMap.remove(fileName); + } + + if (m_model->unnamedQuickTests()) { + // remove unnamed quick tests that are already found for this qml file + if (m_model->removeUnnamedQuickTests(fileName)) { + // make sure m_quickDocMap does not have a inconsistent state now + TestInfo testInfo = m_quickDocMap[tr(Constants::UNNAMED_QUICKTESTS)]; + QStringList testFunctionNames = testInfo.testFunctions(); + foreach (const QString &func, testFunctions) + testFunctionNames.removeOne(func); + testInfo.setTestFunctions(testFunctionNames); + if (testFunctionNames.size() == 0) + m_quickDocMap.remove(tr(Constants::UNNAMED_QUICKTESTS)); + else + m_quickDocMap.insert(tr(Constants::UNNAMED_QUICKTESTS), testInfo); + } + } +} + +void TestCodeParser::recreateUnnamedQuickTest(const QMap<QString, TestCodeLocationAndType> &testFunctions, + const QString &mainFile, TestTreeItem *rootItem) +{ + TestTreeItem *testTreeItem = new TestTreeItem(QString(), QString(), TestTreeItem::TEST_CLASS, + rootItem); + TestTreeItem *unnamedQTItem = m_model->unnamedQuickTests(); + // if there are still other unnamed Quick Tests re-parent them to the new + if (unnamedQTItem) { + for (int i = 0, count = unnamedQTItem->childCount(); i < count; ++i) { + TestTreeItem *child = new TestTreeItem(*unnamedQTItem->child(i)); + child->setParent(testTreeItem); + testTreeItem->appendChild(child); + } + } + // add test functions of the current + foreach (const QString &function, testFunctions.keys()) { + const TestCodeLocationAndType locationAndType = testFunctions.value(function); + TestTreeItem *testTreeFunction = new TestTreeItem(function, locationAndType.m_fileName, + locationAndType.m_type, testTreeItem); + testTreeFunction->setLine(locationAndType.m_line); + testTreeFunction->setColumn(locationAndType.m_column); + testTreeFunction->setMainFile(mainFile); + testTreeItem->appendChild(testTreeFunction); + } + + TestInfo info = m_quickDocMap.contains(tr(Constants::UNNAMED_QUICKTESTS)) + ? m_quickDocMap[tr(Constants::UNNAMED_QUICKTESTS)] + : TestInfo(QString(), QStringList(), 666); + QStringList originalFunctions(info.testFunctions()); + foreach (const QString &function, testFunctions.keys()) + originalFunctions.append(function); + info.setTestFunctions(originalFunctions); + + if (unnamedQTItem) { + m_model->modifyQuickTestSubtree(unnamedQTItem->row(), testTreeItem); + delete testTreeItem; + } else { + m_model->addQuickTest(testTreeItem); + } + m_quickDocMap.insert(tr(Constants::UNNAMED_QUICKTESTS), info); +} + void TestCodeParser::onTaskStarted(Core::Id type) { if (type != CppTools::Constants::TASK_INDEX @@ -760,14 +702,96 @@ void TestCodeParser::onAllTasksFinished(Core::Id type) updateTestTree(); } +void TestCodeParser::updateModelAndCppDocMap(CPlusPlus::Document::Ptr document, + const QString &declFileName, + TestTreeItem *testItem, TestTreeItem *rootItem) +{ + const CppTools::CppModelManager *cppMM = CppTools::CppModelManager::instance(); + const QString fileName = document->fileName(); + const QString testCaseName = testItem->name(); + QString proFile; + const QList<CppTools::ProjectPart::Ptr> ppList = cppMM->projectPart(fileName); + if (ppList.size()) + proFile = ppList.at(0)->projectFile; + + if (m_cppDocMap.contains(fileName)) { + QStringList files = QStringList() << fileName; + if (fileName != declFileName) + files << declFileName; + foreach (const QString &file, files) { + const bool setReferencingFile = (files.size() == 2 && file == declFileName); + const int count = rootItem->childCount(); + for (int i = 0; i < count; ++i) { + TestTreeItem *currentItem = rootItem->child(i); + if (currentItem->filePath() == file) { + m_model->modifyAutoTestSubtree(i, testItem); + TestInfo testInfo(testCaseName, testItem->getChildNames(), + document->revision(), document->editorRevision()); + testInfo.setProfile(proFile); + if (setReferencingFile) + testInfo.setReferencingFile(fileName); + m_cppDocMap.insert(file, testInfo); + break; + } + } + } + delete testItem; // this item is no more needed as model updates the original with its content + } else { + m_model->addAutoTest(testItem); + TestInfo ti(testCaseName, testItem->getChildNames(), + document->revision(), document->editorRevision()); + ti.setProfile(proFile); + m_cppDocMap.insert(fileName, ti); + if (declFileName != fileName) { + ti.setReferencingFile(fileName); + m_cppDocMap.insert(declFileName, ti); + } + } +} + +void TestCodeParser::updateModelAndQuickDocMap(QmlJS::Document::Ptr qmlDoc, const QString ¤tQmlJSFile, + const QString &referencingFileName, + TestTreeItem *testItem, TestTreeItem *rootItem) +{ + const CppTools::CppModelManager *cppMM = CppTools::CppModelManager::instance(); + const QString fileName = qmlDoc->fileName(); + QString proFile; + QList<CppTools::ProjectPart::Ptr> ppList = cppMM->projectPart(referencingFileName); + if (ppList.size()) + proFile = ppList.at(0)->projectFile; + + if (m_quickDocMap.contains(fileName)) { + for (int i = 0; i < rootItem->childCount(); ++i) { + if (rootItem->child(i)->filePath() == fileName) { + m_model->modifyQuickTestSubtree(i, testItem); + TestInfo testInfo(testItem->name(), testItem->getChildNames(), 0, qmlDoc->editorRevision()); + testInfo.setReferencingFile(referencingFileName); + testInfo.setProfile(proFile); + m_quickDocMap.insert(fileName, testInfo); + break; + } + } + delete testItem; // this item is no more needed as model updates the original with its content + } else { + // if it was formerly unnamed remove the respective items + removeUnnamedQuickTests(currentQmlJSFile, testItem->getChildNames()); + + m_model->addQuickTest(testItem); + TestInfo testInfo(testItem->name(), testItem->getChildNames(), 0, qmlDoc->editorRevision()); + testInfo.setReferencingFile(referencingFileName); + testInfo.setProfile(proFile); + m_quickDocMap.insert(testItem->filePath(), testInfo); + } +} + void TestCodeParser::onProFileEvaluated() { ProjectExplorer::Project *project = currentProject(); if (!project) return; - CppTools::CppModelManager *cppMM = CppTools::CppModelManager::instance(); - const QList<CppTools::ProjectPart::Ptr> pp = cppMM->projectInfo(project).projectParts(); + CppTools::CppModelManager *modelManager = CppTools::CppModelManager::instance(); + const QList<CppTools::ProjectPart::Ptr> pp = modelManager->projectInfo(project).projectParts(); foreach (const CppTools::ProjectPart::Ptr &p, pp) { if (!p->selectedForBuilding) removeTestsIfNecessaryByProFile(p->projectFile); diff --git a/plugins/autotest/testcodeparser.h b/plugins/autotest/testcodeparser.h index 8a78026857..2c83433f0a 100644 --- a/plugins/autotest/testcodeparser.h +++ b/plugins/autotest/testcodeparser.h @@ -33,8 +33,10 @@ class Id; namespace Autotest { namespace Internal { +struct TestCodeLocationAndType; class TestInfo; class TestTreeModel; +class TestTreeItem; class TestCodeParser : public QObject { @@ -48,11 +50,11 @@ signals: public slots: void emitUpdateTestTree(); void updateTestTree(); - void checkDocumentForTestCode(CPlusPlus::Document::Ptr doc); - void handleQtQuickTest(CPlusPlus::Document::Ptr doc); + void checkDocumentForTestCode(CPlusPlus::Document::Ptr document); + void handleQtQuickTest(CPlusPlus::Document::Ptr document); - void onCppDocumentUpdated(const CPlusPlus::Document::Ptr &doc); - void onQmlDocumentUpdated(const QmlJS::Document::Ptr &doc); + void onCppDocumentUpdated(const CPlusPlus::Document::Ptr &document); + void onQmlDocumentUpdated(const QmlJS::Document::Ptr &document); void removeFiles(const QStringList &files); void onProFileEvaluated(); @@ -61,8 +63,16 @@ private: void clearMaps(); void removeTestsIfNecessary(const QString &fileName); void removeTestsIfNecessaryByProFile(const QString &proFile); + void removeUnnamedQuickTests(const QString &fileName, const QStringList &testFunctions); + void recreateUnnamedQuickTest(const QMap<QString, TestCodeLocationAndType> &testFunctions, + const QString &mainFile, TestTreeItem *rootItem); void onTaskStarted(Core::Id type); void onAllTasksFinished(Core::Id type); + void updateModelAndCppDocMap(CPlusPlus::Document::Ptr document, const QString &declFileName, + TestTreeItem *testItem, TestTreeItem *rootItem); + void updateModelAndQuickDocMap(QmlJS::Document::Ptr qmlDoc, const QString ¤tQmlJSFile, + const QString &referencingFileName, + TestTreeItem *testItem, TestTreeItem *rootItem); TestTreeModel *m_model; QMap<QString, TestInfo> m_cppDocMap; diff --git a/plugins/autotest/testtreeitem.cpp b/plugins/autotest/testtreeitem.cpp index 2da060da52..83983db2ef 100644 --- a/plugins/autotest/testtreeitem.cpp +++ b/plugins/autotest/testtreeitem.cpp @@ -172,6 +172,14 @@ Qt::CheckState TestTreeItem::checked() const return Qt::Unchecked; } +QList<QString> TestTreeItem::getChildNames() const +{ + QList<QString> names; + foreach (TestTreeItem *item, m_children) + names << item->name(); + return names; +} + void TestTreeItem::revalidateCheckState() { if (m_children.size() == 0) diff --git a/plugins/autotest/testtreeitem.h b/plugins/autotest/testtreeitem.h index 8ec916b0bf..411839a9bb 100644 --- a/plugins/autotest/testtreeitem.h +++ b/plugins/autotest/testtreeitem.h @@ -64,6 +64,7 @@ public: Qt::CheckState checked() const; Type type() const { return m_type; } void setParent(TestTreeItem *parent) { m_parent = parent; } + QList<QString> getChildNames() const; private: void revalidateCheckState(); |