Files
dzc f55561c98d feat:上传到阿里云oss
feat:把以前的图片迁移到阿里云oss
2026-11-05 15:22:55 +08:00

147 lines
5.0 KiB
JavaScript

import test from 'node:test';
import assert from 'node:assert/strict';
import { setImmediate } from 'node:timers/promises';
import { mapBounded } from '../lib/concurrency.mjs';
function deferred() {
let resolve, reject;
const promise = new Promise((res, rej) => { resolve = res; reject = rej; });
return { promise, resolve, reject };
}
test('bounded workers keep input order when tasks finish out of order', { timeout: 2000 }, async () => {
const gates = Array.from({ length: 5 }, deferred);
const started = [];
let active = 0, maximum = 0;
const pending = mapBounded(['a', 'b', 'c', 'd', 'e'], 2, async (value, index) => {
started.push(index);
maximum = Math.max(maximum, ++active);
try { await gates[index].promise; return value.toUpperCase(); }
finally { active--; }
});
assert.deepEqual(started, [0, 1]);
gates[1].resolve();
await setImmediate();
assert.deepEqual(started, [0, 1, 2]);
gates[2].resolve();
await setImmediate();
assert.deepEqual(started, [0, 1, 2, 3]);
gates[0].resolve();
await setImmediate();
assert.deepEqual(started, [0, 1, 2, 3, 4]);
gates[4].resolve();
gates[3].resolve();
assert.deepEqual(await pending, ['A', 'B', 'C', 'D', 'E']);
assert.equal(maximum, 2);
assert.equal(active, 0);
});
test('failure stops new work and waits for all running workers before rejecting', { timeout: 2000 }, async () => {
const gates = Array.from({ length: 5 }, deferred);
const started = [];
const finished = [];
const failure = new Error('first verification failed');
let settled = false;
const pending = mapBounded([0, 1, 2, 3, 4], 3, async index => {
started.push(index);
try { await gates[index].promise; return index; }
finally { finished.push(index); }
});
const outcome = pending.then(
value => { settled = true; return { value }; },
error => { settled = true; return { error }; },
);
assert.deepEqual(started, [0, 1, 2]);
gates[1].reject(failure);
await setImmediate();
assert.deepEqual(started, [0, 1, 2]);
assert.equal(settled, false);
gates[0].resolve();
await setImmediate();
assert.deepEqual(started, [0, 1, 2]);
assert.equal(settled, false);
gates[2].resolve();
assert.equal((await outcome).error, failure);
assert.equal(settled, true);
assert.deepEqual(finished.sort(), [0, 1, 2]);
});
test('additional in-flight rejections are drained while preserving the first failure', { timeout: 2000 }, async () => {
const gates = Array.from({ length: 3 }, deferred);
const first = new Error('first');
const second = new Error('second');
let calls = 0, settled = false;
const outcome = mapBounded([0, 1, 2, 3], 3, async index => {
calls++;
await gates[index].promise;
return index;
}).then(
() => { settled = true; assert.fail('must reject'); },
error => { settled = true; return error; },
);
gates[0].reject(first);
await setImmediate();
gates[1].reject(second);
await setImmediate();
assert.equal(settled, false);
assert.equal(calls, 3);
gates[2].resolve();
assert.equal(await outcome, first);
});
test('a synchronous failure stops scheduling immediately', async () => {
const failure = new Error('synchronous failure');
const started = [];
await assert.rejects(mapBounded([0, 1, 2], 3, value => {
started.push(value);
throw failure;
}), error => error === failure);
assert.deepEqual(started, [0]);
});
test('falsy rejection values still reject and never get treated as success', async () => {
for (const failure of [undefined, null, false, 0, '']) {
let rejected = false;
await mapBounded([1], 1, () => Promise.reject(failure)).then(
() => assert.fail('must reject'),
error => { rejected = true; assert.equal(error, failure); },
);
assert.equal(rejected, true);
}
});
test('limit one executes serially and accepts synchronous return values', async () => {
const order = [];
assert.deepEqual(await mapBounded([2, 4, 6], 1, (value, index) => {
order.push(index);
return value * 2;
}), [4, 8, 12]);
assert.deepEqual(order, [0, 1, 2]);
});
test('limits larger than the item count only start existing items', async () => {
let calls = 0;
const result = await mapBounded(['x', 'y'], 1000, async value => { calls++; return value; });
assert.deepEqual(result, ['x', 'y']);
assert.equal(calls, 2);
});
test('empty input returns an empty array without invoking the worker', async () => {
assert.deepEqual(await mapBounded([], 4, () => assert.fail('must not run')), []);
});
test('invalid limits reject even for an empty input', async () => {
for (const limit of [0, -1, 1.5, Infinity, NaN, '4', null, undefined, Number.MAX_SAFE_INTEGER + 1]) {
await assert.rejects(mapBounded([], limit, () => {}), /正整数/);
}
});
test('input values are snapshotted before workers mutate the original collection', async () => {
const input = ['first', 'second', 'third'];
const result = await mapBounded(input, 1, async (value, index) => {
if (index === 0) input.splice(1, 2, 'changed');
return value;
});
assert.deepEqual(result, ['first', 'second', 'third']);
});