Skip to content

Commit 90c4df6

Browse files
committed
201 status -> 200; more tests; added test duration to description
1 parent a3f5ecb commit 90c4df6

File tree

7 files changed

+113
-20
lines changed

7 files changed

+113
-20
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,11 @@
1414

1515
## Modified
1616

17+
- renamed MATLAB-CI to labCI
1718
- records endpoint can return pending jobs
1819
- tests badge endpoint returns 'error' on errored tests instead of 'unknown'
1920
- job waits for coverage calculation and updating of records before finishing
21+
- On successful completion of tests the duration is appended to the description
2022

2123
## [2.2.1]
2224

config/config.js

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,9 @@ let settings;
99

1010
// Defaults for when there's no user file; will almost certainly fail
1111
const defaults = {
12-
setup_function: null,
13-
test_function: null,
12+
max_description_len: 140, // GitHub status API has a description char limit
1413
listen_port: 3000,
1514
timeout: 8 * 60000,
16-
program: 'python',
1715
strict_coverage: false,
1816
events: {
1917
push: {
@@ -57,12 +55,15 @@ if (env.startsWith('test')) {
5755
settings = testing;
5856
} else if (userSettings) {
5957
settings = userSettings;
60-
if (!('dbFile' in settings)) settings.dbFile = path.join(dataPath, dbFilename);
61-
if (!('dataPath' in settings)) settings.dataPath = dataPath;
6258
} else {
6359
settings = defaults;
6460
}
6561

62+
// Ensure defaults for absent fields
63+
for (let field in defaults) {
64+
if (!(field in settings)) settings[field] = defaults[field];
65+
}
66+
6667
// Check ENV set up correctly
6768
required = ['GITHUB_PRIVATE_KEY', 'GITHUB_APP_IDENTIFIER', 'GITHUB_WEBHOOK_SECRET',
6869
'WEBHOOK_PROXY_URL', 'REPO_PATH', 'REPO_NAME', 'REPO_OWNER', 'TUNNEL_HOST',
@@ -75,4 +76,4 @@ if (missing.length > 0) {
7576
throw ReferenceError(errMsg);
7677
}
7778

78-
module.exports = {settings};
79+
module.exports = { settings };

lib.js

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -165,13 +165,24 @@ async function updateJobFromRecord(job) {
165165
let log = _log.extend('updateJobFromRecord');
166166
log('Loading test records for head commit %g', job.data['sha']);
167167
let rec = loadTestRecords(job.data['sha']); // Load test result from json log
168-
if (rec.length === 0) {
168+
if (rec.length === 0) { // No record found
169169
log('No record found, return false');
170170
return false;
171-
} // No record found
171+
}
172172
rec = Array.isArray(rec) ? rec.pop() : rec; // in case of duplicates, take last
173173
job.data['status'] = rec['status'];
174174
job.data['description'] = rec['description'];
175+
// Append the duration in minutes if available
176+
if (rec['status'] === 'success' && job.created) {
177+
let diff = (new Date().getTime() - job.created.getTime()) / 1000;
178+
let duration = ` (took ${Math.round(diff / 60)} min)`;
179+
// Truncate description if necessary
180+
let strSize = (config.max_description_len - duration.length);
181+
if (job.data['description'].length > strSize) {
182+
job.data['description'] = job.data['description'].slice(0, strSize - 3) + '...';
183+
}
184+
job.data['description'] += duration;
185+
}
175186
job.data['coverage'] = ('coverage' in rec) ? rec['coverage'] : null;
176187
if (!job.data['coverage'] && rec['status'] !== 'error') {
177188
log('Coverage missing, computing from XML');
@@ -523,9 +534,7 @@ function computeCoverage(job) {
523534
console.log('Coverage saved into records');
524535
// If this test was to ascertain coverage, call comparison function
525536
let toCompare = (job.data.context || '').startsWith('coverage') && job.data.base;
526-
if (toCompare) {
527-
return compareCoverage(job);
528-
}
537+
if (toCompare) return compareCoverage(job);
529538
});
530539
}).catch(err => {
531540
job.status = 'error';
@@ -620,12 +629,12 @@ function compareCoverage(job) {
620629
function getBadgeData(data) {
621630
let id = data.sha;
622631
if (!id) throw new ReferenceError('Invalid "sha" field in input data');
623-
var report = {'schemaVersion': 1, 'label': data.context};
632+
const report = {'schemaVersion': 1, 'label': data.context};
624633
// Try to load coverage record
625634
let record = data.force ? [] : loadTestRecords(id);
626635
// If no record found
627636
if (record.length === 0) {
628-
report['message'] = 'pending';
637+
report['message'] = data.context === 'tests'? 'in progress' : 'pending';
629638
report['color'] = 'orange';
630639
// Check test isn't already on the pile
631640
let onPile = false;

serve.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ const secret = process.env['GITHUB_WEBHOOK_SECRET'];
2424
// Currently this app is only set up to process push and pull request events so we will have the
2525
// handler reject any others. We will also check that only these are set up in the config.
2626
const supportedEvents = ['push', 'pull_request']; // events the ci can handle
27-
const maxN = 140; // The maximum n chars of the status description
2827
const ENDPOINT = 'logs'; // The URL endpoint for fetching status check details
2928
// An optional static directory for serving css files
3029
const STATIC = 'public';
@@ -326,6 +325,7 @@ srv.get('/:badge/:repo/:id', async (req, res) => {
326325
repo: req.params.repo,
327326
routine: lib.context2routine(context)
328327
};
328+
329329
// Check we have a matching routine
330330
if (!data.routine) {
331331
console.error(`No routine for "${context}" context`);
@@ -341,7 +341,7 @@ srv.get('/:badge/:repo/:id', async (req, res) => {
341341
console.log(`Request for ${req.params.id} ${data.context}`);
342342
const report = lib.getBadgeData(data);
343343
// Send report
344-
res.statusCode = (report['message'] === 'pending') ? 201 : 200;
344+
res.statusCode = 200;
345345
res.setHeader('Content-Type', 'application/json');
346346
res.end(JSON.stringify(report));
347347
})
@@ -384,7 +384,7 @@ async function updateStatus(data, targetURL = '') {
384384
sha: data['sha'],
385385
state: data['status'],
386386
target_url: targetURL,
387-
description: (data['description'] || '').substring(0, maxN),
387+
description: (data['description'] || '').substring(0, config.max_description_len),
388388
context: data['context']
389389
});
390390
}
@@ -404,7 +404,7 @@ async function eventCallback(event) {
404404
debug('eventCallback called');
405405
var ref; // ref (i.e. branch name) and head commit
406406
const eventType = event.event; // 'push' or 'pull_request'
407-
var job_template = { // the data structure containing information about our check
407+
const job_template = { // the data structure containing information about our check
408408
sha: null, // The head commit sha to test on
409409
base: null, // The previous commit sha (for comparing changes in code coverage)
410410
force: false, // Whether to run tests when results already cached

test/fixtures/.db.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
[{"commit": "cabe27e5c8b8cb7cdc4e152f1cf013a89adc7a71", "results": [{"Duration": 0.07038330000000001, "Details": {}, "Name": "Parameters_test/test_set", "Passed": false, "Failed": true, "Incomplete": true}, {"Duration": 2.5838056999999996, "Details": {}, "Name": "Alyx_test[base_url=https___test_alyx_internationalbrainlab_org]/test_getSessions", "Passed": false, "Failed": true, "Incomplete": false}, {"Duration": 5.1105206, "Details": {}, "Name": "Block2ALF_test/test_incomplete", "Passed": true, "Failed": false, "Incomplete": false}], "status": "failure", "description": "18/320 tests failed", "coverage": 22.19690421937613}, {"commit": "1c33a6e2ac7d7fc098105b21a702e104e09767cf", "results": [{"Duration": 0.0500121, "Details": {}, "Name": "patch_test/Test3_Circle", "Passed": true, "Failed": false, "Incomplete": false}, {"Duration": 0.0482601, "Details": {}, "Name": "toStr_test/test_toStr", "Passed": true, "Failed": false, "Incomplete": false}, {"Duration": 0.0389527, "Details": {}, "Name": "Signals_test/test_output", "Passed": true, "Failed": false, "Incomplete": false}, {"Duration": 0.016370700000000002, "Details": {}, "Name": "Signals_test/test_erf", "Passed": true, "Failed": false, "Incomplete": false}, {"Duration": 0.0152839, "Details": {}, "Name": "rnd_test/test_uni", "Passed": true, "Failed": false, "Incomplete": false}], "status": "success", "description": "All passed", "coverage": 75.77018633540374}, {"commit": "7bdf62", "results": null, "status": "error", "description": "Failed to checkout code: 7bdf62", "coverage": null}]
1+
[{"commit":"cabe27e5c8b8cb7cdc4e152f1cf013a89adc7a71","datetime":"2021-04-30T08:23:06.764580","results":[{"Duration":0.07038330000000001,"Details":{},"Name":"Parameters_test/test_set","Passed":false,"Failed":true,"Incomplete":true},{"Duration":2.5838056999999996,"Details":{},"Name":"Alyx_test[base_url=https___test_alyx_internationalbrainlab_org]/test_getSessions","Passed":false,"Failed":true,"Incomplete":false},{"Duration":5.1105206,"Details":{},"Name":"Block2ALF_test/test_incomplete","Passed":true,"Failed":false,"Incomplete":false}],"status":"failure","description":"18/320 tests failed","statistics":{"total":320,"failed":16,"errored":2,"skipped":5,"passed":297,"duration":146},"coverage":22.19690421937613},{"commit":"1c33a6e2ac7d7fc098105b21a702e104e09767cf","results":[{"Duration":0.0500121,"Details":{},"Name":"patch_test/Test3_Circle","Passed":true,"Failed":false,"Incomplete":false},{"Duration":0.0482601,"Details":{},"Name":"toStr_test/test_toStr","Passed":true,"Failed":false,"Incomplete":false},{"Duration":0.0389527,"Details":{},"Name":"Signals_test/test_output","Passed":true,"Failed":false,"Incomplete":false},{"Duration":0.016370700000000002,"Details":{},"Name":"Signals_test/test_erf","Passed":true,"Failed":false,"Incomplete":false},{"Duration":0.0152839,"Details":{},"Name":"rnd_test/test_uni","Passed":true,"Failed":false,"Incomplete":false}],"status":"success","description":"All passed","coverage":75.77018633540374},{"commit":"7bdf62","results":null,"status":"error","description":"Failed to checkout code: 7bdf62","coverage":null}]

test/lib.test.js

Lines changed: 82 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -229,7 +229,7 @@ describe('Test compareCoverage:', function () {
229229
* A test for the function updateJobFromRecord.
230230
* @todo add test for compareCoverage call
231231
*/
232-
describe('Test updateJobFromRecord:', function () {
232+
describe('Test updateJobFromRecord', function () {
233233
var job;
234234

235235
beforeEach(function () {
@@ -257,6 +257,54 @@ describe('Test updateJobFromRecord:', function () {
257257
});
258258

259259

260+
/**
261+
* A test for inserting the duration in description field by updateJobFromRecord.
262+
*/
263+
describe('Test duration in description', function () {
264+
var job;
265+
var _dbFile = config.dbFile;
266+
267+
before(function (done) {
268+
job = {
269+
data: {
270+
sha: ids[1]
271+
},
272+
created: new Date(Date.now() - 1000 * 60 * 10)
273+
};
274+
config.dbFile = path.join(path.parse(config.dbFile).dir, '._db.json');
275+
fs.copyFile(_dbFile, config.dbFile, err => {
276+
if (err) throw err;
277+
done();
278+
});
279+
});
280+
281+
after(function (done) {
282+
queue.pile = []; // In case a job was added
283+
fs.unlink(config.dbFile, err => {
284+
config.dbFile = _dbFile;
285+
if (err) throw err;
286+
done();
287+
});
288+
});
289+
290+
it('expect duration in description', async function () {
291+
const updated = await lib.updateJobFromRecord(job);
292+
expect(updated).true;
293+
expect(job.data.description).contains('10 min');
294+
});
295+
296+
it('expect truncated description', async function () {
297+
const records = JSON.parse(await fs.promises.readFile(config.dbFile, 'utf8'));
298+
records[1]['description'] = 'Lorem ipsum '.repeat(13);
299+
await fs.promises.writeFile(config.dbFile, JSON.stringify(records));
300+
const updated = await lib.updateJobFromRecord(job);
301+
expect(updated).true;
302+
expect(job.data.description.length).lte(config.max_description_len);
303+
expect(job.data.description.endsWith('... (took 10 min)')).true;
304+
});
305+
});
306+
307+
260308
/**
261309
* A test for the function startJobTimer. Should kill the process when time is up and update the
262310
* job data.
@@ -795,6 +843,39 @@ describe('getBadgeData function', () => {
795843
sandbox.assert.calledOnce(queue.add);
796844
});
797845

846+
it('Check tests status', function () {
847+
var data, expected;
848+
849+
// Failed tests
850+
input['sha'] = ids[0];
851+
input['context'] = 'tests';
852+
data = lib.getBadgeData(input);
853+
expected = {
854+
schemaVersion: 1,
855+
label: 'tests',
856+
message: '297 passed, 18 failed, 5 skipped',
857+
color: 'red'
858+
};
859+
expect(data).to.deep.equal(expected);
860+
sandbox.assert.notCalled(queue.add);
861+
862+
// Errored
863+
input['sha'] = ids[3];
864+
expected['message'] = 'errored';
865+
expected['color'] = 'red';
866+
data = lib.getBadgeData(input);
867+
expect(data).to.deep.equal(expected);
868+
sandbox.assert.notCalled(queue.add);
869+
870+
// No stats field
871+
input['sha'] = ids[1];
872+
expected['message'] = 'passed';
873+
expected['color'] = 'brightgreen';
874+
data = lib.getBadgeData(input);
875+
expect(data).to.deep.equal(expected);
876+
sandbox.assert.notCalled(queue.add);
877+
});
878+
798879
it('Check force flag', function () {
799880
input['sha'] = ids[1];
800881
input['context'] = 'build';

test/serve.test.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -624,7 +624,7 @@ describe('shields callback', () => {
624624
request(srv)
625625
.get(`/coverage/${info.repo}/${SHA}?force=1`)
626626
.expect('Content-Type', 'application/json')
627-
.expect(201)
627+
.expect(200)
628628
.end(function (err, res) {
629629
scope.done();
630630
if (err) return done(err);

0 commit comments

Comments
 (0)