@@ -324,20 +324,48 @@ function getRepoPath(name) {
324324function startJobTimer ( job , kill_children = false ) {
325325 const timeout = config . timeout || 8 * 60000 ; // How long to wait for the tests to run
326326 return setTimeout ( ( ) => {
327+ let log = _log . extend ( 'job_timer' ) ;
327328 console . log ( 'Max test time exceeded' ) ;
328329 log ( kill_children ? 'Killing all processes' : 'Ending test process' ) ;
329330 let pid = job . _child . pid ;
331+ log ( 'Killing process(es) for job #%g, pid = %d' , job . id , pid ) ;
330332 job . _child . kill ( ) ;
331- if ( kill_children ) {
332- kill ( pid ) ;
333- }
333+ if ( kill_children ) kill ( pid ) ;
334+ // Give the processes 1 minute before sending a more aggressive signal
335+ return setTimeout ( ( ) => {
336+ if ( job . _child && job . _child . exitCode == null ) {
337+ log ( 'Failed to kill job process(es); sending SIGKILL (job #%g, pid = %d)' , job . id , pid ) ;
338+ job . _child . kill ( 'SIGKILL' ) ;
339+ if ( kill_children ) kill ( pid , 'SIGKILL' ) ;
340+ }
341+ } , 60000 )
334342 } , timeout ) ;
335343}
336344
337345
346+ /**
347+ * Set dynamic env variables for node-coveralls.
348+ * NB: This does not support submodules.
349+ * @param {Object } job - The Job with an associated process in the data field.
350+ */
351+ function initCoveralls ( job ) {
352+ const debug = log . extend ( 'pipeline' ) ;
353+ debug ( 'Setting COVERALLS env variables' ) ;
354+ process . env . COVERALLS_SERVICE_JOB_ID = job . id ;
355+ const envMap = {
356+ 'COVERALLS_SERVICE_NAME' : job . data . context ,
357+ 'COVERALLS_GIT_COMMIT' : job . data . sha ,
358+ 'COVERALLS_GIT_BRANCH' : job . data . branch ,
359+ 'CI_PULL_REQUEST' : job . data . pull_number
360+ } ;
361+ for ( let key in envMap ) { // assign value or delete key
362+ if ( envMap [ key ] ) { process . env [ key ] = envMap [ key ] ; } else { delete process . env [ key ] ; }
363+ }
364+ }
365+
338366/**
339367 * Build task pipeline. Takes a list of scripts/functions and builds a promise chain.
340- * @param {Object } job - The path of the repository
368+ * @param {Object } job - The Job with an associated process in the data field.
341369 * @returns {Promise } - The job routine
342370 */
343371async function buildRoutine ( job ) {
@@ -365,6 +393,9 @@ async function buildRoutine(job) {
365393 } ) ;
366394 const ops = config . shell ? { 'shell' : config . shell } : { } ;
367395
396+ // If environment variable COVERALLS_REPO_TOKEN is not null, set dynamic variables
397+ if ( process . env . COVERALLS_REPO_TOKEN ) initCoveralls ( job ) ;
398+
368399 const init = ( ) => debug ( 'Executing pipeline for job #%g' , job . id ) ;
369400 const routine = tasks . reduce ( applyTask , Promise . resolve ( ) . then ( init ) ) ;
370401 return routine
@@ -411,6 +442,8 @@ async function buildRoutine(job) {
411442 clearTimeout ( timer ) ;
412443 } )
413444 . on ( 'close' , ( code , signal ) => {
445+ // FIXME Sometime close is not called after a timeout, maybe because
446+ // the IO streams are kept open by some process?
414447 const callback = ( code === 0 ) ? resolve : reject ;
415448 const proc = {
416449 code : code ,
@@ -419,6 +452,8 @@ async function buildRoutine(job) {
419452 stderr : stderr ,
420453 process : child
421454 } ;
455+ // Ensure there's an exitCode as the second kill timer checks for this
456+ if ( child . exitCode === null ) child . exitCode = - 1 ;
422457 callback ( proc ) ;
423458 } ) ;
424459 job . child = child ; // Assign the child process to the job
@@ -447,7 +482,7 @@ async function buildRoutine(job) {
447482 message = `${ errored . code } - Failed to spawn ${ file } ` ;
448483 }
449484 // Check if the process was killed (we'll assume by the test timeout callback)
450- } else if ( errored . process . killed || errored . signal === 'SIGTERM' ) {
485+ } else if ( errored . process . killed || [ 'SIGTERM' , 'SIGKILL' ] . includes ( errored . signal ) ) {
451486 message = `Tests stalled after ~${ ( config . timeout / 60000 ) . toFixed ( 0 ) } min` ;
452487 } else { // Error raised by process; dig through stdout for reason
453488 debug ( 'error from test function %s' , file ) ;
@@ -511,10 +546,10 @@ async function buildRoutine(job) {
511546 */
512547function computeCoverage ( job ) {
513548 if ( typeof job . data . coverage !== 'undefined' && job . data . coverage ) {
514- console . log ( 'Coverage already computed for job #' + job . id ) ;
549+ console . log ( 'Coverage already computed for job #%g' , job . id ) ;
515550 return ;
516551 }
517- console . log ( 'Updating coverage for job #' + job . id ) ;
552+ console . log ( 'Updating coverage for job #%g' , job . id ) ;
518553 const xmlPath = path . join ( config . dataPath , 'reports' , job . data . sha , 'CoverageResults.xml' ) ;
519554 const modules = listSubmodules ( process . env . REPO_PATH ) ;
520555 return Coverage ( xmlPath , job . data . repo , job . data . sha , modules ) . then ( obj => {
@@ -719,5 +754,5 @@ module.exports = {
719754 ensureArray, loadTestRecords, compareCoverage, computeCoverage, getBadgeData, log, shortID,
720755 openTunnel, APIError, queue, partial, startJobTimer, updateJobFromRecord, shortCircuit, isSHA,
721756 fullpath, strToBool, saveTestRecords, listSubmodules, getRepoPath, addParam, context2routine,
722- buildRoutine
757+ buildRoutine, initCoveralls
723758} ;
0 commit comments