jenkins-bot has submitted this change and it was merged.

Change subject: Add an initial scoring function
......................................................................


Add an initial scoring function

This function tries to build a score that reflects the quality of a page.

Bug: T106128
Change-Id: I1f61bb66bcf8fff4b02b31ac9fe8525b85fe9ea5
---
M autoload.php
M includes/BuildDocument/SuggestBuilder.php
M includes/BuildDocument/SuggestScoring.php
M includes/Searcher.php
M maintenance/updateSuggesterIndex.php
A tests/unit/SuggestScoringTest.php
6 files changed, 484 insertions(+), 14 deletions(-)

Approvals:
  Cindy-the-browser-test-bot: Looks good to me, but someone else must approve
  EBernhardson: Looks good to me, approved
  jenkins-bot: Verified



diff --git a/autoload.php b/autoload.php
index df5da06..71d764a 100644
--- a/autoload.php
+++ b/autoload.php
@@ -17,6 +17,7 @@
        'CirrusSearch\\BuildDocument\\PageDataBuilder' => __DIR__ . 
'/includes/BuildDocument/PageDataBuilder.php',
        'CirrusSearch\\BuildDocument\\PageTextBuilder' => __DIR__ . 
'/includes/BuildDocument/PageTextBuilder.php',
        'CirrusSearch\\BuildDocument\\ParseBuilder' => __DIR__ . 
'/includes/BuildDocument/Builder.php',
+       'CirrusSearch\\BuildDocument\\QualityScore' => __DIR__ . 
'/includes/BuildDocument/SuggestScoring.php',
        'CirrusSearch\\BuildDocument\\RedirectsAndIncomingLinks' => __DIR__ . 
'/includes/BuildDocument/RedirectsAndIncomingLinks.php',
        'CirrusSearch\\BuildDocument\\SuggestBuilder' => __DIR__ . 
'/includes/BuildDocument/SuggestBuilder.php',
        'CirrusSearch\\BuildDocument\\SuggestScoringMethod' => __DIR__ . 
'/includes/BuildDocument/SuggestScoring.php',
diff --git a/includes/BuildDocument/SuggestBuilder.php 
b/includes/BuildDocument/SuggestBuilder.php
index 8ad11ea..4188095 100644
--- a/includes/BuildDocument/SuggestBuilder.php
+++ b/includes/BuildDocument/SuggestBuilder.php
@@ -1,7 +1,6 @@
 <?php
 
 namespace CirrusSearch\BuildDocument;
-use SuggestScoringMethod;
 
 /**
  * Build a doc ready for the titlesuggest index.
@@ -22,24 +21,27 @@
  * http://www.gnu.org/copyleft/gpl.html
  */
 
+/**
+ * Builder used to create suggester docs
+ */
 class SuggestBuilder {
        const MAX_INPUT_LENGTH = 50;
 
        /**
-        * $scoringMethod the scoring function
+        * @var SuggestScoringMethod the scoring function
         */
        private $scoringMethod;
 
        /**
-        * @param SuggestScoringMethod
+        * @param SuggestScoringMethod $scoringMethod the scoring function to 
use
         */
-       public function __construct( $scoringMethod ) {
+       public function __construct( SuggestScoringMethod $scoringMethod ) {
                $this->scoringMethod = $scoringMethod;
        }
 
        /**
         * @param int $id the page id
-        * @param $inputDoc the page data
+        * @param array $inputDoc the page data
         * @return array a set of suggest documents
         */
        public function build( $id, $inputDoc ) {
@@ -75,11 +77,15 @@
                                'context' => $location
                        );
                }
-
                return array( $doc );
        }
 
-       public function buildInputs( $input ) {
+       /**
+        * @param array $input Document to build inputs for
+        * @return array list of prepared suggestions that should
+        *  resolve to the document.
+        */
+       public function buildInputs( array $input ) {
                $inputs = array( $this->prepareInput( $input['title'] ) );
                foreach ( $input['redirect'] as $redir ) {
                        $inputs[] = $this->prepareInput( $redir['title'] );
@@ -87,6 +93,11 @@
                return $inputs;
        }
 
+       /**
+        * @param string $input A page title
+        * @return string A page title short enough to not cause indexing
+        *  issues.
+        */
        public function prepareInput( $input ) {
                if ( mb_strlen( $input ) > self::MAX_INPUT_LENGTH ) {
                        $input = mb_substr( $input, 0, self::MAX_INPUT_LENGTH );
diff --git a/includes/BuildDocument/SuggestScoring.php 
b/includes/BuildDocument/SuggestScoring.php
index af7068c..b025b72 100644
--- a/includes/BuildDocument/SuggestScoring.php
+++ b/includes/BuildDocument/SuggestScoring.php
@@ -2,6 +2,8 @@
 
 namespace CirrusSearch\BuildDocument;
 
+use CirrusSearch\Searcher;
+
 /**
  * Scoring methods used by the completion suggester
  *
@@ -23,16 +25,17 @@
  * http://www.gnu.org/copyleft/gpl.html
  */
 
-
 class SuggestScoringMethodFactory {
        /**
         * @param $scoringMethods string the name of the scoring method
         * @return SuggestScoringMethod
         */
-       public static function getScoringMethod( $scoringMethod ) {
+       public static function getScoringMethod( $scoringMethod, $maxDocs ) {
                switch( $scoringMethod ) {
                case 'incomingLinks':
-                       return new IncomingsLinksScoringMethod();
+                       return new IncomingsLinksScoringMethod( $maxDocs );
+               case 'quality':
+                       return new QualityScore( $maxDocs );
                }
                throw new \Exception( 'Unknown scoring method ' . 
$scoringMethod );
        }
@@ -51,7 +54,174 @@
  * Very simple scoring method based on incoming links
  */
 class IncomingsLinksScoringMethod implements SuggestScoringMethod {
+       /**
+        * Constructor
+        * @param integer $maxDocs the number of docs in the index
+        */
+       public function __construct( $maxDocs ) {
+               // This scoring function is very simple and we
+               // don't need to normalize
+       }
+
+       /**
+        * {@inheritDoc}
+        */
        public function score( $doc ) {
-               return $doc['incoming_links'];
+               return isset( $doc['incoming_links'] ) ? $doc['incoming_links'] 
: 0;
+       }
+}
+
+/**
+ * Score that tries to reflect the quality of a page
+ */
+class QualityScore implements SuggestScoringMethod {
+       // TODO: move these constants into a cirrus profile
+       const INCOMING_LINKS_MAX_DOCS_FACTOR = 0.1;
+
+       const EXTERNAL_LINKS_NORM = 1000;
+       const PAGE_SIZE_NORM = 300000;
+       const HEADING_NORM = 50;
+       const REDIRECT_NORM = 100;
+
+       const INCOMING_LINKS_WEIGHT = 0.6;
+       const EXTERNAL_LINKS_WEIGHT = 0.3;
+       const PAGE_SIZE_WEIGHT = 0.1;
+       const HEADING_WEIGHT = 0.2;
+       const REDIRECT_WEIGHT = 0.1;
+
+       // The final score will be in the range [0, SCORE_RANGE]
+       const SCORE_RANGE = 100000;
+
+       /**
+        * Template boosts configured by the mediawiki admin.
+        * @var array of key values, key is the template and value is a float
+        */
+       private $boostTemplates;
+
+       /**
+        * @var integer the number of docs in the index
+        */
+       private $maxDocs;
+
+       /**
+        * @var integer normalisation factor for incoming links
+        */
+       private $incomingLinksNorm;
+
+       /**
+        * @param integer $maxDocs the number of docs in the index
+        * @param array of key values, key is the template name, value the 
boost factor.
+        *        Defaults to Searcher::getDefaultBoostTemplates()
+        */
+       public function __construct( $maxDocs, $boostTemplates = null ) {
+               $this->maxDocs = $maxDocs;
+               $this->boostTemplates = $boostTemplates ?: 
Searcher::getDefaultBoostTemplates();
+               // We normalize incoming links according to the size of the 
index
+               $this->incomingLinksNorm = (int) ($maxDocs * 
self::INCOMING_LINKS_MAX_DOCS_FACTOR);
+               if ( $this->incomingLinksNorm < 1 ) {
+                       // it's a very small wiki let's force the norm to 1
+                       $this->incomingLinksNorm = 1;
+               }
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       public function score( $doc ) {
+               $incLinks = $this->scoreNormL2( isset( $doc['incoming_links'] ) 
? $doc['incoming_links'] : 0, $this->incomingLinksNorm );
+               $extLinks = $this->scoreNormL2( isset( $doc['external_link'] ) 
? count( $doc['external_link'] ) : 0, self::EXTERNAL_LINKS_NORM );
+               $pageSize = $this->scoreNormL2( isset( $doc['text_bytes'] ) ? 
$doc['text_bytes'] : 0, self::PAGE_SIZE_NORM );
+               $headings = $this->scoreNorm( isset( $doc['heading'] ) ? count( 
$doc['heading'] ) : 0, self::HEADING_NORM );
+               $redirects = $this->scoreNorm( isset( $doc['redirect'] ) ? 
count( $doc['redirect'] ) : 0, self::REDIRECT_NORM );
+
+               $score = $incLinks * self::INCOMING_LINKS_WEIGHT;
+
+               $score += $extLinks * self::EXTERNAL_LINKS_WEIGHT;
+               $score += $pageSize * self::PAGE_SIZE_WEIGHT;
+               $score += $headings * self::HEADING_WEIGHT;
+               $score += $redirects * self::REDIRECT_WEIGHT;
+
+               // We have a standardized composite score between 0 and 1
+               $score /= self::INCOMING_LINKS_WEIGHT + 
self::EXTERNAL_LINKS_WEIGHT + self::PAGE_SIZE_WEIGHT + self::HEADING_WEIGHT + 
self::REDIRECT_WEIGHT;
+
+               $score = $this->boostTemplates( $doc, $score );
+
+               return intval( $score * self::SCORE_RANGE );
+       }
+
+       /**
+        * log2( ( value / norm ) + 1 ) => [0-1]
+        *
+        * @param float $value
+        * @param float $norm
+        * @return float between 0 and 1
+        */
+       public function scoreNormL2( $value, $norm ) {
+               return log( $value > $norm ? 2 : ( $value / $norm ) + 1, 2 );
+       }
+
+       /**
+        * value / norm => [0-1]
+        *
+        * @param float $value
+        * @param float $norm
+        * @return float between 0 and 1
+        */
+       public function scoreNorm( $value, $norm ) {
+               return $value > $norm ? 1 : $value / $norm;
+       }
+
+       /**
+        * Modify an existing score based on templates contained
+        * by the document.
+        *
+        * @param array $doc Document score is generated for
+        * @param float $score Current score between 0 and 1
+        * @return float Score after boosting templates
+        */
+       public function boostTemplates( $doc, $score ) {
+               if ( !isset( $doc['template'] ) ) {
+                       return $score;
+               }
+
+               if ( $this->boostTemplates ) {
+                       $boost = 1;
+                       // compute the global boost
+                       foreach ( $this->boostTemplates as $k => $v ) {
+                               if ( in_array( $k, $doc['template'] ) ) {
+                                       $boost *= $v;
+                               }
+                       }
+                       if ( $boost != 1 ) {
+                               return $this->boost( $score, $boost );
+                       }
+               }
+               return $score;
+       }
+
+       /**
+        * Boost the score :
+        *   boost value lower than 1 will decrease the score
+        *   boost value set to 1 will keep the score unchanged
+        *   boost value greater than 1 will increase the score
+        *
+        * score = 0.5, boost = 0.5 result is 0.375
+        * score = 0.1, boost = 2 result is 0.325
+        *
+        * @param float $score
+        * @param float $boost
+        * @return float adjusted score
+        */
+       public function boost( $score, $boost ) {
+               if ( $boost == 1 ) {
+                       return $score;
+               }
+
+               $boost = $boost > 1 ? 1 - ( 1 / $boost ) : - ( 1 - $boost );
+               if ( $boost > 0 ) {
+                       return $score + ( ( ( 1 - $score ) / 2 ) * $boost );
+               } else {
+                       return $score + ( ( $score / 2 ) * $boost );
+               }
        }
 }
diff --git a/includes/Searcher.php b/includes/Searcher.php
index 341c804..e3f68e3 100644
--- a/includes/Searcher.php
+++ b/includes/Searcher.php
@@ -1476,7 +1476,7 @@
                $query->setRewrite( 'top_terms_boost_1024' );
 
                if ( isset( $wgCirrusSearchQueryStringMaxDeterminizedStates ) ) 
{
-                       # Requires ES 1.4+
+                       // Requires ES 1.4+
                        $query->setParam( 'max_determinized_states', 
$wgCirrusSearchQueryStringMaxDeterminizedStates );
                }
 
@@ -1817,7 +1817,7 @@
        /**
         * @return float[]
         */
-       private static function getDefaultBoostTemplates() {
+       public static function getDefaultBoostTemplates() {
                static $defaultBoostTemplates = null;
                if ( $defaultBoostTemplates === null ) {
                        $source = wfMessage( 'cirrussearch-boost-templates' 
)->inContentLanguage();
diff --git a/maintenance/updateSuggesterIndex.php 
b/maintenance/updateSuggesterIndex.php
index 79225b2..4df6f0d 100644
--- a/maintenance/updateSuggesterIndex.php
+++ b/maintenance/updateSuggesterIndex.php
@@ -51,6 +51,11 @@
        private $indexIdentifier;
 
        /**
+        * @var SuggestScoringMethod the score function to use.
+        */
+       private $scoreMethod;
+
+       /**
         * @var old suggester index that will be deleted at the end of the 
process
         */
        private $oldIndex;
@@ -89,6 +94,8 @@
                        'of moving a shard this can time out.  This will retry 
the attempt after some backoff ' .
                        'rather than failing the whole reindex process.  
Defaults to 5.', false, true );
                $this->addOption( 'optimize', 'Optimize the index to 1 segment. 
Defaults to false.', false, false );
+               $this->addOption( 'scoringMethod', 'The scoring method to use 
when computing suggestion weights. ' .
+                       'Detauls to quality.', false, true );
        }
 
        public function execute() {
@@ -195,7 +202,6 @@
                        'size' => $this->indexChunkSize
                );
 
-               $builder = new SuggestBuilder( 
SuggestScoringMethodFactory::getScoringMethod( 'incomingLinks' ) );
 
                // TODO: only content index for now ( we'll have to check how 
it works with commons )
                $sourceIndex = Connection::getIndex( $this->indexBaseName, 
Connection::CONTENT_INDEX_TYPE );
@@ -204,6 +210,10 @@
                $totalDocsInIndex = $totalDocsInIndex['hits']['total'];
                $totalDocsToDump = $totalDocsInIndex;
 
+               $scoreMethodName = $this->getOption( 'scoringMethod', 'quality' 
);
+               $this->scoreMethod = 
SuggestScoringMethodFactory::getScoringMethod( $scoreMethodName, 
$totalDocsInIndex );
+               $builder = new SuggestBuilder( $this->scoreMethod );
+
                $docsDumped = 0;
                $this->output( "Indexing $totalDocsToDump documents 
($totalDocsInIndex in the index)\n" );
                $self = $this;
diff --git a/tests/unit/SuggestScoringTest.php 
b/tests/unit/SuggestScoringTest.php
new file mode 100644
index 0000000..c2bf342
--- /dev/null
+++ b/tests/unit/SuggestScoringTest.php
@@ -0,0 +1,278 @@
+<?php
+
+namespace CirrusSearch;
+
+use CirrusSearch\BuildDocument\QualityScore;
+
+/**
+ * test suggest scoring functions.
+ *
+ * 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
+ */
+class SuggestScoringTest extends \MediaWikiTestCase {
+       public function testQualityScoreNormFunctions() {
+               $qs = new QualityScore( 100000 );
+               for( $i = 0; $i < 1000; $i++ ) {
+                       $value = rand( 0, 1000000 );
+                       $norm = rand( 1, 1000000 );
+                       $score = $qs->scoreNorm( $value, $norm );
+                       $this->assertLessThanOrEqual( 1, $score, "scoreNorm 
cannot produce a score greater than 1" );
+                       $this->assertGreaterThanOrEqual( 0, $score, "scoreNorm 
cannot produce a score lower than 0" );
+
+                       $score = $qs->scoreNormL2( $value, $norm );
+                       $this->assertLessThanOrEqual( 1, $score, "scoreNormL2 
cannot produce a score greater than 1" );
+                       $this->assertGreaterThanOrEqual( 0, $score, 
"scoreNormL2 cannot produce a score lower than 0" );
+               }
+
+               # Edges
+               $score = $qs->scoreNorm( 1, 1 );
+               $this->assertLessThanOrEqual( 1, $score, "scoreNorm cannot 
produce a score greater than 1" );
+               $this->assertGreaterThanOrEqual( 0, $score, "scoreNorm cannot 
produce a score lower than 0" );
+
+               $score = $qs->scoreNorm( 0, 1 );
+               $this->assertLessThanOrEqual( 1, $score, "scoreNorm cannot 
produce a score greater than 1" );
+               $this->assertGreaterThanOrEqual( 0, $score, "scoreNorm cannot 
produce a score lower than 0" );
+
+               $score = $qs->scoreNormL2( 1, 1 );
+               $this->assertLessThanOrEqual( 1, $score, "scoreNormL2 cannot 
produce a score greater than 1" );
+               $this->assertGreaterThanOrEqual( 0, $score, "scoreNormL2 cannot 
produce a score lower than 0" );
+
+               $score = $qs->scoreNormL2( 0, 1 );
+               $this->assertLessThanOrEqual( 1, $score, "scoreNormL2 cannot 
produce a score greater than 1" );
+               $this->assertGreaterThanOrEqual( 0, $score, "scoreNormL2 cannot 
produce a score lower than 0" );
+       }
+
+       public function testQualityScoreBoostFunction() {
+               $qs = new QualityScore( 100000 );
+               for( $i = 0; $i < 1000; $i++ ) {
+                       $score = (float) rand() / (float) mt_getrandmax();
+                       $boost = (float) rand( 0, 10000 ) / rand( 1, 10000 );
+                       $res = $qs->boost( $score, $boost );
+                       $this->assertLessThanOrEqual( 1, $score, "boost cannot 
produce a score greater than 1" );
+                       $this->assertGreaterThanOrEqual( 0, $score, "boost 
cannot produce a score lower than 0" );
+                       if ( $boost > 1 ) {
+                               $this->assertGreaterThan( $score, $res, "With a 
boost ($boost) greater than 1 the boosted score must be greater than the 
original." );
+                       } else if ( $boost < 1 ) {
+                               $this->assertLessThan( $score, $res, "With a 
boost ($boost) lesser than 1 the boosted score must be lesser than the 
original." );
+                       } else {
+                               $this->assertEquals( $score, $res, "When boost 
is 1 the score remains unchanged." );
+                       }
+               }
+               for( $i = 1; $i < 1000; $i++ ) {
+                       # The same boost value must keep original score ordering
+                       $score1 = 0.1;
+                       $score2 = 0.5;
+
+                       $boost = $i;
+
+                       $res1 = $qs->boost( $score1, $boost );
+                       $res2 = $qs->boost( $score2, $boost );
+
+                       $this->assertGreaterThan( $res1, $res2, "A boost cannot 
'overboost' a score" );
+                       $res1 = $qs->boost( $score1, (float) 1/(float) $boost );
+                       $res2 = $qs->boost( $score2, (float) 1/(float) $boost );
+                       $this->assertGreaterThan( $res1, $res2, "A boost cannot 
'overboost' a score" );
+               }
+
+               # Edges
+               $res = $qs->boost( 1, 1 );
+               $this->assertEquals( $res, 1, "When boost is 1 the score 
remains unchanged." );
+               $res = $qs->boost( 1, 0 );
+               $this->assertEquals( $res, 0.5, "When boost is 0 the score is 
divided by 2." );
+               $res = $qs->boost( 1,  2^31-1);
+               $this->assertEquals( $res, 1, "When score is 1 and boost is 
very high the score is still 1." );
+               $res = $qs->boost( 0,  0 );
+               $this->assertEquals( $res, 0, "When score is 0 and boost is 0 
the score is still 0." );
+       }
+
+
+       public function testQualityScoreBoostTemplates() {
+               $goodDoc = array(
+                       'template' => array( 'Good' )
+               );
+
+               $badDoc = array(
+                       'template' => array( 'Bad' )
+               );
+
+               $mixedDoc = array(
+                       'template' => array( 'Good', 'Bad' )
+               );
+
+               $neutralDoc = array(
+                       'template' => array( 'Neutral' )
+               );
+
+               $qs = new QualityScore( 100000, array( 'Good' => 2, 'Bad' => 
0.5 ) );
+
+               $score = 0.5;
+               $res = $qs->boostTemplates( $goodDoc, $score );
+               $this->assertGreaterThan( $score, $res, "A good doc gets a 
better score" );
+
+               $res = $qs->boostTemplates( $badDoc, $score );
+               $this->assertLessThan( $score, $res, "A good doc gets a lower 
score" );
+
+               $res = $qs->boostTemplates( $mixedDoc, $score );
+               $this->assertEquals( $score, $res, "A mixed doc gets the same 
score");
+
+               $res = $qs->boostTemplates( $neutralDoc, $score );
+               $this->assertEquals( $res, $score, "A neutral doc gets the same 
score" );
+       }
+
+       public function testQualityScoreRanking() {
+               $maxDocs = 10000000;
+               $qs = new QualityScore( $maxDocs, array( 'Good' => 2, 'Bad' => 
0.5 ) );
+               $veryGoodArticle = array(
+                       'incoming_links' => 120340,
+                       'external_link' => array_fill( 0, 200, null ),
+                       'text_bytes' => '230000',
+                       'heading' => array_fill( 0, 30, null ),
+                       'redirect' => array_fill( 0, 100, null ),
+                       'template' => array( 'Good' )
+               );
+
+               $goodArticle = array(
+                       'incoming_links' => 120340,
+                       'external_link' => array_fill( 0, 200, null ),
+                       'text_bytes' => '230000',
+                       'heading' => array_fill( 0, 30, null ),
+                       'redirect' => array_fill( 0, 100, null ),
+                       'template' => array()
+               );
+
+               $goodButBadArticle = array(
+                       'incoming_links' => 120340,
+                       'external_link' => array_fill( 0, 200, null ),
+                       'text_bytes' => '230000',
+                       'heading' => array_fill( 0, 30, null ),
+                       'redirect' => array_fill( 0, 100, null ),
+                       'template' => array( 'Bad' )
+               );
+
+
+               $this->assertLessThan( $qs->score( $veryGoodArticle ), 
$qs->score( $goodArticle ),
+                       "Same values but a boosted template give a better 
score" );
+               $this->assertLessThan( $qs->score( $goodArticle ), $qs->score( 
$goodButBadArticle ),
+                       "Same values but without a negative boosted template 
give a better score" );
+
+               $page1 = array(
+                       'incoming_links' => $maxDocs * 
QualityScore::INCOMING_LINKS_MAX_DOCS_FACTOR,
+                       'external_link' => array_fill( 0, 200, null ),
+                       'text_bytes' => '230000',
+                       'heading' => array_fill( 0, 30, null ),
+                       'redirect' => array_fill( 0, 100, null ),
+                       'template' => array( 'Good' )
+               );
+
+               $page2 = array(
+                       'incoming_links' => $maxDocs * 
QualityScore::INCOMING_LINKS_MAX_DOCS_FACTOR + 1,
+                       'external_link' => array_fill( 0, 200, null ),
+                       'text_bytes' => '230000',
+                       'heading' => array_fill( 0, 30, null ),
+                       'redirect' => array_fill( 0, 100, null ),
+                       'template' => array( 'Good' )
+               );
+               $this->assertEquals( $qs->score( $page1 ), $qs->score( $page2 ),
+                       "Having more incoming links than the norm give the same 
score" );
+
+               $page1 = array(
+                       'incoming_links' => $maxDocs * 
QualityScore::INCOMING_LINKS_MAX_DOCS_FACTOR,
+                       'external_link' => array_fill( 0, 200, null ),
+                       'text_bytes' => QualityScore::PAGE_SIZE_NORM,
+                       'heading' => array_fill( 0, 30, null ),
+                       'redirect' => array_fill( 0, 100, null ),
+                       'template' => array( 'Good' )
+               );
+
+               $page2 = array(
+                       'incoming_links' => $maxDocs * 
QualityScore::INCOMING_LINKS_MAX_DOCS_FACTOR,
+                       'external_link' => array_fill( 0, 200, null ),
+                       'text_bytes' => QualityScore::PAGE_SIZE_NORM + 1,
+                       'heading' => array_fill( 0, 30, null ),
+                       'redirect' => array_fill( 0, 100, null ),
+                       'template' => array( 'Good' )
+               );
+
+               $this->assertEquals( $qs->score( $page1 ), $qs->score( $page2 ),
+                       "Having more text_bytes than the norm give the same 
score" );
+       }
+
+       public function testQualityScoreWithRandomValues() {
+               $maxDocs = 10000000;
+               $qs = new QualityScore( $maxDocs, array( 'Good' => 2, 'Bad' => 
0.5 ) );
+
+               for( $i = 0; $i < 1000; $i++ ) {
+                       $page = array(
+                               'incoming_links' => rand( 0, 2^31-1 ),
+                               'external_link' => array_fill( 0, rand( 1, 2000 
), null ),
+                               'text_bytes' => rand( 1, 400000 ),
+                               'heading' => array_fill( 0, rand( 1, 1000 ), 
null ),
+                               'redirect' => array_fill( 0, rand( 1, 1000 ), 
null ),
+                               'template' => rand( 0, 1 ) == 1 ? array( 'Good' 
) : array('Bad')
+                       );
+                       $this->assertGreaterThan( 0, $qs->score( $page ), 
"Score is always greater than 0" );
+                       $this->assertLessThan( QualityScore::SCORE_RANGE, 
$qs->score( $page ), "Score is always lower than " . QualityScore::SCORE_RANGE 
);
+               }
+
+               # Edges
+               $page = array(
+                       'incoming_links' => $maxDocs * 
QualityScore::INCOMING_LINKS_MAX_DOCS_FACTOR,
+                       'external_link' => array_fill( 0, 
QualityScore::EXTERNAL_LINKS_NORM, null ),
+                       'text_bytes' => QualityScore::PAGE_SIZE_NORM,
+                       'heading' => array_fill( 0, QualityScore::HEADING_NORM, 
null ),
+                       'redirect' => array_fill( 0, 
QualityScore::REDIRECT_NORM, null ),
+                       'template' => array()
+               );
+               $this->assertEquals( QualityScore::SCORE_RANGE, $qs->score( 
$page ), "Highest score is " . QualityScore::SCORE_RANGE );
+
+               $page = array(
+                       'incoming_links' => 0,
+                       'external_link' => array(),
+                       'text_bytes' => 0,
+                       'heading' => array(),
+                       'redirect' => array(),
+                       'template' => array()
+               );
+               $this->assertEquals( 0, $qs->score( $page ), "Lowest score is 
0" );
+
+               $page = array();
+               $this->assertEquals( 0, $qs->score( $page ), "Score of a broken 
article is 0" );
+
+               # A very small wiki
+               $qs = new QualityScore( 1 );
+               $page = array(
+                       'incoming_links' => 1,
+                       'external_link' => array_fill( 0, 
QualityScore::EXTERNAL_LINKS_NORM, null ),
+                       'text_bytes' => QualityScore::PAGE_SIZE_NORM,
+                       'heading' => array_fill( 0, QualityScore::HEADING_NORM, 
null ),
+                       'redirect' => array_fill( 0, 
QualityScore::REDIRECT_NORM, null ),
+                       'template' => array()
+               );
+               $this->assertEquals( QualityScore::SCORE_RANGE, $qs->score( 
$page ), "With very small wiki the highest score is also " . 
QualityScore::SCORE_RANGE );
+
+               # The scoring function should not fail with 0 page
+               $qs = new QualityScore( 0 );
+               $page = array(
+                       'incoming_links' => 1,
+                       'external_link' => array_fill( 0, 
QualityScore::EXTERNAL_LINKS_NORM, null ),
+                       'text_bytes' => QualityScore::PAGE_SIZE_NORM,
+                       'heading' => array_fill( 0, QualityScore::HEADING_NORM, 
null ),
+                       'redirect' => array_fill( 0, 
QualityScore::REDIRECT_NORM, null ),
+                       'template' => array()
+               );
+               $this->assertEquals( QualityScore::SCORE_RANGE, $qs->score( 
$page ), "With a zero page wiki the highest score is also " . 
QualityScore::SCORE_RANGE );
+       }
+}

-- 
To view, visit https://gerrit.wikimedia.org/r/231023
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: merged
Gerrit-Change-Id: I1f61bb66bcf8fff4b02b31ac9fe8525b85fe9ea5
Gerrit-PatchSet: 10
Gerrit-Project: mediawiki/extensions/CirrusSearch
Gerrit-Branch: master
Gerrit-Owner: DCausse <[email protected]>
Gerrit-Reviewer: Chad <[email protected]>
Gerrit-Reviewer: Cindy-the-browser-test-bot <[email protected]>
Gerrit-Reviewer: DCausse <[email protected]>
Gerrit-Reviewer: EBernhardson <[email protected]>
Gerrit-Reviewer: Manybubbles <[email protected]>
Gerrit-Reviewer: Tjones <[email protected]>
Gerrit-Reviewer: jenkins-bot <>

_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to