Clang: Add reset to ModifiedTimeChecker

We can reset some file once to flag a file dirty if the included file has
changed.

Change-Id: I8763bb80f65882fba4e70057f569234e77097927
Reviewed-by: Tim Jenssen <tim.jenssen@qt.io>
This commit is contained in:
Marco Bubke
2019-06-19 15:34:24 +02:00
parent e031ada154
commit e777ad57c5
8 changed files with 279 additions and 207 deletions

View File

@@ -157,6 +157,7 @@ HEADERS += \
$$PWD/refactoringserverinterface.h \
$$PWD/refactoringserverproxy.h \
$$PWD/referencesmessage.h \
$$PWD/set_algorithm.h \
$$PWD/unsavedfilesupdatedmessage.h \
$$PWD/removeprojectpartsmessage.h \
$$PWD/requestannotationsmessage.h \

View File

@@ -26,6 +26,8 @@
#include "filestatuscache.h"
#include "filesystem.h"
#include <set_algorithm.h>
#include <utils/algorithm.h>
#include <QDateTime>
@@ -51,49 +53,15 @@ void FileStatusCache::update(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(),
std::set_intersection(m_cacheEntries.begin(),
m_cacheEntries.end(),
filePathIds.begin(),
filePathIds.end(),
[&](auto &entry) {
make_iterator([&](auto &entry) {
entry.lastModified = m_fileSystem.lastModified(entry.filePathId);
});
}));
}
FilePathIds FileStatusCache::modified(FilePathIds filePathIds) const
@@ -101,30 +69,30 @@ FilePathIds FileStatusCache::modified(FilePathIds filePathIds) const
FilePathIds modifiedFilePathIds;
modifiedFilePathIds.reserve(filePathIds.size());
set_intersection_call(m_cacheEntries.begin(),
std::set_intersection(m_cacheEntries.begin(),
m_cacheEntries.end(),
filePathIds.begin(),
filePathIds.end(),
[&](auto &entry) {
make_iterator([&](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(),
std::set_difference(filePathIds.begin(),
filePathIds.end(),
m_cacheEntries.begin(),
m_cacheEntries.end(),
[&](FilePathId newFilePathId) {
make_iterator([&](FilePathId newFilePathId) {
newEntries.emplace_back(newFilePathId,
m_fileSystem.lastModified(newFilePathId));
modifiedFilePathIds.push_back(newFilePathId);
});
}));
if (newEntries.size()) {
Internal::FileStatusCacheEntries mergedEntries;

View File

@@ -25,24 +25,23 @@
#pragma once
#include "clangpathwatcher.h"
#include "filepathcachinginterface.h"
#include "filesysteminterface.h"
#include "modifiedtimecheckerinterface.h"
#include "set_algorithm.h"
#include <algorithm>
#include <iterator>
namespace ClangBackEnd {
template<typename SourceEntries = ::ClangBackEnd::SourceEntries>
class ModifiedTimeChecker final : public ModifiedTimeCheckerInterface<SourceEntries>
{
using SourceEntry = typename SourceEntries::value_type;
public:
using GetModifiedTime = std::function<ClangBackEnd::TimeStamp(ClangBackEnd::FilePathView filePath)>;
ModifiedTimeChecker(GetModifiedTime &getModifiedTime, FilePathCachingInterface &filePathCache)
: m_getModifiedTime(getModifiedTime)
, m_filePathCache(filePathCache)
ModifiedTimeChecker(FileSystemInterface &fileSystem)
: m_fileSystem(fileSystem)
{}
bool isUpToDate(const SourceEntries &sourceEntries) const
@@ -52,165 +51,101 @@ public:
updateCurrentSourceTimeStamps(sourceEntries);
return compareEntries(sourceEntries);
return compareEntries(sourceEntries) && notReseted(sourceEntries);
}
void pathsChanged(const FilePathIds &filePathIds) override
{
using SourceTimeStampReferences = std::vector<std::reference_wrapper<SourceTimeStamp>>;
SourceTimeStampReferences timeStampsToUpdate;
timeStampsToUpdate.reserve(filePathIds.size());
std::set_intersection(m_currentSourceTimeStamps.begin(),
m_currentSourceTimeStamps.end(),
filePathIds.begin(),
filePathIds.end(),
std::back_inserter(timeStampsToUpdate));
make_iterator([&](SourceTimeStamp &sourceTimeStamp) {
sourceTimeStamp.timeStamp = m_fileSystem.lastModified(
sourceTimeStamp.sourceId);
}));
}
for (SourceTimeStamp &sourceTimeStamp : timeStampsToUpdate) {
sourceTimeStamp.timeStamp = m_getModifiedTime(
m_filePathCache.filePath(sourceTimeStamp.sourceId));
}
void reset(const FilePathIds &filePathIds)
{
FilePathIds newResetFilePathIds;
newResetFilePathIds.reserve(newResetFilePathIds.size() + m_resetFilePathIds.size());
std::set_union(m_resetFilePathIds.begin(),
m_resetFilePathIds.end(),
filePathIds.begin(),
filePathIds.end(),
std::back_inserter(newResetFilePathIds));
m_resetFilePathIds = std::move(newResetFilePathIds);
}
private:
bool compareEntries(const SourceEntries &sourceEntries) const
{
class CompareSourceId
{
public:
bool operator()(SourceTimeStamp first, SourceTimeStamp second)
{
return first.sourceId < second.sourceId;
}
bool operator()(::ClangBackEnd::SourceEntry first, ::ClangBackEnd::SourceEntry second)
{
return first.sourceId < second.sourceId;
}
bool operator()(SourceTimeStamp first, ::ClangBackEnd::SourceEntry second)
{
return first.sourceId < second.sourceId;
}
bool operator()(::ClangBackEnd::SourceEntry first, SourceTimeStamp second)
{
return first.sourceId < second.sourceId;
}
};
SourceTimeStamps currentSourceTimeStamp;
currentSourceTimeStamp.reserve(sourceEntries.size());
std::set_intersection(m_currentSourceTimeStamps.begin(),
m_currentSourceTimeStamps.end(),
sourceEntries.begin(),
sourceEntries.end(),
std::back_inserter(currentSourceTimeStamp),
CompareSourceId{});
class CompareTime
{
public:
bool operator()(SourceTimeStamp first, SourceTimeStamp second)
{
return first.timeStamp <= second.timeStamp;
}
bool operator()(::ClangBackEnd::SourceEntry first, ::ClangBackEnd::SourceEntry second)
{
return first.timeStamp <= second.timeStamp;
}
bool operator()(SourceTimeStamp first, ::ClangBackEnd::SourceEntry second)
{
return first.timeStamp <= second.timeStamp;
}
bool operator()(::ClangBackEnd::SourceEntry first, SourceTimeStamp second)
{
return first.timeStamp <= second.timeStamp;
}
};
return std::lexicographical_compare(currentSourceTimeStamp.begin(),
currentSourceTimeStamp.end(),
sourceEntries.begin(),
sourceEntries.end(),
CompareTime{});
return set_intersection_compare(
m_currentSourceTimeStamps.begin(),
m_currentSourceTimeStamps.end(),
sourceEntries.begin(),
sourceEntries.end(),
[](auto first, auto second) { return second.timeStamp > first.timeStamp; },
[](auto first, auto second) { return first.sourceId < second.sourceId; });
}
void updateCurrentSourceTimeStamps(const SourceEntries &sourceEntries) const
{
SourceTimeStamps sourceTimeStamps = newSourceTimeStamps(sourceEntries);
for (SourceTimeStamp &newSourceTimeStamp : sourceTimeStamps) {
newSourceTimeStamp.timeStamp = m_getModifiedTime(
m_filePathCache.filePath(newSourceTimeStamp.sourceId));
}
auto split = sourceTimeStamps.insert(sourceTimeStamps.end(),
m_currentSourceTimeStamps.begin(),
m_currentSourceTimeStamps.end());
std::inplace_merge(sourceTimeStamps.begin(), split, sourceTimeStamps.end());
m_currentSourceTimeStamps = sourceTimeStamps;
m_currentSourceTimeStamps = std::move(sourceTimeStamps);
}
SourceTimeStamps newSourceTimeStamps(const SourceEntries &sourceEntries) const
{
SourceEntries newSourceEntries;
newSourceEntries.reserve(sourceEntries.size());
class CompareSourceId
{
public:
bool operator()(SourceTimeStamp first, SourceTimeStamp second)
{
return first.sourceId < second.sourceId;
}
bool operator()(::ClangBackEnd::SourceEntry first, ::ClangBackEnd::SourceEntry second)
{
return first.sourceId < second.sourceId;
}
bool operator()(SourceTimeStamp first, ::ClangBackEnd::SourceEntry second)
{
return first.sourceId < second.sourceId;
}
bool operator()(::ClangBackEnd::SourceEntry first, SourceTimeStamp second)
{
return first.sourceId < second.sourceId;
}
};
SourceTimeStamps newTimeStamps;
newTimeStamps.reserve(sourceEntries.size());
std::set_difference(sourceEntries.begin(),
sourceEntries.end(),
m_currentSourceTimeStamps.begin(),
m_currentSourceTimeStamps.end(),
std::back_inserter(newSourceEntries),
CompareSourceId{});
SourceTimeStamps newTimeStamps;
newTimeStamps.reserve(newSourceEntries.size());
std::transform(newSourceEntries.begin(),
newSourceEntries.end(),
std::back_inserter(newTimeStamps),
[](SourceEntry entry) {
return SourceTimeStamp{entry.sourceId, {}};
});
make_iterator([&](const SourceEntry &sourceEntry) {
newTimeStamps.emplace_back(sourceEntry.sourceId,
m_fileSystem.lastModified(
sourceEntry.sourceId));
}),
[](auto first, auto second) {
return first.sourceId < second.sourceId && first.timeStamp > 0;
});
return newTimeStamps;
}
bool notReseted(const SourceEntries &sourceEntries) const
{
auto oldSize = m_resetFilePathIds.size();
FilePathIds newResetFilePathIds;
newResetFilePathIds.reserve(newResetFilePathIds.size());
std::set_difference(m_resetFilePathIds.begin(),
m_resetFilePathIds.end(),
sourceEntries.begin(),
sourceEntries.end(),
std::back_inserter(newResetFilePathIds));
m_resetFilePathIds = std::move(newResetFilePathIds);
return oldSize == m_resetFilePathIds.size();
}
private:
mutable SourceTimeStamps m_currentSourceTimeStamps;
GetModifiedTime &m_getModifiedTime;
FilePathCachingInterface &m_filePathCache;
mutable FilePathIds m_resetFilePathIds;
FileSystemInterface &m_fileSystem;
};
} // namespace ClangBackEnd

View File

@@ -0,0 +1,101 @@
/****************************************************************************
**
** 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 <algorithm>
namespace ClangBackEnd {
template<class Callable>
class function_output_iterator
{
public:
typedef std::output_iterator_tag iterator_category;
typedef void value_type;
typedef void difference_type;
typedef void pointer;
typedef void reference;
explicit function_output_iterator() {}
explicit function_output_iterator(const Callable &callable)
: m_callable(&callable)
{}
function_output_iterator &operator=(const function_output_iterator &iterator)
{
m_callable = iterator.m_callable;
return *this;
}
struct helper
{
helper(const Callable *callable)
: m_callable(callable)
{}
template<class T>
helper &operator=(T &&value)
{
(*m_callable)(std::forward<T>(value));
return *this;
}
const Callable *m_callable;
};
helper operator*() { return helper(m_callable); }
function_output_iterator &operator++() { return *this; }
function_output_iterator &operator++(int) { return *this; }
private:
const Callable *m_callable;
};
template<typename Callable>
function_output_iterator<Callable> make_iterator(const Callable &callable)
{
return function_output_iterator<Callable>(callable);
}
template<class InputIt1, class InputIt2, class Callable, class Compare>
bool set_intersection_compare(
InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, Callable call, Compare comp)
{
while (first1 != last1 && first2 != last2) {
if (comp(*first1, *first2)) {
++first1;
} else {
if (!comp(*first2, *first1)) {
if (call(*first2, *first1++))
return false;
}
++first2;
}
}
return true;
}
} // namespace ClangBackEnd

View File

@@ -131,6 +131,10 @@ public:
return first.sourceId < second.sourceId;
}
friend bool operator<(SourceEntry first, FilePathId second) { return first.sourceId < second; }
friend bool operator<(FilePathId first, SourceEntry second) { return first < second.sourceId; }
friend bool operator==(SourceEntry first, SourceEntry second)
{
return first.sourceId == second.sourceId && first.sourceType == second.sourceType

View File

@@ -214,12 +214,7 @@ struct Data // because we have a cycle dependency
ClangBackEnd::BuildDependencyCollector buildDependencyCollector{filePathCache,
generatedFiles,
environment};
std::function<TimeStamp(FilePathView filePath)> getModifiedTime{
[&](ClangBackEnd::FilePathView path) -> TimeStamp {
return QFileInfo(QString(path)).lastModified().toSecsSinceEpoch();
}};
ClangBackEnd::ModifiedTimeChecker<ClangBackEnd::SourceEntries> modifiedTimeChecker{getModifiedTime,
filePathCache};
ClangBackEnd::ModifiedTimeChecker<ClangBackEnd::SourceEntries> modifiedTimeChecker{fileSystem};
ClangBackEnd::BuildDependenciesProvider buildDependencyProvider{buildDependencyStorage,
modifiedTimeChecker,
buildDependencyCollector,

View File

@@ -148,12 +148,7 @@ private:
FileStatusCache m_fileStatusCache{m_fileSytem};
SymbolsCollectorManager m_collectorManger;
ProgressCounter m_progressCounter;
std::function<TimeStamp(FilePathView filePath)> getModifiedTime{
[&](ClangBackEnd::FilePathView path) -> TimeStamp {
return QFileInfo(QString(path)).lastModified().toSecsSinceEpoch();
}};
ModifiedTimeChecker<ClangBackEnd::SourceTimeStamps> m_modifiedTimeChecker{getModifiedTime,
m_filePathCache};
ModifiedTimeChecker<ClangBackEnd::SourceTimeStamps> m_modifiedTimeChecker{m_fileSytem};
SymbolIndexer m_indexer;
SymbolIndexerTaskQueue m_indexerQueue;
SymbolIndexerTaskScheduler m_indexerScheduler;

View File

@@ -24,6 +24,7 @@
****************************************************************************/
#include "googletest.h"
#include "mockfilesystem.h"
#include <filepathcaching.h>
#include <modifiedtimechecker.h>
@@ -39,29 +40,24 @@ using ClangBackEnd::FilePathView;
class ModifiedTimeChecker : public testing::Test
{
protected:
ClangBackEnd::FilePathId id(const Utils::SmallStringView &path) const
{
return filePathCache.filePathId(ClangBackEnd::FilePathView{path});
}
ModifiedTimeChecker()
{
ON_CALL(getModifiedTimeCallback, Call(Eq(FilePathView("/path1")))).WillByDefault(Return(50));
ON_CALL(getModifiedTimeCallback, Call(Eq(FilePathView("/path2")))).WillByDefault(Return(30));
ON_CALL(mockFileSystem, lastModified(Eq(1))).WillByDefault(Return(50));
ON_CALL(mockFileSystem, lastModified(Eq(2))).WillByDefault(Return(30));
ON_CALL(mockFileSystem, lastModified(Eq(3))).WillByDefault(Return(50));
ON_CALL(mockFileSystem, lastModified(Eq(4))).WillByDefault(Return(30));
}
NiceMock<MockFunction<ClangBackEnd::TimeStamp(ClangBackEnd::FilePathView filePath)>> getModifiedTimeCallback;
Sqlite::Database database{":memory:", Sqlite::JournalMode::Memory};
ClangBackEnd::RefactoringDatabaseInitializer<Sqlite::Database> databaseInitializer{database};
ClangBackEnd::FilePathCaching filePathCache{database};
decltype(getModifiedTimeCallback.AsStdFunction()) callback = getModifiedTimeCallback
.AsStdFunction();
ClangBackEnd::ModifiedTimeChecker<> checker{callback, filePathCache};
SourceEntries upToDateEntries = {{id("/path1"), SourceType::UserInclude, 100},
{id("/path2"), SourceType::SystemInclude, 30}};
SourceEntries notUpToDateEntries = {{id("/path1"), SourceType::UserInclude, 50},
{id("/path2"), SourceType::SystemInclude, 20}};
NiceMock<MockFileSystem> mockFileSystem;
ClangBackEnd::ModifiedTimeChecker<> checker{mockFileSystem};
SourceEntries upToDateEntries = {{1, SourceType::UserInclude, 100},
{2, SourceType::SystemInclude, 30},
{3, SourceType::UserInclude, 100},
{4, SourceType::SystemInclude, 30}};
SourceEntries notUpToDateEntries = {{1, SourceType::UserInclude, 50},
{2, SourceType::SystemInclude, 20},
{3, SourceType::UserInclude, 100},
{4, SourceType::SystemInclude, 30}};
};
TEST_F(ModifiedTimeChecker, IsUpToDate)
@@ -71,6 +67,15 @@ TEST_F(ModifiedTimeChecker, IsUpToDate)
ASSERT_TRUE(isUpToDate);
}
TEST_F(ModifiedTimeChecker, IsUpToDateSecondRun)
{
checker.isUpToDate(upToDateEntries);
auto isUpToDate = checker.isUpToDate(upToDateEntries);
ASSERT_TRUE(isUpToDate);
}
TEST_F(ModifiedTimeChecker, IsNotUpToDateIfSourceEntriesAreEmpty)
{
auto isUpToDate = checker.isUpToDate({});
@@ -80,7 +85,16 @@ TEST_F(ModifiedTimeChecker, IsNotUpToDateIfSourceEntriesAreEmpty)
TEST_F(ModifiedTimeChecker, IsNotUpToDate)
{
auto isUpToDate = checker.isUpToDate({});
auto isUpToDate = checker.isUpToDate(notUpToDateEntries);
ASSERT_FALSE(isUpToDate);
}
TEST_F(ModifiedTimeChecker, IsNotUpToDateSecondRun)
{
checker.isUpToDate(notUpToDateEntries);
auto isUpToDate = checker.isUpToDate(notUpToDateEntries);
ASSERT_FALSE(isUpToDate);
}
@@ -89,21 +103,80 @@ TEST_F(ModifiedTimeChecker, PathChangesUpdatesTimeStamps)
{
checker.isUpToDate(upToDateEntries);
EXPECT_CALL(getModifiedTimeCallback, Call(Eq(FilePathView("/path1"))));
EXPECT_CALL(getModifiedTimeCallback, Call(Eq(FilePathView("/path2"))));
EXPECT_CALL(mockFileSystem, lastModified(Eq(2)));
EXPECT_CALL(mockFileSystem, lastModified(Eq(3)));
checker.pathsChanged({id(FilePathView("/path1")), id(FilePathView("/path2")), id(FilePathView("/path3"))});
checker.pathsChanged({2, 3});
}
TEST_F(ModifiedTimeChecker, IsNotUpToDateAnyMoreAfterUpdating)
{
checker.isUpToDate(upToDateEntries);
ON_CALL(getModifiedTimeCallback, Call(Eq(FilePathView("/path1")))).WillByDefault(Return(120));
ON_CALL(getModifiedTimeCallback, Call(Eq(FilePathView("/path2")))).WillByDefault(Return(30));
ON_CALL(mockFileSystem, lastModified(Eq(1))).WillByDefault(Return(120));
ON_CALL(mockFileSystem, lastModified(Eq(2))).WillByDefault(Return(30));
checker.pathsChanged({id(FilePathView("/path1")), id(FilePathView("/path2")), id(FilePathView("/path3"))});
checker.pathsChanged({1, 2, 3});
ASSERT_FALSE(checker.isUpToDate(upToDateEntries));
}
TEST_F(ModifiedTimeChecker, Reset)
{
checker.isUpToDate(upToDateEntries);
checker.reset({2, 3});
ASSERT_FALSE(checker.isUpToDate(upToDateEntries));
}
TEST_F(ModifiedTimeChecker, UpdateNonResetedId)
{
checker.isUpToDate(upToDateEntries);
checker.reset({2, 3});
ASSERT_TRUE(checker.isUpToDate({upToDateEntries[0]}));
}
TEST_F(ModifiedTimeChecker, ResetTwoTimes)
{
checker.isUpToDate(upToDateEntries);
checker.reset({2, 3});
checker.reset({2, 3});
ASSERT_FALSE(checker.isUpToDate(upToDateEntries));
ASSERT_TRUE(checker.isUpToDate(upToDateEntries));
}
TEST_F(ModifiedTimeChecker, ResetSecondUpdate)
{
checker.isUpToDate(upToDateEntries);
checker.reset({2, 3});
checker.isUpToDate(upToDateEntries);
auto isUpToDate = checker.isUpToDate(upToDateEntries);
ASSERT_TRUE(isUpToDate);
}
TEST_F(ModifiedTimeChecker, ResetPartialUpdate)
{
checker.isUpToDate(upToDateEntries);
checker.reset({2, 3});
checker.isUpToDate({upToDateEntries[1]});
ASSERT_FALSE(checker.isUpToDate({upToDateEntries[2]}));
}
TEST_F(ModifiedTimeChecker, ResetMoreIds)
{
checker.isUpToDate(upToDateEntries);
checker.reset({2, 3});
checker.reset({1, 5});
ASSERT_FALSE(checker.isUpToDate({upToDateEntries[2]}));
}
} // namespace