mirror of
https://github.com/fluxerapp/fluxer
synced 2026-10-14 14:38:35 +09:00
97 lines
3.9 KiB
TypeScript
97 lines
3.9 KiB
TypeScript
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
|
|
import {describe, expect, it} from 'vitest';
|
|
import {FuzzPrng} from './FuzzPrng';
|
|
import {
|
|
EXTERNAL_CONNECTION_EVENT_KINDS,
|
|
FUZZ_REDUCER_ARBITRARY_ITERATIONS,
|
|
FUZZ_REDUCER_EXTERNAL_CONNECTION_ITERATIONS,
|
|
ReducerFuzzer,
|
|
} from './ReducerFuzzer';
|
|
|
|
const SEEDS: ReadonlyArray<number> = [1, 2, 7, 13, 99];
|
|
|
|
describe('ReducerFuzzer.fuzzExternalConnectionCycles', () => {
|
|
it('survives weighted establish/disconnect cycles without invariant failures', () => {
|
|
for (const seed of SEEDS) {
|
|
const fuzzer = new ReducerFuzzer(seed);
|
|
const report = fuzzer.fuzzExternalConnectionCycles(FUZZ_REDUCER_EXTERNAL_CONNECTION_ITERATIONS);
|
|
expect(report.mode).toBe('externalConnection');
|
|
expect(report.iterations).toBe(FUZZ_REDUCER_EXTERNAL_CONNECTION_ITERATIONS);
|
|
expect(report.failures).toEqual([]);
|
|
expect(report.dispatched + report.rejected).toBe(FUZZ_REDUCER_EXTERNAL_CONNECTION_ITERATIONS);
|
|
expect(report.dispatched).toBeGreaterThan(0);
|
|
}
|
|
});
|
|
|
|
it('produces an identical snapshot hash and counters for the same seed (determinism)', () => {
|
|
for (const seed of SEEDS) {
|
|
const first = new ReducerFuzzer(seed).fuzzExternalConnectionCycles(256);
|
|
const second = new ReducerFuzzer(seed).fuzzExternalConnectionCycles(256);
|
|
expect(first.snapshotHash).toBeDefined();
|
|
expect(first.snapshotHash).toBe(second.snapshotHash);
|
|
expect(first.dispatched).toBe(second.dispatched);
|
|
expect(first.rejected).toBe(second.rejected);
|
|
expect(first.failures.length).toBe(second.failures.length);
|
|
}
|
|
});
|
|
|
|
it('produces diverging traces for different seeds', () => {
|
|
const first = new ReducerFuzzer(1).fuzzExternalConnectionCycles(256);
|
|
const second = new ReducerFuzzer(2).fuzzExternalConnectionCycles(256);
|
|
expect(first.snapshotHash === second.snapshotHash && first.dispatched === second.dispatched).toBe(false);
|
|
});
|
|
|
|
it('rejects an iteration budget above the global cap', () => {
|
|
const fuzzer = new ReducerFuzzer(1);
|
|
expect(() => fuzzer.fuzzExternalConnectionCycles(1025)).toThrow('iterations must respect FUZZ_ITERATIONS_MAX');
|
|
});
|
|
|
|
it('rejects a non-positive iteration budget', () => {
|
|
const fuzzer = new ReducerFuzzer(1);
|
|
expect(() => fuzzer.fuzzExternalConnectionCycles(0)).toThrow('iterations must be >= 1');
|
|
});
|
|
});
|
|
|
|
describe('ReducerFuzzer arbitrary-order generation with external connection events', () => {
|
|
it('keeps the arbitrary-order mode crash-free now that external events are in the pool', () => {
|
|
for (const seed of SEEDS) {
|
|
const fuzzer = new ReducerFuzzer(seed);
|
|
const report = fuzzer.fuzzArbitraryOrder(FUZZ_REDUCER_ARBITRARY_ITERATIONS);
|
|
expect(report.failures).toEqual([]);
|
|
expect(report.dispatched + report.rejected).toBe(FUZZ_REDUCER_ARBITRARY_ITERATIONS);
|
|
}
|
|
});
|
|
|
|
it('remains deterministic across runs with the widened event pool', () => {
|
|
const first = new ReducerFuzzer(42).fuzzArbitraryOrder(128);
|
|
const second = new ReducerFuzzer(42).fuzzArbitraryOrder(128);
|
|
expect(first.dispatched).toBe(second.dispatched);
|
|
expect(first.rejected).toBe(second.rejected);
|
|
});
|
|
});
|
|
|
|
describe('external connection event kind catalogue', () => {
|
|
it('contains exactly the externally-driven connection lifecycle events', () => {
|
|
expect(EXTERNAL_CONNECTION_EVENT_KINDS).toEqual([
|
|
'connection.externallyEstablished',
|
|
'connection.remoteDisconnected',
|
|
]);
|
|
});
|
|
|
|
it('keeps the seeded PRNG as the only source of randomness for kind selection', () => {
|
|
const prng = new FuzzPrng({seed: 7});
|
|
const picks: Array<string> = [];
|
|
for (let index = 0; index < 32; index += 1) {
|
|
picks.push(prng.nextChoice(EXTERNAL_CONNECTION_EVENT_KINDS));
|
|
}
|
|
const replayPrng = new FuzzPrng({seed: 7});
|
|
const replays: Array<string> = [];
|
|
for (let index = 0; index < 32; index += 1) {
|
|
replays.push(replayPrng.nextChoice(EXTERNAL_CONNECTION_EVENT_KINDS));
|
|
}
|
|
expect(picks).toEqual(replays);
|
|
expect(new Set(picks).size).toBe(2);
|
|
});
|
|
});
|