@@ -54,215 +54,226 @@ describe('FieldMask', () => {
5454 } ) ;
5555} ) ;
5656
57- class StringPair {
58- constructor ( readonly s1 : string , readonly s2 : string ) { }
59- }
60-
61- class StringPairGenerator {
62- constructor ( private stringGenerator : StringGenerator ) { }
63-
64- next ( ) : StringPair {
65- const prefix = this . stringGenerator . next ( ) ;
66- const s1 = prefix + this . stringGenerator . next ( ) ;
67- const s2 = prefix + this . stringGenerator . next ( ) ;
68- return new StringPair ( s1 , s2 ) ;
69- }
70- }
71-
72- class StringGenerator {
73- private static readonly DEFAULT_SURROGATE_PAIR_PROBABILITY = 0.33 ;
74- private static readonly DEFAULT_MAX_LENGTH = 20 ;
75-
76- private readonly rnd : Random ;
77- private readonly surrogatePairProbability : number ;
78- private readonly maxLength : number ;
79-
80- constructor ( seed : number ) ;
81- constructor ( rnd : Random , surrogatePairProbability : number , maxLength : number ) ;
82- constructor (
83- seedOrRnd : number | Random ,
84- surrogatePairProbability ?: number ,
85- maxLength ?: number
86- ) {
87- if ( typeof seedOrRnd === 'number' ) {
88- this . rnd = new Random ( seedOrRnd ) ;
89- this . surrogatePairProbability =
90- StringGenerator . DEFAULT_SURROGATE_PAIR_PROBABILITY ;
91- this . maxLength = StringGenerator . DEFAULT_MAX_LENGTH ;
92- } else {
93- this . rnd = seedOrRnd ;
94- this . surrogatePairProbability = StringGenerator . validateProbability (
95- surrogatePairProbability !
96- ) ;
97- this . maxLength = StringGenerator . validateLength ( maxLength ! ) ;
57+ describe ( 'CompareUtf8Strings' , ( ) => {
58+ it ( 'compareUtf8Strings should return correct results' , ( ) => {
59+ const errors = [ ] ;
60+ const seed = Math . floor ( Math . random ( ) * Number . MAX_SAFE_INTEGER ) ;
61+ let passCount = 0 ;
62+ const stringGenerator = new StringGenerator ( new Random ( seed ) , 0.33 , 20 ) ;
63+ const stringPairGenerator = new StringPairGenerator ( stringGenerator ) ;
64+
65+ for ( let i = 0 ; i < 1000000 && errors . length < 10 ; i ++ ) {
66+ const { s1, s2 } = stringPairGenerator . next ( ) ;
67+
68+ const actual = compareUtf8Strings ( s1 , s2 ) ;
69+ const expected = Buffer . from ( s1 , 'utf8' ) . compare ( Buffer . from ( s2 , 'utf8' ) ) ;
70+
71+ if ( actual === expected ) {
72+ passCount ++ ;
73+ } else {
74+ errors . push (
75+ `compareUtf8Strings(s1="${ s1 } ", s2="${ s2 } ") returned ${ actual } , ` +
76+ `but expected ${ expected } (i=${ i } , s1.length=${ s1 . length } , s2.length=${ s2 . length } )`
77+ ) ;
78+ }
9879 }
99- }
10080
101- private static validateProbability ( probability : number ) : number {
102- if ( ! Number . isFinite ( probability ) ) {
103- throw new Error (
104- `invalid surrogate pair probability: ${ probability } (must be between 0.0 and 1.0, inclusive)`
105- ) ;
106- } else if ( probability < 0.0 ) {
107- throw new Error (
108- `invalid surrogate pair probability: ${ probability } (must be greater than or equal to zero)`
81+ if ( errors . length > 0 ) {
82+ console . error (
83+ `${ errors . length } test cases failed, ${ passCount } test cases passed, seed=${ seed } ;`
10984 ) ;
110- } else if ( probability > 1.0 ) {
111- throw new Error (
112- `invalid surrogate pair probability: ${ probability } (must be less than or equal to 1)`
85+ errors . forEach ( ( error , index ) =>
86+ console . error ( `errors[${ index } ]: ${ error } ` )
11387 ) ;
88+ throw new Error ( 'Test failed' ) ;
11489 }
115- return probability ;
116- }
90+ } ) . timeout ( 20000 ) ;
11791
118- private static validateLength ( length : number ) : number {
119- if ( length < 0 ) {
120- throw new Error (
121- `invalid maximum string length: ${ length } (must be greater than or equal to zero)`
122- ) ;
123- }
124- return length ;
92+ class StringPair {
93+ constructor ( readonly s1 : string , readonly s2 : string ) { }
12594 }
12695
127- next ( ) : string {
128- const length = this . rnd . nextInt ( this . maxLength + 1 ) ;
129- const sb = new StringBuilder ( ) ;
130- while ( sb . length ( ) < length ) {
131- const codePoint = this . nextCodePoint ( ) ;
132- sb . appendCodePoint ( codePoint ) ;
96+ class StringPairGenerator {
97+ constructor ( private stringGenerator : StringGenerator ) { }
98+
99+ next ( ) : StringPair {
100+ const prefix = this . stringGenerator . next ( ) ;
101+ const s1 = prefix + this . stringGenerator . next ( ) ;
102+ const s2 = prefix + this . stringGenerator . next ( ) ;
103+ return new StringPair ( s1 , s2 ) ;
133104 }
134- return sb . toString ( ) ;
135105 }
136106
137- private isNextSurrogatePair ( ) : boolean {
138- return StringGenerator . nextBoolean ( this . rnd , this . surrogatePairProbability ) ;
139- }
107+ class StringGenerator {
108+ private static readonly DEFAULT_SURROGATE_PAIR_PROBABILITY = 0.33 ;
109+ private static readonly DEFAULT_MAX_LENGTH = 20 ;
140110
141- private static nextBoolean ( rnd : Random , probability : number ) : boolean {
142- if ( probability === 0.0 ) {
143- return false ;
144- } else if ( probability === 1.0 ) {
145- return true ;
146- } else {
147- return rnd . nextFloat ( ) < probability ;
148- }
149- }
111+ // Pseudo-random number generator. Seed can be set for repeatable tests.
112+ private readonly rnd : Random ;
113+ private readonly surrogatePairProbability : number ;
114+ private readonly maxLength : number ;
150115
151- private nextCodePoint ( ) : number {
152- if ( this . isNextSurrogatePair ( ) ) {
153- return this . nextSurrogateCodePoint ( ) ;
154- } else {
155- return this . nextNonSurrogateCodePoint ( ) ;
116+ constructor ( seed : number ) ;
117+ constructor (
118+ rnd : Random ,
119+ surrogatePairProbability : number ,
120+ maxLength : number
121+ ) ;
122+ constructor (
123+ seedOrRnd : number | Random ,
124+ surrogatePairProbability ?: number ,
125+ maxLength ?: number
126+ ) {
127+ if ( typeof seedOrRnd === 'number' ) {
128+ this . rnd = new Random ( seedOrRnd ) ;
129+ this . surrogatePairProbability =
130+ StringGenerator . DEFAULT_SURROGATE_PAIR_PROBABILITY ;
131+ this . maxLength = StringGenerator . DEFAULT_MAX_LENGTH ;
132+ } else {
133+ this . rnd = seedOrRnd ;
134+ this . surrogatePairProbability = StringGenerator . validateProbability (
135+ surrogatePairProbability !
136+ ) ;
137+ this . maxLength = StringGenerator . validateLength ( maxLength ! ) ;
138+ }
156139 }
157- }
158140
159- private nextSurrogateCodePoint ( ) : number {
160- const highSurrogateMin = 0xd800 ;
161- const highSurrogateMax = 0xdbff ;
162- const lowSurrogateMin = 0xdc00 ;
163- const lowSurrogateMax = 0xdfff ;
141+ private static validateProbability ( probability : number ) : number {
142+ if ( ! Number . isFinite ( probability ) ) {
143+ throw new Error (
144+ `invalid surrogate pair probability: ${ probability } (must be between 0.0 and 1.0, inclusive)`
145+ ) ;
146+ } else if ( probability < 0.0 ) {
147+ throw new Error (
148+ `invalid surrogate pair probability: ${ probability } (must be greater than or equal to zero)`
149+ ) ;
150+ } else if ( probability > 1.0 ) {
151+ throw new Error (
152+ `invalid surrogate pair probability: ${ probability } (must be less than or equal to 1)`
153+ ) ;
154+ }
155+ return probability ;
156+ }
164157
165- const highSurrogate = this . nextCodePointRange (
166- highSurrogateMin ,
167- highSurrogateMax
168- ) ;
169- const lowSurrogate = this . nextCodePointRange (
170- lowSurrogateMin ,
171- lowSurrogateMax
172- ) ;
158+ private static validateLength ( length : number ) : number {
159+ if ( length < 0 ) {
160+ throw new Error (
161+ `invalid maximum string length: ${ length } (must be greater than or equal to zero)`
162+ ) ;
163+ }
164+ return length ;
165+ }
173166
174- return ( highSurrogate - 0xd800 ) * 0x400 + ( lowSurrogate - 0xdc00 ) + 0x10000 ;
175- }
167+ next ( ) : string {
168+ const length = this . rnd . nextInt ( this . maxLength + 1 ) ;
169+ const sb = new StringBuilder ( ) ;
170+ while ( sb . length ( ) < length ) {
171+ const codePoint = this . nextCodePoint ( ) ;
172+ sb . appendCodePoint ( codePoint ) ;
173+ }
174+ return sb . toString ( ) ;
175+ }
176176
177- private nextNonSurrogateCodePoint ( ) : number {
178- let codePoint ;
179- do {
180- codePoint = this . nextCodePointRange ( 0 , 0xffff ) ; // BMP range
181- } while ( codePoint >= 0xd800 && codePoint <= 0xdfff ) ; // Exclude surrogate range
177+ private isNextSurrogatePair ( ) : boolean {
178+ return StringGenerator . nextBoolean (
179+ this . rnd ,
180+ this . surrogatePairProbability
181+ ) ;
182+ }
182183
183- return codePoint ;
184- }
184+ private static nextBoolean ( rnd : Random , probability : number ) : boolean {
185+ if ( probability === 0.0 ) {
186+ return false ;
187+ } else if ( probability === 1.0 ) {
188+ return true ;
189+ } else {
190+ return rnd . nextFloat ( ) < probability ;
191+ }
192+ }
185193
186- private nextCodePointRange ( min : number , max : number ) : number {
187- const rangeSize = max - min + 1 ;
188- const offset = this . rnd . nextInt ( rangeSize ) ;
189- return min + offset ;
190- }
191- }
194+ private nextCodePoint ( ) : number {
195+ if ( this . isNextSurrogatePair ( ) ) {
196+ return this . nextSurrogateCodePoint ( ) ;
197+ } else {
198+ return this . nextNonSurrogateCodePoint ( ) ;
199+ }
200+ }
192201
193- class Random {
194- private seed : number ;
202+ private nextSurrogateCodePoint ( ) : number {
203+ const highSurrogateMin = 0xd800 ;
204+ const highSurrogateMax = 0xdbff ;
205+ const lowSurrogateMin = 0xdc00 ;
206+ const lowSurrogateMax = 0xdfff ;
195207
196- constructor ( seed : number ) {
197- this . seed = seed ;
198- }
208+ const highSurrogate = this . nextCodePointRange (
209+ highSurrogateMin ,
210+ highSurrogateMax
211+ ) ;
212+ const lowSurrogate = this . nextCodePointRange (
213+ lowSurrogateMin ,
214+ lowSurrogateMax
215+ ) ;
199216
200- nextInt ( max : number ) : number {
201- this . seed = ( this . seed * 9301 + 49297 ) % 233280 ;
202- const rnd = this . seed / 233280 ;
203- return Math . floor ( rnd * max ) ;
204- }
217+ return (
218+ ( highSurrogate - 0xd800 ) * 0x400 + ( lowSurrogate - 0xdc00 ) + 0x10000
219+ ) ;
220+ }
205221
206- nextFloat ( ) : number {
207- this . seed = ( this . seed * 9301 + 49297 ) % 233280 ;
208- return this . seed / 233280 ;
209- }
210- }
222+ private nextNonSurrogateCodePoint ( ) : number {
223+ let codePoint ;
224+ do {
225+ codePoint = this . nextCodePointRange ( 0 , 0xffff ) ; // BMP range
226+ } while ( codePoint >= 0xd800 && codePoint <= 0xdfff ) ; // Exclude surrogate range
211227
212- class StringBuilder {
213- private buffer : string [ ] = [ ] ;
228+ return codePoint ;
229+ }
214230
215- append ( str : string ) : StringBuilder {
216- this . buffer . push ( str ) ;
217- return this ;
231+ private nextCodePointRange ( min : number , max : number ) : number {
232+ const rangeSize = max - min + 1 ;
233+ const offset = this . rnd . nextInt ( rangeSize ) ;
234+ return min + offset ;
235+ }
218236 }
219237
220- appendCodePoint ( codePoint : number ) : StringBuilder {
221- this . buffer . push ( String . fromCodePoint ( codePoint ) ) ;
222- return this ;
223- }
238+ class Random {
239+ private seed : number ;
224240
225- toString ( ) : string {
226- return this . buffer . join ( '' ) ;
227- }
241+ constructor ( seed : number ) {
242+ this . seed = seed ;
243+ }
244+
245+ nextInt ( max : number ) : number {
246+ // Update the seed with pseudo-randomized numbers using a Linear Congruential Generator (LCG).
247+ this . seed = ( this . seed * 9301 + 49297 ) % 233280 ;
248+ const rnd = this . seed / 233280 ;
249+ return Math . floor ( rnd * max ) ;
250+ }
228251
229- length ( ) : number {
230- return this . buffer . join ( '' ) . length ;
252+ nextFloat ( ) : number {
253+ this . seed = ( this . seed * 9301 + 49297 ) % 233280 ;
254+ return this . seed / 233280 ;
255+ }
231256 }
232- }
233257
234- describe ( 'CompareUtf8Strings' , ( ) => {
235- it ( 'compareUtf8Strings should return correct results' , ( ) => {
236- const errors = [ ] ;
237- const seed = Math . floor ( Math . random ( ) * Number . MAX_SAFE_INTEGER ) ;
238- let passCount = 0 ;
239- const stringGenerator = new StringGenerator ( new Random ( seed ) , 0.33 , 20 ) ;
240- const stringPairGenerator = new StringPairGenerator ( stringGenerator ) ;
258+ class StringBuilder {
259+ private buffer : string [ ] = [ ] ;
241260
242- for ( let i = 0 ; i < 1000000 && errors . length < 10 ; i ++ ) {
243- const { s1, s2 } = stringPairGenerator . next ( ) ;
261+ append ( str : string ) : StringBuilder {
262+ this . buffer . push ( str ) ;
263+ return this ;
264+ }
244265
245- const actual = compareUtf8Strings ( s1 , s2 ) ;
246- const expected = Buffer . from ( s1 , 'utf8' ) . compare ( Buffer . from ( s2 , 'utf8' ) ) ;
266+ appendCodePoint ( codePoint : number ) : StringBuilder {
267+ this . buffer . push ( String . fromCodePoint ( codePoint ) ) ;
268+ return this ;
269+ }
247270
248- if ( actual === expected ) {
249- passCount ++ ;
250- } else {
251- errors . push (
252- `compareUtf8Strings(s1="${ s1 } ", s2="${ s2 } ") returned ${ actual } , ` +
253- `but expected ${ expected } (i=${ i } , s1.length=${ s1 . length } , s2.length=${ s2 . length } )`
254- ) ;
255- }
271+ toString ( ) : string {
272+ return this . buffer . join ( '' ) ;
256273 }
257274
258- if ( errors . length > 0 ) {
259- console . error (
260- `${ errors . length } test cases failed, ${ passCount } test cases passed, seed=${ seed } ;`
261- ) ;
262- errors . forEach ( ( error , index ) =>
263- console . error ( `errors[${ index } ]: ${ error } ` )
264- ) ;
265- throw new Error ( 'Test failed' ) ;
275+ length ( ) : number {
276+ return this . buffer . join ( '' ) . length ;
266277 }
267- } ) . timeout ( 20000 ) ;
278+ }
268279} ) ;
0 commit comments