Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@
"@metamask/eslint-config-typescript": "^15.0.0",
"@ts-bridge/cli": "^0.1.2",
"@ts-bridge/shims": "^0.1.1",
"@types/jest": "^28.1.7",
"@types/jest": "^30.0.0",
"@types/jest-when": "^3.5.3",
"@types/node": "~18.18.14",
"@types/semver": "^7",
Expand All @@ -100,13 +100,13 @@
"eslint-plugin-n": "^17.10.3",
"eslint-plugin-prettier": "^5.2.1",
"eslint-plugin-promise": "^7.1.0",
"jest": "^29.2.2",
"jest": "^30.4.2",
"jest-it-up": "^2.0.2",
"jest-when": "^3.7.0",
"oxfmt": "^0.44.0",
"prettier": "^3.3.3",
"stdio-mock": "^1.2.0",
"ts-jest": "^29.0.3",
"ts-jest": "^29.4.11",
"ts-node": "^10.7.0",
"tsd": "^0.29.0",
"typedoc": "^0.25.13",
Expand All @@ -127,7 +127,8 @@
"lavamoat": {
"allowScripts": {
"@lavamoat/preinstall-always-fail": false,
"eslint-plugin-import-x>unrs-resolver": false
"eslint-plugin-import-x>unrs-resolver": false,
"jest>@jest/core>jest-haste-map>@parcel/watcher": false
}
}
}
50 changes: 33 additions & 17 deletions src/bytes.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -592,6 +592,7 @@ describe('areUint8ArraysEqual', () => {
it('has similar runtime for early vs late differences on large arrays', () => {
const LENGTH = 100_000;
const ITERATIONS = 200;
const TRIALS = 7;

const base = new Uint8Array(LENGTH).fill(7);
const early = base.slice();
Expand All @@ -610,28 +611,43 @@ describe('areUint8ArraysEqual', () => {

const now = () => Number(process.hrtime.bigint());

let earlyTotal = 0;
let lateTotal = 0;

// Measure early difference
const startEarly = now();
for (let i = 0; i < ITERATIONS; i++) {
areUint8ArraysEqual(early, base);
const measure = (candidate: Uint8Array) => {
const start = now();
for (let i = 0; i < ITERATIONS; i++) {
areUint8ArraysEqual(candidate, base);
}
return now() - start;
};

// Timing noise is strictly additive: scheduling, garbage collection and
// cache pressure only ever make a run slower, never faster. The fastest
// sample is therefore the closest to the function's true cost, which makes
// the minimum a much steadier estimator here than a single reading. A lone
// sample per side is what made this assertion fail intermittently on CI.
const fastest = (values: number[]) => Math.min(...values);

const earlySamples: number[] = [];
const lateSamples: number[] = [];
for (let trial = 0; trial < TRIALS; trial++) {
earlySamples.push(measure(early));
lateSamples.push(measure(late));
}
earlyTotal = now() - startEarly;

// Measure late difference
const startLate = now();
for (let i = 0; i < ITERATIONS; i++) {
areUint8ArraysEqual(late, base);
}
lateTotal = now() - startLate;
const earlyTotal = fastest(earlySamples);
const lateTotal = fastest(lateSamples);

// Ratio ≈ 1.0 ⇒ similar runtimes regardless of diff position.
// The threshold enforces the same order of magnitude while allowing normal system jitter.
// It's an empirical upper bound (~p95). To tune: run multiple trials, take a high percentile, and set slightly above it.
//
// The bound is deliberately loose. What this test exists to catch is the
// comparison regaining an early return on the first differing byte. Were
// that to happen, `early` would stop after one byte while `late` still
// walked all 100,000, so the ratio would land in the thousands rather than
// slightly above 1. Anything under an order of magnitude is measurement
// noise, and on shared CI runners that noise reaches ~1.14 no matter how
// the samples are taken. A tighter bound buys no sensitivity to the real
// regression, only flakiness.
const ratio =
earlyTotal > lateTotal ? earlyTotal / lateTotal : lateTotal / earlyTotal;
expect(ratio).toBeLessThan(1.1);
expect(ratio).toBeLessThan(2);
});
});
3 changes: 3 additions & 0 deletions src/coercers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ export function createBigInt(value: NumberLike): bigint {
// There is no need to validate the value manually.
return create(value, BigIntCoercer);
} catch (error) {
/* istanbul ignore else -- Coercion failures always surface as StructError; the rethrow below is a safety net. */
if (error instanceof StructError) {
throw new Error(
`Expected a number-like value, got "${String(error.value)}".`,
Expand Down Expand Up @@ -143,6 +144,7 @@ export function createBytes(value: BytesLike): Uint8Array {
try {
return create(value, BytesCoercer);
} catch (error) {
/* istanbul ignore else -- Coercion failures always surface as StructError; the rethrow below is a safety net. */
if (error instanceof StructError) {
throw new Error(
`Expected a bytes-like value, got "${String(error.value)}".`,
Expand Down Expand Up @@ -184,6 +186,7 @@ export function createHex(value: BytesLike): Hex {
try {
return create(value, HexCoercer);
} catch (error) {
/* istanbul ignore else -- Coercion failures always surface as StructError; the rethrow below is a safety net. */
if (error instanceof StructError) {
throw new Error(
`Expected a bytes-like value, got "${String(error.value)}".`,
Expand Down
Loading
Loading