Merge pull request #6021 from timvandermeij/test-driver

Refactor the test driver
This commit is contained in:
Jonas Jenwald 2015-05-26 21:01:13 +02:00
commit 39d2103063
2 changed files with 455 additions and 433 deletions

View File

@ -18,186 +18,40 @@
'use strict'; 'use strict';
/* var WAITING_TIME = 100; // ms
* A Test Driver for PDF.js var PDF_TO_CSS_UNITS = 96.0 / 72.0;
/**
* @class
*/ */
(function DriverClosure() { var NullTextLayerBuilder = (function NullTextLayerBuilderClosure() {
/**
* @constructs NullTextLayerBuilder
*/
function NullTextLayerBuilder() {}
PDFJS.enableStats = true;
PDFJS.cMapUrl = '../external/bcmaps/';
PDFJS.cMapPacked = true;
var appPath, masterMode, browser, canvas, dummyCanvas, currentTaskIdx,
manifest, stdout;
var inFlightRequests = 0;
// Chrome for Windows locks during testing on low end machines
var letItCooldown = /Windows.*?Chrom/i.test(navigator.userAgent);
function queryParams() {
var qs = window.location.search.substring(1);
var kvs = qs.split('&');
var params = { };
for (var i = 0; i < kvs.length; ++i) {
var kv = kvs[i].split('=');
params[unescape(kv[0])] = unescape(kv[1]);
}
return params;
}
window.load = function load() {
var params = queryParams();
browser = params.browser;
var manifestFile = params.manifestFile;
appPath = params.path;
masterMode = params.masterMode === 'True';
var delay = params.delay || 0;
canvas = document.createElement('canvas');
stdout = document.getElementById('stdout');
info('User Agent: ' + navigator.userAgent);
log('load...\n');
log('Harness thinks this browser is "' + browser + '" with path "' +
appPath + '"\n');
log('Fetching manifest "' + manifestFile + '"... ');
var r = new XMLHttpRequest();
r.open('GET', manifestFile, false);
r.onreadystatechange = function loadOnreadystatechange(e) {
if (r.readyState === 4) {
log('done\n');
manifest = JSON.parse(r.responseText);
currentTaskIdx = 0;
nextTask();
}
};
if (delay) {
log('\nDelaying for ' + delay + 'ms...\n');
}
// When gathering the stats the numbers seem to be more reliable if the
// browser is given more time to startup.
setTimeout(function() {
r.send(null);
}, delay);
};
function cleanup(callback) {
// Clear out all the stylesheets since a new one is created for each font.
while (document.styleSheets.length > 0) {
var styleSheet = document.styleSheets[0];
while (styleSheet.cssRules.length > 0) {
styleSheet.deleteRule(0);
}
var ownerNode = styleSheet.ownerNode;
ownerNode.parentNode.removeChild(ownerNode);
}
var guard = document.getElementById('content-end');
var body = document.body;
while (body.lastChild !== guard) {
body.removeChild(body.lastChild);
}
// Wipe out the link to the pdfdoc so it can be GC'ed.
for (var i = 0; i < manifest.length; i++) {
if (manifest[i].pdfDoc) {
manifest[i].pdfDoc.destroy();
delete manifest[i].pdfDoc;
}
}
if (letItCooldown) {
setTimeout(callback, 500);
} else {
callback();
}
}
function exceptionToString(e) {
if (typeof e !== 'object') {
return String(e);
}
if (!('message' in e)) {
return JSON.stringify(e);
}
return e.message + ('stack' in e ? ' at ' + e.stack.split('\n')[0] : '');
}
function nextTask() {
cleanup(continueNextTask);
}
function continueNextTask() {
if (currentTaskIdx === manifest.length) {
done();
return;
}
var task = manifest[currentTaskIdx];
task.round = 0;
task.stats = {times: []};
log('Loading file "' + task.file + '"\n');
var absoluteUrl = combineUrl(window.location.href, task.file);
var failure;
function continuation() {
task.pageNum = task.firstPage || 1;
nextPage(task, failure);
}
PDFJS.disableRange = task.disableRange;
PDFJS.disableAutoFetch = !task.enableAutoFetch;
try {
var promise = PDFJS.getDocument({
url: absoluteUrl,
password: task.password
});
promise.then(function(doc) {
task.pdfDoc = doc;
continuation();
}, function(e) {
failure = 'load PDF doc : ' + e;
continuation();
});
return;
} catch (e) {
failure = 'load PDF doc : ' + exceptionToString(e);
}
continuation();
}
function getLastPageNum(task) {
if (!task.pdfDoc) {
return task.firstPage || 1;
}
var lastPageNum = task.lastPage || 0;
if (!lastPageNum || lastPageNum > task.pdfDoc.numPages) {
lastPageNum = task.pdfDoc.numPages;
}
return lastPageNum;
}
function isLastPage(task) {
return task.pageNum > getLastPageNum(task);
}
function canvasToDataURL() {
return canvas.toDataURL('image/png');
}
function NullTextLayerBuilder() {
}
NullTextLayerBuilder.prototype = { NullTextLayerBuilder.prototype = {
beginLayout: function NullTextLayerBuilder_BeginLayout() {}, beginLayout: function NullTextLayerBuilder_BeginLayout() {},
endLayout: function NullTextLayerBuilder_EndLayout() {}, endLayout: function NullTextLayerBuilder_EndLayout() {},
appendText: function NullTextLayerBuilder_AppendText() {} appendText: function NullTextLayerBuilder_AppendText() {}
}; };
return NullTextLayerBuilder;
})();
/**
* @class
*/
var SimpleTextLayerBuilder = (function SimpleTextLayerBuilderClosure() {
/**
* @constructs SimpleTextLayerBuilder
*/
function SimpleTextLayerBuilder(ctx, viewport) { function SimpleTextLayerBuilder(ctx, viewport) {
this.ctx = ctx; this.ctx = ctx;
this.viewport = viewport; this.viewport = viewport;
this.textCounter = 0; this.textCounter = 0;
} }
SimpleTextLayerBuilder.prototype = { SimpleTextLayerBuilder.prototype = {
appendText: function SimpleTextLayerBuilder_AppendText(geom, styles) { appendText: function SimpleTextLayerBuilder_AppendText(geom, styles) {
var style = styles[geom.fontName]; var style = styles[geom.fontName];
@ -207,6 +61,7 @@ SimpleTextLayerBuilder.prototype = {
var fontHeight = Math.sqrt((tx[2] * tx[2]) + (tx[3] * tx[3])); var fontHeight = Math.sqrt((tx[2] * tx[2]) + (tx[3] * tx[3]));
var fontAscent = (style.ascent ? style.ascent * fontHeight : var fontAscent = (style.ascent ? style.ascent * fontHeight :
(style.descent ? (1 + style.descent) * fontHeight : fontHeight)); (style.descent ? (1 + style.descent) * fontHeight : fontHeight));
ctx.save(); ctx.save();
ctx.beginPath(); ctx.beginPath();
ctx.strokeStyle = 'red'; ctx.strokeStyle = 'red';
@ -224,63 +79,237 @@ SimpleTextLayerBuilder.prototype = {
this.textCounter++; this.textCounter++;
}, },
setTextContent: function SimpleTextLayerBuilder_SetTextContent(textContent) {
setTextContent:
function SimpleTextLayerBuilder_SetTextContent(textContent) {
this.ctx.save(); this.ctx.save();
var textItems = textContent.items; var textItems = textContent.items;
for (var i = 0; i < textItems.length; i++) { for (var i = 0, ii = textItems.length; i < ii; i++) {
this.appendText(textItems[i], textContent.styles); this.appendText(textItems[i], textContent.styles);
} }
this.ctx.restore(); this.ctx.restore();
} }
}; };
function nextPage(task, loadError) { return SimpleTextLayerBuilder;
})();
/**
* @typedef {Object} DriverOptions
* @property {HTMLSpanElement} inflight - Field displaying the number of
* inflight requests.
* @property {HTMLInputElement} disableScrolling - Checkbox to disable
* automatic scrolling of the output container.
* @property {HTMLPreElement} output - Container for all output messages.
* @property {HTMLDivElement} end - Container for a completion message.
*/
/**
* @class
*/
var Driver = (function DriverClosure() {
/**
* @constructs Driver
* @param {DriverOptions} options
*/
function Driver(options) {
// Configure the global PDFJS object
PDFJS.workerSrc = '../src/worker_loader.js';
PDFJS.cMapPacked = true;
PDFJS.cMapUrl = '../external/bcmaps/';
PDFJS.enableStats = true;
// Set the passed options
this.inflight = options.inflight;
this.disableScrolling = options.disableScrolling;
this.output = options.output;
this.end = options.end;
// Set parameters from the query string
var parameters = this._getQueryStringParameters();
this.browser = parameters.browser;
this.manifestFile = parameters.manifestFile;
this.appPath = parameters.path;
this.delay = (parameters.delay | 0) || 0;
this.inFlightRequests = 0;
// Create a working canvas
this.canvas = document.createElement('canvas');
}
Driver.prototype = {
_getQueryStringParameters: function Driver_getQueryStringParameters() {
var queryString = window.location.search.substring(1);
var values = queryString.split('&');
var parameters = {};
for (var i = 0, ii = values.length; i < ii; i++) {
var value = values[i].split('=');
parameters[unescape(value[0])] = unescape(value[1]);
}
return parameters;
},
run: function Driver_run() {
var self = this;
this._info('User agent: ' + navigator.userAgent);
this._log('Harness thinks this browser is "' + this.browser +
'" with path "' + this.appPath + '"\n');
this._log('Fetching manifest "' + this.manifestFile + '"... ');
var r = new XMLHttpRequest();
r.open('GET', this.manifestFile, false);
r.onreadystatechange = function() {
if (r.readyState === 4) {
self._log('done\n');
self.manifest = JSON.parse(r.responseText);
self.currentTask = 0;
self._nextTask();
}
};
if (this.delay > 0) {
this._log('\nDelaying for ' + this.delay + ' ms...\n');
}
// When gathering the stats the numbers seem to be more reliable
// if the browser is given more time to start.
setTimeout(function() {
r.send(null);
}, this.delay);
},
_nextTask: function Driver_nextTask() {
var self = this;
var failure = '';
this._cleanup();
if (this.currentTask === this.manifest.length) {
this._done();
return;
}
var task = this.manifest[this.currentTask];
task.round = 0;
task.pageNum = task.firstPage || 1;
task.stats = { times: [] };
this._log('Loading file "' + task.file + '"\n');
var absoluteUrl = combineUrl(window.location.href, task.file);
PDFJS.disableRange = task.disableRange;
PDFJS.disableAutoFetch = !task.enableAutoFetch;
try {
PDFJS.getDocument({
url: absoluteUrl,
password: task.password
}).then(function(doc) {
task.pdfDoc = doc;
self._nextPage(task, failure);
}, function(e) {
failure = 'Loading PDF document: ' + e;
self._nextPage(task, failure);
});
return;
} catch (e) {
failure = 'Loading PDF document: ' + this._exceptionToString(e);
}
this._nextPage(task, failure);
},
_cleanup: function Driver_cleanup() {
// Clear out all the stylesheets since a new one is created for each font.
while (document.styleSheets.length > 0) {
var styleSheet = document.styleSheets[0];
while (styleSheet.cssRules.length > 0) {
styleSheet.deleteRule(0);
}
var ownerNode = styleSheet.ownerNode;
ownerNode.parentNode.removeChild(ownerNode);
}
var body = document.body;
while (body.lastChild !== this.end) {
body.removeChild(body.lastChild);
}
// Wipe out the link to the pdfdoc so it can be GC'ed.
for (var i = 0; i < this.manifest.length; i++) {
if (this.manifest[i].pdfDoc) {
this.manifest[i].pdfDoc.destroy();
delete this.manifest[i].pdfDoc;
}
}
},
_exceptionToString: function Driver_exceptionToString(e) {
if (typeof e !== 'object') {
return String(e);
}
if (!('message' in e)) {
return JSON.stringify(e);
}
return e.message + ('stack' in e ? ' at ' + e.stack.split('\n')[0] : '');
},
_getLastPageNumber: function Driver_getLastPageNumber(task) {
if (!task.pdfDoc) {
return task.firstPage || 1;
}
var lastPageNumber = task.lastPage || 0;
if (!lastPageNumber || lastPageNumber > task.pdfDoc.numPages) {
lastPageNumber = task.pdfDoc.numPages;
}
return lastPageNumber;
},
_nextPage: function Driver_nextPage(task, loadError) {
var self = this;
var failure = loadError || ''; var failure = loadError || '';
if (!task.pdfDoc) { if (!task.pdfDoc) {
sendTaskResult(canvasToDataURL(), task, failure, function () { var dataUrl = this.canvas.toDataURL('image/png');
log('done' + (failure ? ' (failed !: ' + failure + ')' : '') + '\n'); this._sendResult(dataUrl, task, failure, function () {
++currentTaskIdx; self._log('done' + (failure ? ' (failed !: ' + failure + ')' : '') +
nextTask(); '\n');
self.currentTask++;
self._nextTask();
}); });
return; return;
} }
if (isLastPage(task)) { if (task.pageNum > this._getLastPageNumber(task)) {
if (++task.round < task.rounds) { if (++task.round < task.rounds) {
log(' Round ' + (1 + task.round) + '\n'); this._log(' Round ' + (1 + task.round) + '\n');
task.pageNum = task.firstPage || 1; task.pageNum = task.firstPage || 1;
} else { } else {
++currentTaskIdx; this.currentTask++;
nextTask(); this._nextTask();
return; return;
} }
} }
if (task.skipPages && task.skipPages.indexOf(task.pageNum) >= 0) { if (task.skipPages && task.skipPages.indexOf(task.pageNum) >= 0) {
log(' skipping page ' + task.pageNum + '/' + task.pdfDoc.numPages + this._log(' Skipping page ' + task.pageNum + '/' +
'... '); task.pdfDoc.numPages + '... ');
// empty the canvas
canvas.width = 1;
canvas.height = 1;
clear(canvas.getContext('2d'));
snapshotCurrentPage(task, ''); // Empty the canvas
this.canvas.width = 1;
this.canvas.height = 1;
this._clearCanvas();
this._snapshot(task, '');
return; return;
} }
if (!failure) { if (!failure) {
try { try {
log(' loading page ' + task.pageNum + '/' + task.pdfDoc.numPages + this._log(' Loading page ' + task.pageNum + '/' +
'... '); task.pdfDoc.numPages + '... ');
var ctx = canvas.getContext('2d'); var ctx = this.canvas.getContext('2d');
task.pdfDoc.getPage(task.pageNum).then(function(page) { task.pdfDoc.getPage(task.pageNum).then(function(page) {
var pdfToCssUnitsCoef = 96.0 / 72.0; var viewport = page.getViewport(PDF_TO_CSS_UNITS);
var viewport = page.getViewport(pdfToCssUnitsCoef); self.canvas.width = viewport.width;
canvas.width = viewport.width; self.canvas.height = viewport.height;
canvas.height = viewport.height; self._clearCanvas();
clear(ctx);
var drawContext, textLayerBuilder; var drawContext, textLayerBuilder;
var resolveInitPromise; var resolveInitPromise;
@ -288,12 +317,12 @@ function nextPage(task, loadError) {
resolveInitPromise = resolve; resolveInitPromise = resolve;
}); });
if (task.type === 'text') { if (task.type === 'text') {
// using dummy canvas for pdf context drawing operations // Using a dummy canvas for PDF context drawing operations
if (!dummyCanvas) { if (!self.dummyCanvas) {
dummyCanvas = document.createElement('canvas'); self.dummyCanvas = document.createElement('canvas');
} }
drawContext = dummyCanvas.getContext('2d'); drawContext = self.dummyCanvas.getContext('2d');
// ... text builder will draw its content on the test canvas // The text builder will draw its content on the test canvas
textLayerBuilder = new SimpleTextLayerBuilder(ctx, viewport); textLayerBuilder = new SimpleTextLayerBuilder(ctx, viewport);
page.getTextContent().then(function(textContent) { page.getTextContent().then(function(textContent) {
@ -313,7 +342,7 @@ function nextPage(task, loadError) {
page.destroy(); page.destroy();
task.stats = page.stats; task.stats = page.stats;
page.stats = new StatTimer(); page.stats = new StatTimer();
snapshotCurrentPage(task, error); self._snapshot(task, error);
}); });
initPromise.then(function () { initPromise.then(function () {
page.render(renderContext).promise.then(function() { page.render(renderContext).promise.then(function() {
@ -325,64 +354,88 @@ function nextPage(task, loadError) {
}); });
}, },
function(error) { function(error) {
snapshotCurrentPage(task, 'render : ' + error); self._snapshot(task, 'render : ' + error);
}); });
} catch (e) { } catch (e) {
failure = 'page setup : ' + exceptionToString(e); failure = 'page setup : ' + this._exceptionToString(e);
snapshotCurrentPage(task, failure); this._snapshot(task, failure);
}
} }
} }
},
function snapshotCurrentPage(task, failure) { _clearCanvas: function Driver_clearCanvas() {
log('done, snapshotting... '); var ctx = this.canvas.getContext('2d');
ctx.beginPath();
ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
},
sendTaskResult(canvasToDataURL(), task, failure, function () { _snapshot: function Driver_snapshot(task, failure) {
log('done' + (failure ? ' (failed !: ' + failure + ')' : '') + '\n'); var self = this;
this._log('Snapshotting... ');
++task.pageNum; var dataUrl = this.canvas.toDataURL('image/png');
nextPage(task); this._sendResult(dataUrl, task, failure, function () {
self._log('done' + (failure ? ' (failed !: ' + failure + ')' : '') +
'\n');
task.pageNum++;
self._nextPage(task);
}); });
} },
function sendQuitRequest(cb) { _quit: function Driver_quit() {
this._log('Done !');
this.end.textContent = 'Tests finished. Close this window!';
// Send the quit request
var r = new XMLHttpRequest(); var r = new XMLHttpRequest();
r.open('POST', '/tellMeToQuit?path=' + escape(appPath), false); r.open('POST', '/tellMeToQuit?path=' + escape(this.appPath), false);
r.onreadystatechange = function sendQuitRequestOnreadystatechange(e) { r.onreadystatechange = function(e) {
if (r.readyState === 4) { if (r.readyState === 4) {
if (cb) { window.close();
cb();
}
} }
}; };
r.send(null); r.send(null);
} },
function quitApp() { _info: function Driver_info(message) {
log('Done !'); this._send('/info', JSON.stringify({
document.body.innerHTML = 'Tests are finished. <h1>CLOSE ME!</h1>' + browser: this.browser,
document.body.innerHTML; message: message
sendQuitRequest(function () { }));
window.close(); },
});
}
function done() { _log: function Driver_log(message) {
if (inFlightRequests > 0) { // Using insertAdjacentHTML yields a large performance gain and
document.getElementById('inFlightCount').innerHTML = inFlightRequests; // reduces runtime significantly.
setTimeout(done, 100); if (this.output.insertAdjacentHTML) {
this.output.insertAdjacentHTML('BeforeEnd', message);
} else { } else {
setTimeout(quitApp, 100); this.output.textContent += message;
}
} }
function sendTaskResult(snapshot, task, failure, callback) { if (message.lastIndexOf('\n') >= 0 && !this.disableScrolling.checked) {
// Scroll to the bottom of the page
this.output.scrollTop = this.output.scrollHeight;
}
},
_done: function Driver_done() {
if (this.inFlightRequests > 0) {
this.inflight.textContent = this.inFlightRequests;
setTimeout(this._done(), WAITING_TIME);
} else {
setTimeout(this._quit(), WAITING_TIME);
}
},
_sendResult: function Driver_sendResult(snapshot, task, failure,
callback) {
var result = JSON.stringify({ var result = JSON.stringify({
browser: browser, browser: this.browser,
id: task.id, id: task.id,
numPages: task.pdfDoc ? numPages: task.pdfDoc ?
(task.lastPage || task.pdfDoc.numPages) : 0, (task.lastPage || task.pdfDoc.numPages) : 0,
lastPageNum: getLastPageNum(task), lastPageNum: this._getLastPageNumber(task),
failure: failure, failure: failure,
file: task.file, file: task.file,
round: task.round, round: task.round,
@ -390,66 +443,33 @@ function sendTaskResult(snapshot, task, failure, callback) {
snapshot: snapshot, snapshot: snapshot,
stats: task.stats.times stats: task.stats.times
}); });
this._send('/submit_task_results', result, callback);
},
send('/submit_task_results', result, callback); _send: function Driver_send(url, message, callback) {
} var self = this;
function send(url, message, callback) {
var r = new XMLHttpRequest(); var r = new XMLHttpRequest();
// (The POST URI is ignored atm.)
r.open('POST', url, true); r.open('POST', url, true);
r.setRequestHeader('Content-Type', 'application/json'); r.setRequestHeader('Content-Type', 'application/json');
r.onreadystatechange = function sendTaskResultOnreadystatechange(e) { r.onreadystatechange = function(e) {
if (r.readyState === 4) { if (r.readyState === 4) {
inFlightRequests--; self.inFlightRequests--;
// Retry until successful // Retry until successful
if (r.status !== 200) { if (r.status !== 200) {
setTimeout(function() { setTimeout(function() {
send(url, message); self._send(url, message);
}); });
} }
if (callback) { if (callback) {
if (letItCooldown) {
setTimeout(callback, 100);
} else {
callback(); callback();
} }
} }
}
}; };
document.getElementById('inFlightCount').innerHTML = inFlightRequests++; this.inflight.textContent = this.inFlightRequests++;
r.send(message); r.send(message);
} }
};
function info(message) { return Driver;
send('/info', JSON.stringify({ })();
browser: browser,
message: message
}));
}
function clear(ctx) {
ctx.beginPath();
ctx.clearRect(0, 0, canvas.width, canvas.height);
}
/* Auto-scroll if the scrollbar is near the bottom, otherwise do nothing. */
function checkScrolling() {
if ((stdout.scrollHeight - stdout.scrollTop) <= stdout.offsetHeight) {
stdout.scrollTop = stdout.scrollHeight;
}
}
function log(str) {
if (stdout.insertAdjacentHTML) {
stdout.insertAdjacentHTML('BeforeEnd', str);
} else {
stdout.innerHTML += str;
}
if (str.lastIndexOf('\n') >= 0) {
checkScrolling();
}
}
})(); // DriverClosure

View File

@ -1,6 +1,6 @@
<!DOCTYPE html> <!DOCTYPE html>
<!-- <!--
Copyright 2012 Mozilla Foundation Copyright 2015 Mozilla Foundation
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@ -14,34 +14,36 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
--> -->
<html> <html>
<head> <head>
<title>pdf.js test slave</title> <title>PDF.js test slave</title>
<style type="text/css"></style> <meta charset="utf-8">
<script src="/src/shared/util.js"></script> <script src="../src/shared/util.js"></script>
<script src="/src/display/api.js"></script> <script src="../src/display/api.js"></script>
<script src="/src/display/metadata.js"></script> <script src="../src/display/metadata.js"></script>
<script src="/src/display/canvas.js"></script> <script src="../src/display/canvas.js"></script>
<script src="/src/display/webgl.js"></script> <script src="../src/display/webgl.js"></script>
<script src="/src/display/pattern_helper.js"></script> <script src="../src/display/pattern_helper.js"></script>
<script src="/src/display/font_loader.js"></script> <script src="../src/display/font_loader.js"></script>
<script src="/src/display/annotation_helper.js"></script> <script src="../src/display/annotation_helper.js"></script>
<script src="driver.js"></script> <script src="driver.js"></script>
<script>
PDFJS.workerSrc = '/src/worker_loader.js';
</script>
</head> </head>
<body> <body>
<pre style="width:800px; height:800px; overflow:scroll;" id="stdout"></pre> <p>Inflight requests: <span id="inflight"></span></p>
<p>Inflight requests: <span id="inFlightCount"></span></p> <p>
<div id="content-end"></div> <input type="checkbox" id="disableScrolling">
<label for="disableScrolling">Disable automatic scrolling</label>
<script> </p>
'use strict'; <pre id="output" style="max-height: 800px; overflow-y: scroll;"></pre>
load(); <div id="end"></div>
</script>
</body> </body>
<script>
var driver = new Driver({
disableScrolling: document.getElementById('disableScrolling'),
inflight: document.getElementById('inflight'),
output: document.getElementById('output'),
end: document.getElementById('end')
});
driver.run();
</script>
</html> </html>