UNPKG

@stryker-mutator/core

Version:

The extendable JavaScript mutation testing framework

306 lines 12.3 kB
import { expect } from 'chai'; import sinon from 'sinon'; import { factory, tick } from '@stryker-mutator/test-helpers'; import { Task, ExpirableTask } from '@stryker-mutator/util'; import { toArray, mergeWith, lastValueFrom, range, ReplaySubject } from 'rxjs'; import { Pool } from '../../../src/concurrent/index.js'; describe(Pool.name, () => { let worker1; let worker2; let genericWorkerForAllSubsequentCreates; let createWorkerStub; let concurrencyTokenSubject; let sut; beforeEach(() => { concurrencyTokenSubject = new ReplaySubject(); worker1 = factory.testRunner(1); worker2 = factory.testRunner(2); genericWorkerForAllSubsequentCreates = factory.testRunner(); createWorkerStub = sinon.stub(); }); afterEach(async () => { concurrencyTokenSubject.complete(); await sut.dispose(); }); function arrangeWorkers() { createWorkerStub.returns(genericWorkerForAllSubsequentCreates).onCall(0).returns(worker1).onCall(1).returns(worker2); } function createSut() { return new Pool(createWorkerStub, concurrencyTokenSubject); } function setConcurrency(n) { range(0, n).subscribe(concurrencyTokenSubject.next.bind(concurrencyTokenSubject)); } describe('schedule', () => { it("should create 2 workers at a time (for performance reasons, we don't to overwhelm the device)", async () => { // Arrange arrangeWorkers(); setConcurrency(3); const initWorker1Task = new Task(); const initWorker2Task = new Task(); const initWorker3Task = new Task(); worker1.init.returns(initWorker1Task.promise); worker2.init.returns(initWorker2Task.promise); genericWorkerForAllSubsequentCreates.init.returns(initWorker3Task.promise); sut = createSut(); const actualWorkers = []; // Act const onGoingTask = lastValueFrom(sut.schedule(range(0, 3), async (worker) => actualWorkers.push(worker))); // Assert expect(actualWorkers).lengthOf(0); expect(createWorkerStub).callCount(2); initWorker1Task.resolve(); initWorker2Task.resolve(); initWorker3Task.resolve(); await tick(2); await sut.dispose(); expect(actualWorkers).lengthOf(3); await onGoingTask; }); it('should eventually create all workers', async () => { arrangeWorkers(); setConcurrency(8); sut = createSut(); const result = await captureWorkers(8); expect(result).lengthOf(8); expect(result).deep.eq([ worker1, worker2, genericWorkerForAllSubsequentCreates, genericWorkerForAllSubsequentCreates, genericWorkerForAllSubsequentCreates, genericWorkerForAllSubsequentCreates, genericWorkerForAllSubsequentCreates, genericWorkerForAllSubsequentCreates, ]); }); it("should reject when a worker couldn't be created", async () => { setConcurrency(1); const expectedError = new Error('foo error'); createWorkerStub.throws(expectedError); sut = createSut(); await expect(captureWorkers(1)).rejectedWith(expectedError); }); it('should share workers across subscribers (for sharing between dry runner and mutation test runner)', async () => { // Arrange setConcurrency(2); arrangeWorkers(); sut = createSut(); // Act const firstResult = await lastValueFrom(sut.schedule(range(0, 2), (worker) => worker).pipe(toArray())); const secondResult = await lastValueFrom(sut.schedule(range(0, 2), (worker) => worker).pipe(toArray())); // Assert await sut.dispose(); expect(firstResult).lengthOf(2); expect(secondResult).lengthOf(2); expect(firstResult[0]).eq(secondResult[0]); expect(firstResult[1]).eq(secondResult[1]); }); it('should re-emit the presented worker in the stream', async () => { // Arrange arrangeWorkers(); setConcurrency(2); const actualScheduledWork = []; sut = createSut(); const onGoingWork = lastValueFrom(sut .schedule(range(0, 3), async (worker, input) => { await tick(); actualScheduledWork.push([input, worker]); }) .pipe(toArray())); await tick(3); // Act await sut.dispose(); // Assert expect(actualScheduledWork).lengthOf(3); expect(actualScheduledWork).deep.eq([ [0, worker1], [1, worker2], [2, worker1], ]); await onGoingWork; }); it('should allow for parallel schedules, without interference (#3473)', async () => { // Arrange arrangeWorkers(); setConcurrency(2); sut = createSut(); let nrOfParallelTasks = 0; let maxNrOfParallelTasks = 0; const countParallelTasks = async () => { nrOfParallelTasks++; maxNrOfParallelTasks = Math.max(nrOfParallelTasks, maxNrOfParallelTasks); await tick(); nrOfParallelTasks--; }; // Act await lastValueFrom(sut.schedule(range(0, 3), countParallelTasks).pipe(mergeWith(sut.schedule(range(3, 3), countParallelTasks)))); await sut.dispose(); // Assert expect(maxNrOfParallelTasks).eq(2); }); it('should reject when an error occurs', async () => { // Arrange arrangeWorkers(); setConcurrency(2); sut = createSut(); // Act const expectedError = new Error('Expected error'); await expect(lastValueFrom(sut.schedule(range(0, 3), (_, n) => { if (n === 1) { throw expectedError; } }))).rejectedWith(expectedError); }); }); describe('init', () => { it('should await the init() of all workers', async () => { // Arrange arrangeWorkers(); setConcurrency(2); const initWorker2Task = new Task(); worker2.init.returns(initWorker2Task.promise); worker1.init.resolves(); sut = createSut(); // Act const timeoutResult = await ExpirableTask.timeout(sut.init(), 20); initWorker2Task.resolve(); concurrencyTokenSubject.complete(); await sut.init(); // Assert expect(timeoutResult).eq(ExpirableTask.TimeoutExpired); expect(worker1.init).called; expect(worker2.init).called; }); it('should cache the workers for later use', async () => { // Arrange arrangeWorkers(); setConcurrency(1); sut = createSut(); concurrencyTokenSubject.complete(); // Act await sut.init(); await sut.init(); const allWorkers = await captureWorkers(1); // Assert expect(createWorkerStub).calledOnce; expect(allWorkers[0]).eq(worker1); }); it('should await for all concurrency tokens to be delivered', async () => { // Arrange arrangeWorkers(); setConcurrency(2); const actualWorkers = []; sut = createSut(); const onGoingScheduledWork = lastValueFrom(sut.schedule(range(0, 2), (worker) => actualWorkers.push(worker))); // Act const timeoutResult = await ExpirableTask.timeout(sut.init(), 20); concurrencyTokenSubject.complete(); // Assert expect(timeoutResult).eq(ExpirableTask.TimeoutExpired); expect(actualWorkers).lengthOf(2); await sut.init(); await onGoingScheduledWork; }); it('should reject when a worker rejects', async () => { // Arrange arrangeWorkers(); setConcurrency(2); const expectedError = new Error('expected error'); worker2.init.rejects(expectedError); sut = createSut(); // Act & Assert await expect(sut.init()).rejectedWith(expectedError); concurrencyTokenSubject.complete(); }); }); describe('dispose', () => { it('should have disposed all workers', async () => { setConcurrency(8); arrangeWorkers(); sut = createSut(); await captureWorkers(9); await sut.dispose(); expect(worker1.dispose).called; expect(worker2.dispose).called; expect(genericWorkerForAllSubsequentCreates.dispose).called; }); it('should dispose workers only once', async () => { // Arrange setConcurrency(2); arrangeWorkers(); concurrencyTokenSubject.complete(); sut = createSut(); await sut.init(); // Act await sut.dispose(); await sut.dispose(); // Assert expect(worker1.dispose).calledOnce; expect(worker2.dispose).calledOnce; }); it('should not do anything if no workers were created', async () => { sut = createSut(); await sut.dispose(); expect(worker1.dispose).not.called; expect(worker2.dispose).not.called; }); it('should not resolve when there are still workers being initialized (issue #713)', async () => { // Arrange arrangeWorkers(); setConcurrency(2); const task = new Task(); const task2 = new Task(); worker1.init.returns(task.promise); worker2.init.returns(task2.promise); sut = createSut(); // Act const resultPromise = lastValueFrom(sut.schedule(range(0, 2), (worker) => worker).pipe(toArray())); task.resolve(); await sut.dispose(); task2.resolve(); concurrencyTokenSubject.complete(); // Assert await resultPromise; expect(worker1.dispose).called; expect(worker2.dispose).called; }); it('should halt creating of new sandboxes', async () => { // Arrange arrangeWorkers(); setConcurrency(3); const task = new Task(); const task2 = new Task(); worker1.init.returns(task.promise); worker2.init.returns(task2.promise); sut = createSut(); // Act const actualWorkers = lastValueFrom(sut.schedule(range(0, 3), (worker) => worker).pipe(toArray())); const disposePromise = sut.dispose(); task.resolve(); task2.resolve(); await disposePromise; concurrencyTokenSubject.complete(); await actualWorkers; // Assert expect(createWorkerStub).calledTwice; }); }); async function captureWorkers(inputCount) { // Eagerly get all test runners const createAllPromise = lastValueFrom(sut .schedule(range(0, inputCount), async (worker) => { await tick(); return worker; }) .pipe(toArray())); // But don't await yet, until after dispose. // Allow processes to be created await tick(inputCount + 1); // Dispose completes the internal recycle bin subject, which in turn will complete. await sut.dispose(); concurrencyTokenSubject.complete(); return createAllPromise; } }); //# sourceMappingURL=pool.spec.js.map