tsl-devkit/lsp-server/test/test_provider/interpreter_test.cppm

211 lines
7.7 KiB
C++

module;
export module lsp.test.provider.interpreter;
import std;
import lsp.codec.facade;
import lsp.core.dispacther;
import lsp.manager.manager_hub;
import lsp.protocol;
import lsp.provider.completion_item.resolve;
import lsp.provider.text_document.completion;
import lsp.scheduler.async_executor;
import lsp.test.framework;
import lsp.test.provider.fixtures;
export namespace lsp::test::provider
{
class InterpreterTests
{
public:
static void Register(TestRunner& runner);
private:
static TestResult TestInterpreterSystemLibraryCompletionResolve();
};
}
namespace lsp::test::provider
{
namespace
{
namespace codec = lsp::codec;
struct ProviderEnv
{
scheduler::AsyncExecutor scheduler{ 4 };
manager::ManagerHub hub{};
core::ExecutionContext context;
ProviderEnv()
: context([](core::ServerLifecycleEvent) {}, scheduler, hub)
{
hub.Initialize();
}
};
protocol::ResponseMessage ParseResponse(const std::string& json)
{
auto parsed = codec::Deserialize<protocol::ResponseMessage>(json);
assertTrue(parsed.has_value(), "Failed to deserialize response JSON");
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 test content");
protocol::Position result{};
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<protocol::uinteger>(marker.size());
}
return result;
}
std::filesystem::path ExpandUserPath(const std::string& value)
{
std::filesystem::path path = value;
if (!value.empty() && value[0] == '~')
{
const char* home = std::getenv("HOME");
assertTrue(home != nullptr, "HOME is not set; cannot expand '~'");
if (value.size() == 1)
{
path = home;
}
else if (value[1] == '/')
{
path = std::filesystem::path(home) / value.substr(2);
}
}
return path;
}
std::size_t CountTsfFiles(const std::filesystem::path& root)
{
std::size_t count = 0;
auto options = std::filesystem::directory_options::follow_directory_symlink |
std::filesystem::directory_options::skip_permission_denied;
for (const auto& entry : std::filesystem::recursive_directory_iterator(root, options))
{
if (!entry.is_regular_file())
{
continue;
}
auto ext = entry.path().extension().string();
std::transform(ext.begin(), ext.end(), ext.begin(), [](unsigned char ch) {
return static_cast<char>(std::tolower(ch));
});
if (ext == ".tsf")
{
++count;
}
}
return count;
}
}
void InterpreterTests::Register(TestRunner& runner)
{
runner.addTest("interpreter system library completion/resolve", TestInterpreterSystemLibraryCompletionResolve);
}
TestResult InterpreterTests::TestInterpreterSystemLibraryCompletionResolve()
{
TestResult result{ "", true, "ok" };
const auto& interpreter_root = InterpreterPath();
if (interpreter_root.empty())
{
return result;
}
auto interpreter_path = ExpandUserPath(interpreter_root);
auto funcext_path = interpreter_path / "funcext";
assertTrue(std::filesystem::exists(funcext_path), "Interpreter funcext path does not exist: " + funcext_path.string());
assertTrue(std::filesystem::is_directory(funcext_path), "Interpreter funcext path is not a directory: " + funcext_path.string());
const auto expected_tsf_count = CountTsfFiles(funcext_path);
ProviderEnv env;
env.hub.symbols().LoadSystemLibrary(funcext_path.string());
const auto system_tables = env.hub.symbols().GetSystemSymbolTables();
assertEqual(expected_tsf_count, system_tables.size(), "System library should index all .tsf files under funcext");
auto nonce = std::to_string(std::chrono::steady_clock::now().time_since_epoch().count());
auto doc_path = std::filesystem::temp_directory_path() / ("tsl_interpreter_completion_" + nonce + ".tsl");
auto uri = ToUri(doc_path);
std::string content = "unit InterpreterCompletion;\n"
"begin\n"
" m := new PageHel\n"
"end;\n";
protocol::DidOpenTextDocumentParams open_params;
open_params.textDocument.uri = uri;
open_params.textDocument.languageId = "tsl";
open_params.textDocument.version = 1;
open_params.textDocument.text = content;
env.hub.documents().OpenDocument(open_params);
protocol::CompletionParams completion_params;
completion_params.textDocument.uri = uri;
completion_params.position = FindPosition(content, "new PageHel", true);
protocol::RequestMessage completion_request;
completion_request.id = "c1";
completion_request.method = "textDocument/completion";
completion_request.params = codec::ToLSPAny(completion_params);
::lsp::provider::text_document::Completion completion_provider;
auto completion_response_json = completion_provider.ProvideResponse(completion_request, env.context);
auto completion_response = ParseResponse(completion_response_json);
assertTrue(completion_response.result.has_value(), "Completion should return result");
auto list = codec::FromLSPAny.template operator()<protocol::CompletionList>(*completion_response.result);
auto item_it = std::find_if(list.items.begin(), list.items.end(), [](const auto& item) {
return item.label == "PageHelperFreeMutex";
});
assertTrue(item_it != list.items.end(), "Completion should include PageHelperFreeMutex from system library");
protocol::RequestMessage resolve_request;
resolve_request.id = "r1";
resolve_request.method = "completionItem/resolve";
resolve_request.params = codec::ToLSPAny(*item_it);
::lsp::provider::completion_item::Resolve resolve_provider;
auto resolve_response_json = resolve_provider.ProvideResponse(resolve_request, env.context);
auto resolve_response = ParseResponse(resolve_response_json);
assertTrue(resolve_response.result.has_value(), "Resolve should return result");
auto resolved = codec::FromLSPAny.template operator()<protocol::CompletionItem>(*resolve_response.result);
assertTrue(resolved.insertText.has_value(), "Resolved completion item should include insertText snippet");
assertTrue(resolved.insertText->contains("PageHelperFreeMutex("), "Resolved snippet should include class name");
assertTrue(resolved.insertText->contains("${1:"), "Resolved snippet should include parameter placeholder");
return result;
}
}