Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

🌿 add/improve tests for interrupt #422

Merged
merged 7 commits into from
Sep 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 19 additions & 3 deletions denops/@denops-private/denops_test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { BatchError, type Meta } from "jsr:@denops/core@^7.0.0";
import {
assert,
assertEquals,
assertFalse,
assertInstanceOf,
assertRejects,
assertStrictEquals,
Expand Down Expand Up @@ -29,15 +31,29 @@ Deno.test("DenopsImpl", async (t) => {
call: () => unimplemented(),
batch: () => unimplemented(),
};
const serviceInterrupt = new AbortController();
const service: Service = {
dispatch: () => unimplemented(),
waitLoaded: () => unimplemented(),
interrupted: new AbortController().signal,
interrupted: serviceInterrupt.signal,
};
const denops = new DenopsImpl("dummy", meta, host, service);

await t.step("interrupted returns AbortSignal instance", () => {
assertInstanceOf(denops.interrupted, AbortSignal);
await t.step(".interrupted property", async (t) => {
await t.step("returns AbortSignal instance", () => {
assertInstanceOf(denops.interrupted, AbortSignal);
});

await t.step("aborts when `Service.interrupted` aborts", () => {
const signal = denops.interrupted;

assertFalse(signal.aborted);

serviceInterrupt.abort("test");

assert(signal.aborted);
assertEquals(signal.reason, "test");
});
});

await t.step(".redraw()", async (t) => {
Expand Down
91 changes: 91 additions & 0 deletions tests/denops/runtime/functions/denops/interrupt_test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import { assert, assertEquals } from "jsr:@std/assert@^1.0.1";
import { resolveTestDataPath } from "/denops-testdata/resolve.ts";
import { testHost } from "/denops-testutil/host.ts";
import { wait } from "/denops-testutil/wait.ts";

const scriptInterrupt = resolveTestDataPath("dummy_interrupt_plugin.ts");

testHost({
name: "denops#interrupt()",
mode: "all",
postlude: [
"let g:__test_denops_events = []",
"autocmd User DenopsPlugin* call add(g:__test_denops_events, expand('<amatch>'))",
"autocmd User DummyInterruptPlugin:* call add(g:__test_denops_events, expand('<amatch>'))",
"runtime plugin/denops.vim",
// NOTE: Disable startup on VimEnter.
"autocmd! denops_plugin_internal_startup VimEnter",
],
fn: async ({ host, t }) => {
await t.step("if the server is not yet running", async (t) => {
await t.step("returns immediately", async () => {
await host.call("denops#interrupt");
});
});

// Start the server and wait.
await host.call("denops#server#start");
await wait(() => host.call("eval", "denops#server#status() ==# 'running'"));

await t.step("if the plugin is not yet loaded", async (t) => {
await t.step("returns immediately", async () => {
await host.call("denops#interrupt");
});
});

await t.step("if the plugin is loaded", async (t) => {
// Load plugin and wait.
await host.call("execute", [
"let g:__test_denops_events = []",
`call denops#plugin#load('dummy', '${scriptInterrupt}')`,
], "");
await wait(async () =>
(await host.call("eval", "g:__test_denops_events") as string[])
.includes("DenopsPluginPost:dummy")
);

await t.step("returns immediately", async () => {
await host.call("execute", [
"let g:__test_denops_events = []",
], "");

await host.call("denops#interrupt");

// It should passed because of delay(MIMIC_ABORT_DELAY) in dummy_interrupt_plugin.ts
assertEquals(await host.call("eval", "g:__test_denops_events"), []);
Milly marked this conversation as resolved.
Show resolved Hide resolved
});

await t.step("sends signal to the plugin", async () => {
await wait(() => host.call("eval", "len(g:__test_denops_events)"));
const events = await host.call(
"eval",
"g:__test_denops_events",
) as string[];
assert(
events.some((event) =>
event.startsWith("DummyInterruptPlugin:Interrupted:")
),
`Expected event 'DummyInterruptPlugin:Interrupted:*', but: ${
JSON.stringify(events)
}`,
);
});

// Reset interrupt event handler.
await host.call("denops#request", "dummy", "reset", []);

await t.step("sends signal to the plugin with reason", async () => {
await host.call("execute", [
"let g:__test_denops_events = []",
], "");

await host.call("denops#interrupt", "test");

await wait(() => host.call("eval", "len(g:__test_denops_events)"));
assertEquals(await host.call("eval", "g:__test_denops_events"), [
"DummyInterruptPlugin:Interrupted:test",
]);
});
});
},
});
49 changes: 39 additions & 10 deletions tests/denops/runtime/functions/denops/notify_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ import { resolveTestDataPath } from "/denops-testdata/resolve.ts";
import { testHost } from "/denops-testutil/host.ts";
import { wait } from "/denops-testutil/wait.ts";

const ASYNC_DELAY = 100;
const MESSAGE_DELAY = 200;

const scriptValid = resolveTestDataPath("dummy_valid_plugin.ts");
const scriptDispatcher = resolveTestDataPath("dummy_dispatcher_plugin.ts");

testHost({
name: "denops#notify()",
Expand All @@ -24,6 +24,7 @@ testHost({
await host.call("execute", [
"let g:__test_denops_events = []",
"autocmd User DenopsPlugin* call add(g:__test_denops_events, expand('<amatch>'))",
"autocmd User DummyDispatcherPlugin:* call add(g:__test_denops_events, expand('<amatch>'))",
], "");

for (const [plugin_name, label] of INVALID_PLUGIN_NAMES) {
Expand All @@ -38,23 +39,51 @@ testHost({
// Load plugin and wait.
await host.call("execute", [
"let g:__test_denops_events = []",
`call denops#plugin#load('dummyLoaded', '${scriptValid}')`,
`call denops#plugin#load('dummy', '${scriptDispatcher}')`,
], "");
await wait(async () =>
(await host.call("eval", "g:__test_denops_events") as string[])
.includes("DenopsPluginPost:dummyLoaded")
.includes("DenopsPluginPost:dummy")
);

outputs = [];
await host.call("denops#notify", "dummyLoaded", "test", ["foo"]);
await t.step("returns immediately", async () => {
await host.call("execute", [
"let g:__test_denops_events = []",
], "");

await t.step("returns immediately", () => {
assertEquals(outputs, []);
await host.call("denops#notify", "dummy", "test", ["foo"]);

assertEquals(await host.call("eval", "g:__test_denops_events"), []);
});

await t.step("calls dispatcher method", async () => {
await delay(ASYNC_DELAY);
assertStringIncludes(outputs.join(""), 'This is test call: ["foo"]');
await wait(() => host.call("eval", "len(g:__test_denops_events)"));
assertEquals(await host.call("eval", "g:__test_denops_events"), [
'DummyDispatcherPlugin:TestCalled:["foo"]',
]);
});

await t.step("if the dispatcher method is not exist", async (t) => {
await t.step("returns immediately (flaky)", async () => {
outputs = [];

await host.call(
"denops#notify",
"dummy",
"not_exist_method",
["foo"],
);

assertEquals(outputs, []);
});

await t.step("outputs an error message", async () => {
await delay(MESSAGE_DELAY);
lambdalisue marked this conversation as resolved.
Show resolved Hide resolved
assertStringIncludes(
outputs.join(""),
"Failed to call 'not_exist_method' API in 'dummy'",
);
});
});
});
},
Expand Down
108 changes: 72 additions & 36 deletions tests/denops/runtime/functions/denops/request_async_test.ts
Original file line number Diff line number Diff line change
@@ -1,35 +1,32 @@
import {
assertArrayIncludes,
assertEquals,
assertObjectMatch,
assertStringIncludes,
} from "jsr:@std/assert@^1.0.1";
import { INVALID_PLUGIN_NAMES } from "/denops-testdata/invalid_plugin_names.ts";
import { resolveTestDataPath } from "/denops-testdata/resolve.ts";
import { testHost } from "/denops-testutil/host.ts";
import { wait } from "/denops-testutil/wait.ts";

const scriptValid = resolveTestDataPath("dummy_valid_plugin.ts");
const scriptDispatcher = resolveTestDataPath("dummy_dispatcher_plugin.ts");

testHost({
name: "denops#request_async()",
mode: "all",
postlude: [
"runtime plugin/denops.vim",
],
fn: async ({ host, t, stderr, mode }) => {
let outputs: string[] = [];
stderr.pipeTo(
new WritableStream({ write: (s) => void outputs.push(s) }),
).catch(() => {});
fn: async ({ host, t }) => {
await wait(() => host.call("eval", "denops#server#status() ==# 'running'"));
await host.call("execute", [
"let g:__test_denops_events = []",
"autocmd User DenopsPlugin* call add(g:__test_denops_events, expand('<amatch>'))",
"autocmd User DummyDispatcherPlugin:* call add(g:__test_denops_events, expand('<amatch>'))",
"function TestDenopsRequestAsyncSuccess(...)",
" call add(g:__test_denops_events, ['TestDenopsRequestAsyncSuccess', a:000])",
" call add(g:__test_denops_events, ['TestDenopsRequestAsyncSuccess:Called', a:000])",
"endfunction",
"function TestDenopsRequestAsyncFailure(...)",
" call add(g:__test_denops_events, ['TestDenopsRequestAsyncFailure', a:000])",
" call add(g:__test_denops_events, ['TestDenopsRequestAsyncFailure:Called', a:000])",
"endfunction",
], "");

Expand All @@ -53,10 +50,10 @@ testHost({
await t.step("calls failure callback", async () => {
await wait(() => host.call("eval", "len(g:__test_denops_events)"));
assertObjectMatch(
await host.call("eval", "g:__test_denops_events") as [],
await host.call("eval", "g:__test_denops_events") as unknown[],
{
0: [
"TestDenopsRequestAsyncFailure",
"TestDenopsRequestAsyncFailure:Called",
[
{
message: `Invalid plugin name: ${plugin_name}`,
Expand All @@ -74,43 +71,82 @@ testHost({
// Load plugin and wait.
await host.call("execute", [
"let g:__test_denops_events = []",
`call denops#plugin#load('dummyLoaded', '${scriptValid}')`,
`call denops#plugin#load('dummy', '${scriptDispatcher}')`,
], "");
await wait(async () =>
(await host.call("eval", "g:__test_denops_events") as string[])
.includes("DenopsPluginPost:dummyLoaded")
.includes("DenopsPluginPost:dummy")
);
await host.call("execute", [
"let g:__test_denops_events = []",
], "");

outputs = [];
await host.call(
"denops#request_async",
"dummyLoaded",
"test",
["foo"],
"TestDenopsRequestAsyncSuccess",
"TestDenopsRequestAsyncFailure",
);
await t.step("returns immediately", async () => {
await host.call("execute", [
"let g:__test_denops_events = []",
], "");

await host.call(
"denops#request_async",
"dummy",
"test",
["foo"],
"TestDenopsRequestAsyncSuccess",
"TestDenopsRequestAsyncFailure",
);

await t.step("returns immediately", () => {
assertEquals(outputs, []);
assertEquals(await host.call("eval", "g:__test_denops_events"), []);
});

await t.step("calls success callback", async () => {
await t.step("calls dispatcher method", async () => {
await wait(() => host.call("eval", "len(g:__test_denops_events)"));
const returnValue = mode === "vim" ? null : 0;
assertObjectMatch(
await host.call("eval", "g:__test_denops_events") as [],
{
0: ["TestDenopsRequestAsyncSuccess", [returnValue]],
},
assertArrayIncludes(
await host.call("eval", "g:__test_denops_events") as unknown[],
['DummyDispatcherPlugin:TestCalled:["foo"]'],
);
});

await t.step("calls success callback", async () => {
assertArrayIncludes(
await host.call("eval", "g:__test_denops_events") as unknown[],
[
[
"TestDenopsRequestAsyncSuccess:Called",
[{ result: "OK", args: ["foo"] }],
],
],
);
});

await t.step("calls dispatcher method", () => {
assertStringIncludes(outputs.join(""), 'This is test call: ["foo"]');
await t.step("if the dispatcher method is not exist", async (t) => {
await t.step("returns immediately", async () => {
await host.call("execute", [
"let g:__test_denops_events = []",
"call denops#request_async('dummy', 'not_exist_method', ['foo'], 'TestDenopsRequestAsyncSuccess', 'TestDenopsRequestAsyncFailure')",
"let g:__test_denops_events_after_called = g:__test_denops_events->copy()",
], "");

assertEquals(
await host.call("eval", "g:__test_denops_events_after_called"),
[],
);
});

await t.step("calls failure callback", async () => {
await wait(() => host.call("eval", "len(g:__test_denops_events)"));
assertObjectMatch(
await host.call("eval", "g:__test_denops_events") as unknown[],
{
0: [
"TestDenopsRequestAsyncFailure:Called",
[
{
message:
"Failed to call 'not_exist_method' API in 'dummy': this[#denops].dispatcher[fn] is not a function",
name: "Error",
},
],
],
},
);
});
});
});
},
Expand Down
Loading