<?php
-
/**
- * @ingroup Testing
+ * Recording for passing/failing tests.
+ *
+ * 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.
*
- * Set of classes to help with test output and such. Right now pretty specific
- * to the parser tests but could be more useful one day :)
+ * 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.
*
- * @todo Fixme: Make this more generic
+ * 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 AnsiTermColorer {
- function __construct() {
- }
-
- /**
- * Return ANSI terminal escape code for changing text attribs/color
- *
- * @param $color String: semicolon-separated list of attribute/color codes
- * @return String
- */
- public function color( $color ) {
- global $wgCommandLineDarkBg;
-
- $light = $wgCommandLineDarkBg ? "1;" : "0;";
-
- return "\x1b[{$light}{$color}m";
- }
-
- /**
- * Return ANSI terminal escape code for restoring default text attributes
- *
- * @return String
- */
- public function reset() {
- return $this->color( 0 );
- }
-}
-
-/* A colour-less terminal */
-class DummyTermColorer {
- public function color( $color ) {
- return '';
- }
-
- public function reset() {
- return '';
- }
-}
-
class TestRecorder {
var $parent;
var $term;
* and all that fun stuff
*/
function start() {
- $this->db->begin();
+ $this->db->begin( __METHOD__ );
if ( ! $this->db->tableExists( 'testrun' )
|| ! $this->db->tableExists( 'testitem' ) )
private $index = 0;
private $test;
private $section = null; /** String|null: current test section being analyzed */
+ private $sectionData = array();
private $lineNum;
private $eof;
function __construct( $file, $parserTest ) {
- global $IP;
-
$this->file = $file;
$this->fh = fopen( $this->file, "rt" );
}
$this->parserTest = $parserTest;
- $this->parserTest->showRunFile( wfRelativePath( $this->file, $IP ) );
$this->lineNum = $this->index = 0;
}
}
function readNextTest() {
- $data = array();
- $this->section = null;
+ $this->clearSection();
+
+ # Create a fake parser tests which never run anything unless
+ # asked to do so. This will avoid running hooks for a disabled test
+ $delayedParserTest = new DelayedParserTest();
while ( false !== ( $line = fgets( $this->fh ) ) ) {
$this->lineNum++;
$this->section = strtolower( $matches[1] );
if ( $this->section == 'endarticle' ) {
- $this->checkSection( $data, 'text' );
- $this->checkSection( $data, 'article' );
+ $this->checkSection( 'text' );
+ $this->checkSection( 'article' );
- $this->parserTest->addArticle( ParserTest::chomp( $data['article'] ), $data['text'], $this->lineNum );
+ $this->parserTest->addArticle( ParserTest::chomp( $this->sectionData['article'] ), $this->sectionData['text'], $this->lineNum );
- $data = array();
- $this->section = null;
+ $this->clearSection();
continue;
}
if ( $this->section == 'endhooks' ) {
- $this->checkSection( $data, 'hooks' );
+ $this->checkSection( 'hooks' );
- foreach ( explode( "\n", $data['hooks'] ) as $line ) {
+ foreach ( explode( "\n", $this->sectionData['hooks'] ) as $line ) {
$line = trim( $line );
if ( $line ) {
- if ( !$this->parserTest->requireHook( $line ) ) {
- return false;
- }
+ $delayedParserTest->requireHook( $line );
}
}
- $data = array();
- $this->section = null;
+ $this->clearSection();
continue;
}
if ( $this->section == 'endfunctionhooks' ) {
- $this->checkSection( $data, 'functionhooks' );
+ $this->checkSection( 'functionhooks' );
- foreach ( explode( "\n", $data['functionhooks'] ) as $line ) {
+ foreach ( explode( "\n", $this->sectionData['functionhooks'] ) as $line ) {
$line = trim( $line );
if ( $line ) {
- if ( !$this->parserTest->requireFunctionHook( $line ) ) {
- return false;
- }
+ $delayedParserTest->requireFunctionHook( $line );
}
}
- $data = array();
- $this->section = null;
+ $this->clearSection();
continue;
}
if ( $this->section == 'end' ) {
- $this->checkSection( $data, 'test' );
- $this->checkSection( $data, 'input' );
- $this->checkSection( $data, 'result' );
+ $this->checkSection( 'test' );
+ $this->checkSection( 'input' );
+ $this->checkSection( 'result' );
- if ( !isset( $data['options'] ) ) {
- $data['options'] = '';
+ if ( !isset( $this->sectionData['options'] ) ) {
+ $this->sectionData['options'] = '';
}
- if ( !isset( $data['config'] ) )
- $data['config'] = '';
+ if ( !isset( $this->sectionData['config'] ) ) {
+ $this->sectionData['config'] = '';
+ }
- if ( ( ( preg_match( '/\\bdisabled\\b/i', $data['options'] ) && !$this->parserTest->runDisabled )
- || !preg_match( "/" . $this->parserTest->regex . "/i", $data['test'] ) ) ) {
+ if ( ( ( preg_match( '/\\bdisabled\\b/i', $this->sectionData['options'] ) && !$this->parserTest->runDisabled )
+ || !preg_match( "/" . $this->parserTest->regex . "/i", $this->sectionData['test'] ) ) ) {
# disabled test
- $data = array();
- $this->section = null;
+ $this->clearSection();
+
+ # Forget any pending hooks call since test is disabled
+ $delayedParserTest->reset();
continue;
}
+ # We are really going to run the test, run pending hooks and hooks function
+ wfDebug( __METHOD__ . " unleashing delayed test for: {$this->sectionData['test']}" );
+ $hooksResult = $delayedParserTest->unleash( $this->parserTest );
+ if( !$hooksResult ) {
+ # Some hook reported an issue. Abort.
+ return false;
+ }
+
$this->test = array(
- 'test' => ParserTest::chomp( $data['test'] ),
- 'input' => ParserTest::chomp( $data['input'] ),
- 'result' => ParserTest::chomp( $data['result'] ),
- 'options' => ParserTest::chomp( $data['options'] ),
- 'config' => ParserTest::chomp( $data['config'] ) );
+ 'test' => ParserTest::chomp( $this->sectionData['test'] ),
+ 'input' => ParserTest::chomp( $this->sectionData['input'] ),
+ 'result' => ParserTest::chomp( $this->sectionData['result'] ),
+ 'options' => ParserTest::chomp( $this->sectionData['options'] ),
+ 'config' => ParserTest::chomp( $this->sectionData['config'] ),
+ );
return true;
}
- if ( isset ( $data[$this->section] ) ) {
- throw new MWException( "duplicate section '$section' at line {$this->lineNum} of $this->file\n" );
+ if ( isset ( $this->sectionData[$this->section] ) ) {
+ throw new MWException( "duplicate section '$this->section' at line {$this->lineNum} of $this->file\n" );
}
- $data[$this->section] = '';
+ $this->sectionData[$this->section] = '';
continue;
}
if ( $this->section ) {
- $data[$this->section] .= $line;
+ $this->sectionData[$this->section] .= $line;
}
}
return false;
}
+
+ /**
+ * Clear section name and its data
+ */
+ private function clearSection() {
+ $this->sectionData = array();
+ $this->section = null;
+
+ }
+
/**
- * Verify the first parameter array ($data) has a value for the second
- * parameter key name ($token).
+ * Verify the current section data has some value for the given token
+ * name (first parameter).
* Throw an exception if it is not set, referencing current section
* and adding the current file name and line number
*
- * @param $data Array: an array of parser test data. See readNextTest()
- * @param $token String: expected token that should have been mentionned before closing this section
+ * @param $token String: expected token that should have been mentionned before closing this section
*/
- private function checkSection( $data, $token ) {
+ private function checkSection( $token ) {
if( is_null( $this->section ) ) {
- throw new MWException( __METHOD__ . " could not verify a null section!\n" );
+ throw new MWException( __METHOD__ . " can not verify a null section!\n" );
}
- if( !isset($data[$token]) ) {
+ if( !isset($this->sectionData[$token]) ) {
throw new MWException( sprintf(
"'%s' without '%s' at line %s of %s\n",
$this->section,
return true;
}
}
+
+/**
+ * A class to delay execution of a parser test hooks.
+ */
+class DelayedParserTest {
+
+ /** Initialized on construction */
+ private $hooks;
+ private $fnHooks;
+
+ public function __construct() {
+ $this->reset();
+ }
+
+ /**
+ * Init/reset or forgot about the current delayed test.
+ * Call to this will erase any hooks function that were pending.
+ */
+ public function reset() {
+ $this->hooks = array();
+ $this->fnHooks = array();
+ }
+
+ /**
+ * Called whenever we actually want to run the hook.
+ * Should be the case if we found the parserTest is not disabled
+ */
+ public function unleash( &$parserTest ) {
+ if( !($parserTest instanceof ParserTest || $parserTest instanceof NewParserTest
+ ) ) {
+ throw new MWException( __METHOD__ . " must be passed an instance of ParserTest or NewParserTest classes\n" );
+ }
+
+ # Trigger delayed hooks. Any failure will make us abort
+ foreach( $this->hooks as $hook ) {
+ $ret = $parserTest->requireHook( $hook );
+ if( !$ret ) {
+ return false;
+ }
+ }
+
+ # Trigger delayed function hooks. Any failure will make us abort
+ foreach( $this->fnHooks as $fnHook ) {
+ $ret = $parserTest->requireFunctionHook( $fnHook );
+ if( !$ret ) {
+ return false;
+ }
+ }
+
+ # Delayed execution was successful.
+ return true;
+ }
+
+ /**
+ * Similar to ParserTest object but does not run anything
+ * Use unleash() to really execute the hook
+ */
+ public function requireHook( $hook ) {
+ $this->hooks[] = $hook;
+ }
+ /**
+ * Similar to ParserTest object but does not run anything
+ * Use unleash() to really execute the hook function
+ */
+ public function requireFunctionHook( $fnHook ) {
+ $this->fnHooks[] = $fnHook;
+ }
+
+}