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