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