From 38f72855b61e104d2c597141756d140668f5b20a Mon Sep 17 00:00:00 2001 From: Nikolai Kosjar Date: Tue, 31 May 2016 16:07:09 +0200 Subject: [PATCH] Clang: Process distinct documents concurrently Speed ups the typical use cases that can profit from this: * Change a header file and switch then to source file * Open documents one after the other (Follow Symbol) * Change documents visible in splits (e.g. by refactoring action) * Restore a session with multiple splits Fixes the test ClangIpcServer.GetCodeCompletionDependingOnArgumets. Change-Id: Ia575bd59780df14146dfc091a4d48794e4a0543d Reviewed-by: Tim Jenssen --- .../clangbackend/ipcsource/clangasyncjob.h | 72 ++ .../ipcsource/clangbackendclangipc-source.pri | 24 +- .../ipcsource/clangcodemodelserver.cpp | 157 ++-- .../ipcsource/clangcodemodelserver.h | 22 +- .../ipcsource/clangcompletecodejob.cpp | 95 +++ .../ipcsource/clangcompletecodejob.h | 53 ++ .../clangcreateinitialdocumentpreamblejob.cpp | 75 ++ .../clangcreateinitialdocumentpreamblejob.h | 24 +- .../clangbackend/ipcsource/clangiasyncjob.cpp | 92 +++ .../clangbackend/ipcsource/clangiasyncjob.h | 69 ++ .../ipcsource/clangjobcontext.cpp | 76 ++ .../clangbackend/ipcsource/clangjobcontext.h | 59 ++ .../clangbackend/ipcsource/clangjobqueue.cpp | 223 ++++++ .../clangbackend/ipcsource/clangjobqueue.h | 77 ++ .../ipcsource/clangjobrequest.cpp | 86 +++ .../clangbackend/ipcsource/clangjobrequest.h | 89 +++ .../clangbackend/ipcsource/clangjobs.cpp | 143 ++++ src/tools/clangbackend/ipcsource/clangjobs.h | 82 +++ .../clangrequestdocumentannotationsjob.cpp | 95 +++ .../clangrequestdocumentannotationsjob.h | 60 ++ .../ipcsource/clangtranslationunit.cpp | 411 +++++------ .../ipcsource/clangtranslationunit.h | 81 +-- .../ipcsource/clangtranslationunitupdater.cpp | 41 +- .../ipcsource/clangtranslationunitupdater.h | 9 +- .../clangupdatedocumentannotationsjob.cpp | 109 +++ .../clangupdatedocumentannotationsjob.h | 64 ++ .../ipcsource/translationunits.cpp | 91 +-- .../clangbackend/ipcsource/translationunits.h | 28 +- tests/unit/unittest/clangasyncjobtest.cpp | 70 ++ tests/unit/unittest/clangasyncjobtest.h | 70 ++ .../unittest/clangcodecompleteresultstest.cpp | 5 +- .../unittest/clangcompletecodejobtest.cpp | 85 +++ ...ngcreateinitialdocumentpreamblejobtest.cpp | 62 ++ tests/unit/unittest/clangipcservertest.cpp | 677 +++++++++++------- tests/unit/unittest/clangjobqueuetest.cpp | 446 ++++++++++++ tests/unit/unittest/clangjobstest.cpp | 166 +++++ ...clangrequestdocumentannotationsjobtest.cpp | 85 +++ .../clangupdatedocumentannotationsjobtest.cpp | 112 +++ .../unittest/codecompletionsextractortest.cpp | 3 +- tests/unit/unittest/cursortest.cpp | 4 +- tests/unit/unittest/dummyclangipcclient.h | 41 ++ .../highlightingmarksreportertest.cpp | 2 + tests/unit/unittest/highlightingmarkstest.cpp | 4 +- .../unit/unittest/mockclangcodemodelclient.h | 9 +- .../unit/unittest/skippedsourcerangestest.cpp | 4 +- tests/unit/unittest/sourcerangetest.cpp | 4 +- tests/unit/unittest/testutils.cpp | 54 ++ tests/unit/unittest/testutils.h | 34 + tests/unit/unittest/translationunitstest.cpp | 236 +----- tests/unit/unittest/translationunittest.cpp | 103 ++- tests/unit/unittest/unittest.pro | 14 +- 51 files changed, 3736 insertions(+), 1061 deletions(-) create mode 100644 src/tools/clangbackend/ipcsource/clangasyncjob.h create mode 100644 src/tools/clangbackend/ipcsource/clangcompletecodejob.cpp create mode 100644 src/tools/clangbackend/ipcsource/clangcompletecodejob.h create mode 100644 src/tools/clangbackend/ipcsource/clangcreateinitialdocumentpreamblejob.cpp rename tests/unit/unittest/mocksenddocumentannotationscallback.h => src/tools/clangbackend/ipcsource/clangcreateinitialdocumentpreamblejob.h (74%) create mode 100644 src/tools/clangbackend/ipcsource/clangiasyncjob.cpp create mode 100644 src/tools/clangbackend/ipcsource/clangiasyncjob.h create mode 100644 src/tools/clangbackend/ipcsource/clangjobcontext.cpp create mode 100644 src/tools/clangbackend/ipcsource/clangjobcontext.h create mode 100644 src/tools/clangbackend/ipcsource/clangjobqueue.cpp create mode 100644 src/tools/clangbackend/ipcsource/clangjobqueue.h create mode 100644 src/tools/clangbackend/ipcsource/clangjobrequest.cpp create mode 100644 src/tools/clangbackend/ipcsource/clangjobrequest.h create mode 100644 src/tools/clangbackend/ipcsource/clangjobs.cpp create mode 100644 src/tools/clangbackend/ipcsource/clangjobs.h create mode 100644 src/tools/clangbackend/ipcsource/clangrequestdocumentannotationsjob.cpp create mode 100644 src/tools/clangbackend/ipcsource/clangrequestdocumentannotationsjob.h create mode 100644 src/tools/clangbackend/ipcsource/clangupdatedocumentannotationsjob.cpp create mode 100644 src/tools/clangbackend/ipcsource/clangupdatedocumentannotationsjob.h create mode 100644 tests/unit/unittest/clangasyncjobtest.cpp create mode 100644 tests/unit/unittest/clangasyncjobtest.h create mode 100644 tests/unit/unittest/clangcompletecodejobtest.cpp create mode 100644 tests/unit/unittest/clangcreateinitialdocumentpreamblejobtest.cpp create mode 100644 tests/unit/unittest/clangjobqueuetest.cpp create mode 100644 tests/unit/unittest/clangjobstest.cpp create mode 100644 tests/unit/unittest/clangrequestdocumentannotationsjobtest.cpp create mode 100644 tests/unit/unittest/clangupdatedocumentannotationsjobtest.cpp create mode 100644 tests/unit/unittest/dummyclangipcclient.h create mode 100644 tests/unit/unittest/testutils.cpp create mode 100644 tests/unit/unittest/testutils.h diff --git a/src/tools/clangbackend/ipcsource/clangasyncjob.h b/src/tools/clangbackend/ipcsource/clangasyncjob.h new file mode 100644 index 00000000000..b44227041e5 --- /dev/null +++ b/src/tools/clangbackend/ipcsource/clangasyncjob.h @@ -0,0 +1,72 @@ +/**************************************************************************** +** +** 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 "clangiasyncjob.h" + +#include + +#include +#include + +namespace ClangBackEnd { + +template +class AsyncJob : public IAsyncJob +{ +public: + AsyncJob() {} + ~AsyncJob() {} + + using Runner = std::function; + Runner runner() const { return m_runner; } + void setRunner(const Runner &runner) { m_runner = runner; } + + Result asyncResult() const { return m_futureWatcher.future().result(); } + + QFuture runAsync() override + { + const auto onFinished = [this]() { + finalizeAsyncRun(); + setIsFinished(true); + finishedHandler()(this); + }; + QObject::connect(&m_futureWatcher, + &QFutureWatcher::finished, + onFinished); + + const QFuture future = Utils::runAsync(m_runner); + m_futureWatcher.setFuture(future); + + return future; + } + +private: + Runner m_runner; + QFutureWatcher m_futureWatcher; +}; + +} // namespace ClangBackEnd diff --git a/src/tools/clangbackend/ipcsource/clangbackendclangipc-source.pri b/src/tools/clangbackend/ipcsource/clangbackendclangipc-source.pri index 667ed1fb5d1..f47655a6ea6 100644 --- a/src/tools/clangbackend/ipcsource/clangbackendclangipc-source.pri +++ b/src/tools/clangbackend/ipcsource/clangbackendclangipc-source.pri @@ -36,9 +36,19 @@ HEADERS += $$PWD/clangcodemodelserver.h \ $$PWD/highlightingmarks.h \ $$PWD/highlightingmarksiterator.h \ $$PWD/utf8positionfromlinecolumn.h \ + $$PWD/clangasyncjob.h \ + $$PWD/clangcompletecodejob.h \ + $$PWD/clangcreateinitialdocumentpreamblejob.h \ $$PWD/clangfilepath.h \ + $$PWD/clangiasyncjob.h \ + $$PWD/clangjobcontext.h \ + $$PWD/clangjobqueue.h \ + $$PWD/clangjobrequest.h \ + $$PWD/clangjobs.h \ + $$PWD/clangrequestdocumentannotationsjob.h \ + $$PWD/clangtranslationunitcore.h \ $$PWD/clangunsavedfilesshallowarguments.h \ - $$PWD/clangtranslationunitcore.h + $$PWD/clangupdatedocumentannotationsjob.h SOURCES += $$PWD/clangcodemodelserver.cpp \ $$PWD/codecompleter.cpp \ @@ -74,6 +84,16 @@ SOURCES += $$PWD/clangcodemodelserver.cpp \ $$PWD/highlightingmark.cpp \ $$PWD/highlightingmarks.cpp \ $$PWD/utf8positionfromlinecolumn.cpp \ + $$PWD/clangcompletecodejob.cpp \ + $$PWD/clangcreateinitialdocumentpreamblejob.cpp \ $$PWD/clangfilepath.cpp \ + $$PWD/clangiasyncjob.cpp \ + $$PWD/clangjobcontext.cpp \ + $$PWD/clangjobqueue.cpp \ + $$PWD/clangjobrequest.cpp \ + $$PWD/clangjobs.cpp \ + $$PWD/clangrequestdocumentannotationsjob.cpp \ + $$PWD/clangtranslationunitcore.cpp \ $$PWD/clangunsavedfilesshallowarguments.cpp \ - $$PWD/clangtranslationunitcore.cpp + $$PWD/clangupdatedocumentannotationsjob.cpp \ + diff --git a/src/tools/clangbackend/ipcsource/clangcodemodelserver.cpp b/src/tools/clangbackend/ipcsource/clangcodemodelserver.cpp index 5d48966986c..5f1161717ac 100644 --- a/src/tools/clangbackend/ipcsource/clangcodemodelserver.cpp +++ b/src/tools/clangbackend/ipcsource/clangcodemodelserver.cpp @@ -82,41 +82,25 @@ int delayedDocumentAnnotationsTimerInterval() return interval; } -} +} // anonymous ClangCodeModelServer::ClangCodeModelServer() : translationUnits(projects, unsavedFiles) + , updateDocumentAnnotationsTimeOutInMs(delayedDocumentAnnotationsTimerInterval()) { - const auto sendDocumentAnnotations - = [this] (const DocumentAnnotationsChangedMessage &documentAnnotationsChangedMessage) { - client()->documentAnnotationsChanged(documentAnnotationsChangedMessage); - }; + updateDocumentAnnotationsTimer.setSingleShot(true); - const auto sendDelayedDocumentAnnotations = [this] () { - try { - auto sendState = translationUnits.sendDocumentAnnotations(); - if (sendState == DocumentAnnotationsSendState::MaybeThereAreDocumentAnnotations) - sendDocumentAnnotationsTimer.setInterval(0); - else - sendDocumentAnnotationsTimer.stop(); - } catch (const std::exception &exception) { - qWarning() << "Error in ClangCodeModelServer::sendDelayedDocumentAnnotationsTimer:" << exception.what(); - } - }; - - const auto onFileChanged = [this] (const Utf8String &filePath) { - startDocumentAnnotationsTimerIfFileIsNotATranslationUnit(filePath); - }; - - translationUnits.setSendDocumentAnnotationsCallback(sendDocumentAnnotations); - - QObject::connect(&sendDocumentAnnotationsTimer, + QObject::connect(&updateDocumentAnnotationsTimer, &QTimer::timeout, - sendDelayedDocumentAnnotations); + [this]() { + processJobsForDirtyAndVisibleDocuments(); + }); QObject::connect(translationUnits.clangFileSystemWatcher(), &ClangFileSystemWatcher::fileChanged, - onFileChanged); + [this](const Utf8String &filePath) { + ClangCodeModelServer::startDocumentAnnotationsTimerIfFileIsNotATranslationUnit(filePath); + }); } void ClangCodeModelServer::end() @@ -133,8 +117,8 @@ void ClangCodeModelServer::registerTranslationUnitsForEditor(const ClangBackEnd: unsavedFiles.createOrUpdate(message.fileContainers()); translationUnits.setUsedByCurrentEditor(message.currentEditorFilePath()); translationUnits.setVisibleInEditors(message.visibleEditorFilePaths()); - startDocumentAnnotations(); - reparseVisibleDocuments(createdTranslationUnits); + + processInitialJobsForDocuments(createdTranslationUnits); } catch (const ProjectPartDoNotExistException &exception) { client()->projectPartsDoNotExist(ProjectPartsDoNotExistMessage(exception.projectPartIds())); } catch (const std::exception &exception) { @@ -151,7 +135,8 @@ void ClangCodeModelServer::updateTranslationUnitsForEditor(const UpdateTranslati if (newerFileContainers.size() > 0) { translationUnits.update(newerFileContainers); unsavedFiles.createOrUpdate(newerFileContainers); - sendDocumentAnnotationsTimer.start(delayedDocumentAnnotationsTimerInterval()); + + updateDocumentAnnotationsTimer.start(updateDocumentAnnotationsTimeOutInMs); } } catch (const ProjectPartDoNotExistException &exception) { client()->projectPartsDoNotExist(ProjectPartsDoNotExistMessage(exception.projectPartIds())); @@ -185,7 +170,8 @@ void ClangCodeModelServer::registerProjectPartsForEditor(const RegisterProjectPa try { projects.createOrUpdate(message.projectContainers()); translationUnits.setTranslationUnitsDirtyIfProjectPartChanged(); - sendDocumentAnnotationsTimer.start(0); + + processJobsForDirtyAndVisibleDocuments(); } catch (const std::exception &exception) { qWarning() << "Error in ClangCodeModelServer::registerProjectPartsForEditor:" << exception.what(); } @@ -211,7 +197,8 @@ void ClangCodeModelServer::registerUnsavedFilesForEditor(const RegisterUnsavedFi try { unsavedFiles.createOrUpdate(message.fileContainers()); translationUnits.updateTranslationUnitsWithChangedDependencies(message.fileContainers()); - sendDocumentAnnotationsTimer.start(delayedDocumentAnnotationsTimerInterval()); + + updateDocumentAnnotationsTimer.start(updateDocumentAnnotationsTimeOutInMs); } catch (const ProjectPartDoNotExistException &exception) { client()->projectPartsDoNotExist(ProjectPartsDoNotExistMessage(exception.projectPartIds())); } catch (const std::exception &exception) { @@ -240,16 +227,16 @@ void ClangCodeModelServer::completeCode(const ClangBackEnd::CompleteCodeMessage TIME_SCOPE_DURATION("ClangCodeModelServer::completeCode"); try { - auto translationUnit = translationUnits.translationUnit(message.filePath(), message.projectPartId()); - auto translationUnitCore = translationUnit.translationUnitCore(); + auto translationUnit = translationUnits.translationUnit(message.filePath(), + message.projectPartId()); - CodeCompleter codeCompleter(translationUnitCore, unsavedFiles); + JobRequest jobRequest = createJobRequest(translationUnit, JobRequest::Type::CompleteCode); + jobRequest.line = message.line(); + jobRequest.column = message.column(); + jobRequest.ticketNumber = message.ticketNumber(); - const auto codeCompletions = codeCompleter.complete(message.line(), message.column()); - - client()->codeCompleted(CodeCompletedMessage(codeCompletions, - codeCompleter.neededCorrection(), - message.ticketNumber())); + jobs().add(jobRequest); + jobs().process(); } catch (const TranslationUnitDoesNotExistException &exception) { client()->translationUnitDoesNotExist(TranslationUnitDoesNotExistMessage(exception.fileContainer())); } catch (const ProjectPartDoNotExistException &exception) { @@ -266,13 +253,12 @@ void ClangCodeModelServer::requestDocumentAnnotations(const RequestDocumentAnnot try { auto translationUnit = translationUnits.translationUnit(message.fileContainer().filePath(), message.fileContainer().projectPartId()); - auto translationUnitCore = translationUnit.translationUnitCore(); - client()->documentAnnotationsChanged(DocumentAnnotationsChangedMessage( - translationUnit.fileContainer(), - translationUnitCore.mainFileDiagnostics(), - translationUnitCore.highlightingMarks().toHighlightingMarksContainers(), - translationUnitCore.skippedSourceRanges().toSourceRangeContainers())); + const JobRequest jobRequest = createJobRequest(translationUnit, + JobRequest::Type::RequestDocumentAnnotations); + + jobs().add(jobRequest); + jobs().process(); } catch (const TranslationUnitDoesNotExistException &exception) { client()->translationUnitDoesNotExist(TranslationUnitDoesNotExistMessage(exception.fileContainer())); } catch (const ProjectPartDoNotExistException &exception) { @@ -289,7 +275,7 @@ void ClangCodeModelServer::updateVisibleTranslationUnits(const UpdateVisibleTran try { translationUnits.setUsedByCurrentEditor(message.currentEditorFilePath()); translationUnits.setVisibleInEditors(message.visibleEditorFilePaths()); - sendDocumentAnnotationsTimer.start(0); + updateDocumentAnnotationsTimer.start(0); } catch (const std::exception &exception) { qWarning() << "Error in ClangCodeModelServer::updateVisibleTranslationUnits:" << exception.what(); } @@ -302,23 +288,80 @@ const TranslationUnits &ClangCodeModelServer::translationUnitsForTestOnly() cons void ClangCodeModelServer::startDocumentAnnotationsTimerIfFileIsNotATranslationUnit(const Utf8String &filePath) { - if (!translationUnits.hasTranslationUnit(filePath)) - sendDocumentAnnotationsTimer.start(0); + if (!translationUnits.hasTranslationUnitWithFilePath(filePath)) + updateDocumentAnnotationsTimer.start(0); } -void ClangCodeModelServer::startDocumentAnnotations() +const Jobs &ClangCodeModelServer::jobsForTestOnly() { - DocumentAnnotationsSendState sendState = DocumentAnnotationsSendState::MaybeThereAreDocumentAnnotations; - - while (sendState == DocumentAnnotationsSendState::MaybeThereAreDocumentAnnotations) - sendState = translationUnits.sendDocumentAnnotations(); + return jobs(); } -void ClangCodeModelServer::reparseVisibleDocuments(std::vector &translationUnits) +bool ClangCodeModelServer::isTimerRunningForTestOnly() const { - for (TranslationUnit &translationUnit : translationUnits) - if (translationUnit.isVisibleInEditor()) - translationUnit.reparse(); + return updateDocumentAnnotationsTimer.isActive(); } +void ClangCodeModelServer::addJobRequestsForDirtyAndVisibleDocuments() +{ + for (const auto &translationUnit : translationUnits.translationUnits()) { + if (translationUnit.isNeedingReparse() && translationUnit.isVisibleInEditor()) { + jobs().add(createJobRequest(translationUnit, + JobRequest::Type::UpdateDocumentAnnotations)); + } + } } + +void ClangCodeModelServer::processJobsForDirtyAndVisibleDocuments() +{ + addJobRequestsForDirtyAndVisibleDocuments(); + jobs().process(); +} + +void ClangCodeModelServer::processInitialJobsForDocuments( + const std::vector &translationUnits) +{ + for (const auto &translationUnit : translationUnits) { + jobs().add(createJobRequest(translationUnit, + JobRequest::Type::UpdateDocumentAnnotations)); + jobs().add(createJobRequest(translationUnit, + JobRequest::Type::CreateInitialDocumentPreamble)); + } + + jobs().process(); +} + +JobRequest ClangCodeModelServer::createJobRequest(const TranslationUnit &translationUnit, + JobRequest::Type type) const +{ + JobRequest jobRequest; + jobRequest.type = type; + jobRequest.requirements = JobRequest::requirementsForType(type); + jobRequest.filePath = translationUnit.filePath(); + jobRequest.projectPartId = translationUnit.projectPartId(); + jobRequest.unsavedFilesChangeTimePoint = unsavedFiles.lastChangeTimePoint(); + jobRequest.documentRevision = translationUnit.documentRevision(); + const ProjectPart &projectPart = projects.project(translationUnit.projectPartId()); + jobRequest.projectChangeTimePoint = projectPart.lastChangeTimePoint(); + + return jobRequest; +} + +void ClangCodeModelServer::setUpdateDocumentAnnotationsTimeOutInMsForTestsOnly(int value) +{ + updateDocumentAnnotationsTimeOutInMs = value; +} + +Jobs &ClangCodeModelServer::jobs() +{ + if (!jobs_) { + // Jobs needs a reference to the client, but the client is not known at + // construction time of ClangCodeModelServer, so construct Jobs in a + // lazy manner. + jobs_.reset(new Jobs(translationUnits, unsavedFiles, projects, *client())); + } + + return *jobs_.data(); +} + +} // namespace ClangBackEnd diff --git a/src/tools/clangbackend/ipcsource/clangcodemodelserver.h b/src/tools/clangbackend/ipcsource/clangcodemodelserver.h index 7dd52e0b771..82c64b560df 100644 --- a/src/tools/clangbackend/ipcsource/clangcodemodelserver.h +++ b/src/tools/clangbackend/ipcsource/clangcodemodelserver.h @@ -32,10 +32,11 @@ #include "clangtranslationunit.h" #include "translationunits.h" #include "unsavedfiles.h" +#include "clangjobs.h" #include -#include +#include #include namespace ClangBackEnd { @@ -57,18 +58,31 @@ public: void updateVisibleTranslationUnits(const UpdateVisibleTranslationUnitsMessage &message) override; void requestDocumentAnnotations(const RequestDocumentAnnotationsMessage &message) override; +public /*for tests*/: const TranslationUnits &translationUnitsForTestOnly() const; + const Jobs &jobsForTestOnly(); + bool isTimerRunningForTestOnly() const; + void setUpdateDocumentAnnotationsTimeOutInMsForTestsOnly(int value); private: + Jobs &jobs(); + void startDocumentAnnotationsTimerIfFileIsNotATranslationUnit(const Utf8String &filePath); - void startDocumentAnnotations(); - void reparseVisibleDocuments(std::vector &translationUnits); + void addJobRequestsForDirtyAndVisibleDocuments(); + void processJobsForDirtyAndVisibleDocuments(); + void processInitialJobsForDocuments(const std::vector &translationUnits); + + JobRequest createJobRequest(const TranslationUnit &translationUnit, + JobRequest::Type type) const; private: ProjectParts projects; UnsavedFiles unsavedFiles; TranslationUnits translationUnits; - QTimer sendDocumentAnnotationsTimer; + QScopedPointer jobs_; + + QTimer updateDocumentAnnotationsTimer; + int updateDocumentAnnotationsTimeOutInMs; }; } // namespace ClangBackEnd diff --git a/src/tools/clangbackend/ipcsource/clangcompletecodejob.cpp b/src/tools/clangbackend/ipcsource/clangcompletecodejob.cpp new file mode 100644 index 00000000000..d85c93190c0 --- /dev/null +++ b/src/tools/clangbackend/ipcsource/clangcompletecodejob.cpp @@ -0,0 +1,95 @@ +/**************************************************************************** +** +** 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 "clangcompletecodejob.h" + +#include +#include +#include + +#include + +namespace ClangBackEnd { + +static CompleteCodeJob::AsyncResult runAsyncHelper(const TranslationUnitCore &translationUnitCore, + UnsavedFiles unsavedFiles, + quint32 line, + quint32 column) +{ + TIME_SCOPE_DURATION("CompleteCodeJobRunner"); + + CompleteCodeJob::AsyncResult asyncResult; + + try { + const TranslationUnitCore::CodeCompletionResult results + = translationUnitCore.complete(unsavedFiles, line, column); + + asyncResult.completions = results.completions; + asyncResult.correction = results.correction; + } catch (const std::exception &exception) { + qWarning() << "Error in CompleteCodeJobRunner:" << exception.what(); + } + + return asyncResult; +} + +bool CompleteCodeJob::prepareAsyncRun() +{ + const JobRequest jobRequest = context().jobRequest; + QTC_ASSERT(jobRequest.type == JobRequest::Type::CompleteCode, return false); + + try { + m_pinnedTranslationUnit = context().translationUnitForJobRequest(); + + const TranslationUnitCore translationUnitCore = m_pinnedTranslationUnit.translationUnitCore(); + const UnsavedFiles unsavedFiles = *context().unsavedFiles; + const quint32 line = jobRequest.line; + const quint32 column = jobRequest.column; + setRunner([translationUnitCore, unsavedFiles, line, column]() { + return runAsyncHelper(translationUnitCore, unsavedFiles, line, column); + }); + + + } catch (const std::exception &exception) { + qWarning() << "Error in CompleteCodeJob::prepareAsyncRun:" << exception.what(); + return false; + } + + return true; +} + +void CompleteCodeJob::finalizeAsyncRun() +{ + if (context().isDocumentOpen()) { + const AsyncResult result = asyncResult(); + + const CodeCompletedMessage message(result.completions, + result.correction, + context().jobRequest.ticketNumber); + context().client->codeCompleted(message); + } +} + +} // namespace ClangBackEnd diff --git a/src/tools/clangbackend/ipcsource/clangcompletecodejob.h b/src/tools/clangbackend/ipcsource/clangcompletecodejob.h new file mode 100644 index 00000000000..d3ee9dbc6b8 --- /dev/null +++ b/src/tools/clangbackend/ipcsource/clangcompletecodejob.h @@ -0,0 +1,53 @@ +/**************************************************************************** +** +** 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 "clangtranslationunit.h" + +#include + +namespace ClangBackEnd { + +struct CompleteCodeJobResult +{ + CodeCompletions completions; + CompletionCorrection correction = CompletionCorrection::NoCorrection; +}; + +class CompleteCodeJob : public AsyncJob +{ +public: + using AsyncResult = CompleteCodeJobResult; + + bool prepareAsyncRun() override; + void finalizeAsyncRun() override; + +private: + TranslationUnit m_pinnedTranslationUnit; +}; + +} // namespace ClangBackEnd diff --git a/src/tools/clangbackend/ipcsource/clangcreateinitialdocumentpreamblejob.cpp b/src/tools/clangbackend/ipcsource/clangcreateinitialdocumentpreamblejob.cpp new file mode 100644 index 00000000000..77ee9815186 --- /dev/null +++ b/src/tools/clangbackend/ipcsource/clangcreateinitialdocumentpreamblejob.cpp @@ -0,0 +1,75 @@ +/**************************************************************************** +** +** 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 "clangcreateinitialdocumentpreamblejob.h" + +#include + +#include + +namespace ClangBackEnd { + +static void runAsyncHelper(const TranslationUnitCore &translationUnitCore, + const TranslationUnitUpdateInput &translationUnitUpdateInput) +{ + TIME_SCOPE_DURATION("CreateInitialDocumentPreambleJobRunner"); + + try { + translationUnitCore.reparse(translationUnitUpdateInput); + } catch (const std::exception &exception) { + qWarning() << "Error in CreateInitialDocumentPreambleJobRunner:" << exception.what(); + } +} + +bool CreateInitialDocumentPreambleJob::prepareAsyncRun() +{ + const JobRequest jobRequest = context().jobRequest; + QTC_ASSERT(jobRequest.type == JobRequest::Type::CreateInitialDocumentPreamble, return false); + + try { + m_pinnedTranslationUnit = context().translationUnitForJobRequest(); + m_pinnedFileContainer = m_pinnedTranslationUnit.fileContainer(); + + const TranslationUnitCore translationUnitCore = m_pinnedTranslationUnit.translationUnitCore(); + const TranslationUnitUpdateInput updateInput = m_pinnedTranslationUnit.createUpdateInput(); + setRunner([translationUnitCore, updateInput]() { + return runAsyncHelper(translationUnitCore, updateInput); + }); + + } catch (const std::exception &exception) { + qWarning() << "Error in CreateInitialDocumentPreambleJob::prepareAsyncRun:" + << exception.what(); + return false; + } + + return true; +} + +void CreateInitialDocumentPreambleJob::finalizeAsyncRun() +{ +} + +} // namespace ClangBackEnd + diff --git a/tests/unit/unittest/mocksenddocumentannotationscallback.h b/src/tools/clangbackend/ipcsource/clangcreateinitialdocumentpreamblejob.h similarity index 74% rename from tests/unit/unittest/mocksenddocumentannotationscallback.h rename to src/tools/clangbackend/ipcsource/clangcreateinitialdocumentpreamblejob.h index 1ab5206ec65..023a34c8f33 100644 --- a/tests/unit/unittest/mocksenddocumentannotationscallback.h +++ b/src/tools/clangbackend/ipcsource/clangcreateinitialdocumentpreamblejob.h @@ -25,22 +25,20 @@ #pragma once -#include -#include -#include -#include "gtest-qt-printing.h" +#include "clangasyncjob.h" +#include "clangtranslationunit.h" -class SendDocumentAnnotationsCallback +namespace ClangBackEnd { + +class CreateInitialDocumentPreambleJob : public AsyncJob { public: - virtual ~SendDocumentAnnotationsCallback() = default; + bool prepareAsyncRun() override; + void finalizeAsyncRun() override; - virtual void sendDocumentAnnotations() = 0; +private: + TranslationUnit m_pinnedTranslationUnit; + FileContainer m_pinnedFileContainer; }; -class MockSendDocumentAnnotationsCallback : public SendDocumentAnnotationsCallback -{ -public: - MOCK_METHOD0(sendDocumentAnnotations, - void()); -}; +} // namespace ClangBackEnd diff --git a/src/tools/clangbackend/ipcsource/clangiasyncjob.cpp b/src/tools/clangbackend/ipcsource/clangiasyncjob.cpp new file mode 100644 index 00000000000..8c433c40928 --- /dev/null +++ b/src/tools/clangbackend/ipcsource/clangiasyncjob.cpp @@ -0,0 +1,92 @@ +/**************************************************************************** +** +** 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 "clangiasyncjob.h" + +#include "clangcompletecodejob.h" +#include "clangcreateinitialdocumentpreamblejob.h" +#include "clangrequestdocumentannotationsjob.h" +#include "clangupdatedocumentannotationsjob.h" + +Q_LOGGING_CATEGORY(jobsLog, "qtc.clangbackend.jobs"); + +namespace ClangBackEnd { + +IAsyncJob *IAsyncJob::create(JobRequest::Type type) +{ + switch (type) { + case JobRequest::Type::UpdateDocumentAnnotations: + return new UpdateDocumentAnnotationsJob(); + case JobRequest::Type::CreateInitialDocumentPreamble: + return new CreateInitialDocumentPreambleJob(); + case JobRequest::Type::CompleteCode: + return new CompleteCodeJob(); + case JobRequest::Type::RequestDocumentAnnotations: + return new RequestDocumentAnnotationsJob(); + } + + return nullptr; +} + +IAsyncJob::IAsyncJob() + : m_context(JobContext()) +{ +} + +IAsyncJob::~IAsyncJob() +{ +} + +JobContext IAsyncJob::context() const +{ + return m_context; +} + +void IAsyncJob::setContext(const JobContext &context) +{ + m_context = context; +} + +IAsyncJob::FinishedHandler IAsyncJob::finishedHandler() const +{ + return m_finishedHandler; +} + +void IAsyncJob::setFinishedHandler(const IAsyncJob::FinishedHandler &finishedHandler) +{ + m_finishedHandler = finishedHandler; +} + +bool IAsyncJob::isFinished() const +{ + return m_isFinished; +} + +void IAsyncJob::setIsFinished(bool isFinished) +{ + m_isFinished = isFinished; +} + +} // namespace ClangBackEnd diff --git a/src/tools/clangbackend/ipcsource/clangiasyncjob.h b/src/tools/clangbackend/ipcsource/clangiasyncjob.h new file mode 100644 index 00000000000..44f503f3cb8 --- /dev/null +++ b/src/tools/clangbackend/ipcsource/clangiasyncjob.h @@ -0,0 +1,69 @@ +/**************************************************************************** +** +** 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 "clangjobcontext.h" + +#include +#include + +#include + +Q_DECLARE_LOGGING_CATEGORY(jobsLog); + +namespace ClangBackEnd { + +class IAsyncJob +{ +public: + static IAsyncJob *create(JobRequest::Type type); + +public: + IAsyncJob(); + virtual ~IAsyncJob(); + + JobContext context() const; + void setContext(const JobContext &context); + + using FinishedHandler = std::function; + FinishedHandler finishedHandler() const; + void setFinishedHandler(const FinishedHandler &finishedHandler); + + virtual bool prepareAsyncRun() = 0; + virtual QFuture runAsync() = 0; + virtual void finalizeAsyncRun() = 0; + +public: // for tests + bool isFinished() const; + void setIsFinished(bool isFinished); + +private: + bool m_isFinished = false; + FinishedHandler m_finishedHandler; + JobContext m_context; +}; + +} // namespace ClangBackEnd diff --git a/src/tools/clangbackend/ipcsource/clangjobcontext.cpp b/src/tools/clangbackend/ipcsource/clangjobcontext.cpp new file mode 100644 index 00000000000..9c9c520fce1 --- /dev/null +++ b/src/tools/clangbackend/ipcsource/clangjobcontext.cpp @@ -0,0 +1,76 @@ +/**************************************************************************** +** +** 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 "clangiasyncjob.h" + +#include "translationunits.h" + +namespace ClangBackEnd { + +JobContext::JobContext(const JobRequest &jobRequest, + TranslationUnits *translationUnits, + UnsavedFiles *unsavedFiles, + ClangCodeModelClientInterface *clientInterface) + : jobRequest(jobRequest) + , translationUnits(translationUnits) + , unsavedFiles(unsavedFiles) + , client(clientInterface) +{ +} + +TranslationUnit JobContext::translationUnitForJobRequest() const +{ + return translationUnits->translationUnit(jobRequest.filePath, jobRequest.projectPartId); +} + +bool JobContext::isOutdated() const +{ + return !isDocumentOpen() || documentRevisionChanged(); +} + +bool JobContext::isDocumentOpen() const +{ + const bool hasTranslationUnit + = translationUnits->hasTranslationUnit(jobRequest.filePath, jobRequest.projectPartId); + + if (!hasTranslationUnit) + qCDebug(jobsLog) << "Document already closed for results of" << jobRequest; + + return hasTranslationUnit; +} + +bool JobContext::documentRevisionChanged() const +{ + const TranslationUnit &translationUnit + = translationUnits->translationUnit(jobRequest.filePath, jobRequest.projectPartId); + const bool revisionChanged = translationUnit.documentRevision() != jobRequest.documentRevision; + + if (revisionChanged) + qCDebug(jobsLog) << "Document revision changed for results of" << jobRequest; + + return revisionChanged; +} + +} // ClangBackEnd diff --git a/src/tools/clangbackend/ipcsource/clangjobcontext.h b/src/tools/clangbackend/ipcsource/clangjobcontext.h new file mode 100644 index 00000000000..6e124c609e8 --- /dev/null +++ b/src/tools/clangbackend/ipcsource/clangjobcontext.h @@ -0,0 +1,59 @@ +/**************************************************************************** +** +** 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 "clangjobrequest.h" + +namespace ClangBackEnd { + +class ClangCodeModelClientInterface; +class TranslationUnit; +class TranslationUnits; +class UnsavedFiles; + +class JobContext +{ +public: + JobContext() = default; + JobContext(const JobRequest &jobRequest, + TranslationUnits *translationUnits, + UnsavedFiles *unsavedFiles, + ClangCodeModelClientInterface *client); + + TranslationUnit translationUnitForJobRequest() const; + + bool isOutdated() const; + bool isDocumentOpen() const; + bool documentRevisionChanged() const; + +public: + JobRequest jobRequest; + TranslationUnits *translationUnits = nullptr; + UnsavedFiles *unsavedFiles = nullptr; + ClangCodeModelClientInterface *client = nullptr; +}; + +} // namespace ClangBackEnd diff --git a/src/tools/clangbackend/ipcsource/clangjobqueue.cpp b/src/tools/clangbackend/ipcsource/clangjobqueue.cpp new file mode 100644 index 00000000000..59d5e5ba9e4 --- /dev/null +++ b/src/tools/clangbackend/ipcsource/clangjobqueue.cpp @@ -0,0 +1,223 @@ +/**************************************************************************** +** +** 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 "clangiasyncjob.h" +#include "clangjobqueue.h" +#include "clangtranslationunit.h" +#include "translationunits.h" +#include "projects.h" +#include "unsavedfiles.h" + +#include + +namespace ClangBackEnd { + +JobQueue::JobQueue(TranslationUnits &translationUnits, + UnsavedFiles &unsavedFiles, + ProjectParts &projectParts, + ClangCodeModelClientInterface &client) + : m_translationUnits(translationUnits) + , m_unsavedFiles(unsavedFiles) + , m_projectParts(projectParts) + , m_client(client) +{ +} + +void JobQueue::add(const JobRequest &job) +{ + qCDebug(jobsLog) << "Adding" << job; + + m_queue.append(job); +} + +int JobQueue::size() const +{ + return m_queue.size(); +} + +JobRequests JobQueue::processQueue() +{ + removeOutDatedRequests(); + prioritizeRequests(); + const JobRequests jobsToRun = takeJobRequestsToRunNow(); + + return jobsToRun; +} + +void JobQueue::removeOutDatedRequests() +{ + JobRequests cleanedRequests; + + foreach (const JobRequest &jobRequest, m_queue) { + try { + if (!isJobRequestOutDated(jobRequest)) + cleanedRequests.append(jobRequest); + } catch (const std::exception &exception) { + qWarning() << "Error in Jobs::removeOutDatedRequests for" + << jobRequest << ":" << exception.what(); + } + } + + m_queue = cleanedRequests; +} + +bool JobQueue::isJobRequestOutDated(const JobRequest &jobRequest) const +{ + const JobRequest::Requirements requirements = jobRequest.requirements; + const UnsavedFiles unsavedFiles = m_translationUnits.unsavedFiles(); + + if (requirements.testFlag(JobRequest::CurrentUnsavedFiles)) { + if (jobRequest.unsavedFilesChangeTimePoint != unsavedFiles.lastChangeTimePoint()) { + qCDebug(jobsLog) << "Removing due to outdated unsaved files:" << jobRequest; + return true; + } + } + + bool projectCheckedAndItExists = false; + + if (requirements.testFlag(JobRequest::DocumentValid)) { + if (!m_translationUnits.hasTranslationUnit(jobRequest.filePath, jobRequest.projectPartId)) { + qCDebug(jobsLog) << "Removing due to already closed document:" << jobRequest; + return true; + } + + if (!m_projectParts.hasProjectPart(jobRequest.projectPartId)) { + qCDebug(jobsLog) << "Removing due to already closed project:" << jobRequest; + return true; + } + projectCheckedAndItExists = true; + + const TranslationUnit translationUnit + = m_translationUnits.translationUnit(jobRequest.filePath, jobRequest.projectPartId); + if (!translationUnit.isIntact()) { + qCDebug(jobsLog) << "Removing due to not intact translation unit:" << jobRequest; + return true; + } + + if (requirements.testFlag(JobRequest::CurrentDocumentRevision)) { + if (translationUnit.documentRevision() != jobRequest.documentRevision) { + qCDebug(jobsLog) << "Removing due to changed document revision:" << jobRequest; + return true; + } + } + } + + if (requirements.testFlag(JobRequest::CurrentProject)) { + if (!projectCheckedAndItExists && !m_projectParts.hasProjectPart(jobRequest.projectPartId)) { + qCDebug(jobsLog) << "Removing due to already closed project:" << jobRequest; + return true; + } + + const ProjectPart &project = m_projectParts.project(jobRequest.projectPartId); + if (project.lastChangeTimePoint() != jobRequest.projectChangeTimePoint) { + qCDebug(jobsLog) << "Removing due to outdated project:" << jobRequest; + return true; + } + } + + return false; +} + +static int priority(const TranslationUnit &translationUnit) +{ + int thePriority = 0; + + if (translationUnit.isUsedByCurrentEditor()) + thePriority += 1000; + + if (translationUnit.isVisibleInEditor()) + thePriority += 100; + + return thePriority; +} + +void JobQueue::prioritizeRequests() +{ + const auto lessThan = [this] (const JobRequest &r1, const JobRequest &r2) { + // TODO: Getting the TU is O(n) currently, so this might become expensive for large n. + const TranslationUnit &t1 = m_translationUnits.translationUnit(r1.filePath, r1.projectPartId); + const TranslationUnit &t2 = m_translationUnits.translationUnit(r2.filePath, r2.projectPartId); + + return priority(t1) > priority(t2); + }; + + std::stable_sort(m_queue.begin(), m_queue.end(), lessThan); +} + +JobRequests JobQueue::takeJobRequestsToRunNow() +{ + JobRequests jobsToRun; + QSet documentsScheduledForThisRun; + + QMutableVectorIterator i(m_queue); + while (i.hasNext()) { + const JobRequest &jobRequest = i.next(); + + try { + const TranslationUnit &translationUnit + = m_translationUnits.translationUnit(jobRequest.filePath, + jobRequest.projectPartId); + const DocumentId documentId = DocumentId(jobRequest.filePath, jobRequest.projectPartId); + + if (!translationUnit.isUsedByCurrentEditor() && !translationUnit.isVisibleInEditor()) + continue; + + if (documentsScheduledForThisRun.contains(documentId)) + continue; + + if (isJobRunningForDocument(documentId)) + continue; + + documentsScheduledForThisRun.insert(documentId); + jobsToRun += jobRequest; + i.remove(); + } catch (const std::exception &exception) { + qWarning() << "Error in Jobs::takeJobRequestsToRunNow for" + << jobRequest << ":" << exception.what(); + } + } + + return jobsToRun; +} + +bool JobQueue::isJobRunningForDocument(const JobQueue::DocumentId &documentId) +{ + if (m_isJobRunningHandler) + return m_isJobRunningHandler(documentId.first, documentId.second); + + return false; +} + +void JobQueue::setIsJobRunningHandler(const IsJobRunningHandler &isJobRunningHandler) +{ + m_isJobRunningHandler = isJobRunningHandler; +} + +JobRequests JobQueue::queue() const +{ + return m_queue; +} + +} // namespace ClangBackEnd diff --git a/src/tools/clangbackend/ipcsource/clangjobqueue.h b/src/tools/clangbackend/ipcsource/clangjobqueue.h new file mode 100644 index 00000000000..f29be968d58 --- /dev/null +++ b/src/tools/clangbackend/ipcsource/clangjobqueue.h @@ -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. +** +****************************************************************************/ + +#pragma once + +#include "clangjobrequest.h" + +#include + +namespace ClangBackEnd { + +class ClangCodeModelClientInterface; +class ProjectParts; +class TranslationUnits; +class UnsavedFiles; + +class JobQueue +{ +public: + JobQueue(TranslationUnits &translationUnits, + UnsavedFiles &unsavedFiles, + ProjectParts &projects, + ClangCodeModelClientInterface &client); + + void add(const JobRequest &job); + + JobRequests processQueue(); + + using IsJobRunningHandler = std::function; + void setIsJobRunningHandler(const IsJobRunningHandler &isJobRunningHandler); + +public: // for tests + JobRequests queue() const; + int size() const; + void prioritizeRequests(); + +private: + using DocumentId = QPair; + bool isJobRunningForDocument(const DocumentId &documentId); + JobRequests takeJobRequestsToRunNow(); + void removeOutDatedRequests(); + bool isJobRequestOutDated(const JobRequest &jobRequest) const; + +private: + TranslationUnits &m_translationUnits; + UnsavedFiles &m_unsavedFiles; + ProjectParts &m_projectParts; + ClangCodeModelClientInterface &m_client; + + IsJobRunningHandler m_isJobRunningHandler; + + JobRequests m_queue; +}; + +} // namespace ClangBackEnd diff --git a/src/tools/clangbackend/ipcsource/clangjobrequest.cpp b/src/tools/clangbackend/ipcsource/clangjobrequest.cpp new file mode 100644 index 00000000000..4c3d692d08c --- /dev/null +++ b/src/tools/clangbackend/ipcsource/clangjobrequest.cpp @@ -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 "clangjobrequest.h" + +namespace ClangBackEnd { + +#define RETURN_TEXT_FOR_CASE(enumValue) case JobRequest::Type::enumValue: return #enumValue +static const char *JobRequestTypeToText(JobRequest::Type type) +{ + switch (type) { + RETURN_TEXT_FOR_CASE(UpdateDocumentAnnotations); + RETURN_TEXT_FOR_CASE(CreateInitialDocumentPreamble); + RETURN_TEXT_FOR_CASE(CompleteCode); + RETURN_TEXT_FOR_CASE(RequestDocumentAnnotations); + } + + return "UnhandledJobRequestType"; +} +#undef RETURN_TEXT_FOR_CASE + +QDebug operator<<(QDebug debug, JobRequest::Type type) +{ + debug << JobRequestTypeToText(type); + + return debug; +} + +QDebug operator<<(QDebug debug, const JobRequest &jobRequest) +{ + debug.nospace() << "Job<" + << jobRequest.id + << "," + << JobRequestTypeToText(jobRequest.type) + << "," + << jobRequest.filePath + << ">"; + + return debug; +} + +JobRequest::JobRequest() +{ + static quint64 idCounter = 0; + id = ++idCounter; +} + +JobRequest::Requirements JobRequest::requirementsForType(Type type) +{ + switch (type) { + case JobRequest::Type::UpdateDocumentAnnotations: + return JobRequest::Requirements(JobRequest::All); + case JobRequest::Type::RequestDocumentAnnotations: + return JobRequest::Requirements(JobRequest::DocumentValid + |JobRequest::CurrentDocumentRevision); + case JobRequest::Type::CompleteCode: + case JobRequest::Type::CreateInitialDocumentPreamble: + return JobRequest::Requirements(JobRequest::DocumentValid); + } + + return JobRequest::Requirements(JobRequest::DocumentValid); +} + +} // namespace ClangBackEnd diff --git a/src/tools/clangbackend/ipcsource/clangjobrequest.h b/src/tools/clangbackend/ipcsource/clangjobrequest.h new file mode 100644 index 00000000000..ca75e4ec675 --- /dev/null +++ b/src/tools/clangbackend/ipcsource/clangjobrequest.h @@ -0,0 +1,89 @@ +/**************************************************************************** +** +** 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 + +#include +#include +#include + +#include + +namespace ClangBackEnd { + +using time_point = std::chrono::steady_clock::time_point; + +class JobRequest +{ +public: + enum class Type { + UpdateDocumentAnnotations, + CreateInitialDocumentPreamble, + CompleteCode, + RequestDocumentAnnotations, + }; + + enum Requirement { + None = 1 << 0, + + DocumentValid = 1 << 1, + CurrentDocumentRevision = 1 << 3, // Only effective if DocumentValid is also set + CurrentUnsavedFiles = 1 << 2, + CurrentProject = 1 << 4, + + All = DocumentValid | CurrentUnsavedFiles | CurrentDocumentRevision | CurrentProject + }; + Q_DECLARE_FLAGS(Requirements, Requirement) + +public: + static Requirements requirementsForType(Type type); + + JobRequest(); + +public: + quint64 id = 0; + Type type; + Requirements requirements; + + // General + Utf8String filePath; + Utf8String projectPartId; + time_point unsavedFilesChangeTimePoint; + time_point projectChangeTimePoint; + uint documentRevision = 0; + + // For code completion + quint32 line = 0; + quint32 column = 0; + quint64 ticketNumber = 0; +}; + +using JobRequests = QVector; + +QDebug operator<<(QDebug debug, const JobRequest &jobRequest); + +} // namespace ClangBackEnd diff --git a/src/tools/clangbackend/ipcsource/clangjobs.cpp b/src/tools/clangbackend/ipcsource/clangjobs.cpp new file mode 100644 index 00000000000..6871b03a401 --- /dev/null +++ b/src/tools/clangbackend/ipcsource/clangjobs.cpp @@ -0,0 +1,143 @@ +/**************************************************************************** +** +** 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 "clangjobs.h" + +#include "clangiasyncjob.h" + +#include +#include +#include + +#include +#include + +namespace ClangBackEnd { + +Jobs::Jobs(TranslationUnits &translationUnits, + UnsavedFiles &unsavedFiles, + ProjectParts &projectParts, + ClangCodeModelClientInterface &client) + : m_translationUnits(translationUnits) + , m_unsavedFiles(unsavedFiles) + , m_projectParts(projectParts) + , m_client(client) + , m_queue(translationUnits, unsavedFiles, projectParts, client) +{ + m_queue.setIsJobRunningHandler([this](const Utf8String &filePath, + const Utf8String &projectPartId) { + return isJobRunning(filePath, projectPartId); + }); +} + +Jobs::~Jobs() +{ + QFutureSynchronizer waitForFinishedJobs; + foreach (const RunningJob &runningJob, m_running.values()) + waitForFinishedJobs.addFuture(runningJob.future); +} + +void Jobs::add(const JobRequest &job) +{ + m_queue.add(job); +} + +JobRequests Jobs::process() +{ + const JobRequests jobsToRun = m_queue.processQueue(); + const JobRequests jobsStarted = runJobs(jobsToRun); + + QTC_CHECK(jobsToRun.size() == jobsStarted.size()); + + return jobsStarted; +} + +JobRequests Jobs::runJobs(const JobRequests &jobsRequests) +{ + JobRequests jobsStarted; + + foreach (const JobRequest &jobRequest, jobsRequests) { + if (runJob(jobRequest)) + jobsStarted += jobRequest; + } + + return jobsStarted; +} + +bool Jobs::runJob(const JobRequest &jobRequest) +{ + if (IAsyncJob *asyncJob = IAsyncJob::create(jobRequest.type)) { + JobContext context(jobRequest, &m_translationUnits, &m_unsavedFiles, &m_client); + asyncJob->setContext(context); + + if (asyncJob->prepareAsyncRun()) { + qCDebug(jobsLog) << "Running" << jobRequest; + + asyncJob->setFinishedHandler([this](IAsyncJob *asyncJob){ onJobFinished(asyncJob); }); + const QFuture future = asyncJob->runAsync(); + + m_running.insert(asyncJob, RunningJob{jobRequest, future}); + return true; + } else { + qCDebug(jobsLog) << "Preparation failed for " << jobRequest; + delete asyncJob; + } + } + + return false; +} + +void Jobs::onJobFinished(IAsyncJob *asyncJob) +{ + qCDebug(jobsLog) << "Finishing" << asyncJob->context().jobRequest; + + m_running.remove(asyncJob); + delete asyncJob; + + process(); +} + +int Jobs::runningJobs() const +{ + return m_running.size(); +} + +JobRequests Jobs::queue() const +{ + return m_queue.queue(); +} + +bool Jobs::isJobRunning(const Utf8String &filePath, const Utf8String &projectPartId) const +{ + const auto hasJobRequest = [filePath, projectPartId](const RunningJob &runningJob) { + const JobRequest &jobRequest = runningJob.jobRequest; + return filePath == jobRequest.filePath + && projectPartId == jobRequest.projectPartId; + }; + + return Utils::anyOf(m_running.values(), hasJobRequest); +} + +} // namespace ClangBackEnd diff --git a/src/tools/clangbackend/ipcsource/clangjobs.h b/src/tools/clangbackend/ipcsource/clangjobs.h new file mode 100644 index 00000000000..30e2fef7d81 --- /dev/null +++ b/src/tools/clangbackend/ipcsource/clangjobs.h @@ -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. +** +****************************************************************************/ + +#pragma once + +#include "clangjobqueue.h" + +#include + +#include + +namespace ClangBackEnd { + +class ClangCodeModelClientInterface; +class IAsyncJob; +class ProjectParts; +class TranslationUnits; +class UnsavedFiles; + +class Jobs +{ +public: + struct RunningJob { + JobRequest jobRequest; + QFuture future; + }; + using RunningJobs = QHash; + +public: + Jobs(TranslationUnits &translationUnits, + UnsavedFiles &unsavedFiles, + ProjectParts &projects, + ClangCodeModelClientInterface &client); + ~Jobs(); + + void add(const JobRequest &job); + + JobRequests process(); + +public /*for tests*/: + int runningJobs() const; + JobRequests queue() const; + bool isJobRunning(const Utf8String &filePath, const Utf8String &projectPartId) const; + +private: + JobRequests runJobs(const JobRequests &jobRequest); + bool runJob(const JobRequest &jobRequest); + void onJobFinished(IAsyncJob *asyncJob); + +private: + TranslationUnits &m_translationUnits; + UnsavedFiles &m_unsavedFiles; + ProjectParts &m_projectParts; + ClangCodeModelClientInterface &m_client; + + JobQueue m_queue; + RunningJobs m_running; +}; + +} // namespace ClangBackEnd diff --git a/src/tools/clangbackend/ipcsource/clangrequestdocumentannotationsjob.cpp b/src/tools/clangbackend/ipcsource/clangrequestdocumentannotationsjob.cpp new file mode 100644 index 00000000000..ce9106d5cbb --- /dev/null +++ b/src/tools/clangbackend/ipcsource/clangrequestdocumentannotationsjob.cpp @@ -0,0 +1,95 @@ +/**************************************************************************** +** +** 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 "clangrequestdocumentannotationsjob.h" + +#include +#include +#include + +#include + +namespace ClangBackEnd { + +static RequestDocumentAnnotationsJob::AsyncResult runAsyncHelper( + const TranslationUnitCore &translationUnitCore) +{ + TIME_SCOPE_DURATION("RequestDocumentAnnotationsJobRunner"); + + RequestDocumentAnnotationsJob::AsyncResult asyncResult; + + try { + translationUnitCore.extractDocumentAnnotations(asyncResult.diagnostics, + asyncResult.highlightingMarks, + asyncResult.skippedSourceRanges); + } catch (const std::exception &exception) { + qWarning() << "Error in RequestDocumentAnnotationsJobRunner:" << exception.what(); + } + + return asyncResult; +} + +bool RequestDocumentAnnotationsJob::prepareAsyncRun() +{ + const JobRequest jobRequest = context().jobRequest; + QTC_ASSERT(jobRequest.type == JobRequest::Type::RequestDocumentAnnotations, return false); + + try { + m_pinnedTranslationUnit = context().translationUnitForJobRequest(); + m_pinnedFileContainer = m_pinnedTranslationUnit.fileContainer(); + + const TranslationUnitCore translationUnitCore = m_pinnedTranslationUnit.translationUnitCore(); + setRunner([translationUnitCore]() { + return runAsyncHelper(translationUnitCore); + }); + + } catch (const std::exception &exception) { + qWarning() << "Error in RequestDocumentAnnotationsJob::prepareAsyncRun:" << exception.what(); + return false; + } + + return true; +} + +void RequestDocumentAnnotationsJob::finalizeAsyncRun() +{ + if (context().isDocumentOpen()) { + const AsyncResult result = asyncResult(); + sendAnnotations(result); + } +} + +void RequestDocumentAnnotationsJob::sendAnnotations( + const RequestDocumentAnnotationsJob::AsyncResult &result) +{ + const DocumentAnnotationsChangedMessage message(m_pinnedFileContainer, + result.diagnostics, + result.highlightingMarks, + result.skippedSourceRanges); + + context().client->documentAnnotationsChanged(message); +} + +} // namespace ClangBackEnd diff --git a/src/tools/clangbackend/ipcsource/clangrequestdocumentannotationsjob.h b/src/tools/clangbackend/ipcsource/clangrequestdocumentannotationsjob.h new file mode 100644 index 00000000000..f44c50b1902 --- /dev/null +++ b/src/tools/clangbackend/ipcsource/clangrequestdocumentannotationsjob.h @@ -0,0 +1,60 @@ +/**************************************************************************** +** +** 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 "clangtranslationunit.h" + +#include +#include +#include + +namespace ClangBackEnd { + +struct RequestDocumentAnnotationsJobResult +{ + QVector diagnostics; + QVector highlightingMarks; + QVector skippedSourceRanges; +}; + +class RequestDocumentAnnotationsJob : public AsyncJob +{ +public: + using AsyncResult = RequestDocumentAnnotationsJobResult; + + bool prepareAsyncRun() override; + void finalizeAsyncRun() override; + +private: + void sendAnnotations(const AsyncResult &result); + +private: + TranslationUnit m_pinnedTranslationUnit; + FileContainer m_pinnedFileContainer; +}; + +} // namespace ClangBackEnd diff --git a/src/tools/clangbackend/ipcsource/clangtranslationunit.cpp b/src/tools/clangbackend/ipcsource/clangtranslationunit.cpp index f271401b363..8907a285bdc 100644 --- a/src/tools/clangbackend/ipcsource/clangtranslationunit.cpp +++ b/src/tools/clangbackend/ipcsource/clangtranslationunit.cpp @@ -25,19 +25,10 @@ #include "clangtranslationunit.h" -#include "cursor.h" -#include "clangfilepath.h" #include "clangstring.h" #include "clangunsavedfilesshallowarguments.h" #include "codecompleter.h" -#include "commandlinearguments.h" -#include "diagnosticcontainer.h" -#include "diagnosticset.h" #include "projectpart.h" -#include "skippedsourceranges.h" -#include "sourcelocation.h" -#include "sourcerange.h" -#include "highlightingmarks.h" #include "translationunitfilenotexitexception.h" #include "translationunitisnullexception.h" #include "translationunitparseerrorexception.h" @@ -69,19 +60,22 @@ public: public: TranslationUnits &translationUnits; - time_point lastProjectPartChangeTimePoint; - QSet dependedFilePaths; + + const Utf8String filePath; + const Utf8StringVector fileArguments; + ProjectPart projectPart; - Utf8StringVector fileArguments; - Utf8String filePath; + time_point lastProjectPartChangeTimePoint; + CXTranslationUnit translationUnit = nullptr; - CXErrorCode parseErrorCode = CXError_Success; - int reparseErrorCode = 0; CXIndex index = nullptr; + + QSet dependedFilePaths; + uint documentRevision = 0; + time_point needsToBeReparsedChangeTimePoint; + bool hasParseOrReparseFailed = false; bool needsToBeReparsed = false; - bool hasNewDiagnostics = true; - bool hasNewHighlightingMarks = true; bool isUsedByCurrentEditor = false; bool isVisibleInEditor = false; }; @@ -91,10 +85,11 @@ TranslationUnitData::TranslationUnitData(const Utf8String &filePath, const Utf8StringVector &fileArguments, TranslationUnits &translationUnits) : translationUnits(translationUnits), - lastProjectPartChangeTimePoint(std::chrono::steady_clock::now()), - projectPart(projectPart), + filePath(filePath), fileArguments(fileArguments), - filePath(filePath) + projectPart(projectPart), + lastProjectPartChangeTimePoint(std::chrono::steady_clock::now()), + needsToBeReparsedChangeTimePoint(lastProjectPartChangeTimePoint) { dependedFilePaths.insert(filePath); } @@ -119,29 +114,20 @@ TranslationUnit::TranslationUnit(const Utf8String &filePath, checkIfFileExists(); } -bool TranslationUnit::isNull() const +TranslationUnit::~TranslationUnit() = default; +TranslationUnit::TranslationUnit(const TranslationUnit &) = default; +TranslationUnit &TranslationUnit::operator=(const TranslationUnit &) = default; + +TranslationUnit::TranslationUnit(TranslationUnit &&other) + : d(std::move(other.d)) { - return !d; } -void TranslationUnit::setIsUsedByCurrentEditor(bool isUsedByCurrentEditor) +TranslationUnit &TranslationUnit::operator=(TranslationUnit &&other) { - d->isUsedByCurrentEditor = isUsedByCurrentEditor; -} + d = std::move(other.d); -bool TranslationUnit::isUsedByCurrentEditor() const -{ - return d->isUsedByCurrentEditor; -} - -void TranslationUnit::setIsVisibleInEditor(bool isVisibleInEditor) -{ - d->isVisibleInEditor = isVisibleInEditor; -} - -bool TranslationUnit::isVisibleInEditor() const -{ - return d->isVisibleInEditor; + return *this; } void TranslationUnit::reset() @@ -149,54 +135,16 @@ void TranslationUnit::reset() d.reset(); } -void TranslationUnit::parse() const +bool TranslationUnit::isNull() const { - checkIfNull(); - - const TranslationUnitUpdateInput updateInput = createUpdateInput(); - TranslationUnitUpdateResult result = translationUnitCore().parse(updateInput); - - incorporateUpdaterResult(result); + return !d; } -void TranslationUnit::reparse() const +bool TranslationUnit::isIntact() const { - parse(); // TODO: Remove - - const TranslationUnitUpdateInput updateInput = createUpdateInput(); - TranslationUnitUpdateResult result = translationUnitCore().reparse(updateInput); - - incorporateUpdaterResult(result); -} - -bool TranslationUnit::parseWasSuccessful() const -{ - return d->parseErrorCode == CXError_Success; -} - -bool TranslationUnit::reparseWasSuccessful() const -{ - return d->reparseErrorCode == 0; -} - -CXIndex &TranslationUnit::index() const -{ - checkIfNull(); - - return d->index; -} - -CXTranslationUnit &TranslationUnit::cxTranslationUnit() const -{ - checkIfNull(); - checkIfFileExists(); - - return d->translationUnit; -} - -UnsavedFile TranslationUnit::unsavedFile() const -{ - return unsavedFiles().unsavedFile(filePath()); + return !isNull() + && fileExists() + && !d->hasParseOrReparseFailed; } Utf8String TranslationUnit::filePath() const @@ -213,13 +161,6 @@ Utf8StringVector TranslationUnit::fileArguments() const return d->fileArguments; } -Utf8String TranslationUnit::projectPartId() const -{ - checkIfNull(); - - return d->projectPart.projectPartId(); -} - FileContainer TranslationUnit::fileContainer() const { checkIfNull(); @@ -231,6 +172,13 @@ FileContainer TranslationUnit::fileContainer() const d->documentRevision); } +Utf8String TranslationUnit::projectPartId() const +{ + checkIfNull(); + + return d->projectPart.projectPartId(); +} + const ProjectPart &TranslationUnit::projectPart() const { checkIfNull(); @@ -238,60 +186,79 @@ const ProjectPart &TranslationUnit::projectPart() const return d->projectPart; } -void TranslationUnit::setDocumentRevision(uint revision) +const time_point TranslationUnit::lastProjectPartChangeTimePoint() const { - d->documentRevision = revision; + checkIfNull(); + + return d->lastProjectPartChangeTimePoint; +} + +bool TranslationUnit::isProjectPartOutdated() const +{ + checkIfNull(); + + return d->projectPart.lastChangeTimePoint() >= d->lastProjectPartChangeTimePoint; } uint TranslationUnit::documentRevision() const { + checkIfNull(); + return d->documentRevision; } -const time_point &TranslationUnit::lastProjectPartChangeTimePoint() const +void TranslationUnit::setDocumentRevision(uint revision) { - return d->lastProjectPartChangeTimePoint; + checkIfNull(); + + d->documentRevision = revision; +} + +bool TranslationUnit::isUsedByCurrentEditor() const +{ + checkIfNull(); + + return d->isUsedByCurrentEditor; +} + +void TranslationUnit::setIsUsedByCurrentEditor(bool isUsedByCurrentEditor) +{ + checkIfNull(); + + d->isUsedByCurrentEditor = isUsedByCurrentEditor; +} + +bool TranslationUnit::isVisibleInEditor() const +{ + checkIfNull(); + + return d->isVisibleInEditor; +} + +void TranslationUnit::setIsVisibleInEditor(bool isVisibleInEditor) +{ + checkIfNull(); + + d->isVisibleInEditor = isVisibleInEditor; +} + +time_point TranslationUnit::isNeededReparseChangeTimePoint() const +{ + checkIfNull(); + + return d->needsToBeReparsedChangeTimePoint; } bool TranslationUnit::isNeedingReparse() const { + checkIfNull(); + return d->needsToBeReparsed; } -bool TranslationUnit::hasNewDiagnostics() const -{ - return d->hasNewDiagnostics; -} - -bool TranslationUnit::hasNewHighlightingMarks() const -{ - return d->hasNewHighlightingMarks; -} - -DiagnosticSet TranslationUnit::diagnostics() const -{ - d->hasNewDiagnostics = false; - - return translationUnitCore().diagnostics(); -} - -QVector TranslationUnit::mainFileDiagnostics() const -{ - d->hasNewDiagnostics = false; - - return translationUnitCore().mainFileDiagnostics(); -} - -const QSet &TranslationUnit::dependedFilePaths() const -{ - cxTranslationUnit(); - - return d->dependedFilePaths; -} - void TranslationUnit::setDirtyIfProjectPartIsOutdated() { - if (projectPartIsOutdated()) + if (isProjectPartOutdated()) setDirty(); } @@ -301,11 +268,95 @@ void TranslationUnit::setDirtyIfDependencyIsMet(const Utf8String &filePath) setDirty(); } -HighlightingMarks TranslationUnit::highlightingMarks() const +TranslationUnitUpdateInput TranslationUnit::createUpdateInput() const { - d->hasNewHighlightingMarks = false; + TranslationUnitUpdateInput updateInput; + updateInput.parseNeeded = isProjectPartOutdated(); + updateInput.reparseNeeded = isNeedingReparse(); + updateInput.needsToBeReparsedChangeTimePoint = d->needsToBeReparsedChangeTimePoint; + updateInput.filePath = filePath(); + updateInput.fileArguments = fileArguments(); + updateInput.unsavedFiles = d->translationUnits.unsavedFiles(); + updateInput.projectId = projectPart().projectPartId(); + updateInput.projectArguments = projectPart().arguments(); - return translationUnitCore().highlightingMarks(); + return updateInput; +} + +TranslationUnitUpdater TranslationUnit::createUpdater() const +{ + const TranslationUnitUpdateInput updateInput = createUpdateInput(); + TranslationUnitUpdater updater(d->index, d->translationUnit, updateInput); + + return updater; +} + +void TranslationUnit::setHasParseOrReparseFailed(bool hasFailed) +{ + d->hasParseOrReparseFailed = hasFailed; +} + +void TranslationUnit::incorporateUpdaterResult(const TranslationUnitUpdateResult &result) const +{ + d->hasParseOrReparseFailed = result.hasParseOrReparseFailed; + if (d->hasParseOrReparseFailed) { + d->needsToBeReparsed = false; + return; + } + + if (result.parseTimePointIsSet) + d->lastProjectPartChangeTimePoint = result.parseTimePoint; + + if (result.parseTimePointIsSet || result.reparsed) + d->dependedFilePaths = result.dependedOnFilePaths; + + d->translationUnits.addWatchedFiles(d->dependedFilePaths); + + if (result.reparsed + && result.needsToBeReparsedChangeTimePoint == d->needsToBeReparsedChangeTimePoint) { + d->needsToBeReparsed = false; + } +} + +TranslationUnitCore TranslationUnit::translationUnitCore() const +{ + checkIfNull(); + + return TranslationUnitCore(d->filePath, d->index, d->translationUnit); +} + +void TranslationUnit::parse() const +{ + checkIfNull(); + + const TranslationUnitUpdateInput updateInput = createUpdateInput(); + TranslationUnitUpdateResult result = translationUnitCore().parse(updateInput); + + incorporateUpdaterResult(result); +} + +void TranslationUnit::reparse() const +{ + checkIfNull(); + + const TranslationUnitUpdateInput updateInput = createUpdateInput(); + TranslationUnitUpdateResult result = translationUnitCore().reparse(updateInput); + + incorporateUpdaterResult(result); +} + +const QSet TranslationUnit::dependedFilePaths() const +{ + checkIfNull(); + checkIfFileExists(); + + return d->dependedFilePaths; +} + +void TranslationUnit::setDirty() +{ + d->needsToBeReparsedChangeTimePoint = std::chrono::steady_clock::now(); + d->needsToBeReparsed = true; } void TranslationUnit::checkIfNull() const @@ -320,16 +371,9 @@ void TranslationUnit::checkIfFileExists() const throw TranslationUnitFileNotExitsException(d->filePath); } -bool TranslationUnit::projectPartIsOutdated() const +bool TranslationUnit::fileExists() const { - return d->projectPart.lastChangeTimePoint() >= d->lastProjectPartChangeTimePoint; -} - -void TranslationUnit::setDirty() -{ - d->needsToBeReparsed = true; - d->hasNewDiagnostics = true; - d->hasNewHighlightingMarks = true; + return QFileInfo::exists(d->filePath.toString()); } bool TranslationUnit::isMainFileAndExistsOrIsOtherFile(const Utf8String &filePath) const @@ -340,99 +384,6 @@ bool TranslationUnit::isMainFileAndExistsOrIsOtherFile(const Utf8String &filePat return true; } -void TranslationUnit::checkParseErrorCode() const -{ - if (!parseWasSuccessful()) { - throw TranslationUnitParseErrorException(d->filePath, - d->projectPart.projectPartId(), - d->parseErrorCode); - } -} - -bool TranslationUnit::fileExists() const -{ - return QFileInfo::exists(d->filePath.toString()); -} - -TranslationUnitUpdateInput TranslationUnit::createUpdateInput() const -{ - TranslationUnitUpdateInput updateInput; - updateInput.reparseNeeded = isNeedingReparse(); - updateInput.parseNeeded = projectPartIsOutdated(); - updateInput.filePath = filePath(); - updateInput.fileArguments = fileArguments(); - updateInput.unsavedFiles = unsavedFiles(); - updateInput.projectId = projectPart().projectPartId(); - updateInput.projectArguments = projectPart().arguments(); - - return updateInput; -} - -TranslationUnitUpdater TranslationUnit::createUpdater() const -{ - const TranslationUnitUpdateInput updateInput = createUpdateInput(); - TranslationUnitUpdater updater(index(), d->translationUnit, updateInput); - - return updater; -} - -void TranslationUnit::incorporateUpdaterResult(const TranslationUnitUpdateResult &result) const -{ - if (result.parseTimePointIsSet) - d->lastProjectPartChangeTimePoint = result.parseTimePoint; - - d->dependedFilePaths = result.dependedOnFilePaths; - d->translationUnits.addWatchedFiles(d->dependedFilePaths); - - if (result.reparsed) - d->needsToBeReparsed = false; -} - -bool TranslationUnit::isIntact() const -{ - return !isNull() - && fileExists() - && parseWasSuccessful() - && reparseWasSuccessful(); -} - -CommandLineArguments TranslationUnit::commandLineArguments() const -{ - return createUpdater().commandLineArguments(); -} - -TranslationUnitCore TranslationUnit::translationUnitCore() const -{ - return TranslationUnitCore(d->filePath, d->index, d->translationUnit); -} - -uint TranslationUnit::unsavedFilesCount() const -{ - return unsavedFiles().count(); -} - -UnsavedFiles TranslationUnit::unsavedFiles() const -{ - return d->translationUnits.unsavedFiles(); -} - -TranslationUnit::~TranslationUnit() = default; - -TranslationUnit::TranslationUnit(const TranslationUnit &) = default; -TranslationUnit &TranslationUnit::operator=(const TranslationUnit &) = default; - -TranslationUnit::TranslationUnit(TranslationUnit &&other) - : d(std::move(other.d)) -{ -} - -TranslationUnit &TranslationUnit::operator=(TranslationUnit &&other) -{ - d = std::move(other.d); - - return *this; -} - bool operator==(const TranslationUnit &first, const TranslationUnit &second) { return first.filePath() == second.filePath() && first.projectPartId() == second.projectPartId(); diff --git a/src/tools/clangbackend/ipcsource/clangtranslationunit.h b/src/tools/clangbackend/ipcsource/clangtranslationunit.h index 36e03fd4dda..e3ec05fa87d 100644 --- a/src/tools/clangbackend/ipcsource/clangtranslationunit.h +++ b/src/tools/clangbackend/ipcsource/clangtranslationunit.h @@ -33,11 +33,11 @@ #include +#include #include #include #include -#include class Utf8String; @@ -46,20 +46,9 @@ namespace ClangBackEnd { class TranslationUnitCore; class TranslationUnitData; class TranslationUnitUpdateResult; -class CodeCompleter; -class UnsavedFile; -class UnsavedFiles; class ProjectPart; -class DiagnosticContainer; -class DiagnosticSet; class FileContainer; -class HighlightingMarks; class TranslationUnits; -class CommandLineArguments; -class Cursor; -class SourceLocation; -class SourceRange; -class SkippedSourceRanges; using time_point = std::chrono::steady_clock::time_point; @@ -85,76 +74,54 @@ public: TranslationUnit(TranslationUnit &&cxTranslationUnit); TranslationUnit &operator=(TranslationUnit &&cxTranslationUnit); - bool isNull() const; - - void setIsUsedByCurrentEditor(bool isUsedByCurrentEditor); - bool isUsedByCurrentEditor() const; - - void setIsVisibleInEditor(bool isVisibleInEditor); - bool isVisibleInEditor() const; - void reset(); - void parse() const; - void reparse() const; + bool isNull() const; bool isIntact() const; - CXIndex &index() const; - - CXTranslationUnit &cxTranslationUnit() const; - - UnsavedFile unsavedFile() const; - UnsavedFiles unsavedFiles() const; - uint unsavedFilesCount() const; - Utf8String filePath() const; Utf8StringVector fileArguments() const; - Utf8String projectPartId() const; FileContainer fileContainer() const; + + Utf8String projectPartId() const; const ProjectPart &projectPart() const; + const time_point lastProjectPartChangeTimePoint() const; + bool isProjectPartOutdated() const; - void setDocumentRevision(uint revision); uint documentRevision() const; + void setDocumentRevision(uint revision); - const time_point &lastProjectPartChangeTimePoint() const; + bool isUsedByCurrentEditor() const; + void setIsUsedByCurrentEditor(bool isUsedByCurrentEditor); + + bool isVisibleInEditor() const; + void setIsVisibleInEditor(bool isVisibleInEditor); bool isNeedingReparse() const; - - // TODO: Remove the following two - bool hasNewDiagnostics() const; - bool hasNewHighlightingMarks() const; - - // TODO: Remove the following two - DiagnosticSet diagnostics() const; - QVector mainFileDiagnostics() const; - - const QSet &dependedFilePaths() const; - void setDirtyIfProjectPartIsOutdated(); void setDirtyIfDependencyIsMet(const Utf8String &filePath); - CommandLineArguments commandLineArguments() const; - - // TODO: Remove - HighlightingMarks highlightingMarks() const; + TranslationUnitUpdateInput createUpdateInput() const; + void incorporateUpdaterResult(const TranslationUnitUpdateResult &result) const; TranslationUnitCore translationUnitCore() const; - bool projectPartIsOutdated() const; +public: // for tests + void parse() const; + void reparse() const; + const QSet dependedFilePaths() const; + TranslationUnitUpdater createUpdater() const; + void setHasParseOrReparseFailed(bool hasFailed); + time_point isNeededReparseChangeTimePoint() const; private: void setDirty(); + void checkIfNull() const; void checkIfFileExists() const; - bool isMainFileAndExistsOrIsOtherFile(const Utf8String &filePath) const; - void checkParseErrorCode() const; - bool parseWasSuccessful() const; - bool reparseWasSuccessful() const; - bool fileExists() const; - TranslationUnitUpdateInput createUpdateInput() const; - TranslationUnitUpdater createUpdater() const; - void incorporateUpdaterResult(const TranslationUnitUpdateResult &result) const; + bool fileExists() const; + bool isMainFileAndExistsOrIsOtherFile(const Utf8String &filePath) const; private: mutable std::shared_ptr d; diff --git a/src/tools/clangbackend/ipcsource/clangtranslationunitupdater.cpp b/src/tools/clangbackend/ipcsource/clangtranslationunitupdater.cpp index 9a09170c43c..c907b6705d5 100644 --- a/src/tools/clangbackend/ipcsource/clangtranslationunitupdater.cpp +++ b/src/tools/clangbackend/ipcsource/clangtranslationunitupdater.cpp @@ -28,8 +28,6 @@ #include "clangfilepath.h" #include "clangstring.h" #include "clangunsavedfilesshallowarguments.h" -#include "translationunitparseerrorexception.h" -#include "translationunitreparseerrorexception.h" #include @@ -105,11 +103,15 @@ void TranslationUnitUpdater::createTranslationUnitIfNeeded() defaultParseOptions(), &m_cxTranslationUnit); - checkParseErrorCode(); - updateIncludeFilePaths(); - updateLastProjectPartChangeTimePoint(); + if (parseWasSuccessful()) { + updateIncludeFilePaths(); + updateLastProjectPartChangeTimePoint(); + } else { + qWarning() << "Parsing" << m_in.filePath << "failed:" << m_parseErrorCode; + m_out.hasParseOrReparseFailed = true; + } } } @@ -129,11 +131,16 @@ void TranslationUnitUpdater::reparse() unsaved.data(), clang_defaultReparseOptions(m_cxTranslationUnit)); - checkReparseErrorCode(); - updateIncludeFilePaths(); + if (reparseWasSuccessful()) { + updateIncludeFilePaths(); - m_out.reparsed = true; + m_out.reparsed = true; + m_out.needsToBeReparsedChangeTimePoint = m_in.needsToBeReparsedChangeTimePoint; + } else { + qWarning() << "Reparsing" << m_in.filePath << "failed:" << m_reparseErrorCode; + m_out.hasParseOrReparseFailed = true; + } } void TranslationUnitUpdater::updateIncludeFilePaths() @@ -146,24 +153,6 @@ void TranslationUnitUpdater::updateIncludeFilePaths() const_cast(this)); } -void TranslationUnitUpdater::checkParseErrorCode() const -{ - if (!parseWasSuccessful()) { - throw TranslationUnitParseErrorException(m_in.filePath, - m_in.projectId, - m_parseErrorCode); - } -} - -void TranslationUnitUpdater::checkReparseErrorCode() const -{ - if (!reparseWasSuccessful()) { - throw TranslationUnitReparseErrorException(m_in.filePath, - m_in.projectId, - m_reparseErrorCode); - } -} - uint TranslationUnitUpdater::defaultParseOptions() { return CXTranslationUnit_CacheCompletionResults diff --git a/src/tools/clangbackend/ipcsource/clangtranslationunitupdater.h b/src/tools/clangbackend/ipcsource/clangtranslationunitupdater.h index a7a9d88f23f..cb7a06b7f6e 100644 --- a/src/tools/clangbackend/ipcsource/clangtranslationunitupdater.h +++ b/src/tools/clangbackend/ipcsource/clangtranslationunitupdater.h @@ -41,9 +41,10 @@ using time_point = std::chrono::steady_clock::time_point; class TranslationUnitUpdateInput { public: - bool reparseNeeded = false; bool parseNeeded = false; + bool reparseNeeded = false; + time_point needsToBeReparsedChangeTimePoint; Utf8String filePath; Utf8StringVector fileArguments; @@ -55,9 +56,12 @@ public: class TranslationUnitUpdateResult { public: + bool hasParseOrReparseFailed = false; + bool parseTimePointIsSet = false; time_point parseTimePoint; + time_point needsToBeReparsedChangeTimePoint; bool reparsed = false; QSet dependedOnFilePaths; @@ -100,9 +104,6 @@ private: bool parseWasSuccessful() const; bool reparseWasSuccessful() const; - void checkReparseErrorCode() const; - void checkParseErrorCode() const; - private: CXIndex &m_cxIndex; CXTranslationUnit &m_cxTranslationUnit; diff --git a/src/tools/clangbackend/ipcsource/clangupdatedocumentannotationsjob.cpp b/src/tools/clangbackend/ipcsource/clangupdatedocumentannotationsjob.cpp new file mode 100644 index 00000000000..2e61c50b92d --- /dev/null +++ b/src/tools/clangbackend/ipcsource/clangupdatedocumentannotationsjob.cpp @@ -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 "clangupdatedocumentannotationsjob.h" + +#include +#include +#include + +#include + +namespace ClangBackEnd { + +static UpdateDocumentAnnotationsJob::AsyncResult runAsyncHelper( + const TranslationUnitCore &translationUnitCore, + const TranslationUnitUpdateInput &translationUnitUpdatInput) +{ + TIME_SCOPE_DURATION("UpdateDocumentAnnotationsJobRunner"); + + UpdateDocumentAnnotationsJob::AsyncResult asyncResult; + + try { + // Update + asyncResult.updateResult = translationUnitCore.update(translationUnitUpdatInput); + + // Collect + translationUnitCore.extractDocumentAnnotations(asyncResult.diagnostics, + asyncResult.highlightingMarks, + asyncResult.skippedSourceRanges); + + } catch (const std::exception &exception) { + qWarning() << "Error in UpdateDocumentAnnotationsJobRunner:" << exception.what(); + } + + return asyncResult; +} + +bool UpdateDocumentAnnotationsJob::prepareAsyncRun() +{ + const JobRequest jobRequest = context().jobRequest; + QTC_ASSERT(jobRequest.type == JobRequest::Type::UpdateDocumentAnnotations, return false); + + try { + m_pinnedTranslationUnit = context().translationUnitForJobRequest(); + m_pinnedFileContainer = m_pinnedTranslationUnit.fileContainer(); + + const TranslationUnitCore translationUnitCore = m_pinnedTranslationUnit.translationUnitCore(); + const TranslationUnitUpdateInput updateInput = m_pinnedTranslationUnit.createUpdateInput(); + setRunner([translationUnitCore, updateInput]() { + return runAsyncHelper(translationUnitCore, updateInput); + }); + + } catch (const std::exception &exception) { + qWarning() << "Error in UpdateDocumentAnnotationsJob::prepareAsyncRun:" << exception.what(); + return false; + } + + return true; +} + +void UpdateDocumentAnnotationsJob::finalizeAsyncRun() +{ + if (!context().isOutdated()) { + const AsyncResult result = asyncResult(); + + incorporateUpdaterResult(result); + sendAnnotations(result); + } +} + +void UpdateDocumentAnnotationsJob::incorporateUpdaterResult(const AsyncResult &result) +{ + m_pinnedTranslationUnit.incorporateUpdaterResult(result.updateResult); +} + +void UpdateDocumentAnnotationsJob::sendAnnotations(const AsyncResult &result) +{ + const DocumentAnnotationsChangedMessage message(m_pinnedFileContainer, + result.diagnostics, + result.highlightingMarks, + result.skippedSourceRanges); + + context().client->documentAnnotationsChanged(message); +} + +} // namespace ClangBackEnd + diff --git a/src/tools/clangbackend/ipcsource/clangupdatedocumentannotationsjob.h b/src/tools/clangbackend/ipcsource/clangupdatedocumentannotationsjob.h new file mode 100644 index 00000000000..643df075712 --- /dev/null +++ b/src/tools/clangbackend/ipcsource/clangupdatedocumentannotationsjob.h @@ -0,0 +1,64 @@ +/**************************************************************************** +** +** 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 "clangtranslationunit.h" +#include "clangtranslationunitupdater.h" + +#include +#include +#include + +namespace ClangBackEnd { + +struct UpdateDocumentAnnotationsJobResult +{ + TranslationUnitUpdateResult updateResult; + + QVector diagnostics; + QVector highlightingMarks; + QVector skippedSourceRanges; +}; + +class UpdateDocumentAnnotationsJob : public AsyncJob +{ +public: + using AsyncResult = UpdateDocumentAnnotationsJobResult; + + bool prepareAsyncRun() override; + void finalizeAsyncRun() override; + +private: + void incorporateUpdaterResult(const AsyncResult &result); + void sendAnnotations(const AsyncResult &result); + +private: + TranslationUnit m_pinnedTranslationUnit; + FileContainer m_pinnedFileContainer; +}; + +} // namespace ClangBackEnd diff --git a/src/tools/clangbackend/ipcsource/translationunits.cpp b/src/tools/clangbackend/ipcsource/translationunits.cpp index 2e90e0bf81e..5fde27b8b90 100644 --- a/src/tools/clangbackend/ipcsource/translationunits.cpp +++ b/src/tools/clangbackend/ipcsource/translationunits.cpp @@ -128,14 +128,10 @@ const TranslationUnit &TranslationUnits::translationUnit(const FileContainer &fi return translationUnit(fileContainer.filePath(), fileContainer.projectPartId()); } -bool TranslationUnits::hasTranslationUnit(const Utf8String &filePath) const +bool TranslationUnits::hasTranslationUnit(const Utf8String &filePath, + const Utf8String &projectPartId) const { - return std::any_of(translationUnits_.cbegin(), - translationUnits_.cend(), - [&filePath] (const TranslationUnit &translationUnit) - { - return translationUnit.filePath() == filePath; - }); + return hasTranslationUnit(FileContainer(filePath, projectPartId)); } const std::vector &TranslationUnits::translationUnits() const @@ -171,75 +167,6 @@ void TranslationUnits::setTranslationUnitsDirtyIfProjectPartChanged() translationUnit.setDirtyIfProjectPartIsOutdated(); } -DocumentAnnotationsSendState TranslationUnits::sendDocumentAnnotations() -{ - auto documentAnnotationsSendState = sendDocumentAnnotationsForCurrentEditor(); - if (documentAnnotationsSendState == DocumentAnnotationsSendState::NoDocumentAnnotationsSent) - documentAnnotationsSendState = sendDocumentAnnotationsForVisibleEditors(); - - return documentAnnotationsSendState; -} - -template -DocumentAnnotationsSendState TranslationUnits::sendDocumentAnnotations(Predicate predicate) -{ - auto foundTranslationUnit = std::find_if(translationUnits_.begin(), - translationUnits_.end(), - predicate); - - if (foundTranslationUnit != translationUnits().end()) { - sendDocumentAnnotations(*foundTranslationUnit); - return DocumentAnnotationsSendState::MaybeThereAreDocumentAnnotations; - } - - return DocumentAnnotationsSendState::NoDocumentAnnotationsSent; -} - -namespace { - -bool translationUnitHasNewDocumentAnnotations(const TranslationUnit &translationUnit) -{ - return translationUnit.isIntact() - && (translationUnit.hasNewDiagnostics() - || translationUnit.hasNewHighlightingMarks()); -} - -} - -DocumentAnnotationsSendState TranslationUnits::sendDocumentAnnotationsForCurrentEditor() -{ - auto hasDocumentAnnotationsForCurrentEditor = [] (const TranslationUnit &translationUnit) { - return translationUnit.isUsedByCurrentEditor() - && translationUnitHasNewDocumentAnnotations(translationUnit); - }; - - return sendDocumentAnnotations(hasDocumentAnnotationsForCurrentEditor); -} - -DocumentAnnotationsSendState TranslationUnits::sendDocumentAnnotationsForVisibleEditors() -{ - auto hasDocumentAnnotationsForVisibleEditor = [] (const TranslationUnit &translationUnit) { - return translationUnit.isVisibleInEditor() - && translationUnitHasNewDocumentAnnotations(translationUnit); - }; - - return sendDocumentAnnotations(hasDocumentAnnotationsForVisibleEditor); -} - -DocumentAnnotationsSendState TranslationUnits::sendDocumentAnnotationsForAll() -{ - auto hasDocumentAnnotations = [] (const TranslationUnit &translationUnit) { - return translationUnitHasNewDocumentAnnotations(translationUnit); - }; - - return sendDocumentAnnotations(hasDocumentAnnotations); -} - -void TranslationUnits::setSendDocumentAnnotationsCallback(SendDocumentAnnotationsCallback &&callback) -{ - sendDocumentAnnotationsCallback = std::move(callback); -} - QVector TranslationUnits::newerFileContainers(const QVector &fileContainers) const { QVector newerContainers; @@ -367,18 +294,6 @@ void TranslationUnits::checkIfTranslationUnitsForFilePathsDoesExists(const QVect } } -void TranslationUnits::sendDocumentAnnotations(const TranslationUnit &translationUnit) -{ - if (sendDocumentAnnotationsCallback) { - DocumentAnnotationsChangedMessage message(translationUnit.fileContainer(), - translationUnit.mainFileDiagnostics(), - translationUnit.highlightingMarks().toHighlightingMarksContainers(), - translationUnit.translationUnitCore().skippedSourceRanges().toSourceRangeContainers()); - - sendDocumentAnnotationsCallback(std::move(message)); - } -} - void TranslationUnits::removeTranslationUnits(const QVector &fileContainers) { QVector processedFileContainers = fileContainers; diff --git a/src/tools/clangbackend/ipcsource/translationunits.h b/src/tools/clangbackend/ipcsource/translationunits.h index 4081efd7abf..5607ebb6653 100644 --- a/src/tools/clangbackend/ipcsource/translationunits.h +++ b/src/tools/clangbackend/ipcsource/translationunits.h @@ -32,27 +32,15 @@ #include -#include #include namespace ClangBackEnd { class ProjectParts; class UnsavedFiles; -class DocumentAnnotationsChangedMessage; - -enum class DocumentAnnotationsSendState -{ - NoDocumentAnnotationsSent, - MaybeThereAreDocumentAnnotations, -}; class TranslationUnits { -public: - using SendDocumentAnnotationsCallback - = std::function; - public: TranslationUnits(ProjectParts &projectParts, UnsavedFiles &unsavedFiles); @@ -65,7 +53,8 @@ public: const TranslationUnit &translationUnit(const Utf8String &filePath, const Utf8String &projectPartId) const; const TranslationUnit &translationUnit(const FileContainer &fileContainer) const; - bool hasTranslationUnit(const Utf8String &filePath) const; + bool hasTranslationUnit(const Utf8String &filePath, const Utf8String &projectPartId) const; + bool hasTranslationUnitWithFilePath(const Utf8String &filePath) const; const std::vector &translationUnits() const; @@ -77,13 +66,6 @@ public: void updateTranslationUnitsWithChangedDependencies(const QVector &fileContainers); void setTranslationUnitsDirtyIfProjectPartChanged(); - DocumentAnnotationsSendState sendDocumentAnnotationsForCurrentEditor(); - DocumentAnnotationsSendState sendDocumentAnnotationsForVisibleEditors(); - DocumentAnnotationsSendState sendDocumentAnnotationsForAll(); - DocumentAnnotationsSendState sendDocumentAnnotations(); - - void setSendDocumentAnnotationsCallback(SendDocumentAnnotationsCallback &&callback); - QVector newerFileContainers(const QVector &fileContainers) const; const ClangFileSystemWatcher *clangFileSystemWatcher() const; @@ -95,7 +77,6 @@ private: std::vector findAllTranslationUnitWithFilePath(const Utf8String &filePath); std::vector::const_iterator findTranslationUnit(const Utf8String &filePath, const Utf8String &projectPartId) const; bool hasTranslationUnit(const FileContainer &fileContainer) const; - bool hasTranslationUnitWithFilePath(const Utf8String &filePath) const; void checkIfProjectPartExists(const Utf8String &projectFileName) const; void checkIfProjectPartsExists(const QVector &fileContainers) const; void checkIfTranslationUnitsDoesNotExists(const QVector &fileContainers) const; @@ -103,13 +84,8 @@ private: void removeTranslationUnits(const QVector &fileContainers); - template - DocumentAnnotationsSendState sendDocumentAnnotations(Predicate predicate); - void sendDocumentAnnotations(const TranslationUnit &translationUnit); - private: ClangFileSystemWatcher fileSystemWatcher; - SendDocumentAnnotationsCallback sendDocumentAnnotationsCallback; std::vector translationUnits_; ProjectParts &projectParts; UnsavedFiles &unsavedFiles_; diff --git a/tests/unit/unittest/clangasyncjobtest.cpp b/tests/unit/unittest/clangasyncjobtest.cpp new file mode 100644 index 00000000000..beffc7e7801 --- /dev/null +++ b/tests/unit/unittest/clangasyncjobtest.cpp @@ -0,0 +1,70 @@ +/**************************************************************************** +** +** 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 "clangasyncjobtest.h" +#include "testutils.h" + +#include + +using namespace ClangBackEnd; + +void ClangAsyncJobTest::BaseSetUp(ClangBackEnd::JobRequest::Type jobRequestType, + IAsyncJob &asyncJob) +{ + projects.createOrUpdate({ProjectPartContainer(projectPartId)}); + + const QVector fileContainer{FileContainer(filePath, projectPartId)}; + translationUnit = translationUnits.create(fileContainer).front(); + translationUnits.setVisibleInEditors({filePath}); + translationUnits.setUsedByCurrentEditor(filePath); + + jobRequest = createJobRequest(filePath, jobRequestType); + jobContext = JobContext(jobRequest, &translationUnits, &unsavedFiles, &dummyIpcClient); + jobContextWithMockClient = JobContext(jobRequest, &translationUnits, &unsavedFiles, &mockIpcClient); + asyncJob.setFinishedHandler([](IAsyncJob *){}); +} + +JobRequest ClangAsyncJobTest::createJobRequest(const Utf8String &filePath, + JobRequest::Type type) const +{ + JobRequest jobRequest; + jobRequest.type = type; + jobRequest.requirements = JobRequest::requirementsForType(type); + jobRequest.filePath = filePath; + jobRequest.projectPartId = projectPartId; + jobRequest.unsavedFilesChangeTimePoint = unsavedFiles.lastChangeTimePoint(); + jobRequest.documentRevision = translationUnit.documentRevision(); + jobRequest.projectChangeTimePoint = projects.project(projectPartId).lastChangeTimePoint(); + + return jobRequest; +} + +bool ClangAsyncJobTest::waitUntilJobFinished(const ClangBackEnd::IAsyncJob &asyncJob, + int timeOutInMs) const +{ + const auto isOnFinishedSlotExecuted = [&asyncJob](){ return asyncJob.isFinished(); }; + + return TestUtils::processEventsUntilTrue(isOnFinishedSlotExecuted, timeOutInMs); +} diff --git a/tests/unit/unittest/clangasyncjobtest.h b/tests/unit/unittest/clangasyncjobtest.h new file mode 100644 index 00000000000..58f1131452f --- /dev/null +++ b/tests/unit/unittest/clangasyncjobtest.h @@ -0,0 +1,70 @@ +/**************************************************************************** +** +** 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 "dummyclangipcclient.h" +#include "mockclangcodemodelclient.h" +#include "clangiasyncjob.h" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include "gtest-qt-printing.h" + +class ClangAsyncJobTest : public ::testing::Test +{ +protected: + void BaseSetUp(ClangBackEnd::JobRequest::Type jobRequestType, + ClangBackEnd::IAsyncJob &asyncJob); + + ClangBackEnd::JobRequest createJobRequest(const Utf8String &filePath, + ClangBackEnd::JobRequest::Type type) const; + + bool waitUntilJobFinished(const ClangBackEnd::IAsyncJob &asyncJob, + int timeOutInMs = 10000) const; + +protected: + ClangBackEnd::ProjectParts projects; + ClangBackEnd::UnsavedFiles unsavedFiles; + ClangBackEnd::TranslationUnits translationUnits{projects, unsavedFiles}; + ClangBackEnd::TranslationUnit translationUnit; + + MockClangCodeModelClient mockIpcClient; + DummyIpcClient dummyIpcClient; + + Utf8String filePath{Utf8StringLiteral(TESTDATA_DIR"/translationunits.cpp")}; + Utf8String projectPartId{Utf8StringLiteral("/path/to/projectfile")}; + + ClangBackEnd::JobRequest jobRequest; + ClangBackEnd::JobContext jobContext; + ClangBackEnd::JobContext jobContextWithMockClient; +}; diff --git a/tests/unit/unittest/clangcodecompleteresultstest.cpp b/tests/unit/unittest/clangcodecompleteresultstest.cpp index c24aa54e719..d1563c7116c 100644 --- a/tests/unit/unittest/clangcodecompleteresultstest.cpp +++ b/tests/unit/unittest/clangcodecompleteresultstest.cpp @@ -29,6 +29,7 @@ #include #include #include +#include #include #include #include @@ -69,7 +70,7 @@ TEST(ClangCodeCompleteResults, GetData) Utf8String nativeFilePath = FilePath::toNativeSeparators(translationUnit.filePath()); translationUnit.parse(); CXCodeCompleteResults *cxCodeCompleteResults = - clang_codeCompleteAt(translationUnit.cxTranslationUnit(), + clang_codeCompleteAt(translationUnit.translationUnitCore().cxTranslationUnit(), nativeFilePath.constData(), 49, 1, 0, 0, completionOptions()); @@ -101,7 +102,7 @@ TEST(ClangCodeCompleteResults, MoveClangCodeCompleteResults) Utf8String nativeFilePath = FilePath::toNativeSeparators(translationUnit.filePath()); translationUnit.parse(); CXCodeCompleteResults *cxCodeCompleteResults = - clang_codeCompleteAt(translationUnit.cxTranslationUnit(), + clang_codeCompleteAt(translationUnit.translationUnitCore().cxTranslationUnit(), nativeFilePath.constData(), 49, 1, 0, 0, completionOptions()); diff --git a/tests/unit/unittest/clangcompletecodejobtest.cpp b/tests/unit/unittest/clangcompletecodejobtest.cpp new file mode 100644 index 00000000000..19ab06553a0 --- /dev/null +++ b/tests/unit/unittest/clangcompletecodejobtest.cpp @@ -0,0 +1,85 @@ +/**************************************************************************** +** +** 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 "clangasyncjobtest.h" + +#include + +using namespace ClangBackEnd; + +using testing::_; + +namespace { + +class CompleteCodeJob : public ClangAsyncJobTest +{ +protected: + void SetUp() override { BaseSetUp(JobRequest::Type::CompleteCode, job); } + +protected: + ClangBackEnd::CompleteCodeJob job; +}; + +TEST_F(CompleteCodeJob, PrepareAsyncRun) +{ + job.setContext(jobContext); + + ASSERT_TRUE(job.prepareAsyncRun()); +} + +TEST_F(CompleteCodeJob, RunAsync) +{ + job.setContext(jobContext); + job.prepareAsyncRun(); + + job.runAsync(); + + ASSERT_TRUE(waitUntilJobFinished(job)); +} + +TEST_F(CompleteCodeJob, SendAnnotations) +{ + job.setContext(jobContextWithMockClient); + job.prepareAsyncRun(); + EXPECT_CALL(mockIpcClient, codeCompleted(_)).Times(1); + + job.runAsync(); + + ASSERT_TRUE(waitUntilJobFinished(job)); +} + +TEST_F(CompleteCodeJob, DontSendCompletionsIfDocumentWasClosed) +{ + job.setContext(jobContextWithMockClient); + job.prepareAsyncRun(); + EXPECT_CALL(mockIpcClient, codeCompleted(_)).Times(0); + + job.runAsync(); + translationUnits.remove({FileContainer{filePath, projectPartId}}); + + ASSERT_TRUE(waitUntilJobFinished(job)); +} + +} // anonymous diff --git a/tests/unit/unittest/clangcreateinitialdocumentpreamblejobtest.cpp b/tests/unit/unittest/clangcreateinitialdocumentpreamblejobtest.cpp new file mode 100644 index 00000000000..d084dd7d359 --- /dev/null +++ b/tests/unit/unittest/clangcreateinitialdocumentpreamblejobtest.cpp @@ -0,0 +1,62 @@ +/**************************************************************************** +** +** 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 "clangasyncjobtest.h" + +#include + +using namespace ClangBackEnd; + +namespace { + +class CreateInitialDocumentPreambleJob : public ClangAsyncJobTest +{ +protected: + void SetUp() override { BaseSetUp(JobRequest::Type::CreateInitialDocumentPreamble, job); } + +protected: + ClangBackEnd::CreateInitialDocumentPreambleJob job; +}; + +TEST_F(CreateInitialDocumentPreambleJob, PrepareAsyncRun) +{ + job.setContext(jobContext); + + ASSERT_TRUE(job.prepareAsyncRun()); +} + +TEST_F(CreateInitialDocumentPreambleJob, RunAsync) +{ + translationUnit.parse(); + translationUnit.setDirtyIfDependencyIsMet(translationUnit.filePath()); + job.setContext(jobContext); + job.prepareAsyncRun(); + + job.runAsync(); + + ASSERT_TRUE(waitUntilJobFinished(job)); +} + +} // anonymous diff --git a/tests/unit/unittest/clangipcservertest.cpp b/tests/unit/unittest/clangipcservertest.cpp index 1689e85fa7c..84ca73c0a66 100644 --- a/tests/unit/unittest/clangipcservertest.cpp +++ b/tests/unit/unittest/clangipcservertest.cpp @@ -24,6 +24,7 @@ ****************************************************************************/ #include "mockclangcodemodelclient.h" +#include "testutils.h" #include #include @@ -33,20 +34,12 @@ #include #include -#include #include -#include #include #include #include #include -#include -#include -#include -#include -#include -#include #include #include @@ -63,23 +56,7 @@ using testing::_; namespace { -using ClangBackEnd::RegisterTranslationUnitForEditorMessage; -using ClangBackEnd::UnregisterTranslationUnitsForEditorMessage; -using ClangBackEnd::RegisterProjectPartsForEditorMessage; -using ClangBackEnd::UnregisterProjectPartsForEditorMessage; -using ClangBackEnd::CompleteCodeMessage; -using ClangBackEnd::CodeCompletedMessage; -using ClangBackEnd::CodeCompletion; -using ClangBackEnd::FileContainer; -using ClangBackEnd::ProjectPartContainer; -using ClangBackEnd::TranslationUnitDoesNotExistMessage; -using ClangBackEnd::ProjectPartsDoNotExistMessage; -using ClangBackEnd::UpdateTranslationUnitsForEditorMessage; -using ClangBackEnd::UpdateVisibleTranslationUnitsMessage; -using ClangBackEnd::RequestDocumentAnnotationsMessage; -using ClangBackEnd::DocumentAnnotationsChangedMessage; -using ClangBackEnd::HighlightingMarkContainer; -using ClangBackEnd::HighlightingTypes; +using namespace ClangBackEnd; MATCHER_P5(HasDirtyTranslationUnit, filePath, @@ -100,15 +77,6 @@ MATCHER_P5(HasDirtyTranslationUnit, auto translationUnit = translationUnits.translationUnit(filePath, projectPartId); if (translationUnit.documentRevision() == documentRevision) { - - if (translationUnit.hasNewDiagnostics() && !hasNewDiagnostics) { - *result_listener << "hasNewDiagnostics is true"; - return false; - } else if (!translationUnit.hasNewDiagnostics() && hasNewDiagnostics) { - *result_listener << "hasNewDiagnostics is false"; - return false; - } - if (translationUnit.isNeedingReparse() && !isNeedingReparse) { *result_listener << "isNeedingReparse is true"; return false; @@ -129,17 +97,60 @@ MATCHER_P5(HasDirtyTranslationUnit, } } - class ClangClangCodeModelServer : public ::testing::Test { protected: void SetUp() override; + void TearDown() override; + +protected: + bool waitUntilAllJobsFinished(int timeOutInMs = 10000); - void registerFiles(); void registerProjectPart(); void changeProjectPartArguments(); - void changeProjectPartArgumentsToWrongValues(); + void unregisterProject(const Utf8String &projectPartId); + + void registerProjectAndFile(const Utf8String &filePath, + int expectedDocumentAnnotationsChangedMessages = 1); + void registerProjectAndFileAndWaitForFinished(const Utf8String &filePath, + int expectedDocumentAnnotationsChangedMessages = 1); + void registerProjectAndFilesAndWaitForFinished(int expectedDocumentAnnotationsChangedMessages = 2); + void registerFile(const Utf8String &filePath, const Utf8String &projectFilePath); + void registerFile(const Utf8String &filePath, + int expectedDocumentAnnotationsChangedMessages = 1); + void registerFiles(int expectedDocumentAnnotationsChangedMessages); + void registerFileWithUnsavedContent(const Utf8String &filePath, const Utf8String &content); + + void updateUnsavedContent(const Utf8String &filePath, + const Utf8String &fileContent, + quint32 revisionNumber); + + void unregisterFile(const Utf8String &filePath); + void unregisterFile(const Utf8String &filePath, const Utf8String &projectPartId); + + void removeUnsavedFile(const Utf8String &filePath); + void updateVisibilty(const Utf8String ¤tEditor, const Utf8String &additionalVisibleEditor); + + void requestDocumentAnnotations(const Utf8String &filePath); + + void completeCode(const Utf8String &filePath, uint line = 1, uint column = 1, + const Utf8String &projectPartId = Utf8String()); + void completeCodeInFileA(); + void completeCodeInFileB(); + + void expectDocumentAnnotationsChanged(int count); + void expectCompletion(const CodeCompletion &completion); + void expectCompletionFromFileA(); + void expectCompletionFromFileBEnabledByMacro(); + void expectCompletionFromFileAUnsavedMethodVersion1(); + void expectCompletionFromFileAUnsavedMethodVersion2(); + void expectCompletionWithTicketNumber(quint64 ticketNumber); + void expectNoCompletionWithUnsavedMethod(); + void expectTranslationUnitDoesNotExist(const Utf8String &filePath); + void expectProjectPartsDoNoExist(const Utf8StringVector &projectFilePaths); + void expectDocumentAnnotationsChangedForFileBWithSpecificHighlightingMark(); + static const Utf8String unsavedContent(const QString &unsavedFilePath); protected: @@ -147,279 +158,216 @@ protected: ClangBackEnd::ClangCodeModelServer clangServer; const ClangBackEnd::TranslationUnits &translationUnits = clangServer.translationUnitsForTestOnly(); const Utf8String projectPartId = Utf8StringLiteral("pathToProjectPart.pro"); - const Utf8String functionTestFilePath = Utf8StringLiteral(TESTDATA_DIR"/complete_extractor_function.cpp"); - const Utf8String variableTestFilePath = Utf8StringLiteral(TESTDATA_DIR"/complete_extractor_variable.cpp"); - const QString unsavedTestFilePath = QStringLiteral(TESTDATA_DIR) + QStringLiteral("/complete_extractor_function_unsaved.cpp"); - const QString updatedUnsavedTestFilePath = QStringLiteral(TESTDATA_DIR) + QStringLiteral("/complete_extractor_function_unsaved_2.cpp"); - const Utf8String parseErrorTestFilePath = Utf8StringLiteral(TESTDATA_DIR"/complete_translationunit_parse_error.cpp"); + + const Utf8String filePathA = Utf8StringLiteral(TESTDATA_DIR"/complete_extractor_function.cpp"); + const QString filePathAUnsavedVersion1 + = QStringLiteral(TESTDATA_DIR) + QStringLiteral("/complete_extractor_function_unsaved.cpp"); + const QString filePathAUnsavedVersion2 + = QStringLiteral(TESTDATA_DIR) + QStringLiteral("/complete_extractor_function_unsaved_2.cpp"); + + const Utf8String filePathB = Utf8StringLiteral(TESTDATA_DIR"/complete_extractor_variable.cpp"); + + const Utf8String aFilePath = Utf8StringLiteral("afile.cpp"); + const Utf8String anExistingFilePath + = Utf8StringLiteral(TESTDATA_DIR"/complete_translationunit_parse_error.cpp"); + const Utf8String aProjectPartId = Utf8StringLiteral("aproject.pro"); }; TEST_F(ClangClangCodeModelServer, GetCodeCompletion) { - CompleteCodeMessage completeCodeMessage(functionTestFilePath, - 20, - 1, - projectPartId); - CodeCompletion codeCompletion(Utf8StringLiteral("Function"), - 34, - CodeCompletion::FunctionCompletionKind); + registerProjectAndFile(filePathA); - EXPECT_CALL(mockClangCodeModelClient, codeCompleted(Property(&CodeCompletedMessage::codeCompletions, Contains(codeCompletion)))) - .Times(1); - - clangServer.completeCode(completeCodeMessage); + expectCompletionFromFileA(); + completeCodeInFileA(); } TEST_F(ClangClangCodeModelServer, RequestDocumentAnnotations) { - RequestDocumentAnnotationsMessage requestDocumentAnnotations({variableTestFilePath, projectPartId}); - HighlightingTypes types; - types.mainHighlightingType = ClangBackEnd::HighlightingType::Function; - types.mixinHighlightingTypes.push_back(ClangBackEnd::HighlightingType::Declaration); - HighlightingMarkContainer highlightingMarkContainer(1, 6, 8, types); + registerProjectAndFileAndWaitForFinished(filePathB); - EXPECT_CALL(mockClangCodeModelClient, documentAnnotationsChanged(Property(&DocumentAnnotationsChangedMessage::highlightingMarks, Contains(highlightingMarkContainer)))) - .Times(1); - - clangServer.requestDocumentAnnotations(requestDocumentAnnotations); + expectDocumentAnnotationsChangedForFileBWithSpecificHighlightingMark(); + requestDocumentAnnotations(filePathB); } -TEST_F(ClangClangCodeModelServer, GetCodeCompletionDependingOnArgumets) +TEST_F(ClangClangCodeModelServer, NoInitialDocumentAnnotationsForClosedDocument) { - CompleteCodeMessage completeCodeMessage(variableTestFilePath, - 35, - 1, - projectPartId); - CodeCompletion codeCompletion(Utf8StringLiteral("ArgumentDefinitionVariable"), - 34, - CodeCompletion::VariableCompletionKind); + const int expectedDocumentAnnotationsChangedCount = 0; + registerProjectAndFile(filePathA, expectedDocumentAnnotationsChangedCount); - EXPECT_CALL(mockClangCodeModelClient, codeCompleted(Property(&CodeCompletedMessage::codeCompletions, Contains(codeCompletion)))) - .Times(1); + unregisterFile(filePathA); +} +TEST_F(ClangClangCodeModelServer, NoDocumentAnnotationsForClosedDocument) +{ + const int expectedDocumentAnnotationsChangedCount = 1; // Only for registration. + registerProjectAndFileAndWaitForFinished(filePathA, expectedDocumentAnnotationsChangedCount); + updateUnsavedContent(filePathA, Utf8String(), 1); + + unregisterFile(filePathA); +} + +TEST_F(ClangClangCodeModelServer, NoInitialDocumentAnnotationsForOutdatedDocumentRevision) +{ + const int expectedDocumentAnnotationsChangedCount = 1; // Only for registration. + registerProjectAndFile(filePathA, expectedDocumentAnnotationsChangedCount); + + updateUnsavedContent(filePathA, Utf8String(), 1); +} + +TEST_F(ClangClangCodeModelServer, NoCompletionsForClosedDocument) +{ + const int expectedDocumentAnnotationsChangedCount = 1; // Only for registration. + registerProjectAndFileAndWaitForFinished(filePathA, expectedDocumentAnnotationsChangedCount); + completeCodeInFileA(); + + unregisterFile(filePathA); +} + +TEST_F(ClangClangCodeModelServer, CodeCompletionDependingOnProject) +{ + const int expectedDocumentAnnotationsChangedCount = 2; // For registration and due to project change. + registerProjectAndFileAndWaitForFinished(filePathB, expectedDocumentAnnotationsChangedCount); + + expectCompletionFromFileBEnabledByMacro(); changeProjectPartArguments(); - clangServer.completeCode(completeCodeMessage); + completeCodeInFileB(); } TEST_F(ClangClangCodeModelServer, GetTranslationUnitDoesNotExistForEditorOnNonExistingTranslationUnit) { - CompleteCodeMessage completeCodeMessage(Utf8StringLiteral("dontexists.cpp"), - 34, - 1, - projectPartId); - TranslationUnitDoesNotExistMessage translationUnitDoesNotExistMessage(Utf8StringLiteral("dontexists.cpp"), projectPartId); + registerProjectPart(); - EXPECT_CALL(mockClangCodeModelClient, translationUnitDoesNotExist(translationUnitDoesNotExistMessage)) - .Times(1); - - clangServer.completeCode(completeCodeMessage); + expectTranslationUnitDoesNotExist(aFilePath); + completeCode(aFilePath); } TEST_F(ClangClangCodeModelServer, GetTranslationUnitDoesNotExistForCompletingUnregisteredFile) { - CompleteCodeMessage completeCodeMessage(parseErrorTestFilePath, - 20, - 1, - projectPartId); - TranslationUnitDoesNotExistMessage translationUnitDoesNotExistMessage(parseErrorTestFilePath, projectPartId); + registerProjectPart(); - EXPECT_CALL(mockClangCodeModelClient, translationUnitDoesNotExist(translationUnitDoesNotExistMessage)) - .Times(1); - - clangServer.completeCode(completeCodeMessage); + expectTranslationUnitDoesNotExist(anExistingFilePath); + completeCode(anExistingFilePath, 20, 1); } TEST_F(ClangClangCodeModelServer, GetCodeCompletionForUnsavedFile) { - CompleteCodeMessage completeCodeMessage(functionTestFilePath, - 20, - 1, - projectPartId); - CodeCompletion codeCompletion(Utf8StringLiteral("Method2"), - 34, - CodeCompletion::FunctionCompletionKind); + registerProjectPart(); + expectDocumentAnnotationsChanged(1); + registerFileWithUnsavedContent(filePathA, unsavedContent(filePathAUnsavedVersion1)); + expectCompletionFromFileAUnsavedMethodVersion1(); - EXPECT_CALL(mockClangCodeModelClient, codeCompleted(Property(&CodeCompletedMessage::codeCompletions, Contains(codeCompletion)))) - .Times(1); - - clangServer.completeCode(completeCodeMessage); + completeCodeInFileA(); } TEST_F(ClangClangCodeModelServer, GetNoCodeCompletionAfterRemovingUnsavedFile) { - clangServer.updateTranslationUnitsForEditor(UpdateTranslationUnitsForEditorMessage( - {FileContainer(functionTestFilePath, projectPartId, Utf8StringVector(), 74)})); - CompleteCodeMessage completeCodeMessage(functionTestFilePath, - 20, - 1, - projectPartId); - CodeCompletion codeCompletion(Utf8StringLiteral("Method2"), - 34, - CodeCompletion::FunctionCompletionKind); + const int expectedDocumentAnnotationsChangedCount = 2; // For registration and update/removal. + registerProjectAndFileAndWaitForFinished(filePathA, expectedDocumentAnnotationsChangedCount); + removeUnsavedFile(filePathA); - EXPECT_CALL(mockClangCodeModelClient, codeCompleted(Property(&CodeCompletedMessage::codeCompletions, Not(Contains(codeCompletion))))) - .Times(1); - - clangServer.completeCode(completeCodeMessage); + expectNoCompletionWithUnsavedMethod(); + completeCodeInFileA(); } TEST_F(ClangClangCodeModelServer, GetNewCodeCompletionAfterUpdatingUnsavedFile) { - clangServer.updateTranslationUnitsForEditor(UpdateTranslationUnitsForEditorMessage({{functionTestFilePath, - projectPartId, - unsavedContent(updatedUnsavedTestFilePath), - true, - 74}})); - CompleteCodeMessage completeCodeMessage(functionTestFilePath, - 20, - 1, - projectPartId); - CodeCompletion codeCompletion(Utf8StringLiteral("Method3"), - 34, - CodeCompletion::FunctionCompletionKind); + const int expectedDocumentAnnotationsChangedCount = 2; // For registration and update/removal. + registerProjectAndFileAndWaitForFinished(filePathA, expectedDocumentAnnotationsChangedCount); + updateUnsavedContent(filePathA, unsavedContent(filePathAUnsavedVersion2), 1); - EXPECT_CALL(mockClangCodeModelClient, codeCompleted(Property(&CodeCompletedMessage::codeCompletions, Contains(codeCompletion)))) - .Times(1); - - clangServer.completeCode(completeCodeMessage); + expectCompletionFromFileAUnsavedMethodVersion2(); + completeCodeInFileA(); } TEST_F(ClangClangCodeModelServer, GetTranslationUnitDoesNotExistForUnregisterTranslationUnitWithWrongFilePath) { - FileContainer fileContainer(Utf8StringLiteral("foo.cpp"), projectPartId); - UnregisterTranslationUnitsForEditorMessage message({fileContainer}); - TranslationUnitDoesNotExistMessage translationUnitDoesNotExistMessage(fileContainer); + registerProjectPart(); - EXPECT_CALL(mockClangCodeModelClient, translationUnitDoesNotExist(translationUnitDoesNotExistMessage)) - .Times(1); - - clangServer.unregisterTranslationUnitsForEditor(message); + expectTranslationUnitDoesNotExist(aFilePath); + unregisterFile(aFilePath); } TEST_F(ClangClangCodeModelServer, UnregisterTranslationUnitAndTestFailingCompletion) { - FileContainer fileContainer(functionTestFilePath, projectPartId); - UnregisterTranslationUnitsForEditorMessage message({fileContainer}); - clangServer.unregisterTranslationUnitsForEditor(message); - CompleteCodeMessage completeCodeMessage(functionTestFilePath, - 20, - 1, - projectPartId); - TranslationUnitDoesNotExistMessage translationUnitDoesNotExistMessage(fileContainer); + const int expectedDocumentAnnotationsChangedCount = 1; // Only for registration. + registerProjectAndFileAndWaitForFinished(filePathA, expectedDocumentAnnotationsChangedCount); + unregisterFile(filePathA); - EXPECT_CALL(mockClangCodeModelClient, translationUnitDoesNotExist(translationUnitDoesNotExistMessage)) - .Times(1); - - clangServer.completeCode(completeCodeMessage); + expectTranslationUnitDoesNotExist(filePathA); + completeCodeInFileA(); } TEST_F(ClangClangCodeModelServer, GetProjectPartDoesNotExistUnregisterProjectPartInexistingProjectPart) { - Utf8StringVector inexistingProjectPartFilePath = {Utf8StringLiteral("projectpartsdoesnotexist.pro"), Utf8StringLiteral("project2doesnotexists.pro")}; - UnregisterProjectPartsForEditorMessage unregisterProjectPartsForEditorMessage(inexistingProjectPartFilePath); - ProjectPartsDoNotExistMessage projectPartsDoNotExistMessage(inexistingProjectPartFilePath); - - EXPECT_CALL(mockClangCodeModelClient, projectPartsDoNotExist(projectPartsDoNotExistMessage)) - .Times(1); - - clangServer.unregisterProjectPartsForEditor(unregisterProjectPartsForEditorMessage); + expectProjectPartsDoNoExist({aProjectPartId}); + unregisterProject(aProjectPartId); } TEST_F(ClangClangCodeModelServer, GetProjectPartDoesNotExistRegisterTranslationUnitWithInexistingProjectPart) { - Utf8String inexistingProjectPartFilePath = Utf8StringLiteral("projectpartsdoesnotexist.pro"); - RegisterTranslationUnitForEditorMessage registerFileForEditorMessage({FileContainer(variableTestFilePath, inexistingProjectPartFilePath)}, - variableTestFilePath, - {variableTestFilePath}); - ProjectPartsDoNotExistMessage projectPartsDoNotExistMessage({inexistingProjectPartFilePath}); - - EXPECT_CALL(mockClangCodeModelClient, projectPartsDoNotExist(projectPartsDoNotExistMessage)) - .Times(1); - - clangServer.registerTranslationUnitsForEditor(registerFileForEditorMessage); + expectProjectPartsDoNoExist({aProjectPartId}); + registerFile(filePathB, aProjectPartId); } TEST_F(ClangClangCodeModelServer, GetProjectPartDoesNotExistUnregisterTranslationUnitWithInexistingProjectPart) { - Utf8String inexistingProjectPartFilePath = Utf8StringLiteral("projectpartsdoesnotexist.pro"); - UnregisterTranslationUnitsForEditorMessage unregisterFileForEditorMessage({FileContainer(variableTestFilePath, inexistingProjectPartFilePath)}); - ProjectPartsDoNotExistMessage projectPartsDoNotExistMessage({inexistingProjectPartFilePath}); - - EXPECT_CALL(mockClangCodeModelClient, projectPartsDoNotExist(projectPartsDoNotExistMessage)) - .Times(1); - - clangServer.unregisterTranslationUnitsForEditor(unregisterFileForEditorMessage); + expectProjectPartsDoNoExist({aProjectPartId}); + unregisterFile(filePathB, aProjectPartId); } TEST_F(ClangClangCodeModelServer, GetProjectPartDoesNotExistForCompletingProjectPartFile) { - Utf8String inexistingProjectPartFilePath = Utf8StringLiteral("projectpartsdoesnotexist.pro"); - CompleteCodeMessage completeCodeMessage(variableTestFilePath, - 20, - 1, - inexistingProjectPartFilePath); - ProjectPartsDoNotExistMessage projectPartsDoNotExistMessage({inexistingProjectPartFilePath}); + registerProjectAndFile(filePathB, 1); - EXPECT_CALL(mockClangCodeModelClient, projectPartsDoNotExist(projectPartsDoNotExistMessage)) - .Times(1); - - clangServer.completeCode(completeCodeMessage); + expectProjectPartsDoNoExist({aProjectPartId}); + completeCode(filePathB, 1, 1, aProjectPartId); } TEST_F(ClangClangCodeModelServer, GetProjectPartDoesNotExistForCompletingUnregisteredFile) { - CompleteCodeMessage completeCodeMessage(parseErrorTestFilePath, - 20, - 1, - projectPartId); - TranslationUnitDoesNotExistMessage translationUnitDoesNotExistMessage(parseErrorTestFilePath, projectPartId); + registerProjectPart(); - EXPECT_CALL(mockClangCodeModelClient, translationUnitDoesNotExist(translationUnitDoesNotExistMessage)) - .Times(1); - - clangServer.completeCode(completeCodeMessage); + expectTranslationUnitDoesNotExist(anExistingFilePath); + completeCode(anExistingFilePath); } TEST_F(ClangClangCodeModelServer, TicketNumberIsForwarded) { - CompleteCodeMessage completeCodeMessage(functionTestFilePath, - 20, - 1, - projectPartId); - CodeCompletion codeCompletion(Utf8StringLiteral("Function"), - 34, - CodeCompletion::FunctionCompletionKind); + registerProjectAndFile(filePathA, 1); + const CompleteCodeMessage message(filePathA, 20, 1, projectPartId); - EXPECT_CALL(mockClangCodeModelClient, codeCompleted(Property(&CodeCompletedMessage::ticketNumber, Eq(completeCodeMessage.ticketNumber())))) - .Times(1); - - clangServer.completeCode(completeCodeMessage); + expectCompletionWithTicketNumber(message.ticketNumber()); + clangServer.completeCode(message); } -TEST_F(ClangClangCodeModelServer, TranslationUnitAfterCreationNeedsNoReparseAndHasNoNewDiagnostics) +TEST_F(ClangClangCodeModelServer, TranslationUnitAfterCreationIsNotDirty) { - ASSERT_THAT(clangServer, HasDirtyTranslationUnit(functionTestFilePath, projectPartId, 0U, false, false)); + registerProjectAndFile(filePathA, 1); + + ASSERT_THAT(clangServer, HasDirtyTranslationUnit(filePathA, projectPartId, 0U, false, false)); } TEST_F(ClangClangCodeModelServer, SetCurrentAndVisibleEditor) { - auto functionTranslationUnit = translationUnits.translationUnit(functionTestFilePath, projectPartId); - auto variableTranslationUnit = translationUnits.translationUnit(variableTestFilePath, projectPartId); + registerProjectAndFilesAndWaitForFinished(); + auto functionTranslationUnit = translationUnits.translationUnit(filePathA, projectPartId); + auto variableTranslationUnit = translationUnits.translationUnit(filePathB, projectPartId); - updateVisibilty(functionTestFilePath, variableTestFilePath); + updateVisibilty(filePathB, filePathA); - ASSERT_TRUE(functionTranslationUnit.isUsedByCurrentEditor()); - ASSERT_TRUE(functionTranslationUnit.isVisibleInEditor()); + ASSERT_TRUE(variableTranslationUnit.isUsedByCurrentEditor()); ASSERT_TRUE(variableTranslationUnit.isVisibleInEditor()); + ASSERT_TRUE(functionTranslationUnit.isVisibleInEditor()); } TEST_F(ClangClangCodeModelServer, IsNotCurrentCurrentAndVisibleEditorAnymore) { - auto functionTranslationUnit = translationUnits.translationUnit(functionTestFilePath, projectPartId); - auto variableTranslationUnit = translationUnits.translationUnit(variableTestFilePath, projectPartId); - updateVisibilty(functionTestFilePath, variableTestFilePath); + registerProjectAndFilesAndWaitForFinished(); + auto functionTranslationUnit = translationUnits.translationUnit(filePathA, projectPartId); + auto variableTranslationUnit = translationUnits.translationUnit(filePathB, projectPartId); + updateVisibilty(filePathB, filePathA); - updateVisibilty(variableTestFilePath, Utf8String()); + updateVisibilty(filePathB, Utf8String()); ASSERT_FALSE(functionTranslationUnit.isUsedByCurrentEditor()); ASSERT_FALSE(functionTranslationUnit.isVisibleInEditor()); @@ -427,25 +375,258 @@ TEST_F(ClangClangCodeModelServer, IsNotCurrentCurrentAndVisibleEditorAnymore) ASSERT_TRUE(variableTranslationUnit.isVisibleInEditor()); } +TEST_F(ClangClangCodeModelServer, TranslationUnitAfterUpdateNeedsReparse) +{ + registerProjectAndFileAndWaitForFinished(filePathA, 2); + + updateUnsavedContent(filePathA, unsavedContent(filePathAUnsavedVersion1), 1U); + ASSERT_THAT(clangServer, HasDirtyTranslationUnit(filePathA, projectPartId, 1U, true, true)); +} + void ClangClangCodeModelServer::SetUp() { clangServer.setClient(&mockClangCodeModelClient); - registerProjectPart(); - registerFiles(); + clangServer.setUpdateDocumentAnnotationsTimeOutInMsForTestsOnly(0); } -void ClangClangCodeModelServer::registerFiles() +void ClangClangCodeModelServer::TearDown() { - RegisterTranslationUnitForEditorMessage message({FileContainer(functionTestFilePath, projectPartId, unsavedContent(unsavedTestFilePath), true), - FileContainer(variableTestFilePath, projectPartId)}, - functionTestFilePath, - {functionTestFilePath, variableTestFilePath}); + ASSERT_TRUE(waitUntilAllJobsFinished()); +} - EXPECT_CALL(mockClangCodeModelClient, documentAnnotationsChanged(_)).Times(2); +bool ClangClangCodeModelServer::waitUntilAllJobsFinished(int timeOutInMs) +{ + const auto noJobsRunningAnymore = [this]() { + return clangServer.jobsForTestOnly().runningJobs() == 0 + && clangServer.jobsForTestOnly().queue().size() == 0 + && !clangServer.isTimerRunningForTestOnly(); + }; + + return TestUtils::processEventsUntilTrue(noJobsRunningAnymore, timeOutInMs); +} + +void ClangClangCodeModelServer::registerProjectAndFilesAndWaitForFinished( + int expectedDocumentAnnotationsChangedMessages) +{ + registerProjectPart(); + registerFiles(expectedDocumentAnnotationsChangedMessages); + + ASSERT_TRUE(waitUntilAllJobsFinished()); +} + +void ClangClangCodeModelServer::registerFile(const Utf8String &filePath, + int expectedDocumentAnnotationsChangedMessages) +{ + const FileContainer fileContainer(filePath, projectPartId); + const RegisterTranslationUnitForEditorMessage message({fileContainer}, filePath, {filePath}); + + expectDocumentAnnotationsChanged(expectedDocumentAnnotationsChangedMessages); clangServer.registerTranslationUnitsForEditor(message); } +void ClangClangCodeModelServer::registerFiles(int expectedDocumentAnnotationsChangedMessages) +{ + const FileContainer fileContainerA(filePathA, projectPartId); + const FileContainer fileContainerB(filePathB, projectPartId); + const RegisterTranslationUnitForEditorMessage message({fileContainerA, + fileContainerB}, + filePathA, + {filePathA, filePathB}); + + expectDocumentAnnotationsChanged(expectedDocumentAnnotationsChangedMessages); + + clangServer.registerTranslationUnitsForEditor(message); +} + +void ClangClangCodeModelServer::expectDocumentAnnotationsChanged(int count) +{ + EXPECT_CALL(mockClangCodeModelClient, documentAnnotationsChanged(_)).Times(count); +} + +void ClangClangCodeModelServer::registerFile(const Utf8String &filePath, const Utf8String &projectFilePath) +{ + const FileContainer fileContainer(filePath, projectFilePath); + const RegisterTranslationUnitForEditorMessage message({fileContainer}, filePath, {filePath}); + + clangServer.registerTranslationUnitsForEditor(message); +} + +void ClangClangCodeModelServer::registerFileWithUnsavedContent(const Utf8String &filePath, + const Utf8String &unsavedContent) +{ + const FileContainer fileContainer(filePath, projectPartId, unsavedContent, true); + const RegisterTranslationUnitForEditorMessage message({fileContainer}, filePath, {filePath}); + + clangServer.registerTranslationUnitsForEditor(message); +} + +void ClangClangCodeModelServer::completeCode(const Utf8String &filePath, + uint line, + uint column, + const Utf8String &projectPartId) +{ + Utf8String theProjectPartId = projectPartId; + if (theProjectPartId.isEmpty()) + theProjectPartId = this->projectPartId; + + const CompleteCodeMessage message(filePath, line, column, theProjectPartId); + + clangServer.completeCode(message); +} + +void ClangClangCodeModelServer::completeCodeInFileA() +{ + completeCode(filePathA, 20, 1); +} + +void ClangClangCodeModelServer::completeCodeInFileB() +{ + completeCode(filePathB, 35, 1); +} + +void ClangClangCodeModelServer::expectCompletion(const CodeCompletion &completion) +{ + EXPECT_CALL(mockClangCodeModelClient, + codeCompleted(Property(&CodeCompletedMessage::codeCompletions, + Contains(completion)))) + .Times(1); +} + +void ClangClangCodeModelServer::expectCompletionFromFileBEnabledByMacro() +{ + const CodeCompletion completion(Utf8StringLiteral("ArgumentDefinitionVariable"), + 34, + CodeCompletion::VariableCompletionKind); + + expectCompletion(completion); +} + +void ClangClangCodeModelServer::expectCompletionFromFileAUnsavedMethodVersion1() +{ + const CodeCompletion completion(Utf8StringLiteral("Method2"), + 34, + CodeCompletion::FunctionCompletionKind); + + expectCompletion(completion); +} + +void ClangClangCodeModelServer::expectCompletionFromFileAUnsavedMethodVersion2() +{ + const CodeCompletion completion(Utf8StringLiteral("Method3"), + 34, + CodeCompletion::FunctionCompletionKind); + + expectCompletion(completion); +} + +void ClangClangCodeModelServer::expectCompletionWithTicketNumber(quint64 ticketNumber) +{ + EXPECT_CALL(mockClangCodeModelClient, + codeCompleted(Property(&CodeCompletedMessage::ticketNumber, + Eq(ticketNumber)))) + .Times(1); +} + +void ClangClangCodeModelServer::expectNoCompletionWithUnsavedMethod() +{ + const CodeCompletion completion(Utf8StringLiteral("Method2"), + 34, + CodeCompletion::FunctionCompletionKind); + + EXPECT_CALL(mockClangCodeModelClient, + codeCompleted(Property(&CodeCompletedMessage::codeCompletions, + Not(Contains(completion))))) + .Times(1); +} + +void ClangClangCodeModelServer::expectTranslationUnitDoesNotExist(const Utf8String &filePath) +{ + const TranslationUnitDoesNotExistMessage message(filePath, projectPartId); + + EXPECT_CALL(mockClangCodeModelClient, translationUnitDoesNotExist(message)) + .Times(1); +} + +void ClangClangCodeModelServer::expectCompletionFromFileA() +{ + const CodeCompletion completion(Utf8StringLiteral("Function"), + 34, + CodeCompletion::FunctionCompletionKind); + + expectCompletion(completion); +} + +void ClangClangCodeModelServer::requestDocumentAnnotations(const Utf8String &filePath) +{ + const RequestDocumentAnnotationsMessage message({filePath, projectPartId}); + + clangServer.requestDocumentAnnotations(message); +} + +void ClangClangCodeModelServer::expectDocumentAnnotationsChangedForFileBWithSpecificHighlightingMark() +{ + HighlightingTypes types; + types.mainHighlightingType = ClangBackEnd::HighlightingType::Function; + types.mixinHighlightingTypes.push_back(ClangBackEnd::HighlightingType::Declaration); + const HighlightingMarkContainer highlightingMark(1, 6, 8, types); + + EXPECT_CALL(mockClangCodeModelClient, + documentAnnotationsChanged( + Property(&DocumentAnnotationsChangedMessage::highlightingMarks, + Contains(highlightingMark)))) + .Times(1); +} + +void ClangClangCodeModelServer::updateUnsavedContent(const Utf8String &filePath, + const Utf8String &fileContent, + quint32 revisionNumber) +{ + const FileContainer fileContainer(filePath, projectPartId, fileContent, true, revisionNumber); + const UpdateTranslationUnitsForEditorMessage message({fileContainer}); + + clangServer.updateTranslationUnitsForEditor(message); +} + +void ClangClangCodeModelServer::removeUnsavedFile(const Utf8String &filePath) +{ + const FileContainer fileContainer(filePath, projectPartId, Utf8StringVector(), 74); + const UpdateTranslationUnitsForEditorMessage message({fileContainer}); + + clangServer.updateTranslationUnitsForEditor(message); +} + +void ClangClangCodeModelServer::unregisterFile(const Utf8String &filePath) +{ + const QVector fileContainers = {FileContainer(filePath, projectPartId)}; + const UnregisterTranslationUnitsForEditorMessage message(fileContainers); + + clangServer.unregisterTranslationUnitsForEditor(message); +} + +void ClangClangCodeModelServer::unregisterFile(const Utf8String &filePath, const Utf8String &projectPartId) +{ + const QVector fileContainers = {FileContainer(filePath, projectPartId)}; + const UnregisterTranslationUnitsForEditorMessage message(fileContainers); + + clangServer.unregisterTranslationUnitsForEditor(message); +} + +void ClangClangCodeModelServer::unregisterProject(const Utf8String &projectPartId) +{ + const UnregisterProjectPartsForEditorMessage message({projectPartId}); + + clangServer.unregisterProjectPartsForEditor(message); +} + +void ClangClangCodeModelServer::expectProjectPartsDoNoExist(const Utf8StringVector &projectFilePaths) +{ + const ProjectPartsDoNotExistMessage message(projectFilePaths); + + EXPECT_CALL(mockClangCodeModelClient, projectPartsDoNotExist(message)) + .Times(1); +} + void ClangClangCodeModelServer::registerProjectPart() { RegisterProjectPartsForEditorMessage message({ProjectPartContainer(projectPartId)}); @@ -453,24 +634,35 @@ void ClangClangCodeModelServer::registerProjectPart() clangServer.registerProjectPartsForEditor(message); } +void ClangClangCodeModelServer::registerProjectAndFile(const Utf8String &filePath, + int expectedDocumentAnnotationsChangedMessages) +{ + registerProjectPart(); + registerFile(filePath, expectedDocumentAnnotationsChangedMessages); +} + +void ClangClangCodeModelServer::registerProjectAndFileAndWaitForFinished( + const Utf8String &filePath, + int expectedDocumentAnnotationsChangedMessages) +{ + registerProjectAndFile(filePath, expectedDocumentAnnotationsChangedMessages); + ASSERT_TRUE(waitUntilAllJobsFinished()); +} + void ClangClangCodeModelServer::changeProjectPartArguments() { - RegisterProjectPartsForEditorMessage message({ProjectPartContainer(projectPartId, {Utf8StringLiteral("-DArgumentDefinition")})}); + const ProjectPartContainer projectPartContainer(projectPartId, + {Utf8StringLiteral("-DArgumentDefinition")}); + const RegisterProjectPartsForEditorMessage message({projectPartContainer}); clangServer.registerProjectPartsForEditor(message); } -void ClangClangCodeModelServer::changeProjectPartArgumentsToWrongValues() +void ClangClangCodeModelServer::updateVisibilty(const Utf8String ¤tEditor, + const Utf8String &additionalVisibleEditor) { - RegisterProjectPartsForEditorMessage message({ProjectPartContainer(projectPartId, {Utf8StringLiteral("-blah")})}); - - clangServer.registerProjectPartsForEditor(message); -} - -void ClangClangCodeModelServer::updateVisibilty(const Utf8String ¤tEditor, const Utf8String &additionalVisibleEditor) -{ - UpdateVisibleTranslationUnitsMessage message(currentEditor, - {currentEditor, additionalVisibleEditor}); + const UpdateVisibleTranslationUnitsMessage message(currentEditor, + {currentEditor, additionalVisibleEditor}); clangServer.updateVisibleTranslationUnits(message); } @@ -478,20 +670,11 @@ void ClangClangCodeModelServer::updateVisibilty(const Utf8String ¤tEditor, const Utf8String ClangClangCodeModelServer::unsavedContent(const QString &unsavedFilePath) { QFile unsavedFileContentFile(unsavedFilePath); - bool isOpen = unsavedFileContentFile.open(QIODevice::ReadOnly | QIODevice::Text); + const bool isOpen = unsavedFileContentFile.open(QIODevice::ReadOnly | QIODevice::Text); if (!isOpen) ADD_FAILURE() << "File with the unsaved content cannot be opened!"; return Utf8String::fromByteArray(unsavedFileContentFile.readAll()); } -TEST_F(ClangClangCodeModelServer, TranslationUnitAfterUpdateNeedsReparseAndHasNewDiagnostics) -{ - const auto fileContainer = FileContainer(functionTestFilePath, projectPartId,unsavedContent(unsavedTestFilePath), true, 1); - - clangServer.updateTranslationUnitsForEditor({{fileContainer}}); - - ASSERT_THAT(clangServer, HasDirtyTranslationUnit(functionTestFilePath, projectPartId, 1U, true, true)); -} - -} +} // anonymous diff --git a/tests/unit/unittest/clangjobqueuetest.cpp b/tests/unit/unittest/clangjobqueuetest.cpp new file mode 100644 index 00000000000..c101fc8b8c8 --- /dev/null +++ b/tests/unit/unittest/clangjobqueuetest.cpp @@ -0,0 +1,446 @@ +/**************************************************************************** +** +** 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 "testutils.h" +#include "dummyclangipcclient.h" + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include "gtest-qt-printing.h" + +#include + +using namespace ClangBackEnd; + +using testing::IsNull; +using testing::NotNull; +using testing::Eq; +using testing::Gt; +using testing::Contains; +using testing::EndsWith; +using testing::AllOf; + +namespace { + +class JobQueue : public ::testing::Test +{ +protected: + void SetUp() override; + + void resetVisibilityAndCurrentEditor(); + + Utf8String createTranslationUnitForDeletedFile(); + + JobRequest createJobRequest(const Utf8String &filePath, + JobRequest::Type type) const; + + void updateDocumentRevision(); + void updateUnsavedFiles(); + void updateProject(); + void removeProject(); + void removeDocument(); + +protected: + ClangBackEnd::ProjectParts projects; + ClangBackEnd::UnsavedFiles unsavedFiles; + ClangBackEnd::TranslationUnits translationUnits{projects, unsavedFiles}; + ClangBackEnd::TranslationUnit translationUnit; + DummyIpcClient dummyClientInterface; + + Utf8String filePath1 = Utf8StringLiteral(TESTDATA_DIR"/translationunits.cpp"); + Utf8String filePath2 = Utf8StringLiteral(TESTDATA_DIR"/skippedsourceranges.cpp"); + Utf8String projectPartId{Utf8StringLiteral("/path/to/projectfile")}; + + ClangBackEnd::JobQueue jobQueue{translationUnits, unsavedFiles, projects, dummyClientInterface}; +}; + +TEST_F(JobQueue, AddJob) +{ + const JobRequest jobRequest = createJobRequest(filePath1, + JobRequest::Type::UpdateDocumentAnnotations); + + jobQueue.add(jobRequest); + + ASSERT_THAT(jobQueue.queue().size(), Eq(1)); +} + +TEST_F(JobQueue, ProcessEmpty) +{ + jobQueue.processQueue(); + + ASSERT_THAT(jobQueue.size(), Eq(0)); +} + +TEST_F(JobQueue, ProcessSingleJob) +{ + jobQueue.add(createJobRequest(filePath1, JobRequest::Type::UpdateDocumentAnnotations)); + + const JobRequests jobsToRun = jobQueue.processQueue(); + + ASSERT_THAT(jobsToRun.size(), Eq(1)); + ASSERT_THAT(jobQueue.size(), Eq(0)); +} + +TEST_F(JobQueue, ProcessUntilEmpty) +{ + jobQueue.add(createJobRequest(filePath1, JobRequest::Type::UpdateDocumentAnnotations)); + jobQueue.add(createJobRequest(filePath1, JobRequest::Type::UpdateDocumentAnnotations)); + + JobRequests jobsToRun; + ASSERT_THAT(jobQueue.size(), Eq(2)); + + jobsToRun = jobQueue.processQueue(); + ASSERT_THAT(jobQueue.size(), Eq(1)); + ASSERT_THAT(jobsToRun.size(), Eq(1)); + + jobsToRun = jobQueue.processQueue(); + ASSERT_THAT(jobQueue.size(), Eq(0)); + ASSERT_THAT(jobsToRun.size(), Eq(1)); +} + +TEST_F(JobQueue, RemoveRequestsForClosedDocuments) +{ + jobQueue.add(createJobRequest(filePath1, JobRequest::Type::UpdateDocumentAnnotations)); + removeDocument(); + + const JobRequests jobsToRun = jobQueue.processQueue(); + + ASSERT_THAT(jobQueue.size(), Eq(0)); + ASSERT_THAT(jobsToRun.size(), Eq(0)); +} + +TEST_F(JobQueue, RemoveRequestsForClosedProject) +{ + jobQueue.add(createJobRequest(filePath1, JobRequest::Type::UpdateDocumentAnnotations)); + removeProject(); + + const JobRequests jobsToRun = jobQueue.processQueue(); + + ASSERT_THAT(jobQueue.size(), Eq(0)); + ASSERT_THAT(jobsToRun.size(), Eq(0)); +} + +TEST_F(JobQueue, RemoveRequestsForOudatedUnsavedFiles) +{ + jobQueue.add(createJobRequest(filePath1, JobRequest::Type::UpdateDocumentAnnotations)); + updateUnsavedFiles(); + + const JobRequests jobsToRun = jobQueue.processQueue(); + + ASSERT_THAT(jobQueue.size(), Eq(0)); + ASSERT_THAT(jobsToRun.size(), Eq(0)); +} + +TEST_F(JobQueue, RemoveRequestsForChangedDocumentRevision) +{ + jobQueue.add(createJobRequest(filePath1, JobRequest::Type::UpdateDocumentAnnotations)); + updateDocumentRevision(); + + const JobRequests jobsToRun = jobQueue.processQueue(); + + ASSERT_THAT(jobQueue.size(), Eq(0)); + ASSERT_THAT(jobsToRun.size(), Eq(0)); +} + +TEST_F(JobQueue, RemoveRequestsForOudatedProject) +{ + jobQueue.add(createJobRequest(filePath1, JobRequest::Type::UpdateDocumentAnnotations)); + updateProject(); + + const JobRequests jobsToRun = jobQueue.processQueue(); + + ASSERT_THAT(jobQueue.size(), Eq(0)); + ASSERT_THAT(jobsToRun.size(), Eq(0)); +} + +TEST_F(JobQueue, RemoveRequestsForNotIntactDocuments) +{ + const Utf8String filePath = createTranslationUnitForDeletedFile(); + jobQueue.add(createJobRequest(filePath, JobRequest::Type::UpdateDocumentAnnotations)); + + const JobRequests jobsToRun = jobQueue.processQueue(); + + ASSERT_THAT(jobQueue.size(), Eq(0)); + ASSERT_THAT(jobsToRun.size(), Eq(0)); +} + +TEST_F(JobQueue, PrioritizeCurrentDocumentOverNotCurrent) +{ + resetVisibilityAndCurrentEditor(); + jobQueue.add(createJobRequest(filePath1, JobRequest::Type::UpdateDocumentAnnotations)); + jobQueue.add(createJobRequest(filePath2, JobRequest::Type::UpdateDocumentAnnotations)); + translationUnits.setUsedByCurrentEditor(filePath2); + + jobQueue.prioritizeRequests(); + + ASSERT_THAT(jobQueue.queue().first().filePath, Eq(filePath2)); +} + +TEST_F(JobQueue, PrioritizeVisibleDocumentsOverNotVisible) +{ + resetVisibilityAndCurrentEditor(); + jobQueue.add(createJobRequest(filePath1, JobRequest::Type::UpdateDocumentAnnotations)); + jobQueue.add(createJobRequest(filePath2, JobRequest::Type::UpdateDocumentAnnotations)); + translationUnits.setVisibleInEditors({filePath2}); + + jobQueue.prioritizeRequests(); + + ASSERT_THAT(jobQueue.queue().first().filePath, Eq(filePath2)); +} + +TEST_F(JobQueue, PrioritizeCurrentDocumentOverVisible) +{ + resetVisibilityAndCurrentEditor(); + jobQueue.add(createJobRequest(filePath1, JobRequest::Type::UpdateDocumentAnnotations)); + jobQueue.add(createJobRequest(filePath2, JobRequest::Type::UpdateDocumentAnnotations)); + translationUnits.setVisibleInEditors({filePath1, filePath2}); + translationUnits.setUsedByCurrentEditor(filePath2); + + jobQueue.prioritizeRequests(); + + ASSERT_THAT(jobQueue.queue().first().filePath, Eq(filePath2)); +} + +TEST_F(JobQueue, RunNothingForNotCurrentOrVisibleDocument) +{ + jobQueue.add(createJobRequest(filePath1, JobRequest::Type::UpdateDocumentAnnotations)); + jobQueue.add(createJobRequest(filePath1, JobRequest::Type::UpdateDocumentAnnotations)); + translationUnits.setVisibleInEditors({}); + translationUnits.setUsedByCurrentEditor(Utf8StringLiteral("aNonExistingFilePath")); + + const JobRequests jobsToRun = jobQueue.processQueue(); + + ASSERT_THAT(jobsToRun.size(), Eq(0)); +} + +TEST_F(JobQueue, RunOnlyOneJobPerDocumentIfMultipleAreInQueue) +{ + jobQueue.add(createJobRequest(filePath1, JobRequest::Type::UpdateDocumentAnnotations)); + jobQueue.add(createJobRequest(filePath1, JobRequest::Type::UpdateDocumentAnnotations)); + + const JobRequests jobsToRun = jobQueue.processQueue(); + + ASSERT_THAT(jobsToRun.size(), Eq(1)); + ASSERT_THAT(jobQueue.size(), Eq(1)); +} + +TEST_F(JobQueue, DoNotRunJobForDocumentThatIsBeingProcessed) +{ + jobQueue.add(createJobRequest(filePath1, JobRequest::Type::UpdateDocumentAnnotations)); + jobQueue.add(createJobRequest(filePath1, JobRequest::Type::UpdateDocumentAnnotations)); + JobRequests jobsToRun = jobQueue.processQueue(); + jobQueue.setIsJobRunningHandler([](const Utf8String &, const Utf8String &) { + return true; + }); + + jobsToRun = jobQueue.processQueue(); + + ASSERT_THAT(jobsToRun.size(), Eq(0)); +} + +TEST_F(JobQueue, RequestUpdateDocumentAnnotationsOutdatableByUnsavedFileChange) +{ + jobQueue.add(createJobRequest(filePath1, JobRequest::Type::UpdateDocumentAnnotations)); + updateUnsavedFiles(); + + const JobRequests jobsToStart = jobQueue.processQueue(); + + ASSERT_THAT(jobsToStart.size(), Eq(0)); +} + +TEST_F(JobQueue, RequestUpdateDocumentAnnotationsOutdatableByProjectRemoval) +{ + jobQueue.add(createJobRequest(filePath1, JobRequest::Type::UpdateDocumentAnnotations)); + removeProject(); + + const JobRequests jobsToStart = jobQueue.processQueue(); + + ASSERT_THAT(jobsToStart.size(), Eq(0)); +} + +TEST_F(JobQueue, RequestUpdateDocumentAnnotationsOutdatableByProjectChange) +{ + jobQueue.add(createJobRequest(filePath1, JobRequest::Type::UpdateDocumentAnnotations)); + updateProject(); + + const JobRequests jobsToStart = jobQueue.processQueue(); + + ASSERT_THAT(jobsToStart.size(), Eq(0)); +} + +TEST_F(JobQueue, RequestUpdateDocumentAnnotationsOutdatableByDocumentClose) +{ + jobQueue.add(createJobRequest(filePath1, JobRequest::Type::UpdateDocumentAnnotations)); + removeDocument(); + + const JobRequests jobsToStart = jobQueue.processQueue(); + + ASSERT_THAT(jobsToStart.size(), Eq(0)); +} + +TEST_F(JobQueue, RequestUpdateDocumentAnnotationsOutdatableByNotIntactDocument) +{ + jobQueue.add(createJobRequest(filePath1, JobRequest::Type::UpdateDocumentAnnotations)); + translationUnit.setHasParseOrReparseFailed(true); + + const JobRequests jobsToStart = jobQueue.processQueue(); + + ASSERT_THAT(jobsToStart.size(), Eq(0)); +} + +TEST_F(JobQueue, RequestCompleteCodeOutdatableByDocumentClose) +{ + jobQueue.add(createJobRequest(filePath1, JobRequest::Type::CompleteCode)); + removeDocument(); + + const JobRequests jobsToStart = jobQueue.processQueue(); + + ASSERT_THAT(jobsToStart.size(), Eq(0)); +} + +TEST_F(JobQueue, RequestCompleteCodeNotOutdatableByUnsavedFilesChange) +{ + jobQueue.add(createJobRequest(filePath1, JobRequest::Type::CompleteCode)); + updateUnsavedFiles(); + + const JobRequests jobsToStart = jobQueue.processQueue(); + + ASSERT_THAT(jobsToStart.size(), Eq(1)); +} + +TEST_F(JobQueue, RequestCompleteCodeNotOutdatableByDocumentRevisionChange) +{ + jobQueue.add(createJobRequest(filePath1, JobRequest::Type::CompleteCode)); + updateDocumentRevision(); + + const JobRequests jobsToStart = jobQueue.processQueue(); + + ASSERT_THAT(jobsToStart.size(), Eq(1)); +} + +TEST_F(JobQueue, RequestCreateInitialDocumentPreambleOutdatableByDocumentClose) +{ + jobQueue.add(createJobRequest(filePath1, JobRequest::Type::CreateInitialDocumentPreamble)); + removeDocument(); + + const JobRequests jobsToStart = jobQueue.processQueue(); + + ASSERT_THAT(jobsToStart.size(), Eq(0)); +} + +TEST_F(JobQueue, RequestCompleteCodeOutdatableByDocumentRevisionChange) +{ + jobQueue.add(createJobRequest(filePath1, JobRequest::Type::RequestDocumentAnnotations)); + updateDocumentRevision(); + + const JobRequests jobsToStart = jobQueue.processQueue(); + + ASSERT_THAT(jobsToStart.size(), Eq(0)); +} + +void JobQueue::SetUp() +{ + projects.createOrUpdate({ProjectPartContainer(projectPartId)}); + + const QVector fileContainer{FileContainer(filePath1, projectPartId), + FileContainer(filePath2, projectPartId)}; + translationUnit = translationUnits.create(fileContainer).front(); + translationUnits.setVisibleInEditors({filePath1}); + translationUnits.setUsedByCurrentEditor(filePath1); +} + +void JobQueue::resetVisibilityAndCurrentEditor() +{ + translationUnits.setVisibleInEditors({}); + translationUnits.setUsedByCurrentEditor(Utf8String()); +} + +Utf8String JobQueue::createTranslationUnitForDeletedFile() +{ + QTemporaryFile temporaryFile(QLatin1String("XXXXXX.cpp")); + EXPECT_TRUE(temporaryFile.open()); + const QString temporaryFilePath = Utf8String::fromString(temporaryFile.fileName()); + + ClangBackEnd::FileContainer fileContainer(temporaryFilePath, + projectPartId, Utf8String(), true); + translationUnits.create({fileContainer}); + auto translationUnit = translationUnits.translationUnit(fileContainer); + translationUnit.setIsUsedByCurrentEditor(true); + + return temporaryFilePath; +} + +JobRequest JobQueue::createJobRequest(const Utf8String &filePath, + JobRequest::Type type) const +{ + JobRequest jobRequest; + jobRequest.type = type; + jobRequest.requirements = JobRequest::requirementsForType(type); + jobRequest.filePath = filePath; + jobRequest.projectPartId = projectPartId; + jobRequest.unsavedFilesChangeTimePoint = unsavedFiles.lastChangeTimePoint(); + jobRequest.documentRevision = translationUnit.documentRevision(); + jobRequest.projectChangeTimePoint = projects.project(projectPartId).lastChangeTimePoint(); + + return jobRequest; +} + +void JobQueue::updateDocumentRevision() +{ + translationUnits.update({FileContainer(filePath1, projectPartId, Utf8String(), true, 1)}); +} + +void JobQueue::updateUnsavedFiles() +{ + unsavedFiles.createOrUpdate({FileContainer(filePath1, projectPartId, Utf8String(), true, 1)}); +} + +void JobQueue::updateProject() +{ + projects.createOrUpdate({projectPartId}); +} + +void JobQueue::removeProject() +{ + projects.remove({projectPartId}); +} + +void JobQueue::removeDocument() +{ + translationUnits.remove({FileContainer(filePath1, projectPartId)}); +} + +} // anonymous diff --git a/tests/unit/unittest/clangjobstest.cpp b/tests/unit/unittest/clangjobstest.cpp new file mode 100644 index 00000000000..9a5442f8a22 --- /dev/null +++ b/tests/unit/unittest/clangjobstest.cpp @@ -0,0 +1,166 @@ +/**************************************************************************** +** +** 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 "testutils.h" +#include "dummyclangipcclient.h" + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include "gtest-qt-printing.h" + +using namespace ClangBackEnd; + +using testing::IsNull; +using testing::NotNull; +using testing::Eq; +using testing::Gt; +using testing::Contains; +using testing::EndsWith; +using testing::AllOf; + +namespace { + +class Jobs : public ::testing::Test +{ +protected: + void SetUp() override; + void TearDown() override; + + bool waitUntilAllJobsFinished(int timeOutInMs = 10000) const; + bool waitUntilJobChainFinished(int timeOutInMs = 10000) const; + + JobRequest createJobRequest(const Utf8String &filePath, + JobRequest::Type type) const; + +protected: + ClangBackEnd::ProjectParts projects; + ClangBackEnd::UnsavedFiles unsavedFiles; + ClangBackEnd::TranslationUnits translationUnits{projects, unsavedFiles}; + ClangBackEnd::TranslationUnit translationUnit; + DummyIpcClient dummyClientInterface; + + Utf8String filePath1 = Utf8StringLiteral(TESTDATA_DIR"/translationunits.cpp"); + Utf8String projectPartId{Utf8StringLiteral("/path/to/projectfile")}; + + ClangBackEnd::Jobs jobs{translationUnits, unsavedFiles, projects, dummyClientInterface}; +}; + +TEST_F(Jobs, ProcessEmptyQueue) +{ + const JobRequests jobsStarted = jobs.process(); + + ASSERT_THAT(jobsStarted.size(), Eq(0)); + ASSERT_THAT(jobs.runningJobs(), Eq(0)); +} + +TEST_F(Jobs, ProcessQueueWithSingleJob) +{ + jobs.add(createJobRequest(filePath1, JobRequest::Type::UpdateDocumentAnnotations)); + + const JobRequests jobsStarted = jobs.process(); + + ASSERT_THAT(jobsStarted.size(), Eq(1)); + ASSERT_THAT(jobs.runningJobs(), Eq(1)); +} + +TEST_F(Jobs, ProcessQueueUntilEmpty) +{ + jobs.add(createJobRequest(filePath1, JobRequest::Type::UpdateDocumentAnnotations)); + jobs.add(createJobRequest(filePath1, JobRequest::Type::UpdateDocumentAnnotations)); + jobs.add(createJobRequest(filePath1, JobRequest::Type::UpdateDocumentAnnotations)); + + jobs.process(); + + waitUntilJobChainFinished(); +} + +TEST_F(Jobs, IsJobRunning) +{ + jobs.add(createJobRequest(filePath1, JobRequest::Type::UpdateDocumentAnnotations)); + jobs.process(); + + const bool isJobRunning = jobs.isJobRunning(filePath1, projectPartId); + + ASSERT_TRUE(isJobRunning); +} + +void Jobs::SetUp() +{ + projects.createOrUpdate({ProjectPartContainer(projectPartId)}); + + const QVector fileContainer{FileContainer(filePath1, projectPartId)}; + translationUnit = translationUnits.create(fileContainer).front(); + translationUnits.setVisibleInEditors({filePath1}); + translationUnits.setUsedByCurrentEditor(filePath1); +} + +void Jobs::TearDown() +{ + ASSERT_TRUE(waitUntilAllJobsFinished()); // QFuture/QFutureWatcher is implemented with events +} + +bool Jobs::waitUntilAllJobsFinished(int timeOutInMs) const +{ + const auto noJobsRunningAnymore = [this](){ return jobs.runningJobs() == 0; }; + + return TestUtils::processEventsUntilTrue(noJobsRunningAnymore, timeOutInMs); +} + +bool Jobs::waitUntilJobChainFinished(int timeOutInMs) const +{ + const auto noJobsRunningAnymore = [this]() { + return jobs.runningJobs() == 0 && jobs.queue().isEmpty(); + }; + + return TestUtils::processEventsUntilTrue(noJobsRunningAnymore, timeOutInMs); +} + +JobRequest Jobs::createJobRequest(const Utf8String &filePath, + JobRequest::Type type) const +{ + JobRequest jobRequest; + jobRequest.type = type; + jobRequest.requirements = JobRequest::requirementsForType(type); + jobRequest.filePath = filePath; + jobRequest.projectPartId = projectPartId; + jobRequest.unsavedFilesChangeTimePoint = unsavedFiles.lastChangeTimePoint(); + jobRequest.documentRevision = translationUnit.documentRevision(); + jobRequest.projectChangeTimePoint = projects.project(projectPartId).lastChangeTimePoint(); + + return jobRequest; +} + +} // anonymous diff --git a/tests/unit/unittest/clangrequestdocumentannotationsjobtest.cpp b/tests/unit/unittest/clangrequestdocumentannotationsjobtest.cpp new file mode 100644 index 00000000000..37a9939465f --- /dev/null +++ b/tests/unit/unittest/clangrequestdocumentannotationsjobtest.cpp @@ -0,0 +1,85 @@ +/**************************************************************************** +** +** 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 "clangasyncjobtest.h" + +#include + +using namespace ClangBackEnd; + +using testing::_; + +namespace { + +class RequestDocumentAnnotationsJob : public ClangAsyncJobTest +{ +protected: + void SetUp() override { BaseSetUp(JobRequest::Type::RequestDocumentAnnotations, job); } + +protected: + ClangBackEnd::RequestDocumentAnnotationsJob job; +}; + +TEST_F(RequestDocumentAnnotationsJob, PrepareAsyncRun) +{ + job.setContext(jobContext); + + ASSERT_TRUE(job.prepareAsyncRun()); +} + +TEST_F(RequestDocumentAnnotationsJob, RunAsync) +{ + job.setContext(jobContext); + job.prepareAsyncRun(); + + job.runAsync(); + + ASSERT_TRUE(waitUntilJobFinished(job)); +} + +TEST_F(RequestDocumentAnnotationsJob, SendAnnotations) +{ + job.setContext(jobContextWithMockClient); + job.prepareAsyncRun(); + EXPECT_CALL(mockIpcClient, documentAnnotationsChanged(_)).Times(1); + + job.runAsync(); + + ASSERT_TRUE(waitUntilJobFinished(job)); +} + +TEST_F(RequestDocumentAnnotationsJob, DontSendAnnotationsIfDocumentWasClosed) +{ + job.setContext(jobContextWithMockClient); + job.prepareAsyncRun(); + EXPECT_CALL(mockIpcClient, documentAnnotationsChanged(_)).Times(0); + + job.runAsync(); + translationUnits.remove({FileContainer{filePath, projectPartId}}); + + ASSERT_TRUE(waitUntilJobFinished(job)); +} + +} // anonymous diff --git a/tests/unit/unittest/clangupdatedocumentannotationsjobtest.cpp b/tests/unit/unittest/clangupdatedocumentannotationsjobtest.cpp new file mode 100644 index 00000000000..76d5e853db4 --- /dev/null +++ b/tests/unit/unittest/clangupdatedocumentannotationsjobtest.cpp @@ -0,0 +1,112 @@ +/**************************************************************************** +** +** 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 "clangasyncjobtest.h" + +#include + +using namespace ClangBackEnd; + +using testing::Not; +using testing::_; + +namespace { + +class UpdateDocumentAnnotationsJob : public ClangAsyncJobTest +{ +protected: + void SetUp() override { BaseSetUp(JobRequest::Type::UpdateDocumentAnnotations, job); } + +protected: + ClangBackEnd::UpdateDocumentAnnotationsJob job; +}; + +TEST_F(UpdateDocumentAnnotationsJob, PrepareAsyncRun) +{ + job.setContext(jobContext); + + ASSERT_TRUE(job.prepareAsyncRun()); +} + +TEST_F(UpdateDocumentAnnotationsJob, RunAsync) +{ + job.setContext(jobContext); + job.prepareAsyncRun(); + + job.runAsync(); + + ASSERT_TRUE(waitUntilJobFinished(job)); +} + +TEST_F(UpdateDocumentAnnotationsJob, SendAnnotations) +{ + job.setContext(jobContextWithMockClient); + job.prepareAsyncRun(); + EXPECT_CALL(mockIpcClient, documentAnnotationsChanged(_)).Times(1); + + job.runAsync(); + + ASSERT_TRUE(waitUntilJobFinished(job)); +} + +TEST_F(UpdateDocumentAnnotationsJob, DontSendAnnotationsIfDocumentWasClosed) +{ + job.setContext(jobContextWithMockClient); + job.prepareAsyncRun(); + EXPECT_CALL(mockIpcClient, documentAnnotationsChanged(_)).Times(0); + + job.runAsync(); + translationUnits.remove({FileContainer{filePath, projectPartId}}); + + ASSERT_TRUE(waitUntilJobFinished(job)); +} + +TEST_F(UpdateDocumentAnnotationsJob, DontSendAnnotationsIfDocumentRevisionChanged) +{ + job.setContext(jobContextWithMockClient); + job.prepareAsyncRun(); + EXPECT_CALL(mockIpcClient, documentAnnotationsChanged(_)).Times(0); + + job.runAsync(); + translationUnits.update({FileContainer(filePath, projectPartId, Utf8String(), true, 99)}); + + ASSERT_TRUE(waitUntilJobFinished(job)); +} + +TEST_F(UpdateDocumentAnnotationsJob, UpdatesTranslationUnit) +{ + const time_point timePointBefore = translationUnit.lastProjectPartChangeTimePoint(); + const QSet dependendOnFilesBefore = translationUnit.dependedFilePaths(); + job.setContext(jobContext); + job.prepareAsyncRun(); + + job.runAsync(); + ASSERT_TRUE(waitUntilJobFinished(job)); + + ASSERT_THAT(timePointBefore, Not(translationUnit.lastProjectPartChangeTimePoint())); + ASSERT_THAT(dependendOnFilesBefore, Not(translationUnit.dependedFilePaths())); +} + +} // anonymous diff --git a/tests/unit/unittest/codecompletionsextractortest.cpp b/tests/unit/unittest/codecompletionsextractortest.cpp index 265d2334f52..6965f1a1211 100644 --- a/tests/unit/unittest/codecompletionsextractortest.cpp +++ b/tests/unit/unittest/codecompletionsextractortest.cpp @@ -31,6 +31,7 @@ #include #include #include +#include #include #include #include @@ -691,7 +692,7 @@ ClangCodeCompleteResults CodeCompletionsExtractor::getResults(const TranslationU const Utf8String nativeFilePath = FilePath::toNativeSeparators(translationUnit.filePath()); UnsavedFilesShallowArguments unsaved = unsavedFiles.shallowArguments(); - return ClangCodeCompleteResults(clang_codeCompleteAt(translationUnit.cxTranslationUnit(), + return ClangCodeCompleteResults(clang_codeCompleteAt(translationUnit.translationUnitCore().cxTranslationUnit(), nativeFilePath.constData(), line, column, diff --git a/tests/unit/unittest/cursortest.cpp b/tests/unit/unittest/cursortest.cpp index 3060b2feefd..8f167348e30 100644 --- a/tests/unit/unittest/cursortest.cpp +++ b/tests/unit/unittest/cursortest.cpp @@ -70,8 +70,8 @@ struct Data { {}, translationUnits}; TranslationUnitCore translationUnitCore{filePath, - translationUnit.index(), - translationUnit.cxTranslationUnit()}; + translationUnit.translationUnitCore().cxIndex(), + translationUnit.translationUnitCore().cxTranslationUnit()}; }; class Cursor : public ::testing::Test diff --git a/tests/unit/unittest/dummyclangipcclient.h b/tests/unit/unittest/dummyclangipcclient.h new file mode 100644 index 00000000000..c12bdcd832b --- /dev/null +++ b/tests/unit/unittest/dummyclangipcclient.h @@ -0,0 +1,41 @@ +/**************************************************************************** +** +** 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 + +class DummyIpcClient: public ClangBackEnd::ClangCodeModelClientInterface +{ +public: + void dispatch(const ClangBackEnd::MessageEnvelop &) override {} + + void alive() override {} + void echo(const ClangBackEnd::EchoMessage &) override {} + void codeCompleted(const ClangBackEnd::CodeCompletedMessage &) override {} + void translationUnitDoesNotExist(const ClangBackEnd::TranslationUnitDoesNotExistMessage &) override {} + void projectPartsDoNotExist(const ClangBackEnd::ProjectPartsDoNotExistMessage &) override {} + void documentAnnotationsChanged(const ClangBackEnd::DocumentAnnotationsChangedMessage &) override {} +}; diff --git a/tests/unit/unittest/highlightingmarksreportertest.cpp b/tests/unit/unittest/highlightingmarksreportertest.cpp index 11d1cbf5edd..ad82c5fd0ea 100644 --- a/tests/unit/unittest/highlightingmarksreportertest.cpp +++ b/tests/unit/unittest/highlightingmarksreportertest.cpp @@ -24,8 +24,10 @@ ****************************************************************************/ #include +#include #include #include +#include #include #include #include diff --git a/tests/unit/unittest/highlightingmarkstest.cpp b/tests/unit/unittest/highlightingmarkstest.cpp index d79131e265a..ba65cacbb9b 100644 --- a/tests/unit/unittest/highlightingmarkstest.cpp +++ b/tests/unit/unittest/highlightingmarkstest.cpp @@ -113,8 +113,8 @@ struct Data { {}, translationUnits}; TranslationUnitCore translationUnitCore{filePath, - translationUnit.index(), - translationUnit.cxTranslationUnit()}; + translationUnit.translationUnitCore().cxIndex(), + translationUnit.translationUnitCore().cxTranslationUnit()}; }; class HighlightingMarks : public ::testing::Test diff --git a/tests/unit/unittest/mockclangcodemodelclient.h b/tests/unit/unittest/mockclangcodemodelclient.h index 5bda81283cd..be4c16cd933 100644 --- a/tests/unit/unittest/mockclangcodemodelclient.h +++ b/tests/unit/unittest/mockclangcodemodelclient.h @@ -25,7 +25,14 @@ #pragma once -#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include diff --git a/tests/unit/unittest/skippedsourcerangestest.cpp b/tests/unit/unittest/skippedsourcerangestest.cpp index c75eaa89848..d60525ce962 100644 --- a/tests/unit/unittest/skippedsourcerangestest.cpp +++ b/tests/unit/unittest/skippedsourcerangestest.cpp @@ -101,8 +101,8 @@ struct Data { {}, translationUnits}; TranslationUnitCore translationUnitCore{filePath, - translationUnit.index(), - translationUnit.cxTranslationUnit()}; + translationUnit.translationUnitCore().cxIndex(), + translationUnit.translationUnitCore().cxTranslationUnit()}; }; class SkippedSourceRanges : public ::testing::Test diff --git a/tests/unit/unittest/sourcerangetest.cpp b/tests/unit/unittest/sourcerangetest.cpp index 6299e5fb47d..6fc17e36a7e 100644 --- a/tests/unit/unittest/sourcerangetest.cpp +++ b/tests/unit/unittest/sourcerangetest.cpp @@ -106,8 +106,8 @@ struct Data { Utf8StringVector(), translationUnits}; TranslationUnitCore translationUnitCore{filePath, - translationUnit.index(), - translationUnit.cxTranslationUnit()}; + translationUnit.translationUnitCore().cxIndex(), + translationUnit.translationUnitCore().cxTranslationUnit()}; std::unique_ptr d; }; diff --git a/tests/unit/unittest/testutils.cpp b/tests/unit/unittest/testutils.cpp new file mode 100644 index 00000000000..7368dce5825 --- /dev/null +++ b/tests/unit/unittest/testutils.cpp @@ -0,0 +1,54 @@ +/**************************************************************************** +** +** 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 "testutils.h" + +#include +#include +#include + +#include + +bool TestUtils::processEventsUntilTrue(std::function condition, int timeOutInMs) +{ + if (condition()) + return true; + + QTime time; + time.start(); + + forever { + if (condition()) + return true; + + if (time.elapsed() > timeOutInMs) { + qWarning() << "Timeout of" << timeOutInMs + << "reached in processEventsAndWaitUntilTrue()"; + return false; + } + + QCoreApplication::processEvents(); + } +} diff --git a/tests/unit/unittest/testutils.h b/tests/unit/unittest/testutils.h new file mode 100644 index 00000000000..fd856cc28aa --- /dev/null +++ b/tests/unit/unittest/testutils.h @@ -0,0 +1,34 @@ +/**************************************************************************** +** +** 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 + +class TestUtils +{ +public: + static bool processEventsUntilTrue(std::function condition, int timeOutInMs); +}; diff --git a/tests/unit/unittest/translationunitstest.cpp b/tests/unit/unittest/translationunitstest.cpp index 9f691842331..476fd41a1d1 100644 --- a/tests/unit/unittest/translationunitstest.cpp +++ b/tests/unit/unittest/translationunitstest.cpp @@ -23,11 +23,6 @@ ** ****************************************************************************/ -#include -#include -#include -#include -#include #include #include #include @@ -40,12 +35,8 @@ #include #include -#include - #include -#include "mocksenddocumentannotationscallback.h" - #include #include #include @@ -55,8 +46,6 @@ using ClangBackEnd::TranslationUnit; using ClangBackEnd::UnsavedFiles; using ClangBackEnd::ProjectPart; using ClangBackEnd::ProjectPartContainer; -using ClangBackEnd::DocumentAnnotationsChangedMessage; -using ClangBackEnd::DocumentAnnotationsSendState; using testing::IsNull; using testing::NotNull; @@ -84,16 +73,11 @@ class TranslationUnits : public ::testing::Test { protected: void SetUp() override; - void createDirtyTranslationUnitAndDeleteFile(); - void sendAllDocumentAnnotations(); - void sendAllDocumentAnnotationsForCurrentEditor(); - void sendAllDocumentAnnotationsForVisibleEditors(); protected: ClangBackEnd::ProjectParts projects; ClangBackEnd::UnsavedFiles unsavedFiles; ClangBackEnd::TranslationUnits translationUnits{projects, unsavedFiles}; - MockSendDocumentAnnotationsCallback mockSendDocumentAnnotationsCallback; const Utf8String filePath = Utf8StringLiteral(TESTDATA_DIR"/translationunits.cpp"); const Utf8String headerPath = Utf8StringLiteral(TESTDATA_DIR"/translationunits.h"); const Utf8String nonExistingFilePath = Utf8StringLiteral("foo.cpp"); @@ -133,15 +117,6 @@ TEST_F(TranslationUnits, DoNotThrowForAddingNonExistingFileWithUnsavedContent) ASSERT_NO_THROW(translationUnits.create({fileContainer})); } -TEST_F(TranslationUnits, DoNotSendDocumentAnnotationsForVanishedMainFile) -{ - createDirtyTranslationUnitAndDeleteFile(); - - EXPECT_CALL(mockSendDocumentAnnotationsCallback, sendDocumentAnnotations()).Times(0); - - sendAllDocumentAnnotations(); -} - TEST_F(TranslationUnits, Add) { ClangBackEnd::FileContainer fileContainer(filePath, projectPartId, Utf8StringVector(), 74u); @@ -219,7 +194,7 @@ TEST_F(TranslationUnits, UpdateUnsavedFileAndCheckForReparse) ASSERT_TRUE(translationUnits.translationUnit(filePath, projectPartId).isNeedingReparse()); } -TEST_F(TranslationUnits, UpdateUnsavedFileAndCheckForDiagnostics) +TEST_F(TranslationUnits, RemoveFileAndCheckForReparse) { ClangBackEnd::FileContainer fileContainer(filePath, projectPartId, Utf8StringVector(), 74u); ClangBackEnd::FileContainer headerContainer(headerPath, projectPartId, Utf8StringVector(), 74u); @@ -227,56 +202,10 @@ TEST_F(TranslationUnits, UpdateUnsavedFileAndCheckForDiagnostics) translationUnits.create({fileContainer, headerContainer}); TranslationUnit translationUnit = translationUnits.translationUnit(filePath, projectPartId); translationUnit.parse(); - translationUnit.diagnostics(); - - translationUnits.update({headerContainerWithUnsavedContent}); - - ASSERT_TRUE(translationUnits.translationUnit(filePath, projectPartId).hasNewDiagnostics()); -} - -TEST_F(TranslationUnits, RemoveFileAndCheckForDiagnostics) -{ - ClangBackEnd::FileContainer fileContainer(filePath, projectPartId, Utf8StringVector(), 74u); - ClangBackEnd::FileContainer headerContainer(headerPath, projectPartId, Utf8StringVector(), 74u); - ClangBackEnd::FileContainer headerContainerWithUnsavedContent(headerPath, projectPartId, Utf8String(), true, 75u); - translationUnits.create({fileContainer, headerContainer}); - TranslationUnit translationUnit = translationUnits.translationUnit(filePath, projectPartId); - translationUnit.parse(); - translationUnit.diagnostics(); translationUnits.remove({headerContainerWithUnsavedContent}); - ASSERT_TRUE(translationUnits.translationUnit(filePath, projectPartId).hasNewDiagnostics()); -} - -TEST_F(TranslationUnits, UpdateUnsavedFileAndCheckForHighlightingMarks) -{ - ClangBackEnd::FileContainer fileContainer(filePath, projectPartId, Utf8StringVector(), 74u); - ClangBackEnd::FileContainer headerContainer(headerPath, projectPartId, Utf8StringVector(), 74u); - ClangBackEnd::FileContainer headerContainerWithUnsavedContent(headerPath, projectPartId, Utf8String(), true, 75u); - translationUnits.create({fileContainer, headerContainer}); - TranslationUnit translationUnit = translationUnits.translationUnit(filePath, projectPartId); - translationUnit.parse(); - translationUnit.highlightingMarks(); - - translationUnits.update({headerContainerWithUnsavedContent}); - - ASSERT_TRUE(translationUnits.translationUnit(filePath, projectPartId).hasNewHighlightingMarks()); -} - -TEST_F(TranslationUnits, RemoveFileAndCheckForHighlightingMarks) -{ - ClangBackEnd::FileContainer fileContainer(filePath, projectPartId, Utf8StringVector(), 74u); - ClangBackEnd::FileContainer headerContainer(headerPath, projectPartId, Utf8StringVector(), 74u); - ClangBackEnd::FileContainer headerContainerWithUnsavedContent(headerPath, projectPartId, Utf8String(), true, 75u); - translationUnits.create({fileContainer, headerContainer}); - TranslationUnit translationUnit = translationUnits.translationUnit(filePath, projectPartId); - translationUnit.parse(); - translationUnit.highlightingMarks(); - - translationUnits.remove({headerContainerWithUnsavedContent}); - - ASSERT_TRUE(translationUnits.translationUnit(filePath, projectPartId).hasNewHighlightingMarks()); + ASSERT_TRUE(translationUnits.translationUnit(filePath, projectPartId).isNeedingReparse()); } TEST_F(TranslationUnits, DontGetNewerFileContainerIfRevisionIsTheSame) @@ -346,12 +275,12 @@ TEST_F(TranslationUnits, HasTranslationUnit) { translationUnits.create({{filePath, projectPartId}}); - ASSERT_TRUE(translationUnits.hasTranslationUnit(filePath)); + ASSERT_TRUE(translationUnits.hasTranslationUnit(filePath, projectPartId)); } TEST_F(TranslationUnits, HasNotTranslationUnit) { - ASSERT_FALSE(translationUnits.hasTranslationUnit(filePath)); + ASSERT_FALSE(translationUnits.hasTranslationUnit(filePath, projectPartId)); } TEST_F(TranslationUnits, isUsedByCurrentEditor) @@ -416,167 +345,10 @@ TEST_F(TranslationUnits, IsNotVisibleEditorAfterBeingVisible) ASSERT_FALSE(translationUnit.isVisibleInEditor()); } -TEST_F(TranslationUnits, DoNotSendDocumentAnnotationsIfThereIsNothingToSend) -{ - EXPECT_CALL(mockSendDocumentAnnotationsCallback, sendDocumentAnnotations()).Times(0); - - sendAllDocumentAnnotations(); -} - -TEST_F(TranslationUnits, DoNotSendDocumentAnnotationsAfterTranslationUnitCreation) -{ - translationUnits.create({fileContainer, headerContainer}); - - EXPECT_CALL(mockSendDocumentAnnotationsCallback, sendDocumentAnnotations()).Times(0); - - sendAllDocumentAnnotations(); -} - -TEST_F(TranslationUnits, DoNotSendDocumentAnnotationsAfterGettingDocumentAnnotations) -{ - translationUnits.create({fileContainer, headerContainer}); - auto translationUnit = translationUnits.translationUnit(fileContainer); - translationUnit.setIsVisibleInEditor(true); - translationUnit.diagnostics(); // Reset - translationUnit.highlightingMarks(); // Reset - - EXPECT_CALL(mockSendDocumentAnnotationsCallback, sendDocumentAnnotations()).Times(0); - - sendAllDocumentAnnotations(); -} - -TEST_F(TranslationUnits, SendDocumentAnnotationsForCurrentEditor) -{ - translationUnits.create({fileContainer, headerContainer}); - auto translationUnit = translationUnits.translationUnit(fileContainer); - translationUnit.setIsUsedByCurrentEditor(true); - - EXPECT_CALL(mockSendDocumentAnnotationsCallback, sendDocumentAnnotations()).Times(1); - - sendAllDocumentAnnotationsForCurrentEditor(); -} - -TEST_F(TranslationUnits, DoNotSendDocumentAnnotationsForCurrentEditorIfThereIsNoCurrentEditor) -{ - translationUnits.create({fileContainer, headerContainer}); - - EXPECT_CALL(mockSendDocumentAnnotationsCallback, sendDocumentAnnotations()).Times(0); - - sendAllDocumentAnnotationsForCurrentEditor(); -} - -TEST_F(TranslationUnits, DoNotSendDocumentAnnotationsForCurrentEditorAfterGettingDocumentAnnotations) -{ - translationUnits.create({fileContainer, headerContainer}); - auto translationUnit = translationUnits.translationUnit(fileContainer); - translationUnit.setIsUsedByCurrentEditor(true); - translationUnit.diagnostics(); // Reset - translationUnit.highlightingMarks(); // Reset - - EXPECT_CALL(mockSendDocumentAnnotationsCallback, sendDocumentAnnotations()).Times(0); - - sendAllDocumentAnnotationsForCurrentEditor(); -} - -TEST_F(TranslationUnits, DoNotSendDocumentAnnotationsForVisibleEditorIfThereAreNoVisibleEditors) -{ - translationUnits.create({fileContainer, headerContainer}); - - EXPECT_CALL(mockSendDocumentAnnotationsCallback, sendDocumentAnnotations()).Times(0); - - translationUnits.sendDocumentAnnotationsForVisibleEditors(); -} - -TEST_F(TranslationUnits, SendDocumentAnnotationsForVisibleEditors) -{ - translationUnits.create({fileContainer, headerContainer}); - auto fileTranslationUnit = translationUnits.translationUnit(fileContainer); - fileTranslationUnit.setIsVisibleInEditor(true); - auto headerTranslationUnit = translationUnits.translationUnit(headerContainer); - headerTranslationUnit.setIsVisibleInEditor(true); - - EXPECT_CALL(mockSendDocumentAnnotationsCallback, sendDocumentAnnotations()).Times(2); - - sendAllDocumentAnnotationsForVisibleEditors(); -} - -TEST_F(TranslationUnits, SendDocumentAnnotationsOnlyOnceForVisibleEditor) -{ - translationUnits.create({fileContainer, headerContainer}); - auto fileTranslationUnit = translationUnits.translationUnit(fileContainer); - fileTranslationUnit.setIsVisibleInEditor(true); - auto headerTranslationUnit = translationUnits.translationUnit(headerContainer); - headerTranslationUnit.setIsVisibleInEditor(true); - headerTranslationUnit.diagnostics(); // Reset - headerTranslationUnit.highlightingMarks(); // Reset - - EXPECT_CALL(mockSendDocumentAnnotationsCallback, sendDocumentAnnotations()).Times(1); - - sendAllDocumentAnnotationsForVisibleEditors(); -} - -TEST_F(TranslationUnits, SendDocumentAnnotationsAfterProjectPartChange) -{ - translationUnits.create({fileContainer, headerContainer}); - auto fileTranslationUnit = translationUnits.translationUnit(fileContainer); - fileTranslationUnit.setIsVisibleInEditor(true); - fileTranslationUnit.diagnostics(); // Reset - fileTranslationUnit.highlightingMarks(); // Reset - projects.createOrUpdate({ProjectPartContainer(projectPartId, {Utf8StringLiteral("-DNEW")})}); - translationUnits.setTranslationUnitsDirtyIfProjectPartChanged(); - - EXPECT_CALL(mockSendDocumentAnnotationsCallback, sendDocumentAnnotations()).Times(1); - - sendAllDocumentAnnotationsForVisibleEditors(); -} - void TranslationUnits::SetUp() { projects.createOrUpdate({ProjectPartContainer(projectPartId)}); projects.createOrUpdate({ProjectPartContainer(otherProjectPartId)}); - - auto callback = [&] (const DocumentAnnotationsChangedMessage &) { - mockSendDocumentAnnotationsCallback.sendDocumentAnnotations(); - }; - translationUnits.setSendDocumentAnnotationsCallback(callback); -} - -void TranslationUnits::createDirtyTranslationUnitAndDeleteFile() -{ - QTemporaryFile temporaryFile(QLatin1String("XXXXXX.cpp")); - EXPECT_TRUE(temporaryFile.open()); - const QString temporaryFilePath = Utf8String::fromString(temporaryFile.fileName()); - - ClangBackEnd::FileContainer fileContainer(temporaryFilePath, - projectPartId, Utf8String(), true); - translationUnits.create({fileContainer}); - auto translationUnit = translationUnits.translationUnit(fileContainer); - translationUnit.setIsUsedByCurrentEditor(true); - translationUnit.setDirtyIfDependencyIsMet(translationUnit.filePath()); -} - -void TranslationUnits::sendAllDocumentAnnotations() -{ - auto sendState = DocumentAnnotationsSendState::MaybeThereAreDocumentAnnotations; - - while (sendState == DocumentAnnotationsSendState::MaybeThereAreDocumentAnnotations) - sendState = translationUnits.sendDocumentAnnotations(); -} - -void TranslationUnits::sendAllDocumentAnnotationsForCurrentEditor() -{ - auto sendState = DocumentAnnotationsSendState::MaybeThereAreDocumentAnnotations; - - while (sendState == DocumentAnnotationsSendState::MaybeThereAreDocumentAnnotations) - sendState = translationUnits.sendDocumentAnnotationsForCurrentEditor(); -} - -void TranslationUnits::sendAllDocumentAnnotationsForVisibleEditors() -{ - auto sendState = DocumentAnnotationsSendState::MaybeThereAreDocumentAnnotations; - - while (sendState == DocumentAnnotationsSendState::MaybeThereAreDocumentAnnotations) - sendState = translationUnits.sendDocumentAnnotationsForVisibleEditors(); } } diff --git a/tests/unit/unittest/translationunittest.cpp b/tests/unit/unittest/translationunittest.cpp index 31b3a6ee33c..fee7d6cd242 100644 --- a/tests/unit/unittest/translationunittest.cpp +++ b/tests/unit/unittest/translationunittest.cpp @@ -24,6 +24,7 @@ ****************************************************************************/ #include +#include #include #include #include @@ -34,6 +35,7 @@ #include #include #include +#include #include #include #include @@ -59,6 +61,7 @@ using ClangBackEnd::UnsavedFiles; using ClangBackEnd::ProjectPart; using ClangBackEnd::ProjectPartContainer; using ClangBackEnd::TranslationUnits; +using ClangBackEnd::TranslationUnitUpdateResult; using testing::IsNull; using testing::NotNull; @@ -122,21 +125,21 @@ TEST_F(TranslationUnit, ThrowExceptionForGettingIndexForInvalidUnit) { ::TranslationUnit translationUnit; - ASSERT_THROW(translationUnit.index(), ClangBackEnd::TranslationUnitIsNullException); + ASSERT_THROW(translationUnit.translationUnitCore().cxIndex(), ClangBackEnd::TranslationUnitIsNullException); } TEST_F(TranslationUnit, ThrowExceptionForGettingCxTranslationUnitForInvalidUnit) { ::TranslationUnit translationUnit; - ASSERT_THROW(translationUnit.cxTranslationUnit(), ClangBackEnd::TranslationUnitIsNullException); + ASSERT_THROW(translationUnit.translationUnitCore().cxIndex(), ClangBackEnd::TranslationUnitIsNullException); } TEST_F(TranslationUnit, CxTranslationUnitGetterIsNonNullForParsedUnit) { translationUnit.parse(); - ASSERT_THAT(translationUnit.cxTranslationUnit(), NotNull()); + ASSERT_THAT(translationUnit.translationUnitCore().cxIndex(), NotNull()); } TEST_F(TranslationUnit, ThrowExceptionIfGettingFilePathForNullUnit) @@ -156,7 +159,7 @@ TEST_F(TranslationUnit, ResetedTranslationUnitIsNull) TEST_F(TranslationUnit, LastCommandLineArgumentIsFilePath) { const Utf8String nativeFilePath = FilePath::toNativeSeparators(translationUnitFilePath); - const auto arguments = translationUnit.commandLineArguments(); + const auto arguments = translationUnit.createUpdater().commandLineArguments(); ASSERT_THAT(arguments.at(arguments.count() - 1), Eq(nativeFilePath)); } @@ -265,94 +268,48 @@ TEST_F(TranslationUnit, IsNotIntactForDeletedFile) ASSERT_FALSE(translationUnit.isIntact()); } -TEST_F(TranslationUnit, HasNewDiagnosticsAfterParse) +TEST_F(TranslationUnit, DoesNotNeedReparseAfterParse) { translationUnit.parse(); - ASSERT_TRUE(translationUnit.hasNewDiagnostics()); + ASSERT_FALSE(translationUnit.isNeedingReparse()); } -TEST_F(TranslationUnit, HasNewDiagnosticsAfterChangeOfMainFile) +TEST_F(TranslationUnit, NeedsReparseAfterMainFileChanged) { translationUnit.parse(); translationUnit.setDirtyIfDependencyIsMet(translationUnitFilePath); - ASSERT_TRUE(translationUnit.hasNewDiagnostics()); + ASSERT_TRUE(translationUnit.isNeedingReparse()); } -TEST_F(TranslationUnit, HasNoNewDiagnosticsForIndependendFile) -{ - translationUnit.parse(); - translationUnit.diagnostics(); // Reset hasNewDiagnostics - - translationUnit.setDirtyIfDependencyIsMet(Utf8StringLiteral(TESTDATA_DIR"/otherfiles.h")); - - ASSERT_FALSE(translationUnit.hasNewDiagnostics()); -} - -TEST_F(TranslationUnit, HasNewDiagnosticsForDependendFile) +TEST_F(TranslationUnit, NeedsReparseAfterIncludedFileChanged) { translationUnit.parse(); translationUnit.setDirtyIfDependencyIsMet(Utf8StringLiteral(TESTDATA_DIR"/translationunits.h")); - ASSERT_TRUE(translationUnit.hasNewDiagnostics()); + ASSERT_TRUE(translationUnit.isNeedingReparse()); } -TEST_F(TranslationUnit, HasNoNewDiagnosticsAfterGettingDiagnostics) +TEST_F(TranslationUnit, DoesNotNeedReparseAfterNotIncludedFileChanged) { translationUnit.parse(); - translationUnit.setDirtyIfDependencyIsMet(translationUnitFilePath); - - translationUnit.diagnostics(); // Reset hasNewDiagnostics - - ASSERT_FALSE(translationUnit.hasNewDiagnostics()); -} - -TEST_F(TranslationUnit, HasNewHighlightingMarksAfterCreation) -{ - translationUnit.parse(); - - ASSERT_TRUE(translationUnit.hasNewHighlightingMarks()); -} - -TEST_F(TranslationUnit, HasNewHighlightingMarksForMainFile) -{ - translationUnit.parse(); - - translationUnit.setDirtyIfDependencyIsMet(translationUnitFilePath); - - ASSERT_TRUE(translationUnit.hasNewHighlightingMarks()); -} - -TEST_F(TranslationUnit, HasNoNewHighlightingMarksForIndependendFile) -{ - translationUnit.parse(); - translationUnit.highlightingMarks(); translationUnit.setDirtyIfDependencyIsMet(Utf8StringLiteral(TESTDATA_DIR"/otherfiles.h")); - ASSERT_FALSE(translationUnit.hasNewHighlightingMarks()); + ASSERT_FALSE(translationUnit.isNeedingReparse()); } -TEST_F(TranslationUnit, HasNewHighlightingMarksForDependendFile) -{ - translationUnit.parse(); - - translationUnit.setDirtyIfDependencyIsMet(Utf8StringLiteral(TESTDATA_DIR"/translationunits.h")); - - ASSERT_TRUE(translationUnit.hasNewHighlightingMarks()); -} - -TEST_F(TranslationUnit, HasNoNewHighlightingMarksAfterGettingHighlightingMarks) +TEST_F(TranslationUnit, DoesNotNeedReparseAfterReparse) { translationUnit.parse(); translationUnit.setDirtyIfDependencyIsMet(translationUnitFilePath); - translationUnit.highlightingMarks(); + translationUnit.reparse(); - ASSERT_FALSE(translationUnit.hasNewHighlightingMarks()); + ASSERT_FALSE(translationUnit.isNeedingReparse()); } TEST_F(TranslationUnit, SetDirtyIfProjectPartIsOutdated) @@ -375,6 +332,30 @@ TEST_F(TranslationUnit, SetNotDirtyIfProjectPartIsNotOutdated) ASSERT_FALSE(translationUnit.isNeedingReparse()); } +TEST_F(TranslationUnit, IncorporateUpdaterResultResetsDirtyness) +{ + translationUnit.setDirtyIfDependencyIsMet(translationUnit.filePath()); + TranslationUnitUpdateResult result; + result.reparsed = true; + result.needsToBeReparsedChangeTimePoint = translationUnit.isNeededReparseChangeTimePoint(); + + translationUnit.incorporateUpdaterResult(result); + + ASSERT_FALSE(translationUnit.isNeedingReparse()); +} + +TEST_F(TranslationUnit, IncorporateUpdaterResultDoesNotResetDirtynessIfItWasChanged) +{ + TranslationUnitUpdateResult result; + result.reparsed = true; + result.needsToBeReparsedChangeTimePoint = std::chrono::steady_clock::now(); + translationUnit.setDirtyIfDependencyIsMet(translationUnit.filePath()); + + translationUnit.incorporateUpdaterResult(result); + + ASSERT_TRUE(translationUnit.isNeedingReparse()); +} + void TranslationUnit::SetUp() { projects.createOrUpdate({ProjectPartContainer(projectPartId)}); diff --git a/tests/unit/unittest/unittest.pro b/tests/unit/unittest/unittest.pro index 9a9752369c7..fe688043a35 100644 --- a/tests/unit/unittest/unittest.pro +++ b/tests/unit/unittest/unittest.pro @@ -70,7 +70,15 @@ SOURCES += \ highlightingmarkstest.cpp \ sizedarraytest.cpp \ utf8positionfromlinecolumntest.cpp \ - translationunitupdatertest.cpp + translationunitupdatertest.cpp \ + testutils.cpp \ + clangasyncjobtest.cpp \ + clangcompletecodejobtest.cpp \ + clangcreateinitialdocumentpreamblejobtest.cpp \ + clangjobqueuetest.cpp \ + clangjobstest.cpp \ + clangrequestdocumentannotationsjobtest.cpp \ + clangupdatedocumentannotationsjobtest.cpp exists($$GOOGLEBENCHMARK_DIR) { SOURCES += \ @@ -82,8 +90,10 @@ HEADERS += \ mockclangcodemodelclient.h \ mockclangcodemodelserver.h \ spydummy.h \ + dummyclangipcclient.h \ matcher-diagnosticcontainer.h \ chunksreportedmonitor.h \ - mocksenddocumentannotationscallback.h + testutils.h \ + clangasyncjobtest.h OTHER_FILES += $$files(data/*)