Skip to content

Commit

Permalink
🌿 fix flaky tests for dispatcher
Browse files Browse the repository at this point in the history
  • Loading branch information
Milly committed Sep 18, 2024
1 parent 7648a1e commit b2cd780
Show file tree
Hide file tree
Showing 4 changed files with 175 additions and 57 deletions.
48 changes: 39 additions & 9 deletions tests/denops/runtime/functions/denops/notify_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@ 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 +25,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 +40,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 delay(100 + ASYNC_DELAY);
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);
assertStringIncludes(
outputs.join(""),
"Failed to call 'not_exist_method' API in 'dummy'",
);
});
});
});
},
Expand Down
115 changes: 79 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,35 @@
import {
assertArrayIncludes,
assertEquals,
assertObjectMatch,
assertStringIncludes,
} from "jsr:@std/assert@^1.0.1";
import { delay } from "jsr:@std/async@^1.0.1/delay";
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 ASYNC_DELAY = 100;

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 +53,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 +74,86 @@ 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 dispatcher method", async () => {
await delay(100 + ASYNC_DELAY);
assertArrayIncludes(
await host.call("eval", "g:__test_denops_events") as unknown[],
['DummyDispatcherPlugin:TestCalled:["foo"]'],
);
});

await t.step("calls success callback", 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[],
[
[
"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 = []",
], "");

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

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

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
53 changes: 41 additions & 12 deletions tests/denops/runtime/functions/denops/request_test.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,23 @@
import { assertRejects, assertStringIncludes } from "jsr:@std/assert@^1.0.1";
import { assertEquals, assertRejects } 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()",
mode: "all",
postlude: [
"runtime plugin/denops.vim",
],
fn: async ({ host, t, stderr }) => {
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>'))",
], "");

for (const [plugin_name, label] of INVALID_PLUGIN_NAMES) {
Expand All @@ -39,18 +36,50 @@ 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 t.step("calls dispatcher method", async () => {
outputs = [];
await host.call("denops#request", "dummyLoaded", "test", ["foo"]);
await host.call("execute", [
"let g:__test_denops_events = []",
], "");

assertStringIncludes(outputs.join(""), 'This is test call: ["foo"]');
await host.call("denops#request", "dummy", "test", ["foo"]);

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

await t.step("returns dispatcher method return value", async () => {
const result = await host.call(
"denops#request",
"dummy",
"test",
["foo"],
);

assertEquals(result, { result: "OK", args: ["foo"] });
});

await t.step("if the dispatcher method is not exist", async (t) => {
await t.step("throws an error", async () => {
await assertRejects(
() =>
host.call(
"denops#request",
"dummy",
"not_exist_method",
["foo"],
),
Error,
"Failed to call 'not_exist_method' API in 'dummy'",
);
});
});
});
},
Expand Down
16 changes: 16 additions & 0 deletions tests/denops/testdata/dummy_dispatcher_plugin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { delay } from "jsr:@std/async@^1.0.1/delay";
import type { Entrypoint } from "jsr:@denops/core@^7.0.0";

export const main: Entrypoint = (denops) => {
denops.dispatcher = {
test: async (...args) => {
await delay(100);
await denops.cmd(
`execute 'doautocmd <nomodeline> User' fnameescape('DummyDispatcherPlugin:TestCalled:${
JSON.stringify(args)
}')`,
);
return { result: "OK", args };
},
};
};

0 comments on commit b2cd780

Please sign in to comment.