3 // Copyright (c) 2011 Blue Static
5 // This program is free software: you can redistribute it and/or modify it
6 // under the terms of the GNU General Public License as published by the Free
7 // Software Foundation, either version 3 of the License, or any later version.
9 // This program is distributed in the hope that it will be useful, but WITHOUT
10 // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 // FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
14 // You should have received a copy of the GNU General Public License along with
15 // this program. If not, see <http://www.gnu.org/licenses/>.
17 namespace hoplite
\test
;
19 // This TestListener is meant to print to stdout and show CLI output for the
20 // running test suite. It unfortunately conflicts with the standard text runner
21 // UI, so it must be configured manually (see runner.php for an example).
23 // The format of the output is designed to mimic the Google Test (GTest)
24 // <http://googletest.googlecode.com> framework output.
25 class TestListener
extends \PHPUnit_Util_Printer
implements \PHPUnit_Framework_TestListener
29 const COLOR_GREEN
= 2;
31 const COLOR_PURPLE
= 4;
34 // The start time of the test suite.
35 private $suite_start_time = 0;
38 private $suite_depth = 0;
40 // The number of errors that occured in a suite.
41 private $suite_error_counts = 0;
43 // Array of failing tests.
44 private $failing = array();
46 // Array of skipped tests.
47 private $skipped = array();
49 // Array of incomplete tests.
50 private $incomplete = array();
53 public function addError(\PHPUnit_Framework_Test
$test,
57 $this->_Print(NULL
, $this->_ErrorLocation($e));
58 $this->_Print(' ', $e->GetMessage());
59 $this->_Print('[ ERROR ]', $test->ToString() . ' (' . $this->_Round($time) . ' ms)', self
::COLOR_RED
);
60 ++
$this->suite_error_count
;
61 $this->failing
[] = $test->ToString();
64 // A failure occurred.
65 public function addFailure(\PHPUnit_Framework_Test
$test,
66 \PHPUnit_Framework_AssertionFailedError
$e,
69 $this->_Print(NULL
, $this->_ErrorLocation($e));
70 $this->_Print(' ', $e->GetMessage());
71 if ($e instanceof \PHPUnit_Framework_ExpectationFailedException
) {
72 $comp = $e->GetComparisonFailure();
73 if ($comp instanceof \PHPUnit_Framework_ComparisonFailure
) {
74 $this->_Print(' ==> ', $comp->GetExpectedAsString());
75 $this->_Print('', 'does not match');
76 $this->_Print(' ==> ', $comp->GetActualAsString());
79 $this->_Print('[ FAILED ]', $test->ToString() . ' (' . $this->_Round($time) . ' ms)', self
::COLOR_RED
);
80 ++
$this->suite_error_count
;
81 $this->failing
[] = $test->ToString();
85 public function addIncompleteTest(\PHPUnit_Framework_Test
$test,
88 $this->incomplete
[] = $test->ToString();
89 $this->_Print('INCOMPLETE', $e->GetMessage(), self
::COLOR_PURPLE
);
93 public function addSkippedTest(\PHPUnit_Framework_Test
$test,
97 $this->skipped
[] = $test->ToString();
98 $this->_Print('SKIPPED', $e->GetMessage(), self
::COLOR_BLUE
);
101 // A test suite started.
102 public function startTestSuite(\PHPUnit_Framework_TestSuite
$suite)
104 // Wrap the suite header.
107 $this->_Print($this->_SuiteMarker(), $this->_DescribeSuite($suite), self
::COLOR_GREEN
);
108 $this->suite_start_time
= microtime(TRUE
);
109 ++
$this->suite_depth
;
110 $this->suite_error_count
= 0;
112 // Wrap the suite contents.
116 // A test suite ended.
117 public function endTestSuite(\PHPUnit_Framework_TestSuite
$suite)
119 $main_suite = (--$this->suite_depth
== 0);
120 $color_red = (($main_suite && count($this->failing
)) ||
$this->suite_error_count
> 0);
121 $any_output = ob_get_length();
123 $delta = microtime(TRUE
) - $this->suite_start_time
;
125 $this->_SuiteMarker(),
126 $this->_DescribeSuite($suite) . ' (' . $this->_Round($delta) . ' ms total)',
127 ($color_red ? self
::COLOR_RED
: self
::COLOR_GREEN
));
130 // If this is the main suite (the one to which all other tests/suites
131 // are attached), then print the test summary.
132 if ($main_suite && $color_red) {
133 $count = count($this->failing
);
134 $tests = $this->_Plural('TEST', $count, TRUE
);
135 $this->Write($this->_Color(" YOU HAVE $count FAILING $tests:\n", self
::COLOR_RED
));
136 foreach ($this->failing
as $test) {
137 $this->Write(" $test\n");
142 $count = count($this->incomplete
);
143 $any_output |
= $count > 0;
144 if ($main_suite && $count) {
145 $tests = $this->_Plural('TEST', $count, TRUE
);
146 $this->Write($this->_Color(" YOU HAVE $count INCOMPLETE $tests:\n", self
::COLOR_PURPLE
));
147 foreach ($this->incomplete
as $test) {
148 $this->Write(" $test\n");
153 $count = count($this->skipped
);
154 if ($main_suite && $count) {
155 $tests = $this->_Plural('TEST', $count, TRUE
);
156 $this->Write($this->_Color(" YOU HAVE $count SKIPPED $tests:\n", self
::COLOR_BLUE
));
157 foreach ($this->skipped
as $test) {
158 $this->Write(" $test\n");
163 // Flush the test output.
166 // Flush the suite header.
167 if ($main_suite ||
$any_output)
174 public function startTest(\PHPUnit_Framework_Test
$test)
176 $this->_Print('[ RUN ]', $test->ToString(), self
::COLOR_GREEN
);
180 public function endTest(\PHPUnit_Framework_Test
$test, $time)
182 $name = $test->ToString();
183 if (in_array($name, $this->skipped
) ||
in_array($name, $this->incomplete
)) {
184 $this->_Print('[ ABORT ]', $name . ' (' . $this->_Round($time) . ' ms)', self
::COLOR_CYAN
);
185 } else if (!in_array($name, $this->failing
)) {
186 $this->_Print('[ OK ]', $name . ' (' . $this->_Round($time) . ' ms)', self
::COLOR_GREEN
);
190 // Returns the description for a test suite.
191 private function _DescribeSuite(\PHPUnit_Framework_TestSuite
$suite)
193 $count = $suite->Count();
194 return sprintf('%d %s from %s', $count, $this->_Plural('test', $count), $suite->GetName());
197 // Returns the test suite marker.
198 private function _SuiteMarker()
200 if ($this->suite_depth
== 0)
201 return '[==========]';
203 return '[----------]';
206 // Prints a line to output.
207 private function _Print($column, $annotation, $color = self
::COLOR_NONE
)
209 $column = $this->_Color($column, $color);
210 $this->Write("$column $annotation\n");
213 // Takes in a float from microtime() and returns it formatted to display as
215 private function _Round($time)
217 return round($time * 1000);
220 // Returns the error location as a string.
221 private function _ErrorLocation(\Exception
$e)
223 $trace = $e->GetTrace();
225 // Find the first frame from non-PHPUnit code, which is where the error
226 // should have occurred.
227 foreach ($trace as $f) {
228 if (isset($f['file']) && strpos($f['file'], 'PHPUnit/Framework') === FALSE
) {
235 return $frame['file'] . ':' . $frame['line'];
238 // Colors |$str| to be a certain |$color|.
239 private function _Color($str, $color)
243 case self
::COLOR_RED
: $color_code = '0;31'; break;
244 case self
::COLOR_GREEN
: $color_code = '0;32'; break;
245 case self
::COLOR_BLUE
: $color_code = '0;34'; break;
246 case self
::COLOR_PURPLE
: $color_code = '0;35'; break;
247 case self
::COLOR_CYAN
: $color_code = '0;36'; break;
249 if ($color == self
::COLOR_NONE
) {
252 return "\x1b[{$color_code}m{$str}\x1b[0m";
255 // Returns the plural of the |$word| if |$count| is greater than one.
256 private function _Plural($word, $count, $capitalize = FALSE
)
259 return $word . ($capitalize ?
'S' : 's');