This is an automated email from the ASF dual-hosted git repository. aherbert pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/commons-rng.git
commit d04c23fb7c506b63ec95610268e2c84af10a93e2 Author: Alex Herbert <[email protected]> AuthorDate: Mon Sep 23 20:55:08 2019 +0100 RNG-117: RandomSource to support creating byte[] seed for implementing class. Added methods: public byte[] createSeed(); public byte[] createSeed(UniformRandomProvider); --- .../apache/commons/rng/simple/RandomSource.java | 47 +++++++++++ .../rng/simple/internal/NativeSeedType.java | 59 ++++++++++++-- .../rng/simple/internal/ProviderBuilder.java | 88 ++++++++++++++++++-- .../commons/rng/simple/internal/SeedFactory.java | 55 +++++++++++++ .../rng/simple/ProvidersCommonParametricTest.java | 15 ++++ .../internal/NativeSeedTypeParametricTest.java | 22 +++++ .../rng/simple/internal/NativeSeedTypeTest.java | 44 ++++++++++ .../RandomSourceInternalParametricTest.java | 95 ++++++++++++++++++++++ .../rng/simple/internal/SeedFactoryTest.java | 59 ++++++++++++++ 9 files changed, 472 insertions(+), 12 deletions(-) diff --git a/commons-rng-simple/src/main/java/org/apache/commons/rng/simple/RandomSource.java b/commons-rng-simple/src/main/java/org/apache/commons/rng/simple/RandomSource.java index 62b83b3..e8048a4 100644 --- a/commons-rng-simple/src/main/java/org/apache/commons/rng/simple/RandomSource.java +++ b/commons-rng-simple/src/main/java/org/apache/commons/rng/simple/RandomSource.java @@ -490,6 +490,53 @@ public enum RandomSource { } /** + * Creates a seed suitable for the implementing class represented by this random source. + * + * <p>The seed will be created as if passing a {@code null} seed to the factory method + * {@link #create(RandomSource, Object, Object...)}. It will satisfy the seed size and any + * other seed requirements for the implementing class. The seed is converted from the native + * type to a byte representation.</p> + * + * <p>Usage example:</p> + * <pre><code> + * RandomSource source = ...; + * byte[] seed = source.createSeed(); + * UniformRandomProvider rng = RandomSource.create(source, seed); + * </code></pre> + * + * @return the seed + * + * @since 1.3 + */ + public byte[] createSeed() { + return internalIdentifier.createSeedBytes(); + } + + /** + * Creates a seed suitable for the implementing class represented by this random source + * using the supplied source of randomness. + * + * <p>The seed will satisfy the seed size and any other seed requirements for the + * implementing class.</p> + * + * <p>Usage example:</p> + * <pre><code> + * RandomSource source = ...; + * UniformRandomProvider seedRng = new JDKRandomWrapper(new SecureRandom()); + * byte[] seed = source.createSeed(seedRng); + * UniformRandomProvider rng = RandomSource.create(source, seed); + * </code></pre> + * + * @param rng Source of randomness. + * @return the seed + * + * @since 1.3 + */ + public byte[] createSeed(UniformRandomProvider rng) { + return internalIdentifier.createSeedBytes(rng); + } + + /** * Checks whether the implementing class represented by this random source * supports the {@link org.apache.commons.rng.JumpableUniformRandomProvider * JumpableUniformRandomProvider} interface. If {@code true} the instance returned diff --git a/commons-rng-simple/src/main/java/org/apache/commons/rng/simple/internal/NativeSeedType.java b/commons-rng-simple/src/main/java/org/apache/commons/rng/simple/internal/NativeSeedType.java index 1db19ee..0fe9da5 100644 --- a/commons-rng-simple/src/main/java/org/apache/commons/rng/simple/internal/NativeSeedType.java +++ b/commons-rng-simple/src/main/java/org/apache/commons/rng/simple/internal/NativeSeedType.java @@ -16,6 +16,8 @@ */ package org.apache.commons.rng.simple.internal; +import org.apache.commons.rng.core.util.NumberFactory; + /** * The native seed type. Contains values for all native seed types and methods * to convert supported seed types to the native seed type. @@ -41,7 +43,7 @@ package org.apache.commons.rng.simple.internal; */ public enum NativeSeedType { /** The seed type is {@code Integer}. */ - INT(Integer.class) { + INT(Integer.class, 4) { @Override public Integer createSeed(int size) { return SeedFactory.createInt(); @@ -68,7 +70,7 @@ public enum NativeSeedType { } }, /** The seed type is {@code Long}. */ - LONG(Long.class) { + LONG(Long.class, 8) { @Override public Long createSeed(int size) { return SeedFactory.createLong(); @@ -95,7 +97,7 @@ public enum NativeSeedType { } }, /** The seed type is {@code int[]}. */ - INT_ARRAY(int[].class) { + INT_ARRAY(int[].class, 4) { @Override public int[] createSeed(int size) { // Limit the number of calls to the synchronized method. The generator @@ -124,7 +126,7 @@ public enum NativeSeedType { } }, /** The seed type is {@code long[]}. */ - LONG_ARRAY(long[].class) { + LONG_ARRAY(long[].class, 8) { @Override public long[] createSeed(int size) { // Limit the number of calls to the synchronized method. The generator @@ -153,6 +155,8 @@ public enum NativeSeedType { } }; + /** Error message for unrecognised seed types. */ + private static final String UNRECOGNISED_SEED = "Unrecognized seed type: "; /** Maximum length of the seed array (for creating array seeds). */ private static final int RANDOM_SEED_ARRAY_SIZE = 128; /** Convert {@code Long} to {@code Integer}. */ @@ -180,10 +184,20 @@ public enum NativeSeedType { private final Class<?> type; /** + * Define the number of bytes required to represent the native seed. If the type is + * an array then this represents the size of a single value of the type. + */ + private final int bytes; + + /** + * Instantiates a new native seed type. + * * @param type Define the class type of the native seed. + * @param bytes Define the number of bytes required to represent the native seed. */ - NativeSeedType(Class<?> type) { + NativeSeedType(Class<?> type, int bytes) { this.type = type; + this.bytes = bytes; } /** @@ -196,6 +210,16 @@ public enum NativeSeedType { } /** + * Gets the number of bytes required to represent the native seed type. If the type is + * an array then this represents the size of a single value of the type. + * + * @return the number of bytes + */ + public int getBytes() { + return bytes; + } + + /** * Creates the seed. The output seed type is determined by the native seed type. If the * output is an array the required size of the array can be specified. * @@ -230,7 +254,7 @@ public enum NativeSeedType { return convert((byte[]) seed, size); } - throw new UnsupportedOperationException("Unrecognized seed type: " + seed); + throw new UnsupportedOperationException(UNRECOGNISED_SEED + seed); } /** @@ -277,4 +301,27 @@ public enum NativeSeedType { * @return the native seed. */ protected abstract Object convert(byte[] seed, int size); + + /** + * Converts the input seed from any of the supported seed types to bytes. + * + * @param seed Input seed. + * @return the seed bytes. + * @throws UnsupportedOperationException if the {@code seed} type is invalid. + */ + public static byte[] convertSeedToBytes(Object seed) { + if (seed instanceof Integer) { + return NumberFactory.makeByteArray((Integer) seed); + } else if (seed instanceof Long) { + return NumberFactory.makeByteArray((Long) seed); + } else if (seed instanceof int[]) { + return NumberFactory.makeByteArray((int[]) seed); + } else if (seed instanceof long[]) { + return NumberFactory.makeByteArray((long[]) seed); + } else if (seed instanceof byte[]) { + return (byte[]) seed; + } + + throw new UnsupportedOperationException(UNRECOGNISED_SEED + seed); + } } diff --git a/commons-rng-simple/src/main/java/org/apache/commons/rng/simple/internal/ProviderBuilder.java b/commons-rng-simple/src/main/java/org/apache/commons/rng/simple/internal/ProviderBuilder.java index 59b9271..02af9fc 100644 --- a/commons-rng-simple/src/main/java/org/apache/commons/rng/simple/internal/ProviderBuilder.java +++ b/commons-rng-simple/src/main/java/org/apache/commons/rng/simple/internal/ProviderBuilder.java @@ -272,20 +272,35 @@ public final class ProviderBuilder { return super.convertSeed(seed); } + @Override + protected byte[] createByteArraySeed(UniformRandomProvider source) { + return NativeSeedType.convertSeedToBytes(createMswsSeed(source)); + } + + /** + * Creates the full length seed array from the input seed. + * + * @param seed the seed + * @return the seed array + */ + private long[] createMswsSeed(long seed) { + return createMswsSeed(new SplitMix64(seed)); + } + /** * Creates the full length seed array from the input seed using the method * recommended for the generator. This is a high quality Weyl increment composed * of a hex character permutation. * - * @param seed the seed + * @param source Source of randomness. * @return the seed array */ - private long[] createMswsSeed(long seed) { - final long increment = SeedUtils.createLongHexPermutation(new SplitMix64(seed)); + private long[] createMswsSeed(UniformRandomProvider source) { + final long increment = SeedUtils.createLongHexPermutation(source); // The initial state should not be low complexity but the Weyl // state can be any number. final long state = increment; - final long weylState = seed; + final long weylState = source.nextLong(); return new long[] {state, weylState, increment}; } }, @@ -383,6 +398,16 @@ public final class ProviderBuilder { } /** + * Gets the number of seed bytes required to seed the implementing class represented by + * this random source. + * + * @return the number of seed bytes + */ + private int getSeedByteSize() { + return nativeSeedSize * nativeSeedType.getBytes(); + } + + /** * Creates a RNG instance. * * <p>This method can be over-ridden to allow fast construction of a generator @@ -442,8 +467,10 @@ public final class ProviderBuilder { /** * Creates a native seed. * - * <p>This method should be over-ridden to satisfy seed requirements for the generator, - * for example if a seed must contain non-zero bits.</p> + * <p>The default implementation creates a seed of the native type and, for array seeds, + * ensures not all bits are zero.</p> + * + * <p>This method should be over-ridden to satisfy seed requirements for the generator.</p> * * @return the native seed */ @@ -452,6 +479,21 @@ public final class ProviderBuilder { } /** + * Creates a {@code byte[]} seed using the provided source of randomness. + * + * <p>The default implementation creates a full-length seed and ensures not all bits + * are zero.</p> + * + * <p>This method should be over-ridden to satisfy seed requirements for the generator.</p> + * + * @param source Source of randomness. + * @return the byte[] seed + */ + protected byte[] createByteArraySeed(UniformRandomProvider source) { + return SeedFactory.createByteArray(source, getSeedByteSize()); + } + + /** * Converts a seed from any of the supported seed types to a native seed. * * @param seed Input seed (must not be null). @@ -476,6 +518,39 @@ public final class ProviderBuilder { } /** + * Creates a seed suitable for the implementing class represented by this random source. + * + * <p>It will satisfy the seed size and any other seed requirements for the + * implementing class. The seed is converted from the native type to bytes.</p> + * + * @return the seed bytes + * + * @since 1.3 + */ + public final byte[] createSeedBytes() { + // Custom implementations can override createSeed + final Object seed = createSeed(); + return NativeSeedType.convertSeedToBytes(seed); + } + + /** + * Creates a seed suitable for the implementing class represented by this random source + * using the supplied source of randomness. + * + * <p>It will satisfy the seed size and any other seed requirements for the + * implementing class. The seed is converted from the native type to bytes.</p> + * + * @param source Source of randomness. + * @return the seed bytes + * + * @since 1.3 + */ + public final byte[] createSeedBytes(UniformRandomProvider source) { + // Custom implementations can override createByteArraySeed + return createByteArraySeed(source); + } + + /** * Gets the constructor. * * @return the RNG constructor. @@ -492,6 +567,7 @@ public final class ProviderBuilder { } return constructor; } + /** * Creates a constructor. * diff --git a/commons-rng-simple/src/main/java/org/apache/commons/rng/simple/internal/SeedFactory.java b/commons-rng-simple/src/main/java/org/apache/commons/rng/simple/internal/SeedFactory.java index 170c978..4b336ea 100644 --- a/commons-rng-simple/src/main/java/org/apache/commons/rng/simple/internal/SeedFactory.java +++ b/commons-rng-simple/src/main/java/org/apache/commons/rng/simple/internal/SeedFactory.java @@ -203,6 +203,25 @@ public final class SeedFactory { } /** + * Creates an array of {@code byte} numbers for use as a seed using the supplied source of + * randomness. The result will not be all zeros. + * + * @param source Source of randomness. + * @param n Size of the array to create. + * @return an array of {@code n} random numbers. + */ + static byte[] createByteArray(UniformRandomProvider source, + int n) { + final byte[] seed = new byte[n]; + source.nextBytes(seed); + // If the seed is zero it is assumed the input source RNG is either broken + // or the seed is small and it was zero by chance. Revert to the built-in + // source of randomness to ensure it is non-zero. + ensureNonZero(seed); + return seed; + } + + /** * Ensure the seed is non-zero at the first position in the array. * * <p>This method will replace a zero at index 0 in the array with @@ -243,6 +262,42 @@ public final class SeedFactory { } /** + * Ensure the seed is not zero at all positions in the array. + * + * <p>This method will check all positions in the array and if all + * are zero it will replace index 0 in the array with + * a non-zero random number. The method ensures any length seed + * contains non-zero bits. The output seed is suitable for generators + * that cannot be seeded with all zeros.</p> + * + * @param seed Seed array (modified in place). + * @see #createInt() + */ + private static void ensureNonZero(byte[] seed) { + // Since zero occurs 1 in 2^8 for a single byte this checks the entire array for zeros. + if (seed.length != 0 && isAllZero(seed)) { + do { + seed[0] = (byte) createInt(); + } while (seed[0] == 0); + } + } + + /** + * Test if each position in the array is zero. + * + * @param array Array data. + * @return true if all position are zero + */ + private static boolean isAllZero(byte[] array) { + for (final byte value : array) { + if (value != 0) { + return false; + } + } + return true; + } + + /** * Ensure the value is non-zero. * * <p>This method will replace a zero with a non-zero random number from the random source.</p> diff --git a/commons-rng-simple/src/test/java/org/apache/commons/rng/simple/ProvidersCommonParametricTest.java b/commons-rng-simple/src/test/java/org/apache/commons/rng/simple/ProvidersCommonParametricTest.java index 4641937..aa403da 100644 --- a/commons-rng-simple/src/test/java/org/apache/commons/rng/simple/ProvidersCommonParametricTest.java +++ b/commons-rng-simple/src/test/java/org/apache/commons/rng/simple/ProvidersCommonParametricTest.java @@ -39,6 +39,7 @@ import org.apache.commons.rng.LongJumpableUniformRandomProvider; import org.apache.commons.rng.RandomProviderState; import org.apache.commons.rng.RestorableUniformRandomProvider; import org.apache.commons.rng.core.RandomProviderDefaultState; +import org.apache.commons.rng.core.source64.SplitMix64; /** * Tests which all generators must pass. @@ -155,6 +156,20 @@ public class ProvidersCommonParametricTest { checkNextIntegerInRange(rng, 10, 10000); } + @Test + public void testRandomSourceCreateSeed() { + final byte[] seed = originalSource.createSeed(); + final UniformRandomProvider rng = RandomSource.create(originalSource, seed, originalArgs); + checkNextIntegerInRange(rng, 10, 10000); + } + + @Test + public void testRandomSourceCreateSeedFromRNG() { + final byte[] seed = originalSource.createSeed(new SplitMix64(RandomSource.createLong())); + final UniformRandomProvider rng = RandomSource.create(originalSource, seed, originalArgs); + checkNextIntegerInRange(rng, 10, 10000); + } + // State save and restore tests. @Test diff --git a/commons-rng-simple/src/test/java/org/apache/commons/rng/simple/internal/NativeSeedTypeParametricTest.java b/commons-rng-simple/src/test/java/org/apache/commons/rng/simple/internal/NativeSeedTypeParametricTest.java index 9a2ebf3..3913341 100644 --- a/commons-rng-simple/src/test/java/org/apache/commons/rng/simple/internal/NativeSeedTypeParametricTest.java +++ b/commons-rng-simple/src/test/java/org/apache/commons/rng/simple/internal/NativeSeedTypeParametricTest.java @@ -112,6 +112,28 @@ public class NativeSeedTypeParametricTest { } /** + * Test the seed can be created, converted to a byte[] and then back to the native type. + */ + @Test + public void testConvertSeedToBytes() { + final int size = 3; + final Object seed = nativeSeedType.createSeed(size); + Assert.assertNotNull("Null seed", seed); + + final byte[] bytes = NativeSeedType.convertSeedToBytes(seed); + Assert.assertNotNull("Null byte[] seed", bytes); + + final Object seed2 = nativeSeedType.convertSeed(bytes, size); + if (type.isArray()) { + // This handles nested primitive arrays + Assert.assertArrayEquals("byte[] seed was not converted back", + new Object[] {seed}, new Object[] {seed2}); + } else { + Assert.assertEquals("byte[] seed was not converted back", seed, seed2); + } + } + + /** * Test the seed can be converted to the correct type from any of the supported input types. */ @Test diff --git a/commons-rng-simple/src/test/java/org/apache/commons/rng/simple/internal/NativeSeedTypeTest.java b/commons-rng-simple/src/test/java/org/apache/commons/rng/simple/internal/NativeSeedTypeTest.java new file mode 100644 index 0000000..be699ae --- /dev/null +++ b/commons-rng-simple/src/test/java/org/apache/commons/rng/simple/internal/NativeSeedTypeTest.java @@ -0,0 +1,44 @@ +/* + * 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 + * + * http://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.rng.simple.internal; + +import org.junit.Assert; +import org.junit.Test; + +/** + * Tests for the {@link NativeSeedType} factory seed conversions. + */ +public class NativeSeedTypeTest { + /** + * Test the conversion throws for an unsupported type. All supported types are + * tested in the {@link NativeSeedTypeParametricTest}. + */ + @Test(expected = UnsupportedOperationException.class) + public void testConvertSeedToBytesUsingNullThrows() { + NativeSeedType.convertSeedToBytes(null); + } + + /** + * Test the conversion passes through a byte[]. This hits the edge case of a seed + * that can be converted that is not a native type. + */ + @Test + public void testConvertSeedToBytesUsingByteArray() { + final byte[] seed = {42, 78, 99}; + Assert.assertSame(seed, NativeSeedType.convertSeedToBytes(seed)); + } +} diff --git a/commons-rng-simple/src/test/java/org/apache/commons/rng/simple/internal/RandomSourceInternalParametricTest.java b/commons-rng-simple/src/test/java/org/apache/commons/rng/simple/internal/RandomSourceInternalParametricTest.java index 69736f2..e58f4ff 100644 --- a/commons-rng-simple/src/test/java/org/apache/commons/rng/simple/internal/RandomSourceInternalParametricTest.java +++ b/commons-rng-simple/src/test/java/org/apache/commons/rng/simple/internal/RandomSourceInternalParametricTest.java @@ -16,6 +16,7 @@ */ package org.apache.commons.rng.simple.internal; +import org.apache.commons.rng.core.source64.SplitMix64; import org.apache.commons.rng.simple.internal.ProviderBuilder.RandomSourceInternal; import org.junit.Assert; import org.junit.Test; @@ -23,6 +24,8 @@ import org.junit.runner.RunWith; import org.junit.runners.Parameterized; import org.junit.runners.Parameterized.Parameters; +import java.util.EnumMap; + /** * Tests for the {@link ProviderBuilder.RandomSourceInternal} seed conversions. This test * ensures that all random sources can create a seed or convert any supported seed to the @@ -43,6 +46,53 @@ public class RandomSourceInternalParametricTest { null, Double.valueOf(Math.PI), }; + /** The expected byte size of the seed for each RandomSource. */ + private static final EnumMap<RandomSourceInternal, Integer> EXPECTED_SEED_BYTES = + new EnumMap<RandomSourceInternal, Integer>(RandomSourceInternal.class); + + static { + final int intBytes = 4; + final int longBytes = 8; + EXPECTED_SEED_BYTES.put(RandomSourceInternal.JDK, longBytes * 1); + EXPECTED_SEED_BYTES.put(RandomSourceInternal.WELL_512_A, intBytes * 16); + EXPECTED_SEED_BYTES.put(RandomSourceInternal.WELL_1024_A, intBytes * 32); + EXPECTED_SEED_BYTES.put(RandomSourceInternal.WELL_19937_A, intBytes * 624); + EXPECTED_SEED_BYTES.put(RandomSourceInternal.WELL_19937_C, intBytes * 624); + EXPECTED_SEED_BYTES.put(RandomSourceInternal.WELL_44497_A, intBytes * 1391); + EXPECTED_SEED_BYTES.put(RandomSourceInternal.WELL_44497_B, intBytes * 1391); + EXPECTED_SEED_BYTES.put(RandomSourceInternal.MT, intBytes * 624); + EXPECTED_SEED_BYTES.put(RandomSourceInternal.ISAAC, intBytes * 256); + EXPECTED_SEED_BYTES.put(RandomSourceInternal.SPLIT_MIX_64, longBytes * 1); + EXPECTED_SEED_BYTES.put(RandomSourceInternal.XOR_SHIFT_1024_S, longBytes * 16); + EXPECTED_SEED_BYTES.put(RandomSourceInternal.TWO_CMRES, intBytes * 1); + EXPECTED_SEED_BYTES.put(RandomSourceInternal.TWO_CMRES_SELECT, intBytes * 1); + EXPECTED_SEED_BYTES.put(RandomSourceInternal.MT_64, longBytes * 312); + EXPECTED_SEED_BYTES.put(RandomSourceInternal.MWC_256, intBytes * 257); + EXPECTED_SEED_BYTES.put(RandomSourceInternal.KISS, intBytes * 4); + EXPECTED_SEED_BYTES.put(RandomSourceInternal.XOR_SHIFT_1024_S_PHI, longBytes * 16); + EXPECTED_SEED_BYTES.put(RandomSourceInternal.XO_RO_SHI_RO_64_S, intBytes * 2); + EXPECTED_SEED_BYTES.put(RandomSourceInternal.XO_RO_SHI_RO_64_SS, intBytes * 2); + EXPECTED_SEED_BYTES.put(RandomSourceInternal.XO_SHI_RO_128_PLUS, intBytes * 4); + EXPECTED_SEED_BYTES.put(RandomSourceInternal.XO_SHI_RO_128_SS, intBytes * 4); + EXPECTED_SEED_BYTES.put(RandomSourceInternal.XO_RO_SHI_RO_128_PLUS, longBytes * 2); + EXPECTED_SEED_BYTES.put(RandomSourceInternal.XO_RO_SHI_RO_128_SS, longBytes * 2); + EXPECTED_SEED_BYTES.put(RandomSourceInternal.XO_SHI_RO_256_PLUS, longBytes * 4); + EXPECTED_SEED_BYTES.put(RandomSourceInternal.XO_SHI_RO_256_SS, longBytes * 4); + EXPECTED_SEED_BYTES.put(RandomSourceInternal.XO_SHI_RO_512_PLUS, longBytes * 8); + EXPECTED_SEED_BYTES.put(RandomSourceInternal.XO_SHI_RO_512_SS, longBytes * 8); + EXPECTED_SEED_BYTES.put(RandomSourceInternal.PCG_XSH_RR_32, longBytes * 2); + EXPECTED_SEED_BYTES.put(RandomSourceInternal.PCG_XSH_RS_32, longBytes * 2); + EXPECTED_SEED_BYTES.put(RandomSourceInternal.PCG_RXS_M_XS_64, longBytes * 2); + EXPECTED_SEED_BYTES.put(RandomSourceInternal.PCG_MCG_XSH_RR_32, longBytes * 1); + EXPECTED_SEED_BYTES.put(RandomSourceInternal.PCG_MCG_XSH_RS_32, longBytes * 1); + EXPECTED_SEED_BYTES.put(RandomSourceInternal.MSWS, longBytes * 3); + EXPECTED_SEED_BYTES.put(RandomSourceInternal.SFC_32, intBytes * 3); + EXPECTED_SEED_BYTES.put(RandomSourceInternal.SFC_64, longBytes * 3); + EXPECTED_SEED_BYTES.put(RandomSourceInternal.JSF_32, intBytes * 1); + EXPECTED_SEED_BYTES.put(RandomSourceInternal.JSF_64, longBytes * 1); + // ... add more here. + // Verify the seed byte size is reflected in the enum javadoc for RandomSource. + } /** Internal identifier for the random source. */ private final RandomSourceInternal randomSourceInternal; @@ -109,4 +159,49 @@ public class RandomSourceInternalParametricTest { } } } + + /** + * Test the seed byte size is reported as the size of a int/long primitive for Int/Long + * seed types and a multiple of it for int[]/long[] types. + */ + @Test + public void testCreateSeedBytesSizeIsPositiveAndMultipleOf4Or8() { + // This should be the full length seed + final byte[] seed = randomSourceInternal.createSeedBytes(new SplitMix64(12345L)); + + final int size = seed.length; + Assert.assertNotEquals("Seed is empty", 0, size); + + if (randomSourceInternal.isNativeSeed(Integer.valueOf(0))) { + Assert.assertEquals("Expect 4 bytes for Integer", 4, size); + } else if (randomSourceInternal.isNativeSeed(Long.valueOf(0))) { + Assert.assertEquals("Expect 8 bytes for Long", 8, size); + } else if (randomSourceInternal.isNativeSeed(new int[0])) { + Assert.assertEquals("Expect 4n bytes for int[]", 0, size % 4); + } else if (randomSourceInternal.isNativeSeed(new long[0])) { + Assert.assertEquals("Expect 8n bytes for long[]", 0, size % 8); + } else { + Assert.fail("Unknown native seed type"); + } + } + + /** + * Test the seed byte size against the expected value. + * + * <p>The expected values are maintained in a table and must be manually updated + * for new generators. This test forms an additional cross-reference check that the + * seed size in RandomSourceInternal has been correctly set and the size should map to + * the array size in the RandomSource javadoc (if applicable). + */ + @Test + public void testCreateSeedBytes() { + // This should be the full length seed + final byte[] seed = randomSourceInternal.createSeedBytes(new SplitMix64(12345L)); + final int size = seed.length; + + final Integer expected = EXPECTED_SEED_BYTES.get(randomSourceInternal); + Assert.assertNotNull("Missing expected seed byte size: " + randomSourceInternal, expected); + Assert.assertEquals(randomSourceInternal.toString(), + expected.intValue(), size); + } } diff --git a/commons-rng-simple/src/test/java/org/apache/commons/rng/simple/internal/SeedFactoryTest.java b/commons-rng-simple/src/test/java/org/apache/commons/rng/simple/internal/SeedFactoryTest.java index 842bdaa..b936d81 100644 --- a/commons-rng-simple/src/test/java/org/apache/commons/rng/simple/internal/SeedFactoryTest.java +++ b/commons-rng-simple/src/test/java/org/apache/commons/rng/simple/internal/SeedFactoryTest.java @@ -20,6 +20,8 @@ import java.util.Map; import java.util.HashMap; import org.junit.Assert; import org.junit.Test; +import org.apache.commons.rng.UniformRandomProvider; +import org.apache.commons.rng.core.source32.IntProvider; import org.apache.commons.rng.core.source64.RandomLongSource; import org.apache.commons.rng.core.util.NumberFactory; @@ -206,6 +208,63 @@ public class SeedFactoryTest { } @Test + public void testCreateByteArrayWithSizeZero() { + assertCreateByteArray(new byte[0]); + } + + @Test + public void testCreateByteArrayIgnoresNonZeroPositions() { + final byte position = 123; + int n = 3; + for (int i = 0; i < n; i++) { + final byte[] expected = new byte[n]; + expected[i] = position; + assertCreateByteArray(expected); + } + } + + /** + * Assert that the SeedFactory uses the bytes exactly as generated by the + * {@link UniformRandomProvider#nextBytes(byte[])} method (assuming they are not all zero). + * + * @param expected the expected + */ + private static void assertCreateByteArray(final byte[] expected) { + final UniformRandomProvider rng = new IntProvider() { + @Override + public int next() { + Assert.fail("This method should not be used"); + return 0; + } + + @Override + public void nextBytes(byte[] bytes) { + System.arraycopy(expected, 0, bytes, 0, Math.min(expected.length, bytes.length)); + } + }; + + final byte[] seed = SeedFactory.createByteArray(rng, expected.length); + Assert.assertArrayEquals(expected, seed); + } + + @Test + public void testCreateByteArrayWithAllZeroBytesUpdatesPosition0() { + final UniformRandomProvider rng = new IntProvider() { + @Override + public int next() { + // Deliberately produce zero + return 0; + } + }; + // Test the method only replaces position 0 + final byte[] seed = SeedFactory.createByteArray(rng, 4); + Assert.assertNotEquals("Zero at position 0 should be modified", 0, seed[0]); + for (int i = 1; i < seed.length; i++) { + Assert.assertEquals("Position above 0 should be unmodified", 0, seed[i]); + } + } + + @Test public void testEnsureNonZeroIntArrayIgnoresEmptySeed() { final int[] seed = new int[0]; SeedFactory.ensureNonZero(seed);
