/**************************************************************************** ** ** Copyright (C) 2017 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 "googletest.h" #include "filesystem-utilities.h" #include "mockclangpathwatcher.h" #include "mocksymbolscollector.h" #include "mocksymbolstorage.h" #include "mockfilepathcaching.h" #include "mocksqlitetransactionbackend.h" #include "mockbuilddependenciesstorage.h" #include #include #include #include #include #include #include #include #include #include #include #include namespace { using Utils::PathString; using ClangBackEnd::CompilerMacro; using ClangBackEnd::FileStatuses; using ClangBackEnd::FilePathId; using ClangBackEnd::FilePathIds; using ClangBackEnd::FilePathView; using ClangBackEnd::ProjectPartContainer; using ClangBackEnd::ProjectPartContainers; using ClangBackEnd::V2::FileContainers; using ClangBackEnd::SymbolEntries; using ClangBackEnd::SymbolEntry; using ClangBackEnd::SymbolIndexerTask; using ClangBackEnd::SymbolIndexerTaskQueue; using ClangBackEnd::TaskScheduler; using ClangBackEnd::ProcessorManager; using ClangBackEnd::SourceDependencies; using ClangBackEnd::SourceLocationEntries; using ClangBackEnd::SourceLocationEntry; using ClangBackEnd::SymbolKind; using ClangBackEnd::SourceLocationKind; using ClangBackEnd::UsedMacros; using OptionalProjectPartArtefact = Utils::optional; MATCHER_P(IsFileId, fileNameId, std::string(negation ? "isn't " : "is ") + PrintToString(ClangBackEnd::FilePathId(fileNameId))) { return arg == ClangBackEnd::FilePathId(fileNameId); } struct Data { Sqlite::Database database{":memory:", Sqlite::JournalMode::Memory}; ClangBackEnd::RefactoringDatabaseInitializer databaseInitializer{database}; ClangBackEnd::FilePathCaching filePathCache{database}; }; class Manager final : public ProcessorManager> { public: using Processor = NiceMock; Manager(const ClangBackEnd::GeneratedFiles &generatedFiles) : ProcessorManager(generatedFiles) {} protected: std::unique_ptr> createProcessor() const { return std::make_unique>(); } }; class SymbolIndexer : public testing::Test { protected: void SetUp() { ON_CALL(mockCollector, symbols()).WillByDefault(ReturnRef(symbolEntries)); ON_CALL(mockCollector, sourceLocations()).WillByDefault(ReturnRef(sourceLocations)); ON_CALL(mockCollector, sourceFiles()).WillByDefault(ReturnRef(sourceFileIds)); ON_CALL(mockCollector, usedMacros()).WillByDefault(ReturnRef(usedMacros)); ON_CALL(mockCollector, fileStatuses()).WillByDefault(ReturnRef(fileStatus)); ON_CALL(mockCollector, sourceDependencies()).WillByDefault(ReturnRef(sourceDependencies)); ON_CALL(mockSymbolStorage, fetchProjectPartArtefact(A())).WillByDefault(Return(artefact)); ON_CALL(mockBuildDependenciesStorage, fetchLowestLastModifiedTime(A())).WillByDefault(Return(-1)); ON_CALL(mockCollector, collectSymbols()).WillByDefault(Return(true)); mockCollector.setIsUsed(false); generatedFiles.update(unsaved); } void TearDown() { syncTasks(); } void syncTasks() { while (!indexerQueue.tasks().empty() || !indexerScheduler.futures().empty()) { indexerScheduler.syncTasks(); QCoreApplication::processEvents(); } } static void SetUpTestCase() { data = std::make_unique(); } static void TearDownTestCase() { data.reset(); } void touchFile(FilePathId filePathId) { std::ofstream ostream(std::string(filePathCache.filePath(filePathId)), std::ios::binary); ostream.write("\n", 1); ostream.close(); } FilePathId filePathId(Utils::SmallStringView path) const { return filePathCache.filePathId(ClangBackEnd::FilePathView(path)); } protected: static std::unique_ptr data; // it can be non const because data holds no tested classes using Scheduler = TaskScheduler; ClangBackEnd::FilePathCaching &filePathCache = data->filePathCache; ClangBackEnd::FilePathId main1PathId{filePathId(TESTDATA_DIR "/symbolindexer_main1.cpp")}; ClangBackEnd::FilePathId main2PathId{filePathId(TESTDATA_DIR "/symbolindexer_main2.cpp")}; ClangBackEnd::FilePathId header2PathId{filePathId(TESTDATA_DIR "/symbolindexer_header1.h")}; ClangBackEnd::FilePathId header1PathId{filePathId(TESTDATA_DIR "/symbolindexer_header2.h")}; PathString generatedFileName = "BuildDependencyCollector_generated_file.h"; ClangBackEnd::FilePathId generatedFilePathId21; ClangBackEnd::IncludeSearchPaths systemIncludeSearchPaths{ {"/includes", 1, ClangBackEnd::IncludeSearchPathType::BuiltIn}, {TESTDATA_DIR, 2, ClangBackEnd::IncludeSearchPathType::System}, {"/other/includes", 3, ClangBackEnd::IncludeSearchPathType::System}}; ClangBackEnd::IncludeSearchPaths projectIncludeSearchPaths{ {"/project/includes", 1, ClangBackEnd::IncludeSearchPathType::User}, {"/other/project/includes", 2, ClangBackEnd::IncludeSearchPathType::User}}; ProjectPartContainer projectPart1{"project1", {"-Wno-pragma-once-outside-header"}, {{"BAR", "1", 1}, {"FOO", "1", 2}}, Utils::clone(systemIncludeSearchPaths), Utils::clone(projectIncludeSearchPaths), {header1PathId}, {main1PathId}, Utils::Language::Cxx, Utils::LanguageVersion::CXX14, Utils::LanguageExtension::None}; ProjectPartContainer projectPart2{"project2", {"-Wno-pragma-once-outside-header"}, {{"BAR", "1", 1}, {"FOO", "0", 2}}, Utils::clone(systemIncludeSearchPaths), Utils::clone(projectIncludeSearchPaths), {header2PathId}, {main2PathId}, Utils::Language::Cxx, Utils::LanguageVersion::CXX14, Utils::LanguageExtension::None}; ProjectPartContainer projectPart3{"project3", {"-Wno-pragma-once-outside-header"}, {{"BAR", "1", 1}, {"FOO", "1", 2}}, Utils::clone(systemIncludeSearchPaths), Utils::clone(projectIncludeSearchPaths), {header1PathId}, {main1PathId}, Utils::Language::Cxx, Utils::LanguageVersion::CXX14, Utils::LanguageExtension::None}; FileContainers unsaved{{{TESTDATA_DIR, "query_simplefunction.h"}, "void f();", {}}}; SymbolEntries symbolEntries{{1, {"function", "function", SymbolKind::Function}}}; SourceLocationEntries sourceLocations{{1, 1, {42, 23}, SourceLocationKind::Declaration}}; FilePathIds sourceFileIds{1, 23}; UsedMacros usedMacros{{"Foo", 1}}; FileStatuses fileStatus{{2, 3, 4, false}}; SourceDependencies sourceDependencies{{1, 2}, {1, 3}}; Utils::SmallString systemIncludeSearchPathsText{ R"([["/includes", 1, 2], [")" TESTDATA_DIR R"(" ,2 , 3], ["/other/includes", 3, 3]])"}; Utils::SmallString projectIncludeSearchPathsText{ R"([["/project/includes", 1, 1], ["/other/project/includes", 2, 1]])"}; ClangBackEnd::ProjectPartArtefact artefact{R"(["-DFOO"])", R"([["FOO","1", 2],["BAR","1", 1]])", systemIncludeSearchPathsText, projectIncludeSearchPathsText, 74, Utils::Language::Cxx, Utils::LanguageVersion::CXX14, Utils::LanguageExtension::None}; ClangBackEnd::ProjectPartArtefact emptyArtefact{"", "", "", "", 74, Utils::Language::Cxx, Utils::LanguageVersion::CXX14, Utils::LanguageExtension::None}; Utils::optional nullArtefact; ClangBackEnd::ProjectPartPch projectPartPch{"/path/to/pch", 4}; NiceMock mockSqliteTransactionBackend; NiceMock mockSymbolStorage; NiceMock mockBuildDependenciesStorage; NiceMock mockPathWatcher; ClangBackEnd::FileStatusCache fileStatusCache{filePathCache}; ClangBackEnd::GeneratedFiles generatedFiles; Manager collectorManger{generatedFiles}; NiceMock> mockSetProgressCallback; ClangBackEnd::ProgressCounter progressCounter{mockSetProgressCallback.AsStdFunction()}; ClangBackEnd::SymbolIndexer indexer{indexerQueue, mockSymbolStorage, mockBuildDependenciesStorage, mockPathWatcher, filePathCache, fileStatusCache, mockSqliteTransactionBackend}; SymbolIndexerTaskQueue indexerQueue{indexerScheduler, progressCounter}; Scheduler indexerScheduler{collectorManger, indexerQueue, progressCounter, 1, ClangBackEnd::CallDoInMainThreadAfterFinished::Yes}; MockSymbolsCollector &mockCollector{static_cast(collectorManger.unusedProcessor())}; }; std::unique_ptr SymbolIndexer::data; TEST_F(SymbolIndexer, UpdateProjectPartsCallsAddFilesInCollector) { EXPECT_CALL(mockCollector, setFile(main1PathId, ElementsAre("clang++", "-Wno-pragma-once-outside-header", "-DNOMINMAX", "-x", "c++", "-std=c++14", "-nostdinc", "-nostdinc++", "-DBAR=1", "-DFOO=1", "-I", toNativePath("/project/includes").path(), "-I", toNativePath("/other/project/includes").path(), "-isystem", toNativePath(TESTDATA_DIR).path(), "-isystem", toNativePath("/other/includes").path(), "-isystem", toNativePath("/includes").path()))); indexer.updateProjectParts({projectPart1}); } TEST_F(SymbolIndexer, UpdateProjectPartsCallsAddFilesWithPrecompiledHeaderInCollector) { ON_CALL(mockSymbolStorage, fetchProjectPartArtefact(TypedEq(projectPart1.projectPartId))).WillByDefault(Return(emptyArtefact)); ON_CALL(mockSymbolStorage, fetchPrecompiledHeader(Eq(artefact.projectPartId))).WillByDefault(Return(projectPartPch)); EXPECT_CALL(mockCollector, setFile(main1PathId, ElementsAre("clang++", "-Wno-pragma-once-outside-header", "-DNOMINMAX", "-x", "c++", "-std=c++14", "-nostdinc", "-nostdinc++", "-DBAR=1", "-DFOO=1", "-I", toNativePath("/project/includes").path(), "-I", toNativePath("/other/project/includes").path(), "-isystem", toNativePath(TESTDATA_DIR).path(), "-isystem", toNativePath("/other/includes").path(), "-isystem", toNativePath("/includes").path(), "-Xclang", "-include-pch", "-Xclang", toNativePath("/path/to/pch").path()))); indexer.updateProjectParts({projectPart1}); } TEST_F(SymbolIndexer, UpdateProjectPartsCallsAddFilesWithoutPrecompiledHeaderInCollector) { ON_CALL(mockSymbolStorage, fetchProjectPartArtefact(TypedEq(projectPart1.projectPartId))).WillByDefault(Return(emptyArtefact)); EXPECT_CALL(mockCollector, setFile(main1PathId, ElementsAre("clang++", "-Wno-pragma-once-outside-header", "-DNOMINMAX", "-x", "c++", "-std=c++14", "-nostdinc", "-nostdinc++", "-DBAR=1", "-DFOO=1", "-I", toNativePath("/project/includes").path(), "-I", toNativePath("/other/project/includes").path(), "-isystem", toNativePath(TESTDATA_DIR).path(), "-isystem", toNativePath("/other/includes").path(), "-isystem", toNativePath("/includes").path()))); indexer.updateProjectParts({projectPart1}); } TEST_F(SymbolIndexer, UpdateProjectPartsCallsClearInCollector) { EXPECT_CALL(mockCollector, clear()).Times(2); indexer.updateProjectParts({projectPart1, projectPart2}); } TEST_F(SymbolIndexer, UpdateProjectPartsCallsAddFilesInCollectorForEveryProjectPart) { EXPECT_CALL(mockCollector, setFile(_, _)).Times(2); indexer.updateProjectParts({projectPart1, projectPart2}); } TEST_F(SymbolIndexer, UpdateProjectPartsDoesNotCallAddFilesInCollectorForEmptyEveryProjectParts) { EXPECT_CALL(mockCollector, setFile(_, _)) .Times(0); indexer.updateProjectParts({}); } TEST_F(SymbolIndexer, UpdateProjectPartsCallscollectSymbolsInCollector) { EXPECT_CALL(mockCollector, collectSymbols()).Times(2);; indexer.updateProjectParts({projectPart1, projectPart2}); } TEST_F(SymbolIndexer, UpdateProjectPartsCallsSymbolsInCollector) { EXPECT_CALL(mockCollector, symbols()).Times(2); indexer.updateProjectParts({projectPart1, projectPart2}); } TEST_F(SymbolIndexer, UpdateProjectPartsCallsSourceLocationsInCollector) { EXPECT_CALL(mockCollector, sourceLocations()).Times(2); indexer.updateProjectParts({projectPart1, projectPart2}); } TEST_F(SymbolIndexer, UpdateProjectPartsCallsAddUnsavedFilesInCollector) { EXPECT_CALL(mockCollector, setUnsavedFiles(unsaved)).Times(2); indexer.updateProjectParts({projectPart1, projectPart2}); } TEST_F(SymbolIndexer, UpdateProjectPartsCallsAddSymbolsAndSourceLocationsInStorage) { EXPECT_CALL(mockSymbolStorage, addSymbolsAndSourceLocations(symbolEntries, sourceLocations)).Times(2); indexer.updateProjectParts({projectPart1, projectPart2}); } TEST_F(SymbolIndexer, UpdateProjectPartsCallsUpdateProjectPartsInStorage) { EXPECT_CALL(mockSymbolStorage, insertOrUpdateProjectPart( Eq("project1"), ElementsAre("-Wno-pragma-once-outside-header"), ElementsAre(CompilerMacro{"BAR", "1", 1}, CompilerMacro{"FOO", "1", 2}), Eq(systemIncludeSearchPaths), Eq(projectIncludeSearchPaths), Eq(Utils::Language::Cxx), Eq(Utils::LanguageVersion::CXX14), Eq(Utils::LanguageExtension::None))); EXPECT_CALL( mockSymbolStorage, insertOrUpdateProjectPart( Eq("project2"), ElementsAre("-Wno-pragma-once-outside-header"), ElementsAre(CompilerMacro{"BAR", "1", 1}, CompilerMacro{"FOO", "0", 2}), Eq(systemIncludeSearchPaths), Eq(projectIncludeSearchPaths), Eq(Utils::Language::Cxx), Eq(Utils::LanguageVersion::CXX14), Eq(Utils::LanguageExtension::None))); indexer.updateProjectParts({projectPart1, projectPart2}); } TEST_F(SymbolIndexer, UpdateProjectPartsCallsUpdateProjectPartSourcesWithArtifact) { ON_CALL(mockSymbolStorage, fetchProjectPartArtefact(TypedEq("project1"))).WillByDefault(Return(artefact)); ON_CALL(mockSymbolStorage, insertOrUpdateProjectPart(Eq("project1"), _, _, _, _, _, _, _)).WillByDefault(Return(-1)); EXPECT_CALL(mockSymbolStorage, updateProjectPartSources(_, _)); indexer.updateProjectParts({projectPart1}); } TEST_F(SymbolIndexer, UpdateProjectPartsCallsUpdateProjectPartSourcesWithoutArtifact) { ON_CALL(mockSymbolStorage, fetchProjectPartArtefact(TypedEq("project2"))).WillByDefault(Return(nullArtefact)); ON_CALL(mockSymbolStorage, insertOrUpdateProjectPart(Eq("project2"), _, _, _, _, _, _, _)).WillByDefault(Return(3)); EXPECT_CALL(mockSymbolStorage, updateProjectPartSources(3, ElementsAre(IsFileId(1), IsFileId(23)))); indexer.updateProjectParts({projectPart2}); } TEST_F(SymbolIndexer, UpdateProjectPartsCallsInsertOrUpdateUsedMacros) { EXPECT_CALL(mockBuildDependenciesStorage, insertOrUpdateUsedMacros(Eq(usedMacros))) .Times(2); indexer.updateProjectParts({projectPart1, projectPart2}); } TEST_F(SymbolIndexer, UpdateProjectPartsCallsInsertFileStatuses) { EXPECT_CALL(mockBuildDependenciesStorage, insertFileStatuses(Eq(fileStatus))) .Times(2); indexer.updateProjectParts({projectPart1, projectPart2}); } TEST_F(SymbolIndexer, UpdateProjectPartsCallsInsertOrUpdateSourceDependencies) { EXPECT_CALL(mockBuildDependenciesStorage, insertOrUpdateSourceDependencies(Eq(sourceDependencies))) .Times(2); indexer.updateProjectParts({projectPart1, projectPart2}); } TEST_F(SymbolIndexer, UpdateProjectPartsCallsFetchProjectPartArtefacts) { EXPECT_CALL(mockSymbolStorage, fetchProjectPartArtefact(TypedEq(projectPart1.projectPartId))); EXPECT_CALL(mockSymbolStorage, fetchProjectPartArtefact(TypedEq(projectPart2.projectPartId))); indexer.updateProjectParts({projectPart1, projectPart2}); } TEST_F(SymbolIndexer, UpdateProjectPartsCallsInOrderWithoutProjectPartArtifact) { InSequence s; EXPECT_CALL(mockSqliteTransactionBackend, immediateBegin()); EXPECT_CALL(mockSymbolStorage, fetchProjectPartArtefact(TypedEq(projectPart1.projectPartId))).WillOnce(Return(nullArtefact)); EXPECT_CALL(mockSymbolStorage, insertOrUpdateProjectPart(Eq(projectPart1.projectPartId), Eq(projectPart1.toolChainArguments), Eq(projectPart1.compilerMacros), Eq(projectPart1.systemIncludeSearchPaths), Eq(projectPart1.projectIncludeSearchPaths), Eq(Utils::Language::Cxx), Eq(Utils::LanguageVersion::CXX14), Eq(Utils::LanguageExtension::None))) .WillOnce(Return(12)); EXPECT_CALL(mockSymbolStorage, fetchPrecompiledHeader(Eq(12))); EXPECT_CALL(mockBuildDependenciesStorage, fetchLowestLastModifiedTime(Eq(main1PathId))).Times(0); EXPECT_CALL(mockSqliteTransactionBackend, commit()); EXPECT_CALL(mockCollector, setFile(main1PathId, ElementsAre("clang++", "-Wno-pragma-once-outside-header", "-DNOMINMAX", "-x", "c++", "-std=c++14", "-nostdinc", "-nostdinc++", "-DBAR=1", "-DFOO=1", "-I", toNativePath("/project/includes").path(), "-I", toNativePath("/other/project/includes").path(), "-isystem", toNativePath(TESTDATA_DIR).path(), "-isystem", toNativePath("/other/includes").path(), "-isystem", toNativePath("/includes").path()))); EXPECT_CALL(mockCollector, collectSymbols()); EXPECT_CALL(mockSqliteTransactionBackend, immediateBegin()); EXPECT_CALL(mockSymbolStorage, addSymbolsAndSourceLocations(symbolEntries, sourceLocations)); EXPECT_CALL(mockSymbolStorage, updateProjectPartSources(TypedEq(12), Eq(sourceFileIds))); EXPECT_CALL(mockBuildDependenciesStorage, insertOrUpdateUsedMacros(Eq(usedMacros))); EXPECT_CALL(mockBuildDependenciesStorage, insertFileStatuses(Eq(fileStatus))); EXPECT_CALL(mockBuildDependenciesStorage, insertOrUpdateSourceDependencies(Eq(sourceDependencies))); EXPECT_CALL(mockSqliteTransactionBackend, commit()); indexer.updateProjectParts({projectPart1}); } TEST_F(SymbolIndexer, UpdateProjectPartsCallsInOrderWithProjectPartArtifact) { InSequence s; EXPECT_CALL(mockSqliteTransactionBackend, immediateBegin()); EXPECT_CALL(mockSymbolStorage, fetchProjectPartArtefact(TypedEq(projectPart1.projectPartId))).WillRepeatedly(Return(artefact)); EXPECT_CALL(mockSymbolStorage, insertOrUpdateProjectPart(Eq(projectPart1.projectPartId), Eq(projectPart1.toolChainArguments), Eq(projectPart1.compilerMacros), Eq(projectPart1.systemIncludeSearchPaths), Eq(projectPart1.projectIncludeSearchPaths), Eq(Utils::Language::Cxx), Eq(Utils::LanguageVersion::CXX14), Eq(Utils::LanguageExtension::None))) .WillOnce(Return(-1)); EXPECT_CALL(mockSymbolStorage, fetchPrecompiledHeader(Eq(artefact.projectPartId))); EXPECT_CALL(mockBuildDependenciesStorage, fetchLowestLastModifiedTime(Eq(main1PathId))).WillOnce(Return(-1)); EXPECT_CALL(mockSqliteTransactionBackend, commit()); EXPECT_CALL(mockCollector, setFile(Eq(main1PathId), ElementsAre("clang++", "-Wno-pragma-once-outside-header", "-DNOMINMAX", "-x", "c++", "-std=c++14", "-nostdinc", "-nostdinc++", "-DBAR=1", "-DFOO=1", "-I", toNativePath("/project/includes").path(), "-I", toNativePath("/other/project/includes").path(), "-isystem", toNativePath(TESTDATA_DIR).path(), "-isystem", toNativePath("/other/includes").path(), "-isystem", toNativePath("/includes").path()))); EXPECT_CALL(mockCollector, collectSymbols()); EXPECT_CALL(mockSqliteTransactionBackend, immediateBegin()); EXPECT_CALL(mockSymbolStorage, addSymbolsAndSourceLocations(symbolEntries, sourceLocations)); EXPECT_CALL(mockSymbolStorage, updateProjectPartSources(TypedEq(artefact.projectPartId), Eq(sourceFileIds))); EXPECT_CALL(mockBuildDependenciesStorage, insertOrUpdateUsedMacros(Eq(usedMacros))); EXPECT_CALL(mockBuildDependenciesStorage, insertFileStatuses(Eq(fileStatus))); EXPECT_CALL(mockBuildDependenciesStorage, insertOrUpdateSourceDependencies(Eq(sourceDependencies))); EXPECT_CALL(mockSqliteTransactionBackend, commit()); indexer.updateProjectParts({projectPart1}); } TEST_F(SymbolIndexer, UpdateProjectPartsCallsInOrderButGetsAnErrorForCollectingSymbols) { InSequence s; EXPECT_CALL(mockSqliteTransactionBackend, immediateBegin()); EXPECT_CALL(mockSymbolStorage, fetchProjectPartArtefact(TypedEq(projectPart1.projectPartId))) .WillOnce(Return(nullArtefact)); EXPECT_CALL(mockSymbolStorage, insertOrUpdateProjectPart(Eq(projectPart1.projectPartId), Eq(projectPart1.toolChainArguments), Eq(projectPart1.compilerMacros), Eq(projectPart1.systemIncludeSearchPaths), Eq(projectPart1.projectIncludeSearchPaths), Eq(Utils::Language::Cxx), Eq(Utils::LanguageVersion::CXX14), Eq(Utils::LanguageExtension::None))) .WillOnce(Return(12)); EXPECT_CALL(mockSymbolStorage, fetchPrecompiledHeader(Eq(12))); EXPECT_CALL(mockBuildDependenciesStorage, fetchLowestLastModifiedTime(Eq(main1PathId))).Times(0); EXPECT_CALL(mockSqliteTransactionBackend, commit()); EXPECT_CALL(mockCollector, setFile(main1PathId, ElementsAre("clang++", "-Wno-pragma-once-outside-header", "-DNOMINMAX", "-x", "c++", "-std=c++14", "-nostdinc", "-nostdinc++", "-DBAR=1", "-DFOO=1", "-I", "/project/includes", "-I", "/other/project/includes", "-isystem", TESTDATA_DIR, "-isystem", "/other/includes", "-isystem", "/includes"))); EXPECT_CALL(mockCollector, collectSymbols()).WillOnce(Return(false)); EXPECT_CALL(mockSqliteTransactionBackend, immediateBegin()).Times(0); EXPECT_CALL(mockSymbolStorage, addSymbolsAndSourceLocations(symbolEntries, sourceLocations)).Times(0); EXPECT_CALL(mockSymbolStorage, updateProjectPartSources(TypedEq(12), Eq(sourceFileIds))).Times(0); EXPECT_CALL(mockBuildDependenciesStorage, insertOrUpdateUsedMacros(Eq(usedMacros))).Times(0); EXPECT_CALL(mockBuildDependenciesStorage, insertFileStatuses(Eq(fileStatus))).Times(0); EXPECT_CALL(mockBuildDependenciesStorage, insertOrUpdateSourceDependencies(Eq(sourceDependencies))) .Times(0); EXPECT_CALL(mockSqliteTransactionBackend, commit()).Times(0); indexer.updateProjectParts({projectPart1}); } TEST_F(SymbolIndexer, CallSetNotifier) { EXPECT_CALL(mockPathWatcher, setNotifier(_)); ClangBackEnd::SymbolIndexer indexer{indexerQueue, mockSymbolStorage, mockBuildDependenciesStorage, mockPathWatcher, filePathCache, fileStatusCache, mockSqliteTransactionBackend}; } TEST_F(SymbolIndexer, PathChangedCallsFetchProjectPartArtefactInStorage) { EXPECT_CALL(mockSymbolStorage, fetchProjectPartArtefact(sourceFileIds[0])); EXPECT_CALL(mockSymbolStorage, fetchProjectPartArtefact(sourceFileIds[1])); indexer.pathsChanged(sourceFileIds); } TEST_F(SymbolIndexer, UpdateChangedPathCallsInOrder) { InSequence s; EXPECT_CALL(mockSqliteTransactionBackend, deferredBegin()); EXPECT_CALL(mockSymbolStorage, fetchProjectPartArtefact(TypedEq(sourceFileIds[0]))).WillOnce(Return(artefact)); EXPECT_CALL(mockSymbolStorage, fetchPrecompiledHeader(Eq(artefact.projectPartId))); EXPECT_CALL(mockSqliteTransactionBackend, commit()); EXPECT_CALL(mockCollector, setFile(Eq(sourceFileIds[0]), ElementsAre("clang++", "-DFOO", "-DNOMINMAX", "-x", "c++", "-std=c++14", "-nostdinc", "-nostdinc++", "-DBAR=1", "-DFOO=1", "-I", toNativePath("/project/includes").path(), "-I", toNativePath("/other/project/includes").path(), "-isystem", toNativePath(TESTDATA_DIR).path(), "-isystem", toNativePath("/other/includes").path(), "-isystem", toNativePath("/includes").path()))); EXPECT_CALL(mockCollector, collectSymbols()); EXPECT_CALL(mockSqliteTransactionBackend, immediateBegin()); EXPECT_CALL(mockSymbolStorage, addSymbolsAndSourceLocations(symbolEntries, sourceLocations)); EXPECT_CALL(mockSymbolStorage, updateProjectPartSources(artefact.projectPartId, Eq(sourceFileIds))); EXPECT_CALL(mockBuildDependenciesStorage, insertOrUpdateUsedMacros(Eq(usedMacros))); EXPECT_CALL(mockBuildDependenciesStorage, insertFileStatuses(Eq(fileStatus))); EXPECT_CALL(mockBuildDependenciesStorage, insertOrUpdateSourceDependencies(Eq(sourceDependencies))); EXPECT_CALL(mockSqliteTransactionBackend, commit()); indexer.pathsChanged({sourceFileIds[0]}); } TEST_F(SymbolIndexer, HandleEmptyOptionalArtifactInUpdateChangedPath) { InSequence s; EXPECT_CALL(mockSqliteTransactionBackend, deferredBegin()); EXPECT_CALL(mockSymbolStorage, fetchProjectPartArtefact(sourceFileIds[0])).WillOnce(Return(nullArtefact)); EXPECT_CALL(mockSymbolStorage, fetchPrecompiledHeader(_)).Times(0); EXPECT_CALL(mockSqliteTransactionBackend, commit()).Times(0); EXPECT_CALL(mockCollector, setFile(_, _)).Times(0); EXPECT_CALL(mockCollector, collectSymbols()).Times(0); EXPECT_CALL(mockSqliteTransactionBackend, immediateBegin()).Times(0); EXPECT_CALL(mockSymbolStorage, addSymbolsAndSourceLocations(_, _)).Times(0); EXPECT_CALL(mockSymbolStorage, updateProjectPartSources(An(), _)).Times(0); EXPECT_CALL(mockBuildDependenciesStorage, insertOrUpdateUsedMacros(_)).Times(0); EXPECT_CALL(mockBuildDependenciesStorage, insertFileStatuses(_)).Times(0); EXPECT_CALL(mockBuildDependenciesStorage, insertOrUpdateSourceDependencies(_)).Times(0); EXPECT_CALL(mockSqliteTransactionBackend, commit()).Times(0); indexer.pathsChanged({sourceFileIds[0]}); } TEST_F(SymbolIndexer, UpdateChangedPathCallsInOrderButGetsAnErrorForCollectingSymbols) { InSequence s; EXPECT_CALL(mockSqliteTransactionBackend, deferredBegin()); EXPECT_CALL(mockSymbolStorage, fetchProjectPartArtefact(TypedEq(sourceFileIds[0]))) .WillOnce(Return(artefact)); EXPECT_CALL(mockSymbolStorage, fetchPrecompiledHeader(Eq(artefact.projectPartId))); EXPECT_CALL(mockSqliteTransactionBackend, commit()); EXPECT_CALL(mockCollector, setFile(Eq(sourceFileIds[0]), ElementsAre("clang++", "-DFOO", "-DNOMINMAX", "-x", "c++", "-std=c++14", "-nostdinc", "-nostdinc++", "-DBAR=1", "-DFOO=1", "-I", "/project/includes", "-I", "/other/project/includes", "-isystem", TESTDATA_DIR, "-isystem", "/other/includes", "-isystem", "/includes"))); EXPECT_CALL(mockCollector, collectSymbols()).WillOnce(Return(false)); EXPECT_CALL(mockSqliteTransactionBackend, immediateBegin()).Times(0); EXPECT_CALL(mockSymbolStorage, addSymbolsAndSourceLocations(symbolEntries, sourceLocations)).Times(0); EXPECT_CALL(mockSymbolStorage, updateProjectPartSources(artefact.projectPartId, Eq(sourceFileIds))) .Times(0); EXPECT_CALL(mockBuildDependenciesStorage, insertOrUpdateUsedMacros(Eq(usedMacros))).Times(0); EXPECT_CALL(mockBuildDependenciesStorage, insertFileStatuses(Eq(fileStatus))).Times(0); EXPECT_CALL(mockBuildDependenciesStorage, insertOrUpdateSourceDependencies(Eq(sourceDependencies))) .Times(0); EXPECT_CALL(mockSqliteTransactionBackend, commit()).Times(0); indexer.pathsChanged({sourceFileIds[0]}); } TEST_F(SymbolIndexer, UpdateChangedPathIsUsingPrecompiledHeader) { ON_CALL(mockSymbolStorage, fetchProjectPartArtefact(TypedEq(sourceFileIds[0]))) .WillByDefault(Return(artefact)); ON_CALL(mockSymbolStorage, fetchPrecompiledHeader(Eq(artefact.projectPartId))) .WillByDefault(Return(projectPartPch)); std::vector symbolIndexerTask; EXPECT_CALL(mockCollector, setFile(Eq(sourceFileIds[0]), ElementsAre("clang++", "-DFOO", "-DNOMINMAX", "-x", "c++", "-std=c++14", "-nostdinc", "-nostdinc++", "-DBAR=1", "-DFOO=1", "-I", toNativePath("/project/includes").path(), "-I", toNativePath("/other/project/includes").path(), "-isystem", toNativePath(TESTDATA_DIR).path(), "-isystem", toNativePath("/other/includes").path(), "-isystem", toNativePath("/includes").path(), "-Xclang", "-include-pch", "-Xclang", toNativePath("/path/to/pch").path()))); indexer.pathsChanged({sourceFileIds[0]}); } TEST_F(SymbolIndexer, UpdateChangedPathIsNotUsingPrecompiledHeaderIfItNotExists) { ON_CALL(mockSymbolStorage, fetchProjectPartArtefact(TypedEq(sourceFileIds[0]))) .WillByDefault(Return(artefact)); std::vector symbolIndexerTask; EXPECT_CALL(mockCollector, setFile(Eq(sourceFileIds[0]), ElementsAre("clang++", "-DFOO", "-DNOMINMAX", "-x", "c++", "-std=c++14", "-nostdinc", "-nostdinc++", "-DBAR=1", "-DFOO=1", "-I", toNativePath("/project/includes").path(), "-I", toNativePath("/other/project/includes").path(), "-isystem", toNativePath(TESTDATA_DIR).path(), "-isystem", toNativePath("/other/includes").path(), "-isystem", toNativePath("/includes").path()))); indexer.pathsChanged({sourceFileIds[0]}); } TEST_F(SymbolIndexer, CompilerMacrosAndIncludeSearchPathsAreNotDifferent) { ON_CALL(mockSymbolStorage, fetchProjectPartArtefact(An())).WillByDefault(Return(artefact)); auto areDifferent = indexer.compilerMacrosOrIncludeSearchPathsAreDifferent(projectPart1, artefact); ASSERT_FALSE(areDifferent); } TEST_F(SymbolIndexer, CompilerMacrosAreDifferent) { ON_CALL(mockSymbolStorage, fetchProjectPartArtefact(An())).WillByDefault(Return(artefact)); auto areDifferent = indexer.compilerMacrosOrIncludeSearchPathsAreDifferent(projectPart2, artefact); ASSERT_TRUE(areDifferent); } TEST_F(SymbolIndexer, SystemIncludeSearchPathsAreDifferent) { ClangBackEnd::IncludeSearchPaths newSystemIncludeSearchPaths{ {"/includes", 1, ClangBackEnd::IncludeSearchPathType::BuiltIn}, {"/other/includes2", 2, ClangBackEnd::IncludeSearchPathType::System}}; ClangBackEnd::IncludeSearchPaths newProjectIncludeSearchPaths{ {"/project/includes", 1, ClangBackEnd::IncludeSearchPathType::User}, {"/other/project/includes2", 2, ClangBackEnd::IncludeSearchPathType::User}}; ProjectPartContainer projectPart3{ "project3", {"-I", TESTDATA_DIR, "-Wno-pragma-once-outside-header"}, {{"BAR", "1", 1}, {"FOO", "1", 2}}, {{"/includes", 1, ClangBackEnd::IncludeSearchPathType::BuiltIn}, {"/other/includes2", 2, ClangBackEnd::IncludeSearchPathType::System}}, Utils::clone(projectIncludeSearchPaths), {header1PathId}, {main1PathId}, Utils::Language::C, Utils::LanguageVersion::C11, Utils::LanguageExtension::All}; ON_CALL(mockSymbolStorage, fetchProjectPartArtefact(An())) .WillByDefault(Return(artefact)); auto areDifferent = indexer.compilerMacrosOrIncludeSearchPathsAreDifferent( projectPart3, artefact); ASSERT_TRUE(areDifferent); } TEST_F(SymbolIndexer, ProjectIncludeSearchPathsAreDifferent) { ProjectPartContainer projectPart3{ "project3", {"-I", TESTDATA_DIR, "-Wno-pragma-once-outside-header"}, {{"BAR", "1", 1}, {"FOO", "1", 2}}, Utils::clone(systemIncludeSearchPaths), {{"/project/includes", 1, ClangBackEnd::IncludeSearchPathType::User}, {"/other/project/includes2", 2, ClangBackEnd::IncludeSearchPathType::User}}, {header1PathId}, {main1PathId}, Utils::Language::C, Utils::LanguageVersion::C11, Utils::LanguageExtension::All}; ON_CALL(mockSymbolStorage, fetchProjectPartArtefact(An())) .WillByDefault(Return(artefact)); auto areDifferent = indexer.compilerMacrosOrIncludeSearchPathsAreDifferent( projectPart3, artefact); ASSERT_TRUE(areDifferent); } TEST_F(SymbolIndexer, DontReparseInUpdateProjectPartsIfDefinesAreTheSame) { InSequence s; ON_CALL(mockBuildDependenciesStorage, fetchLowestLastModifiedTime(A())).WillByDefault(Return(QDateTime::currentSecsSinceEpoch())); EXPECT_CALL(mockSqliteTransactionBackend, immediateBegin()); EXPECT_CALL(mockSymbolStorage, fetchProjectPartArtefact(TypedEq(projectPart1.projectPartId))).WillRepeatedly(Return(artefact)); EXPECT_CALL(mockSymbolStorage, insertOrUpdateProjectPart(Eq(projectPart1.projectPartId), Eq(projectPart1.toolChainArguments), Eq(projectPart1.compilerMacros), Eq(projectPart1.systemIncludeSearchPaths), Eq(projectPart1.projectIncludeSearchPaths), Eq(Utils::Language::Cxx), Eq(Utils::LanguageVersion::CXX14), Eq(Utils::LanguageExtension::None))); EXPECT_CALL(mockBuildDependenciesStorage, fetchLowestLastModifiedTime(A())).WillRepeatedly(Return(QDateTime::currentSecsSinceEpoch())); EXPECT_CALL(mockSqliteTransactionBackend, commit()); EXPECT_CALL(mockCollector, setFile(_, _)).Times(0); EXPECT_CALL(mockCollector, collectSymbols()).Times(0); EXPECT_CALL(mockSqliteTransactionBackend, immediateBegin()).Times(0); EXPECT_CALL(mockSymbolStorage, addSymbolsAndSourceLocations(_, _)).Times(0); EXPECT_CALL(mockSymbolStorage, updateProjectPartSources(An(), _)).Times(0); EXPECT_CALL(mockBuildDependenciesStorage, insertOrUpdateUsedMacros(_)).Times(0); EXPECT_CALL(mockBuildDependenciesStorage, insertFileStatuses(_)).Times(0); EXPECT_CALL(mockBuildDependenciesStorage, insertOrUpdateSourceDependencies(_)).Times(0); EXPECT_CALL(mockSqliteTransactionBackend, commit()).Times(0); indexer.updateProjectPart(std::move(projectPart1)); } TEST_F(SymbolIndexer, PathsChangedUpdatesFileStatusCache) { auto sourceId = filePathId(TESTDATA_DIR "/symbolindexer_pathChanged.cpp"); auto oldLastModified = fileStatusCache.lastModifiedTime(sourceId); touchFile(sourceId); indexer.pathsChanged({sourceId}); ASSERT_THAT(fileStatusCache.lastModifiedTime(sourceId), Gt(oldLastModified)); } TEST_F(SymbolIndexer, GetUpdatableFilePathIdsIfCompilerMacrosAreDifferent) { ON_CALL(mockSymbolStorage, fetchProjectPartArtefact(An())).WillByDefault(Return(artefact)); auto filePathIds = indexer.updatableFilePathIds(projectPart2, artefact); ASSERT_THAT(filePathIds, projectPart2.sourcePathIds); } TEST_F(SymbolIndexer, GetUpdatableFilePathIdsIfIncludeSearchPathsAreDifferent) { ON_CALL(mockSymbolStorage, fetchProjectPartArtefact(An())).WillByDefault(Return(artefact)); auto filePathIds = indexer.updatableFilePathIds(projectPart3, artefact); ASSERT_THAT(filePathIds, projectPart3.sourcePathIds); } TEST_F(SymbolIndexer, GetNoUpdatableFilePathIdsIfArtefactsAreTheSame) { ON_CALL(mockSymbolStorage, fetchProjectPartArtefact(An())).WillByDefault(Return(artefact)); ON_CALL(mockBuildDependenciesStorage, fetchLowestLastModifiedTime(A())).WillByDefault(Return(QDateTime::currentSecsSinceEpoch())); auto filePathIds = indexer.updatableFilePathIds(projectPart1, artefact); ASSERT_THAT(filePathIds, IsEmpty()); } TEST_F(SymbolIndexer, OutdatedFilesPassUpdatableFilePathIds) { indexer.pathsChanged({main1PathId}); ON_CALL(mockSymbolStorage, fetchProjectPartArtefact(An())).WillByDefault(Return(artefact)); ON_CALL(mockBuildDependenciesStorage, fetchLowestLastModifiedTime(A())) .WillByDefault(Return(0)); auto filePathIds = indexer.updatableFilePathIds(projectPart1, artefact); ASSERT_THAT(filePathIds, ElementsAre(main1PathId)); } TEST_F(SymbolIndexer, UpToDateFilesDontPassFilteredUpdatableFilePathIds) { indexer.pathsChanged({main1PathId}); ON_CALL(mockSymbolStorage, fetchProjectPartArtefact(An())).WillByDefault(Return(artefact)); ON_CALL(mockBuildDependenciesStorage, fetchLowestLastModifiedTime(A())) .WillByDefault(Return(QDateTime::currentSecsSinceEpoch())); auto filePathIds = indexer.updatableFilePathIds(projectPart1, artefact); ASSERT_THAT(filePathIds, IsEmpty()); } TEST_F(SymbolIndexer, OutdatedFilesAreParsedInUpdateProjectParts) { indexer.pathsChanged({main1PathId}); indexerScheduler.syncTasks(); ON_CALL(mockSymbolStorage, fetchProjectPartArtefact(An())).WillByDefault(Return(artefact)); ON_CALL(mockBuildDependenciesStorage, fetchLowestLastModifiedTime(A())) .WillByDefault(Return(0)); EXPECT_CALL(mockCollector, setFile(Eq(main1PathId), _)); indexer.updateProjectParts({projectPart1}); } TEST_F(SymbolIndexer, UpToDateFilesAreNotParsedInUpdateProjectParts) { indexer.pathsChanged({main1PathId}); indexerScheduler.syncTasks(); ON_CALL(mockSymbolStorage, fetchProjectPartArtefact(An())).WillByDefault(Return(artefact)); ON_CALL(mockBuildDependenciesStorage, fetchLowestLastModifiedTime(A())) .WillByDefault(Return(QDateTime::currentSecsSinceEpoch())); EXPECT_CALL(mockCollector, setFile(_, _)).Times(0); indexer.updateProjectParts({projectPart1}); } }