Files
qt-creator/plugins/autotest/testcodeparser.cpp

785 lines
31 KiB
C++
Raw Normal View History

2014-10-07 12:30:54 +02:00
/****************************************************************************
**
** Copyright (C) 2014 Digia Plc
** All rights reserved.
** For any questions to Digia, please use contact form at http://qt.digia.com
**
** This file is part of the Qt Creator Enterprise Auto Test Add-on.
**
** Licensees holding valid Qt Enterprise licenses may use this file in
** accordance with the Qt Enterprise License Agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Digia.
**
** If you have questions regarding the use of this file, please use
** contact form at http://qt.digia.com
**
****************************************************************************/
#include "autotestconstants.h"
2014-10-07 12:30:54 +02:00
#include "testcodeparser.h"
#include "testinfo.h"
#include "testtreeitem.h"
#include "testtreemodel.h"
#include "testvisitor.h"
#include <coreplugin/progressmanager/progressmanager.h>
2014-10-07 12:30:54 +02:00
#include <cplusplus/LookupContext.h>
#include <cplusplus/TypeOfExpression.h>
#include <cpptools/cpptoolsconstants.h>
2014-10-07 12:30:54 +02:00
#include <cpptools/cppmodelmanager.h>
#include <cpptools/cppworkingcopy.h>
#include <projectexplorer/session.h>
#include <qmakeprojectmanager/qmakeproject.h>
#include <qmakeprojectmanager/qmakeprojectmanagerconstants.h>
2014-11-06 16:01:06 +01:00
#include <qmljs/parser/qmljsast_p.h>
#include <qmljs/qmljsdialect.h>
#include <qmljstools/qmljsmodelmanager.h>
#include <utils/textfileformat.h>
2014-10-07 12:30:54 +02:00
namespace Autotest {
namespace Internal {
TestCodeParser::TestCodeParser(TestTreeModel *parent)
: QObject(parent),
m_model(parent),
m_parserEnabled(true),
m_pendingUpdate(false)
2014-10-07 12:30:54 +02:00
{
// 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);
2014-10-07 12:30:54 +02:00
}
TestCodeParser::~TestCodeParser()
{
clearMaps();
}
void TestCodeParser::emitUpdateTestTree()
{
QTimer::singleShot(1000, this, SLOT(updateTestTree()));
}
ProjectExplorer::Project *currentProject()
{
const ProjectExplorer::SessionManager *session = ProjectExplorer::SessionManager::instance();
if (!session || !session->hasProjects())
return 0;
return session->startupProject();
}
2014-10-07 12:30:54 +02:00
void TestCodeParser::updateTestTree()
{
if (!m_parserEnabled) {
m_pendingUpdate = true;
qDebug() << "Skipped update due to running parser or pro file evaluate";
return;
}
2014-10-07 12:30:54 +02:00
qDebug("updating TestTreeModel");
clearMaps();
m_model->removeAllAutoTests();
2014-11-06 16:01:06 +01:00
m_model->removeAllQuickTests();
if (ProjectExplorer::Project *proj = currentProject()) {
if (auto qmakeProject = qobject_cast<QmakeProjectManager::QmakeProject *>(proj)) {
connect(qmakeProject, &QmakeProjectManager::QmakeProject::proFilesEvaluated,
this, &TestCodeParser::onProFileEvaluated, Qt::UniqueConnection);
}
} else
2014-10-07 12:30:54 +02:00
return;
scanForTests();
m_pendingUpdate = false;
2014-10-07 12:30:54 +02:00
}
/****** scan for QTest related stuff helpers ******/
static QByteArray getFileContent(QString filePath)
{
QByteArray fileContent;
CppTools::CppModelManager *cppMM = CppTools::CppModelManager::instance();
CppTools::WorkingCopy wc = cppMM->workingCopy();
if (wc.contains(filePath)) {
fileContent = wc.source(filePath);
} else {
QString error;
const QTextCodec *codec = Core::EditorManager::defaultTextCodec();
if (Utils::TextFileFormat::readFileUTF8(filePath, codec, &fileContent, &error)
!= Utils::TextFileFormat::ReadSuccess) {
qDebug() << "Failed to read file" << filePath << ":" << error;
}
}
return fileContent;
}
2014-10-07 12:30:54 +02:00
static bool includesQtTest(const CPlusPlus::Document::Ptr &doc,
const CppTools::CppModelManager *cppMM)
{
const QList<CPlusPlus::Document::Include> includes = doc->resolvedIncludes();
2014-10-07 12:30:54 +02:00
2014-11-06 16:01:06 +01:00
foreach (const CPlusPlus::Document::Include &inc, includes) {
2014-10-07 12:30:54 +02:00
// TODO this short cut works only for #include <QtTest>
// bad, as there could be much more different approaches
if (inc.unresolvedFileName() == QLatin1String("QtTest")
&& inc.resolvedFileName().endsWith(QLatin1String("QtTest/QtTest"))) {
return true;
}
}
if (cppMM) {
CPlusPlus::Snapshot snapshot = cppMM->snapshot();
const QSet<QString> allIncludes = snapshot.allIncludesForDocument(doc->fileName());
2014-11-06 16:01:06 +01:00
foreach (const QString &include, allIncludes) {
2014-10-07 12:30:54 +02:00
if (include.endsWith(QLatin1String("QtTest/qtest.h"))) {
return true;
}
}
}
return false;
}
2014-11-06 16:01:06 +01:00
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;
}
2014-10-07 12:30:54 +02:00
static bool qtTestLibDefined(const CppTools::CppModelManager *cppMM,
const QString &fileName)
{
const QList<CppTools::ProjectPart::Ptr> parts = cppMM->projectPart(fileName);
if (parts.size() > 0)
return parts.at(0)->projectDefines.contains("#define QT_TESTLIB_LIB 1");
2014-10-07 12:30:54 +02:00
return false;
}
2014-11-06 16:01:06 +01:00
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();
}
2014-10-07 12:30:54 +02:00
static QString testClass(const CPlusPlus::Document::Ptr &doc)
{
2014-11-06 16:01:06 +01:00
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 &macro, 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();
2014-10-07 12:30:54 +02:00
2014-11-06 16:01:06 +01:00
foreach (const CPlusPlus::Document::MacroUse &macro, macros) {
2014-10-07 12:30:54 +02:00
if (!macro.isFunctionLike())
continue;
const QByteArray name = macro.macro().name();
2014-10-07 12:30:54 +02:00
if (name == qtTestMacros[0] || name == qtTestMacros[1] || name == qtTestMacros[2]) {
CPlusPlus::Document::Block arg = macro.arguments().at(0);
2014-11-06 16:01:06 +01:00
return QLatin1String(getFileContent(doc->fileName()).mid(arg.bytesBegin(), arg.bytesEnd() - arg.bytesBegin()));
2014-10-07 12:30:54 +02:00
}
}
2014-11-06 16:01:06 +01:00
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 dontEmitDocumentChanges = false;
const bool notOnlyTheLib = false;
QmlJS::ModelManagerInterface::importScan(future, qmlJsMM->workingCopy(), paths, qmlJsMM,
dontEmitDocumentChanges, notOnlyTheLib);
2014-11-06 16:01:06 +01: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();
}
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;
2014-10-07 12:30:54 +02:00
}
/****** end of helpers ******/
void TestCodeParser::checkDocumentForTestCode(CPlusPlus::Document::Ptr doc)
{
const QString file = doc->fileName();
const CppTools::CppModelManager *cppMM = CppTools::CppModelManager::instance();
2014-10-07 12:30:54 +02:00
QList<CppTools::ProjectPart::Ptr> projParts = cppMM->projectPart(file);
if (projParts.size())
if (!projParts.at(0)->selectedForBuilding) {
removeTestsIfNecessary(file);
return;
}
2014-11-06 16:01:06 +01:00
if (includesQtQuickTest(doc, cppMM)) {
handleQtQuickTest(doc);
return;
}
2014-10-07 12:30:54 +02:00
if (includesQtTest(doc, cppMM) && qtTestLibDefined(cppMM, file)) {
QString tc(testClass(doc));
2014-10-07 12:30:54 +02:00
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
const QModelIndex autoTestRootIndex = m_model->index(0, 0);
2014-10-07 12:30:54 +02:00
TestTreeItem *autoTestRootItem = static_cast<TestTreeItem *>(autoTestRootIndex.internalPointer());
TestTreeItem *ttItem = new TestTreeItem(tc, file, TestTreeItem::TEST_CLASS,
autoTestRootItem);
QString declFileName;
2014-10-07 12:30:54 +02:00
CPlusPlus::TypeOfExpression toe;
toe.init(doc, cppMM->snapshot());
CPlusPlus::Document::Ptr declaringDoc = doc;
const QList<CPlusPlus::LookupItem> toeItems = toe(tc.toUtf8(), doc->globalNamespace());
2014-10-07 12:30:54 +02:00
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);
}
2014-10-07 12:30:54 +02:00
}
if (declaringDoc.isNull()) {
delete ttItem;
2014-10-07 12:30:54 +02:00
return;
}
2014-10-07 12:30:54 +02:00
TestVisitor myVisitor(tc);
myVisitor.accept(declaringDoc->globalNamespace());
2014-11-13 12:31:58 +01:00
const QMap<QString, TestCodeLocationAndType> privSlots = myVisitor.privateSlots();
2014-11-06 16:01:06 +01:00
foreach (const QString &privS, privSlots.keys()) {
2014-11-13 12:31:58 +01:00
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);
2014-10-07 12:30:54 +02:00
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;
2014-10-07 12:30:54 +02:00
if (m_cppDocMap.contains(file)) {
info = m_cppDocMap.value(file);
count = autoTestRootItem->childCount();
2014-10-07 12:30:54 +02:00
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);
2014-10-07 12:30:54 +02:00
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;
}
}
}
2014-10-07 12:30:54 +02:00
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);
}
}
}
} 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());
2014-10-07 12:30:54 +02:00
}
}
}
}
2014-11-06 16:01:06 +01:00
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();
2014-11-13 12:31:58 +01:00
const TestCodeLocationAndType tcLocationAndType = qmlVisitor.testCaseLocation();
const QMap<QString, TestCodeLocationAndType> testFunctions = qmlVisitor.testFunctions();
2014-11-06 16:01:06 +01:00
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) {
2014-11-06 16:01:06 +01:00
// 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);
2014-11-06 16:01:06 +01:00
}
// as removeUnnamedQuickTests() could delete this item itself try to get it again
unnamedQTItem = m_model->unnamedQuickTests();
2014-11-06 16:01:06 +01:00
}
// 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);
}
}
2014-11-06 16:01:06 +01:00
foreach (const QString &func, testFunctions.keys()) {
2014-11-13 12:31:58 +01:00
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);
2014-11-06 16:01:06 +01:00
}
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);
2014-11-06 16:01:06 +01:00
info.setTestFunctions(originalFunctions);
if (unnamedQTItem) {
m_model->modifyQuickTestSubtree(unnamedQTItem->row(), ttItem);
delete ttItem;
} else {
2014-11-06 16:01:06 +01:00
m_model->addQuickTest(ttItem);
}
m_quickDocMap.insert(tr(Constants::UNNAMED_QUICKTESTS), info);
2014-11-06 16:01:06 +01:00
continue;
} // end of handling test cases without name property
// construct new/modified TestTreeItem
2014-11-13 12:31:58 +01:00
TestTreeItem *ttItem = new TestTreeItem(tcName, tcLocationAndType.m_fileName,
tcLocationAndType.m_type, quickTestRootItem);
ttItem->setLine(tcLocationAndType.m_line);
ttItem->setColumn(tcLocationAndType.m_column);
2014-11-06 16:01:06 +01:00
ttItem->setMainFile(doc->fileName());
foreach (const QString &func, testFunctions.keys()) {
2014-11-13 12:31:58 +01:00
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);
2014-11-06 16:01:06 +01:00
ttItem->appendChild(ttSub);
}
// update model and internal map
2014-11-13 12:31:58 +01:00
const QString fileName(tcLocationAndType.m_fileName);
2014-11-06 16:01:06 +01:00
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;
2014-11-06 16:01:06 +01:00
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);
2014-11-06 16:01:06 +01:00
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);
}
2014-11-06 16:01:06 +01:00
}
m_model->addQuickTest(ttItem);
TestInfo ti(tcName, testFunctions.keys(), 0, qmlDoc->editorRevision());
ti.setReferencingFile(doc->fileName());
ti.setProfile(proFile);
2014-11-13 12:31:58 +01:00
m_quickDocMap.insert(tcLocationAndType.m_fileName, ti);
2014-11-06 16:01:06 +01:00
}
}
}
void TestCodeParser::onCppDocumentUpdated(const CPlusPlus::Document::Ptr &doc)
2014-10-07 12:30:54 +02:00
{
ProjectExplorer::Project *project = currentProject();
if (!project)
2014-10-07 12:30:54 +02:00
return;
const QString fileName = doc->fileName();
if (m_cppDocMap.contains(fileName)) {
if (m_cppDocMap[fileName].revision() == doc->revision()
&& m_cppDocMap[fileName].editorRevision() == doc->editorRevision()) {
2014-10-07 12:30:54 +02:00
qDebug("Skipped due revision equality"); // added to verify if this ever happens..
return;
}
} else if (!project->files(ProjectExplorer::Project::AllFiles).contains(fileName)) {
return;
2014-10-07 12:30:54 +02:00
}
checkDocumentForTestCode(doc);
}
2014-11-06 16:01:06 +01:00
void TestCodeParser::onQmlDocumentUpdated(const QmlJS::Document::Ptr &doc)
{
ProjectExplorer::Project *project = currentProject();
if (!project)
2014-11-06 16:01:06 +01:00
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 (!project->files(ProjectExplorer::Project::AllFiles).contains(fileName)) {
2014-11-06 16:01:06 +01:00
// 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(tr(Constants::UNNAMED_QUICKTESTS)))
2014-11-06 16:01:06 +01:00
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;
}
}
}
2014-10-07 12:30:54 +02:00
void TestCodeParser::removeFiles(const QStringList &files)
{
foreach (const QString &file, files)
removeTestsIfNecessary(file);
2014-10-07 12:30:54 +02:00
}
void TestCodeParser::scanForTests(const QStringList &fileList)
2014-10-07 12:30:54 +02:00
{
QStringList list;
if (fileList.isEmpty()) {
list = currentProject()->files(ProjectExplorer::Project::AllFiles);
} else {
list << fileList;
}
2014-10-07 12:30:54 +02:00
CppTools::CppModelManager *cppMM = CppTools::CppModelManager::instance();
CPlusPlus::Snapshot snapshot = cppMM->snapshot();
2014-11-06 16:01:06 +01:00
foreach (const QString &file, list) {
2014-10-07 12:30:54 +02:00
if (snapshot.contains(file)) {
CPlusPlus::Document::Ptr doc = snapshot.find(file).value();
checkDocumentForTestCode(doc);
}
}
}
void TestCodeParser::clearMaps()
{
m_cppDocMap.clear();
2014-11-06 16:01:06 +01:00
m_quickDocMap.clear();
}
void TestCodeParser::removeTestsIfNecessary(const QString &fileName)
{
// check if this file was listed before and remove if necessary (switched config,...)
if (m_cppDocMap.contains(fileName)) {
m_cppDocMap.remove(fileName);
m_model->removeAutoTestSubtreeByFilePath(fileName);
} else { // handle Qt Quick Tests
QList<QString> toBeRemoved;
foreach (const QString &file, m_quickDocMap.keys()) {
if (file == fileName) {
toBeRemoved.append(file);
continue;
}
const TestInfo info = m_quickDocMap.value(file);
if (info.referencingFile() == fileName)
toBeRemoved.append(file);
}
foreach (const QString &file, toBeRemoved) {
m_quickDocMap.remove(file);
m_model->removeQuickTestSubtreeByFilePath(file);
}
// unnamed Quick Tests must be handled separately
QSet<QString> filePaths;
QList<QString> functionNames;
if (TestTreeItem *unnamedQT = m_model->unnamedQuickTests()) {
for (int i = 0; i < unnamedQT->childCount(); ++i) {
const TestTreeItem *child = unnamedQT->child(i);
if (child->mainFile() == fileName) {
filePaths.insert(child->filePath());
functionNames.append(child->name());
}
}
foreach (const QString &file, filePaths)
m_model->removeUnnamedQuickTests(file);
// update info map
TestInfo unnamedInfo = m_quickDocMap[tr(Constants::UNNAMED_QUICKTESTS)];
QStringList functions = unnamedInfo.testFunctions();
foreach (const QString &func, functionNames)
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);
}
}
}
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);
}
foreach (const QString &fn, fl) {
m_cppDocMap.remove(fn);
m_model->removeAutoTestSubtreeByFilePath(fn);
}
fl.clear();
foreach (const QString &fn, m_quickDocMap.keys()) {
if (m_quickDocMap[fn].proFile() == proFile)
fl.append(fn);
}
foreach (const QString &fn, fl) {
m_quickDocMap.remove(fn);
m_model->removeQuickTestSubtreeByFilePath(fn);
}
// handle unnamed Quick Tests
fl.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()) {
for (int i = 0; i < unnamedQT->childCount(); ++i) {
const TestTreeItem *child = unnamedQT->child(i);
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());
}
}
}
foreach (const QString &fp, filePaths) {
m_model->removeUnnamedQuickTests(fp);
}
// update info map
TestInfo unnamedInfo = m_quickDocMap[tr(Constants::UNNAMED_QUICKTESTS)];
QStringList functions = unnamedInfo.testFunctions();
foreach (const QString &func, fl)
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);
}
void TestCodeParser::onTaskStarted(Core::Id type)
{
if (type != CppTools::Constants::TASK_INDEX
&& type != QmakeProjectManager::Constants::PROFILE_EVALUATE)
return;
m_parserEnabled = false;
}
void TestCodeParser::onAllTasksFinished(Core::Id type)
{
if (type != CppTools::Constants::TASK_INDEX
&& type != QmakeProjectManager::Constants::PROFILE_EVALUATE)
return;
m_parserEnabled = true;
if (m_pendingUpdate)
updateTestTree();
}
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();
foreach (const CppTools::ProjectPart::Ptr &p, pp) {
if (!p->selectedForBuilding)
removeTestsIfNecessaryByProFile(p->projectFile);
else {
QStringList files;
foreach (auto projectFile, p->files)
files.append(projectFile.path);
scanForTests(files);
}
}
}
2014-10-07 12:30:54 +02:00
} // namespace Internal
} // namespace Autotest