* Stabilise timestamps generated by the parser to avoid diff test false positives
* Fixed msgnw bug. Use RECOVER_ORIG.
* Fixed editintro bug. Cloning the parser in MessageCache has some side-effects that need to be corrected.
* Fixed typo in Parser_DiffTest.php
* General improvements to preprocessorFuzzTest.php
* Fixed breakage of XML output feature in Special:ExpandTemplates
# Persistent:
var $mTagHooks, $mTransparentTagHooks, $mFunctionHooks, $mFunctionSynonyms, $mVariables,
$mImageParams, $mImageParamsMagicArray, $mStripList, $mMarkerSuffix,
- $mExtLinkBracketedRegex, $mPreprocessor, $mDefaultStripList;
+ $mExtLinkBracketedRegex, $mPreprocessor, $mDefaultStripList, $mVarCache, $mConf;
# Cleared with clearState():
* @public
*/
function __construct( $conf = array() ) {
+ $this->mConf = $conf;
$this->mTagHooks = array();
$this->mTransparentTagHooks = array();
$this->mFunctionHooks = array();
$this->mMarkerSuffix = "-QINU\x7f";
$this->mExtLinkBracketedRegex = '/\[(\b(' . wfUrlProtocols() . ')'.
'[^][<>"\\x00-\\x20\\x7F]+) *([^\]\\x0a\\x0d]*?)\]/S';
+ $this->mVarCache = array();
if ( isset( $conf['preprocessorClass'] ) ) {
$this->mPreprocessorClass = $conf['preprocessorClass'];
} else {
* the behaviour of <nowiki> in a link.
*/
#$this->mUniqPrefix = "\x07UNIQ" . Parser::getRandomString();
+ # Changed to \x7f to allow XML double-parsing -- TS
$this->mUniqPrefix = "\x7fUNIQ" . Parser::getRandomString();
# Clear these on every parse, bug 4549
$this->mDefaultSort = false;
$this->mHeadings = array();
+ # Fix cloning
+ if ( isset( $this->mPreprocessor ) && $this->mPreprocessor->parser !== $this ) {
+ $this->mPreprocessor = null;
+ }
+
wfRunHooks( 'ParserClearState', array( &$this ) );
wfProfileOut( __METHOD__ );
}
* Some of these require message or data lookups and can be
* expensive to check many times.
*/
- static $varCache = array();
- if ( wfRunHooks( 'ParserGetVariableValueVarCache', array( &$this, &$varCache ) ) ) {
- if ( isset( $varCache[$index] ) ) {
- return $varCache[$index];
+ if ( wfRunHooks( 'ParserGetVariableValueVarCache', array( &$this, &$this->mVarCache ) ) ) {
+ if ( isset( $this->mVarCache[$index] ) ) {
+ return $this->mVarCache[$index];
}
}
- $ts = time();
+ $ts = wfTimestamp( TS_UNIX, $this->mOptions->getTimestamp() );
wfRunHooks( 'ParserGetVariableValueTs', array( &$this, &$ts ) );
# Use the time zone
switch ( $index ) {
case 'currentmonth':
- return $varCache[$index] = $wgContLang->formatNum( gmdate( 'm', $ts ) );
+ return $this->mVarCache[$index] = $wgContLang->formatNum( gmdate( 'm', $ts ) );
case 'currentmonthname':
- return $varCache[$index] = $wgContLang->getMonthName( gmdate( 'n', $ts ) );
+ return $this->mVarCache[$index] = $wgContLang->getMonthName( gmdate( 'n', $ts ) );
case 'currentmonthnamegen':
- return $varCache[$index] = $wgContLang->getMonthNameGen( gmdate( 'n', $ts ) );
+ return $this->mVarCache[$index] = $wgContLang->getMonthNameGen( gmdate( 'n', $ts ) );
case 'currentmonthabbrev':
- return $varCache[$index] = $wgContLang->getMonthAbbreviation( gmdate( 'n', $ts ) );
+ return $this->mVarCache[$index] = $wgContLang->getMonthAbbreviation( gmdate( 'n', $ts ) );
case 'currentday':
- return $varCache[$index] = $wgContLang->formatNum( gmdate( 'j', $ts ) );
+ return $this->mVarCache[$index] = $wgContLang->formatNum( gmdate( 'j', $ts ) );
case 'currentday2':
- return $varCache[$index] = $wgContLang->formatNum( gmdate( 'd', $ts ) );
+ return $this->mVarCache[$index] = $wgContLang->formatNum( gmdate( 'd', $ts ) );
case 'localmonth':
- return $varCache[$index] = $wgContLang->formatNum( $localMonth );
+ return $this->mVarCache[$index] = $wgContLang->formatNum( $localMonth );
case 'localmonthname':
- return $varCache[$index] = $wgContLang->getMonthName( $localMonthName );
+ return $this->mVarCache[$index] = $wgContLang->getMonthName( $localMonthName );
case 'localmonthnamegen':
- return $varCache[$index] = $wgContLang->getMonthNameGen( $localMonthName );
+ return $this->mVarCache[$index] = $wgContLang->getMonthNameGen( $localMonthName );
case 'localmonthabbrev':
- return $varCache[$index] = $wgContLang->getMonthAbbreviation( $localMonthName );
+ return $this->mVarCache[$index] = $wgContLang->getMonthAbbreviation( $localMonthName );
case 'localday':
- return $varCache[$index] = $wgContLang->formatNum( $localDay );
+ return $this->mVarCache[$index] = $wgContLang->formatNum( $localDay );
case 'localday2':
- return $varCache[$index] = $wgContLang->formatNum( $localDay2 );
+ return $this->mVarCache[$index] = $wgContLang->formatNum( $localDay2 );
case 'pagename':
return wfEscapeWikiText( $this->mTitle->getText() );
case 'pagenamee':
case 'subjectspacee':
return( wfUrlencode( $this->mTitle->getSubjectNsText() ) );
case 'currentdayname':
- return $varCache[$index] = $wgContLang->getWeekdayName( gmdate( 'w', $ts ) + 1 );
+ return $this->mVarCache[$index] = $wgContLang->getWeekdayName( gmdate( 'w', $ts ) + 1 );
case 'currentyear':
- return $varCache[$index] = $wgContLang->formatNum( gmdate( 'Y', $ts ), true );
+ return $this->mVarCache[$index] = $wgContLang->formatNum( gmdate( 'Y', $ts ), true );
case 'currenttime':
- return $varCache[$index] = $wgContLang->time( wfTimestamp( TS_MW, $ts ), false, false );
+ return $this->mVarCache[$index] = $wgContLang->time( wfTimestamp( TS_MW, $ts ), false, false );
case 'currenthour':
- return $varCache[$index] = $wgContLang->formatNum( gmdate( 'H', $ts ), true );
+ return $this->mVarCache[$index] = $wgContLang->formatNum( gmdate( 'H', $ts ), true );
case 'currentweek':
// @bug 4594 PHP5 has it zero padded, PHP4 does not, cast to
// int to remove the padding
- return $varCache[$index] = $wgContLang->formatNum( (int)gmdate( 'W', $ts ) );
+ return $this->mVarCache[$index] = $wgContLang->formatNum( (int)gmdate( 'W', $ts ) );
case 'currentdow':
- return $varCache[$index] = $wgContLang->formatNum( gmdate( 'w', $ts ) );
+ return $this->mVarCache[$index] = $wgContLang->formatNum( gmdate( 'w', $ts ) );
case 'localdayname':
- return $varCache[$index] = $wgContLang->getWeekdayName( $localDayOfWeek + 1 );
+ return $this->mVarCache[$index] = $wgContLang->getWeekdayName( $localDayOfWeek + 1 );
case 'localyear':
- return $varCache[$index] = $wgContLang->formatNum( $localYear, true );
+ return $this->mVarCache[$index] = $wgContLang->formatNum( $localYear, true );
case 'localtime':
- return $varCache[$index] = $wgContLang->time( $localTimestamp, false, false );
+ return $this->mVarCache[$index] = $wgContLang->time( $localTimestamp, false, false );
case 'localhour':
- return $varCache[$index] = $wgContLang->formatNum( $localHour, true );
+ return $this->mVarCache[$index] = $wgContLang->formatNum( $localHour, true );
case 'localweek':
// @bug 4594 PHP5 has it zero padded, PHP4 does not, cast to
// int to remove the padding
- return $varCache[$index] = $wgContLang->formatNum( (int)$localWeek );
+ return $this->mVarCache[$index] = $wgContLang->formatNum( (int)$localWeek );
case 'localdow':
- return $varCache[$index] = $wgContLang->formatNum( $localDayOfWeek );
+ return $this->mVarCache[$index] = $wgContLang->formatNum( $localDayOfWeek );
case 'numberofarticles':
- return $varCache[$index] = $wgContLang->formatNum( SiteStats::articles() );
+ return $this->mVarCache[$index] = $wgContLang->formatNum( SiteStats::articles() );
case 'numberoffiles':
- return $varCache[$index] = $wgContLang->formatNum( SiteStats::images() );
+ return $this->mVarCache[$index] = $wgContLang->formatNum( SiteStats::images() );
case 'numberofusers':
- return $varCache[$index] = $wgContLang->formatNum( SiteStats::users() );
+ return $this->mVarCache[$index] = $wgContLang->formatNum( SiteStats::users() );
case 'numberofpages':
- return $varCache[$index] = $wgContLang->formatNum( SiteStats::pages() );
+ return $this->mVarCache[$index] = $wgContLang->formatNum( SiteStats::pages() );
case 'numberofadmins':
- return $varCache[$index] = $wgContLang->formatNum( SiteStats::admins() );
+ return $this->mVarCache[$index] = $wgContLang->formatNum( SiteStats::admins() );
case 'numberofedits':
- return $varCache[$index] = $wgContLang->formatNum( SiteStats::edits() );
+ return $this->mVarCache[$index] = $wgContLang->formatNum( SiteStats::edits() );
case 'currenttimestamp':
- return $varCache[$index] = wfTimestampNow();
+ return $this->mVarCache[$index] = wfTimestamp( TS_MW, $ts );
case 'localtimestamp':
- return $varCache[$index] = $localTimestamp;
+ return $this->mVarCache[$index] = $localTimestamp;
case 'currentversion':
- return $varCache[$index] = SpecialVersion::getVersion();
+ return $this->mVarCache[$index] = SpecialVersion::getVersion();
case 'sitename':
return $wgSitename;
case 'server':
return $wgContLanguageCode;
default:
$ret = null;
- if ( wfRunHooks( 'ParserGetVariableValueSwitch', array( &$this, &$varCache, &$index, &$ret ) ) )
+ if ( wfRunHooks( 'ParserGetVariableValueSwitch', array( &$this, &$this->mVarCache, &$index, &$ret ) ) )
return $ret;
else
return null;
# Clean up argument array
$newFrame = $frame->newChild( $args, $title );
- if ( $titleText !== false && $newFrame->isEmpty() ) {
+ if ( $nowiki ) {
+ $text = $newFrame->expand( $text, PPFrame::RECOVER_ORIG );
+ } elseif ( $titleText !== false && $newFrame->isEmpty() ) {
# Expansion is eligible for the empty-frame cache
if ( isset( $this->mTplExpandCache[$titleText] ) ) {
$text = $this->mTplExpandCache[$titleText];
$text = $newFrame->expand( $text );
}
}
+ if ( $isLocalObj && $nowiki ) {
+ $text = $frame->expand( $text, PPFrame::RECOVER_ORIG );
+ $isLocalObj = false;
+ }
# Replace raw HTML by a placeholder
# Add a blank line preceding, to prevent it from mucking up
$oldtz = getenv( 'TZ' );
putenv( 'TZ='.$wgLocaltimezone );
}
- $d = $wgContLang->timeanddate( date( 'YmdHis' ), false, false) .
+ $d = $wgContLang->timeanddate( $this->mOptions->getTimestamp(), false, false) .
' (' . date( 'T' ) . ')';
if ( isset( $wgLocaltimezone ) ) {
putenv( 'TZ='.$oldtz );
*/
function testSrvus( $text, $title, $options, $outputType = self::OT_HTML ) {
$this->clearState();
+ if ( ! ( $title instanceof Title ) ) {
+ $title = Title::newFromText( $title );
+ }
$this->mTitle = $title;
$this->mOptions = $options;
$this->setOutputType( $outputType );
function testPst( $text, $title, $options ) {
global $wgUser;
+ if ( ! ( $title instanceof Title ) ) {
+ $title = Title::newFromText( $title );
+ }
return $this->preSaveTransform( $text, $title, $wgUser, $options );
}
function testPreprocess( $text, $title, $options ) {
+ if ( ! ( $title instanceof Title ) ) {
+ $title = Title::newFromText( $title );
+ }
return $this->testSrvus( $text, $title, $options, self::OT_PREPROCESS );
}
}
var $mRemoveComments; # Remove HTML comments. ONLY APPLIES TO PREPROCESS OPERATIONS
var $mTemplateCallback; # Callback for template fetching
var $mEnableLimitReport; # Enable limit report in an HTML comment on output
+ var $mTimestamp; # Timestamp used for {{CURRENTDAY}} etc.
var $mUser; # Stored user object, just used to initialise the skin
return $this->mDateFormat;
}
+ function getTimestamp() {
+ if ( !isset( $this->mTimestamp ) ) {
+ $this->mTimestamp = wfTimestampNow();
+ }
+ return $this->mTimestamp;
+ }
+
function setUseTeX( $x ) { return wfSetVar( $this->mUseTeX, $x ); }
function setUseDynamicDates( $x ) { return wfSetVar( $this->mUseDynamicDates, $x ); }
function setInterwikiMagic( $x ) { return wfSetVar( $this->mInterwikiMagic, $x ); }
function setRemoveComments( $x ) { return wfSetVar( $this->mRemoveComments, $x ); }
function setTemplateCallback( $x ) { return wfSetVar( $this->mTemplateCallback, $x ); }
function enableLimitReport( $x = true ) { return wfSetVar( $this->mEnableLimitReport, $x ); }
+ function setTimestamp( $x ) { return wfSetVar( $this->mTimestamp, $x ); }
function __construct( $user = null ) {
$this->initialiseFromUser( $user );
foreach ( $this->conf['parsers'] as $i => $parserConf ) {
if ( !is_array( $parserConf ) ) {
$class = $parserConf;
- $parserconf = array( 'class' => $parserConf );
+ $parserConf = array( 'class' => $parserConf );
} else {
$class = $parserConf['class'];
}
<?php
class Preprocessor_DOM implements Preprocessor {
- var $parser;
+ var $parser, $memoryLimit;
function __construct( $parser ) {
$this->parser = $parser;
+ $mem = ini_get( 'memory_limit' );
+ $this->memoryLimit = false;
+ if ( strval( $mem ) !== '' && $mem != -1 ) {
+ if ( preg_match( '/^\d+$/', $mem ) ) {
+ $this->memoryLimit = $mem;
+ } elseif ( preg_match( '/^(\d+)M$/i', $mem, $m ) ) {
+ $this->memoryLimit = $m[1] * 1048576;
+ }
+ }
}
function newFrame() {
return new PPFrame_DOM( $this );
}
+ function memCheck() {
+ if ( $this->memoryLimit === false ) {
+ return;
+ }
+ $usage = memory_get_usage();
+ if ( $usage > $this->memoryLimit * 0.9 ) {
+ $limit = intval( $this->memoryLimit * 0.9 / 1048576 + 0.5 );
+ throw new MWException( "Preprocessor hit 90% memory limit ($limit MB)" );
+ }
+ return $usage <= $this->memoryLimit * 0.8;
+ }
+
/**
* Preprocess some wikitext and return the document tree.
* This is the ghost of Parser::replace_variables().
$stack = new PPDStack;
- $searchBase = '[{<';
+ $searchBase = '[{<'; #}
$revText = strrev( $text ); // For fast reverse searches
$i = 0; # Input pointer, starts out pointing to a pseudo-newline before the start
- $accum =& $stack->getAccum(); # Current text accumulator
+ $accum =& $stack->getAccum(); # Current accumulator
$accum = '<root>';
$findEquals = false; # True to find equals signs in arguments
$findPipe = false; # True to take notice of pipe characters
$fakeLineStart = true; # Do a line-start run without outputting an LF character
while ( true ) {
+ if ( ! ($i % 10) ) $this->memCheck();
+
if ( $findOnlyinclude ) {
// Ignore all input up to the next <onlyinclude>
$startPos = strpos( $text, '<onlyinclude>', $i );
$endPos += 2;
}
+ /*
+ if ( $stack->top ) {
+ if ( $stack->top->commentEndPos !== false && $stack->top->commentEndPos == $wsStart ) {
+ // Comments abutting, no change in visual end
+ $stack->top->commentEndPos = $wsEnd;
+ } else {
+ $stack->top->visualEndPos = $wsStart;
+ $stack->top->commentEndPos = $wsEnd;
+ }
+ }
+ */
$i = $endPos + 1;
$inner = substr( $text, $startPos, $endPos - $startPos + 1 );
$accum .= '<comment>' . htmlspecialchars( $inner ) . '</comment>';
$piece = array(
'open' => "\n",
'close' => "\n",
- 'parts' => array( str_repeat( '=', $count ) ),
+ 'parts' => array( new PPDPart( str_repeat( '=', $count ) ) ),
'startPos' => $i,
'count' => $count );
$stack->push( $piece );
'open' => $curChar,
'close' => $rule['end'],
'count' => $count,
- 'parts' => array( '' ),
- 'eqpos' => array(),
'lineStart' => ($i > 0 && $text[$i-1] == "\n"),
);
$name = $rule['names'][$matchingCount];
if ( $name === null ) {
// No element, just literal text
- $element = str_repeat( $piece->open, $matchingCount ) .
- implode( '|', $piece->parts ) .
- str_repeat( $rule['end'], $matchingCount );
+ $element = $piece->breakSyntax( $matchingCount ) . str_repeat( $rule['end'], $matchingCount );
} else {
# Create XML element
# Note: $parts is already XML, does not need to be encoded further
$parts = $piece->parts;
- $title = $parts[0];
+ $title = $parts[0]->out;
unset( $parts[0] );
# The invocation is at the start of the line if lineStart is set in
$element .= "<title>$title</title>";
$argIndex = 1;
foreach ( $parts as $partIndex => $part ) {
- if ( isset( $piece->eqpos[$partIndex] ) ) {
- $eqpos = $piece->eqpos[$partIndex];
- $argName = substr( $part, 0, $eqpos );
- $argValue = substr( $part, $eqpos + 1 );
+ if ( isset( $part->eqpos ) ) {
+ $argName = substr( $part->out, 0, $part->eqpos );
+ $argValue = substr( $part->out, $part->eqpos + 1 );
$element .= "<part><name>$argName</name>=<value>$argValue</value></part>";
} else {
- $element .= "<part><name index=\"$argIndex\" /><value>$part</value></part>";
+ $element .= "<part><name index=\"$argIndex\" /><value>{$part->out}</value></part>";
$argIndex++;
}
}
# Re-add the old stack element if it still has unmatched opening characters remaining
if ($matchingCount < $piece->count) {
- $piece->parts = array( '' );
+ $piece->parts = array( new PPDPart );
$piece->count -= $matchingCount;
- $piece->eqpos = array();
# do we still qualify for any callback with remaining count?
$names = $rules[$piece->open]['names'];
$skippedBraces = 0;
elseif ( $found == 'pipe' ) {
$findEquals = true; // shortcut for getFlags()
- $stack->top->addPart();
+ $stack->addPart();
$accum =& $stack->getAccum();
++$i;
}
elseif ( $found == 'equals' ) {
$findEquals = false; // shortcut for getFlags()
- $partsCount = count( $stack->top->parts );
- $stack->top->eqpos[$partsCount - 1] = strlen( $accum );
+ $stack->getCurrentPart()->eqpos = strlen( $accum );
$accum .= '=';
++$i;
}
# Output any remaining unclosed brackets
foreach ( $stack->stack as $piece ) {
- if ( $piece->open == "\n" ) {
- $stack->topAccum .= $piece->parts[0];
- } else {
- $stack->topAccum .= str_repeat( $piece->open, $piece->count ) . implode( '|', $piece->parts );
- }
+ $stack->rootAccum .= $piece->breakSyntax();
}
- $stack->topAccum .= '</root>';
- $xml = $stack->topAccum;
+ $stack->rootAccum .= '</root>';
+ $xml = $stack->rootAccum;
wfProfileOut( __METHOD__.'-makexml' );
wfProfileIn( __METHOD__.'-loadXML' );
}
}
+/**
+ * Stack class to help Preprocessor::preprocessToObj()
+ */
+class PPDStack {
+ var $stack, $rootAccum, $top;
+ var $out;
+ static $false = false;
+
+ function __construct() {
+ $this->stack = array();
+ $this->top = false;
+ $this->rootAccum = '';
+ $this->accum =& $this->rootAccum;
+ }
+
+ function count() {
+ return count( $this->stack );
+ }
+
+ function &getAccum() {
+ return $this->accum;
+ }
+
+ function getCurrentPart() {
+ if ( $this->top === false ) {
+ return false;
+ } else {
+ return $this->top->getCurrentPart();
+ }
+ }
+
+ function push( $data ) {
+ if ( $data instanceof PPDStackElement ) {
+ $this->stack[] = $data;
+ } else {
+ $this->stack[] = new PPDStackElement( $data );
+ }
+ $this->top = $this->stack[ count( $this->stack ) - 1 ];
+ $this->accum =& $this->top->getAccum();
+ }
+
+ function pop() {
+ if ( !count( $this->stack ) ) {
+ throw new MWException( __METHOD__.': no elements remaining' );
+ }
+ $temp = array_pop( $this->stack );
+
+ if ( count( $this->stack ) ) {
+ $this->top = $this->stack[ count( $this->stack ) - 1 ];
+ $this->accum =& $this->top->getAccum();
+ } else {
+ $this->top = self::$false;
+ $this->accum =& $this->rootAccum;
+ }
+ return $temp;
+ }
+
+ function addPart( $s = '' ) {
+ $this->top->addPart( $s );
+ $this->accum =& $this->top->getAccum();
+ }
+
+ function getFlags() {
+ if ( !count( $this->stack ) ) {
+ return array(
+ 'findEquals' => false,
+ 'findPipe' => false,
+ 'inHeading' => false,
+ );
+ } else {
+ return $this->top->getFlags();
+ }
+ }
+}
+
+class PPDStackElement {
+ var $open, // Opening character (\n for heading)
+ $close, // Matching closing character
+ $count, // Number of opening characters found (number of "=" for heading)
+ $parts, // Array of PPDPart objects describing pipe-separated parts.
+ $lineStart; // True if the open char appeared at the start of the input line. Not set for headings.
+
+ function __construct( $data = array() ) {
+ $this->parts = array( new PPDPart );
+
+ foreach ( $data as $name => $value ) {
+ $this->$name = $value;
+ }
+ }
+
+ function &getAccum() {
+ return $this->parts[count($this->parts) - 1]->out;
+ }
+
+ function addPart( $s = '' ) {
+ $this->parts[] = new PPDPart( $s );
+ }
+
+ function getCurrentPart() {
+ return $this->parts[count($this->parts) - 1];
+ }
+
+ function getFlags() {
+ $partCount = count( $this->parts );
+ $findPipe = $this->open != "\n" && $this->open != '[';
+ return array(
+ 'findPipe' => $findPipe,
+ 'findEquals' => $findPipe && $partCount > 1 && !isset( $this->parts[$partCount - 1]->eqpos ),
+ 'inHeading' => $this->open == "\n",
+ );
+ }
+
+ /**
+ * Get the output string that would result if the close is not found.
+ */
+ function breakSyntax( $openingCount = false ) {
+ if ( $this->open == "\n" ) {
+ $s = $this->parts[0]->out;
+ } else {
+ if ( $openingCount === false ) {
+ $openingCount = $this->count;
+ }
+ $s = str_repeat( $this->open, $openingCount );
+ $first = true;
+ foreach ( $this->parts as $part ) {
+ if ( $first ) {
+ $first = false;
+ } else {
+ $s .= '|';
+ }
+ $s .= $part->out;
+ }
+ }
+ return $s;
+ }
+}
+
+class PPDPart {
+ var $out; // Output accumulator string
+
+ // Optional member variables:
+ // eqpos Position of equals sign in output accumulator
+ // commentEnd Past-the-end input pointer for the last comment encountered
+ // visualEnd Past-the-end input pointer for the end of the accumulator minus comments
+
+ function __construct( $out = '' ) {
+ $this->out = $out;
+ }
+}
+
/**
* An expansion frame, used as a context to expand the result of preprocessToDom()
*/
}
}
-/**
- * Stack class to help Parser::preprocessToDom()
- */
-class PPDStack {
- var $stack, $topAccum, $top;
-
- function __construct() {
- $this->stack = array();
- $this->topAccum = '';
- $this->top = false;
- }
-
- function &getAccum() {
- if ( count( $this->stack ) ) {
- return $this->top->getAccum();
- } else {
- return $this->topAccum;
- }
- }
-
- function push( $data ) {
- if ( $data instanceof PPDStackElement ) {
- $this->stack[] = $data;
- } else {
- $this->stack[] = new PPDStackElement( $data );
- }
- $this->top =& $this->stack[ count( $this->stack ) - 1 ];
- }
-
- function pop() {
- if ( !count( $this->stack ) ) {
- throw new MWException( __METHOD__.': no elements remaining' );
- }
- $temp = array_pop( $this->stack );
- if ( count( $this->stack ) ) {
- $this->top =& $this->stack[ count( $this->stack ) - 1 ];
- } else {
- $this->top = false;
- }
- }
-
- function getFlags() {
- if ( !count( $this->stack ) ) {
- return array(
- 'findEquals' => false,
- 'findPipe' => false,
- 'inHeading' => false,
- );
- } else {
- return $this->top->getFlags();
- }
- }
-}
-
-class PPDStackElement {
- var $open, $close, $count, $parts, $eqpos, $lineStart;
-
- function __construct( $data = array() ) {
- $this->parts = array( '' );
- $this->eqpos = array();
-
- foreach ( $data as $name => $value ) {
- $this->$name = $value;
- }
- }
-
- function &getAccum() {
- return $this->parts[count($this->parts) - 1];
- }
-
- function addPart( $s = '' ) {
- $this->parts[] = $s;
- }
-
- function getFlags() {
- $partCount = count( $this->parts );
- $findPipe = $this->open != "\n" && $this->open != '[';
- return array(
- 'findPipe' => $findPipe,
- 'findEquals' => $findPipe && $partCount > 1 && !isset( $this->eqpos[$partCount - 1] ),
- 'inHeading' => $this->open == "\n",
- );
- }
-}
-
class PPNode_DOM implements PPNode {
var $node;
$s .= $node->ownerDocument->saveXML( $node );
}
} else {
- $s = $this->node->ownerDocument->saveXML( $node );
+ $s = $this->node->ownerDocument->saveXML( $this->node );
}
return $s;
}
class PPFuzzTester {
var $hairs = array(
- '[[', ']]', '{{', '}}', '{{{', '}}}',
+ '[[', ']]', '{{', '{{', '}}', '}}', '{{{', '}}}',
'<', '>', '<nowiki', '<gallery', '</nowiki>', '</gallery>', '<nOwIkI>', '</NoWiKi>',
'<!--' , '-->',
"\n==", "==\n",
var $maxTemplates = 5;
//var $outputTypes = array( 'OT_HTML', 'OT_WIKI', 'OT_PREPROCESS' );
var $entryPoints = array( 'testSrvus', 'testPst', 'testPreprocess' );
+ var $verbose = false;
static $currentTest = false;
function execute() {
echo "Unable to create 'results' directory\n";
exit( 1 );
}
- for ( $i = 0; true; $i++ ) {
+ $overallStart = microtime( true );
+ $reportInterval = 1000;
+ for ( $i = 1; true; $i++ ) {
+ $t = -microtime( true );
try {
self::$currentTest = new PPFuzzTest( $this );
self::$currentTest->execute();
+ $passed = 'passed';
} catch ( MWException $e ) {
$testReport = self::$currentTest->getReport();
$exceptionReport = $e->getText();
file_put_contents( "results/ppft-$hash.fail",
"Input:\n$testReport\n\nException report:\n$exceptionReport\n" );
print "Test $hash failed\n";
+ $passed = 'failed';
}
- if ( $i % 1000 == 0 ) {
+ $t += microtime( true );
+
+ if ( $this->verbose ) {
+ printf( "Test $passed in %.3f seconds\n", $t );
+ print self::$currentTest->getReport();
+ }
+
+ $reportMetric = ( microtime( true ) - $overallStart ) / $i * $reportInterval;
+ if ( $reportMetric > 25 ) {
+ if ( substr( $reportInterval, 0, 1 ) === '1' ) {
+ $reportInterval /= 2;
+ } else {
+ $reportInterval /= 5;
+ }
+ } elseif ( $reportMetric < 4 ) {
+ if ( substr( $reportInterval, 0, 1 ) === '1' ) {
+ $reportInterval *= 5;
+ } else {
+ $reportInterval *= 2;
+ }
+ }
+ if ( $i % $reportInterval == 0 ) {
print "$i tests done\n";
/*
$testReport = self::$currentTest->getReport();
file_put_contents( $filename, "Input:\n$testReport\n" );*/
}
}
+ wfLogProfilingData();
}
- function makeInputText() {
- $length = mt_rand( $this->minLength, $this->maxLength );
+ function makeInputText( $max = false ) {
+ if ( $max === false ) {
+ $max = $this->maxLength;
+ }
+ $length = mt_rand( $this->minLength, $max );
$s = '';
for ( $i = 0; $i < $length; $i++ ) {
$hairIndex = mt_rand( 0, count( $this->hairs ) - 1 );
}
class PPFuzzTest {
- var $templates, $mainText, $title, $entryPoint;
+ var $templates, $mainText, $title, $entryPoint, $output;
function __construct( $tester ) {
+ global $wgMaxSigChars;
$this->parent = $tester;
$this->mainText = $tester->makeInputText();
$this->title = $tester->makeTitle();
//$this->outputType = $tester->pickOutputType();
$this->entryPoint = $tester->pickEntryPoint();
- $this->nickname = $tester->makeInputText();
+ $this->nickname = $tester->makeInputText( $wgMaxSigChars + 10);
$this->fancySig = (bool)mt_rand( 0, 1 );
$this->templates = array();
}
$options = new ParserOptions;
$options->setTemplateCallback( array( $this, 'templateHook' ) );
- //$wgParser->startExternalParse( $this->title, $options, constant( $this->outputType ) );
- return call_user_func( array( $wgParser, $this->entryPoint ), $this->mainText, $this->title, $options );
+ $options->setTimestamp( wfTimestampNow() );
+ $this->output = call_user_func( array( $wgParser, $this->entryPoint ), $this->mainText, $this->title->getPrefixedText(), $options );
+ return $this->output;
}
function getReport() {
$s .= "[[$titleText]]: " . var_export( $template['text'], true ) . "\n";
}
}
+ $s .= "Output: " . var_export( $this->output, true ) . "\n";
return $s;
}
}
class PPFuzzUser extends User {
var $ppfz_test;
+ function load() {
+ if ( $this->mDataLoaded ) {
+ return;
+ }
+ $this->mDataLoaded = true;
+ $this->loadDefaults( $this->mName );
+ }
+
function getOption( $option, $defaultOverride = '' ) {
if ( $option === 'fancysig' ) {
return $this->ppfz_test->fancySig;
exit( 1 );
}
$test = unserialize( $testText );
- print $test->getReport();
$result = $test->execute();
- print "Test passed.\nResult: $result\n";
+ print "Test passed.\n";
} else {
$tester = new PPFuzzTester;
+ $tester->verbose = isset( $options['verbose'] );
$tester->execute();
}