88 * @requires module:express
99 * @requires module:localtunnel
1010 * @requires module:github-webhook-handler
11+ * @todo save auxiliary configuration into a separate config file
12+ * @todo add abort option for when new commits added
1113 */
14+ import * as path from 'path' ;
15+
1216const fs = require ( 'fs' ) ;
1317const express = require ( 'express' )
1418const srv = express ( ) ;
@@ -21,6 +25,10 @@ const localtunnel = require('localtunnel');
2125
2226const id = process . env . GITHUB_APP_IDENTIFIER ;
2327const secret = process . env . GITHUB_WEBHOOK_SECRET ;
28+ const appdata = process . env . APPDATA || process . env . HOMEPATH ;
29+ const dbFile = path . join ( appdata , '.ci-db.json' ) // cache of test results
30+ const config = require ( './config.json' ) ;
31+ const timeout = config . timeout || 8 * 60000
2432
2533// Configure a secure tunnel
2634const openTunnel = async ( ) => {
@@ -78,11 +86,11 @@ srv.post('/github', async (req, res, next) => {
7886} ) ;
7987
8088/**
81- * Load MATLAB test results from db.json file.
89+ * Load MATLAB test results from ci- db.json file.
8290 * @param {string, array } id - Function to call with job and done callback when.
8391 */
8492function loadTestRecords ( id ) {
85- let obj = JSON . parse ( fs . readFileSync ( './db.json' , 'utf8' ) ) ;
93+ let obj = JSON . parse ( fs . readFileSync ( dbFile , 'utf8' ) ) ;
8694 if ( ! Array . isArray ( obj ) ) obj = [ obj ] ; // Ensure array
8795 let records = obj . filter ( o => id . includes ( o . commit ) ) ;
8896 // If single arg return as object, otherwise keep as array
@@ -99,7 +107,7 @@ function compareCoverage(data) {
99107 let records = loadTestRecords ( Object . values ( ids ) ) ;
100108 // Filter duplicates just in case
101109 records = records . filter ( ( set => o => ! set . has ( o . commit ) && set . add ( o . commit ) ) ( new Set ) ) ;
102- has_coverage = records . every ( o => ( typeof o . coverage !== 'undefined' && o . coverage > 0 ) ) ;
110+ let has_coverage = records . every ( o => ( typeof o . coverage !== 'undefined' && o . coverage > 0 ) ) ;
103111 // Check if any errored or failed to update coverage
104112 if ( records . filter ( o => o . status === 'error' ) . length > 0 ) {
105113 status = 'failure' ;
@@ -113,6 +121,7 @@ function compareCoverage(data) {
113121 description = 'Coverage ' + ( coverage > 0 ? 'increased' : 'decreased' )
114122 + ' from ' + Math . round ( records [ 1 ] . coverage * 100 ) / 100 + '%'
115123 + ' to ' + Math . round ( records [ 0 ] . coverage * 100 ) / 100 + '%' ;
124+ // TODO Maybe remove test from pile if we already have it?
116125 } else {
117126 for ( let commit in ids ) {
118127 // Check test isn't already on the pile
@@ -146,22 +155,22 @@ function compareCoverage(data) {
146155 state : status ,
147156 target_url : `${ process . env . WEBHOOK_PROXY_URL } /events/${ ids . head } ` , // fail
148157 description : description ,
149- context : ' coverage/ZTEST' // TODO Generalize
158+ context : ` coverage/${ process . env . USERDOMAIN } `
150159 } ) ;
151160}
152161
153162// Serve the test results for requested commit id
154163srv . get ( '/github/:id' , function ( req , res ) {
155164 console . log ( 'Request for test log for commit ' + req . params . id . substring ( 0 , 6 ) )
156- let log = `.\\src\\matlab_tests-${ req . params . id } .log` ;
165+ let log = `.\\src\\matlab_tests-${ req . params . id } .log` ; // TODO Generalize
157166 fs . readFile ( log , 'utf8' , ( err , data ) => {
158167 if ( err ) {
159168 res . statusCode = 404 ;
160169 res . send ( `Record for commit ${ req . params . id } not found` ) ;
161170 } else {
162171 res . statusCode = 200 ;
163- preText = '<html lang="en-GB"><body><pre>' ;
164- postText = '</pre></body></html>' ;
172+ let preText = '<html lang="en-GB"><body><pre>' ;
173+ let postText = '</pre></body></html>' ;
165174 res . send ( preText + data + postText ) ;
166175
167176 }
@@ -191,7 +200,7 @@ srv.get('/coverage/:repo/:branch', async (req, res) => {
191200 let id = data . object . sha ;
192201 var report = { 'schemaVersion' : 1 , 'label' : 'coverage' } ;
193202 try { // Try to load coverage record
194- record = await loadTestRecords ( id ) ;
203+ let record = await loadTestRecords ( id ) ;
195204 if ( typeof record == 'undefined' || record [ 'coverage' ] == '' ) { throw 404 } // Test not found for commit
196205 if ( record [ 'status' ] === 'error' ) { throw 500 } // Test found for commit but errored
197206 report [ 'message' ] = Math . round ( record [ 'coverage' ] * 100 ) / 100 + '%' ;
@@ -276,7 +285,7 @@ queue.process(async (job, done) => {
276285 // If the repo is a submodule, modify path
277286 var path = process . env . REPO_PATH ;
278287 if ( job . data [ 'repo' ] === 'alyx-matlab' || job . data [ 'repo' ] === 'signals' ) {
279- path = path + '\\' + job . data [ 'repo' ] ; }
288+ path = path + path . sep + job . data [ 'repo' ] ; }
280289 if ( job . data [ 'repo' ] === 'alyx' ) { sha = 'dev' } // For Alyx checkout master
281290 // Checkout commit
282291 checkout = cp . execFile ( 'checkout.bat ' , [ sha , path ] , ( error , stdout , stderr ) => {
@@ -293,12 +302,13 @@ queue.process(async (job, done) => {
293302 const timer = setTimeout ( function ( ) {
294303 console . log ( 'Max test time exceeded' )
295304 job . data [ 'status' ] = 'error' ;
296- job . data [ 'context' ] = ' Tests stalled after ~8 min' ;
297- runTests . kill ( ) ;
298- done ( new Error ( 'Job stalled' ) ) } , 8 * 60000 ) ;
305+ job . data [ 'context' ] = ` Tests stalled after ~${ ( timeout / 60000 ) . toFixed ( 0 ) } min` ;
306+ runTests . kill ( ) ;
307+ done ( new Error ( 'Job stalled' ) ) } , timeout ) ;
299308 let args = [ '-r' , `runAllTests (""${ job . data . sha } "",""${ job . data . repo } "")` ,
300- '-wait' , '-log' , '-nosplash' , '-logfile' , `.\\src\\matlab_tests-${ job . data . sha } .log` ] ;
301- runTests = cp . execFile ( 'matlab' , args , ( error , stdout , stderr ) => {
309+ '-wait' , '-log' , '-nosplash' , '-logfile' , `.\\src\\matlab_tests-${ job . data . sha } .log` ] ; // TODO Generalize
310+ let program = config . program || 'matlab'
311+ runTests = cp . execFile ( program , args , ( error , stdout , stderr ) => {
302312 clearTimeout ( timer ) ;
303313 if ( error ) { // Send error status
304314 // Isolate error from log
@@ -325,7 +335,7 @@ queue.on('finish', job => { // On job end post result to API
325335 console . log ( `Job ${ job . id } complete` )
326336 // If job was part of coverage test and error'd, call compare function
327337 // (otherwise this is done by the on complete callback after writing coverage to file)
328- if ( typeof job . data . coverage !== 'undefined' && job . data [ 'status' ] == 'error' ) {
338+ if ( typeof job . data . coverage !== 'undefined' && job . data [ 'status' ] === 'error' ) {
329339 compareCoverage ( job . data ) ;
330340 }
331341 if ( job . data . skipPost === true ) { return ; }
@@ -339,7 +349,7 @@ queue.on('finish', job => { // On job end post result to API
339349 state : job . data [ 'status' ] ,
340350 target_url : `${ process . env . WEBHOOK_PROXY_URL } /github/${ job . data . sha } ` , // FIXME replace url
341351 description : job . data [ 'context' ] ,
342- context : ' continuous-integration/ZTEST'
352+ context : ` continuous-integration/${ process . env . USERDOMAIN } `
343353 } ) ;
344354} ) ;
345355
@@ -358,11 +368,11 @@ queue.on('complete', job => { // On job end post result to API
358368 hits += file . coverage . filter ( x => x > 0 ) . length ;
359369 }
360370 // Load data and save
361- let records = JSON . parse ( fs . readFileSync ( './db.json' , 'utf8' ) ) ;
371+ let records = JSON . parse ( fs . readFileSync ( dbFile , 'utf8' ) ) ;
362372 if ( ! Array . isArray ( records ) ) records = [ records ] ; // Ensure array
363373 for ( let o of records ) { if ( o . commit === job . data . sha ) { o . coverage = hits / ( hits + misses ) * 100 ; break ; } } // Add percentage
364374 // Save object
365- fs . writeFile ( './db.json' , JSON . stringify ( records ) , function ( err ) {
375+ fs . writeFile ( dbFile , JSON . stringify ( records ) , function ( err ) {
366376 if ( err ) { console . log ( err ) ; return ; }
367377 // If this test was to ascertain coverage, call comparison function
368378 if ( typeof job . data . coverage !== 'undefined' ) { compareCoverage ( job . data ) ; }
@@ -399,7 +409,7 @@ handler.on('push', async function (event) {
399409 state : 'pending' ,
400410 target_url : `${ process . env . WEBHOOK_PROXY_URL } /events/${ head_commit } ` , // fail
401411 description : 'Tests running' ,
402- context : ' continuous-integration/ZTEST'
412+ context : ` continuous-integration/${ process . env . USERDOMAIN } `
403413 } ) ;
404414 // Add a new test job to the queue
405415 queue . add ( {
@@ -438,7 +448,7 @@ handler.on('pull_request', async function (event) {
438448 state : 'pending' ,
439449 target_url : `${ process . env . WEBHOOK_PROXY_URL } /events/${ head_commit } ` , // fail
440450 description : 'Tests running' ,
441- context : ' continuous-integration/ZTEST'
451+ context : ` continuous-integration/${ process . env . USERDOMAIN } `
442452 } ) ;
443453 }
444454
@@ -454,7 +464,7 @@ handler.on('pull_request', async function (event) {
454464 state : 'pending' ,
455465 target_url : `${ process . env . WEBHOOK_PROXY_URL } /events/${ head_commit } ` , // fail
456466 description : 'Checking coverage' ,
457- context : ' coverage/ZTEST'
467+ context : ` coverage/${ process . env . USERDOMAIN } `
458468 } ) ;
459469 // Check coverage exists
460470 let data = {
0 commit comments