From 2a5ce0f6bddd28324665e0f77c4e41e000e750a3 Mon Sep 17 00:00:00 2001 From: jmgomez Date: Thu, 10 Apr 2025 11:16:00 +0100 Subject: [PATCH 1/3] Implements the extension entry point `runTests` --- nimlangserver.nim | 1 + routes.nim | 13 +++- testrunner.nim | 5 +- tests/textensions.nim | 138 ++++++++++++++++++++++++------------------ 4 files changed, 95 insertions(+), 62 deletions(-) diff --git a/nimlangserver.nim b/nimlangserver.nim index 29d8a00..728ade1 100644 --- a/nimlangserver.nim +++ b/nimlangserver.nim @@ -67,6 +67,7 @@ proc registerRoutes(srv: RpcSocketServer, ls: LanguageServer) = srv.register("extension/tasks", wrapRpc(partial(tasks, ls))) srv.register("extension/runTask", wrapRpc(partial(runTask, ls))) srv.register("extension/listTests", wrapRpc(partial(listTests, ls))) + srv.register("extension/runTests", wrapRpc(partial(runTests, ls))) #Notifications srv.register("$/cancelRequest", wrapRpc(partial(cancelRequest, ls))) srv.register("initialized", wrapRpc(partial(initialized, ls))) diff --git a/routes.nim b/routes.nim index 6d640f8..96b79d7 100644 --- a/routes.nim +++ b/routes.nim @@ -854,10 +854,21 @@ proc listTests*( let config = await ls.getWorkspaceConfiguration() let nimPath = config.getNimPath() if nimPath.isNone: + error "Nim path not found when listing tests" return ListTestsResult(projectInfo: TestProjectInfo(entryPoints: params.entryPoints, suites: initTable[string, TestSuiteInfo]())) - let testProjectInfo = await ls.listTestsForEntryPoint(params.entryPoints, nimPath.get()) + let testProjectInfo = await listTests(params.entryPoints, nimPath.get()) result.projectInfo = testProjectInfo +proc runTests*( + ls: LanguageServer, params: RunTestParams +): Future[RunTestProjectResult] {.async.} = + let config = await ls.getWorkspaceConfiguration() + let nimPath = config.getNimPath() + if nimPath.isNone: + error "Nim path not found when running tests" + return RunTestProjectResult() + await runTests(params.entryPoints, nimPath.get()) + #Notifications proc initialized*(ls: LanguageServer, _: JsonNode): Future[void] {.async.} = debug "Client initialized." diff --git a/testrunner.nim b/testrunner.nim index ea5db0e..20a6251 100644 --- a/testrunner.nim +++ b/testrunner.nim @@ -30,8 +30,8 @@ proc extractTestInfo*(rawOutput: string): TestProjectInfo = # echo "Adding test: ", testInfo.name, " to suite: ", suiteName result.suites[suiteName].tests.add(testInfo) -proc listTestsForEntryPoint*( - ls: LanguageServer, entryPoints: seq[string], nimPath: string +proc listTests*( + entryPoints: seq[string], nimPath: string ): Future[TestProjectInfo] {.async.} = #For now only one entry point is supported assert entryPoints.len == 1 @@ -57,7 +57,6 @@ proc listTestsForEntryPoint*( finally: await shutdownChildProcess(process) - proc parseObject(obj: var object, node: XmlNode) = for field, value in obj.fieldPairs: when value is string: diff --git a/tests/textensions.nim b/tests/textensions.nim index 31fb50f..57dd663 100644 --- a/tests/textensions.nim +++ b/tests/textensions.nim @@ -18,67 +18,88 @@ suite "Nimlangserver extensions": "extension/statusUpdate", "textDocument/publishDiagnostics", "$/progress", ) - test "calling extension/suggest with restart in the project uri should restart nimsuggest": - let initParams = - InitializeParams %* { - "processId": %getCurrentProcessId(), - "rootUri": fixtureUri("projects/hw/"), - "capabilities": - {"window": {"workDoneProgress": true}, "workspace": {"configuration": true}}, - } - let initializeResult = waitFor client.initialize(initParams) + # test "calling extension/suggest with restart in the project uri should restart nimsuggest": + # let initParams = + # InitializeParams %* { + # "processId": %getCurrentProcessId(), + # "rootUri": fixtureUri("projects/hw/"), + # "capabilities": + # {"window": {"workDoneProgress": true}, "workspace": {"configuration": true}}, + # } + # let initializeResult = waitFor client.initialize(initParams) - check initializeResult.capabilities.textDocumentSync.isSome + # check initializeResult.capabilities.textDocumentSync.isSome - let helloWorldUri = fixtureUri("projects/hw/hw.nim") - let helloWorldFile = "projects/hw/hw.nim" - let hwAbsFile = uriToPath(helloWorldFile.fixtureUri()) - client.notify("textDocument/didOpen", %createDidOpenParams(helloWorldFile)) + # let helloWorldUri = fixtureUri("projects/hw/hw.nim") + # let helloWorldFile = "projects/hw/hw.nim" + # let hwAbsFile = uriToPath(helloWorldFile.fixtureUri()) + # client.notify("textDocument/didOpen", %createDidOpenParams(helloWorldFile)) - check waitFor client.waitForNotificationMessage( - fmt"Nimsuggest initialized for {hwAbsFile}", - ) + # check waitFor client.waitForNotificationMessage( + # fmt"Nimsuggest initialized for {hwAbsFile}", + # ) - client.notify( - "textDocument/didOpen", %createDidOpenParams("projects/hw/useRoot.nim") - ) + # client.notify( + # "textDocument/didOpen", %createDidOpenParams("projects/hw/useRoot.nim") + # ) - let prevSuggestPid = ls.projectFiles[hwAbsFile].process.pid - let suggestParams = SuggestParams(action: saRestart, projectFile: hwAbsFile) - let suggestRes = client.call("extension/suggest", %suggestParams).waitFor - let suggestPid = ls.projectFiles[hwAbsFile].process.pid + # let prevSuggestPid = ls.projectFiles[hwAbsFile].process.pid + # let suggestParams = SuggestParams(action: saRestart, projectFile: hwAbsFile) + # let suggestRes = client.call("extension/suggest", %suggestParams).waitFor + # let suggestPid = ls.projectFiles[hwAbsFile].process.pid - check prevSuggestPid != suggestPid + # check prevSuggestPid != suggestPid - test "calling extension/tasks should return all existing tasks": - let initParams = - InitializeParams %* { - "processId": %getCurrentProcessId(), - "rootUri": fixtureUri("projects/tasks/"), - "capabilities": - {"window": {"workDoneProgress": true}, "workspace": {"configuration": true}}, - } - let initializeResult = waitFor client.initialize(initParams) + # test "calling extension/tasks should return all existing tasks": + # let initParams = + # InitializeParams %* { + # "processId": %getCurrentProcessId(), + # "rootUri": fixtureUri("projects/tasks/"), + # "capabilities": + # {"window": {"workDoneProgress": true}, "workspace": {"configuration": true}}, + # } + # let initializeResult = waitFor client.initialize(initParams) - let tasksFile = "projects/tasks/src/tasks.nim" - let taskAbsFile = uriToPath(tasksFile.fixtureUri()) - client.notify("textDocument/didOpen", %createDidOpenParams(tasksFile)) + # let tasksFile = "projects/tasks/src/tasks.nim" + # let taskAbsFile = uriToPath(tasksFile.fixtureUri()) + # client.notify("textDocument/didOpen", %createDidOpenParams(tasksFile)) - let tasks = client.call("extension/tasks", jsonutils.toJson(())).waitFor().jsonTo( - seq[NimbleTask] - ) + # let tasks = client.call("extension/tasks", jsonutils.toJson(())).waitFor().jsonTo( + # seq[NimbleTask] + # ) + + # check tasks.len == 3 + # check tasks[0].name == "helloWorld" + # check tasks[0].description == "hello world" + + # test "calling extension/listTests should return all existing tests": + # #We first need to initialize the nimble project + # let projectDir = getCurrentDir() / "tests" / "projects" / "testrunner" + # cd projectDir: + # let (output, _) = execNimble("install", "-l") + # discard execNimble("setup") - check tasks.len == 3 - check tasks[0].name == "helloWorld" - check tasks[0].description == "hello world" + # let initParams = + # InitializeParams %* { + # "processId": %getCurrentProcessId(), + # "rootUri": fixtureUri("projects/testrunner/"), + # "capabilities": + # {"window": {"workDoneProgress": true}, "workspace": {"configuration": true}}, + # } + # let initializeResult = waitFor client.initialize(initParams) - test "calling extension/test should return all existing tests": - #We first need to initialize the nimble project - let projectDir = getCurrentDir() / "tests" / "projects" / "testrunner" - cd projectDir: - let (output, _) = execNimble("install", "-l") - discard execNimble("setup") + # let listTestsParams = ListTestsParams(entryPoints: @["tests/projects/testrunner/tests/sampletests.nim".absolutePath]) + # let tests = client.call("extension/listTests", jsonutils.toJson(listTestsParams)).waitFor().jsonTo( + # ListTestsResult + # ) + # let testProjectInfo = tests.projectInfo + # check testProjectInfo.suites.len == 3 + # check testProjectInfo.suites["Sample Tests"].tests.len == 1 + # check testProjectInfo.suites["Sample Tests"].tests[0].name == "Sample Test" + # check testProjectInfo.suites["Sample Tests"].tests[0].file == "sampletests.nim" + # check testProjectInfo.suites["Sample Tests"].tests[0].line == 4 + test "calling extension/runTests should run the tests and return the results": let initParams = InitializeParams %* { "processId": %getCurrentProcessId(), @@ -88,13 +109,14 @@ suite "Nimlangserver extensions": } let initializeResult = waitFor client.initialize(initParams) - let listTestsParams = ListTestsParams(entryPoints: @["tests/projects/testrunner/tests/sampletests.nim".absolutePath]) - let tests = client.call("extension/listTests", jsonutils.toJson(listTestsParams)).waitFor().jsonTo( - ListTestsResult + let runTestsParams = RunTestParams(entryPoints: @["tests/projects/testrunner/tests/sampletests.nim".absolutePath]) + let runTestsRes = client.call("extension/runTests", jsonutils.toJson(runTestsParams)).waitFor().jsonTo( + RunTestProjectResult ) - let testProjectInfo = tests.projectInfo - check testProjectInfo.suites.len == 3 - check testProjectInfo.suites["Sample Tests"].tests.len == 1 - check testProjectInfo.suites["Sample Tests"].tests[0].name == "Sample Test" - check testProjectInfo.suites["Sample Tests"].tests[0].file == "sampletests.nim" - check testProjectInfo.suites["Sample Tests"].tests[0].line == 4 + check runTestsRes.suites.len == 4 + check runTestsRes.suites[0].name == "Sample Tests" + check runTestsRes.suites[0].tests == 1 + check runTestsRes.suites[0].failures == 0 + check runTestsRes.suites[0].errors == 0 + check runTestsRes.suites[0].skipped == 0 + check runTestsRes.suites[0].time > 0.0 and runTestsRes.suites[0].time < 1.0 \ No newline at end of file From 97d0c08e74cacb1f76c237c52321d2d1986134c8 Mon Sep 17 00:00:00 2001 From: jmgomez Date: Thu, 10 Apr 2025 11:17:02 +0100 Subject: [PATCH 2/3] adds missing files --- protocol/types.nim | 5 +- tests/textensions.nim | 158 +++++++++++++++++++++--------------------- 2 files changed, 83 insertions(+), 80 deletions(-) diff --git a/protocol/types.nim b/protocol/types.nim index a52b4e5..0f94563 100644 --- a/protocol/types.nim +++ b/protocol/types.nim @@ -1076,5 +1076,8 @@ type time*: float testResults*: seq[RunTestResult] + RunTestParams* = object + entryPoints*: seq[string] + RunTestProjectResult* = object - suites*: seq[RunTestSuiteResult] \ No newline at end of file + suites*: seq[RunTestSuiteResult] diff --git a/tests/textensions.nim b/tests/textensions.nim index 57dd663..a8c9ef8 100644 --- a/tests/textensions.nim +++ b/tests/textensions.nim @@ -18,86 +18,86 @@ suite "Nimlangserver extensions": "extension/statusUpdate", "textDocument/publishDiagnostics", "$/progress", ) - # test "calling extension/suggest with restart in the project uri should restart nimsuggest": - # let initParams = - # InitializeParams %* { - # "processId": %getCurrentProcessId(), - # "rootUri": fixtureUri("projects/hw/"), - # "capabilities": - # {"window": {"workDoneProgress": true}, "workspace": {"configuration": true}}, - # } - # let initializeResult = waitFor client.initialize(initParams) - - # check initializeResult.capabilities.textDocumentSync.isSome - - # let helloWorldUri = fixtureUri("projects/hw/hw.nim") - # let helloWorldFile = "projects/hw/hw.nim" - # let hwAbsFile = uriToPath(helloWorldFile.fixtureUri()) - # client.notify("textDocument/didOpen", %createDidOpenParams(helloWorldFile)) + test "calling extension/suggest with restart in the project uri should restart nimsuggest": + let initParams = + InitializeParams %* { + "processId": %getCurrentProcessId(), + "rootUri": fixtureUri("projects/hw/"), + "capabilities": + {"window": {"workDoneProgress": true}, "workspace": {"configuration": true}}, + } + let initializeResult = waitFor client.initialize(initParams) + + check initializeResult.capabilities.textDocumentSync.isSome + + let helloWorldUri = fixtureUri("projects/hw/hw.nim") + let helloWorldFile = "projects/hw/hw.nim" + let hwAbsFile = uriToPath(helloWorldFile.fixtureUri()) + client.notify("textDocument/didOpen", %createDidOpenParams(helloWorldFile)) - # check waitFor client.waitForNotificationMessage( - # fmt"Nimsuggest initialized for {hwAbsFile}", - # ) - - # client.notify( - # "textDocument/didOpen", %createDidOpenParams("projects/hw/useRoot.nim") - # ) - - # let prevSuggestPid = ls.projectFiles[hwAbsFile].process.pid - # let suggestParams = SuggestParams(action: saRestart, projectFile: hwAbsFile) - # let suggestRes = client.call("extension/suggest", %suggestParams).waitFor - # let suggestPid = ls.projectFiles[hwAbsFile].process.pid - - # check prevSuggestPid != suggestPid - - # test "calling extension/tasks should return all existing tasks": - # let initParams = - # InitializeParams %* { - # "processId": %getCurrentProcessId(), - # "rootUri": fixtureUri("projects/tasks/"), - # "capabilities": - # {"window": {"workDoneProgress": true}, "workspace": {"configuration": true}}, - # } - # let initializeResult = waitFor client.initialize(initParams) - - # let tasksFile = "projects/tasks/src/tasks.nim" - # let taskAbsFile = uriToPath(tasksFile.fixtureUri()) - # client.notify("textDocument/didOpen", %createDidOpenParams(tasksFile)) - - # let tasks = client.call("extension/tasks", jsonutils.toJson(())).waitFor().jsonTo( - # seq[NimbleTask] - # ) - - # check tasks.len == 3 - # check tasks[0].name == "helloWorld" - # check tasks[0].description == "hello world" - - # test "calling extension/listTests should return all existing tests": - # #We first need to initialize the nimble project - # let projectDir = getCurrentDir() / "tests" / "projects" / "testrunner" - # cd projectDir: - # let (output, _) = execNimble("install", "-l") - # discard execNimble("setup") - - # let initParams = - # InitializeParams %* { - # "processId": %getCurrentProcessId(), - # "rootUri": fixtureUri("projects/testrunner/"), - # "capabilities": - # {"window": {"workDoneProgress": true}, "workspace": {"configuration": true}}, - # } - # let initializeResult = waitFor client.initialize(initParams) - - # let listTestsParams = ListTestsParams(entryPoints: @["tests/projects/testrunner/tests/sampletests.nim".absolutePath]) - # let tests = client.call("extension/listTests", jsonutils.toJson(listTestsParams)).waitFor().jsonTo( - # ListTestsResult - # ) - # let testProjectInfo = tests.projectInfo - # check testProjectInfo.suites.len == 3 - # check testProjectInfo.suites["Sample Tests"].tests.len == 1 - # check testProjectInfo.suites["Sample Tests"].tests[0].name == "Sample Test" - # check testProjectInfo.suites["Sample Tests"].tests[0].file == "sampletests.nim" - # check testProjectInfo.suites["Sample Tests"].tests[0].line == 4 + check waitFor client.waitForNotificationMessage( + fmt"Nimsuggest initialized for {hwAbsFile}", + ) + + client.notify( + "textDocument/didOpen", %createDidOpenParams("projects/hw/useRoot.nim") + ) + + let prevSuggestPid = ls.projectFiles[hwAbsFile].process.pid + let suggestParams = SuggestParams(action: saRestart, projectFile: hwAbsFile) + let suggestRes = client.call("extension/suggest", %suggestParams).waitFor + let suggestPid = ls.projectFiles[hwAbsFile].process.pid + + check prevSuggestPid != suggestPid + + test "calling extension/tasks should return all existing tasks": + let initParams = + InitializeParams %* { + "processId": %getCurrentProcessId(), + "rootUri": fixtureUri("projects/tasks/"), + "capabilities": + {"window": {"workDoneProgress": true}, "workspace": {"configuration": true}}, + } + let initializeResult = waitFor client.initialize(initParams) + + let tasksFile = "projects/tasks/src/tasks.nim" + let taskAbsFile = uriToPath(tasksFile.fixtureUri()) + client.notify("textDocument/didOpen", %createDidOpenParams(tasksFile)) + + let tasks = client.call("extension/tasks", jsonutils.toJson(())).waitFor().jsonTo( + seq[NimbleTask] + ) + + check tasks.len == 3 + check tasks[0].name == "helloWorld" + check tasks[0].description == "hello world" + + test "calling extension/listTests should return all existing tests": + #We first need to initialize the nimble project + let projectDir = getCurrentDir() / "tests" / "projects" / "testrunner" + cd projectDir: + let (output, _) = execNimble("install", "-l") + discard execNimble("setup") + + let initParams = + InitializeParams %* { + "processId": %getCurrentProcessId(), + "rootUri": fixtureUri("projects/testrunner/"), + "capabilities": + {"window": {"workDoneProgress": true}, "workspace": {"configuration": true}}, + } + let initializeResult = waitFor client.initialize(initParams) + + let listTestsParams = ListTestsParams(entryPoints: @["tests/projects/testrunner/tests/sampletests.nim".absolutePath]) + let tests = client.call("extension/listTests", jsonutils.toJson(listTestsParams)).waitFor().jsonTo( + ListTestsResult + ) + let testProjectInfo = tests.projectInfo + check testProjectInfo.suites.len == 3 + check testProjectInfo.suites["Sample Tests"].tests.len == 1 + check testProjectInfo.suites["Sample Tests"].tests[0].name == "Sample Test" + check testProjectInfo.suites["Sample Tests"].tests[0].file == "sampletests.nim" + check testProjectInfo.suites["Sample Tests"].tests[0].line == 4 test "calling extension/runTests should run the tests and return the results": let initParams = From 0765d5c99253b96378dbd13677c26cfb74924e4f Mon Sep 17 00:00:00 2001 From: jmgomez Date: Thu, 10 Apr 2025 11:21:03 +0100 Subject: [PATCH 3/3] Adds the runTest capability --- protocol/types.nim | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/protocol/types.nim b/protocol/types.nim index 0f94563..cb1fa1c 100644 --- a/protocol/types.nim +++ b/protocol/types.nim @@ -1003,7 +1003,8 @@ type LspExtensionCapability* = enum #List of extensions this server support. Useful for clients excRestartSuggest = "RestartSuggest", - excNimbleTask = "NimbleTask" + excNimbleTask = "NimbleTask", + excRunTests = "RunTests" ProjectError* = object projectFile*: string