diff --git a/.github/workflows/integrate.yaml b/.github/workflows/integrate.yaml index db82574..190d8a5 100644 --- a/.github/workflows/integrate.yaml +++ b/.github/workflows/integrate.yaml @@ -15,36 +15,9 @@ jobs: runs-on: "ubuntu-latest" strategy: - fail-fast: false - matrix: - include: - - php-version: "7.2" - phpunit-version: "8.*" - - - php-version: "7.3" - phpunit-version: "8.*" - - - php-version: "7.3" - phpunit-version: "9.*" - - - php-version: "7.4" - phpunit-version: "8.*" - - - php-version: "7.4" - phpunit-version: "9.*" - - - php-version: "8.0" - phpunit-version: "8.*" - - - php-version: "8.0" - phpunit-version: "9.*" - - - php-version: "8.1" - phpunit-version: "8.*" - - - php-version: "8.1" - phpunit-version: "9.*" + php-version: + - "8.1" steps: - name: "Checkout" @@ -69,8 +42,8 @@ jobs: key: "php-${{ matrix.php-version }}-composer-${{ matrix.phpunit-version }}" restore-keys: "php-${{ matrix.php-version }}-composer-" - - name: "Require phpunit/phpunit ${{ matrix.phpunit-version }}" - run: "composer require phpunit/phpunit:${{ matrix.phpunit-version }}" + - name: "Install dependencies with composer" + run: "composer install --no-interaction --no-progress" - name: "Run tests with phpunit/phpunit" run: "vendor/bin/phpunit" diff --git a/README.md b/README.md index df1c428..f2dafae 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ Enable with all defaults by adding the following code to your project's `phpunit ... - + ``` @@ -46,18 +46,10 @@ Each parameter is set in `phpunit.xml`: - - - - - 500 - - - 10 - - - - + + + + ``` @@ -132,7 +124,7 @@ Step 1) Enable SpeedTrap in phpunit.xml. The slowness report will output during ... - + ``` @@ -165,7 +157,7 @@ Step 1) Setup phpunit.xml to enable SpeedTrap, but disable slowness profiling by - + ``` @@ -192,7 +184,7 @@ The easiest way to set environment variables for the script `simple-phpunit` is - + ``` diff --git a/composer.json b/composer.json index 246098d..18450f2 100644 --- a/composer.json +++ b/composer.json @@ -19,9 +19,11 @@ } ], "require": { - "php": ">=7.2", - "phpunit/phpunit": "^8.0 || ^9.0" + "php": "^8.1", + "phpunit/phpunit": "dev-main#e7dcb0e as 10.0.0" }, + "minimum-stability": "dev", + "prefer-stable": true, "extra": { "branch-alias": { "dev-master": "5.0-dev" diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 08392a5..6b86c76 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -2,7 +2,7 @@ @@ -19,18 +19,10 @@ - - - - - 500 - - - 5 - - - - + + + + diff --git a/src/PreparedTest.php b/src/PreparedTest.php new file mode 100644 index 0000000..68d1126 --- /dev/null +++ b/src/PreparedTest.php @@ -0,0 +1,26 @@ +test; + } + + public function start(): Event\Telemetry\HRTime + { + return $this->start; + } +} diff --git a/src/RecordThatTestHasBeenPrepared.php b/src/RecordThatTestHasBeenPrepared.php new file mode 100644 index 0000000..4959f3a --- /dev/null +++ b/src/RecordThatTestHasBeenPrepared.php @@ -0,0 +1,22 @@ +speedTrap->recordThatTestHasBeenPrepared( + $event->test(), + $event->telemetryInfo()->time(), + ); + } +} diff --git a/src/RecordThatTestHasPassed.php b/src/RecordThatTestHasPassed.php new file mode 100644 index 0000000..f912811 --- /dev/null +++ b/src/RecordThatTestHasPassed.php @@ -0,0 +1,22 @@ +speedTrap->recordThatTestHasPassed( + $event->test(), + $event->telemetryInfo()->time(), + ); + } +} diff --git a/src/ShowSlowTests.php b/src/ShowSlowTests.php new file mode 100644 index 0000000..91db976 --- /dev/null +++ b/src/ShowSlowTests.php @@ -0,0 +1,19 @@ +speedTrap->showSlowTests(); + } +} diff --git a/src/SpeedTrap.php b/src/SpeedTrap.php index 9b84d2c..30e2230 100644 --- a/src/SpeedTrap.php +++ b/src/SpeedTrap.php @@ -3,35 +3,21 @@ namespace JohnKary\PHPUnit\Extension; -use PHPUnit\Runner\AfterLastTestHook; -use PHPUnit\Runner\AfterSuccessfulTestHook; -use PHPUnit\Runner\BeforeFirstTestHook; -use PHPUnit\Util\Test as TestUtil; +use PHPUnit\Event; +use PHPUnit\Metadata; /** * A PHPUnit Extension that exposes your slowest running tests by outputting * results directly to the console. */ -class SpeedTrap implements AfterSuccessfulTestHook, BeforeFirstTestHook, AfterLastTestHook +class SpeedTrap { /** - * Slowness profiling enabled by default. Set to false to disable profiling - * and reporting. + * A map of test identifiers to PreparedTest objects, for all tests that have been prepared, but not yet passed. * - * Use environment variable "PHPUNIT_SPEEDTRAP" set to value "disabled" to - * disable profiling. - * - * @var boolean - */ - protected $enabled = true; - - /** - * Internal tracking for test suites. - * - * Increments as more suites are run, then decremented as they finish. All - * suites have been run when returns to 0. + * @var array */ - protected $suites = 0; + private array $preparedTests = []; /** * Test execution time (milliseconds) after which a test will be considered @@ -55,24 +41,44 @@ class SpeedTrap implements AfterSuccessfulTestHook, BeforeFirstTestHook, AfterLa */ protected $slow = []; - public function __construct(array $options = []) - { - $this->enabled = getenv('PHPUNIT_SPEEDTRAP') === 'disabled' ? false : true; - $this->loadOptions($options); + public function __construct( + int $slowThreshold, + int $reportLength, + ) { + $this->slowThreshold = $slowThreshold; + $this->reportLength = $reportLength; } - /** - * A test successfully ended. - * - * @param string $test - * @param float $time - */ - public function executeAfterSuccessfulTest(string $test, float $time): void - { - if (!$this->enabled) return; + public function recordThatTestHasBeenPrepared( + Event\Code\Test $test, + Event\Telemetry\HRTime $start + ): void { + if (array_key_exists($test->id(), $this->preparedTests)) { + throw new \RuntimeException('This should not happen.'); + } + + $this->preparedTests[$test->id()] = new PreparedTest( + $test, + $start + ); + } + + public function recordThatTestHasPassed( + Event\Code\Test $test, + Event\Telemetry\HRTime $end + ): void { + if (!array_key_exists($test->id(), $this->preparedTests)) { + throw new \RuntimeException('This should not happen.'); + } + + $preparedTest = $this->preparedTests[$test->id()]; + + unset($this->preparedTests[$test->id()]); + + $duration = $end->duration($preparedTest->start()); - $timeMS = $this->toMilliseconds($time); + $timeMS = $this->toMilliseconds($duration->asFloat()); $threshold = $this->getSlowThreshold($test); if ($this->isSlow($timeMS, $threshold)) { @@ -80,26 +86,12 @@ public function executeAfterSuccessfulTest(string $test, float $time): void } } - /** - * A test suite started. - */ - public function executeBeforeFirstTest(): void - { - if (!$this->enabled) return; - - $this->suites++; - } - /** * A test suite ended. */ - public function executeAfterLastTest(): void + public function showSlowTests(): void { - if (!$this->enabled) return; - - $this->suites--; - - if (0 === $this->suites && $this->hasSlowTests()) { + if ($this->hasSlowTests()) { arsort($this->slow); // Sort longest running tests to the top $this->renderHeader(); @@ -124,7 +116,7 @@ protected function isSlow(int $time, int $slowThreshold): bool * * @param int $time Test execution time that was considered slow, in milliseconds */ - protected function addSlowTest(string $test, int $time): void + protected function addSlowTest(Event\Code\Test $test, int $time): void { $label = $this->makeLabel($test); @@ -153,9 +145,15 @@ protected function toMilliseconds(float $time): int * * vendor/bin/phpunit --filter 'JohnKary\\PHPUnit\\Extension\\Tests\\SomeSlowTest::testWithDataProvider with data set "Rock"' */ - protected function makeLabel(string $test): string + protected function makeLabel(Event\Code\Test $test): string { - list($class, $testName) = explode('::', $test); + if (!$test->isTestMethod()) { + return $test->name(); + } + + /** @var Event\Code\TestMethod $test */ + $class = $test->className(); + $testName = $test->methodName(); // Remove argument list from end of string that is appended // by default \PHPUnit\Framework\TestCase->toString() so slowness report @@ -225,15 +223,6 @@ protected function renderFooter(): void } } - /** - * Populate options into class internals. - */ - protected function loadOptions(array $options): void - { - $this->slowThreshold = $options['slowThreshold'] ?? 500; - $this->reportLength = $options['reportLength'] ?? 10; - } - /** * Calculate slow test threshold for given test. A TestCase may override the * suite-wide slowness threshold by using the annotation {@slowThreshold} @@ -247,11 +236,20 @@ protected function loadOptions(array $options): void * public function testLongRunningProcess() {} * */ - protected function getSlowThreshold(string $test): int + protected function getSlowThreshold(Event\Code\Test $test): int { - list($class, $testName) = explode('::', $test); - $ann = TestUtil::parseTestMethodAnnotations($class, $testName); + if (!$test->isTestMethod()) { + return $this->slowThreshold; + } + + /** @var Event\Code\TestMethod $test */ + $docBlock = Metadata\Annotation\Parser\Registry::getInstance()->forMethod( + $test->className(), + $test->methodName() + ); + + $ann = $docBlock->symbolAnnotations(); - return isset($ann['method']['slowThreshold'][0]) ? (int) $ann['method']['slowThreshold'][0] : $this->slowThreshold; + return isset($ann['slowThreshold'][0]) ? (int) $ann['slowThreshold'][0] : $this->slowThreshold; } } diff --git a/src/SpeedTrapExtension.php b/src/SpeedTrapExtension.php new file mode 100644 index 0000000..1b69b7e --- /dev/null +++ b/src/SpeedTrapExtension.php @@ -0,0 +1,43 @@ +has('slowThreshold')) { + $slowThreshold = (int) $parameters->get('slowThreshold'); + } + + $reportLength = 10; + + if ($parameters->has('reportLength')) { + $reportLength = (int) $parameters->get('reportLength'); + } + + $speedTrap = new SpeedTrap( + $slowThreshold, + $reportLength + ); + + $facade->registerSubscribers( + new RecordThatTestHasBeenPrepared($speedTrap), + new RecordThatTestHasPassed($speedTrap), + new ShowSlowTests($speedTrap), + ); + } +}