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 * A TestListener that generates JSON messages.
13 */
14class PHPUnit_Util_Log_JSON extends PHPUnit_Util_Printer implements PHPUnit_Framework_TestListener
15{
16    /**
17     * @var string
18     */
19    protected $currentTestSuiteName = '';
20
21    /**
22     * @var string
23     */
24    protected $currentTestName = '';
25
26    /**
27     * @var bool
28     */
29    protected $currentTestPass = true;
30
31    /**
32     * An error occurred.
33     *
34     * @param PHPUnit_Framework_Test $test
35     * @param Exception              $e
36     * @param float                  $time
37     */
38    public function addError(PHPUnit_Framework_Test $test, Exception $e, $time)
39    {
40        $this->writeCase(
41            'error',
42            $time,
43            PHPUnit_Util_Filter::getFilteredStacktrace($e, false),
44            PHPUnit_Framework_TestFailure::exceptionToString($e),
45            $test
46        );
47
48        $this->currentTestPass = false;
49    }
50
51    /**
52     * A warning occurred.
53     *
54     * @param PHPUnit_Framework_Test    $test
55     * @param PHPUnit_Framework_Warning $e
56     * @param float                     $time
57     */
58    public function addWarning(PHPUnit_Framework_Test $test, PHPUnit_Framework_Warning $e, $time)
59    {
60        $this->writeCase(
61            'warning',
62            $time,
63            PHPUnit_Util_Filter::getFilteredStacktrace($e, false),
64            PHPUnit_Framework_TestFailure::exceptionToString($e),
65            $test
66        );
67
68        $this->currentTestPass = false;
69    }
70
71    /**
72     * A failure occurred.
73     *
74     * @param PHPUnit_Framework_Test                 $test
75     * @param PHPUnit_Framework_AssertionFailedError $e
76     * @param float                                  $time
77     */
78    public function addFailure(PHPUnit_Framework_Test $test, PHPUnit_Framework_AssertionFailedError $e, $time)
79    {
80        $this->writeCase(
81            'fail',
82            $time,
83            PHPUnit_Util_Filter::getFilteredStacktrace($e, false),
84            PHPUnit_Framework_TestFailure::exceptionToString($e),
85            $test
86        );
87
88        $this->currentTestPass = false;
89    }
90
91    /**
92     * Incomplete test.
93     *
94     * @param PHPUnit_Framework_Test $test
95     * @param Exception              $e
96     * @param float                  $time
97     */
98    public function addIncompleteTest(PHPUnit_Framework_Test $test, Exception $e, $time)
99    {
100        $this->writeCase(
101            'error',
102            $time,
103            PHPUnit_Util_Filter::getFilteredStacktrace($e, false),
104            'Incomplete Test: ' . $e->getMessage(),
105            $test
106        );
107
108        $this->currentTestPass = false;
109    }
110
111    /**
112     * Risky test.
113     *
114     * @param PHPUnit_Framework_Test $test
115     * @param Exception              $e
116     * @param float                  $time
117     */
118    public function addRiskyTest(PHPUnit_Framework_Test $test, Exception $e, $time)
119    {
120        $this->writeCase(
121            'error',
122            $time,
123            PHPUnit_Util_Filter::getFilteredStacktrace($e, false),
124            'Risky Test: ' . $e->getMessage(),
125            $test
126        );
127
128        $this->currentTestPass = false;
129    }
130
131    /**
132     * Skipped test.
133     *
134     * @param PHPUnit_Framework_Test $test
135     * @param Exception              $e
136     * @param float                  $time
137     */
138    public function addSkippedTest(PHPUnit_Framework_Test $test, Exception $e, $time)
139    {
140        $this->writeCase(
141            'error',
142            $time,
143            PHPUnit_Util_Filter::getFilteredStacktrace($e, false),
144            'Skipped Test: ' . $e->getMessage(),
145            $test
146        );
147
148        $this->currentTestPass = false;
149    }
150
151    /**
152     * A testsuite started.
153     *
154     * @param PHPUnit_Framework_TestSuite $suite
155     */
156    public function startTestSuite(PHPUnit_Framework_TestSuite $suite)
157    {
158        $this->currentTestSuiteName = $suite->getName();
159        $this->currentTestName      = '';
160
161        $this->write(
162            [
163            'event' => 'suiteStart',
164            'suite' => $this->currentTestSuiteName,
165            'tests' => count($suite)
166            ]
167        );
168    }
169
170    /**
171     * A testsuite ended.
172     *
173     * @param PHPUnit_Framework_TestSuite $suite
174     */
175    public function endTestSuite(PHPUnit_Framework_TestSuite $suite)
176    {
177        $this->currentTestSuiteName = '';
178        $this->currentTestName      = '';
179    }
180
181    /**
182     * A test started.
183     *
184     * @param PHPUnit_Framework_Test $test
185     */
186    public function startTest(PHPUnit_Framework_Test $test)
187    {
188        $this->currentTestName = PHPUnit_Util_Test::describe($test);
189        $this->currentTestPass = true;
190
191        $this->write(
192            [
193            'event' => 'testStart',
194            'suite' => $this->currentTestSuiteName,
195            'test'  => $this->currentTestName
196            ]
197        );
198    }
199
200    /**
201     * A test ended.
202     *
203     * @param PHPUnit_Framework_Test $test
204     * @param float                  $time
205     */
206    public function endTest(PHPUnit_Framework_Test $test, $time)
207    {
208        if ($this->currentTestPass) {
209            $this->writeCase('pass', $time, [], '', $test);
210        }
211    }
212
213    /**
214     * @param string                          $status
215     * @param float                           $time
216     * @param array                           $trace
217     * @param string                          $message
218     * @param PHPUnit_Framework_TestCase|null $test
219     */
220    protected function writeCase($status, $time, array $trace = [], $message = '', $test = null)
221    {
222        $output = '';
223        // take care of TestSuite producing error (e.g. by running into exception) as TestSuite doesn't have hasOutput
224        if ($test !== null && method_exists($test, 'hasOutput') && $test->hasOutput()) {
225            $output = $test->getActualOutput();
226        }
227        $this->write(
228            [
229            'event'   => 'test',
230            'suite'   => $this->currentTestSuiteName,
231            'test'    => $this->currentTestName,
232            'status'  => $status,
233            'time'    => $time,
234            'trace'   => $trace,
235            'message' => PHPUnit_Util_String::convertToUtf8($message),
236            'output'  => $output,
237            ]
238        );
239    }
240
241    /**
242     * @param string $buffer
243     */
244    public function write($buffer)
245    {
246        array_walk_recursive($buffer, function (&$input) {
247            if (is_string($input)) {
248                $input = PHPUnit_Util_String::convertToUtf8($input);
249            }
250        });
251
252        parent::write(json_encode($buffer, JSON_PRETTY_PRINT));
253    }
254}
255