From 67a521ea2daadbc412f67197076724b2fdbaab32 Mon Sep 17 00:00:00 2001 From: Martin von Gagern Date: Thu, 2 Jun 2016 14:01:57 +0200 Subject: [PATCH] Test command-line switches, --host in particular --- package.json | 2 +- test/cli/basic-test.js | 37 ++++++++++++++ test/cli/common.js | 110 +++++++++++++++++++++++++++++++++++++++++ test/cli/host-test.js | 87 ++++++++++++++++++++++++++++++++ 4 files changed, 235 insertions(+), 1 deletion(-) create mode 100644 test/cli/basic-test.js create mode 100644 test/cli/common.js create mode 100644 test/cli/host-test.js diff --git a/package.json b/package.json index 1c923fe..68ff8e0 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,7 @@ "tap": "~2.3.1" }, "scripts": { - "test": "tap test/*.js" + "test": "tap test/*.js test/cli/*-test.js" }, "repository": { "type": "git", diff --git a/test/cli/basic-test.js b/test/cli/basic-test.js new file mode 100644 index 0000000..946c63a --- /dev/null +++ b/test/cli/basic-test.js @@ -0,0 +1,37 @@ +var test = require('tap').test +var common = require('./common') +var serve = common.serve + +test('Basic cli operation', function (t) { + serve([], function (req) { + + req('/st.js', function (er, res, body) { + t.ifError(er) && + t.equal(res.statusCode, 200) && + t.equal(body.toString(), common.stExpect) + }) + + }, function (er, stdout, stderr) { + t.ifError(er) + t.match(stdout, /^listening at http:\/\/(\[::\]|0\.0\.0\.0):[0-9]+\n$/) + t.equal(stderr, '') + t.end() + }) +}) + +test('Listening on localhost only', function (t) { + serve(["--localhost"], function (req) { + + req('/st.js', function (er, res, body) { + t.ifError(er) && + t.equal(res.statusCode, 200) && + t.equal(body.toString(), common.stExpect) + }) + + }, function (er, stdout, stderr) { + t.ifError(er) + t.match(stdout, /^listening at http:\/\/localhost:[0-9]+\n$/) + t.equal(stderr, '') + t.end() + }) +}) diff --git a/test/cli/common.js b/test/cli/common.js new file mode 100644 index 0000000..76022f3 --- /dev/null +++ b/test/cli/common.js @@ -0,0 +1,110 @@ +'use strict' + +var path = require('path') +var fs = require('fs') +var request = require('request') +var child_process = require('child_process') +var bl = require('bl') + +var port = process.env.PORT || 1337 + +var server +var stdout = bl() +var stderr = bl() + +var stExpect = fs.readFileSync(require.resolve('../../st.js'), 'utf8') + + +// Run server with given command line arguments, +// then allow cbRequests to schedule a bunch of requests, +// finally call cbDone. +// cbRequests gets the req function as an argument. + +function serve (args, cbRequests, cbDone) { + args = [require.resolve('../../bin/server.js')].concat(args || []) + var server = child_process.spawn(process.execPath, args, { + cwd: path.dirname(path.dirname(__dirname)), + stdio: ['ignore', 'pipe', 'pipe'], + env: { LANG: 'C', LC_ALL: 'C' } + }) + var stdout = bl() + var stderr = bl() + server.stdout.pipe(stdout) + server.stderr.pipe(stderr) + var thingsToDo = 4 // cbRequests, exit, stdout, stderr + var code = null + var signal = null + var cbReqEr = null + var outputSeen = false + server.once('error', function (er) { + thingsToDo = -10 // only call cbDone once + cbDone(er) + }) + server.once('exit', function (c, s) { + code = c + signal = s + if (!outputSeen) { + outputSeen = true + --thingsToDo + } + then() + }) + stdout.once('finish', then) + stderr.once('finish', then) + server.stdout.once('data', function () { + if (outputSeen) return + outputSeen = true + try { + cbRequests(req) + } catch (er) { + cbReqEr = er + } finally { + then() + } + }) + + function then () { + --thingsToDo + if (thingsToDo === 3) { // all requests done, one way or another + server.kill() + } else if (thingsToDo === 0) { + var er = null + if (cbReqEr) + er = cbReqEr + else if (signal !== null && signal !== 'SIGTERM') + er = Error("Terminated by signal " + signal) + else if (code !== null && code !== 0) + er = Error("Exited with code " + code) + var o = stdout.toString(), e = stderr.toString() + if (er) console.info(o), console.error(e) + cbDone(er, o, e) + } + } + + function req (url, headers, cb) { + if (typeof headers === 'function') { + cb = headers + headers = {} + } + if (!/:\/\//.test(url)) { + url = 'http://localhost:' + port + url + } + ++thingsToDo + request({ + encoding: null, + url: url, + headers: headers + }, function () { + try { + cb.apply(null, arguments) + } finally { + then() + } + }) + } + +} + +module.exports.port = port +module.exports.stExpect = stExpect +module.exports.serve = serve diff --git a/test/cli/host-test.js b/test/cli/host-test.js new file mode 100644 index 0000000..b029070 --- /dev/null +++ b/test/cli/host-test.js @@ -0,0 +1,87 @@ +var os = require('os') +var tap = require('tap') +var test = tap.test +var common = require('./common') +var serve = common.serve + +var otherAddress = (function () { + var ifaces = os.networkInterfaces() + for (var iface in ifaces) { + var addrs = ifaces[iface] + for (var i = 0; i < addrs.length; ++i) { + var addr = addrs[i].address + if (/^127\./.test(addr) || /^::1$/.test(addr)) // loopback device + continue + if (/^fe80:/.test(addr)) // link-local address + continue + return addr + } + } + return null +})() +if (!otherAddress) { + tap.fail('No non-loopback network address found', {skip: true}) + test = function () {} +} else { + tap.comment('Using ' + otherAddress + ' as non-localhost address') +} + +function addr2url (addr, path) { + if (/:/.test(addr)) addr = '[' + addr + ']' + addr = 'http://' + addr + ':' + common.port + if (path) addr += path + return addr +} + +function testServer (name, args, addr, canConnect, cannotConnect) { + test(name, function (t) { + serve(args, function (req) { + canConnect.forEach(checkConnections(t, req, true)) + cannotConnect.forEach(checkConnections(t, req, false)) + }, function (err, stdout, stderr) { + t.ifError(err) + t.equal(stderr, '') + if (addr) { + t.equal(stdout, 'listening at ' + addr2url(addr) + '\n') + } + t.end() + }) + }) +} + +function checkConnections (t, req, canConnect) { + return function (addr) { + var url = addr2url(addr, '/st.js') + req(url, function (er, res, body) { + if (canConnect) { + t.ifError(er, url) && t.equal(res.statusCode, 200, url) + } else { + t.ok(er, url) + } + }) + } +} + +testServer( + 'Listening on all ports by default', + [], null, + ['127.0.0.1', 'localhost', otherAddress], [] +) + +testServer( + 'Restricted to localhost', + ['--localhost'], 'localhost', + ['127.0.0.1', 'localhost'], [otherAddress] +) + +testServer( + 'Restricted to non-local host', + ['--host', otherAddress], otherAddress, + [otherAddress], ['127.0.0.1'] +) + +testServer( + 'Restricted to IPv4', + ['--host', '127.0.0.1'], '127.0.0.1', + ['127.0.0.1'], ['::1'] +)