| 'use strict'; |
| |
| let assert = require('assert'); |
| |
| require('./v3-models.js'); |
| |
| class BuildbotBuildEntry { |
| constructor(syncer, rawData) |
| { |
| this.initialize(syncer, rawData); |
| } |
| |
| initialize(syncer, rawData) |
| { |
| assert.equal(syncer.builderID(), rawData['builderid']); |
| |
| this._syncer = syncer; |
| this._buildbotBuildRequestId = rawData['buildrequestid']; |
| this._hasFinished = rawData['complete']; |
| this._isPending = 'claimed' in rawData && !rawData['claimed']; |
| this._isInProgress = !this._isPending && !this._hasFinished; |
| this._buildNumber = rawData['number']; |
| this._workerName = rawData['properties'] && rawData['properties']['workername'] ? rawData['properties']['workername'][0] : null; |
| this._buildRequestId = rawData['properties'] && rawData['properties'][syncer._buildRequestPropertyName] |
| ? rawData['properties'][syncer._buildRequestPropertyName][0] : null; |
| this._statusDescription = rawData['state_string'] && rawData['results'] !== 0 ? rawData['state_string'] : null; |
| } |
| |
| syncer() { return this._syncer; } |
| buildNumber() { return this._buildNumber; } |
| slaveName() { return this._workerName; } |
| workerName() { return this._workerName; } |
| buildRequestId() { return this._buildRequestId; } |
| isPending() { return this._isPending; } |
| isInProgress() { return this._isInProgress; } |
| hasFinished() { return this._hasFinished; } |
| statusDescription() { return this._statusDescription; } |
| url() { return this.isPending() ? this._syncer.urlForPendingBuild(this._buildbotBuildRequestId) : this._syncer.urlForBuildNumber(this._buildNumber); } |
| |
| buildRequestStatusIfUpdateIsNeeded(request) |
| { |
| assert.equal(request.id(), this._buildRequestId); |
| if (!request) |
| return null; |
| if (this.isPending()) { |
| if (request.isPending()) |
| return 'scheduled'; |
| } else if (this.isInProgress()) { |
| if (!request.hasStarted() || request.isScheduled()) |
| return 'running'; |
| } else if (this.hasFinished()) { |
| if (!request.hasFinished()) |
| return 'failedIfNotCompleted'; |
| } |
| return null; |
| } |
| } |
| |
| |
| class BuildbotSyncer { |
| |
| constructor(remote, object, commonConfigurations) |
| { |
| this._remote = remote; |
| this._type = null; |
| this._configurations = []; |
| this._repositoryGroups = commonConfigurations.repositoryGroups; |
| this._slavePropertyName = commonConfigurations.slaveArgument; |
| this._platformPropertyName = commonConfigurations.platformArgument; |
| this._buildRequestPropertyName = commonConfigurations.buildRequestArgument; |
| this._builderName = object.builder; |
| this._builderID = object.builderID; |
| this._slaveList = object.slaveList; |
| this._entryList = null; |
| this._slavesWithNewRequests = new Set; |
| } |
| |
| builderName() { return this._builderName; } |
| builderID() { return this._builderID; } |
| |
| addTestConfiguration(test, platform, propertiesTemplate) |
| { |
| assert(test instanceof Test); |
| assert(platform instanceof Platform); |
| assert(this._type == null || this._type == 'tester'); |
| this._type = 'tester'; |
| this._configurations.push({test, platform, propertiesTemplate}); |
| } |
| testConfigurations() { return this._type == 'tester' ? this._configurations : []; } |
| |
| addBuildConfiguration(platform, propertiesTemplate) |
| { |
| assert(platform instanceof Platform); |
| assert(this._type == null || this._type == 'builder'); |
| this._type = 'builder'; |
| this._configurations.push({test: null, platform, propertiesTemplate}); |
| } |
| buildConfigurations() { return this._type == 'builder' ? this._configurations : []; } |
| |
| isTester() { return this._type == 'tester'; } |
| |
| repositoryGroups() { return this._repositoryGroups; } |
| |
| matchesConfiguration(request) |
| { |
| return this._configurations.some((config) => config.platform == request.platform() && config.test == request.test()); |
| } |
| |
| scheduleRequest(newRequest, requestsInGroup, slaveName) |
| { |
| assert(!this._slavesWithNewRequests.has(slaveName)); |
| let properties = this._propertiesForBuildRequest(newRequest, requestsInGroup); |
| |
| assert(properties['forcescheduler'], `forcescheduler was not specified in buildbot properties for build request ${newRequest.id()} on platform "${newRequest.platform().name()}" for builder "${this.builderName()}"`); |
| assert.equal(!this._slavePropertyName, !slaveName); |
| if (this._slavePropertyName) |
| properties[this._slavePropertyName] = slaveName; |
| |
| if (this._platformPropertyName) |
| properties[this._platformPropertyName] = newRequest.platform().name(); |
| |
| this._slavesWithNewRequests.add(slaveName); |
| return this.scheduleBuildOnBuildbot(properties); |
| } |
| |
| scheduleBuildOnBuildbot(properties) |
| { |
| const data = {jsonrpc: '2.0', method: 'force', id: properties[this._buildRequestPropertyName], params: properties}; |
| const path = this.pathForForceBuild(properties['forcescheduler']); |
| return this._remote.postJSON(path, data); |
| } |
| |
| scheduleRequestInGroupIfAvailable(newRequest, requestsInGroup, slaveName) |
| { |
| assert(newRequest instanceof BuildRequest); |
| |
| if (!this.matchesConfiguration(newRequest)) |
| return null; |
| |
| let hasPendingBuildsWithoutSlaveNameSpecified = false; |
| let usedSlaves = new Set; |
| for (let entry of this._entryList) { |
| let entryPreventsNewRequest = entry.isPending(); |
| if (entry.isInProgress()) { |
| const requestInProgress = BuildRequest.findById(entry.buildRequestId()); |
| if (!requestInProgress || requestInProgress.testGroupId() != newRequest.testGroupId()) |
| entryPreventsNewRequest = true; |
| } |
| if (entryPreventsNewRequest) { |
| if (!entry.slaveName()) |
| hasPendingBuildsWithoutSlaveNameSpecified = true; |
| usedSlaves.add(entry.slaveName()); |
| } |
| } |
| |
| if (!this._slaveList || hasPendingBuildsWithoutSlaveNameSpecified) { |
| if (usedSlaves.size || this._slavesWithNewRequests.size) |
| return null; |
| return this.scheduleRequest(newRequest, requestsInGroup, null); |
| } |
| |
| if (slaveName) { |
| if (!usedSlaves.has(slaveName) && !this._slavesWithNewRequests.has(slaveName)) |
| return this.scheduleRequest(newRequest, requestsInGroup, slaveName); |
| return null; |
| } |
| |
| for (let slaveName of this._slaveList) { |
| if (!usedSlaves.has(slaveName) && !this._slavesWithNewRequests.has(slaveName)) |
| return this.scheduleRequest(newRequest, requestsInGroup, slaveName); |
| } |
| |
| return null; |
| } |
| |
| pullBuildbot(count) |
| { |
| return this._remote.getJSON(this.pathForPendingBuilds()).then((content) => { |
| const pendingEntries = (content.buildrequests || []).map((entry) => new BuildbotBuildEntry(this, entry)); |
| return this._pullRecentBuilds(count).then((entries) => { |
| let entryByRequest = {}; |
| |
| for (let entry of pendingEntries) |
| entryByRequest[entry.buildRequestId()] = entry; |
| |
| for (let entry of entries) |
| entryByRequest[entry.buildRequestId()] = entry; |
| |
| let entryList = []; |
| for (let id in entryByRequest) |
| entryList.push(entryByRequest[id]); |
| |
| this._entryList = entryList; |
| this._slavesWithNewRequests.clear(); |
| |
| return entryList; |
| }); |
| }); |
| } |
| |
| _pullRecentBuilds(count) |
| { |
| if (!count) |
| return Promise.resolve([]); |
| |
| return this._remote.getJSON(this.pathForRecentBuilds(count)).then((content) => { |
| if (!('builds' in content)) |
| return []; |
| return content.builds.map((build) => new BuildbotBuildEntry(this, build)); |
| }); |
| } |
| |
| pathForPendingBuilds() { return `/api/v2/builders/${this._builderID}/buildrequests?complete=false&claimed=false&property=*`; } |
| pathForRecentBuilds(count) { return `/api/v2/builders/${this._builderID}/builds?limit=${count}&order=-number&property=*`; } |
| pathForForceBuild(schedulerName) { return `/api/v2/forceschedulers/${schedulerName}`; } |
| |
| urlForBuildNumber(number) { return this._remote.url(`/#/builders/${this._builderID}/builds/${number}`); } |
| urlForPendingBuild(buildRequestId) { return this._remote.url(`/#/buildrequests/${buildRequestId}`); } |
| |
| _propertiesForBuildRequest(buildRequest, requestsInGroup) |
| { |
| assert(buildRequest instanceof BuildRequest); |
| assert(requestsInGroup[0] instanceof BuildRequest); |
| |
| const commitSet = buildRequest.commitSet(); |
| assert(commitSet instanceof CommitSet); |
| |
| const repositoryByName = {}; |
| for (let repository of commitSet.repositories()) |
| repositoryByName[repository.name()] = repository; |
| |
| const matchingConfiguration = this._configurations.find((config) => config.platform == buildRequest.platform() && config.test == buildRequest.test()); |
| assert(matchingConfiguration, `Build request ${buildRequest.id()} does not match a configuration in the builder "${this._builderName}"`); |
| const propertiesTemplate = matchingConfiguration.propertiesTemplate; |
| |
| const repositoryGroup = buildRequest.repositoryGroup(); |
| assert(repositoryGroup.accepts(commitSet), `Build request ${buildRequest.id()} does not specify a commit set accepted by the repository group ${repositoryGroup.id()}`); |
| |
| const repositoryGroupConfiguration = this._repositoryGroups[repositoryGroup.name()]; |
| assert(repositoryGroupConfiguration, `Build request ${buildRequest.id()} uses an unsupported repository group "${repositoryGroup.name()}"`); |
| |
| const properties = {}; |
| for (let propertyName in propertiesTemplate) |
| properties[propertyName] = propertiesTemplate[propertyName]; |
| |
| const repositoryGroupTemplate = buildRequest.isBuild() ? repositoryGroupConfiguration.buildPropertiesTemplate : repositoryGroupConfiguration.testPropertiesTemplate; |
| assert(!buildRequest.isBuild() || repositoryGroupTemplate, 'Repository group template cannot be null for build type build request'); |
| for (let propertyName in repositoryGroupTemplate) { |
| let value = repositoryGroupTemplate[propertyName]; |
| const type = typeof(value) == 'object' ? value.type : 'string'; |
| switch (type) { |
| case 'string': |
| break; |
| case 'revision': |
| value = commitSet.revisionForRepository(value.repository); |
| break; |
| case 'patch': |
| const patch = commitSet.patchForRepository(value.repository); |
| if (!patch) |
| continue; |
| value = patch.url(); |
| break; |
| case 'roots': |
| const rootFiles = commitSet.allRootFiles(); |
| if (!rootFiles.length) |
| continue; |
| value = JSON.stringify(rootFiles.map((file) => ({url: file.url()}))); |
| break; |
| case 'ownedRevisions': |
| const ownedRepositories = commitSet.ownedRepositoriesForOwnerRepository(value.ownerRepository); |
| if (!ownedRepositories) |
| continue; |
| |
| const revisionInfo = {}; |
| revisionInfo[value.ownerRepository.name()] = ownedRepositories.map((ownedRepository) => { |
| return { |
| 'revision': commitSet.revisionForRepository(ownedRepository), |
| 'repository': ownedRepository.name(), |
| 'ownerRevision': commitSet.ownerRevisionForRepository(ownedRepository) |
| }; |
| }); |
| value = JSON.stringify(revisionInfo); |
| break; |
| case 'conditional': |
| switch (value.condition) { |
| case 'built': |
| const repositoryRequirement = value.repositoryRequirement; |
| const meetRepositoryRequirement = !repositoryRequirement.length || repositoryRequirement.some((repository) => commitSet.requiresBuildForRepository(repository)); |
| if (!meetRepositoryRequirement || !requestsInGroup.some((otherRequest) => otherRequest.isBuild() && otherRequest.commitSet() == buildRequest.commitSet())) |
| continue; |
| break; |
| case 'requiresBuild': |
| const requiresBuild = value.repositoriesToCheck.some((repository) => commitSet.requiresBuildForRepository(repository)); |
| if (!requiresBuild) |
| continue; |
| break; |
| } |
| value = value.value; |
| } |
| properties[propertyName] = value; |
| } |
| properties[this._buildRequestPropertyName] = buildRequest.id(); |
| |
| return properties; |
| } |
| |
| _revisionSetFromCommitSetWithExclusionList(commitSet, exclusionList) |
| { |
| const revisionSet = {}; |
| for (let repository of commitSet.repositories()) { |
| if (exclusionList.indexOf(repository.name()) >= 0) |
| continue; |
| const commit = commitSet.commitForRepository(repository); |
| revisionSet[repository.name()] = { |
| id: commit.id(), |
| time: +commit.time(), |
| repository: repository.name(), |
| revision: commit.revision(), |
| }; |
| } |
| return revisionSet; |
| } |
| |
| static _loadConfig(remote, config, builderNameToIDMap) |
| { |
| assert(builderNameToIDMap); |
| const types = config['types'] || {}; |
| const builders = config['builders'] || {}; |
| |
| assert(config.buildRequestArgument, 'buildRequestArgument must specify the name of the property used to store the build request ID'); |
| |
| assert.equal(typeof(config.repositoryGroups), 'object', 'repositoryGroups must specify a dictionary from the name to its definition'); |
| |
| const repositoryGroups = {}; |
| for (const name in config.repositoryGroups) |
| repositoryGroups[name] = this._parseRepositoryGroup(name, config.repositoryGroups[name]); |
| |
| const commonConfigurations = { |
| repositoryGroups, |
| slaveArgument: config.slaveArgument, |
| buildRequestArgument: config.buildRequestArgument, |
| platformArgument: config.platformArgument, |
| }; |
| |
| const syncerByBuilder = new Map; |
| const ensureBuildbotSyncer = (builderInfo) => { |
| let builderSyncer = syncerByBuilder.get(builderInfo.builder); |
| if (!builderSyncer) { |
| builderSyncer = new BuildbotSyncer(remote, builderInfo, commonConfigurations); |
| syncerByBuilder.set(builderInfo.builder, builderSyncer); |
| } |
| return builderSyncer; |
| } |
| |
| assert(Array.isArray(config['testConfigurations']), `The test configuration must be an array`); |
| this._resolveBuildersWithPlatforms('test', config['testConfigurations'], builders, builderNameToIDMap).forEach((entry, configurationIndex) => { |
| assert(Array.isArray(entry['types']), `The test configuration ${configurationIndex} does not specify "types" as an array`); |
| for (const type of entry['types']) { |
| const typeConfig = this._validateAndMergeConfig({}, entry.builderConfig); |
| assert(types[type], `"${type}" is not a valid type in the configuration`); |
| this._validateAndMergeConfig(typeConfig, types[type]); |
| |
| const testPath = typeConfig.test; |
| const test = Test.findByPath(testPath); |
| assert(test, `"${testPath.join('", "')}" is not a valid test path in the test configuration ${configurationIndex}`); |
| |
| ensureBuildbotSyncer(entry.builderConfig).addTestConfiguration(test, entry.platform, typeConfig.properties); |
| } |
| }); |
| |
| const buildConfigurations = config['buildConfigurations']; |
| if (buildConfigurations) { |
| assert(Array.isArray(buildConfigurations), `The test configuration must be an array`); |
| this._resolveBuildersWithPlatforms('test', buildConfigurations, builders, builderNameToIDMap).forEach((entry, configurationIndex) => { |
| const syncer = ensureBuildbotSyncer(entry.builderConfig); |
| assert(!syncer.isTester(), `The build configuration ${configurationIndex} uses a tester: ${syncer.builderName()}`); |
| syncer.addBuildConfiguration(entry.platform, entry.builderConfig.properties); |
| }); |
| } |
| |
| return Array.from(syncerByBuilder.values()); |
| } |
| |
| static _resolveBuildersWithPlatforms(configurationType, configurationList, builders, builderNameToIDMap) |
| { |
| const resolvedConfigurations = []; |
| let configurationIndex = 0; |
| for (const entry of configurationList) { |
| configurationIndex++; |
| assert(Array.isArray(entry['builders']), `The ${configurationType} configuration ${configurationIndex} does not specify "builders" as an array`); |
| assert(Array.isArray(entry['platforms']), `The ${configurationType} configuration ${configurationIndex} does not specify "platforms" as an array`); |
| for (const builderKey of entry['builders']) { |
| const matchingBuilder = builders[builderKey]; |
| assert(matchingBuilder, `"${builderKey}" is not a valid builder in the configuration`); |
| assert('builder' in matchingBuilder, `Builder ${builderKey} does not specify a buildbot builder name`); |
| assert(matchingBuilder.builder in builderNameToIDMap, `Builder ${matchingBuilder.builder} not found in Buildbot configuration.`); |
| matchingBuilder['builderID'] = builderNameToIDMap[matchingBuilder.builder]; |
| const builderConfig = this._validateAndMergeConfig({}, matchingBuilder); |
| for (const platformName of entry['platforms']) { |
| const platform = Platform.findByName(platformName); |
| assert(platform, `${platformName} is not a valid platform name`); |
| resolvedConfigurations.push({types: entry.types, builderConfig, platform}); |
| } |
| } |
| } |
| return resolvedConfigurations; |
| } |
| |
| static _parseRepositoryGroup(name, group) |
| { |
| assert.equal(typeof(group.repositories), 'object', |
| `Repository group "${name}" does not specify a dictionary of repositories`); |
| assert(!('description' in group) || typeof(group['description']) == 'string', |
| `Repository group "${name}" have an invalid description`); |
| assert([undefined, true, false].includes(group.acceptsRoots), |
| `Repository group "${name}" contains invalid acceptsRoots value: ${JSON.stringify(group.acceptsRoots)}`); |
| |
| const repositoryByName = {}; |
| const parsedRepositoryList = []; |
| const patchAcceptingRepositoryList = new Set; |
| for (const repositoryName in group.repositories) { |
| const options = group.repositories[repositoryName]; |
| const repository = Repository.findTopLevelByName(repositoryName); |
| assert(repository, `"${repositoryName}" is not a valid repository name`); |
| repositoryByName[repositoryName] = repository; |
| assert.equal(typeof(options), 'object', `"${repositoryName}" specifies a non-dictionary value`); |
| assert([undefined, true, false].includes(options.acceptsPatch), |
| `"${repositoryName}" contains invalid acceptsPatch value: ${JSON.stringify(options.acceptsPatch)}`); |
| if (options.acceptsPatch) |
| patchAcceptingRepositoryList.add(repository); |
| repositoryByName[repositoryName] = repository; |
| parsedRepositoryList.push({repository: repository.id(), acceptsPatch: options.acceptsPatch}); |
| } |
| assert(parsedRepositoryList.length, `Repository group "${name}" does not specify any repository`); |
| |
| assert.equal(typeof(group.testProperties), 'object', `Repository group "${name}" specifies the test configurations with an invalid type`); |
| |
| const resolveRepository = (repositoryName) => { |
| const repository = repositoryByName[repositoryName]; |
| assert(repository, `Repository group "${name}" an invalid repository "${repositoryName}"`); |
| return repository; |
| } |
| |
| const testRepositories = new Set; |
| let specifiesRoots = false; |
| const testPropertiesTemplate = this._parseRepositoryGroupPropertyTemplate('test', name, group.testProperties, (type, value, condition) => { |
| assert(type != 'patch', `Repository group "${name}" specifies a patch for "${value}" in the properties for testing`); |
| switch (type) { |
| case 'revision': |
| const repository = resolveRepository(value); |
| testRepositories.add(repository); |
| return {type, repository}; |
| case 'roots': |
| assert(group.acceptsRoots, `Repository group "${name}" specifies roots in a property but it does not accept roots`); |
| specifiesRoots = true; |
| return {type}; |
| case 'ifBuilt': |
| assert('condition', 'condition must set if type is "ifBuilt"'); |
| return {type: 'conditional', condition: 'built', value, repositoryRequirement: condition.map(resolveRepository)}; |
| } |
| return null; |
| }); |
| assert(!group.acceptsRoots == !specifiesRoots, |
| `Repository group "${name}" accepts roots but does not specify roots in testProperties`); |
| assert.equal(parsedRepositoryList.length, testRepositories.size, |
| `Repository group "${name}" does not use some of the repositories listed in testing`); |
| |
| let buildPropertiesTemplate = null; |
| if ('buildProperties' in group) { |
| assert(group.acceptsRoots, `Repository group "${name}" specifies the properties for building but does not accept roots in testing`); |
| const revisionRepositories = new Set; |
| const patchRepositories = new Set; |
| let hasOwnedRevisions = false; |
| buildPropertiesTemplate = this._parseRepositoryGroupPropertyTemplate('build', name, group.buildProperties, (type, value, condition) => { |
| assert(type != 'roots', `Repository group "${name}" specifies roots in the properties for building`); |
| let repository = null; |
| switch (type) { |
| case 'patch': |
| repository = resolveRepository(value); |
| assert(patchAcceptingRepositoryList.has(repository), `Repository group "${name}" specifies a patch for "${value}" but it does not accept a patch`); |
| patchRepositories.add(repository); |
| return {type, repository}; |
| case 'revision': |
| repository = resolveRepository(value); |
| revisionRepositories.add(repository); |
| return {type, repository}; |
| case 'ownedRevisions': |
| hasOwnedRevisions = true; |
| return {type, ownerRepository: resolveRepository(value)}; |
| case 'ifRepositorySet': |
| assert(condition, 'condition must set if type is "ifRepositorySet"'); |
| return {type: 'conditional', condition: 'requiresBuild', value, repositoriesToCheck: condition.map(resolveRepository)}; |
| } |
| return null; |
| }); |
| assert(patchAcceptingRepositoryList.size || hasOwnedRevisions, `Repository group "${name}" specifies the properties for building but does not accept any patches or need to build owned components`); |
| for (const repository of patchRepositories) |
| assert(revisionRepositories.has(repository), `Repository group "${name}" specifies a patch for "${repository.name()}" but does not specify a revision`); |
| assert.equal(patchAcceptingRepositoryList.size, patchRepositories.size, |
| `Repository group "${name}" does not use some of the repositories listed in building a patch`); |
| } |
| |
| return { |
| name: group.name, |
| description: group.description, |
| acceptsRoots: group.acceptsRoots, |
| testPropertiesTemplate: testPropertiesTemplate, |
| buildPropertiesTemplate: buildPropertiesTemplate, |
| repositoryList: parsedRepositoryList, |
| }; |
| } |
| |
| static _parseRepositoryGroupPropertyTemplate(parsingMode, groupName, properties, makeOption) |
| { |
| const propertiesTemplate = {}; |
| for (const propertyName in properties) { |
| let value = properties[propertyName]; |
| const isDictionary = typeof(value) == 'object'; |
| assert(isDictionary || typeof(value) == 'string' || typeof(value) == 'boolean', |
| `Repository group "${groupName}" uses an invalid value "${value}" in property "${propertyName}"`); |
| |
| if (!isDictionary) { |
| propertiesTemplate[propertyName] = value; |
| continue; |
| } |
| |
| const keys = Object.keys(value); |
| assert(keys.length == 1 || keys.length == 2, |
| `Repository group "${groupName}" specifies more than two types in property "${propertyName}": "${keys.join('", "')}"`); |
| let type; |
| let condition = null; |
| let optionValue; |
| if (keys.length == 2) { |
| assert(keys.includes('value'), `Repository group "${groupName}" with two types in property "${propertyName}": "${keys.join('", "')}" should contains 'value' as one type`); |
| type = keys.find((key) => key != 'value'); |
| optionValue = value.value; |
| condition = value[type]; |
| } |
| else { |
| type = keys[0]; |
| optionValue = value[type]; |
| } |
| const option = makeOption(type, optionValue, condition); |
| assert(option, `Repository group "${groupName}" specifies an invalid type "${type}" in property "${propertyName}"`); |
| propertiesTemplate[propertyName] = option; |
| } |
| return propertiesTemplate; |
| } |
| |
| static _validateAndMergeConfig(config, valuesToMerge, excludedProperty) |
| { |
| for (const name in valuesToMerge) { |
| const value = valuesToMerge[name]; |
| if (name == excludedProperty) |
| continue; |
| |
| switch (name) { |
| case 'properties': // Fallthrough |
| assert.equal(typeof(value), 'object', 'Build properties should be a dictionary'); |
| if (!config['properties']) |
| config['properties'] = {}; |
| const properties = config['properties']; |
| for (const name in value) { |
| assert.equal(typeof(value[name]), 'string', `Build properties "${name}" specifies a non-string value of type "${typeof(value)}"`); |
| properties[name] = value[name]; |
| } |
| break; |
| case 'test': // Fallthrough |
| case 'slaveList': // Fallthrough |
| assert(value instanceof Array, `${name} should be an array`); |
| assert(value.every(function (part) { return typeof part == 'string'; }), `${name} should be an array of strings`); |
| config[name] = value.slice(); |
| break; |
| case 'builder': // Fallthrough |
| assert.equal(typeof(value), 'string', `${name} should be of string type`); |
| config[name] = value; |
| break; |
| case 'builderID': |
| assert(value, 'builderID should not be undefined.'); |
| config[name] = value; |
| break; |
| default: |
| assert(false, `Unrecognized parameter "${name}"`); |
| } |
| } |
| return config; |
| } |
| |
| } |
| |
| if (typeof module != 'undefined') { |
| module.exports.BuildbotSyncer = BuildbotSyncer; |
| module.exports.BuildbotBuildEntry = BuildbotBuildEntry; |
| } |