Continuous integration/Entry points: Difference between revisions
mNo edit summary |
Marked this version for translation |
||
| (48 intermediate revisions by 9 users not shown) | |||
| Line 1: | Line 1: | ||
<languages/> |
<languages/> |
||
{{WMF CI navigation}} |
|||
{{TOCright}} |
|||
<translate> |
|||
<translate><!--T:1--> These '''entry points''' allow you to easily define and run tests in your repository.</translate> |
|||
<!--T:1--> |
|||
<translate><!--T:101--> We have '''standardized tools''' and entry points that run in WMF CI across over 1000+ Git repositories including MediaWiki core, extensions, PHP libraries, and other standalone projects.</translate> |
|||
We have standardized on the following tools for testing our code: |
|||
</translate> |
|||
<translate><!--T:17--> Documentation on how to configure and set up these tools for each programming language can be found further down this page.</translate> |
|||
{| class="wikitable" |
{| class="wikitable" |
||
| Line 20: | Line 21: | ||
<code>php -l</code> |
<code>php -l</code> |
||
|<translate><!--T:16--> |
| <translate><!--T:16--> <tvar name=1>{{ll|Continuous integration/PHP CodeSniffer|PHP CodeSniffer}}</tvar> with <tvar name=2>[https://github.com/wikimedia/mediawiki-tools-codesniffer mediawiki-tools-codesniffer]</tvar></translate> |
||
| |
| <translate><!--T:55--> <tvar name=1>{{ll|Continuous integration/Phan|Phan}}</tvar> with <tvar name=2>[https://github.com/wikimedia/mediawiki-phan-config mediawiki-phan-config]</tvar></translate> |
||
|[https://phpunit.de/ PHPUnit] |
|[https://phpunit.de/ PHPUnit] |
||
|[[Special:MyLanguage/Doxygen|Doxygen]] |
|[[Special:MyLanguage/Doxygen|Doxygen]] |
||
| Line 27: | Line 28: | ||
!JavaScript |
!JavaScript |
||
| rowspan="4" |npm & [https://gruntjs.com/ grunt] |
| rowspan="4" |npm & [https://gruntjs.com/ grunt] |
||
| |
|rowspan="2"| <translate><!--T:56--> <tvar name=1>[https://www.npmjs.com/package/grunt-eslint grunt-eslint]</tvar> with <tvar name=2>[https://github.com/wikimedia/eslint-config-wikimedia eslint-config-wikimedia]</tvar></translate> |
||
| |
|colspan="2"| <translate><!--T:57--> <tvar name=1>[https://www.npmjs.com/package/grunt-eslint grunt-eslint]</tvar> with <tvar name=2>[https://github.com/wikimedia/eslint-config-wikimedia eslint-config-wikimedia]</tvar></translate> |
||
|[[Special:MyLanguage/Manual:JavaScript unit testing|QUnit]] |
|[[Special:MyLanguage/Manual:JavaScript unit testing|QUnit]] |
||
|[[JSDoc]] |
|[[JSDoc]] |
||
| Line 46: | Line 47: | ||
|- |
|- |
||
!CSS/LESS |
!CSS/LESS |
||
| |
|colspan="2"| <translate><!--T:60--> <tvar name=1>[https://github.com/wikimedia/grunt-stylelint grunt-stylelint]</tvar> with <tvar name=2>[https://github.com/wikimedia/stylelint-config-wikimedia stylelint-config-wikimedia]</tvar></translate> |
||
|N/A |
|N/A |
||
|N/A |
|N/A |
||
| Line 60: | Line 61: | ||
|- |
|- |
||
!Python |
!Python |
||
|[https://tox. |
|[https://tox.wiki/en/latest/ tox] |
||
| colspan="3" |[ |
| colspan="3" |[https://flake8.pycqa.org/en/latest/ flake8] |
||
|unittest |
|unittest |
||
pytest<ref>https://lists.wikimedia.org/ |
pytest<ref>[https://lists.wikimedia.org/hyperkitty/list/wikitech-l@lists.wikimedia.org/message/NYY2AD6ZVO2LOCU4IE5ZNPRCAMUTJDV2/ Re: Wikitech-l Migrating away from nose]</ref> |
||
|[ |
|[https://www.sphinx-doc.org/en/master/ sphinx]? |
||
|- |
|- |
||
!Ruby |
!Ruby |
||
|[ |
|[https://bundler.io/ bundler] |
||
| colspan="2" |[https://github.com/rubocop |
| colspan="2" |[https://github.com/rubocop/rubocop rubocop] |
||
|? |
|? |
||
|rake test |
|rake test |
||
|? |
|? |
||
|} |
|} |
||
<translate><!--T:17--> Documentation on how to configure and set up these tools can be found below.</translate> |
|||
{{TOC|limit=2}} |
|||
== JavaScript == |
|||
<translate> |
<translate> |
||
== List == <!--T:102--> |
|||
</translate> |
|||
<translate><!--T:103--> Here is a list of the commands that will be executed by the various Jenkins jobs on a MediaWiki core patch.</translate> |
|||
<translate><!--T:112--> You can control which tests and linters run for a repository's continuous integration by changing how commands such as these are configured.</translate> |
|||
* <translate><!--T:104--> <tvar name=1><code>api-testing</code></tvar> - for [[<tvar name=2>Special:MyLanguage/MediaWiki API integration tests</tvar>|API tests]]</translate> |
|||
<!--T:19--> |
|||
* <translate><!--T:105--> <tvar name=1><code>composer phpunit:unit</code></tvar> - for [[<tvar name=2>Special:MyLanguage/Manual:PHP unit testing</tvar>|PHPUnit]] tests and [[parser tests]]</translate> |
|||
* <translate><!--T:106--> <tvar name=1><code>composer test</code></tvar> - for PHP linters</translate> |
|||
* <translate><!--T:107--> <tvar name=1><code>exec vendor/bin/phan</code></tvar> - for [[<tvar name=2>Special:MyLanguage/Continuous integration/Phan</tvar>|Phan]] linting</translate> |
|||
* <translate><!--T:108--> <tvar name=1><code>npm run-script test</code></tvar> - for JavaScript linters, [[<tvar name=2>Special:MyLanguage/Jest</tvar>|Jest]] tests, and [[JSDoc]] warnings</translate> |
|||
* <translate><!--T:109--> <tvar name=1><code>selenium-test</code></tvar> - for [[<tvar name=2>Special:MyLanguage/Selenium</tvar>|Selenium]] tests</translate> |
|||
* <translate><!--T:110--> <tvar name=1>[[Special:JavaScriptTest]]</tvar> - for [[<tvar name=2>Special:MyLanguage/Manual:JavaScript unit testing</tvar>|QUnit]] tests</translate> |
|||
<translate> |
|||
== JavaScript == <!--T:111--> |
|||
=== Testing JavaScript === <!--T:18--> |
|||
</translate> |
|||
<translate><!--T:19--> We are using <tvar name=1><code>npm test</code></tvar> as an entry point for linters and some test suites such as Jest.</translate> |
|||
<translate><!--T:20--> If your project has any JavaScript files, it should at least have a <tvar name=1><code>package.json</code></tvar> file which defines a <tvar name=2><code>test</code></tvar> script, and the related <tvar name=3><code>package-lock.json</code></tvar> file to ensure consistency in CI runs and security upgrades.</translate> |
<translate><!--T:20--> If your project has any JavaScript files, it should at least have a <tvar name=1><code>package.json</code></tvar> file which defines a <tvar name=2><code>test</code></tvar> script, and the related <tvar name=3><code>package-lock.json</code></tvar> file to ensure consistency in CI runs and security upgrades.</translate> |
||
<translate><!--T: |
<translate><!--T:22--> QUnit tests are tested by a Jenkins job that loads a browser and visits [[Special:JavaScriptTest]].</translate> |
||
<translate><!--T:22--> Anything other than linters (''e.g.'' unit/integration tests) are run through the normal MediaWiki channels; if you want a stand-alone runner, you should put that into another script.</translate> |
|||
<translate><!--T:23--> You will need the <tvar name=1><code>.eslintrc.json</code></tvar> configuration file in your project (see [[<tvar name=2>Special:MyLanguage/Manual:Coding conventions/JavaScript#Linting</tvar>|Manual:Coding conventions/JavaScript#Linting]]).</translate> |
<translate><!--T:23--> You will need the <tvar name=1><code>.eslintrc.json</code></tvar> configuration file in your project (see [[<tvar name=2>Special:MyLanguage/Manual:Coding conventions/JavaScript#Linting</tvar>|Manual:Coding conventions/JavaScript#Linting]]).</translate> |
||
<translate><!--T:24--> Look at one of the projects listed in the [[<tvar name=1>#Examples</tvar>|example section]] below for an example of these files.</translate> |
<translate><!--T:24--> Look at one of the projects listed in the [[<tvar name=1>#Examples</tvar>|example section]] below for an example of these files.</translate> |
||
{{Note|<translate><!--T:25--> On older Linux boxes, if the <tvar name=1><code>npm</code></tvar> commands fails with "node: No such file or directory", you may need to install the "<tvar name=2><code>nodejs-legacy</code></tvar>" package.</translate>}} |
{{Note|1=<translate><!--T:25--> On older Linux boxes, if the <tvar name=1><code>npm</code></tvar> commands fails with "node: No such file or directory", you may need to install the "<tvar name=2><code>nodejs-legacy</code></tvar>" package.</translate>}} |
||
<translate> |
<translate> |
||
==== Grunt task runner ==== <!--T:26--> |
==== Grunt task runner ==== <!--T:26--> |
||
</translate> |
|||
<translate><!--T:27--> If your project has complex build processes, or is an extension or skin that will benefit from i18n checking and JSON file linting, the convention is to use [<tvar name=url>https://gruntjs.com/getting-started</tvar> Grunt] as a task runner.</translate> |
|||
<!--T:27--> |
|||
If your project has complex build processes, or is an extension or skin that will benefit from i18n checking and JSON file linting, the convention is to use [<tvar name=url>http://gruntjs.com/getting-started</tvar> Grunt] as a task runner.</translate> |
|||
<translate><!--T:28--> Your project still has a <tvar name=1><code>package.json</code></tvar> file, which has a dependency on <tvar name=2><code>grunt</code></tvar> and sets <tvar name=3><code>"test": "grunt test"</code></tvar>.</translate> |
<translate><!--T:28--> Your project still has a <tvar name=1><code>package.json</code></tvar> file, which has a dependency on <tvar name=2><code>grunt</code></tvar> and sets <tvar name=3><code>"test": "grunt test"</code></tvar>.</translate> |
||
<translate><!--T:29--> In turn, a <tvar name=1><code>Gruntfile.js</code></tvar> file implements <tvar name=2><code>grunt test</code></tvar>, and this can run a wide variety of tools and tests:</translate> |
<translate><!--T:29--> In turn, a <tvar name=1><code>Gruntfile.js</code></tvar> file implements <tvar name=2><code>grunt test</code></tvar>, and this can run a wide variety of tools and tests:</translate> |
||
* <translate><!--T:30--> <tvar name=1><code>eslint</code></tvar>, which checks both JS and JSON files.</translate> |
* <translate><!--T:30--> <tvar name=1><code>eslint</code></tvar>, which checks both JS and JSON files.</translate> |
||
* <translate><!--T:31--> <tvar name=1><code>stylelint</code></tvar>, which checks both CSS and LESS files.</translate> |
* <translate><!--T:31--> <tvar name=1><code>stylelint</code></tvar>, which checks both CSS and LESS files.</translate> |
||
| Line 104: | Line 118: | ||
<translate><!--T:33--> You can specify configuration settings for these tools in <tvar name=1><code>Gruntfile.js</code></tvar>.</translate> |
<translate><!--T:33--> You can specify configuration settings for these tools in <tvar name=1><code>Gruntfile.js</code></tvar>.</translate> |
||
<translate><!--T:34--> However, it should contain little to no configuration for tools that can run outside <tvar name=1><code>grunt</code></tvar> so that they operate the same when run standalone or from a text editor plugin.</translate> |
<translate><!--T:34--> However, it should contain little to no configuration for tools that can run outside <tvar name=1><code>grunt</code></tvar> so that they operate the same when run standalone or from a text editor plugin.</translate> |
||
<translate><!--T:35--> Always use native configuration files where possible, including <tvar name=1><code>.eslintrc.json</code></tvar> mentioned above.</translate> |
|||
<translate><!--T:35--> |
|||
Always use native configuration files where possible, including <tvar name=1><code>.eslintrc.json</code></tvar> mentioned above. |
|||
<translate> |
|||
=== JavaScript documentation === <!--T:36--> |
=== JavaScript documentation === <!--T:36--> |
||
</translate> |
|||
<translate><!--T:37--> Use <tvar name=2><code>npm run doc</code></tvar> as the entry point.</translate> |
|||
<!--T:37--> |
|||
Use <tvar name=2><code>npm run doc</code></tvar> as the entry point.</translate> |
|||
<translate><!--T:38--> The convention is to use <tvar name=2>{{ll|JSDoc}}</tvar>.</translate> |
<translate><!--T:38--> The convention is to use <tvar name=2>{{ll|JSDoc}}</tvar>.</translate> |
||
<translate><!--T:39--> The <tvar name=1><code>predoc</code></tvar> and <tvar name=2><code>postdoc</code></tvar> script hooks in <tvar name=3><code>package.json</code></tvar> can be used to run any additional scripts (''e.g.'' build files for inclusion beforehand, or copy additional files for publication afterwards).</translate> |
<translate><!--T:39--> The <tvar name=1><code>predoc</code></tvar> and <tvar name=2><code>postdoc</code></tvar> script hooks in <tvar name=3><code>package.json</code></tvar> can be used to run any additional scripts (''e.g.'' build files for inclusion beforehand, or copy additional files for publication afterwards).</translate> |
||
| Line 117: | Line 130: | ||
=== Examples === <!--T:40--> |
=== Examples === <!--T:40--> |
||
</translate> |
</translate> |
||
{{Rellink|<translate><!--T:61--> Use the [[<tvar name=1>Extension:BoilerPlate</tvar>|BoilerPlate]] extension as starting point for a new MediaWiki extension.</translate>}} |
{{Rellink|1=<translate><!--T:61--> Use the [[<tvar name=1>Extension:BoilerPlate</tvar>|BoilerPlate]] extension as starting point for a new MediaWiki extension.</translate>}} |
||
<translate> |
<translate> |
||
| Line 128: | Line 141: | ||
"private": true, |
"private": true, |
||
"scripts": { |
"scripts": { |
||
"test": "grunt test" |
"test": "grunt test" |
||
"doc": "jsduck" |
|||
}, |
}, |
||
"devDependencies": { |
"devDependencies": { |
||
| Line 181: | Line 193: | ||
==== Example projects ==== <!--T:43--> |
==== Example projects ==== <!--T:43--> |
||
</translate> |
</translate> |
||
* <translate><!--T:63--> <tvar name=1>{{ll|Extension:BoilerPlate}}</tvar> has a <tvar name=2><code>{{Git file |project=mediawiki/extensions/BoilerPlate |file=Gruntfile.js}}</code></tvar> that runs jshint, jscs, and banana-checker (for MediaWiki's <tvar name=3><code>i18n</code></tvar> JSON files).</translate> |
* <translate><!--T:63--> <tvar name=1>{{ll|Extension:BoilerPlate}}</tvar> has a <tvar name=2><code>{{Git file |project=mediawiki/extensions/BoilerPlate |file=Gruntfile.js}}</code></tvar> that runs jshint, jscs, and banana-checker (for MediaWiki's <tvar name=3><code>i18n</code></tvar> JSON files).</translate> |
||
* <translate><!--T:64--> <tvar name=1>jquery-client</tvar>: [<tvar name=url>https://github.com/wikimedia/jquery-client/blob/master/package.json</tvar> package.json] (jshint, jscs, karma; no Grunt needed)</translate> |
* <translate><!--T:64--> <tvar name=1>jquery-client</tvar>: [<tvar name=url>https://github.com/wikimedia/jquery-client/blob/master/package.json</tvar> package.json] (jshint, jscs, karma; no Grunt needed)</translate> |
||
| Line 190: | Line 201: | ||
==== Further reading ==== <!--T:44--> |
==== Further reading ==== <!--T:44--> |
||
</translate> |
</translate> |
||
* <translate><!--T:67--> [<tvar name=url1>https://docs.npmjs.com/files/package.json</tvar> package.json format] on <tvar name=1>docs.npmjs.org</tvar></translate> |
* <translate><!--T:67--> [<tvar name=url1>https://docs.npmjs.com/files/package.json</tvar> package.json format] on <tvar name=1>docs.npmjs.org</tvar></translate> |
||
* <translate><!--T:68--> [<tvar name=url2>https://docs.npmjs.com/misc/scripts</tvar> package "scripts" lifecycle] on <tvar name=1>docs.npmjs.org</tvar></translate> |
* <translate><!--T:68--> [<tvar name=url2>https://docs.npmjs.com/misc/scripts</tvar> package "scripts" lifecycle] on <tvar name=1>docs.npmjs.org</tvar></translate> |
||
== PHP == |
== PHP == |
||
{{Rellink| |
{{Rellink|1=<nowiki/> |
||
* <translate><!--T:69--> Use the <tvar name=1>[[Extension:BoilerPlate|BoilerPlate]]</tvar> extension as starting point for a new MediaWiki extension.</translate> |
* <translate><!--T:69--> Use the <tvar name=1>[[Extension:BoilerPlate|BoilerPlate]]</tvar> extension as starting point for a new MediaWiki extension.</translate> |
||
* <translate><!--T:70--> Use the [<tvar name=url>https://github.com/wikimedia/mediawiki-tools-cookiecutter-library#readme</tvar> mediawiki-tools-cookiecutter-library] for creating a new PHP library.</translate>}} |
* <translate><!--T:70--> Use the [<tvar name=url>https://github.com/wikimedia/mediawiki-tools-cookiecutter-library#readme</tvar> mediawiki-tools-cookiecutter-library] for creating a new PHP library.</translate>}} |
||
| Line 202: | Line 212: | ||
=== Testing PHP === <!--T:9--> |
=== Testing PHP === <!--T:9--> |
||
</translate> |
</translate> |
||
<translate><!--T:10--> We are using <tvar name=1><code>composer test</code></tvar> as an entry point.</translate> |
<translate><!--T:10--> We are using <tvar name=1><code>composer test</code></tvar> as an entry point.</translate> |
||
<translate><!--T:11--> If your project has PHP files it should list the test framework packages it needs in <tvar name=1><code>composer.json</code></tvar> under <tvar name=2><code>require-dev</code></tvar> and list the commands to be run in the <tvar name=3><code>scripts.test</code></tvar> property:</translate> |
<translate><!--T:11--> If your project has PHP files it should list the test framework packages it needs in <tvar name=1><code>composer.json</code></tvar> under <tvar name=2><code>require-dev</code></tvar> and list the commands to be run in the <tvar name=3><code>scripts.test</code></tvar> property:</translate> |
||
<syntaxhighlight lang="json"> |
<syntaxhighlight lang="json">{ |
||
{ |
|||
"require-dev": { |
"require-dev": { |
||
"mediawiki/mediawiki-codesniffer": " |
"mediawiki/mediawiki-codesniffer": "48.0.0", |
||
"mediawiki/ |
"mediawiki/mediawiki-phan-config": "0.18.0", |
||
" |
"mediawiki/minus-x": "2.0.0", |
||
" |
"ockcyp/covers-validator": "1.6.0", |
||
"php-parallel-lint/php- |
"php-parallel-lint/php-console-highlighter": "1.0.0", |
||
"php-parallel-lint/php-parallel-lint": "1.4.0", |
|||
"phpunit/phpunit": "^8.5" |
|||
"phpunit/phpunit": "10.5.58" |
|||
}, |
}, |
||
"scripts": { |
"scripts": { |
||
"test": [ |
"test": [ |
||
"parallel-lint . --exclude vendor --exclude node_modules", |
"parallel-lint . --exclude vendor --exclude node_modules", |
||
"php -d 'extension=pcov.so' vendor/bin/phpunit", |
|||
"minus-x check .", |
|||
"phpunit", |
|||
"covers-validator", |
"covers-validator", |
||
"phpcs - |
"phpcs -sp", |
||
"phan --allow-polyfill-parser --long-progress-bar", |
|||
"minus-x check ." |
|||
], |
], |
||
"cover": "phpunit --coverage-html coverage", |
|||
"fix": [ |
"fix": [ |
||
"minus-x fix .", |
"minus-x fix .", |
||
"phpcbf" |
"phpcbf" |
||
] |
] |
||
}, |
|||
"config": { |
|||
"allow-plugins": { |
|||
"dealerdirect/phpcodesniffer-composer-installer": true |
|||
} |
|||
} |
} |
||
}</syntaxhighlight> |
|||
} |
|||
</syntaxhighlight> |
|||
<translate><!--T:12--> See< |
<translate><!--T:12--> See <tvar name=1>{{git file |project=cdb |file=composer.json |text=composer.json}}</tvar> of the cdb project for a good example.</translate> |
||
<translate><!--T:13--> Note that '''MediaWiki extensions are not standalone projects''' and cannot run their own PHPUnit test suite from composer.</translate> |
<translate><!--T:13--> Note that '''MediaWiki extensions are not standalone projects''' and cannot run their own PHPUnit test suite from composer.</translate> |
||
| Line 243: | Line 255: | ||
{ |
{ |
||
"require-dev": { |
"require-dev": { |
||
"mediawiki/mediawiki-codesniffer": " |
"mediawiki/mediawiki-codesniffer": "44.0.0", |
||
"mediawiki/ |
"mediawiki/mediawiki-phan-config": "0.14.0", |
||
"php-parallel-lint/php-console-highlighter": " |
"mediawiki/minus-x": "1.1.3", |
||
"php-parallel-lint/php-console-highlighter": "1.0.0", |
|||
"php-parallel-lint/php-parallel-lint": "1. |
"php-parallel-lint/php-parallel-lint": "1.4.0" |
||
}, |
}, |
||
"scripts": { |
"scripts": { |
||
"test": [ |
"test": [ |
||
"parallel-lint . --exclude vendor --exclude node_modules", |
"parallel-lint . --exclude vendor --exclude node_modules", |
||
" |
"phpcs -sp --cache", |
||
" |
"minus-x check ." |
||
], |
], |
||
"fix": [ |
"fix": [ |
||
"minus-x fix .", |
"minus-x fix .", |
||
"phpcbf" |
"phpcbf" |
||
] |
], |
||
"phan": "phan -d . --long-progress-bar" |
|||
}, |
|||
"config": { |
|||
"allow-plugins": { |
|||
"dealerdirect/phpcodesniffer-composer-installer": true |
|||
} |
|||
} |
} |
||
} |
} |
||
</syntaxhighlight> |
</syntaxhighlight> |
||
<translate> |
|||
<translate><!--T:45--> See</translate> {{git file|project=mediawiki/extensions/AbuseFilter|file=composer.json|text=<translate><!--T:46--> <tvar name=1>composer.json</tvar> of the AbuseFilter MediaWiki extension</translate>}} <translate><!--T:47--> |
|||
<!--T:45--> |
|||
for a good example. |
|||
See <tvar name=1>{{git file|project=mediawiki/extensions/AbuseFilter|file=composer.json|text=composer.json}}</tvar> of the AbuseFilter MediaWiki extension for a good example. |
|||
=== PHP Documentation === <!--T:48--> |
=== PHP Documentation === <!--T:48--> |
||
| Line 282: | Line 302: | ||
=== Rake === |
=== Rake === |
||
<translate><!--T:53--> |
<translate> |
||
<!--T:53--> |
|||
Use [<tvar name=url1>https://github.com/ruby/rake</tvar> Rake] to define your commands, they will be executed via [<tvar name=url2> |
Use [<tvar name=url1>https://github.com/ruby/rake</tvar> Rake] to define your commands, they will be executed via [<tvar name=url2>https://bundler.io/</tvar> Bundler]. |
||
<!--T:54--> |
<!--T:54--> |
||
Example [<tvar name=url3>https://phabricator.wikimedia.org/diffusion/EMMV/browse/master/Rakefile</tvar> <code>Rakefile</code>]:</translate> |
Example [<tvar name=url3>https://phabricator.wikimedia.org/diffusion/EMMV/browse/master/Rakefile</tvar> <code>Rakefile</code>]: |
||
</translate> |
|||
<syntaxhighlight lang=ruby> |
<syntaxhighlight lang=ruby> |
||
| Line 319: | Line 341: | ||
</pre> |
</pre> |
||
<translate> |
|||
<translate><!--T:79--> The Jenkins job <tvar name=1><code>rake-jessie</code></tvar> invokes <tvar name=2><code>test</code></tvar> target by running <tvar name=3><code>bundle exec rake test</code></tvar>.</translate> |
|||
<!--T:79--> |
|||
The Jenkins job <tvar name=1><code>rake-jessie</code></tvar> invokes <tvar name=2><code>test</code></tvar> target by running <tvar name=3><code>bundle exec rake test</code></tvar>. |
|||
</translate> |
|||
<translate><!--T:80--> Reference:</translate> ''[[phab:T104024]]'' |
<translate><!--T:80--> Reference:</translate> ''[[phab:T104024]]'' |
||
| Line 325: | Line 350: | ||
<translate> |
<translate> |
||
=== ruby debug tip === <!--T:81--> |
=== ruby debug tip === <!--T:81--> |
||
</translate> |
|||
<translate><!--T:82--> You can use the gem <tvar name=1><code>pry</code></tvar> to break on error and get shown a console in the context of the failure.</translate> |
|||
<!--T:82--> |
|||
You can use the gem <tvar name=1><code>pry</code></tvar> to break on error and get shown a console in the context of the failure.</translate> |
|||
<translate><!--T:83--> To your Gemfile add <tvar name=1><code>gem 'pry'</code></tvar> then to break:</translate> |
<translate><!--T:83--> To your Gemfile add <tvar name=1><code>gem 'pry'</code></tvar> then to break:</translate> |
||
<syntaxhighlight lang=ruby> |
<syntaxhighlight lang=ruby> |
||
require 'pry' |
require 'pry' |
||
| Line 360: | Line 385: | ||
</syntaxhighlight> |
</syntaxhighlight> |
||
<translate> |
|||
<translate><!--T:91--> As you can see, there are three variables, <tvar name=1><code>BROWSER</code></tvar>, <tvar name=2><code>MEDIAWIKI_ENVIRONMENT</code></tvar> and <tvar name=3><code>PLATFORM</code></tvar>.</translate> |
|||
<!--T:91--> |
|||
As you can see, there are three variables, <tvar name=1><code>BROWSER</code></tvar>, <tvar name=2><code>MEDIAWIKI_ENVIRONMENT</code></tvar> and <tvar name=3><code>PLATFORM</code></tvar>. |
|||
<!--T:92--> |
|||
<translate><!--T:92--> <tvar name=1><code>BROWSER</code></tvar> and <tvar name=2><code>PLATFORM</code></tvar> can be any valid [<tvar name=url>https://saucelabs.com/platforms/</tvar> Sauce Labs browser/OS/version combination].</translate> |
|||
<tvar name=1><code>BROWSER</code></tvar> and <tvar name=2><code>PLATFORM</code></tvar> can be any valid [<tvar name=url>https://saucelabs.com/platforms/</tvar> Sauce Labs browser/OS/version combination]. |
|||
<!--T:93--> |
|||
<translate><!--T:93--> <tvar name=1><code>MEDIAWIKI_ENVIRONMENT</code></tvar> can have values <tvar name=2><code>beta</code></tvar>, <tvar name=3><code>mediawiki</code></tvar> and <tvar name=4><code>test</code></tvar>, or any other environment configured in <tvar name=url><code>[[phab:diffusion/EMMV/browse/master/tests/browser/environments.yml|environments.yml]]</code></tvar>.</translate> |
|||
<tvar name=1><code>MEDIAWIKI_ENVIRONMENT</code></tvar> can have values <tvar name=2><code>beta</code></tvar>, <tvar name=3><code>mediawiki</code></tvar> and <tvar name=4><code>test</code></tvar>, or any other environment configured in <tvar name=url><code>[[phab:diffusion/EMMV/browse/master/tests/browser/environments.yml|environments.yml]]</code></tvar>. |
|||
<!--T:94--> |
|||
For example: |
|||
</translate> |
|||
<syntaxhighlight lang=yaml> |
<syntaxhighlight lang=yaml> |
||
| Line 387: | Line 418: | ||
<translate><!--T:95--> Example of a complicated <tvar name=url><code>[[phab:diffusion/EMMV/browse/master/tests/browser/ci.yml|ci.yml]]</code></tvar> is in <tvar name=1><code>mediawiki/extensions/MultimediaViewer</code></tvar>.</translate> |
<translate><!--T:95--> Example of a complicated <tvar name=url><code>[[phab:diffusion/EMMV/browse/master/tests/browser/ci.yml|ci.yml]]</code></tvar> is in <tvar name=1><code>mediawiki/extensions/MultimediaViewer</code></tvar>.</translate> |
||
<translate><!--T:96--> For more information see Jenkins [<tvar name=url>https:// |
<translate><!--T:96--> For more information see Jenkins [<tvar name=url>https://plugins.jenkins.io/yaml-axis/</tvar> Yaml Axis Plugin].</translate> |
||
<translate><!--T:97--> Reference:</translate> |
<translate><!--T:97--> Reference:</translate> |
||
''[[phab:T128190]]'' |
|||
<translate> |
<translate> |
||
== Notes == <!--T:99--> |
== Notes == <!--T:99--> |
||
</translate> |
</translate> |
||
<references /> |
<references /> |
||
[[Category:Continuous integration{{#translation:}}]] |
|||
[[Category:Selenium/Ruby{{#translation:}}]] |
[[Category:Selenium/Ruby{{#translation:}}]] |
||
Latest revision as of 06:19, 11 April 2026
| Continuous integration |
|---|
| Infrastructure |
These entry points allow you to easily define and run tests in your repository. We have standardized tools and entry points that run in WMF CI across over 1000+ Git repositories including MediaWiki core, extensions, PHP libraries, and other standalone projects.
Documentation on how to configure and set up these tools for each programming language can be found further down this page.
| Language | Command runner | Linting | Code style | Static analysis | Unit tests | Documentation |
|---|---|---|---|---|---|---|
| PHP | composer | php-parallel-lint
|
PHP CodeSniffer with mediawiki-tools-codesniffer | Phan with mediawiki-phan-config | PHPUnit | Doxygen |
| JavaScript | npm & grunt | grunt-eslint with eslint-config-wikimedia | grunt-eslint with eslint-config-wikimedia | QUnit | JSDoc | |
| JSON | N/A | json-schema | N/A | N/A | ||
| i18n | grunt-banana-checker | N/A | N/A | N/A | Localisation Message documentation | |
| CSS/LESS | grunt-stylelint with stylelint-config-wikimedia | N/A | N/A | N/A | ||
| Java | maven wrapper (maven) | ? | ? | ? | ? | ? |
| Python | tox | flake8 | unittest
pytest[1] |
sphinx? | ||
| Ruby | bundler | rubocop | ? | rake test | ? | |
List
[edit | edit source]Here is a list of the commands that will be executed by the various Jenkins jobs on a MediaWiki core patch. You can control which tests and linters run for a repository's continuous integration by changing how commands such as these are configured.
api-testing- for API testscomposer phpunit:unit- for PHPUnit tests and parser testscomposer test- for PHP lintersexec vendor/bin/phan- for Phan lintingnpm run-script test- for JavaScript linters, Jest tests, and JSDoc warningsselenium-test- for Selenium tests- Special:JavaScriptTest - for QUnit tests
JavaScript
[edit | edit source]Testing JavaScript
[edit | edit source]We are using npm test as an entry point for linters and some test suites such as Jest.
If your project has any JavaScript files, it should at least have a package.json file which defines a test script, and the related package-lock.json file to ensure consistency in CI runs and security upgrades.
QUnit tests are tested by a Jenkins job that loads a browser and visits Special:JavaScriptTest.
You will need the .eslintrc.json configuration file in your project (see Manual:Coding conventions/JavaScript#Linting).
Look at one of the projects listed in the example section below for an example of these files.
npm commands fails with "node: No such file or directory", you may need to install the "nodejs-legacy" package.Grunt task runner
[edit | edit source]If your project has complex build processes, or is an extension or skin that will benefit from i18n checking and JSON file linting, the convention is to use Grunt as a task runner.
Your project still has a package.json file, which has a dependency on grunt and sets "test": "grunt test".
In turn, a Gruntfile.js file implements grunt test, and this can run a wide variety of tools and tests:
eslint, which checks both JS and JSON files.stylelint, which checks both CSS and LESS files.banana-checker, which checks messages in MediaWiki i18n files.
You can specify configuration settings for these tools in Gruntfile.js.
However, it should contain little to no configuration for tools that can run outside grunt so that they operate the same when run standalone or from a text editor plugin.
Always use native configuration files where possible, including .eslintrc.json mentioned above.
JavaScript documentation
[edit | edit source]Use npm run doc as the entry point.
The convention is to use JSDoc.
The predoc and postdoc script hooks in package.json can be used to run any additional scripts (e.g. build files for inclusion beforehand, or copy additional files for publication afterwards).
Examples
[edit | edit source]Advanced setup using Grunt
[edit | edit source]package.json
[edit | edit source]{
"private": true,
"scripts": {
"test": "grunt test"
},
"devDependencies": {
"eslint-config-wikimedia": "0.15.0",
"grunt": "1.0.4",
"grunt-banana-checker": "0.8.1",
"grunt-eslint": "22.0.0",
"grunt-stylelint": "0.12.0",
"stylelint-config-wikimedia": "0.7.0"
}
}
Gruntfile.js
[edit | edit source]/* eslint-env node, es6 */
module.exports = function ( grunt ) {
grunt.loadNpmTasks( 'grunt-banana-checker' );
grunt.loadNpmTasks( 'grunt-eslint' );
grunt.loadNpmTasks( 'grunt-stylelint' );
grunt.initConfig( {
eslint: {
options: {
extensions: [ '.js', '.json' ],
cache: true
},
all: [
'**/*.{js,json}',
'!{vendor,node_modules}/**'
]
},
stylelint: {
all: [
'**/*.{css,less}',
'!{vendor,node_modules}/**'
]
},
banana: {
all: 'i18n/'
}
} );
grunt.registerTask( 'test', [ 'eslint', 'stylelint', 'banana' ] );
grunt.registerTask( 'default', 'test' );
};
Example projects
[edit | edit source]- Extension:BoilerPlate has a
Gruntfile.jsthat runs jshint, jscs, and banana-checker (for MediaWiki'si18nJSON files). - jquery-client: package.json (jshint, jscs, karma; no Grunt needed)
- CSSJanus: package.json / Gruntfile.js (jshint, jscs, custom test)
- TemplateData: package.json / Gruntfile.js (jshint, jscs, banana-checker)
Further reading
[edit | edit source]- package.json format on docs.npmjs.org
- package "scripts" lifecycle on docs.npmjs.org
PHP
[edit | edit source]- Use the BoilerPlate extension as starting point for a new MediaWiki extension.
- Use the mediawiki-tools-cookiecutter-library for creating a new PHP library.
Testing PHP
[edit | edit source]We are using composer test as an entry point.
If your project has PHP files it should list the test framework packages it needs in composer.json under require-dev and list the commands to be run in the scripts.test property:
{
"require-dev": {
"mediawiki/mediawiki-codesniffer": "48.0.0",
"mediawiki/mediawiki-phan-config": "0.18.0",
"mediawiki/minus-x": "2.0.0",
"ockcyp/covers-validator": "1.6.0",
"php-parallel-lint/php-console-highlighter": "1.0.0",
"php-parallel-lint/php-parallel-lint": "1.4.0",
"phpunit/phpunit": "10.5.58"
},
"scripts": {
"test": [
"parallel-lint . --exclude vendor --exclude node_modules",
"php -d 'extension=pcov.so' vendor/bin/phpunit",
"covers-validator",
"phpcs -sp",
"phan --allow-polyfill-parser --long-progress-bar",
"minus-x check ."
],
"fix": [
"minus-x fix .",
"phpcbf"
]
},
"config": {
"allow-plugins": {
"dealerdirect/phpcodesniffer-composer-installer": true
}
}
}
See composer.json of the cdb project for a good example.
Note that MediaWiki extensions are not standalone projects and cannot run their own PHPUnit test suite from composer.
Those repositories have a separate mediawiki-extensions job.
PHPCS and PHP lint are still run via composer.json and composer test:
{
"require-dev": {
"mediawiki/mediawiki-codesniffer": "44.0.0",
"mediawiki/mediawiki-phan-config": "0.14.0",
"mediawiki/minus-x": "1.1.3",
"php-parallel-lint/php-console-highlighter": "1.0.0",
"php-parallel-lint/php-parallel-lint": "1.4.0"
},
"scripts": {
"test": [
"parallel-lint . --exclude vendor --exclude node_modules",
"phpcs -sp --cache",
"minus-x check ."
],
"fix": [
"minus-x fix .",
"phpcbf"
],
"phan": "phan -d . --long-progress-bar"
},
"config": {
"allow-plugins": {
"dealerdirect/phpcodesniffer-composer-installer": true
}
}
}
See composer.json of the AbuseFilter MediaWiki extension for a good example.
PHP Documentation
[edit | edit source]See: Doxygen.
Use the doxygen program to generate a Doxyfile file in the project root.
Testing Python
[edit | edit source]See Continuous integration/Tutorials/Test your python.
Ruby
[edit | edit source]Rake
[edit | edit source]Use Rake to define your commands, they will be executed via Bundler.
Example Rakefile:
require 'bundler/setup'
require 'rubocop/rake_task'
RuboCop::RakeTask.new(:rubocop) do |task|
# if you use mediawiki-vagrant, rubocop will by default use it's .rubocop.yml
# the next line makes it explicit that you want .rubocop.yml from the directory
# where `bundle exec rake` is executed
task.options = ['-c', '.rubocop.yml']
end
require 'mediawiki_selenium/rake_task'
MediawikiSelenium::RakeTask.new
task default: [:test]
desc 'Run all build/tests commands (CI entry point)'
task test: [:rubocop]
The above code will create following Rake targets.
$ bundle exec rake -T rake rubocop # Run RuboCop rake rubocop:auto_correct # Auto-correct RuboCop offenses rake selenium # Run Cucumber features rake test # Run all build/tests commands (CI entry point)
The Jenkins job rake-jessie invokes test target by running bundle exec rake test.
Reference: phab:T104024
ruby debug tip
[edit | edit source]You can use the gem pry to break on error and get shown a console in the context of the failure.
To your Gemfile add gem 'pry' then to break:
require 'pry'
binding.pry
your call that fail
You will then be in a console before the breakage that let you inspect the environment (ls).
See https://github.com/pry/pry for details.
ci.yml
[edit | edit source]We have a set of Jenkins jobs that run daily and execute Ruby + Selenium tests.
The jobs are named selenium*.
Each repositories has only a single job defined in Jenkins.
It is a multi configuration job that spawns one or more child job based on a configuration in each repositories: tests/browser/ci.yml.
The main job will then spawn child jobs based on its content.
Example of a simple ci.yml is in mediawiki/core.
BROWSER:
- firefox
MEDIAWIKI_ENVIRONMENT:
- beta
PLATFORM:
- Linux
As you can see, there are three variables, BROWSER, MEDIAWIKI_ENVIRONMENT and PLATFORM.
BROWSER and PLATFORM can be any valid Sauce Labs browser/OS/version combination.
MEDIAWIKI_ENVIRONMENT can have values beta, mediawiki and test, or any other environment configured in environments.yml.
For example:
BROWSER:
- chrome
- firefox
- internet_explorer 9.0
- safari
MEDIAWIKI_ENVIRONMENT:
- beta
- mediawiki
- test
PLATFORM:
- Linux
- OS X 10.9
- Windows 8.1
Example of a complicated ci.yml is in mediawiki/extensions/MultimediaViewer.
For more information see Jenkins Yaml Axis Plugin.
Reference: phab:T128190