forked from qt-creator/qt-creator
Enables to limit the scanning for tests and respectively any further action to a list of user defined patterns. If limitation is enabled and any of the filter patterns does match the file will be processed. If no filter pattern matches the file will be ignored. Change-Id: I6a6de8f4137485e83b750997fb3c948dc6e79c68 Reviewed-by: Leena Miettinen <riitta-leena.miettinen@qt.io> Reviewed-by: David Schulz <david.schulz@qt.io>
559 lines
20 KiB
C++
559 lines
20 KiB
C++
// Copyright (C) 2016 The Qt Company Ltd.
|
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
|
|
|
#include "testcodeparser.h"
|
|
|
|
#include "autotestconstants.h"
|
|
#include "autotestplugin.h"
|
|
#include "autotesttr.h"
|
|
#include "testprojectsettings.h"
|
|
#include "testsettings.h"
|
|
#include "testtreemodel.h"
|
|
|
|
#include <coreplugin/editormanager/editormanager.h>
|
|
#include <coreplugin/progressmanager/progressmanager.h>
|
|
#include <coreplugin/progressmanager/taskprogress.h>
|
|
#include <cppeditor/cppeditorconstants.h>
|
|
#include <cppeditor/cppmodelmanager.h>
|
|
#include <projectexplorer/buildsystem.h>
|
|
#include <projectexplorer/project.h>
|
|
#include <projectexplorer/projectmanager.h>
|
|
|
|
#include <utils/algorithm.h>
|
|
#include <utils/async.h>
|
|
#include <utils/qtcassert.h>
|
|
|
|
#include <QLoggingCategory>
|
|
|
|
using namespace Core;
|
|
using namespace Tasking;
|
|
using namespace Utils;
|
|
|
|
namespace Autotest {
|
|
namespace Internal {
|
|
|
|
Q_LOGGING_CATEGORY(LOG, "qtc.autotest.testcodeparser", QtWarningMsg)
|
|
|
|
using namespace ProjectExplorer;
|
|
|
|
static bool isProjectParsing()
|
|
{
|
|
const BuildSystem *bs = ProjectManager::startupBuildSystem();
|
|
return bs && (bs->isParsing() || bs->isWaitingForParse());
|
|
}
|
|
|
|
TestCodeParser::TestCodeParser()
|
|
{
|
|
// connect to ProgressManager to postpone test parsing when CppModelManager is parsing
|
|
ProgressManager *progressManager = ProgressManager::instance();
|
|
connect(progressManager, &ProgressManager::taskStarted,
|
|
this, &TestCodeParser::onTaskStarted);
|
|
connect(progressManager, &ProgressManager::allTasksFinished,
|
|
this, &TestCodeParser::onAllTasksFinished);
|
|
connect(this, &TestCodeParser::parsingFinished, this, &TestCodeParser::releaseParserInternals);
|
|
connect(EditorManager::instance(), &EditorManager::documentClosed, this, [this](IDocument *doc){
|
|
QTC_ASSERT(doc, return);
|
|
if (FilePath filePath = doc->filePath(); filePath.endsWith(".qml"))
|
|
m_qmlEditorRev.remove(filePath);
|
|
});
|
|
m_reparseTimer.setSingleShot(true);
|
|
m_reparseTimer.setInterval(1000);
|
|
connect(&m_reparseTimer, &QTimer::timeout, this, &TestCodeParser::parsePostponedFiles);
|
|
connect(&m_taskTreeRunner, &TaskTreeRunner::aboutToStart, this, [this](TaskTree *taskTree) {
|
|
if (m_withTaskProgress) {
|
|
auto progress = new TaskProgress(taskTree);
|
|
progress->setDisplayName(Tr::tr("Scanning for Tests"));
|
|
progress->setId(Constants::TASK_PARSE);
|
|
}
|
|
emit parsingStarted();
|
|
});
|
|
connect(&m_taskTreeRunner, &TaskTreeRunner::done, this, [this](DoneWith result) {
|
|
onFinished(result == DoneWith::Success);
|
|
});
|
|
}
|
|
|
|
TestCodeParser::~TestCodeParser() = default;
|
|
|
|
void TestCodeParser::setState(State state)
|
|
{
|
|
if (m_parserState == Shutdown)
|
|
return;
|
|
qCDebug(LOG) << "setState(" << state << "), currentState:" << m_parserState;
|
|
if (m_parserState == DisabledTemporarily && state == Idle) {
|
|
m_parserState = Idle;
|
|
qCDebug(LOG) << "Just re-enabling parser.";
|
|
return;
|
|
}
|
|
|
|
// avoid triggering parse before code model parsing has finished, but mark as dirty
|
|
if (isProjectParsing() || m_codeModelParsing) {
|
|
m_dirty = true;
|
|
qCDebug(LOG) << "Not setting new state - code model parsing is running, just marking dirty";
|
|
return;
|
|
}
|
|
|
|
if ((state == Idle) && (m_parserState == PartialParse || m_parserState == FullParse)) {
|
|
qCDebug(LOG) << "Not setting state, parse is running";
|
|
return;
|
|
}
|
|
m_parserState = state;
|
|
|
|
if (m_parserState == Idle && ProjectManager::startupProject()) {
|
|
if (m_postponedUpdateType == UpdateType::FullUpdate || m_dirty) {
|
|
emitUpdateTestTree();
|
|
} else if (m_postponedUpdateType == UpdateType::PartialUpdate) {
|
|
m_postponedUpdateType = UpdateType::NoUpdate;
|
|
qCDebug(LOG) << "calling scanForTests with postponed files (setState)";
|
|
if (!m_reparseTimer.isActive())
|
|
scanForTests(m_postponedFiles);
|
|
}
|
|
}
|
|
}
|
|
|
|
void TestCodeParser::syncTestFrameworks(const QList<ITestParser *> &parsers)
|
|
{
|
|
if (m_parserState != Idle) {
|
|
// there's a running parse
|
|
m_postponedUpdateType = UpdateType::FullUpdate;
|
|
m_postponedFiles.clear();
|
|
ProgressManager::cancelTasks(Constants::TASK_PARSE);
|
|
}
|
|
qCDebug(LOG) << "Setting" << parsers << "as current parsers";
|
|
m_testCodeParsers = parsers;
|
|
}
|
|
|
|
void TestCodeParser::emitUpdateTestTree(ITestParser *parser)
|
|
{
|
|
if (m_testCodeParsers.isEmpty())
|
|
return;
|
|
if (parser)
|
|
m_updateParsers.insert(parser);
|
|
else
|
|
m_updateParsers.clear();
|
|
if (m_singleShotScheduled) {
|
|
qCDebug(LOG) << "not scheduling another updateTestTree";
|
|
return;
|
|
}
|
|
|
|
qCDebug(LOG) << "adding singleShot";
|
|
m_singleShotScheduled = true;
|
|
QTimer::singleShot(1000, this, [this] { updateTestTree(m_updateParsers); });
|
|
}
|
|
|
|
void TestCodeParser::updateTestTree(const QSet<ITestParser *> &parsers)
|
|
{
|
|
m_singleShotScheduled = false;
|
|
if (isProjectParsing() || m_codeModelParsing) {
|
|
m_postponedUpdateType = UpdateType::FullUpdate;
|
|
m_postponedFiles.clear();
|
|
if (parsers.isEmpty()) {
|
|
m_updateParsers.clear();
|
|
} else {
|
|
for (ITestParser *parser : parsers)
|
|
m_updateParsers.insert(parser);
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (!ProjectManager::startupProject())
|
|
return;
|
|
|
|
m_postponedUpdateType = UpdateType::NoUpdate;
|
|
qCDebug(LOG) << "calling scanForTests (updateTestTree)";
|
|
const QList<ITestParser *> sortedParsers = Utils::sorted(Utils::toList(parsers),
|
|
[](const ITestParser *lhs, const ITestParser *rhs) {
|
|
return lhs->framework()->priority() < rhs->framework()->priority();
|
|
});
|
|
scanForTests({}, sortedParsers);
|
|
}
|
|
|
|
/****** threaded parsing stuff *******/
|
|
|
|
void TestCodeParser::onDocumentUpdated(const FilePath &fileName, bool isQmlFile)
|
|
{
|
|
if (isProjectParsing() || m_codeModelParsing || m_postponedUpdateType == UpdateType::FullUpdate)
|
|
return;
|
|
|
|
Project *project = ProjectManager::startupProject();
|
|
if (!project)
|
|
return;
|
|
// Quick tests: qml files aren't necessarily listed inside project files
|
|
if (!isQmlFile && !project->isKnownFile(fileName))
|
|
return;
|
|
|
|
scanForTests({fileName});
|
|
}
|
|
|
|
void TestCodeParser::onCppDocumentUpdated(const CPlusPlus::Document::Ptr &document)
|
|
{
|
|
onDocumentUpdated(document->filePath());
|
|
}
|
|
|
|
void TestCodeParser::onQmlDocumentUpdated(const QmlJS::Document::Ptr &document)
|
|
{
|
|
static const QStringList ignoredSuffixes{ "qbs", "ui.qml" };
|
|
const FilePath fileName = document->fileName();
|
|
int editorRevision = document->editorRevision();
|
|
if (editorRevision != m_qmlEditorRev.value(fileName, 0)) {
|
|
m_qmlEditorRev.insert(fileName, editorRevision);
|
|
if (!ignoredSuffixes.contains(fileName.suffix()))
|
|
onDocumentUpdated(fileName, true);
|
|
}
|
|
}
|
|
|
|
void TestCodeParser::onStartupProjectChanged(Project *project)
|
|
{
|
|
m_qmlEditorRev.clear();
|
|
if (m_parserState == FullParse || m_parserState == PartialParse) {
|
|
qCDebug(LOG) << "Canceling scanForTest (startup project changed)";
|
|
ProgressManager::cancelTasks(Constants::TASK_PARSE);
|
|
}
|
|
emit aboutToPerformFullParse();
|
|
if (project)
|
|
emitUpdateTestTree();
|
|
}
|
|
|
|
void TestCodeParser::onProjectPartsUpdated(Project *project)
|
|
{
|
|
if (project != ProjectManager::startupProject())
|
|
return;
|
|
if (isProjectParsing() || m_codeModelParsing)
|
|
m_postponedUpdateType = UpdateType::FullUpdate;
|
|
else
|
|
emitUpdateTestTree();
|
|
}
|
|
|
|
void TestCodeParser::aboutToShutdown(bool isFinal)
|
|
{
|
|
qCDebug(LOG) << "Disabling (immediately) -"
|
|
<< (isFinal ? "shutting down" : "disabled temporarily");
|
|
m_parserState = isFinal ? Shutdown : DisabledTemporarily;
|
|
m_taskTreeRunner.reset();
|
|
m_futureSynchronizer.waitForFinished();
|
|
if (!isFinal)
|
|
onFinished(false);
|
|
}
|
|
|
|
bool TestCodeParser::postponed(const QSet<FilePath> &filePaths)
|
|
{
|
|
switch (m_parserState) {
|
|
case Idle:
|
|
if (filePaths.size() == 1) {
|
|
if (m_reparseTimerTimedOut)
|
|
return false;
|
|
|
|
m_postponedFiles.insert(*filePaths.begin());
|
|
m_reparseTimer.start();
|
|
return true;
|
|
}
|
|
return false;
|
|
case PartialParse:
|
|
case FullParse:
|
|
// parse is running, postponing a full parse
|
|
if (filePaths.isEmpty()) {
|
|
m_postponedFiles.clear();
|
|
m_postponedUpdateType = UpdateType::FullUpdate;
|
|
qCDebug(LOG) << "Canceling scanForTest (full parse triggered while running a scan)";
|
|
ProgressManager::cancelTasks(Constants::TASK_PARSE);
|
|
} else {
|
|
// partial parse triggered, but full parse is postponed already, ignoring this
|
|
if (m_postponedUpdateType == UpdateType::FullUpdate)
|
|
return true;
|
|
// partial parse triggered, postpone or add current files to already postponed partial
|
|
m_postponedFiles += filePaths;
|
|
m_postponedUpdateType = UpdateType::PartialUpdate;
|
|
}
|
|
return true;
|
|
case Shutdown:
|
|
case DisabledTemporarily:
|
|
break;
|
|
}
|
|
QTC_ASSERT(false, return false); // should not happen at all
|
|
}
|
|
|
|
static void parseFileForTests(QPromise<TestParseResultPtr> &promise,
|
|
const QList<ITestParser *> &parsers, const FilePath &fileName)
|
|
{
|
|
for (ITestParser *parser : parsers) {
|
|
if (promise.isCanceled())
|
|
return;
|
|
if (parser->processDocument(promise, fileName))
|
|
break;
|
|
}
|
|
}
|
|
|
|
void TestCodeParser::scanForTests(const QSet<FilePath> &filePaths,
|
|
const QList<ITestParser *> &parsers)
|
|
{
|
|
if (m_parserState == Shutdown || m_parserState == DisabledTemporarily || m_testCodeParsers.isEmpty())
|
|
return;
|
|
|
|
if (postponed(filePaths))
|
|
return;
|
|
|
|
QSet<FilePath> files = filePaths; // avoid getting cleared if m_postponedFiles have been passed
|
|
m_reparseTimer.stop();
|
|
m_reparseTimerTimedOut = false;
|
|
m_postponedFiles.clear();
|
|
const bool isFullParse = files.isEmpty();
|
|
Project *project = ProjectManager::startupProject();
|
|
if (!project)
|
|
return;
|
|
if (isFullParse) {
|
|
files = Utils::toSet(project->files(Project::SourceFiles));
|
|
if (files.isEmpty()) {
|
|
// at least project file should be there, but might happen if parsing current project
|
|
// takes too long, especially when opening sessions holding multiple projects
|
|
qCDebug(LOG) << "File list empty (FullParse) - trying again in a sec";
|
|
emitUpdateTestTree();
|
|
return;
|
|
} else if (files.size() == 1 && *files.constBegin() == project->projectFilePath()) {
|
|
qCDebug(LOG) << "File list contains only the project file.";
|
|
return;
|
|
}
|
|
|
|
qCDebug(LOG) << "setting state to FullParse (scanForTests)";
|
|
m_parserState = FullParse;
|
|
} else {
|
|
qCDebug(LOG) << "setting state to PartialParse (scanForTests)";
|
|
m_parserState = PartialParse;
|
|
}
|
|
|
|
m_parsingHasFailed = false;
|
|
TestTreeModel::instance()->updateCheckStateCache();
|
|
if (isFullParse) {
|
|
// remove qml files as they will be found automatically by the referencing cpp file
|
|
files = Utils::filtered(files, [](const FilePath &fn) { return !fn.endsWith(".qml"); });
|
|
if (!parsers.isEmpty()) {
|
|
for (ITestParser *parser : parsers)
|
|
parser->framework()->rootNode()->markForRemovalRecursively(true);
|
|
} else {
|
|
emit requestRemoveAllFrameworkItems();
|
|
}
|
|
} else if (!parsers.isEmpty()) {
|
|
for (ITestParser *parser: parsers) {
|
|
parser->framework()->rootNode()->markForRemovalRecursively(files);
|
|
}
|
|
} else {
|
|
emit requestRemoval(files);
|
|
}
|
|
|
|
const TestProjectSettings *settings = projectSettings(project);
|
|
if (settings->limitToFilters()) {
|
|
qCDebug(LOG) << "Applying project path filters - currently" << files.size() << "files";
|
|
const QStringList filters = settings->pathFilters();
|
|
if (!filters.isEmpty()) {
|
|
// we cannot rely on QRegularExpression::fromWildcard() as we want handle paths
|
|
const QList<QRegularExpression> regexes
|
|
= Utils::transform(filters, [] (const QString &filter) {
|
|
return QRegularExpression(wildcardPatternFromString(filter));
|
|
});
|
|
|
|
files = Utils::filtered(files, [®exes](const FilePath &fn) {
|
|
for (const QRegularExpression ®ex : regexes) {
|
|
if (!regex.isValid()) {
|
|
qCDebug(LOG) << "Skipping invalid pattern? Pattern:" << regex.pattern();
|
|
continue;
|
|
}
|
|
if (regex.match(fn.path()).hasMatch())
|
|
return true;
|
|
}
|
|
return false;
|
|
});
|
|
}
|
|
qCDebug(LOG) << "After applying filters" << files.size() << "files";
|
|
|
|
if (files.isEmpty()) {
|
|
qCDebug(LOG) << "No filter matched a file - canceling scan immediately";
|
|
onFinished(true);
|
|
return;
|
|
}
|
|
}
|
|
|
|
QTC_ASSERT(!(isFullParse && files.isEmpty()), onFinished(true); return);
|
|
|
|
// use only a single parser or all current active?
|
|
const QList<ITestParser *> codeParsers = parsers.isEmpty() ? m_testCodeParsers : parsers;
|
|
qCDebug(LOG) << QDateTime::currentDateTime().toString("hh:mm:ss.zzz") << "StartParsing";
|
|
m_parsingTimer.restart();
|
|
QSet<QString> extensions;
|
|
const auto cppSnapshot = CppEditor::CppModelManager::snapshot();
|
|
|
|
for (ITestParser *parser : codeParsers) {
|
|
parser->init(files, isFullParse);
|
|
for (const QString &ext : parser->supportedExtensions())
|
|
extensions.insert(ext);
|
|
}
|
|
// We are only interested in files that have been either parsed by the c++ parser,
|
|
// or have an extension that one of the parsers is specifically interested in.
|
|
const QSet<FilePath> filteredFiles
|
|
= Utils::filtered(files, [&extensions, &cppSnapshot](const FilePath &fn) {
|
|
const bool isSupportedExtension = Utils::anyOf(extensions, [&fn](const QString &ext) {
|
|
return fn.suffix() == ext;
|
|
});
|
|
if (isSupportedExtension)
|
|
return true;
|
|
return cppSnapshot.contains(fn);
|
|
});
|
|
m_withTaskProgress = isFullParse || filteredFiles.size() > 20;
|
|
|
|
qCDebug(LOG) << "Starting scan of" << filteredFiles.size() << "(" << files.size() << ")"
|
|
<< "files with" << codeParsers.size() << "parsers";
|
|
|
|
int limit = testSettings().scanThreadLimit();
|
|
if (limit == 0)
|
|
limit = std::max(QThread::idealThreadCount() / 4, 1);
|
|
qCDebug(LOG) << "Using" << limit << "threads for scan.";
|
|
|
|
const Storage<QSet<FilePath>::const_iterator> storage;
|
|
const auto onSetup = [this, codeParsers, storage](Async<TestParseResultPtr> &async) {
|
|
async.setConcurrentCallData(parseFileForTests, codeParsers, **storage);
|
|
async.setPriority(QThread::LowestPriority);
|
|
async.setFutureSynchronizer(&m_futureSynchronizer);
|
|
++*storage;
|
|
};
|
|
const auto onDone = [this](const Async<TestParseResultPtr> &async) {
|
|
const QList<TestParseResultPtr> &results = async.results();
|
|
if (!results.isEmpty())
|
|
emit testParseResultsReady(results);
|
|
};
|
|
const Group root {
|
|
parallelLimit(limit),
|
|
storage,
|
|
onGroupSetup([storage, filteredFiles] { *storage = filteredFiles.cbegin(); }),
|
|
LoopRepeat(filteredFiles.size()),
|
|
AsyncTask<TestParseResultPtr>(onSetup, onDone, CallDoneIf::Success)
|
|
};
|
|
m_taskTreeRunner.start(root);
|
|
}
|
|
|
|
void TestCodeParser::onTaskStarted(Id type)
|
|
{
|
|
if (type == CppEditor::Constants::TASK_INDEX) {
|
|
m_codeModelParsing = true;
|
|
if (m_parserState == FullParse || m_parserState == PartialParse) {
|
|
m_postponedUpdateType = m_parserState == FullParse
|
|
? UpdateType::FullUpdate : UpdateType::PartialUpdate;
|
|
qCDebug(LOG) << "Canceling scan for test (CppModelParsing started)";
|
|
m_parsingHasFailed = true;
|
|
ProgressManager::cancelTasks(Constants::TASK_PARSE);
|
|
}
|
|
}
|
|
}
|
|
|
|
void TestCodeParser::onAllTasksFinished(Id type)
|
|
{
|
|
// if we cancel parsing ensure that progress animation is canceled as well
|
|
if (type == Constants::TASK_PARSE && m_parsingHasFailed)
|
|
emit parsingFailed();
|
|
|
|
// only CPP parsing is relevant as we trigger Qml parsing internally anyway
|
|
if (type != CppEditor::Constants::TASK_INDEX)
|
|
return;
|
|
m_codeModelParsing = false;
|
|
|
|
// avoid illegal parser state if respective widgets became hidden while parsing
|
|
if (m_parserState != DisabledTemporarily)
|
|
setState(Idle);
|
|
}
|
|
|
|
void TestCodeParser::onFinished(bool success)
|
|
{
|
|
m_parsingHasFailed = !success;
|
|
switch (m_parserState) {
|
|
case PartialParse:
|
|
qCDebug(LOG) << "setting state to Idle (onFinished, PartialParse)";
|
|
m_parserState = Idle;
|
|
onPartialParsingFinished();
|
|
qCDebug(LOG) << QDateTime::currentDateTime().toString("hh:mm:ss.zzz") << "PartParsingFin";
|
|
break;
|
|
case FullParse:
|
|
qCDebug(LOG) << "setting state to Idle (onFinished, FullParse)";
|
|
m_parserState = Idle;
|
|
m_dirty = m_parsingHasFailed;
|
|
if (m_postponedUpdateType != UpdateType::NoUpdate || m_parsingHasFailed) {
|
|
onPartialParsingFinished();
|
|
} else {
|
|
qCDebug(LOG) << "emitting parsingFinished"
|
|
<< "(onFinished, FullParse, nothing postponed, parsing succeeded)";
|
|
m_updateParsers.clear();
|
|
emit parsingFinished();
|
|
qCDebug(LOG) << QDateTime::currentDateTime().toString("hh:mm:ss.zzz") << "ParsingFin";
|
|
qCDebug(LOG) << "Parsing took:" << m_parsingTimer.elapsed() << "ms";
|
|
if (LOG().isInfoEnabled()) {
|
|
qCInfo(LOG).noquote().nospace()
|
|
<< "Current test tree:" << TestTreeModel::instance()->report(true);
|
|
} else {
|
|
qCDebug(LOG).noquote().nospace()
|
|
<< "Current test tree:" << TestTreeModel::instance()->report(false);
|
|
}
|
|
}
|
|
m_dirty = false;
|
|
break;
|
|
case Shutdown:
|
|
qCDebug(LOG) << "Shutdown complete - not emitting parsingFinished (onFinished)";
|
|
break;
|
|
case DisabledTemporarily:
|
|
qCDebug(LOG) << "Disabling complete - emitting parsingFinished";
|
|
emit parsingFinished(); // ensure hidden progress indicator
|
|
break;
|
|
default:
|
|
qWarning("I should not be here... State: %d", m_parserState);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void TestCodeParser::onPartialParsingFinished()
|
|
{
|
|
const UpdateType oldType = m_postponedUpdateType;
|
|
m_postponedUpdateType = UpdateType::NoUpdate;
|
|
switch (oldType) {
|
|
case UpdateType::FullUpdate:
|
|
qCDebug(LOG) << "calling updateTestTree (onPartialParsingFinished)";
|
|
updateTestTree(m_updateParsers);
|
|
break;
|
|
case UpdateType::PartialUpdate:
|
|
qCDebug(LOG) << "calling scanForTests with postponed files (onPartialParsingFinished)";
|
|
if (!m_reparseTimer.isActive())
|
|
scanForTests(m_postponedFiles);
|
|
break;
|
|
case UpdateType::NoUpdate:
|
|
m_dirty |= m_codeModelParsing;
|
|
if (m_dirty) {
|
|
emit parsingFailed();
|
|
qCDebug(LOG) << QDateTime::currentDateTime().toString("hh:mm:ss.zzz") << "ParsingFail";
|
|
} else if (!m_singleShotScheduled) {
|
|
qCDebug(LOG) << "emitting parsingFinished"
|
|
<< "(onPartialParsingFinished, nothing postponed, not dirty)";
|
|
m_updateParsers.clear();
|
|
emit parsingFinished();
|
|
qCDebug(LOG) << QDateTime::currentDateTime().toString("hh:mm:ss.zzz") << "ParsingFin";
|
|
if (LOG().isDebugEnabled()) {
|
|
QMetaObject::invokeMethod(this, [] { // sweep() needs to be processed before logging
|
|
qCDebug(LOG).noquote().nospace()
|
|
<< "Current test tree:" << TestTreeModel::instance()->report(false);
|
|
}, Qt::QueuedConnection);
|
|
}
|
|
} else {
|
|
qCDebug(LOG) << "not emitting parsingFinished"
|
|
<< "(on PartialParsingFinished, singleshot scheduled)";
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
void TestCodeParser::parsePostponedFiles()
|
|
{
|
|
m_reparseTimerTimedOut = true;
|
|
scanForTests(m_postponedFiles);
|
|
}
|
|
|
|
void TestCodeParser::releaseParserInternals()
|
|
{
|
|
for (ITestParser *parser : std::as_const(m_testCodeParsers))
|
|
parser->release();
|
|
}
|
|
|
|
} // namespace Internal
|
|
} // namespace Autotest
|