This is an automated email from the ASF dual-hosted git repository. garydgregory pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/commons-lang.git
commit 2780ad699014d6ce9c7141b15c45bb2c806ef173 Author: Gary Gregory <[email protected]> AuthorDate: Mon Jun 15 12:00:37 2026 +0000 Fix RandomStringUtils.random() false rejection of letters and digits (#1703). Sort members --- src/changes/changes.xml | 1 + .../commons/lang3/RandomStringUtilsTest.java | 60 +++++++++++----------- 2 files changed, 31 insertions(+), 30 deletions(-) diff --git a/src/changes/changes.xml b/src/changes/changes.xml index f76c6f675..dc87f2991 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -187,6 +187,7 @@ java.lang.NullPointerException: Cannot invoke <action type="fix" dev="ggregory" due-to="alhudz">Fix int overflow in StringUtils.midString() and StrBuilder.midString() length handling (#1699).</action> <action type="fix" dev="ggregory" due-to="Gary Gregory, Javid Khan">Throw IllegalArgumentException for trailing whitespace in ExtendedMessageFormat argument index (#1701).</action> <action type="fix" dev="ggregory" due-to="alhudz, Gary Gregory">Reject doubled leading sign in NumberUtils.createBigInteger (#1702).</action> + <action type="fix" dev="ggregory" due-to="alhudz, Gary Gregory">Fix RandomStringUtils.random() false rejection of letters and digits (#1703).</action> <!-- ADD --> <action type="add" dev="ggregory" due-to="Gary Gregory">Add JavaVersion.JAVA_27.</action> <action type="add" dev="ggregory" due-to="Gary Gregory">Add SystemUtils.IS_JAVA_27.</action> diff --git a/src/test/java/org/apache/commons/lang3/RandomStringUtilsTest.java b/src/test/java/org/apache/commons/lang3/RandomStringUtilsTest.java index 2c6a29181..ab0c3717f 100644 --- a/src/test/java/org/apache/commons/lang3/RandomStringUtilsTest.java +++ b/src/test/java/org/apache/commons/lang3/RandomStringUtilsTest.java @@ -119,36 +119,6 @@ public void testCustomLetterCharsArrayDoesNotThrowIAE() { }, "RandomStringUtils.random() threw IAE for valid letter chars array - pre-patch behavior"); } - /** - * Asking for {@code letters && digits} must never be stricter than asking for {@code digits} alone. The range - * {@code ['0', 'A')} holds the digits but no letters, so {@code random(count, '0', 'A', true, true, ...)} must - * generate digits like the digits-only call over the same range, not throw IllegalArgumentException. - */ - @Test - void testLettersAndDigitsOverDigitOnlyRange() { - final String both = RandomStringUtils.random(100, '0', 'A', true, true, null, new Random(42)); - assertEquals(100, both.length()); - for (final char c : both.toCharArray()) { - assertTrue(c >= '0' && c <= '9', () -> "Expected a digit but got: " + c); - } - // digits alone already works over this range, so letters && digits must not reject it - assertDoesNotThrow(() -> RandomStringUtils.random(100, '0', 'A', false, true, null, new Random(42))); - } - - /** - * The {@code letters && digits} ASCII fast path clamps {@code start} up to {@code '0'} and {@code end} down to - * {@code 'z' + 1}. A range sitting entirely above the alphanumerics, e.g. {@code ['z' + 1, 0x7f)}, collapses to - * {@code start >= end} after that clamp. It must throw a clear range IllegalArgumentException, not fall through to - * {@code nextBits(0)} which reports the unrelated "number of bits must be between 1 and 32". - */ - @Test - void testLettersAndDigitsOverEmptyAsciiRange() { - final IllegalArgumentException e = assertThrows(IllegalArgumentException.class, - () -> RandomStringUtils.random(10, 'z' + 1, 0x7f, true, true, null, new Random(42))); - assertTrue(e.getMessage() != null && !e.getMessage().contains("number of bits"), - () -> "Expected a range-validation message but got: " + e.getMessage()); - } - @Test void testExceptionsRandom() { assertIllegalArgumentException(() -> RandomStringUtils.random(-1)); @@ -367,6 +337,36 @@ void testLANG807(final RandomStringUtils rsu) { assertTrue(msg.contains("end"), "Message (" + msg + ") must contain 'end'"); } + /** + * Asking for {@code letters && digits} must never be stricter than asking for {@code digits} alone. The range + * {@code ['0', 'A')} holds the digits but no letters, so {@code random(count, '0', 'A', true, true, ...)} must + * generate digits like the digits-only call over the same range, not throw IllegalArgumentException. + */ + @Test + void testLettersAndDigitsOverDigitOnlyRange() { + final String both = RandomStringUtils.random(100, '0', 'A', true, true, null, new Random(42)); + assertEquals(100, both.length()); + for (final char c : both.toCharArray()) { + assertTrue(c >= '0' && c <= '9', () -> "Expected a digit but got: " + c); + } + // digits alone already works over this range, so letters && digits must not reject it + assertDoesNotThrow(() -> RandomStringUtils.random(100, '0', 'A', false, true, null, new Random(42))); + } + + /** + * The {@code letters && digits} ASCII fast path clamps {@code start} up to {@code '0'} and {@code end} down to + * {@code 'z' + 1}. A range sitting entirely above the alphanumerics, e.g. {@code ['z' + 1, 0x7f)}, collapses to + * {@code start >= end} after that clamp. It must throw a clear range IllegalArgumentException, not fall through to + * {@code nextBits(0)} which reports the unrelated "number of bits must be between 1 and 32". + */ + @Test + void testLettersAndDigitsOverEmptyAsciiRange() { + final IllegalArgumentException e = assertThrows(IllegalArgumentException.class, + () -> RandomStringUtils.random(10, 'z' + 1, 0x7f, true, true, null, new Random(42))); + assertTrue(e.getMessage() != null && !e.getMessage().contains("number of bits"), + () -> "Expected a range-validation message but got: " + e.getMessage()); + } + /** * Test {@code RandomStringUtils.random} works appropriately when letters=true * and the range does not only include ASCII letters.
