refactor: Decoupling chat architecture (#367)

* refactor: Migrate tests to Qt Creator plugin framework and update llmqore transports

* refactor: Move conversation history into IDE-free session library

* refactor: Extract turn context assembly behind injected ports

* build: Add RunQodeAssistTests target for the plugin test suite

* refactor: Route the chat through a Session and ChatBackend seam
This commit is contained in:
Petr Mironychev
2026-07-19 22:45:04 +02:00
committed by GitHub
parent bd809edb8a
commit af85efb554
83 changed files with 5421 additions and 4620 deletions

View File

@@ -1,30 +0,0 @@
add_executable(QodeAssistTest
../sources/completion/CodeHandler.cpp
../sources/completion/LLMClientInterface.cpp
../sources/completion/LLMSuggestion.cpp
../sources/providers/Provider.cpp
CodeHandlerTest.cpp
ClaudeCacheControlTest.cpp
DocumentContextReaderTest.cpp
LLMSuggestionTest.cpp
# LLMClientInterfaceTests.cpp
unittest_main.cpp
)
target_link_libraries(QodeAssistTest PRIVATE
Qt::Core
Qt::Test
GTest::GTest
GTest::gmock
GTest::Main
QtCreator::LanguageClient
Context
QodeAssistLogger
LLMQore
)
target_include_directories(QodeAssistTest PRIVATE ${CMAKE_SOURCE_DIR}/sources)
target_compile_definitions(QodeAssistTest PRIVATE CMAKE_CURRENT_SOURCE_DIR="${CMAKE_CURRENT_SOURCE_DIR}")
add_test(NAME QodeAssistTest COMMAND QodeAssistTest)

View File

@@ -1,182 +0,0 @@
// Copyright (C) 2024-2026 Petr Mironychev
// SPDX-License-Identifier: GPL-3.0-or-later
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
#include <gtest/gtest.h>
#include <QJsonArray>
#include <QJsonObject>
#include "providers/ClaudeCacheControl.hpp"
using namespace QodeAssist::Providers::ClaudeCacheControl;
namespace {
QJsonObject expectedEphemeral(bool extendedTtl)
{
QJsonObject obj{{"type", "ephemeral"}};
if (extendedTtl)
obj["ttl"] = "1h";
return obj;
}
} // namespace
TEST(ClaudeCacheControlTest, BreakpointWithoutExtendedTTL)
{
const QJsonObject cc = buildBreakpoint(false);
EXPECT_EQ(cc.value("type").toString(), "ephemeral");
EXPECT_FALSE(cc.contains("ttl"));
}
TEST(ClaudeCacheControlTest, BreakpointWithExtendedTTL)
{
const QJsonObject cc = buildBreakpoint(true);
EXPECT_EQ(cc.value("type").toString(), "ephemeral");
EXPECT_EQ(cc.value("ttl").toString(), "1h");
}
TEST(ClaudeCacheControlTest, SystemAsStringWrappedIntoArray)
{
QJsonObject request;
request["system"] = "you are a helpful agent";
apply(request, false);
ASSERT_TRUE(request.value("system").isArray());
const QJsonArray sys = request.value("system").toArray();
ASSERT_EQ(sys.size(), 1);
const QJsonObject block = sys.first().toObject();
EXPECT_EQ(block.value("type").toString(), "text");
EXPECT_EQ(block.value("text").toString(), "you are a helpful agent");
EXPECT_EQ(block.value("cache_control").toObject(), expectedEphemeral(false));
}
TEST(ClaudeCacheControlTest, EmptySystemStringIsNotWrapped)
{
QJsonObject request;
request["system"] = "";
apply(request, false);
EXPECT_TRUE(request.value("system").isString());
}
TEST(ClaudeCacheControlTest, SystemAsArrayMarksLastBlock)
{
QJsonObject request;
request["system"] = QJsonArray{
QJsonObject{{"type", "text"}, {"text", "a"}},
QJsonObject{{"type", "text"}, {"text", "b"}}};
apply(request, false);
const QJsonArray sys = request.value("system").toArray();
ASSERT_EQ(sys.size(), 2);
EXPECT_FALSE(sys[0].toObject().contains("cache_control"));
EXPECT_EQ(sys[1].toObject().value("cache_control").toObject(), expectedEphemeral(false));
}
TEST(ClaudeCacheControlTest, ToolsLastEntryGetsCacheControl)
{
QJsonObject request;
request["tools"] = QJsonArray{
QJsonObject{{"name", "read_file"}},
QJsonObject{{"name", "edit_file"}},
QJsonObject{{"name", "search"}}};
apply(request, true);
const QJsonArray tools = request.value("tools").toArray();
ASSERT_EQ(tools.size(), 3);
EXPECT_FALSE(tools[0].toObject().contains("cache_control"));
EXPECT_FALSE(tools[1].toObject().contains("cache_control"));
EXPECT_EQ(tools[2].toObject().value("cache_control").toObject(), expectedEphemeral(true));
}
TEST(ClaudeCacheControlTest, SingleMessageHistorySkipped)
{
QJsonObject request;
request["messages"]
= QJsonArray{QJsonObject{{"role", "user"}, {"content", "first message"}}};
apply(request, false);
const QJsonArray msgs = request.value("messages").toArray();
ASSERT_EQ(msgs.size(), 1);
EXPECT_TRUE(msgs[0].toObject().value("content").isString());
}
TEST(ClaudeCacheControlTest, HistoryBreakpointOnSecondToLastMessage)
{
QJsonObject request;
request["messages"] = QJsonArray{
QJsonObject{{"role", "user"}, {"content", "u1"}},
QJsonObject{{"role", "assistant"}, {"content", "a1"}},
QJsonObject{{"role", "user"}, {"content", "u2-current"}}};
apply(request, false);
const QJsonArray msgs = request.value("messages").toArray();
ASSERT_EQ(msgs.size(), 3);
EXPECT_TRUE(msgs[0].toObject().value("content").isString());
const QJsonArray a1Content = msgs[1].toObject().value("content").toArray();
ASSERT_EQ(a1Content.size(), 1);
EXPECT_EQ(a1Content.first().toObject().value("text").toString(), "a1");
EXPECT_EQ(
a1Content.first().toObject().value("cache_control").toObject(),
expectedEphemeral(false));
EXPECT_TRUE(msgs[2].toObject().value("content").isString());
}
TEST(ClaudeCacheControlTest, HistoryArrayContentMarksLastBlock)
{
QJsonObject request;
request["messages"] = QJsonArray{
QJsonObject{
{"role", "user"},
{"content",
QJsonArray{
QJsonObject{{"type", "text"}, {"text", "describe this"}},
QJsonObject{{"type", "image"}}}}},
QJsonObject{{"role", "assistant"}, {"content", "ok"}}};
apply(request, false);
const QJsonArray msgs = request.value("messages").toArray();
const QJsonArray content = msgs[0].toObject().value("content").toArray();
ASSERT_EQ(content.size(), 2);
EXPECT_FALSE(content[0].toObject().contains("cache_control"));
EXPECT_EQ(content[1].toObject().value("cache_control").toObject(), expectedEphemeral(false));
}
TEST(ClaudeCacheControlTest, NoSystemNoToolsNoMessagesIsNoop)
{
QJsonObject request;
request["model"] = "claude-sonnet-4-5";
request["max_tokens"] = 1024;
apply(request, false);
EXPECT_EQ(request.value("model").toString(), "claude-sonnet-4-5");
EXPECT_EQ(request.value("max_tokens").toInt(), 1024);
EXPECT_FALSE(request.contains("system"));
EXPECT_FALSE(request.contains("tools"));
EXPECT_FALSE(request.contains("messages"));
}
TEST(ClaudeCacheControlTest, EmptyToolsArrayIsNoop)
{
QJsonObject request;
request["tools"] = QJsonArray{};
apply(request, false);
EXPECT_TRUE(request.value("tools").isArray());
EXPECT_TRUE(request.value("tools").toArray().isEmpty());
}

View File

@@ -1,218 +0,0 @@
// Copyright (C) 2025 Povilas Kanapickas
// SPDX-License-Identifier: GPL-3.0-or-later
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
#include "completion/CodeHandler.hpp"
#include "TestUtils.hpp"
#include <gtest/gtest.h>
#include <QObject>
#include <QString>
using namespace QodeAssist;
class CodeHandlerTest : public QObject, public testing::Test
{
// Tests are written in the fixture format with the expectation that CodeHandler will
// be expanded.
Q_OBJECT
};
TEST_F(CodeHandlerTest, testProcessTextEmpty)
{
EXPECT_EQ(CodeHandler::processText("", "/file.py"), "\n\n");
}
TEST_F(CodeHandlerTest, testProcessTextWithLanguageCodeBlock)
{
QString input = "This is a comment\n"
"```python\nprint('Hello, world!')\n```\n"
"Another comment";
EXPECT_EQ(
CodeHandler::processText(input, "/file.py"),
"# This is a comment\n\nprint('Hello, world!')\n# Another comment\n\n");
}
TEST_F(CodeHandlerTest, testProcessTextWithPlainCodeBlockNoNewline)
{
QString input = "This is a comment\n"
"```print('Hello, world!')\n```\n"
"Another comment";
EXPECT_EQ(
CodeHandler::processText(input, "/file.py"),
"# This is a comment\n\nprint('Hello, world!')\n# Another comment\n\n");
}
TEST_F(CodeHandlerTest, testProcessTextWithPlainCodeBlockWithNewline)
{
QString input = "This is a comment\n"
"```\nprint('Hello, world!')\n```\n"
"Another comment";
EXPECT_EQ(
CodeHandler::processText(input, "/file.py"),
"# This is a comment\n\n\nprint('Hello, world!')\n# Another comment\n\n");
}
TEST_F(CodeHandlerTest, testProcessTextNoCommentsWithLanguageCodeBlock)
{
QString input = "```python\nprint('Hello, world!')\n```";
EXPECT_EQ(CodeHandler::processText(input, "/file.py"), "print('Hello, world!')\n");
}
TEST_F(CodeHandlerTest, testProcessTextNoCommentsWithPlainCodeBlockNoNewline)
{
QString input = "```print('Hello, world!')\n```";
EXPECT_EQ(CodeHandler::processText(input, "/file.py"), "print('Hello, world!')\n");
}
TEST_F(CodeHandlerTest, testProcessTextNoCommentsWithPlainCodeBlockWithNewline)
{
QString input = "```\nprint('Hello, world!')\n```";
EXPECT_EQ(CodeHandler::processText(input, "/file.py"), "\nprint('Hello, world!')\n");
}
TEST_F(CodeHandlerTest, testProcessTextWithMultipleCodeBlocksDifferentLanguages)
{
QString input = "First comment\n```python\nprint('Block 1')\n"
"```\nMiddle comment\n"
"```cpp\ncout << \"Block 2\";\n```\n"
"Last comment";
EXPECT_EQ(
CodeHandler::processText(input, "/file.py"),
"# First comment\n\n"
"print('Block 1')\n"
"// Middle comment\n\n"
"cout << \"Block 2\";\n"
"// Last comment\n\n");
}
TEST_F(CodeHandlerTest, testProcessTextWithMultipleCodeBlocksSameLanguage)
{
QString input = "First comment\n```python\nprint('Block 1')\n"
"```\nMiddle comment\n"
"```python\nprint('Block 2')\n```\n"
"Last comment";
EXPECT_EQ(
CodeHandler::processText(input, "/file.py"),
"# First comment\n\n"
"print('Block 1')\n"
"# Middle comment\n\n"
"print('Block 2')\n"
"# Last comment\n\n");
}
TEST_F(CodeHandlerTest, testProcessTextWithMultiplePlainCodeBlocksWithNewline)
{
QString input = "First comment\n```\nprint('Block 1')\n"
"```\nMiddle comment\n"
"```\ncout << \"Block 2\";\n```\n"
"Last comment";
EXPECT_EQ(
CodeHandler::processText(input, "/file.py"),
"# First comment\n\n\n"
"print('Block 1')\n"
"# Middle comment\n\n\n"
"cout << \"Block 2\";\n"
"# Last comment\n\n");
}
TEST_F(CodeHandlerTest, testProcessTextWithMultiplePlainCodeBlocksWithoutNewline)
{
QString input = "First comment\n```print('Block 1')\n"
"```\nMiddle comment\n"
"```cout << \"Block 2\";\n```\n"
"Last comment";
EXPECT_EQ(
CodeHandler::processText(input, "/file.py"),
"# First comment\n\n"
"print('Block 1')\n"
"# Middle comment\n\n"
"cout << \"Block 2\";\n"
"# Last comment\n\n");
}
TEST_F(CodeHandlerTest, testProcessTextWithEmptyLines)
{
QString input = "Comment with empty line\n\n```python\nprint('Hello')\n```\n\nAnother comment";
EXPECT_EQ(
CodeHandler::processText(input, "/file.py"),
"# Comment with empty line\n\n\n"
"print('Hello')\n\n"
"# Another comment\n\n");
}
TEST_F(CodeHandlerTest, testProcessTextPlainCodeBlockWithNewlineWithEmptyLines)
{
QString input = "Comment with empty line\n\n```\nprint('Hello')\n```\n\nAnother comment";
EXPECT_EQ(
CodeHandler::processText(input, "/file.py"),
"# Comment with empty line\n\n\n\n"
"print('Hello')\n\n"
"# Another comment\n\n");
}
TEST_F(CodeHandlerTest, testProcessTextWithoutCodeBlock)
{
QString input = "This is just a comment\nwith multiple lines";
EXPECT_EQ(
CodeHandler::processText(input, "/file.py"),
"# This is just a comment\n# with multiple lines\n\n");
}
TEST_F(CodeHandlerTest, testDetectLanguageFromLine)
{
EXPECT_EQ(CodeHandler::detectLanguageFromLine("```python"), "python");
EXPECT_EQ(CodeHandler::detectLanguageFromLine("```javascript"), "js");
EXPECT_EQ(CodeHandler::detectLanguageFromLine("```cpp"), "c-like");
EXPECT_EQ(CodeHandler::detectLanguageFromLine("``` ruby "), "ruby");
EXPECT_EQ(CodeHandler::detectLanguageFromLine("```"), "");
EXPECT_EQ(CodeHandler::detectLanguageFromLine("``` "), "");
}
TEST_F(CodeHandlerTest, testDetectLanguageFromExtension)
{
EXPECT_EQ(CodeHandler::detectLanguageFromExtension("py"), "python");
EXPECT_EQ(CodeHandler::detectLanguageFromExtension("js"), "js");
EXPECT_EQ(CodeHandler::detectLanguageFromExtension("cpp"), "c-like");
EXPECT_EQ(CodeHandler::detectLanguageFromExtension("hpp"), "c-like");
EXPECT_EQ(CodeHandler::detectLanguageFromExtension("rb"), "ruby");
EXPECT_EQ(CodeHandler::detectLanguageFromExtension("sh"), "shell");
EXPECT_EQ(CodeHandler::detectLanguageFromExtension("unknown"), "");
EXPECT_EQ(CodeHandler::detectLanguageFromExtension(""), "");
}
TEST_F(CodeHandlerTest, testCommentPrefixForDifferentLanguages)
{
struct TestCase
{
std::string language;
QString input;
QString expected;
};
std::vector<TestCase> testCases
= {{"python", "Comment\n```python\ncode\n```", "# Comment\n\ncode\n"},
{"cpp", "Comment\n```cpp\ncode\n```", "// Comment\n\ncode\n"},
{"ruby", "Comment\n```ruby\ncode\n```", "# Comment\n\ncode\n"},
{"lua", "Comment\n```lua\ncode\n```", "-- Comment\n\ncode\n"}};
for (const auto &testCase : testCases) {
EXPECT_EQ(CodeHandler::processText(testCase.input, ""), testCase.expected)
<< "Failed for language: " << testCase.language;
}
}
#include "CodeHandlerTest.moc"

View File

@@ -1,393 +0,0 @@
// Copyright (C) 2025 Povilas Kanapickas
// SPDX-License-Identifier: GPL-3.0-or-later
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
#include "context/DocumentContextReader.hpp"
#include "TestUtils.hpp"
#include <gtest/gtest.h>
#include <QSharedPointer>
#include <QTextDocument>
namespace QodeAssist::LLMCore {
void PrintTo(const ContextData &data, std::ostream *os)
{
*os << "ContextData{prefix="
<< (data.prefix ? data.prefix->toStdString() : "<nullopt>")
<< ", suffix=" << (data.suffix ? data.suffix->toStdString() : "<nullopt>")
<< ", fileContext=" << (data.fileContext ? data.fileContext->toStdString() : "<nullopt>")
<< "}";
}
} // namespace QodeAssist::LLMCore
using namespace QodeAssist::Context;
using namespace QodeAssist::LLMCore;
using namespace QodeAssist::Settings;
class DocumentContextReaderTest : public QObject, public testing::Test
{
Q_OBJECT
protected:
QTextDocument *createTestDocument(const QString &text)
{
auto *doc = new QTextDocument(this);
doc->setPlainText(text);
return doc;
}
DocumentContextReader createTestReader(const QString &text)
{
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)
{
auto reader = createTestReader("Line 0\nLine 1\nLine 2");
EXPECT_EQ(reader.getLineText(0), "Line 0");
EXPECT_EQ(reader.getLineText(1), "Line 1");
EXPECT_EQ(reader.getLineText(2), "Line 2");
EXPECT_EQ(reader.getLineText(0, 4), "Line");
}
TEST_F(DocumentContextReaderTest, testGetContext)
{
auto reader = createTestReader("Line 0\nLine 1\nLine 2\nLine 3\nLine 4");
// Unknown cursor position
EXPECT_EQ(reader.getContextBefore(0, -1, 2), "Line 0");
EXPECT_EQ(reader.getContextAfter(0, -1, 2), "Line 0\nLine 1");
EXPECT_EQ(reader.getContextBefore(1, -1, 2), "Line 0\nLine 1");
EXPECT_EQ(reader.getContextAfter(1, -1, 2), "Line 1\nLine 2");
EXPECT_EQ(reader.getContextBefore(2, -1, 2), "Line 1\nLine 2");
EXPECT_EQ(reader.getContextAfter(2, -1, 2), "Line 2\nLine 3");
EXPECT_EQ(reader.getContextBefore(3, -1, 2), "Line 2\nLine 3");
EXPECT_EQ(reader.getContextAfter(3, -1, 2), "Line 3\nLine 4");
// Known cursor position
EXPECT_EQ(reader.getContextBefore(0, 1, 2), "L");
EXPECT_EQ(reader.getContextAfter(0, 1, 2), "ine 0\nLine 1");
EXPECT_EQ(reader.getContextBefore(1, 1, 2), "Line 0\nL");
EXPECT_EQ(reader.getContextAfter(1, 1, 2), "ine 1\nLine 2");
EXPECT_EQ(reader.getContextBefore(2, 1, 2), "Line 1\nL");
EXPECT_EQ(reader.getContextAfter(2, 1, 2), "ine 2\nLine 3");
EXPECT_EQ(reader.getContextBefore(3, 1, 2), "Line 2\nL");
EXPECT_EQ(reader.getContextAfter(3, 1, 2), "ine 3\nLine 4");
}
TEST_F(DocumentContextReaderTest, testGetContextWithCopyright)
{
auto reader = createTestReader("/* Copyright (C) 2024 */\nLine 0\nLine 1\nLine 2\nLine 3");
// Unknown cursor position
EXPECT_EQ(reader.getContextBefore(0, -1, 2), "");
EXPECT_EQ(reader.getContextAfter(0, -1, 2), "Line 0");
EXPECT_EQ(reader.getContextBefore(1, -1, 2), "Line 0");
EXPECT_EQ(reader.getContextAfter(1, -1, 2), "Line 0\nLine 1");
EXPECT_EQ(reader.getContextBefore(2, -1, 2), "Line 0\nLine 1");
EXPECT_EQ(reader.getContextAfter(2, -1, 2), "Line 1\nLine 2");
EXPECT_EQ(reader.getContextBefore(3, -1, 2), "Line 1\nLine 2");
EXPECT_EQ(reader.getContextAfter(3, -1, 2), "Line 2\nLine 3");
// Known cursor position
EXPECT_EQ(reader.getContextBefore(0, 1, 2), "");
EXPECT_EQ(reader.getContextAfter(0, 1, 2), "Line 0");
EXPECT_EQ(reader.getContextBefore(1, 1, 2), "L");
EXPECT_EQ(reader.getContextAfter(1, 1, 2), "ine 0\nLine 1");
EXPECT_EQ(reader.getContextBefore(2, 1, 2), "Line 0\nL");
EXPECT_EQ(reader.getContextAfter(2, 1, 2), "ine 1\nLine 2");
EXPECT_EQ(reader.getContextBefore(3, 1, 2), "Line 1\nL");
EXPECT_EQ(reader.getContextAfter(3, 1, 2), "ine 2\nLine 3");
}
TEST_F(DocumentContextReaderTest, testReadWholeFile)
{
auto reader = createTestReader("Line 0\nLine 1\nLine 2\nLine 3\nLine 4");
// Unknown cursor position
EXPECT_EQ(reader.readWholeFileBefore(0, -1), "Line 0");
EXPECT_EQ(reader.readWholeFileAfter(0, -1), "Line 0\nLine 1\nLine 2\nLine 3\nLine 4");
EXPECT_EQ(reader.readWholeFileBefore(1, -1), "Line 0\nLine 1");
EXPECT_EQ(reader.readWholeFileAfter(1, -1), "Line 1\nLine 2\nLine 3\nLine 4");
EXPECT_EQ(reader.readWholeFileBefore(2, -1), "Line 0\nLine 1\nLine 2");
EXPECT_EQ(reader.readWholeFileAfter(2, -1), "Line 2\nLine 3\nLine 4");
EXPECT_EQ(reader.readWholeFileBefore(3, -1), "Line 0\nLine 1\nLine 2\nLine 3");
EXPECT_EQ(reader.readWholeFileAfter(3, -1), "Line 3\nLine 4");
EXPECT_EQ(reader.readWholeFileBefore(4, -1), "Line 0\nLine 1\nLine 2\nLine 3\nLine 4");
EXPECT_EQ(reader.readWholeFileAfter(4, -1), "Line 4");
// Known cursor position
EXPECT_EQ(reader.readWholeFileBefore(0, 1), "L");
EXPECT_EQ(reader.readWholeFileAfter(0, 1), "ine 0\nLine 1\nLine 2\nLine 3\nLine 4");
EXPECT_EQ(reader.readWholeFileBefore(1, 1), "Line 0\nL");
EXPECT_EQ(reader.readWholeFileAfter(1, 1), "ine 1\nLine 2\nLine 3\nLine 4");
EXPECT_EQ(reader.readWholeFileBefore(2, 1), "Line 0\nLine 1\nL");
EXPECT_EQ(reader.readWholeFileAfter(2, 1), "ine 2\nLine 3\nLine 4");
EXPECT_EQ(reader.readWholeFileBefore(3, 1), "Line 0\nLine 1\nLine 2\nL");
EXPECT_EQ(reader.readWholeFileAfter(3, 1), "ine 3\nLine 4");
EXPECT_EQ(reader.readWholeFileBefore(4, 1), "Line 0\nLine 1\nLine 2\nLine 3\nL");
EXPECT_EQ(reader.readWholeFileAfter(4, 1), "ine 4");
}
TEST_F(DocumentContextReaderTest, testReadWholeFileWithCopyright)
{
auto reader = createTestReader("/* Copyright (C) 2024 */\nLine 0\nLine 1\nLine 2\nLine 3");
// Unknown cursor position
EXPECT_EQ(reader.readWholeFileBefore(0, -1), "");
EXPECT_EQ(reader.readWholeFileAfter(0, -1), "Line 0\nLine 1\nLine 2\nLine 3");
EXPECT_EQ(reader.readWholeFileBefore(1, -1), "Line 0");
EXPECT_EQ(reader.readWholeFileAfter(1, -1), "Line 0\nLine 1\nLine 2\nLine 3");
EXPECT_EQ(reader.readWholeFileBefore(2, -1), "Line 0\nLine 1");
EXPECT_EQ(reader.readWholeFileAfter(2, -1), "Line 1\nLine 2\nLine 3");
EXPECT_EQ(reader.readWholeFileBefore(3, -1), "Line 0\nLine 1\nLine 2");
EXPECT_EQ(reader.readWholeFileAfter(3, -1), "Line 2\nLine 3");
// Known cursor position
EXPECT_EQ(reader.readWholeFileBefore(0, 0), "");
EXPECT_EQ(reader.readWholeFileAfter(0, 0), "Line 0\nLine 1\nLine 2\nLine 3");
EXPECT_EQ(reader.readWholeFileBefore(1, 0), "");
EXPECT_EQ(reader.readWholeFileAfter(1, 0), "Line 0\nLine 1\nLine 2\nLine 3");
EXPECT_EQ(reader.readWholeFileBefore(2, 0), "Line 0\n");
EXPECT_EQ(reader.readWholeFileAfter(2, 0), "Line 1\nLine 2\nLine 3");
EXPECT_EQ(reader.readWholeFileBefore(3, 0), "Line 0\nLine 1\n");
EXPECT_EQ(reader.readWholeFileAfter(3, 0), "Line 2\nLine 3");
EXPECT_EQ(reader.readWholeFileBefore(0, 1), "");
EXPECT_EQ(reader.readWholeFileAfter(0, 1), "Line 0\nLine 1\nLine 2\nLine 3");
EXPECT_EQ(reader.readWholeFileBefore(1, 1), "L");
EXPECT_EQ(reader.readWholeFileAfter(1, 1), "ine 0\nLine 1\nLine 2\nLine 3");
EXPECT_EQ(reader.readWholeFileBefore(2, 1), "Line 0\nL");
EXPECT_EQ(reader.readWholeFileAfter(2, 1), "ine 1\nLine 2\nLine 3");
EXPECT_EQ(reader.readWholeFileBefore(3, 1), "Line 0\nLine 1\nL");
EXPECT_EQ(reader.readWholeFileAfter(3, 1), "ine 2\nLine 3");
}
TEST_F(DocumentContextReaderTest, testReadWholeFileWithMultilineCopyright)
{
auto reader = createTestReader(
"/*\n * Copyright (C) 2024\n * \n * This file is part of QodeAssist.\n */\n"
"Line 0\nLine 1\nLine 2\nLine 3");
// Unknown cursor position
EXPECT_EQ(reader.readWholeFileBefore(0, -1), "");
EXPECT_EQ(reader.readWholeFileAfter(0, -1), "Line 0\nLine 1\nLine 2\nLine 3");
EXPECT_EQ(reader.readWholeFileBefore(1, -1), "");
EXPECT_EQ(reader.readWholeFileAfter(1, -1), "Line 0\nLine 1\nLine 2\nLine 3");
EXPECT_EQ(reader.readWholeFileBefore(2, -1), "");
EXPECT_EQ(reader.readWholeFileAfter(2, -1), "Line 0\nLine 1\nLine 2\nLine 3");
EXPECT_EQ(reader.readWholeFileBefore(3, -1), "");
EXPECT_EQ(reader.readWholeFileAfter(3, -1), "Line 0\nLine 1\nLine 2\nLine 3");
EXPECT_EQ(reader.readWholeFileBefore(4, -1), "");
EXPECT_EQ(reader.readWholeFileAfter(4, -1), "Line 0\nLine 1\nLine 2\nLine 3");
EXPECT_EQ(reader.readWholeFileBefore(5, -1), "Line 0");
EXPECT_EQ(reader.readWholeFileAfter(5, -1), "Line 0\nLine 1\nLine 2\nLine 3");
EXPECT_EQ(reader.readWholeFileBefore(6, -1), "Line 0\nLine 1");
EXPECT_EQ(reader.readWholeFileAfter(6, -1), "Line 1\nLine 2\nLine 3");
EXPECT_EQ(reader.readWholeFileBefore(7, -1), "Line 0\nLine 1\nLine 2");
EXPECT_EQ(reader.readWholeFileAfter(7, -1), "Line 2\nLine 3");
EXPECT_EQ(reader.readWholeFileBefore(8, -1), "Line 0\nLine 1\nLine 2\nLine 3");
EXPECT_EQ(reader.readWholeFileAfter(8, -1), "Line 3");
// Known cursor position
EXPECT_EQ(reader.readWholeFileBefore(0, 0), "");
EXPECT_EQ(reader.readWholeFileAfter(0, 0), "Line 0\nLine 1\nLine 2\nLine 3");
EXPECT_EQ(reader.readWholeFileBefore(1, 0), "");
EXPECT_EQ(reader.readWholeFileAfter(1, 0), "Line 0\nLine 1\nLine 2\nLine 3");
EXPECT_EQ(reader.readWholeFileBefore(2, 0), "");
EXPECT_EQ(reader.readWholeFileAfter(2, 0), "Line 0\nLine 1\nLine 2\nLine 3");
EXPECT_EQ(reader.readWholeFileBefore(3, 0), "");
EXPECT_EQ(reader.readWholeFileAfter(3, 0), "Line 0\nLine 1\nLine 2\nLine 3");
EXPECT_EQ(reader.readWholeFileBefore(4, 0), "");
EXPECT_EQ(reader.readWholeFileAfter(4, 0), "Line 0\nLine 1\nLine 2\nLine 3");
EXPECT_EQ(reader.readWholeFileBefore(5, 0), "");
EXPECT_EQ(reader.readWholeFileAfter(5, 0), "Line 0\nLine 1\nLine 2\nLine 3");
EXPECT_EQ(reader.readWholeFileBefore(6, 0), "Line 0\n");
EXPECT_EQ(reader.readWholeFileAfter(6, 0), "Line 1\nLine 2\nLine 3");
EXPECT_EQ(reader.readWholeFileBefore(7, 0), "Line 0\nLine 1\n");
EXPECT_EQ(reader.readWholeFileAfter(7, 0), "Line 2\nLine 3");
EXPECT_EQ(reader.readWholeFileBefore(8, 0), "Line 0\nLine 1\nLine 2\n");
EXPECT_EQ(reader.readWholeFileAfter(8, 0), "Line 3");
EXPECT_EQ(reader.readWholeFileBefore(0, 1), "");
EXPECT_EQ(reader.readWholeFileAfter(0, 1), "Line 0\nLine 1\nLine 2\nLine 3");
EXPECT_EQ(reader.readWholeFileBefore(1, 1), "");
EXPECT_EQ(reader.readWholeFileAfter(1, 1), "Line 0\nLine 1\nLine 2\nLine 3");
EXPECT_EQ(reader.readWholeFileBefore(2, 1), "");
EXPECT_EQ(reader.readWholeFileAfter(2, 1), "Line 0\nLine 1\nLine 2\nLine 3");
EXPECT_EQ(reader.readWholeFileBefore(3, 1), "");
EXPECT_EQ(reader.readWholeFileAfter(3, 1), "Line 0\nLine 1\nLine 2\nLine 3");
EXPECT_EQ(reader.readWholeFileBefore(4, 1), "");
EXPECT_EQ(reader.readWholeFileAfter(4, 1), "Line 0\nLine 1\nLine 2\nLine 3");
EXPECT_EQ(reader.readWholeFileBefore(5, 1), "L");
EXPECT_EQ(reader.readWholeFileAfter(5, 1), "ine 0\nLine 1\nLine 2\nLine 3");
EXPECT_EQ(reader.readWholeFileBefore(6, 1), "Line 0\nL");
EXPECT_EQ(reader.readWholeFileAfter(6, 1), "ine 1\nLine 2\nLine 3");
EXPECT_EQ(reader.readWholeFileBefore(7, 1), "Line 0\nLine 1\nL");
EXPECT_EQ(reader.readWholeFileAfter(7, 1), "ine 2\nLine 3");
EXPECT_EQ(reader.readWholeFileBefore(8, 1), "Line 0\nLine 1\nLine 2\nL");
EXPECT_EQ(reader.readWholeFileAfter(8, 1), "ine 3");
}
TEST_F(DocumentContextReaderTest, testFindCopyrightSingleLine)
{
auto reader = createTestReader("/* Copyright (C) 2024 */\nCode line 0\nCode line 1");
auto info = reader.findCopyright();
ASSERT_TRUE(info.found);
EXPECT_EQ(info.startLine, 0);
EXPECT_EQ(info.endLine, 0);
}
TEST_F(DocumentContextReaderTest, testFindCopyrightMultiLine)
{
auto reader = createTestReader(
"/*\n * Copyright (C) 2024\n * \n * This file is part of QodeAssist.\n */\nCode line 0");
auto info = reader.findCopyright();
ASSERT_TRUE(info.found);
EXPECT_EQ(info.startLine, 0);
EXPECT_EQ(info.endLine, 4);
}
TEST_F(DocumentContextReaderTest, testFindCopyrightMultipleBlocks)
{
auto reader = createTestReader("/* Copyright 2023 */\n\n/* Copyright 2024 */\nCode");
auto info = reader.findCopyright();
ASSERT_TRUE(info.found);
EXPECT_EQ(info.startLine, 0);
EXPECT_EQ(info.endLine, 0);
}
TEST_F(DocumentContextReaderTest, testFindCopyrightNoCopyright)
{
auto reader = createTestReader("/* Just a comment */\nCode line 0");
auto info = reader.findCopyright();
ASSERT_TRUE(!info.found);
EXPECT_EQ(info.startLine, -1);
EXPECT_EQ(info.endLine, -1);
}
TEST_F(DocumentContextReaderTest, testGetContextBetween)
{
auto reader = createTestReader("Line 0\nLine 1\nLine 2\nLine 3\nLine 4");
EXPECT_EQ(reader.getContextBetween(2, -1, 0, -1), "");
EXPECT_EQ(reader.getContextBetween(0, -1, 0, -1), "Line 0");
EXPECT_EQ(reader.getContextBetween(1, -1, 1, -1), "Line 1");
EXPECT_EQ(reader.getContextBetween(1, 3, 1, 1), "");
EXPECT_EQ(reader.getContextBetween(1, 3, 1, 3), "");
EXPECT_EQ(reader.getContextBetween(1, 3, 1, 4), "e");
EXPECT_EQ(reader.getContextBetween(1, -1, 3, -1), "Line 1\nLine 2\nLine 3");
EXPECT_EQ(reader.getContextBetween(1, 2, 3, -1), "ne 1\nLine 2\nLine 3");
EXPECT_EQ(reader.getContextBetween(1, -1, 3, 2), "Line 1\nLine 2\nLi");
EXPECT_EQ(reader.getContextBetween(1, 2, 3, 2), "ne 1\nLine 2\nLi");
}
TEST_F(DocumentContextReaderTest, testPrepareContext)
{
auto reader = createTestReader("Line 0\nLine 1\nLine 2\nLine 3\nLine 4");
EXPECT_EQ(
reader.prepareContext(2, 3, *createSettingsForWholeFile()),
(QodeAssist::LLMCore::ContextData{
.prefix = "Line 0\nLine 1\nLin",
.suffix = "e 2\nLine 3\nLine 4",
.fileContext = "\n Language: (MIME: text/python) filepath: /path/to/file()\n\n"
"Recent Project Changes Context:\n "}));
EXPECT_EQ(
reader.prepareContext(2, 3, *createSettingsForLines(1, 1)),
(QodeAssist::LLMCore::ContextData{
.prefix = "Line 1\nLin",
.suffix = "e 2\nLine 3",
.fileContext = "\n Language: (MIME: text/python) filepath: /path/to/file()\n\n"
"Recent Project Changes Context:\n "}));
EXPECT_EQ(
reader.prepareContext(2, 3, *createSettingsForLines(2, 2)),
(QodeAssist::LLMCore::ContextData{
.prefix = "Line 0\nLine 1\nLin",
.suffix = "e 2\nLine 3\nLine 4",
.fileContext = "\n Language: (MIME: text/python) filepath: /path/to/file()\n\n"
"Recent Project Changes Context:\n "}));
}
#include "DocumentContextReaderTest.moc"

View File

@@ -1,112 +0,0 @@
// Copyright (C) 2024-2026 Petr Mironychev
// SPDX-License-Identifier: GPL-3.0-or-later
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
#include "completion/LLMSuggestion.hpp"
#include "TestUtils.hpp"
#include <gtest/gtest.h>
#include <QObject>
#include <QString>
using namespace QodeAssist;
class LLMSuggestionTest : public QObject, public testing::Test
{
Q_OBJECT
};
// Degenerate / no-op cases
TEST_F(LLMSuggestionTest, emptyRight)
{
EXPECT_EQ(LLMSuggestion::calculateReplaceLength("foo", ""), 0);
}
TEST_F(LLMSuggestionTest, noOverlap)
{
EXPECT_EQ(LLMSuggestion::calculateReplaceLength("foo", "bar"), 0);
}
TEST_F(LLMSuggestionTest, singleCharNoOverlap)
{
EXPECT_EQ(LLMSuggestion::calculateReplaceLength("a", "b"), 0);
}
// LCP: model echoed the existing right side as its own prefix
TEST_F(LLMSuggestionTest, lcpExtendsRight)
{
// cursor: int |myVariable ; suggestion echoes "myVar" then adds nothing.
// LCP=5, remainder "iable" not a closing tail -> replace 5.
EXPECT_EQ(LLMSuggestion::calculateReplaceLength("myVar", "myVariable"), 5);
}
TEST_F(LLMSuggestionTest, lcpPartialEngineCall)
{
// cursor: |engine.rootContext() ; suggestion: engine.load()
// LCP="engine." (7); remainder "rootContext()" is not closing-only -> keep LCP
EXPECT_EQ(LLMSuggestion::calculateReplaceLength("engine.load()", "engine.rootContext()"), 7);
}
// LCP + closing-only tail: extend to full right
TEST_F(LLMSuggestionTest, lcpThenClosingTailExtendsFull)
{
// cursor: QStringList |colors{}; ; suggestion: colors << "red"
// LCP=6 ("colors"); remainder "{};" is punctuation -> replace all (9)
EXPECT_EQ(LLMSuggestion::calculateReplaceLength("colors << \"red\"", "colors{};"), 9);
}
// LCP=0, right is closing-only, suggestion ends with a matching closer -> replace full right
TEST_F(LLMSuggestionTest, closingTailReplaceSemicolon)
{
EXPECT_EQ(LLMSuggestion::calculateReplaceLength("x;", ";"), 1);
}
TEST_F(LLMSuggestionTest, closingTailReplaceParen)
{
EXPECT_EQ(LLMSuggestion::calculateReplaceLength("arg1, arg2)", ")"), 1);
}
TEST_F(LLMSuggestionTest, closingTailReplaceBrackets)
{
EXPECT_EQ(LLMSuggestion::calculateReplaceLength("[0]", "];"), 2);
}
TEST_F(LLMSuggestionTest, closingTailReplaceBracesAndSemi)
{
// cursor: colors|{}; ; suggestion: = {"red"}
// LCP=0; right "{};" is closing-only; suggestion ends on "}" which is in right -> replace 3
EXPECT_EQ(LLMSuggestion::calculateReplaceLength("= {\"red\"}", "{};"), 3);
}
TEST_F(LLMSuggestionTest, closingTailWithLeadingSpace)
{
EXPECT_EQ(LLMSuggestion::calculateReplaceLength(" = {\"red\"}", "{};"), 3);
}
TEST_F(LLMSuggestionTest, closingTailEqualsSemi)
{
EXPECT_EQ(LLMSuggestion::calculateReplaceLength("= 5;", ";"), 1);
}
// LCP=0, right is closing-only but suggestion does not end on any of its chars -> leave right alone
TEST_F(LLMSuggestionTest, closingTailNoMatchingClose)
{
// right "};" closes; suggestion ends on '"' (not in close set) -> 0
EXPECT_EQ(LLMSuggestion::calculateReplaceLength("\"red\", \"green\"", "};"), 0);
}
// Right side has real code (not closing-only) and no LCP -> leave it alone
TEST_F(LLMSuggestionTest, realCodeRightNoLcp)
{
// cursor: int |myVar = 5; ; suggestion: myVar
// LCP=0, right " = 5;" is not closing-only (has '5') -> 0
EXPECT_EQ(LLMSuggestion::calculateReplaceLength("myVar", " = 5;"), 0);
}
// Trailing whitespace is not treated as a closer, suggestion has no closer -> leave alone
TEST_F(LLMSuggestionTest, trailingWhitespaceOnlyLeftAlone)
{
EXPECT_EQ(LLMSuggestion::calculateReplaceLength("code", " "), 0);
}
#include "LLMSuggestionTest.moc"

1746
tests/QodeAssistTest.cpp Normal file

File diff suppressed because it is too large Load Diff

130
tests/QodeAssistTest.hpp Normal file
View File

@@ -0,0 +1,130 @@
// Copyright (C) 2026 Petr Mironychev
// Copyright (C) 2025 Povilas Kanapickas <povilas@radix.lt>
// SPDX-License-Identifier: GPL-3.0-or-later
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
#pragma once
#include <QJsonObject>
#include <QObject>
#include <QSharedPointer>
#include "context/DocumentContextReader.hpp"
#include "settings/CodeCompletionSettings.hpp"
QT_BEGIN_NAMESPACE
class QTextDocument;
QT_END_NAMESPACE
namespace QodeAssist {
class QodeAssistTest final : public QObject
{
Q_OBJECT
private slots:
void testProcessTextEmpty();
void testProcessTextCommentsAroundCodeBlock();
void testProcessTextWithPlainCodeBlockNoNewline();
void testProcessTextWithPlainCodeBlockWithNewline();
void testProcessTextNoCommentsWithLanguageCodeBlock();
void testProcessTextNoCommentsWithPlainCodeBlockNoNewline();
void testProcessTextNoCommentsWithPlainCodeBlockWithNewline();
void testProcessTextWithMultipleCodeBlocksDifferentLanguages();
void testProcessTextWithMultipleCodeBlocksSameLanguage();
void testProcessTextWithMultiplePlainCodeBlocksWithNewline();
void testProcessTextWithMultiplePlainCodeBlocksWithoutNewline();
void testProcessTextWithEmptyLines();
void testProcessTextPlainCodeBlockWithNewlineWithEmptyLines();
void testProcessTextWithoutCodeBlock();
void testDetectLanguageFromLine();
void testDetectLanguageFromExtension();
void testCommentPrefixForDifferentLanguages();
void testHasCodeBlocks();
void testReplaceLengthEmptyRight();
void testReplaceLengthNoOverlap();
void testReplaceLengthSingleCharNoOverlap();
void testReplaceLengthLcpExtendsRight();
void testReplaceLengthLcpPartialEngineCall();
void testReplaceLengthLcpThenClosingTailExtendsFull();
void testReplaceLengthClosingTailReplaceSemicolon();
void testReplaceLengthClosingTailReplaceParen();
void testReplaceLengthClosingTailReplaceBrackets();
void testReplaceLengthClosingTailReplaceBracesAndSemi();
void testReplaceLengthClosingTailWithLeadingSpace();
void testReplaceLengthClosingTailEqualsSemi();
void testReplaceLengthClosingTailNoMatchingClose();
void testReplaceLengthRealCodeRightNoLcp();
void testReplaceLengthTrailingWhitespaceOnlyLeftAlone();
void testCacheControlBreakpointWithoutExtendedTtl();
void testCacheControlBreakpointWithExtendedTtl();
void testCacheControlSystemAsStringWrappedIntoArray();
void testCacheControlEmptySystemStringIsNotWrapped();
void testCacheControlSystemAsArrayMarksLastBlock();
void testCacheControlToolsLastEntryGetsCacheControl();
void testCacheControlSingleMessageHistorySkipped();
void testCacheControlHistoryBreakpointOnSecondToLastMessage();
void testCacheControlHistoryArrayContentMarksLastBlock();
void testCacheControlNoSystemNoToolsNoMessagesIsNoop();
void testCacheControlEmptyToolsArrayIsNoop();
void testGetLineText();
void testGetContext();
void testGetContextWithCopyright();
void testReadWholeFile();
void testReadWholeFileWithCopyright();
void testReadWholeFileWithMultilineCopyright();
void testFindCopyrightSingleLine();
void testFindCopyrightMultiLine();
void testFindCopyrightMultipleBlocks();
void testFindCopyrightNoCopyright();
void testGetContextBetween();
void testPrepareContext();
void testHistorySnapshotUsesCurrentVersion();
void testHistorySnapshotRoundTrip();
void testUnsupportedChatVersionIsRejected();
void testChatFileWithoutMessagesArrayIsRejected();
void testLegacyChatFileConvertsToHistory();
void testLegacyToolRowWithoutMetadataKeepsItsText();
void testChatRowsRoundTripThroughHistory();
void testCompressedChatShapeReloadsAsOneAssistantRow();
void testHistoryAppliesToChatModel();
void testAdjacentThinkingBlocksSurviveReload();
void testSessionAppendsUserTurnBeforeSending();
void testSessionStreamsTextThinkingAndTools();
void testSessionTrimsStreamedText();
void testSessionAbandoningTheTurnCancelsTheBackend();
void testSessionProjectionMatchesHistory();
void testSessionAccumulatesStreamedThinking();
void testSessionKeepsThinkingAfterToolContinuation();
void testSessionDropsPreToolTextWhenAsked();
void testSessionToolResultAppendsFileEditRow();
void testSessionIgnoresEchoedFileEditMarker();
void testSessionTruncationKeepsBlockOrder();
void testHistorySnapshotReportsUnreadableBlocks();
void testSessionUsageLandsOnTheTurn();
void testSessionIgnoresEventsFromOtherTurns();
void testSessionReportsFailureWithoutStartedTurn();
void testSessionIgnoresFailureFromAbandonedTurn();
void testSessionTruncateRowsRewritesHistory();
void testTurnContextCollectsPartsFromPorts();
void testTurnContextSkillCommandScanning();
void testTurnContextWithoutSkillsPort();
void testLinkedFilesHeaderSurvivesUnreadablePaths();
void testSystemPromptRenderingWithProject();
void testSystemPromptRenderingWithoutProject();
private:
Context::DocumentContextReader createReader(const QString &text);
QSharedPointer<Settings::CodeCompletionSettings> createSettingsForWholeFile();
QSharedPointer<Settings::CodeCompletionSettings> createSettingsForLines(
int linesBefore, int linesAfter);
static QJsonObject expectedEphemeral(bool extendedTtl);
};
} // namespace QodeAssist

View File

@@ -1,65 +0,0 @@
// Copyright (C) 2025 Povilas Kanapickas
// SPDX-License-Identifier: GPL-3.0-or-later
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
#include <iostream>
#include "llmcore/ContextData.hpp"
#include <QString>
QT_BEGIN_NAMESPACE
// gtest can't pick pretty printer when comparing QString
inline void PrintTo(const QString &value, ::std::ostream *out)
{
*out << '"' << value.toStdString() << '"';
}
QT_END_NAMESPACE
inline std::ostream &operator<<(std::ostream &out, const QString &value)
{
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 {
inline std::ostream &operator<<(std::ostream &out, const Message &value)
{
out << "Message{"
<< "role=" << value.role << "content=" << value.content << "}";
return out;
}
inline 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

View File

@@ -1,23 +0,0 @@
// Copyright (C) 2025 Povilas Kanapickas
// SPDX-License-Identifier: GPL-3.0-or-later
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
#include <gtest/gtest.h>
#include <QGuiApplication>
#include <QtGlobal>
void silenceQtWarningNoise(QtMsgType type, const QMessageLogContext &context, const QString &msg)
{
if (msg.startsWith("SOFT ASSERT") || msg.startsWith("Accessing MimeDatabase")) {
return;
}
qt_message_output(type, context, msg);
}
int main(int argc, char *argv[])
{
qInstallMessageHandler(silenceQtWarningNoise);
QGuiApplication application(argc, argv);
testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}