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