diff options
author | Marco Bubke <marco.bubke@qt.io> | 2019-06-06 10:57:55 +0200 |
---|---|---|
committer | Marco Bubke <marco.bubke@qt.io> | 2019-06-27 12:31:52 +0000 |
commit | bbd58ca30b32f6015a5e7eb16884dfb9f6108f17 (patch) | |
tree | 8f50bd05c6953797a1f73c6938925742284b24bc | |
parent | 9d290fc68206ce8323c259aab099faba93f7ec83 (diff) | |
download | qt-creator-bbd58ca30b32f6015a5e7eb16884dfb9f6108f17.tar.gz |
Clang: Watch directories instead of files
Because there a limited resources to watch files we watch now directories.
So we need much less resources.
Change-Id: Iac558832e9521a7a1a67c5ea99b42ad1b0b5129c
Reviewed-by: Tim Jenssen <tim.jenssen@qt.io>
38 files changed, 1479 insertions, 535 deletions
diff --git a/src/libs/clangsupport/clangpathwatcher.h b/src/libs/clangsupport/clangpathwatcher.h index dab52996c2..7a7d6ccfcf 100644 --- a/src/libs/clangsupport/clangpathwatcher.h +++ b/src/libs/clangsupport/clangpathwatcher.h @@ -1,4 +1,4 @@ -/**************************************************************************** +; /**************************************************************************** ** ** Copyright (C) 2016 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ @@ -27,71 +27,94 @@ #include "clangpathwatcherinterface.h" #include "clangpathwatchernotifier.h" -#include "changedfilepathcompressor.h" +#include "directorypathcompressor.h" #include "filepathcachinginterface.h" +#include "filesystem.h" #include "stringcache.h" +#include <utils/algorithm.h> + #include <QTimer> namespace ClangBackEnd { +template<class InputIt1, class InputIt2, class Callable> +void set_greedy_intersection_call( + InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, Callable callable) +{ + while (first1 != last1 && first2 != last2) { + if (*first1 < *first2) { + ++first1; + } else { + if (*first2 < *first1) + ++first2; + else + callable(*first1++); + } + } +} + class WatcherEntry { public: ProjectPartId id; - FilePathId pathId; + DirectoryPathId directoryPathId; + FilePathId filePathId; + long long lastModified = -1; friend bool operator==(WatcherEntry first, WatcherEntry second) { - return first.id == second.id && first.pathId == second.pathId; + return first.id == second.id && first.directoryPathId == second.directoryPathId + && first.filePathId == second.filePathId; } friend bool operator<(WatcherEntry first, WatcherEntry second) { - return std::tie(first.pathId, first.id) < std::tie(second.pathId, second.id); + return std::tie(first.directoryPathId, first.filePathId, first.id) + < std::tie(second.directoryPathId, second.filePathId, second.id); } - friend bool operator<(WatcherEntry entry, FilePathId pathId) + friend bool operator<(DirectoryPathId directoryPathId, WatcherEntry entry) { - return entry.pathId < pathId; + return directoryPathId < entry.directoryPathId; } - friend bool operator<(FilePathId pathId, WatcherEntry entry) + friend bool operator<(WatcherEntry entry, DirectoryPathId directoryPathId) { - return pathId < entry.pathId; + return entry.directoryPathId < directoryPathId; } - operator FilePathId() const - { - return pathId; - } + operator FilePathId() const { return filePathId; } + + operator DirectoryPathId() const { return directoryPathId; } }; using WatcherEntries = std::vector<WatcherEntry>; -template <typename FileSystemWatcher, - typename Timer> +template<typename FileSystemWatcher, typename Timer> class CLANGSUPPORT_GCCEXPORT ClangPathWatcher : public ClangPathWatcherInterface { public: ClangPathWatcher(FilePathCachingInterface &pathCache, - ClangPathWatcherNotifier *notifier=nullptr) - : m_changedFilePathCompressor(pathCache), - m_pathCache(pathCache), - m_notifier(notifier) + FileSystemInterface &fileSystem, + ClangPathWatcherNotifier *notifier = nullptr) + : m_pathCache(pathCache) + , m_fileStatusCache(fileSystem) + , m_fileSystem(fileSystem) + , m_notifier(notifier) { QObject::connect(&m_fileSystemWatcher, - &FileSystemWatcher::fileChanged, - [&] (const QString &filePath) { compressChangedFilePath(filePath); }); + &FileSystemWatcher::directoryChanged, + [&](const QString &path) { compressChangedDirectoryPath(path); }); - m_changedFilePathCompressor.setCallback([&] (ClangBackEnd::FilePathIds &&filePathIds) { - addChangedPathForFilePath(std::move(filePathIds)); + m_directoryPathCompressor.setCallback([&](ClangBackEnd::DirectoryPathIds &&directoryPathIds) { + addChangedPathForFilePath(std::move(directoryPathIds)); }); } ~ClangPathWatcher() { - m_changedFilePathCompressor.setCallback([&] (FilePathIds &&) {}); + m_directoryPathCompressor.setCallback([&](DirectoryPathIds &&) {}); } void updateIdPaths(const std::vector<IdPaths> &idPaths) override @@ -109,7 +132,7 @@ public: auto filteredPaths = filterNotWatchedPaths(removedEntries); if (!filteredPaths.empty()) - m_fileSystemWatcher.removePaths(convertWatcherEntriesToQStringList(filteredPaths)); + m_fileSystemWatcher.removePaths(convertWatcherEntriesToDirectoryPathList(filteredPaths)); } void setNotifier(ClangPathWatcherNotifier *notifier) override @@ -164,7 +187,13 @@ public: outputIterator = std::transform(idPath.filePathIds.begin(), idPath.filePathIds.end(), outputIterator, - [&] (FilePathId pathId) { return WatcherEntry{id, pathId}; }); + [&](FilePathId filePathId) { + return WatcherEntry{ + id, + m_pathCache.directoryPathId(filePathId), + filePathId, + m_fileStatusCache.lastModifiedTime(filePathId)}; + }); } std::sort(entries.begin(), entries.end()); @@ -182,7 +211,7 @@ public: mergeToWatchedEntries(newEntries); if (!filteredPaths.empty()) - m_fileSystemWatcher.addPaths(convertWatcherEntriesToQStringList(filteredPaths)); + m_fileSystemWatcher.addPaths(convertWatcherEntriesToDirectoryPathList(filteredPaths)); } void removeUnusedEntries(const WatcherEntries &entries, const ProjectPartIds &ids) @@ -194,33 +223,31 @@ public: auto filteredPaths = filterNotWatchedPaths(oldEntries); if (!filteredPaths.empty()) - m_fileSystemWatcher.removePaths(convertWatcherEntriesToQStringList(filteredPaths)); + m_fileSystemWatcher.removePaths(convertWatcherEntriesToDirectoryPathList(filteredPaths)); } - FileSystemWatcher &fileSystemWatcher() + FileSystemWatcher &fileSystemWatcher() { return m_fileSystemWatcher; } + + QStringList convertWatcherEntriesToDirectoryPathList(const DirectoryPathIds &directoryPathIds) const { - return m_fileSystemWatcher; + return Utils::transform<QStringList>(directoryPathIds, [&](DirectoryPathId id) { + return QString(m_pathCache.directoryPath(id)); + }); } - QStringList convertWatcherEntriesToQStringList( - const WatcherEntries &watcherEntries) + QStringList convertWatcherEntriesToDirectoryPathList(const WatcherEntries &watcherEntries) const { - QStringList paths; - paths.reserve(int(watcherEntries.size())); + DirectoryPathIds directoryPathIds = Utils::transform<DirectoryPathIds>( + watcherEntries, [&](WatcherEntry entry) { return entry.directoryPathId; }); - std::transform(watcherEntries.begin(), - watcherEntries.end(), - std::back_inserter(paths), - [&] (WatcherEntry entry) { - return QString(m_pathCache.filePath(entry.pathId).path()); - }); + std::sort(directoryPathIds.begin(), directoryPathIds.end()); + directoryPathIds.erase(std::unique(directoryPathIds.begin(), directoryPathIds.end()), + directoryPathIds.end()); - return paths; + return convertWatcherEntriesToDirectoryPathList(directoryPathIds); } - template <typename Compare> - WatcherEntries notWatchedEntries(const WatcherEntries &entries, - Compare compare) const + WatcherEntries notWatchedEntries(const WatcherEntries &entries) const { WatcherEntries notWatchedEntries; notWatchedEntries.reserve(entries.size()); @@ -229,24 +256,23 @@ public: entries.end(), m_watchedEntries.cbegin(), m_watchedEntries.cend(), - std::back_inserter(notWatchedEntries), - compare); + std::back_inserter(notWatchedEntries)); return notWatchedEntries; } - WatcherEntries notWatchedEntries(const WatcherEntries &entries) const + DirectoryPathIds notWatchedPaths(const DirectoryPathIds &ids) const { - return notWatchedEntries(entries, std::less<WatcherEntry>()); - } + DirectoryPathIds notWatchedDirectoryIds; + notWatchedDirectoryIds.reserve(ids.size()); - WatcherEntries notWatchedPaths(const WatcherEntries &entries) const - { - auto compare = [] (WatcherEntry first, WatcherEntry second) { - return first.pathId < second.pathId; - }; + std::set_difference(ids.begin(), + ids.end(), + m_watchedEntries.cbegin(), + m_watchedEntries.cend(), + std::back_inserter(notWatchedDirectoryIds)); - return notWatchedEntries(entries, compare); + return notWatchedDirectoryIds; } template <typename Compare> @@ -297,25 +323,24 @@ public: m_watchedEntries = std::move(newWatchedEntries); } - static - WatcherEntries uniquePaths(const WatcherEntries &pathEntries) + static DirectoryPathIds uniquePaths(const WatcherEntries &pathEntries) { - WatcherEntries uniqueEntries; - uniqueEntries.reserve(pathEntries.size()); + DirectoryPathIds uniqueDirectoryIds; + uniqueDirectoryIds.reserve(pathEntries.size()); - auto compare = [] (WatcherEntry first, WatcherEntry second) { - return first.pathId == second.pathId; + auto compare = [](WatcherEntry first, WatcherEntry second) { + return first.directoryPathId == second.directoryPathId; }; std::unique_copy(pathEntries.begin(), pathEntries.end(), - std::back_inserter(uniqueEntries), + std::back_inserter(uniqueDirectoryIds), compare); - return uniqueEntries; + return uniqueDirectoryIds; } - WatcherEntries filterNotWatchedPaths(const WatcherEntries &entries) + DirectoryPathIds filterNotWatchedPaths(const WatcherEntries &entries) const { return notWatchedPaths(uniquePaths(entries)); } @@ -351,40 +376,48 @@ public: oldEntries.end(), std::back_inserter(newWatchedEntries)); - - m_watchedEntries = newWatchedEntries; + m_watchedEntries = std::move(newWatchedEntries); } - void compressChangedFilePath(const QString &filePath) + void compressChangedDirectoryPath(const QString &path) { - m_changedFilePathCompressor.addFilePath(filePath); + m_directoryPathCompressor.addDirectoryPathId( + m_pathCache.directoryPathId(Utils::PathString{path})); } - WatcherEntries watchedEntriesForPaths(ClangBackEnd::FilePathIds &&filePathIds) + WatcherEntries watchedEntriesForPaths(ClangBackEnd::DirectoryPathIds &&directoryPathIds) { WatcherEntries foundEntries; - foundEntries.reserve(filePathIds.size()); - - for (FilePathId pathId : filePathIds) { - auto range = std::equal_range(m_watchedEntries.begin(), m_watchedEntries.end(), pathId); - foundEntries.insert(foundEntries.end(), range.first, range.second); - } + foundEntries.reserve(m_watchedEntries.size()); + + set_greedy_intersection_call(m_watchedEntries.begin(), + m_watchedEntries.end(), + directoryPathIds.begin(), + directoryPathIds.end(), + [&](WatcherEntry &entry) { + m_fileStatusCache.update(entry.filePathId); + auto currentLastModified = m_fileStatusCache.lastModifiedTime( + entry.filePathId); + if (entry.lastModified < currentLastModified) { + foundEntries.push_back(entry); + entry.lastModified = currentLastModified; + } + }); return foundEntries; } - FilePathIds watchedPaths(const FilePathIds &filePathIds) const + FilePathIds watchedPaths(const WatcherEntries &entries) const { - FilePathIds watchedFilePathIds; - watchedFilePathIds.reserve(filePathIds.size()); + auto filePathIds = Utils::transform<FilePathIds>(entries, [](WatcherEntry entry) { + return entry.filePathId; + }); + + std::sort(filePathIds.begin(), filePathIds.end()); - std::set_intersection(m_watchedEntries.begin(), - m_watchedEntries.end(), - filePathIds.begin(), - filePathIds.end(), - std::back_inserter(watchedFilePathIds)); + filePathIds.erase(std::unique(filePathIds.begin(), filePathIds.end()), filePathIds.end()); - return watchedFilePathIds; + return filePathIds; } ProjectPartIds idsForWatcherEntries(const WatcherEntries &foundEntries) @@ -403,21 +436,20 @@ public: ProjectPartIds uniqueIds(ProjectPartIds &&ids) { std::sort(ids.begin(), ids.end()); - auto newEnd = std::unique(ids.begin(), ids.end()); - ids.erase(newEnd, ids.end()); + ids.erase(std::unique(ids.begin(), ids.end()), ids.end()); return std::move(ids); } - void addChangedPathForFilePath(FilePathIds &&filePathIds) + void addChangedPathForFilePath(DirectoryPathIds &&directoryPathIds) { if (m_notifier) { - WatcherEntries foundEntries = watchedEntriesForPaths(std::move(filePathIds)); + WatcherEntries foundEntries = watchedEntriesForPaths(std::move(directoryPathIds)); ProjectPartIds changedIds = idsForWatcherEntries(foundEntries); m_notifier->pathsWithIdsChanged(uniqueIds(std::move(changedIds))); - m_notifier->pathsChanged(watchedPaths(filePathIds)); + m_notifier->pathsChanged(watchedPaths(foundEntries)); } } @@ -428,10 +460,12 @@ public: private: WatcherEntries m_watchedEntries; - ChangedFilePathCompressor<Timer> m_changedFilePathCompressor; FileSystemWatcher m_fileSystemWatcher; + FileStatusCache m_fileStatusCache; + FileSystemInterface &m_fileSystem; FilePathCachingInterface &m_pathCache; ClangPathWatcherNotifier *m_notifier; + DirectoryPathCompressor<Timer> m_directoryPathCompressor; }; } // namespace ClangBackEnd diff --git a/src/libs/clangsupport/clangsupport-lib.pri b/src/libs/clangsupport/clangsupport-lib.pri index df537f5848..9b6bbdf3ee 100644 --- a/src/libs/clangsupport/clangsupport-lib.pri +++ b/src/libs/clangsupport/clangsupport-lib.pri @@ -18,6 +18,7 @@ SOURCES += \ $$PWD/clangcodemodelserverproxy.cpp \ $$PWD/alivemessage.cpp \ $$PWD/completionsmessage.cpp \ + $$PWD/filesystem.cpp \ $$PWD/requestcompletionsmessage.cpp \ $$PWD/echomessage.cpp \ $$PWD/endmessage.cpp \ @@ -87,7 +88,8 @@ SOURCES += \ $$PWD/removegeneratedfilesmessage.cpp \ $$PWD/generatedfiles.cpp \ $$PWD/projectpartartefact.cpp \ - $$PWD/projectpartcontainer.cpp + $$PWD/projectpartcontainer.cpp \ + $$PWD/filestatuscache.cpp HEADERS += \ $$PWD/cancelmessage.h \ @@ -109,7 +111,11 @@ HEADERS += \ $$PWD/alivemessage.h \ $$PWD/clangsupportexceptions.h \ $$PWD/completionsmessage.h \ + $$PWD/directoryandfilepathid.h \ + $$PWD/directorypathid.h \ $$PWD/executeinloop.h \ + $$PWD/filesystem.h \ + $$PWD/filesysteminterface.h \ $$PWD/pchpaths.h \ $$PWD/projectpartid.h \ $$PWD/projectpartsstorage.h \ @@ -217,6 +223,8 @@ HEADERS += \ $$PWD/sourceentry.h \ $$PWD/modifiedtimecheckerinterface.h \ $$PWD/environment.h \ + $$PWD/filestatus.h \ + $$PWD/filestatuscache.h \ $$PWD/modifiedtimechecker.h contains(QT_CONFIG, reduce_exports):CONFIG += hide_symbols diff --git a/src/libs/clangsupport/directoryandfilepathid.h b/src/libs/clangsupport/directoryandfilepathid.h new file mode 100644 index 0000000000..8969372919 --- /dev/null +++ b/src/libs/clangsupport/directoryandfilepathid.h @@ -0,0 +1,90 @@ +/**************************************************************************** +** +** Copyright (C) 2019 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 "directorypathid.h" +#include "filepathid.h" + +#include <QDataStream> + +#include <vector> + +namespace ClangBackEnd { +class DirectoryAndFilePathId +{ +public: + constexpr DirectoryAndFilePathId() = default; + + DirectoryAndFilePathId(const char *) = delete; + + DirectoryAndFilePathId(int directoryPathId, int filePathId) + : directoryPathId(directoryPathId) + , filePathId(filePathId) + {} + + bool isValid() const { return directoryPathId.isValid() && filePathId.isValid(); } + + friend bool operator==(DirectoryAndFilePathId first, DirectoryAndFilePathId second) + { + return first.isValid() && first.directoryPathId == second.directoryPathId + && first.filePathId == second.filePathId; + } + + friend bool operator!=(DirectoryAndFilePathId first, DirectoryAndFilePathId second) + { + return !(first == second); + } + + friend bool operator<(DirectoryAndFilePathId first, DirectoryAndFilePathId second) + { + return std::tie(first.directoryPathId, first.filePathId) + < std::tie(second.directoryPathId, second.filePathId); + } + + friend QDataStream &operator<<(QDataStream &out, + const DirectoryAndFilePathId &directoryAndFilePathId) + { + out << directoryAndFilePathId.directoryPathId; + out << directoryAndFilePathId.filePathId; + + return out; + } + + friend QDataStream &operator>>(QDataStream &in, DirectoryAndFilePathId &directoryAndFilePathId) + { + in >> directoryAndFilePathId.directoryPathId; + in >> directoryAndFilePathId.filePathId; + return in; + } + +public: + DirectoryPathId directoryPathId; + FilePathId filePathId; +}; + +using DirectoryAndFilePathIds = std::vector<DirectoryAndFilePathId>; + +} // namespace ClangBackEnd diff --git a/src/libs/clangsupport/changedfilepathcompressor.h b/src/libs/clangsupport/directorypathcompressor.h index aa8e1ec71a..cd02948e83 100644 --- a/src/libs/clangsupport/changedfilepathcompressor.h +++ b/src/libs/clangsupport/directorypathcompressor.h @@ -27,53 +27,44 @@ #include "clangsupport_global.h" -#include <filepathid.h> -#include <filepathcache.h> +#include "filepathcachinginterface.h" +#include <QDir> #include <QTimer> -#include <filepathcachinginterface.h> +#include <utils/algorithm.h> #include <functional> namespace ClangBackEnd { -template <typename Timer> -class ChangedFilePathCompressor +template<typename Timer> +class DirectoryPathCompressor { public: - ChangedFilePathCompressor(FilePathCachingInterface &filePathCache) - : m_filePathCache(filePathCache) - { - m_timer.setSingleShot(true); - } + DirectoryPathCompressor() { m_timer.setSingleShot(true); } - virtual ~ChangedFilePathCompressor() - { - } + virtual ~DirectoryPathCompressor() = default; - void addFilePath(const QString &filePath) + void addDirectoryPathId(DirectoryPathId directoryPathIdId) { - FilePathId filePathId = m_filePathCache.filePathId(FilePath(filePath)); + auto found = std::lower_bound(m_directoryPathIds.begin(), + m_directoryPathIds.end(), + directoryPathIdId); - auto found = std::lower_bound(m_filePaths.begin(), m_filePaths.end(), filePathId); - - if (found == m_filePaths.end() || *found != filePathId) - m_filePaths.insert(found, filePathId); + if (found == m_directoryPathIds.end() || *found != directoryPathIdId) + m_directoryPathIds.insert(found, directoryPathIdId); restartTimer(); } - FilePathIds takeFilePathIds() - { - return std::move(m_filePaths); - } + DirectoryPathIds takeDirectoryPathIds() { return std::move(m_directoryPathIds); } - virtual void setCallback(std::function<void(ClangBackEnd::FilePathIds &&)> &&callback) + virtual void setCallback(std::function<void(ClangBackEnd::DirectoryPathIds &&)> &&callback) { - QObject::connect(&m_timer, - &Timer::timeout, - [this, callback=std::move(callback)] { callback(takeFilePathIds()); }); + QObject::connect(&m_timer, &Timer::timeout, [this, callback = std::move(callback)] { + callback(takeDirectoryPathIds()); + }); } unittest_public: @@ -88,9 +79,8 @@ unittest_public: } private: - FilePathIds m_filePaths; + DirectoryPathIds m_directoryPathIds; Timer m_timer; - FilePathCachingInterface &m_filePathCache; }; } // namespace ClangBackEnd diff --git a/src/libs/clangsupport/directorypathid.h b/src/libs/clangsupport/directorypathid.h new file mode 100644 index 0000000000..2fd0b5847e --- /dev/null +++ b/src/libs/clangsupport/directorypathid.h @@ -0,0 +1,81 @@ +/**************************************************************************** +** +** Copyright (C) 2019 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 <QDataStream> + +#include <vector> + +namespace ClangBackEnd { +class DirectoryPathId +{ +public: + constexpr DirectoryPathId() = default; + + DirectoryPathId(const char *) = delete; + + DirectoryPathId(int directoryPathId) + : directoryPathId(directoryPathId) + {} + + bool isValid() const { return directoryPathId >= 0; } + + friend bool operator==(DirectoryPathId first, DirectoryPathId second) + { + return first.isValid() && first.directoryPathId == second.directoryPathId; + } + + friend bool operator!=(DirectoryPathId first, DirectoryPathId second) + { + return !(first == second); + } + + friend bool operator<(DirectoryPathId first, DirectoryPathId second) + { + return first.directoryPathId < second.directoryPathId; + } + + friend QDataStream &operator<<(QDataStream &out, const DirectoryPathId &directoryPathId) + { + out << directoryPathId.directoryPathId; + + return out; + } + + friend QDataStream &operator>>(QDataStream &in, DirectoryPathId &directoryPathId) + { + in >> directoryPathId.directoryPathId; + + return in; + } + +public: + int directoryPathId = -1; +}; + +using DirectoryPathIds = std::vector<DirectoryPathId>; + +} // namespace ClangBackEnd diff --git a/src/libs/clangsupport/filepathcache.h b/src/libs/clangsupport/filepathcache.h index bdd925a79c..4845416d22 100644 --- a/src/libs/clangsupport/filepathcache.h +++ b/src/libs/clangsupport/filepathcache.h @@ -25,9 +25,10 @@ #pragma once +#include "directorypathid.h" +#include "filepath.h" #include "filepathexceptions.h" #include "filepathid.h" -#include "filepath.h" #include "filepathview.h" #include "stringcache.h" @@ -121,10 +122,10 @@ public: { Utils::SmallStringView directoryPath = filePath.directory(); - int directoryId = m_directoryPathCache.stringId(directoryPath, - [&] (const Utils::SmallStringView) { - return m_filePathStorage.fetchDirectoryId(directoryPath); - }); + int directoryId = m_directoryPathCache.stringId( + directoryPath, [&](const Utils::SmallStringView directoryPath) { + return m_filePathStorage.fetchDirectoryId(directoryPath); + }); Utils::SmallStringView fileName = filePath.name(); @@ -136,6 +137,17 @@ public: return fileNameId; } + DirectoryPathId directoryPathId(Utils::SmallStringView directoryPath) const + { + Utils::SmallStringView path = directoryPath.back() == '/' + ? directoryPath.mid(0, directoryPath.size() - 1) + : directoryPath; + + return m_directoryPathCache.stringId(path, [&](const Utils::SmallStringView directoryPath) { + return m_filePathStorage.fetchDirectoryId(directoryPath); + }); + } + FilePath filePath(FilePathId filePathId) const { if (Q_UNLIKELY(!filePathId.isValid())) @@ -157,6 +169,32 @@ public: return FilePath{directoryPath, entry.fileName}; } + Utils::PathString directoryPath(DirectoryPathId directoryPathId) const + { + if (Q_UNLIKELY(!directoryPathId.isValid())) + throw NoDirectoryPathForInvalidDirectoryPathId(); + + auto fetchDirectoryPath = [&](int id) { return m_filePathStorage.fetchDirectoryPath(id); }; + + return m_directoryPathCache.string(directoryPathId.directoryPathId, fetchDirectoryPath); + } + + DirectoryPathId directoryPathId(FilePathId filePathId) const + { + if (Q_UNLIKELY(!filePathId.isValid())) + throw NoFilePathForInvalidFilePathId(); + + auto fetchSoureNameAndDirectoryId = [&](int id) { + auto entry = m_filePathStorage.fetchSourceNameAndDirectoryId(id); + return FileNameEntry{entry.sourceName, entry.directoryId}; + }; + + FileNameEntry entry = m_fileNameCache.string(filePathId.filePathId, + fetchSoureNameAndDirectoryId); + + return m_fileNameCache.string(filePathId.filePathId, fetchSoureNameAndDirectoryId).directoryId; + } + private: mutable DirectoryPathCache m_directoryPathCache; mutable FileNameCache m_fileNameCache; diff --git a/src/libs/clangsupport/filepathcaching.cpp b/src/libs/clangsupport/filepathcaching.cpp index 0a3fc6ee41..372ed86bd3 100644 --- a/src/libs/clangsupport/filepathcaching.cpp +++ b/src/libs/clangsupport/filepathcaching.cpp @@ -37,4 +37,19 @@ FilePath FilePathCaching::filePath(FilePathId filePathId) const return m_cache.filePath(filePathId); } +DirectoryPathId FilePathCaching::directoryPathId(Utils::SmallStringView directoryPath) const +{ + return m_cache.directoryPathId(directoryPath); +} + +Utils::PathString FilePathCaching::directoryPath(DirectoryPathId directoryPathId) const +{ + return m_cache.directoryPath(directoryPathId); +} + +DirectoryPathId FilePathCaching::directoryPathId(FilePathId filePathId) const +{ + return m_cache.directoryPathId(filePathId); +} + } // namespace ClangBackEnd diff --git a/src/libs/clangsupport/filepathcaching.h b/src/libs/clangsupport/filepathcaching.h index a3337755fd..f69b940b15 100644 --- a/src/libs/clangsupport/filepathcaching.h +++ b/src/libs/clangsupport/filepathcaching.h @@ -50,6 +50,9 @@ public: FilePathId filePathId(FilePathView filePath) const override; FilePath filePath(FilePathId filePathId) const override; + DirectoryPathId directoryPathId(Utils::SmallStringView directoryPath) const override; + Utils::PathString directoryPath(DirectoryPathId directoryPathId) const override; + DirectoryPathId directoryPathId(FilePathId filePathId) const override; private: Factory m_factory; diff --git a/src/libs/clangsupport/filepathcachinginterface.h b/src/libs/clangsupport/filepathcachinginterface.h index b9668362f7..098fd39ca9 100644 --- a/src/libs/clangsupport/filepathcachinginterface.h +++ b/src/libs/clangsupport/filepathcachinginterface.h @@ -25,6 +25,7 @@ #pragma once +#include "directorypathid.h" #include "filepath.h" #include "filepathid.h" #include "filepathview.h" @@ -40,8 +41,11 @@ public: virtual FilePathId filePathId(FilePathView filePath) const = 0; virtual FilePath filePath(FilePathId filePathId) const = 0; + virtual DirectoryPathId directoryPathId(Utils::SmallStringView directoryPath) const = 0; + virtual DirectoryPathId directoryPathId(FilePathId filePathId) const = 0; + virtual Utils::PathString directoryPath(DirectoryPathId directoryPathId) const = 0; - template <typename Container> + template<typename Container> FilePathIds filePathIds(Container &&filePaths) const { FilePathIds filePathIds; diff --git a/src/libs/clangsupport/filepathexceptions.h b/src/libs/clangsupport/filepathexceptions.h index 80bded0c66..3174d89479 100644 --- a/src/libs/clangsupport/filepathexceptions.h +++ b/src/libs/clangsupport/filepathexceptions.h @@ -38,6 +38,15 @@ public: } }; +class NoDirectoryPathForInvalidDirectoryPathId : std::exception +{ +public: + const char *what() const noexcept override + { + return "You cannot get a directory path for an invalid directory path id!"; + } +}; + class SourceNameIdDoesNotExists : std::exception { public: diff --git a/src/libs/clangsupport/filepathstorage.h b/src/libs/clangsupport/filepathstorage.h index 5917504dbf..8aba2ef6d2 100644 --- a/src/libs/clangsupport/filepathstorage.h +++ b/src/libs/clangsupport/filepathstorage.h @@ -179,12 +179,32 @@ public: transaction.commit(); - return optionalSourceName.value(); + return *optionalSourceName; } catch (const Sqlite::StatementIsBusy &) { return fetchSourceNameAndDirectoryId(sourceId); } } + int fetchDirectoryId(int sourceId) + { + try { + Sqlite::DeferredTransaction transaction{m_statementFactory.database}; + + ReadStatement &statement = m_statementFactory.selectDirectoryIdFromSourcesBySourceId; + + auto optionalDirectoryId = statement.template value<int>(sourceId); + + if (!optionalDirectoryId) + throw SourceNameIdDoesNotExists(); + + transaction.commit(); + + return *optionalDirectoryId; + } catch (const Sqlite::StatementIsBusy &) { + return fetchDirectoryId(sourceId); + } + } + std::vector<Sources::Source> fetchAllSources() { try { diff --git a/src/libs/clangsupport/filepathstoragesqlitestatementfactory.h b/src/libs/clangsupport/filepathstoragesqlitestatementfactory.h index dbf8071775..27296a1df6 100644 --- a/src/libs/clangsupport/filepathstoragesqlitestatementfactory.h +++ b/src/libs/clangsupport/filepathstoragesqlitestatementfactory.h @@ -69,6 +69,8 @@ public: "SELECT sourceName, directoryId FROM sources WHERE sourceId = ?", database }; + ReadStatement selectDirectoryIdFromSourcesBySourceId{ + "SELECT directoryId FROM sources WHERE sourceId = ?", database}; WriteStatement insertIntoSources{ "INSERT INTO sources(directoryId, sourceName) VALUES (?,?)", database diff --git a/src/tools/clangrefactoringbackend/source/filestatus.h b/src/libs/clangsupport/filestatus.h index da2be4a304..da2be4a304 100644 --- a/src/tools/clangrefactoringbackend/source/filestatus.h +++ b/src/libs/clangsupport/filestatus.h diff --git a/src/libs/clangsupport/filestatuscache.cpp b/src/libs/clangsupport/filestatuscache.cpp new file mode 100644 index 0000000000..480b745525 --- /dev/null +++ b/src/libs/clangsupport/filestatuscache.cpp @@ -0,0 +1,169 @@ +/**************************************************************************** +** +** 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 "filestatuscache.h" +#include "filesystem.h" + +#include <utils/algorithm.h> + +#include <QDateTime> +#include <QFileInfo> + +namespace ClangBackEnd { + +long long FileStatusCache::lastModifiedTime(FilePathId filePathId) const +{ + return findEntry(filePathId).lastModified; +} + +void FileStatusCache::update(FilePathId filePathId) +{ + auto found = std::lower_bound(m_cacheEntries.begin(), + m_cacheEntries.end(), + Internal::FileStatusCacheEntry{filePathId}, + [] (const auto &first, const auto &second) { + return first.filePathId < second.filePathId; + }); + + if (found != m_cacheEntries.end() && found->filePathId == filePathId) + found->lastModified = m_fileSystem.lastModified(filePathId); +} + +namespace { +template<class InputIt1, class InputIt2, class Callable> +void set_intersection_call(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, Callable callable) +{ + while (first1 != last1 && first2 != last2) { + if (*first1 < *first2) { + ++first1; + } else { + if (!(*first2 < *first1)) + callable(*first1++); + ++first2; + } + } +} + +template<class InputIt1, class InputIt2, class Callable> +void set_difference_call(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, Callable callable) +{ + while (first1 != last1) { + if (first2 == last2) { + std::for_each(first1, last1, callable); + return; + } + if (*first1 < *first2) { + callable(*first1++); + } else { + if (!(*first2 < *first1)) + ++first1; + ++first2; + } + } +} +} // namespace + +void FileStatusCache::update(FilePathIds filePathIds) +{ + set_intersection_call(m_cacheEntries.begin(), + m_cacheEntries.end(), + filePathIds.begin(), + filePathIds.end(), + [&](auto &entry) { + entry.lastModified = m_fileSystem.lastModified(entry.filePathId); + }); +} + +FilePathIds FileStatusCache::modified(FilePathIds filePathIds) const +{ + FilePathIds modifiedFilePathIds; + modifiedFilePathIds.reserve(filePathIds.size()); + + set_intersection_call(m_cacheEntries.begin(), + m_cacheEntries.end(), + filePathIds.begin(), + filePathIds.end(), + [&](auto &entry) { + auto newLastModified = m_fileSystem.lastModified(entry.filePathId); + if (newLastModified > entry.lastModified) { + modifiedFilePathIds.push_back(entry.filePathId); + entry.lastModified = newLastModified; + } + }); + + Internal::FileStatusCacheEntries newEntries; + newEntries.reserve(filePathIds.size()); + + set_difference_call(filePathIds.begin(), + filePathIds.end(), + m_cacheEntries.begin(), + m_cacheEntries.end(), + [&](FilePathId newFilePathId) { + newEntries.emplace_back(newFilePathId, + m_fileSystem.lastModified(newFilePathId)); + modifiedFilePathIds.push_back(newFilePathId); + }); + + if (newEntries.size()) { + Internal::FileStatusCacheEntries mergedEntries; + mergedEntries.reserve(m_cacheEntries.size() + newEntries.size()); + + std::set_union(newEntries.begin(), + newEntries.end(), + m_cacheEntries.begin(), + m_cacheEntries.end(), + std::back_inserter(mergedEntries)); + + m_cacheEntries = std::move(mergedEntries); + } + + std::sort(modifiedFilePathIds.begin(), modifiedFilePathIds.end()); + + return modifiedFilePathIds; +} + +FileStatusCache::size_type FileStatusCache::size() const +{ + return m_cacheEntries.size(); +} + +Internal::FileStatusCacheEntry FileStatusCache::findEntry(FilePathId filePathId) const +{ + auto found = std::lower_bound(m_cacheEntries.begin(), + m_cacheEntries.end(), + Internal::FileStatusCacheEntry{filePathId}, + [] (const auto &first, const auto &second) { + return first.filePathId < second.filePathId; + }); + + if (found != m_cacheEntries.end() && found->filePathId == filePathId) + return *found; + + auto inserted = m_cacheEntries.emplace(found, filePathId, m_fileSystem.lastModified(filePathId)); + + return *inserted; +} + +} // namespace ClangBackEnd diff --git a/src/tools/clangrefactoringbackend/source/filestatuscache.h b/src/libs/clangsupport/filestatuscache.h index 23195da605..3f0af56b3a 100644 --- a/src/tools/clangrefactoringbackend/source/filestatuscache.h +++ b/src/libs/clangsupport/filestatuscache.h @@ -31,6 +31,8 @@ QT_FORWARD_DECLARE_CLASS(QFileInfo) namespace ClangBackEnd { +class FileSystemInterface; + namespace Internal { class FileStatusCacheEntry { @@ -41,8 +43,23 @@ public: lastModified(lastModified) {} + friend bool operator<(FileStatusCacheEntry first, FileStatusCacheEntry second) + { + return first.filePathId < second.filePathId; + } + + friend bool operator<(FileStatusCacheEntry first, FilePathId second) + { + return first.filePathId < second; + } + + friend bool operator<(FilePathId first, FileStatusCacheEntry second) + { + return first < second.filePathId; + } + public: - ClangBackEnd::FilePathId filePathId; + FilePathId filePathId; long long lastModified; }; @@ -50,27 +67,30 @@ using FileStatusCacheEntries = std::vector<FileStatusCacheEntry>; } -class FileStatusCache +class CLANGSUPPORT_EXPORT FileStatusCache { public: using size_type = Internal::FileStatusCacheEntries::size_type; - FileStatusCache(FilePathCachingInterface &filePathCache); + FileStatusCache(FileSystemInterface &fileSystem) + : m_fileSystem(fileSystem) + {} FileStatusCache &operator=(const FileStatusCache &) = delete; FileStatusCache(const FileStatusCache &) = delete; - long long lastModifiedTime(ClangBackEnd::FilePathId filePathId) const; - void update(ClangBackEnd::FilePathId filePathId); + long long lastModifiedTime(FilePathId filePathId) const; + void update(FilePathId filePathId); + void update(FilePathIds filePathIds); + FilePathIds modified(FilePathIds filePathIds) const; size_type size() const; private: - Internal::FileStatusCacheEntry findEntry(ClangBackEnd::FilePathId filePathId) const; - QFileInfo qFileInfo(ClangBackEnd::FilePathId filePathId) const; + Internal::FileStatusCacheEntry findEntry(FilePathId filePathId) const; private: mutable Internal::FileStatusCacheEntries m_cacheEntries; - FilePathCachingInterface &m_filePathCache; + FileSystemInterface &m_fileSystem; }; } // namespace ClangBackEnd diff --git a/src/libs/clangsupport/filesystem.cpp b/src/libs/clangsupport/filesystem.cpp new file mode 100644 index 0000000000..5b8c037f80 --- /dev/null +++ b/src/libs/clangsupport/filesystem.cpp @@ -0,0 +1,61 @@ +/**************************************************************************** +** +** Copyright (C) 2019 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 "filesystem.h" +#include "filepathcachinginterface.h" + +#include <utils/algorithm.h> + +#include <QDateTime> +#include <QDir> +#include <QFileInfo> + +namespace ClangBackEnd { + +FilePathIds FileSystem::directoryEntries(const QString &directoryPath) const +{ + QDir directory{directoryPath}; + + QFileInfoList fileInfos = directory.entryInfoList(); + + FilePathIds filePathIds = Utils::transform<FilePathIds>(fileInfos, [&](const QFileInfo &fileInfo) { + return m_filePathCache.filePathId(FilePath{fileInfo.path()}); + }); + + std::sort(filePathIds.begin(), filePathIds.end()); + + return filePathIds; +} + +long long FileSystem::lastModified(FilePathId filePathId) const +{ + QFileInfo fileInfo(QString(m_filePathCache.filePath(filePathId))); + + fileInfo.refresh(); + + return fileInfo.lastModified().toMSecsSinceEpoch() / 1000; +} + +} // namespace ClangBackEnd diff --git a/src/libs/clangsupport/filesystem.h b/src/libs/clangsupport/filesystem.h new file mode 100644 index 0000000000..ecf332de55 --- /dev/null +++ b/src/libs/clangsupport/filesystem.h @@ -0,0 +1,48 @@ +/**************************************************************************** +** +** Copyright (C) 2019 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 "filestatuscache.h" +#include "filesysteminterface.h" + +namespace ClangBackEnd { +class FilePathCachingInterface; + +class CLANGSUPPORT_EXPORT FileSystem final : public FileSystemInterface +{ +public: + FileSystem(FilePathCachingInterface &filePathCache) + : m_filePathCache(filePathCache) + {} + + FilePathIds directoryEntries(const QString &directoryPath) const override; + long long lastModified(FilePathId filePathId) const override; + +private: + FilePathCachingInterface &m_filePathCache; +}; + +} // namespace ClangBackEnd diff --git a/src/libs/clangsupport/filesysteminterface.h b/src/libs/clangsupport/filesysteminterface.h new file mode 100644 index 0000000000..9d64d2a93a --- /dev/null +++ b/src/libs/clangsupport/filesysteminterface.h @@ -0,0 +1,43 @@ +/**************************************************************************** +** +** Copyright (C) 2019 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 "filepathid.h" + +#include <utils/smallstringview.h> + +namespace ClangBackEnd { + +class FileSystemInterface +{ +public: + virtual FilePathIds directoryEntries(const QString &directoryPath) const = 0; + virtual long long lastModified(FilePathId filePathId) const = 0; + +protected: + ~FileSystemInterface() = default; +}; +} // namespace ClangBackEnd diff --git a/src/libs/utils/smallstringview.h b/src/libs/utils/smallstringview.h index 6052ff8777..911ba2eafd 100644 --- a/src/libs/utils/smallstringview.h +++ b/src/libs/utils/smallstringview.h @@ -179,6 +179,10 @@ public: return m_pointer[0] == characterToSearch; } + char back() const { return m_pointer[m_size - 1]; } + + char operator[](std::size_t index) { return m_pointer[index]; } + private: const char *m_pointer = ""; size_type m_size = 0; diff --git a/src/tools/clangpchmanagerbackend/clangpchmanagerbackendmain.cpp b/src/tools/clangpchmanagerbackend/clangpchmanagerbackendmain.cpp index 0ce49e829d..d9e7dd3175 100644 --- a/src/tools/clangpchmanagerbackend/clangpchmanagerbackendmain.cpp +++ b/src/tools/clangpchmanagerbackend/clangpchmanagerbackendmain.cpp @@ -31,6 +31,7 @@ #include <environment.h> #include <executeinloop.h> #include <filepathcaching.h> +#include <filesystem.h> #include <generatedfiles.h> #include <modifiedtimechecker.h> #include <pchcreator.h> @@ -179,7 +180,8 @@ struct Data // because we have a cycle dependency Sqlite::Database database; ClangBackEnd::RefactoringDatabaseInitializer<Sqlite::Database> databaseInitializer{database}; ClangBackEnd::FilePathCaching filePathCache{database}; - ClangPathWatcher<QFileSystemWatcher, QTimer> includeWatcher{filePathCache}; + ClangBackEnd::FileSystem fileSystem{filePathCache}; + ClangPathWatcher<QFileSystemWatcher, QTimer> includeWatcher{filePathCache, fileSystem}; ApplicationEnvironment environment; ProjectPartsStorage<> projectPartsStorage{database}; PrecompiledHeaderStorage<> preCompiledHeaderStorage{database}; diff --git a/src/tools/clangrefactoringbackend/source/clangrefactoringbackend-source.pri b/src/tools/clangrefactoringbackend/source/clangrefactoringbackend-source.pri index e7e83da788..a3da84d83b 100644 --- a/src/tools/clangrefactoringbackend/source/clangrefactoringbackend-source.pri +++ b/src/tools/clangrefactoringbackend/source/clangrefactoringbackend-source.pri @@ -17,8 +17,6 @@ HEADERS += \ $$PWD/symbolsvisitorbase.h \ $$PWD/usedmacro.h \ $$PWD/sourcedependency.h \ - $$PWD/filestatus.h \ - $$PWD/filestatuscache.h \ $$PWD/indexdataconsumer.h \ $$PWD/sourcesmanager.h \ $$PWD/symbolindexertaskqueue.h \ @@ -67,5 +65,4 @@ HEADERS += \ SOURCES += \ $$PWD/filestatuspreprocessorcallbacks.cpp \ $$PWD/sourcerangefilter.cpp \ - $$PWD/symbolindexer.cpp \ - $$PWD/filestatuscache.cpp + $$PWD/symbolindexer.cpp diff --git a/src/tools/clangrefactoringbackend/source/filestatuscache.cpp b/src/tools/clangrefactoringbackend/source/filestatuscache.cpp deleted file mode 100644 index 14ad48da14..0000000000 --- a/src/tools/clangrefactoringbackend/source/filestatuscache.cpp +++ /dev/null @@ -1,93 +0,0 @@ -/**************************************************************************** -** -** 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 "filestatuscache.h" - -#include <QDateTime> -#include <QFileInfo> - -namespace ClangBackEnd { - -FileStatusCache::FileStatusCache(FilePathCachingInterface &filePathCache) - : m_filePathCache(filePathCache) -{ - -} - -long long FileStatusCache::lastModifiedTime(FilePathId filePathId) const -{ - return findEntry(filePathId).lastModified; -} - -void FileStatusCache::update(FilePathId filePathId) -{ - auto found = std::lower_bound(m_cacheEntries.begin(), - m_cacheEntries.end(), - Internal::FileStatusCacheEntry{filePathId}, - [] (const auto &first, const auto &second) { - return first.filePathId < second.filePathId; - }); - - if (found != m_cacheEntries.end() && found->filePathId == filePathId) { - QFileInfo fileInfo = qFileInfo(filePathId); - found->lastModified = fileInfo.lastModified().toMSecsSinceEpoch() / 1000; - } -} - -FileStatusCache::size_type FileStatusCache::size() const -{ - return m_cacheEntries.size(); -} - -Internal::FileStatusCacheEntry FileStatusCache::findEntry(FilePathId filePathId) const -{ - auto found = std::lower_bound(m_cacheEntries.begin(), - m_cacheEntries.end(), - Internal::FileStatusCacheEntry{filePathId}, - [] (const auto &first, const auto &second) { - return first.filePathId < second.filePathId; - }); - - if (found != m_cacheEntries.end() && found->filePathId == filePathId) - return *found; - - QFileInfo fileInfo = qFileInfo(filePathId); - auto inserted = m_cacheEntries.emplace(found, - filePathId, - fileInfo.lastModified().toMSecsSinceEpoch() / 1000); - - return *inserted; -} - -QFileInfo FileStatusCache::qFileInfo(FilePathId filePathId) const -{ - QFileInfo fileInfo(QString(m_filePathCache.filePath(filePathId))); - - fileInfo.refresh(); - - return fileInfo; -} - -} // namespace ClangBackEnd diff --git a/src/tools/clangrefactoringbackend/source/symbolindexing.h b/src/tools/clangrefactoringbackend/source/symbolindexing.h index 5c391503bf..bed279904d 100644 --- a/src/tools/clangrefactoringbackend/source/symbolindexing.h +++ b/src/tools/clangrefactoringbackend/source/symbolindexing.h @@ -39,6 +39,7 @@ #include <projectpartsstorage.h> #include <filepathcachingfwd.h> +#include <filesystem.h> #include <modifiedtimechecker.h> #include <refactoringdatabaseinitializer.h> @@ -142,8 +143,9 @@ private: PrecompiledHeaderStorage<Sqlite::Database> m_precompiledHeaderStorage; ProjectPartsStorage<Sqlite::Database> m_projectPartsStorage; SymbolStorage m_symbolStorage; - ClangPathWatcher<QFileSystemWatcher, QTimer> m_sourceWatcher{m_filePathCache}; - FileStatusCache m_fileStatusCache{m_filePathCache}; + FileSystem m_fileSytem{m_filePathCache}; + ClangPathWatcher<QFileSystemWatcher, QTimer> m_sourceWatcher{m_filePathCache, m_fileSytem}; + FileStatusCache m_fileStatusCache{m_fileSytem}; SymbolsCollectorManager m_collectorManger; ProgressCounter m_progressCounter; std::function<TimeStamp(FilePathView filePath)> getModifiedTime{ diff --git a/tests/unit/unittest/changedfilepathcompressor-test.cpp b/tests/unit/unittest/changedfilepathcompressor-test.cpp deleted file mode 100644 index 08e66a2541..0000000000 --- a/tests/unit/unittest/changedfilepathcompressor-test.cpp +++ /dev/null @@ -1,112 +0,0 @@ -/**************************************************************************** -** -** 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 "googletest.h" - -#include "mocktimer.h" - -#include <changedfilepathcompressor.h> -#include <filepathcaching.h> -#include <refactoringdatabaseinitializer.h> - -namespace { - -using testing::ElementsAre; -using testing::Invoke; -using testing::IsEmpty; -using testing::NiceMock; - -using ClangBackEnd::FilePath; -using ClangBackEnd::FilePathId; - -class ChangedFilePathCompressor : public testing::Test -{ -protected: - void SetUp() - { - compressor.setCallback(mockCompressorCallback.AsStdFunction()); - } - - FilePathId filePathId(const QString &filePath) - { - Utils::SmallString utf8FilePath{filePath}; - - return filePathCache.filePathId(ClangBackEnd::FilePathView{utf8FilePath}); - } - -protected: - Sqlite::Database database{":memory:", Sqlite::JournalMode::Memory}; - ClangBackEnd::RefactoringDatabaseInitializer<Sqlite::Database> initializer{database}; - ClangBackEnd::FilePathCaching filePathCache{database}; - NiceMock<MockFunction<void (const ClangBackEnd::FilePathIds &filePathIds)>> mockCompressorCallback; - ClangBackEnd::ChangedFilePathCompressor<NiceMock<MockTimer>> compressor{filePathCache}; - NiceMock<MockTimer> &mockTimer = compressor.timer(); - QString filePath1{"filePath1"}; - QString filePath2{"filePath2"}; - FilePathId filePathId1 = filePathId(filePath1); - FilePathId filePathId2 = filePathId(filePath2); -}; - -TEST_F(ChangedFilePathCompressor, AddFilePath) -{ - compressor.addFilePath(filePath1); - - ASSERT_THAT(compressor.takeFilePathIds(), ElementsAre(filePathId(filePath1))); -} - -TEST_F(ChangedFilePathCompressor, NoFilePathsAferTakenThem) -{ - compressor.addFilePath(filePath1); - - compressor.takeFilePathIds(); - - ASSERT_THAT(compressor.takeFilePathIds(), IsEmpty()); -} - -TEST_F(ChangedFilePathCompressor, CallRestartTimerAfterAddingPath) -{ - EXPECT_CALL(mockTimer, start(20)); - - compressor.addFilePath(filePath1); -} - -TEST_F(ChangedFilePathCompressor, CallTimeOutAfterAddingPath) -{ - EXPECT_CALL(mockCompressorCallback, Call(ElementsAre(filePathId1, filePathId2))); - - compressor.addFilePath(filePath1); - compressor.addFilePath(filePath2); -} - -TEST_F(ChangedFilePathCompressor, RemoveDuplicates) -{ - EXPECT_CALL(mockCompressorCallback, Call(ElementsAre(filePathId1, filePathId2))); - - compressor.addFilePath(filePath1); - compressor.addFilePath(filePath2); - compressor.addFilePath(filePath1); -} - -} diff --git a/tests/unit/unittest/clangpathwatcher-test.cpp b/tests/unit/unittest/clangpathwatcher-test.cpp index 00fe91dc49..f4d99e358d 100644 --- a/tests/unit/unittest/clangpathwatcher-test.cpp +++ b/tests/unit/unittest/clangpathwatcher-test.cpp @@ -25,10 +25,11 @@ #include "googletest.h" -#include "mocktimer.h" +#include "mockclangpathwatchernotifier.h" #include "mockfilepathcaching.h" +#include "mockfilesystem.h" #include "mockqfilesystemwatcher.h" -#include "mockclangpathwatchernotifier.h" +#include "mocktimer.h" #include <clangpathwatcher.h> @@ -55,91 +56,126 @@ using ClangBackEnd::WatcherEntry; class ClangPathWatcher : public testing::Test { protected: - void SetUp(); + void SetUp() + { + ON_CALL(mockFilePathCache, filePathId(Eq(path1))).WillByDefault(Return(pathIds[0])); + ON_CALL(mockFilePathCache, filePathId(Eq(path2))).WillByDefault(Return(pathIds[1])); + ON_CALL(mockFilePathCache, filePathId(Eq(path3))).WillByDefault(Return(pathIds[2])); + ON_CALL(mockFilePathCache, filePathId(Eq(path4))).WillByDefault(Return(pathIds[3])); + ON_CALL(mockFilePathCache, filePathId(Eq(path5))).WillByDefault(Return(pathIds[4])); + ON_CALL(mockFilePathCache, filePath(Eq(pathIds[0]))).WillByDefault(Return(FilePath{path1})); + ON_CALL(mockFilePathCache, filePath(Eq(pathIds[1]))).WillByDefault(Return(FilePath{path2})); + ON_CALL(mockFilePathCache, filePath(Eq(pathIds[2]))).WillByDefault(Return(FilePath{path3})); + ON_CALL(mockFilePathCache, filePath(Eq(pathIds[3]))).WillByDefault(Return(FilePath{path4})); + ON_CALL(mockFilePathCache, filePath(Eq(pathIds[4]))).WillByDefault(Return(FilePath{path5})); + ON_CALL(mockFilePathCache, directoryPathId(TypedEq<FilePathId>(pathIds[0]))) + .WillByDefault(Return(directoryPaths[0])); + ON_CALL(mockFilePathCache, directoryPathId(TypedEq<FilePathId>(pathIds[1]))) + .WillByDefault(Return(directoryPaths[0])); + ON_CALL(mockFilePathCache, directoryPathId(TypedEq<FilePathId>(pathIds[2]))) + .WillByDefault(Return(directoryPaths[1])); + ON_CALL(mockFilePathCache, directoryPathId(TypedEq<FilePathId>(pathIds[3]))) + .WillByDefault(Return(directoryPaths[1])); + ON_CALL(mockFilePathCache, directoryPathId(TypedEq<FilePathId>(pathIds[4]))) + .WillByDefault(Return(directoryPaths[2])); + ON_CALL(mockFileSystem, lastModified(_)).WillByDefault(Return(1)); + ON_CALL(mockFilePathCache, + directoryPathId(TypedEq<Utils::SmallStringView>(directoryPathString))) + .WillByDefault(Return(directoryPaths[0])); + ON_CALL(mockFilePathCache, + directoryPathId(TypedEq<Utils::SmallStringView>(directoryPathString2))) + .WillByDefault(Return(directoryPaths[1])); + ON_CALL(mockFilePathCache, directoryPath(Eq(directoryPaths[0]))) + .WillByDefault(Return(directoryPath)); + ON_CALL(mockFilePathCache, directoryPath(Eq(directoryPaths[1]))) + .WillByDefault(Return(directoryPath2)); + ON_CALL(mockFilePathCache, directoryPath(Eq(directoryPaths[2]))) + .WillByDefault(Return(directoryPath3)); + ON_CALL(mockFileSystem, directoryEntries(Eq(directoryPath))) + .WillByDefault(Return(FilePathIds{pathIds[0], pathIds[1]})); + ON_CALL(mockFileSystem, directoryEntries(Eq(directoryPath2))) + .WillByDefault(Return(FilePathIds{pathIds[2], pathIds[3]})); + ON_CALL(mockFileSystem, directoryEntries(Eq(directoryPath3))) + .WillByDefault(Return(FilePathIds{pathIds[4]})); + } static WatcherEntries sorted(WatcherEntries &&entries) { std::stable_sort(entries.begin(), entries.end()); - return entries; + return std::move(entries); } protected: - NiceMock<MockFilePathCaching> filePathCache; + NiceMock<MockFilePathCaching> mockFilePathCache; NiceMock<MockClangPathWatcherNotifier> notifier; - Watcher watcher{filePathCache, ¬ifier}; + NiceMock<MockFileSystem> mockFileSystem; + Watcher watcher{mockFilePathCache, mockFileSystem, ¬ifier}; NiceMock<MockQFileSytemWatcher> &mockQFileSytemWatcher = watcher.fileSystemWatcher(); ProjectPartId id1{2}; ProjectPartId id2{3}; ProjectPartId id3{4}; FilePathView path1{"/path/path1"}; FilePathView path2{"/path/path2"}; + FilePathView path3{"/path2/path1"}; + FilePathView path4{"/path2/path2"}; + FilePathView path5{"/path3/path"}; QString path1QString = QString(path1.toStringView()); QString path2QString = QString(path2.toStringView()); - FilePathIds pathIds = {1, 2}; + QString directoryPath = "/path"; + QString directoryPath2 = "/path2"; + QString directoryPath3 = "/path3"; + Utils::PathString directoryPathString = directoryPath; + Utils::PathString directoryPathString2 = directoryPath2; + FilePathIds pathIds = {1, 2, 3, 4, 5}; + ClangBackEnd::DirectoryPathIds directoryPaths = {1, 2, 3}; ClangBackEnd::ProjectPartIds ids{id1, id2, id3}; - WatcherEntry watcherEntry1{ids[0], pathIds[0]}; - WatcherEntry watcherEntry2{ids[1], pathIds[0]}; - WatcherEntry watcherEntry3{ids[0], pathIds[1]}; - WatcherEntry watcherEntry4{ids[1], pathIds[1]}; - WatcherEntry watcherEntry5{ids[2], pathIds[1]}; + WatcherEntry watcherEntry1{ids[0], directoryPaths[0], pathIds[0]}; + WatcherEntry watcherEntry2{ids[1], directoryPaths[0], pathIds[0]}; + WatcherEntry watcherEntry3{ids[0], directoryPaths[0], pathIds[1]}; + WatcherEntry watcherEntry4{ids[1], directoryPaths[0], pathIds[1]}; + WatcherEntry watcherEntry5{ids[2], directoryPaths[0], pathIds[1]}; + WatcherEntry watcherEntry6{ids[0], directoryPaths[1], pathIds[2]}; + WatcherEntry watcherEntry7{ids[1], directoryPaths[1], pathIds[3]}; }; -TEST_F(ClangPathWatcher, ConvertWatcherEntriesToQStringList) -{ - auto convertedList = watcher.convertWatcherEntriesToQStringList(sorted({watcherEntry1, watcherEntry3})); - - ASSERT_THAT(convertedList, ElementsAre(path1QString, path2QString)); -} - -TEST_F(ClangPathWatcher, UniquePaths) -{ - auto uniqueEntries = watcher.uniquePaths(sorted({watcherEntry1, watcherEntry2, watcherEntry3, watcherEntry4})); - - ASSERT_THAT(uniqueEntries, ElementsAre(watcherEntry1, watcherEntry3)); -} - -TEST_F(ClangPathWatcher, NotWatchedEntries) -{ - watcher.addEntries({watcherEntry1, watcherEntry4}); - - auto newEntries = watcher.notWatchedEntries(sorted({watcherEntry1, watcherEntry2, watcherEntry3, watcherEntry4})); - - ASSERT_THAT(newEntries, ElementsAre(watcherEntry2, watcherEntry3)); -} - TEST_F(ClangPathWatcher, AddIdPaths) { - EXPECT_CALL(mockQFileSytemWatcher, addPaths(QStringList{path1QString, path2QString})); + EXPECT_CALL(mockQFileSytemWatcher, + addPaths(UnorderedElementsAre(QString(directoryPath), QString(directoryPath2)))); - watcher.updateIdPaths({{id1, {pathIds[0], pathIds[1]}}, {id2, {pathIds[0], pathIds[1]}}}); + watcher.updateIdPaths( + {{id1, {pathIds[0], pathIds[1], pathIds[2]}}, {id2, {pathIds[0], pathIds[1], pathIds[3]}}}); } TEST_F(ClangPathWatcher, UpdateIdPathsCallsAddPathInFileWatcher) { - watcher.updateIdPaths({{id1, {pathIds[0]}}, {id2, {pathIds[0]}}}); + watcher.updateIdPaths({{id1, {pathIds[0], pathIds[1]}}, {id2, {pathIds[0], pathIds[1]}}}); - EXPECT_CALL(mockQFileSytemWatcher, addPaths(QStringList{path2QString})); + EXPECT_CALL(mockQFileSytemWatcher, addPaths(UnorderedElementsAre(QString(directoryPath2)))); - watcher.updateIdPaths({{id1, {pathIds[0], pathIds[1]}}, {id2, {pathIds[0], pathIds[1]}}}); + watcher.updateIdPaths( + {{id1, {pathIds[0], pathIds[1], pathIds[2]}}, {id2, {pathIds[0], pathIds[1], pathIds[3]}}}); } TEST_F(ClangPathWatcher, UpdateIdPathsAndRemoveUnusedPathsCallsRemovePathInFileWatcher) { - watcher.updateIdPaths({{id1, {pathIds[0], pathIds[1]}}, {id2, {pathIds[0], pathIds[1]}}, {id3, {pathIds[0]}}}); + watcher.updateIdPaths( + {{id1, {pathIds[0], pathIds[1], pathIds[2]}}, {id2, {pathIds[0], pathIds[1], pathIds[3]}}}); - EXPECT_CALL(mockQFileSytemWatcher, removePaths(QStringList{path2QString})); + EXPECT_CALL(mockQFileSytemWatcher, removePaths(UnorderedElementsAre(QString(directoryPath2)))); - watcher.updateIdPaths({{id1, {pathIds[0]}}, {id2, {pathIds[0]}}}); + watcher.updateIdPaths({{id1, {pathIds[0], pathIds[1]}}, {id2, {pathIds[0], pathIds[1]}}}); } TEST_F(ClangPathWatcher, UpdateIdPathsAndRemoveUnusedPathsDoNotCallsRemovePathInFileWatcher) { - watcher.updateIdPaths({{id1, {pathIds[0], pathIds[1]}}, {id2, {pathIds[0], pathIds[1]}}, {id3, {pathIds[0]}}}); + watcher.updateIdPaths({{id1, {pathIds[0], pathIds[1], pathIds[2]}}, + {id2, {pathIds[0], pathIds[1], pathIds[3]}}, + {id3, {pathIds[0]}}}); - EXPECT_CALL(mockQFileSytemWatcher, removePaths(QStringList{path2QString})) - .Times(0); + EXPECT_CALL(mockQFileSytemWatcher, removePaths(_)).Times(0); - watcher.updateIdPaths({{id1, {pathIds[1]}}, {id2, {pathIds[0]}}}); + watcher.updateIdPaths({{id1, {pathIds[1]}}, {id2, {pathIds[3]}}}); } TEST_F(ClangPathWatcher, UpdateIdPathsAndRemoveUnusedPaths) @@ -165,36 +201,18 @@ TEST_F(ClangPathWatcher, ExtractSortedIdsFromConvertIdPaths) ASSERT_THAT(entriesAndIds.second, ElementsAre(ids[0], ids[1], ids[2])); } -TEST_F(ClangPathWatcher, NotWatchedPaths) -{ - watcher.mergeToWatchedEntries(sorted({watcherEntry1})); - - auto newEntries = watcher.notWatchedPaths({watcherEntry2, watcherEntry3}); - - ASSERT_THAT(newEntries, ElementsAre(watcherEntry3)); -} - -TEST_F(ClangPathWatcher, AddedPaths) -{ - watcher.mergeToWatchedEntries(sorted({watcherEntry1, watcherEntry2})); - - auto filteredEntries = watcher.filterNotWatchedPaths({watcherEntry1, watcherEntry2, watcherEntry3, watcherEntry4}); - - ASSERT_THAT(filteredEntries, ElementsAre(watcherEntry3)); -} - TEST_F(ClangPathWatcher, MergeEntries) { - watcher.mergeToWatchedEntries(sorted({watcherEntry1, watcherEntry4})); + watcher.updateIdPaths({{id1, {pathIds[0]}}, {id2, {pathIds[1]}}}); ASSERT_THAT(watcher.watchedEntries(), ElementsAre(watcherEntry1, watcherEntry4)); } TEST_F(ClangPathWatcher, MergeMoreEntries) { - watcher.mergeToWatchedEntries(sorted({watcherEntry1, watcherEntry4})); + watcher.updateIdPaths({{id2, {pathIds[0], pathIds[1]}}}); - watcher.mergeToWatchedEntries(sorted({watcherEntry2, watcherEntry3})); + watcher.updateIdPaths({{id1, {pathIds[0], pathIds[1]}}}); ASSERT_THAT(watcher.watchedEntries(), ElementsAre(watcherEntry1, watcherEntry2, watcherEntry3, watcherEntry4)); } @@ -204,48 +222,48 @@ TEST_F(ClangPathWatcher, AddEmptyEntries) EXPECT_CALL(mockQFileSytemWatcher, addPaths(_)) .Times(0); - watcher.addEntries({}); + watcher.updateIdPaths({}); } TEST_F(ClangPathWatcher, AddEntriesWithSameIdAndDifferentPaths) { - EXPECT_CALL(mockQFileSytemWatcher, addPaths(QStringList{path1QString, path2QString})); + EXPECT_CALL(mockQFileSytemWatcher, + addPaths(ElementsAre(directoryPath, directoryPath2, directoryPath3))); - watcher.addEntries({watcherEntry1, watcherEntry3}); + watcher.updateIdPaths({{id1, {pathIds[0], pathIds[1], pathIds[2], pathIds[4]}}}); } TEST_F(ClangPathWatcher, AddEntriesWithDifferentIdAndSamePaths) { - EXPECT_CALL(mockQFileSytemWatcher, addPaths(QStringList{path1QString})); + EXPECT_CALL(mockQFileSytemWatcher, addPaths(ElementsAre(directoryPath))); - watcher.addEntries({watcherEntry1, watcherEntry2}); + watcher.updateIdPaths({{id1, {pathIds[0], pathIds[1]}}}); } TEST_F(ClangPathWatcher, DontAddNewEntriesWithSameIdAndSamePaths) { - watcher.addEntries({watcherEntry1}); + watcher.updateIdPaths({{id1, {pathIds[0], pathIds[1], pathIds[2], pathIds[3], pathIds[4]}}}); - EXPECT_CALL(mockQFileSytemWatcher, addPaths(QStringList{path1QString})) - .Times(0); + EXPECT_CALL(mockQFileSytemWatcher, addPaths(_)).Times(0); - watcher.addEntries({watcherEntry1}); + watcher.updateIdPaths({{id1, {pathIds[0], pathIds[1], pathIds[2], pathIds[3], pathIds[4]}}}); } TEST_F(ClangPathWatcher, DontAddNewEntriesWithDifferentIdAndSamePaths) { - watcher.addEntries({watcherEntry1}); + watcher.updateIdPaths({{id1, {pathIds[0], pathIds[1], pathIds[2], pathIds[3], pathIds[4]}}}); - EXPECT_CALL(mockQFileSytemWatcher, addPaths(QStringList{path1QString})) - .Times(0); + EXPECT_CALL(mockQFileSytemWatcher, addPaths(_)).Times(0); - watcher.addEntries({watcherEntry2}); + watcher.updateIdPaths({{id2, {pathIds[0], pathIds[1], pathIds[2], pathIds[3], pathIds[4]}}}); } TEST_F(ClangPathWatcher, RemoveEntriesWithId) { - watcher.addEntries(sorted({watcherEntry1, watcherEntry2, watcherEntry3, watcherEntry4, watcherEntry5})); + watcher.updateIdPaths( + {{id1, {pathIds[0], pathIds[1]}}, {id2, {pathIds[0], pathIds[1]}}, {id3, {pathIds[1]}}}); - watcher.removeIdsFromWatchedEntries({ids[0]}); + watcher.removeIds({id1}); ASSERT_THAT(watcher.watchedEntries(), ElementsAre(watcherEntry2, watcherEntry4, watcherEntry5)); } @@ -260,37 +278,52 @@ TEST_F(ClangPathWatcher, RemoveNoPathsForEmptyIds) TEST_F(ClangPathWatcher, RemoveNoPathsForOneId) { - watcher.addEntries(sorted({watcherEntry1, watcherEntry2, watcherEntry3, watcherEntry4})); + watcher.updateIdPaths( + {{id1, {pathIds[0], pathIds[1]}}, {id2, {pathIds[0], pathIds[1], pathIds[3]}}}); EXPECT_CALL(mockQFileSytemWatcher, removePaths(_)) .Times(0); - watcher.removeIds({id1}); + watcher.removeIds({id3}); } TEST_F(ClangPathWatcher, RemovePathForOneId) { - watcher.addEntries(sorted({watcherEntry1, watcherEntry2, watcherEntry3})); + watcher.updateIdPaths( + {{id1, {pathIds[0], pathIds[1]}}, {id2, {pathIds[0], pathIds[1], pathIds[3]}}}); - EXPECT_CALL(mockQFileSytemWatcher, removePaths(QStringList{path2QString})); + EXPECT_CALL(mockQFileSytemWatcher, removePaths(ElementsAre(directoryPath2))); - watcher.removeIds({id1}); + watcher.removeIds({id2}); +} + +TEST_F(ClangPathWatcher, RemoveNoPathSecondTime) +{ + watcher.updateIdPaths( + {{id1, {pathIds[0], pathIds[1]}}, {id2, {pathIds[0], pathIds[1], pathIds[3]}}}); + watcher.removeIds({id2}); + + EXPECT_CALL(mockQFileSytemWatcher, removePaths(_)).Times(0); + + watcher.removeIds({id2}); } TEST_F(ClangPathWatcher, RemoveAllPathsForThreeId) { - watcher.addEntries(sorted({watcherEntry1, watcherEntry2, watcherEntry3, watcherEntry4, watcherEntry5})); + watcher.updateIdPaths( + {{id1, {pathIds[0], pathIds[1], pathIds[2]}}, {id2, {pathIds[0], pathIds[1], pathIds[3]}}}); - EXPECT_CALL(mockQFileSytemWatcher, removePaths(QStringList{path1QString, path2QString})); + EXPECT_CALL(mockQFileSytemWatcher, removePaths(ElementsAre(directoryPath, directoryPath2))); watcher.removeIds({id1, id2, id3}); } TEST_F(ClangPathWatcher, RemoveOnePathForTwoId) { - watcher.addEntries(sorted({watcherEntry1, watcherEntry2, watcherEntry3, watcherEntry4, watcherEntry5})); + watcher.updateIdPaths( + {{id1, {pathIds[0], pathIds[1]}}, {id2, {pathIds[0], pathIds[1]}}, {id3, {pathIds[3]}}}); - EXPECT_CALL(mockQFileSytemWatcher, removePaths(QStringList{path1QString})); + EXPECT_CALL(mockQFileSytemWatcher, removePaths(ElementsAre(directoryPath))); watcher.removeIds({id1, id2}); } @@ -313,71 +346,51 @@ TEST_F(ClangPathWatcher, RemoveUnusedEntries) ASSERT_THAT(watcher.watchedEntries(), ElementsAre(watcherEntry1, watcherEntry4, watcherEntry5)); } -TEST_F(ClangPathWatcher, EmptyVectorNotifyFileChange) -{ - watcher.addEntries({watcherEntry3}); - - EXPECT_CALL(notifier, pathsWithIdsChanged(IsEmpty())); - - mockQFileSytemWatcher.fileChanged(path1QString); -} - -TEST_F(ClangPathWatcher, NotifyFileChange) -{ - watcher.addEntries(sorted({watcherEntry1, watcherEntry2, watcherEntry3, watcherEntry4, watcherEntry5})); - - EXPECT_CALL(notifier, pathsWithIdsChanged(ElementsAre(id1, id2))); - - mockQFileSytemWatcher.fileChanged(path1QString); -} - TEST_F(ClangPathWatcher, TwoNotifyFileChanges) { - watcher.addEntries(sorted({watcherEntry1, watcherEntry2, watcherEntry3, watcherEntry4, watcherEntry5})); + watcher.updateIdPaths({{id1, {pathIds[0], pathIds[1], pathIds[2]}}, + {id2, {pathIds[0], pathIds[1], pathIds[3]}}, + {id3, {pathIds[4]}}}); + ON_CALL(mockFileSystem, lastModified(Eq(pathIds[0]))).WillByDefault(Return(2)); + ON_CALL(mockFileSystem, lastModified(Eq(pathIds[1]))).WillByDefault(Return(2)); + ON_CALL(mockFileSystem, lastModified(Eq(pathIds[3]))).WillByDefault(Return(2)); - EXPECT_CALL(notifier, pathsWithIdsChanged(ElementsAre(id1, id2, id3))); + EXPECT_CALL(notifier, pathsWithIdsChanged(ElementsAre(id1, id2))); - mockQFileSytemWatcher.fileChanged(path2QString); - mockQFileSytemWatcher.fileChanged(path1QString); + mockQFileSytemWatcher.directoryChanged(directoryPath); + mockQFileSytemWatcher.directoryChanged(directoryPath2); } TEST_F(ClangPathWatcher, NotifyForPathChanges) { - watcher.addEntries({watcherEntry1}); + watcher.updateIdPaths( + {{id1, {pathIds[0], pathIds[1], pathIds[2]}}, {id2, {pathIds[0], pathIds[1], pathIds[3]}}}); + ON_CALL(mockFileSystem, lastModified(Eq(pathIds[0]))).WillByDefault(Return(2)); + ON_CALL(mockFileSystem, lastModified(Eq(pathIds[3]))).WillByDefault(Return(2)); EXPECT_CALL(notifier, pathsChanged(ElementsAre(pathIds[0]))); - mockQFileSytemWatcher.fileChanged(path1QString); + mockQFileSytemWatcher.directoryChanged(directoryPath); } TEST_F(ClangPathWatcher, NoNotifyForUnwatchedPathChanges) { - watcher.addEntries({watcherEntry3}); + watcher.updateIdPaths({{id1, {pathIds[3]}}, {id2, {pathIds[3]}}}); EXPECT_CALL(notifier, pathsChanged(IsEmpty())); - mockQFileSytemWatcher.fileChanged(path1QString); + mockQFileSytemWatcher.directoryChanged(directoryPath); } TEST_F(ClangPathWatcher, NoDuplicatePathChanges) { - watcher.addEntries({watcherEntry1}); + watcher.updateIdPaths( + {{id1, {pathIds[0], pathIds[1], pathIds[2]}}, {id2, {pathIds[0], pathIds[1], pathIds[3]}}}); + ON_CALL(mockFileSystem, lastModified(Eq(pathIds[0]))).WillByDefault(Return(2)); EXPECT_CALL(notifier, pathsChanged(ElementsAre(pathIds[0]))); - mockQFileSytemWatcher.fileChanged(path1QString); - mockQFileSytemWatcher.fileChanged(path1QString); -} - -void ClangPathWatcher::SetUp() -{ - ON_CALL(filePathCache, filePathId(Eq(path1))) - .WillByDefault(Return(pathIds[0])); - ON_CALL(filePathCache, filePathId(Eq(path2))) - .WillByDefault(Return(pathIds[1])); - ON_CALL(filePathCache, filePath(pathIds[0])) - .WillByDefault(Return(FilePath{path1})); - ON_CALL(filePathCache, filePath(Eq(pathIds[1]))) - .WillByDefault(Return(FilePath{path2})); -} + mockQFileSytemWatcher.directoryChanged(directoryPath); + mockQFileSytemWatcher.directoryChanged(directoryPath); } +} // namespace diff --git a/tests/unit/unittest/directorypathcompressor-test.cpp b/tests/unit/unittest/directorypathcompressor-test.cpp new file mode 100644 index 0000000000..50853e8e1c --- /dev/null +++ b/tests/unit/unittest/directorypathcompressor-test.cpp @@ -0,0 +1,99 @@ +/**************************************************************************** +** +** 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 "googletest.h" + +#include "mockfilesystem.h" +#include "mocktimer.h" + +#include <directorypathcompressor.h> + +namespace { + +using testing::ElementsAre; +using testing::Invoke; +using testing::IsEmpty; +using testing::NiceMock; + +using ClangBackEnd::DirectoryPathId; +using ClangBackEnd::DirectoryPathIds; + +class DirectoryPathCompressor : public testing::Test +{ +protected: + void SetUp() + { + compressor.setCallback(mockCompressorCallback.AsStdFunction()); + } + +protected: + NiceMock<MockFunction<void(const DirectoryPathIds &directoryPathIds)>> mockCompressorCallback; + ClangBackEnd::DirectoryPathCompressor<NiceMock<MockTimer>> compressor; + NiceMock<MockTimer> &mockTimer = compressor.timer(); + DirectoryPathId directoryPathId1{1}; + DirectoryPathId directoryPathId2{2}; +}; + +TEST_F(DirectoryPathCompressor, AddFilePath) +{ + compressor.addDirectoryPathId(directoryPathId1); + + ASSERT_THAT(compressor.takeDirectoryPathIds(), ElementsAre(directoryPathId1)); +} + +TEST_F(DirectoryPathCompressor, NoFilePathsAferTakenThem) +{ + compressor.addDirectoryPathId(directoryPathId1); + + compressor.takeDirectoryPathIds(); + + ASSERT_THAT(compressor.takeDirectoryPathIds(), IsEmpty()); +} + +TEST_F(DirectoryPathCompressor, CallRestartTimerAfterAddingPath) +{ + EXPECT_CALL(mockTimer, start(20)); + + compressor.addDirectoryPathId(directoryPathId1); +} + +TEST_F(DirectoryPathCompressor, CallTimeOutAfterAddingPath) +{ + EXPECT_CALL(mockCompressorCallback, Call(ElementsAre(directoryPathId1, directoryPathId2))); + + compressor.addDirectoryPathId(directoryPathId1); + compressor.addDirectoryPathId(directoryPathId2); +} + +TEST_F(DirectoryPathCompressor, RemoveDuplicates) +{ + EXPECT_CALL(mockCompressorCallback, Call(ElementsAre(directoryPathId1, directoryPathId2))); + + compressor.addDirectoryPathId(directoryPathId1); + compressor.addDirectoryPathId(directoryPathId2); + compressor.addDirectoryPathId(directoryPathId1); +} + +} diff --git a/tests/unit/unittest/filepathcache-test.cpp b/tests/unit/unittest/filepathcache-test.cpp index 43b5b026bb..478e5f0215 100644 --- a/tests/unit/unittest/filepathcache-test.cpp +++ b/tests/unit/unittest/filepathcache-test.cpp @@ -31,6 +31,7 @@ namespace { +using ClangBackEnd::DirectoryPathId; using ClangBackEnd::FilePathId; using Cache = ClangBackEnd::FilePathCache<NiceMock<MockFilePathStorage>>; using ClangBackEnd::FilePathId; @@ -166,4 +167,153 @@ TEST_F(FilePathCache, DuplicateFilePathsAreEqual) ASSERT_THAT(filePath2Id, Eq(filePath1Id)); } +TEST_F(FilePathCache, DirectoryPathIdCallsFetchDirectoryId) +{ + EXPECT_CALL(mockStorage, fetchDirectoryId(Eq("/path/to"))); + + cache.directoryPathId(Utils::SmallString("/path/to")); +} + +TEST_F(FilePathCache, SecondDirectoryPathIdCallsNotFetchDirectoryId) +{ + cache.directoryPathId(Utils::SmallString("/path/to")); + + EXPECT_CALL(mockStorage, fetchDirectoryId(Eq("/path/to"))).Times(0); + + cache.directoryPathId(Utils::SmallString("/path/to")); +} + +TEST_F(FilePathCache, DirectoryPathIdWithTrailingSlash) +{ + EXPECT_CALL(mockStorage, fetchDirectoryId(Eq("/path/to"))); + + cache.directoryPathId(Utils::SmallString("/path/to/")); +} + +TEST_F(FilePathCache, DirectoryPathId) +{ + auto id = cache.directoryPathId(Utils::SmallString("/path/to")); + + ASSERT_THAT(id, Eq(DirectoryPathId{5})); +} + +TEST_F(FilePathCache, DirectoryPathIdIsAlreadyInCache) +{ + auto firstId = cache.directoryPathId(Utils::SmallString("/path/to")); + + auto secondId = cache.directoryPathId(Utils::SmallString("/path/to")); + + ASSERT_THAT(secondId, firstId); +} + +TEST_F(FilePathCache, DirectoryPathIdIsAlreadyInCacheWithTrailingSlash) +{ + auto firstId = cache.directoryPathId(Utils::SmallString("/path/to/")); + + auto secondId = cache.directoryPathId(Utils::SmallString("/path/to/")); + + ASSERT_THAT(secondId, firstId); +} + +TEST_F(FilePathCache, DirectoryPathIdIsAlreadyInCacheWithAndWithoutTrailingSlash) +{ + auto firstId = cache.directoryPathId(Utils::SmallString("/path/to/")); + + auto secondId = cache.directoryPathId(Utils::SmallString("/path/to")); + + ASSERT_THAT(secondId, firstId); +} + +TEST_F(FilePathCache, DirectoryPathIdIsAlreadyInCacheWithoutAndWithTrailingSlash) +{ + auto firstId = cache.directoryPathId(Utils::SmallString("/path/to")); + + auto secondId = cache.directoryPathId(Utils::SmallString("/path/to/")); + + ASSERT_THAT(secondId, firstId); +} + +TEST_F(FilePathCache, ThrowForGettingADirectoryPathWithAnInvalidId) +{ + DirectoryPathId directoryPathId; + + ASSERT_THROW(cache.directoryPath(directoryPathId), + ClangBackEnd::NoDirectoryPathForInvalidDirectoryPathId); +} + +TEST_F(FilePathCache, GetADirectoryPath) +{ + DirectoryPathId directoryPathId{5}; + + auto directoryPath = cache.directoryPath(directoryPathId); + + ASSERT_THAT(directoryPath, Eq(Utils::SmallStringView{"/path/to"})); +} + +TEST_F(FilePathCache, GetADirectoryPathWithCachedDirectoryPathId) +{ + DirectoryPathId directoryPathId{5}; + cache.directoryPath(directoryPathId); + + auto directoryPath = cache.directoryPath(directoryPathId); + + ASSERT_THAT(directoryPath, Eq(Utils::SmallStringView{"/path/to"})); +} + +TEST_F(FilePathCache, DirectoryPathCallsFetchDirectoryPath) +{ + EXPECT_CALL(mockStorage, fetchDirectoryPath(Eq(DirectoryPathId{5}))); + + cache.directoryPath(5); +} + +TEST_F(FilePathCache, SecondDirectoryPathCallsNotFetchDirectoryPath) +{ + cache.directoryPath(5); + + EXPECT_CALL(mockStorage, fetchDirectoryPath(_)).Times(0); + + cache.directoryPath(5); +} + +TEST_F(FilePathCache, ThrowForGettingADirectoryPathIdWithAnInvalidFilePathId) +{ + FilePathId filePathId; + + ASSERT_THROW(cache.directoryPathId(filePathId), ClangBackEnd::NoFilePathForInvalidFilePathId); +} + +TEST_F(FilePathCache, FetchDirectoryPathIdByFilePathId) +{ + auto directoryId = cache.directoryPathId(42); + + ASSERT_THAT(directoryId, Eq(5)); +} + +TEST_F(FilePathCache, FetchDirectoryPathIdByFilePathIdCached) +{ + cache.directoryPathId(42); + + auto directoryId = cache.directoryPathId(42); + + ASSERT_THAT(directoryId, Eq(5)); +} + +TEST_F(FilePathCache, FetchFilePathAfterFetchingDirectoryIdByFilePathId) +{ + cache.directoryPathId(42); + + auto filePath = cache.filePath(42); + + ASSERT_THAT(filePath, Eq("/path/to/file.cpp")); +} + +TEST_F(FilePathCache, FetchDirectoryPathIdAfterFetchingFilePathByFilePathId) +{ + cache.filePath(42); + + auto directoryId = cache.directoryPathId(42); + + ASSERT_THAT(directoryId, Eq(5)); } +} // namespace diff --git a/tests/unit/unittest/filepathstorage-test.cpp b/tests/unit/unittest/filepathstorage-test.cpp index 37f325daf0..fb60e32512 100644 --- a/tests/unit/unittest/filepathstorage-test.cpp +++ b/tests/unit/unittest/filepathstorage-test.cpp @@ -46,8 +46,8 @@ protected: void SetUp() { ON_CALL(selectDirectoryIdFromDirectoriesByDirectoryPath, - valueReturnInt32(_)) - .WillByDefault(Return(Utils::optional<int>())); + valueReturnInt32(A<Utils::SmallStringView>())) + .WillByDefault(Return(Utils::optional<int>())); ON_CALL(selectDirectoryIdFromDirectoriesByDirectoryPath, valueReturnInt32(Utils::SmallStringView(""))) .WillByDefault(Return(Utils::optional<int>(0))); @@ -77,9 +77,12 @@ protected: ON_CALL(selectSourceNameAndDirectoryIdFromSourcesBySourceId, valueReturnSourceNameAndDirectoryId(42)) .WillByDefault(Return(Utils::optional<ClangBackEnd::Sources::SourceNameAndDirectoryId>({"file.cpp", 5}))); + ON_CALL(selectDirectoryIdFromSourcesBySourceId, valueReturnInt32(TypedEq<int>(42))) + .WillByDefault(Return(Utils::optional<int>(5))); - EXPECT_CALL(selectDirectoryIdFromDirectoriesByDirectoryPath, valueReturnInt32(_)) - .Times(AnyNumber()); + EXPECT_CALL(selectDirectoryIdFromDirectoriesByDirectoryPath, + valueReturnInt32(A<Utils::SmallStringView>())) + .Times(AnyNumber()); EXPECT_CALL(selectSourceIdFromSourcesByDirectoryIdAndSourceName, valueReturnInt32(_, _)) .Times(AnyNumber()); EXPECT_CALL(insertIntoDirectories, write(An<Utils::SmallStringView>())) @@ -107,6 +110,7 @@ protected: MockSqliteWriteStatement &insertIntoDirectories = factory.insertIntoDirectories; MockSqliteWriteStatement &insertIntoSources = factory.insertIntoSources; MockSqliteReadStatement &selectAllSources = factory.selectAllSources; + MockSqliteReadStatement &selectDirectoryIdFromSourcesBySourceId = factory.selectDirectoryIdFromSourcesBySourceId; Storage storage{factory}; }; @@ -229,7 +233,7 @@ TEST_F(FilePathStorage, CallSelectForFetchingDirectoryIdForKnownPath) EXPECT_CALL(mockDatabase, deferredBegin()); EXPECT_CALL(selectDirectoryIdFromDirectoriesByDirectoryPath, - valueReturnInt32(Eq("/path/to"))); + valueReturnInt32(TypedEq<Utils::SmallStringView>("/path/to"))); EXPECT_CALL(mockDatabase, commit()); storage.fetchDirectoryId("/path/to"); @@ -267,7 +271,7 @@ TEST_F(FilePathStorage, CallSelectAndWriteForFetchingDirectoryIdForUnknownPath) EXPECT_CALL(mockDatabase, deferredBegin()); EXPECT_CALL(selectDirectoryIdFromDirectoriesByDirectoryPath, - valueReturnInt32(Eq("/some/not/known/path"))); + valueReturnInt32(TypedEq<Utils::SmallStringView>("/some/not/known/path"))); EXPECT_CALL(insertIntoDirectories, write(TypedEq<Utils::SmallStringView>("/some/not/known/path"))); EXPECT_CALL(mockDatabase, commit()); @@ -296,7 +300,7 @@ TEST_F(FilePathStorage, RestartFetchDirectoryIDIfTheStatementIsBusyInBeginBecaus EXPECT_CALL(mockDatabase, rollback()).Times(0); EXPECT_CALL(mockDatabase, deferredBegin()); EXPECT_CALL(selectDirectoryIdFromDirectoriesByDirectoryPath, - valueReturnInt32(Eq("/other/unknow/path"))); + valueReturnInt32(TypedEq<Utils::SmallStringView>("/other/unknow/path"))); EXPECT_CALL(insertIntoDirectories, write(TypedEq<Utils::SmallStringView>("/other/unknow/path"))); EXPECT_CALL(mockDatabase, commit()); @@ -310,13 +314,13 @@ TEST_F(FilePathStorage, EXPECT_CALL(mockDatabase, deferredBegin()); EXPECT_CALL(selectDirectoryIdFromDirectoriesByDirectoryPath, - valueReturnInt32(Eq("/other/unknow/path"))); + valueReturnInt32(TypedEq<Utils::SmallStringView>("/other/unknow/path"))); EXPECT_CALL(insertIntoDirectories, write(TypedEq<Utils::SmallStringView>("/other/unknow/path"))) .WillOnce(Throw(Sqlite::StatementIsBusy("busy"))); EXPECT_CALL(mockDatabase, rollback()); EXPECT_CALL(mockDatabase, deferredBegin()); EXPECT_CALL(selectDirectoryIdFromDirectoriesByDirectoryPath, - valueReturnInt32(Eq("/other/unknow/path"))); + valueReturnInt32(TypedEq<Utils::SmallStringView>("/other/unknow/path"))); EXPECT_CALL(insertIntoDirectories, write(TypedEq<Utils::SmallStringView>("/other/unknow/path"))); EXPECT_CALL(mockDatabase, commit()); @@ -329,13 +333,13 @@ TEST_F(FilePathStorage, CallSelectAndWriteForFetchingDirectoryIdTwoTimesIfTheInd EXPECT_CALL(mockDatabase,deferredBegin()); EXPECT_CALL(selectDirectoryIdFromDirectoriesByDirectoryPath, - valueReturnInt32(Eq("/other/unknow/path"))); + valueReturnInt32(TypedEq<Utils::SmallStringView>("/other/unknow/path"))); EXPECT_CALL(insertIntoDirectories, write(TypedEq<Utils::SmallStringView>("/other/unknow/path"))) .WillOnce(Throw(Sqlite::ConstraintPreventsModification("busy"))); EXPECT_CALL(mockDatabase, rollback()); EXPECT_CALL(mockDatabase,deferredBegin()); EXPECT_CALL(selectDirectoryIdFromDirectoriesByDirectoryPath, - valueReturnInt32(Eq("/other/unknow/path"))); + valueReturnInt32(TypedEq<Utils::SmallStringView>("/other/unknow/path"))); EXPECT_CALL(mockDatabase, commit()); storage.fetchDirectoryId("/other/unknow/path"); @@ -368,7 +372,6 @@ TEST_F(FilePathStorage, EXPECT_CALL(insertIntoSources, write(TypedEq<int>(5), TypedEq<Utils::SmallStringView>("otherunknownfile.h"))) .WillOnce(Throw(Sqlite::StatementIsBusy("busy"))); - ; EXPECT_CALL(mockDatabase, rollback()); EXPECT_CALL(mockDatabase, deferredBegin()); EXPECT_CALL(selectSourceIdFromSourcesByDirectoryIdAndSourceName, @@ -545,4 +548,59 @@ TEST_F(FilePathStorage, RestartFetchAllSourcesIfBeginIsBusy) storage.fetchAllSources(); } +TEST_F(FilePathStorage, FetchDirectoryIdForUnknownFileID) +{ + ASSERT_THROW(storage.fetchDirectoryId(1111), ClangBackEnd::SourceNameIdDoesNotExists); +} + +TEST_F(FilePathStorage, FetchDirectoryId) +{ + auto directoryId = storage.fetchDirectoryId(42); + + ASSERT_THAT(directoryId, 5); +} + +TEST_F(FilePathStorage, FetchDirectoryIdCalls) +{ + InSequence s; + + EXPECT_CALL(mockDatabase, lock()); + EXPECT_CALL(mockDatabase, deferredBegin()); + EXPECT_CALL(selectDirectoryIdFromSourcesBySourceId, valueReturnInt32(TypedEq<int>(42))); + EXPECT_CALL(mockDatabase, commit()); + EXPECT_CALL(mockDatabase, unlock()); + + storage.fetchDirectoryId(42); } + +TEST_F(FilePathStorage, FetchDirectoryIdCallsDatabaseIsBusy) +{ + InSequence s; + + EXPECT_CALL(mockDatabase, lock()); + EXPECT_CALL(mockDatabase, deferredBegin()).WillOnce(Throw(Sqlite::StatementIsBusy("busy"))); + EXPECT_CALL(mockDatabase, rollback()).Times(0); + EXPECT_CALL(mockDatabase, unlock()); + EXPECT_CALL(mockDatabase, lock()); + EXPECT_CALL(mockDatabase, deferredBegin()); + EXPECT_CALL(selectDirectoryIdFromSourcesBySourceId, valueReturnInt32(TypedEq<int>(42))); + EXPECT_CALL(mockDatabase, commit()); + EXPECT_CALL(mockDatabase, unlock()); + + storage.fetchDirectoryId(42); +} + +TEST_F(FilePathStorage, FetchDirectoryIdCallsThrows) +{ + InSequence s; + + EXPECT_CALL(mockDatabase, lock()); + EXPECT_CALL(mockDatabase, deferredBegin()); + EXPECT_CALL(selectDirectoryIdFromSourcesBySourceId, valueReturnInt32(TypedEq<int>(41))); + EXPECT_CALL(mockDatabase, rollback()); + EXPECT_CALL(mockDatabase, unlock()); + + ASSERT_ANY_THROW(storage.fetchDirectoryId(41)); +} + +} // namespace diff --git a/tests/unit/unittest/filepathstoragesqlitestatementfactory-test.cpp b/tests/unit/unittest/filepathstoragesqlitestatementfactory-test.cpp index ac902fa700..40af2d99c3 100644 --- a/tests/unit/unittest/filepathstoragesqlitestatementfactory-test.cpp +++ b/tests/unit/unittest/filepathstoragesqlitestatementfactory-test.cpp @@ -61,7 +61,13 @@ TEST_F(FilePathStorageSqliteStatementFactory, SelectSourceIdFromSourcesByDirecto Eq("SELECT sourceId FROM sources WHERE directoryId = ? AND sourceName = ?")); } -TEST_F(FilePathStorageSqliteStatementFactory, SelectSourceNameFromSourcesByDirectoryIdAndSourceId) +TEST_F(FilePathStorageSqliteStatementFactory, SelectSourceNameAndDirectoryIdFromSourcesByAndSourceId) +{ + ASSERT_THAT(factory.selectSourceNameAndDirectoryIdFromSourcesBySourceId.sqlStatement, + Eq("SELECT sourceName, directoryId FROM sources WHERE sourceId = ?")); +} + +TEST_F(FilePathStorageSqliteStatementFactory, SelectSourceNameAndDirectoryIdBySourceId) { ASSERT_THAT(factory.selectSourceNameAndDirectoryIdFromSourcesBySourceId.sqlStatement, Eq("SELECT sourceName, directoryId FROM sources WHERE sourceId = ?")); diff --git a/tests/unit/unittest/filestatuscache-test.cpp b/tests/unit/unittest/filestatuscache-test.cpp index db13bc28a7..07ba02655b 100644 --- a/tests/unit/unittest/filestatuscache-test.cpp +++ b/tests/unit/unittest/filestatuscache-test.cpp @@ -24,6 +24,7 @@ ****************************************************************************/ #include "googletest.h" +#include "mockfilesystem.h" #include <filepathcaching.h> #include <filestatuscache.h> @@ -37,31 +38,33 @@ namespace { using ClangBackEnd::FilePathId; +using ClangBackEnd::FilePathIds; class FileStatusCache : public testing::Test { protected: - FilePathId filePathId(Utils::SmallStringView path) const + FileStatusCache() { - return filePathCache.filePathId(ClangBackEnd::FilePathView(path)); - } - - void touchFile(FilePathId filePathId) - { - std::ofstream ostream(std::string(filePathCache.filePath(filePathId)), std::ios::binary); - ostream.write("\n", 1); - ostream.close(); + ON_CALL(fileSystem, lastModified(Eq(header))).WillByDefault(Return(headerLastModifiedTime)); + ON_CALL(fileSystem, lastModified(Eq(source))).WillByDefault(Return(sourceLastModifiedTime)); + ON_CALL(fileSystem, lastModified(Eq(header2))).WillByDefault(Return(header2LastModifiedTime)); + ON_CALL(fileSystem, lastModified(Eq(source2))).WillByDefault(Return(source2LastModifiedTime)); } protected: - Sqlite::Database database{":memory:", Sqlite::JournalMode::Memory}; - ClangBackEnd::RefactoringDatabaseInitializer<Sqlite::Database> databaseInitializer{database}; - ClangBackEnd::FilePathCaching filePathCache{database}; - ClangBackEnd::FileStatusCache cache{filePathCache}; - FilePathId header{filePathId(TESTDATA_DIR "/filestatuscache_header.h")}; - FilePathId source{filePathId(TESTDATA_DIR "/filestatuscache_header.cpp")}; - long long headerLastModifiedTime = QFileInfo(TESTDATA_DIR "/filestatuscache_header.h").lastModified().toSecsSinceEpoch(); - long long sourceLastModifiedTime = QFileInfo(TESTDATA_DIR "/filestatuscache_header.cpp").lastModified().toSecsSinceEpoch(); + NiceMock<MockFileSystem> fileSystem; + ClangBackEnd::FileStatusCache cache{fileSystem}; + FilePathId header{1}; + FilePathId source{2}; + FilePathId header2{3}; + FilePathId source2{4}; + FilePathIds entries{header, source, header2, source2}; + long long headerLastModifiedTime = 100; + long long headerLastModifiedTime2 = 110; + long long header2LastModifiedTime = 300; + long long header2LastModifiedTime2 = 310; + long long sourceLastModifiedTime = 200; + long long source2LastModifiedTime = 400; }; TEST_F(FileStatusCache, CreateEntry) @@ -134,18 +137,24 @@ TEST_F(FileStatusCache, AskNewEntryReverseOrderAddedForLastModifiedTime) TEST_F(FileStatusCache, UpdateFile) { - auto oldLastModified = cache.lastModifiedTime(header); - touchFile(header); + EXPECT_CALL(fileSystem, lastModified(Eq(header))) + .Times(2) + .WillOnce(Return(headerLastModifiedTime)) + .WillOnce(Return(headerLastModifiedTime2)); + cache.lastModifiedTime(header); cache.update(header); - ASSERT_THAT(cache.lastModifiedTime(header), Gt(oldLastModified)); + ASSERT_THAT(cache.lastModifiedTime(header), headerLastModifiedTime2); } TEST_F(FileStatusCache, UpdateFileDoesNotChangeEntryCount) { + EXPECT_CALL(fileSystem, lastModified(Eq(header))) + .Times(2) + .WillOnce(Return(headerLastModifiedTime)) + .WillOnce(Return(headerLastModifiedTime2)); cache.lastModifiedTime(header); - touchFile(header); cache.update(header); @@ -154,11 +163,136 @@ TEST_F(FileStatusCache, UpdateFileDoesNotChangeEntryCount) TEST_F(FileStatusCache, UpdateFileForNonExistingEntry) { - touchFile(header); - cache.update(header); ASSERT_THAT(cache, SizeIs(0)); } +TEST_F(FileStatusCache, UpdateFiles) +{ + EXPECT_CALL(fileSystem, lastModified(Eq(header))) + .Times(2) + .WillOnce(Return(headerLastModifiedTime)) + .WillOnce(Return(headerLastModifiedTime2)); + EXPECT_CALL(fileSystem, lastModified(Eq(header2))) + .Times(2) + .WillOnce(Return(header2LastModifiedTime)) + .WillOnce(Return(header2LastModifiedTime2)); + cache.lastModifiedTime(header); + cache.lastModifiedTime(header2); + + cache.update(entries); + + ASSERT_THAT(cache.lastModifiedTime(header), headerLastModifiedTime2); + ASSERT_THAT(cache.lastModifiedTime(header2), header2LastModifiedTime2); } + +TEST_F(FileStatusCache, UpdateFilesDoesNotChangeEntryCount) +{ + EXPECT_CALL(fileSystem, lastModified(Eq(header))) + .Times(2) + .WillOnce(Return(headerLastModifiedTime)) + .WillOnce(Return(headerLastModifiedTime2)); + EXPECT_CALL(fileSystem, lastModified(Eq(header2))) + .Times(2) + .WillOnce(Return(header2LastModifiedTime)) + .WillOnce(Return(header2LastModifiedTime2)); + cache.lastModifiedTime(header); + cache.lastModifiedTime(header2); + + cache.update(entries); + + ASSERT_THAT(cache, SizeIs(2)); +} + +TEST_F(FileStatusCache, UpdateFilesForNonExistingEntry) +{ + cache.update(entries); + + ASSERT_THAT(cache, SizeIs(0)); +} + +TEST_F(FileStatusCache, NewModifiedEntries) +{ + auto modifiedIds = cache.modified(entries); + + ASSERT_THAT(modifiedIds, entries); +} + +TEST_F(FileStatusCache, NoNewModifiedEntries) +{ + cache.modified(entries); + + auto modifiedIds = cache.modified(entries); + + ASSERT_THAT(modifiedIds, IsEmpty()); +} + +TEST_F(FileStatusCache, SomeNewModifiedEntries) +{ + cache.modified({source, header2}); + + auto modifiedIds = cache.modified(entries); + + ASSERT_THAT(modifiedIds, ElementsAre(header, source2)); +} + +TEST_F(FileStatusCache, SomeAlreadyExistingModifiedEntries) +{ + EXPECT_CALL(fileSystem, lastModified(Eq(header))) + .Times(2) + .WillOnce(Return(headerLastModifiedTime)) + .WillOnce(Return(headerLastModifiedTime2)); + EXPECT_CALL(fileSystem, lastModified(Eq(header2))) + .Times(2) + .WillOnce(Return(header2LastModifiedTime)) + .WillOnce(Return(header2LastModifiedTime2)); + EXPECT_CALL(fileSystem, lastModified(Eq(source))).Times(2).WillRepeatedly(Return(sourceLastModifiedTime)); + EXPECT_CALL(fileSystem, lastModified(Eq(source2))) + .Times(2) + .WillRepeatedly(Return(source2LastModifiedTime)); + cache.modified(entries); + + auto modifiedIds = cache.modified(entries); + + ASSERT_THAT(modifiedIds, ElementsAre(header, header2)); +} + +TEST_F(FileStatusCache, SomeAlreadyExistingAndSomeNewModifiedEntries) +{ + EXPECT_CALL(fileSystem, lastModified(Eq(header))).WillRepeatedly(Return(headerLastModifiedTime)); + EXPECT_CALL(fileSystem, lastModified(Eq(header2))) + .Times(2) + .WillOnce(Return(header2LastModifiedTime)) + .WillOnce(Return(header2LastModifiedTime2)); + EXPECT_CALL(fileSystem, lastModified(Eq(source))).Times(2).WillRepeatedly(Return(sourceLastModifiedTime)); + EXPECT_CALL(fileSystem, lastModified(Eq(source2))).WillRepeatedly(Return(source2LastModifiedTime)); + cache.modified({source, header2}); + + auto modifiedIds = cache.modified(entries); + + ASSERT_THAT(modifiedIds, ElementsAre(header, header2, source2)); +} + +TEST_F(FileStatusCache, TimeIsUpdatedForSomeAlreadyExistingModifiedEntries) +{ + EXPECT_CALL(fileSystem, lastModified(Eq(header))) + .Times(2) + .WillOnce(Return(headerLastModifiedTime)) + .WillOnce(Return(headerLastModifiedTime2)); + EXPECT_CALL(fileSystem, lastModified(Eq(header2))) + .Times(2) + .WillOnce(Return(header2LastModifiedTime)) + .WillOnce(Return(header2LastModifiedTime2)); + EXPECT_CALL(fileSystem, lastModified(Eq(source))).Times(2).WillRepeatedly(Return(sourceLastModifiedTime)); + EXPECT_CALL(fileSystem, lastModified(Eq(source2))) + .Times(2) + .WillRepeatedly(Return(source2LastModifiedTime)); + cache.modified(entries); + + cache.modified(entries); + + ASSERT_THAT(cache.lastModifiedTime(header), headerLastModifiedTime2); +} + +} // namespace diff --git a/tests/unit/unittest/gtest-creator-printing.cpp b/tests/unit/unittest/gtest-creator-printing.cpp index 7ac4a676e3..0ab962dfa5 100644 --- a/tests/unit/unittest/gtest-creator-printing.cpp +++ b/tests/unit/unittest/gtest-creator-printing.cpp @@ -361,7 +361,7 @@ std::ostream &operator<<(std::ostream &out, const WatcherEntry &entry) { out << "(" << entry.id << ", " - << entry.pathId + << entry.filePathId << ")"; return out; diff --git a/tests/unit/unittest/mockfilepathcaching.h b/tests/unit/unittest/mockfilepathcaching.h index 1b22e910ee..ca7960513e 100644 --- a/tests/unit/unittest/mockfilepathcaching.h +++ b/tests/unit/unittest/mockfilepathcaching.h @@ -36,5 +36,11 @@ public: ClangBackEnd::FilePathId (ClangBackEnd::FilePathView filePath)); MOCK_CONST_METHOD1(filePath, ClangBackEnd::FilePath (ClangBackEnd::FilePathId filePathId)); + MOCK_CONST_METHOD1(directoryPathId, + ClangBackEnd::DirectoryPathId(Utils::SmallStringView directoryPath)); + MOCK_CONST_METHOD1(directoryPath, + Utils::PathString(ClangBackEnd::DirectoryPathId directoryPathId)); + MOCK_CONST_METHOD1(directoryPathId, + ClangBackEnd::DirectoryPathId(ClangBackEnd::FilePathId filePathId)); }; diff --git a/tests/unit/unittest/mockfilesystem.h b/tests/unit/unittest/mockfilesystem.h new file mode 100644 index 0000000000..688edbcae5 --- /dev/null +++ b/tests/unit/unittest/mockfilesystem.h @@ -0,0 +1,37 @@ +/**************************************************************************** +** +** Copyright (C) 2019 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 "googletest.h" + +#include <filesysteminterface.h> + +class MockFileSystem : public ClangBackEnd::FileSystemInterface +{ +public: + MOCK_CONST_METHOD1(directoryEntries, ClangBackEnd::FilePathIds(const QString &directoryPath)); + MOCK_CONST_METHOD1(lastModified, long long(ClangBackEnd::FilePathId filePathId)); +}; diff --git a/tests/unit/unittest/mockqfilesystemwatcher.h b/tests/unit/unittest/mockqfilesystemwatcher.h index 1bb90b2c37..00f4c600b4 100644 --- a/tests/unit/unittest/mockqfilesystemwatcher.h +++ b/tests/unit/unittest/mockqfilesystemwatcher.h @@ -41,4 +41,5 @@ public: signals: void fileChanged(const QString &); + void directoryChanged(const QString &); }; diff --git a/tests/unit/unittest/mocksqlitereadstatement.cpp b/tests/unit/unittest/mocksqlitereadstatement.cpp index b0df1ab75d..1e800e2be1 100644 --- a/tests/unit/unittest/mocksqlitereadstatement.cpp +++ b/tests/unit/unittest/mocksqlitereadstatement.cpp @@ -137,6 +137,12 @@ MockSqliteReadStatement::value<int>(const int &directoryId, const Utils::SmallSt return valueReturnInt32(directoryId, text); } +template<> +Utils::optional<int> MockSqliteReadStatement::value<int>(const int &value) +{ + return valueReturnInt32(value); +} + template <> Utils::optional<long long> MockSqliteReadStatement::value<long long>(const int &sourceId) diff --git a/tests/unit/unittest/mocksqlitereadstatement.h b/tests/unit/unittest/mocksqlitereadstatement.h index d952c90dec..191c38accd 100644 --- a/tests/unit/unittest/mocksqlitereadstatement.h +++ b/tests/unit/unittest/mocksqlitereadstatement.h @@ -92,11 +92,11 @@ public: MOCK_METHOD1(valueReturnInt32, Utils::optional<int>(Utils::SmallStringView)); - MOCK_METHOD2(valueReturnInt32, - Utils::optional<int>(int, Utils::SmallStringView)); + MOCK_METHOD2(valueReturnInt32, Utils::optional<int>(int, Utils::SmallStringView)); - MOCK_METHOD1(valueReturnInt64, - Utils::optional<long long>(int)); + MOCK_METHOD1(valueReturnInt32, Utils::optional<int>(int)); + + MOCK_METHOD1(valueReturnInt64, Utils::optional<long long>(int)); MOCK_METHOD1(valueReturnPathString, Utils::optional<Utils::PathString>(int)); @@ -244,6 +244,9 @@ template <> Utils::optional<int> MockSqliteReadStatement::value<int>(const int&, const Utils::SmallStringView&); +template<> +Utils::optional<int> MockSqliteReadStatement::value<int>(const int &); + template <> Utils::optional<long long> MockSqliteReadStatement::value<long long>(const ClangBackEnd::FilePathId&); diff --git a/tests/unit/unittest/symbolindexer-test.cpp b/tests/unit/unittest/symbolindexer-test.cpp index 2db11dc5fb..0bfcf13188 100644 --- a/tests/unit/unittest/symbolindexer-test.cpp +++ b/tests/unit/unittest/symbolindexer-test.cpp @@ -28,6 +28,7 @@ #include "mockbuilddependenciesstorage.h" #include "mockclangpathwatcher.h" #include "mockfilepathcaching.h" +#include "mockfilesystem.h" #include "mockmodifiedtimechecker.h" #include "mockprecompiledheaderstorage.h" #include "mockprojectpartsstorage.h" @@ -149,13 +150,6 @@ protected: 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)); @@ -249,7 +243,8 @@ protected: NiceMock<MockPrecompiledHeaderStorage> mockPrecompiledHeaderStorage; NiceMock<MockProjectPartsStorage> mockProjectPartsStorage; NiceMock<MockClangPathWatcher> mockPathWatcher; - ClangBackEnd::FileStatusCache fileStatusCache{filePathCache}; + NiceMock<MockFileSystem> mockFileSystem; + ClangBackEnd::FileStatusCache fileStatusCache{mockFileSystem}; ClangBackEnd::GeneratedFiles generatedFiles; Manager collectorManger{generatedFiles}; NiceMock<MockFunction<void(int, int)>> mockSetProgressCallback; @@ -1664,13 +1659,12 @@ TEST_F(SymbolIndexer, DISABLED_DontReparseInUpdateProjectPartsIfDefinesAreTheSam TEST_F(SymbolIndexer, PathsChangedUpdatesFileStatusCache) { auto sourceId = filePathId(TESTDATA_DIR "/symbolindexer_pathChanged.cpp"); - auto oldLastModified = fileStatusCache.lastModifiedTime(sourceId); - touchFile(sourceId); + ON_CALL(mockFileSystem, lastModified(Eq(sourceId))).WillByDefault(Return(65)); ON_CALL(mockSymbolStorage, fetchDependentSourceIds(_)).WillByDefault(Return(FilePathIds{sourceId})); indexer.pathsChanged({sourceId}); - ASSERT_THAT(fileStatusCache.lastModifiedTime(sourceId), Gt(oldLastModified)); + ASSERT_THAT(fileStatusCache.lastModifiedTime(sourceId), 65); } TEST_F(SymbolIndexer, GetUpdatableFilePathIdsIfCompilerMacrosAreDifferent) @@ -1706,6 +1700,7 @@ TEST_F(SymbolIndexer, GetNoUpdatableFilePathIdsIfArtefactsAreTheSame) TEST_F(SymbolIndexer, OutdatedFilesPassUpdatableFilePathIds) { + ON_CALL(mockFileSystem, lastModified(Eq(main1PathId))).WillByDefault(Return(65)); indexer.pathsChanged({main1PathId}); ON_CALL(mockProjectPartsStorage, fetchProjectPartArtefact(A<ProjectPartId>())) .WillByDefault(Return(artefact)); diff --git a/tests/unit/unittest/unittest.pro b/tests/unit/unittest/unittest.pro index 63980ab055..d9cef683e7 100644 --- a/tests/unit/unittest/unittest.pro +++ b/tests/unit/unittest/unittest.pro @@ -46,7 +46,7 @@ QMAKE_SUBSTITUTES += cpptoolsjson DEFINES += CPPTOOLS_JSON=\"R\\\"xxx($${cpptoolsjson.output})xxx\\\"\" SOURCES += \ - changedfilepathcompressor-test.cpp \ + directorypathcompressor-test.cpp \ clangpathwatcher-test.cpp \ clangqueryexamplehighlightmarker-test.cpp \ clangqueryhighlightmarker-test.cpp \ @@ -175,7 +175,7 @@ SOURCES += \ translationunitupdater-test.cpp \ unsavedfiles-test.cpp \ unsavedfile-test.cpp \ - utf8positionfromlinecolumn-test.cpp \ + utf8positionfromlinecolumn-test.cpp } !isEmpty(LIBTOOLING_LIBS) { @@ -226,6 +226,7 @@ HEADERS += \ mockclangcodemodelserver.h \ mockclangpathwatcher.h \ mockclangpathwatchernotifier.h \ + mockfilesystem.h \ mockpchcreator.h \ mockpchmanagerclient.h \ mockpchmanagernotifier.h \ |