OSBuildFetcher should respect maxRevision while finding OS builds to report.
[WebKit.git] / Websites / perf.webkit.org / tools / js / os-build-fetcher.js
1 'use strict';
2
3 let assert = require('assert');
4
5 function mapInSerialPromiseChain(list, callback)
6 {
7     const results = [];
8     return list.reduce((chainedPromise, item) => {
9         return chainedPromise.then(() => callback(item)).then((result) => results.push(result));
10     }, Promise.resolve()).then(() => results);
11 }
12
13 class OSBuildFetcher {
14
15     constructor(osConfig, remoteAPI, slaveAuth, subprocess, logger)
16     {
17         this._osConfig = osConfig;
18         this._logger = logger;
19         this._slaveAuth = slaveAuth;
20         this._remoteAPI = remoteAPI;
21         this._subprocess = subprocess;
22         this._maxSubmitCount = osConfig['maxSubmitCount'] || 20;
23     }
24
25     static fetchAndReportAllInOrder(fetcherList)
26     {
27         return mapInSerialPromiseChain(fetcherList, (fetcher) => fetcher.fetchAndReportNewBuilds());
28     }
29
30     fetchAndReportNewBuilds()
31     {
32         return this._fetchAvailableBuilds().then((results) => {
33             this._logger.log(`Submitting ${results.length} builds for ${this._osConfig['name']}`);
34             if (results.length == 0)
35                 return this._submitCommits(results);
36
37             const splittedResults = [];
38             for (let startIndex = 0; startIndex < results.length; startIndex += this._maxSubmitCount)
39                 splittedResults.push(results.slice(startIndex, startIndex + this._maxSubmitCount));
40
41             return mapInSerialPromiseChain(splittedResults, this._submitCommits.bind(this)).then((responses) => {
42                 assert(responses.every((response) => response['status'] == 'OK'));
43                 assert(responses.length > 0);
44                 return responses[0];
45             });
46         });
47     }
48
49     _fetchAvailableBuilds()
50     {
51         const config = this._osConfig;
52         const repositoryName = config['name'];
53         let customCommands = config['customCommands'];
54
55         return mapInSerialPromiseChain(customCommands, (command) => {
56             assert(command['minRevision']);
57             assert(command['maxRevision']);
58             const minRevisionOrder = this._computeOrder(command['minRevision']);
59             const maxRevisionOrder = this._computeOrder(command['maxRevision']);
60
61             const url = `/api/commits/${escape(repositoryName)}/last-reported?from=${minRevisionOrder}&to=${maxRevisionOrder}`;
62
63             return this._remoteAPI.getJSONWithStatus(url).then((result) => {
64                 const minOrder = result['commits'].length == 1 ? parseInt(result['commits'][0]['order']) + 1 : minRevisionOrder;
65                 return this._commitsForAvailableBuilds(repositoryName, command['command'], command['linesToIgnore'], minOrder, maxRevisionOrder);
66             }).then((commits) => {
67                 const label = 'name' in command ? `"${command['name']}"` : `"${command['minRevision']}" to "${command['maxRevision']}"`;
68                 this._logger.log(`Found ${commits.length} builds for ${label}`);
69
70                 if ('ownedCommitCommand' in command) {
71                     this._logger.log(`Resolving ownedCommits for ${label}`);
72                     return this._addOwnedCommitsForBuild(commits, command['ownedCommitCommand']);
73                 }
74
75                 return commits;
76             });
77         }).then((results) => [].concat(...results));
78     }
79
80     _computeOrder(revision)
81     {
82         const buildNameRegex = /(\d+)([a-zA-Z])(\d+)([a-zA-Z]*)$/;
83         const match = buildNameRegex.exec(revision);
84         assert(match);
85         const major = parseInt(match[1]);
86         const kind = match[2].toUpperCase().charCodeAt(0) - "A".charCodeAt(0);
87         const minor = parseInt(match[3]);
88         const variant = match[4] ? match[4].toUpperCase().charCodeAt(0) - "A".charCodeAt(0) + 1 : 0;
89         return ((major * 100 + kind) * 10000 + minor) * 100 + variant;
90     }
91
92     _commitsForAvailableBuilds(repository, command, linesToIgnore, minOrder, maxOrder)
93     {
94         return this._subprocess.execute(command).then((output) => {
95             let lines = output.split('\n');
96             if (linesToIgnore){
97                 const regex = new RegExp(linesToIgnore);
98                 lines = lines.filter((line) => !regex.exec(line));
99             }
100             return lines.map((revision) => ({repository, revision, 'order': this._computeOrder(revision)}))
101                 .filter((commit) => commit['order'] >= minOrder && commit['order'] <= maxOrder);
102         });
103     }
104
105     _addOwnedCommitsForBuild(commits, command)
106     {
107         return mapInSerialPromiseChain(commits, (commit) => {
108             return this._subprocess.execute(command.concat(commit['revision'])).then((ownedCommitOutput) => {
109                 const ownedCommits = JSON.parse(ownedCommitOutput);
110                 this._logger.log(`Got ${Object.keys((ownedCommits)).length} owned commits for "${commit['revision']}"`);
111                 for (let repositoryName in ownedCommits) {
112                     const ownedCommit = ownedCommits[repositoryName];
113                     assert.deepEqual(Object.keys(ownedCommit), ['revision']);
114                     assert(typeof(ownedCommit['revision']) == 'string');
115                 }
116                 commit['ownedCommits'] = ownedCommits;
117                 return commit;
118             });
119         });
120     }
121
122     _submitCommits(commits)
123     {
124         const commitsToReport = {"slaveName": this._slaveAuth['name'], "slavePassword": this._slaveAuth['password'], 'commits': commits};
125         return this._remoteAPI.postJSONWithStatus('/api/report-commits/', commitsToReport);
126     }
127 }
128
129 if (typeof module != 'undefined')
130     module.exports.OSBuildFetcher = OSBuildFetcher;