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
| 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 |