NOBUG standardising prevention of output buffering
[moodle.git] / admin / report / unittest / ex_reporter.php
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
8  * @package SimpleTestEx
9  */
11 if (!defined('MOODLE_INTERNAL')) {
12     die('Direct access to this script is forbidden.');    ///  It must be included from a Moodle page
13 }
15 require_once($CFG->libdir . '/simpletestlib/reporter.php');
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  */
24 class ExHtmlReporter extends HtmlReporter {
26     // Options set when the class is created.
27     var $showpasses;
29     // Lang strings. Set in the constructor.
30     var $strrunonlyfolder;
31     var $strrunonlyfile;
33     var $strseparator;
35     var $timestart;
37     /**
38      * Constructor.
39      *
40      * @param bool $showpasses Whether this reporter should output anything for passes.
41      */
42     function ExHtmlReporter($showpasses) {
43         $this->HtmlReporter();
44         $this->showpasses = $showpasses;
46         $this->strrunonlyfolder = $this->get_string('runonlyfolder');
47         $this->strrunonlyfile = $this->get_string('runonlyfile');
48         $this->strseparator = get_separator();
49     }
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     }
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);
68         $this->_paintPassFail('fail', $message, debug_backtrace());
69     }
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     }
80     /**
81      * Called when an error (uncaught exception or PHP error) needs to be output.
82      */
83     function paintError($message) {
84         // Explicitly call grandparent, not parent::paintError.
85         SimpleScorer::paintError($message);
86         $this->_paintPassFail('exception', $message);
87     }
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);
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 . '}';
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             }
114         } else {
115             $exceptionmessage = $exception->getMessage();
116         }
117         $message = 'Unexpected exception of type [' . get_class($exception) .
118                 '] with message ['. $exceptionmessage .
119                 '] in ['. $exception->getFile() .
120                 ' line ' . $exception->getLine() . ']';
122         $debuginfo = null;
123         if (!empty($exception->debuginfo)) {
124             $debuginfo = $exception->debuginfo;
125         }
127         $this->_paintPassFail('exception', $message, $exception->getTrace(), $debuginfo);
128     }
130     /**
131      * Private method. Used by printPass/Fail/Skip/Error.
132      */
133     function _paintPassFail($passorfail, $message, $stacktrace = null, $debuginfo = null) {
134         global $FULLME, $CFG, $OUTPUT;
136         echo $OUTPUT->box_start($passorfail . ' generalbox ');
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);
153         echo '<br />', $this->_htmlEntities($message), "\n\n";
155         if (!empty($debuginfo)) {
156             print_object('Debug info:');
157             print_object($debuginfo);
158         }
160         if ($stacktrace) {
161             $dotsadded = false;
162             $interestinglines = 0;
163             $filteredstacktrace = array();
164             foreach ($stacktrace as $frame) {
165                 if (empty($frame['file']) || (strpos($frame['file'], 'simpletestlib') === false &&
166                         strpos($frame['file'], 'simpletestcoveragelib') === false
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             }
176             if ($interestinglines > 1 || ($passorfail == 'exception' && $interestinglines > 0)) {
177                 echo '<div class="notifytiny">' . format_backtrace($filteredstacktrace) . "</div>\n\n";
178             }
179         }
181         echo $OUTPUT->box_end();
182         flush();
183     }
185     /**
186      * Called when a notice needs to be output.
187      */
188     function paintNotice($message) {
189         $this->paintMessage($this->_htmlEntities($message));
190     }
192     /**
193      * Paints a simple supplementary message.
194      * @param string $message Text to display.
195      */
196     function paintMessage($message) {
197         global $OUTPUT;
198         if ($this->showpasses) {
199             echo $OUTPUT->box_start();
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";
205             echo $OUTPUT->box_end();
206             flush();
207         }
208     }
210     /**
211      * Output anything that should appear above all the test output.
212      */
213     function paintHeader($test_name) {
214         $this->timestart = time();
215         // We do this the moodle way instead.
216     }
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();
229         if ($summarydata->fails == 0 && $summarydata->exceptions == 0) {
230             $status = "passed";
231         } else {
232             $status = "failed";
233         }
234         echo '<div class="unittestsummary ', $status, '">';
235         echo $this->get_string('summary', $summarydata);
236         echo '</div>';
238         echo '<div class="performanceinfo">',
239                 $this->get_string('runat', userdate($this->timestart)), ' ',
240                 $this->get_string('timetakes', format_time(time() - $this->timestart)), ' ',
241                 $this->get_string('version', SimpleTestOptions::getVersion()),
242                 '</div>';
243     }
245     /**
246      * Strip a specified parameter from the query string of a URL, if present.
247      * Adds a separator to the end of the URL, so that a new parameter
248      * can easily be appended. For example (assuming $param = 'frog'):
249      *
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&
254      *
255      * @param string $url the URL to modify.
256      * @param string $param the parameter to strip from the URL, if present.
257      *
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     }
270     /**
271      * Look up a lang string in the appropriate file.
272      */
273     function get_string($identifier, $a = NULL) {
274         return get_string($identifier, 'simpletest', $a);
275     }