Skip to content

Commit 52f5b3a

Browse files
authored
Coverage check (#17)
* Added coverage increase check for head commit on pull request events * Bug fixes * Logs archived #15 & log returned on GET id
1 parent 5a2d422 commit 52f5b3a

File tree

2 files changed

+149
-8
lines changed

2 files changed

+149
-8
lines changed

CHANGELOG.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,19 @@
44

55
### Added
66

7+
### Modified
8+
9+
- Fix'd description field for pending status response
10+
11+
## [2.0.0]
12+
### Added
13+
714
- changelog
815
- status and coverage endpoints for shields
16+
- coverage increase check on pull request events
917

1018
### Modified
1119

1220
- changed from using Smee client to Serveo for exposing ZTEST
1321
- fixes for test reports endpoint
14-
- tests now performed only on head commit
22+
- tests now performed only on head commit

index.js

Lines changed: 140 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -74,24 +74,99 @@ srv.post('/github', async (req, res, next) => {
7474

7575
/**
7676
* Load MATLAB test results from db.json file.
77-
* @param {string} id - Function to call with job and done callback when.
77+
* @param {string, array} id - Function to call with job and done callback when.
7878
*/
7979
function loadTestRecords(id) {
8080
let obj = JSON.parse(fs.readFileSync('./db.json', 'utf8'));
8181
if (!Array.isArray(obj)) obj = [obj]; // Ensure array
82-
return obj.find(o => o.commit === id);
82+
let records = obj.filter(o => id.includes(o.commit));
83+
// If single arg return as object, otherwise keep as array
84+
return (!Array.isArray(id) && records.length === 1 ? records[0] : records)
85+
};
86+
87+
/**
88+
* Compare coverage of two commits and post a failed status if coverage of head commit <= base commit.
89+
* @param {object} data - job data object with coverage field holding head and base commit ids.
90+
*/
91+
function compareCoverage(data) {
92+
let ids = data.coverage;
93+
let status, description;
94+
let records = loadTestRecords(Object.values(ids));
95+
// Filter duplicates just in case
96+
records = records.filter((set => o => !set.has(o.commit) && set.add(o.commit))(new Set));
97+
has_coverage = records.every(o => (typeof o.coverage !== 'undefined' && o.coverage > 0));
98+
// Check if any errored or failed to update coverage
99+
if (records.filter(o => o.status === 'error').length > 0) {
100+
status = 'failure';
101+
description = 'Failed to determine coverage as tests incomplete due to errors';
102+
} else if (records.length === 2 && has_coverage) {
103+
// Ensure first record is for head commit
104+
if (records[0].commit === ids.base) { records.reverse() };
105+
// Calculate coverage change
106+
let coverage = records[0].coverage - records[1].coverage;
107+
status = (coverage > 0 ? 'success' : 'failure');
108+
description = 'Coverage ' + (coverage > 0 ? 'increased' : 'decreased')
109+
+ ' from ' + Math.round(records[1].coverage*100)/100 + '%'
110+
+ ' to ' + Math.round(records[0].coverage*100)/100 + '%';
111+
} else {
112+
for (let commit in ids) {
113+
// Check test isn't already on the pile
114+
let job = queue.pile.filter(o => o.data.sha === ids[commit]);
115+
if (job.length > 0) { // Already on pile
116+
// Add coverage key to job data structure
117+
if (typeof job[0].data.coverage === 'undefined') { job[0].data.coverage = ids; };
118+
} else { // Add test to queue
119+
queue.add({
120+
skipPost: true,
121+
sha: ids[commit],
122+
owner: 'cortex-lab', // @todo Generalize repo owner
123+
repo: data.repo,
124+
status: '',
125+
context: '',
126+
coverage: ids // Note cf commit
127+
});
128+
}
129+
}
130+
return;
131+
};
132+
// Post a our coverage status
133+
request('POST /repos/:owner/:repo/statuses/:sha', {
134+
owner: 'cortex-lab',
135+
repo: data.repo,
136+
headers: {
137+
authorization: `token ${installationAccessToken}`,
138+
accept: 'application/vnd.github.machine-man-preview+json'
139+
},
140+
sha: ids.head,
141+
state: status,
142+
target_url: `${process.env.WEBHOOK_PROXY_URL}/events/${ids.head}`, // fail
143+
description: description,
144+
context: 'coverage/ZTEST'
145+
});
83146
};
84147

85148
// Serve the test results for requested commit id
86149
srv.get('/github/:id', function (req, res) {
87-
console.log('Request for test results for commit ' + req.params.id.substring(0,6))
150+
console.log('Request for test log for commit ' + req.params.id.substring(0,6))
151+
let log = `.\\src\\matlab_tests-${req.params.id}.log`;
152+
fs.readFile(log, 'utf8', (err, data) => {
153+
if (err) {
154+
res.statusCode = 404;
155+
res.send(`Record for commit ${req.params.id} not found`);
156+
} else {
157+
res.statusCode = 200;
158+
res.send(data);
159+
}
160+
});
161+
/*
88162
const record = loadTestRecords(req.params.id);
89163
if (typeof record == 'undefined') {
90164
res.statusCode = 404;
91165
res.send(`Record for commit ${req.params.id} not found`);
92166
} else {
93167
res.send(record['results']);
94168
}
169+
*/
95170
});
96171

97172
// Serve the coverage results
@@ -214,7 +289,7 @@ queue.process(async (job, done) => {
214289
runTests.kill();
215290
done(new Error('Job stalled')) }, 5*60000);
216291
let args = ['-r', `runAllTests (""${job.data.sha}"",""${job.data.repo}"")`,
217-
'-wait', '-log', '-nosplash', '-logfile', 'matlab_tests.log'];
292+
'-wait', '-log', '-nosplash', '-logfile', `.\\src\\matlab_tests-${job.data.sha}.log`];
218293
runTests = cp.execFile('matlab', args, (error, stdout, stderr) => {
219294
clearTimeout(timer);
220295
if (error) { // Send error status
@@ -240,6 +315,11 @@ queue.process(async (job, done) => {
240315
*/
241316
queue.on('finish', job => { // On job end post result to API
242317
console.log(`Job ${job.id} complete`)
318+
// If job was part of coverage test and error'd, call compare function
319+
// (otherwise this is done by the on complete callback after writing coverage to file)
320+
if (typeof job.data.coverage !== 'undefined' && job.data['status'] == 'error') {
321+
compareCoverage(job.data);
322+
};
243323
if (job.data.skipPost === true) { return; }
244324
request("POST /repos/:owner/:repo/statuses/:sha", {
245325
owner: job.data['owner'],
@@ -275,7 +355,9 @@ queue.on('complete', job => { // On job end post result to API
275355
for (let o of records) { if (o.commit === job.data.sha) {o.coverage = hits / (hits + misses) * 100; break; }} // Add percentage
276356
// Save object
277357
fs.writeFile('./db.json', JSON.stringify(records), function(err) {
278-
if (err) { console.log(err); }
358+
if (err) { console.log(err); return; }
359+
// If this test was to ascertain coverage, call comparison function
360+
if (typeof job.data.coverage !== 'undefined') { compareCoverage(job.data); };
279361
});
280362
});
281363
});
@@ -293,7 +375,7 @@ handler.on('push', async function (event) {
293375
console.log('Received a push event for %s to %s',
294376
event.payload.repository.name,
295377
event.payload.ref)
296-
// Ignore documentaion branches
378+
// Ignore documentation branches
297379
if (event.payload.ref.endsWith('documentation')) { return; }
298380
try { // Run tests for head commit only
299381
let head_commit = event.payload.head_commit.id;
@@ -308,7 +390,7 @@ handler.on('push', async function (event) {
308390
sha: head_commit,
309391
state: 'pending',
310392
target_url: `${process.env.WEBHOOK_PROXY_URL}/events/${head_commit}`, // fail
311-
description: 'Tests error',
393+
description: 'Tests running',
312394
context: 'continuous-integration/ZTEST'
313395
});
314396
// Add a new test job to the queue
@@ -322,6 +404,57 @@ handler.on('push', async function (event) {
322404
} catch (error) {console.log(error)}
323405
});
324406

407+
// Handle pull request events
408+
// Here we'll update coverage
409+
handler.on('pull_request', async function (event) {
410+
// Log the event
411+
console.log('Received a pull_request event for %s to %s',
412+
event.payload.pull_request.head.repo.name,
413+
event.payload.pull_request.head.ref)
414+
if (!event.payload.action.endsWith('opened') && event.payload.action !== 'synchronize') { return; }
415+
try { // Compare test coverage
416+
let head_commit = event.payload.pull_request.head.sha;
417+
let base_commit = event.payload.pull_request.base.sha;
418+
if (false) { // TODO for alyx only
419+
// Post a 'pending' status while we do our tests
420+
await request('POST /repos/:owner/:repo/statuses/:sha', {
421+
owner: 'cortex-lab',
422+
repo: event.payload.repository.name,
423+
headers: {
424+
authorization: `token ${installationAccessToken}`,
425+
accept: 'application/vnd.github.machine-man-preview+json'
426+
},
427+
sha: head_commit,
428+
state: 'pending',
429+
target_url: `${process.env.WEBHOOK_PROXY_URL}/events/${head_commit}`, // fail
430+
description: 'Tests running',
431+
context: 'continuous-integration/ZTEST'
432+
});
433+
};
434+
435+
// Post a 'pending' status while we do our tests
436+
request('POST /repos/:owner/:repo/statuses/:sha', {
437+
owner: 'cortex-lab',
438+
repo: event.payload.repository.name,
439+
headers: {
440+
authorization: `token ${installationAccessToken}`,
441+
accept: 'application/vnd.github.machine-man-preview+json'
442+
},
443+
sha: head_commit,
444+
state: 'pending',
445+
target_url: `${process.env.WEBHOOK_PROXY_URL}/events/${head_commit}`, // fail
446+
description: 'Checking coverage',
447+
context: 'coverage/ZTEST'
448+
});
449+
// Check coverage exists
450+
let data = {
451+
repo: event.payload.repository.name,
452+
coverage: {head: head_commit, base: base_commit}
453+
};
454+
compareCoverage(data);
455+
} catch (error) {console.log(error)}
456+
});
457+
325458
// Start the server in the port 3000
326459
var server = srv.listen(3000, function () {
327460
var host = server.address().address

0 commit comments

Comments
 (0)