Extend 'ifBuilt' config key to set property based on whether certain repositories...
[WebKit-https.git] / Websites / perf.webkit.org / unit-tests / buildbot-syncer-tests.js
1 'use strict';
2
3 let assert = require('assert');
4
5 require('../tools/js/v3-models.js');
6 let MockRemoteAPI = require('./resources/mock-remote-api.js').MockRemoteAPI;
7 let MockModels = require('./resources/mock-v3-models.js').MockModels;
8
9 let BuildbotBuildEntry = require('../tools/js/buildbot-syncer.js').BuildbotBuildEntry;
10 let BuildbotSyncer = require('../tools/js/buildbot-syncer.js').BuildbotSyncer;
11
12 function sampleiOSConfig()
13 {
14     return {
15         'slaveArgument': 'slavename',
16         'buildRequestArgument': 'build_request_id',
17         'repositoryGroups': {
18             'ios-svn-webkit': {
19                 'repositories': {'WebKit': {}, 'iOS': {}},
20                 'testProperties': {
21                     'desired_image': {'revision': 'iOS'},
22                     'opensource': {'revision': 'WebKit'},
23                 }
24             }
25         },
26         'types': {
27             'speedometer': {
28                 'test': ['Speedometer'],
29                 'properties': {'test_name': 'speedometer'}
30             },
31             'jetstream': {
32                 'test': ['JetStream'],
33                 'properties': {'test_name': 'jetstream'}
34             },
35             'dromaeo-dom': {
36                 'test': ['Dromaeo', 'DOM Core Tests'],
37                 'properties': {'tests': 'dromaeo-dom'}
38             },
39         },
40         'builders': {
41             'iPhone-bench': {
42                 'builder': 'ABTest-iPhone-RunBenchmark-Tests',
43                 'properties': {'forcescheduler': 'ABTest-iPhone-RunBenchmark-Tests-ForceScheduler'},
44                 'slaveList': ['ABTest-iPhone-0'],
45             },
46             'iPad-bench': {
47                 'builder': 'ABTest-iPad-RunBenchmark-Tests',
48                 'properties': {'forcescheduler': 'ABTest-iPad-RunBenchmark-Tests-ForceScheduler'},
49                 'slaveList': ['ABTest-iPad-0', 'ABTest-iPad-1'],
50             },
51             'iOS-builder': {
52                 'builder': 'ABTest-iOS-Builder',
53                 'properties': {'forcescheduler': 'ABTest-Builder-ForceScheduler'},
54             },
55         },
56         'buildConfigurations': [
57             {'builders': ['iOS-builder'], 'platforms': ['iPhone', 'iPad']},
58         ],
59         'testConfigurations': [
60             {'builders': ['iPhone-bench'], 'types': ['speedometer', 'jetstream', 'dromaeo-dom'], 'platforms': ['iPhone']},
61             {'builders': ['iPad-bench'], 'types': ['speedometer', 'jetstream'], 'platforms': ['iPad']},
62         ]
63     };
64 }
65
66 function sampleiOSConfigWithExpansions()
67 {
68     return {
69         "triggerableName": "build-webkit-ios",
70         "buildRequestArgument": "build-request-id",
71         "repositoryGroups": { },
72         "types": {
73             "iphone-plt": {
74                 "test": ["PLT-iPhone"],
75                 "properties": {"test_name": "plt"}
76             },
77             "ipad-plt": {
78                 "test": ["PLT-iPad"],
79                 "properties": {"test_name": "plt"}
80             },
81             "speedometer": {
82                 "test": ["Speedometer"],
83                 "properties": {"tests": "speedometer"}
84             },
85         },
86         "builders": {
87             "iphone": {
88                 "builder": "iPhone AB Tests",
89                 "properties": {"forcescheduler": "force-iphone-ab-tests"},
90             },
91             "iphone-2": {
92                 "builder": "iPhone 2 AB Tests",
93                 "properties": {"forcescheduler": "force-iphone-2-ab-tests"},
94             },
95             "ipad": {
96                 "builder": "iPad AB Tests",
97                 "properties": {"forcescheduler": "force-ipad-ab-tests"},
98             },
99         },
100         "testConfigurations": [
101             {
102                 "builders": ["iphone", "iphone-2"],
103                 "platforms": ["iPhone", "iOS 10 iPhone"],
104                 "types": ["iphone-plt", "speedometer"],
105             },
106             {
107                 "builders": ["ipad"],
108                 "platforms": ["iPad"],
109                 "types": ["ipad-plt", "speedometer"],
110             },
111         ]
112     }
113 }
114
115 function smallConfiguration()
116 {
117     return {
118         'buildRequestArgument': 'id',
119         'repositoryGroups': {
120             'ios-svn-webkit': {
121                 'repositories': {'iOS': {}, 'WebKit': {}},
122                 'testProperties': {
123                     'os': {'revision': 'iOS'},
124                     'wk': {'revision': 'WebKit'}
125                 }
126             }
127         },
128         'types': {
129             'some-test': {
130                 'test': ['Some test'],
131             }
132         },
133         'builders': {
134             'some-builder': {
135                 'builder': 'some builder',
136             }
137         },
138         'testConfigurations': [{
139             'builders': ['some-builder'],
140             'platforms': ['Some platform'],
141             'types': ['some-test'],
142         }]
143     };
144 }
145
146 function builderNameToIDMap()
147 {
148     return {
149         'some builder' : '100',
150         'ABTest-iPhone-RunBenchmark-Tests': '101',
151         'ABTest-iPad-RunBenchmark-Tests': '102',
152         'ABTest-iOS-Builder': '103',
153         'iPhone AB Tests' : '104',
154         'iPhone 2 AB Tests': '105',
155         'iPad AB Tests': '106'
156     }    
157 }
158
159 function smallPendingBuild()
160 {
161     return {
162         'builderName': 'some builder',
163         'builds': [],
164         'properties': [],
165         'source': {
166             'branch': '',
167             'changes': [],
168             'codebase': 'WebKit',
169             'hasPatch': false,
170             'project': '',
171             'repository': '',
172             'revision': ''
173         },
174     };
175 }
176
177 function smallInProgressBuild()
178 {
179     return {
180         'builderName': 'some builder',
181         'builds': [],
182         'properties': [],
183         'currentStep': { },
184         'eta': 123,
185         'number': 456,
186         'source': {
187             'branch': '',
188             'changes': [],
189             'codebase': 'WebKit',
190             'hasPatch': false,
191             'project': '',
192             'repository': '',
193             'revision': ''
194         },
195     };
196 }
197
198 function smallFinishedBuild()
199 {
200     return {
201         'builderName': 'some builder',
202         'builds': [],
203         'properties': [],
204         'currentStep': null,
205         'eta': null,
206         'number': 789,
207         'source': {
208             'branch': '',
209             'changes': [],
210             'codebase': 'WebKit',
211             'hasPatch': false,
212             'project': '',
213             'repository': '',
214             'revision': ''
215         },
216         'times': [0, 1],
217     };
218 }
219
220 function createSampleBuildRequest(platform, test)
221 {
222     assert(platform instanceof Platform);
223     assert(test instanceof Test);
224
225     const webkit197463 = CommitLog.ensureSingleton('111127', {'id': '111127', 'time': 1456955807334, 'repository': MockModels.webkit, 'revision': '197463'});
226     const shared111237 = CommitLog.ensureSingleton('111237', {'id': '111237', 'time': 1456931874000, 'repository': MockModels.sharedRepository, 'revision': '80229'});
227     const ios13A452 = CommitLog.ensureSingleton('88930', {'id': '88930', 'time': 0, 'repository': MockModels.ios, 'revision': '13A452'});
228
229     const commitSet = CommitSet.ensureSingleton('4197', {customRoots: [], revisionItems: [{commit: webkit197463}, {commit: shared111237}, {commit: ios13A452}]});
230
231     return BuildRequest.ensureSingleton('16733-' + platform.id(), {'triggerable': MockModels.triggerable,
232         repositoryGroup: MockModels.svnRepositoryGroup,
233         'commitSet': commitSet, 'status': 'pending', 'platform': platform, 'test': test});
234 }
235
236 function createSampleBuildRequestWithPatch(platform, test, order)
237 {
238     assert(platform instanceof Platform);
239     assert(!test || test instanceof Test);
240
241     const webkit197463 = CommitLog.ensureSingleton('111127', {'id': '111127', 'time': 1456955807334, 'repository': MockModels.webkit, 'revision': '197463'});
242     const shared111237 = CommitLog.ensureSingleton('111237', {'id': '111237', 'time': 1456931874000, 'repository': MockModels.sharedRepository, 'revision': '80229'});
243     const ios13A452 = CommitLog.ensureSingleton('88930', {'id': '88930', 'time': 0, 'repository': MockModels.ios, 'revision': '13A452'});
244
245     const patch = new UploadedFile(453, {'createdAt': new Date('2017-05-01T19:16:53Z'), 'filename': 'patch.dat', 'extension': '.dat', 'author': 'some user',
246         size: 534637, sha256: '169463c8125e07c577110fe144ecd63942eb9472d438fc0014f474245e5df8a1'});
247
248     const root = new UploadedFile(456, {'createdAt': new Date('2017-05-01T21:03:27Z'), 'filename': 'root.dat', 'extension': '.dat', 'author': 'some user',
249         size: 16452234, sha256: '03eed7a8494ab8794c44b7d4308e55448fc56f4d6c175809ba968f78f656d58d'});
250
251     const commitSet = CommitSet.ensureSingleton('53246456', {customRoots: [root], revisionItems: [{commit: webkit197463, patch, requiresBuild: true}, {commit: shared111237}, {commit: ios13A452}]});
252
253     return BuildRequest.ensureSingleton(`6345645376-${order}`, {'triggerable': MockModels.triggerable,
254         repositoryGroup: MockModels.svnRepositoryGroup,
255         'commitSet': commitSet, 'status': 'pending', 'platform': platform, 'test': test, 'order': order});
256 }
257
258 function createSampleBuildRequestWithOwnedCommit(platform, test, order)
259 {
260     assert(platform instanceof Platform);
261     assert(!test || test instanceof Test);
262
263     const webkit197463 = CommitLog.ensureSingleton('111127', {'id': '111127', 'time': 1456955807334, 'repository': MockModels.webkit, 'revision': '197463'});
264     const owner111289 = CommitLog.ensureSingleton('111289', {'id': '111289', 'time': 1456931874000, 'repository': MockModels.ownerRepository, 'revision': 'owner-001'});
265     const owned111222 = CommitLog.ensureSingleton('111222', {'id': '111222', 'time': 1456932774000, 'repository': MockModels.ownedRepository, 'revision': 'owned-002'});
266     const ios13A452 = CommitLog.ensureSingleton('88930', {'id': '88930', 'time': 0, 'repository': MockModels.ios, 'revision': '13A452'});
267
268     const root = new UploadedFile(456, {'createdAt': new Date('2017-05-01T21:03:27Z'), 'filename': 'root.dat', 'extension': '.dat', 'author': 'some user',
269         size: 16452234, sha256: '03eed7a8494ab8794c44b7d4308e55448fc56f4d6c175809ba968f78f656d58d'});
270
271     const commitSet = CommitSet.ensureSingleton('53246486', {customRoots: [root], revisionItems: [{commit: webkit197463}, {commit: owner111289}, {commit: owned111222, commitOwner: owner111289, requiresBuild: true}, {commit: ios13A452}]});
272
273     return BuildRequest.ensureSingleton(`6345645370-${order}`, {'triggerable': MockModels.triggerable,
274         repositoryGroup: MockModels.svnRepositoryWithOwnedRepositoryGroup,
275         'commitSet': commitSet, 'status': 'pending', 'platform': platform, 'test': test, 'order': order});
276 }
277
278 function createSampleBuildRequestWithOwnedCommitAndPatch(platform, test, order)
279 {
280     assert(platform instanceof Platform);
281     assert(!test || test instanceof Test);
282
283     const webkit197463 = CommitLog.ensureSingleton('111127', {'id': '111127', 'time': 1456955807334, 'repository': MockModels.webkit, 'revision': '197463'});
284     const owner111289 = CommitLog.ensureSingleton('111289', {'id': '111289', 'time': 1456931874000, 'repository': MockModels.ownerRepository, 'revision': 'owner-001'});
285     const owned111222 = CommitLog.ensureSingleton('111222', {'id': '111222', 'time': 1456932774000, 'repository': MockModels.ownedRepository, 'revision': 'owned-002'});
286     const ios13A452 = CommitLog.ensureSingleton('88930', {'id': '88930', 'time': 0, 'repository': MockModels.ios, 'revision': '13A452'});
287
288     const patch = new UploadedFile(453, {'createdAt': new Date('2017-05-01T19:16:53Z'), 'filename': 'patch.dat', 'extension': '.dat', 'author': 'some user',
289         size: 534637, sha256: '169463c8125e07c577110fe144ecd63942eb9472d438fc0014f474245e5df8a1'});
290
291     const commitSet = CommitSet.ensureSingleton('53246486', {customRoots: [], revisionItems: [{commit: webkit197463, patch, requiresBuild: true}, {commit: owner111289}, {commit: owned111222, commitOwner: owner111289, requiresBuild: true}, {commit: ios13A452}]});
292
293     return BuildRequest.ensureSingleton(`6345645370-${order}`, {'triggerable': MockModels.triggerable,
294         repositoryGroup: MockModels.svnRepositoryWithOwnedRepositoryGroup,
295         'commitSet': commitSet, 'status': 'pending', 'platform': platform, 'test': test, 'order': order});
296 }
297
298 function samplePendingBuild(buildRequestId, buildTime, slaveName)
299 {
300     return {
301         'builderName': 'ABTest-iPad-RunBenchmark-Tests',
302         'builds': [],
303         'properties': [
304             ['build_request_id', buildRequestId || '16733', 'Force Build Form'],
305             ['desired_image', '13A452', 'Force Build Form'],
306             ['owner', '<unknown>', 'Force Build Form'],
307             ['test_name', 'speedometer', 'Force Build Form'],
308             ['reason', 'force build','Force Build Form'],
309             ['slavename', slaveName, ''],
310             ['scheduler', 'ABTest-iPad-RunBenchmark-Tests-ForceScheduler', 'Scheduler']
311         ],
312         'source': {
313             'branch': '',
314             'changes': [],
315             'codebase': 'compiler-rt',
316             'hasPatch': false,
317             'project': '',
318             'repository': '',
319             'revision': ''
320         },
321         'submittedAt': buildTime || 1458704983
322     };
323 }
324
325 function sampleInProgressBuild(slaveName)
326 {
327     return {
328         'blame': [],
329         'builderName': 'ABTest-iPad-RunBenchmark-Tests',
330         'currentStep': {
331             'eta': 0.26548067698460565,
332             'expectations': [['output', 845, 1315.0]],
333             'hidden': false,
334             'isFinished': false,
335             'isStarted': true,
336             'logs': [['stdio', 'https://build.webkit.org/builders/ABTest-iPad-RunBenchmark-Tests/builds/614/steps/Some%20step/logs/stdio']],
337             'name': 'Some step',
338             'results': [null,[]],
339             'statistics': {},
340             'step_number': 1,
341             'text': [''],
342             'times': [1458718657.581628, null],
343             'urls': {}
344         },
345         'eta': 6497.991612434387,
346         'logs': [['stdio','https://build.webkit.org/builders/ABTest-iPad-RunBenchmark-Tests/builds/614/steps/shell/logs/stdio']],
347         'number': 614,
348         'properties': [
349             ['build_request_id', '16733', 'Force Build Form'],
350             ['buildername', 'ABTest-iPad-RunBenchmark-Tests', 'Builder'],
351             ['buildnumber', 614, 'Build'],
352             ['desired_image', '13A452', 'Force Build Form'],
353             ['owner', '<unknown>', 'Force Build Form'],
354             ['reason', 'force build', 'Force Build Form'],
355             ['scheduler', 'ABTest-iPad-RunBenchmark-Tests-ForceScheduler', 'Scheduler'],
356             ['slavename', slaveName || 'ABTest-iPad-0', 'BuildSlave'],
357         ],
358         'reason': 'A build was forced by \'<unknown>\': force build',
359         'results': null,
360         'slave': 'ABTest-iPad-0',
361         'sourceStamps': [{'branch': '', 'changes': [], 'codebase': 'compiler-rt', 'hasPatch': false, 'project': '', 'repository': '', 'revision': ''}],
362         'steps': [
363             {
364                 'eta': null,
365                 'expectations': [['output',2309,2309.0]],
366                 'hidden': false,
367                 'isFinished': true,
368                 'isStarted': true,
369                 'logs': [['stdio', 'https://build.webkit.org/builders/ABTest-iPad-RunBenchmark-Tests/builds/614/steps/shell/logs/stdio']],
370                 'name': 'Finished step',
371                 'results': [0, []],
372                 'statistics': {},
373                 'step_number': 0,
374                 'text': [''],
375                 'times': [1458718655.419865, 1458718655.453633],
376                 'urls': {}
377             },
378             {
379                 'eta': 0.26548067698460565,
380                 'expectations': [['output', 845, 1315.0]],
381                 'hidden': false,
382                 'isFinished': false,
383                 'isStarted': true,
384                 'logs': [['stdio', 'https://build.webkit.org/builders/ABTest-iPad-RunBenchmark-Tests/builds/614/steps/Some%20step/logs/stdio']],
385                 'name': 'Some step',
386                 'results': [null,[]],
387                 'statistics': {},
388                 'step_number': 1,
389                 'text': [''],
390                 'times': [1458718657.581628, null],
391                 'urls': {}
392             },
393             {
394                 'eta': null,
395                 'expectations': [['output', null, null]],
396                 'hidden': false,
397                 'isFinished': false,
398                 'isStarted': false,
399                 'logs': [],
400                 'name': 'Some other step',
401                 'results': [null, []],
402                 'statistics': {},
403                 'step_number': 2,
404                 'text': [],
405                 'times': [null, null],
406                 'urls': {}
407             },
408         ],
409         'text': [],
410         'times': [1458718655.415821, null]
411     };
412 }
413
414 function sampleFinishedBuild(buildRequestId, slaveName)
415 {
416     return {
417         'blame': [],
418         'builderName': 'ABTest-iPad-RunBenchmark-Tests',
419         'currentStep': null,
420         'eta': null,
421         'logs': [['stdio','https://build.webkit.org/builders/ABTest-iPad-RunBenchmark-Tests/builds/1755/steps/shell/logs/stdio']],
422         'number': 1755,
423         'properties': [
424             ['build_request_id', buildRequestId || '18935', 'Force Build Form'],
425             ['buildername', 'ABTest-iPad-RunBenchmark-Tests', 'Builder'],
426             ['buildnumber', 1755, 'Build'],
427             ['desired_image', '13A452', 'Force Build Form'],
428             ['owner', '<unknown>', 'Force Build Form'],
429             ['reason', 'force build', 'Force Build Form'],
430             ['scheduler', 'ABTest-iPad-RunBenchmark-Tests-ForceScheduler', 'Scheduler'],
431             ['slavename', slaveName || 'ABTest-iPad-0', 'BuildSlave'],
432         ],
433         'reason': 'A build was forced by \'<unknown>\': force build',
434         'results': 2,
435         'slave': 'ABTest-iPad-0',
436         'sourceStamps': [{'branch': '', 'changes': [], 'codebase': 'compiler-rt', 'hasPatch': false, 'project': '', 'repository': '', 'revision': ''}],
437         'steps': [
438             {
439                 'eta': null,
440                 'expectations': [['output',2309,2309.0]],
441                 'hidden': false,
442                 'isFinished': true,
443                 'isStarted': true,
444                 'logs': [['stdio', 'https://build.webkit.org/builders/ABTest-iPad-RunBenchmark-Tests/builds/614/steps/shell/logs/stdio']],
445                 'name': 'Finished step',
446                 'results': [0, []],
447                 'statistics': {},
448                 'step_number': 0,
449                 'text': [''],
450                 'times': [1458718655.419865, 1458718655.453633],
451                 'urls': {}
452             },
453             {
454                 'eta': null,
455                 'expectations': [['output', 845, 1315.0]],
456                 'hidden': false,
457                 'isFinished': true,
458                 'isStarted': true,
459                 'logs': [['stdio', 'https://build.webkit.org/builders/ABTest-iPad-RunBenchmark-Tests/builds/614/steps/Some%20step/logs/stdio']],
460                 'name': 'Some step',
461                 'results': [null,[]],
462                 'statistics': {},
463                 'step_number': 1,
464                 'text': [''],
465                 'times': [1458718657.581628, null],
466                 'urls': {}
467             },
468             {
469                 'eta': null,
470                 'expectations': [['output', null, null]],
471                 'hidden': false,
472                 'isFinished': true,
473                 'isStarted': true,
474                 'logs': [],
475                 'name': 'Some other step',
476                 'results': [null, []],
477                 'statistics': {},
478                 'step_number': 2,
479                 'text': [],
480                 'times': [null, null],
481                 'urls': {}
482             },
483         ],
484         'text': [],
485         'times': [1458937478.25837, 1458946147.173785]
486     };
487 }
488
489 describe('BuildbotSyncer', () => {
490     MockModels.inject();
491     let requests = MockRemoteAPI.inject('http://build.webkit.org');
492
493     describe('_loadConfig', () => {
494
495         it('should create BuildbotSyncer objects for a configuration that specify all required options', () => {
496             assert.equal(BuildbotSyncer._loadConfig(MockRemoteAPI, smallConfiguration(), builderNameToIDMap()).length, 1);
497         });
498
499         it('should throw when some required options are missing', () => {
500             assert.throws(() => {
501                 const config = smallConfiguration();
502                 delete config.builders;
503                 BuildbotSyncer._loadConfig(MockRemoteAPI, config, builderNameToIDMap());
504             }, /"some-builder" is not a valid builder in the configuration/);
505             assert.throws(() => {
506                 const config = smallConfiguration();
507                 delete config.types;
508                 BuildbotSyncer._loadConfig(MockRemoteAPI, config, builderNameToIDMap());
509             }, /"some-test" is not a valid type in the configuration/);
510             assert.throws(() => {
511                 const config = smallConfiguration();
512                 delete config.testConfigurations[0].builders;
513                 BuildbotSyncer._loadConfig(MockRemoteAPI, config, builderNameToIDMap());
514             }, /The test configuration 1 does not specify "builders" as an array/);
515             assert.throws(() => {
516                 const config = smallConfiguration();
517                 delete config.testConfigurations[0].platforms;
518                 BuildbotSyncer._loadConfig(MockRemoteAPI, config, builderNameToIDMap());
519             }, /The test configuration 1 does not specify "platforms" as an array/);
520             assert.throws(() => {
521                 const config = smallConfiguration();
522                 delete config.testConfigurations[0].types;
523                 BuildbotSyncer._loadConfig(MockRemoteAPI, config, builderNameToIDMap());
524             }, /The test configuration 0 does not specify "types" as an array/);
525             assert.throws(() => {
526                 const config = smallConfiguration();
527                 delete config.buildRequestArgument;
528                 BuildbotSyncer._loadConfig(MockRemoteAPI, config, builderNameToIDMap());
529             }, /buildRequestArgument must specify the name of the property used to store the build request ID/);
530         });
531
532         it('should throw when a test name is not an array of strings', () => {
533             assert.throws(() => {
534                 const config = smallConfiguration();
535                 config.testConfigurations[0].types = 'some test';
536                 BuildbotSyncer._loadConfig(MockRemoteAPI, config, builderNameToIDMap());
537             }, /The test configuration 0 does not specify "types" as an array/);
538             assert.throws(() => {
539                 const config = smallConfiguration();
540                 config.testConfigurations[0].types = [1];
541                 BuildbotSyncer._loadConfig(MockRemoteAPI, config, builderNameToIDMap());
542             }, /"1" is not a valid type in the configuration/);
543         });
544
545         it('should throw when properties is not an object', () => {
546             assert.throws(() => {
547                 const config = smallConfiguration();
548                 config.builders[Object.keys(config.builders)[0]].properties = 'hello';
549                 BuildbotSyncer._loadConfig(MockRemoteAPI, config, builderNameToIDMap());
550             }, /Build properties should be a dictionary/);
551             assert.throws(() => {
552                 const config = smallConfiguration();
553                 config.types[Object.keys(config.types)[0]].properties = 'hello';
554                 BuildbotSyncer._loadConfig(MockRemoteAPI, config, builderNameToIDMap());
555             }, /Build properties should be a dictionary/);
556         });
557
558         it('should throw when testProperties is specifed in a type or a builder', () => {
559             assert.throws(() => {
560                 const config = smallConfiguration();
561                 const firstType = Object.keys(config.types)[0];
562                 config.types[firstType].testProperties = {};
563                 BuildbotSyncer._loadConfig(MockRemoteAPI, config, builderNameToIDMap());
564             }, /Unrecognized parameter "testProperties"/);
565             assert.throws(() => {
566                 const config = smallConfiguration();
567                 const firstBuilder = Object.keys(config.builders)[0];
568                 config.builders[firstBuilder].testProperties = {};
569                 BuildbotSyncer._loadConfig(MockRemoteAPI, config, builderNameToIDMap());
570             }, /Unrecognized parameter "testProperties"/);
571         });
572
573         it('should throw when buildProperties is specifed in a type or a builder', () => {
574             assert.throws(() => {
575                 const config = smallConfiguration();
576                 const firstType = Object.keys(config.types)[0];
577                 config.types[firstType].buildProperties = {};
578                 BuildbotSyncer._loadConfig(MockRemoteAPI, config, builderNameToIDMap());
579             }, /Unrecognized parameter "buildProperties"/);
580             assert.throws(() => {
581                 const config = smallConfiguration();
582                 const firstBuilder = Object.keys(config.builders)[0];
583                 config.builders[firstBuilder].buildProperties = {};
584                 BuildbotSyncer._loadConfig(MockRemoteAPI, config, builderNameToIDMap());
585             }, /Unrecognized parameter "buildProperties"/);
586         });
587
588         it('should throw when properties for a type is malformed', () => {
589             const firstType = Object.keys(smallConfiguration().types)[0];
590             assert.throws(() => {
591                 const config = smallConfiguration();
592                 config.types[firstType].properties = 'hello';
593                 BuildbotSyncer._loadConfig(MockRemoteAPI, config, builderNameToIDMap());
594             }, /Build properties should be a dictionary/);
595             assert.throws(() => {
596                 const config = smallConfiguration();
597                 config.types[firstType].properties = {'some': {'otherKey': 'some root'}};
598                 BuildbotSyncer._loadConfig(RemoteAPI, config, builderNameToIDMap());
599             }, /Build properties "some" specifies a non-string value of type "object"/);
600             assert.throws(() => {
601                 const config = smallConfiguration();
602                 config.types[firstType].properties = {'some': {'otherKey': 'some root'}};
603                 BuildbotSyncer._loadConfig(RemoteAPI, config, builderNameToIDMap());
604             }, /Build properties "some" specifies a non-string value of type "object"/);
605             assert.throws(() => {
606                 const config = smallConfiguration();
607                 config.types[firstType].properties = {'some': {'revision': 'WebKit'}};
608                 BuildbotSyncer._loadConfig(RemoteAPI, config, builderNameToIDMap());
609             }, /Build properties "some" specifies a non-string value of type "object"/);
610             assert.throws(() => {
611                 const config = smallConfiguration();
612                 config.types[firstType].properties = {'some': 1};
613                 BuildbotSyncer._loadConfig(RemoteAPI, config, builderNameToIDMap());
614             }, / Build properties "some" specifies a non-string value of type "object"/);
615         });
616
617         it('should throw when properties for a builder is malformed', () => {
618             const firstBuilder = Object.keys(smallConfiguration().builders)[0];
619             assert.throws(() => {
620                 const config = smallConfiguration();
621                 config.builders[firstBuilder].properties = 'hello';
622                 BuildbotSyncer._loadConfig(MockRemoteAPI, config, builderNameToIDMap());
623             }, /Build properties should be a dictionary/);
624             assert.throws(() => {
625                 const config = smallConfiguration();
626                 config.builders[firstBuilder].properties = {'some': {'otherKey': 'some root'}};
627                 BuildbotSyncer._loadConfig(RemoteAPI, config, builderNameToIDMap());
628             }, /Build properties "some" specifies a non-string value of type "object"/);
629             assert.throws(() => {
630                 const config = smallConfiguration();
631                 config.builders[firstBuilder].properties = {'some': {'otherKey': 'some root'}};
632                 BuildbotSyncer._loadConfig(RemoteAPI, config, builderNameToIDMap());
633             }, /Build properties "some" specifies a non-string value of type "object"/);
634             assert.throws(() => {
635                 const config = smallConfiguration();
636                 config.builders[firstBuilder].properties = {'some': {'revision': 'WebKit'}};
637                 BuildbotSyncer._loadConfig(RemoteAPI, config, builderNameToIDMap());
638             }, /Build properties "some" specifies a non-string value of type "object"/);
639             assert.throws(() => {
640                 const config = smallConfiguration();
641                 config.builders[firstBuilder].properties = {'some': 1};
642                 BuildbotSyncer._loadConfig(RemoteAPI, config, builderNameToIDMap());
643             }, /Build properties "some" specifies a non-string value of type "object"/);
644         });
645
646         it('should create BuildbotSyncer objects for valid configurations', () => {
647             let syncers = BuildbotSyncer._loadConfig(RemoteAPI, sampleiOSConfig(), builderNameToIDMap());
648             assert.equal(syncers.length, 3);
649             assert.ok(syncers[0] instanceof BuildbotSyncer);
650             assert.ok(syncers[1] instanceof BuildbotSyncer);
651             assert.ok(syncers[2] instanceof BuildbotSyncer);
652         });
653
654         it('should parse builder names correctly', () => {
655             let syncers = BuildbotSyncer._loadConfig(RemoteAPI, sampleiOSConfig(), builderNameToIDMap());
656             assert.equal(syncers[0].builderName(), 'ABTest-iPhone-RunBenchmark-Tests');
657             assert.equal(syncers[1].builderName(), 'ABTest-iPad-RunBenchmark-Tests');
658             assert.equal(syncers[2].builderName(), 'ABTest-iOS-Builder');
659         });
660
661         it('should parse test configurations with build configurations correctly', () => {
662             let syncers = BuildbotSyncer._loadConfig(RemoteAPI, sampleiOSConfig(), builderNameToIDMap());
663
664             let configurations = syncers[0].testConfigurations();
665             assert(syncers[0].isTester());
666             assert.equal(configurations.length, 3);
667             assert.equal(configurations[0].platform, MockModels.iphone);
668             assert.equal(configurations[0].test, MockModels.speedometer);
669             assert.equal(configurations[1].platform, MockModels.iphone);
670             assert.equal(configurations[1].test, MockModels.jetstream);
671             assert.equal(configurations[2].platform, MockModels.iphone);
672             assert.equal(configurations[2].test, MockModels.domcore);
673             assert.deepEqual(syncers[0].buildConfigurations(), []);
674
675             configurations = syncers[1].testConfigurations();
676             assert(syncers[1].isTester());
677             assert.equal(configurations.length, 2);
678             assert.equal(configurations[0].platform, MockModels.ipad);
679             assert.equal(configurations[0].test, MockModels.speedometer);
680             assert.equal(configurations[1].platform, MockModels.ipad);
681             assert.equal(configurations[1].test, MockModels.jetstream);
682             assert.deepEqual(syncers[1].buildConfigurations(), []);
683
684             assert(!syncers[2].isTester());
685             assert.deepEqual(syncers[2].testConfigurations(), []);
686             configurations = syncers[2].buildConfigurations();
687             assert.equal(configurations.length, 2);
688             assert.equal(configurations[0].platform, MockModels.iphone);
689             assert.equal(configurations[0].test, null);
690             assert.equal(configurations[1].platform, MockModels.ipad);
691             assert.equal(configurations[1].test, null);
692         });
693
694         it('should throw when a build configuration use the same builder as a test configuration', () => {
695             assert.throws(() => {
696                 const config = sampleiOSConfig();
697                 config.buildConfigurations[0].builders = config.testConfigurations[0].builders;
698                 BuildbotSyncer._loadConfig(MockRemoteAPI, config, builderNameToIDMap());
699             });
700         });
701
702         it('should parse test configurations with types and platforms expansions correctly', () => {
703             const syncers = BuildbotSyncer._loadConfig(RemoteAPI, sampleiOSConfigWithExpansions(), builderNameToIDMap());
704
705             assert.equal(syncers.length, 3);
706
707             let configurations = syncers[0].testConfigurations();
708             assert.equal(configurations.length, 4);
709             assert.equal(configurations[0].platform, MockModels.iphone);
710             assert.equal(configurations[0].test, MockModels.iPhonePLT);
711             assert.equal(configurations[1].platform, MockModels.iphone);
712             assert.equal(configurations[1].test, MockModels.speedometer);
713             assert.equal(configurations[2].platform, MockModels.iOS10iPhone);
714             assert.equal(configurations[2].test, MockModels.iPhonePLT);
715             assert.equal(configurations[3].platform, MockModels.iOS10iPhone);
716             assert.equal(configurations[3].test, MockModels.speedometer);
717             assert.deepEqual(syncers[0].buildConfigurations(), []);
718
719             configurations = syncers[1].testConfigurations();
720             assert.equal(configurations.length, 4);
721             assert.equal(configurations[0].platform, MockModels.iphone);
722             assert.equal(configurations[0].test, MockModels.iPhonePLT);
723             assert.equal(configurations[1].platform, MockModels.iphone);
724             assert.equal(configurations[1].test, MockModels.speedometer);
725             assert.equal(configurations[2].platform, MockModels.iOS10iPhone);
726             assert.equal(configurations[2].test, MockModels.iPhonePLT);
727             assert.equal(configurations[3].platform, MockModels.iOS10iPhone);
728             assert.equal(configurations[3].test, MockModels.speedometer);
729             assert.deepEqual(syncers[1].buildConfigurations(), []);
730
731             configurations = syncers[2].testConfigurations();
732             assert.equal(configurations.length, 2);
733             assert.equal(configurations[0].platform, MockModels.ipad);
734             assert.equal(configurations[0].test, MockModels.iPadPLT);
735             assert.equal(configurations[1].platform, MockModels.ipad);
736             assert.equal(configurations[1].test, MockModels.speedometer);
737             assert.deepEqual(syncers[2].buildConfigurations(), []);
738         });
739
740         it('should throw when repositoryGroups is not an object', () => {
741             assert.throws(() => {
742                 const config = smallConfiguration();
743                 config.repositoryGroups = 1;
744                 BuildbotSyncer._loadConfig(MockRemoteAPI, config, builderNameToIDMap());
745             }, /repositoryGroups must specify a dictionary from the name to its definition/);
746             assert.throws(() => {
747                 const config = smallConfiguration();
748                 config.repositoryGroups = 'hello';
749                 BuildbotSyncer._loadConfig(MockRemoteAPI, config, builderNameToIDMap());
750             }, /repositoryGroups must specify a dictionary from the name to its definition/);
751         });
752
753         it('should throw when a repository group does not specify a dictionary of repositories', () => {
754             assert.throws(() => {
755                 const config = smallConfiguration();
756                 config.repositoryGroups = {'some-group': {testProperties: {}}};
757                 BuildbotSyncer._loadConfig(MockRemoteAPI, config, builderNameToIDMap());
758             }, /Repository group "some-group" does not specify a dictionary of repositories/);
759             assert.throws(() => {
760                 const config = smallConfiguration();
761                 config.repositoryGroups = {'some-group': {repositories: 1}, testProperties: {}};
762                 BuildbotSyncer._loadConfig(MockRemoteAPI, config, builderNameToIDMap());
763             }, /Repository group "some-group" does not specify a dictionary of repositories/);
764         });
765
766         it('should throw when a repository group specifies an empty dictionary', () => {
767             assert.throws(() => {
768                 const config = smallConfiguration();
769                 config.repositoryGroups = {'some-group': {repositories: {}, testProperties: {}}};
770                 BuildbotSyncer._loadConfig(MockRemoteAPI, config, builderNameToIDMap());
771             }, /Repository group "some-group" does not specify any repository/);
772         });
773
774         it('should throw when a repository group specifies an invalid repository name', () => {
775             assert.throws(() => {
776                 const config = smallConfiguration();
777                 config.repositoryGroups = {'some-group': {repositories: {'InvalidRepositoryName': {}}}};
778                 BuildbotSyncer._loadConfig(MockRemoteAPI, config, builderNameToIDMap());
779             }, /"InvalidRepositoryName" is not a valid repository name/);
780         });
781
782         it('should throw when a repository group specifies a repository with a non-dictionary value', () => {
783             assert.throws(() => {
784                 const config = smallConfiguration();
785                 config.repositoryGroups = {'some-group': {repositories: {'WebKit': 1}}};
786                 BuildbotSyncer._loadConfig(MockRemoteAPI, config, builderNameToIDMap());
787             }, /"WebKit" specifies a non-dictionary value/);
788         });
789
790         it('should throw when the description of a repository group is not a string', () => {
791             assert.throws(() => {
792                 const config = smallConfiguration();
793                 config.repositoryGroups = {'some-group': {repositories: {'WebKit': {}}, description: 1}};
794                 BuildbotSyncer._loadConfig(MockRemoteAPI, config, builderNameToIDMap());
795             }, /Repository group "some-group" have an invalid description/);
796             assert.throws(() => {
797                 const config = smallConfiguration();
798                 config.repositoryGroups = {'some-group': {repositories: {'WebKit': {}}, description: [1, 2]}};
799                 BuildbotSyncer._loadConfig(MockRemoteAPI, config, builderNameToIDMap());
800             }, /Repository group "some-group" have an invalid description/);
801         });
802
803         it('should throw when a repository group does not specify a dictionary of properties', () => {
804             assert.throws(() => {
805                 const config = smallConfiguration();
806                 config.repositoryGroups = {'some-group': {repositories: {'WebKit': {}}, testProperties: 1}};
807                 BuildbotSyncer._loadConfig(MockRemoteAPI, config, builderNameToIDMap());
808             }, /Repository group "some-group" specifies the test configurations with an invalid type/);
809             assert.throws(() => {
810                 const config = smallConfiguration();
811                 config.repositoryGroups = {'some-group': {repositories: {'WebKit': {}}, testProperties: 'hello'}};
812                 BuildbotSyncer._loadConfig(MockRemoteAPI, config, builderNameToIDMap());
813             }, /Repository group "some-group" specifies the test configurations with an invalid type/);
814         });
815
816         it('should throw when a repository group refers to a non-existent repository in the properties dictionary', () => {
817             assert.throws(() => {
818                 const config = smallConfiguration();
819                 config.repositoryGroups = {'some-group': {repositories: {'WebKit': {}}, testProperties: {'wk': {revision: 'InvalidRepository'}}}};
820                 BuildbotSyncer._loadConfig(MockRemoteAPI, config, builderNameToIDMap());
821             }, /Repository group "some-group" an invalid repository "InvalidRepository"/);
822         });
823
824         it('should throw when a repository group refers to a repository which is not listed in the list of repositories', () => {
825             assert.throws(() => {
826                 const config = smallConfiguration();
827                 config.repositoryGroups = {'some-group': {repositories: {'WebKit': {}}, testProperties: {'os': {revision: 'iOS'}}}};
828                 BuildbotSyncer._loadConfig(MockRemoteAPI, config, builderNameToIDMap());
829             }, /Repository group "some-group" an invalid repository "iOS"/);
830             assert.throws(() => {
831                 const config = smallConfiguration();
832                 config.repositoryGroups = {'some-group': {
833                     repositories: {'WebKit': {acceptsPatch: true}},
834                     testProperties: {'wk': {revision: 'WebKit'}, 'install-roots': {'roots': {}}},
835                     buildProperties: {'os': {revision: 'iOS'}},
836                     acceptsRoots: true}};
837                 BuildbotSyncer._loadConfig(MockRemoteAPI, config, builderNameToIDMap());
838             }, /Repository group "some-group" an invalid repository "iOS"/);
839         });
840
841         it('should throw when a repository group refers to a repository in building a patch which does not accept a patch', () => {
842             assert.throws(() => {
843                 const config = smallConfiguration();
844                 config.repositoryGroups = {'some-group': {
845                     repositories: {'WebKit': {acceptsPatch: true}, 'iOS': {}},
846                     testProperties: {'wk': {revision: 'WebKit'}, 'ios': {revision: 'iOS'}, 'install-roots': {'roots': {}}},
847                     buildProperties: {'wk': {revision: 'WebKit'}, 'ios': {revision: 'iOS'}, 'wk-patch': {patch: 'iOS'}},
848                     acceptsRoots: true}};
849                 BuildbotSyncer._loadConfig(MockRemoteAPI, config, builderNameToIDMap());
850             }, /Repository group "some-group" specifies a patch for "iOS" but it does not accept a patch/);
851         });
852
853         it('should throw when a repository group specifies a patch without specifying a revision', () => {
854             assert.throws(() => {
855                 const config = smallConfiguration();
856                 config.repositoryGroups = {'some-group': {
857                     repositories: {'WebKit': {acceptsPatch: true}},
858                     testProperties: {'wk': {revision: 'WebKit'}, 'install-roots': {'roots': {}}},
859                     buildProperties: {'wk-patch': {patch: 'WebKit'}},
860                     acceptsRoots: true}};
861                 BuildbotSyncer._loadConfig(MockRemoteAPI, config, builderNameToIDMap());
862             }, /Repository group "some-group" specifies a patch for "WebKit" but does not specify a revision/);
863         });
864
865         it('should throw when a repository group does not use a listed repository', () => {
866             assert.throws(() => {
867                 const config = smallConfiguration();
868                 config.repositoryGroups = {'some-group': {'repositories': {'WebKit': {}}, testProperties: {}}};
869                 BuildbotSyncer._loadConfig(MockRemoteAPI, config, builderNameToIDMap());
870             }, /Repository group "some-group" does not use some of the repositories listed in testing/);
871             assert.throws(() => {
872                 const config = smallConfiguration();
873                 config.repositoryGroups = {'some-group': {
874                     repositories: {'WebKit': {acceptsPatch: true}},
875                     testProperties: {'wk': {revision: 'WebKit'}, 'install-roots': {'roots': {}}},
876                     buildProperties: {},
877                     acceptsRoots: true}};
878                 BuildbotSyncer._loadConfig(MockRemoteAPI, config, builderNameToIDMap());
879             }, /Repository group "some-group" does not use some of the repositories listed in building a patch/);
880         });
881
882         it('should throw when a repository group specifies non-boolean value to acceptsRoots', () => {
883             assert.throws(() => {
884                 const config = smallConfiguration();
885                 config.repositoryGroups = {'some-group': {'repositories': {'WebKit': {}}, 'testProperties': {'webkit': {'revision': 'WebKit'}}, acceptsRoots: 1}};
886                 BuildbotSyncer._loadConfig(MockRemoteAPI, config, builderNameToIDMap());
887             }, /Repository group "some-group" contains invalid acceptsRoots value:/);
888             assert.throws(() => {
889                 const config = smallConfiguration();
890                 config.repositoryGroups = {'some-group': {'repositories': {'WebKit': {}}, 'testProperties': {'webkit': {'revision': 'WebKit'}}, acceptsRoots: []}};
891                 BuildbotSyncer._loadConfig(MockRemoteAPI, config, builderNameToIDMap());
892             }, /Repository group "some-group" contains invalid acceptsRoots value:/);
893         });
894
895         it('should throw when a repository group specifies non-boolean value to acceptsPatch', () => {
896             assert.throws(() => {
897                 const config = smallConfiguration();
898                 config.repositoryGroups = {'some-group': {'repositories': {'WebKit': {acceptsPatch: 1}}, 'testProperties': {'webkit': {'revision': 'WebKit'}}}};
899                 BuildbotSyncer._loadConfig(MockRemoteAPI, config, builderNameToIDMap());
900             }, /"WebKit" contains invalid acceptsPatch value:/);
901             assert.throws(() => {
902                 const config = smallConfiguration();
903                 config.repositoryGroups = {'some-group': {'repositories': {'WebKit': {acceptsPatch: []}}, 'testProperties': {'webkit': {'revision': 'WebKit'}}}};
904                 BuildbotSyncer._loadConfig(MockRemoteAPI, config, builderNameToIDMap());
905             }, /"WebKit" contains invalid acceptsPatch value:/);
906         });
907
908         it('should throw when a repository group specifies a patch in testProperties', () => {
909             assert.throws(() => {
910                 const config = smallConfiguration();
911                 config.repositoryGroups = {'some-group': {'repositories': {'WebKit': {acceptsPatch: true}},
912                     'testProperties': {'webkit': {'revision': 'WebKit'}, 'webkit-patch': {'patch': 'WebKit'}}}};
913                 BuildbotSyncer._loadConfig(MockRemoteAPI, config, builderNameToIDMap());
914             }, /Repository group "some-group" specifies a patch for "WebKit" in the properties for testing/);
915         });
916
917         it('should throw when a repository group specifies roots in buildProperties', () => {
918             assert.throws(() => {
919                 const config = smallConfiguration();
920                 config.repositoryGroups = {'some-group': {
921                     repositories: {'WebKit': {acceptsPatch: true}},
922                     testProperties: {'webkit': {revision: 'WebKit'}, 'install-roots': {'roots': {}}},
923                     buildProperties: {'webkit': {revision: 'WebKit'}, 'patch': {patch: 'WebKit'}, 'install-roots': {roots: {}}},
924                     acceptsRoots: true}};
925                 BuildbotSyncer._loadConfig(MockRemoteAPI, config, builderNameToIDMap());
926             }, /Repository group "some-group" specifies roots in the properties for building/);
927         });
928
929         it('should throw when a repository group that does not accept roots specifies roots in testProperties', () => {
930             assert.throws(() => {
931                 const config = smallConfiguration();
932                 config.repositoryGroups = {'some-group': {
933                     repositories: {'WebKit': {}},
934                     testProperties: {'webkit': {'revision': 'WebKit'}, 'install-roots': {'roots': {}}}}};
935                 BuildbotSyncer._loadConfig(MockRemoteAPI, config, builderNameToIDMap());
936             }, /Repository group "some-group" specifies roots in a property but it does not accept roots/);
937         });
938
939         it('should throw when a repository group specifies buildProperties but does not accept roots', () => {
940             assert.throws(() => {
941                 const config = smallConfiguration();
942                 config.repositoryGroups = {'some-group': {
943                     repositories: {'WebKit': {acceptsPatch: true}},
944                     testProperties: {'webkit': {revision: 'WebKit'}},
945                     buildProperties: {'webkit': {revision: 'WebKit'}, 'webkit-patch': {patch: 'WebKit'}}}};
946                 BuildbotSyncer._loadConfig(MockRemoteAPI, config, builderNameToIDMap());
947             }, /Repository group "some-group" specifies the properties for building but does not accept roots in testing/);
948         });
949
950         it('should throw when a repository group specifies buildProperties but does not accept any patch', () => {
951             assert.throws(() => {
952                 const config = smallConfiguration();
953                 config.repositoryGroups = {'some-group': {
954                     repositories: {'WebKit': {}},
955                     testProperties: {'webkit': {'revision': 'WebKit'}, 'install-roots': {'roots': {}}},
956                     buildProperties: {'webkit': {'revision': 'WebKit'}},
957                     acceptsRoots: true}};
958                 BuildbotSyncer._loadConfig(MockRemoteAPI, config, builderNameToIDMap());
959             }, /Repository group "some-group" specifies the properties for building but does not accept any patches/);
960         });
961
962         it('should throw when a repository group accepts roots but does not specify roots in testProperties', () => {
963             assert.throws(() => {
964                 const config = smallConfiguration();
965                 config.repositoryGroups = {'some-group': {
966                     repositories: {'WebKit': {acceptsPatch: true}},
967                     testProperties: {'webkit': {revision: 'WebKit'}},
968                     buildProperties: {'webkit': {revision: 'WebKit'}, 'webkit-patch': {patch: 'WebKit'}},
969                     acceptsRoots: true}};
970                 BuildbotSyncer._loadConfig(MockRemoteAPI, config, builderNameToIDMap());
971             }, /Repository group "some-group" accepts roots but does not specify roots in testProperties/);
972         });
973     });
974
975     describe('_propertiesForBuildRequest', () => {
976         it('should include all properties specified in a given configuration', () => {
977             const syncers = BuildbotSyncer._loadConfig(RemoteAPI, sampleiOSConfig(), builderNameToIDMap());
978             const request = createSampleBuildRequest(MockModels.iphone, MockModels.speedometer);
979             const properties = syncers[0]._propertiesForBuildRequest(request, [request]);
980             assert.deepEqual(Object.keys(properties).sort(), ['build_request_id', 'desired_image', 'forcescheduler', 'opensource', 'test_name']);
981         });
982
983         it('should preserve non-parametric property values', () => {
984             const syncers = BuildbotSyncer._loadConfig(RemoteAPI, sampleiOSConfig(), builderNameToIDMap());
985             let request = createSampleBuildRequest(MockModels.iphone, MockModels.speedometer);
986             let properties = syncers[0]._propertiesForBuildRequest(request, [request]);
987             assert.equal(properties['test_name'], 'speedometer');
988             assert.equal(properties['forcescheduler'], 'ABTest-iPhone-RunBenchmark-Tests-ForceScheduler');
989
990             request = createSampleBuildRequest(MockModels.ipad, MockModels.jetstream);
991             properties = syncers[1]._propertiesForBuildRequest(request, [request]);
992             assert.equal(properties['test_name'], 'jetstream');
993             assert.equal(properties['forcescheduler'], 'ABTest-iPad-RunBenchmark-Tests-ForceScheduler');
994         });
995
996         it('should resolve "root"', () => {
997             const syncers = BuildbotSyncer._loadConfig(RemoteAPI, sampleiOSConfig(), builderNameToIDMap());
998             const request = createSampleBuildRequest(MockModels.iphone, MockModels.speedometer);
999             const properties = syncers[0]._propertiesForBuildRequest(request, [request]);
1000             assert.equal(properties['desired_image'], '13A452');
1001         });
1002
1003         it('should resolve "revision"', () => {
1004             const syncers = BuildbotSyncer._loadConfig(RemoteAPI, sampleiOSConfig(), builderNameToIDMap());
1005             const request = createSampleBuildRequest(MockModels.iphone, MockModels.speedometer);
1006             const properties = syncers[0]._propertiesForBuildRequest(request, [request]);
1007             assert.equal(properties['opensource'], '197463');
1008         });
1009
1010         it('should resolve "patch"', () => {
1011             const config = sampleiOSConfig();
1012             config.repositoryGroups['ios-svn-webkit'] = {
1013                 'repositories': {'WebKit': {'acceptsPatch': true}, 'Shared': {}, 'iOS': {}},
1014                 'testProperties': {
1015                     'os': {'revision': 'iOS'},
1016                     'webkit': {'revision': 'WebKit'},
1017                     'shared': {'revision': 'Shared'},
1018                     'roots': {'roots': {}},
1019                 },
1020                 'buildProperties': {
1021                     'webkit': {'revision': 'WebKit'},
1022                     'webkit-patch': {'patch': 'WebKit'},
1023                     'checkbox': {'ifRepositorySet': ['WebKit'], 'value': 'build-webkit'},
1024                     'shared': {'revision': 'Shared'},
1025                 },
1026                 'acceptsRoots': true,
1027             };
1028             const syncers = BuildbotSyncer._loadConfig(RemoteAPI, config, builderNameToIDMap());
1029             const request = createSampleBuildRequestWithPatch(MockModels.iphone, null, -1);
1030             const properties = syncers[2]._propertiesForBuildRequest(request, [request]);
1031             assert.equal(properties['webkit'], '197463');
1032             assert.equal(properties['webkit-patch'], 'http://build.webkit.org/api/uploaded-file/453.dat');
1033             assert.equal(properties['checkbox'], 'build-webkit');
1034         });
1035
1036         it('should resolve "ifBuilt"', () => {
1037             const config = sampleiOSConfig();
1038             config.repositoryGroups['ios-svn-webkit'] = {
1039                 'repositories': {'WebKit': {}, 'Shared': {}, 'iOS': {}},
1040                 'testProperties': {
1041                     'os': {'revision': 'iOS'},
1042                     'webkit': {'revision': 'WebKit'},
1043                     'shared': {'revision': 'Shared'},
1044                     'roots': {'roots': {}},
1045                     'test-custom-build': {'ifBuilt': [], 'value': ''},
1046                     'has-built-patch': {'ifBuilt': [], 'value': 'true'},
1047                 },
1048                 'acceptsRoots': true,
1049             };
1050             const syncers = BuildbotSyncer._loadConfig(RemoteAPI, config, builderNameToIDMap());
1051             const requestToBuild = createSampleBuildRequestWithPatch(MockModels.iphone, null, -1);
1052             const requestToTest = createSampleBuildRequestWithPatch(MockModels.iphone, MockModels.speedometer, 0);
1053             const otherRequestToTest = createSampleBuildRequest(MockModels.iphone, MockModels.speedometer);
1054
1055             let properties = syncers[0]._propertiesForBuildRequest(requestToTest, [requestToTest]);
1056             assert.equal(properties['webkit'], '197463');
1057             assert.equal(properties['roots'], '[{"url":"http://build.webkit.org/api/uploaded-file/456.dat"}]');
1058             assert.equal(properties['test-custom-build'], undefined);
1059             assert.equal(properties['has-built-patch'], undefined);
1060
1061             properties = syncers[0]._propertiesForBuildRequest(requestToTest, [requestToBuild, requestToTest]);
1062             assert.equal(properties['webkit'], '197463');
1063             assert.equal(properties['roots'], '[{"url":"http://build.webkit.org/api/uploaded-file/456.dat"}]');
1064             assert.equal(properties['test-custom-build'], '');
1065             assert.equal(properties['has-built-patch'], 'true');
1066
1067             properties = syncers[0]._propertiesForBuildRequest(otherRequestToTest, [requestToBuild, otherRequestToTest, requestToTest]);
1068             assert.equal(properties['webkit'], '197463');
1069             assert.equal(properties['roots'], undefined);
1070             assert.equal(properties['test-custom-build'], undefined);
1071             assert.equal(properties['has-built-patch'], undefined);
1072
1073         });
1074
1075         it('should set the value for "ifBuilt" if the repository in the list appears', () => {
1076             const config = sampleiOSConfig();
1077             config.repositoryGroups['ios-svn-webkit'] = {
1078                 'repositories': {'WebKit': {'acceptsPatch': true}, 'Shared': {}, 'iOS': {}},
1079                 'testProperties': {
1080                     'os': {'revision': 'iOS'},
1081                     'webkit': {'revision': 'WebKit'},
1082                     'shared': {'revision': 'Shared'},
1083                     'roots': {'roots': {}},
1084                     'checkbox': {'ifBuilt': ['WebKit'], 'value': 'test-webkit'}
1085                 },
1086                 'buildProperties': {
1087                     'webkit': {'revision': 'WebKit'},
1088                     'webkit-patch': {'patch': 'WebKit'},
1089                     'checkbox': {'ifRepositorySet': ['WebKit'], 'value': 'build-webkit'},
1090                     'shared': {'revision': 'Shared'},
1091                 },
1092                 'acceptsRoots': true,
1093             };
1094             const syncers = BuildbotSyncer._loadConfig(RemoteAPI, config, builderNameToIDMap());
1095             const requestToBuild = createSampleBuildRequestWithPatch(MockModels.iphone, null, -1);
1096             const requestToTest = createSampleBuildRequestWithPatch(MockModels.iphone, MockModels.speedometer, 0);
1097             const properties = syncers[0]._propertiesForBuildRequest(requestToTest, [requestToBuild, requestToTest]);
1098             assert.equal(properties['webkit'], '197463');
1099             assert.equal(properties['roots'], '[{"url":"http://build.webkit.org/api/uploaded-file/456.dat"}]');
1100             assert.equal(properties['checkbox'], 'test-webkit');
1101         });
1102
1103         it('should not set the value for "ifBuilt" if no build for the repository in the list appears', () => {
1104             const config = sampleiOSConfig();
1105             config.repositoryGroups['ios-svn-webkit-with-owned-commit'] = {
1106                 'repositories': {'WebKit': {'acceptsPatch': true}, 'Owner Repository': {}, 'iOS': {}},
1107                 'testProperties': {
1108                     'os': {'revision': 'iOS'},
1109                     'webkit': {'revision': 'WebKit'},
1110                     'owner-repo': {'revision': 'Owner Repository'},
1111                     'roots': {'roots': {}},
1112                     'checkbox': {'ifBuilt': ['WebKit'], 'value': 'test-webkit'}
1113                 },
1114                 'buildProperties': {
1115                     'webkit': {'revision': 'WebKit'},
1116                     'webkit-patch': {'patch': 'WebKit'},
1117                     'owner-repo': {'revision': 'Owner Repository'},
1118                     'checkbox': {'ifRepositorySet': ['WebKit'], 'value': 'build-webkit'},
1119                     'owned-commits': {'ownedRevisions': 'Owner Repository'}
1120                 },
1121                 'acceptsRoots': true,
1122             };
1123             const syncers = BuildbotSyncer._loadConfig(RemoteAPI, config, builderNameToIDMap());
1124             const requestToBuild =  createSampleBuildRequestWithOwnedCommit(MockModels.iphone, null, -1);
1125             const requestToTest = createSampleBuildRequestWithOwnedCommit(MockModels.iphone, MockModels.speedometer, 0);
1126             const properties = syncers[0]._propertiesForBuildRequest(requestToTest, [requestToBuild, requestToTest]);
1127
1128             assert.equal(properties['webkit'], '197463');
1129             assert.equal(properties['roots'], '[{"url":"http://build.webkit.org/api/uploaded-file/456.dat"}]');
1130             assert.equal(properties['checkbox'], undefined);
1131         });
1132
1133         it('should resolve "ifRepositorySet" and "requiresBuild"', () => {
1134             const config = sampleiOSConfig();
1135             config.repositoryGroups['ios-svn-webkit-with-owned-commit'] = {
1136                 'repositories': {'WebKit': {'acceptsPatch': true}, 'Owner Repository': {}, 'iOS': {}},
1137                 'testProperties': {
1138                     'os': {'revision': 'iOS'},
1139                     'webkit': {'revision': 'WebKit'},
1140                     'owner-repo': {'revision': 'Owner Repository'},
1141                     'roots': {'roots': {}},
1142                 },
1143                 'buildProperties': {
1144                     'webkit': {'revision': 'WebKit'},
1145                     'webkit-patch': {'patch': 'WebKit'},
1146                     'owner-repo': {'revision': 'Owner Repository'},
1147                     'checkbox': {'ifRepositorySet': ['WebKit'], 'value': 'build-webkit'},
1148                     'owned-commits': {'ownedRevisions': 'Owner Repository'}
1149                 },
1150                 'acceptsRoots': true,
1151             };
1152             const syncers = BuildbotSyncer._loadConfig(RemoteAPI, config, builderNameToIDMap());
1153             const request = createSampleBuildRequestWithOwnedCommit(MockModels.iphone, null, -1);
1154             const properties = syncers[2]._propertiesForBuildRequest(request, [request]);
1155             assert.equal(properties['webkit'], '197463');
1156             assert.equal(properties['owner-repo'], 'owner-001');
1157             assert.equal(properties['checkbox'], undefined);
1158             assert.deepEqual(JSON.parse(properties['owned-commits']), {'Owner Repository': [{revision: 'owned-002', repository: 'Owned Repository', ownerRevision: 'owner-001'}]});
1159         });
1160
1161         it('should resolve "patch", "ifRepositorySet" and "requiresBuild"', () => {
1162
1163             const config = sampleiOSConfig();
1164             config.repositoryGroups['ios-svn-webkit-with-owned-commit'] = {
1165                 'repositories': {'WebKit': {'acceptsPatch': true}, 'Owner Repository': {}, 'iOS': {}},
1166                 'testProperties': {
1167                     'os': {'revision': 'iOS'},
1168                     'webkit': {'revision': 'WebKit'},
1169                     'owner-repo': {'revision': 'Owner Repository'},
1170                     'roots': {'roots': {}},
1171                 },
1172                 'buildProperties': {
1173                     'webkit': {'revision': 'WebKit'},
1174                     'webkit-patch': {'patch': 'WebKit'},
1175                     'owner-repo': {'revision': 'Owner Repository'},
1176                     'checkbox': {'ifRepositorySet': ['WebKit'], 'value': 'build-webkit'},
1177                     'owned-commits': {'ownedRevisions': 'Owner Repository'}
1178                 },
1179                 'acceptsRoots': true,
1180             };
1181             const syncers = BuildbotSyncer._loadConfig(RemoteAPI, config, builderNameToIDMap());
1182             const request = createSampleBuildRequestWithOwnedCommitAndPatch(MockModels.iphone, null, -1);
1183             const properties = syncers[2]._propertiesForBuildRequest(request, [request]);
1184             assert.equal(properties['webkit'], '197463');
1185             assert.equal(properties['owner-repo'], 'owner-001');
1186             assert.equal(properties['checkbox'], 'build-webkit');
1187             assert.equal(properties['webkit-patch'], 'http://build.webkit.org/api/uploaded-file/453.dat');
1188             assert.deepEqual(JSON.parse(properties['owned-commits']), {'Owner Repository': [{revision: 'owned-002', repository: 'Owned Repository', ownerRevision: 'owner-001'}]});
1189         });
1190
1191         it('should set the property for the build request id', () => {
1192             const syncers = BuildbotSyncer._loadConfig(RemoteAPI, sampleiOSConfig(), builderNameToIDMap());
1193             const request = createSampleBuildRequest(MockModels.iphone, MockModels.speedometer);
1194             const properties = syncers[0]._propertiesForBuildRequest(request, [request]);
1195             assert.equal(properties['build_request_id'], request.id());
1196         });
1197     });
1198
1199     describe('pullBuildbot', () => {
1200         it('should fetch pending builds from the right URL', () => {
1201             let syncer = BuildbotSyncer._loadConfig(MockRemoteAPI, sampleiOSConfig(), builderNameToIDMap())[1];
1202             assert.equal(syncer.builderName(), 'ABTest-iPad-RunBenchmark-Tests');
1203             let expectedURL = '/json/builders/ABTest-iPad-RunBenchmark-Tests/pendingBuilds';
1204             assert.equal(syncer.pathForPendingBuildsJSON(), expectedURL);
1205             syncer.pullBuildbot();
1206             assert.equal(requests.length, 1);
1207             assert.equal(requests[0].url, expectedURL);
1208         });
1209
1210         it('should fetch recent builds once pending builds have been fetched', () => {
1211             let syncer = BuildbotSyncer._loadConfig(MockRemoteAPI, sampleiOSConfig(), builderNameToIDMap())[1];
1212             assert.equal(syncer.builderName(), 'ABTest-iPad-RunBenchmark-Tests');
1213
1214             syncer.pullBuildbot(1);
1215             assert.equal(requests.length, 1);
1216             assert.equal(requests[0].url, '/json/builders/ABTest-iPad-RunBenchmark-Tests/pendingBuilds');
1217             requests[0].resolve([]);
1218             return MockRemoteAPI.waitForRequest().then(() => {
1219                 assert.equal(requests.length, 2);
1220                 assert.equal(requests[1].url, '/json/builders/ABTest-iPad-RunBenchmark-Tests/builds/?select=-1');
1221             });
1222         });
1223
1224         it('should fetch the right number of recent builds', () => {
1225             let syncer = BuildbotSyncer._loadConfig(MockRemoteAPI, sampleiOSConfig(), builderNameToIDMap())[1];
1226
1227             syncer.pullBuildbot(3);
1228             assert.equal(requests.length, 1);
1229             assert.equal(requests[0].url, '/json/builders/ABTest-iPad-RunBenchmark-Tests/pendingBuilds');
1230             requests[0].resolve([]);
1231             return MockRemoteAPI.waitForRequest().then(() => {
1232                 assert.equal(requests.length, 2);
1233                 assert.equal(requests[1].url, '/json/builders/ABTest-iPad-RunBenchmark-Tests/builds/?select=-1&select=-2&select=-3');
1234             });
1235         });
1236
1237         it('should create BuildbotBuildEntry for pending builds', () => {
1238             let syncer = BuildbotSyncer._loadConfig(MockRemoteAPI, sampleiOSConfig(), builderNameToIDMap())[1];
1239             let promise = syncer.pullBuildbot();
1240             requests[0].resolve([samplePendingBuild()]);
1241             return promise.then((entries) => {
1242                 assert.equal(entries.length, 1);
1243                 let entry = entries[0];
1244                 assert.ok(entry instanceof BuildbotBuildEntry);
1245                 assert.ok(!entry.buildNumber());
1246                 assert.ok(!entry.slaveName());
1247                 assert.equal(entry.buildRequestId(), 16733);
1248                 assert.ok(entry.isPending());
1249                 assert.ok(!entry.isInProgress());
1250                 assert.ok(!entry.hasFinished());
1251                 assert.equal(entry.url(), 'http://build.webkit.org/builders/ABTest-iPad-RunBenchmark-Tests/');
1252             });
1253         });
1254
1255         it('should create BuildbotBuildEntry for in-progress builds', () => {
1256             let syncer = BuildbotSyncer._loadConfig(MockRemoteAPI, sampleiOSConfig(), builderNameToIDMap())[1];
1257
1258             let promise = syncer.pullBuildbot(1);
1259             assert.equal(requests.length, 1);
1260             requests[0].resolve([]);
1261             return MockRemoteAPI.waitForRequest().then(() => {
1262                 assert.equal(requests.length, 2);
1263                 requests[1].resolve({[-1]: sampleInProgressBuild()});
1264                 return promise;
1265             }).then((entries) => {
1266                 assert.equal(entries.length, 1);
1267                 let entry = entries[0];
1268                 assert.ok(entry instanceof BuildbotBuildEntry);
1269                 assert.equal(entry.buildNumber(), 614);
1270                 assert.equal(entry.slaveName(), 'ABTest-iPad-0');
1271                 assert.equal(entry.buildRequestId(), 16733);
1272                 assert.ok(!entry.isPending());
1273                 assert.ok(entry.isInProgress());
1274                 assert.ok(!entry.hasFinished());
1275                 assert.equal(entry.url(), 'http://build.webkit.org/builders/ABTest-iPad-RunBenchmark-Tests/builds/614');
1276             });
1277         });
1278
1279         it('should create BuildbotBuildEntry for finished builds', () => {
1280             let syncer = BuildbotSyncer._loadConfig(MockRemoteAPI, sampleiOSConfig(), builderNameToIDMap())[1];
1281
1282             let promise = syncer.pullBuildbot(1);
1283             assert.equal(requests.length, 1);
1284             requests[0].resolve([]);
1285             return MockRemoteAPI.waitForRequest().then(() => {
1286                 assert.equal(requests.length, 2);
1287                 requests[1].resolve({[-1]: sampleFinishedBuild()});
1288                 return promise;
1289             }).then((entries) => {
1290                 assert.deepEqual(entries.length, 1);
1291                 let entry = entries[0];
1292                 assert.ok(entry instanceof BuildbotBuildEntry);
1293                 assert.equal(entry.buildNumber(), 1755);
1294                 assert.equal(entry.slaveName(), 'ABTest-iPad-0');
1295                 assert.equal(entry.buildRequestId(), 18935);
1296                 assert.ok(!entry.isPending());
1297                 assert.ok(!entry.isInProgress());
1298                 assert.ok(entry.hasFinished());
1299                 assert.equal(entry.url(), 'http://build.webkit.org/builders/ABTest-iPad-RunBenchmark-Tests/builds/1755');
1300             });
1301         });
1302
1303         it('should create BuildbotBuildEntry for mixed pending, in-progress, finished, and missing builds', () => {
1304             let syncer = BuildbotSyncer._loadConfig(MockRemoteAPI, sampleiOSConfig(), builderNameToIDMap())[1];
1305
1306             let promise = syncer.pullBuildbot(5);
1307             assert.equal(requests.length, 1);
1308
1309             requests[0].resolve([samplePendingBuild(123)]);
1310
1311             return MockRemoteAPI.waitForRequest().then(() => {
1312                 assert.equal(requests.length, 2);
1313                 requests[1].resolve({[-1]: sampleFinishedBuild(), [-2]: {'error': 'Not available'}, [-4]: sampleInProgressBuild()});
1314                 return promise;
1315             }).then((entries) => {
1316                 assert.deepEqual(entries.length, 3);
1317
1318                 let entry = entries[0];
1319                 assert.ok(entry instanceof BuildbotBuildEntry);
1320                 assert.equal(entry.buildNumber(), null);
1321                 assert.equal(entry.slaveName(), null);
1322                 assert.equal(entry.buildRequestId(), 123);
1323                 assert.ok(entry.isPending());
1324                 assert.ok(!entry.isInProgress());
1325                 assert.ok(!entry.hasFinished());
1326                 assert.equal(entry.url(), 'http://build.webkit.org/builders/ABTest-iPad-RunBenchmark-Tests/');
1327
1328                 entry = entries[1];
1329                 assert.ok(entry instanceof BuildbotBuildEntry);
1330                 assert.equal(entry.buildNumber(), 614);
1331                 assert.equal(entry.slaveName(), 'ABTest-iPad-0');
1332                 assert.equal(entry.buildRequestId(), 16733);
1333                 assert.ok(!entry.isPending());
1334                 assert.ok(entry.isInProgress());
1335                 assert.ok(!entry.hasFinished());
1336                 assert.equal(entry.url(), 'http://build.webkit.org/builders/ABTest-iPad-RunBenchmark-Tests/builds/614');
1337
1338                 entry = entries[2];
1339                 assert.ok(entry instanceof BuildbotBuildEntry);
1340                 assert.equal(entry.buildNumber(), 1755);
1341                 assert.equal(entry.slaveName(), 'ABTest-iPad-0');
1342                 assert.equal(entry.buildRequestId(), 18935);
1343                 assert.ok(!entry.isPending());
1344                 assert.ok(!entry.isInProgress());
1345                 assert.ok(entry.hasFinished());
1346                 assert.equal(entry.url(), 'http://build.webkit.org/builders/ABTest-iPad-RunBenchmark-Tests/builds/1755');
1347             });
1348         });
1349
1350         it('should sort BuildbotBuildEntry by order', () => {
1351             let syncer = BuildbotSyncer._loadConfig(MockRemoteAPI, sampleiOSConfig(), builderNameToIDMap())[1];
1352
1353             let promise = syncer.pullBuildbot(5);
1354             assert.equal(requests.length, 1);
1355
1356             requests[0].resolve([samplePendingBuild(456, 2), samplePendingBuild(123, 1)]);
1357
1358             return MockRemoteAPI.waitForRequest().then(() => {
1359                 assert.equal(requests.length, 2);
1360                 requests[1].resolve({[-3]: sampleFinishedBuild(), [-1]: {'error': 'Not available'}, [-2]: sampleInProgressBuild()});
1361                 return promise;
1362             }).then((entries) => {
1363                 assert.deepEqual(entries.length, 4);
1364
1365                 let entry = entries[0];
1366                 assert.ok(entry instanceof BuildbotBuildEntry);
1367                 assert.equal(entry.buildNumber(), null);
1368                 assert.equal(entry.slaveName(), null);
1369                 assert.equal(entry.buildRequestId(), 123);
1370                 assert.ok(entry.isPending());
1371                 assert.ok(!entry.isInProgress());
1372                 assert.ok(!entry.hasFinished());
1373                 assert.equal(entry.url(), 'http://build.webkit.org/builders/ABTest-iPad-RunBenchmark-Tests/');
1374
1375                 entry = entries[1];
1376                 assert.ok(entry instanceof BuildbotBuildEntry);
1377                 assert.equal(entry.buildNumber(), null);
1378                 assert.equal(entry.slaveName(), null);
1379                 assert.equal(entry.buildRequestId(), 456);
1380                 assert.ok(entry.isPending());
1381                 assert.ok(!entry.isInProgress());
1382                 assert.ok(!entry.hasFinished());
1383                 assert.equal(entry.url(), 'http://build.webkit.org/builders/ABTest-iPad-RunBenchmark-Tests/');
1384
1385                 entry = entries[2];
1386                 assert.ok(entry instanceof BuildbotBuildEntry);
1387                 assert.equal(entry.buildNumber(), 614);
1388                 assert.equal(entry.slaveName(), 'ABTest-iPad-0');
1389                 assert.equal(entry.buildRequestId(), 16733);
1390                 assert.ok(!entry.isPending());
1391                 assert.ok(entry.isInProgress());
1392                 assert.ok(!entry.hasFinished());
1393                 assert.equal(entry.url(), 'http://build.webkit.org/builders/ABTest-iPad-RunBenchmark-Tests/builds/614');
1394
1395                 entry = entries[3];
1396                 assert.ok(entry instanceof BuildbotBuildEntry);
1397                 assert.equal(entry.buildNumber(), 1755);
1398                 assert.equal(entry.slaveName(), 'ABTest-iPad-0');
1399                 assert.equal(entry.buildRequestId(), 18935);
1400                 assert.ok(!entry.isPending());
1401                 assert.ok(!entry.isInProgress());
1402                 assert.ok(entry.hasFinished());
1403                 assert.equal(entry.url(), 'http://build.webkit.org/builders/ABTest-iPad-RunBenchmark-Tests/builds/1755');
1404             });
1405         });
1406
1407         it('should override BuildbotBuildEntry for pending builds by in-progress builds', () => {
1408             let syncer = BuildbotSyncer._loadConfig(MockRemoteAPI, sampleiOSConfig(), builderNameToIDMap())[1];
1409
1410             let promise = syncer.pullBuildbot(5);
1411             assert.equal(requests.length, 1);
1412
1413             requests[0].resolve([samplePendingBuild()]);
1414
1415             return MockRemoteAPI.waitForRequest().then(() => {
1416                 assert.equal(requests.length, 2);
1417                 requests[1].resolve({[-1]: sampleInProgressBuild()});
1418                 return promise;
1419             }).then((entries) => {
1420                 assert.equal(entries.length, 1);
1421
1422                 let entry = entries[0];
1423                 assert.ok(entry instanceof BuildbotBuildEntry);
1424                 assert.equal(entry.buildNumber(), 614);
1425                 assert.equal(entry.slaveName(), 'ABTest-iPad-0');
1426                 assert.equal(entry.buildRequestId(), 16733);
1427                 assert.ok(!entry.isPending());
1428                 assert.ok(entry.isInProgress());
1429                 assert.ok(!entry.hasFinished());
1430                 assert.equal(entry.url(), 'http://build.webkit.org/builders/ABTest-iPad-RunBenchmark-Tests/builds/614');
1431             });
1432         });
1433
1434         it('should override BuildbotBuildEntry for pending builds by finished builds', () => {
1435             let syncer = BuildbotSyncer._loadConfig(MockRemoteAPI, sampleiOSConfig(), builderNameToIDMap())[1];
1436
1437             let promise = syncer.pullBuildbot(5);
1438             assert.equal(requests.length, 1);
1439
1440             requests[0].resolve([samplePendingBuild()]);
1441
1442             return MockRemoteAPI.waitForRequest().then(() => {
1443                 assert.equal(requests.length, 2);
1444                 requests[1].resolve({[-1]: sampleFinishedBuild(16733)});
1445                 return promise;
1446             }).then((entries) => {
1447                 assert.equal(entries.length, 1);
1448
1449                 let entry = entries[0];
1450                 assert.ok(entry instanceof BuildbotBuildEntry);
1451                 assert.equal(entry.buildNumber(), 1755);
1452                 assert.equal(entry.slaveName(), 'ABTest-iPad-0');
1453                 assert.equal(entry.buildRequestId(), 16733);
1454                 assert.ok(!entry.isPending());
1455                 assert.ok(!entry.isInProgress());
1456                 assert.ok(entry.hasFinished());
1457                 assert.equal(entry.url(), 'http://build.webkit.org/builders/ABTest-iPad-RunBenchmark-Tests/builds/1755');
1458             });
1459         });
1460     });
1461
1462     describe('scheduleRequest', () => {
1463         it('should schedule a build request on a specified slave', () => {
1464             let syncer = BuildbotSyncer._loadConfig(MockRemoteAPI, sampleiOSConfig(), builderNameToIDMap())[0];
1465
1466             const waitForRequest = MockRemoteAPI.waitForRequest();
1467             const request = createSampleBuildRequest(MockModels.iphone, MockModels.speedometer);
1468             syncer.scheduleRequest(request, [request], 'some-slave');
1469             return waitForRequest.then(() => {
1470                 assert.equal(requests.length, 1);
1471                 assert.equal(requests[0].url, '/builders/ABTest-iPhone-RunBenchmark-Tests/force');
1472                 assert.equal(requests[0].method, 'POST');
1473                 assert.deepEqual(requests[0].data, {
1474                     'build_request_id': '16733-' + MockModels.iphone.id(),
1475                     'desired_image': '13A452',
1476                     "opensource": "197463",
1477                     'forcescheduler': 'ABTest-iPhone-RunBenchmark-Tests-ForceScheduler',
1478                     'slavename': 'some-slave',
1479                     'test_name': 'speedometer'
1480                 });
1481             });
1482         });
1483     });
1484
1485     describe('scheduleRequestInGroupIfAvailable', () => {
1486
1487         function pullBuildbotWithAssertion(syncer, pendingBuilds, inProgressAndFinishedBuilds)
1488         {
1489             const promise = syncer.pullBuildbot(5);
1490             assert.equal(requests.length, 1);
1491             requests[0].resolve(pendingBuilds);
1492             return MockRemoteAPI.waitForRequest().then(() => {
1493                 assert.equal(requests.length, 2);
1494                 requests[1].resolve(inProgressAndFinishedBuilds);
1495                 requests.length = 0;
1496                 return promise;
1497             });
1498         }
1499
1500         it('should schedule a build if builder has no builds if slaveList is not specified', () => {
1501             let syncer = BuildbotSyncer._loadConfig(MockRemoteAPI, smallConfiguration(), builderNameToIDMap())[0];
1502
1503             return pullBuildbotWithAssertion(syncer, [], {}).then(() => {
1504                 const request = createSampleBuildRequest(MockModels.somePlatform, MockModels.someTest);
1505                 syncer.scheduleRequestInGroupIfAvailable(request, [request]);
1506                 assert.equal(requests.length, 1);
1507                 assert.equal(requests[0].url, '/builders/some%20builder/force');
1508                 assert.equal(requests[0].method, 'POST');
1509                 assert.deepEqual(requests[0].data, {id: '16733-' + MockModels.somePlatform.id(), 'os': '13A452', 'wk': '197463'});
1510             });
1511         });
1512
1513         it('should schedule a build if builder only has finished builds if slaveList is not specified', () => {
1514             let syncer = BuildbotSyncer._loadConfig(MockRemoteAPI, smallConfiguration(), builderNameToIDMap())[0];
1515
1516             return pullBuildbotWithAssertion(syncer, [], {[-1]: smallFinishedBuild()}).then(() => {
1517                 const request = createSampleBuildRequest(MockModels.somePlatform, MockModels.someTest);
1518                 syncer.scheduleRequestInGroupIfAvailable(request, [request]);
1519                 assert.equal(requests.length, 1);
1520                 assert.equal(requests[0].url, '/builders/some%20builder/force');
1521                 assert.equal(requests[0].method, 'POST');
1522                 assert.deepEqual(requests[0].data, {id: '16733-' + MockModels.somePlatform.id(), 'os': '13A452', 'wk': '197463'});
1523             });
1524         });
1525
1526         it('should not schedule a build if builder has a pending build if slaveList is not specified', () => {
1527             let syncer = BuildbotSyncer._loadConfig(MockRemoteAPI, smallConfiguration(), builderNameToIDMap())[0];
1528
1529             return pullBuildbotWithAssertion(syncer, [smallPendingBuild()], {}).then(() => {
1530                 syncer.scheduleRequestInGroupIfAvailable(createSampleBuildRequest(MockModels.somePlatform, MockModels.someTest));
1531                 assert.equal(requests.length, 0);
1532             });
1533         });
1534
1535         it('should schedule a build if builder does not have pending or completed builds on the matching slave', () => {
1536             let syncer = BuildbotSyncer._loadConfig(MockRemoteAPI, sampleiOSConfig(), builderNameToIDMap())[0];
1537
1538             return pullBuildbotWithAssertion(syncer, [], {}).then(() => {
1539                 const request = createSampleBuildRequest(MockModels.iphone, MockModels.speedometer);
1540                 syncer.scheduleRequestInGroupIfAvailable(request, [request], null);
1541                 assert.equal(requests.length, 1);
1542                 assert.equal(requests[0].url, '/builders/ABTest-iPhone-RunBenchmark-Tests/force');
1543                 assert.equal(requests[0].method, 'POST');
1544             });
1545         });
1546
1547         it('should schedule a build if builder only has finished builds on the matching slave', () => {
1548             let syncer = BuildbotSyncer._loadConfig(MockRemoteAPI, sampleiOSConfig(), builderNameToIDMap())[1];
1549
1550             pullBuildbotWithAssertion(syncer, [], {[-1]: sampleFinishedBuild()}).then(() => {
1551                 const request = createSampleBuildRequest(MockModels.ipad, MockModels.speedometer);
1552                 syncer.scheduleRequestInGroupIfAvailable(request, [request], null);
1553                 assert.equal(requests.length, 1);
1554                 assert.equal(requests[0].url, '/builders/ABTest-iPad-RunBenchmark-Tests/force');
1555                 assert.equal(requests[0].method, 'POST');
1556             });
1557         });
1558
1559         it('should not schedule a build if builder has a pending build on the maching slave', () => {
1560             let syncer = BuildbotSyncer._loadConfig(MockRemoteAPI, sampleiOSConfig(), builderNameToIDMap())[1];
1561
1562             pullBuildbotWithAssertion(syncer, [samplePendingBuild()], {}).then(() => {
1563                 const request = createSampleBuildRequest(MockModels.ipad, MockModels.speedometer);
1564                 syncer.scheduleRequestInGroupIfAvailable(request, [request], null);
1565                 assert.equal(requests.length, 0);
1566             });
1567         });
1568
1569         it('should schedule a build if builder only has a pending build on a non-maching slave', () => {
1570             let syncer = BuildbotSyncer._loadConfig(MockRemoteAPI, sampleiOSConfig(), builderNameToIDMap())[1];
1571
1572             return pullBuildbotWithAssertion(syncer, [samplePendingBuild(1, 1, 'another-slave')], {}).then(() => {
1573                 const request = createSampleBuildRequest(MockModels.ipad, MockModels.speedometer);
1574                 syncer.scheduleRequestInGroupIfAvailable(request, [request], null);
1575                 assert.equal(requests.length, 1);
1576             });
1577         });
1578
1579         it('should schedule a build if builder only has an in-progress build on the matching slave', () => {
1580             let syncer = BuildbotSyncer._loadConfig(MockRemoteAPI, sampleiOSConfig(), builderNameToIDMap())[1];
1581
1582             return pullBuildbotWithAssertion(syncer, [], {[-1]: sampleInProgressBuild()}).then(() => {
1583                 const request = createSampleBuildRequest(MockModels.ipad, MockModels.speedometer);
1584                 syncer.scheduleRequestInGroupIfAvailable(request, [request], null);
1585                 assert.equal(requests.length, 1);
1586             });
1587         });
1588
1589         it('should schedule a build if builder has an in-progress build on another slave', () => {
1590             let syncer = BuildbotSyncer._loadConfig(MockRemoteAPI, sampleiOSConfig(), builderNameToIDMap())[1];
1591
1592             return pullBuildbotWithAssertion(syncer, [], {[-1]: sampleInProgressBuild('other-slave')}).then(() => {
1593                 const request = createSampleBuildRequest(MockModels.ipad, MockModels.speedometer);
1594                 syncer.scheduleRequestInGroupIfAvailable(request, [request], null);
1595                 assert.equal(requests.length, 1);
1596             });
1597         });
1598
1599         it('should not schedule a build if the request does not match any configuration', () => {
1600             let syncer = BuildbotSyncer._loadConfig(MockRemoteAPI, sampleiOSConfig(), builderNameToIDMap())[0];
1601
1602             return pullBuildbotWithAssertion(syncer, [], {}).then(() => {
1603                 const request = createSampleBuildRequest(MockModels.ipad, MockModels.speedometer);
1604                 syncer.scheduleRequestInGroupIfAvailable(request, [request], null);
1605                 assert.equal(requests.length, 0);
1606             });
1607         });
1608
1609         it('should not schedule a build if a new request had been submitted to the same slave', (done) => {
1610             let syncer = BuildbotSyncer._loadConfig(MockRemoteAPI, sampleiOSConfig(), builderNameToIDMap())[1];
1611
1612             pullBuildbotWithAssertion(syncer, [], {}).then(() => {
1613                 let request = createSampleBuildRequest(MockModels.ipad, MockModels.speedometer);
1614                 syncer.scheduleRequest(request, [request], 'ABTest-iPad-0');
1615                 request = createSampleBuildRequest(MockModels.ipad, MockModels.speedometer);
1616                 syncer.scheduleRequest(request, [request], 'ABTest-iPad-1');
1617             }).then(() => {
1618                 assert.equal(requests.length, 2);
1619                 const request = createSampleBuildRequest(MockModels.ipad, MockModels.speedometer);
1620                 syncer.scheduleRequestInGroupIfAvailable(request, [request], null);
1621             }).then(() => {
1622                 assert.equal(requests.length, 2);
1623                 done();
1624             }).catch(done);
1625         });
1626
1627         it('should schedule a build if a new request had been submitted to another slave', () => {
1628             let syncer = BuildbotSyncer._loadConfig(MockRemoteAPI, sampleiOSConfig(), builderNameToIDMap())[1];
1629
1630             return pullBuildbotWithAssertion(syncer, [], {}).then(() => {
1631                 let request = createSampleBuildRequest(MockModels.ipad, MockModels.speedometer);
1632                 syncer.scheduleRequest(request, [request], 'ABTest-iPad-0');
1633                 assert.equal(requests.length, 1);
1634                 request = createSampleBuildRequest(MockModels.ipad, MockModels.speedometer)
1635                 syncer.scheduleRequestInGroupIfAvailable(request, [request], 'ABTest-iPad-1');
1636                 assert.equal(requests.length, 2);
1637             });
1638         });
1639
1640         it('should not schedule a build if a new request had been submitted to the same builder without slaveList', () => {
1641             let syncer = BuildbotSyncer._loadConfig(MockRemoteAPI, smallConfiguration(), builderNameToIDMap())[0];
1642
1643             return pullBuildbotWithAssertion(syncer, [], {}).then(() => {
1644                 let request = createSampleBuildRequest(MockModels.somePlatform, MockModels.someTest);
1645                 syncer.scheduleRequest(request, [request], null);
1646                 assert.equal(requests.length, 1);
1647                 request = createSampleBuildRequest(MockModels.somePlatform, MockModels.someTest);
1648                 syncer.scheduleRequestInGroupIfAvailable(request, [request], null);
1649                 assert.equal(requests.length, 1);
1650             });
1651         });
1652     });
1653 });