From 6b166b47e26acc824cd6ea95fa035320db06baf8 Mon Sep 17 00:00:00 2001 From: Rob Frawley 2nd Date: Fri, 8 Sep 2017 01:58:46 -0400 Subject: [PATCH] fix and amend console resolve and remove commands - change --as-script/-s option name/shortcut to --machine-readable/-m to fix collision with core symfony 2.x console options (the long name was changed to provide additional clarity) - change option shortcut and name for "as-script/s" to "machine-readable/m" to fix collision with symfony-provided "shell/s" option from the 2.x series - align output formatting and internal implementation of remove command with the improvements already introduced to the resolve command in 1.9.0 - update help content for both remove and resolve commands to align with updated output formatting and better describe all available functionality of the commands - extracted common functionality between resolve and remove commands to a new shared abstract class to remove code duplication - updated CHANGELOG.md and UPGRADE.md to note BC break and detail additional changes - renamed test filter sets to more realistic names and added some additional ones too - completely rewrote remove command tests and refactored resolve command tests to enable shared fixtures and methods between the two - in test app, moved default web loader to descrete, new "web" named loader and made the "default" loader a chain loader that uses "web" as welll as many of the other ones --- Binary/Loader/FileSystemLoader.php | 2 +- CHANGELOG.md | 11 +- Command/AbstractCacheCommand.php | 242 ++++++++++++ Command/RemoveCacheCommand.php | 129 ++++-- Command/ResolveCacheCommand.php | 216 ++++------ .../Filter/Loader/ResampleFilterLoader.php | 2 +- .../LiipImagineExtensionTest.php | 2 +- .../Binary/Loader/ChainLoaderTest.php | 2 +- .../Command/AbstractCacheCommandTestCase.php | 263 +++++++++++++ .../Command/AbstractCommandTestCase.php | 43 -- .../Command/RemoveCacheCommandTest.php | 340 ++++++++++++++++ Tests/Functional/Command/RemoveCacheTest.php | 269 ------------- .../Command/ResolveCacheCommandTest.php | 306 +++++++++++++++ Tests/Functional/Command/ResolveCacheTest.php | 369 ------------------ .../Controller/ImagineControllerTest.php | 36 +- .../Resources/public/cats-bar-bundle.jpg | Bin 0 -> 34087 bytes .../Fixtures/CacheCommandFixtures.php | 99 +++++ .../Resources/public/cats-foo-bundle.jpg | Bin 0 -> 34087 bytes Tests/Functional/app/config/config.yml | 32 +- .../Loader/ResampleFilterLoaderTest.php | 2 +- UPGRADE.md | 15 + 21 files changed, 1477 insertions(+), 903 deletions(-) create mode 100644 Command/AbstractCacheCommand.php create mode 100644 Tests/Functional/Command/AbstractCacheCommandTestCase.php delete mode 100644 Tests/Functional/Command/AbstractCommandTestCase.php create mode 100644 Tests/Functional/Command/RemoveCacheCommandTest.php delete mode 100644 Tests/Functional/Command/RemoveCacheTest.php create mode 100644 Tests/Functional/Command/ResolveCacheCommandTest.php delete mode 100644 Tests/Functional/Command/ResolveCacheTest.php create mode 100644 Tests/Functional/Fixtures/BarBundle/Resources/public/cats-bar-bundle.jpg create mode 100644 Tests/Functional/Fixtures/CacheCommandFixtures.php create mode 100644 Tests/Functional/Fixtures/FooBundle/Resources/public/cats-foo-bundle.jpg diff --git a/Binary/Loader/FileSystemLoader.php b/Binary/Loader/FileSystemLoader.php index ef7104b0f..529c26f47 100644 --- a/Binary/Loader/FileSystemLoader.php +++ b/Binary/Loader/FileSystemLoader.php @@ -42,7 +42,7 @@ class FileSystemLoader implements LoaderInterface * * @param MimeTypeGuesserInterface $mimeGuesser * @param ExtensionGuesserInterface $extensionGuesser - * @param LocatorInterface + * @param LocatorInterface $locator */ public function __construct(MimeTypeGuesserInterface $mimeGuesser, ExtensionGuesserInterface $extensionGuesser, $locator) { diff --git a/CHANGELOG.md b/CHANGELOG.md index 529132f9d..3c1d47a3f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,11 +10,18 @@ This project adheres to [semantic versioning](http://semver.org/spec/v2.0.0.html ## [Unreleased](https://github.com/liip/LiipImagineBundle/tree/HEAD) *Note: Recent developments can be tracked via the -[latest changelog](https://github.com/liip/LiipImagineBundle/compare/1.9.0...HEAD), the -[active milestone](https://github.com/liip/LiipImagineBundle/milestone/15), as well as all +[latest changelog](https://github.com/liip/LiipImagineBundle/compare/1.9.1...HEAD), the +[active milestone](https://github.com/liip/LiipImagineBundle/milestone/16), as well as all [open milestones](https://github.com/liip/LiipImagineBundle/milestones).* +## [v1.9.1](https://github.com/liip/LiipImagineBundle/tree/1.9.1) + +*Released on* 2017-09-08 *and assigned* [`1.9.1`](https://github.com/liip/LiipImagineBundle/releases/tag/1.9.1) *tag \([view verbose changelog](https://github.com/liip/LiipImagineBundle/compare/1.9.0...1.9.1)\).* + +- __\[Console\]__ __\[BC BREAK\]__ The resolve command's --as-script/-s option/shortcut renamed to --machine-readable/-m \(fixes [\#988](https://github.com/liip/LiipImagineBundle/pull/988)\), its output updated to aligned with the resolve command, and the "--machine-readable/-m" option added. [\#991](https://github.com/liip/LiipImagineBundle/pull/991) *([robfrawley](https://github.com/robfrawley))* + + ## [v1.9.0](https://github.com/liip/LiipImagineBundle/tree/1.9.0) *Released on* 2017-08-30 *and assigned* [`1.9.0`](https://github.com/liip/LiipImagineBundle/releases/tag/1.9.0) *tag \([view verbose changelog](https://github.com/liip/LiipImagineBundle/compare/1.8.0...1.9.0)\).* diff --git a/Command/AbstractCacheCommand.php b/Command/AbstractCacheCommand.php new file mode 100644 index 000000000..557892eb6 --- /dev/null +++ b/Command/AbstractCacheCommand.php @@ -0,0 +1,242 @@ +output = $output; + $this->machineReadable = $input->getOption('machine-readable'); + $this->actionFailures = 0; + } + + /** + * @param InputInterface $input + * + * @return array + */ + protected function resolveFilters(InputInterface $input) + { + $filters = $input->getOption('filter'); + + if (0 !== count($deprecated = $input->getOption('filters'))) { + $filters = array_merge($filters, $deprecated); + @trigger_error('The --filters option was deprecated in 1.9.0 and removed in 2.0.0. Use the --filter option instead.', E_USER_DEPRECATED); + } + + if (0 === count($filters) && 0 === count($filters = array_keys($this->getFilterManager()->getFilterConfiguration()->all()))) { + $this->output->writeln(' [ERROR] You do not have any configured filters available. '); + } + + return $filters; + } + + /** + * @param string $command + */ + protected function writeCommandHeading($command) + { + if ($this->machineReadable) { + return; + } + + $title = sprintf('[liip/imagine-bundle] %s Image Caches', ucfirst($command)); + $this->writeNewline(); + $this->output->writeln(sprintf('%s', $title)); + $this->output->writeln(str_repeat('=', strlen($title))); + $this->writeNewline(); + } + + /** + * @param string[] $filters + * @param string[] $targets + * @param bool $glob + */ + protected function writeResultSummary(array $filters, array $targets, $glob = false) + { + if ($this->machineReadable) { + return; + } + + $targetCount = count($targets); + $filterCount = count($filters); + $actionCount = ($glob ? $filterCount : ($filterCount * $targetCount)) - $this->actionFailures; + + $this->writeNewline(); + $this->output->writeln(vsprintf(' Completed %d %s (%d %s / %s %s) %s', array( + $actionCount, + $this->getPluralized($actionCount, 'operation'), + $filterCount, + $this->getPluralized($filterCount, 'filter'), + $glob ? '?' : $targetCount, + $this->getPluralized($targetCount, 'image'), + $this->getResultSummaryFailureMarkup(), + ))); + $this->writeNewline(); + } + + /** + * @param string $filter + * @param string|null $target + */ + protected function writeActionStart($filter, $target = null) + { + if (!$this->machineReadable) { + $this->output->write(' - '); + } + + $this->output->write(sprintf('%s[%s] ', $target ?: '*', $filter)); + } + + /** + * @param string $result + * @param bool $continued + */ + protected function writeActionResult($result, $continued = true) + { + $this->output->write($continued ? sprintf('%s: ', $result) : $result); + + if (!$continued) { + $this->writeNewline(); + } + } + + /** + * @param string $detail + */ + protected function writeActionDetail($detail) + { + $this->output->write($detail); + $this->writeNewline(); + } + + /** + * @param \Exception $exception + */ + protected function writeActionException(\Exception $exception) + { + $this->writeActionResult('failure'); + $this->writeActionDetail($exception->getMessage()); + ++$this->actionFailures; + } + + /** + * @return int + */ + protected function getReturnCode() + { + return 0 === $this->actionFailures ? 0 : 255; + } + + /** + * @return CacheManager + */ + protected function getCacheManager() + { + static $manager; + + if (null === $manager) { + $manager = $this->getContainer()->get('liip_imagine.cache.manager'); + } + + return $manager; + } + + /** + * @return FilterManager + */ + protected function getFilterManager() + { + static $manager; + + if (null === $manager) { + $manager = $this->getContainer()->get('liip_imagine.filter.manager'); + } + + return $manager; + } + + /** + * @return DataManager + */ + protected function getDataManager() + { + static $manager; + + if (null === $manager) { + $manager = $this->getContainer()->get('liip_imagine.data.manager'); + } + + return $manager; + } + + /** + * @param int $count + */ + private function writeNewline($count = 1) + { + $this->output->write(str_repeat(PHP_EOL, $count)); + } + + /** + * @param int $size + * @param string $word + * + * @return string + */ + private function getPluralized($size, $word) + { + return 1 === $size ? $word : sprintf('%ss', $word); + } + + /** + * @return string + */ + private function getResultSummaryFailureMarkup() + { + if (0 === $this->actionFailures) { + return ''; + } + + return vsprintf(' encountered %s %s ', array( + $this->actionFailures, + $this->getPluralized($this->actionFailures, 'failure'), + )); + } +} diff --git a/Command/RemoveCacheCommand.php b/Command/RemoveCacheCommand.php index aa8d53514..ab6e33376 100644 --- a/Command/RemoveCacheCommand.php +++ b/Command/RemoveCacheCommand.php @@ -11,41 +11,62 @@ namespace Liip\ImagineBundle\Command; -use Liip\ImagineBundle\Imagine\Cache\CacheManager; -use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; -class RemoveCacheCommand extends ContainerAwareCommand +class RemoveCacheCommand extends AbstractCacheCommand { protected function configure() { $this ->setName('liip:imagine:cache:remove') - ->setDescription('Remove cache for given paths and set of filters.') - ->addArgument('paths', InputArgument::OPTIONAL | InputArgument::IS_ARRAY, 'Image paths') - ->addOption('filters', null, InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY, - 'List of filters to remove for passed images (Deprecated, use "filter").') + ->setDescription('Remove asset caches for the passed asset paths(s) and filter name(s)') + ->addArgument('paths', InputArgument::OPTIONAL | InputArgument::IS_ARRAY, + 'Asset paths to remove caches of (passing none will use all paths).') ->addOption('filter', 'f', InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY, - 'List of filters to remove for passed images.') + 'Filter name to remove caches of (passing none will use all registered filters)') + ->addOption('machine-readable', 'm', InputOption::VALUE_NONE, + 'Enable machine parseable output (removing extraneous output and all text styles)') + ->addOption('filters', null, InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY, + 'Deprecated in 1.9.0 and removed in 2.0.0 (use the --filter option instead)') ->setHelp(<<<'EOF' -The %command.name% command removes cache by specified parameters. +The %command.name% command removes asset cache for the passed image(s) and filter(s). + +For an application that has only the two files "foo.ext" and "bar.ext", and only the filter sets +named "thumb_sm" and "thumb_lg", the following examples will behave as shown. + +# bin/console %command.name% foo.ext +Removes caches for foo.ext using all configured filters, outputting: + - foo.ext[thumb_sm] removed + - foo.ext[thumb_lg] removed + +# bin/console %command.name% --filter=thumb_sm --filter=thumb_lg foo.ext bar.ext +Removes caches for both foo.ext and bar.ext using thumb_sm and thumb_lg filters, outputting: + - foo.ext[thumb_sm] removed + - foo.ext[thumb_lg] removed + - bar.ext[thumb_sm] removed + - bar.ext[thumb_lg] removed + +# bin/console %command.name% --filter=thumb_sm +Removes all caches for thumb_sm filter, outputting: + - *[thumb_sm] glob-removal -Paths should be separated by spaces: -php app/console %command.name% path1 path2 -All cache for a given `paths` will be lost. +# bin/console %command.name% +Removes all caches for all configured filters, removing all cached assets, outputting: + - *[thumb_sm] glob-removal + - *[thumb_lg] glob-removal -If you use --filter parameter: -php app/console %command.name% --filter=thumb1 --filter=thumb2 -All cache for a given filters will be lost. +# bin/console %command.name% --force --filter=thumb_sm foo.ext +Removing caches for foo.ext using thumb_sm filter when already removed will caused skipping, outputting: + - foo.ext[thumb_sm] skipped -You can combine these parameters: -php app/console %command.name% path1 path2 --filter=thumb1 --filter=thumb2 +# bin/console %command.name% --filter=does_not_exist --filter=thumb_sm foo.ext +Removes caches for foo.ext for thumb_sm while failing inline on invalid filter (or other errors), outputting: + - foo.ext[does_not_exist] failure: Could not find configuration for a filter: does_not_exist + - foo.ext[thumb_sm] removed -php app/console %command.name% -Cache for all paths and filters will be lost when executing this command without parameters. EOF ); } @@ -58,34 +79,68 @@ protected function configure() */ protected function execute(InputInterface $input, OutputInterface $output) { - $paths = $input->getArgument('paths'); - $filters = $this->resolveInputFilters($input); + $this->initializeInstState($input, $output); + $this->writeCommandHeading('remove'); - if (empty($filters)) { - $filters = null; - } + $filters = $this->resolveFilters($input); + $targets = $input->getArgument('paths'); - /* @var CacheManager cacheManager */ - $cacheManager = $this->getContainer()->get('liip_imagine.cache.manager'); - $cacheManager->remove($paths, $filters); + if (0 === count($targets)) { + $this->doCacheRemoveAsGlobbedFilterName($filters); + } else { + $this->doCacheRemoveAsFiltersAndTargets($filters, $targets); + } - return 0; + return $this->getReturnCode(); } /** - * @param InputInterface $input - * - * @return array|mixed + * @param string[] $filters */ - private function resolveInputFilters(InputInterface $input) + private function doCacheRemoveAsGlobbedFilterName(array $filters) { - $filters = $input->getOption('filter'); + foreach ($filters as $f) { + $this->doCacheRemove($f); + } + + $this->writeResultSummary($filters, array(), true); + } - if (count($filtersDeprecated = $input->getOption('filters'))) { - $filters = array_merge($filters, $filtersDeprecated); - @trigger_error('As of 1.9, use of the "--filters" option has been deprecated in favor of "--filter" and will be removed in 2.0.', E_USER_DEPRECATED); + /** + * @param string[] $filters + * @param string[] $targets + */ + private function doCacheRemoveAsFiltersAndTargets(array $filters, array $targets) + { + foreach ($targets as $t) { + foreach ($filters as $f) { + $this->doCacheRemove($f, $t); + } } - return $filters; + $this->writeResultSummary($filters, $targets); + } + + /** + * @param string $filter + * @param string|null $target + */ + private function doCacheRemove($filter, $target = null) + { + $this->writeActionStart($filter, $target); + + try { + if (null === $target) { + $this->getCacheManager()->remove(null, $filter); + $this->writeActionResult('glob-removal', false); + } elseif ($this->getCacheManager()->isStored($target, $filter)) { + $this->getCacheManager()->remove($target, $filter); + $this->writeActionResult('removed', false); + } else { + $this->writeActionResult('skipped', false); + } + } catch (\Exception $e) { + $this->writeActionException($e); + } } } diff --git a/Command/ResolveCacheCommand.php b/Command/ResolveCacheCommand.php index 0b3209cb3..df9d3cdf7 100644 --- a/Command/ResolveCacheCommand.php +++ b/Command/ResolveCacheCommand.php @@ -11,193 +11,113 @@ namespace Liip\ImagineBundle\Command; -use Liip\ImagineBundle\Imagine\Cache\CacheManager; -use Liip\ImagineBundle\Imagine\Data\DataManager; -use Liip\ImagineBundle\Imagine\Filter\FilterManager; -use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; -class ResolveCacheCommand extends ContainerAwareCommand +class ResolveCacheCommand extends AbstractCacheCommand { protected function configure() { $this ->setName('liip:imagine:cache:resolve') - ->setDescription('Resolve cache for given path and set of filters.') + ->setDescription('Resolves asset caches for the passed asset paths(s) and filter set name(s)') ->addArgument('paths', InputArgument::REQUIRED | InputArgument::IS_ARRAY, - 'Any number of image paths to act on.') - ->addOption('filters', null, InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY, - 'List of filters to apply to passed images (Deprecated, use "filter").') + 'Asset paths to resolve caches for') ->addOption('filter', 'f', InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY, - 'List of filters to apply to passed images.') + 'Filter name to resolve caches for (passing none will use all registered filters)') ->addOption('force', 'F', InputOption::VALUE_NONE, - 'Force image resolution regardless of cache.') - ->addOption('as-script', 's', InputOption::VALUE_NONE, - 'Only print machine-parseable results.') + 'Force asset cache resolution (ignoring whether it already cached)') + ->addOption('machine-readable', 'm', InputOption::VALUE_NONE, + 'Enable machine parseable output (removing extraneous output and all text styles)') + ->addOption('filters', null, InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY, + 'Deprecated in 1.9.0 and removed in 2.0.0 (use the --filter option instead)') ->setHelp(<<<'EOF' -The %command.name% command resolves the passed image(s) for the resolved -filter(s), outputting results using the following basic format: - - "image.ext[filter]" (resolved|cached|failed) as "path/to/cached/image.ext" - -# bin/console %command.name% --filter=thumb1 foo.ext bar.ext -Resolve both foo.ext and bar.ext using thumb1 filter, outputting: - - "foo.ext[thumb1]" resolved as "http://localhost/media/cache/thumb1/foo.ext" - - "bar.ext[thumb1]" resolved as "http://localhost/media/cache/thumb1/bar.ext" +The %command.name% command resolves asset cache for the passed image(s) and filter(s). -# bin/console %command.name% --filter=thumb1 --filter=thumb2 foo.ext -Resolve foo.ext using both thumb1 and thumb2 filters, outputting: - - "foo.ext[thumb1]" resolved as "http://localhost/media/cache/thumb1/foo.ext" - - "foo.ext[thumb2]" resolved as "http://localhost/media/cache/thumb2/foo.ext" +For an application that has only the two files "foo.ext" and "bar.ext", and only the filter sets +named "thumb_sm" and "thumb_lg", the following examples will behave as shown. -# bin/console %command.name% foo.ext -Resolve foo.ext using all configured filters (as none are specified), outputting: - - "foo.ext[thumb1]" resolved as "http://localhost/media/cache/thumb1/foo.ext" - - "foo.ext[thumb2]" resolved as "http://localhost/media/cache/thumb2/foo.ext" +# bin/console %command.name% foo.ext bar.ext +Resolves caches for both foo.ext and bar.ext using all configured filters, outputting: + - foo.ext[thumb_sm] resolved: http://localhost/media/cache/thumb_sm/foo.ext + - bar.ext[thumb_sm] resolved: http://localhost/media/cache/thumb_sm/bar.ext + - foo.ext[thumb_lg] resolved: http://localhost/media/cache/thumb_lg/foo.ext + - bar.ext[thumb_lg] resolved: http://localhost/media/cache/thumb_lg/bar.ext -# bin/console %command.name% --force --filter=thumb1 foo.ext -Resolve foo.ext using thumb1 and force creation regardless of cache, outputting: - - "foo.ext[thumb1]" resolved as "http://localhost/media/cache/thumb1/foo.ext" - -EOF - ); - } - - protected function execute(InputInterface $input, OutputInterface $output) - { - $force = $input->getOption('force'); - $paths = $input->getArgument('paths'); - $filters = $this->resolveInputFilters($input); - $machine = $input->getOption('as-script'); - $failed = 0; - - $filterManager = $this->getFilterManager(); - $dataManager = $this->getDataManager(); - $cacheManager = $this->getCacheManager(); - - if (0 === count($filters)) { - $filters = array_keys($filterManager->getFilterConfiguration()->all()); - } +# bin/console %command.name% --filter=thumb_sm foo.ext +Resolves caches for foo.ext using only thumb_sm filter, outputting: + - foo.ext[thumb_sm] resolved: http://localhost/media/cache/thumb_sm/foo.ext - $this->outputTitle($output, $machine); - - foreach ($paths as $path) { - foreach ($filters as $filter) { - $output->write(sprintf('- %s[%s] ', $path, $filter)); - - try { - if ($force || !$cacheManager->isStored($path, $filter)) { - $cacheManager->store($filterManager->applyFilter($dataManager->find($filter, $path), $filter), $path, $filter); - $output->write('resolved: '); - } else { - $output->write('cached: '); - } - - $output->writeln($cacheManager->resolve($path, $filter)); - } catch (\Exception $e) { - $output->writeln(sprintf('failed: %s', $e->getMessage())); - ++$failed; - } - } - } +# bin/console %command.name% --filter=thumb_sm --filter=thumb_lg foo.ext +Resolves caches for foo.ext using both thumb_sm and thumb_lg filters, outputting: + - foo.ext[thumb_sm] resolved: http://localhost/media/cache/thumb_sm/foo.ext + - foo.ext[thumb_lg] resolved: http://localhost/media/cache/thumb_lg/foo.ext - $this->outputSummary($output, $machine, count($filters), count($paths), $failed); +# bin/console %command.name% --force --filter=thumb_sm foo.ext +Resolving caches for foo.ext using thumb_sm filter when already cached will caused skipped, outputting: + - foo.ext[thumb_sm] skipped: http://localhost/media/cache/thumb_sm/foo.ext - return 0 === $failed ? 0 : 255; - } +# bin/console %command.name% --force --filter=thumb_sm foo.ext +Resolving caches for foo.ext using thumb_sm filter when already cached with force option re-resolves (ignoring cache), outputting: + - foo.ext[thumb_sm] resolved: http://localhost/media/cache/thumb_sm/foo.ext - /** - * @param OutputInterface $output - * @param bool $machine - */ - private function outputTitle(OutputInterface $output, $machine) - { - if (!$machine) { - $title = '[liip/imagine-bundle] Image Resolver'; +# bin/console %command.name% --filter=does_not_exist --filter=thumb_sm foo.ext +Resolves caches for foo.ext using thumb_sm while failing inline on invalid filter (or other errors), outputting: + - foo.ext[does_not_exist] failed: Could not find configuration for a filter: does_not_exist + - foo.ext[thumb_sm] removed: http://localhost/media/cache/thumb_sm/foo.ext - $output->writeln(sprintf('%s', $title)); - $output->writeln(str_repeat('=', strlen($title))); - $output->writeln(''); - } +EOF + ); } /** + * @param InputInterface $input * @param OutputInterface $output - * @param bool $machine - * @param int $filters - * @param int $paths - * @param int $failed - */ - private function outputSummary(OutputInterface $output, $machine, $filters, $paths, $failed) - { - if (!$machine) { - $operations = ($filters * $paths) - $failed; - - $output->writeln(''); - $output->writeln(vsprintf('Completed %d %s (%d %s on %d %s) %s', array( - $operations, - $this->pluralizeWord($operations, 'operation'), - $filters, - $this->pluralizeWord($filters, 'filter'), - $paths, - $this->pluralizeWord($paths, 'image'), - 0 === $failed ? '' : sprintf('[encountered %d %s]', $failed, $this->pluralizeWord($failed, 'failure')), - ))); - } - } - - /** - * @param int $count - * @param string $singular - * @param string $pluralEnding * - * @return string + * @return int */ - private function pluralizeWord($count, $singular, $pluralEnding = 's') + protected function execute(InputInterface $input, OutputInterface $output) { - return 1 === $count ? $singular : $singular.$pluralEnding; - } + $this->initializeInstState($input, $output); + $this->writeCommandHeading('resolve'); - /** - * @param InputInterface $input - * - * @return array|mixed - */ - private function resolveInputFilters(InputInterface $input) - { - $filters = $input->getOption('filter'); + $filters = $this->resolveFilters($input); + $targets = $input->getArgument('paths'); + $doForce = $input->getOption('force'); - if (count($filtersDeprecated = $input->getOption('filters'))) { - $filters = array_merge($filters, $filtersDeprecated); - @trigger_error('As of 1.9, use of the "--filters" option has been deprecated in favor of "--filter" and will be removed in 2.0.', E_USER_DEPRECATED); + foreach ($targets as $t) { + foreach ($filters as $f) { + $this->doCacheResolve($t, $f, $doForce); + } } - return $filters; - } + $this->writeResultSummary($filters, $targets); - /** - * @return FilterManager - */ - private function getFilterManager() - { - return $this->getContainer()->get('liip_imagine.filter.manager'); + return $this->getReturnCode(); } /** - * @return DataManager + * @param string $target + * @param string $filter + * @param bool $forced */ - private function getDataManager() + private function doCacheResolve($target, $filter, $forced) { - return $this->getContainer()->get('liip_imagine.data.manager'); - } + $this->writeActionStart($filter, $target); + + try { + if ($forced || !$this->getCacheManager()->isStored($target, $filter)) { + $this->getCacheManager()->store($this->getFilterManager()->applyFilter($this->getDataManager()->find($filter, $target), $filter), $target, $filter); + $this->writeActionResult('resolved'); + } else { + $this->writeActionResult('skipped'); + } - /** - * @return CacheManager - */ - private function getCacheManager() - { - return $this->getContainer()->get('liip_imagine.cache.manager'); + $this->writeActionDetail($this->getCacheManager()->resolve($target, $filter)); + } catch (\Exception $e) { + $this->writeActionException($e); + } } } diff --git a/Imagine/Filter/Loader/ResampleFilterLoader.php b/Imagine/Filter/Loader/ResampleFilterLoader.php index 0c91d5579..b19fc5f03 100644 --- a/Imagine/Filter/Loader/ResampleFilterLoader.php +++ b/Imagine/Filter/Loader/ResampleFilterLoader.php @@ -128,7 +128,7 @@ private function resolveOptions(array $options) $resolver->setAllowedValues('unit', array( ImageInterface::RESOLUTION_PIXELSPERINCH, - ImageInterface::RESOLUTION_PIXELSPERCENTIMETER + ImageInterface::RESOLUTION_PIXELSPERCENTIMETER, )); $resolver->setNormalizer('filter', function (Options $options, $value) { diff --git a/Tests/DependencyInjection/LiipImagineExtensionTest.php b/Tests/DependencyInjection/LiipImagineExtensionTest.php index 9d7034d1c..f12b8cc2c 100644 --- a/Tests/DependencyInjection/LiipImagineExtensionTest.php +++ b/Tests/DependencyInjection/LiipImagineExtensionTest.php @@ -57,7 +57,7 @@ public function testLoadWithDefaults() new Reference('liip_imagine.cache.manager'), new Reference('liip_imagine.cache.signer'), new Reference('logger', ContainerInterface::IGNORE_ON_INVALID_REFERENCE), - '%liip_imagine.controller.redirect_response_code%' + '%liip_imagine.controller.redirect_response_code%', ) ); } diff --git a/Tests/Functional/Binary/Loader/ChainLoaderTest.php b/Tests/Functional/Binary/Loader/ChainLoaderTest.php index f95611823..2e2813a6f 100644 --- a/Tests/Functional/Binary/Loader/ChainLoaderTest.php +++ b/Tests/Functional/Binary/Loader/ChainLoaderTest.php @@ -33,7 +33,7 @@ public function testFind() { static::createClient(); - $loader = $this->getLoader('baz'); + $loader = $this->getLoader('default'); foreach (array('images/cats.jpeg', 'images/cats2.jpeg', 'file.ext', 'bar-bundle-file.ext', 'foo-bundle-file.ext') as $file) { $this->assertNotNull($loader->find($file)); diff --git a/Tests/Functional/Command/AbstractCacheCommandTestCase.php b/Tests/Functional/Command/AbstractCacheCommandTestCase.php new file mode 100644 index 000000000..5fc824958 --- /dev/null +++ b/Tests/Functional/Command/AbstractCacheCommandTestCase.php @@ -0,0 +1,263 @@ +setApplication(new Application($this->createClient()->getKernel())); + if ($command instanceof ContainerAwareCommand) { + $command->setContainer($this->createClient()->getContainer()); + } + + $arguments = array_replace(array('command' => $command->getName()), $arguments); + + $commandTester = new CommandTester($command); + $return = $commandTester->execute($arguments, array('--env' => 'test')); + + return $commandTester->getDisplay(); + } + + /** + * @param string[] $images + * @param string[] $filters + */ + protected function assertImagesNotExist($images, $filters) + { + foreach ($images as $i) { + foreach ($filters as $f) { + $this->assertFileNotExists(sprintf('%s/%s/%s', $this->cacheRoot, $f, $i)); + } + } + } + + /** + * @param string[] $images + * @param string[] $filters + */ + protected function assertImagesExist($images, $filters) + { + foreach ($images as $i) { + foreach ($filters as $f) { + $this->assertFileExists(sprintf('%s/%s/%s', $this->cacheRoot, $f, $i)); + } + } + } + + /** + * @param string $output + * @param array $images + * @param array $filters + */ + protected function assertOutputContainsResolvedImages($output, array $images, array $filters) + { + foreach ($images as $i) { + foreach ($filters as $f) { + $this->assertOutputContainsImage($output, $i, $f, 'resolved'); + } + } + } + + /** + * @param string $output + * @param array $images + * @param array $filters + */ + protected function assertOutputContainsRemovedImages($output, array $images, array $filters) + { + foreach ($images as $i) { + foreach ($filters as $f) { + $this->assertOutputContainsImageShort($output, $i, $f, 'removed'); + } + } + } + + /** + * @param string $output + * @param array $filters + */ + protected function assertOutputContainsRemovedGlob($output, array $filters) + { + foreach ($filters as $f) { + $this->assertOutputContainsImageShort($output, '*', $f, 'glob-removal'); + } + } + + /** + * @param string $output + * @param array $images + * @param array $filters + */ + protected function assertOutputContainsSkippedImages($output, array $images, array $filters) + { + foreach ($images as $i) { + foreach ($filters as $f) { + $this->assertOutputContainsImage($output, $i, $f, 'skipped'); + } + } + } + + /** + * @param string $output + * @param array $images + * @param array $filters + */ + protected function assertOutputContainsSkippedImagesShort($output, array $images, array $filters) + { + foreach ($images as $i) { + foreach ($filters as $f) { + $this->assertOutputContainsImageShort($output, $i, $f, 'skipped'); + } + } + } + + /** + * @param string $output + * @param array $images + * @param array $filters + */ + protected function assertOutputContainsFailedImages($output, array $images, array $filters) + { + foreach ($images as $i) { + foreach ($filters as $f) { + $this->assertContains(sprintf('%s[%s] failure: ', $i, $f), $output); + } + } + } + + /** + * @param string $output + * @param string $image + * @param string $filter + * @param string $type + */ + protected function assertOutputContainsImage($output, $image, $filter, $type) + { + $expected = vsprintf('%s[%s] %s: http://localhost/media/cache/%s/%s', array( + $image, + $filter, + $type, + $filter, + $image, + )); + $this->assertContains($expected, $output); + } + + /** + * @param string $output + * @param string $image + * @param string $filter + * @param string $type + */ + protected function assertOutputContainsImageShort($output, $image, $filter, $type) + { + $expected = vsprintf('%s[%s] %s', array( + $image, + $filter, + $type, + )); + $this->assertContains($expected, $output); + } + + /** + * @param string $output + * @param string[] $images + * @param string[] $filters + * @param int $failures + */ + protected function assertOutputContainsSummary($output, array $images, array $filters, $failures = 0) + { + $this->assertContains(sprintf('Completed %d operation', (count($images) * count($filters)) - $failures), $output); + $this->assertContains(sprintf('%d image', count($images)), $output); + $this->assertContains(sprintf('%d filter', count($filters)), $output); + if (0 !== $failures) { + $this->assertContains(sprintf('%d failure', $failures), $output); + } + } + + /** + * @param string $output + * @param string[] $filters + * @param int $failures + */ + protected function assertOutputContainsSummaryGlob($output, array $filters, $failures = 0) + { + $this->assertContains(sprintf('Completed %d operation', count($filters) - $failures), $output); + $this->assertContains('? images', $output); + $this->assertContains(sprintf('%d filter', count($filters)), $output); + + if (0 !== $failures) { + $this->assertContains(sprintf('%d failure', $failures), $output); + } + } + + /** + * @param string $output + * @param string[] $images + * @param string[] $filters + * @param int $failures + */ + protected function assertOutputNotContainsSummary($output, array $images, array $filters, $failures = 0) + { + $this->assertNotContains(sprintf('Completed %d operation', (count($images) * count($filters)) - $failures), $output); + $this->assertNotContains(sprintf('%d image', count($images)), $output); + $this->assertNotContains(sprintf('%d filter', count($filters)), $output); + if (0 !== $failures) { + $this->assertNotContains(sprintf('%d failure', $failures), $output); + } + } + + /** + * @param string[] $images + * @param string[] $filters + */ + protected function delResolvedImages(array $images, array $filters) + { + foreach ($images as $i) { + foreach ($filters as $f) { + if (file_exists($f = sprintf('%s/%s/%s', $this->cacheRoot, $f, $i))) { + @unlink($f); + } + } + } + } + + /** + * @param string[] $images + * @param string[] $filters + * @param string $content + */ + protected function putResolvedImages(array $images, array $filters, $content = 'anImageContent') + { + $this->delResolvedImages($images, $filters); + + foreach ($images as $i) { + foreach ($filters as $f) { + $this->filesystem->dumpFile(sprintf('%s/%s/%s', $this->cacheRoot, $f, $i), $content); + } + } + } +} diff --git a/Tests/Functional/Command/AbstractCommandTestCase.php b/Tests/Functional/Command/AbstractCommandTestCase.php deleted file mode 100644 index 35e56d8a6..000000000 --- a/Tests/Functional/Command/AbstractCommandTestCase.php +++ /dev/null @@ -1,43 +0,0 @@ -setApplication(new Application($this->createClient()->getKernel())); - if ($command instanceof ContainerAwareCommand) { - $command->setContainer($this->createClient()->getContainer()); - } - - $arguments = array_replace(array('command' => $command->getName()), $arguments); - - $commandTester = new CommandTester($command); - $return = $commandTester->execute($arguments, array('--env' => 'test')); - - return $commandTester->getDisplay(); - } -} diff --git a/Tests/Functional/Command/RemoveCacheCommandTest.php b/Tests/Functional/Command/RemoveCacheCommandTest.php new file mode 100644 index 000000000..73945b35f --- /dev/null +++ b/Tests/Functional/Command/RemoveCacheCommandTest.php @@ -0,0 +1,340 @@ +putResolvedImages($images, $filters); + $this->assertImagesExist($images, $filters); + + $output = $this->executeRemoveCacheCommand(array(), array()); + + $this->assertImagesNotExist($images, $allFilters); + $this->assertOutputContainsRemovedGlob($output, $allFilters); + $this->assertOutputContainsSummaryGlob($output, $allFilters); + + $this->delResolvedImages($images, $allFilters); + } + + /** + * @return array + */ + public static function provideRemovesWhenPassedPathsAndFiltersData() + { + return CacheCommandFixtures::getAvailableFilterAndImageCombinations(); + } + + /** + * @dataProvider provideRemovesWhenPassedPathsAndFiltersData + * + * @param array $images + * @param array $filters + */ + public function testRemovesWhenPassedPathsAndFilters(array $images, array $filters) + { + $this->putResolvedImages($images, $filters); + $this->assertImagesExist($images, $filters); + + $output = $this->executeRemoveCacheCommand($images, $filters); + + $this->assertImagesNotExist($images, $filters); + $this->assertOutputContainsRemovedImages($output, $images, $filters); + $this->assertOutputContainsSummary($output, $images, $filters); + + $this->delResolvedImages($images, $filters); + } + + /** + * @return array + */ + public static function provideRemovesWhenPassedOnlyImagesData() + { + return static::provideRemovesCachesWithoutPathsOrFiltersData(); + } + + /** + * @dataProvider provideRemovesWhenPassedOnlyImagesData + * + * @param array $images + * @param array $filters + * @param array $allFilters + */ + public function testRemovesWhenPassedOnlyImages(array $images, array $filters, array $allFilters) + { + $this->putResolvedImages($images, $filters); + $this->assertImagesExist($images, $filters); + + $output = $this->executeRemoveCacheCommand($images, array()); + + $this->assertImagesNotExist($images, $filters); + $this->assertOutputContainsRemovedImages($output, $images, $filters); + $this->assertOutputContainsSummary($output, $images, $allFilters); + + if (count($allFilters) !== count($filters)) { + $this->assertOutputContainsSkippedImagesShort($output, $images, array_diff($allFilters, $filters)); + } + + $this->delResolvedImages($images, $allFilters); + } + + /** + * @return array + */ + public static function provideRemovesWhenPassedOnlyFiltersData() + { + return array_map(function (array $entry) { + $entry[] = CacheCommandFixtures::getValidImages(); + + return $entry; + }, CacheCommandFixtures::getAvailableFilterAndImageCombinations()); + } + + /** + * @dataProvider provideRemovesWhenPassedOnlyFiltersData + * + * @param array $images + * @param array $filters + * @param array $allImages + */ + public function testRemovesWhenPassedOnlyFilters(array $images, array $filters, array $allImages) + { + $this->putResolvedImages($allImages, $filters); + $this->assertImagesExist($allImages, $filters); + + $output = $this->executeRemoveCacheCommand(array(), $filters); + + $this->assertImagesNotExist($allImages, $filters); + $this->assertOutputContainsRemovedGlob($output, $filters); + $this->assertOutputContainsSummaryGlob($output, $filters); + + $this->delResolvedImages($allImages, $filters); + } + + /** + * @return array + */ + public static function provideRemoveSkipsWhenCacheDoesNotExistData() + { + $data = array_map(function (array $entry) { + $entry[] = CacheCommandFixtures::getImagesNotInArray($entry[0]); + + return $entry; + }, CacheCommandFixtures::getAvailableFilterAndImageCombinations()); + + return array_filter($data, function (array $entry) { + return null !== $entry[2]; + }); + } + + /** + * @dataProvider provideRemoveSkipsWhenCacheDoesNotExistData + * + * @param array $images + * @param array $filters + * @param array $existingImages + */ + public function testRemoveSkipsWhenCacheDoesNotExist(array $images, array $filters, array $existingImages) + { + $this->putResolvedImages($existingImages, $filters); + $this->assertImagesExist($existingImages, $filters); + $this->assertImagesNotExist($images, $filters); + + $output = $this->executeRemoveCacheCommand($images, $filters); + + $this->assertImagesExist($existingImages, $filters); + $this->assertImagesNotExist($images, $filters); + $this->assertOutputContainsSkippedImagesShort($output, $images, $filters); + $this->assertOutputContainsSummary($output, $images, $filters); + + $this->delResolvedImages(array_merge($images, $existingImages), $filters); + } + + /** + * @return array + */ + public static function provideRemoveShowsFailureAndContinuesWhenPassedInvalidFiltersData() + { + return array_map(function (array $entry) { + $entry[] = CacheCommandFixtures::getInvalidFilters(); + + return $entry; + }, CacheCommandFixtures::getAvailableFilterAndImageCombinations()); + } + + /** + * @dataProvider provideRemoveShowsFailureAndContinuesWhenPassedInvalidFiltersData + * + * @param array $images + * @param array $filters + * @param array $invalidFilters + */ + public function testRemoveShowsFailureAndContinuesWhenPassedInvalidFilters(array $images, array $filters, array $invalidFilters) + { + $allFilters = array_merge($filters, $invalidFilters); + + $this->putResolvedImages($images, $allFilters); + $this->assertImagesExist($images, $allFilters); + + $return = null; + $output = $this->executeRemoveCacheCommand($images, $allFilters, array(), $return); + + $this->assertSame(255, $return); + $this->assertImagesNotExist($images, $filters); + $this->assertImagesExist($images, $invalidFilters); + $this->assertOutputContainsRemovedImages($output, $images, $filters); + $this->assertOutputContainsFailedImages($output, $images, $invalidFilters); + $this->assertOutputContainsSummary($output, $images, $allFilters, count($images) * count($invalidFilters)); + + $this->delResolvedImages($images, $allFilters); + } + + /** + * @return array + */ + public static function provideRemoveShowsFailureAndContinuesWhenPassedInvalidPathsData() + { + return array_map(function (array $entry) { + $entry[] = CacheCommandFixtures::getInvalidImages(); + + return $entry; + }, CacheCommandFixtures::getAvailableFilterAndImageCombinations()); + } + + /** + * @dataProvider provideRemoveShowsFailureAndContinuesWhenPassedInvalidPathsData + * + * @param array $images + * @param array $filters + * @param array $invalidImages + */ + public function testRemoveShowsFailureAndContinuesWhenPassedInvalidPaths(array $images, array $filters, array $invalidImages) + { + $allImages = array_merge($images, $invalidImages); + + $this->putResolvedImages($images, $filters); + $this->assertImagesExist($images, $filters); + $this->assertImagesNotExist($invalidImages, $filters); + + $output = $this->executeRemoveCacheCommand($allImages, $filters); + + $this->assertImagesNotExist($allImages, $filters); + $this->assertOutputContainsRemovedImages($output, $images, $filters); + $this->assertOutputContainsSkippedImagesShort($output, $invalidImages, $filters); + $this->assertOutputContainsSummary($output, $allImages, $filters); + + $this->delResolvedImages($allImages, $filters); + } + + /** + * @return array + */ + public static function provideRemoveOutputsMachineParseableTextWhenPassedMachineReadableOptionData() + { + return CacheCommandFixtures::getAvailableFilterAndImageCombinations(); + } + + /** + * @dataProvider provideRemoveOutputsMachineParseableTextWhenPassedMachineReadableOptionData + * + * @param array $images + * @param array $filters + */ + public function testRemoveOutputsMachineParseableTextWhenPassedMachineReadableOption(array $images, array $filters) + { + $this->putResolvedImages($images, $filters); + $this->assertImagesExist($images, $filters); + + $output = $this->executeRemoveCacheCommand($images, $filters, array('--machine-readable' => true)); + + $this->assertImagesNotExist($images, $filters); + $this->assertNotContains('[liip/imagine-bundle]', $output); + $this->assertNotContains('=====================', $output); + $this->assertOutputContainsRemovedImages($output, $images, $filters); + $this->assertOutputNotContainsSummary($output, $images, $filters); + + $this->delResolvedImages($images, $filters); + } + + /** + * @return array + */ + public static function provideRemoveEmitsDeprecationMessageWhenUsingLegacyFiltersOptionData() + { + return CacheCommandFixtures::getAvailableFilterAndImageCombinations(); + } + + /** + * @dataProvider provideRemoveEmitsDeprecationMessageWhenUsingLegacyFiltersOptionData + * + * @group legacy + * @expectedDeprecation The --filters option was deprecated in 1.9.0 and removed in 2.0.0. Use the --filter option instead. + */ + public function testRemoveEmitsDeprecationMessageWhenUsingLegacyFiltersOption(array $images, array $filters) + { + $this->putResolvedImages($images, $filters); + $this->assertImagesExist($images, $filters); + + $output = $this->executeRemoveCacheCommand($images, array(), array('paths' => $images, '--filters' => $filters)); + + $this->assertImagesNotExist($images, $filters); + $this->assertOutputContainsRemovedImages($output, $images, $filters); + + $this->delResolvedImages($images, $filters); + } + + /** + * @param string[] $paths + * @param string[] $filters + * @param string[] $additionalOptions + * @param int $return + * + * @return string + */ + private function executeRemoveCacheCommand(array $paths, array $filters = array(), array $additionalOptions = array(), &$return = null) + { + $options = array_merge(array('paths' => $paths), $additionalOptions); + + if (0 < count($filters)) { + $options['--filter'] = $filters; + } + + return $this->executeConsole(new RemoveCacheCommand(), $options, $return); + } +} diff --git a/Tests/Functional/Command/RemoveCacheTest.php b/Tests/Functional/Command/RemoveCacheTest.php deleted file mode 100644 index bd98777f2..000000000 --- a/Tests/Functional/Command/RemoveCacheTest.php +++ /dev/null @@ -1,269 +0,0 @@ -assertFileNotExists($this->cacheRoot.'/thumbnail_web_path/images/cats.jpeg'); - - $this->executeConsole(new RemoveCacheCommand()); - } - - public function testExecuteSuccessfullyWithEmptyCacheAndOnePathAndOneFilter() - { - $this->assertFileNotExists($this->cacheRoot.'/thumbnail_web_path/images/cats.jpeg'); - - $this->executeConsole( - new RemoveCacheCommand(), - array( - 'paths' => array('images/cats.jpeg'), - '--filter' => array('thumbnail_web_path'), - )); - } - - public function testExecuteSuccessfullyWithEmptyCacheAndMultiplePaths() - { - $this->assertFileNotExists($this->cacheRoot.'/thumbnail_web_path/images/cats.jpeg'); - - $this->executeConsole( - new RemoveCacheCommand(), - array('paths' => array('images/cats.jpeg', 'images/cats2.jpeg')) - ); - } - - public function testExecuteSuccessfullyWithEmptyCacheAndMultipleFilters() - { - $this->assertFileNotExists($this->cacheRoot.'/thumbnail_web_path/images/cats.jpeg'); - - $this->executeConsole( - new RemoveCacheCommand(), - array('--filter' => array('thumbnail_web_path', 'thumbnail_default')) - ); - } - - public function testShouldRemoveAllCacheIfParametersDoesNotPassed() - { - $this->filesystem->dumpFile( - $this->cacheRoot.'/thumbnail_web_path/images/cats.jpeg', - 'anImageContent' - ); - $this->filesystem->dumpFile( - $this->cacheRoot.'/thumbnail_web_path/images/cats2.jpeg', - 'anImageContent2' - ); - $this->filesystem->dumpFile( - $this->cacheRoot.'/thumbnail_default/images/cats.jpeg', - 'anImageContent' - ); - - $this->executeConsole(new RemoveCacheCommand()); - - $this->assertFileNotExists($this->cacheRoot.'/thumbnail_web_path/images/cats.jpeg'); - $this->assertFileNotExists($this->cacheRoot.'/thumbnail_web_path/images/cats2.jpeg'); - $this->assertFileNotExists($this->cacheRoot.'/thumbnail_default/images/cats.jpeg'); - } - - public function testShouldRemoveCacheBySinglePath() - { - $this->filesystem->dumpFile( - $this->cacheRoot.'/thumbnail_web_path/images/cats.jpeg', - 'anImageContent' - ); - $this->filesystem->dumpFile( - $this->cacheRoot.'/thumbnail_default/images/cats.jpeg', - 'anImageContent' - ); - $this->filesystem->dumpFile( - $this->cacheRoot.'/thumbnail_web_path/images/cats2.jpeg', - 'anImageContent2' - ); - $this->filesystem->dumpFile( - $this->cacheRoot.'/thumbnail_default/images/cats2.jpeg', - 'anImageContent2' - ); - - $this->executeConsole( - new RemoveCacheCommand(), - array('paths' => array('images/cats.jpeg')) - ); - - $this->assertFileNotExists($this->cacheRoot.'/thumbnail_web_path/images/cats.jpeg'); - $this->assertFileNotExists($this->cacheRoot.'/thumbnail_default/images/cats.jpeg'); - $this->assertFileExists($this->cacheRoot.'/thumbnail_web_path/images/cats2.jpeg'); - $this->assertFileExists($this->cacheRoot.'/thumbnail_default/images/cats2.jpeg'); - } - - public function testShouldRemoveCacheByMultiplePaths() - { - $this->filesystem->dumpFile( - $this->cacheRoot.'/thumbnail_web_path/images/cats.jpeg', - 'anImageContent' - ); - $this->filesystem->dumpFile( - $this->cacheRoot.'/thumbnail_default/images/cats.jpeg', - 'anImageContent' - ); - $this->filesystem->dumpFile( - $this->cacheRoot.'/thumbnail_web_path/images/cats2.jpeg', - 'anImageContent2' - ); - $this->filesystem->dumpFile( - $this->cacheRoot.'/thumbnail_default/images/cats2.jpeg', - 'anImageContent2' - ); - - $this->executeConsole( - new RemoveCacheCommand(), - array('paths' => array('images/cats.jpeg', 'images/cats2.jpeg')) - ); - - $this->assertFileNotExists($this->cacheRoot.'/thumbnail_web_path/images/cats.jpeg'); - $this->assertFileNotExists($this->cacheRoot.'/thumbnail_default/images/cats.jpeg'); - $this->assertFileNotExists($this->cacheRoot.'/thumbnail_web_path/images/cats2.jpeg'); - $this->assertFileNotExists($this->cacheRoot.'/thumbnail_default/images/cats2.jpeg'); - } - - public function testShouldRemoveCacheBySingleFilter() - { - $this->filesystem->dumpFile( - $this->cacheRoot.'/thumbnail_web_path/images/cats.jpeg', - 'anImageContent' - ); - $this->filesystem->dumpFile( - $this->cacheRoot.'/thumbnail_default/images/cats.jpeg', - 'anImageContent' - ); - $this->filesystem->dumpFile( - $this->cacheRoot.'/thumbnail_web_path/images/cats2.jpeg', - 'anImageContent2' - ); - $this->filesystem->dumpFile( - $this->cacheRoot.'/thumbnail_default/images/cats2.jpeg', - 'anImageContent2' - ); - - $this->executeConsole( - new RemoveCacheCommand(), - array('--filter' => array('thumbnail_default')) - ); - - $this->assertFileNotExists($this->cacheRoot.'/thumbnail_default/images/cats.jpeg'); - $this->assertFileNotExists($this->cacheRoot.'/thumbnail_default/images/cats2.jpeg'); - $this->assertFileExists($this->cacheRoot.'/thumbnail_web_path/images/cats.jpeg'); - $this->assertFileExists($this->cacheRoot.'/thumbnail_web_path/images/cats2.jpeg'); - } - - public function testShouldRemoveCacheByMultipleFilters() - { - $this->filesystem->dumpFile( - $this->cacheRoot.'/thumbnail_web_path/images/cats.jpeg', - 'anImageContent' - ); - $this->filesystem->dumpFile( - $this->cacheRoot.'/thumbnail_default/images/cats.jpeg', - 'anImageContent' - ); - $this->filesystem->dumpFile( - $this->cacheRoot.'/thumbnail_web_path/images/cats2.jpeg', - 'anImageContent2' - ); - $this->filesystem->dumpFile( - $this->cacheRoot.'/thumbnail_default/images/cats2.jpeg', - 'anImageContent2' - ); - - $this->executeConsole( - new RemoveCacheCommand(), - array('--filter' => array('thumbnail_default', 'thumbnail_web_path')) - ); - - $this->assertFileNotExists($this->cacheRoot.'/thumbnail_web_path/images/cats.jpeg'); - $this->assertFileNotExists($this->cacheRoot.'/thumbnail_default/images/cats.jpeg'); - $this->assertFileNotExists($this->cacheRoot.'/thumbnail_web_path/images/cats2.jpeg'); - $this->assertFileNotExists($this->cacheRoot.'/thumbnail_default/images/cats2.jpeg'); - } - - public function testShouldRemoveCacheByOnePathAndMultipleFilters() - { - $this->filesystem->dumpFile( - $this->cacheRoot.'/thumbnail_web_path/images/cats.jpeg', - 'anImageContent' - ); - $this->filesystem->dumpFile( - $this->cacheRoot.'/thumbnail_default/images/cats.jpeg', - 'anImageContent' - ); - $this->filesystem->dumpFile( - $this->cacheRoot.'/thumbnail_web_path/images/cats2.jpeg', - 'anImageContent2' - ); - - $this->executeConsole( - new RemoveCacheCommand(), - array( - 'paths' => array('images/cats.jpeg'), - '--filter' => array('thumbnail_default', 'thumbnail_web_path'), ) - ); - - $this->assertFileNotExists($this->cacheRoot.'/thumbnail_web_path/images/cats.jpeg'); - $this->assertFileNotExists($this->cacheRoot.'/thumbnail_default/images/cats.jpeg'); - $this->assertFileExists($this->cacheRoot.'/thumbnail_web_path/images/cats2.jpeg'); - } - - public function testShouldRemoveCacheByMultiplePathsAndSingleFilter() - { - $this->filesystem->dumpFile( - $this->cacheRoot.'/thumbnail_web_path/images/cats.jpeg', - 'anImageContent' - ); - $this->filesystem->dumpFile( - $this->cacheRoot.'/thumbnail_default/images/cats.jpeg', - 'anImageContent' - ); - $this->filesystem->dumpFile( - $this->cacheRoot.'/thumbnail_web_path/images/cats2.jpeg', - 'anImageContent2' - ); - - $this->executeConsole( - new RemoveCacheCommand(), - array( - 'paths' => array('images/cats.jpeg', 'images/cats2.jpeg'), - '--filter' => array('thumbnail_web_path'), ) - ); - - $this->assertFileNotExists($this->cacheRoot.'/thumbnail_web_path/images/cats.jpeg'); - $this->assertFileNotExists($this->cacheRoot.'/thumbnail_web_path/images/cats2.jpeg'); - $this->assertFileExists($this->cacheRoot.'/thumbnail_default/images/cats.jpeg'); - } - - /** - * @group legacy - * @expectedDeprecation As of 1.9, use of the "--filters" option has been deprecated in favor of "--filter" and will be removed in 2.0. - */ - public function testDeprecatedFiltersOption() - { - $this->executeConsole( - new RemoveCacheCommand(), - array( - 'paths' => array('images/cats.jpeg', 'images/cats2.jpeg'), - '--filters' => array('thumbnail_web_path'), ) - ); - } -} diff --git a/Tests/Functional/Command/ResolveCacheCommandTest.php b/Tests/Functional/Command/ResolveCacheCommandTest.php new file mode 100644 index 000000000..a9b87dfd5 --- /dev/null +++ b/Tests/Functional/Command/ResolveCacheCommandTest.php @@ -0,0 +1,306 @@ +putResolvedImages($images, $filters); + + $output = $this->executeResolveCacheCommand($images, $filters); + + $this->assertImagesExist($images, $filters); + $this->assertOutputContainsSkippedImages($output, $images, $filters); + $this->assertOutputContainsSummary($output, $images, $filters); + + $this->delResolvedImages($images, $filters); + } + + /** + * @return array + */ + public static function provideResolvesWhenPassedOnlyImagesData() + { + return CacheCommandFixtures::getAvailableFilterAndImageCombinations(); + } + + /** + * @param array $images + * @param array $filters + * + * @dataProvider provideResolvedWhenPassedPathsAndFiltersData + */ + public function testResolvesWhenPassedOnlyImages(array $images, array $filters) + { + $this->putResolvedImages($images, $filters); + + $output = $this->executeResolveCacheCommand($images, $filters); + + $this->assertImagesExist($images, $filters); + $this->assertOutputContainsSkippedImages($output, $images, $filters); + $this->assertOutputContainsSummary($output, $images, $filters); + + $this->delResolvedImages($images, $filters); + } + + /** + * @return array + */ + public static function provideResolvesWhenPassedPathsAndFiltersWithPartialCachesData() + { + $data = array_map(function (array $entry) { + $entry[] = CacheCommandFixtures::getImagesNotInArray($entry[0]); + $entry[] = CacheCommandFixtures::getFiltersNotInArray($entry[1]); + + return $entry; + }, CacheCommandFixtures::getAvailableFilterAndImageCombinations()); + + return array_filter($data, function (array $entry) { + return null !== $entry[2] && null !== $entry[3]; + }); + } + + /** + * @param array $images + * @param array $filters + * @param array $cachedImages + * @param array $cachedFilters + * + * @dataProvider provideResolvesWhenPassedPathsAndFiltersWithPartialCachesData + */ + public function testResolvesWhenPassedPathsAndFiltersWithPartialCaches(array $images, array $filters, array $cachedImages, array $cachedFilters) + { + $allImages = array_merge($images, $cachedImages); + $allFilters = array_merge($filters, $cachedFilters); + + $this->putResolvedImages($cachedImages, $cachedFilters); + + $this->assertImagesNotExist($images, $filters); + $this->assertImagesExist($cachedImages, $cachedFilters); + + $output = $this->executeResolveCacheCommand($allImages, $allFilters); + + $this->assertImagesExist($allImages, $allFilters); + $this->assertOutputContainsResolvedImages($output, $images, $filters); + $this->assertOutputContainsSkippedImages($output, $cachedImages, $cachedFilters); + $this->assertOutputContainsSummary($output, $allImages, $allFilters); + + $this->delResolvedImages($allImages, $allFilters); + } + + /** + * @return array + */ + public static function provideResolvesForcedAllWhenPassedPathsAndFiltersWithPartialCachesData() + { + return static::provideResolvesWhenPassedPathsAndFiltersWithPartialCachesData(); + } + + /** + * @param array $images + * @param array $filters + * @param array $cachedImages + * @param array $cachedFilters + * + * @dataProvider provideResolvesForcedAllWhenPassedPathsAndFiltersWithPartialCachesData + */ + public function testForcedResolveWhenPassedPathsAndFiltersWithPartialCaches(array $images, array $filters, array $cachedImages, array $cachedFilters) + { + $allImages = array_merge($images, $cachedImages); + $allFilters = array_merge($filters, $cachedFilters); + + $this->putResolvedImages($cachedImages, $cachedFilters); + + $this->assertImagesNotExist($images, $filters); + $this->assertImagesExist($cachedImages, $cachedFilters); + + $output = $this->executeResolveCacheCommand($allImages, $allFilters, array('--force' => true)); + + $this->assertImagesExist($allImages, $allFilters); + $this->assertOutputContainsResolvedImages($output, $allImages, $allFilters); + $this->assertOutputContainsSummary($output, $allImages, $allFilters); + + $this->delResolvedImages($allImages, $allFilters); + } + + /** + * @return array + */ + public static function provideResolveShowsFailureAndContinuesWhenPassedInvalidFiltersData() + { + return array_map(function (array $entry) { + $entry[] = CacheCommandFixtures::getInvalidFilters(); + + return $entry; + }, CacheCommandFixtures::getAvailableFilterAndImageCombinations()); + } + + /** + * @param array $images + * @param array $filters + * @param array $invalidFilters + * + * @dataProvider provideResolveShowsFailureAndContinuesWhenPassedInvalidFiltersData + */ + public function testResolveShowsFailureAndContinuesWhenPassedInvalidFilters(array $images, array $filters, array $invalidFilters) + { + $allFilters = array_merge($filters, $invalidFilters); + + $this->assertImagesNotExist($images, $allFilters); + + $return = null; + $output = $this->executeResolveCacheCommand($images, $allFilters, array(), $return); + + $this->assertSame(255, $return); + $this->assertImagesExist($images, $filters); + $this->assertImagesNotExist($images, $invalidFilters); + $this->assertOutputContainsResolvedImages($output, $images, $filters); + $this->assertOutputContainsFailedImages($output, $images, $invalidFilters); + $this->assertOutputContainsSummary($output, $images, $allFilters, count($images) * count($invalidFilters)); + + $this->delResolvedImages($images, $allFilters); + } + + /** + * @return array + */ + public static function provideResolveShowsFailureAndContinuesWhenPassedInvalidPathsData() + { + return array_map(function (array $entry) { + $entry[] = CacheCommandFixtures::getInvalidImages(); + + return $entry; + }, CacheCommandFixtures::getAvailableFilterAndImageCombinations()); + } + + /** + * @param array $images + * @param array $filters + * @param array $invalidImages + * + * @dataProvider provideResolveShowsFailureAndContinuesWhenPassedInvalidPathsData + */ + public function testResolveShowsFailureAndContinuesWhenPassedInvalidPaths(array $images, array $filters, array $invalidImages) + { + $allImages = array_merge($images, $invalidImages); + + $this->assertImagesNotExist($allImages, $filters); + + $return = null; + $output = $this->executeResolveCacheCommand($allImages, $filters, array(), $return); + + $this->assertSame(255, $return); + $this->assertImagesExist($images, $filters); + $this->assertImagesNotExist($invalidImages, $filters); + $this->assertOutputContainsResolvedImages($output, $images, $filters); + $this->assertOutputContainsFailedImages($output, $invalidImages, $filters); + $this->assertOutputContainsSummary($output, $allImages, $filters, count($filters) * count($invalidImages)); + + $this->delResolvedImages($allImages, $filters); + } + + /** + * @return array + */ + public static function provideResolveOutputsMachineParseableTextWhenPassedMachineReadableOptionData() + { + return CacheCommandFixtures::getAvailableFilterAndImageCombinations(); + } + + /** + * @param array $images + * @param array $filters + * + * @dataProvider provideResolveOutputsMachineParseableTextWhenPassedMachineReadableOptionData + */ + public function testResolveOutputsMachineParseableTextWhenPassedMachineReadableOption(array $images, array $filters) + { + $this->assertImagesNotExist($images, $filters); + + $output = $this->executeResolveCacheCommand($images, $filters, array('--machine-readable' => true)); + + $this->assertImagesExist($images, $filters); + $this->assertNotContains('[liip/imagine-bundle]', $output); + $this->assertNotContains('=====================', $output); + $this->assertOutputContainsResolvedImages($output, $images, $filters); + $this->assertOutputNotContainsSummary($output, $images, $filters); + + $this->delResolvedImages($images, $filters); + } + + /** + * @return array + */ + public static function provideResolveEmitsDeprecationMessageWhenUsingLegacyFiltersOptionData() + { + return CacheCommandFixtures::getAvailableFilterAndImageCombinations(); + } + + /** + * @dataProvider provideResolveEmitsDeprecationMessageWhenUsingLegacyFiltersOptionData + * + * @group legacy + * @expectedDeprecation The --filters option was deprecated in 1.9.0 and removed in 2.0.0. Use the --filter option instead. + */ + public function testResolveEmitsDeprecationMessageWhenUsingLegacyFiltersOption(array $images, array $filters) + { + $this->assertImagesNotExist($images, $filters); + + $output = $this->executeResolveCacheCommand($images, array(), array('paths' => $images, '--filters' => $filters)); + + $this->assertImagesExist($images, $filters); + $this->assertOutputContainsResolvedImages($output, $images, $filters); + + $this->delResolvedImages($images, $filters); + } + + /** + * @param string[] $paths + * @param string[] $filters + * @param string[] $additionalOptions + * @param int $return + * + * @return string + */ + protected function executeResolveCacheCommand(array $paths, array $filters = array(), array $additionalOptions = array(), &$return = null) + { + $options = array_merge(array('paths' => $paths), $additionalOptions); + + if (0 < count($filters)) { + $options['--filter'] = $filters; + } + + return $this->executeConsole(new ResolveCacheCommand(), $options, $return); + } +} diff --git a/Tests/Functional/Command/ResolveCacheTest.php b/Tests/Functional/Command/ResolveCacheTest.php deleted file mode 100644 index 822944812..000000000 --- a/Tests/Functional/Command/ResolveCacheTest.php +++ /dev/null @@ -1,369 +0,0 @@ -assertImagesNotExist($images, $filters); - - $return = null; - $output = $this->executeResolveCacheCommand($images, $filters, array(), $return); - - $this->assertSame(0, $return); - $this->assertImagesExist($images, $filters); - $this->assertImagesNotExist($images, array('thumbnail_default')); - $this->assertOutputContainsResolvedImages($output, $images, $filters); - $this->assertOutputContainsSummary($output, $images, $filters); - - $this->delResolvedImages($images, $filters); - } - - public function testShouldResolveWithCacheExists() - { - $images = array('images/cats.jpeg'); - $filters = array('thumbnail_web_path'); - - $this->putResolvedImages($images, $filters); - - $output = $this->executeResolveCacheCommand($images, $filters); - - $this->assertImagesExist($images, $filters); - $this->assertImagesNotExist($images, array('thumbnail_default')); - $this->assertOutputContainsCachedImages($output, $images, $filters); - $this->assertOutputContainsSummary($output, $images, $filters); - - $this->delResolvedImages($images, $filters); - } - - public function testShouldResolveWithFewPathsAndSingleFilter() - { - $images = array('images/cats.jpeg', 'images/cats2.jpeg'); - $filters = array('thumbnail_web_path'); - - $output = $this->executeResolveCacheCommand($images, $filters); - - $this->assertImagesExist($images, $filters); - $this->assertOutputContainsResolvedImages($output, $images, $filters); - - $output = $this->executeResolveCacheCommand($images, $filters); - - $this->assertImagesExist($images, $filters); - $this->assertOutputContainsCachedImages($output, $images, $filters); - $this->assertOutputContainsSummary($output, $images, $filters); - - $this->delResolvedImages($images, $filters); - } - - public function testShouldResolveWithFewPathsSingleFilterAndPartiallyFullCache() - { - $imagesResolved = array('images/cats.jpeg'); - $imagesCached = array('images/cats2.jpeg'); - $images = array_merge($imagesResolved, $imagesCached); - $filters = array('thumbnail_web_path'); - - $this->putResolvedImages($imagesCached, $filters); - - $this->assertImagesNotExist($imagesResolved, $filters); - $this->assertImagesExist($imagesCached, $filters); - - $output = $this->executeResolveCacheCommand($images, $filters); - - $this->assertImagesExist($images, $filters); - $this->assertOutputContainsResolvedImages($output, $imagesResolved, $filters); - $this->assertOutputContainsCachedImages($output, $imagesCached, $filters); - $this->assertOutputContainsSummary($output, $images, $filters); - - $this->delResolvedImages($images, $filters); - } - - public function testShouldResolveWithFewPathsAndFewFilters() - { - $images = array('images/cats.jpeg', 'images/cats2.jpeg'); - $filters = array('thumbnail_web_path', 'thumbnail_default'); - - $output = $this->executeResolveCacheCommand($images, $filters); - - $this->assertImagesExist($images, $filters); - $this->assertOutputContainsResolvedImages($output, $images, $filters); - $this->assertOutputContainsSummary($output, $images, $filters); - - $this->delResolvedImages($images, $filters); - } - - public function testShouldResolveWithFewPathsAndWithoutFilters() - { - $images = array('images/cats.jpeg', 'images/cats2.jpeg'); - $filters = array('thumbnail_web_path', 'thumbnail_default'); - - $output = $this->executeResolveCacheCommand($images); - - $this->assertImagesExist($images, $filters); - $this->assertOutputContainsResolvedImages($output, $images, $filters); - $this->assertOutputContainsSummary($output, $images, $filters); - - $this->delResolvedImages($images, $filters); - } - - public function testCachedAndForceResolve() - { - $images = array('images/cats.jpeg', 'images/cats2.jpeg'); - $filters = array('thumbnail_web_path', 'thumbnail_default'); - - $this->assertImagesNotExist($images, $filters); - $this->putResolvedImages($images, $filters); - $this->assertImagesExist($images, $filters); - - $output = $this->executeResolveCacheCommand($images, $filters); - - $this->assertImagesExist($images, $filters); - $this->assertOutputContainsCachedImages($output, $images, $filters); - - $output = $this->executeResolveCacheCommand($images, $filters, array('--force' => true)); - - $this->assertImagesExist($images, $filters); - $this->assertOutputContainsResolvedImages($output, $images, $filters); - $this->assertOutputContainsSummary($output, $images, $filters); - - $this->delResolvedImages($images, $filters); - } - - public function testFailedResolve() - { - $images = array('images/cats.jpeg', 'images/cats2.jpeg'); - $filters = array('does_not_exist'); - - $this->assertImagesNotExist($images, $filters); - - $return = null; - $output = $this->executeResolveCacheCommand($images, $filters, array(), $return); - - $this->assertSame(255, $return); - $this->assertImagesNotExist($images, $filters); - $this->assertOutputContainsFailedImages($output, $images, $filters); - $this->assertOutputContainsSummary($output, $images, $filters, 2); - - $this->delResolvedImages($images, $filters); - } - - public function testMachineOption() - { - $images = array('images/cats.jpeg', 'images/cats2.jpeg'); - $filters = array('does_not_exist'); - - $this->assertImagesNotExist($images, $filters); - - $output = $this->executeResolveCacheCommand($images, $filters, array('--as-script' => true)); - - $this->assertImagesNotExist($images, $filters); - $this->assertNotContains('liip/imagine-bundle (cache resolver)', $output); - $this->assertNotContains('====================================', $output); - $this->assertOutputContainsFailedImages($output, $images, $filters); - $this->assertOutputNotContainsSummary($output, $images, $filters, 2); - - $this->delResolvedImages($images, $filters); - } - - /** - * @group legacy - * @expectedDeprecation As of 1.9, use of the "--filters" option has been deprecated in favor of "--filter" and will be removed in 2.0. - */ - public function testDeprecatedFiltersOption() - { - $images = array('images/cats.jpeg'); - $filters = array('thumbnail_web_path'); - - $this->assertImagesNotExist($images, $filters); - - $output = $this->executeConsole(new ResolveCacheCommand(), array('paths' => $images, '--filters' => $filters)); - - $this->assertImagesExist($images, $filters); - $this->assertOutputContainsResolvedImages($output, $images, $filters); - - $this->delResolvedImages($images, $filters); - } - - /** - * @param string[] $paths - * @param string[] $filters - * @param string[] $additionalOptions - * @param int $return - * - * @return string - */ - private function executeResolveCacheCommand(array $paths, array $filters = array(), array $additionalOptions = array(), &$return = null) - { - $options = array_merge(array('paths' => $paths), $additionalOptions); - - if (0 < count($filters)) { - $options['--filter'] = $filters; - } - - return $this->executeConsole(new ResolveCacheCommand(), $options, $return); - } - - /** - * @param string[] $images - * @param string[] $filters - */ - private function assertImagesNotExist($images, $filters) - { - foreach ($images as $i) { - foreach ($filters as $f) { - $this->assertFileNotExists(sprintf('%s/%s/%s', $this->cacheRoot, $f, $i)); - } - } - } - - /** - * @param string[] $images - * @param string[] $filters - */ - private function assertImagesExist($images, $filters) - { - foreach ($images as $i) { - foreach ($filters as $f) { - $this->assertFileExists(sprintf('%s/%s/%s', $this->cacheRoot, $f, $i)); - } - } - } - - /** - * @param string $output - * @param array $images - * @param array $filters - */ - private function assertOutputContainsResolvedImages($output, array $images, array $filters) - { - foreach ($images as $i) { - foreach ($filters as $f) { - $this->assertOutputContainsImage($output, $i, $f, 'resolved'); - } - } - } - - /** - * @param string $output - * @param array $images - * @param array $filters - */ - private function assertOutputContainsCachedImages($output, array $images, array $filters) - { - foreach ($images as $i) { - foreach ($filters as $f) { - $this->assertOutputContainsImage($output, $i, $f, 'cached'); - } - } - } - - /** - * @param string $output - * @param array $images - * @param array $filters - */ - private function assertOutputContainsFailedImages($output, array $images, array $filters) - { - foreach ($images as $i) { - foreach ($filters as $f) { - $this->assertContains(sprintf('%s[%s] failed: ', $i, $f), $output); - } - } - } - - /** - * @param string $output - * @param string $image - * @param string $filter - * @param string $type - */ - private function assertOutputContainsImage($output, $image, $filter, $type) - { - $expected = vsprintf('%s[%s] %s: http://localhost/media/cache/%s/%s', array( - $image, - $filter, - $type, - $filter, - $image, - )); - $this->assertContains($expected, $output); - } - - /** - * @param string $output - * @param string[] $images - * @param string[] $filters - * @param int $failures - */ - private function assertOutputContainsSummary($output, array $images, array $filters, $failures = 0) - { - $this->assertContains(sprintf('Completed %d operation', (count($images) * count($filters)) - $failures), $output); - $this->assertContains(sprintf('%d image', count($images)), $output); - $this->assertContains(sprintf('%d filter', count($filters)), $output); - if (0 !== $failures) { - $this->assertContains(sprintf('%d failure', $failures), $output); - } - } - - /** - * @param string $output - * @param string[] $images - * @param string[] $filters - * @param int $failures - */ - private function assertOutputNotContainsSummary($output, array $images, array $filters, $failures = 0) - { - $this->assertNotContains(sprintf('Completed %d operation', (count($images) * count($filters)) - $failures), $output); - $this->assertNotContains(sprintf('%d image', count($images)), $output); - $this->assertNotContains(sprintf('%d filter', count($filters)), $output); - if (0 !== $failures) { - $this->assertNotContains(sprintf('%d failure', $failures), $output); - } - } - - /** - * @param string[] $images - * @param string[] $filters - */ - private function delResolvedImages(array $images, array $filters) - { - foreach ($images as $i) { - foreach ($filters as $f) { - if (file_exists($f = sprintf('%s/%s/%s', $this->cacheRoot, $f, $i))) { - @unlink($f); - } - } - } - } - - /** - * @param string[] $images - * @param string[] $filters - * @param string $content - */ - private function putResolvedImages(array $images, array $filters, $content = 'anImageContent') - { - foreach ($images as $i) { - foreach ($filters as $f) { - $this->filesystem->dumpFile(sprintf('%s/%s/%s', $this->cacheRoot, $f, $i), $content); - } - } - } -} diff --git a/Tests/Functional/Controller/ImagineControllerTest.php b/Tests/Functional/Controller/ImagineControllerTest.php index 14301a439..426014bab 100644 --- a/Tests/Functional/Controller/ImagineControllerTest.php +++ b/Tests/Functional/Controller/ImagineControllerTest.php @@ -30,44 +30,44 @@ public function testCouldBeGetFromContainer() public function testShouldResolvePopulatingCacheFirst() { //guard - $this->assertFileNotExists($this->cacheRoot.'/thumbnail_web_path/images/cats.jpeg'); + $this->assertFileNotExists($this->cacheRoot.'/profile_thumb_sm/images/cats.jpeg'); - $this->client->request('GET', '/media/cache/resolve/thumbnail_web_path/images/cats.jpeg'); + $this->client->request('GET', '/media/cache/resolve/profile_thumb_sm/images/cats.jpeg'); $response = $this->client->getResponse(); $this->assertInstanceOf('\Symfony\Component\HttpFoundation\RedirectResponse', $response); $this->assertEquals(302, $response->getStatusCode()); - $this->assertEquals('http://localhost/media/cache/thumbnail_web_path/images/cats.jpeg', $response->getTargetUrl()); + $this->assertEquals('http://localhost/media/cache/profile_thumb_sm/images/cats.jpeg', $response->getTargetUrl()); - $this->assertFileExists($this->cacheRoot.'/thumbnail_web_path/images/cats.jpeg'); + $this->assertFileExists($this->cacheRoot.'/profile_thumb_sm/images/cats.jpeg'); } public function testShouldResolveFromCache() { $this->filesystem->dumpFile( - $this->cacheRoot.'/thumbnail_web_path/images/cats.jpeg', + $this->cacheRoot.'/profile_thumb_sm/images/cats.jpeg', 'anImageContent' ); - $this->client->request('GET', '/media/cache/resolve/thumbnail_web_path/images/cats.jpeg'); + $this->client->request('GET', '/media/cache/resolve/profile_thumb_sm/images/cats.jpeg'); $response = $this->client->getResponse(); $this->assertInstanceOf('\Symfony\Component\HttpFoundation\RedirectResponse', $response); $this->assertEquals(302, $response->getStatusCode()); - $this->assertEquals('http://localhost/media/cache/thumbnail_web_path/images/cats.jpeg', $response->getTargetUrl()); + $this->assertEquals('http://localhost/media/cache/profile_thumb_sm/images/cats.jpeg', $response->getTargetUrl()); - $this->assertFileExists($this->cacheRoot.'/thumbnail_web_path/images/cats.jpeg'); + $this->assertFileExists($this->cacheRoot.'/profile_thumb_sm/images/cats.jpeg'); } /** * @expectedException \Symfony\Component\HttpKernel\Exception\BadRequestHttpException - * @expectedExceptionMessage Signed url does not pass the sign check for path "images/cats.jpeg" and filter "thumbnail_web_path" and runtime config {"thumbnail":{"size":["50","50"]}} + * @expectedExceptionMessage Signed url does not pass the sign check for path "images/cats.jpeg" and filter "profile_thumb_sm" and runtime config {"thumbnail":{"size":["50","50"]}} */ public function testThrowBadRequestIfSignInvalidWhileUsingCustomFilters() { - $this->client->request('GET', '/media/cache/resolve/thumbnail_web_path/rc/invalidHash/images/cats.jpeg?'.http_build_query(array( + $this->client->request('GET', '/media/cache/resolve/profile_thumb_sm/rc/invalidHash/images/cats.jpeg?'.http_build_query(array( 'filters' => array( 'thumbnail' => array('size' => array(50, 50)), ), @@ -81,7 +81,7 @@ public function testThrowBadRequestIfSignInvalidWhileUsingCustomFilters() */ public function testShouldThrowNotFoundHttpExceptionIfFiltersNotArray() { - $this->client->request('GET', '/media/cache/resolve/thumbnail_web_path/rc/invalidHash/images/cats.jpeg?'.http_build_query(array( + $this->client->request('GET', '/media/cache/resolve/profile_thumb_sm/rc/invalidHash/images/cats.jpeg?'.http_build_query(array( 'filters' => 'some-string', '_hash' => 'hash', ))); @@ -93,7 +93,7 @@ public function testShouldThrowNotFoundHttpExceptionIfFiltersNotArray() */ public function testShouldThrowNotFoundHttpExceptionIfFileNotExists() { - $this->client->request('GET', '/media/cache/resolve/thumbnail_web_path/images/shrodinger_cats_which_not_exist.jpeg'); + $this->client->request('GET', '/media/cache/resolve/profile_thumb_sm/images/shrodinger_cats_which_not_exist.jpeg'); } /** @@ -119,7 +119,7 @@ public function testShouldResolveWithCustomFiltersPopulatingCacheFirst() $hash = $signer->sign($path, $params['filters']); - $expectedCachePath = 'thumbnail_web_path/rc/'.$hash.'/'.$path; + $expectedCachePath = 'profile_thumb_sm/rc/'.$hash.'/'.$path; $url = 'http://localhost/media/cache/resolve/'.$expectedCachePath.'?'.http_build_query($params); @@ -152,7 +152,7 @@ public function testShouldResolveWithCustomFiltersFromCache() $hash = $signer->sign($path, $params['filters']); - $expectedCachePath = 'thumbnail_web_path/rc/'.$hash.'/'.$path; + $expectedCachePath = 'profile_thumb_sm/rc/'.$hash.'/'.$path; $url = 'http://localhost/media/cache/resolve/'.$expectedCachePath.'?'.http_build_query($params); @@ -175,20 +175,20 @@ public function testShouldResolveWithCustomFiltersFromCache() public function testShouldResolvePathWithSpecialCharactersAndWhiteSpaces() { $this->filesystem->dumpFile( - $this->cacheRoot.'/thumbnail_web_path/images/foo bar.jpeg', + $this->cacheRoot.'/profile_thumb_sm/images/foo bar.jpeg', 'anImageContent' ); // we are calling url with encoded file name as it will be called by browser $urlEncodedFileName = 'foo+bar'; - $this->client->request('GET', '/media/cache/resolve/thumbnail_web_path/images/'.$urlEncodedFileName.'.jpeg'); + $this->client->request('GET', '/media/cache/resolve/profile_thumb_sm/images/'.$urlEncodedFileName.'.jpeg'); $response = $this->client->getResponse(); $this->assertInstanceOf('\Symfony\Component\HttpFoundation\RedirectResponse', $response); $this->assertEquals(302, $response->getStatusCode()); - $this->assertEquals('http://localhost/media/cache/thumbnail_web_path/images/foo bar.jpeg', $response->getTargetUrl()); + $this->assertEquals('http://localhost/media/cache/profile_thumb_sm/images/foo bar.jpeg', $response->getTargetUrl()); - $this->assertFileExists($this->cacheRoot.'/thumbnail_web_path/images/foo bar.jpeg'); + $this->assertFileExists($this->cacheRoot.'/profile_thumb_sm/images/foo bar.jpeg'); } } diff --git a/Tests/Functional/Fixtures/BarBundle/Resources/public/cats-bar-bundle.jpg b/Tests/Functional/Fixtures/BarBundle/Resources/public/cats-bar-bundle.jpg new file mode 100644 index 0000000000000000000000000000000000000000..bc458e3508af75192b1b7c5d03e9f46206a18d88 GIT binary patch literal 34087 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 CN;`=F literal 0 HcmV?d00001 diff --git a/Tests/Functional/Fixtures/CacheCommandFixtures.php b/Tests/Functional/Fixtures/CacheCommandFixtures.php new file mode 100644 index 000000000..9ff8bdd7a --- /dev/null +++ b/Tests/Functional/Fixtures/CacheCommandFixtures.php @@ -0,0 +1,99 @@ +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 CN;`=F literal 0 HcmV?d00001 diff --git a/Tests/Functional/app/config/config.yml b/Tests/Functional/app/config/config.yml index 41b99282a..a1f92db59 100644 --- a/Tests/Functional/app/config/config.yml +++ b/Tests/Functional/app/config/config.yml @@ -26,6 +26,14 @@ liip_imagine: loaders: default: + chain: + loaders: + - web + - foo + - bar + - bundles_all + + web: filesystem: data_root: "%kernel.root_dir%/web" @@ -37,14 +45,6 @@ liip_imagine: filesystem: data_root: "%kernel.root_dir%/../../Fixtures/FileSystemLocator/root-02" - baz: - chain: - loaders: - - foo - - bar - - default - - bundles_all - bundles_all: filesystem: data_root: ~ @@ -76,14 +76,22 @@ liip_imagine: filter_sets: - thumbnail_web_path: + profile_thumb_sm: filters: - thumbnail: { size: [223, 223], mode: inset } + thumbnail: + size: [100, 100] + mode: inset - thumbnail_default: + profile_thumb_lg: filters: - thumbnail: { size: [223, 223], mode: inset } + thumbnail: + size: [200, 200] + mode: inset + profile_main: + filters: + scale: + dim: [300, 300] controller: redirect_response_code: 302 diff --git a/Tests/Imagine/Filter/Loader/ResampleFilterLoaderTest.php b/Tests/Imagine/Filter/Loader/ResampleFilterLoaderTest.php index 81d6833c0..82974b85a 100644 --- a/Tests/Imagine/Filter/Loader/ResampleFilterLoaderTest.php +++ b/Tests/Imagine/Filter/Loader/ResampleFilterLoaderTest.php @@ -60,7 +60,7 @@ public static function provideResampleData() $resolutions = array( 72.0, 120.0, - 240.0 + 240.0, ); $data = array(); diff --git a/UPGRADE.md b/UPGRADE.md index b68000daa..4cb2f0ddb 100644 --- a/UPGRADE.md +++ b/UPGRADE.md @@ -7,6 +7,21 @@ All important upgrade requirements will be enumerated in this This project adheres to [semantic versioning](http://semver.org/spec/v2.0.0.html). +## v1.9.1 + +*View the [changelog entry](https://github.com/liip/LiipImagineBundle/blob/1.0/CHANGELOG.md#v191) for the `1.9.1` release.* + + - __\[Console\]__ __\[BC BREAK\]__ The resolve command's `--as-script`/`-s` option name/shortcut conflicted with Symfony + 2.x core console options (specifically `--shell`/`-s`) and has been renamed to `--machine-readable`/`-m` + \(fixes [\#988](https://github.com/liip/LiipImagineBundle/pull/988)\). The `-s` option shortcut was the only conflict, + but the `--as-script` option name proved confusing and unclear so it too was renamed. + + - __\[Console\]__ The output formatting for the `remove` command has been updated and aligned with the behavior + previously introduced in `1.9.0` for the `resolve` command, making both of them consistent and in-line with the + expected `2.0.0` output. The `--machine-readable`/`-m` option name/shortcut has now been added to the `remove` command + as well, enabling predictable, consistent, script parseable output stripped of text styles and supplemental formatting. + + ## v1.9.0 *View the [changelog entry](https://github.com/liip/LiipImagineBundle/blob/1.0/CHANGELOG.md#v190) for the `1.9.0` release.*