2016-05-11 13:02:42 +02:00
|
|
|
/****************************************************************************
|
|
|
|
|
**
|
|
|
|
|
** 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"
|
2017-03-06 15:24:16 +01:00
|
|
|
#include "../testcodeparser.h"
|
2016-05-11 13:02:42 +02:00
|
|
|
|
2017-03-06 15:24:16 +01:00
|
|
|
#include <projectexplorer/session.h>
|
2016-05-11 13:02:42 +02:00
|
|
|
#include <qmljs/parser/qmljsast_p.h>
|
|
|
|
|
#include <qmljs/qmljsdialect.h>
|
|
|
|
|
#include <qmljstools/qmljsmodelmanager.h>
|
|
|
|
|
#include <utils/hostosinfo.h>
|
|
|
|
|
#include <utils/qtcassert.h>
|
|
|
|
|
|
2017-03-06 15:24:16 +01:00
|
|
|
#include <QFileSystemWatcher>
|
|
|
|
|
|
2016-05-11 13:02:42 +02:00
|
|
|
namespace Autotest {
|
|
|
|
|
namespace Internal {
|
|
|
|
|
|
2017-03-06 15:24:16 +01:00
|
|
|
static QFileSystemWatcher s_directoryWatcher;
|
|
|
|
|
|
2016-05-11 13:02:42 +02:00
|
|
|
TestTreeItem *QuickTestParseResult::createTestTreeItem() const
|
|
|
|
|
{
|
|
|
|
|
if (itemType == TestTreeItem::Root || itemType == TestTreeItem::TestDataTag)
|
2017-01-05 12:03:42 +01:00
|
|
|
return nullptr;
|
|
|
|
|
|
|
|
|
|
QuickTestTreeItem *item = new QuickTestTreeItem(name, fileName, itemType);
|
|
|
|
|
item->setProFile(proFile);
|
|
|
|
|
item->setLine(line);
|
|
|
|
|
item->setColumn(column);
|
2017-02-13 10:05:06 +01:00
|
|
|
for (const TestParseResult *funcResult : children)
|
2017-01-05 12:03:42 +01:00
|
|
|
item->appendChild(funcResult->createTestTreeItem());
|
|
|
|
|
return item;
|
2016-05-11 13:02:42 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static bool includesQtQuickTest(const CPlusPlus::Document::Ptr &doc,
|
|
|
|
|
const CPlusPlus::Snapshot &snapshot)
|
|
|
|
|
{
|
|
|
|
|
static QStringList expectedHeaderPrefixes
|
|
|
|
|
= Utils::HostOsInfo::isMacHost()
|
2017-02-22 15:09:35 +01:00
|
|
|
? QStringList({"QtQuickTest.framework/Headers", "QtQuickTest"})
|
|
|
|
|
: QStringList({"QtQuickTest"});
|
2016-05-11 13:02:42 +02:00
|
|
|
|
|
|
|
|
const QList<CPlusPlus::Document::Include> includes = doc->resolvedIncludes();
|
|
|
|
|
|
2017-02-13 10:05:06 +01:00
|
|
|
for (const CPlusPlus::Document::Include &inc : includes) {
|
2016-09-29 12:15:43 +02:00
|
|
|
if (inc.unresolvedFileName() == "QtQuickTest/quicktest.h") {
|
2017-02-13 10:05:06 +01:00
|
|
|
for (const QString &prefix : expectedHeaderPrefixes) {
|
2016-05-11 13:02:42 +02:00
|
|
|
if (inc.resolvedFileName().endsWith(
|
2016-09-29 12:15:43 +02:00
|
|
|
QString("%1/quicktest.h").arg(prefix))) {
|
2016-05-11 13:02:42 +02:00
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-02-13 10:05:06 +01:00
|
|
|
for (const QString &include : snapshot.allIncludesForDocument(doc->fileName())) {
|
|
|
|
|
for (const QString &prefix : expectedHeaderPrefixes) {
|
2016-09-29 12:15:43 +02:00
|
|
|
if (include.endsWith(QString("%1/quicktest.h").arg(prefix)))
|
2016-05-11 13:02:42 +02:00
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
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);
|
2017-02-13 10:05:06 +01:00
|
|
|
for (const QByteArray &line : projDefines.split('\n')) {
|
2016-05-11 13:02:42 +02:00
|
|
|
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<CPlusPlus::Document::MacroUse> macros = doc->macroUses();
|
|
|
|
|
|
2017-02-13 10:05:06 +01:00
|
|
|
for (const CPlusPlus::Document::MacroUse ¯o : macros) {
|
2016-05-11 13:02:42 +02:00
|
|
|
if (!macro.isFunctionLike())
|
|
|
|
|
continue;
|
|
|
|
|
const QByteArray name = macro.macro().name();
|
|
|
|
|
if (QuickTestUtils::isQuickTestMacro(name)) {
|
|
|
|
|
CPlusPlus::Document::Block arg = macro.arguments().at(0);
|
2016-06-20 07:03:55 +02:00
|
|
|
return QLatin1String(CppParser::getFileContent(doc->fileName())
|
2016-05-11 13:02:42 +02:00
|
|
|
.mid(arg.bytesBegin(), arg.bytesEnd() - arg.bytesBegin()));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
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,
|
2017-03-07 15:49:02 +01:00
|
|
|
false /*emitDocumentChanges*/, false /*onlyTheLib*/, true /*forceRescan*/ );
|
2016-05-11 13:02:42 +02:00
|
|
|
|
|
|
|
|
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();
|
|
|
|
|
}
|
2017-03-06 15:24:16 +01:00
|
|
|
s_directoryWatcher.addPaths(dirs);
|
|
|
|
|
|
2016-05-11 13:02:42 +02:00
|
|
|
QList<QmlJS::Document::Ptr> foundDocs;
|
|
|
|
|
|
2017-02-13 10:05:06 +01:00
|
|
|
for (const QString &path : dirs) {
|
2016-05-11 13:02:42 +02:00
|
|
|
const QList<QmlJS::Document::Ptr> docs = snapshot.documentsInDirectory(path);
|
2017-02-13 10:05:06 +01:00
|
|
|
for (const QmlJS::Document::Ptr &doc : docs) {
|
2017-03-06 15:18:07 +01:00
|
|
|
const QFileInfo fi(doc->fileName());
|
|
|
|
|
// using working copy above might provide no more existing files
|
|
|
|
|
if (!fi.exists())
|
|
|
|
|
continue;
|
|
|
|
|
const QString fileName(fi.fileName());
|
2016-09-29 12:15:43 +02:00
|
|
|
if (fileName.startsWith("tst_") && fileName.endsWith(".qml"))
|
2016-05-11 13:02:42 +02:00
|
|
|
foundDocs << doc;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return foundDocs;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static bool checkQmlDocumentForQuickTestCode(QFutureInterface<TestParseResultPtr> futureInterface,
|
2017-02-27 09:30:16 +01:00
|
|
|
const QmlJS::Snapshot &snapshot,
|
2016-05-11 13:02:42 +02:00
|
|
|
const QmlJS::Document::Ptr &qmlJSDoc,
|
2016-06-06 14:18:38 +02:00
|
|
|
const Core::Id &id,
|
2016-05-11 13:02:42 +02:00
|
|
|
const QString &proFile = QString())
|
|
|
|
|
{
|
|
|
|
|
if (qmlJSDoc.isNull())
|
|
|
|
|
return false;
|
|
|
|
|
QmlJS::AST::Node *ast = qmlJSDoc->ast();
|
|
|
|
|
QTC_ASSERT(ast, return false);
|
2017-02-27 09:30:16 +01:00
|
|
|
TestQmlVisitor qmlVisitor(qmlJSDoc, snapshot);
|
2016-05-11 13:02:42 +02:00
|
|
|
QmlJS::AST::Node::accept(ast, &qmlVisitor);
|
2017-02-27 09:30:16 +01:00
|
|
|
if (!qmlVisitor.isValid())
|
|
|
|
|
return false;
|
2016-05-11 13:02:42 +02:00
|
|
|
|
|
|
|
|
const QString testCaseName = qmlVisitor.testCaseName();
|
|
|
|
|
const TestCodeLocationAndType tcLocationAndType = qmlVisitor.testCaseLocation();
|
|
|
|
|
const QMap<QString, TestCodeLocationAndType> &testFunctions = qmlVisitor.testFunctions();
|
|
|
|
|
|
2016-06-06 14:18:38 +02:00
|
|
|
QuickTestParseResult *parseResult = new QuickTestParseResult(id);
|
2016-05-11 13:02:42 +02:00
|
|
|
parseResult->proFile = proFile;
|
|
|
|
|
parseResult->itemType = TestTreeItem::TestCase;
|
|
|
|
|
QMap<QString, TestCodeLocationAndType>::ConstIterator it = testFunctions.begin();
|
|
|
|
|
const QMap<QString, TestCodeLocationAndType>::ConstIterator end = testFunctions.end();
|
|
|
|
|
for ( ; it != end; ++it) {
|
|
|
|
|
const TestCodeLocationAndType &loc = it.value();
|
2016-06-06 14:18:38 +02:00
|
|
|
QuickTestParseResult *funcResult = new QuickTestParseResult(id);
|
2016-05-11 13:02:42 +02:00
|
|
|
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<TestParseResultPtr> futureInterface,
|
2017-02-27 09:30:16 +01:00
|
|
|
const QmlJS::Snapshot &snapshot,
|
2016-06-06 14:18:38 +02:00
|
|
|
CPlusPlus::Document::Ptr document,
|
|
|
|
|
const Core::Id &id)
|
2016-05-11 13:02:42 +02:00
|
|
|
{
|
|
|
|
|
const CppTools::CppModelManager *modelManager = CppTools::CppModelManager::instance();
|
|
|
|
|
if (quickTestName(document).isEmpty())
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
const QString cppFileName = document->fileName();
|
|
|
|
|
QList<CppTools::ProjectPart::Ptr> ppList = modelManager->projectPart(cppFileName);
|
2016-07-08 12:53:57 +02:00
|
|
|
if (ppList.isEmpty()) // happens if shutting down while parsing
|
|
|
|
|
return false;
|
2016-05-11 13:02:42 +02:00
|
|
|
const QString &proFile = ppList.at(0)->projectFile;
|
|
|
|
|
|
|
|
|
|
const QString srcDir = quickTestSrcDir(modelManager, cppFileName);
|
|
|
|
|
if (srcDir.isEmpty())
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
const QList<QmlJS::Document::Ptr> qmlDocs = scanDirectoryForQuickTestQmlFiles(srcDir);
|
|
|
|
|
bool result = false;
|
2017-02-13 10:05:06 +01:00
|
|
|
for (const QmlJS::Document::Ptr &qmlJSDoc : qmlDocs)
|
2017-02-27 09:30:16 +01:00
|
|
|
result |= checkQmlDocumentForQuickTestCode(futureInterface, snapshot, qmlJSDoc, id, proFile);
|
2016-05-11 13:02:42 +02:00
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
2017-03-06 15:24:16 +01:00
|
|
|
QuickTestParser::QuickTestParser()
|
|
|
|
|
: CppParser()
|
|
|
|
|
{
|
|
|
|
|
QObject::connect(ProjectExplorer::SessionManager::instance(),
|
|
|
|
|
&ProjectExplorer::SessionManager::startupProjectChanged, [] {
|
|
|
|
|
const QStringList &dirs = s_directoryWatcher.directories();
|
|
|
|
|
if (!dirs.isEmpty())
|
|
|
|
|
s_directoryWatcher.removePaths(dirs);
|
|
|
|
|
});
|
|
|
|
|
QObject::connect(&s_directoryWatcher, &QFileSystemWatcher::directoryChanged,
|
|
|
|
|
[this] { TestTreeModel::instance()->parser()->emitUpdateTestTree(this); });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QuickTestParser::~QuickTestParser()
|
|
|
|
|
{
|
|
|
|
|
QObject::disconnect(&s_directoryWatcher, 0, 0, 0);
|
|
|
|
|
const QStringList &dirs = s_directoryWatcher.directories();
|
|
|
|
|
if (!dirs.isEmpty())
|
|
|
|
|
s_directoryWatcher.removePaths(dirs);
|
|
|
|
|
}
|
|
|
|
|
|
2016-05-11 13:02:42 +02:00
|
|
|
void QuickTestParser::init(const QStringList &filesToParse)
|
|
|
|
|
{
|
|
|
|
|
m_qmlSnapshot = QmlJSTools::Internal::ModelManager::instance()->snapshot();
|
2016-07-12 09:45:55 +02:00
|
|
|
m_proFilesForQmlFiles = QuickTestUtils::proFilesForQmlFiles(id(), filesToParse);
|
2016-05-11 13:02:42 +02:00
|
|
|
CppParser::init(filesToParse);
|
|
|
|
|
}
|
|
|
|
|
|
2016-07-12 10:35:43 +02:00
|
|
|
void QuickTestParser::release()
|
|
|
|
|
{
|
|
|
|
|
m_qmlSnapshot = QmlJS::Snapshot();
|
|
|
|
|
m_proFilesForQmlFiles.clear();
|
|
|
|
|
CppParser::release();
|
|
|
|
|
}
|
|
|
|
|
|
2016-05-11 13:02:42 +02:00
|
|
|
bool QuickTestParser::processDocument(QFutureInterface<TestParseResultPtr> futureInterface,
|
|
|
|
|
const QString &fileName)
|
|
|
|
|
{
|
|
|
|
|
if (fileName.endsWith(".qml")) {
|
2016-07-12 09:45:55 +02:00
|
|
|
const QString &proFile = m_proFilesForQmlFiles.value(fileName);
|
|
|
|
|
if (proFile.isEmpty())
|
|
|
|
|
return false;
|
2016-05-11 13:02:42 +02:00
|
|
|
QmlJS::Document::Ptr qmlJSDoc = m_qmlSnapshot.document(fileName);
|
2017-02-27 09:30:16 +01:00
|
|
|
return checkQmlDocumentForQuickTestCode(futureInterface, m_qmlSnapshot, qmlJSDoc, id(), proFile);
|
2016-05-11 13:02:42 +02:00
|
|
|
}
|
|
|
|
|
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;
|
2017-02-27 09:30:16 +01:00
|
|
|
return handleQtQuickTest(futureInterface, m_qmlSnapshot, document, id());
|
2016-05-11 13:02:42 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} // namespace Internal
|
|
|
|
|
} // namespace Autotest
|