/**************************************************************************** ** ** Copyright (C) 2016 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of Qt Creator. ** ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see https://www.qt.io/terms-conditions. For further ** information use the contact form at https://www.qt.io/contact-us. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 3 as published by the Free Software ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-3.0.html. ** ****************************************************************************/ #include "quicktestparser.h" #include "quicktesttreeitem.h" #include "quicktestvisitors.h" #include "quicktest_utils.h" #include "../autotest_utils.h" #include #include #include #include #include namespace Autotest { namespace Internal { TestTreeItem *QuickTestParseResult::createTestTreeItem() const { if (itemType == TestTreeItem::Root || itemType == TestTreeItem::TestDataTag) return 0; return QuickTestTreeItem::createTestItem(this); } static bool includesQtQuickTest(const CPlusPlus::Document::Ptr &doc, const CPlusPlus::Snapshot &snapshot) { static QStringList expectedHeaderPrefixes = Utils::HostOsInfo::isMacHost() ? QStringList({ QLatin1String("QtQuickTest.framework/Headers"), QLatin1String("QtQuickTest") }) : QStringList({ QLatin1String("QtQuickTest") }); const QList includes = doc->resolvedIncludes(); foreach (const CPlusPlus::Document::Include &inc, includes) { if (inc.unresolvedFileName() == QLatin1String("QtQuickTest/quicktest.h")) { foreach (const QString &prefix, expectedHeaderPrefixes) { if (inc.resolvedFileName().endsWith( QString::fromLatin1("%1/quicktest.h").arg(prefix))) { return true; } } } } foreach (const QString &include, snapshot.allIncludesForDocument(doc->fileName())) { foreach (const QString &prefix, expectedHeaderPrefixes) { if (include.endsWith(QString::fromLatin1("%1/quicktest.h").arg(prefix))) return true; } } return false; } static QString quickTestSrcDir(const CppTools::CppModelManager *cppMM, const QString &fileName) { static const QByteArray qtsd(" QUICK_TEST_SOURCE_DIR "); const QList 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 quickTestName(const CPlusPlus::Document::Ptr &doc) { const QList macros = doc->macroUses(); foreach (const CPlusPlus::Document::MacroUse ¯o, macros) { if (!macro.isFunctionLike()) continue; const QByteArray name = macro.macro().name(); if (QuickTestUtils::isQuickTestMacro(name)) { CPlusPlus::Document::Block arg = macro.arguments().at(0); return QLatin1String(CppParser::getFileContent(doc->fileName()) .mid(arg.bytesBegin(), arg.bytesEnd() - arg.bytesBegin())); } } return QString(); } static QList 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 future; QmlJS::PathsAndLanguages paths; paths.maybeInsert(Utils::FileName::fromString(srcDir), QmlJS::Dialect::Qml); const bool emitDocumentChanges = false; const bool onlyTheLib = false; QmlJS::ModelManagerInterface::importScan(future, qmlJsMM->workingCopy(), paths, qmlJsMM, emitDocumentChanges, onlyTheLib); 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 foundDocs; foreach (const QString &path, dirs) { const QList 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; } static bool checkQmlDocumentForQuickTestCode(QFutureInterface futureInterface, const QmlJS::Document::Ptr &qmlJSDoc, const Core::Id &id, const QString &proFile = QString()) { if (qmlJSDoc.isNull()) return false; QmlJS::AST::Node *ast = qmlJSDoc->ast(); QTC_ASSERT(ast, return false); TestQmlVisitor qmlVisitor(qmlJSDoc); QmlJS::AST::Node::accept(ast, &qmlVisitor); const QString testCaseName = qmlVisitor.testCaseName(); const TestCodeLocationAndType tcLocationAndType = qmlVisitor.testCaseLocation(); const QMap &testFunctions = qmlVisitor.testFunctions(); QuickTestParseResult *parseResult = new QuickTestParseResult(id); parseResult->proFile = proFile; parseResult->itemType = TestTreeItem::TestCase; QMap::ConstIterator it = testFunctions.begin(); const QMap::ConstIterator end = testFunctions.end(); for ( ; it != end; ++it) { const TestCodeLocationAndType &loc = it.value(); QuickTestParseResult *funcResult = new QuickTestParseResult(id); funcResult->name = it.key(); funcResult->displayName = it.key(); funcResult->itemType = loc.m_type; funcResult->fileName = loc.m_name; funcResult->line = loc.m_line; funcResult->column = loc.m_column; funcResult->proFile = proFile; parseResult->children.append(funcResult); } if (!testCaseName.isEmpty()) { parseResult->fileName = tcLocationAndType.m_name; parseResult->name = testCaseName; parseResult->line = tcLocationAndType.m_line; parseResult->column = tcLocationAndType.m_column; } futureInterface.reportResult(TestParseResultPtr(parseResult)); return true; } static bool handleQtQuickTest(QFutureInterface futureInterface, CPlusPlus::Document::Ptr document, const Core::Id &id) { const CppTools::CppModelManager *modelManager = CppTools::CppModelManager::instance(); if (quickTestName(document).isEmpty()) return false; const QString cppFileName = document->fileName(); QList ppList = modelManager->projectPart(cppFileName); if (ppList.isEmpty()) // happens if shutting down while parsing return false; const QString &proFile = ppList.at(0)->projectFile; const QString srcDir = quickTestSrcDir(modelManager, cppFileName); if (srcDir.isEmpty()) return false; const QList qmlDocs = scanDirectoryForQuickTestQmlFiles(srcDir); bool result = false; foreach (const QmlJS::Document::Ptr &qmlJSDoc, qmlDocs) result |= checkQmlDocumentForQuickTestCode(futureInterface, qmlJSDoc, id, proFile); return result; } void QuickTestParser::init(const QStringList &filesToParse) { m_qmlSnapshot = QmlJSTools::Internal::ModelManager::instance()->snapshot(); m_proFilesForQmlFiles = QuickTestUtils::proFilesForQmlFiles(id(), filesToParse); CppParser::init(filesToParse); } void QuickTestParser::release() { m_qmlSnapshot = QmlJS::Snapshot(); m_proFilesForQmlFiles.clear(); CppParser::release(); } bool QuickTestParser::processDocument(QFutureInterface futureInterface, const QString &fileName) { if (fileName.endsWith(".qml")) { const QString &proFile = m_proFilesForQmlFiles.value(fileName); if (proFile.isEmpty()) return false; QmlJS::Document::Ptr qmlJSDoc = m_qmlSnapshot.document(fileName); return checkQmlDocumentForQuickTestCode(futureInterface, qmlJSDoc, id(), proFile); } if (!m_cppSnapshot.contains(fileName) || !selectedForBuilding(fileName)) return false; CPlusPlus::Document::Ptr document = m_cppSnapshot.find(fileName).value(); if (!includesQtQuickTest(document, m_cppSnapshot)) return false; return handleQtQuickTest(futureInterface, document, id()); } } // namespace Internal } // namespace Autotest