comparison vendor/squizlabs/php_codesniffer/tests/Standards/AbstractSniffUnitTest.php @ 17:129ea1e6d783

Update, including to Drupal core 8.6.10
author Chris Cannam
date Thu, 28 Feb 2019 13:21:36 +0000
parents
children af1871eacc83
comparison
equal deleted inserted replaced
16:c2387f117808 17:129ea1e6d783
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 <gsherwood@squiz.net>
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' && substr($path, -4) !== '.bak') {
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 $GLOBALS['PHP_CODESNIFFER_SNIFF_CASE_FILES'][] = $testFiles;
131
132 if (isset($GLOBALS['PHP_CODESNIFFER_CONFIG']) === true) {
133 $config = $GLOBALS['PHP_CODESNIFFER_CONFIG'];
134 } else {
135 $config = new Config();
136 $config->cache = false;
137 $GLOBALS['PHP_CODESNIFFER_CONFIG'] = $config;
138 }
139
140 $config->standards = [$standardName];
141 $config->sniffs = [$sniffCode];
142 $config->ignored = [];
143
144 if (isset($GLOBALS['PHP_CODESNIFFER_RULESETS']) === false) {
145 $GLOBALS['PHP_CODESNIFFER_RULESETS'] = [];
146 }
147
148 if (isset($GLOBALS['PHP_CODESNIFFER_RULESETS'][$standardName]) === false) {
149 $ruleset = new Ruleset($config);
150 $GLOBALS['PHP_CODESNIFFER_RULESETS'][$standardName] = $ruleset;
151 }
152
153 $ruleset = $GLOBALS['PHP_CODESNIFFER_RULESETS'][$standardName];
154
155 $sniffFile = $this->standardsDir.DIRECTORY_SEPARATOR.'Sniffs'.DIRECTORY_SEPARATOR.$categoryName.DIRECTORY_SEPARATOR.$sniffName.'Sniff.php';
156
157 $sniffClassName = substr(get_class($this), 0, -8).'Sniff';
158 $sniffClassName = str_replace('\Tests\\', '\Sniffs\\', $sniffClassName);
159 $sniffClassName = Common::cleanSniffClass($sniffClassName);
160
161 $restrictions = [strtolower($sniffClassName) => true];
162 $ruleset->registerSniffs([$sniffFile], $restrictions, []);
163 $ruleset->populateTokenListeners();
164
165 $failureMessages = [];
166 foreach ($testFiles as $testFile) {
167 $filename = basename($testFile);
168 $oldConfig = $config->getSettings();
169
170 try {
171 $this->setCliValues($filename, $config);
172 $phpcsFile = new LocalFile($testFile, $ruleset, $config);
173 $phpcsFile->process();
174 } catch (RuntimeException $e) {
175 $this->fail('An unexpected exception has been caught: '.$e->getMessage());
176 }
177
178 $failures = $this->generateFailureMessages($phpcsFile);
179 $failureMessages = array_merge($failureMessages, $failures);
180
181 if ($phpcsFile->getFixableCount() > 0) {
182 // Attempt to fix the errors.
183 $phpcsFile->fixer->fixFile();
184 $fixable = $phpcsFile->getFixableCount();
185 if ($fixable > 0) {
186 $failureMessages[] = "Failed to fix $fixable fixable violations in $filename";
187 }
188
189 // Check for a .fixed file to check for accuracy of fixes.
190 $fixedFile = $testFile.'.fixed';
191 if (file_exists($fixedFile) === true) {
192 $diff = $phpcsFile->fixer->generateDiff($fixedFile);
193 if (trim($diff) !== '') {
194 $filename = basename($testFile);
195 $fixedFilename = basename($fixedFile);
196 $failureMessages[] = "Fixed version of $filename does not match expected version in $fixedFilename; the diff is\n$diff";
197 }
198 }
199 }
200
201 // Restore the config.
202 $config->setSettings($oldConfig);
203 }//end foreach
204
205 if (empty($failureMessages) === false) {
206 $this->fail(implode(PHP_EOL, $failureMessages));
207 }
208
209 }//end testSniff()
210
211
212 /**
213 * Generate a list of test failures for a given sniffed file.
214 *
215 * @param \PHP_CodeSniffer\Files\LocalFile $file The file being tested.
216 *
217 * @return array
218 * @throws \PHP_CodeSniffer\Exceptions\RuntimeException
219 */
220 public function generateFailureMessages(LocalFile $file)
221 {
222 $testFile = $file->getFilename();
223
224 $foundErrors = $file->getErrors();
225 $foundWarnings = $file->getWarnings();
226 $expectedErrors = $this->getErrorList(basename($testFile));
227 $expectedWarnings = $this->getWarningList(basename($testFile));
228
229 if (is_array($expectedErrors) === false) {
230 throw new RuntimeException('getErrorList() must return an array');
231 }
232
233 if (is_array($expectedWarnings) === false) {
234 throw new RuntimeException('getWarningList() must return an array');
235 }
236
237 /*
238 We merge errors and warnings together to make it easier
239 to iterate over them and produce the errors string. In this way,
240 we can report on errors and warnings in the same line even though
241 it's not really structured to allow that.
242 */
243
244 $allProblems = [];
245 $failureMessages = [];
246
247 foreach ($foundErrors as $line => $lineErrors) {
248 foreach ($lineErrors as $column => $errors) {
249 if (isset($allProblems[$line]) === false) {
250 $allProblems[$line] = [
251 'expected_errors' => 0,
252 'expected_warnings' => 0,
253 'found_errors' => [],
254 'found_warnings' => [],
255 ];
256 }
257
258 $foundErrorsTemp = [];
259 foreach ($allProblems[$line]['found_errors'] as $foundError) {
260 $foundErrorsTemp[] = $foundError;
261 }
262
263 $errorsTemp = [];
264 foreach ($errors as $foundError) {
265 $errorsTemp[] = $foundError['message'].' ('.$foundError['source'].')';
266
267 $source = $foundError['source'];
268 if (in_array($source, $GLOBALS['PHP_CODESNIFFER_SNIFF_CODES']) === false) {
269 $GLOBALS['PHP_CODESNIFFER_SNIFF_CODES'][] = $source;
270 }
271
272 if ($foundError['fixable'] === true
273 && in_array($source, $GLOBALS['PHP_CODESNIFFER_FIXABLE_CODES']) === false
274 ) {
275 $GLOBALS['PHP_CODESNIFFER_FIXABLE_CODES'][] = $source;
276 }
277 }
278
279 $allProblems[$line]['found_errors'] = array_merge($foundErrorsTemp, $errorsTemp);
280 }//end foreach
281
282 if (isset($expectedErrors[$line]) === true) {
283 $allProblems[$line]['expected_errors'] = $expectedErrors[$line];
284 } else {
285 $allProblems[$line]['expected_errors'] = 0;
286 }
287
288 unset($expectedErrors[$line]);
289 }//end foreach
290
291 foreach ($expectedErrors as $line => $numErrors) {
292 if (isset($allProblems[$line]) === false) {
293 $allProblems[$line] = [
294 'expected_errors' => 0,
295 'expected_warnings' => 0,
296 'found_errors' => [],
297 'found_warnings' => [],
298 ];
299 }
300
301 $allProblems[$line]['expected_errors'] = $numErrors;
302 }
303
304 foreach ($foundWarnings as $line => $lineWarnings) {
305 foreach ($lineWarnings as $column => $warnings) {
306 if (isset($allProblems[$line]) === false) {
307 $allProblems[$line] = [
308 'expected_errors' => 0,
309 'expected_warnings' => 0,
310 'found_errors' => [],
311 'found_warnings' => [],
312 ];
313 }
314
315 $foundWarningsTemp = [];
316 foreach ($allProblems[$line]['found_warnings'] as $foundWarning) {
317 $foundWarningsTemp[] = $foundWarning;
318 }
319
320 $warningsTemp = [];
321 foreach ($warnings as $warning) {
322 $warningsTemp[] = $warning['message'].' ('.$warning['source'].')';
323 }
324
325 $allProblems[$line]['found_warnings'] = array_merge($foundWarningsTemp, $warningsTemp);
326 }//end foreach
327
328 if (isset($expectedWarnings[$line]) === true) {
329 $allProblems[$line]['expected_warnings'] = $expectedWarnings[$line];
330 } else {
331 $allProblems[$line]['expected_warnings'] = 0;
332 }
333
334 unset($expectedWarnings[$line]);
335 }//end foreach
336
337 foreach ($expectedWarnings as $line => $numWarnings) {
338 if (isset($allProblems[$line]) === false) {
339 $allProblems[$line] = [
340 'expected_errors' => 0,
341 'expected_warnings' => 0,
342 'found_errors' => [],
343 'found_warnings' => [],
344 ];
345 }
346
347 $allProblems[$line]['expected_warnings'] = $numWarnings;
348 }
349
350 // Order the messages by line number.
351 ksort($allProblems);
352
353 foreach ($allProblems as $line => $problems) {
354 $numErrors = count($problems['found_errors']);
355 $numWarnings = count($problems['found_warnings']);
356 $expectedErrors = $problems['expected_errors'];
357 $expectedWarnings = $problems['expected_warnings'];
358
359 $errors = '';
360 $foundString = '';
361
362 if ($expectedErrors !== $numErrors || $expectedWarnings !== $numWarnings) {
363 $lineMessage = "[LINE $line]";
364 $expectedMessage = 'Expected ';
365 $foundMessage = 'in '.basename($testFile).' but found ';
366
367 if ($expectedErrors !== $numErrors) {
368 $expectedMessage .= "$expectedErrors error(s)";
369 $foundMessage .= "$numErrors error(s)";
370 if ($numErrors !== 0) {
371 $foundString .= 'error(s)';
372 $errors .= implode(PHP_EOL.' -> ', $problems['found_errors']);
373 }
374
375 if ($expectedWarnings !== $numWarnings) {
376 $expectedMessage .= ' and ';
377 $foundMessage .= ' and ';
378 if ($numWarnings !== 0) {
379 if ($foundString !== '') {
380 $foundString .= ' and ';
381 }
382 }
383 }
384 }
385
386 if ($expectedWarnings !== $numWarnings) {
387 $expectedMessage .= "$expectedWarnings warning(s)";
388 $foundMessage .= "$numWarnings warning(s)";
389 if ($numWarnings !== 0) {
390 $foundString .= 'warning(s)';
391 if (empty($errors) === false) {
392 $errors .= PHP_EOL.' -> ';
393 }
394
395 $errors .= implode(PHP_EOL.' -> ', $problems['found_warnings']);
396 }
397 }
398
399 $fullMessage = "$lineMessage $expectedMessage $foundMessage.";
400 if ($errors !== '') {
401 $fullMessage .= " The $foundString found were:".PHP_EOL." -> $errors";
402 }
403
404 $failureMessages[] = $fullMessage;
405 }//end if
406 }//end foreach
407
408 return $failureMessages;
409
410 }//end generateFailureMessages()
411
412
413 /**
414 * Get a list of CLI values to set before the file is tested.
415 *
416 * @param string $filename The name of the file being tested.
417 * @param \PHP_CodeSniffer\Config $config The config data for the run.
418 *
419 * @return void
420 */
421 public function setCliValues($filename, $config)
422 {
423 return;
424
425 }//end setCliValues()
426
427
428 /**
429 * Returns the lines where errors should occur.
430 *
431 * The key of the array should represent the line number and the value
432 * should represent the number of errors that should occur on that line.
433 *
434 * @return array<int, int>
435 */
436 abstract protected function getErrorList();
437
438
439 /**
440 * Returns the lines where warnings should occur.
441 *
442 * The key of the array should represent the line number and the value
443 * should represent the number of warnings that should occur on that line.
444 *
445 * @return array<int, int>
446 */
447 abstract protected function getWarningList();
448
449
450 }//end class