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.Assertions;
20  import org.junit.jupiter.api.Assumptions;
21  import org.junit.jupiter.params.ParameterizedTest;
22  import org.junit.jupiter.params.provider.MethodSource;
23  
24  import java.util.Arrays;
25  
26  import org.apache.commons.rng.JumpableUniformRandomProvider;
27  import org.apache.commons.rng.LongJumpableUniformRandomProvider;
28  import org.apache.commons.rng.RandomProviderState;
29  import org.apache.commons.rng.RestorableUniformRandomProvider;
30  import org.apache.commons.rng.UniformRandomProvider;
31  import org.apache.commons.rng.core.source32.IntProvider;
32  import org.apache.commons.rng.core.source64.LongProvider;
33  
34  /**
35   * Tests which all {@link JumpableUniformRandomProvider} generators must pass.
36   */
37  class JumpableProvidersParametricTest {
38      /** The default state for the IntProvider. */
39      private static final byte[] INT_PROVIDER_STATE;
40      /** The default state for the LongProvider. */
41      private static final byte[] LONG_PROVIDER_STATE;
42  
43      static {
44          INT_PROVIDER_STATE = new State32Generator().getState();
45          LONG_PROVIDER_STATE = new State64Generator().getState();
46      }
47  
48      /**
49       * Gets the list of Jumpable generators.
50       *
51       * @return the list
52       */
53      private static Iterable<JumpableUniformRandomProvider> getJumpableProviders() {
54          return ProvidersList.listJumpable();
55      }
56  
57      /**
58       * Gets the function using the {@link LongJumpableUniformRandomProvider#longJump()} method.
59       * If the RNG is not long jumpable then this will raise an exception to skip the test.
60       *
61       * @param generator RNG under test.
62       * @return the jump function
63       */
64      private static TestJumpFunction getLongJumpFunction(JumpableUniformRandomProvider generator) {
65          Assumptions.assumeTrue(generator instanceof LongJumpableUniformRandomProvider, "No long jump function");
66          final LongJumpableUniformRandomProvider rng2 = (LongJumpableUniformRandomProvider) generator;
67          return rng2::jump;
68      }
69  
70      /**
71       * Test that the random generator returned from the jump is a new instance of the same class.
72       */
73      @ParameterizedTest
74      @MethodSource("getJumpableProviders")
75      void testJumpReturnsACopy(JumpableUniformRandomProvider generator) {
76          assertJumpReturnsACopy(generator::jump, generator);
77      }
78  
79      /**
80       * Test that the random generator returned from the long jump is a new instance of the same class.
81       */
82      @ParameterizedTest
83      @MethodSource("getJumpableProviders")
84      void testLongJumpReturnsACopy(JumpableUniformRandomProvider generator) {
85          assertJumpReturnsACopy(getLongJumpFunction(generator), generator);
86      }
87  
88      /**
89       * Assert that the random generator returned from the jump function is a new instance of the same class.
90       *
91       * @param jumpFunction Jump function to test.
92       * @param generator RNG under test.
93       */
94      private static void assertJumpReturnsACopy(TestJumpFunction jumpFunction,
95                                                 JumpableUniformRandomProvider generator) {
96          final UniformRandomProvider copy = jumpFunction.jump();
97          Assertions.assertNotSame(generator, copy, "The copy instance should be a different object");
98          Assertions.assertEquals(generator.getClass(), copy.getClass(), "The copy instance should be the same class");
99      }
100 
101     /**
102      * Test that the random generator state of the copy instance returned from the jump
103      * matches the input state.
104      */
105     @ParameterizedTest
106     @MethodSource("getJumpableProviders")
107     void testJumpCopyMatchesPreJumpState(JumpableUniformRandomProvider generator) {
108         assertCopyMatchesPreJumpState(generator::jump, generator);
109     }
110 
111     /**
112      * Test that the random generator state of the copy instance returned from the long jump
113      * matches the input state.
114      */
115     @ParameterizedTest
116     @MethodSource("getJumpableProviders")
117     void testLongJumpCopyMatchesPreJumpState(JumpableUniformRandomProvider generator) {
118         assertCopyMatchesPreJumpState(getLongJumpFunction(generator), generator);
119     }
120 
121     /**
122      * Assert that the random generator state of the copy instance returned from the jump
123      * function matches the input state.
124      *
125      * <p>The generator must be a {@link RestorableUniformRandomProvider} and return an
126      * instance of {@link RandomProviderDefaultState}.</p>
127      *
128      * <p>The input generator is sampled using methods in the
129      * {@link UniformRandomProvider} interface, the state is saved and a jump is
130      * performed. The states from the pre-jump generator and the returned copy instance
131      * must match.</p>
132      *
133      * <p>This test targets any cached state of the default implementation of a generator
134      * in {@link IntProvider} and {@link LongProvider} such as the state cached for the
135      * nextBoolean() and nextInt() functions.</p>
136      *
137      * @param jumpFunction Jump function to test.
138      * @param generator RNG under test.
139      */
140     private static void assertCopyMatchesPreJumpState(TestJumpFunction jumpFunction,
141                                                       JumpableUniformRandomProvider generator) {
142         Assumptions.assumeTrue(generator instanceof RestorableUniformRandomProvider, "Not a restorable RNG");
143 
144         for (int repeats = 0; repeats < 2; repeats++) {
145             // Exercise the generator.
146             // This calls nextInt() once so the default implementation of LongProvider
147             // should have cached a state for nextInt() in one of the two repeats.
148             // Calls nextBoolean() to ensure a cached state in one of the two repeats.
149             generator.nextInt();
150             generator.nextBoolean();
151 
152             final RandomProviderState preJumpState = ((RestorableUniformRandomProvider) generator).saveState();
153             Assumptions.assumeTrue(preJumpState instanceof RandomProviderDefaultState, "Not a recognised state");
154 
155             final UniformRandomProvider copy = jumpFunction.jump();
156 
157             final RandomProviderState copyState = ((RestorableUniformRandomProvider) copy).saveState();
158             final RandomProviderDefaultState expected = (RandomProviderDefaultState) preJumpState;
159             final RandomProviderDefaultState actual = (RandomProviderDefaultState) copyState;
160             Assertions.assertArrayEquals(expected.getState(), actual.getState(),
161                 "The copy instance state should match the state of the original");
162         }
163     }
164 
165     /**
166      * Test that a jump resets the state of the default implementation of a generator in
167      * {@link IntProvider} and {@link LongProvider}.
168      */
169     @ParameterizedTest
170     @MethodSource("getJumpableProviders")
171     void testJumpResetsDefaultState(JumpableUniformRandomProvider generator) {
172         assertJumpResetsDefaultState(generator::jump, generator);
173     }
174 
175     /**
176      * Test that a long jump resets the state of the default implementation of a generator in
177      * {@link IntProvider} and {@link LongProvider}.
178      */
179     @ParameterizedTest
180     @MethodSource("getJumpableProviders")
181     void testLongJumpResetsDefaultState(JumpableUniformRandomProvider generator) {
182         assertJumpResetsDefaultState(getLongJumpFunction(generator), generator);
183     }
184 
185     /**
186      * Assert the jump resets the specified number of bytes of the state. The bytes are
187      * checked from the end of the saved state.
188      *
189      * <p>This is intended to check the default state of the base implementation of
190      * {@link IntProvider} and {@link LongProvider} is reset.</p>
191      *
192      * @param jumpFunction Jump function to test.
193      * @param generator RNG under test.
194      */
195     private static void assertJumpResetsDefaultState(TestJumpFunction jumpFunction,
196                                                      JumpableUniformRandomProvider generator) {
197         byte[] expected;
198         if (generator instanceof IntProvider) {
199             expected = INT_PROVIDER_STATE;
200         } else if (generator instanceof LongProvider) {
201             expected = LONG_PROVIDER_STATE;
202         } else {
203             throw new AssertionError("Unsupported RNG");
204         }
205         final int stateSize = expected.length;
206         for (int repeats = 0; repeats < 2; repeats++) {
207             // Exercise the generator.
208             // This calls nextInt() once so the default implementation of LongProvider
209             // should have cached a state for nextInt() in one of the two repeats.
210             // Calls nextBoolean() to ensure a cached state in one of the two repeats.
211             generator.nextInt();
212             generator.nextBoolean();
213 
214             jumpFunction.jump();
215 
216             // An Int/LongProvider so must be a RestorableUniformRandomProvider
217             final RandomProviderState postJumpState = ((RestorableUniformRandomProvider) generator).saveState();
218             final byte[] actual = ((RandomProviderDefaultState) postJumpState).getState();
219 
220             Assumptions.assumeTrue(actual.length >= stateSize, "Implementation has removed default state");
221 
222             // The implementation requires that any sub-class state is prepended to the
223             // state thus the default state is at the end.
224             final byte[] defaultState = Arrays.copyOfRange(actual, actual.length - stateSize, actual.length);
225             Assertions.assertArrayEquals(expected, defaultState,
226                 "The jump should reset the default state to zero");
227         }
228     }
229 
230     /**
231      * Dummy class for checking the state size of the IntProvider.
232      */
233     static class State32Generator extends IntProvider {
234         /** {@inheritDoc} */
235         @Override
236         public int next() {
237             return 0;
238         }
239 
240         /**
241          * Gets the default state. This captures the initial state of the IntProvider
242          * including the values of the cache variables.
243          *
244          * @return the state
245          */
246         byte[] getState() {
247             return getStateInternal();
248         }
249     }
250 
251     /**
252      * Dummy class for checking the state size of the LongProvider.
253      */
254     static class State64Generator extends LongProvider {
255         /** {@inheritDoc} */
256         @Override
257         public long next() {
258             return 0;
259         }
260 
261         /**
262          * Gets the default state. This captures the initial state of the LongProvider
263          * including the values of the cache variables.
264          *
265          * @return the state size
266          */
267         byte[] getState() {
268             return getStateInternal();
269         }
270     }
271 
272     /**
273      * Specify the jump operation to test.
274      *
275      * <p>This allows testing {@link JumpableUniformRandomProvider} or
276      * {@link LongJumpableUniformRandomProvider}.</p>
277      */
278     interface TestJumpFunction {
279         /**
280          * Perform the jump and return a pre-jump copy.
281          *
282          * @return the pre-jump copy.
283          */
284         UniformRandomProvider jump();
285     }
286 }