Testing RequestTracker Cache Management in Parcel Bundler
This test suite evaluates the RequestTracker functionality in Parcel’s core module, focusing on request caching, invalidation, and handling of concurrent requests. It verifies critical request tracking behaviors essential for Parcel’s build process optimization.
Test Coverage Overview
Implementation Analysis
Technical Details
Best Practices Demonstrated
parcel-bundler/parcel
packages/core/core/test/RequestTracker.test.js
// @flow strict-local
import assert from 'assert';
import nullthrows from 'nullthrows';
import RequestTracker, {type RunAPI} 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: 7,
run: () => {},
input: null,
});
let called = false;
await tracker.runRequest({
id: 'abc',
type: 7,
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: 7,
run: () => {},
input: null,
});
tracker.graph.invalidateNode(
tracker.graph.getNodeIdByContentKey('abc'),
INITIAL_BUILD,
);
let called = false;
await tracker.runRequest({
id: 'abc',
type: 7,
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: 7,
run: async ({api}) => {
await api.runRequest({
id: 'xyz',
type: 7,
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: 7,
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: 7,
run: async ({api}) => {
await api.runRequest({
id: 'xyz',
type: 7,
run: () => {},
input: null,
});
},
input: null,
});
let nodeId = nullthrows(tracker.graph.getNodeIdByContentKey('abc'));
tracker.graph.invalidateNode(nodeId, INITIAL_BUILD);
await tracker.runRequest({
id: 'abc',
type: 7,
run: async ({api}) => {
await api.runRequest({
id: '123',
type: 7,
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: 7,
// $FlowFixMe string isn't a valid result
run: async ({api}: {api: RunAPI<string | void>, ...}) => {
let result = await Promise.resolve('hello');
api.storeResult(result);
},
input: null,
});
let result = await tracker.runRequest({
id: 'abc',
type: 7,
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: 7,
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: 7,
// $FlowFixMe string isn't a valid result
run: async ({api}: {api: RunAPI<string>, ...}) => {
await lockA.promise;
api.storeResult('a');
return 'a';
},
input: null,
});
let calledB = false;
let requestB = tracker.runRequest({
id: 'abc',
type: 7,
// $FlowFixMe string isn't a valid result
run: async ({api}: {api: RunAPI<string>, ...}) => {
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: 7,
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: 7,
run: async () => {
await lockA.promise;
throw new Error('whoops');
},
input: null,
})
.catch(() => {
// ignore
});
let requestB = tracker.runRequest({
id: 'abc',
type: 7,
// $FlowFixMe string isn't a valid result
run: async ({api}: {api: RunAPI<string | void>, ...}) => {
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: 7,
run: () => {
called = true;
},
input: null,
});
assert.strictEqual(cachedResult, 'b');
assert.strictEqual(called, false);
});
});