Marcoil has uploaded a new change for review.
https://gerrit.wikimedia.org/r/99348
Change subject: Bug 56590: Use the Parsoid HTTP API in round-trip testing
......................................................................
Bug 56590: Use the Parsoid HTTP API in round-trip testing
roundtrip-test.js now uses a (configurable) Parsoid HTTP API to obtain html
and wikitext for a page before comparing to the original wikitext.
The round-trip test client also has a new configuration option for the URL of
the Parsoid API.
If no Parsoid URL is passed, the new module apiServer.js starts one for us.
Change-Id: Ibb9daa72d51d91c6192ac0d695e6ecc4e70005aa
---
A js/tests/apiServer.js
M js/tests/client/client.js
M js/tests/client/config.example.js
M js/tests/roundtrip-test.js
4 files changed, 164 insertions(+), 46 deletions(-)
git pull ssh://gerrit.wikimedia.org:29418/mediawiki/extensions/Parsoid
refs/changes/48/99348/1
diff --git a/js/tests/apiServer.js b/js/tests/apiServer.js
new file mode 100644
index 0000000..2424a72
--- /dev/null
+++ b/js/tests/apiServer.js
@@ -0,0 +1,49 @@
+#!/usr/bin/env node
+"use strict";
+/**
+ * Manages a Parsoid server for testing.
+ */
+
+var child_process = require( 'child_process' );
+
+var forkedServer;
+
+/**
+ * Starts a Parsoid server on passed port or a random port if none passed,
+ * @return The URL of the created server
+ */
+var startParsoidServer = function ( port ) {
+ if ( !port ) {
+ port = 9000 + Math.floor( Math.random() * 100 );
+ }
+
+ forkedServer = child_process.fork( __dirname + '/../api/server.js',
+ [ '-c', '1' ], { env: { VCAP_APP_PORT: port } } );
+
+ // If this process dies, kill our Parsoid server
+ var weDied = function () {
+ forkedServer.removeListener( 'exit', startParsoidServer );
+ forkedServer.kill();
+ };
+ process.on( 'exit', weDied );
+
+ // If it dies on its own, restart it
+ forkedServer.on( 'exit', function( ) {
+ process.removeListener( 'exit', weDied );
+ startParsoidServer( port );
+ } );
+
+ return 'http://localhost:' + port.toString() + '/';
+};
+
+var stopParsoidServer = function () {
+ if ( forkedServer ) {
+ forkedServer.removeListener( 'exit', startParsoidServer );
+ forkedServer.kill();
+ }
+};
+
+module.exports = {
+ startParsoidServer: startParsoidServer,
+ stopParsoidServer: stopParsoidServer
+};
diff --git a/js/tests/client/client.js b/js/tests/client/client.js
index ba9f96f..7a658e3 100755
--- a/js/tests/client/client.js
+++ b/js/tests/client/client.js
@@ -7,12 +7,14 @@
var http = require( 'http' ),
qs = require( 'querystring' ),
exec = require( 'child_process' ).exec,
+ apiServer = require( '../apiServer.js' ),
commit, ctime,
lastCommit, lastCommitTime, lastCommitCheck,
repoPath = __dirname,
config = require( process.argv[2] || './config.js' ),
+ parsoidURL = config.parsoidURL,
rtTest = require( '../roundtrip-test.js' );
var getTitle = function( cb ) {
@@ -78,7 +80,8 @@
rtTest.fetch( title, callback, {
setup: config.setup,
prefix: prefix,
- editMode: false
+ editMode: false,
+ parsoidURL: parsoidURL
} );
} catch ( err ) {
// Log it to console (for gabriel to watch scroll by)
@@ -192,6 +195,11 @@
}
if ( module && !module.parent ) {
+ if ( !config.parsoidURL ) {
+ // If no Parsoid server was passed, start our own
+ parsoidURL = apiServer.startParsoidServer();
+ }
+
getGitCommit( function ( commitHash, commitTime ) {
commit = commitHash;
ctime = commitTime;
diff --git a/js/tests/client/config.example.js
b/js/tests/client/config.example.js
index 923ae8f..4d13d47 100644
--- a/js/tests/client/config.example.js
+++ b/js/tests/client/config.example.js
@@ -30,7 +30,10 @@
// Insert the interwiki prefix for a localhost wiki
parsoidConfig.setInterwiki( 'localhost',
'http://localhost/wiki/api.php' );
- }
+ },
+
+ // The parsoid API to use. If null, create our own server
+ parsoidURL: null
};
}
diff --git a/js/tests/roundtrip-test.js b/js/tests/roundtrip-test.js
index 2c0859a..b81a736 100755
--- a/js/tests/roundtrip-test.js
+++ b/js/tests/roundtrip-test.js
@@ -2,13 +2,15 @@
"use strict";
var jsDiff = require( 'diff' ),
+ http = require( 'http' ),
optimist = require( 'optimist' ),
+ querystring = require( 'querystring' ),
domino = require( 'domino' ),
+ url = require( 'url' ),
zlib = require( 'zlib' ),
Util = require( '../lib/mediawiki.Util.js' ).Util,
DU = require( '../lib/mediawiki.DOMUtils.js' ).DOMUtils,
- WikitextSerializer = require(
'../lib/mediawiki.WikitextSerializer.js').WikitextSerializer,
TemplateRequest = require( '../lib/mediawiki.ApiRequest.js'
).TemplateRequest,
ParsoidConfig = require( '../lib/mediawiki.ParsoidConfig'
).ParsoidConfig,
MWParserEnvironment = require( '../lib/mediawiki.parser.environment.js'
).MWParserEnvironment;
@@ -380,8 +382,6 @@
};
var doubleRoundtripDiff = function ( env, offsets, body, out, cb ) {
- var src = env.page.src;
-
if ( offsets.length > 0 ) {
env.setPageSrcInfo( out );
env.errCB = function ( error ) {
@@ -390,7 +390,7 @@
};
var parserPipeline = Util.getParserPipeline( env,
'text/x-mediawiki/full' );
- parserPipeline.on( 'document', checkIfSignificant.bind( null,
env, offsets, src, body, out, cb ) );
+ parserPipeline.on( 'document', checkIfSignificant.bind( null,
env, offsets, env.page.src, body, out, cb ) );
parserPipeline.processToplevelDoc( out );
} else {
@@ -398,28 +398,71 @@
}
};
-var roundTripDiff = function ( env, document, cb ) {
- var out, diff, offsetPairs;
+var parsoidPost = function ( env, parsoidURL, prefix, title, text, oldid, cb )
{
+ var options = url.parse( url.resolve( parsoidURL, prefix + '/' + title
) );
+ options.method = 'POST';
+ options.headers = {
+ 'Content-Type': 'application/x-www-form-urlencoded',
+ };
- // Re-parse the HTML to uncover foster-parenting issues
- var origBody = document.body;
- document = domino.createDocument(DU.serializeNode(document));
+ var req = http.request( options, function( res ) {
+ res.setEncoding( 'utf8' );
+ res.body = '';
+
+ res.on( 'data', function ( chunk ) { res.body += chunk; } );
+
+ res.on( 'end', function () {
+ if ( res.statusCode !== 200 ) {
+ cb( res.body, null );
+ } else {
+ if ( env.profile ) {
+ // Record the time it's taken to parse
+ var timePrefix = oldid ? 'html2wt' :
'wt2html';
+ if ( res.headers[
'x-parsoid-performance' ] ) {
+ env.profile.time[ timePrefix ] =
+ parseInt( res.headers[
'x-parsoid-performance' ].
+ match(
/duration=((\d)+);/ )[1], 10 );
+ }
+ // Record the sizes
+ var sizePrefix = oldid ? 'wt' : 'html';
+ env.profile.size[ sizePrefix + 'raw' ] =
+ res.body.length;
+ // Compress to record the gzipped size
+ zlib.gzip( res.body, function( err,
gzippedbuf ) {
+ if ( !err ) {
+ env.profile.size[
sizePrefix + 'gzip' ] =
+
gzippedbuf.length;
+ }
+ cb( null, res );
+ } );
+ } else {
+ cb( null, res );
+ }
+ }
+ } );
+ } );
+
+ var data = {};
+ if ( oldid ) {
+ data.oldid = oldid;
+ data.html = text;
+ } else {
+ data.wt = text;
+ }
+ req.write( querystring.stringify( data ) );
+ req.end();
+};
+
+var roundTripDiff = function ( env, html, out, cb ) {
+ var diff, offsetPairs;
+
try {
- env.profile.time.serialize = new Date();
- out = new WikitextSerializer( { env: env }
).serializeDOM(document.body);
- env.profile.time.serialize = new Date() -
env.profile.time.serialize;
- env.profile.size.wikitext = out.length;
-
- // Finish the total time now
- if ( env.profile && env.profile.time ) {
- env.profile.time.total += new Date() -
env.profile.time.total_timer;
- delete( env.profile.time.total_timer );
- }
diff = jsDiff.diffLines( out, env.page.src );
offsetPairs = Util.convertDiffToOffsetPairs( diff );
if ( diff.length > 0 ) {
- doubleRoundtripDiff( env, offsetPairs, origBody, out,
cb );
+ var body = domino.createDocument( html ).body;
+ doubleRoundtripDiff( env, offsetPairs, body, out, cb );
} else {
cb( null, env, [] );
}
@@ -430,6 +473,11 @@
var fetch = function ( page, cb, options ) {
cb = typeof cb === 'function' ? cb : function () {};
+ var prefix = options.prefix || 'enwiki';
+
+ if ( options.apiURL ) {
+ prefix = 'customwiki';
+ }
var envCb = function ( err, env ) {
env.errCB = function ( error ) {
@@ -450,33 +498,31 @@
cb( err, env, [] );
} else {
env.setPageSrcInfo( src_and_metadata );
- env.profile.time.parse = new Date();
- Util.parse( env, function ( src, err, doc ) {
- env.profile.time.parse = new Date() -
env.profile.time.parse;
- if ( err ) {
- cb( err, env, [] );
- } else {
- // Pause the total time while
we compute these sizes
- env.profile.time.total += new
Date() - env.profile.time.total_timer;
- env.profile.size.htmlraw =
doc.outerHTML.length;
- zlib.gzip( doc.outerHTML,
function( err, buf ) {
- if ( !err ) {
-
env.profile.size.htmlgzip = buf.length;
- }
-
env.profile.time.total_timer = new Date();
- roundTripDiff( env,
doc, cb );
- });
- }
- }, err, env.page.src );
+ // First, fetch the HTML for the requested
page's wikitext
+ parsoidPost( env, options.parsoidURL, prefix,
page,
+ env.page.src, null, function ( err,
htmlRes ) {
+ if ( err ) {
+ cb( err, env, [] );
+ } else {
+ // And now, request the
wikitext for the obtained HTML
+ parsoidPost( env,
options.parsoidURL, prefix, page,
+ htmlRes.body,
src_and_metadata.revision.revid, function ( err, wtRes ) {
+ if (
err ) {
+
cb( err, env, [] );
+ } else {
+
// Finish the total time now
+
if ( env.profile && env.profile.time ) {
+
env.profile.time.total += new Date() - env.profile.time.total_timer;
+
delete( env.profile.time.total_timer );
+
}
+
roundTripDiff( env, htmlRes.body, wtRes.body, cb );
+ }
+ } );
+ }
+ } );
}
} );
};
-
- var prefix = options.prefix || 'enwiki';
-
- if ( options.apiURL ) {
- prefix = 'customwiki';
- }
var parsoidConfig = new ParsoidConfig( options, { defaultWiki: prefix }
);
@@ -501,6 +547,7 @@
process.exit( 1 );
} else {
console.log( output );
+ process.exit( 0 );
}
};
@@ -552,6 +599,10 @@
description: 'Dump state (see below for supported dump
flags)',
'boolean': false,
'default': ""
+ },
+ 'parsoidURL': {
+ description: 'The URL for the Parsoid API',
+ 'boolean': false
}
});
@@ -563,6 +614,13 @@
callback = cbCombinator.bind( null,
Util.booleanOption( argv.xml ) ?
xmlCallback : plainCallback,
consoleOut );
+ if ( !argv.parsoidURL ) {
+ // Start our own Parsoid server
+ // TODO: This will not be necessary once we have a
top-level testing
+ // script that takes care of setting everything up.
+ var apiServer = require( './apiServer.js' );
+ argv.parsoidURL = apiServer.startParsoidServer();
+ }
fetch( title, callback, argv );
} else {
opts.showHelp();
--
To view, visit https://gerrit.wikimedia.org/r/99348
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings
Gerrit-MessageType: newchange
Gerrit-Change-Id: Ibb9daa72d51d91c6192ac0d695e6ecc4e70005aa
Gerrit-PatchSet: 1
Gerrit-Project: mediawiki/extensions/Parsoid
Gerrit-Branch: master
Gerrit-Owner: Marcoil <[email protected]>
_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits