<?php /** * base include file for SimpleTest * @package SimpleTest * @subpackage UnitTester * @version $Id: dumper.php 1532 2006-12-01 12:28:55Z xue $ */ /** * does type matter */ if (! defined('TYPE_MATTERS')) { define('TYPE_MATTERS', true); } /** * Displays variables as text and does diffs. * @package SimpleTest * @subpackage UnitTester */ class SimpleDumper { /** * Renders a variable in a shorter form than print_r(). * @param mixed $value Variable to render as a string. * @return string Human readable string form. * @access public */ function describeValue($value) { $type = $this->getType($value); switch($type) { case "Null": return "NULL"; case "Boolean": return "Boolean: " . ($value ? "true" : "false"); case "Array": return "Array: " . count($value) . " items"; case "Object": return "Object: of " . get_class($value); case "String": return "String: " . $this->clipString($value, 200); default: return "$type: $value"; } return "Unknown"; } /** * Gets the string representation of a type. * @param mixed $value Variable to check against. * @return string Type. * @access public */ function getType($value) { if (! isset($value)) { return "Null"; } elseif (is_bool($value)) { return "Boolean"; } elseif (is_string($value)) { return "String"; } elseif (is_integer($value)) { return "Integer"; } elseif (is_float($value)) { return "Float"; } elseif (is_array($value)) { return "Array"; } elseif (is_resource($value)) { return "Resource"; } elseif (is_object($value)) { return "Object"; } return "Unknown"; } /** * Creates a human readable description of the * difference between two variables. Uses a * dynamic call. * @param mixed $first First variable. * @param mixed $second Value to compare with. * @param boolean $identical If true then type anomolies count. * @return string Description of difference. * @access public */ function describeDifference($first, $second, $identical = false) { if ($identical) { if (! $this->_isTypeMatch($first, $second)) { return "with type mismatch as [" . $this->describeValue($first) . "] does not match [" . $this->describeValue($second) . "]"; } } $type = $this->getType($first); if ($type == "Unknown") { return "with unknown type"; } $method = '_describe' . $type . 'Difference'; return $this->$method($first, $second, $identical); } /** * Tests to see if types match. * @param mixed $first First variable. * @param mixed $second Value to compare with. * @return boolean True if matches. * @access private */ function _isTypeMatch($first, $second) { return ($this->getType($first) == $this->getType($second)); } /** * Clips a string to a maximum length. * @param string $value String to truncate. * @param integer $size Minimum string size to show. * @param integer $position Centre of string section. * @return string Shortened version. * @access public */ function clipString($value, $size, $position = 0) { $length = strlen($value); if ($length <= $size) { return $value; } $position = min($position, $length); $start = ($size/2 > $position ? 0 : $position - $size/2); if ($start + $size > $length) { $start = $length - $size; } $value = substr($value, $start, $size); return ($start > 0 ? "..." : "") . $value . ($start + $size < $length ? "..." : ""); } /** * Creates a human readable description of the * difference between two variables. The minimal * version. * @param null $first First value. * @param mixed $second Value to compare with. * @return string Human readable description. * @access private */ function _describeGenericDifference($first, $second) { return "as [" . $this->describeValue($first) . "] does not match [" . $this->describeValue($second) . "]"; } /** * Creates a human readable description of the * difference between a null and another variable. * @param null $first First null. * @param mixed $second Null to compare with. * @param boolean $identical If true then type anomolies count. * @return string Human readable description. * @access private */ function _describeNullDifference($first, $second, $identical) { return $this->_describeGenericDifference($first, $second); } /** * Creates a human readable description of the * difference between a boolean and another variable. * @param boolean $first First boolean. * @param mixed $second Boolean to compare with. * @param boolean $identical If true then type anomolies count. * @return string Human readable description. * @access private */ function _describeBooleanDifference($first, $second, $identical) { return $this->_describeGenericDifference($first, $second); } /** * Creates a human readable description of the * difference between a string and another variable. * @param string $first First string. * @param mixed $second String to compare with. * @param boolean $identical If true then type anomolies count. * @return string Human readable description. * @access private */ function _describeStringDifference($first, $second, $identical) { if (is_object($second) || is_array($second)) { return $this->_describeGenericDifference($first, $second); } $position = $this->_stringDiffersAt($first, $second); $message = "at character $position"; $message .= " with [" . $this->clipString($first, 200, $position) . "] and [" . $this->clipString($second, 200, $position) . "]"; return $message; } /** * Creates a human readable description of the * difference between an integer and another variable. * @param integer $first First number. * @param mixed $second Number to compare with. * @param boolean $identical If true then type anomolies count. * @return string Human readable description. * @access private */ function _describeIntegerDifference($first, $second, $identical) { if (is_object($second) || is_array($second)) { return $this->_describeGenericDifference($first, $second); } return "because [" . $this->describeValue($first) . "] differs from [" . $this->describeValue($second) . "] by " . abs($first - $second); } /** * Creates a human readable description of the * difference between two floating point numbers. * @param float $first First float. * @param mixed $second Float to compare with. * @param boolean $identical If true then type anomolies count. * @return string Human readable description. * @access private */ function _describeFloatDifference($first, $second, $identical) { if (is_object($second) || is_array($second)) { return $this->_describeGenericDifference($first, $second); } return "because [" . $this->describeValue($first) . "] differs from [" . $this->describeValue($second) . "] by " . abs($first - $second); } /** * Creates a human readable description of the * difference between two arrays. * @param array $first First array. * @param mixed $second Array to compare with. * @param boolean $identical If true then type anomolies count. * @return string Human readable description. * @access private */ function _describeArrayDifference($first, $second, $identical) { if (! is_array($second)) { return $this->_describeGenericDifference($first, $second); } if (! $this->_isMatchingKeys($first, $second, $identical)) { return "as key list [" . implode(", ", array_keys($first)) . "] does not match key list [" . implode(", ", array_keys($second)) . "]"; } foreach (array_keys($first) as $key) { if ($identical && ($first[$key] === $second[$key])) { continue; } if (! $identical && ($first[$key] == $second[$key])) { continue; } return "with member [$key] " . $this->describeDifference( $first[$key], $second[$key], $identical); } return ""; } /** * Compares two arrays to see if their key lists match. * For an identical match, the ordering and types of the keys * is significant. * @param array $first First array. * @param array $second Array to compare with. * @param boolean $identical If true then type anomolies count. * @return boolean True if matching. * @access private */ function _isMatchingKeys($first, $second, $identical) { $first_keys = array_keys($first); $second_keys = array_keys($second); if ($identical) { return ($first_keys === $second_keys); } sort($first_keys); sort($second_keys); return ($first_keys == $second_keys); } /** * Creates a human readable description of the * difference between a resource and another variable. * @param resource $first First resource. * @param mixed $second Resource to compare with. * @param boolean $identical If true then type anomolies count. * @return string Human readable description. * @access private */ function _describeResourceDifference($first, $second, $identical) { return $this->_describeGenericDifference($first, $second); } /** * Creates a human readable description of the * difference between two objects. * @param object $first First object. * @param mixed $second Object to compare with. * @param boolean $identical If true then type anomolies count. * @return string Human readable description. * @access private */ function _describeObjectDifference($first, $second, $identical) { if (! is_object($second)) { return $this->_describeGenericDifference($first, $second); } return $this->_describeArrayDifference( get_object_vars($first), get_object_vars($second), $identical); } /** * Find the first character position that differs * in two strings by binary chop. * @param string $first First string. * @param string $second String to compare with. * @return integer Position of first differing * character. * @access private */ function _stringDiffersAt($first, $second) { if (! $first || ! $second) { return 0; } if (strlen($first) < strlen($second)) { list($first, $second) = array($second, $first); } $position = 0; $step = strlen($first); while ($step > 1) { $step = (integer)(($step + 1) / 2); if (strncmp($first, $second, $position + $step) == 0) { $position += $step; } } return $position; } /** * Sends a formatted dump of a variable to a string. * @param mixed $variable Variable to display. * @return string Output from print_r(). * @access public * @static */ static function dump($variable) { ob_start(); print_r($variable); $formatted = ob_get_contents(); ob_end_clean(); return $formatted; } /** * Extracts the last assertion that was not within * Simpletest itself. The name must start with "assert". * @param array $stack List of stack frames. * @access public * @static */ static function getFormattedAssertionLine($stack) { foreach ($stack as $frame) { if (isset($frame['file'])) { if (strpos($frame['file'], SIMPLE_TEST) !== false) { if (dirname($frame['file']) . '/' == SIMPLE_TEST) { continue; } } } if (SimpleDumper::_stackFrameIsAnAssertion($frame)) { return ' at [' . $frame['file'] . ' line ' . $frame['line'] . ']'; } } return ''; } /** * Tries to determine if the method call is an assertion. * @param array $frame PHP stack frame. * @access private * @static */ static function _stackFrameIsAnAssertion($frame) { if (($frame['function'] == 'fail') || ($frame['function'] == 'pass')) { return true; } if (strncmp($frame['function'], 'assert', 6) == 0) { return true; } if (strncmp($frame['function'], 'expect', 6) == 0) { return true; } return false; } }