diff --git a/composer.json b/composer.json index 3b8d229d..96e6e428 100755 --- a/composer.json +++ b/composer.json @@ -89,7 +89,8 @@ "padraic/phar-updater": "^1.0" }, "require-dev": { - "phpunit/phpunit": "^5.7" + "phpunit/phpunit": "^5.7", + "mockery/mockery": "^1.3" }, "config": { "platform": { diff --git a/composer.lock b/composer.lock index 8bed47e1..7a71153e 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "7ec8559bdf0136a99e1f68c2c278d24e", + "content-hash": "ee277424273531d2fad2697a3476b87c", "packages": [ { "name": "composer/ca-bundle", @@ -1843,6 +1843,118 @@ ], "time": "2015-06-14T21:17:01+00:00" }, + { + "name": "hamcrest/hamcrest-php", + "version": "v2.0.1", + "source": { + "type": "git", + "url": "https://github.com/hamcrest/hamcrest-php.git", + "reference": "8c3d0a3f6af734494ad8f6fbbee0ba92422859f3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/hamcrest/hamcrest-php/zipball/8c3d0a3f6af734494ad8f6fbbee0ba92422859f3", + "reference": "8c3d0a3f6af734494ad8f6fbbee0ba92422859f3", + "shasum": "" + }, + "require": { + "php": "^5.3|^7.0|^8.0" + }, + "replace": { + "cordoval/hamcrest-php": "*", + "davedevelopment/hamcrest-php": "*", + "kodova/hamcrest-php": "*" + }, + "require-dev": { + "phpunit/php-file-iterator": "^1.4 || ^2.0", + "phpunit/phpunit": "^4.8.36 || ^5.7 || ^6.5 || ^7.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.1-dev" + } + }, + "autoload": { + "classmap": [ + "hamcrest" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "This is the PHP port of Hamcrest Matchers", + "keywords": [ + "test" + ], + "time": "2020-07-09T08:09:16+00:00" + }, + { + "name": "mockery/mockery", + "version": "1.3.3", + "source": { + "type": "git", + "url": "https://github.com/mockery/mockery.git", + "reference": "60fa2f67f6e4d3634bb4a45ff3171fa52215800d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/mockery/mockery/zipball/60fa2f67f6e4d3634bb4a45ff3171fa52215800d", + "reference": "60fa2f67f6e4d3634bb4a45ff3171fa52215800d", + "shasum": "" + }, + "require": { + "hamcrest/hamcrest-php": "^2.0.1", + "lib-pcre": ">=7.0", + "php": ">=5.6.0" + }, + "require-dev": { + "phpunit/phpunit": "^5.7.10|^6.5|^7.5|^8.5|^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.3.x-dev" + } + }, + "autoload": { + "psr-0": { + "Mockery": "library/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Pádraic Brady", + "email": "padraic.brady@gmail.com", + "homepage": "http://blog.astrumfutura.com" + }, + { + "name": "Dave Marshall", + "email": "dave.marshall@atstsolutions.co.uk", + "homepage": "http://davedevelopment.co.uk" + } + ], + "description": "Mockery is a simple yet flexible PHP mock object framework", + "homepage": "https://github.com/mockery/mockery", + "keywords": [ + "BDD", + "TDD", + "library", + "mock", + "mock objects", + "mockery", + "stub", + "test", + "test double", + "testing" + ], + "time": "2020-08-11T18:10:21+00:00" + }, { "name": "myclabs/deep-copy", "version": "1.7.0", diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index c5d2c853..fec4b572 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -17,6 +17,9 @@ The format of this change log follows the advice given at [Keep a CHANGELOG](htt [gha.dist.yml](https://github.com/moodlehq/moodle-plugin-ci/blob/master/gha.dist.yml) and add missing `NVM_DIR` line your plugin's GHA workflow file. +### Changed +- `moodle-plugin-ci add-plugin` command now uses default banch to checkout + instead of `master` if `--branch` param is not specified.. ## [3.0.4] - 2021-01-29 ### Fixed diff --git a/docs/CLI.md b/docs/CLI.md index b7e4ce73..6bbd0a78 100644 --- a/docs/CLI.md +++ b/docs/CLI.md @@ -135,7 +135,7 @@ Queue up an additional plugin to be installed in the test site #### `project` -GitHub project, EG: moodlehq/moodle-local_hub +GitHub project, EG: moodlehq/moodle-local_hub, can't be used with --clone option * Is required: no * Is array: no @@ -145,16 +145,16 @@ GitHub project, EG: moodlehq/moodle-local_hub #### `--branch|-b` -The branch to checkout within the plugin +The branch to checkout in plugin repo (if non-default) * Accept value: yes * Is value required: yes * Is multiple: no -* Default: `'master'` +* Default: `NULL` #### `--clone|-c` -Git clone URL +Git clone URL, can't be used with --project option * Accept value: yes * Is value required: yes diff --git a/src/Command/AddPluginCommand.php b/src/Command/AddPluginCommand.php index 04dd1893..42fbfeef 100644 --- a/src/Command/AddPluginCommand.php +++ b/src/Command/AddPluginCommand.php @@ -47,9 +47,9 @@ protected function configure() { $this->setName('add-plugin') ->setDescription('Queue up an additional plugin to be installed in the test site') - ->addArgument('project', InputArgument::OPTIONAL, 'GitHub project, EG: moodlehq/moodle-local_hub') - ->addOption('branch', 'b', InputOption::VALUE_REQUIRED, 'The branch to checkout within the plugin', 'master') - ->addOption('clone', 'c', InputOption::VALUE_REQUIRED, 'Git clone URL') + ->addArgument('project', InputArgument::OPTIONAL, 'GitHub project, EG: moodlehq/moodle-local_hub, can\'t be used with --clone option') + ->addOption('branch', 'b', InputOption::VALUE_REQUIRED, 'The branch to checkout in plugin repo (if non-default)', null) + ->addOption('clone', 'c', InputOption::VALUE_REQUIRED, 'Git clone URL, can\'t be used with --project option') ->addOption('storage', null, InputOption::VALUE_REQUIRED, 'Plugin storage directory', 'moodle-plugin-ci-plugins'); } @@ -82,8 +82,9 @@ protected function execute(InputInterface $input, OutputInterface $output) $filesystem->mkdir($storage); $storageDir = realpath($validate->directory($storage)); + $branch = $branch !== null ? '--branch '.$branch : ''; /** @psalm-suppress PossiblyInvalidArgument */ - $cloneUrl = sprintf('git clone --depth 1 --branch %s %s', $branch, $cloneUrl); + $cloneUrl = sprintf('git clone --depth 1 %s %s', $branch, $cloneUrl); $process = new Process($cloneUrl, $storageDir); $this->execute->mustRun($process); diff --git a/src/Command/ExecuteTrait.php b/src/Command/ExecuteTrait.php index c82c1157..8e99ad0d 100644 --- a/src/Command/ExecuteTrait.php +++ b/src/Command/ExecuteTrait.php @@ -34,6 +34,12 @@ trait ExecuteTrait */ protected function initializeExecute(OutputInterface $output, ProcessHelper $helper) { - $this->execute = $this->execute ?: new Execute($output, $helper); + if (isset($this->execute)) { + // Define output and process helper. + $this->execute->setOutput($output); + $this->execute->setHelper($helper); + } else { + $this->execute = new Execute($output, $helper); + } } } diff --git a/src/Process/Execute.php b/src/Process/Execute.php index cd18d7f0..b21c51f0 100644 --- a/src/Process/Execute.php +++ b/src/Process/Execute.php @@ -12,7 +12,10 @@ namespace MoodlePluginCI\Process; +use Symfony\Component\Console\Helper\DebugFormatterHelper; +use Symfony\Component\Console\Helper\HelperSet; use Symfony\Component\Console\Helper\ProcessHelper; +use Symfony\Component\Console\Output\NullOutput; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Process\Exception\ProcessFailedException; use Symfony\Component\Process\Process; @@ -25,12 +28,12 @@ class Execute /** * @var OutputInterface */ - private $output; + protected $output; /** * @var ProcessHelper */ - private $helper; + protected $helper; /** * Sleep for .2 seconds to avoid race conditions in Moodle scripts when running them in parallel. @@ -41,9 +44,42 @@ class Execute */ public $parallelWaitTime = 200000; - public function __construct(OutputInterface $output, ProcessHelper $helper) + /** + * TODO: Add nullable type declaration for params when we switch to php 7.1. + * + * @param OutputInterface|null $output + * @param ProcessHelper|null $helper + */ + public function __construct($output = null, $helper = null) + { + $this->setOutput($output); + $this->setHelper($helper); + } + + /** + * Output setter. + * TODO: Add nullable type declaration for param when we switch to php 7.1. + * + * @param OutputInterface|null $output + */ + public function setOutput($output) + { + $this->output = $output ?? new NullOutput(); + } + + /** + * Process helper setter. + * TODO: Add nullable type declaration for param when we switch to php 7.1. + * + * @param ProcessHelper|null $helper + */ + public function setHelper($helper) { - $this->output = $output; + if (empty($helper)) { + $helper = new ProcessHelper(); + // Looks like $helper->run is not possible without DebugFormatterHelper. + $helper->setHelperSet(new HelperSet([new DebugFormatterHelper()])); + } $this->helper = $helper; } diff --git a/tests/Command/AddPluginCommandTest.php b/tests/Command/AddPluginCommandTest.php index 8007f363..05875f2a 100644 --- a/tests/Command/AddPluginCommandTest.php +++ b/tests/Command/AddPluginCommandTest.php @@ -16,6 +16,7 @@ use MoodlePluginCI\Tests\Fake\Process\DummyExecute; use MoodlePluginCI\Tests\FilesystemTestCase; use Symfony\Component\Console\Application; +use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Tester\CommandTester; class AddPluginCommandTest extends FilesystemTestCase @@ -51,12 +52,36 @@ public function testExecute() public function testExecuteWithClone() { $commandTester = $this->getCommandTester(); + // Execute with verbosity, so process helper outputs command line. $commandTester->execute([ '--clone' => 'https://github.com/user/moodle-mod_foo.git', '--storage' => $this->tempDir.'/plugins', - ]); + ], ['verbosity' => OutputInterface::VERBOSITY_VERY_VERBOSE]); + + $this->assertSame(0, $commandTester->getStatusCode()); + $this->assertContains('git clone --depth 1 https://github.com/user/moodle-mod_foo.git', + $commandTester->getDisplay()); + $this->assertTrue(is_dir($this->tempDir.'/plugins')); + $this->assertFileExists($this->tempDir.'/.env'); + $this->assertSame( + sprintf("EXTRA_PLUGINS_DIR=%s/plugins\n", realpath($this->tempDir)), + file_get_contents($this->tempDir.'/.env') + ); + } + + public function testExecuteWithCloneAndBranch() + { + $commandTester = $this->getCommandTester(); + // Execute with verbosity, so process helper outputs command line. + $commandTester->execute([ + '--clone' => 'https://github.com/user/moodle-mod_foo.git', + '--branch' => 'dev', + '--storage' => $this->tempDir.'/plugins', + ], ['verbosity' => OutputInterface::VERBOSITY_VERY_VERBOSE]); $this->assertSame(0, $commandTester->getStatusCode()); + $this->assertContains('git clone --depth 1 --branch dev https://github.com/user/moodle-mod_foo.git', + $commandTester->getDisplay()); $this->assertTrue(is_dir($this->tempDir.'/plugins')); $this->assertFileExists($this->tempDir.'/.env'); $this->assertSame( diff --git a/tests/Fake/Process/DummyExecute.php b/tests/Fake/Process/DummyExecute.php index 26327387..4d06cc06 100644 --- a/tests/Fake/Process/DummyExecute.php +++ b/tests/Fake/Process/DummyExecute.php @@ -17,20 +17,46 @@ class DummyExecute extends Execute { - /** @noinspection PhpMissingParentConstructorInspection */ - public function __construct() + private function getMockProcess($cmd) { - // Do nothing. + $process = \Mockery::mock('Symfony\Component\Process\Process'); + $process->shouldReceive( + 'setTimeout', + 'run', + 'wait', + 'stop', + 'getExitCode', + 'getExitCodeText', + 'getWorkingDirectory', + 'isOutputDisabled', + 'getErrorOutput' + ); + + $process->shouldReceive('isSuccessful')->andReturn(true); + $process->shouldReceive('getOutput')->andReturn(''); + $process->shouldReceive('getCommandLine')->andReturn($cmd); + + return $process; } public function run($cmd, $error = null) { - return new DummyProcess('dummy'); + if ($cmd instanceof Process) { + // Get the command line from process. + $cmd = $cmd->getCommandLine(); + } + + return $this->helper->run($this->output, $this->getMockProcess($cmd), $error); } public function mustRun($cmd, $error = null) { - return new DummyProcess('dummy'); + if ($cmd instanceof Process) { + // Get the command line from process. + $cmd = $cmd->getCommandLine(); + } + + return $this->helper->mustRun($this->output, $this->getMockProcess($cmd), $error); } public function runAll($processes) @@ -45,15 +71,15 @@ public function mustRunAll($processes) public function passThrough($commandline, $cwd = null, $timeout = null) { - return $this->passThroughProcess(new DummyProcess($commandline, $cwd, null, null, $timeout)); + return $this->passThroughProcess($this->getMockProcess($commandline)); } public function passThroughProcess(Process $process) { - if ($process instanceof DummyProcess) { + if ($process instanceof \Mockery\MockInterface) { return $process; } - return new DummyProcess($process->getCommandLine(), $process->getWorkingDirectory(), null, null, $process->getTimeout()); + return $this->getMockProcess($process->getCommandLine()); } } diff --git a/tests/Fake/Process/DummyProcess.php b/tests/Fake/Process/DummyProcess.php deleted file mode 100644 index 4a06c882..00000000 --- a/tests/Fake/Process/DummyProcess.php +++ /dev/null @@ -1,28 +0,0 @@ -setHelperSet(new HelperSet([new DebugFormatterHelper()])); - - $execute = new Execute(new NullOutput(), $helper); + $execute = new Execute(); $pathenv = getenv('PATH'); // RUNTIME_NVM_BIN in undefined. @@ -69,10 +62,7 @@ public function testSetNodeEnv() public function testRun() { - $helper = new ProcessHelper(); - $helper->setHelperSet(new HelperSet([new DebugFormatterHelper()])); - - $execute = new Execute(new NullOutput(), $helper); + $execute = new Execute(); $process = $execute->run('env'); $this->assertInstanceOf('Symfony\Component\Process\Process', $process); @@ -83,10 +73,7 @@ public function testRun() public function testMustRun() { - $helper = new ProcessHelper(); - $helper->setHelperSet(new HelperSet([new DebugFormatterHelper()])); - - $execute = new Execute(new NullOutput(), $helper); + $execute = new Execute(); $process = $execute->mustRun('env'); $this->assertInstanceOf('Symfony\Component\Process\Process', $process); @@ -97,9 +84,6 @@ public function testMustRun() public function testRunAllVerbose() { - $helper = new ProcessHelper(); - $helper->setHelperSet(new HelperSet([new DebugFormatterHelper()])); - /** @var Process[] $processes */ $processes = [ new Process('env'), @@ -107,7 +91,7 @@ public function testRunAllVerbose() ]; $output = new BufferedOutput(OutputInterface::VERBOSITY_VERY_VERBOSE); - $execute = new Execute($output, $helper); + $execute = new Execute($output); $execute->runAll($processes); foreach ($processes as $process) { @@ -120,9 +104,6 @@ public function testRunAllVerbose() public function testMustRunAll() { - $helper = new ProcessHelper(); - $helper->setHelperSet(new HelperSet([new DebugFormatterHelper()])); - /** @var Process[] $processes */ $processes = [ new Process('env'), @@ -130,7 +111,7 @@ public function testMustRunAll() new Process('env'), ]; - $execute = new Execute(new NullOutput(), $helper); + $execute = new Execute(); $execute->parallelWaitTime = 1; $execute->mustRunAll($processes); @@ -146,9 +127,6 @@ public function testMustRunAllFail() { $this->expectException(ProcessFailedException::class); - $helper = new ProcessHelper(); - $helper->setHelperSet(new HelperSet([new DebugFormatterHelper()])); - /** @var Process[] $processes */ $processes = [ new Process('php -r "echo 42;"'), @@ -156,8 +134,7 @@ public function testMustRunAllFail() new Process('php -r "echo 42;"'), ]; - $execute = new Execute(new NullOutput(), $helper); - + $execute = new Execute(); $execute->parallelWaitTime = 1; $execute->mustRunAll($processes); } @@ -165,7 +142,7 @@ public function testMustRunAllFail() public function testPassThrough() { $output = new BufferedOutput(OutputInterface::VERBOSITY_VERY_VERBOSE); - $execute = new Execute($output, new ProcessHelper()); + $execute = new Execute($output); $process = $execute->passThrough('php -r "echo 42;"'); $this->assertInstanceOf('Symfony\Component\Process\Process', $process);