All files / semaphore Semaphore.spec.ts

100% Statements 128/128
100% Branches 0/0
100% Functions 36/36
100% Lines 98/98

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 1841x 1x 1x   1x   1x 1x 1x 1x 1x     1x 1x 1x 1x   1x 5x 5x 5x       1x 1x 5x     1x   1x     1x 1x 1x   1x 5x     1x 1x 5x     1x     1x 1x 1x   1x 1x 5x     1x     1x 1x 1x 1x   1x 1x 6x     1x     1x 1x 1x     1x 1x 1x   1x 10x     1x 10x     1x   1x       1x     1x 1x   1x     1x     1x 1x   1x   1x     1x 1x     1x 1x   1x     1x     1x 1x   1x       1x   1x     1x 1x   1x       1x   1x     1x 1x   1x 1x   1x 1x 15x     1x 1x 5x       1x 1x 5x       15x 1x      
import { Semaphore } from './Semaphore';
import { expect } from 'chai';
import 'mocha';
 
import { TIME_STEP, wait, expectTimelyIn, expectNever } from '../timing.common-spec';
 
describe('Semaphore', () => {
	it('Take/free', async () => {
		const sem = new Semaphore();
		wait(TIME_STEP).then(() => sem.free());
		await sem.take();
	});
 
	it('x blocking takes, then x frees upon x timeouts', async () => {
		const COUNT = 5;
		const sem = new Semaphore();
		let timeoutCounter = 0;
 
		for (let i = 0; i < COUNT; i++) {
			wait(TIME_STEP).then(() => {
				timeoutCounter++;
				sem.free();
			});
		}
 
		const timelyPromises = [];
		for (let i = 0; i < COUNT; i++) {
			timelyPromises.push(expectTimelyIn(sem.take(), TIME_STEP));
		}
 
		await Promise.all(timelyPromises);
 
		expect(timeoutCounter).to.equal(COUNT);
	});
 
	it('x frees, then x immediately resolved takes', async () => {
		const COUNT = 5;
		const sem = new Semaphore();
 
		for (let i = 0; i < COUNT; i++) {
			sem.free();
		}
 
		const timelyPromises = [];
		for (let i = 0; i < COUNT; i++) {
			timelyPromises.push(expectTimelyIn(sem.take(), 0));
		}
 
		await Promise.all(timelyPromises);
	});
 
	it('x pre-initialized, then x immediately resolved takes', async () => {
		const COUNT = 5;
		const sem = new Semaphore(COUNT);
 
		const timelyPromises = [];
		for (let i = 0; i < COUNT; i++) {
			timelyPromises.push(expectTimelyIn(sem.take(), 0));
		}
 
		await Promise.all(timelyPromises);
	});
 
	it('x pre-initialized, one free, then (x + 1) immediately resolved takes', async () => {
		const COUNT = 5;
		const sem = new Semaphore(COUNT);
		sem.free();
 
		const timelyPromises = [];
		for (let i = 0; i < COUNT + 1; i++) {
			timelyPromises.push(expectTimelyIn(sem.take(), 0));
		}
 
		await Promise.all(timelyPromises);
	});
 
	it('tryTake on 0-initialized semaphore', async () => {
		const sem = new Semaphore(0);
		expect(sem.tryTake()).to.be.false;
	});
 
	it('x frees, x + 1 tryTakes', async () => {
		const COUNT = 10;
		const sem = new Semaphore(0);
 
		for (let i = 0; i < COUNT; i++) {
			sem.free();
		}
 
		for (let i = 0; i < COUNT; i++) {
			expect(sem.tryTake()).to.be.true;
		}
 
		expect(sem.tryTake()).to.be.false;
 
		wait(TIME_STEP).then(() => sem.free());
 
		// The last falsy tryTake() should not have acquired a resource.
		// => This take() must terminate.
		await expectTimelyIn(sem.take(), TIME_STEP);
	});
 
	it('tryTakeWithin timely returns false on 0-initialized semaphore with no frees', async () => {
		const sem = new Semaphore(0);
 
		await expectTimelyIn(expect(sem.tryTakeWithin(TIME_STEP)).to.eventually.be.false, TIME_STEP);
 
		// The semaphore's counter must be 0 => any take must block
		await expectNever(sem.take());
	});
 
	it('tryTakeWithin timely returns false on 0-initialized semaphore with one free out of time', async () => {
		const sem = new Semaphore(0);
 
		wait(2 * TIME_STEP).then(() => sem.free());
 
		await expectTimelyIn(expect(sem.tryTakeWithin(TIME_STEP)).to.eventually.be.false, TIME_STEP);
 
		// The semaphore's counter must be 1 now => only one take may resolve
		await expectTimelyIn(sem.take(), TIME_STEP, 10, 30);
		await expectNever(sem.take());
	});
 
	it('successful tryTakeWithin must decrement the counter', async () => {
		const sem = new Semaphore(1);
 
		await expectTimelyIn(expect(sem.tryTakeWithin(TIME_STEP)).to.eventually.be.true, 0);
 
		// take must block
		await expectNever(sem.take());
	});
 
	it('successful tryTakeWithin must resolve "immediately" with 1-initialized semaphore', async () => {
		const sem = new Semaphore(1);
 
		wait(2 * TIME_STEP).then(() => sem.free());
 
		// tryTakeWithin must resolve in time despite of the second free() issued
		// after 2 * TIME_STEP
		await expectTimelyIn(expect(sem.tryTakeWithin(TIME_STEP)).to.eventually.be.true, 0);
 
		await expectNever(sem.take());
	});
 
	it('successful tryTakeWithin must resolve immediately after the first free', async () => {
		const sem = new Semaphore(0);
 
		wait(TIME_STEP).then(() => sem.free());
 
		// tryTakeWithin must resolve in time despite of the second free() issues
		// after 20ms.
		await expectTimelyIn(expect(sem.tryTakeWithin(2 * TIME_STEP)).to.eventually.be.true, TIME_STEP);
 
		await expectNever(sem.take());
	});
 
	it('x + y tryTakeWithin with x frees in time', async () => {
		const sem = new Semaphore(0);
 
		const SUCCESSFULL_TAKES = 10;
		const REMAINING_UNSUCCESSFUL_TAKES = 5;
 
		const takeWithinPromises: Promise<boolean>[] = [];
		for (let i = 0; i < SUCCESSFULL_TAKES + REMAINING_UNSUCCESSFUL_TAKES; i++) {
			takeWithinPromises.push(sem.tryTakeWithin(3 * TIME_STEP));
		}
 
		wait(TIME_STEP).then(() => {
			for (let i = 0; i < Math.floor(SUCCESSFULL_TAKES / 2); i++) {
				sem.free();
			}
		});
 
		wait(2 * TIME_STEP).then(() => {
			for (let i = Math.floor(SUCCESSFULL_TAKES / 2); i < SUCCESSFULL_TAKES; i++) {
				sem.free();
			}
		});
 
		const successfulTakes = (await Promise.all(takeWithinPromises)).filter(x => x).length;
		expect(successfulTakes).to.equal(SUCCESSFULL_TAKES);
	});
});