travis-after-all
Advanced tools
Comparing version 1.3.0 to 1.4.0
@@ -0,1 +1,7 @@ | ||
### 1.4.0 (September 10, 2015) | ||
* Make `travis-after-all` take partial decisions | ||
[[#3](https://github.com/alrra/travis-after-all/issues/3), | ||
[2a656d9](https://github.com/alrra/travis-after-all/commit/2a656d9390543ceba776a2c790805bbfdad68e2b)]. | ||
### 1.3.0 (August 12, 2015) | ||
@@ -2,0 +8,0 @@ |
@@ -6,48 +6,72 @@ var crypto = require('crypto'); | ||
// Travis CI set environment variables | ||
// http://docs.travis-ci.com/user/ci-environment/#Environment-variables | ||
var travis = { | ||
var TRAVIS_BUILD_ID = parseInt(process.env.TRAVIS_BUILD_ID, 10); | ||
var TRAVIS_CURRENT_JOB_ID = parseInt(process.env.TRAVIS_JOB_ID, 10); | ||
var TRAVIS_CURRENT_JOB_TEST_RESULT = parseInt(process.env.TRAVIS_TEST_RESULT, 10); | ||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | ||
// Travis CI set environment variables | ||
// http://docs.travis-ci.com/user/ci-environment/#Environment-variables | ||
// Travis CI API URLs | ||
CURRENT_JOB_ID: parseInt(process.env.TRAVIS_JOB_ID, 10), | ||
CURRENT_JOB_TEST_RESULT: parseInt(process.env.TRAVIS_TEST_RESULT, 10), | ||
var TRAVIS_API_BUILD_URL = 'https://api.travis-ci.org/builds/' + TRAVIS_BUILD_ID; | ||
var TRAVIS_API_JOBS_URL = 'https://api.travis-ci.org/jobs/'; | ||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | ||
// Travis CI API URLs | ||
// http://docs.travis-ci.com/api/ | ||
// Other | ||
API_BUILD_URL: 'https://api.travis-ci.org/builds/' + parseInt(process.env.TRAVIS_BUILD_ID, 10), | ||
API_JOBS_URL: 'https://api.travis-ci.org/jobs/' | ||
var CHECK_INTERVAL_LIMIT = 60000; | ||
var checkInterval = 5000; | ||
var KEYS = { | ||
'failure': 'travis-after-all: failed', | ||
'success': 'travis-after-all: succeeded' | ||
}; | ||
var LOG_PREFIX='[travis-after-all]'; | ||
var configs = { | ||
var returnCode; | ||
callback: undefined, | ||
CHECK_INTERVAL_LIMIT: 60000, | ||
checkInterval: 1000, | ||
keys: { | ||
'FAILURE': 'travis-after-all: failed', | ||
'SUCCESS': 'travis-after-all: succeeded' | ||
}, | ||
LOG_PREFIX: '[travis-after-all]', | ||
}; | ||
// --------------------------------------------------------------------- | ||
function allJobsAreDone(jobs) { | ||
return jobs.every(function (job) { | ||
return job.result !== null; | ||
}); | ||
} | ||
function allJobsPassed(jobs) { | ||
return jobs.every(function (job) { | ||
return jobPassed(job); | ||
// A job passed if it either succeeded or it failed but | ||
// it was allowed to fail (has `allow_failure` set to true). | ||
// | ||
// http://docs.travis-ci.com/user/customizing-the-build/#Rows-that-are-Allowed-to-Fail | ||
return job.result === 0 || ( job.result === 1 && job.allow_failure === true ); | ||
}); | ||
} | ||
function createReturnFunction(callback) { | ||
function endJob(code, error) { | ||
if ( typeof callback === 'function' ) { | ||
returnCode = callback; | ||
if ( error !== undefined ) { | ||
log(error.message); | ||
} | ||
if ( code === 2 ) { | ||
log('Some other job was assigned to do the task'); | ||
} | ||
if ( typeof configs.callback === 'function' ) { | ||
configs.callback(code, error); | ||
} else { | ||
returnCode = function (code, error) { | ||
process.exit(code); | ||
}; | ||
process.exit(code); | ||
} | ||
@@ -61,15 +85,21 @@ | ||
function getFirstSuccessfulJob(jobs) { | ||
function getBuildData() { | ||
getJSON(travis.API_BUILD_URL, function (data) { | ||
var i = 0; | ||
var numberOfJobs = jobs.length; | ||
var jobs = data.matrix; | ||
for ( i = 0; i < numberOfJobs; i++ ) { | ||
if ( jobs[i].result === 0 ) { | ||
return jobs[i]; | ||
} | ||
} | ||
// For the current job the result is already | ||
// known, so there is no need to figure that out. | ||
return undefined; | ||
jobs.some(function (job) { | ||
if ( job.id === travis.CURRENT_JOB_ID ) { | ||
job.result = travis.CURRENT_JOB_TEST_RESULT; | ||
return true; | ||
} | ||
return false; | ||
}); | ||
handleJob(jobs); | ||
}); | ||
} | ||
@@ -90,8 +120,14 @@ | ||
response.on('end', function () { | ||
callback(parseJSON(body)); | ||
try { | ||
callback(JSON.parse(body)); | ||
} catch (error) { | ||
error.message = 'Failed to parse JSON from "' + url +'".\nError: ' + error.message; | ||
endJob(3, error); | ||
} | ||
}); | ||
}).on('error', function (error) { | ||
log('Error: ' + e.message); | ||
returnCode(2, error); | ||
endJob(3, error); | ||
}); | ||
@@ -101,17 +137,19 @@ | ||
function getJobNumbersOfUndoneJobs(jobs) { | ||
function getJob(jobs, id) { | ||
var undoneJobs = []; | ||
var result; | ||
jobs.forEach(function (job) { | ||
if ( job.result === null ) { | ||
undoneJobs.push(job.number); | ||
jobs.some(function (job) { | ||
if ( job.id === id ) { | ||
result = job; | ||
return true; | ||
} | ||
return false; | ||
}); | ||
return undoneJobs; | ||
return result; | ||
} | ||
function getResults(jobList) { | ||
function getJobData(jobList) { | ||
@@ -126,22 +164,10 @@ var currentJob; | ||
if ( currentJob.id === travis.CURRENT_JOB_ID || | ||
currentJob.finished_at !== null && currentJob.result !== null ) { | ||
// The `result` of a job is only set after the job is finished, | ||
// so, if the value of `result` is different from `null`, it | ||
// means that it was extracted from its log, and thus, there is | ||
// no need to check the log again | ||
getJobData(jobList); | ||
if ( currentJob.result !== null ) { | ||
getResults(jobList); | ||
// Also, there is no need to check the log for the current job | ||
} else if ( currentJob.id === TRAVIS_CURRENT_JOB_ID ) { | ||
currentJob.result = TRAVIS_CURRENT_JOB_TEST_RESULT; | ||
getResults(jobList); | ||
// In all other cases, check the job's log for the result | ||
} else { | ||
getJSON(TRAVIS_API_JOBS_URL + currentJob.id, function (data) { | ||
getJSON(travis.API_JOBS_URL + currentJob.id, function (data) { | ||
@@ -151,12 +177,14 @@ var jobLog = data.log; | ||
// If written, get the result of the job from | ||
// the special token written within the job's log | ||
// the special token written within its log. | ||
if ( jobLog.indexOf(generateToken(currentJob.id, KEYS.success)) !== -1 ) { | ||
if ( jobLog.indexOf(generateToken(currentJob.id, configs.keys.SUCCESS)) !== -1 ) { | ||
currentJob.result = 0; | ||
} else if ( jobLog.indexOf(generateToken(currentJob.id, KEYS.failure)) !== -1 ) { | ||
} else if ( jobLog.indexOf(generateToken(currentJob.id, configs.keys.FAILURE)) !== -1 ) { | ||
currentJob.result = 1; | ||
} | ||
getResults(jobList); | ||
currentJob.finished_at = data.finished_at; | ||
getJobData(jobList); | ||
}); | ||
@@ -167,50 +195,42 @@ | ||
} else { | ||
handleJob(jobList.jobs); | ||
} | ||
undoneJobs = getJobNumbersOfUndoneJobs(jobList.jobs); | ||
} | ||
function getJobNumbersOfUndoneJobs(jobs) { | ||
// If there are jobs that are not yet done, wait | ||
// a bit, and then check again for their result | ||
var undoneJobs = []; | ||
if ( undoneJobs.length !== 0 ) { | ||
jobs.forEach(function (job) { | ||
if ( job.finished_at === null ) { | ||
undoneJobs.push(job.number); | ||
} | ||
}); | ||
log('Waiting for ' + undoneJobs.join(', ').replace(/,(?!.*,)/, ' and') + ' to be done...'); | ||
return undoneJobs; | ||
jobList.index = 0; | ||
} | ||
// If the jobs take longer to be done, gradually increase | ||
// the check interval time up to the specified limit | ||
function getPreviousJobs(jobs, id) { | ||
checkInterval = ( checkInterval * 2 > CHECK_INTERVAL_LIMIT ? | ||
CHECK_INTERVAL_LIMIT : checkInterval * 2 ); | ||
var result = []; | ||
setTimeout(function () { | ||
getResults(jobList); | ||
}, checkInterval); | ||
jobs.some(function (job) { | ||
// If all jobs are done, make this script | ||
// return with the appropriate exit code | ||
} else { | ||
returnWithAppropriateCode(jobList.jobs); | ||
if ( job.id === id ) { | ||
return true; | ||
} | ||
} | ||
result.push(job); | ||
return false; | ||
} | ||
}); | ||
function jobPassed(job) { | ||
return result; | ||
// A job passed if it either succeeded or it has failed but | ||
// it was allowed to fail (has `allow_failure` set to true) | ||
// | ||
// http://docs.travis-ci.com/user/build-configuration/#Rows-That-are-Allowed-To-Fail | ||
return job.result === 0 || ( job.result === 1 && job.allow_failure === true ); | ||
} | ||
function log(msg) { | ||
console.log('%s %s', LOG_PREFIX, msg); | ||
console.log('%s %s', configs.LOG_PREFIX, msg); | ||
} | ||
@@ -224,11 +244,11 @@ | ||
// Write a special token generated based on the result of | ||
// the job so that the chance of that text existing in the | ||
// log is minimal / non-existent. | ||
// Create a special token based on the result of the job (this | ||
// is done so that the chance of that text existing in the log | ||
// is minimal / non-existent). | ||
if ( TRAVIS_CURRENT_JOB_TEST_RESULT === 0 ) { | ||
token = generateToken(TRAVIS_CURRENT_JOB_ID, KEYS.success); | ||
if ( travis.CURRENT_JOB_TEST_RESULT === 0 ) { | ||
token = generateToken(travis.CURRENT_JOB_ID, configs.keys.SUCCESS); | ||
msg += 'Job succeeded (' + token + ')'; | ||
} else { | ||
token = generateToken(TRAVIS_CURRENT_JOB_ID, KEYS.failure); | ||
token = generateToken(travis.CURRENT_JOB_ID, configs.keys.FAILURE); | ||
msg += 'Job failed (' + token + ')'; | ||
@@ -242,3 +262,3 @@ } | ||
// e.g.: the user uses multiple scripts that include this | ||
// script) | ||
// script). | ||
@@ -251,60 +271,248 @@ if ( job.log.indexOf(token) === -1 ) { | ||
function parseJSON(data) { | ||
function removeJob(jobs, id) { | ||
var json = ''; | ||
var result = []; | ||
try { | ||
json = JSON.parse(data); | ||
} catch (error) { | ||
log('Failed to parse JSON (' + error + ')'); | ||
log('Data: "' + data + '"'); | ||
returnCode(2, error); | ||
jobs.forEach(function (job) { | ||
if ( job.id !== id ) { | ||
result.push(job); | ||
} | ||
}); | ||
return result; | ||
} | ||
function thereIsAFailingJob(jobs) { | ||
return jobs.some(function (job) { | ||
return job.result === 1 && job.allow_failure === false; | ||
}); | ||
} | ||
function thereIsASuccessfulJob(jobs) { | ||
return jobs.some(function (job) { | ||
return job.result === 0; | ||
}); | ||
} | ||
function waitThenGetJobData(jobs) { | ||
var jobList = { | ||
index: 0, | ||
jobs: jobs | ||
}; | ||
var undoneJobs = getJobNumbersOfUndoneJobs(removeJob(jobList.jobs, travis.CURRENT_JOB_ID)); | ||
if ( undoneJobs.length !== 0 ) { | ||
log('Waiting for ' + undoneJobs.join(', ').replace(/,(?!.*,)/, ' and') + ' to be done...'); | ||
} | ||
return json; | ||
// If the jobs take longer to be done, gradually increase | ||
// the check interval time up to the specified limit. | ||
configs.checkInterval = ( configs.checkInterval * 2 > configs.CHECK_INTERVAL_LIMIT ? | ||
configs.CHECK_INTERVAL_LIMIT : configs.checkInterval * 2 ); | ||
setTimeout(function () { | ||
getJobData(jobList); | ||
}, configs.checkInterval); | ||
} | ||
function returnWithAppropriateCode(jobs) { | ||
// --------------------------------------------------------------------- | ||
// Convention for exit codes | ||
// | ||
// 0 - if the current job is the one assigned to run the | ||
// code if the build succeeded | ||
// | ||
// 1 - if the current job is the one assigned to run the | ||
// code if the build failed | ||
// | ||
// 2 - none of the above | ||
// | ||
// Conventions: | ||
// | ||
// * For builds that failed, or for builds that succeeded but had | ||
// all their jobs failed but being allowed to fail), the first | ||
// job will be assign to run the user defined fail tasks. | ||
// | ||
// * For builds that succeeded, the first successful job will be | ||
// assign to run the user defined success tasks. | ||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | ||
function handleJob(jobs) { | ||
var currentJob = getJob(jobs, travis.CURRENT_JOB_ID); | ||
if ( currentJob.result === 1 ) { | ||
if ( currentJob.allow_failure === false ) { | ||
handleFailingJob(currentJob, jobs); | ||
} else { | ||
handleFailingJobAllowedToFail(currentJob, jobs); | ||
} | ||
} else { | ||
handleSuccessfulJob(currentJob, jobs); | ||
} | ||
} | ||
function handleFailingJob(currentJob, jobs) { | ||
// Since the current job failed, the build also failed, therefore, | ||
// the current job can be ended with the appropriate code. | ||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | ||
var firstJob = jobs[0]; | ||
var firstSuccesfulJob = getFirstSuccessfulJob(jobs); | ||
if ( allJobsPassed(jobs) && | ||
// If the current job is the first one in the build, assign it | ||
// to run the user defined fail tasks. | ||
// Even if all jobs passed we still need to check if there | ||
// is at least one successful job (this guards against the | ||
// rare case where all jobs are allowed to fail and they do | ||
// fail) | ||
if ( currentJob.id === firstJob.id ) { | ||
endJob(1); | ||
firstSuccesfulJob !== undefined ) { | ||
// Otherwise, don't assign it to do anything as the first job | ||
// in the build will be assign to run the user defined fail tasks. | ||
if ( firstSuccesfulJob.id !== TRAVIS_CURRENT_JOB_ID ) { | ||
log('Job ' + firstSuccesfulJob.number + ' was assigned to do the "after success" task'); | ||
returnCode(2); | ||
} else { | ||
endJob(2); | ||
} | ||
} | ||
function handleFailingJobAllowedToFail(currentJob, jobs) { | ||
// Since the current job failed, but it was allowed to fail, | ||
// the status of the build is unknown, therefore, the current | ||
// job can only be ended with the appropriate code if there is | ||
// enough information about the status of the other jobs. | ||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | ||
var firstJob = jobs[0]; | ||
var otherJobs = removeJob(jobs, currentJob.id); | ||
if ( currentJob.id === firstJob.id ) { | ||
// If there is at least one job that failed, the build | ||
// failed, therefore, assign the current job to run the | ||
// user defined fail tasks. | ||
if ( thereIsAFailingJob(otherJobs) ) { | ||
endJob(1); | ||
// Otherwise | ||
} else { | ||
returnCode(0); | ||
// Note: By this point it is known that currently there | ||
// aren't any jobs that were not allowed to fail and did | ||
// fail. | ||
if ( allJobsAreDone(otherJobs) ) { | ||
// If there is a successful job, that job will be | ||
// assigned to run the user defined success tasks, | ||
// therefore, the current job doesn't need to be | ||
// assigned to do anything. | ||
if ( thereIsASuccessfulJob(otherJobs) ) { | ||
endJob(2); | ||
// Otherwise, there are only jobs that where allowed | ||
// to fail and all of them fail, therefore, the build | ||
// failed, so assign the current job to run the user | ||
// defined fail tasks. | ||
} else { | ||
endJob(1); | ||
} | ||
// If this point is reached, it means a decision cannot | ||
// be made because there isn't enough information. So, we | ||
// need to wait a bit, check the status of the other jobs, | ||
// and try again. | ||
} else { | ||
waitThenGetJobData(jobs); | ||
} | ||
} | ||
// If the current job is not the first job, no matter what the | ||
// status of build will be, this job won't be assign to do any | ||
// tasks (if the build failed, the first job will be assign to | ||
// do the fail tasks, and if the build succeeded, the first | ||
// successful job will be assigned to do the success tasks) | ||
} else { | ||
endJob(2); | ||
} | ||
if ( firstJob.id !== TRAVIS_CURRENT_JOB_ID ) { | ||
log('Job ' + firstJob.number + ' was assigned to do the "after failure" task'); | ||
returnCode(2); | ||
} | ||
function handleSuccessfulJob(currentJob, jobs) { | ||
// Since the current job is a successful job, the status of the | ||
// build is unknown, therefore, the current job can only be ended | ||
// with the appropriate code if there is enough information about | ||
// the status of the other jobs | ||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | ||
var firstJob = jobs[0]; | ||
var otherJobs = removeJob(jobs, currentJob.id); | ||
var previousJobs = getPreviousJobs(jobs, currentJob.id); | ||
// If a job failed, it means the build failed, therefore, | ||
if ( thereIsAFailingJob(otherJobs) ) { | ||
// If the current job is the first one in the build, assign | ||
// it to run the user defined fail tasks. | ||
if ( currentJob.id === firstJob.id ) { | ||
endJob(1); | ||
// Otherwise, don't assign it to do anything as the first | ||
// job in the build will be assign to run the user defined | ||
// fail tasks. | ||
} else { | ||
returnCode(1); | ||
endJob(2); | ||
} | ||
} else { | ||
// If there is a previous successful job, no matter what the | ||
// status of the build will be, this job won't be assign to do | ||
// any tasks (if the build succeeded a previous successful job | ||
// will be assigned to do the success tasks, and if the build | ||
// fails, the first job in the build will be assign to do the | ||
// fail tasks). | ||
if ( thereIsASuccessfulJob(previousJobs) ) { | ||
endJob(2); | ||
} else { | ||
// Note: By this point it is known that currently there | ||
// aren't any jobs that were not allowed to fail and did | ||
// fail, or any previous successful jobs. | ||
// If all jobs are done, it means the current job | ||
// is the first successful job in a successful build, | ||
// therefore, assign it to run the success tasks. | ||
if ( allJobsAreDone(otherJobs) ) { | ||
endJob(0); | ||
// If this point is reached, it means a decision cannot | ||
// be made because there isn't enough information. So, we | ||
// need to wait a bit, check the status of the other jobs, | ||
// and try again. | ||
} else { | ||
waitThenGetJobData(jobs); | ||
} | ||
} | ||
} | ||
@@ -318,23 +526,18 @@ | ||
createReturnFunction(callback); | ||
configs.callback = callback; | ||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | ||
getJSON(travis.API_JOBS_URL + travis.CURRENT_JOB_ID, function (data) { | ||
// Write the result of this job in its log if it isn't written | ||
// already (this needs to be done because there isn't any other | ||
// way for the other jobs to know the result of this job without | ||
// it actually be finished) | ||
// Write the result of current job in its log if it isn't | ||
// written already (this needs to be done because there isn't | ||
// any other way for the other jobs to know the result of the | ||
// current job without it actually be finished). | ||
getJSON(TRAVIS_API_JOBS_URL + TRAVIS_CURRENT_JOB_ID, logJobResult); | ||
logJobResult(data); | ||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | ||
// Get the list of jobs for this build and start checking for | ||
// their results. | ||
// Get the list of jobs for this build, and start checking for | ||
// their results | ||
getBuildData(); | ||
getJSON(TRAVIS_API_BUILD_URL, function (data) { | ||
getResults({ | ||
index: 0, | ||
jobs: data.matrix | ||
}); | ||
}); | ||
@@ -346,13 +549,16 @@ | ||
// Based on how this script is called, invoke main differentlly: | ||
// | ||
// * if this script is called directly | ||
// | ||
if ( require.main === module || module.parent === undefined ) { | ||
// Based on how this script is called, | ||
// invoke `main` differentlly. | ||
// 1) If this script is called directly: | ||
if ( require.main === module || | ||
module.parent === undefined ) { | ||
main(); | ||
// | ||
// * if required as a module | ||
// | ||
// 2) If it's required as a module: | ||
} else { | ||
module.exports = main; | ||
} |
@@ -27,3 +27,4 @@ { | ||
"glob": "^5.0.14", | ||
"mocha": "^2.2.5" | ||
"tap": "^1.3.2", | ||
"tap-mocha-reporter": "0.0.16" | ||
}, | ||
@@ -40,5 +41,6 @@ "homepage": "https://github.com/alrra/travis-after-all#readme", | ||
"scripts": { | ||
"test": "mocha --compilers js:babel/register --delay --reporter spec --slow 15m --timeout 30m test/main.js" | ||
"pretest": "rm -rf dist_test && babel test --out-dir dist_test", | ||
"test": "node dist_test/main.js | tap-mocha-reporter spec" | ||
}, | ||
"version": "1.3.0" | ||
"version": "1.4.0" | ||
} |
# travis-after-all | ||
[![Build Status](https://travis-ci.org/alrra/travis-after-all.svg?branch=master)](https://travis-ci.org/alrra/travis-after-all) | ||
@@ -36,3 +35,3 @@ [![devDependency Status](https://david-dm.org/alrra/travis-after-all/dev-status.svg)](https://david-dm.org/alrra/travis-after-all#info=devDependencies) | ||
* other - job was not assigned to do anything | ||
* `2` - the job that gets this exit code was not assigned to do anything | ||
@@ -134,3 +133,3 @@ -- | ||
curl -sSL https://raw.githubusercontent.com/alrra/travis-after-all/1.3.0/lib/travis-after-all.js | node | ||
curl -sSL https://raw.githubusercontent.com/alrra/travis-after-all/1.4.0/lib/travis-after-all.js | node | ||
exitCode=$? | ||
@@ -137,0 +136,0 @@ |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
23772
8
368
6
159
2