Skip to content

Commit e60df16

Browse files
committed
resolve comments
1 parent 4b528d9 commit e60df16

File tree

1 file changed

+184
-173
lines changed

1 file changed

+184
-173
lines changed

packages/firestore/test/unit/util/misc.test.ts

Lines changed: 184 additions & 173 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)