13 changed files with 1579 additions and 1024 deletions
@ -0,0 +1,3 @@
@@ -0,0 +1,3 @@
|
||||
[submodule "test/ttx/fonttools-code"] |
||||
path = test/ttx/fonttools-code |
||||
url = git://git.code.sf.net/p/fonttools/code |
@ -0,0 +1,173 @@
@@ -0,0 +1,173 @@
|
||||
/* -*- Mode: js; js-indent-level: 2; indent-tabs-mode: nil; tab-width: 2 -*- */ |
||||
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */ |
||||
/* |
||||
* Copyright 2014 Mozilla Foundation |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
/*jslint node: true */ |
||||
|
||||
'use strict'; |
||||
|
||||
var fs = require('fs'); |
||||
var crypto = require('crypto'); |
||||
var http = require('http'); |
||||
var https = require('https'); |
||||
|
||||
function downloadFile(file, url, callback, redirects) { |
||||
var completed = false; |
||||
var protocol = /^https:\/\//.test(url) ? https : http; |
||||
protocol.get(url, function (response) { |
||||
if (response.statusCode === 301 || response.statusCode === 302 || |
||||
response.statusCode === 307 || response.statusCode === 308) { |
||||
if (redirects > 10) { |
||||
callback('Too many redirects'); |
||||
} |
||||
var redirectTo = response.headers.location; |
||||
redirectTo = require('url').resolve(url, redirectTo); |
||||
downloadFile(file, redirectTo, callback, (redirects || 0) + 1); |
||||
return; |
||||
} |
||||
if (response.statusCode === 404 && url.indexOf('web.archive.org') < 0) { |
||||
// trying waybackmachine
|
||||
var redirectTo = 'http://web.archive.org/web/' + url; |
||||
downloadFile(file, redirectTo, callback, (redirects || 0) + 1); |
||||
return; |
||||
} |
||||
|
||||
if (response.statusCode !== 200) { |
||||
if (!completed) { |
||||
completed = true; |
||||
callback('HTTP ' + response.statusCode); |
||||
} |
||||
return; |
||||
} |
||||
var stream = fs.createWriteStream(file); |
||||
stream.on('error', function (err) { |
||||
if (!completed) { |
||||
completed = true; |
||||
callback(err); |
||||
} |
||||
}); |
||||
response.pipe(stream); |
||||
stream.on('finish', function() { |
||||
stream.close(); |
||||
if (!completed) { |
||||
completed = true; |
||||
callback(); |
||||
} |
||||
}); |
||||
}).on('error', function (err) { |
||||
if (!completed) { |
||||
if (typeof err === 'object' && err.errno === 'ENOTFOUND' && |
||||
url.indexOf('web.archive.org') < 0) { |
||||
// trying waybackmachine
|
||||
var redirectTo = 'http://web.archive.org/web/' + url; |
||||
downloadFile(file, redirectTo, callback, (redirects || 0) + 1); |
||||
return; |
||||
} |
||||
completed = true; |
||||
callback(err); |
||||
} |
||||
}); |
||||
} |
||||
|
||||
function downloadManifestFiles(manifest, callback) { |
||||
function downloadNext() { |
||||
if (i >= links.length) { |
||||
callback(); |
||||
return; |
||||
} |
||||
var file = links[i].file; |
||||
var url = links[i].url; |
||||
console.log('Downloading ' + url + ' to ' + file + '...'); |
||||
downloadFile(file, url, function (err) { |
||||
if (err) { |
||||
console.error('Error during downloading of ' + url + ': ' + err); |
||||
fs.writeFileSync(file, ''); // making it empty file
|
||||
fs.writeFileSync(file + '.error', err); |
||||
} |
||||
i++; |
||||
downloadNext(); |
||||
}); |
||||
} |
||||
|
||||
var links = manifest.filter(function (item) { |
||||
return item.link && !fs.existsSync(item.file); |
||||
}).map(function (item) { |
||||
var file = item.file; |
||||
var linkfile = file + '.link'; |
||||
var url = fs.readFileSync(linkfile).toString(); |
||||
url = url.replace(/\s+$/, ''); |
||||
return {file: file, url: url}; |
||||
}); |
||||
|
||||
var i = 0; |
||||
downloadNext(); |
||||
} |
||||
|
||||
function calculateMD5(file, callback) { |
||||
var hash = crypto.createHash('md5'); |
||||
var stream = fs.createReadStream(file); |
||||
stream.on('data', function (data) { |
||||
hash.update(data); |
||||
}); |
||||
stream.on('error', function (err) { |
||||
callback(err); |
||||
}); |
||||
stream.on('end', function() { |
||||
var result = hash.digest('hex'); |
||||
callback(null, result); |
||||
}); |
||||
} |
||||
|
||||
function verifyManifestFiles(manifest, callback) { |
||||
function verifyNext() { |
||||
if (i >= manifest.length) { |
||||
callback(error); |
||||
return; |
||||
} |
||||
var item = manifest[i]; |
||||
if (fs.existsSync(item.file + '.error')) { |
||||
console.error('WARNING: File was not downloaded. See "' + |
||||
item.file + '.error" file.'); |
||||
error = true; |
||||
i++; |
||||
verifyNext(); |
||||
return; |
||||
} |
||||
calculateMD5(item.file, function (err, md5) { |
||||
if (err) { |
||||
console.log('WARNING: Unable to open file for reading "' + err + '".'); |
||||
error = true; |
||||
} else if (!item.md5) { |
||||
console.error('WARNING: Missing md5 for file "' + item.file + '". ' + |
||||
'Hash for current file is "' + md5 + '"'); |
||||
error = true; |
||||
} else if (md5 !== item.md5) { |
||||
console.error('WARNING: MD5 of file "' + item.file + |
||||
'" does not match file. Expected "' + |
||||
item.md5 + '" computed "' + md5 + '"'); |
||||
error = true; |
||||
} |
||||
i++; |
||||
verifyNext(); |
||||
}); |
||||
} |
||||
var i = 0; |
||||
var error = false; |
||||
verifyNext(); |
||||
} |
||||
|
||||
exports.downloadManifestFiles = downloadManifestFiles; |
||||
exports.verifyManifestFiles = verifyManifestFiles; |
@ -0,0 +1,86 @@
@@ -0,0 +1,86 @@
|
||||
/* -*- Mode: js; js-indent-level: 2; indent-tabs-mode: nil; tab-width: 2 -*- */ |
||||
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */ |
||||
/* |
||||
* Copyright 2014 Mozilla Foundation |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
/*jslint node: true */ |
||||
|
||||
'use strict'; |
||||
|
||||
var fs = require('fs'); |
||||
var path = require('path'); |
||||
var spawn = require('child_process').spawn; |
||||
|
||||
var ttxResourcesHome = path.join(__dirname, '..', 'ttx'); |
||||
|
||||
var nextTTXTaskId = Date.now(); |
||||
|
||||
function runTtx(ttxResourcesHome, fontPath, registerOnCancel, callback) { |
||||
fs.realpath(ttxResourcesHome, function (err, ttxResourcesHome) { |
||||
var fontToolsHome = path.join(ttxResourcesHome, 'fonttools-code'); |
||||
fs.realpath(fontPath, function (err, fontPath) { |
||||
var ttxPath = path.join('Tools', 'ttx'); |
||||
if (!fs.existsSync(path.join(fontToolsHome, ttxPath))) { |
||||
callback('TTX was not found, please checkout PDF.js submodules'); |
||||
return; |
||||
} |
||||
var ttxEnv = { |
||||
'PYTHONPATH': path.join(fontToolsHome, 'Lib'), |
||||
'PYTHONDONTWRITEBYTECODE': true |
||||
}; |
||||
var ttxStdioMode = 'ignore'; |
||||
var ttx = spawn('python', [ttxPath, fontPath], |
||||
{cwd: fontToolsHome, stdio: ttxStdioMode, env: ttxEnv}); |
||||
var ttxRunError; |
||||
registerOnCancel(function (reason) { |
||||
ttxRunError = reason; |
||||
callback(reason); |
||||
ttx.kill(); |
||||
}); |
||||
ttx.on('error', function (err) { |
||||
ttxRunError = err; |
||||
callback('Unable to execute ttx'); |
||||
}); |
||||
ttx.on('close', function (code) { |
||||
if (ttxRunError) { |
||||
return; |
||||
} |
||||
callback(); |
||||
}); |
||||
}); |
||||
}); |
||||
} |
||||
|
||||
exports.translateFont = function translateFont(content, registerOnCancel, |
||||
callback) { |
||||
var buffer = new Buffer(content, 'base64'); |
||||
var taskId = (nextTTXTaskId++).toString(); |
||||
var fontPath = path.join(ttxResourcesHome, taskId + '.otf'); |
||||
var resultPath = path.join(ttxResourcesHome, taskId + '.ttx'); |
||||
|
||||
fs.writeFileSync(fontPath, buffer); |
||||
runTtx(ttxResourcesHome, fontPath, registerOnCancel, function (err) { |
||||
fs.unlink(fontPath); |
||||
if (err) { |
||||
console.error(err); |
||||
callback(err); |
||||
} else if (!fs.existsSync(resultPath)) { |
||||
callback('Output was not generated'); |
||||
} else { |
||||
callback(null, fs.readFileSync(resultPath)); |
||||
fs.unlink(resultPath); |
||||
} |
||||
}); |
||||
}; |
@ -0,0 +1,723 @@
@@ -0,0 +1,723 @@
|
||||
/* -*- Mode: js; js-indent-level: 2; indent-tabs-mode: nil; tab-width: 2 -*- */ |
||||
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */ |
||||
/* |
||||
* Copyright 2014 Mozilla Foundation |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
/*jslint node: true */ |
||||
|
||||
'use strict'; |
||||
|
||||
var WebServer = require('./webserver.js').WebServer; |
||||
var WebBrowser = require('./webbrowser.js').WebBrowser; |
||||
var path = require('path'); |
||||
var fs = require('fs'); |
||||
var os = require('os'); |
||||
var url = require('url'); |
||||
var spawn = require('child_process').spawn; |
||||
var testUtils = require('./testutils.js'); |
||||
|
||||
function parseOptions() { |
||||
function describeCheck(fn, text) { |
||||
fn.toString = function () { |
||||
return text; |
||||
}; |
||||
return fn; |
||||
} |
||||
|
||||
var yargs = require('yargs') |
||||
.usage('Usage: $0') |
||||
.boolean(['help', 'masterMode', 'reftest', 'unitTest', 'fontTest', |
||||
'noPrompts', 'noDownload']) |
||||
.string(['manifestFile', 'browser', 'browserManifestFile', |
||||
'port', 'statsFile', 'statsDelay']) |
||||
.alias('browser', 'b').alias('help', 'h').alias('masterMode', 'm') |
||||
.describe('help', 'Show this help message') |
||||
.describe('masterMode', 'Run the script in master mode.') |
||||
.describe('noPrompts', |
||||
'Uses default answers (intended for CLOUD TESTS only!).') |
||||
.describe('manifestFile', |
||||
'A path to JSON file in the form of test_manifest.json') |
||||
.default('manifestFile', 'test_manifest.json') |
||||
.describe('browser', 'The path to a single browser ') |
||||
.describe('browserManifestFile', 'A path to JSON file in the form of ' + |
||||
'those found in resources/browser_manifests/') |
||||
.describe('reftest', 'Automatically start reftest showing comparison ' + |
||||
'test failures, if there are any.') |
||||
.describe('port', 'The port the HTTP server should listen on.') |
||||
.default('port', 8000) |
||||
.describe('unitTest', 'Run the unit tests.') |
||||
.describe('fontTest', 'Run the font tests.') |
||||
.describe('noDownload', 'Skips test PDFs downloading.') |
||||
.describe('statsFile', 'The file where to store stats.') |
||||
.describe('statsDelay', 'The amount of time in milliseconds the browser ' + |
||||
'should wait before starting stats.') |
||||
.default('statsDelay', 0) |
||||
.check(describeCheck(function (argv) { |
||||
return +argv.reftest + argv.unitTest + argv.fontTest + |
||||
argv.masterMode <= 1; |
||||
}, '--reftest, --unitTest, --fontTest and --masterMode must not be ' + |
||||
'specified at the same time.')) |
||||
.check(describeCheck(function (argv) { |
||||
return !argv.masterMode || argv.manifestFile === 'test_manifest.json'; |
||||
}, 'when --masterMode is specified --manifestFile shall be equal ' + |
||||
'test_manifest.json')) |
||||
.check(describeCheck(function (argv) { |
||||
return !argv.browser || !argv.browserManifestFile; |
||||
}, '--browser and --browserManifestFile must not be specified at the ' +'' + |
||||
'same time.')); |
||||
var result = yargs.argv; |
||||
if (result.help) { |
||||
yargs.showHelp(); |
||||
process.exit(0); |
||||
} |
||||
return result; |
||||
} |
||||
|
||||
var refsTmpDir = 'tmp'; |
||||
var testResultDir = 'test_snapshots'; |
||||
var refsDir = 'ref'; |
||||
var eqLog = 'eq.log'; |
||||
var browserTimeout = 120; |
||||
|
||||
function monitorBrowserTimeout(session, onTimeout) { |
||||
if (session.timeoutMonitor) { |
||||
clearTimeout(session.timeoutMonitor); |
||||
} |
||||
if (!onTimeout) { |
||||
session.timeoutMonitor = null; |
||||
return; |
||||
} |
||||
session.timeoutMonitor = setTimeout(function () { |
||||
onTimeout(session); |
||||
}, browserTimeout * 1000); |
||||
} |
||||
|
||||
function updateRefImages() { |
||||
function sync(removeTmp) { |
||||
console.log(' Updating ref/ ... '); |
||||
testUtils.copySubtreeSync(refsTmpDir, refsDir); |
||||
if (removeTmp) { |
||||
testUtils.removeDirSync(refsTmpDir); |
||||
} |
||||
console.log('done'); |
||||
} |
||||
|
||||
if (options.noPrompts) { |
||||
sync(false); // don't remove tmp/ for botio
|
||||
return; |
||||
} |
||||
testUtils.confirm('Would you like to update the master copy in ref/? [yn] ', |
||||
function (confirmed) { |
||||
if (confirmed) { |
||||
sync(true); |
||||
} else { |
||||
console.log(' OK, not updating.'); |
||||
} |
||||
}); |
||||
} |
||||
|
||||
function examineRefImages() { |
||||
startServer(); |
||||
var startUrl = 'http://' + server.host + ':' + server.port + |
||||
'/test/resources/reftest-analyzer.html#web=/test/eq.log'; |
||||
var browser = WebBrowser.create(sessions[0].config); |
||||
browser.start(startUrl); |
||||
} |
||||
|
||||
function startRefTest(masterMode, showRefImages) { |
||||
function finalize() { |
||||
stopServer(); |
||||
var numErrors = 0; |
||||
var numFBFFailures = 0; |
||||
var numEqFailures = 0; |
||||
var numEqNoSnapshot = 0; |
||||
sessions.forEach(function (session) { |
||||
numErrors += session.numErrors; |
||||
numFBFFailures += session.numFBFFailures; |
||||
numEqFailures += session.numEqFailures; |
||||
numEqNoSnapshot += session.numEqNoSnapshot; |
||||
}); |
||||
var numFatalFailures = numErrors + numFBFFailures; |
||||
console.log(); |
||||
if (numFatalFailures + numEqFailures > 0) { |
||||
console.log('OHNOES! Some tests failed!'); |
||||
if (numErrors > 0) { |
||||
console.log(' errors: ' + numErrors); |
||||
} |
||||
if (numEqFailures > 0) { |
||||
console.log(' different ref/snapshot: ' + numEqFailures); |
||||
} |
||||
if (numFBFFailures > 0) { |
||||
console.log(' different first/second rendering: ' + numFBFFailures); |
||||
} |
||||
} else { |
||||
console.log('All regression tests passed.'); |
||||
} |
||||
var runtime = (Date.now() - startTime) / 1000; |
||||
console.log('Runtime was ' + runtime.toFixed(1) + ' seconds'); |
||||
|
||||
if (options.statsFile) { |
||||
fs.writeFileSync(options.statsFile, JSON.stringify(stats, null, 2)); |
||||
} |
||||
if (masterMode) { |
||||
if (numEqFailures + numEqNoSnapshot > 0) { |
||||
console.log(); |
||||
console.log('Some eq tests failed or didn\'t have snapshots.'); |
||||
console.log('Checking to see if master references can be updated...'); |
||||
if (numFatalFailures > 0) { |
||||
console.log(' No. Some non-eq tests failed.'); |
||||
} else { |
||||
console.log( |
||||
' Yes! The references in tmp/ can be synced with ref/.'); |
||||
updateRefImages(); |
||||
} |
||||
} |
||||
} else if (showRefImages && numEqFailures > 0) { |
||||
console.log(); |
||||
console.log('Starting reftest harness to examine ' + numEqFailures + |
||||
' eq test failures.'); |
||||
examineRefImages(numEqFailures); |
||||
} |
||||
} |
||||
|
||||
function setup() { |
||||
if (fs.existsSync(refsTmpDir)) { |
||||
console.error('tmp/ exists -- unable to proceed with testing'); |
||||
process.exit(1); |
||||
} |
||||
|
||||
if (fs.existsSync(eqLog)) { |
||||
fs.unlink(eqLog); |
||||
} |
||||
if (fs.existsSync(testResultDir)) { |
||||
testUtils.removeDirSync(testResultDir); |
||||
} |
||||
|
||||
startTime = Date.now(); |
||||
startServer(); |
||||
server.hooks['POST'].push(refTestPostHandler); |
||||
onAllSessionsClosed = finalize; |
||||
|
||||
startBrowsers('/test/test_slave.html', function (session) { |
||||
session.masterMode = masterMode; |
||||
session.taskResults = {}; |
||||
session.tasks = {}; |
||||
session.remaining = manifest.length; |
||||
manifest.forEach(function (item) { |
||||
var rounds = item.rounds || 1; |
||||
var roundsResults = []; |
||||
roundsResults.length = rounds; |
||||
session.taskResults[item.id] = roundsResults; |
||||
session.tasks[item.id] = item; |
||||
}); |
||||
session.numErrors = 0; |
||||
session.numFBFFailures = 0; |
||||
session.numEqNoSnapshot = 0; |
||||
session.numEqFailures = 0; |
||||
monitorBrowserTimeout(session, handleSessionTimeout); |
||||
}); |
||||
} |
||||
function checkRefsTmp() { |
||||
if (masterMode && fs.existsSync(refsTmpDir)) { |
||||
if (options.noPrompt) { |
||||
testUtils.removeDirSync(refsTmpDir); |
||||
setup(); |
||||
return; |
||||
} |
||||
console.log('Temporary snapshot dir tmp/ is still around.'); |
||||
console.log('tmp/ can be removed if it has nothing you need.'); |
||||
testUtils.confirm('SHOULD THIS SCRIPT REMOVE tmp/? THINK CAREFULLY [yn] ', |
||||
function (confirmed) { |
||||
if (confirmed) { |
||||
testUtils.removeDirSync(refsTmpDir); |
||||
} |
||||
setup(); |
||||
}); |
||||
} else { |
||||
setup(); |
||||
} |
||||
} |
||||
|
||||
var startTime; |
||||
var manifest = JSON.parse(fs.readFileSync(options.manifestFile)); |
||||
if (options.noDownload) { |
||||
checkRefsTmp(); |
||||
} else { |
||||
ensurePDFsDownloaded(checkRefsTmp); |
||||
} |
||||
} |
||||
|
||||
function handleSessionTimeout(session) { |
||||
if (session.closed) { |
||||
return; |
||||
} |
||||
var browser = session.name; |
||||
console.log('TEST-UNEXPECTED-FAIL | test failed ' + browser + |
||||
' has not responded in ' + browserTimeout + 's'); |
||||
session.numErrors += session.remaining; |
||||
session.remaining = 0; |
||||
closeSession(browser); |
||||
} |
||||
|
||||
function checkEq(task, results, browser, masterMode) { |
||||
var taskId = task.id; |
||||
var refSnapshotDir = path.join(refsDir, os.platform(), browser, taskId); |
||||
var testSnapshotDir = path.join(testResultDir, os.platform(), browser, |
||||
taskId); |
||||
|
||||
var pageResults = results[0]; |
||||
var taskType = task.type; |
||||
var numEqNoSnapshot = 0; |
||||
var numEqFailures = 0; |
||||
for (var page = 0; page < pageResults.length; page++) { |
||||
if (!pageResults[page]) { |
||||
continue; |
||||
} |
||||
var testSnapshot = pageResults[page].snapshot; |
||||
if (testSnapshot && testSnapshot.indexOf('data:image/png;base64,') === 0) { |
||||
testSnapshot = new Buffer(testSnapshot.substring(22), 'base64'); |
||||
} else { |
||||
console.error('Valid snapshot was not found.'); |
||||
} |
||||
|
||||
var refSnapshot = null; |
||||
var eq = false; |
||||
var refPath = path.join(refSnapshotDir, (page + 1) + '.png'); |
||||
if (!fs.existsSync(refPath)) { |
||||
numEqNoSnapshot++; |
||||
if (!masterMode) { |
||||
console.log('WARNING: no reference snapshot ' + refPath); |
||||
} |
||||
} else { |
||||
refSnapshot = fs.readFileSync(refPath); |
||||
eq = (refSnapshot.toString('hex') === testSnapshot.toString('hex')); |
||||
if (!eq) { |
||||
console.log('TEST-UNEXPECTED-FAIL | ' + taskType + ' ' + taskId + |
||||
' | in ' + browser + ' | rendering of page ' + (page + 1) + |
||||
' != reference rendering'); |
||||
|
||||
testUtils.ensureDirSync(testSnapshotDir); |
||||
fs.writeFileSync(path.join(testSnapshotDir, (page + 1) + '.png'), |
||||
testSnapshot); |
||||
fs.writeFileSync(path.join(testSnapshotDir, (page + 1) + '_ref.png'), |
||||
refSnapshot); |
||||
|
||||
// NB: this follows the format of Mozilla reftest output so that
|
||||
// we can reuse its reftest-analyzer script
|
||||
fs.appendFileSync(eqLog, 'REFTEST TEST-UNEXPECTED-FAIL | ' + browser + |
||||
'-' + taskId + '-page' + (page + 1) + ' | image comparison (==)\n' + |
||||
'REFTEST IMAGE 1 (TEST): ' + |
||||
path.join(testSnapshotDir, (page + 1) + '.png') + '\n' + |
||||
'REFTEST IMAGE 2 (REFERENCE): ' + |
||||
path.join(testSnapshotDir, (page + 1) + '_ref.png') + '\n'); |
||||
numEqFailures++; |
||||
} |
||||
} |
||||
if (masterMode && (!refSnapshot || !eq)) { |
||||
var tmpSnapshotDir = path.join(refsTmpDir, os.platform(), browser, |
||||
taskId); |
||||
testUtils.ensureDirSync(tmpSnapshotDir); |
||||
fs.writeFileSync(path.join(tmpSnapshotDir, (page + 1) + '.png'), |
||||
testSnapshot); |
||||
} |
||||
} |
||||
|
||||
var session = getSession(browser); |
||||
session.numEqNoSnapshot += numEqNoSnapshot; |
||||
if (numEqFailures > 0) { |
||||
session.numEqFailures += numEqFailures; |
||||
} else { |
||||
console.log('TEST-PASS | ' + taskType + ' test ' + taskId + ' | in ' + |
||||
browser); |
||||
} |
||||
} |
||||
|
||||
function checkFBF(task, results, browser) { |
||||
var numFBFFailures = 0; |
||||
var round0 = results[0], round1 = results[1]; |
||||
if (round0.length !== round1.length) { |
||||
console.error('round 1 and 2 sizes are different'); |
||||
} |
||||
|
||||
for (var page = 0; page < round1.length; page++) { |
||||
var r0Page = round0[page], r1Page = round1[page]; |
||||
if (!r0Page) { |
||||
continue; |
||||
} |
||||
if (r0Page.snapshot !== r1Page.snapshot) { |
||||
console.log('TEST-UNEXPECTED-FAIL | forward-back-forward test ' + |
||||
task.id + ' | in ' + browser + ' | first rendering of page ' + |
||||
(page + 1) + ' != second'); |
||||
numFBFFailures++; |
||||
} |
||||
} |
||||
|
||||
if (numFBFFailures > 0) { |
||||
getSession(browser).numFBFFailures += numFBFFailures; |
||||
} else { |
||||
console.log('TEST-PASS | forward-back-forward test ' + task.id + |
||||
' | in ' + browser); |
||||
} |
||||
} |
||||
|
||||
function checkLoad(task, results, browser) { |
||||
// Load just checks for absence of failure, so if we got here the
|
||||
// test has passed
|
||||
console.log('TEST-PASS | load test ' + task.id + ' | in ' + browser); |
||||
} |
||||
|
||||
function checkRefTestResults(browser, id, results) { |
||||
var failed = false; |
||||
var session = getSession(browser); |
||||
var task = session.tasks[id]; |
||||
results.forEach(function (roundResults, round) { |
||||
roundResults.forEach(function (pageResult, page) { |
||||
if (!pageResult) { |
||||
return; // no results
|
||||
} |
||||
if (pageResult.failure) { |
||||
failed = true; |
||||
if (fs.existsSync(task.file + '.error')) { |
||||
console.log('TEST-SKIPPED | PDF was not downloaded ' + id + ' | in ' + |
||||
browser + ' | page' + (page + 1) + ' round ' + |
||||
(round + 1) + ' | ' + pageResult.failure); |
||||
} else { |
||||
session.numErrors++; |
||||
console.log('TEST-UNEXPECTED-FAIL | test failed ' + id + ' | in ' + |
||||
browser + ' | page' + (page + 1) + ' round ' + |
||||
(round + 1) + ' | ' + pageResult.failure); |
||||
} |
||||
} |
||||
}); |
||||
}); |
||||
if (failed) { |
||||
return; |
||||
} |
||||
switch (task.type) { |
||||
case 'eq': |
||||
case 'text': |
||||
checkEq(task, results, browser, session.masterMode); |
||||
break; |
||||
case 'fbf': |
||||
checkFBF(task, results, browser); |
||||
break; |
||||
case 'load': |
||||
checkLoad(task, results, browser); |
||||
break; |
||||
default: |
||||
throw new Error('Unknown test type'); |
||||
} |
||||
} |
||||
|
||||
function refTestPostHandler(req, res) { |
||||
var parsedUrl = url.parse(req.url, true); |
||||
var pathname = parsedUrl.pathname; |
||||
if (pathname !== '/tellMeToQuit' && |
||||
pathname !== '/info' && |
||||
pathname !== '/submit_task_results') { |
||||
return false; |
||||
} |
||||
|
||||
var body = ''; |
||||
req.on('data', function (data) { |
||||
body += data; |
||||
}); |
||||
req.on('end', function () { |
||||
res.writeHead(200, {'Content-Type': 'text/plain'}); |
||||
res.end(); |
||||
|
||||
if (pathname === '/tellMeToQuit') { |
||||
// finding by path
|
||||
var browserPath = parsedUrl.query.path; |
||||
var session = sessions.filter(function (session) { |
||||
return session.config.path === browserPath; |
||||
})[0]; |
||||
monitorBrowserTimeout(session, null); |
||||
closeSession(session.name); |
||||
return; |
||||
} |
||||
|
||||
var data = JSON.parse(body); |
||||
if (pathname === '/info') { |
||||
console.log(data.message); |
||||
return; |
||||
} |
||||
|
||||
var browser = data.browser; |
||||
var round = data.round; |
||||
var id = data.id; |
||||
var page = data.page - 1; |
||||
var failure = data.failure; |
||||
var snapshot = data.snapshot; |
||||
var lastPageNum = data.lastPageNum; |
||||
|
||||
var session = getSession(browser); |
||||
monitorBrowserTimeout(session, handleSessionTimeout); |
||||
|
||||
var taskResults = session.taskResults[id]; |
||||
if (!taskResults[round]) { |
||||
taskResults[round] = []; |
||||
} |
||||
|
||||
if (taskResults[round][page]) { |
||||
console.error('Results for ' + browser + ':' + id + ':' + round + |
||||
':' + page + ' were already submitted'); |
||||
// TODO abort testing here?
|
||||
} |
||||
|
||||
taskResults[round][page] = { |
||||
failure: failure, |
||||
snapshot: snapshot |
||||
}; |
||||
if (stats) { |
||||
stats.push({ |
||||
'browser': browser, |
||||
'pdf': id, |
||||
'page': page, |
||||
'round': round, |
||||
'stats': data.stats |
||||
}); |
||||
} |
||||
|
||||
var isDone = taskResults[taskResults.length - 1] && |
||||
taskResults[taskResults.length - 1][lastPageNum - 1]; |
||||
if (isDone) { |
||||
checkRefTestResults(browser, id, taskResults); |
||||
session.remaining--; |
||||
} |
||||
}); |
||||
return true; |
||||
} |
||||
|
||||
function startUnitTest(url, name) { |
||||
var startTime = Date.now(); |
||||
startServer(); |
||||
server.hooks['POST'].push(unitTestPostHandler); |
||||
onAllSessionsClosed = function () { |
||||
stopServer(); |
||||
var numRuns = 0, numErrors = 0; |
||||
sessions.forEach(function (session) { |
||||
numRuns += session.numRuns; |
||||
numErrors += session.numErrors; |
||||
}); |
||||
console.log(); |
||||
console.log('Run ' + numRuns + ' tests'); |
||||
if (numErrors > 0) { |
||||
console.log('OHNOES! Some ' + name + ' tests failed!'); |
||||
console.log(' ' + numErrors + ' of ' + numRuns + ' failed'); |
||||
} else { |
||||
console.log('All ' + name + ' tests passed.'); |
||||
} |
||||
var runtime = (Date.now() - startTime) / 1000; |
||||
console.log(name + ' tests runtime was ' + runtime.toFixed(1) + ' seconds'); |
||||
}; |
||||
startBrowsers(url, function (session) { |
||||
session.numRuns = 0; |
||||
session.numErrors = 0; |
||||
}); |
||||
} |
||||
|
||||
function unitTestPostHandler(req, res) { |
||||
var parsedUrl = url.parse(req.url); |
||||
var pathname = parsedUrl.pathname; |
||||
if (pathname !== '/tellMeToQuit' && |
||||
pathname !== '/info' && |
||||
pathname !== '/ttx' && |
||||
pathname !== '/submit_task_results') { |
||||
return false; |
||||
} |
||||
|
||||
var body = ''; |
||||
req.on('data', function (data) { |
||||
body += data; |
||||
}); |
||||
req.on('end', function () { |
||||
if (pathname === '/ttx') { |
||||
var translateFont = require('./font/ttxdriver.js').translateFont; |
||||
var onCancel = null, ttxTimeout = 10000; |
||||
var timeoutId = setTimeout(function () { |
||||
if (onCancel) { |
||||
onCancel('TTX timeout'); |
||||
} |
||||
}, ttxTimeout); |
||||
translateFont(body, function (fn) { |
||||
onCancel = fn; |
||||
}, function (err, xml) { |
||||
clearTimeout(timeoutId); |
||||
res.writeHead(200, {'Content-Type': 'text/xml'}); |
||||
res.end(err ? '<error>' + err + '</error>' : xml); |
||||
}); |
||||
return; |
||||
} |
||||
|
||||
res.writeHead(200, {'Content-Type': 'text/plain'}); |
||||
res.end(); |
||||
|
||||
var data = JSON.parse(body); |
||||
if (pathname === '/tellMeToQuit') { |
||||
closeSession(data.browser); |
||||
return; |
||||
} |
||||
if (pathname === '/info') { |
||||
console.log(data.message); |
||||
return; |
||||
} |
||||
var session = getSession(data.browser); |
||||
session.numRuns++; |
||||
var message = data.status + ' | ' + data.description; |
||||
if (data.status === 'TEST-UNEXPECTED-FAIL') { |
||||
session.numErrors++; |
||||
} |
||||
if (data.error) { |
||||
message += ' | ' + data.error; |
||||
} |
||||
console.log(message); |
||||
}); |
||||
return true; |
||||
} |
||||
|
||||
function startBrowsers(url, initSessionCallback) { |
||||
var browsers; |
||||
if (options.browserManifestFile) { |
||||
browsers = JSON.parse(fs.readFileSync(options.browserManifestFile)); |
||||
} else if (options.browser) { |
||||
var browserPath = options.browser; |
||||
var name = path.basename(browserPath, path.extname(browserPath)); |
||||
browsers = [{name: name, path: browserPath}]; |
||||
} else { |
||||
console.error('Specify either browser or browserManifestFile.'); |
||||
process.exit(1); |
||||
} |
||||
sessions = []; |
||||
browsers.forEach(function (b) { |
||||
var browser = WebBrowser.create(b); |
||||
var startUrl = getServerBaseAddress() + url + |
||||
'?browser=' + encodeURIComponent(b.name) + |
||||
'&manifestFile=' + encodeURIComponent('/test/' + options.manifestFile) + |
||||
'&path=' + encodeURIComponent(b.path) + |
||||
'&delay=' + options.statsDelay + |
||||
'&masterMode=' + options.masterMode; |
||||
browser.start(startUrl); |
||||
var session = { |
||||
name: b.name, |
||||
config: b, |
||||
browser: browser, |
||||
closed: false |
||||
}; |
||||
if (initSessionCallback) { |
||||
initSessionCallback(session); |
||||
} |
||||
sessions.push(session); |
||||
}); |
||||
} |
||||
|
||||
function stopBrowsers(callback) { |
||||
var count = sessions.length; |
||||
sessions.forEach(function (session) { |
||||
if (session.closed) { |
||||
return; |
||||
} |
||||
session.browser.stop(function () { |
||||
session.closed = true; |
||||
count--; |
||||
if (count === 0 && callback) { |
||||
callback(); |
||||
} |
||||
}); |
||||
}); |
||||
} |
||||
|
||||
function getServerBaseAddress() { |
||||
return 'http://' + host + ':' + server.port; |
||||
} |
||||
|
||||
function startServer() { |
||||
server = new WebServer(); |
||||
server.host = host; |
||||
server.port = options.port; |
||||
server.root = '..'; |
||||
server.start(); |
||||
} |
||||
|
||||
function stopServer() { |
||||
server.stop(); |
||||
} |
||||
|
||||
function getSession(browser) { |
||||
return sessions.filter(function (session) { |
||||
return session.name === browser; |
||||
})[0]; |
||||
} |
||||
|
||||
function closeSession(browser) { |
||||
var i = 0; |
||||
while (i < sessions.length && sessions[i].name !== browser) { |
||||
i++; |
||||
} |
||||
if (i < sessions.length) { |
||||
var session = sessions[i]; |
||||
session.browser.stop(function () { |
||||
session.closed = true; |
||||
var allClosed = sessions.every(function (s) { |
||||
return s.closed; |
||||
}); |
||||
if (allClosed && onAllSessionsClosed) { |
||||
onAllSessionsClosed(); |
||||
} |
||||
}); |
||||
} |
||||
} |
||||
|
||||
function ensurePDFsDownloaded(callback) { |
||||
var downloadUtils = require('./downloadutils.js'); |
||||
var downloadManifestFiles = downloadUtils.downloadManifestFiles; |
||||
var manifest = JSON.parse(fs.readFileSync(options.manifestFile)); |
||||
downloadUtils.downloadManifestFiles(manifest, function () { |
||||
downloadUtils.verifyManifestFiles(manifest, function (hasErrors) { |
||||
if (hasErrors) { |
||||
console.log('Unable to verify the checksum for the files that are ' + |
||||
'used for testing.'); |
||||
console.log('Please re-download the files, or adjust the MD5 ' + |
||||
'checksum in the manifest for the files listed above.\n'); |
||||
} |
||||
callback(); |
||||
}); |
||||
}); |
||||
} |
||||
|
||||
function main() { |
||||
if (options.statsFile) { |
||||
stats = []; |
||||
} |
||||
|
||||
if (!options.browser && !options.browserManifestFile) { |
||||
startServer(); |
||||
} else if (options.unitTest) { |
||||
startUnitTest('/test/unit/unit_test.html', 'unit'); |
||||
} else if (options.fontTest) { |
||||
startUnitTest('/test/font/font_test.html', 'font'); |
||||
} else { |
||||
startRefTest(options.masterMode, options.reftest); |
||||
} |
||||
} |
||||
|
||||
var server; |
||||
var sessions; |
||||
var onAllSessionsClosed; |
||||
var host = '127.0.0.1'; |
||||
var options = parseOptions(); |
||||
var stats; |
||||
|
||||
main(); |
@ -1,960 +0,0 @@
@@ -1,960 +0,0 @@
|
||||
# Copyright 2012 Mozilla Foundation |
||||
# |
||||
# Licensed under the Apache License, Version 2.0 (the "License"); |
||||
# you may not use this file except in compliance with the License. |
||||
# You may obtain a copy of the License at |
||||
# |
||||
# http://www.apache.org/licenses/LICENSE-2.0 |
||||
# |
||||
# Unless required by applicable law or agreed to in writing, software |
||||
# distributed under the License is distributed on an "AS IS" BASIS, |
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
# See the License for the specific language governing permissions and |
||||
# limitations under the License. |
||||
|
||||
import json, platform, os, shutil, sys, subprocess, tempfile, threading |
||||
import time, urllib, urllib2, hashlib, re, base64, uuid, socket, errno |
||||
import traceback |
||||
from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer |
||||
from SocketServer import ThreadingMixIn |
||||
from optparse import OptionParser |
||||
from urlparse import urlparse, parse_qs |
||||
from threading import Lock |
||||
|
||||
USAGE_EXAMPLE = "%prog" |
||||
|
||||
# The local web server uses the git repo as the document root. |
||||
DOC_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__),"..")) |
||||
|
||||
GIT_CLONE_CHECK = True |
||||
DEFAULT_MANIFEST_FILE = 'test_manifest.json' |
||||
EQLOG_FILE = 'eq.log' |
||||
BROWSERLOG_FILE = 'browser.log' |
||||
REFDIR = 'ref' |
||||
TEST_SNAPSHOTS = 'test_snapshots' |
||||
TMPDIR = 'tmp' |
||||
VERBOSE = False |
||||
BROWSER_TIMEOUT = 120 |
||||
|
||||
SERVER_HOST = "localhost" |
||||
|
||||
lock = Lock() |
||||
|
||||
class TestOptions(OptionParser): |
||||
def __init__(self, **kwargs): |
||||
OptionParser.__init__(self, **kwargs) |
||||
self.add_option("-m", "--masterMode", action="store_true", dest="masterMode", |
||||
help="Run the script in master mode.", default=False) |
||||
self.add_option("--noPrompts", action="store_true", dest="noPrompts", |
||||
help="Uses default answers (intended for CLOUD TESTS only!).", default=False) |
||||
self.add_option("--manifestFile", action="store", type="string", dest="manifestFile", |
||||
help="A JSON file in the form of test_manifest.json (the default).") |
||||
self.add_option("-b", "--browser", action="store", type="string", dest="browser", |
||||
help="The path to a single browser (right now, only Firefox is supported).") |
||||
self.add_option("--browserManifestFile", action="store", type="string", |
||||
dest="browserManifestFile", |
||||
help="A JSON file in the form of those found in resources/browser_manifests") |
||||
self.add_option("--reftest", action="store_true", dest="reftest", |
||||
help="Automatically start reftest showing comparison test failures, if there are any.", |
||||
default=False) |
||||
self.add_option("--port", action="store", dest="port", type="int", |
||||
help="The port the HTTP server should listen on.", default=8080) |
||||
self.add_option("--unitTest", action="store_true", dest="unitTest", |
||||
help="Run the unit tests.", default=False) |
||||
self.add_option("--fontTest", action="store_true", dest="fontTest", |
||||
help="Run the font tests.", default=False) |
||||
self.add_option("--noDownload", action="store_true", dest="noDownload", |
||||
help="Skips test PDFs downloading.", default=False) |
||||
self.add_option("--statsFile", action="store", dest="statsFile", type="string", |
||||
help="The file where to store stats.", default=None) |
||||
self.add_option("--statsDelay", action="store", dest="statsDelay", type="int", |
||||
help="The amount of time in milliseconds the browser should wait before starting stats.", default=10000) |
||||
self.set_usage(USAGE_EXAMPLE) |
||||
|
||||
def verifyOptions(self, options): |
||||
if options.reftest and (options.unitTest or options.fontTest): |
||||
self.error("--reftest and --unitTest/--fontTest must not be specified at the same time.") |
||||
if options.masterMode and options.manifestFile: |
||||
self.error("--masterMode and --manifestFile must not be specified at the same time.") |
||||
if not options.manifestFile: |
||||
options.manifestFile = DEFAULT_MANIFEST_FILE |
||||
if options.browser and options.browserManifestFile: |
||||
print "Warning: ignoring browser argument since manifest file was also supplied" |
||||
if not options.browser and not options.browserManifestFile: |
||||
print "Starting server on port %s." % options.port |
||||
if not options.statsFile: |
||||
options.statsDelay = 0 |
||||
|
||||
return options |
||||
|
||||
|
||||
def prompt(question): |
||||
'''Return True iff the user answered "yes" to |question|.''' |
||||
inp = raw_input(question +' [yes/no] > ') |
||||
return inp == 'yes' |
||||
|
||||
MIMEs = { |
||||
'.css': 'text/css', |
||||
'.html': 'text/html', |
||||
'.js': 'application/javascript', |
||||
'.json': 'application/json', |
||||
'.svg': 'image/svg+xml', |
||||
'.pdf': 'application/pdf', |
||||
'.xhtml': 'application/xhtml+xml', |
||||
'.gif': 'image/gif', |
||||
'.ico': 'image/x-icon', |
||||
'.png': 'image/png', |
||||
'.log': 'text/plain', |
||||
'.bcmap': 'application/octet-stream', |
||||
'.properties': 'text/plain' |
||||
} |
||||
|
||||
class State: |
||||
browsers = [ ] |
||||
manifest = { } |
||||
taskResults = { } |
||||
remaining = { } |
||||
results = { } |
||||
done = False |
||||
numErrors = 0 |
||||
numEqFailures = 0 |
||||
numEqNoSnapshot = 0 |
||||
numFBFFailures = 0 |
||||
numLoadFailures = 0 |
||||
eqLog = None |
||||
saveStats = False |
||||
stats = [ ] |
||||
lastPost = { } |
||||
|
||||
class UnitTestState: |
||||
browsers = [ ] |
||||
browsersRunning = 0 |
||||
lastPost = { } |
||||
numErrors = 0 |
||||
numRun = 0 |
||||
|
||||
class Result: |
||||
def __init__(self, snapshot, failure, page): |
||||
self.snapshot = snapshot |
||||
self.failure = failure |
||||
self.page = page |
||||
|
||||
class TestServer(ThreadingMixIn, HTTPServer): |
||||
pass |
||||
|
||||
class TestHandlerBase(BaseHTTPRequestHandler): |
||||
# Disable annoying noise by default |
||||
def log_request(code=0, size=0): |
||||
if VERBOSE: |
||||
BaseHTTPRequestHandler.log_request(code, size) |
||||
|
||||
def handle_one_request(self): |
||||
try: |
||||
BaseHTTPRequestHandler.handle_one_request(self) |
||||
except socket.error, v: |
||||
if v[0] == errno.ECONNRESET: |
||||
# Ignoring connection reset by peer exceptions |
||||
if VERBOSE: |
||||
print 'Detected connection reset' |
||||
elif v[0] == errno.EPIPE: |
||||
if VERBOSE: |
||||
print 'Detected remote peer disconnected' |
||||
elif v[0] == 10053: |
||||
if VERBOSE: |
||||
print 'An established connection was aborted by the' \ |
||||
' software in your host machine' |
||||
else: |
||||
raise |
||||
|
||||
def finish(self,*args,**kw): |
||||
# From http://stackoverflow.com/a/14355079/1834797 |
||||
try: |
||||
if not self.wfile.closed: |
||||
self.wfile.flush() |
||||
self.wfile.close() |
||||
except socket.error: |
||||
pass |
||||
self.rfile.close() |
||||
|
||||
def sendFile(self, path, ext): |
||||
self.send_response(200) |
||||
self.send_header("Accept-Ranges", "bytes") |
||||
self.send_header("Content-Type", MIMEs[ext]) |
||||
self.send_header("Content-Length", os.path.getsize(path)) |
||||
self.end_headers() |
||||
with open(path, "rb") as f: |
||||
self.wfile.write(f.read()) |
||||
|
||||
def sendFileRange(self, path, ext, start, end): |
||||
file_len = os.path.getsize(path) |
||||
if (end is None) or (file_len < end): |
||||
end = file_len |
||||
if (file_len < start) or (end <= start): |
||||
self.send_error(416) |
||||
return |
||||
chunk_len = end - start |
||||
time.sleep(chunk_len / 1000000.0) |
||||
self.send_response(206) |
||||
self.send_header("Accept-Ranges", "bytes") |
||||
self.send_header("Content-Type", MIMEs[ext]) |
||||
self.send_header("Content-Length", chunk_len) |
||||
self.send_header("Content-Range", 'bytes ' + str(start) + '-' + str(end - 1) + '/' + str(file_len)) |
||||
self.end_headers() |
||||
with open(path, "rb") as f: |
||||
f.seek(start) |
||||
self.wfile.write(f.read(chunk_len)) |
||||
|
||||
def do_GET(self): |
||||
url = urlparse(self.path) |
||||
|
||||
# Ignore query string |
||||
path, _ = urllib.unquote_plus(url.path), url.query |
||||
path = os.path.abspath(os.path.realpath(DOC_ROOT + os.sep + path)) |
||||
prefix = os.path.commonprefix(( path, DOC_ROOT )) |
||||
_, ext = os.path.splitext(path.lower()) |
||||
|
||||
if url.path == "/favicon.ico": |
||||
self.sendFile(os.path.join(DOC_ROOT, "test", "resources", "favicon.ico"), ext) |
||||
return |
||||
|
||||
if os.path.isdir(path): |
||||
self.sendIndex(url.path, url.query) |
||||
return |
||||
|
||||
pieces = path.split(os.sep); |
||||
if pieces[len(pieces) - 2] == 'cmaps': |
||||
self.sendFile(path, '.properties'); |
||||
return |
||||
|
||||
if not (prefix == DOC_ROOT |
||||
and os.path.isfile(path) |
||||
and ext in MIMEs): |
||||
print path |
||||
self.send_error(404) |
||||
return |
||||
|
||||
if 'Range' in self.headers: |
||||
range_re = re.compile(r"^bytes=(\d+)\-(\d+)?") |
||||
parsed_range = range_re.search(self.headers.getheader("Range")) |
||||
if parsed_range is None: |
||||
self.send_error(501) |
||||
return |
||||
if VERBOSE: |
||||
print 'Range requested %s - %s: %s' % ( |
||||
parsed_range.group(1), parsed_range.group(2)) |
||||
start = int(parsed_range.group(1)) |
||||
if parsed_range.group(2) is None: |
||||
self.sendFileRange(path, ext, start, None) |
||||
else: |
||||
end = int(parsed_range.group(2)) + 1 |
||||
self.sendFileRange(path, ext, start, end) |
||||
return |
||||
|
||||
self.sendFile(path, ext) |
||||
|
||||
class UnitTestHandler(TestHandlerBase): |
||||
def sendIndex(self, path, query): |
||||
print "send index" |
||||
|
||||
def translateFont(self, base64Data): |
||||
self.send_response(200) |
||||
self.send_header("Content-Type", "text/xml") |
||||
self.end_headers() |
||||
|
||||
data = base64.b64decode(base64Data) |
||||
taskId = str(uuid.uuid4()) |
||||
fontPath = 'ttx/' + taskId + '.otf' |
||||
resultPath = 'ttx/' + taskId + '.ttx' |
||||
with open(fontPath, "wb") as f: |
||||
f.write(data) |
||||
|
||||
# When fontTools used directly, we need to snif ttx file |
||||
# to check what version of python is used |
||||
ttxPath = '' |
||||
for path in os.environ["PATH"].split(os.pathsep): |
||||
if os.path.isfile(path + os.sep + "ttx"): |
||||
ttxPath = path + os.sep + "ttx" |
||||
break |
||||
if ttxPath == '': |
||||
self.wfile.write("<error>TTX was not found</error>") |
||||
return |
||||
|
||||
ttxRunner = '' |
||||
with open(ttxPath, "r") as f: |
||||
firstLine = f.readline() |
||||
if firstLine[:2] == '#!' and firstLine.find('python') > -1: |
||||
ttxRunner = firstLine[2:].strip() |
||||
|
||||
with open(os.devnull, "w") as fnull: |
||||
if ttxRunner != '': |
||||
result = subprocess.call([ttxRunner, ttxPath, fontPath], stdout = fnull) |
||||
else: |
||||
result = subprocess.call([ttxPath, fontPath], stdout = fnull) |
||||
|
||||
os.remove(fontPath) |
||||
|
||||
if not os.path.isfile(resultPath): |
||||
self.wfile.write("<error>Output was not generated</error>") |
||||
return |
||||
|
||||
with open(resultPath, "rb") as f: |
||||
self.wfile.write(f.read()) |
||||
|
||||
os.remove(resultPath) |
||||
|
||||
return |
||||
|
||||
def do_POST(self): |
||||
with lock: |
||||
url = urlparse(self.path) |
||||
numBytes = int(self.headers['Content-Length']) |
||||
content = self.rfile.read(numBytes) |
||||
|
||||
# Process special utility requests |
||||
if url.path == '/ttx': |
||||
self.translateFont(content) |
||||
return |
||||
|
||||
self.send_response(200) |
||||
self.send_header('Content-Type', 'text/plain') |
||||
self.end_headers() |
||||
|
||||
result = json.loads(content) |
||||
browser = result['browser'] |
||||
UnitTestState.lastPost[browser] = int(time.time()) |
||||
if url.path == "/tellMeToQuit": |
||||
tellAppToQuit(url.path, url.query) |
||||
UnitTestState.browsersRunning -= 1 |
||||
UnitTestState.lastPost[browser] = None |
||||
return |
||||
elif url.path == '/info': |
||||
print result['message'] |
||||
elif url.path == '/submit_task_results': |
||||
status, description = result['status'], result['description'] |
||||
UnitTestState.numRun += 1 |
||||
if status == 'TEST-UNEXPECTED-FAIL': |
||||
UnitTestState.numErrors += 1 |
||||
message = status + ' | ' + description + ' | in ' + browser |
||||
if 'error' in result: |
||||
message += ' | ' + result['error'] |
||||
print message |
||||
else: |
||||
print 'Error: uknown action' + url.path |
||||
|
||||
class PDFTestHandler(TestHandlerBase): |
||||
|
||||
def sendIndex(self, path, query): |
||||
if not path.endswith("/"): |
||||
# we need trailing slash |
||||
self.send_response(301) |
||||
redirectLocation = path + "/" |
||||
if query: |
||||
redirectLocation += "?" + query |
||||
self.send_header("Location", redirectLocation) |
||||
self.end_headers() |
||||
return |
||||
|
||||
self.send_response(200) |
||||
self.send_header("Content-Type", "text/html") |
||||
self.end_headers() |
||||
if query == "frame": |
||||
self.wfile.write("<html><frameset cols=*,200><frame name=pdf>" + |
||||
"<frame src='" + path + "'></frameset></html>") |
||||
return |
||||
|
||||
location = os.path.abspath(os.path.realpath(DOC_ROOT + os.sep + path)) |
||||
self.wfile.write("<html><body><h1>PDFs of " + path + "</h1>\n") |
||||
for filename in os.listdir(location): |
||||
if filename.lower().endswith('.pdf'): |
||||
self.wfile.write("<a href='/web/viewer.html?file=" + |
||||
urllib.quote_plus(path + filename, '/') + "' target=pdf>" + |
||||
filename + "</a><br>\n") |
||||
self.wfile.write("</body></html>") |
||||
|
||||
|
||||
def do_POST(self): |
||||
with lock: |
||||
numBytes = int(self.headers['Content-Length']) |
||||
|
||||
self.send_response(200) |
||||
self.send_header('Content-Type', 'text/plain') |
||||
self.end_headers() |
||||
|
||||
url = urlparse(self.path) |
||||
if url.path == "/tellMeToQuit": |
||||
tellAppToQuit(url.path, url.query) |
||||
return |
||||
|
||||
result = json.loads(self.rfile.read(numBytes)) |
||||
browser = result['browser'] |
||||
State.lastPost[browser] = int(time.time()) |
||||
if url.path == "/info": |
||||
print result['message'] |
||||
return |
||||
|
||||
id = result['id'] |
||||
failure = result['failure'] |
||||
round = result['round'] |
||||
page = result['page'] |
||||
snapshot = result['snapshot'] |
||||
|
||||
taskResults = State.taskResults[browser][id] |
||||
taskResults[round].append(Result(snapshot, failure, page)) |
||||
if State.saveStats: |
||||
stat = { |
||||
'browser': browser, |
||||
'pdf': id, |
||||
'page': page, |
||||
'round': round, |
||||
'stats': result['stats'] |
||||
} |
||||
State.stats.append(stat) |
||||
|
||||
def isTaskDone(): |
||||
last_page_num = result['lastPageNum'] |
||||
rounds = State.manifest[id]['rounds'] |
||||
for round in range(0,rounds): |
||||
if not taskResults[round]: |
||||
return False |
||||
latest_page = taskResults[round][-1] |
||||
if not latest_page.page == last_page_num: |
||||
return False |
||||
return True |
||||
|
||||
if isTaskDone(): |
||||
# sort the results since they sometimes come in out of order |
||||
for results in taskResults: |
||||
results.sort(key=lambda result: result.page) |
||||
check(State.manifest[id], taskResults, browser, |
||||
self.server.masterMode) |
||||
# Please oh please GC this ... |
||||
del State.taskResults[browser][id] |
||||
State.remaining[browser] -= 1 |
||||
|
||||
checkIfDone() |
||||
|
||||
def checkIfDone(): |
||||
State.done = True |
||||
for key in State.remaining: |
||||
if State.remaining[key] != 0: |
||||
State.done = False |
||||
return |
||||
|
||||
# Applescript hack to quit Chrome on Mac |
||||
def tellAppToQuit(path, query): |
||||
if platform.system() != "Darwin": |
||||
return |
||||
d = parse_qs(query) |
||||
path = d['path'][0] |
||||
cmd = """osascript<<END |
||||
tell application "%s" |
||||
quit |
||||
end tell |
||||
END""" % path |
||||
os.system(cmd) |
||||
|
||||
class BaseBrowserCommand(object): |
||||
def __init__(self, browserRecord): |
||||
self.name = browserRecord["name"] |
||||
self.path = browserRecord["path"] |
||||
self.tempDir = None |
||||
self.process = None |
||||
|
||||
if platform.system() == "Darwin" and (self.path.endswith(".app") or self.path.endswith(".app/")): |
||||
self._fixupMacPath() |
||||
|
||||
if not os.path.exists(self.path): |
||||
raise Exception("Path to browser '%s' does not exist." % self.path) |
||||
|
||||
def setup(self): |
||||
self.tempDir = tempfile.mkdtemp() |
||||
self.profileDir = os.path.join(self.tempDir, "profile") |
||||
self.browserLog = open(BROWSERLOG_FILE, "w") |
||||
|
||||
def teardown(self): |
||||
self.process.terminate() |
||||
|
||||
# If the browser is still running, wait up to ten seconds for it to quit |
||||
if self.process and self.process.poll() is None: |
||||
checks = 0 |
||||
while self.process.poll() is None and checks < 20: |
||||
checks += 1 |
||||
time.sleep(.5) |
||||
# If it's still not dead, try to kill it |
||||
if self.process.poll() is None: |
||||
print "Process %s is still running. Killing." % self.name |
||||
self.process.kill() |
||||
self.process.wait() |
||||
|
||||
if self.tempDir is not None and os.path.exists(self.tempDir): |
||||
shutil.rmtree(self.tempDir) |
||||
|
||||
self.browserLog.close() |
||||
|
||||
def start(self, url): |
||||
raise Exception("Can't start BaseBrowserCommand") |
||||
|
||||
class FirefoxBrowserCommand(BaseBrowserCommand): |
||||
def _fixupMacPath(self): |
||||
self.path = os.path.join(self.path, "Contents", "MacOS", "firefox-bin") |
||||
|
||||
def setup(self): |
||||
super(FirefoxBrowserCommand, self).setup() |
||||
shutil.copytree(os.path.join(DOC_ROOT, "test", "resources", "firefox"), |
||||
self.profileDir) |
||||
|
||||
def start(self, url): |
||||
cmds = [self.path] |
||||
if platform.system() == "Darwin": |
||||
cmds.append("-foreground") |
||||
cmds.extend(["-no-remote", "-profile", self.profileDir, url]) |
||||
self.process = subprocess.Popen(cmds, stdout = self.browserLog, stderr = self.browserLog) |
||||
|
||||
class ChromeBrowserCommand(BaseBrowserCommand): |
||||
def _fixupMacPath(self): |
||||
self.path = os.path.join(self.path, "Contents", "MacOS", "Google Chrome") |
||||
|
||||
def start(self, url): |
||||
cmds = [self.path] |
||||
cmds.extend(["--user-data-dir=%s" % self.profileDir, |
||||
"--no-first-run", "--disable-sync", url]) |
||||
self.process = subprocess.Popen(cmds, stdout = self.browserLog, stderr = self.browserLog) |
||||
|
||||
def makeBrowserCommand(browser): |
||||
path = browser["path"].lower() |
||||
name = browser["name"] |
||||
if name is not None: |
||||
name = name.lower() |
||||
|
||||
types = {"firefox": FirefoxBrowserCommand, |
||||
"chrome": ChromeBrowserCommand } |
||||
command = None |
||||
for key in types.keys(): |
||||
if (name and name.find(key) > -1) or path.find(key) > -1: |
||||
command = types[key](browser) |
||||
command.name = command.name or key |
||||
break |
||||
|
||||
if command is None: |
||||
raise Exception("Unrecognized browser: %s" % browser) |
||||
|
||||
return command |
||||
|
||||
def makeBrowserCommands(browserManifestFile): |
||||
with open(browserManifestFile) as bmf: |
||||
browsers = [makeBrowserCommand(browser) for browser in json.load(bmf)] |
||||
return browsers |
||||
|
||||
def downloadLinkedPDF(f): |
||||
linkFile = open(f +'.link') |
||||
link = linkFile.read() |
||||
linkFile.close() |
||||
|
||||
sys.stdout.write('Downloading '+ link +' to '+ f +' ...') |
||||
sys.stdout.flush() |
||||
response = urllib2.urlopen(link) |
||||
|
||||
with open(f, 'wb') as out: |
||||
out.write(response.read()) |
||||
|
||||
print 'done' |
||||
|
||||
def downloadLinkedPDFs(manifestList): |
||||
for item in manifestList: |
||||
f, isLink = item['file'], item.get('link', False) |
||||
if isLink and not os.access(f, os.R_OK): |
||||
try: |
||||
downloadLinkedPDF(f) |
||||
except: |
||||
exc_type, exc_value, exc_traceback = sys.exc_info() |
||||
print 'ERROR: Unable to download file "' + f + '".' |
||||
open(f, 'wb').close() |
||||
with open(f + '.error', 'w') as out: |
||||
out.write('\n'.join(traceback.format_exception(exc_type, |
||||
exc_value, |
||||
exc_traceback))) |
||||
|
||||
def verifyPDFs(manifestList): |
||||
error = False |
||||
for item in manifestList: |
||||
f = item['file'] |
||||
if os.path.isfile(f + '.error'): |
||||
print 'WARNING: File was not downloaded. See "' + f + '.error" file.' |
||||
error = True |
||||
elif os.access(f, os.R_OK): |
||||
fileMd5 = hashlib.md5(open(f, 'rb').read()).hexdigest() |
||||
if 'md5' not in item: |
||||
print 'WARNING: Missing md5 for file "' + f + '".', |
||||
print 'Hash for current file is "' + fileMd5 + '"' |
||||
error = True |
||||
continue |
||||
md5 = item['md5'] |
||||
if fileMd5 != md5: |
||||
print 'WARNING: MD5 of file "' + f + '" does not match file.', |
||||
print 'Expected "' + md5 + '" computed "' + fileMd5 + '"' |
||||
error = True |
||||
continue |
||||
else: |
||||
print 'WARNING: Unable to open file for reading "' + f + '".' |
||||
error = True |
||||
return not error |
||||
|
||||
def getTestBrowsers(options): |
||||
testBrowsers = [] |
||||
if options.browserManifestFile: |
||||
testBrowsers = makeBrowserCommands(options.browserManifestFile) |
||||
elif options.browser: |
||||
testBrowsers = [makeBrowserCommand({"path":options.browser, "name":None})] |
||||
|
||||
if options.browserManifestFile or options.browser: |
||||
assert len(testBrowsers) > 0 |
||||
return testBrowsers |
||||
|
||||
def setUp(options): |
||||
# Only serve files from a pdf.js clone |
||||
assert not GIT_CLONE_CHECK or os.path.isfile('../src/pdf.js') and os.path.isdir('../.git') |
||||
|
||||
if options.masterMode and os.path.isdir(TMPDIR): |
||||
print 'Temporary snapshot dir tmp/ is still around.' |
||||
print 'tmp/ can be removed if it has nothing you need.' |
||||
if options.noPrompts or prompt('SHOULD THIS SCRIPT REMOVE tmp/? THINK CAREFULLY'): |
||||
subprocess.call(( 'rm', '-rf', 'tmp' )) |
||||
|
||||
assert not os.path.isdir(TMPDIR) |
||||
|
||||
testBrowsers = getTestBrowsers(options) |
||||
|
||||
with open(options.manifestFile) as mf: |
||||
manifestList = json.load(mf) |
||||
|
||||
if not options.noDownload: |
||||
downloadLinkedPDFs(manifestList) |
||||
|
||||
if not verifyPDFs(manifestList): |
||||
print 'Unable to verify the checksum for the files that are used for testing.' |
||||
print 'Please re-download the files, or adjust the MD5 checksum in the manifest for the files listed above.\n' |
||||
|
||||
for b in testBrowsers: |
||||
State.taskResults[b.name] = { } |
||||
State.remaining[b.name] = len(manifestList) |
||||
State.lastPost[b.name] = int(time.time()) |
||||
for item in manifestList: |
||||
id, rounds = item['id'], int(item['rounds']) |
||||
State.manifest[id] = item |
||||
taskResults = [ ] |
||||
for r in xrange(rounds): |
||||
taskResults.append([ ]) |
||||
State.taskResults[b.name][id] = taskResults |
||||
|
||||
if options.statsFile != None: |
||||
State.saveStats = True |
||||
return testBrowsers |
||||
|
||||
def setUpUnitTests(options): |
||||
# Only serve files from a pdf.js clone |
||||
assert not GIT_CLONE_CHECK or os.path.isfile('../src/pdf.js') and os.path.isdir('../.git') |
||||
|
||||
testBrowsers = getTestBrowsers(options) |
||||
|
||||
UnitTestState.browsersRunning = len(testBrowsers) |
||||
for b in testBrowsers: |
||||
UnitTestState.lastPost[b.name] = int(time.time()) |
||||
return testBrowsers |
||||
|
||||
def startBrowsers(browsers, options, path): |
||||
for b in browsers: |
||||
b.setup() |
||||
print 'Launching', b.name |
||||
host = 'http://%s:%s' % (SERVER_HOST, options.port) |
||||
qs = '?browser='+ urllib.quote(b.name) +'&manifestFile='+ urllib.quote(options.manifestFile) |
||||
qs += '&path=' + b.path |
||||
qs += '&delay=' + str(options.statsDelay) |
||||
qs += '&masterMode=' + str(options.masterMode) |
||||
b.start(host + path + qs) |
||||
|
||||
def teardownBrowsers(browsers): |
||||
for b in browsers: |
||||
try: |
||||
b.teardown() |
||||
except: |
||||
print "Error cleaning up after browser at ", b.path |
||||
print "Temp dir was ", b.tempDir |
||||
print "Error:", sys.exc_info()[0] |
||||
|
||||
def check(task, results, browser, masterMode): |
||||
failed = False |
||||
for r in xrange(len(results)): |
||||
pageResults = results[r] |
||||
for p in xrange(len(pageResults)): |
||||
pageResult = pageResults[p] |
||||
if pageResult is None: |
||||
continue |
||||
failure = pageResult.failure |
||||
if failure: |
||||
failed = True |
||||
if os.path.isfile(task['file'] + '.error'): |
||||
print 'TEST-SKIPPED | PDF was not downloaded', task['id'], '| in', browser, '| page', p + 1, 'round', r, '|', failure |
||||
else: |
||||
State.numErrors += 1 |
||||
print 'TEST-UNEXPECTED-FAIL | test failed', task['id'], '| in', browser, '| page', p + 1, 'round', r, '|', failure |
||||
|
||||
if failed: |
||||
return |
||||
|
||||
kind = task['type'] |
||||
if 'eq' == kind or 'text' == kind: |
||||
checkEq(task, results, browser, masterMode) |
||||
elif 'fbf' == kind: |
||||
checkFBF(task, results, browser) |
||||
elif 'load' == kind: |
||||
checkLoad(task, results, browser) |
||||
else: |
||||
assert 0 and 'Unknown test type' |
||||
|
||||
def createDir(dir): |
||||
try: |
||||
os.makedirs(dir) |
||||
except OSError, e: |
||||
if e.errno != 17: # file exists |
||||
print >>sys.stderr, 'Creating', dir, 'failed!' |
||||
|
||||
|
||||
def readDataUri(data): |
||||
metadata, encoded = data.rsplit(",", 1) |
||||
return base64.b64decode(encoded) |
||||
|
||||
def checkEq(task, results, browser, masterMode): |
||||
pfx = os.path.join(REFDIR, sys.platform, browser, task['id']) |
||||
testSnapshotDir = os.path.join(TEST_SNAPSHOTS, sys.platform, browser, task['id']) |
||||
results = results[0] |
||||
taskId = task['id'] |
||||
taskType = task['type'] |
||||
|
||||
passed = True |
||||
for result in results: |
||||
page = result.page |
||||
snapshot = readDataUri(result.snapshot) |
||||
ref = None |
||||
eq = True |
||||
|
||||
path = os.path.join(pfx, str(page) + '.png') |
||||
if not os.access(path, os.R_OK): |
||||
State.numEqNoSnapshot += 1 |
||||
if not masterMode: |
||||
print 'WARNING: no reference snapshot', path |
||||
else: |
||||
f = open(path, 'rb') |
||||
ref = f.read() |
||||
f.close() |
||||
|
||||
eq = (ref == snapshot) |
||||
if not eq: |
||||
print 'TEST-UNEXPECTED-FAIL |', taskType, taskId, '| in', browser, '| rendering of page', page, '!= reference rendering' |
||||
|
||||
if not State.eqLog: |
||||
State.eqLog = open(EQLOG_FILE, 'w') |
||||
eqLog = State.eqLog |
||||
|
||||
createDir(testSnapshotDir) |
||||
testSnapshotPath = os.path.join(testSnapshotDir, str(page) + '.png') |
||||
handle = open(testSnapshotPath, 'wb') |
||||
handle.write(snapshot) |
||||
handle.close() |
||||
|
||||
refSnapshotPath = os.path.join(testSnapshotDir, str(page) + '_ref.png') |
||||
handle = open(refSnapshotPath, 'wb') |
||||
handle.write(ref) |
||||
handle.close() |
||||
|
||||
# NB: this follows the format of Mozilla reftest |
||||
# output so that we can reuse its reftest-analyzer |
||||
# script |
||||
eqLog.write('REFTEST TEST-UNEXPECTED-FAIL | ' + browser +'-'+ taskId +'-page'+ str(page) + ' | image comparison (==)\n') |
||||
eqLog.write('REFTEST IMAGE 1 (TEST): ' + testSnapshotPath + '\n') |
||||
eqLog.write('REFTEST IMAGE 2 (REFERENCE): ' + refSnapshotPath + '\n') |
||||
|
||||
passed = False |
||||
State.numEqFailures += 1 |
||||
|
||||
if masterMode and (ref is None or not eq): |
||||
tmpTaskDir = os.path.join(TMPDIR, sys.platform, browser, task['id']) |
||||
createDir(tmpTaskDir) |
||||
|
||||
handle = open(os.path.join(tmpTaskDir, str(page)) + '.png', 'wb') |
||||
handle.write(snapshot) |
||||
handle.close() |
||||
|
||||
if passed: |
||||
print 'TEST-PASS |', taskType, 'test', task['id'], '| in', browser |
||||
|
||||
def checkFBF(task, results, browser): |
||||
round0, round1 = results[0], results[1] |
||||
assert len(round0) == len(round1) |
||||
|
||||
passed = True |
||||
for page in xrange(len(round1)): |
||||
r0Page, r1Page = round0[page], round1[page] |
||||
if r0Page is None: |
||||
break |
||||
if r0Page.snapshot != r1Page.snapshot: |
||||
print 'TEST-UNEXPECTED-FAIL | forward-back-forward test', task['id'], '| in', browser, '| first rendering of page', page + 1, '!= second' |
||||
passed = False |
||||
State.numFBFFailures += 1 |
||||
if passed: |
||||
print 'TEST-PASS | forward-back-forward test', task['id'], '| in', browser |
||||
|
||||
|
||||
def checkLoad(task, results, browser): |
||||
# Load just checks for absence of failure, so if we got here the |
||||
# test has passed |
||||
print 'TEST-PASS | load test', task['id'], '| in', browser |
||||
|
||||
|
||||
def processResults(options): |
||||
print '' |
||||
numFatalFailures = (State.numErrors + State.numFBFFailures) |
||||
if 0 == State.numEqFailures and 0 == numFatalFailures: |
||||
print 'All regression tests passed.' |
||||
else: |
||||
print 'OHNOES! Some tests failed!' |
||||
if 0 < State.numErrors: |
||||
print ' errors:', State.numErrors |
||||
if 0 < State.numEqFailures: |
||||
print ' different ref/snapshot:', State.numEqFailures |
||||
if 0 < State.numFBFFailures: |
||||
print ' different first/second rendering:', State.numFBFFailures |
||||
if options.statsFile != None: |
||||
with open(options.statsFile, 'w') as sf: |
||||
sf.write(json.dumps(State.stats, sort_keys=True, indent=4)) |
||||
print 'Wrote stats file: ' + options.statsFile |
||||
|
||||
|
||||
def maybeUpdateRefImages(options, browser): |
||||
if options.masterMode and (0 < State.numEqFailures or 0 < State.numEqNoSnapshot): |
||||
print "Some eq tests failed or didn't have snapshots." |
||||
print 'Checking to see if master references can be updated...' |
||||
numFatalFailures = (State.numErrors + State.numFBFFailures) |
||||
if 0 < numFatalFailures: |
||||
print ' No. Some non-eq tests failed.' |
||||
else: |
||||
print ' Yes! The references in tmp/ can be synced with ref/.' |
||||
if options.reftest: |
||||
startReftest(browser, options) |
||||
if options.noPrompts or prompt('Would you like to update the master copy in ref/?'): |
||||
sys.stdout.write(' Updating ref/ ... ') |
||||
|
||||
if not os.path.exists('ref'): |
||||
subprocess.check_call('mkdir ref', shell = True) |
||||
subprocess.check_call('cp -Rf tmp/* ref/', shell = True) |
||||
|
||||
print 'done' |
||||
else: |
||||
print ' OK, not updating.' |
||||
|
||||
def startReftest(browser, options): |
||||
url = "http://%s:%s" % (SERVER_HOST, options.port) |
||||
url += "/test/resources/reftest-analyzer.html" |
||||
url += "#web=/test/eq.log" |
||||
try: |
||||
browser.setup() |
||||
browser.start(url) |
||||
print "Waiting for browser..." |
||||
browser.process.wait() |
||||
finally: |
||||
teardownBrowsers([browser]) |
||||
print "Completed reftest usage." |
||||
|
||||
def runTests(options, browsers): |
||||
try: |
||||
shutil.rmtree(TEST_SNAPSHOTS); |
||||
except OSError, e: |
||||
if e.errno != 2: # folder doesn't exist |
||||
print >>sys.stderr, 'Deleting', dir, 'failed!' |
||||
t1 = time.time() |
||||
try: |
||||
startBrowsers(browsers, options, '/test/test_slave.html') |
||||
while not State.done: |
||||
for b in State.lastPost: |
||||
if State.remaining[b] > 0 and int(time.time()) - State.lastPost[b] > BROWSER_TIMEOUT: |
||||
print 'TEST-UNEXPECTED-FAIL | test failed', b, "has not responded in", BROWSER_TIMEOUT, "s" |
||||
State.numErrors += State.remaining[b] |
||||
State.remaining[b] = 0 |
||||
checkIfDone() |
||||
time.sleep(1) |
||||
processResults(options) |
||||
finally: |
||||
teardownBrowsers(browsers) |
||||
t2 = time.time() |
||||
print "Runtime was", int(t2 - t1), "seconds" |
||||
if State.eqLog: |
||||
State.eqLog.close(); |
||||
if options.masterMode: |
||||
maybeUpdateRefImages(options, browsers[0]) |
||||
elif options.reftest and State.numEqFailures > 0: |
||||
print "\nStarting reftest harness to examine %d eq test failures." % State.numEqFailures |
||||
startReftest(browsers[0], options) |
||||
|
||||
def runUnitTests(options, browsers, url, name): |
||||
t1 = time.time() |
||||
try: |
||||
startBrowsers(browsers, options, url) |
||||
while UnitTestState.browsersRunning > 0: |
||||
for b in UnitTestState.lastPost: |
||||
if UnitTestState.lastPost[b] != None and int(time.time()) - UnitTestState.lastPost[b] > BROWSER_TIMEOUT: |
||||
print 'TEST-UNEXPECTED-FAIL | test failed', b, "has not responded in", BROWSER_TIMEOUT, "s" |
||||
UnitTestState.lastPost[b] = None |
||||
UnitTestState.browsersRunning -= 1 |
||||
UnitTestState.numErrors += 1 |
||||
time.sleep(1) |
||||
print '' |
||||
print 'Ran', UnitTestState.numRun, 'tests' |
||||
if UnitTestState.numErrors > 0: |
||||
print 'OHNOES! Some', name, 'tests failed!' |
||||
print ' ', UnitTestState.numErrors, 'of', UnitTestState.numRun, 'failed' |
||||
else: |
||||
print 'All', name, 'tests passed.' |
||||
finally: |
||||
teardownBrowsers(browsers) |
||||
t2 = time.time() |
||||
print '', name, 'tests runtime was', int(t2 - t1), 'seconds' |
||||
|
||||
def main(): |
||||
optionParser = TestOptions() |
||||
options, args = optionParser.parse_args() |
||||
options = optionParser.verifyOptions(options) |
||||
if options == None: |
||||
sys.exit(1) |
||||
|
||||
if options.unitTest or options.fontTest: |
||||
httpd = TestServer((SERVER_HOST, options.port), UnitTestHandler) |
||||
httpd_thread = threading.Thread(target=httpd.serve_forever) |
||||
httpd_thread.setDaemon(True) |
||||
httpd_thread.start() |
||||
|
||||
browsers = setUpUnitTests(options) |
||||
if len(browsers) > 0: |
||||
if options.unitTest: |
||||
runUnitTests(options, browsers, '/test/unit/unit_test.html', 'unit') |
||||
if options.fontTest: |
||||
runUnitTests(options, browsers, '/test/font/font_test.html', 'font') |
||||
else: |
||||
httpd = TestServer((SERVER_HOST, options.port), PDFTestHandler) |
||||
httpd.masterMode = options.masterMode |
||||
httpd_thread = threading.Thread(target=httpd.serve_forever) |
||||
httpd_thread.setDaemon(True) |
||||
httpd_thread.start() |
||||
|
||||
browsers = setUp(options) |
||||
if len(browsers) > 0: |
||||
runTests(options, browsers) |
||||
else: |
||||
# just run the server |
||||
print "Running HTTP server. Press Ctrl-C to quit." |
||||
try: |
||||
while True: |
||||
time.sleep(1) |
||||
except (KeyboardInterrupt): |
||||
print "\nExiting." |
||||
|
||||
if __name__ == '__main__': |
||||
main() |
@ -0,0 +1,146 @@
@@ -0,0 +1,146 @@
|
||||
/* -*- Mode: js; js-indent-level: 2; indent-tabs-mode: nil; tab-width: 2 -*- */ |
||||
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */ |
||||
/* |
||||
* Copyright 2014 Mozilla Foundation |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
/*jslint node: true */ |
||||
|
||||
'use strict'; |
||||
|
||||
var fs = require('fs'); |
||||
var path = require('path'); |
||||
|
||||
exports.removeDirSync = function removeDirSync(dir) { |
||||
var files = fs.readdirSync(dir); |
||||
files.forEach(function (filename) { |
||||
var file = path.join(dir, filename); |
||||
var stats = fs.statSync(file); |
||||
if (stats.isDirectory()) { |
||||
removeDirSync(file); |
||||
} else { |
||||
fs.unlinkSync(file); |
||||
} |
||||
}); |
||||
fs.rmdirSync(dir); |
||||
}; |
||||
|
||||
exports.copySubtreeSync = function copySubtreeSync(src, dest) { |
||||
var files = fs.readdirSync(src); |
||||
if (!fs.existsSync(dest)) { |
||||
fs.mkdirSync(dest); |
||||
} |
||||
files.forEach(function (filename) { |
||||
var srcFile = path.join(src, filename); |
||||
var file = path.join(dest, filename); |
||||
var stats = fs.statSync(srcFile); |
||||
if (stats.isDirectory()) { |
||||
copySubtreeSync(srcFile, file); |
||||
} else { |
||||
fs.writeFileSync(file, fs.readFileSync(srcFile)); |
||||
} |
||||
}); |
||||
}; |
||||
|
||||
exports.ensureDirSync = function ensureDirSync(dir) { |
||||
if (fs.existsSync(dir)) { |
||||
return; |
||||
} |
||||
var parts = dir.split(path.sep), i = parts.length; |
||||
while (i > 1 && !fs.existsSync(parts.slice(0, i - 1).join(path.sep))) { |
||||
i--; |
||||
} |
||||
if (i < 0 || (i === 0 && parts[0])) { |
||||
throw new Error(); |
||||
} |
||||
|
||||
while (i <= parts.length) { |
||||
fs.mkdirSync(parts.slice(0, i).join(path.sep)); |
||||
i++; |
||||
} |
||||
}; |
||||
|
||||
var stdinBuffer = '', endOfStdin = false, stdinInitialized = false; |
||||
var stdinOnLineCallbacks = []; |
||||
|
||||
function handleStdinBuffer() { |
||||
if (endOfStdin) { |
||||
if (stdinBuffer && stdinOnLineCallbacks.length > 0) { |
||||
var callback = stdinOnLineCallbacks.shift(); |
||||
callback(stdinBuffer); |
||||
stdinBuffer = null; |
||||
} |
||||
while (stdinOnLineCallbacks.length > 0) { |
||||
var callback = stdinOnLineCallbacks.shift(); |
||||
callback(); |
||||
} |
||||
return; |
||||
} |
||||
while (stdinOnLineCallbacks.length > 0) { |
||||
var i = stdinBuffer.indexOf('\n'); |
||||
if (i < 0) { |
||||
return; |
||||
} |
||||
var callback = stdinOnLineCallbacks.shift(); |
||||
var result = stdinBuffer.substring(0, i + 1); |
||||
stdinBuffer = stdinBuffer.substring(i + 1); |
||||
callback(result); |
||||
} |
||||
// all callbacks handled, stop stdin processing
|
||||
process.stdin.pause(); |
||||
} |
||||
|
||||
function initStdin() { |
||||
process.stdin.setEncoding('utf8'); |
||||
|
||||
process.stdin.on('data', function(chunk) { |
||||
stdinBuffer += chunk; |
||||
handleStdinBuffer(); |
||||
}); |
||||
|
||||
process.stdin.on('end', function() { |
||||
endOfStdin = true; |
||||
handleStdinBuffer(); |
||||
}); |
||||
} |
||||
|
||||
exports.prompt = function prompt(message, callback) { |
||||
if (!stdinInitialized) { |
||||
process.stdin.resume(); |
||||
initStdin(); |
||||
stdinInitialized = true; |
||||
} else if (stdinOnLineCallbacks.length === 0) { |
||||
process.stdin.resume(); |
||||
} |
||||
|
||||
process.stdout.write(message); |
||||
stdinOnLineCallbacks.push(callback); |
||||
handleStdinBuffer(); |
||||
}; |
||||
|
||||
exports.confirm = function confirm(message, callback) { |
||||
exports.prompt(message, function (answer) { |
||||
if (answer === undefined) { |
||||
callback(); |
||||
return; |
||||
} |
||||
if (answer[0].toLowerCase() === 'y') { |
||||
callback(true); |
||||
} else if (answer[0].toLowerCase() === 'n') { |
||||
callback(false); |
||||
} else { |
||||
confirm(message, callback); |
||||
} |
||||
}); |
||||
}; |
@ -1,19 +1,3 @@
@@ -1,19 +1,3 @@
|
||||
This folder is a place for temporary files generated by ttx |
||||
If `git clone --recursive` was not used, please run `git submodile init; git submodule update` to pull fonttools code. |
||||
|
||||
# About TTX Installation |
||||
|
||||
The numpy module is required -- use "easy_install numpy" to install it. |
||||
|
||||
Download and extract fonttools from http://sourceforge.net/projects/fonttools/ in any folder on your computer. |
||||
|
||||
From the font tools directory run "python setup.py install" from the command line. |
||||
|
||||
# TTX for Mac Change |
||||
|
||||
On Mac OSX, if you are getting error message related to "/Library/Python/2.7/site-packages/FontTools/fontTools/ttLib/macUtils.py", line 18, in MyOpenResFile, use the following patch to change the fonttools |
||||
|
||||
https://github.com/mcolyer/fonttools/commit/e732bd3ba63c51df9aed903eb2147fa1af1bfdc2 |
||||
|
||||
# TTX for Windows Change |
||||
|
||||
On Windows, if ttx generate an exception, it waits for a key to be pressed. Pleaase change "/c/mozilla-build/python/Lib/site-packages/Font-Tools/fontTools/ttx.py" file: replace the waitForKeyPress function body with just 'return'. |
||||
Note: python 2.6 for 32-bit is required to run ttx. |
@ -0,0 +1 @@
@@ -0,0 +1 @@
|
||||
Subproject commit 48ea31215c9886ab2a1c6bfe81c4a6cf308f275b |
@ -0,0 +1,147 @@
@@ -0,0 +1,147 @@
|
||||
/* -*- Mode: js; js-indent-level: 2; indent-tabs-mode: nil; tab-width: 2 -*- */ |
||||
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */ |
||||
/* |
||||
* Copyright 2014 Mozilla Foundation |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
/*jslint node: true */ |
||||
|
||||
'use strict'; |
||||
|
||||
var os = require('os'); |
||||
var fs = require('fs'); |
||||
var path = require('path'); |
||||
var spawn = require('child_process').spawn; |
||||
var testUtils = require('./testutils.js'); |
||||
|
||||
var tempDirPrefix = 'pdfjs_'; |
||||
|
||||
function WebBrowser(name, path) { |
||||
this.name = name; |
||||
this.path = path; |
||||
this.tmpDir = null; |
||||
this.profileDir = null; |
||||
this.process = null; |
||||
this.finished = false; |
||||
this.callback = null; |
||||
} |
||||
WebBrowser.prototype = { |
||||
start: function (url) { |
||||
this.tmpDir = path.join(os.tmpdir(), tempDirPrefix + this.name); |
||||
if (!fs.existsSync(this.tmpDir)) { |
||||
fs.mkdirSync(this.tmpDir); |
||||
} |
||||
this.process = this.startProcess(url); |
||||
}, |
||||
getProfileDir: function () { |
||||
if (!this.profileDir) { |
||||
var profileDir = path.join(this.tmpDir, 'profile'); |
||||
if (fs.existsSync(profileDir)) { |
||||
testUtils.removeDirSync(profileDir); |
||||
} |
||||
fs.mkdirSync(profileDir); |
||||
this.profileDir = profileDir; |
||||
this.setupProfileDir(profileDir); |
||||
} |
||||
return this.profileDir; |
||||
}, |
||||
buildArguments: function (url) { |
||||
return [url]; |
||||
}, |
||||
setupProfileDir: function (dir) { |
||||
}, |
||||
startProcess: function (url) { |
||||
var args = this.buildArguments(url); |
||||
var proc = spawn(this.path, args); |
||||
proc.on('close', function (code) { |
||||
this.finished = true; |
||||
if (this.callback) { |
||||
this.callback.call(null, code); |
||||
} |
||||
this.cleanup(); |
||||
}.bind(this)); |
||||
return proc; |
||||
}, |
||||
cleanup: function () { |
||||
testUtils.removeDirSync(this.tmpDir); |
||||
}, |
||||
stop: function (callback) { |
||||
if (this.finished) { |
||||
if (callback) { |
||||
callback(); |
||||
} |
||||
} else { |
||||
this.callback = callback; |
||||
} |
||||
|
||||
this.process.kill(); |
||||
this.process = null; |
||||
} |
||||
}; |
||||
|
||||
var firefoxResourceDir = path.join(__dirname, 'resources', 'firefox'); |
||||
|
||||
function FirefoxBrowser(name, path) { |
||||
if (os.platform() === 'darwin') { |
||||
var m = /([^.\/]+)\.app(\/?)$/.exec(path); |
||||
if (m) { |
||||
path += (m[2] ? '' : '/') + 'Contents/MacOS/firefox'; |
||||
} |
||||
} |
||||
WebBrowser.call(this, name, path); |
||||
} |
||||
FirefoxBrowser.prototype = Object.create(WebBrowser.prototype); |
||||
FirefoxBrowser.prototype.buildArguments = function (url) { |
||||
var profileDir = this.getProfileDir(); |
||||
var args = []; |
||||
if (os.platform() === 'darwin') { |
||||
args.push('-foreground'); |
||||
} |
||||
args.push('-no-remote', '-profile', profileDir, url); |
||||
return args; |
||||
}; |
||||
FirefoxBrowser.prototype.setupProfileDir = function (dir) { |
||||
testUtils.copySubtreeSync(firefoxResourceDir, dir); |
||||
}; |
||||
|
||||
function ChromiumBrowser(name, path) { |
||||
if (os.platform() === 'darwin') { |
||||
var m = /([^.\/]+)\.app(\/?)$/.exec(path); |
||||
if (m) { |
||||
path += (m[2] ? '' : '/') + 'Contents/MacOS/' + m[1]; |
||||
console.log(path); |
||||
} |
||||
} |
||||
WebBrowser.call(this, name, path); |
||||
} |
||||
ChromiumBrowser.prototype = Object.create(WebBrowser.prototype); |
||||
ChromiumBrowser.prototype.buildArguments = function (url) { |
||||
var profileDir = this.getProfileDir(); |
||||
return ['--user-data-dir=' + profileDir, |
||||
'--no-first-run', '--disable-sync', url]; |
||||
}; |
||||
|
||||
WebBrowser.create = function (desc) { |
||||
var name = desc.name; |
||||
if (/firefox/i.test(name)) { |
||||
return new FirefoxBrowser(desc.name, desc.path); |
||||
} |
||||
if (/(chrome|chromium)/i.test(name)) { |
||||
return new ChromiumBrowser(desc.name, desc.path); |
||||
} |
||||
return new WebBrowser(desc.name, desc.path); |
||||
}; |
||||
|
||||
|
||||
exports.WebBrowser = WebBrowser; |
@ -0,0 +1,256 @@
@@ -0,0 +1,256 @@
|
||||
/* -*- Mode: js; js-indent-level: 2; indent-tabs-mode: nil; tab-width: 2 -*- */ |
||||
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */ |
||||
/* |
||||
* Copyright 2014 Mozilla Foundation |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
/*jslint node: true */ |
||||
|
||||
'use strict'; |
||||
|
||||
var http = require('http'); |
||||
var path = require('path'); |
||||
var fs = require('fs'); |
||||
|
||||
var mimeTypes = { |
||||
'.css': 'text/css', |
||||
'.html': 'text/html', |
||||
'.js': 'application/javascript', |
||||
'.json': 'application/json', |
||||
'.svg': 'image/svg+xml', |
||||
'.pdf': 'application/pdf', |
||||
'.xhtml': 'application/xhtml+xml', |
||||
'.gif': 'image/gif', |
||||
'.ico': 'image/x-icon', |
||||
'.png': 'image/png', |
||||
'.log': 'text/plain', |
||||
'.bcmap': 'application/octet-stream', |
||||
'.properties': 'text/plain' |
||||
}; |
||||
|
||||
var defaultMimeType = 'application/octet-stream'; |
||||
|
||||
function WebServer() { |
||||
this.root = '.'; |
||||
this.host = 'localhost'; |
||||
this.port = 8000; |
||||
this.server = null; |
||||
this.verbose = false; |
||||
this.hooks = { |
||||
'GET': [], |
||||
'POST': [] |
||||
}; |
||||
} |
||||
WebServer.prototype = { |
||||
start: function (callback) { |
||||
this.server = http.createServer(this._handler.bind(this)); |
||||
this.server.listen(this.port, this.host, callback); |
||||
console.log( |
||||
'Server running at http://' + this.host + ':' + this.port + '/'); |
||||
}, |
||||
stop: function (callback) { |
||||
this.server.close(callback); |
||||
this.server = null; |
||||
}, |
||||
_handler: function (req, res) { |
||||
var agent = req.headers['user-agent']; |
||||
var url = req.url; |
||||
var urlParts = /([^?]*)((?:\?(.*))?)/.exec(url); |
||||
var pathPart = decodeURI(urlParts[1]), queryPart = urlParts[3]; |
||||
var verbose = this.verbose; |
||||
|
||||
var methodHooks = this.hooks[req.method]; |
||||
if (!methodHooks) { |
||||
res.writeHead(405); |
||||
res.end('Unsupported request method', 'utf8'); |
||||
return; |
||||
} |
||||
var handled = methodHooks.some(function (hook) { |
||||
return hook(req, res); |
||||
}); |
||||
if (handled) { |
||||
return; |
||||
} |
||||
|
||||
if (pathPart === '/favicon.ico') { |
||||
fs.realpath(path.join(this.root, 'test/resources/favicon.ico'), |
||||
checkFile); |
||||
return; |
||||
} |
||||
|
||||
// disables range requests for chrome windows -- locks during testing
|
||||
var disableRangeRequests = /Windows.*?Chrom/i.test(agent); |
||||
|
||||
var filePath; |
||||
fs.realpath(path.join(this.root, pathPart), checkFile); |
||||
|
||||
function checkFile(err, file) { |
||||
if (err) { |
||||
res.writeHead(404); |
||||
res.end(); |
||||
if (verbose) { |
||||
console.error(url + ': not found'); |
||||
} |
||||
return; |
||||
} |
||||
filePath = file; |
||||
fs.stat(filePath, statFile); |
||||
} |
||||
|
||||
var fileSize; |
||||
|
||||
function statFile(err, stats) { |
||||
if (err) { |
||||
res.writeHead(500); |
||||
res.end(); |
||||
return; |
||||
} |
||||
|
||||
fileSize = stats.size; |
||||
var isDir = stats.isDirectory(); |
||||
if (isDir && !/\/$/.test(pathPart)) { |
||||
res.setHeader('Location', pathPart + '/' + urlParts[2]); |
||||
res.writeHead(301); |
||||
res.end('Redirected', 'utf8'); |
||||
return; |
||||
} |
||||
if (isDir) { |
||||
serveDirectoryIndex(filePath); |
||||
return; |
||||
} |
||||
|
||||
var range = req.headers['range']; |
||||
if (range && !disableRangeRequests) { |
||||
var rangesMatches = /^bytes=(\d+)\-(\d+)?/.exec(range); |
||||
if (!rangesMatches) { |
||||
res.writeHead(501); |
||||
res.end('Bad range', 'utf8'); |
||||
if (verbose) { |
||||
console.error(url + ': bad range: "' + range + '"'); |
||||
} |
||||
return; |
||||
} |
||||
var start = +rangesMatches[1]; |
||||
var end = +rangesMatches[2]; |
||||
if (verbose) { |
||||
console.log(url + ': range ' + start + ' - ' + end); |
||||
} |
||||
serveRequestedFileRange(filePath, |
||||
start, |
||||
isNaN(end) ? fileSize : (end + 1)); |
||||
return; |
||||
} |
||||
if (verbose) { |
||||
console.log(url); |
||||
} |
||||
serveRequestedFile(filePath); |
||||
} |
||||
|
||||
function serveDirectoryIndex(dir) { |
||||
res.setHeader('Content-Type', 'text/html'); |
||||
res.writeHead(200); |
||||
|
||||
var content = ''; |
||||
if (queryPart === 'frame') { |
||||
res.end('<html><frameset cols=*,200><frame name=pdf>' + |
||||
'<frame src=\"' + encodeURI(pathPart) + |
||||
'?side\"></frameset></html>', 'utf8'); |
||||
return; |
||||
} |
||||
var all = queryPart === 'all'; |
||||
fs.readdir(dir, function (err, files) { |
||||
if (err) { |
||||
res.end(); |
||||
return; |
||||
} |
||||
res.write('<html><body><h1>PDFs of ' + pathPart + '</h1>\n'); |
||||
if (pathPart !== '/') { |
||||
res.write('<a href=\"..\">..</a><br>\n'); |
||||
} |
||||
files.forEach(function (file) { |
||||
var stat = fs.statSync(path.join(dir, file)); |
||||
var item = pathPart + file; |
||||
if (stat.isDirectory()) { |
||||
res.write('<a href=\"' + encodeURI(item) + '\">' + |
||||
file + '</a><br>\n'); |
||||
return; |
||||
} |
||||
var ext = path.extname(file).toLowerCase(); |
||||
if (ext === '.pdf') { |
||||
res.write('<a href=\"/web/viewer.html?file=' + |
||||
encodeURI(item) + '\" target=pdf>' + |
||||
file + '</a><br>\n'); |
||||
} else if (all) { |
||||
res.write('<a href=\"' + encodeURI(item) + '\">' + |
||||
file + '</a><br>\n'); |
||||
} |
||||
}); |
||||
if (files.length === 0) { |
||||
res.write('<p>no files found</p>\n'); |
||||
} |
||||
if (!all && queryPart !== 'side') { |
||||
res.write('<hr><p>(only PDF files are shown, ' + |
||||
'<a href=\"?all\">show all</a>)</p>\n'); |
||||
} |
||||
res.end('</body></html>'); |
||||
}); |
||||
} |
||||
|
||||
function serveRequestedFile(filePath) { |
||||
var stream = fs.createReadStream(filePath, {flags: 'rs'}); |
||||
|
||||
stream.on('error', function (error) { |
||||
res.writeHead(500); |
||||
res.end(); |
||||
}); |
||||
|
||||
var ext = path.extname(filePath).toLowerCase(); |
||||
var contentType = mimeTypes[ext] || defaultMimeType; |
||||
|
||||
if (!disableRangeRequests) { |
||||
res.setHeader('Accept-Ranges', 'bytes'); |
||||
} |
||||
res.setHeader('Content-Type', contentType); |
||||
res.setHeader('Content-Length', fileSize); |
||||
res.writeHead(200); |
||||
|
||||
stream.pipe(res); |
||||
} |
||||
|
||||
function serveRequestedFileRange(filePath, start, end) { |
||||
var stream = fs.createReadStream(filePath, { |
||||
flags: 'rs', start: start, end: end - 1}); |
||||
|
||||
stream.on('error', function (error) { |
||||
res.writeHead(500); |
||||
res.end(); |
||||
}); |
||||
|
||||
var ext = path.extname(filePath).toLowerCase(); |
||||
var contentType = mimeTypes[ext] || defaultMimeType; |
||||
|
||||
res.setHeader('Accept-Ranges', 'bytes'); |
||||
res.setHeader('Content-Type', contentType); |
||||
res.setHeader('Content-Length', (end - start)); |
||||
res.setHeader('Content-Range', |
||||
'bytes ' + start + '-' + (end - 1) + '/' + fileSize); |
||||
res.writeHead(206); |
||||
|
||||
stream.pipe(res); |
||||
} |
||||
|
||||
} |
||||
}; |
||||
|
||||
exports.WebServer = WebServer; |
Loading…
Reference in new issue