1<?php
2/*
3 * This file is part of PHPUnit.
4 *
5 * (c) Sebastian Bergmann <sebastian@phpunit.de>
6 *
7 * For the full copyright and license information, please view the LICENSE
8 * file that was distributed with this source code.
9 */
10
11/**
12 * Base class for printers of TestDox documentation.
13 */
14abstract class PHPUnit_Util_TestDox_ResultPrinter extends PHPUnit_Util_Printer implements PHPUnit_Framework_TestListener
15{
16    /**
17     * @var PHPUnit_Util_TestDox_NamePrettifier
18     */
19    protected $prettifier;
20
21    /**
22     * @var string
23     */
24    protected $testClass = '';
25
26    /**
27     * @var int
28     */
29    protected $testStatus = false;
30
31    /**
32     * @var array
33     */
34    protected $tests = [];
35
36    /**
37     * @var int
38     */
39    protected $successful = 0;
40
41    /**
42     * @var int
43     */
44    protected $warned = 0;
45
46    /**
47     * @var int
48     */
49    protected $failed = 0;
50
51    /**
52     * @var int
53     */
54    protected $risky = 0;
55
56    /**
57     * @var int
58     */
59    protected $skipped = 0;
60
61    /**
62     * @var int
63     */
64    protected $incomplete = 0;
65
66    /**
67     * @var string
68     */
69    protected $currentTestClassPrettified;
70
71    /**
72     * @var string
73     */
74    protected $currentTestMethodPrettified;
75
76    /**
77     * @var array
78     */
79    private $groups;
80
81    /**
82     * @var array
83     */
84    private $excludeGroups;
85
86    /**
87     * @param resource $out
88     * @param array    $groups
89     * @param array    $excludeGroups
90     */
91    public function __construct($out = null, array $groups = [], array $excludeGroups = [])
92    {
93        parent::__construct($out);
94
95        $this->groups        = $groups;
96        $this->excludeGroups = $excludeGroups;
97
98        $this->prettifier = new PHPUnit_Util_TestDox_NamePrettifier;
99        $this->startRun();
100    }
101
102    /**
103     * Flush buffer and close output.
104     */
105    public function flush()
106    {
107        $this->doEndClass();
108        $this->endRun();
109
110        parent::flush();
111    }
112
113    /**
114     * An error occurred.
115     *
116     * @param PHPUnit_Framework_Test $test
117     * @param Exception              $e
118     * @param float                  $time
119     */
120    public function addError(PHPUnit_Framework_Test $test, Exception $e, $time)
121    {
122        if (!$this->isOfInterest($test)) {
123            return;
124        }
125
126        $this->testStatus = PHPUnit_Runner_BaseTestRunner::STATUS_ERROR;
127        $this->failed++;
128    }
129
130    /**
131     * A warning occurred.
132     *
133     * @param PHPUnit_Framework_Test    $test
134     * @param PHPUnit_Framework_Warning $e
135     * @param float                     $time
136     */
137    public function addWarning(PHPUnit_Framework_Test $test, PHPUnit_Framework_Warning $e, $time)
138    {
139        if (!$this->isOfInterest($test)) {
140            return;
141        }
142
143        $this->testStatus = PHPUnit_Runner_BaseTestRunner::STATUS_WARNING;
144        $this->warned++;
145    }
146
147    /**
148     * A failure occurred.
149     *
150     * @param PHPUnit_Framework_Test                 $test
151     * @param PHPUnit_Framework_AssertionFailedError $e
152     * @param float                                  $time
153     */
154    public function addFailure(PHPUnit_Framework_Test $test, PHPUnit_Framework_AssertionFailedError $e, $time)
155    {
156        if (!$this->isOfInterest($test)) {
157            return;
158        }
159
160        $this->testStatus = PHPUnit_Runner_BaseTestRunner::STATUS_FAILURE;
161        $this->failed++;
162    }
163
164    /**
165     * Incomplete test.
166     *
167     * @param PHPUnit_Framework_Test $test
168     * @param Exception              $e
169     * @param float                  $time
170     */
171    public function addIncompleteTest(PHPUnit_Framework_Test $test, Exception $e, $time)
172    {
173        if (!$this->isOfInterest($test)) {
174            return;
175        }
176
177        $this->testStatus = PHPUnit_Runner_BaseTestRunner::STATUS_INCOMPLETE;
178        $this->incomplete++;
179    }
180
181    /**
182     * Risky test.
183     *
184     * @param PHPUnit_Framework_Test $test
185     * @param Exception              $e
186     * @param float                  $time
187     */
188    public function addRiskyTest(PHPUnit_Framework_Test $test, Exception $e, $time)
189    {
190        if (!$this->isOfInterest($test)) {
191            return;
192        }
193
194        $this->testStatus = PHPUnit_Runner_BaseTestRunner::STATUS_RISKY;
195        $this->risky++;
196    }
197
198    /**
199     * Skipped test.
200     *
201     * @param PHPUnit_Framework_Test $test
202     * @param Exception              $e
203     * @param float                  $time
204     */
205    public function addSkippedTest(PHPUnit_Framework_Test $test, Exception $e, $time)
206    {
207        if (!$this->isOfInterest($test)) {
208            return;
209        }
210
211        $this->testStatus = PHPUnit_Runner_BaseTestRunner::STATUS_SKIPPED;
212        $this->skipped++;
213    }
214
215    /**
216     * A testsuite started.
217     *
218     * @param PHPUnit_Framework_TestSuite $suite
219     */
220    public function startTestSuite(PHPUnit_Framework_TestSuite $suite)
221    {
222    }
223
224    /**
225     * A testsuite ended.
226     *
227     * @param PHPUnit_Framework_TestSuite $suite
228     */
229    public function endTestSuite(PHPUnit_Framework_TestSuite $suite)
230    {
231    }
232
233    /**
234     * A test started.
235     *
236     * @param PHPUnit_Framework_Test $test
237     */
238    public function startTest(PHPUnit_Framework_Test $test)
239    {
240        if (!$this->isOfInterest($test)) {
241            return;
242        }
243
244        $class = get_class($test);
245
246        if ($this->testClass != $class) {
247            if ($this->testClass != '') {
248                $this->doEndClass();
249            }
250
251            $classAnnotations = PHPUnit_Util_Test::parseTestMethodAnnotations($class);
252            if (isset($classAnnotations['class']['testdox'][0])) {
253                $this->currentTestClassPrettified = $classAnnotations['class']['testdox'][0];
254            } else {
255                $this->currentTestClassPrettified = $this->prettifier->prettifyTestClass($class);
256            }
257
258            $this->startClass($class);
259
260            $this->testClass = $class;
261            $this->tests     = [];
262        }
263
264        $annotations = $test->getAnnotations();
265
266        if (isset($annotations['method']['testdox'][0])) {
267            $this->currentTestMethodPrettified = $annotations['method']['testdox'][0];
268        } else {
269            $this->currentTestMethodPrettified = $this->prettifier->prettifyTestMethod($test->getName(false));
270        }
271
272        if ($test instanceof PHPUnit_Framework_TestCase && $test->usesDataProvider()) {
273            $this->currentTestMethodPrettified .= ' ' . $test->dataDescription();
274        }
275
276        $this->testStatus = PHPUnit_Runner_BaseTestRunner::STATUS_PASSED;
277    }
278
279    /**
280     * A test ended.
281     *
282     * @param PHPUnit_Framework_Test $test
283     * @param float                  $time
284     */
285    public function endTest(PHPUnit_Framework_Test $test, $time)
286    {
287        if (!$this->isOfInterest($test)) {
288            return;
289        }
290
291        if (!isset($this->tests[$this->currentTestMethodPrettified])) {
292            if ($this->testStatus == PHPUnit_Runner_BaseTestRunner::STATUS_PASSED) {
293                $this->tests[$this->currentTestMethodPrettified]['success'] = 1;
294                $this->tests[$this->currentTestMethodPrettified]['failure'] = 0;
295            } else {
296                $this->tests[$this->currentTestMethodPrettified]['success'] = 0;
297                $this->tests[$this->currentTestMethodPrettified]['failure'] = 1;
298            }
299        } else {
300            if ($this->testStatus == PHPUnit_Runner_BaseTestRunner::STATUS_PASSED) {
301                $this->tests[$this->currentTestMethodPrettified]['success']++;
302            } else {
303                $this->tests[$this->currentTestMethodPrettified]['failure']++;
304            }
305        }
306
307        $this->currentTestClassPrettified  = null;
308        $this->currentTestMethodPrettified = null;
309    }
310
311    protected function doEndClass()
312    {
313        foreach ($this->tests as $name => $data) {
314            $this->onTest($name, $data['failure'] == 0);
315        }
316
317        $this->endClass($this->testClass);
318    }
319
320    /**
321     * Handler for 'start run' event.
322     */
323    protected function startRun()
324    {
325    }
326
327    /**
328     * Handler for 'start class' event.
329     *
330     * @param string $name
331     */
332    protected function startClass($name)
333    {
334    }
335
336    /**
337     * Handler for 'on test' event.
338     *
339     * @param string $name
340     * @param bool   $success
341     */
342    protected function onTest($name, $success = true)
343    {
344    }
345
346    /**
347     * Handler for 'end class' event.
348     *
349     * @param string $name
350     */
351    protected function endClass($name)
352    {
353    }
354
355    /**
356     * Handler for 'end run' event.
357     */
358    protected function endRun()
359    {
360    }
361
362    /**
363     * @param PHPUnit_Framework_Test $test
364     *
365     * @return bool
366     */
367    private function isOfInterest(PHPUnit_Framework_Test $test)
368    {
369        if (!$test instanceof PHPUnit_Framework_TestCase) {
370            return false;
371        }
372
373        if ($test instanceof PHPUnit_Framework_WarningTestCase) {
374            return false;
375        }
376
377        if (!empty($this->groups)) {
378            foreach ($test->getGroups() as $group) {
379                if (in_array($group, $this->groups)) {
380                    return true;
381                }
382            }
383
384            return false;
385        }
386
387        if (!empty($this->excludeGroups)) {
388            foreach ($test->getGroups() as $group) {
389                if (in_array($group, $this->excludeGroups)) {
390                    return false;
391                }
392            }
393
394            return true;
395        }
396
397        return true;
398    }
399}
400