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

Change subject: (Bug 51577) SMW\PropertyDisparityDetector (lift some scope 
creep in ParserData)  
......................................................................


(Bug 51577) SMW\PropertyDisparityDetector (lift some scope creep in ParserData) 
 

This change helps to eliminate some scope creep in ParserData
which does too may things at once (has currently a CRAP of 2140).

SMW\PropertyDisparityDetector
Comparison is done synchronously but job generation has been moved
into a backgound dispatcher to shield against timeouts when
getAllPropertySubjects() is executed.

Code coverage: 100%
CRAP: 15

SMW\PropertySubjectsUpdateDispatcherJob
Generates UpdateJobs in order to rebuild property-subject parity

Code coverage: 100%
CRAP: 15

Change-Id: I81a4b206e1548e48475c6318ec4728b3cd8cb924
---
M includes/ParserData.php
A includes/PropertyDisparityDetector.php
M includes/Setup.php
A includes/jobs/PropertySubjectsUpdateDispatcherJob.php
A tests/phpunit/includes/PropertyDisparityDetectorTest.php
A tests/phpunit/includes/jobs/PropertySubjectsUpdateDispatcherJobTest.php
6 files changed, 758 insertions(+), 211 deletions(-)

Approvals:
  Mwjames: Looks good to me, approved
  jenkins-bot: Verified



diff --git a/includes/ParserData.php b/includes/ParserData.php
index a40baf3..be8276c 100644
--- a/includes/ParserData.php
+++ b/includes/ParserData.php
@@ -16,7 +16,6 @@
 use SMWDIBlob;
 use SMWDIBoolean;
 use SMWDITime;
-use SMWUpdateJob;
 
 /**
  * Interface handling semantic data storage to a ParserOutput instance
@@ -509,18 +508,24 @@
        public function updateStore() {
                Profiler::In( __METHOD__, true );
 
+               // Protect against namespace -1 see Bug 50153
+               if ( $this->title->isSpecialPage() ) {
+                       return true;
+               }
+
+               $dispatcherJob = null;
+
+               $namespace = $this->title->getNamespace();
+               $wikiPage  = WikiPage::factory( $this->title );
+               $revision  = $wikiPage->getRevision();
+               $store     = StoreFactory::getStore();
+
                // FIXME get rid of globals and use options array instead while
                // invoking the constructor
                $this->options = array(
                        'smwgDeclarationProperties' => 
$GLOBALS['smwgDeclarationProperties'],
                        'smwgPageSpecialProperties' => 
$GLOBALS['smwgPageSpecialProperties']
                );
-
-               $namespace = $this->title->getNamespace();
-               $wikiPage = WikiPage::factory( $this->title );
-               $revision = $wikiPage->getRevision();
-               $store    = smwfGetStore();
-               $jobs     = array();
 
                // Make sure to have a valid revision (null means delete etc.)
                // Check if semantic data should be processed and displayed for 
a page in
@@ -535,12 +540,11 @@
                        $this->semanticData = new SMWSemanticData( 
$this->getSubject() );
                }
 
-               // Careful: storage access must happen *before* the storage 
update;
+               // Comparison must happen *before* the storage update;
                // even finding uses of a property fails after its type was 
changed.
-               if ( $this->updateJobs && ( $namespace === SMW_NS_PROPERTY ) ) {
-                       $this->getDiffPropertyTypes( $store, $jobs );
-               } else if ( $this->updateJobs && ( $namespace === SMW_NS_TYPE ) 
) {
-                       $this->getDiffConversionFactors( $store, $jobs );
+               if ( $this->updateJobs ) {
+                       $disparityDetector = new PropertyDisparityDetector( 
$store, $this->semanticData, Settings::newFromGlobals() );
+                       $dispatcherJob = 
$disparityDetector->detectDisparity()->getDispatcherJob();
                }
 
                // Actually store semantic data, or at least clear it if needed
@@ -552,8 +556,8 @@
 
                // Job::batchInsert was deprecated in MW 1.21
                // @see JobQueueGroup::singleton()->push( $job );
-               if ( $jobs !== array() ) {
-                       Job::batchInsert( $jobs );
+               if ( $dispatcherJob !== null ) {
+                       Job::batchInsert( $dispatcherJob );
                }
 
                Profiler::Out( __METHOD__, true );
@@ -561,199 +565,4 @@
                return true;
        }
 
-       /**
-        * Helper method to handle diff/change in type property pages
-        *
-        * @note If it is a property, then we need to check if the type or the
-        * allowed values have been changed.
-        *
-        * @since 1.9
-        *
-        * @param SMWStore $store
-        * @param array &$jobs
-        */
-       protected function getDiffPropertyTypes( SMWStore $store, array &$jobs 
) {
-               Profiler::In( __METHOD__, true );
-
-               $updatejobflag = false;
-               $ptype = new SMWDIProperty( SMWDIProperty::TYPE_HAS_TYPE );
-
-               // Get values from the store
-               $oldtype = $store->getPropertyValues(
-                       $this->semanticData->getSubject(),
-                       $ptype
-               );
-
-               // Get values currently hold by the semantic container
-               $newtype = $this->semanticData->getPropertyValues( $ptype );
-
-               // Compare old and new type
-               if ( !$this->equalDatavalues( $oldtype, $newtype ) ) {
-                       $updatejobflag = true;
-               } else {
-                       // Compare values (in case of _PVAL (allowed values) 
for a
-                       // property change must be processed again)
-                       foreach ( $this->options['smwgDeclarationProperties'] 
as $prop ) {
-
-                               $dataItem = new SMWDIProperty( $prop );
-                               $oldValues = $store->getPropertyValues(
-                                       $this->semanticData->getSubject(),
-                                       $dataItem
-                               );
-                               $newValues = 
$this->semanticData->getPropertyValues( $dataItem );
-                               $updatejobflag = !$this->equalDatavalues( 
$oldValues, $newValues );
-                       }
-               }
-
-               // Job generation
-               if ( $updatejobflag ) {
-                       $prop = new SMWDIProperty( $this->title->getDBkey() );
-
-                       // Array of all subjects that have some value for the 
given property
-                       $subjects = $store->getAllPropertySubjects( $prop );
-
-                       // Add jobs
-                       $this->addJobs( $subjects, $jobs );
-
-                       // Hook
-                       wfRunHooks( 'smwUpdatePropertySubjects', array( &$jobs 
) );
-
-                       // Fetch all those that have an error property attached 
and
-                       // re-run it through the job-queue
-                       $subjects = $store->getPropertySubjects(
-                               new SMWDIProperty( SMWDIProperty::TYPE_ERROR ),
-                               $this->semanticData->getSubject()
-                       );
-
-                       // Add jobs
-                       $this->addJobs( $subjects, $jobs );
-               }
-
-               Profiler::Out( __METHOD__, true );
-       }
-
-       /**
-        * Helper method to handle diff/change of conversion related properties
-        *
-        * @note if it is a type we need to check if the conversion factors
-        * have been changed
-        *
-        * @since 1.9
-        *
-        * @param SMWStore $store
-        * @param array &$jo
-        */
-       protected function getDiffConversionFactors( SMWStore $store, array 
&$jobs ) {
-               wfProfileIn( __METHOD__ );
-
-               $updatejobflag = false;
-               $pconv = new SMWDIProperty( SMWDIProperty::TYPE_CONVERSION );
-               $ptype = new SMWDIProperty( SMWDIProperty::TYPE_HAS_TYPE );
-
-               $oldfactors = smwfGetStore()->getPropertyValues(
-                       $this->semanticData->getSubject(),
-                       $pconv
-               );
-               $newfactors = $this->semanticData->getPropertyValues( $pconv );
-
-               // Compare
-               $updatejobflag = !$this->equalDatavalues( $oldfactors, 
$newfactors );
-
-               // Job generation
-               if ( $updatejobflag ) {
-
-                       /// FIXME: this will kill large wikis! Use incremental 
updates!
-                       $dataValue = SMWDataValueFactory::newTypeIdValue( 
'__typ', $title->getDBkey() );
-                       $propertyPages = $store->getPropertySubjects( $ptype, 
$dataValue );
-
-                       foreach ( $propertyPages as $propertyPage ) {
-                               // Add jobs
-                               $this->addJobs( array( $propertyPage ), $jobs );
-
-                               $prop = new SMWDIProperty( 
$propertyPage->getDBkey() );
-                               $subjects = $store->getAllPropertySubjects( 
$prop );
-
-                               // Add jobs
-                               $this->addJobs( $subjects, $jobs );
-
-                               $subjects = $store->getPropertySubjects(
-                                       new SMWDIProperty( 
SMWDIProperty::TYPE_ERROR  ),
-                                       $prop->getWikiPageValue()
-                               );
-
-                               // Add jobs
-                               $this->addJobs( $subjects, $jobs );
-                       }
-               }
-
-               wfProfileOut( __METHOD__ );
-       }
-
-       /**
-        * Helper method to iterate over an array of SMWDIWikiPage and return 
and
-        * array of SMWUpdateJob jobs
-        *
-        * Check whether a job with the same getPrefixedDBkey string (prefixed 
title,
-        * with underscores and any interwiki and namespace prefixes) is already
-        * registered and if so don't insert a new job. This is particular 
important
-        * for pages that include a large amount of subobjects where the same 
Title
-        * and ParserOutput object is used (subobjects are included using the 
same
-        * WikiPage which means the resulting ParserOutput object is the same)
-        *
-        * @since 1.9
-        *
-        * @param SMWDIWikiPage[] $subjects
-        * @param array &$jobs
-        */
-       protected function addJobs( array $subjects, &$jobs ) {
-
-               foreach ( $subjects as $subject ) {
-                       $duplicate = false;
-                       $subjectTitle = $subject->getTitle();
-
-                       if ( $subjectTitle instanceof Title ) {
-
-                               // Avoid duplicate jobs for the same title 
object
-                               foreach ( $jobs as $job ) {
-                                       if ( 
$job->getTitle()->getPrefixedDBkey() === $subjectTitle->getPrefixedDBkey() ){
-                                               $duplicate = true;
-                                               break;
-                                       }
-                               }
-                               if ( !$duplicate ) {
-                                       $jobs[] = new SMWUpdateJob( 
$subjectTitle );
-                               }
-                       }
-               }
-       }
-
-       /**
-        * Helper function that compares two arrays of data values to check 
whether
-        * they contain the same content. Returns true if the two arrays 
contain the
-        * same data values (irrespective of their order), false otherwise.
-        *
-        * @since  1.9
-        */
-       protected function equalDatavalues( $oldDataValue, $newDataValue ) {
-               // The hashes of all values of both arrays are taken, then 
sorted
-               // and finally concatenated, thus creating one long hash out of 
each
-               // of the data value arrays. These are compared.
-               $values = array();
-               foreach ( $oldDataValue as $v ) {
-                       $values[] = $v->getHash();
-               }
-
-               sort( $values );
-               $oldDataValueHash = implode( '___', $values );
-
-               $values = array();
-               foreach ( $newDataValue as $v ) {
-                       $values[] = $v->getHash();
-               }
-
-               sort( $values );
-               $newDataValueHash = implode( '___', $values );
-
-               return ( $oldDataValueHash == $newDataValueHash );
-       }
 }
diff --git a/includes/PropertyDisparityDetector.php 
b/includes/PropertyDisparityDetector.php
new file mode 100644
index 0000000..bed5dc9
--- /dev/null
+++ b/includes/PropertyDisparityDetector.php
@@ -0,0 +1,223 @@
+<?php
+
+namespace SMW;
+
+use Title;
+use Job;
+
+/**
+ * Class that detects a disparity between the property object and its store 
data
+ *
+ * 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
+ *
+ * @license GNU GPL v2+
+ * @since   1.9
+ *
+ * @author mwjames
+ * @author Markus Krötzsch
+ */
+
+/**
+ * Class that detects a disparity between the property object and its store 
data
+ *
+ * @ingroup SMW
+ */
+class PropertyDisparityDetector {
+
+       /** @var Store */
+       protected $store;
+
+       /** @var SemanticData */
+       protected $semanticData;
+
+       /** @var Settings */
+       protected $settings;
+
+       /** @var Job */
+       protected $dispatcherJob = null;
+
+       /**
+        * @since 1.9
+        *
+        * @param Store $store
+        * @param SemanticData $semanticData
+        * @param Settings $settings
+        */
+       public function __construct( Store $store, SemanticData $semanticData, 
Settings $settings ) {
+               $this->store = $store;
+               $this->semanticData = $semanticData;
+               $this->settings = $settings;
+       }
+
+       /**
+        * Returns update jobs as a result of the data comparison
+        *
+        * @since 1.9
+        *
+        * @return PropertyDisparityDispatcherJob|null
+        */
+       public function getDispatcherJob() {
+               return $this->dispatcherJob;
+       }
+
+       /**
+        * Returns if a data disparity exists
+        *
+        * @since 1.9
+        *
+        * @return boolean
+        */
+       public function hasDisparity() {
+               return $this->getDispatcherJob() !== null;
+       }
+
+       /**
+        * Compare and compute the difference between invoked semantic data
+        * and the current store data
+        *
+        * @since 1.9
+        *
+        * @return PropertyDisparityFinder
+        */
+       public function detectDisparity() {
+               Profiler::In( __METHOD__, true );
+
+               if ( $this->semanticData->getSubject()->getNamespace() === 
SMW_NS_PROPERTY ) {
+                       $this->comparePropertyTypes();
+                       $this->compareConversionFactors();
+               }
+
+               Profiler::Out( __METHOD__, true );
+               return $this;
+       }
+
+       /**
+        * Compare and find change related to the property type
+        *
+        * @since 1.9
+        */
+       protected function comparePropertyTypes() {
+               Profiler::In( __METHOD__, true );
+
+               $update = false;
+               $ptype  = new DIProperty( DIProperty::TYPE_HAS_TYPE );
+
+               // Get values from the store
+               $oldtype = $this->store->getPropertyValues(
+                       $this->semanticData->getSubject(),
+                       $ptype
+               );
+
+               // Get values currently hold by the semantic container
+               $newtype = $this->semanticData->getPropertyValues( $ptype );
+
+               // Compare old and new type
+               if ( !$this->isEqual( $oldtype, $newtype ) ) {
+                       $update = true;
+               } else {
+
+                       // Compare values (in case of _PVAL (allowed values) 
for a
+                       // property change must be processed again)
+                       foreach ( $this->settings->get( 
'smwgDeclarationProperties' ) as $prop ) {
+                               $dataItem = new DIProperty( $prop );
+                               $oldValues = $this->store->getPropertyValues(
+                                       $this->semanticData->getSubject(),
+                                       $dataItem
+                               );
+
+                               $newValues = 
$this->semanticData->getPropertyValues( $dataItem );
+                               $update = $update || !$this->isEqual( 
$oldValues, $newValues );
+                       }
+               }
+
+               $this->addDispatchJob( $update );
+
+               Profiler::Out( __METHOD__, true );
+       }
+
+       /**
+        * Compare and find change related to conversion factor
+        *
+        * @since 1.9
+        */
+       protected function compareConversionFactors() {
+               Profiler::In( __METHOD__, true );
+
+               $pconversion  = new DIProperty( DIProperty::TYPE_CONVERSION );
+
+               $oldfactors = $this->store->getPropertyValues(
+                       $this->semanticData->getSubject(),
+                       $pconversion
+               );
+               $newfactors = $this->semanticData->getPropertyValues( 
$pconversion );
+
+               $this->addDispatchJob( !$this->isEqual( $oldfactors, 
$newfactors ) );
+
+               Profiler::Out( __METHOD__, true );
+       }
+
+       /**
+        * Adds a Dispatcher job to resolve a disparity asynchronously
+        *
+        * @since 1.9
+        *
+        * @param boolean $addJob
+        */
+       protected function addDispatchJob( $addJob = true ) {
+               if ( $addJob && $this->dispatcherJob === null ) {
+                       $this->dispatcherJob[] = new 
PropertySubjectsUpdateDispatcherJob(
+                               $this->semanticData->getSubject()->getTitle(),
+                               array( 'store' => get_class( $this->store ) )
+                       );
+               }
+       }
+
+       /**
+        * Helper function that compares two arrays of data values to check 
whether
+        * they contain the same content. Returns true if the two arrays 
contain the
+        * same data values (irrespective of their order), false otherwise.
+        *
+        * @since 1.9
+        *
+        * @param $oldDataValue
+        * @param $newDataValue
+        */
+       protected function isEqual( $oldDataValue, $newDataValue ) {
+
+               // The hashes of all values of both arrays are taken, then 
sorted
+               // and finally concatenated, thus creating one long hash out of 
each
+               // of the data value arrays. These are compared.
+               $values = array();
+               foreach ( $oldDataValue as $v ) {
+                       $values[] = $v->getHash();
+               }
+
+               sort( $values );
+               $oldDataValueHash = implode( '___', $values );
+
+               $values = array();
+               foreach ( $newDataValue as $v ) {
+                       $values[] = $v->getHash();
+               }
+
+               sort( $values );
+               $newDataValueHash = implode( '___', $values );
+
+               return ( $oldDataValueHash == $newDataValueHash );
+       }
+}
diff --git a/includes/Setup.php b/includes/Setup.php
index fe93e10..a14f600 100644
--- a/includes/Setup.php
+++ b/includes/Setup.php
@@ -141,6 +141,7 @@
        $wgAutoloadClasses['SMWParseData']              = $incDir . 
'SMW_ParseData.php';
        $wgAutoloadClasses['SMW\IParserData']           = $incDir . 
'ParserData.php';
        $wgAutoloadClasses['SMW\ParserData']            = $incDir . 
'ParserData.php';
+       $wgAutoloadClasses['SMW\PropertyDisparityDetector']  = $incDir . 
'PropertyDisparityDetector.php';
 
        $wgAutoloadClasses['SMW\Subobject']             = $incDir . 
'Subobject.php';
        $wgAutoloadClasses['SMW\RecurringEvents']       = $incDir . 
'RecurringEvents.php';
@@ -173,7 +174,7 @@
        $wgAutoloadClasses['SMW\ApiRequestParameterFormatter'] = $incDir . 
'formatters/ApiRequestParameterFormatter.php';
 
        // Exceptions
-       $wgAutoloadClasses['SMW\InvalidStoreException']       = $incDir . 
'/exceptions/InvalidStoreException.php';
+       $wgAutoloadClasses['SMW\InvalidStoreException']        = $incDir . 
'/exceptions/InvalidStoreException.php';
        $wgAutoloadClasses['SMW\InvalidSemanticDataException'] = $incDir . 
'/exceptions/InvalidSemanticDataException.php';
        $wgAutoloadClasses['SMW\InvalidNamespaceException']    = $incDir . 
'/exceptions/InvalidNamespaceException.php';
        $wgAutoloadClasses['SMW\InvalidPropertyException']     = $incDir . 
'/exceptions/InvalidPropertyException.php';
@@ -411,10 +412,13 @@
        // Jobs
        $wgJobClasses['SMWUpdateJob']       = 'SMWUpdateJob';
        $wgAutoloadClasses['SMWUpdateJob']  = $smwgIP . 
'includes/jobs/SMW_UpdateJob.php';
-       $wgAutoloadClasses['SMW\UpdateJob'] = $smwgIP . 
'includes/jobs/SMW_UpdateJob.php';
+       $wgAutoloadClasses['SMW\UpdateJob']  = $smwgIP . 
'includes/jobs/SMW_UpdateJob.php'; // 1.9
        $wgJobClasses['SMWRefreshJob']      = 'SMWRefreshJob';
        $wgAutoloadClasses['SMWRefreshJob'] = $smwgIP . 
'includes/jobs/SMW_RefreshJob.php';
 
+       $wgJobClasses['SMW\PropertySubjectsUpdateDispatcherJob']      = 
'SMW\PropertySubjectsUpdateDispatcherJob';
+       $wgAutoloadClasses['SMW\PropertySubjectsUpdateDispatcherJob'] = $smwgIP 
. 'includes/jobs/PropertySubjectsUpdateDispatcherJob.php';
+
        // Store migration job class
        $wgJobClasses['SMWMigrationJob']          = 'SMW\MigrationJob';
        $wgAutoloadClasses['SMW\MigrationJob']    = $smwgIP . 
'includes/jobs/MigrationJob.php';
diff --git a/includes/jobs/PropertySubjectsUpdateDispatcherJob.php 
b/includes/jobs/PropertySubjectsUpdateDispatcherJob.php
new file mode 100644
index 0000000..e0d4142
--- /dev/null
+++ b/includes/jobs/PropertySubjectsUpdateDispatcherJob.php
@@ -0,0 +1,191 @@
+<?php
+
+namespace SMW;
+
+use Title;
+use Job;
+
+/**
+ * Background dispatch to generate necessary UpdateJob's in order
+ * to restore the data parity between a property in its attached subjects
+ *
+ * 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
+ *
+ * @license GNU GPL v2+
+ * @since   1.9
+ *
+ * @author mwjames
+ */
+
+/**
+ * Background dispatch to generate necessary UpdateJob's in order
+ * to restore the data parity between a property in its attached subjects
+ *
+ * @ingroup Job
+ * @ingroup Dispatcher
+ */
+class PropertySubjectsUpdateDispatcherJob extends Job {
+
+       /** $var Store */
+       protected $store = null;
+
+       /** $var Job */
+       protected $jobs = array();
+
+       /** $var boolean */
+       protected $enabled = true;
+
+       /**
+        * @since  1.9
+        *
+        * @param Title $title
+        * @param array $params job parameters
+        * @param integer $id job id
+        */
+       public function __construct( Title $title, $params = array(), $id = 0 ) 
{
+               parent::__construct( 'SMW\PropertySubjectsUpdateDispatcherJob', 
$title, $params, $id );
+               $this->store = StoreFactory::getStore( isset( $params['store'] 
) ? $params['store'] : null );
+       }
+
+       /**
+        * Sets Store object
+        *
+        * @since 1.9
+        *
+        * @param Store $store
+        */
+       public function setStore( Store $store ) {
+               $this->store = $store;
+       }
+
+       /**
+        * Disables ability to insert jobs into the
+        * JobQueue
+        *
+        * @since 1.9
+        *
+        * @return PropertySubjectsUpdateDispatcherJob
+        */
+       public function disable() {
+               $this->enabled = false;
+               return $this;
+       }
+
+       /**
+        * @see Job::run
+        *
+        * @since  1.9
+        *
+        * @return boolean
+        */
+       public function run() {
+               Profiler::In( __METHOD__, true );
+
+               if ( $this->title->getNamespace() === SMW_NS_PROPERTY ) {
+                       $this->getSubjects( DIProperty::newFromUserLabel( 
$this->title->getText() ) )->push();
+               }
+
+               Profiler::Out( __METHOD__, true );
+               return true;
+       }
+
+       /**
+        * Insert batch jobs
+        *
+        * @note Job::batchInsert was deprecated in MW 1.21
+        * JobQueueGroup::singleton()->push( $job );
+        *
+        * @since 1.9
+        */
+       public function push() {
+               $this->enabled ? Job::batchInsert( $this->jobs ) : null;
+       }
+
+       /**
+        * Generates list of involved subjects
+        *
+        * @since 1.9
+        *
+        * @param DIProperty $property
+        */
+       protected function getSubjects( DIProperty $property ) {
+               Profiler::In( __METHOD__, true );
+
+               // Array of all subjects that have some value for the given 
property
+               $subjects = $this->store->getAllPropertySubjects( $property );
+
+               $this->addJobs( $subjects );
+
+               // Hook deprecated with 1.9
+               wfRunHooks( 'smwUpdatePropertySubjects', array( &$this->jobs ) 
);
+
+               // Hook since 1.9
+               wfRunHooks( 'SMW::Data::UpdatePropertySubjects', array( 
&$this->jobs ) );
+
+               // Fetch all those that have an error property attached and
+               // re-run it through the job-queue
+               $subjects = $this->store->getPropertySubjects(
+                       new DIProperty( DIProperty::TYPE_ERROR ),
+                       DIWikiPage::newFromTitle( $this->title )
+               );
+
+               $this->addJobs( $subjects );
+
+               Profiler::Out( __METHOD__, true );
+               return $this;
+       }
+
+       /**
+        * Helper method to iterate over an array of DIWikiPage and return and
+        * array of UpdateJobs
+        *
+        * Check whether a job with the same getPrefixedDBkey string (prefixed 
title,
+        * with underscores and any interwiki and namespace prefixes) is already
+        * registered and if so don't insert a new job. This is particular 
important
+        * for pages that include a large amount of subobjects where the same 
Title
+        * and ParserOutput object is used (subobjects are included using the 
same
+        * WikiPage which means the resulting ParserOutput object is the same)
+        *
+        * @since 1.9
+        *
+        * @param DIWikiPage[] $subjects
+        */
+       protected function addJobs( array $subjects = array() ) {
+
+               foreach ( $subjects as $subject ) {
+
+                       $duplicate = false;
+                       $title     = $subject->getTitle();
+
+                       if ( $title instanceof Title ) {
+
+                               // Avoid duplicates by comparing the title DBkey
+                               foreach ( $this->jobs as $job ) {
+                                       if ( 
$job->getTitle()->getPrefixedDBkey() === $title->getPrefixedDBkey() ){
+                                               $duplicate = true;
+                                               break;
+                                       }
+                               }
+
+                               if ( !$duplicate ) {
+                                       $this->jobs[] = new UpdateJob( $title );
+                               }
+                       }
+               }
+       }
+}
diff --git a/tests/phpunit/includes/PropertyDisparityDetectorTest.php 
b/tests/phpunit/includes/PropertyDisparityDetectorTest.php
new file mode 100644
index 0000000..5bf0b4e
--- /dev/null
+++ b/tests/phpunit/includes/PropertyDisparityDetectorTest.php
@@ -0,0 +1,162 @@
+<?php
+
+namespace SMW\Test;
+
+use SMW\PropertyDisparityDetector;
+use SMW\DIProperty;
+
+use Title;
+
+/**
+ * Tests for the PropertyDisparityDetector class
+ *
+ * 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
+ *
+ * @license GNU GPL v2+
+ * @since   1.9
+ *
+ * @author mwjames
+ */
+
+/**
+ * @covers \SMW\PropertyDisparityDetector
+ *
+ * @ingroup Test
+ *
+ * @group SMW
+ * @group SMWExtension
+ */
+class PropertyDisparityDetectorTest extends SemanticMediaWikiTestCase {
+
+       /** @var DIWikiPage[] */
+       protected $storeValues;
+
+       /**
+        * Returns the name of the class to be tested
+        *
+        * @return string|false
+        */
+       public function getClass() {
+               return '\SMW\PropertyDisparityDetector';
+       }
+
+       /**
+        * Helper method that returns a PropertyDisparityDetector object
+        *
+        * @since 1.9
+        *
+        * @param $store
+        * @param $data
+        * @param $setting
+        *
+        * @return PropertyDisparityDetector
+        */
+       private function getInstance( $store = array(), $data = array(), 
$setting = null ) {
+
+               $mockStore = $this->newMockObject( $store )->getMockStore();
+               $mockData  = $this->newMockObject( $data 
)->getMockSemanticData();
+               $settings  = $this->getSettings( array(
+                       'smwgDeclarationProperties' => $setting === null ? 
array( '_PVAL' ): $setting
+               ) );
+
+               return new PropertyDisparityDetector( $mockStore, $mockData, 
$settings );
+       }
+
+       /**
+        * @test PropertyDisparityDetector::__construct
+        *
+        * @since 1.9
+        */
+       public function testConstructor() {
+               $this->assertInstanceOf( $this->getClass(), 
$this->getInstance() );
+       }
+
+
+       /**
+        * @test PropertyDisparityDetector::detectDisparity
+        * @dataProvider dataItemDataProvider
+        *
+        * @since 1.9
+        */
+       public function testFindDisparity( $storeValues, $dataValues, 
$settings, $expected ) {
+
+               $title = $this->getTitle( SMW_NS_PROPERTY );
+               $this->storeValues = $storeValues;
+
+               $store = array(
+                       'getPropertyValues' => array( $this, 
'mockStorePropertyValuesCallback' ),
+               );
+
+               $data  = array(
+                       'getSubject'        => $this->newSubject( $title ),
+                       'getPropertyValues' => $dataValues
+               );
+
+               $instance = $this->getInstance( $store, $data, $settings );
+
+               $this->assertInstanceOf( $this->getClass(), 
$instance->detectDisparity() );
+               $this->assertEquals( $expected['disp'], 
$instance->hasDisparity() );
+               $this->assertInternalType( $expected['type'], 
$instance->getDispatcherJob() );
+               $this->assertEquals( $expected['count'], count( 
$instance->getDispatcherJob() ) );
+
+       }
+
+       /**
+        * Provides array of dataItems
+        *
+        * @return array
+        */
+       public function dataItemDataProvider() {
+
+               $subject  = array(
+                       $this->newSubject()
+               );
+
+               $subjects = array(
+                       $this->newSubject(),
+                       $this->newSubject(),
+                       $this->newSubject()
+               );
+
+               return array(
+                       //  $storeValues, $dataValues, $settings,               
$expected
+                       array( $subjects, array(),   array( '_PVAL', '_LIST' ), 
array( 'disp' => true,  'type' => 'array', 'count' => 1 ) ),
+                       array( array(),   $subjects, array( '_PVAL', '_LIST' ), 
array( 'disp' => true,  'type' => 'array', 'count' => 1 ) ),
+                       array( $subject,  $subjects, array( '_PVAL', '_LIST' ), 
array( 'disp' => true,  'type' => 'array', 'count' => 1 ) ),
+                       array( $subject,  array(),   array( '_PVAL', '_LIST' ), 
array( 'disp' => true,  'type' => 'array', 'count' => 1 ) ),
+                       array( $subject,  array(),   array( '_PVAL' ),          
array( 'disp' => true,  'type' => 'array', 'count' => 1 ) ),
+                       array( $subjects, $subjects, array( '_PVAL' ),          
array( 'disp' => false, 'type' => 'null',  'count' => 0 ) ),
+                       array( $subject,  $subject,  array( '_PVAL' ),          
array( 'disp' => false, 'type' => 'null',  'count' => 0 ) ),
+                       array( $subjects, $subjects, array( '_PVAL', '_LIST' ), 
array( 'disp' => true,  'type' => 'array', 'count' => 1 ) ),
+                       array( $subject,  $subject,  array( '_PVAL', '_LIST' ), 
array( 'disp' => true,  'type' => 'array', 'count' => 1 ) )
+               );
+       }
+
+       /**
+        * Returns an array of SMWDataItem and simulates an alternating
+        * existencance of return values ('_LIST')
+        *
+        * @see Store::getPropertyValues
+        *
+        * @return SMWDataItem[]
+        */
+       public function mockStorePropertyValuesCallback( $subject, DIProperty 
$property, $requestoptions = null ) {
+               return $property->getKey() === '_LIST' ? array() : 
$this->storeValues;
+       }
+
+}
diff --git 
a/tests/phpunit/includes/jobs/PropertySubjectsUpdateDispatcherJobTest.php 
b/tests/phpunit/includes/jobs/PropertySubjectsUpdateDispatcherJobTest.php
new file mode 100644
index 0000000..b2bd20a
--- /dev/null
+++ b/tests/phpunit/includes/jobs/PropertySubjectsUpdateDispatcherJobTest.php
@@ -0,0 +1,158 @@
+<?php
+
+namespace SMW\Test;
+
+use SMW\PropertySubjectsUpdateDispatcherJob;
+use SMW\DIProperty;
+
+use Title;
+
+/**
+ * Tests for the PropertySubjectsUpdateDispatcherJob class
+ *
+ * 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
+ *
+ * @license GNU GPL v2+
+ * @since   1.9
+ *
+ * @author mwjames
+ */
+
+/**
+ * @covers \SMW\PropertySubjectsUpdateDispatcherJob
+ *
+ * @ingroup Test
+ *
+ * @group SMW
+ * @group SMWExtension
+ */
+class PropertySubjectsUpdateDispatcherJobTest extends 
SemanticMediaWikiTestCase {
+
+       /** @var DIProperty */
+       protected $property;
+
+       /** @var DIWikiPage[] */
+       protected $subjects;
+
+       /**
+        * Returns the name of the class to be tested
+        *
+        * @return string|false
+        */
+       public function getClass() {
+               return '\SMW\PropertySubjectsUpdateDispatcherJob';
+       }
+
+       /**
+        * Helper method that returns a PropertySubjectsUpdateDispatcherJob 
object
+        *
+        * @since 1.9
+        *
+        * @param Title|null $title
+        *
+        * @return PropertySubjectsUpdateDispatcherJob
+        */
+       private function getInstance( Title $title = null ) {
+               return new PropertySubjectsUpdateDispatcherJob( $title === null 
? $this->getTitle() : $title );
+       }
+
+       /**
+        * @test PropertySubjectsUpdateDispatcherJob::__construct
+        *
+        * @since 1.9
+        */
+       public function testConstructor() {
+               $this->assertInstanceOf( $this->getClass(), 
$this->getInstance() );
+       }
+
+       /**
+        * @test PropertySubjectsUpdateDispatcherJob::push
+        *
+        * Just verify that the push method is accessible
+        * without inserting any real job
+        *
+        * @since 1.9
+        */
+       public function testPush() {
+               $this->assertNull( $this->getInstance()->push() );
+       }
+
+       /**
+        * @test PropertySubjectsUpdateDispatcherJob::run
+        *
+        * @since 1.9
+        */
+       public function testRun() {
+
+               $title = $this->getTitle( SMW_NS_PROPERTY );
+
+               // Set-up expected property, accessible in the mock callback
+               $this->property = DIProperty::newFromUserLabel( 
$title->getText() );
+
+               // Set-up expected "raw" subjects to be returned (plus 
duplicate)
+               $duplicate = $this->newSubject();
+               $this->subjects = array(
+                       $duplicate,
+                       $this->newSubject(),
+                       $this->newSubject(),
+                       $duplicate,
+                       $this->newSubject()
+               );
+               $count = count( $this->subjects ) - 1; // eliminate duplicate 
count
+
+               $mockStore = $this->newMockObject( array(
+                       'getAllPropertySubjects' => array( $this, 
'mockStoreAllPropertySubjectsCallback' ),
+                       'getPropertySubjects'    => array()
+               ) )->getMockStore();
+
+               $instance = $this->getInstance( $title );
+               $instance->setStore( $mockStore );
+
+               // Disable dispatch jobs to avoid test
+               // jobs being inserted
+               $instance->disable()->run();
+
+               // Get access to protected jobs property
+               $reflector = $this->newReflector();
+               $jobs = $reflector->getProperty( 'jobs' );
+               $jobs->setAccessible( true );
+
+               $result = $jobs->getValue( $instance );
+
+               $this->assertInternalType( 'array', $result );
+               $this->assertCount( $count, $result );
+
+               foreach ( $result as $job ) {
+                       $this->assertInstanceOf( 'SMW\UpdateJob', $job );
+               }
+
+       }
+
+       /**
+        * Returns an array of DIWikiPage objects if the expected property
+        * and the argument property are identical
+        *
+        * @see Store::getAllPropertySubjects
+        *
+        * @return DIWikiPage[]
+        */
+       public function mockStoreAllPropertySubjectsCallback( DIProperty 
$property, $requestoptions = null ) {
+               return $this->property == $property ? $this->subjects : array();
+       }
+
+}

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

Gerrit-MessageType: merged
Gerrit-Change-Id: I81a4b206e1548e48475c6318ec4728b3cd8cb924
Gerrit-PatchSet: 7
Gerrit-Project: mediawiki/extensions/SemanticMediaWiki
Gerrit-Branch: master
Gerrit-Owner: Mwjames <[email protected]>
Gerrit-Reviewer: Mwjames <[email protected]>
Gerrit-Reviewer: jenkins-bot

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

Reply via email to