Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add phpunit v10 support #373

Merged
merged 18 commits into from
Nov 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions .github/workflows/build-changelog.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,11 @@ on:
- develop

jobs:
update_release_draft:
update:
name: Update
runs-on: ubuntu-latest
steps:
- name: Run Release Drafter
- name: Run
uses: release-drafter/release-drafter@v5
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
10 changes: 5 additions & 5 deletions .github/workflows/test-unit.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,15 +46,15 @@ jobs:

- name: Install PHP dependencies
run: |
if [ "${{ matrix.type }}" != "Phpunit" ] && [ "${{ matrix.type }}" != "StaticAnalysis" ]; then composer remove --no-interaction --no-update phpunit/phpunit johnkary/phpunit-speedtrap --dev; fi
if [ "${{ matrix.type }}" != "Phpunit" ] && [ "${{ matrix.type }}" != "StaticAnalysis" ]; then composer remove --no-interaction --no-update phpunit/phpunit atk4/ergebnis-phpunit-slow-test-detector --dev; fi
if [ "${{ matrix.type }}" != "CodingStyle" ]; then composer remove --no-interaction --no-update friendsofphp/php-cs-fixer --dev; fi
if [ "${{ matrix.type }}" != "StaticAnalysis" ]; then composer remove --no-interaction --no-update phpstan/\* --dev; fi
composer update --ansi --prefer-dist --no-interaction --no-progress --optimize-autoloader

- name: "Run tests (only for Phpunit)"
if: startsWith(matrix.type, 'Phpunit')
run: |
vendor/bin/phpunit --exclude-group none --no-coverage -v
vendor/bin/phpunit --exclude-group none --no-coverage --fail-on-warning --fail-on-risky $(if vendor/bin/phpunit --version | grep -q '^PHPUnit 9\.'; then echo -v; else echo --fail-on-notice --fail-on-deprecation --display-notices --display-deprecations --display-warnings --display-errors --display-incomplete --display-skipped; fi)

- name: Check Coding Style (only for CodingStyle)
if: matrix.type == 'CodingStyle'
Expand Down Expand Up @@ -108,21 +108,21 @@ jobs:

- name: Install PHP dependencies
run: |
if [ "${{ matrix.type }}" != "Phpunit" ] && [ "${{ matrix.type }}" != "Phpunit Lowest" ] && [ "${{ matrix.type }}" != "Phpunit Burn" ]; then composer remove --no-interaction --no-update phpunit/phpunit johnkary/phpunit-speedtrap --dev; fi
if [ "${{ matrix.type }}" != "Phpunit" ] && [ "${{ matrix.type }}" != "Phpunit Lowest" ] && [ "${{ matrix.type }}" != "Phpunit Burn" ]; then composer remove --no-interaction --no-update phpunit/phpunit atk4/ergebnis-phpunit-slow-test-detector --dev; fi
if [ "${{ matrix.type }}" != "CodingStyle" ]; then composer remove --no-interaction --no-update friendsofphp/php-cs-fixer --dev; fi
if [ "${{ matrix.type }}" != "StaticAnalysis" ]; then composer remove --no-interaction --no-update phpstan/\* --dev; fi
if [ -n "$LOG_COVERAGE" ]; then composer require --no-interaction --no-install phpunit/phpcov; fi
composer update --ansi --prefer-dist --no-interaction --no-progress --optimize-autoloader
if [ "${{ matrix.type }}" = "Phpunit Lowest" ]; then composer update --ansi --prefer-dist --prefer-lowest --prefer-stable --no-interaction --no-progress --optimize-autoloader; fi
if [ "${{ matrix.type }}" = "Phpunit Burn" ]; then sed -i 's~ *public function runBare(): void~public function runBare(): void { gc_collect_cycles(); gc_collect_cycles(); $memDiffs = array_fill(0, '"$(if [ \"$GITHUB_EVENT_NAME\" == \"schedule\" ]; then echo 1024; else echo 64; fi)"', 0); for ($i = -1; $i < count($memDiffs); ++$i) { $this->_runBare(); gc_collect_cycles(); gc_collect_cycles(); $mem = memory_get_usage(); if ($i !== -1) { $memDiffs[$i] = $mem - $memPrev; } $memPrev = $mem; rsort($memDiffs); if (array_sum($memDiffs) >= 4096 * 1024 || $memDiffs[2] > 0) { $this->onNotSuccessfulTest(new AssertionFailedError("Memory leak detected! (" . implode(" + ", array_map(static fn ($v) => number_format($v / 1024, 3, ".", " "), array_filter($memDiffs))) . " KB, " . ($i + 2) . " iterations)")); } } } private function _runBare(): void~' vendor/phpunit/phpunit/src/Framework/TestCase.php && cat vendor/phpunit/phpunit/src/Framework/TestCase.php | grep '_runBare('; fi
if [ "${{ matrix.type }}" = "Phpunit Burn" ]; then sed -i 's~public function runBare(): void~public function runBare(): void { gc_collect_cycles(); gc_collect_cycles(); $memDiffs = array_fill(0, '"$(if [ \"$GITHUB_EVENT_NAME\" == \"schedule\" ]; then echo 1024; else echo 64; fi)"', 0); for ($i = -1; $i < count($memDiffs); ++$i) { $this->_runBare(); gc_collect_cycles(); gc_collect_cycles(); $mem = memory_get_usage(); if ($i !== -1) { $memDiffs[$i] = $mem - $memPrev; } $memPrev = $mem; rsort($memDiffs); if (array_sum($memDiffs) >= 4096 * 1024 || $memDiffs[2] > 0) { $this->onNotSuccessfulTest(new AssertionFailedError("Memory leak detected! (" . implode(" + ", array_map(static fn ($v) => number_format($v / 1024, 3, ".", " "), array_filter($memDiffs))) . " KB, " . ($i + 2) . " iterations)")); } } } private function _runBare(): void~' vendor/phpunit/phpunit/src/Framework/TestCase.php && cat vendor/phpunit/phpunit/src/Framework/TestCase.php | grep '_runBare('; fi

- name: Init
run: |
if [ -n "$LOG_COVERAGE" ]; then mkdir coverage; fi

- name: "Run tests"
run: |
php -d opcache.enable_cli=1 vendor/bin/phpunit --exclude-group none $(if [ -n "$LOG_COVERAGE" ]; then echo --coverage-text; else echo --no-coverage; fi) -v
php -d opcache.enable_cli=1 vendor/bin/phpunit --exclude-group none $(if [ -n "$LOG_COVERAGE" ]; then echo --coverage-text; else echo --no-coverage; fi) --fail-on-warning --fail-on-risky $(if vendor/bin/phpunit --version | grep -q '^PHPUnit 9\.'; then echo -v; else echo --fail-on-notice --fail-on-deprecation --display-notices --display-deprecations --display-warnings --display-errors --display-incomplete --display-skipped; fi)

- name: Upload coverage logs 1/2 (only for coverage)
if: env.LOG_COVERAGE
Expand Down
4 changes: 2 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,14 +40,14 @@
"symfony/yaml": "^3.4 || ^4.4 || ^5.1 || ^6.0 || ^7.0"
},
"require-dev": {
"atk4/ergebnis-phpunit-slow-test-detector": "^2.4",
"ergebnis/composer-normalize": "^2.13",
"friendsofphp/php-cs-fixer": "^3.0",
"johnkary/phpunit-speedtrap": "^3.3",
"phpstan/extension-installer": "^1.1",
"phpstan/phpstan": "^1.0",
"phpstan/phpstan-deprecation-rules": "^1.0",
"phpstan/phpstan-strict-rules": "^1.3",
"phpunit/phpunit": "^9.5.5"
"phpunit/phpunit": "^9.5.5 || ^10.0"
},
"minimum-stability": "dev",
"prefer-stable": true,
Expand Down
2 changes: 1 addition & 1 deletion docs/container.md
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,7 @@ $args = ['name' => 'child_name']; // obsolete, backward-compatible
```

Method will return the object. Will throw exception if child with same
name already exist.
name already exists.
:::

:::{php:method} removeElement($shortName)
Expand Down
26 changes: 26 additions & 0 deletions phpstan.neon.dist
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,29 @@ parameters:
path: 'tests/DynamicMethodTraitTest.php'
message: '~^Call to an undefined method Atk4\\Core\\Tests\\(DynamicMethodMock|DynamicMethodWithoutHookMock)::\w+\(\)\.$~'
count: 10

# remove once PHPUnit 9.x support is removed
-
path: 'src/Phpunit/TestCase.php'
message: '~^Access to constant (STATUS_INCOMPLETE|STATUS_SKIPPED) on an unknown class PHPUnit\\Runner\\BaseTestRunner\.$~'
count: 2
-
path: 'src/Phpunit/TestCase.php'
message: '~^Call to an undefined method Atk4\\Core\\Phpunit\\TestCase::(getName|getStatus|getTestResultObject)\(\)\.$~'
count: 4
-
path: 'src/Phpunit/TestCase.php'
message: '~^Call to an undefined static method PHPUnit\\Util\\Test::(getLinesToBeCovered|getLinesToBeUsed)\(\)\.$~'
count: 2
-
path: 'tests/HookTraitTest.php'
message: '~^Call to an undefined method Atk4\\Core\\Tests\\HookTraitTest::getName\(\)\.$~'
count: 2
-
path: 'tests/Phpunit/TestCaseTest.php'
message: '~^Call to an undefined method Atk4\\Core\\Tests\\Phpunit\\TestCaseTest::getStatus\(\)\.$~'
count: 1
-
path: 'tests/Phpunit/TestCaseTest.php'
message: '~^Access to constant STATUS_INCOMPLETE on an unknown class PHPUnit\\Runner\\BaseTestRunner\.$~'
count: 1
10 changes: 6 additions & 4 deletions phpunit.xml.dist
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,16 @@
<directory>tests</directory>
</testsuite>
</testsuites>
<listeners>
<listener class="JohnKary\PHPUnit\Listener\SpeedTrapListener" />
</listeners>
<coverage>
<extensions>
<bootstrap class="Ergebnis\PHPUnit\SlowTestDetector\Extension" />
</extensions>
<source>
<include>
<directory>src</directory>
<directory>tests</directory>
</include>
</source>
<coverage>
<report>
<php outputFile="coverage/phpunit.cov" />
</report>
Expand Down
21 changes: 11 additions & 10 deletions src/CollectionTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,10 @@ trait CollectionTrait
*
* @param string $collection property name
*/
public function _addIntoCollection(string $name, object $item, string $collection): object
protected function _addIntoCollection(string $name, object $item, string $collection): object
{
if (!isset($this->{$collection}) || !is_array($this->{$collection})) {
throw (new Exception('Collection does NOT exist'))
throw (new Exception('Collection does not exist'))
->addMoreInfo('collection', $collection);
}

Expand All @@ -41,11 +41,10 @@ public function _addIntoCollection(string $name, object $item, string $collectio
}

if ($this->_hasInCollection($name, $collection)) {
throw (new Exception('Element with the same name already exist in the collection'))
throw (new Exception('Element with the same name already exists in the collection'))
->addMoreInfo('collection', $collection)
->addMoreInfo('name', $name);
}
$this->{$collection}[$name] = $item;

// carry on reference to application if we have appScopeTraits set
if ((TraitUtil::hasAppScopeTrait($this) && TraitUtil::hasAppScopeTrait($item))
Expand All @@ -69,6 +68,8 @@ public function _addIntoCollection(string $name, object $item, string $collectio
}
}

$this->{$collection}[$name] = $item;

return $item;
}

Expand All @@ -77,10 +78,10 @@ public function _addIntoCollection(string $name, object $item, string $collectio
*
* @param string $collection property name
*/
public function _removeFromCollection(string $name, string $collection): void
protected function _removeFromCollection(string $name, string $collection): void
{
if (!$this->_hasInCollection($name, $collection)) {
throw (new Exception('Element is NOT in the collection'))
throw (new Exception('Element is not in the collection'))
->addMoreInfo('collection', $collection)
->addMoreInfo('name', $name);
}
Expand All @@ -94,7 +95,7 @@ public function _removeFromCollection(string $name, string $collection): void
*
* @param string $collectionName property name to be cloned
*/
public function _cloneCollection(string $collectionName): void
protected function _cloneCollection(string $collectionName): void
{
$this->{$collectionName} = array_map(function ($item) {
$item = clone $item;
Expand All @@ -111,19 +112,19 @@ public function _cloneCollection(string $collectionName): void
*
* @param string $collection property name
*/
public function _hasInCollection(string $name, string $collection): bool
protected function _hasInCollection(string $name, string $collection): bool
{
return isset($this->{$collection}[$name]);
}

/**
* @param string $collection property name
*/
public function _getFromCollection(string $name, string $collection): object
protected function _getFromCollection(string $name, string $collection): object
{
$res = $this->{$collection}[$name] ?? null;
if ($res === null) {
throw (new Exception('Element is NOT in the collection'))
throw (new Exception('Element is not in the collection'))
->addMoreInfo('collection', $collection)
->addMoreInfo('name', $name);
}
Expand Down
4 changes: 2 additions & 2 deletions src/ContainerTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ trait ContainerTrait
/**
* Returns unique element name based on desired name.
*/
public function _uniqueElementName(string $desired): string
protected function _uniqueElementName(string $desired): string
{
if (!isset($this->_elementNameCounts[$desired])) {
$this->_elementNameCounts[$desired] = 1;
Expand Down Expand Up @@ -130,7 +130,7 @@ public function removeElement($shortName)
}

if (!isset($this->elements[$shortName])) {
throw (new Exception('Could not remove child from parent. Instead of destroy() try using removeField / removeColumn / ..'))
throw (new Exception('Child element not found'))
->addMoreInfo('parent', $this)
->addMoreInfo('name', $shortName);
}
Expand Down
2 changes: 1 addition & 1 deletion src/DynamicMethodTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ public function addMethod(string $name, \Closure $fx)
}

if ($this->hasMethod($name)) {
throw (new Exception('Registering method twice'))
throw (new Exception('Method is already defined'))
->addMoreInfo('name', $name);
}

Expand Down
47 changes: 38 additions & 9 deletions src/Phpunit/TestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,43 @@
use Atk4\Core\WarnDynamicPropertyTrait;
use PHPUnit\Framework\TestCase as BaseTestCase;
use PHPUnit\Framework\TestResult;
use PHPUnit\Metadata\Api\CodeCoverage as CodeCoverageMetadata;
use PHPUnit\Runner\BaseTestRunner;
use PHPUnit\Runner\CodeCoverage;
use PHPUnit\Util\Test as TestUtil;
use SebastianBergmann\CodeCoverage\CodeCoverage;
use SebastianBergmann\CodeCoverage\CodeCoverage as CodeCoverageRaw;

if (\PHP_VERSION_ID >= 8_01_00) {
trait Phpunit9xTestCaseTrait
{
protected function onNotSuccessfulTest(\Throwable $e): never
{
$this->_onNotSuccessfulTest($e);
}
}
} else {
trait Phpunit9xTestCaseTrait
{
protected function onNotSuccessfulTest(\Throwable $e): void
{
$this->_onNotSuccessfulTest($e);
}
}
}

/**
* Generic TestCase for PHPUnit tests for ATK4 repos.
*/
abstract class TestCase extends BaseTestCase
{
use Phpunit9xTestCaseTrait;
use WarnDynamicPropertyTrait;

final public static function isPhpunit9x(): bool
{
return (new \ReflectionClass(self::class))->hasMethod('getStatus');
}

protected function setUp(): void
{
// rerun data providers to fix coverage when coverage for test files is enabled
Expand Down Expand Up @@ -89,15 +115,15 @@ protected function tearDown(): void
gc_collect_cycles();

// fix coverage for skipped/incomplete tests
// based on https://github.com/sebastianbergmann/phpunit/blob/9.5.21/src/Framework/TestResult.php#L830
// and https://github.com/sebastianbergmann/phpunit/blob/9.5.21/src/Framework/TestResult.php#L857
if (in_array($this->getStatus(), [BaseTestRunner::STATUS_SKIPPED, BaseTestRunner::STATUS_INCOMPLETE], true)) {
$coverage = $this->getTestResultObject()->getCodeCoverage();
// based on https://github.com/sebastianbergmann/phpunit/blob/9.5.21/src/Framework/TestResult.php#L830 https://github.com/sebastianbergmann/phpunit/blob/10.4.2/src/Framework/TestRunner.php#L154
// and https://github.com/sebastianbergmann/phpunit/blob/9.5.21/src/Framework/TestResult.php#L857 https://github.com/sebastianbergmann/phpunit/blob/10.4.2/src/Framework/TestRunner.php#L178
if (self::isPhpunit9x() ? in_array($this->getStatus(), [BaseTestRunner::STATUS_SKIPPED, BaseTestRunner::STATUS_INCOMPLETE], true) : $this->status()->isSkipped() || $this->status()->isIncomplete()) {
$coverage = self::isPhpunit9x() ? $this->getTestResultObject()->getCodeCoverage() : (CodeCoverage::instance()->isActive() ? CodeCoverage::instance() : null);
if ($coverage !== null) {
$coverageId = \Closure::bind(static fn () => $coverage->currentId, null, CodeCoverage::class)();
$coverageId = self::isPhpunit9x() ? \Closure::bind(static fn () => $coverage->currentId, null, CodeCoverageRaw::class)() : (\Closure::bind(static fn () => $coverage->collecting, null, CodeCoverage::class)() ? $this : null);
if ($coverageId !== null) {
$linesToBeCovered = TestUtil::getLinesToBeCovered(static::class, $this->getName(false));
$linesToBeUsed = TestUtil::getLinesToBeUsed(static::class, $this->getName(false));
$linesToBeCovered = self::isPhpunit9x() ? TestUtil::getLinesToBeCovered(static::class, $this->getName(false)) : (new CodeCoverageMetadata())->linesToBeCovered(static::class, $this->name());
$linesToBeUsed = self::isPhpunit9x() ? TestUtil::getLinesToBeUsed(static::class, $this->getName(false)) : (new CodeCoverageMetadata())->linesToBeUsed(static::class, $this->name());
$coverage->stop(true, $linesToBeCovered, $linesToBeUsed);
$coverage->start($coverageId);
}
Expand Down Expand Up @@ -131,7 +157,10 @@ private function releaseObjectsFromExceptionTrace(\Throwable $e): void
}
}

protected function onNotSuccessfulTest(\Throwable $e): void
/**
* @return never
*/
protected function _onNotSuccessfulTest(\Throwable $e): void
{
// release objects from uncaught exception as it is never released
$this->releaseObjectsFromExceptionTrace($e);
Expand Down
2 changes: 1 addition & 1 deletion src/TraitUtil.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ public static function hasTrait($class, string $traitName): bool
// prevent mass use for other than internal use then we can decide
// if we want to keep support this or replace with pure interfaces
if (!str_starts_with($traitName, 'Atk4\Core\\')) {
throw new Exception(self::class . '::hasTrait is not intended for use with other than Atk4\Core\* traits');
throw new Exception(self::class . '::hasTrait() method is not intended for use with other than Atk4\Core\* traits');
}

$parentClass = get_parent_class($class);
Expand Down
2 changes: 1 addition & 1 deletion src/TranslatableTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
trait TranslatableTrait
{
/**
* Translates the given message.
* Translate the given message.
*
* @param string $message The message to be translated
* @param array<string, mixed> $parameters Array of parameters used to translate message
Expand Down
2 changes: 1 addition & 1 deletion tests/CollectionMock.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ class CollectionMock
protected $fields = [];

/**
* @param array<mixed>|object|null $seed
* @param array<mixed>|FieldMock|null $seed
*/
public function addField(string $name, $seed = null): FieldMock
{
Expand Down
Loading