1: <?php
2:
3: 4: 5: 6:
7:
8: namespace Tester;
9:
10:
11: 12: 13: 14: 15:
16: class Dumper
17: {
18: public static $maxLength = 70;
19: public static $maxDepth = 50;
20: public static $dumpDir = 'output';
21: public static $maxPathSegments = 3;
22:
23: 24: 25: 26: 27:
28: public static function toLine($var)
29: {
30: static $table;
31: if ($table === NULL) {
32: foreach (array_merge(range("\x00", "\x1F"), range("\x7F", "\xFF")) as $ch) {
33: $table[$ch] = '\x' . str_pad(dechex(ord($ch)), 2, '0', STR_PAD_LEFT);
34: }
35: $table["\\"] = '\\\\';
36: $table["\r"] = '\r';
37: $table["\n"] = '\n';
38: $table["\t"] = '\t';
39: }
40:
41: if (is_bool($var)) {
42: return $var ? 'TRUE' : 'FALSE';
43:
44: } elseif ($var === NULL) {
45: return 'NULL';
46:
47: } elseif (is_int($var)) {
48: return "$var";
49:
50: } elseif (is_float($var)) {
51: if (!is_finite($var)) {
52: return var_export($var, TRUE);
53: }
54: $var = json_encode($var);
55: return strpos($var, '.') === FALSE ? $var . '.0' : $var;
56:
57: } elseif (is_string($var)) {
58: if ($cut = @iconv_strlen($var, 'UTF-8') > self::$maxLength) {
59: $var = iconv_substr($var, 0, self::$maxLength, 'UTF-8') . '...';
60: } elseif ($cut = strlen($var) > self::$maxLength) {
61: $var = substr($var, 0, self::$maxLength) . '...';
62: }
63: return (preg_match('#[^\x09\x0A\x0D\x20-\x7E\xA0-\x{10FFFF}]#u', $var) || preg_last_error() ? '"' . strtr($var, $table) . '"' : "'$var'");
64:
65: } elseif (is_array($var)) {
66: $out = '';
67: $counter = 0;
68: foreach ($var as $k => & $v) {
69: $out .= ($out === '' ? '' : ', ');
70: if (strlen($out) > self::$maxLength) {
71: $out .= '...';
72: break;
73: }
74: $out .= ($k === $counter ? '' : self::toLine($k) . ' => ')
75: . (is_array($v) ? 'array(...)' : self::toLine($v));
76: $counter = is_int($k) ? max($k + 1, $counter) : $counter;
77: }
78: return "array($out)";
79:
80: } elseif ($var instanceof \Exception) {
81: return 'Exception ' . get_class($var) . ': ' . ($var->getCode() ? '#' . $var->getCode() . ' ' : '') . $var->getMessage();
82:
83: } elseif (is_object($var)) {
84: return get_class($var) . '(#' . substr(md5(spl_object_hash($var)), 0, 4) . ')';
85:
86: } elseif (is_resource($var)) {
87: return 'resource(' . get_resource_type($var) . ')';
88:
89: } else {
90: return 'unknown type';
91: }
92: }
93:
94:
95: 96: 97: 98: 99:
100: public static function toPhp($var)
101: {
102: return self::_toPhp($var);
103: }
104:
105:
106: 107: 108:
109: private static function _toPhp(&$var, $level = 0)
110: {
111: if (is_float($var)) {
112: $var = json_encode($var);
113: return strpos($var, '.') === FALSE ? $var . '.0' : $var;
114:
115: } elseif (is_bool($var)) {
116: return $var ? 'TRUE' : 'FALSE';
117:
118: } elseif (is_string($var) && (preg_match('#[^\x09\x20-\x7E\xA0-\x{10FFFF}]#u', $var) || preg_last_error())) {
119: static $table;
120: if ($table === NULL) {
121: foreach (array_merge(range("\x00", "\x1F"), range("\x7F", "\xFF")) as $ch) {
122: $table[$ch] = '\x' . str_pad(dechex(ord($ch)), 2, '0', STR_PAD_LEFT);
123: }
124: $table['\\'] = '\\\\';
125: $table["\r"] = '\r';
126: $table["\n"] = '\n';
127: $table["\t"] = '\t';
128: $table['$'] = '\$';
129: $table['"'] = '\"';
130: }
131: return '"' . strtr($var, $table) . '"';
132:
133: } elseif (is_array($var)) {
134: $space = str_repeat("\t", $level);
135:
136: static $marker;
137: if ($marker === NULL) {
138: $marker = uniqid("\x00", TRUE);
139: }
140: if (empty($var)) {
141: $out = '';
142:
143: } elseif ($level > self::$maxDepth || isset($var[$marker])) {
144: return '/* Nesting level too deep or recursive dependency */';
145:
146: } else {
147: $out = '';
148: $outAlt = "\n$space";
149: $var[$marker] = TRUE;
150: $counter = 0;
151: foreach ($var as $k => &$v) {
152: if ($k !== $marker) {
153: $item = ($k === $counter ? '' : self::_toPhp($k, $level + 1) . ' => ') . self::_toPhp($v, $level + 1);
154: $counter = is_int($k) ? max($k + 1, $counter) : $counter;
155: $out .= ($out === '' ? '' : ', ') . $item;
156: $outAlt .= "\t$item,\n$space";
157: }
158: }
159: unset($var[$marker]);
160: }
161: return 'array(' . (strpos($out, "\n") === FALSE && strlen($out) < self::$maxLength ? $out : $outAlt) . ')';
162:
163: } elseif (is_object($var)) {
164: $arr = (array) $var;
165: $space = str_repeat("\t", $level);
166:
167: static $list = array();
168: if (empty($arr)) {
169: $out = '';
170:
171: } elseif ($level > self::$maxDepth || in_array($var, $list, TRUE)) {
172: return '/* Nesting level too deep or recursive dependency */';
173:
174: } else {
175: $out = "\n";
176: $list[] = $var;
177: foreach ($arr as $k => &$v) {
178: if ($k[0] === "\x00") {
179: $k = substr($k, strrpos($k, "\x00") + 1);
180: }
181: $out .= "$space\t" . self::_toPhp($k, $level + 1) . ' => ' . self::_toPhp($v, $level + 1) . ",\n";
182: }
183: array_pop($list);
184: $out .= $space;
185: }
186: return get_class($var) === 'stdClass'
187: ? "(object) array($out)"
188: : get_class($var) . "::__set_state(array($out))";
189:
190: } elseif (is_resource($var)) {
191: return '/* resource ' . get_resource_type($var) . ' */';
192:
193: } else {
194: return var_export($var, TRUE);
195: }
196: }
197:
198:
199:
200: public static function dumpException(\Exception $e)
201: {
202: $trace = $e->getTrace();
203: array_splice($trace, 0, $e instanceof \ErrorException ? 1 : 0, array(array('file' => $e->getFile(), 'line' => $e->getLine())));
204:
205: $testFile = NULL;
206: foreach (array_reverse($trace) as $item) {
207: if (isset($item['file'])) {
208: $testFile = $item['file'];
209: break;
210: }
211: }
212:
213: if ($e instanceof AssertException) {
214: if (is_object($e->expected) || is_array($e->expected) || (is_string($e->expected) && strlen($e->expected) > self::$maxLength)
215: || is_object($e->actual) || is_array($e->actual) || (is_string($e->actual) && strlen($e->actual) > self::$maxLength)
216: ) {
217: $args = isset($_SERVER['argv'][1]) ? '.[' . preg_replace('#[^a-z0-9-. ]+#i', '_', $_SERVER['argv'][1]) . ']' : '';
218: $stored[] = self::saveOutput($testFile, $e->expected, $args . '.expected');
219: $stored[] = self::saveOutput($testFile, $e->actual, $args . '.actual');
220: }
221:
222: if ((is_string($e->actual) && is_string($e->expected))) {
223: for ($i = 0; $i < strlen($e->actual) && isset($e->expected[$i]) && $e->actual[$i] === $e->expected[$i]; $i++);
224: $i = max(0, min($i, max(strlen($e->actual), strlen($e->expected)) - self::$maxLength + 3));
225: for (; $i && $i < count($e->actual) && $e->actual[$i-1] >= "\x80" && $e->actual[$i] >= "\x80" && $e->actual[$i] < "\xC0"; $i--);
226: if ($i) {
227: $e->expected = substr_replace($e->expected, '...', 0, $i);
228: $e->actual = substr_replace($e->actual, '...', 0, $i);
229: }
230: }
231:
232: $message = 'Failed: ' . $e->getMessage();
233: if (((is_string($e->actual) && is_string($e->expected)) || (is_array($e->actual) && is_array($e->expected)))
234: && preg_match('#^(.*)(%\d)(.*)(%\d.*)\z#s', $message, $m)
235: ) {
236: if (($delta = strlen($m[1]) - strlen($m[3])) >= 3) {
237: $message = "$m[1]$m[2]\n" . str_repeat(' ', $delta - 3) . "...$m[3]$m[4]";
238: } else {
239: $message = "$m[1]$m[2]$m[3]\n" . str_repeat(' ', strlen($m[1]) - 4) . "... $m[4]";
240: }
241: }
242: $message = strtr($message, array(
243: '%1' => "\033[1;33m" . Dumper::toLine($e->actual) . "\033[1;37m",
244: '%2' => "\033[1;33m" . Dumper::toLine($e->expected) . "\033[1;37m",
245: ));
246: } else {
247: $message = ($e instanceof \ErrorException ? Helpers::errorTypeToString($e->getSeverity()) : get_class($e))
248: . ": {$e->getMessage()}";
249: }
250:
251: $s = "\033[1;37m$message\033[0m\n\n"
252: . (isset($stored) ? 'diff ' . Helpers::escapeArg($stored[0]) . ' ' . Helpers::escapeArg($stored[1]) . "\n\n" : '');
253:
254: foreach ($trace as $item) {
255: $s .= 'in '
256: . (isset($item['file'])
257: ? (
258: ($item['file'] === $testFile ? "\033[1;37m" : '')
259: . implode(DIRECTORY_SEPARATOR, array_slice(explode(DIRECTORY_SEPARATOR, $item['file']), -self::$maxPathSegments))
260: . "($item[line])\033[1;30m "
261: )
262: : '[internal function]'
263: )
264: . (isset($item['class']) ? $item['class'] . $item['type'] : '')
265: . (isset($item['function']) ? $item['function'] . '()' : '')
266: . "\033[0m\n";
267: }
268:
269: if ($e->getPrevious()) {
270: $s .= "\n(previous) " . static::dumpException($e->getPrevious());
271: }
272: return $s;
273: }
274:
275:
276: 277: 278: 279: 280:
281: public static function saveOutput($testFile, $content, $suffix = '')
282: {
283: $path = self::$dumpDir . DIRECTORY_SEPARATOR . basename($testFile, '.phpt') . $suffix;
284: if (!preg_match('#/|\w:#A', self::$dumpDir)) {
285: $path = dirname($testFile) . DIRECTORY_SEPARATOR . $path;
286: }
287: @mkdir(dirname($path));
288: file_put_contents($path, is_string($content) ? $content : self::toPhp($content));
289: return $path;
290: }
291:
292:
293: public static function removeColors($s)
294: {
295: return preg_replace('#\033\[[\d;]+m#', '', $s);
296: }
297:
298: }
299: