Clang: Hook up supportive translation unit on first edit

Parsing happens rotationally on the translation units.

The recently parsed translation unit is used for completion jobs while
the older version is used for parse jobs.

Advantages:
  A1. A completion job cannot be blocked anymore by currently running
      parse job.
  A2. Faster triggering of parse jobs. A reparse was triggered about
      1650ms after the last keystroke. This is down to 500ms now since we
      do not have a blocking translation unit for the completion anymore.

Disadvantages:
  D1. Memory consumption is doubled for an edited document.
      This could be addressed by suspending the second translation unit
      after some time of inactivity.
  D2. Setup of the supportive translation unit takes some time.

Change-Id: I958c883c01f274530f5482c788c15cd38d6f4c3e
Reviewed-by: David Schulz <david.schulz@qt.io>
This commit is contained in:
Nikolai Kosjar
2016-09-14 16:16:10 +02:00
parent a85d5c7222
commit 380d756a03
34 changed files with 1201 additions and 17 deletions

View File

@@ -72,6 +72,11 @@ ClangEditorDocumentProcessor::ClangEditorDocumentProcessor(
, m_semanticHighlighter(document)
, m_builtinProcessor(document, /*enableSemanticHighlighter=*/ false)
{
m_updateTranslationUnitTimer.setSingleShot(true);
m_updateTranslationUnitTimer.setInterval(350);
connect(&m_updateTranslationUnitTimer, &QTimer::timeout,
this, &ClangEditorDocumentProcessor::updateTranslationUnitIfProjectPartExists);
// Forwarding the semantic info from the builtin processor enables us to provide all
// editor (widget) related features that are not yet implemented by the clang plugin.
connect(&m_builtinProcessor, &CppTools::BuiltinEditorDocumentProcessor::cppDocumentUpdated,
@@ -82,6 +87,8 @@ ClangEditorDocumentProcessor::ClangEditorDocumentProcessor(
ClangEditorDocumentProcessor::~ClangEditorDocumentProcessor()
{
m_updateTranslationUnitTimer.stop();
m_parserWatcher.cancel();
m_parserWatcher.waitForFinished();
@@ -93,7 +100,7 @@ ClangEditorDocumentProcessor::~ClangEditorDocumentProcessor()
void ClangEditorDocumentProcessor::run()
{
updateTranslationUnitIfProjectPartExists();
m_updateTranslationUnitTimer.start();
// Run clang parser
disconnect(&m_parserWatcher, &QFutureWatcher<void>::finished,
@@ -251,6 +258,11 @@ void ClangEditorDocumentProcessor::addDiagnosticToolTipToLayout(uint line,
addToolTipToLayout(diagnostic, target);
}
void ClangEditorDocumentProcessor::editorDocumentTimerRestarted()
{
m_updateTranslationUnitTimer.stop(); // Wait for the next call to run().
}
ClangBackEnd::FileContainer ClangEditorDocumentProcessor::fileContainerWithArguments() const
{
return fileContainerWithArguments(m_projectPart.data());

View File

@@ -32,6 +32,7 @@
#include <cpptools/semantichighlighter.h>
#include <QFutureWatcher>
#include <QTimer>
namespace ClangBackEnd {
class DiagnosticContainer;
@@ -78,6 +79,8 @@ public:
bool hasDiagnosticsAt(uint line, uint column) const override;
void addDiagnosticToolTipToLayout(uint line, uint column, QLayout *target) const override;
void editorDocumentTimerRestarted() override;
ClangBackEnd::FileContainer fileContainerWithArguments() const;
void clearDiagnosticsWithFixIts();
@@ -102,6 +105,7 @@ private:
QSharedPointer<ClangEditorDocumentParser> m_parser;
CppTools::ProjectPart::Ptr m_projectPart;
QFutureWatcher<void> m_parserWatcher;
QTimer m_updateTranslationUnitTimer;
unsigned m_parserRevision;
CppTools::SemanticHighlighter m_semanticHighlighter;

View File

@@ -230,12 +230,14 @@ void CppEditorDocument::scheduleProcessDocument()
{
m_processorRevision = document()->revision();
m_processorTimer.start();
processor()->editorDocumentTimerRestarted();
}
void CppEditorDocument::processDocument()
{
if (processor()->isParserRunning() || m_processorRevision != contentsRevision()) {
m_processorTimer.start();
processor()->editorDocumentTimerRestarted();
return;
}

View File

@@ -67,6 +67,10 @@ void BaseEditorDocumentProcessor::addDiagnosticToolTipToLayout(uint, uint, QLayo
{
}
void BaseEditorDocumentProcessor::editorDocumentTimerRestarted()
{
}
void BaseEditorDocumentProcessor::runParser(QFutureInterface<void> &future,
BaseEditorDocumentParser::Ptr parser,
const WorkingCopy workingCopy)

View File

@@ -67,6 +67,8 @@ public:
virtual bool hasDiagnosticsAt(uint line, uint column) const;
virtual void addDiagnosticToolTipToLayout(uint line, uint column, QLayout *layout) const;
virtual void editorDocumentTimerRestarted();
signals:
// Signal interface to implement
void codeWarningsUpdated(unsigned revision,

View File

@@ -31,6 +31,7 @@ enum class PreferredTranslationUnit
{
RecentlyParsed,
PreviouslyParsed,
LastUninitialized,
};
} // namespace ClangBackEnd

View File

@@ -47,6 +47,9 @@ HEADERS += $$PWD/clangcodemodelserver.h \
$$PWD/clangdocumentprocessors.h \
$$PWD/clangtranslationunits.h \
$$PWD/clangclock.h \
$$PWD/clangsupportivetranslationunitinitializer.h \
$$PWD/clangparsesupportivetranslationunitjob.h \
$$PWD/clangreparsesupportivetranslationunitjob.h \
SOURCES += $$PWD/clangcodemodelserver.cpp \
$$PWD/codecompleter.cpp \
@@ -88,4 +91,7 @@ SOURCES += $$PWD/clangcodemodelserver.cpp \
$$PWD/clangexceptions.cpp \
$$PWD/clangdocumentprocessor.cpp \
$$PWD/clangdocumentprocessors.cpp \
$$PWD/clangtranslationunits.cpp
$$PWD/clangtranslationunits.cpp \
$$PWD/clangsupportivetranslationunitinitializer.cpp \
$$PWD/clangparsesupportivetranslationunitjob.cpp \
$$PWD/clangreparsesupportivetranslationunitjob.cpp \

View File

@@ -27,6 +27,7 @@
#include "clangdocuments.h"
#include "clangfilesystemwatcher.h"
#include "clangtranslationunits.h"
#include "codecompleter.h"
#include "diagnosticset.h"
#include "highlightingmarks.h"
@@ -126,10 +127,16 @@ void ClangCodeModelServer::updateTranslationUnitsForEditor(const UpdateTranslati
try {
const auto newerFileContainers = documents.newerFileContainers(message.fileContainers());
if (newerFileContainers.size() > 0) {
documents.update(newerFileContainers);
const std::vector<Document> updateDocuments = documents.update(newerFileContainers);
unsavedFiles.createOrUpdate(newerFileContainers);
updateDocumentAnnotationsTimer.start(updateDocumentAnnotationsTimeOutInMs);
// Start the jobs on the next event loop iteration since otherwise
// we might block the translation unit for a completion request
// that comes right after this message.
updateDocumentAnnotationsTimer.start(0);
QTimer::singleShot(0, [this, updateDocuments](){
startInitializingSupportiveTranslationUnits(updateDocuments);
});
}
} catch (const std::exception &exception) {
qWarning() << "Error in ClangCodeModelServer::updateTranslationUnitsForEditor:" << exception.what();
@@ -286,7 +293,9 @@ void ClangCodeModelServer::processJobsForDirtyAndVisibleDocuments()
for (const auto &document : documents.documents()) {
if (document.isNeedingReparse() && document.isVisibleInEditor()) {
DocumentProcessor processor = documentProcessors().processor(document);
processor.addJob(createJobRequest(document, JobRequest::Type::UpdateDocumentAnnotations));
processor.addJob(createJobRequest(document,
JobRequest::Type::UpdateDocumentAnnotations,
PreferredTranslationUnit::PreviouslyParsed));
}
}
@@ -297,14 +306,37 @@ void ClangCodeModelServer::processInitialJobsForDocuments(const std::vector<Docu
{
for (const auto &document : documents) {
DocumentProcessor processor = documentProcessors().create(document);
const auto jobRequestCreator = [this](const Document &document,
JobRequest::Type jobRequestType,
PreferredTranslationUnit preferredTranslationUnit) {
return createJobRequest(document, jobRequestType, preferredTranslationUnit);
};
processor.setJobRequestCreator(jobRequestCreator);
processor.addJob(createJobRequest(document, JobRequest::Type::UpdateDocumentAnnotations));
processor.addJob(createJobRequest(document, JobRequest::Type::CreateInitialDocumentPreamble));
processor.process();
}
}
JobRequest ClangCodeModelServer::createJobRequest(const Document &document,
JobRequest::Type type) const
void ClangCodeModelServer::startInitializingSupportiveTranslationUnits(
const std::vector<Document> &documents)
{
for (const Document &document : documents) {
try {
DocumentProcessor processor = documentProcessors().processor(document);
if (!processor.hasSupportiveTranslationUnit())
processor.startInitializingSupportiveTranslationUnit();
} catch (const DocumentProcessorDoesNotExist &) {
// OK, document was already closed.
}
}
}
JobRequest ClangCodeModelServer::createJobRequest(
const Document &document,
JobRequest::Type type,
PreferredTranslationUnit preferredTranslationUnit) const
{
JobRequest jobRequest;
jobRequest.type = type;
@@ -313,6 +345,7 @@ JobRequest ClangCodeModelServer::createJobRequest(const Document &document,
jobRequest.projectPartId = document.projectPartId();
jobRequest.unsavedFilesChangeTimePoint = unsavedFiles.lastChangeTimePoint();
jobRequest.documentRevision = document.documentRevision();
jobRequest.preferredTranslationUnit = preferredTranslationUnit;
const ProjectPart &projectPart = projects.project(document.projectPartId());
jobRequest.projectChangeTimePoint = projectPart.lastChangeTimePoint();

View File

@@ -65,16 +65,20 @@ public: // for tests
int queueSizeForTestsOnly();
bool isTimerRunningForTestOnly() const;
void setUpdateDocumentAnnotationsTimeOutInMsForTestsOnly(int value);
DocumentProcessors &documentProcessors();
private:
DocumentProcessors &documentProcessors();
void startDocumentAnnotationsTimerIfFileIsNotOpenAsDocument(const Utf8String &filePath);
void addJobRequestsForDirtyAndVisibleDocuments();
void processJobsForDirtyAndVisibleDocuments();
void processInitialJobsForDocuments(const std::vector<Document> &documents);
void startInitializingSupportiveTranslationUnits(const std::vector<Document> &documents);
JobRequest createJobRequest(const Document &document, JobRequest::Type type) const;
JobRequest createJobRequest(const Document &document,
JobRequest::Type type,
PreferredTranslationUnit preferredTranslationUnit
= PreferredTranslationUnit::RecentlyParsed) const;
private:
ProjectParts projects;

View File

@@ -58,7 +58,8 @@ IAsyncJob::AsyncPrepareResult CompleteCodeJob::prepareAsyncRun()
try {
m_pinnedDocument = context().documentForJobRequest();
const TranslationUnit translationUnit = m_pinnedDocument.translationUnit();
const TranslationUnit translationUnit
= m_pinnedDocument.translationUnit(jobRequest.preferredTranslationUnit);
const UnsavedFiles unsavedFiles = *context().unsavedFiles;
const quint32 line = jobRequest.line;
const quint32 column = jobRequest.column;

View File

@@ -48,7 +48,8 @@ IAsyncJob::AsyncPrepareResult CreateInitialDocumentPreambleJob::prepareAsyncRun(
m_pinnedDocument = context().documentForJobRequest();
m_pinnedFileContainer = m_pinnedDocument.fileContainer();
const TranslationUnit translationUnit = m_pinnedDocument.translationUnit();
const TranslationUnit translationUnit
= m_pinnedDocument.translationUnit(jobRequest.preferredTranslationUnit);
const TranslationUnitUpdateInput updateInput = m_pinnedDocument.createUpdateInput();
setRunner([translationUnit, updateInput]() {
return runAsyncHelper(translationUnit, updateInput);

View File

@@ -25,9 +25,14 @@
#include "clangdocumentprocessor.h"
#include "clangdocuments.h"
#include "clangjobs.h"
#include "clangsupportivetranslationunitinitializer.h"
#include "clangdocument.h"
#include "clangtranslationunits.h"
#include <utils/qtcassert.h>
namespace ClangBackEnd {
@@ -40,12 +45,24 @@ public:
ProjectParts &projects,
ClangCodeModelClientInterface &client)
: document(document)
, documents(documents)
, jobs(documents, unsavedFiles, projects, client)
{}
, supportiveTranslationUnitInitializer(document, jobs)
{
const auto isDocumentClosedChecker = [this](const Utf8String &filePath,
const Utf8String &projectPartId) {
return !this->documents.hasDocument(filePath, projectPartId);
};
supportiveTranslationUnitInitializer.setIsDocumentClosedChecker(isDocumentClosedChecker);
}
public:
Document document;
Documents &documents;
Jobs jobs;
SupportiveTranslationUnitInitializer supportiveTranslationUnitInitializer;
JobRequestCreator jobRequestCreator;
};
DocumentProcessor::DocumentProcessor(const Document &document,
@@ -61,6 +78,11 @@ DocumentProcessor::DocumentProcessor(const Document &document,
{
}
void DocumentProcessor::setJobRequestCreator(const JobRequestCreator &creator)
{
d->supportiveTranslationUnitInitializer.setJobRequestCreator(creator);
}
void DocumentProcessor::addJob(const JobRequest &jobRequest)
{
d->jobs.add(jobRequest);
@@ -76,6 +98,23 @@ Document DocumentProcessor::document() const
return d->document;
}
bool DocumentProcessor::hasSupportiveTranslationUnit() const
{
return d->supportiveTranslationUnitInitializer.state()
!= SupportiveTranslationUnitInitializer::State::NotInitialized;
}
void DocumentProcessor::startInitializingSupportiveTranslationUnit()
{
d->supportiveTranslationUnitInitializer.startInitializing();
}
bool DocumentProcessor::isSupportiveTranslationUnitInitialized() const
{
return d->supportiveTranslationUnitInitializer.state()
== SupportiveTranslationUnitInitializer::State::Initialized;
}
QList<Jobs::RunningJob> DocumentProcessor::runningJobs() const
{
return d->jobs.runningJobs();

View File

@@ -49,12 +49,18 @@ public:
ProjectParts &projects,
ClangCodeModelClientInterface &client);
void setJobRequestCreator(const JobRequestCreator &creator);
void addJob(const JobRequest &jobRequest);
JobRequests process();
Document document() const;
bool hasSupportiveTranslationUnit() const;
void startInitializingSupportiveTranslationUnit();
public: // for tests
bool isSupportiveTranslationUnitInitialized() const;
QList<Jobs::RunningJob> runningJobs() const;
int queueSize() const;

View File

@@ -27,6 +27,8 @@
#include "clangcompletecodejob.h"
#include "clangcreateinitialdocumentpreamblejob.h"
#include "clangparsesupportivetranslationunitjob.h"
#include "clangreparsesupportivetranslationunitjob.h"
#include "clangrequestdocumentannotationsjob.h"
#include "clangupdatedocumentannotationsjob.h"
@@ -39,6 +41,10 @@ IAsyncJob *IAsyncJob::create(JobRequest::Type type)
switch (type) {
case JobRequest::Type::UpdateDocumentAnnotations:
return new UpdateDocumentAnnotationsJob();
case JobRequest::Type::ParseSupportiveTranslationUnit:
return new ParseSupportiveTranslationUnitJob();
case JobRequest::Type::ReparseSupportiveTranslationUnit:
return new ReparseSupportiveTranslationUnitJob();
case JobRequest::Type::CreateInitialDocumentPreamble:
return new CreateInitialDocumentPreambleJob();
case JobRequest::Type::CompleteCode:

View File

@@ -34,6 +34,8 @@ static const char *JobRequestTypeToText(JobRequest::Type type)
{
switch (type) {
RETURN_TEXT_FOR_CASE(UpdateDocumentAnnotations);
RETURN_TEXT_FOR_CASE(ParseSupportiveTranslationUnit);
RETURN_TEXT_FOR_CASE(ReparseSupportiveTranslationUnit);
RETURN_TEXT_FOR_CASE(CreateInitialDocumentPreamble);
RETURN_TEXT_FOR_CASE(CompleteCode);
RETURN_TEXT_FOR_CASE(RequestDocumentAnnotations);
@@ -49,6 +51,7 @@ const char *preferredTranslationUnitToText(PreferredTranslationUnit type)
switch (type) {
RETURN_TEXT_FOR_CASE(RecentlyParsed);
RETURN_TEXT_FOR_CASE(PreviouslyParsed);
RETURN_TEXT_FOR_CASE(LastUninitialized);
}
return "UnhandledPreferredTranslationUnitType";
@@ -93,6 +96,8 @@ JobRequest::Requirements JobRequest::requirementsForType(Type type)
|JobRequest::CurrentDocumentRevision);
case JobRequest::Type::CompleteCode:
case JobRequest::Type::CreateInitialDocumentPreamble:
case JobRequest::Type::ParseSupportiveTranslationUnit:
case JobRequest::Type::ReparseSupportiveTranslationUnit:
return JobRequest::Requirements(JobRequest::DocumentValid);
}

View File

@@ -34,14 +34,22 @@
#include <QDebug>
#include <QVector>
#include <functional>
namespace ClangBackEnd {
class Document;
class JobRequest
{
public:
enum class Type {
UpdateDocumentAnnotations,
CreateInitialDocumentPreamble,
ParseSupportiveTranslationUnit,
ReparseSupportiveTranslationUnit,
CompleteCode,
RequestDocumentAnnotations,
};
@@ -83,6 +91,9 @@ public:
};
using JobRequests = QVector<JobRequest>;
using JobRequestCreator = std::function<JobRequest(const Document &,
JobRequest::Type ,
PreferredTranslationUnit)>;
QDebug operator<<(QDebug debug, const JobRequest &jobRequest);

View File

@@ -120,12 +120,22 @@ void Jobs::onJobFinished(IAsyncJob *asyncJob)
{
qCDebug(jobsLog) << "Finishing" << asyncJob->context().jobRequest;
if (m_jobFinishedCallback) {
const RunningJob runningJob = m_running.value(asyncJob);
m_jobFinishedCallback(runningJob);
}
m_running.remove(asyncJob);
delete asyncJob;
process();
}
void Jobs::setJobFinishedCallback(const JobFinishedCallback &jobFinishedCallback)
{
m_jobFinishedCallback = jobFinishedCallback;
}
QList<Jobs::RunningJob> Jobs::runningJobs() const
{
return m_running.values();

View File

@@ -31,6 +31,8 @@
#include <QFuture>
#include <functional>
namespace ClangBackEnd {
class ClangCodeModelClientInterface;
@@ -46,7 +48,9 @@ public:
Utf8String translationUnitId;
QFuture<void> future;
};
using RunningJobs = QHash<IAsyncJob *, RunningJob>;
using JobFinishedCallback = std::function<void(RunningJob)>;
public:
Jobs(Documents &documents,
@@ -59,6 +63,8 @@ public:
JobRequests process();
void setJobFinishedCallback(const JobFinishedCallback &jobFinishedCallback);
public /*for tests*/:
QList<RunningJob> runningJobs() const;
JobRequests queue() const;
@@ -76,6 +82,7 @@ private:
JobQueue m_queue;
RunningJobs m_running;
JobFinishedCallback m_jobFinishedCallback;
};
} // namespace ClangBackEnd

View File

@@ -0,0 +1,78 @@
/****************************************************************************
**
** 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 "clangparsesupportivetranslationunitjob.h"
#include <clangbackendipc/clangbackendipcdebugutils.h>
#include <utils/qtcassert.h>
namespace ClangBackEnd {
static ParseSupportiveTranslationUnitJob::AsyncResult runAsyncHelper(
const TranslationUnit &translationUnit,
const TranslationUnitUpdateInput &translationUnitUpdateInput)
{
TIME_SCOPE_DURATION("ParseSupportiveTranslationUnitJob");
TranslationUnitUpdateInput updateInput = translationUnitUpdateInput;
updateInput.parseNeeded = true;
ParseSupportiveTranslationUnitJob::AsyncResult asyncResult;
asyncResult.updateResult = translationUnit.update(updateInput);
return asyncResult;
}
IAsyncJob::AsyncPrepareResult ParseSupportiveTranslationUnitJob::prepareAsyncRun()
{
const JobRequest jobRequest = context().jobRequest;
QTC_ASSERT(jobRequest.type == JobRequest::Type::ParseSupportiveTranslationUnit, return AsyncPrepareResult());
try {
m_pinnedDocument = context().documentForJobRequest();
m_pinnedFileContainer = m_pinnedDocument.fileContainer();
const TranslationUnit translationUnit
= m_pinnedDocument.translationUnit(jobRequest.preferredTranslationUnit);
const TranslationUnitUpdateInput updateInput = m_pinnedDocument.createUpdateInput();
setRunner([translationUnit, updateInput]() {
return runAsyncHelper(translationUnit, updateInput);
});
return AsyncPrepareResult{translationUnit.id()};
} catch (const std::exception &exception) {
qWarning() << "Error in ParseForSupportiveTranslationUnitJob::prepareAsyncRun:"
<< exception.what();
return AsyncPrepareResult();
}
}
void ParseSupportiveTranslationUnitJob::finalizeAsyncRun()
{
}
} // namespace ClangBackEnd

View File

@@ -0,0 +1,51 @@
/****************************************************************************
**
** 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.
**
****************************************************************************/
#pragma once
#include "clangasyncjob.h"
#include "clangdocument.h"
namespace ClangBackEnd {
struct ParseSupportiveTranslationUnitJobResult
{
TranslationUnitUpdateResult updateResult;
};
class ParseSupportiveTranslationUnitJob : public AsyncJob<ParseSupportiveTranslationUnitJobResult>
{
public:
using AsyncResult = ParseSupportiveTranslationUnitJobResult;
AsyncPrepareResult prepareAsyncRun() override;
void finalizeAsyncRun() override;
private:
Document m_pinnedDocument;
FileContainer m_pinnedFileContainer;
};
} // namespace ClangBackEnd

View File

@@ -0,0 +1,82 @@
/****************************************************************************
**
** 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 "clangreparsesupportivetranslationunitjob.h"
#include <clangbackendipc/clangbackendipcdebugutils.h>
#include <utils/qtcassert.h>
namespace ClangBackEnd {
static ReparseSupportiveTranslationUnitJob::AsyncResult runAsyncHelper(
const TranslationUnit &translationUnit,
const TranslationUnitUpdateInput &translationUnitUpdateInput)
{
TIME_SCOPE_DURATION("ReparseSupportiveTranslationUnitJob");
TranslationUnitUpdateInput updateInput = translationUnitUpdateInput;
updateInput.reparseNeeded = true;
ReparseSupportiveTranslationUnitJob::AsyncResult asyncResult;
asyncResult.updateResult = translationUnit.reparse(updateInput);
return asyncResult;
}
IAsyncJob::AsyncPrepareResult ReparseSupportiveTranslationUnitJob::prepareAsyncRun()
{
const JobRequest jobRequest = context().jobRequest;
QTC_ASSERT(jobRequest.type == JobRequest::Type::ReparseSupportiveTranslationUnit, return AsyncPrepareResult());
try {
m_pinnedDocument = context().documentForJobRequest();
m_pinnedFileContainer = m_pinnedDocument.fileContainer();
const TranslationUnit translationUnit
= m_pinnedDocument.translationUnit(jobRequest.preferredTranslationUnit);
const TranslationUnitUpdateInput updateInput = m_pinnedDocument.createUpdateInput();
setRunner([translationUnit, updateInput]() {
return runAsyncHelper(translationUnit, updateInput);
});
return AsyncPrepareResult{translationUnit.id()};
} catch (const std::exception &exception) {
qWarning() << "Error in ReparseSupportiveTranslationUnitJob::prepareAsyncRun:"
<< exception.what();
return AsyncPrepareResult();
}
}
void ReparseSupportiveTranslationUnitJob::finalizeAsyncRun()
{
if (!context().isOutdated()) {
const AsyncResult result = asyncResult();
m_pinnedDocument.incorporateUpdaterResult(result.updateResult);
}
}
} // namespace ClangBackEnd

View File

@@ -0,0 +1,51 @@
/****************************************************************************
**
** 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.
**
****************************************************************************/
#pragma once
#include "clangasyncjob.h"
#include "clangdocument.h"
namespace ClangBackEnd {
struct ReparseSupportiveTranslationUnitJobResult
{
TranslationUnitUpdateResult updateResult;
};
class ReparseSupportiveTranslationUnitJob : public AsyncJob<ReparseSupportiveTranslationUnitJobResult>
{
public:
using AsyncResult = ReparseSupportiveTranslationUnitJobResult;
AsyncPrepareResult prepareAsyncRun() override;
void finalizeAsyncRun() override;
private:
Document m_pinnedDocument;
FileContainer m_pinnedFileContainer;
};
} // namespace ClangBackEnd

View File

@@ -57,7 +57,8 @@ IAsyncJob::AsyncPrepareResult RequestDocumentAnnotationsJob::prepareAsyncRun()
m_pinnedDocument = context().documentForJobRequest();
m_pinnedFileContainer = m_pinnedDocument.fileContainer();
const TranslationUnit translationUnit = m_pinnedDocument.translationUnit();
const TranslationUnit translationUnit
= m_pinnedDocument.translationUnit(jobRequest.preferredTranslationUnit);
setRunner([translationUnit]() {
return runAsyncHelper(translationUnit);
});

View File

@@ -0,0 +1,141 @@
/****************************************************************************
**
** 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 "clangsupportivetranslationunitinitializer.h"
#include "clangjobs.h"
#include "clangtranslationunits.h"
#include <utils/qtcassert.h>
namespace ClangBackEnd {
// TODO: Check translation unit id?
SupportiveTranslationUnitInitializer::SupportiveTranslationUnitInitializer(
const Document &document,
Jobs &jobs)
: m_document(document)
, m_jobs(jobs)
{
}
void SupportiveTranslationUnitInitializer::setJobRequestCreator(const JobRequestCreator &creator)
{
m_jobRequestCreator = creator;
}
void SupportiveTranslationUnitInitializer::setIsDocumentClosedChecker(
const IsDocumentClosedChecker &isDocumentClosedChecker)
{
m_isDocumentClosedChecker = isDocumentClosedChecker;
}
SupportiveTranslationUnitInitializer::State SupportiveTranslationUnitInitializer::state() const
{
return m_state;
}
void SupportiveTranslationUnitInitializer::startInitializing()
{
QTC_CHECK(m_state == State::NotInitialized);
if (abortIfDocumentIsClosed())
return;
m_document.translationUnits().createAndAppend();
m_jobs.setJobFinishedCallback([this](const Jobs::RunningJob &runningJob) {
checkIfParseJobFinished(runningJob);
});
addJob(JobRequest::Type::ParseSupportiveTranslationUnit);
m_jobs.process();
m_state = State::WaitingForParseJob;
}
void SupportiveTranslationUnitInitializer::checkIfParseJobFinished(const Jobs::RunningJob &job)
{
QTC_CHECK(m_state == State::WaitingForParseJob);
if (abortIfDocumentIsClosed())
return;
if (job.jobRequest.type == JobRequest::Type::ParseSupportiveTranslationUnit) {
m_jobs.setJobFinishedCallback([this](const Jobs::RunningJob &runningJob) {
checkIfReparseJobFinished(runningJob);
});
addJob(JobRequest::Type::ReparseSupportiveTranslationUnit);
m_state = State::WaitingForReparseJob;
}
}
void SupportiveTranslationUnitInitializer::checkIfReparseJobFinished(const Jobs::RunningJob &job)
{
QTC_CHECK(m_state == State::WaitingForReparseJob);
if (abortIfDocumentIsClosed())
return;
if (job.jobRequest.type == JobRequest::Type::ReparseSupportiveTranslationUnit) {
if (m_document.translationUnits().areAllTranslationUnitsParsed()) {
m_jobs.setJobFinishedCallback(nullptr);
m_state = State::Initialized;
} else {
// The supportive translation unit was reparsed, but the document
// revision changed in the meanwhile, so try again.
addJob(JobRequest::Type::ReparseSupportiveTranslationUnit);
}
}
}
bool SupportiveTranslationUnitInitializer::abortIfDocumentIsClosed()
{
QTC_CHECK(m_isDocumentClosedChecker);
if (m_isDocumentClosedChecker(m_document.filePath(), m_document.projectPartId())) {
m_state = State::Aborted;
return true;
}
return false;
}
void SupportiveTranslationUnitInitializer::addJob(JobRequest::Type jobRequestType)
{
QTC_CHECK(m_jobRequestCreator);
const JobRequest jobRequest = m_jobRequestCreator(m_document,
jobRequestType,
PreferredTranslationUnit::LastUninitialized);
m_jobs.add(jobRequest);
}
void SupportiveTranslationUnitInitializer::setState(const State &state)
{
m_state = state;
}
} // namespace ClangBackEnd

View File

@@ -0,0 +1,77 @@
/****************************************************************************
**
** 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 "clangdocument.h"
#include "clangjobrequest.h"
#include "clangjobs.h"
#include <functional>
#pragma once
namespace ClangBackEnd {
class SupportiveTranslationUnitInitializer
{
public:
using IsDocumentClosedChecker = std::function<bool(const Utf8String &, const Utf8String &)>;
enum class State {
NotInitialized,
WaitingForParseJob,
WaitingForReparseJob,
Initialized,
Aborted
};
public:
SupportiveTranslationUnitInitializer(const Document &document, Jobs &jobs);
void setJobRequestCreator(const JobRequestCreator &creator);
void setIsDocumentClosedChecker(const IsDocumentClosedChecker &isDocumentClosedChecker);
State state() const;
void startInitializing();
public: // for tests
void setState(const State &state);
void checkIfParseJobFinished(const Jobs::RunningJob &job);
void checkIfReparseJobFinished(const Jobs::RunningJob &job);
private:
bool abortIfDocumentIsClosed();
void addJob(JobRequest::Type jobRequestType);
private:
Document m_document;
Jobs &m_jobs;
State m_state = State::NotInitialized;
JobRequestCreator m_jobRequestCreator;
IsDocumentClosedChecker m_isDocumentClosedChecker;
};
} // namespace ClangBackEnd

View File

@@ -70,10 +70,15 @@ TranslationUnit TranslationUnits::get(PreferredTranslationUnit type)
if (m_tuDatas.isEmpty())
throw TranslationUnitDoesNotExist(m_filePath);
if (m_tuDatas.size() == 1 || !areAllTranslationUnitsParsed())
if (m_tuDatas.size() == 1)
return toTranslationUnit(m_tuDatas.first());
if (areAllTranslationUnitsParsed())
return getPreferredTranslationUnit(type);
else if (type == PreferredTranslationUnit::LastUninitialized)
return toTranslationUnit(m_tuDatas.last());
return toTranslationUnit(m_tuDatas.first());
}
void TranslationUnits::updateParseTimePoint(const Utf8String &translationUnitId,
@@ -89,6 +94,11 @@ void TranslationUnits::updateParseTimePoint(const Utf8String &translationUnitId,
<< "PreviouslyParsed:" << get(PreferredTranslationUnit::PreviouslyParsed).id();
}
TimePoint TranslationUnits::parseTimePoint(const Utf8String &translationUnitId)
{
return findUnit(translationUnitId).parseTimePoint;
}
bool TranslationUnits::areAllTranslationUnitsParsed() const
{
return Utils::allOf(m_tuDatas, [](const TranslationUnitData &unit) {
@@ -96,6 +106,11 @@ bool TranslationUnits::areAllTranslationUnitsParsed() const
});
}
int TranslationUnits::size() const
{
return m_tuDatas.size();
}
TranslationUnit TranslationUnits::getPreferredTranslationUnit(PreferredTranslationUnit type)
{
using TuData = TranslationUnitData;

View File

@@ -63,8 +63,13 @@ public:
TranslationUnit get(PreferredTranslationUnit type = PreferredTranslationUnit::RecentlyParsed);
void updateParseTimePoint(const Utf8String &translationUnitId, TimePoint timePoint);
private:
bool areAllTranslationUnitsParsed() const;
public: // for tests
int size() const;
TimePoint parseTimePoint(const Utf8String &translationUnitId);
private:
TranslationUnit getPreferredTranslationUnit(PreferredTranslationUnit type);
TranslationUnitData &findUnit(const Utf8String &translationUnitId);
TranslationUnit toTranslationUnit(TranslationUnitData &unit);

View File

@@ -62,7 +62,8 @@ IAsyncJob::AsyncPrepareResult UpdateDocumentAnnotationsJob::prepareAsyncRun()
m_pinnedDocument = context().documentForJobRequest();
m_pinnedFileContainer = m_pinnedDocument.fileContainer();
const TranslationUnit translationUnit = m_pinnedDocument.translationUnit();
const TranslationUnit translationUnit
= m_pinnedDocument.translationUnit(jobRequest.preferredTranslationUnit);
const TranslationUnitUpdateInput updateInput = m_pinnedDocument.createUpdateInput();
setRunner([translationUnit, updateInput]() {
return runAsyncHelper(translationUnit, updateInput);

View File

@@ -32,6 +32,7 @@
#include <highlightingmarkcontainer.h>
#include <clangcodemodelclientproxy.h>
#include <clangcodemodelserverproxy.h>
#include <clangtranslationunits.h>
#include <requestdocumentannotations.h>
#include <clangexceptions.h>
@@ -135,6 +136,8 @@ protected:
void completeCodeInFileA();
void completeCodeInFileB();
bool isSupportiveTranslationUnitInitialized(const Utf8String &filePath);
void expectDocumentAnnotationsChanged(int count);
void expectCompletion(const CodeCompletion &completion);
void expectCompletionFromFileA();
@@ -286,6 +289,50 @@ TEST_F(ClangClangCodeModelServer, SetCurrentAndVisibleEditor)
ASSERT_TRUE(functionDocument.isVisibleInEditor());
}
TEST_F(ClangClangCodeModelServer, StartCompletionJobFirstOnEditThatTriggersCompletion)
{
registerProjectAndFile(filePathA, 2);
ASSERT_TRUE(waitUntilAllJobsFinished());
expectCompletionFromFileA();
updateUnsavedContent(filePathA, unsavedContent(filePathAUnsavedVersion2), 1);
completeCodeInFileA();
const QList<Jobs::RunningJob> jobs = clangServer.runningJobsForTestsOnly();
ASSERT_THAT(jobs.size(), Eq(1));
ASSERT_THAT(jobs.first().jobRequest.type, Eq(JobRequest::Type::CompleteCode));
}
TEST_F(ClangClangCodeModelServer, SupportiveTranslationUnitNotInitializedAfterRegister)
{
registerProjectAndFile(filePathA, 1);
ASSERT_TRUE(waitUntilAllJobsFinished());
ASSERT_FALSE(isSupportiveTranslationUnitInitialized(filePathA));
}
TEST_F(ClangClangCodeModelServer, SupportiveTranslationUnitIsSetupAfterFirstEdit)
{
registerProjectAndFile(filePathA, 2);
ASSERT_TRUE(waitUntilAllJobsFinished());
updateUnsavedContent(filePathA, unsavedContent(filePathAUnsavedVersion2), 1);
ASSERT_TRUE(waitUntilAllJobsFinished());
ASSERT_TRUE(isSupportiveTranslationUnitInitialized(filePathA));
}
TEST_F(ClangClangCodeModelServer, OpenDocumentAndEdit)
{
registerProjectAndFile(filePathA, 4);
ASSERT_TRUE(waitUntilAllJobsFinished());
for (unsigned revision = 1; revision <= 3; ++revision) {
updateUnsavedContent(filePathA, unsavedContent(filePathAUnsavedVersion2), revision);
ASSERT_TRUE(waitUntilAllJobsFinished());
}
}
TEST_F(ClangClangCodeModelServer, IsNotCurrentCurrentAndVisibleEditorAnymore)
{
registerProjectAndFilesAndWaitForFinished();
@@ -411,6 +458,16 @@ void ClangClangCodeModelServer::completeCodeInFileB()
completeCode(filePathB, 35, 1);
}
bool ClangClangCodeModelServer::isSupportiveTranslationUnitInitialized(const Utf8String &filePath)
{
Document document = clangServer.documentsForTestOnly().document(filePath, projectPartId);
DocumentProcessor documentProcessor = clangServer.documentProcessors().processor(document);
return document.translationUnits().size() == 2
&& documentProcessor.hasSupportiveTranslationUnit()
&& documentProcessor.isSupportiveTranslationUnitInitialized();
}
void ClangClangCodeModelServer::expectCompletion(const CodeCompletion &completion)
{
EXPECT_CALL(mockClangCodeModelClient,

View File

@@ -0,0 +1,86 @@
/****************************************************************************
**
** 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 "clangasyncjob-base.h"
#include <clangparsesupportivetranslationunitjob.h>
#include <clangtranslationunits.h>
using namespace ClangBackEnd;
using testing::Eq;
using testing::Not;
using testing::_;
namespace {
class ParseSupportiveTranslationUnitJob : public ClangAsyncJobTest
{
protected:
void SetUp() override { BaseSetUp(JobRequest::Type::ParseSupportiveTranslationUnit, job); }
TimePoint parseTimePointOfDocument();
protected:
ClangBackEnd::ParseSupportiveTranslationUnitJob job;
};
TEST_F(ParseSupportiveTranslationUnitJob, PrepareAsyncRun)
{
job.setContext(jobContext);
ASSERT_TRUE(job.prepareAsyncRun());
}
TEST_F(ParseSupportiveTranslationUnitJob, RunAsync)
{
job.setContext(jobContext);
job.prepareAsyncRun();
job.runAsync();
ASSERT_TRUE(waitUntilJobFinished(job));
}
TEST_F(ParseSupportiveTranslationUnitJob, DoNotIncorporateUpdaterResult)
{
const TimePoint parseTimePointBefore = parseTimePointOfDocument();
job.setContext(jobContext);
job.prepareAsyncRun();
job.runAsync();
ASSERT_TRUE(waitUntilJobFinished(job));
ASSERT_THAT(parseTimePointOfDocument(), Eq(parseTimePointBefore));
}
TimePoint ParseSupportiveTranslationUnitJob::parseTimePointOfDocument()
{
const Utf8String translationUnitId = document.translationUnit().id();
return document.translationUnits().parseTimePoint(translationUnitId);
}
} // anonymous

View File

@@ -0,0 +1,109 @@
/****************************************************************************
**
** 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 "clangasyncjob-base.h"
#include <clangreparsesupportivetranslationunitjob.h>
#include <clangtranslationunits.h>
using namespace ClangBackEnd;
using testing::Eq;
using testing::Not;
using testing::_;
namespace {
class ReparseSupportiveTranslationUnitJob : public ClangAsyncJobTest
{
protected:
void SetUp() override { BaseSetUp(JobRequest::Type::ReparseSupportiveTranslationUnit, job); }
TimePoint parseTimePointOfDocument();
void parse();
protected:
ClangBackEnd::ReparseSupportiveTranslationUnitJob job;
};
TEST_F(ReparseSupportiveTranslationUnitJob, PrepareAsyncRun)
{
job.setContext(jobContext);
ASSERT_TRUE(job.prepareAsyncRun());
}
TEST_F(ReparseSupportiveTranslationUnitJob, RunAsync)
{
parse();
job.setContext(jobContext);
job.prepareAsyncRun();
job.runAsync();
ASSERT_TRUE(waitUntilJobFinished(job));
}
TEST_F(ReparseSupportiveTranslationUnitJob, IncorporateUpdaterResult)
{
parse();
const TimePoint parseTimePointBefore = parseTimePointOfDocument();
job.setContext(jobContext);
job.prepareAsyncRun();
job.runAsync();
ASSERT_TRUE(waitUntilJobFinished(job));
ASSERT_THAT(parseTimePointOfDocument(), Not(Eq(parseTimePointBefore)));
}
TEST_F(ReparseSupportiveTranslationUnitJob, DoNotIncorporateUpdaterResultIfDocumentWasClosed)
{
parse();
const TimePoint parseTimePointBefore = parseTimePointOfDocument();
job.setContext(jobContext);
job.prepareAsyncRun();
job.runAsync();
documents.remove({FileContainer{filePath, projectPartId}});
ASSERT_TRUE(waitUntilJobFinished(job));
ASSERT_THAT(parseTimePointOfDocument(), Eq(parseTimePointBefore));
}
TimePoint ReparseSupportiveTranslationUnitJob::parseTimePointOfDocument()
{
const Utf8String translationUnitId = document.translationUnit().id();
return document.translationUnits().parseTimePoint(translationUnitId);
}
void ReparseSupportiveTranslationUnitJob::parse()
{
projects.createOrUpdate({ProjectPartContainer{projectPartId, Utf8StringVector()}});
document.parse();
}
} // anonymous

View File

@@ -0,0 +1,262 @@
/****************************************************************************
**
** 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 "dummyclangipcclient.h"
#include "processevents-utilities.h"
#include <clangbackend_global.h>
#include <clangdocuments.h>
#include <clangexceptions.h>
#include <clangsupportivetranslationunitinitializer.cpp>
#include <clangtranslationunit.h>
#include <clangtranslationunits.h>
#include <projects.h>
#include <utf8string.h>
#include <clang-c/Index.h>
#include <memory>
#include <gmock/gmock.h>
#include <gmock/gmock-matchers.h>
#include <gtest/gtest.h>
#include "gtest-qt-printing.h"
using namespace ClangBackEnd;
using testing::Eq;
namespace {
class Data {
public:
Data()
{
projects.createOrUpdate({ProjectPartContainer(projectPartId)});
const QVector<FileContainer> fileContainer{FileContainer(filePath, projectPartId)};
document = documents.create(fileContainer).front();
documents.setVisibleInEditors({filePath});
documents.setUsedByCurrentEditor(filePath);
const auto isDocumentClosed = [this](const Utf8String &filePath,
const Utf8String &projectPartId) {
return !documents.hasDocument(filePath, projectPartId);
};
const auto jobRequestCreator = [this](const Document &document,
JobRequest::Type type,
PreferredTranslationUnit preferredTranslationUnit) {
return createJobRequest(document, type, preferredTranslationUnit);
};
initializer.reset(new ClangBackEnd::SupportiveTranslationUnitInitializer{document, jobs});
initializer->setIsDocumentClosedChecker(isDocumentClosed);
initializer->setJobRequestCreator(jobRequestCreator);
}
JobRequest createJobRequest(const Document &document,
JobRequest::Type type,
PreferredTranslationUnit preferredTranslationUnit) const
{
JobRequest jobRequest;
jobRequest.type = type;
jobRequest.requirements = JobRequest::requirementsForType(type);
jobRequest.filePath = document.filePath();
jobRequest.projectPartId = document.projectPartId();
jobRequest.unsavedFilesChangeTimePoint = unsavedFiles.lastChangeTimePoint();
jobRequest.documentRevision = document.documentRevision();
jobRequest.preferredTranslationUnit = preferredTranslationUnit;
const ProjectPart &projectPart = projects.project(document.projectPartId());
jobRequest.projectChangeTimePoint = projectPart.lastChangeTimePoint();
return jobRequest;
}
public:
Utf8String filePath{Utf8StringLiteral(TESTDATA_DIR"/translationunits.cpp")};
Utf8String projectPartId{Utf8StringLiteral("/path/to/projectfile")};
ProjectParts projects;
UnsavedFiles unsavedFiles;
Documents documents{projects, unsavedFiles};
Document document;
DummyIpcClient dummyClientInterface;
Jobs jobs{documents, unsavedFiles, projects, dummyClientInterface};
std::unique_ptr<ClangBackEnd::SupportiveTranslationUnitInitializer> initializer;
};
class SupportiveTranslationUnitInitializer : public ::testing::Test
{
protected:
void parse();
Jobs::RunningJob createRunningJob(JobRequest::Type type) const;
void assertNoJobIsRunningAndEmptyQueue();
void assertSingleJobRunningAndEmptyQueue();
bool waitUntilJobChainFinished(int timeOutInMs = 10000) const;
protected:
Data d;
Utf8String &filePath{d.filePath};
Utf8String &projectPartId{d.projectPartId};
ProjectParts projects{d.projects};
Document &document{d.document};
Documents &documents{d.documents};
Jobs &jobs{d.jobs};
ClangBackEnd::SupportiveTranslationUnitInitializer &initializer{*d.initializer};
};
TEST_F(SupportiveTranslationUnitInitializer, HasInitiallyNotInitializedState)
{
ASSERT_THAT(initializer.state(), Eq(ClangBackEnd::SupportiveTranslationUnitInitializer::State::NotInitialized));
}
TEST_F(SupportiveTranslationUnitInitializer, StartInitializingAbortsIfDocumentIsClosed)
{
documents.remove({FileContainer(filePath, projectPartId)});
initializer.startInitializing();
assertNoJobIsRunningAndEmptyQueue();
ASSERT_THAT(initializer.state(), Eq(ClangBackEnd::SupportiveTranslationUnitInitializer::State::Aborted));
}
TEST_F(SupportiveTranslationUnitInitializer, StartInitializingAddsTranslationUnit)
{
initializer.startInitializing();
ASSERT_THAT(document.translationUnits().size(), Eq(2));
ASSERT_FALSE(document.translationUnits().areAllTranslationUnitsParsed());
}
TEST_F(SupportiveTranslationUnitInitializer, StartInitializingStartsJob)
{
initializer.startInitializing();
assertSingleJobRunningAndEmptyQueue();
const Jobs::RunningJob runningJob = jobs.runningJobs().first();
ASSERT_THAT(runningJob.jobRequest.type, JobRequest::Type::ParseSupportiveTranslationUnit);
}
TEST_F(SupportiveTranslationUnitInitializer, CheckIfParseJobFinishedAbortsIfDocumentIsClosed)
{
documents.remove({FileContainer(filePath, projectPartId)});
initializer.setState(ClangBackEnd::SupportiveTranslationUnitInitializer::State::WaitingForParseJob);
const Jobs::RunningJob runningJob = createRunningJob(JobRequest::Type::ParseSupportiveTranslationUnit);
initializer.checkIfParseJobFinished(runningJob);
assertNoJobIsRunningAndEmptyQueue();
ASSERT_THAT(initializer.state(), Eq(ClangBackEnd::SupportiveTranslationUnitInitializer::State::Aborted));
}
TEST_F(SupportiveTranslationUnitInitializer, CheckIfParseJobFinishedStartsJob)
{
parse();
initializer.setState(ClangBackEnd::SupportiveTranslationUnitInitializer::State::WaitingForParseJob);
Jobs::RunningJob runningJob = createRunningJob(JobRequest::Type::ParseSupportiveTranslationUnit);
initializer.checkIfParseJobFinished(runningJob);
jobs.process();
assertSingleJobRunningAndEmptyQueue();
runningJob = jobs.runningJobs().first();
ASSERT_THAT(runningJob.jobRequest.type, JobRequest::Type::ReparseSupportiveTranslationUnit);
}
TEST_F(SupportiveTranslationUnitInitializer, CheckIfReparseJobFinishedAbortsIfDocumentIsClosed)
{
documents.remove({FileContainer(filePath, projectPartId)});
initializer.setState(ClangBackEnd::SupportiveTranslationUnitInitializer::State::WaitingForReparseJob);
const Jobs::RunningJob runningJob = createRunningJob(JobRequest::Type::ReparseSupportiveTranslationUnit);
initializer.checkIfReparseJobFinished(runningJob);
assertNoJobIsRunningAndEmptyQueue();
ASSERT_THAT(initializer.state(), Eq(ClangBackEnd::SupportiveTranslationUnitInitializer::State::Aborted));
}
TEST_F(SupportiveTranslationUnitInitializer, CheckIfReparseJobFinishedStartsJob)
{
parse();
initializer.setState(ClangBackEnd::SupportiveTranslationUnitInitializer::State::WaitingForReparseJob);
Jobs::RunningJob runningJob = createRunningJob(JobRequest::Type::ReparseSupportiveTranslationUnit);
initializer.checkIfReparseJobFinished(runningJob);
jobs.process();
assertNoJobIsRunningAndEmptyQueue();
ASSERT_THAT(initializer.state(), Eq(ClangBackEnd::SupportiveTranslationUnitInitializer::State::Initialized));
}
TEST_F(SupportiveTranslationUnitInitializer, FullRun)
{
parse();
initializer.startInitializing();
waitUntilJobChainFinished();
ASSERT_THAT(initializer.state(), Eq(ClangBackEnd::SupportiveTranslationUnitInitializer::State::Initialized));
}
void SupportiveTranslationUnitInitializer::parse()
{
projects.createOrUpdate({ProjectPartContainer{projectPartId, Utf8StringVector()}});
document.parse();
}
Jobs::RunningJob SupportiveTranslationUnitInitializer::createRunningJob(JobRequest::Type type) const
{
const JobRequest jobRequest = d.createJobRequest(document,
type,
PreferredTranslationUnit::LastUninitialized);
return Jobs::RunningJob{jobRequest, Utf8String(), QFuture<void>()};
}
void SupportiveTranslationUnitInitializer::assertNoJobIsRunningAndEmptyQueue()
{
ASSERT_TRUE(jobs.runningJobs().isEmpty());
ASSERT_TRUE(jobs.queue().isEmpty());
}
void SupportiveTranslationUnitInitializer::assertSingleJobRunningAndEmptyQueue()
{
ASSERT_THAT(jobs.runningJobs().size(), Eq(1));
ASSERT_TRUE(jobs.queue().isEmpty());
}
bool SupportiveTranslationUnitInitializer::waitUntilJobChainFinished(int timeOutInMs) const
{
const auto noJobsRunningAnymore = [this]() {
return jobs.runningJobs().isEmpty() && jobs.queue().isEmpty();
};
return ProcessEventUtilities::processEventsUntilTrue(noJobsRunningAnymore, timeOutInMs);
}
} // anonymous namespace

View File

@@ -106,6 +106,17 @@ TEST_F(TranslationUnits, GetFirstForMultipleTranslationUnitsAndOnlySecondParsed)
ASSERT_THAT(queried.id(), Eq(created1.id()));
}
TEST_F(TranslationUnits, GetLastUnitializedForMultipleTranslationUnits)
{
const TranslationUnit created1 = translationUnits.createAndAppend();
translationUnits.updateParseTimePoint(created1.id(), Clock::now());
const TranslationUnit created2 = translationUnits.createAndAppend();
const TranslationUnit queried = translationUnits.get(PreferredTranslationUnit::LastUninitialized);
ASSERT_THAT(queried.id(), Eq(created2.id()));
}
TEST_F(TranslationUnits, GetRecentForMultipleTranslationUnits)
{
const TranslationUnit created1 = translationUnits.createAndAppend();

View File

@@ -56,7 +56,10 @@ SOURCES += \
clangisdiagnosticrelatedtolocation-test.cpp \
clangjobqueue-test.cpp \
clangjobs-test.cpp \
clangparsesupportivetranslationunitjobtest.cpp \
clangreparsesupportivetranslationunitjobtest.cpp \
clangrequestdocumentannotationsjob-test.cpp \
clangsupportivetranslationunitinitializertest.cpp \
clangstring-test.cpp \
clangtranslationunits-test.cpp \
clangupdatedocumentannotationsjob-test.cpp \