[READ-ONLY] a fast, modern browser for the npm registry
at main 136 lines 4.0 kB view raw
1import { describe, expect, it, vi } from 'vitest' 2import * as fc from 'fast-check' 3import { mapWithConcurrency } from '../../../../shared/utils/async' 4 5describe('mapWithConcurrency', () => { 6 it('processes all items and returns results in order', async () => { 7 const items = [1, 2, 3, 4, 5] 8 const results = await mapWithConcurrency(items, async x => x * 2) 9 10 expect(results).toEqual([2, 4, 6, 8, 10]) 11 }) 12 13 it('respects concurrency limit', async () => { 14 let concurrent = 0 15 let maxConcurrent = 0 16 17 const items = Array.from({ length: 10 }, (_, i) => i) 18 19 await mapWithConcurrency( 20 items, 21 async () => { 22 concurrent++ 23 maxConcurrent = Math.max(maxConcurrent, concurrent) 24 await new Promise(resolve => setTimeout(resolve, 10)) 25 concurrent-- 26 }, 27 3, 28 ) 29 30 expect(maxConcurrent).toBe(3) 31 }) 32 33 it('handles empty array', async () => { 34 const results = await mapWithConcurrency([], async x => x) 35 expect(results).toEqual([]) 36 }) 37 38 it('handles single item', async () => { 39 const results = await mapWithConcurrency([42], async x => x * 2) 40 expect(results).toEqual([84]) 41 }) 42 43 it('passes index to callback', async () => { 44 const items = ['a', 'b', 'c'] 45 const results = await mapWithConcurrency(items, async (item, index) => `${item}${index}`) 46 47 expect(results).toEqual(['a0', 'b1', 'c2']) 48 }) 49 50 it('propagates errors', async () => { 51 const items = [1, 2, 3] 52 const fn = vi.fn(async (x: number) => { 53 if (x === 2) throw new Error('test error') 54 return x 55 }) 56 57 await expect(mapWithConcurrency(items, fn)).rejects.toThrow('test error') 58 }) 59 60 it('uses default concurrency of 10', async () => { 61 let concurrent = 0 62 let maxConcurrent = 0 63 64 const items = Array.from({ length: 20 }, (_, i) => i) 65 66 await mapWithConcurrency(items, async () => { 67 concurrent++ 68 maxConcurrent = Math.max(maxConcurrent, concurrent) 69 await new Promise(resolve => setTimeout(resolve, 5)) 70 concurrent-- 71 }) 72 73 expect(maxConcurrent).toBe(10) 74 }) 75 76 it('handles fewer items than concurrency limit', async () => { 77 let concurrent = 0 78 let maxConcurrent = 0 79 80 const items = [1, 2, 3] 81 82 await mapWithConcurrency( 83 items, 84 async () => { 85 concurrent++ 86 maxConcurrent = Math.max(maxConcurrent, concurrent) 87 await new Promise(resolve => setTimeout(resolve, 10)) 88 concurrent-- 89 }, 90 10, 91 ) 92 93 // Should only have 3 concurrent since we only have 3 items 94 expect(maxConcurrent).toBe(3) 95 }) 96 97 it('waits for all tasks to succeed and return them in order whatever their count and the concurrency', async () => { 98 await fc.assert( 99 fc.asyncProperty( 100 fc.array(fc.anything()), 101 fc.integer({ min: 1 }), 102 fc.scheduler(), 103 async (items, concurrency, s) => { 104 const fn = s.scheduleFunction(async item => item) 105 const results = await s.waitFor(mapWithConcurrency(items, fn, concurrency)) 106 expect(results).toEqual(items) 107 }, 108 ), 109 ) 110 }) 111 112 it('not run more than concurrency tasks in parallel', async () => { 113 await fc.assert( 114 fc.asyncProperty( 115 fc.array(fc.anything()), 116 fc.integer({ min: 1 }), 117 fc.scheduler(), 118 async (items, concurrency, s) => { 119 let tooManyRunningTasksEncountered = false 120 let currentlyRunning = 0 121 const fn = async (item: (typeof items)[number]) => { 122 currentlyRunning++ 123 if (currentlyRunning > concurrency) { 124 tooManyRunningTasksEncountered = true 125 } 126 const task = s.schedule(Promise.resolve(item)) 127 task.then(() => currentlyRunning--) // this task always succeeds by construct 128 return task 129 } 130 await s.waitFor(mapWithConcurrency(items, fn, concurrency)) 131 expect(tooManyRunningTasksEncountered).toBe(false) 132 }, 133 ), 134 ) 135 }) 136})