From 5ed6607d37558564e3fc52f764746e2fca5a196b Mon Sep 17 00:00:00 2001 From: Alex Skrypnyk Date: Sat, 9 Mar 2024 07:46:22 +1100 Subject: [PATCH] Changed coding standards to Drupal. --- composer.json | 9 +- phpcs.xml | 45 +- src/Artifact.php | 1709 ++++++++--------- src/Commands/ArtifactCommand.php | 162 +- src/FilesystemTrait.php | 421 ++-- src/GitTrait.php | 689 ++++--- src/TokenTrait.php | 86 +- src/app.php | 2 +- tests/phpunit/AbstractTestCase.php | 121 +- tests/phpunit/Exception/ErrorException.php | 24 +- .../Functional/AbstractFunctionalTestCase.php | 343 ++-- tests/phpunit/Functional/BranchTest.php | 258 ++- tests/phpunit/Functional/ForcePushTest.php | 548 +++--- tests/phpunit/Functional/GeneralTest.php | 188 +- tests/phpunit/Functional/TagTest.php | 52 +- tests/phpunit/Functional/TokenTest.php | 218 ++- tests/phpunit/Traits/CommandTrait.php | 984 +++++----- tests/phpunit/Traits/MockTrait.php | 86 +- tests/phpunit/Traits/ReflectionTrait.php | 81 +- tests/phpunit/Unit/AbstractUnitTestCase.php | 45 +- tests/phpunit/Unit/ExcludeTest.php | 343 ++-- 21 files changed, 3137 insertions(+), 3277 deletions(-) diff --git a/composer.json b/composer.json index a63d9f7..4f7b311 100644 --- a/composer.json +++ b/composer.json @@ -17,20 +17,19 @@ "require": { "php": ">=8.1", "cpliakas/git-wrapper": "^3.1", + "monolog/monolog": "^3.5", "symfony/console": "^6", - "symfony/finder": "^6", "symfony/filesystem": "^6", - "monolog/monolog": "^3.5" + "symfony/finder": "^6" }, "require-dev": { "bamarni/composer-bin-plugin": "^1.8", "dealerdirect/phpcodesniffer-composer-installer": "^1.0", - "escapestudios/symfony2-coding-standard": "^3", + "drupal/coder": "^8.3", "phpmd/phpmd": "^2.15", "phpstan/phpstan": "^1.10", "phpunit/phpunit": "^10.5", - "rector/rector": "^1.0.0", - "squizlabs/php_codesniffer": "^3.6" + "rector/rector": "^1.0.0" }, "autoload": { "psr-4": { diff --git a/phpcs.xml b/phpcs.xml index efe3f8a..3be9ccb 100644 --- a/phpcs.xml +++ b/phpcs.xml @@ -3,8 +3,8 @@ Custom PHPCS standard. - - + + @@ -13,37 +13,26 @@ - src tests/phpunit - - *.* - - - *.* - - - *.* - - - - + + + *.Test\.php + *.TestCase\.php + *.test - - - tests/*.Test\.php - tests/*.TestCase\.php + + + *.Test\.php + *.TestCase\.php + *.test - - tests/*.Test\.php - tests/*.TestCase\.php + + + *.Test\.php + *.TestCase\.php + *.test diff --git a/src/Artifact.php b/src/Artifact.php index 688cb84..c3323b9 100644 --- a/src/Artifact.php +++ b/src/Artifact.php @@ -1,11 +1,10 @@ fsFileSystem = $fsFileSystem; - $this->gitWrapper = $gitWrapper; - } + $this->fsFileSystem = $fsFileSystem; + $this->gitWrapper = $gitWrapper; + } - /** - * Push artifact of current repository to remote git repository. - * - * @param string $remote - * Path to the remote git repository. - * @param array $opts - * Options. - * - * @option $branch Destination branch with optional tokens. - * @option $debug Print debug information. - * @option $gitignore Path to gitignore file to replace current .gitignore. - * @option $message Commit message with optional tokens. - * @option $mode Mode of artifact build: branch, force-push or diff. - * Defaults to force-push. - * @option $now Internal value used to set internal time. - * @option $no-cleanup Do not cleanup after run. - * @option $push Push artifact to the remote repository. Defaults to FALSE. - * @option $report Path to the report file. - * @option $root Path to the root for file path resolution. If not - * specified, current directory is used. - * @option $show-changes Show changes made to the repo by the build in the - * output. - * @option $src Directory where source repository is located. If not - * specified, root directory is used. - * - * @throws \Exception - * - * @phpstan-ignore-next-line - */ - public function artifact(string $remote, array $opts = [ - 'branch' => '[branch]', - 'debug' => false, - 'gitignore' => '', - 'message' => 'Deployment commit', - 'mode' => 'force-push', - 'no-cleanup' => false, - 'now' => '', - 'push' => false, - 'report' => '', - 'root' => '', - 'show-changes' => false, - 'src' => '', - ]): void - { - try { - $error = null; - - $this->checkRequirements(); - $this->resolveOptions($opts); - - $this->printDebug('Debug messages enabled'); - - $this->gitSetDst($remote); - - $this->showInfo(); - $this->prepareArtifact(); - - if ($this->needsPush) { - $this->doPush(); - } else { - $this->yell('Cowardly refusing to push to remote. Use --push option to perform an actual push.'); - } - $this->result = true; - } catch (\Exception $exception) { - // Capture message and allow to rollback. - $error = $exception->getMessage(); - } + /** + * Push artifact of current repository to remote git repository. + * + * @param string $remote + * Path to the remote git repository. + * @param array $opts + * Options. + * + * @option $branch Destination branch with optional tokens. + * @option $debug Print debug information. + * @option $gitignore Path to gitignore file to replace current .gitignore. + * @option $message Commit message with optional tokens. + * @option $mode Mode of artifact build: branch, force-push or diff. + * Defaults to force-push. + * @option $now Internal value used to set internal time. + * @option $no-cleanup Do not cleanup after run. + * @option $push Push artifact to the remote repository. Defaults to FALSE. + * @option $report Path to the report file. + * @option $root Path to the root for file path resolution. If not + * specified, current directory is used. + * @option $show-changes Show changes made to the repo by the build in the + * output. + * @option $src Directory where source repository is located. If not + * specified, root directory is used. + * + * @throws \Exception + * + * @phpstan-ignore-next-line + */ + public function artifact(string $remote, array $opts = [ + 'branch' => '[branch]', + 'debug' => FALSE, + 'gitignore' => '', + 'message' => 'Deployment commit', + 'mode' => 'force-push', + 'no-cleanup' => FALSE, + 'now' => '', + 'push' => FALSE, + 'report' => '', + 'root' => '', + 'show-changes' => FALSE, + 'src' => '', + ]): void { + try { + $error = NULL; + + $this->checkRequirements(); + $this->resolveOptions($opts); + + $this->printDebug('Debug messages enabled'); + + $this->gitSetDst($remote); + + $this->showInfo(); + $this->prepareArtifact(); + + if ($this->needsPush) { + $this->doPush(); + } + else { + $this->yell('Cowardly refusing to push to remote. Use --push option to perform an actual push.'); + } + $this->result = TRUE; + } + catch (\Exception $exception) { + // Capture message and allow to rollback. + $error = $exception->getMessage(); + } + + if ($this->report) { + $this->dumpReport(); + } + + if ($this->needCleanup) { + $this->cleanup(); + } + + if ($this->result) { + $this->say('Deployment finished successfully.'); + } + else { + $this->say('Deployment failed.'); + throw new \Exception((string) $error); + } + } - if ($this->report) { - $this->dumpReport(); - } + /** + * Branch mode. + * + * @return string + * Branch mode name. + */ + public static function modeBranch(): string { + return 'branch'; + } - if ($this->needCleanup) { - $this->cleanup(); - } + /** + * Force-push mode. + * + * @return string + * Force-push mode name. + */ + public static function modeForcePush(): string { + return 'force-push'; + } - if ($this->result) { - $this->say('Deployment finished successfully.'); - } else { - $this->say('Deployment failed.'); - throw new \Exception((string) $error); - } - } + /** + * Diff mode. + * + * @return string + * Diff mode name. + */ + public static function modeDiff(): string { + return 'diff'; + } - /** - * Branch mode. - * - * @return string - * Branch mode name. - */ - public static function modeBranch(): string - { - return 'branch'; - } + /** + * Prepare artifact to be then deployed. + * + * @throws \Exception + */ + protected function prepareArtifact(): void { + $this->gitSwitchToBranch($this->src, $this->artifactBranch, TRUE); - /** - * Force-push mode. - * - * @return string - * Force-push mode name. - */ - public static function modeForcePush(): string - { - return 'force-push'; - } + $this->removeSubRepos($this->src); + $this->disableLocalExclude($this->src); - /** - * Diff mode. - * - * @return string - * Diff mode name. - */ - public static function modeDiff(): string - { - return 'diff'; + if (!empty($this->gitignoreFile)) { + $this->replaceGitignore($this->gitignoreFile, $this->src); + $this->gitAddAll($this->src); + $this->removeIgnoredFiles($this->src); + } + else { + $this->gitAddAll($this->src); } - /** - * Prepare artifact to be then deployed. - * - * @throws \Exception - */ - protected function prepareArtifact(): void - { - $this->gitSwitchToBranch($this->src, $this->artifactBranch, true); - - $this->removeSubRepos($this->src); - $this->disableLocalExclude($this->src); - - if (!empty($this->gitignoreFile)) { - $this->replaceGitignore($this->gitignoreFile, $this->src); - $this->gitAddAll($this->src); - $this->removeIgnoredFiles($this->src); - } else { - $this->gitAddAll($this->src); - } - - $this->removeOtherFiles($this->src); - - $result = $this->gitCommit($this->src, $this->message); + $this->removeOtherFiles($this->src); - if ($this->showChanges) { - $this->say(sprintf('Added changes: %s', $result)); - } - } + $result = $this->gitCommit($this->src, $this->message); - /** - * Cleanup after build. - * - * @throws \Exception - */ - protected function cleanup(): void - { - $this->restoreLocalExclude($this->src); - $this->gitSwitchToBranch($this->src, (string) $this->originalBranch); - $this->gitRemoveBranch($this->src, $this->artifactBranch); - $this->gitRemoveRemote($this->src, $this->remoteName); + if ($this->showChanges) { + $this->say(sprintf('Added changes: %s', $result)); } + } - /** - * Perform actual push to remote. - * - * @throws \Exception - */ - protected function doPush(): void - { - if (!$this->gitRemoteExists($this->src, $this->remoteName)) { - $this->gitAddRemote($this->src, $this->remoteName, $this->dst); - } + /** + * Cleanup after build. + * + * @throws \Exception + */ + protected function cleanup(): void { + $this->restoreLocalExclude($this->src); + $this->gitSwitchToBranch($this->src, (string) $this->originalBranch); + $this->gitRemoveBranch($this->src, $this->artifactBranch); + $this->gitRemoveRemote($this->src, $this->remoteName); + } - try { - $this->gitPush( - $this->src, - $this->artifactBranch, - $this->remoteName, + /** + * Perform actual push to remote. + * + * @throws \Exception + */ + protected function doPush(): void { + if (!$this->gitRemoteExists($this->src, $this->remoteName)) { + $this->gitAddRemote($this->src, $this->remoteName, $this->dst); + } + + try { + $this->gitPush( + $this->src, + $this->artifactBranch, + $this->remoteName, + $this->dstBranch, + $this->mode === self::modeForcePush() + ); + $this->sayOkay(sprintf('Pushed branch "%s" with commit message "%s"', $this->dstBranch, $this->message)); + } + catch (\Exception $exception) { + // Re-throw the message with additional context. + throw new \Exception( + sprintf( + 'Error occurred while pushing branch "%s" with commit message "%s"', $this->dstBranch, - $this->mode === self::modeForcePush() + $this->message + ), + $exception->getCode(), + $exception ); - $this->sayOkay(sprintf('Pushed branch "%s" with commit message "%s"', $this->dstBranch, $this->message)); - } catch (\Exception $exception) { - // Re-throw the message with additional context. - throw new \Exception( - sprintf( - 'Error occurred while pushing branch "%s" with commit message "%s"', - $this->dstBranch, - $this->message - ), - $exception->getCode(), - $exception - ); - } } + } - /** - * Resolve and validate CLI options values into internal values. - * - * @param array $options - * Array of CLI options. - * - * @throws \Exception - * - * @phpstan-ignore-next-line - */ - protected function resolveOptions(array $options): void - { - $this->now = empty($options['now']) ? time() : (int) $options['now']; + /** + * Resolve and validate CLI options values into internal values. + * + * @param array $options + * Array of CLI options. + * + * @throws \Exception + * + * @phpstan-ignore-next-line + */ + protected function resolveOptions(array $options): void { + $this->now = empty($options['now']) ? time() : (int) $options['now']; - $this->debug = !empty($options['debug']); + $this->debug = !empty($options['debug']); - $this->remoteName = 'dst'; + $this->remoteName = 'dst'; - $this->fsSetRootDir($options['root']); + $this->fsSetRootDir($options['root']); - // Default source to the root directory. - $srcPath = empty($options['src']) ? $this->fsGetRootDir() : $this->fsGetAbsolutePath($options['src']); - $this->gitSetSrcRepo($srcPath); + // Default source to the root directory. + $srcPath = empty($options['src']) ? $this->fsGetRootDir() : $this->fsGetAbsolutePath($options['src']); + $this->gitSetSrcRepo($srcPath); - $this->originalBranch = $this->resolveOriginalBranch($this->src); - $this->setDstBranch($options['branch']); - $this->artifactBranch = $this->dstBranch.'-artifact'; + $this->originalBranch = $this->resolveOriginalBranch($this->src); + $this->setDstBranch($options['branch']); + $this->artifactBranch = $this->dstBranch . '-artifact'; - $this->setMessage($options['message']); + $this->setMessage($options['message']); - if (!empty($options['gitignore'])) { - $this->setGitignoreFile($options['gitignore']); - } + if (!empty($options['gitignore'])) { + $this->setGitignoreFile($options['gitignore']); + } - $this->showChanges = !empty($options['show-changes']); + $this->showChanges = !empty($options['show-changes']); - $this->needCleanup = empty($options['no-cleanup']); + $this->needCleanup = empty($options['no-cleanup']); - $this->needsPush = !empty($options['push']); + $this->needsPush = !empty($options['push']); - $this->report = empty($options['report']) ? null : $options['report']; + $this->report = empty($options['report']) ? NULL : $options['report']; - $this->setMode($options['mode'], $options); - } + $this->setMode($options['mode'], $options); + } - /** - * Show artifact build information. - */ - protected function showInfo(): void - { - $lines[] = ('----------------------------------------------------------------------'); - $lines[] = (' Artifact information'); - $lines[] = ('----------------------------------------------------------------------'); - $lines[] = (' Build timestamp: '.date('Y/m/d H:i:s', $this->now)); - $lines[] = (' Mode: '.$this->mode); - $lines[] = (' Source repository: '.$this->src); - $lines[] = (' Remote repository: '.$this->dst); - $lines[] = (' Remote branch: '.$this->dstBranch); - $lines[] = (' Gitignore file: '.($this->gitignoreFile ? $this->gitignoreFile : 'No')); - $lines[] = (' Will push: '.($this->needsPush ? 'Yes' : 'No')); - $lines[] = ('----------------------------------------------------------------------'); - $this->output->writeln($lines); - } + /** + * Show artifact build information. + */ + protected function showInfo(): void { + $lines[] = ('----------------------------------------------------------------------'); + $lines[] = (' Artifact information'); + $lines[] = ('----------------------------------------------------------------------'); + $lines[] = (' Build timestamp: ' . date('Y/m/d H:i:s', $this->now)); + $lines[] = (' Mode: ' . $this->mode); + $lines[] = (' Source repository: ' . $this->src); + $lines[] = (' Remote repository: ' . $this->dst); + $lines[] = (' Remote branch: ' . $this->dstBranch); + $lines[] = (' Gitignore file: ' . ($this->gitignoreFile ? $this->gitignoreFile : 'No')); + $lines[] = (' Will push: ' . ($this->needsPush ? 'Yes' : 'No')); + $lines[] = ('----------------------------------------------------------------------'); + $this->output->writeln($lines); + } - /** - * Dump artifact report to a file. - */ - protected function dumpReport(): void - { - $lines[] = '----------------------------------------------------------------------'; - $lines[] = ' Artifact report'; - $lines[] = '----------------------------------------------------------------------'; - $lines[] = ' Build timestamp: '.date('Y/m/d H:i:s', $this->now); - $lines[] = ' Mode: '.$this->mode; - $lines[] = ' Source repository: '.$this->src; - $lines[] = ' Remote repository: '.$this->dst; - $lines[] = ' Remote branch: '.$this->dstBranch; - $lines[] = ' Gitignore file: '.($this->gitignoreFile ? $this->gitignoreFile : 'No'); - $lines[] = ' Commit message: '.$this->message; - $lines[] = ' Push result: '.($this->result ? 'Success' : 'Failure'); - $lines[] = '----------------------------------------------------------------------'; - - $this->fsFileSystem->dumpFile($this->report, implode(PHP_EOL, $lines)); - } + /** + * Dump artifact report to a file. + */ + protected function dumpReport(): void { + $lines[] = '----------------------------------------------------------------------'; + $lines[] = ' Artifact report'; + $lines[] = '----------------------------------------------------------------------'; + $lines[] = ' Build timestamp: ' . date('Y/m/d H:i:s', $this->now); + $lines[] = ' Mode: ' . $this->mode; + $lines[] = ' Source repository: ' . $this->src; + $lines[] = ' Remote repository: ' . $this->dst; + $lines[] = ' Remote branch: ' . $this->dstBranch; + $lines[] = ' Gitignore file: ' . ($this->gitignoreFile ? $this->gitignoreFile : 'No'); + $lines[] = ' Commit message: ' . $this->message; + $lines[] = ' Push result: ' . ($this->result ? 'Success' : 'Failure'); + $lines[] = '----------------------------------------------------------------------'; + + $this->fsFileSystem->dumpFile($this->report, implode(PHP_EOL, $lines)); + } - /** - * Set build mode. - * - * @param string $mode - * Mode to set. - * @param array $options - * Array of CLI options. - * - * @phpstan-ignore-next-line - */ - protected function setMode(string $mode, array $options): void - { - $this->say(sprintf('Running in "%s" mode', $mode)); - - switch ($mode) { - case self::modeForcePush(): - // Intentionally empty. - break; - - case self::modeBranch(): - if (!$this->hasToken($options['branch'])) { - $this->say('WARNING! Provided branch name does not have a token. + /** + * Set build mode. + * + * @param string $mode + * Mode to set. + * @param array $options + * Array of CLI options. + * + * @phpstan-ignore-next-line + */ + protected function setMode(string $mode, array $options): void { + $this->say(sprintf('Running in "%s" mode', $mode)); + + switch ($mode) { + case self::modeForcePush(): + // Intentionally empty. + break; + + case self::modeBranch(): + if (!$this->hasToken($options['branch'])) { + $this->say('WARNING! Provided branch name does not have a token. Pushing of the artifact into this branch will fail on second and follow up pushes to remote. Consider adding tokens with unique values to the branch name.'); - } - break; - - case self::modeDiff(): - throw new \RuntimeException('Diff mode is not yet implemented.'); - - default: - throw new \RuntimeException(sprintf('Invalid mode provided. Allowed modes are: %s', implode(', ', [ - self::modeForcePush(), - self::modeBranch(), - self::modeDiff(), - ]))); } + break; - $this->mode = $mode; - } - - /** - * Resolve original branch to handle detached repositories. - * - * Usually, repository become detached when a tag is checked out. - * - * @param string $location - * Path to repository. - * - * @return null|string - * Branch or detachment source. - * - * @throws \Exception - * If neither branch nor detachment source is not found. - */ - protected function resolveOriginalBranch(string $location): ?string - { - $branch = $this->gitGetCurrentBranch($location); - - // Repository could be in detached state. If this the case - we need to - // capture the source of detachment, if exist. - if ($branch === 'HEAD') { - $branch = null; - $result = $this->gitCommandRun($location, 'branch'); - $branchList = preg_split('/\R/', $result); - if ($branchList) { - $branchList = array_filter($branchList); - foreach ($branchList as $branch) { - if (preg_match('/\* \(.*detached .* ([^\)]+)\)/', $branch, $matches)) { - $branch = $matches[1]; - break; - } - } - } - if (empty($branch)) { - throw new \Exception('Unable to determine detachment source'); - } - } + case self::modeDiff(): + throw new \RuntimeException('Diff mode is not yet implemented.'); - return $branch; + default: + throw new \RuntimeException(sprintf('Invalid mode provided. Allowed modes are: %s', implode(', ', [ + self::modeForcePush(), + self::modeBranch(), + self::modeDiff(), + ]))); } - /** - * Set the branch in the remote repository where commits will be pushed to. - * - * @param string $branch - * Branch in the remote repository. - */ - protected function setDstBranch(string $branch): void - { - $branch = (string) $this->tokenProcess($branch); - - if (!self::gitIsValidBranch($branch)) { - throw new \RuntimeException(sprintf('Incorrect value "%s" specified for git remote branch', $branch)); + $this->mode = $mode; + } + + /** + * Resolve original branch to handle detached repositories. + * + * Usually, repository become detached when a tag is checked out. + * + * @param string $location + * Path to repository. + * + * @return null|string + * Branch or detachment source. + * + * @throws \Exception + * If neither branch nor detachment source is not found. + */ + protected function resolveOriginalBranch(string $location): ?string { + $branch = $this->gitGetCurrentBranch($location); + + // Repository could be in detached state. If this the case - we need to + // capture the source of detachment, if exist. + if ($branch === 'HEAD') { + $branch = NULL; + $result = $this->gitCommandRun($location, 'branch'); + $branchList = preg_split('/\R/', $result); + if ($branchList) { + $branchList = array_filter($branchList); + foreach ($branchList as $branch) { + if (preg_match('/\* \(.*detached .* ([^\)]+)\)/', $branch, $matches)) { + $branch = $matches[1]; + break; + } } - $this->dstBranch = $branch; + } + if (empty($branch)) { + throw new \Exception('Unable to determine detachment source'); + } } - /** - * Set commit message. - * - * @param string $message - * Commit message to set on the deployment commit. - */ - protected function setMessage(string $message): void - { - $message = (string) $this->tokenProcess($message); - $this->message = $message; - } + return $branch; + } - /** - * Set replacement gitignore file path location. - * - * @param string $path - * Path to the replacement .gitignore file. - * - * @throws \Exception - */ - protected function setGitignoreFile(string $path): void - { - $path = $this->fsGetAbsolutePath($path); - $this->fsPathsExist($path); - $this->gitignoreFile = $path; - } + /** + * Set the branch in the remote repository where commits will be pushed to. + * + * @param string $branch + * Branch in the remote repository. + */ + protected function setDstBranch(string $branch): void { + $branch = (string) $this->tokenProcess($branch); - /** - * Check that there all requirements are met in order to to run this - * command. - */ - protected function checkRequirements(): void - { - // @todo: Refactor this into more generic implementation. - $this->say('Checking requirements'); - if (!$this->fsIsCommandAvailable('git')) { - throw new \RuntimeException('At least one of the script running requirements was not met'); - } - $this->sayOkay('All requirements were met'); + if (!self::gitIsValidBranch($branch)) { + throw new \RuntimeException(sprintf('Incorrect value "%s" specified for git remote branch', $branch)); } + $this->dstBranch = $branch; + } - /** - * Replace gitignore file with provided file. - * - * @param string $filename - * Path to new gitignore to replace current file with. - * @param string $path - * Path to repository. - */ - protected function replaceGitignore(string $filename, string $path): void - { - $this->printDebug('Replacing .gitignore: %s with %s', $path.DIRECTORY_SEPARATOR.'.gitignore', $filename); - $this->fsFileSystem->copy($filename, $path.DIRECTORY_SEPARATOR.'.gitignore', true); - $this->fsFileSystem->remove($filename); - } + /** + * Set commit message. + * + * @param string $message + * Commit message to set on the deployment commit. + */ + protected function setMessage(string $message): void { + $message = (string) $this->tokenProcess($message); + $this->message = $message; + } - /** - * Helper to get a file name of the local exclude file. - * - * @param string $path - * Path to directory. - * - * @return string - * Exclude file name path. - */ - protected function getLocalExcludeFileName(string $path): string - { - return $path.DIRECTORY_SEPARATOR.'.git'.DIRECTORY_SEPARATOR.'info'.DIRECTORY_SEPARATOR.'exclude'; - } + /** + * Set replacement gitignore file path location. + * + * @param string $path + * Path to the replacement .gitignore file. + * + * @throws \Exception + */ + protected function setGitignoreFile(string $path): void { + $path = $this->fsGetAbsolutePath($path); + $this->fsPathsExist($path); + $this->gitignoreFile = $path; + } - /** - * Check if local exclude (.git/info/exclude) file exists. - * - * @param string $path - * Path to repository. - * - * @return bool - * True if exists, false otherwise. - */ - protected function localExcludeExists(string $path): bool - { - return $this->fsFileSystem->exists($this->getLocalExcludeFileName($path)); + /** + * Check that there all requirements are met in order to to run this command. + */ + protected function checkRequirements(): void { + // @todo Refactor this into more generic implementation. + $this->say('Checking requirements'); + if (!$this->fsIsCommandAvailable('git')) { + throw new \RuntimeException('At least one of the script running requirements was not met'); } + $this->sayOkay('All requirements were met'); + } - /** - * Check if local exclude (.git/info/exclude) file is empty. - * - * @param string $path - * Path to repository. - * @param bool $strict - * Flag to check if the file is empty. If false, comments and empty lines - * are considered as empty. - * - * @return bool - * - true, if $strict is true and file has no records. - * - false, if $strict is true and file has some records. - * - true, if $strict is false and file has only empty lines and comments. - * - false, if $strict is false and file lines other than empty lines or - * comments. - * - * @throws \Exception - */ - protected function localExcludeEmpty(string $path, bool $strict = false): bool - { - if (!$this->localExcludeExists($path)) { - throw new \Exception(sprintf('File "%s" does not exist', $path)); - } - - $filename = $this->getLocalExcludeFileName($path); - if ($strict) { - return empty(file_get_contents($filename)); - } - $lines = file($filename); - if ($lines) { - $lines = array_map('trim', $lines); - $lines = array_filter($lines, static function ($line) : bool { - return strlen($line) > 0; - }); - $lines = array_filter($lines, static function ($line) : bool { - return !str_starts_with(trim($line), '#'); - }); - } - + /** + * Replace gitignore file with provided file. + * + * @param string $filename + * Path to new gitignore to replace current file with. + * @param string $path + * Path to repository. + */ + protected function replaceGitignore(string $filename, string $path): void { + $this->printDebug('Replacing .gitignore: %s with %s', $path . DIRECTORY_SEPARATOR . '.gitignore', $filename); + $this->fsFileSystem->copy($filename, $path . DIRECTORY_SEPARATOR . '.gitignore', TRUE); + $this->fsFileSystem->remove($filename); + } - return empty($lines); - } + /** + * Helper to get a file name of the local exclude file. + * + * @param string $path + * Path to directory. + * + * @return string + * Exclude file name path. + */ + protected function getLocalExcludeFileName(string $path): string { + return $path . DIRECTORY_SEPARATOR . '.git' . DIRECTORY_SEPARATOR . 'info' . DIRECTORY_SEPARATOR . 'exclude'; + } - /** - * Disable local exclude file (.git/info/exclude). - * - * @param string $path - * Path to repository. - */ - protected function disableLocalExclude(string $path): void - { - $filename = $this->getLocalExcludeFileName($path); - $filenameDisabled = $filename.'.bak'; - if ($this->fsFileSystem->exists($filename)) { - $this->printDebug('Disabling local exclude'); - $this->fsFileSystem->rename($filename, $filenameDisabled); - } - } + /** + * Check if local exclude (.git/info/exclude) file exists. + * + * @param string $path + * Path to repository. + * + * @return bool + * True if exists, false otherwise. + */ + protected function localExcludeExists(string $path): bool { + return $this->fsFileSystem->exists($this->getLocalExcludeFileName($path)); + } - /** - * Restore previously disabled local exclude file. - * - * @param string $path - * Path to repository. - */ - protected function restoreLocalExclude(string $path): void - { - $filename = $this->getLocalExcludeFileName($path); - $filenameDisabled = $filename.'.bak'; - if ($this->fsFileSystem->exists($filenameDisabled)) { - $this->printDebug('Restoring local exclude'); - $this->fsFileSystem->rename($filenameDisabled, $filename); - } - } + /** + * Check if local exclude (.git/info/exclude) file is empty. + * + * @param string $path + * Path to repository. + * @param bool $strict + * Flag to check if the file is empty. If false, comments and empty lines + * are considered as empty. + * + * @return bool + * - true, if $strict is true and file has no records. + * - false, if $strict is true and file has some records. + * - true, if $strict is false and file has only empty lines and comments. + * - false, if $strict is false and file lines other than empty lines or + * comments. + * + * @throws \Exception + */ + protected function localExcludeEmpty(string $path, bool $strict = FALSE): bool { + if (!$this->localExcludeExists($path)) { + throw new \Exception(sprintf('File "%s" does not exist', $path)); + } + + $filename = $this->getLocalExcludeFileName($path); + if ($strict) { + return empty(file_get_contents($filename)); + } + $lines = file($filename); + if ($lines) { + $lines = array_map('trim', $lines); + $lines = array_filter($lines, static function ($line) : bool { + return strlen($line) > 0; + }); + $lines = array_filter($lines, static function ($line) : bool { + return !str_starts_with(trim($line), '#'); + }); + } + + return empty($lines); + } - /** - * Update index for all files. - * - * @param string $location - * Path to repository. - * - * @throws \Exception - */ - protected function gitAddAll(string $location): void - { - $result = $this->gitCommandRun( - $location, - 'add -A', - ); + /** + * Disable local exclude file (.git/info/exclude). + * + * @param string $path + * Path to repository. + */ + protected function disableLocalExclude(string $path): void { + $filename = $this->getLocalExcludeFileName($path); + $filenameDisabled = $filename . '.bak'; + if ($this->fsFileSystem->exists($filename)) { + $this->printDebug('Disabling local exclude'); + $this->fsFileSystem->rename($filename, $filenameDisabled); + } + } - $this->printDebug(sprintf("Added all files:\n%s", $result)); - } + /** + * Restore previously disabled local exclude file. + * + * @param string $path + * Path to repository. + */ + protected function restoreLocalExclude(string $path): void { + $filename = $this->getLocalExcludeFileName($path); + $filenameDisabled = $filename . '.bak'; + if ($this->fsFileSystem->exists($filenameDisabled)) { + $this->printDebug('Restoring local exclude'); + $this->fsFileSystem->rename($filenameDisabled, $filename); + } + } - /** - * Update index for all files. - * - * @param string $location - * Path to repository. - * - * @throws \Exception - */ - protected function gitUpdateIndex(string $location): void - { - $finder = new Finder(); - $files = $finder - ->in($location) - ->ignoreDotFiles(false) - ->files(); - - foreach ($files as $file) { - $this->gitCommandRun( - $location, - sprintf('update-index --info-only --add "%s"', $file), - ); - $this->printDebug(sprintf('Updated index for file "%s"', $file)); - } - } + /** + * Update index for all files. + * + * @param string $location + * Path to repository. + * + * @throws \Exception + */ + protected function gitAddAll(string $location): void { + $result = $this->gitCommandRun( + $location, + 'add -A', + ); + + $this->printDebug(sprintf("Added all files:\n%s", $result)); + } - /** - * Remove ignored files. - * - * @param string $location - * Path to repository. - * @param string|null $gitignorePath - * Gitignore file name. - * - * @throws \Exception - * If removal command finished with an error. - */ - protected function removeIgnoredFiles(string $location, string $gitignorePath = null): void - { - $gitignorePath = $gitignorePath ?: $location.DIRECTORY_SEPARATOR.'.gitignore'; - - $gitignoreContent = file_get_contents($gitignorePath); - if (!$gitignoreContent) { - $this->printDebug('Unable to load '.$gitignoreContent); - } else { - $this->printDebug('-----.gitignore---------'); - $this->printDebug($gitignoreContent); - $this->printDebug('-----.gitignore---------'); - } - $command = sprintf('ls-files --directory -i -c --exclude-from=%s %s', $gitignorePath, $location); - $result = $this->gitCommandRun( + /** + * Update index for all files. + * + * @param string $location + * Path to repository. + * + * @throws \Exception + */ + protected function gitUpdateIndex(string $location): void { + $finder = new Finder(); + $files = $finder + ->in($location) + ->ignoreDotFiles(FALSE) + ->files(); + + foreach ($files as $file) { + $this->gitCommandRun( $location, - $command, - 'Unable to remove ignored files', + sprintf('update-index --info-only --add "%s"', $file), ); - $files = preg_split('/\R/', $result); - if (!empty($files)) { - $files = array_filter($files); - foreach ($files as $file) { - $fileName = $location.DIRECTORY_SEPARATOR.$file; - $this->printDebug('Removing excluded file %s', $fileName); - if ($this->fsFileSystem->exists($fileName)) { - $this->fsFileSystem->remove($fileName); - } - } - } + $this->printDebug(sprintf('Updated index for file "%s"', $file)); } + } - /** - * Remove 'other' files. - * - * 'Other' files are files that are neither staged nor tracked in git. - * - * @param string $location - * Path to repository. - * - * @throws \Exception - * If removal command finished with an error. - */ - protected function removeOtherFiles(string $location): void - { - $command = 'ls-files --others --exclude-standard'; - $result = $this->gitCommandRun( - $location, - $command, - 'Unable to remove other files', - ); - $files = preg_split('/\R/', $result); - if (!empty($files)) { - $files = array_filter($files); - foreach ($files as $file) { - $fileName = $location.DIRECTORY_SEPARATOR.$file; - $this->printDebug('Removing other file %s', $fileName); - $this->fsFileSystem->remove($fileName); - } + /** + * Remove ignored files. + * + * @param string $location + * Path to repository. + * @param string|null $gitignorePath + * Gitignore file name. + * + * @throws \Exception + * If removal command finished with an error. + */ + protected function removeIgnoredFiles(string $location, string $gitignorePath = NULL): void { + $gitignorePath = $gitignorePath ?: $location . DIRECTORY_SEPARATOR . '.gitignore'; + + $gitignoreContent = file_get_contents($gitignorePath); + if (!$gitignoreContent) { + $this->printDebug('Unable to load ' . $gitignoreContent); + } + else { + $this->printDebug('-----.gitignore---------'); + $this->printDebug($gitignoreContent); + $this->printDebug('-----.gitignore---------'); + } + $command = sprintf('ls-files --directory -i -c --exclude-from=%s %s', $gitignorePath, $location); + $result = $this->gitCommandRun( + $location, + $command, + 'Unable to remove ignored files', + ); + $files = preg_split('/\R/', $result); + if (!empty($files)) { + $files = array_filter($files); + foreach ($files as $file) { + $fileName = $location . DIRECTORY_SEPARATOR . $file; + $this->printDebug('Removing excluded file %s', $fileName); + if ($this->fsFileSystem->exists($fileName)) { + $this->fsFileSystem->remove($fileName); } + } } + } - /** - * Remove any repositories within current repository. - * - * @param string $path - * Path to current repository. - */ - protected function removeSubRepos(string $path): void - { - $finder = new Finder(); - $dirs = $finder - ->directories() - ->name('.git') - ->ignoreDotFiles(false) - ->ignoreVCS(false) - ->depth('>0') - ->in($path); - - $dirs = iterator_to_array($dirs->directories()); - - foreach ($dirs as $dir) { - if ($dir instanceof SplFileInfo) { - $dir = $dir->getPathname(); - } - $this->fsFileSystem->remove($dir); - $this->printDebug('Removing sub-repository "%s"', (string) $dir); - } - } + /** + * Remove 'other' files. + * + * 'Other' files are files that are neither staged nor tracked in git. + * + * @param string $location + * Path to repository. + * + * @throws \Exception + * If removal command finished with an error. + */ + protected function removeOtherFiles(string $location): void { + $command = 'ls-files --others --exclude-standard'; + $result = $this->gitCommandRun( + $location, + $command, + 'Unable to remove other files', + ); + $files = preg_split('/\R/', $result); + if (!empty($files)) { + $files = array_filter($files); + foreach ($files as $file) { + $fileName = $location . DIRECTORY_SEPARATOR . $file; + $this->printDebug('Removing other file %s', $fileName); + $this->fsFileSystem->remove($fileName); + } + } + } - /** - * Token callback to get current branch. - * - * @return string - * Branch name. - * - * @throws \Exception - */ - protected function getTokenBranch(): string - { - return $this->gitGetCurrentBranch($this->src); - } + /** + * Remove any repositories within current repository. + * + * @param string $path + * Path to current repository. + */ + protected function removeSubRepos(string $path): void { + $finder = new Finder(); + $dirs = $finder + ->directories() + ->name('.git') + ->ignoreDotFiles(FALSE) + ->ignoreVCS(FALSE) + ->depth('>0') + ->in($path); + + $dirs = iterator_to_array($dirs->directories()); + + foreach ($dirs as $dir) { + if ($dir instanceof \SplFileInfo) { + $dir = $dir->getPathname(); + } + $this->fsFileSystem->remove($dir); + $this->printDebug('Removing sub-repository "%s"', (string) $dir); + } + } - /** - * Token callback to get tags. - * - * @param string|null $delimiter - * Token delimiter. Defaults to ', '. - * - * @return string - * String of tags. - * - * @throws \Exception - */ - protected function getTokenTags(string $delimiter = null): string - { - $delimiter = $delimiter ? $delimiter : '-'; - $tags = $this->gitGetTags($this->src); - - return implode($delimiter, $tags); - } + /** + * Token callback to get current branch. + * + * @return string + * Branch name. + * + * @throws \Exception + */ + protected function getTokenBranch(): string { + return $this->gitGetCurrentBranch($this->src); + } - /** - * Token callback to get current timestamp. - * - * @param string $format - * Date format suitable for date() function. - * - * @return string - * Date string. - */ - protected function getTokenTimestamp(string $format = 'Y-m-d_H-i-s'): string - { - return date($format, $this->now); - } + /** + * Token callback to get tags. + * + * @param string|null $delimiter + * Token delimiter. Defaults to ', '. + * + * @return string + * String of tags. + * + * @throws \Exception + */ + protected function getTokenTags(string $delimiter = NULL): string { + $delimiter = $delimiter ? $delimiter : '-'; + $tags = $this->gitGetTags($this->src); + + return implode($delimiter, $tags); + } - /** - * Check if running in debug mode. - * - * @return bool - * Check is debugging mode or not. - */ - protected function isDebug(): bool - { - return $this->debug || $this->output->isDebug(); - } + /** + * Token callback to get current timestamp. + * + * @param string $format + * Date format suitable for date() function. + * + * @return string + * Date string. + */ + protected function getTokenTimestamp(string $format = 'Y-m-d_H-i-s'): string { + return date($format, $this->now); + } - /** - * Write line as yell style. - * - * @param string $text - * Text yell. - */ - protected function yell(string $text): void - { - $color = 'green'; - $char = $this->decorationCharacter('>', '➜'); - $format = sprintf('%%s %%s', $color, $color); - $this->writeln(sprintf($format, $char, $text)); - } + /** + * Check if running in debug mode. + * + * @return bool + * Check is debugging mode or not. + */ + protected function isDebug(): bool { + return $this->debug || $this->output->isDebug(); + } - /** - * Write line as say style. - * - * @param string $text - * Text. - */ - protected function say(string $text): void - { - $char = $this->decorationCharacter('>', '➜'); - $this->writeln(sprintf('%s %s', $char, $text)); - } + /** + * Write line as yell style. + * + * @param string $text + * Text yell. + */ + protected function yell(string $text): void { + $color = 'green'; + $char = $this->decorationCharacter('>', '➜'); + $format = sprintf('%%s %%s', $color, $color); + $this->writeln(sprintf($format, $char, $text)); + } - /** - * Print success message. - * - * Usually used to explicitly state that some action was successfully - * executed. - * - * @param string $text - * Message text. - */ - protected function sayOkay(string $text): void - { - $color = 'green'; - $char = $this->decorationCharacter('V', '✔'); - $format = sprintf('%%s %%s', $color, $color); - $this->writeln(sprintf($format, $char, $text)); - } + /** + * Write line as say style. + * + * @param string $text + * Text. + */ + protected function say(string $text): void { + $char = $this->decorationCharacter('>', '➜'); + $this->writeln(sprintf('%s %s', $char, $text)); + } - /** - * Print debug information. - * - * @param mixed ...$args - * The args. - */ - protected function printDebug(mixed ...$args): void - { - if (!$this->isDebug()) { - return; - } - $message = array_shift($args); - /* @phpstan-ignore-next-line */ - $this->writeln(vsprintf($message, $args)); - } + /** + * Print success message. + * + * Usually used to explicitly state that some action was successfully + * executed. + * + * @param string $text + * Message text. + */ + protected function sayOkay(string $text): void { + $color = 'green'; + $char = $this->decorationCharacter('V', '✔'); + $format = sprintf('%%s %%s', $color, $color); + $this->writeln(sprintf($format, $char, $text)); + } - /** - * Write output. - * - * @param string $text - * Text. - */ - protected function writeln(string $text): void - { - $this->output->writeln($text); - } + /** + * Print debug information. + * + * @param mixed ...$args + * The args. + */ + protected function printDebug(mixed ...$args): void { + if (!$this->isDebug()) { + return; + } + $message = array_shift($args); + /* @phpstan-ignore-next-line */ + $this->writeln(vsprintf($message, $args)); + } - /** - * Decoration character. - * - * @param string $nonDecorated - * Non decorated. - * @param string $decorated - * Decorated. - * - * @return string - * The decoration character. - */ - protected function decorationCharacter(string $nonDecorated, string $decorated): string - { - if (!$this->output->isDecorated() || (strncasecmp(PHP_OS, 'WIN', 3) === 0)) { - return $nonDecorated; - } + /** + * Write output. + * + * @param string $text + * Text. + */ + protected function writeln(string $text): void { + $this->output->writeln($text); + } + + /** + * Decoration character. + * + * @param string $nonDecorated + * Non decorated. + * @param string $decorated + * Decorated. + * + * @return string + * The decoration character. + */ + protected function decorationCharacter(string $nonDecorated, string $decorated): string { + if (!$this->output->isDecorated() || (strncasecmp(PHP_OS, 'WIN', 3) === 0)) { + return $nonDecorated; + } + + return $decorated; + } - return $decorated; - } } diff --git a/src/Commands/ArtifactCommand.php b/src/Commands/ArtifactCommand.php index 77276fd..b22a8d3 100644 --- a/src/Commands/ArtifactCommand.php +++ b/src/Commands/ArtifactCommand.php @@ -1,6 +1,6 @@ setName('artifact'); - $this->setDescription('Push artifact of current repository to remote git repository.'); - $this->addArgument('remote', InputArgument::REQUIRED, 'Path to the remote git repository.'); - $this - ->addOption('branch', null, InputOption::VALUE_REQUIRED, 'Destination branch with optional tokens.', '[branch]') - ->addOption('debug', null, InputOption::VALUE_NONE, 'Print debug information.') - ->addOption( - 'gitignore', - null, - InputOption::VALUE_REQUIRED, - 'Path to gitignore file to replace current .gitignore.' - ) - ->addOption( - 'message', - null, - InputOption::VALUE_REQUIRED, - 'Commit message with optional tokens.', - 'Deployment commit' - ) - ->addOption( - 'mode', - null, - InputOption::VALUE_REQUIRED, - 'Mode of artifact build: branch, force-push or diff. Defaults to force-push.', - 'force-push' - ) - ->addOption('no-cleanup', null, InputOption::VALUE_NONE, 'Do not cleanup after run.') - ->addOption('now', null, InputOption::VALUE_REQUIRED, 'Internal value used to set internal time.') - ->addOption('push', null, InputOption::VALUE_NONE, 'Push artifact to the remote repository') - ->addOption('report', null, InputOption::VALUE_REQUIRED, 'Path to the report file.') - ->addOption( - 'root', - null, - InputOption::VALUE_REQUIRED, - 'Path to the root for file path resolution. If not specified, current directory is used.' - ) - ->addOption( - 'show-changes', - null, - InputOption::VALUE_NONE, - 'Show changes made to the repo by the build in the output.' - ) - ->addOption( - 'src', - null, - InputOption::VALUE_REQUIRED, - 'Directory where source repository is located. If not specified, root directory is used.' - ); + protected function configure(): void { + $this->setName('artifact'); + $this->setDescription('Push artifact of current repository to remote git repository.'); + $this->addArgument('remote', InputArgument::REQUIRED, 'Path to the remote git repository.'); + $this + ->addOption('branch', NULL, InputOption::VALUE_REQUIRED, 'Destination branch with optional tokens.', '[branch]') + ->addOption('debug', NULL, InputOption::VALUE_NONE, 'Print debug information.') + ->addOption( + 'gitignore', + NULL, + InputOption::VALUE_REQUIRED, + 'Path to gitignore file to replace current .gitignore.' + ) + ->addOption( + 'message', + NULL, + InputOption::VALUE_REQUIRED, + 'Commit message with optional tokens.', + 'Deployment commit' + ) + ->addOption( + 'mode', + NULL, + InputOption::VALUE_REQUIRED, + 'Mode of artifact build: branch, force-push or diff. Defaults to force-push.', + 'force-push' + ) + ->addOption('no-cleanup', NULL, InputOption::VALUE_NONE, 'Do not cleanup after run.') + ->addOption('now', NULL, InputOption::VALUE_REQUIRED, 'Internal value used to set internal time.') + ->addOption('push', NULL, InputOption::VALUE_NONE, 'Push artifact to the remote repository') + ->addOption('report', NULL, InputOption::VALUE_REQUIRED, 'Path to the report file.') + ->addOption( + 'root', + NULL, + InputOption::VALUE_REQUIRED, + 'Path to the root for file path resolution. If not specified, current directory is used.' + ) + ->addOption( + 'show-changes', + NULL, + InputOption::VALUE_NONE, + 'Show changes made to the repo by the build in the output.' + ) + ->addOption( + 'src', + NULL, + InputOption::VALUE_REQUIRED, + 'Directory where source repository is located. If not specified, root directory is used.' + ); + } + + /** + * Perform actual command. + * + * @param \Symfony\Component\Console\Input\InputInterface $input + * Input. + * @param \Symfony\Component\Console\Output\OutputInterface $output + * Output. + * + * @return int + * Status code. + * + * @throws \Exception + */ + protected function execute(InputInterface $input, OutputInterface $output): int { + $gitWrapper = new GitWrapper(); + $optionDebug = $input->getOption('debug'); + if (($optionDebug || $output->isDebug())) { + $logger = new Logger('git'); + $logger->pushHandler(new StreamHandler('php://stdout', Level::Debug)); + $gitWrapper->addLoggerEventSubscriber(new GitLoggerEventSubscriber($logger)); } + $fileSystem = new Filesystem(); + $artifact = new Artifact($gitWrapper, $fileSystem, $output); + $remote = $input->getArgument('remote'); + // @phpstan-ignore-next-line + $artifact->artifact($remote, $input->getOptions()); - /** - * Perform actual command. - * - * @param InputInterface $input - * Input. - * @param OutputInterface $output - * Output. - * - * @return int - * Status code. - * - * @throws \Exception - */ - protected function execute(InputInterface $input, OutputInterface $output): int - { - $gitWrapper = new GitWrapper(); - $optionDebug = $input->getOption('debug'); - if (($optionDebug || $output->isDebug())) { - $logger = new Logger('git'); - $logger->pushHandler(new StreamHandler('php://stdout', Level::Debug)); - $gitWrapper->addLoggerEventSubscriber(new GitLoggerEventSubscriber($logger)); - } - $fileSystem = new Filesystem(); - $artifact = new Artifact($gitWrapper, $fileSystem, $output); - $remote = $input->getArgument('remote'); - // @phpstan-ignore-next-line - $artifact->artifact($remote, $input->getOptions()); + return Command::SUCCESS; + } - return Command::SUCCESS; - } } diff --git a/src/FilesystemTrait.php b/src/FilesystemTrait.php index 5ccc820..338b4b2 100644 --- a/src/FilesystemTrait.php +++ b/src/FilesystemTrait.php @@ -10,232 +10,223 @@ /** * Trait FilesystemTrait. */ -trait FilesystemTrait -{ - - /** - * Current directory where call originated. - * - * @var string - */ - protected $fsRootDir; - - /** - * File system for custom commands. - */ - protected Filesystem $fsFileSystem; - - /** - * Stack of original current working directories. - * - * This is used throughout commands to track working directories. - * Usually, each command would call setCwd() in the beginning and - * restoreCwd() at the end of the run. - * - * @var array - */ - protected $fsOriginalCwdStack = []; - - /** - * Set root directory path. - * - * @param string|null $path - * The path of the root directory. - * - * @throws \Exception - */ - protected function fsSetRootDir(string $path = null): void - { - $path = empty($path) ? $this->fsGetRootDir() : $this->fsGetAbsolutePath($path); - $this->fsPathsExist($path); - $this->fsRootDir = $path; +trait FilesystemTrait { + + /** + * Current directory where call originated. + * + * @var string + */ + protected $fsRootDir; + + /** + * File system for custom commands. + */ + protected Filesystem $fsFileSystem; + + /** + * Stack of original current working directories. + * + * This is used throughout commands to track working directories. + * Usually, each command would call setCwd() in the beginning and + * restoreCwd() at the end of the run. + * + * @var array + */ + protected $fsOriginalCwdStack = []; + + /** + * Set root directory path. + * + * @param string|null $path + * The path of the root directory. + * + * @throws \Exception + */ + protected function fsSetRootDir(string $path = NULL): void { + $path = empty($path) ? $this->fsGetRootDir() : $this->fsGetAbsolutePath($path); + $this->fsPathsExist($path); + $this->fsRootDir = $path; + } + + /** + * Get root directory. + * + * @return string + * Get value of the root directory, the directory where the + * script was started from or current working directory. + */ + protected function fsGetRootDir(): string { + if ($this->fsRootDir) { + return $this->fsRootDir; } - /** - * Get root directory. - * - * @return string - * Get value of the root directory, the directory where the - * script was started from or current working directory. - */ - protected function fsGetRootDir(): string - { - if ($this->fsRootDir) { - return $this->fsRootDir; - } - - if (isset($_SERVER['PWD'])) { - return $_SERVER['PWD']; - } - - return (string) getcwd(); + if (isset($_SERVER['PWD'])) { + return $_SERVER['PWD']; } - /** - * Set current working directory. - * - * It is important to note that this should be called in pair with - * cwdRestore(). - * - * @param string $dir - * Path to the current directory. - */ - protected function fsCwdSet(string $dir): void - { - chdir($dir); - $this->fsOriginalCwdStack[] = $dir; + return (string) getcwd(); + } + + /** + * Set current working directory. + * + * It is important to note that this should be called in pair with + * cwdRestore(). + * + * @param string $dir + * Path to the current directory. + */ + protected function fsCwdSet(string $dir): void { + chdir($dir); + $this->fsOriginalCwdStack[] = $dir; + } + + /** + * Set current working directory to a previously saved path. + * + * It is important to note that this should be called in pair with cwdSet(). + */ + protected function fsCwdRestore(): void { + $dir = array_shift($this->fsOriginalCwdStack); + if ($dir) { + chdir($dir); } - - /** - * Set current working directory to a previously saved path. - * - * It is important to note that this should be called in pair with cwdSet(). - */ - protected function fsCwdRestore(): void - { - $dir = array_shift($this->fsOriginalCwdStack); - if ($dir) { - chdir($dir); - } + } + + /** + * Get current working directory. + * + * @return string + * Full path of current working directory. + */ + protected function fsCwdGet(): string { + return (string) getcwd(); + } + + /** + * Check that a command is available in current session. + * + * @param string $command + * Command to check. + * + * @return bool + * TRUE if command is available, FALSE otherwise. + */ + protected function fsIsCommandAvailable(string $command): bool { + $process = new Process(['which', $command]); + $process->run(); + + return $process->isSuccessful(); + } + + /** + * Get absolute path for provided file. + * + * @param string $file + * File to resolve. If absolute, no resolution will be performed. + * @param string|null $root + * Optional path to root dir. If not provided, internal root path is used. + * + * @return string + * Absolute path for provided file. + */ + protected function fsGetAbsolutePath(string $file, string $root = NULL): string { + if ($this->fsFileSystem->isAbsolutePath($file)) { + return $this->realpath($file); } - - /** - * Get current working directory. - * - * @return string - * Full path of current working directory. - */ - protected function fsCwdGet(): string - { - return (string) getcwd(); + $root = $root ? $root : $this->fsGetRootDir(); + $root = $this->realpath($root); + $file = $root . DIRECTORY_SEPARATOR . $file; + + return $this->realpath($file); + } + + /** + * Check that path exists. + * + * @param string|array $paths + * File name or array of file names to check. + * @param bool $strict + * If TRUE and the file does not exist, an exception will be thrown. + * Defaults to TRUE. + * + * @return bool + * TRUE if file exists and FALSE if not, but only if $strict is FALSE. + * + * @throws \Exception + * If at least one file does not exist. + */ + protected function fsPathsExist($paths, bool $strict = TRUE): bool { + $paths = is_array($paths) ? $paths : [$paths]; + if (!$this->fsFileSystem->exists($paths)) { + if ($strict) { + throw new \Exception(sprintf('One of the files or directories does not exist: %s', implode(', ', $paths))); + } + + return FALSE; } - /** - * Check that a command is available in current session. - * - * @param string $command - * Command to check. - * - * @return bool - * TRUE if command is available, FALSE otherwise. - * - */ - protected function fsIsCommandAvailable(string $command): bool - { - $process = new Process(['which', $command]); - $process->run(); - - return $process->isSuccessful(); + return TRUE; + } + + /** + * Replacement for PHP's `realpath` resolves non-existing paths. + * + * The main deference is that it does not return FALSE on non-existing + * paths. + * + * @param string $path + * Path that needs to be resolved. + * + * @return string + * Resolved path. + * + * @see https://stackoverflow.com/a/29372360/712666 + * + * @SuppressWarnings(PHPMD.CyclomaticComplexity) + * @SuppressWarnings(PHPMD.NPathComplexity) + */ + protected function realpath(string $path): string { + // Whether $path is unix or not. + $unipath = $path === '' || $path[0] !== '/'; + $unc = str_starts_with($path, '\\\\'); + // Attempt to detect if path is relative in which case, add cwd. + if (!str_contains($path, ':') && $unipath && !$unc) { + $path = getcwd() . DIRECTORY_SEPARATOR . $path; + if ($path[0] === '/') { + $unipath = FALSE; + } } - /** - * Get absolute path for provided file. - * - * @param string $file - * File to resolve. If absolute, no resolution will be performed. - * @param string|null $root - * Optional path to root dir. If not provided, internal root path is used. - * - * @return string - * Absolute path for provided file. - */ - protected function fsGetAbsolutePath(string $file, string $root = null): string - { - if ($this->fsFileSystem->isAbsolutePath($file)) { - return $this->realpath($file); - } - $root = $root ? $root : $this->fsGetRootDir(); - $root = $this->realpath($root); - $file = $root.DIRECTORY_SEPARATOR.$file; - - return $this->realpath($file); + // Resolve path parts (single dot, double dot and double delimiters). + $path = str_replace(['/', '\\'], DIRECTORY_SEPARATOR, $path); + $parts = array_filter(explode(DIRECTORY_SEPARATOR, $path), static function ($part) : bool { + return strlen($part) > 0; + }); + + $absolutes = []; + foreach ($parts as $part) { + if ('.' === $part) { + continue; + } + if ('..' === $part) { + array_pop($absolutes); + } + else { + $absolutes[] = $part; + } } - - /** - * Check that path exists. - * - * @param string|array $paths - * File name or array of file names to check. - * @param bool $strict - * If TRUE and the file does not exist, an exception will be thrown. - * Defaults to TRUE. - * - * @return bool - * TRUE if file exists and FALSE if not, but only if $strict is FALSE. - * - * @throws \Exception - * If at least one file does not exist. - */ - protected function fsPathsExist($paths, bool $strict = true): bool - { - $paths = is_array($paths) ? $paths : [$paths]; - if (!$this->fsFileSystem->exists($paths)) { - if ($strict) { - throw new \Exception(sprintf('One of the files or directories does not exist: %s', implode(', ', $paths))); - } - - return false; - } - - return true; + $path = implode(DIRECTORY_SEPARATOR, $absolutes); + // Resolve any symlinks. + if (function_exists('readlink') && file_exists($path) && linkinfo($path) > 0) { + $path = readlink($path); } + // Put initial separator that could have been lost. + $path = $unipath ? $path : '/' . $path; + + /* @phpstan-ignore-next-line */ + return $unc ? '\\\\' . $path : $path; + } - /** - * Replacement for PHP's `realpath` resolves non-existing paths. - * - * The main deference is that it does not return FALSE on non-existing - * paths. - * - * @param string $path - * Path that needs to be resolved. - * - * @return string - * Resolved path. - * - * @see https://stackoverflow.com/a/29372360/712666 - * - * @SuppressWarnings(PHPMD.CyclomaticComplexity) - * @SuppressWarnings(PHPMD.NPathComplexity) - */ - protected function realpath(string $path): string - { - // Whether $path is unix or not. - $unipath = $path === '' || $path[0] !== '/'; - $unc = str_starts_with($path, '\\\\'); - // Attempt to detect if path is relative in which case, add cwd. - if (!str_contains($path, ':') && $unipath && !$unc) { - $path = getcwd().DIRECTORY_SEPARATOR.$path; - if ($path[0] === '/') { - $unipath = false; - } - } - - // Resolve path parts (single dot, double dot and double delimiters). - $path = str_replace(['/', '\\'], DIRECTORY_SEPARATOR, $path); - $parts = array_filter(explode(DIRECTORY_SEPARATOR, $path), static function ($part) : bool { - return strlen($part) > 0; - }); - - $absolutes = []; - foreach ($parts as $part) { - if ('.' === $part) { - continue; - } - if ('..' === $part) { - array_pop($absolutes); - } else { - $absolutes[] = $part; - } - } - $path = implode(DIRECTORY_SEPARATOR, $absolutes); - // Resolve any symlinks. - if (function_exists('readlink') && file_exists($path) && linkinfo($path) > 0) { - $path = readlink($path); - } - // Put initial separator that could have been lost. - $path = $unipath ? $path : '/'.$path; - - /* @phpstan-ignore-next-line */ - return $unc ? '\\\\'.$path : $path; - } } diff --git a/src/GitTrait.php b/src/GitTrait.php index 857057c..0fe8910 100644 --- a/src/GitTrait.php +++ b/src/GitTrait.php @@ -1,6 +1,6 @@ fsPathsExist($path); - $this->src = $path; - } +trait GitTrait { - /** - * Set remote path. - * - * @param string $path - * Path or URL of the remote git repository. - * - * @throws \Exception - */ - protected function gitSetDst(string $path): void - { - if (!$this->gitIsRemote($path)) { - throw new \RuntimeException(sprintf('Incorrect value "%s" specified for git remote', $path)); - } - $this->dst = $this->gitIsRemote($path, 'local') ? $this->fsGetAbsolutePath($path) : $path; - } + use FilesystemTrait; - /** - * Add remote for specified repo. - * - * @param string $location - * Local path or remote URI of the repository to add remote for. - * @param string $name - * Remote name. - * @param string $remote - * Path or remote URI of the repository to add remote for. - * - * @return string - * stdout. - * - * @throws \Exception - * If unable to add a remote. - */ - protected function gitAddRemote(string $location, string $name, string $remote): string - { - return $this->gitCommandRun( - $location, - sprintf('remote add %s %s', $name, $remote), - sprintf('Unable to add "%s" remote', $name) - ); - } + /** + * Git Wrapper. + */ + protected GitWrapper $gitWrapper; - /** - * Check if specified remote already exists in current repo. - * - * @param string $location - * Local path or remote URI of the repository to add remote for. - * @param string $name - * Remote name. - * - * @return bool - * TRUE if remote with the name already exists in current repo, FALSE - * otherwise. - * - * @throws \Exception - */ - protected function gitRemoteExists(string $location, string $name): bool - { - $result = $this->gitCommandRun( - $location, - 'remote', - 'Unable to list remotes', - ); + /** + * Path to the source repository. + * + * @var string + */ + protected $src; - $lines = preg_split('/\R/', (string) $result); + /** + * Path to the destination repository. + * + * @var string + */ + protected $dst; - if (empty($lines)) { - return false; - } + /** + * Set source repository path with validation. + * + * @param string $path + * Path to repo. + * + * @throws \Exception + */ + protected function gitSetSrcRepo(string $path): void { + $this->fsPathsExist($path); + $this->src = $path; + } - return in_array($name, $lines); + /** + * Set remote path. + * + * @param string $path + * Path or URL of the remote git repository. + * + * @throws \Exception + */ + protected function gitSetDst(string $path): void { + if (!$this->gitIsRemote($path)) { + throw new \RuntimeException(sprintf('Incorrect value "%s" specified for git remote', $path)); } + $this->dst = $this->gitIsRemote($path, 'local') ? $this->fsGetAbsolutePath($path) : $path; + } - /** - * Switch to new branch. - * - * @param string $location - * Local path or remote URI of the repository. - * @param string $branch - * Branch name. - * @param bool $createNew - * Optional flag to also create a branch before switching. Defaults to - * false. - * - * @return string - * stdout. - * - * @throws \Exception - */ - protected function gitSwitchToBranch(string $location, string $branch, bool $createNew = false): string - { - $command = $createNew ? sprintf('checkout -b %s', $branch) : sprintf('checkout %s', $branch); - - return $this->gitCommandRun( - $location, - $command, - ); + /** + * Add remote for specified repo. + * + * @param string $location + * Local path or remote URI of the repository to add remote for. + * @param string $name + * Remote name. + * @param string $remote + * Path or remote URI of the repository to add remote for. + * + * @return string + * stdout. + * + * @throws \Exception + * If unable to add a remote. + */ + protected function gitAddRemote(string $location, string $name, string $remote): string { + return $this->gitCommandRun( + $location, + sprintf('remote add %s %s', $name, $remote), + sprintf('Unable to add "%s" remote', $name) + ); + } + + /** + * Check if specified remote already exists in current repo. + * + * @param string $location + * Local path or remote URI of the repository to add remote for. + * @param string $name + * Remote name. + * + * @return bool + * TRUE if remote with the name already exists in current repo, FALSE + * otherwise. + * + * @throws \Exception + */ + protected function gitRemoteExists(string $location, string $name): bool { + $result = $this->gitCommandRun( + $location, + 'remote', + 'Unable to list remotes', + ); + + $lines = preg_split('/\R/', (string) $result); + + if (empty($lines)) { + return FALSE; } - /** - * Remove git branch. - * - * @param string $location - * Local path or remote URI of the repository. - * @param string $branch - * Branch name. - * - * @return string - * stdout. - * - * @throws \Exception - */ - protected function gitRemoveBranch($location, $branch): string - { - return $this->gitCommandRun( + return in_array($name, $lines); + } + + /** + * Switch to new branch. + * + * @param string $location + * Local path or remote URI of the repository. + * @param string $branch + * Branch name. + * @param bool $createNew + * Optional flag to also create a branch before switching. Defaults to + * false. + * + * @return string + * stdout. + * + * @throws \Exception + */ + protected function gitSwitchToBranch(string $location, string $branch, bool $createNew = FALSE): string { + $command = $createNew ? sprintf('checkout -b %s', $branch) : sprintf('checkout %s', $branch); + + return $this->gitCommandRun( + $location, + $command, + ); + } + + /** + * Remove git branch. + * + * @param string $location + * Local path or remote URI of the repository. + * @param string $branch + * Branch name. + * + * @return string + * stdout. + * + * @throws \Exception + */ + protected function gitRemoveBranch($location, $branch): string { + return $this->gitCommandRun( + $location, + sprintf('branch -D %s', $branch), + ); + } + + /** + * Removed git remote. + * + * @param string $location + * Local path or remote URI of the repository. + * @param string $remote + * Remote name. + * + * @throws \Exception + */ + protected function gitRemoveRemote(string $location, string $remote): void { + if ($this->gitRemoteExists($location, $remote)) { + $this->gitCommandRun( $location, - sprintf('branch -D %s', $branch), + sprintf('remote rm %s', $remote), ); } + } - /** - * Removed git remote. - * - * @param string $location - * Local path or remote URI of the repository. - * @param string $remote - * Remote name. - * - * @throws \Exception - */ - protected function gitRemoveRemote(string $location, string $remote): void - { - if ($this->gitRemoteExists($location, $remote)) { - $this->gitCommandRun( - $location, - sprintf('remote rm %s', $remote), - ); - } - } - - /** - * Commit and push to specified remote. - * - * @param string $location - * Repository location path or URI. - * @param string $localBranch - * Local branch name. - * @param string $remoteName - * Remote name. - * @param string $remoteBranch - * Remote branch to push to. - * @param bool $force - * Force push. - * - * @return string - * Stdout. - * - * @throws \Exception - */ - protected function gitPush( + /** + * Commit and push to specified remote. + * + * @param string $location + * Repository location path or URI. + * @param string $localBranch + * Local branch name. + * @param string $remoteName + * Remote name. + * @param string $remoteBranch + * Remote branch to push to. + * @param bool $force + * Force push. + * + * @return string + * Stdout. + * + * @throws \Exception + */ + protected function gitPush( string $location, string $localBranch, string $remoteName, string $remoteBranch, - bool $force = false + bool $force = FALSE ): string { - return $this->gitCommandRun( - $location, - sprintf( - 'push %s refs/heads/%s:refs/heads/%s%s', - $remoteName, - $localBranch, - $remoteBranch, - $force ? ' --force' : '' - ), - sprintf( - 'Unable to push local branch "%s" to "%s" remote branch "%s"', - $localBranch, - $remoteName, - $remoteBranch - ) - ); - } + return $this->gitCommandRun( + $location, + sprintf( + 'push %s refs/heads/%s:refs/heads/%s%s', + $remoteName, + $localBranch, + $remoteBranch, + $force ? ' --force' : '' + ), + sprintf( + 'Unable to push local branch "%s" to "%s" remote branch "%s"', + $localBranch, + $remoteName, + $remoteBranch + ) + ); + } - /** - * Commit all files to git repo. - * - * @param string $location - * Repository location path or URI. - * @param string $message - * Commit message. - * - * @return string - * Stdout. - * - * @throws \Exception - */ - protected function gitCommit(string $location, string $message): string - { - $this->gitCommandRun( - $location, - 'add -A', - ); - $command = new GitCommand('commit', [ - 'allow-empty' => true, - 'm' => $message, - ]); - $command->setDirectory($location); + /** + * Commit all files to git repo. + * + * @param string $location + * Repository location path or URI. + * @param string $message + * Commit message. + * + * @return string + * Stdout. + * + * @throws \Exception + */ + protected function gitCommit(string $location, string $message): string { + $this->gitCommandRun( + $location, + 'add -A', + ); + $command = new GitCommand('commit', [ + 'allow-empty' => TRUE, + 'm' => $message, + ]); + $command->setDirectory($location); - return $this->gitWrapper->run($command); - } + return $this->gitWrapper->run($command); + } - /** - * Get current branch. - * - * @param string $location - * Repository location path or URI. - * - * @return string - * Current branch. - * - * @throws \Exception - * If unable to get the branch. - */ - protected function gitGetCurrentBranch(string $location): string - { - $result = $this->gitCommandRun( - $location, - 'rev-parse --abbrev-ref HEAD', - 'Unable to get current repository branch', - ); - - return trim((string) $result); - } + /** + * Get current branch. + * + * @param string $location + * Repository location path or URI. + * + * @return string + * Current branch. + * + * @throws \Exception + * If unable to get the branch. + */ + protected function gitGetCurrentBranch(string $location): string { + $result = $this->gitCommandRun( + $location, + 'rev-parse --abbrev-ref HEAD', + 'Unable to get current repository branch', + ); - /** - * Get all tags for the current commit. - * - * @param string $location - * Repository location path or URI. - * - * @return array - * Array of tags. - * - * @throws \Exception - * If not able to retrieve tags. - */ - protected function gitGetTags(string $location): array - { - $result = $this->gitCommandRun( - $location, - 'tag -l --points-at HEAD', - 'Unable to retrieve tags', - ); - $tags = preg_split('/\R/', (string) $result); - if (empty($tags)) { - return []; - } + return trim((string) $result); + } - return array_filter($tags); + /** + * Get all tags for the current commit. + * + * @param string $location + * Repository location path or URI. + * + * @return array + * Array of tags. + * + * @throws \Exception + * If not able to retrieve tags. + */ + protected function gitGetTags(string $location): array { + $result = $this->gitCommandRun( + $location, + 'tag -l --points-at HEAD', + 'Unable to retrieve tags', + ); + $tags = preg_split('/\R/', (string) $result); + if (empty($tags)) { + return []; } - /** - * Run git command. - * - * @param string $location - * Repository location path or URI. - * @param string $command - * Command to run. - * @param string $errorMessage - * Optional error message. - * - * @return string - * Stdout. - * - * @throws \Exception If command did not finish successfully. - */ - protected function gitCommandRun( + return array_filter($tags); + } + + /** + * Run git command. + * + * @param string $location + * Repository location path or URI. + * @param string $command + * Command to run. + * @param string $errorMessage + * Optional error message. + * + * @return string + * Stdout. + * + * @throws \Exception + * If command did not finish successfully. + */ + protected function gitCommandRun( string $location, string $command, string $errorMessage = '', ): string { - $command = '--no-pager '.$command; - try { - return $this->gitWrapper->git($command, $location); - } catch (\Exception $exception) { - if ($errorMessage !== '') { - throw new \Exception($errorMessage, $exception->getCode(), $exception); - } - throw $exception; - } + $command = '--no-pager ' . $command; + try { + return $this->gitWrapper->git($command, $location); } + catch (\Exception $exception) { + if ($errorMessage !== '') { + throw new \Exception($errorMessage, $exception->getCode(), $exception); + } + throw $exception; + } + } + + /** + * Check if provided location is local path or remote URI. + * + * @param string $location + * Local path or remote URI. + * @param string $type + * One of the predefined types: + * - any: Expected to have either local path or remote URI provided. + * - local: Expected to have local path provided. + * - uri: Expected to have remote URI provided. + * + * @return bool + * Returns TRUE if location matches type, FALSE otherwise. + * + * @throws \Exception + */ + protected function gitIsRemote(string $location, string $type = 'any'): bool { + $isLocal = $this->fsPathsExist($this->fsGetAbsolutePath($location), FALSE); + $isUri = self::gitIsUri($location); - /** - * Check if provided location is local path or remote URI. - * - * @param string $location - * Local path or remote URI. - * @param string $type - * One of the predefined types: - * - any: Expected to have either local path or remote URI provided. - * - local: Expected to have local path provided. - * - uri: Expected to have remote URI provided. - * - * @return bool - * Returns TRUE if location matches type, FALSE otherwise. - * - * @throws \Exception - */ - protected function gitIsRemote(string $location, string $type = 'any'): bool - { - $isLocal = $this->fsPathsExist($this->fsGetAbsolutePath($location), false); - $isUri = self::gitIsUri($location); - - return match ($type) { - 'any' => $isLocal || $isUri, + return match ($type) { + 'any' => $isLocal || $isUri, 'local' => $isLocal, 'uri' => $isUri, default => throw new \InvalidArgumentException(sprintf('Invalid argument "%s" provided', $type)), - }; - } + }; + } - /** - * Check if provided branch name can be used in git. - * - * @param string $branch - * Branch to check. - * - * @return bool - * TRUE if it is a valid Git branch, FALSE otherwise. - */ - protected static function gitIsValidBranch(string $branch): bool - { - return preg_match('/^(?!\/|.*(?:[\/\.]\.|\/\/|\\|@\{))[^\040\177\s\~\^\:\?\*\[]+(? 1) { - $parts = explode(':', $match[1], 2); - $token = isset($parts[0]) ? $parts[0] : null; - $argument = isset($parts[1]) ? $parts[1] : null; - if ($token) { - $method = 'getToken'.ucfirst($token); - if (method_exists($this, $method)) { - /* @phpstan-ignore-next-line */ - $match[0] = call_user_func([$this, $method], $argument); - } - } - } + /** + * Process tokens. + * + * @param string $string + * String that may contain tokens surrounded by '[' and ']'. + * + * @return string|null + * String with replaced tokens if replacements are available or + * original string. + */ + protected function tokenProcess(string $string): ?string { + return preg_replace_callback('/(?:\[([^\]]+)\])/', function (array $match) { + if (count($match) > 1) { + $parts = explode(':', $match[1], 2); + $token = $parts[0] ?? NULL; + $argument = $parts[1] ?? NULL; + if ($token) { + $method = 'getToken' . ucfirst($token); + if (method_exists($this, $method)) { + /* @phpstan-ignore-next-line */ + $match[0] = call_user_func([$this, $method], $argument); + } + } + } - return $match[0]; - }, $string); - } + return $match[0]; + }, $string); + } + + /** + * Check if the string has at least one token. + * + * @param string $string + * String to check. + * + * @return bool + * True if there is at least one token present, false otherwise. + */ + protected function hasToken(string $string): bool { + return (bool) preg_match('/\[[^]]+]/', $string); + } - /** - * Check if the string has at least one token. - * - * @param string $string - * String to check. - * - * @return bool - * True if there is at least one token present, false otherwise. - */ - protected function hasToken(string $string): bool - { - return (bool) preg_match('/\[[^]]+]/', $string); - } } diff --git a/src/app.php b/src/app.php index e1eb894..030e762 100644 --- a/src/app.php +++ b/src/app.php @@ -11,5 +11,5 @@ $application = new Application(); $command = new ArtifactCommand(); $application->add($command); -$application->setDefaultCommand((string) $command->getName(), true); +$application->setDefaultCommand((string) $command->getName(), TRUE); $application->run(); diff --git a/tests/phpunit/AbstractTestCase.php b/tests/phpunit/AbstractTestCase.php index a7a784f..e5cc538 100644 --- a/tests/phpunit/AbstractTestCase.php +++ b/tests/phpunit/AbstractTestCase.php @@ -1,6 +1,6 @@ fs = new Filesystem(); - - $this->fixtureDir = sys_get_temp_dir().DIRECTORY_SEPARATOR.'git_artifact'; - $this->fs->mkdir($this->fixtureDir); - - $this->commandTraitSetUp( - $this->fixtureDir.DIRECTORY_SEPARATOR.'git_src', - $this->fixtureDir.DIRECTORY_SEPARATOR.'git_remote', - $this->isDebug() - ); - } + use ReflectionTrait; + use MockTrait; - /** - * {@inheritdoc} - */ - protected function tearDown(): void - { - $this->commandTraitTearDown(); + /** + * File system. + * + * @var \Symfony\Component\Filesystem\Filesystem + */ + protected $fs; - if ($this->fs->exists($this->fixtureDir)) { - $this->fs->remove($this->fixtureDir); - } - } + /** + * Fixture directory. + * + * @var string + */ + protected $fixtureDir; + + /** + * {@inheritdoc} + */ + protected function setUp(): void { + parent::setUp(); + + $this->fs = new Filesystem(); - /** - * Check if testing framework was ran with --debug option. - * - * @return bool - * TRUE if is in debug mode, FALSE otherwise. - */ - protected function isDebug(): bool - { - return in_array('--debug', $_SERVER['argv'], true); + $this->fixtureDir = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'git_artifact'; + $this->fs->mkdir($this->fixtureDir); + + $this->commandTraitSetUp( + $this->fixtureDir . DIRECTORY_SEPARATOR . 'git_src', + $this->fixtureDir . DIRECTORY_SEPARATOR . 'git_remote', + $this->isDebug() + ); + } + + /** + * {@inheritdoc} + */ + protected function tearDown(): void { + $this->commandTraitTearDown(); + + if ($this->fs->exists($this->fixtureDir)) { + $this->fs->remove($this->fixtureDir); } + } + + /** + * Check if testing framework was ran with --debug option. + * + * @return bool + * TRUE if is in debug mode, FALSE otherwise. + */ + protected function isDebug(): bool { + return in_array('--debug', $_SERVER['argv'], TRUE); + } + } diff --git a/tests/phpunit/Exception/ErrorException.php b/tests/phpunit/Exception/ErrorException.php index be199d1..a2a08bf 100644 --- a/tests/phpunit/Exception/ErrorException.php +++ b/tests/phpunit/Exception/ErrorException.php @@ -1,6 +1,6 @@ file = $file; + $this->line = $line; + } - $this->file = $file; - $this->line = $line; - } } diff --git a/tests/phpunit/Functional/AbstractFunctionalTestCase.php b/tests/phpunit/Functional/AbstractFunctionalTestCase.php index cccbd97..5215d3e 100644 --- a/tests/phpunit/Functional/AbstractFunctionalTestCase.php +++ b/tests/phpunit/Functional/AbstractFunctionalTestCase.php @@ -1,192 +1,183 @@ now = time(); - $this->currentBranch = 'master'; - $this->artifactBranch = 'master-artifact'; - $this->remote = 'dst'; +abstract class AbstractFunctionalTestCase extends AbstractTestCase { + + /** + * Current branch. + * + * @var string + */ + protected $currentBranch; + + /** + * Artifact branch. + * + * @var string + */ + protected $artifactBranch; + + /** + * Remote name. + * + * @var string + */ + protected $remote; + + /** + * Mode in which the build will run. + * + * Passed as a value of the --mode option. + * + * @var string + */ + protected $mode; + + /** + * Current timestamp to run commands with. + * + * Used for generating internal tokens that could be based on time. + * + * @var int + */ + protected $now; + + /** + * {@inheritdoc} + */ + protected function setUp(): void { + parent::setUp(); + + $this->now = time(); + $this->currentBranch = 'master'; + $this->artifactBranch = 'master-artifact'; + $this->remote = 'dst'; + } + + /** + * Build the artifact and assert success. + * + * @param string $args + * Optional string of arguments to pass to the build. + * @param string $branch + * Optional --branch value. Defaults to 'testbranch'. + * @param string $commit + * Optional commit string. Defaults to 'Deployment commit'. + * + * @return string + * Command output. + */ + protected function assertBuildSuccess(string $args = '', string $branch = 'testbranch', string $commit = 'Deployment commit'): string { + $output = $this->runBuild(sprintf('--push --branch=%s %s', $branch, $args)); + $this->assertStringNotContainsString('[error]', $output); + $this->assertStringContainsString(sprintf('Pushed branch "%s" with commit message "%s"', $branch, $commit), $output); + $this->assertStringContainsString('Deployment finished successfully.', $output); + $this->assertStringNotContainsString('Deployment failed.', $output); + + return $output; + } + + /** + * Build the artifact and assert failure. + * + * @param string $args + * Optional string of arguments to pass to the build. + * @param string $branch + * Optional --branch value. Defaults to 'testbranch'. + * @param string $commit + * Optional commit string. Defaults to 'Deployment commit'. + * + * @return string + * Command output. + */ + protected function assertBuildFailure(string $args = '', string $branch = 'testbranch', string $commit = 'Deployment commit'): string { + $output = $this->runBuild(sprintf('--push --branch=%s %s', $branch, $args), TRUE); + $this->assertStringNotContainsString(sprintf('Pushed branch "%s" with commit message "%s"', $branch, $commit), $output); + $this->assertStringNotContainsString('Deployment finished successfully.', $output); + $this->assertStringContainsString('Deployment failed.', $output); + + return $output; + } + + /** + * Run artifact build. + * + * @param string $args + * Additional arguments or options as a string. + * @param bool $expectFail + * Expect on fail. + * + * @return string + * Output string. + */ + protected function runBuild(string $args = '', bool $expectFail = FALSE): string { + if ($this->mode) { + $args .= ' --mode=' . $this->mode; } - /** - * Build the artifact and assert success. - * - * @param string $args - * Optional string of arguments to pass to the build. - * @param string $branch - * Optional --branch value. Defaults to 'testbranch'. - * @param string $commit - * Optional commit string. Defaults to 'Deployment commit'. - * - * @return string - * Command output. - */ - protected function assertBuildSuccess(string $args = '', string $branch = 'testbranch', string $commit = 'Deployment commit'): string - { - $output = $this->runBuild(sprintf('--push --branch=%s %s', $branch, $args)); - $this->assertStringNotContainsString('[error]', $output); - $this->assertStringContainsString(sprintf('Pushed branch "%s" with commit message "%s"', $branch, $commit), $output); - $this->assertStringContainsString('Deployment finished successfully.', $output); - $this->assertStringNotContainsString('Deployment failed.', $output); - - return $output; - } - - /** - * Build the artifact and assert failure. - * - * @param string $args - * Optional string of arguments to pass to the build. - * @param string $branch - * Optional --branch value. Defaults to 'testbranch'. - * @param string $commit - * Optional commit string. Defaults to 'Deployment commit'. - * - * @return string - * Command output. - */ - protected function assertBuildFailure(string $args = '', string $branch = 'testbranch', string $commit = 'Deployment commit'): string - { - $output = $this->runBuild(sprintf('--push --branch=%s %s', $branch, $args), true); - $this->assertStringNotContainsString(sprintf('Pushed branch "%s" with commit message "%s"', $branch, $commit), $output); - $this->assertStringNotContainsString('Deployment finished successfully.', $output); - $this->assertStringContainsString('Deployment failed.', $output); - - return $output; - } + $output = $this->runGitArtifactCommandTimestamped(sprintf('--src=%s %s %s', $this->src, $this->dst, $args), $expectFail); - /** - * Run artifact build. - * - * @param string $args - * Additional arguments or options as a string. - * @param bool $expectFail - * Expect on fail. - * - * @return string - * Output string. - */ - protected function runBuild(string $args = '', bool $expectFail = false): string - { - if ($this->mode) { - $args .= ' --mode='.$this->mode; - } - - $output = $this->runGitArtifactCommandTimestamped(sprintf('--src=%s %s %s', $this->src, $this->dst, $args), $expectFail); - - if ($this->isDebug()) { - print str_pad('', 80, '+').PHP_EOL; - print implode(PHP_EOL, $output).PHP_EOL; - print str_pad('', 80, '+').PHP_EOL; - } - - return implode(PHP_EOL, $output); + if ($this->isDebug()) { + print str_pad('', 80, '+') . PHP_EOL; + print implode(PHP_EOL, $output) . PHP_EOL; + print str_pad('', 80, '+') . PHP_EOL; } - /** - * Run command with current timestamp attached to artifact commands. - * - * @param string $command - * Command string to run. - * @param bool $expectFail - * Flag to state that the command should fail. - * - * @return array - * Array of output lines. - */ - protected function runGitArtifactCommandTimestamped(string $command, bool $expectFail = false): array - { - // Add --now option to all 'artifact' commands. - $command .= ' --now='.$this->now; - - return $this->commandRunGitArtifactCommand($command, $expectFail); - } + return implode(PHP_EOL, $output); + } + + /** + * Run command with current timestamp attached to artifact commands. + * + * @param string $command + * Command string to run. + * @param bool $expectFail + * Flag to state that the command should fail. + * + * @return array + * Array of output lines. + */ + protected function runGitArtifactCommandTimestamped(string $command, bool $expectFail = FALSE): array { + // Add --now option to all 'artifact' commands. + $command .= ' --now=' . $this->now; + + return $this->commandRunGitArtifactCommand($command, $expectFail); + } + + /** + * Assert current git branch. + * + * @param string $path + * Path to repository. + * @param string $branch + * Branch name to assert. + */ + protected function assertGitCurrentBranch(string $path, string $branch): void { + $currentBranch = $this->runGitCommand('rev-parse --abbrev-ref HEAD', $path); + + $this->assertStringContainsString($branch, implode('', $currentBranch), sprintf('Current branch is "%s"', $branch)); + } + + /** + * Assert that there is no remote specified in git repository. + * + * @param string $path + * Path to repository. + * @param string $remote + * Remote name to assert. + */ + protected function assertGitNoRemote(string $path, string $remote): void { + $remotes = $this->runGitCommand('remote', $path); + + $this->assertStringNotContainsString($remote, implode('', $remotes), sprintf('Remote "%s" is not present"', $remote)); + } - /** - * Assert current git branch. - * - * @param string $path - * Path to repository. - * - * @param string $branch - * Branch name to assert. - */ - protected function assertGitCurrentBranch(string $path, string $branch): void - { - $currentBranch = $this->runGitCommand('rev-parse --abbrev-ref HEAD', $path); - - $this->assertStringContainsString($branch, implode('', $currentBranch), sprintf('Current branch is "%s"', $branch)); - } - - /** - * Assert that there is no remote specified in git repository. - * - * @param string $path - * Path to repository. - * - * @param string $remote - * Remote name to assert. - */ - protected function assertGitNoRemote(string $path, string $remote): void - { - $remotes = $this->runGitCommand('remote', $path); - - $this->assertStringNotContainsString($remote, implode('', $remotes), sprintf('Remote "%s" is not present"', $remote)); - } } diff --git a/tests/phpunit/Functional/BranchTest.php b/tests/phpunit/Functional/BranchTest.php index 4ea41c0..bc9c7d8 100644 --- a/tests/phpunit/Functional/BranchTest.php +++ b/tests/phpunit/Functional/BranchTest.php @@ -1,6 +1,6 @@ mode = 'branch'; - parent::setUp(); - } + /** + * {@inheritdoc} + */ + protected function setUp(): void { + $this->mode = 'branch'; + parent::setUp(); + } - public function testBuild(): void - { - $this->gitCreateFixtureCommits(2); + public function testBuild(): void { + $this->gitCreateFixtureCommits(2); - $output = $this->assertBuildSuccess(); - $this->assertStringContainsString('WARNING! Provided branch name does not have a token', $output); - $this->assertStringContainsString('Mode: branch', $output); - $this->assertStringContainsString('Will push: Yes', $output); - - $this->assertFixtureCommits(2, $this->dst, 'testbranch', ['Deployment commit']); - } - - public function testBuildMoreCommitsSameBranch(): void - { - $this->gitCreateFixtureCommits(2); - - $this->assertBuildSuccess(); + $output = $this->assertBuildSuccess(); + $this->assertStringContainsString('WARNING! Provided branch name does not have a token', $output); + $this->assertStringContainsString('Mode: branch', $output); + $this->assertStringContainsString('Will push: Yes', $output); - $this->assertFixtureCommits(2, $this->dst, 'testbranch', ['Deployment commit']); - - $this->gitCreateFixtureCommits(3, 2); - $this->assertBuildFailure(); - - // Make sure that broken artifact was not pushed. - $this->assertFixtureCommits(2, $this->dst, 'testbranch', ['Deployment commit']); - } + $this->assertFixtureCommits(2, $this->dst, 'testbranch', ['Deployment commit']); + } - public function testBuildMoreCommits(): void - { - $this->gitCreateFixtureCommits(2); - - $this->now = time() - rand(1, 10 * 60); - $branch1 = 'testbranch-'.date('Y-m-d_H-i-s', $this->now); - $output = $this->assertBuildSuccess('--branch=testbranch-[timestamp:Y-m-d_H-i-s]', $branch1); - $this->assertStringContainsString('Remote branch: '.$branch1, $output); - $this->assertStringNotContainsString('WARNING! Provided branch name does not have a token', $output); - - $this->assertFixtureCommits(2, $this->dst, $branch1, ['Deployment commit']); - - $this->gitCreateFixtureCommits(3, 2); - - $this->now = time() - rand(1, 10 * 60); - $branch2 = 'testbranch-'.date('Y-m-d_H-i-s', $this->now); - $output = $this->assertBuildSuccess('--branch=testbranch-[timestamp:Y-m-d_H-i-s]', $branch2); - $this->assertStringContainsString('Remote branch: '.$branch2, $output); - $this->assertFixtureCommits(5, $this->dst, $branch2, ['Deployment commit']); - - // Also, check that no changes were done to branch1. - $this->assertFixtureCommits(2, $this->dst, $branch1, ['Deployment commit']); - } - - public function testCleanupAfterSuccess(): void - { - $this->gitCreateFixtureCommits(2); - - $this->assertBuildSuccess(); - $this->assertFixtureCommits(2, $this->dst, 'testbranch', ['Deployment commit']); - - $this->assertGitCurrentBranch($this->src, $this->currentBranch); - $this->assertGitNoRemote($this->src, $this->remote); - } - - public function testCleanupAfterFailure(): void - { - $this->gitCreateFixtureCommits(2); - - $this->assertBuildSuccess('', 'testbranch'); - $this->assertFixtureCommits(2, $this->dst, 'testbranch', ['Deployment commit']); - - $this->gitCreateFixtureCommits(3, 2); - // Trigger erroneous build by pushing to the same branch. - $this->assertBuildFailure('', 'testbranch'); - - $this->assertGitCurrentBranch($this->src, $this->currentBranch); - $this->assertGitNoRemote($this->src, $this->remote); - } - - public function testGitignore(): void - { - $this->gitCreateFixtureFile($this->src, '.gitignore', 'f3'); - $this->gitCreateFixtureCommits(2); - $this->gitCreateFixtureFile($this->src, 'f3'); - - $this->now = time() - rand(1, 10 * 60); - $branch1 = 'testbranch-'.date('Y-m-d_H-i-s', $this->now); - $this->assertBuildSuccess('--branch=testbranch-[timestamp:Y-m-d_H-i-s]', $branch1); - - $this->assertFixtureCommits(2, $this->dst, $branch1, ['Deployment commit']); - $this->gitAssertFilesNotExist($this->dst, 'f3'); - - // Now, remove the .gitignore and push again. - $this->gitRemoveFixtureFile($this->src, '.gitignore'); - $this->gitCommitAll($this->src, 'Commit number 3'); - $this->now = time() - rand(1, 10 * 60); - $branch2 = 'testbranch-'.date('Y-m-d_H-i-s', $this->now); - $this->assertBuildSuccess('--branch=testbranch-[timestamp:Y-m-d_H-i-s]', $branch2); + public function testBuildMoreCommitsSameBranch(): void { + $this->gitCreateFixtureCommits(2); - $this->assertFixtureCommits(3, $this->dst, $branch2, ['Deployment commit']); + $this->assertBuildSuccess(); - // Assert that branch from previous deployment was not affected. - $this->assertFixtureCommits(2, $this->dst, $branch1, ['Deployment commit']); - $this->gitAssertFilesNotExist($this->dst, 'f3'); - } + $this->assertFixtureCommits(2, $this->dst, 'testbranch', ['Deployment commit']); - public function testGitignoreCustom(): void - { - $this->gitCreateFixtureFile($this->src, 'mygitignore', 'f3'); - $this->gitCreateFixtureCommits(2); - $this->gitCreateFixtureFile($this->src, 'f3'); + $this->gitCreateFixtureCommits(3, 2); + $this->assertBuildFailure(); - $this->now = time() - rand(1, 10 * 60); - $branch1 = 'testbranch-'.date('Y-m-d_H-i-s', $this->now); - $this->assertBuildSuccess('--branch=testbranch-[timestamp:Y-m-d_H-i-s] --gitignore='.$this->src.DIRECTORY_SEPARATOR.'mygitignore', $branch1); + // Make sure that broken artifact was not pushed. + $this->assertFixtureCommits(2, $this->dst, 'testbranch', ['Deployment commit']); + } - $this->assertFixtureCommits(2, $this->dst, $branch1, ['Deployment commit']); - $this->gitAssertFilesNotExist($this->dst, 'f3'); + public function testBuildMoreCommits(): void { + $this->gitCreateFixtureCommits(2); + + $this->now = time() - rand(1, 10 * 60); + $branch1 = 'testbranch-' . date('Y-m-d_H-i-s', $this->now); + $output = $this->assertBuildSuccess('--branch=testbranch-[timestamp:Y-m-d_H-i-s]', $branch1); + $this->assertStringContainsString('Remote branch: ' . $branch1, $output); + $this->assertStringNotContainsString('WARNING! Provided branch name does not have a token', $output); + + $this->assertFixtureCommits(2, $this->dst, $branch1, ['Deployment commit']); + + $this->gitCreateFixtureCommits(3, 2); + + $this->now = time() - rand(1, 10 * 60); + $branch2 = 'testbranch-' . date('Y-m-d_H-i-s', $this->now); + $output = $this->assertBuildSuccess('--branch=testbranch-[timestamp:Y-m-d_H-i-s]', $branch2); + $this->assertStringContainsString('Remote branch: ' . $branch2, $output); + $this->assertFixtureCommits(5, $this->dst, $branch2, ['Deployment commit']); + + // Also, check that no changes were done to branch1. + $this->assertFixtureCommits(2, $this->dst, $branch1, ['Deployment commit']); + } + + public function testCleanupAfterSuccess(): void { + $this->gitCreateFixtureCommits(2); + + $this->assertBuildSuccess(); + $this->assertFixtureCommits(2, $this->dst, 'testbranch', ['Deployment commit']); + + $this->assertGitCurrentBranch($this->src, $this->currentBranch); + $this->assertGitNoRemote($this->src, $this->remote); + } + + public function testCleanupAfterFailure(): void { + $this->gitCreateFixtureCommits(2); + + $this->assertBuildSuccess('', 'testbranch'); + $this->assertFixtureCommits(2, $this->dst, 'testbranch', ['Deployment commit']); + + $this->gitCreateFixtureCommits(3, 2); + // Trigger erroneous build by pushing to the same branch. + $this->assertBuildFailure('', 'testbranch'); + + $this->assertGitCurrentBranch($this->src, $this->currentBranch); + $this->assertGitNoRemote($this->src, $this->remote); + } + + public function testGitignore(): void { + $this->gitCreateFixtureFile($this->src, '.gitignore', 'f3'); + $this->gitCreateFixtureCommits(2); + $this->gitCreateFixtureFile($this->src, 'f3'); + + $this->now = time() - rand(1, 10 * 60); + $branch1 = 'testbranch-' . date('Y-m-d_H-i-s', $this->now); + $this->assertBuildSuccess('--branch=testbranch-[timestamp:Y-m-d_H-i-s]', $branch1); + + $this->assertFixtureCommits(2, $this->dst, $branch1, ['Deployment commit']); + $this->gitAssertFilesNotExist($this->dst, 'f3'); + + // Now, remove the .gitignore and push again. + $this->gitRemoveFixtureFile($this->src, '.gitignore'); + $this->gitCommitAll($this->src, 'Commit number 3'); + $this->now = time() - rand(1, 10 * 60); + $branch2 = 'testbranch-' . date('Y-m-d_H-i-s', $this->now); + $this->assertBuildSuccess('--branch=testbranch-[timestamp:Y-m-d_H-i-s]', $branch2); + + $this->assertFixtureCommits(3, $this->dst, $branch2, ['Deployment commit']); + + // Assert that branch from previous deployment was not affected. + $this->assertFixtureCommits(2, $this->dst, $branch1, ['Deployment commit']); + $this->gitAssertFilesNotExist($this->dst, 'f3'); + } + + public function testGitignoreCustom(): void { + $this->gitCreateFixtureFile($this->src, 'mygitignore', 'f3'); + $this->gitCreateFixtureCommits(2); + $this->gitCreateFixtureFile($this->src, 'f3'); + + $this->now = time() - rand(1, 10 * 60); + $branch1 = 'testbranch-' . date('Y-m-d_H-i-s', $this->now); + $this->assertBuildSuccess('--branch=testbranch-[timestamp:Y-m-d_H-i-s] --gitignore=' . $this->src . DIRECTORY_SEPARATOR . 'mygitignore', $branch1); + + $this->assertFixtureCommits(2, $this->dst, $branch1, ['Deployment commit']); + $this->gitAssertFilesNotExist($this->dst, 'f3'); + + // Now, remove the .gitignore and push again. + $this->gitCreateFixtureFile($this->src, 'f3'); + $this->gitRemoveFixtureFile($this->src, 'mygitignore'); + $this->gitCommitAll($this->src, 'Commit number 3'); + $this->now = time() - rand(1, 10 * 60); + $branch2 = 'testbranch-' . date('Y-m-d_H-i-s', $this->now); + $this->assertBuildSuccess('--branch=testbranch-[timestamp:Y-m-d_H-i-s]', $branch2); + + $this->assertFixtureCommits(3, $this->dst, $branch2, ['Deployment commit']); + + // Assert that branch from previous deployment was not affected. + $this->assertFixtureCommits(2, $this->dst, $branch1, ['Deployment commit']); + $this->gitAssertFilesNotExist($this->dst, 'f3'); + } - // Now, remove the .gitignore and push again. - $this->gitCreateFixtureFile($this->src, 'f3'); - $this->gitRemoveFixtureFile($this->src, 'mygitignore'); - $this->gitCommitAll($this->src, 'Commit number 3'); - $this->now = time() - rand(1, 10 * 60); - $branch2 = 'testbranch-'.date('Y-m-d_H-i-s', $this->now); - $this->assertBuildSuccess('--branch=testbranch-[timestamp:Y-m-d_H-i-s]', $branch2); - - $this->assertFixtureCommits(3, $this->dst, $branch2, ['Deployment commit']); - - // Assert that branch from previous deployment was not affected. - $this->assertFixtureCommits(2, $this->dst, $branch1, ['Deployment commit']); - $this->gitAssertFilesNotExist($this->dst, 'f3'); - } } diff --git a/tests/phpunit/Functional/ForcePushTest.php b/tests/phpunit/Functional/ForcePushTest.php index 46bfd2c..4a6b8bd 100644 --- a/tests/phpunit/Functional/ForcePushTest.php +++ b/tests/phpunit/Functional/ForcePushTest.php @@ -1,6 +1,6 @@ mode = 'force-push'; - parent::setUp(); - } + protected function setUp(): void { + $this->mode = 'force-push'; + parent::setUp(); + } - public function testBuild(): void - { - $this->gitCreateFixtureCommits(2); + public function testBuild(): void { + $this->gitCreateFixtureCommits(2); - $output = $this->assertBuildSuccess(); - $this->assertStringContainsString('Mode: force-push', $output); - $this->assertStringContainsString('Will push: Yes', $output); + $output = $this->assertBuildSuccess(); + $this->assertStringContainsString('Mode: force-push', $output); + $this->assertStringContainsString('Will push: Yes', $output); - $this->assertFixtureCommits(2, $this->dst, 'testbranch', ['Deployment commit']); - } + $this->assertFixtureCommits(2, $this->dst, 'testbranch', ['Deployment commit']); + } - public function testBuildMoreCommits(): void - { - $this->gitCreateFixtureCommits(2); + public function testBuildMoreCommits(): void { + $this->gitCreateFixtureCommits(2); - $this->assertBuildSuccess(); + $this->assertBuildSuccess(); - $this->assertFixtureCommits(2, $this->dst, 'testbranch', ['Deployment commit']); - - $this->gitCreateFixtureCommits(3, 2); - $this->assertBuildSuccess(); + $this->assertFixtureCommits(2, $this->dst, 'testbranch', ['Deployment commit']); - $this->assertFixtureCommits(5, $this->dst, 'testbranch', ['Deployment commit']); - } + $this->gitCreateFixtureCommits(3, 2); + $this->assertBuildSuccess(); - public function testIdempotence(): void - { - $this->gitCreateFixtureCommits(2); + $this->assertFixtureCommits(5, $this->dst, 'testbranch', ['Deployment commit']); + } - $this->assertBuildSuccess(); - $this->assertFixtureCommits(2, $this->dst, 'testbranch', ['Deployment commit']); + public function testIdempotence(): void { + $this->gitCreateFixtureCommits(2); + + $this->assertBuildSuccess(); + $this->assertFixtureCommits(2, $this->dst, 'testbranch', ['Deployment commit']); + + $this->assertBuildSuccess(); + $this->assertFixtureCommits(2, $this->dst, 'testbranch', ['Deployment commit']); + } + + public function testSubRepos(): void { + $this->gitCreateFixtureCommits(2); + + $this->gitCreateFixtureFile($this->src, 'c'); + $this->gitCommitAll($this->src, 'Commit number 3'); + + $this->gitInitRepo($this->src . DIRECTORY_SEPARATOR . 'r1/'); + $this->gitCreateFixtureFile($this->src, 'r1/c'); + + $this->gitInitRepo($this->src . DIRECTORY_SEPARATOR . 'r2/r21'); + $this->gitCreateFixtureFile($this->src, 'r2/r21/c'); + + $this->gitInitRepo($this->src . DIRECTORY_SEPARATOR . 'r3/r31/r311'); + $this->gitCreateFixtureFile($this->src, 'r3/r31/r311/c'); + + $this->gitAssertFilesExist($this->src, ['r1/c']); + $this->gitAssertFilesNotExist($this->src, ['r1/.git/index']); + $this->gitAssertFilesNotExist($this->src, ['r2/r21.git/index']); + $this->gitAssertFilesNotExist($this->src, ['r3/r31/r311/.git/index']); + + $output = $this->assertBuildSuccess('--debug'); + $this->assertStringContainsString(sprintf('Removing sub-repository "%s"', $this->src . DIRECTORY_SEPARATOR . 'r1/.git'), $output); + $this->assertStringContainsString(sprintf('Removing sub-repository "%s"', $this->src . DIRECTORY_SEPARATOR . 'r2/r21/.git'), $output); + $this->assertStringContainsString(sprintf('Removing sub-repository "%s"', $this->src . DIRECTORY_SEPARATOR . 'r3/r31/r311/.git'), $output); + $this->assertFixtureCommits(2, $this->dst, 'testbranch', ['Commit number 3', 'Deployment commit']); + + $this->gitAssertFilesExist($this->dst, ['r1/c']); + $this->gitAssertFilesExist($this->dst, ['r2/r21/c']); + $this->gitAssertFilesExist($this->dst, ['r3/r31/r311/c']); + $this->gitAssertFilesNotExist($this->dst, ['r1/.git/index']); + $this->gitAssertFilesNotExist($this->dst, ['r1/.git']); + $this->gitAssertFilesNotExist($this->dst, ['r2/r21/.git/index']); + $this->gitAssertFilesNotExist($this->dst, ['r2/r21/.git']); + $this->gitAssertFilesNotExist($this->dst, ['r3/r31/311/.git/index']); + $this->gitAssertFilesNotExist($this->dst, ['r3/r31/311/.git']); + } + + public function testCleanupAfterSuccess(): void { + $this->gitCreateFixtureCommits(2); + + $this->assertBuildSuccess(); + $this->assertFixtureCommits(2, $this->dst, 'testbranch', ['Deployment commit']); + + $this->assertGitCurrentBranch($this->src, $this->currentBranch); + $this->assertGitNoRemote($this->src, $this->remote); + } + + public function testCleanupAfterFailure(): void { + $this->gitCreateFixtureCommits(1); + + $output = $this->assertBuildFailure('--branch=*invalid'); + + $this->assertStringContainsString('Incorrect value "*invalid" specified for git remote branch', $output); + $this->assertGitCurrentBranch($this->src, $this->currentBranch); + $this->assertGitNoRemote($this->src, $this->remote); + } + + public function testGitignore(): void { + $this->gitCreateFixtureFile($this->src, '.gitignore', 'f3'); + $this->gitCreateFixtureCommits(2); + $this->gitCreateFixtureFile($this->src, 'f3'); + + $this->assertBuildSuccess(); + + $this->assertFixtureCommits(2, $this->dst, 'testbranch', ['Deployment commit']); + $this->gitAssertFilesNotExist($this->dst, 'f3'); + + // Now, remove the .gitignore and push again. + $this->gitRemoveFixtureFile($this->src, '.gitignore'); + $this->gitCommitAll($this->src, 'Commit number 3'); + $this->assertBuildSuccess(); + $this->assertFixtureCommits(3, $this->dst, 'testbranch', ['Deployment commit']); + } + + public function testGitignoreCustom(): void { + $this->gitCreateFixtureCommits(2); + $this->gitCreateFixtureFile($this->src, 'uic'); + $this->gitCreateFixtureFile($this->src, 'uc'); + + $this->gitCreateFixtureFile($this->src, 'mygitignore', 'uic'); + + $this->assertBuildSuccess('--gitignore=' . $this->src . DIRECTORY_SEPARATOR . 'mygitignore'); + + $this->assertFixtureCommits(2, $this->dst, 'testbranch', ['Deployment commit']); + $this->gitAssertFilesNotExist($this->dst, 'uic'); + $this->gitAssertFilesExist($this->dst, 'uc'); + + // Now, remove the .gitignore and push again. + // We have to create 'uic' file since it was rightfully + // removed during previous build run and the source repo branch was not + // reset (uncommitted files would be removed, unless they are excluded + // in .gitignore). + $this->gitCreateFixtureFile($this->src, 'uic'); + $this->gitRemoveFixtureFile($this->src, 'mygitignore'); + $this->gitCommitAll($this->src, 'Commit number 3'); + $this->assertBuildSuccess(); + + $this->assertFixtureCommits(3, $this->dst, 'testbranch', ['Deployment commit'], FALSE); + $this->gitAssertFilesCommitted($this->dst, ['f1', 'f2', 'uic'], 'testbranch'); + $this->gitAssertFilesExist($this->dst, ['f1', 'f2', 'uic'], 'testbranch'); + $this->gitAssertNoFilesCommitted($this->dst, ['uc'], 'testbranch'); + } + + public function testGitignoreCustomRemoveCommittedFiles(): void { + $this->gitCreateFixtureFile($this->src, '.gitignore', ['ii', 'ic']); + + $this->gitCreateFixtureFile($this->src, 'ii'); + $this->gitCreateFixtureFile($this->src, 'ic'); + $this->gitCreateFixtureFile($this->src, 'd/cc'); + $this->gitCreateFixtureFile($this->src, 'd/ci'); + $this->gitCreateFixtureCommits(2); + $this->gitCommitAll($this->src, 'Custom third commit'); + $this->gitCreateFixtureFile($this->src, 'ui'); + $this->gitCreateFixtureFile($this->src, 'uc'); + $this->gitAssertFilesCommitted($this->src, ['.gitignore', 'f1', 'f2', 'd/cc', 'd/ci']); + $this->gitAssertNoFilesCommitted($this->src, ['ii', 'ic', 'ui', 'uc']); + + $this->gitCreateFixtureFile($this->src, 'mygitignore', ['f1', 'ii', 'ci', 'ui']); + + $this->assertBuildSuccess('--gitignore=' . $this->src . DIRECTORY_SEPARATOR . 'mygitignore'); + + $this->assertFixtureCommits(2, $this->dst, 'testbranch', ['Custom third commit', 'Deployment commit'], FALSE); + $this->gitAssertFilesCommitted($this->dst, ['.gitignore', 'f2', 'ic', 'd/cc', 'uc'], 'testbranch'); + $this->gitAssertNoFilesCommitted($this->dst, ['f1', 'ii', 'd/ci', 'ui'], 'testbranch'); + $this->gitAssertFilesExist($this->dst, ['f2', 'ic', 'd/cc', 'uc'], 'testbranch'); + $this->gitAssertFilesNotExist($this->dst, ['f1', 'ii', 'd/ci', 'ui'], 'testbranch'); + } + + public function testGitignoreCustomWhitelisting(): void { + $this->gitCreateFixtureFile($this->src, '.gitignore', ['ii', 'ic', 'd_ic', 'd_ii', '/vendor']); + + $this->gitCreateFixtureFile($this->src, 'ii'); + $this->gitCreateFixtureFile($this->src, 'ic'); + $this->gitCreateFixtureFile($this->src, 'cc'); + $this->gitCreateFixtureFile($this->src, 'ci'); + + $this->gitCreateFixtureFile($this->src, 'd_cc/sub_cc'); + $this->gitCreateFixtureFile($this->src, 'd_ci/sub_ci'); + $this->gitCreateFixtureFile($this->src, 'd_ic/sub_ic'); + $this->gitCreateFixtureFile($this->src, 'd_ii/sub_ii'); + + $this->gitCreateFixtureFile($this->src, 'vendor/ve_ii'); + $this->gitCreateFixtureFile($this->src, 'vendor_cc'); + $this->gitCreateFixtureFile($this->src, 'vendor_com with space com.txt'); + $this->gitCreateFixtureFile($this->src, 'dir_other/vendor/ve_cc'); + + $this->gitCreateFixtureCommits(2); + + $this->gitCommitAll($this->src, 'Custom third commit'); + + $this->gitAssertFilesCommitted($this->src, [ + '.gitignore', 'f1', 'f2', + 'cc', 'ci', + 'd_cc/sub_cc', 'd_ci/sub_ci', + 'vendor_cc', 'dir_other/vendor/ve_cc', 'vendor_com with space com.txt', + ]); + + $this->gitAssertNoFilesCommitted($this->src, [ + 'ii', 'ic', 'ui', 'uc', 'ud', + 'd_ic/sub_ic', 'd_ii/sub_ii', + 'vendor/ve_ii', + ]); + + $this->gitCreateFixtureFile($this->src, 'ui'); + $this->gitCreateFixtureFile($this->src, 'uc'); + $this->gitCreateFixtureFile($this->src, 'ud'); + $this->gitCreateFixtureFile($this->src, 'd_ui/sub_ui'); + $this->gitCreateFixtureFile($this->src, 'd_uc/sub_uc'); + $this->gitCreateFixtureFile($this->src, 'd_ud/sub_ud'); + + // Now, create a custom .gitignore and add non-ignored files + // (whitelisting). + $this->gitCreateFixtureFile($this->src, 'mygitignore', [ + '/*', '!f2', '!ic', '!cc', '!uc', + '!d_cc', '!d_ic', '!d_uc', + '!vendor', + ]); + + // Run the build. + $this->assertBuildSuccess('--debug --gitignore=' . $this->src . DIRECTORY_SEPARATOR . 'mygitignore'); + + $this->assertFixtureCommits(2, $this->dst, 'testbranch', ['Custom third commit', 'Deployment commit'], FALSE); + + $this->gitAssertFilesCommitted($this->dst, [ + 'f2', 'ic', 'cc', 'uc', + 'd_cc/sub_cc', 'd_ic/sub_ic', 'd_uc/sub_uc', + 'vendor/ve_ii', + ], 'testbranch'); + + $this->gitAssertNoFilesCommitted($this->dst, [ + 'f1', 'ii', 'ci', 'ui', 'ud', + 'd_ci/sub_ci', 'd_ii/sub_ii', 'd_ui/sub_ui', 'd_ud/sub_ud', + 'vendor_cc', 'dir_other/vendor/ve_cc', 'vendor_com with space com.txt', + ], 'testbranch'); + + $this->gitAssertFilesExist($this->dst, [ + 'f2', 'ic', 'cc', 'uc', + 'd_cc/sub_cc', 'd_ic/sub_ic', 'd_uc/sub_uc', + 'vendor/ve_ii', + ], 'testbranch'); + $this->gitAssertFilesNotExist($this->dst, [ + 'f1', 'ii', 'ci', 'ui', 'ud', + 'd_ci/sub_ci', + 'd_ii/sub_ii', 'd_ui/sub_ui', 'd_ud/sub_ud', + 'vendor_cc', 'dir_other/vendor/ve_cc', 'vendor_com with space com.txt', + ], 'testbranch'); + } + + public function testBuildTag(): void { + $this->gitCreateFixtureCommits(2); + $this->gitAddTag($this->src, 'tag1'); + + $this->assertBuildSuccess('--branch=[tags]', 'tag1'); + + $this->assertFixtureCommits(2, $this->dst, 'tag1', ['Deployment commit']); + } + + public function testBuildMultipleTags(): void { + $this->gitCreateFixtureCommits(2); + $this->gitAddTag($this->src, 'tag1'); + $this->gitAddTag($this->src, 'tag2'); + + $this->assertBuildSuccess('--branch=[tags]', 'tag1-tag2'); + + $this->assertFixtureCommits(2, $this->dst, 'tag1-tag2', ['Deployment commit']); + } + + public function testBuildMultipleTagsDelimiter(): void { + $this->gitCreateFixtureCommits(2); + $this->gitAddTag($this->src, 'tag1'); + $this->gitAddTag($this->src, 'tag2'); + + $this->assertBuildSuccess('--branch=[tags:__]', 'tag1__tag2'); + + $this->assertFixtureCommits(2, $this->dst, 'tag1__tag2', ['Deployment commit']); + } - $this->assertBuildSuccess(); - $this->assertFixtureCommits(2, $this->dst, 'testbranch', ['Deployment commit']); - } - - public function testSubRepos(): void - { - $this->gitCreateFixtureCommits(2); - - $this->gitCreateFixtureFile($this->src, 'c'); - $this->gitCommitAll($this->src, 'Commit number 3'); - - $this->gitInitRepo($this->src.DIRECTORY_SEPARATOR.'r1/'); - $this->gitCreateFixtureFile($this->src, 'r1/c'); - - $this->gitInitRepo($this->src.DIRECTORY_SEPARATOR.'r2/r21'); - $this->gitCreateFixtureFile($this->src, 'r2/r21/c'); - - $this->gitInitRepo($this->src.DIRECTORY_SEPARATOR.'r3/r31/r311'); - $this->gitCreateFixtureFile($this->src, 'r3/r31/r311/c'); - - $this->gitAssertFilesExist($this->src, ['r1/c']); - $this->gitAssertFilesNotExist($this->src, ['r1/.git/index']); - $this->gitAssertFilesNotExist($this->src, ['r2/r21.git/index']); - $this->gitAssertFilesNotExist($this->src, ['r3/r31/r311/.git/index']); - - $output = $this->assertBuildSuccess('--debug'); - $this->assertStringContainsString(sprintf('Removing sub-repository "%s"', $this->src.DIRECTORY_SEPARATOR.'r1/.git'), $output); - $this->assertStringContainsString(sprintf('Removing sub-repository "%s"', $this->src.DIRECTORY_SEPARATOR.'r2/r21/.git'), $output); - $this->assertStringContainsString(sprintf('Removing sub-repository "%s"', $this->src.DIRECTORY_SEPARATOR.'r3/r31/r311/.git'), $output); - $this->assertFixtureCommits(2, $this->dst, 'testbranch', ['Commit number 3', 'Deployment commit']); - - $this->gitAssertFilesExist($this->dst, ['r1/c']); - $this->gitAssertFilesExist($this->dst, ['r2/r21/c']); - $this->gitAssertFilesExist($this->dst, ['r3/r31/r311/c']); - $this->gitAssertFilesNotExist($this->dst, ['r1/.git/index']); - $this->gitAssertFilesNotExist($this->dst, ['r1/.git']); - $this->gitAssertFilesNotExist($this->dst, ['r2/r21/.git/index']); - $this->gitAssertFilesNotExist($this->dst, ['r2/r21/.git']); - $this->gitAssertFilesNotExist($this->dst, ['r3/r31/311/.git/index']); - $this->gitAssertFilesNotExist($this->dst, ['r3/r31/311/.git']); - } - - public function testCleanupAfterSuccess(): void - { - $this->gitCreateFixtureCommits(2); - - $this->assertBuildSuccess(); - $this->assertFixtureCommits(2, $this->dst, 'testbranch', ['Deployment commit']); - - $this->assertGitCurrentBranch($this->src, $this->currentBranch); - $this->assertGitNoRemote($this->src, $this->remote); - } - - public function testCleanupAfterFailure(): void - { - $this->gitCreateFixtureCommits(1); - - $output = $this->assertBuildFailure('--branch=*invalid'); - - $this->assertStringContainsString('Incorrect value "*invalid" specified for git remote branch', $output); - $this->assertGitCurrentBranch($this->src, $this->currentBranch); - $this->assertGitNoRemote($this->src, $this->remote); - } - - public function testGitignore(): void - { - $this->gitCreateFixtureFile($this->src, '.gitignore', 'f3'); - $this->gitCreateFixtureCommits(2); - $this->gitCreateFixtureFile($this->src, 'f3'); - - $this->assertBuildSuccess(); - - $this->assertFixtureCommits(2, $this->dst, 'testbranch', ['Deployment commit']); - $this->gitAssertFilesNotExist($this->dst, 'f3'); - - // Now, remove the .gitignore and push again. - $this->gitRemoveFixtureFile($this->src, '.gitignore'); - $this->gitCommitAll($this->src, 'Commit number 3'); - $this->assertBuildSuccess(); - $this->assertFixtureCommits(3, $this->dst, 'testbranch', ['Deployment commit']); - } - - public function testGitignoreCustom(): void - { - $this->gitCreateFixtureCommits(2); - $this->gitCreateFixtureFile($this->src, 'uic'); - $this->gitCreateFixtureFile($this->src, 'uc'); - - $this->gitCreateFixtureFile($this->src, 'mygitignore', 'uic'); - - $this->assertBuildSuccess('--gitignore='.$this->src.DIRECTORY_SEPARATOR.'mygitignore'); - - $this->assertFixtureCommits(2, $this->dst, 'testbranch', ['Deployment commit']); - $this->gitAssertFilesNotExist($this->dst, 'uic'); - $this->gitAssertFilesExist($this->dst, 'uc'); - - // Now, remove the .gitignore and push again. - // We have to create 'uic' file since it was rightfully - // removed during previous build run and the source repo branch was not - // reset (uncommitted files would be removed, unless they are excluded - // in .gitignore). - $this->gitCreateFixtureFile($this->src, 'uic'); - $this->gitRemoveFixtureFile($this->src, 'mygitignore'); - $this->gitCommitAll($this->src, 'Commit number 3'); - $this->assertBuildSuccess(); - - $this->assertFixtureCommits(3, $this->dst, 'testbranch', ['Deployment commit'], false); - $this->gitAssertFilesCommitted($this->dst, ['f1', 'f2', 'uic'], 'testbranch'); - $this->gitAssertFilesExist($this->dst, ['f1', 'f2', 'uic'], 'testbranch'); - $this->gitAssertNoFilesCommitted($this->dst, ['uc'], 'testbranch'); - } - - public function testGitignoreCustomRemoveCommittedFiles(): void - { - $this->gitCreateFixtureFile($this->src, '.gitignore', ['ii', 'ic']); - - $this->gitCreateFixtureFile($this->src, 'ii'); - $this->gitCreateFixtureFile($this->src, 'ic'); - $this->gitCreateFixtureFile($this->src, 'd/cc'); - $this->gitCreateFixtureFile($this->src, 'd/ci'); - $this->gitCreateFixtureCommits(2); - $this->gitCommitAll($this->src, 'Custom third commit'); - $this->gitCreateFixtureFile($this->src, 'ui'); - $this->gitCreateFixtureFile($this->src, 'uc'); - $this->gitAssertFilesCommitted($this->src, ['.gitignore', 'f1', 'f2', 'd/cc', 'd/ci']); - $this->gitAssertNoFilesCommitted($this->src, ['ii', 'ic', 'ui', 'uc']); - - $this->gitCreateFixtureFile($this->src, 'mygitignore', ['f1', 'ii', 'ci', 'ui']); - - $this->assertBuildSuccess('--gitignore='.$this->src.DIRECTORY_SEPARATOR.'mygitignore'); - - $this->assertFixtureCommits(2, $this->dst, 'testbranch', ['Custom third commit', 'Deployment commit'], false); - $this->gitAssertFilesCommitted($this->dst, ['.gitignore', 'f2', 'ic', 'd/cc', 'uc'], 'testbranch'); - $this->gitAssertNoFilesCommitted($this->dst, ['f1', 'ii', 'd/ci', 'ui'], 'testbranch'); - $this->gitAssertFilesExist($this->dst, ['f2', 'ic', 'd/cc', 'uc'], 'testbranch'); - $this->gitAssertFilesNotExist($this->dst, ['f1', 'ii', 'd/ci', 'ui'], 'testbranch'); - } - - public function testGitignoreCustomWhitelisting(): void - { - $this->gitCreateFixtureFile($this->src, '.gitignore', ['ii', 'ic', 'd_ic', 'd_ii', '/vendor']); - - $this->gitCreateFixtureFile($this->src, 'ii'); - $this->gitCreateFixtureFile($this->src, 'ic'); - $this->gitCreateFixtureFile($this->src, 'cc'); - $this->gitCreateFixtureFile($this->src, 'ci'); - - $this->gitCreateFixtureFile($this->src, 'd_cc/sub_cc'); - $this->gitCreateFixtureFile($this->src, 'd_ci/sub_ci'); - $this->gitCreateFixtureFile($this->src, 'd_ic/sub_ic'); - $this->gitCreateFixtureFile($this->src, 'd_ii/sub_ii'); - - $this->gitCreateFixtureFile($this->src, 'vendor/ve_ii'); - $this->gitCreateFixtureFile($this->src, 'vendor_cc'); - $this->gitCreateFixtureFile($this->src, 'vendor_com with space com.txt'); - $this->gitCreateFixtureFile($this->src, 'dir_other/vendor/ve_cc'); - - $this->gitCreateFixtureCommits(2); - - $this->gitCommitAll($this->src, 'Custom third commit'); - - $this->gitAssertFilesCommitted($this->src, [ - '.gitignore', 'f1', 'f2', - 'cc', 'ci', - 'd_cc/sub_cc', 'd_ci/sub_ci', - 'vendor_cc', 'dir_other/vendor/ve_cc', 'vendor_com with space com.txt', - ]); - - $this->gitAssertNoFilesCommitted($this->src, [ - 'ii', 'ic', 'ui', 'uc', 'ud', - 'd_ic/sub_ic', 'd_ii/sub_ii', - 'vendor/ve_ii', - ]); - - $this->gitCreateFixtureFile($this->src, 'ui'); - $this->gitCreateFixtureFile($this->src, 'uc'); - $this->gitCreateFixtureFile($this->src, 'ud'); - $this->gitCreateFixtureFile($this->src, 'd_ui/sub_ui'); - $this->gitCreateFixtureFile($this->src, 'd_uc/sub_uc'); - $this->gitCreateFixtureFile($this->src, 'd_ud/sub_ud'); - - // Now, create a custom .gitignore and add non-ignored files - // (whitelisting). - $this->gitCreateFixtureFile($this->src, 'mygitignore', [ - '/*', '!f2', '!ic', '!cc', '!uc', - '!d_cc', '!d_ic', '!d_uc', - '!vendor', - ]); - - // Run the build. - $this->assertBuildSuccess('--debug --gitignore='.$this->src.DIRECTORY_SEPARATOR.'mygitignore'); - - $this->assertFixtureCommits(2, $this->dst, 'testbranch', ['Custom third commit', 'Deployment commit'], false); - - $this->gitAssertFilesCommitted($this->dst, [ - 'f2', 'ic', 'cc', 'uc', - 'd_cc/sub_cc', 'd_ic/sub_ic', 'd_uc/sub_uc', - 'vendor/ve_ii', - ], 'testbranch'); - - $this->gitAssertNoFilesCommitted($this->dst, [ - 'f1', 'ii', 'ci', 'ui', 'ud', - 'd_ci/sub_ci', 'd_ii/sub_ii', 'd_ui/sub_ui', 'd_ud/sub_ud', - 'vendor_cc', 'dir_other/vendor/ve_cc', 'vendor_com with space com.txt', - ], 'testbranch'); - - $this->gitAssertFilesExist($this->dst, [ - 'f2', 'ic', 'cc', 'uc', - 'd_cc/sub_cc', 'd_ic/sub_ic', 'd_uc/sub_uc', - 'vendor/ve_ii', - ], 'testbranch'); - $this->gitAssertFilesNotExist($this->dst, [ - 'f1', 'ii', 'ci', 'ui', 'ud', - 'd_ci/sub_ci', - 'd_ii/sub_ii', 'd_ui/sub_ui', 'd_ud/sub_ud', - 'vendor_cc', 'dir_other/vendor/ve_cc', 'vendor_com with space com.txt', - ], 'testbranch'); - } - - public function testBuildTag(): void - { - $this->gitCreateFixtureCommits(2); - $this->gitAddTag($this->src, 'tag1'); - - $this->assertBuildSuccess('--branch=[tags]', 'tag1'); - - $this->assertFixtureCommits(2, $this->dst, 'tag1', ['Deployment commit']); - } - - public function testBuildMultipleTags(): void - { - $this->gitCreateFixtureCommits(2); - $this->gitAddTag($this->src, 'tag1'); - $this->gitAddTag($this->src, 'tag2'); - - $this->assertBuildSuccess('--branch=[tags]', 'tag1-tag2'); - - $this->assertFixtureCommits(2, $this->dst, 'tag1-tag2', ['Deployment commit']); - } - - public function testBuildMultipleTagsDelimiter(): void - { - $this->gitCreateFixtureCommits(2); - $this->gitAddTag($this->src, 'tag1'); - $this->gitAddTag($this->src, 'tag2'); - - $this->assertBuildSuccess('--branch=[tags:__]', 'tag1__tag2'); - - $this->assertFixtureCommits(2, $this->dst, 'tag1__tag2', ['Deployment commit']); - } } diff --git a/tests/phpunit/Functional/GeneralTest.php b/tests/phpunit/Functional/GeneralTest.php index 5db1b1a..a14ea47 100644 --- a/tests/phpunit/Functional/GeneralTest.php +++ b/tests/phpunit/Functional/GeneralTest.php @@ -1,6 +1,6 @@ runGitArtifactCommand('--help'); - $this->assertStringContainsString('artifact [options] [--] ', implode(PHP_EOL, $output)); - $this->assertStringContainsString('Push artifact of current repository to remote git repository.', implode(PHP_EOL, $output)); - } - - public function testCompulsoryParameter(): void - { - $output = $this->runGitArtifactCommand('', true); - - $this->assertStringContainsString('Not enough arguments (missing: "remote")', implode(PHP_EOL, $output)); - } - - public function testInfo(): void - { - $this->gitCreateFixtureCommits(1); - $output = $this->runBuild(); - - $this->assertStringContainsString('Artifact information', $output); - $this->assertStringContainsString('Mode: force-push', $output); - $this->assertStringContainsString('Source repository: '.$this->src, $output); - $this->assertStringContainsString('Remote repository: '.$this->dst, $output); - $this->assertStringContainsString('Remote branch: '.$this->currentBranch, $output); - $this->assertStringContainsString('Gitignore file: No', $output); - $this->assertStringContainsString('Will push: No', $output); - $this->assertStringNotContainsString('Added changes:', $output); - - $this->assertStringContainsString('Cowardly refusing to push to remote. Use --push option to perform an actual push.', $output); - - $this->gitAssertFilesNotExist($this->dst, 'f1', $this->currentBranch); - } - - public function testShowChanges(): void - { - $this->gitCreateFixtureCommits(1); - $output = $this->runBuild('--show-changes'); - - $this->assertStringContainsString('Added changes:', $output); - - $this->assertStringContainsString('Cowardly refusing to push to remote. Use --push option to perform an actual push.', $output); - $this->gitAssertFilesNotExist($this->dst, 'f1', $this->currentBranch); - } - - public function testNoCleanup(): void - { - $this->gitCreateFixtureCommits(1); - $output = $this->runBuild('--no-cleanup'); - - $this->assertGitCurrentBranch($this->src, $this->artifactBranch); - - $this->assertStringContainsString('Cowardly refusing to push to remote. Use --push option to perform an actual push.', $output); - $this->gitAssertFilesNotExist($this->dst, 'f1', $this->currentBranch); - } - - public function testReport(): void - { - $report = $this->src.DIRECTORY_SEPARATOR.'report.txt'; - - $this->gitCreateFixtureCommits(1); - $this->runBuild(sprintf('--report=%s', $report)); - - $this->assertFileExists($report); - $output = file_get_contents($report); - - $this->assertStringContainsString('Artifact report', (string) $output); - $this->assertStringContainsString(sprintf('Source repository: %s', $this->src), (string) $output); - $this->assertStringContainsString(sprintf('Remote repository: %s', $this->dst), (string) $output); - $this->assertStringContainsString(sprintf('Remote branch: %s', $this->currentBranch), (string) $output); - $this->assertStringContainsString('Gitignore file: No', (string) $output); - $this->assertStringContainsString('Push result: Success', (string) $output); - } - - public function testDebug(): void - { - $this->gitCreateFixtureCommits(1); - $output = $this->runBuild('--debug'); - - $this->assertStringContainsString('Debug messages enabled', $output); - - $this->assertStringContainsString('Cowardly refusing to push to remote. Use --push option to perform an actual push.', $output); - $this->gitAssertFilesNotExist($this->dst, 'f1', $this->currentBranch); - } - - public function testDebugDisabled(): void - { - $this->gitCreateFixtureCommits(1); - $output = $this->runBuild(); - - $this->assertStringNotContainsString('Debug messages enabled', $output); - - $this->assertStringContainsString('Cowardly refusing to push to remote. Use --push option to perform an actual push.', $output); - $this->gitAssertFilesNotExist($this->dst, 'f1', $this->currentBranch); - } +class GeneralTest extends AbstractFunctionalTestCase { + + public function testHelp(): void { + $output = $this->runGitArtifactCommand('--help'); + $this->assertStringContainsString('artifact [options] [--] ', implode(PHP_EOL, $output)); + $this->assertStringContainsString('Push artifact of current repository to remote git repository.', implode(PHP_EOL, $output)); + } + + public function testCompulsoryParameter(): void { + $output = $this->runGitArtifactCommand('', TRUE); + + $this->assertStringContainsString('Not enough arguments (missing: "remote")', implode(PHP_EOL, $output)); + } + + public function testInfo(): void { + $this->gitCreateFixtureCommits(1); + $output = $this->runBuild(); + + $this->assertStringContainsString('Artifact information', $output); + $this->assertStringContainsString('Mode: force-push', $output); + $this->assertStringContainsString('Source repository: ' . $this->src, $output); + $this->assertStringContainsString('Remote repository: ' . $this->dst, $output); + $this->assertStringContainsString('Remote branch: ' . $this->currentBranch, $output); + $this->assertStringContainsString('Gitignore file: No', $output); + $this->assertStringContainsString('Will push: No', $output); + $this->assertStringNotContainsString('Added changes:', $output); + + $this->assertStringContainsString('Cowardly refusing to push to remote. Use --push option to perform an actual push.', $output); + + $this->gitAssertFilesNotExist($this->dst, 'f1', $this->currentBranch); + } + + public function testShowChanges(): void { + $this->gitCreateFixtureCommits(1); + $output = $this->runBuild('--show-changes'); + + $this->assertStringContainsString('Added changes:', $output); + + $this->assertStringContainsString('Cowardly refusing to push to remote. Use --push option to perform an actual push.', $output); + $this->gitAssertFilesNotExist($this->dst, 'f1', $this->currentBranch); + } + + public function testNoCleanup(): void { + $this->gitCreateFixtureCommits(1); + $output = $this->runBuild('--no-cleanup'); + + $this->assertGitCurrentBranch($this->src, $this->artifactBranch); + + $this->assertStringContainsString('Cowardly refusing to push to remote. Use --push option to perform an actual push.', $output); + $this->gitAssertFilesNotExist($this->dst, 'f1', $this->currentBranch); + } + + public function testReport(): void { + $report = $this->src . DIRECTORY_SEPARATOR . 'report.txt'; + + $this->gitCreateFixtureCommits(1); + $this->runBuild(sprintf('--report=%s', $report)); + + $this->assertFileExists($report); + $output = file_get_contents($report); + + $this->assertStringContainsString('Artifact report', (string) $output); + $this->assertStringContainsString(sprintf('Source repository: %s', $this->src), (string) $output); + $this->assertStringContainsString(sprintf('Remote repository: %s', $this->dst), (string) $output); + $this->assertStringContainsString(sprintf('Remote branch: %s', $this->currentBranch), (string) $output); + $this->assertStringContainsString('Gitignore file: No', (string) $output); + $this->assertStringContainsString('Push result: Success', (string) $output); + } + + public function testDebug(): void { + $this->gitCreateFixtureCommits(1); + $output = $this->runBuild('--debug'); + + $this->assertStringContainsString('Debug messages enabled', $output); + + $this->assertStringContainsString('Cowardly refusing to push to remote. Use --push option to perform an actual push.', $output); + $this->gitAssertFilesNotExist($this->dst, 'f1', $this->currentBranch); + } + + public function testDebugDisabled(): void { + $this->gitCreateFixtureCommits(1); + $output = $this->runBuild(); + + $this->assertStringNotContainsString('Debug messages enabled', $output); + + $this->assertStringContainsString('Cowardly refusing to push to remote. Use --push option to perform an actual push.', $output); + $this->gitAssertFilesNotExist($this->dst, 'f1', $this->currentBranch); + } + } diff --git a/tests/phpunit/Functional/TagTest.php b/tests/phpunit/Functional/TagTest.php index 3779ec6..6a1c151 100644 --- a/tests/phpunit/Functional/TagTest.php +++ b/tests/phpunit/Functional/TagTest.php @@ -1,6 +1,6 @@ mode = 'force-push'; - parent::setUp(); - } - - public function testDetachedTag(): void - { - $this->gitCreateFixtureCommits(2); - $this->gitAddTag($this->src, 'tag1'); - $this->gitCheckout($this->src, 'tag1'); - $srcBranches = $this->runGitCommand('branch'); - - $output = $this->assertBuildSuccess(); - $this->assertStringContainsString('Mode: force-push', $output); - $this->assertStringContainsString('Will push: Yes', $output); - - $this->assertFixtureCommits(2, $this->dst, 'testbranch', ['Deployment commit']); - $this->assertEquals($srcBranches, $this->runGitCommand('branch'), 'Cleanup has correctly returned to the previous branch.'); - } +class TagTest extends AbstractFunctionalTestCase { + + /** + * {@inheritdoc} + */ + protected function setUp(): void { + $this->mode = 'force-push'; + parent::setUp(); + } + + public function testDetachedTag(): void { + $this->gitCreateFixtureCommits(2); + $this->gitAddTag($this->src, 'tag1'); + $this->gitCheckout($this->src, 'tag1'); + $srcBranches = $this->runGitCommand('branch'); + + $output = $this->assertBuildSuccess(); + $this->assertStringContainsString('Mode: force-push', $output); + $this->assertStringContainsString('Will push: Yes', $output); + + $this->assertFixtureCommits(2, $this->dst, 'testbranch', ['Deployment commit']); + $this->assertEquals($srcBranches, $this->runGitCommand('branch'), 'Cleanup has correctly returned to the previous branch.'); + } + } diff --git a/tests/phpunit/Functional/TokenTest.php b/tests/phpunit/Functional/TokenTest.php index 5187e72..ec2a455 100644 --- a/tests/phpunit/Functional/TokenTest.php +++ b/tests/phpunit/Functional/TokenTest.php @@ -1,6 +1,6 @@ prepareMock(TokenTrait::class, [ - 'getToken'.ucfirst($name) => static function (?string $prop) use ($replacement) : string { - return empty($prop) ? $replacement : $replacement.' with property '.$prop; - }, - ]); + /** + * @dataProvider dataProviderTokenProcess + */ + public function testTokenProcess(string $string, string $name, string $replacement, string $expectedString): void { + $mock = $this->prepareMock(TokenTrait::class, [ + 'getToken' . ucfirst($name) => static function (?string $prop) use ($replacement) : string { + return empty($prop) ? $replacement : $replacement . ' with property ' . $prop; + }, + ]); - $actual = $this->callProtectedMethod($mock, 'tokenProcess', [$string]); - $this->assertEquals($expectedString, $actual); - } + $actual = $this->callProtectedMethod($mock, 'tokenProcess', [$string]); + $this->assertEquals($expectedString, $actual); + } - /** - * @return array> - * Data provider. - */ - public static function dataProviderTokenProcess(): array - { - return [ - [ - '', - '', - '', - '', - ], - [ - '', - 'sometoken', - 'somevalue', - '', - ], - [ - 'string without a token', - 'sometoken', - 'somevalue', - 'string without a token', - ], - [ - 'string with sometoken without delimiters', - 'sometoken', - 'somevalue', - 'string with sometoken without delimiters', - ], - [ - 'string with [sometoken broken delimiters', - 'sometoken', - 'somevalue', - 'string with [sometoken broken delimiters', - ], - [ - 'string with sometoken] broken delimiters', - 'sometoken', - 'somevalue', - 'string with sometoken] broken delimiters', - ], - // Proper token. - [ - '[sometoken]', - 'sometoken', - 'somevalue', - 'somevalue', - ], - [ - 'string with [sometoken] present', - 'sometoken', - 'somevalue', - 'string with somevalue present', - ], - // Token with properties. - [ - 'string with [sometoken:prop] present', - 'sometoken', - 'somevalue', - 'string with somevalue with property prop present', - ], - [ - 'string with [sometoken:prop:otherprop] present', - 'sometoken', - 'somevalue', - 'string with somevalue with property prop:otherprop present', - ], - ]; - } + /** + * @return array> + * Data provider. + */ + public static function dataProviderTokenProcess(): array { + return [ + [ + '', + '', + '', + '', + ], + [ + '', + 'sometoken', + 'somevalue', + '', + ], + [ + 'string without a token', + 'sometoken', + 'somevalue', + 'string without a token', + ], + [ + 'string with sometoken without delimiters', + 'sometoken', + 'somevalue', + 'string with sometoken without delimiters', + ], + [ + 'string with [sometoken broken delimiters', + 'sometoken', + 'somevalue', + 'string with [sometoken broken delimiters', + ], + [ + 'string with sometoken] broken delimiters', + 'sometoken', + 'somevalue', + 'string with sometoken] broken delimiters', + ], + // Proper token. + [ + '[sometoken]', + 'sometoken', + 'somevalue', + 'somevalue', + ], + [ + 'string with [sometoken] present', + 'sometoken', + 'somevalue', + 'string with somevalue present', + ], + // Token with properties. + [ + 'string with [sometoken:prop] present', + 'sometoken', + 'somevalue', + 'string with somevalue with property prop present', + ], + [ + 'string with [sometoken:prop:otherprop] present', + 'sometoken', + 'somevalue', + 'string with somevalue with property prop:otherprop present', + ], + ]; + } - /** - * @dataProvider dataProviderHasToken - */ - public function testHasToken(string $string, bool $hasToken): void - { - $mock = $this->prepareMock(TokenTrait::class); + /** + * @dataProvider dataProviderHasToken + */ + public function testHasToken(string $string, bool $hasToken): void { + $mock = $this->prepareMock(TokenTrait::class); - $actual = $this->callProtectedMethod($mock, 'hasToken', [$string]); - $this->assertEquals($hasToken, $actual); - } + $actual = $this->callProtectedMethod($mock, 'hasToken', [$string]); + $this->assertEquals($hasToken, $actual); + } + + /** + * @return array + * Data provider. + */ + public static function dataProviderHasToken(): array { + return [ + ['notoken', FALSE], + ['[broken token', FALSE], + ['broken token]', FALSE], + ['[token]', TRUE], + ['string with [token] and other string', TRUE], + ['[token] and [otherttoken]', TRUE], + ]; + } - /** - * @return array - * Data provider. - */ - public static function dataProviderHasToken(): array - { - return [ - ['notoken', false], - ['[broken token', false], - ['broken token]', false], - ['[token]', true], - ['string with [token] and other string', true], - ['[token] and [otherttoken]', true], - ]; - } } diff --git a/tests/phpunit/Traits/CommandTrait.php b/tests/phpunit/Traits/CommandTrait.php index 9bc4144..dbd4301 100644 --- a/tests/phpunit/Traits/CommandTrait.php +++ b/tests/phpunit/Traits/CommandTrait.php @@ -1,6 +1,6 @@ printDebug = $printDebug; - $this->fs = new Filesystem(); - $this->src = $src; - $this->gitInitRepo($this->src); - $this->dst = $remote; - $this->gitInitRepo($this->dst); - // Allow pushing into already checked out branch. We need this to - // avoid additional management of fixture repository. - $this->runGitCommand('config receive.denyCurrentBranch ignore', $this->dst); - } +trait CommandTrait { - /** - * Tear down test. - * - * To be called by test's tearDown() method. - */ - protected function tearDown(): void - { - if ($this->fs->exists($this->src)) { - $this->fs->remove($this->src); - } - if ($this->fs->exists($this->dst)) { - $this->fs->remove($this->dst); - } - } + /** + * Fixture source repository directory. + * + * @var string + */ + protected $src; - /** - * Init git repository. - * - * @param string $path - * Path to the repository directory. - */ - protected function gitInitRepo(string $path): void - { - if ($this->fs->exists($path)) { - $this->fs->remove($path); - } - $this->fs->mkdir($path); - - $this->runGitCommand('init -b master', $path); - } + /** + * Fixture remote repository directory. + * + * @var string + */ + protected $dst; - /** - * Get all commit hashes in the repository. - * - * @param string|null $path - * Optional path to the repository directory. If not provided, fixture - * directory is used. - * @param string $format - * Format of commits. - * - * @return array - * Array of commit hashes, sorted from the earliest to the latest commit. - * - * @throws \Exception - */ - protected function gitGetAllCommits(string $path = null, string $format = '%s'): array - { - $commits = []; - try { - $commits = $this->runGitCommand('log --format="'.$format.'"', $path); - } catch (\Exception $exception) { - $output = ($exception->getPrevious() instanceof \Throwable) ? $exception->getPrevious()->getMessage() : ''; - $output = trim($output); - // Different versions of Git may produce these expected messages. - $expectedErrorMessages = [ - "fatal: bad default revision 'HEAD'", - "fatal: your current branch 'master' does not have any commits yet", - ]; - if (!in_array($output, $expectedErrorMessages)) { - throw $exception; - } - } - - return array_reverse(array_filter($commits)); - } + /** + * File system. + * + * @var \Symfony\Component\Filesystem\Filesystem + */ + protected $fs; - /** - * Get a range of commits. - * - * @param array $range - * Array of commit indexes, stating from 1. - * @param string|null $path - * Optional path to the repository directory. If not provided, fixture - * directory is used. - * - * @return array - * Array of commit hashes, ordered by keys in the $range. - * - * @throws \Exception - */ - protected function gitGetCommitsHashesFromRange(array $range, string $path = null): array - { - $commits = $this->gitGetAllCommits($path); - - array_walk($range, static function (&$v) : void { - --$v; - }); - - $ret = []; - foreach ($range as $key) { - $ret[] = $commits[$key]; - } - - return $ret; - } + /** + * Flag to denote that debug information should be printed. + * + * @var bool + */ + protected $printDebug; - /** - * Get all committed files. - * - * @param string|null $path - * Optional path to the repository directory. If not provided, fixture - * directory is used. - * - * @return array - * Array of commit committed files. - */ - protected function gitGetCommittedFiles(string $path = null): array - { - return $this->runGitCommand('ls-tree --full-tree --name-only -r HEAD', $path); + /** + * Setup test. + * + * To be called by test's setUp() method. + * + * @param string $src + * Source path. + * @param string $remote + * Remote path. + * @param bool $printDebug + * Optional flag to print debug information when running commands. + * Defaults to FALSE. + */ + protected function setUp(string $src, string $remote, bool $printDebug = FALSE): void { + $this->printDebug = $printDebug; + $this->fs = new Filesystem(); + $this->src = $src; + $this->gitInitRepo($this->src); + $this->dst = $remote; + $this->gitInitRepo($this->dst); + // Allow pushing into already checked out branch. We need this to + // avoid additional management of fixture repository. + $this->runGitCommand('config receive.denyCurrentBranch ignore', $this->dst); + } + + /** + * Tear down test. + * + * To be called by test's tearDown() method. + */ + protected function tearDown(): void { + if ($this->fs->exists($this->src)) { + $this->fs->remove($this->src); + } + if ($this->fs->exists($this->dst)) { + $this->fs->remove($this->dst); } + } - /** - * Create multiple fixture commits. - * - * @param int $count - * Number of commits to create. - * @param int $offset - * Number of commit indices to offset. - * @param string|null $path - * Optional path to the repository directory. If not provided, fixture - * directory is used. - */ - protected function gitCreateFixtureCommits(int $count, int $offset = 0, string $path = null): void - { - $path = $path ? $path : $this->src; - for ($i = $offset; $i < $count + $offset; $i++) { - $this->gitCreateFixtureCommit($i + 1, $path); - } + /** + * Init git repository. + * + * @param string $path + * Path to the repository directory. + */ + protected function gitInitRepo(string $path): void { + if ($this->fs->exists($path)) { + $this->fs->remove($path); } + $this->fs->mkdir($path); + + $this->runGitCommand('init -b master', $path); + } - /** - * Create fixture commit with specified index. - * - * @param int $index - * Index of the commit to be used in the message. - * @param string|null $path - * Optional path to the repository directory. If not provided, fixture - * directory is used. - * - * @return string - * Hash of created commit. - */ - protected function gitCreateFixtureCommit(int $index, string $path = null): string - { - $path = $path ? $path : $this->src; - $this->gitCreateFixtureFile($path, 'f'.$index); - $this->runGitCommand(sprintf('add f%s', $index), $path); - $this->runGitCommand(sprintf('commit -am "Commit number %s"', $index), $path); - - $output = $this->runGitCommand('rev-parse HEAD', $path); - - return trim(implode(' ', $output)); + /** + * Get all commit hashes in the repository. + * + * @param string|null $path + * Optional path to the repository directory. If not provided, fixture + * directory is used. + * @param string $format + * Format of commits. + * + * @return array + * Array of commit hashes, sorted from the earliest to the latest commit. + * + * @throws \Exception + */ + protected function gitGetAllCommits(string $path = NULL, string $format = '%s'): array { + $commits = []; + try { + $commits = $this->runGitCommand('log --format="' . $format . '"', $path); + } + catch (\Exception $exception) { + $output = ($exception->getPrevious() instanceof \Throwable) ? $exception->getPrevious()->getMessage() : ''; + $output = trim($output); + // Different versions of Git may produce these expected messages. + $expectedErrorMessages = [ + "fatal: bad default revision 'HEAD'", + "fatal: your current branch 'master' does not have any commits yet", + ]; + if (!in_array($output, $expectedErrorMessages)) { + throw $exception; + } } - /** - * Commit all uncommitted files. - * - * @param string $path - * Path to repository. - * @param string $message - * Commit message. - */ - protected function gitCommitAll(string $path, string $message): void - { - $this->runGitCommand('add .', $path); - $this->runGitCommand(sprintf('commit -am "%s"', $message), $path); + return array_reverse(array_filter($commits)); + } + + /** + * Get a range of commits. + * + * @param array $range + * Array of commit indexes, stating from 1. + * @param string|null $path + * Optional path to the repository directory. If not provided, fixture + * directory is used. + * + * @return array + * Array of commit hashes, ordered by keys in the $range. + * + * @throws \Exception + */ + protected function gitGetCommitsHashesFromRange(array $range, string $path = NULL): array { + $commits = $this->gitGetAllCommits($path); + + array_walk($range, static function (&$v) : void { + --$v; + }); + + $ret = []; + foreach ($range as $key) { + $ret[] = $commits[$key]; } - /** - * Checkout branch. - * - * @param string $path - * Path to repository. - * @param string $branch - * Branch name. - */ - protected function gitCheckout(string $path, string $branch): void - { - try { - $this->runGitCommand(sprintf('checkout %s', $branch), $path); - } catch (ErrorException $errorException) { - $allowedFails = [ - sprintf("error: pathspec '%s' did not match any file(s) known to git", $branch), - ]; - - $output = explode(PHP_EOL, ($errorException->getPrevious() instanceof \Throwable) ? $errorException->getPrevious()->getMessage() : ''); - // Re-throw exception if it is not one of the allowed ones. - if (empty(array_intersect($output, $allowedFails))) { - throw $errorException; - } - } + return $ret; + } + + /** + * Get all committed files. + * + * @param string|null $path + * Optional path to the repository directory. If not provided, fixture + * directory is used. + * + * @return array + * Array of commit committed files. + */ + protected function gitGetCommittedFiles(string $path = NULL): array { + return $this->runGitCommand('ls-tree --full-tree --name-only -r HEAD', $path); + } + + /** + * Create multiple fixture commits. + * + * @param int $count + * Number of commits to create. + * @param int $offset + * Number of commit indices to offset. + * @param string|null $path + * Optional path to the repository directory. If not provided, fixture + * directory is used. + */ + protected function gitCreateFixtureCommits(int $count, int $offset = 0, string $path = NULL): void { + $path = $path ? $path : $this->src; + for ($i = $offset; $i < $count + $offset; $i++) { + $this->gitCreateFixtureCommit($i + 1, $path); } + } + + /** + * Create fixture commit with specified index. + * + * @param int $index + * Index of the commit to be used in the message. + * @param string|null $path + * Optional path to the repository directory. If not provided, fixture + * directory is used. + * + * @return string + * Hash of created commit. + */ + protected function gitCreateFixtureCommit(int $index, string $path = NULL): string { + $path = $path ? $path : $this->src; + $this->gitCreateFixtureFile($path, 'f' . $index); + $this->runGitCommand(sprintf('add f%s', $index), $path); + $this->runGitCommand(sprintf('commit -am "Commit number %s"', $index), $path); + + $output = $this->runGitCommand('rev-parse HEAD', $path); + + return trim(implode(' ', $output)); + } + + /** + * Commit all uncommitted files. + * + * @param string $path + * Path to repository. + * @param string $message + * Commit message. + */ + protected function gitCommitAll(string $path, string $message): void { + $this->runGitCommand('add .', $path); + $this->runGitCommand(sprintf('commit -am "%s"', $message), $path); + } - /** - * Reset git repo at path. - * - * @param string $path - * Path to the repo. - */ - protected function gitReset($path): void - { - $this->runGitCommand('reset --hard', $path); - $this->runGitCommand('clean -dfx', $path); + /** + * Checkout branch. + * + * @param string $path + * Path to repository. + * @param string $branch + * Branch name. + */ + protected function gitCheckout(string $path, string $branch): void { + try { + $this->runGitCommand(sprintf('checkout %s', $branch), $path); } + catch (ErrorException $errorException) { + $allowedFails = [ + sprintf("error: pathspec '%s' did not match any file(s) known to git", $branch), + ]; + + $output = explode(PHP_EOL, ($errorException->getPrevious() instanceof \Throwable) ? $errorException->getPrevious()->getMessage() : ''); + // Re-throw exception if it is not one of the allowed ones. + if (empty(array_intersect($output, $allowedFails))) { + throw $errorException; + } + } + } - /** - * Create fixture file at provided path. - * - * @param string $path - * File path. - * @param string $name - * Optional file name. - * @param string|array $content - * Optional file content. - * - * @return string - * Created file name. - */ - protected function gitCreateFixtureFile(string $path, string $name = '', $content = ''): string - { - $name = $name !== '' && $name !== '0' ? $name : 'tmp'.rand(1000, 100000); - $path = $path.DIRECTORY_SEPARATOR.$name; - $dir = dirname($path); - if (!empty($dir)) { - $this->fs->mkdir($dir); - } - $this->fs->touch($path); - if (!empty($content)) { - $content = is_array($content) ? implode(PHP_EOL, $content) : $content; - $this->fs->dumpFile($path, $content); - } - - return $path; + /** + * Reset git repo at path. + * + * @param string $path + * Path to the repo. + */ + protected function gitReset($path): void { + $this->runGitCommand('reset --hard', $path); + $this->runGitCommand('clean -dfx', $path); + } + + /** + * Create fixture file at provided path. + * + * @param string $path + * File path. + * @param string $name + * Optional file name. + * @param string|array $content + * Optional file content. + * + * @return string + * Created file name. + */ + protected function gitCreateFixtureFile(string $path, string $name = '', $content = ''): string { + $name = $name !== '' && $name !== '0' ? $name : 'tmp' . rand(1000, 100000); + $path = $path . DIRECTORY_SEPARATOR . $name; + $dir = dirname($path); + if (!empty($dir)) { + $this->fs->mkdir($dir); + } + $this->fs->touch($path); + if (!empty($content)) { + $content = is_array($content) ? implode(PHP_EOL, $content) : $content; + $this->fs->dumpFile($path, $content); } - /** - * Remove fixture file at provided path. - * - * @param string $path - * File path. - * @param string $name - * File name. - */ - protected function gitRemoveFixtureFile(string $path, string $name): void - { - $path = $path.DIRECTORY_SEPARATOR.$name; - $this->fs->remove($path); + return $path; + } + + /** + * Remove fixture file at provided path. + * + * @param string $path + * File path. + * @param string $name + * File name. + */ + protected function gitRemoveFixtureFile(string $path, string $name): void { + $path = $path . DIRECTORY_SEPARATOR . $name; + $this->fs->remove($path); + } + + /** + * Create fixture tag with specified name and optional annotation. + * + * Annotated tags and lightweight tags have a different object + * representation in git, therefore may need to be created explicitly for + * some tests. + * + * @param string $path + * Optional path to the repository directory. + * @param string $name + * Tag name. + * @param bool $annotate + * Optional flag to add random annotation to the tag. Defaults to FALSE. + */ + protected function gitAddTag(string $path, string $name, bool $annotate = FALSE): void { + if ($annotate) { + $this->runGitCommand(sprintf('tag -a %s -m "%s"', $name, 'Annotation for tag ' . $name), $path); } + else { + $this->runGitCommand(sprintf('tag %s', $name), $path); + } + } - /** - * Create fixture tag with specified name and optional annotation. - * - * Annotated tags and lightweight tags have a different object - * representation in git, therefore may need to be created explicitly for - * some tests. - * - * @param string $path - * Optional path to the repository directory. - * @param string $name - * Tag name. - * @param bool $annotate - * Optional flag to add random annotation to the tag. Defaults to FALSE. - */ - protected function gitAddTag(string $path, string $name, bool $annotate = false): void - { - if ($annotate) { - $this->runGitCommand(sprintf('tag -a %s -m "%s"', $name, 'Annotation for tag '.$name), $path); - } else { - $this->runGitCommand(sprintf('tag %s', $name), $path); - } + /** + * Assert that files exist in repository in specified branch. + * + * @param string $path + * Repository location. + * @param array|string $files + * File or array of files. + * @param string|null $branch + * Optional branch. If set, will be checked out before assertion. + * + * @todo Update arguments order and add assertion message. + */ + protected function gitAssertFilesExist(string $path, $files, string $branch = NULL): void { + $files = is_array($files) ? $files : [$files]; + if ($branch) { + $this->gitCheckout($path, $branch); + } + foreach ($files as $file) { + $this->assertFileExists($path . DIRECTORY_SEPARATOR . $file); } + } - /** - * Assert that files exist in repository in specified branch. - * - * @param string $path - * Repository location. - * @param array|string $files - * File or array of files. - * @param string|null $branch - * Optional branch. If set, will be checked out before assertion. - * - * @todo: Update arguments order and add assertion message. - */ - protected function gitAssertFilesExist(string $path, $files, string $branch = null): void - { - $files = is_array($files) ? $files : [$files]; - if ($branch) { - $this->gitCheckout($path, $branch); - } - foreach ($files as $file) { - $this->assertFileExists($path.DIRECTORY_SEPARATOR.$file); - } + /** + * Assert that files do not exist in repository in specified branch. + * + * @param string $path + * Repository location. + * @param array|string $files + * File or array of files. + * @param string|null $branch + * Optional branch. If set, will be checked out before assertion. + */ + protected function gitAssertFilesNotExist(string $path, $files, string $branch = NULL): void { + $files = is_array($files) ? $files : [$files]; + if ($branch) { + $this->gitCheckout($path, $branch); + } + foreach ($files as $file) { + $this->assertFileDoesNotExist($path . DIRECTORY_SEPARATOR . $file); } + } - /** - * Assert that files do not exist in repository in specified branch. - * - * @param string $path - * Repository location. - * @param array|string $files - * File or array of files. - * @param string|null $branch - * Optional branch. If set, will be checked out before assertion. - */ - protected function gitAssertFilesNotExist(string $path, $files, string $branch = null): void - { - $files = is_array($files) ? $files : [$files]; - if ($branch) { - $this->gitCheckout($path, $branch); - } - foreach ($files as $file) { - $this->assertFileDoesNotExist($path.DIRECTORY_SEPARATOR.$file); - } + /** + * Assert git files are present and were committed. + * + * @param string $path + * Path to repo. + * @param array|string $expectedFiles + * Array of files or a single file. + * @param string $branch + * Optional branch name. + */ + protected function gitAssertFilesCommitted(string $path, $expectedFiles, string $branch = ''): void { + if ($branch !== '' && $branch !== '0') { + $this->gitCheckout($path, $branch); } + $expectedFiles = is_array($expectedFiles) ? $expectedFiles : [$expectedFiles]; + $committedFiles = $this->gitGetCommittedFiles($path); + $this->assertArraySimilar($expectedFiles, $committedFiles); + } - /** - * Assert git files are present and were committed. - * - * @param string $path - * Path to repo. - * @param array|string $expectedFiles - * Array of files or a single file. - * @param string $branch - * Optional branch name. - */ - protected function gitAssertFilesCommitted(string $path, $expectedFiles, string $branch = ''): void - { - if ($branch !== '' && $branch !== '0') { - $this->gitCheckout($path, $branch); - } - $expectedFiles = is_array($expectedFiles) ? $expectedFiles : [$expectedFiles]; - $committedFiles = $this->gitGetCommittedFiles($path); - $this->assertArraySimilar($expectedFiles, $committedFiles); + /** + * Assert git files were not committed. + * + * @param string $path + * Path to repo. + * @param array|string $expectedFiles + * Array of files or a single file. + * @param string $branch + * Optional branch name. + */ + protected function gitAssertNoFilesCommitted(string $path, $expectedFiles, string $branch = ''): void { + if ($branch !== '' && $branch !== '0') { + $this->gitCheckout($path, $branch); } + $expectedFiles = is_array($expectedFiles) ? $expectedFiles : [$expectedFiles]; + $committedFiles = $this->gitGetCommittedFiles($path); + $intersectedFiles = array_intersect($committedFiles, $expectedFiles); + $this->assertArraySimilar([], $intersectedFiles); + } - /** - * Assert git files were not committed. - * - * @param string $path - * Path to repo. - * @param array|string $expectedFiles - * Array of files or a single file. - * @param string $branch - * Optional branch name. - */ - protected function gitAssertNoFilesCommitted(string $path, $expectedFiles, string $branch = ''): void - { - if ($branch !== '' && $branch !== '0') { - $this->gitCheckout($path, $branch); - } - $expectedFiles = is_array($expectedFiles) ? $expectedFiles : [$expectedFiles]; - $committedFiles = $this->gitGetCommittedFiles($path); - $intersectedFiles = array_intersect($committedFiles, $expectedFiles); - $this->assertArraySimilar([], $intersectedFiles); + /** + * Assert which git commits are present. + * + * @param int $count + * Number of commits. + * @param string $path + * Path to the repo. + * @param string $branch + * Branch name. + * @param array $additionalCommits + * Array of additional commits. + * @param bool $assertFiles + * Assert files or not. + * + * @throws \Exception + */ + protected function assertFixtureCommits(int $count, string $path, string $branch, array $additionalCommits = [], bool $assertFiles = TRUE): void { + $this->gitCheckout($path, $branch); + $this->gitReset($path); + + $expectedCommits = []; + $expectedFiles = []; + for ($i = 1; $i <= $count; $i++) { + $expectedCommits[] = sprintf('Commit number %s', $i); + $expectedFiles[] = sprintf('f%s', $i); } + $expectedCommits = array_merge($expectedCommits, $additionalCommits); + + $commits = $this->gitGetAllCommits($path); + $this->assertEquals($expectedCommits, $commits, 'All fixture commits are present'); - /** - * Assert which git commits are present. - * - * @param int $count - * Number of commits. - * @param string $path - * Path to the repo. - * @param string $branch - * Branch name. - * @param array $additionalCommits - * Array of additional commits. - * @param bool $assertFiles - * Assert files or not. - * - * @throws \Exception - */ - protected function assertFixtureCommits(int $count, string $path, string $branch, array $additionalCommits = [], bool $assertFiles = true): void - { - $this->gitCheckout($path, $branch); - $this->gitReset($path); - - $expectedCommits = []; - $expectedFiles = []; - for ($i = 1; $i <= $count; $i++) { - $expectedCommits[] = sprintf('Commit number %s', $i); - $expectedFiles[] = sprintf('f%s', $i); - } - $expectedCommits = array_merge($expectedCommits, $additionalCommits); - - $commits = $this->gitGetAllCommits($path); - $this->assertEquals($expectedCommits, $commits, 'All fixture commits are present'); - - if ($assertFiles) { - $this->gitAssertFilesExist($this->dst, $expectedFiles, $branch); - } + if ($assertFiles) { + $this->gitAssertFilesExist($this->dst, $expectedFiles, $branch); } + } - /** - * Run Git command. - * - * @param string $args - * CLI arguments. - * @param string|null $path - * Optional path to the repository. If not provided, fixture repository is - * used. - * - * @return array - * Array of output lines. - */ - protected function runGitCommand(string $args, string $path = null): array - { - $path = $path ? $path : $this->src; - - $command = 'git --no-pager'; - if (!empty($path)) { - $command .= ' --git-dir='.$path.'/.git'; - $command .= ' --work-tree='.$path; - } - - return $this->runCliCommand($command.' '.trim($args)); + /** + * Run Git command. + * + * @param string $args + * CLI arguments. + * @param string|null $path + * Optional path to the repository. If not provided, fixture repository is + * used. + * + * @return array + * Array of output lines. + */ + protected function runGitCommand(string $args, string $path = NULL): array { + $path = $path ? $path : $this->src; + + $command = 'git --no-pager'; + if (!empty($path)) { + $command .= ' --git-dir=' . $path . '/.git'; + $command .= ' --work-tree=' . $path; } + return $this->runCliCommand($command . ' ' . trim($args)); + } + /** * Run command. * @@ -500,61 +482,60 @@ protected function runGitCommand(string $args, string $path = null): array * @return array * Array of output lines. */ - public function runGitArtifactCommand(string $argsAndOptions, bool $expectFail = false, string $gitArtifactBin = './git-artifact'): array - { - if (!file_exists($gitArtifactBin)) { - throw new \RuntimeException(sprintf('git-artifact binary is not available at path "%s"', $gitArtifactBin)); - } - - try { - $output = $this->runCliCommand($gitArtifactBin.' '.$argsAndOptions); - if ($expectFail) { - throw new AssertionFailedError('Command exited successfully but should not'); - } - } catch (ErrorException $errorException) { - if (!$expectFail) { - throw $errorException; - } - $output = explode(PHP_EOL, ($errorException->getPrevious() instanceof \Throwable) ? $errorException->getPrevious()->getMessage() : ''); - } - - return $output; + public function runGitArtifactCommand(string $argsAndOptions, bool $expectFail = FALSE, string $gitArtifactBin = './git-artifact'): array { + if (!file_exists($gitArtifactBin)) { + throw new \RuntimeException(sprintf('git-artifact binary is not available at path "%s"', $gitArtifactBin)); } - /** - * Run CLI command. - * - * @param string $command - * Command string to run. - * - * @return array - * Array of output lines. - * - * @throws \DrevOps\GitArtifact\Tests\Exception\ErrorException - * If commands exists with non-zero status. - */ - protected function runCliCommand(string $command): array - { - if ($this->printDebug) { - print '++ '.$command.PHP_EOL; - } - exec($command.' 2>&1', $output, $code); - - if ($code !== 0) { - throw new ErrorException(sprintf('Command "%s" exited with non-zero status', $command), $code, '', -1, new ErrorException(implode(PHP_EOL, $output), $code, '', -1)); - } - if ($this->printDebug) { - print '++++ '.implode(PHP_EOL, $output).PHP_EOL; - } - - return $output; + try { + $output = $this->runCliCommand($gitArtifactBin . ' ' . $argsAndOptions); + if ($expectFail) { + throw new AssertionFailedError('Command exited successfully but should not'); + } + } + catch (ErrorException $errorException) { + if (!$expectFail) { + throw $errorException; + } + $output = explode(PHP_EOL, ($errorException->getPrevious() instanceof \Throwable) ? $errorException->getPrevious()->getMessage() : ''); + } + + return $output; + } + + /** + * Run CLI command. + * + * @param string $command + * Command string to run. + * + * @return array + * Array of output lines. + * + * @throws \DrevOps\GitArtifact\Tests\Exception\ErrorException + * If commands exists with non-zero status. + */ + protected function runCliCommand(string $command): array { + if ($this->printDebug) { + print '++ ' . $command . PHP_EOL; + } + exec($command . ' 2>&1', $output, $code); + + if ($code !== 0) { + throw new ErrorException(sprintf('Command "%s" exited with non-zero status', $command), $code, '', -1, new ErrorException(implode(PHP_EOL, $output), $code, '', -1)); + } + if ($this->printDebug) { + print '++++ ' . implode(PHP_EOL, $output) . PHP_EOL; } + return $output; + } + /** * Asserts that two associative arrays are similar. * * Both arrays must have the same indexes with identical values - * without respect to key ordering + * without respect to key ordering. * * @param array $expected * Expected assert. @@ -563,16 +544,17 @@ protected function runCliCommand(string $command): array * * @phpstan-ignore-next-line */ - protected function assertArraySimilar(array $expected, array $array): void - { - $this->assertEquals([], array_diff($array, $expected)); - $this->assertEquals([], array_diff_key($array, $expected)); - foreach ($expected as $key => $value) { - if (is_array($value)) { - $this->assertArraySimilar($value, $array[$key]); - } else { - $this->assertContains($value, $array); - } - } + protected function assertArraySimilar(array $expected, array $array): void { + $this->assertEquals([], array_diff($array, $expected)); + $this->assertEquals([], array_diff_key($array, $expected)); + foreach ($expected as $key => $value) { + if (is_array($value)) { + $this->assertArraySimilar($value, $array[$key]); + } + else { + $this->assertContains($value, $array); + } } + } + } diff --git a/tests/phpunit/Traits/MockTrait.php b/tests/phpunit/Traits/MockTrait.php index 03c241b..b5782e6 100644 --- a/tests/phpunit/Traits/MockTrait.php +++ b/tests/phpunit/Traits/MockTrait.php @@ -7,8 +7,7 @@ * * This trait provides a method to prepare class mock. */ -trait MockTrait -{ +trait MockTrait { /** * Helper to prepare class or trait mock. @@ -28,47 +27,52 @@ trait MockTrait * * @throws \ReflectionException */ - protected function prepareMock(string $class, array $methodsMap = [], array $args = []) - { - $methods = array_keys($methodsMap); + protected function prepareMock(string $class, array $methodsMap = [], array $args = []) { + $methods = array_keys($methodsMap); - $reflectionClass = new \ReflectionClass($class); + $reflectionClass = new \ReflectionClass($class); - if ($reflectionClass->isAbstract()) { - $mock = $this->getMockForAbstractClass($class, $args, '', !empty($args), true, true, $methods); - } elseif ($reflectionClass->isTrait()) { - $mock = $this->getMockForTrait($class, [], '', true, true, true, array_keys($methodsMap)); - } else { - $mockBuilder = $this->getMockBuilder($class); - if (!empty($args)) { - $mockBuilder = $mockBuilder->enableOriginalConstructor() - ->setConstructorArgs($args); - } else { - $mockBuilder = $mockBuilder->disableOriginalConstructor(); - } - /* @todo setMethods method is not found on MockBuilder */ - /* @phpstan-ignore-next-line */ - $mock = $mockBuilder->setMethods($methods) - ->getMock(); - } - - foreach ($methodsMap as $method => $value) { - // Handle callback values differently. - if (is_object($value) && str_contains($value::class, 'Callback')) { - $mock->expects($this->any()) - ->method($method) - ->will($value); - } elseif (is_object($value) && str_contains($value::class, 'Closure')) { - $mock->expects($this->any()) - ->method($method) - ->will($this->returnCallback($value)); - } else { - $mock->expects($this->any()) - ->method($method) - ->willReturn($value); - } - } + if ($reflectionClass->isAbstract()) { + $mock = $this->getMockForAbstractClass($class, $args, '', !empty($args), TRUE, TRUE, $methods); + } + elseif ($reflectionClass->isTrait()) { + $mock = $this->getMockForTrait($class, [], '', TRUE, TRUE, TRUE, array_keys($methodsMap)); + } + else { + $mockBuilder = $this->getMockBuilder($class); + if (!empty($args)) { + $mockBuilder = $mockBuilder->enableOriginalConstructor() + ->setConstructorArgs($args); + } + else { + $mockBuilder = $mockBuilder->disableOriginalConstructor(); + } + /* @todo setMethods method is not found on MockBuilder */ + /* @phpstan-ignore-next-line */ + $mock = $mockBuilder->setMethods($methods) + ->getMock(); + } - return $mock; + foreach ($methodsMap as $method => $value) { + // Handle callback values differently. + if (is_object($value) && str_contains($value::class, 'Callback')) { + $mock->expects($this->any()) + ->method($method) + ->will($value); + } + elseif (is_object($value) && str_contains($value::class, 'Closure')) { + $mock->expects($this->any()) + ->method($method) + ->will($this->returnCallback($value)); + } + else { + $mock->expects($this->any()) + ->method($method) + ->willReturn($value); + } } + + return $mock; + } + } diff --git a/tests/phpunit/Traits/ReflectionTrait.php b/tests/phpunit/Traits/ReflectionTrait.php index 51904a2..adaf451 100644 --- a/tests/phpunit/Traits/ReflectionTrait.php +++ b/tests/phpunit/Traits/ReflectionTrait.php @@ -7,8 +7,7 @@ * * Provides methods to work with class reflection. */ -trait ReflectionTrait -{ +trait ReflectionTrait { /** * Call protected methods on the class. @@ -24,43 +23,42 @@ trait ReflectionTrait * @return mixed * Method result. */ - protected static function callProtectedMethod(object|string $object, string $name, array $args = []) - { - $objectOrClass = is_object($object) ? $object::class : $object; + protected static function callProtectedMethod(object|string $object, string $name, array $args = []) { + $objectOrClass = is_object($object) ? $object::class : $object; - if (!class_exists($objectOrClass)) { - throw new \InvalidArgumentException(sprintf('Class %s does not exist', $objectOrClass)); - } + if (!class_exists($objectOrClass)) { + throw new \InvalidArgumentException(sprintf('Class %s does not exist', $objectOrClass)); + } - $class = new \ReflectionClass($objectOrClass); + $class = new \ReflectionClass($objectOrClass); - if (!$class->hasMethod($name)) { - throw new \InvalidArgumentException(sprintf('Method %s does not exist', $name)); - } + if (!$class->hasMethod($name)) { + throw new \InvalidArgumentException(sprintf('Method %s does not exist', $name)); + } - $method = $class->getMethod($name); + $method = $class->getMethod($name); - $originalAccessibility = $method->isPublic(); + $originalAccessibility = $method->isPublic(); - // Set method accessibility to true, so it can be invoked. - $method->setAccessible(true); + // Set method accessibility to true, so it can be invoked. + $method->setAccessible(TRUE); - // If the method is static, we won't pass an object instance to invokeArgs() - // Otherwise, we ensure to pass the object instance. - $invokeObject = $method->isStatic() ? null : (is_object($object) ? $object : null); + // If the method is static, we won't pass an object instance to invokeArgs() + // Otherwise, we ensure to pass the object instance. + $invokeObject = $method->isStatic() ? NULL : (is_object($object) ? $object : NULL); - // Ensure we have an object for non-static methods. - if (!$method->isStatic() && $invokeObject === null) { - throw new \InvalidArgumentException("An object instance is required for non-static methods"); - } + // Ensure we have an object for non-static methods. + if (!$method->isStatic() && $invokeObject === NULL) { + throw new \InvalidArgumentException("An object instance is required for non-static methods"); + } - $result = $method->invokeArgs($invokeObject, $args); + $result = $method->invokeArgs($invokeObject, $args); - // Reset the method's accessibility to its original state. - $method->setAccessible($originalAccessibility); + // Reset the method's accessibility to its original state. + $method->setAccessible($originalAccessibility); - return $result; - } + return $result; + } /** * Set protected property value. @@ -72,14 +70,13 @@ protected static function callProtectedMethod(object|string $object, string $nam * @param mixed $value * Value to set to the property. */ - protected static function setProtectedValue($object, $property, mixed $value): void - { - $class = new \ReflectionClass($object::class); - $property = $class->getProperty($property); - $property->setAccessible(true); + protected static function setProtectedValue($object, $property, mixed $value): void { + $class = new \ReflectionClass($object::class); + $property = $class->getProperty($property); + $property->setAccessible(TRUE); - $property->setValue($object, $value); - } + $property->setValue($object, $value); + } /** * Get protected value from the object. @@ -92,12 +89,12 @@ protected static function setProtectedValue($object, $property, mixed $value): v * @return mixed * Protected property value. */ - protected static function getProtectedValue($object, $property) - { - $class = new \ReflectionClass($object::class); - $property = $class->getProperty($property); - $property->setAccessible(true); + protected static function getProtectedValue($object, $property) { + $class = new \ReflectionClass($object::class); + $property = $class->getProperty($property); + $property->setAccessible(TRUE); + + return $property->getValue($class); + } - return $property->getValue($class); - } } diff --git a/tests/phpunit/Unit/AbstractUnitTestCase.php b/tests/phpunit/Unit/AbstractUnitTestCase.php index 6412f26..04ec16a 100644 --- a/tests/phpunit/Unit/AbstractUnitTestCase.php +++ b/tests/phpunit/Unit/AbstractUnitTestCase.php @@ -11,27 +11,26 @@ /** * Class AbstractUnitTestCase. */ -abstract class AbstractUnitTestCase extends AbstractTestCase -{ - - /** - * Mock of the class. - * - * @var \PHPUnit\Framework\MockObject\MockObject - */ - protected $mock; - - protected function setUp(): void - { - parent::setUp(); - - $mockBuilder = $this->getMockBuilder(Artifact::class); - $fileSystem = new Filesystem(); - $gitWrapper = new GitWrapper(); - $output = new ConsoleOutput(); - - $mockBuilder->setConstructorArgs([$gitWrapper, $fileSystem, $output]); - $this->mock = $mockBuilder->getMock(); - $this->callProtectedMethod($this->mock, 'fsSetRootDir', [$this->fixtureDir]); - } +abstract class AbstractUnitTestCase extends AbstractTestCase { + + /** + * Mock of the class. + * + * @var \PHPUnit\Framework\MockObject\MockObject + */ + protected $mock; + + protected function setUp(): void { + parent::setUp(); + + $mockBuilder = $this->getMockBuilder(Artifact::class); + $fileSystem = new Filesystem(); + $gitWrapper = new GitWrapper(); + $output = new ConsoleOutput(); + + $mockBuilder->setConstructorArgs([$gitWrapper, $fileSystem, $output]); + $this->mock = $mockBuilder->getMock(); + $this->callProtectedMethod($this->mock, 'fsSetRootDir', [$this->fixtureDir]); + } + } diff --git a/tests/phpunit/Unit/ExcludeTest.php b/tests/phpunit/Unit/ExcludeTest.php index cac2aed..fc026df 100644 --- a/tests/phpunit/Unit/ExcludeTest.php +++ b/tests/phpunit/Unit/ExcludeTest.php @@ -1,6 +1,6 @@ createFixtureExcludeFile(); - - $actual = $this->callProtectedMethod($this->mock, 'localExcludeExists', [$this->fixtureDir]); - - $this->assertTrue($actual); - } - - - /** - * @param array $lines - * Lines. - * @param bool $strict - * Strict. - * @param bool $expected - * Expected. - * - * - * @dataProvider dataProviderExcludeEmpty - * - * @throws \ReflectionException - */ - public function testExcludeEmpty(array $lines, bool $strict, bool $expected): void - { - $this->createFixtureExcludeFile(implode(PHP_EOL, $lines)); - - $actual = $this->callProtectedMethod($this->mock, 'localExcludeEmpty', [$this->fixtureDir, $strict]); - - $this->assertEquals($expected, $actual); - } - - /** - * @return array - * Data provider. - * - * @SuppressWarnings(PHPMD.ExcessiveMethodLength) - */ - public static function dataProviderExcludeEmpty(): array - { - return [ - // Empty file. - [ - [], true, true, - ], - [ - [], false, true, - ], - - // Spaces single line. - [ - [ - ' ', - ], true, false, - ], - - [ - [ - ' ', - ], false, true, - ], - - // Spaces. - [ - [ - ' ', - ' ', - ], true, false, - ], - - [ - [ - ' ', - ' ', - ], false, true, - ], - - // Spaces, comments. - [ - [ - ' ', - '#comment ', - ' ', - ], true, false, - ], - - [ - [ - ' ', - '#comment ', - ' ', - ], false, true, - ], - - // Spaces, padded comments. - [ - [ - ' ', - ' #comment ', - ' ', - ], true, false, - ], - - [ - [ - ' ', - ' #comment ', - ' ', - ], false, true, - ], - - // Spaces, comments and valid content. - [ - [ - ' ', - '#comment ', - 'valid', - ' ', - ], true, false, - ], - - [ - [ - ' ', - '#comment ', - 'valid', - ' ', - ], false, false, - ], - - // Spaces, inline comments and valid content. - [ - [ - ' ', - '#comment ', - 'valid', - 'valid # other comment', - ' ', - ], true, false, - ], - - [ - [ - ' ', - '#comment ', - 'valid', - 'valid # other comment', - ' ', - ], false, false, - ], - - ]; - } - - /** - * Helper to create an exclude file. - * - * @param string $contents - * Optional file contents. - * - * @return string - * Created file name. - */ - protected function createFixtureExcludeFile(string $contents = ''): string - { - return $this->gitCreateFixtureFile($this->fixtureDir.DIRECTORY_SEPARATOR.'.git'.DIRECTORY_SEPARATOR.'info', 'exclude', $contents); - } +class ExcludeTest extends AbstractUnitTestCase { + + /** + * @throws \ReflectionException + */ + public function testExcludeExists(): void { + $this->createFixtureExcludeFile(); + + $actual = $this->callProtectedMethod($this->mock, 'localExcludeExists', [$this->fixtureDir]); + + $this->assertTrue($actual); + } + + /** + * @param array $lines + * Lines. + * @param bool $strict + * Strict. + * @param bool $expected + * Expected. + * + * + * @dataProvider dataProviderExcludeEmpty + * + * @throws \ReflectionException + */ + public function testExcludeEmpty(array $lines, bool $strict, bool $expected): void { + $this->createFixtureExcludeFile(implode(PHP_EOL, $lines)); + + $actual = $this->callProtectedMethod($this->mock, 'localExcludeEmpty', [$this->fixtureDir, $strict]); + + $this->assertEquals($expected, $actual); + } + + /** + * @return array + * Data provider. + * + * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + */ + public static function dataProviderExcludeEmpty(): array { + return [ + // Empty file. + [ + [], TRUE, TRUE, + ], + [ + [], FALSE, TRUE, + ], + + // Spaces single line. + [ + [ + ' ', + ], TRUE, FALSE, + ], + + [ + [ + ' ', + ], FALSE, TRUE, + ], + + // Spaces. + [ + [ + ' ', + ' ', + ], TRUE, FALSE, + ], + + [ + [ + ' ', + ' ', + ], FALSE, TRUE, + ], + + // Spaces, comments. + [ + [ + ' ', + '#comment ', + ' ', + ], TRUE, FALSE, + ], + + [ + [ + ' ', + '#comment ', + ' ', + ], FALSE, TRUE, + ], + + // Spaces, padded comments. + [ + [ + ' ', + ' #comment ', + ' ', + ], TRUE, FALSE, + ], + + [ + [ + ' ', + ' #comment ', + ' ', + ], FALSE, TRUE, + ], + + // Spaces, comments and valid content. + [ + [ + ' ', + '#comment ', + 'valid', + ' ', + ], TRUE, FALSE, + ], + + [ + [ + ' ', + '#comment ', + 'valid', + ' ', + ], FALSE, FALSE, + ], + + // Spaces, inline comments and valid content. + [ + [ + ' ', + '#comment ', + 'valid', + 'valid # other comment', + ' ', + ], TRUE, FALSE, + ], + + [ + [ + ' ', + '#comment ', + 'valid', + 'valid # other comment', + ' ', + ], FALSE, FALSE, + ], + + ]; + } + + /** + * Helper to create an exclude file. + * + * @param string $contents + * Optional file contents. + * + * @return string + * Created file name. + */ + protected function createFixtureExcludeFile(string $contents = ''): string { + return $this->gitCreateFixtureFile($this->fixtureDir . DIRECTORY_SEPARATOR . '.git' . DIRECTORY_SEPARATOR . 'info', 'exclude', $contents); + } + }