Skip to content

Commit

Permalink
WIP Tests
Browse files Browse the repository at this point in the history
  • Loading branch information
jmgomez committed Sep 2, 2024
1 parent 1aec92d commit a98b922
Show file tree
Hide file tree
Showing 9 changed files with 182 additions and 45 deletions.
2 changes: 1 addition & 1 deletion config.nims
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@
when withDir(thisDir(), system.fileExists("nimble.paths")):
include "nimble.paths"
# end Nimble config
--mm:refc
--mm:markAndSweep
6 changes: 4 additions & 2 deletions ls.nim
Original file line number Diff line number Diff line change
Expand Up @@ -267,14 +267,16 @@ proc getLspStatus*(ls: LanguageServer): NimLangServerStatus {.raises: [].} =
let futNs = ls.projectFiles.getOrDefault(projectFile, nil)
if futNs.finished:
try:
var ns: NimSuggest = futNs.read
var ns = futNs.read
var nsStatus = NimSuggestStatus(
projectFile: projectFile,
capabilities: ns.capabilities.toSeq,
version: ns.version,
path: ns.nimsuggestPath,
port: ns.port,
)
for open in ns.openFiles:
nsStatus.openFiles.add open
result.nimsuggestInstances.add nsStatus
except CatchableError:
discard
Expand All @@ -285,7 +287,7 @@ proc getLspStatus*(ls: LanguageServer): NimLangServerStatus {.raises: [].} =


proc sendStatusChanged*(ls: LanguageServer) {.raises: [].} =
let status = ls.getLspStatus()
let status: NimLangServerStatus = ls.getLspStatus()
ls.notify("extension/statusUpdate", %* status)


Expand Down
11 changes: 7 additions & 4 deletions lstransports.nim
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ proc processContentLength*(inputStream: FileStream): string =
else:
error "No content length \n"

proc processContentLength*(transport: StreamTransport): Future[string] {.async:(raises:[]).} =
proc processContentLength*(transport: StreamTransport, error: bool = true): Future[string] {.async:(raises:[]).} =
try:
result = await transport.readLine()
if result.startsWith(CONTENT_LENGTH):
Expand All @@ -107,11 +107,14 @@ proc processContentLength*(transport: StreamTransport): Future[string] {.async:(
result = (await transport.read(length)).mapIt($(it.char)).join()

else:
error "No content length \n"
if error:
error "No content length \n"
except TransportError as ex:
error "Error reading content length", msg = ex.msg
if error:
error "Error reading content length", msg = ex.msg
except CatchableError as ex:
error "Error reading content length", msg = ex.msg
if error:
error "Error reading content length", msg = ex.msg


proc readStdin*(transport: StreamTransport) {.thread.} =
Expand Down
20 changes: 10 additions & 10 deletions nimlangserver.nim
Original file line number Diff line number Diff line change
Expand Up @@ -122,28 +122,28 @@ proc registerProcMonitor(ls: LanguageServer) =
globalLS.stopNimsuggestProcessesP()
exitnow(1)

proc main*(cmdLineParams: CommandLineParams) =
proc main*(cmdLineParams: CommandLineParams): LanguageServer =
debug "Starting nimlangserver", params = cmdLineParams
#[
`nimlangserver` supports both transports: stdio and socket. By default it uses stdio transport.
But we do construct a RPC socket server even in stdio mode, so that we can reuse the same code for both transports.
]#
var ls = initLs(cmdLineParams, ensureStorageDir())
case ls.transportMode:
result = initLs(cmdLineParams, ensureStorageDir())
case result.transportMode:
of stdio:
ls.startStdioServer()
result.startStdioServer()
of socket:
ls.startSocketServer(cmdLineParams.port)
result.startSocketServer(cmdLineParams.port)

globalLS = addr ls #TODO use partial instead inside the func
ls.srv.registerRoutes(ls)
ls.registerProcMonitor()
globalLS = addr result #TODO use partial instead inside the func
result.srv.registerRoutes(result)
result.registerProcMonitor()

when isMainModule:
try:
main(handleParams())
discard main(handleParams())
runForever()

except Exception as e:
error "Error in main"
writeStackTrace e
Expand Down
2 changes: 1 addition & 1 deletion nimlangserver.nimble
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
mode = ScriptMode.Verbose

packageName = "nimlangserver"
version = "1.5.0"
version = "1.5.1"
author = "The core Nim team"
description = "Nim language server for IDEs"
license = "MIT"
Expand Down
10 changes: 6 additions & 4 deletions routes.nim
Original file line number Diff line number Diff line change
Expand Up @@ -716,12 +716,14 @@ proc didOpen*(ls: LanguageServer, params: DidOpenTextDocumentParams):

debug "Document associated with the following projectFile", uri = uri, projectFile = projectFile
if not ls.projectFiles.hasKey(projectFile):
debug "******Will create nimsuggest for this file", uri = uri
debug "Will create nimsuggest for this file", uri = uri
ls.createOrRestartNimsuggest(projectFile, uri)



for line in text.splitLines:
ls.openFiles[uri].fingerTable.add line.createUTFMapping()
file.writeLine line
if uri in ls.openFiles:
ls.openFiles[uri].fingerTable.add line.createUTFMapping()
file.writeLine line
file.close()
ls.tryGetNimSuggest(uri).addCallback() do (fut: Future[Option[Nimsuggest]]) -> void:
if not fut.failed and fut.read.isSome:
Expand Down
4 changes: 2 additions & 2 deletions tests/all.nim
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import
# tnimlangserver, #These tests need to be redone
# tsuggestapi
tsuggestapi,
tnimlangserver2
# tprojectsetup
# tprojectsetup #these tests need to be redone
114 changes: 102 additions & 12 deletions tests/lspsocketclient.nim
Original file line number Diff line number Diff line change
@@ -1,17 +1,25 @@
import ../[
ls, lstransports
ls, lstransports, utils
]

import ../protocol/types
import std/[options, unittest, json, os, jsonutils]
import std/[options, unittest, json, os, jsonutils, tables, strutils, sequtils]
import json_rpc/[rpcclient]
import chronicles

#Utils
proc fixtureUri*(path: string): string =
result = pathToUri(getCurrentDir() / "tests" / path)

type
NotificationRpc* = proc (params: JsonNode): Future[void] {.gcsafe, raises:[].}
LspSocketClient* = ref object of RpcSocketClient
routes*: TableRef[string, NotificationRpc]


proc newLspSocketClient*(): LspSocketClient = LspSocketClient.new()
proc newLspSocketClient*(): LspSocketClient =
result = LspSocketClient.new()
result.routes = newTable[string, NotificationRpc]()

method call*(client: LspSocketClient, name: string,
params: JsonNode): Future[JsonString] {.async, gcsafe.} =
Expand All @@ -38,6 +46,23 @@ method call*(client: LspSocketClient, name: string,

return await newFut

# proc processMessage*(client: LspSocketClient, msg: string): Future[void] {.async: (raises: []).} =
# try:
# echo "process message ", msg
# let msg = msg.parseJson()
# if "id" in msg and msg["id"].kind == JInt: #TODO this is just a response from the server
# echo "Response from Server"
# await sleepAsync(1)


# # let res = newJObject()
# # res["rpc"]
# #Here we should write in the transport the request i.e.
# # discard await client.transport.write()

# except CatchableError as ex:
# error "Processing message error ", ex = ex.msg

proc processData(client: LspSocketClient) {.async: (raises: []).} =
while true:
var localException: ref JsonRpcError
Expand All @@ -51,10 +76,7 @@ proc processData(client: LspSocketClient) {.async: (raises: []).} =
break

let res = client.processMessage(value)
if res.isErr:
error "Error when processing RPC message", msg=res.error
localException = newException(JsonRpcError, res.error)
break

except TransportError as exc:
localException = newException(JsonRpcError, exc.msg)
await client.transport.closeWait()
Expand All @@ -70,7 +92,7 @@ proc processData(client: LspSocketClient) {.async: (raises: []).} =
if client.batchFut.isNil.not and not client.batchFut.completed():
client.batchFut.fail(localException)

# async loop reconnection and waiting
# async loop reconnection and waiting
try:
info "Reconnect to server", address=`$`(client.address)
client.transport = await connect(client.address)
Expand All @@ -81,13 +103,81 @@ proc processData(client: LspSocketClient) {.async: (raises: []).} =
error "Error when reconnecting to server", msg=exc.msg
break

proc onProcessMessage(client: RpcClient, line: string): Result[bool, string] {.gcsafe, raises: [].} =
# echo "onProcessmessage ", line #line contains the json response from the server. From here one could implement an actual client
return ok(true)

proc processClientLoop*(client: LspSocketClient) {.async: (raises: []), gcsafe.} =
while true:
# if not client.transport.isOpen:
try:
await sleepAsync(100)

let msg = await processContentLength(client.transport, error = false)
if msg == "": continue
let serverReq = msg.parseJson()
let meth = serverReq["method"].jsonTo(string)
debug "[Process client loop ]", meth = meth
if meth in client.routes:
echo msg
await client.routes[meth](serverReq["params"])
else:
error "Method not found in client", meth = meth

except CatchableError as ex:
error "[ProcessClientLoop ]", ex = ex.msg
discard

proc connect*(client: LspSocketClient, address: string, port: Port) {.async.} =
let addresses = resolveTAddress(address, port)
client.transport = await connect(addresses[0])
client.address = addresses[0]
client.loop = processData(client)
client.onProcessMessage = onProcessMessage
asyncSpawn client.processClientLoop()


proc notify*(client: LspSocketClient, name: string, params: JsonNode) =
proc wrap(): Future[void] {.async.} =
discard await client.call(name, params)
asyncSpawn wrap()


proc register*(client: LspSocketClient, name: string, rpc: NotificationRpc) =
client.routes[name] = rpc


#Calls
proc initialize*(client: LspSocketClient): Future[InitializeResult] {.async.} =
let initParams = InitializeParams %* {
"processId": %getCurrentProcessId(),
"rootUri": fixtureUri("projects/hw/"),
"capabilities": {
"window": {
"workDoneProgress": true
},
"workspace": {"configuration": true}
}
}
client.call("initialize", %initParams).await.string.parseJson.jsonTo(InitializeResult)
#Should we await here for the response to come back?




proc createDidOpenParams*(file: string): DidOpenTextDocumentParams =
return DidOpenTextDocumentParams %* {
"textDocument": {
"uri": fixtureUri(file),
"languageId": "nim",
"version": 0,
"text": readFile("tests" / file)
}
}

proc positionParams*(uri: string, line, character: int): TextDocumentPositionParams =
return TextDocumentPositionParams %* {
"position": {
"line": line,
"character": character
},
"textDocument": {
"uri": uri
}
}
58 changes: 49 additions & 9 deletions tests/tnimlangserver2.nim
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import ../[
nimlangserver, ls, lstransports
]
import ../protocol/types
import ../protocol/[enums, types]
import std/[options, unittest, json, os, jsonutils]
import json_rpc/[rpcclient]
import chronicles
Expand All @@ -10,20 +10,60 @@ import lspsocketclient

suite "Nimlangserver":
let cmdParams = CommandLineParams(transport: some socket, port: getNextFreePort())
main(cmdParams) #we could accesss to the ls here to test against its state
let ls = main(cmdParams) #we could accesss to the ls here to test against its state
let client = newLspSocketClient()
waitFor client.connect("localhost", cmdParams.port)

test "initialize from the client should call initialized on the server":
let initParams = InitializeParams(
processId: some(%getCurrentProcessId()),
rootUri: some("file:///tmp/"),
capabilities: ClientCapabilities())
let initializeResult = client.call("initialize", %initParams).waitFor.string.parseJson.jsonTo(InitializeResult)

check initializeResult.capabilities.textDocumentSync.isSome

let initializeResult = waitFor client.initialize()

check initializeResult.capabilities.textDocumentSync.isSome


teardown:
#TODO properly stop the server
echo "Teardown"
client.close()
ls.onExit()


proc showMessage(params: JsonNode): Future[void] =
try:
let notification = params.jsonTo(tuple[`type`: MessageType, msg: string])
debug "showMessage Called with ", params = params
except CatchableError:
discard
result = newFuture[void]("showMessage")

proc configuration(params: JsonNode): Future[void] =
try:
let conf = params
debug "configuration called with ", params = params
except CatchableError as ex:
error "Error in configuration ", msg = ex.msg
discard
result = newFuture[void]("configuration")


suite "Suggest API selection":
let cmdParams = CommandLineParams(transport: some socket, port: getNextFreePort())
let ls = main(cmdParams) #we could accesss to the ls here to test against its state
let client = newLspSocketClient()

client.register("window/showMessage", showMessage)
client.register("workspace/configuration", configuration)
waitFor client.connect("localhost", cmdParams.port)
discard waitFor client.initialize()
client.notify("initialized", newJObject())


test "Suggest api":
client.notify("textDocument/didOpen", %createDidOpenParams("projects/hw/hw.nim"))
check true
waitFor sleepAsync(1000)





0 comments on commit a98b922

Please sign in to comment.