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