Add cache for CommitLog objects to avoid refetching same commit.
[WebKit-https.git] / Websites / perf.webkit.org / unit-tests / commit-log-tests.js
1 'use strict';
2
3 const assert = require('assert');
4
5 require('../tools/js/v3-models.js');
6 const BrowserPrivilegedAPI = require('../public/v3/privileged-api.js').PrivilegedAPI;
7 const MockModels = require('./resources/mock-v3-models.js').MockModels;
8 const MockRemoteAPI = require('../unit-tests/resources/mock-remote-api.js').MockRemoteAPI;
9
10 function webkitCommit()
11 {
12     return new CommitLog(1, {
13         id: 1,
14         repository: MockModels.webkit,
15         revision: '200805',
16         time: +(new Date('2016-05-13T00:55:57.841344Z')),
17     });
18 }
19
20 function oldWebKitCommit()
21 {
22     return new CommitLog(2, {
23         id: 2,
24         repository: MockModels.webkit,
25         revision: '200574',
26         time: +(new Date('2016-05-09T14:59:23.553767Z')),
27     });
28 }
29
30 function gitWebKitCommit()
31 {
32     return new CommitLog(3, {
33         id: 3,
34         repository: MockModels.webkit,
35         revision: '6f8b0dbbda95a440503b88db1dd03dad3a7b07fb',
36         time: +(new Date('2016-05-13T00:55:57.841344Z')),
37     });
38 }
39
40 function oldGitWebKitCommit()
41 {
42     return new CommitLog(4, {
43         id: 4,
44         repository: MockModels.webkit,
45         revision: 'ffda14e6db0746d10d0f050907e4a7325851e502',
46         time: +(new Date('2016-05-09T14:59:23.553767Z')),
47     });
48 }
49
50 function osxCommit()
51 {
52     return new CommitLog(5, {
53         id: 5,
54         repository: MockModels.osx,
55         revision: '10.11.4 15E65',
56         time: null,
57         order: 1504065
58     });
59 }
60
61 function oldOSXCommit()
62 {
63     return new CommitLog(6, {
64         id: 6,
65         repository: MockModels.osx,
66         revision: '10.11.3 15D21',
67         time: null,
68         order: 1503021
69     });
70 }
71
72 function commitWithoutOwnedCommits()
73 {
74     return new CommitLog(6, {
75         id: 6,
76         repository: MockModels.ownerRepository,
77         revision: '10.11.4 15E66',
78         ownsCommits: false,
79         time: null,
80         order: 1504065
81     });
82 }
83
84 function ownerCommit()
85 {
86     return new CommitLog(5, {
87         id: 5,
88         repository: MockModels.ownerRepository,
89         revision: '10.11.4 15E65',
90         ownsCommits: true,
91         time: null,
92         order: 1504065
93     });
94 }
95
96 function otherOwnerCommit()
97 {
98     return new CommitLog(5, {
99         id: 5,
100         repository: MockModels.ownerRepository,
101         revision: '10.11.4 15E66',
102         ownsCommits: true,
103         time: null,
104         order: 1504066
105     });
106 }
107
108 function ownedCommit()
109 {
110     return new CommitLog(11, {
111         id: 11,
112         repository: MockModels.ownedRepository,
113         revision: 'owned-commit-0',
114         ownsCommits: true,
115         time: null
116     });
117 }
118
119 function anotherOwnedCommit()
120 {
121     return new CommitLog(11, {
122         id: 11,
123         repository: MockModels.ownedRepository,
124         revision: 'owned-commit-1',
125         ownsCommits: true,
126         time: null
127     });
128 }
129
130 describe('CommitLog', function () {
131     MockModels.inject();
132
133     describe('label', function () {
134         it('should prefix SVN revision with "r"', function () {
135             assert.equal(webkitCommit().label(), 'r200805');
136         });
137
138         it('should truncate a Git hash at 8th character', function () {
139             assert.equal(gitWebKitCommit().label(), '6f8b0dbb');
140         });
141
142         it('should not modify OS X version', function () {
143             assert.equal(osxCommit().label(), '10.11.4 15E65');
144         });
145     });
146
147     describe('title', function () {
148         it('should prefix SVN revision with "r"', function () {
149             assert.equal(webkitCommit().title(), 'WebKit at r200805');
150         });
151
152         it('should truncate a Git hash at 8th character', function () {
153             assert.equal(gitWebKitCommit().title(), 'WebKit at 6f8b0dbb');
154         });
155
156         it('should not modify OS X version', function () {
157             assert.equal(osxCommit().title(), 'OS X at 10.11.4 15E65');
158         });
159     });
160
161     describe('order', () => {
162         it('should return null if no commit order', () => {
163             assert.equal(webkitCommit().order(), null);
164         });
165         it('should return commit order if order exists', () => {
166             assert.equal(osxCommit().order(), 1504065);
167         });
168     });
169
170     describe('diff', function () {
171         it('should use label() as the label the previous commit is missing', function () {
172             assert.deepEqual(webkitCommit().diff(), {
173                 label: 'r200805',
174                 url: 'http://trac.webkit.org/changeset/200805',
175                 repository: MockModels.webkit
176             });
177
178             assert.deepEqual(gitWebKitCommit().diff(), {
179                 label: '6f8b0dbb',
180                 url: 'http://trac.webkit.org/changeset/6f8b0dbbda95a440503b88db1dd03dad3a7b07fb',
181                 repository: MockModels.webkit,
182             });
183
184             assert.deepEqual(osxCommit().diff(), {
185                 label: '10.11.4 15E65',
186                 url: '',
187                 repository: MockModels.osx,
188             });
189         });
190
191         it('should use increment the old SVN revision by 1', function () {
192             assert.deepEqual(webkitCommit().diff(oldWebKitCommit()), {
193                 label: 'r200574-r200805',
194                 url: '',
195                 repository: MockModels.webkit
196             });
197         });
198
199         it('should truncate a Git hash at 8th character', function () {
200             assert.deepEqual(gitWebKitCommit().diff(oldGitWebKitCommit()), {
201                 label: 'ffda14e6..6f8b0dbb',
202                 url: '',
203                 repository: MockModels.webkit
204             });
205         });
206
207         it('should surround "-" with spaces', function () {
208             assert.deepEqual(osxCommit().diff(oldOSXCommit()), {
209                 label: '10.11.3 15D21 - 10.11.4 15E65',
210                 url: '',
211                 repository: MockModels.osx
212             });
213         });
214     });
215
216     describe('hasOrdering', () => {
217         it('should return "true" when both commits have commit orders', () => {
218             assert.ok(CommitLog.hasOrdering(osxCommit(), oldOSXCommit()));
219         });
220
221         it('should return "true" when both commits have commit time', () => {
222             assert.ok(CommitLog.hasOrdering(webkitCommit(), oldWebKitCommit()));
223         });
224
225         it('should return "false" when neither commit time nor commit order exists', () => {
226             assert.ok(!CommitLog.hasOrdering(ownedCommit(), anotherOwnedCommit()));
227         });
228
229         it('should return "false" when one commit only has commit time and another only has commit order', () => {
230             assert.ok(!CommitLog.hasOrdering(webkitCommit(), osxCommit()));
231         });
232     });
233
234     describe('hasCommitOrder', () => {
235         it('should return "true" when a commit has commit order', () => {
236             assert.ok(osxCommit().hasCommitOrder());
237         });
238
239         it('should return "false" when a commit only has commit time', () => {
240             assert.ok(!webkitCommit().hasCommitOrder());
241         });
242     });
243
244     describe('hasCommitTime', () => {
245         it('should return "true" when a commit has commit order', () => {
246             assert.ok(!osxCommit().hasCommitTime());
247         });
248
249         it('should return "false" when a commit only has commit time', () => {
250             assert.ok(webkitCommit().hasCommitTime());
251         });
252     });
253
254     describe('orderTowCommits', () => {
255         it('should order by time when both commits have time', () => {
256             const startCommit = oldWebKitCommit();
257             const endCommit = webkitCommit();
258             assert.deepEqual(CommitLog.orderTwoCommits(endCommit, startCommit), [startCommit, endCommit]);
259             assert.deepEqual(CommitLog.orderTwoCommits(startCommit, endCommit), [startCommit, endCommit]);
260         });
261
262         it('should order by commit order when both commits only have commit order', () => {
263             const startCommit = oldOSXCommit();
264             const endCommit = osxCommit();
265             assert.deepEqual(CommitLog.orderTwoCommits(endCommit, startCommit), [startCommit, endCommit]);
266             assert.deepEqual(CommitLog.orderTwoCommits(startCommit, endCommit), [startCommit, endCommit]);
267         });
268     });
269
270     describe('fetchOwnedCommits', () => {
271         beforeEach(() => {
272             MockRemoteAPI.inject(null, BrowserPrivilegedAPI);
273         });
274
275         it('should reject if repository of the commit does not own other repositories', () => {
276             const commit = osxCommit();
277             return commit.fetchOwnedCommits().then(() => {
278                assert(false, 'Should not execute this line.');
279             }, (error) => {
280                 assert.equal(error, undefined);
281             });
282         });
283
284         it('should reject if commit does not own other owned-commits', () => {
285             const commit = commitWithoutOwnedCommits();
286             return commit.fetchOwnedCommits().then(() => {
287                 assert(false, 'Should not execute this line.');
288             }, (error) => {
289                 assert.equal(error, undefined);
290             });
291         });
292
293         it('should return owned-commit for a valid commit revision', () => {
294             const commit = ownerCommit();
295             const fetchingPromise = commit.fetchOwnedCommits();
296             const requests = MockRemoteAPI.requests;
297             assert.equal(requests.length, 1);
298             assert.equal(requests[0].url, '../api/commits/111/owned-commits?owner-revision=10.11.4%2015E65');
299             assert.equal(requests[0].method, 'GET');
300
301             requests[0].resolve({commits: [{
302                 id: 233,
303                 repository: MockModels.ownedRepository.id(),
304                 revision: '6f8b0dbbda95a440503b88db1dd03dad3a7b07fb',
305                 time: +(new Date('2016-05-13T00:55:57.841344Z')),
306             }]});
307             return fetchingPromise.then((ownedCommits) => {
308                 assert.equal(ownedCommits.length, 1);
309                 assert.equal(ownedCommits[0].repository(), MockModels.ownedRepository);
310                 assert.equal(ownedCommits[0].revision(), '6f8b0dbbda95a440503b88db1dd03dad3a7b07fb');
311                 assert.equal(ownedCommits[0].id(), 233);
312                 assert.equal(ownedCommits[0].ownerCommit(), commit);
313             });
314         });
315
316         it('should only fetch owned-commits exactly once', () => {
317             const commit = ownerCommit();
318             const fetchingPromise = commit.fetchOwnedCommits();
319             const requests = MockRemoteAPI.requests;
320             let existingOwnedCommits = null;
321             assert.equal(requests.length, 1);
322             assert.equal(requests[0].url, '../api/commits/111/owned-commits?owner-revision=10.11.4%2015E65');
323             assert.equal(requests[0].method, 'GET');
324
325             MockRemoteAPI.requests[0].resolve({commits: [{
326                 id: 233,
327                 repository: MockModels.ownedRepository.id(),
328                 revision: '6f8b0dbbda95a440503b88db1dd03dad3a7b07fb',
329                 time: +(new Date('2016-05-13T00:55:57.841344Z')),
330             }]});
331
332             return fetchingPromise.then((ownedCommits) => {
333                 existingOwnedCommits = ownedCommits;
334                 assert.equal(ownedCommits.length, 1);
335                 assert.equal(ownedCommits[0].repository(), MockModels.ownedRepository);
336                 assert.equal(ownedCommits[0].revision(), '6f8b0dbbda95a440503b88db1dd03dad3a7b07fb');
337                 assert.equal(ownedCommits[0].id(), 233);
338                 assert.equal(ownedCommits[0].ownerCommit(), commit);
339                 return commit.fetchOwnedCommits();
340             }).then((ownedCommits) => {
341                 assert.equal(requests.length, 1);
342                 assert.equal(existingOwnedCommits, ownedCommits);
343             });
344         });
345     });
346
347     describe('ownedCommitDifferenceForOwnerCommits', () => {
348         beforeEach(() => {
349             MockRemoteAPI.reset();
350         });
351
352         it('should return difference between owned-commits of 2 owner commits', () => {
353             const oneCommit = ownerCommit();
354             const otherCommit = otherOwnerCommit();
355             const fetchingPromise = oneCommit.fetchOwnedCommits();
356             const requests = MockRemoteAPI.requests;
357             assert.equal(requests.length, 1);
358             assert.equal(requests[0].url, '../api/commits/111/owned-commits?owner-revision=10.11.4%2015E65');
359             assert.equal(requests[0].method, 'GET');
360
361             requests[0].resolve({commits: [{
362                 id: 233,
363                 repository: MockModels.ownedRepository.id(),
364                 revision: '6f8b0dbbda95a440503b88db1dd03dad3a7b07fb',
365                 time: +(new Date('2016-05-13T00:55:57.841344Z')),
366             }, {
367                 id: 299,
368                 repository: MockModels.webkitGit.id(),
369                 revision: '04a6c72038f0b771a19248ca2549e1258617b5fc',
370                 time: +(new Date('2016-05-13T00:55:57.841344Z')),
371             }]});
372
373             return fetchingPromise.then((ownedCommits) => {
374                 assert.equal(ownedCommits.length, 2);
375                 assert.equal(ownedCommits[0].repository(), MockModels.ownedRepository);
376                 assert.equal(ownedCommits[0].revision(), '6f8b0dbbda95a440503b88db1dd03dad3a7b07fb');
377                 assert.equal(ownedCommits[0].id(), 233);
378                 assert.equal(ownedCommits[0].ownerCommit(), oneCommit);
379                 assert.equal(ownedCommits[1].repository(), MockModels.webkitGit);
380                 assert.equal(ownedCommits[1].revision(), '04a6c72038f0b771a19248ca2549e1258617b5fc');
381                 assert.equal(ownedCommits[1].id(), 299);
382                 assert.equal(ownedCommits[1].ownerCommit(), oneCommit);
383
384                 const otherFetchingPromise = otherCommit.fetchOwnedCommits();
385                 assert.equal(requests.length, 2);
386                 assert.equal(requests[1].url, '../api/commits/111/owned-commits?owner-revision=10.11.4%2015E66');
387                 assert.equal(requests[1].method, 'GET');
388
389                 requests[1].resolve({commits: [{
390                     id: 234,
391                     repository: MockModels.ownedRepository.id(),
392                     revision: 'd5099e03b482abdd77f6c4dcb875afd05bda5ab8',
393                     time: +(new Date('2016-05-13T00:55:57.841344Z')),
394                 }, {
395                     id: 299,
396                     repository: MockModels.webkitGit.id(),
397                     revision: '04a6c72038f0b771a19248ca2549e1258617b5fc',
398                     time: +(new Date('2016-05-13T00:55:57.841344Z')),
399                 }]});
400
401                 return otherFetchingPromise;
402             }).then((ownedCommits) => {
403                 assert.equal(ownedCommits.length, 2);
404                 assert.equal(ownedCommits[0].repository(), MockModels.ownedRepository);
405                 assert.equal(ownedCommits[0].revision(), 'd5099e03b482abdd77f6c4dcb875afd05bda5ab8');
406                 assert.equal(ownedCommits[0].id(), 234);
407                 assert.equal(ownedCommits[0].ownerCommit(), otherCommit);
408                 assert.equal(ownedCommits[1].repository(), MockModels.webkitGit);
409                 assert.equal(ownedCommits[1].revision(), '04a6c72038f0b771a19248ca2549e1258617b5fc');
410                 assert.equal(ownedCommits[1].id(), 299);
411                 assert.equal(ownedCommits[1].ownerCommit(), otherCommit);
412                 const difference = CommitLog.ownedCommitDifferenceForOwnerCommits(oneCommit, otherCommit);
413                 assert.equal(difference.size, 1);
414                 assert.equal(difference.keys().next().value, MockModels.ownedRepository);
415             });
416
417         });
418     });
419
420     const commitsAPIResponse = {
421         "commits":[{
422             "id": "831151",
423             "revision": "236643",
424             "repository": "11",
425             "previousCommit": null,
426             "time": 1538223077403,
427             "order": null,
428             "authorName": "Commit Queue",
429             "authorEmail": "commit-queue@webkit.org",
430             "message": "message",
431             "ownsCommits": false
432         }],
433         "status":"OK"
434     };
435
436     const commitsAPIResponseForOwnedWebKit = {
437         "commits":[{
438             "id": "1831151",
439             "revision": "236643",
440             "repository": "191",
441             "previousCommit": null,
442             "time": 1538223077403,
443             "order": null,
444             "authorName": "Commit Queue",
445             "authorEmail": "commit-queue@webkit.org",
446             "message": "message",
447             "ownsCommits": false
448         }],
449         "status":"OK"
450     };
451
452     describe('fetchForSingleRevision', () => {
453         beforeEach(() => {
454             MockRemoteAPI.reset();
455         });
456
457         it('should avoid fetching same revision from same repository twice', async () => {
458             let fetchingPromise = CommitLog.fetchForSingleRevision(MockModels.webkit, '236643');
459
460             const requests = MockRemoteAPI.requests;
461             assert.equal(requests.length, 1);
462             assert.equal(requests[0].url, `/api/commits/${MockModels.webkit.id()}/236643`);
463
464             requests[0].resolve(commitsAPIResponse);
465             const commits = await fetchingPromise;
466             assert.equal(commits.length, 1);
467
468             MockRemoteAPI.reset();
469             assert.equal(requests.length, 0);
470             fetchingPromise = CommitLog.fetchForSingleRevision(MockModels.webkit, '236643');
471             assert.equal(requests.length, 0);
472             const newCommits = await fetchingPromise;
473             assert.equal(newCommits.length, 1);
474
475             assert.equal(commits[0], newCommits[0]);
476         });
477
478         it('should avoid fetching same revision from same repository again if it has been fetched by "fetchBetweenRevisions" before', async () => {
479             let fetchingPromise = CommitLog.fetchBetweenRevisions(MockModels.webkit, '236642', '236643');
480
481             const requests = MockRemoteAPI.requests;
482             assert.equal(requests.length, 1);
483             assert.equal(requests[0].url, `/api/commits/${MockModels.webkit.id()}/?precedingRevision=236642&lastRevision=236643`);
484
485             requests[0].resolve(commitsAPIResponse);
486             const commits = await fetchingPromise;
487             assert.equal(commits.length, 1);
488
489             MockRemoteAPI.reset();
490             assert.equal(requests.length, 0);
491             fetchingPromise = CommitLog.fetchForSingleRevision(MockModels.webkit, '236643');
492             assert.equal(requests.length, 0);
493             const newCommits = await fetchingPromise;
494             assert.equal(newCommits.length, 1);
495
496             assert.equal(commits[0], newCommits[0]);
497         });
498
499         it('should not overwrite cache for commits with same revision but from different repositories', async () => {
500             let fetchingPromise = CommitLog.fetchForSingleRevision(MockModels.webkit, '236643');
501
502             const requests = MockRemoteAPI.requests;
503             assert.equal(requests.length, 1);
504             assert.equal(requests[0].url, `/api/commits/${MockModels.webkit.id()}/236643`);
505
506             requests[0].resolve(commitsAPIResponse);
507             const commits = await fetchingPromise;
508             assert.equal(commits.length, 1);
509
510             MockRemoteAPI.reset();
511             assert.equal(requests.length, 0);
512
513             fetchingPromise = CommitLog.fetchForSingleRevision(MockModels.ownedWebkit, '236643');
514             assert.equal(requests.length, 1);
515             assert.equal(requests[0].url, `/api/commits/${MockModels.ownedWebkit.id()}/236643`);
516
517             requests[0].resolve(commitsAPIResponseForOwnedWebKit);
518             const newCommits = await fetchingPromise;
519             assert.equal(newCommits.length, 1);
520
521             assert.notEqual(commits[0], newCommits[0]);
522         });
523     });
524 });