diff --git a/website/docs/tutorials/detect-race-conditions/snippets.mjs b/website/docs/tutorials/detect-race-conditions/snippets.mjs index 835bda472aa..f7d3a3f277d 100644 --- a/website/docs/tutorials/detect-race-conditions/snippets.mjs +++ b/website/docs/tutorials/detect-race-conditions/snippets.mjs @@ -1,396 +1,411 @@ // @ts-check // Does not queue at all -export const queueCodeV0 = `export function queue(fun) { - return fun; - }`; +export const queueCodeV0 = ` +export function queue(fun) { + return fun; +}`.trim(); // Only queue after last request -export const queueCodeV1 = `export function queue(fun) { - let lastQuery = Promise.resolve(); - return (...args) => { - const currentQuery = fun(...args); - const returnedQuery = lastQuery.then(() => currentQuery); - lastQuery = currentQuery; - return returnedQuery; - }; - }`; +export const queueCodeV1 = ` +export function queue(fun) { + let lastQuery = Promise.resolve(); + return (...args) => { + const currentQuery = fun(...args); + const returnedQuery = lastQuery.then(() => currentQuery); + lastQuery = currentQuery; + return returnedQuery; + }; +}`.trim(); -export const queueCodeV2 = `export function queue(fun) { - let pastQueries = []; - return (...args) => { - const currentQuery = fun(...args); - const returnedQuery = Promise.all(pastQueries) - .finally(() => (pastQueries = [])) - .then(() => currentQuery); - pastQueries.push(currentQuery); - return returnedQuery; - }; - }`; +export const queueCodeV2 = ` +export function queue(fun) { + let pastQueries = []; + return (...args) => { + const currentQuery = fun(...args); + const returnedQuery = Promise.all(pastQueries) + .finally(() => (pastQueries = [])) + .then(() => currentQuery); + pastQueries.push(currentQuery); + return returnedQuery; + }; +}`.trim(); -export const queueCodeV3 = `export function queue(fun) { - let pastQueries = []; - return (...args) => { - const currentQuery = fun(...args); - const knownPastQueries = pastQueries; - const returnedQuery = Promise.all(pastQueries) - .finally(() => { - if (knownPastQueries === pastQueries) - pastQueries = []; - }) - .then(() => currentQuery); - pastQueries = [...pastQueries, currentQuery]; - return returnedQuery; - }; - }`; +export const queueCodeV3 = ` +export function queue(fun) { + let pastQueries = []; + return (...args) => { + const currentQuery = fun(...args); + const knownPastQueries = pastQueries; + const returnedQuery = Promise.all(pastQueries) + .finally(() => { + if (knownPastQueries === pastQueries) + pastQueries = []; + }) + .then(() => currentQuery); + pastQueries = [...pastQueries, currentQuery]; + return returnedQuery; + }; +}`.trim(); -export const queueCodeV4 = `export function queue(fun) { - let queryId = 0; - let lastQuery = null; - return (...args) => { - const selfQueryId = ++queryId; - if (lastQuery === null) { - lastQuery = fun(...args); - } else { - lastQuery = lastQuery - .then( - () => fun(...args), - () => fun(...args), - ); - } - lastQuery - .finally(() => { - if (queryId === selfQueryId) - lastQuery = null; - }); - return lastQuery; - }; - }`; +export const queueCodeV4 = ` +export function queue(fun) { + let queryId = 0; + let lastQuery = null; + return (...args) => { + const selfQueryId = ++queryId; + if (lastQuery === null) { + lastQuery = fun(...args); + } else { + lastQuery = lastQuery + .then( + () => fun(...args), + () => fun(...args), + ); + } + lastQuery + .finally(() => { + if (queryId === selfQueryId) + lastQuery = null; + }); + return lastQuery; + }; +}`.trim(); -export const queueCodeV5 = `export function queue(fun) { - let pending = false; - let onDone = []; - function runNext() { - if (onDone.length === 0) { - pending = false; - return; - } - onDone.shift()(); +export const queueCodeV5 = ` +export function queue(fun) { + let pending = false; + let onDone = []; + function runNext() { + if (onDone.length === 0) { + pending = false; + return; } - return (...args) => { - if (!pending) { - pending = true; - return new Promise((resolve, reject) => { - const p = fun(...args); - p.then(runNext, runNext); - p.then(resolve, reject); - }); - } + onDone.shift()(); + } + return (...args) => { + if (!pending) { + pending = true; return new Promise((resolve, reject) => { - onDone.push(() => { - const p = fun(...args); - p.then(runNext, runNext); - p.then(resolve, reject); - }); + const p = fun(...args); + p.then(runNext, runNext); + p.then(resolve, reject); }); - }; - }`; + } + return new Promise((resolve, reject) => { + onDone.push(() => { + const p = fun(...args); + p.then(runNext, runNext); + p.then(resolve, reject); + }); + }); + }; +}`.trim(); -export const queueUnitSpecCode = `import {queue} from './queue.js'; - - test('should resolve in call order', async () => { +export const queueUnitSpecCode = ` +import {queue} from './queue.js'; + +test('should resolve in call order', async () => { + // Arrange + const seenAnswers = []; + const call = jest.fn() + .mockImplementation(v => Promise.resolve(v)); + + // Act + const queued = queue(call); + await Promise.all([ + queued(1).then(v => (seenAnswers.push(v))), + queued(2).then(v => (seenAnswers.push(v))), + ]); + + // Assert + expect(seenAnswers).toEqual([1, 2]); +})`.trim(); + +export const queueBasicPBTSpecCode = ` +import {queue} from './queue.js'; +import fc from 'fast-check'; + +test('should resolve in call order', async () => { + await fc.assert(fc.asyncProperty(fc.scheduler(), async (s) => { // Arrange + const pendingQueries = []; const seenAnswers = []; const call = jest.fn() .mockImplementation(v => Promise.resolve(v)); // Act - const queued = queue(call); - await Promise.all([ - queued(1).then(v => (seenAnswers.push(v))), - queued(2).then(v => (seenAnswers.push(v))), - ]); + const queued = queue(s.scheduleFunction(call)); + pendingQueries.push(queued(1).then(v => (seenAnswers.push(v)))); + pendingQueries.push(queued(2).then(v => (seenAnswers.push(v)))); + await s.waitFor(Promise.all(pendingQueries)); // Assert expect(seenAnswers).toEqual([1, 2]); - })`; + })) +})`.trim(); -export const queueBasicPBTSpecCode = `import {queue} from './queue.js'; - import fc from 'fast-check'; +export const queueBasicPBTWaitAllSpecCode = ` +import {queue} from './queue.js'; +import fc from 'fast-check'; + +test('should resolve in call order', async () => { + await fc.assert(fc.asyncProperty(fc.scheduler(), async (s) => { + // Arrange + const seenAnswers = []; + const call = jest.fn() + .mockImplementation(v => Promise.resolve(v)); - test('should resolve in call order', async () => { - await fc.assert(fc.asyncProperty(fc.scheduler(), async (s) => { - // Arrange - const pendingQueries = []; - const seenAnswers = []; - const call = jest.fn() - .mockImplementation(v => Promise.resolve(v)); - - // Act - const queued = queue(s.scheduleFunction(call)); - pendingQueries.push(queued(1).then(v => (seenAnswers.push(v)))); - pendingQueries.push(queued(2).then(v => (seenAnswers.push(v)))); - await s.waitFor(Promise.all(pendingQueries)); - - // Assert - expect(seenAnswers).toEqual([1, 2]); - })) - })`; + // Act + const queued = queue(s.scheduleFunction(call)); + queued(1).then(v => (seenAnswers.push(v))); + queued(2).then(v => (seenAnswers.push(v))); + await s.waitAll(); + + // Assert + expect(seenAnswers).toEqual([1, 2]); + })) +})`.trim(); + +export const queueMoreThan2CallsPBTSpecCode = ` +import {queue} from './queue.js'; +import fc from 'fast-check'; -export const queueBasicPBTWaitAllSpecCode = `import {queue} from './queue.js'; - import fc from 'fast-check'; +test('should resolve in call order', async () => { + await fc.assert(fc.asyncProperty(fc.scheduler(), fc.integer({min: 1, max: 10}), async (s, numCalls) => { + // Arrange + const pendingQueries = []; + const seenAnswers = []; + const expectedAnswers = []; + const call = jest.fn() + .mockImplementation(v => Promise.resolve(v)); - test('should resolve in call order', async () => { - await fc.assert(fc.asyncProperty(fc.scheduler(), async (s) => { - // Arrange - const seenAnswers = []; - const call = jest.fn() - .mockImplementation(v => Promise.resolve(v)); - - // Act - const queued = queue(s.scheduleFunction(call)); - queued(1).then(v => (seenAnswers.push(v))); - queued(2).then(v => (seenAnswers.push(v))); - await s.waitAll(); - - // Assert - expect(seenAnswers).toEqual([1, 2]); - })) - })`; + // Act + const queued = queue(s.scheduleFunction(call)); + for (let id = 0 ; id !== numCalls ; ++id) { + expectedAnswers.push(id); + pendingQueries.push(queued(id).then(v => (seenAnswers.push(v)))); + } + await s.waitFor(Promise.all(pendingQueries)); + + // Assert + expect(seenAnswers).toEqual(expectedAnswers); + })) +})`.trim(); -export const queueMoreThan2CallsPBTSpecCode = `import {queue} from './queue.js'; - import fc from 'fast-check'; +export const queueBatchesAlternativePBTSpecCode = ` +import {queue} from './queue.js'; +import fc from 'fast-check'; + +test('should resolve in call order', async () => { + await fc.assert(fc.asyncProperty(fc.scheduler(), fc.integer({min: 1, max: 10}), async (s, numCalls) => { + // Arrange + const pendingQueries = []; + const seenAnswers = []; + const expectedAnswers = []; + const call = jest.fn() + .mockImplementation(v => Promise.resolve(v)); - test('should resolve in call order', async () => { - await fc.assert(fc.asyncProperty(fc.scheduler(), fc.integer({min: 1, max: 10}), async (s, numCalls) => { - // Arrange - const pendingQueries = []; - const seenAnswers = []; - const expectedAnswers = []; - const call = jest.fn() - .mockImplementation(v => Promise.resolve(v)); - - // Act - const queued = queue(s.scheduleFunction(call)); - for (let id = 0 ; id !== numCalls ; ++id) { - expectedAnswers.push(id); - pendingQueries.push(queued(id).then(v => (seenAnswers.push(v)))); - } - await s.waitFor(Promise.all(pendingQueries)); - - // Assert - expect(seenAnswers).toEqual(expectedAnswers); - })) - })`; + // Act + const queued = queue(s.scheduleFunction(call)); + for (let id = 0 ; id !== numCalls ; ++id) { + pendingQueries.push( + s.schedule(Promise.resolve(\`Fire the call for \${id}\`)) + .then(() => { + expectedAnswers.push(id); + return queued(id); + }) + .then(v => (seenAnswers.push(v))) + ); + } + await s.waitFor(Promise.all(pendingQueries)); + + // Assert + expect(seenAnswers).toEqual(expectedAnswers); + })) +})`.trim(); + +export const queueFromBatchesPBTSpecCode = ` +import {queue} from './queue.js'; +import fc from 'fast-check'; -export const queueBatchesAlternativePBTSpecCode = `import {queue} from './queue.js'; - import fc from 'fast-check'; +test('should resolve in call order', async () => { + await fc.assert(fc.asyncProperty(fc.scheduler(), fc.array(fc.integer({min: 1, max: 10}), {minLength: 1}), async (s, batches) => { + // Arrange + const pendingQueries = []; + const seenAnswers = []; + const expectedAnswers = []; + const call = jest.fn() + .mockImplementation(v => Promise.resolve(v)); - test('should resolve in call order', async () => { - await fc.assert(fc.asyncProperty(fc.scheduler(), fc.integer({min: 1, max: 10}), async (s, numCalls) => { - // Arrange - const pendingQueries = []; - const seenAnswers = []; - const expectedAnswers = []; - const call = jest.fn() - .mockImplementation(v => Promise.resolve(v)); - - // Act - const queued = queue(s.scheduleFunction(call)); - for (let id = 0 ; id !== numCalls ; ++id) { - pendingQueries.push( - s.schedule(Promise.resolve(\`Fire the call for \${id}\`)) - .then(() => { - expectedAnswers.push(id); - return queued(id); - }) - .then(v => (seenAnswers.push(v))) - ); + // Act + const queued = queue(s.scheduleFunction(call)); + let lastId = 0; + const { task } = s.scheduleSequence(batches.map((batch, index) => { + return { + label: \`Fire batch #\${index + 1} (\${batch} calls)\`, + builder: async () => { + for (let id = 0 ; id !== batch ; ++id, ++lastId) { + expectedAnswers.push(lastId); + pendingQueries.push(queued(lastId).then(v => (seenAnswers.push(v)))); + } + }, } - await s.waitFor(Promise.all(pendingQueries)); - - // Assert - expect(seenAnswers).toEqual(expectedAnswers); - })) - })`; - -export const queueFromBatchesPBTSpecCode = `import {queue} from './queue.js'; - import fc from 'fast-check'; + })); + await s.waitFor(task); + await s.waitFor(Promise.all(pendingQueries)); - test('should resolve in call order', async () => { - await fc.assert(fc.asyncProperty(fc.scheduler(), fc.array(fc.integer({min: 1, max: 10}), {minLength: 1}), async (s, batches) => { - // Arrange - const pendingQueries = []; - const seenAnswers = []; - const expectedAnswers = []; - const call = jest.fn() - .mockImplementation(v => Promise.resolve(v)); - - // Act - const queued = queue(s.scheduleFunction(call)); - let lastId = 0; - const { task } = s.scheduleSequence(batches.map((batch, index) => { - return { - label: \`Fire batch #\${index + 1} (\${batch} calls)\`, - builder: async () => { - for (let id = 0 ; id !== batch ; ++id, ++lastId) { - expectedAnswers.push(lastId); - pendingQueries.push(queued(lastId).then(v => (seenAnswers.push(v)))); - } - }, - } - })); - await s.waitFor(task); - await s.waitFor(Promise.all(pendingQueries)); - - // Assert - expect(seenAnswers).toEqual(expectedAnswers); - })) - })`; + // Assert + expect(seenAnswers).toEqual(expectedAnswers); + })) +})`.trim(); -export const missingPartPBTSpecCode = `import {queue} from './queue.js'; - import fc from 'fast-check'; - - test('should resolve in call order', async () => { - await fc.assert(fc.asyncProperty(fc.scheduler(), fc.array(fc.integer({min: 1, max: 10}), {minLength: 1}), async (s, batches) => { - // Arrange - const pendingQueries = []; - const seenAnswers = []; - const expectedAnswers = []; - const call = jest.fn() - .mockImplementation(v => Promise.resolve(v)); - const scheduledCall = s.scheduleFunction(call); - let concurrentQueriesDetected = false; - let queryPending = false; - const monitoredScheduledCall = (...args) => { - concurrentQueriesDetected ||= queryPending; - queryPending = true; - return scheduledCall(...args).finally(() => (queryPending = false)); - }; - - // Act - const queued = queue(monitoredScheduledCall); - let lastId = 0; - const { task } = s.scheduleSequence(batches.map((batch, index) => { - return { - label: \`Fire batch #\${index + 1} (\${batch} calls)\`, - builder: async () => { - for (let id = 0 ; id !== batch ; ++id, ++lastId) { - expectedAnswers.push(lastId); - pendingQueries.push(queued(lastId).then(v => (seenAnswers.push(v)))); - } - }, - } - })); - await s.waitFor(task); - await s.waitFor(Promise.all(pendingQueries)); - - // Assert - expect(seenAnswers).toEqual(expectedAnswers); - expect(concurrentQueriesDetected).toBe(false); - })) - })`; +export const missingPartPBTSpecCode = ` +import {queue} from './queue.js'; +import fc from 'fast-check'; -export const extendedBackToWaitAllPBTSpecCode = `import {queue} from './queue.js'; - import fc from 'fast-check'; +test('should resolve in call order', async () => { + await fc.assert(fc.asyncProperty(fc.scheduler(), fc.array(fc.integer({min: 1, max: 10}), {minLength: 1}), async (s, batches) => { + // Arrange + const pendingQueries = []; + const seenAnswers = []; + const expectedAnswers = []; + const call = jest.fn() + .mockImplementation(v => Promise.resolve(v)); + const scheduledCall = s.scheduleFunction(call); + let concurrentQueriesDetected = false; + let queryPending = false; + const monitoredScheduledCall = (...args) => { + concurrentQueriesDetected ||= queryPending; + queryPending = true; + return scheduledCall(...args).finally(() => (queryPending = false)); + }; + + // Act + const queued = queue(monitoredScheduledCall); + let lastId = 0; + const { task } = s.scheduleSequence(batches.map((batch, index) => { + return { + label: \`Fire batch #\${index + 1} (\${batch} calls)\`, + builder: async () => { + for (let id = 0 ; id !== batch ; ++id, ++lastId) { + expectedAnswers.push(lastId); + pendingQueries.push(queued(lastId).then(v => (seenAnswers.push(v)))); + } + }, + } + })); + await s.waitFor(task); + await s.waitFor(Promise.all(pendingQueries)); - test('should resolve in call order', async () => { - await fc.assert(fc.asyncProperty(fc.scheduler(), fc.array(fc.integer({min: 1, max: 10}), {minLength: 1}), async (s, batches) => { - // Arrange - const seenAnswers = []; - const expectedAnswers = []; - const call = jest.fn() - .mockImplementation(v => Promise.resolve(v)); - const scheduledCall = s.scheduleFunction(call); - let concurrentQueriesDetected = false; - let queryPending = false; - const monitoredScheduledCall = (...args) => { - concurrentQueriesDetected ||= queryPending; - queryPending = true; - const out = scheduledCall(...args); - out.finally(() => (queryPending = false)); - return out; - }; - - // Act - const queued = queue(monitoredScheduledCall); - let lastId = 0; - s.scheduleSequence(batches.map((batch, index) => { - return { - label: \`Fire batch #\${index + 1} (\${batch} calls)\`, - builder: async () => { - for (let id = 0 ; id !== batch ; ++id, ++lastId) { - expectedAnswers.push(lastId); - queued(lastId).then(v => (seenAnswers.push(v))); - } - }, - } - })); - await s.waitAll(); - - // Assert - expect(seenAnswers).toEqual(expectedAnswers); - expect(concurrentQueriesDetected).toBe(false); - })) - })`; + // Assert + expect(seenAnswers).toEqual(expectedAnswers); + expect(concurrentQueriesDetected).toBe(false); + })) +})`.trim(); -export const extendedWithExceptionsPBTSpecCode = `import {queue} from './queue.js'; - import fc from "fast-check"; +export const extendedBackToWaitAllPBTSpecCode = ` +import {queue} from './queue.js'; +import fc from 'fast-check'; + +test('should resolve in call order', async () => { + await fc.assert(fc.asyncProperty(fc.scheduler(), fc.array(fc.integer({min: 1, max: 10}), {minLength: 1}), async (s, batches) => { + // Arrange + const seenAnswers = []; + const expectedAnswers = []; + const call = jest.fn() + .mockImplementation(v => Promise.resolve(v)); + const scheduledCall = s.scheduleFunction(call); + let concurrentQueriesDetected = false; + let queryPending = false; + const monitoredScheduledCall = (...args) => { + concurrentQueriesDetected ||= queryPending; + queryPending = true; + const out = scheduledCall(...args); + out.finally(() => (queryPending = false)); + return out; + }; - test("should resolve in call order", async () => { - await fc.assert( - fc.asyncProperty( - fc.scheduler(), - fc.array(fc.integer({ min: 1, max: 10 }), { minLength: 1 }), - fc.func(fc.boolean()), - async (s, batches, isFailure) => { - // Arrange - const seenAnswers = []; - const expectedAnswers = []; - const call = jest - .fn() - .mockImplementation((v) => - isFailure(v) ? Promise.reject(v) : Promise.resolve(v) - ); - const scheduledCall = s.scheduleFunction(call); - let concurrentQueriesDetected = false; - let queryPending = false; - const monitoredScheduledCall = (...args) => { - concurrentQueriesDetected ||= queryPending; - queryPending = true; - const out = scheduledCall(...args); - out.finally(() => (queryPending = false)).catch(() => {}); - return out; - }; + // Act + const queued = queue(monitoredScheduledCall); + let lastId = 0; + s.scheduleSequence(batches.map((batch, index) => { + return { + label: \`Fire batch #\${index + 1} (\${batch} calls)\`, + builder: async () => { + for (let id = 0 ; id !== batch ; ++id, ++lastId) { + expectedAnswers.push(lastId); + queued(lastId).then(v => (seenAnswers.push(v))); + } + }, + } + })); + await s.waitAll(); - // Act - const queued = queue(monitoredScheduledCall); - let lastId = 0; - s.scheduleSequence( - batches.map((batch, index) => { - return { - label: \`Fire batch #\${index + 1} (\${batch} calls)\`, - builder: async () => { - for (let id = 0; id !== batch; ++id, ++lastId) { - expectedAnswers.push( - isFailure(lastId) - ? \`failure:\${lastId}\` - : \`success:\${lastId}\` - ); - queued(lastId).then( - (v) => seenAnswers.push(\`success:\${v}\`), - (v) => seenAnswers.push(\`failure:\${v}\`) - ); - } - } - }; - }) + // Assert + expect(seenAnswers).toEqual(expectedAnswers); + expect(concurrentQueriesDetected).toBe(false); + })) +})`.trim(); + +export const extendedWithExceptionsPBTSpecCode = ` +import {queue} from './queue.js'; +import fc from "fast-check"; + +test("should resolve in call order", async () => { + await fc.assert( + fc.asyncProperty( + fc.scheduler(), + fc.array(fc.integer({ min: 1, max: 10 }), { minLength: 1 }), + fc.func(fc.boolean()), + async (s, batches, isFailure) => { + // Arrange + const seenAnswers = []; + const expectedAnswers = []; + const call = jest + .fn() + .mockImplementation((v) => + isFailure(v) ? Promise.reject(v) : Promise.resolve(v) ); - await s.waitAll(); - - // Assert - expect(seenAnswers).toEqual(expectedAnswers); - expect(concurrentQueriesDetected).toBe(false); - } - ) - ); - })`; + const scheduledCall = s.scheduleFunction(call); + let concurrentQueriesDetected = false; + let queryPending = false; + const monitoredScheduledCall = (...args) => { + concurrentQueriesDetected ||= queryPending; + queryPending = true; + const out = scheduledCall(...args); + out.finally(() => (queryPending = false)).catch(() => {}); + return out; + }; + + // Act + const queued = queue(monitoredScheduledCall); + let lastId = 0; + s.scheduleSequence( + batches.map((batch, index) => { + return { + label: \`Fire batch #\${index + 1} (\${batch} calls)\`, + builder: async () => { + for (let id = 0; id !== batch; ++id, ++lastId) { + expectedAnswers.push( + isFailure(lastId) + ? \`failure:\${lastId}\` + : \`success:\${lastId}\` + ); + queued(lastId).then( + (v) => seenAnswers.push(\`success:\${v}\`), + (v) => seenAnswers.push(\`failure:\${v}\`) + ); + } + } + }; + }) + ); + await s.waitAll(); + + // Assert + expect(seenAnswers).toEqual(expectedAnswers); + expect(concurrentQueriesDetected).toBe(false); + } + ) + ); +})`.trim();