First pass at Gulpifying the test suite.

This commit is contained in:
Kevin Gisi 2015-06-06 08:27:15 -04:00
parent c39ad2a2ae
commit 6a397a8be5
3 changed files with 130 additions and 100 deletions

125
gulpfile.babel.js Normal file
View file

@ -0,0 +1,125 @@
import { exec } from 'child_process';
import psTree from 'ps-tree';
import gulp from 'gulp';
import net from 'net';
import Q from 'q';
const TEST_SERVER_PORT = 3001
const TEST_DB = 'habitrpg_test'
const TEST_DB_URI = `mongodb://localhost/${TEST_DB}`
/*
* This is a helper function that allows us to kill background tasks, such as
* the Selenium webdriver. We need to recurse through any child processes they
* have spun up, or gulp will hang after task completion.
*/
let kill = (proc) => {
((pid) => {
psTree(pid, (_, pids) => {
if(pids.length) {
pids.forEach(kill); return
}
try {
exec(/^win/.test(process.platform)
? `taskkill /PID ${pid} /T /F`
: `kill -9 ${pid}`)
}
catch(e) { console.log(e) }
});
}(proc.PID || proc.pid));
};
/*
* Another helper function, returns a promise that will resolve when a response
* is received on the specified port. Accepts a second argument indicating the
* maximum seconds to wait before failing.
*/
let awaitPort = (port, max = 60) => {
let socket, timeout, interval;
let deferred = Q.defer();
timeout = setTimeout(() => {
clearInterval(interval);
deferred.reject(`Timed out after ${max} seconds`);
}, max * 1000);
interval = setInterval(() => {
socket = net.connect({port: port}, () => {
clearInterval(interval);
clearTimeout(timeout);
socket.destroy();
deferred.resolve();
}).on('error', () => { socket.destroy });
}, 1000);
return deferred.promise
};
/*
* And another helper function to add "noisy" listeners (pipe child process
* stdout and stderr to the parent.
*/
let listen = (child) => {
child.stdout.on('data', (data) => { process.stdout.write(data) });
child.stderr.on('data', (data) => { process.stderr.write(data) });
};
gulp.task('test:common', (cb) => {
listen(exec('NODE_ENV=testing ./node_modules/.bin/mocha test/common', cb));
});
gulp.task('test:api', (cb) => {
listen(exec('NODE_ENV=testing ./node_modules/.bin/mocha test/api', cb));
});
gulp.task('test:karma', (cb) => {
listen(exec('NODE_ENV=testing ./node_modules/.bin/grunt karma:continuous', cb));
});
gulp.task('test:prepare:build', (cb) => {
exec('grunt build:test', cb);
});
gulp.task('test:prepare:mongo', (cb) => {
exec(`mongo "${TEST_DB}" --eval "db.dropDatabase()"`, cb);
});
gulp.task('test:prepare', [
'test:prepare:build',
'test:prepare:webdriver',
'test:prepare:mongo'
]);
gulp.task('test:prepare:webdriver', (cb) => {
exec('./node_modules/protractor/bin/webdriver-manager update', cb);
});
gulp.task('test:e2e', ['test:prepare'], (cb) => {
let support = [
'Xvfb :99 -screen 0 1024x768x24 -extension RANDR',
`NODE_DB_URI="${TEST_DB_URI}" PORT="${TEST_SERVER_PORT}" node ./website/src/server.js`,
'./node_modules/protractor/bin/webdriver-manager start',
].map(exec);
awaitPort(3001)
.then(awaitPort.bind(null, 4444))
.then(() => {
listen(
exec('DISPLAY=:99 NODE_ENV=testing ./node_modules/protractor/bin/protractor protractor.conf.js', () => {
support.forEach(kill);
cb();
})
);
});
});
gulp.task('test', [
'test:common',
'test:api',
'test:karma',
'test:e2e'
]);
gulp.task('default', ['test']);

View file

@ -6,6 +6,7 @@
"dependencies": {
"async": "~0.9.0",
"aws-sdk": "^2.0.25",
"babel": "^5.5.4",
"bower": "~1.3.12",
"browserify": "~3.30.2",
"coffee-script": "1.6.x",
@ -30,6 +31,7 @@
"grunt-karma": "~0.6.2",
"grunt-nodemon": "~0.3.0",
"grunt-spritesmith": "~3.5.0",
"gulp": "^3.9.0",
"icalendar": "git://github.com/lefnire/node-icalendar#master",
"image-size": "~0.3.2",
"in-app-purchase": "^0.2.0",
@ -52,7 +54,9 @@
"paypal-ipn": "2.1.0",
"paypal-rest-sdk": "^1.2.1",
"pretty-data": "git://github.com/vkiryukhin/pretty-data#master",
"ps-tree": "^1.0.0",
"push-notify": "^1.1.1",
"q": "^1.4.1",
"qs": "^2.3.2",
"request": "~2.44.0",
"s3-upload-stream": "^1.0.6",
@ -74,7 +78,7 @@
"node": "0.10.x"
},
"scripts": {
"test": "./node_modules/coffee-script/bin/coffee ./test/runTests.coffee -n",
"test": "./node_modules/.bin/gulp",
"start": "grunt run:dev",
"postinstall": "./node_modules/bower/bin/bower --config.interactive=false install -f; ./node_modules/.bin/grunt;",
"coverage": "COVERAGE=true mocha --require register-handlers.js --reporter html-cov > coverage.html; open coverage.html"

View file

@ -1,99 +0,0 @@
sh = require('shelljs')
async = require('async')
TEST_DB = 'habitrpg_test'
TEST_DB_URI = "mongodb://localhost/#{TEST_DB}"
TEST_SERVER_PORT = 3001
MAX_WAIT = 60
announce = (msg) ->
sh.echo '\x1b[36m%s\x1b[0m', "TEST SUITE: #{msg}"
Suite =
# Primary Task
run: ->
announce "Preparing the test environment."
Suite.prepareEnvironment ->
announce "Test prep complete. Waiting for server availability."
Suite.awaitServers ->
announce "Servers are ready. Beginning tests."
Suite.summarize
"API Specs": Suite.runApiSpecs()
"Common Specs": Suite.runCommonSpecs()
"End-to-End Specs": Suite.runE2ESpecs()
"Karma Specs": Suite.runKarmaSpecs()
# Output summary report when tests are done.
summarize: (results) ->
anyFailed = 0
sh.echo ""
announce "Tests complete!\n\nSummary\n-------\n"
for name, result of results
if result is 0
sh.echo '\x1b[36m%s\x1b[0m', "#{name}: \x1b[32mpassing"
else
anyFailed = 1
sh.echo '\x1b[36m%s\x1b[0m', "#{name}: \x1b[31mfailing"
sh.echo ""
announce "Thanks for helping keep Habitica clean!"
process.exit(anyFailed)
# Prepare files, db, and spin up servers.
prepareEnvironment: (cb) ->
sh.exec "grunt build:test"
sh.exec "mongo \"#{TEST_DB}\" --eval \"db.dropDatabase()\""
sh.exec "./node_modules/protractor/bin/webdriver-manager update"
# Spin this up even if we're not in a headless environment. Shouldn't matter.
sh.exec "Xvfb :99 -screen 0 1024x768x24 -extension RANDR", silent: true, async: true
sh.exec "./node_modules/protractor/bin/webdriver-manager start", silent: true, async: true
sh.exec "NODE_DB_URI=\"#{TEST_DB_URI}\" PORT=\"#{TEST_SERVER_PORT}\" node ./website/src/server.js", silent: true, async: true
cb()
# Ensure both the selenium and node servers are available
awaitServers: (cb) ->
async.parallel [Suite.awaitSelenium, Suite.awaitNode], (err, results) ->
throw err if err?
cb()
awaitSelenium: (cb) ->
waited = 0
interval = setInterval ->
if sh.exec('nc -z localhost 4444').code is 0
clearInterval(interval)
cb()
waited += 1
if waited > MAX_WAIT
clearInterval(interval)
cb(new Error("Timed out waiting for Selenium"))
, 1000
awaitNode: (cb) ->
waited = 0
interval = setInterval ->
if sh.exec('nc -z localhost 3001').code is 0
clearInterval(interval)
cb()
waited += 1
if waited > MAX_WAIT
clearInterval(interval)
cb(new Error("Timed out waiting for Node server"))
, 1000
runApiSpecs: ->
announce "Running API Specs (Mocha)"
sh.exec("NODE_ENV=testing ./node_modules/mocha/bin/mocha test/api").code
runCommonSpecs: ->
announce "Running Common Specs (Mocha)"
sh.exec("NODE_ENV=testing ./node_modules/mocha/bin/mocha test/common").code
runE2ESpecs: ->
announce "Running End-to-End Specs (Protractor)"
sh.exec("DISPLAY=:99 NODE_ENV=testing ./node_modules/protractor/bin/protractor protractor.conf.js").code
runKarmaSpecs: ->
announce "Running Karma Specs"
sh.exec("NODE_ENV=testing grunt karma:continuous").code
Suite.run()