From 632a21f2258ddbf502e6b54dd5242017ea582fab Mon Sep 17 00:00:00 2001 From: "Eloy Lafuente (stronk7)" Date: Sat, 4 Nov 2023 16:39:13 +0100 Subject: [PATCH] Add support for phpunit configure, testsuite and filter options Covered by tests that use intensively the new ->lastCmd introduced a couple of commits ago. Also, added a TODO about something, pre-existing, that is an incorrect unit tests, some day will be investigated. Not today. Fixes #100 --- docs/CHANGELOG.md | 3 +- docs/CLI.md | 52 ++++++++++++++++----- src/Command/PHPUnitCommand.php | 68 +++++++++++++++++++++++----- tests/Command/PHPUnitCommandTest.php | 60 ++++++++++++++++++++++-- 4 files changed, 154 insertions(+), 29 deletions(-) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index dae36adc..4057aec2 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -10,7 +10,8 @@ The format of this change log follows the advice given at [Keep a CHANGELOG](htt ## [Unreleased] ### Added -- Added support for the `--tags` and `--name` options into the `behat` command. +- Added support for the `--tags` and `--name` options to the `behat` command. +- Added support for the `--configure`, `--testsuite` and `--filter` options to the `phpunit` command. ### Changed - ACTION SUGGESTED: If you are using GitHub Actions, it's recomended to use `!cancelled()` instead of `always()` for moodle-plugin-ci tests. Adding a final step that always returns failure when the workflow is cancelled will ensure that cancelled workflows are not marked as successful. For a working example, please reference the updated `gha.dist.yml` file. diff --git a/docs/CLI.md b/docs/CLI.md index 1507eab6..13e86ae6 100644 --- a/docs/CLI.md +++ b/docs/CLI.md @@ -1975,7 +1975,7 @@ Run PHPUnit on a plugin ### Usage -* `phpunit [-m|--moodle MOODLE] [--coverage-text] [--coverage-clover] [--coverage-pcov] [--coverage-xdebug] [--coverage-phpdbg] [--fail-on-incomplete] [--fail-on-risky] [--fail-on-skipped] [--fail-on-warning] [--testdox] [--] ` +* `phpunit [-m|--moodle MOODLE] [-c|--configuration CONFIGURATION] [--testsuite TESTSUITE] [--filter FILTER] [--testdox] [--coverage-text] [--coverage-clover] [--coverage-pcov] [--coverage-xdebug] [--coverage-phpdbg] [--fail-on-incomplete] [--fail-on-risky] [--fail-on-skipped] [--fail-on-warning] [--] ` Run PHPUnit on a plugin @@ -2001,6 +2001,46 @@ Path to Moodle * Is negatable: no * Default: `'.'` +#### `--configuration|-c` + +PHPUnit configuration XML file (relative to plugin directory) + +* Accept value: yes +* Is value required: yes +* Is multiple: no +* Is negatable: no +* Default: `NULL` + +#### `--testsuite` + +PHPUnit testsuite option to use (must exist in the configuration file being used) + +* Accept value: yes +* Is value required: yes +* Is multiple: no +* Is negatable: no +* Default: `NULL` + +#### `--filter` + +PHPUnit filter option to use + +* Accept value: yes +* Is value required: yes +* Is multiple: no +* Is negatable: no +* Default: `NULL` + +#### `--testdox` + +Enable testdox formatter + +* Accept value: no +* Is value required: no +* Is multiple: no +* Is negatable: no +* Default: `false` + #### `--coverage-text` Generate and print code coverage report in text format @@ -2091,16 +2131,6 @@ Treat tests with warnings as failures * Is negatable: no * Default: `false` -#### `--testdox` - -Enable testdox formatter - -* Accept value: no -* Is value required: no -* Is multiple: no -* Is negatable: no -* Default: `false` - #### `--help|-h` Display help for the given command. When no command is given display help for the list command diff --git a/src/Command/PHPUnitCommand.php b/src/Command/PHPUnitCommand.php index 602bb91b..7e4728bb 100644 --- a/src/Command/PHPUnitCommand.php +++ b/src/Command/PHPUnitCommand.php @@ -29,6 +29,20 @@ protected function configure(): void $this->setName('phpunit') ->setDescription('Run PHPUnit on a plugin') + ->addOption( + 'configuration', + 'c', + InputOption::VALUE_REQUIRED, + 'PHPUnit configuration XML file (relative to plugin directory)' + ) + ->addOption( + 'testsuite', + null, + InputOption::VALUE_REQUIRED, + 'PHPUnit testsuite option to use (must exist in the configuration file being used)' + ) + ->addOption('filter', null, InputOption::VALUE_REQUIRED, 'PHPUnit filter option to use') + ->addOption('testdox', null, InputOption::VALUE_NONE, 'Enable testdox formatter') ->addOption('coverage-text', null, InputOption::VALUE_NONE, 'Generate and print code coverage report in text format') ->addOption('coverage-clover', null, InputOption::VALUE_NONE, 'Generate code coverage report in Clover XML format') ->addOption('coverage-pcov', null, InputOption::VALUE_NONE, 'Use the pcov extension to calculate code coverage') @@ -37,8 +51,7 @@ protected function configure(): void ->addOption('fail-on-incomplete', null, InputOption::VALUE_NONE, 'Treat incomplete tests as failures') ->addOption('fail-on-risky', null, InputOption::VALUE_NONE, 'Treat risky tests as failures') ->addOption('fail-on-skipped', null, InputOption::VALUE_NONE, 'Treat skipped tests as failures') - ->addOption('fail-on-warning', null, InputOption::VALUE_NONE, 'Treat tests with warnings as failures') - ->addOption('testdox', null, InputOption::VALUE_NONE, 'Enable testdox formatter'); + ->addOption('fail-on-warning', null, InputOption::VALUE_NONE, 'Treat tests with warnings as failures'); } protected function initialize(InputInterface $input, OutputInterface $output): void @@ -80,6 +93,28 @@ protected function execute(InputInterface $input, OutputInterface $output): int private function resolveOptions(InputInterface $input): array { $options = []; + + if ($input->getOption('configuration')) { + $options[] = [ + '--configuration', + $this->plugin->directory . '/' . $input->getOption('configuration'), + ]; + } + + if ($input->getOption('testsuite')) { + $options[] = [ + '--testsuite', + $input->getOption('testsuite'), + ]; + } + + if ($input->getOption('filter')) { + $options[] = [ + '--filter', + $input->getOption('filter'), + ]; + } + if ($this->supportsCoverage() && $input->getOption('coverage-text')) { $options[] = [ '--coverage-text', @@ -103,16 +138,25 @@ private function resolveOptions(InputInterface $input): array ]; } } - if (is_file($this->plugin->directory . '/phpunit.xml')) { - $options[] = [ - '--configuration', - $this->plugin->directory, - ]; - } else { - $options[] = [ - '--testsuite', - $this->plugin->getComponent(), - ]; + + // Only can set configuration or testsuite here (auto) if the former has not been set via command line option. + if (!$input->getOption('configuration')) { + // Use default configuration (phpunit.xml) only if it exists. + if (is_file($this->plugin->directory . '/phpunit.xml')) { + $options[] = [ + '--configuration', + $this->plugin->directory . '/phpunit.xml', + ]; + } else { + // Fallback to try to use the best testsuite potentially available. + // Only can set automatic testsuite if it has not been passed via command line option. + if (!$input->getOption('testsuite')) { + $options[] = [ + '--testsuite', + $this->plugin->getComponent() . '_testsuite', // This is our best guess. + ]; + } + } } return array_merge(...$options); // Merge all options into a single array. diff --git a/tests/Command/PHPUnitCommandTest.php b/tests/Command/PHPUnitCommandTest.php index 9e550e22..959a3a34 100644 --- a/tests/Command/PHPUnitCommandTest.php +++ b/tests/Command/PHPUnitCommandTest.php @@ -21,7 +21,7 @@ class PHPUnitCommandTest extends MoodleTestCase { - protected function executeCommand($pluginDir = null, $moodleDir = null): CommandTester + protected function executeCommand($pluginDir = null, $moodleDir = null, array $cmdOptions = []): CommandTester { if ($pluginDir === null) { $pluginDir = $this->pluginDir; @@ -37,10 +37,15 @@ protected function executeCommand($pluginDir = null, $moodleDir = null): Command $application->add($command); $commandTester = new CommandTester($application->find('phpunit')); - $commandTester->execute([ - 'plugin' => $pluginDir, - '--moodle' => $moodleDir, - ]); + $cmdOptions = array_merge( + [ + 'plugin' => $pluginDir, + '--moodle' => $moodleDir, + ], + $cmdOptions + ); + $commandTester->execute($cmdOptions); + $this->lastCmd = $command->execute->lastCmd; // We need this for assertions against the command run. return $commandTester; } @@ -49,6 +54,50 @@ public function testExecute() { $commandTester = $this->executeCommand(); $this->assertSame(0, $commandTester->getStatusCode()); + $this->assertMatchesRegularExpression('/vendor.bin.phpunit/', $this->lastCmd); + $this->assertMatchesRegularExpression('/--testsuite.*local_ci_testsuite/', $this->lastCmd); + $this->assertDoesNotMatchRegularExpression('/--configuration.*local\/ci/', $this->lastCmd); + } + + public function testExecuteWithCustomPHPUnitXMLFile() + { + $commandTester = $this->executeCommand(null, null, ['--configuration' => 'some_config.xml']); + $this->assertSame(0, $commandTester->getStatusCode()); + $this->assertMatchesRegularExpression('/vendor.bin.phpunit/', $this->lastCmd); + $this->assertMatchesRegularExpression('/--configuration.*.*local\/ci\/some_config.xml/', $this->lastCmd); + $this->assertDoesNotMatchRegularExpression('/--configuration.*local\/ci\/phpunit.xml/', $this->lastCmd); + $this->assertDoesNotMatchRegularExpression('/--testsuite.*local_ci_testsuite/', $this->lastCmd); + } + + public function testExecuteWithGeneratedPHPUnitXMLFile() + { + $fs = new Filesystem(); + $fs->touch($this->pluginDir . '/phpunit.xml'); + $commandTester = $this->executeCommand(); + $this->assertSame(0, $commandTester->getStatusCode()); + $this->assertMatchesRegularExpression('/vendor.bin.phpunit/', $this->lastCmd); + $this->assertMatchesRegularExpression('/--configuration.*local\/ci\/phpunit.xml/', $this->lastCmd); + $this->assertDoesNotMatchRegularExpression('/--testsuite.*local_ci_testsuite/', $this->lastCmd); + } + + public function testExecuteWithTestSuite() + { + $commandTester = $this->executeCommand(null, null, ['--testsuite' => 'some_testsuite']); + $this->assertSame(0, $commandTester->getStatusCode()); + $this->assertMatchesRegularExpression('/vendor.bin.phpunit/', $this->lastCmd); + $this->assertMatchesRegularExpression('/--testsuite.*some_testsuite/', $this->lastCmd); + $this->assertDoesNotMatchRegularExpression('/--configuration.*local\/ci/', $this->lastCmd); + $this->assertDoesNotMatchRegularExpression('/--testsuite.*local_ci_testsuite/', $this->lastCmd); + } + + public function testExecuteWithFilter() + { + $commandTester = $this->executeCommand(null, null, ['--filter' => 'some_filter']); + $this->assertSame(0, $commandTester->getStatusCode()); + $this->assertMatchesRegularExpression('/vendor.bin.phpunit/', $this->lastCmd); + $this->assertMatchesRegularExpression('/--filter.*some_filter/', $this->lastCmd); + $this->assertMatchesRegularExpression('/--testsuite.*local_ci_testsuite/', $this->lastCmd); + $this->assertDoesNotMatchRegularExpression('/--configuration.*local\/ci/', $this->lastCmd); } public function testExecuteNoTests() @@ -70,6 +119,7 @@ public function testExecuteNoPlugin() public function testExecuteNoMoodle() { $this->expectException(\InvalidArgumentException::class); + // TODO: Check what's happening here. moodleDir should be the 2nd parameter, but then the test fails. $this->executeCommand($this->moodleDir . '/no/moodle'); } }