mirror of
https://gitlab.silvrtree.co.uk/martind2000/nci.git
synced 2025-01-25 16:16:16 +00:00
Merge pull request #10 from node-ci/feature_expose_executor
Feature expose executor
This commit is contained in:
commit
84c6b8ee05
12
app.js
12
app.js
@ -16,6 +16,10 @@ var env = process.env.NODE_ENV || 'development',
|
||||
ProjectsCollection = require('./lib/project').ProjectsCollection,
|
||||
BuildsCollection = require('./lib/build').BuildsCollection,
|
||||
libLogger = require('./lib/logger'),
|
||||
libNode = require('./lib/node'),
|
||||
libCommand = require('./lib/command'),
|
||||
libExecutor = require('./lib/executor'),
|
||||
libScm = require('./lib/scm'),
|
||||
EventEmitter = require('events').EventEmitter,
|
||||
validateConfig = require('./lib/validateConfig'),
|
||||
utils = require('./lib/utils');
|
||||
@ -64,6 +68,10 @@ app.lib = {};
|
||||
app.lib.BaseReaderLoader = BaseReaderLoader;
|
||||
app.lib.BaseNotifierTransport = BaseNotifierTransport;
|
||||
app.lib.logger = libLogger;
|
||||
app.lib.command = libCommand;
|
||||
app.lib.executor = libExecutor;
|
||||
app.lib.scm = libScm;
|
||||
app.lib.node = libNode;
|
||||
|
||||
var configDefaults = {
|
||||
notify: {},
|
||||
@ -222,7 +230,7 @@ Steppy(
|
||||
completeUncompletedBuilds(this.slot());
|
||||
},
|
||||
function() {
|
||||
require('./distributor').init(app, this.slot());
|
||||
require('./distributor').create(app, this.slot());
|
||||
},
|
||||
function(err, distributor) {
|
||||
app.builds = new BuildsCollection({
|
||||
@ -236,6 +244,8 @@ Steppy(
|
||||
require(plugin).register(app);
|
||||
});
|
||||
|
||||
distributor.init();
|
||||
|
||||
app.notifier.init(app.config.notify, this.slot());
|
||||
},
|
||||
function() {
|
||||
|
@ -7,7 +7,7 @@ var Steppy = require('twostep').Steppy,
|
||||
logger = require('./lib/logger')('distributor');
|
||||
|
||||
|
||||
exports.init = function(app, callback) {
|
||||
exports.create = function(app, callback) {
|
||||
var distributor = new Distributor({
|
||||
nodes: app.config.nodes,
|
||||
projects: app.projects,
|
||||
|
@ -4,23 +4,9 @@ var EventEmitter = require('events').EventEmitter,
|
||||
inherits = require('util').inherits;
|
||||
|
||||
function Command(params) {
|
||||
params = params || {};
|
||||
this.emitIn = params.emitIn;
|
||||
this.emitOut = params.emitOut;
|
||||
this.emitErr = params.emitErr;
|
||||
this.attachStderr = params.attachStderr;
|
||||
this.setParams(params);
|
||||
}
|
||||
|
||||
exports.Command = Command;
|
||||
|
||||
inherits(Command, EventEmitter);
|
||||
|
||||
Command.prototype.enableEmitter = function() {
|
||||
this.emitOut = true;
|
||||
return this;
|
||||
};
|
||||
|
||||
Command.prototype.disableEmitter = function() {
|
||||
this.emitOut = false;
|
||||
return this;
|
||||
};
|
||||
|
@ -1,5 +1,9 @@
|
||||
'use strict';
|
||||
|
||||
var SpawnCommand = require('./spawn').Command;
|
||||
|
||||
exports.SpawnCommand = SpawnCommand;
|
||||
|
||||
exports.createCommand = function(params) {
|
||||
var Constructor = require('./' + params.type).Command;
|
||||
return new Constructor(params);
|
||||
|
@ -6,15 +6,22 @@ var spawn = require('child_process').spawn,
|
||||
_ = require('underscore');
|
||||
|
||||
function Command(params) {
|
||||
params = params || {};
|
||||
ParentCommand.call(this, params);
|
||||
this.cwd = params.cwd;
|
||||
ParentCommand.call(this, params || {});
|
||||
}
|
||||
|
||||
exports.Command = Command;
|
||||
|
||||
inherits(Command, ParentCommand);
|
||||
|
||||
Command.prototype.setParams = function(params) {
|
||||
if (params.cwd) this.cwd = params.cwd;
|
||||
if (params.emitIn) this.emitIn = params.emitIn;
|
||||
if (params.emitOut) this.emitOut = params.emitOut;
|
||||
if (params.emitErr) this.emitErr = params.emitErr;
|
||||
if (params.attachStderr) this.attachStderr = params.attachStderr;
|
||||
if (params.collectOut) this.collectOut = params.collectOut;
|
||||
};
|
||||
|
||||
/**
|
||||
* Executes `params.cmd` with `params.args` and `params.options`
|
||||
*/
|
||||
|
@ -2,42 +2,58 @@
|
||||
|
||||
var Steppy = require('twostep').Steppy,
|
||||
_ = require('underscore'),
|
||||
Node = require('./node').Node,
|
||||
createNode = require('./node').createNode,
|
||||
EventEmitter = require('events').EventEmitter,
|
||||
inherits = require('util').inherits,
|
||||
logger = require('./logger')('distributor');
|
||||
|
||||
|
||||
function Distributor(params) {
|
||||
var self = this;
|
||||
// nodes to execute builds
|
||||
self.nodes = _(params.nodes).map(function(nodeParams) {
|
||||
return self._createNode(nodeParams);
|
||||
});
|
||||
// queued projects to build
|
||||
self.queue = [];
|
||||
this.nodes = params.nodes;
|
||||
|
||||
self.saveBuild = params.saveBuild || function(build, callback) {
|
||||
// queued projects to build
|
||||
this.queue = [];
|
||||
|
||||
this.saveBuild = params.saveBuild || function(build, callback) {
|
||||
callback(null, build);
|
||||
};
|
||||
|
||||
self.removeBuild = params.removeBuild || function(build, callback) {
|
||||
this.removeBuild = params.removeBuild || function(build, callback) {
|
||||
callback();
|
||||
};
|
||||
|
||||
self.projects = params.projects;
|
||||
self.notifier = params.notifier;
|
||||
this.projects = params.projects;
|
||||
this.notifier = params.notifier;
|
||||
|
||||
self.buildLogLineNumbersHash = {},
|
||||
self.lastLinesHash = {};
|
||||
this.buildLogLineNumbersHash = {},
|
||||
this.lastLinesHash = {};
|
||||
}
|
||||
|
||||
inherits(Distributor, EventEmitter);
|
||||
|
||||
exports.Distributor = Distributor;
|
||||
|
||||
Distributor.prototype._createNode = function(nodeParams) {
|
||||
return new Node(nodeParams);
|
||||
// do deferred initialization (e.g. create nodes after all plugins load)
|
||||
Distributor.prototype.init = function() {
|
||||
var self = this,
|
||||
namesHash = {};
|
||||
|
||||
self.nodes = _(self.nodes).map(function(nodeParams) {
|
||||
if (!nodeParams.name) {
|
||||
nodeParams.name = nodeParams.type;
|
||||
}
|
||||
|
||||
if (namesHash[nodeParams.name]) {
|
||||
throw new Error('Node name `' + nodeParams.name + '` already used');
|
||||
}
|
||||
namesHash[nodeParams.name] = 1;
|
||||
|
||||
return self._createNode(nodeParams);
|
||||
});
|
||||
};
|
||||
|
||||
Distributor.prototype._createNode = function(params) {
|
||||
return createNode(params);
|
||||
};
|
||||
|
||||
Distributor.prototype._runNext = function(callback) {
|
||||
@ -189,7 +205,7 @@ Distributor.prototype._updateWaitReasons = function() {
|
||||
});
|
||||
|
||||
var waitReason = _(waitReasons).compact().join(', ');
|
||||
// set only non-empty reasons
|
||||
// set only non-empty and new reasons
|
||||
if (waitReason && waitReason !== item.build.waitReason) {
|
||||
self._updateBuild(item.build, {waitReason: waitReason});
|
||||
}
|
||||
|
@ -1,14 +1,12 @@
|
||||
'use strict';
|
||||
|
||||
var Steppy = require('twostep').Steppy,
|
||||
path = require('path'),
|
||||
_ = require('underscore'),
|
||||
EventEmitter = require('events').EventEmitter,
|
||||
inherits = require('util').inherits;
|
||||
|
||||
function Executor(params) {
|
||||
this.project = params.project;
|
||||
this.cwd = path.join(this.project.dir, 'workspace');
|
||||
}
|
||||
|
||||
exports.Executor = Executor;
|
||||
@ -20,15 +18,135 @@ Executor.prototype.throttledEmit = _(function() {
|
||||
}).throttle(500);
|
||||
|
||||
Executor.prototype._getSources = function(params, callback) {
|
||||
var self = this,
|
||||
scm;
|
||||
Steppy(
|
||||
function() {
|
||||
self._getChanges(params, this.slot());
|
||||
},
|
||||
function(err, data) {
|
||||
scm = data.scm;
|
||||
this.pass(data.changes);
|
||||
scm.update(data.rev, this.slot());
|
||||
},
|
||||
function(err, changes) {
|
||||
scm.getCurrent(this.slot());
|
||||
this.pass(changes);
|
||||
scm.getRev(params.rev, this.slot());
|
||||
},
|
||||
function(err, currentRev, changes, latestRev) {
|
||||
this.pass({
|
||||
rev: currentRev,
|
||||
changes: changes,
|
||||
isLatest: currentRev.id === latestRev.id
|
||||
});
|
||||
},
|
||||
callback
|
||||
);
|
||||
};
|
||||
|
||||
Executor.prototype._runStep = function(params, callback) {
|
||||
Executor.prototype._runStep = function(step, callback) {
|
||||
var self = this,
|
||||
params = _(step).clone();
|
||||
|
||||
Steppy(
|
||||
function() {
|
||||
if (params.type !== 'shell') {
|
||||
throw new Error('Unknown step type: ' + params.type);
|
||||
}
|
||||
// set command cwd to executor cwd
|
||||
params.cwd = self.cwd;
|
||||
var command = self._createCommand(
|
||||
_({
|
||||
emitIn: true,
|
||||
emitOut: true,
|
||||
emitErr: true,
|
||||
attachStderr: true
|
||||
}).extend(params)
|
||||
);
|
||||
|
||||
command.on('stdin', function(data) {
|
||||
self.emit('data', '> ' + String(data));
|
||||
});
|
||||
|
||||
command.on('stdout', function(data) {
|
||||
self.emit('data', String(data));
|
||||
});
|
||||
|
||||
command.on('stderr', function(data) {
|
||||
self.emit('data', 'stderr: ' + String(data));
|
||||
});
|
||||
|
||||
// TODO: should be fixed properly, currently it's quick fix for
|
||||
// NODE_ENV which affects npm install/prune calls
|
||||
params.options = params.options || {};
|
||||
params.options.env = params.options.env || process.env;
|
||||
delete params.options.env.NODE_ENV;
|
||||
|
||||
command.run(params, this.slot())
|
||||
},
|
||||
callback
|
||||
);
|
||||
};
|
||||
|
||||
Executor.prototype._getChanges = function(params, callback) {
|
||||
var self = this,
|
||||
scm, isFirstRun, oldRev;
|
||||
Steppy(
|
||||
function() {
|
||||
self._isCloned(this.slot());
|
||||
},
|
||||
function(err, cloned) {
|
||||
var scmParams = {type: params.type};
|
||||
if (cloned) {
|
||||
scmParams.cwd = self.cwd;
|
||||
isFirstRun = false;
|
||||
} else {
|
||||
scmParams.repository = params.repository;
|
||||
isFirstRun = true;
|
||||
}
|
||||
scm = self._createScm(scmParams);
|
||||
|
||||
scm.on('stdin', function(data) {
|
||||
self.emit('data', '> ' + String(data));
|
||||
});
|
||||
|
||||
if (isFirstRun) {
|
||||
this.pass(null);
|
||||
} else {
|
||||
scm.getCurrent(this.slot());
|
||||
}
|
||||
},
|
||||
function(err, id) {
|
||||
oldRev = id;
|
||||
|
||||
if (isFirstRun) {
|
||||
scm.clone(self.cwd, params.rev, this.slot());
|
||||
} else {
|
||||
scm.pull(params.rev, this.slot())
|
||||
}
|
||||
},
|
||||
function() {
|
||||
scm.getChanges(oldRev && oldRev.id, params.rev, this.slot());
|
||||
},
|
||||
function(err, changes) {
|
||||
var target = self._getTarget(params.rev, changes);
|
||||
this.pass({
|
||||
scm: scm,
|
||||
oldRev: oldRev,
|
||||
rev: target.rev,
|
||||
changes: target.changes
|
||||
});
|
||||
},
|
||||
callback
|
||||
);
|
||||
};
|
||||
|
||||
// Does current project scm has new changes to build
|
||||
Executor.prototype.hasScmChanges = function(callback) {
|
||||
|
||||
this._getChanges(this.project.scm, function(err, data) {
|
||||
callback(err, !err && data.changes.length > 0);
|
||||
});
|
||||
};
|
||||
|
||||
Executor.prototype.run = function(params, callback) {
|
||||
|
@ -1,5 +1,9 @@
|
||||
'use strict';
|
||||
|
||||
var BaseExecutor = require('./base').Executor;
|
||||
|
||||
exports.BaseExecutor = BaseExecutor;
|
||||
|
||||
exports.createExecutor = function(params) {
|
||||
var Constructor = require('./' + params.type).Executor;
|
||||
return new Constructor(params);
|
||||
|
@ -6,10 +6,12 @@ var Steppy = require('twostep').Steppy,
|
||||
createScm = require('../scm').createScm,
|
||||
createCommand = require('../command').createCommand,
|
||||
fs = require('fs'),
|
||||
_ = require('underscore');
|
||||
path = require('path'),
|
||||
SpawnCommand = require('../command/spawn').Command;
|
||||
|
||||
function Executor(params) {
|
||||
ParentExecutor.call(this, params);
|
||||
this.cwd = path.join(this.project.dir, 'workspace');
|
||||
}
|
||||
|
||||
inherits(Executor, ParentExecutor);
|
||||
@ -17,140 +19,17 @@ inherits(Executor, ParentExecutor);
|
||||
exports.Executor = Executor;
|
||||
|
||||
Executor.prototype._createScm = function(params) {
|
||||
params.command = new SpawnCommand();
|
||||
|
||||
return createScm(params);
|
||||
};
|
||||
|
||||
Executor.prototype._getChanges = function(params, callback) {
|
||||
var self = this,
|
||||
scm, isFirstRun, oldRev;
|
||||
Steppy(
|
||||
function() {
|
||||
var slot = this.slot();
|
||||
fs.exists(self.cwd, function(exists) {
|
||||
slot(null, exists);
|
||||
});
|
||||
},
|
||||
function(err, exists) {
|
||||
var scmParams = {type: params.type};
|
||||
if (exists) {
|
||||
scmParams.cwd = self.cwd;
|
||||
isFirstRun = false;
|
||||
} else {
|
||||
scmParams.repository = params.repository;
|
||||
isFirstRun = true;
|
||||
}
|
||||
scm = self._createScm(scmParams);
|
||||
|
||||
scm.on('stdin', function(data) {
|
||||
self.emit('data', '> ' + String(data));
|
||||
});
|
||||
|
||||
if (isFirstRun) {
|
||||
this.pass(null);
|
||||
} else {
|
||||
scm.getCurrent(this.slot());
|
||||
}
|
||||
},
|
||||
function(err, id) {
|
||||
oldRev = id;
|
||||
|
||||
if (isFirstRun) {
|
||||
scm.clone(self.cwd, params.rev, this.slot());
|
||||
} else {
|
||||
scm.pull(params.rev, this.slot())
|
||||
}
|
||||
},
|
||||
function() {
|
||||
scm.getChanges(oldRev && oldRev.id, params.rev, this.slot());
|
||||
},
|
||||
function(err, changes) {
|
||||
var target = self._getTarget(params.rev, changes);
|
||||
this.pass({
|
||||
scm: scm,
|
||||
oldRev: oldRev,
|
||||
rev: target.rev,
|
||||
changes: target.changes
|
||||
});
|
||||
},
|
||||
callback
|
||||
);
|
||||
Executor.prototype._createCommand = function(params) {
|
||||
return createCommand(params);
|
||||
};
|
||||
|
||||
Executor.prototype.hasScmChanges = function(callback) {
|
||||
this._getChanges(this.project.scm, function(err, data) {
|
||||
callback(err, !err && data.changes.length > 0);
|
||||
Executor.prototype._isCloned = function(callback) {
|
||||
fs.exists(this.cwd, function(exists) {
|
||||
callback(null, exists);
|
||||
});
|
||||
};
|
||||
|
||||
Executor.prototype._getSources = function(params, callback) {
|
||||
var self = this,
|
||||
scm;
|
||||
Steppy(
|
||||
function() {
|
||||
self._getChanges(params, this.slot());
|
||||
},
|
||||
function(err, data) {
|
||||
scm = data.scm;
|
||||
this.pass(data.changes);
|
||||
scm.update(data.rev, this.slot());
|
||||
},
|
||||
function(err, changes) {
|
||||
scm.getCurrent(this.slot());
|
||||
this.pass(changes);
|
||||
scm.getRev(params.rev, this.slot());
|
||||
},
|
||||
function(err, currentRev, changes, latestRev) {
|
||||
this.pass({
|
||||
rev: currentRev,
|
||||
changes: changes,
|
||||
isLatest: currentRev.id === latestRev.id
|
||||
});
|
||||
},
|
||||
callback
|
||||
);
|
||||
};
|
||||
|
||||
Executor.prototype._runStep = function(step, callback) {
|
||||
var self = this,
|
||||
params = _(step).clone();
|
||||
|
||||
Steppy(
|
||||
function() {
|
||||
if (params.type !== 'shell') {
|
||||
throw new Error('Unknown step type: ' + params.type);
|
||||
}
|
||||
// set command cwd to executor cwd
|
||||
params.cwd = self.cwd;
|
||||
var command = createCommand(
|
||||
_({
|
||||
emitIn: true,
|
||||
emitOut: true,
|
||||
emitErr: true,
|
||||
attachStderr: true
|
||||
}).extend(params)
|
||||
);
|
||||
|
||||
command.on('stdin', function(data) {
|
||||
self.emit('data', '> ' + String(data));
|
||||
});
|
||||
|
||||
command.on('stdout', function(data) {
|
||||
self.emit('data', String(data));
|
||||
});
|
||||
|
||||
command.on('stderr', function(data) {
|
||||
self.emit('data', 'stderr: ' + String(data));
|
||||
});
|
||||
|
||||
// TODO: should be fixed properly, currently it's quick fix for
|
||||
// NODE_ENV which affects npm install/prune calls
|
||||
params.options = params.options || {};
|
||||
params.options.env = params.options.env || process.env;
|
||||
delete params.options.env.NODE_ENV;
|
||||
|
||||
command.run(params, this.slot())
|
||||
},
|
||||
callback
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -1,17 +1,25 @@
|
||||
'use strict';
|
||||
|
||||
var _ = require('underscore'),
|
||||
createExecutor = require('./executor').createExecutor;
|
||||
var _ = require('underscore');
|
||||
|
||||
|
||||
function Node(params) {
|
||||
this.type = params.type;
|
||||
this.maxExecutorsCount = params.maxExecutorsCount;
|
||||
this.name = params.name;
|
||||
this.usageStrategy = params.usageStrategy || 'maximum';
|
||||
|
||||
if (!this.usageStrategiesHash[this.usageStrategy]) {
|
||||
throw new Error('Unknown usage strategy: ' + this.usageStrategy);
|
||||
}
|
||||
|
||||
this.executors = {};
|
||||
}
|
||||
|
||||
exports.Node = Node;
|
||||
|
||||
Node.prototype.usageStrategiesHash = {maximum: 1, specificProject: 1};
|
||||
|
||||
Node.prototype._getBlockerExecutor = function(getBlockers, getTarget) {
|
||||
return _(this.executors).find(function(executor) {
|
||||
var target = getTarget(executor);
|
||||
@ -28,10 +36,20 @@ Node.prototype._getBlockerExecutor = function(getBlockers, getTarget) {
|
||||
Node.prototype.getExecutorWaitReason = function(project) {
|
||||
var waitReason;
|
||||
|
||||
if (_(this.executors).size() >= this.maxExecutorsCount) {
|
||||
waitReason = 'All executors are busy';
|
||||
var targetNodeNames = project.node && project.node.target;
|
||||
|
||||
if (targetNodeNames && !_(targetNodeNames).isArray()) {
|
||||
targetNodeNames = [targetNodeNames];
|
||||
}
|
||||
|
||||
if (targetNodeNames && !_(targetNodeNames).contains(this.name)) {
|
||||
waitReason = this.name + ': not a target node';
|
||||
} else if (this.usageStrategy === 'specificProject' && !targetNodeNames) {
|
||||
waitReason = this.name + ': only for specific projects';
|
||||
} else if (_(this.executors).size() >= this.maxExecutorsCount) {
|
||||
waitReason = this.name + ': all executors are busy';
|
||||
} else if (project.name in this.executors) {
|
||||
waitReason = 'Project already running on node';
|
||||
waitReason = this.name + ': project already running on node';
|
||||
} else {
|
||||
var blockerExecutor;
|
||||
|
||||
@ -59,7 +77,7 @@ Node.prototype.getExecutorWaitReason = function(project) {
|
||||
|
||||
if (blockerExecutor) {
|
||||
waitReason = (
|
||||
'Blocked by currently running "' +
|
||||
this.name + ': blocked by currently running "' +
|
||||
blockerExecutor.project.name + '"'
|
||||
);
|
||||
}
|
||||
@ -76,13 +94,6 @@ Node.prototype.getFreeExecutorsCount = function() {
|
||||
return this.maxExecutorsCount - _(this.executors).size();
|
||||
};
|
||||
|
||||
Node.prototype._createExecutor = function(project) {
|
||||
return createExecutor({
|
||||
type: this.type,
|
||||
project: project
|
||||
});
|
||||
};
|
||||
|
||||
Node.prototype.hasScmChanges = function(project, callback) {
|
||||
this._createExecutor(project).hasScmChanges(callback);
|
||||
};
|
22
lib/node/index.js
Normal file
22
lib/node/index.js
Normal file
@ -0,0 +1,22 @@
|
||||
'use strict';
|
||||
|
||||
var BaseNode = require('./base').Node;
|
||||
|
||||
exports.BaseNode = BaseNode;
|
||||
|
||||
var constructors = {
|
||||
local: require('./local').Node
|
||||
};
|
||||
|
||||
exports.register = function(type, constructor) {
|
||||
constructors[type] = constructor;
|
||||
};
|
||||
|
||||
exports.createNode = function(params) {
|
||||
if (params.type in constructors === false) {
|
||||
throw new Error('Unknown node type: ' + params.type);
|
||||
}
|
||||
|
||||
var Constructor = constructors[params.type];
|
||||
return new Constructor(params);
|
||||
};
|
20
lib/node/local.js
Normal file
20
lib/node/local.js
Normal file
@ -0,0 +1,20 @@
|
||||
'user strict';
|
||||
|
||||
var inherits = require('util').inherits,
|
||||
ParentNode = require('./base').Node,
|
||||
LocalExecutor = require('../executor/local').Executor;
|
||||
|
||||
function Node(params) {
|
||||
ParentNode.call(this, params);
|
||||
}
|
||||
|
||||
inherits(Node, ParentNode);
|
||||
|
||||
exports.Node = Node;
|
||||
|
||||
Node.prototype._createExecutor = function(project) {
|
||||
return new LocalExecutor({
|
||||
type: this.type,
|
||||
project: project
|
||||
});
|
||||
};
|
@ -1,22 +1,48 @@
|
||||
'use strict';
|
||||
|
||||
var ParentCommand = require('../command/spawn').Command,
|
||||
inherits = require('util').inherits;
|
||||
var EventEmitter = require('events').EventEmitter,
|
||||
inherits = require('util').inherits,
|
||||
_ = require('underscore');
|
||||
|
||||
function Scm(params) {
|
||||
ParentCommand.call(this, params);
|
||||
this.repository = params.repository;
|
||||
if (!this.repository && !this.cwd) throw new Error(
|
||||
'`repository` or `cwd` must be set'
|
||||
);
|
||||
this.collectOut = true;
|
||||
this.emitIn = true;
|
||||
this.attachStderr = true;
|
||||
var self = this;
|
||||
|
||||
EventEmitter.call(self);
|
||||
|
||||
self.repository = params.repository;
|
||||
self.cwd = params.cwd;
|
||||
|
||||
if (!self.repository && !self.cwd) {
|
||||
throw new Error('`repository` or `cwd` must be set');
|
||||
}
|
||||
|
||||
self.command = params.command;
|
||||
|
||||
if (!self.command) throw new Error('`command` is required');
|
||||
|
||||
self.command.setParams({
|
||||
collectOut: true,
|
||||
emitIn: true,
|
||||
attachStderr: true
|
||||
});
|
||||
|
||||
self.command.on('stdin', function(data) {
|
||||
self.emit('stdin', data);
|
||||
});
|
||||
}
|
||||
|
||||
exports.Scm = Scm;
|
||||
|
||||
inherits(Scm, ParentCommand);
|
||||
inherits(Scm, EventEmitter);
|
||||
|
||||
Scm.prototype._run = function(params, callback) {
|
||||
if (this.cwd) {
|
||||
params.options = params.options || {};
|
||||
params.options.cwd = this.cwd;
|
||||
}
|
||||
|
||||
this.command.run(params, callback);
|
||||
};
|
||||
|
||||
/**
|
||||
* Clone repository to the `dst` update to `rev` and set `this.cwd` to `dst`
|
||||
|
@ -60,14 +60,14 @@ Scm.prototype.clone = function(dst, rev, callback) {
|
||||
function() {
|
||||
// git can't clearly clone specified rev but can clone branch
|
||||
// possible solution to change clone params to (dst, branch, callback)
|
||||
self.run({
|
||||
self._run({
|
||||
cmd: 'git',
|
||||
args: ['clone', '--recursive', self.repository, dst]
|
||||
}, this.slot());
|
||||
self.cwd = dst;
|
||||
},
|
||||
function() {
|
||||
self.run({cmd: 'git', args: ['checkout', '-f', rev]}, this.slot());
|
||||
self._run({cmd: 'git', args: ['checkout', '-f', rev]}, this.slot());
|
||||
},
|
||||
callback
|
||||
);
|
||||
@ -82,7 +82,7 @@ Scm.prototype.pull = function(rev, callback) {
|
||||
},
|
||||
function(err, currentRev) {
|
||||
this.pass(currentRev);
|
||||
self.run({cmd: 'git', args: ['pull']}, this.slot());
|
||||
self._run({cmd: 'git', args: ['pull']}, this.slot());
|
||||
},
|
||||
function(err, currentRev) {
|
||||
self.update(currentRev.id, this.slot());
|
||||
@ -104,7 +104,7 @@ Scm.prototype.getRev = function(rev, callback) {
|
||||
var self = this;
|
||||
Steppy(
|
||||
function() {
|
||||
self.run({cmd: 'git', args: [
|
||||
self._run({cmd: 'git', args: [
|
||||
'show', rev,
|
||||
'--pretty=' + self._revTemplate
|
||||
]}, this.slot());
|
||||
@ -146,7 +146,7 @@ Scm.prototype.getChanges = function(rev1, rev2, callback) {
|
||||
function(err, currentRev) {
|
||||
this.pass(currentRev);
|
||||
|
||||
self.run({cmd: 'git', args: [
|
||||
self._run({cmd: 'git', args: [
|
||||
'log', rev1 ? rev1 + '..' + rev2 : rev2,
|
||||
'--pretty=' + self._revTemplate + self._linesSeparator
|
||||
]}, this.slot());
|
||||
@ -175,10 +175,10 @@ Scm.prototype.update = function(rev, callback) {
|
||||
var self = this;
|
||||
Steppy(
|
||||
function() {
|
||||
self.run({cmd: 'git', args: ['checkout', '-f', rev]}, this.slot());
|
||||
self._run({cmd: 'git', args: ['checkout', '-f', rev]}, this.slot());
|
||||
},
|
||||
function() {
|
||||
self.run({cmd: 'git', args: ['submodule', 'update']}, this.slot());
|
||||
self._run({cmd: 'git', args: ['submodule', 'update']}, this.slot());
|
||||
},
|
||||
callback
|
||||
);
|
||||
|
@ -1,6 +1,23 @@
|
||||
'use strict';
|
||||
|
||||
var BaseScm = require('./base').Scm;
|
||||
|
||||
exports.BaseScm = BaseScm;
|
||||
|
||||
var constructors = {
|
||||
git: require('./git').Scm,
|
||||
mercurial: require('./mercurial').Scm
|
||||
};
|
||||
|
||||
exports.register = function(type, constructor) {
|
||||
constructors[type] = constructor;
|
||||
};
|
||||
|
||||
exports.createScm = function(params) {
|
||||
var Constructor = require('./' + params.type).Scm;
|
||||
if (params.type in constructors === false) {
|
||||
throw new Error('Unknown scm type: ' + params.type);
|
||||
}
|
||||
|
||||
var Constructor = constructors[params.type];
|
||||
return new Constructor(params);
|
||||
};
|
||||
|
@ -55,7 +55,7 @@ Scm.prototype.clone = function(dst, rev, callback) {
|
||||
var self = this;
|
||||
Steppy(
|
||||
function() {
|
||||
self.run({
|
||||
self._run({
|
||||
cmd: 'hg',
|
||||
args: ['clone', '--rev', rev, self.repository, dst]
|
||||
}, this.slot());
|
||||
@ -66,14 +66,14 @@ Scm.prototype.clone = function(dst, rev, callback) {
|
||||
};
|
||||
|
||||
Scm.prototype.pull = function(rev, callback) {
|
||||
this.run({cmd: 'hg', args: ['pull', '--rev', rev]}, callback);
|
||||
this._run({cmd: 'hg', args: ['pull', '--rev', rev]}, callback);
|
||||
};
|
||||
|
||||
Scm.prototype.getCurrent = function(callback) {
|
||||
var self = this;
|
||||
Steppy(
|
||||
function() {
|
||||
self.run({cmd: 'hg', args: [
|
||||
self._run({cmd: 'hg', args: [
|
||||
'parent', '--template', self._revTemplate
|
||||
]}, this.slot());
|
||||
},
|
||||
@ -88,7 +88,7 @@ Scm.prototype.getChanges = function(rev1, rev2, callback) {
|
||||
var self = this;
|
||||
Steppy(
|
||||
function() {
|
||||
self.run({cmd: 'hg', args: [
|
||||
self._run({cmd: 'hg', args: [
|
||||
'log', '--rev', rev2 + ':' + rev1,
|
||||
'--template', self._revTemplate + self._linesSeparator
|
||||
]}, this.slot());
|
||||
@ -111,5 +111,5 @@ Scm.prototype.getChanges = function(rev1, rev2, callback) {
|
||||
};
|
||||
|
||||
Scm.prototype.update = function(rev, callback) {
|
||||
this.run({cmd: 'hg', args: ['up', '-C', rev]}, callback);
|
||||
this._run({cmd: 'hg', args: ['up', '-C', rev]}, callback);
|
||||
};
|
||||
|
@ -19,8 +19,11 @@ module.exports = function(config, callback) {
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
type: {type: 'string', enum: ['local']},
|
||||
maxExecutorsCount: {type: 'integer'}
|
||||
name: {type: 'string'},
|
||||
type: {type: 'string'},
|
||||
usageStrategy: {type: 'string'},
|
||||
maxExecutorsCount: {type: 'integer'},
|
||||
options: {type: 'object'}
|
||||
}
|
||||
},
|
||||
minItems: 1
|
||||
|
@ -93,7 +93,7 @@ describe('Distributor blocking with max 2 executors count', function() {
|
||||
var spy = updateBuildSpy;
|
||||
expect(spy.getCall(3).args[0].project.name).equal('project2');
|
||||
expect(spy.getCall(3).args[1].waitReason).equal(
|
||||
'Blocked by currently running "project1"'
|
||||
'local: blocked by currently running "project1"'
|
||||
);
|
||||
});
|
||||
|
||||
|
@ -2,16 +2,16 @@
|
||||
|
||||
var _ = require('underscore'),
|
||||
sinon = require('sinon'),
|
||||
Node = require('../../lib/node').Node,
|
||||
createNode = require('../../lib/node').createNode,
|
||||
EventEmitter = require('events').EventEmitter,
|
||||
ProjectsCollection = require('../../lib/project').ProjectsCollection,
|
||||
Distributor = require('../../lib/distributor').Distributor,
|
||||
Notifier = require('../../lib/notifier').Notifier;
|
||||
|
||||
|
||||
var createNode = function(executorRun) {
|
||||
var createMockedNode = function(executorRun) {
|
||||
return function(params) {
|
||||
var node = new Node(params);
|
||||
var node = createNode(params);
|
||||
node._createExecutor = function(project) {
|
||||
var executor = new EventEmitter();
|
||||
executor.project = project;
|
||||
@ -38,7 +38,7 @@ exports.createDistributor = function(params) {
|
||||
distributorParams.executorRun || sinon.stub().callsArgAsync(1)
|
||||
);
|
||||
// patch method which will be called at constructor
|
||||
sinon.stub(Distributor.prototype, '_createNode', createNode(
|
||||
sinon.stub(Distributor.prototype, '_createNode', createMockedNode(
|
||||
executorRun
|
||||
));
|
||||
delete distributorParams.executorRun;
|
||||
@ -52,6 +52,7 @@ exports.createDistributor = function(params) {
|
||||
});
|
||||
|
||||
var distributor = new Distributor(distributorParams);
|
||||
distributor.init();
|
||||
|
||||
if (mockNode) {
|
||||
Distributor.prototype._createNode.restore();
|
||||
|
115
test/node.js
115
test/node.js
@ -1,7 +1,8 @@
|
||||
'use strict';
|
||||
|
||||
var Node = require('../lib/node').Node,
|
||||
expect = require('expect.js');
|
||||
var createNode = require('../lib/node').createNode,
|
||||
expect = require('expect.js'),
|
||||
_ = require('underscore');
|
||||
|
||||
|
||||
describe('Node', function() {
|
||||
@ -9,6 +10,114 @@ describe('Node', function() {
|
||||
project1 = {name: 'project1'},
|
||||
project2 = {name: 'project2'};
|
||||
|
||||
var createNodeMock = function(params) {
|
||||
params = params || {};
|
||||
|
||||
var node = createNode(_({
|
||||
name: 'executor1',
|
||||
type: 'local',
|
||||
maxExecutorsCount: 1,
|
||||
usageStrategy: 'maximum'
|
||||
}).extend(params));
|
||||
|
||||
// only for testing
|
||||
if (params.executors) {
|
||||
node.executors = params.executors;
|
||||
}
|
||||
return node;
|
||||
};
|
||||
|
||||
describe('wait reason', function() {
|
||||
|
||||
it('should be not a target node when node target is not match', function() {
|
||||
var waitReason = createNodeMock({
|
||||
name: 'executor1'
|
||||
}).getExecutorWaitReason({
|
||||
name: 'project1',
|
||||
node: {target: 'other executor'}
|
||||
});
|
||||
expect(waitReason).eql('executor1: not a target node');
|
||||
});
|
||||
|
||||
it('should be falsy when node target match', function() {
|
||||
var waitReason = createNodeMock({
|
||||
name: 'executor1'
|
||||
}).getExecutorWaitReason({
|
||||
name: 'project1',
|
||||
node: {target: 'executor1'}
|
||||
});
|
||||
expect(waitReason).not.ok();
|
||||
});
|
||||
|
||||
it('should be falsy when node target (array) match', function() {
|
||||
var waitReason = createNodeMock({
|
||||
name: 'executor1'
|
||||
}).getExecutorWaitReason({
|
||||
name: 'project1',
|
||||
node: {target: ['executor1']}
|
||||
});
|
||||
expect(waitReason).not.ok();
|
||||
});
|
||||
|
||||
it('should be only for specific projects when target is not set', function() {
|
||||
var waitReason = createNodeMock({
|
||||
usageStrategy: 'specificProject'
|
||||
}).getExecutorWaitReason({
|
||||
name: 'project1'
|
||||
});
|
||||
expect(waitReason).eql('executor1: only for specific projects');
|
||||
});
|
||||
|
||||
it('should be all executors are busy when true', function() {
|
||||
var waitReason = createNodeMock({
|
||||
maxExecutorsCount: 1,
|
||||
executors: {project2: 1}
|
||||
}).getExecutorWaitReason({
|
||||
name: 'project1'
|
||||
});
|
||||
expect(waitReason).eql('executor1: all executors are busy');
|
||||
});
|
||||
|
||||
it('should be project already running on node when true', function() {
|
||||
var waitReason = createNodeMock({
|
||||
maxExecutorsCount: 2,
|
||||
executors: {project1: 1}
|
||||
}).getExecutorWaitReason({
|
||||
name: 'project1'
|
||||
});
|
||||
expect(waitReason).eql('executor1: project already running on node');
|
||||
});
|
||||
|
||||
it('should be blocked by project when blocked by executing', function() {
|
||||
var waitReason = createNodeMock({
|
||||
maxExecutorsCount: 2,
|
||||
executors: {project2: {project: {name: 'project2'}}}
|
||||
}).getExecutorWaitReason({
|
||||
name: 'project1',
|
||||
blockedBy: ['project2']
|
||||
});
|
||||
expect(waitReason).eql(
|
||||
'executor1: blocked by currently running "project2"'
|
||||
);
|
||||
});
|
||||
|
||||
it('should be blocked by project when executing blocks it', function() {
|
||||
var waitReason = createNodeMock({
|
||||
maxExecutorsCount: 2,
|
||||
executors: {project2: {project: {
|
||||
name: 'project2',
|
||||
blocks: ['project1']
|
||||
}}}
|
||||
}).getExecutorWaitReason({
|
||||
name: 'project1'
|
||||
});
|
||||
expect(waitReason).eql(
|
||||
'executor1: blocked by currently running "project2"'
|
||||
);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
var expectNodeHasFreeExecutor = function(project, value) {
|
||||
it('should' + (value ? ' ' : ' not ') + 'has free executors for ' +
|
||||
project.name, function() {
|
||||
@ -19,7 +128,7 @@ describe('Node', function() {
|
||||
|
||||
describe('basic', function() {
|
||||
it('instance should be created without errors', function() {
|
||||
node = new Node({
|
||||
node = createNode({
|
||||
type: 'local',
|
||||
maxExecutorsCount: 1
|
||||
});
|
||||
|
@ -42,7 +42,8 @@ var expect = require('expect.js'),
|
||||
it('create scm instance attached to new repository without errors', function() {
|
||||
scm = createScm({
|
||||
type: type,
|
||||
repository: originalRepositoryPath
|
||||
repository: originalRepositoryPath,
|
||||
command: new SpawnCommand()
|
||||
});
|
||||
});
|
||||
|
||||
@ -129,7 +130,11 @@ var expect = require('expect.js'),
|
||||
});
|
||||
|
||||
it('create scm instance attached to existing `cwd` without errors', function() {
|
||||
scm = createScm({type: type, cwd: repositoryPath});
|
||||
scm = createScm({
|
||||
type: type,
|
||||
cwd: repositoryPath,
|
||||
command: new SpawnCommand()
|
||||
});
|
||||
});
|
||||
|
||||
it('expect repository log from rev0 to default revision equals to ' +
|
||||
|
Loading…
Reference in New Issue
Block a user