speed-trap-listener.php 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306
  1. <?php
  2. /**
  3. * A PHPUnit TestListener that exposes your slowest running tests by outputting
  4. * results directly to the console.
  5. */
  6. class SpeedTrapListener implements PHPUnit_Framework_TestListener {
  7. /**
  8. * Internal tracking for test suites.
  9. *
  10. * Increments as more suites are run, then decremented as they finish. All
  11. * suites have been run when returns to 0.
  12. *
  13. * @var integer
  14. */
  15. protected $suites = 0;
  16. /**
  17. * Time in milliseconds at which a test will be considered "slow" and be
  18. * reported by this listener.
  19. *
  20. * @var int
  21. */
  22. protected $slow_threshold;
  23. /**
  24. * Number of tests to report on for slowness.
  25. *
  26. * @var int
  27. */
  28. protected $report_length;
  29. /**
  30. * Collection of slow tests.
  31. *
  32. * @var array
  33. */
  34. protected $slow = array();
  35. /**
  36. * Construct a new instance.
  37. *
  38. * @param array $options
  39. */
  40. public function __construct( array $options = array() ) {
  41. $this->loadOptions( $options );
  42. }
  43. /**
  44. * An error occurred.
  45. *
  46. * @param PHPUnit_Framework_Test $test
  47. * @param Exception $e
  48. * @param float $time
  49. */
  50. public function addError( PHPUnit_Framework_Test $test, Exception $e, $time ) {
  51. }
  52. /**
  53. * A warning occurred.
  54. *
  55. * @param PHPUnit_Framework_Test $test
  56. * @param PHPUnit_Framework_Warning $e
  57. * @param float $time
  58. * @since Method available since Release 5.1.0
  59. */
  60. public function addWarning( PHPUnit_Framework_Test $test, PHPUnit_Framework_Warning $e, $time ) {
  61. }
  62. /**
  63. * A failure occurred.
  64. *
  65. * @param PHPUnit_Framework_Test $test
  66. * @param PHPUnit_Framework_AssertionFailedError $e
  67. * @param float $time
  68. */
  69. public function addFailure( PHPUnit_Framework_Test $test, PHPUnit_Framework_AssertionFailedError $e, $time ) {
  70. }
  71. /**
  72. * Incomplete test.
  73. *
  74. * @param PHPUnit_Framework_Test $test
  75. * @param Exception $e
  76. * @param float $time
  77. */
  78. public function addIncompleteTest( PHPUnit_Framework_Test $test, Exception $e, $time ) {
  79. }
  80. /**
  81. * Risky test.
  82. *
  83. * @param PHPUnit_Framework_Test $test
  84. * @param Exception $e
  85. * @param float $time
  86. * @since Method available since Release 4.0.0
  87. */
  88. public function addRiskyTest( PHPUnit_Framework_Test $test, Exception $e, $time ) {
  89. }
  90. /**
  91. * Skipped test.
  92. *
  93. * @param PHPUnit_Framework_Test $test
  94. * @param Exception $e
  95. * @param float $time
  96. */
  97. public function addSkippedTest( PHPUnit_Framework_Test $test, Exception $e, $time ) {
  98. }
  99. /**
  100. * A test started.
  101. *
  102. * @param PHPUnit_Framework_Test $test
  103. */
  104. public function startTest( PHPUnit_Framework_Test $test ) {
  105. }
  106. /**
  107. * A test ended.
  108. *
  109. * @param PHPUnit_Framework_Test $test
  110. * @param float $time
  111. */
  112. public function endTest( PHPUnit_Framework_Test $test, $time ) {
  113. if ( ! $test instanceof PHPUnit_Framework_TestCase ) {
  114. return;
  115. }
  116. $time = $this->toMilliseconds( $time );
  117. $threshold = $this->getSlowThreshold( $test );
  118. if ( $this->isSlow( $time, $threshold ) ) {
  119. $this->addSlowTest( $test, $time );
  120. }
  121. }
  122. /**
  123. * A test suite started.
  124. *
  125. * @param PHPUnit_Framework_TestSuite $suite
  126. */
  127. public function startTestSuite( PHPUnit_Framework_TestSuite $suite ) {
  128. $this->suites++;
  129. }
  130. /**
  131. * A test suite ended.
  132. *
  133. * @param PHPUnit_Framework_TestSuite $suite
  134. */
  135. public function endTestSuite( PHPUnit_Framework_TestSuite $suite ) {
  136. $this->suites--;
  137. if ( 0 === $this->suites && $this->hasSlowTests() ) {
  138. arsort( $this->slow ); // Sort longest running tests to the top.
  139. $this->renderHeader();
  140. $this->renderBody();
  141. $this->renderFooter();
  142. }
  143. }
  144. /**
  145. * Whether the given test execution time is considered slow.
  146. *
  147. * @param int $time Test execution time in milliseconds
  148. * @param int $slow_threshold Test execution time at which a test should be considered slow (milliseconds)
  149. * @return bool
  150. */
  151. protected function isSlow( $time, $slow_threshold ) {
  152. return $time >= $slow_threshold;
  153. }
  154. /**
  155. * Stores a test as slow.
  156. *
  157. * @param PHPUnit_Framework_TestCase $test
  158. * @param int $time Test execution time in milliseconds
  159. */
  160. protected function addSlowTest( PHPUnit_Framework_TestCase $test, $time ) {
  161. $label = $this->makeLabel( $test );
  162. $this->slow[ $label ] = $time;
  163. }
  164. /**
  165. * Whether at least one test has been considered slow.
  166. *
  167. * @return bool
  168. */
  169. protected function hasSlowTests() {
  170. return ! empty( $this->slow );
  171. }
  172. /**
  173. * Convert PHPUnit's reported test time (microseconds) to milliseconds.
  174. *
  175. * @param float $time
  176. * @return int
  177. */
  178. protected function toMilliseconds( $time ) {
  179. return (int) round( $time * 1000 );
  180. }
  181. /**
  182. * Label for describing a test.
  183. *
  184. * @param PHPUnit_Framework_TestCase $test
  185. * @return string
  186. */
  187. protected function makeLabel( PHPUnit_Framework_TestCase $test ) {
  188. return sprintf( '%s:%s', get_class( $test ), $test->getName() );
  189. }
  190. /**
  191. * Calculate number of slow tests to report about.
  192. *
  193. * @return int
  194. */
  195. protected function getReportLength() {
  196. return min( count( $this->slow ), $this->report_length );
  197. }
  198. /**
  199. * Find how many slow tests occurred that won't be shown due to list length.
  200. *
  201. * @return int Number of hidden slow tests
  202. */
  203. protected function getHiddenCount() {
  204. $total = count( $this->slow );
  205. $showing = $this->getReportLength( $this->slow );
  206. $hidden = 0;
  207. if ( $total > $showing ) {
  208. $hidden = $total - $showing;
  209. }
  210. return $hidden;
  211. }
  212. /**
  213. * Renders slow test report header.
  214. */
  215. protected function renderHeader() {
  216. echo sprintf( "\n\nYou should really fix these slow tests (>%sms)...\n", $this->slow_threshold );
  217. }
  218. /**
  219. * Renders slow test report body.
  220. */
  221. protected function renderBody() {
  222. $slow_tests = $this->slow;
  223. $length = $this->getReportLength( $slow_tests );
  224. for ( $i = 1; $i <= $length; ++$i ) {
  225. $label = key( $slow_tests );
  226. $time = array_shift( $slow_tests );
  227. echo sprintf( " %s. %sms to run %s\n", $i, $time, $label );
  228. }
  229. }
  230. /**
  231. * Renders slow test report footer.
  232. */
  233. protected function renderFooter() {
  234. if ( $hidden = $this->getHiddenCount( $this->slow ) ) {
  235. echo sprintf( '...and there %s %s more above your threshold hidden from view', 1 === $hidden ? 'is' : 'are', $hidden );
  236. }
  237. }
  238. /**
  239. * Populate options into class internals.
  240. *
  241. * @param array $options
  242. */
  243. protected function loadOptions( array $options ) {
  244. $this->slow_threshold = isset( $options['slowThreshold'] ) ? $options['slowThreshold'] : 500;
  245. $this->report_length = isset( $options['reportLength'] ) ? $options['reportLength'] : 10;
  246. }
  247. /**
  248. * Get slow test threshold for given test. A TestCase can override the
  249. * suite-wide slow threshold by using the annotation @slowThreshold with
  250. * the threshold value in milliseconds.
  251. *
  252. * The following test will only be considered slow when its execution time
  253. * reaches 5000ms (5 seconds):
  254. *
  255. * <code>
  256. *
  257. * @slowThreshold 5000
  258. * public function testLongRunningProcess() {}
  259. * </code>
  260. *
  261. * @param PHPUnit_Framework_TestCase $test
  262. * @return int
  263. */
  264. protected function getSlowThreshold( PHPUnit_Framework_TestCase $test ) {
  265. $ann = $test->getAnnotations();
  266. return isset( $ann['method']['slowThreshold'][0] ) ? $ann['method']['slowThreshold'][0] : $this->slow_threshold;
  267. }
  268. }