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 }