@@ -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 */
7979function 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
86149srv . 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 */
241316queue . 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
326459var server = srv . listen ( 3000 , function ( ) {
327460 var host = server . address ( ) . address
0 commit comments