🐛 fix(core): enforce strict LSP lifecycle

This commit is contained in:
csh
2026-07-13 21:35:40 +08:00
parent 5edd4b3637
commit 06a0da5124
21 changed files with 555 additions and 664 deletions
+6 -3
View File
@@ -20,6 +20,7 @@ set(SOURCES
main.cc
test_main.cppm
../test_lsp_any/test_framework.cppm
core_server_fixture.cppm
fixtures.cppm
completion_test.cppm
json_flow_test.cppm
@@ -49,6 +50,7 @@ target_sources(
${CMAKE_CURRENT_SOURCE_DIR}/../../src
FILES ${CMAKE_CURRENT_SOURCE_DIR}/test_main.cppm
${CMAKE_CURRENT_SOURCE_DIR}/../test_lsp_any/test_framework.cppm
${CMAKE_CURRENT_SOURCE_DIR}/core_server_fixture.cppm
${CMAKE_CURRENT_SOURCE_DIR}/fixtures.cppm
${CMAKE_CURRENT_SOURCE_DIR}/completion_test.cppm
${CMAKE_CURRENT_SOURCE_DIR}/json_flow_test.cppm
@@ -62,10 +64,14 @@ target_sources(
../../src/bridge/spdlog.cppm
../../src/bridge/taskflow.cppm
../../src/bridge/tree_sitter.cppm
../../src/bridge/win32_stdio.cppm
../../src/utils/args_parser.cppm
../../src/utils/string.cppm
../../src/utils/text_coordinates.cppm
../../src/core/dispatcher.cppm
../../src/core/server.cppm
../../src/scheduler/async_executor.cppm
../../src/manager/bootstrap.cppm
../../src/manager/event_bus.cppm
../../src/manager/events.cppm
../../src/manager/detail/text_document.cppm
@@ -140,9 +146,6 @@ target_sources(
../../src/provider/completion_item/resolve.cppm
../../src/provider/initialize/initialize.cppm
../../src/provider/initialized/initialized.cppm
../../src/provider/shutdown/shutdown.cppm
../../src/provider/exit/exit.cppm
../../src/provider/cancel_request/cancel_request.cppm
../../src/provider/trace/set_trace.cppm
../../src/provider/client/register_capability.cppm
../../src/provider/client/unregister_capability.cppm
@@ -69,7 +69,7 @@ namespace lsp::test::provider
core::ExecutionContext context;
ProviderEnv()
: context([](core::ServerLifecycleEvent) {}, scheduler, hub)
: context(scheduler, hub)
{
hub.Initialize();
}
@@ -0,0 +1,56 @@
module;
export module lsp.test.provider.core_server_fixture;
import spdlog;
import std;
import lsp.codec.facade;
import lsp.core.server;
import lsp.protocol;
import lsp.provider.base.interface;
export namespace lsp::test::provider
{
int RunCoreServerFixture();
}
namespace lsp::test::provider
{
class FixtureInitialize final : public core::IRequestProvider
{
public:
std::string GetMethod() const override
{
return "initialize";
}
std::string GetProviderName() const override
{
return "FixtureInitialize";
}
std::string ProvideResponse(const protocol::RequestMessage& request,
core::ExecutionContext&) override
{
protocol::ResponseMessage response;
response.id = request.id;
response.result = protocol::LSPAny(protocol::LSPObject{});
return codec::Serialize(response).value();
}
};
int RunCoreServerFixture()
{
spdlog::set_level(spdlog::level::off);
core::LspServer server(
std::cin,
std::cout,
[](core::RequestDispatcher& dispatcher) {
dispatcher.RegisterRequestProvider(std::make_shared<FixtureInitialize>());
},
2,
"");
return server.Run();
}
}
@@ -38,7 +38,7 @@ namespace lsp::test::provider
core::ExecutionContext context;
ProviderEnv()
: context([](core::ServerLifecycleEvent) {}, scheduler, hub)
: context(scheduler, hub)
{
hub.Initialize();
}
@@ -39,7 +39,7 @@ namespace lsp::test::provider
core::ExecutionContext context;
ProviderEnv()
: context([](core::ServerLifecycleEvent) {}, scheduler, hub)
: context(scheduler, hub)
{
hub.Initialize();
}
@@ -207,4 +207,3 @@ namespace lsp::test::provider
return result;
}
}
@@ -40,7 +40,7 @@ namespace lsp::test::provider
core::ExecutionContext context;
ProviderEnv()
: context([](core::ServerLifecycleEvent) {}, scheduler, hub)
: context(scheduler, hub)
{
hub.Initialize();
}
@@ -51,13 +51,11 @@ namespace lsp::test::provider
{
scheduler::async_executor::AsyncExecutor scheduler{ 1 };
manager::ManagerHub hub{};
core::RequestDispatcher dispatcher{};
core::RequestDispatcher dispatcher{ scheduler, hub };
ProviderEnv()
{
hub.Initialize();
dispatcher.SetRequestScheduler(&scheduler);
dispatcher.SetManagerHub(&hub);
provider::RegisterAllProviders(dispatcher);
}
};
@@ -77,10 +77,7 @@ import lsp.provider.workspace.will_delete_files;
import lsp.provider.workspace.will_rename_files;
import lsp.provider.workspace_symbol.resolve;
import lsp.provider.text_document.publish_diagnostics;
import lsp.provider.shutdown.shutdown;
import lsp.provider.cancel_request.cancel_request;
import lsp.provider.trace.set_trace;
import lsp.provider.exit.exit;
import lsp.core.dispatcher;
import lsp.manager.manager_hub;
import lsp.manager.symbol;
@@ -160,13 +157,8 @@ export namespace lsp::test::provider
static TestResult TestExecuteCommandProvider();
static TestResult TestWillFileOperationsProviders();
static TestResult TestWorkspaceSymbolResolveProvider();
static TestResult TestShutdownProvider();
static TestResult TestCancelRequestProvider();
static TestResult TestSetTraceProvider();
static TestResult TestExitProvider();
};
int RunExitProviderChild();
}
namespace lsp::test::provider
@@ -175,13 +167,12 @@ namespace lsp::test::provider
{
struct ProviderEnv
{
std::vector<core::ServerLifecycleEvent> events;
scheduler::async_executor::AsyncExecutor scheduler{ 1 };
manager::ManagerHub hub{};
core::ExecutionContext context;
ProviderEnv()
: context([this](core::ServerLifecycleEvent event) { events.push_back(event); }, scheduler, hub)
: context(scheduler, hub)
{
hub.Initialize();
}
@@ -365,10 +356,7 @@ namespace lsp::test::provider
runner.addTest("workspace executeCommand provider", TestExecuteCommandProvider);
runner.addTest("workspace will file operations providers", TestWillFileOperationsProviders);
runner.addTest("workspaceSymbol/resolve provider", TestWorkspaceSymbolResolveProvider);
runner.addTest("shutdown provider", TestShutdownProvider);
runner.addTest("cancel request provider", TestCancelRequestProvider);
runner.addTest("setTrace provider", TestSetTraceProvider);
runner.addTest("exit provider", TestExitProvider);
}
TestResult ProviderMiscTests::TestInitializeProvider()
@@ -434,8 +422,6 @@ namespace lsp::test::provider
});
assertTrue(found_workspace, "Workspace symbols should be indexed");
assertTrue(!env.events.empty(), "Initialize should emit lifecycle event");
assertTrue(env.events.back() == core::ServerLifecycleEvent::kInitialized, "Initialize should emit initialized");
return result;
}
@@ -2233,7 +2219,6 @@ namespace lsp::test::provider
const auto& stored_obj = stored.Get<protocol::LSPObject>();
assertTrue(stored_obj.contains("tsl"), "stored settings should include tsl section");
assertTrue(env.events.empty(), "didChangeConfiguration should not trigger lifecycle events");
return result;
}
@@ -2761,7 +2746,6 @@ namespace lsp::test::provider
provider.HandleNotification(notification, env.context);
}
assertTrue(env.events.empty(), "window message notifications should not trigger lifecycle events");
return result;
}
@@ -2781,7 +2765,6 @@ namespace lsp::test::provider
::lsp::provider::telemetry::Event provider;
provider.HandleNotification(notification, env.context);
assertTrue(env.events.empty(), "telemetry/event should not trigger lifecycle events");
return result;
}
@@ -2816,7 +2799,6 @@ namespace lsp::test::provider
::lsp::provider::text_document::PublishDiagnostics provider;
provider.HandleNotification(notification, env.context);
assertTrue(env.events.empty(), "publishDiagnostics should not trigger lifecycle events");
return result;
}
@@ -3396,63 +3378,6 @@ namespace lsp::test::provider
return result;
}
TestResult ProviderMiscTests::TestShutdownProvider()
{
TestResult result{ "", true, "ok" };
ProviderEnv env;
auto path = FixturePath("rename_case.tsl");
auto content = ReadTextFile(path);
auto uri = ToUri(path);
OpenDocument(env.hub, uri, content, 1);
protocol::RequestMessage request;
request.id = "shutdown";
request.method = "shutdown";
::lsp::provider::Shutdown provider;
auto json = provider.ProvideResponse(request, env.context);
auto response = ParseResponse(json);
assertTrue(!response.error.has_value(), "Shutdown should not return error");
assertTrue(env.events.size() >= 1, "Shutdown should emit lifecycle event");
assertTrue(env.events.back() == core::ServerLifecycleEvent::kShuttingDown, "Shutdown should emit shutting down");
assertFalse(env.hub.documents().GetContent(uri).has_value(), "Shutdown should clear documents");
return result;
}
TestResult ProviderMiscTests::TestCancelRequestProvider()
{
TestResult result{ "", true, "ok" };
ProviderEnv env;
std::atomic<bool> started{ false };
env.scheduler.Submit("cancel_me", [&started](std::stop_token) -> std::optional<std::string> {
started.store(true);
std::this_thread::sleep_for(std::chrono::milliseconds(200));
return std::string("done");
});
while (!started.load())
{
std::this_thread::sleep_for(std::chrono::milliseconds(5));
}
protocol::CancelParams params;
params.id = std::string("cancel_me");
protocol::NotificationMessage notification;
notification.method = "$/cancelRequest";
notification.params = codec::ToLSPAny(params);
::lsp::provider::CancelRequest provider;
provider.HandleNotification(notification, env.context);
env.scheduler.WaitAll();
auto stats = env.scheduler.GetStatistics();
assertEqual(std::size_t(1), static_cast<std::size_t>(stats.cancelled),
"CancelRequest should mark task cancelled");
return result;
}
TestResult ProviderMiscTests::TestSetTraceProvider()
{
TestResult result{ "", true, "ok" };
@@ -3484,24 +3409,4 @@ namespace lsp::test::provider
return result;
}
TestResult ProviderMiscTests::TestExitProvider()
{
TestResult result{ "", true, "ok" };
auto exe = ExecutablePath();
assertTrue(!exe.empty(), "ExecutablePath should be set");
std::string command = "\"" + exe + "\" --exit-provider";
int code = std::system(command.c_str());
assertEqual(0, code, "Exit should return code 0");
return result;
}
int RunExitProviderChild()
{
ProviderEnv env;
::lsp::provider::Exit provider;
protocol::NotificationMessage notification;
notification.method = "exit";
provider.HandleNotification(notification, env.context);
return 1;
}
}
@@ -12,16 +12,13 @@ import lsp.manager.manager_hub;
import lsp.scheduler.async_executor;
import lsp.test.provider.fixtures;
import lsp.provider.cancel_request.cancel_request;
import lsp.provider.code_action.resolve;
import lsp.provider.code_lens.resolve;
import lsp.provider.completion_item.resolve;
import lsp.provider.document_link.resolve;
import lsp.provider.exit.exit;
import lsp.provider.initialize.initialize;
import lsp.provider.initialized.initialized;
import lsp.provider.inlay_hint.resolve;
import lsp.provider.shutdown.shutdown;
import lsp.provider.trace.set_trace;
import lsp.provider.call_hierarchy.incoming_calls;
import lsp.provider.call_hierarchy.outgoing_calls;
@@ -118,7 +115,7 @@ namespace lsp::test::provider
core::ExecutionContext context;
ProviderEnv()
: context([](core::ServerLifecycleEvent) {}, scheduler, hub)
: context(scheduler, hub)
{
hub.Initialize();
}
@@ -174,7 +171,6 @@ namespace lsp::test::provider
TestResult result{ "", true, "ok" };
CheckProviderMetadata<provider::Initialize>("initialize", "Initialize");
CheckProviderMetadata<provider::Shutdown>("shutdown", "Shutdown");
CheckProviderMetadata<provider::completion_item::Resolve>("completionItem/resolve", "CompletionItemResolve");
CheckProviderMetadata<provider::text_document::Completion>("textDocument/completion", "TextDocumentCompletion");
CheckProviderMetadata<provider::text_document::Definition>("textDocument/definition", "TextDocumentDefinition");
@@ -273,8 +269,6 @@ namespace lsp::test::provider
CheckProviderMetadata<provider::workspace::DidDeleteFiles>("workspace/didDeleteFiles", "WorkspaceDidDeleteFiles");
CheckProviderMetadata<provider::workspace::DidRenameFiles>("workspace/didRenameFiles", "WorkspaceDidRenameFiles");
CheckProviderMetadata<provider::Initialized>("initialized", "Initialized");
CheckProviderMetadata<provider::Exit>("exit", "Exit");
CheckProviderMetadata<provider::CancelRequest>("$/cancelRequest", "CancelRequest");
CheckProviderMetadata<provider::SetTrace>("$/setTrace", "SetTrace");
CheckProviderMetadata<provider::text_document::DidOpen>("textDocument/didOpen", "TextDocumentDidOpen");
CheckProviderMetadata<provider::text_document::DidChange>("textDocument/didChange", "TextDocumentDidChange");
@@ -287,7 +281,6 @@ namespace lsp::test::provider
{
TestResult result{ "", true, "ok" };
CheckRequestResponse<provider::Shutdown>();
CheckRequestResponse<provider::completion_item::Resolve>();
CheckRequestResponse<provider::text_document::Completion>();
CheckRequestResponse<provider::text_document::Definition>();
@@ -357,7 +350,6 @@ namespace lsp::test::provider
TestResult result{ "", true, "ok" };
CheckNotificationHandler<provider::Initialized>(std::nullopt);
CheckNotificationHandler<provider::Exit>(std::nullopt);
CheckNotificationHandler<provider::workspace::DidChangeConfiguration>(std::nullopt);
CheckNotificationHandler<provider::workspace::DidChangeWatchedFiles>(std::nullopt);
CheckNotificationHandler<provider::workspace::DidChangeWorkspaceFolders>(std::nullopt);
+3 -2
View File
@@ -6,6 +6,7 @@ import std;
import lsp.test.framework;
import lsp.test.provider.completion;
import lsp.test.provider.core_server_fixture;
import lsp.test.provider.definitions;
import lsp.test.provider.interpreter;
import lsp.test.provider.json_flow;
@@ -26,9 +27,9 @@ export int Run(int argc, char** argv)
{
std::string_view arg(argv[i]);
if (arg == "--exit-provider")
if (arg == "--core-server-fixture")
{
return lsp::test::provider::RunExitProviderChild();
return lsp::test::provider::RunCoreServerFixture();
}
constexpr std::string_view kInterpreterPrefix = "--interpreter=";