diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8fa18c93f..8cbed4d7d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -131,6 +131,22 @@ jobs: - uses: julia-actions/julia-processcoverage@v1 - uses: codecov/codecov-action@v5 + LSP: + name: Test LSP.jl + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: julia-actions/setup-julia@v1 + with: + version: "1.12" # most stable version + arch: x64 + - uses: julia-actions/cache@v2 + - name: test LSP + shell: julia --color=yes --project=. {0} # this is necessary for the next command to work on Windows + run: 'using Pkg; Pkg.activate(normpath(pwd(), "LSP")); Pkg.instantiate(); Pkg.test(; coverage=true)' + - uses: julia-actions/julia-processcoverage@v1 + - uses: codecov/codecov-action@v5 + test_runserver: name: Test runserver.jl runs-on: ubuntu-latest diff --git a/LSP/Project.toml b/LSP/Project.toml index 5cc4d8de8..e508a4c77 100644 --- a/LSP/Project.toml +++ b/LSP/Project.toml @@ -3,10 +3,15 @@ uuid = "880dcf91-6fde-4251-87fc-bfd84012291a" version = "0.1.0" authors = ["Shuhei Kadowaki "] +[workspace] +projects = ["test"] + [deps] JSON3 = "0f8b85d8-7281-11e9-16c2-39a750bddbf1" +PrecompileTools = "aea7be01-6a6a-4083-8856-8a6e6704d82a" StructTypes = "856f2bd8-1eba-4b0a-8007-ebc267875bd4" [compat] JSON3 = "1.14.3" +PrecompileTools = "1.3.3" StructTypes = "1.11.0" diff --git a/LSP/src/LSP.jl b/LSP/src/LSP.jl index 2274d0b74..09f7bf1c3 100644 --- a/LSP/src/LSP.jl +++ b/LSP/src/LSP.jl @@ -46,6 +46,8 @@ baremodule Communication export Endpoint, send end +include("precompile.jl") + for name in exports Core.eval(@__MODULE__, Expr(:export, name)) end diff --git a/LSP/src/precompile.jl b/LSP/src/precompile.jl new file mode 100644 index 000000000..d509333d3 --- /dev/null +++ b/LSP/src/precompile.jl @@ -0,0 +1,65 @@ +function test_roundtrip(f, s::AbstractString, Typ) + x = JSON3.read(s, Typ) + f(x) + s′ = to_lsp_json(x) + x′ = JSON3.read(s′, Typ) + f(x′) +end + +function test_roundtrip(f, s::AbstractString) + x = to_lsp_object(s) + f(x) + s′ = to_lsp_json(x) + x′ = to_lsp_object(s′) + f(x′) +end + +using PrecompileTools + +@setup_workload let + uri = LSP.URIs2.filepath2uri(abspath(@__FILE__)) + @compile_workload let + test_roundtrip("""{ + "jsonrpc": "2.0", + "id": 0, + "method": "textDocument/completion", + "params": { + "textDocument": { + "uri": "$uri" + }, + "position": { + "line": 0, + "character": 0 + }, + "workDoneToken": "workDoneToken", + "partialResultToken": "partialResultToken" + } + }""") do req + @assert req isa CompletionRequest + end + test_roundtrip("""{ + "jsonrpc": "2.0", + "id": 0, + "method": "completionItem/resolve", + "params": { + "label": "label", + "textEdit": { + "range": { + "start": { + "line": 0, + "character": 0 + }, + "end": { + "line": 0, + "character": 0 + } + }, + "newText": "newText" + } + } + }""") do req + @assert req isa CompletionResolveRequest + @assert req.params.textEdit isa TextEdit + end + end +end diff --git a/LSP/test/Project.toml b/LSP/test/Project.toml new file mode 100644 index 000000000..49f657774 --- /dev/null +++ b/LSP/test/Project.toml @@ -0,0 +1,9 @@ +[deps] +LSP = "880dcf91-6fde-4251-87fc-bfd84012291a" +Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" + +[sources] +LSP = {path = ".."} + +[compat] +Test = "1" diff --git a/LSP/test/runtests.jl b/LSP/test/runtests.jl new file mode 100644 index 000000000..2ca8d29c7 --- /dev/null +++ b/LSP/test/runtests.jl @@ -0,0 +1,235 @@ +using LSP +using LSP.URIs2 +using LSP: to_lsp_json, test_roundtrip +using Test + +@testset "LSP" begin + # De/serializing complex LSP objects + uri = filename2uri(@__FILE__) + + test_roundtrip("""{ + "jsonrpc": "2.0", + "id": 0, + "result": null + }""", DefinitionResponse) do res + @test res isa DefinitionResponse + @test_broken res.result === null # this null should be preserved through the roundtrip + end + test_roundtrip("""{ + "jsonrpc": "2.0", + "id": 0, + "result": { + "uri": "$uri", + "range": { + "start": { + "line": 0, + "character": 0 + }, + "end": { + "line": 0, + "character": 5 + } + } + } + }""", DefinitionResponse) do res + @test res isa DefinitionResponse + @test res.result isa Location + end + test_roundtrip("""{ + "jsonrpc": "2.0", + "id": 0, + "result": [{ + "uri": "$uri", + "range": { + "start": { + "line": 0, + "character": 0 + }, + "end": { + "line": 0, + "character": 5 + } + } + }] + }""", DefinitionResponse) do res + @test res isa DefinitionResponse + @test res.result isa Vector{Location} + end + test_roundtrip("""{ + "jsonrpc": "2.0", + "id": 0, + "result": [{ + "targetUri": "$uri", + "targetRange": { + "start": { + "line": 0, + "character": 0 + }, + "end": { + "line": 0, + "character": 5 + } + }, + "targetSelectionRange": { + "start": { + "line": 0, + "character": 0 + }, + "end": { + "line": 0, + "character": 5 + } + } + }] + }""", DefinitionResponse) do res + @test res isa DefinitionResponse + @test res.result isa Vector{LocationLink} + end + test_roundtrip("""{ + "jsonrpc": "2.0", + "id": 0, + "error": { + "code": $(ErrorCodes.InvalidRequest), + "message": "Test message" + } + }""", DefinitionResponse) do res + @test res isa DefinitionResponse + @test res.result === nothing + @test res.error isa ResponseError + end + + test_roundtrip("""{ + "jsonrpc": "2.0", + "id": 0, + "method": "completionItem/resolve", + "params": { + "label": "label", + "textEdit": { + "range": { + "start": { + "line": 0, + "character": 0 + }, + "end": { + "line": 0, + "character": 0 + } + }, + "newText": "newText" + } + } + }""", CompletionResolveRequest) do req + @test req isa CompletionResolveRequest + @test req.params.textEdit isa TextEdit + end + test_roundtrip("""{ + "jsonrpc": "2.0", + "id": 0, + "method": "completionItem/resolve", + "params": { + "label": "label", + "textEdit": { + "newText": "newText", + "insert": { + "start": { + "line": 0, + "character": 0 + }, + "end": { + "line": 0, + "character": 0 + } + }, + "replace": { + "start": { + "line": 0, + "character": 0 + }, + "end": { + "line": 0, + "character": 6 + } + } + } + } + }""", CompletionResolveRequest) do req + @test req isa CompletionResolveRequest + @test req.params.textEdit isa InsertReplaceEdit + end + test_roundtrip("""{ + "jsonrpc": "2.0", + "id": 0, + "method": "completionItem/resolve", + "params": { + "label": "label", + "documentation": "documentation" + } + }""", CompletionResolveRequest) do req + @test req isa CompletionResolveRequest + @test req.params.documentation isa String + end + test_roundtrip("""{ + "jsonrpc": "2.0", + "id": 0, + "method": "completionItem/resolve", + "params": { + "label": "label", + "documentation": { + "kind": "markdown", + "value": "value" + } + } + }""", CompletionResolveRequest) do req + @test req isa CompletionResolveRequest + @test req.params.documentation isa MarkupContent + end + + test_roundtrip(""" + { + "jsonrpc": "2.0", + "id": 0, + "method": "initialize", + "params": { + "processId": 42, + "clientInfo": { + "name": "Test client", + "version": "1.0" + }, + "capabilities": {}, + "workspaceFolders": [] + } + } + """) do init_req + @test init_req isa InitializeRequest + @test init_req.jsonrpc == "2.0" + @test init_req.id == 0 + @test init_req.method == "initialize" + @test init_req.params.processId == 42 + @test init_req.params.clientInfo.name == "Test client" + @test init_req.params.clientInfo.version == "1.0" + @test init_req.params.capabilities == ClientCapabilities() + @test init_req.params.workspaceFolders isa Vector{WorkspaceFolder} && isempty(init_req.params.workspaceFolders) + end + + # ResponseMessage should omit the `error` field on success, and omit `result` an error + @testset "ResponseMessage result field" begin + success_res = ResponseMessage(; + id = "id", + result = null) + success_res_s = to_lsp_json(success_res) + @test occursin("\"result\"", success_res_s) && occursin("null", success_res_s) + @test !occursin("\"error\"", success_res_s) + end + @testset "ResponseMessage error field" begin + error_res = ResponseMessage(; + id = "id", + result = nothing, + error = ResponseError(; + code = ErrorCodes.RequestFailed, + message = "test message", + data = :test_data)) + error_res_s = to_lsp_json(error_res) + @test !occursin("\"result\"", error_res_s) + @test occursin("\"error\"", error_res_s) + end +end diff --git a/test/Project.toml b/test/Project.toml index 7a552418d..5822feaf6 100644 --- a/test/Project.toml +++ b/test/Project.toml @@ -4,6 +4,9 @@ JETLS = "a3b70258-0602-4ee2-b5a6-54c2470400db" Pkg = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" +[sources] +JETLS = {path = ".."} + [compat] InteractiveUtils = "1.11" Pkg = "1.11.0"