From bed54f57c58b6ef8b74917d7154630eafa3edc32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20B=C3=BCttner?= Date: Sun, 11 Nov 2018 14:53:53 +0100 Subject: [PATCH 01/20] replacing instances of DIRECTORY_SEPARATOR that break regexes on windows --- src/Candidate/CandidateExtractor.php | 8 ++++---- src/Composer/Command/Exporter/TextViolationExporter.php | 2 +- src/Composer/Iterator/SourceFileIteratorFactory.php | 5 ++++- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/Candidate/CandidateExtractor.php b/src/Candidate/CandidateExtractor.php index e065041..806214e 100644 --- a/src/Candidate/CandidateExtractor.php +++ b/src/Candidate/CandidateExtractor.php @@ -30,7 +30,7 @@ public function extract( SymbolIteratorInterface $symbols ): iterable { $repository = $composer->getRepositoryManager()->getLocalRepository(); - $vendorPath = $composer->getConfig()->get('vendor-dir', 0); + $vendorPath = str_replace('\\', '/', $composer->getConfig()->get('vendor-dir', 0)); $packages = []; @@ -103,7 +103,7 @@ private function extractPackage( if (!array_key_exists($name, $packagesPerSymbol)) { $reflection = $this->getClassReflection($name); - $file = $reflection->getFileName(); + $file = str_replace('\\', '/', $reflection->getFileName()); // This happens for symbols in the current package. if (strpos($file, $vendorPath) !== 0) { @@ -111,11 +111,11 @@ private function extractPackage( } $structure = explode( - DIRECTORY_SEPARATOR, + '/', preg_replace( sprintf( '/^%s/', - preg_quote($vendorPath . DIRECTORY_SEPARATOR, '/') + preg_quote($vendorPath . '/', '/') ), '', $file diff --git a/src/Composer/Command/Exporter/TextViolationExporter.php b/src/Composer/Command/Exporter/TextViolationExporter.php index 5395b57..e746248 100644 --- a/src/Composer/Command/Exporter/TextViolationExporter.php +++ b/src/Composer/Command/Exporter/TextViolationExporter.php @@ -42,7 +42,7 @@ public function __construct( */ public function export(ViolationIteratorInterface $violations): void { - $root = $this->workingDirectory . DIRECTORY_SEPARATOR; + $root = preg_quote($this->workingDirectory . '/', '#'); foreach ($violations as $violation) { $this->prompt->error($violation->getMessage()); diff --git a/src/Composer/Iterator/SourceFileIteratorFactory.php b/src/Composer/Iterator/SourceFileIteratorFactory.php index 88a862d..1f84189 100644 --- a/src/Composer/Iterator/SourceFileIteratorFactory.php +++ b/src/Composer/Iterator/SourceFileIteratorFactory.php @@ -156,6 +156,9 @@ public function __construct( string ...$excludePatterns ) { if (!empty($excludePatterns)) { + array_walk($excludePatterns, function(string &$item) { + $item = preg_quote(str_replace('\\', '/', $item), '@'); + }); $this->excludePattern = sprintf( '@^(%s)$@', implode('|', $excludePatterns) @@ -181,7 +184,7 @@ public function accept(): bool $this->excludePattern === null ?: !preg_match( $this->excludePattern, - $file->getRealPath() + str_replace('\\', '/', $file->getRealPath()) ) ) ); From 326e72ef2cc2f93a3cf5ffc15d0d379baa2ff58f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20B=C3=BCttner?= Date: Sun, 11 Nov 2018 15:21:24 +0100 Subject: [PATCH 02/20] fixing format of array_walk anonymous function --- src/Composer/Iterator/SourceFileIteratorFactory.php | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/Composer/Iterator/SourceFileIteratorFactory.php b/src/Composer/Iterator/SourceFileIteratorFactory.php index 1f84189..c59492b 100644 --- a/src/Composer/Iterator/SourceFileIteratorFactory.php +++ b/src/Composer/Iterator/SourceFileIteratorFactory.php @@ -156,9 +156,12 @@ public function __construct( string ...$excludePatterns ) { if (!empty($excludePatterns)) { - array_walk($excludePatterns, function(string &$item) { - $item = preg_quote(str_replace('\\', '/', $item), '@'); - }); + array_walk( + $excludePatterns, + function (string &$item) { + $item = preg_quote(str_replace('\\', '/', $item), '@'); + } + ); $this->excludePattern = sprintf( '@^(%s)$@', implode('|', $excludePatterns) From 6f31c686a31c9f48897b8d44a792de84f538f7da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20B=C3=BCttner?= Date: Tue, 13 Nov 2018 13:21:20 +0100 Subject: [PATCH 03/20] extracting preparation of exclude patterns --- .../Iterator/SourceFileIteratorFactory.php | 34 +++++++++++++------ 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/src/Composer/Iterator/SourceFileIteratorFactory.php b/src/Composer/Iterator/SourceFileIteratorFactory.php index c59492b..088828c 100644 --- a/src/Composer/Iterator/SourceFileIteratorFactory.php +++ b/src/Composer/Iterator/SourceFileIteratorFactory.php @@ -156,21 +156,35 @@ public function __construct( string ...$excludePatterns ) { if (!empty($excludePatterns)) { - array_walk( - $excludePatterns, - function (string &$item) { - $item = preg_quote(str_replace('\\', '/', $item), '@'); - } - ); - $this->excludePattern = sprintf( - '@^(%s)$@', - implode('|', $excludePatterns) - ); + $this->excludePattern = $this->preparePattern(...$excludePatterns); } parent::__construct($iterator); } + /** + * @param string ...$excludePatterns + * @return string + */ + private function preparePattern(string ...$excludePatterns): string + { + return sprintf( + '@^(%s)$@', + implode( + '|', + array_map( + function (string $pattern): string { + return preg_quote( + str_replace('\\', '/', $pattern), + '@' + ); + }, + $excludePatterns + ) + ) + ); + } + /** * Check whether the current element of the iterator is acceptable. * From 36789a48ebd4b225bd8b9eefea7cc32eb5b38521 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20B=C3=BCttner?= Date: Tue, 13 Nov 2018 13:55:08 +0100 Subject: [PATCH 04/20] formatting fixes --- src/Composer/Iterator/SourceFileIteratorFactory.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Composer/Iterator/SourceFileIteratorFactory.php b/src/Composer/Iterator/SourceFileIteratorFactory.php index 088828c..39de0ff 100644 --- a/src/Composer/Iterator/SourceFileIteratorFactory.php +++ b/src/Composer/Iterator/SourceFileIteratorFactory.php @@ -164,6 +164,7 @@ public function __construct( /** * @param string ...$excludePatterns + * * @return string */ private function preparePattern(string ...$excludePatterns): string @@ -172,7 +173,7 @@ private function preparePattern(string ...$excludePatterns): string '@^(%s)$@', implode( '|', - array_map( + array_map( function (string $pattern): string { return preg_quote( str_replace('\\', '/', $pattern), From 6b1a8734fff8757cabef050581c1d6824991c43b Mon Sep 17 00:00:00 2001 From: Jan-Marten de Boer Date: Tue, 13 Nov 2018 14:06:19 +0100 Subject: [PATCH 05/20] Add AppVeyor configuration By adding AppVeyor configuration, the code can be tested against Windows x64 platforms. --- appveyor.yml | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 appveyor.yml diff --git a/appveyor.yml b/appveyor.yml new file mode 100644 index 0000000..dbf312b --- /dev/null +++ b/appveyor.yml @@ -0,0 +1,40 @@ +build: off +platform: [x64] +clone_folder: c:\projects\php-project-workspace + +init: + - SET PATH=C:\Program Files\OpenSSL;c:\tools\php;%PATH% + - SET COMPOSER_NO_INTERACTION=1 + - SET PHP=1 + - SET ANSICON=121x90 (121x90) + +environment: + matrix: + - dependencies: lowest + php_ver_target: 7.1 + - dependencies: current + php_ver_target: 7.2 + - dependencies: highest + php_ver_target: 7.3 + +install: + - IF EXIST c:\tools\php (SET PHP=0) + - ps: appveyor-retry cinst --params '""/InstallDir:C:\tools\php""' --ignore-checksums -y php --version ((choco search php --exact --all-versions -r | select-string -pattern $env:php_ver_target | sort { [version]($_ -split '\|' | select -last 1) } -Descending | Select-Object -first 1) -replace '[php|]','') + - cd c:\tools\php + - IF %PHP%==1 copy php.ini-production php.ini /Y + - IF %PHP%==1 echo date.timezone="UTC" >> php.ini + - IF %PHP%==1 echo extension_dir=ext >> php.ini + - IF %PHP%==1 echo extension=php_openssl.dll >> php.ini + - IF %PHP%==1 echo extension=php_mbstring.dll >> php.ini + - IF %PHP%==1 echo extension=php_fileinfo.dll >> php.ini + - IF %PHP%==1 echo @php %%~dp0composer.phar %%* > composer.bat + - appveyor-retry appveyor DownloadFile https://getcomposer.org/composer.phar + - cd c:\projects\php-project-workspace + - IF %dependencies%==lowest appveyor-retry composer update --prefer-lowest --no-progress --profile -n + - IF %dependencies%==current appveyor-retry composer install --no-progress --profile + - IF %dependencies%==highest appveyor-retry composer update --no-progress --profile -n + - composer show + +test_script: + - cd c:\projects\php-project-workspace + - vendor/bin/grumphp run From 66c6c9654f9ca00b5dd39bc4652c4b735c4d671d Mon Sep 17 00:00:00 2001 From: Jan-Marten de Boer Date: Tue, 13 Nov 2018 14:16:29 +0100 Subject: [PATCH 06/20] Enable AppVeyor caching --- appveyor.yml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index dbf312b..fa5ef58 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -15,7 +15,14 @@ environment: - dependencies: current php_ver_target: 7.2 - dependencies: highest - php_ver_target: 7.3 + php_ver_target: 7.2 + +cache: + - '%LOCALAPPDATA%\Composer\files -> composer.lock' + - composer.phar + - C:\ProgramData\chocolatey\bin -> .appveyor.yml + - C:\ProgramData\chocolatey\lib -> .appveyor.yml + - c:\tools\php -> .appveyor.yml install: - IF EXIST c:\tools\php (SET PHP=0) From 2147c7de6e847a718f4640ef1851706262f7dd22 Mon Sep 17 00:00:00 2001 From: Jan-Marten de Boer Date: Tue, 13 Nov 2018 14:19:11 +0100 Subject: [PATCH 07/20] Remove build from matrix The highest and current versions are equal, so running those tests twice is superfluous. --- appveyor.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index fa5ef58..165d88b 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -14,8 +14,6 @@ environment: php_ver_target: 7.1 - dependencies: current php_ver_target: 7.2 - - dependencies: highest - php_ver_target: 7.2 cache: - '%LOCALAPPDATA%\Composer\files -> composer.lock' From 604cb0844fe6ed0076090c3cdc305de33bd8a693 Mon Sep 17 00:00:00 2001 From: Jan-Marten de Boer Date: Tue, 13 Nov 2018 14:21:00 +0100 Subject: [PATCH 08/20] Add AppVeyor build badge Add a badge for the build status of the master branch, from AppVeyor. --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index e3b5240..f740cba 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,7 @@ [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/mediact/dependency-guard/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/mediact/dependency-guard/?branch=master) [![Code Coverage](https://scrutinizer-ci.com/g/mediact/dependency-guard/badges/coverage.png?b=master)](https://scrutinizer-ci.com/g/mediact/dependency-guard/?branch=master) [![Build Status](https://scrutinizer-ci.com/g/mediact/dependency-guard/badges/build.png?b=master)](https://scrutinizer-ci.com/g/mediact/dependency-guard/build-status/master) +[![Build status](https://ci.appveyor.com/api/projects/status/79f486l0u1p2i5gq/branch/master?svg=true)](https://ci.appveyor.com/project/mediactbv/dependency-guard/branch/master) [![Code Intelligence Status](https://scrutinizer-ci.com/g/mediact/dependency-guard/badges/code-intelligence.svg?b=master)](https://scrutinizer-ci.com/code-intelligence) [![license](https://img.shields.io/github/license/mediact/dependency-guard.png)](LICENSE.md) From 2d19bfc280e3b7915d17caa5c67d4a0468b69994 Mon Sep 17 00:00:00 2001 From: Jan-Marten de Boer Date: Tue, 13 Nov 2018 14:37:31 +0100 Subject: [PATCH 09/20] Reduce Composer scenarios The project does not need multiple approaches to install Composer packages. --- appveyor.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index 165d88b..dd712c1 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -35,9 +35,7 @@ install: - IF %PHP%==1 echo @php %%~dp0composer.phar %%* > composer.bat - appveyor-retry appveyor DownloadFile https://getcomposer.org/composer.phar - cd c:\projects\php-project-workspace - - IF %dependencies%==lowest appveyor-retry composer update --prefer-lowest --no-progress --profile -n - - IF %dependencies%==current appveyor-retry composer install --no-progress --profile - - IF %dependencies%==highest appveyor-retry composer update --no-progress --profile -n + - appveyor-retry composer install --no-progress --profile - composer show test_script: From 5e092d7b6ad2b4baeb2937088ef403c78d3ca05d Mon Sep 17 00:00:00 2001 From: Jan-Marten de Boer Date: Tue, 13 Nov 2018 14:49:37 +0100 Subject: [PATCH 10/20] Remove composer.lock between builds The file `composer.lock` appears to get corrupted between AppVeyor builds. By removing it before installation, the lock file should be created freshly every time. --- appveyor.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index dd712c1..82ed371 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -16,7 +16,6 @@ environment: php_ver_target: 7.2 cache: - - '%LOCALAPPDATA%\Composer\files -> composer.lock' - composer.phar - C:\ProgramData\chocolatey\bin -> .appveyor.yml - C:\ProgramData\chocolatey\lib -> .appveyor.yml @@ -35,7 +34,8 @@ install: - IF %PHP%==1 echo @php %%~dp0composer.phar %%* > composer.bat - appveyor-retry appveyor DownloadFile https://getcomposer.org/composer.phar - cd c:\projects\php-project-workspace - - appveyor-retry composer install --no-progress --profile + - del /f composer.lock + - appveyor-retry composer update --no-progress --profile - composer show test_script: From e34cc935e36c82afdb6fc2da5bfb04662497de6a Mon Sep 17 00:00:00 2001 From: Jan-Marten de Boer Date: Fri, 23 Nov 2018 16:56:16 +0100 Subject: [PATCH 11/20] Add regression test: Candidate extractor Add a regression test to verify that the candidate extractor does not pass tests when a Windows like vendor directory is used. --- .../Issue31/CandidateExtractorTest.php | 266 ++++++++++++++++++ 1 file changed, 266 insertions(+) create mode 100644 tests/Regression/Issue31/CandidateExtractorTest.php diff --git a/tests/Regression/Issue31/CandidateExtractorTest.php b/tests/Regression/Issue31/CandidateExtractorTest.php new file mode 100644 index 0000000..0324940 --- /dev/null +++ b/tests/Regression/Issue31/CandidateExtractorTest.php @@ -0,0 +1,266 @@ +extract($composer, $symbols); + + $this->assertCount($expected, $candidates); + + foreach ($candidates as $candidate) { + $this->assertInstanceOf(CandidateInterface::class, $candidate); + } + } + + /** + * @return Composer[][]|SymbolIteratorInterface[][]|int[][] + */ + public function extractProvider(): array + { + $config = $this->createMock(Config::class); + + $config + ->expects(self::any()) + ->method('get') + ->with('vendor-dir', 0) + ->willReturn( + str_replace( + '/', + '\\', + realpath( + __DIR__ . '/../../../vendor' + ) + ) + ); + + return [ + [ + $this->createComposer( + $config, + $this->createRepository() + ), + $this->createSymbolIterator(), + 0 + ], + [ + $this->createComposer( + $config, + $this->createRepository() + ), + $this->createSymbolIterator( + // Code outside vendor. + $this->createSymbol(CandidateExtractor::class), + // Code inside vendor. + $this->createSymbol(Composer::class), + // Core code, inside vendor, outside a vendor package. + $this->createSymbol(ClassLoader::class) + ), + 0 + ], + [ + $this->createComposer( + $config, + $this->createRepository( + $this->createPackage('composer/composer') + ) + ), + $this->createSymbolIterator(), + 0 + ], + [ + $this->createComposer( + $config, + $this->createRepository( + $this->createPackage('composer/composer') + ) + ), + $this->createSymbolIterator( + $this->createSymbol(Composer::class) + ), + 1 + ], + [ + $this->createComposer( + $config, + $this->createRepository( + $this->createPackage('composer/composer') + ) + ), + // Multiple symbols per package. + $this->createSymbolIterator( + $this->createSymbol(Composer::class), + $this->createSymbol(Composer::class) + ), + 1 + ], + [ + $this->createComposer( + $config, + // Contains duplicate packages. + $this->createRepository( + $this->createPackage('composer/composer'), + $this->createPackage('composer/composer') + ) + ), + // Multiple symbols per package. + $this->createSymbolIterator( + $this->createSymbol(Composer::class), + $this->createSymbol(Composer::class) + ), + 1 + ] + ]; + } + + /** + * @param string $name + * + * @return SymbolInterface + */ + private function createSymbol(string $name): SymbolInterface + { + /** @var SymbolInterface|MockObject $symbol */ + $symbol = $this->createMock(SymbolInterface::class); + + $symbol + ->expects(self::any()) + ->method('getName') + ->willReturn($name); + + return $symbol; + } + + /** + * @param SymbolInterface ...$symbols + * + * @return SymbolIteratorInterface + */ + private function createSymbolIterator( + SymbolInterface ...$symbols + ): SymbolIteratorInterface { + /** @var SymbolIteratorInterface|MockObject $iterator */ + $iterator = $this->createMock(SymbolIteratorInterface::class); + $valid = array_fill(0, count($symbols), true); + $valid[] = false; + + $iterator + ->expects(self::exactly(count($valid))) + ->method('valid') + ->willReturn(...$valid); + + $iterator + ->expects(self::exactly(count($symbols))) + ->method('current') + ->willReturnOnConsecutiveCalls(...$symbols); + + return $iterator; + } + + /** + * @param string $name + * + * @return PackageInterface + */ + private function createPackage(string $name): PackageInterface + { + /** @var PackageInterface|MockObject $package */ + $package = $this->createMock(PackageInterface::class); + + $package + ->expects(self::any()) + ->method('getName') + ->willReturn($name); + + return $package; + } + + /** + * @param PackageInterface ...$packages + * + * @return WritableRepositoryInterface + */ + private function createRepository( + PackageInterface ...$packages + ): WritableRepositoryInterface { + /** @var WritableRepositoryInterface|MockObject $repository */ + $repository = $this->createMock(WritableRepositoryInterface::class); + + $repository + ->expects(self::any()) + ->method('getPackages') + ->willReturn($packages); + + return $repository; + } + + /** + * @param Config $config + * @param WritableRepositoryInterface $localRepository + * + * @return Composer + */ + private function createComposer( + Config $config, + WritableRepositoryInterface $localRepository + ): Composer { + $repositoryManager = $this->createMock(RepositoryManager::class); + + $repositoryManager + ->expects(self::any()) + ->method('getLocalRepository') + ->willReturn($localRepository); + + /** @var Composer|MockObject $composer */ + $composer = $this->createMock(Composer::class); + + $composer + ->expects(self::any()) + ->method('getConfig') + ->willReturn($config); + + $composer + ->expects(self::any()) + ->method('getRepositoryManager') + ->willReturn($repositoryManager); + + return $composer; + } +} From b5b2823a7116ac60a1c900029a50b2b505d56dd3 Mon Sep 17 00:00:00 2001 From: Jan-Marten de Boer Date: Fri, 23 Nov 2018 16:58:55 +0100 Subject: [PATCH 12/20] Ensure regression tests run Ensure the tests inside the regression test folder are run correctly by adding its directory as the first directory in the default testsuite. --- phpunit.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/phpunit.xml b/phpunit.xml index 5c4659c..e22397a 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -3,6 +3,7 @@ bootstrap="vendor/autoload.php"> + tests/Regression tests From 3e4b6ada39d1b6b9e134c863ddd59c4925657c83 Mon Sep 17 00:00:00 2001 From: Jan-Marten de Boer Date: Fri, 23 Nov 2018 17:00:12 +0100 Subject: [PATCH 13/20] Add regression test: Text violation exporter Add a regression test to confirm that tests fail when using a Windows like working directory, when the directory separator is a backslash (\). --- .../Issue31/TextViolationExporterTest.php | 230 ++++++++++++++++++ 1 file changed, 230 insertions(+) create mode 100644 tests/Regression/Issue31/TextViolationExporterTest.php diff --git a/tests/Regression/Issue31/TextViolationExporterTest.php b/tests/Regression/Issue31/TextViolationExporterTest.php new file mode 100644 index 0000000..abb2a77 --- /dev/null +++ b/tests/Regression/Issue31/TextViolationExporterTest.php @@ -0,0 +1,230 @@ +markTestSkipped( + 'This can only be tested on an OS having backslash (\\) as directory separator.' + ); + return; + } + + /** @var SymfonyStyle|MockObject $prompt */ + $prompt = $this->createMock(SymfonyStyle::class); + $subject = new TextViolationExporter( + $prompt, + static::WORKING_DIRECTORY + ); + + $prompt + ->expects(self::exactly($numSuccess)) + ->method('success') + ->with('No dependency violations encountered!'); + + $prompt + ->expects(self::exactly($numError)) + ->method('error') + ->with(self::isType('string')); + + $subject->export($violations); + } + + /** + * @param ViolationInterface ...$violations + * + * @return ViolationIteratorInterface + */ + private function createViolations( + ViolationInterface ...$violations + ): ViolationIteratorInterface { + /** @var ViolationIteratorInterface|MockObject $iterator */ + $iterator = $this->createMock(ViolationIteratorInterface::class); + $valid = array_fill(0, count($violations), true); + $valid[] = false; + + $iterator + ->expects(self::any()) + ->method('count') + ->willReturn(count($violations)); + + $iterator + ->expects(self::any()) + ->method('valid') + ->willReturn(...$valid); + + $iterator + ->expects(self::any()) + ->method('current') + ->willReturnOnConsecutiveCalls( + ...$violations + ); + + return $iterator; + } + + /** + * @param string $message + * @param SymbolInterface ...$symbols + * + * @return ViolationInterface + */ + private function createViolation( + string $message, + SymbolInterface ...$symbols + ): ViolationInterface { + /** @var SymbolIteratorInterface|MockObject $symbolIterator */ + $symbolIterator = $this->createMock(SymbolIteratorInterface::class); + $valid = array_fill(0, count($symbols), true); + $valid[] = false; + + $symbolIterator + ->expects(self::any()) + ->method('valid') + ->willReturn(...$valid); + + $symbolIterator + ->expects(self::any()) + ->method('current') + ->willReturnOnConsecutiveCalls(...$symbols); + + /** @var ViolationInterface|MockObject $violation */ + $violation = $this->createMock(ViolationInterface::class); + + $violation + ->expects(self::any()) + ->method('getMessage') + ->willReturn($message); + + $violation + ->expects(self::any()) + ->method('getSymbols') + ->willReturn($symbolIterator); + + return $violation; + } + + /** + * @param string $name + * @param string $file + * @param int $line + * + * @return SymbolInterface + */ + private function createSymbol( + string $name, + string $file, + int $line + ): SymbolInterface { + /** @var SymbolInterface|MockObject $symbol */ + $symbol = $this->createMock(SymbolInterface::class); + + $symbol + ->expects(self::any()) + ->method('getName') + ->willReturn($name); + + $symbol + ->expects(self::any()) + ->method('getFile') + ->willReturn( + str_replace('/', '\\', $file) + ); + + $symbol + ->expects(self::any()) + ->method('getLine') + ->willReturn($line); + + return $symbol; + } + + /** + * @return ViolationIteratorInterface[][]|int[][] + */ + public function violationProvider(): array + { + return [ + [ + $this->createViolations(), + 1, + 0 + ], + [ + $this->createViolations( + $this->createViolation('foo') + ), + 0, + 2 + ], + [ + $this->createViolations( + $this->createViolation('foo'), + $this->createViolation( + 'bar', + $this->createSymbol( + __CLASS__, + __FILE__, + __LINE__ + ) + ), + $this->createViolation( + 'baz', + $this->createSymbol( + __CLASS__, + __FILE__, + __LINE__ + ), + $this->createSymbol( + __CLASS__, + __FILE__, + __LINE__ + ), + $this->createSymbol( + __CLASS__, + __FILE__, + __LINE__ + ) + ) + ), + 0, + 4 + ] + ]; + } +} From d21c4f82004d63cb73954e953c992e1ca94e2f42 Mon Sep 17 00:00:00 2001 From: Jan-Marten de Boer Date: Fri, 23 Nov 2018 17:31:11 +0100 Subject: [PATCH 14/20] Add regression test: Source file iterator factory Add a regression test to test that the regular expressions in the source file iterator break when a backslash (\) is used as directory separator. --- .../Issue31/SourceFileIteratorFactoryTest.php | 276 ++++++++++++++++++ 1 file changed, 276 insertions(+) create mode 100644 tests/Regression/Issue31/SourceFileIteratorFactoryTest.php diff --git a/tests/Regression/Issue31/SourceFileIteratorFactoryTest.php b/tests/Regression/Issue31/SourceFileIteratorFactoryTest.php new file mode 100644 index 0000000..0b8bd2f --- /dev/null +++ b/tests/Regression/Issue31/SourceFileIteratorFactoryTest.php @@ -0,0 +1,276 @@ +assertInstanceOf( + FileIteratorInterface::class, + $subject->create($composer) + ); + } + + /** + * @param Config $config + * @param AutoloadGenerator $autoloadGenerator + * + * @return Composer + */ + private function createComposer( + Config $config, + AutoloadGenerator $autoloadGenerator + ): Composer { + /** @var Composer|MockObject $composer */ + $composer = $this->createMock(Composer::class); + + $composer + ->expects(self::any()) + ->method('getInstallationManager') + ->willReturn( + $this->createMock(InstallationManager::class) + ); + + $composer + ->expects(self::any()) + ->method('getPackage') + ->willReturn( + $this->createMock(RootPackageInterface::class) + ); + + $composer + ->expects(self::any()) + ->method('getConfig') + ->willReturn($config); + + $composer + ->expects(self::any()) + ->method('getAutoloadGenerator') + ->willReturn($autoloadGenerator); + + return $composer; + } + + /** + * @param bool $authoritative + * + * @return Config + */ + private function createConfig(bool $authoritative): Config + { + /** @var Config|MockObject $config */ + $config = $this->createMock(Config::class); + + $config + ->expects(self::any()) + ->method('get') + ->with('classmap-authoritative') + ->willReturn($authoritative); + + return $config; + } + + /** + * @param array $directives + * + * @return AutoloadGenerator + */ + public function createAutoloadGenerator( + array $directives = [] + ): AutoloadGenerator { + /** @var AutoloadGenerator|MockObject $generator */ + $generator = $this->createMock(AutoloadGenerator::class); + + $generator + ->expects(self::any()) + ->method('buildPackageMap') + ->with( + self::isInstanceOf(InstallationManager::class), + self::isInstanceOf(RootPackageInterface::class), + self::isType('array') + ) + ->willReturn([]); + + $generator + ->expects(self::any()) + ->method('parseAutoloads') + ->with( + self::isType('array'), + self::isInstanceOf(RootPackageInterface::class) + ) + ->willReturn($directives); + + return $generator; + } + + /** + * @return Composer[][] + */ + public function emptyProvider(): array + { + return [ + [ + $this->createComposer( + $this->createConfig(true), + $this->createAutoloadGenerator() + ) + ], + [ + $this->createComposer( + $this->createConfig(false), + $this->createAutoloadGenerator() + ) + ] + ]; + } + + /** + * @return Composer[][] + */ + public function filesProvider(): array + { + $config = $this->createConfig(true); + + return [ + [ + $this->createComposer( + $config, + $this->createAutoloadGenerator() + ) + ], + [ + $this->createComposer( + $config, + $this->createAutoloadGenerator( + [ + 'files' => [ + // Readable file. + __FILE__, + // Not readable. + __CLASS__ + ] + ] + ) + ) + ] + ]; + } + + /** + * @return Composer[][] + */ + public function classmapProvider(): array + { + return [ + [ + $this->createComposer( + $this->createConfig(true), + $this->createAutoloadGenerator( + [ + 'classmap' => [ + __NAMESPACE__ => __DIR__ + ] + ] + ) + ) + ], + [ + $this->createComposer( + $this->createConfig(false), + $this->createAutoloadGenerator( + [ + 'classmap' => [ + __NAMESPACE__ => __DIR__ + ] + ] + ) + ) + ], + [ + $this->createComposer( + $this->createConfig(true), + $this->createAutoloadGenerator( + [ + 'classmap' => [ + __NAMESPACE__ => __DIR__, + __CLASS__ => __FILE__ + ], + 'exclude-from-classmap' => [ + str_replace('/', '\\', __FILE__) + ] + ] + ) + ) + ], + ]; + } + + /** + * @return Composer[][] + */ + public function namespaceProvider(): array + { + $config = $this->createConfig(true); + + return [ + [ + $this->createComposer( + $config, + $this->createAutoloadGenerator( + [ + 'psr-0' => [ + __NAMESPACE__ => [__DIR__], + __CLASS__ => [__DIR__] + ] + ] + ) + ) + ], + [ + $this->createComposer( + $config, + $this->createAutoloadGenerator( + [ + 'psr-4' => [ + __NAMESPACE__ => [__DIR__], + __CLASS__ => [__DIR__] + ] + ] + ) + ) + ] + ]; + } +} From a05dbb2fb95e2073b3065434d3aa7c8aebc596d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20B=C3=BCttner?= Date: Sat, 24 Nov 2018 00:40:03 +0100 Subject: [PATCH 15/20] adding missing covers annotation --- tests/Composer/Iterator/SourceFileIteratorFactoryTest.php | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/Composer/Iterator/SourceFileIteratorFactoryTest.php b/tests/Composer/Iterator/SourceFileIteratorFactoryTest.php index affa003..57aad5f 100644 --- a/tests/Composer/Iterator/SourceFileIteratorFactoryTest.php +++ b/tests/Composer/Iterator/SourceFileIteratorFactoryTest.php @@ -35,6 +35,7 @@ class SourceFileIteratorFactoryTest extends TestCase * @covers ::createFilesIterator * @covers ::createNamespaceIterator * @covers ::createClassmapIterator + * @covers ::preparePattern */ public function testCreate(Composer $composer): void { From 6d8514c802397d4b14624e14209aa60cb1f941ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20B=C3=BCttner?= Date: Sat, 24 Nov 2018 00:57:14 +0100 Subject: [PATCH 16/20] removing method locking of covers annotation to handle different providers --- tests/Composer/Iterator/SourceFileIteratorFactoryTest.php | 6 ------ 1 file changed, 6 deletions(-) diff --git a/tests/Composer/Iterator/SourceFileIteratorFactoryTest.php b/tests/Composer/Iterator/SourceFileIteratorFactoryTest.php index 57aad5f..dea1b34 100644 --- a/tests/Composer/Iterator/SourceFileIteratorFactoryTest.php +++ b/tests/Composer/Iterator/SourceFileIteratorFactoryTest.php @@ -30,12 +30,6 @@ class SourceFileIteratorFactoryTest extends TestCase * @param Composer $composer * * @return void - * - * @covers ::create - * @covers ::createFilesIterator - * @covers ::createNamespaceIterator - * @covers ::createClassmapIterator - * @covers ::preparePattern */ public function testCreate(Composer $composer): void { From 926779cd7d518a9f8911dc362084f145f560107f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20B=C3=BCttner?= Date: Sat, 24 Nov 2018 01:07:56 +0100 Subject: [PATCH 17/20] adding duplicate covers annotation to follow phpcs rule --- tests/Composer/Iterator/SourceFileIteratorFactoryTest.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/Composer/Iterator/SourceFileIteratorFactoryTest.php b/tests/Composer/Iterator/SourceFileIteratorFactoryTest.php index dea1b34..b139a93 100644 --- a/tests/Composer/Iterator/SourceFileIteratorFactoryTest.php +++ b/tests/Composer/Iterator/SourceFileIteratorFactoryTest.php @@ -30,6 +30,8 @@ class SourceFileIteratorFactoryTest extends TestCase * @param Composer $composer * * @return void + * + * @covers \Mediact\DependencyGuard\Composer\Iterator\SourceFileIteratorFactory */ public function testCreate(Composer $composer): void { From 22f012564cee5a527558747a0b6a26cb6ca74b85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20B=C3=BCttner?= Date: Sat, 24 Nov 2018 11:07:56 +0100 Subject: [PATCH 18/20] moving preparePattern to outer class adjusting typehints to match types to keep logic for pattern localized --- .../Iterator/SourceFileIteratorFactory.php | 64 ++++++++++--------- .../SourceFileIteratorFactoryTest.php | 6 +- 2 files changed, 38 insertions(+), 32 deletions(-) diff --git a/src/Composer/Iterator/SourceFileIteratorFactory.php b/src/Composer/Iterator/SourceFileIteratorFactory.php index 39de0ff..fbdb9b7 100644 --- a/src/Composer/Iterator/SourceFileIteratorFactory.php +++ b/src/Composer/Iterator/SourceFileIteratorFactory.php @@ -141,51 +141,25 @@ private function createClassmapIterator( ); } - return new class ($files, ...$exclude) extends FilterIterator { + return new class ($files, $this->preparePattern(...$exclude)) extends FilterIterator { /** @var string|null */ private $excludePattern; /** * Constructor. * - * @param Iterator $iterator - * @param string ...$excludePatterns + * @param Iterator $iterator + * @param string|null $excludePattern */ public function __construct( Iterator $iterator, - string ...$excludePatterns + ?string $excludePattern ) { - if (!empty($excludePatterns)) { - $this->excludePattern = $this->preparePattern(...$excludePatterns); - } + $this->excludePattern = $excludePattern; parent::__construct($iterator); } - /** - * @param string ...$excludePatterns - * - * @return string - */ - private function preparePattern(string ...$excludePatterns): string - { - return sprintf( - '@^(%s)$@', - implode( - '|', - array_map( - function (string $pattern): string { - return preg_quote( - str_replace('\\', '/', $pattern), - '@' - ); - }, - $excludePatterns - ) - ) - ); - } - /** * Check whether the current element of the iterator is acceptable. * @@ -209,4 +183,32 @@ public function accept(): bool } }; } + + /** + * @param string ...$excludePatterns + * + * @return string|null + */ + private function preparePattern(string ...$excludePatterns): ?string + { + if (count($excludePatterns) === 0) { + return null; + } + + return sprintf( + '@^(%s)$@', + implode( + '|', + array_map( + function (string $pattern): string { + return preg_quote( + str_replace('\\', '/', $pattern), + '@' + ); + }, + $excludePatterns + ) + ) + ); + } } diff --git a/tests/Composer/Iterator/SourceFileIteratorFactoryTest.php b/tests/Composer/Iterator/SourceFileIteratorFactoryTest.php index b139a93..57aad5f 100644 --- a/tests/Composer/Iterator/SourceFileIteratorFactoryTest.php +++ b/tests/Composer/Iterator/SourceFileIteratorFactoryTest.php @@ -31,7 +31,11 @@ class SourceFileIteratorFactoryTest extends TestCase * * @return void * - * @covers \Mediact\DependencyGuard\Composer\Iterator\SourceFileIteratorFactory + * @covers ::create + * @covers ::createFilesIterator + * @covers ::createNamespaceIterator + * @covers ::createClassmapIterator + * @covers ::preparePattern */ public function testCreate(Composer $composer): void { From 7b919acedeb3a2e24cc2f18f0fe59c6dd9e51798 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20B=C3=BCttner?= Date: Sat, 24 Nov 2018 13:05:41 +0100 Subject: [PATCH 19/20] introducing regression test free testsuite for coverage --- phpunit.xml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/phpunit.xml b/phpunit.xml index e22397a..c66cce5 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -3,9 +3,12 @@ bootstrap="vendor/autoload.php"> - tests/Regression tests + + tests + tests/Regression + tests/Regression From 40a1f52b902dc8898fa0f632754fc20b23acfb45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20B=C3=BCttner?= Date: Sat, 24 Nov 2018 16:12:06 +0100 Subject: [PATCH 20/20] restoring regression test positioning in default test suite --- phpunit.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/phpunit.xml b/phpunit.xml index c66cce5..ad07d23 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -3,6 +3,7 @@ bootstrap="vendor/autoload.php"> + tests/Regression tests