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.simple;
18  
19  import java.lang.reflect.Method;
20  import java.lang.reflect.Modifier;
21  import java.time.Duration;
22  import java.util.SplittableRandom;
23  import java.util.stream.DoubleStream;
24  import java.util.stream.IntStream;
25  import java.util.stream.LongStream;
26  import org.apache.commons.rng.RandomProviderState;
27  import org.apache.commons.rng.RestorableUniformRandomProvider;
28  import org.apache.commons.rng.UniformRandomProvider;
29  import org.apache.commons.rng.core.source64.LongProvider;
30  import org.junit.jupiter.api.Assertions;
31  import org.junit.jupiter.api.Test;
32  
33  /**
34   * Tests for {@link RandomSource}.
35   */
36  class RandomSourceTest {
37      @Test
38      void testCreateInt() {
39          final int n = 4;
40          for (int i = 0; i < n; i++) {
41              // Can fail, but unlikely given the range.
42              Assertions.assertNotEquals(RandomSource.createInt(),
43                                         RandomSource.createInt());
44          }
45      }
46  
47      @Test
48      void testCreateLong() {
49          final int n = 6;
50          for (int i = 0; i < n; i++) {
51              // Can fail, but unlikely given the range.
52              Assertions.assertNotEquals(RandomSource.createLong(),
53                                         RandomSource.createLong());
54          }
55      }
56  
57      @Test
58      void testCreateIntArray() {
59          final int n = 13;
60          final int[] seed = RandomSource.createIntArray(n);
61          Assertions.assertEquals(n, seed.length);
62  
63          for (int i = 1; i < n; i++) {
64              // Can fail, but unlikely given the range.
65              Assertions.assertNotEquals(seed[i - 1], seed[i]);
66          }
67      }
68  
69      @Test
70      void testCreateLongArray() {
71          final int n = 9;
72          final long[] seed = RandomSource.createLongArray(n);
73          Assertions.assertEquals(n, seed.length);
74  
75          for (int i = 1; i < n; i++) {
76              // Can fail, but unlikely given the range.
77              Assertions.assertNotEquals(seed[i - 1], seed[i]);
78          }
79      }
80  
81      @Test
82      void testIsJumpable() {
83          Assertions.assertFalse(RandomSource.JDK.isJumpable(), "JDK is not Jumpable");
84          Assertions.assertTrue(RandomSource.XOR_SHIFT_1024_S_PHI.isJumpable(), "XOR_SHIFT_1024_S_PHI is Jumpable");
85          Assertions.assertTrue(RandomSource.XO_SHI_RO_256_SS.isJumpable(), "XO_SHI_RO_256_SS is Jumpable");
86      }
87  
88      @Test
89      void testIsLongJumpable() {
90          Assertions.assertFalse(RandomSource.JDK.isLongJumpable(), "JDK is not LongJumpable");
91          Assertions.assertFalse(RandomSource.XOR_SHIFT_1024_S_PHI.isLongJumpable(), "XOR_SHIFT_1024_S_PHI is not LongJumpable");
92          Assertions.assertTrue(RandomSource.XO_SHI_RO_256_SS.isLongJumpable(), "XO_SHI_RO_256_SS is LongJumpable");
93      }
94  
95      @Test
96      void testIsSplittable() {
97          Assertions.assertFalse(RandomSource.JDK.isSplittable(), "JDK is not Splittable");
98          Assertions.assertTrue(RandomSource.L32_X64_MIX.isSplittable(), "L32_X64_MIX is Splittable");
99          Assertions.assertTrue(RandomSource.L64_X128_MIX.isSplittable(), "L64_X128_MIX is Splittable");
100     }
101 
102     /**
103      * MSWS should not infinite loop if the input RNG fails to provide randomness to create a seed.
104      * See RNG-175.
105      */
106     @Test
107     void testMSWSCreateSeed() {
108         final LongProvider broken = new LongProvider() {
109             @Override
110             public long next() {
111                 return 0;
112             }
113         };
114         Assertions.assertTimeoutPreemptively(Duration.ofMillis(100), () -> {
115             RandomSource.MSWS.createSeed(broken);
116         });
117     }
118 
119     /**
120      * Test the unrestorable method correctly delegates all methods.
121      * This includes the methods with default implementations in UniformRandomProvider, i.e.
122      * the default methods should not be used.
123      */
124     @Test
125     void testUnrestorable() {
126         // The test class should override all default methods
127         assertNoDefaultMethods(RestorableRNG.class);
128 
129         final UniformRandomProvider rng1 = new RestorableRNG();
130         final RestorableRNG r = new RestorableRNG();
131         final UniformRandomProvider rng2 = RandomSource.unrestorable(r);
132         Assertions.assertNotSame(rng2, r);
133 
134         // The unrestorable instance should override all default methods
135         assertNoDefaultMethods(rng2.getClass());
136 
137         // Despite the newly created RNG not being a RestorableUniformRandomProvider,
138         // the method still wraps it in a new object. This is the behaviour since version 1.0.
139         // It allows the method to wrap objects that implement UniformRandomProvider (such
140         // as sub-interfaces or concrete classes) to prevent access to any additional methods.
141         Assertions.assertNotSame(rng2, RandomSource.unrestorable(rng2));
142 
143         // Ensure that they generate the same values.
144         RandomAssert.assertProduceSameSequence(rng1, rng2);
145 
146         // Cast must work.
147         @SuppressWarnings("unused")
148         final RestorableUniformRandomProvider restorable = (RestorableUniformRandomProvider) rng1;
149         // Cast must fail.
150         Assertions.assertThrows(ClassCastException.class, () -> {
151             @SuppressWarnings("unused")
152             final RestorableUniformRandomProvider dummy = (RestorableUniformRandomProvider) rng2;
153         });
154     }
155 
156     /**
157      * Assert the class has overridden all default public interface methods.
158      *
159      * @param cls the class
160      */
161     private static void assertNoDefaultMethods(Class<?> cls) {
162         for (final Method method : cls.getMethods()) {
163             if ((method.getModifiers() & Modifier.PUBLIC) != 0) {
164                 Assertions.assertTrue(!method.isDefault(),
165                     () -> cls.getName() + " should override method: " + method.toGenericString());
166             }
167         }
168     }
169 
170     /**
171      * Class to provide a complete implementation of the {@link UniformRandomProvider} interface.
172      * This must return a different result than the default implementations in the interface.
173      */
174     private static class RestorableRNG implements RestorableUniformRandomProvider {
175         /** The source of randomness. */
176         private final SplittableRandom rng = new SplittableRandom(123);
177 
178         @Override
179         public void nextBytes(byte[] bytes) {
180             nextBytes(bytes, 0, bytes.length);
181         }
182 
183         @Override
184         public void nextBytes(byte[] bytes, int start, int len) {
185             RestorableUniformRandomProvider.super.nextBytes(bytes, start, len);
186             // Rotate
187             for (int i = start + len; i-- > start;) {
188                 bytes[i] += 1;
189             }
190         }
191 
192         @Override
193         public int nextInt() {
194             return RestorableUniformRandomProvider.super.nextInt() + 1;
195         }
196 
197         @Override
198         public int nextInt(int n) {
199             final int v = RestorableUniformRandomProvider.super.nextInt(n) + 1;
200             return v == n ? 0 : v;
201         }
202 
203         @Override
204         public int nextInt(int origin, int bound) {
205             final int v = RestorableUniformRandomProvider.super.nextInt(origin, bound) + 1;
206             return v == bound ? origin : v;
207         }
208 
209         @Override
210         public long nextLong() {
211             // Source of randomness for all derived methods
212             return rng.nextLong();
213         }
214 
215         @Override
216         public long nextLong(long n) {
217             final long v = RestorableUniformRandomProvider.super.nextLong(n) + 1;
218             return v == n ? 0 : v;
219         }
220 
221         @Override
222         public long nextLong(long origin, long bound) {
223             final long v = RestorableUniformRandomProvider.super.nextLong(origin, bound) + 1;
224             return v == bound ? origin : v;
225         }
226 
227         @Override
228         public boolean nextBoolean() {
229             return !RestorableUniformRandomProvider.super.nextBoolean();
230         }
231 
232         @Override
233         public float nextFloat() {
234             final float v = 1 - RestorableUniformRandomProvider.super.nextFloat();
235             return v == 1 ? 0 : v;
236         }
237 
238         @Override
239         public float nextFloat(float bound) {
240             final float v = Math.nextUp(RestorableUniformRandomProvider.super.nextFloat(bound));
241             return v == bound ? 0 : v;
242         }
243 
244         @Override
245         public float nextFloat(float origin, float bound) {
246             final float v = Math.nextUp(RestorableUniformRandomProvider.super.nextFloat(origin, bound));
247             return v == bound ? 0 : v;
248         }
249 
250         @Override
251         public double nextDouble() {
252             final double v = 1 - RestorableUniformRandomProvider.super.nextDouble();
253             return v == 1 ? 0 : v;
254         }
255 
256         @Override
257         public double nextDouble(double bound) {
258             final double v = Math.nextUp(RestorableUniformRandomProvider.super.nextDouble(bound));
259             return v == bound ? 0 : v;
260         }
261 
262         @Override
263         public double nextDouble(double origin, double bound) {
264             final double v = Math.nextUp(RestorableUniformRandomProvider.super.nextDouble(origin, bound));
265             return v == bound ? 0 : v;
266         }
267 
268         // Stream methods must return different values than the default so we reimplement them
269 
270         @Override
271         public IntStream ints() {
272             return IntStream.generate(() -> nextInt() + 1).sequential();
273         }
274 
275         @Override
276         public IntStream ints(int origin, int bound) {
277             return IntStream.generate(() -> {
278                 final int v = nextInt(origin, bound) + 1;
279                 return v == bound ? origin : v;
280             }).sequential();
281         }
282 
283         @Override
284         public IntStream ints(long streamSize) {
285             return ints().limit(streamSize);
286         }
287 
288         @Override
289         public IntStream ints(long streamSize, int origin, int bound) {
290             return ints(origin, bound).limit(streamSize);
291         }
292 
293         @Override
294         public LongStream longs() {
295             return LongStream.generate(() -> nextLong() + 1).sequential();
296         }
297 
298         @Override
299         public LongStream longs(long origin, long bound) {
300             return LongStream.generate(() -> {
301                 final long v = nextLong(origin, bound) + 1;
302                 return v == bound ? origin : v;
303             }).sequential();
304         }
305 
306         @Override
307         public LongStream longs(long streamSize) {
308             return longs().limit(streamSize);
309         }
310 
311         @Override
312         public LongStream longs(long streamSize, long origin, long bound) {
313             return longs(origin, bound).limit(streamSize);
314         }
315 
316         @Override
317         public DoubleStream doubles() {
318             return DoubleStream.generate(() -> {
319                 final double v = Math.nextUp(nextDouble());
320                 return v == 1 ? 0 : v;
321             }).sequential();
322         }
323 
324         @Override
325         public DoubleStream doubles(double origin, double bound) {
326             return DoubleStream.generate(() -> {
327                 final double v = Math.nextUp(nextDouble(origin, bound));
328                 return v == bound ? origin : v;
329             }).sequential();
330         }
331 
332         @Override
333         public DoubleStream doubles(long streamSize) {
334             return doubles().limit(streamSize);
335         }
336 
337         @Override
338         public DoubleStream doubles(long streamSize, double origin, double bound) {
339             return doubles(origin, bound).limit(streamSize);
340         }
341 
342         @Override
343         public RandomProviderState saveState() {
344             // Do nothing
345             return null;
346         }
347 
348         @Override
349         public void restoreState(RandomProviderState state) {
350             // Do nothing
351         }
352     }
353 }