AbstractTest   F
last analyzed

Complexity

Total Complexity 60

Size/Duplication

Total Lines 669
Duplicated Lines 0 %

Coupling/Cohesion

Components 2
Dependencies 22

Importance

Changes 0
Metric Value
wmc 60
lcom 2
cbo 22
dl 0
loc 669
rs 3.531
c 0
b 0
f 0

37 Methods

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