'use strict'; let assert = require('assert'); require('../tools/js/v3-models.js'); const BrowserPrivilegedAPI = require('../public/v3/privileged-api.js').PrivilegedAPI; const MockRemoteAPI = require('./resources/mock-remote-api.js').MockRemoteAPI; const MockModels = require('./resources/mock-v3-models.js').MockModels; const BuildbotBuildEntry = require('../tools/js/buildbot-syncer.js').BuildbotBuildEntry; const BuildbotSyncer = require('../tools/js/buildbot-syncer.js').BuildbotSyncer; function sampleiOSConfig() { return { 'workerArgument': 'workername', 'buildRequestArgument': 'build_request_id', 'repositoryGroups': { 'ios-svn-webkit': { 'repositories': {'WebKit': {}, 'iOS': {}}, 'testProperties': { 'desired_image': {'revision': 'iOS'}, 'opensource': {'revision': 'WebKit'}, } } }, 'types': { 'speedometer': { 'test': ['Speedometer'], 'properties': {'test_name': 'speedometer'} }, 'jetstream': { 'test': ['JetStream'], 'properties': {'test_name': 'jetstream'} }, 'dromaeo-dom': { 'test': ['Dromaeo', 'DOM Core Tests'], 'properties': {'tests': 'dromaeo-dom'} }, }, 'builders': { 'iPhone-bench': { 'builder': 'ABTest-iPhone-RunBenchmark-Tests', 'properties': {'forcescheduler': 'ABTest-iPhone-RunBenchmark-Tests-ForceScheduler'}, 'workerList': ['ABTest-iPhone-0'], }, 'iPad-bench': { 'builder': 'ABTest-iPad-RunBenchmark-Tests', 'properties': {'forcescheduler': 'ABTest-iPad-RunBenchmark-Tests-ForceScheduler'}, 'workerList': ['ABTest-iPad-0', 'ABTest-iPad-1'], }, 'iOS-builder': { 'builder': 'ABTest-iOS-Builder', 'properties': {'forcescheduler': 'ABTest-Builder-ForceScheduler'}, }, }, 'buildConfigurations': [ {'builders': ['iOS-builder'], 'platforms': ['iPhone', 'iPad']}, ], 'testConfigurations': [ {'builders': ['iPhone-bench'], 'types': ['speedometer', 'jetstream', 'dromaeo-dom'], 'platforms': ['iPhone']}, {'builders': ['iPad-bench'], 'types': ['speedometer', 'jetstream'], 'platforms': ['iPad']}, ] }; } function sampleiOSConfigWithExpansions() { return { "triggerableName": "build-webkit-ios", "buildRequestArgument": "build-request-id", "repositoryGroups": { }, "types": { "iphone-plt": { "test": ["PLT-iPhone"], "properties": {"test_name": "plt"} }, "ipad-plt": { "test": ["PLT-iPad"], "properties": {"test_name": "plt"} }, "speedometer": { "test": ["Speedometer"], "properties": {"tests": "speedometer"} }, }, "builders": { "iphone": { "builder": "iPhone AB Tests", "properties": {"forcescheduler": "force-iphone-ab-tests"}, }, "iphone-2": { "builder": "iPhone 2 AB Tests", "properties": {"forcescheduler": "force-iphone-2-ab-tests"}, }, "ipad": { "builder": "iPad AB Tests", "properties": {"forcescheduler": "force-ipad-ab-tests"}, }, }, "testConfigurations": [ { "builders": ["iphone", "iphone-2"], "platforms": ["iPhone", "iOS 10 iPhone"], "types": ["iphone-plt", "speedometer"], }, { "builders": ["ipad"], "platforms": ["iPad"], "types": ["ipad-plt", "speedometer"], }, ] } } function smallConfiguration() { return { 'buildRequestArgument': 'id', 'repositoryGroups': { 'ios-svn-webkit': { 'repositories': {'iOS': {}, 'WebKit': {}}, 'testProperties': { 'os': {'revision': 'iOS'}, 'wk': {'revision': 'WebKit'} } } }, 'types': { 'some-test': { 'test': ['Some test'], } }, 'builders': { 'some-builder': { 'builder': 'some builder', 'properties': {'forcescheduler': 'some-builder-ForceScheduler'} } }, 'testConfigurations': [{ 'builders': ['some-builder'], 'platforms': ['Some platform'], 'types': ['some-test'], }] }; } function builderNameToIDMap() { return { 'some builder' : '100', 'ABTest-iPhone-RunBenchmark-Tests': '101', 'ABTest-iPad-RunBenchmark-Tests': '102', 'ABTest-iOS-Builder': '103', 'iPhone AB Tests' : '104', 'iPhone 2 AB Tests': '105', 'iPad AB Tests': '106' }; } function smallPendingBuild() { return samplePendingBuildRequests(null, null, null, "some builder"); } function smallInProgressBuild() { return sampleInProgressBuild(); } function smallFinishedBuild() { return sampleFinishedBuild(null, null, "some builder"); } function createSampleBuildRequest(platform, test) { assert(platform instanceof Platform); assert(test instanceof Test); const webkit197463 = CommitLog.ensureSingleton('111127', {'id': '111127', 'time': 1456955807334, 'repository': MockModels.webkit, 'revision': '197463'}); const shared111237 = CommitLog.ensureSingleton('111237', {'id': '111237', 'time': 1456931874000, 'repository': MockModels.sharedRepository, 'revision': '80229'}); const ios13A452 = CommitLog.ensureSingleton('88930', {'id': '88930', 'time': 0, 'repository': MockModels.ios, 'revision': '13A452'}); const commitSet = CommitSet.ensureSingleton('4197', {customRoots: [], revisionItems: [{commit: webkit197463}, {commit: shared111237}, {commit: ios13A452}]}); return BuildRequest.ensureSingleton('16733-' + platform.id(), {'triggerable': MockModels.triggerable, repositoryGroup: MockModels.svnRepositoryGroup, 'commitSet': commitSet, 'status': 'pending', 'platform': platform, 'test': test, order: 0}); } function createSampleBuildRequestWithPatch(platform, test, order) { assert(platform instanceof Platform); assert(!test || test instanceof Test); const webkit197463 = CommitLog.ensureSingleton('111127', {'id': '111127', 'time': 1456955807334, 'repository': MockModels.webkit, 'revision': '197463'}); const shared111237 = CommitLog.ensureSingleton('111237', {'id': '111237', 'time': 1456931874000, 'repository': MockModels.sharedRepository, 'revision': '80229'}); const ios13A452 = CommitLog.ensureSingleton('88930', {'id': '88930', 'time': 0, 'repository': MockModels.ios, 'revision': '13A452'}); const patch = new UploadedFile(453, {'createdAt': new Date('2017-05-01T19:16:53Z'), 'filename': 'patch.dat', 'extension': '.dat', 'author': 'some user', size: 534637, sha256: '169463c8125e07c577110fe144ecd63942eb9472d438fc0014f474245e5df8a1'}); const root = new UploadedFile(456, {'createdAt': new Date('2017-05-01T21:03:27Z'), 'filename': 'root.dat', 'extension': '.dat', 'author': 'some user', size: 16452234, sha256: '03eed7a8494ab8794c44b7d4308e55448fc56f4d6c175809ba968f78f656d58d'}); const commitSet = CommitSet.ensureSingleton('53246456', {customRoots: [root], revisionItems: [{commit: webkit197463, patch, requiresBuild: true}, {commit: shared111237}, {commit: ios13A452}]}); return BuildRequest.ensureSingleton(`6345645376-${order}`, {'triggerable': MockModels.triggerable, repositoryGroup: MockModels.svnRepositoryGroup, 'commitSet': commitSet, 'status': 'pending', 'platform': platform, 'test': test, 'order': order}); } function createSampleBuildRequestWithOwnedCommit(platform, test, order) { assert(platform instanceof Platform); assert(!test || test instanceof Test); const webkit197463 = CommitLog.ensureSingleton('111127', {'id': '111127', 'time': 1456955807334, 'repository': MockModels.webkit, 'revision': '197463'}); const owner111289 = CommitLog.ensureSingleton('111289', {'id': '111289', 'time': 1456931874000, 'repository': MockModels.ownerRepository, 'revision': 'owner-001'}); const owned111222 = CommitLog.ensureSingleton('111222', {'id': '111222', 'time': 1456932774000, 'repository': MockModels.ownedRepository, 'revision': 'owned-002'}); const ios13A452 = CommitLog.ensureSingleton('88930', {'id': '88930', 'time': 0, 'repository': MockModels.ios, 'revision': '13A452'}); const root = new UploadedFile(456, {'createdAt': new Date('2017-05-01T21:03:27Z'), 'filename': 'root.dat', 'extension': '.dat', 'author': 'some user', size: 16452234, sha256: '03eed7a8494ab8794c44b7d4308e55448fc56f4d6c175809ba968f78f656d58d'}); const commitSet = CommitSet.ensureSingleton('53246486', {customRoots: [root], revisionItems: [{commit: webkit197463}, {commit: owner111289}, {commit: owned111222, commitOwner: owner111289, requiresBuild: true}, {commit: ios13A452}]}); return BuildRequest.ensureSingleton(`6345645370-${order}`, {'triggerable': MockModels.triggerable, repositoryGroup: MockModels.svnRepositoryWithOwnedRepositoryGroup, 'commitSet': commitSet, 'status': 'pending', 'platform': platform, 'test': test, 'order': order}); } function createSampleBuildRequestWithOwnedCommitAndPatch(platform, test, order) { assert(platform instanceof Platform); assert(!test || test instanceof Test); const webkit197463 = CommitLog.ensureSingleton('111127', {'id': '111127', 'time': 1456955807334, 'repository': MockModels.webkit, 'revision': '197463'}); const owner111289 = CommitLog.ensureSingleton('111289', {'id': '111289', 'time': 1456931874000, 'repository': MockModels.ownerRepository, 'revision': 'owner-001'}); const owned111222 = CommitLog.ensureSingleton('111222', {'id': '111222', 'time': 1456932774000, 'repository': MockModels.ownedRepository, 'revision': 'owned-002'}); const ios13A452 = CommitLog.ensureSingleton('88930', {'id': '88930', 'time': 0, 'repository': MockModels.ios, 'revision': '13A452'}); const patch = new UploadedFile(453, {'createdAt': new Date('2017-05-01T19:16:53Z'), 'filename': 'patch.dat', 'extension': '.dat', 'author': 'some user', size: 534637, sha256: '169463c8125e07c577110fe144ecd63942eb9472d438fc0014f474245e5df8a1'}); const commitSet = CommitSet.ensureSingleton('53246486', {customRoots: [], revisionItems: [{commit: webkit197463, patch, requiresBuild: true}, {commit: owner111289}, {commit: owned111222, commitOwner: owner111289, requiresBuild: true}, {commit: ios13A452}]}); return BuildRequest.ensureSingleton(`6345645370-${order}`, {'triggerable': MockModels.triggerable, repositoryGroup: MockModels.svnRepositoryWithOwnedRepositoryGroup, 'commitSet': commitSet, 'status': 'pending', 'platform': platform, 'test': test, 'order': order}); } function samplePendingBuildRequestData(buildRequestId, buildTime, workerName, builderId) { return { "builderid": builderId || 102, "buildrequestid": 17, "buildsetid": 894720, "claimed": false, "claimed_at": null, "claimed_by_masterid": null, "complete": false, "complete_at": null, "priority": 0, "results": -1, "submitted_at": buildTime || 1458704983, "waited_for": false, "properties": { "build_request_id": [buildRequestId || 16733, "Force Build Form"], "scheduler": ["ABTest-iPad-RunBenchmark-Tests-ForceScheduler", "Scheduler"], "workername": [workerName, "Worker (deprecated)"], "workername": [workerName, "Worker"] } }; } function samplePendingBuildRequests(buildRequestId, buildTime, workerName, builderName) { return { "buildrequests" : [samplePendingBuildRequestData(buildRequestId, buildTime, workerName, builderNameToIDMap()[builderName])] }; } function sampleBuildData(workerName, isComplete, buildRequestId, buildTag, builderId, state_string) { return { "builderid": builderId || 102, "number": buildTag || 614, "buildrequestid": 17, "complete": isComplete, "complete_at": null, "buildid": 418744, "masterid": 1, "results": null, "started_at": 1513725109, state_string, "workerid": 41, "properties": { "build_request_id": [buildRequestId || 16733, "Force Build Form"], "platform": ["mac", "Unknown"], "scheduler": ["ABTest-iPad-RunBenchmark-Tests-ForceScheduler", "Scheduler"], "workername": [workerName || "ABTest-iPad-0", "Worker (deprecated)"], "workername": [workerName || "ABTest-iPad-0", "Worker"], } }; } function sampleInProgressBuildData(workerName) { return sampleBuildData(workerName, false, null, null, null, 'building'); } function sampleInProgressBuild(workerName) { return { "builds": [sampleInProgressBuildData(workerName)] }; } function sampleFinishedBuildData(buildRequestId, workerName, builderName) { return sampleBuildData(workerName, true, buildRequestId || 18935, 1755, builderNameToIDMap()[builderName]); } function sampleFinishedBuild(buildRequestId, workerName, builderName) { return { "builds": [sampleFinishedBuildData(buildRequestId, workerName, builderName)] }; } describe('BuildbotSyncer', () => { MockModels.inject(); const requests = MockRemoteAPI.inject('http://build.webkit.org', BrowserPrivilegedAPI); describe('_loadConfig', () => { it('should create BuildbotSyncer objects for a configuration that specify all required options', () => { assert.equal(BuildbotSyncer._loadConfig(MockRemoteAPI, smallConfiguration(), builderNameToIDMap()).length, 1); }); it('should throw when some required options are missing', () => { assert.throws(() => { const config = smallConfiguration(); delete config.builders; BuildbotSyncer._loadConfig(MockRemoteAPI, config, builderNameToIDMap()); }, /"some-builder" is not a valid builder in the configuration/); assert.throws(() => { const config = smallConfiguration(); delete config.types; BuildbotSyncer._loadConfig(MockRemoteAPI, config, builderNameToIDMap()); }, /"some-test" is not a valid type in the configuration/); assert.throws(() => { const config = smallConfiguration(); delete config.testConfigurations[0].builders; BuildbotSyncer._loadConfig(MockRemoteAPI, config, builderNameToIDMap()); }, /The test configuration 1 does not specify "builders" as an array/); assert.throws(() => { const config = smallConfiguration(); delete config.testConfigurations[0].platforms; BuildbotSyncer._loadConfig(MockRemoteAPI, config, builderNameToIDMap()); }, /The test configuration 1 does not specify "platforms" as an array/); assert.throws(() => { const config = smallConfiguration(); delete config.testConfigurations[0].types; BuildbotSyncer._loadConfig(MockRemoteAPI, config, builderNameToIDMap()); }, /The test configuration 0 does not specify "types" as an array/); assert.throws(() => { const config = smallConfiguration(); delete config.buildRequestArgument; BuildbotSyncer._loadConfig(MockRemoteAPI, config, builderNameToIDMap()); }, /buildRequestArgument must specify the name of the property used to store the build request ID/); }); it('should throw when a test name is not an array of strings', () => { assert.throws(() => { const config = smallConfiguration(); config.testConfigurations[0].types = 'some test'; BuildbotSyncer._loadConfig(MockRemoteAPI, config, builderNameToIDMap()); }, /The test configuration 0 does not specify "types" as an array/); assert.throws(() => { const config = smallConfiguration(); config.testConfigurations[0].types = [1]; BuildbotSyncer._loadConfig(MockRemoteAPI, config, builderNameToIDMap()); }, /"1" is not a valid type in the configuration/); }); it('should throw when properties is not an object', () => { assert.throws(() => { const config = smallConfiguration(); config.builders[Object.keys(config.builders)[0]].properties = 'hello'; BuildbotSyncer._loadConfig(MockRemoteAPI, config, builderNameToIDMap()); }, /Build properties should be a dictionary/); assert.throws(() => { const config = smallConfiguration(); config.types[Object.keys(config.types)[0]].properties = 'hello'; BuildbotSyncer._loadConfig(MockRemoteAPI, config, builderNameToIDMap()); }, /Build properties should be a dictionary/); }); it('should throw when testProperties is specifed in a type or a builder', () => { assert.throws(() => { const config = smallConfiguration(); const firstType = Object.keys(config.types)[0]; config.types[firstType].testProperties = {}; BuildbotSyncer._loadConfig(MockRemoteAPI, config, builderNameToIDMap()); }, /Unrecognized parameter "testProperties"/); assert.throws(() => { const config = smallConfiguration(); const firstBuilder = Object.keys(config.builders)[0]; config.builders[firstBuilder].testProperties = {}; BuildbotSyncer._loadConfig(MockRemoteAPI, config, builderNameToIDMap()); }, /Unrecognized parameter "testProperties"/); }); it('should throw when buildProperties is specifed in a type or a builder', () => { assert.throws(() => { const config = smallConfiguration(); const firstType = Object.keys(config.types)[0]; config.types[firstType].buildProperties = {}; BuildbotSyncer._loadConfig(MockRemoteAPI, config, builderNameToIDMap()); }, /Unrecognized parameter "buildProperties"/); assert.throws(() => { const config = smallConfiguration(); const firstBuilder = Object.keys(config.builders)[0]; config.builders[firstBuilder].buildProperties = {}; BuildbotSyncer._loadConfig(MockRemoteAPI, config, builderNameToIDMap()); }, /Unrecognized parameter "buildProperties"/); }); it('should throw when properties for a type is malformed', () => { const firstType = Object.keys(smallConfiguration().types)[0]; assert.throws(() => { const config = smallConfiguration(); config.types[firstType].properties = 'hello'; BuildbotSyncer._loadConfig(MockRemoteAPI, config, builderNameToIDMap()); }, /Build properties should be a dictionary/); assert.throws(() => { const config = smallConfiguration(); config.types[firstType].properties = {'some': {'otherKey': 'some root'}}; BuildbotSyncer._loadConfig(RemoteAPI, config, builderNameToIDMap()); }, /Build properties "some" specifies a non-string value of type "object"/); assert.throws(() => { const config = smallConfiguration(); config.types[firstType].properties = {'some': {'otherKey': 'some root'}}; BuildbotSyncer._loadConfig(RemoteAPI, config, builderNameToIDMap()); }, /Build properties "some" specifies a non-string value of type "object"/); assert.throws(() => { const config = smallConfiguration(); config.types[firstType].properties = {'some': {'revision': 'WebKit'}}; BuildbotSyncer._loadConfig(RemoteAPI, config, builderNameToIDMap()); }, /Build properties "some" specifies a non-string value of type "object"/); assert.throws(() => { const config = smallConfiguration(); config.types[firstType].properties = {'some': 1}; BuildbotSyncer._loadConfig(RemoteAPI, config, builderNameToIDMap()); }, / Build properties "some" specifies a non-string value of type "object"/); }); it('should throw when properties for a builder is malformed', () => { const firstBuilder = Object.keys(smallConfiguration().builders)[0]; assert.throws(() => { const config = smallConfiguration(); config.builders[firstBuilder].properties = 'hello'; BuildbotSyncer._loadConfig(MockRemoteAPI, config, builderNameToIDMap()); }, /Build properties should be a dictionary/); assert.throws(() => { const config = smallConfiguration(); config.builders[firstBuilder].properties = {'some': {'otherKey': 'some root'}}; BuildbotSyncer._loadConfig(RemoteAPI, config, builderNameToIDMap()); }, /Build properties "some" specifies a non-string value of type "object"/); assert.throws(() => { const config = smallConfiguration(); config.builders[firstBuilder].properties = {'some': {'otherKey': 'some root'}}; BuildbotSyncer._loadConfig(RemoteAPI, config, builderNameToIDMap()); }, /Build properties "some" specifies a non-string value of type "object"/); assert.throws(() => { const config = smallConfiguration(); config.builders[firstBuilder].properties = {'some': {'revision': 'WebKit'}}; BuildbotSyncer._loadConfig(RemoteAPI, config, builderNameToIDMap()); }, /Build properties "some" specifies a non-string value of type "object"/); assert.throws(() => { const config = smallConfiguration(); config.builders[firstBuilder].properties = {'some': 1}; BuildbotSyncer._loadConfig(RemoteAPI, config, builderNameToIDMap()); }, /Build properties "some" specifies a non-string value of type "object"/); }); it('should create BuildbotSyncer objects for valid configurations', () => { let syncers = BuildbotSyncer._loadConfig(RemoteAPI, sampleiOSConfig(), builderNameToIDMap()); assert.equal(syncers.length, 3); assert.ok(syncers[0] instanceof BuildbotSyncer); assert.ok(syncers[1] instanceof BuildbotSyncer); assert.ok(syncers[2] instanceof BuildbotSyncer); }); it('should parse builder names correctly', () => { let syncers = BuildbotSyncer._loadConfig(RemoteAPI, sampleiOSConfig(), builderNameToIDMap()); assert.equal(syncers[0].builderName(), 'ABTest-iPhone-RunBenchmark-Tests'); assert.equal(syncers[1].builderName(), 'ABTest-iPad-RunBenchmark-Tests'); assert.equal(syncers[2].builderName(), 'ABTest-iOS-Builder'); }); it('should parse test configurations with build configurations correctly', () => { let syncers = BuildbotSyncer._loadConfig(RemoteAPI, sampleiOSConfig(), builderNameToIDMap()); let configurations = syncers[0].testConfigurations(); assert(syncers[0].isTester()); assert.equal(configurations.length, 3); assert.equal(configurations[0].platform, MockModels.iphone); assert.equal(configurations[0].test, MockModels.speedometer); assert.equal(configurations[1].platform, MockModels.iphone); assert.equal(configurations[1].test, MockModels.jetstream); assert.equal(configurations[2].platform, MockModels.iphone); assert.equal(configurations[2].test, MockModels.domcore); assert.deepEqual(syncers[0].buildConfigurations(), []); configurations = syncers[1].testConfigurations(); assert(syncers[1].isTester()); assert.equal(configurations.length, 2); assert.equal(configurations[0].platform, MockModels.ipad); assert.equal(configurations[0].test, MockModels.speedometer); assert.equal(configurations[1].platform, MockModels.ipad); assert.equal(configurations[1].test, MockModels.jetstream); assert.deepEqual(syncers[1].buildConfigurations(), []); assert(!syncers[2].isTester()); assert.deepEqual(syncers[2].testConfigurations(), []); configurations = syncers[2].buildConfigurations(); assert.equal(configurations.length, 2); assert.equal(configurations[0].platform, MockModels.iphone); assert.equal(configurations[0].test, null); assert.equal(configurations[1].platform, MockModels.ipad); assert.equal(configurations[1].test, null); }); it('should throw when a build configuration use the same builder as a test configuration', () => { assert.throws(() => { const config = sampleiOSConfig(); config.buildConfigurations[0].builders = config.testConfigurations[0].builders; BuildbotSyncer._loadConfig(MockRemoteAPI, config, builderNameToIDMap()); }); }); it('should parse test configurations with types and platforms expansions correctly', () => { const syncers = BuildbotSyncer._loadConfig(RemoteAPI, sampleiOSConfigWithExpansions(), builderNameToIDMap()); assert.equal(syncers.length, 3); let configurations = syncers[0].testConfigurations(); assert.equal(configurations.length, 4); assert.equal(configurations[0].platform, MockModels.iphone); assert.equal(configurations[0].test, MockModels.iPhonePLT); assert.equal(configurations[1].platform, MockModels.iphone); assert.equal(configurations[1].test, MockModels.speedometer); assert.equal(configurations[2].platform, MockModels.iOS10iPhone); assert.equal(configurations[2].test, MockModels.iPhonePLT); assert.equal(configurations[3].platform, MockModels.iOS10iPhone); assert.equal(configurations[3].test, MockModels.speedometer); assert.deepEqual(syncers[0].buildConfigurations(), []); configurations = syncers[1].testConfigurations(); assert.equal(configurations.length, 4); assert.equal(configurations[0].platform, MockModels.iphone); assert.equal(configurations[0].test, MockModels.iPhonePLT); assert.equal(configurations[1].platform, MockModels.iphone); assert.equal(configurations[1].test, MockModels.speedometer); assert.equal(configurations[2].platform, MockModels.iOS10iPhone); assert.equal(configurations[2].test, MockModels.iPhonePLT); assert.equal(configurations[3].platform, MockModels.iOS10iPhone); assert.equal(configurations[3].test, MockModels.speedometer); assert.deepEqual(syncers[1].buildConfigurations(), []); configurations = syncers[2].testConfigurations(); assert.equal(configurations.length, 2); assert.equal(configurations[0].platform, MockModels.ipad); assert.equal(configurations[0].test, MockModels.iPadPLT); assert.equal(configurations[1].platform, MockModels.ipad); assert.equal(configurations[1].test, MockModels.speedometer); assert.deepEqual(syncers[2].buildConfigurations(), []); }); it('should throw when repositoryGroups is not an object', () => { assert.throws(() => { const config = smallConfiguration(); config.repositoryGroups = 1; BuildbotSyncer._loadConfig(MockRemoteAPI, config, builderNameToIDMap()); }, /repositoryGroups must specify a dictionary from the name to its definition/); assert.throws(() => { const config = smallConfiguration(); config.repositoryGroups = 'hello'; BuildbotSyncer._loadConfig(MockRemoteAPI, config, builderNameToIDMap()); }, /repositoryGroups must specify a dictionary from the name to its definition/); }); it('should throw when a repository group does not specify a dictionary of repositories', () => { assert.throws(() => { const config = smallConfiguration(); config.repositoryGroups = {'some-group': {testProperties: {}}}; BuildbotSyncer._loadConfig(MockRemoteAPI, config, builderNameToIDMap()); }, /Repository group "some-group" does not specify a dictionary of repositories/); assert.throws(() => { const config = smallConfiguration(); config.repositoryGroups = {'some-group': {repositories: 1}, testProperties: {}}; BuildbotSyncer._loadConfig(MockRemoteAPI, config, builderNameToIDMap()); }, /Repository group "some-group" does not specify a dictionary of repositories/); }); it('should throw when a repository group specifies an empty dictionary', () => { assert.throws(() => { const config = smallConfiguration(); config.repositoryGroups = {'some-group': {repositories: {}, testProperties: {}}}; BuildbotSyncer._loadConfig(MockRemoteAPI, config, builderNameToIDMap()); }, /Repository group "some-group" does not specify any repository/); }); it('should throw when a repository group specifies an invalid repository name', () => { assert.throws(() => { const config = smallConfiguration(); config.repositoryGroups = {'some-group': {repositories: {'InvalidRepositoryName': {}}}}; BuildbotSyncer._loadConfig(MockRemoteAPI, config, builderNameToIDMap()); }, /"InvalidRepositoryName" is not a valid repository name/); }); it('should throw when a repository group specifies a repository with a non-dictionary value', () => { assert.throws(() => { const config = smallConfiguration(); config.repositoryGroups = {'some-group': {repositories: {'WebKit': 1}}}; BuildbotSyncer._loadConfig(MockRemoteAPI, config, builderNameToIDMap()); }, /"WebKit" specifies a non-dictionary value/); }); it('should throw when the description of a repository group is not a string', () => { assert.throws(() => { const config = smallConfiguration(); config.repositoryGroups = {'some-group': {repositories: {'WebKit': {}}, description: 1}}; BuildbotSyncer._loadConfig(MockRemoteAPI, config, builderNameToIDMap()); }, /Repository group "some-group" have an invalid description/); assert.throws(() => { const config = smallConfiguration(); config.repositoryGroups = {'some-group': {repositories: {'WebKit': {}}, description: [1, 2]}}; BuildbotSyncer._loadConfig(MockRemoteAPI, config, builderNameToIDMap()); }, /Repository group "some-group" have an invalid description/); }); it('should throw when a repository group does not specify a dictionary of properties', () => { assert.throws(() => { const config = smallConfiguration(); config.repositoryGroups = {'some-group': {repositories: {'WebKit': {}}, testProperties: 1}}; BuildbotSyncer._loadConfig(MockRemoteAPI, config, builderNameToIDMap()); }, /Repository group "some-group" specifies the test configurations with an invalid type/); assert.throws(() => { const config = smallConfiguration(); config.repositoryGroups = {'some-group': {repositories: {'WebKit': {}}, testProperties: 'hello'}}; BuildbotSyncer._loadConfig(MockRemoteAPI, config, builderNameToIDMap()); }, /Repository group "some-group" specifies the test configurations with an invalid type/); }); it('should throw when a repository group refers to a non-existent repository in the properties dictionary', () => { assert.throws(() => { const config = smallConfiguration(); config.repositoryGroups = {'some-group': {repositories: {'WebKit': {}}, testProperties: {'wk': {revision: 'InvalidRepository'}}}}; BuildbotSyncer._loadConfig(MockRemoteAPI, config, builderNameToIDMap()); }, /Repository group "some-group" an invalid repository "InvalidRepository"/); }); it('should throw when a repository group refers to a repository which is not listed in the list of repositories', () => { assert.throws(() => { const config = smallConfiguration(); config.repositoryGroups = {'some-group': {repositories: {'WebKit': {}}, testProperties: {'os': {revision: 'iOS'}}}}; BuildbotSyncer._loadConfig(MockRemoteAPI, config, builderNameToIDMap()); }, /Repository group "some-group" an invalid repository "iOS"/); assert.throws(() => { const config = smallConfiguration(); config.repositoryGroups = {'some-group': { repositories: {'WebKit': {acceptsPatch: true}}, testProperties: {'wk': {revision: 'WebKit'}, 'install-roots': {'roots': {}}}, buildProperties: {'os': {revision: 'iOS'}}, acceptsRoots: true}}; BuildbotSyncer._loadConfig(MockRemoteAPI, config, builderNameToIDMap()); }, /Repository group "some-group" an invalid repository "iOS"/); }); it('should throw when a repository group refers to a repository in building a patch which does not accept a patch', () => { assert.throws(() => { const config = smallConfiguration(); config.repositoryGroups = {'some-group': { repositories: {'WebKit': {acceptsPatch: true}, 'iOS': {}}, testProperties: {'wk': {revision: 'WebKit'}, 'ios': {revision: 'iOS'}, 'install-roots': {'roots': {}}}, buildProperties: {'wk': {revision: 'WebKit'}, 'ios': {revision: 'iOS'}, 'wk-patch': {patch: 'iOS'}}, acceptsRoots: true}}; BuildbotSyncer._loadConfig(MockRemoteAPI, config, builderNameToIDMap()); }, /Repository group "some-group" specifies a patch for "iOS" but it does not accept a patch/); }); it('should throw when a repository group specifies a patch without specifying a revision', () => { assert.throws(() => { const config = smallConfiguration(); config.repositoryGroups = {'some-group': { repositories: {'WebKit': {acceptsPatch: true}}, testProperties: {'wk': {revision: 'WebKit'}, 'install-roots': {'roots': {}}}, buildProperties: {'wk-patch': {patch: 'WebKit'}}, acceptsRoots: true}}; BuildbotSyncer._loadConfig(MockRemoteAPI, config, builderNameToIDMap()); }, /Repository group "some-group" specifies a patch for "WebKit" but does not specify a revision/); }); it('should throw when a repository group does not use a listed repository', () => { assert.throws(() => { const config = smallConfiguration(); config.repositoryGroups = {'some-group': {'repositories': {'WebKit': {}}, testProperties: {}}}; BuildbotSyncer._loadConfig(MockRemoteAPI, config, builderNameToIDMap()); }, /Repository group "some-group" does not use some of the repositories listed in testing/); assert.throws(() => { const config = smallConfiguration(); config.repositoryGroups = {'some-group': { repositories: {'WebKit': {acceptsPatch: true}}, testProperties: {'wk': {revision: 'WebKit'}, 'install-roots': {'roots': {}}}, buildProperties: {}, acceptsRoots: true}}; BuildbotSyncer._loadConfig(MockRemoteAPI, config, builderNameToIDMap()); }, /Repository group "some-group" does not use some of the repositories listed in building a patch/); }); it('should throw when a repository group specifies non-boolean value to acceptsRoots', () => { assert.throws(() => { const config = smallConfiguration(); config.repositoryGroups = {'some-group': {'repositories': {'WebKit': {}}, 'testProperties': {'webkit': {'revision': 'WebKit'}}, acceptsRoots: 1}}; BuildbotSyncer._loadConfig(MockRemoteAPI, config, builderNameToIDMap()); }, /Repository group "some-group" contains invalid acceptsRoots value:/); assert.throws(() => { const config = smallConfiguration(); config.repositoryGroups = {'some-group': {'repositories': {'WebKit': {}}, 'testProperties': {'webkit': {'revision': 'WebKit'}}, acceptsRoots: []}}; BuildbotSyncer._loadConfig(MockRemoteAPI, config, builderNameToIDMap()); }, /Repository group "some-group" contains invalid acceptsRoots value:/); }); it('should throw when a repository group specifies non-boolean value to acceptsPatch', () => { assert.throws(() => { const config = smallConfiguration(); config.repositoryGroups = {'some-group': {'repositories': {'WebKit': {acceptsPatch: 1}}, 'testProperties': {'webkit': {'revision': 'WebKit'}}}}; BuildbotSyncer._loadConfig(MockRemoteAPI, config, builderNameToIDMap()); }, /"WebKit" contains invalid acceptsPatch value:/); assert.throws(() => { const config = smallConfiguration(); config.repositoryGroups = {'some-group': {'repositories': {'WebKit': {acceptsPatch: []}}, 'testProperties': {'webkit': {'revision': 'WebKit'}}}}; BuildbotSyncer._loadConfig(MockRemoteAPI, config, builderNameToIDMap()); }, /"WebKit" contains invalid acceptsPatch value:/); }); it('should throw when a repository group specifies a patch in testProperties', () => { assert.throws(() => { const config = smallConfiguration(); config.repositoryGroups = {'some-group': {'repositories': {'WebKit': {acceptsPatch: true}}, 'testProperties': {'webkit': {'revision': 'WebKit'}, 'webkit-patch': {'patch': 'WebKit'}}}}; BuildbotSyncer._loadConfig(MockRemoteAPI, config, builderNameToIDMap()); }, /Repository group "some-group" specifies a patch for "WebKit" in the properties for testing/); }); it('should throw when a repository group specifies roots in buildProperties', () => { assert.throws(() => { const config = smallConfiguration(); config.repositoryGroups = {'some-group': { repositories: {'WebKit': {acceptsPatch: true}}, testProperties: {'webkit': {revision: 'WebKit'}, 'install-roots': {'roots': {}}}, buildProperties: {'webkit': {revision: 'WebKit'}, 'patch': {patch: 'WebKit'}, 'install-roots': {roots: {}}}, acceptsRoots: true}}; BuildbotSyncer._loadConfig(MockRemoteAPI, config, builderNameToIDMap()); }, /Repository group "some-group" specifies roots in the properties for building/); }); it('should throw when a repository group that does not accept roots specifies roots in testProperties', () => { assert.throws(() => { const config = smallConfiguration(); config.repositoryGroups = {'some-group': { repositories: {'WebKit': {}}, testProperties: {'webkit': {'revision': 'WebKit'}, 'install-roots': {'roots': {}}}}}; BuildbotSyncer._loadConfig(MockRemoteAPI, config, builderNameToIDMap()); }, /Repository group "some-group" specifies roots in a property but it does not accept roots/); }); it('should throw when a repository group specifies buildProperties but does not accept roots', () => { assert.throws(() => { const config = smallConfiguration(); config.repositoryGroups = {'some-group': { repositories: {'WebKit': {acceptsPatch: true}}, testProperties: {'webkit': {revision: 'WebKit'}}, buildProperties: {'webkit': {revision: 'WebKit'}, 'webkit-patch': {patch: 'WebKit'}}}}; BuildbotSyncer._loadConfig(MockRemoteAPI, config, builderNameToIDMap()); }, /Repository group "some-group" specifies the properties for building but does not accept roots in testing/); }); it('should throw when a repository group specifies buildProperties but does not accept any patch', () => { assert.throws(() => { const config = smallConfiguration(); config.repositoryGroups = {'some-group': { repositories: {'WebKit': {}}, testProperties: {'webkit': {'revision': 'WebKit'}, 'install-roots': {'roots': {}}}, buildProperties: {'webkit': {'revision': 'WebKit'}}, acceptsRoots: true}}; BuildbotSyncer._loadConfig(MockRemoteAPI, config, builderNameToIDMap()); }, /Repository group "some-group" specifies the properties for building but does not accept any patches/); }); it('should throw when a repository group accepts roots but does not specify roots in testProperties', () => { assert.throws(() => { const config = smallConfiguration(); config.repositoryGroups = {'some-group': { repositories: {'WebKit': {acceptsPatch: true}}, testProperties: {'webkit': {revision: 'WebKit'}}, buildProperties: {'webkit': {revision: 'WebKit'}, 'webkit-patch': {patch: 'WebKit'}}, acceptsRoots: true}}; BuildbotSyncer._loadConfig(MockRemoteAPI, config, builderNameToIDMap()); }, /Repository group "some-group" accepts roots but does not specify roots in testProperties/); }); }); describe('_propertiesForBuildRequest', () => { it('should include all properties specified in a given configuration', () => { const syncers = BuildbotSyncer._loadConfig(RemoteAPI, sampleiOSConfig(), builderNameToIDMap()); const request = createSampleBuildRequest(MockModels.iphone, MockModels.speedometer); const properties = syncers[0]._propertiesForBuildRequest(request, [request]); assert.deepEqual(Object.keys(properties).sort(), ['build_request_id', 'desired_image', 'forcescheduler', 'opensource', 'test_name']); }); it('should preserve non-parametric property values', () => { const syncers = BuildbotSyncer._loadConfig(RemoteAPI, sampleiOSConfig(), builderNameToIDMap()); let request = createSampleBuildRequest(MockModels.iphone, MockModels.speedometer); let properties = syncers[0]._propertiesForBuildRequest(request, [request]); assert.equal(properties['test_name'], 'speedometer'); assert.equal(properties['forcescheduler'], 'ABTest-iPhone-RunBenchmark-Tests-ForceScheduler'); request = createSampleBuildRequest(MockModels.ipad, MockModels.jetstream); properties = syncers[1]._propertiesForBuildRequest(request, [request]); assert.equal(properties['test_name'], 'jetstream'); assert.equal(properties['forcescheduler'], 'ABTest-iPad-RunBenchmark-Tests-ForceScheduler'); }); it('should resolve "root"', () => { const syncers = BuildbotSyncer._loadConfig(RemoteAPI, sampleiOSConfig(), builderNameToIDMap()); const request = createSampleBuildRequest(MockModels.iphone, MockModels.speedometer); const properties = syncers[0]._propertiesForBuildRequest(request, [request]); assert.equal(properties['desired_image'], '13A452'); }); it('should resolve "revision"', () => { const syncers = BuildbotSyncer._loadConfig(RemoteAPI, sampleiOSConfig(), builderNameToIDMap()); const request = createSampleBuildRequest(MockModels.iphone, MockModels.speedometer); const properties = syncers[0]._propertiesForBuildRequest(request, [request]); assert.equal(properties['opensource'], '197463'); }); it('should resolve "patch"', () => { const config = sampleiOSConfig(); config.repositoryGroups['ios-svn-webkit'] = { 'repositories': {'WebKit': {'acceptsPatch': true}, 'Shared': {}, 'iOS': {}}, 'testProperties': { 'os': {'revision': 'iOS'}, 'webkit': {'revision': 'WebKit'}, 'shared': {'revision': 'Shared'}, 'roots': {'roots': {}}, }, 'buildProperties': { 'webkit': {'revision': 'WebKit'}, 'webkit-patch': {'patch': 'WebKit'}, 'checkbox': {'ifRepositorySet': ['WebKit'], 'value': 'build-webkit'}, 'build-webkit': {'ifRepositorySet': ['WebKit'], 'value': true}, 'shared': {'revision': 'Shared'}, }, 'acceptsRoots': true, }; const syncers = BuildbotSyncer._loadConfig(RemoteAPI, config, builderNameToIDMap()); const request = createSampleBuildRequestWithPatch(MockModels.iphone, null, -1); const properties = syncers[2]._propertiesForBuildRequest(request, [request]); assert.equal(properties['webkit'], '197463'); assert.equal(properties['webkit-patch'], 'http://build.webkit.org/api/uploaded-file/453.dat'); assert.equal(properties['checkbox'], 'build-webkit'); assert.equal(properties['build-webkit'], true); }); it('should resolve "ifBuilt"', () => { const config = sampleiOSConfig(); config.repositoryGroups['ios-svn-webkit'] = { 'repositories': {'WebKit': {}, 'Shared': {}, 'iOS': {}}, 'testProperties': { 'os': {'revision': 'iOS'}, 'webkit': {'revision': 'WebKit'}, 'shared': {'revision': 'Shared'}, 'roots': {'roots': {}}, 'test-custom-build': {'ifBuilt': [], 'value': ''}, 'has-built-patch': {'ifBuilt': [], 'value': 'true'}, }, 'acceptsRoots': true, }; const syncers = BuildbotSyncer._loadConfig(RemoteAPI, config, builderNameToIDMap()); const requestToBuild = createSampleBuildRequestWithPatch(MockModels.iphone, null, -1); const requestToTest = createSampleBuildRequestWithPatch(MockModels.iphone, MockModels.speedometer, 0); const otherRequestToTest = createSampleBuildRequest(MockModels.iphone, MockModels.speedometer); let properties = syncers[0]._propertiesForBuildRequest(requestToTest, [requestToTest]); assert.equal(properties['webkit'], '197463'); assert.equal(properties['roots'], '[{"url":"http://build.webkit.org/api/uploaded-file/456.dat"}]'); assert.equal(properties['test-custom-build'], undefined); assert.equal(properties['has-built-patch'], undefined); properties = syncers[0]._propertiesForBuildRequest(requestToTest, [requestToBuild, requestToTest]); assert.equal(properties['webkit'], '197463'); assert.equal(properties['roots'], '[{"url":"http://build.webkit.org/api/uploaded-file/456.dat"}]'); assert.equal(properties['test-custom-build'], ''); assert.equal(properties['has-built-patch'], 'true'); properties = syncers[0]._propertiesForBuildRequest(otherRequestToTest, [requestToBuild, otherRequestToTest, requestToTest]); assert.equal(properties['webkit'], '197463'); assert.equal(properties['roots'], undefined); assert.equal(properties['test-custom-build'], undefined); assert.equal(properties['has-built-patch'], undefined); }); it('should set the value for "ifBuilt" if the repository in the list appears', () => { const config = sampleiOSConfig(); config.repositoryGroups['ios-svn-webkit'] = { 'repositories': {'WebKit': {'acceptsPatch': true}, 'Shared': {}, 'iOS': {}}, 'testProperties': { 'os': {'revision': 'iOS'}, 'webkit': {'revision': 'WebKit'}, 'shared': {'revision': 'Shared'}, 'roots': {'roots': {}}, 'checkbox': {'ifBuilt': ['WebKit'], 'value': 'test-webkit'}, 'test-webkit': {'ifBuilt': ['WebKit'], 'value': true} }, 'buildProperties': { 'webkit': {'revision': 'WebKit'}, 'webkit-patch': {'patch': 'WebKit'}, 'checkbox': {'ifRepositorySet': ['WebKit'], 'value': 'build-webkit'}, 'build-webkit': {'ifRepositorySet': ['WebKit'], 'value': true}, 'shared': {'revision': 'Shared'}, }, 'acceptsRoots': true, }; const syncers = BuildbotSyncer._loadConfig(RemoteAPI, config, builderNameToIDMap()); const requestToBuild = createSampleBuildRequestWithPatch(MockModels.iphone, null, -1); const requestToTest = createSampleBuildRequestWithPatch(MockModels.iphone, MockModels.speedometer, 0); const properties = syncers[0]._propertiesForBuildRequest(requestToTest, [requestToBuild, requestToTest]); assert.equal(properties['webkit'], '197463'); assert.equal(properties['roots'], '[{"url":"http://build.webkit.org/api/uploaded-file/456.dat"}]'); assert.equal(properties['checkbox'], 'test-webkit'); assert.equal(properties['test-webkit'], true); }); it('should not set the value for "ifBuilt" if no build for the repository in the list appears', () => { const config = sampleiOSConfig(); config.repositoryGroups['ios-svn-webkit-with-owned-commit'] = { 'repositories': {'WebKit': {'acceptsPatch': true}, 'Owner Repository': {}, 'iOS': {}}, 'testProperties': { 'os': {'revision': 'iOS'}, 'webkit': {'revision': 'WebKit'}, 'owner-repo': {'revision': 'Owner Repository'}, 'roots': {'roots': {}}, 'checkbox': {'ifBuilt': ['WebKit'], 'value': 'test-webkit'}, 'test-webkit': {'ifBuilt': ['WebKit'], 'value': true} }, 'buildProperties': { 'webkit': {'revision': 'WebKit'}, 'webkit-patch': {'patch': 'WebKit'}, 'owner-repo': {'revision': 'Owner Repository'}, 'checkbox': {'ifRepositorySet': ['WebKit'], 'value': 'build-webkit'}, 'build-webkit': {'ifRepositorySet': ['WebKit'], 'value': true}, 'owned-commits': {'ownedRevisions': 'Owner Repository'} }, 'acceptsRoots': true, }; const syncers = BuildbotSyncer._loadConfig(RemoteAPI, config, builderNameToIDMap()); const requestToBuild = createSampleBuildRequestWithOwnedCommit(MockModels.iphone, null, -1); const requestToTest = createSampleBuildRequestWithOwnedCommit(MockModels.iphone, MockModels.speedometer, 0); const properties = syncers[0]._propertiesForBuildRequest(requestToTest, [requestToBuild, requestToTest]); assert.equal(properties['webkit'], '197463'); assert.equal(properties['roots'], '[{"url":"http://build.webkit.org/api/uploaded-file/456.dat"}]'); assert.equal(properties['checkbox'], undefined); assert.equal(properties['test-webkit'], undefined); }); it('should resolve "ifRepositorySet" and "requiresBuild"', () => { const config = sampleiOSConfig(); config.repositoryGroups['ios-svn-webkit-with-owned-commit'] = { 'repositories': {'WebKit': {'acceptsPatch': true}, 'Owner Repository': {}, 'iOS': {}}, 'testProperties': { 'os': {'revision': 'iOS'}, 'webkit': {'revision': 'WebKit'}, 'owner-repo': {'revision': 'Owner Repository'}, 'roots': {'roots': {}}, }, 'buildProperties': { 'webkit': {'revision': 'WebKit'}, 'webkit-patch': {'patch': 'WebKit'}, 'owner-repo': {'revision': 'Owner Repository'}, 'checkbox': {'ifRepositorySet': ['WebKit'], 'value': 'build-webkit'}, 'build-webkit': {'ifRepositorySet': ['WebKit'], 'value': true}, 'owned-commits': {'ownedRevisions': 'Owner Repository'} }, 'acceptsRoots': true, }; const syncers = BuildbotSyncer._loadConfig(RemoteAPI, config, builderNameToIDMap()); const request = createSampleBuildRequestWithOwnedCommit(MockModels.iphone, null, -1); const properties = syncers[2]._propertiesForBuildRequest(request, [request]); assert.equal(properties['webkit'], '197463'); assert.equal(properties['owner-repo'], 'owner-001'); assert.equal(properties['checkbox'], undefined); assert.equal(properties['build-webkit'], undefined); assert.deepEqual(JSON.parse(properties['owned-commits']), {'Owner Repository': [{revision: 'owned-002', repository: 'Owned Repository', ownerRevision: 'owner-001'}]}); }); it('should resolve "patch", "ifRepositorySet" and "requiresBuild"', () => { const config = sampleiOSConfig(); config.repositoryGroups['ios-svn-webkit-with-owned-commit'] = { 'repositories': {'WebKit': {'acceptsPatch': true}, 'Owner Repository': {}, 'iOS': {}}, 'testProperties': { 'os': {'revision': 'iOS'}, 'webkit': {'revision': 'WebKit'}, 'owner-repo': {'revision': 'Owner Repository'}, 'roots': {'roots': {}}, }, 'buildProperties': { 'webkit': {'revision': 'WebKit'}, 'webkit-patch': {'patch': 'WebKit'}, 'owner-repo': {'revision': 'Owner Repository'}, 'checkbox': {'ifRepositorySet': ['WebKit'], 'value': 'build-webkit'}, 'build-webkit': {'ifRepositorySet': ['WebKit'], 'value': true}, 'owned-commits': {'ownedRevisions': 'Owner Repository'} }, 'acceptsRoots': true, }; const syncers = BuildbotSyncer._loadConfig(RemoteAPI, config, builderNameToIDMap()); const request = createSampleBuildRequestWithOwnedCommitAndPatch(MockModels.iphone, null, -1); const properties = syncers[2]._propertiesForBuildRequest(request, [request]); assert.equal(properties['webkit'], '197463'); assert.equal(properties['owner-repo'], 'owner-001'); assert.equal(properties['checkbox'], 'build-webkit'); assert.equal(properties['build-webkit'], true); assert.equal(properties['webkit-patch'], 'http://build.webkit.org/api/uploaded-file/453.dat'); assert.deepEqual(JSON.parse(properties['owned-commits']), {'Owner Repository': [{revision: 'owned-002', repository: 'Owned Repository', ownerRevision: 'owner-001'}]}); }); it('should allow to build with an owned component even if no repository accepts a patch in the triggerable repository group', () => { const config = sampleiOSConfig(); config.repositoryGroups['owner-repository'] = { 'repositories': {'Owner Repository': {}}, 'testProperties': { 'owner-repo': {'revision': 'Owner Repository'}, 'roots': {'roots': {}}, }, 'buildProperties': { 'owned-commits': {'ownedRevisions': 'Owner Repository'} }, 'acceptsRoots': true, }; const syncers = BuildbotSyncer._loadConfig(RemoteAPI, config, builderNameToIDMap()); const owner111289 = CommitLog.ensureSingleton('111289', {'id': '111289', 'time': 1456931874000, 'repository': MockModels.ownerRepository, 'revision': 'owner-001'}); const owned111222 = CommitLog.ensureSingleton('111222', {'id': '111222', 'time': 1456932774000, 'repository': MockModels.ownedRepository, 'revision': 'owned-002'}); const commitSet = CommitSet.ensureSingleton('53246486', {customRoots: [], revisionItems: [{commit: owner111289}, {commit: owned111222, commitOwner: owner111289, requiresBuild: true}]}); const request = BuildRequest.ensureSingleton(`123123`, {'triggerable': MockModels.triggerable, repositoryGroup: MockModels.ownerRepositoryGroup, 'commitSet': commitSet, 'status': 'pending', 'platform': MockModels.iphone, 'test': null, 'order': -1}); const properties = syncers[2]._propertiesForBuildRequest(request, [request]); assert.deepEqual(JSON.parse(properties['owned-commits']), {'Owner Repository': [{revision: 'owned-002', repository: 'Owned Repository', ownerRevision: 'owner-001'}]}); }); it('should fail if build type build request does not have any build repository group template', () => { const config = sampleiOSConfig(); config.repositoryGroups['owner-repository'] = { 'repositories': {'Owner Repository': {}}, 'testProperties': { 'owner-repo': {'revision': 'Owner Repository'}, 'roots': {'roots': {}}, }, 'acceptsRoots': true, }; const syncers = BuildbotSyncer._loadConfig(RemoteAPI, config, builderNameToIDMap()); const owner1 = CommitLog.ensureSingleton('111289', {'id': '111289', 'time': 1456931874000, 'repository': MockModels.ownerRepository, 'revision': 'owner-001'}); const owned2 = CommitLog.ensureSingleton('111222', {'id': '111222', 'time': 1456932774000, 'repository': MockModels.ownedRepository, 'revision': 'owned-002'}); const commitSet = CommitSet.ensureSingleton('53246486', {customRoots: [], revisionItems: [{commit: owner1}, {commit: owned2, commitOwner: owner1, requiresBuild: true}]}); const request = BuildRequest.ensureSingleton(`123123`, {'triggerable': MockModels.triggerable, repositoryGroup: MockModels.ownerRepositoryGroup, 'commitSet': commitSet, 'status': 'pending', 'platform': MockModels.iphone, 'test': null, 'order': -1}); assert.throws(() => syncers[2]._propertiesForBuildRequest(request, [request]), (error) => error.code === 'ERR_ASSERTION'); }); it('should set the property for the build request id', () => { const syncers = BuildbotSyncer._loadConfig(RemoteAPI, sampleiOSConfig(), builderNameToIDMap()); const request = createSampleBuildRequest(MockModels.iphone, MockModels.speedometer); const properties = syncers[0]._propertiesForBuildRequest(request, [request]); assert.equal(properties['build_request_id'], request.id()); }); }); describe('BuildbotBuildEntry', () => { it('should create BuildbotBuildEntry for pending build', () => { let syncer = BuildbotSyncer._loadConfig(MockRemoteAPI, sampleiOSConfig(), builderNameToIDMap())[1]; const buildbotData = samplePendingBuildRequests(); const pendingEntries = buildbotData.buildrequests.map((entry) => new BuildbotBuildEntry(syncer, entry)); assert.equal(pendingEntries.length, 1); const entry = pendingEntries[0]; assert.ok(entry instanceof BuildbotBuildEntry); assert.ok(!entry.buildTag()); assert.ok(!entry.workerName()); assert.equal(entry.buildRequestId(), 16733); assert.ok(entry.isPending()); assert.ok(!entry.isInProgress()); assert.ok(!entry.hasFinished()); assert.equal(entry.url(), 'http://build.webkit.org/#/buildrequests/17'); assert.equal(entry.statusDescription(), null); }); it('should create BuildbotBuildEntry for in-progress build', () => { let syncer = BuildbotSyncer._loadConfig(MockRemoteAPI, sampleiOSConfig(), builderNameToIDMap())[1]; const buildbotData = sampleInProgressBuild(); const entries = buildbotData.builds.map((entry) => new BuildbotBuildEntry(syncer, entry)); assert.equal(entries.length, 1); const entry = entries[0]; assert.ok(entry instanceof BuildbotBuildEntry); assert.equal(entry.buildTag(), 614); assert.equal(entry.workerName(), 'ABTest-iPad-0'); assert.equal(entry.buildRequestId(), 16733); assert.ok(!entry.isPending()); assert.ok(entry.isInProgress()); assert.ok(!entry.hasFinished()); assert.equal(entry.url(), 'http://build.webkit.org/#/builders/102/builds/614'); assert.equal(entry.statusDescription(), 'building'); }); it('should create BuildbotBuildEntry for finished build', () => { let syncer = BuildbotSyncer._loadConfig(MockRemoteAPI, sampleiOSConfig(), builderNameToIDMap())[1]; const buildbotData = sampleFinishedBuild(); const entries = buildbotData.builds.map((entry) => new BuildbotBuildEntry(syncer, entry)); assert.deepEqual(entries.length, 1); const entry = entries[0]; assert.ok(entry instanceof BuildbotBuildEntry); assert.equal(entry.buildTag(), 1755); assert.equal(entry.workerName(), 'ABTest-iPad-0'); assert.equal(entry.buildRequestId(), 18935); assert.ok(!entry.isPending()); assert.ok(!entry.isInProgress()); assert.ok(entry.hasFinished()); assert.equal(entry.url(), 'http://build.webkit.org/#/builders/102/builds/1755'); assert.equal(entry.statusDescription(), null); }); it('should create BuildbotBuildEntry for mix of in-progress and finished builds', () => { let syncer = BuildbotSyncer._loadConfig(MockRemoteAPI, sampleiOSConfig(), builderNameToIDMap())[1]; const buildbotData = {'builds': [sampleInProgressBuildData(), sampleFinishedBuildData()]}; const entries = buildbotData.builds.map((entry) => new BuildbotBuildEntry(syncer, entry)); assert.deepEqual(entries.length, 2); let entry = entries[0]; assert.ok(entry instanceof BuildbotBuildEntry); assert.equal(entry.buildTag(), 614); assert.equal(entry.workerName(), 'ABTest-iPad-0'); assert.equal(entry.buildRequestId(), 16733); assert.ok(!entry.isPending()); assert.ok(entry.isInProgress()); assert.ok(!entry.hasFinished()); assert.equal(entry.url(), 'http://build.webkit.org/#/builders/102/builds/614'); assert.equal(entry.statusDescription(), 'building'); entry = entries[1]; assert.ok(entry instanceof BuildbotBuildEntry); assert.equal(entry.buildTag(), 1755); assert.equal(entry.workerName(), 'ABTest-iPad-0'); assert.equal(entry.buildRequestId(), 18935); assert.ok(!entry.isPending()); assert.ok(!entry.isInProgress()); assert.ok(entry.hasFinished()); assert.equal(entry.url(), 'http://build.webkit.org/#/builders/102/builds/1755'); assert.equal(entry.statusDescription(), null); }); }); describe('_pullRecentBuilds()', () => { it('should not fetch recent builds when count is zero', async () => { const syncer = BuildbotSyncer._loadConfig(MockRemoteAPI, sampleiOSConfig(), builderNameToIDMap())[1]; const promise = syncer._pullRecentBuilds(0); assert.equal(requests.length, 0); const content = await promise; assert.deepEqual(content, []); }); it('should pull the right number of recent builds', () => { const syncer = BuildbotSyncer._loadConfig(MockRemoteAPI, sampleiOSConfig(), builderNameToIDMap())[1]; syncer._pullRecentBuilds(12); assert.equal(requests.length, 1); assert.equal(requests[0].url, '/api/v2/builders/102/builds?limit=12&order=-number&property=*'); }); it('should handle unexpected error while fetching recent builds', async () => { const syncer = BuildbotSyncer._loadConfig(MockRemoteAPI, sampleiOSConfig(), builderNameToIDMap())[1]; const promise = syncer._pullRecentBuilds(2); assert.equal(requests.length, 1); assert.equal(requests[0].url, '/api/v2/builders/102/builds?limit=2&order=-number&property=*'); requests[0].resolve({'error': 'Unexpected error'}); const content = await promise; assert.deepEqual(content, []); }); it('should create BuildbotBuildEntry after fetching recent builds', async () => { const syncer = BuildbotSyncer._loadConfig(MockRemoteAPI, sampleiOSConfig(), builderNameToIDMap())[1]; const promise = syncer._pullRecentBuilds(2); assert.equal(requests.length, 1); assert.equal(requests[0].url, '/api/v2/builders/102/builds?limit=2&order=-number&property=*'); requests[0].resolve({'builds': [sampleFinishedBuildData(), sampleInProgressBuildData()]}); const entries = await promise; assert.deepEqual(entries.length, 2); let entry = entries[0]; assert.ok(entry instanceof BuildbotBuildEntry); assert.equal(entry.buildTag(), 1755); assert.equal(entry.workerName(), 'ABTest-iPad-0'); assert.equal(entry.buildRequestId(), 18935); assert.ok(!entry.isPending()); assert.ok(!entry.isInProgress()); assert.ok(entry.hasFinished()); assert.equal(entry.url(), 'http://build.webkit.org/#/builders/102/builds/1755'); entry = entries[1]; assert.ok(entry instanceof BuildbotBuildEntry); assert.equal(entry.buildTag(), 614); assert.equal(entry.workerName(), 'ABTest-iPad-0'); assert.equal(entry.buildRequestId(), 16733); assert.ok(!entry.isPending()); assert.ok(entry.isInProgress()); assert.ok(!entry.hasFinished()); assert.equal(entry.url(), 'http://build.webkit.org/#/builders/102/builds/614'); }); }); describe('pullBuildbot', () => { it('should fetch pending builds from the right URL', () => { let syncer = BuildbotSyncer._loadConfig(MockRemoteAPI, sampleiOSConfig(), builderNameToIDMap())[1]; assert.equal(syncer.builderName(), 'ABTest-iPad-RunBenchmark-Tests'); let expectedURL = '/api/v2/builders/102/buildrequests?complete=false&claimed=false&property=*'; assert.equal(syncer.pathForPendingBuilds(), expectedURL); syncer.pullBuildbot(); assert.equal(requests.length, 1); assert.equal(requests[0].url, expectedURL); }); it('should fetch recent builds once pending builds have been fetched', () => { let syncer = BuildbotSyncer._loadConfig(MockRemoteAPI, sampleiOSConfig(), builderNameToIDMap())[1]; assert.equal(syncer.builderName(), 'ABTest-iPad-RunBenchmark-Tests'); syncer.pullBuildbot(1); assert.equal(requests.length, 1); assert.equal(requests[0].url, '/api/v2/builders/102/buildrequests?complete=false&claimed=false&property=*'); requests[0].resolve([]); return MockRemoteAPI.waitForRequest().then(() => { assert.equal(requests.length, 2); assert.equal(requests[1].url, '/api/v2/builders/102/builds?limit=1&order=-number&property=*'); }); }); it('should fetch the right number of recent builds', () => { let syncer = BuildbotSyncer._loadConfig(MockRemoteAPI, sampleiOSConfig(), builderNameToIDMap())[1]; syncer.pullBuildbot(3); assert.equal(requests.length, 1); assert.equal(requests[0].url, '/api/v2/builders/102/buildrequests?complete=false&claimed=false&property=*'); requests[0].resolve([]); return MockRemoteAPI.waitForRequest().then(() => { assert.equal(requests.length, 2); assert.equal(requests[1].url, '/api/v2/builders/102/builds?limit=3&order=-number&property=*'); }); }); it('should create BuildbotBuildEntry for pending builds', () => { let syncer = BuildbotSyncer._loadConfig(MockRemoteAPI, sampleiOSConfig(), builderNameToIDMap())[1]; let promise = syncer.pullBuildbot(); requests[0].resolve(samplePendingBuildRequests()); return promise.then((entries) => { assert.equal(entries.length, 1); let entry = entries[0]; assert.ok(entry instanceof BuildbotBuildEntry); assert.ok(!entry.buildTag()); assert.ok(!entry.workerName()); assert.equal(entry.buildRequestId(), 16733); assert.ok(entry.isPending()); assert.ok(!entry.isInProgress()); assert.ok(!entry.hasFinished()); assert.equal(entry.url(), 'http://build.webkit.org/#/buildrequests/17'); }); }); it('should create BuildbotBuildEntry for in-progress builds', () => { let syncer = BuildbotSyncer._loadConfig(MockRemoteAPI, sampleiOSConfig(), builderNameToIDMap())[1]; let promise = syncer.pullBuildbot(1); assert.equal(requests.length, 1); requests[0].resolve([]); return MockRemoteAPI.waitForRequest().then(() => { assert.equal(requests.length, 2); requests[1].resolve(sampleInProgressBuild()); return promise; }).then((entries) => { assert.equal(entries.length, 1); let entry = entries[0]; assert.ok(entry instanceof BuildbotBuildEntry); assert.equal(entry.buildTag(), 614); assert.equal(entry.workerName(), 'ABTest-iPad-0'); assert.equal(entry.buildRequestId(), 16733); assert.ok(!entry.isPending()); assert.ok(entry.isInProgress()); assert.ok(!entry.hasFinished()); assert.equal(entry.url(), 'http://build.webkit.org/#/builders/102/builds/614'); }); }); it('should create BuildbotBuildEntry for finished builds', () => { let syncer = BuildbotSyncer._loadConfig(MockRemoteAPI, sampleiOSConfig(), builderNameToIDMap())[1]; let promise = syncer.pullBuildbot(1); assert.equal(requests.length, 1); requests[0].resolve([]); return MockRemoteAPI.waitForRequest().then(() => { assert.equal(requests.length, 2); requests[1].resolve(sampleFinishedBuild()); return promise; }).then((entries) => { assert.deepEqual(entries.length, 1); let entry = entries[0]; assert.ok(entry instanceof BuildbotBuildEntry); assert.equal(entry.buildTag(), 1755); assert.equal(entry.workerName(), 'ABTest-iPad-0'); assert.equal(entry.buildRequestId(), 18935); assert.ok(!entry.isPending()); assert.ok(!entry.isInProgress()); assert.ok(entry.hasFinished()); assert.equal(entry.url(), 'http://build.webkit.org/#/builders/102/builds/1755'); }); }); it('should create BuildbotBuildEntry for mixed pending, in-progress, finished, and missing builds', () => { let syncer = BuildbotSyncer._loadConfig(MockRemoteAPI, sampleiOSConfig(), builderNameToIDMap())[1]; let promise = syncer.pullBuildbot(5); assert.equal(requests.length, 1); requests[0].resolve(samplePendingBuildRequests(123)); return MockRemoteAPI.waitForRequest().then(() => { assert.equal(requests.length, 2); requests[1].resolve({'builds': [sampleFinishedBuildData(), sampleInProgressBuildData()]}); return promise; }).then((entries) => { assert.deepEqual(entries.length, 3); let entry = entries[0]; assert.ok(entry instanceof BuildbotBuildEntry); assert.equal(entry.buildTag(), null); assert.equal(entry.workerName(), null); assert.equal(entry.buildRequestId(), 123); assert.ok(entry.isPending()); assert.ok(!entry.isInProgress()); assert.ok(!entry.hasFinished()); assert.equal(entry.url(), 'http://build.webkit.org/#/buildrequests/17'); entry = entries[1]; assert.ok(entry instanceof BuildbotBuildEntry); assert.equal(entry.buildTag(), 614); assert.equal(entry.workerName(), 'ABTest-iPad-0'); assert.equal(entry.buildRequestId(), 16733); assert.ok(!entry.isPending()); assert.ok(entry.isInProgress()); assert.ok(!entry.hasFinished()); assert.equal(entry.url(), 'http://build.webkit.org/#/builders/102/builds/614'); entry = entries[2]; assert.ok(entry instanceof BuildbotBuildEntry); assert.equal(entry.buildTag(), 1755); assert.equal(entry.workerName(), 'ABTest-iPad-0'); assert.equal(entry.buildRequestId(), 18935); assert.ok(!entry.isPending()); assert.ok(!entry.isInProgress()); assert.ok(entry.hasFinished()); assert.equal(entry.url(), 'http://build.webkit.org/#/builders/102/builds/1755'); }); }); it('should sort BuildbotBuildEntry by order', () => { let syncer = BuildbotSyncer._loadConfig(MockRemoteAPI, sampleiOSConfig(), builderNameToIDMap())[1]; let promise = syncer.pullBuildbot(5); assert.equal(requests.length, 1); requests[0].resolve({"buildrequests": [samplePendingBuildRequestData(456, 2), samplePendingBuildRequestData(123, 1)]}); return MockRemoteAPI.waitForRequest().then(() => { assert.equal(requests.length, 2); requests[1].resolve({'builds': [sampleFinishedBuildData(), sampleInProgressBuildData()]}); return promise; }).then((entries) => { assert.deepEqual(entries.length, 4); let entry = entries[0]; assert.ok(entry instanceof BuildbotBuildEntry); assert.equal(entry.buildTag(), null); assert.equal(entry.workerName(), null); assert.equal(entry.buildRequestId(), 123); assert.ok(entry.isPending()); assert.ok(!entry.isInProgress()); assert.ok(!entry.hasFinished()); assert.equal(entry.url(), 'http://build.webkit.org/#/buildrequests/17'); entry = entries[1]; assert.ok(entry instanceof BuildbotBuildEntry); assert.equal(entry.buildTag(), null); assert.equal(entry.workerName(), null); assert.equal(entry.buildRequestId(), 456); assert.ok(entry.isPending()); assert.ok(!entry.isInProgress()); assert.ok(!entry.hasFinished()); assert.equal(entry.url(), 'http://build.webkit.org/#/buildrequests/17'); entry = entries[2]; assert.ok(entry instanceof BuildbotBuildEntry); assert.equal(entry.buildTag(), 614); assert.equal(entry.workerName(), 'ABTest-iPad-0'); assert.equal(entry.buildRequestId(), 16733); assert.ok(!entry.isPending()); assert.ok(entry.isInProgress()); assert.ok(!entry.hasFinished()); assert.equal(entry.url(), 'http://build.webkit.org/#/builders/102/builds/614'); entry = entries[3]; assert.ok(entry instanceof BuildbotBuildEntry); assert.equal(entry.buildTag(), 1755); assert.equal(entry.workerName(), 'ABTest-iPad-0'); assert.equal(entry.buildRequestId(), 18935); assert.ok(!entry.isPending()); assert.ok(!entry.isInProgress()); assert.ok(entry.hasFinished()); assert.equal(entry.url(), 'http://build.webkit.org/#/builders/102/builds/1755'); }); }); it('should override BuildbotBuildEntry for pending builds by in-progress builds', () => { let syncer = BuildbotSyncer._loadConfig(MockRemoteAPI, sampleiOSConfig(), builderNameToIDMap())[1]; let promise = syncer.pullBuildbot(5); assert.equal(requests.length, 1); requests[0].resolve(samplePendingBuildRequests()); return MockRemoteAPI.waitForRequest().then(() => { assert.equal(requests.length, 2); requests[1].resolve(sampleInProgressBuild()); return promise; }).then((entries) => { assert.equal(entries.length, 1); let entry = entries[0]; assert.ok(entry instanceof BuildbotBuildEntry); assert.equal(entry.buildTag(), 614); assert.equal(entry.workerName(), 'ABTest-iPad-0'); assert.equal(entry.buildRequestId(), 16733); assert.ok(!entry.isPending()); assert.ok(entry.isInProgress()); assert.ok(!entry.hasFinished()); assert.equal(entry.url(), 'http://build.webkit.org/#/builders/102/builds/614'); }); }); it('should override BuildbotBuildEntry for pending builds by finished builds', () => { let syncer = BuildbotSyncer._loadConfig(MockRemoteAPI, sampleiOSConfig(), builderNameToIDMap())[1]; let promise = syncer.pullBuildbot(5); assert.equal(requests.length, 1); requests[0].resolve(samplePendingBuildRequests()); return MockRemoteAPI.waitForRequest().then(() => { assert.equal(requests.length, 2); requests[1].resolve(sampleFinishedBuild(16733)); return promise; }).then((entries) => { assert.equal(entries.length, 1); let entry = entries[0]; assert.ok(entry instanceof BuildbotBuildEntry); assert.equal(entry.buildTag(), 1755); assert.equal(entry.workerName(), 'ABTest-iPad-0'); assert.equal(entry.buildRequestId(), 16733); assert.ok(!entry.isPending()); assert.ok(!entry.isInProgress()); assert.ok(entry.hasFinished()); assert.equal(entry.url(), 'http://build.webkit.org/#/builders/102/builds/1755'); }); }); }); describe('scheduleBuildOnBuildbot', () => { it('should schedule a build request on Buildbot', async () => { const syncer = BuildbotSyncer._loadConfig(MockRemoteAPI, sampleiOSConfig(), builderNameToIDMap())[0]; const request = createSampleBuildRequest(MockModels.iphone, MockModels.speedometer); const properties = syncer._propertiesForBuildRequest(request, [request]); const promise = syncer.scheduleBuildOnBuildbot(properties); assert.equal(requests.length, 1); assert.equal(requests[0].method, 'POST'); assert.equal(requests[0].url, '/api/v2/forceschedulers/ABTest-iPhone-RunBenchmark-Tests-ForceScheduler'); requests[0].resolve(); await promise; assert.deepEqual(requests[0].data, { 'id': '16733-' + MockModels.iphone.id(), 'jsonrpc': '2.0', 'method': 'force', 'params': { 'build_request_id': '16733-' + MockModels.iphone.id(), 'desired_image': '13A452', 'opensource': '197463', 'forcescheduler': 'ABTest-iPhone-RunBenchmark-Tests-ForceScheduler', 'test_name': 'speedometer' } }); }); }); describe('scheduleRequest', () => { it('should schedule a build request on a specified worker', () => { let syncer = BuildbotSyncer._loadConfig(MockRemoteAPI, sampleiOSConfig(), builderNameToIDMap())[0]; const waitForRequest = MockRemoteAPI.waitForRequest(); const request = createSampleBuildRequest(MockModels.iphone, MockModels.speedometer); syncer.scheduleRequest(request, [request], 'some-worker'); return waitForRequest.then(() => { assert.equal(requests.length, 1); assert.equal(requests[0].url, '/api/v2/forceschedulers/ABTest-iPhone-RunBenchmark-Tests-ForceScheduler'); assert.equal(requests[0].method, 'POST'); assert.deepEqual(requests[0].data, { 'id': '16733-' + MockModels.iphone.id(), 'jsonrpc': '2.0', 'method': 'force', 'params': { 'build_request_id': '16733-' + MockModels.iphone.id(), 'desired_image': '13A452', 'opensource': '197463', 'forcescheduler': 'ABTest-iPhone-RunBenchmark-Tests-ForceScheduler', 'workername': 'some-worker', 'test_name': 'speedometer' } }); }); }); }); describe('scheduleRequestInGroupIfAvailable', () => { function pullBuildbotWithAssertion(syncer, pendingBuilds, inProgressAndFinishedBuilds) { const promise = syncer.pullBuildbot(5); assert.equal(requests.length, 1); requests[0].resolve(pendingBuilds); return MockRemoteAPI.waitForRequest().then(() => { assert.equal(requests.length, 2); requests[1].resolve(inProgressAndFinishedBuilds); requests.length = 0; return promise; }); } it('should schedule a build if builder has no builds if workerList is not specified', () => { let syncer = BuildbotSyncer._loadConfig(MockRemoteAPI, smallConfiguration(), builderNameToIDMap())[0]; return pullBuildbotWithAssertion(syncer, {}, {}).then(() => { const request = createSampleBuildRequest(MockModels.somePlatform, MockModels.someTest); syncer.scheduleRequestInGroupIfAvailable(request, [request]); assert.equal(requests.length, 1); assert.equal(requests[0].url, '/api/v2/forceschedulers/some-builder-ForceScheduler'); assert.equal(requests[0].method, 'POST'); assert.deepEqual(requests[0].data, {id: '16733-' + MockModels.somePlatform.id(), 'jsonrpc': '2.0', 'method': 'force', 'params': {id: '16733-' + MockModels.somePlatform.id(), 'forcescheduler': 'some-builder-ForceScheduler', 'os': '13A452', 'wk': '197463'}}); }); }); it('should schedule a build if builder only has finished builds if workerList is not specified', () => { let syncer = BuildbotSyncer._loadConfig(MockRemoteAPI, smallConfiguration(), builderNameToIDMap())[0]; return pullBuildbotWithAssertion(syncer, {}, smallFinishedBuild()).then(() => { const request = createSampleBuildRequest(MockModels.somePlatform, MockModels.someTest); syncer.scheduleRequestInGroupIfAvailable(request, [request]); assert.equal(requests.length, 1); assert.equal(requests[0].url, '/api/v2/forceschedulers/some-builder-ForceScheduler'); assert.equal(requests[0].method, 'POST'); assert.deepEqual(requests[0].data, {id: '16733-' + MockModels.somePlatform.id(), 'jsonrpc': '2.0', 'method': 'force', 'params': {id: '16733-' + MockModels.somePlatform.id(), 'forcescheduler': 'some-builder-ForceScheduler', 'os': '13A452', 'wk': '197463'}}); }); }); it('should not schedule a build if builder has a pending build if workerList is not specified', () => { let syncer = BuildbotSyncer._loadConfig(MockRemoteAPI, smallConfiguration(), builderNameToIDMap())[0]; return pullBuildbotWithAssertion(syncer, smallPendingBuild(), {}).then(() => { syncer.scheduleRequestInGroupIfAvailable(createSampleBuildRequest(MockModels.somePlatform, MockModels.someTest)); assert.equal(requests.length, 0); }); }); it('should schedule a build if builder does not have pending or completed builds on the matching worker', () => { let syncer = BuildbotSyncer._loadConfig(MockRemoteAPI, sampleiOSConfig(), builderNameToIDMap())[0]; return pullBuildbotWithAssertion(syncer, {}, {}).then(() => { const request = createSampleBuildRequest(MockModels.iphone, MockModels.speedometer); syncer.scheduleRequestInGroupIfAvailable(request, [request], null); assert.equal(requests.length, 1); assert.equal(requests[0].url, '/api/v2/forceschedulers/ABTest-iPhone-RunBenchmark-Tests-ForceScheduler'); assert.equal(requests[0].method, 'POST'); }); }); it('should schedule a build if builder only has finished builds on the matching worker', () => { let syncer = BuildbotSyncer._loadConfig(MockRemoteAPI, sampleiOSConfig(), builderNameToIDMap())[1]; pullBuildbotWithAssertion(syncer, {}, sampleFinishedBuild()).then(() => { const request = createSampleBuildRequest(MockModels.ipad, MockModels.speedometer); syncer.scheduleRequestInGroupIfAvailable(request, [request], null); assert.equal(requests.length, 1); assert.equal(requests[0].url, '/api/v2/forceschedulers/ABTest-iPad-RunBenchmark-Tests-ForceScheduler'); assert.equal(requests[0].method, 'POST'); }); }); it('should not schedule a build if builder has a pending build on the maching worker', () => { let syncer = BuildbotSyncer._loadConfig(MockRemoteAPI, sampleiOSConfig(), builderNameToIDMap())[1]; pullBuildbotWithAssertion(syncer, samplePendingBuildRequests(), {}).then(() => { const request = createSampleBuildRequest(MockModels.ipad, MockModels.speedometer); syncer.scheduleRequestInGroupIfAvailable(request, [request], null); assert.equal(requests.length, 0); }); }); it('should schedule a build if builder only has a pending build on a non-maching worker', () => { let syncer = BuildbotSyncer._loadConfig(MockRemoteAPI, sampleiOSConfig(), builderNameToIDMap())[1]; return pullBuildbotWithAssertion(syncer, samplePendingBuildRequests(1, 1, 'another-worker'), {}).then(() => { const request = createSampleBuildRequest(MockModels.ipad, MockModels.speedometer); syncer.scheduleRequestInGroupIfAvailable(request, [request], null); assert.equal(requests.length, 1); }); }); it('should schedule a build if builder only has an in-progress build on the matching worker', () => { let syncer = BuildbotSyncer._loadConfig(MockRemoteAPI, sampleiOSConfig(), builderNameToIDMap())[1]; return pullBuildbotWithAssertion(syncer, {}, sampleInProgressBuild()).then(() => { const request = createSampleBuildRequest(MockModels.ipad, MockModels.speedometer); syncer.scheduleRequestInGroupIfAvailable(request, [request], null); assert.equal(requests.length, 1); }); }); it('should schedule a build if builder has an in-progress build on another worker', () => { let syncer = BuildbotSyncer._loadConfig(MockRemoteAPI, sampleiOSConfig(), builderNameToIDMap())[1]; return pullBuildbotWithAssertion(syncer, {}, sampleInProgressBuild('other-worker')).then(() => { const request = createSampleBuildRequest(MockModels.ipad, MockModels.speedometer); syncer.scheduleRequestInGroupIfAvailable(request, [request], null); assert.equal(requests.length, 1); }); }); it('should not schedule a build if the request does not match any configuration', () => { let syncer = BuildbotSyncer._loadConfig(MockRemoteAPI, sampleiOSConfig(), builderNameToIDMap())[0]; return pullBuildbotWithAssertion(syncer, {}, {}).then(() => { const request = createSampleBuildRequest(MockModels.ipad, MockModels.speedometer); syncer.scheduleRequestInGroupIfAvailable(request, [request], null); assert.equal(requests.length, 0); }); }); it('should not schedule a build if a new request had been submitted to the same worker', (done) => { let syncer = BuildbotSyncer._loadConfig(MockRemoteAPI, sampleiOSConfig(), builderNameToIDMap())[1]; pullBuildbotWithAssertion(syncer, {}, {}).then(() => { let request = createSampleBuildRequest(MockModels.ipad, MockModels.speedometer); syncer.scheduleRequest(request, [request], 'ABTest-iPad-0'); request = createSampleBuildRequest(MockModels.ipad, MockModels.speedometer); syncer.scheduleRequest(request, [request], 'ABTest-iPad-1'); }).then(() => { assert.equal(requests.length, 2); const request = createSampleBuildRequest(MockModels.ipad, MockModels.speedometer); syncer.scheduleRequestInGroupIfAvailable(request, [request], null); }).then(() => { assert.equal(requests.length, 2); done(); }).catch(done); }); it('should schedule a build if a new request had been submitted to another worker', () => { let syncer = BuildbotSyncer._loadConfig(MockRemoteAPI, sampleiOSConfig(), builderNameToIDMap())[1]; return pullBuildbotWithAssertion(syncer, {}, {}).then(() => { let request = createSampleBuildRequest(MockModels.ipad, MockModels.speedometer); syncer.scheduleRequest(request, [request], 'ABTest-iPad-0'); assert.equal(requests.length, 1); request = createSampleBuildRequest(MockModels.ipad, MockModels.speedometer) syncer.scheduleRequestInGroupIfAvailable(request, [request], 'ABTest-iPad-1'); assert.equal(requests.length, 2); }); }); it('should not schedule a build if a new request had been submitted to the same builder without workerList', () => { let syncer = BuildbotSyncer._loadConfig(MockRemoteAPI, smallConfiguration(), builderNameToIDMap())[0]; return pullBuildbotWithAssertion(syncer, {}, {}).then(() => { let request = createSampleBuildRequest(MockModels.somePlatform, MockModels.someTest); syncer.scheduleRequest(request, [request], null); assert.equal(requests.length, 1); request = createSampleBuildRequest(MockModels.somePlatform, MockModels.someTest); syncer.scheduleRequestInGroupIfAvailable(request, [request], null); assert.equal(requests.length, 1); }); }); }); });