Add the support for scheduling a A/B testing with a patch.
[WebKit-https.git] / Websites / perf.webkit.org / server-tests / tools-buildbot-triggerable-tests.js
1 'use strict';
2
3 const assert = require('assert');
4
5 const BuildbotTriggerable = require('../tools/js/buildbot-triggerable.js').BuildbotTriggerable;
6 const MockData = require('./resources/mock-data.js');
7 const MockRemoteAPI = require('../unit-tests/resources/mock-remote-api.js').MockRemoteAPI;
8 const TestServer = require('./resources/test-server.js');
9 const prepareServerTest = require('./resources/common-operations.js').prepareServerTest;
10 const MockLogger = require('./resources/mock-logger.js').MockLogger;
11
12 describe('BuildbotTriggerable', function () {
13     prepareServerTest(this);
14
15     beforeEach(function () {
16         MockData.resetV3Models();
17         MockRemoteAPI.reset('http://build.webkit.org');
18     });
19
20     describe('syncOnce', () => {
21         it('should schedule the next build request when there are no pending builds', () => {
22             const db = TestServer.database();
23             let syncPromise;
24             return MockData.addMockData(db, ['completed', 'running', 'pending', 'pending']).then(() => {
25                 return Manifest.fetch();
26             }).then(() => {
27                 const config = MockData.mockTestSyncConfigWithSingleBuilder();
28                 const logger = new MockLogger;
29                 const slaveInfo = {name: 'sync-slave', password: 'password'};
30                 const triggerable = new BuildbotTriggerable(config, TestServer.remoteAPI(), MockRemoteAPI, slaveInfo, logger);
31                 syncPromise = triggerable.syncOnce();
32                 return MockRemoteAPI.waitForRequest();
33             }).then(() => {
34                 assert.equal(BuildRequest.all().length, 4);
35                 assert.equal(BuildRequest.findById(700).status(), 'completed');
36                 assert.equal(BuildRequest.findById(701).status(), 'running');
37                 assert.equal(BuildRequest.findById(702).status(), 'pending');
38                 assert.equal(BuildRequest.findById(703).status(), 'pending');
39                 assert.equal(MockRemoteAPI.requests[0].method, 'GET');
40                 assert.equal(MockRemoteAPI.requests[0].url, '/json/builders/some-builder-1/pendingBuilds');
41                 MockRemoteAPI.requests[0].resolve([]);
42                 return MockRemoteAPI.waitForRequest();
43             }).then(() => {
44                 assert.equal(MockRemoteAPI.requests[1].method, 'GET');
45                 assert.equal(MockRemoteAPI.requests[1].url, '/json/builders/some-builder-1/builds/?select=-1&select=-2');
46                 MockRemoteAPI.requests[1].resolve({[-1]: MockData.runningBuild(), [-2]: MockData.finishedBuild()});
47                 return MockRemoteAPI.waitForRequest();
48             }).then(() => {
49                 assert.equal(MockRemoteAPI.requests[2].method, 'POST');
50                 assert.equal(MockRemoteAPI.requests[2].url, '/builders/some-builder-1/force');
51                 assert.deepEqual(MockRemoteAPI.requests[2].data, {'wk': '191622', 'os': '10.11 15A284', 'build-request-id': '702'});
52                 MockRemoteAPI.requests[2].resolve('OK');
53                 return MockRemoteAPI.waitForRequest();
54             }).then(() => {
55                 assert.equal(MockRemoteAPI.requests[3].method, 'GET');
56                 assert.equal(MockRemoteAPI.requests[3].url, '/json/builders/some-builder-1/pendingBuilds');
57                 MockRemoteAPI.requests[3].resolve([MockData.pendingBuild()])
58                 return MockRemoteAPI.waitForRequest();
59             }).then(() => {
60                 assert.equal(MockRemoteAPI.requests[4].method, 'GET');
61                 assert.equal(MockRemoteAPI.requests[4].url, '/json/builders/some-builder-1/builds/?select=-1&select=-2');
62                 MockRemoteAPI.requests[4].resolve({[-1]: MockData.runningBuild(), [-2]: MockData.finishedBuild()});
63                 return syncPromise;
64             }).then(() => {
65                 return BuildRequest.fetchForTriggerable(MockData.mockTestSyncConfigWithSingleBuilder().triggerableName);
66             }).then(() => {
67                 assert.equal(BuildRequest.all().length, 4);
68                 assert.equal(BuildRequest.findById(700).status(), 'completed');
69                 assert.equal(BuildRequest.findById(701).status(), 'running');
70                 assert.equal(BuildRequest.findById(702).status(), 'scheduled');
71                 assert.equal(BuildRequest.findById(703).status(), 'pending');
72             });
73         });
74
75         it('should not schedule the next build request when there is a pending build', () => {
76             const db = TestServer.database();
77             let syncPromise;
78             return MockData.addMockData(db, ['completed', 'running', 'pending', 'pending']).then(() => {
79                 return Manifest.fetch();
80             }).then(() => {
81                 let config = MockData.mockTestSyncConfigWithSingleBuilder();
82                 let logger = new MockLogger;
83                 let slaveInfo = {name: 'sync-slave', password: 'password'};
84                 let triggerable = new BuildbotTriggerable(config, TestServer.remoteAPI(), MockRemoteAPI, slaveInfo, logger);
85                 syncPromise = triggerable.syncOnce();
86                 return MockRemoteAPI.waitForRequest();
87             }).then(() => {
88                 assert.equal(MockRemoteAPI.requests[0].method, 'GET');
89                 assert.equal(MockRemoteAPI.requests[0].url, '/json/builders/some-builder-1/pendingBuilds');
90                 MockRemoteAPI.requests[0].resolve([MockData.pendingBuild()]);
91                 return MockRemoteAPI.waitForRequest();
92             }).then(() => {
93                 assert.equal(MockRemoteAPI.requests[1].method, 'GET');
94                 assert.equal(MockRemoteAPI.requests[1].url, '/json/builders/some-builder-1/builds/?select=-1&select=-2');
95                 MockRemoteAPI.requests[1].resolve({[-1]: MockData.runningBuild(), [-2]: MockData.finishedBuild()});
96                 return MockRemoteAPI.waitForRequest();
97             }).then(() => {
98                 assert.equal(MockRemoteAPI.requests[2].method, 'GET');
99                 assert.equal(MockRemoteAPI.requests[2].url, '/json/builders/some-builder-1/pendingBuilds');
100                 MockRemoteAPI.requests[2].resolve([MockData.pendingBuild()])
101                 return MockRemoteAPI.waitForRequest();
102             }).then(() => {
103                 assert.equal(MockRemoteAPI.requests[3].method, 'GET');
104                 assert.equal(MockRemoteAPI.requests[3].url, '/json/builders/some-builder-1/builds/?select=-1&select=-2');
105                 MockRemoteAPI.requests[3].resolve({[-1]: MockData.runningBuild(), [-2]: MockData.finishedBuild()});
106                 return syncPromise;
107             }).then(() => {
108                 assert.equal(BuildRequest.all().length, 4);
109                 assert.equal(BuildRequest.findById(700).status(), 'completed');
110                 assert.equal(BuildRequest.findById(701).status(), 'running');
111                 assert.equal(BuildRequest.findById(702).status(), 'pending');
112                 assert.equal(BuildRequest.findById(703).status(), 'pending');
113                 return BuildRequest.fetchForTriggerable(MockData.mockTestSyncConfigWithSingleBuilder().triggerableName);
114             }).then(() => {
115                 assert.equal(BuildRequest.all().length, 4);
116                 assert.equal(BuildRequest.findById(700).status(), 'completed');
117                 assert.equal(BuildRequest.findById(701).status(), 'running');
118                 assert.equal(BuildRequest.findById(702).status(), 'scheduled');
119                 assert.equal(BuildRequest.findById(703).status(), 'pending');
120             });
121         });
122
123         it('should schedule the build request on a builder without a pending build if it\'s the first request in the group', () => {
124             const db = TestServer.database();
125             let syncPromise;
126             return MockData.addMockData(db, ['pending', 'pending', 'pending', 'pending']).then(() => {
127                 return Manifest.fetch();
128             }).then(() => {
129                 const config = MockData.mockTestSyncConfigWithTwoBuilders();
130                 const logger = new MockLogger;
131                 const slaveInfo = {name: 'sync-slave', password: 'password'};
132                 const triggerable = new BuildbotTriggerable(config, TestServer.remoteAPI(), MockRemoteAPI, slaveInfo, logger);
133                 syncPromise = triggerable.syncOnce();
134                 return MockRemoteAPI.waitForRequest();
135             }).then(() => {
136                 assert.equal(MockRemoteAPI.requests.length, 2);
137                 assert.equal(MockRemoteAPI.requests[0].method, 'GET');
138                 assert.equal(MockRemoteAPI.requests[0].url, '/json/builders/some-builder-1/pendingBuilds');
139                 MockRemoteAPI.requests[0].resolve([MockData.pendingBuild({buildRequestId: 999})]);
140                 assert.equal(MockRemoteAPI.requests[1].method, 'GET');
141                 assert.equal(MockRemoteAPI.requests[1].url, '/json/builders/some%20builder%202/pendingBuilds');
142                 MockRemoteAPI.requests[1].resolve([]);
143                 return MockRemoteAPI.waitForRequest();
144             }).then(() => {
145                 assert.equal(MockRemoteAPI.requests.length, 4);
146                 assert.equal(MockRemoteAPI.requests[2].method, 'GET');
147                 assert.equal(MockRemoteAPI.requests[2].url, '/json/builders/some-builder-1/builds/?select=-1&select=-2');
148                 MockRemoteAPI.requests[2].resolve({});
149                 assert.equal(MockRemoteAPI.requests[3].method, 'GET');
150                 assert.equal(MockRemoteAPI.requests[3].url, '/json/builders/some%20builder%202/builds/?select=-1&select=-2');
151                 MockRemoteAPI.requests[3].resolve({});
152                 return MockRemoteAPI.waitForRequest();
153             }).then(() => {
154                 assert.equal(MockRemoteAPI.requests.length, 5);
155                 assert.equal(MockRemoteAPI.requests[4].method, 'POST');
156                 assert.equal(MockRemoteAPI.requests[4].url, '/builders/some%20builder%202/force');
157                 assert.deepEqual(MockRemoteAPI.requests[4].data, {'wk': '191622', 'os': '10.11 15A284', 'build-request-id': '700'});
158                 MockRemoteAPI.requests[4].resolve('OK');
159                 return MockRemoteAPI.waitForRequest();
160             }).then(() => {
161                 assert.equal(MockRemoteAPI.requests.length, 7);
162                 assert.equal(MockRemoteAPI.requests[5].method, 'GET');
163                 assert.equal(MockRemoteAPI.requests[5].url, '/json/builders/some-builder-1/pendingBuilds');
164                 MockRemoteAPI.requests[5].resolve([MockData.pendingBuild({buildRequestId: 999})]);
165                 assert.equal(MockRemoteAPI.requests[6].method, 'GET');
166                 assert.equal(MockRemoteAPI.requests[6].url, '/json/builders/some%20builder%202/pendingBuilds');
167                 MockRemoteAPI.requests[6].resolve([MockData.pendingBuild({builder: 'some builder 2', buildRequestId: 700})]);
168                 return MockRemoteAPI.waitForRequest();
169             }).then(() => {
170                 assert.equal(MockRemoteAPI.requests.length, 9);
171                 assert.equal(MockRemoteAPI.requests[7].method, 'GET');
172                 assert.equal(MockRemoteAPI.requests[7].url, '/json/builders/some-builder-1/builds/?select=-1&select=-2');
173                 MockRemoteAPI.requests[7].resolve({});
174                 assert.equal(MockRemoteAPI.requests[8].method, 'GET');
175                 assert.equal(MockRemoteAPI.requests[8].url, '/json/builders/some%20builder%202/builds/?select=-1&select=-2');
176                 MockRemoteAPI.requests[8].resolve({});
177                 return syncPromise;
178             }).then(() => {
179                 assert.equal(BuildRequest.all().length, 4);
180                 assert.equal(BuildRequest.findById(700).status(), 'pending');
181                 assert.equal(BuildRequest.findById(700).statusUrl(), null);
182                 assert.equal(BuildRequest.findById(701).status(), 'pending');
183                 assert.equal(BuildRequest.findById(701).statusUrl(), null);
184                 assert.equal(BuildRequest.findById(702).status(), 'pending');
185                 assert.equal(BuildRequest.findById(702).statusUrl(), null);
186                 assert.equal(BuildRequest.findById(703).status(), 'pending');
187                 assert.equal(BuildRequest.findById(703).statusUrl(), null);
188                 return BuildRequest.fetchForTriggerable(MockData.mockTestSyncConfigWithTwoBuilders().triggerableName);
189             }).then(() => {
190                 assert.equal(BuildRequest.all().length, 4);
191                 assert.equal(BuildRequest.findById(700).status(), 'scheduled');
192                 assert.equal(BuildRequest.findById(700).statusUrl(), 'http://build.webkit.org/builders/some%20builder%202/');
193                 assert.equal(BuildRequest.findById(701).status(), 'pending');
194                 assert.equal(BuildRequest.findById(701).statusUrl(), null);
195                 assert.equal(BuildRequest.findById(702).status(), 'pending');
196                 assert.equal(BuildRequest.findById(702).statusUrl(), null);
197                 assert.equal(BuildRequest.findById(703).status(), 'pending');
198                 assert.equal(BuildRequest.findById(703).statusUrl(), null);
199             });
200         });
201
202         it('should not schedule a build request on a different builder than the one the first build request is pending', () => {
203             const db = TestServer.database();
204             let syncPromise;
205             return MockData.addMockData(db, ['pending', 'pending', 'pending', 'pending']).then(() => {
206                 return Manifest.fetch();
207             }).then(() => {
208                 let config = MockData.mockTestSyncConfigWithTwoBuilders();
209                 let logger = new MockLogger;
210                 let slaveInfo = {name: 'sync-slave', password: 'password'};
211                 let triggerable = new BuildbotTriggerable(config, TestServer.remoteAPI(), MockRemoteAPI, slaveInfo, logger);
212                 syncPromise = triggerable.syncOnce();
213                 return MockRemoteAPI.waitForRequest();
214             }).then(() => {
215                 assert.equal(MockRemoteAPI.requests.length, 2);
216                 assert.equal(MockRemoteAPI.requests[0].method, 'GET');
217                 assert.equal(MockRemoteAPI.requests[0].url, '/json/builders/some-builder-1/pendingBuilds');
218                 MockRemoteAPI.requests[0].resolve([MockData.pendingBuild({buildRequestId: 700})]);
219                 assert.equal(MockRemoteAPI.requests[1].method, 'GET');
220                 assert.equal(MockRemoteAPI.requests[1].url, '/json/builders/some%20builder%202/pendingBuilds');
221                 MockRemoteAPI.requests[1].resolve([]);
222                 return MockRemoteAPI.waitForRequest();
223             }).then(() => {
224                 assert.equal(MockRemoteAPI.requests.length, 4);
225                 assert.equal(MockRemoteAPI.requests[2].method, 'GET');
226                 assert.equal(MockRemoteAPI.requests[2].url, '/json/builders/some-builder-1/builds/?select=-1&select=-2');
227                 MockRemoteAPI.requests[2].resolve({});
228                 assert.equal(MockRemoteAPI.requests[3].method, 'GET');
229                 assert.equal(MockRemoteAPI.requests[3].url, '/json/builders/some%20builder%202/builds/?select=-1&select=-2');
230                 MockRemoteAPI.requests[3].resolve({});
231                 return MockRemoteAPI.waitForRequest();
232             }).then(() => {
233                 assert.equal(MockRemoteAPI.requests.length, 6);
234                 assert.equal(MockRemoteAPI.requests[4].method, 'GET');
235                 assert.equal(MockRemoteAPI.requests[4].url, '/json/builders/some-builder-1/pendingBuilds');
236                 MockRemoteAPI.requests[4].resolve([MockData.pendingBuild({buildRequestId: 700})]);
237                 assert.equal(MockRemoteAPI.requests[5].method, 'GET');
238                 assert.equal(MockRemoteAPI.requests[5].url, '/json/builders/some%20builder%202/pendingBuilds');
239                 MockRemoteAPI.requests[5].resolve([]);
240                 return MockRemoteAPI.waitForRequest();
241             }).then(() => {
242                 assert.equal(MockRemoteAPI.requests.length, 8);
243                 assert.equal(MockRemoteAPI.requests[6].method, 'GET');
244                 assert.equal(MockRemoteAPI.requests[6].url, '/json/builders/some-builder-1/builds/?select=-1&select=-2');
245                 MockRemoteAPI.requests[6].resolve({});
246                 assert.equal(MockRemoteAPI.requests[7].method, 'GET');
247                 assert.equal(MockRemoteAPI.requests[7].url, '/json/builders/some%20builder%202/builds/?select=-1&select=-2');
248                 MockRemoteAPI.requests[7].resolve({});
249                 return syncPromise;
250             }).then(() => {
251                 assert.equal(BuildRequest.all().length, 4);
252                 assert.equal(BuildRequest.findById(700).status(), 'pending');
253                 assert.equal(BuildRequest.findById(700).statusUrl(), null);
254                 assert.equal(BuildRequest.findById(701).status(), 'pending');
255                 assert.equal(BuildRequest.findById(701).statusUrl(), null);
256                 assert.equal(BuildRequest.findById(702).status(), 'pending');
257                 assert.equal(BuildRequest.findById(702).statusUrl(), null);
258                 assert.equal(BuildRequest.findById(703).status(), 'pending');
259                 assert.equal(BuildRequest.findById(703).statusUrl(), null);
260                 return BuildRequest.fetchForTriggerable(MockData.mockTestSyncConfigWithTwoBuilders().triggerableName);
261             }).then(() => {
262                 assert.equal(BuildRequest.all().length, 4);
263                 assert.equal(BuildRequest.findById(700).status(), 'scheduled');
264                 assert.equal(BuildRequest.findById(700).statusUrl(), 'http://build.webkit.org/builders/some-builder-1/');
265                 assert.equal(BuildRequest.findById(701).status(), 'pending');
266                 assert.equal(BuildRequest.findById(701).statusUrl(), null);
267                 assert.equal(BuildRequest.findById(702).status(), 'pending');
268                 assert.equal(BuildRequest.findById(702).statusUrl(), null);
269                 assert.equal(BuildRequest.findById(703).status(), 'pending');
270                 assert.equal(BuildRequest.findById(703).statusUrl(), null);
271             });
272         });
273
274         it('should update the status of a pending build and schedule a new build if the pending build had started running', () => {
275             const db = TestServer.database();
276             let syncPromise;
277             return MockData.addMockData(db, ['pending', 'pending', 'pending', 'pending']).then(() => {
278                 return Manifest.fetch();
279             }).then(() => {
280                 const config = MockData.mockTestSyncConfigWithTwoBuilders();
281                 const logger = new MockLogger;
282                 const slaveInfo = {name: 'sync-slave', password: 'password'};
283                 const triggerable = new BuildbotTriggerable(config, TestServer.remoteAPI(), MockRemoteAPI, slaveInfo, logger);
284                 syncPromise = triggerable.syncOnce();
285                 return MockRemoteAPI.waitForRequest();
286             }).then(() => {
287                 assert.equal(MockRemoteAPI.requests.length, 2);
288                 assert.equal(MockRemoteAPI.requests[0].method, 'GET');
289                 assert.equal(MockRemoteAPI.requests[0].url, '/json/builders/some-builder-1/pendingBuilds');
290                 MockRemoteAPI.requests[0].resolve([]);
291                 assert.equal(MockRemoteAPI.requests[1].method, 'GET');
292                 assert.equal(MockRemoteAPI.requests[1].url, '/json/builders/some%20builder%202/pendingBuilds');
293                 MockRemoteAPI.requests[1].resolve([]);
294                 return MockRemoteAPI.waitForRequest();
295             }).then(() => {
296                 assert.equal(MockRemoteAPI.requests.length, 4);
297                 assert.equal(MockRemoteAPI.requests[2].method, 'GET');
298                 assert.equal(MockRemoteAPI.requests[2].url, '/json/builders/some-builder-1/builds/?select=-1&select=-2');
299                 MockRemoteAPI.requests[2].resolve({[-1]: MockData.runningBuild({buildRequestId: 701}), [-2]: MockData.finishedBuild({buildRequestId: 700})});
300                 assert.equal(MockRemoteAPI.requests[3].method, 'GET');
301                 assert.equal(MockRemoteAPI.requests[3].url, '/json/builders/some%20builder%202/builds/?select=-1&select=-2');
302                 MockRemoteAPI.requests[3].resolve({});
303                 return MockRemoteAPI.waitForRequest();
304             }).then(() => {
305                 assert.equal(MockRemoteAPI.requests.length, 5);
306                 assert.equal(MockRemoteAPI.requests[4].method, 'POST');
307                 assert.equal(MockRemoteAPI.requests[4].url, '/builders/some-builder-1/force');
308                 assert.deepEqual(MockRemoteAPI.requests[4].data, {'wk': '191622', 'os': '10.11 15A284', 'build-request-id': '702'});
309                 MockRemoteAPI.requests[4].resolve('OK');
310                 return MockRemoteAPI.waitForRequest();
311             }).then(() => {
312                 assert.equal(MockRemoteAPI.requests.length, 7);
313                 assert.equal(MockRemoteAPI.requests[5].method, 'GET');
314                 assert.equal(MockRemoteAPI.requests[5].url, '/json/builders/some-builder-1/pendingBuilds');
315                 MockRemoteAPI.requests[5].resolve([MockData.pendingBuild({buildRequestId: 702})]);
316                 assert.equal(MockRemoteAPI.requests[6].method, 'GET');
317                 assert.equal(MockRemoteAPI.requests[6].url, '/json/builders/some%20builder%202/pendingBuilds');
318                 MockRemoteAPI.requests[6].resolve([]);
319                 return MockRemoteAPI.waitForRequest();
320             }).then(() => {
321                 assert.equal(MockRemoteAPI.requests.length, 9);
322                 assert.equal(MockRemoteAPI.requests[7].method, 'GET');
323                 assert.equal(MockRemoteAPI.requests[7].url, '/json/builders/some-builder-1/builds/?select=-1&select=-2');
324                 MockRemoteAPI.requests[7].resolve({[-1]: MockData.runningBuild({buildRequestId: 701}), [-2]: MockData.finishedBuild({buildRequestId: 700})});
325                 assert.equal(MockRemoteAPI.requests[8].method, 'GET');
326                 assert.equal(MockRemoteAPI.requests[8].url, '/json/builders/some%20builder%202/builds/?select=-1&select=-2');
327                 MockRemoteAPI.requests[8].resolve({});
328                 return syncPromise;
329             }).then(() => {
330                 assert.equal(BuildRequest.all().length, 4);
331                 assert.equal(BuildRequest.findById(700).status(), 'pending');
332                 assert.equal(BuildRequest.findById(700).statusUrl(), null);
333                 assert.equal(BuildRequest.findById(701).status(), 'pending');
334                 assert.equal(BuildRequest.findById(701).statusUrl(), null);
335                 assert.equal(BuildRequest.findById(702).status(), 'pending');
336                 assert.equal(BuildRequest.findById(702).statusUrl(), null);
337                 assert.equal(BuildRequest.findById(703).status(), 'pending');
338                 assert.equal(BuildRequest.findById(703).statusUrl(), null);
339                 return BuildRequest.fetchForTriggerable(MockData.mockTestSyncConfigWithTwoBuilders().triggerableName);
340             }).then(() => {
341                 assert.equal(BuildRequest.all().length, 4);
342                 assert.equal(BuildRequest.findById(700).status(), 'failed');
343                 assert.equal(BuildRequest.findById(700).statusUrl(), 'http://build.webkit.org/builders/some-builder-1/builds/123');
344                 assert.equal(BuildRequest.findById(701).status(), 'running');
345                 assert.equal(BuildRequest.findById(701).statusUrl(), 'http://build.webkit.org/builders/some-builder-1/builds/124');
346                 assert.equal(BuildRequest.findById(702).status(), 'scheduled');
347                 assert.equal(BuildRequest.findById(702).statusUrl(), 'http://build.webkit.org/builders/some-builder-1/');
348                 assert.equal(BuildRequest.findById(703).status(), 'pending');
349                 assert.equal(BuildRequest.findById(703).statusUrl(), null);
350             });
351         });
352
353         it('should update the status of a scheduled build if the pending build had started running', () => {
354             const db = TestServer.database();
355             let syncPromise;
356             return MockData.addMockData(db, ['scheduled', 'pending', 'pending', 'pending']).then(() => {
357                 return Manifest.fetch();
358             }).then(() => {
359                 let config = MockData.mockTestSyncConfigWithSingleBuilder();
360                 let logger = new MockLogger;
361                 let slaveInfo = {name: 'sync-slave', password: 'password'};
362                 let triggerable = new BuildbotTriggerable(config, TestServer.remoteAPI(), MockRemoteAPI, slaveInfo, logger);
363                 syncPromise = triggerable.syncOnce();
364                 return MockRemoteAPI.waitForRequest();
365             }).then(() => {
366                 assert.equal(MockRemoteAPI.requests.length, 1);
367                 assert.equal(MockRemoteAPI.requests[0].method, 'GET');
368                 assert.equal(MockRemoteAPI.requests[0].url, '/json/builders/some-builder-1/pendingBuilds');
369                 MockRemoteAPI.requests[0].resolve([MockData.pendingBuild({buildRequestId: 700})]);
370                 return MockRemoteAPI.waitForRequest();
371             }).then(() => {
372                 assert.equal(MockRemoteAPI.requests.length, 2);
373                 assert.equal(MockRemoteAPI.requests[1].method, 'GET');
374                 assert.equal(MockRemoteAPI.requests[1].url, '/json/builders/some-builder-1/builds/?select=-1&select=-2');
375                 MockRemoteAPI.requests[1].resolve({});
376                 return MockRemoteAPI.waitForRequest();
377             }).then(() => {
378                 assert.equal(MockRemoteAPI.requests.length, 3);
379                 assert.equal(MockRemoteAPI.requests[2].method, 'GET');
380                 assert.equal(MockRemoteAPI.requests[2].url, '/json/builders/some-builder-1/pendingBuilds');
381                 MockRemoteAPI.requests[2].resolve([]);
382                 return MockRemoteAPI.waitForRequest();
383             }).then(() => {
384                 assert.equal(MockRemoteAPI.requests.length, 4);
385                 assert.equal(MockRemoteAPI.requests[3].method, 'GET');
386                 assert.equal(MockRemoteAPI.requests[3].url, '/json/builders/some-builder-1/builds/?select=-1&select=-2');
387                 MockRemoteAPI.requests[3].resolve({[-1]: MockData.runningBuild({buildRequestId: 700})});
388                 return syncPromise;
389             }).then(() => {
390                 assert.equal(BuildRequest.all().length, 4);
391                 assert.equal(BuildRequest.findById(700).status(), 'scheduled');
392                 assert.equal(BuildRequest.findById(700).statusUrl(), null);
393                 assert.equal(BuildRequest.findById(701).status(), 'pending');
394                 assert.equal(BuildRequest.findById(701).statusUrl(), null);
395                 assert.equal(BuildRequest.findById(702).status(), 'pending');
396                 assert.equal(BuildRequest.findById(702).statusUrl(), null);
397                 assert.equal(BuildRequest.findById(703).status(), 'pending');
398                 assert.equal(BuildRequest.findById(703).statusUrl(), null);
399                 return BuildRequest.fetchForTriggerable(MockData.mockTestSyncConfigWithTwoBuilders().triggerableName);
400             }).then(() => {
401                 assert.equal(BuildRequest.all().length, 4);
402                 assert.equal(BuildRequest.findById(700).status(), 'running');
403                 assert.equal(BuildRequest.findById(700).statusUrl(), 'http://build.webkit.org/builders/some-builder-1/builds/124');
404                 assert.equal(BuildRequest.findById(701).status(), 'pending');
405                 assert.equal(BuildRequest.findById(701).statusUrl(), null);
406                 assert.equal(BuildRequest.findById(702).status(), 'pending');
407                 assert.equal(BuildRequest.findById(702).statusUrl(), null);
408                 assert.equal(BuildRequest.findById(703).status(), 'pending');
409                 assert.equal(BuildRequest.findById(703).statusUrl(), null);
410             });
411         });
412
413         it('should schedule a build request on a builder without pending builds if the request belongs to a new test group', () => {
414             const db = TestServer.database();
415             let syncPromise;
416             return Promise.all([
417                 MockData.addMockData(db, ['completed', 'pending', 'pending', 'pending']),
418                 MockData.addAnotherMockTestGroup(db, ['pending', 'pending', 'pending', 'pending'])
419             ]).then(() => {
420                 return Manifest.fetch();
421             }).then(() => {
422                 const config = MockData.mockTestSyncConfigWithTwoBuilders();
423                 const logger = new MockLogger;
424                 const slaveInfo = {name: 'sync-slave', password: 'password'};
425                 const triggerable = new BuildbotTriggerable(config, TestServer.remoteAPI(), MockRemoteAPI, slaveInfo, logger);
426                 syncPromise = triggerable.syncOnce();
427                 return MockRemoteAPI.waitForRequest();
428             }).then(() => {
429                 assert.equal(MockRemoteAPI.requests.length, 2);
430                 assert.equal(MockRemoteAPI.requests[0].method, 'GET');
431                 assert.equal(MockRemoteAPI.requests[0].url, '/json/builders/some-builder-1/pendingBuilds');
432                 MockRemoteAPI.requests[0].resolve([MockData.pendingBuild({buildRequestId: 702})]);
433                 assert.equal(MockRemoteAPI.requests[1].method, 'GET');
434                 assert.equal(MockRemoteAPI.requests[1].url, '/json/builders/some%20builder%202/pendingBuilds');
435                 MockRemoteAPI.requests[1].resolve([]);
436                 return MockRemoteAPI.waitForRequest();
437             }).then(() => {
438                 assert.equal(MockRemoteAPI.requests.length, 4);
439                 assert.equal(MockRemoteAPI.requests[2].method, 'GET');
440                 assert.equal(MockRemoteAPI.requests[2].url, '/json/builders/some-builder-1/builds/?select=-1&select=-2');
441                 MockRemoteAPI.requests[2].resolve({[-1]: MockData.runningBuild({buildRequestId: 701}), [-2]: MockData.finishedBuild({buildRequestId: 700})});
442                 assert.equal(MockRemoteAPI.requests[3].method, 'GET');
443                 assert.equal(MockRemoteAPI.requests[3].url, '/json/builders/some%20builder%202/builds/?select=-1&select=-2');
444                 MockRemoteAPI.requests[3].resolve({});
445                 return MockRemoteAPI.waitForRequest();
446             }).then(() => {
447                 assert.equal(MockRemoteAPI.requests.length, 5);
448                 assert.equal(MockRemoteAPI.requests[4].method, 'POST');
449                 assert.equal(MockRemoteAPI.requests[4].url, '/builders/some%20builder%202/force');
450                 assert.deepEqual(MockRemoteAPI.requests[4].data, {'wk': '191622', 'os': '10.11 15A284', 'build-request-id': '710'});
451                 MockRemoteAPI.requests[4].resolve('OK');
452                 return MockRemoteAPI.waitForRequest();
453             }).then(() => {
454                 assert.equal(MockRemoteAPI.requests.length, 7);
455                 assert.equal(MockRemoteAPI.requests[5].method, 'GET');
456                 assert.equal(MockRemoteAPI.requests[5].url, '/json/builders/some-builder-1/pendingBuilds');
457                 MockRemoteAPI.requests[5].resolve([MockData.pendingBuild({buildRequestId: 702})]);
458                 assert.equal(MockRemoteAPI.requests[6].method, 'GET');
459                 assert.equal(MockRemoteAPI.requests[6].url, '/json/builders/some%20builder%202/pendingBuilds');
460                 MockRemoteAPI.requests[6].resolve([MockData.pendingBuild({builder: 'some builder 2', buildRequestId: 710})]);
461                 return MockRemoteAPI.waitForRequest();
462             }).then(() => {
463                 assert.equal(MockRemoteAPI.requests.length, 9);
464                 assert.equal(MockRemoteAPI.requests[7].method, 'GET');
465                 assert.equal(MockRemoteAPI.requests[7].url, '/json/builders/some-builder-1/builds/?select=-1&select=-2');
466                 MockRemoteAPI.requests[7].resolve({[-1]: MockData.runningBuild({buildRequestId: 701}), [-2]: MockData.finishedBuild({buildRequestId: 700})});
467                 assert.equal(MockRemoteAPI.requests[8].method, 'GET');
468                 assert.equal(MockRemoteAPI.requests[8].url, '/json/builders/some%20builder%202/builds/?select=-1&select=-2');
469                 MockRemoteAPI.requests[8].resolve({});
470                 return syncPromise;
471             }).then(() => {
472                 assert.equal(BuildRequest.all().length, 8);
473                 assert.equal(BuildRequest.findById(700).status(), 'completed');
474                 assert.equal(BuildRequest.findById(700).statusUrl(), null);
475                 assert.equal(BuildRequest.findById(701).status(), 'pending');
476                 assert.equal(BuildRequest.findById(701).statusUrl(), null);
477                 assert.equal(BuildRequest.findById(702).status(), 'pending');
478                 assert.equal(BuildRequest.findById(702).statusUrl(), null);
479                 assert.equal(BuildRequest.findById(703).status(), 'pending');
480                 assert.equal(BuildRequest.findById(703).statusUrl(), null);
481                 assert.equal(BuildRequest.findById(710).status(), 'pending');
482                 assert.equal(BuildRequest.findById(710).statusUrl(), null);
483                 assert.equal(BuildRequest.findById(711).status(), 'pending');
484                 assert.equal(BuildRequest.findById(711).statusUrl(), null);
485                 assert.equal(BuildRequest.findById(712).status(), 'pending');
486                 assert.equal(BuildRequest.findById(712).statusUrl(), null);
487                 assert.equal(BuildRequest.findById(713).status(), 'pending');
488                 assert.equal(BuildRequest.findById(713).statusUrl(), null);
489                 return BuildRequest.fetchForTriggerable(MockData.mockTestSyncConfigWithTwoBuilders().triggerableName);
490             }).then(() => {
491                 assert.equal(BuildRequest.all().length, 8);
492                 assert.equal(BuildRequest.findById(700).status(), 'completed');
493                 assert.equal(BuildRequest.findById(700).statusUrl(), 'http://build.webkit.org/builders/some-builder-1/builds/123');
494                 assert.equal(BuildRequest.findById(701).status(), 'running');
495                 assert.equal(BuildRequest.findById(701).statusUrl(), 'http://build.webkit.org/builders/some-builder-1/builds/124');
496                 assert.equal(BuildRequest.findById(702).status(), 'scheduled');
497                 assert.equal(BuildRequest.findById(702).statusUrl(), 'http://build.webkit.org/builders/some-builder-1/');
498                 assert.equal(BuildRequest.findById(703).status(), 'pending');
499                 assert.equal(BuildRequest.findById(703).statusUrl(), null);
500                 assert.equal(BuildRequest.findById(710).status(), 'scheduled');
501                 assert.equal(BuildRequest.findById(710).statusUrl(), 'http://build.webkit.org/builders/some%20builder%202/');
502                 assert.equal(BuildRequest.findById(711).status(), 'pending');
503                 assert.equal(BuildRequest.findById(711).statusUrl(), null);
504                 assert.equal(BuildRequest.findById(712).status(), 'pending');
505                 assert.equal(BuildRequest.findById(712).statusUrl(), null);
506                 assert.equal(BuildRequest.findById(713).status(), 'pending');
507                 assert.equal(BuildRequest.findById(713).statusUrl(), null);
508             });
509         });
510
511         it('should schedule a build request on the same scheduler the first request had ran', () => {
512             const db = TestServer.database();
513             let syncPromise;
514             return Promise.all([
515                 MockData.addMockData(db, ['running', 'pending', 'pending', 'pending']),
516                 MockData.addAnotherMockTestGroup(db, ['running', 'pending', 'pending', 'pending'])
517             ]).then(() => {
518                 return Manifest.fetch();
519             }).then(() => {
520                 const config = MockData.mockTestSyncConfigWithTwoBuilders();
521                 const logger = new MockLogger;
522                 const slaveInfo = {name: 'sync-slave', password: 'password'};
523                 const triggerable = new BuildbotTriggerable(config, TestServer.remoteAPI(), MockRemoteAPI, slaveInfo, logger);
524                 syncPromise = triggerable.syncOnce();
525                 return MockRemoteAPI.waitForRequest();
526             }).then(() => {
527                 assert.equal(MockRemoteAPI.requests.length, 2);
528                 assert.equal(MockRemoteAPI.requests[0].method, 'GET');
529                 assert.equal(MockRemoteAPI.requests[0].url, '/json/builders/some-builder-1/pendingBuilds');
530                 MockRemoteAPI.requests[0].resolve([]);
531                 assert.equal(MockRemoteAPI.requests[1].method, 'GET');
532                 assert.equal(MockRemoteAPI.requests[1].url, '/json/builders/some%20builder%202/pendingBuilds');
533                 MockRemoteAPI.requests[1].resolve([]);
534                 return MockRemoteAPI.waitForRequest();
535             }).then(() => {
536                 assert.equal(MockRemoteAPI.requests.length, 4);
537                 assert.equal(MockRemoteAPI.requests[2].method, 'GET');
538                 assert.equal(MockRemoteAPI.requests[2].url, '/json/builders/some-builder-1/builds/?select=-1&select=-2');
539                 MockRemoteAPI.requests[2].resolve({[-1]: MockData.runningBuild({buildRequestId: 710})});
540                 assert.equal(MockRemoteAPI.requests[3].method, 'GET');
541                 assert.equal(MockRemoteAPI.requests[3].url, '/json/builders/some%20builder%202/builds/?select=-1&select=-2');
542                 MockRemoteAPI.requests[3].resolve({[-1]: MockData.runningBuild({builder: 'some builder 2', buildRequestId: 700})});
543                 return MockRemoteAPI.waitForRequest();
544             }).then(() => {
545                 assert.equal(MockRemoteAPI.requests.length, 6);
546                 assert.equal(MockRemoteAPI.requests[4].method, 'POST');
547                 assert.equal(MockRemoteAPI.requests[4].url, '/builders/some%20builder%202/force');
548                 assert.deepEqual(MockRemoteAPI.requests[4].data, {'wk': '192736', 'os': '10.11 15A284', 'build-request-id': '701'});
549                 MockRemoteAPI.requests[4].resolve('OK');
550                 assert.equal(MockRemoteAPI.requests[5].method, 'POST');
551                 assert.equal(MockRemoteAPI.requests[5].url, '/builders/some-builder-1/force');
552                 assert.deepEqual(MockRemoteAPI.requests[5].data, {'wk': '192736', 'os': '10.11 15A284', 'build-request-id': '711'});
553                 MockRemoteAPI.requests[5].resolve('OK');
554                 return MockRemoteAPI.waitForRequest();
555             }).then(() => {
556                 assert.equal(MockRemoteAPI.requests.length, 8);
557                 assert.equal(MockRemoteAPI.requests[6].method, 'GET');
558                 assert.equal(MockRemoteAPI.requests[6].url, '/json/builders/some-builder-1/pendingBuilds');
559                 MockRemoteAPI.requests[6].resolve([MockData.pendingBuild({buildRequestId: 711})]);
560                 assert.equal(MockRemoteAPI.requests[7].method, 'GET');
561                 assert.equal(MockRemoteAPI.requests[7].url, '/json/builders/some%20builder%202/pendingBuilds');
562                 MockRemoteAPI.requests[7].resolve([MockData.pendingBuild({builder: 'some builder 2',buildRequestId: 701})]);
563                 return MockRemoteAPI.waitForRequest();
564             }).then(() => {
565                 assert.equal(MockRemoteAPI.requests.length, 10);
566                 assert.equal(MockRemoteAPI.requests[8].method, 'GET');
567                 assert.equal(MockRemoteAPI.requests[8].url, '/json/builders/some-builder-1/builds/?select=-1&select=-2');
568                 MockRemoteAPI.requests[8].resolve({[-1]: MockData.runningBuild({buildRequestId: 710})});
569                 assert.equal(MockRemoteAPI.requests[9].method, 'GET');
570                 assert.equal(MockRemoteAPI.requests[9].url, '/json/builders/some%20builder%202/builds/?select=-1&select=-2');
571                 MockRemoteAPI.requests[9].resolve({[-1]: MockData.runningBuild({builder: 'some builder 2', buildRequestId: 700})});
572                 return syncPromise;
573             }).then(() => {
574                 assert.equal(BuildRequest.all().length, 8);
575                 assert.equal(BuildRequest.findById(700).status(), 'running');
576                 assert.equal(BuildRequest.findById(700).statusUrl(), null);
577                 assert.equal(BuildRequest.findById(701).status(), 'pending');
578                 assert.equal(BuildRequest.findById(701).statusUrl(), null);
579                 assert.equal(BuildRequest.findById(702).status(), 'pending');
580                 assert.equal(BuildRequest.findById(702).statusUrl(), null);
581                 assert.equal(BuildRequest.findById(703).status(), 'pending');
582                 assert.equal(BuildRequest.findById(703).statusUrl(), null);
583                 assert.equal(BuildRequest.findById(710).status(), 'running');
584                 assert.equal(BuildRequest.findById(710).statusUrl(), null);
585                 assert.equal(BuildRequest.findById(711).status(), 'pending');
586                 assert.equal(BuildRequest.findById(711).statusUrl(), null);
587                 assert.equal(BuildRequest.findById(712).status(), 'pending');
588                 assert.equal(BuildRequest.findById(712).statusUrl(), null);
589                 assert.equal(BuildRequest.findById(713).status(), 'pending');
590                 assert.equal(BuildRequest.findById(713).statusUrl(), null);
591                 return BuildRequest.fetchForTriggerable(MockData.mockTestSyncConfigWithTwoBuilders().triggerableName);
592             }).then(() => {
593                 assert.equal(BuildRequest.all().length, 8);
594                 assert.equal(BuildRequest.findById(700).status(), 'running');
595                 assert.equal(BuildRequest.findById(700).statusUrl(), 'http://build.webkit.org/builders/some%20builder%202/builds/124');
596                 assert.equal(BuildRequest.findById(701).status(), 'scheduled');
597                 assert.equal(BuildRequest.findById(701).statusUrl(), 'http://build.webkit.org/builders/some%20builder%202/');
598                 assert.equal(BuildRequest.findById(702).status(), 'pending');
599                 assert.equal(BuildRequest.findById(702).statusUrl(), null);
600                 assert.equal(BuildRequest.findById(703).status(), 'pending');
601                 assert.equal(BuildRequest.findById(703).statusUrl(), null);
602                 assert.equal(BuildRequest.findById(710).status(), 'running');
603                 assert.equal(BuildRequest.findById(710).statusUrl(), 'http://build.webkit.org/builders/some-builder-1/builds/124');
604                 assert.equal(BuildRequest.findById(711).status(), 'scheduled');
605                 assert.equal(BuildRequest.findById(711).statusUrl(), 'http://build.webkit.org/builders/some-builder-1/');
606                 assert.equal(BuildRequest.findById(712).status(), 'pending');
607                 assert.equal(BuildRequest.findById(712).statusUrl(), null);
608                 assert.equal(BuildRequest.findById(713).status(), 'pending');
609                 assert.equal(BuildRequest.findById(713).statusUrl(), null);
610             });
611         });
612
613         it('should wait for POST to complete before trying to poll buildbot again', () => {
614             const db = TestServer.database();
615             const requests = MockRemoteAPI.requests;
616             let syncPromise;
617             return Promise.all([
618                 MockData.addMockData(db, ['pending', 'pending', 'pending', 'pending']),
619                 MockData.addAnotherMockTestGroup(db, ['pending', 'pending', 'pending', 'pending'])
620             ]).then(() => Manifest.fetch()).then(() => {
621                 const config = MockData.mockTestSyncConfigWithSingleBuilder();
622                 const logger = new MockLogger;
623                 const slaveInfo = {name: 'sync-slave', password: 'password'};
624                 const triggerable = new BuildbotTriggerable(config, TestServer.remoteAPI(), MockRemoteAPI, slaveInfo, logger);
625                 syncPromise = triggerable.syncOnce();
626                 return MockRemoteAPI.waitForRequest();
627             }).then(() => {
628                 assert.equal(requests.length, 1);
629                 assert.equal(requests[0].method, 'GET');
630                 assert.equal(requests[0].url, '/json/builders/some-builder-1/pendingBuilds');
631                 MockRemoteAPI.requests[0].resolve([]);
632                 return MockRemoteAPI.waitForRequest();
633             }).then(() => {
634                 assert.equal(requests.length, 2);
635                 assert.equal(requests[1].method, 'GET');
636                 assert.equal(requests[1].url, '/json/builders/some-builder-1/builds/?select=-1&select=-2');
637                 requests[1].resolve({});
638                 return MockRemoteAPI.waitForRequest();
639             }).then(() => {
640                 assert.equal(requests.length, 3);
641                 assert.equal(requests[2].method, 'POST');
642                 assert.equal(requests[2].url, '/builders/some-builder-1/force');
643                 assert.deepEqual(requests[2].data, {'wk': '191622', 'os': '10.11 15A284', 'build-request-id': '700'});
644                 return new Promise((resolve) => setTimeout(resolve, 10));
645             }).then(() => {
646                 assert.equal(requests.length, 3);
647                 requests[2].resolve('OK');
648                 return MockRemoteAPI.waitForRequest();
649             }).then(() => {
650                 assert.equal(requests.length, 4);
651                 assert.equal(requests[3].method, 'GET');
652                 assert.equal(requests[3].url, '/json/builders/some-builder-1/pendingBuilds');
653                 MockRemoteAPI.requests[3].resolve([MockData.pendingBuild({buildRequestId: 700})]);
654                 return MockRemoteAPI.waitForRequest();
655             }).then(() => {
656                 assert.equal(requests.length, 5);
657                 assert.equal(requests[4].method, 'GET');
658                 assert.equal(requests[4].url, '/json/builders/some-builder-1/builds/?select=-1&select=-2');
659                 requests[4].resolve({});
660                 return syncPromise;
661             }).then(() => {
662                 return BuildRequest.fetchForTriggerable(MockData.mockTestSyncConfigWithTwoBuilders().triggerableName);
663             }).then(() => {
664                 assert.equal(BuildRequest.all().length, 8);
665                 assert.equal(BuildRequest.findById(700).status(), 'scheduled');
666                 assert.equal(BuildRequest.findById(700).statusUrl(), 'http://build.webkit.org/builders/some-builder-1/');
667                 assert.equal(BuildRequest.findById(701).status(), 'pending');
668                 assert.equal(BuildRequest.findById(701).statusUrl(), null);
669                 assert.equal(BuildRequest.findById(702).status(), 'pending');
670                 assert.equal(BuildRequest.findById(702).statusUrl(), null);
671                 assert.equal(BuildRequest.findById(703).status(), 'pending');
672                 assert.equal(BuildRequest.findById(703).statusUrl(), null);
673                 assert.equal(BuildRequest.findById(710).status(), 'pending');
674                 assert.equal(BuildRequest.findById(710).statusUrl(), null);
675                 assert.equal(BuildRequest.findById(711).status(), 'pending');
676                 assert.equal(BuildRequest.findById(711).statusUrl(), null);
677                 assert.equal(BuildRequest.findById(712).status(), 'pending');
678                 assert.equal(BuildRequest.findById(712).statusUrl(), null);
679                 assert.equal(BuildRequest.findById(713).status(), 'pending');
680                 assert.equal(BuildRequest.findById(713).statusUrl(), null);
681             });
682         });
683
684         it('should recover from multiple test groups running simultenously', () => {
685             const db = TestServer.database();
686             let syncPromise;
687             return Promise.all([
688                 MockData.addMockData(db, ['completed', 'pending', 'pending', 'pending']),
689                 MockData.addAnotherMockTestGroup(db, ['completed', 'pending', 'pending', 'pending'])
690             ]).then(() => {
691                 return Manifest.fetch();
692             }).then(() => {
693                 const config = MockData.mockTestSyncConfigWithSingleBuilder();
694                 const logger = new MockLogger;
695                 const slaveInfo = {name: 'sync-slave', password: 'password'};
696                 const triggerable = new BuildbotTriggerable(config, TestServer.remoteAPI(), MockRemoteAPI, slaveInfo, logger);
697                 syncPromise = triggerable.syncOnce();
698                 return MockRemoteAPI.waitForRequest();
699             }).then(() => {
700                 assert.equal(MockRemoteAPI.requests.length, 1);
701                 assert.equal(MockRemoteAPI.requests[0].method, 'GET');
702                 assert.equal(MockRemoteAPI.requests[0].url, '/json/builders/some-builder-1/pendingBuilds');
703                 MockRemoteAPI.requests[0].resolve([MockData.pendingBuild({buildRequestId: 711})]);
704                 return MockRemoteAPI.waitForRequest();
705             }).then(() => {
706                 assert.equal(MockRemoteAPI.requests.length, 2);
707                 assert.equal(MockRemoteAPI.requests[1].method, 'GET');
708                 assert.equal(MockRemoteAPI.requests[1].url, '/json/builders/some-builder-1/builds/?select=-1&select=-2');
709                 MockRemoteAPI.requests[1].resolve({[-1]: MockData.runningBuild({buildRequestId: 700}), [-2]: MockData.finishedBuild({buildRequestId: 710})});
710                 return MockRemoteAPI.waitForRequest();
711             }).then(() => {
712                 assert.equal(MockRemoteAPI.requests.length, 3);
713                 assert.equal(MockRemoteAPI.requests[2].method, 'GET');
714                 assert.equal(MockRemoteAPI.requests[2].url, '/json/builders/some-builder-1/pendingBuilds');
715                 MockRemoteAPI.requests[2].resolve([MockData.pendingBuild({buildRequestId: 701})]);
716                 return MockRemoteAPI.waitForRequest();
717             }).then(() => {
718                 assert.equal(MockRemoteAPI.requests.length, 4);
719                 assert.equal(MockRemoteAPI.requests[3].method, 'GET');
720                 assert.equal(MockRemoteAPI.requests[3].url, '/json/builders/some-builder-1/builds/?select=-1&select=-2');
721                 MockRemoteAPI.requests[3].resolve({[-1]: MockData.runningBuild({buildRequestId: 700}), [-2]: MockData.finishedBuild({buildRequestId: 710})});
722                 return syncPromise;
723             }).then(() => {
724                 assert.equal(BuildRequest.all().length, 8);
725                 assert.equal(BuildRequest.findById(700).status(), 'completed');
726                 assert.equal(BuildRequest.findById(700).statusUrl(), null);
727                 assert.equal(BuildRequest.findById(701).status(), 'pending');
728                 assert.equal(BuildRequest.findById(701).statusUrl(), null);
729                 assert.equal(BuildRequest.findById(702).status(), 'pending');
730                 assert.equal(BuildRequest.findById(702).statusUrl(), null);
731                 assert.equal(BuildRequest.findById(703).status(), 'pending');
732                 assert.equal(BuildRequest.findById(703).statusUrl(), null);
733                 assert.equal(BuildRequest.findById(710).status(), 'completed');
734                 assert.equal(BuildRequest.findById(710).statusUrl(), null);
735                 assert.equal(BuildRequest.findById(711).status(), 'pending');
736                 assert.equal(BuildRequest.findById(711).statusUrl(), null);
737                 assert.equal(BuildRequest.findById(712).status(), 'pending');
738                 assert.equal(BuildRequest.findById(712).statusUrl(), null);
739                 assert.equal(BuildRequest.findById(713).status(), 'pending');
740                 assert.equal(BuildRequest.findById(713).statusUrl(), null);
741                 return BuildRequest.fetchForTriggerable(MockData.mockTestSyncConfigWithTwoBuilders().triggerableName);
742             }).then(() => {
743                 assert.equal(BuildRequest.all().length, 8);
744                 assert.equal(BuildRequest.findById(700).status(), 'completed');
745                 assert.equal(BuildRequest.findById(700).statusUrl(), 'http://build.webkit.org/builders/some-builder-1/builds/124');
746                 assert.equal(BuildRequest.findById(701).status(), 'scheduled');
747                 assert.equal(BuildRequest.findById(701).statusUrl(), 'http://build.webkit.org/builders/some-builder-1/');
748                 assert.equal(BuildRequest.findById(702).status(), 'pending');
749                 assert.equal(BuildRequest.findById(702).statusUrl(), null);
750                 assert.equal(BuildRequest.findById(703).status(), 'pending');
751                 assert.equal(BuildRequest.findById(703).statusUrl(), null);
752                 assert.equal(BuildRequest.findById(710).status(), 'completed');
753                 assert.equal(BuildRequest.findById(710).statusUrl(), 'http://build.webkit.org/builders/some-builder-1/builds/123');
754                 assert.equal(BuildRequest.findById(711).status(), 'pending');
755                 assert.equal(BuildRequest.findById(711).statusUrl(), null);
756                 assert.equal(BuildRequest.findById(712).status(), 'pending');
757                 assert.equal(BuildRequest.findById(712).statusUrl(), null);
758                 assert.equal(BuildRequest.findById(713).status(), 'pending');
759                 assert.equal(BuildRequest.findById(713).statusUrl(), null);
760             });
761         });
762
763         it('should recover from missing failed build request', () => {
764             const db = TestServer.database();
765             let syncPromise;
766             return MockData.addMockData(db, ['failed', 'pending', 'pending', 'pending']).then(() => {
767                 return Manifest.fetch();
768             }).then(() => {
769                 const config = MockData.mockTestSyncConfigWithSingleBuilder();
770                 const logger = new MockLogger;
771                 const slaveInfo = {name: 'sync-slave', password: 'password'};
772                 const triggerable = new BuildbotTriggerable(config, TestServer.remoteAPI(), MockRemoteAPI, slaveInfo, logger);
773                 syncPromise = triggerable.syncOnce();
774                 return MockRemoteAPI.waitForRequest();
775             }).then(() => {
776                 assert.equal(MockRemoteAPI.requests.length, 1);
777                 assert.equal(MockRemoteAPI.requests[0].method, 'GET');
778                 assert.equal(MockRemoteAPI.requests[0].url, '/json/builders/some-builder-1/pendingBuilds');
779                 MockRemoteAPI.requests[0].resolve([]);
780                 return MockRemoteAPI.waitForRequest();
781             }).then(() => {
782                 assert.equal(MockRemoteAPI.requests.length, 2);
783                 assert.equal(MockRemoteAPI.requests[1].method, 'GET');
784                 assert.equal(MockRemoteAPI.requests[1].url, '/json/builders/some-builder-1/builds/?select=-1&select=-2');
785                 MockRemoteAPI.requests[1].resolve({});
786                 return MockRemoteAPI.waitForRequest();
787             }).then(() => {
788                 assert.equal(MockRemoteAPI.requests.length, 3);
789                 assert.equal(MockRemoteAPI.requests[2].method, 'POST');
790                 assert.equal(MockRemoteAPI.requests[2].url, '/builders/some-builder-1/force');
791                 assert.deepEqual(MockRemoteAPI.requests[2].data, {'wk': '192736', 'os': '10.11 15A284', 'build-request-id': '701'});
792                 MockRemoteAPI.requests[2].resolve('OK');
793                 return MockRemoteAPI.waitForRequest();
794             }).then(() => {
795                 assert.equal(MockRemoteAPI.requests.length, 4);
796                 assert.equal(MockRemoteAPI.requests[3].method, 'GET');
797                 assert.equal(MockRemoteAPI.requests[3].url, '/json/builders/some-builder-1/pendingBuilds');
798                 MockRemoteAPI.requests[3].resolve([MockData.pendingBuild({buildRequestId: 701})]);
799                 return MockRemoteAPI.waitForRequest();
800             }).then(() => {
801                 assert.equal(MockRemoteAPI.requests.length, 5);
802                 assert.equal(MockRemoteAPI.requests[4].method, 'GET');
803                 assert.equal(MockRemoteAPI.requests[4].url, '/json/builders/some-builder-1/builds/?select=-1&select=-2');
804                 MockRemoteAPI.requests[4].resolve({});
805                 return syncPromise;
806             }).then(() => {
807                 assert.equal(BuildRequest.all().length, 4);
808                 assert.equal(BuildRequest.findById(700).status(), 'failed');
809                 assert.equal(BuildRequest.findById(700).statusUrl(), null);
810                 assert.equal(BuildRequest.findById(701).status(), 'pending');
811                 assert.equal(BuildRequest.findById(701).statusUrl(), null);
812                 assert.equal(BuildRequest.findById(702).status(), 'pending');
813                 assert.equal(BuildRequest.findById(702).statusUrl(), null);
814                 assert.equal(BuildRequest.findById(703).status(), 'pending');
815                 assert.equal(BuildRequest.findById(703).statusUrl(), null);
816                 return BuildRequest.fetchForTriggerable(MockData.mockTestSyncConfigWithTwoBuilders().triggerableName);
817             }).then(() => {
818                 assert.equal(BuildRequest.all().length, 4);
819                 assert.equal(BuildRequest.findById(700).status(), 'failed');
820                 assert.equal(BuildRequest.findById(700).statusUrl(), null);
821                 assert.equal(BuildRequest.findById(701).status(), 'scheduled');
822                 assert.equal(BuildRequest.findById(701).statusUrl(), 'http://build.webkit.org/builders/some-builder-1/');
823                 assert.equal(BuildRequest.findById(702).status(), 'pending');
824                 assert.equal(BuildRequest.findById(702).statusUrl(), null);
825                 assert.equal(BuildRequest.findById(703).status(), 'pending');
826                 assert.equal(BuildRequest.findById(703).statusUrl(), null);
827             });
828         });
829
830         it('should update the status of a supposedly scheduled build that went missing', () => {
831             const db = TestServer.database();
832             let syncPromise;
833             return MockData.addMockData(db, ['scheduled', 'pending', 'pending', 'pending']).then(() => {
834                 return Manifest.fetch();
835             }).then(() => {
836                 const config = MockData.mockTestSyncConfigWithSingleBuilder();
837                 const logger = new MockLogger;
838                 const slaveInfo = {name: 'sync-slave', password: 'password'};
839                 const triggerable = new BuildbotTriggerable(config, TestServer.remoteAPI(), MockRemoteAPI, slaveInfo, logger);
840                 syncPromise = triggerable.syncOnce();
841                 return MockRemoteAPI.waitForRequest();
842             }).then(() => {
843                 assert.equal(MockRemoteAPI.requests.length, 1);
844                 assert.equal(MockRemoteAPI.requests[0].method, 'GET');
845                 assert.equal(MockRemoteAPI.requests[0].url, '/json/builders/some-builder-1/pendingBuilds');
846                 MockRemoteAPI.requests[0].resolve([]);
847                 return MockRemoteAPI.waitForRequest();
848             }).then(() => {
849                 assert.equal(MockRemoteAPI.requests.length, 2);
850                 assert.equal(MockRemoteAPI.requests[1].method, 'GET');
851                 assert.equal(MockRemoteAPI.requests[1].url, '/json/builders/some-builder-1/builds/?select=-1&select=-2');
852                 MockRemoteAPI.requests[1].resolve({});
853                 return MockRemoteAPI.waitForRequest();
854             }).then(() => {
855                 assert.equal(MockRemoteAPI.requests.length, 3);
856                 assert.equal(MockRemoteAPI.requests[2].method, 'GET');
857                 assert.equal(MockRemoteAPI.requests[2].url, '/json/builders/some-builder-1/pendingBuilds');
858                 MockRemoteAPI.requests[2].resolve([]);
859                 return MockRemoteAPI.waitForRequest();
860             }).then(() => {
861                 assert.equal(MockRemoteAPI.requests.length, 4);
862                 assert.equal(MockRemoteAPI.requests[3].method, 'GET');
863                 assert.equal(MockRemoteAPI.requests[3].url, '/json/builders/some-builder-1/builds/?select=-1&select=-2');
864                 MockRemoteAPI.requests[3].resolve({});
865                 return syncPromise;
866             }).then(() => {
867                 assert.equal(BuildRequest.all().length, 4);
868                 assert.equal(BuildRequest.findById(700).status(), 'scheduled');
869                 assert.equal(BuildRequest.findById(701).status(), 'pending');
870                 assert.equal(BuildRequest.findById(702).status(), 'pending');
871                 assert.equal(BuildRequest.findById(703).status(), 'pending');
872                 return BuildRequest.fetchForTriggerable(MockData.mockTestSyncConfigWithTwoBuilders().triggerableName);
873             }).then(() => {
874                 assert.equal(BuildRequest.all().length, 4);
875                 assert.equal(BuildRequest.findById(700).status(), 'failed');
876                 assert.equal(BuildRequest.findById(701).status(), 'pending');
877                 assert.equal(BuildRequest.findById(702).status(), 'pending');
878                 assert.equal(BuildRequest.findById(703).status(), 'pending');
879             });
880         });
881
882         it('should schedule a build request of an user created test group before ones created by automatic change detection', () => {
883             const db = TestServer.database();
884             let syncPromise;
885             return Promise.all([
886                 MockData.addMockData(db, ['pending', 'pending', 'pending', 'pending']),
887                 MockData.addAnotherMockTestGroup(db, ['pending', 'pending', 'pending', 'pending'], 'rniwa'),
888             ]).then(() => {
889                 return Manifest.fetch();
890             }).then(() => {
891                 const config = MockData.mockTestSyncConfigWithSingleBuilder();
892                 const logger = new MockLogger;
893                 const slaveInfo = {name: 'sync-slave', password: 'password'};
894                 const triggerable = new BuildbotTriggerable(config, TestServer.remoteAPI(), MockRemoteAPI, slaveInfo, logger);
895                 syncPromise = triggerable.syncOnce();
896                 return MockRemoteAPI.waitForRequest();
897             }).then(() => {
898                 assert.equal(MockRemoteAPI.requests.length, 1);
899                 assert.equal(MockRemoteAPI.requests[0].method, 'GET');
900                 assert.equal(MockRemoteAPI.requests[0].url, '/json/builders/some-builder-1/pendingBuilds');
901                 MockRemoteAPI.requests[0].resolve([]);
902                 return MockRemoteAPI.waitForRequest();
903             }).then(() => {
904                 assert.equal(MockRemoteAPI.requests.length, 2);
905                 assert.equal(MockRemoteAPI.requests[1].method, 'GET');
906                 assert.equal(MockRemoteAPI.requests[1].url, '/json/builders/some-builder-1/builds/?select=-1&select=-2');
907                 MockRemoteAPI.requests[1].resolve({});
908                 return MockRemoteAPI.waitForRequest();
909             }).then(() => {
910                 assert.equal(MockRemoteAPI.requests.length, 3);
911                 assert.equal(MockRemoteAPI.requests[2].method, 'POST');
912                 assert.equal(MockRemoteAPI.requests[2].url, '/builders/some-builder-1/force');
913                 assert.deepEqual(MockRemoteAPI.requests[2].data, {'wk': '191622', 'os': '10.11 15A284', 'build-request-id': '710'});
914                 MockRemoteAPI.requests[2].resolve('OK');
915             });
916         });
917     });
918
919     describe('updateTriggerables', () => {
920
921         function refetchManifest()
922         {
923             MockData.resetV3Models();
924             return TestServer.remoteAPI().getJSON('/api/manifest').then((content) => Manifest._didFetchManifest(content));
925         }
926
927         it('should update available triggerables', () => {
928             const db = TestServer.database();
929             let macos;
930             let webkit;
931             return MockData.addMockData(db).then(() => {
932                 return Manifest.fetch();
933             }).then(() => {
934                 macos = Repository.findById(9);
935                 assert.equal(macos.name(), 'macOS');
936                 webkit = Repository.findById(11);
937                 assert.equal(webkit.name(), 'WebKit');
938                 assert.equal(Triggerable.all().length, 1);
939
940                 const triggerable = Triggerable.all()[0];
941                 assert.equal(triggerable.name(), 'build-webkit');
942
943                 const test = Test.findById(MockData.someTestId());
944                 const platform = Platform.findById(MockData.somePlatformId());
945                 assert.equal(Triggerable.findByTestConfiguration(test, platform), null);
946
947                 const groups = TriggerableRepositoryGroup.sortByName(triggerable.repositoryGroups());
948                 assert.equal(groups.length, 1);
949                 assert.equal(groups[0].name(), 'webkit-svn');
950                 assert.deepEqual(groups[0].repositories(), [webkit, macos]);
951
952                 const config = MockData.mockTestSyncConfigWithSingleBuilder();
953                 config.repositoryGroups = {
954                     'system-and-roots': {description: 'Custom Roots', repositories: {'macOS': {}}, properties: {'os': '<macOS>'}, acceptsRoots: true},
955                     'system-and-webkit': {repositories: {'WebKit': {acceptsPatch: true}, 'macOS': {}}, properties: {'os': '<macOS>', 'wk': '<WebKit>'}}
956                 }
957
958                 const logger = new MockLogger;
959                 const slaveInfo = {name: 'sync-slave', password: 'password'};
960                 const buildbotTriggerable = new BuildbotTriggerable(config, TestServer.remoteAPI(), MockRemoteAPI, slaveInfo, logger);
961                 return buildbotTriggerable.updateTriggerable();
962             }).then(() => refetchManifest()).then(() => {
963                 assert.equal(Triggerable.all().length, 1);
964
965                 let test = Test.findById(MockData.someTestId());
966                 let platform = Platform.findById(MockData.somePlatformId());
967                 let triggerable = Triggerable.findByTestConfiguration(test, platform);
968                 assert.equal(triggerable.name(), 'build-webkit');
969
970                 const groups = TriggerableRepositoryGroup.sortByName(triggerable.repositoryGroups());
971                 assert.equal(groups.length, 2);
972                 assert.equal(groups[0].name(), 'system-and-roots');
973                 assert.equal(groups[0].description(), 'Custom Roots');
974                 assert.deepEqual(groups[0].repositories(), [macos]);
975                 assert.equal(groups[0].acceptsCustomRoots(), true);
976                 assert.equal(groups[1].name(), 'system-and-webkit');
977                 assert.deepEqual(groups[1].repositories(), [webkit, macos]);
978                 assert.equal(groups[1].acceptsCustomRoots(), false);
979
980                 const config = MockData.mockTestSyncConfigWithSingleBuilder();
981                 config.repositoryGroups = [ ];
982
983                 const logger = new MockLogger;
984                 const slaveInfo = {name: 'sync-slave', password: 'password'};
985                 const buildbotTriggerable = new BuildbotTriggerable(config, TestServer.remoteAPI(), MockRemoteAPI, slaveInfo, logger);
986                 return buildbotTriggerable.updateTriggerable();
987             }).then(() => refetchManifest()).then(() => {
988                 assert.equal(Triggerable.all().length, 1);
989                 const groups = TriggerableRepositoryGroup.sortByName(Triggerable.all()[0].repositoryGroups());
990                 assert.equal(groups.length, 2);
991                 assert.equal(groups[0].name(), 'system-and-roots');
992                 assert.deepEqual(groups[0].repositories(), [macos]);
993                 assert.equal(groups[1].name(), 'system-and-webkit');
994                 assert.deepEqual(groups[1].repositories(), [webkit, macos]);
995             })
996         });
997     });
998
999 });