123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336 |
- <?php
- /**
- * 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
- * @ingroup Testing
- */
- class TestFileReader {
- private $file;
- private $fh;
- private $section = null;
- /** String|null: current test section being analyzed */
- private $sectionData = [];
- private $sectionLineNum = [];
- private $lineNum = 0;
- private $runDisabled;
- private $runParsoid;
- private $regex;
- private $articles = [];
- private $requirements = [];
- private $tests = [];
- public static function read( $file, array $options = [] ) {
- $reader = new self( $file, $options );
- $reader->execute();
- $requirements = [];
- foreach ( $reader->requirements as $type => $reqsOfType ) {
- foreach ( $reqsOfType as $name => $unused ) {
- $requirements[] = [
- 'type' => $type,
- 'name' => $name
- ];
- }
- }
- return [
- 'requirements' => $requirements,
- 'tests' => $reader->tests,
- 'articles' => $reader->articles
- ];
- }
- private function __construct( $file, $options ) {
- $this->file = $file;
- $this->fh = fopen( $this->file, "rt" );
- if ( !$this->fh ) {
- throw new MWException( "Couldn't open file '$file'\n" );
- }
- $options = $options + [
- 'runDisabled' => false,
- 'runParsoid' => false,
- 'regex' => '//',
- ];
- $this->runDisabled = $options['runDisabled'];
- $this->runParsoid = $options['runParsoid'];
- $this->regex = $options['regex'];
- }
- private function addCurrentTest() {
- // "input" and "result" are old section names allowed
- // for backwards-compatibility.
- $input = $this->checkSection( [ 'wikitext', 'input' ], false );
- $nonTidySection = $this->checkSection(
- [ 'html/php', 'html/*', 'html', 'result' ], false );
- // Some tests have "with tidy" and "without tidy" variants
- $tidySection = $this->checkSection( [ 'html/php+tidy', 'html+tidy' ], false );
- // Remove trailing newline
- $data = array_map( 'ParserTestRunner::chomp', $this->sectionData );
- // Apply defaults
- $data += [
- 'options' => '',
- 'config' => ''
- ];
- if ( $input === false ) {
- throw new MWException( "Test at {$this->file}:{$this->sectionLineNum['test']} " .
- "lacks input section" );
- }
- if ( preg_match( '/\\bdisabled\\b/i', $data['options'] ) && !$this->runDisabled ) {
- // Disabled
- return;
- }
- if ( $tidySection === false && $nonTidySection === false ) {
- if ( isset( $data['html/parsoid'] ) || isset( $data['wikitext/edited'] ) ) {
- // Parsoid only
- return;
- } else {
- throw new MWException( "Test at {$this->file}:{$this->sectionLineNum['test']} " .
- "lacks result section" );
- }
- }
- if ( preg_match( '/\\bparsoid\\b/i', $data['options'] ) && $nonTidySection === 'html'
- && !$this->runParsoid
- ) {
- // A test which normally runs on Parsoid but can optionally be run with MW
- return;
- }
- if ( !preg_match( $this->regex, $data['test'] ) ) {
- // Filtered test
- return;
- }
- $commonInfo = [
- 'test' => $data['test'],
- 'desc' => $data['test'],
- 'input' => $data[$input],
- 'options' => $data['options'],
- 'config' => $data['config'],
- 'line' => $this->sectionLineNum['test'],
- 'file' => $this->file
- ];
- if ( $nonTidySection !== false ) {
- // Add non-tidy test
- $this->tests[] = [
- 'result' => $data[$nonTidySection],
- 'resultSection' => $nonTidySection
- ] + $commonInfo;
- if ( $tidySection !== false ) {
- // Add tidy subtest
- $this->tests[] = [
- 'desc' => $data['test'] . ' (with tidy)',
- 'result' => $data[$tidySection],
- 'resultSection' => $tidySection,
- 'options' => $data['options'] . ' tidy',
- 'isSubtest' => true,
- ] + $commonInfo;
- }
- } elseif ( $tidySection !== false ) {
- // No need to override desc when there is no subtest
- $this->tests[] = [
- 'result' => $data[$tidySection],
- 'resultSection' => $tidySection,
- 'options' => $data['options'] . ' tidy'
- ] + $commonInfo;
- } else {
- throw new MWException( "Test at {$this->file}:{$this->sectionLineNum['test']} " .
- "lacks result section" );
- }
- }
- private function execute() {
- while ( false !== ( $line = fgets( $this->fh ) ) ) {
- $this->lineNum++;
- $matches = [];
- if ( preg_match( '/^!!\s*(\S+)/', $line, $matches ) ) {
- $this->section = strtolower( $matches[1] );
- if ( $this->section == 'endarticle' ) {
- $this->checkSection( 'text' );
- $this->checkSection( 'article' );
- $this->addArticle(
- ParserTestRunner::chomp( $this->sectionData['article'] ),
- $this->sectionData['text'], $this->lineNum );
- $this->clearSection();
- continue;
- }
- if ( $this->section == 'endhooks' ) {
- $this->checkSection( 'hooks' );
- foreach ( explode( "\n", $this->sectionData['hooks'] ) as $line ) {
- $line = trim( $line );
- if ( $line ) {
- $this->addRequirement( 'hook', $line );
- }
- }
- $this->clearSection();
- continue;
- }
- if ( $this->section == 'endfunctionhooks' ) {
- $this->checkSection( 'functionhooks' );
- foreach ( explode( "\n", $this->sectionData['functionhooks'] ) as $line ) {
- $line = trim( $line );
- if ( $line ) {
- $this->addRequirement( 'functionHook', $line );
- }
- }
- $this->clearSection();
- continue;
- }
- if ( $this->section == 'endtransparenthooks' ) {
- $this->checkSection( 'transparenthooks' );
- foreach ( explode( "\n", $this->sectionData['transparenthooks'] ) as $line ) {
- $line = trim( $line );
- if ( $line ) {
- $this->addRequirement( 'transparentHook', $line );
- }
- }
- $this->clearSection();
- continue;
- }
- if ( $this->section == 'end' ) {
- $this->checkSection( 'test' );
- $this->addCurrentTest();
- $this->clearSection();
- continue;
- }
- if ( isset( $this->sectionData[$this->section] ) ) {
- throw new MWException( "duplicate section '$this->section' "
- . "at line {$this->lineNum} of $this->file\n" );
- }
- $this->sectionLineNum[$this->section] = $this->lineNum;
- $this->sectionData[$this->section] = '';
- continue;
- }
- if ( $this->section ) {
- $this->sectionData[$this->section] .= $line;
- }
- }
- }
- /**
- * Clear section name and its data
- */
- private function clearSection() {
- $this->sectionLineNum = [];
- $this->sectionData = [];
- $this->section = null;
- }
- /**
- * Verify the current section data has some value for the given token
- * name(s) (first parameter).
- * Throw an exception if it is not set, referencing current section
- * and adding the current file name and line number
- *
- * @param string|array $tokens Expected token(s) that should have been
- * mentioned before closing this section
- * @param bool $fatal True iff an exception should be thrown if
- * the section is not found.
- * @return bool|string
- * @throws MWException
- */
- private function checkSection( $tokens, $fatal = true ) {
- if ( is_null( $this->section ) ) {
- throw new MWException( __METHOD__ . " can not verify a null section!\n" );
- }
- if ( !is_array( $tokens ) ) {
- $tokens = [ $tokens ];
- }
- if ( count( $tokens ) == 0 ) {
- throw new MWException( __METHOD__ . " can not verify zero sections!\n" );
- }
- $data = $this->sectionData;
- $tokens = array_filter( $tokens, function ( $token ) use ( $data ) {
- return isset( $data[$token] );
- } );
- if ( count( $tokens ) == 0 ) {
- if ( !$fatal ) {
- return false;
- }
- throw new MWException( sprintf(
- "'%s' without '%s' at line %s of %s\n",
- $this->section,
- implode( ',', $tokens ),
- $this->lineNum,
- $this->file
- ) );
- }
- if ( count( $tokens ) > 1 ) {
- throw new MWException( sprintf(
- "'%s' with unexpected tokens '%s' at line %s of %s\n",
- $this->section,
- implode( ',', $tokens ),
- $this->lineNum,
- $this->file
- ) );
- }
- return array_values( $tokens )[0];
- }
- private function addArticle( $name, $text, $line ) {
- $this->articles[] = [
- 'name' => $name,
- 'text' => $text,
- 'line' => $line,
- 'file' => $this->file
- ];
- }
- private function addRequirement( $type, $name ) {
- $this->requirements[$type][$name] = true;
- }
- }
|