Completed
Pull Request — master (#193)
by Juliette
02:50
created

BaseSniffTest::setUpBeforeClass()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 4
rs 10
cc 1
eloc 2
nc 1
nop 0
1
<?php
2
/**
3
 * Base sniff test class file
4
 *
5
 * @package PHPCompatibility
6
 */
7
8
/**
9
 * BaseSniffTest
10
 *
11
 * Adds PHPCS sniffing logic and custom assertions for PHPCS errors and
12
 * warnings
13
 *
14
 * @uses PHPUnit_Framework_TestCase
15
 * @package PHPCompatibility
16
 * @author Jansen Price <[email protected]>
17
 */
18
class BaseSniffTest extends PHPUnit_Framework_TestCase
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class must be in a namespace of at least one level to avoid collisions.

You can fix this by adding a namespace to your class:

namespace YourVendor;

class YourClass { }

When choosing a vendor namespace, try to pick something that is not too generic to avoid conflicts with other libraries.

Loading history...
19
{
20
    /**
21
     * The PHP_CodeSniffer object used for testing.
22
     *
23
     * @var PHP_CodeSniffer
24
     */
25
    protected static $phpcs = null;
26
27
    /**
28
     * An array of PHPCS results by filename and PHP version.
29
     *
30
     * @var array
31
     */
32
    public static $sniffFiles = array();
33
34
    /**
35
     * Sets up this unit test.
36
     *
37
     * @return void
38
     */
39
    public static function setUpBeforeClass()
40
    {
41
        self::$sniffFiles = array();
42
    }
43
44
    /**
45
     * Sets up this unit test.
46
     *
47
     * @return void
48
     */
49
    protected function setUp()
50
    {
51
        if (self::$phpcs === null) {
52
            self::$phpcs = new PHP_CodeSniffer();
53
        }
54
55
        PHP_CodeSniffer::setConfigData('testVersion', null, true);
56
        if (method_exists('PHP_CodeSniffer_CLI', 'setCommandLineValues')) { // For PHPCS 2.x
57
            self::$phpcs->cli->setCommandLineValues(array('-pq', '--colors'));
58
        }
59
60
        self::$phpcs->process(array(), dirname( __FILE__ ) . '/../');
61
        self::$phpcs->setIgnorePatterns(array());
62
    }
63
64
    /**
65
     * Tear down after each test
66
     *
67
     * @return void
68
     */
69
    public function tearDown()
70
    {
71
        // Reset any settingsStandard (targetPhpVersion)
72
        self::$phpcs->cli->settingsStandard = array();
0 ignored issues
show
Bug introduced by
The property settingsStandard does not seem to exist in PHP_CodeSniffer_CLI.

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
73
    }
74
75
    /**
76
     * Tear down after each test
77
     *
78
     * @return void
79
     */
80
    public static function tearDownAfterClass()
81
    {
82
        self::$sniffFiles = array();
83
    }
84
85
    /**
86
     * Sniff a file and return resulting file object
87
     *
88
     * @param string $filename Filename to sniff
89
     * @param string $targetPhpVersion Value of 'testVersion' to set on PHPCS object
90
     * @return PHP_CodeSniffer_File File object|false
91
     */
92
    public function sniffFile($filename, $targetPhpVersion = null)
93
    {
94
        if ( isset(self::$sniffFiles[$filename][$targetPhpVersion])) {
95
            return self::$sniffFiles[$filename][$targetPhpVersion];
96
        }
97
98
        if (null !== $targetPhpVersion) {
99
            PHP_CodeSniffer::setConfigData('testVersion', $targetPhpVersion, true);
100
        }
101
102
        $pathToFile = realpath(dirname(__FILE__)) . DIRECTORY_SEPARATOR . $filename;
103
        try {
104
            self::$sniffFiles[$filename][$targetPhpVersion] = self::$phpcs->processFile($pathToFile);
105
        } catch (Exception $e) {
106
            $this->fail('An unexpected exception has been caught: ' . $e->getMessage());
107
            return false;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return false; (false) is incompatible with the return type documented by BaseSniffTest::sniffFile of type PHP_CodeSniffer_File.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
108
        }
109
110
        return self::$sniffFiles[$filename][$targetPhpVersion];
111
    }
112
113
    /**
114
     * Assert a PHPCS error on a particular line number
115
     *
116
     * @param PHP_CodeSniffer_File $file Codesniffer file object
117
     * @param int $lineNumber Line number
118
     * @param string $expectedMessage Expected error message (assertContains)
119
     * @return bool
120
     */
121
    public function assertError(PHP_CodeSniffer_File $file, $lineNumber, $expectedMessage)
122
    {
123
        $errors = $this->gatherErrors($file);
124
125
        return $this->assertForType($errors, 'error', $lineNumber, $expectedMessage);
126
    }
127
128
    /**
129
     * Assert a PHPCS warning on a particular line number
130
     *
131
     * @param PHP_CodeSniffer_File $file Codesniffer file object
132
     * @param int $lineNumber Line number
133
     * @param string $expectedMessage Expected message (assertContains)
134
     * @return bool
135
     */
136
    public function assertWarning(PHP_CodeSniffer_File $file, $lineNumber, $expectedMessage)
137
    {
138
        $warnings = $this->gatherWarnings($file);
139
140
        return $this->assertForType($warnings, 'warning', $lineNumber, $expectedMessage);
141
    }
142
143
    /**
144
     * Assert a PHPCS error or warning on a particular line number.
145
     *
146
     * @param array  $issues          Array of issues of a particular type.
147
     * @param string $type            The type of issues, either 'error' or 'warning'.
148
     * @param int    $lineNumber      Line number.
149
     * @param string $expectedMessage Expected message (assertContains).
150
     * @return bool
151
     */
152
    private function assertForType($issues, $type, $lineNumber, $expectedMessage)
153
    {
154
        if (!isset($issues[$lineNumber])) {
155
            throw new Exception("Expected $type '$expectedMessage' on line number $lineNumber, but none found.");
156
        }
157
158
        $insteadFoundMessages = array();
159
160
        // Concat any error messages so we can do an assertContains
161
        foreach ($issues[$lineNumber] as $issue) {
162
            $insteadFoundMessages[] = $issue['message'];
163
        }
164
165
        $insteadMessagesString = implode(', ', $insteadFoundMessages);
166
        return $this->assertContains(
167
            $expectedMessage, $insteadMessagesString,
168
            "Expected $type message '$expectedMessage' on line $lineNumber not found. Instead found: $insteadMessagesString."
169
        );
170
    }
171
172
    /**
173
     * Assert no violation (warning or error) on a given line number
174
     *
175
     * @param PHP_CodeSniffer_File $file Codesniffer File object
176
     * @param mixed $lineNumber Line number
177
     * @return bool
178
     */
179
    public function assertNoViolation(PHP_CodeSniffer_File $file, $lineNumber = 0)
180
    {
181
        $errors   = $this->gatherErrors($file);
182
        $warnings = $this->gatherWarnings($file);
183
184
        if (!count($errors) && !count($warnings)) {
185
            return $this->assertTrue(true);
186
        }
187
188
        if ($lineNumber == 0) {
189
            $allMessages = $errors + $warnings;
190
            // TODO: Update the fail message to give the tester some
191
            // indication of what the errors or warnings were
192
            return $this->assertEmpty($allMessages, 'Failed asserting no violations in file');
193
        }
194
195
        $encounteredMessages = array();
196
        if (isset($errors[$lineNumber])) {
197
            foreach ($errors[$lineNumber] as $error) {
198
                $encounteredMessages[] = 'ERROR: ' . $error['message'];
199
            }
200
        }
201
202
        if (isset($warnings[$lineNumber])) {
203
            foreach ($warnings[$lineNumber] as $warning) {
204
                $encounteredMessages[] = 'WARNING: ' . $warning['message'];
205
            }
206
        }
207
208
        if (!count($encounteredMessages)) {
209
            return $this->assertTrue(true);
210
        }
211
212
        $failMessage = "Failed asserting no standards violation on line $lineNumber: "
213
            . implode(', ', $encounteredMessages);
214
215
        $this->assertEmpty($encounteredMessages, $failMessage);
216
    }
217
218
    /**
219
     * Show violations in file by line number
220
     *
221
     * This is useful for debugging sniffs on a file
222
     *
223
     * @param PHP_CodeSniffer_File $file Codesniffer file object
224
     * @return void
225
     */
226
    public function showViolations(PHP_CodeSniffer_File $file)
227
    {
228
        $violations = array(
229
            'errors'   => $this->gatherErrors($file),
230
            'warnings' => $this->gatherWarnings($file),
231
        );
232
233
        return $violations;
234
    }
235
236
    /**
237
     * Gather all error messages by line number from phpcs file result
238
     *
239
     * @param PHP_CodeSniffer_File $file Codesniffer File object
240
     * @return array
241
     */
242
    public function gatherErrors(PHP_CodeSniffer_File $file)
243
    {
244
        $foundErrors = $file->getErrors();
245
246
        return $this->gatherIssues($foundErrors);
247
    }
248
249
    /**
250
     * Gather all warning messages by line number from phpcs file result
251
     *
252
     * @param PHP_CodeSniffer_File $file Codesniffer File object
253
     * @return array
254
     */
255
    public function gatherWarnings(PHP_CodeSniffer_File $file)
256
    {
257
        $foundWarnings = $file->getWarnings();
258
259
        return $this->gatherIssues($foundWarnings);
260
    }
261
    
262
    /**
263
     * Gather all messages or a particular type by line number.
264
     *
265
     * @param array $IssuesArray Array of a particular type of issues,
0 ignored issues
show
Documentation introduced by
There is no parameter named $IssuesArray. Did you maybe mean $issuesArray?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function. It has, however, found a similar but not annotated parameter which might be a good fit.

Consider the following example. The parameter $ireland is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $ireland
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was changed, but the annotation was not.

Loading history...
266
	 *                           i.e. errors or warnings.
267
     * @return array
268
     */
269
    private function gatherIssues($issuesArray)
270
    {
271
        $allIssues = array();
272
        foreach ($issuesArray as $line => $lineIssues) {
273
            foreach ($lineIssues as $column => $issues) {
274
                foreach ($issues as $issue) {
275
276
                    if (!isset($allIssues[$line])) {
277
                        $allIssues[$line] = array();
278
                    }
279
280
                    $allIssues[$line][] = $issue;
281
                }
282
            }
283
        }
284
285
        return $allIssues;
286
    }
287
}
288
289