Permalink
Browse files

Catch all SORTS of zany errors!!

Plus add --retry
  • Loading branch information...
1 parent bdaa660 commit f1c5349132033c5ed09a60735c150df7c0eeacf4 Mark Trostler committed with Aug 26, 2011
@@ -0,0 +1,67 @@
+/*
+Copyright (c) 2011, Yahoo! Inc.
+All rights reserved.
+
+Redistribution and use of this software in source and binary forms,
+with or without modification, are permitted provided that the following
+conditions are met:
+
+* Redistributions of source code must retain the above
+ copyright notice, this list of conditions and the
+ following disclaimer.
+
+* Redistributions in binary form must reproduce the above
+ copyright notice, this list of conditions and the
+ following disclaimer in the documentation and/or other
+ materials provided with the distribution.
+
+* Neither the name of Yahoo! Inc. nor the names of its
+ contributors may be used to endorse or promote products
+ derived from this software without specific prior
+ written permission of Yahoo! Inc.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+module.exports = {
+ Create: function(hub, common) {
+ // A test just dumped to console.error
+ var path = require('path'),
+ cache = hub.cache;
+
+ // Events I care about
+ hub.addListener('action:message', message);
+
+ function message(req, res) {
+ var obj = req.body,
+ msg = obj.msg,
+ why = obj.why,
+ browser = req.session.uuid, test;
+
+ // Find this test
+ for (var i = 0; i < cache.tests_to_run.length; i++) {
+ test = cache.tests_to_run[i];
+ if (test.browser != browser) continue;
+ if (test.running) {
+ common.addTestOutput(test, why + ': ' + msg);
+ hub.emit(hub.LOG, hub.LOG, 'MESSAGE ' + why + ' from ' + test.url + ': ' + msg);
+
+ break;
+ }
+ }
+
+ res.end('OK');
+ }
+ }
+};
+
@@ -94,7 +94,6 @@ module.exports = {
}
}
}
- // So we have to either *2 (arbitrary) on the timeout here OR reset the get_test timestamp above otherwise we get in an inf loop!
if (cache.browsers[browser] && cache.browsers[browser].get_test && (now - cache.browsers[browser].get_test > TEST_TIME_THRESHOLD)) {
// A link test taking too long - these are NOT in cache.tests_to_run
hub.emit(hub.LOG, hub.ERROR, "Test running for too long - killing it");
@@ -101,11 +101,12 @@ module.exports = {
for (var i = 0; i < tests.length; i++) {
var test = tests[i],
test_obj = {
- running: 0,
- url: path.join('/', hub.config.testDirWeb, test),
- output: '',
+ running: 0,
+ url: path.join('/', hub.config.testDirWeb, test),
+ output: '',
requestKey: requestKey,
- sendOutput: obj.send_output
+ sendOutput: obj.send_output,
+ retry: parseInt(obj.retry, 10) || 0
};
if (test.match(/\.js/)) {
@@ -92,15 +92,17 @@ module.exports = {
find(compDir, /\.txt$/, function(err, debugFiles) {
find(compDir, /\.png$/, function(err, snapshotFiles) {
find(compDir, /\.xml$/, function(err, testFiles) {
+ // Determined if failed or not
if (!err) {
- // Determined if failed or not
- testFiles.forEach(function(testFile) {
- if (common.failedTests(testFile)) {
- testResults.push({ name: path.basename(testFile), failed: 1 });
- } else {
- testResults.push({ name: path.basename(testFile), failed: 0 });
- }
- });
+ if (testFiles && testFiles.length) {
+ testFiles.forEach(function(testFile) {
+ if (common.failedTests(testFile)) {
+ testResults.push({ name: path.basename(testFile), failed: 1 });
+ } else {
+ testResults.push({ name: path.basename(testFile), failed: 0 });
+ }
+ });
+ }
try {
var coverage = path.existsSync(path.join(baseDir, component, 'lcov-report'));
@@ -70,11 +70,11 @@ module.exports = {
if (obj.log) {
var log = JSON.parse(obj.log);
- output += "------------ CONSOLE OUTPUT ------------\n";
+ output += "------------ START CONSOLE OUTPUT ------------\n";
log.forEach(function(msg) {
output += msg.msg + "\n";
});
- output += "------------ CONSOLE OUTPUT ------------\n";
+ output += "------------ END CONSOLE OUTPUT ------------\n";
}
if (obj.coverage && obj.coverage !== 'null') {
@@ -157,8 +157,18 @@ module.exports = {
// Clear this test out
common.addTestOutput(test, output);
- common.addTestOutput(test, obj.name + " finished - it " + (succeeded ? 'SUCCEEDED' : 'FAILED') + ' - it took ' + (now - test.running) + "ms\n");
- cache.tests_to_run.splice(i, 1);
+ common.addTestOutput(test, obj.name + " finished - it " +
+ (succeeded ? 'SUCCEEDED' : 'FAILED') + ' - it took ' +
+ (now - test.running) + "ms\n");
+
+ if (test.retry && !succeeded) {
+ common.addTestOutput(test, 'Test failed - trying it ' + test.retry + ' more times');
+ common.addTestOutput(test, '--------------------------------------------------------------------------------------');
+ test.retry--;
+ test.running = 0;
+ } else {
+ cache.tests_to_run.splice(i, 1);
+ }
// Take a snapshot & wait or we're done - always if 'snapshot' is set otherwise
// only if a test fails
@@ -43,8 +43,15 @@ function start() {
return;
}
+ try {
+ jute = spawn('./jute_backend.js', [], { setsid: false, customFds: [-1, fd, fd], cwd: process.cwd() });
+ } catch(e) {
+ console.error("Error spawing JUTE: " + e);
+ process.exit(1);
+ }
+
+ jute.on('exit', function() { fs.unlinkSync(pidfile); });
- jute = spawn('./jute_backend.js', [], { setsid: true, customFds: [-1, fd, fd], cwd: process.cwd() });
try {
fs.writeFileSync(pidfile, "" + jute.pid);
} catch(e) {
@@ -21,15 +21,45 @@ YUI().add('jute', function(Y) {
);
}
);
+
+ var pushBack = function(where, what, why) {
+ Y.io('/jute/_' + where,
+ {
+ method: 'PUT',
+ data: 'msg=' + escape(what) + '&why=' + escape(why),
+ headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
+ }
+ );
+ };
Y.Global.on('yui:log', function(log) {
totalLog.push(log);
+ pushBack('message', log.msg, 'Y.Global.log');
});
Y.on('yui:log', function(log) {
totalLog.push(log);
+ pushBack('message', log.msg, 'Y.log');
});
+ // Immediately push console message in case we never hear from this dude again
+ if (console) {
+ var oCL = console.log,
+ oCE = console.error;
+
+ console.log = function(msg) {
+ totalLog.push({ msg: 'console.log: ' + msg });
+ oCL.apply(console, arguments);
+ pushBack('message', msg, 'console.log');
+ }
+
+ console.error = function(msg) {
+ totalLog.push({ msg: 'console.error: ' + msg });
+ oCE.apply(console, arguments);
+ pushBack('message', msg, 'console.error');
+ }
+ }
+
// A helpful function - setup console & run tests
Y.namespace('UnitTest').go = function() {
@@ -131,7 +131,7 @@ Create: function(hub) {
exec(hub.config.java + ' -jar ' + p.join(__dirname, "yuitest-coverage.jar") + " -o " + tempFile + " " + path, function(err) {
if (err) {
hub.emit(hub.LOG, 'error', "Error coverage'ing " + path + ": " + err);
- hub.emit(hub.LOG, 'error', "Sending plain file");
+ hub.emit(hub.LOG, 'error', "Sending plain file instead");
_doSend(path, req, res, next);
} else {
_doSend(tempFile, req, res, next);
@@ -148,7 +148,17 @@ Create: function(hub) {
*/
function _doSend(path, req, res, next) {
- var mime = require('mime');
+ var mime = require('mime'),
+ efun = "\
+YUI().use('io-base', function(Y) {\
+ Y.io('/jute/_message',\
+ {\
+ method: 'PUT',\
+ data: 'msg=' + escape(e) + '&why=script.error',\
+ headers: { 'Content-Type': 'application/x-www-form-urlencoded' }\
+ }\
+ );\
+});";
fs.stat(path, function(err, stat) {
var type, charset,
@@ -172,25 +182,41 @@ Create: function(hub) {
res.setHeader('Content-Type', type + (charset ? '; charset=' + charset : ''));
}
- // dynamically inject JUTE?
- if (parseInt(hub.config.inject, 10) && type.match(/javascript/)) {
+ console.log(path + ' type: ' + type);
+
+ if (type.match(/javascript/)) {
var file = fs.readFileSync(path, 'utf8'),
- regex = /\)\s*\.\s*use\s*\(([^)]+)/;
+ add = "}catch(e){" + efun + "}";
- var matches = regex.exec(file);
- if (matches) {
- if (matches[1].match('test')) {
+ file = "try{" + file + add;
+ res.setHeader('Content-Length', stat.size + add.length + 4);
- hub.emit(hub.LOG, hub.INFO, "Dynamically injecting JUTE client into " + path);
+ console.log('put try/catch block around: ' + path);
- res.setHeader('Content-Length', stat.size + juteClient.length);
- res.write(juteClient);
- res.end(file.replace('test', 'jute'));
- return;
- }
- }
+ // dynamically inject JUTE?
+ if (parseInt(hub.config.inject, 10)) {
+ regex = /\)\s*\.\s*use\s*\(([^)]+)/;
+
+ var matches = regex.exec(file);
+ if (matches) {
+ if (matches[1].match('test')) {
+
+ hub.emit(hub.LOG, hub.INFO, "Dynamically injecting JUTE client into " + path);
+
+ res.setHeader('Content-Length', res.getHeader('Content-Length') + juteClient.length);
+ res.write(juteClient);
+ file.replace('test', 'jute'); // Need to smarter some day
+ }
+ }
+ }
+ res.end(file);
+ } else if (type.match(/html/i)) {
+ var file = fs.readFileSync(path, 'utf8'),
+ err = '<script src="http://yui.yahooapis.com/3.3.0/build/yui/yui.js"></script>' +
+ '<script>window.onerror=function(e){' + efun + '};</script>';
- // Nope, just some plain old JS file...
+ res.setHeader('Content-Length', res.getHeader('Content-Length') + err.length);
+ res.write(err);
res.end(file);
} else {
fs.createReadStream(path).pipe(res);
@@ -94,3 +94,4 @@ eventHub.on('configureDone', function() {
});
eventHub.emit('configure');
+process.on('exit', function() { fs.unlinkSync(process.env['npm_package_config_pidfile']); });
@@ -2,7 +2,7 @@
"name": "jute",
"description": "Javascript Unit Test Environment",
"keywords": ["selenium", "test", "testing", "unit", "tests"],
- "version": "0.0.46",
+ "version": "0.0.47",
"author": "Mark Ethan Trostler <mark@zzo.com>",
"preferGlobal": true,
"bin" : {
@@ -41,7 +41,7 @@ var config = (require('./getConfig'))(),
events = require("events"),
eventHubF = function() { events.EventEmitter.call(this); },
args = opt
- .usage('Usage: $0 --test [testfile] [ --test [another testfile] ] [ --host [JUTE host] ] [ --port [JUTE host port] ] [ --sel_host [Selenium host] ] [ --sel_browser [Selenium browser spec] [ --load ] ] [ --send_output ] [ --wait ] [ --clear_results ] [ -v8 ] [ --status ] [ --snapshot ]')
+ .usage('Usage: $0 --test [testfile] [ --test [another testfile] ] [ --host [JUTE host] ] [ --port [JUTE host port] ] [ --sel_host [Selenium host] ] [ --sel_browser [Selenium browser spec] [ --load ] ] [ --send_output ] [ --wait ] [ --clear_results ] [ -v8 ] [ --status ] [ --snapshot ] [ --retry ]')
.alias('t', 'test')
.alias('h', 'host')
.alias('p', 'port')
@@ -52,6 +52,7 @@ var config = (require('./getConfig'))(),
.alias('sn', 'snapshot')
.alias('c', 'clear_results')
.alias('w', 'wait')
+ .alias('r', 'retry')
.default('host', (config && config.host) || os.hostname())
.default('port', (config && config.port) || 8080)
.default('send_output', false)
@@ -61,6 +62,7 @@ var config = (require('./getConfig'))(),
.default('load', false)
.default('clear_results', false)
.default('sel_browser', '*firefox')
+ .default('retry', 0)
.describe('test', 'Test file to run - relative to docRoot/testDir (npm set jute.testDir) - can specify multiple of these')
.describe('host', 'Hostname of JUTE server')
.describe('port', 'Port of JUTE server')
@@ -73,6 +75,7 @@ var config = (require('./getConfig'))(),
.describe('clear_results', 'Clear ALL previous test results before running specified test(s)')
.describe('v8', 'Run these test(s) using the V8 backend')
.describe('status', 'Just get status')
+ .describe('retry', 'Number of time to retry a failed test')
.argv,
sys = require('sys'),
qs = require('querystring'),
@@ -139,6 +142,7 @@ eventHub.on('tests', function(tests) {
if (args.wait) {
juteArgs.wait = 1;
}
+ juteArgs.retry = args.retry;
// Toss in Selenium stuff
if (args.sel_host) {

0 comments on commit f1c5349

Please sign in to comment.