Passed
Push — master ( cf8677...e90ce7 )
by Kyle
02:51 queued 11s
created

AbstractTest::getViolationFailureMessage()   A

Complexity

Conditions 4
Paths 8

Size

Total Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
nc 8
nop 4
dl 0
loc 10
rs 9.9332
c 0
b 0
f 0
1
<?php
2
/**
3
 * This file is part of PHP Mess Detector.
4
 *
5
 * Copyright (c) Manuel Pichler <[email protected]>.
6
 * All rights reserved.
7
 *
8
 * Licensed under BSD License
9
 * For full copyright and license information, please see the LICENSE file.
10
 * Redistributions of files must retain the above copyright notice.
11
 *
12
 * @author Manuel Pichler <[email protected]>
13
 * @copyright Manuel Pichler. All rights reserved.
14
 * @license https://opensource.org/licenses/bsd-license.php BSD License
15
 * @link http://phpmd.org/
16
 */
17
18
namespace PHPMD;
19
20
use ErrorException;
21
use Iterator;
22
use PDepend\Source\AST\ASTClass;
23
use PDepend\Source\AST\ASTFunction;
24
use PDepend\Source\AST\ASTMethod;
25
use PDepend\Source\AST\ASTNamespace;
26
use PDepend\Source\Language\PHP\PHPBuilder;
27
use PDepend\Source\Language\PHP\PHPParserGeneric;
28
use PDepend\Source\Language\PHP\PHPTokenizerInternal;
29
use PDepend\Util\Cache\Driver\MemoryCacheDriver;
30
use PHPMD\Node\ClassNode;
31
use PHPMD\Node\FunctionNode;
32
use PHPMD\Node\InterfaceNode;
33
use PHPMD\Node\MethodNode;
34
use PHPMD\Node\TraitNode;
35
use PHPMD\Rule\Design\TooManyFields;
36
use PHPMD\Stubs\RuleStub;
37
use PHPUnit_Framework_ExpectationFailedException;
38
use PHPUnit_Framework_MockObject_MockBuilder;
39
use PHPUnit_Framework_MockObject_MockObject;
40
use ReflectionProperty;
41
use Traversable;
42
43
/**
44
 * Abstract base class for PHPMD test cases.
45
 */
46
abstract class AbstractTest extends AbstractStaticTest
47
{
48
    /** @var int At least one violation is expected */
49
    const AL_LEAST_ONE_VIOLATION = -1;
50
51
    /** @var int No violation is expected */
52
    const NO_VIOLATION = 0;
53
54
    /** @var int One violation is expected */
55
    const ONE_VIOLATION = 1;
56
57
    /**
58
     * Get a list of files that should trigger a rule violation.
59
     *
60
     * By default, files named like "testRuleAppliesTo*", but it can be overridden in sub-classes.
61
     *
62
     * @return string[]
63
     */
64
    public function getApplyingFiles()
65
    {
66
        return $this->getFilesForCalledClass('testRuleAppliesTo*');
67
    }
68
69
    /**
70
     * Get a list of files that should not trigger a rule violation.
71
     *
72
     * By default, files named like "testRuleDoesNotApplyTo*", but it can be overridden in sub-classes.
73
     *
74
     * @return string[]
75
     */
76
    public function getNotApplyingFiles()
77
    {
78
        return $this->getFilesForCalledClass('testRuleDoesNotApplyTo*');
79
    }
80
81
    /**
82
     * Get a list of test files specified by getApplyingFiles() as an array of 1-length arguments lists.
83
     *
84
     * @return string[][]
85
     */
86
    public function getApplyingCases()
87
    {
88
        return static::getValuesAsArrays($this->getApplyingFiles());
89
    }
90
91
    /**
92
     * Get a list of test files specified by getNotApplyingFiles() as an array of 1-length arguments lists.
93
     *
94
     * @return string[][]
95
     */
96
    public function getNotApplyingCases()
97
    {
98
        return static::getValuesAsArrays($this->getNotApplyingFiles());
99
    }
100
101
    /**
102
     * Resets a changed working directory.
103
     *
104
     * @return void
105
     */
106
    protected function tearDown()
107
    {
108
        static::returnToOriginalWorkingDirectory();
109
        static::cleanupTempFiles();
110
111
        parent::tearDown();
112
    }
113
114
    /**
115
     * Returns the first class found in a source file related to the calling
116
     * test method.
117
     *
118
     * @return ClassNode
119
     */
120
    protected function getClass()
121
    {
122
        return new ClassNode(
123
            $this->getNodeForCallingTestCase(
124
                $this->parseTestCaseSource()->getClasses()
125
            )
126
        );
127
    }
128
129
    /**
130
     * Returns the first interface found in a source file related to the calling
131
     * test method.
132
     *
133
     * @return InterfaceNode
134
     */
135
    protected function getInterface()
136
    {
137
        return new InterfaceNode(
138
            $this->getNodeForCallingTestCase(
139
                $this->parseTestCaseSource()->getInterfaces()
140
            )
141
        );
142
    }
143
144
    /**
145
     * @return TraitNode
146
     */
147
    protected function getTrait()
148
    {
149
        return new TraitNode(
150
            $this->getNodeForCallingTestCase(
151
                $this->parseTestCaseSource()->getTraits()
152
            )
153
        );
154
    }
155
156
    /**
157
     * Returns the first method found in a source file related to the calling
158
     * test method.
159
     *
160
     * @return MethodNode
161
     */
162
    protected function getMethod()
163
    {
164
        return new MethodNode(
165
            $this->getNodeForCallingTestCase(
166
                $this->parseTestCaseSource()
167
                    ->getTypes()
168
                    ->current()
169
                    ->getMethods()
170
            )
171
        );
172
    }
173
174
    /**
175
     * Returns the first function found in a source files related to the calling
176
     * test method.
177
     *
178
     * @return FunctionNode
179
     */
180
    protected function getFunction()
181
    {
182
        return new FunctionNode(
183
            $this->getNodeForCallingTestCase(
184
                $this->parseTestCaseSource()->getFunctions()
185
            )
186
        );
187
    }
188
189
    /**
190
     * Returns the first class found for a given test file.
191
     *
192
     * @return ClassNode
193
     */
194
    protected function getClassNodeForTestFile($file)
195
    {
196
        return new ClassNode(
197
            $this->parseSource($file)
198
                ->getTypes()
199
                ->current()
200
        );
201
    }
202
203
    /**
204
     * Returns the first method or function node for a given test file.
205
     *
206
     * @param string $file
207
     * @return MethodNode|FunctionNode
208
     * @since 2.8.3
209
     */
210
    protected function getNodeForTestFile($file)
211
    {
212
        $source = $this->parseSource($file);
213
        $class = $source
214
            ->getTypes()
215
            ->current();
216
        $nodeClassName = 'PHPMD\\Node\\FunctionNode';
217
        $getter = 'getFunctions';
218
219
        if ($class) {
220
            $source = $class;
221
            $nodeClassName = 'PHPMD\\Node\\MethodNode';
222
            $getter = 'getMethods';
223
        }
224
225
        return new $nodeClassName(
226
            $this->getNodeByName(
227
                $source->$getter(),
228
                pathinfo($file, PATHINFO_FILENAME)
229
            )
230
        );
231
    }
232
233
    /**
234
     * Assert that a given file trigger N times the given rule.
235
     *
236
     * Rethrows the PHPUnit ExpectationFailedException with the base name
237
     * of the file for better readability.
238
     *
239
     * @param Rule $rule Rule to test.
240
     * @param int $expectedInvokes Count of expected invocations.
241
     * @param string $file Test file containing a method with the same name to be tested.
242
     */
243
    protected function expectRuleHasViolationsForFile(Rule $rule, $expectedInvokes, $file)
244
    {
245
        $report = new Report();
246
        $rule->setReport($report);
247
        $rule->apply($this->getNodeForTestFile($file));
248
        $violations = $report->getRuleViolations();
249
        $actualInvokes = count($violations);
250
        $assertion = $expectedInvokes === self::AL_LEAST_ONE_VIOLATION
251
            ? $actualInvokes > 0
252
            : $actualInvokes === $expectedInvokes;
253
254
        if (!$assertion) {
255
            throw new PHPUnit_Framework_ExpectationFailedException(
256
                $this->getViolationFailureMessage($file, $actualInvokes, $expectedInvokes, $violations)
257
            );
258
        }
259
260
        $this->assertTrue($assertion);
261
    }
262
263
    /**
264
     * Return a human-friendly failure message for a given list of violations and the actual/expected counts.
265
     *
266
     * @param string $file
267
     * @param int $expectedInvokes
268
     * @param int $actualInvokes
269
     * @param array|iterable|Traversable $violations
270
     *
271
     * @return string
272
     */
273
    protected function getViolationFailureMessage($file, $expectedInvokes, $actualInvokes, $violations)
274
    {
275
        return basename($file)." failed:\n".
276
            "Expected $expectedInvokes violation".($expectedInvokes !== 1 ? 's' : '')."\n".
277
            "But $actualInvokes violation".($actualInvokes !== 1 ? 's' : '')." raised".
278
            ($actualInvokes > 0
279
                ? ":\n".$this->getViolationsSummary($violations)
280
                : '.'
281
            );
282
    }
283
284
    /**
285
     * Return a human-friendly summary for a list of violations.
286
     *
287
     * @param array|iterable|Traversable $violations
288
     * @return string
289
     */
290
    protected function getViolationsSummary($violations)
291
    {
292
        if (!is_array($violations)) {
293
            $violations = iterator_to_array($violations);
294
        }
295
296
        return implode("\n", array_map(function (RuleViolation $violation) {
297
            $nodeExtractor = new ReflectionProperty('PHPMD\\RuleViolation', 'node');
298
            $nodeExtractor->setAccessible(true);
299
            $node = $nodeExtractor->getValue($violation);
300
            $node = $node ? $node->getNode() : null;
301
            $message = '  - line '.$violation->getBeginLine();
302
303
            if ($node) {
304
                $type = preg_replace('/^PDepend\\\\Source\\\\AST\\\\AST/', '', get_class($node));
305
                $message .= ' on '.$type.' '.$node->getImage();
306
            }
307
308
            return $message;
309
        }, $violations));
310
    }
311
312
    /**
313
     * Returns the absolute path for a test resource for the current test.
314
     *
315
     * @return string
316
     * @since 1.1.0
317
     */
318
    protected static function createCodeResourceUriForTest()
319
    {
320
        $frame = static::getCallingTestCase();
321
322
        return self::createResourceUriForTest($frame['function'] . '.php');
323
    }
324
325
    /**
326
     * Returns the absolute path for a test resource for the current test.
327
     *
328
     * @param string $localPath The local/relative file location
329
     * @return string
330
     * @since 1.1.0
331
     */
332
    protected static function createResourceUriForTest($localPath)
333
    {
334
        $frame = static::getCallingTestCase();
335
336
        return static::getResourceFilePathFromClassName($frame['class'], $localPath);
337
    }
338
339
    /**
340
     * Return URI for a given pattern with directory based on the current called class name.
341
     *
342
     * @param string $pattern
343
     * @return string
344
     */
345
    protected function createResourceUriForCalledClass($pattern)
346
    {
347
        return $this->getResourceFilePathFromClassName(get_class($this), $pattern);
348
    }
349
350
    /**
351
     * Return list of files matching a given pattern with directory based on the current called class name.
352
     *
353
     * @param string $pattern
354
     * @return string[]
355
     */
356
    protected function getFilesForCalledClass($pattern = '*')
357
    {
358
        return glob($this->createResourceUriForCalledClass($pattern));
359
    }
360
361
    /**
362
     * Creates a mocked class node instance.
363
     *
364
     * @param string $metric
365
     * @param integer $value
366
     * @return ClassNode
367
     */
368
    protected function getClassMock($metric = null, $value = null)
369
    {
370
        $class = $this->getMockFromBuilder(
371
            $this->getMockBuilder('PHPMD\\Node\\ClassNode')
372
                ->setConstructorArgs(array(new ASTClass('FooBar')))
373
        );
374
375
        if ($metric !== null) {
376
            $class->expects($this->atLeastOnce())
377
                ->method('getMetric')
378
                ->with($this->equalTo($metric))
379
                ->willReturn($value);
380
        }
381
382
        return $class;
383
    }
384
385
    /**
386
     * Creates a mocked method node instance.
387
     *
388
     * @param string $metric
389
     * @param integer $value
390
     * @return MethodNode
391
     */
392
    protected function getMethodMock($metric = null, $value = null)
393
    {
394
        return $this->createFunctionOrMethodMock('PHPMD\\Node\\MethodNode', new ASTMethod('fooBar'), $metric, $value);
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->createFunc...ar'), $metric, $value); (PHPMD\Node\FunctionNode|...k_MockObject_MockObject) is incompatible with the return type documented by PHPMD\AbstractTest::getMethodMock of type PHPMD\Node\MethodNode.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
395
    }
396
397
    /**
398
     * Creates a mocked function node instance.
399
     *
400
     * @param string $metric The metric acronym used by PHP_Depend.
401
     * @param mixed $value The expected metric return value.
402
     * @return FunctionNode
403
     */
404
    protected function createFunctionMock($metric = null, $value = null)
405
    {
406
        return $this->createFunctionOrMethodMock(
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->createFunc...ar'), $metric, $value); (PHPMD\Node\FunctionNode|...k_MockObject_MockObject) is incompatible with the return type documented by PHPMD\AbstractTest::createFunctionMock of type PHPMD\Node\FunctionNode.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
407
            'PHPMD\\Node\\FunctionNode',
408
            new ASTFunction('fooBar'),
409
            $metric,
410
            $value
411
        );
412
    }
413
414
    /**
415
     * Initializes the getMetric() method of the given function or method node.
416
     *
417
     * @param FunctionNode|MethodNode|PHPUnit_Framework_MockObject_MockObject $mock
418
     * @param string $metric
419
     * @param mixed $value
420
     * @return FunctionNode|MethodNode
421
     */
422
    protected function initFunctionOrMethod($mock, $metric, $value)
423
    {
424
        if ($metric === null) {
425
            return $mock;
426
        }
427
428
        $mock->expects($this->atLeastOnce())
0 ignored issues
show
Bug introduced by
The method expects does only exist in PHPUnit_Framework_MockObject_MockObject, but not in PHPMD\Node\FunctionNode and PHPMD\Node\MethodNode.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
429
            ->method('getMetric')
430
            ->with($this->equalTo($metric))
431
            ->willReturn($value);
432
433
        return $mock;
434
    }
435
436
    /**
437
     * Creates a mocked report instance.
438
     *
439
     * @param integer $expectedInvokes Number of expected invokes.
440
     * @return Report|PHPUnit_Framework_MockObject_MockObject
441
     */
442
    protected function getReportMock($expectedInvokes = -1)
443
    {
444
        if ($expectedInvokes === self::AL_LEAST_ONE_VIOLATION) {
445
            $expects = $this->atLeastOnce();
446
        } elseif ($expectedInvokes === self::NO_VIOLATION) {
447
            $expects = $this->never();
448
        } elseif ($expectedInvokes === self::ONE_VIOLATION) {
449
            $expects = $this->once();
450
        } else {
451
            $expects = $this->exactly($expectedInvokes);
452
        }
453
454
        $report = $this->getMockFromBuilder($this->getMockBuilder('PHPMD\\Report'));
455
        $report->expects($expects)
456
            ->method('addRuleViolation');
457
458
        return $report;
459
    }
460
461
    /**
462
     * Get a mocked report with one violation
463
     *
464
     * @return Report
465
     */
466
    public function getReportWithOneViolation()
467
    {
468
        return $this->getReportMock(self::ONE_VIOLATION);
0 ignored issues
show
Bug Compatibility introduced by
The expression $this->getReportMock(self::ONE_VIOLATION); of type PHPMD\Report|PHPUnit_Fra...k_MockObject_MockObject adds the type PHPUnit_Framework_MockObject_MockObject to the return on line 468 which is incompatible with the return type documented by PHPMD\AbstractTest::getReportWithOneViolation of type PHPMD\Report.
Loading history...
469
    }
470
471
    /**
472
     * Get a mocked report with no violation
473
     *
474
     * @return Report
475
     */
476
    public function getReportWithNoViolation()
477
    {
478
        return $this->getReportMock(self::NO_VIOLATION);
0 ignored issues
show
Bug Compatibility introduced by
The expression $this->getReportMock(self::NO_VIOLATION); of type PHPMD\Report|PHPUnit_Fra...k_MockObject_MockObject adds the type PHPUnit_Framework_MockObject_MockObject to the return on line 478 which is incompatible with the return type documented by PHPMD\AbstractTest::getReportWithNoViolation of type PHPMD\Report.
Loading history...
479
    }
480
481
    /**
482
     * Get a mocked report with at least one violation
483
     *
484
     * @return Report
485
     */
486
    public function getReportWithAtLeastOneViolation()
487
    {
488
        return $this->getReportMock(self::AL_LEAST_ONE_VIOLATION);
0 ignored issues
show
Bug Compatibility introduced by
The expression $this->getReportMock(sel...L_LEAST_ONE_VIOLATION); of type PHPMD\Report|PHPUnit_Fra...k_MockObject_MockObject adds the type PHPUnit_Framework_MockObject_MockObject to the return on line 488 which is incompatible with the return type documented by PHPMD\AbstractTest::getR...WithAtLeastOneViolation of type PHPMD\Report.
Loading history...
489
    }
490
491
    protected function getMockFromBuilder(PHPUnit_Framework_MockObject_MockBuilder $builder)
492
    {
493
        if (version_compare(PHP_VERSION, '7.4.0-dev', '<')) {
494
            return $builder->getMock();
495
        }
496
497
        return @$builder->getMock();
498
    }
499
500
    /**
501
     * Creates a mocked {@link \PHPMD\AbstractRule} instance.
502
     *
503
     * @return AbstractRule|PHPUnit_Framework_MockObject_MockObject
504
     */
505
    protected function getRuleMock()
506
    {
507
        if (version_compare(PHP_VERSION, '7.4.0-dev', '<')) {
508
            return $this->getMockForAbstractClass('PHPMD\\AbstractRule');
509
        }
510
511
        return @$this->getMockForAbstractClass('PHPMD\\AbstractRule');
512
    }
513
514
    /**
515
     * Creates a mocked rule-set instance.
516
     *
517
     * @param string $expectedClass Optional class name for apply() expected at least once.
518
     * @param int|string $count How often should apply() be called?
519
     * @return RuleSet|PHPUnit_Framework_MockObject_MockObject
520
     */
521
    protected function getRuleSetMock($expectedClass = null, $count = '*')
522
    {
523
        $ruleSet = $this->getMockFromBuilder($this->getMockBuilder('PHPMD\RuleSet'));
524
        if ($expectedClass === null) {
525
            $ruleSet->expects($this->never())->method('apply');
526
527
            return $ruleSet;
528
        }
529
530
        if ($count === '*') {
531
            $count = $this->atLeastOnce();
532
        } else {
533
            $count = $this->exactly($count);
534
        }
535
536
        $ruleSet->expects($count)
537
            ->method('apply')
538
            ->with($this->isInstanceOf($expectedClass));
539
540
        return $ruleSet;
541
    }
542
543
    /**
544
     * Creates a mocked rule violation instance.
545
     *
546
     * @param string $fileName The filename to use.
547
     * @param integer $beginLine The begin of violation line number to use.
548
     * @param integer $endLine The end of violation line number to use.
549
     * @param null|object $rule The rule object to use.
550
     * @param null|string $description The violation description to use.
551
     * @return PHPUnit_Framework_MockObject_MockObject
552
     */
553
    protected function getRuleViolationMock(
554
        $fileName = '/foo/bar.php',
555
        $beginLine = 23,
556
        $endLine = 42,
557
        $rule = null,
558
        $description = null
559
    ) {
560
        $ruleViolation = $this->getMockFromBuilder(
561
            $this->getMockBuilder('PHPMD\\RuleViolation')
562
                ->setConstructorArgs(array(new TooManyFields(), new FunctionNode(new ASTFunction('fooBar')), 'Hello'))
563
        );
564
565
        if ($rule === null) {
566
            $rule = new RuleStub();
567
        }
568
569
        if ($description === null) {
570
            $description = 'Test description';
571
        }
572
573
        $ruleViolation
574
            ->method('getRule')
575
            ->willReturn($rule);
576
        $ruleViolation
577
            ->method('getFileName')
578
            ->willReturn($fileName);
579
        $ruleViolation
580
            ->method('getBeginLine')
581
            ->willReturn($beginLine);
582
        $ruleViolation
583
            ->method('getEndLine')
584
            ->willReturn($endLine);
585
        $ruleViolation
586
            ->method('getNamespaceName')
587
            ->willReturn('TestStubPackage');
588
        $ruleViolation
589
            ->method('getDescription')
590
            ->willReturn($description);
591
592
        return $ruleViolation;
593
    }
594
595
    /**
596
     * Creates a mocked rul violation instance.
597
     *
598
     * @param string $file
599
     * @param string $message
600
     * @return ProcessingError|PHPUnit_Framework_MockObject_MockObject
601
     */
602
    protected function getErrorMock(
603
        $file = '/foo/baz.php',
604
        $message = 'Error in file "/foo/baz.php"'
605
    ) {
606
607
        $processingError = $this->getMockFromBuilder(
608
            $this->getMockBuilder('PHPMD\\ProcessingError')
609
                ->setConstructorArgs(array(null))
610
                ->setMethods(array('getFile', 'getMessage'))
611
        );
612
613
        $processingError
614
            ->method('getFile')
615
            ->willReturn($file);
616
        $processingError
617
            ->method('getMessage')
618
            ->willReturn($message);
619
620
        return $processingError;
621
    }
622
623
    /**
624
     * Parses the source code for the calling test method and returns the first
625
     * package node found in the parsed file.
626
     *
627
     * @return ASTNamespace
628
     */
629
    private function parseTestCaseSource()
630
    {
631
        return $this->parseSource($this->createCodeResourceUriForTest());
632
    }
633
634
    /**
635
     * @param string $mockBuilder
636
     * @param ASTFunction|ASTMethod $mock
637
     * @param string $metric The metric acronym used by PHP_Depend.
638
     * @param mixed $value The expected metric return value.
639
     * @return FunctionNode|MethodNode
640
     */
641
    private function createFunctionOrMethodMock($mockBuilder, $mock, $metric = null, $value = null)
642
    {
643
        return $this->initFunctionOrMethod(
644
            $this->getMockFromBuilder(
645
                $this->getMockBuilder($mockBuilder)
646
                    ->setConstructorArgs(array($mock))
647
            ),
648
            $metric,
649
            $value
650
        );
651
    }
652
653
    /**
654
     * Returns the PHP_Depend node having the given name.
655
     *
656
     * @param Iterator $nodes
657
     * @return PHP_Depend_Code_AbstractItem
658
     * @throws ErrorException
659
     */
660
    private function getNodeByName(Iterator $nodes, $name)
661
    {
662
        foreach ($nodes as $node) {
663
            if ($node->getName() === $name) {
664
                return $node;
665
            }
666
        }
667
        throw new ErrorException("Cannot locate node named $name.");
668
    }
669
670
    /**
671
     * Returns the PHP_Depend node for the calling test case.
672
     *
673
     * @param Iterator $nodes
674
     * @return PHP_Depend_Code_AbstractItem
675
     * @throws ErrorException
676
     */
677
    private function getNodeForCallingTestCase(Iterator $nodes)
678
    {
679
        $frame = $this->getCallingTestCase();
680
681
        return $this->getNodeByName($nodes, $frame['function']);
682
    }
683
684
    /**
685
     * Parses the source of the given file and returns the first package found
686
     * in that file.
687
     *
688
     * @param string $sourceFile
689
     * @return ASTNamespace
690
     * @throws ErrorException
691
     */
692
    private function parseSource($sourceFile)
693
    {
694
        if (file_exists($sourceFile) === false) {
695
            throw new ErrorException('Cannot locate source file: ' . $sourceFile);
696
        }
697
698
        $tokenizer = new PHPTokenizerInternal();
699
        $tokenizer->setSourceFile($sourceFile);
700
701
        $builder = new PHPBuilder();
702
703
        $parser = new PHPParserGeneric(
704
            $tokenizer,
705
            $builder,
706
            new MemoryCacheDriver()
707
        );
708
        $parser->parse();
709
710
        return $builder->getNamespaces()->current();
711
    }
712
}
713