View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.lang3;
18  
19  import static org.hamcrest.MatcherAssert.assertThat;
20  import static org.hamcrest.Matchers.allOf;
21  import static org.hamcrest.Matchers.greaterThanOrEqualTo;
22  import static org.hamcrest.Matchers.is;
23  import static org.hamcrest.Matchers.lessThan;
24  import static org.hamcrest.Matchers.lessThanOrEqualTo;
25  import static org.junit.jupiter.api.Assertions.assertEquals;
26  import static org.junit.jupiter.api.Assertions.assertFalse;
27  import static org.junit.jupiter.api.Assertions.assertNotNull;
28  import static org.junit.jupiter.api.Assertions.assertThrows;
29  import static org.junit.jupiter.api.Assertions.assertTrue;
30  import static org.junit.jupiter.api.Assertions.fail;
31  
32  import java.lang.reflect.Constructor;
33  import java.lang.reflect.Modifier;
34  import java.nio.charset.Charset;
35  import java.nio.charset.StandardCharsets;
36  import java.util.Random;
37  
38  import org.junit.jupiter.api.Test;
39  
40  /**
41   * Tests {@link org.apache.commons.lang3.RandomStringUtils}.
42   */
43  public class RandomStringUtilsTest extends AbstractLangTest {
44  
45      private static final int LOOP_COUNT = 1_000;
46  
47      /**
48       * Computes Chi-Square statistic given observed and expected counts
49       * @param observed array of observed frequency counts
50       * @param expected array of expected frequency counts
51       */
52      private double chiSquare(final int[] expected, final int[] observed) {
53          double sumSq = 0.0d;
54          double dev = 0.0d;
55          for (int i = 0; i < observed.length; i++) {
56              dev = observed[i] - expected[i];
57              sumSq += dev * dev / expected[i];
58          }
59          return sumSq;
60      }
61  
62      /**
63       * Test for LANG-1286. Creates situation where old code would
64       * overflow a char and result in a code point outside the specified
65       * range.
66       */
67      @Test
68      public void testCharOverflow() {
69          final int start = Character.MAX_VALUE;
70          final int end = Integer.MAX_VALUE;
71  
72          @SuppressWarnings("serial")
73          final
74          Random fixedRandom = new Random() {
75              @Override
76              public int nextInt(final int n) {
77                  // Prevents selection of 'start' as the character
78                  return super.nextInt(n - 1) + 1;
79              }
80          };
81  
82          final String result = RandomStringUtils.random(2, start, end, false, false, null, fixedRandom);
83          final int c = result.codePointAt(0);
84          assertTrue(c >= start && c < end, String.format("Character '%d' not in range [%d,%d).", c, start, end));
85      }
86  
87      @Test
88      public void testConstructor() {
89          assertNotNull(new RandomStringUtils());
90          final Constructor<?>[] cons = RandomStringUtils.class.getDeclaredConstructors();
91          assertEquals(1, cons.length);
92          assertTrue(Modifier.isPublic(cons[0].getModifiers()));
93          assertTrue(Modifier.isPublic(RandomStringUtils.class.getModifiers()));
94          assertFalse(Modifier.isFinal(RandomStringUtils.class.getModifiers()));
95      }
96  
97      @Test
98      public void testExceptions() {
99          final char[] DUMMY = {'a'}; // valid char array
100         assertThrows(IllegalArgumentException.class, () -> RandomStringUtils.random(-1));
101         assertThrows(IllegalArgumentException.class, () -> RandomStringUtils.random(-1, true, true));
102         assertThrows(IllegalArgumentException.class, () -> RandomStringUtils.random(-1, DUMMY));
103         assertThrows(IllegalArgumentException.class, () -> RandomStringUtils.random(1, new char[0]));
104         assertThrows(IllegalArgumentException.class, () -> RandomStringUtils.random(-1, ""));
105         assertThrows(IllegalArgumentException.class, () -> RandomStringUtils.random(-1, (String) null));
106         assertThrows(IllegalArgumentException.class, () -> RandomStringUtils.random(-1, 'a', 'z', false, false));
107         assertThrows(IllegalArgumentException.class, () -> RandomStringUtils.random(-1, 'a', 'z', false, false, DUMMY));
108         assertThrows(
109                 IllegalArgumentException.class,
110                 () -> RandomStringUtils.random(-1, 'a', 'z', false, false, DUMMY, new Random()));
111         assertThrows(IllegalArgumentException.class, () -> RandomStringUtils.random(8, 32, 48, false, true));
112         assertThrows(IllegalArgumentException.class, () -> RandomStringUtils.random(8, 32, 65, true, false));
113         assertThrows(IllegalArgumentException.class, () -> RandomStringUtils.randomAlphabetic(-1));
114         assertThrows(IllegalArgumentException.class, () -> RandomStringUtils.randomAscii(-1));
115         assertThrows(IllegalArgumentException.class, () -> RandomStringUtils.randomGraph(-1));
116         assertThrows(IllegalArgumentException.class, () -> RandomStringUtils.randomNumeric(-1));
117         assertThrows(IllegalArgumentException.class, () -> RandomStringUtils.randomPrint(-1));
118     }
119 
120     /**
121      * Checks if the string got by {@link RandomStringUtils#random(int)}
122      * can be converted to UTF-8 and back without loss.
123      *
124      * @see <a href="https://issues.apache.org/jira/browse/LANG-100">LANG-100</a>
125      */
126     @Test
127     public void testLang100() {
128         final int size = 5000;
129         final Charset charset = StandardCharsets.UTF_8;
130         final String orig = RandomStringUtils.random(size);
131         final byte[] bytes = orig.getBytes(charset);
132         final String copy = new String(bytes, charset);
133 
134         // for a verbose compare:
135         for (int i=0; i < orig.length() && i < copy.length(); i++) {
136             final char o = orig.charAt(i);
137             final char c = copy.charAt(i);
138             assertEquals(o, c,
139                     "differs at " + i + "(" + Integer.toHexString(Character.valueOf(o).hashCode()) + "," +
140                             Integer.toHexString(Character.valueOf(c).hashCode()) + ")");
141         }
142         // compare length also
143         assertEquals(orig.length(), copy.length());
144         // just to be complete
145         assertEquals(orig, copy);
146     }
147 
148     @Test
149     public void testLANG805() {
150         final long seedMillis = System.currentTimeMillis();
151         assertEquals("aaa", RandomStringUtils.random(3, 0, 0, false, false, new char[]{'a'}, new Random(seedMillis)));
152     }
153 
154     @Test
155     public void testLANG807() {
156         final IllegalArgumentException ex =
157                 assertThrows(IllegalArgumentException.class, () -> RandomStringUtils.random(3, 5, 5, false, false));
158         final String msg = ex.getMessage();
159         assertTrue(msg.contains("start"), "Message (" + msg + ") must contain 'start'");
160         assertTrue(msg.contains("end"), "Message (" + msg + ") must contain 'end'");
161     }
162 
163     /**
164      * Make sure boundary alpha characters are generated by randomAlphabetic
165      * This test will fail randomly with probability = 4 * (51/52)**1000 ~ 1.58E-8
166      */
167     @Test
168     public void testRandomAlphabetic() {
169         final char[] testChars = {'a', 'z', 'A', 'Z'};
170         final boolean[] found = {false, false, false, false};
171         for (int i = 0; i < LOOP_COUNT; i++) {
172             final String randString = RandomStringUtils.randomAlphabetic(10);
173             for (int j = 0; j < testChars.length; j++) {
174                 if (randString.indexOf(testChars[j]) > 0) {
175                     found[j] = true;
176                 }
177             }
178         }
179         for (int i = 0; i < testChars.length; i++) {
180             assertTrue(found[i],
181                     "alphanumeric character not generated in 1000 attempts: " + testChars[i] +
182                             " -- repeated failures indicate a problem ");
183         }
184     }
185 
186     @Test
187     public void testRandomAlphabeticRange() {
188         final int expectedMinLengthInclusive = 1;
189         final int expectedMaxLengthExclusive = 11;
190         final String pattern = "^\\p{Alpha}{" + expectedMinLengthInclusive + ',' + expectedMaxLengthExclusive + "}$";
191 
192         int maxCreatedLength = expectedMinLengthInclusive;
193         int minCreatedLength = expectedMaxLengthExclusive - 1;
194         for (int i = 0; i < LOOP_COUNT; i++) {
195             final String s = RandomStringUtils.randomAlphabetic(expectedMinLengthInclusive, expectedMaxLengthExclusive);
196             assertThat("within range", s.length(), allOf(greaterThanOrEqualTo(expectedMinLengthInclusive), lessThanOrEqualTo(expectedMaxLengthExclusive - 1)));
197             assertTrue(s.matches(pattern), s);
198 
199             if (s.length() < minCreatedLength) {
200                 minCreatedLength = s.length();
201             }
202 
203             if (s.length() > maxCreatedLength) {
204                 maxCreatedLength = s.length();
205             }
206         }
207         assertThat("min generated, may fail randomly rarely", minCreatedLength, is(expectedMinLengthInclusive));
208         assertThat("max generated, may fail randomly rarely", maxCreatedLength, is(expectedMaxLengthExclusive - 1));
209     }
210 
211     /**
212      * Make sure boundary alphanumeric characters are generated by randomAlphaNumeric
213      * This test will fail randomly with probability = 6 * (61/62)**1000 ~ 5.2E-7
214      */
215     @Test
216     public void testRandomAlphaNumeric() {
217         final char[] testChars = {'a', 'z', 'A', 'Z', '0', '9'};
218         final boolean[] found = {false, false, false, false, false, false};
219         for (int i = 0; i < LOOP_COUNT; i++) {
220             final String randString = RandomStringUtils.randomAlphanumeric(10);
221             for (int j = 0; j < testChars.length; j++) {
222                 if (randString.indexOf(testChars[j]) > 0) {
223                     found[j] = true;
224                 }
225             }
226         }
227         for (int i = 0; i < testChars.length; i++) {
228             assertTrue(found[i],
229                     "alphanumeric character not generated in 1000 attempts: " +
230                             testChars[i] + " -- repeated failures indicate a problem ");
231         }
232     }
233 
234     @Test
235     public void testRandomAlphanumericRange() {
236         final int expectedMinLengthInclusive = 1;
237         final int expectedMaxLengthExclusive = 11;
238         final String pattern = "^\\p{Alnum}{" + expectedMinLengthInclusive + ',' + expectedMaxLengthExclusive + "}$";
239 
240         int maxCreatedLength = expectedMinLengthInclusive;
241         int minCreatedLength = expectedMaxLengthExclusive - 1;
242         for (int i = 0; i < LOOP_COUNT; i++) {
243             final String s = RandomStringUtils.randomAlphanumeric(expectedMinLengthInclusive, expectedMaxLengthExclusive);
244             assertThat("within range", s.length(), allOf(greaterThanOrEqualTo(expectedMinLengthInclusive), lessThanOrEqualTo(expectedMaxLengthExclusive - 1)));
245             assertTrue(s.matches(pattern), s);
246 
247             if (s.length() < minCreatedLength) {
248                 minCreatedLength = s.length();
249             }
250 
251             if (s.length() > maxCreatedLength) {
252                 maxCreatedLength = s.length();
253             }
254         }
255         assertThat("min generated, may fail randomly rarely", minCreatedLength, is(expectedMinLengthInclusive));
256         assertThat("max generated, may fail randomly rarely", maxCreatedLength, is(expectedMaxLengthExclusive - 1));
257     }
258 
259     /**
260      * Make sure 32 and 127 are generated by randomNumeric
261      * This test will fail randomly with probability = 2*(95/96)**1000 ~ 5.7E-5
262      */
263     @Test
264     public void testRandomAscii() {
265         final char[] testChars = {(char) 32, (char) 126};
266         final boolean[] found = {false, false};
267         // Test failures have been observed on GitHub builds with a 100 limit.
268         for (int i = 0; i < LOOP_COUNT; i++) {
269             final String randString = RandomStringUtils.randomAscii(10);
270             for (int j = 0; j < testChars.length; j++) {
271                 if (randString.indexOf(testChars[j]) > 0) {
272                     found[j] = true;
273                 }
274             }
275         }
276         for (int i = 0; i < testChars.length; i++) {
277             assertTrue(found[i],
278                     "ascii character not generated in 1000 attempts: " + (int) testChars[i] +
279                             " -- repeated failures indicate a problem");
280         }
281     }
282 
283     @Test
284     public void testRandomAsciiRange() {
285         final int expectedMinLengthInclusive = 1;
286         final int expectedMaxLengthExclusive = 11;
287         final String pattern = "^\\p{ASCII}{" + expectedMinLengthInclusive + ',' + expectedMaxLengthExclusive + "}$";
288 
289         int maxCreatedLength = expectedMinLengthInclusive;
290         int minCreatedLength = expectedMaxLengthExclusive - 1;
291         for (int i = 0; i < LOOP_COUNT; i++) {
292             final String s = RandomStringUtils.randomAscii(expectedMinLengthInclusive, expectedMaxLengthExclusive);
293             assertThat("within range", s.length(), allOf(greaterThanOrEqualTo(expectedMinLengthInclusive), lessThanOrEqualTo(expectedMaxLengthExclusive - 1)));
294             assertTrue(s.matches(pattern), s);
295 
296             if (s.length() < minCreatedLength) {
297                 minCreatedLength = s.length();
298             }
299 
300             if (s.length() > maxCreatedLength) {
301                 maxCreatedLength = s.length();
302             }
303         }
304         assertThat("min generated, may fail randomly rarely", minCreatedLength, is(expectedMinLengthInclusive));
305         assertThat("max generated, may fail randomly rarely", maxCreatedLength, is(expectedMaxLengthExclusive - 1));
306     }
307 
308     @Test
309     public void testRandomGraphRange() {
310         final int expectedMinLengthInclusive = 1;
311         final int expectedMaxLengthExclusive = 11;
312         final String pattern = "^\\p{Graph}{" + expectedMinLengthInclusive + ',' + expectedMaxLengthExclusive + "}$";
313 
314         int maxCreatedLength = expectedMinLengthInclusive;
315         int minCreatedLength = expectedMaxLengthExclusive - 1;
316         for (int i = 0; i < LOOP_COUNT; i++) {
317             final String s = RandomStringUtils.randomGraph(expectedMinLengthInclusive, expectedMaxLengthExclusive);
318             assertThat("within range", s.length(), allOf(greaterThanOrEqualTo(expectedMinLengthInclusive), lessThanOrEqualTo(expectedMaxLengthExclusive - 1)));
319             assertTrue(s.matches(pattern), s);
320 
321             if (s.length() < minCreatedLength) {
322                 minCreatedLength = s.length();
323             }
324 
325             if (s.length() > maxCreatedLength) {
326                 maxCreatedLength = s.length();
327             }
328         }
329         assertThat("min generated, may fail randomly rarely", minCreatedLength, is(expectedMinLengthInclusive));
330         assertThat("max generated, may fail randomly rarely", maxCreatedLength, is(expectedMaxLengthExclusive - 1));
331     }
332 
333     /**
334      * Make sure '0' and '9' are generated by randomNumeric
335      * This test will fail randomly with probability = 2 * (9/10)**1000 ~ 3.5E-46
336      */
337     @Test
338     public void testRandomNumeric() {
339         final char[] testChars = {'0', '9'};
340         final boolean[] found = {false, false};
341         for (int i = 0; i < LOOP_COUNT; i++) {
342             final String randString = RandomStringUtils.randomNumeric(10);
343             for (int j = 0; j < testChars.length; j++) {
344                 if (randString.indexOf(testChars[j]) > 0) {
345                     found[j] = true;
346                 }
347             }
348         }
349         for (int i = 0; i < testChars.length; i++) {
350             assertTrue(found[i],
351                     "digit not generated in 1000 attempts: " + testChars[i] +
352                             " -- repeated failures indicate a problem ");
353         }
354     }
355 
356     @Test
357     public void testRandomNumericRange() {
358         final int expectedMinLengthInclusive = 1;
359         final int expectedMaxLengthExclusive = 11;
360         final String pattern = "^\\p{Digit}{" + expectedMinLengthInclusive + ',' + expectedMaxLengthExclusive + "}$";
361 
362         int maxCreatedLength = expectedMinLengthInclusive;
363         int minCreatedLength = expectedMaxLengthExclusive - 1;
364         for (int i = 0; i < LOOP_COUNT; i++) {
365             final String s = RandomStringUtils.randomNumeric(expectedMinLengthInclusive, expectedMaxLengthExclusive);
366             assertThat("within range", s.length(), allOf(greaterThanOrEqualTo(expectedMinLengthInclusive), lessThanOrEqualTo(expectedMaxLengthExclusive - 1)));
367             assertTrue(s.matches(pattern), s);
368 
369             if (s.length() < minCreatedLength) {
370                 minCreatedLength = s.length();
371             }
372 
373             if (s.length() > maxCreatedLength) {
374                 maxCreatedLength = s.length();
375             }
376         }
377         assertThat("min generated, may fail randomly rarely", minCreatedLength, is(expectedMinLengthInclusive));
378         assertThat("max generated, may fail randomly rarely", maxCreatedLength, is(expectedMaxLengthExclusive - 1));
379     }
380 
381     @Test
382     public void testRandomPrintRange() {
383         final int expectedMinLengthInclusive = 1;
384         final int expectedMaxLengthExclusive = 11;
385         final String pattern = "^\\p{Print}{" + expectedMinLengthInclusive + ',' + expectedMaxLengthExclusive + "}$";
386 
387         int maxCreatedLength = expectedMinLengthInclusive;
388         int minCreatedLength = expectedMaxLengthExclusive - 1;
389         for (int i = 0; i < LOOP_COUNT; i++) {
390             final String s = RandomStringUtils.randomPrint(expectedMinLengthInclusive, expectedMaxLengthExclusive);
391             assertThat("within range", s.length(), allOf(greaterThanOrEqualTo(expectedMinLengthInclusive), lessThanOrEqualTo(expectedMaxLengthExclusive - 1)));
392             assertTrue(s.matches(pattern), s);
393 
394             if (s.length() < minCreatedLength) {
395                 minCreatedLength = s.length();
396             }
397 
398             if (s.length() > maxCreatedLength) {
399                 maxCreatedLength = s.length();
400             }
401         }
402         assertThat("min generated, may fail randomly rarely", minCreatedLength, is(expectedMinLengthInclusive));
403         assertThat("max generated, may fail randomly rarely", maxCreatedLength, is(expectedMaxLengthExclusive - 1));
404     }
405 
406     /**
407      * Test the implementation
408      */
409     @Test
410     public void testRandomStringUtils() {
411         String r1 = RandomStringUtils.random(50);
412         assertEquals(50, r1.length(), "random(50) length");
413         String r2 = RandomStringUtils.random(50);
414         assertEquals(50, r2.length(), "random(50) length");
415         assertFalse(r1.equals(r2), "!r1.equals(r2)");
416 
417         r1 = RandomStringUtils.randomAscii(50);
418         assertEquals(50, r1.length(), "randomAscii(50) length");
419         for (int i = 0; i < r1.length(); i++) {
420             assertThat("char >= 32 && <= 127", ((int) r1.charAt(i)), allOf(greaterThanOrEqualTo(32), lessThanOrEqualTo(127)));
421         }
422         r2 = RandomStringUtils.randomAscii(50);
423         assertFalse(r1.equals(r2), "!r1.equals(r2)");
424 
425         r1 = RandomStringUtils.randomAlphabetic(50);
426         assertEquals(50, r1.length(), "randomAlphabetic(50)");
427         for (int i = 0; i < r1.length(); i++) {
428             assertTrue(Character.isLetter(r1.charAt(i)) && !Character.isDigit(r1.charAt(i)), "r1 contains alphabetic");
429         }
430         r2 = RandomStringUtils.randomAlphabetic(50);
431         assertFalse(r1.equals(r2), "!r1.equals(r2)");
432 
433         r1 = RandomStringUtils.randomAlphanumeric(50);
434         assertEquals(50, r1.length(), "randomAlphanumeric(50)");
435         for (int i = 0; i < r1.length(); i++) {
436             assertTrue(Character.isLetterOrDigit(r1.charAt(i)), "r1 contains alphanumeric");
437         }
438         r2 = RandomStringUtils.randomAlphabetic(50);
439         assertFalse(r1.equals(r2), "!r1.equals(r2)");
440 
441         r1 = RandomStringUtils.randomGraph(50);
442         assertEquals(50, r1.length(), "randomGraph(50) length");
443         for (int i = 0; i < r1.length(); i++) {
444             assertTrue(r1.charAt(i) >= 33 && r1.charAt(i) <= 126, "char between 33 and 126");
445         }
446         r2 = RandomStringUtils.randomGraph(50);
447         assertFalse(r1.equals(r2), "!r1.equals(r2)");
448 
449         r1 = RandomStringUtils.randomNumeric(50);
450         assertEquals(50, r1.length(), "randomNumeric(50)");
451         for (int i = 0; i < r1.length(); i++) {
452             assertTrue(Character.isDigit(r1.charAt(i)) && !Character.isLetter(r1.charAt(i)), "r1 contains numeric");
453         }
454         r2 = RandomStringUtils.randomNumeric(50);
455         assertFalse(r1.equals(r2), "!r1.equals(r2)");
456 
457         r1 = RandomStringUtils.randomPrint(50);
458         assertEquals(50, r1.length(), "randomPrint(50) length");
459         for (int i = 0; i < r1.length(); i++) {
460             assertTrue(r1.charAt(i) >= 32 && r1.charAt(i) <= 126, "char between 32 and 126");
461         }
462         r2 = RandomStringUtils.randomPrint(50);
463         assertFalse(r1.equals(r2), "!r1.equals(r2)");
464 
465         String set = "abcdefg";
466         r1 = RandomStringUtils.random(50, set);
467         assertEquals(50, r1.length(), "random(50, \"abcdefg\")");
468         for (int i = 0; i < r1.length(); i++) {
469             assertTrue(set.indexOf(r1.charAt(i)) > -1, "random char in set");
470         }
471         r2 = RandomStringUtils.random(50, set);
472         assertFalse(r1.equals(r2), "!r1.equals(r2)");
473 
474         r1 = RandomStringUtils.random(50, (String) null);
475         assertEquals(50, r1.length(), "random(50) length");
476         r2 = RandomStringUtils.random(50, (String) null);
477         assertEquals(50, r2.length(), "random(50) length");
478         assertFalse(r1.equals(r2), "!r1.equals(r2)");
479 
480         set = "stuvwxyz";
481         r1 = RandomStringUtils.random(50, set.toCharArray());
482         assertEquals(50, r1.length(), "random(50, \"stuvwxyz\")");
483         for (int i = 0; i < r1.length(); i++) {
484             assertTrue(set.indexOf(r1.charAt(i)) > -1, "random char in set");
485         }
486         r2 = RandomStringUtils.random(50, set);
487         assertFalse(r1.equals(r2), "!r1.equals(r2)");
488 
489         r1 = RandomStringUtils.random(50, (char[]) null);
490         assertEquals(50, r1.length(), "random(50) length");
491         r2 = RandomStringUtils.random(50, (char[]) null);
492         assertEquals(50, r2.length(), "random(50) length");
493         assertFalse(r1.equals(r2), "!r1.equals(r2)");
494 
495         final long seedMillis = System.currentTimeMillis();
496         r1 = RandomStringUtils.random(50, 0, 0, true, true, null, new Random(seedMillis));
497         r2 = RandomStringUtils.random(50, 0, 0, true, true, null, new Random(seedMillis));
498         assertEquals(r1, r2, "r1.equals(r2)");
499 
500         r1 = RandomStringUtils.random(0);
501         assertEquals("", r1, "random(0).equals(\"\")");
502     }
503 
504 
505     /**
506      * Test homogeneity of random strings generated --
507      * i.e., test that characters show up with expected frequencies
508      * in generated strings.  Will fail randomly about 1 in 100,000 times.
509      * Repeated failures indicate a problem.
510      */
511     @Test
512     public void testRandomStringUtilsHomog() {
513         final String set = "abc";
514         final char[] chars = set.toCharArray();
515         String gen = "";
516         final int[] counts = {0, 0, 0};
517         final int[] expected = {200, 200, 200};
518         for (int i = 0; i < 100; i++) {
519             gen = RandomStringUtils.random(6, chars);
520             for (int j = 0; j < 6; j++) {
521                 switch (gen.charAt(j)) {
522                     case 'a': {
523                         counts[0]++;
524                         break;
525                     }
526                     case 'b': {
527                         counts[1]++;
528                         break;
529                     }
530                     case 'c': {
531                         counts[2]++;
532                         break;
533                     }
534                     default: {
535                         fail("generated character not in set");
536                     }
537                 }
538             }
539         }
540         // Perform chi-square test with degrees of freedom = 3-1 = 2, testing at 1e-5 level.
541         // This expects a failure rate of 1 in 100,000.
542         // critical value: from scipy.stats import chi2; chi2(2).isf(1e-5)
543         assertThat("test homogeneity -- will fail about 1 in 100,000 times", chiSquare(expected, counts), lessThan(23.025850929940457d));
544     }
545 }
546