Completed
Pull Request — master (#148)
by Juliette
04:48
created

BaseSniffTest::tearDown()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 0
dl 0
loc 5
rs 9.4285
c 0
b 0
f 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
        if (self::$phpcs === null) {
42
            self::$phpcs = new PHP_CodeSniffer();
43
        }
44
45
        PHP_CodeSniffer::setConfigData('testVersion', null, true);
46
        if (method_exists('PHP_CodeSniffer_CLI', 'setCommandLineValues')) { // For PHPCS 2.x
47
            self::$phpcs->cli->setCommandLineValues(array('-pq', '--colors'));
48
        }
49
50
        self::$phpcs->process(array(), __DIR__ . '/../');
51
        self::$phpcs->setIgnorePatterns(array());
52
        self::$sniffFiles = array();
53
    }
54
55
    /**
56
     * Tear down after each test
57
     *
58
     * @return void
59
     */
60
    public static function tearDownAfterClass()
61
    {
62
        // Reset any settingsStandard (targetPhpVersion)
63
        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...
64
        self::$sniffFiles = array();
65
    }
66
67
    /**
68
     * Sniff a file and return resulting file object
69
     *
70
     * @param string $filename Filename to sniff
71
     * @param string $targetPhpVersion Value of 'testVersion' to set on PHPCS object
72
     * @return PHP_CodeSniffer_File File object|false
73
     */
74
    public function sniffFile($filename, $targetPhpVersion = null)
75
    {
76
        if ( isset(self::$sniffFiles[$filename][$targetPhpVersion])) {
77
            return self::$sniffFiles[$filename][$targetPhpVersion];
78
        }
79
80
        if (null !== $targetPhpVersion) {
81
            PHP_CodeSniffer::setConfigData('testVersion', $targetPhpVersion, true);
82
        }
83
84
        $filename = realpath(dirname(__FILE__)) . DIRECTORY_SEPARATOR . $filename;
85
        try {
86
            $phpcsFile = self::$phpcs->processFile($filename);
87
            self::$sniffFiles[$filename][$targetPhpVersion] = $phpcsFile;
88
        } catch (Exception $e) {
89
            $this->fail('An unexpected exception has been caught: ' . $e->getMessage());
90
            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...
91
        }
92
93
        return $phpcsFile;
94
    }
95
96
    /**
97
     * Assert a PHPCS error on a particular line number
98
     *
99
     * @param PHP_CodeSniffer_File $file Codesniffer file object
100
     * @param int $lineNumber Line number
101
     * @param string $expectedMessage Expected error message (assertContains)
102
     * @return bool
103
     */
104
    public function assertError(PHP_CodeSniffer_File $file, $lineNumber, $expectedMessage)
105
    {
106
        $errors = $this->gatherErrors($file);
107
108
        return $this->assertForType($errors, 'error', $lineNumber, $expectedMessage);
109
    }
110
111
    /**
112
     * Assert a PHPCS warning on a particular line number
113
     *
114
     * @param PHP_CodeSniffer_File $file Codesniffer file object
115
     * @param int $lineNumber Line number
116
     * @param string $expectedMessage Expected message (assertContains)
117
     * @return bool
118
     */
119
    public function assertWarning(PHP_CodeSniffer_File $file, $lineNumber, $expectedMessage)
120
    {
121
        $warnings = $this->gatherWarnings($file);
122
123
        return $this->assertForType($warnings, 'warning', $lineNumber, $expectedMessage);
124
    }
125
126
    /**
127
     * Assert a PHPCS error or warning on a particular line number.
128
     *
129
     * @param array  $issues          Array of issues of a particular type.
130
     * @param string $type            The type of issues, either 'error' or 'warning'.
131
     * @param int    $lineNumber      Line number.
132
     * @param string $expectedMessage Expected message (assertContains).
133
     * @return bool
134
     */
135
    private function assertForType($issues, $type, $lineNumber, $expectedMessage)
136
    {
137
        if (!isset($issues[$lineNumber])) {
138
            throw new Exception("Expected $type '$expectedMessage' on line number $lineNumber, but none found.");
139
        }
140
141
        $insteadFoundMessages = array();
142
143
        // Concat any error messages so we can do an assertContains
144
        foreach ($issues[$lineNumber] as $issue) {
145
            $insteadFoundMessages[] = $issue['message'];
146
        }
147
148
        $insteadMessagesString = implode(', ', $insteadFoundMessages);
149
        return $this->assertContains(
150
            $expectedMessage, $insteadMessagesString,
151
            "Expected $type message '$expectedMessage' on line $lineNumber not found. Instead found: $insteadMessagesString."
152
        );
153
    }
154
155
    /**
156
     * Assert no violation (warning or error) on a given line number
157
     *
158
     * @param PHP_CodeSniffer_File $file Codesniffer File object
159
     * @param mixed $lineNumber Line number
160
     * @return bool
161
     */
162
    public function assertNoViolation(PHP_CodeSniffer_File $file, $lineNumber = 0)
163
    {
164
        $errors   = $this->gatherErrors($file);
165
        $warnings = $this->gatherWarnings($file);
166
167
        if (!count($errors) && !count($warnings)) {
168
            return $this->assertTrue(true);
169
        }
170
171
        if ($lineNumber == 0) {
172
            $allMessages = $errors + $warnings;
173
            // TODO: Update the fail message to give the tester some
174
            // indication of what the errors or warnings were
175
            return $this->assertEmpty($allMessages, 'Failed asserting no violations in file');
176
        }
177
178
        $encounteredMessages = array();
179
        if (isset($errors[$lineNumber])) {
180
            foreach ($errors[$lineNumber] as $error) {
181
                $encounteredMessages[] = 'ERROR: ' . $error['message'];
182
            }
183
        }
184
185
        if (isset($warnings[$lineNumber])) {
186
            foreach ($warnings[$lineNumber] as $warning) {
187
                $encounteredMessages[] = 'WARNING: ' . $warning['message'];
188
            }
189
        }
190
191
        if (!count($encounteredMessages)) {
192
            return $this->assertTrue(true);
193
        }
194
195
        $failMessage = "Failed asserting no standards violation on line $lineNumber: "
196
            . implode(', ', $encounteredMessages);
197
198
        $this->assertEmpty($encounteredMessages, $failMessage);
199
    }
200
201
    /**
202
     * Show violations in file by line number
203
     *
204
     * This is useful for debugging sniffs on a file
205
     *
206
     * @param PHP_CodeSniffer_File $file Codesniffer file object
207
     * @return void
208
     */
209
    public function showViolations(PHP_CodeSniffer_File $file)
210
    {
211
        $violations = array(
212
            'errors'   => $this->gatherErrors($file),
213
            'warnings' => $this->gatherWarnings($file),
214
        );
215
216
        return $violations;
217
    }
218
219
    /**
220
     * Gather all error messages by line number from phpcs file result
221
     *
222
     * @param PHP_CodeSniffer_File $file Codesniffer File object
223
     * @return array
224
     */
225
    public function gatherErrors(PHP_CodeSniffer_File $file)
226
    {
227
        $foundErrors = $file->getErrors();
228
229
        return $this->gatherIssues($foundErrors);
230
    }
231
232
    /**
233
     * Gather all warning messages by line number from phpcs file result
234
     *
235
     * @param PHP_CodeSniffer_File $file Codesniffer File object
236
     * @return array
237
     */
238
    public function gatherWarnings(PHP_CodeSniffer_File $file)
239
    {
240
        $foundWarnings = $file->getWarnings();
241
242
        return $this->gatherIssues($foundWarnings);
243
    }
244
    
245
    /**
246
     * Gather all messages or a particular type by line number.
247
     *
248
     * @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...
249
	 *                           i.e. errors or warnings.
250
     * @return array
251
     */
252
    private function gatherIssues($issuesArray)
253
    {
254
        $allIssues = array();
255
        foreach ($issuesArray as $line => $lineIssues) {
256
            foreach ($lineIssues as $column => $issues) {
257
                foreach ($issues as $issue) {
258
259
                    if (!isset($allIssues[$line])) {
260
                        $allIssues[$line] = array();
261
                    }
262
263
                    $allIssues[$line][] = $issue;
264
                }
265
            }
266
        }
267
268
        return $allIssues;
269
    }
270
}
271
272