Failed Conditions
Push — master ( a46fbb...c35fd9 )
by Alexander
01:13
created

AbstractSniffUnitTest::testSniff()   F

Complexity

Conditions 12
Paths 928

Size

Total Lines 92

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 12
nc 928
nop 0
dl 0
loc 92
rs 2.4945
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * An abstract class that all sniff unit tests must extend.
4
 *
5
 * PHP version 5
6
 *
7
 * @category  PHP
8
 * @package   PHP_CodeSniffer
9
 * @author    Greg Sherwood <[email protected]>
10
 * @author    Marc McIntyre <[email protected]>
11
 * @copyright 2006-2012 Squiz Pty Ltd (ABN 77 084 670 600)
12
 * @license   https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
13
 * @link      http://pear.php.net/package/PHP_CodeSniffer
14
 */
15
16
namespace TestSuite;
17
18
use PHP_CodeSniffer\Config;
19
use PHP_CodeSniffer\Exceptions\RuntimeException;
20
use PHP_CodeSniffer\Files\LocalFile;
21
use PHP_CodeSniffer\Ruleset;
22
use PHP_CodeSniffer\Util\Common;
23
use PHPUnit\Framework\TestCase;
24
25
/**
26
 * An abstract class that all sniff unit tests must extend.
27
 *
28
 * A sniff unit test checks a .inc file for expected violations of a single
29
 * coding standard. Expected errors and warnings that are not found, or
30
 * warnings and errors that are not expected, are considered test failures.
31
 *
32
 * @category  PHP
33
 * @package   PHP_CodeSniffer
34
 * @author    Greg Sherwood <[email protected]>
35
 * @author    Marc McIntyre <[email protected]>
36
 * @copyright 2006-2012 Squiz Pty Ltd (ABN 77 084 670 600)
37
 * @license   https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
38
 * @version   Release: @package_version@
39
 * @link      http://pear.php.net/package/PHP_CodeSniffer
40
 */
41
abstract class AbstractSniffUnitTest extends TestCase
42
{
43
44
    /**
45
     * Enable or disable the backup and restoration of the $GLOBALS array.
46
     * Overwrite this attribute in a child class of TestCase.
47
     * Setting this attribute in setUp() has no effect!
48
     *
49
     * @var boolean
50
     */
51
    protected $backupGlobals = false;
52
53
    /**
54
     * The path to the standard's main directory.
55
     *
56
     * @var string
57
     */
58
    public $standardsDir = null;
59
60
    /**
61
     * The path to the standard's test directory.
62
     *
63
     * @var string
64
     */
65
    public $testsDir = null;
66
67
68
    /**
69
     * Sets up this unit test.
70
     *
71
     * @return void
72
     */
73
    protected function setUp()
74
    {
75
        $this->standardsDir = STANDARDS_PATH.DIRECTORY_SEPARATOR.STANDARD_NAME;
76
        $this->testsDir     = STANDARDS_PATH.DIRECTORY_SEPARATOR.STANDARD_NAME.DIRECTORY_SEPARATOR.'Tests'.DIRECTORY_SEPARATOR;
77
    }//end setUp()
78
79
80
    /**
81
     * Get a list of all test files to check.
82
     *
83
     * These will have the same base as the sniff name but different extensions.
84
     * We ignore the .php file as it is the class.
85
     *
86
     * @param string $testFileBase The base path that the unit tests files will have.
87
     *
88
     * @return string[]
89
     */
90
    protected function getTestFiles($testFileBase)
91
    {
92
        $testFiles = array();
93
94
        $dir = substr($testFileBase, 0, strrpos($testFileBase, DIRECTORY_SEPARATOR));
95
        $di  = new \DirectoryIterator($dir);
96
97
        foreach ($di as $file) {
98
            $path = $file->getPathname();
99
            if (substr($path, 0, strlen($testFileBase)) === $testFileBase) {
100
                if ($path !== $testFileBase.'php' && substr($path, -5) !== 'fixed' && substr($path, -4) !== '.bak') {
101
                    $testFiles[] = $path;
102
                }
103
            }
104
        }
105
106
        // Put them in order.
107
        sort($testFiles);
108
109
        return $testFiles;
110
    }//end getTestFiles()
111
112
113
    /**
114
     * Should this test be skipped for some reason.
115
     *
116
     * @return bool
117
     */
118
    protected function shouldSkipTest()
119
    {
120
        return false;
121
    }//end shouldSkipTest()
122
123
124
    /**
125
     * Tests the extending classes Sniff class.
126
     *
127
     * @return void
128
     */
129
    final public function testSniff()
130
    {
131
        // Skip this test if we can't run in this environment.
132
        if ($this->shouldSkipTest() === true) {
133
            $this->markTestSkipped();
134
        }
135
136
        $sniffCode = Common::getSniffCode(get_class($this));
137
        list($standardName, $categoryName, $sniffName) = explode('.', $sniffCode);
138
139
        $testFileBase = $this->testsDir.$categoryName.DIRECTORY_SEPARATOR.$sniffName.'UnitTest.';
140
141
        // Get a list of all test files to check.
142
        $testFiles = $this->getTestFiles($testFileBase);
143
144
        if (isset($GLOBALS['PHP_CODESNIFFER_CONFIG']) === true) {
145
            $config = $GLOBALS['PHP_CODESNIFFER_CONFIG'];
146
        } else {
147
            $config        = new Config();
148
            $config->cache = false;
149
            $GLOBALS['PHP_CODESNIFFER_CONFIG'] = $config;
150
        }
151
152
        $config->standards = array($standardName);
153
        $config->sniffs    = array($sniffCode);
154
        $config->ignored   = array();
155
156
        if (isset($GLOBALS['PHP_CODESNIFFER_RULESETS']) === false) {
157
            $GLOBALS['PHP_CODESNIFFER_RULESETS'] = array();
158
        }
159
160
        if (isset($GLOBALS['PHP_CODESNIFFER_RULESETS'][$standardName]) === false) {
161
            $ruleset = new Ruleset($config);
162
            $GLOBALS['PHP_CODESNIFFER_RULESETS'][$standardName] = $ruleset;
163
        }
164
165
        $ruleset = $GLOBALS['PHP_CODESNIFFER_RULESETS'][$standardName];
166
167
        $sniffFile = $this->standardsDir.DIRECTORY_SEPARATOR.'Sniffs'.DIRECTORY_SEPARATOR.$categoryName.DIRECTORY_SEPARATOR.$sniffName.'Sniff.php';
168
169
        $sniffClassName = substr(get_class($this), 0, -8).'Sniff';
170
        $sniffClassName = str_replace('\Tests\\', '\Sniffs\\', $sniffClassName);
171
        $sniffClassName = Common::cleanSniffClass($sniffClassName);
172
173
        $restrictions = [strtolower($sniffClassName) => true];
174
        $ruleset->registerSniffs([$sniffFile], $restrictions, array());
175
        $ruleset->populateTokenListeners();
176
177
        $failureMessages = array();
178
        foreach ($testFiles as $testFile) {
179
            $filename  = basename($testFile);
180
            $oldConfig = $config->getSettings();
181
182
            try {
183
                $this->setCliValues($filename, $config);
184
                $phpcsFile = new LocalFile($testFile, $ruleset, $config);
185
                $phpcsFile->process();
186
            } catch (RuntimeException $e) {
187
                $this->fail('An unexpected exception has been caught: '.$e->getMessage());
188
            }
189
190
            $failures        = $this->generateFailureMessages($phpcsFile);
0 ignored issues
show
Bug introduced by
The variable $phpcsFile does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
191
            $failureMessages = array_merge($failureMessages, $failures);
192
193
            if ($phpcsFile->getFixableCount() > 0) {
194
                // Attempt to fix the errors.
195
                $phpcsFile->fixer->fixFile();
196
                $fixable = $phpcsFile->getFixableCount();
197
                if ($fixable > 0) {
198
                    $failureMessages[] = "Failed to fix $fixable fixable violations in $filename";
199
                }
200
201
                // Check for a .fixed file to check for accuracy of fixes.
202
                $fixedFile = $testFile.'.fixed';
203
                if (file_exists($fixedFile) === true) {
204
                    $diff = $phpcsFile->fixer->generateDiff($fixedFile);
205
                    if (trim($diff) !== '') {
206
                        $filename          = basename($testFile);
207
                        $fixedFilename     = basename($fixedFile);
208
                        $failureMessages[] = "Fixed version of $filename does not match expected version in $fixedFilename; the diff is\n$diff";
209
                    }
210
                }
211
            }
212
213
            // Restore the config.
214
            $config->setSettings($oldConfig);
215
        }//end foreach
216
217
        if (empty($failureMessages) === false) {
218
            $this->fail(implode(PHP_EOL, $failureMessages));
219
        }
220
    }//end testStandard()
221
222
223
    /**
224
     * Determines if used PHPCS version supports fixing.
225
     *
226
     * @return bool
227
     */
228
    protected function isFixingSupported()
229
    {
230
        $classReflection = new \ReflectionClass('PHP_CodeSniffer_File');
231
232
        return $classReflection->hasProperty('fixer');
233
    }//end isFixingSupported()
234
235
236
    /**
237
     * Generate a list of test failures for a given sniffed file.
238
     *
239
     * @param LocalFile $file The file being tested.
240
     *
241
     * @return array
242
     * @throws RuntimeException
243
     */
244
    public function generateFailureMessages(LocalFile $file)
245
    {
246
        $testFile = $file->getFilename();
247
248
        $foundErrors      = $file->getErrors();
249
        $foundWarnings    = $file->getWarnings();
250
        $expectedErrors   = $this->getErrorList(basename($testFile));
251
        $expectedWarnings = $this->getWarningList(basename($testFile));
252
253
        if (is_array($expectedErrors) === false) {
254
            throw new RuntimeException('getErrorList() must return an array');
255
        }
256
257
        if (is_array($expectedWarnings) === false) {
258
            throw new RuntimeException('getWarningList() must return an array');
259
        }
260
261
        /*
262
            We merge errors and warnings together to make it easier
263
            to iterate over them and produce the errors string. In this way,
264
            we can report on errors and warnings in the same line even though
265
            it's not really structured to allow that.
266
        */
267
268
        $allProblems     = array();
269
        $failureMessages = array();
270
271
        foreach ($foundErrors as $line => $lineErrors) {
272
            foreach ($lineErrors as $column => $errors) {
273
                if (isset($allProblems[$line]) === false) {
274
                    $allProblems[$line] = array(
275
                                           'expected_errors'   => 0,
276
                                           'expected_warnings' => 0,
277
                                           'found_errors'      => array(),
278
                                           'found_warnings'    => array(),
279
                                          );
280
                }
281
282
                $foundErrorsTemp = array();
283
                foreach ($allProblems[$line]['found_errors'] as $foundError) {
284
                    $foundErrorsTemp[] = $foundError;
285
                }
286
287
                $errorsTemp = array();
288
                foreach ($errors as $foundError) {
289
                    $errorsTemp[] = $foundError['message'].' ('.$foundError['source'].')';
290
                }
291
292
                $allProblems[$line]['found_errors'] = array_merge($foundErrorsTemp, $errorsTemp);
293
            }//end foreach
294
295
            if (isset($expectedErrors[$line]) === true) {
296
                $allProblems[$line]['expected_errors'] = $expectedErrors[$line];
297
            } else {
298
                $allProblems[$line]['expected_errors'] = 0;
299
            }
300
301
            unset($expectedErrors[$line]);
302
        }//end foreach
303
304
        foreach ($expectedErrors as $line => $numErrors) {
305
            if (isset($allProblems[$line]) === false) {
306
                $allProblems[$line] = array(
307
                                       'expected_errors'   => 0,
308
                                       'expected_warnings' => 0,
309
                                       'found_errors'      => array(),
310
                                       'found_warnings'    => array(),
311
                                      );
312
            }
313
314
            $allProblems[$line]['expected_errors'] = $numErrors;
315
        }
316
317
        foreach ($foundWarnings as $line => $lineWarnings) {
318
            foreach ($lineWarnings as $column => $warnings) {
319
                if (isset($allProblems[$line]) === false) {
320
                    $allProblems[$line] = array(
321
                                           'expected_errors'   => 0,
322
                                           'expected_warnings' => 0,
323
                                           'found_errors'      => array(),
324
                                           'found_warnings'    => array(),
325
                                          );
326
                }
327
328
                $foundWarningsTemp = array();
329
                foreach ($allProblems[$line]['found_warnings'] as $foundWarning) {
330
                    $foundWarningsTemp[] = $foundWarning;
331
                }
332
333
                $warningsTemp = array();
334
                foreach ($warnings as $warning) {
335
                    $warningsTemp[] = $warning['message'].' ('.$warning['source'].')';
336
                }
337
338
                $allProblems[$line]['found_warnings'] = array_merge($foundWarningsTemp, $warningsTemp);
339
            }//end foreach
340
341
            if (isset($expectedWarnings[$line]) === true) {
342
                $allProblems[$line]['expected_warnings'] = $expectedWarnings[$line];
343
            } else {
344
                $allProblems[$line]['expected_warnings'] = 0;
345
            }
346
347
            unset($expectedWarnings[$line]);
348
        }//end foreach
349
350
        foreach ($expectedWarnings as $line => $numWarnings) {
351
            if (isset($allProblems[$line]) === false) {
352
                $allProblems[$line] = array(
353
                                       'expected_errors'   => 0,
354
                                       'expected_warnings' => 0,
355
                                       'found_errors'      => array(),
356
                                       'found_warnings'    => array(),
357
                                      );
358
            }
359
360
            $allProblems[$line]['expected_warnings'] = $numWarnings;
361
        }
362
363
        // Order the messages by line number.
364
        ksort($allProblems);
365
366
        foreach ($allProblems as $line => $problems) {
367
            $numErrors        = count($problems['found_errors']);
368
            $numWarnings      = count($problems['found_warnings']);
369
            $expectedErrors   = $problems['expected_errors'];
370
            $expectedWarnings = $problems['expected_warnings'];
371
372
            $errors      = '';
373
            $foundString = '';
374
375
            if ($expectedErrors !== $numErrors || $expectedWarnings !== $numWarnings) {
376
                $lineMessage     = "[LINE $line]";
377
                $expectedMessage = 'Expected ';
378
                $foundMessage    = 'in '.basename($testFile).' but found ';
379
380
                if ($expectedErrors !== $numErrors) {
381
                    $expectedMessage .= "$expectedErrors error(s)";
382
                    $foundMessage    .= "$numErrors error(s)";
383
                    if ($numErrors !== 0) {
384
                        $foundString .= 'error(s)';
385
                        $errors      .= implode(PHP_EOL.' -> ', $problems['found_errors']);
386
                    }
387
388
                    if ($expectedWarnings !== $numWarnings) {
389
                        $expectedMessage .= ' and ';
390
                        $foundMessage    .= ' and ';
391
                        if ($numWarnings !== 0) {
392
                            if ($foundString !== '') {
393
                                $foundString .= ' and ';
394
                            }
395
                        }
396
                    }
397
                }
398
399
                if ($expectedWarnings !== $numWarnings) {
400
                    $expectedMessage .= "$expectedWarnings warning(s)";
401
                    $foundMessage    .= "$numWarnings warning(s)";
402
                    if ($numWarnings !== 0) {
403
                        $foundString .= 'warning(s)';
404
                        if (empty($errors) === false) {
405
                            $errors .= PHP_EOL.' -> ';
406
                        }
407
408
                        $errors .= implode(PHP_EOL.' -> ', $problems['found_warnings']);
409
                    }
410
                }
411
412
                $fullMessage = "$lineMessage $expectedMessage $foundMessage.";
413
                if ($errors !== '') {
414
                    $fullMessage .= " The $foundString found were:".PHP_EOL." -> $errors";
415
                }
416
417
                $failureMessages[] = $fullMessage;
418
            }//end if
419
        }//end foreach
420
421
        return $failureMessages;
422
    }//end generateFailureMessages()
423
424
425
    /**
426
     * Get a list of CLI values to set before the file is tested.
427
     *
428
     * @param string                  $filename The name of the file being tested.
429
     * @param Config $config   The config data for the run.
430
     *
431
     * @return void
432
     */
433
    public function setCliValues($filename, Config $config)
434
    {
435
        $config->setConfigData('php_version', 50445, true);
436
437
        return;
438
    }//end setCliValues()
439
440
441
    /**
442
     * Returns the lines where errors should occur.
443
     *
444
     * The key of the array should represent the line number and the value
445
     * should represent the number of errors that should occur on that line.
446
     *
447
     * @param string $testFile Name of the file with test data.
448
     *
449
     * @return array(int => int)
450
     */
451
    abstract protected function getErrorList($testFile);
452
453
454
    /**
455
     * Returns the lines where warnings should occur.
456
     *
457
     * The key of the array should represent the line number and the value
458
     * should represent the number of warnings that should occur on that line.
459
     *
460
     * @param string $testFile Name of the file with test data.
461
     *
462
     * @return array(int => int)
463
     */
464
    abstract protected function getWarningList($testFile);
465
}//end class
466