diff --git a/test/tc_as_a_exe/source/app.d b/test/tc_as_a_exe/source/app.d index a8d25e00..094bddc2 100644 --- a/test/tc_as_a_exe/source/app.d +++ b/test/tc_as_a_exe/source/app.d @@ -14,10 +14,12 @@ import core.thread; import core.thread.fiber; import served.lsp.filereader; +import served.lsp.jsonops; import served.lsp.jsonrpc; import served.lsp.protocol; import served.lsp.uri; -import served.lsp.jsonops; + +import tests._basic; version (assert) { @@ -25,29 +27,6 @@ version (assert) else static assert(false, "Compile with asserts."); -__gshared RPCProcessor rpc; -__gshared string cwd; - -// https://forum.dlang.org/post/akucvkduasjlwgykkrzs@forum.dlang.org -void copyDir(string inDir, string outDir) -{ - if (!exists(outDir)) - mkdir(outDir); - else - if (!isDir(outDir)) - throw new FileException(format("Destination path %s is not a folder.", outDir)); - - foreach (entry; dirEntries(inDir.idup, SpanMode.shallow)) - { - auto fileName = baseName(entry.name); - auto destName = buildPath(outDir, fileName); - if (entry.isDir()) - copyDir(entry.name, destName); - else - copy(entry.name, destName); - } -} - void main() { version (Windows) @@ -57,94 +36,8 @@ void main() globalLogLevel = LogLevel.all; - cwd = buildNormalizedPath(tempDir, randomUUID.toString); - copyDir("template", cwd); - info("temporary CWD: ", cwd); - scope (failure) - info("Keeping temporary directory for debugging purposes"); - scope (success) - rmdirRecurse(cwd); - - auto proc = pipeProcess([exe, "--loglevel=all"], Redirect.stdin | Redirect.stdout); - - auto reader = newFileReader(proc.stdout); - reader.start(); - scope (exit) - reader.stop(); - rpc = new RPCProcessor(reader, proc.stdin); - auto testMethod = new Fiber(&doTests); - do - { - rpc.call(); - if (testMethod.state == Fiber.State.TERM) { - if (rpc.state == Fiber.State.TERM) - break; - assert(false, "doTests exitted too early "); - } - testMethod.call(); - Thread.sleep(1.msecs); - } - while (rpc.state != Fiber.State.TERM); - assert(testMethod.state == Fiber.State.TERM); - auto exitCode = proc.pid.wait; - assert(exitCode == 0, "serve-d failed with exit code " ~ exitCode.to!string); - return; -} - -void delegate(RequestMessageRaw msg) gotRequest; -void delegate(RequestMessageRaw msg) gotNotify; - -shared static this() -{ - gotRequest = toDelegate(&defaultRequestHandler); - gotNotify = toDelegate(&defaultNotifyHandler); -} - -void defaultRequestHandler(RequestMessageRaw msg) -{ - assert(false, "Unexpected request " ~ msg.toString); -} - -void defaultNotifyHandler(RequestMessageRaw msg) -{ - info("Ignoring notification " ~ msg.toString); -} - -void pumpEvents() -{ - while (rpc.hasData) - { - auto msg = rpc.poll; - if (!msg.id.isNone) - gotRequest(msg); - else - gotNotify(msg); - } -} - -void doTests() -{ - WorkspaceClientCapabilities workspace = { - configuration: opt(true) - }; - InitializeParams init = { - processId: thisProcessID, - rootUri: uriFromFile(cwd), - capabilities: { - workspace: opt(workspace) - } - }; - auto msg = rpc.sendRequest("initialize", init, 10.seconds); - info("Response: ", msg.resultJson); - - // TODO: do actual tests here - - info("Shutting down..."); - rpc.sendRequest("shutdown", init); - pumpEvents(); - Fiber.yield(); - rpc.notifyMethod("exit"); - pumpEvents(); - Thread.sleep(2.seconds); // give serve-d a chance to clean up - Fiber.yield(); + foreach (test; [ + new BasicTests(exe) + ]) + test.run(); } diff --git a/test/tc_as_a_exe/source/tests/_basic.d b/test/tc_as_a_exe/source/tests/_basic.d new file mode 100644 index 00000000..16e53fe0 --- /dev/null +++ b/test/tc_as_a_exe/source/tests/_basic.d @@ -0,0 +1,38 @@ +module tests._basic; + +import tests; + +class BasicTests : ServedInstancedTest +{ + this(string servedExe) + { + super(servedExe, buildPath(__FILE_FULL_PATH__, "../..")); + } + + override void runImpl() + { + // dfmt off + WorkspaceClientCapabilities workspace = { + configuration: opt(true) + }; + InitializeParams init = InitializeParams( + processId: typeof(InitializeParams.processId)(thisProcessID), + rootUri: uriFromFile(cwd), + capabilities: ClientCapabilities( + workspace: opt(workspace) + ) + ); + // dfmt on + auto msg = rpc.sendRequest("initialize", init, 10.seconds); + info("Response: ", msg.resultJson); + + info("Shutting down..."); + rpc.sendRequest("shutdown", init); + pumpEvents(); + Fiber.yield(); + rpc.notifyMethod("exit"); + pumpEvents(); + Thread.sleep(2.seconds); // give serve-d a chance to clean up + Fiber.yield(); + } +} diff --git a/test/tc_as_a_exe/source/tests/package.d b/test/tc_as_a_exe/source/tests/package.d new file mode 100644 index 00000000..25f77f95 --- /dev/null +++ b/test/tc_as_a_exe/source/tests/package.d @@ -0,0 +1,147 @@ +module tests; + +public import core.thread : Fiber, Thread; +public import core.time; +public import served.lsp.jsonrpc; +public import served.lsp.protocol; +public import served.lsp.uri; +public import std.conv; +public import std.experimental.logger; +public import std.path; +public import std.process : thisProcessID; +import std.functional : toDelegate; + +abstract class ServedTest +{ + this(string servedExe) + { + gotRequest = toDelegate(&defaultRequestHandler); + gotNotify = toDelegate(&defaultNotifyHandler); + this.servedExe = servedExe; + } + + abstract void run(); + + void tick() + { + pumpEvents(); + Fiber.yield(); + } + + void processResponse(void delegate(RequestMessageRaw msg) cb) + { + bool called; + gotRequest = (msg) { called = true; cb(msg); }; + tick(); + assert(called, "no response received!"); + gotRequest = null; + } + + void pumpEvents() + { + while (rpc.hasData) + { + auto msg = rpc.poll; + if (!msg.id.isNone) + gotRequest(msg); + else + gotNotify(msg); + } + } + + protected string servedExe; + + RPCProcessor rpc; + + void delegate(RequestMessageRaw msg) gotRequest; + void delegate(RequestMessageRaw msg) gotNotify; +} + +void defaultRequestHandler(RequestMessageRaw msg) +{ + assert(false, "Unexpected request " ~ msg.toString); +} + +void defaultNotifyHandler(RequestMessageRaw msg) +{ + info("Ignoring notification " ~ msg.toString); +} + +abstract class ServedInstancedTest : ServedTest +{ + import std.file; + import std.process; + + string templateDir; + string cwd; + + this(string servedExe, string templateDir) + { + super(servedExe); + this.templateDir = templateDir; + } + + override void run() + { + import served.lsp.filereader; + import std.uuid; + + cwd = buildNormalizedPath(tempDir, randomUUID.toString); + copyDir("template", cwd); + info("temporary CWD: ", cwd); + scope (failure) + info("Keeping temporary directory for debugging purposes"); + scope (success) + rmdirRecurse(cwd); + + auto proc = pipeProcess([servedExe, "--loglevel=all"], Redirect.stdin | + Redirect.stdout); + + auto reader = newFileReader(proc.stdout); + reader.start(); + scope (exit) + reader.stop(); + rpc = new RPCProcessor(reader, proc.stdin); + auto testMethod = new Fiber(&runImpl); + do + { + rpc.call(); + if (testMethod.state == Fiber.State.TERM) + { + if (rpc.state == Fiber.State.TERM) + break; + assert(false, "doTests exitted too early "); + } + testMethod.call(); + Thread.sleep(1.msecs); + } + while (rpc.state != Fiber.State.TERM); + assert(testMethod.state == Fiber.State.TERM); + auto exitCode = proc.pid.wait; + assert(exitCode == 0, "serve-d failed with exit code " ~ exitCode.to!string); + } + + abstract void runImpl(); +} + +// https://forum.dlang.org/post/akucvkduasjlwgykkrzs@forum.dlang.org +void copyDir(string inDir, string outDir) +{ + import std.file; + import std.format; + + if (!exists(outDir)) + mkdir(outDir); + else if (!isDir(outDir)) + throw new FileException(format("Destination path %s is not a folder.", outDir)); + + foreach (entry; dirEntries(inDir.idup, SpanMode.shallow)) + { + auto fileName = baseName(entry.name); + auto destName = buildPath(outDir, fileName); + if (entry.isDir()) + copyDir(entry.name, destName); + else + copy(entry.name, destName); + } +}