Development

/tools/lime/trunk/lib/lime.php

You must first sign up to be able to contribute.

root/tools/lime/trunk/lib/lime.php

Revision 10005, 27.7 kB (checked in by dwhittle, 6 days ago)

lime: fixed potential notice during a test failure (closes #3854 - thanks carl)

  • Property svn:mime-type set to text/x-php
  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
Line 
1 <?php
2
3 /**
4  * This file is part of the symfony package.
5  * (c) 2004-2006 Fabien Potencier <fabien.potencier@gmail.com>
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  * Unit test library.
13  *
14  * @package    lime
15  * @author     Fabien Potencier <fabien.potencier@gmail.com>
16  * @version    SVN: $Id$
17  */
18 class lime_test
19 {
20   public $plan = null;
21   public $test_nb = 0;
22   public $failed = 0;
23   public $passed = 0;
24   public $skipped = 0;
25   public $output = null;
26
27   public function __construct($plan = null, $output_instance = null)
28   {
29     $this->plan = $plan;
30     $this->output = $output_instance ? $output_instance : new lime_output();
31
32     null !== $this->plan and $this->output->echoln(sprintf("1..%d", $this->plan));
33   }
34
35   public function __destruct()
36   {
37     $total = $this->passed + $this->failed + $this->skipped;
38
39     null === $this->plan and $this->plan = $total and $this->output->echoln(sprintf("1..%d", $this->plan));
40
41     if ($total > $this->plan)
42     {
43       $this->output->red_bar(sprintf(" Looks like you planned %d tests but ran %d extra.", $this->plan, $total - $this->plan));
44     }
45     elseif ($total < $this->plan)
46     {
47       $this->output->red_bar(sprintf(" Looks like you planned %d tests but only ran %d.", $this->plan, $total));
48     }
49
50     if ($this->failed)
51     {
52       $this->output->red_bar(sprintf(" Looks like you failed %d tests of %d.", $this->failed, $this->passed + $this->failed));
53     }
54     else if ($total == $this->plan)
55     {
56       $this->output->green_bar(" Looks like everything went fine.");
57     }
58
59     flush();
60   }
61
62   public function ok($exp, $message = '')
63   {
64     if ($result = (boolean) $exp)
65     {
66       ++$this->passed;
67     }
68     else
69     {
70       ++$this->failed;
71     }
72     $this->output->echoln(sprintf("%s %d%s", $result ? 'ok' : 'not ok', ++$this->test_nb, $message = $message ? sprintf('%s %s', 0 === strpos($message, '#') ? '' : ' -', $message) : ''));
73
74     if (!$result)
75     {
76       $traces = debug_backtrace();
77       if (!empty($_SERVER['PHP_SELF']))
78       {
79         $i = strstr($traces[0]['file'], $_SERVER['PHP_SELF']) ? 0 : (isset($traces[1]['file']) ? 1 : 0);
80       }
81       else
82       {
83         $i = 0;
84       }
85       $this->output->diag(sprintf('    Failed test (%s at line %d)', str_replace(getcwd(), '.', $traces[$i]['file']), $traces[$i]['line']));
86     }
87
88     return $result;
89   }
90
91   public function is($exp1, $exp2, $message = '')
92   {
93     if (is_object($exp1) || is_object($exp2))
94     {
95       $value = $exp1 === $exp2;
96     }
97     else
98     {
99       $value = $exp1 == $exp2;
100     }
101
102     if (!$result = $this->ok($value, $message))
103     {
104       $this->output->diag(sprintf("           got: %s", var_export($exp1, true)), sprintf("      expected: %s", var_export($exp2, true)));
105     }
106
107     return $result;
108   }
109
110   public function isnt($exp1, $exp2, $message = '')
111   {
112     if (!$result = $this->ok($exp1 != $exp2, $message))
113     {
114       $this->output->diag(sprintf("      %s", var_export($exp2, true)), '          ne', sprintf("      %s", var_export($exp2, true)));
115     }
116
117     return $result;
118   }
119
120   public function like($exp, $regex, $message = '')
121   {
122     if (!$result = $this->ok(preg_match($regex, $exp), $message))
123     {
124       $this->output->diag(sprintf("                    '%s'", $exp), sprintf("      doesn't match '%s'", $regex));
125     }
126
127     return $result;
128   }
129
130   public function unlike($exp, $regex, $message = '')
131   {
132     if (!$result = $this->ok(!preg_match($regex, $exp), $message))
133     {
134       $this->output->diag(sprintf("               '%s'", $exp), sprintf("      matches '%s'", $regex));
135     }
136
137     return $result;
138   }
139
140   public function cmp_ok($exp1, $op, $exp2, $message = '')
141   {
142     eval(sprintf("\$result = \$exp1 $op \$exp2;"));
143     if (!$this->ok($result, $message))
144     {
145       $this->output->diag(sprintf("      %s", str_replace("\n", '', var_export($exp1, true))), sprintf("          %s", $op), sprintf("      %s", str_replace("\n", '', var_export($exp2, true))));
146     }
147
148     return $result;
149   }
150
151   public function can_ok($object, $methods, $message = '')
152   {
153     $result = true;
154     $failed_messages = array();
155     foreach ((array) $methods as $method)
156     {
157       if (!method_exists($object, $method))
158       {
159         $failed_messages[] = sprintf("      method '%s' does not exist", $method);
160         $result = false;
161       }
162     }
163
164     !$this->ok($result, $message);
165
166     !$result and $this->output->diag($failed_messages);
167
168     return $result;
169   }
170
171   public function isa_ok($var, $class, $message = '')
172   {
173     $type = is_object($var) ? get_class($var) : gettype($var);
174     if (!$result = $this->ok($type == $class, $message))
175     {
176       $this->output->diag(sprintf("      isa_ok isn't a '%s' it's a '%s'", $class, $type));
177     }
178
179     return $result;
180   }
181
182   public function is_deeply($exp1, $exp2, $message = '')
183   {
184     if (!$result = $this->ok($this->test_is_deeply($exp1, $exp2), $message))
185     {
186       $this->output->diag(sprintf("           got: %s", str_replace("\n", '', var_export($exp1, true))), sprintf("      expected: %s", str_replace("\n", '', var_export($exp2, true))));
187     }
188
189     return $result;
190   }
191
192   public function pass($message = '')
193   {
194     return $this->ok(true, $message);
195   }
196
197   public function fail($message = '')
198   {
199     return $this->ok(false, $message);
200   }
201
202   public function diag($message)
203   {
204     $this->output->diag($message);
205   }
206
207   public function skip($message = '', $nb_tests = 1)
208   {
209     for ($i = 0; $i < $nb_tests; $i++)
210     {
211       ++$this->skipped and --$this->passed;
212       $this->pass(sprintf("# SKIP%s", $message ? ' '.$message : ''));
213     }
214   }
215
216   public function todo($message = '')
217   {
218     ++$this->skipped and --$this->passed;
219     $this->pass(sprintf("# TODO%s", $message ? ' '.$message : ''));
220   }
221
222   public function include_ok($file, $message = '')
223   {
224     if (!$result = $this->ok((@include($file)) == 1, $message))
225     {
226       $this->output->diag(sprintf("      Tried to include '%s'", $file));
227     }
228
229     return $result;
230   }
231
232   private function test_is_deeply($var1, $var2)
233   {
234     if (gettype($var1) != gettype($var2))
235     {
236       return false;
237     }
238
239     if (is_array($var1))
240     {
241       ksort($var1);
242       ksort($var2);
243
244       $keys1 = array_keys($var1);
245       $keys2 = array_keys($var2);
246       if (array_diff($keys1, $keys2) || array_diff($keys2, $keys1))
247       {
248         return false;
249       }
250       $is_equal = true;
251       foreach ($var1 as $key => $value)
252       {
253         $is_equal = $this->test_is_deeply($var1[$key], $var2[$key]);
254         if ($is_equal === false)
255         {
256           break;
257         }
258       }
259
260       return $is_equal;
261     }
262     else
263     {
264       return $var1 === $var2;
265     }
266   }
267
268   public function comment($message)
269   {
270     $this->output->comment($message);
271   }
272
273   public static function get_temp_directory()
274   {
275     if ('\\' == DIRECTORY_SEPARATOR)
276     {
277       foreach (array('TEMP', 'TMP', 'windir') as $dir)
278       {
279         if ($var = isset($_ENV[$dir]) ? $_ENV[$dir] : getenv($dir))
280         {
281           return $var;
282         }
283       }
284
285       return getenv('SystemRoot').'\temp';
286     }
287
288     if ($var = isset($_ENV['TMPDIR']) ? $_ENV['TMPDIR'] : getenv('TMPDIR'))
289     {
290       return $var;
291     }
292
293     return '/tmp';
294   }
295 }
296
297 class lime_output
298 {
299   public function diag()
300   {
301     $messages = func_get_args();
302     foreach ($messages as $message)
303     {
304       array_map(array($this, 'comment'), (array) $message);
305     }
306   }
307
308   public function comment($message)
309   {
310     echo "# $message\n";
311   }
312
313   public function echoln($message)
314   {
315     echo "$message\n";
316   }
317
318   public function green_bar($message)
319   {
320     echo "$message\n";
321   }
322
323   public function red_bar($message)
324   {
325     echo "$message\n";
326   }
327 }
328
329 class lime_output_color extends lime_output
330 {
331   public $colorizer = null;
332
333   public function __construct()
334   {
335     $this->colorizer = new lime_colorizer();
336   }
337
338   public function diag()
339   {
340     $messages = func_get_args();
341     foreach ($messages as $message)
342     {
343       echo $this->colorizer->colorize('# '.join("\n# ", (array) $message), 'COMMENT')."\n";
344     }
345   }
346
347   public function comment($message)
348   {
349     echo $this->colorizer->colorize(sprintf('# %s', $message), 'COMMENT')."\n";
350   }
351
352   public function echoln($message, $colorizer_parameter = null)
353   {
354     $message = preg_replace('/(?:^|\.)((?:not ok|dubious) *\d*)\b/e', '$this->colorizer->colorize(\'$1\', \'ERROR\')', $message);
355     $message = preg_replace('/(?:^|\.)(ok *\d*)\b/e', '$this->colorizer->colorize(\'$1\', \'INFO\')', $message);
356     $message = preg_replace('/"(.+?)"/e', '$this->colorizer->colorize(\'$1\', \'PARAMETER\')', $message);
357     $message = preg_replace('/(\->|\:\:)?([a-zA-Z0-9_]+?)\(\)/e', '$this->colorizer->colorize(\'$1$2()\', \'PARAMETER\')', $message);
358
359     echo ($colorizer_parameter ? $this->colorizer->colorize($message, $colorizer_parameter) : $message)."\n";
360   }
361
362   public function green_bar($message)
363   {
364     echo $this->colorizer->colorize($message.str_repeat(' ', 71 - min(71, strlen($message))), 'GREEN_BAR')."\n";
365   }
366
367   public function red_bar($message)
368   {
369     echo $this->colorizer->colorize($message.str_repeat(' ', 71 - min(71, strlen($message))), 'RED_BAR')."\n";
370   }
371 }
372
373 class lime_colorizer
374 {
375   static public $styles = array();
376
377   public static function style($name, $options = array())
378   {
379     self::$styles[$name] = $options;
380   }
381
382   public static function colorize($text = '', $parameters = array())
383   {
384     // disable colors if not supported (windows or non tty console)
385     if (DIRECTORY_SEPARATOR == '\\' || !function_exists('posix_isatty') || !@posix_isatty(STDOUT))
386     {
387       return $text;
388     }
389
390     static $options    = array('bold' => 1, 'underscore' => 4, 'blink' => 5, 'reverse' => 7, 'conceal' => 8);
391     static $foreground = array('black' => 30, 'red' => 31, 'green' => 32, 'yellow' => 33, 'blue' => 34, 'magenta' => 35, 'cyan' => 36, 'white' => 37);
392     static $background = array('black' => 40, 'red' => 41, 'green' => 42, 'yellow' => 43, 'blue' => 44, 'magenta' => 45, 'cyan' => 46, 'white' => 47);
393
394     !is_array($parameters) && isset(self::$styles[$parameters]) and $parameters = self::$styles[$parameters];
395
396     $codes = array();
397     isset($parameters['fg']) and $codes[] = $foreground[$parameters['fg']];
398     isset($parameters['bg']) and $codes[] = $background[$parameters['bg']];
399     foreach ($options as $option => $value)
400     {
401       isset($parameters[$option]) && $parameters[$option] and $codes[] = $value;
402     }
403
404     return "\033[".implode(';', $codes).'m'.$text."\033[0m";
405   }
406 }
407
408 lime_colorizer::style('ERROR', array('bg' => 'red', 'fg' => 'white', 'bold' => true));
409 lime_colorizer::style('INFO',  array('fg' => 'green', 'bold' => true));
410 lime_colorizer::style('PARAMETER', array('fg' => 'cyan'));
411 lime_colorizer::style('COMMENT',  array('fg' => 'yellow'));
412
413 lime_colorizer::style('GREEN_BAR',  array('fg' => 'white', 'bg' => 'green', 'bold' => true));
414 lime_colorizer::style('RED_BAR',  array('fg' => 'white', 'bg' => 'red', 'bold' => true));
415
416 class lime_harness extends lime_registration
417 {
418   public $php_cli = '';
419   public $stats = array();
420   public $output = null;
421
422   public function __construct($output_instance, $php_cli = null)
423   {
424     if (getenv('PHP_PATH'))
425     {
426       $this->php_cli = getenv('PHP_PATH');
427
428       if (!is_executable($this->php_cli))
429       {
430         throw new Exception('The defined PHP_PATH environment variable is not a valid PHP executable.');
431       }
432     }
433
434     $this->php_cli = null === $php_cli ? PHP_BINDIR.DIRECTORY_SEPARATOR.'php' : $php_cli;
435
436     if (!is_executable($this->php_cli))
437     {
438       $this->php_cli = $this->find_php_cli();
439     }
440
441     $this->output = $output_instance ? $output_instance : new lime_output();
442   }
443
444   protected function find_php_cli()
445   {
446     $path = getenv('PATH') ? getenv('PATH') : getenv('Path');
447     $exe_suffixes = DIRECTORY_SEPARATOR == '\\' ? (getenv('PATHEXT') ? explode(PATH_SEPARATOR, getenv('PATHEXT')) : array('.exe', '.bat', '.cmd', '.com')) : array('');
448     foreach (array('php5', 'php') as $php_cli)
449     {
450       foreach ($exe_suffixes as $suffix)
451       {
452         foreach (explode(PATH_SEPARATOR, $path) as $dir)
453         {
454           $file = $dir.DIRECTORY_SEPARATOR.$php_cli.$suffix;
455           if (is_executable($file))
456           {
457             return $file;
458           }
459         }
460       }
461     }
462
463     throw new Exception("Unable to find PHP executable.");
464   }
465
466   public function run()
467   {
468     if (!count($this->files))
469     {
470       throw new Exception('You must register some test files before running them!');
471     }
472
473     // sort the files to be able to predict the order
474     sort($this->files);
475
476     $this->stats =array(
477       '_failed_files' => array(),
478       '_failed_tests' => 0,
479       '_nb_tests'     => 0,
480     );
481
482     foreach ($this->files as $file)
483     {
484       $this->stats[$file] = array(
485         'plan'     =>   null,
486         'nb_tests' => 0,
487         'failed'   => array(),
488         'passed'