diff --git a/Makefile b/Makefile index a9c66e39..e065c876 100644 --- a/Makefile +++ b/Makefile @@ -14,7 +14,7 @@ help: ## Build ##--------------------------------------------------------------------------- -build: ## Build the PHAR +build: ## Build the PHAR build: vendor # Cleanup existing artefacts rm -f bin/php-scoper.phar @@ -42,18 +42,18 @@ build: vendor ## Tests ##--------------------------------------------------------------------------- -test: ## Run all the tests +test: ## Run all the tests test: tu e2e -tu: ## Run PHPUnit tests +tu: ## Run PHPUnit tests tu: vendor php -d zend.enable_gc=0 $(PHPUNIT) -tc: ## Run PHPUnit tests with test coverage +tc: ## Run PHPUnit tests with test coverage tc: vendor phpdbg -qrr -d zend.enable_gc=0 $(PHPUNIT) --coverage-html=dist/coverage --coverage-text -e2e: ## Run end-to-end tests +e2e: ## Run end-to-end tests e2e: scoper php -d zend.enable_gc=0 $(PHPSCOPER) add-prefix fixtures/set004 -o build/set004 -f composer -d=build/set004 dump-autoload @@ -67,7 +67,7 @@ e2e: scoper php build/set005/bin/greet.phar > build/output diff fixtures/set005/expected-output build/output -tb: ## Run Blackfire profiling +tb: ## Run Blackfire profiling tb: vendor rm -rf build # diff --git a/fixtures/set006/scoper.inc.php b/fixtures/set006/scoper.inc.php new file mode 100644 index 00000000..1b654078 --- /dev/null +++ b/fixtures/set006/scoper.inc.php @@ -0,0 +1,9 @@ +, + * Pádraic Brady + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + function (string $filePath, string $prefix, string $content): string { + // + // PHP-Parser patch + // + + if ($filePath === realpath(__DIR__.'vendor/nikic/php-parser/lib/PhpParser/Lexer.php')) { + return preg_replace( + '%if \(defined\(\$name \= \'PhpParser\\\\Parser\\\\Tokens\:\:\'%', + <<<'PHP' +$ns = explode(\'\\\\\', __NAMESPACE__); +if (defined($name = array_shift($ns) . '\\\\' . 'PhpParser\\\\Parser\\\\Tokens::' +PHP + , + $content + ); + } + + if ($filePath === realpath(__DIR__.'vendor/nikic/php-parser/lib/PhpParser/NodeAbstract.php')) { + return preg_replace( + '%rtrim\(get_class\(\$this\), \'\'_\'\'\), 15\)%', + 'rtrim(get_class($this), \'_\'), 15+23)', + $content + ); + } + + return $content; + }, +]; diff --git a/src/Console/Command/AddPrefixCommand.php b/src/Console/Command/AddPrefixCommand.php index 176785f3..70955904 100644 --- a/src/Console/Command/AddPrefixCommand.php +++ b/src/Console/Command/AddPrefixCommand.php @@ -37,6 +37,10 @@ final class AddPrefixCommand extends Command const OUTPUT_DIR_OPT = 'output-dir'; /** @internal */ const FORCE_OPT = 'force'; + /** @internal */ + const PATCH_FILE = 'patch-file'; + /** @internal */ + const PATCH_FILE_DEFAULT = 'scoper.inc.php'; private $fileSystem; private $handle; @@ -69,7 +73,7 @@ protected function configure() self::PREFIX_OPT, 'p', InputOption::VALUE_REQUIRED, - 'The namespace prefix to add' + 'The namespace prefix to add.' ) ->addOption( self::OUTPUT_DIR_OPT, @@ -82,7 +86,17 @@ protected function configure() self::FORCE_OPT, 'f', InputOption::VALUE_NONE, - 'Deletes any existing content in the output directory without any warning' + 'Deletes any existing content in the output directory without any warning.' + ) + ->addOption( + self::PATCH_FILE, + 'c', + InputOption::VALUE_REQUIRED, + sprintf( + 'Configuration file for the patchers. Will use "%s" if found by default', + self::PATCH_FILE_DEFAULT + ), + null ) ; } @@ -97,6 +111,7 @@ protected function execute(InputInterface $input, OutputInterface $output) $this->validatePrefix($input); $this->validatePaths($input); $this->validateOutputDir($input, $io); + $patchers = $this->validatePatchers($input, $io); $logger = new ConsoleLogger( $this->getApplication(), @@ -113,6 +128,7 @@ protected function execute(InputInterface $input, OutputInterface $output) $input->getOption(self::PREFIX_OPT), $input->getArgument(self::PATH_ARG), $input->getOption(self::OUTPUT_DIR_OPT), + $patchers, $logger ); } catch (Throwable $throwable) { @@ -234,4 +250,83 @@ private function validateOutputDir(InputInterface $input, OutputStyle $io) $this->fileSystem->remove($outputDir); } } + + /** + * @param InputInterface $input + * @param OutputStyle $io + * + * @return callable[] + */ + private function validatePatchers(InputInterface $input, OutputStyle $io): array + { + $patchFile = $input->getOption(self::PATCH_FILE); + + if (null === $patchFile) { + $patchFile = $this->makeAbsolutePath(self::PATCH_FILE_DEFAULT); + + if (false === file_exists($patchFile)) { + $io->writeln( + sprintf( + 'Patch file "%s" not found. Skipping.', + $patchFile + ), + OutputStyle::VERBOSITY_DEBUG + ); + + return []; + } + } else { + $patchFile = $this->makeAbsolutePath($patchFile); + } + + if (false === file_exists($patchFile)) { + throw new RuntimeException( + sprintf( + 'Could not find the file "%s".', + $patchFile + ) + ); + } + + $io->writeln( + sprintf( + 'Using the configuration file "%s".', + $patchFile + ), + OutputStyle::VERBOSITY_DEBUG + ); + + $patchers = include $patchFile; + + if (false === is_array($patchers)) { + throw new RuntimeException( + sprintf( + 'Expected patchers to be an array of callables, found "%s" instead.', + gettype($patchers) + ) + ); + } + + foreach ($patchers as $index => $patcher) { + if (false === is_callable($patcher)) { + throw new RuntimeException( + sprintf( + 'Expected patchers to be an array of callables, the "%d" element is not.', + $index + ) + ); + } + } + + return $patchers; + } + + private function makeAbsolutePath(string $path): string + { + if (false === $this->fileSystem->isAbsolutePath($path)) { + $path = getcwd().DIRECTORY_SEPARATOR.$path; + } + + return $path; + } } diff --git a/src/Handler/HandleAddPrefix.php b/src/Handler/HandleAddPrefix.php index 659c8773..8fac5cc3 100644 --- a/src/Handler/HandleAddPrefix.php +++ b/src/Handler/HandleAddPrefix.php @@ -42,19 +42,20 @@ public function __construct(Scoper $scoper) /** * Apply prefix to all the code found in the given paths, AKA scope all the files found. * - * @param string $prefix e.g. 'Foo' - * @param string[] $paths List of files to scan (absolute paths) - * @param string $output absolute path to the output directory + * @param string $prefix e.g. 'Foo' + * @param string[] $paths List of files to scan (absolute paths) + * @param string $output absolute path to the output directory + * @param callable[] $patchers * @param ConsoleLogger $logger */ - public function __invoke(string $prefix, array $paths, string $output, ConsoleLogger $logger) + public function __invoke(string $prefix, array $paths, string $output, array $patchers, ConsoleLogger $logger) { $this->fileSystem->mkdir($output); try { $files = $this->retrieveFiles($paths, $output); - $this->scopeFiles($files, $prefix, $logger); + $this->scopeFiles($files, $prefix, $patchers, $logger); } catch (Throwable $throwable) { $this->fileSystem->remove($output); @@ -141,22 +142,35 @@ function (array $files, string $file) use ($output, $commonPath): array { /** * @param string[] $files * @param string $prefix + * @param callable[] $patchers * @param ConsoleLogger $logger */ - private function scopeFiles(array $files, string $prefix, ConsoleLogger $logger) + private function scopeFiles(array $files, string $prefix, array $patchers, ConsoleLogger $logger) { $count = count($files); $logger->outputFileCount($count); foreach ($files as $inputFilePath => $outputFilePath) { - $this->scopeFile($inputFilePath, $outputFilePath, $prefix, $logger); + $this->scopeFile($inputFilePath, $outputFilePath, $prefix, $patchers, $logger); } } - private function scopeFile(string $inputFilePath, string $outputFilePath, string $prefix, ConsoleLogger $logger) - { + /** + * @param string $inputFilePath + * @param string $outputFilePath + * @param string $prefix + * @param callable[] $patchers + * @param ConsoleLogger $logger + */ + private function scopeFile( + string $inputFilePath, + string $outputFilePath, + string $prefix, + array $patchers, + ConsoleLogger $logger + ) { try { - $scoppedContent = $this->scoper->scope($inputFilePath, $prefix); + $scoppedContent = $this->scoper->scope($inputFilePath, $prefix, $patchers); } catch (PhpParserError $error) { throw new ParsingException( sprintf( diff --git a/src/Scoper.php b/src/Scoper.php index 8c9b1381..e5e7b71c 100644 --- a/src/Scoper.php +++ b/src/Scoper.php @@ -21,12 +21,13 @@ interface Scoper /** * Scope AKA. apply the given prefix to the file in the appropriate way. * - * @param string $filePath File to scope - * @param string $prefix Prefix to apply to the file + * @param string $filePath File to scope + * @param string $prefix Prefix to apply to the file + * @param callable[] $patchers * * @throws ParsingException * * @return string Content of the file with the prefix applied */ - public function scope(string $filePath, string $prefix): string; + public function scope(string $filePath, string $prefix, array $patchers): string; } diff --git a/src/Scoper/Composer/InstalledPackagesScoper.php b/src/Scoper/Composer/InstalledPackagesScoper.php index 796a19d5..e0b7eaa3 100644 --- a/src/Scoper/Composer/InstalledPackagesScoper.php +++ b/src/Scoper/Composer/InstalledPackagesScoper.php @@ -44,14 +44,14 @@ public function __construct(Scoper $decoratedScoper) * * {@inheritdoc} */ - public function scope(string $filePath, string $prefix): string + public function scope(string $filePath, string $prefix, array $patchers): string { if (null === self::$filePattern) { throw new LogicException('Cannot be used without being initialised first.'); } if (1 !== preg_match(self::$filePattern, $filePath)) { - return $this->decoratedScoper->scope($filePath, $prefix); + return $this->decoratedScoper->scope($filePath, $prefix, $patchers); } $decodedJson = json_decode( diff --git a/src/Scoper/Composer/JsonFileScoper.php b/src/Scoper/Composer/JsonFileScoper.php index c3ba438d..40652603 100644 --- a/src/Scoper/Composer/JsonFileScoper.php +++ b/src/Scoper/Composer/JsonFileScoper.php @@ -30,10 +30,10 @@ public function __construct(Scoper $decoratedScoper) * * {@inheritdoc} */ - public function scope(string $filePath, string $prefix): string + public function scope(string $filePath, string $prefix, array $patchers): string { if (1 !== preg_match('/composer\.json$/', $filePath)) { - return $this->decoratedScoper->scope($filePath, $prefix); + return $this->decoratedScoper->scope($filePath, $prefix, $patchers); } $decodedJson = json_decode( diff --git a/src/Scoper/NullScoper.php b/src/Scoper/NullScoper.php index 6f8adf37..57efe186 100644 --- a/src/Scoper/NullScoper.php +++ b/src/Scoper/NullScoper.php @@ -21,7 +21,7 @@ final class NullScoper implements Scoper /** * @inheritdoc */ - public function scope(string $filePath, string $prefix): string + public function scope(string $filePath, string $prefix, array $patchers): string { return file_get_contents($filePath); } diff --git a/src/Scoper/PatchScoper.php b/src/Scoper/PatchScoper.php new file mode 100644 index 00000000..3d020699 --- /dev/null +++ b/src/Scoper/PatchScoper.php @@ -0,0 +1,43 @@ +, + * Pádraic Brady + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Humbug\PhpScoper\Scoper; + +use Humbug\PhpScoper\Scoper; + +final class PatchScoper implements Scoper +{ + private $decoratedScoper; + + public function __construct(Scoper $decoratedScoper) + { + $this->decoratedScoper = $decoratedScoper; + } + + /** + * @inheritdoc + */ + public function scope(string $filePath, string $prefix, array $patchers): string + { + $content = $this->decoratedScoper->scope($filePath, $prefix, $patchers); + + return array_reduce( + $patchers, + function (string $content, callable $patcher) use ($filePath, $prefix): string { + return $patcher($filePath, $prefix, $content); + }, + $content + ); + } +} diff --git a/src/Scoper/PhpScoper.php b/src/Scoper/PhpScoper.php index 4614cd12..8e83e957 100644 --- a/src/Scoper/PhpScoper.php +++ b/src/Scoper/PhpScoper.php @@ -53,10 +53,10 @@ public function __construct(Parser $parser, Scoper $decoratedScoper) * * @throws PhpParserError */ - public function scope(string $filePath, string $prefix): string + public function scope(string $filePath, string $prefix, array $patchers): string { if (false === $this->isPhpFile($filePath)) { - return $this->decoratedScoper->scope($filePath, $prefix); + return $this->decoratedScoper->scope($filePath, $prefix, $patchers); } $content = file_get_contents($filePath); diff --git a/src/functions.php b/src/functions.php index 4f9a087f..0d94e5a5 100644 --- a/src/functions.php +++ b/src/functions.php @@ -20,6 +20,7 @@ use Humbug\PhpScoper\Scoper\Composer\InstalledPackagesScoper; use Humbug\PhpScoper\Scoper\Composer\JsonFileScoper; use Humbug\PhpScoper\Scoper\NullScoper; +use Humbug\PhpScoper\Scoper\PatchScoper; use Humbug\PhpScoper\Scoper\PhpScoper; use PackageVersions\Versions; use PhpParser\Parser; @@ -63,11 +64,13 @@ function get_version(): string */ function create_scoper(): Scoper { - return new JsonFileScoper( - new InstalledPackagesScoper( - new PhpScoper( - create_parser(), - new NullScoper() + return new PatchScoper( + new JsonFileScoper( + new InstalledPackagesScoper( + new PhpScoper( + create_parser(), + new NullScoper() + ) ) ) ); diff --git a/tests/Console/Command/AddPrefixCommandTest.php b/tests/Console/Command/AddPrefixCommandTest.php index 7c96f986..6a8aed35 100644 --- a/tests/Console/Command/AddPrefixCommandTest.php +++ b/tests/Console/Command/AddPrefixCommandTest.php @@ -21,6 +21,7 @@ use PHPUnit\Framework\TestCase; use Prophecy\Argument; use Prophecy\Prophecy\ObjectProphecy; +use Symfony\Component\Console\Exception\RuntimeException; use Symfony\Component\Console\Exception\RuntimeException as SymfonyConsoleRuntimeException; use Symfony\Component\Console\Tester\ApplicationTester; use Symfony\Component\Filesystem\Filesystem; @@ -32,6 +33,8 @@ */ class AddPrefixCommandTest extends TestCase { + const FIXTURE_PATH = __DIR__.'/../../../fixtures'; + /** * @var ApplicationTester */ @@ -164,6 +167,7 @@ public function test_scope_the_given_paths() '--no-interaction', ]; + $this->fileSystemProphecy->isAbsolutePath('scoper.inc.php')->willReturn(false); $this->fileSystemProphecy->isAbsolutePath(Argument::cetera())->willReturn(true); $this->fileSystemProphecy->exists(Argument::cetera())->willReturn(false); @@ -176,6 +180,7 @@ public function test_scope_the_given_paths() escape_path('/path/to/file'), ], $this->tmp, + Argument::type('array'), Argument::type(ConsoleLogger::class) ) ->shouldBeCalled() @@ -185,7 +190,8 @@ public function test_scope_the_given_paths() $this->assertSame(0, $this->appTester->getStatusCode()); - $this->fileSystemProphecy->isAbsolutePath(Argument::cetera())->shouldHaveBeenCalledTimes(4); + $this->fileSystemProphecy->isAbsolutePath('scoper.inc.php')->shouldHaveBeenCalledTimes(1); + $this->fileSystemProphecy->isAbsolutePath(Argument::cetera())->shouldHaveBeenCalledTimes(5); $this->fileSystemProphecy->exists(Argument::cetera())->shouldHaveBeenCalledTimes(1); $this->handleProphecy->__invoke(Argument::cetera())->shouldHaveBeenCalledTimes(1); @@ -203,6 +209,7 @@ public function test_applies_a_random_prefix_when_none_given() '--output-dir' => $this->tmp, ]; + $this->fileSystemProphecy->isAbsolutePath('scoper.inc.php')->willReturn(false); $this->fileSystemProphecy->isAbsolutePath(Argument::cetera())->willReturn(true); $this->fileSystemProphecy->exists(Argument::cetera())->willReturn(false); @@ -224,6 +231,7 @@ function (string $prefix): bool { escape_path('/path/to/file'), ], $this->tmp, + Argument::type('array'), Argument::type(ConsoleLogger::class) ) ->shouldBeCalled() @@ -233,7 +241,8 @@ function (string $prefix): bool { $this->assertSame(0, $this->appTester->getStatusCode()); - $this->fileSystemProphecy->isAbsolutePath(Argument::cetera())->shouldHaveBeenCalledTimes(4); + $this->fileSystemProphecy->isAbsolutePath('scoper.inc.php')->shouldHaveBeenCalledTimes(1); + $this->fileSystemProphecy->isAbsolutePath(Argument::cetera())->shouldHaveBeenCalledTimes(5); $this->fileSystemProphecy->exists(Argument::cetera())->shouldHaveBeenCalledTimes(1); $this->handleProphecy->__invoke(Argument::cetera())->shouldHaveBeenCalledTimes(1); @@ -248,6 +257,7 @@ public function test_scope_the_current_working_directory_if_no_path_given() ]; $this->fileSystemProphecy->isAbsolutePath($this->tmp)->willReturn(true); + $this->fileSystemProphecy->isAbsolutePath('scoper.inc.php')->willReturn(false); $this->fileSystemProphecy->exists($this->tmp)->willReturn(false); $this->handleProphecy @@ -257,6 +267,7 @@ public function test_scope_the_current_working_directory_if_no_path_given() $this->cwd, ], $this->tmp, + Argument::type('array'), Argument::type(ConsoleLogger::class) ) ->shouldBeCalled() @@ -266,7 +277,8 @@ public function test_scope_the_current_working_directory_if_no_path_given() $this->assertSame(0, $this->appTester->getStatusCode()); - $this->fileSystemProphecy->isAbsolutePath(Argument::cetera())->shouldHaveBeenCalledTimes(1); + $this->fileSystemProphecy->isAbsolutePath('scoper.inc.php')->shouldHaveBeenCalledTimes(1); + $this->fileSystemProphecy->isAbsolutePath(Argument::cetera())->shouldHaveBeenCalledTimes(2); $this->fileSystemProphecy->exists(Argument::cetera())->shouldHaveBeenCalledTimes(1); $this->handleProphecy->__invoke(Argument::cetera())->shouldHaveBeenCalledTimes(1); @@ -289,6 +301,7 @@ public function test_relative_paths_are_relative_to_the_current_working_director $this->fileSystemProphecy->isAbsolutePath($path1)->willReturn(false); $this->fileSystemProphecy->isAbsolutePath($path2)->willReturn(false); $this->fileSystemProphecy->isAbsolutePath($this->tmp)->willReturn(true); + $this->fileSystemProphecy->isAbsolutePath('scoper.inc.php')->willReturn(false); $this->fileSystemProphecy->exists($this->tmp)->willReturn(false); $this->handleProphecy @@ -300,6 +313,7 @@ public function test_relative_paths_are_relative_to_the_current_working_director escape_path($this->cwd.'/relative-path/to/file'), ], $this->tmp, + Argument::type('array'), Argument::type(ConsoleLogger::class) ) ->shouldBeCalled() @@ -309,7 +323,7 @@ public function test_relative_paths_are_relative_to_the_current_working_director $this->assertSame(0, $this->appTester->getStatusCode()); - $this->fileSystemProphecy->isAbsolutePath(Argument::cetera())->shouldHaveBeenCalledTimes(4); + $this->fileSystemProphecy->isAbsolutePath(Argument::cetera())->shouldHaveBeenCalledTimes(5); $this->fileSystemProphecy->exists(Argument::cetera())->shouldHaveBeenCalledTimes(1); $this->handleProphecy->__invoke(Argument::cetera())->shouldHaveBeenCalledTimes(1); @@ -328,6 +342,7 @@ public function test_prefix_can_end_by_a_backslash() '--output-dir' => $this->tmp, ]; + $this->fileSystemProphecy->isAbsolutePath('scoper.inc.php')->willReturn(false); $this->fileSystemProphecy->isAbsolutePath(Argument::cetera())->willReturn(true); $this->fileSystemProphecy->exists($this->tmp)->willReturn(false); @@ -340,6 +355,7 @@ public function test_prefix_can_end_by_a_backslash() escape_path('/path/to/file'), ], $this->tmp, + Argument::type('array'), Argument::type(ConsoleLogger::class) ) ->shouldBeCalled() @@ -349,7 +365,8 @@ public function test_prefix_can_end_by_a_backslash() $this->assertSame(0, $this->appTester->getStatusCode()); - $this->fileSystemProphecy->isAbsolutePath(Argument::cetera())->shouldHaveBeenCalledTimes(4); + $this->fileSystemProphecy->isAbsolutePath('scoper.inc.php')->shouldHaveBeenCalledTimes(1); + $this->fileSystemProphecy->isAbsolutePath(Argument::cetera())->shouldHaveBeenCalledTimes(5); $this->fileSystemProphecy->exists(Argument::cetera())->shouldHaveBeenCalledTimes(1); $this->handleProphecy->__invoke(Argument::cetera())->shouldHaveBeenCalledTimes(1); @@ -368,6 +385,7 @@ public function test_prefix_can_end_by_multiple_backslashes() '--output-dir' => $this->tmp, ]; + $this->fileSystemProphecy->isAbsolutePath('scoper.inc.php')->willReturn(false); $this->fileSystemProphecy->isAbsolutePath(Argument::cetera())->willReturn(true); $this->fileSystemProphecy->exists($this->tmp)->willReturn(false); @@ -380,6 +398,7 @@ public function test_prefix_can_end_by_multiple_backslashes() escape_path('/path/to/file'), ], $this->tmp, + Argument::type('array'), Argument::type(ConsoleLogger::class) ) ->shouldBeCalled() @@ -389,7 +408,8 @@ public function test_prefix_can_end_by_multiple_backslashes() $this->assertSame(0, $this->appTester->getStatusCode()); - $this->fileSystemProphecy->isAbsolutePath(Argument::cetera())->shouldHaveBeenCalledTimes(4); + $this->fileSystemProphecy->isAbsolutePath('scoper.inc.php')->shouldHaveBeenCalledTimes(1); + $this->fileSystemProphecy->isAbsolutePath(Argument::cetera())->shouldHaveBeenCalledTimes(5); $this->fileSystemProphecy->exists(Argument::cetera())->shouldHaveBeenCalledTimes(1); $this->handleProphecy->__invoke(Argument::cetera())->shouldHaveBeenCalledTimes(1); @@ -406,6 +426,7 @@ public function test_an_output_directory_can_be_given() '--output-dir' => $outDir = $this->tmp.DIRECTORY_SEPARATOR.'output-dir', ]; + $this->fileSystemProphecy->isAbsolutePath('scoper.inc.php')->willReturn(false); $this->fileSystemProphecy->isAbsolutePath(Argument::cetera())->willReturn(true); $this->fileSystemProphecy->exists($outDir)->willReturn(false); @@ -416,6 +437,7 @@ public function test_an_output_directory_can_be_given() escape_path('/path/to/dir1'), ], $outDir, + Argument::type('array'), Argument::type(ConsoleLogger::class) ) ->shouldBeCalled() @@ -425,7 +447,8 @@ public function test_an_output_directory_can_be_given() $this->assertSame(0, $this->appTester->getStatusCode()); - $this->fileSystemProphecy->isAbsolutePath(Argument::cetera())->shouldHaveBeenCalledTimes(2); + $this->fileSystemProphecy->isAbsolutePath('scoper.inc.php')->shouldHaveBeenCalledTimes(1); + $this->fileSystemProphecy->isAbsolutePath(Argument::cetera())->shouldHaveBeenCalledTimes(3); $this->fileSystemProphecy->exists(Argument::cetera())->shouldHaveBeenCalledTimes(1); $this->handleProphecy->__invoke(Argument::cetera())->shouldHaveBeenCalledTimes(1); @@ -446,6 +469,7 @@ public function test_relative_output_directory_are_made_absolute() $expectedOutputDir = $this->tmp.DIRECTORY_SEPARATOR.'output-dir'; + $this->fileSystemProphecy->isAbsolutePath('scoper.inc.php')->willReturn(false); $this->fileSystemProphecy->isAbsolutePath('output-dir')->willReturn(false); $this->fileSystemProphecy->isAbsolutePath(Argument::cetera())->willReturn(true); $this->fileSystemProphecy->exists($expectedOutputDir)->willReturn(false); @@ -457,6 +481,7 @@ public function test_relative_output_directory_are_made_absolute() escape_path('/path/to/dir1'), ], $expectedOutputDir, + [], Argument::type(ConsoleLogger::class) ) ->shouldBeCalled() @@ -466,7 +491,7 @@ public function test_relative_output_directory_are_made_absolute() $this->assertSame(0, $this->appTester->getStatusCode()); - $this->fileSystemProphecy->isAbsolutePath(Argument::cetera())->shouldHaveBeenCalledTimes(2); + $this->fileSystemProphecy->isAbsolutePath(Argument::cetera())->shouldHaveBeenCalledTimes(3); $this->fileSystemProphecy->exists(Argument::cetera())->shouldHaveBeenCalledTimes(1); $this->handleProphecy->__invoke(Argument::cetera())->shouldHaveBeenCalledTimes(1); @@ -513,6 +538,7 @@ public function test_throws_an_error_when_scoping_fails() ], ]; + $this->fileSystemProphecy->isAbsolutePath('scoper.inc.php')->willReturn(false); $this->fileSystemProphecy->isAbsolutePath(Argument::cetera())->willReturn(true); $this->fileSystemProphecy->exists('build')->willReturn(false); @@ -534,6 +560,150 @@ public function test_throws_an_error_when_scoping_fails() $this->handleProphecy->__invoke(Argument::cetera())->shouldHaveBeenCalledTimes(1); } + public function test_throws_an_error_when_passing_a_non_existent_path_file() + { + $input = [ + 'add-prefix', + '--prefix' => 'MyPrefix', + '--patch-file' => 'unknown', + 'paths' => [ + escape_path('/path/to/dir1'), + ], + ]; + + $this->fileSystemProphecy->isAbsolutePath('unknown')->willReturn(false); + $this->fileSystemProphecy->isAbsolutePath(Argument::cetera())->willReturn(true); + $this->fileSystemProphecy->exists('build')->willReturn(false); + + $this->handleProphecy->__invoke(Argument::cetera())->shouldNotBeCalled(); + + try { + $this->appTester->run($input); + + $this->fail('Expected exception to be thrown.'); + } catch (RuntimeException $exception) { + $patchFile = escape_path($this->cwd.'/unknown'); + + $this->assertSame( + "Could not find the file \"$patchFile\".", + $exception->getMessage() + ); + $this->assertSame(0, $exception->getCode()); + $this->assertNull($exception->getPrevious()); + } + } + + public function test_attemps_to_use_patch_file_in_current_directory() + { + chdir(escape_path(self::FIXTURE_PATH.'/set006')); + + $input = [ + 'add-prefix', + '--prefix' => 'MyPrefix', + 'paths' => [ + escape_path('/path/to/dir1'), + ], + ]; + + $this->fileSystemProphecy->isAbsolutePath('unknown')->willReturn(false); + $this->fileSystemProphecy->isAbsolutePath(Argument::cetera())->willReturn(true); + $this->fileSystemProphecy->exists('build')->willReturn(false); + + $patchersFound = []; + $this->handleProphecy + ->__invoke( + Argument::any(), + Argument::any(), + Argument::any(), + Argument::that(function ($arg) use (&$patchersFound) { + $patchersFound = $arg; + + return true; + }), + Argument::any() + ) + ->shouldBeCalled(); + + $this->appTester->run($input); + + $this->assertCount(1, $patchersFound); + $this->assertEquals('Hello world!', $patchersFound[0]()); + + $this->handleProphecy->__invoke(Argument::cetera())->shouldHaveBeenCalledTimes(1); + } + + public function test_do_no_apply_any_patcher_if_default_patcher_file_not_found() + { + chdir(escape_path(self::FIXTURE_PATH.'/set007')); + + $input = [ + 'add-prefix', + '--prefix' => 'MyPrefix', + 'paths' => [ + escape_path('/path/to/dir1'), + ], + ]; + + $this->fileSystemProphecy->isAbsolutePath('unknown')->willReturn(false); + $this->fileSystemProphecy->isAbsolutePath(Argument::cetera())->willReturn(true); + $this->fileSystemProphecy->exists('build')->willReturn(false); + + $patchersFound = []; + $this->handleProphecy + ->__invoke( + Argument::any(), + Argument::any(), + Argument::any(), + Argument::that(function ($arg) use (&$patchersFound) { + $patchersFound = $arg; + + return true; + }), + Argument::any() + ) + ->shouldBeCalled(); + + $this->appTester->run($input); + + $this->assertCount(0, $patchersFound); + + $this->handleProphecy->__invoke(Argument::cetera())->shouldHaveBeenCalledTimes(1); + } + + public function test_throws_an_error_if_patch_file_returns_an_array_with_invalid_values() + { + chdir(escape_path(self::FIXTURE_PATH.'/set009')); + + $input = [ + 'add-prefix', + '--prefix' => 'MyPrefix', + 'paths' => [ + escape_path('/path/to/dir1'), + ], + ]; + + $this->fileSystemProphecy->isAbsolutePath('unknown')->willReturn(false); + $this->fileSystemProphecy->isAbsolutePath(Argument::cetera())->willReturn(true); + $this->fileSystemProphecy->exists('build')->willReturn(false); + + $this->handleProphecy->__invoke(Argument::cetera())->shouldNotBeCalled(); + + try { + $this->appTester->run($input); + + $this->fail('Expected exception to be thrown.'); + } catch (RuntimeException $exception) { + $patchFile = escape_path($this->cwd.'/unknown'); + + $this->assertSame( + 'Expected patchers to be an array of callables, the "0" element is not.', + $exception->getMessage() + ); + $this->assertSame(0, $exception->getCode()); + $this->assertNull($exception->getPrevious()); + } + } + public function provideEmptyPrefixes() { yield 'empty' => ['']; diff --git a/tests/Handler/HandleAddPrefixTest.php b/tests/Handler/HandleAddPrefixTest.php index fded272b..b65d7499 100644 --- a/tests/Handler/HandleAddPrefixTest.php +++ b/tests/Handler/HandleAddPrefixTest.php @@ -24,6 +24,7 @@ use Prophecy\Argument; use Prophecy\Prophecy\ObjectProphecy; use Throwable; +use function Humbug\PhpScoper\create_fake_patcher; use function Humbug\PhpScoper\escape_path; use function Humbug\PhpScoper\make_tmp_dir; use function Humbug\PhpScoper\remove_dir; @@ -108,6 +109,8 @@ function (string $relativePath) { $outputPath = $this->tmp; + $patchers = [create_fake_patcher()]; + /** @var ConsoleLogger $logger */ $logger = $this->loggerProphecy->reveal(); @@ -115,14 +118,14 @@ function (string $relativePath) { $filePath = realpath(escape_path(self::FIXTURE_PATH_000.$fileContent)); $this->assertNotFalse($filePath, 'Type check.'); - $this->scoperProphecy->scope($filePath, $prefix)->shouldBeCalled(); + $this->scoperProphecy->scope($filePath, $prefix, $patchers)->shouldBeCalled(); $this->loggerProphecy->outputSuccess($filePath)->shouldBeCalled(); } $this->loggerProphecy->outputFileCount(count($expected))->shouldBeCalled(); - $this->handle->__invoke($prefix, $paths, $outputPath, $logger); + $this->handle->__invoke($prefix, $paths, $outputPath, $patchers, $logger); $this->scoperProphecy->scope(Argument::cetera())->shouldHaveBeenCalledTimes(count($expected)); @@ -140,6 +143,8 @@ public function test_replaces_the_content_of_the_files_with_the_scoped_content() $outputPath = $this->tmp; + $patchers = [create_fake_patcher()]; + /** @var ConsoleLogger $logger */ $logger = $this->loggerProphecy->reveal(); @@ -152,9 +157,9 @@ public function test_replaces_the_content_of_the_files_with_the_scoped_content() PHP; - $this->scoperProphecy->scope(Argument::any(), $prefix)->willReturn($expected); + $this->scoperProphecy->scope(Argument::any(), $prefix, $patchers)->willReturn($expected); - $this->handle->__invoke($prefix, $paths, $outputPath, $logger); + $this->handle->__invoke($prefix, $paths, $outputPath, $patchers, $logger); $actual = file_get_contents( escape_path($this->tmp.'/file.php') @@ -173,13 +178,15 @@ public function test_cannot_collect_files_from_unknown_paths() $outputPath = $this->tmp.DIRECTORY_SEPARATOR.'output-dir'; + $patchers = [create_fake_patcher()]; + /** @var ConsoleLogger $logger */ $logger = $this->loggerProphecy->reveal(); $this->scoperProphecy->scope(Argument::cetera())->shouldNotBeCalled(); try { - $this->handle->__invoke($prefix, $paths, $outputPath, $logger); + $this->handle->__invoke($prefix, $paths, $outputPath, $patchers, $logger); $this->fail('Expected exception to be thrown.'); } catch (RuntimeException $exception) { @@ -204,16 +211,18 @@ public function test_removes_scoped_files_on_failure() $outputPath = $this->tmp.DIRECTORY_SEPARATOR.'output-dir'; + $patchers = [create_fake_patcher()]; + /** @var ConsoleLogger $logger */ $logger = $this->loggerProphecy->reveal(); $this->scoperProphecy - ->scope(Argument::any(), $prefix) + ->scope(Argument::any(), $prefix, $patchers) ->willThrow($error = new Error('Unknown error')) ; try { - $this->handle->__invoke($prefix, $paths, $outputPath, $logger); + $this->handle->__invoke($prefix, $paths, $outputPath, $patchers, $logger); $this->fail('Expected exception to be thrown.'); } catch (Throwable $throwable) { @@ -233,16 +242,18 @@ public function test_throws_an_error_when_cannot_parse_a_file() $outputPath = $this->tmp.DIRECTORY_SEPARATOR.'output-dir'; + $patchers = [create_fake_patcher()]; + /** @var ConsoleLogger $logger */ $logger = $this->loggerProphecy->reveal(); $this->scoperProphecy - ->scope(Argument::any(), $prefix) + ->scope(Argument::any(), $prefix, $patchers) ->willThrow($error = new PhpParserError('Could not parse file')) ; try { - $this->handle->__invoke($prefix, $paths, $outputPath, $logger); + $this->handle->__invoke($prefix, $paths, $outputPath, $patchers, $logger); $this->fail('Expected exception to be thrown.'); } catch (ParsingException $exception) { diff --git a/tests/Scoper/Composer/InstalledPackagesScoperTest.php b/tests/Scoper/Composer/InstalledPackagesScoperTest.php index 633f82c3..f08d7dfd 100644 --- a/tests/Scoper/Composer/InstalledPackagesScoperTest.php +++ b/tests/Scoper/Composer/InstalledPackagesScoperTest.php @@ -19,6 +19,7 @@ use PHPUnit\Framework\TestCase; use Prophecy\Argument; use Prophecy\Prophecy\ObjectProphecy; +use function Humbug\PhpScoper\create_fake_patcher; use function Humbug\PhpScoper\escape_path; use function Humbug\PhpScoper\make_tmp_dir; use function Humbug\PhpScoper\remove_dir; @@ -70,6 +71,7 @@ public function test_delegates_scoping_to_the_decorated_scoper_if_is_not_a_insta { $filePath = escape_path($this->tmp.'/file.php'); $prefix = 'Humbug'; + $patchers = [create_fake_patcher()]; touch($filePath); file_put_contents($filePath, ''); @@ -77,7 +79,7 @@ public function test_delegates_scoping_to_the_decorated_scoper_if_is_not_a_insta /** @var Scoper|ObjectProphecy $decoratedScoperProphecy */ $decoratedScoperProphecy = $this->prophesize(Scoper::class); $decoratedScoperProphecy - ->scope($filePath, $prefix) + ->scope($filePath, $prefix, $patchers) ->willReturn( $expected = 'Scoped content' ) @@ -87,7 +89,7 @@ public function test_delegates_scoping_to_the_decorated_scoper_if_is_not_a_insta $scoper = new InstalledPackagesScoper($decoratedScoper); - $actual = $scoper->scope($filePath, $prefix); + $actual = $scoper->scope($filePath, $prefix, $patchers); $this->assertSame($expected, $actual); @@ -105,7 +107,10 @@ public function test_it_prefixes_the_composer_autoloaders(string $fileContent, s $scoper = new InstalledPackagesScoper(new FakeScoper()); - $actual = $scoper->scope($filePath, 'Foo'); + $prefix = 'Foo'; + $patchers = [create_fake_patcher()]; + + $actual = $scoper->scope($filePath, $prefix, $patchers); $this->assertSame($expected, $actual); } diff --git a/tests/Scoper/Composer/JsonFileScoperTest.php b/tests/Scoper/Composer/JsonFileScoperTest.php index c71cf5b9..6a26108f 100644 --- a/tests/Scoper/Composer/JsonFileScoperTest.php +++ b/tests/Scoper/Composer/JsonFileScoperTest.php @@ -19,6 +19,7 @@ use PHPUnit\Framework\TestCase; use Prophecy\Argument; use Prophecy\Prophecy\ObjectProphecy; +use function Humbug\PhpScoper\create_fake_patcher; use function Humbug\PhpScoper\escape_path; use function Humbug\PhpScoper\make_tmp_dir; use function Humbug\PhpScoper\remove_dir; @@ -70,6 +71,7 @@ public function test_delegates_scoping_to_the_decorated_scoper_if_is_not_a_compo { $filePath = escape_path($this->tmp.'/file.php'); $prefix = 'Humbug'; + $patchers = [create_fake_patcher()]; touch($filePath); file_put_contents($filePath, ''); @@ -77,7 +79,7 @@ public function test_delegates_scoping_to_the_decorated_scoper_if_is_not_a_compo /** @var Scoper|ObjectProphecy $decoratedScoperProphecy */ $decoratedScoperProphecy = $this->prophesize(Scoper::class); $decoratedScoperProphecy - ->scope($filePath, $prefix) + ->scope($filePath, $prefix, $patchers) ->willReturn( $expected = 'Scoped content' ) @@ -87,7 +89,7 @@ public function test_delegates_scoping_to_the_decorated_scoper_if_is_not_a_compo $scoper = new JsonFileScoper($decoratedScoper); - $actual = $scoper->scope($filePath, $prefix); + $actual = $scoper->scope($filePath, $prefix, $patchers); $this->assertSame($expected, $actual); @@ -104,7 +106,10 @@ public function test_it_prefixes_the_composer_autoloaders(string $fileContent, s $scoper = new JsonFileScoper(new FakeScoper()); - $actual = $scoper->scope($filePath, 'Foo'); + $prefix = 'Foo'; + $patchers = [create_fake_patcher()]; + + $actual = $scoper->scope($filePath, $prefix, $patchers); $this->assertSame($expected, $actual); } diff --git a/tests/Scoper/FakeScoper.php b/tests/Scoper/FakeScoper.php index 3c4e7838..4599f074 100644 --- a/tests/Scoper/FakeScoper.php +++ b/tests/Scoper/FakeScoper.php @@ -21,7 +21,7 @@ final class FakeScoper implements Scoper /** * @inheritdoc */ - public function scope(string $filePath, string $prefix): string + public function scope(string $filePath, string $prefix, array $patchers): string { throw new \LogicException(); } diff --git a/tests/Scoper/NullScoperTest.php b/tests/Scoper/NullScoperTest.php index 9f8faf53..6f6a64f8 100644 --- a/tests/Scoper/NullScoperTest.php +++ b/tests/Scoper/NullScoperTest.php @@ -16,6 +16,7 @@ use Humbug\PhpScoper\Scoper; use PHPUnit\Framework\TestCase; +use function Humbug\PhpScoper\create_fake_patcher; use function Humbug\PhpScoper\escape_path; use function Humbug\PhpScoper\make_tmp_dir; use function Humbug\PhpScoper\remove_dir; @@ -73,9 +74,11 @@ public function test_returns_the_file_content_unchanged() $prefix = 'Humbug'; + $patchers = [create_fake_patcher()]; + $scoper = new NullScoper(); - $actual = $scoper->scope($filePath, $prefix); + $actual = $scoper->scope($filePath, $prefix, $patchers); $this->assertSame($expected, $actual); } diff --git a/tests/Scoper/PhpScoperTest.php b/tests/Scoper/PhpScoperTest.php index acf75e4d..99a594b4 100644 --- a/tests/Scoper/PhpScoperTest.php +++ b/tests/Scoper/PhpScoperTest.php @@ -20,6 +20,7 @@ use PHPUnit\Framework\TestCase; use Prophecy\Argument; use Prophecy\Prophecy\ObjectProphecy; +use function Humbug\PhpScoper\create_fake_patcher; use function Humbug\PhpScoper\create_parser; use function Humbug\PhpScoper\escape_path; use function Humbug\PhpScoper\make_tmp_dir; @@ -94,8 +95,8 @@ public function test_is_a_Scoper() public function test_can_scope_a_PHP_file() { $prefix = 'Humbug'; - $filePath = escape_path($this->tmp.'/file.php'); + $patchers = [create_fake_patcher()]; $content = <<<'PHP' echo "Humbug!"; @@ -109,7 +110,7 @@ public function test_can_scope_a_PHP_file() PHP; - $actual = $this->scoper->scope($filePath, $prefix); + $actual = $this->scoper->scope($filePath, $prefix, $patchers); $this->assertSame($expected, $actual); } @@ -118,9 +119,10 @@ public function test_does_not_scope_file_if_is_not_a_PHP_file() { $filePath = 'file.yaml'; $prefix = 'Humbug'; + $patchers = [create_fake_patcher()]; $this->decoratedScoperProphecy - ->scope($filePath, $prefix) + ->scope($filePath, $prefix, $patchers) ->willReturn( $expected = 'Scoped content' ) @@ -131,7 +133,7 @@ public function test_does_not_scope_file_if_is_not_a_PHP_file() $this->decoratedScoper ); - $actual = $scoper->scope($filePath, $prefix); + $actual = $scoper->scope($filePath, $prefix, $patchers); $this->assertSame($expected, $actual); @@ -141,8 +143,8 @@ public function test_does_not_scope_file_if_is_not_a_PHP_file() public function test_can_scope_PHP_binary_files() { $prefix = 'Humbug'; - $filePath = escape_path($this->tmp.'/hello'); + $patchers = [create_fake_patcher()]; $content = <<<'PHP' #!/usr/bin/env php @@ -161,7 +163,7 @@ public function test_can_scope_PHP_binary_files() PHP; - $actual = $this->scoper->scope($filePath, $prefix); + $actual = $this->scoper->scope($filePath, $prefix, $patchers); $this->assertSame($expected, $actual); } @@ -172,6 +174,8 @@ public function test_does_not_scope_a_non_PHP_binary_files() $filePath = escape_path($this->tmp.'/hello'); + $patchers = [create_fake_patcher()]; + $content = <<<'PHP' #!/usr/bin/env bash decoratedScoperProphecy - ->scope($filePath, $prefix) + ->scope($filePath, $prefix, $patchers) ->willReturn( $expected = 'Scoped content' ) @@ -194,7 +198,7 @@ public function test_does_not_scope_a_non_PHP_binary_files() $this->decoratedScoper ); - $actual = $scoper->scope($filePath, $prefix); + $actual = $scoper->scope($filePath, $prefix, $patchers); $this->assertSame($expected, $actual); @@ -215,9 +219,10 @@ public function test_cannot_scope_an_invalid_PHP_file() file_put_contents($filePath, $content); $prefix = 'Humbug'; + $patchers = [create_fake_patcher()]; try { - $this->scoper->scope($filePath, $prefix); + $this->scoper->scope($filePath, $prefix, $patchers); $this->fail('Expected exception to have been thrown.'); } catch (PhpParserError $error) { @@ -240,7 +245,9 @@ public function test_can_scope_valid_files(string $content, string $prefix, stri touch($filePath); file_put_contents($filePath, $content); - $actual = $this->scoper->scope($filePath, $prefix); + $patchers = [create_fake_patcher()]; + + $actual = $this->scoper->scope($filePath, $prefix, $patchers); $this->assertSame($expected, $actual); } diff --git a/tests/functions.php b/tests/functions.php index 488b4eea..9b52a4cb 100644 --- a/tests/functions.php +++ b/tests/functions.php @@ -14,6 +14,8 @@ namespace Humbug\PhpScoper; +use Closure; +use LogicException; use Symfony\Component\Filesystem\Filesystem; /** @@ -63,3 +65,10 @@ function remove_dir(string $path) (new Filesystem())->remove($path); } } + +function create_fake_patcher(): Closure +{ + return function () { + throw new LogicException('Did not expect to be called'); + }; +}