Skip to content

Commit

Permalink
Add deployment tests. Closes #21.
Browse files Browse the repository at this point in the history
  • Loading branch information
jmdobry committed Nov 5, 2015
1 parent 5469269 commit 300677b
Show file tree
Hide file tree
Showing 5 changed files with 255 additions and 69 deletions.
6 changes: 5 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
**/node_modules/**
npm-debug.log
coverage/
coverage/

test/encrypted/secrets.tar
test/encrypted/express-demo.json
test/encrypted/hapi-demo.json
12 changes: 12 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,18 @@ before_install:
printf '\ny\n\ny\ny\n' | ./google-cloud-sdk/install.sh &&
cd $TRAVIS_BUILD_DIR;
fi
- gcloud components update -q
- gcloud components update preview -q
- openssl aes-256-cbc -K $encrypted_4e84c7c7ab67_key -iv $encrypted_4e84c7c7ab67_iv -in test/encrypted/secrets.tar.enc -out test/encrypted/secrets.tar -d
- if [ -a test/encrypted/secrets.tar ]; then
cd test/encrypted && tar xvf secrets.tar && cd ../..;
fi
- if [ -a test/encrypted/express-demo.json ]; then
gcloud auth activate-service-account --key-file test/encrypted/express-demo.json;
fi
- if [ -a test/encrypted/hapi-demo.json ]; then
gcloud auth activate-service-account --key-file test/encrypted/hapi-demo.json;
fi
- openssl aes-256-cbc -K $encrypted_95e832a36b06_key -iv $encrypted_95e832a36b06_iv -in nodejs-docs-samples.json.enc -out nodejs-docs-samples.json -d
- if [ -a nodejs-docs-samples.json ]; then
gcloud auth activate-service-account --key-file nodejs-docs-samples.json;
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
"googleapis": "~2.1.3"
},
"devDependencies": {
"async": "^1.5.0",
"coveralls": "^2.11.4",
"istanbul": "^0.4.0",
"jshint": "~2.8.0",
Expand Down
305 changes: 237 additions & 68 deletions test/appengine/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

var spawn = require('child_process').spawn;
var request = require('request');
var async = require('async');

var cwd = process.cwd();

Expand All @@ -25,6 +26,8 @@ function getPath(dir) {
var sampleTests = [
{
dir: 'express',
projectId: 'express-demo',
account: '[email protected]',
cmd: 'node',
arg1: './bin/www',
msg: 'Hello World! Express.js on Google App Engine.'
Expand All @@ -43,6 +46,8 @@ var sampleTests = [
},
{
dir: 'hapi',
projectId: 'hapi-demo',
'account': '[email protected]',
cmd: 'node',
arg1: 'index.js',
msg: 'Hello World! Hapi.js on Google App Engine.'
Expand Down Expand Up @@ -93,97 +98,261 @@ if (process.env.TRAVIS_NODE_VERSION !== 'stable') {
});
}

// Send a request to the given url and test that the response body has the
// expected value
function testRequest(url, sample, cb) {
request(url, function (err, res, body) {
if (err) {
// Request error
return cb(err);
} else {
if (body && body.indexOf(sample.msg) !== -1 &&
(res.statusCode === 200 || res.statusCode === sample.code)) {
// Success
return cb(null, true);
} else {
// Short-circuit app test
var message = sample.dir + ': failed verification!\n' +
'Expected: ' + sample.msg + '\n' +
'Actual: ' + body;

// Response body did not match expected
return cb(new Error(message));
}
}
});
}

// Helper for deciding whether to hide certain noisy console output
function shouldHide(data) {
if (data && typeof data === 'function' && (data.indexOf('.../') !== -1 ||
data.indexOf('...-') !== -1 ||
data.indexOf('...\\') !== -1 ||
data.indexOf('...|') !== -1)) {
return true;
}
}

describe('appengine/', function () {
sampleTests.forEach(function (sample) {
it(sample.dir + ': dependencies should install', function (done) {
// Allow extra time for "npm install"
this.timeout(sample.timeout || 120000);
var calledDone = false;

var proc = spawn('npm', ['install'], {
cwd: getPath(sample.dir)
});
testInstallation(sample, done);
});

proc.on('error', function (err) {
if (!calledDone) {
calledDone = true;
done(err);
}
});
it(sample.dir + ': should return 200 and Hello World', function (done) {
testLocalApp(sample, done);
});
});

if (!process.env.TRAVIS) {
proc.stderr.on('data', function (data) {
console.log('stderr: ' + data);
});
}
if (!process.env.TRAVIS || process.env.TRAVIS_NODE_VERSION !== 'stable') {
return;
}

proc.on('exit', function (code) {
if (!calledDone) {
calledDone = true;
if (code !== 0) {
done(new Error(sample.dir + ': failed to install dependencies!'));
} else {
done();
}
}
});
it('should deploy all samples', function (done) {
// 10 minutes because deployments are slow
this.timeout(10 * 60 * 1000);

testDeployments(done);
});
});

function testInstallation(sample, done) {

// Keep track off whether "done" has been called yet
var calledDone = false;

var proc = spawn('npm', ['install'], {
cwd: getPath(sample.dir)
});

proc.on('error', finish);

if (!process.env.TRAVIS) {
proc.stderr.on('data', function (data) {
console.log('stderr: ' + data);
});
}

it(sample.dir + ': should return 200 and Hello World', function (done) {
var timeoutId;
var intervalId;
var success = false;
proc.on('exit', function (code) {
if (code !== 0) {
finish(new Error(sample.dir + ': failed to install dependencies!'));
} else {
finish();
}
});

// Exit helper so we don't call "cb" more than once
function finish(err) {
if (!calledDone) {
calledDone = true;
done(err);
}
}
}

function testLocalApp(sample, done) {
var calledDone = false;

var proc = spawn(sample.cmd, [sample.arg1], {
cwd: getPath(sample.dir)
});

proc.on('error', finish);

if (!process.env.TRAVIS) {
proc.stderr.on('data', function (data) {
console.log('stderr: ' + data);
});
}

proc.on('exit', function (code, signal) {
if (code !== 0 && signal !== 'SIGKILL') {
return finish(new Error(sample.dir + ': failed to run!'));
} else {
return finish();
}
});

// Give the server time to start up
setTimeout(function () {
// Test that the app is working
testRequest('http://localhost:8080', sample, function (err) {
proc.kill('SIGKILL');
return finish(err);
});
}, 5000);

// Exit helper so we don't call "cb" more than once
function finish(err) {
if (!calledDone) {
calledDone = true;
done(err);
}
}
}

function testDeployments(done) {

// Only deploy samples that have a projectId
var samplesToDeploy = sampleTests.filter(function (sample) {
return sample.projectId;
});

// Create deployment tasks
var tasks = samplesToDeploy.map(function (sample) {
return function (cb) {
// Keep track off whether "cb" has been called yet
var calledDone = false;

var proc = spawn(sample.cmd, [sample.arg1], {
cwd: getPath(sample.dir)
var _cwd = getPath(sample.dir);
var args = [
'preview',
'app',
'deploy',
'app.yaml',
// Skip prompt
'-q',
'--project',
sample.projectId,
'--promote',
// Deploy over existing version so we don't have to clean up
'--version',
'demo',
// Use the service account for the sample's gcloud project
'--account',
sample.account,
// Override any existing deployment
'--force'
];

console.log(_cwd + ' $ gcloud ' + args.join(' '));

// Don't use "npm run deploy" because we need extra flags
var proc = spawn('gcloud', args, {
cwd: _cwd
});

proc.on('error', function (err) {
// Exit helper so we don't call "cb" more than once
function finish(err, result) {
if (!calledDone) {
calledDone = true;
done(err);
cb(err, result);
}
});

if (!process.env.TRAVIS) {
proc.stderr.on('data', function (data) {
console.log('stderr: ' + data);
});
}

proc.on('exit', function (code, signal) {
if (!calledDone) {
calledDone = true;
if (code !== 0 && signal !== 'SIGKILL') {
done(new Error(sample.dir + ': failed to run!'));
} else {
if (!success) {
done(new Error(sample.dir + ': failed verification!'));
} else {
done();
}
}
// Print stderr of process
proc.stderr.on('data', function (data) {
if (!shouldHide(data)) {
console.log(sample.projectId + ' stderr: ' + data);
}
});

timeoutId = setTimeout(end, 5000);
intervalId = setInterval(testRequest, 1000);
// Print stdout of process
proc.stdout.on('data', function (data) {
if (!shouldHide(data)) {
console.log(sample.projectId + ' stdout: ' + data);
}
});

function end() {
clearTimeout(timeoutId);
clearInterval(intervalId);
proc.kill('SIGKILL');
}
// This is called if the process fails to start. "error" event may or may
// not be fired in addition to the "exit" event.
proc.on('error', finish);

function testRequest() {
request('http://localhost:8080', function (err, res, body) {
if (body && body.indexOf(sample.msg) !== -1 &&
(res.statusCode === 200 || res.statusCode === sample.code)) {
success = true;
end();
}
});
// Process has completed
proc.on('exit', function (code) {
if (code !== 0) { // Deployment failed
// Pass error as second argument so we don't short-circuit the
// parallel tasks
return finish(null, new Error(sample.dir + ': failed to deploy!'));
} else { // Deployment succeeded
// Test that sample app is running successfully
return async.waterfall([
function (cb) {
// Give apps time to start
setTimeout(cb, 5000);
},
function (cb) {
// Test "default" module
var url = 'http://' + sample.projectId + '.appspot.com';
testRequest(url, sample, cb);
},
function (result, cb) {
// Test versioned url of "default" module
var demoUrl = 'http://demo.' + sample.projectId + '.appspot.com';
testRequest(demoUrl, sample, cb);
}
], finish);
}
});
};
});

// Deploy sample apps in parallel
return async.parallel(tasks, function (err, results) {
if (err) {
return done(err);
} else {
var success = true;
var message = '';
// Find errors that didn't short-circuit the parallel tasks
results.forEach(function (result) {
if (result instanceof Error) {
// Gather error messages
message = message + result.message + '\n';
success = false;
} else {
// "result" should be "true" for those apps that passed verification
success = success && result;
}
});
if (success) {
return done();
} else {
return done(new Error(message));
}
});
}
});
});
}
Binary file added test/encrypted/secrets.tar.enc
Binary file not shown.

0 comments on commit 300677b

Please sign in to comment.