mirror of
https://github.com/denoland/deno.git
synced 2025-01-22 23:19:55 -05:00
chore(tests): fix flaky flock tests (#12099)
This commit is contained in:
parent
4b79e5a459
commit
c8b43a0328
1 changed files with 150 additions and 88 deletions
|
@ -1,102 +1,164 @@
|
||||||
// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
|
// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
|
||||||
import { assertEquals, unitTest } from "./test_util.ts";
|
import { assertEquals, unitTest } from "./test_util.ts";
|
||||||
|
import { readAll } from "../../../test_util/std/io/util.ts";
|
||||||
|
|
||||||
unitTest(
|
unitTest(
|
||||||
{ perms: { read: true, run: true, hrtime: true } },
|
{ perms: { read: true, run: true, hrtime: true } },
|
||||||
async function flockFileSync() {
|
async function flockFileSync() {
|
||||||
const path = "cli/tests/testdata/fixture.json";
|
await runFlockTests({ sync: true });
|
||||||
const script = (exclusive: boolean, wait: number) => `
|
|
||||||
const { rid } = Deno.openSync("${path}");
|
|
||||||
Deno.flockSync(rid, ${exclusive ? "true" : "false"});
|
|
||||||
await new Promise(res => setTimeout(res, ${wait}));
|
|
||||||
Deno.funlockSync(rid);
|
|
||||||
`;
|
|
||||||
const run = (e: boolean, w: number) =>
|
|
||||||
Deno.run({ cmd: [Deno.execPath(), "eval", "--unstable", script(e, w)] });
|
|
||||||
const firstBlocksSecond = async (
|
|
||||||
first: boolean,
|
|
||||||
second: boolean,
|
|
||||||
): Promise<boolean> => {
|
|
||||||
const firstPs = run(first, 1000);
|
|
||||||
await new Promise((res) => setTimeout(res, 250));
|
|
||||||
const start = performance.now();
|
|
||||||
const secondPs = run(second, 0);
|
|
||||||
await secondPs.status();
|
|
||||||
const didBlock = (performance.now() - start) > 500;
|
|
||||||
firstPs.close();
|
|
||||||
secondPs.close();
|
|
||||||
return didBlock;
|
|
||||||
};
|
|
||||||
|
|
||||||
assertEquals(
|
|
||||||
await firstBlocksSecond(true, false),
|
|
||||||
true,
|
|
||||||
"exclusive blocks shared",
|
|
||||||
);
|
|
||||||
assertEquals(
|
|
||||||
await firstBlocksSecond(false, true),
|
|
||||||
true,
|
|
||||||
"shared blocks exclusive",
|
|
||||||
);
|
|
||||||
assertEquals(
|
|
||||||
await firstBlocksSecond(true, true),
|
|
||||||
true,
|
|
||||||
"exclusive blocks exclusive",
|
|
||||||
);
|
|
||||||
assertEquals(
|
|
||||||
await firstBlocksSecond(false, false),
|
|
||||||
false,
|
|
||||||
"shared does not block shared",
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
unitTest(
|
unitTest(
|
||||||
{ perms: { read: true, run: true, hrtime: true } },
|
{ perms: { read: true, run: true, hrtime: true } },
|
||||||
async function flockFileAsync() {
|
async function flockFileAsync() {
|
||||||
const path = "cli/tests/testdata/fixture.json";
|
await runFlockTests({ sync: false });
|
||||||
const script = (exclusive: boolean, wait: number) => `
|
|
||||||
const { rid } = await Deno.open("${path}");
|
|
||||||
await Deno.flock(rid, ${exclusive ? "true" : "false"});
|
|
||||||
await new Promise(res => setTimeout(res, ${wait}));
|
|
||||||
await Deno.funlock(rid);
|
|
||||||
`;
|
|
||||||
const run = (e: boolean, w: number) =>
|
|
||||||
Deno.run({ cmd: [Deno.execPath(), "eval", "--unstable", script(e, w)] });
|
|
||||||
const firstBlocksSecond = async (
|
|
||||||
first: boolean,
|
|
||||||
second: boolean,
|
|
||||||
): Promise<boolean> => {
|
|
||||||
const firstPs = run(first, 1000);
|
|
||||||
await new Promise((res) => setTimeout(res, 250));
|
|
||||||
const start = performance.now();
|
|
||||||
const secondPs = run(second, 0);
|
|
||||||
await secondPs.status();
|
|
||||||
const didBlock = (performance.now() - start) > 500;
|
|
||||||
firstPs.close();
|
|
||||||
secondPs.close();
|
|
||||||
return didBlock;
|
|
||||||
};
|
|
||||||
|
|
||||||
assertEquals(
|
|
||||||
await firstBlocksSecond(true, false),
|
|
||||||
true,
|
|
||||||
"exclusive blocks shared",
|
|
||||||
);
|
|
||||||
assertEquals(
|
|
||||||
await firstBlocksSecond(false, true),
|
|
||||||
true,
|
|
||||||
"shared blocks exclusive",
|
|
||||||
);
|
|
||||||
assertEquals(
|
|
||||||
await firstBlocksSecond(true, true),
|
|
||||||
true,
|
|
||||||
"exclusive blocks exclusive",
|
|
||||||
);
|
|
||||||
assertEquals(
|
|
||||||
await firstBlocksSecond(false, false),
|
|
||||||
false,
|
|
||||||
"shared does not block shared",
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
async function runFlockTests(opts: { sync: boolean }) {
|
||||||
|
assertEquals(
|
||||||
|
await checkFirstBlocksSecond({
|
||||||
|
firstExclusive: true,
|
||||||
|
secondExclusive: false,
|
||||||
|
sync: opts.sync,
|
||||||
|
}),
|
||||||
|
true,
|
||||||
|
"exclusive blocks shared",
|
||||||
|
);
|
||||||
|
assertEquals(
|
||||||
|
await checkFirstBlocksSecond({
|
||||||
|
firstExclusive: false,
|
||||||
|
secondExclusive: true,
|
||||||
|
sync: opts.sync,
|
||||||
|
}),
|
||||||
|
true,
|
||||||
|
"shared blocks exclusive",
|
||||||
|
);
|
||||||
|
assertEquals(
|
||||||
|
await checkFirstBlocksSecond({
|
||||||
|
firstExclusive: true,
|
||||||
|
secondExclusive: true,
|
||||||
|
sync: opts.sync,
|
||||||
|
}),
|
||||||
|
true,
|
||||||
|
"exclusive blocks exclusive",
|
||||||
|
);
|
||||||
|
assertEquals(
|
||||||
|
await checkFirstBlocksSecond({
|
||||||
|
firstExclusive: false,
|
||||||
|
secondExclusive: false,
|
||||||
|
sync: opts.sync,
|
||||||
|
}),
|
||||||
|
false,
|
||||||
|
"shared does not block shared",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function checkFirstBlocksSecond(opts: {
|
||||||
|
firstExclusive: boolean;
|
||||||
|
secondExclusive: boolean;
|
||||||
|
sync: boolean;
|
||||||
|
}) {
|
||||||
|
const firstProcess = runFlockTestProcess({
|
||||||
|
exclusive: opts.firstExclusive,
|
||||||
|
sync: opts.sync,
|
||||||
|
});
|
||||||
|
const secondProcess = runFlockTestProcess({
|
||||||
|
exclusive: opts.secondExclusive,
|
||||||
|
sync: opts.sync,
|
||||||
|
});
|
||||||
|
try {
|
||||||
|
const sleep = (time: number) => new Promise((r) => setTimeout(r, time));
|
||||||
|
|
||||||
|
// wait for both processes to signal that they're ready
|
||||||
|
await Promise.all([firstProcess.waitSignal(), secondProcess.waitSignal()]);
|
||||||
|
|
||||||
|
// signal to the first process to enter the lock
|
||||||
|
await firstProcess.signal();
|
||||||
|
await firstProcess.waitSignal(); // entering signal
|
||||||
|
await firstProcess.waitSignal(); // entered signal
|
||||||
|
await sleep(20);
|
||||||
|
// signal the second to enter the lock
|
||||||
|
await secondProcess.signal();
|
||||||
|
await secondProcess.waitSignal(); // entering signal
|
||||||
|
await sleep(20);
|
||||||
|
// signal to the first to exit the lock
|
||||||
|
await firstProcess.signal();
|
||||||
|
await sleep(20);
|
||||||
|
// signal to the second to exit the lock
|
||||||
|
await secondProcess.waitSignal(); // entered signal
|
||||||
|
await secondProcess.signal();
|
||||||
|
// collect the remaining JSON output of both processes
|
||||||
|
const firstPsTimes = await firstProcess.getTimes();
|
||||||
|
const secondPsTimes = await secondProcess.getTimes();
|
||||||
|
return firstPsTimes.exitTime < secondPsTimes.enterTime;
|
||||||
|
} finally {
|
||||||
|
firstProcess.close();
|
||||||
|
secondProcess.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function runFlockTestProcess(opts: { exclusive: boolean; sync: boolean }) {
|
||||||
|
const path = "cli/tests/testdata/fixture.json";
|
||||||
|
const scriptText = `
|
||||||
|
const { rid } = Deno.openSync("${path}");
|
||||||
|
|
||||||
|
// ready signal
|
||||||
|
Deno.stdout.writeSync(new Uint8Array(1));
|
||||||
|
// wait for enter lock signal
|
||||||
|
Deno.stdin.readSync(new Uint8Array(1));
|
||||||
|
|
||||||
|
// entering signal
|
||||||
|
Deno.stdout.writeSync(new Uint8Array(1));
|
||||||
|
// lock and record the entry time
|
||||||
|
${
|
||||||
|
opts.sync
|
||||||
|
? `Deno.flockSync(rid, ${opts.exclusive ? "true" : "false"});`
|
||||||
|
: `await Deno.flock(rid, ${opts.exclusive ? "true" : "false"});`
|
||||||
|
}
|
||||||
|
const enterTime = new Date().getTime();
|
||||||
|
// entered signal
|
||||||
|
Deno.stdout.writeSync(new Uint8Array(1));
|
||||||
|
|
||||||
|
// wait for exit lock signal
|
||||||
|
Deno.stdin.readSync(new Uint8Array(1));
|
||||||
|
|
||||||
|
// record the exit time and wait a little bit before releasing
|
||||||
|
// the lock so that the enter time of the next process doesn't
|
||||||
|
// occur at the same time as this exit time (do double the
|
||||||
|
// windows clock resolution)
|
||||||
|
const exitTime = new Date().getTime();
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 30));
|
||||||
|
|
||||||
|
// release the lock
|
||||||
|
${opts.sync ? "Deno.funlockSync(rid);" : "await Deno.funlock(rid);"}
|
||||||
|
|
||||||
|
// output the enter and exit time
|
||||||
|
console.log(JSON.stringify({ enterTime, exitTime }));
|
||||||
|
`;
|
||||||
|
|
||||||
|
const process = Deno.run({
|
||||||
|
cmd: [Deno.execPath(), "eval", "--unstable", scriptText],
|
||||||
|
stdout: "piped",
|
||||||
|
stdin: "piped",
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
waitSignal: () => process.stdout.read(new Uint8Array(1)),
|
||||||
|
signal: () => process.stdin.write(new Uint8Array(1)),
|
||||||
|
getTimes: async () => {
|
||||||
|
const outputBytes = await readAll(process.stdout);
|
||||||
|
const text = new TextDecoder().decode(outputBytes);
|
||||||
|
return JSON.parse(text) as {
|
||||||
|
enterTime: number;
|
||||||
|
exitTime: number;
|
||||||
|
};
|
||||||
|
},
|
||||||
|
close: () => {
|
||||||
|
process.stdout.close();
|
||||||
|
process.stdin.close();
|
||||||
|
process.close();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue