Updating commit in OSBuildFetcher should respect revision range in config.
[WebKit-https.git] / Websites / perf.webkit.org / server-tests / tools-os-build-fetcher-tests.js
1 'use strict';
2
3 const assert = require('assert');
4
5 const OSBuildFetcher = require('../tools/js/os-build-fetcher.js').OSBuildFetcher;
6 const MockRemoteAPI = require('../unit-tests/resources/mock-remote-api.js').MockRemoteAPI;
7 const TestServer = require('./resources/test-server.js');
8 const addSlaveForReport = require('./resources/common-operations.js').addSlaveForReport;
9 const prepareServerTest = require('./resources/common-operations.js').prepareServerTest;
10 const MockSubprocess = require('./resources/mock-subprocess.js').MockSubprocess;
11 const MockLogger = require('./resources/mock-logger.js').MockLogger;
12
13
14 describe('OSBuildFetcher', function() {
15     this.timeout(5000);
16     TestServer.inject('node');
17
18     beforeEach(function () {
19         MockRemoteAPI.reset('http://build.webkit.org');
20         MockSubprocess.reset();
21     });
22
23     const emptyReport = {
24         'slaveName': 'someSlave',
25         'slavePassword': 'somePassword',
26     };
27
28     const slaveAuth = {
29         'name': 'someSlave',
30         'password': 'somePassword'
31     };
32
33     const ownedCommitWithWebKit = {
34         'WebKit': {'revision': '141978'}
35     };
36
37     const anotherownedCommitWithWebKit = {
38         'WebKit': {'revision': '141999'}
39     };
40
41     const anotherownedCommitWithWebKitAndJavaScriptCore = {
42         'WebKit': {'revision': '142000'},
43         'JavaScriptCore': {'revision': '142000'}
44     };
45
46     const osxCommit = {
47         'repository': 'OSX',
48         'revision': 'Sierra16D32',
49         'order': 1603003200
50     };
51
52     const anotherOSXCommit = {
53         'repository': 'OSX',
54         'revision': 'Sierra16E32',
55         'order': 1603003200
56     };
57
58
59     const config = {
60         'name': 'OSX',
61         'customCommands': [
62             {
63                 'command': ['list', 'all osx 16Dxx builds'],
64                 'ownedCommitCommand': ['list', 'ownedCommit', 'for', 'revision'],
65                 'linesToIgnore': '^\\.*$',
66                 'minRevision': 'Sierra16D0',
67                 'maxRevision': 'Sierra16D999'
68             },
69             {
70                 'command': ['list', 'all osx 16Exx builds'],
71                 'ownedCommitCommand': ['list', 'ownedCommit', 'for', 'revision'],
72                 'linesToIgnore': '^\\.*$',
73                 'minRevision': 'Sierra16E0',
74                 'maxRevision': 'Sierra16E999'
75             }
76         ]
77     };
78
79
80     const configWithoutOwnedCommitCommand = {
81         'name': 'OSX',
82         'customCommands': [
83             {
84                 'command': ['list', 'all osx 16Dxx builds'],
85                 'linesToIgnore': '^\\.*$',
86                 'minRevision': 'Sierra16D0',
87                 'maxRevision': 'Sierra16D999'
88             },
89             {
90                 'command': ['list', 'all osx 16Exx builds'],
91                 'linesToIgnore': '^\\.*$',
92                 'minRevision': 'Sierra16E0',
93                 'maxRevision': 'Sierra16E999'
94             }
95         ]
96     };
97
98     const configTrackingOneOS = {
99         'name': 'OSX',
100         'customCommands': [
101             {
102                 'command': ['list', 'all osx 16Dxx builds'],
103                 'linesToIgnore': '^\\.*$',
104                 'minRevision': 'Sierra16D100',
105                 'maxRevision': 'Sierra16D999'
106             }
107         ]
108     };
109
110     describe('OSBuilderFetcher._computeOrder', () => {
111         it('should calculate the right order for a given valid revision', () => {
112             const fetcher = new OSBuildFetcher({});
113             assert.equal(fetcher._computeOrder('Sierra16D32'), 1603003200);
114             assert.equal(fetcher._computeOrder('16D321'), 1603032100);
115             assert.equal(fetcher._computeOrder('16d321'), 1603032100);
116             assert.equal(fetcher._computeOrder('16D321z'), 1603032126);
117             assert.equal(fetcher._computeOrder('16d321Z'), 1603032126);
118             assert.equal(fetcher._computeOrder('10.12.3 16D32'), 1603003200);
119             assert.equal(fetcher._computeOrder('10.12.3 Sierra16D32'), 1603003200);
120         });
121
122         it('should throw assertion error when given a invalid revision', () => {
123             const fetcher = new OSBuildFetcher({});
124             assert.throws(() => fetcher._computeOrder('invalid'), 'AssertionError [ERR_ASSERTION]');
125             assert.throws(() => fetcher._computeOrder(''), 'AssertionError [ERR_ASSERTION]');
126             assert.throws(() => fetcher._computeOrder('16'), 'AssertionError [ERR_ASSERTION]');
127             assert.throws(() => fetcher._computeOrder('16D'), 'AssertionError [ERR_ASSERTION]');
128             assert.throws(() => fetcher._computeOrder('123'), 'AssertionError [ERR_ASSERTION]');
129             assert.throws(() => fetcher._computeOrder('D123'), 'AssertionError [ERR_ASSERTION]');
130             assert.throws(() => fetcher._computeOrder('123z'), 'AssertionError [ERR_ASSERTION]');
131             assert.throws(() => fetcher._computeOrder('16[163'), 'AssertionError [ERR_ASSERTION]');
132             assert.throws(() => fetcher._computeOrder('16D163['), 'AssertionError [ERR_ASSERTION]');
133         })
134     });
135
136     describe('OSBuilderFetcher._commitsForAvailableBuilds', () => {
137         it('should compatible with command output only contains lines of revision', async () => {
138             const logger = new MockLogger;
139             const fetcher = new OSBuildFetcher({}, null, null, MockSubprocess, logger);
140             const waitForInvocationPromise = MockSubprocess.waitForInvocation();
141             const fetchCommitsPromise = fetcher._commitsForAvailableBuilds(['list', 'build1'], '^\\.*$');
142
143             await waitForInvocationPromise;
144             assert.equal(MockSubprocess.invocations.length, 1);
145             assert.deepEqual(MockSubprocess.invocations[0].command, ['list', 'build1']);
146
147             const expectedResults = {allRevisions: ["16D321", "16E321z", "16F321"], commitsWithTestability: {}};
148             await MockSubprocess.invocations[0].resolve('16D321\n16E321z\n\n16F321');
149             const buildInfo = await fetchCommitsPromise;
150             assert.deepEqual(expectedResults, buildInfo);
151         });
152
153         it('should parse the command output as JSON format', async () => {
154             const logger = new MockLogger;
155             const fetcher = new OSBuildFetcher({}, null, null, MockSubprocess, logger);
156             const waitForInvocationPromise = MockSubprocess.waitForInvocation();
157             const fetchCommitsPromise = fetcher._commitsForAvailableBuilds(['list', 'build1']);
158
159             await waitForInvocationPromise;
160             assert.equal(MockSubprocess.invocations.length, 1);
161             assert.deepEqual(MockSubprocess.invocations[0].command, ['list', 'build1']);
162
163             const outputObject = {allRevisions: ["16D321", "16E321z", "16F321"], commitsWithTestability: {"16D321": "Panic"}};
164             await MockSubprocess.invocations[0].resolve(JSON.stringify(outputObject));
165             const buildInfo = await fetchCommitsPromise;
166             assert.deepEqual(outputObject, buildInfo);
167         });
168     });
169
170
171     describe('OSBuilderFetcher._commitsWithinRange', () => {
172         it('should only return commits whose orders are higher than specified order', async () => {
173             const logger = new MockLogger;
174             const fetcher = new OSBuildFetcher({}, null, null, MockSubprocess, logger);
175             const results = fetcher._commitsWithinRange(["16D321", "16E321z", "16F321"], "OSX", 1604000000, 1606000000);
176             assert.equal(results.length, 2);
177             assert.deepEqual(results[0], {repository: 'OSX', order: 1604032126, revision: '16E321z'});
178             assert.deepEqual(results[1], {repository: 'OSX', order: 1605032100, revision: '16F321'});
179
180         });
181
182         it('should only return commits whose orders are higher than minOrder and lower than the maxOrder', async () => {
183             const logger = new MockLogger;
184             const fetcher = new OSBuildFetcher({}, null, null, MockSubprocess, logger);
185             const results = fetcher._commitsWithinRange(["16D321", "16E321z", "16F321"], "OSX", 1604000000, 1605000000);
186             assert.equal(results.length, 1);
187             assert.deepEqual(results[0], {repository: 'OSX', order: 1604032126, revision: '16E321z'});
188         });
189     });
190
191     describe('OSBuildFetcher._addOwnedCommitsForBuild', () => {
192         it('should add owned-commit info for commits', () => {
193             const logger = new MockLogger;
194             const fetcher = new OSBuildFetcher({}, null, null, MockSubprocess, logger);
195             const waitForInvocationPromise = MockSubprocess.waitForInvocation();
196             const addownedCommitPromise = fetcher._addOwnedCommitsForBuild([osxCommit, anotherOSXCommit], ['ownedCommit', 'for', 'revision']);
197
198             return waitForInvocationPromise.then(() => {
199                 assert.equal(MockSubprocess.invocations.length, 1);
200                 assert.deepEqual(MockSubprocess.invocations[0].command, ['ownedCommit', 'for', 'revision', 'Sierra16D32']);
201                 MockSubprocess.invocations[0].resolve(JSON.stringify(ownedCommitWithWebKit));
202                 MockSubprocess.reset();
203                 return MockSubprocess.waitForInvocation();
204             }).then(() => {
205                 assert.equal(MockSubprocess.invocations.length, 1);
206                 assert.deepEqual(MockSubprocess.invocations[0].command, ['ownedCommit', 'for', 'revision', 'Sierra16E32']);
207                 MockSubprocess.invocations[0].resolve(JSON.stringify(anotherownedCommitWithWebKit));
208                 return addownedCommitPromise;
209             }).then((results) => {
210                 assert.equal(results.length, 2);
211                 assert.equal(results[0]['repository'], osxCommit['repository']);
212                 assert.equal(results[0]['revision'], osxCommit['revision']);
213                 assert.deepEqual(results[0]['ownedCommits'], ownedCommitWithWebKit);
214                 assert.equal(results[1]['repository'], anotherOSXCommit['repository']);
215                 assert.equal(results[1]['revision'], anotherOSXCommit['revision']);
216                 assert.deepEqual(results[1]['ownedCommits'], anotherownedCommitWithWebKit);
217             });
218         });
219
220         it('should fail if the command to get owned-commit info fails', () => {
221             const logger = new MockLogger;
222             const fetcher = new OSBuildFetcher({}, null, null, MockSubprocess, logger);
223             const waitForInvocationPromise = MockSubprocess.waitForInvocation();
224             const addownedCommitPromise = fetcher._addOwnedCommitsForBuild([osxCommit], ['ownedCommit', 'for', 'revision'])
225
226             return waitForInvocationPromise.then(() => {
227                 assert.equal(MockSubprocess.invocations.length, 1);
228                 assert.deepEqual(MockSubprocess.invocations[0].command, ['ownedCommit', 'for', 'revision', 'Sierra16D32']);
229                 MockSubprocess.invocations[0].reject('Failed getting owned-commit');
230
231                 return addownedCommitPromise.then(() => {
232                     assert(false, 'should never be reached');
233                 }, (error_output) => {
234                     assert(error_output);
235                     assert.equal(error_output, 'Failed getting owned-commit');
236                 });
237             });
238         });
239
240
241         it('should fail if entries in owned-commits does not contain revision', () => {
242             const logger = new MockLogger;
243             const fetcher = new OSBuildFetcher({}, null, null, MockSubprocess, logger);
244             const waitForInvocationPromise = MockSubprocess.waitForInvocation();
245             const addownedCommitPromise = fetcher._addOwnedCommitsForBuild([osxCommit], ['ownedCommit', 'for', 'revision'])
246
247             return waitForInvocationPromise.then(() => {
248                 assert.equal(MockSubprocess.invocations.length, 1);
249                 assert.deepEqual(MockSubprocess.invocations[0].command, ['ownedCommit', 'for', 'revision', 'Sierra16D32']);
250                 MockSubprocess.invocations[0].resolve('{"WebKit":{"RandomKey": "RandomValue"}}');
251
252                 return addownedCommitPromise.then(() => {
253                     assert(false, 'should never be reached');
254                 }, (error_output) => {
255                     assert(error_output);
256                     assert.equal(error_output.name, 'AssertionError [ERR_ASSERTION]');
257                 });
258             });
259         })
260     });
261
262     describe('OSBuildFetcher.fetchReportAndUpdateBuilds', () => {
263         const invocations = MockSubprocess.invocations;
264
265         beforeEach(function () {
266             TestServer.database().connect({keepAlive: true});
267         });
268
269         afterEach(function () {
270             TestServer.database().disconnect();
271         });
272
273         it('should be backward compatible and report all build commits with owned-commits', () => {
274             const logger = new MockLogger;
275             const fetcher = new OSBuildFetcher(config, TestServer.remoteAPI(), slaveAuth, MockSubprocess, logger);
276             const db = TestServer.database();
277
278             let fetchReportAndUpdateBuildsPromise = null;
279             let fetchAvailableBuildsPromise = null;
280
281             return addSlaveForReport(emptyReport).then(() => {
282                 return Promise.all([
283                     db.insert('repositories', {'id': 10, 'name': 'OSX'}),
284                     db.insert('commits', {'repository': 10, 'revision': 'Sierra16D67', 'order': 1603006700, 'reported': true}),
285                     db.insert('commits', {'repository': 10, 'revision': 'Sierra16D68', 'order': 1603006800, 'reported': true}),
286                     db.insert('commits', {'repository': 10, 'revision': 'Sierra16D69', 'order': 1603006900, 'reported': false}),
287                     db.insert('commits', {'repository': 10, 'revision': 'Sierra16E32', 'order': 1604003200, 'reported': true}),
288                     db.insert('commits', {'repository': 10, 'revision': 'Sierra16E33', 'order': 1604003300, 'reported': true}),
289                     db.insert('commits', {'repository': 10, 'revision': 'Sierra16E33g', 'order': 1604003307, 'reported': true})]);
290             }).then(() => {
291                 return TestServer.remoteAPI().getJSON('/api/commits/OSX/last-reported?from=1603000000&to=1603099900');
292             }).then((result) => {
293                 assert.equal(result['commits'].length, 1);
294                 assert.equal(result['commits'][0]['revision'], 'Sierra16D68');
295                 assert.equal(result['commits'][0]['order'], 1603006800);
296                 return TestServer.remoteAPI().getJSON('/api/commits/OSX/last-reported?from=1604000000&to=1604099900');
297             }).then((result) => {
298                 assert.equal(result['commits'].length, 1);
299                 assert.equal(result['commits'][0]['revision'], 'Sierra16E33g');
300                 assert.equal(result['commits'][0]['order'], 1604003307);
301                 const waitForInvocationPromise = MockSubprocess.waitForInvocation();
302                 fetchAvailableBuildsPromise = fetcher._fetchAvailableBuilds();
303                 return waitForInvocationPromise;
304             }).then(() => {
305                 assert.equal(invocations.length, 1);
306                 assert.deepEqual(invocations[0].command, ['list', 'all osx 16Dxx builds']);
307                 invocations[0].resolve('\n\nSierra16D68\nSierra16D69\n');
308                 return MockSubprocess.resetAndWaitForInvocation();
309             }).then(() => {
310                 assert.equal(invocations.length, 1);
311                 assert.deepEqual(invocations[0].command, ['list', 'ownedCommit', 'for', 'revision', 'Sierra16D69']);
312                 invocations[0].resolve(JSON.stringify(ownedCommitWithWebKit));
313                 return MockSubprocess.resetAndWaitForInvocation();
314             }).then(() => {
315                 assert.equal(invocations.length, 1);
316                 assert.deepEqual(invocations[0].command, ['list', 'all osx 16Exx builds']);
317                 invocations[0].resolve('\n\nSierra16E32\nSierra16E33\nSierra16E33h\nSierra16E34');
318                 return MockSubprocess.resetAndWaitForInvocation();
319             }).then(() => {
320                 assert.equal(invocations.length, 1);
321                 assert.deepEqual(invocations[0].command, ['list', 'ownedCommit', 'for', 'revision', 'Sierra16E33h']);
322                 invocations[0].resolve(JSON.stringify(anotherownedCommitWithWebKit));
323                 return MockSubprocess.resetAndWaitForInvocation();
324             }).then(() => {
325                 assert.equal(invocations.length, 1);
326                 assert.deepEqual(invocations[0].command, ['list', 'ownedCommit', 'for', 'revision', 'Sierra16E34']);
327                 invocations[0].resolve(JSON.stringify(anotherownedCommitWithWebKitAndJavaScriptCore));
328                 return fetchAvailableBuildsPromise;
329             }).then(() => {
330                 MockSubprocess.reset();
331                 fetchReportAndUpdateBuildsPromise = fetcher.fetchReportAndUpdateBuilds();
332                 return MockSubprocess.waitForInvocation();
333             }).then(() => {
334                 assert.equal(invocations.length, 1);
335                 assert.deepEqual(invocations[0].command, ['list', 'all osx 16Dxx builds']);
336                 invocations[0].resolve('\n\nSierra16D68\nSierra16D69\n');
337                 return MockSubprocess.resetAndWaitForInvocation();
338             }).then(() => {
339                 assert.equal(invocations.length, 1);
340                 assert.deepEqual(invocations[0].command, ['list', 'ownedCommit', 'for', 'revision', 'Sierra16D69']);
341                 invocations[0].resolve(JSON.stringify(ownedCommitWithWebKit));
342                 return MockSubprocess.resetAndWaitForInvocation();
343             }).then(() => {
344                 assert.equal(invocations.length, 1);
345                 assert.deepEqual(invocations[0].command, ['list', 'all osx 16Exx builds']);
346                 invocations[0].resolve('\n\nSierra16E32\nSierra16E33\nSierra16E33h\nSierra16E34');
347                 return MockSubprocess.resetAndWaitForInvocation();
348             }).then(() => {
349                 assert.equal(invocations.length, 1);
350                 assert.deepEqual(invocations[0].command, ['list', 'ownedCommit', 'for', 'revision', 'Sierra16E33h']);
351                 invocations[0].resolve(JSON.stringify(anotherownedCommitWithWebKit));
352                 return MockSubprocess.resetAndWaitForInvocation();
353             }).then(() => {
354                 assert.equal(invocations.length, 1);
355                 invocations[0].resolve(JSON.stringify(anotherownedCommitWithWebKitAndJavaScriptCore));
356                 assert.deepEqual(invocations[0].command, ['list', 'ownedCommit', 'for', 'revision', 'Sierra16E34']);
357
358                 return fetchReportAndUpdateBuildsPromise;
359             }).then(() => {
360                 return Promise.all([
361                     db.selectRows('repositories', {'name': 'WebKit'}),
362                     db.selectRows('repositories', {'name': 'JavaScriptCore'}),
363                     db.selectRows('commits', {'revision': 'Sierra16D69'}),
364                     db.selectRows('commits', {'revision': 'Sierra16E33h'}),
365                     db.selectRows('commits', {'revision': 'Sierra16E34'})]);
366             }).then((results) => {
367                 const webkitRepository = results[0];
368                 const jscRepository = results[1];
369                 const osxCommit16D69 = results[2];
370                 const osxCommit16E33h = results[3];
371                 const osxCommit16E34 = results[4];
372
373                 assert.equal(webkitRepository.length, 1);
374                 assert.equal(webkitRepository[0]['owner'], 10);
375                 assert.equal(jscRepository.length, 1);
376                 assert.equal(jscRepository[0]['owner'], 10);
377
378                 assert.equal(osxCommit16D69.length, 1);
379                 assert.equal(osxCommit16D69[0]['repository'], 10);
380                 assert.equal(osxCommit16D69[0]['order'], 1603006900);
381
382                 assert.equal(osxCommit16E33h.length, 1);
383                 assert.equal(osxCommit16E33h[0]['repository'], 10);
384                 assert.equal(osxCommit16E33h[0]['order'], 1604003308);
385
386                 assert.equal(osxCommit16E34.length, 1);
387                 assert.equal(osxCommit16E34[0]['repository'], 10);
388                 assert.equal(osxCommit16E34[0]['order'], 1604003400);
389
390                 return TestServer.remoteAPI().getJSON('/api/commits/OSX/last-reported?from=1603000000&to=1603099900');
391             }).then((result) => {
392                 assert.equal(result['commits'].length, 1);
393                 assert.equal(result['commits'][0]['revision'], 'Sierra16D69');
394                 assert.equal(result['commits'][0]['order'], 1603006900);
395
396                 return TestServer.remoteAPI().getJSON('/api/commits/OSX/last-reported?from=1604000000&to=1604099900');
397             }).then((result) => {
398                 assert.equal(result['commits'].length, 1);
399                 assert.equal(result['commits'][0]['revision'], 'Sierra16E34');
400                 assert.equal(result['commits'][0]['order'], 1604003400);
401             });
402         });
403
404         it('should report all build commits with owned-commits', () => {
405             const logger = new MockLogger;
406             const fetcher = new OSBuildFetcher(config, TestServer.remoteAPI(), slaveAuth, MockSubprocess, logger);
407             const db = TestServer.database();
408             const resultsForSierraD = {allRevisions: ["Sierra16D68", "Sierra16D69"], commitsWithTestability: {}};
409             const resultsForSierraE = {allRevisions: ["Sierra16E32", "Sierra16E33", "Sierra16E33h", "Sierra16E34"], commitsWithTestability: {}};
410
411             let fetchReportAndUpdateBuildsPromise = null;
412             let fetchAvailableBuildsPromise = null;
413
414             return addSlaveForReport(emptyReport).then(() => {
415                 return Promise.all([
416                     db.insert('repositories', {'id': 10, 'name': 'OSX'}),
417                     db.insert('commits', {'repository': 10, 'revision': 'Sierra16D67', 'order': 1603006700, 'reported': true}),
418                     db.insert('commits', {'repository': 10, 'revision': 'Sierra16D68', 'order': 1603006800, 'reported': true}),
419                     db.insert('commits', {'repository': 10, 'revision': 'Sierra16D69', 'order': 1603006900, 'reported': false}),
420                     db.insert('commits', {'repository': 10, 'revision': 'Sierra16E32', 'order': 1604003200, 'reported': true}),
421                     db.insert('commits', {'repository': 10, 'revision': 'Sierra16E33', 'order': 1604003300, 'reported': true}),
422                     db.insert('commits', {'repository': 10, 'revision': 'Sierra16E33g', 'order': 1604003307, 'reported': true})]);
423             }).then(() => {
424                 return TestServer.remoteAPI().getJSON('/api/commits/OSX/last-reported?from=1603000000&to=1603099900');
425             }).then((result) => {
426                 assert.equal(result['commits'].length, 1);
427                 assert.equal(result['commits'][0]['revision'], 'Sierra16D68');
428                 assert.equal(result['commits'][0]['order'], 1603006800);
429                 return TestServer.remoteAPI().getJSON('/api/commits/OSX/last-reported?from=1604000000&to=1604099900');
430             }).then((result) => {
431                 assert.equal(result['commits'].length, 1);
432                 assert.equal(result['commits'][0]['revision'], 'Sierra16E33g');
433                 assert.equal(result['commits'][0]['order'], 1604003307);
434                 const waitForInvocationPromise = MockSubprocess.waitForInvocation();
435                 fetchAvailableBuildsPromise = fetcher._fetchAvailableBuilds();
436                 return waitForInvocationPromise;
437             }).then(() => {
438                 assert.equal(invocations.length, 1);
439                 assert.deepEqual(invocations[0].command, ['list', 'all osx 16Dxx builds']);
440                 invocations[0].resolve(JSON.stringify(resultsForSierraD));
441                 return MockSubprocess.resetAndWaitForInvocation();
442             }).then(() => {
443                 assert.equal(invocations.length, 1);
444                 assert.deepEqual(invocations[0].command, ['list', 'ownedCommit', 'for', 'revision', 'Sierra16D69']);
445                 invocations[0].resolve(JSON.stringify(ownedCommitWithWebKit));
446                 return MockSubprocess.resetAndWaitForInvocation();
447             }).then(() => {
448                 assert.equal(invocations.length, 1);
449                 assert.deepEqual(invocations[0].command, ['list', 'all osx 16Exx builds']);
450                 invocations[0].resolve(JSON.stringify(resultsForSierraE));
451                 return MockSubprocess.resetAndWaitForInvocation();
452             }).then(() => {
453                 assert.equal(invocations.length, 1);
454                 assert.deepEqual(invocations[0].command, ['list', 'ownedCommit', 'for', 'revision', 'Sierra16E33h']);
455                 invocations[0].resolve(JSON.stringify(anotherownedCommitWithWebKit));
456                 return MockSubprocess.resetAndWaitForInvocation();
457             }).then(() => {
458                 assert.equal(invocations.length, 1);
459                 assert.deepEqual(invocations[0].command, ['list', 'ownedCommit', 'for', 'revision', 'Sierra16E34']);
460                 invocations[0].resolve(JSON.stringify(anotherownedCommitWithWebKitAndJavaScriptCore));
461                 return fetchAvailableBuildsPromise;
462             }).then(() => {
463                 MockSubprocess.reset();
464                 fetchReportAndUpdateBuildsPromise = fetcher.fetchReportAndUpdateBuilds();
465                 return MockSubprocess.waitForInvocation();
466             }).then(() => {
467                 assert.equal(invocations.length, 1);
468                 assert.deepEqual(invocations[0].command, ['list', 'all osx 16Dxx builds']);
469                 invocations[0].resolve(JSON.stringify(resultsForSierraD));
470                 return MockSubprocess.resetAndWaitForInvocation();
471             }).then(() => {
472                 assert.equal(invocations.length, 1);
473                 assert.deepEqual(invocations[0].command, ['list', 'ownedCommit', 'for', 'revision', 'Sierra16D69']);
474                 invocations[0].resolve(JSON.stringify(ownedCommitWithWebKit));
475                 return MockSubprocess.resetAndWaitForInvocation();
476             }).then(() => {
477                 assert.equal(invocations.length, 1);
478                 assert.deepEqual(invocations[0].command, ['list', 'all osx 16Exx builds']);
479                 invocations[0].resolve(JSON.stringify(resultsForSierraE));
480                 return MockSubprocess.resetAndWaitForInvocation();
481             }).then(() => {
482                 assert.equal(invocations.length, 1);
483                 assert.deepEqual(invocations[0].command, ['list', 'ownedCommit', 'for', 'revision', 'Sierra16E33h']);
484                 invocations[0].resolve(JSON.stringify(anotherownedCommitWithWebKit));
485                 return MockSubprocess.resetAndWaitForInvocation();
486             }).then(() => {
487                 assert.equal(invocations.length, 1);
488                 invocations[0].resolve(JSON.stringify(anotherownedCommitWithWebKitAndJavaScriptCore));
489                 assert.deepEqual(invocations[0].command, ['list', 'ownedCommit', 'for', 'revision', 'Sierra16E34']);
490
491                 return fetchReportAndUpdateBuildsPromise;
492             }).then(() => {
493                 return Promise.all([
494                     db.selectRows('repositories', {'name': 'WebKit'}),
495                     db.selectRows('repositories', {'name': 'JavaScriptCore'}),
496                     db.selectRows('commits', {'revision': 'Sierra16D69'}),
497                     db.selectRows('commits', {'revision': 'Sierra16E33h'}),
498                     db.selectRows('commits', {'revision': 'Sierra16E34'})]);
499             }).then((results) => {
500                 const webkitRepository = results[0];
501                 const jscRepository = results[1];
502                 const osxCommit16D69 = results[2];
503                 const osxCommit16E33h = results[3];
504                 const osxCommit16E34 = results[4];
505
506                 assert.equal(webkitRepository.length, 1);
507                 assert.equal(webkitRepository[0]['owner'], 10);
508                 assert.equal(jscRepository.length, 1);
509                 assert.equal(jscRepository[0]['owner'], 10);
510
511                 assert.equal(osxCommit16D69.length, 1);
512                 assert.equal(osxCommit16D69[0]['repository'], 10);
513                 assert.equal(osxCommit16D69[0]['order'], 1603006900);
514
515                 assert.equal(osxCommit16E33h.length, 1);
516                 assert.equal(osxCommit16E33h[0]['repository'], 10);
517                 assert.equal(osxCommit16E33h[0]['order'], 1604003308);
518
519                 assert.equal(osxCommit16E34.length, 1);
520                 assert.equal(osxCommit16E34[0]['repository'], 10);
521                 assert.equal(osxCommit16E34[0]['order'], 1604003400);
522
523                 return TestServer.remoteAPI().getJSON('/api/commits/OSX/last-reported?from=1603000000&to=1603099900');
524             }).then((result) => {
525                 assert.equal(result['commits'].length, 1);
526                 assert.equal(result['commits'][0]['revision'], 'Sierra16D69');
527                 assert.equal(result['commits'][0]['order'], 1603006900);
528
529                 return TestServer.remoteAPI().getJSON('/api/commits/OSX/last-reported?from=1604000000&to=1604099900');
530             }).then((result) => {
531                 assert.equal(result['commits'].length, 1);
532                 assert.equal(result['commits'][0]['revision'], 'Sierra16E34');
533                 assert.equal(result['commits'][0]['order'], 1604003400);
534             });
535         });
536
537         it('should update testability message for commits', async () => {
538             const logger = new MockLogger;
539             const fetcher = new OSBuildFetcher(config, TestServer.remoteAPI(), slaveAuth, MockSubprocess, logger);
540             const db = TestServer.database();
541             const resultsForSierraD = {allRevisions: ["Sierra16D68", "Sierra16D69"], commitsWithTestability: {"Sierra16D68": "Panic", "Sierra16D69": "Spin CPU"}};
542             const resultsForSierraE = {allRevisions: ["Sierra16E32", "Sierra16E33", "Sierra16E33h", "Sierra16E34"], commitsWithTestability: {"Sierra16E31": "WebKit crashes"}};
543
544             await addSlaveForReport(emptyReport);
545
546             await Promise.all([
547                 db.insert('repositories', {'id': 10, 'name': 'OSX'}),
548                 db.insert('commits', {'repository': 10, 'revision': 'Sierra16D67', 'order': 1603006700, 'reported': true}),
549                 db.insert('commits', {'repository': 10, 'revision': 'Sierra16D68', 'order': 1603006800, 'reported': true}),
550                 db.insert('commits', {'repository': 10, 'revision': 'Sierra16D69', 'order': 1603006900, 'reported': false}),
551                 db.insert('commits', {'repository': 10, 'revision': 'Sierra16E31', 'order': 1604003100, 'reported': true}),
552                 db.insert('commits', {'repository': 10, 'revision': 'Sierra16E32', 'order': 1604003200, 'reported': true}),
553                 db.insert('commits', {'repository': 10, 'revision': 'Sierra16E33', 'order': 1604003300, 'reported': true}),
554                 db.insert('commits', {'repository': 10, 'revision': 'Sierra16E33g', 'order': 1604003307, 'reported': true})]);
555
556             let result = await TestServer.remoteAPI().getJSON('/api/commits/OSX/last-reported?from=1603000000&to=1603099900');
557
558             assert.equal(result['commits'].length, 1);
559             assert.equal(result['commits'][0]['revision'], 'Sierra16D68');
560             assert.equal(result['commits'][0]['order'], 1603006800);
561             result = await TestServer.remoteAPI().getJSON('/api/commits/OSX/last-reported?from=1604000000&to=1604099900');
562
563             assert.equal(result['commits'].length, 1);
564             assert.equal(result['commits'][0]['revision'], 'Sierra16E33g');
565             assert.equal(result['commits'][0]['order'], 1604003307);
566
567             const fetchReportAndUpdatePromise = fetcher.fetchReportAndUpdateBuilds();
568             await MockSubprocess.waitForInvocation();
569
570             assert.equal(invocations.length, 1);
571             assert.deepEqual(invocations[0].command, ['list', 'all osx 16Dxx builds']);
572             invocations[0].resolve(JSON.stringify(resultsForSierraD));
573             await MockSubprocess.resetAndWaitForInvocation();
574
575             assert.equal(invocations.length, 1);
576             assert.deepEqual(invocations[0].command, ['list', 'ownedCommit', 'for', 'revision', 'Sierra16D69']);
577             invocations[0].resolve(JSON.stringify(ownedCommitWithWebKit));
578             await MockSubprocess.resetAndWaitForInvocation();
579
580             assert.equal(invocations.length, 1);
581             assert.deepEqual(invocations[0].command, ['list', 'all osx 16Exx builds']);
582             invocations[0].resolve(JSON.stringify(resultsForSierraE));
583
584             await MockSubprocess.resetAndWaitForInvocation();
585
586             assert.equal(invocations.length, 1);
587             assert.deepEqual(invocations[0].command, ['list', 'ownedCommit', 'for', 'revision', 'Sierra16E33h']);
588             invocations[0].resolve(JSON.stringify(anotherownedCommitWithWebKit));
589             await  MockSubprocess.resetAndWaitForInvocation();
590             assert.equal(invocations.length, 1);
591             invocations[0].resolve(JSON.stringify(anotherownedCommitWithWebKitAndJavaScriptCore));
592             assert.deepEqual(invocations[0].command, ['list', 'ownedCommit', 'for', 'revision', 'Sierra16E34']);
593
594             await fetchReportAndUpdatePromise;
595
596             const webkitRepository = await db.selectRows('repositories', {'name': 'WebKit'});
597             const jscRepository = await db.selectRows('repositories', {'name': 'JavaScriptCore'});
598             const osxCommit16D68 = await db.selectRows('commits', {'revision': 'Sierra16D68'});
599             const osxCommit16D69 = await db.selectRows('commits', {'revision': 'Sierra16D69'});
600             const osxCommit16E31 = await db.selectRows('commits', {'revision': 'Sierra16E31'});
601             const osxCommit16E33h = await db.selectRows('commits', {'revision': 'Sierra16E33h'});
602             const osxCommit16E34 = await db.selectRows('commits', {'revision': 'Sierra16E34'});
603
604             assert.equal(webkitRepository.length, 1);
605             assert.equal(webkitRepository[0]['owner'], 10);
606             assert.equal(jscRepository.length, 1);
607             assert.equal(jscRepository[0]['owner'], 10);
608
609             assert.equal(osxCommit16D68.length, 1);
610             assert.equal(osxCommit16D68[0]['repository'], 10);
611             assert.equal(osxCommit16D68[0]['order'], 1603006800);
612             assert.equal(osxCommit16D68[0]['testability'], "Panic");
613
614             assert.equal(osxCommit16D69.length, 1);
615             assert.equal(osxCommit16D69[0]['repository'], 10);
616             assert.equal(osxCommit16D69[0]['order'], 1603006900);
617             assert.equal(osxCommit16D69[0]['testability'], "Spin CPU");
618
619             assert.equal(osxCommit16E31.length, 1);
620             assert.equal(osxCommit16E31[0]['repository'], 10);
621             assert.equal(osxCommit16E31[0]['order'], 1604003100);
622             assert.equal(osxCommit16E31[0]['testability'], "WebKit crashes");
623
624             assert.equal(osxCommit16E33h.length, 1);
625             assert.equal(osxCommit16E33h[0]['repository'], 10);
626             assert.equal(osxCommit16E33h[0]['order'], 1604003308);
627
628             assert.equal(osxCommit16E34.length, 1);
629             assert.equal(osxCommit16E34[0]['repository'], 10);
630             assert.equal(osxCommit16E34[0]['order'], 1604003400);
631
632             result = await TestServer.remoteAPI().getJSON('/api/commits/OSX/last-reported?from=1603000000&to=1603099900');
633             assert.equal(result['commits'].length, 1);
634             assert.equal(result['commits'][0]['revision'], 'Sierra16D69');
635             assert.equal(result['commits'][0]['order'], 1603006900);
636
637             result = await TestServer.remoteAPI().getJSON('/api/commits/OSX/last-reported?from=1604000000&to=1604099900');
638             assert.equal(result['commits'].length, 1);
639             assert.equal(result['commits'][0]['revision'], 'Sierra16E34');
640             assert.equal(result['commits'][0]['order'], 1604003400);
641         });
642
643         it('should report commits without owned-commits if "ownedCommitCommand" is not specified in config', async () => {
644
645             const logger = new MockLogger;
646             const fetcher = new OSBuildFetcher(configWithoutOwnedCommitCommand, TestServer.remoteAPI(), slaveAuth, MockSubprocess, logger);
647             const db = TestServer.database();
648             const resultsForSierraD = {allRevisions: ["Sierra16D68", "Sierra16D69"], commitsWithTestability: {}};
649             const resultsForSierraE = {allRevisions: ["Sierra16E32", "Sierra16E33", "Sierra16E33h", "Sierra16E34"], commitsWithTestability: {}};
650
651             await addSlaveForReport(emptyReport);
652             await Promise.all([
653                 db.insert('repositories', {'id': 10, 'name': 'OSX'}),
654                 db.insert('commits', {'repository': 10, 'revision': 'Sierra16D67', 'order': 1603006700, 'reported': true}),
655                 db.insert('commits', {'repository': 10, 'revision': 'Sierra16D68', 'order': 1603006800, 'reported': true}),
656                 db.insert('commits', {'repository': 10, 'revision': 'Sierra16D69', 'order': 1603006900, 'reported': false}),
657                 db.insert('commits', {'repository': 10, 'revision': 'Sierra16E32', 'order': 1604003200, 'reported': true}),
658                 db.insert('commits', {'repository': 10, 'revision': 'Sierra16E33', 'order': 1604003300, 'reported': true}),
659                 db.insert('commits', {'repository': 10, 'revision': 'Sierra16E33g', 'order': 1604003307, 'reported': true})]);
660
661             let result = await TestServer.remoteAPI().getJSON('/api/commits/OSX/last-reported?from=1603000000&to=1603099900');
662             assert.equal(result['commits'].length, 1);
663             assert.equal(result['commits'][0]['revision'], 'Sierra16D68');
664             assert.equal(result['commits'][0]['order'], 1603006800);
665
666             result = await TestServer.remoteAPI().getJSON('/api/commits/OSX/last-reported?from=1604000000&to=1604099900');
667             assert.equal(result['commits'].length, 1);
668             assert.equal(result['commits'][0]['revision'], 'Sierra16E33g');
669             assert.equal(result['commits'][0]['order'], 1604003307);
670
671             const waitForInvocationPromise = MockSubprocess.waitForInvocation();
672             const fetchReportAndUpdatePromise = fetcher.fetchReportAndUpdateBuilds();
673             await waitForInvocationPromise;
674             assert.equal(invocations.length, 1);
675             assert.deepEqual(invocations[0].command, ['list', 'all osx 16Dxx builds']);
676             invocations[0].resolve(JSON.stringify(resultsForSierraD));
677
678             await MockSubprocess.resetAndWaitForInvocation();
679             assert.equal(invocations.length, 1);
680             assert.deepEqual(invocations[0].command, ['list', 'all osx 16Exx builds']);
681             invocations[0].resolve(JSON.stringify(resultsForSierraE));
682
683             result = await fetchReportAndUpdatePromise;
684             const results = await Promise.all([
685                 db.selectRows('repositories', {'name': 'WebKit'}),
686                 db.selectRows('repositories', {'name': 'JavaScriptCore'}),
687                 db.selectRows('commits', {'revision': 'Sierra16D69'}),
688                 db.selectRows('commits', {'revision': 'Sierra16E33h'}),
689                 db.selectRows('commits', {'revision': 'Sierra16E34'})]);
690
691             const webkitRepository = results[0];
692             const jscRepository = results[1];
693             const osxCommit16D69 = results[2];
694             const osxCommit16E33h = results[3];
695             const osxCommit16E34 = results[4];
696
697             assert.equal(webkitRepository.length, 0);
698             assert.equal(jscRepository.length, 0);
699
700             assert.equal(osxCommit16D69.length, 1);
701             assert.equal(osxCommit16D69[0]['repository'], 10);
702             assert.equal(osxCommit16D69[0]['order'], 1603006900);
703
704             assert.equal(osxCommit16E33h.length, 1);
705             assert.equal(osxCommit16E33h[0]['repository'], 10);
706             assert.equal(osxCommit16E33h[0]['order'], 1604003308);
707
708             assert.equal(osxCommit16E34.length, 1);
709             assert.equal(osxCommit16E34[0]['repository'], 10);
710             assert.equal(osxCommit16E34[0]['order'], 1604003400);
711
712             result = await TestServer.remoteAPI().getJSON('/api/commits/OSX/last-reported?from=1603000000&to=1603099900');
713             assert.equal(result['commits'].length, 1);
714             assert.equal(result['commits'][0]['revision'], 'Sierra16D69');
715             assert.equal(result['commits'][0]['order'], 1603006900);
716
717             result = await TestServer.remoteAPI().getJSON('/api/commits/OSX/last-reported?from=1604000000&to=1604099900');
718             assert.equal(result['commits'].length, 1);
719             assert.equal(result['commits'][0]['revision'], 'Sierra16E34');
720             assert.equal(result['commits'][0]['order'], 1604003400);
721         });
722
723         it('should report commits within specified revision range', async () => {
724             const logger = new MockLogger;
725             const fetcher = new OSBuildFetcher(configWithoutOwnedCommitCommand, TestServer.remoteAPI(), slaveAuth, MockSubprocess, logger);
726             const db = TestServer.database();
727             const resultsForSierraD = {allRevisions: ["Sierra16D68", "Sierra16D69", "Sierra16D10000"], commitsWithTestability: {}};
728             const resultsForSierraE = {allRevisions: ["Sierra16E32", "Sierra16E33", "Sierra16E33h", "Sierra16E34", "Sierra16E10000"], commitsWithTestability: {}};
729
730             await addSlaveForReport(emptyReport);
731             await Promise.all([
732                 db.insert('repositories', {'id': 10, 'name': 'OSX'}),
733                 db.insert('commits', {'repository': 10, 'revision': 'Sierra16D67', 'order': 1603006700, 'reported': true}),
734                 db.insert('commits', {'repository': 10, 'revision': 'Sierra16D68', 'order': 1603006800, 'reported': true}),
735                 db.insert('commits', {'repository': 10, 'revision': 'Sierra16D69', 'order': 1603006900, 'reported': false}),
736                 db.insert('commits', {'repository': 10, 'revision': 'Sierra16E32', 'order': 1604003200, 'reported': true}),
737                 db.insert('commits', {'repository': 10, 'revision': 'Sierra16E33', 'order': 1604003300, 'reported': true}),
738                 db.insert('commits', {'repository': 10, 'revision': 'Sierra16E33g', 'order': 1604003307, 'reported': true})]);
739
740             let result = await TestServer.remoteAPI().getJSON('/api/commits/OSX/last-reported?from=1603000000&to=1603099900');
741             assert.equal(result['commits'].length, 1);
742             assert.equal(result['commits'][0]['revision'], 'Sierra16D68');
743             assert.equal(result['commits'][0]['order'], 1603006800);
744
745             result = await TestServer.remoteAPI().getJSON('/api/commits/OSX/last-reported?from=1604000000&to=1604099900');
746             assert.equal(result['commits'].length, 1);
747             assert.equal(result['commits'][0]['revision'], 'Sierra16E33g');
748             assert.equal(result['commits'][0]['order'], 1604003307);
749             const waitForInvocationPromise = MockSubprocess.waitForInvocation();
750             const fetchReportAndUpdatePromise = fetcher.fetchReportAndUpdateBuilds();
751
752             await waitForInvocationPromise;
753             assert.equal(invocations.length, 1);
754             assert.deepEqual(invocations[0].command, ['list', 'all osx 16Dxx builds']);
755             invocations[0].resolve(JSON.stringify(resultsForSierraD));
756
757             await MockSubprocess.resetAndWaitForInvocation();
758             assert.equal(invocations.length, 1);
759             assert.deepEqual(invocations[0].command, ['list', 'all osx 16Exx builds']);
760             invocations[0].resolve(JSON.stringify(resultsForSierraE));
761
762             result = await fetchReportAndUpdatePromise;
763             const results = await Promise.all([
764                 db.selectRows('repositories', {'name': 'WebKit'}),
765                 db.selectRows('repositories', {'name': 'JavaScriptCore'}),
766                 db.selectRows('commits', {'revision': 'Sierra16D69'}),
767                 db.selectRows('commits', {'revision': 'Sierra16E33h'}),
768                 db.selectRows('commits', {'revision': 'Sierra16E34'})]);
769
770             const webkitRepository = results[0];
771             const jscRepository = results[1];
772             const osxCommit16D69 = results[2];
773             const osxCommit16E33h = results[3];
774             const osxCommit16E34 = results[4];
775
776             assert.equal(webkitRepository.length, 0);
777             assert.equal(jscRepository.length, 0);
778
779             assert.equal(osxCommit16D69.length, 1);
780             assert.equal(osxCommit16D69[0]['repository'], 10);
781             assert.equal(osxCommit16D69[0]['order'], 1603006900);
782
783             assert.equal(osxCommit16E33h.length, 1);
784             assert.equal(osxCommit16E33h[0]['repository'], 10);
785             assert.equal(osxCommit16E33h[0]['order'], 1604003308);
786
787             assert.equal(osxCommit16E34.length, 1);
788             assert.equal(osxCommit16E34[0]['repository'], 10);
789             assert.equal(osxCommit16E34[0]['order'], 1604003400);
790
791             result = await TestServer.remoteAPI().getJSON('/api/commits/OSX/last-reported?from=1603000000&to=1603099900');
792             assert.equal(result['commits'].length, 1);
793             assert.equal(result['commits'][0]['revision'], 'Sierra16D69');
794             assert.equal(result['commits'][0]['order'], 1603006900);
795
796             result = await TestServer.remoteAPI().getJSON('/api/commits/OSX/last-reported?from=1604000000&to=1604099900');
797             assert.equal(result['commits'].length, 1);
798             assert.equal(result['commits'][0]['revision'], 'Sierra16E34');
799             assert.equal(result['commits'][0]['order'], 1604003400);
800         });
801
802         it('should update commits within specified revision range', async () => {
803             const logger = new MockLogger;
804             const fetcher = new OSBuildFetcher(configWithoutOwnedCommitCommand, TestServer.remoteAPI(), slaveAuth, MockSubprocess, logger);
805             const db = TestServer.database();
806             const resultsForSierraD = {allRevisions: ["Sierra16D68", "Sierra16D69"], commitsWithTestability: {"Sierra16D10000": "Panic"}};
807             const resultsForSierraE = {allRevisions: ["Sierra16E32", "Sierra16E33", "Sierra16E33h", "Sierra16E34", "Sierra16E10000"], commitsWithTestability: {}};
808
809             await addSlaveForReport(emptyReport);
810             await Promise.all([
811                 db.insert('repositories', {'id': 10, 'name': 'OSX'}),
812                 db.insert('commits', {'repository': 10, 'revision': 'Sierra16D67', 'order': 1603006700, 'reported': true}),
813                 db.insert('commits', {'repository': 10, 'revision': 'Sierra16D68', 'order': 1603006800, 'reported': true}),
814                 db.insert('commits', {'repository': 10, 'revision': 'Sierra16D69', 'order': 1603006900, 'reported': false}),
815                 db.insert('commits', {'repository': 10, 'revision': 'Sierra16E32', 'order': 1604003200, 'reported': true}),
816                 db.insert('commits', {'repository': 10, 'revision': 'Sierra16E33', 'order': 1604003300, 'reported': true}),
817                 db.insert('commits', {'repository': 10, 'revision': 'Sierra16E33g', 'order': 1604003307, 'reported': true})]);
818
819             let result = await TestServer.remoteAPI().getJSON('/api/commits/OSX/last-reported?from=1603000000&to=1603099900');
820             assert.equal(result['commits'].length, 1);
821             assert.equal(result['commits'][0]['revision'], 'Sierra16D68');
822             assert.equal(result['commits'][0]['order'], 1603006800);
823
824             result = await TestServer.remoteAPI().getJSON('/api/commits/OSX/last-reported?from=1604000000&to=1604099900');
825             assert.equal(result['commits'].length, 1);
826             assert.equal(result['commits'][0]['revision'], 'Sierra16E33g');
827             assert.equal(result['commits'][0]['order'], 1604003307);
828             const waitForInvocationPromise = MockSubprocess.waitForInvocation();
829             const fetchAvailableBuildsPromise = fetcher._fetchAvailableBuilds();
830
831             await waitForInvocationPromise;
832             assert.equal(invocations.length, 1);
833             assert.deepEqual(invocations[0].command, ['list', 'all osx 16Dxx builds']);
834             invocations[0].resolve(JSON.stringify(resultsForSierraD));
835
836             await MockSubprocess.resetAndWaitForInvocation();
837             assert.equal(invocations.length, 1);
838             assert.deepEqual(invocations[0].command, ['list', 'all osx 16Exx builds']);
839             invocations[0].resolve(JSON.stringify(resultsForSierraE));
840
841             result = await fetchAvailableBuildsPromise;
842             assert.equal(result.commitsToUpdate.length, 0);
843         });
844
845         it('should use "last-reported" order + 1 as "minOrder"', async () => {
846             const logger = new MockLogger;
847             const fetcher = new OSBuildFetcher(configTrackingOneOS, TestServer.remoteAPI(), slaveAuth, MockSubprocess, logger);
848             const db = TestServer.database();
849             const resultsForSierraD = {allRevisions: ["Sierra16D68", "Sierra16D69", "Sierra16D100", "Sierra16D100a"], commitsWithTestability: {}};
850
851             await addSlaveForReport(emptyReport);
852             await db.insert('repositories', {'id': 10, 'name': 'OSX'});
853             await db.insert('commits', {'repository': 10, 'revision': 'Sierra16D100', 'order': 1603010000, 'reported': true});
854
855             let result = await TestServer.remoteAPI().getJSON('/api/commits/OSX/last-reported?from=1603010000&to=1603099900');
856             assert.equal(result['commits'].length, 1);
857             assert.equal(result['commits'][0]['revision'], 'Sierra16D100');
858             assert.equal(result['commits'][0]['order'], 1603010000);
859
860             const waitForInvocationPromise = MockSubprocess.waitForInvocation();
861             const fetchAndReportPromise = fetcher.fetchReportAndUpdateBuilds();
862             await waitForInvocationPromise;
863             assert.equal(invocations.length, 1);
864             assert.deepEqual(invocations[0].command, ['list', 'all osx 16Dxx builds']);
865             invocations[0].resolve(JSON.stringify(resultsForSierraD));
866
867             result = await fetchAndReportPromise;
868             const results = await Promise.all([
869                 db.selectRows('repositories', {'name': 'WebKit'}),
870                 db.selectRows('repositories', {'name': 'JavaScriptCore'}),
871                 db.selectRows('commits', {'revision': 'Sierra16D69'}),
872                 db.selectRows('commits', {'revision': 'Sierra16D100a'})]);
873
874             const webkitRepository = results[0];
875             const jscRepository = results[1];
876             const osxCommit16D69 = results[2];
877             const osxCommit16D100a = results[3];
878
879             assert.equal(webkitRepository.length, 0);
880             assert.equal(jscRepository.length, 0);
881
882             assert.equal(osxCommit16D69.length, 0);
883
884             assert.equal(osxCommit16D100a.length, 1);
885             assert.equal(osxCommit16D100a[0]['repository'], 10);
886             assert.equal(osxCommit16D100a[0]['order'], 1603010001);
887
888             result = await TestServer.remoteAPI().getJSON('/api/commits/OSX/last-reported?from=1603010000&to=1603099900');
889             assert.equal(result['commits'].length, 1);
890             assert.equal(result['commits'][0]['revision'], 'Sierra16D100a');
891             assert.equal(result['commits'][0]['order'], 1603010001);
892         });
893
894         it('should use minRevision in the config if there is no commit', async () => {
895             const logger = new MockLogger;
896             const fetcher = new OSBuildFetcher(configTrackingOneOS, TestServer.remoteAPI(), slaveAuth, MockSubprocess, logger);
897             const db = TestServer.database();
898             const resultsForSierraD = {allRevisions: ["Sierra16D68", "Sierra16D69", "Sierra16D100", "Sierra16D101"], commitsWithTestability: {}};
899
900             await addSlaveForReport(emptyReport);
901             await db.insert('repositories', {'id': 10, 'name': 'OSX'});
902
903             let result = await TestServer.remoteAPI().getJSON('/api/commits/OSX/last-reported?from=1603010000&to=1603099900');
904             assert.equal(result['commits'].length, 0);
905
906             const waitForInvocationPromise = MockSubprocess.waitForInvocation();
907             const fetchReportAndUpdatePromise = fetcher.fetchReportAndUpdateBuilds();
908             await waitForInvocationPromise;
909             assert.equal(invocations.length, 1);
910             assert.deepEqual(invocations[0].command, ['list', 'all osx 16Dxx builds']);
911             invocations[0].resolve(JSON.stringify(resultsForSierraD));
912
913             result = await fetchReportAndUpdatePromise;
914             const results = await Promise.all([
915                 db.selectRows('repositories', {'name': 'WebKit'}),
916                 db.selectRows('repositories', {'name': 'JavaScriptCore'}),
917                 db.selectRows('commits', {'revision': 'Sierra16D69'}),
918                 db.selectRows('commits', {'revision': 'Sierra16D100'}),
919                 db.selectRows('commits', {'revision': 'Sierra16D101'})]);
920
921             const webkitRepository = results[0];
922             const jscRepository = results[1];
923             const osxCommit16D69 = results[2];
924             const osxCommit16D100 = results[3];
925             const osxCommit16D101 = results[4];
926
927             assert.equal(webkitRepository.length, 0);
928             assert.equal(jscRepository.length, 0);
929
930             assert.equal(osxCommit16D69.length, 0);
931
932             assert.equal(osxCommit16D100.length, 1);
933             assert.equal(osxCommit16D100[0]['repository'], 10);
934             assert.equal(osxCommit16D100[0]['order'], 1603010000);
935
936             assert.equal(osxCommit16D101.length, 1);
937             assert.equal(osxCommit16D101[0]['repository'], 10);
938             assert.equal(osxCommit16D101[0]['order'], 1603010100);
939
940             result = await TestServer.remoteAPI().getJSON('/api/commits/OSX/last-reported?from=1603010000&to=1603099900');
941             assert.equal(result['commits'].length, 1);
942             assert.equal(result['commits'][0]['revision'], 'Sierra16D101');
943             assert.equal(result['commits'][0]['order'], 1603010100);
944         });
945
946         it('should stop reporting if any custom command fails', () => {
947             const logger = new MockLogger;
948             const fetcher = new OSBuildFetcher(config, TestServer.remoteAPI(), slaveAuth, MockSubprocess, logger);
949             const db = TestServer.database();
950             const resultsForSierraD = {allRevisions: ["Sierra16D68", "Sierra16D69"], commitsWithTestability: {}};
951             const resultsForSierraE = {allRevisions: ["Sierra16E32", "Sierra16E33", "Sierra16E33h", "Sierra16E34"], commitsWithTestability: {}};
952             let fetchAndReportPromise = null;
953
954             return addSlaveForReport(emptyReport).then(() => {
955                 return Promise.all([
956                     db.insert('repositories', {'id': 10, 'name': 'OSX'}),
957                     db.insert('commits', {'repository': 10, 'revision': 'Sierra16D67', 'order': 1603006700, 'reported': true}),
958                     db.insert('commits', {'repository': 10, 'revision': 'Sierra16D68', 'order': 1603006800, 'reported': true}),
959                     db.insert('commits', {'repository': 10, 'revision': 'Sierra16D69', 'order': 1603006900, 'reported': false}),
960                     db.insert('commits', {'repository': 10, 'revision': 'Sierra16E32', 'order': 1604003200, 'reported': true}),
961                     db.insert('commits', {'repository': 10, 'revision': 'Sierra16E33', 'order': 1604003300, 'reported': true}),
962                     db.insert('commits', {'repository': 10, 'revision': 'Sierra16E33g', 'order': 1604003307, 'reported': true})]);
963             }).then(() => {
964                 return TestServer.remoteAPI().getJSON('/api/commits/OSX/last-reported?from=1603000000&to=1603099900');
965             }).then((result) => {
966                 assert.equal(result['commits'].length, 1);
967                 assert.equal(result['commits'][0]['revision'], 'Sierra16D68');
968                 assert.equal(result['commits'][0]['order'], 1603006800);
969
970                 return TestServer.remoteAPI().getJSON('/api/commits/OSX/last-reported?from=1604000000&to=1604099900');
971             }).then((result) => {
972                 assert.equal(result['commits'].length, 1);
973                 assert.equal(result['commits'][0]['revision'], 'Sierra16E33g');
974                 assert.equal(result['commits'][0]['order'], 1604003307);
975
976                 const waitForInvocationPromise = MockSubprocess.waitForInvocation();
977                 fetchAndReportPromise = fetcher.fetchReportAndUpdateBuilds();
978                 return waitForInvocationPromise;
979             }).then(() => {
980                 assert.equal(invocations.length, 1);
981                 assert.deepEqual(invocations[0].command, ['list', 'all osx 16Dxx builds']);
982                 invocations[0].resolve(JSON.stringify(resultsForSierraD));
983                 return MockSubprocess.resetAndWaitForInvocation();
984             }).then(() => {
985                 assert.equal(invocations.length, 1);
986                 assert.deepEqual(invocations[0].command, ['list', 'ownedCommit', 'for', 'revision', 'Sierra16D69']);
987                 MockSubprocess.invocations[0].resolve(JSON.stringify(ownedCommitWithWebKit));
988                 return MockSubprocess.resetAndWaitForInvocation();
989             }).then(() => {
990                 assert.equal(invocations.length, 1);
991                 assert.deepEqual(invocations[0].command, ['list', 'all osx 16Exx builds']);
992                 invocations[0].resolve(JSON.stringify(resultsForSierraE));
993                 return MockSubprocess.resetAndWaitForInvocation();
994             }).then(() => {
995                 assert.deepEqual(invocations[0].command, ['list', 'ownedCommit', 'for', 'revision', 'Sierra16E33h']);
996                 invocations[0].resolve(JSON.stringify(anotherownedCommitWithWebKit));
997                 return MockSubprocess.resetAndWaitForInvocation();
998             }).then(() => {
999                 assert.equal(invocations.length, 1);
1000                 assert.deepEqual(invocations[0].command, ['list', 'ownedCommit', 'for', 'revision', 'Sierra16E34']);
1001                 invocations[0].reject('Command failed');
1002                 return fetchAndReportPromise.then(() => {
1003                     assert(false, 'should never be reached');
1004                 }, (error_output) => {
1005                     assert(error_output);
1006                     assert.equal(error_output, 'Command failed');
1007                 });
1008             }).then(() => {
1009                 return TestServer.remoteAPI().getJSON('/api/commits/OSX/last-reported?from=1603000000&to=1603099900');
1010             }).then((result) => {
1011                 assert.equal(result['commits'].length, 1);
1012                 assert.equal(result['commits'][0]['revision'], 'Sierra16D68');
1013                 assert.equal(result['commits'][0]['order'], 1603006800);
1014
1015                 return TestServer.remoteAPI().getJSON('/api/commits/OSX/last-reported?from=1604000000&to=1604099900');
1016             }).then((result) => {
1017                 assert.equal(result['commits'].length, 1);
1018                 assert.equal(result['commits'][0]['revision'], 'Sierra16E33g');
1019                 assert.equal(result['commits'][0]['order'], 1604003307);
1020             });
1021         })
1022     })
1023 });