Coveralls logob
Coveralls logo
  • Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

PHPCSStandards / PHPCSUtils / 360

21 Feb 2020 - 23:25 coverage: 91.695% (-6.05%) from 97.743%
360

Pull #100

travis-ci-com

9181eb84f9c35729a3bad740fb7f9d93?size=18&default=identiconweb-flow
PHPUnit: be strict about covers annotations
Pull Request #100: PHPUnit: be strict about covers annotations

2153 of 2348 relevant lines covered (91.7%)

37.38 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

98.41
/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\Exceptions\RuntimeException;
14
use PHP_CodeSniffer\Exceptions\TokenizerException;
15
use PHPCSUtils\BackCompat\Helper;
16
use PHPUnit\Framework\TestCase;
17
use ReflectionClass;
18

19
/**
20
 * Base class for use when testing utility methods for PHP_CodeSniffer.
21
 *
22
 * This class is compatible with PHP_CodeSniffer 2.x as well as 3.x.
23
 *
24
 * This class is compatible with PHPUnit 4.5 - 8.x providing the PHPCSUtils autoload
25
 * file is included in the test bootstrap.
26
 *
27
 * To allow for testing of tab vs space content, the tabWidth is set to `4` by default.
28
 *
29
 * Typical usage:
30
 *
31
 * Test case file `path/to/ClassUnderTestUnitTest.inc`:
32
 * ```php
33
 * <?php
34
 *
35
 * /* testTestCaseDescription * /
36
 * const BAR = false;
37
 * ```
38
 *
39
 * Test file `path/to/ClassUnderTestUnitTest.php`:
40
 * ```php
41
 * <?php
42
 *
43
 * use PHPCSUtils\TestUtils\UtilityMethodTestCase;
44
 * use YourStandard\ClassUnderTest;
45
 *
46
 * class ClassUnderTestUnitTest extends UtilityMethodTestCase {
47
 *
48
 *     /**
49
 *      * Testing utility method MyMethod.
50
 *      *
51
 *      * @dataProvider dataMyMethod
52
 *      *
53
 *      * @covers \YourStandard\ClassUnderTest::MyMethod
54
 *      *
55
 *      * @param string $commentString The comment which prefaces the target token in the test file.
56
 *      * @param string $expected      The expected return value.
57
 *      *
58
 *      * @return void
59
 *      * /
60
 *    public function testMyMethod($commentString, $expected)
61
 *    {
62
 *        $stackPtr = $this->getTargetToken($commentString, [\T_TOKEN_CONSTANT, \T_ANOTHER_TOKEN]);
63
 *        $class    = new ClassUnderTest();
64
 *        $result   = $class->MyMethod(self::$phpcsFile, $stackPtr);
65
 *        // Or for static utility methods:
66
 *        $result   = ClassUnderTest::MyMethod(self::$phpcsFile, $stackPtr);
67
 *
68
 *        $this->assertSame($expected, $result);
69
 *    }
70
 *
71
 *    /**
72
 *     * Data Provider.
73
 *     *
74
 *     * @see testMyMethod() For the array format.
75
 *     *
76
 *     * @return array
77
 *     * /
78
 *    public function dataMyMethod()
79
 *    {
80
 *        return array(
81
 *            array('/* testTestCaseDescription * /', false),
82
 *        );
83
 *    }
84
 * }
85
 * ```
86
 *
87
 * Note:
88
 * - Remove the space between the comment closers `* /` for a working example.
89
 * - Each test case separator comment MUST start with `/* test`.
90
 *   This is to allow the `getTargetToken()` method to distinquish between the
91
 *   test separation comments and comments which may be part of the test case.
92
 * - The test case file and unit test file should be placed in the same directory.
93
 * - For working examples using this abstract class, have a look at the unit tests
94
 *   for the PHPCSUtils utility functions themselves.
95
 *
96
 * @since 1.0.0
97
 */
98
abstract class UtilityMethodTestCase extends TestCase
99
{
100

101
    /**
102
     * The file extension of the test case file (without leading dot).
103
     *
104
     * This allows concrete test classes to overrule the default `inc` with, for instance,
105
     * `js` or `css` when applicable.
106
     *
107
     * @since 1.0.0
108
     *
109
     * @var string
110
     */
111
    protected static $fileExtension = 'inc';
112

113
    /**
114
     * Full path to the test case file associated with the concrete test class.
115
     *
116
     * Optional. If left empty, the case file will be presumed to be in
117
     * the same directory and named the same as the test class, but with an
118
     * `inc` file extension.
119
     *
120
     * @since 1.0.0
121
     *
122
     * @var string
123
     */
124
    protected static $caseFile = '';
125

126
    /**
127
     * The tab width setting to use when tokenizing the file.
128
     *
129
     * This allows for test case files to use a different tab width than the default.
130
     *
131
     * @since 1.0.0
132
     *
133
     * @var int
134
     */
135
    protected static $tabWidth = 4;
136

137
    /**
138
     * The {@see \PHP_CodeSniffer\Files\File} object containing the parsed contents of the test case file.
139
     *
140
     * @since 1.0.0
141
     *
142
     * @var \PHP_CodeSniffer\Files\File
143
     */
144
    protected static $phpcsFile;
145

146
    /**
147
     * Set the name of a sniff to pass to PHPCS to limit the run (and force it to record errors).
148
     *
149
     * Normally, this propery won't need to be overloaded, but for utility methods which record
150
     * violations and contain fixers, setting a dummy sniff name equal to the sniff name passed
151
     * in the error code for `addError()`/`addWarning()` during the test, will allow for testing
152
     * the recording of these violations, as well as testing the fixer.
153
     *
154
     * @since 1.0.0
155
     *
156
     * @var array
157
     */
158
    protected static $selectedSniff = ['Dummy.Dummy.Dummy'];
159

160
    /**
161
     * Initialize PHPCS & tokenize the test case file.
162
     *
163
     * The test case file for a unit test class has to be in the same directory
164
     * directory and use the same file name as the test class, using the .inc extension.
165
     *
166
     * @since 1.0.0
167
     *
168
     * @beforeClass
169
     *
170
     * @return void
171
     */
172
    public static function setUpTestFile()
173
    {
174
        parent::setUpBeforeClass();
8×
175

176
        $caseFile = static::$caseFile;
8×
177
        if (\is_string($caseFile) === false || $caseFile === '') {
8×
178
            $testClass = \get_called_class();
8×
179
            $testFile  = (new ReflectionClass($testClass))->getFileName();
8×
180
            $caseFile  = \substr($testFile, 0, -3) . static::$fileExtension;
8×
181
        }
182

183
        if (\is_readable($caseFile) === false) {
8×
184
            parent::fail("Test case file missing. Expected case file location: $caseFile");
4×
185
        }
186

187
        $contents = \file_get_contents($caseFile);
4×
188

189
        if (\version_compare(Helper::getVersion(), '2.99.99', '>')) {
4×
190
            // PHPCS 3.x.
191
            $config = new \PHP_CodeSniffer\Config();
2×
192

193
            /*
194
             * We just need to provide a standard so PHPCS will tokenize the file.
195
             * The standard itself doesn't actually matter for testing utility methods,
196
             * so use the smallest one to get the fastest results.
197
             */
198
            $config->standards = ['PSR1'];
2×
199

200
            /*
201
             * Limiting the run to just one sniff will make it, yet again, slightly faster.
202
             * Picked the simplest/fastest sniff available which is registered in PSR1.
203
             */
204
            $config->sniffs = static::$selectedSniff;
2×
205

206
            // Disable caching.
207
            $config->cache = false;
2×
208

209
            // Also set a tab-width to enable testing tab-replaced vs `orig_content`.
210
            $config->tabWidth = static::$tabWidth;
2×
211

212
            $ruleset = new \PHP_CodeSniffer\Ruleset($config);
2×
213

214
            // Make sure the file gets parsed correctly based on the file type.
215
            $contents = 'phpcs_input_file: ' . $caseFile . \PHP_EOL . $contents;
2×
216

217
            self::$phpcsFile = new \PHP_CodeSniffer\Files\DummyFile($contents, $ruleset, $config);
2×
218

219
            // Only tokenize the file, do not process it.
220
            try {
221
                self::$phpcsFile->parse();
2×
222
            } catch (TokenizerException $e) {
2×
223
                // PHPCS 3.5.0 and higher.
UNCOV
224
            } catch (RuntimeException $e) {
!
225
                // PHPCS 3.0.0 < 3.5.0.
226
            }
227
        } else {
228
            // PHPCS 2.x.
229
            $phpcs           = new \PHP_CodeSniffer(null, static::$tabWidth);
2×
230
            self::$phpcsFile = new \PHP_CodeSniffer_File(
2×
231
                $caseFile,
2×
232
                [],
2×
233
                [],
2×
234
                $phpcs
235
            );
236

237
            /*
238
             * Using error silencing to drown out "undefined index" notices for tokenizer
239
             * issues in PHPCS 2.x which won't get fixed anymore anyway.
240
             */
241
            @self::$phpcsFile->start($contents);
2×
242
        }
243

244
        // Fail the test if the case file failed to tokenize.
245
        if (self::$phpcsFile->numTokens === 0) {
4×
246
            parent::fail("Tokenizing of the test case file failed for case file: $caseFile");
2×
247
        }
248
    }
2×
249

250
    /**
251
     * Clean up after finished test.
252
     *
253
     * @since 1.0.0
254
     *
255
     * @afterClass
256
     *
257
     * @return void
258
     */
259
    public static function resetTestFile()
260
    {
261
        self::$phpcsFile = null;
4×
262
    }
4×
263

264
    /**
265
     * Get the token pointer for a target token based on a specific comment found on the line before.
266
     *
267
     * Note: the test delimiter comment MUST start with "/* test" to allow this function to
268
     * distinguish between comments used *in* a test and test delimiters.
269
     *
270
     * @since 1.0.0
271
     *
272
     * @param string           $commentString The delimiter comment to look for.
273
     * @param int|string|array $tokenType     The type of token(s) to look for.
274
     * @param string           $tokenContent  Optional. The token content for the target token.
275
     *
276
     * @return int
277
     */
278
    public function getTargetToken($commentString, $tokenType, $tokenContent = null)
279
    {
280
        $start   = (self::$phpcsFile->numTokens - 1);
40×
281
        $comment = self::$phpcsFile->findPrevious(
40×
282
            \T_COMMENT,
40×
283
            $start,
284
            null,
40×
285
            false,
40×
286
            $commentString
287
        );
288

289
        $tokens = self::$phpcsFile->getTokens();
40×
290
        $end    = ($start + 1);
40×
291

292
        // Limit the token finding to between this and the next delimiter comment.
293
        for ($i = ($comment + 1); $i < $end; $i++) {
40×
294
            if ($tokens[$i]['code'] !== \T_COMMENT) {
40×
295
                continue;
40×
296
            }
297

298
            if (\stripos($tokens[$i]['content'], '/* test') === 0) {
40×
299
                $end = $i;
40×
300
                break;
40×
301
            }
302
        }
303

304
        $target = self::$phpcsFile->findNext(
40×
305
            $tokenType,
40×
306
            ($comment + 1),
40×
307
            $end,
308
            false,
40×
309
            $tokenContent
310
        );
311

312
        if ($target === false) {
40×
313
            $msg = 'Failed to find test target token for comment string: ' . $commentString;
4×
314
            if ($tokenContent !== null) {
4×
315
                $msg .= ' With token content: ' . $tokenContent;
4×
316
            }
317

318
            $this->fail($msg);
4×
319
        }
320

321
        return $target;
36×
322
    }
323

324
    /**
325
     * Helper method to tell PHPUnit to expect a PHPCS Exception in a PHPUnit cross-version
326
     * compatible manner.
327
     *
328
     * @since 1.0.0
329
     *
330
     * @param string $msg  The expected exception message.
331
     * @param string $type The exception type to expect. Either 'runtime' or 'tokenizer'.
332
     *                     Defaults to 'runtime'.
333
     *
334
     * @return void
335
     */
336
    public function expectPhpcsException($msg, $type = 'runtime')
337
    {
338
        $exception = 'PHP_CodeSniffer\Exceptions\RuntimeException';
8×
339
        if ($type === 'tokenizer') {
8×
340
            $exception = 'PHP_CodeSniffer\Exceptions\TokenizerException';
4×
341
        }
342

343
        if (\method_exists($this, 'expectException')) {
8×
344
            // PHPUnit 5+.
345
            $this->expectException($exception);
4×
346
            $this->expectExceptionMessage($msg);
4×
347
        } else {
348
            // PHPUnit 4.
349
            $this->setExpectedException($exception, $msg);
4×
350
        }
351
    }
8×
352
}
Troubleshooting · Open an Issue · Sales · Support · ENTERPRISE · CAREERS · STATUS
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2023 Coveralls, Inc