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 89349c0..c319815 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,23 @@ Each parameter is set in `phpunit.xml`:
false
+
+
+
+ \JohnKary\PHPUnit\Listener\Renderer\NgWarningsRenderer
+
+
+
+
+ /tmp/phpunit-speedtrap-report.json
+
+
+ /project
+
+
+
+
+
diff --git a/phpunit.xml.dist b/phpunit.xml.dist
index 5665b88..6d05c03 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
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Renderer/ConsoleRenderer.php b/src/Renderer/ConsoleRenderer.php
new file mode 100644
index 0000000..fdb0aac
--- /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) {
+ [$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);
+ }
+ }
+
+ /**
+ * @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 @@
+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 310631e..082d89c 100644
--- a/src/SpeedTrapListener.php
+++ b/src/SpeedTrapListener.php
@@ -3,6 +3,9 @@
namespace JohnKary\PHPUnit\Listener;
+use Exception;
+use JohnKary\PHPUnit\Listener\Renderer\ConsoleRenderer;
+use JohnKary\PHPUnit\Listener\Renderer\ReportRendererInterface;
use PHPUnit\Framework\{TestListener, TestListenerDefaultImplementation, TestSuite, Test, TestCase};
use PHPUnit\Util\Test as TestUtil;
@@ -41,13 +44,6 @@ class SpeedTrapListener implements TestListener
*/
protected $slowThreshold;
- /**
- * Number of tests to print in slowness report.
- *
- * @var int
- */
- protected $reportLength;
-
/**
* Whether the test runner should halt running additional tests after
* finding a slow test.
@@ -56,16 +52,24 @@ class SpeedTrapListener implements TestListener
*/
protected $stopOnSlow;
+
+ /**
+ * @var SpeedTrapReport instance responsible to grab statistics
+ */
+ protected $report;
+
/**
- * Collection of slow tests.
- * Keys (string) => 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,39 @@ 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 (isset($options['reportRenderer']) && 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..d791804
--- /dev/null
+++ b/src/SpeedTrapReport.php
@@ -0,0 +1,103 @@
+ 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
+ {
+ $this->slow[] = [$test, $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);
+ }
+
+
+ /**
+ * @return int
+ */
+ public function getSlowThreshold(): int
+ {
+ return $this->slowThreshold;
+ }
+}