// @flow strict-local import assert from 'assert'; import nullthrows from 'nullthrows'; import RequestTracker from '../src/RequestTracker'; import WorkerFarm from '@parcel/workers'; import {DEFAULT_OPTIONS} from './test-utils'; import {INITIAL_BUILD} from '../src/constants'; import {makeDeferredWithPromise} from '@parcel/utils'; const options = DEFAULT_OPTIONS; const farm = new WorkerFarm({workerPath: require.resolve('../src/worker.js')}); describe('RequestTracker', () => { it('should not run requests that have not been invalidated', async () => { let tracker = new RequestTracker({farm, options}); await tracker.runRequest({ id: 'abc', type: 'mock_request', run: () => {}, input: null, }); let called = false; await tracker.runRequest({ id: 'abc', type: 'mock_request', run: () => { called = true; }, input: null, }); assert(called === false); }); it('should rerun requests that have been invalidated', async () => { let tracker = new RequestTracker({farm, options}); await tracker.runRequest({ id: 'abc', type: 'mock_request', run: () => {}, input: null, }); tracker.graph.invalidateNode( tracker.graph.getNodeIdByContentKey('abc'), INITIAL_BUILD, ); let called = false; await tracker.runRequest({ id: 'abc', type: 'mock_request', run: () => { called = true; }, input: null, }); assert(called === true); }); it('should invalidate requests with invalidated subrequests', async () => { let tracker = new RequestTracker({farm, options}); await tracker.runRequest({ id: 'abc', type: 'mock_request', run: async ({api}) => { await api.runRequest({ id: 'xyz', type: 'mock_request', run: () => {}, input: null, }); }, input: null, }); tracker.graph.invalidateNode( tracker.graph.getNodeIdByContentKey('xyz'), INITIAL_BUILD, ); assert( tracker .getInvalidRequests() .map(req => req.id) .includes('abc'), ); }); it('should invalidate requests that failed', async () => { let tracker = new RequestTracker({farm, options}); await tracker .runRequest({ id: 'abc', type: 'mock_request', run: async () => { await Promise.resolve(); throw new Error('woops'); }, input: null, }) .then(null, () => { /* do nothing */ }); assert( tracker .getInvalidRequests() .map(req => req.id) .includes('abc'), ); }); it('should remove subrequests that are no longer called within a request', async () => { let tracker = new RequestTracker({farm, options}); await tracker.runRequest({ id: 'abc', type: 'mock_request', run: async ({api}) => { await api.runRequest({ id: 'xyz', type: 'mock_request', run: () => {}, input: null, }); }, input: null, }); let nodeId = nullthrows(tracker.graph.getNodeIdByContentKey('abc')); tracker.graph.invalidateNode(nodeId, INITIAL_BUILD); await tracker.runRequest({ id: 'abc', type: 'mock_request', run: async ({api}) => { await api.runRequest({ id: '123', type: 'mock_request', run: () => {}, input: null, }); }, input: null, }); assert(!tracker.graph.hasContentKey('xyz')); }); it('should return a cached result if it was stored', async () => { let tracker = new RequestTracker({farm, options}); await tracker.runRequest({ id: 'abc', type: 'mock_request', run: async ({api}) => { let result = await Promise.resolve('hello'); api.storeResult(result); }, input: null, }); let result = await tracker.runRequest({ id: 'abc', type: 'mock_request', run: async () => {}, input: null, }); assert(result === 'hello'); }); it('should reject all in progress requests when the abort controller aborts', async () => { let tracker = new RequestTracker({farm, options}); let p = tracker .runRequest({ id: 'abc', type: 'mock_request', run: async () => { await Promise.resolve('hello'); }, input: null, }) .then(null, () => { /* do nothing */ }); // $FlowFixMe tracker.setSignal({aborted: true}); await p; assert( tracker .getInvalidRequests() .map(req => req.id) .includes('abc'), ); }); it('should not requeue requests if the previous request is still running', async () => { let tracker = new RequestTracker({farm, options}); let lockA = makeDeferredWithPromise(); let lockB = makeDeferredWithPromise(); let requestA = tracker.runRequest({ id: 'abc', type: 'mock_request', run: async ({api}) => { await lockA.promise; api.storeResult('a'); return 'a'; }, input: null, }); let calledB = false; let requestB = tracker.runRequest({ id: 'abc', type: 'mock_request', run: async ({api}) => { calledB = true; await lockB.promise; api.storeResult('b'); return 'b'; }, input: null, }); lockA.deferred.resolve(); lockB.deferred.resolve(); let resultA = await requestA; let resultB = await requestB; assert.strictEqual(resultA, 'a'); assert.strictEqual(resultB, 'a'); assert.strictEqual(calledB, false); let cachedResult = await tracker.runRequest({ id: 'abc', type: 'mock_request', run: () => {}, input: null, }); assert.strictEqual(cachedResult, 'a'); }); it('should requeue requests if the previous request is still running but failed', async () => { let tracker = new RequestTracker({farm, options}); let lockA = makeDeferredWithPromise(); let lockB = makeDeferredWithPromise(); let requestA = tracker .runRequest({ id: 'abc', type: 'mock_request', run: async () => { await lockA.promise; throw new Error('whoops'); }, input: null, }) .catch(() => { // ignore }); let requestB = tracker.runRequest({ id: 'abc', type: 'mock_request', run: async ({api}) => { await lockB.promise; api.storeResult('b'); }, input: null, }); lockA.deferred.resolve(); lockB.deferred.resolve(); await requestA; await requestB; let called = false; let cachedResult = await tracker.runRequest({ id: 'abc', type: 'mock_request', run: () => { called = true; }, input: null, }); assert.strictEqual(cachedResult, 'b'); assert.strictEqual(called, false); }); });