Skip to content

Commit d4d2c4e

Browse files
authored
Merge pull request #443 from vim-denops/fix-tests-flaky
2 parents 10ae0c8 + c6a1901 commit d4d2c4e

File tree

6 files changed

+77
-64
lines changed

6 files changed

+77
-64
lines changed

tests/denops/runtime/functions/server/close_test.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -217,19 +217,22 @@ testHost({
217217
"call denops#server#close()",
218218
], "");
219219

220+
// Wait for times out
221+
await delay(10);
222+
220223
await host.call("execute", [
221224
"let g:__test_denops_server_status_after_close_called = denops#server#status()",
222225
], "");
223226

224-
await t.step("closes the channel forcibly", async () => {
227+
await t.step("closes the channel forcibly (flaky)", async () => {
225228
const actual = await host.call(
226229
"eval",
227230
"g:__test_denops_server_status_after_close_called",
228231
);
229232
assertEquals(actual, "stopped");
230233
});
231234

232-
await t.step("fires DenopsClosed", async () => {
235+
await t.step("fires DenopsClosed (flaky)", async () => {
233236
assert(await host.call("exists", "g:__test_denops_closed_fired"));
234237
});
235238

tests/denops/runtime/functions/server/start_test.ts

Lines changed: 32 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -142,12 +142,30 @@ testHost({
142142
// 2nd call, do nothing.
143143
"let g:__test_denops_server_start_result_2nd = denops#server#start()",
144144
"let g:__test_denops_server_status_2nd = denops#server#status()",
145-
], "");
146-
await host.call("execute", [
147145
// 3rd call with asynchronously, do nothing.
148-
"let g:__test_denops_server_start_result_3rd = denops#server#start()",
149-
"let g:__test_denops_server_status_3rd = denops#server#status()",
146+
"function! TestDenopsServerStartAlreadyStarting(...) abort",
147+
" try",
148+
" let g:__test_denops_server_start_result_3rd = denops#server#start()",
149+
" let g:__test_denops_server_status_3rd = denops#server#status()",
150+
" catch",
151+
" let g:__test_denops_server_error_3rd = v:exception",
152+
" finally",
153+
" let g:__test_denops_server_called_3rd = 1",
154+
" endtry",
155+
"endfunction",
156+
"call timer_start(0, 'TestDenopsServerStartAlreadyStarting')",
150157
], "");
158+
// Wait for the 3rd call to finish.
159+
await wait(
160+
() => host.call("exists", "g:__test_denops_server_called_3rd"),
161+
{ message: "3rd call to start() is not finished" },
162+
);
163+
164+
const error = await host.call(
165+
"eval",
166+
"get(g:, '__test_denops_server_error_3rd', '')",
167+
);
168+
assertEquals(error, "", "should not throws an error");
151169

152170
await t.step("returns falsy", async () => {
153171
assertFalse(
@@ -158,13 +176,16 @@ testHost({
158176
);
159177
});
160178

161-
await t.step("does not change status from 'running'", async () => {
162-
const actual = await host.call(
163-
"eval",
164-
"[g:__test_denops_server_status_2nd, g:__test_denops_server_status_3rd]",
165-
);
166-
assertEquals(actual, ["starting", "starting"]);
167-
});
179+
await t.step(
180+
"does not change status from 'starting' (flaky)",
181+
async () => {
182+
const actual = await host.call(
183+
"eval",
184+
"[g:__test_denops_server_status_2nd, g:__test_denops_server_status_3rd]",
185+
);
186+
assertEquals(actual, ["starting", "starting"]);
187+
},
188+
);
168189
});
169190

170191
await t.step("if already connected to shared-server", async (t) => {

tests/denops/runtime/functions/server/status_test.ts

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,12 @@ testHost({
3030
await host.call("execute", [
3131
"call denops#server#start()",
3232
"let g:__test_denops_server_status_when_start_called = denops#server#status()",
33+
"function! TestDenopsServerStatusBeforeReady(...) abort",
34+
" if exists('g:__test_denops_ready_fired') | return 0 | endif",
35+
" let g:__test_denops_server_status_before_ready = denops#server#status()",
36+
" call timer_start(0, 'TestDenopsServerStatusBeforeReady')",
37+
"endfunction",
38+
"call TestDenopsServerStatusBeforeReady()",
3339
], "");
3440

3541
await t.step(
@@ -46,14 +52,10 @@ testHost({
4652
await t.step(
4753
"returns 'preparing' before DenopsReady is fired (flaky)",
4854
async () => {
49-
const actual = await wait(() =>
50-
host.call(
51-
"eval",
52-
"exists('g:__test_denops_ready_fired')" +
53-
"? 'DenopsReady is fired (flaky result)'" +
54-
": denops#server#status() !=# 'starting'" +
55-
" ? denops#server#status() : 0",
56-
)
55+
await wait(() => host.call("exists", "g:__test_denops_ready_fired"));
56+
const actual = await host.call(
57+
"eval",
58+
"g:__test_denops_server_status_before_ready",
5759
);
5860
assertEquals(actual, "preparing");
5961
},

tests/denops/testutil/shared_server.ts

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -82,11 +82,7 @@ export async function useSharedServer(
8282
addrPromise.finally(() => addrReader.cancel());
8383

8484
const abort = async (reason: unknown) => {
85-
try {
86-
aborter.abort(reason);
87-
} catch {
88-
// Already exited, do nothing.
89-
}
85+
aborter.abort(reason);
9086
await proc.status;
9187
await Promise.allSettled([
9288
proc.stdout.cancel(reason),
@@ -96,12 +92,13 @@ export async function useSharedServer(
9692

9793
try {
9894
const addr = await deadline(addrPromise, timeout);
99-
assert(typeof addr === "string");
100-
const [_, host, port] = addr.match(/^([^:]*):(\d+)(?:\n|$)/) ?? [];
95+
assert(addr, "Shared server did not returns an address");
96+
const [_, host, portStr] = addr.match(/^([^:]*):(\d+)(?:\n|$)/) ?? [];
97+
const port = Number.parseInt(portStr);
10198
return {
10299
addr: {
103100
host,
104-
port: Number.parseInt(port),
101+
port,
105102
toString: () => `${host}:${port}`,
106103
},
107104
stdout: stdoutReader2,

tests/denops/testutil/shared_server_test.ts

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
assertRejects,
77
} from "jsr:@std/assert@^1.0.1";
88
import { delay } from "jsr:@std/async@^1.0.1/delay";
9+
import { retry } from "jsr:@std/async@^1.0.1/retry";
910
import { resolveTestDataPath } from "/denops-testdata/resolve.ts";
1011
import { useSharedServer } from "./shared_server.ts";
1112

@@ -104,13 +105,15 @@ Deno.test("useSharedServer()", async (t) => {
104105
});
105106
});
106107

107-
await t.step("closes child process when rejectes", async () => {
108-
await assertRejects(
109-
async () => {
110-
await useSharedServer({ timeout: 0 });
111-
},
112-
Error,
113-
"Signal timed out",
114-
);
108+
await t.step("rejects if the server startup times out (flaky)", async () => {
109+
await retry(async () => {
110+
await assertRejects(
111+
async () => {
112+
await using _server = await useSharedServer({ timeout: 0 });
113+
},
114+
Error,
115+
"Signal timed out",
116+
);
117+
});
115118
});
116119
});

tests/denops/testutil/wait.ts

Lines changed: 14 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import { AssertionError } from "jsr:@std/assert@^1.0.1/assertion-error";
2+
import { abortable } from "jsr:@std/async@^1.0.1/abortable";
3+
import { delay } from "jsr:@std/async@^1.0.1/delay";
24
import { getConfig } from "./conf.ts";
35

46
const DEFAULT_TIMEOUT = 30_000;
@@ -32,40 +34,25 @@ export async function wait<T>(
3234
interval = DEFAULT_INTERVAL,
3335
message,
3436
} = { ...getConfig(), ...options };
35-
const TIMEOUT = {};
37+
const signal = AbortSignal.timeout(timeout);
3638

37-
let timeoutId: number | undefined;
38-
const timeoutPromise = new Promise<never>((_, reject) => {
39-
timeoutId = setTimeout(() => reject(TIMEOUT), timeout);
40-
});
41-
42-
let intervalId: number | undefined;
43-
const delay = () =>
44-
new Promise<void>((resolve) => {
45-
intervalId = setTimeout(resolve, interval);
46-
});
39+
const waitTruthy = async () => {
40+
for (;;) {
41+
const res = await fn();
42+
if (res) {
43+
return res;
44+
}
45+
await delay(interval, { signal });
46+
}
47+
};
4748

4849
try {
49-
return await Promise.race([
50-
(async () => {
51-
for (;;) {
52-
const res = await fn();
53-
if (res) {
54-
return res;
55-
}
56-
await delay();
57-
}
58-
})(),
59-
timeoutPromise,
60-
]);
50+
return await abortable(waitTruthy(), signal);
6151
} catch (e) {
62-
if (e === TIMEOUT) {
52+
if (e === signal.reason) {
6353
const suffix = message ? `: ${message}` : ".";
6454
throw new AssertionError(`Timeout in ${timeout} millisec${suffix}`);
6555
}
6656
throw e;
67-
} finally {
68-
clearTimeout(timeoutId);
69-
clearTimeout(intervalId);
7057
}
7158
}

0 commit comments

Comments
 (0)