Completed
Push — master ( f7e2e6...df6590 )
by Tomasz
03:07
created

GeneratorTest   A

Complexity

Total Complexity 32

Size/Duplication

Total Lines 362
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 3

Importance

Changes 1
Bugs 0 Features 1
Metric Value
c 1
b 0
f 1
dl 0
loc 362
wmc 32
lcom 1
cbo 3
rs 9.6
1
<?php
2
3
namespace Gendoria\CruftFlake;
4
5
use Gendoria\CruftFlake\Generator\Generator;
6
use OverflowException;
7
use PHPUnit_Framework_TestCase;
8
use UnexpectedValueException;
9
10
class GeneratorTest extends PHPUnit_Framework_TestCase
11
{
12
    private $machineId = 1;
13
    
14
    private $timer;
15
    private $config;
16
    
17
    public function setUp()
18
    {
19
        $this->timer = $this->getMockBuilder('\Gendoria\CruftFlake\Timer\TimerInterface')
20
                            ->disableOriginalConstructor()
21
                            ->getMock();
22
        $this->config = $this->getMockBuilder('\Gendoria\CruftFlake\Config\ConfigInterface')
23
                            ->disableOriginalConstructor()
24
                            ->getMock();
25
    }
26
    
27
    /**
28
     * Get generator for normal tests.
29
     * 
30
     * @return Generator
31
     */
32
    private function buildSystemUnderTest()
33
    {
34
        $this->config->expects($this->once())
35
                     ->method('getMachine')
36
                     ->will($this->returnValue($this->machineId));
37
        return new Generator($this->config, $this->timer);
38
    }
39
    
40
    /**
41
     * Get generator for 32 bit tests.
42
     * 
43
     * @return Generator
44
     */
45
    private function buildSystemUnderTest32Bit()
46
    {
47
        $this->config->expects($this->once())
48
                     ->method('getMachine')
49
                     ->will($this->returnValue($this->machineId));
50
        $generator = $this->getMock('Gendoria\CruftFlake\Generator\Generator', array('is32Bit'), array($this->config, $this->timer));
51
        $generator->expects($this->any())
52
            ->method('is32Bit')
53
            ->will($this->returnValue(true));
54
        return $generator;
55
    }
56
    
57
    /**
58
     * Get generator for normal tests.
59
     * 
60
     * @return Generator
61
     */
62
    private function buildSystemUnderTestHeartbeat($newMachineId)
63
    {
64
        $this->config->expects($this->exactly(2))
65
                     ->method('getMachine')
66
                     ->will($this->onConsecutiveCalls($this->machineId, $newMachineId));
67
        $this->config->expects($this->once())
68
                     ->method('heartbeat')
69
                     ->will($this->returnValue(true));
70
        return new Generator($this->config, $this->timer);
71
    }
72
        
73
    private function assertId($id)
74
    {
75
        $this->assertTrue(is_string($id));
76
        $this->assertTrue(ctype_digit($id));
77
    }
78
    
79
    private function assertReallyNotEquals($v1, $v2)
80
    {
81
        $this->assertTrue($v1 !== $v2);
82
    }
83
    
84
    // ---
85
    
86
    public function testConstructs()
87
    {
88
        $cf = $this->buildSystemUnderTest();
89
        $this->assertInstanceOf('\Gendoria\CruftFlake\Generator\Generator', $cf);
90
    }
91
    
92
    public function testFailsWithBadMachineIdString()
93
    {
94
        $this->setExpectedException('\InvalidArgumentException');
95
        $this->machineId = '1';
96
        $cf = $this->buildSystemUnderTest();
97
    }
98
    
99
    public function testFailsWithBadMachineIdNegative()
100
    {
101
        $this->setExpectedException('\InvalidArgumentException');
102
        $this->machineId = -1;
103
        $cf = $this->buildSystemUnderTest();
104
    }
105
106
    public function testFailsWithBadMachineIdTooBig()
107
    {
108
        $this->setExpectedException('\InvalidArgumentException');
109
        $this->machineId = 1024;
110
        $cf = $this->buildSystemUnderTest();
111
    }
112
113
    public function testFailsWithBadMachineIdFloat()
114
    {
115
        $this->setExpectedException('\InvalidArgumentException');
116
        $this->machineId = 1.1;
117
        $cf = $this->buildSystemUnderTest();
118
    }
119
120
    public function testLargestPossibleMachineId()
121
    {
122
        $this->machineId = 1023;
123
        $cf = $this->buildSystemUnderTest();
124
        $this->assertInstanceOf('\Gendoria\CruftFlake\Generator\Generator', $cf);
125
    }
126
    
127
    public function testGenerate()
128
    {
129
        $this->timer->expects($this->once())
130
                    ->method('getUnixTimestamp')
131
                    ->will($this->returnValue(1341246960000));
132
        $cf = $this->buildSystemUnderTest();
133
        $id = $cf->generate();
134
        $this->assertId($id);
135
    }
136
    
137
    public function testGenerate32bit()
138
    {
139
        $this->timer->expects($this->once())
140
                    ->method('getUnixTimestamp')
141
                    ->will($this->returnValue(1341246960000));
142
        $cf = $this->buildSystemUnderTest32Bit();
143
        $id = $cf->generate();
144
        $this->assertId($id);
145
    }
146
    
147
    
148
    public function testGenerateForPerMillisecondCollisions()
149
    {
150
        $this->timer->expects($this->any())
151
                    ->method('getUnixTimestamp')
152
                    ->will($this->returnValue(1341246960000));
153
        $cf = $this->buildSystemUnderTest();
154
        
155
        $id1 = $cf->generate();
156
        $id2 = $cf->generate();
157
        $id3 = $cf->generate();
158
        
159
        $this->assertId($id1);
160
        $this->assertId($id2);
161
        $this->assertId($id3);
162
        
163
        $this->assertReallyNotEquals($id1, $id2);
164
        $this->assertReallyNotEquals($id2, $id3);
165
    }
166
167
    public function testGenerateAsksForTimeEachTime()
168
    {
169
        $this->timer->expects($this->at(0))
170
                    ->method('getUnixTimestamp')
171
                    ->will($this->returnValue(1341246960000));
172
        $this->timer->expects($this->at(1))
173
                    ->method('getUnixTimestamp')
174
                    ->will($this->returnValue(1341246960001));
175
        $cf = $this->buildSystemUnderTest();
176
        
177
        $id1 = $cf->generate();
178
        $id2 = $cf->generate();
179
        
180
        $this->assertId($id1);
181
        $this->assertId($id2);
182
        
183
        $this->assertReallyNotEquals($id1, $id2);
184
    }
185
    
186
    public function testFailsWithTimestampBeforeEpoch()
187
    {
188
        $this->timer->expects($this->at(0))
189
                    ->method('getUnixTimestamp')
190
                    ->will($this->returnValue(1325375999999));
191
        $cf = $this->buildSystemUnderTest();
192
        
193
        $e = NULL;
194
        try {
195
            $id1 = $cf->generate();
196
        } catch (UnexpectedValueException $e) {
197
            
198
        }
199
        $this->assertInstanceOf('\UnexpectedValueException', $e);
200
        $this->assertEquals('Time is currently set before our epoch - unable to generate IDs for 1 milliseconds', $e->getMessage());
201
    }
202
    
203
    public function testFailsIfTimeRunsBackwards()
204
    {
205
        //$this->setExpectedException('\UnexpectedValueException');
206
        
207
        $this->timer->expects($this->at(0))
208
                    ->method('getUnixTimestamp')
209
                    ->will($this->returnValue(1341246960001));
210
        $this->timer->expects($this->at(1))
211
                    ->method('getUnixTimestamp')
212
                    ->will($this->returnValue(1341246960000));
213
        $cf = $this->buildSystemUnderTest();
214
        
215
        $id1 = $cf->generate();
216
        
217
        $e = NULL;
218
        try {
219
            $id2 = $cf->generate();
220
        } catch (UnexpectedValueException $e) {
221
            
222
        }
223
        $this->assertInstanceOf('\UnexpectedValueException', $e);
224
        $this->assertEquals('Time moved backwards. We cannot generate IDs for 1 milliseconds', $e->getMessage());
225
    }
226
    
227
    public function testFullSequenceRange()
228
    {
229
        $this->timer->expects($this->any())
230
                    ->method('getUnixTimestamp')
231
                    ->will($this->returnValue(1341246960000));
232
        $cf = $this->buildSystemUnderTest();
233
        
234
        $ids = array();
235
        for ($i=0; $i<4095; $i++) {
236
            $id = $cf->generate();
237
            $ids[$id] = 1;
238
        }
239
        
240
        $this->assertEquals(4095, count($ids));
241
    }
242
    
243
    public function testFailsIfSequenceOverflow()
244
    {
245
        $this->timer->expects($this->any())
246
                    ->method('getUnixTimestamp')
247
                    ->will($this->returnValue(1341246960000));
248
        $cf = $this->buildSystemUnderTest();
249
        
250
        $ids = array();
251
        for ($i=0; $i<4096; $i++) {
252
            $id = $cf->generate();
253
            $ids[$id] = 1;
254
        }
255
        
256
        $e = NULL;
257
        try {
258
            $id2 = $cf->generate();
259
        } catch (OverflowException $e) {
260
            
261
        }
262
        $this->assertInstanceOf('\OverflowException', $e);
263
        $this->assertEquals('Sequence overflow (too many IDs generated) - unable to generate IDs for 1 milliseconds', $e->getMessage());
264
    }
265
    
266
    public function testSmallestTimestampId()
267
    {
268
        $this->machineId = 0;
269
        $this->timer->expects($this->any())
270
                    ->method('getUnixTimestamp')
271
                    ->will($this->returnValue(1325376000000));
272
        $cf = $this->buildSystemUnderTest();
273
        
274
        $id1 = $cf->generate();
275
        $id2 = $cf->generate();
276
        
277
        $this->assertId($id1);
278
        $this->assertId($id2);
279
        $this->assertReallyNotEquals($id1, $id2);
280
        
281
        $this->assertEquals(0, $id1);
282
    }
283
    
284
    public function testSmallestTimestampWithMachine()
285
    {
286
        $this->timer->expects($this->any())
287
                    ->method('getUnixTimestamp')
288
                    ->will($this->returnValue(1325376000000));
289
        $cf = $this->buildSystemUnderTest();
290
        
291
        $id1 = $cf->generate();
292
        $id2 = $cf->generate();
293
        
294
        $this->assertId($id1);
295
        $this->assertId($id2);
296
        $this->assertReallyNotEquals($id1, $id2);
297
        
298
        $this->assertEquals(1 << 12, $id1);
299
        $this->assertEquals(1 << 12 | 1, $id2);
300
    }
301
302
    public function testSmallTimestampWithMachine()
303
    {
304
        $this->timer->expects($this->any())
305
                    ->method('getUnixTimestamp')
306
                    ->will($this->returnValue(1325376000001));
307
        $cf = $this->buildSystemUnderTest();
308
        
309
        $id1 = $cf->generate();
310
        $id2 = $cf->generate();
311
        
312
        $this->assertId($id1);
313
        $this->assertId($id2);
314
        $this->assertReallyNotEquals($id1, $id2);
315
        
316
        $this->assertEquals(1 << 22 | 1 << 12, $id1);
317
        $this->assertEquals(1 << 22 | 1 << 12 | 1, $id2);
318
    }
319
    
320
    public function testFailsOnTimestampOverflow()
321
    {
322
        $this->timer->expects($this->any())
323
                    ->method('getUnixTimestamp')
324
                    ->will($this->returnValue(3524399255552));
325
        $cf = $this->buildSystemUnderTest();
326
        $e = NULL;
327
        try {
328
            $id2 = $cf->generate();
329
        } catch (OverflowException $e) {
330
            
331
        }
332
        $this->assertInstanceOf('\OverflowException', $e);
333
        $this->assertEquals('Timestamp overflow (past end of lifespan) - unable to generate any more IDs', $e->getMessage());
334
    }
335
    
336
    public function testLargestTimestampWithLargestEverythingElse()
337
    {
338
        $this->machineId = 1023;
339
        $this->timer->expects($this->any())
340
                    ->method('getUnixTimestamp')
341
                    ->will($this->returnValue(3524399255551));
342
        $cf = $this->buildSystemUnderTest();
343
        
344
        $id1 = $cf->generate();
345
        $id2 = $cf->generate();
346
        
347
        $this->assertId($id1);
348
        $this->assertId($id2);
349
        $this->assertReallyNotEquals($id1, $id2);
350
        $this->assertEquals('9223372036854771712', $id1);
351
    }
352
    
353
    public function testHeartbeat()
354
    {
355
        $this->machineId = 1;
356
        $this->timer->expects($this->any())
357
                    ->method('getUnixTimestamp')
358
                    ->will($this->returnValue(1325376000000));
359
        $cf = $this->buildSystemUnderTestHeartbeat(2);
360
        
361
        //Test. First ID, heartbeat changing machine ID to 2, second ID
362
        $id1 = $cf->generate();
363
        $cf->heartbeat();
364
        $id2 = $cf->generate();
365
        
366
        $expectedId1 = $value = (0 << 22) | (1 << 12) | 0; //timestamp 0, machine 1, sequence 0
367
        $expectedId2 = $value = (0 << 22) | (2 << 12) | 1; //timestamp 0, machine 2, sequence 1
368
        $this->assertEquals($expectedId1, $id1);
369
        $this->assertEquals($expectedId2, $id2);
370
    }
371
}
372