next-update
Version:
Tests if module's dependencies can be updated to the newer version without breaking the tests
347 lines (302 loc) • 11.5 kB
JavaScript
var la = require('lazy-ass')
var check = require('check-more-types')
var verify = check.verify
var q = require('q')
var _ = require('lodash')
var semver = require('semver')
var quote = require('quote')
var installModule = require('./module-install')
var reportSuccess = require('./report').reportSuccess
var reportFailure = require('./report').reportFailure
const {getTestCommand} = require('./utils')
const path = require('path')
const debug = require('debug')('next-update')
var stats = require('./stats')
var cleanVersions = require('./registry').cleanVersions
check.verify.fn(cleanVersions, 'cleanVersions should be a function')
var revertModules = require('./revert')
check.verify.fn(revertModules, 'revert is not a function, but ' +
JSON.stringify(revertModules))
var npmTest = require('./npm-test').test
var execTest = require('./exec-test')
var report = require('./report-available')
var filterAllowed = require('./filter-allowed-updates')
// expect array of objects, each {name, versions (Array) }
// returns promise
function testModulesVersions (options, available) {
verify.object(options, 'missing options')
verify.array(available, 'expected array of available modules')
var cleaned = cleanVersions(options.modules)
// console.log('cleaned', cleaned);
// var listed = _.zipObject(cleaned);
var names = _.pluck(cleaned, 'name')
var listed = _.zipObject(names, cleaned)
/*
console.log('testing module versions');
console.log('current versions', listed);
console.log('options', options);
console.log('available', available);
*/
var allowed = filterAllowed(listed, available, options)
la(check.array(allowed), 'could not filter allowed updates', listed, available, options)
if (available.length && !allowed.length) {
console.log('No updates allowed using option', quote(options.allow || options.allowed))
console.log(available.length + ' available updates filtered')
return q(listed)
}
// console.log('allowed', allowed);
return q.when(report(allowed, listed, options))
.then(function testInstalls () {
// console.log('testing installs');
if (options.all) {
var install = installAll(allowed, options)
console.assert(install, 'could not get install all promise')
var test = testPromise(options, options.command)
console.assert(test, 'could not get test promise for command', options.command)
// console.dir(listed);
// console.dir(options.modules);
var installThenTest = install.then(test)
if (options.keep) {
return installThenTest
}
var revert = revertModules.bind(null, listed)
console.assert(revert, 'could not get revert promise')
return installThenTest.then(revert)
}
return installEachTestRevert(listed, allowed,
options.command, options.color, options.keep, options.tldr)
})
}
// returns promise, does not revert
function installAll (available, options) {
verify.array(available, 'expected array')
var installFunctions = available.map(function (nameVersions) {
var name = nameVersions.name
var version = nameVersions.versions[0]
verify.string(name, 'missing module name from ' +
JSON.stringify(nameVersions))
verify.string(version, 'missing module version from ' +
JSON.stringify(nameVersions))
var installOptions = {
name: name,
version: version,
keep: options.keep,
tldr: options.tldr
}
var installFunction = installModule.bind(null, installOptions)
return installFunction
})
var installAllPromise = installFunctions.reduce(q.when, q())
return installAllPromise
}
function installEachTestRevert (listed, available, command, color, keep, tldr) {
verify.object(listed, 'expected listed object')
verify.array(available, 'expected array')
const packageFilename = path.resolve('./package.json')
const getCommand = _.partial(getTestCommand, packageFilename)
var checkModulesFunctions = available.map(function (nameVersion) {
var name = nameVersion.name
la(check.unemptyString(name), 'missing name', nameVersion)
var currentVersion = listed[name].version
la(check.string(currentVersion), 'cannot find current version for', name,
'among current dependencies', listed)
var installOptions = {
name: name,
version: currentVersion,
keep: keep,
tldr: tldr
}
var revertFunction = installModule.bind(null, installOptions)
const testCommand = getCommand(name) || command
debug('module %s should use test command "%s"', name, testCommand)
var checkModuleFunction = testModuleVersions.bind(null, {
moduleVersions: nameVersion,
revertFunction: revertFunction,
command: testCommand,
color: color,
currentVersion: currentVersion,
keep: keep,
tldr: tldr
})
return checkModuleFunction
})
var checkAllPromise = checkModulesFunctions.reduce(q.when, q())
return checkAllPromise
}
// test particular dependency with multiple versions
// returns promise
function testModuleVersions (options, results) {
verify.object(options, 'missing options')
var nameVersions = options.moduleVersions
var restoreVersionFunc = options.revertFunction
var name = nameVersions.name
var versions = nameVersions.versions
verify.string(name, 'expected name string')
verify.array(versions, 'expected versions array')
results = results || []
verify.array(results, 'expected results array')
if (!semver.valid(options.currentVersion)) {
throw new Error('do not have current version for ' + name)
}
var deferred = q.defer()
var checkPromises = versions.map(function (version) {
return testModuleVersion.bind(null, {
name: name,
version: version,
command: options.command,
color: options.color,
currentVersion: options.currentVersion,
tldr: options.tldr
})
})
var checkAllPromise = checkPromises.reduce(q.when, q())
if (options.keep) {
debug('keep working updates for %s', name)
checkAllPromise = checkAllPromise.then(function (result) {
verify.array(result, 'expected array of results', result)
var lastSuccess = _.last(_.filter(result, { works: true }))
if (lastSuccess) {
console.log('keeping last working version', lastSuccess.name + '@' + lastSuccess.version)
return installModule({
name: lastSuccess.name,
version: lastSuccess.version,
keep: true,
tldr: options.tldr
}, result)
} else {
return restoreVersionFunc().then(function () {
// console.log('returning result after reverting', result);
return q(result)
})
}
})
} else {
checkAllPromise = checkAllPromise
.then(restoreVersionFunc, (err) => {
console.error('Could not check all versions')
console.error(err)
throw err
})
}
checkAllPromise
.then(function (result) {
debug('got result')
debug(result)
check.verify.array(result, 'could not get result array')
results.push(result)
deferred.resolve(results)
}, function (error) {
console.error('could not check', nameVersions, error)
deferred.reject(error)
})
return deferred.promise
}
var logLine = (function formLine () {
var n = process.stdout.isTTY ? process.stdout.columns : 40
n = n || 40
verify.positiveNumber(n, 'expected to get terminal width, got ' + n)
var k
var str = ''
for (k = 0; k < n; k += 1) {
str += '-'
}
return function () {
console.log(str)
}
}())
// checks specific module@version
// returns promise
function testModuleVersion (options, results) {
verify.object(options, 'missing test module options')
verify.string(options.name, 'missing module name')
verify.string(options.version, 'missing version string')
verify.unemptyString(options.currentVersion, 'missing current version')
if (options.command) {
verify.string(options.command, 'expected command string')
}
// console.log('options', options);
results = results || []
verify.array(results, 'missing previous results array')
var nameVersion = options.name + '@' + options.version
if (!options.tldr) {
console.log('\ntesting', nameVersion)
}
var result = {
name: options.name,
version: options.version,
from: options.currentVersion,
works: true
}
var test = testPromise(options, options.command)
console.assert(test, 'could not get test promise for command', options.command)
var deferred = q.defer()
var getSuccess = stats.getSuccessStats({
name: options.name,
from: options.currentVersion,
to: options.version
})
getSuccess
.then(stats.printStats.bind(null, options), function () {
console.log('could not get update stats', options.name)
})
.then(function () {
return installModule({
name: options.name,
version: options.version,
keep: false,
tldr: options.tldr
})
})
.then(test)
.then(function () {
reportSuccess(nameVersion + ' works', options.color)
stats.sendUpdateResult({
name: options.name,
from: options.currentVersion,
to: options.version,
success: true
})
results.push(result)
deferred.resolve(results)
}, function (error) {
reportFailure(nameVersion + ' tests failed :(', options.color)
debug('sending stats results')
stats.sendUpdateResult({
name: options.name,
from: options.currentVersion,
to: options.version,
success: false
})
debug('checking error code', error.code)
verify.number(error.code, 'expected code in error ' +
JSON.stringify(error, null, 2))
var horizontalLine = options.tldr ? _.noop : logLine
horizontalLine()
if (!options.tldr) {
console.error('test finished with exit code', error.code)
verify.string(error.errors, 'expected errors string in error ' +
JSON.stringify(error, null, 2))
console.error(error.errors)
}
horizontalLine()
result.works = false
results.push(result)
deferred.resolve(results)
})
return deferred.promise
}
function testPromise (options, command) {
var testFunction = npmTest.bind(null, options)
if (command) {
verify.unemptyString(command, 'expected string command, not ' + command)
testFunction = execTest.bind(null, options, command)
} else {
debug('missing test command')
}
return testFunction
}
module.exports = {
testModulesVersions: testModulesVersions,
testModuleVersion: testModuleVersion,
testPromise: testPromise
}