Passed
Push — feature/issue-601-unused-prope... ( 4303eb )
by Kyle
02:25
created

AbstractTest   B

Complexity

Total Complexity 51

Size/Duplication

Total Lines 614
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 20

Importance

Changes 0
Metric Value
dl 0
loc 614
rs 7.906
c 0
b 0
f 0
wmc 51
lcom 1
cbo 20

35 Methods

Rating   Name   Duplication   Size   Complexity  
A getApplyingFiles() 0 4 1
A getNotApplyingFiles() 0 4 1
A getApplyingCases() 0 4 1
A getNotApplyingCases() 0 4 1
A tearDown() 0 7 1
A getClass() 0 8 1
A getInterface() 0 8 1
A getTrait() 0 8 1
A getMethod() 0 11 1
A getFunction() 0 8 1
A getClassNodeForTestFile() 0 8 1
A getNodeForTestFile() 0 21 2
A expectRuleHasViolationsForFile() 0 15 2
A createCodeResourceUriForTest() 0 6 1
A createResourceUriForTest() 0 6 1
A createResourceUriForCalledClass() 0 4 1
A getFilesForCalledClass() 0 4 1
A getClassMock() 0 16 2
A getMethodMock() 0 4 1
A createFunctionMock() 0 9 1
A initFunctionOrMethod() 0 13 2
A getReportMock() 0 18 4
A getReportWithOneViolation() 0 4 1
A getReportWithNoViolation() 0 4 1
A getReportWithAtLeastOneViolation() 0 4 1
A getMockFromBuilder() 0 8 2
A getRuleMock() 0 8 2
A getRuleSetMock() 0 21 3
A getRuleViolationMock() 0 41 3
A getErrorMock() 0 20 1
A parseTestCaseSource() 0 4 1
A createFunctionOrMethodMock() 0 11 1
A getNodeByName() 0 9 3
A getNodeForCallingTestCase() 0 6 1
A parseSource() 0 20 2

How to fix   Complexity   

Complex Class

Complex classes like AbstractTest often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use AbstractTest, and based on these observations, apply Extract Interface, too.

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
41
/**
42
 * Abstract base class for PHPMD test cases.
43
 */
44
abstract class AbstractTest extends AbstractStaticTest
45
{
46
    /** @var int At least one violation is expected */
47
    const AL_LEAST_ONE_VIOLATION = -1;
48
49
    /** @var int No violation is expected */
50
    const NO_VIOLATION = 0;
51
52
    /** @var int One violation is expected */
53
    const ONE_VIOLATION = 1;
54
55
    /**
56
     * Get a list of files that should trigger a rule violation.
57
     *
58
     * By default, files named like "testRuleAppliesTo*", but it can be overridden in sub-classes.
59
     *
60
     * @return string[]
61
     */
62
    public function getApplyingFiles()
63
    {
64
        return $this->getFilesForCalledClass('testRuleApplies*');
65
    }
66
67
    /**
68
     * Get a list of files that should not trigger a rule violation.
69
     *
70
     * By default, files named like "testRuleDoesNotApplyTo*", but it can be overridden in sub-classes.
71
     *
72
     * @return string[]
73
     */
74
    public function getNotApplyingFiles()
75
    {
76
        return $this->getFilesForCalledClass('testRuleDoesNotApply*');
77
    }
78
79
    /**
80
     * Get a list of test files specified by getApplyingFiles() as an array of 1-length arguments lists.
81
     *
82
     * @return string[][]
83
     */
84
    public function getApplyingCases()
85
    {
86
        return static::getValuesAsArrays($this->getApplyingFiles());
87
    }
88
89
    /**
90
     * Get a list of test files specified by getNotApplyingFiles() as an array of 1-length arguments lists.
91
     *
92
     * @return string[][]
93
     */
94
    public function getNotApplyingCases()
95
    {
96
        return static::getValuesAsArrays($this->getNotApplyingFiles());
97
    }
98
99
    /**
100
     * Resets a changed working directory.
101
     *
102
     * @return void
103
     */
104
    protected function tearDown()
105
    {
106
        static::returnToOriginalWorkingDirectory();
107
        static::cleanupTempFiles();
108
109
        parent::tearDown();
110
    }
111
112
    /**
113
     * Returns the first class found in a source file related to the calling
114
     * test method.
115
     *
116
     * @return ClassNode
117
     */
118
    protected function getClass()
119
    {
120
        return new ClassNode(
121
            $this->getNodeForCallingTestCase(
122
                $this->parseTestCaseSource()->getClasses()
123
            )
124
        );
125
    }
126
127
    /**
128
     * Returns the first interface found in a source file related to the calling
129
     * test method.
130
     *
131
     * @return InterfaceNode
132
     */
133
    protected function getInterface()
134
    {
135
        return new InterfaceNode(
136
            $this->getNodeForCallingTestCase(
137
                $this->parseTestCaseSource()->getInterfaces()
138
            )
139
        );
140
    }
141
142
    /**
143
     * @return TraitNode
144
     */
145
    protected function getTrait()
146
    {
147
        return new TraitNode(
148
            $this->getNodeForCallingTestCase(
149
                $this->parseTestCaseSource()->getTraits()
150
            )
151
        );
152
    }
153
154
    /**
155
     * Returns the first method found in a source file related to the calling
156
     * test method.
157
     *
158
     * @return MethodNode
159
     */
160
    protected function getMethod()
161
    {
162
        return new MethodNode(
163
            $this->getNodeForCallingTestCase(
164
                $this->parseTestCaseSource()
165
                    ->getTypes()
166
                    ->current()
167
                    ->getMethods()
168
            )
169
        );
170
    }
171
172
    /**
173
     * Returns the first function found in a source files related to the calling
174
     * test method.
175
     *
176
     * @return FunctionNode
177
     */
178
    protected function getFunction()
179
    {
180
        return new FunctionNode(
181
            $this->getNodeForCallingTestCase(
182
                $this->parseTestCaseSource()->getFunctions()
183
            )
184
        );
185
    }
186
187
    /**
188
     * Returns the first method as a MethodNode for a given test file.
189
     *
190
     * @param string $file
191
     * @return ClassNode
192
     * @since 2.8.3
193
     */
194
    protected function getClassNodeForTestFile($file)
195
    {
196
        return new ClassNode(
197
            $this->parseSource($file)
198
                ->getTypes()
199
                ->current()
200
        );
201
    }
202
203
    /**
204
     * Get the node for a given test file.
205
     *
206
     * This method can be overridden to select the node to test with the rule.
207
     *
208
     * @param string $file
209
     *
210
     * @return AbstractNode
211
     */
212
    protected function getNodeForTestFile($file)
213
    {
214
        $source = $this->parseSource($file);
215
        $class = $source
216
            ->getTypes()
217
            ->current();
218
219
        return $class
220
            ? new MethodNode(
221
                $this->getNodeByName(
222
                    $class->getMethods(),
223
                    pathinfo($file, PATHINFO_FILENAME)
224
                )
225
            )
226
            : new FunctionNode(
227
                $this->getNodeByName(
228
                    $source->getFunctions(),
229
                    pathinfo($file, PATHINFO_FILENAME)
230
                )
231
            );
232
    }
233
234
    /**
235
     * Test that a given file trigger N times the given rule.
236
     *
237
     * @param Rule $rule Rule to test.
238
     * @param int $expectedInvokes Count of expected invocations.
239
     * @param string $file Test file containing a method with the same name to be tested.
240
     */
241
    protected function expectRuleHasViolationsForFile(Rule $rule, $expectedInvokes, $file)
242
    {
243
        $rule->setReport($this->getReportMock($expectedInvokes));
0 ignored issues
show
Bug introduced by
It seems like $this->getReportMock($expectedInvokes) targeting PHPMD\AbstractTest::getReportMock() can also be of type object<PHPUnit_Framework_MockObject_MockObject>; however, PHPMD\Rule::setReport() does only seem to accept object<PHPMD\Report>, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
244
245
        try {
246
            $rule->apply($this->getNodeForTestFile($file));
247
        } catch (PHPUnit_Framework_ExpectationFailedException $failedException) {
248
            throw new PHPUnit_Framework_ExpectationFailedException(
249
                basename($file)."\n".
250
                $failedException->getMessage(),
251
                $failedException->getComparisonFailure(),
252
                $failedException->getPrevious()
253
            );
254
        }
255
    }
256
257
    /**
258
     * Returns the absolute path for a test resource for the current test.
259
     *
260
     * @return string
261
     * @since 1.1.0
262
     */
263
    protected static function createCodeResourceUriForTest()
264
    {
265
        $frame = static::getCallingTestCase();
266
267
        return self::createResourceUriForTest($frame['function'] . '.php');
268
    }
269
270
    /**
271
     * Returns the absolute path for a test resource for the current test.
272
     *
273
     * @param string $localPath The local/relative file location
274
     * @return string
275
     * @since 1.1.0
276
     */
277
    protected static function createResourceUriForTest($localPath)
278
    {
279
        $frame = static::getCallingTestCase();
280
281
        return static::getResourceFilePathFromClassName($frame['class'], $localPath);
282
    }
283
284
    /**
285
     * Return URI for a given pattern with directory based on the current called class name.
286
     *
287
     * @param string $pattern
288
     * @return string
289
     */
290
    protected function createResourceUriForCalledClass($pattern)
291
    {
292
        return $this->getResourceFilePathFromClassName(get_class($this), $pattern);
293
    }
294
295
    /**
296
     * Return list of files matching a given pattern with directory based on the current called class name.
297
     *
298
     * @param string $pattern
299
     * @return string[]
300
     */
301
    protected function getFilesForCalledClass($pattern = '*')
302
    {
303
        return glob($this->createResourceUriForCalledClass($pattern));
304
    }
305
306
    /**
307
     * Creates a mocked class node instance.
308
     *
309
     * @param string $metric
310
     * @param integer $value
311
     * @return ClassNode
312
     */
313
    protected function getClassMock($metric = null, $value = null)
314
    {
315
        $class = $this->getMockFromBuilder(
316
            $this->getMockBuilder('PHPMD\\Node\\ClassNode')
317
                ->setConstructorArgs(array(new ASTClass('FooBar')))
318
        );
319
320
        if ($metric !== null) {
321
            $class->expects($this->atLeastOnce())
322
                ->method('getMetric')
323
                ->with($this->equalTo($metric))
324
                ->willReturn($value);
325
        }
326
327
        return $class;
328
    }
329
330
    /**
331
     * Creates a mocked method node instance.
332
     *
333
     * @param string $metric
334
     * @param integer $value
335
     * @return MethodNode
336
     */
337
    protected function getMethodMock($metric = null, $value = null)
338
    {
339
        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...
340
    }
341
342
    /**
343
     * Creates a mocked function node instance.
344
     *
345
     * @param string $metric The metric acronym used by PHP_Depend.
346
     * @param mixed $value The expected metric return value.
347
     * @return FunctionNode
348
     */
349
    protected function createFunctionMock($metric = null, $value = null)
350
    {
351
        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...
352
            'PHPMD\\Node\\FunctionNode',
353
            new ASTFunction('fooBar'),
354
            $metric,
355
            $value
356
        );
357
    }
358
359
    /**
360
     * Initializes the getMetric() method of the given function or method node.
361
     *
362
     * @param FunctionNode|MethodNode|PHPUnit_Framework_MockObject_MockObject $mock
363
     * @param string $metric
364
     * @param mixed $value
365
     * @return FunctionNode|MethodNode
366
     */
367
    protected function initFunctionOrMethod($mock, $metric, $value)
368
    {
369
        if ($metric === null) {
370
            return $mock;
371
        }
372
373
        $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...
374
            ->method('getMetric')
375
            ->with($this->equalTo($metric))
376
            ->willReturn($value);
377
378
        return $mock;
379
    }
380
381
    /**
382
     * Creates a mocked report instance.
383
     *
384
     * @param integer $expectedInvokes Number of expected invokes.
385
     * @return Report|PHPUnit_Framework_MockObject_MockObject
386
     */
387
    protected function getReportMock($expectedInvokes = -1)
388
    {
389
        if ($expectedInvokes === self::AL_LEAST_ONE_VIOLATION) {
390
            $expects = $this->atLeastOnce();
391
        } elseif ($expectedInvokes === self::NO_VIOLATION) {
392
            $expects = $this->never();
393
        } elseif ($expectedInvokes === self::ONE_VIOLATION) {
394
            $expects = $this->once();
395
        } else {
396
            $expects = $this->exactly($expectedInvokes);
397
        }
398
399
        $report = $this->getMockFromBuilder($this->getMockBuilder('PHPMD\\Report'));
400
        $report->expects($expects)
401
            ->method('addRuleViolation');
402
403
        return $report;
404
    }
405
406
    /**
407
     * Get a mocked report with one violation
408
     *
409
     * @return Report
410
     */
411
    public function getReportWithOneViolation()
412
    {
413
        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 413 which is incompatible with the return type documented by PHPMD\AbstractTest::getReportWithOneViolation of type PHPMD\Report.
Loading history...
414
    }
415
416
    /**
417
     * Get a mocked report with no violation
418
     *
419
     * @return Report
420
     */
421
    public function getReportWithNoViolation()
422
    {
423
        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 423 which is incompatible with the return type documented by PHPMD\AbstractTest::getReportWithNoViolation of type PHPMD\Report.
Loading history...
424
    }
425
426
    /**
427
     * Get a mocked report with at least one violation
428
     *
429
     * @return Report
430
     */
431
    public function getReportWithAtLeastOneViolation()
432
    {
433
        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 433 which is incompatible with the return type documented by PHPMD\AbstractTest::getR...WithAtLeastOneViolation of type PHPMD\Report.
Loading history...
434
    }
435
436
    protected function getMockFromBuilder(PHPUnit_Framework_MockObject_MockBuilder $builder)
437
    {
438
        if (version_compare(PHP_VERSION, '7.4.0-dev', '<')) {
439
            return $builder->getMock();
440
        }
441
442
        return @$builder->getMock();
443
    }
444
445
    /**
446
     * Creates a mocked {@link \PHPMD\AbstractRule} instance.
447
     *
448
     * @return AbstractRule|PHPUnit_Framework_MockObject_MockObject
449
     */
450
    protected function getRuleMock()
451
    {
452
        if (version_compare(PHP_VERSION, '7.4.0-dev', '<')) {
453
            return $this->getMockForAbstractClass('PHPMD\\AbstractRule');
454
        }
455
456
        return @$this->getMockForAbstractClass('PHPMD\\AbstractRule');
457
    }
458
459
    /**
460
     * Creates a mocked rule-set instance.
461
     *
462
     * @param string $expectedClass Optional class name for apply() expected at least once.
463
     * @param int|string $count How often should apply() be called?
464
     * @return RuleSet|PHPUnit_Framework_MockObject_MockObject
465
     */
466
    protected function getRuleSetMock($expectedClass = null, $count = '*')
467
    {
468
        $ruleSet = $this->getMockFromBuilder($this->getMockBuilder('PHPMD\RuleSet'));
469
        if ($expectedClass === null) {
470
            $ruleSet->expects($this->never())->method('apply');
471
472
            return $ruleSet;
473
        }
474
475
        if ($count === '*') {
476
            $count = $this->atLeastOnce();
477
        } else {
478
            $count = $this->exactly($count);
479
        }
480
481
        $ruleSet->expects($count)
482
            ->method('apply')
483
            ->with($this->isInstanceOf($expectedClass));
484
485
        return $ruleSet;
486
    }
487
488
    /**
489
     * Creates a mocked rule violation instance.
490
     *
491
     * @param string $fileName The filename to use.
492
     * @param integer $beginLine The begin of violation line number to use.
493
     * @param integer $endLine The end of violation line number to use.
494
     * @param null|object $rule The rule object to use.
495
     * @param null|string $description The violation description to use.
496
     * @return PHPUnit_Framework_MockObject_MockObject
497
     */
498
    protected function getRuleViolationMock(
499
        $fileName = '/foo/bar.php',
500
        $beginLine = 23,
501
        $endLine = 42,
502
        $rule = null,
503
        $description = null
504
    ) {
505
        $ruleViolation = $this->getMockFromBuilder(
506
            $this->getMockBuilder('PHPMD\\RuleViolation')
507
                ->setConstructorArgs(array(new TooManyFields(), new FunctionNode(new ASTFunction('fooBar')), 'Hello'))
508
        );
509
510
        if ($rule === null) {
511
            $rule = new RuleStub();
512
        }
513
514
        if ($description === null) {
515
            $description = 'Test description';
516
        }
517
518
        $ruleViolation
519
            ->method('getRule')
520
            ->willReturn($rule);
521
        $ruleViolation
522
            ->method('getFileName')
523
            ->willReturn($fileName);
524
        $ruleViolation
525
            ->method('getBeginLine')
526
            ->willReturn($beginLine);
527
        $ruleViolation
528
            ->method('getEndLine')
529
            ->willReturn($endLine);
530
        $ruleViolation
531
            ->method('getNamespaceName')
532
            ->willReturn('TestStubPackage');
533
        $ruleViolation
534
            ->method('getDescription')
535
            ->willReturn($description);
536
537
        return $ruleViolation;
538
    }
539
540
    /**
541
     * Creates a mocked rul violation instance.
542
     *
543
     * @param string $file
544
     * @param string $message
545
     * @return ProcessingError|PHPUnit_Framework_MockObject_MockObject
546
     */
547
    protected function getErrorMock(
548
        $file = '/foo/baz.php',
549
        $message = 'Error in file "/foo/baz.php"'
550
    ) {
551
552
        $processingError = $this->getMockFromBuilder(
553
            $this->getMockBuilder('PHPMD\\ProcessingError')
554
                ->setConstructorArgs(array(null))
555
                ->setMethods(array('getFile', 'getMessage'))
556
        );
557
558
        $processingError
559
            ->method('getFile')
560
            ->willReturn($file);
561
        $processingError
562
            ->method('getMessage')
563
            ->willReturn($message);
564
565
        return $processingError;
566
    }
567
568
    /**
569
     * Parses the source code for the calling test method and returns the first
570
     * package node found in the parsed file.
571
     *
572
     * @return ASTNamespace
573
     */
574
    private function parseTestCaseSource()
575
    {
576
        return $this->parseSource($this->createCodeResourceUriForTest());
577
    }
578
579
    /**
580
     * @param string $mockBuilder
581
     * @param ASTFunction|ASTMethod $mock
582
     * @param string $metric The metric acronym used by PHP_Depend.
583
     * @param mixed $value The expected metric return value.
584
     * @return FunctionNode|MethodNode
585
     */
586
    private function createFunctionOrMethodMock($mockBuilder, $mock, $metric = null, $value = null)
587
    {
588
        return $this->initFunctionOrMethod(
589
            $this->getMockFromBuilder(
590
                $this->getMockBuilder($mockBuilder)
591
                    ->setConstructorArgs(array($mock))
592
            ),
593
            $metric,
594
            $value
595
        );
596
    }
597
598
    /**
599
     * Returns the PHP_Depend node having the given name.
600
     *
601
     * @param Iterator $nodes
602
     * @return PHP_Depend_Code_AbstractItem
603
     * @throws ErrorException
604
     */
605
    private function getNodeByName(Iterator $nodes, $name)
606
    {
607
        foreach ($nodes as $node) {
608
            if ($node->getName() === $name) {
609
                return $node;
610
            }
611
        }
612
        throw new ErrorException("Cannot locate node named $name.");
613
    }
614
615
    /**
616
     * Returns the PHP_Depend node for the calling test case.
617
     *
618
     * @param Iterator $nodes
619
     * @return PHP_Depend_Code_AbstractItem
620
     * @throws ErrorException
621
     */
622
    private function getNodeForCallingTestCase(Iterator $nodes)
623
    {
624
        $frame = $this->getCallingTestCase();
625
626
        return $this->getNodeByName($nodes, $frame['function']);
627
    }
628
629
    /**
630
     * Parses the source of the given file and returns the first package found
631
     * in that file.
632
     *
633
     * @param string $sourceFile
634
     * @return ASTNamespace
635
     * @throws ErrorException
636
     */
637
    private function parseSource($sourceFile)
638
    {
639
        if (file_exists($sourceFile) === false) {
640
            throw new ErrorException('Cannot locate source file: ' . $sourceFile);
641
        }
642
643
        $tokenizer = new PHPTokenizerInternal();
644
        $tokenizer->setSourceFile($sourceFile);
645
646
        $builder = new PHPBuilder();
647
648
        $parser = new PHPParserGeneric(
649
            $tokenizer,
650
            $builder,
651
            new MemoryCacheDriver()
652
        );
653
        $parser->parse();
654
655
        return $builder->getNamespaces()->current();
656
    }
657
}
658