PluginProbe
MLSImport: IDX Plugin & MLS Plugin for Real Estate Listings / 5.8.4
MLSImport: IDX Plugin & MLS Plugin for Real Estate Listings v5.8.4
7.2.2 7.2.1 7.2 7.1.2 7.1.1 7.1 7.0.4 7.0.6 7.0.7 6.3.8 6.3.7 6.3.6 6.3.5 6.3.4 6.3.3 6.3.1 trunk 5.7.3 5.7.5 5.8.1 5.8.2 5.8.3 5.8.4 5.8.6 6.0.4 All 37 releases
mlsimport / vendor / phpcsstandards / phpcsutils / PHPCSUtils / TestUtils / UtilityMethodTestCase.php

UtilityMethodTestCase.php in MLSImport: IDX Plugin & MLS Plugin for Real Estate Listings 5.8.4, at vendor/phpcsstandards/phpcsutils/PHPCSUtils/TestUtils/UtilityMethodTestCase.php

460 lines 15.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * PHPCSUtils, utility functions and classes for PHP_CodeSniffer sniff developers.
4 *
5 * @package PHPCSUtils
6 * @copyright 2019-2020 PHPCSUtils Contributors
7 * @license https://opensource.org/licenses/LGPL-3.0 LGPL3
8 * @link https://github.com/PHPCSStandards/PHPCSUtils
9 */
10
11 namespace PHPCSUtils\TestUtils;
12
13 use PHP_CodeSniffer\Config;
14 use PHP_CodeSniffer\Exceptions\TokenizerException;
15 use PHP_CodeSniffer\Files\DummyFile;
16 use PHP_CodeSniffer\Files\File;
17 use PHP_CodeSniffer\Ruleset;
18 use PHPCSUtils\BackCompat\Helper;
19 use PHPCSUtils\Exceptions\TestFileNotFound;
20 use PHPCSUtils\Exceptions\TestMarkerNotFound;
21 use PHPCSUtils\Exceptions\TestTargetNotFound;
22 use PHPUnit\Framework\TestCase;
23 use ReflectionClass;
24 use ReflectionProperty;
25
26 /**
27 * Base class for use when testing utility methods for PHP_CodeSniffer.
28 *
29 * This class is compatible with PHP_CodeSniffer 3.x and contains preliminary compatibility with 4.x
30 * based on its currently known state/roadmap.
31 *
32 * This class is compatible with {@link https://phpunit.de/ PHPUnit} 4.5 - 9.x providing the PHPCSUtils
33 * autoload file is included in the test bootstrap. For more information about that, please consult
34 * the project's {@link https://github.com/PHPCSStandards/PHPCSUtils/blob/develop/README.md README}.
35 *
36 * To allow for testing of tab vs space content, the `tabWidth` is set to `4` by default.
37 *
38 * Typical usage:
39 *
40 * Test case file `path/to/ClassUnderTestUnitTest.inc`:
41 * ```php
42 * <?php
43 *
44 * /* testTestCaseDescription * /
45 * const BAR = false;
46 * ```
47 *
48 * Test file `path/to/ClassUnderTestUnitTest.php`:
49 * ```php
50 * <?php
51 *
52 * use PHPCSUtils\TestUtils\UtilityMethodTestCase;
53 * use YourStandard\ClassUnderTest;
54 *
55 * class ClassUnderTestUnitTest extends UtilityMethodTestCase {
56 *
57 * /**
58 * * Testing utility method MyMethod.
59 * *
60 * * @dataProvider dataMyMethod
61 * *
62 * * @covers \YourStandard\ClassUnderTest::MyMethod
63 * *
64 * * @param string $commentString The comment which prefaces the target token in the test file.
65 * * @param string $expected The expected return value.
66 * *
67 * * @return void
68 * * /
69 * public function testMyMethod($commentString, $expected)
70 * {
71 * $stackPtr = $this->getTargetToken($commentString, [\T_TOKEN_CONSTANT, \T_ANOTHER_TOKEN]);
72 * $class = new ClassUnderTest();
73 * $result = $class->MyMethod(self::$phpcsFile, $stackPtr);
74 * // Or for static utility methods:
75 * $result = ClassUnderTest::MyMethod(self::$phpcsFile, $stackPtr);
76 *
77 * $this->assertSame($expected, $result);
78 * }
79 *
80 * /**
81 * * Data Provider.
82 * *
83 * * @see ClassUnderTestUnitTest::testMyMethod() For the array format.
84 * *
85 * * @return array
86 * * /
87 * public function dataMyMethod()
88 * {
89 * return array(
90 * array('/* testTestCaseDescription * /', false),
91 * );
92 * }
93 * }
94 * ```
95 *
96 * Note:
97 * - Remove the space between the comment closers `* /` for a working example.
98 * - Each test case separator comment MUST start with `/* test`.
99 * This is to allow the {@see UtilityMethodTestCase::getTargetToken()} method to
100 * distinquish between the test separation comments and comments which may be part
101 * of the test case.
102 * - The test case file and unit test file should be placed in the same directory.
103 * - For working examples using this abstract class, have a look at the unit tests
104 * for the PHPCSUtils utility functions themselves.
105 *
106 * @since 1.0.0
107 */
108 abstract class UtilityMethodTestCase extends TestCase
109 {
110
111 /**
112 * The PHPCS version the tests are being run on.
113 *
114 * @since 1.0.0
115 *
116 * @var string
117 */
118 protected static $phpcsVersion = '0';
119
120 /**
121 * The file extension of the test case file (without leading dot).
122 *
123 * This allows concrete test classes to overrule the default `"inc"` with, for instance,
124 * `"js"` or `"css"` when applicable.
125 *
126 * @since 1.0.0
127 *
128 * @var string
129 */
130 protected static $fileExtension = 'inc';
131
132 /**
133 * Full path to the test case file associated with the concrete test class.
134 *
135 * Optional. If left empty, the case file will be presumed to be in
136 * the same directory and named the same as the test class, but with an
137 * `"inc"` file extension.
138 *
139 * @since 1.0.0
140 *
141 * @var string
142 */
143 protected static $caseFile = '';
144
145 /**
146 * The tab width setting to use when tokenizing the file.
147 *
148 * This allows for test case files to use a different tab width than the default.
149 *
150 * @since 1.0.0
151 *
152 * @var int
153 */
154 protected static $tabWidth = 4;
155
156 /**
157 * The \PHP_CodeSniffer\Files\File object containing the parsed contents of the test case file.
158 *
159 * @since 1.0.0
160 *
161 * @var \PHP_CodeSniffer\Files\File|null
162 */
163 protected static $phpcsFile;
164
165 /**
166 * Set the name of a sniff to pass to PHPCS to limit the run (and force it to record errors).
167 *
168 * Normally, this propery won't need to be overloaded, but for utility methods which record
169 * violations and contain fixers, setting a dummy sniff name equal to the sniff name passed
170 * in the error code for `addError()`/`addWarning()` during the test, will allow for testing
171 * the recording of these violations, as well as testing the fixer.
172 *
173 * @since 1.0.0
174 *
175 * @var array<string>
176 */
177 protected static $selectedSniff = ['Dummy.Dummy.Dummy'];
178
179 /**
180 * Initialize PHPCS & tokenize the test case file.
181 *
182 * The test case file for a unit test class has to be in the same directory
183 * directory and use the same file name as the test class, using the `.inc` extension
184 * or be explicitly set using the {@see UtilityMethodTestCase::$fileExtension}/
185 * {@see UtilityMethodTestCase::$caseFile} properties.
186 *
187 * Note: This is a PHPUnit cross-version compatible {@see \PHPUnit\Framework\TestCase::setUpBeforeClass()}
188 * method.
189 *
190 * @since 1.0.0
191 *
192 * @beforeClass
193 *
194 * @return void
195 */
196 public static function setUpTestFile()
197 {
198 parent::setUpBeforeClass();
199
200 self::$phpcsVersion = Helper::getVersion();
201
202 $caseFile = static::$caseFile;
203 if (\is_string($caseFile) === false || $caseFile === '') {
204 $testClass = \get_called_class();
205 $testFile = (new ReflectionClass($testClass))->getFileName();
206 $caseFile = \substr($testFile, 0, -3) . static::$fileExtension;
207 }
208
209 if (\is_readable($caseFile) === false) {
210 parent::fail("Test case file missing. Expected case file location: $caseFile");
211 }
212
213 $contents = \file_get_contents($caseFile);
214
215 /*
216 * Set the static properties in the Config class to specific values for performance
217 * and to clear out values from other tests.
218 */
219 self::setStaticConfigProperty('executablePaths', []);
220
221 // Set to values which prevent the test-runner user's `CodeSniffer.conf` file
222 // from being read and influencing the tests. Also prevent an `exec()` call to stty.
223 self::setStaticConfigProperty('configData', ['report_width' => 80]);
224 self::setStaticConfigProperty('configDataFile', '');
225
226 $config = new Config();
227
228 /*
229 * Set to a usable value to circumvent Config trying to find a phpcs.xml config file.
230 *
231 * We just need to provide a standard so PHPCS will tokenize the file.
232 * The standard itself doesn't actually matter for testing utility methods,
233 * so use the smallest one to get the fastest results.
234 */
235 $config->standards = ['PSR1'];
236
237 /*
238 * Limiting the run to just one sniff will make it, yet again, slightly faster.
239 * Picked the simplest/fastest sniff available which is registered in PSR1.
240 */
241 $config->sniffs = static::$selectedSniff;
242
243 // Disable caching.
244 $config->cache = false;
245
246 // Also set a tab-width to enable testing tab-replaced vs `orig_content`.
247 $config->tabWidth = static::$tabWidth;
248
249 $ruleset = new Ruleset($config);
250
251 // Make sure the file gets parsed correctly based on the file type.
252 $contents = 'phpcs_input_file: ' . $caseFile . \PHP_EOL . $contents;
253
254 self::$phpcsFile = new DummyFile($contents, $ruleset, $config);
255
256 // Only tokenize the file, do not process it.
257 try {
258 self::$phpcsFile->parse();
259 } catch (TokenizerException $e) {
260 // PHPCS 3.5.0 and higher. This is handled below.
261 }
262
263 // Fail the test if the case file failed to tokenize.
264 if (self::$phpcsFile->numTokens === 0) {
265 parent::fail("Tokenizing of the test case file failed for case file: $caseFile");
266 }
267 }
268
269 /**
270 * Skip JS and CSS related tests on PHPCS 4.x.
271 *
272 * PHPCS 4.x drops support for the JS and CSS tokenizers.
273 * This method takes care of automatically skipping tests involving JS/CSS case files
274 * when the tests are being run with PHPCS 4.x.
275 *
276 * Note: This is a PHPUnit cross-version compatible {@see \PHPUnit\Framework\TestCase::setUp()}
277 * method.
278 *
279 * @since 1.0.0
280 *
281 * @before
282 *
283 * @return void
284 */
285 public function skipJSCSSTestsOnPHPCS4()
286 {
287 if (static::$fileExtension !== 'js' && static::$fileExtension !== 'css') {
288 return;
289 }
290
291 if (\version_compare(self::$phpcsVersion, '3.99.99', '<=')) {
292 return;
293 }
294
295 $this->markTestSkipped('JS and CSS support has been removed in PHPCS 4.');
296 }
297
298 /**
299 * Clean up after finished test by resetting all static properties to their default values.
300 *
301 * Note: This is a PHPUnit cross-version compatible {@see \PHPUnit\Framework\TestCase::tearDownAfterClass()}
302 * method.
303 *
304 * @since 1.0.0
305 *
306 * @afterClass
307 *
308 * @return void
309 */
310 public static function resetTestFile()
311 {
312 self::$phpcsVersion = '0';
313 self::$fileExtension = 'inc';
314 self::$caseFile = '';
315 self::$tabWidth = 4;
316 self::$phpcsFile = null;
317 self::$selectedSniff = ['Dummy.Dummy.Dummy'];
318
319 // Reset the static properties in the Config class to their defaults to prevent tests influencing each other.
320 self::setStaticConfigProperty('executablePaths', []);
321 self::setStaticConfigProperty('configData', null);
322 self::setStaticConfigProperty('configDataFile', null);
323 }
324
325 /**
326 * Helper function to set the value of a private static property on the PHPCS Config class.
327 *
328 * @since 1.0.9
329 *
330 * @param string $name The name of the property to set.
331 * @param mixed $value The value to set the property to.
332 *
333 * @return void
334 */
335 public static function setStaticConfigProperty($name, $value)
336 {
337 $property = new ReflectionProperty('PHP_CodeSniffer\Config', $name);
338 $property->setAccessible(true);
339 $property->setValue(null, $value);
340 $property->setAccessible(false);
341 }
342
343 /**
344 * Check whether or not the PHP 8.0 identifier name tokens will be in use.
345 *
346 * The expected token positions/token counts for certain tokens will differ depending
347 * on whether the PHP 8.0 identifier name tokenization is used or the PHP < 8.0
348 * identifier name tokenization.
349 *
350 * Tests can use this method to determine which flavour of tokenization to expect and
351 * to set test expectations accordingly.
352 *
353 * @codeCoverageIgnore Nothing to test.
354 *
355 * @since 1.0.0
356 *
357 * @return bool
358 */
359 public static function usesPhp8NameTokens()
360 {
361 return \version_compare(Helper::getVersion(), '3.99.99', '>=');
362 }
363
364 /**
365 * Get the token pointer for a target token based on a specific comment.
366 *
367 * Note: the test delimiter comment MUST start with `/* test` to allow this function to
368 * distinguish between comments used *in* a test and test delimiters.
369 *
370 * @since 1.0.0
371 *
372 * @param string $commentString The complete delimiter comment to look for as a string.
373 * This string should include the comment opener and closer.
374 * @param int|string|array<int|string> $tokenType The type of token(s) to look for.
375 * @param string|null $tokenContent Optional. The token content for the target token.
376 *
377 * @return int
378 *
379 * @throws \PHPCSUtils\Exceptions\TestMarkerNotFound When the delimiter comment for the test was not found.
380 * @throws \PHPCSUtils\Exceptions\TestTargetNotFound When the target token cannot be found.
381 */
382 public static function getTargetToken($commentString, $tokenType, $tokenContent = null)
383 {
384 if ((self::$phpcsFile instanceof File) === false) {
385 throw new TestFileNotFound();
386 }
387
388 $start = (self::$phpcsFile->numTokens - 1);
389 $comment = self::$phpcsFile->findPrevious(
390 \T_COMMENT,
391 $start,
392 null,
393 false,
394 $commentString
395 );
396
397 if ($comment === false) {
398 throw TestMarkerNotFound::create($commentString, self::$phpcsFile->getFilename());
399 }
400
401 $tokens = self::$phpcsFile->getTokens();
402 $end = ($start + 1);
403
404 // Limit the token finding to between this and the next delimiter comment.
405 for ($i = ($comment + 1); $i < $end; $i++) {
406 if ($tokens[$i]['code'] !== \T_COMMENT) {
407 continue;
408 }
409
410 if (\stripos($tokens[$i]['content'], '/* test') === 0) {
411 $end = $i;
412 break;
413 }
414 }
415
416 $target = self::$phpcsFile->findNext(
417 $tokenType,
418 ($comment + 1),
419 $end,
420 false,
421 $tokenContent
422 );
423
424 if ($target === false) {
425 throw TestTargetNotFound::create($commentString, $tokenContent, self::$phpcsFile->getFilename());
426 }
427
428 return $target;
429 }
430
431 /**
432 * Helper method to tell PHPUnit to expect a PHPCS Exception in a PHPUnit and PHPCS cross-version
433 * compatible manner.
434 *
435 * @since 1.0.0
436 *
437 * @param string $msg The expected exception message.
438 * @param string $type The PHPCS native exception type to expect. Either 'runtime' or 'tokenizer'.
439 * Defaults to 'runtime'.
440 *
441 * @return void
442 */
443 public function expectPhpcsException($msg, $type = 'runtime')
444 {
445 $exception = 'PHP_CodeSniffer\Exceptions\RuntimeException';
446 if ($type === 'tokenizer') {
447 $exception = 'PHP_CodeSniffer\Exceptions\TokenizerException';
448 }
449
450 if (\method_exists($this, 'expectException')) {
451 // PHPUnit 5+.
452 $this->expectException($exception);
453 $this->expectExceptionMessage($msg);
454 } else {
455 // PHPUnit 4.
456 $this->setExpectedException($exception, $msg);
457 }
458 }
459 }
460