Skip to content

Commit

Permalink
Merge pull request #1270 from rectorphp/listener-hook
Browse files Browse the repository at this point in the history
[PHPUnit 9.0] Add TestListenerToHooksRector
  • Loading branch information
TomasVotruba committed Mar 31, 2019
2 parents 8aa22d2 + 1e24821 commit 44ee91f
Show file tree
Hide file tree
Showing 5 changed files with 350 additions and 0 deletions.
2 changes: 2 additions & 0 deletions config/level/phpunit/phpunit90.yaml
@@ -0,0 +1,2 @@
services:
Rector\PHPUnit\Rector\Class_\TestListenerToHooksRector: ~
174 changes: 174 additions & 0 deletions packages/PHPUnit/src/Rector/Class_/TestListenerToHooksRector.php
@@ -0,0 +1,174 @@
<?php declare(strict_types=1);

namespace Rector\PHPUnit\Rector\Class_;

use PhpParser\Node;
use PhpParser\Node\Identifier;
use PhpParser\Node\Name\FullyQualified;
use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\Stmt\ClassMethod;
use Rector\Rector\AbstractRector;
use Rector\RectorDefinition\CodeSample;
use Rector\RectorDefinition\RectorDefinition;

/**
* @see https://github.com/sebastianbergmann/phpunit/issues/3388
* @see https://github.com/sebastianbergmann/phpunit/commit/34a0abd8b56a4a9de83c9e56384f462541a0f939
*
* @see https://github.com/sebastianbergmann/phpunit/tree/master/src/Runner/Hook
*/
final class TestListenerToHooksRector extends AbstractRector
{
/**
* @var string[][]
*/
private $listenerMethodToHookInterfaces = [
'addIncompleteTest' => ['PHPUnit\Runner\AfterIncompleteTestHook', 'executeAfterIncompleteTest'],
'addRiskyTest' => ['PHPUnit\Runner\AfterRiskyTestHook', 'executeAfterRiskyTest'],
'addSkippedTest' => ['PHPUnit\Runner\AfterSkippedTestHook', 'executeAfterSkippedTest'],
'addError' => ['PHPUnit\Runner\AfterTestErrorHook', 'executeAfterTestError'],
'addFailure' => ['PHPUnit\Runner\AfterTestFailureHook', 'executeAfterTestFailure'],
'addWarning' => ['PHPUnit\Runner\AfterTestWarningHook', 'executeAfterTestWarning'],
# test
'startTest' => ['PHPUnit\Runner\BeforeTestHook', 'executeBeforeTest'],
'endTest' => ['PHPUnit\Runner\AfterTestHook', 'executeAfterTest'],
# suite
'startTestSuite' => ['PHPUnit\Runner\BeforeFirstTestHook', 'executeBeforeFirstTest'],
'endTestSuite' => ['PHPUnit\Runner\AfterLastTestHook', 'executeAfterLastTest'],
];

/**
* @var string
*/
private $testListenerClass;

public function __construct(string $testListenerClass = 'PHPUnit\Framework\TestListener')
{
$this->testListenerClass = $testListenerClass;
}

public function getDefinition(): RectorDefinition
{
return new RectorDefinition('Refactor "*TestListener.php" to particular "*Hook.php" files', [
new CodeSample(
<<<'CODE_SAMPLE'
namespace App\Tests;
use PHPUnit\Framework\TestListener;
final class BeforeListHook implements TestListener
{
public function addError(Test $test, \Throwable $t, float $time): void
{
}
public function addWarning(Test $test, Warning $e, float $time): void
{
}
public function addFailure(Test $test, AssertionFailedError $e, float $time): void
{
}
public function addIncompleteTest(Test $test, \Throwable $t, float $time): void
{
}
public function addRiskyTest(Test $test, \Throwable $t, float $time): void
{
}
public function addSkippedTest(Test $test, \Throwable $t, float $time): void
{
}
public function startTestSuite(TestSuite $suite): void
{
}
public function endTestSuite(TestSuite $suite): void
{
}
public function startTest(Test $test): void
{
echo 'start test!';
}
public function endTest(Test $test, float $time): void
{
echo $time;
}
}
CODE_SAMPLE
,
<<<'CODE_SAMPLE'
namespace App\Tests;
final class BeforeListHook implements \PHPUnit\Runner\BeforeTestHook, \PHPUnit\Runner\AfterTestHook
{
public function executeBeforeTest(Test $test): void
{
echo 'start test!';
}
public function executeAfterTest(Test $test, float $time): void
{
echo $time;
}
}
CODE_SAMPLE
),
]);
}

/**
* List of nodes this class checks, classes that implement @see \PhpParser\Node
* @return string[]
*/
public function getNodeTypes(): array
{
return [Class_::class];
}

/**
* Process Node of matched type
* @param Class_ $node
*/
public function refactor(Node $node): ?Node
{
if (! $this->isType($node, $this->testListenerClass)) {
return null;
}

foreach ($node->implements as $implement) {
if ($this->isName($implement, $this->testListenerClass)) {
$this->removeNode($implement);
}
}

foreach ($node->getMethods() as $classMethod) {
$this->processClassMethod($node, $classMethod);
}

return $node;
}

private function processClassMethod(Class_ $class, ClassMethod $classMethod): void
{
foreach ($this->listenerMethodToHookInterfaces as $methodName => $hookClassAndMethod) {
/** @var string $methodName */
if (! $this->isName($classMethod, $methodName)) {
continue;
}

// remove empty methods
if (empty($classMethod->stmts)) {
$this->removeNode($classMethod);
} else {
$class->implements[] = new FullyQualified($hookClassAndMethod[0]);
$classMethod->name = new Identifier($hookClassAndMethod[1]);
}
}
}
}
@@ -0,0 +1,82 @@
<?php

namespace Rector\PHPUnit\Tests\Rector\Class_\TryCatchToExpectExceptionRector\Fixture;

use PHPUnit\Framework\AssertionFailedError;
use PHPUnit\Framework\Test;
use PHPUnit\Framework\TestListener;
use PHPUnit\Framework\TestSuite;
use PHPUnit\Framework\Warning;

final class BeforeListHook implements TestListener
{
public function addError(Test $test, \Throwable $t, float $time): void
{
}

public function addWarning(Test $test, Warning $e, float $time): void
{
}

public function addFailure(Test $test, AssertionFailedError $e, float $time): void
{
}

public function addIncompleteTest(Test $test, \Throwable $t, float $time): void
{
}

public function addRiskyTest(Test $test, \Throwable $t, float $time): void
{
}

public function addSkippedTest(Test $test, \Throwable $t, float $time): void
{
}

public function startTestSuite(TestSuite $suite): void
{
}

public function endTestSuite(TestSuite $suite): void
{
}

public function startTest(Test $test): void
{
dump($test);
echo 'start test!';
}

public function endTest(Test $test, float $time): void
{
dump($time);
}
}

?>
-----
<?php

namespace Rector\PHPUnit\Tests\Rector\Class_\TryCatchToExpectExceptionRector\Fixture;

use PHPUnit\Framework\AssertionFailedError;
use PHPUnit\Framework\Test;
use PHPUnit\Framework\TestListener;
use PHPUnit\Framework\TestSuite;
use PHPUnit\Framework\Warning;

final class BeforeListHook implements \PHPUnit\Runner\BeforeTestHook, \PHPUnit\Runner\AfterTestHook
{
public function executeBeforeTest(Test $test): void
{
dump($test);
echo 'start test!';
}
public function executeAfterTest(Test $test, float $time): void
{
dump($time);
}
}

?>
@@ -0,0 +1,70 @@
<?php

namespace Rector\PHPUnit\Tests\Rector\Class_\TryCatchToExpectExceptionRector\Fixture;

use PHPUnit\Framework\AssertionFailedError;
use PHPUnit\Framework\Test;
use PHPUnit\Framework\TestListener;
use PHPUnit\Framework\TestSuite;
use PHPUnit\Framework\Warning;

final class SomeListener implements TestListener
{
public function addError(Test $test, \Throwable $t, float $time): void
{
}

public function addWarning(Test $test, Warning $e, float $time): void
{
}

public function addFailure(Test $test, AssertionFailedError $e, float $time): void
{
}

public function addIncompleteTest(Test $test, \Throwable $t, float $time): void
{
}

public function addRiskyTest(Test $test, \Throwable $t, float $time): void
{
}

public function addSkippedTest(Test $test, \Throwable $t, float $time): void
{
}

public function startTestSuite(TestSuite $suite): void
{
}

public function endTestSuite(TestSuite $suite): void
{
}

public function startTest(Test $test): void
{
}

public function endTest(Test $test, float $time): void
{
}
}

?>
-----
<?php

namespace Rector\PHPUnit\Tests\Rector\Class_\TryCatchToExpectExceptionRector\Fixture;

use PHPUnit\Framework\AssertionFailedError;
use PHPUnit\Framework\Test;
use PHPUnit\Framework\TestListener;
use PHPUnit\Framework\TestSuite;
use PHPUnit\Framework\Warning;

final class SomeListener implements TestListener
{
}

?>
@@ -0,0 +1,22 @@
<?php declare(strict_types=1);

namespace Rector\PHPUnit\Tests\Rector\Class_\TestListenerToHooksRector;

use Rector\PHPUnit\Rector\Class_\TestListenerToHooksRector;
use Rector\Testing\PHPUnit\AbstractRectorTestCase;

final class TestListenerToHooksRectorTest extends AbstractRectorTestCase
{
public function test(): void
{
$this->doTestFiles([
__DIR__ . '/Fixture/clear_it_all.php.inc',
__DIR__ . '/Fixture/before_list_hook.php.inc',
]);
}

public function getRectorClass(): string
{
return TestListenerToHooksRector::class;
}
}

0 comments on commit 44ee91f

Please sign in to comment.