diff options
Diffstat (limited to 'lldb/unittests/ProtocolServer/ProtocolMCPServerTest.cpp')
| -rw-r--r-- | lldb/unittests/ProtocolServer/ProtocolMCPServerTest.cpp | 333 |
1 files changed, 0 insertions, 333 deletions
diff --git a/lldb/unittests/ProtocolServer/ProtocolMCPServerTest.cpp b/lldb/unittests/ProtocolServer/ProtocolMCPServerTest.cpp deleted file mode 100644 index 18112428950c..000000000000 --- a/lldb/unittests/ProtocolServer/ProtocolMCPServerTest.cpp +++ /dev/null @@ -1,333 +0,0 @@ -//===-- ProtocolServerMCPTest.cpp -----------------------------------------===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// - -#include "Plugins/Platform/MacOSX/PlatformRemoteMacOSX.h" -#include "Plugins/Protocol/MCP/ProtocolServerMCP.h" -#include "TestingSupport/Host/JSONTransportTestUtilities.h" -#include "TestingSupport/SubsystemRAII.h" -#include "lldb/Core/Debugger.h" -#include "lldb/Core/ProtocolServer.h" -#include "lldb/Host/FileSystem.h" -#include "lldb/Host/HostInfo.h" -#include "lldb/Host/JSONTransport.h" -#include "lldb/Host/MainLoop.h" -#include "lldb/Host/MainLoopBase.h" -#include "lldb/Host/Socket.h" -#include "lldb/Host/common/TCPSocket.h" -#include "lldb/Protocol/MCP/MCPError.h" -#include "lldb/Protocol/MCP/Protocol.h" -#include "llvm/Support/Error.h" -#include "llvm/Support/JSON.h" -#include "llvm/Testing/Support/Error.h" -#include "gmock/gmock.h" -#include "gtest/gtest.h" -#include <chrono> -#include <condition_variable> -#include <mutex> - -using namespace llvm; -using namespace lldb; -using namespace lldb_private; -using namespace lldb_protocol::mcp; -using testing::_; - -namespace { -class TestProtocolServerMCP : public lldb_private::mcp::ProtocolServerMCP { -public: - using ProtocolServerMCP::AddNotificationHandler; - using ProtocolServerMCP::AddRequestHandler; - using ProtocolServerMCP::AddResourceProvider; - using ProtocolServerMCP::AddTool; - using ProtocolServerMCP::GetSocket; - using ProtocolServerMCP::ProtocolServerMCP; -}; - -using Message = typename Transport<Request, Response, Notification>::Message; - -class TestJSONTransport final - : public lldb_private::JSONRPCTransport<Request, Response, Notification> { -public: - using JSONRPCTransport::JSONRPCTransport; - - void Log(llvm::StringRef message) override { - log_messages.emplace_back(message); - } - - std::vector<std::string> log_messages; -}; - -/// Test tool that returns it argument as text. -class TestTool : public Tool { -public: - using Tool::Tool; - - llvm::Expected<TextResult> Call(const ToolArguments &args) override { - std::string argument; - if (const json::Object *args_obj = - std::get<json::Value>(args).getAsObject()) { - if (const json::Value *s = args_obj->get("arguments")) { - argument = s->getAsString().value_or(""); - } - } - - TextResult text_result; - text_result.content.emplace_back(TextContent{{argument}}); - return text_result; - } -}; - -class TestResourceProvider : public ResourceProvider { - using ResourceProvider::ResourceProvider; - - std::vector<Resource> GetResources() const override { - std::vector<Resource> resources; - - Resource resource; - resource.uri = "lldb://foo/bar"; - resource.name = "name"; - resource.description = "description"; - resource.mimeType = "application/json"; - - resources.push_back(resource); - return resources; - } - - llvm::Expected<ResourceResult> - ReadResource(llvm::StringRef uri) const override { - if (uri != "lldb://foo/bar") - return llvm::make_error<UnsupportedURI>(uri.str()); - - ResourceContents contents; - contents.uri = "lldb://foo/bar"; - contents.mimeType = "application/json"; - contents.text = "foobar"; - - ResourceResult result; - result.contents.push_back(contents); - return result; - } -}; - -/// Test tool that returns an error. -class ErrorTool : public Tool { -public: - using Tool::Tool; - - llvm::Expected<TextResult> Call(const ToolArguments &args) override { - return llvm::createStringError("error"); - } -}; - -/// Test tool that fails but doesn't return an error. -class FailTool : public Tool { -public: - using Tool::Tool; - - llvm::Expected<TextResult> Call(const ToolArguments &args) override { - TextResult text_result; - text_result.content.emplace_back(TextContent{{"failed"}}); - text_result.isError = true; - return text_result; - } -}; - -class ProtocolServerMCPTest : public ::testing::Test { -public: - SubsystemRAII<FileSystem, HostInfo, PlatformRemoteMacOSX, Socket> subsystems; - DebuggerSP m_debugger_sp; - - lldb::IOObjectSP m_io_sp; - std::unique_ptr<TestJSONTransport> m_transport_up; - std::unique_ptr<TestProtocolServerMCP> m_server_up; - MainLoop loop; - MockMessageHandler<Request, Response, Notification> message_handler; - - static constexpr llvm::StringLiteral k_localhost = "localhost"; - - llvm::Error Write(llvm::StringRef message) { - std::string output = llvm::formatv("{0}\n", message).str(); - size_t bytes_written = output.size(); - return m_io_sp->Write(output.data(), bytes_written).takeError(); - } - - void CloseInput() { - EXPECT_THAT_ERROR(m_io_sp->Close().takeError(), Succeeded()); - } - - /// Run the transport MainLoop and return any messages received. - llvm::Error - Run(std::chrono::milliseconds timeout = std::chrono::milliseconds(200)) { - loop.AddCallback([](MainLoopBase &loop) { loop.RequestTermination(); }, - timeout); - auto handle = m_transport_up->RegisterMessageHandler(loop, message_handler); - if (!handle) - return handle.takeError(); - - return loop.Run().takeError(); - } - - void SetUp() override { - // Create a debugger. - ArchSpec arch("arm64-apple-macosx-"); - Platform::SetHostPlatform( - PlatformRemoteMacOSX::CreateInstance(true, &arch)); - m_debugger_sp = Debugger::CreateInstance(); - - // Create & start the server. - ProtocolServer::Connection connection; - connection.protocol = Socket::SocketProtocol::ProtocolTcp; - connection.name = llvm::formatv("{0}:0", k_localhost).str(); - m_server_up = std::make_unique<TestProtocolServerMCP>(); - m_server_up->AddTool(std::make_unique<TestTool>("test", "test tool")); - m_server_up->AddResourceProvider(std::make_unique<TestResourceProvider>()); - ASSERT_THAT_ERROR(m_server_up->Start(connection), llvm::Succeeded()); - - // Connect to the server over a TCP socket. - auto connect_socket_up = std::make_unique<TCPSocket>(true); - ASSERT_THAT_ERROR(connect_socket_up - ->Connect(llvm::formatv("{0}:{1}", k_localhost, - static_cast<TCPSocket *>( - m_server_up->GetSocket()) - ->GetLocalPortNumber()) - .str()) - .ToError(), - llvm::Succeeded()); - - // Set up JSON transport for the client. - m_io_sp = std::move(connect_socket_up); - m_transport_up = std::make_unique<TestJSONTransport>(m_io_sp, m_io_sp); - } - - void TearDown() override { - // Stop the server. - ASSERT_THAT_ERROR(m_server_up->Stop(), llvm::Succeeded()); - } -}; - -} // namespace - -TEST_F(ProtocolServerMCPTest, Initialization) { - llvm::StringLiteral request = - R"json({"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"lldb-unit","version":"0.1.0"}},"jsonrpc":"2.0","id":1})json"; - llvm::StringLiteral response = - R"json({"id":1,"jsonrpc":"2.0","result":{"capabilities":{"resources":{"listChanged":false,"subscribe":false},"tools":{"listChanged":true}},"protocolVersion":"2024-11-05","serverInfo":{"name":"lldb-mcp","version":"0.1.0"}}})json"; - - ASSERT_THAT_ERROR(Write(request), Succeeded()); - llvm::Expected<Response> expected_resp = json::parse<Response>(response); - ASSERT_THAT_EXPECTED(expected_resp, llvm::Succeeded()); - EXPECT_CALL(message_handler, Received(*expected_resp)); - EXPECT_THAT_ERROR(Run(), Succeeded()); -} - -TEST_F(ProtocolServerMCPTest, ToolsList) { - llvm::StringLiteral request = - R"json({"method":"tools/list","params":{},"jsonrpc":"2.0","id":"one"})json"; - - ToolDefinition test_tool; - test_tool.name = "test"; - test_tool.description = "test tool"; - test_tool.inputSchema = json::Object{{"type", "object"}}; - - ToolDefinition lldb_command_tool; - lldb_command_tool.description = "Run an lldb command."; - lldb_command_tool.name = "lldb_command"; - lldb_command_tool.inputSchema = json::Object{ - {"type", "object"}, - {"properties", - json::Object{{"arguments", json::Object{{"type", "string"}}}, - {"debugger_id", json::Object{{"type", "number"}}}}}, - {"required", json::Array{"debugger_id"}}}; - Response response; - response.id = "one"; - response.result = json::Object{ - {"tools", - json::Array{std::move(test_tool), std::move(lldb_command_tool)}}, - }; - - ASSERT_THAT_ERROR(Write(request), llvm::Succeeded()); - EXPECT_CALL(message_handler, Received(response)); - EXPECT_THAT_ERROR(Run(), Succeeded()); -} - -TEST_F(ProtocolServerMCPTest, ResourcesList) { - llvm::StringLiteral request = - R"json({"method":"resources/list","params":{},"jsonrpc":"2.0","id":2})json"; - llvm::StringLiteral response = - R"json({"id":2,"jsonrpc":"2.0","result":{"resources":[{"description":"description","mimeType":"application/json","name":"name","uri":"lldb://foo/bar"}]}})json"; - - ASSERT_THAT_ERROR(Write(request), llvm::Succeeded()); - llvm::Expected<Response> expected_resp = json::parse<Response>(response); - ASSERT_THAT_EXPECTED(expected_resp, llvm::Succeeded()); - EXPECT_CALL(message_handler, Received(*expected_resp)); - EXPECT_THAT_ERROR(Run(), Succeeded()); -} - -TEST_F(ProtocolServerMCPTest, ToolsCall) { - llvm::StringLiteral request = - R"json({"method":"tools/call","params":{"name":"test","arguments":{"arguments":"foo","debugger_id":0}},"jsonrpc":"2.0","id":11})json"; - llvm::StringLiteral response = - R"json({"id":11,"jsonrpc":"2.0","result":{"content":[{"text":"foo","type":"text"}],"isError":false}})json"; - - ASSERT_THAT_ERROR(Write(request), llvm::Succeeded()); - llvm::Expected<Response> expected_resp = json::parse<Response>(response); - ASSERT_THAT_EXPECTED(expected_resp, llvm::Succeeded()); - EXPECT_CALL(message_handler, Received(*expected_resp)); - EXPECT_THAT_ERROR(Run(), Succeeded()); -} - -TEST_F(ProtocolServerMCPTest, ToolsCallError) { - m_server_up->AddTool(std::make_unique<ErrorTool>("error", "error tool")); - - llvm::StringLiteral request = - R"json({"method":"tools/call","params":{"name":"error","arguments":{"arguments":"foo","debugger_id":0}},"jsonrpc":"2.0","id":11})json"; - llvm::StringLiteral response = - R"json({"error":{"code":-32603,"message":"error"},"id":11,"jsonrpc":"2.0"})json"; - - ASSERT_THAT_ERROR(Write(request), llvm::Succeeded()); - llvm::Expected<Response> expected_resp = json::parse<Response>(response); - ASSERT_THAT_EXPECTED(expected_resp, llvm::Succeeded()); - EXPECT_CALL(message_handler, Received(*expected_resp)); - EXPECT_THAT_ERROR(Run(), Succeeded()); -} - -TEST_F(ProtocolServerMCPTest, ToolsCallFail) { - m_server_up->AddTool(std::make_unique<FailTool>("fail", "fail tool")); - - llvm::StringLiteral request = - R"json({"method":"tools/call","params":{"name":"fail","arguments":{"arguments":"foo","debugger_id":0}},"jsonrpc":"2.0","id":11})json"; - llvm::StringLiteral response = - R"json({"id":11,"jsonrpc":"2.0","result":{"content":[{"text":"failed","type":"text"}],"isError":true}})json"; - - ASSERT_THAT_ERROR(Write(request), llvm::Succeeded()); - llvm::Expected<Response> expected_resp = json::parse<Response>(response); - ASSERT_THAT_EXPECTED(expected_resp, llvm::Succeeded()); - EXPECT_CALL(message_handler, Received(*expected_resp)); - EXPECT_THAT_ERROR(Run(), Succeeded()); -} - -TEST_F(ProtocolServerMCPTest, NotificationInitialized) { - bool handler_called = false; - std::condition_variable cv; - std::mutex mutex; - - m_server_up->AddNotificationHandler( - "notifications/initialized", [&](const Notification ¬ification) { - { - std::lock_guard<std::mutex> lock(mutex); - handler_called = true; - } - cv.notify_all(); - }); - llvm::StringLiteral request = - R"json({"method":"notifications/initialized","jsonrpc":"2.0"})json"; - - ASSERT_THAT_ERROR(Write(request), llvm::Succeeded()); - - std::unique_lock<std::mutex> lock(mutex); - cv.wait(lock, [&] { return handler_called; }); -} |
