/**************************************************************************** ** ** 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 "autotestconstants.h" #include "autotest_utils.h" #include "testcodeparser.h" #include "testvisitor.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static Q_LOGGING_CATEGORY(LOG, "qtc.autotest.testcodeparser") namespace Autotest { namespace Internal { TestCodeParser::TestCodeParser(TestTreeModel *parent) : QObject(parent), m_model(parent), m_codeModelParsing(false), m_fullUpdatePostponed(false), m_partialUpdatePostponed(false), m_dirty(false), m_singleShotScheduled(false), m_parserState(Disabled) { // connect to ProgressManager to postpone test parsing when CppModelManager is parsing auto progressManager = qobject_cast(Core::ProgressManager::instance()); connect(progressManager, &Core::ProgressManager::taskStarted, this, &TestCodeParser::onTaskStarted); connect(progressManager, &Core::ProgressManager::allTasksFinished, this, &TestCodeParser::onAllTasksFinished); connect(&m_futureWatcher, &QFutureWatcher::started, this, &TestCodeParser::parsingStarted); connect(&m_futureWatcher, &QFutureWatcher::finished, this, &TestCodeParser::onFinished); connect(&m_futureWatcher, &QFutureWatcher::resultReadyAt, this, [this] (int index) { emit testParseResultReady(m_futureWatcher.resultAt(index)); }); } TestCodeParser::~TestCodeParser() { } void TestCodeParser::setState(State state) { qCDebug(LOG) << "setState(" << state << "), currentState:" << m_parserState; // avoid triggering parse before code model parsing has finished, but mark as dirty if (m_codeModelParsing) { m_dirty = true; qCDebug(LOG) << "Not setting new state - code model parsing is running, just marking dirty"; return; } if ((state == Disabled || state == Idle) && (m_parserState == PartialParse || m_parserState == FullParse)) { qCDebug(LOG) << "Not setting state, parse is running"; return; } m_parserState = state; if (m_parserState == Disabled) { m_fullUpdatePostponed = m_partialUpdatePostponed = false; m_postponedFiles.clear(); } else if (m_parserState == Idle && ProjectExplorer::SessionManager::startupProject()) { if (m_fullUpdatePostponed || m_dirty) { emitUpdateTestTree(); } else if (m_partialUpdatePostponed) { m_partialUpdatePostponed = false; qCDebug(LOG) << "calling scanForTests with postponed files (setState)"; scanForTests(m_postponedFiles.toList()); } } } void TestCodeParser::emitUpdateTestTree() { if (m_singleShotScheduled) { qCDebug(LOG) << "not scheduling another updateTestTree"; return; } qCDebug(LOG) << "adding singleShot"; m_singleShotScheduled = true; QTimer::singleShot(1000, this, SLOT(updateTestTree())); } void TestCodeParser::updateTestTree() { m_singleShotScheduled = false; if (m_codeModelParsing) { m_fullUpdatePostponed = true; m_partialUpdatePostponed = false; m_postponedFiles.clear(); return; } if (!ProjectExplorer::SessionManager::startupProject()) return; m_fullUpdatePostponed = false; emit aboutToPerformFullParse(); qCDebug(LOG) << "calling scanForTests (updateTestTree)"; 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 includes = doc->resolvedIncludes(); foreach (const CPlusPlus::Document::Include &inc, includes) { // TODO this short cut works only for #include // 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 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 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 includesGTest(const CPlusPlus::Document::Ptr &doc, const CppTools::CppModelManager *cppMM) { const QString gtestH = QLatin1String("gtest/gtest.h"); foreach (const CPlusPlus::Document::Include &inc, doc->resolvedIncludes()) { if (inc.resolvedFileName().endsWith(gtestH)) return true; } if (cppMM) { const CPlusPlus::Snapshot snapshot = cppMM->snapshot(); foreach (const QString &include, snapshot.allIncludesForDocument(doc->fileName())) { if (include.endsWith(gtestH)) return true; } } return false; } static bool qtTestLibDefined(const CppTools::CppModelManager *cppMM, const QString &fileName) { const QList 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 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) { const QList macros = document->macroUses(); foreach (const CPlusPlus::Document::MacroUse ¯o, macros) { if (!macro.isFunctionLike()) continue; const QByteArray name = macro.macro().name(); if (TestUtils::isQTestMacro(name)) { 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) { const QList macros = doc->macroUses(); foreach (const CPlusPlus::Document::MacroUse ¯o, macros) { if (!macro.isFunctionLike()) continue; const QByteArray name = macro.macro().name(); if (TestUtils::isQuickTestMacro(name)) { CPlusPlus::Document::Block arg = macro.arguments().at(0); return QLatin1String(getFileContent(doc->fileName()) .mid(arg.bytesBegin(), arg.bytesEnd() - arg.bytesBegin())); } } return QString(); } static bool hasGTestNames(CPlusPlus::Document::Ptr &document) { foreach (const CPlusPlus::Document::MacroUse ¯o, document->macroUses()) { if (!macro.isFunctionLike()) continue; if (TestUtils::isGTestMacro(QLatin1String(macro.macro().name()))) { const QVector args = macro.arguments(); if (args.size() != 2) continue; return true; } } return false; } 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 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 bool hasFunctionWithDataTagUsage(const QMap &testFunctions) { foreach (const QString &functionName, testFunctions.keys()) { if (functionName.endsWith(QLatin1String("_data")) && testFunctions.contains(functionName.left(functionName.size() - 5))) { return true; } } return false; } static QMap checkForDataTags(const QString &fileName, const QMap &testFunctions) { if (hasFunctionWithDataTagUsage(testFunctions)) { const CPlusPlus::Snapshot snapshot = CPlusPlus::CppModelManagerBase::instance()->snapshot(); const QByteArray fileContent = getFileContent(fileName); CPlusPlus::Document::Ptr document = snapshot.preprocessedDocument(fileContent, fileName); document->check(); CPlusPlus::AST *ast = document->translationUnit()->ast(); TestDataFunctionVisitor visitor(document); visitor.accept(ast); return visitor.dataTags(); } return QMap(); } /****** end of helpers ******/ static void checkQmlDocumentForTestCode(QFutureInterface futureInterface, const QmlJS::Document::Ptr &qmlJSDoc, const QString &proFile = QString()) { QmlJS::AST::Node *ast = qmlJSDoc->ast(); QTC_ASSERT(ast, return); TestQmlVisitor qmlVisitor(qmlJSDoc); QmlJS::AST::Node::accept(ast, &qmlVisitor); const QString testCaseName = qmlVisitor.testCaseName(); const TestCodeLocationAndType tcLocationAndType = qmlVisitor.testCaseLocation(); const QMap testFunctions = qmlVisitor.testFunctions(); TestParseResult parseResult(TestTreeModel::QuickTest); parseResult.proFile = proFile; parseResult.functions = testFunctions; if (!testCaseName.isEmpty()) { parseResult.fileName = tcLocationAndType.m_name; parseResult.testCaseName = testCaseName; parseResult.line = tcLocationAndType.m_line; parseResult.column = tcLocationAndType.m_column; } futureInterface.reportResult(parseResult); } static void handleQtQuickTest(QFutureInterface futureInterface, CPlusPlus::Document::Ptr document) { const CppTools::CppModelManager *modelManager = CppTools::CppModelManager::instance(); if (quickTestName(document).isEmpty()) return; const QString cppFileName = document->fileName(); QList ppList = modelManager->projectPart(cppFileName); QTC_ASSERT(!ppList.isEmpty(), return); const QString &proFile = ppList.at(0)->projectFile; const QString srcDir = quickTestSrcDir(modelManager, cppFileName); if (srcDir.isEmpty()) return; const QList qmlDocs = scanDirectoryForQuickTestQmlFiles(srcDir); foreach (const QmlJS::Document::Ptr &qmlJSDoc, qmlDocs) checkQmlDocumentForTestCode(futureInterface, qmlJSDoc, proFile); } static void handleGTest(QFutureInterface futureInterface, const QString &filePath) { const QByteArray &fileContent = getFileContent(filePath); const CPlusPlus::Snapshot snapshot = CPlusPlus::CppModelManagerBase::instance()->snapshot(); CPlusPlus::Document::Ptr document = snapshot.preprocessedDocument(fileContent, filePath); document->check(); CPlusPlus::AST *ast = document->translationUnit()->ast(); GTestVisitor visitor(document); visitor.accept(ast); QMap result = visitor.gtestFunctions(); QString proFile; const CppTools::CppModelManager *cppMM = CppTools::CppModelManager::instance(); QList ppList = cppMM->projectPart(filePath); if (ppList.size()) proFile = ppList.first()->projectFile; foreach (const GTestCaseSpec &testSpec, result.keys()) { TestParseResult parseResult(TestTreeModel::GoogleTest); parseResult.fileName = filePath; parseResult.testCaseName = testSpec.testCaseName; parseResult.parameterized = testSpec.parameterized; parseResult.typed = testSpec.typed; parseResult.proFile = proFile; parseResult.dataTagsOrTestSets.insert(QString(), result.value(testSpec)); futureInterface.reportResult(parseResult); } } static void checkDocumentForTestCode(QFutureInterface futureInterface, CPlusPlus::Document::Ptr document, QMap testCaseNames) { const QString fileName = document->fileName(); const CppTools::CppModelManager *modelManager = CppTools::CppModelManager::instance(); QList projParts = modelManager->projectPart(fileName); if (projParts.size()) { if (!projParts.at(0)->selectedForBuilding) return; } if (includesQtQuickTest(document, modelManager)) { handleQtQuickTest(futureInterface, document); } else if (testCaseNames.contains(fileName) // if we do a reparse || (includesQtTest(document, modelManager) && qtTestLibDefined(modelManager, fileName))) { QString testCaseName(testClass(modelManager, document)); // we might be in a reparse without the original entry point with the QTest::qExec() if (testCaseName.isEmpty()) testCaseName = testCaseNames.value(fileName); 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 testFunctions = visitor.privateSlots(); QMap dataTags = checkForDataTags(declaringDoc->fileName(), testFunctions); if (declaringDoc->fileName() != document->fileName()) dataTags.unite(checkForDataTags(document->fileName(), testFunctions)); TestParseResult parseResult(TestTreeModel::AutoTest); parseResult.fileName = declaringDoc->fileName(); parseResult.testCaseName = testCaseName; parseResult.line = line; parseResult.column = column; parseResult.functions = testFunctions; parseResult.dataTagsOrTestSets = dataTags; parseResult.proFile = projParts.first()->projectFile; futureInterface.reportResult(parseResult); } } else if (includesGTest(document, modelManager)) { if (hasGTestNames(document)) handleGTest(futureInterface, document->fileName()); } } // used internally to indicate a parse that failed due to having triggered a parse for a file that // is not (yet) part of the CppModelManager's snapshot static bool parsingHasFailed; static void performParse(QFutureInterface &futureInterface, const QStringList &list, const QMap testCaseNames) { int progressValue = 0; futureInterface.setProgressRange(0, list.size()); futureInterface.setProgressValue(progressValue); CppTools::CppModelManager *cppMM = CppTools::CppModelManager::instance(); CPlusPlus::Snapshot snapshot = cppMM->snapshot(); QmlJS::Snapshot qmlSnapshot = QmlJSTools::Internal::ModelManager::instance()->snapshot(); foreach (const QString &file, list) { if (file.endsWith(QLatin1String(".qml"))) { checkQmlDocumentForTestCode(futureInterface, qmlSnapshot.document(file)); } else if (snapshot.contains(file)) { CPlusPlus::Document::Ptr doc = snapshot.find(file).value(); futureInterface.setProgressValue(++progressValue); checkDocumentForTestCode(futureInterface, doc, testCaseNames); } else { parsingHasFailed |= (CppTools::ProjectFile::classify(file) != CppTools::ProjectFile::Unclassified); } } futureInterface.setProgressValue(list.size()); } /****** threaded parsing stuff *******/ void TestCodeParser::onCppDocumentUpdated(const CPlusPlus::Document::Ptr &document) { if (m_codeModelParsing) { if (!m_fullUpdatePostponed) { m_partialUpdatePostponed = true; m_postponedFiles.insert(document->fileName()); } return; } ProjectExplorer::Project *project = ProjectExplorer::SessionManager::startupProject(); if (!project) return; const QString fileName = document->fileName(); if (!project->files(ProjectExplorer::Project::AllFiles).contains(fileName)) return; qCDebug(LOG) << "calling scanForTests (onCppDocumentUpdated)"; scanForTests(QStringList(fileName)); } void TestCodeParser::onQmlDocumentUpdated(const QmlJS::Document::Ptr &document) { const QString &fileName = document->fileName(); if (m_codeModelParsing) { if (!m_fullUpdatePostponed) { m_partialUpdatePostponed = true; m_postponedFiles.insert(fileName); } return; } ProjectExplorer::Project *project = ProjectExplorer::SessionManager::startupProject(); if (!project) return; 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; } scanForTests(QStringList(fileName)); } void TestCodeParser::onStartupProjectChanged(ProjectExplorer::Project *project) { if (m_parserState == FullParse || m_parserState == PartialParse) Core::ProgressManager::instance()->cancelTasks(Constants::TASK_PARSE); else if (project) emitUpdateTestTree(); } void TestCodeParser::onProjectPartsUpdated(ProjectExplorer::Project *project) { if (project != ProjectExplorer::SessionManager::startupProject()) return; if (m_codeModelParsing || m_parserState == Disabled) m_fullUpdatePostponed = true; else emitUpdateTestTree(); } bool TestCodeParser::postponed(const QStringList &fileList) { switch (m_parserState) { case Idle: return false; case PartialParse: case FullParse: // parse 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 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; m_postponedFiles.clear(); bool isFullParse = fileList.isEmpty(); QStringList list; if (isFullParse) { list = ProjectExplorer::SessionManager::startupProject()->files(ProjectExplorer::Project::AllFiles); if (list.isEmpty()) return; qCDebug(LOG) << "setting state to FullParse (scanForTests)"; m_parserState = FullParse; } else { list << fileList; qCDebug(LOG) << "setting state to PartialParse (scanForTests)"; m_parserState = PartialParse; } parsingHasFailed = false; QMap testCaseNames; if (isFullParse) { // remove qml files as they will be found automatically by the referencing cpp file list = Utils::filtered(list, [] (const QString &fn) { return !fn.endsWith(QLatin1String(".qml")); }); m_model->markAllForRemoval(); } else { testCaseNames = m_model->testCaseNamesForFiles(list); foreach (const QString &filePath, list) m_model->markForRemoval(filePath); } QFuture future = Utils::runAsync(&performParse, list, testCaseNames); m_futureWatcher.setFuture(future); if (list.size() > 5) { Core::ProgressManager::addTask(future, tr("Scanning for Tests"), Autotest::Constants::TASK_PARSE); } } void TestCodeParser::onTaskStarted(Core::Id type) { if (type == CppTools::Constants::TASK_INDEX) m_codeModelParsing = true; } 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_codeModelParsing = false; // avoid illegal parser state if respective widgets became hidden while parsing setState(Idle); } void TestCodeParser::onFinished() { switch (m_parserState) { case PartialParse: qCDebug(LOG) << "setting state to Idle (onFinished, PartialParse)"; m_parserState = Idle; onPartialParsingFinished(); break; case FullParse: qCDebug(LOG) << "setting state to Idle (onFinished, FullParse)"; m_parserState = Idle; m_dirty = parsingHasFailed; if (m_partialUpdatePostponed || m_fullUpdatePostponed || parsingHasFailed) { onPartialParsingFinished(); } else { qCDebug(LOG) << "emitting parsingFinished" << "(onFinished, FullParse, nothing postponed, parsing succeeded)"; emit parsingFinished(); } m_dirty = false; break; case Disabled: // can happen if all Test related widgets become hidden while parsing qCDebug(LOG) << "emitting parsingFinished (onFinished, Disabled)"; 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; qCDebug(LOG) << "calling updateTestTree (onPartialParsingFinished)"; updateTestTree(); } else if (m_partialUpdatePostponed) { m_partialUpdatePostponed = false; qCDebug(LOG) << "calling scanForTests with postponed files (onPartialParsingFinished)"; scanForTests(m_postponedFiles.toList()); } else { m_dirty |= m_codeModelParsing; if (m_dirty) { emit parsingFailed(); } else if (!m_singleShotScheduled) { qCDebug(LOG) << "emitting parsingFinished" << "(onPartialParsingFinished, nothing postponed, not dirty)"; emit parsingFinished(); } else { qCDebug(LOG) << "not emitting parsingFinished" << "(on PartialParsingFinished, singleshot scheduled)"; } } } } // namespace Internal } // namespace Autotest