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    *      http://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.rng.core;
18  
19  import org.junit.jupiter.api.Test;
20  import org.junit.jupiter.params.ParameterizedTest;
21  import org.junit.jupiter.params.provider.ValueSource;
22  
23  import java.util.Arrays;
24  import java.util.SplittableRandom;
25  
26  import org.apache.commons.rng.core.source64.SplitMix64;
27  import org.junit.jupiter.api.Assertions;
28  import org.junit.jupiter.api.Assumptions;
29  
30  /**
31   * Tests for {@link BaseProvider}.
32   *
33   * This class should only contain unit tests that cover code paths not
34   * exercised elsewhere.  Those code paths are most probably only taken
35   * in case of a wrong implementation (and would therefore fail other
36   * tests too).
37   */
38  class BaseProviderTest {
39      @Test
40      void testStateSizeTooSmall() {
41          final DummyGenerator dummy = new DummyGenerator();
42          final int size = dummy.getStateSize();
43          Assumptions.assumeTrue(size > 0);
44          final RandomProviderDefaultState state = new RandomProviderDefaultState(new byte[size - 1]);
45          Assertions.assertThrows(IllegalStateException.class, () -> dummy.restoreState(state));
46      }
47  
48      @Test
49      void testStateSizeTooLarge() {
50          final DummyGenerator dummy = new DummyGenerator();
51          final int size = dummy.getStateSize();
52          final RandomProviderDefaultState state = new RandomProviderDefaultState(new byte[size + 1]);
53          Assertions.assertThrows(IllegalStateException.class, () -> dummy.restoreState(state));
54      }
55  
56      @Test
57      void testFillStateInt() {
58          final int[] state = new int[10];
59          final int[] seed = {1, 2, 3};
60  
61          for (int i = 0; i < state.length; i++) {
62              Assertions.assertEquals(0, state[i]);
63          }
64  
65          new DummyGenerator().fillState(state, seed);
66          for (int i = 0; i < seed.length; i++) {
67              Assertions.assertEquals(seed[i], state[i]);
68          }
69          for (int i = seed.length; i < state.length; i++) {
70              Assertions.assertNotEquals(0, state[i]);
71          }
72      }
73  
74      @Test
75      void testFillStateLong() {
76          final long[] state = new long[10];
77          final long[] seed = {1, 2, 3};
78  
79          for (int i = 0; i < state.length; i++) {
80              Assertions.assertEquals(0, state[i]);
81          }
82  
83          new DummyGenerator().fillState(state, seed);
84          for (int i = 0; i < seed.length; i++) {
85              Assertions.assertEquals(seed[i], state[i]);
86          }
87          for (int i = seed.length; i < state.length; i++) {
88              Assertions.assertNotEquals(0, state[i]);
89          }
90      }
91  
92      /**
93       * Test the checkIndex method. This is not used by any RNG implementations
94       * as it has been superseded by the equivalent of JDK 9 Objects.checkFromIndexSize.
95       */
96      @Test
97      void testCheckIndex() {
98          final BaseProvider rng = new BaseProvider() {
99              @Override
100             public void nextBytes(byte[] bytes) { /* noop */ }
101             @Override
102             public void nextBytes(byte[] bytes, int start, int len) { /* noop */ }
103             @Override
104             public int nextInt() {
105                 return 0;
106             }
107             @Override
108             public long nextLong() {
109                 return 0;
110             }
111             @Override
112             public boolean nextBoolean() {
113                 return false;
114             }
115             @Override
116             public float nextFloat() {
117                 return 0;
118             }
119             @Override
120             public double nextDouble() {
121                 return 0;
122             }
123         };
124         // Arguments are (min, max, index)
125         // index must be in [min, max]
126         rng.checkIndex(-10, 5, 0);
127         rng.checkIndex(-10, 5, -10);
128         rng.checkIndex(-10, 5, 5);
129         rng.checkIndex(Integer.MIN_VALUE, Integer.MAX_VALUE, Integer.MIN_VALUE);
130         rng.checkIndex(Integer.MIN_VALUE, Integer.MAX_VALUE, Integer.MAX_VALUE);
131         Assertions.assertThrows(IndexOutOfBoundsException.class, () -> rng.checkIndex(-10, 5, -11));
132         Assertions.assertThrows(IndexOutOfBoundsException.class, () -> rng.checkIndex(-10, 5, 6));
133         Assertions.assertThrows(IndexOutOfBoundsException.class, () -> rng.checkIndex(-10, 5, Integer.MIN_VALUE));
134         Assertions.assertThrows(IndexOutOfBoundsException.class, () -> rng.checkIndex(-10, 5, Integer.MAX_VALUE));
135     }
136 
137     /**
138      * Test a seed can be extended to a required size by filling with a SplitMix64 generator.
139      */
140     @ParameterizedTest
141     @ValueSource(ints = {0, 1, 2, 4, 5, 6, 7, 8, 9})
142     void testExpandSeedLong(int length) {
143         // The seed does not matter.
144         // Create random seeds that are smaller or larger than length.
145         final SplittableRandom rng = new SplittableRandom();
146         for (final long[] seed : new long[][] {
147             {},
148             rng.longs(1).toArray(),
149             rng.longs(2).toArray(),
150             rng.longs(3).toArray(),
151             rng.longs(4).toArray(),
152             rng.longs(5).toArray(),
153             rng.longs(6).toArray(),
154             rng.longs(7).toArray(),
155             rng.longs(8).toArray(),
156             rng.longs(9).toArray(),
157         }) {
158             Assertions.assertArrayEquals(expandSeed(length, seed),
159                                          BaseProvider.extendSeed(seed, length));
160         }
161     }
162 
163     /**
164      * Expand the seed to the minimum specified length using a {@link SplitMix64} generator
165      * seeded with {@code seed[0]}, or zero if the seed length is zero.
166      *
167      * @param length the length
168      * @param seed the seed
169      * @return the seed
170      */
171     private static long[] expandSeed(int length, long... seed) {
172         if (seed.length < length) {
173             final long[] s = Arrays.copyOf(seed, length);
174             final SplitMix64 rng = new SplitMix64(s[0]);
175             for (int i = seed.length; i < length; i++) {
176                 s[i] = rng.nextLong();
177             }
178             return s;
179         }
180         return seed;
181     }
182 
183     /**
184      * Test a seed can be extended to a required size by filling with a SplitMix64-style
185      * generator using MurmurHash3's 32-bit mix function.
186      *
187      * <p>There is no reference RNG for this output. The test uses fixed output computed
188      * from the reference c++ function in smhasher.
189      */
190     @ParameterizedTest
191     @ValueSource(ints = {0, 1, 2, 4, 5, 6, 7, 8, 9})
192     void testExpandSeedInt(int length) {
193         // Reference output from the c++ function fmix32(uint32_t) in smhasher.
194         // https://github.com/aappleby/smhasher/blob/master/src/MurmurHash3.cpp
195         final int seedA = 0x012de1ba;
196         final int[] valuesA = {
197             0x2f66c8b6, 0x256c0269, 0x054ef409, 0x402425ba, 0x78ebf590, 0x76bea1db,
198             0x8bf5dcbe, 0x104ecdd4, 0x43cfc87e, 0xa33c7643, 0x4d210f56, 0xfa12093d,
199         };
200         // Values from a seed of zero
201         final int[] values0 = {
202             0x92ca2f0e, 0x3cd6e3f3, 0x1b147dcc, 0x4c081dbf, 0x487981ab, 0xdb408c9d,
203             0x78bc1b8f, 0xd83072e5, 0x65cbdd54, 0x1f4b8cef, 0x91783bb0, 0x0231739b,
204         };
205 
206         // Create a random seed that is larger than the maximum length;
207         // start with the initial value
208         final int[] data = new SplittableRandom().ints(10).toArray();
209         data[0] = seedA;
210 
211         for (int i = 0; i <= 9; i++) {
212             final int seedLength = i;
213             // Truncate the random seed
214             final int[] seed = Arrays.copyOf(data, seedLength);
215             // Create the expected output length.
216             // If too short it should be extended with values from the reference output
217             final int[] expected = Arrays.copyOf(seed, Math.max(seedLength, length));
218             if (expected.length == 0) {
219                 // Edge case for zero length
220                 Assertions.assertArrayEquals(new int[0],
221                                              BaseProvider.extendSeed(seed, length));
222                 continue;
223             }
224             // Extend the truncated seed using the reference output.
225             // This may be seeded with zero or the non-zero initial value.
226             final int[] source = expected[0] == 0 ? values0 : valuesA;
227             System.arraycopy(source, 0, expected, seedLength, expected.length - seedLength);
228             Assertions.assertArrayEquals(expected,
229                                          BaseProvider.extendSeed(seed, length),
230                                          () -> String.format("%d -> %d", seedLength, length));
231         }
232     }
233 
234     /**
235      * Dummy class for checking the behaviorof the IntProvider. Tests:
236      * <ul>
237      *  <li>an incomplete implementation</li>
238      *  <li>{@code fillState} methods with "protected" access</li>
239      * </ul>
240      */
241     static class DummyGenerator extends org.apache.commons.rng.core.source32.IntProvider {
242         /** {@inheritDoc} */
243         @Override
244         public int next() {
245             return 4; // https://www.xkcd.com/221/
246         }
247 
248         /**
249          * Gets the state size. This captures the state size of the IntProvider.
250          *
251          * @return the state size
252          */
253         int getStateSize() {
254             return getStateInternal().length;
255         }
256 
257         // Missing overrides of "setStateInternal" and "getStateInternal".
258 
259         /** {@inheritDoc} */
260         @Override
261         public void fillState(int[] state, int[] seed) {
262             super.fillState(state, seed);
263         }
264 
265         /** {@inheritDoc} */
266         @Override
267         public void fillState(long[] state, long[] seed) {
268             super.fillState(state, seed);
269         }
270     }
271 }