--- /dev/null
+<?php
+/**
+ * @category Horde
+ * @package Support
+ * @copyright 2007-2008 The Horde Project (http://www.horde.org/)
+ * @license http://opensource.org/licenses/bsd-license.php
+ */
+
+/**
+ * @category Horde
+ * @package Support
+ * @copyright 2007-2008 The Horde Project (http://www.horde.org/)
+ * @license http://opensource.org/licenses/bsd-license.php
+ *
+ * Incorporate stuff from Horde_Array?
+ * http://docs.python.org/lib/typesmapping.html
+ */
+class Horde_Support_Array implements ArrayAccess, Countable, Iterator
+{
+ /**
+ * Array variables
+ */
+ protected $_array = array();
+
+ /**
+ */
+ public function __construct($vars = array())
+ {
+ if (is_array($vars)) {
+ $this->update($vars);
+ }
+ }
+
+ /**
+ */
+ public function get($key, $default = null)
+ {
+ return isset($this->_array[$key]) ? $this->_array[$key] : $default;
+ }
+
+ /**
+ * Gets the value at $offset. If no value exists at that offset, or the
+ * value $offset is NULL, then $default is set as the value of $offset.
+ *
+ * @param string $offset Offset to retrieve and set if unset
+ * @param string $default Default value if $offset does not exist
+ *
+ * @return mixed Value at $offset or $default
+ */
+ public function getOrSet($offset, $default = null)
+ {
+ $value = $this->offsetGet($offset);
+ if (is_null($value)) {
+ $this->offsetSet($offset, $value = $default);
+ }
+ return $value;
+ }
+
+ /**
+ * Gets the value at $offset and deletes it from the array. If no value
+ * exists at $offset, or the value at $offset is null, then $default
+ * will be returned.
+ *
+ * @param string $offset Offset to pop
+ * @param string $default Default value
+ *
+ * @return mixed Value at $offset or $default
+ */
+ public function pop($offset, $default = null)
+ {
+ $value = $this->offsetGet($offset);
+ $this->offsetUnset($offset);
+ return isset($value) ? $value : $default;
+ }
+
+ /**
+ * Update the array with the key/value pairs from $array
+ *
+ * @param array $array Key/value pairs to set/change in the array.
+ */
+ public function update($array)
+ {
+ if (!is_array($array) && !$array instanceof Traversable) {
+ throw new InvalidArgumentException('expected array or traversable, got ' . gettype($array));
+ }
+
+ foreach ($array as $key => $val) {
+ $this->offsetSet($key, $val);
+ }
+ }
+
+ /**
+ * Get the keys in the array
+ *
+ * @return array
+ */
+ public function getKeys()
+ {
+ return array_keys($this->_array);
+ }
+
+ /**
+ * Get the values in the array
+ *
+ * @return array
+ */
+ public function getValues()
+ {
+ return array_values($this->_array);
+ }
+
+ /**
+ * Clear out the array
+ */
+ public function clear()
+ {
+ $this->_array = array();
+ }
+
+ /**
+ */
+ public function __get($key)
+ {
+ return $this->get($key);
+ }
+
+ /**
+ */
+ public function __set($key, $value)
+ {
+ $this->_array[$key] = $value;
+ }
+
+ /**
+ * Checks the existance of $key in this array
+ */
+ public function __isset($key)
+ {
+ return array_key_exists($key, $this->_array);
+ }
+
+ /**
+ * Removes $key from this array
+ */
+ public function __unset($key)
+ {
+ unset($this->_array[$key]);
+ }
+
+ /**
+ * Count the number of elements
+ *
+ * @return integer
+ */
+ public function count()
+ {
+ return count($this->_array);
+ }
+
+ /**
+ * Gets the current value of this array's Iterator
+ */
+ public function current()
+ {
+ return current($this->_array);
+ }
+
+ /**
+ * Advances this array's Iterator to the next value
+ */
+ public function next()
+ {
+ return next($this->_array);
+ }
+
+ /**
+ * Returns the current key of this array's Iterator
+ */
+ public function key()
+ {
+ return key($this->_array);
+ }
+
+ /**
+ * Checks if this array's Iterator is in a valid position
+ */
+ public function valid()
+ {
+ return $this->current() !== false;
+ }
+
+ /**
+ * Rewinds this array's Iterator
+ */
+ public function rewind()
+ {
+ reset($this->_array);
+ }
+
+ /**
+ * Gets the value of $offset in this array
+ *
+ * @see __get()
+ */
+ public function offsetGet($offset)
+ {
+ return $this->__get($offset);
+ }
+
+ /**
+ * Sets the value of $offset to $value
+ *
+ * @see __set()
+ */
+ public function offsetSet($offset, $value)
+ {
+ return $this->__set($offset, $value);
+ }
+
+ /**
+ * Checks the existence of $offset in this array
+ *
+ * @see __isset()
+ */
+ public function offsetExists($offset)
+ {
+ return $this->__isset($offset);
+ }
+
+ /**
+ * Removes $offset from this array
+ *
+ * @see __unset()
+ */
+ public function offsetUnset($offset)
+ {
+ return $this->__unset($offset);
+ }
+
+}
--- /dev/null
+<?php
+/**
+ * @category Horde
+ * @package Support
+ * @copyright 2007-2008 The Horde Project (http://www.horde.org/)
+ * @license http://opensource.org/licenses/bsd-license.php
+ */
+
+/**
+ * @category Horde
+ * @package Support
+ * @copyright 2007-2008 The Horde Project (http://www.horde.org/)
+ * @license http://opensource.org/licenses/bsd-license.php
+ *
+ * For a thorough description of consistent hashing, see
+ * http://www.spiteful.com/2008/03/17/programmers-toolbox-part-3-consistent-hashing/,
+ * and also the original paper:
+ * http://www8.org/w8-papers/2a-webserver/caching/paper2.html
+ */
+class Horde_Support_ConsistentHash
+{
+ /**
+ * Number of times to put each node into the hash circle per weight value.
+ * @var integer
+ */
+ protected $_numberOfReplicas = 100;
+
+ /**
+ * Array representing our circle
+ * @var array
+ */
+ protected $_circle = array();
+
+ /**
+ * Numeric indices into the circle by hash position
+ * @var array
+ */
+ protected $_pointMap = array();
+
+ /**
+ * Number of points on the circle
+ * @var integer
+ */
+ protected $_pointCount = 0;
+
+ /**
+ * Array of nodes.
+ * @var array
+ */
+ protected $_nodes = array();
+
+ /**
+ * Number of nodes
+ * @var integer
+ */
+ protected $_nodeCount = 0;
+
+ /**
+ * Create a new consistent hash, with initial $nodes at $numberOfReplicas
+ *
+ * @param array $nodes Initial array of nodes to add at $weight.
+ * @param integer $weight The weight for the initial node list.
+ * @param integer $numberOfReplicas The number of points on the circle to generate for each node.
+ */
+ public function __construct($nodes = array(), $weight = 1, $numberOfReplicas = 100)
+ {
+ $this->_numberOfReplicas = $numberOfReplicas;
+ $this->addNodes($nodes, $weight);
+ }
+
+ /**
+ * Get the primary node for $key.
+ *
+ * @param string $key The key to look up.
+ *
+ * @param string The primary node for $key.
+ */
+ public function get($key)
+ {
+ $nodes = $this->getNodes($key, 1);
+ if (!$nodes) {
+ throw new Exception('No nodes found');
+ }
+ return $nodes[0];
+ }
+
+ /**
+ * Get an ordered list of nodes for $key.
+ *
+ * @param string $key The key to look up.
+ * @param integer $count The number of nodes to look up.
+ *
+ * @return array An ordered array of nodes.
+ */
+ public function getNodes($key, $count = 5)
+ {
+ // Degenerate cases
+ if ($this->_nodeCount < $count) {
+ throw new Exception('Not enough nodes (have ' . $this->_nodeCount . ', ' . $count . ' requested)');
+ }
+ if ($this->_nodeCount == 0) {
+ return array();
+ }
+
+ // Simple case
+ if ($this->_nodeCount == 1) {
+ return array($this->_nodes[0]['n']);
+ }
+
+ $hash = $this->hash(serialize($key));
+
+ // Find the first point on the circle greater than $hash by binary search.
+ $low = 0;
+ $high = $this->_pointCount - 1;
+ $index = null;
+ while (true) {
+ $mid = (int)(($low + $high) / 2);
+ if ($mid == $this->_pointCount) {
+ $index = 0;
+ break;
+ }
+
+ $midval = $this->_pointMap[$mid];
+ $midval1 = ($mid == 0) ? 0 : $this->_pointMap[$mid - 1];
+ if ($midval1 < $hash && $hash <= $midval) {
+ $index = $mid;
+ break;
+ }
+
+ if ($midval > $hash) {
+ $high = $mid - 1;
+ } else {
+ $low = $mid + 1;
+ }
+
+ if ($low > $high) {
+ $index = 0;
+ break;
+ }
+ }
+
+ $nodes = array();
+ while (count($nodes) < $count) {
+ $nodeIndex = $this->_pointMap[$index++ % $this->_pointCount];
+ $nodes[$nodeIndex] = $this->_nodes[$this->_circle[$nodeIndex]]['n'];
+ }
+ return array_values($nodes);
+ }
+
+ /**
+ * Add $node with weight $weight
+ *
+ * @param mixed $node
+ */
+ public function add($node, $weight = 1)
+ {
+ // Delegate to addNodes so that the circle is only regenerated once when
+ // adding multiple nodes.
+ $this->addNodes(array($node), $weight);
+ }
+
+ /**
+ * Add multiple nodes to the hash with the same weight.
+ *
+ * @param array $nodes An array of nodes.
+ * @param integer $weight The weight to add the nodes with.
+ */
+ public function addNodes($nodes, $weight = 1)
+ {
+ foreach ($nodes as $node) {
+ $this->_nodes[] = array('n' => $node, 'w' => $weight);
+ $this->_nodeCount++;
+
+ $nodeIndex = $this->_nodeCount - 1;
+ $nodeString = serialize($node);
+
+ $numberOfReplicas = (int)($weight * $this->_numberOfReplicas);
+ for ($i = 0; $i < $numberOfReplicas; $i++) {
+ $this->_circle[$this->hash($nodeString . $i)] = $nodeIndex;
+ }
+ }
+
+ $this->_updateCircle();
+ }
+
+ /**
+ * Remove $node from the hash.
+ *
+ * @param mixed $node
+ */
+ public function remove($node)
+ {
+ $nodeIndex = null;
+ $nodeString = serialize($node);
+
+ // Search for the node in the node list
+ foreach (array_keys($this->_nodes) as $i) {
+ if ($this->_nodes[$i]['n'] === $node) {
+ $nodeIndex = $i;
+ break;
+ }
+ }
+
+ if (is_null($nodeIndex)) {
+ throw new InvalidArgumentException('Node was not in the hash');
+ }
+
+ // Remove all points from the circle
+ $numberOfReplicas = (int)($this->_nodes[$nodeIndex]['w'] * $this->_numberOfReplicas);
+ for ($i = 0; $i < $numberOfReplicas; $i++) {
+ unset($this->_circle[$this->hash($nodeString . $i)]);
+ }
+ $this->_updateCircle();
+
+ // Unset the node from the node list
+ unset($this->_nodes[$nodeIndex]);
+ $this->_nodeCount--;
+ }
+
+ /**
+ * Expose the hash function for testing, probing, and extension.
+ *
+ * @param string $key
+ *
+ * @return string Hash value
+ */
+ public function hash($key)
+ {
+ return substr(md5($key), 0, 8);
+ }
+
+ /**
+ * Maintain the circle and arrays of points.
+ */
+ protected function _updateCircle()
+ {
+ // Sort the circle
+ ksort($this->_circle);
+
+ // Now that the hashes are sorted, generate numeric indices into the
+ // circle.
+ $this->_pointMap = array_keys($this->_circle);
+ $this->_pointCount = count($this->_pointMap);
+ }
+
+}
--- /dev/null
+<?php
+/**
+ * @category Horde
+ * @package Support
+ * @copyright 2007-2008 The Horde Project (http://www.horde.org/)
+ * @license http://opensource.org/licenses/bsd-license.php
+ */
+
+/**
+ * Horde Inflector class.
+ *
+ * @category Horde
+ * @package Support
+ * @copyright 2007-2008 The Horde Project (http://www.horde.org/)
+ * @license http://opensource.org/licenses/bsd-license.php
+ */
+class Horde_Support_Inflector {
+
+ /**
+ * Inflection cache
+ * @var array
+ */
+ protected $_cache = array();
+
+ /**
+ * Rules for pluralizing English nouns.
+ *
+ * @var array
+ */
+ protected $_pluralizationRules = array(
+ '/move$/i' => 'moves',
+ '/sex$/i' => 'sexes',
+ '/child$/i' => 'children',
+ '/man$/i' => 'men',
+ '/foot$/i' => 'feet',
+ '/person$/i' => 'people',
+ '/(quiz)$/i' => '$1zes',
+ '/^(ox)$/i' => '$1en',
+ '/(m|l)ouse$/i' => '$1ice',
+ '/(matr|vert|ind)ix|ex$/i' => '$1ices',
+ '/(x|ch|ss|sh)$/i' => '$1es',
+ '/([^aeiouy]|qu)ies$/i' => '$1y',
+ '/([^aeiouy]|qu)y$/i' => '$1ies',
+ '/(?:([^f])fe|([lr])f)$/i' => '$1$2ves',
+ '/sis$/i' => 'ses',
+ '/([ti])um$/i' => '$1a',
+ '/(buffal|tomat)o$/i' => '$1oes',
+ '/(bu)s$/i' => '$1ses',
+ '/(alias|status)$/i' => '$1es',
+ '/(octop|vir)us$/i' => '$1i',
+ '/(ax|test)is$/i' => '$1es',
+ '/s$/i' => 's',
+ '/$/' => 's',
+ );
+
+ /**
+ * Rules for singularizing English nouns.
+ *
+ * @var array
+ */
+ protected $_singularizationRules = array(
+ '/cookies$/i' => 'cookie',
+ '/moves$/i' => 'move',
+ '/sexes$/i' => 'sex',
+ '/children$/i' => 'child',
+ '/men$/i' => 'man',
+ '/feet$/i' => 'foot',
+ '/people$/i' => 'person',
+ '/databases$/i'=> 'database',
+ '/(quiz)zes$/i' => '\1',
+ '/(matr)ices$/i' => '\1ix',
+ '/(vert|ind)ices$/i' => '\1ex',
+ '/^(ox)en/i' => '\1',
+ '/(alias|status)es$/i' => '\1',
+ '/([octop|vir])i$/i' => '\1us',
+ '/(cris|ax|test)es$/i' => '\1is',
+ '/(shoe)s$/i' => '\1',
+ '/(o)es$/i' => '\1',
+ '/(bus)es$/i' => '\1',
+ '/([m|l])ice$/i' => '\1ouse',
+ '/(x|ch|ss|sh)es$/i' => '\1',
+ '/(m)ovies$/i' => '\1ovie',
+ '/(s)eries$/i' => '\1eries',
+ '/([^aeiouy]|qu)ies$/i' => '\1y',
+ '/([lr])ves$/i' => '\1f',
+ '/(tive)s$/i' => '\1',
+ '/(hive)s$/i' => '\1',
+ '/([^f])ves$/i' => '\1fe',
+ '/(^analy)ses$/i' => '\1sis',
+ '/((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$/i' => '\1\2sis',
+ '/([ti])a$/i' => '\1um',
+ '/(n)ews$/i' => '\1ews',
+ '/(.*)s$/i' => '\1',
+ );
+
+ /**
+ * An array of words with the same singular and plural spellings.
+ *
+ * @var array
+ */
+ protected $_uncountables = array(
+ 'aircraft',
+ 'cannon',
+ 'deer',
+ 'equipment',
+ 'fish',
+ 'information',
+ 'money',
+ 'moose',
+ 'rice',
+ 'series',
+ 'sheep',
+ 'species',
+ 'swine',
+ );
+
+ /**
+ * Constructor
+ *
+ * Store a map of the uncountable words for quicker checks.
+ */
+ public function __construct()
+ {
+ $this->_uncountables_keys = array_flip($this->_uncountables);
+ }
+
+ /**
+ * Add an uncountable word.
+ *
+ * @param string $word The uncountable word.
+ */
+ public function uncountable($word)
+ {
+ $this->_uncountables[] = $word;
+ $this->_uncountables_keys[$word] = true;
+ }
+
+ /**
+ * Singular English word to pluralize.
+ *
+ * @param string $word Word to pluralize.
+ *
+ * @return string Plural form of $word.
+ */
+ public function pluralize($word)
+ {
+ if ($plural = $this->_getCache($word, 'pluralize')) {
+ return $plural;
+ }
+
+ if (isset($this->_uncountables_keys[$word])) {
+ return $word;
+ }
+
+ foreach ($this->_pluralizationRules as $regexp => $replacement) {
+ $plural = preg_replace($regexp, $replacement, $word, -1, $matches);
+ if ($matches > 0) {
+ return $this->_cache($word, 'pluralize', $plural);
+ }
+ }
+
+ return $this->_cache($word, 'pluralize', $word);
+ }
+
+ /**
+ * Plural English word to singularize.
+ *
+ * @param string $word Word to singularize.
+ *
+ * @return string Singular form of $word.
+ */
+ public function singularize($word)
+ {
+ if ($singular = $this->_getCache($word, 'singularize')) {
+ return $singular;
+ }
+
+ if (isset($this->_uncountables_keys[$word])) {
+ return $word;
+ }
+
+ foreach ($this->_singularizationRules as $regexp => $replacement) {
+ $singular = preg_replace($regexp, $replacement, $word, -1, $matches);
+ if ($matches > 0) {
+ return $this->_cache($word, 'singularize', $singular);
+ }
+ }
+
+ return $this->_cache($word, 'singularize', $word);
+ }
+
+ /**
+ * Camel-case a word
+ *
+ * @param string $word The word to camel-case
+ * @param string $firstLetter Whether to upper or lower case the first
+ * letter of each slash-separated section. Defaults to 'upper';
+ *
+ * @return string Camelized $word
+ */
+ public function camelize($word, $firstLetter = 'upper')
+ {
+ if ($camelized = $this->_getCache($word, 'camelize' . $firstLetter)) {
+ return $camelized;
+ }
+
+ $camelized = $word;
+ if (strtolower($camelized) != $camelized && strpos($camelized, '_') !== false) {
+ $camelized = str_replace('_', '/', $camelized);
+ }
+ if (strpos($camelized, '/') !== false) {
+ $camelized = str_replace('/', '/ ', $camelized);
+ }
+ if (strpos($camelized, '_') !== false) {
+ $camelized = strtr($camelized, '_', ' ');
+ }
+
+ $camelized = str_replace(' ' , '', ucwords($camelized));
+
+ if ($firstLetter == 'lower') {
+ $parts = array();
+ foreach (explode('/', $camelized) as $part) {
+ $part[0] = strtolower($part[0]);
+ $parts[] = $part;
+ }
+ $camelized = implode('/', $parts);
+ }
+
+ return $this->_cache($word, 'camelize' . $firstLetter, $camelized);
+ }
+
+ /**
+ * Get a cached inflection
+ *
+ * @return string | false
+ */
+ protected function _getCache($word, $rule)
+ {
+ return isset($this->_cache[$word . '|' . $rule]) ?
+ $this->_cache[$word . '|' . $rule] : false;
+ }
+
+ /**
+ * Cache an inflection
+ *
+ * @param string $word The word being inflected
+ * @param string $rule The inflection rule
+ * @param string $value The inflected value of $word
+ *
+ * @return string The inflected value
+ */
+ protected function _cache($word, $rule, $value)
+ {
+ $this->_cache[$word . '|' . $rule] = $value;
+ return $value;
+ }
+
+ /**
+ * Clear the inflection cache
+ */
+ public function clearCache()
+ {
+ $this->_cache = array();
+ }
+
+}
--- /dev/null
+<?php
+/**
+ * @category Horde
+ * @package Support
+ * @copyright 2008 The Horde Project (http://www.horde.org/)
+ * @license http://opensource.org/licenses/bsd-license.php
+ */
+
+/**
+ * Class that can substitute for any object and safely do nothing.
+ *
+ * @category Horde
+ * @package Support
+ * @copyright 2008 The Horde Project (http://www.horde.org/)
+ * @license http://opensource.org/licenses/bsd-license.php
+ */
+class Horde_Support_Stub
+{
+ /**
+ * Cooerce to an empty string
+ *
+ * @return string
+ */
+ public function __toString()
+ {
+ return '';
+ }
+
+ /**
+ * Return self for any requested property.
+ *
+ * @param string $key The requested object property
+ *
+ * @return null
+ */
+ public function __get($key)
+ {
+ }
+
+ /**
+ * Gracefully accept any method call and do nothing.
+ *
+ * @param string $method The method that was called
+ * @param array $args The method's arguments
+ *
+ * @return null
+ */
+ public function __call($method, $args)
+ {
+ }
+
+ /**
+ * Gracefully accept any static method call and do nothing.
+ *
+ * @param string $method The method that was called
+ * @param array $args The method's arguments
+ *
+ * @return null
+ */
+ public static function __callStatic($method, $args)
+ {
+ }
+
+}
--- /dev/null
+<?php
+/**
+ * @category Horde
+ * @package Support
+ * @copyright 1999-2008 The Horde Project (http://www.horde.org/)
+ * @license http://opensource.org/licenses/bsd-license.php
+ */
+
+/**
+ * Simple interface for timing operations.
+ *
+ * <code>
+ * $t = new Horde_Support_Timer;
+ * $t->push();
+ * $elapsed = $t->pop();
+ * </code>
+ *
+ * @category Horde
+ * @package Support
+ * @copyright 1999-2008 The Horde Project (http://www.horde.org/)
+ * @license http://opensource.org/licenses/bsd-license.php
+ */
+class Horde_Support_Timer
+{
+ /**
+ * @var array
+ */
+ protected $_start = array();
+
+ /**
+ * @var integer
+ */
+ protected $_idx = 0;
+
+ /**
+ * Push a new timer start on the stack.
+ */
+ public function push()
+ {
+ $start = $this->_start[$this->_idx++] = microtime(true);
+ return $start;
+ }
+
+ /**
+ * Pop the latest timer start and return the difference with the current
+ * time.
+ */
+ public function pop()
+ {
+ $etime = microtime(true);
+
+ if (! ($this->_idx > 0)) {
+ throw new Exception('No timers have been started');
+ }
+
+ return $etime - $this->_start[--$this->_idx];
+ }
+
+}
--- /dev/null
+<?php
+/**
+ * @category Horde
+ * @package Support
+ * @copyright 2008 The Horde Project (http://www.horde.org/)
+ * @license http://opensource.org/licenses/bsd-license.php
+ */
+
+/**
+ * Class for generating RFC 4122 UUIDs. Usage:
+ *
+ * <code>
+ * <?php
+ *
+ * $uuid = (string)new Horde_Support_Uuid;
+ *
+ * ?>
+ * </code>
+ *
+ * @category Horde
+ * @package Support
+ * @copyright 2008 The Horde Project (http://www.horde.org/)
+ * @license http://opensource.org/licenses/bsd-license.php
+ */
+class Horde_Support_Uuid
+{
+ /**
+ * Generated UUID
+ * @var string
+ */
+ private $_uuid;
+
+ /**
+ * New UUID
+ */
+ public function __construct()
+ {
+ $this->generate();
+ }
+
+ /**
+ * Generate a 36-character RFC 4122 UUID, without the urn:uuid: prefix.
+ *
+ * @see http://www.ietf.org/rfc/rfc4122.txt
+ * @see http://labs.omniti.com/alexandria/trunk/OmniTI/Util/UUID.php
+ *
+ * @return string
+ */
+ public function generate()
+ {
+ list($time_mid, $time_low) = explode(' ', microtime());
+ $time_low = (int)$time_low;
+ $time_mid = (int)substr($time_mid, 2) & 0xffff;
+ $time_high = mt_rand(0, 0x0fff) | 0x4000;
+
+ $clock = mt_rand(0, 0x3fff) | 0x8000;
+
+ $node_low = function_exists('zend_thread_id') ?
+ zend_thread_id() : getmypid();
+ $node_high = isset($_SERVER['SERVER_ADDR']) ?
+ ip2long($_SERVER['SERVER_ADDR']) : crc32(php_uname());
+ $node = bin2hex(pack('nN', $node_low, $node_high));
+
+ $this->_uuid = sprintf('%08x-%04x-%04x-%04x-%s',
+ $time_low, $time_mid, $time_high, $clock, $node);
+ }
+
+ /**
+ * Cooerce to string
+ *
+ * @return string
+ */
+ public function __toString()
+ {
+ return $this->_uuid;
+ }
+
+}
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<package packagerversion="1.4.9" version="2.0" xmlns="http://pear.php.net/dtd/package-2.0" xmlns:tasks="http://pear.php.net/dtd/tasks-1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://pear.php.net/dtd/tasks-1.0
+http://pear.php.net/dtd/tasks-1.0.xsd
+http://pear.php.net/dtd/package-2.0
+http://pear.php.net/dtd/package-2.0.xsd">
+ <name>Support</name>
+ <channel>pear.horde.org</channel>
+ <summary>Horde support package</summary>
+ <description>This package provides supporting functionality for Horde that is not tied to Horde but is used by it. These classes can be used outside of Horde as well.
+ </description>
+ <lead>
+ <name>Chuck Hagenbuch</name>
+ <user>chuck</user>
+ <email>chuck@horde.org</email>
+ <active>yes</active>
+ </lead>
+ <date>2008-08-01</date>
+ <version>
+ <release>0.1.0</release>
+ <api>0.1.0</api>
+ </version>
+ <stability>
+ <release>beta</release>
+ <api>beta</api>
+ </stability>
+ <license uri="http://opensource.org/licenses/bsd-license.php">BSD</license>
+ <notes>
+ * Initial horde/support package
+ * Initial Horde_Support_Array object
+ * Initial Horde_Support_ConsistentHash object
+ * Initial Horde_Support_Inflector object
+ * Initial Horde_Support_Stub object
+ * Initial Horde_Support_Timer object
+ * Initial Horde_Support_Uuid object
+ </notes>
+ <contents>
+ <dir name="/">
+ <dir name="lib">
+ <dir name="Horde">
+ <dir name="Support">
+ <file name="Array.php" role="php" />
+ <file name="ConsistentHash.php" role="php" />
+ <file name="Inflector.php" role="php" />
+ <file name="Stub.php" role="php" />
+ <file name="Timer.php" role="php" />
+ <file name="Uuid.php" role="php" />
+ </dir> <!-- /lib/Horde/Support -->
+ </dir> <!-- /lib/Horde -->
+ </dir> <!-- /lib -->
+ </dir> <!-- / -->
+ </contents>
+ <dependencies>
+ <required>
+ <php>
+ <min>5.2.0</min>
+ </php>
+ <pearinstaller>
+ <min>1.5.0</min>
+ </pearinstaller>
+ </required>
+ </dependencies>
+ <phprelease>
+ <filelist>
+ <install name="lib/Horde/Support/Array.php" as="Horde/Support/Array.php" />
+ <install name="lib/Horde/Support/ConsistentHash.php" as="Horde/Support/ConsistentHash.php" />
+ <install name="lib/Horde/Support/Inflector.php" as="Horde/Support/Inflector.php" />
+ <install name="lib/Horde/Support/Stub.php" as="Horde/Support/Stub.php" />
+ <install name="lib/Horde/Support/Timer.php" as="Horde/Support/Timer.php" />
+ <install name="lib/Horde/Support/Uuid.php" as="Horde/Support/Uuid.php" />
+ </filelist>
+ </phprelease>
+</package>
--- /dev/null
+<?php
+/**
+ * @category Horde
+ * @package Support
+ * @subpackage UnitTests
+ * @copyright 2008 The Horde Project (http://www.horde.org/)
+ * @license http://opensource.org/licenses/bsd-license.php
+ */
+
+if (!defined('PHPUnit_MAIN_METHOD')) {
+ define('PHPUnit_MAIN_METHOD', 'Horde_Support_AllTests::main');
+}
+
+require_once 'PHPUnit/Framework/TestSuite.php';
+require_once 'PHPUnit/TextUI/TestRunner.php';
+
+class Horde_Support_AllTests {
+
+ public static function main()
+ {
+ PHPUnit_TextUI_TestRunner::run(self::suite());
+ }
+
+ public static function suite()
+ {
+ set_include_path(dirname(__FILE__) . '/../../../lib' . PATH_SEPARATOR . get_include_path());
+ if (!spl_autoload_functions()) {
+ spl_autoload_register(create_function('$class', '$filename = str_replace(array(\'::\', \'_\'), \'/\', $class); include "$filename.php";'));
+ }
+
+ $suite = new PHPUnit_Framework_TestSuite('Horde Framework - Horde_Support');
+
+ $basedir = dirname(__FILE__);
+ $baseregexp = preg_quote($basedir . DIRECTORY_SEPARATOR, '/');
+
+ foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator($basedir)) as $file) {
+ if ($file->isFile() && preg_match('/Test.php$/', $file->getFilename())) {
+ $pathname = $file->getPathname();
+ require $pathname;
+
+ $class = str_replace(DIRECTORY_SEPARATOR, '_',
+ preg_replace("/^$baseregexp(.*)\.php/", '\\1', $pathname));
+ $suite->addTestSuite('Horde_Support_' . $class);
+ }
+ }
+
+ return $suite;
+ }
+
+}
+
+if (PHPUnit_MAIN_METHOD == 'Horde_Support_AllTests::main') {
+ Horde_Support_AllTests::main();
+}
--- /dev/null
+<?php
+/**
+ * @category Horde
+ * @package Support
+ * @subpackage UnitTests
+ * @copyright 2007-2008 The Horde Project (http://www.horde.org/)
+ * @license http://opensource.org/licenses/bsd-license.php
+ */
+
+/**
+ * @group support
+ * @category Horde
+ * @package Support
+ * @subpackage UnitTests
+ * @copyright 2007-2008 The Horde Project (http://www.horde.org/)
+ * @license http://opensource.org/licenses/bsd-license.php
+ */
+class Horde_Support_ArrayTest extends PHPUnit_Framework_TestCase
+{
+ public function testImplementsArrayAccess()
+ {
+ $o = new Horde_Support_Array();
+ $this->assertType('ArrayAccess', $o);
+ }
+
+ public function testImplementsIterator()
+ {
+ $o = new Horde_Support_Array();
+ $this->assertType('Iterator', $o);
+ }
+
+ public function testImplementsCountable()
+ {
+ $o = new Horde_Support_Array();
+ $this->assertType('Countable', $o);
+ }
+
+ // offsetGet()
+
+ public function testOffsetGetReturnsValueAtOffset()
+ {
+ $o = new Horde_Support_Array(array('foo' => 'bar'));
+ $this->assertEquals('bar', $o->offsetGet('foo'));
+ }
+
+ public function testOffsetGetReturnsNullWhenOffsetDoesNotExist()
+ {
+ $o = new Horde_Support_Array();
+ $this->assertNull($o->offsetGet('foo'));
+ }
+
+ // get()
+
+ public function testGetReturnsValueAtOffset()
+ {
+ $o = new Horde_Support_Array(array('foo' => 'bar'));
+ $this->assertEquals('bar', $o->get('foo'));
+ }
+
+ public function testGetReturnsNullByDefaultWhenOffsetDoesNotExist()
+ {
+ $o = new Horde_Support_Array();
+ $this->assertNull($o->get('foo'));
+ }
+
+ public function testGetReturnsDefaultSpecifiedWhenOffsetDoesNotExist()
+ {
+ $o = new Horde_Support_Array();
+ $this->assertEquals('bar', $o->get('foo', 'bar'));
+ }
+
+ public function testGetReturnsDefaultSpecifiedWhenValueAtOffsetIsNull()
+ {
+ $o = new Horde_Support_Array(array('foo' => null));
+ $this->assertEquals('bar', $o->get('foo', 'bar'));
+ }
+
+ // getOrSet()
+
+ public function testGetOrSetReturnsValueAtOffset()
+ {
+ $o = new Horde_Support_Array(array('foo' => 'bar'));
+ $this->assertEquals('bar', $o->getOrSet('foo'));
+ }
+
+ public function testGetOrSetReturnsAndSetsNullWhenOffsetDoesNotExist()
+ {
+ $o = new Horde_Support_Array();
+ $this->assertNull($o->getOrSet('foo'));
+ $this->assertTrue($o->offsetExists('foo'));
+ $this->assertNull($o->offsetGet('foo'));
+ }
+
+ public function testGetOrSetReturnsAndSetsDefaultSpecifiedWhenOffsetDoesNotExist()
+ {
+ $o = new Horde_Support_Array();
+ $this->assertEquals('bar', $o->getOrSet('foo', 'bar'));
+ $this->assertTrue($o->offsetExists('foo'));
+ $this->assertEquals('bar', $o->offsetGet('foo'));
+ }
+
+ public function testGetOrSetReturnsAndSetsDefaultSpecifiedValueAtOffsetIsNull()
+ {
+ $o = new Horde_Support_Array(array('foo' => null));
+ $this->assertEquals('bar', $o->getOrSet('foo', 'bar'));
+ $this->assertTrue($o->offsetExists('foo'));
+ $this->assertEquals('bar', $o->offsetGet('foo'));
+ }
+
+ // pop()
+
+ public function testPopReturnsValueAtOffsetAndUnsetsIt()
+ {
+ $o = new Horde_Support_Array(array('foo' => 'bar'));
+ $this->assertEquals('bar', $o->pop('foo'));
+ $this->assertFalse($o->offsetExists('foo'));
+ }
+
+ public function testPopReturnsNullByDefaultWhenOffsetDoesNotExist()
+ {
+ $o = new Horde_Support_Array();
+ $this->assertNull($o->pop('foo'));
+ }
+
+ public function testPopReturnsDefaultSpecifiedWhenOffsetDoesNotExist()
+ {
+ $o = new Horde_Support_Array();
+ $this->assertEquals('bar', $o->pop('foo', 'bar'));
+ }
+
+ public function testPopReturnsDefaultSpecifiedWhenValueAtOffsetIsNull()
+ {
+ $o = new Horde_Support_Array(array('foo' => null));
+ $this->assertEquals('bar', $o->pop('foo', 'bar'));
+ }
+
+ // update()
+
+ public function testUpdateDoesNotThrowWhenArgumentIsAnArray()
+ {
+ $o = new Horde_Support_Array();
+ $o->update(array());
+ }
+
+ public function testUpdateDoesNotThrowWhenArgumentIsTraversable()
+ {
+ $o = new Horde_Support_Array();
+ $o->update(new ArrayObject());
+ }
+
+ public function testUpdateMergesNewValuesFromArayInArgument()
+ {
+ $o = new Horde_Support_Array();
+ $o->update(array('foo' => 'bar'));
+ $this->assertEquals('bar', $o->offsetGet('foo'));
+ }
+
+ public function testUpdateMergesAndOverwritesExistingOffsets()
+ {
+ $o = new Horde_Support_Array(array('foo' => 'bar'));
+ $o->update(array('foo' => 'baz'));
+ $this->assertEquals('baz', $o->offsetGet('foo'));
+ }
+
+ public function testUpdateMergeDoesNotAffectUnrelatedKeys()
+ {
+ $o = new Horde_Support_Array(array('foo' => 'bar'));
+ $o->update(array('baz' => 'qux'));
+ $this->assertEquals('qux', $o->offsetGet('baz'));
+ }
+
+ // clear()
+
+ public function testClearErasesTheArray()
+ {
+ $o = new Horde_Support_Array(array('foo' => 'bar'));
+ $o->clear();
+ $this->assertEquals(0, $o->count());
+ }
+
+ // getKeys()
+
+ public function testGetKeysReturnsEmptyArrayWhenArrayIsEmpty()
+ {
+ $o = new Horde_Support_Array();
+ $this->assertSame(array(), $o->getKeys());
+ }
+
+ public function testGetKeysReturnsArrayOfKeysInTheArray()
+ {
+ $o = new Horde_Support_Array(array('foo'=> 1, 'bar' => 2));
+ $this->assertSame(array('foo', 'bar'), $o->getKeys());
+ }
+
+ // getValues()
+
+ public function testGetValuesReturnsEmptyArrayWhenArrayIsEmpty()
+ {
+ $o = new Horde_Support_Array();
+ $this->assertSame(array(), $o->getValues());
+ }
+
+ public function testGetValuesReturnsArrayOfValuesInTheArray()
+ {
+ $o = new Horde_Support_Array(array('foo' => 1, 'bar' => 2));
+ $this->assertSame(array(1, 2), $o->getValues());
+ }
+
+}
--- /dev/null
+<?php
+/**
+ * @category Horde
+ * @package Support
+ * @subpackage UnitTests
+ * @copyright 2008 The Horde Project (http://www.horde.org/)
+ * @license http://opensource.org/licenses/bsd-license.php
+ */
+
+/**
+ * @group support
+ * @category Horde
+ * @package Support
+ * @subpackage UnitTests
+ * @copyright 2008 The Horde Project (http://www.horde.org/)
+ * @license http://opensource.org/licenses/bsd-license.php
+ */
+class Horde_Support_ConsistentHashTest extends PHPUnit_Framework_TestCase
+{
+ public function testAddUpdatesCount()
+ {
+ $h = new Horde_Support_ConsistentHash;
+ $this->assertEquals(0, $this->readAttribute($h, '_nodeCount'));
+
+ $h->add('a');
+ $this->assertEquals(1, $this->readAttribute($h, '_nodeCount'));
+ $this->assertEquals(count($this->readAttribute($h, '_nodes')), $this->readAttribute($h, '_nodeCount'));
+ }
+
+ public function testAddUpdatesPointCount()
+ {
+ $numberOfReplicas = 100;
+ $h = new Horde_Support_ConsistentHash(array(), 1, $numberOfReplicas);
+ $this->assertEquals(0, $this->readAttribute($h, '_pointCount'));
+ $this->assertEquals(count($this->readAttribute($h, '_circle')), $this->readAttribute($h, '_pointCount'));
+ $this->assertEquals(count($this->readAttribute($h, '_pointMap')), $this->readAttribute($h, '_pointCount'));
+
+ $h->add('a');
+ $this->assertEquals(100, $this->readAttribute($h, '_pointCount'));
+ $this->assertEquals(count($this->readAttribute($h, '_circle')), $this->readAttribute($h, '_pointCount'));
+ $this->assertEquals(count($this->readAttribute($h, '_pointMap')), $this->readAttribute($h, '_pointCount'));
+ }
+
+ public function testAddWithWeightGeneratesMorePoints()
+ {
+ $weight = 2;
+ $numberOfReplicas = 100;
+ $h = new Horde_Support_ConsistentHash(array(), 1, $numberOfReplicas);
+ $this->assertEquals(0, $this->readAttribute($h, '_pointCount'));
+ $this->assertEquals(count($this->readAttribute($h, '_circle')), $this->readAttribute($h, '_pointCount'));
+ $this->assertEquals(count($this->readAttribute($h, '_pointMap')), $this->readAttribute($h, '_pointCount'));
+
+ $h->add('a', $weight);
+ $this->assertEquals($numberOfReplicas * $weight, $this->readAttribute($h, '_pointCount'));
+ $this->assertEquals(count($this->readAttribute($h, '_circle')), $this->readAttribute($h, '_pointCount'));
+ $this->assertEquals(count($this->readAttribute($h, '_pointMap')), $this->readAttribute($h, '_pointCount'));
+ }
+
+ public function testRemoveRemovesPoints()
+ {
+ $h = new Horde_Support_ConsistentHash;
+ $this->assertEquals(0, $this->readAttribute($h, '_nodeCount'));
+
+ $h->add('a');
+ $h->remove('a');
+ $this->assertEquals(0, $this->readAttribute($h, '_nodeCount'));
+ $this->assertEquals(0, $this->readAttribute($h, '_pointCount'));
+ $this->assertEquals(count($this->readAttribute($h, '_circle')), $this->readAttribute($h, '_pointCount'));
+ $this->assertEquals(count($this->readAttribute($h, '_pointMap')), $this->readAttribute($h, '_pointCount'));
+ }
+
+ public function testRemoveThrowsOnNonexistentNode()
+ {
+ $h = new Horde_Support_ConsistentHash;
+ $this->setExpectedException('InvalidArgumentException');
+ $h->remove('a');
+ }
+
+ public function testLookupsReturnValidNodes()
+ {
+ $nodes = range(1, 10);
+ $h = new Horde_Support_ConsistentHash($nodes);
+
+ foreach (range(1, 10) as $i) {
+ $this->assertContains($h->get($i), $nodes);
+ }
+ }
+
+ public function testLookupRatiosWithDifferentNodeWeights()
+ {
+ $h = new Horde_Support_ConsistentHash;
+ $h->add('a', 2);
+ $h->add('b', 1);
+ $h->add('c', 3);
+ $h->add('d', 4);
+
+ $choices = array('a' => 0, 'b' => 0, 'c' => 0, 'd' => 0);
+ for ($i = 0; $i < 1000; $i++) {
+ $choices[$h->get(uniqid(mt_rand()))]++;
+ }
+
+ // Due to randomness it's entirely possible to have some overlap in the
+ // middle, but the highest-weighted node should definitely be chosen
+ // more than the lowest-weighted one.
+ $this->assertGreaterThan($choices['b'], $choices['d']);
+ }
+
+ public function testRepeatableLookups()
+ {
+ $h = new Horde_Support_ConsistentHash(range(1, 10));
+
+ $this->assertEquals($h->get('t1'), $h->get('t1'));
+ $this->assertEquals($h->get('t2'), $h->get('t2'));
+ }
+
+ public function testRepeatableLookupsAfterAddingAndRemoving()
+ {
+ $h = new Horde_Support_ConsistentHash(range(1, 100));
+
+ $results1 = array();
+ foreach (range(1, 100) as $i)
+ $results1[] = $h->get($i);
+
+ $h->add('new');
+ $h->remove('new');
+ $h->add('new');
+ $h->remove('new');
+
+ $results2 = array();
+ foreach (range(1, 100) as $i)
+ $results2[] = $h->get($i);
+
+ $this->assertEquals($results1, $results2);
+ }
+
+ public function testRepeatableLookupsBetweenInstances()
+ {
+ $h1 = new Horde_Support_ConsistentHash(range(1, 10));
+ $results1 = array();
+ foreach (range(1, 100) as $i)
+ $results1[] = $h1->get($i);
+
+ $h2 = new Horde_Support_ConsistentHash(range(1, 10));
+ $results2 = array();
+ foreach (range(1, 100) as $i)
+ $results2[] = $h2->get($i);
+
+ $this->assertEquals($results1, $results2);
+ }
+
+ public function testGetNodes()
+ {
+ $h = new Horde_Support_ConsistentHash(range(1, 10));
+ $nodes = $h->getNodes('r', 2);
+
+ $this->assertType('array', $nodes);
+ $this->assertEquals(count($nodes), 2);
+ $this->assertNotEquals($nodes[0], $nodes[1]);
+ }
+
+ public function testGetNodesWithNotEnoughNodes()
+ {
+ $h = new Horde_Support_ConsistentHash(array('t'));
+
+ $this->setExpectedException('Exception');
+ $nodes = $h->getNodes('resource', 2);
+ }
+
+ public function testGetNodesWrapsToBeginningOfCircle()
+ {
+ $h = new Horde_Support_ConsistentHash(array(), 1, 1);
+
+ // Create an array of random values and one fixed test value and sort
+ // them by their hashes
+ $nodes = array();
+ for ($i = 0; $i < 10; $i++) {
+ $val = uniqid(mt_rand(), true);
+ $nodes[$h->hash(serialize($val) . '0')] = $val;
+ }
+ $nodes[$h->hash(serialize('key'))] = 'key';
+ ksort($nodes);
+
+ // Remove the fixed test value.
+ $nodes = array_values($nodes);
+ $testindex = array_search('key', $nodes);
+ $testvalue = array_shift(array_splice($nodes, $testindex, 1));
+
+ foreach ($nodes as $node) {
+ $h->add($node);
+ }
+
+ $expected = array();
+ for ($i = 0; $i < 10; $i++) {
+ $expected[] = $nodes[($testindex + $i) % 10];
+ }
+
+ $this->assertEquals(
+ $expected,
+ $h->getNodes($testvalue, 10));
+ }
+
+ public function testFallbackWhenANodeIsRemoved()
+ {
+ $h = new Horde_Support_ConsistentHash(array(), 1, 1);
+
+ // Create an array of random values and one fixed test value and sort
+ // them by their hashes
+ $nodes = array();
+ for ($i = 0; $i < 10; $i++) {
+ $val = uniqid(mt_rand(), true);
+ $nodes[$h->hash(serialize($val) . '0')] = $val;
+ }
+ $nodes[$h->hash(serialize('key'))] = 'key';
+ ksort($nodes);
+
+ // Remove the fixed test value.
+ $nodes = array_values($nodes);
+ $testindex = array_search('key', $nodes);
+ $testvalue = array_shift(array_splice($nodes, $testindex, 1));
+
+ foreach ($nodes as $node) {
+ $h->add($node);
+ }
+
+ $this->assertEquals($h->get('key'), $nodes[$testindex]);
+
+ $h->remove($nodes[$testindex]);
+ $this->assertEquals($h->get('key'), $nodes[($testindex + 1) % 10]);
+
+ $h->remove($nodes[($testindex + 1) % 10]);
+ $this->assertEquals($h->get('key'), $nodes[($testindex + 2) % 10]);
+ }
+
+}
--- /dev/null
+<?php
+/**
+ * @category Horde
+ * @package Support
+ * @subpackage UnitTests
+ * @copyright 2007-2008 The Horde Project (http://www.horde.org/)
+ * @license http://opensource.org/licenses/bsd-license.php
+ */
+
+/**
+ * @group support
+ * @category Horde
+ * @package Support
+ * @subpackage UnitTests
+ * @copyright 2007-2008 The Horde Project (http://www.horde.org/)
+ * @license http://opensource.org/licenses/bsd-license.php
+ */
+class Horde_Support_InflectorTest extends PHPUnit_Framework_TestCase
+{
+ /**
+ * Words to test
+ *
+ * @var array $words
+ */
+ public $words = array(
+ 'sheep' => 'sheep',
+ 'man' => 'men',
+ 'woman' => 'women',
+ 'user' => 'users',
+ 'foot' => 'feet',
+ 'hive' => 'hives',
+ 'chive' => 'chives',
+ 'event' => 'events',
+ 'task' => 'tasks',
+ 'preference' => 'preferences',
+ 'child' => 'children',
+ 'moose' => 'moose',
+ 'mouse' => 'mice',
+ );
+
+ public function setUp()
+ {
+ $this->inflector = new Horde_Support_Inflector;
+ }
+
+ public function testSingularizeAndPluralize()
+ {
+ foreach ($this->words as $singular => $plural) {
+ $this->assertEquals($plural, $this->inflector->pluralize($singular));
+ $this->assertEquals($singular, $this->inflector->singularize($plural));
+ }
+ }
+
+ public function testCamelize()
+ {
+ // underscore => camelize
+ $this->assertEquals('Test', $this->inflector->camelize('test'));
+ $this->assertEquals('TestCase', $this->inflector->camelize('test_case'));
+ $this->assertEquals('Test/Case', $this->inflector->camelize('test/case'));
+ $this->assertEquals('TestCase/Name', $this->inflector->camelize('test_case/name'));
+
+ // already camelized
+ $this->assertEquals('Test', $this->inflector->camelize('Test'));
+ $this->assertEquals('TestCase', $this->inflector->camelize('testCase'));
+ $this->assertEquals('TestCase', $this->inflector->camelize('TestCase'));
+ $this->assertEquals('Test/Case', $this->inflector->camelize('Test_Case'));
+ }
+
+ public function testCamelizeLower()
+ {
+ // underscore => camelize
+ $this->assertEquals('test', $this->inflector->camelize('test', 'lower'));
+ $this->assertEquals('testCase', $this->inflector->camelize('test_case', 'lower'));
+ $this->assertEquals('test/case', $this->inflector->camelize('test/case', 'lower'));
+ $this->assertEquals('testCase/name', $this->inflector->camelize('test_case/name', 'lower'));
+
+ // already camelized
+ $this->assertEquals('test', $this->inflector->camelize('Test', 'lower'));
+ $this->assertEquals('testCase', $this->inflector->camelize('testCase', 'lower'));
+ $this->assertEquals('testCase', $this->inflector->camelize('TestCase', 'lower'));
+ $this->assertEquals('test/case', $this->inflector->camelize('Test_Case', 'lower'));
+ }
+
+}
--- /dev/null
+<?php
+/**
+ * @category Horde
+ * @package Support
+ * @subpackage UnitTests
+ * @copyright 2008 The Horde Project (http://www.horde.org/)
+ * @license http://opensource.org/licenses/bsd-license.php
+ */
+
+/**
+ * @group support
+ * @category Horde
+ * @package Support
+ * @subpackage UnitTests
+ * @copyright 2008 The Horde Project (http://www.horde.org/)
+ * @license http://opensource.org/licenses/bsd-license.php
+ */
+class Horde_Support_StubTest extends PHPUnit_Framework_TestCase
+{
+ public function testAnyOffsetIsGettable()
+ {
+ $stub = new Horde_Support_Stub;
+ $oldTrackErrors = ini_set('track_errors', 1);
+ $php_errormsg = null;
+ $this->assertNull($stub->{uniqid()});
+ $this->assertNull($php_errormsg);
+ }
+
+ public function testAnyMethodIsCallable()
+ {
+ $stub = new Horde_Support_Stub;
+ $this->assertTrue(is_callable(array($stub, uniqid())));
+ }
+
+ public function testAnyStaticMethodIsCallable()
+ {
+ if (version_compare(PHP_VERSION, '5.3', '<')) {
+ $this->markTestSkipped();
+ }
+ $this->assertTrue(is_callable(array('Horde_Support_Stub', uniqid())));
+ }
+
+}
--- /dev/null
+<?php
+/**
+ * @category Horde
+ * @package Support
+ * @subpackage UnitTests
+ * @copyright 1999-2008 The Horde Project (http://www.horde.org/)
+ * @license http://opensource.org/licenses/bsd-license.php
+ */
+
+/**
+ * @group support
+ * @category Horde
+ * @package Support
+ * @subpackage UnitTests
+ * @copyright 1999-2008 The Horde Project (http://www.horde.org/)
+ * @license http://opensource.org/licenses/bsd-license.php
+ */
+class Horde_Support_TimerTest extends PHPUnit_Framework_TestCase
+{
+ /**
+ * test instantiating a normal timer
+ */
+ public function testNormalTiming()
+ {
+ $t = new Horde_Support_Timer;
+ $start = $t->push();
+ $elapsed = $t->pop();
+
+ $this->assertTrue(is_float($start));
+ $this->assertTrue(is_float($elapsed));
+ $this->assertTrue($elapsed > 0);
+ }
+
+ /**
+ * test getting the finish time before starting the timer
+ * @expectedException Exception
+ */
+ public function testNotStartedYetThrowsException()
+ {
+ $t = new Horde_Support_Timer();
+ $t->pop();
+ }
+
+}
--- /dev/null
+<?php
+/**
+ * @category Horde
+ * @package Support
+ * @subpackage UnitTests
+ * @copyright 1999-2008 The Horde Project (http://www.horde.org/)
+ * @license http://opensource.org/licenses/bsd-license.php
+ */
+
+/**
+ * @group support
+ * @category Horde
+ * @package Support
+ * @subpackage UnitTests
+ * @copyright 1999-2008 The Horde Project (http://www.horde.org/)
+ * @license http://opensource.org/licenses/bsd-license.php
+ */
+class Horde_Support_UuidTest extends PHPUnit_Framework_TestCase
+{
+ /**
+ * test instantiating a normal timer
+ */
+ public function testUuidLength()
+ {
+ $uuid = (string)new Horde_Support_Uuid;
+ $this->assertEquals(36, strlen($uuid));
+ }
+
+}