Merge pull request #10 from node-ci/feature_expose_executor

Feature expose executor
This commit is contained in:
Oleg Korobenko 2016-03-16 21:38:53 +03:00
commit 84c6b8ee05
21 changed files with 459 additions and 221 deletions

12
app.js
View File

@ -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() {

View File

@ -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,

View File

@ -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;
};

View File

@ -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);

View File

@ -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`
*/

View File

@ -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});
}

View File

@ -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) {

View File

@ -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);

View File

@ -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
);
};

View File

@ -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
View 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
View 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
});
};

View File

@ -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`

View File

@ -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
);

View File

@ -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);
};

View File

@ -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);
};

View File

@ -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

View File

@ -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"'
);
});

View File

@ -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();

View File

@ -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
});

View File

@ -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 ' +