From 6e50f2c22ff0afdc1c42520845dbb36de36b465a Mon Sep 17 00:00:00 2001 From: RomainMazB Date: Tue, 8 Jun 2021 22:20:59 +0200 Subject: [PATCH 01/21] Add plugin:test command --- modules/system/ServiceProvider.php | 1 + modules/system/console/PluginTest.php | 71 +++++++++++++++++++++++++++ 2 files changed, 72 insertions(+) create mode 100644 modules/system/console/PluginTest.php diff --git a/modules/system/ServiceProvider.php b/modules/system/ServiceProvider.php index aa84442da1..45775e1eb7 100644 --- a/modules/system/ServiceProvider.php +++ b/modules/system/ServiceProvider.php @@ -270,6 +270,7 @@ protected function registerConsole() $this->registerConsoleCommand('plugin.refresh', 'System\Console\PluginRefresh'); $this->registerConsoleCommand('plugin.rollback', 'System\Console\PluginRollback'); $this->registerConsoleCommand('plugin.list', 'System\Console\PluginList'); + $this->registerConsoleCommand('plugin.test', 'System\Console\PluginTest'); $this->registerConsoleCommand('theme.install', 'System\Console\ThemeInstall'); $this->registerConsoleCommand('theme.remove', 'System\Console\ThemeRemove'); diff --git a/modules/system/console/PluginTest.php b/modules/system/console/PluginTest.php new file mode 100644 index 0000000000..5faf1733de --- /dev/null +++ b/modules/system/console/PluginTest.php @@ -0,0 +1,71 @@ +argument('name'); + $pluginName = PluginManager::instance()->normalizeIdentifier($pluginName); + if (!PluginManager::instance()->exists($pluginName)) { + throw new \InvalidArgumentException(sprintf('Plugin "%s" not found.', $pluginName)); + } + + $pluginDir = PluginManager::instance()->getPluginPath($pluginName); + + if (!file_exists($pluginDir . '/phpunit.xml')) { + throw new \InvalidArgumentException(sprintf('phpunit.xml file not found in "%s".', $pluginDir)); + } + + /* + * Test plugin + */ + $exec = 'cd ' . $pluginDir . ' && '; + $exec .= '../../../vendor/bin/phpunit'; + $this->output->writeln(sprintf('Running plugin\'s tests: %s.', $pluginName)); + echo shell_exec($exec); + } + + /** + * Get the console command arguments. + * @return array + */ + protected function getArguments() + { + return [ + ['name', InputArgument::REQUIRED, 'The name of the plugin. Eg: AuthorName.PluginName'], + ]; + } +} From cea9433defd88bd5ff52cd3f6c04989e7c38124b Mon Sep 17 00:00:00 2001 From: RomainMazB Date: Tue, 8 Jun 2021 22:22:36 +0200 Subject: [PATCH 02/21] Remove author name --- modules/system/console/PluginTest.php | 1 - 1 file changed, 1 deletion(-) diff --git a/modules/system/console/PluginTest.php b/modules/system/console/PluginTest.php index 5faf1733de..35a95f4671 100644 --- a/modules/system/console/PluginTest.php +++ b/modules/system/console/PluginTest.php @@ -11,7 +11,6 @@ * This searches for a phpunit.xml file into the plugin's directory and run its tests * * @package winter\wn-system-module - * @author Alexey Bobkov, Samuel Georges */ class PluginTest extends Command { From 5ed06e06e4badfdcb6a4868c6143b089bb6e53ef Mon Sep 17 00:00:00 2001 From: RomainMazB Date: Wed, 30 Jun 2021 21:35:34 +0200 Subject: [PATCH 03/21] rename to winter:test and support core tests --- modules/system/ServiceProvider.php | 2 +- modules/system/console/PluginTest.php | 70 ---------------------- modules/system/console/WinterTest.php | 83 +++++++++++++++++++++++++++ 3 files changed, 84 insertions(+), 71 deletions(-) delete mode 100644 modules/system/console/PluginTest.php create mode 100644 modules/system/console/WinterTest.php diff --git a/modules/system/ServiceProvider.php b/modules/system/ServiceProvider.php index 45775e1eb7..2c85ff576a 100644 --- a/modules/system/ServiceProvider.php +++ b/modules/system/ServiceProvider.php @@ -262,6 +262,7 @@ protected function registerConsole() $this->registerConsoleCommand('winter.passwd', 'System\Console\WinterPasswd'); $this->registerConsoleCommand('winter.version', 'System\Console\WinterVersion'); $this->registerConsoleCommand('winter.manifest', 'System\Console\WinterManifest'); + $this->registerConsoleCommand('winter.test', 'System\Console\WinterTest'); $this->registerConsoleCommand('plugin.install', 'System\Console\PluginInstall'); $this->registerConsoleCommand('plugin.remove', 'System\Console\PluginRemove'); @@ -270,7 +271,6 @@ protected function registerConsole() $this->registerConsoleCommand('plugin.refresh', 'System\Console\PluginRefresh'); $this->registerConsoleCommand('plugin.rollback', 'System\Console\PluginRollback'); $this->registerConsoleCommand('plugin.list', 'System\Console\PluginList'); - $this->registerConsoleCommand('plugin.test', 'System\Console\PluginTest'); $this->registerConsoleCommand('theme.install', 'System\Console\ThemeInstall'); $this->registerConsoleCommand('theme.remove', 'System\Console\ThemeRemove'); diff --git a/modules/system/console/PluginTest.php b/modules/system/console/PluginTest.php deleted file mode 100644 index 35a95f4671..0000000000 --- a/modules/system/console/PluginTest.php +++ /dev/null @@ -1,70 +0,0 @@ -argument('name'); - $pluginName = PluginManager::instance()->normalizeIdentifier($pluginName); - if (!PluginManager::instance()->exists($pluginName)) { - throw new \InvalidArgumentException(sprintf('Plugin "%s" not found.', $pluginName)); - } - - $pluginDir = PluginManager::instance()->getPluginPath($pluginName); - - if (!file_exists($pluginDir . '/phpunit.xml')) { - throw new \InvalidArgumentException(sprintf('phpunit.xml file not found in "%s".', $pluginDir)); - } - - /* - * Test plugin - */ - $exec = 'cd ' . $pluginDir . ' && '; - $exec .= '../../../vendor/bin/phpunit'; - $this->output->writeln(sprintf('Running plugin\'s tests: %s.', $pluginName)); - echo shell_exec($exec); - } - - /** - * Get the console command arguments. - * @return array - */ - protected function getArguments() - { - return [ - ['name', InputArgument::REQUIRED, 'The name of the plugin. Eg: AuthorName.PluginName'], - ]; - } -} diff --git a/modules/system/console/WinterTest.php b/modules/system/console/WinterTest.php new file mode 100644 index 0000000000..d9ed18dabe --- /dev/null +++ b/modules/system/console/WinterTest.php @@ -0,0 +1,83 @@ +argument('plugin')) { + $pluginManager = PluginManager::instance(); + $pluginName = $pluginManager->normalizeIdentifier($pluginArgument); + + if (!$pluginManager->exists($pluginName)) { + throw new \InvalidArgumentException(sprintf('Plugin "%s" not found.', $pluginName)); + } + + $pluginDir = $pluginManager->getPluginPath($pluginName); + + if (!file_exists($pluginDir . '/phpunit.xml')) { + throw new \InvalidArgumentException(sprintf('phpunit.xml file not found in "%s".', $pluginDir)); + } + + /* + * Test plugin + */ + $exec = 'cd ' . $pluginDir . ' && '; + $exec .= '../../../vendor/bin/phpunit'; + $this->output->writeln(sprintf('Running plugin\'s tests: %s.', $pluginName)); + } + + // Else run winter's tests + else { + $exec = './vendor/bin/phpunit'; + $this->output->writeln('Running Winter\'s tests.'); + } + + echo shell_exec($exec); + } + + /** + * Get the console command arguments. + * @return array + */ + protected function getArguments() + { + return [ + ['plugin', InputArgument::OPTIONAL, 'The name of the plugin. Eg: AuthorName.PluginName'], + ]; + } +} From dc7c7d1205aefc32d081fa8ebda6437ae0d41cf1 Mon Sep 17 00:00:00 2001 From: RomainMazB Date: Wed, 30 Jun 2021 21:53:35 +0200 Subject: [PATCH 04/21] Use Process to run live-tests --- modules/system/console/WinterTest.php | 32 +++++++++++++++++---------- 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/modules/system/console/WinterTest.php b/modules/system/console/WinterTest.php index d9ed18dabe..01e26a5bf2 100644 --- a/modules/system/console/WinterTest.php +++ b/modules/system/console/WinterTest.php @@ -1,6 +1,8 @@ getPluginPath($pluginName); - if (!file_exists($pluginDir . '/phpunit.xml')) { + $pluginPHPUnitXMLFile = $pluginDir . '/phpunit.xml'; + if (!file_exists($pluginPHPUnitXMLFile)) { throw new \InvalidArgumentException(sprintf('phpunit.xml file not found in "%s".', $pluginDir)); } /* * Test plugin */ - $exec = 'cd ' . $pluginDir . ' && '; - $exec .= '../../../vendor/bin/phpunit'; + $this->exec[] = "--configuration=$pluginPHPUnitXMLFile"; $this->output->writeln(sprintf('Running plugin\'s tests: %s.', $pluginName)); } // Else run winter's tests else { - $exec = './vendor/bin/phpunit'; - $this->output->writeln('Running Winter\'s tests.'); + $this->output->writeln('Running Winter\'s core tests.'); } - echo shell_exec($exec); + $process = (new Process($this->exec))->setTimeout(null); + + try { + return $process->run(function ($type, $line) { + $this->output->write($line); + }); + } catch (ProcessSignaledException $e) { + if (extension_loaded('pcntl') && $e->getSignal() !== SIGINT) { + throw $e; + } + } } /** From 553510965177836d03b2dc39ca8f82ff03f283a4 Mon Sep 17 00:00:00 2001 From: RomainMazB Date: Wed, 30 Jun 2021 22:54:09 +0200 Subject: [PATCH 05/21] Proxy all options to phpunit --- modules/system/console/WinterTest.php | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/modules/system/console/WinterTest.php b/modules/system/console/WinterTest.php index 01e26a5bf2..ecd748b6e8 100644 --- a/modules/system/console/WinterTest.php +++ b/modules/system/console/WinterTest.php @@ -31,6 +31,22 @@ class WinterTest extends Command protected $exec = ['./vendor/bin/phpunit']; + /** + * Create a new command instance. + * + * @return void + */ + public function __construct() + { + parent::__construct(); + + // Because we need to proxy some options that are not defined + // We need to ignore validation errors + // Same is done in the native Laravel test commands + // @link https://github.com/nunomaduro/collision/blob/stable/src/Adapters/Laravel/Commands/TestCommand.php + $this->ignoreValidationErrors(); + } + /** * Execute the console command. * @return int @@ -65,6 +81,11 @@ public function handle() $this->output->writeln('Running Winter\'s core tests.'); } + // Add eventual PHPUnit native options + $options = array_slice($_SERVER['argv'], $this->argument('plugin') ? 3 : 2); + + $this->exec = array_merge($this->exec, $options); + $process = (new Process($this->exec))->setTimeout(null); try { From b12b2c0da40770d9ce0b6d265ed7cd0c7d0e8151 Mon Sep 17 00:00:00 2001 From: Romain 'Maz' BILLOIR Date: Thu, 1 Jul 2021 08:25:52 +0200 Subject: [PATCH 06/21] Change comment for ignoring validation Co-authored-by: Luke Towers --- modules/system/console/WinterTest.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/system/console/WinterTest.php b/modules/system/console/WinterTest.php index ecd748b6e8..42ecc3b588 100644 --- a/modules/system/console/WinterTest.php +++ b/modules/system/console/WinterTest.php @@ -40,10 +40,10 @@ public function __construct() { parent::__construct(); - // Because we need to proxy some options that are not defined - // We need to ignore validation errors - // Same is done in the native Laravel test commands - // @link https://github.com/nunomaduro/collision/blob/stable/src/Adapters/Laravel/Commands/TestCommand.php + /** + * Ignore validation errors as option proxying is used by this command + * @see https://github.com/nunomaduro/collision/blob/stable/src/Adapters/Laravel/Commands/TestCommand.php + */ $this->ignoreValidationErrors(); } From 7f83be294bf297926760f4d300cea51ae1cabf86 Mon Sep 17 00:00:00 2001 From: Romain 'Maz' BILLOIR Date: Thu, 1 Jul 2021 20:44:30 +0200 Subject: [PATCH 07/21] Improve comments Co-authored-by: Ben Thomson --- modules/system/console/WinterTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/system/console/WinterTest.php b/modules/system/console/WinterTest.php index 42ecc3b588..c8e791de83 100644 --- a/modules/system/console/WinterTest.php +++ b/modules/system/console/WinterTest.php @@ -9,7 +9,7 @@ /** * Console command to test a plugin or winter's core. * - * This searches for a phpunit.xml file into the plugin's directory and run its tests + * If a plugin is provided, this command will search for a `phpunit.xml` file inside the plugin's directory and run its tests. * * @package winter\wn-system-module */ From 0638b3a18ca5e01589715d66206ad8f657455b89 Mon Sep 17 00:00:00 2001 From: Romain 'Maz' BILLOIR Date: Thu, 1 Jul 2021 20:45:02 +0200 Subject: [PATCH 08/21] Improve command description Co-authored-by: Ben Thomson --- modules/system/console/WinterTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/system/console/WinterTest.php b/modules/system/console/WinterTest.php index c8e791de83..7df154fcfb 100644 --- a/modules/system/console/WinterTest.php +++ b/modules/system/console/WinterTest.php @@ -27,7 +27,7 @@ class WinterTest extends Command * The console command description. * @var string */ - protected $description = "Run tests of Winter's core or an existing plugin."; + protected $description = "Run tests for the Winter CMS core or an existing plugin."; protected $exec = ['./vendor/bin/phpunit']; From 186da19721588adaf0cd21f1920c065d6ee48c18 Mon Sep 17 00:00:00 2001 From: Romain 'Maz' BILLOIR Date: Thu, 1 Jul 2021 20:45:25 +0200 Subject: [PATCH 09/21] Improve class comment Co-authored-by: Ben Thomson --- modules/system/console/WinterTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/system/console/WinterTest.php b/modules/system/console/WinterTest.php index 7df154fcfb..f0e5d4e85e 100644 --- a/modules/system/console/WinterTest.php +++ b/modules/system/console/WinterTest.php @@ -7,7 +7,7 @@ use Symfony\Component\Console\Input\InputArgument; /** - * Console command to test a plugin or winter's core. + * Console command to test a plugin or the Winter CMS core. * * If a plugin is provided, this command will search for a `phpunit.xml` file inside the plugin's directory and run its tests. * From 42e1a0ce91202849007a3b4a730de62ef8b034b5 Mon Sep 17 00:00:00 2001 From: Romain 'Maz' BILLOIR Date: Thu, 1 Jul 2021 20:45:55 +0200 Subject: [PATCH 10/21] Improve comments Co-authored-by: Ben Thomson --- modules/system/console/WinterTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/system/console/WinterTest.php b/modules/system/console/WinterTest.php index f0e5d4e85e..ff563d820d 100644 --- a/modules/system/console/WinterTest.php +++ b/modules/system/console/WinterTest.php @@ -76,7 +76,7 @@ public function handle() $this->output->writeln(sprintf('Running plugin\'s tests: %s.', $pluginName)); } - // Else run winter's tests + // Run the core Winter CMS tests else { $this->output->writeln('Running Winter\'s core tests.'); } From 4fa873f410da573775687dd9c075ef1727c9ae53 Mon Sep 17 00:00:00 2001 From: Romain 'Maz' BILLOIR Date: Thu, 1 Jul 2021 20:49:13 +0200 Subject: [PATCH 11/21] Change output info with info shortcut Co-authored-by: Ben Thomson --- modules/system/console/WinterTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/system/console/WinterTest.php b/modules/system/console/WinterTest.php index ff563d820d..c2cf86ac54 100644 --- a/modules/system/console/WinterTest.php +++ b/modules/system/console/WinterTest.php @@ -78,7 +78,7 @@ public function handle() // Run the core Winter CMS tests else { - $this->output->writeln('Running Winter\'s core tests.'); + $this->info('Running Winter\'s core tests.'); } // Add eventual PHPUnit native options From 17bc4d08901404c5913e8e884a328b4a67f4e2d9 Mon Sep 17 00:00:00 2001 From: Romain 'Maz' BILLOIR Date: Thu, 1 Jul 2021 21:00:57 +0200 Subject: [PATCH 12/21] Apply suggestions from code review Co-authored-by: Ben Thomson --- modules/system/console/WinterTest.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/modules/system/console/WinterTest.php b/modules/system/console/WinterTest.php index c2cf86ac54..49c8b04b9a 100644 --- a/modules/system/console/WinterTest.php +++ b/modules/system/console/WinterTest.php @@ -54,12 +54,13 @@ public function __construct() public function handle() { // If the plugin argument is set, search for it and run its tests - if ($pluginArgument = $this->argument('plugin')) { + $pluginName = $this->argument('plugin'); + if (!is_null($pluginName)) { $pluginManager = PluginManager::instance(); $pluginName = $pluginManager->normalizeIdentifier($pluginArgument); if (!$pluginManager->exists($pluginName)) { - throw new \InvalidArgumentException(sprintf('Plugin "%s" not found.', $pluginName)); + $this->error(sprintf('Plugin "%s" not found.', $pluginName)); } $pluginDir = $pluginManager->getPluginPath($pluginName); @@ -73,7 +74,7 @@ public function handle() * Test plugin */ $this->exec[] = "--configuration=$pluginPHPUnitXMLFile"; - $this->output->writeln(sprintf('Running plugin\'s tests: %s.', $pluginName)); + $this->info(sprintf('Running plugin\'s tests: %s.', $pluginName)); } // Run the core Winter CMS tests From 9dc7e526bc3e0d3b5c8bf4d076febedec67e23ba Mon Sep 17 00:00:00 2001 From: RomainMazB Date: Thu, 1 Jul 2021 22:28:56 +0200 Subject: [PATCH 13/21] Gives the ability to pass a custom configuration file, and now fallback to phpunit.xml.dist file --- modules/system/console/WinterTest.php | 106 +++++++++++++++++++++----- 1 file changed, 88 insertions(+), 18 deletions(-) diff --git a/modules/system/console/WinterTest.php b/modules/system/console/WinterTest.php index 49c8b04b9a..949e50cde9 100644 --- a/modules/system/console/WinterTest.php +++ b/modules/system/console/WinterTest.php @@ -21,7 +21,7 @@ class WinterTest extends Command */ protected $name = 'winter:test'; - protected $signature = 'winter:test {plugin?}'; + protected $signature = 'winter:test {plugin?} {--configuration=}'; /** * The console command description. @@ -49,47 +49,55 @@ public function __construct() /** * Execute the console command. - * @return int + * @return int|void */ public function handle() { // If the plugin argument is set, search for it and run its tests - $pluginName = $this->argument('plugin'); - if (!is_null($pluginName)) { + $pluginArgument = $this->argument('plugin'); + if (!is_null($pluginArgument)) { $pluginManager = PluginManager::instance(); $pluginName = $pluginManager->normalizeIdentifier($pluginArgument); if (!$pluginManager->exists($pluginName)) { $this->error(sprintf('Plugin "%s" not found.', $pluginName)); - } - - $pluginDir = $pluginManager->getPluginPath($pluginName); - $pluginPHPUnitXMLFile = $pluginDir . '/phpunit.xml'; - if (!file_exists($pluginPHPUnitXMLFile)) { - throw new \InvalidArgumentException(sprintf('phpunit.xml file not found in "%s".', $pluginDir)); + return; } - /* - * Test plugin - */ - $this->exec[] = "--configuration=$pluginPHPUnitXMLFile"; - $this->info(sprintf('Running plugin\'s tests: %s.', $pluginName)); + $testsPath = $pluginManager->getPluginPath($pluginName); } // Run the core Winter CMS tests else { - $this->info('Running Winter\'s core tests.'); + $testsPath = base_path(); + } + + $phpunitXMLFile = $this->lookForPHPUnitXMLFile($testsPath); + + if (!$phpunitXMLFile) { + $this->error(sprintf('Configuration file not found in "%s".', $testsPath)); + + return; } + $this->exec[] = "--configuration=$phpunitXMLFile"; + // Add eventual PHPUnit native options - $options = array_slice($_SERVER['argv'], $this->argument('plugin') ? 3 : 2); + $phpunitArguments = $this->getPHPUnitArguments(); - $this->exec = array_merge($this->exec, $options); + $this->exec = array_merge($this->exec, $phpunitArguments); $process = (new Process($this->exec))->setTimeout(null); try { + $runningTestsMessage = + isset($pluginName) ? sprintf('Running plugin\'s tests: %s.', $pluginName) + : 'Running Winter\'s core tests.'; + + $this->info($runningTestsMessage); + $this->info(sprintf('Configuration file used: %s.', $phpunitXMLFile)); + return $process->run(function ($type, $line) { $this->output->write($line); }); @@ -110,4 +118,66 @@ protected function getArguments() ['plugin', InputArgument::OPTIONAL, 'The name of the plugin. Eg: AuthorName.PluginName'], ]; } + + /** + * Search for the config file to use + * and gives an opportunity to change it if not found, + * priority order is: --configuration option, phpunit.xml then phpunit.xml.dist + * @param string|bool $directory + */ + protected function lookForPHPUnitXMLFile(string $directory) + { + $configurationOption = $this->option('configuration'); + + // If the user explicitly provided a custom configuration file + // Make sure it exists or abort as its clear we don't want to run default phpunit files + if ($configurationOption) { + $optionFilePath = $directory . DIRECTORY_SEPARATOR . $configurationOption; + + if (file_exists($optionFilePath)) { + return $optionFilePath; + } + + return false; + } + + // If a phpunit.xml file exists, returns its path + $distFilePath = $directory . DIRECTORY_SEPARATOR . 'phpunit.xml'; + if (file_exists($distFilePath)) { + return $distFilePath; + } + + // Fallback to phpunit.xml.dist file path if it exists + $configFilePath = $directory . DIRECTORY_SEPARATOR . 'phpunit.xml.dist'; + if (file_exists($configFilePath)) { + return $configFilePath; + } + + return false; + } + + /** + * Strips out this commands arguments and options in order to return arguments/options for PHPUnit. + * + * @return array + */ + protected function getPHPUnitArguments() + { + $arguments = $_SERVER['argv']; + + // First two are always "artisan" and "winter:test" + $arguments = array_slice($arguments, 2); + + // Strip plugin argument + if ($this->argument('plugin')) { + array_shift($arguments); + } + + // Strip "--configuration" + if ($this->option('configuration')) { + array_shift($arguments); + } + + return $arguments; + } } From 32bfc2703a634a20c96d902c9a06f15c571716bf Mon Sep 17 00:00:00 2001 From: RomainMazB Date: Thu, 1 Jul 2021 22:36:53 +0200 Subject: [PATCH 14/21] Fix indentation --- modules/system/console/WinterTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/system/console/WinterTest.php b/modules/system/console/WinterTest.php index 949e50cde9..b36d95e8e0 100644 --- a/modules/system/console/WinterTest.php +++ b/modules/system/console/WinterTest.php @@ -93,7 +93,7 @@ public function handle() try { $runningTestsMessage = isset($pluginName) ? sprintf('Running plugin\'s tests: %s.', $pluginName) - : 'Running Winter\'s core tests.'; + : 'Running Winter\'s core tests.'; $this->info($runningTestsMessage); $this->info(sprintf('Configuration file used: %s.', $phpunitXMLFile)); From f8ccb564257f6b869f064581651260335d0bc115 Mon Sep 17 00:00:00 2001 From: RomainMazB Date: Thu, 1 Jul 2021 23:15:12 +0200 Subject: [PATCH 15/21] Remove useless part of a comment --- modules/system/console/WinterTest.php | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/modules/system/console/WinterTest.php b/modules/system/console/WinterTest.php index b36d95e8e0..33104e1b12 100644 --- a/modules/system/console/WinterTest.php +++ b/modules/system/console/WinterTest.php @@ -120,9 +120,8 @@ protected function getArguments() } /** - * Search for the config file to use - * and gives an opportunity to change it if not found, - * priority order is: --configuration option, phpunit.xml then phpunit.xml.dist + * Search for the config file to use. + * Priority order is: --configuration option, phpunit.xml then phpunit.xml.dist * @param string|bool $directory */ protected function lookForPHPUnitXMLFile(string $directory) From c34af66ca7557ddcc90be5027ce66aa019ac7122 Mon Sep 17 00:00:00 2001 From: Jack Wilkinson Date: Mon, 27 Sep 2021 21:45:54 +0100 Subject: [PATCH 16/21] Reworked command argument handling and test execution --- modules/system/console/WinterTest.php | 226 +++++++++++++++++++------- 1 file changed, 163 insertions(+), 63 deletions(-) diff --git a/modules/system/console/WinterTest.php b/modules/system/console/WinterTest.php index 33104e1b12..e0eaf4f952 100644 --- a/modules/system/console/WinterTest.php +++ b/modules/system/console/WinterTest.php @@ -1,10 +1,12 @@ argument('plugin'); - if (!is_null($pluginArgument)) { - $pluginManager = PluginManager::instance(); - $pluginName = $pluginManager->normalizeIdentifier($pluginArgument); + $arguments = $this->getAdditionalArguments(); + + if (($config = $this->option('configuration')) && file_exists($config)) { + return $this->execPhpUnit($config, $arguments); + } - if (!$pluginManager->exists($pluginName)) { - $this->error(sprintf('Plugin "%s" not found.', $pluginName)); + $configs = $this->getPhpUnitConfigs(); - return; + if ($this->option('core')) { + if (!$configs['core']) { + throw new ApplicationException('unable to find core `phpunit.xml`. Try downloading it from github.'); } + $this->info('Running tests for: Winter CMS core'); - $testsPath = $pluginManager->getPluginPath($pluginName); + return $this->execPhpUnit($configs['core'], $arguments); } - // Run the core Winter CMS tests - else { - $testsPath = base_path(); + if ($plugin = $this->option('plugin')) { + if (!isset($configs['plugins'][strtolower($plugin)])) { + throw new ApplicationException(sprintf('unable to find %s\'s `phpunit.xml`', $plugin)); + } + $this->info('Running tests for plugin: ' . PluginManager::instance()->normalizeIdentifier($plugin)); + + return $this->execPhpUnit($configs['plugins'][strtolower($plugin)], $arguments); } - $phpunitXMLFile = $this->lookForPHPUnitXMLFile($testsPath); + $exitCode = 0; - if (!$phpunitXMLFile) { - $this->error(sprintf('Configuration file not found in "%s".', $testsPath)); + foreach (['core', 'plugins'] as $type) { + if (is_array($configs[$type])) { + foreach ($configs[$type] as $plugin => $config) { + $this->info('Running tests for plugin: ' . PluginManager::instance()->normalizeIdentifier($plugin)); + $exit = $this->execPhpUnit($config, $arguments); + $exitCode = $exitCode === 0 ? $exit : $exitCode; + } + continue; + } - return; + $this->info('Running tests for: Winter CMS ' . $type); + $exit = $this->execPhpUnit($configs[$type], $arguments); + $exitCode = $exitCode === 0 ? $exit : $exitCode; } - $this->exec[] = "--configuration=$phpunitXMLFile"; + return $exitCode; + } - // Add eventual PHPUnit native options - $phpunitArguments = $this->getPHPUnitArguments(); + /** + * Get the console command options. + * + * @return array + */ + protected function getOptions(): array + { + return [ + ['plugin', 'p', InputOption::VALUE_OPTIONAL, 'The name of the plugin. Eg: AuthorName.PluginName', null], + ['configuration', 'c', InputOption::VALUE_OPTIONAL, 'The path to a phpunit xml config', null], + ['core', 'o', InputOption::VALUE_NONE, 'Test the core'], + ]; + } - $this->exec = array_merge($this->exec, $phpunitArguments); + /** + * Get the console command arguments. + * + * @return array + */ + protected function getArguments(): array + { + return []; + } - $process = (new Process($this->exec))->setTimeout(null); + /** + * Execute a phpunit test + * @param string $config path to configuration file + * @param array $args array of params for phpunit + * + * @return int exit code from process + */ + protected function execPhpUnit(string $config, array $args): int + { + // find and bind the phpunit executable + if (!$this->phpUnitExec) { + $this->phpUnitExec = (new ExecutableFinder()) + ->find('phpunit', base_path('vendor/bin/phpunit'), [base_path('vendor')]); + } - try { - $runningTestsMessage = - isset($pluginName) ? sprintf('Running plugin\'s tests: %s.', $pluginName) - : 'Running Winter\'s core tests.'; + $process = new Process( + array_merge([$this->phpUnitExec, '--configuration=' . $config], $args), + dirname($config), + null, + null + ); - $this->info($runningTestsMessage); - $this->info(sprintf('Configuration file used: %s.', $phpunitXMLFile)); + // attempt to set tty mode, if unsupported catch and warn the exception message + try { + $process->setTty(true); + } catch (\Throwable $e) { + $this->warn($e->getMessage()); + } + try { return $process->run(function ($type, $line) { $this->output->write($line); }); @@ -105,78 +172,111 @@ public function handle() if (extension_loaded('pcntl') && $e->getSignal() !== SIGINT) { throw $e; } + + return 1; } } /** - * Get the console command arguments. + * Find all PHPUnit config files (core, lib, plugins) + * * @return array */ - protected function getArguments() + protected function getPhpUnitConfigs(): array { - return [ - ['plugin', InputArgument::OPTIONAL, 'The name of the plugin. Eg: AuthorName.PluginName'], + $configs = [ + 'core' => $this->getPhpUnitXmlFile(base_path()), + 'plugins' => [] ]; + + foreach (PluginManager::instance()->getPlugins() as $plugin) { + if ($path = $this->getPhpUnitXmlFile($plugin->getPluginPath())) { + $configs['plugins'][strtolower($plugin->getPluginIdentifier())] = $path; + } + } + + return $configs; } /** * Search for the config file to use. - * Priority order is: --configuration option, phpunit.xml then phpunit.xml.dist - * @param string|bool $directory + * Priority order is: phpunit.xml, phpunit.xml.dist + * @param string $path + * + * @return ?string */ - protected function lookForPHPUnitXMLFile(string $directory) + protected function getPhpUnitXmlFile(string $path): ?string { - $configurationOption = $this->option('configuration'); - - // If the user explicitly provided a custom configuration file - // Make sure it exists or abort as its clear we don't want to run default phpunit files - if ($configurationOption) { - $optionFilePath = $directory . DIRECTORY_SEPARATOR . $configurationOption; - - if (file_exists($optionFilePath)) { - return $optionFilePath; - } - - return false; - } - // If a phpunit.xml file exists, returns its path - $distFilePath = $directory . DIRECTORY_SEPARATOR . 'phpunit.xml'; + $distFilePath = $path . DIRECTORY_SEPARATOR . 'phpunit.xml'; if (file_exists($distFilePath)) { return $distFilePath; } // Fallback to phpunit.xml.dist file path if it exists - $configFilePath = $directory . DIRECTORY_SEPARATOR . 'phpunit.xml.dist'; + $configFilePath = $path . DIRECTORY_SEPARATOR . 'phpunit.xml.dist'; if (file_exists($configFilePath)) { return $configFilePath; } - return false; + return null; } /** - * Strips out this commands arguments and options in order to return arguments/options for PHPUnit. + * Strips out commands arguments and options in order to return arguments/options for PHPUnit. * * @return array */ - protected function getPHPUnitArguments() + protected function getAdditionalArguments(): array { $arguments = $_SERVER['argv']; // First two are always "artisan" and "winter:test" $arguments = array_slice($arguments, 2); - // Strip plugin argument - if ($this->argument('plugin')) { - array_shift($arguments); + // If nothing to do then just return + if (!count($arguments)) { + return $arguments; } - // Strip "--configuration" - if ($this->option('configuration')) { - array_shift($arguments); + // Get the arguments provided by this command + foreach ($this->getOptions() as $argument) { + // For position 0 & 1, pass their names with appropriate dashes + for ($i = 0; $i < 2; $i++) { + $arguments = $this->removeArgument($arguments, str_repeat('-', 2 - $i) . $argument[$i]); + } } return $arguments; } + + /** + * Removes flags from argument list and their value if present + * + * @param array $arguments + * @param string $remove + * + * @return array + */ + protected function removeArgument(array $arguments, string $remove): array + { + // find args that have trailing chars + $key = array_values(preg_grep("/^({$remove}|{$remove}=).*/i", $arguments)); + $remove = (isset($key[0])) ? $key[0] : $remove; + + // find the position of arguments to remove + if (($position = array_search($remove, $arguments)) === false) { + return $arguments; + } + + // remove argument + unset($arguments[$position]); + + // if the next item in the array is not a flag, consider it a value of the removed argument + if (isset($arguments[$position + 1]) && substr($arguments[$position + 1], 0, 1) !== '-') { + unset($arguments[$position + 1]); + } + + return array_values($arguments); + } } From e2d5a975f7797c4cf78e0fa20755f22dbf03d833 Mon Sep 17 00:00:00 2001 From: Luke Towers Date: Thu, 21 Oct 2021 12:13:26 -0600 Subject: [PATCH 17/21] Apply suggestions from code review No need to include docblock comments for information provided by type hints unless additional comments are made that justify a docblock comment --- modules/system/console/WinterTest.php | 49 ++++++++------------------- 1 file changed, 15 insertions(+), 34 deletions(-) diff --git a/modules/system/console/WinterTest.php b/modules/system/console/WinterTest.php index e0eaf4f952..581be727ea 100644 --- a/modules/system/console/WinterTest.php +++ b/modules/system/console/WinterTest.php @@ -24,20 +24,17 @@ class WinterTest extends Command protected $name = 'winter:test'; /** - * The console command signature as ignoreValidationErrors causes options not to be registered. - * @var string + * @var string The console command signature as ignoreValidationErrors causes options not to be registered. */ protected $signature = 'winter:test {?--p|plugin=} {?--c|configuration=} {?--o|core}'; /** - * The console command description. - * @var string + * @var string The console command description. */ protected $description = 'Run tests for the Winter CMS core or an existing plugin.'; /** - * Path to phpunit binary - * @var ?string + * @var ?string Path to phpunit binary */ protected $phpUnitExec = null; @@ -59,8 +56,8 @@ public function __construct() /** * Execute the console command. - * @throws ApplicationException * + * @throws ApplicationException * @return int|void */ public function handle() @@ -75,7 +72,7 @@ public function handle() if ($this->option('core')) { if (!$configs['core']) { - throw new ApplicationException('unable to find core `phpunit.xml`. Try downloading it from github.'); + throw new ApplicationException("Unable to find the core's phpunit.xml file. Try downloading it from GitHub."); } $this->info('Running tests for: Winter CMS core'); @@ -84,7 +81,7 @@ public function handle() if ($plugin = $this->option('plugin')) { if (!isset($configs['plugins'][strtolower($plugin)])) { - throw new ApplicationException(sprintf('unable to find %s\'s `phpunit.xml`', $plugin)); + throw new ApplicationException(sprintf("Unable to find %s\'s phpunit.xml file', $plugin)); } $this->info('Running tests for plugin: ' . PluginManager::instance()->normalizeIdentifier($plugin)); @@ -103,7 +100,7 @@ public function handle() continue; } - $this->info('Running tests for: Winter CMS ' . $type); + $this->info('Running tests for Winter CMS: ' . $type); $exit = $this->execPhpUnit($configs[$type], $arguments); $exitCode = $exitCode === 0 ? $exit : $exitCode; } @@ -113,22 +110,18 @@ public function handle() /** * Get the console command options. - * - * @return array */ protected function getOptions(): array { return [ - ['plugin', 'p', InputOption::VALUE_OPTIONAL, 'The name of the plugin. Eg: AuthorName.PluginName', null], - ['configuration', 'c', InputOption::VALUE_OPTIONAL, 'The path to a phpunit xml config', null], - ['core', 'o', InputOption::VALUE_NONE, 'Test the core'], + ['plugin', 'p', InputOption::VALUE_OPTIONAL, 'The name of the plugin. Ex: AuthorName.PluginName', null], + ['configuration', 'c', InputOption::VALUE_OPTIONAL, 'The path to a PHPUnit XML config file', null], + ['core', 'o', InputOption::VALUE_NONE, 'Run the Winter CMS core tests'], ]; } /** * Get the console command arguments. - * - * @return array */ protected function getArguments(): array { @@ -137,14 +130,14 @@ protected function getArguments(): array /** * Execute a phpunit test - * @param string $config path to configuration file - * @param array $args array of params for phpunit * - * @return int exit code from process + * @param string $config Path to configuration file + * @param array $args Array of params for PHPUnit + * @return int Exit code from process */ protected function execPhpUnit(string $config, array $args): int { - // find and bind the phpunit executable + // Find and bind the phpunit executable if (!$this->phpUnitExec) { $this->phpUnitExec = (new ExecutableFinder()) ->find('phpunit', base_path('vendor/bin/phpunit'), [base_path('vendor')]); @@ -157,7 +150,7 @@ protected function execPhpUnit(string $config, array $args): int null ); - // attempt to set tty mode, if unsupported catch and warn the exception message + // Attempt to set tty mode, catch and warn with the exception message if unsupported try { $process->setTty(true); } catch (\Throwable $e) { @@ -179,8 +172,6 @@ protected function execPhpUnit(string $config, array $args): int /** * Find all PHPUnit config files (core, lib, plugins) - * - * @return array */ protected function getPhpUnitConfigs(): array { @@ -201,9 +192,6 @@ protected function getPhpUnitConfigs(): array /** * Search for the config file to use. * Priority order is: phpunit.xml, phpunit.xml.dist - * @param string $path - * - * @return ?string */ protected function getPhpUnitXmlFile(string $path): ?string { @@ -224,8 +212,6 @@ protected function getPhpUnitXmlFile(string $path): ?string /** * Strips out commands arguments and options in order to return arguments/options for PHPUnit. - * - * @return array */ protected function getAdditionalArguments(): array { @@ -252,11 +238,6 @@ protected function getAdditionalArguments(): array /** * Removes flags from argument list and their value if present - * - * @param array $arguments - * @param string $remove - * - * @return array */ protected function removeArgument(array $arguments, string $remove): array { From 09c0d4dc42f57573ec2745df79f47a702cc545e1 Mon Sep 17 00:00:00 2001 From: Luke Towers Date: Thu, 21 Oct 2021 12:14:01 -0600 Subject: [PATCH 18/21] Apply suggestions from code review --- modules/system/console/WinterTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/system/console/WinterTest.php b/modules/system/console/WinterTest.php index 581be727ea..4e45469c1a 100644 --- a/modules/system/console/WinterTest.php +++ b/modules/system/console/WinterTest.php @@ -81,7 +81,7 @@ public function handle() if ($plugin = $this->option('plugin')) { if (!isset($configs['plugins'][strtolower($plugin)])) { - throw new ApplicationException(sprintf("Unable to find %s\'s phpunit.xml file', $plugin)); + throw new ApplicationException(sprintf("Unable to find %s\'s phpunit.xml file", $plugin)); } $this->info('Running tests for plugin: ' . PluginManager::instance()->normalizeIdentifier($plugin)); From ddcfc3596d63218b6a447a37e02345bb30d0d759 Mon Sep 17 00:00:00 2001 From: Luke Towers Date: Thu, 21 Oct 2021 12:14:39 -0600 Subject: [PATCH 19/21] Update modules/system/console/WinterTest.php --- modules/system/console/WinterTest.php | 8 -------- 1 file changed, 8 deletions(-) diff --git a/modules/system/console/WinterTest.php b/modules/system/console/WinterTest.php index 4e45469c1a..b212c5c42e 100644 --- a/modules/system/console/WinterTest.php +++ b/modules/system/console/WinterTest.php @@ -120,14 +120,6 @@ protected function getOptions(): array ]; } - /** - * Get the console command arguments. - */ - protected function getArguments(): array - { - return []; - } - /** * Execute a phpunit test * From 6d9a6c44ad90dc76d0226bd3a12eba893579c77a Mon Sep 17 00:00:00 2001 From: Luke Towers Date: Thu, 21 Oct 2021 12:15:38 -0600 Subject: [PATCH 20/21] Update modules/system/console/WinterTest.php --- modules/system/console/WinterTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/system/console/WinterTest.php b/modules/system/console/WinterTest.php index b212c5c42e..812a677918 100644 --- a/modules/system/console/WinterTest.php +++ b/modules/system/console/WinterTest.php @@ -9,7 +9,7 @@ use Winter\Storm\Exception\ApplicationException; /** - * Console command to test a plugin or the Winter CMS core. + * Console command to run tests for plugins or the Winter CMS core. * * If a plugin is provided, this command will search for a `phpunit.xml` file inside the plugin's directory and run its tests. * From ab569144ba331a0f204c3e7325f16bcfd0da3a48 Mon Sep 17 00:00:00 2001 From: Luke Towers Date: Thu, 21 Oct 2021 12:16:05 -0600 Subject: [PATCH 21/21] Update modules/system/console/WinterTest.php --- modules/system/console/WinterTest.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/modules/system/console/WinterTest.php b/modules/system/console/WinterTest.php index 812a677918..29934c9484 100644 --- a/modules/system/console/WinterTest.php +++ b/modules/system/console/WinterTest.php @@ -18,8 +18,7 @@ class WinterTest extends Command { /** - * The console command name. - * @var string + * @var string The console command name. */ protected $name = 'winter:test';