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"
|
|
|
|
|
|
|
|
|
|
#include <qmljs/parser/qmljsast_p.h>
|
|
|
|
|
#include <qmljs/qmljsdialect.h>
|
|
|
|
|
#include <qmljstools/qmljsmodelmanager.h>
|
|
|
|
|
#include <utils/hostosinfo.h>
|
|
|
|
|
#include <utils/qtcassert.h>
|
|
|
|
|
|
|
|
|
|
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()
|
2016-09-29 12:15:43 +02:00
|
|
|
? QStringList({ "QtQuickTest.framework/Headers", "QtQuickTest" })
|
|
|
|
|
: QStringList({ "QtQuickTest" });
|
2016-05-11 13:02:42 +02:00
|
|
|
|
|
|
|
|
const QList<CPlusPlus::Document::Include> includes = doc->resolvedIncludes();
|
|
|
|
|
|
|
|
|
|
foreach (const CPlusPlus::Document::Include &inc, includes) {
|
2016-09-29 12:15:43 +02:00
|
|
|
if (inc.unresolvedFileName() == "QtQuickTest/quicktest.h") {
|
2016-05-11 13:02:42 +02:00
|
|
|
foreach (const QString &prefix, expectedHeaderPrefixes) {
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
foreach (const QString &include, snapshot.allIncludesForDocument(doc->fileName())) {
|
|
|
|
|
foreach (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);
|
|
|
|
|
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<CPlusPlus::Document::MacroUse> 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);
|
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);
|
|
|
|
|
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<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());
|
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,
|
|
|
|
|
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);
|
|
|
|
|
TestQmlVisitor qmlVisitor(qmlJSDoc);
|
|
|
|
|
QmlJS::AST::Node::accept(ast, &qmlVisitor);
|
|
|
|
|
|
|
|
|
|
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,
|
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;
|
|
|
|
|
foreach (const QmlJS::Document::Ptr &qmlJSDoc, qmlDocs)
|
2016-06-06 14:18:38 +02:00
|
|
|
result |= checkQmlDocumentForQuickTestCode(futureInterface, qmlJSDoc, id, proFile);
|
2016-05-11 13:02:42 +02:00
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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);
|
2016-07-12 09:45:55 +02:00
|
|
|
return checkQmlDocumentForQuickTestCode(futureInterface, 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;
|
2016-06-06 14:18:38 +02:00
|
|
|
return handleQtQuickTest(futureInterface, document, id());
|
2016-05-11 13:02:42 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} // namespace Internal
|
|
|
|
|
} // namespace Autotest
|