From ec5e34b2c99ecdbd7e746cd04c1898ca648582df Mon Sep 17 00:00:00 2001 From: Francois Chastanet Date: Thu, 16 Sep 2021 16:54:05 +0200 Subject: [PATCH 1/3] migrated phpunit.xml.dist as asked by phpunit --- phpunit.xml.dist | 75 ++++++++++++++++++++++++++---------------------- 1 file changed, 41 insertions(+), 34 deletions(-) diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 5665b88..61eda16 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,40 +1,47 @@ - - - - - tests - - - - - - - - - 500 - - - 5 - - - false - - - - - + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.3/phpunit.xsd" + colors="true" + bootstrap="vendor/autoload.php"> - - - src - - + + + src + + + + + tests + + + + + + + + 500 + + + 5 + + + false + + + + + \JohnKary\PHPUnit\Listener\Renderer\ConsoleRenderer + + + + + + + + + + From e8d6aae003c89fbf4c0391a5daec7e3ca359858d Mon Sep 17 00:00:00 2001 From: Francois Chastanet Date: Thu, 16 Sep 2021 18:57:26 +0200 Subject: [PATCH 2/3] add ability to specify a custom renderer --- README.md | 15 +++ phpunit.xml.dist | 2 +- src/Renderer/ConsoleRenderer.php | 57 ++++++++ src/Renderer/ReportRendererInterface.php | 31 +++++ src/SpeedTrapListener.php | 161 +++++++---------------- src/SpeedTrapReport.php | 116 ++++++++++++++++ 6 files changed, 269 insertions(+), 113 deletions(-) create mode 100644 src/Renderer/ConsoleRenderer.php create mode 100644 src/Renderer/ReportRendererInterface.php create mode 100644 src/SpeedTrapReport.php diff --git a/README.md b/README.md index 89349c0..ba844ba 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,11 @@ SpeedTrap also supports these parameters: * **slowThreshold** - Number of milliseconds when a test is considered "slow" (Default: 500ms) * **reportLength** - Number of slow tests included in the report (Default: 10 tests) * **stopOnSlow** - Stop execution upon first slow test (Default: false). Activate by setting "true". +* **reportRenderer** - Used to specify the report renderer to use (ConsoleRenderer is used by default if omitted) + * **class** the class to be used in order to render the result of SpeedTrap, for the moment 2 renderers are available + * \JohnKary\PHPUnit\Listener\Renderer\NgWarningsRenderer + * \JohnKary\PHPUnit\Listener\Renderer\ConsoleRenderer + * **options** options to pass to the renderer (this argument can be omitted if not options are needed) Each parameter is set in `phpunit.xml`: @@ -59,6 +64,16 @@ Each parameter is set in `phpunit.xml`: false + + + + \JohnKary\PHPUnit\Listener\Renderer\NgWarningsRenderer + + + + + + diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 61eda16..6d05c03 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -18,7 +18,7 @@ - + diff --git a/src/Renderer/ConsoleRenderer.php b/src/Renderer/ConsoleRenderer.php new file mode 100644 index 0000000..a394a9b --- /dev/null +++ b/src/Renderer/ConsoleRenderer.php @@ -0,0 +1,57 @@ +speedTrapReport = $speedTrapReport; + } + + /** + * @see ReportRendererInterface::renderHeader + */ + public function renderHeader(): void + { + echo sprintf( + "\n\nYou should really speed up these slow tests (>%sms)...\n", + $this->speedTrapReport->getSlowThreshold() + ); + } + + /** + * @see ReportRendererInterface::renderBody + */ + public function renderBody(): void + { + $slowTests = $this->speedTrapReport->getSlow(); + + $length = $this->speedTrapReport->getReportLength(); + for ($i = 1; $i <= $length; ++$i) { + $label = key($slowTests); + $time = array_shift($slowTests); + + echo sprintf(" %s. %sms to run %s\n", $i, $time, $label); + } + } + + /** + * @see ReportRendererInterface::renderFooter + */ + public function renderFooter(): void + { + if ($hidden = $this->speedTrapReport->getHiddenCount()) { + printf("...and there %s %s more above your threshold hidden from view\n", $hidden == 1 ? 'is' : 'are', $hidden); + } + } +} diff --git a/src/Renderer/ReportRendererInterface.php b/src/Renderer/ReportRendererInterface.php new file mode 100644 index 0000000..9595051 --- /dev/null +++ b/src/Renderer/ReportRendererInterface.php @@ -0,0 +1,31 @@ + Printable label describing the test - * Values (int) => Test execution time, in milliseconds + * @var ReportRendererInterface instance responsible to render report statistics */ - protected $slow = []; + protected $reportRenderer; + + /** + * @throws Exception + */ public function __construct(array $options = []) { - $this->enabled = getenv('PHPUNIT_SPEEDTRAP') === 'disabled' ? false : true; + $this->enabled = !(getenv('PHPUNIT_SPEEDTRAP') === 'disabled'); $this->loadOptions($options); } @@ -85,7 +89,10 @@ public function endTest(Test $test, float $time): void $threshold = $this->getSlowThreshold($test); if ($this->isSlow($timeInMilliseconds, $threshold)) { - $this->addSlowTest($test, $timeInMilliseconds); + $this->report->addSlowTest($test, $timeInMilliseconds); + if ($this->stopOnSlow) { + $test->getTestResultObject()->stop(); + } } } @@ -112,12 +119,10 @@ public function endTestSuite(TestSuite $suite): void $this->suites--; - if (0 === $this->suites && $this->hasSlowTests()) { - arsort($this->slow); // Sort longest running tests to the top - - $this->renderHeader(); - $this->renderBody(); - $this->renderFooter(); + if (0 === $this->suites && $this->report->hasSlowTests()) { + $this->reportRenderer->renderHeader(); + $this->reportRenderer->renderBody(); + $this->reportRenderer->renderFooter(); } } @@ -132,28 +137,6 @@ protected function isSlow(int $time, int $slowThreshold): bool return $slowThreshold && $time >= $slowThreshold; } - /** - * Stores a test as slow. - */ - protected function addSlowTest(TestCase $test, int $time) - { - $label = $this->makeLabel($test); - - $this->slow[$label] = $time; - - if ($this->stopOnSlow) { - $test->getTestResultObject()->stop(); - } - } - - /** - * Whether at least one test has been considered slow. - */ - protected function hasSlowTests(): bool - { - return !empty($this->slow); - } - /** * Convert PHPUnit's reported test time (microseconds) to milliseconds. */ @@ -162,84 +145,38 @@ protected function toMilliseconds(float $time): int return (int) round($time * 1000); } - /** - * Label describing a slow test case. Formatted to support copy/paste with - * PHPUnit's --filter CLI option: - * - * vendor/bin/phpunit --filter 'JohnKary\\PHPUnit\\Listener\\Tests\\SomeSlowTest::testWithDataProvider with data set "Rock"' - */ - protected function makeLabel(TestCase $test): string - { - return sprintf('%s::%s', addslashes(get_class($test)), $test->getName()); - } - - /** - * Calculate number of tests to include in slowness report. - */ - protected function getReportLength(): int - { - return min(count($this->slow), $this->reportLength); - } - - /** - * Calculate number of slow tests to be hidden from the slowness report - * due to list length. - */ - protected function getHiddenCount(): int - { - $total = count($this->slow); - $showing = $this->getReportLength(); - - $hidden = 0; - if ($total > $showing) { - $hidden = $total - $showing; - } - - return $hidden; - } - - /** - * Renders slowness report header. - */ - protected function renderHeader() - { - echo sprintf("\n\nYou should really speed up these slow tests (>%sms)...\n", $this->slowThreshold); - } - - /** - * Renders slowness report body. - */ - protected function renderBody() - { - $slowTests = $this->slow; - - $length = $this->getReportLength(); - for ($i = 1; $i <= $length; ++$i) { - $label = key($slowTests); - $time = array_shift($slowTests); - - echo sprintf(" %s. %sms to run %s\n", $i, $time, $label); - } - } - - /** - * Renders slowness report footer. - */ - protected function renderFooter() - { - if ($hidden = $this->getHiddenCount()) { - printf("...and there %s %s more above your threshold hidden from view\n", $hidden == 1 ? 'is' : 'are', $hidden); - } - } - /** * Populate options into class internals. + * + * @throws Exception */ protected function loadOptions(array $options) { $this->slowThreshold = $options['slowThreshold'] ?? 500; - $this->reportLength = $options['reportLength'] ?? 10; $this->stopOnSlow = $options['stopOnSlow'] ?? false; + $this->report = new SpeedTrapReport( + $options['reportLength'] ?? 10, + $this->slowThreshold + ); + if (is_array($options['reportRenderer'])) { + if (empty($options['reportRenderer']['class'])) { + throw new Exception('option reportRenderer - missing class option'); + } + $reportRendererClass = $options['reportRenderer']['class']; + if (!class_exists($reportRendererClass)) { + throw new Exception("option reportRenderer class - class $reportRendererClass does not exists"); + } + $reportRendererClassInterfaces = class_implements($reportRendererClass); + if (!isset($reportRendererClassInterfaces[ReportRendererInterface::class])) { + throw new Exception( + "option reportRenderer class - class $reportRendererClass does not implement interface " + . ReportRendererInterface::class + ); + } + $this->reportRenderer = new $reportRendererClass($this->report, $options['reportRenderer']['options'] ?? []); + } else { + $this->reportRenderer = new ConsoleRenderer($this->report, []); + } } /** diff --git a/src/SpeedTrapReport.php b/src/SpeedTrapReport.php new file mode 100644 index 0000000..aa7d41d --- /dev/null +++ b/src/SpeedTrapReport.php @@ -0,0 +1,116 @@ + Printable label describing the test + * Values (int) => Test execution time, in milliseconds + */ + protected $slow = []; + + + /** + * Number of tests to print in slowness report. + * + * @var int + */ + protected $reportLength; + + /** + * Test execution time (milliseconds) after which a test will be considered + * "slow" and be included in the slowness report. + * + * @var int + */ + protected $slowThreshold; + + /** + * @param int $reportLength Number of tests to print in slowness report. + */ + public function __construct(int $reportLength, int $slowThreshold) { + $this->reportLength = $reportLength; + $this->slowThreshold = $slowThreshold; + } + + /** + * Whether at least one test has been considered slow. + */ + public function hasSlowTests(): bool + { + return !empty($this->slow); + } + + /** + * @return array + */ + public function getSlow(): array + { + arsort($this->slow); // Sort longest running tests to the top + return $this->slow; + } + + /** + * Stores a test as slow. + */ + public function addSlowTest(TestCase $test, int $time): void + { + $label = $this->makeLabel($test); + + $this->slow[$label] = $time; + } + + /** + * Calculate number of slow tests to be hidden from the slowness report + * due to list length. + */ + public function getHiddenCount(): int + { + $total = count($this->slow); + $showing = $this->getReportLength(); + + $hidden = 0; + if ($total > $showing) { + $hidden = $total - $showing; + } + + return $hidden; + } + + /** + * Calculate number of tests to include in slowness report. + */ + public function getReportLength(): int + { + if ($this->reportLength === -1) { + return count($this->slow); + } + return min(count($this->slow), $this->reportLength); + } + + + /** + * Label describing a slow test case. Formatted to support copy/paste with + * PHPUnit's --filter CLI option: + * + * vendor/bin/phpunit --filter 'JohnKary\\PHPUnit\\Listener\\Tests\\SomeSlowTest::testWithDataProvider with data set "Rock"' + */ + protected function makeLabel(TestCase $test): string + { + return sprintf('%s::%s', addslashes(get_class($test)), $test->getName()); + } + + /** + * @return int + */ + public function getSlowThreshold(): int + { + return $this->slowThreshold; + } +} From 99d57b4011a942beab1c9e4a6b306d0afec1f08a Mon Sep 17 00:00:00 2001 From: Francois Chastanet Date: Thu, 16 Sep 2021 21:33:15 +0200 Subject: [PATCH 3/3] added WarningsNgRenderer.php --- .gitignore | 2 + README.md | 9 ++- src/Renderer/ConsoleRenderer.php | 4 +- src/Renderer/WarningsNgRenderer.php | 113 ++++++++++++++++++++++++++++ src/SpeedTrapListener.php | 3 +- src/SpeedTrapReport.php | 15 +--- 6 files changed, 128 insertions(+), 18 deletions(-) create mode 100644 src/Renderer/WarningsNgRenderer.php diff --git a/.gitignore b/.gitignore index 238a9c6..2f94176 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ /phpunit.xml /vendor /composer.lock +/.phpunit.result.cache +/.idea diff --git a/README.md b/README.md index ba844ba..c319815 100644 --- a/README.md +++ b/README.md @@ -70,7 +70,14 @@ Each parameter is set in `phpunit.xml`: \JohnKary\PHPUnit\Listener\Renderer\NgWarningsRenderer - + + + /tmp/phpunit-speedtrap-report.json + + + /project + + diff --git a/src/Renderer/ConsoleRenderer.php b/src/Renderer/ConsoleRenderer.php index a394a9b..fdb0aac 100644 --- a/src/Renderer/ConsoleRenderer.php +++ b/src/Renderer/ConsoleRenderer.php @@ -38,8 +38,8 @@ public function renderBody(): void $length = $this->speedTrapReport->getReportLength(); for ($i = 1; $i <= $length; ++$i) { - $label = key($slowTests); - $time = array_shift($slowTests); + [$testCase, $time] = array_shift($slowTests); + $label = sprintf('%s::%s', addslashes(get_class($testCase)), $testCase->getName()); echo sprintf(" %s. %sms to run %s\n", $i, $time, $label); } diff --git a/src/Renderer/WarningsNgRenderer.php b/src/Renderer/WarningsNgRenderer.php new file mode 100644 index 0000000..68b76cf --- /dev/null +++ b/src/Renderer/WarningsNgRenderer.php @@ -0,0 +1,113 @@ +speedTrapReport = $speedTrapReport; + $this->data = []; + if ( + !isset($options['file']) + || !is_string($options['file']) + ) { + throw new Exception( + 'WarningsNgRenderer - invalid filepath provided' + ); + } + if (!is_writable(dirname($options['file']))) + { + throw new Exception( + 'WarningsNgRenderer - parent directory should be writable ' + . $options['file'] + ); + } + $this->targetFile = $options['file']; + $this->projectBaseDir = $options['projectBaseDir'] ?? false; + if ($this->projectBaseDir) { + if (is_dir($this->projectBaseDir)) { + $this->projectBaseDir = realpath($this->projectBaseDir); + } else { + throw new Exception( + 'WarningsNgRenderer - project base directory should exist' + .$options['file'] + ); + } + } + } + + /** + * @see ReportRendererInterface::renderHeader + */ + public function renderHeader(): void + { + $this->data['_class'] = 'io.jenkins.plugins.analysis.core.restapi.ReportApi'; + $this->data['issues'] = []; + } + + /** + * @see ReportRendererInterface::renderBody + */ + public function renderBody(): void + { + $slowTests = $this->speedTrapReport->getSlow(); + + $length = count($slowTests); + $this->data['size'] = $length; + for ($i = 1; $i <= $length; ++$i) { + /** @var TestCase $testCase */ + [$testCase, $time] = array_shift($slowTests); + $label = sprintf("%sms to run %s", $time, $testCase->getName(true)); + + $testCaseClass = new \ReflectionClass(get_class($testCase)); + $fileName = $testCaseClass->getFileName(); + if ($this->projectBaseDir) { + $fileName = str_replace($this->projectBaseDir, '.', $fileName); + } + + $this->data['issues'][] = [ + "fileName" => $fileName, + "packageName" => $testCaseClass->getNamespaceName(), + "message" => $label, + "severity" => "HIGH", + "duration" => $time, + ]; + } + } + + /** + * @see ReportRendererInterface::renderFooter + */ + public function renderFooter(): void + { + // write the file + file_put_contents($this->targetFile, json_encode($this->data, JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES)); + } + +} diff --git a/src/SpeedTrapListener.php b/src/SpeedTrapListener.php index 7606097..082d89c 100644 --- a/src/SpeedTrapListener.php +++ b/src/SpeedTrapListener.php @@ -158,7 +158,8 @@ protected function loadOptions(array $options) $options['reportLength'] ?? 10, $this->slowThreshold ); - if (is_array($options['reportRenderer'])) { + + if (isset($options['reportRenderer']) && is_array($options['reportRenderer'])) { if (empty($options['reportRenderer']['class'])) { throw new Exception('option reportRenderer - missing class option'); } diff --git a/src/SpeedTrapReport.php b/src/SpeedTrapReport.php index aa7d41d..d791804 100644 --- a/src/SpeedTrapReport.php +++ b/src/SpeedTrapReport.php @@ -61,9 +61,7 @@ public function getSlow(): array */ public function addSlowTest(TestCase $test, int $time): void { - $label = $this->makeLabel($test); - - $this->slow[$label] = $time; + $this->slow[] = [$test, $time]; } /** @@ -95,17 +93,6 @@ public function getReportLength(): int } - /** - * Label describing a slow test case. Formatted to support copy/paste with - * PHPUnit's --filter CLI option: - * - * vendor/bin/phpunit --filter 'JohnKary\\PHPUnit\\Listener\\Tests\\SomeSlowTest::testWithDataProvider with data set "Rock"' - */ - protected function makeLabel(TestCase $test): string - { - return sprintf('%s::%s', addslashes(get_class($test)), $test->getName()); - } - /** * @return int */