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 typeof(this._buildNumber) != 'number'; }
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() || request.isScheduled())
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 scheduleRequestInGroupIfAvailable(newRequest, slaveName)
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);
128 if (!usedSlaves.has(slaveName) && !this._slavesWithNewRequests.has(slaveName))
129 return this.scheduleRequest(newRequest, slaveName);
133 for (let slaveName of this._slaveList) {
134 if (!usedSlaves.has(slaveName) && !this._slavesWithNewRequests.has(slaveName))
135 return this.scheduleRequest(newRequest, slaveName);
144 return this._remote.getJSON(this.pathForPendingBuildsJSON()).then(function (content) {
145 let pendingEntries = content.map(function (entry) { return new BuildbotBuildEntry(self, entry); });
146 return self._pullRecentBuilds(count).then(function (entries) {
147 let entryByRequest = {};
149 for (let entry of pendingEntries)
150 entryByRequest[entry.buildRequestId()] = entry;
152 for (let entry of entries)
153 entryByRequest[entry.buildRequestId()] = entry;
156 for (let id in entryByRequest)
157 entryList.push(entryByRequest[id]);
159 self._entryList = entryList;
160 self._slavesWithNewRequests.clear();
167 _pullRecentBuilds(count)
170 return Promise.resolve([]);
172 let selectedBuilds = new Array(count);
173 for (let i = 0; i < count; i++)
174 selectedBuilds[i] = -i - 1;
177 return this._remote.getJSON(this.pathForBuildJSON(selectedBuilds)).then(function (content) {
179 for (let index of selectedBuilds) {
180 let entry = content[index];
181 if (entry && !entry['error'])
182 entries.push(new BuildbotBuildEntry(self, entry));
188 pathForPendingBuildsJSON() { return `/json/builders/${escape(this._builderName)}/pendingBuilds`; }
189 pathForBuildJSON(selectedBuilds)
191 return `/json/builders/${escape(this._builderName)}/builds/?`
192 + selectedBuilds.map(function (number) { return 'select=' + number; }).join('&');
194 pathForForceBuild() { return `/builders/${escape(this._builderName)}/force`; }
196 url() { return this._remote.url(`/builders/${escape(this._builderName)}/`); }
197 urlForBuildNumber(number) { return this._remote.url(`/builders/${escape(this._builderName)}/builds/${number}`); }
199 _propertiesForBuildRequest(buildRequest)
201 assert(buildRequest instanceof BuildRequest);
203 let rootSet = buildRequest.rootSet();
204 assert(rootSet instanceof RootSet);
206 let repositoryByName = {};
207 for (let repository of rootSet.repositories())
208 repositoryByName[repository.name()] = repository;
210 let propertiesTemplate = null;
211 for (let config of this._testConfigurations) {
212 if (config.platform == buildRequest.platform() && config.test == buildRequest.test())
213 propertiesTemplate = config.propertiesTemplate;
215 assert(propertiesTemplate);
218 for (let key in propertiesTemplate) {
219 let value = propertiesTemplate[key];
220 if (typeof(value) != 'object')
221 properties[key] = value;
222 else if ('root' in value) {
223 let repositoryName = value['root'];
224 let repository = repositoryByName[repositoryName];
225 assert(repository, '"${repositoryName}" must be specified');
226 properties[key] = rootSet.revisionForRepository(repository);
227 } else if ('rootsExcluding' in value) {
228 let revisionSet = this._revisionSetFromRootSetWithExclusionList(rootSet, value['rootsExcluding']);
229 properties[key] = JSON.stringify(revisionSet);
233 properties[this._buildRequestPropertyName] = buildRequest.id();
238 _revisionSetFromRootSetWithExclusionList(rootSet, exclusionList)
240 let revisionSet = {};
241 for (let repository of rootSet.repositories()) {
242 if (exclusionList.indexOf(repository.name()) >= 0)
244 let commit = rootSet.commitForRepository(repository);
245 revisionSet[repository.name()] = {
247 time: +commit.time(),
248 repository: repository.name(),
249 revision: commit.revision(),
255 static _loadConfig(remote, config)
257 let shared = config['shared'] || {};
258 let types = config['types'] || {};
259 let builders = config['builders'] || {};
261 let syncerByBuilder = new Map;
262 for (let entry of config['configurations']) {
264 this._validateAndMergeConfig(newConfig, shared);
266 this._validateAndMergeConfig(newConfig, entry);
268 let type = entry['type'];
271 this._validateAndMergeConfig(newConfig, types[type]);
274 let builder = entry['builder'];
275 if (builders[builder])
276 this._validateAndMergeConfig(newConfig, builders[builder]);
278 assert('platform' in newConfig, 'configuration must specify a platform');
279 assert('test' in newConfig, 'configuration must specify a test');
280 assert('builder' in newConfig, 'configuration must specify a builder');
281 assert('properties' in newConfig, 'configuration must specify arguments to post on a builder');
282 assert('buildRequestArgument' in newConfig, 'configuration must specify buildRequestArgument');
284 let test = Test.findByPath(newConfig.test);
285 assert(test, `${newConfig.test} is not a valid test path`);
287 let platform = Platform.findByName(newConfig.platform);
288 assert(platform, `${newConfig.platform} is not a valid platform name`);
290 let syncer = syncerByBuilder.get(newConfig.builder);
292 syncer = new BuildbotSyncer(remote, newConfig);
293 syncerByBuilder.set(newConfig.builder, syncer);
295 syncer.addTestConfiguration(test, platform, newConfig.properties);
298 return Array.from(syncerByBuilder.values());
301 static _validateAndMergeConfig(config, valuesToMerge)
303 for (let name in valuesToMerge) {
304 let value = valuesToMerge[name];
307 assert.equal(typeof(value), 'object', 'arguments should be a dictionary');
308 if (!config['properties'])
309 config['properties'] = {};
310 this._validateAndMergeProperties(config['properties'], value);
313 assert(value instanceof Array, 'test should be an array');
314 assert(value.every(function (part) { return typeof part == 'string'; }), 'test should be an array of strings');
315 config[name] = value.slice();
318 assert(value instanceof Array, 'slaveList should be an array');
319 assert(value.every(function (part) { return typeof part == 'string'; }), 'slaveList should be an array of strings');
320 config[name] = value;
322 case 'type': // fallthrough
323 case 'builder': // fallthrough
324 case 'platform': // fallthrough
325 case 'slaveArgument': // fallthrough
326 case 'buildRequestArgument':
327 assert.equal(typeof(value), 'string', `${name} should be of string type`);
328 config[name] = value;
331 assert(false, `Unrecognized parameter ${name}`);
336 static _validateAndMergeProperties(properties, configArguments)
338 for (let name in configArguments) {
339 let value = configArguments[name];
340 if (typeof(value) == 'string') {
341 properties[name] = value;
344 assert.equal(typeof(value), 'object', 'A argument value must be either a string or a dictionary');
346 let keys = Object.keys(value);
347 assert.equal(keys.length, 1, 'arguments value cannot contain more than one key');
348 let namedValue = value[keys[0]];
351 assert.equal(typeof(namedValue), 'string', 'root name must be a string');
353 case 'rootsExcluding':
354 assert(namedValue instanceof Array, 'rootsExcluding must specify an array');
355 for (let excludedRootName of namedValue)
356 assert.equal(typeof(excludedRootName), 'string', 'rootsExcluding must specify an array of strings');
357 namedValue = namedValue.slice();
360 assert(false, `Unrecognized named argument ${keys[0]}`);
362 properties[name] = {[keys[0]]: namedValue};
368 if (typeof module != 'undefined') {
369 module.exports.BuildbotSyncer = BuildbotSyncer;
370 module.exports.BuildbotBuildEntry = BuildbotBuildEntry;