summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDavid Sherret <dsherret@users.noreply.github.com>2021-09-17 09:02:23 -0400
committerGitHub <noreply@github.com>2021-09-17 09:02:23 -0400
commitc8b43a032887a077f50f3c88e3b4f170846c2d9d (patch)
tree1fd4a44798173ee89e7289abbb9c6588095d88df
parent4b79e5a459de16bb85c70eaf9c716e51f1c281bd (diff)
chore(tests): fix flaky flock tests (#12099)
-rw-r--r--cli/tests/unit/flock_test.ts238
1 files changed, 150 insertions, 88 deletions
diff --git a/cli/tests/unit/flock_test.ts b/cli/tests/unit/flock_test.ts
index 13d09bcf5..6c4f1e90d 100644
--- a/cli/tests/unit/flock_test.ts
+++ b/cli/tests/unit/flock_test.ts
@@ -1,102 +1,164 @@
// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
import { assertEquals, unitTest } from "./test_util.ts";
+import { readAll } from "../../../test_util/std/io/util.ts";
unitTest(
{ perms: { read: true, run: true, hrtime: true } },
async function flockFileSync() {
- const path = "cli/tests/testdata/fixture.json";
- 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",
- );
+ await runFlockTests({ sync: true });
},
);
unitTest(
{ perms: { read: true, run: true, hrtime: true } },
async function flockFileAsync() {
- const path = "cli/tests/testdata/fixture.json";
- 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",
- );
+ await runFlockTests({ sync: false });
},
);
+
+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();
+ },
+ };
+}