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 java.util.Arrays;
20  import java.util.List;
21  import java.util.ArrayList;
22  import java.util.concurrent.Callable;
23  import java.io.IOException;
24  import java.io.ObjectOutputStream;
25  import java.io.ObjectInputStream;
26  import java.io.ByteArrayOutputStream;
27  import java.io.ByteArrayInputStream;
28  
29  import org.junit.Assert;
30  import org.junit.Test;
31  import org.junit.Assume;
32  import org.junit.Ignore;
33  import org.junit.runner.RunWith;
34  import org.junit.runners.Parameterized;
35  import org.junit.runners.Parameterized.Parameters;
36  
37  import org.apache.commons.rng.UniformRandomProvider;
38  import org.apache.commons.rng.RestorableUniformRandomProvider;
39  import org.apache.commons.rng.RandomProviderState;
40  
41  /**
42   * Tests which all generators must pass.
43   */
44  @RunWith(value=Parameterized.class)
45  public class ProvidersCommonParametricTest {
46      /** RNG under test. */
47      private final RestorableUniformRandomProvider generator;
48  
49      /**
50       * Initializes generator instance.
51       *
52       * @param rng RNG to be tested.
53       */
54      public ProvidersCommonParametricTest(RestorableUniformRandomProvider rng) {
55          generator = rng;
56      }
57  
58      @Parameters(name = "{index}: data={0}")
59      public static Iterable<RestorableUniformRandomProvider[]> getList() {
60          return ProvidersList.list();
61      }
62  
63  
64      // Precondition tests
65  
66      @Test(expected=IllegalArgumentException.class)
67      public void testPreconditionNextInt1() {
68          generator.nextInt(-1);
69      }
70  
71      @Test(expected=IllegalArgumentException.class)
72      public void testPreconditionNextInt2() {
73          generator.nextInt(0);
74      }
75  
76      @Test(expected=IllegalArgumentException.class)
77      public void testPreconditionNextLong1() {
78          generator.nextLong(-1);
79      }
80  
81      @Test(expected=IllegalArgumentException.class)
82      public void testPreconditionNextLong2() {
83          generator.nextLong(0);
84      }
85  
86      @Test(expected=IndexOutOfBoundsException.class)
87      public void testPreconditionNextBytes1() {
88          final int size = 10;
89          final int num = 1;
90          final byte[] buf = new byte[size];
91          generator.nextBytes(buf, -1, num);
92      }
93      @Test(expected=IndexOutOfBoundsException.class)
94      public void testPreconditionNextBytes2() {
95          final int size = 10;
96          final byte[] buf = new byte[size];
97          generator.nextBytes(buf, size, 0);
98      }
99      @Test(expected=IndexOutOfBoundsException.class)
100     public void testPreconditionNextBytes3() {
101         final int size = 10;
102         final int offset = 2;
103         final byte[] buf = new byte[size];
104         generator.nextBytes(buf, offset, size - offset + 1);
105     }
106     @Test(expected=IndexOutOfBoundsException.class)
107     public void testPreconditionNextBytes4() {
108         final int size = 10;
109         final int offset = 1;
110         final byte[] buf = new byte[size];
111         generator.nextBytes(buf, offset, -1);
112     }
113 
114 
115     // Uniformity tests
116  
117     @Test
118     public void testUniformNextBytesFullBuffer() {
119         // Value chosen to exercise all the code lines in the
120         // "nextBytes" methods.
121         final int size = 23;
122         final byte[] buffer = new byte[size];
123 
124         final Runnable nextMethod = new Runnable() {
125             @Override
126             public void run() {
127                 generator.nextBytes(buffer);
128             }
129         };
130 
131         Assert.assertTrue(isUniformNextBytes(buffer, 0, size, nextMethod));
132     }
133 
134     @Test
135     public void testUniformNextBytesPartialBuffer() {
136         final int totalSize = 1234;
137         final int offset = 567;
138         final int size = 89;
139 
140         final byte[] buffer = new byte[totalSize];
141 
142         final Runnable nextMethod = new Runnable() {
143             @Override
144             public void run() {
145                 generator.nextBytes(buffer, offset, size);
146             }
147         };
148 
149         // Test should pass for the part of the buffer where values are put.
150         Assert.assertTrue(isUniformNextBytes(buffer, offset, offset + size, nextMethod));
151 
152         // Test must fail for the parts of the buffer where no values are put.
153         Assert.assertFalse(isUniformNextBytes(buffer, 0, offset, nextMethod));
154         Assert.assertFalse(isUniformNextBytes(buffer, offset + size, buffer.length, nextMethod));
155     }
156 
157     @Test
158     public void testUniformNextIntegerInRange() {
159         checkNextIntegerInRange(4, 1000);
160         checkNextIntegerInRange(10, 1000);
161         checkNextIntegerInRange(12, 1000);
162         checkNextIntegerInRange(31, 1000);
163         checkNextIntegerInRange(32, 1000);
164         checkNextIntegerInRange(2016128993, 1000);
165         checkNextIntegerInRange(1834691456, 1000);
166         checkNextIntegerInRange(869657561, 1000);
167         checkNextIntegerInRange(1570504788, 1000);
168     }
169 
170     @Test
171     public void testUniformNextLongInRange() {
172         checkNextLongInRange(4, 1000);
173         checkNextLongInRange(11, 1000);
174         checkNextLongInRange(19, 1000);
175         checkNextLongInRange(31, 1000);
176         checkNextLongInRange(32, 1000);
177 
178         final long q = Long.MAX_VALUE / 4;
179         checkNextLongInRange(q, 1000);
180         checkNextLongInRange(2 * q, 1000);
181         checkNextLongInRange(3 * q, 1000);
182     }
183 
184     @Test
185     public void testUniformNextFloat() {
186         checkNextFloat(1000);
187     }
188 
189     @Test
190     public void testUniformNextDouble() {
191         checkNextDouble(1000);
192     }
193 
194     @Test
195     public void testUniformNextIntRandomWalk() {
196         final Callable<Boolean> nextMethod = new Callable<Boolean>() {
197             @Override
198             public Boolean call() throws Exception {
199                 return generator.nextInt() >= 0;
200             }
201         };
202 
203         checkRandomWalk(1000, nextMethod);
204     }
205 
206     @Test
207     public void testUniformNextLongRandomWalk() {
208         final Callable<Boolean> nextMethod = new Callable<Boolean>() {
209             @Override
210             public Boolean call() throws Exception {
211                 return generator.nextLong() >= 0;
212             }
213         };
214 
215         checkRandomWalk(1000, nextMethod);
216     }
217 
218     @Test
219     public void testUniformNextBooleanRandomWalk() {
220         final Callable<Boolean> nextMethod = new Callable<Boolean>() {
221             @Override
222             public Boolean call() throws Exception {
223                 return generator.nextBoolean();
224             }
225         };
226 
227         checkRandomWalk(1000, nextMethod);
228     }
229 
230     // State save and restore tests.
231 
232     @Test
233     public void testStateSettable() {
234         // Should be fairly large in order to ensure that all the internal
235         // state is away from its initial settings.
236         final int n = 10000;
237 
238         // Save.
239         final RandomProviderState state = generator.saveState();
240         // Store some values.
241         final List<Number> listOrig = makeList(n);
242         // Discard a few more.
243         final List<Number> listDiscard = makeList(n);
244         Assert.assertTrue(listDiscard.size() != 0);
245         Assert.assertFalse(listOrig.equals(listDiscard));
246         // Reset.
247         generator.restoreState(state);
248         // Replay.
249         final List<Number> listReplay = makeList(n);
250         Assert.assertFalse(listOrig == listReplay);
251         // Check that the restored state is the same as the orginal.
252         Assert.assertTrue(listOrig.equals(listReplay));
253     }
254 
255     @Test(expected=IllegalArgumentException.class)
256     public void testStateWrongSize() {
257         // We don't know what is the state of "java.lang.Random": skipping.
258         Assume.assumeTrue(generator.toString().indexOf("JDKRandom") == -1);
259 
260         final RandomProviderState state = new DummyGenerator().saveState();
261         // Try to restore with an invalid state (wrong size).
262         generator.restoreState(state);
263     }
264 
265     @Test(expected=IllegalArgumentException.class)
266     public void testRestoreForeignState() {
267         generator.restoreState(new RandomProviderState() {});
268     }
269 
270 
271     ///// Support methods below.
272 
273     /**
274      * Populates a list with random numbers.
275      *
276      * @param n Loop counter.
277      * @return a list containing {@code 11 * n} random numbers.
278      */
279     private List<Number> makeList(int n) {
280         final List<Number> list = new ArrayList<Number>();
281 
282         for (int i = 0; i < n; i++) {
283             // Append 11 values.
284             list.add(generator.nextInt());
285             list.add(generator.nextInt(21));
286             list.add(generator.nextInt(436));
287             list.add(generator.nextLong());
288             list.add(generator.nextLong(157894));
289             list.add(generator.nextLong(5745833));
290             list.add(generator.nextFloat());
291             list.add(generator.nextFloat());
292             list.add(generator.nextDouble());
293             list.add(generator.nextDouble());
294             list.add(generator.nextDouble());
295         }
296 
297         return list;
298     }
299 
300     /**
301      * Checks that the generator values can be placed into 256 bins with
302      * approximately equal number of counts.
303      * Test allows to select only part of the buffer for performing the
304      * statistics.
305      *
306      * @param buffer Buffer to be filled.
307      * @param first First element (included) of {@code buffer} range for
308      * which statistics must be taken into account.
309      * @param last Last element (excluded) of {@code buffer} range for
310      * which statistics must be taken into account.
311      * @param nextMethod Method that fills the given {@code buffer}.
312      * @return {@code true} if the distribution is uniform.
313      */
314     private boolean isUniformNextBytes(byte[] buffer,
315                                        int first,
316                                        int last,
317                                        Runnable nextMethod) {
318         final int sampleSize = 10000;
319 
320         // Number of possible values (do not change).
321         final int byteRange = 256;
322         // Chi-square critical value with 256 degrees of freedom
323         // and 1% significance level.
324         final double chi2CriticalValue = 311.560343;
325         // To transform a byte value into its bin index.
326         final int byteRangeOffset = 128;
327 
328         // Bins.
329         final long[] observed = new long[byteRange];
330         final double[] expected = new double[byteRange];
331 
332         for (int i = 0; i < byteRange; i++) {
333             expected[i] = sampleSize * (last - first) / (double) byteRange;
334         }
335 
336         try {
337             for (int k = 0; k < sampleSize; k++) {
338                 nextMethod.run();
339 
340                 for (int i = first; i < last; i++) {
341                     final byte b = buffer[i];
342                     ++observed[b + byteRangeOffset];
343                 }
344             }
345         } catch (Exception e) {
346             // Should never happen.
347             throw new RuntimeException("Unexpected");
348         }
349 
350         // Compute chi-square.
351         double chi2 = 0;
352         for (int k = 0; k < byteRange; k++) {
353             final double diff = observed[k] - expected[k];
354             chi2 += diff * diff / expected[k];
355         }
356 
357         // Statistics check.
358         return chi2 < chi2CriticalValue;
359     }
360 
361     /**
362      * Checks that the generator values can be placed into 2 bins with
363      * approximately equal number of counts.
364      * The test uses the expectation from a fixed-step "random walk".
365      *
366      * @param nextMethod Method that returns {@code true} if the generated
367      * values are to be placed in the first bin, {@code false} if it must
368      * go to the second bin.
369      */
370     private void checkRandomWalk(int sampleSize,
371                                  Callable<Boolean> nextMethod) {
372         int walk = 0;
373 
374         try {
375             for (int k = 0; k < sampleSize; ++k) {
376                 if (nextMethod.call()) {
377                     ++walk;
378                 } else {
379                     --walk;
380                 }
381             }
382         } catch (Exception e) {
383             // Should never happen.
384             throw new RuntimeException("Unexpected");
385         }
386 
387         final double actual = Math.abs(walk);
388         final double max = Math.sqrt(sampleSize) * 2.576;
389         Assert.assertTrue(generator + ": Walked too far astray: " + actual +
390                           " > " + max +
391                           " (test will fail randomly about 1 in 100 times)",
392                           actual < max);
393     }
394 
395     /**
396      * Tests uniformity of the distribution produced by {@code nextInt(int)}.
397      *
398      * @param max Upper bound.
399      * @param sampleSize Number of random values generated.
400      */
401     private void checkNextIntegerInRange(final int max,
402                                          int sampleSize) {
403         checkNextIntegerInRange(generator, max, sampleSize);
404     }
405 
406     /**
407      * Tests uniformity of the distribution produced by {@code nextInt(int)}.
408      *
409      * @param rng Generator.
410      * @param max Upper bound.
411      * @param sampleSize Number of random values generated.
412      */
413     private void checkNextIntegerInRange(final UniformRandomProvider rng,
414                                          final int max,
415                                          int sampleSize) {
416         final Callable<Integer> nextMethod = new Callable<Integer>() {
417             @Override
418             public Integer call() throws Exception {
419                 return rng.nextInt(max);
420             }
421         };
422 
423         checkNextInRange(max, sampleSize, nextMethod);
424     }
425 
426     /**
427      * Tests uniformity of the distribution produced by {@code nextLong(long)}.
428      *
429      * @param max Upper bound.
430      * @param sampleSize Number of random values generated.
431      */
432     private void checkNextLongInRange(final long max,
433                                       int sampleSize) {
434         final Callable<Long> nextMethod = new Callable<Long>() {
435             @Override
436             public Long call() throws Exception {
437                 return generator.nextLong(max);
438             }
439         };
440 
441         checkNextInRange(max, sampleSize, nextMethod);
442     }
443 
444     /**
445      * Tests uniformity of the distribution produced by {@code nextFloat()}.
446      *
447      * @param sampleSize Number of random values generated.
448      */
449     private void checkNextFloat(int sampleSize) {
450         final int max = 1234;
451         final Callable<Integer> nextMethod = new Callable<Integer>() {
452             @Override
453             public Integer call() throws Exception {
454                 return (int) (max * generator.nextFloat());
455             }
456         };
457 
458         checkNextInRange(max, sampleSize, nextMethod);
459     }
460 
461     /**
462      * Tests uniformity of the distribution produced by {@code nextDouble()}.
463      *
464      * @param sampleSize Number of random values generated.
465      */
466     private void checkNextDouble(int sampleSize) {
467         final int max = 578;
468         final Callable<Integer> nextMethod = new Callable<Integer>() {
469             @Override
470             public Integer call() throws Exception {
471                 return (int) (max * generator.nextDouble());
472             }
473         };
474 
475         checkNextInRange(max, sampleSize, nextMethod);
476     }
477 
478     /**
479      * Tests uniformity of the distribution produced by the given
480      * {@code nextMethod}.
481      * It performs a chi-square test of homogeneity of the observed
482      * distribution with the expected uniform distribution.
483      * Tests are performed at the 1% level and an average failure rate
484      * higher than 2% causes the test case to fail.
485      *
486      * @param max Upper bound.
487      * @param nextMethod method to call.
488      * @param sampleSize Number of random values generated.
489      */
490     private <T extends Number> void checkNextInRange(T max,
491                                                      int sampleSize,
492                                                      Callable<T> nextMethod) {
493         final int numTests = 500;
494 
495         // Do not change (statistical test assumes that dof = 9).
496         final int numBins = 10; // dof = numBins - 1
497 
498         // Set up bins.
499         final long n = max.longValue();
500         final long[] binUpperBounds = new long[numBins];
501         final double step = n / (double) numBins;
502         for (int k = 0; k < numBins; k++) {
503             binUpperBounds[k] = (long) ((k + 1) * step);
504         }
505 
506         // Run the tests.
507         int numFailures = 0;
508 
509         final double[] expected = new double[numBins];
510         long previousUpperBound = 0;
511         for (int k = 0; k < numBins; k++) {
512             final long range = binUpperBounds[k] - previousUpperBound;
513             expected[k] = sampleSize * (range / (double) n);
514             previousUpperBound = binUpperBounds[k];
515         }
516 
517         final int[] observed = new int[numBins];
518         // Chi-square critical value with 9 degrees of freedom
519         // and 1% significance level.
520         final double chi2CriticalValue = 21.67;
521 
522         try {
523             for (int i = 0; i < numTests; i++) {
524                 Arrays.fill(observed, 0);
525                 for (int j = 0; j < sampleSize; j++) {
526                     final long value = nextMethod.call().longValue();
527                     Assert.assertTrue("Range", (value >= 0) && (value < n));
528 
529                     for (int k = 0; k < numBins; k++) {
530                         if (value < binUpperBounds[k]) {
531                             ++observed[k];
532                             break;
533                         }
534                     }
535                 }
536 
537                 // Compute chi-square.
538                 double chi2 = 0;
539                 for (int k = 0; k < numBins; k++) {
540                     final double diff = observed[k] - expected[k];
541                     chi2 += diff * diff / expected[k];
542                 }
543 
544                 // Statistics check.
545                 if (chi2 > chi2CriticalValue) {
546                     ++numFailures;
547                 }
548             }
549         } catch (Exception e) {
550             // Should never happen.
551             throw new RuntimeException("Unexpected", e);
552         }
553 
554         if ((double) numFailures / (double) numTests > 0.02) {
555             Assert.fail(generator + ": Too many failures for n = " + n +
556                         " (" + numFailures + " out of " + numTests + " tests failed)");
557         }
558     }
559 
560     /**
561      * @param rng Generator.
562      * @param chunkSize Size of the small buffer.
563      * @param numChunks Number of chunks that make the large buffer.
564      */
565     static void checkNextBytesChunks(RestorableUniformRandomProvider rng,
566                                      int chunkSize,
567                                      int numChunks) {
568         final byte[] b1 = new byte[chunkSize * numChunks];
569         final byte[] b2 = new byte[chunkSize];
570 
571         final RandomProviderState state = rng.saveState();
572 
573         // Generate the chunks in a single call.
574         rng.nextBytes(b1);
575 
576         // Reset to previous state.
577         rng.restoreState(state);
578 
579         // Generate the chunks in consecutive calls.
580         for (int i = 0; i < numChunks; i++) {
581             rng.nextBytes(b2);
582         }
583 
584         // Store last "chunkSize" bytes of b1 into b3.
585         final byte[] b3 = new byte[chunkSize];
586         System.arraycopy(b1, b1.length - b3.length, b3, 0, b3.length);
587 
588         // Sequence of calls must be the same.
589         Assert.assertArrayEquals("chunkSize=" + chunkSize + " numChunks=" + numChunks,
590                                  b2, b3);
591     }
592 
593     /**
594      * Dummy class for checking that restoring fails when an invalid state is used.
595      */
596     class DummyGenerator extends org.apache.commons.rng.core.source32.IntProvider {
597         /** {@inheritDoc} */
598         @Override
599         public int next() {
600             return 4; // https://www.xkcd.com/221/
601         }
602 
603         /** {@inheritDoc} */
604         @Override
605         protected byte[] getStateInternal() {
606             return new byte[0];
607         }
608 
609         /** {@inheritDoc} */
610         @Override
611         protected void setStateInternal(byte[] s) {
612             // No state.
613         }
614     }
615 }