Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

[api test] Update `portfinder` to also be capable of returning unboun…

…d socket paths
  • Loading branch information...
commit f1cd7aeab8e1962a28fbda69255a3588e83add70 1 parent 6d3a040
@indexzero authored
View
145 lib/portfinder.js
@@ -5,10 +5,30 @@
*
*/
-var http = require('http');
+var fs = require('fs'),
+ http = require('http'),
+ net = require('net'),
+ path = require('path'),
+ mkdirp = require('mkdirp').mkdirp;
+//
+// ### @basePort {Number}
+// The lowest port to begin any port search from
+//
exports.basePort = 8000;
+//
+// ### @basePath {string}
+// Default path to begin any socket search from
+//
+exports.basePath = '/tmp/portfinder'
+
+//
+// ### function getPort (options, callback)
+// #### @options {Object} Settings to use when finding the necessary port
+// #### @callback {function} Continuation to respond to when complete.
+// Responds with a unbound port on the current machine.
+//
exports.getPort = function (options, callback) {
if (!callback) {
callback = options;
@@ -25,6 +45,7 @@ exports.getPort = function (options, callback) {
function onListen () {
options.server.removeListener('error', onError);
+ options.server.close();
callback(null, options.port)
}
@@ -47,6 +68,128 @@ exports.getPort = function (options, callback) {
options.server.listen(options.port, options.host);
};
+//
+// ### function getSocket (options, callback)
+// #### @options {Object} Settings to use when finding the necessary port
+// #### @callback {function} Continuation to respond to when complete.
+// Responds with a unbound socket using the specified directory and base
+// name on the current machine.
+//
+exports.getSocket = function (options, callback) {
+ if (!callback) {
+ callback = options;
+ options = {};
+ }
+
+ var socket = new net.Socket();
+ options.mod = options.mod || 0755;
+ options.path = options.path || exports.basePath + '.sock';
+
+ //
+ // If an `ENOENT` or `ECONNREFUSED` error is emitted from
+ // the socket then it is available.
+ //
+ function onError (err) {
+ socket.removeListener('connect', onConnect);
+ socket.destroy();
+
+ return ~['ENOENT', 'ECONNREFUSED'].indexOf(err.code)
+ ? callback(null, options.path)
+ : callback(err);
+ }
+
+ //
+ // If we are able to connect to the socket then it is
+ // not available and we should keep trying.
+ //
+ function onConnect () {
+ socket.removeListener('error', onError);
+ socket.destroy();
+
+ options.path = exports.nextSocket(options.path);
+ exports.getSocket(options, callback);
+ }
+
+ //
+ // Tests the specified socket
+ //
+ function testSocket () {
+ socket.once('error', onError);
+ socket.once('connect', onConnect);
+ socket.connect(options.path);
+ }
+
+ //
+ // Create the target `dir` then test connection
+ // against the socket.
+ //
+ function createAndTestSocket (dir) {
+ mkdirp(dir, options.mod, function (err) {
+ if (err) {
+ return callback(err);
+ }
+
+ options.exists = true;
+ testSocket();
+ });
+ }
+
+ //
+ // Check if the parent directory of the target
+ // socket path exists. If it does, test connection
+ // against the socket. Otherwise, create the directory
+ // then test connection.
+ //
+ function checkAndTestSocket () {
+ var dir = path.dirname(options.path);
+
+ fs.stat(dir, function (err, stats) {
+ if (err || !stats.isDirectory()) {
+ return createAndTestSocket(dir);
+ }
+
+ options.exists = true;
+ testSocket();
+ });
+ }
+
+ //
+ // If it has been explicitly stated that the
+ // target `options.path` already exists, then
+ // simply test the socket.
+ //
+ return options.exists
+ ? testSocket()
+ : checkAndTestSocket();
+};
+
+//
+// ### function nextPort (port)
+// #### @port {Number} Port to increment from.
+// Gets the next port in sequence from the
+// specified `port`.
+//
exports.nextPort = function (port) {
return port + 1;
+};
+
+//
+// ### function nextSocket (socketPath)
+// #### @socketPath {string} Path to increment from
+// Gets the next socket path in sequence from the
+// specified `socketPath`.
+//
+exports.nextSocket = function (socketPath) {
+ var dir = path.dirname(socketPath),
+ name = path.basename(socketPath, '.sock'),
+ match = name.match(/^([a-zA-z]+)(\d*)$/i),
+ index = parseInt(match[2]),
+ base = match[1];
+
+ if (isNaN(index)) {
+ index = 0;
+ }
+
+ index += 1;
+ return path.join(dir, base + index + '.sock');
};
View
0  test/fixtures/.gitkeep
No changes.
View
92 test/port-finder-socket-test.js
@@ -0,0 +1,92 @@
+/*
+ * portfinder-test.js: Tests for the `portfinder` module.
+ *
+ * (C) 2011, Charlie Robbins
+ *
+ */
+
+var assert = require('assert'),
+ exec = require('child_process').exec,
+ net = require('net'),
+ path = require('path'),
+ async = require('async'),
+ vows = require('vows'),
+ portfinder = require('../lib/portfinder');
+
+var servers = [],
+ socketDir = path.join(__dirname, 'fixtures'),
+ badDir = path.join(__dirname, 'bad-dir');
+
+function createServers (callback) {
+ var base = 0;
+
+ async.whilst(
+ function () { return base < 5 },
+ function (next) {
+ var server = net.createServer(function () { }),
+ name = base === 0 ? 'test.sock' : 'test' + base + '.sock';
+
+ server.listen(path.join(socketDir, name), next);
+ base++;
+ servers.push(server);
+ }, callback);
+}
+
+vows.describe('portfinder').addBatch({
+ "When using portfinder module": {
+ "with 5 existing servers": {
+ topic: function () {
+ createServers(this.callback);
+ },
+ "the getPort() method": {
+ topic: function () {
+ portfinder.getSocket({
+ path: path.join(socketDir, 'test.sock')
+ }, this.callback);
+ },
+ "should respond with the first free socket (test5.sock)": function (err, socket) {
+ assert.isTrue(!err);
+ assert.equal(socket, path.join(socketDir, 'test5.sock'));
+ }
+ }
+ }
+ }
+}).addBatch({
+ "When using portfinder module": {
+ "with no existing servers": {
+ "the getSocket() method": {
+ "with a directory that doesnt exist": {
+ topic: function () {
+ var that = this;
+ exec('rm -rf ' + badDir, function () {
+ portfinder.getSocket({
+ path: path.join(badDir, 'test.sock')
+ }, that.callback);
+ });
+ },
+ "should respond with the first free socket (test.sock)": function (err, socket) {
+ assert.isTrue(!err);
+ assert.equal(socket, path.join(badDir, 'test.sock'));
+ }
+ },
+ "with a directory that exists": {
+ topic: function () {
+ portfinder.getSocket({
+ path: path.join(socketDir, 'exists.sock')
+ }, this.callback);
+ },
+ "should respond with the first free socket (exists.sock)": function (err, socket) {
+ assert.isTrue(!err);
+ assert.equal(socket, path.join(socketDir, 'exists.sock'));
+ }
+ }
+ }
+ }
+ }
+}).addBatch({
+ "When the tests are over": {
+ "necessary cleanup should take place": function () {
+ exec('rm -rf ' + badDir + ' ' + path.join(socketDir, '*'), function () { });
+ }
+ }
+}).export(module);
Please sign in to comment.
Something went wrong with that request. Please try again.