summaryrefslogtreecommitdiff
path: root/cli/tests
diff options
context:
space:
mode:
Diffstat (limited to 'cli/tests')
-rw-r--r--cli/tests/unit/files_test.ts195
1 files changed, 195 insertions, 0 deletions
diff --git a/cli/tests/unit/files_test.ts b/cli/tests/unit/files_test.ts
index a96f60a7a..aed09e814 100644
--- a/cli/tests/unit/files_test.ts
+++ b/cli/tests/unit/files_test.ts
@@ -898,3 +898,198 @@ Deno.test(
await Deno.remove(filename);
},
);
+
+Deno.test(
+ { permissions: { read: true, run: true, hrtime: true } },
+ async function fsFileLockFileSync() {
+ await runFlockTests({ sync: true });
+ },
+);
+
+Deno.test(
+ { permissions: { read: true, run: true, hrtime: true } },
+ async function fsFileLockFileAsync() {
+ 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,
+ // need to wait for both to enter the lock to prevent the case where the
+ // first process enters and exits the lock before the second even enters
+ waitBothEnteredLock: true,
+ }),
+ false,
+ "shared does not block shared",
+ );
+}
+
+async function checkFirstBlocksSecond(opts: {
+ firstExclusive: boolean;
+ secondExclusive: boolean;
+ sync: boolean;
+ waitBothEnteredLock?: 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));
+
+ await Promise.all([
+ firstProcess.waitStartup(),
+ secondProcess.waitStartup(),
+ ]);
+
+ await firstProcess.enterLock();
+ await firstProcess.waitEnterLock();
+
+ await secondProcess.enterLock();
+ await sleep(100);
+
+ if (!opts.waitBothEnteredLock) {
+ await firstProcess.exitLock();
+ }
+
+ await secondProcess.waitEnterLock();
+
+ if (opts.waitBothEnteredLock) {
+ await firstProcess.exitLock();
+ }
+
+ await secondProcess.exitLock();
+
+ // collect the final output
+ const firstPsTimes = await firstProcess.getTimes();
+ const secondPsTimes = await secondProcess.getTimes();
+ return firstPsTimes.exitTime < secondPsTimes.enterTime;
+ } finally {
+ await firstProcess.close();
+ await secondProcess.close();
+ }
+}
+
+function runFlockTestProcess(opts: { exclusive: boolean; sync: boolean }) {
+ const path = "cli/tests/testdata/assets/lock_target.txt";
+ const scriptText = `
+ const file = 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
+ ? `file.lockSync(${opts.exclusive ? "true" : "false"});`
+ : `await file.lock(${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
+ const exitTime = new Date().getTime();
+ await new Promise(resolve => setTimeout(resolve, 100));
+
+ // release the lock
+ ${opts.sync ? "file.unlockSync();" : "await file.unlock();"}
+
+ // exited signal
+ Deno.stdout.writeSync(new Uint8Array(1));
+
+ // output the enter and exit time
+ console.log(JSON.stringify({ enterTime, exitTime }));
+`;
+
+ const process = new Deno.Command(Deno.execPath(), {
+ args: ["eval", "--unstable", scriptText],
+ stdin: "piped",
+ stdout: "piped",
+ stderr: "null",
+ }).spawn();
+
+ const waitSignal = async () => {
+ const reader = process.stdout.getReader({ mode: "byob" });
+ await reader.read(new Uint8Array(1));
+ reader.releaseLock();
+ };
+ const signal = async () => {
+ const writer = process.stdin.getWriter();
+ await writer.write(new Uint8Array(1));
+ writer.releaseLock();
+ };
+
+ return {
+ async waitStartup() {
+ await waitSignal();
+ },
+ async enterLock() {
+ await signal();
+ await waitSignal(); // entering signal
+ },
+ async waitEnterLock() {
+ await waitSignal();
+ },
+ async exitLock() {
+ await signal();
+ await waitSignal();
+ },
+ getTimes: async () => {
+ const { stdout } = await process.output();
+ const text = new TextDecoder().decode(stdout);
+ return JSON.parse(text) as {
+ enterTime: number;
+ exitTime: number;
+ };
+ },
+ close: async () => {
+ await process.status;
+ await process.stdin.close();
+ },
+ };
+}