Skip to content

Commit b740bcc

Browse files
committed
UtilTest.java: add test for compareUtf8Strings(), which fails and needs to be fixed
1 parent 003a529 commit b740bcc

File tree

1 file changed

+179
-0
lines changed
  • firebase-firestore/src/test/java/com/google/firebase/firestore/util

1 file changed

+179
-0
lines changed

firebase-firestore/src/test/java/com/google/firebase/firestore/util/UtilTest.java

Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import static com.google.common.truth.Truth.assertThat;
1818
import static com.google.firebase.firestore.util.Util.firstNEntries;
1919
import static org.junit.Assert.assertEquals;
20+
import static org.junit.Assert.fail;
2021

2122
import com.google.firebase.firestore.testutil.TestUtil;
2223
import com.google.protobuf.ByteString;
@@ -26,6 +27,7 @@
2627
import java.util.HashMap;
2728
import java.util.List;
2829
import java.util.Map;
30+
import java.util.Random;
2931
import org.junit.Test;
3032
import org.junit.runner.RunWith;
3133
import org.junit.runners.JUnit4;
@@ -87,4 +89,181 @@ private void validateDiffCollection(List<String> before, List<String> after) {
8789
Util.diffCollections(before, after, String::compareTo, result::add, result::remove);
8890
assertThat(result).containsExactlyElementsIn(after);
8991
}
92+
93+
@Test
94+
public void compareUtf8StringsShouldReturnCorrectValue() {
95+
ArrayList<String> errors = new ArrayList<>();
96+
int seed = new Random().nextInt();
97+
int passCount = 0;
98+
StringGenerator stringGenerator = new StringGenerator(seed);
99+
for (int i = 0; i < 1_000_000 && errors.size() < 10; i++) {
100+
String s1 = stringGenerator.next();
101+
String s2 = stringGenerator.next();
102+
103+
int actual = Util.compareUtf8Strings(s1, s2);
104+
105+
ByteString b1 = ByteString.copyFromUtf8(s1);
106+
ByteString b2 = ByteString.copyFromUtf8(s2);
107+
int expected = Util.compareByteStrings(b1, b2);
108+
109+
if (actual == expected) {
110+
passCount++;
111+
} else {
112+
errors.add(
113+
"compareUtf8Strings(s1=\""
114+
+ s1
115+
+ "\", s2=\""
116+
+ s2
117+
+ "\") returned "
118+
+ actual
119+
+ ", but expected "
120+
+ expected
121+
+ " (i="
122+
+ i
123+
+ ", s1.length="
124+
+ s1.length()
125+
+ ", s2.length="
126+
+ s2.length()
127+
+ ")");
128+
}
129+
}
130+
131+
if (!errors.isEmpty()) {
132+
StringBuilder sb = new StringBuilder();
133+
sb.append(errors.size()).append(" test cases failed, ");
134+
sb.append(passCount).append(" test cases passed, ");
135+
sb.append("seed=").append(seed).append(";");
136+
for (int i = 0; i < errors.size(); i++) {
137+
sb.append("\nerrors[").append(i).append("]: ").append(errors.get(i));
138+
}
139+
fail(sb.toString());
140+
}
141+
}
142+
143+
private static class StringGenerator {
144+
145+
private static final float DEFAULT_SURROGATE_PAIR_PROBABILITY = 0.33f;
146+
private static final int DEFAULT_MAX_LENGTH = 20;
147+
148+
// The first Unicode code point that is in the basic multilingual plane ("BMP") and,
149+
// therefore requires 1 UTF-16 code unit to be represented in UTF-16.
150+
private static final int MIN_BMP_CODE_POINT = 0x00000000;
151+
152+
// The last Unicode code point that is in the basic multilingual plane ("BMP") and,
153+
// therefore requires 1 UTF-16 code unit to be represented in UTF-16.
154+
private static final int MAX_BMP_CODE_POINT = 0x0000FFFF;
155+
156+
// The first Unicode code point that is outside of the basic multilingual plane ("BMP") and,
157+
// therefore requires 2 UTF-16 code units, a surrogate pair, to be represented in UTF-16.
158+
private static final int MIN_SUPPLEMENTARY_CODE_POINT = 0x00010000;
159+
160+
// The last Unicode code point that is outside of the basic multilingual plane ("BMP") and,
161+
// therefore requires 2 UTF-16 code units, a surrogate pair, to be represented in UTF-16.
162+
private static final int MAX_SUPPLEMENTARY_CODE_POINT = 0x0010FFFF;
163+
164+
private final Random rnd;
165+
private final float surrogatePairProbability;
166+
private final int maxLength;
167+
168+
public StringGenerator(int seed) {
169+
this(new Random(seed), DEFAULT_SURROGATE_PAIR_PROBABILITY, DEFAULT_MAX_LENGTH);
170+
}
171+
172+
public StringGenerator(Random rnd, float surrogatePairProbability, int maxLength) {
173+
this.rnd = rnd;
174+
this.surrogatePairProbability =
175+
validateProbability("surrogate pair", surrogatePairProbability);
176+
this.maxLength = validateLength("maximum string", maxLength);
177+
}
178+
179+
private static float validateProbability(String name, float probability) {
180+
if (!Float.isFinite(probability)) {
181+
throw new IllegalArgumentException(
182+
"invalid "
183+
+ name
184+
+ " probability: "
185+
+ probability
186+
+ " (must be between 0.0 and 1.0, inclusive)");
187+
} else if (probability < 0.0f) {
188+
throw new IllegalArgumentException(
189+
"invalid "
190+
+ name
191+
+ " probability: "
192+
+ probability
193+
+ " (must be greater than or equal to zero)");
194+
} else if (probability > 1.0f) {
195+
throw new IllegalArgumentException(
196+
"invalid "
197+
+ name
198+
+ " probability: "
199+
+ probability
200+
+ " (must be less than or equal to 1)");
201+
}
202+
return probability;
203+
}
204+
205+
private static int validateLength(String name, int length) {
206+
if (length < 0) {
207+
throw new IllegalArgumentException(
208+
"invalid " + name + " length: " + length + " (must be greater than or equal to zero)");
209+
}
210+
return length;
211+
}
212+
213+
public String next() {
214+
final int length = rnd.nextInt(maxLength + 1);
215+
final StringBuilder sb = new StringBuilder();
216+
while (sb.length() < length) {
217+
sb.appendCodePoint(nextCodePoint());
218+
}
219+
return sb.toString();
220+
}
221+
222+
private boolean isNextSurrogatePair() {
223+
return nextBoolean(rnd, surrogatePairProbability);
224+
}
225+
226+
private static boolean nextBoolean(Random rnd, float probability) {
227+
if (probability == 0.0f) {
228+
return false;
229+
} else if (probability == 1.0f) {
230+
return true;
231+
} else {
232+
return rnd.nextFloat() < probability;
233+
}
234+
}
235+
236+
private int nextCodePoint() {
237+
if (isNextSurrogatePair()) {
238+
return nextSurrogateCodePoint();
239+
} else {
240+
return nextNonSurrogateCodePoint();
241+
}
242+
}
243+
244+
private int nextSurrogateCodePoint() {
245+
return nextCodePoint(rnd, MIN_SUPPLEMENTARY_CODE_POINT, MAX_SUPPLEMENTARY_CODE_POINT, 2);
246+
}
247+
248+
private int nextNonSurrogateCodePoint() {
249+
return nextCodePoint(rnd, MIN_BMP_CODE_POINT, MAX_BMP_CODE_POINT, 1);
250+
}
251+
252+
private int nextCodePoint(Random rnd, int min, int max, int expectedCharCount) {
253+
int rangeSize = max - min;
254+
int offset = rnd.nextInt(rangeSize);
255+
int codePoint = min + offset;
256+
if (Character.charCount(codePoint) != expectedCharCount) {
257+
throw new RuntimeException(
258+
"internal error vqgqnxcy97: "
259+
+ "Character.charCount("
260+
+ codePoint
261+
+ ") returned "
262+
+ Character.charCount(codePoint)
263+
+ ", but expected "
264+
+ expectedCharCount);
265+
}
266+
return codePoint;
267+
}
268+
}
90269
}

0 commit comments

Comments
 (0)