1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
/** |
4
|
|
|
* Unit test library. |
5
|
|
|
* |
6
|
|
|
* @package lime |
7
|
|
|
* @author Fabien Potencier <[email protected]> |
8
|
|
|
* @version SVN: $Id$ |
9
|
|
|
*/ |
10
|
|
|
class lime_test |
11
|
|
|
{ |
12
|
|
|
const EPSILON = 0.0000000001; |
13
|
|
|
|
14
|
|
|
protected $test_nb = 0; |
15
|
|
|
protected $output = null; |
16
|
|
|
protected $results = array(); |
17
|
|
|
protected $options = array(); |
18
|
|
|
|
19
|
|
|
static protected $all_results = array(); |
20
|
|
|
|
21
|
|
|
/** |
22
|
|
|
* Constructor for the lime.php class |
23
|
|
|
* |
24
|
|
|
* @param null $plan |
25
|
|
|
* @param array $options |
26
|
|
|
*/ |
27
|
|
|
public function __construct($plan = null, $options = array()) |
28
|
|
|
{ |
29
|
|
|
// for BC |
30
|
|
|
if (!is_array($options)) { |
31
|
|
|
$options = array('output' => $options); |
32
|
|
|
} |
33
|
|
|
|
34
|
|
|
$this->options = array_merge(array( |
35
|
|
|
'force_colors' => false, |
36
|
|
|
'output' => null, |
37
|
|
|
'verbose' => false, |
38
|
|
|
'error_reporting' => false, |
39
|
|
|
), $options); |
40
|
|
|
|
41
|
|
|
$this->output = $this->options['output'] ? $this->options['output'] : new lime_output($this->options['force_colors']); |
42
|
|
|
|
43
|
|
|
$caller = $this->find_caller(debug_backtrace()); |
44
|
|
|
self::$all_results[] = array( |
45
|
|
|
'file' => $caller[0], |
46
|
|
|
'tests' => array(), |
47
|
|
|
'stats' => array( |
48
|
|
|
'plan' => $plan, |
49
|
|
|
'total' => 0, |
50
|
|
|
'failed' => array(), |
51
|
|
|
'passed' => array(), |
52
|
|
|
'skipped' => array(), |
53
|
|
|
'errors' => array() |
54
|
|
|
), |
55
|
|
|
); |
56
|
|
|
|
57
|
|
|
$this->results = &self::$all_results[count(self::$all_results) - 1]; |
58
|
|
|
|
59
|
|
|
null !== $plan and $this->output->echoln(sprintf("1..%d", $plan)); |
60
|
|
|
|
61
|
|
|
set_error_handler(array($this, 'handle_error')); |
62
|
|
|
set_exception_handler(array($this, 'handle_exception')); |
63
|
|
|
} |
64
|
|
|
|
65
|
|
|
static public function reset() |
66
|
|
|
{ |
67
|
|
|
self::$all_results = array(); |
68
|
|
|
} |
69
|
|
|
|
70
|
|
|
/** |
71
|
|
|
* @return array |
72
|
|
|
*/ |
73
|
|
|
static public function to_array() |
74
|
|
|
{ |
75
|
|
|
return self::$all_results; |
76
|
|
|
} |
77
|
|
|
|
78
|
|
|
/** |
79
|
|
|
* @param null $results |
80
|
|
|
* @return string |
81
|
|
|
*/ |
82
|
|
|
static public function to_xml($results = null) |
83
|
|
|
{ |
84
|
|
|
if (is_null($results)) { |
85
|
|
|
$results = self::$all_results; |
86
|
|
|
} |
87
|
|
|
|
88
|
|
|
$dom = new DOMDocument('1.0', 'UTF-8'); |
89
|
|
|
$dom->formatOutput = true; |
90
|
|
|
$dom->appendChild($testsuites = $dom->createElement('testsuites')); |
91
|
|
|
|
92
|
|
|
$failures = 0; |
93
|
|
|
$errors = 0; |
94
|
|
|
$skipped = 0; |
95
|
|
|
$assertions = 0; |
96
|
|
|
|
97
|
|
|
foreach ($results as $result) { |
98
|
|
|
$testsuites->appendChild($testsuite = $dom->createElement('testsuite')); |
99
|
|
|
$testsuite->setAttribute('name', basename($result['file'], '.php')); |
100
|
|
|
$testsuite->setAttribute('file', $result['file']); |
101
|
|
|
$testsuite->setAttribute('failures', count($result['stats']['failed'])); |
102
|
|
|
$testsuite->setAttribute('errors', count($result['stats']['errors'])); |
103
|
|
|
$testsuite->setAttribute('skipped', count($result['stats']['skipped'])); |
104
|
|
|
$testsuite->setAttribute('tests', $result['stats']['plan']); |
105
|
|
|
$testsuite->setAttribute('assertions', $result['stats']['plan']); |
106
|
|
|
|
107
|
|
|
$failures += count($result['stats']['failed']); |
108
|
|
|
$errors += count($result['stats']['errors']); |
109
|
|
|
$skipped += count($result['stats']['skipped']); |
110
|
|
|
$assertions += $result['stats']['plan']; |
111
|
|
|
|
112
|
|
|
foreach ($result['tests'] as $test) { |
113
|
|
|
$testsuite->appendChild($testcase = $dom->createElement('testcase')); |
114
|
|
|
$testcase->setAttribute('name', $test['message']); |
115
|
|
|
$testcase->setAttribute('file', $test['file']); |
116
|
|
|
$testcase->setAttribute('line', $test['line']); |
117
|
|
|
$testcase->setAttribute('assertions', 1); |
118
|
|
|
if (!$test['status']) { |
119
|
|
|
$testcase->appendChild($failure = $dom->createElement('failure')); |
120
|
|
|
$failure->setAttribute('type', 'lime'); |
121
|
|
|
if (isset($test['error'])) { |
122
|
|
|
$failure->appendChild($dom->createTextNode($test['error'])); |
123
|
|
|
} |
124
|
|
|
} |
125
|
|
|
} |
126
|
|
|
} |
127
|
|
|
|
128
|
|
|
$testsuites->setAttribute('failures', $failures); |
129
|
|
|
$testsuites->setAttribute('errors', $errors); |
130
|
|
|
$testsuites->setAttribute('tests', $assertions); |
131
|
|
|
$testsuites->setAttribute('assertions', $assertions); |
132
|
|
|
$testsuites->setAttribute('skipped', $skipped); |
133
|
|
|
|
134
|
|
|
return $dom->saveXml(); |
135
|
|
|
} |
136
|
|
|
|
137
|
|
|
/** |
138
|
|
|
* |
139
|
|
|
*/ |
140
|
|
|
public function __destruct() |
141
|
|
|
{ |
142
|
|
|
$plan = $this->results['stats']['plan']; |
143
|
|
|
$passed = count($this->results['stats']['passed']); |
144
|
|
|
$failed = count($this->results['stats']['failed']); |
145
|
|
|
$total = $this->results['stats']['total']; |
146
|
|
|
is_null($plan) and $plan = $total and $this->output->echoln(sprintf("1..%d", $plan)); |
147
|
|
|
|
148
|
|
|
if ($total > $plan) { |
149
|
|
|
$this->output->red_bar(sprintf("# Looks like you planned %d tests but ran %d extra.", $plan, |
150
|
|
|
$total - $plan)); |
151
|
|
|
} elseif ($total < $plan) { |
152
|
|
|
$this->output->red_bar(sprintf("# Looks like you planned %d tests but only ran %d.", $plan, $total)); |
153
|
|
|
} |
154
|
|
|
|
155
|
|
|
if ($failed) { |
156
|
|
|
$this->output->red_bar(sprintf("# Looks like you failed %d tests of %d.", $failed, $passed + $failed)); |
157
|
|
|
} else { |
158
|
|
|
if ($total == $plan) { |
159
|
|
|
$this->output->green_bar("# Looks like everything went fine."); |
160
|
|
|
} |
161
|
|
|
} |
162
|
|
|
|
163
|
|
|
flush(); |
164
|
|
|
} |
165
|
|
|
|
166
|
|
|
/** |
167
|
|
|
* Tests a condition and passes if it is true |
168
|
|
|
* |
169
|
|
|
* @param mixed $exp condition to test |
170
|
|
|
* @param string $message display output message when the test passes |
171
|
|
|
* |
172
|
|
|
* @return boolean |
173
|
|
|
*/ |
174
|
|
|
public function ok($exp, $message = '') |
175
|
|
|
{ |
176
|
|
|
$this->update_stats(); |
177
|
|
|
|
178
|
|
|
if ($result = (boolean)$exp) { |
179
|
|
|
$this->results['stats']['passed'][] = $this->test_nb; |
180
|
|
|
} else { |
181
|
|
|
$this->results['stats']['failed'][] = $this->test_nb; |
182
|
|
|
} |
183
|
|
|
$this->results['tests'][$this->test_nb]['message'] = $message; |
184
|
|
|
$this->results['tests'][$this->test_nb]['status'] = $result; |
185
|
|
|
$this->output->echoln(sprintf("%s %d%s", $result ? 'ok' : 'not ok', $this->test_nb, |
186
|
|
|
$message = $message ? sprintf('%s %s', 0 === strpos($message, '#') ? '' : ' -', $message) : '')); |
187
|
|
|
|
188
|
|
|
if (!$result) { |
189
|
|
|
$this->output->diag(sprintf(' Failed test (%s at line %d)', |
190
|
|
|
str_replace(getcwd(), '.', $this->results['tests'][$this->test_nb]['file']), |
191
|
|
|
$this->results['tests'][$this->test_nb]['line'])); |
192
|
|
|
} |
193
|
|
|
|
194
|
|
|
return $result; |
195
|
|
|
} |
196
|
|
|
|
197
|
|
|
/** |
198
|
|
|
* Compares two values and passes if they are equal (==) |
199
|
|
|
* |
200
|
|
|
* @param mixed $exp1 left value |
201
|
|
|
* @param mixed $exp2 right value |
202
|
|
|
* @param string $message display output message when the test passes |
203
|
|
|
* |
204
|
|
|
* @return boolean |
205
|
|
|
*/ |
206
|
|
|
public function is($exp1, $exp2, $message = '') |
207
|
|
|
{ |
208
|
|
|
if (is_object($exp1) || is_object($exp2)) { |
209
|
|
|
$value = $exp1 === $exp2; |
210
|
|
|
} else { |
211
|
|
|
if (is_float($exp1) && is_float($exp2)) { |
212
|
|
|
$value = abs($exp1 - $exp2) < self::EPSILON; |
213
|
|
|
} else { |
214
|
|
|
$value = $exp1 == $exp2; |
215
|
|
|
} |
216
|
|
|
} |
217
|
|
|
|
218
|
|
|
if (!$result = $this->ok($value, $message)) { |
219
|
|
|
$this->set_last_test_errors(array( |
220
|
|
|
sprintf(" got: %s", var_export($exp1, true)), |
221
|
|
|
sprintf(" expected: %s", var_export($exp2, true)) |
222
|
|
|
)); |
223
|
|
|
} |
224
|
|
|
|
225
|
|
|
return $result; |
226
|
|
|
} |
227
|
|
|
|
228
|
|
|
/** |
229
|
|
|
* Compares two values and passes if they are not equal |
230
|
|
|
* |
231
|
|
|
* @param mixed $exp1 left value |
232
|
|
|
* @param mixed $exp2 right value |
233
|
|
|
* @param string $message display output message when the test passes |
234
|
|
|
* |
235
|
|
|
* @return boolean |
236
|
|
|
*/ |
237
|
|
|
public function isnt($exp1, $exp2, $message = '') |
238
|
|
|
{ |
239
|
|
|
if (!$result = $this->ok($exp1 != $exp2, $message)) { |
240
|
|
|
$this->set_last_test_errors(array( |
241
|
|
|
sprintf(" %s", var_export($exp2, true)), |
242
|
|
|
' ne', |
243
|
|
|
sprintf(" %s", var_export($exp2, true)) |
244
|
|
|
)); |
245
|
|
|
} |
246
|
|
|
|
247
|
|
|
return $result; |
248
|
|
|
} |
249
|
|
|
|
250
|
|
|
/** |
251
|
|
|
* Tests a string against a regular expression |
252
|
|
|
* |
253
|
|
|
* @param string $exp value to test |
254
|
|
|
* @param string $regex the pattern to search for, as a string |
255
|
|
|
* @param string $message display output message when the test passes |
256
|
|
|
* |
257
|
|
|
* @return boolean |
258
|
|
|
*/ |
259
|
|
|
public function like($exp, $regex, $message = '') |
260
|
|
|
{ |
261
|
|
|
if (!$result = $this->ok(preg_match($regex, $exp), $message)) { |
262
|
|
|
$this->set_last_test_errors(array( |
263
|
|
|
sprintf(" '%s'", $exp), |
264
|
|
|
sprintf(" doesn't match '%s'", $regex) |
265
|
|
|
)); |
266
|
|
|
} |
267
|
|
|
|
268
|
|
|
return $result; |
269
|
|
|
} |
270
|
|
|
|
271
|
|
|
/** |
272
|
|
|
* Checks that a string doesn't match a regular expression |
273
|
|
|
* |
274
|
|
|
* @param string $exp value to test |
275
|
|
|
* @param string $regex the pattern to search for, as a string |
276
|
|
|
* @param string $message display output message when the test passes |
277
|
|
|
* |
278
|
|
|
* @return boolean |
279
|
|
|
*/ |
280
|
|
|
public function unlike($exp, $regex, $message = '') |
281
|
|
|
{ |
282
|
|
|
if (!$result = $this->ok(!preg_match($regex, $exp), $message)) { |
283
|
|
|
$this->set_last_test_errors(array( |
284
|
|
|
sprintf(" '%s'", $exp), |
285
|
|
|
sprintf(" matches '%s'", $regex) |
286
|
|
|
)); |
287
|
|
|
} |
288
|
|
|
|
289
|
|
|
return $result; |
290
|
|
|
} |
291
|
|
|
|
292
|
|
|
/** |
293
|
|
|
* Compares two arguments with an operator |
294
|
|
|
* |
295
|
|
|
* @param mixed $exp1 left value |
296
|
|
|
* @param string $op operator |
297
|
|
|
* @param mixed $exp2 right value |
298
|
|
|
* @param string $message display output message when the test passes |
299
|
|
|
* |
300
|
|
|
* @return boolean |
301
|
|
|
*/ |
302
|
|
|
public function cmp_ok($exp1, $op, $exp2, $message = '') |
303
|
|
|
{ |
304
|
|
|
$result = false; |
305
|
|
|
$php = sprintf("\$result = \$exp1 $op \$exp2;"); |
306
|
|
|
// under some unknown conditions the sprintf() call causes a segmentation fault |
307
|
|
|
// when placed directly in the eval() call |
308
|
|
|
eval($php); |
|
|
|
|
309
|
|
|
|
310
|
|
|
if (!$this->ok($result, $message)) { |
311
|
|
|
$this->set_last_test_errors(array( |
312
|
|
|
sprintf(" %s", str_replace("\n", '', var_export($exp1, true))), |
313
|
|
|
sprintf(" %s", $op), |
314
|
|
|
sprintf(" %s", str_replace("\n", '', var_export($exp2, true))) |
315
|
|
|
)); |
316
|
|
|
} |
317
|
|
|
|
318
|
|
|
return $result; |
319
|
|
|
} |
320
|
|
|
|
321
|
|
|
/** |
322
|
|
|
* Checks the availability of a method for an object or a class |
323
|
|
|
* |
324
|
|
|
* @param mixed $object an object instance or a class name |
325
|
|
|
* @param string|array $methods one or more method names |
326
|
|
|
* @param string $message display output message when the test passes |
327
|
|
|
* |
328
|
|
|
* @return boolean |
329
|
|
|
*/ |
330
|
|
|
public function can_ok($object, $methods, $message = '') |
331
|
|
|
{ |
332
|
|
|
$result = true; |
333
|
|
|
$failed_messages = array(); |
334
|
|
|
foreach ((array)$methods as $method) { |
335
|
|
|
if (!method_exists($object, $method)) { |
336
|
|
|
$failed_messages[] = sprintf(" method '%s' does not exist", $method); |
337
|
|
|
$result = false; |
338
|
|
|
} |
339
|
|
|
} |
340
|
|
|
|
341
|
|
|
!$this->ok($result, $message); |
342
|
|
|
|
343
|
|
|
!$result and $this->set_last_test_errors($failed_messages); |
344
|
|
|
|
345
|
|
|
return $result; |
346
|
|
|
} |
347
|
|
|
|
348
|
|
|
/** |
349
|
|
|
* Checks the type of an argument |
350
|
|
|
* |
351
|
|
|
* @param mixed $var variable instance |
352
|
|
|
* @param string $class class or type name |
353
|
|
|
* @param string $message display output message when the test passes |
354
|
|
|
* |
355
|
|
|
* @return boolean |
356
|
|
|
*/ |
357
|
|
|
public function isa_ok($var, $class, $message = '') |
358
|
|
|
{ |
359
|
|
|
$type = is_object($var) ? get_class($var) : gettype($var); |
360
|
|
|
if (!$result = $this->ok($type == $class, $message)) { |
361
|
|
|
$this->set_last_test_errors(array(sprintf(" variable isn't a '%s' it's a '%s'", $class, $type))); |
362
|
|
|
} |
363
|
|
|
|
364
|
|
|
return $result; |
365
|
|
|
} |
366
|
|
|
|
367
|
|
|
/** |
368
|
|
|
* Checks that two arrays have the same values |
369
|
|
|
* |
370
|
|
|
* @param mixed $exp1 first variable |
371
|
|
|
* @param mixed $exp2 second variable |
372
|
|
|
* @param string $message display output message when the test passes |
373
|
|
|
* |
374
|
|
|
* @return boolean |
375
|
|
|
*/ |
376
|
|
|
public function is_deeply($exp1, $exp2, $message = '') |
377
|
|
|
{ |
378
|
|
|
if (!$result = $this->ok($this->test_is_deeply($exp1, $exp2), $message)) { |
379
|
|
|
$this->set_last_test_errors(array( |
380
|
|
|
sprintf(" got: %s", str_replace("\n", '', var_export($exp1, true))), |
381
|
|
|
sprintf(" expected: %s", str_replace("\n", '', var_export($exp2, true))) |
382
|
|
|
)); |
383
|
|
|
} |
384
|
|
|
|
385
|
|
|
return $result; |
386
|
|
|
} |
387
|
|
|
|
388
|
|
|
/** |
389
|
|
|
* is_ignore_nl function. |
390
|
|
|
* |
391
|
|
|
* @access public |
392
|
|
|
* @param mixed $exp1 |
393
|
|
|
* @param mixed $exp2 |
394
|
|
|
* @param string $message (default: '') |
395
|
|
|
* @return boolean |
396
|
|
|
*/ |
397
|
|
|
public function is_ignore_nl($exp1, $exp2, $message = '') |
398
|
|
|
{ |
399
|
|
|
return $this->is( |
400
|
|
|
str_replace("\n", '', $exp1), |
401
|
|
|
str_replace("\n", '', $exp2), |
402
|
|
|
$message |
403
|
|
|
); |
404
|
|
|
} |
405
|
|
|
|
406
|
|
|
/** |
407
|
|
|
* Sortcut for {@link is}(), performs a strict check. |
408
|
|
|
* |
409
|
|
|
* @access public |
410
|
|
|
* @param mixed $exp1 |
411
|
|
|
* @param mixed $exp2 |
412
|
|
|
* @param string $message (default: '') |
413
|
|
|
* @return boolean |
414
|
|
|
*/ |
415
|
|
|
public function is_strict($exp1, $exp2, $message = '') |
416
|
|
|
{ |
417
|
|
|
if (is_float($exp1) && is_float($exp2)) { |
418
|
|
|
$value = abs($exp1 - $exp2) < self::EPSILON; |
419
|
|
|
} else { |
420
|
|
|
$value = $exp1 === $exp2; |
421
|
|
|
} |
422
|
|
|
|
423
|
|
|
if (!$result = $this->ok($value, $message)) { |
424
|
|
|
$this->set_last_test_errors(array( |
425
|
|
|
sprintf(" got: (%s) %s", gettype($exp1), var_export($exp1, true)), |
426
|
|
|
sprintf(" expected: (%s) %s", gettype($exp2), var_export($exp2, true)) |
427
|
|
|
)); |
428
|
|
|
} |
429
|
|
|
|
430
|
|
|
return $result; |
431
|
|
|
} |
432
|
|
|
|
433
|
|
|
/** |
434
|
|
|
* Sortcut for {@link isnt}(), performs a strict check. |
435
|
|
|
* |
436
|
|
|
* @access public |
437
|
|
|
* @param mixed $exp1 |
438
|
|
|
* @param mixed $exp2 |
439
|
|
|
* @param string $message . (default: '') |
440
|
|
|
* @return boolean |
441
|
|
|
*/ |
442
|
|
|
public function isnt_strict($exp1, $exp2, $message = '') |
443
|
|
|
{ |
444
|
|
|
if (!$result = $this->ok($exp1 !== $exp2, $message)) { |
445
|
|
|
$this->set_last_test_errors(array( |
446
|
|
|
sprintf(" (%s) %s", gettype($exp1), var_export($exp1, true)), |
447
|
|
|
' ne', |
448
|
|
|
sprintf(" (%s) %s", gettype($exp2), var_export($exp2, true)) |
449
|
|
|
)); |
450
|
|
|
} |
451
|
|
|
|
452
|
|
|
return $result; |
453
|
|
|
} |
454
|
|
|
|
455
|
|
|
/** |
456
|
|
|
* Always passes--useful for testing exceptions |
457
|
|
|
* |
458
|
|
|
* @param string $message display output message |
459
|
|
|
* |
460
|
|
|
* @return boolean |
461
|
|
|
*/ |
462
|
|
|
public function pass($message = '') |
463
|
|
|
{ |
464
|
|
|
return $this->ok(true, $message); |
465
|
|
|
} |
466
|
|
|
|
467
|
|
|
/** |
468
|
|
|
* Always fails--useful for testing exceptions |
469
|
|
|
* |
470
|
|
|
* @param string $message display output message |
471
|
|
|
* |
472
|
|
|
* @return false |
473
|
|
|
*/ |
474
|
|
|
public function fail($message = '') |
475
|
|
|
{ |
476
|
|
|
return $this->ok(false, $message); |
477
|
|
|
} |
478
|
|
|
|
479
|
|
|
/** |
480
|
|
|
* Outputs a diag message but runs no test |
481
|
|
|
* |
482
|
|
|
* @param string $message display output message |
483
|
|
|
* |
484
|
|
|
* @return void |
485
|
|
|
*/ |
486
|
|
|
public function diag($message) |
487
|
|
|
{ |
488
|
|
|
$this->output->diag($message); |
489
|
|
|
} |
490
|
|
|
|
491
|
|
|
/** |
492
|
|
|
* Counts as $nb_tests tests--useful for conditional tests |
493
|
|
|
* |
494
|
|
|
* @param string $message display output message |
495
|
|
|
* @param integer $nb_tests number of tests to skip |
496
|
|
|
* |
497
|
|
|
* @return void |
498
|
|
|
*/ |
499
|
|
|
public function skip($message = '', $nb_tests = 1) |
500
|
|
|
{ |
501
|
|
|
for ($i = 0; $i < $nb_tests; $i++) { |
502
|
|
|
$this->pass(sprintf("# SKIP%s", $message ? ' ' . $message : '')); |
503
|
|
|
$this->results['stats']['skipped'][] = $this->test_nb; |
504
|
|
|
array_pop($this->results['stats']['passed']); |
505
|
|
|
} |
506
|
|
|
} |
507
|
|
|
|
508
|
|
|
/** |
509
|
|
|
* Counts as a test--useful for tests yet to be written |
510
|
|
|
* |
511
|
|
|
* @param string $message display output message |
512
|
|
|
* |
513
|
|
|
* @return void |
514
|
|
|
*/ |
515
|
|
|
public function todo($message = '') |
516
|
|
|
{ |
517
|
|
|
$this->pass(sprintf("# TODO%s", $message ? ' ' . $message : '')); |
518
|
|
|
$this->results['stats']['skipped'][] = $this->test_nb; |
519
|
|
|
array_pop($this->results['stats']['passed']); |
520
|
|
|
} |
521
|
|
|
|
522
|
|
|
/** |
523
|
|
|
* Validates that a file exists and that it is properly included |
524
|
|
|
* |
525
|
|
|
* @param string $file file path |
526
|
|
|
* @param string $message display output message when the test passes |
527
|
|
|
* |
528
|
|
|
* @return boolean |
529
|
|
|
*/ |
530
|
|
|
public function include_ok($file, $message = '') |
531
|
|
|
{ |
532
|
|
|
if (!$result = $this->ok((@include($file)) == 1, $message)) { |
533
|
|
|
$this->set_last_test_errors(array(sprintf(" Tried to include '%s'", $file))); |
534
|
|
|
} |
535
|
|
|
|
536
|
|
|
return $result; |
537
|
|
|
} |
538
|
|
|
|
539
|
|
|
/** |
540
|
|
|
* @param $var1 |
541
|
|
|
* @param $var2 |
542
|
|
|
* @return bool |
543
|
|
|
*/ |
544
|
|
|
private function test_is_deeply($var1, $var2) |
545
|
|
|
{ |
546
|
|
|
if (gettype($var1) != gettype($var2)) { |
547
|
|
|
return false; |
548
|
|
|
} |
549
|
|
|
|
550
|
|
|
if (is_array($var1)) { |
551
|
|
|
ksort($var1); |
552
|
|
|
ksort($var2); |
553
|
|
|
|
554
|
|
|
$keys1 = array_keys($var1); |
555
|
|
|
$keys2 = array_keys($var2); |
556
|
|
|
if (array_diff($keys1, $keys2) || array_diff($keys2, $keys1)) { |
557
|
|
|
return false; |
558
|
|
|
} |
559
|
|
|
$is_equal = true; |
560
|
|
|
foreach ($var1 as $key => $value) { |
561
|
|
|
$is_equal = $this->test_is_deeply($var1[$key], $var2[$key]); |
562
|
|
|
if ($is_equal === false) { |
563
|
|
|
break; |
564
|
|
|
} |
565
|
|
|
} |
566
|
|
|
|
567
|
|
|
return $is_equal; |
568
|
|
|
} else { |
569
|
|
|
return $var1 === $var2; |
570
|
|
|
} |
571
|
|
|
} |
572
|
|
|
|
573
|
|
|
/** |
574
|
|
|
* @param $message |
575
|
|
|
*/ |
576
|
|
|
public function comment($message) |
577
|
|
|
{ |
578
|
|
|
$this->output->comment($message); |
579
|
|
|
} |
580
|
|
|
|
581
|
|
|
/** |
582
|
|
|
* @param $message |
583
|
|
|
*/ |
584
|
|
|
public function info($message) |
585
|
|
|
{ |
586
|
|
|
$this->output->info($message); |
587
|
|
|
} |
588
|
|
|
|
589
|
|
|
/** |
590
|
|
|
* @param string $message |
591
|
|
|
*/ |
592
|
|
|
public function error($message, $file = null, $line = null, array $traces = array()) |
593
|
|
|
{ |
594
|
|
|
$this->output->error($message, $file, $line, $traces); |
595
|
|
|
|
596
|
|
|
$this->results['stats']['errors'][] = array( |
597
|
|
|
'message' => $message, |
598
|
|
|
'file' => $file, |
599
|
|
|
'line' => $line, |
600
|
|
|
); |
601
|
|
|
} |
602
|
|
|
|
603
|
|
|
protected function update_stats() |
604
|
|
|
{ |
605
|
|
|
++$this->test_nb; |
606
|
|
|
++$this->results['stats']['total']; |
607
|
|
|
|
608
|
|
|
list($this->results['tests'][$this->test_nb]['file'], $this->results['tests'][$this->test_nb]['line']) = $this->find_caller(debug_backtrace()); |
609
|
|
|
} |
610
|
|
|
|
611
|
|
|
/** |
612
|
|
|
* @param array $errors |
613
|
|
|
*/ |
614
|
|
|
protected function set_last_test_errors(array $errors) |
615
|
|
|
{ |
616
|
|
|
$this->output->diag($errors); |
617
|
|
|
|
618
|
|
|
$this->results['tests'][$this->test_nb]['error'] = implode("\n", $errors); |
619
|
|
|
} |
620
|
|
|
|
621
|
|
|
/** |
622
|
|
|
* @param $traces |
623
|
|
|
* @return array |
624
|
|
|
*/ |
625
|
|
|
protected function find_caller($traces) |
626
|
|
|
{ |
627
|
|
|
// find the first call to a method of an object that is an instance of lime_test |
628
|
|
|
$t = array_reverse($traces); |
629
|
|
|
foreach ($t as $trace) { |
630
|
|
|
if (isset($trace['object']) && $trace['object'] instanceof lime_test) { |
631
|
|
|
return array($trace['file'], $trace['line']); |
632
|
|
|
} |
633
|
|
|
} |
634
|
|
|
|
635
|
|
|
// return the first call |
636
|
|
|
$last = count($traces) - 1; |
637
|
|
|
|
638
|
|
|
return array($traces[$last]['file'], $traces[$last]['line']); |
639
|
|
|
} |
640
|
|
|
|
641
|
|
|
/** |
642
|
|
|
* @param $code |
643
|
|
|
* @param $message |
644
|
|
|
* @param $file |
645
|
|
|
* @param $line |
646
|
|
|
* @param $context |
647
|
|
|
* @return bool |
648
|
|
|
* |
649
|
|
|
* @FIXME: This method has inconsistent return types |
650
|
|
|
*/ |
651
|
|
|
public function handle_error($code, $message, $file, $line, $context) |
|
|
|
|
652
|
|
|
{ |
653
|
|
|
if (!$this->options['error_reporting'] || ($code & error_reporting()) == 0) { |
654
|
|
|
return false; |
655
|
|
|
} |
656
|
|
|
|
657
|
|
|
switch ($code) { |
658
|
|
|
case E_WARNING: |
659
|
|
|
$type = 'Warning'; |
660
|
|
|
break; |
661
|
|
|
default: |
662
|
|
|
$type = 'Notice'; |
663
|
|
|
break; |
664
|
|
|
} |
665
|
|
|
|
666
|
|
|
$trace = debug_backtrace(); |
667
|
|
|
array_shift($trace); // remove the handle_error() call from the trace |
668
|
|
|
|
669
|
|
|
$this->error($type . ': ' . $message, $file, $line, $trace); |
670
|
|
|
} |
671
|
|
|
|
672
|
|
|
/** |
673
|
|
|
* @param Exception $exception |
674
|
|
|
* @return bool |
675
|
|
|
*/ |
676
|
|
|
public function handle_exception(Exception $exception) |
677
|
|
|
{ |
678
|
|
|
$this->error(get_class($exception) . ': ' . $exception->getMessage(), $exception->getFile(), |
679
|
|
|
$exception->getLine(), $exception->getTrace()); |
680
|
|
|
|
681
|
|
|
// exception was handled |
682
|
|
|
return true; |
683
|
|
|
} |
684
|
|
|
} |
On one hand,
eval
might be exploited by malicious users if they somehow manage to inject dynamic content. On the other hand, with the emergence of faster PHP runtimes like the HHVM,eval
prevents some optimization that they perform.