From: Chuck Hagenbuch Date: Tue, 21 Oct 2008 01:49:49 +0000 (-0400) Subject: initial import to initialize repository - framework horde/support package X-Git-Url: https://git.internetallee.de/?a=commitdiff_plain;h=23529319730b1eff9d70f697d2f5a9353dbcf16a;p=horde.git initial import to initialize repository - framework horde/support package --- 23529319730b1eff9d70f697d2f5a9353dbcf16a diff --git a/framework/Support/lib/Horde/Support/Array.php b/framework/Support/lib/Horde/Support/Array.php new file mode 100644 index 000000000..21541bbd3 --- /dev/null +++ b/framework/Support/lib/Horde/Support/Array.php @@ -0,0 +1,240 @@ +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); + } + +} diff --git a/framework/Support/lib/Horde/Support/ConsistentHash.php b/framework/Support/lib/Horde/Support/ConsistentHash.php new file mode 100644 index 000000000..b5d052e28 --- /dev/null +++ b/framework/Support/lib/Horde/Support/ConsistentHash.php @@ -0,0 +1,246 @@ +_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); + } + +} diff --git a/framework/Support/lib/Horde/Support/Inflector.php b/framework/Support/lib/Horde/Support/Inflector.php new file mode 100644 index 000000000..e5ec15f4d --- /dev/null +++ b/framework/Support/lib/Horde/Support/Inflector.php @@ -0,0 +1,266 @@ + '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(); + } + +} diff --git a/framework/Support/lib/Horde/Support/Stub.php b/framework/Support/lib/Horde/Support/Stub.php new file mode 100644 index 000000000..946b7237d --- /dev/null +++ b/framework/Support/lib/Horde/Support/Stub.php @@ -0,0 +1,64 @@ + + * $t = new Horde_Support_Timer; + * $t->push(); + * $elapsed = $t->pop(); + * + * + * @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]; + } + +} diff --git a/framework/Support/lib/Horde/Support/Uuid.php b/framework/Support/lib/Horde/Support/Uuid.php new file mode 100644 index 000000000..a5a914217 --- /dev/null +++ b/framework/Support/lib/Horde/Support/Uuid.php @@ -0,0 +1,78 @@ + + * + * + * + * @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; + } + +} diff --git a/framework/Support/package.xml b/framework/Support/package.xml new file mode 100644 index 000000000..a21039f6e --- /dev/null +++ b/framework/Support/package.xml @@ -0,0 +1,72 @@ + + + Support + pear.horde.org + Horde support package + 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. + + + Chuck Hagenbuch + chuck + chuck@horde.org + yes + + 2008-08-01 + + 0.1.0 + 0.1.0 + + + beta + beta + + BSD + + * 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 + + + + + + + + + + + + + + + + + + + + + 5.2.0 + + + 1.5.0 + + + + + + + + + + + + + + diff --git a/framework/Support/test/Horde/Support/AllTests.php b/framework/Support/test/Horde/Support/AllTests.php new file mode 100644 index 000000000..7fac8b189 --- /dev/null +++ b/framework/Support/test/Horde/Support/AllTests.php @@ -0,0 +1,54 @@ +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(); +} diff --git a/framework/Support/test/Horde/Support/ArrayTest.php b/framework/Support/test/Horde/Support/ArrayTest.php new file mode 100644 index 000000000..f4d8fd3df --- /dev/null +++ b/framework/Support/test/Horde/Support/ArrayTest.php @@ -0,0 +1,209 @@ +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()); + } + +} diff --git a/framework/Support/test/Horde/Support/ConsistentHashTest.php b/framework/Support/test/Horde/Support/ConsistentHashTest.php new file mode 100644 index 000000000..7d16d9066 --- /dev/null +++ b/framework/Support/test/Horde/Support/ConsistentHashTest.php @@ -0,0 +1,234 @@ +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]); + } + +} diff --git a/framework/Support/test/Horde/Support/InflectorTest.php b/framework/Support/test/Horde/Support/InflectorTest.php new file mode 100644 index 000000000..b15722dec --- /dev/null +++ b/framework/Support/test/Horde/Support/InflectorTest.php @@ -0,0 +1,84 @@ + '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')); + } + +} diff --git a/framework/Support/test/Horde/Support/StubTest.php b/framework/Support/test/Horde/Support/StubTest.php new file mode 100644 index 000000000..0d89323ba --- /dev/null +++ b/framework/Support/test/Horde/Support/StubTest.php @@ -0,0 +1,43 @@ +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()))); + } + +} diff --git a/framework/Support/test/Horde/Support/TimerTest.php b/framework/Support/test/Horde/Support/TimerTest.php new file mode 100644 index 000000000..6fc11cd31 --- /dev/null +++ b/framework/Support/test/Horde/Support/TimerTest.php @@ -0,0 +1,44 @@ +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(); + } + +} diff --git a/framework/Support/test/Horde/Support/UuidTest.php b/framework/Support/test/Horde/Support/UuidTest.php new file mode 100644 index 000000000..153d0fd5a --- /dev/null +++ b/framework/Support/test/Horde/Support/UuidTest.php @@ -0,0 +1,29 @@ +assertEquals(36, strlen($uuid)); + } + +}