Skip to content

Commit 08146a5

Browse files
authored
Merge pull request #422 from vim-denops/interrupt-test
🌿 add/improve tests for interrupt
2 parents f9ab988 + 5a22b02 commit 08146a5

File tree

7 files changed

+304
-61
lines changed

7 files changed

+304
-61
lines changed

denops/@denops-private/denops_test.ts

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import { BatchError, type Meta } from "jsr:@denops/core@^7.0.0";
22
import {
3+
assert,
34
assertEquals,
5+
assertFalse,
46
assertInstanceOf,
57
assertRejects,
68
assertStrictEquals,
@@ -29,15 +31,29 @@ Deno.test("DenopsImpl", async (t) => {
2931
call: () => unimplemented(),
3032
batch: () => unimplemented(),
3133
};
34+
const serviceInterrupt = new AbortController();
3235
const service: Service = {
3336
dispatch: () => unimplemented(),
3437
waitLoaded: () => unimplemented(),
35-
interrupted: new AbortController().signal,
38+
interrupted: serviceInterrupt.signal,
3639
};
3740
const denops = new DenopsImpl("dummy", meta, host, service);
3841

39-
await t.step("interrupted returns AbortSignal instance", () => {
40-
assertInstanceOf(denops.interrupted, AbortSignal);
42+
await t.step(".interrupted property", async (t) => {
43+
await t.step("returns AbortSignal instance", () => {
44+
assertInstanceOf(denops.interrupted, AbortSignal);
45+
});
46+
47+
await t.step("aborts when `Service.interrupted` aborts", () => {
48+
const signal = denops.interrupted;
49+
50+
assertFalse(signal.aborted);
51+
52+
serviceInterrupt.abort("test");
53+
54+
assert(signal.aborted);
55+
assertEquals(signal.reason, "test");
56+
});
4157
});
4258

4359
await t.step(".redraw()", async (t) => {
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
import { assert, assertEquals } from "jsr:@std/assert@^1.0.1";
2+
import { resolveTestDataPath } from "/denops-testdata/resolve.ts";
3+
import { testHost } from "/denops-testutil/host.ts";
4+
import { wait } from "/denops-testutil/wait.ts";
5+
6+
const scriptInterrupt = resolveTestDataPath("dummy_interrupt_plugin.ts");
7+
8+
testHost({
9+
name: "denops#interrupt()",
10+
mode: "all",
11+
postlude: [
12+
"let g:__test_denops_events = []",
13+
"autocmd User DenopsPlugin* call add(g:__test_denops_events, expand('<amatch>'))",
14+
"autocmd User DummyInterruptPlugin:* call add(g:__test_denops_events, expand('<amatch>'))",
15+
"runtime plugin/denops.vim",
16+
// NOTE: Disable startup on VimEnter.
17+
"autocmd! denops_plugin_internal_startup VimEnter",
18+
],
19+
fn: async ({ host, t }) => {
20+
await t.step("if the server is not yet running", async (t) => {
21+
await t.step("returns immediately", async () => {
22+
await host.call("denops#interrupt");
23+
});
24+
});
25+
26+
// Start the server and wait.
27+
await host.call("denops#server#start");
28+
await wait(() => host.call("eval", "denops#server#status() ==# 'running'"));
29+
30+
await t.step("if the plugin is not yet loaded", async (t) => {
31+
await t.step("returns immediately", async () => {
32+
await host.call("denops#interrupt");
33+
});
34+
});
35+
36+
await t.step("if the plugin is loaded", async (t) => {
37+
// Load plugin and wait.
38+
await host.call("execute", [
39+
"let g:__test_denops_events = []",
40+
`call denops#plugin#load('dummy', '${scriptInterrupt}')`,
41+
], "");
42+
await wait(async () =>
43+
(await host.call("eval", "g:__test_denops_events") as string[])
44+
.includes("DenopsPluginPost:dummy")
45+
);
46+
47+
await t.step("returns immediately", async () => {
48+
await host.call("execute", [
49+
"let g:__test_denops_events = []",
50+
], "");
51+
52+
await host.call("denops#interrupt");
53+
54+
// It should passed because of delay(MIMIC_ABORT_DELAY) in dummy_interrupt_plugin.ts
55+
assertEquals(await host.call("eval", "g:__test_denops_events"), []);
56+
});
57+
58+
await t.step("sends signal to the plugin", async () => {
59+
await wait(() => host.call("eval", "len(g:__test_denops_events)"));
60+
const events = await host.call(
61+
"eval",
62+
"g:__test_denops_events",
63+
) as string[];
64+
assert(
65+
events.some((event) =>
66+
event.startsWith("DummyInterruptPlugin:Interrupted:")
67+
),
68+
`Expected event 'DummyInterruptPlugin:Interrupted:*', but: ${
69+
JSON.stringify(events)
70+
}`,
71+
);
72+
});
73+
74+
// Reset interrupt event handler.
75+
await host.call("denops#request", "dummy", "reset", []);
76+
77+
await t.step("sends signal to the plugin with reason", async () => {
78+
await host.call("execute", [
79+
"let g:__test_denops_events = []",
80+
], "");
81+
82+
await host.call("denops#interrupt", "test");
83+
84+
await wait(() => host.call("eval", "len(g:__test_denops_events)"));
85+
assertEquals(await host.call("eval", "g:__test_denops_events"), [
86+
"DummyInterruptPlugin:Interrupted:test",
87+
]);
88+
});
89+
});
90+
},
91+
});

tests/denops/runtime/functions/denops/notify_test.ts

Lines changed: 39 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@ import { resolveTestDataPath } from "/denops-testdata/resolve.ts";
55
import { testHost } from "/denops-testutil/host.ts";
66
import { wait } from "/denops-testutil/wait.ts";
77

8-
const ASYNC_DELAY = 100;
8+
const MESSAGE_DELAY = 200;
99

10-
const scriptValid = resolveTestDataPath("dummy_valid_plugin.ts");
10+
const scriptDispatcher = resolveTestDataPath("dummy_dispatcher_plugin.ts");
1111

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

2930
for (const [plugin_name, label] of INVALID_PLUGIN_NAMES) {
@@ -38,23 +39,51 @@ testHost({
3839
// Load plugin and wait.
3940
await host.call("execute", [
4041
"let g:__test_denops_events = []",
41-
`call denops#plugin#load('dummyLoaded', '${scriptValid}')`,
42+
`call denops#plugin#load('dummy', '${scriptDispatcher}')`,
4243
], "");
4344
await wait(async () =>
4445
(await host.call("eval", "g:__test_denops_events") as string[])
45-
.includes("DenopsPluginPost:dummyLoaded")
46+
.includes("DenopsPluginPost:dummy")
4647
);
4748

48-
outputs = [];
49-
await host.call("denops#notify", "dummyLoaded", "test", ["foo"]);
49+
await t.step("returns immediately", async () => {
50+
await host.call("execute", [
51+
"let g:__test_denops_events = []",
52+
], "");
5053

51-
await t.step("returns immediately", () => {
52-
assertEquals(outputs, []);
54+
await host.call("denops#notify", "dummy", "test", ["foo"]);
55+
56+
assertEquals(await host.call("eval", "g:__test_denops_events"), []);
5357
});
5458

5559
await t.step("calls dispatcher method", async () => {
56-
await delay(ASYNC_DELAY);
57-
assertStringIncludes(outputs.join(""), 'This is test call: ["foo"]');
60+
await wait(() => host.call("eval", "len(g:__test_denops_events)"));
61+
assertEquals(await host.call("eval", "g:__test_denops_events"), [
62+
'DummyDispatcherPlugin:TestCalled:["foo"]',
63+
]);
64+
});
65+
66+
await t.step("if the dispatcher method is not exist", async (t) => {
67+
await t.step("returns immediately (flaky)", async () => {
68+
outputs = [];
69+
70+
await host.call(
71+
"denops#notify",
72+
"dummy",
73+
"not_exist_method",
74+
["foo"],
75+
);
76+
77+
assertEquals(outputs, []);
78+
});
79+
80+
await t.step("outputs an error message", async () => {
81+
await delay(MESSAGE_DELAY);
82+
assertStringIncludes(
83+
outputs.join(""),
84+
"Failed to call 'not_exist_method' API in 'dummy'",
85+
);
86+
});
5887
});
5988
});
6089
},

tests/denops/runtime/functions/denops/request_async_test.ts

Lines changed: 72 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,32 @@
11
import {
2+
assertArrayIncludes,
23
assertEquals,
34
assertObjectMatch,
4-
assertStringIncludes,
55
} from "jsr:@std/assert@^1.0.1";
66
import { INVALID_PLUGIN_NAMES } from "/denops-testdata/invalid_plugin_names.ts";
77
import { resolveTestDataPath } from "/denops-testdata/resolve.ts";
88
import { testHost } from "/denops-testutil/host.ts";
99
import { wait } from "/denops-testutil/wait.ts";
1010

11-
const scriptValid = resolveTestDataPath("dummy_valid_plugin.ts");
11+
const scriptDispatcher = resolveTestDataPath("dummy_dispatcher_plugin.ts");
1212

1313
testHost({
1414
name: "denops#request_async()",
1515
mode: "all",
1616
postlude: [
1717
"runtime plugin/denops.vim",
1818
],
19-
fn: async ({ host, t, stderr, mode }) => {
20-
let outputs: string[] = [];
21-
stderr.pipeTo(
22-
new WritableStream({ write: (s) => void outputs.push(s) }),
23-
).catch(() => {});
19+
fn: async ({ host, t }) => {
2420
await wait(() => host.call("eval", "denops#server#status() ==# 'running'"));
2521
await host.call("execute", [
2622
"let g:__test_denops_events = []",
2723
"autocmd User DenopsPlugin* call add(g:__test_denops_events, expand('<amatch>'))",
24+
"autocmd User DummyDispatcherPlugin:* call add(g:__test_denops_events, expand('<amatch>'))",
2825
"function TestDenopsRequestAsyncSuccess(...)",
29-
" call add(g:__test_denops_events, ['TestDenopsRequestAsyncSuccess', a:000])",
26+
" call add(g:__test_denops_events, ['TestDenopsRequestAsyncSuccess:Called', a:000])",
3027
"endfunction",
3128
"function TestDenopsRequestAsyncFailure(...)",
32-
" call add(g:__test_denops_events, ['TestDenopsRequestAsyncFailure', a:000])",
29+
" call add(g:__test_denops_events, ['TestDenopsRequestAsyncFailure:Called', a:000])",
3330
"endfunction",
3431
], "");
3532

@@ -53,10 +50,10 @@ testHost({
5350
await t.step("calls failure callback", async () => {
5451
await wait(() => host.call("eval", "len(g:__test_denops_events)"));
5552
assertObjectMatch(
56-
await host.call("eval", "g:__test_denops_events") as [],
53+
await host.call("eval", "g:__test_denops_events") as unknown[],
5754
{
5855
0: [
59-
"TestDenopsRequestAsyncFailure",
56+
"TestDenopsRequestAsyncFailure:Called",
6057
[
6158
{
6259
message: `Invalid plugin name: ${plugin_name}`,
@@ -74,43 +71,82 @@ testHost({
7471
// Load plugin and wait.
7572
await host.call("execute", [
7673
"let g:__test_denops_events = []",
77-
`call denops#plugin#load('dummyLoaded', '${scriptValid}')`,
74+
`call denops#plugin#load('dummy', '${scriptDispatcher}')`,
7875
], "");
7976
await wait(async () =>
8077
(await host.call("eval", "g:__test_denops_events") as string[])
81-
.includes("DenopsPluginPost:dummyLoaded")
78+
.includes("DenopsPluginPost:dummy")
8279
);
83-
await host.call("execute", [
84-
"let g:__test_denops_events = []",
85-
], "");
8680

87-
outputs = [];
88-
await host.call(
89-
"denops#request_async",
90-
"dummyLoaded",
91-
"test",
92-
["foo"],
93-
"TestDenopsRequestAsyncSuccess",
94-
"TestDenopsRequestAsyncFailure",
95-
);
81+
await t.step("returns immediately", async () => {
82+
await host.call("execute", [
83+
"let g:__test_denops_events = []",
84+
], "");
85+
86+
await host.call(
87+
"denops#request_async",
88+
"dummy",
89+
"test",
90+
["foo"],
91+
"TestDenopsRequestAsyncSuccess",
92+
"TestDenopsRequestAsyncFailure",
93+
);
9694

97-
await t.step("returns immediately", () => {
98-
assertEquals(outputs, []);
95+
assertEquals(await host.call("eval", "g:__test_denops_events"), []);
9996
});
10097

101-
await t.step("calls success callback", async () => {
98+
await t.step("calls dispatcher method", async () => {
10299
await wait(() => host.call("eval", "len(g:__test_denops_events)"));
103-
const returnValue = mode === "vim" ? null : 0;
104-
assertObjectMatch(
105-
await host.call("eval", "g:__test_denops_events") as [],
106-
{
107-
0: ["TestDenopsRequestAsyncSuccess", [returnValue]],
108-
},
100+
assertArrayIncludes(
101+
await host.call("eval", "g:__test_denops_events") as unknown[],
102+
['DummyDispatcherPlugin:TestCalled:["foo"]'],
103+
);
104+
});
105+
106+
await t.step("calls success callback", async () => {
107+
assertArrayIncludes(
108+
await host.call("eval", "g:__test_denops_events") as unknown[],
109+
[
110+
[
111+
"TestDenopsRequestAsyncSuccess:Called",
112+
[{ result: "OK", args: ["foo"] }],
113+
],
114+
],
109115
);
110116
});
111117

112-
await t.step("calls dispatcher method", () => {
113-
assertStringIncludes(outputs.join(""), 'This is test call: ["foo"]');
118+
await t.step("if the dispatcher method is not exist", async (t) => {
119+
await t.step("returns immediately", async () => {
120+
await host.call("execute", [
121+
"let g:__test_denops_events = []",
122+
"call denops#request_async('dummy', 'not_exist_method', ['foo'], 'TestDenopsRequestAsyncSuccess', 'TestDenopsRequestAsyncFailure')",
123+
"let g:__test_denops_events_after_called = g:__test_denops_events->copy()",
124+
], "");
125+
126+
assertEquals(
127+
await host.call("eval", "g:__test_denops_events_after_called"),
128+
[],
129+
);
130+
});
131+
132+
await t.step("calls failure callback", async () => {
133+
await wait(() => host.call("eval", "len(g:__test_denops_events)"));
134+
assertObjectMatch(
135+
await host.call("eval", "g:__test_denops_events") as unknown[],
136+
{
137+
0: [
138+
"TestDenopsRequestAsyncFailure:Called",
139+
[
140+
{
141+
message:
142+
"Failed to call 'not_exist_method' API in 'dummy': this[#denops].dispatcher[fn] is not a function",
143+
name: "Error",
144+
},
145+
],
146+
],
147+
},
148+
);
149+
});
114150
});
115151
});
116152
},

0 commit comments

Comments
 (0)