Merge "When encountering bad blobs, log the text row id."
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Tue, 19 Jun 2018 12:32:44 +0000 (12:32 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Tue, 19 Jun 2018 12:32:44 +0000 (12:32 +0000)
1  2 
includes/Revision.php
tests/phpunit/includes/RevisionTest.php

diff --combined includes/Revision.php
@@@ -581,11 -581,11 +581,11 @@@ class Revision implements IDBAccessObje
                                return $row['title'];
                        }
  
 -                      $pageId = isset( $row['page'] ) ? $row['page'] : 0;
 -                      $revId = isset( $row['id'] ) ? $row['id'] : 0;
 +                      $pageId = $row['page'] ?? 0;
 +                      $revId = $row['id'] ?? 0;
                } else {
 -                      $pageId = isset( $row->rev_page ) ? $row->rev_page : 0;
 -                      $revId = isset( $row->rev_id ) ? $row->rev_id : 0;
 +                      $pageId = $row->rev_page ?? 0;
 +                      $revId = $row->rev_id ?? 0;
                }
  
                try {
                        ? SqlBlobStore::makeAddressFromTextId( $row->old_id )
                        : null;
  
-               return self::getBlobStore( $wiki )->expandBlob( $text, $flags, $cacheKey );
+               $revisionText = self::getBlobStore( $wiki )->expandBlob( $text, $flags, $cacheKey );
+               if ( $revisionText === false ) {
+                       if ( isset( $row->old_id ) ) {
+                               wfLogWarning( __METHOD__ . ": Bad data in text row {$row->old_id}! " );
+                       } else {
+                               wfLogWarning( __METHOD__ . ": Bad data in text row! " );
+                       }
+                       return false;
+               }
+               return $revisionText;
        }
  
        /**
  
                // Avoid PHP 7.1 warning of passing $this by reference
                $revision = $this;
 -              // TODO: hard-deprecate in 1.32 (or even 1.31?)
 -              Hooks::run( 'RevisionInsertComplete', [ &$revision, null, null ] );
  
                return $rec->getId();
        }
@@@ -17,11 -17,6 +17,11 @@@ use Wikimedia\Rdbms\LoadBalancer
   */
  class RevisionTest extends MediaWikiTestCase {
  
 +      public function setUp() {
 +              parent::setUp();
 +              $this->setMwGlobals( 'wgMultiContentRevisionSchemaMigrationStage', MIGRATION_OLD );
 +      }
 +
        public function provideConstructFromArray() {
                yield 'with text' => [
                        [
                                'content' => new WikitextContent( 'GOAT' ),
                                'text_id' => 'someid',
                        ],
 -                      new MWException( "Text already stored in external store (id someid), " .
 -                              "can't serialize content object" )
 +                      new MWException( 'Text already stored in external store (id someid),' )
                ];
                yield 'with bad content object (class)' => [
                        [ 'content' => new stdClass() ],
 -                      new MWException( 'content field must contain a Content object.' )
 +                      new MWException( 'content field must contain a Content object' )
                ];
                yield 'with bad content object (string)' => [
                        [ 'content' => 'ImAGoat' ],
 -                      new MWException( 'content field must contain a Content object.' )
 +                      new MWException( 'content field must contain a Content object' )
                ];
                yield 'bad row format' => [
                        'imastring, not a row',
                $this->testGetRevisionText( $expected, $rowData );
        }
  
+       public function provideGetRevisionTextWithZlibExtension_badData() {
+               yield 'Generic gzip test' => [
+                       'This is a small goat of revision text.',
+                       [
+                               'old_flags' => 'gzip',
+                               'old_text' => 'DEAD BEEF',
+                       ],
+               ];
+       }
+       /**
+        * @covers Revision::getRevisionText
+        * @dataProvider provideGetRevisionTextWithZlibExtension_badData
+        */
+       public function testGetRevisionWithZlibExtension_badData( $expected, $rowData ) {
+               $this->checkPHPExtension( 'zlib' );
+               Wikimedia\suppressWarnings();
+               $this->assertFalse(
+                       Revision::getRevisionText(
+                               (object)$rowData
+                       )
+               );
+               Wikimedia\suppressWarnings( true );
+       }
        private function getWANObjectCache() {
                return new WANObjectCache( [ 'cache' => new HashBagOStuff() ] );
        }
                        $this->getBlobStore(),
                        $cache,
                        MediaWikiServices::getInstance()->getCommentStore(),
 +                      MediaWikiServices::getInstance()->getContentModelStore(),
 +                      MediaWikiServices::getInstance()->getSlotRoleStore(),
 +                      MIGRATION_OLD,
                        MediaWikiServices::getInstance()->getActorMigration()
                );
                return $blobStore;
        public function testGetRevisionText_external_returnsFalseWhenNotEnoughUrlParts(
                $text
        ) {
+               Wikimedia\suppressWarnings();
                $this->assertFalse(
                        Revision::getRevisionText(
                                (object)[
                                ]
                        )
                );
+               Wikimedia\suppressWarnings( true );
        }
  
        /**
                $revisionStore = $this->getRevisionStore();
                $revisionStore->setContentHandlerUseDB( $globals['wgContentHandlerUseDB'] );
                $this->setService( 'RevisionStore', $revisionStore );
 -              $this->assertEquals(
 -                      $expected,
 -                      Revision::getArchiveQueryInfo()
 +
 +              $queryInfo = Revision::getArchiveQueryInfo();
 +
 +              $this->assertArrayEqualsIgnoringIntKeyOrder(
 +                      $expected['tables'],
 +                      $queryInfo['tables']
                );
 +              $this->assertArrayEqualsIgnoringIntKeyOrder(
 +                      $expected['fields'],
 +                      $queryInfo['fields']
 +              );
 +              $this->assertArrayEqualsIgnoringIntKeyOrder(
 +                      $expected['joins'],
 +                      $queryInfo['joins']
 +              );
 +      }
 +
 +      /**
 +       * Assert that the two arrays passed are equal, ignoring the order of the values that integer
 +       * keys.
 +       *
 +       * Note: Failures of this assertion can be slightly confusing as the arrays are actually
 +       * split into a string key array and an int key array before assertions occur.
 +       *
 +       * @param array $expected
 +       * @param array $actual
 +       */
 +      private function assertArrayEqualsIgnoringIntKeyOrder( array $expected, array $actual ) {
 +              $this->objectAssociativeSort( $expected );
 +              $this->objectAssociativeSort( $actual );
 +
 +              // Separate the int key values from the string key values so that assertion failures are
 +              // easier to understand.
 +              $expectedIntKeyValues = [];
 +              $actualIntKeyValues = [];
 +
 +              // Remove all int keys and re add them at the end after sorting by value
 +              // This will result in all int keys being in the same order with same ints at the end of
 +              // the array
 +              foreach ( $expected as $key => $value ) {
 +                      if ( is_int( $key ) ) {
 +                              unset( $expected[$key] );
 +                              $expectedIntKeyValues[] = $value;
 +                      }
 +              }
 +              foreach ( $actual as $key => $value ) {
 +                      if ( is_int( $key ) ) {
 +                              unset( $actual[$key] );
 +                              $actualIntKeyValues[] = $value;
 +                      }
 +              }
 +
 +              $this->assertArrayEquals( $expected, $actual, false, true );
 +              $this->assertArrayEquals( $expectedIntKeyValues, $actualIntKeyValues, false, true );
        }
  
        public function provideGetQueryInfo() {
                $revisionStore->setContentHandlerUseDB( $globals['wgContentHandlerUseDB'] );
                $this->setService( 'RevisionStore', $revisionStore );
  
 -              $this->assertEquals(
 -                      $expected,
 -                      Revision::getQueryInfo( $options )
 +              $queryInfo = Revision::getQueryInfo( $options );
 +
 +              $this->assertArrayEqualsIgnoringIntKeyOrder(
 +                      $expected['tables'],
 +                      $queryInfo['tables']
 +              );
 +              $this->assertArrayEqualsIgnoringIntKeyOrder(
 +                      $expected['fields'],
 +                      $queryInfo['fields']
 +              );
 +              $this->assertArrayEqualsIgnoringIntKeyOrder(
 +                      $expected['joins'],
 +                      $queryInfo['joins']
                );
        }