Middleware no longer takes a Pipeline.
[hoplite.git] / testing / test_listener.php
1 <?php
2 // Hoplite
3 // Copyright (c) 2011 Blue Static
4 //
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.
8 //
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
12 // more details.
13 //
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/>.
16
17 namespace hoplite\test;
18
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).
22 //
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
26 {
27 const COLOR_NONE = 0;
28 const COLOR_RED = 1;
29 const COLOR_GREEN = 2;
30 const COLOR_BLUE = 3;
31 const COLOR_PURPLE = 4;
32 const COLOR_CYAN = 5;
33
34 // The start time of the test suite.
35 private $suite_start_time = 0;
36
37 // The suite depth.
38 private $suite_depth = 0;
39
40 // The number of errors that occured in a suite.
41 private $suite_error_counts = 0;
42
43 // Array of failing tests.
44 private $failing = array();
45
46 // Array of skipped tests.
47 private $skipped = array();
48
49 // Array of incomplete tests.
50 private $incomplete = array();
51 private $risky = array();
52
53 // An error occurred.
54 public function addError(\PHPUnit_Framework_Test $test,
55 \Exception $e,
56 $time)
57 {
58 $this->_Print(NULL, $this->_ErrorLocation($e));
59 $this->_Print(' ', $e->GetMessage());
60 $this->_Print('[ ERROR ]', $test->ToString() . ' (' . $this->_Round($time) . ' ms)', self::COLOR_RED);
61 ++$this->suite_error_count;
62 $this->failing[] = $test->ToString();
63 }
64
65 // A failure occurred.
66 public function addFailure(\PHPUnit_Framework_Test $test,
67 \PHPUnit_Framework_AssertionFailedError $e,
68 $time)
69 {
70 $this->_Print(NULL, $this->_ErrorLocation($e));
71 $this->_Print(' ', $e->GetMessage());
72 if ($e instanceof \PHPUnit_Framework_ExpectationFailedException) {
73 $comp = $e->GetComparisonFailure();
74 if ($comp) {
75 $this->_Print(' ==> ', $comp->GetExpectedAsString());
76 $this->_Print('', 'does not match');
77 $this->_Print(' ==> ', $comp->GetActualAsString());
78 }
79 }
80 $this->_Print('[ FAILED ]', $test->ToString() . ' (' . $this->_Round($time) . ' ms)', self::COLOR_RED);
81 ++$this->suite_error_count;
82 $this->failing[] = $test->ToString();
83 }
84
85 // Risky test.
86 public function addRiskyTest(\PHPUnit_Framework_Test $test,
87 \Exception $e, $time)
88 {
89 $this->risky[] = $test->ToString();
90 $this->_Print('RISKY', $e->GetMessage(), self::COLOR_PURPLE);
91 }
92
93 // Incomplete test.
94 public function addIncompleteTest(\PHPUnit_Framework_Test $test,
95 \Exception $e, $time)
96 {
97 $this->incomplete[] = $test->ToString();
98 $this->_Print('INCOMPLETE', $e->GetMessage(), self::COLOR_PURPLE);
99 }
100
101 // Skipped test.
102 public function addSkippedTest(\PHPUnit_Framework_Test $test,
103 \Exception $e,
104 $time)
105 {
106 $this->skipped[] = $test->ToString();
107 $this->_Print('SKIPPED', $e->GetMessage(), self::COLOR_BLUE);
108 }
109
110 // A test suite started.
111 public function startTestSuite(\PHPUnit_Framework_TestSuite $suite)
112 {
113 // Wrap the suite header.
114 ob_start();
115
116 $this->_Print($this->_SuiteMarker(), $this->_DescribeSuite($suite), self::COLOR_GREEN);
117 $this->suite_start_time = microtime(TRUE);
118 ++$this->suite_depth;
119 $this->suite_error_count = 0;
120
121 // Wrap the suite contents.
122 ob_start();
123 }
124
125 // A test suite ended.
126 public function endTestSuite(\PHPUnit_Framework_TestSuite $suite)
127 {
128 $main_suite = (--$this->suite_depth == 0);
129 $color_red = (($main_suite && count($this->failing)) || $this->suite_error_count > 0);
130 $any_output = ob_get_length();
131
132 $delta = microtime(TRUE) - $this->suite_start_time;
133 $this->_Print(
134 $this->_SuiteMarker(),
135 $this->_DescribeSuite($suite) . ' (' . $this->_Round($delta) . ' ms total)',
136 ($color_red ? self::COLOR_RED : self::COLOR_GREEN));
137 $this->Write("\n");
138
139 // If this is the main suite (the one to which all other tests/suites
140 // are attached), then print the test summary.
141 if ($main_suite && $color_red) {
142 $count = count($this->failing);
143 $tests = $this->_Plural('TEST', $count, TRUE);
144 $this->Write($this->_Color(" YOU HAVE $count FAILING $tests:\n", self::COLOR_RED));
145 foreach ($this->failing as $test) {
146 $this->Write(" $test\n");
147 }
148 $this->Write("\n");
149 }
150
151 $count = count($this->incomplete);
152 $any_output |= $count > 0;
153 if ($main_suite && $count) {
154 $tests = $this->_Plural('TEST', $count, TRUE);
155 $this->Write($this->_Color(" YOU HAVE $count INCOMPLETE $tests:\n", self::COLOR_PURPLE));
156 foreach ($this->incomplete as $test) {
157 $this->Write(" $test\n");
158 }
159 $this->Write("\n");
160 }
161
162 $count = count($this->skipped);
163 if ($main_suite && $count) {
164 $tests = $this->_Plural('TEST', $count, TRUE);
165 $this->Write($this->_Color(" YOU HAVE $count SKIPPED $tests:\n", self::COLOR_BLUE));
166 foreach ($this->skipped as $test) {
167 $this->Write(" $test\n");
168 }
169 $this->Write("\n");
170 }
171
172 // Flush the test output.
173 ob_end_flush();
174
175 // Flush the suite header.
176 if ($main_suite || $any_output)
177 ob_end_flush();
178 else
179 ob_end_clean();
180 }
181
182 // A test started.
183 public function startTest(\PHPUnit_Framework_Test $test)
184 {
185 $this->_Print('[ RUN ]', $test->ToString(), self::COLOR_GREEN);
186 }
187
188 // A test ended.
189 public function endTest(\PHPUnit_Framework_Test $test, $time)
190 {
191 $name = $test->ToString();
192 if (in_array($name, $this->skipped) || in_array($name, $this->incomplete)) {
193 $this->_Print('[ ABORT ]', $name . ' (' . $this->_Round($time) . ' ms)', self::COLOR_CYAN);
194 } else if (!in_array($name, $this->failing)) {
195 $this->_Print('[ OK ]', $name . ' (' . $this->_Round($time) . ' ms)', self::COLOR_GREEN);
196 }
197 }
198
199 // Returns the description for a test suite.
200 private function _DescribeSuite(\PHPUnit_Framework_TestSuite $suite)
201 {
202 $count = $suite->Count();
203 return sprintf('%d %s from %s', $count, $this->_Plural('test', $count), $suite->GetName());
204 }
205
206 // Returns the test suite marker.
207 private function _SuiteMarker()
208 {
209 if ($this->suite_depth == 0)
210 return '[==========]';
211 else
212 return '[----------]';
213 }
214
215 // Prints a line to output.
216 private function _Print($column, $annotation, $color = self::COLOR_NONE)
217 {
218 $column = $this->_Color($column, $color);
219 $this->Write("$column $annotation\n");
220 }
221
222 // Takes in a float from microtime() and returns it formatted to display as
223 // milliseconds.
224 private function _Round($time)
225 {
226 return round($time * 1000);
227 }
228
229 // Returns the error location as a string.
230 private function _ErrorLocation(\Exception $e)
231 {
232 $trace = $e->GetTrace();
233 $frame = NULL;
234 // Find the first frame from non-PHPUnit code, which is where the error
235 // should have occurred.
236 foreach ($trace as $f) {
237 if (isset($f['file']) && strpos($f['file'], 'PHPUnit/Framework') === FALSE) {
238 $frame = $f;
239 break;
240 }
241 }
242 if (!$frame)
243 $frame = $trace[0];
244 return $frame['file'] . ':' . $frame['line'];
245 }
246
247 // Colors |$str| to be a certain |$color|.
248 private function _Color($str, $color)
249 {
250 $color_code = '';
251 switch ($color) {
252 case self::COLOR_RED: $color_code = '0;31'; break;
253 case self::COLOR_GREEN: $color_code = '0;32'; break;
254 case self::COLOR_BLUE: $color_code = '0;34'; break;
255 case self::COLOR_PURPLE: $color_code = '0;35'; break;
256 case self::COLOR_CYAN: $color_code = '0;36'; break;
257 }
258 if ($color == self::COLOR_NONE) {
259 return $str;
260 }
261 return "\x1b[{$color_code}m{$str}\x1b[0m";
262 }
263
264 // Returns the plural of the |$word| if |$count| is greater than one.
265 private function _Plural($word, $count, $capitalize = FALSE)
266 {
267 if ($count > 1)
268 return $word . ($capitalize ? 'S' : 's');
269 return $word;
270 }
271 }