diff --git a/psalm.xml b/psalm.xml index b173a0333..37204efe1 100644 --- a/psalm.xml +++ b/psalm.xml @@ -32,5 +32,10 @@ + + + + + diff --git a/src/Collection/FilesCollection.php b/src/Collection/FilesCollection.php index 20aa29d45..f9c9d9905 100644 --- a/src/Collection/FilesCollection.php +++ b/src/Collection/FilesCollection.php @@ -7,26 +7,17 @@ use Closure; use Doctrine\Common\Collections\ArrayCollection; use GrumPHP\Util\Regex; -use Symfony\Component\Finder\Comparator; -use Symfony\Component\Finder\Iterator; use SplFileInfo; -use Symfony\Component\Finder\SplFileInfo as SymfonySplFileInfo; use Traversable; /** - * @extends ArrayCollection + * @extends ArrayCollection */ class FilesCollection extends ArrayCollection { /** - * @param \Traversable $iterator - */ - public static function fromTraversable(\Traversable $iterator): FilesCollection - { - return new self(array_values(iterator_to_array($iterator))); - } - - /** + * @deprecated use FilesCollectionFilter::name directly + * * Adds a rule that files must match. * * You can use a pattern (delimited with / sign), a glob or a simple string. @@ -39,10 +30,12 @@ public static function fromTraversable(\Traversable $iterator): FilesCollection */ public function name($pattern): self { - return $this->names([$pattern]); + return FilesCollectionFilter::name($this, $pattern); } /** + * @deprecated use FilesCollectionFilter::names directly + * * Adds rules that files must match. * * You can use patterns (delimited with / sign), globs or simple strings. @@ -53,12 +46,12 @@ public function name($pattern): self */ public function names(array $patterns): self { - $filter = new Iterator\FilenameFilterIterator($this->getIterator(), $patterns, []); - - return self::fromTraversable($filter); + return FilesCollectionFilter::names($this, $patterns); } /** + * @deprecated use FilesCollectionFilter::notName directly + * * Adds rules that files must match. * * You can use patterns (delimited with / sign), globs or simple strings. @@ -69,36 +62,36 @@ public function names(array $patterns): self */ public function notName(string $pattern): self { - $filter = new Iterator\FilenameFilterIterator($this->getIterator(), [], [$pattern]); - - return self::fromTraversable($filter); + return FilesCollectionFilter::notName($this, $pattern); } /** + * @deprecated use FilesCollectionFilter::path directly + * * Filter by path. * * $collection->path('/^spec\/') */ public function path(string $pattern): self { - return $this->paths([$pattern]); + return FilesCollectionFilter::path($this, $pattern); } /** + * @deprecated use FilesCollectionFilter::paths directly + * * Filter by paths. * * $collection->paths(['/^spec\/','/^src\/']) - * - * @psalm-suppress ArgumentTypeCoercion - Works on int, \SplFileInfo as well */ public function paths(array $patterns): self { - $filter = new Iterator\PathFilterIterator($this->getIterator(), $patterns, []); - - return self::fromTraversable($filter); + return FilesCollectionFilter::paths($this, $patterns); } /** + * @deprecated use FilesCollectionFilter::notPath directly + * * Adds rules that filenames must not match. * * You can use patterns (delimited with / sign) or simple strings. @@ -107,54 +100,50 @@ public function paths(array $patterns): self */ public function notPath(string $pattern): self { - return $this->notPaths([$pattern]); + return FilesCollectionFilter::notPath($this, $pattern); } /** + * @deprecated use FilesCollectionFilter::notPaths directly + * * Adds rules that filenames must not match. * * You can use patterns (delimited with / sign) or simple strings. * * $collection->notPaths(['/^spec\/','/^src\/']) - * - * @psalm-suppress ArgumentTypeCoercion - Works on int, \SplFileInfo as well */ public function notPaths(array $pattern): self { - $filter = new Iterator\PathFilterIterator($this->getIterator(), [], $pattern); - - return self::fromTraversable($filter); + return FilesCollectionFilter::notPaths($this, $pattern); } + /** + * @deprecated use FilesCollectionFilter::extensions directly + */ public function extensions(array $extensions): self { - if (!\count($extensions)) { - return new self(); - } - - return $this->name(sprintf('/\.(%s)$/i', implode('|', $extensions))); + return FilesCollectionFilter::extensions($this, $extensions); } /** + * @deprecated use FilesCollectionFilter::size directly + * * Adds tests for file sizes. * * $collection->filterBySize('> 10K'); * $collection->filterBySize('<= 1Ki'); * $collection->filterBySize(4); * - * - * * @see NumberComparator */ public function size(string $size): self { - $comparator = new Comparator\NumberComparator($size); - $filter = new Iterator\SizeRangeFilterIterator($this->getIterator(), [$comparator]); - - return self::fromTraversable($filter); + return FilesCollectionFilter::size($this, $size); } /** + * @deprecated use FilesCollectionFilter::date directly + * * Adds tests for file dates (last modified). * * The date must be something that strtotime() is able to parse: @@ -164,26 +153,21 @@ public function size(string $size): self * $collection->filterByDate('> now - 2 hours'); * $collection->filterByDate('>= 2005-10-15'); * - * - * * @see DateComparator */ public function date(string $date): self { - $comparator = new Comparator\DateComparator($date); - $filter = new Iterator\DateRangeFilterIterator($this->getIterator(), [$comparator]); - - return self::fromTraversable($filter); + return FilesCollectionFilter::date($this, $date); } /** + * @deprecated use FilesCollectionFilter::filter directly + * * Filters the iterator with an anonymous function. * * The anonymous function receives a \SplFileInfo and must return false * to remove files. * - * - * * @see CustomFilterIterator * * @psalm-suppress LessSpecificImplementedReturnType @@ -192,26 +176,20 @@ public function date(string $date): self */ public function filter(Closure $p): self { - $filter = new Iterator\CustomFilterIterator($this->getIterator(), [$p]); - - return self::fromTraversable($filter); + return FilesCollectionFilter::filter($this, $p); } /** + * @deprecated use FilesCollectionFilter::filterByFileList directly + * * @param Traversable $fileList */ - public function filterByFileList(Traversable $fileList): FilesCollection + public function filterByFileList(Traversable $fileList): self { - $allowedFiles = array_map(function (SplFileInfo $file) { - return $file->getPathname(); - }, iterator_to_array($fileList)); - - return $this->filter(function (SplFileInfo $file) use ($allowedFiles) { - return \in_array($file->getPathname(), $allowedFiles, true); - }); + return FilesCollectionFilter::filterByFileList($this, $fileList); } - public function ensureFiles(self $files): FilesCollection + public function ensureFiles(self $files): self { $newFiles = new self($this->toArray()); @@ -224,49 +202,27 @@ public function ensureFiles(self $files): FilesCollection return $newFiles; } - public function ignoreSymlinks(): FilesCollection + /** + * @deprecated use FilesCollectionFilter::ignoreSymlinks directly + */ + public function ignoreSymlinks(): self { - return $this->filter(function (SplFileInfo $file) { - return !$file->isLink(); - }); + return FilesCollectionFilter::ignoreSymlinks($this); } - /** - * SplFileInfo cannot be serialized. Therefor, we help PHP a bit. - * This stuff is used for running tasks in parallel. - */ public function __serialize(): array { - return $this->map(function (SplFileInfo $fileInfo): string { - return $fileInfo instanceof SymfonySplFileInfo - ? $fileInfo->getRelativePathname() - : $fileInfo->getPathname(); - })->toArray(); + return $this->toArray(); } - /** - * SplFileInfo cannot be serialized. Therefor, we help PHP a bit. - * This stuff is used for running tasks in parallel. - */ public function __unserialize(array $data): void { - $files = $data; $this->clear(); - foreach ($files as $file) { - $this->add(new SymfonySplFileInfo($file, dirname($file), $file)); + foreach ($data as $path) { + $this->add($path); } } - /** - * Help Psalm out a bit: - * - * @return \ArrayIterator - */ - public function getIterator(): \ArrayIterator - { - return new \ArrayIterator($this->toArray()); - } - public function toFileList(): string { return \implode(PHP_EOL, $this->toArray()); diff --git a/src/Collection/FilesCollectionFilter.php b/src/Collection/FilesCollectionFilter.php new file mode 100644 index 000000000..1483c7478 --- /dev/null +++ b/src/Collection/FilesCollectionFilter.php @@ -0,0 +1,221 @@ + 10K'); + * FilesCollectionFilter::size($collection, '<= 1Ki'); + * FilesCollectionFilter::size($collection, '4'); + * + * @see NumberComparator + */ + public static function size(FilesCollection $files, string $size): FilesCollection + { + $comparator = new Comparator\NumberComparator($size); + $filter = new Iterator\SizeRangeFilterIterator(self::toFileInfoIterator($files), [$comparator]); + + return self::toFilesCollection($filter); + } + + /** + * Adds tests for file dates (last modified). + * + * The date must be something that strtotime() is able to parse: + * + * FilesCollectionFilter::date($collection, 'since yesterday'); + * FilesCollectionFilter::date($collection, 'until 2 days ago'); + * FilesCollectionFilter::date($collection, '> now - 2 hours'); + * FilesCollectionFilter::date($collection, '>= 2005-10-15'); + * + * @see DateComparator + */ + public static function date(FilesCollection $files, string $date): FilesCollection + { + $comparator = new Comparator\DateComparator($date); + $filter = new Iterator\DateRangeFilterIterator(self::toFileInfoIterator($files), [$comparator]); + + return self::toFilesCollection($filter); + } + + /** + * Filters the iterator with an anonymous function. + * + * @see CustomFilterIterator + */ + public static function filter(FilesCollection $files, Closure $p): FilesCollection + { + $filter = new Iterator\CustomFilterIterator(self::toFileInfoIterator($files), [$p]); + + return self::toFilesCollection($filter); + } + + public static function ignoreSymlinks(FilesCollection $files): FilesCollection + { + return self::filter($files, static function (\SplFileInfo $file): bool { + return !$file->isLink(); + }); + } + + /** + * Keeps only files whose pathname is present in the given file list. + * + * @param Traversable $fileList + */ + public static function filterByFileList(FilesCollection $files, Traversable $fileList): FilesCollection + { + $allowedFiles = []; + foreach ($fileList as $file) { + $allowedFiles[$file->getPathname()] = true; + } + + return self::filter($files, static function (\SplFileInfo $file) use ($allowedFiles): bool { + return isset($allowedFiles[$file->getPathname()]); + }); + } + + /** + * @return \Generator + */ + private static function toFileInfoIterator(FilesCollection $files): \Generator + { + foreach ($files as $path) { + yield $path => new SymfonySplFileInfo($path, \dirname($path), $path); + } + } + + /** + * @param Traversable $iterator + */ + private static function toFilesCollection(Traversable $iterator): FilesCollection + { + $paths = []; + foreach ($iterator as $file) { + $paths[] = $file->getPathname(); + } + + return new FilesCollection($paths); + } +} diff --git a/src/Collection/ProcessArgumentsCollection.php b/src/Collection/ProcessArgumentsCollection.php index 3a0f19db5..ad66e35ce 100644 --- a/src/Collection/ProcessArgumentsCollection.php +++ b/src/Collection/ProcessArgumentsCollection.php @@ -93,35 +93,18 @@ public function addRequiredArgument(string $argument, string $value): void public function addFiles(FilesCollection $files): void { foreach ($files as $file) { - $this->addFile($file); + $this->add($file); } } - public function addFile(\SplFileInfo $file): void - { - $this->add($file->getPathname()); - } - public function addCommaSeparatedFiles(FilesCollection $files): void { - $paths = []; - - foreach ($files as $file) { - $paths[] = $file->getPathname(); - } - - $this->add(implode(',', $paths)); + $this->add(implode(',', $files->toArray())); } public function addArgumentWithCommaSeparatedFiles(string $argument, FilesCollection $files): void { - $paths = []; - - foreach ($files as $file) { - $paths[] = $file->getPathname(); - } - - $this->add(sprintf($argument, implode(',', $paths))); + $this->add(sprintf($argument, implode(',', $files->toArray()))); } public function addOptionalBooleanArgument( diff --git a/src/Console/Command/Git/CommitMsgCommand.php b/src/Console/Command/Git/CommitMsgCommand.php index 1f907fa82..77ac86745 100644 --- a/src/Console/Command/Git/CommitMsgCommand.php +++ b/src/Console/Command/Git/CommitMsgCommand.php @@ -119,6 +119,16 @@ public function execute(InputInterface $input, OutputInterface $output): int $results = $this->taskRunner->run($context); + if ($this->io->isVerbose()) { + $this->io->write([ + PHP_EOL, + sprintf( + 'Peak memory usage: %.1f MB', + (float) memory_get_peak_usage(true) / 1024.0 / 1024.0 + ) + ]); + } + return $results->isFailed() ? self::EXIT_CODE_NOK : self::EXIT_CODE_OK; } diff --git a/src/Console/Command/Git/PreCommitCommand.php b/src/Console/Command/Git/PreCommitCommand.php index 2e00bba15..e0e5143e3 100644 --- a/src/Console/Command/Git/PreCommitCommand.php +++ b/src/Console/Command/Git/PreCommitCommand.php @@ -90,6 +90,16 @@ public function execute(InputInterface $input, OutputInterface $output): int $results = $this->taskRunner->run($context); + if ($this->io->isVerbose()) { + $this->io->write([ + PHP_EOL, + sprintf( + 'Peak memory usage: %.1f MB', + (float) memory_get_peak_usage(true) / 1024.0 / 1024.0 + ) + ]); + } + return $results->isFailed() ? self::EXIT_CODE_NOK : self::EXIT_CODE_OK; } diff --git a/src/Console/Command/RunCommand.php b/src/Console/Command/RunCommand.php index e65ade451..03118d012 100644 --- a/src/Console/Command/RunCommand.php +++ b/src/Console/Command/RunCommand.php @@ -102,6 +102,16 @@ public function execute(InputInterface $input, OutputInterface $output): int $results = $this->taskRunner->run($context); + if ($this->io->isVerbose()) { + $this->io->write([ + PHP_EOL, + sprintf( + 'Peak memory usage: %.1f MB', + (float) memory_get_peak_usage(true) / 1024.0 / 1024.0 + ) + ]); + } + return $results->isFailed() ? self::EXIT_CODE_NOK : self::EXIT_CODE_OK; } diff --git a/src/Linter/Json/JsonLintError.php b/src/Linter/Json/JsonLintError.php index c5f837829..f6bd3dd3e 100644 --- a/src/Linter/Json/JsonLintError.php +++ b/src/Linter/Json/JsonLintError.php @@ -10,9 +10,9 @@ class JsonLintError extends LintError { - public static function fromParsingException(SplFileInfo $file, ParsingException $exception): self + public static function fromParsingException(string $file, ParsingException $exception): self { - return new self(LintError::TYPE_ERROR, $exception->getMessage(), $file->getPathname(), 0); + return new self(LintError::TYPE_ERROR, $exception->getMessage(), $file, 0); } public function __toString(): string diff --git a/src/Linter/Json/JsonLinter.php b/src/Linter/Json/JsonLinter.php index 05da8106e..2bc969665 100644 --- a/src/Linter/Json/JsonLinter.php +++ b/src/Linter/Json/JsonLinter.php @@ -37,13 +37,13 @@ public function __construct(Filesystem $filesystem, JsonParser $jsonParser) /** * @throws ParsingException */ - public function lint(SplFileInfo $file): LintErrorsCollection + public function lint(string $file): LintErrorsCollection { $errors = new LintErrorsCollection(); $flags = $this->calculateFlags(); try { - $json = $this->filesystem->readFromFileInfo($file); + $json = $this->filesystem->readPath($file); $this->jsonParser->parse($json, $flags); } catch (ParsingException $exception) { $errors->add(JsonLintError::fromParsingException($file, $exception)); diff --git a/src/Linter/LinterInterface.php b/src/Linter/LinterInterface.php index 7767548a7..1d9b43608 100644 --- a/src/Linter/LinterInterface.php +++ b/src/Linter/LinterInterface.php @@ -9,7 +9,7 @@ interface LinterInterface { - public function lint(SplFileInfo $file): LintErrorsCollection; + public function lint(string $file): LintErrorsCollection; public function isInstalled(): bool; } diff --git a/src/Linter/Xml/XmlLinter.php b/src/Linter/Xml/XmlLinter.php index 905bf4234..24b85aa56 100644 --- a/src/Linter/Xml/XmlLinter.php +++ b/src/Linter/Xml/XmlLinter.php @@ -33,7 +33,7 @@ class XmlLinter implements LinterInterface */ private $schemeValidation = false; - public function lint(SplFileInfo $file): LintErrorsCollection + public function lint(string $file): LintErrorsCollection { $errors = new LintErrorsCollection(); $useInternalErrors = $this->useInternalXmlLoggin(true); @@ -96,7 +96,7 @@ private function useInternalXmlLoggin(bool $useInternalErrors = false): bool return libxml_use_internal_errors($useInternalErrors); } - private function loadDocument(SplFileInfo $file): ?DOMDocument + private function loadDocument(string $file): ?DOMDocument { $this->registerXmlStreamContext(); @@ -104,7 +104,7 @@ private function loadDocument(SplFileInfo $file): ?DOMDocument $document->resolveExternals = $this->loadFromNet; $document->preserveWhiteSpace = false; $document->formatOutput = false; - $loaded = $document->load($file->getPathname()); + $loaded = $document->load($file); return $loaded ? $document : null; } @@ -156,7 +156,7 @@ private function validateDTD(DOMDocument $document): bool return $document->validate(); } - private function validateInternalSchemes(SplFileInfo $file, DOMDocument $document): bool + private function validateInternalSchemes(string $file, DOMDocument $document): bool { $schemas = []; $attributes = $document->documentElement->attributes; @@ -187,13 +187,13 @@ private function validateInternalSchemes(SplFileInfo $file, DOMDocument $documen /** * @return null|string */ - private function locateScheme(SplFileInfo $xmlFile, string $scheme) + private function locateScheme(string $xmlFile, string $scheme) { if (filter_var($scheme, FILTER_VALIDATE_URL)) { return $this->loadFromNet ? $scheme : null; } - $xmlFilePath = $xmlFile->getPath(); + $xmlFilePath = \dirname($xmlFile); $schemePath = empty($xmlFilePath) ? $scheme : rtrim($xmlFilePath, '/').DIRECTORY_SEPARATOR.$scheme; $schemeFile = new SplFileInfo($schemePath); diff --git a/src/Linter/Yaml/YamlLinter.php b/src/Linter/Yaml/YamlLinter.php index 22bde7087..8c89e2526 100644 --- a/src/Linter/Yaml/YamlLinter.php +++ b/src/Linter/Yaml/YamlLinter.php @@ -56,15 +56,15 @@ public function __construct(Filesystem $filesystem) $this->filesystem = $filesystem; } - public function lint(SplFileInfo $file): LintErrorsCollection + public function lint(string $file): LintErrorsCollection { $errors = new LintErrorsCollection(); try { - $content = $this->filesystem->readFromFileInfo($file); + $content = $this->filesystem->readPath($file); $this->parseYaml($content); } catch (ParseException $exception) { - $exception->setParsedFile($file->getPathname()); + $exception->setParsedFile($file); $errors[] = YamlLintError::fromParseException($exception); } diff --git a/src/Locator/ChangedFiles.php b/src/Locator/ChangedFiles.php index 6f4294078..e539d53fd 100644 --- a/src/Locator/ChangedFiles.php +++ b/src/Locator/ChangedFiles.php @@ -63,7 +63,7 @@ private function parseFilesFromDiff(Diff $diff): FilesCollection continue; } - $files[] = $fileObject; + $files[] = $fileObject->getPathname(); } return new FilesCollection($files); diff --git a/src/Locator/ListedFiles.php b/src/Locator/ListedFiles.php index 760d6e173..9e4a3e8c1 100644 --- a/src/Locator/ListedFiles.php +++ b/src/Locator/ListedFiles.php @@ -26,8 +26,7 @@ public function locate(string $fileList): FilesCollection $files = []; foreach (array_filter($filePaths) as $file) { - $relativeFile = $this->paths->makePathRelativeToProjectDir($file); - $files[] = new SplFileInfo($relativeFile, dirname($relativeFile), $relativeFile); + $files[] = $this->paths->makePathRelativeToProjectDir($file); } return new FilesCollection($files); diff --git a/src/Parser/ParserInterface.php b/src/Parser/ParserInterface.php index 9648f0d5c..5d52e1039 100644 --- a/src/Parser/ParserInterface.php +++ b/src/Parser/ParserInterface.php @@ -9,7 +9,7 @@ interface ParserInterface { - public function parse(SplFileInfo $file): ParseErrorsCollection; + public function parse(string $file): ParseErrorsCollection; public function isInstalled(): bool; } diff --git a/src/Parser/Php/PhpParser.php b/src/Parser/Php/PhpParser.php index edd299371..9a6dae03b 100644 --- a/src/Parser/Php/PhpParser.php +++ b/src/Parser/Php/PhpParser.php @@ -54,19 +54,20 @@ public function setParserOptions(array $options): void $this->parserOptions = $options; } - public function parse(SplFileInfo $file): ParseErrorsCollection + public function parse(string $file): ParseErrorsCollection { + $fileObject = new SplFileInfo($file); $errors = new ParseErrorsCollection(); - $context = new ParserContext($file, $errors); + $context = new ParserContext($fileObject, $errors); $parser = $this->parserFactory->createFromOptions($this->parserOptions); $traverser = $this->traverserFactory->createForTaskContext($this->parserOptions, $context); try { - $code = $this->filesystem->readFromFileInfo($file); + $code = $this->filesystem->readFromFileInfo($fileObject); $stmts = $parser->parse($code); $traverser->traverse((array) $stmts); } catch (Error $e) { - $errors->add(PhpParserError::fromParseException($e, $file->getRealPath())); + $errors->add(PhpParserError::fromParseException($e, $fileObject->getRealPath())); } return $errors; diff --git a/src/Task/Composer.php b/src/Task/Composer.php index e812ee8a7..26669f41f 100644 --- a/src/Task/Composer.php +++ b/src/Task/Composer.php @@ -102,9 +102,9 @@ public function run(ContextInterface $context): TaskResultInterface return TaskResult::createPassed($this, $context); } - private function hasLocalRepository(SplFileInfo $composerFile): bool + private function hasLocalRepository(string $composerFile): bool { - $json = $this->filesystem->readFromFileInfo($composerFile); + $json = $this->filesystem->readPath($composerFile); $package = json_decode($json, true); if (!array_key_exists('repositories', $package)) { diff --git a/src/Task/FileSize.php b/src/Task/FileSize.php index fd85a3135..799a461c9 100644 --- a/src/Task/FileSize.php +++ b/src/Task/FileSize.php @@ -78,7 +78,7 @@ public function run(ContextInterface $context): TaskResultInterface foreach ($files as $file) { $errorMessage .= sprintf( '- %s exceeded the maximum size of %s.'.PHP_EOL, - $file->getFilename(), + $file, $maxSize ); } diff --git a/src/Test/Task/AbstractTaskTestCase.php b/src/Test/Task/AbstractTaskTestCase.php index e616536c3..d79c0b991 100644 --- a/src/Test/Task/AbstractTaskTestCase.php +++ b/src/Test/Task/AbstractTaskTestCase.php @@ -168,14 +168,7 @@ protected static function mockContext(string $class = ContextInterface::class, a /** @var ContextInterface|ObjectProphecy $context */ $context = (new Prophet())->prophesize($class); $context->getFiles()->willReturn( - new FilesCollection( - array_map( - static function ($file): SplFileInfo { - return $file instanceof SplFileInfo ? $file : new SplFileInfo($file, $file, $file); - }, - $files - ) - ) + new FilesCollection($files) ); return $context->reveal(); diff --git a/test/Unit/Linter/Xml/XmlLinterTest.php b/test/Unit/Linter/Xml/XmlLinterTest.php index 9310db2d9..f2a3fdfb6 100644 --- a/test/Unit/Linter/Xml/XmlLinterTest.php +++ b/test/Unit/Linter/Xml/XmlLinterTest.php @@ -26,7 +26,7 @@ protected function setUp(): void /** * @param string $fixture * - * @return SplFileInfo + * @return string */ private function getFixture($fixture) { @@ -35,7 +35,7 @@ private function getFixture($fixture) throw new RuntimeException(sprintf('The fixture %s could not be loaded!', $fixture)); } - return $file; + return $file->getPathname(); } /** diff --git a/test/Unit/Task/ComposerTest.php b/test/Unit/Task/ComposerTest.php index 3984ecfd7..5766e59d2 100644 --- a/test/Unit/Task/ComposerTest.php +++ b/test/Unit/Task/ComposerTest.php @@ -10,7 +10,6 @@ use GrumPHP\Task\TaskInterface; use GrumPHP\Test\Task\AbstractExternalTaskTestCase; use GrumPHP\Util\Filesystem; -use Prophecy\Argument; use Prophecy\Prophecy\ObjectProphecy; class ComposerTest extends AbstractExternalTaskTestCase @@ -83,7 +82,7 @@ function () { self::mockContext(RunContext::class, ['composer.json']), function () { $this->mockProcessBuilder('composer', self::mockProcess(0)); - $this->filesystem->readFromFileInfo(Argument::which('getBasename', 'composer.json'))->willReturn( + $this->filesystem->readPath('composer.json')->willReturn( json_encode([ 'repositories' => [ ['type' => 'path'], @@ -100,7 +99,7 @@ function () { self::mockContext(RunContext::class, ['composer.json']), function () { $this->mockProcessBuilder('composer', self::mockProcess(0)); - $this->filesystem->readFromFileInfo(Argument::which('getBasename', 'composer.json'))->willReturn( + $this->filesystem->readPath('composer.json')->willReturn( json_encode([ 'repositories' => [ ['type' => 'path'], @@ -129,7 +128,7 @@ function () { self::mockContext(RunContext::class, ['composer.json']), function () { $this->mockProcessBuilder('composer', self::mockProcess(0)); - $this->filesystem->readFromFileInfo(Argument::which('getBasename', 'composer.json'))->willReturn( + $this->filesystem->readPath('composer.json')->willReturn( json_encode([ 'name' => 'my/package', ]) @@ -143,7 +142,7 @@ function () { self::mockContext(RunContext::class, ['composer.json']), function () { $this->mockProcessBuilder('composer', self::mockProcess(0)); - $this->filesystem->readFromFileInfo(Argument::which('getBasename', 'composer.json'))->willReturn( + $this->filesystem->readPath('composer.json')->willReturn( json_encode([ 'repositories' => [ ['type' => 'git'], @@ -159,7 +158,7 @@ function () { self::mockContext(RunContext::class, ['composer.json']), function () { $this->mockProcessBuilder('composer', self::mockProcess(0)); - $this->filesystem->readFromFileInfo(Argument::which('getBasename', 'composer.json'))->willReturn( + $this->filesystem->readPath('composer.json')->willReturn( json_encode([ 'repositories' => [ ['packagist.org' => false], diff --git a/test/Unit/Task/FileSizeTest.php b/test/Unit/Task/FileSizeTest.php index 034907583..78064ae71 100644 --- a/test/Unit/Task/FileSizeTest.php +++ b/test/Unit/Task/FileSizeTest.php @@ -10,11 +10,12 @@ use GrumPHP\Task\FileSize; use GrumPHP\Task\TaskInterface; use GrumPHP\Test\Task\AbstractTaskTestCase; -use Prophecy\Prophet; -use Symfony\Component\Finder\SplFileInfo; +use Symfony\Component\Filesystem\Filesystem; class FileSizeTest extends AbstractTaskTestCase { + protected static ?Filesystem $filesystem; + protected function provideTask(): TaskInterface { return new FileSize(); @@ -58,42 +59,41 @@ public static function provideRunContexts(): iterable public static function provideFailsOnStuff(): iterable { + $singleFile1 = self::fixture('single-invalid/file1.php', 6); + $singleFile2 = self::fixture('single-invalid/file2.php', 12); yield 'single-invalid-filesizes' => [ [], - self::mockContext(RunContext::class, [ - self::mockFile('file1.php', 6), - self::mockFile('file2.php', 12), - ]), + self::mockContext(RunContext::class, [$singleFile1, $singleFile2]), function (array $options, ContextInterface $context) { }, 'Large files detected:'.PHP_EOL. - '- file2.php exceeded the maximum size of 10M.'.PHP_EOL, + '- '. $singleFile2 .' exceeded the maximum size of 10M.'.PHP_EOL, ]; + + $invalidFile1 = self::fixture('invalid/file1.php', 12); + $invalidFile2 = self::fixture('invalid/file2.php', 12); yield 'invalid-filesizes' => [ [], - self::mockContext(RunContext::class, [ - self::mockFile('file1.php', 12), - self::mockFile('file2.php', 12), - ]), + self::mockContext(RunContext::class, [$invalidFile1, $invalidFile2]), function (array $options, ContextInterface $context) { }, 'Large files detected:'.PHP_EOL. - '- file1.php exceeded the maximum size of 10M.'.PHP_EOL. - '- file2.php exceeded the maximum size of 10M.'.PHP_EOL, + '- '.$invalidFile1.' exceeded the maximum size of 10M.'.PHP_EOL. + '- '.$invalidFile2.' exceeded the maximum size of 10M.'.PHP_EOL, ]; + + $customFile1 = self::fixture('invalid-custom/file1.php', 12); + $customFile2 = self::fixture('invalid-custom/file2.php', 12); yield 'invalid-filesizes-custom-size' => [ [ 'max_size' => '5M' ], - self::mockContext(RunContext::class, [ - self::mockFile('file1.php', 12), - self::mockFile('file2.php', 12), - ]), + self::mockContext(RunContext::class, [$customFile1, $customFile2]), function (array $options, ContextInterface $context) { }, 'Large files detected:'.PHP_EOL. - '- file1.php exceeded the maximum size of 5M.'.PHP_EOL. - '- file2.php exceeded the maximum size of 5M.'.PHP_EOL, + '- '.$customFile1.' exceeded the maximum size of 5M.'.PHP_EOL. + '- '.$customFile2.' exceeded the maximum size of 5M.'.PHP_EOL, ]; } @@ -102,8 +102,8 @@ public static function providePassesOnStuff(): iterable yield 'valid-filesizes' => [ [], self::mockContext(RunContext::class, [ - self::mockFile('file1.php', 6), - self::mockFile('file2.php', 6), + self::fixture('valid/file1.php', 6), + self::fixture('valid/file2.php', 6), ]), function () { } @@ -113,14 +113,14 @@ function () { 'ignore_patterns' => ['test/'], ], self::mockContext(RunContext::class, [ - self::mockFile('test/file.php', 2323, true), + self::fixture('ignored/test/file.php', 12), ]), function () {} ]; yield 'dont-validate-symlinks' => [ [], self::mockContext(RunContext::class, [ - self::mockFile('file.php', 2323, true), + self::fixtureSymlink('symlinks/file.php', 12), ]), function () {} ]; @@ -136,16 +136,51 @@ function () { ]; } - private static function mockFile(string $file, int $megaBytes, $isSymlink = false): SplFileInfo + public static function tearDownAfterClass(): void + { + self::filesystem()->remove(self::fixtureRoot()); + } + + private static function fixture(string $relative, int $sizeMB): string + { + $path = self::fixtureRoot() . '/' . $relative; + + self::filesystem()->mkdir(\dirname($path)); + self::createFileWithContent($path, $sizeMB); + + return $path; + } + + private static function fixtureSymlink(string $relative, int $sizeMB): string + { + $linkPath = self::fixtureRoot() . '/' . $relative; + self::filesystem()->mkdir(\dirname($linkPath)); + + $targetPath = \dirname($linkPath) . '/_target_' . \basename($linkPath); + self::createFileWithContent($targetPath, $sizeMB); + + self::filesystem()->remove($linkPath); + self::filesystem()->symlink($targetPath, $linkPath); + + return $linkPath; + } + + private static function createFileWithContent(string $path, int $sizeMB): void { - /** @var SplFileInfo $mock */ - $mock = (new Prophet())->prophesize(SplFileInfo::class); - $mock->getFilename()->willReturn($file); - $mock->getRelativePathname()->willReturn($file); - $mock->isLink()->willReturn($isSymlink); - $mock->isFile()->willReturn(true); - $mock->getSize()->willReturn($megaBytes * 1024 * 1024); - - return $mock->reveal(); + $fh = \fopen($path, 'w'); + \ftruncate($fh, $sizeMB * 1024 * 1024); + \fclose($fh); + } + + private static function fixtureRoot(): string + { + return \sys_get_temp_dir() . '/grumphp-filesize-test'; + } + + private static function filesystem(): Filesystem + { + self::$filesystem ??= new Filesystem(); + + return self::$filesystem; } } diff --git a/test/fixtures/e2e/tasks/ValidatePathsTask.php b/test/fixtures/e2e/tasks/ValidatePathsTask.php index a995b150d..3f2e88282 100644 --- a/test/fixtures/e2e/tasks/ValidatePathsTask.php +++ b/test/fixtures/e2e/tasks/ValidatePathsTask.php @@ -55,9 +55,7 @@ public function canRunInContext(ContextInterface $context): bool public function run(ContextInterface $context): TaskResultInterface { - $contextFiles = $context->getFiles()->map(function(\SplFileInfo $file) { - return $file->getPathname(); - })->toArray(); + $contextFiles = $context->getFiles()->toArray(); try { Assert::assertEquals($this->availableFiles, $contextFiles);