From: jeroendedauw Date: Fri, 27 Jul 2012 18:31:23 +0000 (+0200) Subject: Added GenericArrayObject class and associated test base X-Git-Tag: 1.31.0-rc.0~22747^2 X-Git-Url: http://git.cyclocoop.org/%24href?a=commitdiff_plain;h=afe46f1403a49ed470091458f932c334e9d5467f;p=lhc%2Fweb%2Fwiklou.git Added GenericArrayObject class and associated test base Change-Id: Id7e9b59c7ed4a9338744db58935307ecb4bc441f --- diff --git a/includes/AutoLoader.php b/includes/AutoLoader.php index 98abb960d5..c5dc30544f 100644 --- a/includes/AutoLoader.php +++ b/includes/AutoLoader.php @@ -651,6 +651,7 @@ $wgAutoloadLocalClasses = array( 'CSSJanus' => 'includes/libs/CSSJanus.php', 'CSSJanus_Tokenizer' => 'includes/libs/CSSJanus.php', 'CSSMin' => 'includes/libs/CSSMin.php', + 'GenericArrayObject' => 'includes/libs/GenericArrayObject.php', 'HttpStatus' => 'includes/libs/HttpStatus.php', 'IEContentAnalyzer' => 'includes/libs/IEContentAnalyzer.php', 'IEUrlExtension' => 'includes/libs/IEUrlExtension.php', @@ -1051,6 +1052,9 @@ $wgAutoloadLocalClasses = array( 'TestFileIterator' => 'tests/testHelpers.inc', 'TestRecorder' => 'tests/testHelpers.inc', + # tests/phpunit/includes + 'GenericArrayObjectTest' => 'tests/phpunit/includes/libs/GenericArrayObjectTest.php', + # tests/phpunit/includes/db 'ORMRowTest' => 'tests/phpunit/includes/db/ORMRowTest.php', diff --git a/includes/libs/GenericArrayObject.php b/includes/libs/GenericArrayObject.php new file mode 100644 index 0000000000..d4cc5253db --- /dev/null +++ b/includes/libs/GenericArrayObject.php @@ -0,0 +1,244 @@ + + */ +abstract class GenericArrayObject extends ArrayObject { + + /** + * Returns the name of an interface/class that the element should implement/extend. + * + * @since 1.20 + * + * @return string + */ + public abstract function getObjectType(); + + /** + * @see SiteList::getNewOffset() + * @since 1.20 + * @var integer + */ + protected $indexOffset = 0; + + /** + * Finds a new offset for when appending an element. + * The base class does this, so it would be better to integrate, + * but there does not appear to be any way to do this... + * + * @since 1.20 + * + * @return integer + */ + protected function getNewOffset() { + while ( true ) { + if ( !$this->offsetExists( $this->indexOffset ) ) { + return $this->indexOffset; + } + + $this->indexOffset++; + } + } + + /** + * Constructor. + * @see ArrayObject::__construct + * + * @since 1.20 + * + * @param null|array $input + * @param int $flags + * @param string $iterator_class + */ + public function __construct( $input = null, $flags = 0, $iterator_class = 'ArrayIterator' ) { + parent::__construct( array(), $flags, $iterator_class ); + + if ( !is_null( $input ) ) { + foreach ( $input as $offset => $value ) { + $this->offsetSet( $offset, $value ); + } + } + } + + /** + * @see ArrayObject::append + * + * @since 1.20 + * + * @param mixed $value + */ + public function append( $value ) { + $this->setElement( null, $value ); + } + + /** + * @see ArrayObject::offsetSet() + * + * @since 1.20 + * + * @param mixed $index + * @param mixed $value + */ + public function offsetSet( $index, $value ) { + $this->setElement( $index, $value ); + } + + /** + * Returns if the provided value has the same type as the elements + * that can be added to this ArrayObject. + * + * @since 1.20 + * + * @param mixed $value + * + * @return boolean + */ + protected function hasValidType( $value ) { + $class = $this->getObjectType(); + return $value instanceof $class; + } + + /** + * Method that actually sets the element and holds + * all common code needed for set operations, including + * type checking and offset resolving. + * + * If you want to do additional indexing or have code that + * otherwise needs to be executed whenever an element is added, + * you can overload @see preSetElement. + * + * @since 1.20 + * + * @param mixed $index + * @param mixed $value + * + * @throws Exception + */ + protected function setElement( $index, $value ) { + if ( !$this->hasValidType( $value ) ) { + throw new Exception( + 'Can only add ' . $this->getObjectType() . ' implementing objects to ' . get_called_class() . '.' + ); + } + + if ( is_null( $index ) ) { + $index = $this->getNewOffset(); + } + + if ( $this->preSetElement( $index, $value ) ) { + parent::offsetSet( $index, $value ); + } + } + + /** + * Gets called before a new element is added to the ArrayObject. + * + * At this point the index is always set (ie not null) and the + * value is always of the type returned by @see getObjectType. + * + * Should return a boolean. When false is returned the element + * does not get added to the ArrayObject. + * + * @since 1.20 + * + * @param integer|string $index + * @param mixed $value + * + * @return boolean + */ + protected function preSetElement( $index, $value ) { + return true; + } + + /** + * @see Serializable::serialize + * + * @since 1.20 + * + * @return string + */ + public function serialize() { + return serialize( $this->getSerializationData() ); + } + + /** + * Returns an array holding all the data that should go into serialization calls. + * This is intended to allow overloading without having to reimplement the + * behaviour of this base class. + * + * @since 1.20 + * + * @return array + */ + protected function getSerializationData() { + return array( + 'data' => $this->getArrayCopy(), + 'index' => $this->indexOffset, + ); + } + + /** + * @see Serializable::unserialize + * + * @since 1.20 + * + * @param string $serialization + * + * @return array + */ + public function unserialize( $serialization ) { + $serializationData = unserialize( $serialization ); + + foreach ( $serializationData['data'] as $offset => $value ) { + // Just set the element, bypassing checks and offset resolving, + // as these elements have already gone through this. + parent::offsetSet( $offset, $value ); + } + + $this->indexOffset = $serializationData['index']; + + return $serializationData; + } + + /** + * Returns if the ArrayObject has no elements. + * + * @since 1.20 + * + * @return boolean + */ + public function isEmpty() { + return $this->count() === 0; + } + +} diff --git a/tests/phpunit/includes/libs/GenericArrayObjectTest.php b/tests/phpunit/includes/libs/GenericArrayObjectTest.php new file mode 100644 index 0000000000..5e4cda1eea --- /dev/null +++ b/tests/phpunit/includes/libs/GenericArrayObjectTest.php @@ -0,0 +1,238 @@ + + */ +abstract class GenericArrayObjectTest extends MediaWikiTestCase { + + /** + * Returns objects that can serve as elements in the concrete GenericArrayObject deriving class being tested. + * + * @since 1.20 + * + * @return array + */ + public abstract function elementInstancesProvider(); + + /** + * Provides instances of the concrete class being tested. + * + * @since 1.20 + * + * @return array + */ + public abstract function instanceProvider(); + + /** + * Returns the name of the concrete class being tested. + * + * @since 1.20 + * + * @return string + */ + public abstract function getInstanceClass(); + + /** + * @since 1.20 + * + * @param array $elements + * + * @return GenericArrayObject + */ + protected function getNew( array $elements = array() ) { + $class = $this->getInstanceClass(); + return new $class( $elements ); + } + + /** + * @dataProvider elementInstancesProvider + * + * @since 1.20 + * + * @param array $elements + */ + public function testConstructor( array $elements ) { + $arrayObject = $this->getNew( $elements ); + + $this->assertEquals( count( $elements ), $arrayObject->count() ); + } + + /** + * @dataProvider elementInstancesProvider + * + * @since 1.20 + * + * @param array $elements + */ + public function testIsEmpty( array $elements ) { + $arrayObject = $this->getNew( $elements ); + + $this->assertEquals( $elements === array(), $arrayObject->isEmpty() ); + } + + /** + * @dataProvider instanceProvider + * + * @since 1.20 + * + * @param GenericArrayObject $list + */ + public function testUnset( GenericArrayObject $list ) { + if ( !$list->isEmpty() ) { + $offset = $list->getIterator()->key(); + $count = $list->count(); + $list->offsetUnset( $offset ); + $this->assertEquals( $count - 1, $list->count() ); + } + + if ( !$list->isEmpty() ) { + $offset = $list->getIterator()->key(); + $count = $list->count(); + unset( $list[$offset] ); + $this->assertEquals( $count - 1, $list->count() ); + } + + $exception = null; + try { $list->offsetUnset( 'sdfsedtgsrdysftu' ); } catch ( \Exception $exception ){} + $this->assertInstanceOf( '\Exception', $exception ); + } + + /** + * @dataProvider elementInstancesProvider + * + * @since 1.20 + * + * @param array $elements + */ + public function testAppend( array $elements ) { + $list = $this->getNew(); + + $listSize = count( $elements ); + + foreach ( $elements as $element ) { + $list->append( $element ); + } + + $this->assertEquals( $listSize, $list->count() ); + + $list = $this->getNew(); + + foreach ( $elements as $element ) { + $list[] = $element; + } + + $this->assertEquals( $listSize, $list->count() ); + + $this->checkTypeChecks( function( GenericArrayObject $list, $element ) { + $list->append( $element ); + } ); + } + + /** + * @since 1.20 + * + * @param callback $function + */ + protected function checkTypeChecks( $function ) { + $excption = null; + $list = $this->getNew(); + + $elementClass = $list->getObjectType(); + + foreach ( array( 42, 'foo', array(), new \stdClass(), 4.2 ) as $element ) { + $validValid = $element instanceof $elementClass; + + try{ + call_user_func( $function, $list, $element ); + $valid = true; + } + catch ( \MWException $exception ) { + $valid = false; + } + + $this->assertEquals( + $validValid, + $valid, + 'Object of invalid type got successfully added to a GenericArrayObject' + ); + } + } + + /** + * @dataProvider elementInstancesProvider + * + * @since 1.20 + * + * @param array $elements + */ + public function testOffsetSet( array $elements ) { + if ( $elements === array() ) { + $this->assertTrue( true ); + return; + } + + $list = $this->getNew(); + + $element = reset( $elements ); + $list->offsetSet( 42, $element ); + $this->assertEquals( $element, $list->offsetGet( 42 ) ); + + $list = $this->getNew(); + + $element = reset( $elements ); + $list['oHai'] = $element; + $this->assertEquals( $element, $list['oHai'] ); + + $list = $this->getNew(); + + $element = reset( $elements ); + $list->offsetSet( 9001, $element ); + $this->assertEquals( $element, $list[9001] ); + + $list = $this->getNew(); + + $element = reset( $elements ); + $list->offsetSet( null, $element ); + $this->assertEquals( $element, $list[0] ); + + $list = $this->getNew(); + $offset = 0; + + foreach ( $elements as $element ) { + $list->offsetSet( null, $element ); + $this->assertEquals( $element, $list[$offset++] ); + } + + $this->assertEquals( count( $elements ), $list->count() ); + + $this->checkTypeChecks( function( GenericArrayObject $list, $element ) { + $list->offsetSet( mt_rand(), $element ); + } ); + } + +}