🧪 test: add LSP json fixtures and provider suite
This commit is contained in:
@@ -0,0 +1,222 @@
|
||||
module;
|
||||
|
||||
export module lsp.test.provider.json_flow;
|
||||
|
||||
import std;
|
||||
|
||||
import lsp.test.framework;
|
||||
import lsp.provider.initialize.initialize;
|
||||
import lsp.provider.text_document.did_open;
|
||||
import lsp.provider.text_document.completion;
|
||||
import lsp.provider.completion_item.resolve;
|
||||
import lsp.provider.text_document.definition;
|
||||
import lsp.core.dispacther;
|
||||
import lsp.manager.manager_hub;
|
||||
import lsp.scheduler.async_executor;
|
||||
import lsp.protocol;
|
||||
import lsp.codec.facade;
|
||||
import lsp.test.provider.fixtures;
|
||||
|
||||
export namespace lsp::test::provider
|
||||
{
|
||||
class JsonFlowTests
|
||||
{
|
||||
public:
|
||||
static void Register(TestRunner& runner);
|
||||
|
||||
private:
|
||||
static TestResult TestInitializeCompletionResolveDefinition();
|
||||
};
|
||||
}
|
||||
|
||||
namespace lsp::test::provider
|
||||
{
|
||||
namespace
|
||||
{
|
||||
struct ProviderEnv
|
||||
{
|
||||
scheduler::AsyncExecutor scheduler{ 1 };
|
||||
manager::ManagerHub hub{};
|
||||
core::ExecutionContext context;
|
||||
|
||||
ProviderEnv()
|
||||
: context([](core::ServerLifecycleEvent) {}, scheduler, hub)
|
||||
{
|
||||
hub.Initialize();
|
||||
}
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
std::string SerializeOrThrow(const T& obj)
|
||||
{
|
||||
auto json = codec::Serialize(obj);
|
||||
assertTrue(json.has_value(), "Failed to serialize LSP JSON");
|
||||
return json.value();
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
T DeserializeOrThrow(const std::string& json)
|
||||
{
|
||||
auto parsed = codec::Deserialize<T>(json);
|
||||
assertTrue(parsed.has_value(), "Failed to deserialize LSP JSON");
|
||||
return parsed.value();
|
||||
}
|
||||
|
||||
protocol::ResponseMessage ParseResponse(const std::string& json)
|
||||
{
|
||||
auto parsed = codec::Deserialize<protocol::ResponseMessage>(json);
|
||||
assertTrue(parsed.has_value(), "Failed to deserialize response");
|
||||
return parsed.value();
|
||||
}
|
||||
|
||||
protocol::Position FindPosition(const std::string& content, const std::string& marker, bool after_marker)
|
||||
{
|
||||
auto pos = content.find(marker);
|
||||
assertTrue(pos != std::string::npos, "Marker not found in fixture");
|
||||
protocol::Position result{};
|
||||
result.line = 0;
|
||||
result.character = 0;
|
||||
for (std::size_t i = 0; i < pos; ++i)
|
||||
{
|
||||
if (content[i] == '\n')
|
||||
{
|
||||
result.line++;
|
||||
result.character = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
result.character++;
|
||||
}
|
||||
}
|
||||
if (after_marker)
|
||||
{
|
||||
result.character += static_cast<std::uint32_t>(marker.size());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::optional<protocol::CompletionItem> FindItem(const std::vector<protocol::CompletionItem>& items,
|
||||
const std::string& label)
|
||||
{
|
||||
auto it = std::find_if(items.begin(), items.end(), [&](const auto& item) {
|
||||
return item.label == label;
|
||||
});
|
||||
if (it == items.end())
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
return *it;
|
||||
}
|
||||
}
|
||||
|
||||
void JsonFlowTests::Register(TestRunner& runner)
|
||||
{
|
||||
runner.addTest("json flow initialize->completion->resolve->definition", TestInitializeCompletionResolveDefinition);
|
||||
}
|
||||
|
||||
TestResult JsonFlowTests::TestInitializeCompletionResolveDefinition()
|
||||
{
|
||||
TestResult result{ "", true, "ok" };
|
||||
ProviderEnv env;
|
||||
|
||||
auto workspace_uri = ToUri(FixturePath("workspace"));
|
||||
protocol::LSPArray folders;
|
||||
folders.emplace_back(protocol::LSPObject{
|
||||
{ "uri", workspace_uri },
|
||||
{ "name", "workspace" }
|
||||
});
|
||||
|
||||
protocol::LSPObject init_params;
|
||||
init_params["trace"] = protocol::string(protocol::TraceValueLiterals::Off);
|
||||
init_params["workspaceFolders"] = folders;
|
||||
|
||||
protocol::RequestMessage init_request;
|
||||
init_request.id = "init";
|
||||
init_request.method = "initialize";
|
||||
init_request.params = protocol::LSPAny(init_params);
|
||||
|
||||
auto init_json = SerializeOrThrow(init_request);
|
||||
auto parsed_init = DeserializeOrThrow<protocol::RequestMessage>(init_json);
|
||||
::lsp::provider::Initialize init_provider;
|
||||
auto init_response_json = init_provider.ProvideResponse(parsed_init, env.context);
|
||||
auto init_response = ParseResponse(init_response_json);
|
||||
assertTrue(init_response.result.has_value(), "Initialize should return result");
|
||||
env.scheduler.WaitAll();
|
||||
|
||||
auto path = FixturePath("main_unit.tsf");
|
||||
auto content = ReadTextFile(path);
|
||||
auto uri = ToUri(path);
|
||||
|
||||
protocol::LSPObject text_doc{
|
||||
{ "uri", uri },
|
||||
{ "languageId", "tsl" },
|
||||
{ "version", 1 },
|
||||
{ "text", content }
|
||||
};
|
||||
protocol::NotificationMessage open_message;
|
||||
open_message.method = "textDocument/didOpen";
|
||||
open_message.params = protocol::LSPAny(protocol::LSPObject{ { "textDocument", protocol::LSPAny(text_doc) } });
|
||||
|
||||
auto open_json = SerializeOrThrow(open_message);
|
||||
auto parsed_open = DeserializeOrThrow<protocol::NotificationMessage>(open_json);
|
||||
::lsp::provider::text_document::DidOpen open_provider;
|
||||
open_provider.HandleNotification(parsed_open, env.context);
|
||||
|
||||
auto completion_pos = FindPosition(content, "new Wid", true);
|
||||
protocol::LSPObject completion_params{
|
||||
{ "textDocument", protocol::LSPObject{ { "uri", uri } } },
|
||||
{ "position", protocol::LSPObject{
|
||||
{ "line", completion_pos.line },
|
||||
{ "character", completion_pos.character } } }
|
||||
};
|
||||
protocol::RequestMessage completion_request;
|
||||
completion_request.id = "c1";
|
||||
completion_request.method = "textDocument/completion";
|
||||
completion_request.params = protocol::LSPAny(completion_params);
|
||||
|
||||
auto completion_json = SerializeOrThrow(completion_request);
|
||||
auto parsed_completion = DeserializeOrThrow<protocol::RequestMessage>(completion_json);
|
||||
::lsp::provider::text_document::Completion completion_provider;
|
||||
auto completion_response_json = completion_provider.ProvideResponse(parsed_completion, env.context);
|
||||
auto completion_response = ParseResponse(completion_response_json);
|
||||
auto list = codec::FromLSPAny.template operator()<protocol::CompletionList>(completion_response.result.value());
|
||||
auto item = FindItem(list.items, "Widget");
|
||||
assertTrue(item.has_value(), "Expected Widget completion");
|
||||
|
||||
protocol::RequestMessage resolve_request;
|
||||
resolve_request.id = "r1";
|
||||
resolve_request.method = "completionItem/resolve";
|
||||
resolve_request.params = codec::ToLSPAny(*item);
|
||||
|
||||
auto resolve_json = SerializeOrThrow(resolve_request);
|
||||
auto parsed_resolve = DeserializeOrThrow<protocol::RequestMessage>(resolve_json);
|
||||
::lsp::provider::completion_item::Resolve resolve_provider;
|
||||
auto resolve_response_json = resolve_provider.ProvideResponse(parsed_resolve, env.context);
|
||||
auto resolve_response = ParseResponse(resolve_response_json);
|
||||
auto resolved = codec::FromLSPAny.template operator()<protocol::CompletionItem>(resolve_response.result.value());
|
||||
assertTrue(resolved.insertText.has_value(), "Resolved item should have insertText");
|
||||
|
||||
auto def_pos = FindPosition(content, "UnitFunc(1);", false);
|
||||
protocol::LSPObject definition_params{
|
||||
{ "textDocument", protocol::LSPObject{ { "uri", uri } } },
|
||||
{ "position", protocol::LSPObject{
|
||||
{ "line", def_pos.line },
|
||||
{ "character", def_pos.character } } }
|
||||
};
|
||||
protocol::RequestMessage def_request;
|
||||
def_request.id = "d1";
|
||||
def_request.method = "textDocument/definition";
|
||||
def_request.params = protocol::LSPAny(definition_params);
|
||||
|
||||
auto def_json = SerializeOrThrow(def_request);
|
||||
auto parsed_def = DeserializeOrThrow<protocol::RequestMessage>(def_json);
|
||||
::lsp::provider::text_document::Definition def_provider;
|
||||
auto def_response_json = def_provider.ProvideResponse(parsed_def, env.context);
|
||||
auto def_response = ParseResponse(def_response_json);
|
||||
auto location = codec::FromLSPAny.template operator()<protocol::Location>(def_response.result.value());
|
||||
|
||||
auto expected_pos = FindPosition(content, "function UnitFunc", false);
|
||||
assertTrue(location.range.start.line == expected_pos.line, "Definition should resolve in document");
|
||||
return result;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user