RandomStringGeneratorTest.java

/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.commons.text;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertThrowsExactly;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;

import java.util.function.IntUnaryOperator;

import org.apache.commons.text.RandomStringGenerator.Builder;
import org.junit.jupiter.api.Test;

/**
 * Tests for {@link RandomStringGenerator}
 */
class RandomStringGeneratorTest {

    private static final CharacterPredicate A_FILTER = codePoint -> codePoint == 'a';

    private static final CharacterPredicate B_FILTER = codePoint -> codePoint == 'b';

    private static int codePointLength(final String s) {
        return s.codePointCount(0, s.length());
    }

    @Test
    void testBadMaximumCodePoint() {
        assertThrowsExactly(IllegalArgumentException.class, () -> RandomStringGenerator.builder().withinRange(0, Character.MAX_CODE_POINT + 1));
    }

    @Test
    void testBadMinAndMax() {
        assertThrowsExactly(IllegalArgumentException.class, () -> RandomStringGenerator.builder().withinRange(2, 1));
    }

    @Test
    void testBadMinimumCodePoint() {
        assertThrowsExactly(IllegalArgumentException.class, () -> RandomStringGenerator.builder().withinRange(-1, 1));
    }

    @Test
    void testChangeOfFilter() {
        final RandomStringGenerator.Builder builder = RandomStringGenerator.builder().withinRange('a', 'z').filteredBy(A_FILTER);
        final String str = builder.filteredBy(B_FILTER).build().generate(100);
        for (final char c : str.toCharArray()) {
            assertEquals('b', c);
        }
    }

    @Test
    void testGenerateMinMaxLength() {
        final int minLength = 0;
        final int maxLength = 3;
        final RandomStringGenerator generator = RandomStringGenerator.builder().build();
        final String str = generator.generate(minLength, maxLength);
        final int codePointLength = codePointLength(str);
        assertTrue(codePointLength >= minLength && codePointLength <= maxLength);
    }

    @Test
    void testGenerateMinMaxLengthInvalidLength() {
        assertThrowsExactly(IllegalArgumentException.class, () -> {
            final RandomStringGenerator generator = RandomStringGenerator.builder().build();
            generator.generate(-1, 0);
        });
    }

    @Test
    void testGenerateMinMaxLengthMinGreaterThanMax() {
        assertThrowsExactly(IllegalArgumentException.class, () -> {
            final RandomStringGenerator generator = RandomStringGenerator.builder().build();
            generator.generate(1, 0);
        });
    }

    @Test
    void testGenerateTakingIntThrowsNullPointerException() {
        assertThrowsExactly(NullPointerException.class, () -> {
            final RandomStringGenerator.Builder randomStringGeneratorBuilder = RandomStringGenerator.builder();
            final CharacterPredicate[] characterPredicateArray = new CharacterPredicate[2];
            randomStringGeneratorBuilder.filteredBy(characterPredicateArray);
            final RandomStringGenerator randomStringGenerator = randomStringGeneratorBuilder.build();
            randomStringGenerator.generate(18);
        });
    }

    @Test
    void testInvalidLength() {
        assertThrowsExactly(IllegalArgumentException.class, () -> RandomStringGenerator.builder().build().generate(-1));
    }

    @Test
    void testMultipleFilters() {
        final String str = RandomStringGenerator.builder().withinRange('a', 'd').filteredBy(A_FILTER, B_FILTER).build().generate(5000);

        boolean aFound = false;
        boolean bFound = false;

        for (final char c : str.toCharArray()) {
            if (c == 'a') {
                aFound = true;
            } else if (c == 'b') {
                bFound = true;
            } else {
                fail("Invalid character");
            }
        }

        assertTrue(aFound && bFound);
    }

    @Test
    void testNoLoneSurrogates() {
        final int length = 5000;
        final String str = RandomStringGenerator.builder().build().generate(length);

        char lastChar = str.charAt(0);
        for (int i = 1; i < str.length(); i++) {
            final char c = str.charAt(i);

            if (Character.isLowSurrogate(c)) {
                assertTrue(Character.isHighSurrogate(lastChar));
            }

            if (Character.isHighSurrogate(lastChar)) {
                assertTrue(Character.isLowSurrogate(c));
            }

            if (Character.isHighSurrogate(c)) {
                // test this isn't the last character in the string
                assertTrue(i + 1 < str.length());
            }

            lastChar = c;
        }
    }

    @Test
    void testNoPrivateCharacters() {
        final int startOfPrivateBMPChars = 0xE000;

        // Request a string in an area of the Basic Multilingual Plane that is
        // largely occupied by private characters
        final String str = RandomStringGenerator.builder().withinRange(startOfPrivateBMPChars, Character.MIN_SUPPLEMENTARY_CODE_POINT - 1).build()
                .generate(5000);

        int i = 0;
        do {
            final int codePoint = str.codePointAt(i);
            assertFalse(Character.getType(codePoint) == Character.PRIVATE_USE);
            i += Character.charCount(codePoint);
        } while (i < str.length());
    }

    @Test
    void testRemoveFilters() {
        final RandomStringGenerator.Builder builder = RandomStringGenerator.builder().withinRange('a', 'z').filteredBy(A_FILTER);

        builder.filteredBy();

        final String str = builder.build().generate(100);
        for (final char c : str.toCharArray()) {
            if (c != 'a') {
                // filter was successfully removed
                return;
            }
        }

        fail("Filter appears to have remained in place");
    }

    @Test
    void testSelectFromCharArray() {
        final String str = "abc";
        final char[] charArray = str.toCharArray();
        final RandomStringGenerator generator = RandomStringGenerator.builder().selectFrom(charArray).build();

        final String randomText = generator.generate(5);

        for (final char c : randomText.toCharArray()) {
            assertTrue(str.indexOf(c) != -1);
        }
    }

    @Test
    void testSelectFromCharVarargs() {
        final String str = "abc";
        final RandomStringGenerator generator = RandomStringGenerator.builder().selectFrom('a', 'b', 'c').build();
        final String randomText = generator.generate(5);
        for (final char c : randomText.toCharArray()) {
            assertTrue(str.indexOf(c) != -1);
        }
    }

    @Test
    void testSelectFromCharVarargs2() {
        final String str = "abcde";
        // @formatter:off
        final RandomStringGenerator generator = RandomStringGenerator.builder()
                .selectFrom()
                .selectFrom(null)
                .selectFrom('a', 'b')
                .selectFrom('a', 'b', 'c')
                .selectFrom('a', 'b', 'c', 'd')
                .selectFrom('a', 'b', 'c', 'd', 'e') // only this last call matters
                .build();
        // @formatter:on
        final String randomText = generator.generate(10);
        for (final char c : randomText.toCharArray()) {
            assertTrue(str.indexOf(c) != -1);
        }
    }

    @Test
    void testSelectFromCharVarargSize1() {
        final RandomStringGenerator generator = RandomStringGenerator.builder().selectFrom('a').build();
        final String randomText = generator.generate(5);
        for (final char c : randomText.toCharArray()) {
            assertEquals('a', c);
        }
    }

    @Test
    void testSelectFromEmptyCharVarargs() {
        final RandomStringGenerator generator = RandomStringGenerator.builder().selectFrom().build();
        final String randomText = generator.generate(5);
        for (final char c : randomText.toCharArray()) {
            assertTrue(c >= Character.MIN_CODE_POINT && c <= Character.MAX_CODE_POINT);
        }
    }

    @Test
    void testSelectFromNullCharVarargs() {
        final int length = 5;
        RandomStringGenerator generator = RandomStringGenerator.builder().selectFrom(null).build();
        String randomText = generator.generate(length);
        assertEquals(length, codePointLength(randomText));
        for (final char c : randomText.toCharArray()) {
            assertTrue(c >= Character.MIN_CODE_POINT && c <= Character.MAX_CODE_POINT);
        }
        //
        final Builder builder = RandomStringGenerator.builder().selectFrom('a');
        generator = builder.build();
        randomText = generator.generate(length);
        for (final char c : randomText.toCharArray()) {
            assertEquals('a', c);
        }
        // null input resets
        generator = builder.selectFrom(null).build();
        randomText = generator.generate(length);
        assertEquals(length, codePointLength(randomText));
        for (final char c : randomText.toCharArray()) {
            assertTrue(c >= Character.MIN_CODE_POINT && c <= Character.MAX_CODE_POINT);
        }
    }

    @Test
    void testSetLength() {
        final int length = 99;
        final RandomStringGenerator generator = RandomStringGenerator.builder().build();
        final String str = generator.generate(length);
        assertEquals(length, codePointLength(str));
    }

    @Test
    void testUsingRandomTextRandomProvider() {
        final char testChar = 'a';
        final TextRandomProvider testRandom = n -> testChar;
        final String str = RandomStringGenerator.builder().usingRandom(testRandom).build().generate(10);
        for (final char c : str.toCharArray()) {
            assertEquals(testChar, c);
        }
    }

    @Test
    void testUsingRandomIntUnaryOperator() {
        final char testChar = 'a';
        final IntUnaryOperator testRandom = n -> testChar;
        final String str = RandomStringGenerator.builder().usingRandom(testRandom).build().generate(10);
        for (final char c : str.toCharArray()) {
            assertEquals(testChar, c);
        }
    }

    @Test
    void testWithinMultipleRanges() {
        final int length = 5000;
        final char[][] pairs = { { 'a', 'z' }, { '0', '9' } };
        // @formatter:off
        final RandomStringGenerator generator = RandomStringGenerator.builder()
                .withinRange()
                .withinRange((char[][]) null)
                .withinRange(pairs)
                .build();
        // @formatter:on
        final String str = generator.generate(length);

        int minimumCodePoint = 0, maximumCodePoint = 0;

        for (final char[] pair : pairs) {
            minimumCodePoint = Math.min(minimumCodePoint, pair[0]);
            maximumCodePoint = Math.max(maximumCodePoint, pair[1]);
        }

        int i = 0;
        do {
            final int codePoint = str.codePointAt(i);
            assertTrue(codePoint >= minimumCodePoint && codePoint <= maximumCodePoint);
            i += Character.charCount(codePoint);
        } while (i < str.length());
    }

    @Test
    void testWithinRange() {
        final int length = 5000;
        final int minimumCodePoint = 'a';
        final int maximumCodePoint = 'z';
        final RandomStringGenerator generator = RandomStringGenerator.builder().withinRange(minimumCodePoint, maximumCodePoint).build();
        final String str = generator.generate(length);

        int i = 0;
        do {
            final int codePoint = str.codePointAt(i);
            assertTrue(codePoint >= minimumCodePoint && codePoint <= maximumCodePoint);
            i += Character.charCount(codePoint);
        } while (i < str.length());
    }

    @Test
    void testZeroLength() {
        final RandomStringGenerator generator = RandomStringGenerator.builder().build();
        assertEquals("", generator.generate(0));
    }
}