Make unit tests return a promise instead of manually calling done
[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         'shared':
16             {
17                 'arguments': {
18                     'desired_image': {'root': 'iOS'},
19                     'opensource': {'rootOptions': ['WebKit-SVN', 'WebKit-Git']},
20                     'roots_dict': {'rootsExcluding': ['iOS']}
21                 },
22                 'slaveArgument': 'slavename',
23                 'buildRequestArgument': 'build_request_id'
24             },
25         'types': {
26             'speedometer': {
27                 'test': ['Speedometer'],
28                 'arguments': {'test_name': 'speedometer'}
29             },
30             'jetstream': {
31                 'test': ['JetStream'],
32                 'arguments': {'test_name': 'jetstream'}
33             },
34             'dromaeo-dom': {
35                 'test': ['Dromaeo', 'DOM Core Tests'],
36                 'arguments': {'tests': 'dromaeo-dom'}
37             },
38         },
39         'builders': {
40             'iPhone-bench': {
41                 'builder': 'ABTest-iPhone-RunBenchmark-Tests',
42                 'arguments': { 'forcescheduler': 'ABTest-iPhone-RunBenchmark-Tests-ForceScheduler' },
43                 'slaveList': ['ABTest-iPhone-0'],
44             },
45             'iPad-bench': {
46                 'builder': 'ABTest-iPad-RunBenchmark-Tests',
47                 'arguments': { 'forcescheduler': 'ABTest-iPad-RunBenchmark-Tests-ForceScheduler' },
48                 'slaveList': ['ABTest-iPad-0', 'ABTest-iPad-1'],
49             }
50         },
51         'configurations': [
52             {'type': 'speedometer', 'builder': 'iPhone-bench', 'platform': 'iPhone'},
53             {'type': 'jetstream', 'builder': 'iPhone-bench', 'platform': 'iPhone'},
54             {'type': 'dromaeo-dom', 'builder': 'iPhone-bench', 'platform': 'iPhone'},
55
56             {'type': 'speedometer', 'builder': 'iPad-bench', 'platform': 'iPad'},
57             {'type': 'jetstream', 'builder': 'iPad-bench', 'platform': 'iPad'},
58         ]
59     };
60 }
61
62 function sampleiOSConfigWithExpansions()
63 {
64     return {
65         "triggerableName": "build-webkit-ios",
66         "shared":
67             {
68                 "arguments": {
69                     "webkit-revision": {"root": "WebKit"},
70                     "os-version": {"root": "iOS"}
71                 },
72                 "buildRequestArgument": "build-request-id"
73             },
74         "types": {
75             "iphone-plt": {
76                 "test": ["PLT-iPhone"],
77                 "arguments": {"test_name": "plt"}
78             },
79             "ipad-plt": {
80                 "test": ["PLT-iPad"],
81                 "arguments": {"test_name": "plt"}
82             },
83             "speedometer": {
84                 "test": ["Speedometer"],
85                 "arguments": {"tests": "speedometer"}
86             },
87         },
88         "builders": {
89             "iphone": {
90                 "builder": "iPhone AB Tests",
91                 "arguments": {"forcescheduler": "force-iphone-ab-tests"}
92             },
93             "ipad": {
94                 "builder": "iPad AB Tests",
95                 "arguments": {"forcescheduler": "force-ipad-ab-tests"}
96             },
97         },
98         "configurations": [
99             {
100                 "builder": "iphone",
101                 "platforms": ["iPhone", "iOS 10 iPhone"],
102                 "types": ["iphone-plt", "speedometer"],
103             },
104             {
105                 "builder": "ipad",
106                 "platforms": ["iPad"],
107                 "types": ["ipad-plt", "speedometer"],
108             },
109         ]
110     }
111 }
112
113 let sampleCommitSetData = {
114     'WebKit': {
115         'id': '111127',
116         'time': 1456955807334,
117         'repository': 'WebKit',
118         'revision': '197463',
119     },
120     'Shared': {
121         'id': '111237',
122         'time': 1456931874000,
123         'repository': 'Shared',
124         'revision': '80229',
125     },
126     'WebKit-Git': {
127         "id":"111239",
128         "time":1456931874000,
129         "repository":"WebKit-Git",
130         "revision":"9abcdef",
131     },
132 };
133
134 function smallConfiguration()
135 {
136     return {
137         'builder': 'some builder',
138         'platform': 'Some platform',
139         'test': ['Some test'],
140         'arguments': {},
141         'buildRequestArgument': 'id'};
142 }
143
144 function smallPendingBuild()
145 {
146     return {
147         'builderName': 'some builder',
148         'builds': [],
149         'properties': [],
150         'source': {
151             'branch': '',
152             'changes': [],
153             'codebase': 'WebKit',
154             'hasPatch': false,
155             'project': '',
156             'repository': '',
157             'revision': ''
158         },
159     };
160 }
161
162 function smallInProgressBuild()
163 {
164     return {
165         'builderName': 'some builder',
166         'builds': [],
167         'properties': [],
168         'currentStep': { },
169         'eta': 123,
170         'number': 456,
171         'source': {
172             'branch': '',
173             'changes': [],
174             'codebase': 'WebKit',
175             'hasPatch': false,
176             'project': '',
177             'repository': '',
178             'revision': ''
179         },
180     };
181 }
182
183 function smallFinishedBuild()
184 {
185     return {
186         'builderName': 'some builder',
187         'builds': [],
188         'properties': [],
189         'currentStep': null,
190         'eta': null,
191         'number': 789,
192         'source': {
193             'branch': '',
194             'changes': [],
195             'codebase': 'WebKit',
196             'hasPatch': false,
197             'project': '',
198             'repository': '',
199             'revision': ''
200         },
201         'times': [0, 1],
202     };
203 }
204
205 function createSampleBuildRequest(platform, test)
206 {
207     assert(platform instanceof Platform);
208     assert(test instanceof Test);
209
210     let commitSet = CommitSet.ensureSingleton('4197', {commits: [
211         {'id': '111127', 'time': 1456955807334, 'repository': MockModels.webkit, 'revision': '197463'},
212         {'id': '111237', 'time': 1456931874000, 'repository': MockModels.sharedRepository, 'revision': '80229'},
213         {'id': '111239', 'time': 1456931874000, 'repository': MockModels.webkitGit, 'revision': '9abcdef'},
214         {'id': '88930', 'time': 0, 'repository': MockModels.ios, 'revision': '13A452'},
215     ]});
216
217     let request = BuildRequest.ensureSingleton('16733-' + platform.id(), {'commitSet': commitSet, 'status': 'pending', 'platform': platform, 'test': test});
218     return request;
219 }
220
221 function samplePendingBuild(buildRequestId, buildTime, slaveName)
222 {
223     return {
224         'builderName': 'ABTest-iPad-RunBenchmark-Tests',
225         'builds': [],
226         'properties': [
227             ['build_request_id', buildRequestId || '16733', 'Force Build Form'],
228             ['desired_image', '13A452', 'Force Build Form'],
229             ['owner', '<unknown>', 'Force Build Form'],
230             ['test_name', 'speedometer', 'Force Build Form'],
231             ['reason', 'force build','Force Build Form'],
232             [
233                 'roots_dict',
234                 JSON.stringify(sampleCommitSetData),
235                 'Force Build Form'
236             ],
237             ['slavename', slaveName, ''],
238             ['scheduler', 'ABTest-iPad-RunBenchmark-Tests-ForceScheduler', 'Scheduler']
239         ],
240         'source': {
241             'branch': '',
242             'changes': [],
243             'codebase': 'compiler-rt',
244             'hasPatch': false,
245             'project': '',
246             'repository': '',
247             'revision': ''
248         },
249         'submittedAt': buildTime || 1458704983
250     };
251 }
252
253 function sampleInProgressBuild(slaveName)
254 {
255     return {
256         'blame': [],
257         'builderName': 'ABTest-iPad-RunBenchmark-Tests',
258         'currentStep': {
259             'eta': 0.26548067698460565,
260             'expectations': [['output', 845, 1315.0]],
261             'hidden': false,
262             'isFinished': false,
263             'isStarted': true,
264             'logs': [['stdio', 'https://build.webkit.org/builders/ABTest-iPad-RunBenchmark-Tests/builds/614/steps/Some%20step/logs/stdio']],
265             'name': 'Some step',
266             'results': [null,[]],
267             'statistics': {},
268             'step_number': 1,
269             'text': [''],
270             'times': [1458718657.581628, null],
271             'urls': {}
272         },
273         'eta': 6497.991612434387,
274         'logs': [['stdio','https://build.webkit.org/builders/ABTest-iPad-RunBenchmark-Tests/builds/614/steps/shell/logs/stdio']],
275         'number': 614,
276         'properties': [
277             ['build_request_id', '16733', 'Force Build Form'],
278             ['buildername', 'ABTest-iPad-RunBenchmark-Tests', 'Builder'],
279             ['buildnumber', 614, 'Build'],
280             ['desired_image', '13A452', 'Force Build Form'],
281             ['owner', '<unknown>', 'Force Build Form'],
282             ['reason', 'force build', 'Force Build Form'],
283             ['roots_dict', JSON.stringify(sampleCommitSetData), 'Force Build Form'],
284             ['scheduler', 'ABTest-iPad-RunBenchmark-Tests-ForceScheduler', 'Scheduler'],
285             ['slavename', slaveName || 'ABTest-iPad-0', 'BuildSlave'],
286         ],
287         'reason': 'A build was forced by \'<unknown>\': force build',
288         'results': null,
289         'slave': 'ABTest-iPad-0',
290         'sourceStamps': [{'branch': '', 'changes': [], 'codebase': 'compiler-rt', 'hasPatch': false, 'project': '', 'repository': '', 'revision': ''}],
291         'steps': [
292             {
293                 'eta': null,
294                 'expectations': [['output',2309,2309.0]],
295                 'hidden': false,
296                 'isFinished': true,
297                 'isStarted': true,
298                 'logs': [['stdio', 'https://build.webkit.org/builders/ABTest-iPad-RunBenchmark-Tests/builds/614/steps/shell/logs/stdio']],
299                 'name': 'Finished step',
300                 'results': [0, []],
301                 'statistics': {},
302                 'step_number': 0,
303                 'text': [''],
304                 'times': [1458718655.419865, 1458718655.453633],
305                 'urls': {}
306             },
307             {
308                 'eta': 0.26548067698460565,
309                 'expectations': [['output', 845, 1315.0]],
310                 'hidden': false,
311                 'isFinished': false,
312                 'isStarted': true,
313                 'logs': [['stdio', 'https://build.webkit.org/builders/ABTest-iPad-RunBenchmark-Tests/builds/614/steps/Some%20step/logs/stdio']],
314                 'name': 'Some step',
315                 'results': [null,[]],
316                 'statistics': {},
317                 'step_number': 1,
318                 'text': [''],
319                 'times': [1458718657.581628, null],
320                 'urls': {}
321             },
322             {
323                 'eta': null,
324                 'expectations': [['output', null, null]],
325                 'hidden': false,
326                 'isFinished': false,
327                 'isStarted': false,
328                 'logs': [],
329                 'name': 'Some other step',
330                 'results': [null, []],
331                 'statistics': {},
332                 'step_number': 2,
333                 'text': [],
334                 'times': [null, null],
335                 'urls': {}
336             },
337         ],
338         'text': [],
339         'times': [1458718655.415821, null]
340     };
341 }
342
343 function sampleFinishedBuild(buildRequestId, slaveName)
344 {
345     return {
346         'blame': [],
347         'builderName': 'ABTest-iPad-RunBenchmark-Tests',
348         'currentStep': null,
349         'eta': null,
350         'logs': [['stdio','https://build.webkit.org/builders/ABTest-iPad-RunBenchmark-Tests/builds/1755/steps/shell/logs/stdio']],
351         'number': 1755,
352         'properties': [
353             ['build_request_id', buildRequestId || '18935', 'Force Build Form'],
354             ['buildername', 'ABTest-iPad-RunBenchmark-Tests', 'Builder'],
355             ['buildnumber', 1755, 'Build'],
356             ['desired_image', '13A452', 'Force Build Form'],
357             ['owner', '<unknown>', 'Force Build Form'],
358             ['reason', 'force build', 'Force Build Form'],
359             ['roots_dict', JSON.stringify(sampleCommitSetData), 'Force Build Form'],
360             ['scheduler', 'ABTest-iPad-RunBenchmark-Tests-ForceScheduler', 'Scheduler'],
361             ['slavename', slaveName || 'ABTest-iPad-0', 'BuildSlave'],
362         ],
363         'reason': 'A build was forced by \'<unknown>\': force build',
364         'results': 2,
365         'slave': 'ABTest-iPad-0',
366         'sourceStamps': [{'branch': '', 'changes': [], 'codebase': 'compiler-rt', 'hasPatch': false, 'project': '', 'repository': '', 'revision': ''}],
367         'steps': [
368             {
369                 'eta': null,
370                 'expectations': [['output',2309,2309.0]],
371                 'hidden': false,
372                 'isFinished': true,
373                 'isStarted': true,
374                 'logs': [['stdio', 'https://build.webkit.org/builders/ABTest-iPad-RunBenchmark-Tests/builds/614/steps/shell/logs/stdio']],
375                 'name': 'Finished step',
376                 'results': [0, []],
377                 'statistics': {},
378                 'step_number': 0,
379                 'text': [''],
380                 'times': [1458718655.419865, 1458718655.453633],
381                 'urls': {}
382             },
383             {
384                 'eta': null,
385                 'expectations': [['output', 845, 1315.0]],
386                 'hidden': false,
387                 'isFinished': true,
388                 'isStarted': true,
389                 'logs': [['stdio', 'https://build.webkit.org/builders/ABTest-iPad-RunBenchmark-Tests/builds/614/steps/Some%20step/logs/stdio']],
390                 'name': 'Some step',
391                 'results': [null,[]],
392                 'statistics': {},
393                 'step_number': 1,
394                 'text': [''],
395                 'times': [1458718657.581628, null],
396                 'urls': {}
397             },
398             {
399                 'eta': null,
400                 'expectations': [['output', null, null]],
401                 'hidden': false,
402                 'isFinished': true,
403                 'isStarted': true,
404                 'logs': [],
405                 'name': 'Some other step',
406                 'results': [null, []],
407                 'statistics': {},
408                 'step_number': 2,
409                 'text': [],
410                 'times': [null, null],
411                 'urls': {}
412             },
413         ],
414         'text': [],
415         'times': [1458937478.25837, 1458946147.173785]
416     };
417 }
418
419 describe('BuildbotSyncer', () => {
420     MockModels.inject();
421     let requests = MockRemoteAPI.inject('http://build.webkit.org');
422
423     describe('_loadConfig', () => {
424
425         it('should create BuildbotSyncer objects for a configuration that specify all required options', () => {
426             let syncers = BuildbotSyncer._loadConfig(MockRemoteAPI, {'configurations': [smallConfiguration()]});
427             assert.equal(syncers.length, 1);
428         });
429
430         it('should throw when some required options are missing', () => {
431             assert.throws(() => {
432                 let config = smallConfiguration();
433                 delete config['builder'];
434                 BuildbotSyncer._loadConfig(MockRemoteAPI, {'configurations': [config]});
435             }, 'builder should be a required option');
436             assert.throws(() => {
437                 let config = smallConfiguration();
438                 delete config['platform'];
439                 BuildbotSyncer._loadConfig(MockRemoteAPI, {'configurations': [config]});
440             }, 'platform should be a required option');
441             assert.throws(() => {
442                 let config = smallConfiguration();
443                 delete config['test'];
444                 BuildbotSyncer._loadConfig(MockRemoteAPI, {'configurations': [config]});
445             }, 'test should be a required option');
446             assert.throws(() => {
447                 let config = smallConfiguration();
448                 delete config['arguments'];
449                 BuildbotSyncer._loadConfig(MockRemoteAPI, {'configurations': [config]});
450             });
451             assert.throws(() => {
452                 let config = smallConfiguration();
453                 delete config['buildRequestArgument'];
454                 BuildbotSyncer._loadConfig(MockRemoteAPI, {'configurations': [config]});
455             });
456         });
457
458         it('should throw when a test name is not an array of strings', () => {
459             assert.throws(() => {
460                 let config = smallConfiguration();
461                 config.test = 'some test';
462                 BuildbotSyncer._loadConfig(MockRemoteAPI, {'configurations': [config]});
463             });
464             assert.throws(() => {
465                 let config = smallConfiguration();
466                 config.test = [1];
467                 BuildbotSyncer._loadConfig(MockRemoteAPI, {'configurations': [config]});
468             });
469         });
470
471         it('should throw when arguments is not an object', () => {
472             assert.throws(() => {
473                 let config = smallConfiguration();
474                 config.arguments = 'hello';
475                 BuildbotSyncer._loadConfig(MockRemoteAPI, {'configurations': [config]});
476             });
477         });
478
479         it('should throw when arguments\'s values are malformed', () => {
480             assert.throws(() => {
481                 let config = smallConfiguration();
482                 config.arguments = {'some': {'root': 'some root', 'rootsExcluding': ['other root']}};
483                 BuildbotSyncer._loadConfig(RemoteAPI, {'configurations': [config]});
484             });
485             assert.throws(() => {
486                 let config = smallConfiguration();
487                 config.arguments = {'some': {'otherKey': 'some root'}};
488                 BuildbotSyncer._loadConfig(RemoteAPI, {'configurations': [config]});
489             });
490             assert.throws(() => {
491                 let config = smallConfiguration();
492                 config.arguments = {'some': {'root': ['a', 'b']}};
493                 BuildbotSyncer._loadConfig(RemoteAPI, {'configurations': [config]});
494             });
495             assert.throws(() => {
496                 let config = smallConfiguration();
497                 config.arguments = {'some': {'root': 1}};
498                 BuildbotSyncer._loadConfig(RemoteAPI, {'configurations': [config]});
499             });
500             assert.throws(() => {
501                 let config = smallConfiguration();
502                 config.arguments = {'some': {'rootsExcluding': 'a'}};
503                 BuildbotSyncer._loadConfig(RemoteAPI, {'configurations': [config]});
504             });
505             assert.throws(() => {
506                 let config = smallConfiguration();
507                 config.arguments = {'some': {'rootsExcluding': [1]}};
508                 BuildbotSyncer._loadConfig(RemoteAPI, {'configurations': [config]});
509             });
510         });
511
512         it('should create BuildbotSyncer objects for valid configurations', () => {
513             let syncers = BuildbotSyncer._loadConfig(RemoteAPI, sampleiOSConfig());
514             assert.equal(syncers.length, 2);
515             assert.ok(syncers[0] instanceof BuildbotSyncer);
516             assert.ok(syncers[1] instanceof BuildbotSyncer);
517         });
518
519         it('should parse builder names correctly', () => {
520             let syncers = BuildbotSyncer._loadConfig(RemoteAPI, sampleiOSConfig());
521             assert.equal(syncers[0].builderName(), 'ABTest-iPhone-RunBenchmark-Tests');
522             assert.equal(syncers[1].builderName(), 'ABTest-iPad-RunBenchmark-Tests');
523         });
524
525         it('should parse test configurations correctly', () => {
526             let syncers = BuildbotSyncer._loadConfig(RemoteAPI, sampleiOSConfig());
527
528             let configurations = syncers[0].testConfigurations();
529             assert.equal(configurations.length, 3);
530             assert.equal(configurations[0].platform, MockModels.iphone);
531             assert.equal(configurations[0].test, MockModels.speedometer);
532             assert.equal(configurations[1].platform, MockModels.iphone);
533             assert.equal(configurations[1].test, MockModels.jetstream);
534             assert.equal(configurations[2].platform, MockModels.iphone);
535             assert.equal(configurations[2].test, MockModels.domcore);
536
537             configurations = syncers[1].testConfigurations();
538             assert.equal(configurations.length, 2);
539             assert.equal(configurations[0].platform, MockModels.ipad);
540             assert.equal(configurations[0].test, MockModels.speedometer);
541             assert.equal(configurations[1].platform, MockModels.ipad);
542             assert.equal(configurations[1].test, MockModels.jetstream);
543         });
544
545         it('should parse test configurations with types and platforms expansions correctly', () => {
546             let syncers = BuildbotSyncer._loadConfig(RemoteAPI, sampleiOSConfigWithExpansions());
547
548             assert.equal(syncers.length, 2);
549
550             let configurations = syncers[0].testConfigurations();
551             assert.equal(configurations.length, 4);
552             assert.equal(configurations[0].platform, MockModels.iphone);
553             assert.equal(configurations[0].test, MockModels.iPhonePLT);
554             assert.equal(configurations[1].platform, MockModels.iOS10iPhone);
555             assert.equal(configurations[1].test, MockModels.iPhonePLT);
556             assert.equal(configurations[2].platform, MockModels.iphone);
557             assert.equal(configurations[2].test, MockModels.speedometer);
558             assert.equal(configurations[3].platform, MockModels.iOS10iPhone);
559             assert.equal(configurations[3].test, MockModels.speedometer);
560
561             configurations = syncers[1].testConfigurations();
562             assert.equal(configurations.length, 2);
563             assert.equal(configurations[0].platform, MockModels.ipad);
564             assert.equal(configurations[0].test, MockModels.iPadPLT);
565             assert.equal(configurations[1].platform, MockModels.ipad);
566             assert.equal(configurations[1].test, MockModels.speedometer);
567         });
568     });
569
570     describe('_propertiesForBuildRequest', () => {
571         it('should include all properties specified in a given configuration', () => {
572             let syncers = BuildbotSyncer._loadConfig(RemoteAPI, sampleiOSConfig());
573             let properties = syncers[0]._propertiesForBuildRequest(createSampleBuildRequest(MockModels.iphone, MockModels.speedometer));
574             assert.deepEqual(Object.keys(properties), ['desired_image', 'opensource', 'roots_dict', 'test_name', 'forcescheduler', 'build_request_id']);
575         });
576
577         it('should preserve non-parametric property values', () => {
578             let syncers = BuildbotSyncer._loadConfig(RemoteAPI, sampleiOSConfig());
579             let properties = syncers[0]._propertiesForBuildRequest(createSampleBuildRequest(MockModels.iphone, MockModels.speedometer));
580             assert.equal(properties['test_name'], 'speedometer');
581             assert.equal(properties['forcescheduler'], 'ABTest-iPhone-RunBenchmark-Tests-ForceScheduler');
582
583             properties = syncers[1]._propertiesForBuildRequest(createSampleBuildRequest(MockModels.ipad, MockModels.jetstream));
584             assert.equal(properties['test_name'], 'jetstream');
585             assert.equal(properties['forcescheduler'], 'ABTest-iPad-RunBenchmark-Tests-ForceScheduler');
586         });
587
588         it('should resolve "root"', () => {
589             let syncers = BuildbotSyncer._loadConfig(RemoteAPI, sampleiOSConfig());
590             let properties = syncers[0]._propertiesForBuildRequest(createSampleBuildRequest(MockModels.iphone, MockModels.speedometer));
591             assert.equal(properties['desired_image'], '13A452');
592         });
593
594         it('should resolve "rootOptions"', () => {
595             let syncers = BuildbotSyncer._loadConfig(RemoteAPI, sampleiOSConfig());
596             let properties = syncers[0]._propertiesForBuildRequest(createSampleBuildRequest(MockModels.iphone, MockModels.speedometer));
597             assert.equal(properties['roots_dict'], JSON.stringify(sampleCommitSetData));
598         });
599
600         it('should resolve "rootsExcluding"', () => {
601             let syncers = BuildbotSyncer._loadConfig(RemoteAPI, sampleiOSConfig());
602             let properties = syncers[0]._propertiesForBuildRequest(createSampleBuildRequest(MockModels.iphone, MockModels.speedometer));
603             assert.equal(properties['roots_dict'], JSON.stringify(sampleCommitSetData));
604         });
605
606         it('should set the property for the build request id', () => {
607             let syncers = BuildbotSyncer._loadConfig(RemoteAPI, sampleiOSConfig());
608             let request = createSampleBuildRequest(MockModels.iphone, MockModels.speedometer);
609             let properties = syncers[0]._propertiesForBuildRequest(request);
610             assert.equal(properties['build_request_id'], request.id());
611         });
612     });
613
614     describe('pullBuildbot', () => {
615         it('should fetch pending builds from the right URL', () => {
616             let syncer = BuildbotSyncer._loadConfig(MockRemoteAPI, sampleiOSConfig())[1];
617             assert.equal(syncer.builderName(), 'ABTest-iPad-RunBenchmark-Tests');
618             let expectedURL = '/json/builders/ABTest-iPad-RunBenchmark-Tests/pendingBuilds';
619             assert.equal(syncer.pathForPendingBuildsJSON(), expectedURL);
620             syncer.pullBuildbot();
621             assert.equal(requests.length, 1);
622             assert.equal(requests[0].url, expectedURL);
623         });
624
625         it('should fetch recent builds once pending builds have been fetched', () => {
626             let syncer = BuildbotSyncer._loadConfig(MockRemoteAPI, sampleiOSConfig())[1];
627             assert.equal(syncer.builderName(), 'ABTest-iPad-RunBenchmark-Tests');
628
629             syncer.pullBuildbot(1);
630             assert.equal(requests.length, 1);
631             assert.equal(requests[0].url, '/json/builders/ABTest-iPad-RunBenchmark-Tests/pendingBuilds');
632             requests[0].resolve([]);
633             return MockRemoteAPI.waitForRequest().then(() => {
634                 assert.equal(requests.length, 2);
635                 assert.equal(requests[1].url, '/json/builders/ABTest-iPad-RunBenchmark-Tests/builds/?select=-1');
636             });
637         });
638
639         it('should fetch the right number of recent builds', () => {
640             let syncer = BuildbotSyncer._loadConfig(MockRemoteAPI, sampleiOSConfig())[1];
641
642             syncer.pullBuildbot(3);
643             assert.equal(requests.length, 1);
644             assert.equal(requests[0].url, '/json/builders/ABTest-iPad-RunBenchmark-Tests/pendingBuilds');
645             requests[0].resolve([]);
646             return MockRemoteAPI.waitForRequest().then(() => {
647                 assert.equal(requests.length, 2);
648                 assert.equal(requests[1].url, '/json/builders/ABTest-iPad-RunBenchmark-Tests/builds/?select=-1&select=-2&select=-3');
649             });
650         });
651
652         it('should create BuildbotBuildEntry for pending builds', () => {
653             let syncer = BuildbotSyncer._loadConfig(MockRemoteAPI, sampleiOSConfig())[1];
654             let promise = syncer.pullBuildbot();
655             requests[0].resolve([samplePendingBuild()]);
656             return promise.then((entries) => {
657                 assert.equal(entries.length, 1);
658                 let entry = entries[0];
659                 assert.ok(entry instanceof BuildbotBuildEntry);
660                 assert.ok(!entry.buildNumber());
661                 assert.ok(!entry.slaveName());
662                 assert.equal(entry.buildRequestId(), 16733);
663                 assert.ok(entry.isPending());
664                 assert.ok(!entry.isInProgress());
665                 assert.ok(!entry.hasFinished());
666                 assert.equal(entry.url(), 'http://build.webkit.org/builders/ABTest-iPad-RunBenchmark-Tests/');
667             });
668         });
669
670         it('should create BuildbotBuildEntry for in-progress builds', () => {
671             let syncer = BuildbotSyncer._loadConfig(MockRemoteAPI, sampleiOSConfig())[1];
672
673             let promise = syncer.pullBuildbot(1);
674             assert.equal(requests.length, 1);
675             requests[0].resolve([]);
676             return MockRemoteAPI.waitForRequest().then(() => {
677                 assert.equal(requests.length, 2);
678                 requests[1].resolve({[-1]: sampleInProgressBuild()});
679                 return promise;
680             }).then((entries) => {
681                 assert.equal(entries.length, 1);
682                 let entry = entries[0];
683                 assert.ok(entry instanceof BuildbotBuildEntry);
684                 assert.equal(entry.buildNumber(), 614);
685                 assert.equal(entry.slaveName(), 'ABTest-iPad-0');
686                 assert.equal(entry.buildRequestId(), 16733);
687                 assert.ok(!entry.isPending());
688                 assert.ok(entry.isInProgress());
689                 assert.ok(!entry.hasFinished());
690                 assert.equal(entry.url(), 'http://build.webkit.org/builders/ABTest-iPad-RunBenchmark-Tests/builds/614');
691             });
692         });
693
694         it('should create BuildbotBuildEntry for finished builds', () => {
695             let syncer = BuildbotSyncer._loadConfig(MockRemoteAPI, sampleiOSConfig())[1];
696
697             let promise = syncer.pullBuildbot(1);
698             assert.equal(requests.length, 1);
699             requests[0].resolve([]);
700             return MockRemoteAPI.waitForRequest().then(() => {
701                 assert.equal(requests.length, 2);
702                 requests[1].resolve({[-1]: sampleFinishedBuild()});
703                 return promise;
704             }).then((entries) => {
705                 assert.deepEqual(entries.length, 1);
706                 let entry = entries[0];
707                 assert.ok(entry instanceof BuildbotBuildEntry);
708                 assert.equal(entry.buildNumber(), 1755);
709                 assert.equal(entry.slaveName(), 'ABTest-iPad-0');
710                 assert.equal(entry.buildRequestId(), 18935);
711                 assert.ok(!entry.isPending());
712                 assert.ok(!entry.isInProgress());
713                 assert.ok(entry.hasFinished());
714                 assert.equal(entry.url(), 'http://build.webkit.org/builders/ABTest-iPad-RunBenchmark-Tests/builds/1755');
715             });
716         });
717
718         it('should create BuildbotBuildEntry for mixed pending, in-progress, finished, and missing builds', () => {
719             let syncer = BuildbotSyncer._loadConfig(MockRemoteAPI, sampleiOSConfig())[1];
720
721             let promise = syncer.pullBuildbot(5);
722             assert.equal(requests.length, 1);
723
724             requests[0].resolve([samplePendingBuild(123)]);
725
726             return MockRemoteAPI.waitForRequest().then(() => {
727                 assert.equal(requests.length, 2);
728                 requests[1].resolve({[-1]: sampleFinishedBuild(), [-2]: {'error': 'Not available'}, [-4]: sampleInProgressBuild()});
729                 return promise;
730             }).then((entries) => {
731                 assert.deepEqual(entries.length, 3);
732
733                 let entry = entries[0];
734                 assert.ok(entry instanceof BuildbotBuildEntry);
735                 assert.equal(entry.buildNumber(), null);
736                 assert.equal(entry.slaveName(), null);
737                 assert.equal(entry.buildRequestId(), 123);
738                 assert.ok(entry.isPending());
739                 assert.ok(!entry.isInProgress());
740                 assert.ok(!entry.hasFinished());
741                 assert.equal(entry.url(), 'http://build.webkit.org/builders/ABTest-iPad-RunBenchmark-Tests/');
742
743                 entry = entries[1];
744                 assert.ok(entry instanceof BuildbotBuildEntry);
745                 assert.equal(entry.buildNumber(), 614);
746                 assert.equal(entry.slaveName(), 'ABTest-iPad-0');
747                 assert.equal(entry.buildRequestId(), 16733);
748                 assert.ok(!entry.isPending());
749                 assert.ok(entry.isInProgress());
750                 assert.ok(!entry.hasFinished());
751                 assert.equal(entry.url(), 'http://build.webkit.org/builders/ABTest-iPad-RunBenchmark-Tests/builds/614');
752
753                 entry = entries[2];
754                 assert.ok(entry instanceof BuildbotBuildEntry);
755                 assert.equal(entry.buildNumber(), 1755);
756                 assert.equal(entry.slaveName(), 'ABTest-iPad-0');
757                 assert.equal(entry.buildRequestId(), 18935);
758                 assert.ok(!entry.isPending());
759                 assert.ok(!entry.isInProgress());
760                 assert.ok(entry.hasFinished());
761                 assert.equal(entry.url(), 'http://build.webkit.org/builders/ABTest-iPad-RunBenchmark-Tests/builds/1755');
762             });
763         });
764
765         it('should sort BuildbotBuildEntry by order', () => {
766             let syncer = BuildbotSyncer._loadConfig(MockRemoteAPI, sampleiOSConfig())[1];
767
768             let promise = syncer.pullBuildbot(5);
769             assert.equal(requests.length, 1);
770
771             requests[0].resolve([samplePendingBuild(456, 2), samplePendingBuild(123, 1)]);
772
773             return MockRemoteAPI.waitForRequest().then(() => {
774                 assert.equal(requests.length, 2);
775                 requests[1].resolve({[-3]: sampleFinishedBuild(), [-1]: {'error': 'Not available'}, [-2]: sampleInProgressBuild()});
776                 return promise;
777             }).then((entries) => {
778                 assert.deepEqual(entries.length, 4);
779
780                 let entry = entries[0];
781                 assert.ok(entry instanceof BuildbotBuildEntry);
782                 assert.equal(entry.buildNumber(), null);
783                 assert.equal(entry.slaveName(), null);
784                 assert.equal(entry.buildRequestId(), 123);
785                 assert.ok(entry.isPending());
786                 assert.ok(!entry.isInProgress());
787                 assert.ok(!entry.hasFinished());
788                 assert.equal(entry.url(), 'http://build.webkit.org/builders/ABTest-iPad-RunBenchmark-Tests/');
789
790                 entry = entries[1];
791                 assert.ok(entry instanceof BuildbotBuildEntry);
792                 assert.equal(entry.buildNumber(), null);
793                 assert.equal(entry.slaveName(), null);
794                 assert.equal(entry.buildRequestId(), 456);
795                 assert.ok(entry.isPending());
796                 assert.ok(!entry.isInProgress());
797                 assert.ok(!entry.hasFinished());
798                 assert.equal(entry.url(), 'http://build.webkit.org/builders/ABTest-iPad-RunBenchmark-Tests/');
799
800                 entry = entries[2];
801                 assert.ok(entry instanceof BuildbotBuildEntry);
802                 assert.equal(entry.buildNumber(), 614);
803                 assert.equal(entry.slaveName(), 'ABTest-iPad-0');
804                 assert.equal(entry.buildRequestId(), 16733);
805                 assert.ok(!entry.isPending());
806                 assert.ok(entry.isInProgress());
807                 assert.ok(!entry.hasFinished());
808                 assert.equal(entry.url(), 'http://build.webkit.org/builders/ABTest-iPad-RunBenchmark-Tests/builds/614');
809
810                 entry = entries[3];
811                 assert.ok(entry instanceof BuildbotBuildEntry);
812                 assert.equal(entry.buildNumber(), 1755);
813                 assert.equal(entry.slaveName(), 'ABTest-iPad-0');
814                 assert.equal(entry.buildRequestId(), 18935);
815                 assert.ok(!entry.isPending());
816                 assert.ok(!entry.isInProgress());
817                 assert.ok(entry.hasFinished());
818                 assert.equal(entry.url(), 'http://build.webkit.org/builders/ABTest-iPad-RunBenchmark-Tests/builds/1755');
819             });
820         });
821
822         it('should override BuildbotBuildEntry for pending builds by in-progress builds', () => {
823             let syncer = BuildbotSyncer._loadConfig(MockRemoteAPI, sampleiOSConfig())[1];
824
825             let promise = syncer.pullBuildbot(5);
826             assert.equal(requests.length, 1);
827
828             requests[0].resolve([samplePendingBuild()]);
829
830             return MockRemoteAPI.waitForRequest().then(() => {
831                 assert.equal(requests.length, 2);
832                 requests[1].resolve({[-1]: sampleInProgressBuild()});
833                 return promise;
834             }).then((entries) => {
835                 assert.equal(entries.length, 1);
836
837                 let entry = entries[0];
838                 assert.ok(entry instanceof BuildbotBuildEntry);
839                 assert.equal(entry.buildNumber(), 614);
840                 assert.equal(entry.slaveName(), 'ABTest-iPad-0');
841                 assert.equal(entry.buildRequestId(), 16733);
842                 assert.ok(!entry.isPending());
843                 assert.ok(entry.isInProgress());
844                 assert.ok(!entry.hasFinished());
845                 assert.equal(entry.url(), 'http://build.webkit.org/builders/ABTest-iPad-RunBenchmark-Tests/builds/614');
846             });
847         });
848
849         it('should override BuildbotBuildEntry for pending builds by finished builds', () => {
850             let syncer = BuildbotSyncer._loadConfig(MockRemoteAPI, sampleiOSConfig())[1];
851
852             let promise = syncer.pullBuildbot(5);
853             assert.equal(requests.length, 1);
854
855             requests[0].resolve([samplePendingBuild()]);
856
857             return MockRemoteAPI.waitForRequest().then(() => {
858                 assert.equal(requests.length, 2);
859                 requests[1].resolve({[-1]: sampleFinishedBuild(16733)});
860                 return promise;
861             }).then((entries) => {
862                 assert.equal(entries.length, 1);
863
864                 let entry = entries[0];
865                 assert.ok(entry instanceof BuildbotBuildEntry);
866                 assert.equal(entry.buildNumber(), 1755);
867                 assert.equal(entry.slaveName(), 'ABTest-iPad-0');
868                 assert.equal(entry.buildRequestId(), 16733);
869                 assert.ok(!entry.isPending());
870                 assert.ok(!entry.isInProgress());
871                 assert.ok(entry.hasFinished());
872                 assert.equal(entry.url(), 'http://build.webkit.org/builders/ABTest-iPad-RunBenchmark-Tests/builds/1755');
873             });
874         });
875     });
876
877     describe('scheduleRequest', () => {
878         it('should schedule a build request on a specified slave', () => {
879             let syncer = BuildbotSyncer._loadConfig(MockRemoteAPI, sampleiOSConfig())[0];
880
881             const waitForRequest = MockRemoteAPI.waitForRequest();
882             syncer.scheduleRequest(createSampleBuildRequest(MockModels.iphone, MockModels.speedometer), 'some-slave');
883             return waitForRequest.then(() => {
884                 assert.equal(requests.length, 1);
885                 assert.equal(requests[0].url, '/builders/ABTest-iPhone-RunBenchmark-Tests/force');
886                 assert.equal(requests[0].method, 'POST');
887                 assert.deepEqual(requests[0].data, {
888                     'build_request_id': '16733-' + MockModels.iphone.id(),
889                     'desired_image': '13A452',
890                     "opensource": "9abcdef",
891                     'forcescheduler': 'ABTest-iPhone-RunBenchmark-Tests-ForceScheduler',
892                     'roots_dict': '{"WebKit":{"id":"111127","time":1456955807334,"repository":"WebKit","revision":"197463"},'
893                         + '"Shared":{"id":"111237","time":1456931874000,"repository":"Shared","revision":"80229"},'
894                         + '"WebKit-Git":{"id":"111239","time":1456931874000,"repository":"WebKit-Git","revision":"9abcdef"}}',
895                     'slavename': 'some-slave',
896                     'test_name': 'speedometer'
897                 });
898             });
899         });
900     });
901
902     describe('scheduleRequestInGroupIfAvailable', () => {
903
904         function pullBuildbotWithAssertion(syncer, pendingBuilds, inProgressAndFinishedBuilds)
905         {
906             const promise = syncer.pullBuildbot(5);
907             assert.equal(requests.length, 1);
908             requests[0].resolve(pendingBuilds);
909             return MockRemoteAPI.waitForRequest().then(() => {
910                 assert.equal(requests.length, 2);
911                 requests[1].resolve(inProgressAndFinishedBuilds);
912                 requests.length = 0;
913                 return promise;
914             });
915         }
916
917         it('should schedule a build if builder has no builds if slaveList is not specified', () => {
918             let syncer = BuildbotSyncer._loadConfig(MockRemoteAPI, {'configurations': [smallConfiguration()]})[0];
919
920             return pullBuildbotWithAssertion(syncer, [], {}).then(() => {
921                 syncer.scheduleRequestInGroupIfAvailable(createSampleBuildRequest(MockModels.somePlatform, MockModels.someTest));
922                 assert.equal(requests.length, 1);
923                 assert.equal(requests[0].url, '/builders/some%20builder/force');
924                 assert.equal(requests[0].method, 'POST');
925                 assert.deepEqual(requests[0].data, {id: '16733-' + MockModels.somePlatform.id()});
926             });
927         });
928
929         it('should schedule a build if builder only has finished builds if slaveList is not specified', () => {
930             let syncer = BuildbotSyncer._loadConfig(MockRemoteAPI, {'configurations': [smallConfiguration()]})[0];
931
932             return pullBuildbotWithAssertion(syncer, [], {[-1]: smallFinishedBuild()}).then(() => {
933                 syncer.scheduleRequestInGroupIfAvailable(createSampleBuildRequest(MockModels.somePlatform, MockModels.someTest));
934                 assert.equal(requests.length, 1);
935                 assert.equal(requests[0].url, '/builders/some%20builder/force');
936                 assert.equal(requests[0].method, 'POST');
937                 assert.deepEqual(requests[0].data, {id: '16733-' + MockModels.somePlatform.id()});
938             });
939         });
940
941         it('should not schedule a build if builder has a pending build if slaveList is not specified', () => {
942             let syncer = BuildbotSyncer._loadConfig(MockRemoteAPI, {'configurations': [smallConfiguration()]})[0];
943
944             return pullBuildbotWithAssertion(syncer, [smallPendingBuild()], {}).then(() => {
945                 syncer.scheduleRequestInGroupIfAvailable(createSampleBuildRequest(MockModels.somePlatform, MockModels.someTest));
946                 assert.equal(requests.length, 0);
947             });
948         });
949
950         it('should schedule a build if builder does not have pending or completed builds on the matching slave', () => {
951             let syncer = BuildbotSyncer._loadConfig(MockRemoteAPI, sampleiOSConfig())[0];
952
953             return pullBuildbotWithAssertion(syncer, [], {}).then(() => {
954                 syncer.scheduleRequestInGroupIfAvailable(createSampleBuildRequest(MockModels.iphone, MockModels.speedometer));
955                 assert.equal(requests.length, 1);
956                 assert.equal(requests[0].url, '/builders/ABTest-iPhone-RunBenchmark-Tests/force');
957                 assert.equal(requests[0].method, 'POST');
958             });
959         });
960
961         it('should schedule a build if builder only has finished builds on the matching slave', () => {
962             let syncer = BuildbotSyncer._loadConfig(MockRemoteAPI, sampleiOSConfig())[1];
963
964             pullBuildbotWithAssertion(syncer, [], {[-1]: sampleFinishedBuild()}).then(() => {
965                 syncer.scheduleRequestInGroupIfAvailable(createSampleBuildRequest(MockModels.ipad, MockModels.speedometer));
966                 assert.equal(requests.length, 1);
967                 assert.equal(requests[0].url, '/builders/ABTest-iPad-RunBenchmark-Tests/force');
968                 assert.equal(requests[0].method, 'POST');
969             });
970         });
971
972         it('should not schedule a build if builder has a pending build on the maching slave', () => {
973             let syncer = BuildbotSyncer._loadConfig(MockRemoteAPI, sampleiOSConfig())[1];
974
975             pullBuildbotWithAssertion(syncer, [samplePendingBuild()], {}).then(() => {
976                 syncer.scheduleRequestInGroupIfAvailable(createSampleBuildRequest(MockModels.ipad, MockModels.speedometer));
977                 assert.equal(requests.length, 0);
978             });
979         });
980
981         it('should schedule a build if builder only has a pending build on a non-maching slave', () => {
982             let syncer = BuildbotSyncer._loadConfig(MockRemoteAPI, sampleiOSConfig())[1];
983
984             return pullBuildbotWithAssertion(syncer, [samplePendingBuild(1, 1, 'another-slave')], {}).then(() => {
985                 syncer.scheduleRequestInGroupIfAvailable(createSampleBuildRequest(MockModels.ipad, MockModels.speedometer));
986                 assert.equal(requests.length, 1);
987             });
988         });
989
990         it('should schedule a build if builder only has an in-progress build on the matching slave', () => {
991             let syncer = BuildbotSyncer._loadConfig(MockRemoteAPI, sampleiOSConfig())[1];
992
993             return pullBuildbotWithAssertion(syncer, [], {[-1]: sampleInProgressBuild()}).then(() => {
994                 syncer.scheduleRequestInGroupIfAvailable(createSampleBuildRequest(MockModels.ipad, MockModels.speedometer));
995                 assert.equal(requests.length, 1);
996             });
997         });
998
999         it('should schedule a build if builder has an in-progress build on another slave', () => {
1000             let syncer = BuildbotSyncer._loadConfig(MockRemoteAPI, sampleiOSConfig())[1];
1001
1002             return pullBuildbotWithAssertion(syncer, [], {[-1]: sampleInProgressBuild('other-slave')}).then(() => {
1003                 syncer.scheduleRequestInGroupIfAvailable(createSampleBuildRequest(MockModels.ipad, MockModels.speedometer));
1004                 assert.equal(requests.length, 1);
1005             });
1006         });
1007
1008         it('should not schedule a build if the request does not match any configuration', () => {
1009             let syncer = BuildbotSyncer._loadConfig(MockRemoteAPI, sampleiOSConfig())[0];
1010
1011             return pullBuildbotWithAssertion(syncer, [], {}).then(() => {
1012                 syncer.scheduleRequestInGroupIfAvailable(createSampleBuildRequest(MockModels.ipad, MockModels.speedometer));
1013                 assert.equal(requests.length, 0);
1014             });
1015         });
1016
1017         it('should not schedule a build if a new request had been submitted to the same slave', (done) => {
1018             let syncer = BuildbotSyncer._loadConfig(MockRemoteAPI, sampleiOSConfig())[1];
1019
1020             pullBuildbotWithAssertion(syncer, [], {}).then(() => {
1021                 syncer.scheduleRequest(createSampleBuildRequest(MockModels.ipad, MockModels.speedometer), 'ABTest-iPad-0');
1022                 syncer.scheduleRequest(createSampleBuildRequest(MockModels.ipad, MockModels.speedometer), 'ABTest-iPad-1');
1023             }).then(() => {
1024                 assert.equal(requests.length, 2);
1025                 syncer.scheduleRequestInGroupIfAvailable(createSampleBuildRequest(MockModels.ipad, MockModels.speedometer));
1026             }).then(() => {
1027                 assert.equal(requests.length, 2);
1028                 done();
1029             }).catch(done);
1030         });
1031
1032         it('should schedule a build if a new request had been submitted to another slave', () => {
1033             let syncer = BuildbotSyncer._loadConfig(MockRemoteAPI, sampleiOSConfig())[1];
1034
1035             return pullBuildbotWithAssertion(syncer, [], {}).then(() => {
1036                 syncer.scheduleRequest(createSampleBuildRequest(MockModels.ipad, MockModels.speedometer), 'ABTest-iPad-0');
1037                 assert.equal(requests.length, 1);
1038                 syncer.scheduleRequestInGroupIfAvailable(createSampleBuildRequest(MockModels.ipad, MockModels.speedometer), 'ABTest-iPad-1');
1039                 assert.equal(requests.length, 2);
1040             });
1041         });
1042
1043         it('should not schedule a build if a new request had been submitted to the same builder without slaveList', () => {
1044             let syncer = BuildbotSyncer._loadConfig(MockRemoteAPI, {'configurations': [smallConfiguration()]})[0];
1045
1046             return pullBuildbotWithAssertion(syncer, [], {}).then(() => {
1047                 syncer.scheduleRequest(createSampleBuildRequest(MockModels.somePlatform, MockModels.someTest), null);
1048                 assert.equal(requests.length, 1);
1049                 syncer.scheduleRequestInGroupIfAvailable(createSampleBuildRequest(MockModels.somePlatform, MockModels.someTest));
1050                 assert.equal(requests.length, 1);
1051             });
1052         });
1053     });
1054 });