@@ -3,19 +3,20 @@ import { tryParse as parse } from "../src/parse";
33import render from '../src/render' ;
44import { webcrypto } from 'crypto' ;
55
6- const testCount = 500 ;
6+ // tests per bit-size
7+ const testCount = 50 ;
78
8- const auras : aura [ ] = [
9+ const simpleAuras : aura [ ] = [
910 'c' ,
1011 'da' ,
1112 // 'dr', //TODO unsupported
1213 // 'f', // limited legitimate values
13- // 'if', // fixed size
14- // 'is', // fixed size
14+ // 'if', // won't round-trip above "native" size limit
15+ // 'is', // won't round-trip above "native" size limit
1516 // 'n', // limited legitimate values
1617 'p' ,
1718 'q' ,
18- // 'rh', // strict size limits, and NaN won't always round-trip
19+ // 'rh', // strict size limits, and NaN usually won't round-trip
1920 // 'rd', //
2021 // 'rq', //
2122 // 'rs', //
@@ -37,21 +38,60 @@ const auras: aura[] = [
3738 'ux'
3839]
3940
40- function fuzz ( nom : string , arr : Uint8Array | Uint16Array | Uint32Array | BigUint64Array ) {
41- webcrypto . getRandomValues ( arr ) ;
42- auras . forEach ( ( a ) => {
43- describe ( nom + ' @' + a , ( ) => {
44- it ( 'round-trips losslessly' , ( ) => {
45- arr . forEach ( ( n ) => {
46- n = BigInt ( n ) ;
47- expect ( parse ( a , render ( a , n ) ) ) . toEqual ( n ) ;
48- } ) ;
41+ function fuzz ( minBits : number , maxBits : number , auras : aura [ ] , f : ( n :bigint ) => bigint = ( n ) => n ) {
42+ describe ( minBits + '—' + maxBits + '-bit values' , ( ) => {
43+ const tests : { [ bits : number ] : bigint [ ] } = { } ;
44+ for ( let bits = minBits ; bits <= maxBits ; bits ++ ) {
45+ const parts = Math . ceil ( bits / 64 ) ;
46+ const src = bits <= 8 ? new Uint8Array ( testCount )
47+ : bits <= 16 ? new Uint16Array ( testCount )
48+ : bits <= 32 ? new Uint32Array ( testCount )
49+ : bits <= 64 ? new BigUint64Array ( testCount )
50+ : new BigUint64Array ( testCount * parts ) ;
51+ webcrypto . getRandomValues ( src ) ;
52+ const arr : bigint [ ] = [ ] ;
53+ if ( bits <= 64 ) {
54+ src . forEach ( ( n ) => arr . push ( f ( BigInt ( n ) ) ) ) ;
55+ } else {
56+ const mask = ( 1n << BigInt ( bits % 64 ) ) - 1n ;
57+ for ( let t = 0 ; t < testCount ; t += parts ) {
58+ let num = ( src [ t ] as bigint ) & mask ;
59+ for ( let p = 1 ; p < parts ; p ++ ) {
60+ num = ( num << 64n ) | ( src [ t + p ] as bigint ) ;
61+ }
62+ arr . push ( f ( num ) ) ;
63+ }
64+ }
65+ tests [ bits ] = arr ;
66+ }
67+ auras . forEach ( ( a ) => {
68+ it ( a + ' round-trips losslessly' , ( ) => {
69+ for ( let bits = minBits ; bits <= maxBits ; bits ++ ) {
70+ tests [ bits ] . forEach ( ( n ) => {
71+ n = BigInt ( n ) ;
72+ expect ( parse ( a , render ( a , n ) ) ) . toEqual ( n ) ;
73+ } ) ;
74+ }
4975 } ) ;
5076 } ) ;
5177 } ) ;
5278}
5379
54- fuzz ( '8-bit' , new Uint8Array ( testCount ) ) ;
55- fuzz ( '16-bit' , new Uint16Array ( testCount ) ) ;
56- fuzz ( '32-bit' , new Uint32Array ( testCount ) ) ;
57- fuzz ( '64-bit' , new BigUint64Array ( testCount ) ) ;
80+ fuzz ( 1 , 32 , [ ...simpleAuras , 'if' , 'is' ] ) ;
81+ fuzz ( 33 , 64 , [ ...simpleAuras , 'is' ] ) ;
82+ fuzz ( 65 , 128 , simpleAuras ) ;
83+
84+ // for floats, avoid NaNs, whose payload gets discarded during rendering,
85+ // and as such usually won't round-trip cleanly.
86+ // NaNs are encoded as "exponent bits all 1, non-zero significand",
87+ // so we hard-set one pseudo-random exponent bit to 0
88+ function safeFloat ( size : bigint , w : bigint , p : bigint ) {
89+ const full = ( 1n << size ) - 1n ;
90+ const makemask = ( n : bigint ) => full ^ ( 1n << ( p + ( n % w ) ) ) ;
91+ return ( n : bigint ) => n & makemask ( n ) ;
92+ }
93+ //TODO tests fail, off-by-one...
94+ // fuzz(4, 16, ['rh'], safeFloat( 16n, 5n, 10n));
95+ // fuzz(4, 32, ['rs'], safeFloat( 32n, 8n, 23n));
96+ // fuzz(4, 64, ['rd'], safeFloat( 64n, 11n, 52n));
97+ // fuzz(4, 128, ['rq'], safeFloat(128n, 15n, 112n));
0 commit comments