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    *      https://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.apache.commons.lang3.LangAssertions.assertIllegalArgumentException;
20  import static org.junit.jupiter.api.Assertions.assertEquals;
21  import static org.junit.jupiter.api.Assertions.assertNotEquals;
22  import static org.junit.jupiter.api.Assertions.assertNotNull;
23  import static org.junit.jupiter.api.Assertions.assertThrows;
24  import static org.junit.jupiter.api.Assertions.assertTrue;
25  import static org.junit.jupiter.api.Assertions.fail;
26  
27  import java.nio.charset.Charset;
28  import java.nio.charset.StandardCharsets;
29  import java.util.Random;
30  import java.util.stream.Stream;
31  
32  import org.junit.jupiter.api.Test;
33  import org.junit.jupiter.api.Timeout;
34  import org.junit.jupiter.api.condition.EnabledIfSystemProperty;
35  import org.junit.jupiter.params.ParameterizedTest;
36  import org.junit.jupiter.params.provider.MethodSource;
37  import org.junit.jupiter.params.provider.ValueSource;
38  
39  /**
40   * Tests {@link RandomStringUtils}.
41   */
42  class RandomStringUtilsTest extends AbstractLangTest {
43  
44      private static final int LOOP_COUNT = 1_000;
45  
46      /** Maximum safe value for count to avoid overflow: (21x + 3) / 5 + 10 < 0x0FFF_FFFF */
47      private static final int MAX_SAFE_COUNT = 63_913_201;
48  
49  
50      static Stream<RandomStringUtils> randomProvider() {
51          return Stream.of(RandomStringUtils.secure(), RandomStringUtils.secureStrong(), RandomStringUtils.insecure());
52      }
53  
54      /**
55       * Computes Chi-Square statistic given observed and expected counts
56       *
57       * @param observed array of observed frequency counts
58       * @param expected array of expected frequency counts
59       */
60      private double chiSquare(final int[] expected, final int[] observed) {
61          double sumSq = 0.0d;
62          for (int i = 0; i < observed.length; i++) {
63              final double dev = observed[i] - expected[i];
64              sumSq += dev * dev / expected[i];
65          }
66          return sumSq;
67      }
68  
69      /**
70       * Test for LANG-1286. Creates situation where old code would overflow a char and result in a code point outside the specified range.
71       */
72      @Test
73      void testCharOverflow() {
74          final int start = Character.MAX_VALUE;
75          final int end = Integer.MAX_VALUE;
76  
77          @SuppressWarnings("serial")
78          final Random fixedRandom = new Random() {
79              @Override
80              public int nextInt(final int n) {
81                  // Prevents selection of 'start' as the character
82                  return super.nextInt(n - 1) + 1;
83              }
84          };
85  
86          final String result = RandomStringUtils.random(2, start, end, false, false, null, fixedRandom);
87          final int c = result.codePointAt(0);
88          assertTrue(c >= start && c < end, String.format("Character '%d' not in range [%d,%d).", c, start, end));
89      }
90  
91      @Test
92      void testConstructor() {
93          assertNotNull(new RandomStringUtils());
94      }
95  
96      @Test
97      void testExceptionsRandom() {
98          assertIllegalArgumentException(() -> RandomStringUtils.random(-1));
99          assertIllegalArgumentException(() -> RandomStringUtils.random(-1, true, true));
100         assertIllegalArgumentException(() -> RandomStringUtils.random(-1, new char[] { 'a' }));
101         assertIllegalArgumentException(() -> RandomStringUtils.random(1, new char[0]));
102         assertIllegalArgumentException(() -> RandomStringUtils.random(-1, ""));
103         assertIllegalArgumentException(() -> RandomStringUtils.random(-1, (String) null));
104         assertIllegalArgumentException(() -> RandomStringUtils.random(-1, 'a', 'z', false, false));
105         assertIllegalArgumentException(() -> RandomStringUtils.random(-1, 'a', 'z', false, false, new char[] { 'a' }));
106         assertIllegalArgumentException(() -> RandomStringUtils.random(-1, 'a', 'z', false, false, new char[] { 'a' }, new Random()));
107         assertIllegalArgumentException(() -> RandomStringUtils.random(8, 32, 48, false, true));
108         assertIllegalArgumentException(() -> RandomStringUtils.random(8, 32, 65, true, false));
109         assertIllegalArgumentException(() -> RandomStringUtils.random(1, Integer.MIN_VALUE, -10, false, false, null));
110     }
111 
112     @ParameterizedTest
113     @MethodSource("randomProvider")
114     void testExceptionsRandom(final RandomStringUtils rsu) {
115         assertIllegalArgumentException(() -> rsu.next(-1));
116         assertIllegalArgumentException(() -> rsu.next(-1, true, true));
117         assertIllegalArgumentException(() -> rsu.next(-1, new char[] { 'a' }));
118         assertIllegalArgumentException(() -> rsu.next(1, new char[0]));
119         assertIllegalArgumentException(() -> rsu.next(-1, ""));
120         assertIllegalArgumentException(() -> rsu.next(-1, (String) null));
121         assertIllegalArgumentException(() -> rsu.next(-1, 'a', 'z', false, false));
122         assertIllegalArgumentException(() -> rsu.next(-1, 'a', 'z', false, false, new char[] { 'a' }));
123         assertIllegalArgumentException(() -> rsu.next(8, 32, 48, false, true));
124         assertIllegalArgumentException(() -> rsu.next(8, 32, 65, true, false));
125         assertIllegalArgumentException(() -> rsu.next(1, Integer.MIN_VALUE, -10, false, false, null));
126     }
127 
128     @Test
129     void testExceptionsRandomAlphabetic() {
130         assertIllegalArgumentException(() -> RandomStringUtils.randomAlphabetic(-1));
131     }
132 
133     @ParameterizedTest
134     @MethodSource("randomProvider")
135     void testExceptionsRandomAlphabetic(final RandomStringUtils rsu) {
136         assertIllegalArgumentException(() -> rsu.nextAlphabetic(-1));
137     }
138 
139     @Test
140     void testExceptionsRandomAscii() {
141         assertIllegalArgumentException(() -> RandomStringUtils.randomAscii(-1));
142     }
143 
144     @ParameterizedTest
145     @MethodSource("randomProvider")
146     void testExceptionsRandomAscii(final RandomStringUtils rsu) {
147         assertIllegalArgumentException(() -> rsu.nextAscii(-1));
148     }
149 
150     @Test
151     void testExceptionsRandomGraph() {
152         assertIllegalArgumentException(() -> RandomStringUtils.randomGraph(-1));
153     }
154 
155     @ParameterizedTest
156     @MethodSource("randomProvider")
157     void testExceptionsRandomGraph(final RandomStringUtils rsu) {
158         assertIllegalArgumentException(() -> rsu.nextGraph(-1));
159     }
160 
161     @Test
162     void testExceptionsRandomNumeric() {
163         assertIllegalArgumentException(() -> RandomStringUtils.randomNumeric(-1));
164     }
165 
166     @ParameterizedTest
167     @MethodSource("randomProvider")
168     void testExceptionsRandomNumeric(final RandomStringUtils rsu) {
169         assertIllegalArgumentException(() -> rsu.nextNumeric(-1));
170     }
171 
172     @Test
173     void testExceptionsRandomPrint() {
174         assertIllegalArgumentException(() -> RandomStringUtils.randomPrint(-1));
175     }
176 
177     @ParameterizedTest
178     @MethodSource("randomProvider")
179     void testExceptionsRandomPrint(final RandomStringUtils rsu) {
180         assertIllegalArgumentException(() -> rsu.nextPrint(-1));
181     }
182 
183     @Test
184     @Timeout(value = 2, threadMode = Timeout.ThreadMode.SAME_THREAD)
185     void testFilterLetters() {
186         assertThrows(IllegalArgumentException.class, () -> RandomStringUtils.random(5, 0x80, 0xA0, true, false, null, new Random()));
187     }
188 
189     @Test
190     @Timeout(value = 2, threadMode = Timeout.ThreadMode.SAME_THREAD)
191     void testFilterNumbers() {
192         assertThrows(IllegalArgumentException.class, () -> RandomStringUtils.random(5, 0x80, 0xA0, false, true, null, new Random()));
193     }
194 
195     /**
196      * Test homogeneity of random strings generated -- i.e., test that characters show up with expected frequencies in generated strings. Will fail randomly
197      * about 1 in 100,000 times. Repeated failures indicate a problem.
198      *
199      * @param rsu the instance to test.
200      */
201     @ParameterizedTest
202     @MethodSource("randomProvider")
203     void testHomogeneity(final RandomStringUtils rsu) {
204         final String set = "abc";
205         final char[] chars = set.toCharArray();
206         final int[] counts = { 0, 0, 0 };
207         final int[] expected = { 200, 200, 200 };
208         for (int i = 0; i < 100; i++) {
209             final String gen = rsu.next(6, chars);
210             for (int j = 0; j < 6; j++) {
211                 switch (gen.charAt(j)) {
212                 case 'a': {
213                     counts[0]++;
214                     break;
215                 }
216                 case 'b': {
217                     counts[1]++;
218                     break;
219                 }
220                 case 'c': {
221                     counts[2]++;
222                     break;
223                 }
224                 default: {
225                     fail("generated character not in set");
226                 }
227                 }
228             }
229         }
230         // Perform chi-square test with degrees of freedom = 3-1 = 2, testing at 1e-5 level.
231         // This expects a failure rate of 1 in 100,000.
232         // critical value: from scipy.stats import chi2; chi2(2).isf(1e-5)
233         assertTrue(chiSquare(expected, counts) < 23.025850929940457d, "test homogeneity -- will fail about 1 in 100,000 times");
234     }
235 
236     @ParameterizedTest
237     @ValueSource(ints = {MAX_SAFE_COUNT, MAX_SAFE_COUNT + 1})
238     @EnabledIfSystemProperty(named = "test.large.heap", matches = "true")
239     void testHugeStrings(final int expectedLength) {
240         final String hugeString = RandomStringUtils.random(expectedLength);
241         assertEquals(expectedLength, hugeString.length(), "hugeString.length() == expectedLength");
242     }
243 
244     /**
245      * Checks if the string got by {@link RandomStringUtils#random(int)} can be converted to UTF-8 and back without loss.
246      *
247      * @see <a href="https://issues.apache.org/jira/browse/LANG-100">LANG-100</a>
248      */
249     @Test
250     void testLang100() {
251         final int size = 5000;
252         final Charset charset = StandardCharsets.UTF_8;
253         final String orig = RandomStringUtils.random(size);
254         final byte[] bytes = orig.getBytes(charset);
255         final String copy = new String(bytes, charset);
256 
257         // for a verbose compare:
258         for (int i = 0; i < orig.length() && i < copy.length(); i++) {
259             final char o = orig.charAt(i);
260             final char c = copy.charAt(i);
261             assertEquals(o, c, "differs at " + i + "(" + Integer.toHexString(Character.valueOf(o).hashCode()) + ","
262                     + Integer.toHexString(Character.valueOf(c).hashCode()) + ")");
263         }
264         // compare length also
265         assertEquals(orig.length(), copy.length());
266         // just to be complete
267         assertEquals(orig, copy);
268     }
269 
270     /**
271      * Checks if the string got by {@link RandomStringUtils#random(int)} can be converted to UTF-8 and back without loss.
272      *
273      * @param rsu the instance to test
274      * @see <a href="https://issues.apache.org/jira/browse/LANG-100">LANG-100</a>
275      */
276     @ParameterizedTest
277     @MethodSource("randomProvider")
278     void testLang100(final RandomStringUtils rsu) {
279         final int size = 5000;
280         final Charset charset = StandardCharsets.UTF_8;
281         final String orig = rsu.next(size);
282         final byte[] bytes = orig.getBytes(charset);
283         final String copy = new String(bytes, charset);
284 
285         // for a verbose compare:
286         for (int i = 0; i < orig.length() && i < copy.length(); i++) {
287             final char o = orig.charAt(i);
288             final char c = copy.charAt(i);
289             assertEquals(o, c, "differs at " + i + "(" + Integer.toHexString(Character.valueOf(o).hashCode()) + ","
290                     + Integer.toHexString(Character.valueOf(c).hashCode()) + ")");
291         }
292         // compare length also
293         assertEquals(orig.length(), copy.length());
294         // just to be complete
295         assertEquals(orig, copy);
296     }
297 
298     @Test
299     void testLANG805() {
300         final long seedMillis = System.currentTimeMillis();
301         assertEquals("aaa", RandomStringUtils.random(3, 0, 0, false, false, new char[] { 'a' }, new Random(seedMillis)));
302     }
303 
304     @ParameterizedTest
305     @MethodSource("randomProvider")
306     void testLANG807(final RandomStringUtils rsu) {
307         final IllegalArgumentException ex = assertIllegalArgumentException(() -> rsu.next(3, 5, 5, false, false));
308         final String msg = ex.getMessage();
309         assertTrue(msg.contains("start"), "Message (" + msg + ") must contain 'start'");
310         assertTrue(msg.contains("end"), "Message (" + msg + ") must contain 'end'");
311     }
312 
313     /**
314      * Test {@code RandomStringUtils.random} works appropriately when letters=true
315      * and the range does not only include ASCII letters.
316      * Fails with probability less than 2^-40 (in practice this never happens).
317      */
318     @ParameterizedTest
319     @MethodSource("randomProvider")
320     void testNonASCIILetters(final RandomStringUtils rsu) {
321         // Check that the following create a string with 10 characters 0x4e00 (a non-ASCII letter)
322         String r1 = rsu.next(10, 0x4e00, 0x4e01, true, false);
323         assertEquals(10, r1.length(), "wrong length");
324         for (int i = 0; i < r1.length(); i++) {
325             assertEquals(0x4e00, r1.charAt(i), "characters not all equal to 0x4e00");
326         }
327 
328         // Same with both letters=true and numbers=true
329         r1 = rsu.next(10, 0x4e00, 0x4e01, true, true);
330         assertEquals(10, r1.length(), "wrong length");
331         for (int i = 0; i < r1.length(); i++) {
332             assertEquals(0x4e00, r1.charAt(i), "characters not all equal to 0x4e00");
333         }
334 
335         // Check that at least one letter is not ASCII
336         boolean found = false;
337         r1 = rsu.next(40, 'F', 0x3000, true, false);
338         assertEquals(40, r1.length(), "wrong length");
339         for (int i = 0; i < r1.length(); i++) {
340             assertTrue(Character.isLetter(r1.charAt(i)), "characters not all letters");
341             if (r1.charAt(i) > 0x7f) {
342                 found = true;
343             }
344         }
345         assertTrue(found, "no non-ASCII letter generated");
346     }
347 
348     /**
349      * Test {@code RandomStringUtils.random} works appropriately when numbers=true
350      * and the range does not only include ASCII numbers/digits.
351      * Fails with probability less than 2^-40 (in practice this never happens).
352      */
353     @ParameterizedTest
354     @MethodSource("randomProvider")
355     void testNonASCIINumbers(final RandomStringUtils rsu) {
356         // Check that the following create a string with 10 characters 0x0660 (a non-ASCII digit)
357         String r1 = rsu.next(10, 0x0660, 0x0661, false, true);
358         assertEquals(10, r1.length(), "wrong length");
359         for (int i = 0; i < r1.length(); i++) {
360             assertEquals(0x0660, r1.charAt(i), "characters not all equal to 0x0660");
361         }
362 
363         // Same with both letters=true and numbers=true
364         r1 = rsu.next(10, 0x0660, 0x0661, true, true);
365         assertEquals(10, r1.length(), "wrong length");
366         for (int i = 0; i < r1.length(); i++) {
367             assertEquals(0x0660, r1.charAt(i), "characters not all equal to 0x0660");
368         }
369 
370         // Check that at least one letter is not ASCII
371         boolean found = false;
372         r1 = rsu.next(40, 'F', 0x3000, false, true);
373         assertEquals(40, r1.length(), "wrong length");
374         for (int i = 0; i < r1.length(); i++) {
375             assertTrue(Character.isDigit(r1.charAt(i)), "characters not all numbers");
376             if (r1.charAt(i) > 0x7f) {
377                 found = true;
378             }
379         }
380         assertTrue(found, "no non-ASCII number generated");
381     }
382 
383     /**
384      * Make sure boundary alpha characters are generated by randomAlphabetic This test will fail randomly with probability = 4 * (51/52)**1000 ~ 1.58E-8
385      */
386     @Test
387     void testRandomAlphabetic() {
388         final char[] testChars = { 'a', 'z', 'A', 'Z' };
389         final boolean[] found = { false, false, false, false };
390         for (int i = 0; i < LOOP_COUNT; i++) {
391             final String randString = RandomStringUtils.randomAlphabetic(10);
392             for (int j = 0; j < testChars.length; j++) {
393                 if (randString.indexOf(testChars[j]) > 0) {
394                     found[j] = true;
395                 }
396             }
397         }
398         for (int i = 0; i < testChars.length; i++) {
399             assertTrue(found[i], "alphanumeric character not generated in 1000 attempts: " + testChars[i] + " -- repeated failures indicate a problem ");
400         }
401     }
402 
403     /**
404      * Make sure boundary alpha characters are generated by randomAlphabetic This test will fail randomly with probability = 4 * (51/52)**1000 ~ 1.58E-8
405      *
406      * @param rsu the instance to test
407      */
408     @ParameterizedTest
409     @MethodSource("randomProvider")
410     void testRandomAlphabetic(final RandomStringUtils rsu) {
411         final char[] testChars = { 'a', 'z', 'A', 'Z' };
412         final boolean[] found = { false, false, false, false };
413         for (int i = 0; i < LOOP_COUNT; i++) {
414             final String randString = rsu.nextAlphabetic(10);
415             for (int j = 0; j < testChars.length; j++) {
416                 if (randString.indexOf(testChars[j]) > 0) {
417                     found[j] = true;
418                 }
419             }
420         }
421         for (int i = 0; i < testChars.length; i++) {
422             assertTrue(found[i], "alphanumeric character not generated in 1000 attempts: " + testChars[i] + " -- repeated failures indicate a problem ");
423         }
424     }
425 
426     @Test
427     void testRandomAlphabeticRange() {
428         final int expectedMinLengthInclusive = 1;
429         final int expectedMaxLengthExclusive = 11;
430         final String pattern = "^\\p{Alpha}{" + expectedMinLengthInclusive + ',' + expectedMaxLengthExclusive + "}$";
431 
432         int maxCreatedLength = expectedMinLengthInclusive;
433         int minCreatedLength = expectedMaxLengthExclusive - 1;
434         for (int i = 0; i < LOOP_COUNT; i++) {
435             final String s = RandomStringUtils.randomAlphabetic(expectedMinLengthInclusive, expectedMaxLengthExclusive);
436             assertTrue(s.length() >= expectedMinLengthInclusive, "within range");
437             assertTrue(s.length() <= expectedMaxLengthExclusive - 1, "within range");
438             assertTrue(s.matches(pattern), s);
439 
440             if (s.length() < minCreatedLength) {
441                 minCreatedLength = s.length();
442             }
443 
444             if (s.length() > maxCreatedLength) {
445                 maxCreatedLength = s.length();
446             }
447         }
448         assertEquals(expectedMinLengthInclusive, minCreatedLength, "min generated, may fail randomly rarely");
449         assertEquals(expectedMaxLengthExclusive - 1, maxCreatedLength, "max generated, may fail randomly rarely");
450     }
451 
452     @ParameterizedTest
453     @MethodSource("randomProvider")
454     void testRandomAlphabeticRange(final RandomStringUtils rsu) {
455         final int expectedMinLengthInclusive = 1;
456         final int expectedMaxLengthExclusive = 11;
457         final String pattern = "^\\p{Alpha}{" + expectedMinLengthInclusive + ',' + expectedMaxLengthExclusive + "}$";
458 
459         int maxCreatedLength = expectedMinLengthInclusive;
460         int minCreatedLength = expectedMaxLengthExclusive - 1;
461         for (int i = 0; i < LOOP_COUNT; i++) {
462             final String s = rsu.nextAlphabetic(expectedMinLengthInclusive, expectedMaxLengthExclusive);
463             assertTrue(s.length() >= expectedMinLengthInclusive, "within range");
464             assertTrue(s.length() <= expectedMaxLengthExclusive - 1, "within range");
465 
466             assertTrue(s.matches(pattern), s);
467 
468             if (s.length() < minCreatedLength) {
469                 minCreatedLength = s.length();
470             }
471 
472             if (s.length() > maxCreatedLength) {
473                 maxCreatedLength = s.length();
474             }
475         }
476         assertEquals(expectedMinLengthInclusive, minCreatedLength, "min generated, may fail randomly rarely");
477         assertEquals(expectedMaxLengthExclusive - 1, maxCreatedLength, "max generated, may fail randomly rarely");
478     }
479 
480     /**
481      * Make sure boundary alphanumeric characters are generated by randomAlphaNumeric This test will fail randomly with probability = 6 * (61/62)**1000 ~ 5.2E-7
482      */
483     @Test
484     void testRandomAlphaNumeric() {
485         final char[] testChars = { 'a', 'z', 'A', 'Z', '0', '9' };
486         final boolean[] found = { false, false, false, false, false, false };
487         for (int i = 0; i < LOOP_COUNT; i++) {
488             final String randString = RandomStringUtils.randomAlphanumeric(10);
489             for (int j = 0; j < testChars.length; j++) {
490                 if (randString.indexOf(testChars[j]) > 0) {
491                     found[j] = true;
492                 }
493             }
494         }
495         for (int i = 0; i < testChars.length; i++) {
496             assertTrue(found[i], "alphanumeric character not generated in 1000 attempts: " + testChars[i] + " -- repeated failures indicate a problem ");
497         }
498     }
499 
500     /**
501      * Make sure boundary alphanumeric characters are generated by randomAlphaNumeric This test will fail randomly with probability = 6 * (61/62)**1000 ~ 5.2E-7
502      *
503      * @param rsu the instance to test
504      */
505     @ParameterizedTest
506     @MethodSource("randomProvider")
507     void testRandomAlphaNumeric(final RandomStringUtils rsu) {
508         final char[] testChars = { 'a', 'z', 'A', 'Z', '0', '9' };
509         final boolean[] found = { false, false, false, false, false, false };
510         for (int i = 0; i < LOOP_COUNT; i++) {
511             final String randString = rsu.nextAlphanumeric(10);
512             for (int j = 0; j < testChars.length; j++) {
513                 if (randString.indexOf(testChars[j]) > 0) {
514                     found[j] = true;
515                 }
516             }
517         }
518         for (int i = 0; i < testChars.length; i++) {
519             assertTrue(found[i], "alphanumeric character not generated in 1000 attempts: " + testChars[i] + " -- repeated failures indicate a problem ");
520         }
521     }
522 
523     @Test
524     void testRandomAlphanumericRange() {
525         final int expectedMinLengthInclusive = 1;
526         final int expectedMaxLengthExclusive = 11;
527         final String pattern = "^\\p{Alnum}{" + expectedMinLengthInclusive + ',' + expectedMaxLengthExclusive + "}$";
528 
529         int maxCreatedLength = expectedMinLengthInclusive;
530         int minCreatedLength = expectedMaxLengthExclusive - 1;
531         for (int i = 0; i < LOOP_COUNT; i++) {
532             final String s = RandomStringUtils.randomAlphanumeric(expectedMinLengthInclusive, expectedMaxLengthExclusive);
533             assertTrue(s.length() >= expectedMinLengthInclusive, "within range");
534             assertTrue(s.length() <= expectedMaxLengthExclusive - 1, "within range");
535             assertTrue(s.matches(pattern), s);
536 
537             if (s.length() < minCreatedLength) {
538                 minCreatedLength = s.length();
539             }
540 
541             if (s.length() > maxCreatedLength) {
542                 maxCreatedLength = s.length();
543             }
544         }
545         assertEquals(expectedMinLengthInclusive, minCreatedLength, "min generated, may fail randomly rarely");
546         assertEquals(expectedMaxLengthExclusive - 1, maxCreatedLength, "max generated, may fail randomly rarely");
547     }
548 
549     /**
550      * Test the implementation
551      *
552      * @param rsu the instance to test.
553      */
554     @ParameterizedTest
555     @MethodSource("randomProvider")
556     void testRandomApis(final RandomStringUtils rsu) {
557         String r1 = rsu.next(50);
558         assertEquals(50, r1.length(), "random(50) length");
559         String r2 = rsu.next(50);
560         assertEquals(50, r2.length(), "random(50) length");
561         assertNotEquals(r1, r2, "!r1.equals(r2)");
562 
563         r1 = rsu.nextAscii(50);
564         assertEquals(50, r1.length(), "randomAscii(50) length");
565         for (int i = 0; i < r1.length(); i++) {
566             final int ch = r1.charAt(i);
567             assertTrue(ch >= 32, "char >= 32");
568             assertTrue(ch <= 127, "char <= 127");
569         }
570         r2 = rsu.nextAscii(50);
571         assertNotEquals(r1, r2, "!r1.equals(r2)");
572 
573         r1 = rsu.nextAlphabetic(50);
574         assertEquals(50, r1.length(), "randomAlphabetic(50)");
575         for (int i = 0; i < r1.length(); i++) {
576             assertTrue(Character.isLetter(r1.charAt(i)) && !Character.isDigit(r1.charAt(i)), "r1 contains alphabetic");
577         }
578         r2 = rsu.nextAlphabetic(50);
579         assertNotEquals(r1, r2, "!r1.equals(r2)");
580 
581         r1 = rsu.nextAlphanumeric(50);
582         assertEquals(50, r1.length(), "randomAlphanumeric(50)");
583         for (int i = 0; i < r1.length(); i++) {
584             assertTrue(Character.isLetterOrDigit(r1.charAt(i)), "r1 contains alphanumeric");
585         }
586         r2 = rsu.nextAlphabetic(50);
587         assertNotEquals(r1, r2, "!r1.equals(r2)");
588 
589         r1 = rsu.nextGraph(50);
590         assertEquals(50, r1.length(), "randomGraph(50) length");
591         for (int i = 0; i < r1.length(); i++) {
592             assertTrue(r1.charAt(i) >= 33 && r1.charAt(i) <= 126, "char between 33 and 126");
593         }
594         r2 = rsu.nextGraph(50);
595         assertNotEquals(r1, r2, "!r1.equals(r2)");
596 
597         r1 = rsu.nextNumeric(50);
598         assertEquals(50, r1.length(), "randomNumeric(50)");
599         for (int i = 0; i < r1.length(); i++) {
600             assertTrue(Character.isDigit(r1.charAt(i)) && !Character.isLetter(r1.charAt(i)), "r1 contains numeric");
601         }
602         r2 = rsu.nextNumeric(50);
603         assertNotEquals(r1, r2, "!r1.equals(r2)");
604 
605         r1 = rsu.nextPrint(50);
606         assertEquals(50, r1.length(), "randomPrint(50) length");
607         for (int i = 0; i < r1.length(); i++) {
608             assertTrue(r1.charAt(i) >= 32 && r1.charAt(i) <= 126, "char between 32 and 126");
609         }
610         r2 = rsu.nextPrint(50);
611         assertNotEquals(r1, r2, "!r1.equals(r2)");
612 
613         String set = "abcdefg";
614         r1 = rsu.next(50, set);
615         assertEquals(50, r1.length(), "random(50, \"abcdefg\")");
616         for (int i = 0; i < r1.length(); i++) {
617             assertTrue(set.indexOf(r1.charAt(i)) > -1, "random char in set");
618         }
619         r2 = rsu.next(50, set);
620         assertNotEquals(r1, r2, "!r1.equals(r2)");
621 
622         r1 = rsu.next(50, (String) null);
623         assertEquals(50, r1.length(), "random(50) length");
624         r2 = rsu.next(50, (String) null);
625         assertEquals(50, r2.length(), "random(50) length");
626         assertNotEquals(r1, r2, "!r1.equals(r2)");
627 
628         set = "stuvwxyz";
629         r1 = rsu.next(50, set.toCharArray());
630         assertEquals(50, r1.length(), "random(50, \"stuvwxyz\")");
631         for (int i = 0; i < r1.length(); i++) {
632             assertTrue(set.indexOf(r1.charAt(i)) > -1, "random char in set");
633         }
634         r2 = rsu.next(50, set);
635         assertNotEquals(r1, r2, "!r1.equals(r2)");
636 
637         r1 = rsu.next(50, (char[]) null);
638         assertEquals(50, r1.length(), "random(50) length");
639         r2 = rsu.next(50, (char[]) null);
640         assertEquals(50, r2.length(), "random(50) length");
641         assertNotEquals(r1, r2, "!r1.equals(r2)");
642 
643         r1 = rsu.next(0);
644         assertEquals("", r1, "random(0).equals(\"\")");
645     }
646 
647     /**
648      * Make sure 32 and 127 are generated by randomNumeric This test will fail randomly with probability = 2*(95/96)**1000 ~ 5.7E-5
649      *
650      * @param rsu the instance to test
651      */
652     @ParameterizedTest
653     @MethodSource("randomProvider")
654     void testRandomAscii(final RandomStringUtils rsu) {
655         final char[] testChars = { (char) 32, (char) 126 };
656         final boolean[] found = { false, false };
657         // Test failures have been observed on GitHub builds with a 100 limit.
658         for (int i = 0; i < LOOP_COUNT; i++) {
659             final String randString = rsu.nextAscii(10);
660             for (int j = 0; j < testChars.length; j++) {
661                 if (randString.indexOf(testChars[j]) > 0) {
662                     found[j] = true;
663                 }
664             }
665         }
666         for (int i = 0; i < testChars.length; i++) {
667             assertTrue(found[i], "ascii character not generated in 1000 attempts: " + (int) testChars[i] + " -- repeated failures indicate a problem");
668         }
669     }
670 
671     @ParameterizedTest
672     @MethodSource("randomProvider")
673     void testRandomAsciiRange(final RandomStringUtils rsu) {
674         final int expectedMinLengthInclusive = 1;
675         final int expectedMaxLengthExclusive = 11;
676         final String pattern = "^\\p{ASCII}{" + expectedMinLengthInclusive + ',' + expectedMaxLengthExclusive + "}$";
677 
678         int maxCreatedLength = expectedMinLengthInclusive;
679         int minCreatedLength = expectedMaxLengthExclusive - 1;
680         for (int i = 0; i < LOOP_COUNT; i++) {
681             final String s = rsu.nextAscii(expectedMinLengthInclusive, expectedMaxLengthExclusive);
682             assertTrue(s.length() >= expectedMinLengthInclusive, "within range");
683             assertTrue(s.length() <= expectedMaxLengthExclusive - 1, "within range");
684             assertTrue(s.matches(pattern), s);
685 
686             if (s.length() < minCreatedLength) {
687                 minCreatedLength = s.length();
688             }
689 
690             if (s.length() > maxCreatedLength) {
691                 maxCreatedLength = s.length();
692             }
693         }
694         assertEquals(expectedMinLengthInclusive, minCreatedLength, "min generated, may fail randomly rarely");
695         assertEquals(expectedMaxLengthExclusive - 1, maxCreatedLength, "max generated, may fail randomly rarely");
696     }
697 
698     @ParameterizedTest
699     @MethodSource("randomProvider")
700     void testRandomGraphRange(final RandomStringUtils rsu) {
701         final int expectedMinLengthInclusive = 1;
702         final int expectedMaxLengthExclusive = 11;
703         final String pattern = "^\\p{Graph}{" + expectedMinLengthInclusive + ',' + expectedMaxLengthExclusive + "}$";
704 
705         int maxCreatedLength = expectedMinLengthInclusive;
706         int minCreatedLength = expectedMaxLengthExclusive - 1;
707         for (int i = 0; i < LOOP_COUNT; i++) {
708             final String s = rsu.nextGraph(expectedMinLengthInclusive, expectedMaxLengthExclusive);
709             assertTrue(s.length() >= expectedMinLengthInclusive, "within range");
710             assertTrue(s.length() <= expectedMaxLengthExclusive - 1, "within range");
711             assertTrue(s.matches(pattern), s);
712 
713             if (s.length() < minCreatedLength) {
714                 minCreatedLength = s.length();
715             }
716 
717             if (s.length() > maxCreatedLength) {
718                 maxCreatedLength = s.length();
719             }
720         }
721         assertEquals(expectedMinLengthInclusive, minCreatedLength, "min generated, may fail randomly rarely");
722         assertEquals(expectedMaxLengthExclusive - 1, maxCreatedLength, "max generated, may fail randomly rarely");
723     }
724 
725     /**
726      * Make sure '0' and '9' are generated by randomNumeric This test will fail randomly with probability = 2 * (9/10)**1000 ~ 3.5E-46
727      *
728      * @param rsu the instance to test
729      */
730     @ParameterizedTest
731     @MethodSource("randomProvider")
732     void testRandomNumeric(final RandomStringUtils rsu) {
733         final char[] testChars = { '0', '9' };
734         final boolean[] found = { false, false };
735         for (int i = 0; i < LOOP_COUNT; i++) {
736             final String randString = rsu.nextNumeric(10);
737             for (int j = 0; j < testChars.length; j++) {
738                 if (randString.indexOf(testChars[j]) > 0) {
739                     found[j] = true;
740                 }
741             }
742         }
743         for (int i = 0; i < testChars.length; i++) {
744             assertTrue(found[i], "digit not generated in 1000 attempts: " + testChars[i] + " -- repeated failures indicate a problem ");
745         }
746     }
747 
748     @ParameterizedTest
749     @MethodSource("randomProvider")
750     void testRandomNumericRange(final RandomStringUtils rsu) {
751         final int expectedMinLengthInclusive = 1;
752         final int expectedMaxLengthExclusive = 11;
753         final String pattern = "^\\p{Digit}{" + expectedMinLengthInclusive + ',' + expectedMaxLengthExclusive + "}$";
754 
755         int maxCreatedLength = expectedMinLengthInclusive;
756         int minCreatedLength = expectedMaxLengthExclusive - 1;
757         for (int i = 0; i < LOOP_COUNT; i++) {
758             final String s = rsu.nextNumeric(expectedMinLengthInclusive, expectedMaxLengthExclusive);
759             assertTrue(s.length() >= expectedMinLengthInclusive, "within range");
760             assertTrue(s.length() <= expectedMaxLengthExclusive - 1, "within range");
761             assertTrue(s.matches(pattern), s);
762 
763             if (s.length() < minCreatedLength) {
764                 minCreatedLength = s.length();
765             }
766 
767             if (s.length() > maxCreatedLength) {
768                 maxCreatedLength = s.length();
769             }
770         }
771         assertEquals(expectedMinLengthInclusive, minCreatedLength, "min generated, may fail randomly rarely");
772         assertEquals(expectedMaxLengthExclusive - 1, maxCreatedLength, "max generated, may fail randomly rarely");
773     }
774 
775     @Test
776     void testRandomParameter() {
777         final long seedMillis = System.currentTimeMillis();
778         final String r1 = RandomStringUtils.random(50, 0, 0, true, true, null, new Random(seedMillis));
779         final String r2 = RandomStringUtils.random(50, 0, 0, true, true, null, new Random(seedMillis));
780         assertEquals(r1, r2, "r1.equals(r2)");
781     }
782 
783     @ParameterizedTest
784     @MethodSource("randomProvider")
785     void testRandomPrintRange(final RandomStringUtils rsu) {
786         final int expectedMinLengthInclusive = 1;
787         final int expectedMaxLengthExclusive = 11;
788         final String pattern = "^\\p{Print}{" + expectedMinLengthInclusive + ',' + expectedMaxLengthExclusive + "}$";
789 
790         int maxCreatedLength = expectedMinLengthInclusive;
791         int minCreatedLength = expectedMaxLengthExclusive - 1;
792         for (int i = 0; i < LOOP_COUNT; i++) {
793             final String s = rsu.nextPrint(expectedMinLengthInclusive, expectedMaxLengthExclusive);
794             assertTrue(s.length() >= expectedMinLengthInclusive, "within range");
795             assertTrue(s.length() <= expectedMaxLengthExclusive - 1, "within range");
796             assertTrue(s.matches(pattern), s);
797 
798             if (s.length() < minCreatedLength) {
799                 minCreatedLength = s.length();
800             }
801 
802             if (s.length() > maxCreatedLength) {
803                 maxCreatedLength = s.length();
804             }
805         }
806         assertEquals(expectedMinLengthInclusive, minCreatedLength, "min generated, may fail randomly rarely");
807         assertEquals(expectedMaxLengthExclusive - 1, maxCreatedLength, "max generated, may fail randomly rarely");
808     }
809 
810     /**
811      * Test {@code RandomStringUtils.random} works appropriately when chars specified.
812      *
813      * @param rsu the instance to test.
814      */
815     @ParameterizedTest
816     @MethodSource("randomProvider")
817     void testRandomWithChars(final RandomStringUtils rsu) {
818         final char[] digitChars = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'};
819         final String r1 = rsu.next(50, 0, 0, true, true, digitChars);
820         assertEquals(50, r1.length(), "randomNumeric(50)");
821         for (int i = 0; i < r1.length(); i++) {
822             assertTrue(
823                     Character.isDigit(r1.charAt(i)) && !Character.isLetter(r1.charAt(i)),
824                     "r1 contains numeric");
825         }
826         final String r2 = rsu.nextNumeric(50);
827         assertNotEquals(r1, r2);
828 
829         final String r3 = rsu.next(50, 0, 0, true, true, digitChars);
830         assertNotEquals(r1, r3);
831         assertNotEquals(r2, r3);
832     }
833 }