Test Setup Failed
Push — master ( 42932c...e4aa9e )
by Kyle
16:43 queued 11s
created

AbstractTest::getNodeForTestFile()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 22

Duplication

Lines 0
Ratio 0 %

Importance

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

Scrutinizer analyzes your composer.json/composer.lock file if available to determine the classes, and functions that are defined by your dependencies.

It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.

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