From 5c3130cc364ce17011cca7b7b8e8be52bc1eb8fe Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Sat, 14 Nov 2026 13:58:44 +0100 Subject: [PATCH 1/2] [bench] Add AddDoesNotPerformAssertionToNonAssertingTestRector bench on deep parent chains --- ..._not_perform_assertion_lifecycle_bench.php | 131 ++++++++++++++++++ 1 file changed, 131 insertions(+) create mode 100644 bench/does_not_perform_assertion_lifecycle_bench.php diff --git a/bench/does_not_perform_assertion_lifecycle_bench.php b/bench/does_not_perform_assertion_lifecycle_bench.php new file mode 100644 index 00000000..59983cd5 --- /dev/null +++ b/bench/does_not_perform_assertion_lifecycle_bench.php @@ -0,0 +1,131 @@ +prepare{$level}();\n }\n\n"; + $methods .= " protected function tearDown(): void\n {\n \$this->cleanup = null;\n parent::tearDown();\n }\n\n"; + $methods .= " public function testInherited(): void\n {\n \$value = {$level};\n }\n\n"; + $methods .= " private function prepare{$level}(): void\n {\n \$this->ready = true;\n }\n\n"; + + for ($filler = 0; $filler < FILLER_METHODS; ++$filler) { + $methods .= " protected function helper{$filler}(array \$items): array\n {\n return array_map(fn (\$item) => \$item * {$filler}, \$items);\n }\n\n"; + } + + $writeClass( + $className, + "abstract class {$className} extends {$parentClassName}\n{\n protected \$ready;\n protected \$cleanup;\n\n{$methods}}\n" + ); + + $parentClassName = $className; + } + + for ($test = 0; $test < TESTS_PER_CHAIN; ++$test) { + $className = sprintf('Chain%dTest%d', $chain, $test); + + $methods = " public function testInherited(): void\n {\n parent::testInherited();\n }\n\n"; + for ($testMethod = 0; $testMethod < TEST_METHODS; ++$testMethod) { + $methods .= " public function testSomething{$testMethod}(): void\n {\n \$value = \$this->helper{$testMethod}([1, 2, 3]);\n }\n\n"; + } + + $testFilePaths[] = $writeClass( + $className, + "final class {$className} extends {$parentClassName}\n{\n{$methods}}\n" + ); + } +} + +$rectorConfig = (new LazyContainerFactory())->create(); +$rectorConfig->boot(); + +/** @var TestingParser $testingParser */ +$testingParser = $rectorConfig->make(TestingParser::class); + +/** @var BetterNodeFinder $betterNodeFinder */ +$betterNodeFinder = $rectorConfig->make(BetterNodeFinder::class); + +$rector = $rectorConfig->make(AddDoesNotPerformAssertionToNonAssertingTestRector::class); + +// parse test files up front, parent files are left for the rule to resolve +$classMethods = []; +foreach ($testFilePaths as $testFilePath) { + $stmts = $testingParser->parseFileToDecoratedNodes($testFilePath); + $classMethods = [...$classMethods, ...$betterNodeFinder->findInstanceOf($stmts, ClassMethod::class)]; +} + +$cpuSeconds = static function (): float { + $usage = getrusage(); + return $usage['ru_utime.tv_sec'] + $usage['ru_utime.tv_usec'] / 1_000_000 + + $usage['ru_stime.tv_sec'] + $usage['ru_stime.tv_usec'] / 1_000_000; +}; + +$wallStart = microtime(true); +$cpuStart = $cpuSeconds(); + +$changedCount = 0; +foreach ($classMethods as $classMethod) { + if ($rector->refactor($classMethod) instanceof ClassMethod) { + ++$changedCount; + } +} + +$cpu = $cpuSeconds() - $cpuStart; +$wall = microtime(true) - $wallStart; + +array_map('unlink', glob($directory . '/*.php')); +@rmdir($directory); + +printf('test classes: %d, test methods: %d, changed: %d%s', count($testFilePaths), count($classMethods), $changedCount, PHP_EOL); +printf('CPU: %.4f s (%.3f ms/method)%s', $cpu, $cpu / count($classMethods) * 1000, PHP_EOL); +printf('wall: %.4f s%s', $wall, PHP_EOL); From 8656a6cd6208474cdcfb70a926eb6211396b100f Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Sat, 14 Nov 2026 14:03:47 +0100 Subject: [PATCH 2/2] [ci] Run AddDoesNotPerformAssertionToNonAssertingTestRector bench in CI, fail above 1 s CPU --- .github/workflows/code_analysis.yaml | 4 ++++ bench/does_not_perform_assertion_lifecycle_bench.php | 6 ++++++ 2 files changed, 10 insertions(+) diff --git a/.github/workflows/code_analysis.yaml b/.github/workflows/code_analysis.yaml index 7a9374bc..abf35b6d 100644 --- a/.github/workflows/code_analysis.yaml +++ b/.github/workflows/code_analysis.yaml @@ -36,6 +36,10 @@ jobs: name: 'Tests' run: vendor/bin/phpunit + - + name: 'Bench' + run: php bench/does_not_perform_assertion_lifecycle_bench.php + name: ${{ matrix.actions.name }} runs-on: ubuntu-latest timeout-minutes: 10 diff --git a/bench/does_not_perform_assertion_lifecycle_bench.php b/bench/does_not_perform_assertion_lifecycle_bench.php index 59983cd5..dbfa0808 100644 --- a/bench/does_not_perform_assertion_lifecycle_bench.php +++ b/bench/does_not_perform_assertion_lifecycle_bench.php @@ -24,6 +24,7 @@ const TESTS_PER_CHAIN = 30; // test classes extending the deepest parent of each chain const TEST_METHODS = 5; // non-asserting test methods per test class const FILLER_METHODS = 15; // extra methods per parent class, to make parsing realistic +const MAX_CPU_SECONDS = 1.0; // fails in CI above this $directory = sys_get_temp_dir() . '/rector_phpunit_bench_' . getmypid(); @mkdir($directory); @@ -129,3 +130,8 @@ printf('test classes: %d, test methods: %d, changed: %d%s', count($testFilePaths), count($classMethods), $changedCount, PHP_EOL); printf('CPU: %.4f s (%.3f ms/method)%s', $cpu, $cpu / count($classMethods) * 1000, PHP_EOL); printf('wall: %.4f s%s', $wall, PHP_EOL); + +if ($cpu > MAX_CPU_SECONDS) { + printf('CPU %.4f s is over the %.1f s limit%s', $cpu, MAX_CPU_SECONDS, PHP_EOL); + exit(1); +}