GeneratorTest.php 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168
  1. <?php
  2. /*
  3. * The RandomLib library for securely generating random numbers and strings in PHP
  4. *
  5. * @author Anthony Ferrara <ircmaxell@ircmaxell.com>
  6. * @copyright 2011 The Authors
  7. * @license http://www.opensource.org/licenses/mit-license.html MIT License
  8. * @version Build @@version@@
  9. */
  10. use RandomLib\Generator;
  11. use RandomLibTest\Mocks\Random\Mixer;
  12. use RandomLibTest\Mocks\Random\Source;
  13. class Vectors_Random_GeneratorTest extends PHPUnit_Framework_TestCase
  14. {
  15. public static function provideGenerateInt()
  16. {
  17. return array(
  18. // First, lets test each offset based range
  19. array(0, 7),
  20. array(0, 15),
  21. array(0, 31),
  22. array(0, 63),
  23. array(0, 127),
  24. array(0, 255),
  25. array(0, 511),
  26. array(0, 1023),
  27. // Let's try a range not starting at 0
  28. array(8, 15),
  29. // Let's try a range with a negative number
  30. array(-18, -11),
  31. // Let's try a non-power-of-2 range
  32. array(10, 100),
  33. // Finally, let's try two large numbers
  34. array(100000, 100007),
  35. array(100000000, 100002047),
  36. // Now, let's force a few loops by setting a valid offset
  37. array(0, 5, 2),
  38. array(0, 9, 5),
  39. array(0, 27, 4),
  40. );
  41. }
  42. public static function provideGenerators()
  43. {
  44. $factory = new \RandomLib\Factory();
  45. $generator = $factory->getLowStrengthGenerator();
  46. $sources = $generator->getSources();
  47. $ret = array();
  48. $ret[] = array(new Generator($sources, new \RandomLib\Mixer\Hash()), 10000, 'hash');
  49. return $ret;
  50. }
  51. /**
  52. * This test asserts that the algorithm that generates the integers does not
  53. * actually introduce any bias into the generated numbers. If this test
  54. * passes, the generated integers from the generator will be as unbiased as
  55. * the sources that provide the data.
  56. *
  57. * @dataProvider provideGenerateInt
  58. */
  59. public function testGenerateInt($min, $max, $offset = 0)
  60. {
  61. $generator = $this->getGenerator($max - $min + $offset);
  62. for ($i = $max; $i >= $min; $i--) {
  63. $this->assertEquals($i, $generator->generateInt($min, $max));
  64. }
  65. }
  66. /**
  67. * This generator generates two bytes at a time, and uses each 8 bit segment of
  68. * the generated byte as a coordinate on a grid (so 01011010 would be the
  69. * coordinate (0101, 1010) or (5, 10). These are used as inputs to a MonteCarlo
  70. * algorithm for the integral of y=x over a 15x15 grid. The expected answer is
  71. * 1/2 * 15 * 15 (or 1/2 * base * height, since the result is a triangle).
  72. * Therefore, if we get an answer close to that, we know the generator is good.
  73. *
  74. * Now, since the area under the line should be equal to the area above the line.
  75. * Therefore, the ratio of the two areas should be equal. This way, we can avoid
  76. * computing total to figure out the areas.
  77. *
  78. * I have set the bounds on the test to be 80% and 120%. Meaning that I will
  79. * consider the test valid and unbiased if the number of random elements that
  80. * fall under (inside) of the line and the number that fall outside of the line
  81. * are at most 20% apart.
  82. *
  83. * Since testing randomness is not reliable or repeatable, I will only fail the
  84. * test in two different scenarios. The first is if after the iterations the
  85. * outside or the inside is 0. The chances of that happening are so low that
  86. * if it happens, it's relatively safe to assume that something bad happened. The
  87. * second scenario happens when the ratio is outside of the 20% tolerance. If
  88. * that happens, I will re-run the entire test. If that test is outside of the 20%
  89. * tolerance, then the test will fail
  90. *
  91. *
  92. * @dataProvider provideGenerators
  93. */
  94. public function testGenerate(\RandomLib\Generator $generator, $times)
  95. {
  96. $ratio = $this->doTestGenerate($generator, $times);
  97. if ($ratio < 0.8 || $ratio > 1.2) {
  98. $ratio2 = $this->doTestGenerate($generator, $times);
  99. if ($ratio2 > 1.2 || $ratio2 < 0.8) {
  100. $this->fail(
  101. sprintf(
  102. 'The test failed multiple runs with final ratios %f and %f',
  103. $ratio,
  104. $ratio2
  105. )
  106. );
  107. }
  108. }
  109. }
  110. protected function doTestGenerate(\RandomLib\Generator $generator, $times)
  111. {
  112. $inside = 0;
  113. $outside = 0;
  114. $on = 0;
  115. for ($i = 0; $i < $times; $i++) {
  116. $byte = $generator->generate(2);
  117. $byte = unpack('n', $byte);
  118. $byte = array_shift($byte);
  119. $xCoord = ($byte >> 8);
  120. $yCoord = ($byte & 0xFF);
  121. if ($xCoord < $yCoord) {
  122. $outside++;
  123. } elseif ($xCoord == $yCoord) {
  124. $on++;
  125. } else {
  126. $inside++;
  127. }
  128. }
  129. $this->assertGreaterThan(0, $outside, 'Outside Is 0');
  130. $this->assertGreaterThan(0, $inside, 'Inside Is 0');
  131. $ratio = $inside / $outside;
  132. return $ratio;
  133. }
  134. public function getGenerator($random)
  135. {
  136. $source1 = new Source(array(
  137. 'generate' => function ($size) use (&$random) {
  138. $ret = pack('N', $random);
  139. $random--;
  140. return substr($ret, -1 * $size);
  141. },
  142. ));
  143. $sources = array($source1);
  144. $mixer = new Mixer(array(
  145. 'mix'=> function (array $sources) {
  146. if (empty($sources)) {
  147. return '';
  148. }
  149. return array_pop($sources);
  150. },
  151. ));
  152. return new Generator($sources, $mixer);
  153. }
  154. }