Skip to content

Commit 226ac34

Browse files
authored
Merge pull request #70 from jrfnl/feature/initial-unit-test-setup
Initial unit test setup, including tests for the Backticks sniff
2 parents 1359bce + c961ebd commit 226ac34

14 files changed

+406
-14
lines changed

.gitattributes

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@
88
/.gitattributes export-ignore
99
/.gitignore export-ignore
1010
/.travis.yml export-ignore
11+
/phpunit.xml.dist export-ignore
12+
/phpunit-bootstrap.php export-ignore
13+
/Security/Tests/ export-ignore
1114

1215
#
1316
# Auto detect text files and perform LF normalization

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,4 @@
11
/vendor
22
composer.lock
3+
/phpunit.xml
4+
/.phpunit.result.cache

.travis.yml

Lines changed: 77 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,30 @@ cache:
1313

1414
php:
1515
- 5.4
16-
- 7.4
17-
- "nightly"
16+
- 5.5
17+
- 5.6
18+
- 7.0
19+
- 7.1
20+
- 7.2
21+
22+
env:
23+
jobs:
24+
# `master`
25+
- PHPCS_VERSION="dev-master" LINT=1
26+
# Lowest supported PHPCS version.
27+
- PHPCS_VERSION="3.1.0"
1828

1929
# Define the stages used.
30+
# For non-PRs, only the sniff and quicktest stages are run.
31+
# For pull requests and merges, the full script is run (skipping quicktest).
32+
# Note: for pull requests, "master" is the base branch name.
33+
# See: https://docs.travis-ci.com/user/conditions-v1
2034
stages:
2135
- name: sniff
36+
- name: quicktest
37+
if: type = push AND branch NOT IN (master)
2238
- name: test
39+
if: branch IN (master)
2340

2441
jobs:
2542
fast_finish: true
@@ -48,6 +65,37 @@ jobs:
4865
- diff -B ./example_base_ruleset.xml <(xmllint --format "./example_base_ruleset.xml")
4966
- diff -B ./example_drupal7_ruleset.xml <(xmllint --format "./example_drupal7_ruleset.xml")
5067

68+
#### QUICK TEST STAGE ####
69+
# This is a much quicker test which only runs the unit tests and linting against the low/high
70+
# supported PHP/PHPCS combinations.
71+
- stage: quicktest
72+
php: 7.4
73+
env: PHPCS_VERSION="dev-master" LINT=1
74+
- php: 7.2
75+
# PHP 7.3 is only supported since PHPCS 3.3.1, PHP 7.4 since PHPCS 3.5.0, so running low against PHP 7.2.
76+
env: PHPCS_VERSION="3.1.0"
77+
78+
- php: 5.4
79+
env: PHPCS_VERSION="dev-master" LINT=1
80+
- php: 5.4
81+
env: PHPCS_VERSION="3.1.0"
82+
83+
#### TEST STAGE ####
84+
# Additional builds to prevent issues with PHPCS versions incompatible with certain PHP versions.
85+
# PHP 7.3 is only supported since PHPCS 3.3.1, PHP 7.4 since PHPCS 3.5.0.
86+
- stage: test
87+
php: 7.4
88+
env: PHPCS_VERSION="dev-master" LINT=1
89+
- php: 7.4
90+
env: PHPCS_VERSION="3.5.0"
91+
- php: 7.3
92+
env: PHPCS_VERSION="dev-master" LINT=1
93+
- php: 7.3
94+
env: PHPCS_VERSION="3.3.1"
95+
96+
- php: "nightly"
97+
env: PHPCS_VERSION="n/a" LINT=1
98+
5199
allow_failures:
52100
# Allow failures for unstable builds.
53101
- php: "nightly"
@@ -58,9 +106,35 @@ before_install:
58106

59107
- export XMLLINT_INDENT=" "
60108

109+
# On stable PHPCS versions, allow for PHP deprecation notices.
110+
# Unit tests don't need to fail on those for stable releases where those issues won't get fixed anymore.
111+
- |
112+
if [[ "$TRAVIS_BUILD_STAGE_NAME" != "Sniff" && $PHPCS_BRANCH != "dev-master" && "$PHPCS_VERSION" != "n/a" ]]; then
113+
echo 'error_reporting = E_ALL & ~E_DEPRECATED' >> ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/travis.ini
114+
fi
115+
116+
install:
117+
# Set up test environment using Composer.
118+
- |
119+
if [[ $PHPCS_VERSION != "n/a" ]]; then
120+
composer require --no-update --no-scripts squizlabs/php_codesniffer:${PHPCS_VERSION}
121+
fi
122+
- |
123+
if [[ "$TRAVIS_BUILD_STAGE_NAME" == "Sniff" || $PHPCS_VERSION == "n/a" ]]; then
124+
# The sniff stage doesn't run the unit tests, so no need for PHPUnit.
125+
# The build on nightly also doesn't run the tests (yet).
126+
composer remove --dev phpunit/phpunit --no-update --no-scripts
127+
fi
128+
61129
# --prefer-dist will allow for optimal use of the travis caching ability.
62130
- composer install --prefer-dist --no-suggest
63131

64132
script:
65133
# Lint PHP files against parse errors.
66-
- composer lint
134+
- if [[ "$LINT" == "1" ]]; then composer lint; fi
135+
136+
# Run the unit tests.
137+
- |
138+
if [[ $PHPCS_VERSION != "n/a" ]]; then
139+
composer test
140+
fi

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ phpcs-security-audit in its beginning was backed by Pheromone (later on named Fl
3131
Install
3232
-------
3333

34-
Requires [PHP CodeSniffer](http://pear.php.net/package/PHP_CodeSniffer/) version 3.x with PHP 5.4 or higher.
34+
Requires [PHP CodeSniffer](http://pear.php.net/package/PHP_CodeSniffer/) version 3.1.0 or higher with PHP 5.4 or higher.
3535

3636
The easiest way to install is using [Composer](https://getcomposer.org/):
3737
```bash

Security/Sniffs/BadFunctions/BackticksSniff.php

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -26,20 +26,20 @@ public function register() {
2626
* @return void
2727
*/
2828
public function process(File $phpcsFile, $stackPtr) {
29-
$utils = \PHPCS_SecurityAudit\Security\Sniffs\UtilsFactory::getInstance();
30-
$tokens = $phpcsFile->getTokens();
31-
$closer = $phpcsFile->findNext(T_BACKTICK, $stackPtr + 1, null, false, null, true);
29+
$closer = $phpcsFile->findNext(T_BACKTICK, $stackPtr + 1, null, false, null, true);
3230
if (!$closer) {
3331
return;
3432
}
35-
$s = $stackPtr + 1;
36-
$s = $phpcsFile->findNext(T_VARIABLE, $s, $closer);
37-
if ($s) {
33+
34+
$utils = \PHPCS_SecurityAudit\Security\Sniffs\UtilsFactory::getInstance();
35+
$tokens = $phpcsFile->getTokens();
36+
$s = $stackPtr;
37+
while (($s = $phpcsFile->findNext(T_VARIABLE, ($s + 1), $closer)) !== false) {
3838
$msg = 'System execution with backticks detected with dynamic parameter';
3939
if ($utils::is_token_user_input($tokens[$s])) {
40-
$phpcsFile->addError($msg . ' directly from user input', $stackPtr, 'ErrSystemExec');
40+
$phpcsFile->addError($msg . ' directly from user input', $s, 'ErrSystemExec');
4141
} else {
42-
$phpcsFile->addWarning($msg, $stackPtr, 'WarnSystemExec');
42+
$phpcsFile->addWarning($msg, $s, 'WarnSystemExec');
4343
}
4444
}
4545

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
<?php
2+
/**
3+
* An abstract class that all Security sniff unit tests must extend.
4+
*
5+
* A sniff unit test checks a .inc file for expected violations of a single
6+
* coding standard. Expected errors and warnings that are not found, as well as
7+
* unexpected warnings and errors, are considered test failures.
8+
*
9+
* This class will take care of setting the configuration variables in PHP_CodeSniffer
10+
* needed to test all relevant configuration combinations for each sniff in
11+
* the Security standard.
12+
*
13+
* The configuration variables set are based on the file name of a test case file.
14+
*
15+
* Naming conventions for the test case files:
16+
* SniffNameUnitTest[.CmsFramework][.ParanoiaMode].inc
17+
*
18+
* Both `[.CmsFramework]` as well as `[.ParanoiaMode]` are optional.
19+
* If neither is set, the defaults of no CmsFramework and Paranoia level 0 will be used.
20+
*
21+
* Separate test case files for different paranoia levels and different frameworks are
22+
* only needed if the sniff behaves differently based on these settings.
23+
*
24+
* - If the sniff behaviour is the same all round, just having one plain `SniffNameUnitTest.inc`
25+
* test case file will be sufficient.
26+
* - If the sniff behaviour is only dependent on one of the two configuration settings,
27+
* the other can be left out.
28+
* Examples:
29+
* - Sniff behaviour only depends on `ParanoiaMode`: `SniffNameUnitTest.[01].inc`.
30+
* - Sniff behaviour only depends on `CmsFramework`: `SniffNameUnitTest.[CmsFramework].inc`.
31+
*/
32+
33+
namespace PHPCS_SecurityAudit\Security\Tests;
34+
35+
use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest;
36+
37+
abstract class AbstractSecurityTestCase extends AbstractSniffUnitTest
38+
{
39+
40+
/**
41+
* Get a list of CLI values to set before the file is tested.
42+
*
43+
* @param string $filename The name of the file being tested.
44+
* @param \PHP_CodeSniffer\Config $config The config data for the run.
45+
*
46+
* @return void
47+
*/
48+
public function setCliValues($filename, $config)
49+
{
50+
// Set paranoia level.
51+
$paranoia = substr($filename, (strlen($filename) - 5), 1);
52+
if ($paranoia === '1') {
53+
$config->setConfigData('ParanoiaMode', 1, true);
54+
} else {
55+
$config->setConfigData('ParanoiaMode', 0, true);
56+
}
57+
58+
// Set the CMS Framework if necessary.
59+
$firstDot = strpos($filename, '.');
60+
$firstOffset = ($firstDot + 1);
61+
$secondDot = strpos($filename, '.', $firstOffset);
62+
63+
$extendedExtension = '';
64+
if ($secondDot !== false) {
65+
$extendedExtension = substr($filename, $firstOffset, ($secondDot - $firstOffset));
66+
}
67+
68+
switch ($extendedExtension) {
69+
case 'Drupal7':
70+
$config->setConfigData('CmsFramework', 'Drupal7', true);
71+
break;
72+
73+
case 'Drupal8':
74+
$config->setConfigData('CmsFramework', 'Drupal8', true);
75+
break;
76+
77+
case 'Symfony2':
78+
$config->setConfigData('CmsFramework', 'Symfony2', true);
79+
break;
80+
81+
default:
82+
$config->setConfigData('CmsFramework', null, true);
83+
break;
84+
}
85+
}
86+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<?php
2+
3+
$output = `$form['field']`; // Error (user input).
4+
$output = `$request['field']`; // Warning.
5+
`$_GET`; // Error (user input).
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<?php
2+
3+
$output = `$form['field']`; // Error (user input).
4+
$output = `$request['field']`; // Error (user input).
5+
`$_GET`; // Error (user input).
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<?php
2+
3+
$output = `$form['field']`; // Warning.
4+
$output = `$request['field']`; // Error (user input).
5+
`$_GET`; // Error (user input).
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?php
2+
3+
// Not using variable input.
4+
$output = `ls -al`;
5+
6+
// These should all give an error/warning.
7+
$output = `$form['field']`; // Warning.
8+
$output = `$request['field']`; // Warning.
9+
`$_GET`; // Error (user input).
10+
11+
$output = `git blame --date=short "$filename"`; // Warning.
12+
13+
$output = `git blame --date=$_POST['key'] "$filename"`; // Warning + error.
14+
15+
$output = `git blame
16+
--date=$_POST['key'] // Error.
17+
"$filename"`; // Warning.
18+
19+
// Incomplete command. Ignore.
20+
// Intentional parse error. This should be the last test in the file.
21+
$output = `ls

0 commit comments

Comments
 (0)