1<?php
2/*
3 * This file is part of the php-code-coverage package.
4 *
5 * (c) Sebastian Bergmann <sebastian@phpunit.de>
6 *
7 * For the full copyright and license information, please view the LICENSE
8 * file that was distributed with this source code.
9 */
10
11namespace SebastianBergmann\CodeCoverage;
12
13use SebastianBergmann\CodeCoverage\Driver\PHPDBG;
14use SebastianBergmann\CodeCoverage\Driver\Xdebug;
15
16/**
17 * @covers SebastianBergmann\CodeCoverage\CodeCoverage
18 */
19class CodeCoverageTest extends TestCase
20{
21    /**
22     * @var CodeCoverage
23     */
24    private $coverage;
25
26    protected function setUp()
27    {
28        $this->coverage = new CodeCoverage;
29    }
30
31    public function testCanBeConstructedForXdebugWithoutGivenFilterObject()
32    {
33        if (PHP_SAPI == 'phpdbg') {
34            $this->markTestSkipped('Requires PHP CLI and Xdebug');
35        }
36
37        $this->assertAttributeInstanceOf(
38            Xdebug::class,
39            'driver',
40            $this->coverage
41        );
42
43        $this->assertAttributeInstanceOf(
44            Filter::class,
45            'filter',
46            $this->coverage
47        );
48    }
49
50    public function testCanBeConstructedForXdebugWithGivenFilterObject()
51    {
52        if (PHP_SAPI == 'phpdbg') {
53            $this->markTestSkipped('Requires PHP CLI and Xdebug');
54        }
55
56        $filter   = new Filter;
57        $coverage = new CodeCoverage(null, $filter);
58
59        $this->assertAttributeInstanceOf(
60            Xdebug::class,
61            'driver',
62            $coverage
63        );
64
65        $this->assertSame($filter, $coverage->filter());
66    }
67
68    public function testCanBeConstructedForPhpdbgWithoutGivenFilterObject()
69    {
70        if (PHP_SAPI != 'phpdbg') {
71            $this->markTestSkipped('Requires PHPDBG');
72        }
73
74        $this->assertAttributeInstanceOf(
75            PHPDBG::class,
76            'driver',
77            $this->coverage
78        );
79
80        $this->assertAttributeInstanceOf(
81            Filter::class,
82            'filter',
83            $this->coverage
84        );
85    }
86
87    public function testCanBeConstructedForPhpdbgWithGivenFilterObject()
88    {
89        if (PHP_SAPI != 'phpdbg') {
90            $this->markTestSkipped('Requires PHPDBG');
91        }
92
93        $filter   = new Filter;
94        $coverage = new CodeCoverage(null, $filter);
95
96        $this->assertAttributeInstanceOf(
97            PHPDBG::class,
98            'driver',
99            $coverage
100        );
101
102        $this->assertSame($filter, $coverage->filter());
103    }
104
105    /**
106     * @expectedException SebastianBergmann\CodeCoverage\Exception
107     */
108    public function testCannotStartWithInvalidArgument()
109    {
110        $this->coverage->start(null, null);
111    }
112
113    /**
114     * @expectedException SebastianBergmann\CodeCoverage\Exception
115     */
116    public function testCannotStopWithInvalidFirstArgument()
117    {
118        $this->coverage->stop(null);
119    }
120
121    /**
122     * @expectedException SebastianBergmann\CodeCoverage\Exception
123     */
124    public function testCannotStopWithInvalidSecondArgument()
125    {
126        $this->coverage->stop(true, null);
127    }
128
129    /**
130     * @expectedException SebastianBergmann\CodeCoverage\Exception
131     */
132    public function testCannotAppendWithInvalidArgument()
133    {
134        $this->coverage->append([], null);
135    }
136
137    /**
138     * @expectedException SebastianBergmann\CodeCoverage\Exception
139     */
140    public function testSetCacheTokensThrowsExceptionForInvalidArgument()
141    {
142        $this->coverage->setCacheTokens(null);
143    }
144
145    public function testSetCacheTokens()
146    {
147        $this->coverage->setCacheTokens(true);
148        $this->assertAttributeEquals(true, 'cacheTokens', $this->coverage);
149    }
150
151    /**
152     * @expectedException SebastianBergmann\CodeCoverage\Exception
153     */
154    public function testSetCheckForUnintentionallyCoveredCodeThrowsExceptionForInvalidArgument()
155    {
156        $this->coverage->setCheckForUnintentionallyCoveredCode(null);
157    }
158
159    public function testSetCheckForUnintentionallyCoveredCode()
160    {
161        $this->coverage->setCheckForUnintentionallyCoveredCode(true);
162        $this->assertAttributeEquals(
163            true,
164            'checkForUnintentionallyCoveredCode',
165            $this->coverage
166        );
167    }
168
169    /**
170     * @expectedException SebastianBergmann\CodeCoverage\Exception
171     */
172    public function testSetForceCoversAnnotationThrowsExceptionForInvalidArgument()
173    {
174        $this->coverage->setForceCoversAnnotation(null);
175    }
176
177    public function testSetCheckForMissingCoversAnnotation()
178    {
179        $this->coverage->setCheckForMissingCoversAnnotation(true);
180        $this->assertAttributeEquals(
181            true,
182            'checkForMissingCoversAnnotation',
183            $this->coverage
184        );
185    }
186
187    /**
188     * @expectedException SebastianBergmann\CodeCoverage\Exception
189     */
190    public function testSetCheckForMissingCoversAnnotationThrowsExceptionForInvalidArgument()
191    {
192        $this->coverage->setCheckForMissingCoversAnnotation(null);
193    }
194
195    public function testSetForceCoversAnnotation()
196    {
197        $this->coverage->setForceCoversAnnotation(true);
198        $this->assertAttributeEquals(
199            true,
200            'forceCoversAnnotation',
201            $this->coverage
202        );
203    }
204
205    /**
206     * @expectedException SebastianBergmann\CodeCoverage\Exception
207     */
208    public function testSetCheckForUnexecutedCoveredCodeThrowsExceptionForInvalidArgument()
209    {
210        $this->coverage->setCheckForUnexecutedCoveredCode(null);
211    }
212
213    public function testSetCheckForUnexecutedCoveredCode()
214    {
215        $this->coverage->setCheckForUnexecutedCoveredCode(true);
216        $this->assertAttributeEquals(
217            true,
218            'checkForUnexecutedCoveredCode',
219            $this->coverage
220        );
221    }
222
223    /**
224     * @expectedException SebastianBergmann\CodeCoverage\Exception
225     */
226    public function testSetAddUncoveredFilesFromWhitelistThrowsExceptionForInvalidArgument()
227    {
228        $this->coverage->setAddUncoveredFilesFromWhitelist(null);
229    }
230
231    public function testSetAddUncoveredFilesFromWhitelist()
232    {
233        $this->coverage->setAddUncoveredFilesFromWhitelist(true);
234        $this->assertAttributeEquals(
235            true,
236            'addUncoveredFilesFromWhitelist',
237            $this->coverage
238        );
239    }
240
241    /**
242     * @expectedException SebastianBergmann\CodeCoverage\Exception
243     */
244    public function testSetProcessUncoveredFilesFromWhitelistThrowsExceptionForInvalidArgument()
245    {
246        $this->coverage->setProcessUncoveredFilesFromWhitelist(null);
247    }
248
249    public function testSetProcessUncoveredFilesFromWhitelist()
250    {
251        $this->coverage->setProcessUncoveredFilesFromWhitelist(true);
252        $this->assertAttributeEquals(
253            true,
254            'processUncoveredFilesFromWhitelist',
255            $this->coverage
256        );
257    }
258
259    public function testSetIgnoreDeprecatedCode()
260    {
261        $this->coverage->setIgnoreDeprecatedCode(true);
262        $this->assertAttributeEquals(
263            true,
264            'ignoreDeprecatedCode',
265            $this->coverage
266        );
267    }
268
269    /**
270     * @expectedException SebastianBergmann\CodeCoverage\Exception
271     */
272    public function testSetIgnoreDeprecatedCodeThrowsExceptionForInvalidArgument()
273    {
274        $this->coverage->setIgnoreDeprecatedCode(null);
275    }
276
277    public function testClear()
278    {
279        $this->coverage->clear();
280
281        $this->assertAttributeEquals(null, 'currentId', $this->coverage);
282        $this->assertAttributeEquals([], 'data', $this->coverage);
283        $this->assertAttributeEquals([], 'tests', $this->coverage);
284    }
285
286    public function testCollect()
287    {
288        $coverage = $this->getCoverageForBankAccount();
289
290        $this->assertEquals(
291            $this->getExpectedDataArrayForBankAccount(),
292            $coverage->getData()
293        );
294
295        $this->assertEquals(
296            [
297                'BankAccountTest::testBalanceIsInitiallyZero'       => ['size' => 'unknown', 'status' => null],
298                'BankAccountTest::testBalanceCannotBecomeNegative'  => ['size' => 'unknown', 'status' => null],
299                'BankAccountTest::testBalanceCannotBecomeNegative2' => ['size' => 'unknown', 'status' => null],
300                'BankAccountTest::testDepositWithdrawMoney'         => ['size' => 'unknown', 'status' => null]
301            ],
302            $coverage->getTests()
303        );
304    }
305
306    public function testMerge()
307    {
308        $coverage = $this->getCoverageForBankAccountForFirstTwoTests();
309        $coverage->merge($this->getCoverageForBankAccountForLastTwoTests());
310
311        $this->assertEquals(
312            $this->getExpectedDataArrayForBankAccount(),
313            $coverage->getData()
314        );
315    }
316
317    public function testMerge2()
318    {
319        $coverage = new CodeCoverage(
320            $this->createMock(Xdebug::class),
321            new Filter
322        );
323
324        $coverage->merge($this->getCoverageForBankAccount());
325
326        $this->assertEquals(
327            $this->getExpectedDataArrayForBankAccount(),
328            $coverage->getData()
329        );
330    }
331
332    public function testGetLinesToBeIgnored()
333    {
334        $this->assertEquals(
335            [
336                1,
337                3,
338                4,
339                5,
340                7,
341                8,
342                9,
343                10,
344                11,
345                12,
346                13,
347                14,
348                15,
349                16,
350                17,
351                18,
352                19,
353                20,
354                21,
355                22,
356                23,
357                24,
358                25,
359                26,
360                27,
361                28,
362                30,
363                32,
364                33,
365                34,
366                35,
367                36,
368                37,
369                38
370            ],
371            $this->getLinesToBeIgnored()->invoke(
372                $this->coverage,
373                TEST_FILES_PATH . 'source_with_ignore.php'
374            )
375        );
376    }
377
378    public function testGetLinesToBeIgnored2()
379    {
380        $this->assertEquals(
381            [1, 5],
382            $this->getLinesToBeIgnored()->invoke(
383                $this->coverage,
384                TEST_FILES_PATH . 'source_without_ignore.php'
385            )
386        );
387    }
388
389    public function testGetLinesToBeIgnored3()
390    {
391        $this->assertEquals(
392            [
393                1,
394                2,
395                3,
396                4,
397                5,
398                8,
399                11,
400                15,
401                16,
402                19,
403                20
404            ],
405            $this->getLinesToBeIgnored()->invoke(
406                $this->coverage,
407                TEST_FILES_PATH . 'source_with_class_and_anonymous_function.php'
408            )
409        );
410    }
411
412    public function testGetLinesToBeIgnoredOneLineAnnotations()
413    {
414        $this->assertEquals(
415            [
416                1,
417                2,
418                3,
419                4,
420                5,
421                6,
422                7,
423                8,
424                9,
425                10,
426                11,
427                12,
428                13,
429                14,
430                15,
431                16,
432                18,
433                20,
434                21,
435                23,
436                24,
437                25,
438                27,
439                28,
440                29,
441                30,
442                31,
443                32,
444                33,
445                34,
446                37
447            ],
448            $this->getLinesToBeIgnored()->invoke(
449                $this->coverage,
450                TEST_FILES_PATH . 'source_with_oneline_annotations.php'
451            )
452        );
453    }
454
455    /**
456     * @return \ReflectionMethod
457     */
458    private function getLinesToBeIgnored()
459    {
460        $getLinesToBeIgnored = new \ReflectionMethod(
461            'SebastianBergmann\CodeCoverage\CodeCoverage',
462            'getLinesToBeIgnored'
463        );
464
465        $getLinesToBeIgnored->setAccessible(true);
466
467        return $getLinesToBeIgnored;
468    }
469
470    public function testGetLinesToBeIgnoredWhenIgnoreIsDisabled()
471    {
472        $this->coverage->setDisableIgnoredLines(true);
473
474        $this->assertEquals(
475            [],
476            $this->getLinesToBeIgnored()->invoke(
477                $this->coverage,
478                TEST_FILES_PATH . 'source_with_ignore.php'
479            )
480        );
481    }
482
483    /**
484     * @expectedException SebastianBergmann\CodeCoverage\CoveredCodeNotExecutedException
485     */
486    public function testAppendThrowsExceptionIfCoveredCodeWasNotExecuted()
487    {
488        $this->coverage->filter()->addDirectoryToWhitelist(TEST_FILES_PATH);
489        $this->coverage->setCheckForUnexecutedCoveredCode(true);
490
491        $data = [
492            TEST_FILES_PATH . 'BankAccount.php' => [
493                29 => -1,
494                31 => -1
495            ]
496        ];
497
498        $linesToBeCovered = [
499            TEST_FILES_PATH . 'BankAccount.php' => [
500                22,
501                24
502            ]
503        ];
504
505        $linesToBeUsed = [];
506
507        $this->coverage->append($data, 'File1.php', true, $linesToBeCovered, $linesToBeUsed);
508    }
509
510    /**
511     * @expectedException SebastianBergmann\CodeCoverage\CoveredCodeNotExecutedException
512     */
513    public function testAppendThrowsExceptionIfUsedCodeWasNotExecuted()
514    {
515        $this->coverage->filter()->addDirectoryToWhitelist(TEST_FILES_PATH);
516        $this->coverage->setCheckForUnexecutedCoveredCode(true);
517
518        $data = [
519            TEST_FILES_PATH . 'BankAccount.php' => [
520                29 => -1,
521                31 => -1
522            ]
523        ];
524
525        $linesToBeCovered = [
526            TEST_FILES_PATH . 'BankAccount.php' => [
527                29,
528                31
529            ]
530        ];
531
532        $linesToBeUsed = [
533            TEST_FILES_PATH . 'BankAccount.php' => [
534                22,
535                24
536            ]
537        ];
538
539        $this->coverage->append($data, 'File1.php', true, $linesToBeCovered, $linesToBeUsed);
540    }
541}
542