Files
qt-creator/plugins/autotest/testcodeparser.cpp
Christian Stenger 2c1a0119bd Fix unexpected parser state when parsing finishes
While parsing test code it might happen that the user closes
all related widgets and so the parser falls into the state Disabled.
This was unexpected and did print a warning.
Now this case is somewhat expected and won't print a warning anymore.

Change-Id: I0082bdc31f6ed351cf1dbd7b34da2f5672aee3dd
Reviewed-by: Nikolai Kosjar <nikolai.kosjar@theqtcompany.com>
Reviewed-by: Christian Stenger <christian.stenger@theqtcompany.com>
2015-03-09 13:01:54 +02:00

920 lines
34 KiB
C++

/****************************************************************************
**
** Copyright (C) 2015 The Qt Company Ltd
** All rights reserved.
** For any questions to The Qt Company, please use contact form at
** http://www.qt.io/contact-us
**
** 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 The Qt Company.
**
** If you have questions regarding the use of this file, please use
** contact form at http://www.qt.io/contact-us
**
****************************************************************************/
#include "autotestconstants.h"
#include "testcodeparser.h"
#include "testinfo.h"
#include "testvisitor.h"
#include <coreplugin/editormanager/editormanager.h>
#include <coreplugin/progressmanager/futureprogress.h>
#include <coreplugin/progressmanager/progressmanager.h>
#include <cplusplus/LookupContext.h>
#include <cplusplus/TypeOfExpression.h>
#include <cpptools/cpptoolsconstants.h>
#include <cpptools/cppmodelmanager.h>
#include <cpptools/cppworkingcopy.h>
#include <projectexplorer/session.h>
#include <qmakeprojectmanager/qmakeproject.h>
#include <qmakeprojectmanager/qmakeprojectmanagerconstants.h>
#include <qmljs/parser/qmljsast_p.h>
#include <qmljs/qmljsdialect.h>
#include <qmljstools/qmljsmodelmanager.h>
#include <utils/multitask.h>
#include <utils/qtcassert.h>
#include <utils/textfileformat.h>
#include <QFuture>
#include <QFutureInterface>
namespace Autotest {
namespace Internal {
TestCodeParser::TestCodeParser(TestTreeModel *parent)
: QObject(parent),
m_model(parent),
m_parserEnabled(true),
m_fullUpdatePostponed(false),
m_partialUpdatePostponed(false),
m_dirty(true),
m_parserState(Disabled)
{
// connect to ProgressManager to postpone test parsing when CppModelManager is parsing
auto progressManager = qobject_cast<Core::ProgressManager *>(Core::ProgressManager::instance());
connect(progressManager, &Core::ProgressManager::taskStarted,
this, &TestCodeParser::onTaskStarted);
connect(progressManager, &Core::ProgressManager::allTasksFinished,
this, &TestCodeParser::onAllTasksFinished);
connect(this, &TestCodeParser::partialParsingFinished,
this, &TestCodeParser::onPartialParsingFinished);
}
TestCodeParser::~TestCodeParser()
{
clearCache();
}
ProjectExplorer::Project *currentProject()
{
const ProjectExplorer::SessionManager *session = ProjectExplorer::SessionManager::instance();
if (!session || !session->hasProjects())
return 0;
return session->startupProject();
}
void TestCodeParser::setState(State state)
{
m_parserState = state;
if (m_parserState == Disabled) {
m_fullUpdatePostponed = m_partialUpdatePostponed = false;
m_postponedFiles.clear();
} else if (m_parserState == Idle && m_dirty && currentProject()) {
scanForTests(m_postponedFiles.toList());
}
}
void TestCodeParser::emitUpdateTestTree()
{
QTimer::singleShot(1000, this, SLOT(updateTestTree()));
}
void TestCodeParser::updateTestTree()
{
if (!m_parserEnabled) {
m_fullUpdatePostponed = true;
return;
}
if (ProjectExplorer::Project *project = currentProject()) {
if (auto qmakeProject = qobject_cast<QmakeProjectManager::QmakeProject *>(project)) {
if (qmakeProject->asyncUpdateState() != QmakeProjectManager::QmakeProject::Base) {
m_fullUpdatePostponed = true;
return;
}
connect(qmakeProject, &QmakeProjectManager::QmakeProject::proFilesEvaluated,
this, &TestCodeParser::onProFileEvaluated, Qt::UniqueConnection);
}
} else
return;
m_fullUpdatePostponed = false;
clearCache();
emit cacheCleared();
scanForTests();
}
/****** 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;
}
static bool includesQtTest(const CPlusPlus::Document::Ptr &doc,
const CppTools::CppModelManager *cppMM)
{
static QString expectedHeaderPrefix
= Utils::HostOsInfo::isMacHost()
? QLatin1String("QtTest.framework/Headers")
: QLatin1String("QtTest");
const QList<CPlusPlus::Document::Include> includes = doc->resolvedIncludes();
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")
&& inc.resolvedFileName().endsWith(
QString::fromLatin1("%1/QtTest").arg(expectedHeaderPrefix))) {
return true;
}
}
if (cppMM) {
CPlusPlus::Snapshot snapshot = cppMM->snapshot();
const QSet<QString> allIncludes = snapshot.allIncludesForDocument(doc->fileName());
foreach (const QString &include, allIncludes) {
if (include.endsWith(QString::fromLatin1("%1/qtest.h").arg(expectedHeaderPrefix))) {
return true;
}
}
}
return false;
}
static bool includesQtQuickTest(const CPlusPlus::Document::Ptr &doc,
const CppTools::CppModelManager *cppMM)
{
static QString expectedHeaderPrefix
= Utils::HostOsInfo::isMacHost()
? QLatin1String("QtQuickTest.framework/Headers")
: QLatin1String("QtQuickTest");
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(
QString::fromLatin1("%1/quicktest.h").arg(expectedHeaderPrefix))) {
return true;
}
}
if (cppMM) {
foreach (const QString &include, cppMM->snapshot().allIncludesForDocument(doc->fileName())) {
if (include.endsWith(QString::fromLatin1("%1/quicktest.h").arg(expectedHeaderPrefix)))
return true;
}
}
return false;
}
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");
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 CppTools::CppModelManager *modelManager,
CPlusPlus::Document::Ptr &document)
{
static const QByteArray qtTestMacros[] = {"QTEST_MAIN", "QTEST_APPLESS_MAIN", "QTEST_GUILESS_MAIN"};
const QList<CPlusPlus::Document::MacroUse> macros = document->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(document->fileName())
.mid(arg.bytesBegin(), arg.bytesEnd() - arg.bytesBegin()));
}
}
// check if one has used a self-defined macro or QTest::qExec() directly
const CPlusPlus::Snapshot snapshot = modelManager->snapshot();
const QByteArray fileContent = getFileContent(document->fileName());
document = snapshot.preprocessedDocument(fileContent, document->fileName());
document->check();
CPlusPlus::AST *ast = document->translationUnit()->ast();
TestAstVisitor astVisitor(document);
astVisitor.accept(ast);
return astVisitor.className();
}
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) {
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);
return QLatin1String(getFileContent(doc->fileName())
.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());
if (fileName.startsWith(QLatin1String("tst_")) && fileName.endsWith(QLatin1String(".qml")))
foundDocs << doc;
}
}
return foundDocs;
}
static CPlusPlus::Document::Ptr declaringDocument(CPlusPlus::Document::Ptr doc,
const QString &testCaseName,
unsigned *line, unsigned *column)
{
CPlusPlus::Document::Ptr declaringDoc = doc;
const CppTools::CppModelManager *cppMM = CppTools::CppModelManager::instance();
CPlusPlus::TypeOfExpression typeOfExpr;
typeOfExpr.init(doc, cppMM->snapshot());
auto lookupItems = typeOfExpr(testCaseName.toUtf8(), doc->globalNamespace());
if (lookupItems.size()) {
CPlusPlus::Class *toeClass = lookupItems.first().declaration()->asClass();
if (toeClass) {
const QString declFileName = QLatin1String(toeClass->fileId()->chars(),
toeClass->fileId()->size());
declaringDoc = cppMM->snapshot().document(declFileName);
*line = toeClass->line();
*column = toeClass->column() - 1;
}
}
return declaringDoc;
}
static TestTreeItem constructTestTreeItem(const QString &fileName,
const QString &mainFile, // used for Quick Tests only
const QString &testCaseName,
int line, int column,
const QMap<QString, TestCodeLocationAndType> functions)
{
TestTreeItem treeItem(testCaseName, fileName, TestTreeItem::TEST_CLASS);
treeItem.setMainFile(mainFile); // used for Quick Tests only
treeItem.setLine(line);
treeItem.setColumn(column);
foreach (const QString &functionName, functions.keys()) {
const TestCodeLocationAndType locationAndType = functions.value(functionName);
TestTreeItem *treeItemChild = new TestTreeItem(functionName, locationAndType.m_fileName,
locationAndType.m_type, &treeItem);
treeItemChild->setLine(locationAndType.m_line);
treeItemChild->setColumn(locationAndType.m_column);
treeItem.appendChild(treeItemChild);
}
return treeItem;
}
/****** end of helpers ******/
void performParse(QFutureInterface<void> &futureInterface, QStringList list,
TestCodeParser *testCodeParser)
{
int progressValue = 0;
futureInterface.setProgressRange(0, list.size());
futureInterface.setProgressValue(progressValue);
CppTools::CppModelManager *cppMM = CppTools::CppModelManager::instance();
CPlusPlus::Snapshot snapshot = cppMM->snapshot();
foreach (const QString &file, list) {
if (snapshot.contains(file)) {
CPlusPlus::Document::Ptr doc = snapshot.find(file).value();
futureInterface.setProgressValue(++progressValue);
testCodeParser->checkDocumentForTestCode(doc);
}
}
futureInterface.setProgressValue(list.size());
}
/****** threaded parsing stuff *******/
void TestCodeParser::checkDocumentForTestCode(CPlusPlus::Document::Ptr document)
{
const QString fileName = document->fileName();
const CppTools::CppModelManager *modelManager = CppTools::CppModelManager::instance();
QList<CppTools::ProjectPart::Ptr> projParts = modelManager->projectPart(fileName);
if (projParts.size())
if (!projParts.at(0)->selectedForBuilding) {
removeTestsIfNecessary(fileName);
return;
}
if (includesQtQuickTest(document, modelManager)) {
handleQtQuickTest(document);
return;
}
if (includesQtTest(document, modelManager) && qtTestLibDefined(modelManager, fileName)) {
QString testCaseName(testClass(modelManager, document));
if (!testCaseName.isEmpty()) {
unsigned line = 0;
unsigned column = 0;
CPlusPlus::Document::Ptr declaringDoc = declaringDocument(document, testCaseName,
&line, &column);
if (declaringDoc.isNull())
return;
TestVisitor visitor(testCaseName);
visitor.accept(declaringDoc->globalNamespace());
const QMap<QString, TestCodeLocationAndType> testFunctions = visitor.privateSlots();
TestTreeItem item = constructTestTreeItem(declaringDoc->fileName(), QString(),
testCaseName, line, column, testFunctions);
updateModelAndCppDocMap(document, declaringDoc->fileName(), item);
return;
}
}
// could not find the class to test, or QTest is not included and QT_TESTLIB_LIB defined
// maybe file is only a referenced file
if (m_cppDocMap.contains(fileName)) {
const TestInfo info = m_cppDocMap[fileName];
CPlusPlus::Snapshot snapshot = modelManager->snapshot();
if (snapshot.contains(info.referencingFile())) {
checkDocumentForTestCode(snapshot.find(info.referencingFile()).value());
} else { // no referencing file too, so this test case is no more a test case
m_cppDocMap.remove(fileName);
emit testItemsRemoved(fileName, TestTreeModel::AutoTest);
}
}
}
void TestCodeParser::handleQtQuickTest(CPlusPlus::Document::Ptr document)
{
const CppTools::CppModelManager *modelManager = CppTools::CppModelManager::instance();
if (quickTestName(document).isEmpty())
return;
const QString cppFileName = document->fileName();
const QString srcDir = quickTestSrcDir(modelManager, cppFileName);
if (srcDir.isEmpty())
return;
const QList<QmlJS::Document::Ptr> qmlDocs = scanDirectoryForQuickTestQmlFiles(srcDir);
foreach (const QmlJS::Document::Ptr &qmlJSDoc, qmlDocs) {
QmlJS::AST::Node *ast = qmlJSDoc->ast();
QTC_ASSERT(ast, continue);
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();
if (testCaseName.isEmpty()) {
updateUnnamedQuickTests(qmlJSDoc->fileName(), cppFileName, testFunctions);
continue;
} // end of handling test cases without name property
// construct new/modified TestTreeItem
TestTreeItem testTreeItem
= constructTestTreeItem(tcLocationAndType.m_fileName, cppFileName, testCaseName,
tcLocationAndType.m_line, tcLocationAndType.m_column,
testFunctions);
// update model and internal map
updateModelAndQuickDocMap(qmlJSDoc, cppFileName, testTreeItem);
}
}
void TestCodeParser::onCppDocumentUpdated(const CPlusPlus::Document::Ptr &document)
{
if (!m_parserEnabled) {
if (!m_fullUpdatePostponed) {
m_partialUpdatePostponed = true;
m_postponedFiles.insert(document->fileName());
}
return;
}
ProjectExplorer::Project *project = currentProject();
if (!project)
return;
const QString fileName = document->fileName();
if (m_cppDocMap.contains(fileName)) {
if (m_cppDocMap[fileName].revision() == document->revision()
&& m_cppDocMap[fileName].editorRevision() == document->editorRevision()) {
return;
}
} else if (!project->files(ProjectExplorer::Project::AllFiles).contains(fileName)) {
return;
}
scanForTests(QStringList(fileName));
}
void TestCodeParser::onQmlDocumentUpdated(const QmlJS::Document::Ptr &document)
{
if (!m_parserEnabled) {
if (!m_fullUpdatePostponed) {
m_partialUpdatePostponed = true;
m_postponedFiles.insert(document->fileName());
}
return;
}
ProjectExplorer::Project *project = currentProject();
if (!project)
return;
const QString fileName = document->fileName();
if (m_quickDocMap.contains(fileName)) {
if ((int)m_quickDocMap[fileName].editorRevision() == document->editorRevision()) {
return;
}
} else if (!project->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())) {
if (!m_quickDocMap[fileName].referencingFile().isEmpty())
scanForTests(QStringList(m_quickDocMap[fileName].referencingFile()));
}
if (m_unnamedQuickDocList.size() == 0)
return;
// special case of having unnamed TestCases
const QString &mainFile = m_model->getMainFileForUnnamedQuickTest(fileName);
if (!mainFile.isEmpty() && snapshot.contains(mainFile)) {
scanForTests(QStringList(mainFile));
}
}
void TestCodeParser::removeFiles(const QStringList &files)
{
foreach (const QString &file, files)
removeTestsIfNecessary(file);
}
bool TestCodeParser::postponed(const QStringList &fileList)
{
switch (m_parserState) {
case Idle:
return false;
case PartialParse:
// partial is running, postponing a full parse
if (fileList.isEmpty()) {
m_partialUpdatePostponed = false;
m_postponedFiles.clear();
m_fullUpdatePostponed = true;
} else {
// partial parse triggered, but full parse is postponed already, ignoring this
if (m_fullUpdatePostponed)
return true;
// partial parse triggered, postpone or add current files to already postponed partial
foreach (const QString &file, fileList)
m_postponedFiles.insert(file);
m_partialUpdatePostponed = true;
}
return true;
case FullParse:
// full parse is running, postponing another full parse
if (fileList.isEmpty()) {
m_partialUpdatePostponed = false;
m_postponedFiles.clear();
m_fullUpdatePostponed = true;
} else {
// full parse already postponed, ignoring triggering a partial parse
if (m_fullUpdatePostponed) {
return true;
}
// partial parse triggered, postpone or add current files to already postponed partial
foreach (const QString &file, fileList)
m_postponedFiles.insert(file);
m_partialUpdatePostponed = true;
}
return true;
case Disabled:
break;
}
QTC_ASSERT(false, return false); // should not happen at all
}
void TestCodeParser::scanForTests(const QStringList &fileList)
{
if (m_parserState == Disabled) {
m_dirty = true;
if (fileList.isEmpty()) {
m_fullUpdatePostponed = true;
m_partialUpdatePostponed = false;
m_postponedFiles.clear();
} else {
if (!m_fullUpdatePostponed) {
m_partialUpdatePostponed = true;
foreach (const QString &file, fileList)
m_postponedFiles.insert(file);
}
}
return;
}
if (postponed(fileList))
return;
bool isFullParse = fileList.isEmpty();
bool isSmallChange = !isFullParse && fileList.size() < 6;
QStringList list;
if (isFullParse) {
list = currentProject()->files(ProjectExplorer::Project::AllFiles);
if (list.isEmpty())
return;
m_parserState = FullParse;
} else {
list << fileList;
m_parserState = PartialParse;
}
if (isSmallChange) { // no need to do this async or should we do this always async?
CppTools::CppModelManager *cppMM = CppTools::CppModelManager::instance();
CPlusPlus::Snapshot snapshot = cppMM->snapshot();
foreach (const QString &file, list) {
if (snapshot.contains(file)) {
CPlusPlus::Document::Ptr doc = snapshot.find(file).value();
checkDocumentForTestCode(doc);
}
}
emit onFinished();
return;
}
QFuture<void> future = QtConcurrent::run(&performParse, list, this);
Core::FutureProgress *progress
= Core::ProgressManager::addTask(future, isFullParse ? tr("Scanning for Tests")
: tr("Updating Tests"),
Autotest::Constants::TASK_PARSE);
connect(progress, &Core::FutureProgress::finished,
this, &TestCodeParser::onFinished);
emit parsingStarted();
}
void TestCodeParser::clearCache()
{
m_cppDocMap.clear();
m_quickDocMap.clear();
m_unnamedQuickDocList.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);
emit testItemsRemoved(fileName, TestTreeModel::AutoTest);
} 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);
emit testItemsRemoved(file, TestTreeModel::QuickTest);
}
// unnamed Quick Tests must be handled separately
if (fileName.endsWith(QLatin1String(".qml"))) {
removeUnnamedQuickTestsByName(fileName);
emit unnamedQuickTestsRemoved(fileName);
} else {
QSet<QString> filePaths;
m_model->qmlFilesForMainFile(fileName, &filePaths);
foreach (const QString &file, filePaths) {
removeUnnamedQuickTestsByName(file);
emit unnamedQuickTestsRemoved(file);
}
}
}
}
void TestCodeParser::removeTestsIfNecessaryByProFile(const QString &proFile)
{
QList<QString> fList;
foreach (const QString &fileName, m_cppDocMap.keys()) {
if (m_cppDocMap[fileName].proFile() == proFile)
fList.append(fileName);
}
foreach (const QString &fileName, fList) {
m_cppDocMap.remove(fileName);
emit testItemsRemoved(fileName, TestTreeModel::AutoTest);
}
fList.clear();
foreach (const QString &fileName, m_quickDocMap.keys()) {
if (m_quickDocMap[fileName].proFile() == proFile)
fList.append(fileName);
}
foreach (const QString &fileName, fList) {
m_quickDocMap.remove(fileName);
emit testItemsRemoved(fileName, TestTreeModel::QuickTest);
}
// handle unnamed Quick Tests
const QSet<QString> &filePaths = m_model->qmlFilesForProFile(proFile);
foreach (const QString &fileName, filePaths) {
removeUnnamedQuickTestsByName(fileName);
emit unnamedQuickTestsRemoved(fileName);
}
}
void TestCodeParser::onTaskStarted(Core::Id type)
{
if (type != CppTools::Constants::TASK_INDEX)
return;
m_parserEnabled = false;
}
void TestCodeParser::onAllTasksFinished(Core::Id type)
{
// only CPP parsing is relevant as we trigger Qml parsing internally anyway
if (type != CppTools::Constants::TASK_INDEX)
return;
m_parserEnabled = true;
if (m_fullUpdatePostponed)
updateTestTree();
else if (m_partialUpdatePostponed) {
m_partialUpdatePostponed = false;
QStringList tmp;
foreach (const QString &file, m_postponedFiles)
tmp << file;
m_postponedFiles.clear();
scanForTests(tmp);
}
}
void TestCodeParser::onFinished()
{
switch (m_parserState) {
case PartialParse:
m_parserState = Idle;
emit partialParsingFinished();
break;
case FullParse:
m_parserState = Idle;
emit parsingFinished();
m_dirty = false;
break;
case Disabled: // can happen if all Test related widgets become hidden while parsing
emit parsingFinished();
break;
default:
qWarning("I should not be here... State: %d", m_parserState);
break;
}
}
void TestCodeParser::onPartialParsingFinished()
{
QTC_ASSERT(m_fullUpdatePostponed != m_partialUpdatePostponed
|| ((m_fullUpdatePostponed || m_partialUpdatePostponed) == false),
m_partialUpdatePostponed = false;m_postponedFiles.clear(););
if (m_fullUpdatePostponed) {
m_fullUpdatePostponed = false;
updateTestTree();
} else if (m_partialUpdatePostponed) {
m_partialUpdatePostponed = false;
QStringList tmp;
foreach (const QString &file, m_postponedFiles)
tmp << file;
m_postponedFiles.clear();
scanForTests(tmp);
} else {
m_dirty = false;
emit parsingFinished();
}
}
void TestCodeParser::updateUnnamedQuickTests(const QString &fileName, const QString &mainFile,
const QMap<QString, TestCodeLocationAndType> &functions)
{
// if this test case was named before remove it
m_quickDocMap.remove(fileName);
emit testItemsRemoved(fileName, TestTreeModel::QuickTest);
removeUnnamedQuickTestsByName(fileName);
foreach (const QString &functionName, functions.keys()) {
UnnamedQuickTestInfo info(functionName, fileName);
m_unnamedQuickDocList.append(info);
}
emit unnamedQuickTestsUpdated(fileName, mainFile, functions);
}
void TestCodeParser::updateModelAndCppDocMap(CPlusPlus::Document::Ptr document,
const QString &declaringFile, TestTreeItem &testItem)
{
const CppTools::CppModelManager *cppMM = CppTools::CppModelManager::instance();
const QString fileName = document->fileName();
const QString testCaseName = testItem.name();
QString proFile;
const QList<CppTools::ProjectPart::Ptr> ppList = cppMM->projectPart(fileName);
if (ppList.size())
proFile = ppList.at(0)->projectFile;
if (m_cppDocMap.contains(fileName)) {
QStringList files = QStringList() << fileName;
if (fileName != declaringFile)
files << declaringFile;
foreach (const QString &file, files) {
const bool setReferencingFile = (files.size() == 2 && file == declaringFile);
emit testItemModified(testItem, TestTreeModel::AutoTest, file);
TestInfo testInfo(testCaseName, testItem.getChildNames(),
document->revision(), document->editorRevision());
testInfo.setProfile(proFile);
if (setReferencingFile)
testInfo.setReferencingFile(fileName);
m_cppDocMap.insert(file, testInfo);
}
} else {
emit testItemCreated(testItem, TestTreeModel::AutoTest);
TestInfo ti(testCaseName, testItem.getChildNames(),
document->revision(), document->editorRevision());
ti.setProfile(proFile);
m_cppDocMap.insert(fileName, ti);
if (declaringFile != fileName) {
ti.setReferencingFile(fileName);
m_cppDocMap.insert(declaringFile, ti);
}
}
}
void TestCodeParser::updateModelAndQuickDocMap(QmlJS::Document::Ptr document,
const QString &referencingFile,
TestTreeItem &testItem)
{
const CppTools::CppModelManager *cppMM = CppTools::CppModelManager::instance();
const QString fileName = document->fileName();
QString proFile;
QList<CppTools::ProjectPart::Ptr> ppList = cppMM->projectPart(referencingFile);
if (ppList.size())
proFile = ppList.at(0)->projectFile;
if (m_quickDocMap.contains(fileName)) {
emit testItemModified(testItem, TestTreeModel::QuickTest, fileName);
TestInfo testInfo(testItem.name(), testItem.getChildNames(), 0, document->editorRevision());
testInfo.setReferencingFile(referencingFile);
testInfo.setProfile(proFile);
m_quickDocMap.insert(fileName, testInfo);
} else {
// if it was formerly unnamed remove the respective items
removeUnnamedQuickTestsByName(fileName);
emit unnamedQuickTestsRemoved(fileName);
emit testItemCreated(testItem, TestTreeModel::QuickTest);
TestInfo testInfo(testItem.name(), testItem.getChildNames(), 0, document->editorRevision());
testInfo.setReferencingFile(referencingFile);
testInfo.setProfile(proFile);
m_quickDocMap.insert(testItem.filePath(), testInfo);
}
}
void TestCodeParser::removeUnnamedQuickTestsByName(const QString &fileName)
{
for (int i = m_unnamedQuickDocList.size() - 1; i >= 0; --i) {
if (m_unnamedQuickDocList.at(i).fileName() == fileName)
m_unnamedQuickDocList.removeAt(i);
}
}
void TestCodeParser::onProFileEvaluated()
{
ProjectExplorer::Project *project = currentProject();
if (!project)
return;
CppTools::CppModelManager *modelManager = CppTools::CppModelManager::instance();
const QList<CppTools::ProjectPart::Ptr> pp = modelManager->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);
}
}
}
#ifdef WITH_TESTS
int TestCodeParser::autoTestsCount() const
{
int count = 0;
foreach (const QString &file, m_cppDocMap.keys()) {
if (m_cppDocMap.value(file).referencingFile().isEmpty())
++count;
}
return count;
}
int TestCodeParser::namedQuickTestsCount() const
{
return m_quickDocMap.size();
}
int TestCodeParser::unnamedQuickTestsCount() const
{
return m_unnamedQuickDocList.size();
}
#endif
} // namespace Internal
} // namespace Autotest