-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #16 from vim-denops/proc
👍 Improved test error handling.
- Loading branch information
Showing
5 changed files
with
208 additions
and
60 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,4 @@ | ||
import { mergeReadableStreams } from "https://deno.land/[email protected]/streams/merge_readable_streams.ts"; | ||
import { is } from "https://deno.land/x/[email protected]/mod.ts"; | ||
import { unreachable } from "https://deno.land/x/[email protected]/mod.ts"; | ||
import { Config, getConfig } from "./conf.ts"; | ||
|
@@ -18,6 +19,25 @@ export interface RunOptions | |
verbose?: boolean; | ||
} | ||
|
||
/** | ||
* Represents results of the runner. | ||
*/ | ||
export interface RunResult extends AsyncDisposable { | ||
/** | ||
* Aborts the process. | ||
*/ | ||
close(): void; | ||
/** | ||
* Wait the process closed and returns status. | ||
*/ | ||
waitClosed(): Promise<WaitClosedResult>; | ||
} | ||
|
||
type WaitClosedResult = { | ||
status: Deno.CommandStatus; | ||
output?: string; | ||
}; | ||
|
||
/** | ||
* Checks if the provided mode is a valid `RunMode`. | ||
*/ | ||
|
@@ -34,22 +54,51 @@ export function run( | |
mode: RunMode, | ||
cmds: string[], | ||
options: RunOptions = {}, | ||
): Deno.ChildProcess { | ||
): RunResult { | ||
const conf = getConfig(); | ||
const verbose = options.verbose ?? conf.verbose; | ||
const { verbose = conf.verbose } = options; | ||
const [cmd, args] = buildArgs(conf, mode); | ||
args.push(...cmds.flatMap((c) => ["-c", c])); | ||
if (verbose) { | ||
args.unshift("--cmd", "redir >> /dev/stdout"); | ||
} | ||
const aborter = new AbortController(); | ||
const { signal } = aborter; | ||
const command = new Deno.Command(cmd, { | ||
args, | ||
env: options.env, | ||
stdin: "piped", | ||
stdout: verbose ? "inherit" : "null", | ||
stderr: verbose ? "inherit" : "null", | ||
stdout: "piped", | ||
stderr: "piped", | ||
signal, | ||
}); | ||
return command.spawn(); | ||
const proc = command.spawn(); | ||
let outputStream = mergeReadableStreams( | ||
proc.stdout.pipeThrough(new TextDecoderStream(), { signal }), | ||
proc.stderr.pipeThrough(new TextDecoderStream(), { signal }), | ||
); | ||
if (verbose) { | ||
const [consoleStream] = [, outputStream] = outputStream.tee(); | ||
consoleStream.pipeTo( | ||
new WritableStream({ write: (data) => console.error(data) }), | ||
).catch(() => {}); | ||
} | ||
return { | ||
close() { | ||
aborter.abort("close"); | ||
}, | ||
async waitClosed() { | ||
const [status, output] = await Promise.all([ | ||
proc.status, | ||
Array.fromAsync(outputStream) | ||
.then((list) => list.join("")) | ||
.catch(() => undefined), | ||
]); | ||
await proc.stdin.abort(); | ||
return { status, output }; | ||
}, | ||
async [Symbol.asyncDispose]() { | ||
this.close(); | ||
await this.waitClosed(); | ||
}, | ||
}; | ||
} | ||
|
||
function buildArgs(conf: Config, mode: RunMode): [string, string[]] { | ||
|
@@ -67,14 +116,20 @@ function buildArgs(conf: Config, mode: RunMode): [string, string[]] { | |
"-X", // Disable xterm | ||
"-e", // Start Vim in Ex mode | ||
"-s", // Silent or batch mode ("-e" is required before) | ||
"-V1", // Verbose level 1 (Echo messages to stderr) | ||
"-c", | ||
"visual", // Go to Normal mode | ||
], | ||
]; | ||
case "nvim": | ||
return [ | ||
conf.nvimExecutable, | ||
["--clean", "--embed", "--headless", "-n"], | ||
[ | ||
"--clean", | ||
"--headless", | ||
"-n", // Disable swap file | ||
"-V1", // Verbose level 1 (Echo messages to stderr) | ||
], | ||
]; | ||
default: | ||
unreachable(mode); | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,29 +1,61 @@ | ||
import { | ||
assert, | ||
assertFalse, | ||
assertRejects, | ||
} from "https://deno.land/[email protected]/assert/mod.ts"; | ||
import { | ||
assertSpyCalls, | ||
spy, | ||
} from "https://deno.land/[email protected]/testing/mock.ts"; | ||
import type { Denops } from "https://deno.land/x/[email protected]/mod.ts"; | ||
import { withDenops } from "./with.ts"; | ||
|
||
Deno.test( | ||
"test(mode:vim) start vim to test denops features", | ||
async () => { | ||
let called = false; | ||
await withDenops("vim", async (denops) => { | ||
assertFalse(await denops.call("has", "nvim")); | ||
called = true; | ||
}); | ||
assert(called, "withDenops main is not called"); | ||
}, | ||
); | ||
Deno.test("test(mode:vim) start vim to test denops features", async () => { | ||
const main = spy(async (denops: Denops) => { | ||
assertFalse(await denops.call("has", "nvim")); | ||
}); | ||
await withDenops("vim", main); | ||
assertSpyCalls(main, 1); | ||
}); | ||
|
||
Deno.test("test(mode:nvim) start nvim to test denops features", async () => { | ||
const main = spy(async (denops: Denops) => { | ||
assert(await denops.call("has", "nvim")); | ||
}); | ||
await withDenops("nvim", main); | ||
assertSpyCalls(main, 1); | ||
}); | ||
|
||
for (const mode of ["vim", "nvim"] as const) { | ||
Deno.test(`test(mode:${mode}) rejects if process aborted`, async () => { | ||
const fn = spy(() => {}); | ||
await assertRejects( | ||
async () => { | ||
await withDenops(mode, fn, { | ||
prelude: [ | ||
"echomsg 'foobar'", | ||
"cquit", | ||
], | ||
}); | ||
}, | ||
Error, | ||
"foobar", | ||
); | ||
assertSpyCalls(fn, 0); | ||
}); | ||
|
||
Deno.test( | ||
"test(mode:nvim) start vim to test denops features", | ||
async () => { | ||
let called = false; | ||
await withDenops("nvim", async (denops) => { | ||
assert(await denops.call("has", "nvim")); | ||
called = true; | ||
}); | ||
assert(called, "withDenops main is not called"); | ||
}, | ||
); | ||
Deno.test(`test(mode:${mode}) rejects if connection failed`, async () => { | ||
const fn = spy(() => {}); | ||
await assertRejects( | ||
async () => { | ||
await withDenops(mode, fn, { | ||
prelude: ["sleep 1"], // Set sleep [s] longer than timeout | ||
connectTimeout: 10, // Set timeout [ms] shorter than sleep | ||
}); | ||
}, | ||
Error, | ||
"Connection failed", | ||
); | ||
assertSpyCalls(fn, 0); | ||
}); | ||
} |