'MediaWiki\\Storage\\RevisionLookup' => __DIR__ . '/includes/Storage/RevisionLookup.php',
'MediaWiki\\Storage\\RevisionRecord' => __DIR__ . '/includes/Storage/RevisionRecord.php',
'MediaWiki\\Storage\\RevisionSlots' => __DIR__ . '/includes/Storage/RevisionSlots.php',
+ 'MediaWiki\\Storage\\RevisionSlotsUpdate' => __DIR__ . '/includes/Storage/RevisionSlotsUpdate.php',
'MediaWiki\\Storage\\RevisionStore' => __DIR__ . '/includes/Storage/RevisionStore.php',
'MediaWiki\\Storage\\RevisionStoreRecord' => __DIR__ . '/includes/Storage/RevisionStoreRecord.php',
'MediaWiki\\Storage\\SlotRecord' => __DIR__ . '/includes/Storage/SlotRecord.php',
unset( $this->slots[$role] );
}
- /**
- * Return all slots that are not inherited.
- *
- * @note This may cause the slot meta-data for the revision to be lazy-loaded.
- *
- * @return SlotRecord[]
- */
- public function getTouchedSlots() {
- return array_filter(
- $this->getSlots(),
- function ( SlotRecord $slot ) {
- return !$slot->isInherited();
- }
- );
- }
-
- /**
- * Return all slots that are inherited.
- *
- * @note This may cause the slot meta-data for the revision to be lazy-loaded.
- *
- * @return SlotRecord[]
- */
- public function getInheritedSlots() {
- return array_filter(
- $this->getSlots(),
- function ( SlotRecord $slot ) {
- return $slot->isInherited();
- }
- );
- }
-
}
* @param SlotRecord[] $slots
*/
private function setSlotsInternal( array $slots ) {
+ Assert::parameterElementType( SlotRecord::class, $slots, '$slots' );
+
$this->slots = [];
// re-key the slot array
}, null );
}
+ /**
+ * Return all slots that are not inherited.
+ *
+ * @note This may cause the slot meta-data for the revision to be lazy-loaded.
+ *
+ * @return SlotRecord[]
+ */
+ public function getTouchedSlots() {
+ return array_filter(
+ $this->getSlots(),
+ function ( SlotRecord $slot ) {
+ return !$slot->isInherited();
+ }
+ );
+ }
+
+ /**
+ * Return all slots that are inherited.
+ *
+ * @note This may cause the slot meta-data for the revision to be lazy-loaded.
+ *
+ * @return SlotRecord[]
+ */
+ public function getInheritedSlots() {
+ return array_filter(
+ $this->getSlots(),
+ function ( SlotRecord $slot ) {
+ return $slot->isInherited();
+ }
+ );
+ }
+
+ /**
+ * Checks whether the other RevisionSlots instance has the same content
+ * as this instance. Note that this does not mean that the slots have to be the same:
+ * they could for instance belong to different revisions.
+ *
+ * @param RevisionSlots $other
+ *
+ * @return bool
+ */
+ public function hasSameContent( RevisionSlots $other ) {
+ if ( $other === $this ) {
+ return true;
+ }
+
+ $aSlots = $this->getSlots();
+ $bSlots = $other->getSlots();
+
+ ksort( $aSlots );
+ ksort( $bSlots );
+
+ if ( array_keys( $aSlots ) !== array_keys( $bSlots ) ) {
+ return false;
+ }
+
+ foreach ( $aSlots as $role => $s ) {
+ $t = $bSlots[$role];
+
+ if ( !$s->hasSameContent( $t ) ) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
}
--- /dev/null
+<?php
+/**
+ * Value object representing a modification of revision slots.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ */
+
+namespace MediaWiki\Storage;
+
+use Content;
+
+/**
+ * Value object representing a modification of revision slots.
+ *
+ * @since 1.32
+ */
+class RevisionSlotsUpdate {
+
+ /**
+ * @var SlotRecord[] modified slots, using the slot role as the key.
+ */
+ private $modifiedSlots = [];
+
+ /**
+ * @var bool[] removed roles, stored in the keys of the array.
+ */
+ private $removedRoles = [];
+
+ /**
+ * Constructs a RevisionSlotsUpdate representing the update that turned $parentSlots
+ * into $newSlots. If $parentSlots is not given, $newSlots is assumed to come from a
+ * page's first revision.
+ *
+ * @param RevisionSlots $newSlots
+ * @param RevisionSlots|null $parentSlots
+ *
+ * @return RevisionSlotsUpdate
+ */
+ public static function newFromRevisionSlots(
+ RevisionSlots $newSlots,
+ RevisionSlots $parentSlots = null
+ ) {
+ $modified = $newSlots->getSlots();
+ $removed = [];
+
+ if ( $parentSlots ) {
+ foreach ( $parentSlots->getSlots() as $role => $slot ) {
+ if ( !isset( $modified[$role] ) ) {
+ $removed[] = $role;
+ } elseif ( $slot->hasSameContent( $modified[$role] ) ) {
+ // Unset slots that had the same content in the parent revision from $modified.
+ unset( $modified[$role] );
+ }
+ }
+ }
+
+ return new RevisionSlotsUpdate( $modified, $removed );
+ }
+
+ /**
+ * @param SlotRecord[] $modifiedSlots
+ * @param string[] $removedRoles
+ */
+ public function __construct( array $modifiedSlots = [], array $removedRoles = [] ) {
+ foreach ( $modifiedSlots as $slot ) {
+ $this->modifySlot( $slot );
+ }
+
+ foreach ( $removedRoles as $role ) {
+ $this->removeSlot( $role );
+ }
+ }
+
+ /**
+ * Returns a list of modified slot roles, that is, roles modified by calling modifySlot(),
+ * and not later removed by calling removeSlot().
+ *
+ * @return string[]
+ */
+ public function getModifiedRoles() {
+ return array_keys( $this->modifiedSlots );
+ }
+
+ /**
+ * Returns a list of removed slot roles, that is, roles removed by calling removeSlot(),
+ * and not later re-introduced by calling modifySlot().
+ *
+ * @return string[]
+ */
+ public function getRemovedRoles() {
+ return array_keys( $this->removedRoles );
+ }
+
+ /**
+ * Returns a list of all slot roles that modified or removed.
+ *
+ * @return string[]
+ */
+ public function getTouchedRoles() {
+ return array_merge( $this->getModifiedRoles(), $this->getRemovedRoles() );
+ }
+
+ /**
+ * Sets the given slot to be modified.
+ * If a slot with the same role is already present, it is replaced.
+ *
+ * The roles used with modifySlot() will be returned from getModifiedRoles(),
+ * unless overwritten with removeSlot().
+ *
+ * @param SlotRecord $slot
+ */
+ public function modifySlot( SlotRecord $slot ) {
+ $role = $slot->getRole();
+
+ // XXX: We should perhaps require this to be an unsaved slot!
+ unset( $this->removedRoles[$role] );
+ $this->modifiedSlots[$role] = $slot;
+ }
+
+ /**
+ * Sets the content for the slot with the given role to be modified.
+ * If a slot with the same role is already present, it is replaced.
+ *
+ * @param string $role
+ * @param Content $content
+ */
+ public function modifyContent( $role, Content $content ) {
+ $slot = SlotRecord::newUnsaved( $role, $content );
+ $this->modifySlot( $slot );
+ }
+
+ /**
+ * Remove the slot for the given role, discontinue the corresponding stream.
+ *
+ * The roles used with removeSlot() will be returned from getRemovedSlots(),
+ * unless overwritten with modifySlot().
+ *
+ * @param string $role
+ */
+ public function removeSlot( $role ) {
+ unset( $this->modifiedSlots[$role] );
+ $this->removedRoles[$role] = true;
+ }
+
+ /**
+ * Returns the SlotRecord associated with the given role, if the slot with that role
+ * was modified (and not again removed).
+ *
+ * @note If the SlotRecord returned by this method returns a non-inherited slot,
+ * the content of that slot may or may not already have PST applied. Methods
+ * that take a RevisionSlotsUpdate as a parameter should specify whether they
+ * expect PST to already have been applied to all slots. Inherited slots
+ * should never have PST applied again.
+ *
+ * @param string $role The role name of the desired slot
+ *
+ * @throws RevisionAccessException if the slot does not exist or was removed.
+ * @return SlotRecord
+ */
+ public function getModifiedSlot( $role ) {
+ if ( isset( $this->modifiedSlots[$role] ) ) {
+ return $this->modifiedSlots[$role];
+ } else {
+ throw new RevisionAccessException( 'No such slot: ' . $role );
+ }
+ }
+
+ /**
+ * Returns whether getModifiedSlot() will return a SlotRecord for the given role.
+ *
+ * Will return true for the role names returned by getModifiedRoles(), false otherwise.
+ *
+ * @param string $role The role name of the desired slot
+ *
+ * @return bool
+ */
+ public function isModifiedSlot( $role ) {
+ return isset( $this->modifiedSlots[$role] );
+ }
+
+ /**
+ * Returns whether the given role is to be removed from the page.
+ *
+ * Will return true for the role names returned by getRemovedRoles(), false otherwise.
+ *
+ * @param string $role The role name of the desired slot
+ *
+ * @return bool
+ */
+ public function isRemovedSlot( $role ) {
+ return isset( $this->removedRoles[$role] );
+ }
+
+ /**
+ * Returns true if $other represents the same update - that is,
+ * if all methods defined by RevisionSlotsUpdate when called on $this or $other
+ * will yield the same result when called with the same parameters.
+ *
+ * SlotRecords for the same role are compared based on their model and content.
+ *
+ * @param RevisionSlotsUpdate $other
+ * @return bool
+ */
+ public function hasSameUpdates( RevisionSlotsUpdate $other ) {
+ // NOTE: use != not !==, since the order of entries is not significant!
+
+ if ( $this->getModifiedRoles() != $other->getModifiedRoles() ) {
+ return false;
+ }
+
+ if ( $this->getRemovedRoles() != $other->getRemovedRoles() ) {
+ return false;
+ }
+
+ foreach ( $this->getModifiedRoles() as $role ) {
+ $s = $this->getModifiedSlot( $role );
+ $t = $other->getModifiedSlot( $role );
+
+ if ( !$s->hasSameContent( $t ) ) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+}
return \Wikimedia\base_convert( sha1( $blob ), 16, 36, 31 );
}
+ /**
+ * Returns true if $other has the same content as this slot.
+ * The check is performed based on the model, address size, and hash.
+ * Two slots can have the same content if they use different content addresses,
+ * but if they have the same address and the same model, they have the same content.
+ * Two slots can have the same content if they belong to different
+ * revisions or pages.
+ *
+ * Note that hasSameContent() may return false even if Content::equals returns true for
+ * the content of two slots. This may happen if the two slots have different serializations
+ * representing equivalent Content. Such false negatives are considered acceptable. Code
+ * that has to be absolutely sure the Content is really not the same if hasSameContent()
+ * returns false should call getContent() and compare the Content objects directly.
+ *
+ * @since 1.32
+ *
+ * @param SlotRecord $other
+ * @return bool
+ */
+ public function hasSameContent( SlotRecord $other ) {
+ if ( $other === $this ) {
+ return true;
+ }
+
+ if ( $this->getModel() !== $other->getModel() ) {
+ return false;
+ }
+
+ if ( $this->hasAddress()
+ && $other->hasAddress()
+ && $this->getAddress() == $other->getAddress()
+ ) {
+ return true;
+ }
+
+ if ( $this->getSize() !== $other->getSize() ) {
+ return false;
+ }
+
+ if ( $this->getSha1() !== $other->getSha1() ) {
+ return false;
+ }
+
+ return true;
+ }
+
}
namespace MediaWiki\Tests\Storage;
+use InvalidArgumentException;
use MediaWiki\Storage\MutableRevisionSlots;
use MediaWiki\Storage\RevisionAccessException;
+use MediaWiki\Storage\RevisionSlots;
use MediaWiki\Storage\SlotRecord;
use WikitextContent;
*/
class MutableRevisionSlotsTest extends RevisionSlotsTest {
+ /**
+ * @param SlotRecord[] $slots
+ * @return RevisionSlots
+ */
+ protected function newRevisionSlots( $slots = [] ) {
+ return new MutableRevisionSlots( $slots );
+ }
+
+ public function provideConstructorFailue() {
+ yield 'array or the wrong thing' => [
+ [ 1, 2, 3 ]
+ ];
+ }
+
+ /**
+ * @dataProvider provideConstructorFailue
+ * @param $slots
+ *
+ * @covers \MediaWiki\Storage\RevisionSlots::__construct
+ * @covers \MediaWiki\Storage\RevisionSlots::setSlotsInternal
+ */
+ public function testConstructorFailue( $slots ) {
+ $this->setExpectedException( InvalidArgumentException::class );
+
+ new MutableRevisionSlots( $slots );
+ }
+
public function testSetMultipleSlots() {
$slots = new MutableRevisionSlots();
namespace MediaWiki\Tests\Storage;
+use InvalidArgumentException;
use MediaWiki\Storage\RevisionAccessException;
use MediaWiki\Storage\RevisionSlots;
use MediaWiki\Storage\SlotRecord;
use MediaWikiTestCase;
+use TextContent;
use WikitextContent;
class RevisionSlotsTest extends MediaWikiTestCase {
return new RevisionSlots( $slots );
}
+ public function provideConstructorFailue() {
+ yield 'not an array or callable' => [
+ 'foo'
+ ];
+ yield 'array of the wrong thing' => [
+ [ 1, 2, 3 ]
+ ];
+ }
+
+ /**
+ * @dataProvider provideConstructorFailue
+ * @param $slots
+ *
+ * @covers \MediaWiki\Storage\RevisionSlots::__construct
+ * @covers \MediaWiki\Storage\RevisionSlots::setSlotsInternal
+ */
+ public function testConstructorFailue( $slots ) {
+ $this->setExpectedException( InvalidArgumentException::class );
+
+ new RevisionSlots( $slots );
+ }
+
/**
* @covers \MediaWiki\Storage\RevisionSlots::getSlot
*/
$this->assertEquals( [ 'main' => $mainSlot, 'aux' => $auxSlot ], $slots->getSlots() );
}
+ /**
+ * @covers \MediaWiki\Storage\RevisionSlots::getInheritedSlots
+ */
+ public function testGetInheritedSlots() {
+ $mainSlot = SlotRecord::newUnsaved( 'main', new WikitextContent( 'A' ) );
+ $auxSlot = SlotRecord::newInherited(
+ SlotRecord::newSaved(
+ 7, 7, 'foo',
+ SlotRecord::newUnsaved( 'aux', new WikitextContent( 'B' ) )
+ )
+ );
+ $slotsArray = [ $mainSlot, $auxSlot ];
+ $slots = $this->newRevisionSlots( $slotsArray );
+
+ $this->assertEquals( [ 'aux' => $auxSlot ], $slots->getInheritedSlots() );
+ }
+
+ /**
+ * @covers \MediaWiki\Storage\RevisionSlots::getTouchedSlots
+ */
+ public function testGetTouchedSlots() {
+ $mainSlot = SlotRecord::newUnsaved( 'main', new WikitextContent( 'A' ) );
+ $auxSlot = SlotRecord::newInherited(
+ SlotRecord::newSaved(
+ 7, 7, 'foo',
+ SlotRecord::newUnsaved( 'aux', new WikitextContent( 'B' ) )
+ )
+ );
+ $slotsArray = [ $mainSlot, $auxSlot ];
+ $slots = $this->newRevisionSlots( $slotsArray );
+
+ $this->assertEquals( [ 'main' => $mainSlot ], $slots->getTouchedSlots() );
+ }
+
public function provideComputeSize() {
yield [ 1, [ 'A' ] ];
yield [ 2, [ 'AA' ] ];
$this->assertSame( $expected, $slots->computeSha1() );
}
+ public function provideHasSameContent() {
+ $fooX = SlotRecord::newUnsaved( 'x', new TextContent( 'Foo' ) );
+ $barZ = SlotRecord::newUnsaved( 'z', new TextContent( 'Bar' ) );
+ $fooY = SlotRecord::newUnsaved( 'y', new TextContent( 'Foo' ) );
+ $barZS = SlotRecord::newSaved( 7, 7, 'xyz', $barZ );
+ $barZ2 = SlotRecord::newUnsaved( 'z', new TextContent( 'Baz' ) );
+
+ $a = $this->newRevisionSlots( [ 'x' => $fooX, 'z' => $barZ ] );
+ $a2 = $this->newRevisionSlots( [ 'x' => $fooX, 'z' => $barZ ] );
+ $a3 = $this->newRevisionSlots( [ 'x' => $fooX, 'z' => $barZS ] );
+ $b = $this->newRevisionSlots( [ 'y' => $fooY, 'z' => $barZ ] );
+ $c = $this->newRevisionSlots( [ 'x' => $fooX, 'z' => $barZ2 ] );
+
+ yield 'same instance' => [ $a, $a, true ];
+ yield 'same slots' => [ $a, $a2, true ];
+ yield 'same content' => [ $a, $a3, true ];
+
+ yield 'different roles' => [ $a, $b, false ];
+ yield 'different content' => [ $a, $c, false ];
+ }
+
+ /**
+ * @dataProvider provideHasSameContent
+ * @covers \MediaWiki\Storage\RevisionSlots::hasSameContent
+ */
+ public function testHasSameContent( RevisionSlots $a, RevisionSlots $b, $same ) {
+ $this->assertSame( $same, $a->hasSameContent( $b ) );
+ $this->assertSame( $same, $b->hasSameContent( $a ) );
+ }
+
}
--- /dev/null
+<?php
+
+namespace MediaWiki\Tests\Storage;
+
+use MediaWiki\Storage\RevisionSlots;
+use MediaWiki\Storage\RevisionSlotsUpdate;
+use MediaWiki\Storage\RevisionAccessException;
+use MediaWiki\Storage\SlotRecord;
+use MediaWikiTestCase;
+use WikitextContent;
+
+/**
+ * @covers \MediaWiki\Storage\RevisionSlotsUpdate
+ */
+class RevisionSlotsUpdateTest extends MediaWikiTestCase {
+
+ public function provideNewFromRevisionSlots() {
+ $slotA = SlotRecord::newUnsaved( 'A', new WikitextContent( 'A' ) );
+ $slotB = SlotRecord::newUnsaved( 'B', new WikitextContent( 'B' ) );
+ $slotC = SlotRecord::newUnsaved( 'C', new WikitextContent( 'C' ) );
+
+ $slotB2 = SlotRecord::newUnsaved( 'B', new WikitextContent( 'B2' ) );
+
+ $parentSlots = new RevisionSlots( [
+ 'A' => $slotA,
+ 'B' => $slotB,
+ 'C' => $slotC,
+ ] );
+
+ $newSlots = new RevisionSlots( [
+ 'A' => $slotA,
+ 'B' => $slotB2,
+ ] );
+
+ yield [ $newSlots, null, [ 'A', 'B' ], [] ];
+ yield [ $newSlots, $parentSlots, [ 'B' ], [ 'C' ] ];
+ }
+
+ /**
+ * @dataProvider provideNewFromRevisionSlots
+ *
+ * @param RevisionSlots $newSlots
+ * @param RevisionSlots $parentSlots
+ * @param $modified
+ * @param $removed
+ */
+ public function testNewFromRevisionSlots(
+ RevisionSlots $newSlots,
+ RevisionSlots $parentSlots = null,
+ array $modified = [],
+ array $removed = []
+ ) {
+ $update = RevisionSlotsUpdate::newFromRevisionSlots( $newSlots, $parentSlots );
+
+ $this->assertEquals( $modified, $update->getModifiedRoles() );
+ $this->assertEquals( $removed, $update->getRemovedRoles() );
+
+ foreach ( $modified as $role ) {
+ $this->assertSame( $newSlots->getSlot( $role ), $update->getModifiedSlot( $role ) );
+ }
+ }
+
+ public function testConstructor() {
+ $update = new RevisionSlotsUpdate();
+
+ $this->assertEmpty( $update->getModifiedRoles() );
+ $this->assertEmpty( $update->getRemovedRoles() );
+
+ $slotA = SlotRecord::newUnsaved( 'A', new WikitextContent( 'A' ) );
+ $update = new RevisionSlotsUpdate( [ 'A' => $slotA ] );
+
+ $this->assertEquals( [ 'A' ], $update->getModifiedRoles() );
+ $this->assertEmpty( $update->getRemovedRoles() );
+
+ $update = new RevisionSlotsUpdate( [ 'A' => $slotA ], [ 'X' ] );
+
+ $this->assertEquals( [ 'A' ], $update->getModifiedRoles() );
+ $this->assertEquals( [ 'X' ], $update->getRemovedRoles() );
+ }
+
+ public function testModifySlot() {
+ $slots = new RevisionSlotsUpdate();
+
+ $this->assertSame( [], $slots->getModifiedRoles() );
+ $this->assertSame( [], $slots->getRemovedRoles() );
+
+ $slotA = SlotRecord::newUnsaved( 'some', new WikitextContent( 'A' ) );
+ $slots->modifySlot( $slotA );
+ $this->assertTrue( $slots->isModifiedSlot( 'some' ) );
+ $this->assertFalse( $slots->isRemovedSlot( 'some' ) );
+ $this->assertSame( $slotA, $slots->getModifiedSlot( 'some' ) );
+ $this->assertSame( [ 'some' ], $slots->getModifiedRoles() );
+ $this->assertSame( [], $slots->getRemovedRoles() );
+
+ $slotB = SlotRecord::newUnsaved( 'other', new WikitextContent( 'B' ) );
+ $slots->modifySlot( $slotB );
+ $this->assertTrue( $slots->isModifiedSlot( 'other' ) );
+ $this->assertFalse( $slots->isRemovedSlot( 'other' ) );
+ $this->assertSame( $slotB, $slots->getModifiedSlot( 'other' ) );
+ $this->assertSame( [ 'some', 'other' ], $slots->getModifiedRoles() );
+ $this->assertSame( [], $slots->getRemovedRoles() );
+
+ // modify slot A again
+ $slots->modifySlot( $slotA );
+ $this->assertArrayEquals( [ 'some', 'other' ], $slots->getModifiedRoles() );
+
+ // remove modified slot
+ $slots->removeSlot( 'some' );
+ $this->assertSame( [ 'other' ], $slots->getModifiedRoles() );
+ $this->assertSame( [ 'some' ], $slots->getRemovedRoles() );
+
+ // modify removed slot
+ $slots->modifySlot( $slotA );
+ $this->assertArrayEquals( [ 'some', 'other' ], $slots->getModifiedRoles() );
+ $this->assertSame( [], $slots->getRemovedRoles() );
+ }
+
+ public function testRemoveSlot() {
+ $slots = new RevisionSlotsUpdate();
+
+ $slotA = SlotRecord::newUnsaved( 'main', new WikitextContent( 'A' ) );
+ $slots->modifySlot( $slotA );
+
+ $this->assertSame( [ 'main' ], $slots->getModifiedRoles() );
+
+ $slots->removeSlot( 'main' );
+ $slots->removeSlot( 'other' );
+ $this->assertSame( [], $slots->getModifiedRoles() );
+ $this->assertSame( [ 'main', 'other' ], $slots->getRemovedRoles() );
+ $this->assertTrue( $slots->isRemovedSlot( 'main' ) );
+ $this->assertTrue( $slots->isRemovedSlot( 'other' ) );
+ $this->assertFalse( $slots->isModifiedSlot( 'main' ) );
+
+ // removing the same slot again should not trigger an error
+ $slots->removeSlot( 'main' );
+
+ // getting a slot after removing it should fail
+ $this->setExpectedException( RevisionAccessException::class );
+ $slots->getModifiedSlot( 'main' );
+ }
+
+ public function testGetModifiedRoles() {
+ $slots = new RevisionSlotsUpdate( [], [ 'xyz' ] );
+
+ $this->assertSame( [], $slots->getModifiedRoles() );
+
+ $slots->modifyContent( 'main', new WikitextContent( 'A' ) );
+ $slots->modifyContent( 'foo', new WikitextContent( 'Foo' ) );
+ $this->assertSame( [ 'main', 'foo' ], $slots->getModifiedRoles() );
+
+ $slots->removeSlot( 'main' );
+ $this->assertSame( [ 'foo' ], $slots->getModifiedRoles() );
+ }
+
+ public function testGetRemovedRoles() {
+ $slotA = SlotRecord::newUnsaved( 'main', new WikitextContent( 'A' ) );
+ $slots = new RevisionSlotsUpdate( [ $slotA ] );
+
+ $this->assertSame( [], $slots->getRemovedRoles() );
+
+ $slots->removeSlot( 'main', new WikitextContent( 'A' ) );
+ $slots->removeSlot( 'foo', new WikitextContent( 'Foo' ) );
+
+ $this->assertSame( [ 'main', 'foo' ], $slots->getRemovedRoles() );
+
+ $slots->modifyContent( 'main', new WikitextContent( 'A' ) );
+ $this->assertSame( [ 'foo' ], $slots->getRemovedRoles() );
+ }
+
+ public function provideHasSameUpdates() {
+ $fooX = SlotRecord::newUnsaved( 'x', new WikitextContent( 'Foo' ) );
+ $barZ = SlotRecord::newUnsaved( 'z', new WikitextContent( 'Bar' ) );
+
+ $a = new RevisionSlotsUpdate();
+ $a->modifySlot( $fooX );
+ $a->modifySlot( $barZ );
+ $a->removeSlot( 'Q' );
+
+ $a2 = new RevisionSlotsUpdate();
+ $a2->modifySlot( $fooX );
+ $a2->modifySlot( $barZ );
+ $a2->removeSlot( 'Q' );
+
+ $b = new RevisionSlotsUpdate();
+ $b->modifySlot( $barZ );
+ $b->removeSlot( 'Q' );
+
+ $c = new RevisionSlotsUpdate();
+ $c->modifySlot( $fooX );
+ $c->modifySlot( $barZ );
+
+ yield 'same instance' => [ $a, $a, true ];
+ yield 'same udpates' => [ $a, $a2, true ];
+
+ yield 'different modified' => [ $a, $b, false ];
+ yield 'different removed' => [ $a, $c, false ];
+ }
+
+ /**
+ * @dataProvider provideHasSameUpdates
+ */
+ public function testHasSameUpdates( RevisionSlotsUpdate $a, RevisionSlotsUpdate $b, $same ) {
+ $this->assertSame( $same, $a->hasSameUpdates( $b ) );
+ $this->assertSame( $same, $b->hasSameUpdates( $a ) );
+ }
+
+}
SlotRecord::newSaved( $revisionId, $contentId, $contentAddress, $protoSlot );
}
+ public function provideHasSameContent() {
+ $fail = function () {
+ self::fail( 'There should be no need to actually load the content.' );
+ };
+
+ $a100a1 = new SlotRecord(
+ $this->makeRow(
+ [
+ 'model_name' => 'A',
+ 'content_size' => 100,
+ 'content_sha1' => 'hash-a',
+ 'content_address' => 'xxx:a1',
+ ]
+ ),
+ $fail
+ );
+ $a100a1b = new SlotRecord(
+ $this->makeRow(
+ [
+ 'model_name' => 'A',
+ 'content_size' => 100,
+ 'content_sha1' => 'hash-a',
+ 'content_address' => 'xxx:a1',
+ ]
+ ),
+ $fail
+ );
+ $a100null = new SlotRecord(
+ $this->makeRow(
+ [
+ 'model_name' => 'A',
+ 'content_size' => 100,
+ 'content_sha1' => 'hash-a',
+ 'content_address' => null,
+ ]
+ ),
+ $fail
+ );
+ $a100a2 = new SlotRecord(
+ $this->makeRow(
+ [
+ 'model_name' => 'A',
+ 'content_size' => 100,
+ 'content_sha1' => 'hash-a',
+ 'content_address' => 'xxx:a2',
+ ]
+ ),
+ $fail
+ );
+ $b100a1 = new SlotRecord(
+ $this->makeRow(
+ [
+ 'model_name' => 'B',
+ 'content_size' => 100,
+ 'content_sha1' => 'hash-a',
+ 'content_address' => 'xxx:a1',
+ ]
+ ),
+ $fail
+ );
+ $a200a1 = new SlotRecord(
+ $this->makeRow(
+ [
+ 'model_name' => 'A',
+ 'content_size' => 200,
+ 'content_sha1' => 'hash-a',
+ 'content_address' => 'xxx:a2',
+ ]
+ ),
+ $fail
+ );
+ $a100x1 = new SlotRecord(
+ $this->makeRow(
+ [
+ 'model_name' => 'A',
+ 'content_size' => 100,
+ 'content_sha1' => 'hash-x',
+ 'content_address' => 'xxx:x1',
+ ]
+ ),
+ $fail
+ );
+
+ yield 'same instance' => [ $a100a1, $a100a1, true ];
+ yield 'no address' => [ $a100a1, $a100null, true ];
+ yield 'same address' => [ $a100a1, $a100a1b, true ];
+ yield 'different address' => [ $a100a1, $a100a2, true ];
+ yield 'different model' => [ $a100a1, $b100a1, false ];
+ yield 'different size' => [ $a100a1, $a200a1, false ];
+ yield 'different hash' => [ $a100a1, $a100x1, false ];
+ }
+
+ /**
+ * @dataProvider provideHasSameContent
+ */
+ public function testHasSameContent( SlotRecord $a, SlotRecord $b, $sameContent ) {
+ $this->assertSame( $sameContent, $a->hasSameContent( $b ) );
+ $this->assertSame( $sameContent, $b->hasSameContent( $a ) );
+ }
+
}