xTestRunner.php 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157
  1. <?php
  2. namespace mindplay\test\lib;
  3. use mindplay\test\lib\ResultPrinter\CliResultPrinter;
  4. use mindplay\test\lib\ResultPrinter\ResultPrinter;
  5. use mindplay\test\lib\ResultPrinter\WebResultPrinter;
  6. /**
  7. * This class implements a very simple test suite runner and code
  8. * coverage benchmarking (where supported by the xdebug extension).
  9. */
  10. class xTestRunner
  11. {
  12. protected $rootPath;
  13. /**
  14. * Code coverage information tracker.
  15. *
  16. * @var \PHP_CodeCoverage
  17. */
  18. protected $coverage;
  19. /**
  20. * Result printer.
  21. *
  22. * @var ResultPrinter
  23. */
  24. protected $resultPrinter;
  25. /**
  26. * @var boolean
  27. */
  28. protected $hasCoverage;
  29. /**
  30. * Creates result printer based on environment.
  31. *
  32. * @return ResultPrinter
  33. */
  34. public static function createResultPrinter()
  35. {
  36. if (PHP_SAPI == 'cli') {
  37. return new CliResultPrinter(new Colors());
  38. }
  39. return new WebResultPrinter();
  40. }
  41. /**
  42. * Creates test runner instance.
  43. *
  44. * @param string $rootPath The absolute path to the root folder of the test suite.
  45. * @param ResultPrinter $resultPrinter Result printer.
  46. * @throws \Exception
  47. */
  48. public function __construct($rootPath, ResultPrinter $resultPrinter)
  49. {
  50. if (!is_dir($rootPath)) {
  51. throw new \Exception("{$rootPath} is not a directory");
  52. }
  53. $this->hasCoverage = version_compare(PHP_VERSION, '8.0.0', '<');
  54. $this->rootPath = $rootPath;
  55. $this->resultPrinter = $resultPrinter;
  56. if ($this->hasCoverage) {
  57. try {
  58. $this->coverage = new \PHP_CodeCoverage();
  59. $this->coverage->filter()->addDirectoryToWhitelist($rootPath);
  60. } catch (\PHP_CodeCoverage_Exception $e) {
  61. // can't collect coverage
  62. }
  63. }
  64. }
  65. /**
  66. * Returns library root path.
  67. *
  68. * @return string
  69. */
  70. public function getRootPath()
  71. {
  72. return $this->rootPath;
  73. }
  74. /**
  75. * Starts coverage information collection for a test.
  76. *
  77. * @param string $testName Test name.
  78. * @return void
  79. */
  80. public function startCoverageCollector($testName)
  81. {
  82. if (isset($this->coverage)) {
  83. $this->coverage->start($testName);
  84. }
  85. }
  86. /**
  87. * Stops coverage information collection.
  88. *
  89. * @return void
  90. */
  91. public function stopCoverageCollector()
  92. {
  93. if (isset($this->coverage)) {
  94. $this->coverage->stop();
  95. }
  96. }
  97. /**
  98. * Runs a suite of unit tests
  99. *
  100. * @param string $directory Directory with tests.
  101. * @param string $suffix Test file suffix.
  102. * @throws \Exception When invalid test found.
  103. * @return boolean
  104. */
  105. public function run($directory, $suffix)
  106. {
  107. $this->resultPrinter->suiteHeader($this, $directory . '/*' . $suffix);
  108. $passed = true;
  109. $facade = new \File_Iterator_Facade;
  110. $old_handler = set_error_handler(array($this, 'handlePHPErrors'));
  111. foreach ($facade->getFilesAsArray($directory, $suffix) as $path) {
  112. $test = require($path);
  113. if (!$test instanceof xTest) {
  114. throw new \Exception("'{$path}' is not a valid unit test");
  115. }
  116. $test->setResultPrinter($this->resultPrinter);
  117. $passed = $passed && $test->run($this);
  118. }
  119. if ($old_handler) {
  120. set_error_handler($old_handler);
  121. } else {
  122. restore_error_handler();
  123. }
  124. if ($this->hasCoverage){
  125. $this->resultPrinter->createCodeCoverageReport($this->coverage);
  126. }
  127. $this->resultPrinter->suiteFooter($this);
  128. return $passed;
  129. }
  130. public function handlePHPErrors($errno, $errstr)
  131. {
  132. throw new xTestException($errstr, xTestException::PHP_ERROR);
  133. }
  134. }