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