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  
18  package org.apache.commons.rng.core;
19  
20  import org.junit.Assert;
21  
22  import java.lang.reflect.Constructor;
23  import java.lang.reflect.InvocationTargetException;
24  
25  import org.apache.commons.rng.JumpableUniformRandomProvider;
26  import org.apache.commons.rng.LongJumpableUniformRandomProvider;
27  import org.apache.commons.rng.UniformRandomProvider;
28  
29  /**
30   * Utility class for testing random generators.
31   */
32  public final class RandomAssert {
33      /**
34       * Class contains only static methods.
35       */
36      private RandomAssert() {}
37  
38      /**
39       * Assert that the random generator produces the expected output using
40       * {@link UniformRandomProvider#nextInt()}.
41       *
42       * @param expected Expected output.
43       * @param rng Random generator.
44       */
45      public static void assertEquals(int[] expected, UniformRandomProvider rng) {
46          assertEquals("Value at position ", expected, rng);
47      }
48  
49      /**
50       * Assert that the random generator produces the expected output using
51       * {@link UniformRandomProvider#nextLong()}.
52       *
53       * @param expected Expected output.
54       * @param rng Random generator.
55       */
56      public static void assertEquals(long[] expected, UniformRandomProvider rng) {
57          assertEquals("Value at position ", expected, rng);
58      }
59  
60      /**
61       * Assert that the random generator produces the expected output using
62       * {@link UniformRandomProvider#nextInt()}.
63       * The message prefix is prepended to the array index for the assertion message.
64       *
65       * @param messagePrefix Message prefix.
66       * @param expected Expected output.
67       * @param rng Random generator.
68       */
69      private static void assertEquals(String messagePrefix, int[] expected, UniformRandomProvider rng) {
70          for (int i = 0; i < expected.length; i++) {
71              Assert.assertEquals(messagePrefix + i, expected[i], rng.nextInt());
72          }
73      }
74  
75      /**
76       * Assert that the random generator produces the expected output using
77       * {@link UniformRandomProvider#nextLong()}.
78       * The message prefix is prepended to the array index for the assertion message.
79       *
80       * @param messagePrefix Message prefix.
81       * @param expected Expected output.
82       * @param rng Random generator.
83       */
84      private static void assertEquals(String messagePrefix, long[] expected, UniformRandomProvider rng) {
85          for (int i = 0; i < expected.length; i++) {
86              Assert.assertEquals(messagePrefix + i, expected[i], rng.nextLong());
87          }
88      }
89  
90      /**
91       * Assert that the random generator produces a <strong>different</strong> output using
92       * {@link UniformRandomProvider#nextLong()} to the expected output.
93       *
94       * @param expected Expected output.
95       * @param rng Random generator.
96       */
97      public static void assertNotEquals(long[] expected, UniformRandomProvider rng) {
98          for (int i = 0; i < expected.length; i++) {
99              if (expected[i] != rng.nextLong()) {
100                 return;
101             }
102         }
103         Assert.fail("Expected a different nextLong output");
104     }
105 
106     /**
107      * Assert that the random generator satisfies the contract of the
108      * {@link JumpableUniformRandomProvider#jump()} function.
109      *
110      * <ul>
111      *  <li>The jump returns a copy instance. This is asserted to be a different object
112      *      of the same class type as the input.
113      *  <li>The copy instance outputs the expected sequence for the current state of the input generator.
114      *      This is asserted using the {@code expectedBefore} sequence.
115      *  <li>The input instance outputs the expected sequence for an advanced state.
116      *      This is asserted using the {@code expectedAfter} sequence.
117      * <ul>
118      *
119      * @param expectedBefore Expected output before the jump.
120      * @param expectedAfter Expected output after the jump.
121      * @param rng Random generator.
122      */
123     public static void assertJumpEquals(int[] expectedBefore,
124                                         int[] expectedAfter,
125                                         JumpableUniformRandomProvider rng) {
126         final UniformRandomProvider copy = rng.jump();
127         Assert.assertNotSame("The copy instance should be a different object", rng, copy);
128         Assert.assertEquals("The copy instance should be the same class", rng.getClass(), copy.getClass());
129         assertEquals("Pre-jump value at position ", expectedBefore, copy);
130         assertEquals("Post-jump value at position ", expectedAfter, rng);
131     }
132 
133     /**
134      * Assert that the random generator satisfies the contract of the
135      * {@link JumpableUniformRandomProvider#jump()} function.
136      *
137      * <ul>
138      *  <li>The jump returns a copy instance. This is asserted to be a different object
139      *      of the same class type as the input.
140      *  <li>The copy instance outputs the expected sequence for the current state of the input generator.
141      *      This is asserted using the {@code expectedBefore} sequence.
142      *  <li>The input instance outputs the expected sequence for an advanced state.
143      *      This is asserted using the {@code expectedAfter} sequence.
144      * <ul>
145      *
146      * @param expectedBefore Expected output before the jump.
147      * @param expectedAfter Expected output after the jump.
148      * @param rng Random generator.
149      */
150     public static void assertJumpEquals(long[] expectedBefore,
151                                         long[] expectedAfter,
152                                         JumpableUniformRandomProvider rng) {
153         final UniformRandomProvider copy = rng.jump();
154         Assert.assertNotSame("The copy instance should be a different object", rng, copy);
155         Assert.assertEquals("The copy instance should be the same class", rng.getClass(), copy.getClass());
156         assertEquals("Pre-jump value at position ", expectedBefore, copy);
157         assertEquals("Post-jump value at position ", expectedAfter, rng);
158     }
159 
160     /**
161      * Assert that the random generator satisfies the contract of the
162      * {@link LongJumpableUniformRandomProvider#longJump()} function.
163      *
164      * <ul>
165      *  <li>The long jump returns a copy instance. This is asserted to be a different object
166      *      of the same class type as the input.
167      *  <li>The copy instance outputs the expected sequence for the current state of the input generator.
168      *      This is asserted using the {@code expectedBefore} sequence.
169      *  <li>The input instance outputs the expected sequence for an advanced state.
170      *      This is asserted using the {@code expectedAfter} sequence.
171      * <ul>
172      *
173      * @param expectedBefore Expected output before the long jump.
174      * @param expectedAfter Expected output after the long jump.
175      * @param rng Random generator.
176      */
177     public static void assertLongJumpEquals(int[] expectedBefore,
178                                             int[] expectedAfter,
179                                             LongJumpableUniformRandomProvider rng) {
180         final UniformRandomProvider copy = rng.longJump();
181         Assert.assertNotSame("The copy instance should be a different object", rng, copy);
182         Assert.assertEquals("The copy instance should be the same class", rng.getClass(), copy.getClass());
183         assertEquals("Pre-jump value at position ", expectedBefore, copy);
184         assertEquals("Post-jump value at position ", expectedAfter, rng);
185     }
186 
187     /**
188      * Assert that the random generator satisfies the contract of the
189      * {@link LongJumpableUniformRandomProvider#longJump()} function.
190      *
191      * <ul>
192      *  <li>The long jump returns a copy instance. This is asserted to be a different object
193      *      of the same class type as the input.
194      *  <li>The copy instance outputs the expected sequence for the current state of the input generator.
195      *      This is asserted using the {@code expectedBefore} sequence.
196      *  <li>The input instance outputs the expected sequence for an advanced state.
197      *      This is asserted using the {@code expectedAfter} sequence.
198      * <ul>
199      *
200      * @param expectedBefore Expected output before the long jump.
201      * @param expectedAfter Expected output after the long jump.
202      * @param rng Random generator.
203      */
204     public static void assertLongJumpEquals(long[] expectedBefore,
205                                             long[] expectedAfter,
206                                             LongJumpableUniformRandomProvider rng) {
207         final UniformRandomProvider copy = rng.longJump();
208         Assert.assertNotSame("The copy instance should be a different object", rng, copy);
209         Assert.assertEquals("The copy instance should be the same class", rng.getClass(), copy.getClass());
210         assertEquals("Pre-jump value at position ", expectedBefore, copy);
211         assertEquals("Post-jump value at position ", expectedAfter, rng);
212     }
213 
214     /**
215      * Assert that the two random generators produce the same output for
216      * {@link UniformRandomProvider#nextInt()} over the given number of cycles.
217      *
218      * @param cycles Number of cycles.
219      * @param rng1 Random generator 1.
220      * @param rng2 Random generator 2.
221      */
222     public static void assertNextIntEquals(int cycles, UniformRandomProvider rng1, UniformRandomProvider rng2) {
223         for (int i = 0; i < cycles; i++) {
224             Assert.assertEquals("Value at position " + i, rng1.nextInt(), rng2.nextInt());
225         }
226     }
227 
228     /**
229      * Assert that the two random generators produce the same output for
230      * {@link UniformRandomProvider#nextLong()} over the given number of cycles.
231      *
232      * @param cycles Number of cycles.
233      * @param rng1 Random generator 1.
234      * @param rng2 Random generator 2.
235      */
236     public static void assertNextLongEquals(int cycles, UniformRandomProvider rng1, UniformRandomProvider rng2) {
237         for (int i = 0; i < cycles; i++) {
238             Assert.assertEquals("Value at position " + i, rng1.nextLong(), rng2.nextLong());
239         }
240     }
241 
242     /**
243      * Assert that the random generator produces zero output for
244      * {@link UniformRandomProvider#nextInt()} over the given number of cycles.
245      * This is used to test a poorly seeded generator cannot generate random output.
246      *
247      * @param rng Random generator.
248      * @param cycles Number of cycles.
249      */
250     public static void assertNextIntZeroOutput(UniformRandomProvider rng, int cycles) {
251         for (int i = 0; i < cycles; i++) {
252             Assert.assertEquals("Expected the generator to output zeros", 0, rng.nextInt());
253         }
254     }
255 
256     /**
257      * Assert that the random generator produces zero output for
258      * {@link UniformRandomProvider#nextLong()} over the given number of cycles.
259      * This is used to test a poorly seeded generator cannot generate random output.
260      *
261      * @param rng Random generator.
262      * @param cycles Number of cycles.
263      */
264     public static void assertNextLongZeroOutput(UniformRandomProvider rng, int cycles) {
265         for (int i = 0; i < cycles; i++) {
266             Assert.assertEquals("Expected the generator to output zeros", 0, rng.nextLong());
267         }
268     }
269 
270     /**
271      * Assert that following a set number of warm-up cycles the random generator produces
272      * at least one non-zero output for {@link UniformRandomProvider#nextLong()} over the
273      * given number of test cycles. This is used to test a poorly seeded generator can recover
274      * internal state to generate "random" output.
275      *
276      * @param rng Random generator.
277      * @param warmupCycles Number of warm-up cycles.
278      * @param testCycles Number of test cycles.
279      */
280     public static void assertNextLongNonZeroOutput(UniformRandomProvider rng,
281                                                    int warmupCycles, int testCycles) {
282         for (int i = 0; i < warmupCycles; i++) {
283             rng.nextLong();
284         }
285         for (int i = 0; i < testCycles; i++) {
286             if (rng.nextLong() != 0L) {
287                 return;
288             }
289         }
290         Assert.fail("No non-zero output after " + (warmupCycles + testCycles) + " cycles");
291     }
292 
293     /**
294      * Assert that following a set number of warm-up cycles the random generator produces
295      * at least one non-zero output for {@link UniformRandomProvider#nextLong()} over the
296      * given number of test cycles.
297      *
298      * <p>Helper function to add the seed element and bit that was non zero to the fail message.
299      *
300      * @param rng Random generator.
301      * @param warmupCycles Number of warm-up cycles.
302      * @param testCycles Number of test cycles.
303      * @param seedElement Seed element for the message.
304      * @param bit Seed bit for the message.
305      */
306     private static void assertNextLongNonZeroOutputFromSingleBitSeed(
307         UniformRandomProvider rng, int warmupCycles, int testCycles, int seedElement, int bit) {
308         try {
309             assertNextLongNonZeroOutput(rng, warmupCycles, testCycles);
310         } catch (AssertionError ex) {
311             Assert.fail("No non-zero output after " + (warmupCycles + testCycles) + " cycles. " +
312                         "Seed element [" + seedElement + "], bit=" + bit);
313         }
314     }
315 
316     /**
317      * Assert that the random generator created using an {@code int[]} seed with a
318      * single bit set is functional. This is tested using the
319      * {@link #assertNextLongNonZeroOutput(UniformRandomProvider, int, int)} using
320      * two times the seed size as the warm-up and test cycles.
321      *
322      * @param type Class of the generator.
323      * @param size Seed size.
324      */
325     public static <T extends UniformRandomProvider> void
326         assertIntArrayConstructorWithSingleBitSeedIsFunctional(Class<T> type, int size) {
327         assertIntArrayConstructorWithSingleBitInPoolIsFunctional(type, 32 * size);
328     }
329 
330     /**
331      * Assert that the random generator created using an {@code int[]} seed with a
332      * single bit set is functional. This is tested using the
333      * {@link #assertNextLongNonZeroOutput(UniformRandomProvider, int, int)} using
334      * two times the seed size as the warm-up and test cycles.
335      *
336      * <p>The seed size is determined from the size of the bit pool. Bits are set for every position
337      * in the pool from most significant first. If the pool size is not a multiple of 32 then the
338      * remaining lower significant bits of the last seed position are not tested.</p>
339      *
340      * @param type Class of the generator.
341      * @param k Number of bits in the pool (not necessarily a multiple of 32).
342      */
343     public static <T extends UniformRandomProvider> void
344         assertIntArrayConstructorWithSingleBitInPoolIsFunctional(Class<T> type, int k) {
345         try {
346             // Find the int[] constructor
347             final Constructor<T> constructor = type.getConstructor(int[].class);
348             final int size = (k + 31) / 32;
349             final int[] seed = new int[size];
350             int remaining = k;
351             for (int i = 0; i < seed.length; i++) {
352                 seed[i] = 0x80000000;
353                 for (int j = 0; j < 32; j++) {
354                     final UniformRandomProvider rng = constructor.newInstance(seed);
355                     RandomAssert.assertNextLongNonZeroOutputFromSingleBitSeed(rng, 2 * size, 2 * size, i, 31 - j);
356                     // Eventually reset to zero
357                     seed[i] >>>= 1;
358                     // Terminate when the entire bit pool has been tested
359                     if (--remaining == 0) {
360                         return;
361                     }
362                 }
363                 Assert.assertEquals("Seed element was not reset", 0, seed[i]);
364             }
365         } catch (IllegalAccessException ex) {
366             Assert.fail(ex.getMessage());
367         } catch (NoSuchMethodException ex) {
368             Assert.fail(ex.getMessage());
369         } catch (InstantiationException ex) {
370             Assert.fail(ex.getMessage());
371         } catch (InvocationTargetException ex) {
372             Assert.fail(ex.getMessage());
373         }
374     }
375 
376     /**
377      * Assert that the random generator created using a {@code long[]} seed with a
378      * single bit set is functional. This is tested using the
379      * {@link #assertNextLongNonZeroOutput(UniformRandomProvider, int, int)} using two times the seed
380      * size as the warm-up and test cycles.
381      *
382      * @param type Class of the generator.
383      * @param size Seed size.
384      */
385     public static <T extends UniformRandomProvider> void
386         assertLongArrayConstructorWithSingleBitSeedIsFunctional(Class<T> type, int size) {
387         try {
388             // Find the long[] constructor
389             final Constructor<T> constructor = type.getConstructor(long[].class);
390             final long[] seed = new long[size];
391             for (int i = 0; i < size; i++) {
392                 seed[i] = 0x8000000000000000L;
393                 for (int j = 0; j < 64; j++) {
394                     final UniformRandomProvider rng = constructor.newInstance(seed);
395                     RandomAssert.assertNextLongNonZeroOutputFromSingleBitSeed(rng, 2 * size, 2 * size, i, 63 - j);
396                     // Eventually reset to zero
397                     seed[i] >>>= 1;
398                 }
399                 Assert.assertEquals("Seed element was not reset", 0L, seed[i]);
400             }
401         } catch (IllegalAccessException ex) {
402             Assert.fail(ex.getMessage());
403         } catch (NoSuchMethodException ex) {
404             Assert.fail(ex.getMessage());
405         } catch (InstantiationException ex) {
406             Assert.fail(ex.getMessage());
407         } catch (InvocationTargetException ex) {
408             Assert.fail(ex.getMessage());
409         }
410     }
411 }