diff --git a/src/Filters/GitModified.php b/src/Filters/GitModified.php index 9bba997c24..b946577909 100644 --- a/src/Filters/GitModified.php +++ b/src/Filters/GitModified.php @@ -37,8 +37,7 @@ protected function getWhitelist() $modified = []; $cmd = 'git ls-files -o -m --exclude-standard -- '.escapeshellarg($this->basedir); - $output = []; - exec($cmd, $output); + $output = $this->exec($cmd); $basedir = $this->basedir; if (is_dir($basedir) === false) { @@ -63,4 +62,27 @@ protected function getWhitelist() }//end getWhitelist() + /** + * Execute an external command. + * + * {@internal This method is only needed to allow for mocking the return value + * to test the class logic.} + * + * @param string $cmd Command. + * + * @return array + */ + protected function exec($cmd) + { + $output = []; + $lastLine = exec($cmd, $output); + if ($lastLine === false) { + return []; + } + + return $output; + + }//end exec() + + }//end class diff --git a/src/Filters/GitStaged.php b/src/Filters/GitStaged.php index 1fdbb952ca..405387ff87 100644 --- a/src/Filters/GitStaged.php +++ b/src/Filters/GitStaged.php @@ -39,8 +39,7 @@ protected function getWhitelist() $modified = []; $cmd = 'git diff --cached --name-only -- '.escapeshellarg($this->basedir); - $output = []; - exec($cmd, $output); + $output = $this->exec($cmd); $basedir = $this->basedir; if (is_dir($basedir) === false) { @@ -65,4 +64,27 @@ protected function getWhitelist() }//end getWhitelist() + /** + * Execute an external command. + * + * {@internal This method is only needed to allow for mocking the return value + * to test the class logic.} + * + * @param string $cmd Command. + * + * @return array + */ + protected function exec($cmd) + { + $output = []; + $lastLine = exec($cmd, $output); + if ($lastLine === false) { + return []; + } + + return $output; + + }//end exec() + + }//end class diff --git a/tests/Core/Filters/AbstractFilterTestCase.php b/tests/Core/Filters/AbstractFilterTestCase.php new file mode 100644 index 0000000000..b867d7aaf8 --- /dev/null +++ b/tests/Core/Filters/AbstractFilterTestCase.php @@ -0,0 +1,227 @@ + + * @copyright 2023 PHPCSStandards Contributors + * @license https://github.com/PHPCSStandards/PHP_CodeSniffer/blob/master/licence.txt BSD Licence + */ + +namespace PHP_CodeSniffer\Tests\Core\Filters; + +use PHP_CodeSniffer\Config; +use PHP_CodeSniffer\Filters\Filter; +use PHP_CodeSniffer\Ruleset; +use PHPUnit\Framework\TestCase; +use RecursiveIteratorIterator; + +/** + * Base functionality and utilities for testing Filter classes. + */ +abstract class AbstractFilterTestCase extends TestCase +{ + + /** + * The Config object. + * + * @var \PHP_CodeSniffer\Config + */ + protected static $config; + + /** + * The Ruleset object. + * + * @var \PHP_CodeSniffer\Ruleset + */ + protected static $ruleset; + + + /** + * Initialize the config and ruleset objects. + * + * @beforeClass + * + * @return void + */ + public static function initializeConfigAndRuleset() + { + self::$config = new Config(['--standard=PSR1', '--extensions=php,inc/php,js,css', '--report-width=80']); + self::$ruleset = new Ruleset(self::$config); + + }//end initializeConfigAndRuleset() + + + /** + * Helper method to retrieve a mock object for a Filter class. + * + * The `setMethods()` method was silently deprecated in PHPUnit 9 and removed in PHPUnit 10. + * + * Note: direct access to the `getMockBuilder()` method is soft deprecated as of PHPUnit 10, + * and expected to be hard deprecated in PHPUnit 11 and removed in PHPUnit 12. + * Dealing with that is something for a later iteration of the test suite. + * + * @param string $className Fully qualified name of the class under test. + * @param array $constructorArgs Optional. Array of parameters to pass to the class constructor. + * @param array|null $methodsToMock Optional. The methods to mock in the class under test. + * Needed for PHPUnit cross-version support as PHPUnit 4.x does + * not have a `setMethodsExcept()` method yet. + * If not passed, no methods will be replaced. + * + * @return \PHPUnit\Framework\MockObject\MockObject + */ + protected function getMockedClass($className, array $constructorArgs=[], $methodsToMock=null) + { + $mockedObj = $this->getMockBuilder($className); + + if (\method_exists($mockedObj, 'onlyMethods') === true) { + // PHPUnit 8+. + if (is_array($methodsToMock) === true) { + return $mockedObj + ->setConstructorArgs($constructorArgs) + ->onlyMethods($methodsToMock) + ->getMock(); + } + + return $mockedObj->getMock() + ->setConstructorArgs($constructorArgs); + } + + // PHPUnit < 8. + return $mockedObj + ->setConstructorArgs($constructorArgs) + ->setMethods($methodsToMock) + ->getMock(); + + }//end getMockedClass() + + + /** + * Retrieve an array of files which were accepted by a filter. + * + * @param \PHP_CodeSniffer\Filters\Filter $filter The Filter object under test. + * + * @return array + */ + protected function getFilteredResultsAsArray(Filter $filter) + { + $iterator = new RecursiveIteratorIterator($filter); + $files = []; + foreach ($iterator as $file) { + $files[] = $file; + } + + return $files; + + }//end getFilteredResultsAsArray() + + + /** + * Retrieve the basedir to use for tests using the `getFakeFileList()` method. + * + * @return string + */ + protected static function getBaseDir() + { + return dirname(dirname(dirname(__DIR__))); + + }//end getBaseDir() + + + /** + * Retrieve a file list containing a range of paths for testing purposes. + * + * This list **must** contain files which exist in this project (well, except for some which don't exist + * purely for testing purposes), as `realpath()` is used in the logic under test and `realpath()` will + * return `false` for any non-existent files, which will automatically filter them out before + * we get to the code under test. + * + * Note this list does not include `.` and `..` as \PHP_CodeSniffer\Files\FileList uses `SKIP_DOTS`. + * + * @return array + */ + protected static function getFakeFileList() + { + $basedir = self::getBaseDir(); + return [ + $basedir.'/.gitignore', + $basedir.'/.yamllint.yml', + $basedir.'/phpcs.xml', + $basedir.'/phpcs.xml.dist', + $basedir.'/autoload.php', + $basedir.'/bin', + $basedir.'/bin/phpcs', + $basedir.'/bin/phpcs.bat', + $basedir.'/scripts', + $basedir.'/scripts/build-phar.php', + $basedir.'/src', + $basedir.'/src/WillNotExist.php', + $basedir.'/src/WillNotExist.bak', + $basedir.'/src/WillNotExist.orig', + $basedir.'/src/Ruleset.php', + $basedir.'/src/Generators', + $basedir.'/src/Generators/Markdown.php', + $basedir.'/src/Standards', + $basedir.'/src/Standards/Generic', + $basedir.'/src/Standards/Generic/Docs', + $basedir.'/src/Standards/Generic/Docs/Classes', + $basedir.'/src/Standards/Generic/Docs/Classes/DuplicateClassNameStandard.xml', + $basedir.'/src/Standards/Generic/Sniffs', + $basedir.'/src/Standards/Generic/Sniffs/Classes', + $basedir.'/src/Standards/Generic/Sniffs/Classes/DuplicateClassNameSniff.php', + $basedir.'/src/Standards/Generic/Tests', + $basedir.'/src/Standards/Generic/Tests/Classes', + $basedir.'/src/Standards/Generic/Tests/Classes/DuplicateClassNameUnitTest.1.inc', + // Will rarely exist when running the tests. + $basedir.'/src/Standards/Generic/Tests/Classes/DuplicateClassNameUnitTest.1.inc.bak', + $basedir.'/src/Standards/Generic/Tests/Classes/DuplicateClassNameUnitTest.2.inc', + $basedir.'/src/Standards/Generic/Tests/Classes/DuplicateClassNameUnitTest.php', + $basedir.'/src/Standards/Squiz', + $basedir.'/src/Standards/Squiz/Docs', + $basedir.'/src/Standards/Squiz/Docs/WhiteSpace', + $basedir.'/src/Standards/Squiz/Docs/WhiteSpace/SemicolonSpacingStandard.xml', + $basedir.'/src/Standards/Squiz/Sniffs', + $basedir.'/src/Standards/Squiz/Sniffs/WhiteSpace', + $basedir.'/src/Standards/Squiz/Sniffs/WhiteSpace/OperatorSpacingSniff.php', + $basedir.'/src/Standards/Squiz/Tests', + $basedir.'/src/Standards/Squiz/Tests/WhiteSpace', + $basedir.'/src/Standards/Squiz/Tests/WhiteSpace/OperatorSpacingUnitTest.inc', + $basedir.'/src/Standards/Squiz/Tests/WhiteSpace/OperatorSpacingUnitTest.inc.fixed', + $basedir.'/src/Standards/Squiz/Tests/WhiteSpace/OperatorSpacingUnitTest.js', + $basedir.'/src/Standards/Squiz/Tests/WhiteSpace/OperatorSpacingUnitTest.js.fixed', + $basedir.'/src/Standards/Squiz/Tests/WhiteSpace/OperatorSpacingUnitTest.php', + ]; + + }//end getFakeFileList() + + + /** + * Translate Linux paths to Windows paths, when necessary. + * + * These type of tests should be able to run and pass on both *nix as well as Windows + * based dev systems. This method is a helper to allow for this. + * + * @param array $paths A single or multi-dimensional array containing + * file paths. + * + * @return array + */ + protected static function mapPathsToRuntimeOs(array $paths) + { + if (DIRECTORY_SEPARATOR !== '\\') { + return $paths; + } + + foreach ($paths as $key => $value) { + if (is_string($value) === true) { + $paths[$key] = strtr($value, '/', '\\\\'); + } else if (is_array($value) === true) { + $paths[$key] = self::mapPathsToRuntimeOs($value); + } + } + + return $paths; + + }//end mapPathsToRuntimeOs() + + +}//end class diff --git a/tests/Core/Filters/Filter/AcceptTest.php b/tests/Core/Filters/Filter/AcceptTest.php index 53d4ba6441..fa3c834f77 100644 --- a/tests/Core/Filters/Filter/AcceptTest.php +++ b/tests/Core/Filters/Filter/AcceptTest.php @@ -13,30 +13,17 @@ use PHP_CodeSniffer\Config; use PHP_CodeSniffer\Filters\Filter; use PHP_CodeSniffer\Ruleset; -use PHPUnit\Framework\TestCase; +use PHP_CodeSniffer\Tests\Core\Filters\AbstractFilterTestCase; +use RecursiveArrayIterator; /** * Tests for the \PHP_CodeSniffer\Filters\Filter::accept method. * * @covers \PHP_CodeSniffer\Filters\Filter */ -class AcceptTest extends TestCase +class AcceptTest extends AbstractFilterTestCase { - /** - * The Config object. - * - * @var \PHP_CodeSniffer\Config - */ - protected static $config; - - /** - * The Ruleset object. - * - * @var \PHP_CodeSniffer\Ruleset - */ - protected static $ruleset; - /** * Initialize the config and ruleset objects based on the `AcceptTest.xml` ruleset file. @@ -48,7 +35,7 @@ class AcceptTest extends TestCase public static function initializeConfigAndRuleset() { $standard = __DIR__.'/'.basename(__FILE__, '.php').'.xml'; - self::$config = new Config(["--standard=$standard", "--ignore=*/somethingelse/*"]); + self::$config = new Config(["--standard=$standard", '--ignore=*/somethingelse/*', '--report-width=80']); self::$ruleset = new Ruleset(self::$config); }//end initializeConfigAndRuleset() @@ -57,8 +44,8 @@ public static function initializeConfigAndRuleset() /** * Test filtering a file list for excluded paths. * - * @param array $inputPaths List of file paths to be filtered. - * @param array $expectedOutput Expected filtering result. + * @param array $inputPaths List of file paths to be filtered. + * @param array $expectedOutput Expected filtering result. * * @dataProvider dataExcludePatterns * @@ -66,16 +53,10 @@ public static function initializeConfigAndRuleset() */ public function testExcludePatterns($inputPaths, $expectedOutput) { - $fakeDI = new \RecursiveArrayIterator($inputPaths); - $filter = new Filter($fakeDI, '/', self::$config, self::$ruleset); - $iterator = new \RecursiveIteratorIterator($filter); - $files = []; - - foreach ($iterator as $file) { - $files[] = $file; - } + $fakeDI = new RecursiveArrayIterator($inputPaths); + $filter = new Filter($fakeDI, '/', self::$config, self::$ruleset); - $this->assertEquals($expectedOutput, $files); + $this->assertEquals($expectedOutput, $this->getFilteredResultsAsArray($filter)); }//end testExcludePatterns() @@ -85,34 +66,34 @@ public function testExcludePatterns($inputPaths, $expectedOutput) * * @see testExcludePatterns * - * @return array + * @return array>> */ - public function dataExcludePatterns() + public static function dataExcludePatterns() { $testCases = [ // Test top-level exclude patterns. - [ - [ + 'Non-sniff specific path based excludes from ruleset and command line are respected and don\'t filter out too much' => [ + 'inputPaths' => [ '/path/to/src/Main.php', '/path/to/src/Something/Main.php', '/path/to/src/Somethingelse/Main.php', '/path/to/src/SomethingelseEvenLonger/Main.php', '/path/to/src/Other/Main.php', ], - [ + 'expectedOutput' => [ '/path/to/src/Main.php', '/path/to/src/SomethingelseEvenLonger/Main.php', ], ], // Test ignoring standard/sniff specific exclude patterns. - [ - [ + 'Filter should not act on standard/sniff specific exclude patterns' => [ + 'inputPaths' => [ '/path/to/src/generic-project/Main.php', '/path/to/src/generic/Main.php', '/path/to/src/anything-generic/Main.php', ], - [ + 'expectedOutput' => [ '/path/to/src/generic-project/Main.php', '/path/to/src/generic/Main.php', '/path/to/src/anything-generic/Main.php', @@ -121,17 +102,7 @@ public function dataExcludePatterns() ]; // Allow these tests to work on Windows as well. - if (DIRECTORY_SEPARATOR === '\\') { - foreach ($testCases as $key => $case) { - foreach ($case as $nr => $param) { - foreach ($param as $file => $value) { - $testCases[$key][$nr][$file] = strtr($value, '/', '\\'); - } - } - } - } - - return $testCases; + return self::mapPathsToRuntimeOs($testCases); }//end dataExcludePatterns() diff --git a/tests/Core/Filters/GitModifiedTest.php b/tests/Core/Filters/GitModifiedTest.php new file mode 100644 index 0000000000..b3d11b631f --- /dev/null +++ b/tests/Core/Filters/GitModifiedTest.php @@ -0,0 +1,260 @@ + + * @copyright 2023 PHPCSStandards Contributors + * @license https://github.com/PHPCSStandards/PHP_CodeSniffer/blob/master/licence.txt BSD Licence + */ + +namespace PHP_CodeSniffer\Tests\Core\Filters; + +use PHP_CodeSniffer\Filters\GitModified; +use PHP_CodeSniffer\Tests\Core\Filters\AbstractFilterTestCase; +use RecursiveArrayIterator; +use ReflectionMethod; + +/** + * Tests for the \PHP_CodeSniffer\Filters\GitModified class. + * + * @covers \PHP_CodeSniffer\Filters\GitModified + */ +class GitModifiedTest extends AbstractFilterTestCase +{ + + + /** + * Test filtering a file list for excluded paths. + * + * @return void + */ + public function testFileNamePassesAsBasePathWillTranslateToDirname() + { + $rootFile = self::getBaseDir().'/autoload.php'; + + $fakeDI = new RecursiveArrayIterator(self::getFakeFileList()); + $constructorArgs = [ + $fakeDI, + $rootFile, + self::$config, + self::$ruleset, + ]; + $mockObj = $this->getMockedClass('PHP_CodeSniffer\Filters\GitModified', $constructorArgs, ['exec']); + + $mockObj->expects($this->once()) + ->method('exec') + ->willReturn(['autoload.php']); + + $this->assertEquals([$rootFile], $this->getFilteredResultsAsArray($mockObj)); + + }//end testFileNamePassesAsBasePathWillTranslateToDirname() + + + /** + * Test filtering a file list for excluded paths. + * + * @param array $inputPaths List of file paths to be filtered. + * @param array $outputGitModified Simulated "git modified" output. + * @param array $expectedOutput Expected filtering result. + * + * @dataProvider dataAcceptOnlyGitModified + * + * @return void + */ + public function testAcceptOnlyGitModified($inputPaths, $outputGitModified, $expectedOutput) + { + $fakeDI = new RecursiveArrayIterator($inputPaths); + $constructorArgs = [ + $fakeDI, + self::getBaseDir(), + self::$config, + self::$ruleset, + ]; + $mockObj = $this->getMockedClass('PHP_CodeSniffer\Filters\GitModified', $constructorArgs, ['exec']); + + $mockObj->expects($this->once()) + ->method('exec') + ->willReturn($outputGitModified); + + $this->assertEquals($expectedOutput, $this->getFilteredResultsAsArray($mockObj)); + + }//end testAcceptOnlyGitModified() + + + /** + * Data provider. + * + * @see testAcceptOnlyGitModified + * + * @return array>> + */ + public static function dataAcceptOnlyGitModified() + { + $basedir = self::getBaseDir(); + $fakeFileList = self::getFakeFileList(); + + $testCases = [ + 'no files marked as git modified' => [ + 'inputPaths' => $fakeFileList, + 'outputGitModified' => [], + 'expectedOutput' => [], + ], + + 'files marked as git modified which don\'t actually exist' => [ + 'inputPaths' => $fakeFileList, + 'outputGitModified' => [ + 'src/WillNotExist.php', + 'src/WillNotExist.bak', + 'src/WillNotExist.orig', + ], + 'expectedOutput' => [], + ], + + 'single file marked as git modified - file in root dir' => [ + 'inputPaths' => $fakeFileList, + 'outputGitModified' => ['autoload.php'], + 'expectedOutput' => [ + $basedir.'/autoload.php', + ], + ], + 'single file marked as git modified - file in sub dir' => [ + 'inputPaths' => $fakeFileList, + 'outputGitModified' => ['src/Standards/Generic/Sniffs/Classes/DuplicateClassNameSniff.php'], + 'expectedOutput' => [ + $basedir.'/src', + $basedir.'/src/Standards', + $basedir.'/src/Standards/Generic', + $basedir.'/src/Standards/Generic/Sniffs', + $basedir.'/src/Standards/Generic/Sniffs/Classes', + $basedir.'/src/Standards/Generic/Sniffs/Classes/DuplicateClassNameSniff.php', + ], + ], + + 'multiple files marked as git modified, none valid for scan' => [ + 'inputPaths' => $fakeFileList, + 'outputGitModified' => [ + '.gitignore', + 'phpcs.xml.dist', + 'src/Standards/Squiz/Tests/WhiteSpace/OperatorSpacingUnitTest.js.fixed', + ], + 'expectedOutput' => [ + $basedir.'/src', + $basedir.'/src/Standards', + $basedir.'/src/Standards/Squiz', + $basedir.'/src/Standards/Squiz/Tests', + $basedir.'/src/Standards/Squiz/Tests/WhiteSpace', + ], + ], + + 'multiple files marked as git modified, only one file valid for scan' => [ + 'inputPaths' => $fakeFileList, + 'outputGitModified' => [ + '.gitignore', + 'src/Standards/Generic/Docs/Classes/DuplicateClassNameStandard.xml', + 'src/Standards/Generic/Sniffs/Classes/DuplicateClassNameSniff.php', + ], + 'expectedOutput' => [ + $basedir.'/src', + $basedir.'/src/Standards', + $basedir.'/src/Standards/Generic', + $basedir.'/src/Standards/Generic/Docs', + $basedir.'/src/Standards/Generic/Docs/Classes', + $basedir.'/src/Standards/Generic/Sniffs', + $basedir.'/src/Standards/Generic/Sniffs/Classes', + $basedir.'/src/Standards/Generic/Sniffs/Classes/DuplicateClassNameSniff.php', + ], + ], + + 'multiple files marked as git modified, multiple files valid for scan' => [ + 'inputPaths' => $fakeFileList, + 'outputGitModified' => [ + '.yamllint.yml', + 'autoload.php', + 'src/Standards/Squiz/Sniffs/WhiteSpace/OperatorSpacingSniff.php', + 'src/Standards/Squiz/Tests/WhiteSpace/OperatorSpacingUnitTest.inc', + 'src/Standards/Squiz/Tests/WhiteSpace/OperatorSpacingUnitTest.inc.fixed', + 'src/Standards/Squiz/Tests/WhiteSpace/OperatorSpacingUnitTest.js', + 'src/Standards/Squiz/Tests/WhiteSpace/OperatorSpacingUnitTest.js.fixed', + 'src/Standards/Squiz/Tests/WhiteSpace/OperatorSpacingUnitTest.php', + ], + 'expectedOutput' => [ + $basedir.'/autoload.php', + $basedir.'/src', + $basedir.'/src/Standards', + $basedir.'/src/Standards/Squiz', + $basedir.'/src/Standards/Squiz/Sniffs', + $basedir.'/src/Standards/Squiz/Sniffs/WhiteSpace', + $basedir.'/src/Standards/Squiz/Sniffs/WhiteSpace/OperatorSpacingSniff.php', + $basedir.'/src/Standards/Squiz/Tests', + $basedir.'/src/Standards/Squiz/Tests/WhiteSpace', + $basedir.'/src/Standards/Squiz/Tests/WhiteSpace/OperatorSpacingUnitTest.inc', + $basedir.'/src/Standards/Squiz/Tests/WhiteSpace/OperatorSpacingUnitTest.js', + $basedir.'/src/Standards/Squiz/Tests/WhiteSpace/OperatorSpacingUnitTest.php', + ], + ], + ]; + + return $testCases; + + }//end dataAcceptOnlyGitModified() + + + /** + * Test filtering a file list for excluded paths. + * + * @param string $cmd Command to run. + * @param array $expected Expected return value. + * + * @dataProvider dataExecAlwaysReturnsArray + * + * @return void + */ + public function testExecAlwaysReturnsArray($cmd, $expected) + { + $fakeDI = new RecursiveArrayIterator(self::getFakeFileList()); + $filter = new GitModified($fakeDI, '/', self::$config, self::$ruleset); + + $reflMethod = new ReflectionMethod($filter, 'exec'); + $reflMethod->setAccessible(true); + $result = $reflMethod->invoke($filter, $cmd); + + $this->assertSame($expected, $result); + + }//end testExecAlwaysReturnsArray() + + + /** + * Data provider. + * + * @see testExecAlwaysReturnsArray + * + * {@internal Missing: test with a command which yields a `false` return value. + * JRF: I've not managed to find a command which does so, let alone one, which then + * doesn't have side-effects of uncatchable output while running the tests.} + * + * @return array>> + */ + public static function dataExecAlwaysReturnsArray() + { + return [ + 'valid command which won\'t have any output unless files in the bin dir have been modified' => [ + // Largely using the command used in the filter, but only checking the bin dir. + // This should prevent the test unexpectedly failing during local development (in most cases). + 'cmd' => 'git ls-files -o -m --exclude-standard -- '.escapeshellarg(self::getBaseDir().'/bin'), + 'expected' => [], + ], + 'valid command which will have output' => [ + 'cmd' => 'git ls-files --exclude-standard -- '.escapeshellarg(self::getBaseDir().'/bin'), + 'expected' => [ + 'bin/phpcbf', + 'bin/phpcbf.bat', + 'bin/phpcs', + 'bin/phpcs.bat', + ], + ], + ]; + + }//end dataExecAlwaysReturnsArray() + + +}//end class diff --git a/tests/Core/Filters/GitStagedTest.php b/tests/Core/Filters/GitStagedTest.php new file mode 100644 index 0000000000..ebf761c4fe --- /dev/null +++ b/tests/Core/Filters/GitStagedTest.php @@ -0,0 +1,260 @@ + + * @copyright 2023 PHPCSStandards Contributors + * @license https://github.com/PHPCSStandards/PHP_CodeSniffer/blob/master/licence.txt BSD Licence + */ + +namespace PHP_CodeSniffer\Tests\Core\Filters; + +use PHP_CodeSniffer\Filters\GitStaged; +use PHP_CodeSniffer\Tests\Core\Filters\AbstractFilterTestCase; +use RecursiveArrayIterator; +use ReflectionMethod; + +/** + * Tests for the \PHP_CodeSniffer\Filters\GitStaged class. + * + * @covers \PHP_CodeSniffer\Filters\GitStaged + */ +class GitStagedTest extends AbstractFilterTestCase +{ + + + /** + * Test filtering a file list for excluded paths. + * + * @return void + */ + public function testFileNamePassesAsBasePathWillTranslateToDirname() + { + $rootFile = self::getBaseDir().'/autoload.php'; + + $fakeDI = new RecursiveArrayIterator(self::getFakeFileList()); + $constructorArgs = [ + $fakeDI, + $rootFile, + self::$config, + self::$ruleset, + ]; + $mockObj = $this->getMockedClass('PHP_CodeSniffer\Filters\GitStaged', $constructorArgs, ['exec']); + + $mockObj->expects($this->once()) + ->method('exec') + ->willReturn(['autoload.php']); + + $this->assertEquals([$rootFile], $this->getFilteredResultsAsArray($mockObj)); + + }//end testFileNamePassesAsBasePathWillTranslateToDirname() + + + /** + * Test filtering a file list for excluded paths. + * + * @param array $inputPaths List of file paths to be filtered. + * @param array $outputGitStaged Simulated "git staged" output. + * @param array $expectedOutput Expected filtering result. + * + * @dataProvider dataAcceptOnlyGitStaged + * + * @return void + */ + public function testAcceptOnlyGitStaged($inputPaths, $outputGitStaged, $expectedOutput) + { + $fakeDI = new RecursiveArrayIterator($inputPaths); + $constructorArgs = [ + $fakeDI, + self::getBaseDir(), + self::$config, + self::$ruleset, + ]; + $mockObj = $this->getMockedClass('PHP_CodeSniffer\Filters\GitStaged', $constructorArgs, ['exec']); + + $mockObj->expects($this->once()) + ->method('exec') + ->willReturn($outputGitStaged); + + $this->assertEquals($expectedOutput, $this->getFilteredResultsAsArray($mockObj)); + + }//end testAcceptOnlyGitStaged() + + + /** + * Data provider. + * + * @see testAcceptOnlyGitStaged + * + * @return array>> + */ + public static function dataAcceptOnlyGitStaged() + { + $basedir = self::getBaseDir(); + $fakeFileList = self::getFakeFileList(); + + $testCases = [ + 'no files marked as git modified' => [ + 'inputPaths' => $fakeFileList, + 'outputGitStaged' => [], + 'expectedOutput' => [], + ], + + 'files marked as git modified which don\'t actually exist' => [ + 'inputPaths' => $fakeFileList, + 'outputGitStaged' => [ + 'src/WillNotExist.php', + 'src/WillNotExist.bak', + 'src/WillNotExist.orig', + ], + 'expectedOutput' => [], + ], + + 'single file marked as git modified - file in root dir' => [ + 'inputPaths' => $fakeFileList, + 'outputGitStaged' => ['autoload.php'], + 'expectedOutput' => [ + $basedir.'/autoload.php', + ], + ], + 'single file marked as git modified - file in sub dir' => [ + 'inputPaths' => $fakeFileList, + 'outputGitStaged' => ['src/Standards/Generic/Sniffs/Classes/DuplicateClassNameSniff.php'], + 'expectedOutput' => [ + $basedir.'/src', + $basedir.'/src/Standards', + $basedir.'/src/Standards/Generic', + $basedir.'/src/Standards/Generic/Sniffs', + $basedir.'/src/Standards/Generic/Sniffs/Classes', + $basedir.'/src/Standards/Generic/Sniffs/Classes/DuplicateClassNameSniff.php', + ], + ], + + 'multiple files marked as git modified, none valid for scan' => [ + 'inputPaths' => $fakeFileList, + 'outputGitStaged' => [ + '.gitignore', + 'phpcs.xml.dist', + 'src/Standards/Squiz/Tests/WhiteSpace/OperatorSpacingUnitTest.js.fixed', + ], + 'expectedOutput' => [ + $basedir.'/src', + $basedir.'/src/Standards', + $basedir.'/src/Standards/Squiz', + $basedir.'/src/Standards/Squiz/Tests', + $basedir.'/src/Standards/Squiz/Tests/WhiteSpace', + ], + ], + + 'multiple files marked as git modified, only one file valid for scan' => [ + 'inputPaths' => $fakeFileList, + 'outputGitStaged' => [ + '.gitignore', + 'src/Standards/Generic/Docs/Classes/DuplicateClassNameStandard.xml', + 'src/Standards/Generic/Sniffs/Classes/DuplicateClassNameSniff.php', + ], + 'expectedOutput' => [ + $basedir.'/src', + $basedir.'/src/Standards', + $basedir.'/src/Standards/Generic', + $basedir.'/src/Standards/Generic/Docs', + $basedir.'/src/Standards/Generic/Docs/Classes', + $basedir.'/src/Standards/Generic/Sniffs', + $basedir.'/src/Standards/Generic/Sniffs/Classes', + $basedir.'/src/Standards/Generic/Sniffs/Classes/DuplicateClassNameSniff.php', + ], + ], + + 'multiple files marked as git modified, multiple files valid for scan' => [ + 'inputPaths' => $fakeFileList, + 'outputGitStaged' => [ + '.yamllint.yml', + 'autoload.php', + 'src/Standards/Squiz/Sniffs/WhiteSpace/OperatorSpacingSniff.php', + 'src/Standards/Squiz/Tests/WhiteSpace/OperatorSpacingUnitTest.inc', + 'src/Standards/Squiz/Tests/WhiteSpace/OperatorSpacingUnitTest.inc.fixed', + 'src/Standards/Squiz/Tests/WhiteSpace/OperatorSpacingUnitTest.js', + 'src/Standards/Squiz/Tests/WhiteSpace/OperatorSpacingUnitTest.js.fixed', + 'src/Standards/Squiz/Tests/WhiteSpace/OperatorSpacingUnitTest.php', + ], + 'expectedOutput' => [ + $basedir.'/autoload.php', + $basedir.'/src', + $basedir.'/src/Standards', + $basedir.'/src/Standards/Squiz', + $basedir.'/src/Standards/Squiz/Sniffs', + $basedir.'/src/Standards/Squiz/Sniffs/WhiteSpace', + $basedir.'/src/Standards/Squiz/Sniffs/WhiteSpace/OperatorSpacingSniff.php', + $basedir.'/src/Standards/Squiz/Tests', + $basedir.'/src/Standards/Squiz/Tests/WhiteSpace', + $basedir.'/src/Standards/Squiz/Tests/WhiteSpace/OperatorSpacingUnitTest.inc', + $basedir.'/src/Standards/Squiz/Tests/WhiteSpace/OperatorSpacingUnitTest.js', + $basedir.'/src/Standards/Squiz/Tests/WhiteSpace/OperatorSpacingUnitTest.php', + ], + ], + ]; + + return $testCases; + + }//end dataAcceptOnlyGitStaged() + + + /** + * Test filtering a file list for excluded paths. + * + * @param string $cmd Command to run. + * @param array $expected Expected return value. + * + * @dataProvider dataExecAlwaysReturnsArray + * + * @return void + */ + public function testExecAlwaysReturnsArray($cmd, $expected) + { + $fakeDI = new RecursiveArrayIterator(self::getFakeFileList()); + $filter = new GitStaged($fakeDI, '/', self::$config, self::$ruleset); + + $reflMethod = new ReflectionMethod($filter, 'exec'); + $reflMethod->setAccessible(true); + $result = $reflMethod->invoke($filter, $cmd); + + $this->assertSame($expected, $result); + + }//end testExecAlwaysReturnsArray() + + + /** + * Data provider. + * + * @see testExecAlwaysReturnsArray + * + * {@internal Missing: test with a command which yields a `false` return value. + * JRF: I've not managed to find a command which does so, let alone one, which then + * doesn't have side-effects of uncatchable output while running the tests.} + * + * @return array>> + */ + public static function dataExecAlwaysReturnsArray() + { + return [ + 'valid command which won\'t have any output unless files in the bin dir have been modified & staged' => [ + // Largely using the command used in the filter, but only checking the bin dir. + // This should prevent the test unexpectedly failing during local development (in most cases). + 'cmd' => 'git diff --cached --name-only -- '.escapeshellarg(self::getBaseDir().'/bin'), + 'expected' => [], + ], + 'valid command which will have output' => [ + 'cmd' => 'git ls-files --exclude-standard -- '.escapeshellarg(self::getBaseDir().'/bin'), + 'expected' => [ + 'bin/phpcbf', + 'bin/phpcbf.bat', + 'bin/phpcs', + 'bin/phpcs.bat', + ], + ], + ]; + + }//end dataExecAlwaysReturnsArray() + + +}//end class