Skip to content
This repository has been archived by the owner on Jun 5, 2020. It is now read-only.

Run buster-server-cli in a separate child process #1

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 10 additions & 13 deletions lib/buster-ci.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
/* global module, require, process */
"use strict";

var Agent = require("buster-ci-agent"),
busterServer = require("buster-server-cli"),
busterTestCli = require("buster-test-cli"),
formatio = require("formatio"),
logger = require("stream-logger"),
faye = require("faye"),
async = require("async"),
when = require("when"),
fs = require('fs');
fs = require("fs"),
Server = require("./server");

function traverseObject(obj, func) {
var prop;
Expand Down Expand Up @@ -87,8 +88,8 @@ function createFayeClientAgent(agentName, agent, cb) {
this._logger.info("create faye client for agent: " + agentName);
var agentUrl = "http://" + agentName + ":" + agent.config.port;
agent.client = new faye.Client(agentUrl);
agent.client.on('transport:up', cb);
agent.client.on('transport:down',
agent.client.on("transport:up", cb);
agent.client.on("transport:down",
cb.bind(null, "Agent " + agentUrl + " not accessible!"));
sendMessage.call(this, agent, { command: "ping" });
}
Expand Down Expand Up @@ -230,11 +231,7 @@ function BusterCi(config) {
throw new Error("No agents specified in the config!");
}

this._server = busterServer.create(process.stdout, process.stderr, {
binary: "buster-ci-server",
unexpectedErrorMessage: ""
});

this._server = Server.create();

var outputStream = config.outputFile ?
fs.createWriteStream(config.outputFile) : process.stdout;
Expand Down Expand Up @@ -274,10 +271,7 @@ BusterCi.prototype = {

var tasks = {
startLocalAgent: startLocalAgent.bind(this),
runServer: this._server.run.bind(
this._server,
["-p" + (this._serverCfg.port || 1111)]
)
runServer: this._server.run.bind(this._server, ["-p" + (this._serverCfg.port || 1111)])
};

var createFayeClientTasks = [];
Expand Down Expand Up @@ -328,6 +322,9 @@ BusterCi.prototype = {
this._logger.error(err);
}
closeBrowsers.call(this, function (err) {
// Be sure to kill the server process
this._server.kill();

if (this._localAgent) {
this._localAgent.close();
}
Expand Down
70 changes: 70 additions & 0 deletions lib/server.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/* global module, require, process, __dirname */
"use strict";

var ChildProcess = require("child_process");
var path = require("path");
var serverProcessPath = path.join(__dirname, "./server/process");

var Server = function() {
// Create a server child process fork to communicate with the server.
// This will prevent long processes in this parent process to stall
// the server child process from responding to slave browsers.
this._childProcess = ChildProcess.fork(serverProcessPath);
this._killProcess = this.kill.bind(this);

// Be sure to kill the server process when this parent process closes
// Creating a new bound function so that the event can be removed during testing for cleanup
process.on("close", this._killProcess);

return this;
};

Server.prototype = {
create: function(){
return new Server();
},

run: function(args, cb) {
var childProcess = this._childProcess;
if (childProcess) {
// callback for passing server message to the task callback
var callback = function(response) {
// Specifically listen a response containing method == run.
if (response.method == "run") {
cb(response.error);
// Clean up by removing the listener
childProcess.removeListener("message", callback);
}
};

// Handle server process' message that will trigger the tasks' callback
childProcess.on("message", callback);

// Tell the server process to run with port arg
childProcess.send({
method: "run",
args: args
});
} else {
cb(new Error("_childProcess is undefined"));
}
},

kill: function() {
var killProccess = this._killProcess;
// Clean up any attached bound listener.
if (killProccess) {
process.removeListener("close", killProccess);
delete this._killProcess;
}

var childProccess = this._childProcess;
if (childProccess) {
delete this._childProcess;

childProccess.kill();
}
}
};

module.exports = Server.prototype;
37 changes: 37 additions & 0 deletions lib/server/process.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/* global require, process */
"use strict";

var busterServer = require("buster-server-cli");
var server = busterServer.create(process.stdout, process.stderr, {
binary: "buster-ci-server",
unexpectedErrorMessage: ""
});

var handleError = function(method, error) {
process.send({
method: method,
error: error || new Error(method + " not found")
});
};

process.on("message", function(request) {
var method = request.method;
var args = request.args || [];

if (method == "run") {
// Make sure to catch any errors in case the server seems to crash.
try {
server.run.apply(server, args.concat([function(err) {
// Let the parent process know the server is running
process.send({
method: "run",
error: err
});
}]));
} catch (error) {
handleError(method, error);
}
} else {
handleError(method);
}
});
37 changes: 25 additions & 12 deletions test/buster-ci-test.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/* global require, process */
"use strict";

var buster = require("buster"),
Expand All @@ -12,6 +13,9 @@ var buster = require("buster"),
stubServerFayeClient = th.stubServerFayeClient,
setFayeClientNotAccessible = th.setFayeClientNotAccessible,
asyncTest = th.asyncTest,
childProcessStub = th.childProcessStub,
childProcessForkMock = th.childProcessForkMock,
ChildProcess = th.ChildProcess,

assert = buster.assert,
refute = buster.refute,
Expand Down Expand Up @@ -54,7 +58,7 @@ buster.testCase("buster-ci", {

this.browsersAgentLocalhost = {
browsers: this.config.browsers
}
};
this.browsersAgentRemotehost1 = {
browsers: {
"FF": {},
Expand Down Expand Up @@ -83,7 +87,7 @@ buster.testCase("buster-ci", {
th.tearDown();
},

"throws if no agents are specified": function () {
"if no agents are specified": function () {
assert.exception(function () {
var busterCi = new BusterCi({});
}, { message: "no agents" });
Expand All @@ -94,14 +98,14 @@ buster.testCase("buster-ci", {
var busterCi = new BusterCi(this.config);

assert.calledOnce(busterServer.create);
assert.calledWith(
busterServer.create,
match.any,
match.any,
match({
binary: match.string
})
);
// assert.calledWith(
// busterServer.create,
// match.any,
// match.any,
// match({
// binary: match.string
// })
// );
},

"starts local agent if agent 'localhost' is specified": function (done) {
Expand Down Expand Up @@ -253,7 +257,7 @@ buster.testCase("buster-ci", {

assert.calledOnce(th.server.run);
assert.calledWith(th.server.run, match(function (array) {
return array.indexOf("-p2222") >= 0
return array.indexOf("-p2222") >= 0;
}));
}));
},
Expand Down Expand Up @@ -635,5 +639,14 @@ buster.testCase("buster-ci", {
refute.called(th.fs.createWriteStream);
assert.calledWith(th.testCli.create, process.stdout);
}.bind(this)));
}
},

"kills server when done": function(done){
var busterCi = new BusterCi(this.config);
var serverKillSpy = this.spy(busterCi._server, "kill");

busterCi.run([], done(function(){
assert.calledOnce(serverKillSpy);
}));
}
});
72 changes: 72 additions & 0 deletions test/server-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/* global require */
"use strict";

var buster = require("buster"),
EventEmitter = require("events").EventEmitter,
ChildProcess = require("child_process"),
proxyquire = require("proxyquire"),
Server = proxyquire("../lib/server", {
"child_process": ChildProcess
}),
assert = buster.assert,
refute = buster.refute,
childProcessForkMock = new EventEmitter(),
sandbox;

childProcessForkMock.send = function(){};
childProcessForkMock.message = function(){};
childProcessForkMock.kill = function(){};

function stubChildProcess() {
sandbox.stub(childProcessForkMock, "kill");

sandbox.stub(childProcessForkMock, "send", function(msg){
if (msg.method == "run") {
childProcessForkMock.emit("message", {
method: "run",
error: null
});
}
});

sandbox.stub(ChildProcess, "fork").returns(childProcessForkMock);

return ChildProcess;
}

buster.testCase("buster-ci server", {
setUp: function(){
sandbox = buster.sinon.sandbox.create();
this.ChildProcess = stubChildProcess.call(this);
},

tearDown: function(){
sandbox.restore();
},
"creates a child process fork": function(){
Server.create();
assert.calledOnce(this.ChildProcess.fork);
},

"run sends a message containing args": function(done){
var server = Server.create();
var args = ["-p" + 1111];

server.run(args, done(function(){
assert.calledWith(childProcessForkMock.send, {
method: "run",
args: args
});
}));
},

"kills child process": function(){
var server = Server.create();
assert.defined(server._childProcess);
assert.defined(server._killProcess);
server.kill();
refute.defined(server._childProcess);
refute.defined(server._killProcess);
assert.calledOnce(childProcessForkMock.kill);
}
});
67 changes: 67 additions & 0 deletions test/server/process-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/* global require, process */
"use strict";

var buster = require("buster"),
busterServer = require("buster-server-cli"),
proxyquire = require("proxyquire"),
assert = buster.assert,
refute = buster.refute,
match = buster.sinon.match,
sandbox = buster.sinon.sandbox.create(),
server,
processSend;

server = busterServer.create(undefined, undefined, {});
sandbox.stub(server, "run");
sandbox.stub(busterServer, "create").returns(server);

proxyquire("../../lib/server/process", {
"buster-server-cli": busterServer
});

buster.testCase("buster-ci server process", {
setUp: function(){
processSend = process.send;
},

tearDown: function(){
if (processSend) {
process.send = processSend;
} else {
delete process.send;
}
},

"creates server": function(){
assert.calledOnce(busterServer.create);
},

"message runs server": function(done){
var message = {
method: "run",
args: ["-p1111"]
};
var args = message.args.concat([match.func]);

process.send = done(function(){
assert.calledOnceWith(server.run, args[0], args[1]);
});

process.emit("message", message);

server.run.callArg(1);
},

"unhandled message sends error": function(done){
var message = {
method: "abc"
};

process.send = done(function(msg){
assert.match(msg, message);
assert.hasPrototype(msg.error, Error.prototype);
});

process.emit("message", message);
}
});
Loading