Overview

Namespaces

  • Jyxo
    • Beholder
      • TestCase
    • Gettext
      • Parser
    • Input
      • Chain
      • Filter
      • Validator
    • Mail
      • Email
        • Attachment
      • Parser
      • Sender
    • Rpc
      • Json
      • Xml
    • Shell
    • Spl
    • Svn
    • Time
    • Webdav
  • PHP

Classes

  • Executor
  • Result
  • TestCase
  • Overview
  • Namespace
  • Class
  • Tree
  • Deprecated
  1: <?php
  2: 
  3: /**
  4:  * Jyxo PHP Library
  5:  *
  6:  * LICENSE
  7:  *
  8:  * This source file is subject to the new BSD license that is bundled
  9:  * with this package in the file license.txt.
 10:  * It is also available through the world-wide-web at this URL:
 11:  * https://github.com/jyxo/php/blob/master/license.txt
 12:  */
 13: 
 14: namespace Jyxo\Beholder;
 15: 
 16: /**
 17:  * Beholder test executor.
 18:  *
 19:  * Includes filtering and HTML output formatting.
 20:  *
 21:  * Tests are performed in random order but results are outputted in alphabetical order with the order they were performed.
 22:  *
 23:  * Example:
 24:  * <code>
 25:  * $beholder = new \Jyxo\Beholder\Executor('Project', $_GET);
 26:  * $beholder->addTest('T1', new \Project\Beholder\Test1('Test 1'));
 27:  * $beholder->addTest('T2', new \Project\Beholder\Test2('Test 2'));
 28:  * $beholder->addTest('T3', new \Project\Beholder\Test3Blah('Test 3 Blah'));
 29:  * $beholder->run();
 30:  * </code>
 31:  *
 32:  * @category Jyxo
 33:  * @package Jyxo\Beholder
 34:  * @copyright Copyright (c) 2005-2011 Jyxo, s.r.o.
 35:  * @license https://github.com/jyxo/php/blob/master/license.txt
 36:  * @author Jan Matoušek
 37:  * @author Jaroslav Hanslík
 38:  */
 39: class Executor
 40: {
 41:     /**
 42:      * Plaintext output.
 43:      *
 44:      * @var string
 45:      */
 46:     const OUTPUT_TEXT = 't';
 47: 
 48:     /**
 49:      * HTML output.
 50:      *
 51:      * @var string
 52:      */
 53:     const OUTPUT_HTML = 'h';
 54: 
 55:     /**
 56:      * Output parameter.
 57:      *
 58:      * @var string
 59:      */
 60:     const PARAM_OUTPUT = 'o';
 61: 
 62:     /**
 63:      * Parameter for including tests.
 64:      *
 65:      * @var string
 66:      */
 67:     const PARAM_INCLUDE = 't';
 68: 
 69:     /**
 70:      * Parameter for excluding tests.
 71:      *
 72:      * @var string
 73:      */
 74:     const PARAM_EXCLUDE = 'nt';
 75: 
 76:     /**
 77:      * Project name.
 78:      *
 79:      * @var string
 80:      */
 81:     private $project = '';
 82: 
 83:     /**
 84:      * List of tests.
 85:      *
 86:      * @var array
 87:      */
 88:     private $tests = array();
 89: 
 90:     /**
 91:      * Filter for including tests.
 92:      *
 93:      * @var string
 94:      */
 95:     private $includeFilter = '*';
 96: 
 97:     /**
 98:      * Filter for excluding tests.
 99:      *
100:      * @var string
101:      */
102:     private $excludeFilter = '';
103: 
104:     /**
105:      * Output type.
106:      *
107:      * @var string
108:      */
109:     private $output = self::OUTPUT_HTML;
110: 
111:     /**
112:      * Constructor.
113:      *
114:      * @param string $project Project name
115:      * @param array $params Parameters; possible parameters are: include, exclude, output
116:      */
117:     public function __construct($project, array $params = array())
118:     {
119:         // Project name
120:         $this->project = (string) $project;
121: 
122:         // Filters
123:         if (!empty($params[self::PARAM_INCLUDE])) {
124:             $this->includeFilter = (string) $params[self::PARAM_INCLUDE];
125:         }
126:         if (!empty($params[self::PARAM_EXCLUDE])) {
127:             $this->excludeFilter = (string) $params[self::PARAM_EXCLUDE];
128:         }
129: 
130:         // Output type
131:         if (!empty($params[self::PARAM_OUTPUT])) {
132:             switch ($params[self::PARAM_OUTPUT]) {
133:                 // Plaintext
134:                 case self::OUTPUT_TEXT:
135:                     $this->output = self::OUTPUT_TEXT;
136:                     break;
137:                 // HTML
138:                 case self::OUTPUT_HTML:
139:                 default:
140:                     $this->output = self::OUTPUT_HTML;
141:                     break;
142:             }
143:         }
144:     }
145: 
146:     /**
147:      * Performs chosen tests and outputs results according to the selected output type.
148:      *
149:      * @return boolean Returns if all tests were successful
150:      */
151:     public function run()
152:     {
153:         // Filters tests
154:         foreach (array_keys($this->tests) as $ident) {
155:             if (!$this->includeTest($ident)) {
156:                 unset($this->tests[$ident]);
157:             }
158:         }
159: 
160:         // Shuffles them
161:         $idents = array_keys($this->tests);
162:         shuffle($idents);
163: 
164:         // Performs tests and gathers results
165:         $outputData = array();
166:         $order = 1;
167:         $allSucceeded = true;
168:         foreach ($idents as $ident) {
169:             // Runs a test
170:             $data = $this->runTest($ident);
171: 
172:             // Saves the overall status
173:             $allSucceeded = $allSucceeded && $data['result']->isSuccess();
174: 
175:             // Adds the text into the output
176:             $data['order'] = $order++;
177:             $outputData[] = $data;
178:         }
179: 
180:         // Sorts tests according to their identifiers
181:         $idents = array();
182:         foreach ($outputData as $key => $data) {
183:             $idents[$key] = $data['ident'];
184:         }
185:         array_multisort($idents, SORT_ASC, $outputData);
186: 
187:         // Outputs the header
188:         if ($allSucceeded) {
189:             header('HTTP/1.1 200 OK');
190:         } else {
191:             header('HTTP/1.1 500 Internal Server Error');
192:         }
193: 
194:         // Outputs the output :)
195:         switch ($this->output) {
196:             // Plaintext
197:             case self::OUTPUT_TEXT:
198:                 $this->writeText($allSucceeded, $outputData);
199:                 break;
200:             // HTML
201:             case self::OUTPUT_HTML:
202:             default:
203:                 $this->writeHtml($allSucceeded, $outputData);
204:                 break;
205:         }
206: 
207:         return $allSucceeded;
208:     }
209: 
210:     /**
211:      * Adds a test.
212:      *
213:      * @param string $ident Tests identifier
214:      * @param \Jyxo\Beholder\TestCase $test Test instance
215:      * @return \Jyxo\Beholder\Executor
216:      */
217:     public function addTest($ident, \Jyxo\Beholder\TestCase $test)
218:     {
219:         $this->tests[(string) $ident] = $test;
220: 
221:         return $this;
222:     }
223: 
224:     /**
225:      * Runs a single test.
226:      *
227:      * @param string $ident Test identifier
228:      * @return array
229:      * @throws \UnexpectedValueException If the test returned an unknown result value
230:      */
231:     private function runTest($ident)
232:     {
233:         // Runs the test
234:         $timer = \Jyxo\Timer::start();
235:         $result = $this->tests[$ident]->run();
236:         if (!($result instanceof \Jyxo\Beholder\Result)) {
237:             throw new \UnexpectedValueException(sprintf('Result %s of the test %s is not a \Jyxo\Beholder\Result instance.', $result, $ident));
238:         }
239: 
240:         // Returns result data
241:         return array(
242:             'ident' => $ident,
243:             'test' => $this->tests[$ident],
244:             'result' => $result,
245:             'duration' => \Jyxo\Timer::stop($timer)
246:         );
247:     }
248: 
249:     /**
250:      * Checks if the given test will be performed according to the current filter settings.
251:      *
252:      * @param string $ident Test identifier
253:      * @return boolean
254:      */
255:     private function includeTest($ident)
256:     {
257:         // If the test is not among the allowed ones, return false
258:         $include = false;
259:         foreach (explode(',', $this->includeFilter) as $pattern) {
260:             if ($this->patternMatch($pattern, $ident)) {
261:                 // We cannot use "return true" because the test might be disabled later
262:                 $include = true;
263:             }
264:         }
265:         if (!$include) {
266:             return false;
267:         }
268: 
269:         // If the test is among the excluded ones, return false
270:         foreach (explode(',', $this->excludeFilter) as $pattern) {
271:             if ($this->patternMatch($pattern, $ident)) {
272:                 return false;
273:             }
274:         }
275: 
276:         // Included otherwise
277:         return true;
278:     }
279: 
280:     /**
281:      * Checks if the given string matches the given pattern.
282:      *
283:      * @param string $pattern Pattern
284:      * @param string $string String to be matched
285:      * @return boolean
286:      */
287:     private function patternMatch($pattern, $string)
288:     {
289:         return fnmatch($pattern, $string);
290:     }
291: 
292:     /**
293:      * Outputs results in HTML form.
294:      *
295:      * @param boolean $allSucceeded Have all tests been successful
296:      * @param array $outputData Test results
297:      */
298:     private function writeHtml($allSucceeded, array $outputData)
299:     {
300:         header('Content-Type: text/html; charset=utf-8');
301:         echo '<head>' . "\n";
302:         echo '<meta http-equiv="content-type" content="text/html; charset=utf-8" />' . "\n";
303:         echo '<title>Beholder for project ' . $this->project . '</title>' . "\n";
304:         echo '<style>' . "\n";
305:         echo '  body {font: 12px Verdana, Geneva, Arial, Helvetica, sans-serif;}' . "\n";
306:         echo '  table {font-size: small; border-collapse: collapse;}' . "\n";
307:         echo '  table th {border: 1px solid #000; background: #000; color: #fff;}' . "\n";
308:         echo '  table td {border: 1px solid #000; padding: .25em .5em;}' . "\n";
309:         echo '</style>' . "\n";
310:         echo '</head>' . "\n";
311:         echo '<body style="background-color: ' . ($allSucceeded ? '#ccffcc' : '#ffcccc') . '; width: 90%; height: 100%; padding: 1em; margin: 0;">' . "\n";
312:         echo '<h1>Beholder for project ' . $this->project . "</h1>\n";
313:         echo '<p>Tests included: ' . $this->includeFilter . "\n";
314:         echo '<br>Tests excluded: ' . $this->excludeFilter . "\n";
315:         echo '</p>' . "\n";
316:         echo '<table><tr><th>Run order</th><th>Duration</th><th>Ident</th><th>Status</th><th>Test name</th><th>Comment</th></tr>' . "\n";
317:         foreach ($outputData as $data) {
318:             echo sprintf('
319:                 <tr>
320:                     <td>%d</td>
321:                     <td>%.2fs</td>
322:                     <td>%s</td>
323:                     <td style="color: %s;">%s</td>
324:                     <td><b>%s</b></td>
325:                     <td><i>%s</i></td>
326:                 </tr>' . "\n",
327:                 $data['order'],
328:                 $data['duration'],
329:                 $data['ident'],
330:                 $data['result']->isSuccess() ? 'green' : 'red; font-weight: bold;', $data['result']->getStatusMessage(),
331:                 $data['test']->getDescription(),
332:                 $data['result']->getDescription()
333:             );
334:         }
335:         echo '</table>
336:             <h2>Parameters</h2>
337:                 <dl>
338:                 <dt>' . self::PARAM_INCLUDE . '</dt>
339:                 <dd>Tests to include, list of shell patterns separated by comma, default *</dd>
340:                 <dt>' . self::PARAM_EXCLUDE . '</dt>
341:                 <dd>Tests to exclude, empty by default</dd>
342:                 <dt>' . self::PARAM_OUTPUT . '</dt>
343:                 <dd>' . self::OUTPUT_HTML . ' = HTML output, ' . self::OUTPUT_TEXT . ' = text output</dd>
344:                 </dl>
345:             <p>Tests are included, then excluded.</p>
346:             <p><a href="?' . self::PARAM_INCLUDE . '=' . $this->includeFilter
347:                 . '&amp;' . self::PARAM_EXCLUDE . '=' . $this->excludeFilter
348:                 . '&amp;' . self::PARAM_OUTPUT . '=' . self::OUTPUT_TEXT . '">Text version</a></p>
349:             </body>' . "\n";
350:     }
351: 
352:     /**
353:      * Outputs results in plaintext.
354:      *
355:      * @param boolean $allSucceeded Have all tests been successful
356:      * @param array $outputData Test results
357:      */
358:     private function writeText($allSucceeded, array $outputData)
359:     {
360:         // HTML is sent on purpose
361:         header('Content-Type: text/html; charset=utf-8');
362:         echo '<pre>This is Beholder for project ' . $this->project . "\n";
363:         echo 'Tests included: ' . $this->includeFilter . "\n";
364:         echo 'Tests excluded: ' . $this->excludeFilter . "\n\n";
365:         echo '<a href="?' . self::PARAM_INCLUDE . '=' . $this->includeFilter
366:             . '&amp;' . self::PARAM_EXCLUDE . '=' . $this->excludeFilter
367:             . '&amp;' . self::PARAM_OUTPUT . '=' . self::OUTPUT_HTML . "\">Html version</a>\n\n";
368: 
369:         echo sprintf("%-9s %10s   %-10s %-7s  %-35s    %s\n",
370:             'Run Order', 'Duration', 'Ident', 'Status', 'Test Name', 'Description');
371:         foreach ($outputData as $data) {
372:             echo sprintf("%9d %9.2fs   %-10s %-7s  %-35s    %s\n",
373:                 $data['order'],
374:                 $data['duration'],
375:                 $data['ident'],
376:                 $data['result']->getStatusMessage(),
377:                 $data['test']->getDescription(),
378:                 $data['result']->getDescription());
379:         }
380: 
381:         if ($allSucceeded) {
382:             echo "\nJust a little prayer so we know we are allright.\n\n";
383: 
384:             for ($i = 0; $i < 5; $i++) {
385:                 echo 'Our Father in heaven,' . "\n";
386:                 echo 'hallowed be your name,' . "\n";
387:                 echo 'your kingdom come,' . "\n";
388:                 echo 'your will be done' . "\n";
389:                 echo 'on earth as it is in heaven.' . "\n";
390:                 echo 'Give us today our daily bread,' . "\n";
391:                 echo 'and forgive us the wrong we have done' . "\n";
392:                 echo 'as we forgive those who wrong us.' . "\n";
393:                 echo 'Subject us not to the trial' . "\n";
394:                 echo 'but deliver us from the evil one.' . "\n";
395:                 echo 'And make the ' . $this->project . " project work.\n";
396:                 echo 'Amen.' . "\n\n";
397:             }
398:         }
399:     }
400: }
401: 
Jyxo PHP Library API documentation generated by ApiGen 2.3.0