diff options
author | Christian Stenger <christian.stenger@digia.com> | 2014-11-06 16:01:06 +0100 |
---|---|---|
committer | Christian Stenger <christian.stenger@theqtcompany.com> | 2014-12-04 13:52:16 +0100 |
commit | 0357b0e98bf049ddb2e365a6ddcace67c2f0e836 (patch) | |
tree | 4a28be6a20b83325f9bb580ba7495e799104f4cc | |
parent | 85efa2c3c969392afbe869ed0fc4a4af91c739f4 (diff) | |
download | qt-creator-0357b0e98bf049ddb2e365a6ddcace67c2f0e836.tar.gz |
Add basic support for Qt Quick Test
-rw-r--r-- | plugins/autotest/autotest_dependencies.pri | 2 | ||||
-rw-r--r-- | plugins/autotest/testcodeparser.cpp | 299 | ||||
-rw-r--r-- | plugins/autotest/testcodeparser.h | 7 | ||||
-rw-r--r-- | plugins/autotest/testconfiguration.cpp | 20 | ||||
-rw-r--r-- | plugins/autotest/testconfiguration.h | 2 | ||||
-rw-r--r-- | plugins/autotest/testrunner.cpp | 2 | ||||
-rw-r--r-- | plugins/autotest/testtreeitem.cpp | 4 | ||||
-rw-r--r-- | plugins/autotest/testtreeitem.h | 3 | ||||
-rw-r--r-- | plugins/autotest/testtreemodel.cpp | 262 | ||||
-rw-r--r-- | plugins/autotest/testtreemodel.h | 11 | ||||
-rw-r--r-- | plugins/autotest/testtreeview.cpp | 53 | ||||
-rw-r--r-- | plugins/autotest/testvisitor.cpp | 79 | ||||
-rw-r--r-- | plugins/autotest/testvisitor.h | 27 |
13 files changed, 681 insertions, 90 deletions
diff --git a/plugins/autotest/autotest_dependencies.pri b/plugins/autotest/autotest_dependencies.pri index c5d76107a0..6546683f45 100644 --- a/plugins/autotest/autotest_dependencies.pri +++ b/plugins/autotest/autotest_dependencies.pri @@ -4,10 +4,12 @@ QTC_PLUGIN_DEPENDS += \ coreplugin \ projectexplorer \ cpptools \ + qmljstools \ licensechecker QTC_LIB_DEPENDS += \ cplusplus \ + qmljs \ utils #QTC_PLUGIN_RECOMMENDS += \ diff --git a/plugins/autotest/testcodeparser.cpp b/plugins/autotest/testcodeparser.cpp index 23006677fe..f58b2a4489 100644 --- a/plugins/autotest/testcodeparser.cpp +++ b/plugins/autotest/testcodeparser.cpp @@ -30,6 +30,10 @@ #include <projectexplorer/session.h> +#include <qmljs/parser/qmljsast_p.h> +#include <qmljs/qmljsdialect.h> +#include <qmljstools/qmljsmodelmanager.h> + #include <utils/textfileformat.h> namespace Autotest { @@ -53,6 +57,7 @@ void TestCodeParser::updateTestTree() clearMaps(); m_model->removeAllAutoTests(); + m_model->removeAllQuickTests(); const ProjectExplorer::SessionManager *session = ProjectExplorer::SessionManager::instance(); if (!session || !session->hasProjects()) return; @@ -88,7 +93,7 @@ static bool includesQtTest(const CPlusPlus::Document::Ptr &doc, { const QList<CPlusPlus::Document::Include> includes = doc->resolvedIncludes(); - foreach (const CPlusPlus::Document::Include inc, includes) { + foreach (const CPlusPlus::Document::Include &inc, includes) { // TODO this short cut works only for #include <QtTest> // bad, as there could be much more different approaches if (inc.unresolvedFileName() == QLatin1String("QtTest") @@ -100,7 +105,7 @@ static bool includesQtTest(const CPlusPlus::Document::Ptr &doc, if (cppMM) { CPlusPlus::Snapshot snapshot = cppMM->snapshot(); const QSet<QString> allIncludes = snapshot.allIncludesForDocument(doc->fileName()); - foreach (const QString include, allIncludes) { + foreach (const QString &include, allIncludes) { if (include.endsWith(QLatin1String("QtTest/qtest.h"))) { return true; } @@ -109,6 +114,27 @@ static bool includesQtTest(const CPlusPlus::Document::Ptr &doc, return false; } +static bool includesQtQuickTest(const CPlusPlus::Document::Ptr &doc, + const CppTools::CppModelManager *cppMM) +{ + const QList<CPlusPlus::Document::Include> includes = doc->resolvedIncludes(); + + foreach (const CPlusPlus::Document::Include &inc, includes) { + if (inc.unresolvedFileName() == QLatin1String("QtQuickTest/quicktest.h") + && inc.resolvedFileName().endsWith(QLatin1String("QtQuickTest/quicktest.h"))) { + return true; + } + } + + if (cppMM) { + foreach (const QString &include, cppMM->snapshot().allIncludesForDocument(doc->fileName())) { + if (include.endsWith(QLatin1String("QtQuickTest/quicktest.h"))) + return true; + } + } + return false; +} + static bool qtTestLibDefined(const CppTools::CppModelManager *cppMM, const QString &fileName) { @@ -118,24 +144,91 @@ static bool qtTestLibDefined(const CppTools::CppModelManager *cppMM, return false; } +static QString quickTestSrcDir(const CppTools::CppModelManager *cppMM, + const QString &fileName) +{ + static const QByteArray qtsd(" QUICK_TEST_SOURCE_DIR "); + const QList<CppTools::ProjectPart::Ptr> parts = cppMM->projectPart(fileName); + if (parts.size() > 0) { + QByteArray projDefines(parts.at(0)->projectDefines); + foreach (const QByteArray &line, projDefines.split('\n')) { + if (line.contains(qtsd)) { + QByteArray result = line.mid(line.indexOf(qtsd) + qtsd.length()); + if (result.startsWith('"')) + result.remove(result.length() - 1, 1).remove(0, 1); + if (result.startsWith("\\\"")) + result.remove(result.length() - 2, 2).remove(0, 2); + return QLatin1String(result); + } + } + } + return QString(); +} + static QString testClass(const CPlusPlus::Document::Ptr &doc) { - static QByteArray qtTestMacros[] = {"QTEST_MAIN", "QTEST_APPLESS_MAIN", "QTEST_GUILESS_MAIN"}; - QString tC; + static const QByteArray qtTestMacros[] = {"QTEST_MAIN", "QTEST_APPLESS_MAIN", "QTEST_GUILESS_MAIN"}; + const QList<CPlusPlus::Document::MacroUse> macros = doc->macroUses(); + + foreach (const CPlusPlus::Document::MacroUse ¯o, macros) { + if (!macro.isFunctionLike()) + continue; + 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 QString(); +} + +static QString quickTestName(const CPlusPlus::Document::Ptr &doc) +{ + static const QByteArray qtTestMacros[] = {"QUICK_TEST_MAIN", "QUICK_TEST_OPENGL_MAIN"}; const QList<CPlusPlus::Document::MacroUse> macros = doc->macroUses(); - foreach (const CPlusPlus::Document::MacroUse macro, macros) { + foreach (const CPlusPlus::Document::MacroUse ¯o, macros) { if (!macro.isFunctionLike()) continue; const QByteArray name = macro.macro().name(); if (name == qtTestMacros[0] || name == qtTestMacros[1] || name == qtTestMacros[2]) { CPlusPlus::Document::Block arg = macro.arguments().at(0); - tC = QLatin1String(getFileContent(doc->fileName()).mid(arg.bytesBegin(), - arg.bytesEnd() - arg.bytesBegin())); - break; + return QLatin1String(getFileContent(doc->fileName()).mid(arg.bytesBegin(), arg.bytesEnd() - arg.bytesBegin())); } } - return tC; + return QString(); +} + +static QList<QmlJS::Document::Ptr> scanDirectoryForQuickTestQmlFiles(const QString &srcDir) +{ + QStringList dirs(srcDir); + QmlJS::ModelManagerInterface *qmlJsMM = QmlJSTools::Internal::ModelManager::instance(); + // make sure even files not listed in pro file are available inside the snapshot + QFutureInterface<void> future; + QmlJS::PathsAndLanguages paths; + paths.maybeInsert(Utils::FileName::fromString(srcDir), QmlJS::Dialect::Qml); + QmlJS::ModelManagerInterface::importScan(future, qmlJsMM->workingCopy(), + paths, qmlJsMM, false, false); + + const QmlJS::Snapshot snapshot = QmlJSTools::Internal::ModelManager::instance()->snapshot(); + QDirIterator it(srcDir, QDir::Dirs | QDir::NoDotAndDotDot, QDirIterator::Subdirectories); + while (it.hasNext()) { + it.next(); + QFileInfo fi(it.fileInfo().canonicalFilePath()); + dirs << fi.filePath(); + } + QList<QmlJS::Document::Ptr> foundDocs; + + foreach (const QString &path, dirs) { + const QList<QmlJS::Document::Ptr> docs = snapshot.documentsInDirectory(path); + foreach (const QmlJS::Document::Ptr &doc, docs) { + const QString fileName(QFileInfo(doc->fileName()).fileName()); + if (fileName.startsWith(QLatin1String("tst_")) && fileName.endsWith(QLatin1String(".qml"))) + foundDocs << doc; + } + } + + return foundDocs; } /****** end of helpers ******/ @@ -145,6 +238,11 @@ void TestCodeParser::checkDocumentForTestCode(CPlusPlus::Document::Ptr doc) const QString file = doc->fileName(); const CppTools::CppModelManager *cppMM = CppTools::CppModelManager::instance(); + if (includesQtQuickTest(doc, cppMM)) { + handleQtQuickTest(doc); + return; + } + if (includesQtTest(doc, cppMM) && qtTestLibDefined(cppMM, file)) { QString tc(testClass(doc)); if (tc.isEmpty()) { @@ -187,7 +285,7 @@ void TestCodeParser::checkDocumentForTestCode(CPlusPlus::Document::Ptr doc) TestVisitor myVisitor(tc); myVisitor.accept(declaringDoc->globalNamespace()); const QMap<QString, TestCodeLocation> privSlots = myVisitor.privateSlots(); - foreach (const QString privS, privSlots.keys()) { + foreach (const QString &privS, privSlots.keys()) { const TestCodeLocation location = privSlots.value(privS); TestTreeItem *ttSub = new TestTreeItem(privS, location.m_fileName, TestTreeItem::TEST_FUNCTION, ttItem); @@ -253,7 +351,143 @@ void TestCodeParser::checkDocumentForTestCode(CPlusPlus::Document::Ptr doc) } } -void TestCodeParser::onDocumentUpdated(CPlusPlus::Document::Ptr doc) +void TestCodeParser::handleQtQuickTest(CPlusPlus::Document::Ptr doc) +{ + const CppTools::CppModelManager *cppMM = CppTools::CppModelManager::instance(); + + if (quickTestName(doc).isEmpty()) + return; + + const QString srcDir = quickTestSrcDir(cppMM, doc->fileName()); + if (srcDir.isEmpty()) + return; + + const QList<QmlJS::Document::Ptr> qmlDocs = scanDirectoryForQuickTestQmlFiles(srcDir); + foreach (const QmlJS::Document::Ptr &d, qmlDocs) { + QmlJS::AST::Node *ast = d->ast(); + if (!ast) { + qDebug() << "ast is zero pointer" << d->fileName(); // should not happen + continue; + } + TestQmlVisitor qmlVisitor(d); + QmlJS::AST::Node::accept(ast, &qmlVisitor); + + const QString tcName = qmlVisitor.testCaseName(); + const TestCodeLocation tcLocation = qmlVisitor.testCaseLocation(); + const QMap<QString, TestCodeLocation> 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()); + } + bool hadUnnamedTestsBefore; + TestTreeItem *ttItem = m_model->unnamedQuickTests(); + if (!ttItem) { + hadUnnamedTestsBefore = false; + ttItem = new TestTreeItem(QString(), QString(), TestTreeItem::TEST_CLASS, + quickTestRootItem); + foreach (const QString &func, testFunctions.keys()) { + const TestCodeLocation location = testFunctions.value(func); + TestTreeItem *ttSub = new TestTreeItem(func, location.m_fileName, + TestTreeItem::TEST_FUNCTION, ttItem); + ttSub->setLine(location.m_line); + ttSub->setColumn(location.m_column); + ttSub->setMainFile(doc->fileName()); + ttItem->appendChild(ttSub); + } + } else { + hadUnnamedTestsBefore = true; + // remove unnamed quick tests that are already found for this qml file + m_model->removeUnnamedQuickTest(d->fileName()); + + foreach (const QString &func, testFunctions.keys()) { + const TestCodeLocation location = testFunctions.value(func); + TestTreeItem *ttSub = new TestTreeItem(func, location.m_fileName, + TestTreeItem::TEST_FUNCTION, ttItem); + ttSub->setLine(location.m_line); + ttSub->setColumn(location.m_column); + ttSub->setMainFile(doc->fileName()); + ttItem->appendChild(ttSub); + } + } + TestInfo info = m_quickDocMap.contains(QLatin1String("<unnamed>")) + ? m_quickDocMap[QLatin1String("<unnamed>")] + : TestInfo(QString(), QStringList(), 666); + QStringList originalFunctions(info.testFunctions()); + foreach (const QString &func, testFunctions.keys()) { + if (!originalFunctions.contains(func)) + originalFunctions.append(func); + } + info.setTestFunctions(originalFunctions); + + if (hadUnnamedTestsBefore) + m_model->modifyQuickTestSubtree(ttItem->row(), ttItem); + else + m_model->addQuickTest(ttItem); + m_quickDocMap.insert(QLatin1String("<unnamed>"), info); + + continue; + } // end of handling test cases without name property + + // construct new/modified TestTreeItem + TestTreeItem *ttItem = new TestTreeItem(tcName, tcLocation.m_fileName, + TestTreeItem::TEST_CLASS, quickTestRootItem); + ttItem->setLine(tcLocation.m_line); + ttItem->setColumn(tcLocation.m_column); + ttItem->setMainFile(doc->fileName()); + + foreach (const QString &func, testFunctions.keys()) { + const TestCodeLocation location = testFunctions.value(func); + TestTreeItem *ttSub = new TestTreeItem(func, location.m_fileName, + TestTreeItem::TEST_FUNCTION, ttItem); + ttSub->setLine(location.m_line); + ttSub->setColumn(location.m_column); + ttItem->appendChild(ttSub); + } + + // update model and internal map + const QString fileName(tcLocation.m_fileName); + const QmlJS::Document::Ptr qmlDoc = + QmlJSTools::Internal::ModelManager::instance()->snapshot().document(fileName); + + 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()); + m_quickDocMap.insert(fileName, ti); + break; + } + } + delete ttItem; + } else { + // if it was formerly unnamed remove the respective items + if (m_quickDocMap.contains(QLatin1String("<unnamed>"))) { + m_model->removeUnnamedQuickTest(d->fileName()); + TestInfo unnamedInfo = m_quickDocMap[QLatin1String("<unnamed>")]; + QStringList functions = unnamedInfo.testFunctions(); + foreach (const QString &func, testFunctions.keys()) + if (functions.contains(func)) + functions.removeOne(func); + unnamedInfo.setTestFunctions(functions); + m_quickDocMap.insert(QLatin1String("<unnamed>"), unnamedInfo); + } + + m_model->addQuickTest(ttItem); + TestInfo ti(tcName, testFunctions.keys(), 0, qmlDoc->editorRevision()); + ti.setReferencingFile(doc->fileName()); + m_quickDocMap.insert(tcLocation.m_fileName, ti); + } + } +} + +void TestCodeParser::onCppDocumentUpdated(const CPlusPlus::Document::Ptr &doc) { if (!m_currentProject) return; @@ -270,13 +504,51 @@ void TestCodeParser::onDocumentUpdated(CPlusPlus::Document::Ptr doc) checkDocumentForTestCode(doc); } +void TestCodeParser::onQmlDocumentUpdated(const QmlJS::Document::Ptr &doc) +{ + if (!m_currentProject) + return; + const QString fileName = doc->fileName(); + if (m_quickDocMap.contains(fileName)) { + if ((int)m_quickDocMap[fileName].editorRevision() == doc->editorRevision()) { + qDebug("Skipped due revision equality (QML)"); // added to verify this ever happens.... + return; + } + } else if (!m_currentProject->files(ProjectExplorer::Project::AllFiles).contains(fileName)) { + // what if the file is not listed inside the pro file, but will be used anyway? + return; + } + const CPlusPlus::Snapshot snapshot = CppTools::CppModelManager::instance()->snapshot(); + if (m_quickDocMap.contains(fileName) + && snapshot.contains(m_quickDocMap[fileName].referencingFile())) { + checkDocumentForTestCode(snapshot.document(m_quickDocMap[fileName].referencingFile())); + } + if (!m_quickDocMap.contains(QLatin1String("<unnamed>"))) + return; + + // special case of having unnamed TestCases + TestTreeItem *unnamed = m_model->unnamedQuickTests(); + for (int row = 0, count = unnamed->childCount(); row < count; ++row) { + const TestTreeItem *child = unnamed->child(row); + if (fileName == child->filePath()) { + if (snapshot.contains(child->mainFile())) + checkDocumentForTestCode(snapshot.document(child->mainFile())); + break; + } + } +} + void TestCodeParser::removeFiles(const QStringList &files) { - foreach (const QString file, files) { + foreach (const QString &file, files) { if (m_cppDocMap.contains(file)) { m_cppDocMap.remove(file); m_model->removeAutoTestSubtreeByFilePath(file); } + if (m_quickDocMap.contains(file)) { + m_quickDocMap.remove(file); + m_model->removeQuickTestSubtreeByFilePath(file); + } } } @@ -286,7 +558,7 @@ void TestCodeParser::scanForTests() CppTools::CppModelManager *cppMM = CppTools::CppModelManager::instance(); CPlusPlus::Snapshot snapshot = cppMM->snapshot(); - foreach (const QString file, list) { + foreach (const QString &file, list) { if (snapshot.contains(file)) { CPlusPlus::Document::Ptr doc = snapshot.find(file).value(); checkDocumentForTestCode(doc); @@ -297,6 +569,7 @@ void TestCodeParser::scanForTests() void TestCodeParser::clearMaps() { m_cppDocMap.clear(); + m_quickDocMap.clear(); } } // namespace Internal diff --git a/plugins/autotest/testcodeparser.h b/plugins/autotest/testcodeparser.h index 192a9ca220..5b422ba0e6 100644 --- a/plugins/autotest/testcodeparser.h +++ b/plugins/autotest/testcodeparser.h @@ -21,6 +21,8 @@ #include <cplusplus/CppDocument.h> +#include <qmljs/qmljsdocument.h> + #include <QObject> #include <QMap> @@ -46,8 +48,10 @@ signals: public slots: void updateTestTree(); void checkDocumentForTestCode(CPlusPlus::Document::Ptr doc); + void handleQtQuickTest(CPlusPlus::Document::Ptr doc); - void onDocumentUpdated(CPlusPlus::Document::Ptr doc); + void onCppDocumentUpdated(const CPlusPlus::Document::Ptr &doc); + void onQmlDocumentUpdated(const QmlJS::Document::Ptr &doc); void removeFiles(const QStringList &files); private: @@ -56,6 +60,7 @@ private: TestTreeModel *m_model; QMap<QString, TestInfo> m_cppDocMap; + QMap<QString, TestInfo> m_quickDocMap; ProjectExplorer::Project *m_currentProject; }; diff --git a/plugins/autotest/testconfiguration.cpp b/plugins/autotest/testconfiguration.cpp index 4841a86fbf..bcb90d75b5 100644 --- a/plugins/autotest/testconfiguration.cpp +++ b/plugins/autotest/testconfiguration.cpp @@ -41,6 +41,26 @@ TestConfiguration::~TestConfiguration() m_testCases.clear(); } +/** + * @brief sets the test cases for this test configuration. + * + * Watch out for special handling of test configurations, because this method also + * updates the test case count to the current size of \a testCases. + * + * @param testCases list of names of the test functions / test cases + */ +void TestConfiguration::setTestCases(const QStringList &testCases) +{ + m_testCases.clear(); + m_testCases << testCases; + m_testCaseCount = m_testCases.size(); +} + +void TestConfiguration::setTestCaseCount(int count) +{ + m_testCaseCount = count; +} + void TestConfiguration::setTargetFile(const QString &targetFile) { m_targetFile = targetFile; diff --git a/plugins/autotest/testconfiguration.h b/plugins/autotest/testconfiguration.h index aab9d31db6..aa82b8e2b5 100644 --- a/plugins/autotest/testconfiguration.h +++ b/plugins/autotest/testconfiguration.h @@ -40,6 +40,8 @@ public: int testCaseCount = 0, QObject *parent = 0); ~TestConfiguration(); + void setTestCases(const QStringList &testCases); + void setTestCaseCount(int count); void setTargetFile(const QString &targetFile); void setTargetName(const QString &targetName); void setProFile(const QString &proFile); diff --git a/plugins/autotest/testrunner.cpp b/plugins/autotest/testrunner.cpp index e4b16ccbc4..ed2383bb3f 100644 --- a/plugins/autotest/testrunner.cpp +++ b/plugins/autotest/testrunner.cpp @@ -224,7 +224,7 @@ static QString which(const QString &path, const QString &cmd) paths = path.split(QLatin1Char(':')); #endif - foreach (const QString p, paths) { + foreach (const QString &p, paths) { const QString fName = p + QDir::separator() + cmd; QFileInfo fi(fName); if (fi.exists() && fi.isExecutable()) diff --git a/plugins/autotest/testtreeitem.cpp b/plugins/autotest/testtreeitem.cpp index 7fb8b85e19..3b7c27fcde 100644 --- a/plugins/autotest/testtreeitem.cpp +++ b/plugins/autotest/testtreeitem.cpp @@ -94,6 +94,10 @@ bool TestTreeItem::modifyContent(const TestTreeItem *modified) m_line = modified->m_line; hasBeenModified = true; } + if (m_mainFile != modified->m_mainFile) { + m_mainFile = modified->m_mainFile; + hasBeenModified = true; + } return hasBeenModified; } diff --git a/plugins/autotest/testtreeitem.h b/plugins/autotest/testtreeitem.h index edda539fd3..eaa1d68656 100644 --- a/plugins/autotest/testtreeitem.h +++ b/plugins/autotest/testtreeitem.h @@ -53,6 +53,8 @@ public: unsigned line() const { return m_line; } void setColumn(unsigned column) { m_column = column; } unsigned column() const { return m_column; } + QString mainFile() const { return m_mainFile; } + void setMainFile(const QString &mainFile) { m_mainFile = mainFile; } void setChecked(const Qt::CheckState checked); Qt::CheckState checked() const { return m_checked; } Type type() const { return m_type; } @@ -66,6 +68,7 @@ private: Type m_type; unsigned m_line; unsigned m_column; + QString m_mainFile; TestTreeItem *m_parent; QList<TestTreeItem *> m_children; }; diff --git a/plugins/autotest/testtreemodel.cpp b/plugins/autotest/testtreemodel.cpp index 214afb110a..38a38c51f9 100644 --- a/plugins/autotest/testtreemodel.cpp +++ b/plugins/autotest/testtreemodel.cpp @@ -43,11 +43,11 @@ TestTreeModel::TestTreeModel(QObject *parent) : QAbstractItemModel(parent), m_rootItem(new TestTreeItem(QString(), QString(), TestTreeItem::ROOT)), m_autoTestRootItem(new TestTreeItem(tr("Auto Tests"), QString(), TestTreeItem::ROOT, m_rootItem)), -// m_quickTestRootItem(new TestTreeItem(tr("Qt Quick Tests"), QString(), TestTreeItem::ROOT, m_rootItem)), + m_quickTestRootItem(new TestTreeItem(tr("Qt Quick Tests"), QString(), TestTreeItem::ROOT, m_rootItem)), m_parser(new TestCodeParser(this)) { m_rootItem->appendChild(m_autoTestRootItem); -// m_rootItem->appendChild(m_quickTestRootItem); + m_rootItem->appendChild(m_quickTestRootItem); m_parser->updateTestTree(); // CppTools::CppModelManagerInterface *cppMM = CppTools::CppModelManagerInterface::instance(); @@ -148,9 +148,11 @@ QVariant TestTreeModel::data(const QModelIndex &index, int role) const if (role == Qt::DisplayRole) { if ((item == m_autoTestRootItem && m_autoTestRootItem->childCount() == 0) - /*|| (item == m_quickTestRootItem && m_quickTestRootItem->childCount() == 0)*/) { + || (item == m_quickTestRootItem && m_quickTestRootItem->childCount() == 0)) { return QString(item->name() + tr(" (none)")); } else { + if (item->name().isEmpty()) + return tr("<unnamed>"); return item->name(); } @@ -158,6 +160,10 @@ QVariant TestTreeModel::data(const QModelIndex &index, int role) const } switch(role) { case Qt::ToolTipRole: + if (item->type() == TestTreeItem::TEST_CLASS && item->name().isEmpty()) + return tr("<p>Unnamed test cases can't be (un)checked - avoid this by assigning a name." + "<br/>Having unnamed test cases invalidates the check state of named test " + "cases with the same main.cpp when executing selected tests.</p>"); return item->filePath(); case Qt::DecorationRole: return testTreeIcon(item->type()); @@ -214,8 +220,12 @@ Qt::ItemFlags TestTreeModel::flags(const QModelIndex &index) const TestTreeItem *item = static_cast<TestTreeItem *>(index.internalPointer()); switch(item->type()) { case TestTreeItem::TEST_CLASS: + if (item->name().isEmpty()) + return Qt::ItemIsSelectable | Qt::ItemIsTristate; return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsTristate | Qt::ItemIsUserCheckable; case TestTreeItem::TEST_FUNCTION: + if (item->parent()->name().isEmpty()) + return Qt::ItemIsSelectable; return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsUserCheckable; case TestTreeItem::ROOT: default: @@ -245,7 +255,7 @@ bool TestTreeModel::removeRows(int row, int count, const QModelIndex &parent) bool TestTreeModel::hasTests() const { - return m_autoTestRootItem->childCount() > 0 /*|| m_quickTestRootItem->childCount() > 0*/; + return m_autoTestRootItem->childCount() > 0 || m_quickTestRootItem->childCount() > 0; } static void addProjectInformation(TestConfiguration *config, const QString &filePath) @@ -265,7 +275,7 @@ static void addProjectInformation(TestConfiguration *config, const QString &file if (project) { if (auto target = project->activeTarget()) { ProjectExplorer::BuildTargetInfoList appTargets = target->applicationTargets(); - foreach (ProjectExplorer::BuildTargetInfo bti, appTargets.list) { + foreach (const ProjectExplorer::BuildTargetInfo &bti, appTargets.list) { if (bti.isValid() && bti.projectFilePath.toString() == proFile) { targetFile = bti.targetFilePath.toString(); targetName = bti.targetName; @@ -301,15 +311,44 @@ QList<TestConfiguration *> TestTreeModel::getAllTestCases() const { QList<TestConfiguration *> result; - int count = m_autoTestRootItem->childCount(); - for (int row = 0; row < count; ++row) { - TestTreeItem *child = m_autoTestRootItem->child(row); + // get all Auto Tests + for (int row = 0, count = m_autoTestRootItem->childCount(); row < count; ++row) { + const TestTreeItem *child = m_autoTestRootItem->child(row); TestConfiguration *tc = new TestConfiguration(child->name(), QStringList(), child->childCount()); addProjectInformation(tc, child->filePath()); result << tc; } + + // get all Quick Tests + QMap<QString, int> foundMains; + for (int row = 0, count = m_quickTestRootItem->childCount(); row < count; ++row) { + TestTreeItem *child = m_quickTestRootItem->child(row); + // unnamed Quick Tests must be handled separately + if (child->name().isEmpty()) { + for (int childRow = 0, ccount = child->childCount(); childRow < ccount; ++ childRow) { + const TestTreeItem *grandChild = child->child(childRow); + const QString mainFile = grandChild->mainFile(); + foundMains.insert(mainFile, foundMains.contains(mainFile) + ? foundMains.value(mainFile) + 1 : 1); + } + continue; + } + // named Quick Test + const QString mainFile = child->mainFile(); + foundMains.insert(mainFile, foundMains.contains(mainFile) + ? foundMains.value(mainFile) + child->childCount() + : child->childCount()); + } + // create TestConfiguration for each main + foreach (const QString &mainFile, foundMains.keys()) { + TestConfiguration *tc = new TestConfiguration(QString(), QStringList(), + foundMains.value(mainFile)); + addProjectInformation(tc, mainFile); + result << tc; + } + return result; } @@ -318,8 +357,7 @@ QList<TestConfiguration *> TestTreeModel::getSelectedTests() const QList<TestConfiguration *> result; TestConfiguration *tc; - int count = m_autoTestRootItem->childCount(); - for (int row = 0; row < count; ++row) { + for (int row = 0, count = m_autoTestRootItem->childCount(); row < count; ++row) { TestTreeItem *child = m_autoTestRootItem->child(row); switch (child->checked()) { @@ -332,7 +370,7 @@ QList<TestConfiguration *> TestTreeModel::getSelectedTests() const continue; case Qt::PartiallyChecked: default: - QString childName = child->name(); + const QString childName = child->name(); int grandChildCount = child->childCount(); QStringList testCases; for (int grandChildRow = 0; grandChildRow < grandChildCount; ++grandChildRow) { @@ -346,23 +384,186 @@ QList<TestConfiguration *> TestTreeModel::getSelectedTests() const result << tc; } } + // Quick Tests must be handled differently - need the calling cpp file to use this in + // addProjectInformation() - additionally this must be unique to not execute the same executable + // on and on and on... + // TODO: do this later on for Auto Tests as well to support strange setups? or redo the model + + QMap<QString, TestConfiguration *> foundMains; + + TestTreeItem *unnamed = unnamedQuickTests(); + for (int childRow = 0, ccount = unnamed->childCount(); childRow < ccount; ++ childRow) { + const TestTreeItem *grandChild = unnamed->child(childRow); + const QString mainFile = grandChild->mainFile(); + if (foundMains.contains(mainFile)) { + foundMains[mainFile]->setTestCaseCount(tc->testCaseCount() + 1); + } else { + TestConfiguration *tc = new TestConfiguration(QString(), QStringList()); + tc->setTestCaseCount(1); + addProjectInformation(tc, mainFile); + foundMains.insert(mainFile, tc); + } + } + + for (int row = 0, count = m_quickTestRootItem->childCount(); row < count; ++row) { + TestTreeItem *child = m_quickTestRootItem->child(row); + // unnamed Quick Tests have been handled separately already + if (child->name().isEmpty()) + continue; + + // named Quick Tests + switch (child->checked()) { + case Qt::Unchecked: + continue; + case Qt::Checked: + case Qt::PartiallyChecked: + default: + QStringList testFunctions; + int grandChildCount = child->childCount(); + for (int grandChildRow = 0; grandChildRow < grandChildCount; ++grandChildRow) { + const TestTreeItem *grandChild = child->child(grandChildRow); + if (grandChild->checked() == Qt::Checked) + testFunctions << child->name() + QLatin1String("::") + grandChild->name(); + } + TestConfiguration *tc; + if (foundMains.contains(child->mainFile())) { + tc = foundMains[child->mainFile()]; + QStringList oldFunctions(tc->testCases()); + // if oldFunctions.size() is 0 this test configuration is used for at least one + // unnamed test case + if (oldFunctions.size() == 0) { + tc->setTestCaseCount(tc->testCaseCount() + testFunctions.size()); + } else { + oldFunctions << testFunctions; + tc->setTestCases(oldFunctions); + } + } else { + tc = new TestConfiguration(QString(), testFunctions); + addProjectInformation(tc, child->mainFile()); + foundMains.insert(child->mainFile(), tc); + } + break; + } + } + + foreach (TestConfiguration *config, foundMains.values()) + result << config; + return result; } +TestTreeItem *TestTreeModel::unnamedQuickTests() const +{ + for (int row = 0, count = m_quickTestRootItem->childCount(); row < count; ++row) { + TestTreeItem *child = m_quickTestRootItem->child(row); + if (child->name().isEmpty()) + return child; + } + return 0; +} + void TestTreeModel::modifyAutoTestSubtree(int row, TestTreeItem *newItem) { - static QVector<int> modificationRoles = QVector<int>() << Qt::DisplayRole - << Qt::ToolTipRole << LinkRole; QModelIndex toBeModifiedIndex = index(0, 0).child(row, 0); + modifyTestSubtree(toBeModifiedIndex, newItem); +} + +void TestTreeModel::removeAutoTestSubtreeByFilePath(const QString &file) +{ + const QModelIndex atRootIndex = index(0, 0); + const int count = rowCount(atRootIndex); + for (int row = 0; row < count; ++row) { + const QModelIndex childIndex = atRootIndex.child(row, 0); + TestTreeItem *childItem = static_cast<TestTreeItem *>(childIndex.internalPointer()); + if (file == childItem->filePath()) { + removeRow(row, atRootIndex); + break; + } + } + emit testTreeModelChanged(); +} + +void TestTreeModel::addAutoTest(TestTreeItem *newItem) +{ + beginInsertRows(index(0, 0), m_autoTestRootItem->childCount(), m_autoTestRootItem->childCount()); + m_autoTestRootItem->appendChild(newItem); + endInsertRows(); + emit testTreeModelChanged(); +} + +void TestTreeModel::removeAllAutoTests() +{ + beginResetModel(); + m_autoTestRootItem->removeChildren(); + endResetModel(); + emit testTreeModelChanged(); +} + +void TestTreeModel::modifyQuickTestSubtree(int row, TestTreeItem *newItem) +{ + QModelIndex toBeModifiedIndex = index(1, 0).child(row, 0); + modifyTestSubtree(toBeModifiedIndex, newItem); +} + +void TestTreeModel::removeQuickTestSubtreeByFilePath(const QString &file) +{ + const QModelIndex qtRootIndex = index(1, 0); + for (int row = 0, count = rowCount(qtRootIndex); row < count; ++row) { + const QModelIndex childIndex = qtRootIndex.child(row, 0); + const TestTreeItem *childItem = static_cast<TestTreeItem *>(childIndex.internalPointer()); + if (file == childItem->filePath()) { + removeRow(row, qtRootIndex); + break; + } + } + emit testTreeModelChanged(); +} + +void TestTreeModel::addQuickTest(TestTreeItem *newItem) +{ + beginInsertRows(index(1, 0), m_quickTestRootItem->childCount(), m_quickTestRootItem->childCount()); + m_quickTestRootItem->appendChild(newItem); + endInsertRows(); + emit testTreeModelChanged(); +} + +void TestTreeModel::removeAllQuickTests() +{ + beginResetModel(); + m_quickTestRootItem->removeChildren(); + endResetModel(); + emit testTreeModelChanged(); +} + +void TestTreeModel::removeUnnamedQuickTest(const QString &filePath) +{ + TestTreeItem *unnamedQT = unnamedQuickTests(); + if (!unnamedQT) + return; + + const QModelIndex unnamedQTIndex = index(1, 0).child(unnamedQT->row(), 0); + for (int childRow = unnamedQT->childCount() - 1; childRow >= 0; --childRow) { + const TestTreeItem *child = unnamedQT->child(childRow); + if (filePath == child->filePath()) + removeRow(childRow, unnamedQTIndex); + } + emit testTreeModelChanged(); +} + +void TestTreeModel::modifyTestSubtree(QModelIndex &toBeModifiedIndex, TestTreeItem *newItem) +{ if (!toBeModifiedIndex.isValid()) return; + + static QVector<int> modificationRoles = QVector<int>() << Qt::DisplayRole + << Qt::ToolTipRole << LinkRole; TestTreeItem *toBeModifiedItem = static_cast<TestTreeItem *>(toBeModifiedIndex.internalPointer()); if (toBeModifiedItem->modifyContent(newItem)) emit dataChanged(toBeModifiedIndex, toBeModifiedIndex, modificationRoles); // process sub-items as well... - int childCount = toBeModifiedItem->childCount(); - int newChildCount = newItem->childCount(); + const int childCount = toBeModifiedItem->childCount(); + const int newChildCount = newItem->childCount(); // for keeping the CheckState on modifications QHash<QString, Qt::CheckState> originalItems; @@ -427,36 +628,5 @@ void TestTreeModel::modifyAutoTestSubtree(int row, TestTreeItem *newItem) emit testTreeModelChanged(); } -void TestTreeModel::removeAutoTestSubtreeByFilePath(const QString &file) -{ - QModelIndex atRootIndex = index(0, 0); - int count = rowCount(atRootIndex); - for (int row = 0; row < count; ++row) { - QModelIndex childIndex = atRootIndex.child(row, 0); - TestTreeItem *childItem = static_cast<TestTreeItem *>(childIndex.internalPointer()); - if (file == childItem->filePath()) { - removeRow(row, atRootIndex); - break; - } - } - emit testTreeModelChanged(); -} - -void TestTreeModel::addAutoTest(TestTreeItem *newItem) -{ - beginInsertRows(index(0, 0), m_autoTestRootItem->childCount(), m_autoTestRootItem->childCount()); - m_autoTestRootItem->appendChild(newItem); - endInsertRows(); - emit testTreeModelChanged(); -} - -void TestTreeModel::removeAllAutoTests() -{ - beginResetModel(); - m_autoTestRootItem->removeChildren(); - endResetModel(); - emit testTreeModelChanged(); -} - } // namespace Internal } // namespace Autotest diff --git a/plugins/autotest/testtreemodel.h b/plugins/autotest/testtreemodel.h index 0a07ef0e3a..72ecf88ff4 100644 --- a/plugins/autotest/testtreemodel.h +++ b/plugins/autotest/testtreemodel.h @@ -60,11 +60,19 @@ public: bool hasTests() const; QList<TestConfiguration *> getAllTestCases() const; QList<TestConfiguration *> getSelectedTests() const; + TestTreeItem *unnamedQuickTests() const; void modifyAutoTestSubtree(int row, TestTreeItem *newItem); void removeAutoTestSubtreeByFilePath(const QString &file); void addAutoTest(TestTreeItem *newItem); void removeAllAutoTests(); + + void modifyQuickTestSubtree(int row, TestTreeItem *newItem); + void removeQuickTestSubtreeByFilePath(const QString &file); + void addQuickTest(TestTreeItem *newItem); + void removeAllQuickTests(); + void removeUnnamedQuickTest(const QString &filePath); + signals: void testTreeModelChanged(); @@ -72,10 +80,11 @@ public slots: private: explicit TestTreeModel(QObject *parent = 0); + void modifyTestSubtree(QModelIndex &toBeModifiedIndex, TestTreeItem *newItem); TestTreeItem *m_rootItem; TestTreeItem *m_autoTestRootItem; -// TestTreeItem *m_quickTestRootItem; + TestTreeItem *m_quickTestRootItem; TestCodeParser *m_parser; }; diff --git a/plugins/autotest/testtreeview.cpp b/plugins/autotest/testtreeview.cpp index 73d0fbbbd9..3773983289 100644 --- a/plugins/autotest/testtreeview.cpp +++ b/plugins/autotest/testtreeview.cpp @@ -32,6 +32,8 @@ #include <projectexplorer/project.h> #include <projectexplorer/session.h> +#include <qmljstools/qmljsmodelmanager.h> + #include <texteditor/texteditor.h> #include <QToolButton> @@ -61,11 +63,16 @@ TestTreeViewWidget::TestTreeViewWidget(QWidget *parent) : CppTools::CppModelManager *cppMM = CppTools::CppModelManager::instance(); connect(cppMM, &CppTools::CppModelManager::documentUpdated, - parser, &TestCodeParser::onDocumentUpdated, Qt::QueuedConnection); - + parser, &TestCodeParser::onCppDocumentUpdated, Qt::QueuedConnection); connect(cppMM, &CppTools::CppModelManager::aboutToRemoveFiles, parser, &TestCodeParser::removeFiles, Qt::QueuedConnection); + QmlJS::ModelManagerInterface *qmlJsMM = QmlJSTools::Internal::ModelManager::instance(); + connect(qmlJsMM, &QmlJS::ModelManagerInterface::documentUpdated, + parser, &TestCodeParser::onQmlDocumentUpdated, Qt::QueuedConnection); + connect(qmlJsMM, &QmlJS::ModelManagerInterface::aboutToRemoveFiles, + parser, &TestCodeParser::removeFiles, Qt::QueuedConnection); + connect(m_view, &TestTreeView::activated, this, &TestTreeViewWidget::onItemActivated); } @@ -208,28 +215,32 @@ void TestTreeView::deselectAll() void TestTreeView::selectOrDeselectAll(const Qt::CheckState checkState) { const TestTreeModel *model = TestTreeModel::instance(); - QModelIndex autoTestsIndex = model->index(0, 0, rootIndex()); - if (!autoTestsIndex.isValid()) - return; - int count = model->rowCount(autoTestsIndex); - QModelIndex last; - for (int i = 0; i < count; ++i) { - const QModelIndex classesIndex = model->index(i, 0, autoTestsIndex); - int funcCount = model->rowCount(classesIndex); - TestTreeItem *item = static_cast<TestTreeItem *>(classesIndex.internalPointer()); - if (item) { - item->setChecked(checkState); - if (!item->childCount()) - last = classesIndex; - } - for (int j = 0; j < funcCount; ++j) { - last = model->index(j, 0, classesIndex); - TestTreeItem *item = static_cast<TestTreeItem *>(last.internalPointer()); - if (item) + + // 2 == Auto Tests and Quick Tests - must be raised if there will be others + for (int rootRow = 0; rootRow < 2; ++rootRow) { + QModelIndex currentRootIndex = model->index(rootRow, 0, rootIndex()); + if (!currentRootIndex.isValid()) + return; + int count = model->rowCount(currentRootIndex); + QModelIndex last; + for (int classesRow = 0; classesRow < count; ++classesRow) { + const QModelIndex classesIndex = model->index(classesRow, 0, currentRootIndex); + int funcCount = model->rowCount(classesIndex); + TestTreeItem *item = static_cast<TestTreeItem *>(classesIndex.internalPointer()); + if (item) { item->setChecked(checkState); + if (!item->childCount()) + last = classesIndex; + } + for (int functionRow = 0; functionRow < funcCount; ++functionRow) { + last = model->index(functionRow, 0, classesIndex); + TestTreeItem *item = static_cast<TestTreeItem *>(last.internalPointer()); + if (item) + item->setChecked(checkState); + } } + emit dataChanged(currentRootIndex, last); } - emit dataChanged(autoTestsIndex, last); } } // namespace Internal diff --git a/plugins/autotest/testvisitor.cpp b/plugins/autotest/testvisitor.cpp index e1c4fda65b..199f3236b2 100644 --- a/plugins/autotest/testvisitor.cpp +++ b/plugins/autotest/testvisitor.cpp @@ -26,11 +26,15 @@ #include <cpptools/cppmodelmanager.h> +#include <qmljs/parser/qmljsast_p.h> + #include <QList> namespace Autotest { namespace Internal { +/************************** Cpp Test Symbol Visitor ***************************/ + TestVisitor::TestVisitor(const QString &fullQualifiedClassName) : m_className(fullQualifiedClassName) { @@ -59,10 +63,11 @@ bool TestVisitor::visit(CPlusPlus::Class *symbol) if (className != m_className) continue; - if (auto func = type->asFunctionType()) { + if (const auto func = type->asFunctionType()) { if (func->isSlot() && member->isPrivate()) { const QString name = o.prettyName(func->name()); if (!ignoredFunctions.contains(name) && !name.endsWith(QLatin1String("_data"))) { + // TODO use definition of function instead of declaration! TestCodeLocation location; location.m_fileName = QLatin1String(member->fileName()); location.m_line = member->line(); @@ -75,6 +80,8 @@ bool TestVisitor::visit(CPlusPlus::Class *symbol) return true; } +/**************************** Cpp Test AST Visitor ****************************/ + TestAstVisitor::TestAstVisitor(CPlusPlus::Document::Ptr doc) : ASTVisitor(doc->translationUnit()), m_currentDoc(doc) @@ -90,15 +97,15 @@ bool TestAstVisitor::visit(CPlusPlus::CallAST *ast) if (!m_currentScope || m_currentDoc.isNull()) return false; - if (auto expressionAST = ast->base_expression) { - if (auto idExpressionAST = expressionAST->asIdExpression()) { - if (auto qualifiedNameAST = idExpressionAST->name->asQualifiedName()) { + if (const auto expressionAST = ast->base_expression) { + if (const auto idExpressionAST = expressionAST->asIdExpression()) { + if (const auto qualifiedNameAST = idExpressionAST->name->asQualifiedName()) { const CPlusPlus::Overview o; const QString prettyName = o.prettyName(qualifiedNameAST->name); if (prettyName == QLatin1String("QTest::qExec")) { - if (auto expressionListAST = ast->expression_list) { + if (const auto expressionListAST = ast->expression_list) { // first argument is the one we need - if (auto argumentExpressionAST = expressionListAST->value) { + if (const auto argumentExpressionAST = expressionListAST->value) { CPlusPlus::TypeOfExpression toe; CppTools::CppModelManager *cppMM = CppTools::CppModelManager::instance(); toe.init(m_currentDoc, cppMM->snapshot()); @@ -106,7 +113,7 @@ bool TestAstVisitor::visit(CPlusPlus::CallAST *ast) = toe(argumentExpressionAST, m_currentDoc, m_currentScope); if (toeItems.size()) { - if (auto pointerType = toeItems.first().type()->asPointerType()) + if (const auto pointerType = toeItems.first().type()->asPointerType()) m_className = o.prettyType(pointerType->elementType()); } } @@ -124,5 +131,63 @@ bool TestAstVisitor::visit(CPlusPlus::CompoundStatementAST *ast) return true; } +/*************************** Quick Test AST Visitor ***************************/ + +TestQmlVisitor::TestQmlVisitor(QmlJS::Document::Ptr doc) + : m_currentDoc(doc) +{ +} + +TestQmlVisitor::~TestQmlVisitor() +{ +} + +bool TestQmlVisitor::visit(QmlJS::AST::UiObjectDefinition *ast) +{ + const QStringRef name = ast->qualifiedTypeNameId->name; + if (name != QLatin1String("TestCase")) + return false; + + m_currentTestCaseName.clear(); + const auto sourceLocation = ast->firstSourceLocation(); + m_testCaseLocation.m_fileName = m_currentDoc->fileName(); + m_testCaseLocation.m_line = sourceLocation.startLine; + m_testCaseLocation.m_column = sourceLocation.startColumn - 1; + return true; +} + +bool TestQmlVisitor::visit(QmlJS::AST::ExpressionStatement *ast) +{ + const QmlJS::AST::ExpressionNode *expr = ast->expression; + return expr->kind == QmlJS::AST::Node::Kind_StringLiteral; +} + +bool TestQmlVisitor::visit(QmlJS::AST::UiScriptBinding *ast) +{ + const QStringRef name = ast->qualifiedId->name; + return name == QLatin1String("name"); +} + +bool TestQmlVisitor::visit(QmlJS::AST::FunctionDeclaration *ast) +{ + const QStringRef name = ast->name; + if (name.startsWith(QLatin1String("test_"))) { + const auto sourceLocation = ast->firstSourceLocation(); + TestCodeLocation location; + location.m_fileName = m_currentDoc->fileName(); + location.m_line = sourceLocation.startLine; + location.m_column = sourceLocation.startColumn - 1; + + m_testFunctions.insert(name.toString(), location); + } + return false; +} + +bool TestQmlVisitor::visit(QmlJS::AST::StringLiteral *ast) +{ + m_currentTestCaseName = ast->value.toString(); + return false; +} + } // namespace Internal } // namespace Autotest diff --git a/plugins/autotest/testvisitor.h b/plugins/autotest/testvisitor.h index 04076cf80c..92bfa009ae 100644 --- a/plugins/autotest/testvisitor.h +++ b/plugins/autotest/testvisitor.h @@ -24,6 +24,9 @@ #include <cplusplus/Scope.h> #include <cplusplus/SymbolVisitor.h> +#include <qmljs/parser/qmljsastvisitor_p.h> +#include <qmljs/qmljsdocument.h> + #include <QMap> #include <QString> @@ -69,6 +72,30 @@ private: }; +class TestQmlVisitor : public QmlJS::AST::Visitor +{ +public: + TestQmlVisitor(QmlJS::Document::Ptr doc); + virtual ~TestQmlVisitor(); + + bool visit(QmlJS::AST::UiObjectDefinition *ast); + bool visit(QmlJS::AST::ExpressionStatement *ast); + bool visit(QmlJS::AST::UiScriptBinding *ast); + bool visit(QmlJS::AST::FunctionDeclaration *ast); + bool visit(QmlJS::AST::StringLiteral *ast); + + QString testCaseName() const { return m_currentTestCaseName; } + TestCodeLocation testCaseLocation() const { return m_testCaseLocation; } + QMap<QString, TestCodeLocation> testFunctions() const { return m_testFunctions; } + +private: + QmlJS::Document::Ptr m_currentDoc; + QString m_currentTestCaseName; + TestCodeLocation m_testCaseLocation; + QMap<QString, TestCodeLocation> m_testFunctions; + +}; + } // namespace Internal } // namespace Autotest |