diff --git a/.editorconfig b/.editorconfig
index c27ebe5..4a2cc0e 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -1,6 +1,3 @@
-# Drupal editor configuration normalization
-# @see http://editorconfig.org/
-
# This is the top-most .editorconfig file; do not search in parent directories.
root = true
@@ -13,10 +10,17 @@ charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
-[composer.{json,lock}]
+[*.php]
+indent_size = 2
+
+
+[*.{json,lock}]
indent_size = 4
-[*.yml]
+[*.{yml,yaml}]
+indent_size = 2
+
+[*.{sh,bash,bats}]
indent_size = 2
[*.xml]
diff --git a/.gitattributes b/.gitattributes
index a77a194..7ec53ed 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -1,9 +1,12 @@
# Ignore files for distribution archives.
-.gitattributes export-ignore
-.gitignore export-ignore
-.editorconfig export-ignore
-tests export-ignore
-.circle export-ignore
-phpcs.xml export-ignore
-phpunit.xml export-ignore
+/.circleci export-ignore
+/.editorconfig export-ignore
+/.gitattributes export-ignore
+/.github export-ignore
+/.gitignore export-ignore
+/tests export-ignore
+/phpcs.xml export-ignore
+/phpmd.xml export-ignore
+/phpstan.neon export-ignore
+/phpunit.xml export-ignore
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
index 7c34e35..95cbae2 100644
--- a/.github/PULL_REQUEST_TEMPLATE.md
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -1,15 +1,12 @@
## Checklist before requesting a review
-- [ ] I have formatted the subject to include ticket number
- as `[#123] Verb in past tense with dot at the end.`
+- [ ] I have formatted the subject to include ticket number as `[#123] Verb in past tense with dot at the end.`
- [ ] I have added a link to the issue tracker
-- [ ] I have provided information in `Changed` section about WHY something was
- done if this was not a normal implementation
+- [ ] I have provided information in `Changed` section about WHY something was done if this was not a normal implementation
- [ ] I have performed a self-review of my code
- [ ] I have commented my code, particularly in hard-to-understand areas
- [ ] I have added tests that prove my fix is effective or that my feature works
-- [ ] I have run new and existing relevant tests locally with my changes, and
- they passed
+- [ ] I have run new and existing relevant tests locally with my changes, and they passed
- [ ] I have provided screenshots, where applicable
## Changed
diff --git a/.github/release-drafter.yml b/.github/release-drafter.yml
index 699ff47..8b07383 100644
--- a/.github/release-drafter.yml
+++ b/.github/release-drafter.yml
@@ -1,12 +1,14 @@
-name-template: '$NEXT_MINOR_VERSION'
-tag-template: '$NEXT_MINOR_VERSION'
+name-template: '$RESOLVED_VERSION'
+tag-template: '$RESOLVED_VERSION'
change-template: '- $TITLE @$AUTHOR (#$NUMBER)'
change-title-escapes: '\<*_&' # You can add # and @ to disable mentions, and add ` to disable code blocks.
+version-resolver:
+ default: minor
template: |
## What's new since $PREVIOUS_TAG
$CHANGES
- **Full Changelog**: https://github.com/$OWNER/$REPOSITORY/compare/$PREVIOUS_TAG...$NEXT_MINOR_VERSION
+ **Full Changelog**: https://github.com/$OWNER/$REPOSITORY/compare/$PREVIOUS_TAG...$RESOLVED_VERSION
$CONTRIBUTORS
diff --git a/.github/workflows/auto-assign-pr-author.yml b/.github/workflows/assign-author.yml
similarity index 71%
rename from .github/workflows/auto-assign-pr-author.yml
rename to .github/workflows/assign-author.yml
index df73ac4..466655f 100644
--- a/.github/workflows/auto-assign-pr-author.yml
+++ b/.github/workflows/assign-author.yml
@@ -12,5 +12,7 @@ permissions:
jobs:
assign-author:
runs-on: ubuntu-latest
+
steps:
- - uses: toshimaru/auto-author-assign@v2.1.0
+ - name: Assign author
+ uses: toshimaru/auto-author-assign@v2.1.0
diff --git a/.github/workflows/release.yml b/.github/workflows/draft-release-notes.yml
similarity index 72%
rename from .github/workflows/release.yml
rename to .github/workflows/draft-release-notes.yml
index c6587e7..c9e0353 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/draft-release-notes.yml
@@ -1,4 +1,4 @@
-name: Release
+name: Draft release notes
on:
push:
@@ -15,8 +15,11 @@ jobs:
permissions:
contents: write
pull-requests: write
+
runs-on: ubuntu-latest
+
steps:
- - uses: release-drafter/release-drafter@v6
+ - name: Draft release notes
+ uses: release-drafter/release-drafter@v6
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.gitignore b/.gitignore
index 5690759..522ecdc 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,8 +1,9 @@
# To ignore OS temporary files use global .gitignore
# https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files#configuring-ignored-files-for-all-repositories-on-your-computer
-vendor
-artifact
-composer.lock
-git_*
-.phpunit.cache
+/.build
+/.coverage-html
+/.phpunit.cache
+/cobertura.xml
+/composer.lock
+/vendor
diff --git a/README.md b/README.md
index 7e24b23..3596a15 100644
--- a/README.md
+++ b/README.md
@@ -235,7 +235,7 @@ would reult in the branch name `release/1.2.0-secondtag`.
```bash
composer lint
-composer lint:fix
+composer lint-fix
```
### Run tests
diff --git a/composer.json b/composer.json
index dd9730c..3ffddba 100644
--- a/composer.json
+++ b/composer.json
@@ -24,17 +24,9 @@
"phpmd/phpmd": "^2.15",
"phpstan/phpstan": "^1.10",
"phpunit/phpunit": "^10.5",
+ "rector/rector": "^1.0.0",
"squizlabs/php_codesniffer": "^3.6"
},
- "scripts": {
- "lint": [
- "phpcs",
- "phpmd --exclude vendor . text phpmd.xml",
- "phpstan"
- ],
- "lint:fix": "phpcbf",
- "test": "if [ \"${XDEBUG_MODE}\" = 'coverage' ]; then phpunit; else phpunit --no-coverage; fi"
- },
"autoload": {
"psr-4": {
"DrevOps\\Robo\\": "src"
@@ -49,5 +41,23 @@
"allow-plugins": {
"dealerdirect/phpcodesniffer-composer-installer": true
}
+ },
+ "scripts": {
+ "lint": [
+ "phpcs",
+ "phpmd --exclude vendor,vendor-bin,node_modules . text phpmd.xml",
+ "phpstan",
+ "rector --clear-cache --dry-run"
+ ],
+ "lint-fix": [
+ "rector --clear-cache",
+ "phpcbf"
+ ],
+ "test": "if [ \"${XDEBUG_MODE}\" = 'coverage' ]; then phpunit; else phpunit --no-coverage; fi",
+ "build": [
+ "@composer bin box require --dev humbug/box",
+ "box validate",
+ "box compile"
+ ]
}
}
diff --git a/phpstan.neon b/phpstan.neon
index 634fb22..351833b 100644
--- a/phpstan.neon
+++ b/phpstan.neon
@@ -16,3 +16,10 @@ parameters:
- node_modules/*
ignoreErrors:
+ -
+ # Since tests and data providers do not have to have parameter docblocks,
+ # it is not possible to specify the type of the parameter, so we ignore
+ # this error.
+ message: '#.*no value type specified in iterable type array.#'
+ path: tests/*
+ reportUnmatched: false
diff --git a/phpunit.xml b/phpunit.xml
index ac2fb05..d8fcc3f 100644
--- a/phpunit.xml
+++ b/phpunit.xml
@@ -14,14 +14,14 @@
displayDetailsOnTestsThatTriggerNotices="true"
>
-
- ./tests/
+
+ tests
diff --git a/rector.php b/rector.php
new file mode 100644
index 0000000..a158b55
--- /dev/null
+++ b/rector.php
@@ -0,0 +1,65 @@
+paths([
+ __DIR__ . '/**',
+ ]);
+
+ $rectorConfig->sets([
+ SetList::PHP_80,
+ SetList::PHP_81,
+ SetList::CODE_QUALITY,
+ SetList::CODING_STYLE,
+ SetList::DEAD_CODE,
+ SetList::INSTANCEOF,
+ SetList::TYPE_DECLARATION,
+ ]);
+
+ $rectorConfig->skip([
+ // Rules added by Rector's rule sets.
+ ArraySpreadInsteadOfArrayMergeRector::class,
+ CountArrayToEmptyArrayComparisonRector::class,
+ DisallowedEmptyRuleFixerRector::class,
+ InlineArrayReturnAssignRector::class,
+ NewlineAfterStatementRector::class,
+ NewlineBeforeNewAssignSetRector::class,
+ PostIncDecToPreIncDecRector::class,
+ RemoveAlwaysTrueIfConditionRector::class,
+ SimplifyEmptyCheckOnEmptyArrayRector::class,
+ // Dependencies.
+ '*/vendor/*',
+ '*/node_modules/*',
+ ]);
+
+ $rectorConfig->fileExtensions([
+ 'php',
+ 'inc',
+ ]);
+
+ $rectorConfig->importNames(TRUE, FALSE);
+ $rectorConfig->importShortClasses(FALSE);
+};
diff --git a/src/ArtefactTrait.php b/src/ArtefactTrait.php
index afe4d09..b4f98e3 100644
--- a/src/ArtefactTrait.php
+++ b/src/ArtefactTrait.php
@@ -38,7 +38,7 @@ trait ArtefactTrait
*
* @var string|null
*/
- protected $originalBranch = null;
+ protected $originalBranch;
/**
* Destination branch with optional tokens.
@@ -277,7 +277,7 @@ protected function doPush(): void
$this->result = $result->wasSuccessful();
} catch (\Exception $exception) {
// Re-throw the message with additional context.
- throw new \Exception(sprintf('Error occurred while pushing branch "%s": %s', $this->dstBranch, $exception->getMessage()));
+ throw new \Exception(sprintf('Error occurred while pushing branch "%s": %s', $this->dstBranch, $exception->getMessage()), $exception->getCode(), $exception);
}
if ($this->result) {
@@ -301,7 +301,7 @@ protected function doPush(): void
*/
protected function resolveOptions(array $options): void
{
- $this->now = !empty($options['now']) ? (int) $options['now'] : time();
+ $this->now = empty($options['now']) ? time() : (int) $options['now'];
$this->debug = !empty($options['debug']);
@@ -310,7 +310,7 @@ protected function resolveOptions(array $options): void
$this->fsSetRootDir($options['root']);
// Default source to the root directory.
- $srcPath = !empty($options['src']) ? $this->fsGetAbsolutePath($options['src']) : $this->fsGetRootDir();
+ $srcPath = empty($options['src']) ? $this->fsGetRootDir() : $this->fsGetAbsolutePath($options['src']);
$this->gitSetSrcRepo($srcPath);
$this->originalBranch = $this->resolveOriginalBranch($this->src);
@@ -329,7 +329,7 @@ protected function resolveOptions(array $options): void
$this->needsPush = !empty($options['push']);
- $this->report = !empty($options['report']) ? $options['report'] : null;
+ $this->report = empty($options['report']) ? null : $options['report'];
$this->setMode($options['mode'], $options);
}
@@ -617,11 +617,11 @@ protected function localExcludeEmpty(string $path, bool $strict = false): bool
$lines = file($filename);
if ($lines) {
$lines = array_map('trim', $lines);
- $lines = array_filter($lines, function ($line) {
+ $lines = array_filter($lines, static function ($line) : bool {
return strlen($line) > 0;
});
- $lines = array_filter($lines, function ($line) {
- return strpos(trim($line), '#') !== 0;
+ $lines = array_filter($lines, static function ($line) : bool {
+ return !str_starts_with(trim($line), '#');
});
}
@@ -856,20 +856,21 @@ protected function sayOkay(string $text): void
{
$color = 'green';
$char = $this->decorationCharacter('V', '✔');
- $format = "%s %s";
+ $format = sprintf('%%s %%s', $color, $color);
$this->writeln(sprintf($format, $char, $text));
}
/**
* Print debug information.
+ *
+ * @param mixed ...$args
+ * The args.
*/
- protected function printDebug(): void
+ protected function printDebug(mixed ...$args): void
{
if (!$this->debug) {
return;
}
-
- $args = func_get_args();
$message = array_shift($args);
/* @phpstan-ignore-next-line */
$this->writeln(vsprintf($message, $args));
diff --git a/src/FilesystemTrait.php b/src/FilesystemTrait.php
index 2a862ae..42f493e 100644
--- a/src/FilesystemTrait.php
+++ b/src/FilesystemTrait.php
@@ -60,7 +60,7 @@ public function __construct()
*/
protected function fsSetRootDir(string $path = null): void
{
- $path = !empty($path) ? $this->fsGetAbsolutePath($path) : $this->fsGetRootDir();
+ $path = empty($path) ? $this->fsGetRootDir() : $this->fsGetAbsolutePath($path);
$this->fsPathsExist($path);
$this->fsRootDir = $path;
}
@@ -219,10 +219,10 @@ protected function fsPathsExist($paths, bool $strict = true): bool
protected function realpath(string $path): string
{
// Whether $path is unix or not.
- $unipath = strlen($path) === 0 || $path[0] !== '/';
- $unc = substr($path, 0, 2) === '\\\\' ? true : false;
+ $unipath = $path === '' || $path[0] !== '/';
+ $unc = str_starts_with($path, '\\\\');
// Attempt to detect if path is relative in which case, add cwd.
- if (strpos($path, ':') === false && $unipath && !$unc) {
+ if (!str_contains($path, ':') && $unipath && !$unc) {
$path = getcwd().DIRECTORY_SEPARATOR.$path;
if ($path[0] === '/') {
$unipath = false;
@@ -231,7 +231,7 @@ protected function realpath(string $path): string
// Resolve path parts (single dot, double dot and double delimiters).
$path = str_replace(['/', '\\'], DIRECTORY_SEPARATOR, $path);
- $parts = array_filter(explode(DIRECTORY_SEPARATOR, $path), function ($part) {
+ $parts = array_filter(explode(DIRECTORY_SEPARATOR, $path), static function ($part) : bool {
return strlen($part) > 0;
});
@@ -252,7 +252,7 @@ protected function realpath(string $path): string
$path = readlink($path);
}
// Put initial separator that could have been lost.
- $path = !$unipath ? '/'.$path : $path;
+ $path = $unipath ? $path : '/'.$path;
/* @phpstan-ignore-next-line */
return $unc ? '\\\\'.$path : $path;
diff --git a/src/GitTrait.php b/src/GitTrait.php
index f2979e2..b594729 100644
--- a/src/GitTrait.php
+++ b/src/GitTrait.php
@@ -381,19 +381,12 @@ protected function gitIsRemote(string $location, string $type = 'any'): bool
$isLocal = $this->fsPathsExist($this->fsGetAbsolutePath($location), false);
$isUri = self::gitIsUri($location);
- switch ($type) {
- case 'any':
- return $isLocal || $isUri;
-
- case 'local':
- return $isLocal;
-
- case 'uri':
- return $isUri;
-
- default:
- throw new \InvalidArgumentException(sprintf('Invalid argument "%s" provided', $type));
- }
+ return match ($type) {
+ 'any' => $isLocal || $isUri,
+ 'local' => $isLocal,
+ 'uri' => $isUri,
+ default => throw new \InvalidArgumentException(sprintf('Invalid argument "%s" provided', $type)),
+ };
}
/**
diff --git a/src/TokenTrait.php b/src/TokenTrait.php
index ca57ef3..88424d7 100644
--- a/src/TokenTrait.php
+++ b/src/TokenTrait.php
@@ -22,7 +22,7 @@ trait TokenTrait
*/
protected function tokenProcess(string $string): ?string
{
- return preg_replace_callback('/(?:\[([^\]]+)\])/', function ($match) {
+ return preg_replace_callback('/(?:\[([^\]]+)\])/', function (array $match) {
if (count($match) > 1) {
$parts = explode(':', $match[1], 2);
$token = isset($parts[0]) ? $parts[0] : null;
diff --git a/tests/AbstractTestCase.php b/tests/AbstractTestCase.php
index 9bf11e1..f14403e 100644
--- a/tests/AbstractTestCase.php
+++ b/tests/AbstractTestCase.php
@@ -4,6 +4,9 @@
namespace DrevOps\Robo\Tests;
+use DrevOps\Robo\Tests\Traits\CommandTrait;
+use DrevOps\Robo\Tests\Traits\MockTrait;
+use DrevOps\Robo\Tests\Traits\ReflectionTrait;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Filesystem\Filesystem;
@@ -21,6 +24,9 @@ abstract class AbstractTestCase extends TestCase
CommandTrait::runRoboCommand as public commandRunRoboCommand;
}
+ use ReflectionTrait;
+ use MockTrait;
+
/**
* File system.
*
@@ -66,136 +72,6 @@ protected function tearDown(): void
}
}
- /**
- * Call protected methods on the class.
- *
- * @param object|class-string $object
- * Object or class name to use for a method call.
- * @param string $method
- * Method name. Method can be static.
- * @param array $args
- * Array of arguments to pass to the method. To pass arguments by
- * reference, pass them by reference as an element of this array.
- *
- * @return mixed
- * Method result.
- *
- * @throws \ReflectionException
- */
- protected static function callProtectedMethod($object, string $method, array $args = [])
- {
- $class = new \ReflectionClass(is_object($object) ? get_class($object) : $object);
- $method = $class->getMethod($method);
- $method->setAccessible(true);
- $object = $method->isStatic() ? null : $object;
-
- /* @phpstan-ignore-next-line */
- return $method->invokeArgs($object, $args);
- }
-
- /**
- * Set protected property value.
- *
- * @param object $object
- * Object to set the value on.
- * @param string $property
- * Property name to set the value. Property should exists in the object.
- * @param mixed $value
- * Value to set to the property.
- *
- * @throws \ReflectionException
- */
- protected static function setProtectedValue($object, string $property, $value): void
- {
- $class = new \ReflectionClass(get_class($object));
- $property = $class->getProperty($property);
- $property->setAccessible(true);
-
- $property->setValue($object, $value);
- }
-
- /**
- * Get protected value from the object.
- *
- * @param object $object
- * Object to set the value on.
- * @param string $property
- * Property name to get the value. Property should exists in the object.
- *
- * @return mixed
- * Protected property value.
- */
- protected static function getProtectedValue($object, $property)
- {
- $class = new \ReflectionClass(get_class($object));
- $property = $class->getProperty($property);
- $property->setAccessible(true);
-
- return $property->getValue($class);
- }
-
- /**
- * Helper to prepare class or trait mock.
- *
- * @param class-string $class
- * Class or trait name to generate the mock.
- * @param array $methodsMap
- * Optional array of methods and values, keyed by method name. Array
- * elements can be return values, callbacks created with
- * $this->returnCallback(), or closures.
- * @param array $args
- * Optional array of constructor arguments. If omitted, a constructor
- * will not be called.
- *
- * @return object
- * Mocked class.
- *
- * @throws \ReflectionException
- */
- protected function prepareMock($class, array $methodsMap = [], array $args = [])
- {
- $methods = array_keys($methodsMap);
-
- $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) && strpos(get_class($value), 'Callback') !== false) {
- $mock->expects($this->any())
- ->method($method)
- ->will($value);
- } elseif (is_object($value) && strpos(get_class($value), 'Closure') !== false) {
- $mock->expects($this->any())
- ->method($method)
- ->will($this->returnCallback($value));
- } else {
- $mock->expects($this->any())
- ->method($method)
- ->willReturn($value);
- }
- }
-
- return $mock;
- }
-
/**
* Check if testing framework was ran with --debug option.
*
diff --git a/tests/Integration/AbstractIntegrationTestCase.php b/tests/Functional/AbstractFunctionalTestCase.php
similarity index 97%
rename from tests/Integration/AbstractIntegrationTestCase.php
rename to tests/Functional/AbstractFunctionalTestCase.php
index 8cc4acb..8c6cd12 100644
--- a/tests/Integration/AbstractIntegrationTestCase.php
+++ b/tests/Functional/AbstractFunctionalTestCase.php
@@ -2,14 +2,14 @@
declare(strict_types = 1);
-namespace DrevOps\Robo\Tests\Integration;
+namespace DrevOps\Robo\Tests\Functional;
use DrevOps\Robo\Tests\AbstractTestCase;
/**
* Class AbstractTestCase
*/
-abstract class AbstractIntegrationTestCase extends AbstractTestCase
+abstract class AbstractFunctionalTestCase extends AbstractTestCase
{
/**
@@ -154,7 +154,7 @@ protected function runBuild(string $args = '', bool $expectFail = false): string
protected function runRoboCommandTimestamped(string $command, bool $expectFail = false): array
{
// Add --now option to all 'artifact' commands.
- if (strpos($command, 'artifact') === 0) {
+ if (str_starts_with($command, 'artifact')) {
$command .= ' --now='.$this->now;
}
diff --git a/tests/Integration/BranchTest.php b/tests/Functional/BranchTest.php
similarity index 97%
rename from tests/Integration/BranchTest.php
rename to tests/Functional/BranchTest.php
index 56399db..b2846fc 100644
--- a/tests/Integration/BranchTest.php
+++ b/tests/Functional/BranchTest.php
@@ -2,7 +2,7 @@
declare(strict_types = 1);
-namespace DrevOps\Robo\Tests\Integration;
+namespace DrevOps\Robo\Tests\Functional;
/**
* Class BranchTest.
@@ -13,13 +13,13 @@
* @covers \DrevOps\Robo\ArtefactTrait
* @covers \DrevOps\Robo\FilesystemTrait
*/
-class BranchTest extends AbstractIntegrationTestCase
+class BranchTest extends AbstractFunctionalTestCase
{
/**
* {@inheritdoc}
*/
- public function setUp(): void
+ protected function setUp(): void
{
$this->mode = 'branch';
parent::setUp();
diff --git a/tests/Integration/ForcePushTest.php b/tests/Functional/ForcePushTest.php
similarity index 99%
rename from tests/Integration/ForcePushTest.php
rename to tests/Functional/ForcePushTest.php
index 1d6fc2d..73bc479 100644
--- a/tests/Integration/ForcePushTest.php
+++ b/tests/Functional/ForcePushTest.php
@@ -2,7 +2,7 @@
declare(strict_types = 1);
-namespace DrevOps\Robo\Tests\Integration;
+namespace DrevOps\Robo\Tests\Functional;
/**
* Class ForcePushTest.
@@ -15,7 +15,7 @@
* @covers \DrevOps\Robo\ArtefactTrait
* @covers \DrevOps\Robo\FilesystemTrait
*/
-class ForcePushTest extends AbstractIntegrationTestCase
+class ForcePushTest extends AbstractFunctionalTestCase
{
protected function setUp(): void
diff --git a/tests/Integration/GeneralTest.php b/tests/Functional/GeneralTest.php
similarity index 97%
rename from tests/Integration/GeneralTest.php
rename to tests/Functional/GeneralTest.php
index 3e1a6ca..720ecd5 100644
--- a/tests/Integration/GeneralTest.php
+++ b/tests/Functional/GeneralTest.php
@@ -2,7 +2,7 @@
declare(strict_types = 1);
-namespace DrevOps\Robo\Tests\Integration;
+namespace DrevOps\Robo\Tests\Functional;
/**
* Class GeneralTest.
@@ -12,7 +12,7 @@
* @covers \DrevOps\Robo\ArtefactTrait
* @covers \DrevOps\Robo\FilesystemTrait
*/
-class GeneralTest extends AbstractIntegrationTestCase
+class GeneralTest extends AbstractFunctionalTestCase
{
public function testPresence(): void
diff --git a/tests/Integration/README.md b/tests/Functional/README.md
similarity index 100%
rename from tests/Integration/README.md
rename to tests/Functional/README.md
diff --git a/tests/Integration/TagTest.php b/tests/Functional/TagTest.php
similarity index 88%
rename from tests/Integration/TagTest.php
rename to tests/Functional/TagTest.php
index b0e4bdd..cc780c3 100644
--- a/tests/Integration/TagTest.php
+++ b/tests/Functional/TagTest.php
@@ -2,7 +2,7 @@
declare(strict_types = 1);
-namespace DrevOps\Robo\Tests\Integration;
+namespace DrevOps\Robo\Tests\Functional;
/**
* Class TagTest.
@@ -13,13 +13,13 @@
* @covers \DrevOps\Robo\ArtefactTrait
* @covers \DrevOps\Robo\FilesystemTrait
*/
-class TagTest extends AbstractIntegrationTestCase
+class TagTest extends AbstractFunctionalTestCase
{
/**
* {@inheritdoc}
*/
- public function setUp(): void
+ protected function setUp(): void
{
$this->mode = 'force-push';
parent::setUp();
diff --git a/tests/Integration/TokenTest.php b/tests/Functional/TokenTest.php
similarity index 88%
rename from tests/Integration/TokenTest.php
rename to tests/Functional/TokenTest.php
index 25e1176..7268426 100644
--- a/tests/Integration/TokenTest.php
+++ b/tests/Functional/TokenTest.php
@@ -2,7 +2,9 @@
declare(strict_types = 1);
-namespace DrevOps\Robo\Tests\Integration;
+namespace DrevOps\Robo\Tests\Functional;
+
+use DrevOps\Robo\TokenTrait;
/**
* Class ForcePushTest.
@@ -11,7 +13,7 @@
*
* @covers \DrevOps\Robo\TokenTrait
*/
-class TokenTest extends AbstractIntegrationTestCase
+class TokenTest extends AbstractFunctionalTestCase
{
/**
@@ -19,9 +21,9 @@ class TokenTest extends AbstractIntegrationTestCase
*/
public function testTokenProcess(string $string, string $name, string $replacement, string $expectedString): void
{
- $mock = $this->prepareMock('DrevOps\Robo\TokenTrait', [
- 'getToken'.ucfirst($name) => function ($prop) use ($replacement) {
- return !empty($prop) ? $replacement.' with property '.$prop : $replacement;
+ $mock = $this->prepareMock(TokenTrait::class, [
+ 'getToken'.ucfirst($name) => static function (?string $prop) use ($replacement) : string {
+ return empty($prop) ? $replacement : $replacement.' with property '.$prop;
},
]);
@@ -106,7 +108,7 @@ public static function dataProviderTokenProcess(): array
*/
public function testHasToken(string $string, bool $hasToken): void
{
- $mock = $this->prepareMock('DrevOps\Robo\TokenTrait');
+ $mock = $this->prepareMock(TokenTrait::class);
$actual = $this->callProtectedMethod($mock, 'hasToken', [$string]);
$this->assertEquals($hasToken, $actual);
diff --git a/tests/CommandTrait.php b/tests/Traits/CommandTrait.php
similarity index 93%
rename from tests/CommandTrait.php
rename to tests/Traits/CommandTrait.php
index 39f99d9..550b12a 100644
--- a/tests/CommandTrait.php
+++ b/tests/Traits/CommandTrait.php
@@ -2,7 +2,7 @@
declare(strict_types = 1);
-namespace DrevOps\Robo\Tests;
+namespace DrevOps\Robo\Tests\Traits;
use DrevOps\Robo\Tests\Exception\ErrorException;
use PHPUnit\Framework\AssertionFailedError;
@@ -153,8 +153,8 @@ protected function gitGetCommitsHashesFromRange(array $range, string $path = nul
{
$commits = $this->gitGetAllCommits($path);
- array_walk($range, function (&$v) {
- $v -= 1;
+ array_walk($range, static function (&$v) : void {
+ --$v;
});
$ret = [];
@@ -249,15 +249,15 @@ protected function gitCheckout(string $path, string $branch): void
{
try {
$this->runGitCommand(sprintf('checkout %s', $branch), $path);
- } catch (ErrorException $error) {
+ } catch (ErrorException $errorException) {
$allowedFails = [
- "error: pathspec '$branch' did not match any file(s) known to git",
+ sprintf("error: pathspec '%s' did not match any file(s) known to git", $branch),
];
- $output = explode(PHP_EOL, ($error->getPrevious() instanceof \Throwable) ? $error->getPrevious()->getMessage() : '');
+ $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 $error;
+ throw $errorException;
}
}
}
@@ -289,7 +289,7 @@ protected function gitReset($path): void
*/
protected function gitCreateFixtureFile(string $path, string $name = '', $content = ''): string
{
- $name = $name ? $name : 'tmp'.rand(1000, 100000);
+ $name = $name !== '' && $name !== '0' ? $name : 'tmp'.rand(1000, 100000);
$path = $path.DIRECTORY_SEPARATOR.$name;
$dir = dirname($path);
if (!empty($dir)) {
@@ -397,7 +397,7 @@ protected function gitAssertFilesNotExist(string $path, $files, string $branch =
*/
protected function gitAssertFilesCommitted(string $path, $expectedFiles, string $branch = ''): void
{
- if ($branch) {
+ if ($branch !== '' && $branch !== '0') {
$this->gitCheckout($path, $branch);
}
$expectedFiles = is_array($expectedFiles) ? $expectedFiles : [$expectedFiles];
@@ -417,7 +417,7 @@ protected function gitAssertFilesCommitted(string $path, $expectedFiles, string
*/
protected function gitAssertNoFilesCommitted(string $path, $expectedFiles, string $branch = ''): void
{
- if ($branch) {
+ if ($branch !== '' && $branch !== '0') {
$this->gitCheckout($path, $branch);
}
$expectedFiles = is_array($expectedFiles) ? $expectedFiles : [$expectedFiles];
@@ -512,11 +512,11 @@ public function runRoboCommand(string $command, bool $expectFail = false, string
if ($expectFail) {
throw new AssertionFailedError('Command exited successfully but should not');
}
- } catch (ErrorException $error) {
+ } catch (ErrorException $errorException) {
if (!$expectFail) {
- throw $error;
+ throw $errorException;
}
- $output = explode(PHP_EOL, ($error->getPrevious() instanceof \Throwable) ? $error->getPrevious()->getMessage() : '');
+ $output = explode(PHP_EOL, ($errorException->getPrevious() instanceof \Throwable) ? $errorException->getPrevious()->getMessage() : '');
}
return $output;
@@ -551,17 +551,19 @@ protected function runCliCommand(string $command): array
return $output;
}
- /**
- * Asserts that two associative arrays are similar.
- *
- * Both arrays must have the same indexes with identical values
- * without respect to key ordering
- *
- * @param array $expected
- * @param array $array
- *
- * @phpstan-ignore-next-line
- */
+ /**
+ * Asserts that two associative arrays are similar.
+ *
+ * Both arrays must have the same indexes with identical values
+ * without respect to key ordering
+ *
+ * @param array $expected
+ * Expected assert.
+ * @param array $array
+ * The array want to assert.
+ *
+ * @phpstan-ignore-next-line
+ */
protected function assertArraySimilar(array $expected, array $array): void
{
$this->assertEquals([], array_diff($array, $expected));
diff --git a/tests/Traits/MockTrait.php b/tests/Traits/MockTrait.php
new file mode 100644
index 0000000..54ead3a
--- /dev/null
+++ b/tests/Traits/MockTrait.php
@@ -0,0 +1,77 @@
+ $methodsMap
+ * Optional array of methods and values, keyed by method name. Array
+ * elements can be return values, callbacks created with
+ * $this->returnCallback(), or closures.
+ * @param array $args
+ * Optional array of constructor arguments. If omitted, a constructor
+ * will not be called.
+ *
+ * @return object
+ * Mocked class.
+ *
+ * @throws \ReflectionException
+ */
+ protected function prepareMock(string $class, array $methodsMap = [], array $args = [])
+ {
+ $methods = array_keys($methodsMap);
+
+ $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);
+ }
+ }
+
+ return $mock;
+ }
+}
diff --git a/tests/Traits/ReflectionTrait.php b/tests/Traits/ReflectionTrait.php
new file mode 100644
index 0000000..46518f0
--- /dev/null
+++ b/tests/Traits/ReflectionTrait.php
@@ -0,0 +1,103 @@
+hasMethod($name)) {
+ throw new \InvalidArgumentException(sprintf('Method %s does not exist', $name));
+ }
+
+ $method = $class->getMethod($name);
+
+ $originalAccessibility = $method->isPublic();
+
+ // 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);
+
+ // 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);
+
+ // Reset the method's accessibility to its original state.
+ $method->setAccessible($originalAccessibility);
+
+ return $result;
+ }
+
+ /**
+ * Set protected property value.
+ *
+ * @param object $object
+ * Object to set the value on.
+ * @param string $property
+ * Property name to set the value. Property should exists in the object.
+ * @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);
+
+ $property->setValue($object, $value);
+ }
+
+ /**
+ * Get protected value from the object.
+ *
+ * @param object $object
+ * Object to set the value on.
+ * @param string $property
+ * Property name to get the value. Property should exists in the object.
+ *
+ * @return mixed
+ * Protected property value.
+ */
+ protected static function getProtectedValue($object, $property)
+ {
+ $class = new \ReflectionClass($object::class);
+ $property = $class->getProperty($property);
+ $property->setAccessible(true);
+
+ return $property->getValue($class);
+ }
+}
diff --git a/tests/Unit/AbstractUnitTestCase.php b/tests/Unit/AbstractUnitTestCase.php
index 1970a81..a05e3c2 100644
--- a/tests/Unit/AbstractUnitTestCase.php
+++ b/tests/Unit/AbstractUnitTestCase.php
@@ -2,6 +2,7 @@
namespace DrevOps\Robo\Tests\Unit;
+use DrevOps\Robo\ArtefactTrait;
use DrevOps\Robo\Tests\AbstractTestCase;
/**
@@ -21,7 +22,7 @@ protected function setUp(): void
{
parent::setUp();
- $this->mock = $this->getMockForTrait('DrevOps\Robo\ArtefactTrait');
+ $this->mock = $this->getMockForTrait(ArtefactTrait::class);
$this->callProtectedMethod($this->mock, 'fsSetRootDir', [$this->fixtureDir]);
}
}
diff --git a/tests/Unit/ExcludeTest.php b/tests/Unit/ExcludeTest.php
index f6436b9..3e7c602 100644
--- a/tests/Unit/ExcludeTest.php
+++ b/tests/Unit/ExcludeTest.php
@@ -35,7 +35,6 @@ public function testExcludeExists(): void
* @param bool $expected
* Expected.
*
- * @return void
*
* @dataProvider dataProviderExcludeEmpty
*