Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 81 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
name: CI

on:
push:
pull_request:

jobs:
test:
name: "PHPUnit: MW ${{ matrix.mw }}, PHP ${{ matrix.php }}"
continue-on-error: ${{ matrix.experimental }}

strategy:
matrix:
include:
- mw: 'REL1_39'
php: '8.1'
experimental: false
- mw: 'REL1_41'
php: '8.2'
experimental: false
- mw: 'REL1_42'
php: '8.3'
experimental: false
- mw: 'REL1_43'
php: '8.3'
experimental: false
- mw: 'master'
php: '8.4'
experimental: true

runs-on: ubuntu-latest


defaults:
run:
working-directory: mediawiki

steps:
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
extensions: mbstring, intl
tools: composer

- name: Cache MediaWiki
id: cache-mediawiki
uses: actions/cache@v3
with:
path: |
mediawiki
!mediawiki/extensions/
!mediawiki/vendor/
key: mw_${{ matrix.mw }}-php${{ matrix.php }}_v4

- name: Cache Composer cache
uses: actions/cache@v3
with:
path: ~/.composer/cache
key: composer-php${{ matrix.php }}

- uses: actions/checkout@v4
with:
path: EarlyCopy

- name: Install MediaWiki
if: steps.cache-mediawiki.outputs.cache-hit != 'true'
working-directory: ~
run: bash EarlyCopy/.github/workflows/installMediaWiki.sh ${{ matrix.mw }} WikibaseConstraintViolationsExport

- uses: actions/checkout@v4
with:
path: mediawiki/extensions/WikibaseConstraintViolationsExport

- run: composer update

- name: Run update.php
run: php maintenance/update.php --quick

- name: Run PHPUnit
run: php tests/phpunit/phpunit.php -c extensions/WikibaseConstraintViolationsExport/
54 changes: 54 additions & 0 deletions .github/workflows/installMediaWiki.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
#! /bin/bash

MW_BRANCH=$1
EXTENSION_NAME=$2

wget "https://github.com/wikimedia/mediawiki/archive/refs/heads/$MW_BRANCH.tar.gz" -nv

tar -zxf $MW_BRANCH.tar.gz
mv mediawiki-$MW_BRANCH mediawiki

cd mediawiki

composer install
php maintenance/install.php --dbtype sqlite --dbuser root --dbname mw --dbpath $(pwd) --pass AdminPassword WikiName AdminUser

cat <<'EOT' >> LocalSettings.php
error_reporting(E_ALL & ~E_DEPRECATED);
ini_set("display_errors", "1");
$wgShowExceptionDetails = true;
$wgShowDBErrorBacktrace = true;
$wgDevelopmentWarnings = true;
EOT

cat <<EOT >> LocalSettings.php
wfLoadExtension( 'WikibaseRepository', __DIR__ . '/extensions/Wikibase/extension-repo.json' );
require_once __DIR__ . '/extensions/Wikibase/repo/ExampleSettings.php';

wfLoadExtension( 'WikibaseQualityConstraints' );
wfLoadExtension( "$EXTENSION_NAME" );
EOT

cat <<EOT >> composer.local.json
{
"extra": {
"merge-plugin": {
"merge-dev": true,
"include": [
"extensions/Wikibase/composer.json",
"extensions/$EXTENSION_NAME/composer.json"
]
}
}
}
EOT

cd extensions
git clone https://gerrit.wikimedia.org/r/mediawiki/extensions/WikibaseQualityConstraints --depth=1 --branch=$MW_BRANCH --recurse-submodules -j8

git clone https://gerrit.wikimedia.org/r/mediawiki/extensions/Wikibase --depth=1 --branch=$MW_BRANCH -j8 && \
cd Wikibase && \
git submodule set-url view/lib/wikibase-serialization https://github.com/wmde/WikibaseSerializationJavaScript.git && \
git submodule set-url view/lib/wikibase-data-values https://github.com/wmde/DataValuesJavaScript.git && \
git submodule set-url view/lib/wikibase-data-model https://github.com/wmde/WikibaseDataModelJavaScript.git && \
git submodule sync && git submodule init && git submodule update --recursive
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
.phpunit.result.cache
11 changes: 11 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
.PHONY: ci test phpunit

ci: test
test: phpunit

phpunit:
ifdef filter
php ../../tests/phpunit/phpunit.php -c phpunit.xml.dist --filter $(filter)
else
php ../../tests/phpunit/phpunit.php -c phpunit.xml.dist
endif
17 changes: 13 additions & 4 deletions maintenance/ExportConstraintViolations.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,17 @@

namespace ProfessionalWiki\WikibaseConstraintViolationsExport\Maintenance;

use MediaWiki\Language\Language;
use MediaWiki\Maintenance\Maintenance;
use Language;
use Maintenance;
use MediaWiki\MediaWikiServices;
use MediaWiki\Message\Message;
use Message;
use MessageLocalizer;
use ProfessionalWiki\WikibaseConstraintViolationsExport\Presentation\PlainTextViolationMessageRenderer;
use ValueFormatters\FormatterOptions;
use ValueFormatters\ValueFormatter;
use Wikibase\DataModel\Services\EntityId\EntityIdPager;
use Wikibase\Lib\Formatters\SnakFormatter;
use Wikibase\Repo\EntityIdLabelFormatterFactory;
use Wikibase\Repo\Store\Sql\SqlEntityIdPagerFactory;
use Wikibase\Repo\WikibaseRepo;
use WikibaseQuality\ConstraintReport\ConstraintCheck\Result\CheckResult;
Expand Down Expand Up @@ -89,7 +90,7 @@ private function getViolationMessageRenderer(): PlainTextViolationMessageRendere
$language = MediaWikiServices::getInstance()->getLanguageFactory()->getLanguage( 'en' );

return new PlainTextViolationMessageRenderer(
entityIdFormatter: WikibaseRepo::getEntityIdLabelFormatterFactory()->getEntityIdFormatter( $language ),
entityIdFormatter: $this->getEntityIdLabelFormatter()->getEntityIdFormatter( $language ),
dataValueFormatter: $this->getValueFormatter( $language ),
languageNameUtils: MediaWikiServices::getInstance()->getLanguageNameUtils(),
userLanguageCode: $language->getCode(),
Expand All @@ -99,6 +100,14 @@ private function getViolationMessageRenderer(): PlainTextViolationMessageRendere
);
}

private function getEntityIdLabelFormatter(): EntityIdLabelFormatterFactory {
if ( method_exists( WikibaseRepo::class, 'getEntityIdLabelFormatterFactory' ) ) {
return WikibaseRepo::getEntityIdLabelFormatterFactory();
}

return new EntityIdLabelFormatterFactory();
}

private function getValueFormatter( Language $language ): ValueFormatter {
$formatterOptions = new FormatterOptions();
$formatterOptions->setOption( SnakFormatter::OPT_LANG, $language->getCode() );
Expand Down
12 changes: 12 additions & 0 deletions phpunit.xml.dist
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<phpunit colors="true">
<testsuites>
<testsuite name="WikibaseConstraintViolationsExport">
<directory>tests/</directory>
</testsuite>
</testsuites>
<filter>
<whitelist addUncoveredFilesFromWhitelist="true">
<directory suffix=".php">src</directory>
</whitelist>
</filter>
</phpunit>
5 changes: 3 additions & 2 deletions src/Presentation/PlainTextViolationMessageRenderer.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@

namespace ProfessionalWiki\WikibaseConstraintViolationsExport\Presentation;

use Config;
use DataValues\DataValue;
use MediaWiki\Config\Config;
use InvalidArgumentException;
use MediaWiki\Languages\LanguageNameUtils;
use MediaWiki\Message\Message;
use Message;
use MessageLocalizer;
use ValueFormatters\ValueFormatter;
use Wikibase\DataModel\Entity\EntityId;
Expand Down
173 changes: 173 additions & 0 deletions tests/Maintenance/ExportConstraintViolationsTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
<?php

declare( strict_types = 1 );

namespace ProfessionalWiki\WikibaseConstraintViolationsExport\Tests\Maintenance;

use DataValues\StringValue;
use MediaWiki\Tests\Maintenance\MaintenanceBaseTestCase;
use ProfessionalWiki\WikibaseConstraintViolationsExport\Maintenance\ExportConstraintViolations;
use Wikibase\DataModel\Entity\EntityDocument;
use Wikibase\DataModel\Entity\Item;
use Wikibase\DataModel\Entity\ItemId;
use Wikibase\DataModel\Entity\NumericPropertyId;
use Wikibase\DataModel\Entity\Property;
use Wikibase\DataModel\Services\Statement\GuidGenerator;
use Wikibase\DataModel\Snak\PropertyValueSnak;
use Wikibase\DataModel\Statement\Statement;
use Wikibase\DataModel\Term\Fingerprint;
use Wikibase\DataModel\Term\Term;
use Wikibase\DataModel\Term\TermList;
use Wikibase\Repo\WikibaseRepo;
use WikibaseQuality\ConstraintReport\Tests\DefaultConfig;

/**
* @covers \ProfessionalWiki\WikibaseConstraintViolationsExport\Maintenance\ExportConstraintViolations
* @group Database
*/
class ExportConstraintViolationsTest extends MaintenanceBaseTestCase {

use DefaultConfig;

private const MULTI_VALUE_CONSTRAINT_ID = '1';
private const SINGLE_VALUE_CONSTRAINT_ID = '2';

private const MULTI_VALUE_CONSTRAINT_TYPE_ID = 'Q1';
private const SINGLE_VALUE_CONSTRAINT_TYPE_ID = 'Q2';

private const MULTI_VALUE_PROPERTY_ID = 'P1';
private const SINGLE_VALUE_PROPERTY_ID = 'P2';

protected function setUp(): void {
parent::setUp();

$this->overrideConfigValues( [
'WBQualityConstraintsMultiValueConstraintId' => self::MULTI_VALUE_CONSTRAINT_TYPE_ID,
'WBQualityConstraintsSingleValueConstraintId' => self::SINGLE_VALUE_CONSTRAINT_TYPE_ID
] );
}

protected function getMaintenanceClass(): string {
return ExportConstraintViolations::class;
}

public function addDBDataOnce() {
$this->saveProperty( self::MULTI_VALUE_PROPERTY_ID, 'string', 'Multiple Values' );
$this->saveProperty( self::SINGLE_VALUE_PROPERTY_ID, 'string', 'Single Value' );

$this->db->insert(
'wbqc_constraints',
[
[
'constraint_guid' => self::MULTI_VALUE_CONSTRAINT_ID,
'pid' => ( new NumericPropertyId( self::MULTI_VALUE_PROPERTY_ID ) )->getNumericId(),
'constraint_type_qid' => self::MULTI_VALUE_CONSTRAINT_TYPE_ID,
'constraint_parameters' => '{}',
],
[
'constraint_guid' => self::SINGLE_VALUE_CONSTRAINT_ID,
'pid' => ( new NumericPropertyId( self::SINGLE_VALUE_PROPERTY_ID ) )->getNumericId(),
'constraint_type_qid' => self::SINGLE_VALUE_CONSTRAINT_TYPE_ID,
'constraint_parameters' => '{}',
]
]
);
}

public function testExportsEmptyArrayIfThereAreNoViolations() : void{
$item1 = new Item( new ItemId( 'Q100' ) );
$this->addStatement( $item1, self::MULTI_VALUE_PROPERTY_ID, 'multi value 1' );
$this->addStatement( $item1, self::MULTI_VALUE_PROPERTY_ID, 'multi value 2' );
$this->addStatement( $item1, self::SINGLE_VALUE_PROPERTY_ID, 'single value 1' );

$this->maintenance->execute();

$this->expectOutputString( '[]' );
}

public function testExportsViolations() : void{
$item1 = new Item( new ItemId( 'Q100' ) );
$this->addStatement( $item1, self::MULTI_VALUE_PROPERTY_ID, 'multi value 1' );
$this->addStatement( $item1, self::SINGLE_VALUE_PROPERTY_ID, 'single value 1' );

$item2 = new Item( new ItemId( 'Q200' ) );
$this->addStatement( $item2, self::MULTI_VALUE_PROPERTY_ID, 'multi value 1' );
$this->addStatement( $item2, self::MULTI_VALUE_PROPERTY_ID, 'multi value 2' );
$this->addStatement( $item2, self::SINGLE_VALUE_PROPERTY_ID, 'single value 1' );
$this->addStatement( $item2, self::SINGLE_VALUE_PROPERTY_ID, 'single value 2' );

$this->maintenance->execute();

$this->assertEqualsCanonicalizing(
[
'Q100' => [
[
'status' => 'warning',
'propertyId' => self::MULTI_VALUE_PROPERTY_ID,
'messageKey' => 'wbqc-violation-message-multi-value',
'message' => 'This property should contain multiple values.',
'constraintId' => self::MULTI_VALUE_CONSTRAINT_ID,
'constraintType' => self::MULTI_VALUE_CONSTRAINT_TYPE_ID,
'value' => 'multi value 1'
]
],
'Q200' => [
[
'status' => 'warning',
'propertyId' => self::SINGLE_VALUE_PROPERTY_ID,
'messageKey' => 'wbqc-violation-message-single-value',
'message' => 'This property should only contain a single value.',
'constraintId' => self::SINGLE_VALUE_CONSTRAINT_ID,
'constraintType' => self::SINGLE_VALUE_CONSTRAINT_TYPE_ID,
'value' => 'single value 2'
],
[
'status' => 'warning',
'propertyId' => self::SINGLE_VALUE_PROPERTY_ID,
'messageKey' => 'wbqc-violation-message-single-value',
'message' => 'This property should only contain a single value.',
'constraintId' => self::SINGLE_VALUE_CONSTRAINT_ID,
'constraintType' => self::SINGLE_VALUE_CONSTRAINT_TYPE_ID,
'value' => 'single value 1'
]
]
],
json_decode( $this->getActualOutput(), true )
);
}

protected function saveEntity( EntityDocument $entity ): void {
WikibaseRepo::getEntityStore()->saveEntity(
entity: $entity,
summary: __CLASS__,
user: self::getTestSysop()->getUser()
);
}

protected function saveProperty( string $pId, string $type, string $label ): void {
$this->saveEntity(
new Property(
id: new NumericPropertyId( $pId ),
fingerprint: new Fingerprint( labels: new TermList( [
new Term( languageCode: 'en', text: $label )
] ) ),
dataTypeId: $type
)
);
}

protected function addStatement( EntityDocument $entity, string $propertyId, string $value ): void {
$statementGuidGenerator = new GuidGenerator();

$dataValue = new StringValue( $value );
$snak = new PropertyValueSnak( new NumericPropertyId( $propertyId ), $dataValue );
$statement = new Statement( $snak );
$statementGuid = $statementGuidGenerator->newGuid( $entity->getId() );
$statement->setGuid( $statementGuid );

$entity->getStatements()->addStatement( $statement );

$this->saveEntity( $entity );
}

}