AbstractTest::assertXmlEquals()   A
last analyzed

Complexity

Conditions 4
Paths 8

Size

Total Lines 24

Duplication

Lines 24
Ratio 100 %

Importance

Changes 0
Metric Value
cc 4
nc 8
nop 2
dl 24
loc 24
rs 9.536
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 PDepend\Source\Language\PHP\PHPBuilder;
21
use PDepend\Source\Language\PHP\PHPParserGeneric;
22
use PDepend\Source\Language\PHP\PHPTokenizerInternal;
23
use PDepend\Util\Cache\Driver\MemoryCacheDriver;
24
use PHPMD\Node\ClassNode;
25
use PHPMD\Node\FunctionNode;
26
use PHPMD\Node\InterfaceNode;
27
use PHPMD\Node\MethodNode;
28
use PHPMD\Node\TraitNode;
29
use PHPMD\Stubs\RuleStub;
30
31
/**
32
 * Abstract base class for PHPMD test cases.
33
 */
34
abstract class AbstractTest extends \PHPUnit\Framework\TestCase
35
{
36
    /**
37
     * @param string $originalClassName
38
     * @param array $methods
39
     * @param array $arguments
40
     * @param string $mockClassName
41
     * @param bool $callOriginalConstructor
42
     * @param bool $callOriginalClone
43
     * @param bool $callAutoload
44
     * @param bool $cloneArguments
45
     * @param bool $callOriginalMethods
46
     * @param null $proxyTarget
47
     * @deprecated Method deprecated since Release 5.4.0; use createMock() or getMockBuilder() instead
48
     * @return \PHPUnit_Framework_MockObject_MockObject
49
     *
50
     */
51
    public function getMock($originalClassName, $methods = [], array $arguments = [], $mockClassName = '', $callOriginalConstructor = true, $callOriginalClone = true, $callAutoload = true, $cloneArguments = false, $callOriginalMethods = false, $proxyTarget = NULL) {
52
        $mockObject = $this->getMockObjectGenerator()->getMock(
53
            $originalClassName,
54
            $methods,
55
            $arguments,
56
            $mockClassName,
57
            $callOriginalConstructor,
58
            $callOriginalClone,
59
            $callAutoload,
60
            $cloneArguments,
61
            $callOriginalMethods,
62
            $proxyTarget
63
        );
64
65
        $this->registerMockObject($mockObject);
66
67
        return $mockObject;
68
    }
69
    /**
70
     * Directory with test files.
71
     *
72
     * @var string $_filesDirectory
73
     */
74
    private static $filesDirectory = null;
75
76
    /**
77
     * Original directory is used to reset a changed working directory.
78
     *
79
     * @return void
80
     */
81
    private static $originalWorkingDirectory = null;
82
83
    /**
84
     * Temporary files created by a test.
85
     *
86
     * @var array(string)
87
     */
88
    private static $tempFiles = array();
89
90
    /**
91
     * Resets a changed working directory.
92
     *
93
     * @return void
94
     */
95
    protected function tearDown()
96
    {
97
        if (self::$originalWorkingDirectory !== null) {
98
            chdir(self::$originalWorkingDirectory);
99
        }
100
        self::$originalWorkingDirectory = null;
101
102
        foreach (self::$tempFiles as $tempFile) {
103
            unlink($tempFile);
104
        }
105
        self::$tempFiles = array();
106
107
        parent::tearDown();
108
    }
109
110
    /**
111
     * Returns the first class found in a source file related to the calling
112
     * test method.
113
     *
114
     * @return \PHPMD\Node\ClassNode
115
     */
116
    protected function getClass()
117
    {
118
        return new ClassNode(
119
            $this->getNodeForCallingTestCase(
120
                $this->parseTestCaseSource()->getClasses()
121
            )
122
        );
123
    }
124
125
    /**
126
     * Returns the first interface found in a source file related to the calling
127
     * test method.
128
     *
129
     * @return \PHPMD\Node\InterfaceNode
130
     */
131
    protected function getInterface()
132
    {
133
        return new InterfaceNode(
134
            $this->getNodeForCallingTestCase(
135
                $this->parseTestCaseSource()->getInterfaces()
136
            )
137
        );
138
    }
139
140
    /**
141
     * @return \PHPMD\Node\InterfaceNode
142
     */
143
    protected function getTrait()
144
    {
145
        return new TraitNode(
146
            $this->getNodeForCallingTestCase(
147
                $this->parseTestCaseSource()->getTraits()
148
            )
149
        );
150
    }
151
152
    /**
153
     * Returns the first method found in a source file related to the calling
154
     * test method.
155
     *
156
     * @return \PHPMD\Node\MethodNode
157
     */
158
    protected function getMethod()
159
    {
160
        return new MethodNode(
161
            $this->getNodeForCallingTestCase(
162
                $this->parseTestCaseSource()
163
                    ->getTypes()
164
                    ->current()
165
                    ->getMethods()
166
            )
167
        );
168
    }
169
170
    /**
171
     * Returns the first function found in a source files related to the calling
172
     * test method.
173
     *
174
     * @return \PHPMD\Node\FunctionNode
175
     */
176
    protected function getFunction()
177
    {
178
        return new FunctionNode(
179
            $this->getNodeForCallingTestCase(
180
                $this->parseTestCaseSource()->getFunctions()
181
            )
182
        );
183
    }
184
185
    /**
186
     * Returns the absolute path for a test resource for the current test.
187
     *
188
     * @return string
189
     * @since 1.1.0
190
     */
191
    protected static function createCodeResourceUriForTest()
192
    {
193
        $frame = self::getCallingTestCase();
194
        return self::createResourceUriForTest($frame['function'] . '.php');
195
    }
196
197
    /**
198
     * Returns the absolute path for a test resource for the current test.
199
     *
200
     * @param string $localPath The local/relative file location
201
     * @return string
202
     * @since 1.1.0
203
     */
204
    protected static function createResourceUriForTest($localPath)
205
    {
206
        $frame = self::getCallingTestCase();
207
208
        $regexp = '([a-z]([0-9]+)Test$)i';
209
        if (preg_match($regexp, $frame['class'], $match)) {
210
            $parts = explode('\\', $frame['class']);
211
            $testPath = $parts[count($parts) - 2] . '/' . $match[1];
212
        } else {
213
            $testPath = strtr(substr($frame['class'], 6, -4), '\\', '/');
214
        }
215
216
        return sprintf(
217
            '%s/../../resources/files/%s/%s',
218
            dirname(__FILE__),
219
            $testPath,
220
            $localPath
221
        );
222
    }
223
224
    /**
225
     * Parses the source code for the calling test method and returns the first
226
     * package node found in the parsed file.
227
     *
228
     * @return PHP_Depend_Code_Package
229
     */
230
    private function parseTestCaseSource()
231
    {
232
        return $this->parseSource($this->createCodeResourceUriForTest());
233
    }
234
235
    /**
236
     * Returns the trace frame of the calling test case.
237
     *
238
     * @return array
239
     * @throws \ErrorException
240
     */
241
    private static function getCallingTestCase()
242
    {
243
        foreach (debug_backtrace() as $frame) {
244
            if (strpos($frame['function'], 'test') === 0) {
245
                return $frame;
246
            }
247
        }
248
        throw new \ErrorException('Cannot locate calling test case.');
249
    }
250
251
    /**
252
     * Returns the PHP_Depend node for the calling test case.
253
     *
254
     * @param \Iterator $nodes
255
     * @return PHP_Depend_Code_AbstractItem
256
     * @throws \ErrorException
257
     */
258
    private function getNodeForCallingTestCase(\Iterator $nodes)
259
    {
260
        $frame = $this->getCallingTestCase();
261
        foreach ($nodes as $node) {
262
            if ($node->getName() === $frame['function']) {
263
                return $node;
264
            }
265
        }
266
        throw new \ErrorException('Cannot locate node for test case.');
267
    }
268
269
    /**
270
     * Parses the source of the given file and returns the first package found
271
     * in that file.
272
     *
273
     * @param string $sourceFile
274
     * @return \PDepend\Source\AST\ASTNamespace
275
     * @throws \ErrorException
276
     */
277
    private function parseSource($sourceFile)
278
    {
279
        if (file_exists($sourceFile) === false) {
280
            throw new \ErrorException('Cannot locate source file: ' . $sourceFile);
281
        }
282
283
        $tokenizer = new PHPTokenizerInternal();
284
        $tokenizer->setSourceFile($sourceFile);
285
286
        $builder =  new PHPBuilder();
287
288
        $parser = new PHPParserGeneric(
289
            $tokenizer,
290
            $builder,
291
            new MemoryCacheDriver()
292
        );
293
        $parser->parse();
294
295
        return $builder->getNamespaces()->current();
296
    }
297
298
    /**
299
     * Creates a mocked class node instance.
300
     *
301
     * @param string $metric
302
     * @param mixed $value
303
     * @return \PHPMD\Node\ClassNode
304
     */
305
    protected function getClassMock($metric = null, $value = null)
306
    {
307
        $class = $this->getMock(
0 ignored issues
show
Deprecated Code introduced by
The method PHPMD\AbstractTest::getMock() has been deprecated with message: Method deprecated since Release 5.4.0; use createMock() or getMockBuilder() instead

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
308
            ClassNode::class,
309
            array(),
310
            array(null),
311
            '',
312
            false
313
        );
314
315
        if ($metric !== null) {
316
            $class->expects($this->atLeastOnce())
317
                ->method('getMetric')
318
                ->with($this->equalTo($metric))
319
                ->willReturn($value);
320
        }
321
        return $class;
322
    }
323
324
    /**
325
     * Creates a mocked method node instance.
326
     *
327
     * @param string $metric
328
     * @param mixed $value
329
     * @return \PHPMD\Node\MethodNode
330
     */
331 View Code Duplication
    protected function getMethodMock($metric = null, $value = null)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
332
    {
333
        return $this->initFunctionOrMethod(
0 ignored issues
show
Bug Compatibility introduced by
The expression $this->initFunctionOrMet...lse), $metric, $value); of type PHPMD\Node\FunctionNode|PHPMD\Node\MethodNode adds the type PHPMD\Node\FunctionNode to the return on line 333 which is incompatible with the return type documented by PHPMD\AbstractTest::getMethodMock of type PHPMD\Node\MethodNode.
Loading history...
334
            $this->getMock(MethodNode::class, array(), array(null), '', false),
0 ignored issues
show
Deprecated Code introduced by
The method PHPMD\AbstractTest::getMock() has been deprecated with message: Method deprecated since Release 5.4.0; use createMock() or getMockBuilder() instead

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
335
            $metric,
336
            $value
337
        );
338
    }
339
340
    /**
341
     * Creates a mocked function node instance.
342
     *
343
     * @param string $metric The metric acronym used by PHP_Depend.
344
     * @param mixed  $value  The expected metric return value.
345
     * @return \PHPMD\Node\FunctionNode
346
     */
347 View Code Duplication
    protected function createFunctionMock($metric = null, $value = null)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
348
    {
349
        return $this->initFunctionOrMethod(
0 ignored issues
show
Bug Compatibility introduced by
The expression $this->initFunctionOrMet...lse), $metric, $value); of type PHPMD\Node\FunctionNode|PHPMD\Node\MethodNode adds the type PHPMD\Node\MethodNode to the return on line 349 which is incompatible with the return type documented by PHPMD\AbstractTest::createFunctionMock of type PHPMD\Node\FunctionNode.
Loading history...
350
            $this->getMock(FunctionNode::class, array(), array(null), '', false),
0 ignored issues
show
Deprecated Code introduced by
The method PHPMD\AbstractTest::getMock() has been deprecated with message: Method deprecated since Release 5.4.0; use createMock() or getMockBuilder() instead

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
351
            $metric,
352
            $value
353
        );
354
    }
355
356
    /**
357
     * Initializes the getMetric() method of the given function or method node.
358
     *
359
     * @param \PHPMD\Node\FunctionNode|\PHPMD\Node\MethodNode $mock
360
     * @param string $metric
361
     * @param mixed $value
362
     * @return \PHPMD\Node\FunctionNode|\PHPMD\Node\MethodNode
363
     */
364
    protected function initFunctionOrMethod($mock, $metric, $value)
365
    {
366
        if ($metric === null) {
367
            return $mock;
368
        }
369
370
        $mock->expects($this->atLeastOnce())
371
            ->method('getMetric')
372
            ->with($this->equalTo($metric))
373
            ->willReturn($value);
374
375
        return $mock;
376
    }
377
378
    /**
379
     * @param int $expectedInvokes number of expected invokes.
380
     * @return \PHPUnit_Framework_MockObject_MockObject
381
     */
382
    protected function getReportMock($expectedInvokes = -1)
383
    {
384
        if ($expectedInvokes < 0) {
385
            $expects = $this->atLeastOnce();
386
        } elseif ($expectedInvokes === 0) {
387
            $expects = $this->never();
388
        } elseif ($expectedInvokes === 1) {
389
            $expects = $this->once();
390
        } else {
391
            $expects = $this->exactly($expectedInvokes);
392
        }
393
394
        $report = $this->getMock(Report::class);
0 ignored issues
show
Deprecated Code introduced by
The method PHPMD\AbstractTest::getMock() has been deprecated with message: Method deprecated since Release 5.4.0; use createMock() or getMockBuilder() instead

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
395
        $report->expects($expects)
396
            ->method('addRuleViolation');
397
398
        return $report;
399
    }
400
401
    /**
402
     * Creates a mocked {@link \PHPMD\AbstractRule} instance.
403
     *
404
     * @return \PHPMD\AbstractRule
405
     */
406
    protected function getRuleMock()
407
    {
408
        return $this->getMockForAbstractClass(AbstractRule::class);
409
    }
410
411
    /**
412
     * Creates a mocked rule-set instance.
413
     *
414
     * @param string $expectedClass Optional class name for apply() expected at least once.
415
     * @param mixed $count How often should apply() be called?
416
     * @return \PHPMD\RuleSet
417
     */
418
    protected function getRuleSetMock($expectedClass = null, $count = '*')
419
    {
420
        $ruleSet = $this->getMock(RuleSet::class);
0 ignored issues
show
Deprecated Code introduced by
The method PHPMD\AbstractTest::getMock() has been deprecated with message: Method deprecated since Release 5.4.0; use createMock() or getMockBuilder() instead

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
421
        if ($expectedClass === null) {
422
            $ruleSet->expects($this->never())->method('apply');
423
        } else {
424
            $ruleSet->expects(
425
                $count === '*' ? $this->atLeastOnce() : $this->exactly($count)
426
            )
427
                ->method('apply')
428
                ->with($this->isInstanceOf($expectedClass));
429
        }
430
        return $ruleSet;
431
    }
432
433
    /**
434
     * Creates a mocked rul violation instance.
435
     *
436
     * @param string  $fileName
437
     * @param integer $beginLine
438
     * @param integer $endLine
439
     * @param object  $rule
440
     * @return \PHPMD\RuleViolation
441
     */
442
    protected function getRuleViolationMock(
443
        $fileName = '/foo/bar.php',
444
        $beginLine = 23,
445
        $endLine = 42,
446
        $rule = null
447
    ) {
448
        $ruleViolation = $this->getMock(
0 ignored issues
show
Deprecated Code introduced by
The method PHPMD\AbstractTest::getMock() has been deprecated with message: Method deprecated since Release 5.4.0; use createMock() or getMockBuilder() instead

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
449
            RuleViolation::class,
450
            array(),
451
            array(null, null, null),
452
            '',
453
            false
454
        );
455
456
        if ($rule === null) {
457
            $rule = new RuleStub();
458
        }
459
460
        $ruleViolation
461
            ->method('getRule')
462
            ->willReturn($rule);
463
        $ruleViolation
464
            ->method('getFileName')
465
            ->willReturn($fileName);
466
        $ruleViolation
467
            ->method('getBeginLine')
468
            ->willReturn($beginLine);
469
        $ruleViolation
470
            ->method('getEndLine')
471
            ->willReturn($endLine);
472
        $ruleViolation
473
            ->method('getNamespaceName')
474
            ->willReturn('TestStubPackage');
475
        $ruleViolation
476
            ->method('getDescription')
477
            ->willReturn('Test description');
478
479
        return $ruleViolation;
480
    }
481
482
    /**
483
     * Asserts the actual xml output matches against the expected file.
484
     *
485
     * @param string $actualOutput     Generated xml output.
486
     * @param string $expectedFileName File with expected xml result.
487
     * @return void
488
     */
489 View Code Duplication
    public static function assertXmlEquals($actualOutput, $expectedFileName)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
490
    {
491
        $actual = simplexml_load_string($actualOutput);
492
        // Remove dynamic timestamp and duration attribute
493
        if (isset($actual['timestamp'])) {
494
            $actual['timestamp'] = '';
495
        }
496
        if (isset($actual['duration'])) {
497
            $actual['duration'] = '';
498
        }
499
        if (isset($actual['version'])) {
500
            $actual['version'] = '@package_version@';
501
        }
502
503
        $expected = str_replace(
504
            '#{rootDirectory}',
505
            self::$filesDirectory,
506
            file_get_contents(self::createFileUri($expectedFileName))
507
        );
508
509
        $expected = str_replace('_DS_', DIRECTORY_SEPARATOR, $expected);
510
511
        self::assertXmlStringEqualsXmlString($expected, $actual->saveXML());
512
    }
513
514
    /**
515
     * Asserts the actual JSON output matches against the expected file.
516
     *
517
     * @param string $actualOutput     Generated JSON output.
518
     * @param string $expectedFileName File with expected JSON result.
519
     *
520
     * @return void
521
     */
522 View Code Duplication
    public function assertJsonEquals($actualOutput, $expectedFileName)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
523
    {
524
        $actual = json_decode($actualOutput, true);
525
        // Remove dynamic timestamp and duration attribute
526
        if (isset($actual['timestamp'])) {
527
            $actual['timestamp'] = '';
528
        }
529
        if (isset($actual['duration'])) {
530
            $actual['duration'] = '';
531
        }
532
        if (isset($actual['version'])) {
533
            $actual['version'] = '@package_version@';
534
        }
535
536
        $expected = str_replace(
537
            '#{rootDirectory}',
538
            self::$filesDirectory,
539
            file_get_contents(self::createFileUri($expectedFileName))
540
        );
541
542
        $expected = str_replace('_DS_', DIRECTORY_SEPARATOR, $expected);
543
544
        self::assertJsonStringEqualsJsonString($expected, json_encode($actual));
545
    }
546
547
    /**
548
     * This method initializes the test environment, it configures the files
549
     * directory and sets the include_path for svn versions.
550
     *
551
     * @return void
552
     */
553
    public static function setUpBeforeClass()
554
    {
555
        self::$filesDirectory = realpath(__DIR__ . '/../../resources/files');
556
557
        if (false === strpos(get_include_path(), self::$filesDirectory)) {
558
            set_include_path(
559
                sprintf(
560
                    '%s%s%s%s%s',
561
                    get_include_path(),
562
                    PATH_SEPARATOR,
563
                    self::$filesDirectory,
564
                    PATH_SEPARATOR,
565
                    realpath(__DIR__ . '/../')
566
                )
567
            );
568
        }
569
570
        // Prevent timezone warnings if no default TZ is set (PHP > 5.1.0)
571
        date_default_timezone_set('UTC');
572
    }
573
574
    /**
575
     * Changes the working directory for a single test.
576
     *
577
     * @param string $localPath The temporary working directory.
578
     * @return void
579
     */
580
    protected static function changeWorkingDirectory($localPath = '')
581
    {
582
        self::$originalWorkingDirectory = getcwd();
583
584
        if (0 === preg_match('(^([A-Z]:|/))', $localPath)) {
585
            $localPath = self::createFileUri($localPath);
586
        }
587
        chdir($localPath);
588
    }
589
590
    /**
591
     * Creates a full filename for a test content in the <em>_files</b> directory.
592
     *
593
     * @param string $localPath
594
     * @return string
595
     */
596
    protected static function createFileUri($localPath = '')
597
    {
598
        return self::$filesDirectory . '/' . $localPath;
599
    }
600
601
    /**
602
     * Creates a file uri for a temporary test file.
603
     *
604
     * @return string
605
     */
606
    protected static function createTempFileUri()
607
    {
608
        return (self::$tempFiles[] = tempnam(sys_get_temp_dir(), 'phpmd.'));
609
    }
610
}
611