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