BuildbotSyncer should be able to fetch JSON from buildbot
[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 require('./resources/mock-remote-api.js');
7 require('./resources/mock-v3-models.js');
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             },
43             'iPad-bench': {
44                 'builder': 'ABTest-iPad-RunBenchmark-Tests',
45                 'arguments': { 'forcescheduler': 'ABTest-iPad-RunBenchmark-Tests-ForceScheduler' }
46             }
47         },
48         'configurations': [
49             {'type': 'speedometer', 'builder': 'iPhone-bench', 'platform': 'iPhone'},
50             {'type': 'jetstream', 'builder': 'iPhone-bench', 'platform': 'iPhone'},
51             {'type': 'dromaeo-dom', 'builder': 'iPhone-bench', 'platform': 'iPhone'},
52
53             {'type': 'speedometer', 'builder': 'iPad-bench', 'platform': 'iPad'},
54             {'type': 'jetstream', 'builder': 'iPad-bench', 'platform': 'iPad'},
55         ]
56     };
57 }
58
59 let sampleRootSetData = {
60     'WebKit': {
61         'id': '111127',
62         'time': 1456955807334,
63         'repository': 'WebKit',
64         'revision': '197463',
65     },
66     'Shared': {
67         'id': '111237',
68         'time': 1456931874000,
69         'repository': 'Shared',
70         'revision': '80229',
71     }
72 };
73
74 function createSampleBuildRequest()
75 {
76     let rootSet = RootSet.ensureSingleton('4197', {roots: [
77         {'id': '111127', 'time': 1456955807334, 'repository': webkit, 'revision': '197463'},
78         {'id': '111237', 'time': 1456931874000, 'repository': sharedRepository, 'revision': '80229'},
79         {'id': '88930', 'time': 0, 'repository': ios, 'revision': '13A452'},
80     ]});
81
82     let request = BuildRequest.ensureSingleton('16733', {'rootSet': rootSet, 'status': 'pending'});
83     return request;
84 }
85
86 function samplePendingBuild(buildRequestId)
87 {
88     return {
89         'builderName': 'ABTest-iPad-RunBenchmark-Tests',
90         'builds': [],
91         'properties': [
92             ['build_request_id', buildRequestId || '16733', 'Force Build Form'],
93             ['desired_image', '13A452', 'Force Build Form'],
94             ['owner', '<unknown>', 'Force Build Form'],
95             ['test_name', 'speedometer', 'Force Build Form'],
96             ['reason', 'force build','Force Build Form'],
97             [
98                 'roots_dict',
99                 JSON.stringify(sampleRootSetData),
100                 'Force Build Form'
101             ],
102             ['scheduler', 'ABTest-iPad-RunBenchmark-Tests-ForceScheduler', 'Scheduler']
103         ],
104         'source': {
105             'branch': '',
106             'changes': [],
107             'codebase': 'compiler-rt',
108             'hasPatch': false,
109             'project': '',
110             'repository': '',
111             'revision': ''
112         },
113         'submittedAt': 1458704983
114     };
115 }
116
117 function sampleInProgressBuild()
118 {
119     return {
120         'blame': [],
121         'builderName': 'ABTest-iPad-RunBenchmark-Tests',
122         'currentStep': {
123             'eta': 0.26548067698460565,
124             'expectations': [['output', 845, 1315.0]],
125             'hidden': false,
126             'isFinished': false,
127             'isStarted': true,
128             'logs': [['stdio', 'https://build.webkit.org/builders/ABTest-iPad-RunBenchmark-Tests/builds/614/steps/Some%20step/logs/stdio']],
129             'name': 'Some step',
130             'results': [null,[]],
131             'statistics': {},
132             'step_number': 1,
133             'text': [''],
134             'times': [1458718657.581628, null],
135             'urls': {}
136         },
137         'eta': 6497.991612434387,
138         'logs': [['stdio','https://build.webkit.org/builders/ABTest-iPad-RunBenchmark-Tests/builds/614/steps/shell/logs/stdio']],
139         'number': 614,
140         'properties': [
141             ['build_request_id', '16733', 'Force Build Form'],
142             ['buildername', 'ABTest-iPad-RunBenchmark-Tests', 'Builder'],
143             ['buildnumber', 614, 'Build'],
144             ['desired_image', '13A452', 'Force Build Form'],
145             ['owner', '<unknown>', 'Force Build Form'],
146             ['reason', 'force build', 'Force Build Form'],
147             ['roots_dict', JSON.stringify(sampleRootSetData), 'Force Build Form'],
148             ['scheduler', 'ABTest-iPad-RunBenchmark-Tests-ForceScheduler', 'Scheduler'],
149             ['slavename', 'ABTest-iPad-0', 'BuildSlave'],
150         ],
151         'reason': 'A build was forced by \'<unknown>\': force build',
152         'results': null,
153         'slave': 'ABTest-iPad-0',
154         'sourceStamps': [{'branch': '', 'changes': [], 'codebase': 'compiler-rt', 'hasPatch': false, 'project': '', 'repository': '', 'revision': ''}],
155         'steps': [
156             {
157                 'eta': null,
158                 'expectations': [['output',2309,2309.0]],
159                 'hidden': false,
160                 'isFinished': true,
161                 'isStarted': true,
162                 'logs': [['stdio', 'https://build.webkit.org/builders/ABTest-iPad-RunBenchmark-Tests/builds/614/steps/shell/logs/stdio']],
163                 'name': 'Finished step',
164                 'results': [0, []],
165                 'statistics': {},
166                 'step_number': 0,
167                 'text': [''],
168                 'times': [1458718655.419865, 1458718655.453633],
169                 'urls': {}
170             },
171             {
172                 'eta': 0.26548067698460565,
173                 'expectations': [['output', 845, 1315.0]],
174                 'hidden': false,
175                 'isFinished': false,
176                 'isStarted': true,
177                 'logs': [['stdio', 'https://build.webkit.org/builders/ABTest-iPad-RunBenchmark-Tests/builds/614/steps/Some%20step/logs/stdio']],
178                 'name': 'Some step',
179                 'results': [null,[]],
180                 'statistics': {},
181                 'step_number': 1,
182                 'text': [''],
183                 'times': [1458718657.581628, null],
184                 'urls': {}
185             },
186             {
187                 'eta': null,
188                 'expectations': [['output', null, null]],
189                 'hidden': false,
190                 'isFinished': false,
191                 'isStarted': false,
192                 'logs': [],
193                 'name': 'Some other step',
194                 'results': [null, []],
195                 'statistics': {},
196                 'step_number': 2,
197                 'text': [],
198                 'times': [null, null],
199                 'urls': {}
200             },
201         ],
202         'text': [],
203         'times': [1458718655.415821, null]
204     };
205 }
206
207 function sampleFinishedBuild(buildRequestId)
208 {
209     return {
210         'blame': [],
211         'builderName': 'ABTest-iPad-RunBenchmark-Tests',
212         'currentStep': null,
213         'eta': null,
214         'logs': [['stdio','https://build.webkit.org/builders/ABTest-iPad-RunBenchmark-Tests/builds/1755/steps/shell/logs/stdio']],
215         'number': 1755,
216         'properties': [
217             ['build_request_id', buildRequestId || '18935', 'Force Build Form'],
218             ['buildername', 'ABTest-iPad-RunBenchmark-Tests', 'Builder'],
219             ['buildnumber', 1755, 'Build'],
220             ['desired_image', '13A452', 'Force Build Form'],
221             ['owner', '<unknown>', 'Force Build Form'],
222             ['reason', 'force build', 'Force Build Form'],
223             ['roots_dict', JSON.stringify(sampleRootSetData), 'Force Build Form'],
224             ['scheduler', 'ABTest-iPad-RunBenchmark-Tests-ForceScheduler', 'Scheduler'],
225             ['slavename', 'ABTest-iPad-0', 'BuildSlave'],
226         ],
227         'reason': 'A build was forced by \'<unknown>\': force build',
228         'results': 2,
229         'slave': 'ABTest-iPad-0',
230         'sourceStamps': [{'branch': '', 'changes': [], 'codebase': 'compiler-rt', 'hasPatch': false, 'project': '', 'repository': '', 'revision': ''}],
231         'steps': [
232             {
233                 'eta': null,
234                 'expectations': [['output',2309,2309.0]],
235                 'hidden': false,
236                 'isFinished': true,
237                 'isStarted': true,
238                 'logs': [['stdio', 'https://build.webkit.org/builders/ABTest-iPad-RunBenchmark-Tests/builds/614/steps/shell/logs/stdio']],
239                 'name': 'Finished step',
240                 'results': [0, []],
241                 'statistics': {},
242                 'step_number': 0,
243                 'text': [''],
244                 'times': [1458718655.419865, 1458718655.453633],
245                 'urls': {}
246             },
247             {
248                 'eta': null,
249                 'expectations': [['output', 845, 1315.0]],
250                 'hidden': false,
251                 'isFinished': true,
252                 'isStarted': true,
253                 'logs': [['stdio', 'https://build.webkit.org/builders/ABTest-iPad-RunBenchmark-Tests/builds/614/steps/Some%20step/logs/stdio']],
254                 'name': 'Some step',
255                 'results': [null,[]],
256                 'statistics': {},
257                 'step_number': 1,
258                 'text': [''],
259                 'times': [1458718657.581628, null],
260                 'urls': {}
261             },
262             {
263                 'eta': null,
264                 'expectations': [['output', null, null]],
265                 'hidden': false,
266                 'isFinished': true,
267                 'isStarted': true,
268                 'logs': [],
269                 'name': 'Some other step',
270                 'results': [null, []],
271                 'statistics': {},
272                 'step_number': 2,
273                 'text': [],
274                 'times': [null, null],
275                 'urls': {}
276             },
277         ],
278         'text': [],
279         'times': [1458937478.25837, 1458946147.173785]
280     };
281 }
282
283 describe('BuildbotSyncer', function () {
284     describe('_loadConfig', function () {
285
286         function smallConfiguration()
287         {
288             return {
289                 'builder': 'some builder',
290                 'platform': 'some platform',
291                 'test': ['some test'],
292                 'arguments': {},
293                 'buildRequestArgument': 'id'};
294         }
295
296         it('should create BuildbotSyncer objects for a configuration that specify all required options', function () {
297             let syncers = BuildbotSyncer._loadConfig('http://build.webkit.org/', {'configurations': [smallConfiguration()]});
298             assert.equal(syncers.length, 1);
299         });
300
301         it('should throw when some required options are missing', function () {
302             assert.throws(function () {
303                 let config = smallConfiguration();
304                 delete config['builder'];
305                 BuildbotSyncer._loadConfig('http://build.webkit.org/', {'configurations': [config]});
306             }, 'builder should be a required option');
307             assert.throws(function () {
308                 let config = smallConfiguration();
309                 delete config['platform'];
310                 BuildbotSyncer._loadConfig('http://build.webkit.org/', {'configurations': [config]});
311             }, 'platform should be a required option');
312             assert.throws(function () {
313                 let config = smallConfiguration();
314                 delete config['test'];
315                 BuildbotSyncer._loadConfig('http://build.webkit.org/', {'configurations': [config]});
316             }, 'test should be a required option');
317             assert.throws(function () {
318                 let config = smallConfiguration();
319                 delete config['arguments'];
320                 BuildbotSyncer._loadConfig('http://build.webkit.org/', {'configurations': [config]});
321             });
322             assert.throws(function () {
323                 let config = smallConfiguration();
324                 delete config['buildRequestArgument'];
325                 BuildbotSyncer._loadConfig('http://build.webkit.org/', {'configurations': [config]});
326             });
327         });
328
329         it('should throw when a test name is not an array of strings', function () {
330             assert.throws(function () {
331                 let config = smallConfiguration();
332                 config.test = 'some test';
333                 BuildbotSyncer._loadConfig('http://build.webkit.org/', {'configurations': [config]});
334             });
335             assert.throws(function () {
336                 let config = smallConfiguration();
337                 config.test = [1];
338                 BuildbotSyncer._loadConfig('http://build.webkit.org/', {'configurations': [config]});
339             });
340         });
341
342         it('should throw when arguments is not an object', function () {
343             assert.throws(function () {
344                 let config = smallConfiguration();
345                 config.arguments = 'hello';
346                 BuildbotSyncer._loadConfig('http://build.webkit.org/', {'configurations': [config]});
347             });
348         });
349
350         it('should throw when arguments\'s values are malformed', function () {
351             assert.throws(function () {
352                 let config = smallConfiguration();
353                 config.arguments = {'some': {'root': 'some root', 'rootsExcluding': ['other root']}};
354                 BuildbotSyncer._loadConfig('http://build.webkit.org/', {'configurations': [config]});
355             });
356             assert.throws(function () {
357                 let config = smallConfiguration();
358                 config.arguments = {'some': {'otherKey': 'some root'}};
359                 BuildbotSyncer._loadConfig('http://build.webkit.org/', {'configurations': [config]});
360             });
361             assert.throws(function () {
362                 let config = smallConfiguration();
363                 config.arguments = {'some': {'root': ['a', 'b']}};
364                 BuildbotSyncer._loadConfig('http://build.webkit.org/', {'configurations': [config]});
365             });
366             assert.throws(function () {
367                 let config = smallConfiguration();
368                 config.arguments = {'some': {'root': 1}};
369                 BuildbotSyncer._loadConfig('http://build.webkit.org/', {'configurations': [config]});
370             });
371             assert.throws(function () {
372                 let config = smallConfiguration();
373                 config.arguments = {'some': {'rootsExcluding': 'a'}};
374                 BuildbotSyncer._loadConfig('http://build.webkit.org/', {'configurations': [config]});
375             });
376             assert.throws(function () {
377                 let config = smallConfiguration();
378                 config.arguments = {'some': {'rootsExcluding': [1]}};
379                 BuildbotSyncer._loadConfig('http://build.webkit.org/', {'configurations': [config]});
380             });
381         });
382
383         it('should create BuildbotSyncer objects for valid configurations', function () {
384             let syncers = BuildbotSyncer._loadConfig('http://build.webkit.org/', sampleiOSConfig());
385             assert.equal(syncers.length, 5);
386             assert.ok(syncers[0] instanceof BuildbotSyncer);
387             assert.ok(syncers[1] instanceof BuildbotSyncer);
388             assert.ok(syncers[2] instanceof BuildbotSyncer);
389             assert.ok(syncers[3] instanceof BuildbotSyncer);
390             assert.ok(syncers[4] instanceof BuildbotSyncer);
391         });
392
393         it('should parse builder names correctly', function () {
394             let syncers = BuildbotSyncer._loadConfig('http://build.webkit.org/', sampleiOSConfig());
395             assert.equal(syncers[0].builderName(), 'ABTest-iPhone-RunBenchmark-Tests');
396             assert.equal(syncers[1].builderName(), 'ABTest-iPhone-RunBenchmark-Tests');
397             assert.equal(syncers[2].builderName(), 'ABTest-iPhone-RunBenchmark-Tests');
398             assert.equal(syncers[3].builderName(), 'ABTest-iPad-RunBenchmark-Tests');
399             assert.equal(syncers[4].builderName(), 'ABTest-iPad-RunBenchmark-Tests');
400         });
401
402         it('should parse platform names correctly', function () {
403             let syncers = BuildbotSyncer._loadConfig('http://build.webkit.org/', sampleiOSConfig());
404             assert.equal(syncers[0].platformName(), 'iPhone');
405             assert.equal(syncers[1].platformName(), 'iPhone');
406             assert.equal(syncers[2].platformName(), 'iPhone');
407             assert.equal(syncers[3].platformName(), 'iPad');
408             assert.equal(syncers[4].platformName(), 'iPad');
409         });
410
411         it('should parse test names correctly', function () {
412             let syncers = BuildbotSyncer._loadConfig('http://build.webkit.org/', sampleiOSConfig());
413             assert.deepEqual(syncers[0].testPath(), ['Speedometer']);
414             assert.deepEqual(syncers[1].testPath(), ['JetStream']);
415             assert.deepEqual(syncers[2].testPath(), ['Dromaeo', 'DOM Core Tests']);
416             assert.deepEqual(syncers[3].testPath(), ['Speedometer']);
417             assert.deepEqual(syncers[4].testPath(), ['JetStream']);
418         });
419     });
420
421     describe('_propertiesForBuildRequest', function () {
422         it('should include all properties specified in a given configuration', function () {
423             let syncers = BuildbotSyncer._loadConfig('http://build.webkit.org/', sampleiOSConfig());
424             let properties = syncers[0]._propertiesForBuildRequest(createSampleBuildRequest());
425             assert.deepEqual(Object.keys(properties), ['desired_image', 'roots_dict', 'test_name', 'forcescheduler', 'build_request_id']);
426         });
427
428         it('should preserve non-parametric property values', function () {
429             let syncers = BuildbotSyncer._loadConfig('http://build.webkit.org/', sampleiOSConfig());
430             let properties = syncers[0]._propertiesForBuildRequest(createSampleBuildRequest());
431             assert.equal(properties['test_name'], 'speedometer');
432             assert.equal(properties['forcescheduler'], 'ABTest-iPhone-RunBenchmark-Tests-ForceScheduler');
433
434             properties = syncers[1]._propertiesForBuildRequest(createSampleBuildRequest());
435             assert.equal(properties['test_name'], 'jetstream');
436             assert.equal(properties['forcescheduler'], 'ABTest-iPhone-RunBenchmark-Tests-ForceScheduler');
437         });
438
439         it('should resolve "root"', function () {
440             let syncers = BuildbotSyncer._loadConfig('http://build.webkit.org/', sampleiOSConfig());
441             let properties = syncers[0]._propertiesForBuildRequest(createSampleBuildRequest());
442             assert.equal(properties['desired_image'], '13A452');
443         });
444
445         it('should resolve "rootsExcluding"', function () {
446             let syncers = BuildbotSyncer._loadConfig('http://build.webkit.org/', sampleiOSConfig());
447             let properties = syncers[0]._propertiesForBuildRequest(createSampleBuildRequest());
448             assert.equal(properties['roots_dict'], JSON.stringify(sampleRootSetData));
449         });
450
451         it('should set the property for the build request id', function () {
452             let syncers = BuildbotSyncer._loadConfig('http://build.webkit.org/', sampleiOSConfig());
453             let properties = syncers[0]._propertiesForBuildRequest(createSampleBuildRequest());
454             assert.equal(properties['build_request_id'], createSampleBuildRequest().id());
455         });
456     });
457
458     describe('pullBuildbot', function () {
459         it('should fetch pending builds from the right URL', function () {
460             let syncer = BuildbotSyncer._loadConfig('http://build.webkit.org', sampleiOSConfig())[3];
461             assert.equal(syncer.builderName(), 'ABTest-iPad-RunBenchmark-Tests');
462             let expectedURL = 'http://build.webkit.org/json/builders/ABTest-iPad-RunBenchmark-Tests/pendingBuilds';
463             assert.equal(syncer.urlForPendingBuildsJSON(), expectedURL);
464             syncer.pullBuildbot();
465             assert.equal(requests.length, 1);
466             assert.equal(requests[0].url, expectedURL);
467         });
468
469         it('should fetch recent builds once pending builds have been fetched', function (done) {
470             let syncer = BuildbotSyncer._loadConfig('http://build.webkit.org', sampleiOSConfig())[3];
471             assert.equal(syncer.builderName(), 'ABTest-iPad-RunBenchmark-Tests');
472
473             syncer.pullBuildbot(1);
474             assert.equal(requests.length, 1);
475             assert.equal(requests[0].url, 'http://build.webkit.org/json/builders/ABTest-iPad-RunBenchmark-Tests/pendingBuilds');
476             requests[0].resolve([]);
477             Promise.resolve().then(function () {
478                 assert.equal(requests.length, 2);
479                 assert.equal(requests[1].url, 'http://build.webkit.org/json/builders/ABTest-iPad-RunBenchmark-Tests/builds/?select=-1');
480                 done();
481             }).catch(done);
482         });
483
484         it('should fetch the right number of recent builds', function (done) {
485             let syncer = BuildbotSyncer._loadConfig('http://build.webkit.org', sampleiOSConfig())[3];
486
487             syncer.pullBuildbot(3);
488             assert.equal(requests.length, 1);
489             assert.equal(requests[0].url, 'http://build.webkit.org/json/builders/ABTest-iPad-RunBenchmark-Tests/pendingBuilds');
490             requests[0].resolve([]);
491             Promise.resolve().then(function () {
492                 assert.equal(requests.length, 2);
493                 assert.equal(requests[1].url, 'http://build.webkit.org/json/builders/ABTest-iPad-RunBenchmark-Tests/builds/?select=-1&select=-2&select=-3');
494                 done();
495             }).catch(done);
496         });
497
498         it('should create BuildbotBuildEntry for pending builds', function (done) {
499             let syncer = BuildbotSyncer._loadConfig('http://build.webkit.org', sampleiOSConfig())[3];
500             let promise = syncer.pullBuildbot();
501             requests[0].resolve([samplePendingBuild()]);
502             promise.then(function (entries) {
503                 assert.deepEqual(Object.keys(entries), ['16733']);
504                 let entry = entries['16733'];
505                 assert.ok(entry instanceof BuildbotBuildEntry);
506                 assert.ok(!entry.buildNumber());
507                 assert.ok(!entry.slaveName());
508                 assert.equal(entry.buildRequestId(), 16733);
509                 assert.ok(entry.isPending());
510                 assert.ok(!entry.isInProgress());
511                 assert.ok(!entry.hasFinished());
512                 assert.equal(entry.url(), 'http://build.webkit.org/builders/ABTest-iPad-RunBenchmark-Tests/');
513                 done();
514             }).catch(done);
515         });
516
517         it('should create BuildbotBuildEntry for in-progress builds', function (done) {
518             let syncer = BuildbotSyncer._loadConfig('http://build.webkit.org', sampleiOSConfig())[3];
519
520             let promise = syncer.pullBuildbot(1);
521             assert.equal(requests.length, 1);
522             requests[0].resolve([]);
523             Promise.resolve().then(function () {
524                 assert.equal(requests.length, 2);
525                 requests[1].resolve({[-1]: sampleInProgressBuild()});
526             }).catch(done);
527
528             promise.then(function (entries) {
529                 assert.deepEqual(Object.keys(entries), ['16733']);
530                 let entry = entries['16733'];
531                 assert.ok(entry instanceof BuildbotBuildEntry);
532                 assert.equal(entry.buildNumber(), 614);
533                 assert.equal(entry.slaveName(), 'ABTest-iPad-0');
534                 assert.equal(entry.buildRequestId(), 16733);
535                 assert.ok(!entry.isPending());
536                 assert.ok(entry.isInProgress());
537                 assert.ok(!entry.hasFinished());
538                 assert.equal(entry.url(), 'http://build.webkit.org/builders/ABTest-iPad-RunBenchmark-Tests/builds/614');
539                 done();
540             }).catch(done);
541         });
542
543         it('should create BuildbotBuildEntry for finished builds', function (done) {
544             let syncer = BuildbotSyncer._loadConfig('http://build.webkit.org', sampleiOSConfig())[3];
545
546             let promise = syncer.pullBuildbot(1);
547             assert.equal(requests.length, 1);
548             requests[0].resolve([]);
549             Promise.resolve().then(function () {
550                 assert.equal(requests.length, 2);
551                 requests[1].resolve({[-1]: sampleFinishedBuild()});
552             }).catch(done);
553
554             promise.then(function (entries) {
555                 assert.deepEqual(Object.keys(entries), ['18935']);
556                 let entry = entries['18935'];
557                 assert.ok(entry instanceof BuildbotBuildEntry);
558                 assert.equal(entry.buildNumber(), 1755);
559                 assert.equal(entry.slaveName(), 'ABTest-iPad-0');
560                 assert.equal(entry.buildRequestId(), 18935);
561                 assert.ok(!entry.isPending());
562                 assert.ok(!entry.isInProgress());
563                 assert.ok(entry.hasFinished());
564                 assert.equal(entry.url(), 'http://build.webkit.org/builders/ABTest-iPad-RunBenchmark-Tests/builds/1755');
565                 done();
566             }).catch(done);
567         });
568
569         it('should create BuildbotBuildEntry for mixed pending, in-progress, finished, and missing builds', function (done) {
570             let syncer = BuildbotSyncer._loadConfig('http://build.webkit.org', sampleiOSConfig())[3];
571
572             let promise = syncer.pullBuildbot(5);
573             assert.equal(requests.length, 1);
574
575             requests[0].resolve([samplePendingBuild(123, 456)]);
576
577             Promise.resolve().then(function () {
578                 assert.equal(requests.length, 2);
579                 requests[1].resolve({[-1]: sampleFinishedBuild(), [-2]: {'error': 'Not available'}, [-4]: sampleInProgressBuild()});
580             }).catch(done);
581
582             promise.then(function (entries) {
583                 assert.deepEqual(Object.keys(entries), ['123', '16733', '18935']);
584
585                 let entry = entries['123'];
586                 assert.ok(entry instanceof BuildbotBuildEntry);
587                 assert.equal(entry.buildNumber(), null);
588                 assert.equal(entry.slaveName(), null);
589                 assert.equal(entry.buildRequestId(), 123);
590                 assert.ok(entry.isPending());
591                 assert.ok(!entry.isInProgress());
592                 assert.ok(!entry.hasFinished());
593                 assert.equal(entry.url(), 'http://build.webkit.org/builders/ABTest-iPad-RunBenchmark-Tests/');
594
595                 entry = entries['16733'];
596                 assert.ok(entry instanceof BuildbotBuildEntry);
597                 assert.equal(entry.buildNumber(), 614);
598                 assert.equal(entry.slaveName(), 'ABTest-iPad-0');
599                 assert.equal(entry.buildRequestId(), 16733);
600                 assert.ok(!entry.isPending());
601                 assert.ok(entry.isInProgress());
602                 assert.ok(!entry.hasFinished());
603                 assert.equal(entry.url(), 'http://build.webkit.org/builders/ABTest-iPad-RunBenchmark-Tests/builds/614');
604
605                 entry = entries['18935'];
606                 assert.ok(entry instanceof BuildbotBuildEntry);
607                 assert.equal(entry.buildNumber(), 1755);
608                 assert.equal(entry.slaveName(), 'ABTest-iPad-0');
609                 assert.equal(entry.buildRequestId(), 18935);
610                 assert.ok(!entry.isPending());
611                 assert.ok(!entry.isInProgress());
612                 assert.ok(entry.hasFinished());
613                 assert.equal(entry.url(), 'http://build.webkit.org/builders/ABTest-iPad-RunBenchmark-Tests/builds/1755');
614
615                 done();
616             }).catch(done);
617         });
618
619         it('should override BuildbotBuildEntry for pending builds by in-progress builds', function (done) {
620             let syncer = BuildbotSyncer._loadConfig('http://build.webkit.org', sampleiOSConfig())[3];
621
622             let promise = syncer.pullBuildbot(5);
623             assert.equal(requests.length, 1);
624
625             requests[0].resolve([samplePendingBuild()]);
626
627             Promise.resolve().then(function () {
628                 assert.equal(requests.length, 2);
629                 requests[1].resolve({[-1]: sampleInProgressBuild()});
630             }).catch(done);
631
632             promise.then(function (entries) {
633                 assert.deepEqual(Object.keys(entries), ['16733']);
634
635                 let entry = entries['16733'];
636                 assert.ok(entry instanceof BuildbotBuildEntry);
637                 assert.equal(entry.buildNumber(), 614);
638                 assert.equal(entry.slaveName(), 'ABTest-iPad-0');
639                 assert.equal(entry.buildRequestId(), 16733);
640                 assert.ok(!entry.isPending());
641                 assert.ok(entry.isInProgress());
642                 assert.ok(!entry.hasFinished());
643                 assert.equal(entry.url(), 'http://build.webkit.org/builders/ABTest-iPad-RunBenchmark-Tests/builds/614');
644
645                 done();
646             }).catch(done);
647         });
648
649         it('should override BuildbotBuildEntry for pending builds by finished builds', function (done) {
650             let syncer = BuildbotSyncer._loadConfig('http://build.webkit.org', sampleiOSConfig())[3];
651
652             let promise = syncer.pullBuildbot(5);
653             assert.equal(requests.length, 1);
654
655             requests[0].resolve([samplePendingBuild()]);
656
657             Promise.resolve().then(function () {
658                 assert.equal(requests.length, 2);
659                 requests[1].resolve({[-1]: sampleFinishedBuild(16733)});
660             }).catch(done);
661
662             promise.then(function (entries) {
663                 assert.deepEqual(Object.keys(entries), ['16733']);
664
665                 let entry = entries['16733'];
666                 assert.ok(entry instanceof BuildbotBuildEntry);
667                 assert.equal(entry.buildNumber(), 1755);
668                 assert.equal(entry.slaveName(), 'ABTest-iPad-0');
669                 assert.equal(entry.buildRequestId(), 16733);
670                 assert.ok(!entry.isPending());
671                 assert.ok(!entry.isInProgress());
672                 assert.ok(entry.hasFinished());
673                 assert.equal(entry.url(), 'http://build.webkit.org/builders/ABTest-iPad-RunBenchmark-Tests/builds/1755');
674
675                 done();
676             }).catch(done);
677         });
678
679     });
680 });