mirror of
https://github.com/gundb/panic-server.git
synced 2026-04-15 03:00:16 -04:00
Customized eslint, use mocha instead, implement client
filters/exclusions, all tests passing. Eslint came with these nasty notions about alphabetizing your variable definitions. Strict enforcement has been enabled, and empty functions are now allowed (noop, and it was causing noise while developing). The tests are now using mocha instead of jasmine, as I'm trying to become more familiar with it. The clientList is now dynamically controlled. Only connected peers are shown, and add/remove events are fired as things change. You can now filter a client set against criteria (platform, generic callback) and return a new list of live updating clients, subscribed to changes on the parent list. There's some syntax sugar also. You can create a list that excludes another set, such as a list of browsers might be everything that isn't a Node.js platform. You'd write that as an exclusion. Added some basic tests for the clientList and mock logic.
This commit is contained in:
@@ -99,7 +99,7 @@ module.exports = {
|
||||
"no-div-regex": "error",
|
||||
"no-duplicate-imports": "error",
|
||||
"no-else-return": "error",
|
||||
"no-empty-function": "error",
|
||||
"no-empty-function": "off",
|
||||
"no-eq-null": "error",
|
||||
"no-eval": "error",
|
||||
"no-extend-native": "off",
|
||||
@@ -203,7 +203,7 @@ module.exports = {
|
||||
"semi": "error",
|
||||
"semi-spacing": "error",
|
||||
"sort-imports": "error",
|
||||
"sort-vars": "error",
|
||||
"sort-vars": "off",
|
||||
"space-before-blocks": "error",
|
||||
"space-before-function-paren": "off",
|
||||
"space-in-parens": [
|
||||
@@ -213,7 +213,7 @@ module.exports = {
|
||||
"space-infix-ops": "error",
|
||||
"space-unary-ops": "error",
|
||||
"spaced-comment": "off",
|
||||
"strict": "off",
|
||||
"strict": "error",
|
||||
"template-curly-spacing": "error",
|
||||
"valid-jsdoc": "error",
|
||||
"vars-on-top": "off",
|
||||
@@ -225,4 +225,4 @@ module.exports = {
|
||||
"never"
|
||||
]
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"description": "E2E distributed test framework",
|
||||
"main": "src/index.js",
|
||||
"scripts": {
|
||||
"test": "jasmine"
|
||||
"test": "mocha"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -24,9 +24,11 @@
|
||||
},
|
||||
"homepage": "https://github.com/gundb/panic-server#readme",
|
||||
"dependencies": {
|
||||
"bluebird": "^3.3.5",
|
||||
"socket.io": "^1.4.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"chai": "^3.5.0",
|
||||
"eslint": "^2.8.0",
|
||||
"mocha": "^2.4.5"
|
||||
}
|
||||
|
||||
@@ -1,64 +0,0 @@
|
||||
/*jslint node: true*/
|
||||
'use strict';
|
||||
|
||||
var Emitter = require('events');
|
||||
var io = require('socket.io');
|
||||
var List = require('../src/framework/ClientList');
|
||||
var server;
|
||||
|
||||
var clients = new List();
|
||||
|
||||
function subscribe(socket) {
|
||||
socket.on('connection', function (client) {
|
||||
|
||||
client.on('details', function (ID, platform) {
|
||||
client.platform = platform;
|
||||
client.PANIC_ID = ID;
|
||||
client.platform.ID = ID;
|
||||
client.setMaxListeners(Infinity);
|
||||
clients.add(client);
|
||||
});
|
||||
|
||||
client.on('ready', function (testID) {
|
||||
server.emit('ready', testID, client);
|
||||
});
|
||||
|
||||
client.on('event', server.emit.bind(server));
|
||||
});
|
||||
}
|
||||
|
||||
function open(port) {
|
||||
|
||||
// set default port
|
||||
port = port || 8080;
|
||||
|
||||
// don't try to re-open
|
||||
if (server.socket) {
|
||||
return server.socket;
|
||||
}
|
||||
|
||||
// update state
|
||||
var socket = io(port);
|
||||
server.socket = socket;
|
||||
|
||||
subscribe(socket);
|
||||
return socket;
|
||||
}
|
||||
|
||||
function close(port) {
|
||||
var socket = server.socket;
|
||||
if (server.socket) {
|
||||
socket.close();
|
||||
server.socket = null;
|
||||
}
|
||||
return socket;
|
||||
}
|
||||
|
||||
// Merge server with event emitter
|
||||
server = module.exports = new Emitter();
|
||||
server.setMaxListeners(Infinity);
|
||||
server.socket = null;
|
||||
server.port = null;
|
||||
server.open = open;
|
||||
server.close = close;
|
||||
server.clients = clients;
|
||||
117
src/ClientList.js
Normal file
117
src/ClientList.js
Normal file
@@ -0,0 +1,117 @@
|
||||
'use strict';
|
||||
var Emitter = require('events');
|
||||
var match = require('./matcher');
|
||||
var Promise = require('bluebird');
|
||||
function ClientList() {
|
||||
Emitter.call(this);
|
||||
this.clients = {};
|
||||
}
|
||||
|
||||
var API = ClientList.prototype = new Emitter();
|
||||
|
||||
API.each = function (cb) {
|
||||
var key;
|
||||
for (key in this.clients) {
|
||||
if (this.clients.hasOwnProperty(key)) {
|
||||
cb(this.clients[key], key, this);
|
||||
}
|
||||
}
|
||||
return this;
|
||||
};
|
||||
|
||||
API.add = function (client) {
|
||||
var list = this;
|
||||
if (!client.socket.connected) {
|
||||
return this;
|
||||
}
|
||||
this.clients[client.socket.id] = client;
|
||||
client.socket.on('disconnect', function () {
|
||||
list.remove(client);
|
||||
});
|
||||
this.emit('add', client, client.socket.id);
|
||||
return this;
|
||||
};
|
||||
|
||||
API.remove = function (client) {
|
||||
if (client.socket.id in this.clients) {
|
||||
delete this.clients[client.socket.id];
|
||||
this.emit('remove', client, client.socket.id);
|
||||
}
|
||||
return this;
|
||||
};
|
||||
|
||||
API.get = function (ID) {
|
||||
return this.clients[ID] || null;
|
||||
};
|
||||
|
||||
API.filter = function (query) {
|
||||
var list = new ClientList();
|
||||
function filter(client, ID) {
|
||||
if (query instanceof Function && query(client, ID)) {
|
||||
list.add(client);
|
||||
return;
|
||||
} else if (typeof query === 'string') {
|
||||
query = {
|
||||
name: query
|
||||
};
|
||||
}
|
||||
if (typeof query === 'object' && query) {
|
||||
if (match(query, client.platform)) {
|
||||
list.add(client);
|
||||
}
|
||||
}
|
||||
}
|
||||
this.each(filter).on('add', filter);
|
||||
return list;
|
||||
};
|
||||
|
||||
API.excluding = function (exclude) {
|
||||
if (!(exclude instanceof ClientList)) {
|
||||
throw new Error('Exclusion set is not a ClientList');
|
||||
}
|
||||
return this.filter(function (client) {
|
||||
return !exclude.get(client.socket.id);
|
||||
});
|
||||
};
|
||||
|
||||
API.len = function () {
|
||||
if (Object.keys instanceof Function) {
|
||||
return Object.keys(this.clients).length;
|
||||
}
|
||||
var num = 0;
|
||||
this.each(function () {
|
||||
num += 1;
|
||||
});
|
||||
return num;
|
||||
};
|
||||
|
||||
API.run = function (cb) {
|
||||
var key, done = 0, list = this, length = this.len();
|
||||
key = Math.random()
|
||||
.toString(36)
|
||||
.slice(2);
|
||||
|
||||
return new Promise(function (resolve, reject) {
|
||||
function count(err) {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else if ((done += 1) >= length) {
|
||||
resolve(list);
|
||||
}
|
||||
}
|
||||
function add() {
|
||||
count(null);
|
||||
}
|
||||
list.each(function (client) {
|
||||
client.socket
|
||||
.on(key, function (err) {
|
||||
count(err);
|
||||
client.socket.removeListener('disconnect', add);
|
||||
})
|
||||
.once('disconnect', add)
|
||||
.emit('run', cb, key);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = ClientList;
|
||||
2
src/clients.js
Normal file
2
src/clients.js
Normal file
@@ -0,0 +1,2 @@
|
||||
var List = require('./ClientList');
|
||||
module.exports = new List();
|
||||
7
src/index.js
Normal file
7
src/index.js
Normal file
@@ -0,0 +1,7 @@
|
||||
var server = require('./server');
|
||||
var clients = require('./clients');
|
||||
|
||||
module.exports = {
|
||||
serve: server,
|
||||
clients: clients
|
||||
};
|
||||
19
src/matcher.js
Normal file
19
src/matcher.js
Normal file
@@ -0,0 +1,19 @@
|
||||
function match(query, platform) {
|
||||
var key, value, matches = true;
|
||||
for (key in query) {
|
||||
if (!(query.hasOwnProperty(key))) {
|
||||
continue;
|
||||
}
|
||||
value = query[key];
|
||||
if (value instanceof RegExp) {
|
||||
matches = matches && !!platform[key].match(value);
|
||||
} else if (typeof value === 'string') {
|
||||
matches = matches && platform[key] === value;
|
||||
} else if (value instanceof Object) {
|
||||
return match(value, platform[key] || {});
|
||||
}
|
||||
}
|
||||
return matches;
|
||||
}
|
||||
|
||||
module.exports = match;
|
||||
31
src/server.js
Normal file
31
src/server.js
Normal file
@@ -0,0 +1,31 @@
|
||||
var io = require('socket.io');
|
||||
var fs = require('fs');
|
||||
var clients = require('./clients');
|
||||
|
||||
var server = require('http').createServer(function (req, res) {
|
||||
if (req.url !== '/panic.js' && req.url !== '/') {
|
||||
return;
|
||||
}
|
||||
var path = require.resolve('../../panic-client/panic.js');
|
||||
fs.createReadStream(path).pipe(res);
|
||||
});
|
||||
|
||||
function open(config) {
|
||||
config = config || {};
|
||||
config.port = config.port || 8080;
|
||||
config.hostname = config.hostname || 'localhost';
|
||||
|
||||
server.listen(config.port, config.hostname);
|
||||
|
||||
io(server).on('connection', function (client) {
|
||||
client.on('handshake', function (platform) {
|
||||
clients.add({
|
||||
socket: client,
|
||||
platform: platform
|
||||
});
|
||||
});
|
||||
});
|
||||
return config;
|
||||
}
|
||||
|
||||
module.exports = open;
|
||||
113
test/index.js
Normal file
113
test/index.js
Normal file
@@ -0,0 +1,113 @@
|
||||
/*globals beforeEach, describe, it*/
|
||||
'use strict';
|
||||
var mock = require('./mock');
|
||||
var Client = mock.Client;
|
||||
var ClientList = require('../src/ClientList');
|
||||
|
||||
var expect = require('chai').expect;
|
||||
|
||||
describe('A clientList', function () {
|
||||
var list, client;
|
||||
beforeEach(function () {
|
||||
list = new ClientList();
|
||||
client = new Client({
|
||||
name: 'Node.js'
|
||||
});
|
||||
});
|
||||
|
||||
it('should emit when a client is added', function () {
|
||||
var fired = false;
|
||||
list.on('add', function () {
|
||||
fired = true;
|
||||
}).add(client);
|
||||
expect(fired).to.eq(true);
|
||||
});
|
||||
|
||||
it('should emit when a client is removed', function () {
|
||||
var fired = false;
|
||||
list.on('remove', function () {
|
||||
fired = true;
|
||||
})
|
||||
.add(client)
|
||||
.remove(client);
|
||||
expect(fired).to.eq(true);
|
||||
});
|
||||
|
||||
it('should return length when "len()" is called', function () {
|
||||
list.add(client);
|
||||
expect(list.len()).to.eq(1);
|
||||
list.remove(client);
|
||||
expect(list.len()).to.eq(0);
|
||||
});
|
||||
|
||||
it('should not add disconnected clients', function () {
|
||||
client.socket.connected = false;
|
||||
list.add(client);
|
||||
expect(list.len()).to.eq(0);
|
||||
});
|
||||
|
||||
it('should remove a client on disconnect', function () {
|
||||
list.add(client);
|
||||
expect(list.len()).to.eq(1);
|
||||
client.socket.emit('disconnect');
|
||||
expect(list.len()).to.eq(0);
|
||||
});
|
||||
|
||||
describe('filter', function () {
|
||||
it('should not mutate the original list', function () {
|
||||
list.add(client);
|
||||
expect(list.len()).to.eq(1);
|
||||
list.filter(function () {
|
||||
return false;
|
||||
});
|
||||
expect(list.len()).to.eq(1);
|
||||
});
|
||||
|
||||
it('should return a new, filtered list', function () {
|
||||
list.add(client);
|
||||
var servers = list.filter('Node.js');
|
||||
var browsers = list.filter(function (client) {
|
||||
return client.platform.name !== 'Node.js';
|
||||
});
|
||||
expect(servers.len()).to.eq(1);
|
||||
expect(browsers.len()).to.eq(0);
|
||||
});
|
||||
|
||||
it('should be reactive to changes to the parent list', function () {
|
||||
var servers = list.filter('Node.js');
|
||||
expect(servers.len()).to.eq(0);
|
||||
list.add(client);
|
||||
expect(servers.len()).to.eq(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('exclusion', function () {
|
||||
it('should not contain excluded clients', function () {
|
||||
list.add(client);
|
||||
var filtered = list.excluding(list);
|
||||
expect(filtered.len()).to.eq(0);
|
||||
});
|
||||
});
|
||||
|
||||
it('should resolve a promise when all clients finish', function (done) {
|
||||
client.socket.on('run', function (cb, jobID) {
|
||||
client.socket.emit(jobID);
|
||||
});
|
||||
list.add(client).run(function () {})
|
||||
.then(function () {
|
||||
done();
|
||||
})
|
||||
.catch(done);
|
||||
});
|
||||
|
||||
it('should reject a promise if an error is sent', function (done) {
|
||||
client.socket.on('run', function (cb, job) {
|
||||
client.socket.emit(job, 'fake error');
|
||||
});
|
||||
list.add(client).run(function () {})
|
||||
.catch(function (err) {
|
||||
expect(err).to.eq('fake error');
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
15
test/mock.js
Normal file
15
test/mock.js
Normal file
@@ -0,0 +1,15 @@
|
||||
'use strict';
|
||||
var Emitter = require('events');
|
||||
|
||||
function Client(platform) {
|
||||
this.socket = new Emitter();
|
||||
this.socket.connected = true;
|
||||
this.socket.id = Math.random()
|
||||
.toString(36)
|
||||
.slice(2);
|
||||
this.platform = platform || {};
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
Client: Client
|
||||
};
|
||||
Reference in New Issue
Block a user