Commit b412eb81 authored by Marco Bubke's avatar Marco Bubke

Clang: Add revision and completion management

Reparsing a document is expensive so we should avoid it by all means. In
this patch we prevent that the same document is send again. It isn't send
too in advance of a code completion if there was no changes before the
the completion position.

Change-Id: I0bb786ba1d4e7ce08611a518cb32f8cf8f4d0037
Reviewed-by: default avatarNikolai Kosjar <nikolai.kosjar@theqtcompany.com>
parent 803cca40
......@@ -382,19 +382,50 @@ void IpcCommunicator::updateUnsavedFileFromCppEditorDocument(const QString &file
updateUnsavedFile(filePath, document->contents(), document->revision());
}
namespace {
CppTools::CppEditorDocumentHandle *cppDocument(const QString &filePath)
{
return CppTools::CppModelManager::instance()->cppEditorDocument(filePath);
}
bool documentHasChanged(const QString &filePath, const QString &projectPartId)
{
auto *document = cppDocument(filePath);
if (document)
return document->sendTracker(projectPartId).shouldSendRevision(document->revision());
return true;
}
void setLastSentDocumentRevision(const QString &filePath,
const QString &projectPartId,
uint revision)
{
auto *document = cppDocument(filePath);
if (document)
document->sendTracker(projectPartId).setLastSentRevision(int(revision));
}
}
void IpcCommunicator::updateTranslationUnit(const QString &filePath,
const QByteArray &contents,
uint documentRevision)
{
const QString projectPartId = Utils::projectPartIdForFile(filePath);
const bool hasUnsavedContent = true;
// TODO: Send new only if changed
registerTranslationUnitsForEditor({{filePath,
projectPartId,
Utf8String::fromByteArray(contents),
hasUnsavedContent,
documentRevision}});
if (documentHasChanged(filePath, projectPartId)) {
const bool hasUnsavedContent = true;
registerTranslationUnitsForEditor({{filePath,
projectPartId,
Utf8String::fromByteArray(contents),
hasUnsavedContent,
documentRevision}});
setLastSentDocumentRevision(filePath, projectPartId, documentRevision);
}
}
void IpcCommunicator::updateUnsavedFile(const QString &filePath, const QByteArray &contents, uint documentRevision)
......@@ -412,8 +443,23 @@ void IpcCommunicator::updateUnsavedFile(const QString &filePath, const QByteArra
void IpcCommunicator::requestDiagnostics(const FileContainer &fileContainer)
{
registerTranslationUnitsForEditor({fileContainer});
m_ipcSender->requestDiagnostics({fileContainer});
if (documentHasChanged(fileContainer.filePath(), fileContainer.projectPartId())) {
registerTranslationUnitsForEditor({fileContainer});
m_ipcSender->requestDiagnostics({fileContainer});
setLastSentDocumentRevision(fileContainer.filePath(),
fileContainer.projectPartId(),
fileContainer.documentRevision());
}
}
void IpcCommunicator::updateChangeContentStartPosition(const QString &filePath, int position)
{
auto *document = cppDocument(filePath);
if (document) {
const QString projectPartId = Utils::projectPartIdForFile(filePath);
document->sendTracker(projectPartId).applyContentChange(position);
}
}
void IpcCommunicator::updateTranslationUnitIfNotCurrentDocument(Core::IDocument *document)
......
......@@ -137,6 +137,7 @@ public:
void updateTranslationUnit(const QString &filePath, const QByteArray &contents, uint documentRevision);
void updateUnsavedFile(const QString &filePath, const QByteArray &contents, uint documentRevision);
void requestDiagnostics(const ClangBackEnd::FileContainer &fileContainer);
void updateChangeContentStartPosition(const QString &filePath, int position);
public: // for tests
IpcSenderInterface *setIpcSender(IpcSenderInterface *ipcSender);
......@@ -159,6 +160,7 @@ private:
void onEditorAboutToClose(Core::IEditor *editor);
void onCoreAboutToClose();
private:
IpcReceiver m_ipcReceiver;
ClangBackEnd::ConnectionClient m_connection;
QScopedPointer<IpcSenderInterface> m_ipcSender;
......
......@@ -35,12 +35,15 @@
#include "clangassistproposalmodel.h"
#include "clangcompletionassistprocessor.h"
#include "clangcompletioncontextanalyzer.h"
#include "clangeditordocumentprocessor.h"
#include "clangfunctionhintmodel.h"
#include "clangutils.h"
#include "completionchunkstotextconverter.h"
#include <utils/qtcassert.h>
#include <cpptools/editordocumenthandle.h>
#include <texteditor/codeassist/assistproposalitem.h>
#include <texteditor/codeassist/functionhintproposal.h>
#include <texteditor/codeassist/ifunctionhintproposalmodel.h>
......@@ -680,6 +683,51 @@ void ClangCompletionAssistProcessor::sendFileContent(const QString &projectPartI
info.isDocumentModified,
uint(m_interface->textDocument()->revision())}});
}
namespace {
CppTools::CppEditorDocumentHandle *cppDocument(const QString &filePath)
{
return CppTools::CppModelManager::instance()->cppEditorDocument(filePath);
}
bool shouldSendDocumentForCompletion(const QString &filePath,
const QString &projectPartId,
int completionPosition)
{
auto *document = cppDocument(filePath);
if (document) {
auto &sendTracker = document->sendTracker(projectPartId);
return sendTracker.shouldSendRevisionWithCompletionPosition(document->revision(),
completionPosition);
}
return true;
}
void setLastCompletionPositionAndDocumentRevision(const QString &filePath,
const QString &projectPartId,
int completionPosition)
{
auto *document = cppDocument(filePath);
if (document) {
document->sendTracker(projectPartId).setLastCompletionPosition(completionPosition);
document->sendTracker(projectPartId).setLastSentRevision(document->revision());
}
}
QString projectPartIdForEditorDocument(const QString &filePath)
{
auto projectPart = ClangEditorDocumentProcessor::get(filePath)->projectPart();
QString projectPartId;
if (projectPart)
projectPartId = projectPart->id();
return projectPartId;
}
}
void ClangCompletionAssistProcessor::sendCompletionRequest(int position,
const QByteArray &customFileContent)
......@@ -689,8 +737,13 @@ void ClangCompletionAssistProcessor::sendCompletionRequest(int position,
++column;
const QString filePath = m_interface->fileName();
const QString projectPartId = Utils::projectPartIdForFile(filePath);
sendFileContent(projectPartId, customFileContent);
const QString projectPartId = projectPartIdForEditorDocument(filePath);
if (shouldSendDocumentForCompletion(filePath, projectPartId, position)) {
sendFileContent(projectPartId, customFileContent);
setLastCompletionPositionAndDocumentRevision(filePath, projectPartId, position);
}
m_interface->ipcCommunicator().completeCode(this,
filePath,
uint(line),
......
......@@ -119,7 +119,7 @@ void ModelManagerSupportClang::connectTextDocumentToTranslationUnit(TextEditor::
Qt::UniqueConnection);
// Handle changes from e.g. refactoring actions
connect(textDocument, &TextEditor::TextDocument::contentsChanged,
connect(textDocument, &TextEditor::TextDocument::contentsChangedWithPosition,
this, &ModelManagerSupportClang::onCppDocumentContentsChangedOnTranslationUnit,
Qt::UniqueConnection);
}
......@@ -162,9 +162,14 @@ void ModelManagerSupportClang::onCppDocumentReloadFinishedOnTranslationUnit(bool
}
}
void ModelManagerSupportClang::onCppDocumentContentsChangedOnTranslationUnit()
void ModelManagerSupportClang::onCppDocumentContentsChangedOnTranslationUnit(int position,
int /*charsRemoved*/,
int /*charsAdded*/)
{
Core::IDocument *document = qobject_cast<Core::IDocument *>(sender());
m_ipcCommunicator.updateChangeContentStartPosition(document->filePath().toString(),
position);
m_ipcCommunicator.updateTranslationUnitIfNotCurrentDocument(document);
}
......
......@@ -67,7 +67,9 @@ private:
void onEditorOpened(Core::IEditor *editor);
void onCurrentEditorChanged(Core::IEditor *newCurrent);
void onCppDocumentReloadFinishedOnTranslationUnit(bool success);
void onCppDocumentContentsChangedOnTranslationUnit();
void onCppDocumentContentsChangedOnTranslationUnit(int position,
int charsRemoved,
int charsAdded);
void onCppDocumentReloadFinishedOnUnsavedFile(bool success);
void onCppDocumentContentsChangedOnUnsavedFile();
......
......@@ -512,6 +512,8 @@ public:
bool succeeded() const { return m_editor && m_backendIsNotified; }
bool waitUntilBackendIsNotified(int timeout = 10000);
bool waitUntilProjectPartChanged(const QString &newProjectPartId, int timeout = 10000);
bool waitUntil(const std::function<bool()> &condition, int timeout);
TextEditor::BaseTextEditor *editor() const { return m_editor; }
private:
......@@ -536,12 +538,37 @@ OpenEditorAtCursorPosition::~OpenEditorAtCursorPosition()
Core::EditorManager::closeEditor(m_editor, /* askAboutModifiedEditors= */ false);
}
bool OpenEditorAtCursorPosition::waitUntilBackendIsNotified(int timeout)
{
QTC_ASSERT(m_editor, return false);
const QString filePath = m_editor->document()->filePath().toString();
auto condition = [filePath] () {
const auto *processor = ClangEditorDocumentProcessor::get(filePath);
return processor && processor->projectPart();
};
return waitUntil(condition, timeout);
}
bool OpenEditorAtCursorPosition::waitUntilProjectPartChanged(const QString &newProjectPartId,
int timeout)
{
const QString filePath = m_editor->document()->filePath().toString();
auto condition = [newProjectPartId, filePath] () {
const auto *processor = ClangEditorDocumentProcessor::get(filePath);
return processor
&& processor->projectPart()
&& processor->projectPart()->id() == newProjectPartId;
};
return waitUntil(condition, timeout);
}
bool OpenEditorAtCursorPosition::waitUntil(const std::function<bool ()> &condition, int timeout)
{
QTime time;
time.start();
......@@ -549,8 +576,7 @@ bool OpenEditorAtCursorPosition::waitUntilBackendIsNotified(int timeout)
if (time.elapsed() > timeout)
return false;
const auto *processor = ClangEditorDocumentProcessor::get(filePath);
if (processor && processor->projectPart())
if (condition())
return true;
QCoreApplication::processEvents();
......@@ -968,6 +994,8 @@ void ClangCodeCompletionTest::testChangingProjectDependentCompletion()
_("#define PROJECT_CONFIGURATION_1\n"),
/* testOnlyForCleanedProjects= */ true);
QVERIFY(projectLoader.load());
openEditor.waitUntilProjectPartChanged(QLatin1String("myproject.project"));
proposal = completionResults(openEditor.editor());
QVERIFY(hasItem(proposal, "projectConfiguration1"));
......@@ -1045,6 +1073,8 @@ void ClangCodeCompletionTest::testUnsavedFilesTrackingByModifyingIncludedFileInN
void ClangCodeCompletionTest::testUnsavedFilesTrackingByModifyingIncludedFileExternally()
{
QSKIP("The file system watcher is doing it in backend process but we wait not long enough");
ChangeDocumentReloadSetting reloadSettingsChanger(Core::IDocument::ReloadUnmodified);
CppTools::Tests::TemporaryDir temporaryDir;
......
......@@ -64,7 +64,8 @@ HEADERS += \
stringtable.h \
symbolfinder.h \
symbolsfindfilter.h \
typehierarchybuilder.h
typehierarchybuilder.h \
senddocumenttracker.h
SOURCES += \
abstracteditorsupport.cpp \
......@@ -126,7 +127,8 @@ SOURCES += \
stringtable.cpp \
symbolfinder.cpp \
symbolsfindfilter.cpp \
typehierarchybuilder.cpp
typehierarchybuilder.cpp \
senddocumenttracker.cpp
FORMS += \
completionsettingspage.ui \
......
......@@ -84,6 +84,7 @@ QtcPlugin {
"insertionpointlocator.cpp", "insertionpointlocator.h",
"searchsymbols.cpp", "searchsymbols.h",
"semantichighlighter.cpp", "semantichighlighter.h",
"senddocumenttracker.cpp", "senddocumenttracker.h",
"stringtable.cpp", "stringtable.h",
"symbolfinder.cpp", "symbolfinder.h",
"symbolsfindfilter.cpp", "symbolsfindfilter.h",
......
HEADERS += $$PWD/senddocumenttracker.h
SOURCES += $$PWD/senddocumenttracker.cpp
......@@ -58,4 +58,9 @@ void CppEditorDocumentHandle::setNeedsRefresh(bool needsRefresh)
m_needsRefresh = needsRefresh;
}
SendDocumentTracker &CppEditorDocumentHandle::sendTracker(const QString &projectPartId)
{
return m_documentRevisionManagements[projectPartId];
}
} // namespace CppTools
......@@ -32,6 +32,10 @@
#define EDITORDOCUMENTHANDLE_H
#include "cpptools_global.h"
#include "senddocumenttracker.h"
#include <QMap>
#include <QString>
namespace CppTools {
class BaseEditorDocumentProcessor;
......@@ -55,7 +59,10 @@ public:
virtual void resetProcessor() = 0;
SendDocumentTracker &sendTracker(const QString &projectPartId);
private:
QMap<QString, SendDocumentTracker> m_documentRevisionManagements;
bool m_needsRefresh;
};
......
/****************************************************************************
**
** Copyright (C) 2015 The Qt Company Ltd.
** Contact: http://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 http://www.qt.io/terms-conditions. For further information
** use the contact form at http://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 or version 3 as published by the Free
** Software Foundation and appearing in the file LICENSE.LGPLv21 and
** LICENSE.LGPLv3 included in the packaging of this file. Please review the
** following information to ensure the GNU Lesser General Public License
** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, The Qt Company gives you certain additional
** rights. These rights are described in The Qt Company LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
****************************************************************************/
#include "senddocumenttracker.h"
#include <algorithm>
namespace CppTools {
void SendDocumentTracker::setLastSentRevision(int revision)
{
m_lastSentRevision = revision;
m_contentChangeStartPosition = std::numeric_limits<int>::max();
}
int SendDocumentTracker::lastSentRevision() const
{
return m_lastSentRevision;
}
void SendDocumentTracker::setLastCompletionPosition(int lastCompletionPosition)
{
m_lastCompletionPosition = lastCompletionPosition;
}
int SendDocumentTracker::lastCompletionPosition() const
{
return m_lastCompletionPosition;
}
void SendDocumentTracker::applyContentChange(int startPosition)
{
if (startPosition < m_lastCompletionPosition)
m_lastCompletionPosition = -1;
m_contentChangeStartPosition = std::min(startPosition, m_contentChangeStartPosition);
}
bool SendDocumentTracker::shouldSendCompletion(int newCompletionPosition) const
{
return m_lastCompletionPosition != newCompletionPosition;
}
bool SendDocumentTracker::shouldSendRevision(uint newRevision) const
{
return m_lastSentRevision != int(newRevision);
}
bool SendDocumentTracker::shouldSendRevisionWithCompletionPosition(int newRevision, int newCompletionPosition) const
{
if (shouldSendRevision(newRevision))
return changedBeforeCompletionPosition(newCompletionPosition);
return false;
}
bool SendDocumentTracker::changedBeforeCompletionPosition(int newCompletionPosition) const
{
return m_contentChangeStartPosition < newCompletionPosition;
}
} // namespace CppTools
/****************************************************************************
**
** Copyright (C) 2015 The Qt Company Ltd.
** Contact: http://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 http://www.qt.io/terms-conditions. For further information
** use the contact form at http://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 or version 3 as published by the Free
** Software Foundation and appearing in the file LICENSE.LGPLv21 and
** LICENSE.LGPLv3 included in the packaging of this file. Please review the
** following information to ensure the GNU Lesser General Public License
** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, The Qt Company gives you certain additional
** rights. These rights are described in The Qt Company LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
****************************************************************************/
#ifndef CPPTOOLS_SENDDOCUMENTTRACKER_H
#define CPPTOOLS_SENDDOCUMENTTRACKER_H
#include "cpptools_global.h"
#include <limits>
namespace CppTools {
class CPPTOOLS_EXPORT SendDocumentTracker
{
public:
void setLastSentRevision(int lastSentRevision);
int lastSentRevision() const;
void setLastCompletionPosition(int lastCompletionPosition);
int lastCompletionPosition() const;
void applyContentChange(int startPosition);
bool shouldSendCompletion(int newCompletionPosition) const;
bool shouldSendRevision(uint newRevision) const;
bool shouldSendRevisionWithCompletionPosition(int newRevision, int newCompletionPosition) const;
private:
bool changedBeforeCompletionPosition(int newCompletionPosition) const;
private:
int m_lastSentRevision = -1;
int m_lastCompletionPosition = -1;
int m_contentChangeStartPosition = std::numeric_limits<int>::max();
};
} // namespace CppTools
#endif // CPPTOOLS_SENDDOCUMENTTRACKER_H
......@@ -236,7 +236,10 @@ TextDocument::TextDocument(Id id)
emit changed();
});
QObject::connect(&d->m_document, &QTextDocument::contentsChanged, this, &TextDocument::contentsChanged);
connect(&d->m_document, &QTextDocument::contentsChanged,
this, &TextDocument::contentsChanged);
connect(&d->m_document, &QTextDocument::contentsChange,
this, &TextDocument::contentsChangedWithPosition);
// set new document layout
QTextOption opt = d->m_document.defaultTextOption();
......
......@@ -144,6 +144,7 @@ signals:
void aboutToOpen(const QString &fileName, const QString &realFileName);
void openFinishedSuccessfully();
void contentsChanged();
void contentsChangedWithPosition(int position, int charsRemoved, int charsAdded);
void tabSettingsChanged();
void fontSettingsChanged();
......
......@@ -2,6 +2,7 @@ include(../../../src/libs/sqlite/sqlite-lib.pri)
include(../../../src/libs/clangbackendipc/clangbackendipc-lib.pri)
include(../../../src/tools/clangbackend/ipcsource/clangbackendclangipc-source.pri)
include(../../../src/plugins/clangcodemodel/clangcodemodelunittestfiles.pri)
include(../../../src/plugins/cpptools/cpptoolsunittestfiles.pri)
include(cplusplus.pri)
INCLUDEPATH += \
......
/****************************************************************************
**
** Copyright (C) 2015 The Qt Company Ltd.
** Contact: http://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 http://www.qt.io/terms-conditions. For further information
** use the contact form at http://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 or version 3 as published by the Free
** Software Foundation and appearing in the file LICENSE.LGPLv21 and
** LICENSE.LGPLv3 included in the packaging of this file. Please review the
** following information to ensure the GNU Lesser General Public License
** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, The Qt Company gives you certain additional
** rights. These rights are described in The Qt Company LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
****************************************************************************/
#include <cpptools/senddocumenttracker.h>
#include <gmock/gmock.h>
#include <gmock/gmock-matchers.h>
#include <gtest/gtest.h>
#include "gtest-qt-printing.h"
using CppTools::SendDocumentTracker;
using testing::PrintToString;
namespace {
MATCHER_P2(HasValues, lastSentRevision, lastCompletionPosition,
std::string(negation ? "isn't" : "is")
+ " DocumentRevisionManagement with last sent revision "+ PrintToString(lastSentRevision)
+ " , last completion position " + PrintToString(lastCompletionPosition))
{
return arg.lastSentRevision() == lastSentRevision
&& arg.lastCompletionPosition() == lastCompletionPosition;
}
class SendDocumentTracker : public testing::Test
{
protected:
::SendDocumentTracker tracker;
};
TEST_F(SendDocumentTracker, DefaultLastSentRevision)
{
ASSERT_THAT(tracker, HasValues(-1, -1));
}
TEST_F(SendDocumentTracker, SetRevision)
{
tracker.setLastSentRevision(46);
ASSERT_THAT(tracker, HasValues(46, -1));
}
TEST_F(SendDocumentTracker, SetLastCompletionPosition)
{
tracker.setLastCompletionPosition(33);
ASSERT_THAT(tracker, HasValues(-1, 33));
}
TEST_F(SendDocumentTracker, ApplyContentChange)
{
tracker.setLastSentRevision(46);
tracker.setLastCompletionPosition(33);
tracker.applyContentChange(10);
ASSERT_THAT(tracker, HasValues(46, -1));
}
TEST_F(SendDocumentTracker, DontSendCompletionIfPositionIsEqual)
{
tracker.setLastCompletionPosition(33);
ASSERT_FALSE(tracker.shouldSendCompletion(33));
}
TEST_F(SendDocumentTracker, SendCompletionIfPositionIsDifferent)
{
tracker.setLastSentRevision(46);
tracker.setLastCompletionPosition(33);
ASSERT_TRUE(tracker.shouldSendCompletion(22));
}
TEST_F(SendDocumentTracker, SendCompletionIfChangeIsBeforeCompletionPositionAndPositionIsEqual)
{
tracker.setLastSentRevision(46);
tracker.setLastCompletionPosition(33);
tracker.applyContentChange(10);
ASSERT_TRUE(tracker.shouldSendCompletion(33));
}
TEST_F(SendDocumentTracker, DontSendCompletionIfChangeIsAfterCompletionPositionAndPositionIsEqual)
{
tracker.setLastSentRevision(46);
tracker.setLastCompletionPosition(33);
tracker.applyContentChange(40);
ASSERT_FALSE(tracker.shouldSendCompletion(33));
}
TEST_F(SendDocumentTracker, DontSendRevisionIfRevisionIsEqual)
{
tracker.setLastSentRevision(46);
ASSERT_FALSE(tracker.shouldSendRevision(46));
}
TEST_F(SendDocumentTracker, SendRevisionIfRevisionIsDifferent)
{
tracker.setLastSentRevision(46);
ASSERT_TRUE(tracker.shouldSendRevision(21));
}
TEST_F(SendDocumentTracker, DontSendIfRevisionIsDifferentAndCompletionPositionIsEqualAndNoContentChange)
{
tracker.setLastSentRevision(46);
tracker.setLastCompletionPosition(33);
ASSERT_FALSE(tracker.shouldSendRevisionWithCompletionPosition(21, 33));
}
TEST_F(SendDocumentTracker, DontSendIfRevisionIsDifferentAndCompletionPositionIsDifferentAndNoContentChange)
{
tracker.setLastSentRevision(46);
tracker.setLastCompletionPosition(33);
ASSERT_FALSE(tracker.shouldSendRevisionWithCompletionPosition(21, 44));
}
TEST_F(SendDocumentTracker, DontSendIfRevisionIsEqualAndCompletionPositionIsDifferentAndNoContentChange)
{
tracker.setLastSentRevision(46);
tracker.setLastCompletionPosition(33);
ASSERT_FALSE(tracker.shouldSendRevisionWithCompletionPosition(46,44));
}
TEST_F(SendDocumentTracker, SendIfChangeIsBeforeCompletionAndPositionIsEqualAndRevisionIsDifferent)
{