Completed
Branch development (b1b115)
by Johannes
10:28
created

AbstractSniffUnitTest::generateFailureMessages()   F

Complexity

Conditions 34
Paths > 20000

Size

Total Lines 189

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 189
rs 0
cc 34
nc 544124
nop 1

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
 * A sniff unit test checks a .inc file for expected violations of a single
6
 * coding standard. Expected errors and warnings that are not found, or
7
 * warnings and errors that are not expected, are considered test failures.
8
 *
9
 * @author    Greg Sherwood <[email protected]>
10
 * @copyright 2006-2015 Squiz Pty Ltd (ABN 77 084 670 600)
11
 * @license   https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
12
 */
13
14
namespace PHP_CodeSniffer\Tests\Standards;
15
16
use PHP_CodeSniffer\Config;
17
use PHP_CodeSniffer\Exceptions\RuntimeException;
18
use PHP_CodeSniffer\Ruleset;
19
use PHP_CodeSniffer\Files\LocalFile;
20
use PHP_CodeSniffer\Util\Common;
21
use PHPUnit\Framework\TestCase;
22
23
abstract class AbstractSniffUnitTest extends TestCase
24
{
25
26
    /**
27
     * Enable or disable the backup and restoration of the $GLOBALS array.
28
     * Overwrite this attribute in a child class of TestCase.
29
     * Setting this attribute in setUp() has no effect!
30
     *
31
     * @var boolean
32
     */
33
    protected $backupGlobals = false;
34
35
    /**
36
     * The path to the standard's main directory.
37
     *
38
     * @var string
39
     */
40
    public $standardsDir = null;
41
42
    /**
43
     * The path to the standard's test directory.
44
     *
45
     * @var string
46
     */
47
    public $testsDir = null;
48
49
50
    /**
51
     * Sets up this unit test.
52
     *
53
     * @return void
54
     */
55
    protected function setUp()
56
    {
57
        $class = get_class($this);
58
        $this->standardsDir = $GLOBALS['PHP_CODESNIFFER_STANDARD_DIRS'][$class];
59
        $this->testsDir     = $GLOBALS['PHP_CODESNIFFER_TEST_DIRS'][$class];
60
61
    }//end setUp()
62
63
64
    /**
65
     * Get a list of all test files to check.
66
     *
67
     * These will have the same base as the sniff name but different extensions.
68
     * We ignore the .php file as it is the class.
69
     *
70
     * @param string $testFileBase The base path that the unit tests files will have.
71
     *
72
     * @return string[]
73
     */
74
    protected function getTestFiles($testFileBase)
75
    {
76
        $testFiles = [];
77
78
        $dir = substr($testFileBase, 0, strrpos($testFileBase, DIRECTORY_SEPARATOR));
79
        $di  = new \DirectoryIterator($dir);
80
81
        foreach ($di as $file) {
82
            $path = $file->getPathname();
83
            if (substr($path, 0, strlen($testFileBase)) === $testFileBase) {
84
                if ($path !== $testFileBase.'php' && substr($path, -5) !== 'fixed') {
85
                    $testFiles[] = $path;
86
                }
87
            }
88
        }
89
90
        // Put them in order.
91
        sort($testFiles);
92
93
        return $testFiles;
94
95
    }//end getTestFiles()
96
97
98
    /**
99
     * Should this test be skipped for some reason.
100
     *
101
     * @return boolean
102
     */
103
    protected function shouldSkipTest()
104
    {
105
        return false;
106
107
    }//end shouldSkipTest()
108
109
110
    /**
111
     * Tests the extending classes Sniff class.
112
     *
113
     * @return void
114
     * @throws \PHPUnit\Framework\Exception
115
     */
116
    final public function testSniff()
117
    {
118
        // Skip this test if we can't run in this environment.
119
        if ($this->shouldSkipTest() === true) {
120
            $this->markTestSkipped();
121
        }
122
123
        $sniffCode = Common::getSniffCode(get_class($this));
124
        list($standardName, $categoryName, $sniffName) = explode('.', $sniffCode);
125
126
        $testFileBase = $this->testsDir.$categoryName.DIRECTORY_SEPARATOR.$sniffName.'UnitTest.';
127
128
        // Get a list of all test files to check.
129
        $testFiles = $this->getTestFiles($testFileBase);
130
131
        if (isset($GLOBALS['PHP_CODESNIFFER_CONFIG']) === true) {
132
            $config = $GLOBALS['PHP_CODESNIFFER_CONFIG'];
133
        } else {
134
            $config        = new Config();
135
            $config->cache = false;
136
            $GLOBALS['PHP_CODESNIFFER_CONFIG'] = $config;
137
        }
138
139
        $config->standards = [$standardName];
140
        $config->sniffs    = [$sniffCode];
141
        $config->ignored   = [];
142
143
        if (isset($GLOBALS['PHP_CODESNIFFER_RULESETS']) === false) {
144
            $GLOBALS['PHP_CODESNIFFER_RULESETS'] = [];
145
        }
146
147
        if (isset($GLOBALS['PHP_CODESNIFFER_RULESETS'][$standardName]) === false) {
148
            $ruleset = new Ruleset($config);
149
            $GLOBALS['PHP_CODESNIFFER_RULESETS'][$standardName] = $ruleset;
150
        }
151
152
        $ruleset = $GLOBALS['PHP_CODESNIFFER_RULESETS'][$standardName];
153
154
        $sniffFile = $this->standardsDir.DIRECTORY_SEPARATOR.'Sniffs'.DIRECTORY_SEPARATOR.$categoryName.DIRECTORY_SEPARATOR.$sniffName.'Sniff.php';
155
156
        $sniffClassName = substr(get_class($this), 0, -8).'Sniff';
157
        $sniffClassName = str_replace('\Tests\\', '\Sniffs\\', $sniffClassName);
158
        $sniffClassName = Common::cleanSniffClass($sniffClassName);
159
160
        $restrictions = [strtolower($sniffClassName) => true];
161
        $ruleset->registerSniffs([$sniffFile], $restrictions, []);
162
        $ruleset->populateTokenListeners();
163
164
        $failureMessages = [];
165
        foreach ($testFiles as $testFile) {
166
            $filename  = basename($testFile);
167
            $oldConfig = $config->getSettings();
168
169
            try {
170
                $this->setCliValues($filename, $config);
171
                $phpcsFile = new LocalFile($testFile, $ruleset, $config);
172
                $phpcsFile->process();
173
            } catch (RuntimeException $e) {
174
                $this->fail('An unexpected exception has been caught: '.$e->getMessage());
175
            }
176
177
            $failures        = $this->generateFailureMessages($phpcsFile);
178
            $failureMessages = array_merge($failureMessages, $failures);
179
180
            if ($phpcsFile->getFixableCount() > 0) {
181
                // Attempt to fix the errors.
182
                $phpcsFile->fixer->fixFile();
183
                $fixable = $phpcsFile->getFixableCount();
184
                if ($fixable > 0) {
185
                    $failureMessages[] = "Failed to fix $fixable fixable violations in $filename";
186
                }
187
188
                // Check for a .fixed file to check for accuracy of fixes.
189
                $fixedFile = $testFile.'.fixed';
190
                if (file_exists($fixedFile) === true) {
191
                    $diff = $phpcsFile->fixer->generateDiff($fixedFile);
192
                    if (trim($diff) !== '') {
193
                        $filename          = basename($testFile);
194
                        $fixedFilename     = basename($fixedFile);
195
                        $failureMessages[] = "Fixed version of $filename does not match expected version in $fixedFilename; the diff is\n$diff";
196
                    }
197
                }
198
            }
199
200
            // Restore the config.
201
            $config->setSettings($oldConfig);
202
        }//end foreach
203
204
        if (empty($failureMessages) === false) {
205
            $this->fail(implode(PHP_EOL, $failureMessages));
206
        }
207
208
    }//end testSniff()
209
210
211
    /**
212
     * Generate a list of test failures for a given sniffed file.
213
     *
214
     * @param \PHP_CodeSniffer\Files\LocalFile $file The file being tested.
215
     *
216
     * @return array
217
     * @throws \PHP_CodeSniffer\Exceptions\RuntimeException
218
     */
219
    public function generateFailureMessages(LocalFile $file)
220
    {
221
        $testFile = $file->getFilename();
222
223
        $foundErrors      = $file->getErrors();
224
        $foundWarnings    = $file->getWarnings();
225
        $expectedErrors   = $this->getErrorList(basename($testFile));
226
        $expectedWarnings = $this->getWarningList(basename($testFile));
227
228
        if (is_array($expectedErrors) === false) {
229
            throw new RuntimeException('getErrorList() must return an array');
230
        }
231
232
        if (is_array($expectedWarnings) === false) {
233
            throw new RuntimeException('getWarningList() must return an array');
234
        }
235
236
        /*
237
            We merge errors and warnings together to make it easier
238
            to iterate over them and produce the errors string. In this way,
239
            we can report on errors and warnings in the same line even though
240
            it's not really structured to allow that.
241
        */
242
243
        $allProblems     = [];
244
        $failureMessages = [];
245
246
        foreach ($foundErrors as $line => $lineErrors) {
247
            foreach ($lineErrors as $column => $errors) {
248
                if (isset($allProblems[$line]) === false) {
249
                    $allProblems[$line] = [
250
                        'expected_errors'   => 0,
251
                        'expected_warnings' => 0,
252
                        'found_errors'      => [],
253
                        'found_warnings'    => [],
254
                    ];
255
                }
256
257
                $foundErrorsTemp = [];
258
                foreach ($allProblems[$line]['found_errors'] as $foundError) {
259
                    $foundErrorsTemp[] = $foundError;
260
                }
261
262
                $errorsTemp = [];
263
                foreach ($errors as $foundError) {
264
                    $errorsTemp[] = $foundError['message'].' ('.$foundError['source'].')';
265
266
                    $source = $foundError['source'];
267
                    if (in_array($source, $GLOBALS['PHP_CODESNIFFER_SNIFF_CODES']) === false) {
268
                        $GLOBALS['PHP_CODESNIFFER_SNIFF_CODES'][] = $source;
269
                    }
270
271
                    if ($foundError['fixable'] === true
272
                        && in_array($source, $GLOBALS['PHP_CODESNIFFER_FIXABLE_CODES']) === false
273
                    ) {
274
                        $GLOBALS['PHP_CODESNIFFER_FIXABLE_CODES'][] = $source;
275
                    }
276
                }
277
278
                $allProblems[$line]['found_errors'] = array_merge($foundErrorsTemp, $errorsTemp);
279
            }//end foreach
280
281
            if (isset($expectedErrors[$line]) === true) {
282
                $allProblems[$line]['expected_errors'] = $expectedErrors[$line];
283
            } else {
284
                $allProblems[$line]['expected_errors'] = 0;
285
            }
286
287
            unset($expectedErrors[$line]);
288
        }//end foreach
289
290
        foreach ($expectedErrors as $line => $numErrors) {
291
            if (isset($allProblems[$line]) === false) {
292
                $allProblems[$line] = [
293
                    'expected_errors'   => 0,
294
                    'expected_warnings' => 0,
295
                    'found_errors'      => [],
296
                    'found_warnings'    => [],
297
                ];
298
            }
299
300
            $allProblems[$line]['expected_errors'] = $numErrors;
301
        }
302
303
        foreach ($foundWarnings as $line => $lineWarnings) {
304
            foreach ($lineWarnings as $column => $warnings) {
305
                if (isset($allProblems[$line]) === false) {
306
                    $allProblems[$line] = [
307
                        'expected_errors'   => 0,
308
                        'expected_warnings' => 0,
309
                        'found_errors'      => [],
310
                        'found_warnings'    => [],
311
                    ];
312
                }
313
314
                $foundWarningsTemp = [];
315
                foreach ($allProblems[$line]['found_warnings'] as $foundWarning) {
316
                    $foundWarningsTemp[] = $foundWarning;
317
                }
318
319
                $warningsTemp = [];
320
                foreach ($warnings as $warning) {
321
                    $warningsTemp[] = $warning['message'].' ('.$warning['source'].')';
322
                }
323
324
                $allProblems[$line]['found_warnings'] = array_merge($foundWarningsTemp, $warningsTemp);
325
            }//end foreach
326
327
            if (isset($expectedWarnings[$line]) === true) {
328
                $allProblems[$line]['expected_warnings'] = $expectedWarnings[$line];
329
            } else {
330
                $allProblems[$line]['expected_warnings'] = 0;
331
            }
332
333
            unset($expectedWarnings[$line]);
334
        }//end foreach
335
336
        foreach ($expectedWarnings as $line => $numWarnings) {
337
            if (isset($allProblems[$line]) === false) {
338
                $allProblems[$line] = [
339
                    'expected_errors'   => 0,
340
                    'expected_warnings' => 0,
341
                    'found_errors'      => [],
342
                    'found_warnings'    => [],
343
                ];
344
            }
345
346
            $allProblems[$line]['expected_warnings'] = $numWarnings;
347
        }
348
349
        // Order the messages by line number.
350
        ksort($allProblems);
351
352
        foreach ($allProblems as $line => $problems) {
353
            $numErrors        = count($problems['found_errors']);
354
            $numWarnings      = count($problems['found_warnings']);
355
            $expectedErrors   = $problems['expected_errors'];
356
            $expectedWarnings = $problems['expected_warnings'];
357
358
            $errors      = '';
359
            $foundString = '';
360
361
            if ($expectedErrors !== $numErrors || $expectedWarnings !== $numWarnings) {
362
                $lineMessage     = "[LINE $line]";
363
                $expectedMessage = 'Expected ';
364
                $foundMessage    = 'in '.basename($testFile).' but found ';
365
366
                if ($expectedErrors !== $numErrors) {
367
                    $expectedMessage .= "$expectedErrors error(s)";
368
                    $foundMessage    .= "$numErrors error(s)";
369
                    if ($numErrors !== 0) {
370
                        $foundString .= 'error(s)';
371
                        $errors      .= implode(PHP_EOL.' -> ', $problems['found_errors']);
372
                    }
373
374
                    if ($expectedWarnings !== $numWarnings) {
375
                        $expectedMessage .= ' and ';
376
                        $foundMessage    .= ' and ';
377
                        if ($numWarnings !== 0) {
378
                            if ($foundString !== '') {
379
                                $foundString .= ' and ';
380
                            }
381
                        }
382
                    }
383
                }
384
385
                if ($expectedWarnings !== $numWarnings) {
386
                    $expectedMessage .= "$expectedWarnings warning(s)";
387
                    $foundMessage    .= "$numWarnings warning(s)";
388
                    if ($numWarnings !== 0) {
389
                        $foundString .= 'warning(s)';
390
                        if (empty($errors) === false) {
391
                            $errors .= PHP_EOL.' -> ';
392
                        }
393
394
                        $errors .= implode(PHP_EOL.' -> ', $problems['found_warnings']);
395
                    }
396
                }
397
398
                $fullMessage = "$lineMessage $expectedMessage $foundMessage.";
399
                if ($errors !== '') {
400
                    $fullMessage .= " The $foundString found were:".PHP_EOL." -> $errors";
401
                }
402
403
                $failureMessages[] = $fullMessage;
404
            }//end if
405
        }//end foreach
406
407
        return $failureMessages;
408
409
    }//end generateFailureMessages()
410
411
412
    /**
413
     * Get a list of CLI values to set before the file is tested.
414
     *
415
     * @param string                  $filename The name of the file being tested.
416
     * @param \PHP_CodeSniffer\Config $config   The config data for the run.
417
     *
418
     * @return void
419
     */
420
    public function setCliValues($filename, $config)
421
    {
422
        return;
423
424
    }//end setCliValues()
425
426
427
    /**
428
     * Returns the lines where errors should occur.
429
     *
430
     * The key of the array should represent the line number and the value
431
     * should represent the number of errors that should occur on that line.
432
     *
433
     * @return array<int, int>
434
     */
435
    abstract protected function getErrorList();
436
437
438
    /**
439
     * Returns the lines where warnings should occur.
440
     *
441
     * The key of the array should represent the line number and the value
442
     * should represent the number of warnings that should occur on that line.
443
     *
444
     * @return array<int, int>
445
     */
446
    abstract protected function getWarningList();
447
448
449
}//end class
450