Completed
Push — master ( 3d39f9...d9b5c0 )
by Anthony
07:02 queued 04:32
created

Vectors_Random_GeneratorTest::doTestGenerate()   B

Complexity

Conditions 4
Paths 4

Size

Total Lines 23
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Importance

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