Skip to content

Commit a3606c5

Browse files
HugoMendes98Hugo Mendes
authored and
Hugo Mendes
committed
test(mutex): add test for lockWith functions
#2
1 parent 2bc30d7 commit a3606c5

File tree

2 files changed

+116
-8
lines changed

2 files changed

+116
-8
lines changed

src/mutex/mutex.spec.ts

Lines changed: 107 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { Mutex } from "./mutex";
2+
import { sleep } from "../../support/sleep";
23
import { timeFunction } from "../../support/time-function";
34
import {
45
ConcurrencyExceedTimeoutException,
@@ -7,6 +8,9 @@ import {
78
} from "../exceptions";
89

910
describe("Mutex", () => {
11+
const delay = 50;
12+
const offset = 5;
13+
1014
describe("Input validation", () => {
1115
const mutex = new Mutex();
1216

@@ -20,9 +24,6 @@ describe("Mutex", () => {
2024
});
2125

2226
describe("`lock` usage", () => {
23-
const delay = 50;
24-
const offset = 5;
25-
2627
it("should work with a basic usage", async () => {
2728
// semaphore as a mutex
2829
const mutex = new Mutex();
@@ -113,9 +114,6 @@ describe("Mutex", () => {
113114
});
114115

115116
describe("`tryLock` usage", () => {
116-
const delay = 50;
117-
const offset = 5;
118-
119117
it("should work with tryLock/unlock", async () => {
120118
const mutex = new Mutex();
121119

@@ -152,6 +150,106 @@ describe("Mutex", () => {
152150
});
153151
});
154152

153+
describe("`lockWith` usage", () => {
154+
it("should work like a regular `lock`/`unlock`", async () => {
155+
const mutex = new Mutex();
156+
157+
const [elapsed] = await timeFunction(async () => {
158+
expect(mutex.isLocked).toBeFalse();
159+
expect(mutex.queueLength).toBe(0);
160+
161+
void sleep(delay / 2).then(() => {
162+
// In the first mutex (that locked)
163+
expect(mutex.isLocked).toBeTrue();
164+
expect(mutex.queueLength).toBe(1);
165+
});
166+
167+
void sleep((delay / 2) * 3).then(() => {
168+
// In the second mutex (that locked)
169+
expect(mutex.isLocked).toBeTrue();
170+
expect(mutex.queueLength).toBe(0);
171+
});
172+
173+
await Promise.all([
174+
mutex.lockWith(() => sleep(delay)),
175+
mutex.lockWith(() => sleep(delay))
176+
]);
177+
178+
expect(mutex.isLocked).toBeFalse();
179+
expect(mutex.queueLength).toBe(0);
180+
});
181+
182+
expect(elapsed).toBeGreaterThanOrEqual(delay * 2 - offset);
183+
expect(elapsed).toBeLessThanOrEqual(delay * 2 + offset);
184+
});
185+
186+
it("should return the value from the critical section", async () => {
187+
const mutex = new Mutex();
188+
189+
const [v1, v2, v3] = await Promise.all([
190+
sleep(Math.random() * delay).then(() => mutex.lockWith(() => 1)),
191+
sleep(Math.random() * delay).then(() => mutex.lockWith(() => 2)),
192+
sleep(Math.random() * delay).then(() => mutex.lockWith(() => 3))
193+
]);
194+
195+
expect(v1).toBe(1);
196+
expect(v2).toBe(2);
197+
expect(v3).toBe(3);
198+
});
199+
});
200+
201+
describe("`tryLockWith` usage", () => {
202+
it("should work like a regular `tryLock`/`unlock`", async () => {
203+
const mutex = new Mutex();
204+
205+
const [elapsed] = await timeFunction(() =>
206+
Promise.all([
207+
mutex.tryLockWith(delay, () => sleep(delay / 2)),
208+
mutex.tryLockWith(delay, () => sleep(delay / 2))
209+
])
210+
);
211+
212+
// each `tryLockWith` keep the lock for delay / 2 time
213+
expect(elapsed).toBeGreaterThanOrEqual(delay - offset);
214+
expect(elapsed).toBeLessThanOrEqual(delay + offset);
215+
});
216+
217+
it("should thrown an error when the time exceeds", async () => {
218+
const mutex = new Mutex();
219+
220+
await mutex.lock();
221+
expect(mutex.isLocked).toBeTrue();
222+
expect(mutex.queueLength).toBe(0);
223+
224+
setTimeout(() => {
225+
expect(mutex.isLocked).toBeTrue();
226+
expect(mutex.queueLength).toBe(1);
227+
}, delay / 2);
228+
229+
await expect(() => mutex.tryLockWith(delay, () => 1)).rejects.toThrow(
230+
ConcurrencyExceedTimeoutException
231+
);
232+
expect(mutex.isLocked).toBeTrue();
233+
expect(mutex.queueLength).toBe(0);
234+
235+
mutex.unlock();
236+
});
237+
238+
it("should return the value from the critical section", async () => {
239+
const mutex = new Mutex();
240+
241+
const [v1, v2, v3] = await Promise.all([
242+
sleep(Math.random() * delay).then(() => mutex.tryLockWith(5, () => 1)),
243+
sleep(Math.random() * delay).then(() => mutex.tryLockWith(5, () => 2)),
244+
sleep(Math.random() * delay).then(() => mutex.tryLockWith(5, () => 3))
245+
]);
246+
247+
expect(v1).toBe(1);
248+
expect(v2).toBe(2);
249+
expect(v3).toBe(3);
250+
});
251+
});
252+
155253
it("should interrupt all", async () => {
156254
const delay = 100;
157255
const mutex = new Mutex();
@@ -162,14 +260,15 @@ describe("Mutex", () => {
162260

163261
setTimeout(() => {
164262
expect(mutex.isLocked).toBeTrue();
165-
expect(mutex.queueLength).toBe(3);
263+
expect(mutex.queueLength).toBe(4);
166264
mutex.interrupt(reason);
167265
}, delay);
168266

169267
const errors = await Promise.all([
170268
mutex.lock().catch((err: unknown) => err),
171269
mutex.tryLock(delay * 2).catch((err: unknown) => err),
172-
mutex.lock().catch((err: unknown) => err)
270+
mutex.lockWith(() => 0).catch((err: unknown) => err),
271+
mutex.tryLockWith(delay * 2, () => 0).catch((err: unknown) => err)
173272
]);
174273

175274
for (const error of errors) {

support/sleep.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
/**
2+
* Sleep an amount of time
3+
*
4+
* @param time to sleep (in ms)
5+
* @returns a Promise after the sleep time
6+
*/
7+
export function sleep(time: number) {
8+
return new Promise(resolve => setTimeout(resolve, time));
9+
}

0 commit comments

Comments
 (0)