Pudu programming language
Menu
Package

@chrismichaelps / pudu-lang-mcp

Model Context Protocol server for Pudu language documentation and compiler tools

0.1.1Apache-2.01

InstallClose

ServerTest.pudu

Pudu93 lines6.7 KB

GitHub ↗
1/** @Test.Integration.ServerTest.Suite — the real server over stdio with the real compiler */2module Integration.ServerTest34import Std.Io as Io5import Std.Json as Json6import Std.List as List7import Std.Option as Option8import Std.Result as Result9import Std.Test as Test10import PuduLangMcp.Constants.Protocol as Protocol11import PuduLangMcp.Services.Process.Bounded as Bounded12import PuduLangMcp.Services.Toolchain as Toolchain13import PuduLangMcp.Utils.JsonAccess as Access1415/// How long the whole session may take.16const SESSION_MILLIS: Int = 1800001718/// Most bytes kept from the session's output.19const SESSION_CAP: Int = 167772162021/// One request line.22fn request(id: Int, method: Str, params: Json.Json) -> Str {23  Json.encode(&Json.object(&[("jsonrpc", Json.Text("2.0")), ("id", Json.Number(id)), ("method", Json.Text(method)), ("params", params)]))24}2526/// A tool call's parameters.27fn tool(name: Str, arguments: &Array[(Str, Json.Json)]) -> Json.Json {28  Json.object(&[("name", Json.Text(name)), ("arguments", Json.object(arguments))])29}3031/// The first text block of the reply with an id.32fn textOf(replies: &Array[Json.Json], id: Int) -> Str {33  match List.find(replies, |reply: Json.Json| Access.integer(&reply, "id") == Some(id)) {34    case Some(reply) => Option.unwrapOr(Option.andThen(Option.andThen(Json.path(&reply, &["result", "content"]), |content: Json.Json| Json.at(&content, 0)), |block: Json.Json| Access.text(&block, "text")), Json.encode(&reply))35    case None => ""36  }37}3839/// The reply at a position, or null when the server wrote fewer lines.40fn replyAt(replies: &Array[Json.Json], index: Int) -> Json.Json {41  Option.unwrapOr(List.get(replies, index), Json.Null)42}4344/// Runs the suite.45fn main() -> Int {46  let toolchain = Toolchain.locate()47  let compiler = Option.unwrapOr(toolchain.compiler, "")48  let modernMeta = Json.object(&[(Protocol.META_PROTOCOL_VERSION, Json.Text(Protocol.MODERN_VERSION)), (Protocol.META_CLIENT_CAPABILITIES, Json.object(&[]))])49  let program = "module Main\n\nimport Std.Io as Io\n\n/// Doubles a number.\nfn double(value: Int) -> Int \{ value * 2 \}\n\nfn main() -> Int \{\n  let _written = Io.writeLine(\"hello from the run\")\n  double(21) - 42\n\}\n"50  let lines = [51    request(1, "initialize", Json.object(&[("protocolVersion", Json.Text("2025-11-25")), ("capabilities", Json.object(&[])), ("clientInfo", Json.object(&[("name", Json.Text("test")), ("version", Json.Text("1"))]))])),52    Json.encode(&Json.object(&[("jsonrpc", Json.Text("2.0")), ("method", Json.Text("notifications/initialized"))])),53    request(2, "tools/list", Json.object(&[])),54    request(3, "tools/call", tool("pudu_check", &[("source", Json.Text("module Main\n\nfn main() -> Int \{ \"x\" \}\n"))])),55    request(4, "tools/call", tool("pudu_hover", &[("source", Json.Text(program)), ("line", Json.Number(10)), ("character", Json.Number(4))])),56    request(5, "tools/call", tool("pudu_run", &[("source", Json.Text(program))])),57    request(6, "tools/call", tool("pudu_module_reference", &[("module", Json.Text("Std.Json"))])),58    request(7, "tools/call", tool("pudu_docs_search", &[("query", Json.Text("ownership"))])),59    request(8, "tools/call", tool("pudu_format", &[("source", Json.Text("module Main\nfn main()->Int\{0\}\n"))])),60    request(9, "server/discover", Json.object(&[(Protocol.META_KEY, modernMeta)])),61    request(10, "tools/call", Json.object(&[("name", Json.Text("pudu_document_symbols")), ("arguments", Json.object(&[("source", Json.Text(program))])), (Protocol.META_KEY, modernMeta)])),62    "not json"63  ]64  let session = Bounded.run(compiler, &["run", "src/Main.pudu"], lines.join("\n") + "\n", "", SESSION_MILLIS, SESSION_CAP)65  let finished = Result.unwrapOr(session, Bounded.Finished{status: -99, output: "", errors: "", timedOut: false, truncated: false, millis: 0})66  let written = finished.output.split("\n").filter(|line: Str| !line.trim().isEmpty())67  let replies = written.map(|line: Str| Result.unwrapOr(Json.decode(line), Json.Null))68  let ids = replies.map(|reply: Json.Json| Access.member(&reply, "id"))69  let skip = compiler.isEmpty()70  let checks = Test.suite("Integration.Server", &[71      Test.skipIf(skip, "no pudu compiler on this machine", &Test.equals("the server exits cleanly at end of input", &finished.status, &0)),72      Test.skipIf(skip, "no pudu compiler on this machine", &Test.equals("every request gets exactly one line, and nothing else is written", &written.length(), &11)),73      Test.skipIf(skip, "no pudu compiler on this machine", &Test.that("every line is JSON", replies.filter(|reply: Json.Json| reply == Json.Null).isEmpty())),74      Test.skipIf(skip, "no pudu compiler on this machine", &Test.equals("replies keep request order", &List.take(&ids, 3), &[Some(Json.Number(1)), Some(Json.Number(2)), Some(Json.Number(3))])),75      Test.skipIf(skip, "no pudu compiler on this machine", &Test.that("initialize echoes the version", Json.encode(&replyAt(&replies, 0)).contains("\"protocolVersion\":\"2025-11-25\""))),76      Test.skipIf(skip, "no pudu compiler on this machine", &Test.that("every tool is listed", Json.encode(&replyAt(&replies, 1)).contains("pudu_workspace_symbols"))),77      Test.skipIf(skip, "no pudu compiler on this machine", &Test.that("check answers the real diagnostic", textOf(&replies, 3).contains("E3001"))),78      Test.skipIf(skip, "no pudu compiler on this machine", &Test.that("hover answers the real type", textOf(&replies, 4).contains("fn(Int) -> Int"))),79      Test.skipIf(skip, "no pudu compiler on this machine", &Test.that("a confined run prints and exits", textOf(&replies, 5).startsWith("exit status 0") && textOf(&replies, 5).contains("hello from the run"))),80      Test.skipIf(skip, "no pudu compiler on this machine", &Test.that("the module reference comes from the installed library", textOf(&replies, 6).contains("fn Std.Json.decode"))),81      Test.skipIf(skip, "no pudu compiler on this machine", &Test.that("the documentation answers from the corpus", textOf(&replies, 7).contains("docs/ownership"))),82      Test.skipIf(skip, "no pudu compiler on this machine", &Test.that("format lays the code out", textOf(&replies, 8).contains("fn main() -> Int \{ 0 \}"))),83      Test.skipIf(skip, "no pudu compiler on this machine", &Test.that("a modern request is served inside a legacy process", Json.encode(&replyAt(&replies, 8)).contains("supportedVersions"))),84      Test.skipIf(skip, "no pudu compiler on this machine", &Test.that("document symbols come from the language server", textOf(&replies, 10).contains("function double"))),85      Test.skipIf(skip, "no pudu compiler on this machine", &Test.equals("garbage is answered with a parse error", &Access.pathInt(&replyAt(&replies, 10), &["error", "code"]), &Some(Protocol.PARSE_ERROR)))86    ])87  let report = Test.run(&checks)88  for failure in Test.failuresOf(&report) {89    let _reported = Io.writeErrorLine(failure)90  }91  Test.report(&report)92}93