refactor: Make DocumentContextReader::prepareContext() testable (#96)

This commit is contained in:
Povilas Kanapickas 2025-03-05 21:18:59 +02:00 committed by GitHub
parent e218699e64
commit 69a8aa80d9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 113 additions and 32 deletions

View File

@ -267,7 +267,7 @@ LLMCore::ContextData LLMClientInterface::prepareContext(
Context::DocumentContextReader reader(
textDocument->document(), textDocument->mimeType(), textDocument->filePath().toString());
return reader.prepareContext(lineNumber, cursorPosition);
return reader.prepareContext(lineNumber, cursorPosition, Settings::codeCompletionSettings());
}
void LLMClientInterface::sendCompletionToClient(

View File

@ -244,39 +244,29 @@ CopyrightInfo DocumentContextReader::copyrightInfo() const
return m_copyrightInfo;
}
LLMCore::ContextData DocumentContextReader::prepareContext(int lineNumber, int cursorPosition) const
LLMCore::ContextData DocumentContextReader::prepareContext(
int lineNumber, int cursorPosition, const Settings::CodeCompletionSettings &settings) const
{
QString contextBefore = getContextBefore(lineNumber, cursorPosition);
QString contextAfter = getContextAfter(lineNumber, cursorPosition);
QString contextBefore;
QString contextAfter;
if (settings.readFullFile()) {
contextBefore = readWholeFileBefore(lineNumber, cursorPosition);
contextAfter = readWholeFileAfter(lineNumber, cursorPosition);
} else {
contextBefore
= getContextBefore(lineNumber, cursorPosition, settings.readStringsBeforeCursor());
contextAfter
= getContextAfter(lineNumber, cursorPosition, settings.readStringsAfterCursor());
}
QString fileContext;
fileContext.append("\n ").append(getLanguageAndFileInfo());
if (Settings::codeCompletionSettings().useProjectChangesCache())
if (settings.useProjectChangesCache())
fileContext.append("\n ").append(
ChangesManager::instance().getRecentChangesContext(m_textDocument));
return {.prefix = contextBefore, .suffix = contextAfter, .fileContext = fileContext};
}
QString DocumentContextReader::getContextBefore(int lineNumber, int cursorPosition) const
{
if (Settings::codeCompletionSettings().readFullFile()) {
return readWholeFileBefore(lineNumber, cursorPosition);
} else {
int linesCount = Settings::codeCompletionSettings().readStringsBeforeCursor();
return getContextBefore(lineNumber, cursorPosition, linesCount);
}
}
QString DocumentContextReader::getContextAfter(int lineNumber, int cursorPosition) const
{
if (Settings::codeCompletionSettings().readFullFile()) {
return readWholeFileAfter(lineNumber, cursorPosition);
} else {
int linesCount = Settings::codeCompletionSettings().readStringsAfterCursor();
return getContextAfter(lineNumber, cursorPosition, linesCount);
}
}
} // namespace QodeAssist::Context

View File

@ -23,6 +23,7 @@
#include <QTextDocument>
#include <llmcore/ContextData.hpp>
#include <settings/CodeCompletionSettings.hpp>
namespace QodeAssist::Context {
@ -72,11 +73,8 @@ public:
CopyrightInfo copyrightInfo() const;
LLMCore::ContextData prepareContext(int lineNumber, int cursorPosition) const;
private:
QString getContextBefore(int lineNumber, int cursorPosition) const;
QString getContextAfter(int lineNumber, int cursorPosition) const;
LLMCore::ContextData prepareContext(
int lineNumber, int cursorPosition, const Settings::CodeCompletionSettings &settings) const;
private:
TextEditor::TextDocument *m_textDocument;

View File

@ -1,4 +1,4 @@
/*
/*
* Copyright (C) 2024 Petr Mironychev
*
* This file is part of QodeAssist.
@ -28,6 +28,10 @@ struct Message
{
QString role;
QString content;
// clang-format off
auto operator<=>(const Message&) const = default;
// clang-format on
};
struct ContextData
@ -37,6 +41,8 @@ struct ContextData
std::optional<QString> suffix;
std::optional<QString> fileContext;
std::optional<QVector<Message>> history;
bool operator==(const ContextData &) const = default;
};
} // namespace QodeAssist::LLMCore

View File

@ -20,16 +20,59 @@
#include "context/DocumentContextReader.hpp"
#include <gtest/gtest.h>
#include <QSharedPointer>
#include <QTextDocument>
using namespace QodeAssist::Context;
using namespace QodeAssist::LLMCore;
using namespace QodeAssist::Settings;
std::ostream &operator<<(std::ostream &out, const QString &value)
{
out << value.toStdString();
out << '"' << value.toStdString() << '"';
return out;
}
template<class T>
std::ostream &operator<<(std::ostream &out, const QVector<T> &value)
{
out << "[";
for (const auto &el : value) {
out << value << ", ";
}
out << "]";
return out;
}
template<class T>
std::ostream &operator<<(std::ostream &out, const std::optional<T> &value)
{
if (value.has_value()) {
out << value.value();
} else {
out << "(no value)";
}
return out;
}
namespace QodeAssist::LLMCore {
std::ostream &operator<<(std::ostream &out, const Message &value)
{
out << "Message{"
<< "role=" << value.role << "content=" << value.content << "}";
return out;
}
std::ostream &operator<<(std::ostream &out, const ContextData &value)
{
out << "ContextData{"
<< "\n systemPrompt=" << value.systemPrompt << "\n prefix=" << value.prefix
<< "\n suffix=" << value.suffix << "\n fileContext=" << value.fileContext
<< "\n history=" << value.history << "\n}";
return out;
}
} // namespace QodeAssist::LLMCore
class DocumentContextReaderTest : public QObject, public testing::Test
{
Q_OBJECT
@ -46,6 +89,24 @@ protected:
{
return DocumentContextReader(createTestDocument(text), "text/python", "/path/to/file");
}
QSharedPointer<CodeCompletionSettings> createSettingsForWholeFile()
{
// CodeCompletionSettings is noncopyable
auto settings = QSharedPointer<CodeCompletionSettings>::create();
settings->readFullFile.setValue(true);
return settings;
}
QSharedPointer<CodeCompletionSettings> createSettingsForLines(int linesBefore, int linesAfter)
{
// CodeCompletionSettings is noncopyable
auto settings = QSharedPointer<CodeCompletionSettings>::create();
settings->readFullFile.setValue(false);
settings->readStringsBeforeCursor.setValue(linesBefore);
settings->readStringsAfterCursor.setValue(linesAfter);
return settings;
}
};
TEST_F(DocumentContextReaderTest, testGetLineText)
@ -208,4 +269,30 @@ TEST_F(DocumentContextReaderTest, testGetContextBetween)
EXPECT_EQ(reader.getContextBetween(1, 2, 3, 2), "ne 2\nLine 3\nLi");
}
TEST_F(DocumentContextReaderTest, testPrepareContext)
{
auto reader = createTestReader("Line 1\nLine 2\nLine 3\nLine 4\nLine 5");
EXPECT_EQ(
reader.prepareContext(2, 3, *createSettingsForWholeFile()),
(ContextData{
.prefix = "Line 1\nLine 2\nLin",
.suffix = "e 3\nLine 4\nLine 5",
.fileContext = "\n Language: (MIME: text/python) filepath: /path/to/file()\n\n\n "}));
EXPECT_EQ(
reader.prepareContext(2, 3, *createSettingsForLines(1, 1)),
(ContextData{
.prefix = "Lin",
.suffix = "e 3",
.fileContext = "\n Language: (MIME: text/python) filepath: /path/to/file()\n\n\n "}));
EXPECT_EQ(
reader.prepareContext(2, 3, *createSettingsForLines(2, 2)),
(ContextData{
.prefix = "Line 2\nLin",
.suffix = "e 3\nLine 4",
.fileContext = "\n Language: (MIME: text/python) filepath: /path/to/file()\n\n\n "}));
}
#include "DocumentContextReaderTest.moc"