jenkins-bot has submitted this change and it was merged. ( https://gerrit.wikimedia.org/r/384298 )
Change subject: Add EntityDiffChangedAspects + Factory ...................................................................... Add EntityDiffChangedAspects + Factory This should later be used when pushing repo changes to clients. See also T113468#3641207. The code in EntityDiffChangedAspectsFactory was mostly copied from AffectedPagesFinder::getChangedAspects. Bug: T113468 Change-Id: I0ac579e2063dfe92a974841330586423e5e7f393 --- M lib/autoload.php A lib/includes/Changes/EntityDiffChangedAspects.php A lib/includes/Changes/EntityDiffChangedAspectsFactory.php A lib/tests/phpunit/Changes/EntityDiffChangedAspectsFactoryTest.php A lib/tests/phpunit/Changes/EntityDiffChangedAspectsTest.php 5 files changed, 869 insertions(+), 0 deletions(-) Approvals: Ladsgroup: Looks good to me, approved jenkins-bot: Verified diff --git a/lib/autoload.php b/lib/autoload.php index fda04cf..6082d0c 100644 --- a/lib/autoload.php +++ b/lib/autoload.php @@ -20,6 +20,8 @@ 'Wikibase\\Lib\\AutoCommentFormatter' => __DIR__ . '/includes/Formatters/AutoCommentFormatter.php', 'Wikibase\\Lib\\Changes\\CentralIdLookupFactory' => __DIR__ . '/includes/Changes/CentralIdLookupFactory.php', 'Wikibase\\Lib\\Changes\\EntityChangeFactory' => __DIR__ . '/includes/Changes/EntityChangeFactory.php', + 'Wikibase\\Lib\\Changes\\EntityDiffChangedAspects' => __DIR__ . '/includes/Changes/EntityDiffChangedAspects.php', + 'Wikibase\\Lib\\Changes\\EntityDiffChangedAspectsFactory' => __DIR__ . '/includes/Changes/EntityDiffChangedAspectsFactory.php', 'Wikibase\\Lib\\CommonsLinkFormatter' => __DIR__ . '/includes/Formatters/CommonsLinkFormatter.php', 'Wikibase\\Lib\\ContentLanguages' => __DIR__ . '/includes/ContentLanguages.php', 'Wikibase\\Lib\\DataTypeDefinitions' => __DIR__ . '/includes/DataTypeDefinitions.php', @@ -156,6 +158,8 @@ 'Wikibase\\Lib\\Tests\\EntityRevisionLookupTest' => __DIR__ . '/tests/phpunit/EntityRevisionLookupTest.php', 'Wikibase\\Lib\\Tests\\MockPropertyLabelResolver' => __DIR__ . '/tests/phpunit/MockPropertyLabelResolver.php', 'Wikibase\\Lib\\Tests\\MockRepository' => __DIR__ . '/tests/phpunit/MockRepository.php', + 'Wikibase\\Lib\\Tests\\Changes\\EntityDiffChangedAspectsTest' => __DIR__ . '/tests/phpunit/Changes/EntityDiffChangedAspectsTest.php', + 'Wikibase\\Lib\\Tests\\Changes\\EntityDiffChangedAspectsFactoryTest' => __DIR__ . '/tests/phpunit/Changes/EntityDiffChangedAspectsFactoryTest.php', 'Wikibase\\Lib\\Tests\\Store\\EntityTermLookupTest' => __DIR__ . '/tests/phpunit/Store/EntityTermLookupTest.php', 'Wikibase\\Lib\\Tests\\Store\\HttpUrlPropertyOrderProviderTestMockHttp' => __DIR__ . '/tests/phpunit/Store/HttpUrlPropertyOrderProviderTestMockHttp.php', 'Wikibase\\Lib\\Tests\\Store\\MockChunkAccess' => __DIR__ . '/tests/phpunit/Store/MockChunkAccess.php', diff --git a/lib/includes/Changes/EntityDiffChangedAspects.php b/lib/includes/Changes/EntityDiffChangedAspects.php new file mode 100644 index 0000000..20b8615 --- /dev/null +++ b/lib/includes/Changes/EntityDiffChangedAspects.php @@ -0,0 +1,178 @@ +<?php + +namespace Wikibase\Lib\Changes; + +use MWException; +use Serializable; +use Wikimedia\Assert\Assert; + +/** + * This class holds a very compact and simple representation of an Entity diff for + * propagating repo changes to clients (T113468). + * This can also be used for entity types which don't have all aspects mentioned here, + * the aspects are represented as unchanged in that case. + * + * @license GPL-2.0+ + * @author Marius Hoch + */ +class EntityDiffChangedAspects implements Serializable { + + /** + * Increases whenever the array format (self::toArray) changes + */ + const ARRAYFORMATVERSION = 1; + + /** + * Language codes of the labels that changed (added, removed or updated) + * + * @var string[] + */ + private $labelChanges; + + /** + * Language codes of the descriptions that changed (added, removed or updated) + * + * @var string[] + */ + private $descriptionChanges; + + /** + * Property id serialization from the statements that changed (added, removed or updated) + * + * @var string[] + */ + private $statementChanges; + + /** + * Map of site ids to bool. The bool indicates whether only the badge has changed (false) + * or the actual value of the sitelink changed (true). + * + * @var bool[] + */ + private $siteLinkChanges; + + /** + * Other changes that are not covered in above, like aliases + * + * @var bool + */ + private $otherChanges; + + /** + * Note: If an entity doesn't have a certain aspect, just report that no changes happened (empty array). + * + * @param string[] $labelChanges Language codes of the labels that changed (added, removed or updated) + * @param string[] $descriptionChanges Language codes of the descriptions that changed (added, removed or updated) + * @param string[] $statementChanges Property id serialization from the statements that changed (added, removed or updated) + * @param bool[] $siteLinkChanges Map of site ids to bool: only the badge has changed (false) or the actual sitelink changed (true) + * @param bool $otherChanges Do we have changes that are not covered more specifically? + */ + public function __construct( + array $labelChanges, + array $descriptionChanges, + array $statementChanges, + array $siteLinkChanges, + $otherChanges + ) { + Assert::parameterElementType( 'string', $labelChanges, '$labelChanges' ); + Assert::parameterElementType( 'string', $descriptionChanges, '$descriptionChanges' ); + Assert::parameterElementType( 'string', $statementChanges, '$statementChanges' ); + Assert::parameterElementType( 'string', array_keys( $siteLinkChanges ), 'array_keys( $siteLinkChanges )' ); + Assert::parameterElementType( 'boolean', $siteLinkChanges, '$siteLinkChanges' ); + Assert::parameterType( 'boolean', $otherChanges, '$otherChanges' ); + + $this->labelChanges = $labelChanges; + $this->descriptionChanges = $descriptionChanges; + $this->statementChanges = $statementChanges; + $this->siteLinkChanges = $siteLinkChanges; + $this->otherChanges = $otherChanges; + } + + /** + * Language codes of the labels that changed (added, removed or updated) + * + * @return string[] + */ + public function getLabelChanges() { + return $this->labelChanges; + } + + /** + * Language codes of the descriptions that changed (added, removed or updated) + * + * @return string[] + */ + public function getDescriptionChanges() { + return $this->descriptionChanges; + } + + /** + * Property id serialization from the statements that changed (added, removed or updated) + * + * @return string[] + */ + public function getStatementChanges() { + return $this->statementChanges; + } + + /** + * Map of site ids to bool: only the badge has changed (false) or the actual sitelink changed (true) + * + * @return bool[] + */ + public function getSiteLinkChanges() { + return $this->siteLinkChanges; + } + + /** + * Do we have changes that are not covered more specifically? + * + * @return bool + */ + public function hasOtherChanges() { + return $this->otherChanges; + } + + /** + * @see Serializable::serialize + * + * @return string + */ + public function serialize() { + return json_encode( $this->toArray() ); + } + + /** + * @see Serializable::unserialize + * + * @return string + */ + public function unserialize( $serialized ) { + $data = json_decode( $serialized ); + + if ( $data->arrayFormatVersion !== self::ARRAYFORMATVERSION ) { + throw new MWException( 'Unsupported format version ' . $data->arrayFormatVersion ); + } + + $this->labelChanges = $data->labelChanges; + $this->descriptionChanges = $data->descriptionChanges; + $this->statementChanges = $data->statementChanges; + $this->siteLinkChanges = (array)$data->siteLinkChanges; + $this->otherChanges = $data->otherChanges; + } + + /** + * @return array[] + */ + public function toArray() { + return [ + 'arrayFormatVersion' => self::ARRAYFORMATVERSION, + 'labelChanges' => $this->getLabelChanges(), + 'descriptionChanges' => $this->getDescriptionChanges(), + 'statementChanges' => $this->getStatementChanges(), + 'siteLinkChanges' => $this->getSiteLinkChanges(), + 'otherChanges' => $this->hasOtherChanges(), + ]; + } + +} diff --git a/lib/includes/Changes/EntityDiffChangedAspectsFactory.php b/lib/includes/Changes/EntityDiffChangedAspectsFactory.php new file mode 100644 index 0000000..0d7b25a --- /dev/null +++ b/lib/includes/Changes/EntityDiffChangedAspectsFactory.php @@ -0,0 +1,150 @@ +<?php + +namespace Wikibase\Lib\Changes; + +use Diff\DiffOp\DiffOp; +use Diff\DiffOp\Diff\Diff; +use Diff\DiffOp\DiffOpAdd; +use Diff\DiffOp\DiffOpChange; +use Diff\DiffOp\DiffOpRemove; +use Wikibase\DataModel\Statement\Statement; +use Wikibase\DataModel\Services\Diff\EntityDiff; +use Wikibase\DataModel\Services\Diff\ItemDiff; + +/** + * Factory for EntityDiffChangedAspects. + * + * @license GPL-2.0+ + * @author Marius Hoch + */ +class EntityDiffChangedAspectsFactory { + + /** + * Get an EntityDiffChangedAspects instance from an EntityDiff. + * + * @param Diff $entityDiff + * @return EntityDiffChangedAspects + */ + public function newFromEntityDiff( Diff $entityDiff ) { + $labelChanges = []; + $descriptionChanges = []; + $statementChanges = []; + $siteLinkChanges = []; + $otherChanges = false; + + $remainingDiffOps = count( $entityDiff ); // this is a "deep" count! + + if ( $entityDiff instanceof ItemDiff && !$entityDiff->getSiteLinkDiff()->isEmpty() ) { + $siteLinkDiff = $entityDiff->getSiteLinkDiff(); + + $remainingDiffOps -= count( $siteLinkDiff ); + $siteLinkChanges = $this->getChangedSiteLinks( $siteLinkDiff ); + } + + if ( $entityDiff instanceof EntityDiff ) { + $labelsDiff = $entityDiff->getLabelsDiff(); + if ( !empty( $labelsDiff ) ) { + $remainingDiffOps -= count( $labelsDiff ); + $labelChanges = $this->getChangedLabels( $labelsDiff ); + } + + $descriptionsDiff = $entityDiff->getDescriptionsDiff(); + if ( !empty( $descriptionsDiff ) ) { + $remainingDiffOps -= count( $descriptionsDiff ); + $descriptionChanges = $this->getChangedDescriptions( $descriptionsDiff ); + } + + $claimsDiff = $entityDiff->getClaimsDiff(); + if ( !empty( $claimsDiff ) ) { + $remainingDiffOps -= count( $claimsDiff ); + $statementChanges = $this->getChangedStatements( $claimsDiff ); + } + } + + if ( $remainingDiffOps > 0 ) { + $otherChanges = true; + } + + return new EntityDiffChangedAspects( + $labelChanges, + $descriptionChanges, + $statementChanges, + $siteLinkChanges, + $otherChanges + ); + } + + /** + * @param Diff $siteLinkDiff + * + * @return string[] + */ + private function getChangedSiteLinks( Diff $siteLinkDiff ) { + $siteLinkChanges = []; + + foreach ( $siteLinkDiff as $siteId => $diffPerSite ) { + $siteLinkChanges[$siteId] = !$this->isBadgesOnlyChange( $diffPerSite ); + } + + return $siteLinkChanges; + } + + /** + * @param DiffOp $siteLinkDiffOp + * + * @return bool + */ + private function isBadgesOnlyChange( DiffOp $siteLinkDiffOp ) { + return $siteLinkDiffOp instanceof Diff && !array_key_exists( 'name', $siteLinkDiffOp ); + } + + /** + * @param Diff $labelsDiff + * + * @return string[] + */ + private function getChangedLabels( Diff $labelsDiff ) { + return array_keys( iterator_to_array( $labelsDiff ) ); + } + + /** + * @param Diff $descriptionsDiff + * + * @return string[] + */ + private function getChangedDescriptions( Diff $descriptionsDiff ) { + return array_keys( iterator_to_array( $descriptionsDiff ) ); + } + + /** + * @param Diff $claimsDiff + * + * @return string[] + */ + private function getChangedStatements( Diff $claimsDiff ) { + $changedStatements = []; + + foreach ( $claimsDiff as $pid => $diffOp ) { + /* @var $statement Statement */ + + if ( $diffOp instanceof DiffOpAdd ) { + $statement = $diffOp->getNewValue(); + } elseif ( $diffOp instanceof DiffOpRemove ) { + $statement = $diffOp->getOldValue(); + } elseif ( $diffOp instanceof DiffOpChange ) { + $statement = $diffOp->getOldValue(); + /* @var $newStatement Statement */ + $newStatement = $diffOp->getNewValue(); + + $changedStatements[] = $newStatement->getPropertyId()->getSerialization(); + } else { + wfLogWarning( 'Unknown DiffOp type ' . get_class( $diffOp ) ); + } + + $changedStatements[] = $statement->getPropertyId()->getSerialization(); + } + + return array_unique( $changedStatements ); + } + +} diff --git a/lib/tests/phpunit/Changes/EntityDiffChangedAspectsFactoryTest.php b/lib/tests/phpunit/Changes/EntityDiffChangedAspectsFactoryTest.php new file mode 100644 index 0000000..ca19dd3 --- /dev/null +++ b/lib/tests/phpunit/Changes/EntityDiffChangedAspectsFactoryTest.php @@ -0,0 +1,347 @@ +<?php + +namespace Wikibase\Lib\Tests\Changes; + +use Diff\DiffOp\DiffOpAdd; +use PHPUnit_Framework_TestCase; +use Wikibase\DataModel\Entity\EntityDocument; +use Wikibase\DataModel\Entity\Item; +use Wikibase\DataModel\Entity\ItemId; +use Wikibase\DataModel\Entity\Property; +use Wikibase\DataModel\Entity\PropertyId; +use Wikibase\DataModel\Services\Diff\EntityDiff; +use Wikibase\DataModel\Services\Diff\EntityDiffer; +use Wikibase\DataModel\SiteLink; +use Wikibase\DataModel\Snak\PropertyNoValueSnak; +use Wikibase\DataModel\Snak\PropertySomeValueSnak; +use Wikibase\DataModel\Statement\Statement; +use Wikibase\DataModel\Statement\StatementList; +use Wikibase\Lib\Changes\EntityDiffChangedAspects; +use Wikibase\Lib\Changes\EntityDiffChangedAspectsFactory; + +/** + * @covers Wikibase\Lib\Changes\EntityDiffChangedAspectsFactory + * + * @group Wikibase + * @group WikibaseChange + * + * @license GPL-2.0+ + * @author Marius Hoch + */ +class EntityDiffChangedAspectsFactoryTest extends PHPUnit_Framework_TestCase { + + public function provideNewFromEntityDiff() { + $emptyDiff = [ + 'arrayFormatVersion' => EntityDiffChangedAspects::ARRAYFORMATVERSION, + 'labelChanges' => [], + 'descriptionChanges' => [], + 'statementChanges' => [], + 'siteLinkChanges' => [], + 'otherChanges' => false, + ]; + + $labelDiff = $emptyDiff; + $labelDiff['labelChanges'] = [ 'de' ]; + + $descriptionDiff = $emptyDiff; + $descriptionDiff['descriptionChanges'] = [ 'ru' ]; + + $statementP1Diff = $emptyDiff; + $statementP1Diff['statementChanges'] = [ 'P1' ]; + + $statementP2Diff = $emptyDiff; + $statementP2Diff['statementChanges'] = [ 'P2' ]; + + $statementP1P2Diff = $emptyDiff; + $statementP1P2Diff['statementChanges'] = [ 'P1', 'P2' ]; + + $siteLinkDiff = $emptyDiff; + $siteLinkDiff['siteLinkChanges'] = [ 'enwiki' => true ]; + + $siteLinkBadgeOnlyDiff = $emptyDiff; + $siteLinkBadgeOnlyDiff['siteLinkChanges'] = [ 'enwiki' => false ]; + + $berlinEmptyDiff = [ + 'arrayFormatVersion' => EntityDiffChangedAspects::ARRAYFORMATVERSION, + 'labelChanges' => [ 'de', 'ru' ], + 'descriptionChanges' => [ 'de', 'es' ], + 'statementChanges' => [ 'P2' ], + 'siteLinkChanges' => [ 'dewiki' => true, 'enwiki' => true ], + 'otherChanges' => false, + ]; + + $parisEmptyDiff = [ + 'arrayFormatVersion' => EntityDiffChangedAspects::ARRAYFORMATVERSION, + 'labelChanges' => [ 'fr', 'ru' ], + 'descriptionChanges' => [ 'de', 'es', 'pl' ], + 'statementChanges' => [ 'P2' ], + 'siteLinkChanges' => [ 'dewiki' => true, 'enwiki' => true, 'ruwiki' => true ], + 'otherChanges' => false, + ]; + + $berlinParisDiff = [ + 'arrayFormatVersion' => EntityDiffChangedAspects::ARRAYFORMATVERSION, + 'labelChanges' => [ 'de', 'fr', 'ru' ], + 'descriptionChanges' => [ 'pl' ], + 'statementChanges' => [ 'P2' ], + 'siteLinkChanges' => [ 'dewiki' => true, 'enwiki' => true, 'ruwiki' => true ], + 'otherChanges' => false, + ]; + + $q2 = new ItemId( 'Q2' ); + $p1 = new PropertyId( 'P1' ); + $p2 = new PropertyId( 'P2' ); + + $noValueP1Statement = new Statement( new PropertyNoValueSnak( $p1 ) ); + $noValueP1Statements = new StatementList( [ $noValueP1Statement ] ); + + $noValueP2Statement = new Statement( new PropertyNoValueSnak( $p2 ) ); + $noValueP2Statements = new StatementList( [ $noValueP2Statement ] ); + + $someValueStatement = new Statement( new PropertySomeValueSnak( $p2 ) ); + $someValueStatements = new StatementList( [ $someValueStatement ] ); + + $emptyItem = new Item( $q2, null, null, null ); + $emptyProperty = new Property( $p2, null, 'hey', null ); + + $labelItem = $emptyItem->copy(); + $labelItem->setLabel( 'de', 'de label' ); + + $descriptionItem = $emptyItem->copy(); + $descriptionItem->setDescription( 'ru', 'ru desc' ); + + $noValueP1StatementItem = $emptyItem->copy(); + $noValueP1StatementItem->setStatements( $noValueP1Statements ); + + $noValueP2StatementItem = $emptyItem->copy(); + $noValueP2StatementItem->setStatements( $noValueP2Statements ); + + $someValueStatementItem = $emptyItem->copy(); + $someValueStatementItem->setStatements( $someValueStatements ); + + $siteLinkItem = $emptyItem->copy(); + $siteLinkItem->addSiteLink( new SiteLink( 'enwiki', 'PHP' ) ); + + $siteLinkBadgeItem = $emptyItem->copy(); + $siteLinkBadgeItem->addSiteLink( new SiteLink( 'enwiki', 'PHP', [ $q2 ] ) ); + + $berlinItem = $emptyItem->copy(); + $berlinItem->addSiteLink( new SiteLink( 'enwiki', 'Berlin' ) ); + $berlinItem->addSiteLink( new SiteLink( 'dewiki', 'Berlin', [ $q2 ] ) ); + $berlinItem->setLabel( 'de', 'Berlin' ); + $berlinItem->setLabel( 'ru', 'Берлин' ); + $berlinItem->setDescription( 'de', 'abc' ); + $berlinItem->setDescription( 'es', 'def' ); + $berlinItem->setStatements( $someValueStatements ); + + $parisItem = $emptyItem->copy(); + $parisItem->addSiteLink( new SiteLink( 'enwiki', 'Paris' ) ); + $parisItem->addSiteLink( new SiteLink( 'dewiki', 'Paris', [ $q2 ] ) ); + $parisItem->addSiteLink( new SiteLink( 'ruwiki', 'Paris' ) ); + $parisItem->setLabel( 'fr', 'Paris' ); + $parisItem->setLabel( 'ru', 'ru label' ); + $parisItem->setDescription( 'de', 'abc' ); + $parisItem->setDescription( 'es', 'def' ); + $parisItem->setDescription( 'pl', 'xyz' ); + $parisItem->setStatements( $noValueP2Statements ); + + $noValueP1Property = $emptyProperty->copy(); + $noValueP1Property->setStatements( $noValueP1Statements ); + + $cases = [ + '$emptyItem === $emptyItem' => [ + $emptyDiff, + $emptyItem, + $emptyItem + ], + '$emptyProperty === $emptyProperty' => [ + $emptyDiff, + $emptyProperty, + $emptyProperty + ], + '$labelItem === $labelItem' => [ + $emptyDiff, + $labelItem, + $labelItem + ], + '$descriptionItem === $descriptionItem' => [ + $emptyDiff, + $descriptionItem, + $descriptionItem + ], + '$noValueP1StatementItem === $noValueP1StatementItem' => [ + $emptyDiff, + $noValueP1StatementItem, + $noValueP1StatementItem + ], + '$noValueP2StatementItem === $noValueP2StatementItem' => [ + $emptyDiff, + $noValueP2StatementItem, + $noValueP2StatementItem + ], + '$someValueStatementItem === $someValueStatementItem' => [ + $emptyDiff, + $someValueStatementItem, + $someValueStatementItem + ], + '$siteLinkItem === $siteLinkItem' => [ + $emptyDiff, + $siteLinkItem, + $siteLinkItem + ], + '$siteLinkBadgeItem === $siteLinkBadgeItem' => [ + $emptyDiff, + $siteLinkBadgeItem, + $siteLinkBadgeItem + ], + '$berlinItem === $berlinItem' => [ + $emptyDiff, + $berlinItem, + $berlinItem + ], + '$parisItem === $parisItem' => [ + $emptyDiff, + $parisItem, + $parisItem + ], + '$noValueP1Property === $noValueP1Property' => [ + $emptyDiff, + $noValueP1Property, + $noValueP1Property + ], + 'label change' => [ + $labelDiff, + $emptyItem, + $labelItem + ], + 'description changes' => [ + $descriptionDiff, + $emptyItem, + $descriptionItem + ], + 'item statement change (no value)' => [ + $statementP1Diff, + $emptyItem, + $noValueP1StatementItem + ], + 'property statement change (no value)' => [ + $statementP1Diff, + $emptyProperty, + $noValueP1Property + ], + 'statement change (some value)' => [ + $statementP2Diff, + $emptyItem, + $someValueStatementItem + ], + 'statement change (other property id + some value <> no value)' => [ + $statementP1P2Diff, + $someValueStatementItem, + $noValueP1StatementItem + ], + 'statement change (some property id + some value <> no value)' => [ + $statementP2Diff, + $someValueStatementItem, + $noValueP2StatementItem + ], + 'sitelink changes' => [ + $siteLinkDiff, + $emptyItem, + $siteLinkItem + ], + 'sitelink change with badge' => [ + $siteLinkDiff, + $emptyItem, + $siteLinkBadgeItem + ], + 'sitelink badge only changes' => [ + $siteLinkBadgeOnlyDiff, + $siteLinkItem, + $siteLinkBadgeItem + ], + 'berlin item <> empty item' => [ + $berlinEmptyDiff, + $emptyItem, + $berlinItem + ], + 'paris item <> empty item' => [ + $parisEmptyDiff, + $emptyItem, + $parisItem + ], + 'paris item <> berlin item' => [ + $berlinParisDiff, + $berlinItem, + $parisItem + ], + ]; + + // All cases should result in the same aspect diff if the old and new entity are exchanged. + $reverseTests = []; + foreach ( $cases as $testDescription => $case ) { + $reverseTests[$testDescription . ' (reversed)'] = [ + $case[0], + $case[2], + $case[1] + ]; + } + + return array_merge( $cases, $reverseTests ); + } + + /** + * @dataProvider provideNewFromEntityDiff + */ + public function testNewFromEntityDiff( + array $expectedDiffArray, + EntityDocument $oldEntity, + EntityDocument $newEntity + ) { + $entityDiffer = new EntityDiffer(); + $entityDiff = $entityDiffer->diffEntities( $oldEntity, $newEntity ); + + $factory = new EntityDiffChangedAspectsFactory(); + $entityDiffChangedAspects = $factory->newFromEntityDiff( $entityDiff ); + $actual = $entityDiffChangedAspects->toArray(); + + $this->sortSubArrays( $actual ); + $this->sortSubArrays( $expectedDiffArray ); + + $this->assertEquals( $expectedDiffArray, $actual ); + } + + public function testNewFromEntityDiff_otherChanges() { + $entityDiff = new EntityDiff(); + // Add some unknown change + $entityDiff->addOperations( [ new DiffOpAdd( 1 ) ] ); + + $factory = new EntityDiffChangedAspectsFactory(); + $entityDiffChangedAspects = $factory->newFromEntityDiff( $entityDiff ); + $actual = $entityDiffChangedAspects->toArray(); + + $expectedDiff = [ + 'arrayFormatVersion' => EntityDiffChangedAspects::ARRAYFORMATVERSION, + 'labelChanges' => [], + 'descriptionChanges' => [], + 'statementChanges' => [], + 'siteLinkChanges' => [], + 'otherChanges' => true, + ]; + + $this->assertEquals( $expectedDiff, $actual ); + } + + /** + * Sort all sub-arrays (but leave the array itself alone). + * + * @param array &$arr + */ + private function sortSubArrays( array &$arr ) { + foreach ( $arr as &$subArr ) { + if ( is_array( $subArr ) ) { + sort( $subArr ); + } + } + } + +} diff --git a/lib/tests/phpunit/Changes/EntityDiffChangedAspectsTest.php b/lib/tests/phpunit/Changes/EntityDiffChangedAspectsTest.php new file mode 100644 index 0000000..bd1bd23 --- /dev/null +++ b/lib/tests/phpunit/Changes/EntityDiffChangedAspectsTest.php @@ -0,0 +1,190 @@ +<?php + +namespace Wikibase\Lib\Tests\Changes; + +use InvalidArgumentException; +use MWException; +use PHPUnit_Framework_TestCase; +use Wikibase\Lib\Changes\EntityDiffChangedAspects; + +/** + * @covers Wikibase\Lib\Changes\EntityDiffChangedAspects + * + * @group Wikibase + * @group WikibaseChange + * + * @license GPL-2.0+ + * @author Marius Hoch + */ +class EntityDiffChangedAspectsTest extends PHPUnit_Framework_TestCase { + + public function invalidConstructionProvider() { + $validParams = [ + 'labelChanges' => [ 'a', '1' ], + 'descriptionChanges' => [ 'b', '2' ], + 'statementChanges' => [ 'c', '3' ], + 'siteLinkChanges' => [ 'd' => true ], + 'otherChanges' => true, + ]; + + $invalidLabelChanges = $validParams; + $invalidLabelChanges['labelChanges'] = [ 'a', 1 ]; + + $invalidDescriptionChanges = $validParams; + $invalidDescriptionChanges['descriptionChanges'] = [ 'b', 2 ]; + + $invalidStatementChanges = $validParams; + $invalidStatementChanges['statementChanges'] = [ 'c', 3 ]; + + $invalidSiteLinkChangesKeys = $validParams; + $invalidSiteLinkChangesKeys['siteLinkChanges'] = [ 1 => true ]; + + $invalidSiteLinkChangesValues = $validParams; + $invalidSiteLinkChangesValues['siteLinkChanges'] = [ 'd' => 12 ]; + + $invalidOtherChanges = $validParams; + $invalidOtherChanges['otherChanges'] = null; + + return [ + 'Invalid labelChanges' => $invalidLabelChanges, + 'Invalid descriptionChanges' => $invalidDescriptionChanges, + 'Invalid statementChanges' => $invalidStatementChanges, + 'Invalid siteLinkChanges keys' => $invalidSiteLinkChangesKeys, + 'Invalid siteLinkChanges values' => $invalidSiteLinkChangesValues, + 'Invalid otherChanges' => $invalidOtherChanges, + ]; + } + + /** + * @dataProvider invalidConstructionProvider + */ + public function testInvalidConstruction( + array $labelChanges, + array $descriptionChanges, + array $statementChanges, + array $siteLinkChanges, + $otherChanges + ) { + $this->setExpectedException( InvalidArgumentException::class ); + + new EntityDiffChangedAspects( $labelChanges, $descriptionChanges, $statementChanges, $siteLinkChanges, $otherChanges ); + } + + private function getEntityDiffChangedAspects() { + return new EntityDiffChangedAspects( + [ 'a', '1' ], + [ 'b', '2' ], + [ 'c', '3' ], + [ 'd' => true ], + true + ); + } + + public function testGetLabelChanges() { + $this->assertSame( + [ 'a', '1' ], + $this->getEntityDiffChangedAspects()->getLabelChanges() + ); + } + + public function testGetDescriptionChanges() { + $this->assertSame( + [ 'b', '2' ], + $this->getEntityDiffChangedAspects()->getDescriptionChanges() + ); + } + + public function testGetStatementChanges() { + $this->assertSame( + [ 'c', '3' ], + $this->getEntityDiffChangedAspects()->getStatementChanges() + ); + } + + public function testGetSiteLinkChanges() { + $this->assertSame( + [ 'd' => true ], + $this->getEntityDiffChangedAspects()->getSiteLinkChanges() + ); + } + + public function testHasOtherChanges() { + $this->assertSame( + true, + $this->getEntityDiffChangedAspects()->hasOtherChanges() + ); + } + + public function testToArray() { + $expected = [ + 'arrayFormatVersion' => EntityDiffChangedAspects::ARRAYFORMATVERSION, + 'labelChanges' => [ 'a', '1' ], + 'descriptionChanges' => [ 'b', '2' ], + 'statementChanges' => [ 'c', '3' ], + 'siteLinkChanges' => [ 'd' => true ], + 'otherChanges' => true, + ]; + + $this->assertSame( $expected, $this->getEntityDiffChangedAspects()->toArray() ); + } + + public function testSerialize() { + $entityDiffChangedAspects = $this->getEntityDiffChangedAspects(); + + $entityDiffChangedAspectsClone = unserialize( serialize( $entityDiffChangedAspects ) ); + + $this->assertSame( $entityDiffChangedAspects->toArray(), $entityDiffChangedAspectsClone->toArray() ); + } + + /** + * @return string + */ + private function getKnownGoodSerialization() { + return 'C:45:"Wikibase\Lib\Changes\EntityDiffChangedAspects":129:' . + '{{"arrayFormatVersion":1,"labelChanges":[],"descriptionChanges":[],' . + '"statementChanges":[],"siteLinkChanges":[],"otherChanges":true}}'; + } + + public function testUnserialize() { + $entityDiffChangedAspects = unserialize( $this->getKnownGoodSerialization() ); + + $this->assertSame( + ( new EntityDiffChangedAspects( [], [], [], [], true ) )->toArray(), + $entityDiffChangedAspects->toArray() + ); + } + + public function wrongArrayFormatVersionProvider() { + // NOTE: If you remove versions here, make sure all good ones can be unserialized! + return [ + [ -1 ], + [ 0 ], + [ 2 ], + [ '"Milch"' ], + ]; + } + + /** + * @dataProvider wrongArrayFormatVersionProvider + */ + public function testUnserialize_wrongFormatVersion( $arrayFormatVersion ) { + $entityDiffChangedAspectsSerialization = $this->getKnownGoodSerialization(); + + // Change the array version in the serialization + $entityDiffChangedAspectsSerialization = str_replace( + '"arrayFormatVersion":1', + '"arrayFormatVersion":' . $arrayFormatVersion, + $entityDiffChangedAspectsSerialization + ); + // Change the length of the serialization "body" (the content from Serializable::serialize) + $entityDiffChangedAspectsSerialization = str_replace( + '":129:', + '":' . ( strlen( $arrayFormatVersion ) + 128 ) . ':', + $entityDiffChangedAspectsSerialization + ); + + $this->setExpectedException( MWException::class ); + unserialize( $entityDiffChangedAspectsSerialization ); + } + +} -- To view, visit https://gerrit.wikimedia.org/r/384298 To unsubscribe, visit https://gerrit.wikimedia.org/r/settings Gerrit-MessageType: merged Gerrit-Change-Id: I0ac579e2063dfe92a974841330586423e5e7f393 Gerrit-PatchSet: 9 Gerrit-Project: mediawiki/extensions/Wikibase Gerrit-Branch: master Gerrit-Owner: Hoo man <h...@online.de> Gerrit-Reviewer: Addshore <addshorew...@gmail.com> Gerrit-Reviewer: Daniel Kinzler <daniel.kinz...@wikimedia.de> Gerrit-Reviewer: Hoo man <h...@online.de> Gerrit-Reviewer: Ladsgroup <ladsgr...@gmail.com> Gerrit-Reviewer: Thiemo Mättig (WMDE) <thiemo.kr...@wikimedia.de> Gerrit-Reviewer: jenkins-bot <> _______________________________________________ MediaWiki-commits mailing list MediaWiki-commits@lists.wikimedia.org https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits