UNPKG

spinner

Version:

Spawns child processes and allocates `process.env.PORT` for each.

471 lines (399 loc) 11.9 kB
var async = require('async'); var fs = require('fs'); var path = require('path'); var logule = require('logule'); var http = require('http'); function createTests() { var GOOD_APP = "require('http').createServer(function(req, res) { return res.end($BODY); }).listen(process.env.PORT);console.log('bound');"; var BAD_APP = "<BAD!>"; var TAKE_YOUR_TIME_APP = "setTimeout(function() {" + GOOD_APP + "}, $TIMEOUT);"; var LATE_DEATH = GOOD_APP + "setTimeout(function() { console.warn('bye!'); process.kill(2); }, $DIE_AFTER);"; // -- helpers function series(test, fns) { return async.series(fns, function(err) { test.ok(!err, err); test.done(); }); } // -- tests return require('nodeunit').testCase({ setUp: function(cb) { try { var self = this; var spinner = require('..').createSpinner({ logger: logule, fsmTraces: false }); var tmpdir = path.join(process.env.TEMP || '/tmp', Math.round(Math.random() * 1000000) + ".test"); fs.mkdirSync(tmpdir); self.tmpdir = tmpdir; self.spinner = spinner; self.logger = console; self.path = function(filename) { return path.join(self.tmpdir, filename); }; self.write = function(filename, data, fields) { return function(cb) { for (var k in fields) { data = data.replace(k, fields[k]); } self.logger.log('writing', filename, '[', data, ']'); return fs.writeFile(self.path(filename), data, function() { return setTimeout(cb, 1000); }); }; }; self.delay = function(delay, fn) { return function(cb) { return setTimeout(function() { return fn(cb); }, delay); }; }; self.start = function(filename, expect) { return function(cb) { self.logger.log('starting', filename); return self.spinner.start(self.path(filename), function(err, port) { if (expect) expect(err, port); return cb(); }); }; }; self.stop = function(filename) { return function(cb) { self.logger.log('stopping', filename); return self.spinner.stop(self.path(filename), cb); }; }; self.waitForFailure = function(filename) { return function(cb) { return self.request(filename, function(err, res, body) { if (!err) return self.waitForFailure(filename)(cb); return cb(); }); }; }; self.request = function(filename, cb) { self.logger.log('pinging', filename); var p = self.path(filename); var spnr = self.spinner.get(p); if (!spnr) return cb(new Error('unable to find spinner for ' + p)); var port = spnr.port; if (!port) return cb(new Error('no port allocated for app')); // check if this is a domain socket or a regular socket. var req = {}; if (parseInt(port)) { req = { port: port, host: 'localhost '}; } else { req = { socketPath: port }; } return http.get({ port: port }, function(res) { var buff = ''; res.on('data', function(data) { buff += data.toString(); }); res.on('end', function() { res.body = buff; return cb(null, res); }); }).on('error', function(err) { return cb(err); }); } self.waitForSuccess = function(filename, expectedBody) { return function(cb) { self.request(filename, function(err, res, body) { if (err && err.code === 'ECONNREFUSED') { self.logger.log('connection refused, trying again in 1sec'); return setTimeout(function() { var fn = self.waitForSuccess(filename, expectedBody); return fn(cb); }, 1000); } if (err) return cb(new Error(err)); if (!res) return cb(new Error("no response")); self.logger.log('response: ', res.statusCode, res.body); if (res.statusCode !== 200) return cb(new Error("expecting 200 OK")); if (res.body !== expectedBody) return cb(new Error("expecting " + expectedBody + " in body. got " + res.body)); return cb(); }); }; }; return cb(); } catch(e) { console.error(e); }; }, tearDown: function(cb) { var self = this; self.spinner.stopall(); return cb(); }, /** * 1. app loads successfuly * 2. app changes so now it cannot be loaded * 3. monitor picks up change and restarts the app * 4. an error is emitted but we expect it not to fail the process (bug) * 5. app is fixed * 6. we expect the monitor to picks the change again and restart again * 7. now it should be up and running */ reloadAfterFailure: function(test) { var self = this; return series(test, [ // // create an app that returns "1" on http requests // self.write("app.js", GOOD_APP, { $BODY: '"1"' }), self.start("app.js", function(err, port) { test.ok(!err, "expecting success"); test.ok(port, "expecint a port to be allocated and returned"); }), self.waitForSuccess('app.js', '1'), // // modify it so it now returns "2" // self.write('app.js', GOOD_APP, { $BODY: '"2"' }), self.waitForSuccess('app.js', '2'), // // screw up with the app and expect the process to move to 'faulted' // self.write('app.js', BAD_APP), // ping until failure self.waitForFailure('app.js'), // // now, fix the app and expect a recycle to happen // self.write('app.js', GOOD_APP, { $BODY: '"3"' }), self.waitForSuccess('app.js', '3'), ]); }, // // Stopped // startWhileStopped: function(test) { var self = this; return series(test, [ self.write('joo.js', GOOD_APP, { $BODY: '"o"' }), self.start('joo.js'), self.waitForSuccess('joo.js', 'o'), self.stop('joo.js'), self.waitForFailure('joo.js'), self.start('joo.js'), self.waitForSuccess('joo.js', 'o'), ]); }, stopWhileStopped: function(test) { var self = this; return series(test, [ self.write('joo.js', GOOD_APP, { $BODY: '"o"' }), self.start('joo.js'), self.waitForSuccess('joo.js', 'o'), self.stop('joo.js'), self.waitForFailure('joo.js'), self.stop('joo.js'), self.waitForFailure('joo.js'), ]); }, termWhileStopped: function(test) { var self = this; // this is an invalid state and impossible to simulate in black box test.done(); }, changeWhileStopped: function(test) { var self = this; return series(test, [ self.write('joo.js', GOOD_APP, { $BODY: '"o"' }), self.start('joo.js'), self.waitForSuccess('joo.js', 'o'), self.stop('joo.js'), self.write('joo.js', GOOD_APP, { $BODY: '"p"' }), self.delay(2000, self.waitForFailure('joo.js')), self.start('joo.js'), self.waitForSuccess('joo.js', 'p'), ]); }, // // Starting // startWhileStarting: function(test) { var self = this; return series(test, [ self.write('foo.js', GOOD_APP, { $BODY: '"x"' }), self.start('foo.js'), self.start('foo.js'), self.waitForSuccess('foo.js', 'x'), ]); }, stopWhileStarting: function(test) { var self = this; return series(test, [ self.write('foo.js', GOOD_APP, { $BODY: '"x"' }), self.start('foo.js'), self.stop('foo.js'), self.waitForFailure('foo.js'), ]); }, termWhileStarting: function(test) { var self = this; return series(test, [ self.write('goo.js', BAD_APP), self.start('goo.js'), self.waitForFailure(), ]); }, changeWhileStarting: function(test) { var self = this; return series(test, [ self.write('loo.js', GOOD_APP, { $BODY: '"5"' }), self.start('loo.js'), self.write('loo.js', GOOD_APP, { $BODY: '"6"' }), self.waitForSuccess('loo.js', '6'), ]); }, // // Binding // startWhileBinding: function(test) { var self = this; return series(test, [ self.write('moo.js', TAKE_YOUR_TIME_APP, { $BODY: '"0"', $TIMEOUT: 2000 }), self.start('moo.js'), self.delay(1000, self.start('moo.js')), self.waitForSuccess('moo.js', '0'), ]); }, stopWhileBinding: function(test) { var self = this; return series(test, [ self.write('zoo.js', TAKE_YOUR_TIME_APP, { $BODY: '"H"', $TIMEOUT: 2000 }), self.start('zoo.js'), self.delay(1000, self.stop('zoo.js')), self.waitForFailure('zoo.js'), ]); }, termWhileBinding: function(test) { var self = this; return series(test, [ self.write('too.js', BAD_APP), self.start('too.js'), self.waitForFailure('too.js'), self.start('too.js'), // try to start again, expect a failure self.waitForFailure('too.js'), // now change the app to be good and expect it will succeed self.write('too.js', GOOD_APP, { $BODY: '"GOOD"' }), self.waitForSuccess('too.js', 'GOOD'), ]); }, /** * 1. start app which takes 10 seconds to bind * 2. after 2 seconds, change it * 3. expect that the final result will be that the new app is spawned */ changeWhileBinding: function(test) { var self = this; return series(test, [ self.write('hanger.js', TAKE_YOUR_TIME_APP, { $BODY: '"A"', $TIMEOUT: 2000 } ), self.start('hanger.js'), self.waitForSuccess('hanger.js', 'A'), // change app, wait 1 second and change it again self.write('hanger.js', TAKE_YOUR_TIME_APP, { $BODY: '"B"', $TIMEOUT: 2000 }), function(cb) { setTimeout(function() { self.write('hanger.js', GOOD_APP, { $BODY: '"C"', $TIMEOUT: 2000 })(cb); }, 1000); }, self.waitForSuccess('hanger.js', 'C'), ]); }, // // Started // startWhileStarted: function(test) { var self = this; return series(test, [ self.write('you.js', GOOD_APP, { $BODY: '"m"' }), self.start('you.js'), self.waitForSuccess('you.js', 'm'), self.start('you.js'), self.waitForSuccess('you.js', 'm'), ]); }, stopWhileStarted: function(test) { var self = this; return series(test, [ self.write('you.js', GOOD_APP, { $BODY: '"m"' }), self.start('you.js'), self.waitForSuccess('you.js', 'm'), self.stop('you.js'), self.waitForFailure('you.js', 'm'), ]); }, termWhileStarted: function(test) { var self = this; return series(test, [ self.write('loo.js', LATE_DEATH, { $BODY: '"m"', $DIE_AFTER: 5000 }), self.start('loo.js'), self.waitForSuccess('loo.js', 'm'), self.delay(7000, self.waitForSuccess('loo.js', 'm')), ]); }, changeWhileStarted: function(test) { var self = this; return series(test, [ self.write('koo.js', GOOD_APP, { $BODY: '"k"' }), self.start('koo.js'), self.waitForSuccess('koo.js', 'k'), self.write('koo.js', GOOD_APP, { $BODY: '"h"' }), self.waitForSuccess('koo.js', 'h'), ]); }, // // Faulted // startWhileFaulted: function(test) { var self = this; return series(test, [ self.write('foo.js', BAD_APP), self.start('foo.js'), self.waitForFailure('foo.js'), self.start('foo.js'), self.waitForFailure('foo.js'), ]); }, stopWhileFaulted: function(test) { var self = this; return series(test, [ self.write('foo.js', BAD_APP), self.start('foo.js'), self.waitForFailure('foo.js'), self.stop('foo.js'), self.write('foo.js', GOOD_APP, { $BODY: '"%"' }), self.start('foo.js'), self.waitForSuccess('foo.js', '%'), ]); }, termWhileFaulted: function(test) { // this is an invalid state and can't be simulated in black box tests test.done(); }, changeWhileFaulted: function(test) { var self = this; return series(test, [ self.write('foo.js', BAD_APP), self.start('foo.js'), self.waitForFailure('foo.js'), self.write('foo.js', GOOD_APP, { $BODY: '"%"' }), self.waitForSuccess('foo.js', '%'), ]); }, // // Restarting: TODO // // // Stopping // startWhileStopping: function(test) { var self = this; return series(test, [ ]); }, }); }; exports.unixdomainports = createTests();