3 let assert = require('assert');
5 require('./v3-models.js');
7 class BuildbotBuildEntry {
8 constructor(syncer, rawData)
10 assert.equal(syncer.builderName(), rawData['builderName']);
12 this._syncer = syncer;
13 this._slaveName = null;
14 this._buildRequestId = null;
15 this._isInProgress = rawData['currentStep'] || (rawData['times'] && !rawData['times'][1]);
16 this._buildNumber = rawData['number'];
18 for (let propertyTuple of (rawData['properties'] || [])) {
19 // e.g. ['build_request_id', '16733', 'Force Build Form']
20 let name = propertyTuple[0];
21 let value = propertyTuple[1];
22 if (name == syncer._slavePropertyName)
23 this._slaveName = value;
24 else if (name == syncer._buildRequestPropertyName)
25 this._buildRequestId = value;
29 syncer() { return this._syncer; }
30 buildNumber() { return this._buildNumber; }
31 slaveName() { return this._slaveName; }
32 buildRequestId() { return this._buildRequestId; }
33 isPending() { return !this._buildNumber; }
34 isInProgress() { return this._isInProgress; }
35 hasFinished() { return !this.isPending() && !this.isInProgress(); }
36 url() { return this.isPending() ? this._syncer.url() : this._syncer.urlForBuildNumber(this._buildNumber); }
38 buildRequestStatusIfUpdateIsNeeded(request)
40 assert.equal(request.id(), this._buildRequestId);
43 if (this.isPending()) {
44 if (request.isPending())
46 } else if (this.isInProgress()) {
47 if (!request.hasStarted())
49 } else if (this.hasFinished()) {
50 if (!request.hasFinished())
51 return 'failedIfNotCompleted';
58 class BuildbotSyncer {
60 constructor(remote, object)
62 this._remote = remote;
63 this._testConfigurations = [];
64 this._builderName = object.builder;
65 this._slavePropertyName = object.slaveArgument;
66 this._slaveList = object.slaveList;
67 this._buildRequestPropertyName = object.buildRequestArgument;
68 this._entryList = null;
69 this._slavesWithNewRequests = new Set;
72 builderName() { return this._builderName; }
74 addTestConfiguration(test, platform, propertiesTemplate)
76 assert(test instanceof Test);
77 assert(platform instanceof Platform);
78 this._testConfigurations.push({test: test, platform: platform, propertiesTemplate: propertiesTemplate});
80 testConfigurations() { return this._testConfigurations; }
82 matchesConfiguration(request)
84 for (let config of this._testConfigurations) {
85 if (config.platform == request.platform() && config.test == request.test())
91 scheduleRequest(newRequest, slaveName)
93 assert(!this._slavesWithNewRequests.has(slaveName));
94 let properties = this._propertiesForBuildRequest(newRequest);
96 assert.equal(!this._slavePropertyName, !slaveName);
97 if (this._slavePropertyName)
98 properties[this._slavePropertyName] = slaveName;
100 this._slavesWithNewRequests.add(slaveName);
101 return this._remote.postFormUrlencodedData(this.pathForForceBuild(), properties);
104 scheduleFirstRequestInGroupIfAvailable(newRequest)
106 assert(newRequest instanceof BuildRequest);
108 if (!this.matchesConfiguration(newRequest))
111 let hasPendingBuildsWithoutSlaveNameSpecified = false;
112 let usedSlaves = new Set;
113 for (let entry of this._entryList) {
114 if (entry.isPending()) {
115 if (!entry.slaveName())
116 hasPendingBuildsWithoutSlaveNameSpecified = true;
117 usedSlaves.add(entry.slaveName());
121 if (!this._slaveList || hasPendingBuildsWithoutSlaveNameSpecified) {
122 if (usedSlaves.size || this._slavesWithNewRequests.size)
124 return this.scheduleRequest(newRequest, null);
127 for (let slaveName of this._slaveList) {
128 if (!usedSlaves.has(slaveName) && !this._slavesWithNewRequests.has(slaveName))
129 return this.scheduleRequest(newRequest, slaveName);
138 return this._remote.getJSON(this.pathForPendingBuildsJSON()).then(function (content) {
139 let pendingEntries = content.map(function (entry) { return new BuildbotBuildEntry(self, entry); });
140 return self._pullRecentBuilds(count).then(function (entries) {
141 let entryByRequest = {};
143 for (let entry of pendingEntries)
144 entryByRequest[entry.buildRequestId()] = entry;
146 for (let entry of entries)
147 entryByRequest[entry.buildRequestId()] = entry;
150 for (let id in entryByRequest)
151 entryList.push(entryByRequest[id]);
153 self._entryList = entryList;
154 self._slavesWithNewRequests.clear();
161 _pullRecentBuilds(count)
164 return Promise.resolve([]);
166 let selectedBuilds = new Array(count);
167 for (let i = 0; i < count; i++)
168 selectedBuilds[i] = -i - 1;
171 return this._remote.getJSON(this.pathForBuildJSON(selectedBuilds)).then(function (content) {
173 for (let index of selectedBuilds) {
174 let entry = content[index];
175 if (entry && !entry['error'])
176 entries.push(new BuildbotBuildEntry(self, entry));
182 pathForPendingBuildsJSON() { return `/json/builders/${this._builderName}/pendingBuilds`; }
183 pathForBuildJSON(selectedBuilds)
185 return `/json/builders/${this._builderName}/builds/?`
186 + selectedBuilds.map(function (number) { return 'select=' + number; }).join('&');
188 pathForForceBuild() { return `/builders/${this._builderName}/force`; }
190 url() { return this._remote.url(`/builders/${this._builderName}/`); }
191 urlForBuildNumber(number) { return this._remote.url(`/builders/${this._builderName}/builds/${number}`); }
193 _propertiesForBuildRequest(buildRequest)
195 assert(buildRequest instanceof BuildRequest);
197 let rootSet = buildRequest.rootSet();
198 assert(rootSet instanceof RootSet);
200 let repositoryByName = {};
201 for (let repository of rootSet.repositories())
202 repositoryByName[repository.name()] = repository;
204 let propertiesTemplate = null;
205 for (let config of this._testConfigurations) {
206 if (config.platform == buildRequest.platform() && config.test == buildRequest.test())
207 propertiesTemplate = config.propertiesTemplate;
209 assert(propertiesTemplate);
212 for (let key in propertiesTemplate) {
213 let value = propertiesTemplate[key];
214 if (typeof(value) != 'object')
215 properties[key] = value;
216 else if ('root' in value) {
217 let repositoryName = value['root'];
218 let repository = repositoryByName[repositoryName];
219 assert(repository, '"${repositoryName}" must be specified');
220 properties[key] = rootSet.revisionForRepository(repository);
221 } else if ('rootsExcluding' in value) {
222 let revisionSet = this._revisionSetFromRootSetWithExclusionList(rootSet, value['rootsExcluding']);
223 properties[key] = JSON.stringify(revisionSet);
227 properties[this._buildRequestPropertyName] = buildRequest.id();
232 _revisionSetFromRootSetWithExclusionList(rootSet, exclusionList)
234 let revisionSet = {};
235 for (let repository of rootSet.repositories()) {
236 if (exclusionList.indexOf(repository.name()) >= 0)
238 let commit = rootSet.commitForRepository(repository);
239 revisionSet[repository.name()] = {
241 time: +commit.time(),
242 repository: repository.name(),
243 revision: commit.revision(),
249 static _loadConfig(remote, config)
251 let shared = config['shared'] || {};
252 let types = config['types'] || {};
253 let builders = config['builders'] || {};
255 let syncerByBuilder = new Map;
256 for (let entry of config['configurations']) {
258 this._validateAndMergeConfig(newConfig, shared);
260 this._validateAndMergeConfig(newConfig, entry);
262 let type = entry['type'];
265 this._validateAndMergeConfig(newConfig, types[type]);
268 let builder = entry['builder'];
269 if (builders[builder])
270 this._validateAndMergeConfig(newConfig, builders[builder]);
272 assert('platform' in newConfig, 'configuration must specify a platform');
273 assert('test' in newConfig, 'configuration must specify a test');
274 assert('builder' in newConfig, 'configuration must specify a builder');
275 assert('properties' in newConfig, 'configuration must specify arguments to post on a builder');
276 assert('buildRequestArgument' in newConfig, 'configuration must specify buildRequestArgument');
278 let test = Test.findByPath(newConfig.test);
279 assert(test, `${newConfig.test} is not a valid test path`);
281 let platform = Platform.findByName(newConfig.platform);
282 assert(platform, `${newConfig.platform} is not a valid platform name`);
284 let syncer = syncerByBuilder.get(newConfig.builder);
286 syncer = new BuildbotSyncer(remote, newConfig);
287 syncerByBuilder.set(newConfig.builder, syncer);
289 syncer.addTestConfiguration(test, platform, newConfig.properties);
292 return Array.from(syncerByBuilder.values());
295 static _validateAndMergeConfig(config, valuesToMerge)
297 for (let name in valuesToMerge) {
298 let value = valuesToMerge[name];
301 assert.equal(typeof(value), 'object', 'arguments should be a dictionary');
302 if (!config['properties'])
303 config['properties'] = {};
304 this._validateAndMergeProperties(config['properties'], value);
307 assert(value instanceof Array, 'test should be an array');
308 assert(value.every(function (part) { return typeof part == 'string'; }), 'test should be an array of strings');
309 config[name] = value.slice();
312 assert(value instanceof Array, 'slaveList should be an array');
313 assert(value.every(function (part) { return typeof part == 'string'; }), 'slaveList should be an array of strings');
314 config[name] = value;
316 case 'type': // fallthrough
317 case 'builder': // fallthrough
318 case 'platform': // fallthrough
319 case 'slaveArgument': // fallthrough
320 case 'buildRequestArgument':
321 assert.equal(typeof(value), 'string', `${name} should be of string type`);
322 config[name] = value;
325 assert(false, `Unrecognized parameter ${name}`);
330 static _validateAndMergeProperties(properties, configArguments)
332 for (let name in configArguments) {
333 let value = configArguments[name];
334 if (typeof(value) == 'string') {
335 properties[name] = value;
338 assert.equal(typeof(value), 'object', 'A argument value must be either a string or a dictionary');
340 let keys = Object.keys(value);
341 assert.equal(keys.length, 1, 'arguments value cannot contain more than one key');
342 let namedValue = value[keys[0]];
345 assert.equal(typeof(namedValue), 'string', 'root name must be a string');
347 case 'rootsExcluding':
348 assert(namedValue instanceof Array, 'rootsExcluding must specify an array');
349 for (let excludedRootName of namedValue)
350 assert.equal(typeof(excludedRootName), 'string', 'rootsExcluding must specify an array of strings');
351 namedValue = namedValue.slice();
354 assert(false, `Unrecognized named argument ${keys[0]}`);
356 properties[name] = {[keys[0]]: namedValue};
362 if (typeof module != 'undefined') {
363 module.exports.BuildbotSyncer = BuildbotSyncer;
364 module.exports.BuildbotBuildEntry = BuildbotBuildEntry;