NOBUG standardising prevention of output buffering
[moodle.git] / admin / report / unittest / ex_reporter.php
CommitLineData
3ef8c936 1<?php
2/**
3 * A SimpleTest report format for Moodle.
4 *
5 * @copyright &copy; 2006 The Open University
6 * @author N.D.Freear@open.ac.uk, T.J.Hunt@open.ac.uk
7 * @license http://www.gnu.org/copyleft/gpl.html GNU Public License
3ef8c936 8 * @package SimpleTestEx
9 */
10
cdbbd26f 11if (!defined('MOODLE_INTERNAL')) {
12 die('Direct access to this script is forbidden.'); /// It must be included from a Moodle page
13}
14
3ef8c936 15require_once($CFG->libdir . '/simpletestlib/reporter.php');
16
17/**
18 * Extended in-browser test displayer. HtmlReporter generates
19 * only failure messages and a pass count. ExHtmlReporter also
20 * generates pass messages and a time-stamp.
21 *
22 * @package SimpleTestEx
23 */
24class ExHtmlReporter extends HtmlReporter {
25
26 // Options set when the class is created.
27 var $showpasses;
eef868d1 28
3ef8c936 29 // Lang strings. Set in the constructor.
30 var $strrunonlyfolder;
31 var $strrunonlyfile;
eef868d1 32
3ef8c936 33 var $strseparator;
eef868d1 34
4ab83611 35 var $timestart;
36
3ef8c936 37 /**
38 * Constructor.
eef868d1 39 *
3ef8c936 40 * @param bool $showpasses Whether this reporter should output anything for passes.
41 */
42 function ExHtmlReporter($showpasses) {
3ef8c936 43 $this->HtmlReporter();
44 $this->showpasses = $showpasses;
eef868d1 45
3ef8c936 46 $this->strrunonlyfolder = $this->get_string('runonlyfolder');
47 $this->strrunonlyfile = $this->get_string('runonlyfile');
a84dea2c 48 $this->strseparator = get_separator();
3ef8c936 49 }
50
51 /**
52 * Called when a pass needs to be output.
53 */
54 function paintPass($message) {
55 //(Implicitly call grandparent, as parent not implemented.)
56 parent::paintPass($message);
57 if ($this->showpasses) {
58 $this->_paintPassFail('pass', $message);
59 }
60 }
61
62 /**
63 * Called when a fail needs to be output.
64 */
65 function paintFail($message) {
66 // Explicitly call grandparent, not parent::paintFail.
67 SimpleScorer::paintFail($message);
39321e0f 68 $this->_paintPassFail('fail', $message, debug_backtrace());
3ef8c936 69 }
70
2319bded
EL
71 /**
72 * Called when a skip needs to be output.
73 */
74 function paintSkip($message) {
75 // Explicitly call grandparent, not parent::paintFail.
76 SimpleScorer::paintSkip($message);
77 $this->_paintPassFail('skip', $message);
78 }
79
3ef8c936 80 /**
81 * Called when an error (uncaught exception or PHP error) needs to be output.
82 */
83 function paintError($message) {
39321e0f 84 // Explicitly call grandparent, not parent::paintError.
3ef8c936 85 SimpleScorer::paintError($message);
86 $this->_paintPassFail('exception', $message);
87 }
88
39321e0f
TH
89 /**
90 * Called when a caught exception needs to be output.
91 */
92 function paintException($exception) {
93 // Explicitly call grandparent, not parent::paintException.
94 SimpleScorer::paintException($exception);
c8a9f066
TH
95
96 if (is_a($exception, 'moodle_exception') &&
97 !get_string_manager()->string_exists($exception->errorcode, $exception->module)) {
98 $exceptionmessage = 'Exception with missing language string {' .
99 $exception->errorcode . '} from language file {' . $exception->module . '}';
100
101 if (!empty($exception->a)) {
102 if (is_string($exception->a)) {
103 $data = $exception->a;
104 } else {
105 $data = array();
106 foreach ((array)$exception->a as $name => $value) {
107 $data[] = $name . ' => [' . $value . ']';
108 }
109 $data = implode(', ', $data);
110 }
111 $exceptionmessage .= ' with data {' . $data . '}';
112 }
113
114 } else {
115 $exceptionmessage = $exception->getMessage();
116 }
39321e0f 117 $message = 'Unexpected exception of type [' . get_class($exception) .
c8a9f066 118 '] with message ['. $exceptionmessage .
39321e0f
TH
119 '] in ['. $exception->getFile() .
120 ' line ' . $exception->getLine() . ']';
121
c8a9f066
TH
122 $debuginfo = null;
123 if (!empty($exception->debuginfo)) {
124 $debuginfo = $exception->debuginfo;
125 }
126
127 $this->_paintPassFail('exception', $message, $exception->getTrace(), $debuginfo);
39321e0f
TH
128 }
129
3ef8c936 130 /**
2319bded 131 * Private method. Used by printPass/Fail/Skip/Error.
3ef8c936 132 */
c8a9f066 133 function _paintPassFail($passorfail, $message, $stacktrace = null, $debuginfo = null) {
20486a5a 134 global $FULLME, $CFG, $OUTPUT;
eef868d1 135
20486a5a 136 echo $OUTPUT->box_start($passorfail . ' generalbox ');
c8a9f066 137
3ef8c936 138 $url = $this->_htmlEntities($this->_stripParameterFromUrl($FULLME, 'path'));
139 echo '<b class="', $passorfail, '">', $this->get_string($passorfail), '</b>: ';
140 $breadcrumb = $this->getTestList();
141 array_shift($breadcrumb);
142 $file = array_shift($breadcrumb);
143 $pathbits = preg_split('/\/|\\\\/', substr($file, strlen($CFG->dirroot) + 1));
144 $file = array_pop($pathbits);
145 $folder = '';
146 foreach ($pathbits as $pathbit) {
147 $folder .= $pathbit . '/';
148 echo "<a href=\"{$url}path=$folder\" title=\"$this->strrunonlyfolder\">$pathbit</a>/";
149 }
150 echo "<a href=\"{$url}path=$folder$file\" title=\"$this->strrunonlyfile\">$file</a>";
151 echo $this->strseparator, implode($this->strseparator, $breadcrumb);
c8a9f066 152
39321e0f 153 echo '<br />', $this->_htmlEntities($message), "\n\n";
c8a9f066
TH
154
155 if (!empty($debuginfo)) {
156 print_object('Debug info:');
157 print_object($debuginfo);
158 }
159
f99b19c5
TH
160 if ($stacktrace) {
161 $dotsadded = false;
162 $interestinglines = 0;
163 $filteredstacktrace = array();
164 foreach ($stacktrace as $frame) {
39321e0f
TH
165 if (empty($frame['file']) || (strpos($frame['file'], 'simpletestlib') === false &&
166 strpos($frame['file'], 'simpletestcoveragelib') === false
f99b19c5
TH
167 && strpos($frame['file'], 'report/unittest') === false)) {
168 $filteredstacktrace[] = $frame;
169 $interestinglines += 1;
170 $dotsadded = false;
171 } else if (!$dotsadded) {
172 $filteredstacktrace[] = array('line' => '...', 'file' => '...');
173 $dotsadded = true;
174 }
175 }
39321e0f 176 if ($interestinglines > 1 || ($passorfail == 'exception' && $interestinglines > 0)) {
f99b19c5
TH
177 echo '<div class="notifytiny">' . format_backtrace($filteredstacktrace) . "</div>\n\n";
178 }
179 }
c8a9f066 180
20486a5a 181 echo $OUTPUT->box_end();
3ef8c936 182 flush();
183 }
eef868d1 184
3ef8c936 185 /**
186 * Called when a notice needs to be output.
187 */
188 function paintNotice($message) {
189 $this->paintMessage($this->_htmlEntities($message));
190 }
191
192 /**
193 * Paints a simple supplementary message.
194 * @param string $message Text to display.
195 */
196 function paintMessage($message) {
8fbce1c8 197 global $OUTPUT;
3ef8c936 198 if ($this->showpasses) {
8fbce1c8 199 echo $OUTPUT->box_start();
3ef8c936 200 echo '<span class="notice">', $this->get_string('notice'), '</span>: ';
201 $breadcrumb = $this->getTestList();
202 array_shift($breadcrumb);
203 echo implode($this->strseparator, $breadcrumb);
204 echo $this->strseparator, '<br />', $message, "\n";
8fbce1c8 205 echo $OUTPUT->box_end();
3ef8c936 206 flush();
207 }
208 }
209
3ef8c936 210 /**
211 * Output anything that should appear above all the test output.
212 */
213 function paintHeader($test_name) {
4ab83611 214 $this->timestart = time();
3ef8c936 215 // We do this the moodle way instead.
216 }
217
218 /**
219 * Output anything that should appear below all the test output, e.g. summary information.
220 */
221 function paintFooter($test_name) {
222 $summarydata = new stdClass;
223 $summarydata->run = $this->getTestCaseProgress();
224 $summarydata->total = $this->getTestCaseCount();
225 $summarydata->passes = $this->getPassCount();
226 $summarydata->fails = $this->getFailCount();
227 $summarydata->exceptions = $this->getExceptionCount();
228
229 if ($summarydata->fails == 0 && $summarydata->exceptions == 0) {
98907dba 230 $status = "passed";
3ef8c936 231 } else {
98907dba 232 $status = "failed";
3ef8c936 233 }
234 echo '<div class="unittestsummary ', $status, '">';
235 echo $this->get_string('summary', $summarydata);
236 echo '</div>';
237
eef868d1 238 echo '<div class="performanceinfo">',
4ab83611 239 $this->get_string('runat', userdate($this->timestart)), ' ',
240 $this->get_string('timetakes', format_time(time() - $this->timestart)), ' ',
3ef8c936 241 $this->get_string('version', SimpleTestOptions::getVersion()),
242 '</div>';
243 }
eef868d1 244
3ef8c936 245 /**
246 * Strip a specified parameter from the query string of a URL, if present.
eef868d1 247 * Adds a separator to the end of the URL, so that a new parameter
3ef8c936 248 * can easily be appended. For example (assuming $param = 'frog'):
eef868d1 249 *
3ef8c936 250 * http://example.com/index.php -> http://example.com/index.php?
251 * http://example.com/index.php?frog=1 -> http://example.com/index.php?
252 * http://example.com/index.php?toad=1 -> http://example.com/index.php?toad=1&
253 * http://example.com/index.php?frog=1&toad=1 -> http://example.com/index.php?toad=1&
eef868d1 254 *
3ef8c936 255 * @param string $url the URL to modify.
256 * @param string $param the parameter to strip from the URL, if present.
eef868d1 257 *
3ef8c936 258 * @return string The modified URL.
259 */
260 function _stripParameterFromUrl($url, $param) {
261 $url = preg_replace('/(\?|&)' . $param . '=[^&]*&?/', '$1', $url);
262 if (strpos($url, '?') === false) {
263 $url = $url . '?';
264 } else {
265 $url = $url . '&';
266 }
267 return $url;
268 }
269
270 /**
271 * Look up a lang string in the appropriate file.
272 */
273 function get_string($identifier, $a = NULL) {
4f9c6e27 274 return get_string($identifier, 'simpletest', $a);
3ef8c936 275 }
276}